Skip to content
openmoqPublic

About

Origin Publisher for OpenMOQ

Resources

Code of conduct

Contributing

Stars

8 stars

Watchers

0 watching

Forks

Repository files navigation

OpenMOQ Publisher

English | Español | Français | Italiano | 日本語 | Português | 中文

moqxr is a C++20 OpenMOQ publisher for Linux, macOS, and Windows.

It packages file and live media for Media over QUIC Transport (MOQT), builds MSF/CMSF catalogs and publish plans, and publishes through picoquic-backed Raw QUIC or WebTransport connections.

Capabilities

  • Parses fragmented MP4 (ftyp + moov + moof/mdat) and remuxes progressive MP4 into fragmented media objects.
  • Extracts track metadata and RFC 6381 codec identifiers, including HEVC signaling and hev1 to hvc1 normalization.
  • Builds MSF/CMSF version 1 catalogs, initialization data, optional media timelines, and SAP event timelines.
  • Detects and signals existing CMAF CENC content protection for batch input, live fragmented MP4 on stdin, and CTE LL-DASH ingest. It does not encrypt or decrypt media.
  • Emits catalog, initialization, media, probe, and publish-plan files for local inspection.
  • Publishes with the main CLI's supported MOQT draft profiles: draft 16, draft 18 (default), draft 21, and draft 22.
  • Publishes over Raw QUIC or WebTransport when picoquic and picotls are available.
  • Accepts live fragmented MP4 from stdin, MPEG-TS over SRT when libsrt is available, and CMAF over HTTP/1.1 CTE LL-DASH ingest (chunked or fixed-length requests).
  • Parses MSF URLs with --url and prints the catalog discovery URL with --print-msf-urls.
  • Supports ordered relay failover with repeated --endpoint options and configurable same-endpoint retries with --retry.
  • Provides C++ Publisher API examples for FFmpeg-generated live media, CAT4MOQ authorization, and MPEG-2 TS/M2TS packaging.
  • Optionally routes publishing through the moq5 C11 Media-over-QUIC library for drafts 16 and 18.

Quick Start

Build and test:

cmake -S . -B build -DOPENMOQ_RUN_PICOQUIC_SMOKE_TESTS=OFF
cmake --build build
ctest --test-dir build --output-on-failure

The default build creates the openmoq-publisher executable and the static Publisher library: build/libopenmoq_publisher.a on Linux/macOS, or build\<config>\openmoq_publisher.lib with Visual Studio generators on Windows.

Check which build you are running:

./build/openmoq-publisher --version

This prints openmoq-publisher <version> (commit <hash>). The version is project(VERSION) from CMakeLists.txt; a build made from the matching v<version> release tag reports it as-is, while any other build appends -dev+g<commit> (plus .dirty when there are uncommitted changes) so a bug report identifies the exact source. --help prints the same banner. Embedders get the same values from openmoq/publisher/version.h (version(), version_full(), git_commit()).

Inspect a publish plan:

./build/openmoq-publisher --input sample.mp4 --dump-plan

Emit the catalog and packaged media objects:

./build/openmoq-publisher --input sample.mp4 --emit-dir out/

CMAF remains the default. Add --packaging locmaf to opt into LOCMAF-01, packaging version 0.3, for file, live stdin, SRT, or CTE LL-DASH publishing on the default backend. The publisher retains CMAF initialization and encoded sample bytes, signals LOCMAF in the catalog, and emits full headers so late joins and queue drops do not depend on earlier delta objects. File input retains original CMAF chunks, including encryption and auxiliary boxes; source constraints can keep an entire track on CMAF. A live track that becomes ineligible fails instead of changing its advertised packaging. --stream-per-object, --coalesce-cmaf-chunks, and the libmoq publishing backend are currently incompatible with LOCMAF. Multiplexed fragments must be demuxed first (for FFmpeg, use separate_moof).

--emit-dir writes LOCMAF media as .locmafobj alongside the usual catalog and initialization segments. For a playback equivalence check against a moq-playa checkout with LOCMAF support, run bun scripts/test-locmaf-playa.mjs build/openmoq-publisher ../moq-playa (requires FFmpeg and ffprobe). See the LOCMAF quickstart, C++ API configuration, and validation coverage.

LOC-04 publishing is available with --packaging loc --draft 18 (or --draft 21 / --draft 22) on the native backend. It sends individual H.264/AAC frames with timestamp, timescale, and codec configuration properties; CMAF remains the default. See the LOC quickstart for scope and constraints.

Publish to a relay with the default draft-18 profile:

