FFmpeg / moq-cli
moq-cli is a command-line tool for publishing media to MoQ relays. It works with FFmpeg for encoding.
Installation
Using Cargo
cargo install moq-cliUsing winget (Windows)
winget install moq-dev.moq-cliUsing Nix
# Run directly
nix run github:moq-dev/moq#moq-cli
# Or build and find the binary in ./result/bin/
nix build github:moq-dev/moq#moq-cliUsing Docker
docker pull moqdev/moq-cli
docker run -v "$(pwd)/video.mp4:/app/video.mp4:ro" moqdev/moq-cli publish /app/video.mp4 https://relay.example.com/anon/streamMulti-arch images (linux/amd64 and linux/arm64) are published to Docker Hub.
From Source
git clone https://github.com/moq-dev/moq
cd moq
cargo build --release --bin moq-cliThe binary will be in target/release/moq-cli.
Basic Usage
Publish a Video File
moq-cli publish video.mp4 https://relay.example.com/anon/my-streamPublish from FFmpeg
Pipe FFmpeg output directly to moq-cli:
ffmpeg -i input.mp4 -f mpegts - | moq-cli publish - https://relay.example.com/anon/my-streamCapture a Webcam
The capture subcommand captures and encodes from local devices directly, no external FFmpeg process required. It publishes the camera as an H.264 video track and the microphone as an Opus audio track on the same broadcast. It is gated behind the capture feature, whose video path pulls in a system FFmpeg (libav*) build dependency (audio is pure-Rust via cpal):
Build (or run) with the feature enabled:
cargo build --release -p moq-cli --features capture
# or run straight from a checkout:
cargo run -p moq-cli --features capture -- publish --url https://relay.example.com --broadcast cam.hang capture
# Default camera + microphone, hardware-encoded H.264 when available:
moq-cli publish --url https://relay.example.com --broadcast cam.hang capture
# Pick devices, resolution, and bitrates:
moq-cli publish --url https://relay.example.com --broadcast cam.hang \
capture --camera 0 --width 1280 --height 720 --fps 30 --bitrate 3000000 \
--microphone "MacBook Pro Microphone" --audio-bitrate 64000
# One medium only:
moq-cli publish --url https://relay.example.com --broadcast cam.hang capture --no-audio
moq-cli publish --url https://relay.example.com --broadcast cam.hang capture --no-videoVideo capture uses the platform backend (avfoundation on macOS, v4l2 on Linux, dshow on Windows) and picks a hardware encoder (h264_videotoolbox / h264_nvenc / h264_vaapi) when one is present, falling back to software (libx264); force either with --hardware / --software. Audio capture uses cpal (CoreAudio / WASAPI / ALSA) and encodes Opus.
Alternatively, pipe an external FFmpeg process as MPEG-TS:
# macOS
ffmpeg -f avfoundation -i "0:0" -f mpegts - | moq-cli publish - https://relay.example.com/anon/webcam
# Linux
ffmpeg -f v4l2 -i /dev/video0 -f mpegts - | moq-cli publish - https://relay.example.com/anon/webcamPublish Screen
# macOS
ffmpeg -f avfoundation -i "1:" -f mpegts - | moq-cli publish - https://relay.example.com/anon/screen
# Linux (X11)
ffmpeg -f x11grab -i :0.0 -f mpegts - | moq-cli publish - https://relay.example.com/anon/screenEncoding Options
Custom Video Settings
ffmpeg -i input.mp4 \
-c:v libx264 -preset ultrafast -tune zerolatency \
-b:v 2500k -maxrate 2500k -bufsize 5000k \
-c:a aac -b:a 128k \
-f mpegts - | moq-cli publish - https://relay.example.com/anon/streamLow Latency Settings
ffmpeg -i input.mp4 \
-c:v libx264 -preset ultrafast -tune zerolatency \
-g 30 -keyint_min 30 \
-c:a aac \
-f mpegts - | moq-cli publish - https://relay.example.com/anon/streamH.265/HEVC
ffmpeg -i input.mp4 \
-c:v libx265 -preset ultrafast \
-c:a aac \
-f mpegts - | moq-cli publish - https://relay.example.com/anon/streamContainer Formats
publish selects its input container with a subcommand; subscribe selects its output container with --format.
Publish (read from stdin unless noted):
avc3- raw H.264 Annex-Bfmp4- fragmented MP4 / CMAFts- MPEG-TS (H.264 / H.265 video; AAC, MP2, AC-3, or E-AC-3 audio)hls --playlist <url>- HLS playlist ingestcapture- capture local devices directly (camera H.264 + microphone Opus; requires thecapturebuild feature; does not read stdin)
Subscribe (--format):
fmp4- fragmented MP4 / CMAFmkv- Matroska / WebMts- MPEG-TS
MPEG-TS
Ingest an MPEG-TS stream from FFmpeg and play one back out:
# Publish: remux a file to MPEG-TS and pipe it in
ffmpeg -i input.mp4 -c copy -f mpegts - | \
moq-cli publish --url https://relay.example.com --broadcast my-stream ts
# Subscribe: pull MPEG-TS back out and play it
moq-cli subscribe --url https://relay.example.com --broadcast my-stream --format ts | ffplay -TS export carries H.264 / H.265 as Annex-B and AAC as ADTS. Both in-band (avc3 / hev1) and out-of-band (avc1 / hvc1, e.g. from an fMP4 import) video sources work: the parameter sets are read from the bitstream or the catalog description and re-injected as Annex-B on each keyframe.
Broadcast audio (MP2, AC-3, E-AC-3) is carried verbatim: complete, well-formed frames pass through byte-exact, never transcoded; malformed input is rejected rather than mis-described. The catalog describes the codec honestly so a subscriber that can decode it (typically TS gear) picks it up; browsers cannot play these codecs and should skip the rendition.
Authentication
Pass a JWT token via the URL:
moq-cli publish video.mp4 "https://relay.example.com/room/123?jwt=<token>"See Authentication for token generation.
Test Videos
The repository includes helper commands for test content:
# Publish Big Buck Bunny
just pub bbb https://relay.example.com/anon
# Publish Tears of Steel
just pub tos https://relay.example.com/anonClock Synchronization
Publish and subscribe to clock broadcasts for testing:
# Publish a clock
just clock publish https://relay.example.com/anon
# Subscribe to a clock
just clock subscribe https://relay.example.com/anonDebugging
Verbose Output
RUST_LOG=debug moq-cli publish video.mp4 https://relay.example.com/anon/streamCheck Connection
# Verify you can connect to the relay
curl http://relay.example.com:4443/announced/Common Issues
"Connection refused"
- Ensure the relay is running
- Check firewall allows UDP traffic
- Verify the URL is correct
"Invalid certificate"
- The relay needs a valid TLS certificate
- For development, use the fingerprint method
- See TLS Setup
"Permission denied"
- Check your JWT token is valid
- Verify the token allows publishing to that path
- See Authentication
Next Steps
- Deploy a relay server
- Use Web Components for playback
- Try the Rust libraries for custom apps