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.

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:

TTY=$(tty)
rstream nc -L rstrm://demo-ffmpeg -c \
  'exec 3>&1 1>'"$TTY"' 2>'"$TTY"'; \
  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 -fflags nobuffer -i pipe:0

The consumer should display the moving test pattern. 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
TTY=$(tty)
rstream nc -L rstrm://camera-ffmpeg -c \
  'exec 3>&1 1>'"$TTY"' 2>'"$TTY"'; \
  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 -fflags nobuffer -i pipe:0

This 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: tcp

Start MediaMTX on the producer:

mediamtx mediamtx.yml

Expose its RTSP port through one private bytestream tunnel:

rstream forward 127.0.0.1:8554 --bytestream --no-publish \
  --name camera-rtsp --output text

On 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-rtsp

In a second consumer terminal, open the unchanged RTSP URL through the local port:

ffplay -hide_banner -loglevel warning -fflags nobuffer \
  -rtsp_transport tcp -i rtsp://127.0.0.1:8555/cam

This local bridge is only needed for the gateway variant because an RTSP client expects a host and port rather than standard input. 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.