OPENMOQ_PICOQUIC_TRACE=1 ./build/openmoq-publisher \
  --input sample.mp4 \
  --endpoint moqt://relay.example.com:443/moq \
  --namespace media \
  --draft 18 \
  --forward 0 \
  --timeout 30 \
  --paced

For file input, --forward 0 announces the namespace and waits for inbound SUBSCRIBE requests for catalog and media tracks. The catalog is available without --publish-catalog; that option proactively sends catalog PUBLISH and requires the relay to accept it. --forward 1 proactively publishes both catalog and media tracks and sends objects when the relay permits forwarding. If the relay reports PUBLISH is not supported, use --forward 0 without --publish-catalog. See publishing modes and troubleshooting.

A printed connection_id= confirms transport and MOQT setup only; it does not confirm namespace acceptance or a downstream subscription.

Add endpoints in preferred order and use --retry N to retry each current endpoint N times before advancing. For example, --endpoint primary:4433 --endpoint backup:4433 --retry 2 makes up to three attempts at the primary, then up to three attempts at the backup. See the CLI quick start for failure handling and live-source details.

On Windows, replace ./build/openmoq-publisher with build\Release\openmoq-publisher.exe or the path for the selected build configuration.

Live Ingest

The CLI exposes one live source at a time:

Source CLI selection Input Notes
Fragmented MP4 --live-source stdin --input - CMAF/fMP4 on standard input Available on all supported platforms
SRT --live-source srt --srt-config FILE MPEG-TS over SRT Requires libsrt; CENC metadata is unavailable in this path
CTE LL-DASH --live-source dash --dash-listen HOST:PORT CMAF POST or PUT requests, chunked or with Content-Length Listener currently requires a Unix-like platform

Fragmented MP4 ingest

The publisher reads fragmented MP4 from standard input. The input must contain ftyp + moov + moof/mdat fragments, and the publisher will emit MSF/CMSF catalogs and media objects.

ffmpeg -hide_banner -stream_loop -1 -re \
 -i fragmented.mp4 \
 -map 0:v:0 -map 0:a:0 \
 -c:v libx264 -preset veryfast \
 -r 30 -g 60 -keyint_min 60 -sc_threshold 0 -bf 0 \
 -c:a aac -b:a 128k -ar 48000 -ac 2 \
 -movflags +frag_keyframe+empty_moov+default_base_moof+separate_moof \
 -f mp4 pipe:1 | ./build/openmoq-publisher \
 --transport webtransport \
 --endpoint https://127.0.0.1:4433/moq \
 --namespace live/stdin \
 --draft 16 \
 --publish-catalog \
 --live-source stdin \
 --input -

SRT ingest

The publisher is an SRT caller. Create /tmp/srt_callers.json with the SRT listener address and MPEG-TS/CMAF settings:

{
  "srt_callers": [
    {
      "id": "cam1",
      "srt": {
        "mode": "caller",
        "host": "127.0.0.1",
        "port": 9000,
        "latency_ms": 120
      },
      "mpegts": {
        "auto_detect_program": true,
        "program_number": null,
        "video_pid": null,
        "audio_pid": null
      },
      "cmaf": {
        "fragment_on_keyframe": true,
        "empty_moov": true,
        "default_base_moof": true,
        "separate_moof_per_track": true,
        "target_fragment_duration_ms": 1000
      }
    }
  ]
}

In the first terminal, start an FFmpeg SRT listener that sends MPEG-TS after the publisher connects:

ffmpeg -hide_banner -stream_loop -1 -re \
  -i input.mp4 \
  -map 0:v:0 -map 0:a:0 \
  -c:v libx264 -preset veryfast -r 30 -g 60 -keyint_min 60 -sc_threshold 0 -bf 0 \
  -c:a aac -b:a 128k -ar 48000 -ac 2 \
  -f mpegts "srt://0.0.0.0:9000?mode=listener&pkt_size=1316"

In the second terminal, start the SRT caller and MoQ publisher:

./build/openmoq-publisher \
  --live-source srt \
  --srt-config /tmp/srt_callers.json \
  --endpoint 127.0.0.1:4443 \
  --transport raw \
  --namespace live/srt \
  --draft 16 \
  --timeout 120 \
  --forward 0

Set srt.mode to caller to connect to an existing SRT listener, or to listener to bind the configured host and port and accept an incoming SRT source. Listener mode accepts one connection per configured entry and uses the same MPEG-TS ingest pipeline. Use --forward 1 for an immediate relay smoke test, or keep --forward 0 to wait for subscriber interest.

CTE LL-DASH ingest

Start the publisher with an HTTP/1.1 CMAF listener and a MoQ relay target:

