How does one consume a plain trainsport via gstreamer from within a NAT?

Hi All,

I’m working on a VR ham radio interface to operate ham radio with others in social VR. I’m using mediasoup as the SFU for all of the audio from the radio to consumers and for the stream back to the radio to be transmitted and this works incredibly well btw with extremely low latency, so kudos and thanks to the developers!

I am struggling with one part still however… I was easily able to use gstreamer and a plain transport to produce audio from the radio to consumers and this works flawlessly. Where I’m struggling is how to consume audio from mediasoup back to the radio via gstreamer.

In the case of producing, it is gstreamer (which is located behind a NAT) that is pushing the audio via RTP to mediasoup’s local IP and rtp port, but in the case of consuming I’m struggling to understand what this connection looks like and how the RTP traffic can make it back to gstreamer as mediasoup does not appear to initiate anything and the gstreamer is behind a NAT.

I assume either gstreamer needs to initiate the connection first to punch through the NAT so that the RTP stream can be received, or some other kind of NAT transversal/port forwarding would need be in place first so that gstreamer can begin to receive the RTP packets from mediasoup.

Does anyone have any advice or gstreamer recipes I could look at that could help me to understand what needs to be done to consume from a plain transport using gstreamer?


You should consider checking out the mediasoup-demo on github, I believe it has a gstreamer demo. If not I know some of the other projects you can find, do detail and list the examples.


I was able to solve this myself by performing a UDP hole punch for both the rtp and the rtcp ports prior to launching gstreamer and after createPlainTransport. This involves sending empty packets from the device behind the NAT to the mediasoup transport matching the source and destination ports precisely as those obtained from the createPlainTransport and the ports you intend to use in the call to .connect().

Here are is my gstreamer recipe for those interested. If anyone needs details on how to perform a UDP hole punch, I’d be happy to explain it.

gst-launch-1.0 \
  rtpbin name=rtpbin udpsrc port={{incomingRtpPort}} caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS" \
  ! rtpbin.recv_rtp_sink_0 udpsrc port={{incomingRtcpPort}} caps="application/x-rtcp" \
  ! rtpbin.recv_rtcp_sink_0 rtpbin. \
  ! rtpopusdepay \
  ! opusdec \
  ! alsasink provide-clock=true buffer-time=20000 async=false


1 Like

I don’t believe that any demo available creates a plain transport / consumer behind a NAT. I just posted the solution I came up with involving performing a UDP hole punch right before calling connect() to ensure that NAT transversal is possible. It’s working great and does not require a port forward of the rtp and rtcp ports which is exactly what I was hoping to avoid.


1 Like