Stream Video with FFmpeg and rstream
Stream an FFmpeg source through a private rstream tunnel, connect an RTSP camera, and add MediaMTX when several consumers need the same feed.
FFmpeg can send video through a private rstream tunnel without opening a port on the producer. FFmpeg writes MPEG-TS to the tunnel, and the consumer reads the same stream with ffplay, VLC, or another player that accepts standard input.
The first part of this guide keeps the path point to point: each consumer starts its own FFmpeg process on the producer. The second part introduces MediaMTX for the case where several consumers need one shared RTSP feed. The direct path stays the recommended starting point.
For a browser-facing application with separate signaling and media planes, ICE and TURN, packet repair, or adaptive bitrate, use Build Device-to-Browser Video Streaming with WebRTC and rstream. This guide stays with FFmpeg, standard input, and RTSP on purpose: FFmpeg is used here for capture, encoding, and remuxing, while rstream provides the private reliable connection.
Private tunnel dialing is available on Pro, Enterprise, and self-hosted deployments. The commands use reliable bytestream tunnels and work with any recent rstream CLI.
Prepare the producer and consumer
Both machines need an rstream context for the same project. The producer needs FFmpeg, and the consumer needs ffplay or another player.
rstream login
rstream project use <project-endpoint>The setup is covered in Installation and CLI Workflow. A production device should use a project-scoped token context instead of an interactive account login.
Validate the point-to-point stream
Start with FFmpeg's built-in test source. On the producer, the media stream uses file descriptor 3 while FFmpeg logs remain attached to the terminal:
rstream nc -L rstrm://demo-ffmpeg -c \
'exec 3>&1 1>&2; \
exec ffmpeg -hide_banner -loglevel warning \
-re -f lavfi -i testsrc2=size=1280x720:rate=30 \
-c:v libx264 -preset veryfast -tune zerolatency -b:v 2000k -g 60 \
-f mpegts pipe:3'On the consumer, dial the private tunnel and start ffplay:
rstream nc rstrm://demo-ffmpeg | \
ffplay -hide_banner -loglevel warning \
-probesize 32 -analyzeduration 0 -flags low_delay -i pipe:0The consumer should display the moving test pattern. The small probe and zero analysis duration reduce startup work, while low_delay asks the decoder to avoid unnecessary reordering. Do not add -fflags nobuffer to this pipe: that input flag can discard packets FFmpeg has already read while probing, which loses initial frames instead of merely reducing latency. Each connection starts a new FFmpeg process, so a late consumer receives a fresh encoder stream rather than joining an existing byte stream midway. Use --max-connections on the producer when the machine needs a strict concurrency limit.
Connect an RTSP camera
An H.264 RTSP camera does not need to be decoded and encoded again. Replace the test source with the camera URL and let FFmpeg remux the stream as MPEG-TS:
export CAMERA_URL=rtsp://192.168.1.10:8554/cam
rstream nc -L rstrm://camera-ffmpeg -c \
'exec 3>&1 1>&2; \
exec ffmpeg -hide_banner -loglevel warning \
-rtsp_transport tcp -i "$CAMERA_URL" \
-c:v copy -f mpegts pipe:3'Change CAMERA_URL to the camera's private address. RTSP runs over TCP on the producer side, and only the MPEG-TS output crosses rstream. The consumer command remains the same apart from the tunnel name:
rstream nc rstrm://camera-ffmpeg | \
ffplay -hide_banner -loglevel warning \
-probesize 32 -analyzeduration 0 -flags low_delay -i pipe:0This pattern also applies to cameras attached directly to a machine. Change the FFmpeg input to Video4Linux, AVFoundation, or the platform capture API, then keep the H.264 and MPEG-TS output settings from the test-source command.
Add MediaMTX for several consumers
The direct path starts one camera session per consumer. When several operators need the same feed, MediaMTX can ingest the camera once and serve it as RTSP. rstream then carries the MediaMTX TCP port instead of starting a media process for every connection.
Declare the RTSP path in mediamtx.yml:
paths:
cam:
source: rtsp://192.168.1.10:8554/cam
rtspTransport: tcpStart MediaMTX on the producer:
mediamtx mediamtx.ymlExpose its RTSP port through one private bytestream tunnel:
rstream forward 127.0.0.1:8554 --bytestream --no-publish \
--name camera-rtsp --output textOn each consumer, keep the local bridge in its own terminal. Port 8555 is local to that consumer:
rstream nc -L 127.0.0.1:8555 -R rstrm://camera-rtspIn a second consumer terminal, open the unchanged RTSP URL through the local port:
ffplay -hide_banner -loglevel warning -flags low_delay \
-rtsp_transport tcp -i rtsp://127.0.0.1:8555/camThis local bridge is only needed for the gateway variant because an RTSP client expects a host and port rather than standard input. Keep normal RTSP probing here: unlike the fresh MPEG-TS pipe above, the RTSP session includes control negotiation and may expose more than one track. low_delay reduces decoder reordering without discarding already received packets. The producer still exposes no inbound port, and several consumers can use the same MediaMTX feed without opening additional camera sessions.
Where to go next
Stream Video with GStreamer and rstream shows the same test source over RTP datagrams when stable live latency matters more than reliable delivery. For browser-facing playback, continue with Build Device-to-Browser Video Streaming with WebRTC and rstream. The private tunnel and declarative service models are documented in Private Tunnels and Declarative Tunnels.
For a media profile tailored to a camera, gateway, codec, or deployment network, contact us.
Technical qualification
We cross FFmpeg and GStreamer producers with the Go and C++ netcat clients in both directions. A finite source is decoded and compared frame by frame before a bounded deadline, covering standard input and output, half-close, cancellation, teardown, and media interoperability.
The MediaMTX profile adds RTSP control. Both bridge implementations must open the same published source, preserve the session, reproduce every frame, and terminate cleanly without creating another camera feed. The qualification record retains the comparisons and diagnostics; the runner reproduces the matrix.
Troubleshooting
If ffplay connects without decoding, keep FFmpeg diagnostics on the terminal and the MPEG-TS stream alone on the pipe. A late consumer must start a new producer process; joining an existing MPEG-TS byte stream in the middle can omit the codec configuration required to decode.
For a camera source, test the private RTSP URL on the producer machine before involving rstream. A successful tunnel with no frames then points to camera authentication, codec support, or FFmpeg probing rather than remote connectivity.