./build/openmoq-publisher \
  --live-source dash \
  --dash-listen 0.0.0.0:8080 \
  --dash-path /ingest \
  --endpoint https://127.0.0.1:4433/moq \
  --transport webtransport \
  --namespace live/dash \
  --draft 18 \
  --publish-catalog \
  --forward 1 \
  --insecure

Send an existing CMAF/fMP4 stream with HTTP/1.1 chunked transfer encoding:

curl -X PUT \
  -H 'Transfer-Encoding: chunked' \
  -H 'Content-Type: video/iso.segment' \
  --data-binary @live-video.cmaf \
  http://127.0.0.1:8080/ingest/video

Requests that carry a Content-Length instead of chunked transfer encoding are accepted too, as the DASH-IF ingest specification only recommends chunking for low latency. This is how livesim2 pushes init segments, so its CMAF ingest output can be pointed straight at the listener:

curl -X PUT \
  -H 'Content-Type: video/mp4' \
  --data-binary @init.cmfv \
  http://127.0.0.1:8080/ingest/video/init.cmfv

When the final path segment has a file extension, such as /ingest/video/init.cmfv followed by /ingest/video/1.cmfv, the enclosing directory (/ingest/video) is the representation path, so media segments find the init segment that preceded them. Paths without an extension, like the FFmpeg recipe below, are used as-is. A request with neither framing header is refused with 411 Length Required, and a declared length above the configured cap (256 MiB by default) with 413 Content Too Large.

FFmpeg can instead create two video representations plus audio and push them directly to the ingest prefix:

ffmpeg -re \
  -f lavfi -i "testsrc2=size=1280x720:rate=25" \
  -f lavfi -i "anullsrc=r=48000:cl=stereo" \
  -filter_complex "[0:v]split=2[v1][v2];[v1]scale=1280:720[v720];[v2]scale=640:360[v360]" \
  -map "[v720]" -c:v:0 libx264 -b:v:0 1500k -g 50 -keyint_min 50 -sc_threshold 0 \
  -map "[v360]" -c:v:1 libx264 -b:v:1 500k -g 50 -keyint_min 50 -sc_threshold 0 \
  -map 1:a -c:a aac -b:a 128k \
  -f dash -seg_duration 2 -use_template 1 -use_timeline 0 \
  -init_seg_name 'video$RepresentationID$' \
  -media_seg_name 'video$RepresentationID$' \
  -adaptation_sets "id=0,streams=v id=1,streams=a" \
  -multiple_requests 1 -streaming 1 -remove_at_exit 0 \
  -window_size 20 -extra_window_size 20 \
  http://127.0.0.1:8080/ingest/

Each representation path below /ingest maintains independent parser state and produces path-prefixed MoQ track names. Use --forward 1 to send objects immediately, or --forward 0 to wait for subscriber interest. The DASH listener currently requires a Unix-like platform.

See the CLI quick start, FFmpeg recipes, and SRT technical note for additional details.

Restarting a live publisher

Each live publisher process numbers its Group IDs from 0. If the process restarts and publishes the same namespace and track names again, its new groups reuse Group IDs that the previous run already published, so two different objects share one Location. MoqTransport section 2.5 suggests that a resuming publisher set its initial Group ID to a value larger than every group it used before, for example one based on the current time. Use --initial-group-id to do that:

./build/openmoq-publisher --input - --initial-group-id time ...
  • --initial-group-id time reads the system clock once at startup and starts at the number of milliseconds since the Unix epoch. This is safe if the previous run published fewer groups than the milliseconds that passed between its start and the restart, and the system clock did not step backwards in between.
  • --initial-group-id N starts at the unsigned integer N, which must not exceed 2^62-1.
  • The default is 0. The option applies to the media tracks and the catalog of the live sources (stdin, SRT, and CTE LL-DASH) on every draft; it is rejected for file input. On draft 22, a GOAWAY migration still resumes above the groups the previous session sent. The moq5 publishing backend numbers Group IDs itself and rejects a non-zero value.
  • The value is fixed for the process. A --retry or endpoint failover reconnect inside the same process starts again from it, so those attempts can reuse Group IDs; the option covers a process restart, not an in-process reconnect.

Publishing via moq5

The opt-in moq5 backend routes batch, live stdin, live SRT, and live-object publishing through moq5's service tier. The service tier handles catalog publication, CMSF/CMAF validation, subscriber-demand gating, bounded backpressure, and graceful transport draining.

CMake fetches current openmoq/moq5 main when this backend is enabled. Set OPENMOQ_LIBMOQ_SOURCE_DIR only to use a local or offline source override:

cmake -S . -B build-libmoq \
  -DOPENMOQ_USE_LIBMOQ_PUBLISHER=ON
