Is there a way to use UDP Hole Punching with Gstreamer?

142 Views Asked by At

I want to build a P2P video stream between two machines, and am streaming video in SRT with Gstreamer. To accomplish this, I want to use UDP hole punching to establish a connection for the stream. The setup I have works when both machines are on the same LAN, but it fails when they are on different ones.

On the receiver side (which is just a slightly modified version of this code, I currently have:

#Before this a connection to the server is made and peer information is received.

print('\ngot peer')
print('  ip:          {}'.format(ip))
print('  source port: {}'.format(sport))
print('  dest port:   {}\n'.format(dport))

# punch hole
# equiv: echo 'punch hole' | nc -u -p 50001 x.x.x.x 50002
print('punching hole')

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', sport))
sock.sendto(b'0', (ip, dport))

sock.close() # Close socket so Gstreamer can listen on 0.0.0.0

print('ready to receive stream\n')

print('Stream listening on port ' + str(sport))

# receive stream
# open up gstreamer, have it listen on source port
os.system("rxp.bat " + str(sport))

With 'rxp.bat' being the batchfile that runs the relevant Gstreamer command: gst-launch-1.0 -v srtsrc uri="srt://0.0.0.0:%1?mode=listener" ! decodebin ! autovideosink

The relevant code on the sender:

# punch hole
print('punching hole')

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', sport))
sock.sendto(b'0', (ip, dport))
#sock.close()

#time.sleep(2)
print('ready to send video stream to ' + str(ip)+ ' at port ' +str(sport))
time.sleep(2)
subprocess.call(shlex.split('./stream.sh ' + str(ip) + " "+ str(sport)))

exit(0)

With stream.sh being the script that launches Gstreamer on the sender:

#!/bin/bash
gst-launch-1.0 -v  filesrc location=test.mp4 \
    ! qtdemux \
    ! h264parse \
    ! avdec_h264 \
    ! x264enc tune=zerolatency \
    ! video/x-h264, profile=high \
    ! mpegtsmux \
    ! srtsink uri="srt://$1:$2?mode=rendez-vous"

The Gstreamer commands are based off of this guide.

This setup is able to with both machines on the same LAN. However, when I connect the receiver to a VPN to see how the program behaves on a wider network, the sender is unable to establish an SRT connection with the receiver.

The error messages are as follows for Gstreamer:

ERROR: from element /GstPipeline:pipeline0/GstSRTSink:srtsink0: Could not write to resource
Additional debug info:
../ext/srt/gstsrtobject.c(1656): gst_srt_object_send_headers (): /GstPipeline:pipeline0/GstSRTSink:srtsink0: Connection does not exist
ERROR: from element /GstPipeline:pipeline0/GstSRTSink:srtsink0: Failed to write to SRT socket: Unknown error
Additional debug info:
../ext/srt/gstsrtsink.c(205): gst_srt_sink_render (): /GstPipeline:pipeline0/GstSRTSink:srtsink0
Execution ended after 0:00:02.989791369

I am thinking that the code actually doesn't work as intended on a LAN, as the sender is able to resolve the host name and port because they're both on the same network. Because sock in the receiver is using 0.0.0.0 to punch the hole I am thinking that the hole closes because sock closes shortly after. However keeping sock open causes Gstreamer to be unable to bind to 0.0.0.0 to receive the stream. Would there be a way to have Gstreamer 'hijack' sock and bind to it, or could Gstreamer receive on a different local IP/Port and still receive the stream via the holepunch?

0

There are 0 best solutions below