Stream Video with GStreamer and rstream

Stream a GStreamer test source through a private rstream tunnel, first as reliable MPEG-TS and then as RTP over datagrams.


Live video makes the difference between bytestream and datagram tunnels visible. A reliable stream preserves every byte but can accumulate delay while the transport recovers loss. A datagram stream preserves packet boundaries and lets late packets disappear instead of blocking the frames that follow.

This guide sends the same GStreamer test pattern through both paths. The producer creates a private tunnel and writes media to it. The consumer dials the tunnel by name and displays the result. No public endpoint, inbound port, or media server is required.

This direct process-to-process model is intentionally narrow. For browser playback, separate signaling and media planes, ICE and TURN, packet repair, or adaptive bitrate, use Build Device-to-Browser Video Streaming with WebRTC and rstream. That guide treats video as a WebRTC application rather than a stream attached to netcat standard input and output.

Private tunnel dialing is available on Pro, Enterprise, and self-hosted deployments. Datagram mode requires rstream CLI 1.24.0 or later on both machines.

Prepare the producer and consumer

Both machines need an rstream context for the same project. Installation and context management are covered in Installation and CLI Workflow.

rstream login
rstream project use <project-endpoint>

Install GStreamer with gst-launch-1.0 and the base, good, bad, ugly, and libav plugin sets. The commands use x264enc, mpegtsmux, the RTP elements, and an H.264 decoder. Confirm that they are available before starting:

gst-inspect-1.0 x264enc mpegtsmux rtph264pay rtpstreampay \
  rtpstreamdepay rtpjitterbuffer avdec_h264

Validate a reliable MPEG-TS stream

Start with a reliable bytestream tunnel. On the producer, videotestsrc generates a test pattern and GStreamer writes MPEG-TS to file descriptor 3. Standard output and standard error stay attached to the terminal, so pipeline state and diagnostics remain visible without entering the media stream.

TTY=$(tty)
rstream nc -L rstrm://demo-ts -c 'exec 3>&1 1>'"$TTY"' 2>'"$TTY"'; \
  exec env GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:2" \
  gst-launch-1.0 -v --no-position \
  videotestsrc is-live=true pattern=smpte ! \
  video/x-raw,width=1280,height=720,framerate=30/1 ! \
  videoconvert ! x264enc tune=zerolatency bitrate=2000 key-int-max=60 ! \
  h264parse config-interval=-1 ! mpegtsmux alignment=7 ! \
  fdsink fd=3 sync=false'

On the consumer, dial the tunnel and display the stream:

rstream nc rstrm://demo-ts | \
  env GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:2" \
  gst-launch-1.0 -v --no-position \
  fdsrc fd=0 ! tsdemux name=demux \
  demux. ! queue ! decodebin ! videoconvert ! autovideosink sync=false

The consumer should display the SMPTE test pattern. The producer terminal shows the pipeline transition to PLAYING, negotiated caps, warnings, and errors. GST_DEBUG="*:2" keeps routine debug output quiet; raise a specific element to level 4 when investigating it, for example GST_DEBUG="*:2,tsdemux:4" on the consumer.

MPEG-TS is a practical first validation because a consumer can join an active stream and recover its structure. The bytestream tunnel delivers data reliably and in order. Under sustained loss, playback can pause while missing data is recovered.

Switch to RTP over datagrams

The second path packetizes the same source as RTP. rtpstreampay adds the RFC 4571 length prefix expected by rstream nc -u, so each framed RTP packet becomes one tunnel datagram.

Start the RTP producer. The media remains on file descriptor 3, while pipeline diagnostics stay visible in the terminal:

TTY=$(tty)
rstream nc -u -L rstrm://demo-rtp -c 'exec 3>&1 1>'"$TTY"' 2>'"$TTY"'; \
  exec env GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:2" \
  gst-launch-1.0 -v --no-position \
  videotestsrc is-live=true pattern=smpte ! \
  video/x-raw,width=1280,height=720,framerate=30/1 ! \
  videoconvert ! x264enc tune=zerolatency bitrate=2000 key-int-max=60 ! \
  rtph264pay pt=96 mtu=1200 config-interval=1 ! \
  rtpstreampay ! fdsink fd=3 sync=false'

Then start the RTP consumer:

RTP_CAPS='application/x-rtp-stream,media=video,clock-rate=90000,encoding-name=H264,payload=96'
rstream nc -u rstrm://demo-rtp | \
  env GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:2" \
  gst-launch-1.0 -v --no-position \
  fdsrc fd=0 do-timestamp=true ! \
  "$RTP_CAPS" ! \
  rtpstreamdepay ! rtpjitterbuffer latency=100 drop-on-latency=true ! \
  rtph264depay ! h264parse ! avdec_h264 ! \
  videoconvert ! autovideosink sync=false

The visible result is the same, but the transport behavior is different. mtu=1200 keeps RTP packets within the QUIC datagram budget. config-interval=1 repeats the decoder configuration, and the 100 ms jitter buffer absorbs modest variation in packet arrival time.

The rstream CLI selects QUIC when it is reachable and falls back to TLS otherwise. Datagram mode remains independent of that choice. With QUIC and default delivery semantics, packets can use QUIC datagrams and are not retransmitted by the tunnel. With TLS, packet boundaries are preserved through framing, but applications must still treat delivery as not guaranteed unless the tunnel explicitly requests guaranteed delivery.

Do not add --datagram-guaranteed-delivery to this RTP example. Reliable delivery below RTP can turn packet loss into growing playback delay. Applications that require every byte should use the MPEG-TS bytestream path instead.

Stop the stream

Stop the producer with Ctrl+C. The consumer should leave its pipeline promptly when the tunnel session closes. An idle timeout is not required for this graceful path.

--idle-timeout measures the absence of received datagrams. It can be useful on a consumer as a guard against an abrupt network failure, but it should not be placed on the one-way producer above: that producer sends packets without receiving any.

Where to go next

Stream Video with FFmpeg and rstream applies the reliable path to FFmpeg, RTSP cameras, and a MediaMTX gateway. For browser playback and several simultaneous viewers, continue with Build Device-to-Browser Video Streaming with WebRTC and rstream. The transport and netcat options are documented in Tunnel Transports and Private Tunnels.