cmake --build build-libmoq
ctest --test-dir build-libmoq --output-on-failure

The default build keeps the built-in transport path. See docs/build.md for backend status, dependency discovery, and configuration details.

CAT4MoQ on this backend requires moq5's MOQ_SERVICE_AUTH_API_VERSION >= 1. Use -DOPENMOQ_LIBMOQ_SOURCE_DIR=../moq5 with the corresponding feature/cat4moq checkout while testing the coordinated change. Older moq5 dependencies reject configured credentials before connecting. See the auth design for backend limits and compatibility profiles.

Examples

Enable examples explicitly with cmake -S . -B build -DOPENMOQ_BUILD_EXAMPLES=ON, then build them with cmake --build build.

Example Target Purpose
Psychedelic live publisher openmoq-publisher-psychedelic-example Runs one FFmpeg audio/video pipeline through Publisher::publish_live(...)
CAT4MOQ authorization openmoq-publisher-auth-example Publishes deterministic live objects with token files or a Catapult token command
MSFTS publisher openmoq-publisher-msfts-example Publishes packet-aligned MPEG-2 TS or M2TS objects through Publisher::publish_live_objects(...)

The MSFTS example follows the local text draft in examples/msfts-publisher/docs/, discovers PAT/PMT data, selects one program, filters unrelated PIDs, and emits an MSF version 1 catalog with packaging: "m2ts".

This example discovers single-packet PAT/PMT sections within the first 4096 source packets. It uses fixed 10 ms object pacing rather than PCR-derived timing, publishes one group, and declares m2tsRandomAccess: false; it does not identify random-access boundaries or track program changes.

./build/examples/msfts-publisher/openmoq-publisher-msfts-example \
  --input sample.m2ts \
  --endpoint https://relay.example.com:443/moq \
  --namespace media.msfts \
  --track transport \
  --draft 17

Add --program NUMBER to select a program, --packets-per-object COUNT to change object sizing, or --insecure only when the relay certificate is intentionally untrusted.

Documentation

Topic Link
Build and dependencies docs/build.md
CLI and live-ingest quick start docs/quickstart.md
Testing docs/testing.md
Design overview docs/design.md
FFmpeg input recipes docs/ffmpeg.md
SRT ingest technical note docs/srt-ingest-technical-note.md
Relay interoperability docs/relay-interop.md
C++ Publisher API docs/publisher-api.md
CAT4MOQ auth example examples/auth/README.md
CAT4MoQ profiles and implementation status docs/cat4moq-design.md, docs/cat4moq-plan.md
MSFTS text draft examples/msfts-publisher/docs/draft-gregoire-moq-msfts.txt
Protocol mapping docs/protocol-mapping.md
WebTransport compliance docs/webtransport-compliance.md
Local MSF version 1 draft docs/draft-ietf-moq-msf-01.txt
Local CMSF version 1 draft docs/draft-ietf-moq-cmsf-01.txt
macOS DASH shutdown behavior docs/macos-accept-shutdown-quirk.txt
Project status and roadmap docs/status.md

Localized Publisher API guides are available in Spanish, French, Italian, Japanese, Portuguese, and Chinese.

Repository Layout

  • include/openmoq/publisher: public C++ headers
  • src: static library and CLI implementation
  • tests: CTest-based unit and integration coverage
  • docs: local draft text, protocol notes, integration guides, and design references
  • examples: Publisher API integrations
  • .github/workflows/ci.yml: Linux, macOS, and Windows CI
  • .github/workflows/release.yml: CLI, header, and static-library release artifacts

Current Status

The main openmoq-publisher CLI accepts drafts 16, 18, 21, and 22; draft 18 is the default and provides the newer request-stream profile, and draft 21 extends it (LOCATION_FILTER, INCLUDE_PROPERTIES, FILL_PARAMETERS). Draft 22 adds explicit LOCATION_FILTER types while retaining draft-21 publishing semantics. Drafts 21 and 22 are native-backend only; the moq5 backend stays on drafts 16 and 18. Text for drafts 14, 17, 19, and 20 remains in docs/ for implementation history and protocol review, but those versions are not selectable in the main CLI. The separate MSFTS example retains draft 14/16/17/18 selection for draft-specific testing.

The default picoquic backend and the opt-in moq5 backend are both under active interoperability testing. For detailed feature coverage, limitations, and roadmap work, see docs/status.md and docs/protocol-mapping.md.

Attribution

Originally created by Paul Gregoire for OpenMOQ

About

Origin Publisher for OpenMOQ

Resources

Code of conduct

Contributing

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages