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?