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, bandwidth adaptation, or several synchronized tracks, 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 rtpbin rtprtxqueue avdec_h264Validate 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. The shell keeps that descriptor connected to rstream and sends GStreamer diagnostics back to the producer terminal, outside 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=falseThe 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:
rstream nc -u -L rstrm://demo-rtp -c 'exec 3>&1 1>&2; \
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=falseThe 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. This first RTP path is deliberately one-way: it establishes the packetized media path before adding session feedback.
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.
Add RTCP feedback and packet repair
A longer-running RTP session benefits from feedback in the opposite direction. rtpbin adds an AVPF session around the media stream: the consumer reorders packets in its jitter buffer and sends RTCP receiver reports and NACK feedback, while the producer retains recent RTP packets in rtprtxqueue and resends a packet when the consumer requests it.
The exchange still uses one private tunnel. RTP travels from producer to consumer, and RTCP feedback returns through the same bidirectional datagram connection. There is no second tunnel to configure or keep in sync.
Start the producer:
rstream nc -u -L rstrm://demo-rtp-session -c 'exec 3>&1 1>&2; \
exec env GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:2" \
gst-launch-1.0 -v --no-position \
rtpbin name=rtp rtp-profile=avpf do-retransmission=true \
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 ! \
rtprtxqueue max-size-packets=0 max-size-time=1000 ! rtp.send_rtp_sink_0 \
rtp.send_rtp_src_0 ! rtpstreampay ! fdsink fd=3 sync=false \
fdsrc fd=0 is-live=true do-timestamp=true ! application/x-rtcp-stream ! \
rtpstreamdepay ! rtp.recv_rtcp_sink_0'Then start the consumer:
rstream nc -u rstrm://demo-rtp-session -c 'exec 3>&1 1>&2; \
exec env GST_DEBUG_NO_COLOR=1 GST_DEBUG="*:2" \
gst-launch-1.0 -v --no-position \
rtpbin name=rtp rtp-profile=avpf latency=150 \
do-retransmission=true drop-on-latency=true \
fdsrc fd=0 is-live=true do-timestamp=true ! \
application/x-rtp-stream,media=video,clock-rate=90000,encoding-name=H264,payload=96 ! \
rtpstreamdepay ! rtp.recv_rtp_sink_0 \
rtp. ! rtph264depay ! h264parse ! avdec_h264 ! \
videoconvert ! autovideosink sync=false \
rtp.send_rtcp_src_0 ! rtpstreampay ! fdsink fd=3 sync=false'is-live=true gives each standard-input source a running clock, and do-timestamp=true timestamps the buffers as they enter the local pipeline. The producer keeps up to one second of RTP packets for repair. This is NACK-driven retransmission of the original RTP packets, not RFC 4588 RTX with a separate payload type. A GStreamer application can add that protocol through rtpbin's auxiliary sender and receiver callbacks; gst-launch-1.0 cannot express those callbacks by itself.
This final pipeline is appropriate for one interactive video flow that needs bounded latency, receiver feedback, and repair of recoverable loss. Multiple synchronized tracks, bandwidth adaptation, browser playback, and signaling belong in a WebRTC application rather than in a larger shell pipeline.
Where to go next
Stream Video with FFmpeg and rstream applies the reliable path to FFmpeg, RTSP cameras, and a MediaMTX gateway. For signaling separated from media, browser playback, multiple synchronized tracks, or adaptive bitrate, 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.