Low Overhead CMAF for MoQ
A new wire format for CMAF over MoQ — including DRM — with the overhead of LOC
✨ Why LOCMAF
CMAF carries the metadata that DRM needs (per-sample IVs, subsample maps,
tenc defaults). LOC is compact but strips this away. LOCMAF keeps
the CMAF structure intact and compresses the redundant moof bytes
out of the wire — so the standard MSE / EME path on the receiver plays
protected content as if it had arrived as plain CMAF.
- Sample-level objects (one frame per MoQ object)
- Delta
moofas small as 2 bytes - Functionally lossless reconstruction
- Works with Widevine, PlayReady, FairPlay, ClearKey
📊 Measured Compression
On a 250-fragment AVC group from the moqlivemock test asset:
- CMAF
mooftotal: 26 040 B - LOCMAF object total: 592 B
moof-header ratio: 45 : 1- 128 kbps AAC wire bitrate: 171.5 → 131.9 kbps (−23 %)
- 128 kbps AAC under
cbcsDRM: 191.4 → 131.9 kbps (−31 %)
LOCMAF saves more relative to CMAF under DRM than in clear,
because senc / saio / saiz make
the CMAF moof grow while LOCMAF only emits what it needs.
See it yourself: after connecting MOQ Player to the server,
click the Show catalog toggle to inspect each track's total wire
bitrate (framing included) and compare a CMAF track to its
LOCMAF counterpart — both renditions live in the same mlm/cmsf/* catalog.
🔗 LOCMAF Resources
Everything you need to understand and implement LOCMAF:
-
locmaf.devoverview, diagrams, measurements
-
draft-einarsson-moq-locmafIETF Internet-Draft — normative LOCMAF spec (wire version 0.3)
-
Eyevinn/locmafGo reference codec (encode/decode + golden vectors)
-
Slide deck20-slide MARP deck adapted from the spec
-
Eyevinn/moqlivemockMoQ test publisher/subscriber (uses the locmaf codec)
-
Eyevinn/warp-playerbrowser MoQ player with LOCMAF + EME
Catalog signal: "packaging": "locmaf" with
"locmafVersion": "0.3" in the CMSF Track entry. The matching
"packaging": "cmaf" rendition shares the same init data through an
initRef, so both variants sit side by side in one draft-01 catalog.
Try LOCMAF Live
Both the publisher and the browser player have shipped LOCMAF since v0.13. Open MOQ Player,
connect to a mlm/cmsf/* namespace and pick a LOCMAF track from the catalog,
and the receiver will rebuild the CMAF chunks and feed them to MSE / EME for you —
clear, ClearKey/ECCP, and commercial DRM all work side by side.
Low-Latency Subtitles
Subtitle tracks at the video's own cadence, as CMAF and LOCMAF — and what a paint model saves
📝 What is Published
The publisher generates WebVTT (wvtt) and TTML (stpp) subtitle
tracks that show the UTC time and group number. A subtitle group holds one CMAF chunk per
video object — 25 a second for the test content — and each chunk is sent when the
interval it covers ends, so text reaches the player with the latency of the picture it
belongs to.
- In every
mlm/cmsf/*namespace — clear, DRM and ClearKey; the LOC (mlm/msf/clear) and moq-mi namespaces carry video and audio only - Every subtitle track also as LOCMAF,
<name>_locmaf, sharing the CMAF track's init data - A cue keeps its true
begin, and gets itsendonly in the chunk where it ends, so an unchanged cue restates byte for byte - Each track's measured
bitratein the catalog, and each format in its own altGroup
🎨 Paint-Model Subtitles
At 25 chunks a second, plain stpp restates a complete TTML document 25 times a
second. The experimental
paint-model
variants stpc and wvtc send a chunk that changes nothing as an
8-byte ttmn or vttn box, and a changed TTML chunk as its
<body> alone in a ttmb box. Wire bitrate, CMAF / LOCMAF:
stpp317 / 296 kbit/s →stpc38 / 17 kbit/swvtt35 / 14 kbit/s →wvtc25 / 4.5 kbit/s
This demo publishes them as subs_stpc_en and subs_wvtc_sv.
mlmpub leaves them off by default, since the 4CCs are unregistered placeholders:
-subsstpc en -subswvtc sv adds them.
▶️ Rendering in MOQ Player
MOQ Player reads each subtitle object itself, CMAF or LOCMAF, and paints the cues on the same overlay as the CTA-608 captions, in step with the picture. TTML is laid out by imscJS, the IMSC renderer dash.js uses; WebVTT by the player itself.
- One Subtitles / CC selector: Off, CC1 or one subtitle track at a time
- The paint-model formats are recognised by the sample entry of the init segment, and a no-change sample is shown again without parsing
- Measure all subtitle tracks side by side shows, per track, the bitrate and what the player parses per second
See the Subtitles
Subtitles are in the mlm/cmsf/* namespaces only. MOQ Player may open
mlm/msf/clear (LOC) first, which has none: pick
mlm/cmsf/clear in the namespace list, then choose a track in
Subtitles / CC — subs_stpp_en, subs_wvtt_sv,
or the paint-model subs_stpc_en and subs_wvtc_sv, each also as
_locmaf. Tick Measure all subtitle tracks side by side to
compare the formats and packagings on the wire.
In-band CTA-608 Captions
Captions carried inside the video elementary stream — through every packaging, and inside the ciphertext
💬 What is Carried
The publisher injects one self-contained CTA-608 CC1 caption per MoQ group, generated with Eyevinn/go-608. Row 13 carries the UTC clock, row 14 the group number, so what you read on screen is directly checkable against the burnt-in overlay.
- AVC and HEVC — an SEI NAL unit before the first VCL NALU
- AV1 — a
metadata_itu_t_t35OBU before the first frame OBU - Present in CMAF, LOCMAF, LOC and moq-mi
- Spliced in before encryption, so captions ride inside the ciphertext
- Each group is independent — a caption comes from that group's samples alone
🎛️ Presentation Modes
mlmpub -cc608mode selects how a caption reaches the screen, covering all
three CTA-608 presentation styles. Measured at 25 fps in a 25-frame group:
paint-on(default) — grows two characters per frame; first characters on frame 4, complete on frame 17, so the caption is displayed over the second it namesroll-up2/roll-up3/roll-up4— types the same way but scrolls the window up before each line, finishing two frames laterpop-on— builds invisibly and flips on whole at frame 18
The roll-up sizes differ in the RU2/RU3/RU4 mode code on the wire but display identically here, since a group holds one cue and the window is reset per group — which is exactly what makes them useful for exercising a decoder.
▶️ Rendering in MOQ Player
MOQ Player decodes the captions from the in-band SEI on both render engines — MSE (CMAF and LOCMAF) and WebCodecs (LOC) — and paints them on the true 32×15 grid inside the CTA-608 safe area, with colours, background boxes and the full pop-on / roll-up / paint-on screen model.
- CC1 is one choice in the Subtitles / CC selector, offered when the track advertises
urn:scte:dash:cc:cea-608:2015and its codec can carry CTA-608; the CC button is a shortcut - Encrypted namespaces work without a key — the caption SEI sits in the clear subsample leader
- Built on a general timed-text overlay seam, which now also paints the WebVTT and IMSC subtitles
Not yet on AV1: AV1 renditions play on the WebCodecs pipeline, but their captions travel in a metadata OBU rather than an SEI NAL unit, so the player does not render them — CC1 is greyed out in the selector on those tracks, with the reason.
See the Captions
Captions are enabled on this demo service. Open MOQ Player, pick an AVC or HEVC video
track in any mlm/cmsf/* or mlm/msf/clear namespace — clear or
encrypted — and choose CC1 in Subtitles / CC, or press
CC.
Why Use MoqLiveMock?
Test your MoQ client implementations against a reliable, always-available server speaking MoQ Transport draft-18. MoqLiveMock simulates a true live MoQ streaming service with UTC-aligned content delivery — all clients receive synchronized streams regardless of join time. Stream 10-second loops of synchronized multi-bitrate AVC, HEVC and AV1 content across CMAF, LOCMAF, LOC, and moq-mi packaging modes, with clear and DRM-protected variants of each, in-band CTA-608 captions in every video track, and subtitle tracks at the video's own cadence.
Available Streams
Specially prepared relatively low-bitrate content for testing
🎥 Video Tracks
H.264 (AVC), HEVC and AV1 encoded video streams at multiple bitrates for adaptive streaming, each carrying in-band CTA-608 captions
- 400 kbps AVC
- 600 kbps AVC
- 900 kbps AVC
- 400 kbps HEVC
- 600 kbps HEVC
- 900 kbps HEVC
- 400 kbps AV1
- 600 kbps AV1
- 900 kbps AV1
All three codecs are served in the mlm/cmsf/* namespaces (CMAF and LOCMAF)
and in LOC; mlm/moq-mi/clear video is AVC only. The AV1 assets are encoded
in low-delay CBR mode (I/P only, no reordering) so they match the AVC/HEVC structure.
🎵 Audio Tracks
Easily discernable audio tracks in AAC, Opus, and AC-3 with synchronized second beeps
- Monotonic AAC (128 kbps)
- Scale AAC (128 kbps)
- Monotonic Opus (128 kbps)
- Scale Opus (128 kbps)
- Monotonic AC-3 (192 kbps)
- Scale AC-3 (192 kbps)
📝 Text Tracks
Out-of-band subtitle tracks in the mlm/cmsf/* namespaces, one object per video frame in CMAF and LOCMAF, plus captions carried inside the video itself
- WVTT (WebVTT in CMAF),
subs_wvtt_sv - STPP (TTML in CMAF),
subs_stpp_en - WVTC and STPC, the experimental paint-model variants,
subs_wvtc_svandsubs_stpc_en - CTA-608 CC1, in-band in every video track
The CTA-608 captions are signaled per video track with an
accessibility descriptor
(urn:scte:dash:cc:cea-608:2015, CC1=eng) in the MSF / CMSF
catalog; catalogless moq-mi signals nothing and is in-band only.
See the captions section.
🔒 Content Protection
Multiple content protection modes served as separate namespaces, each catalog carrying both CMAF and LOCMAF packaging
mlm/cmsf/clear— unencryptedmlm/cmsf/drm-cbcs— Widevine, PlayReady, FairPlaymlm/cmsf/eccp-cbcs— ClearKey (ECCP)
Each namespace has its own CMSF draft-01 catalog, with every rendition in both CMAF and LOCMAF packaging. LOCMAF carries the same per-sample encryption metadata as CMAF, so the same EME / CDM path works for either.
Signaling follows the released CMSF draft-01 specification, also supported by Shaka Player.
📦 Packaging Modes
Side-by-side packaging modes so clients can compare wire-cost and pipelines
mlm/cmsf/*— CMAF and LOCMAF (v0.3) in one catalog, sharing init data viainitRefmlm/msf/clear— LOC, raw codec frames per object; video and audio onlymlm/moq-mi/clear— moq-mi, catalogless interop; video and audio only
LOCMAF gives near-LOC overhead while keeping CMAF's metadata path, so DRM survives the round-trip. LOC and moq-mi carry raw codec frames played via WebCodecs.
⚡ Technical Specs
Modern streaming protocols and formats
- MoQ Transport draft-18
- ALPN-based version selection (
moqt-18) - AVC, HEVC and AV1 video; AAC, Opus and AC-3 audio
- CMAF chunks, LOCMAF delta moofs, LOC raw frames
- In-band CTA-608 CC1 captions (SEI for AVC/HEVC, T.35 OBU for AV1)
- Subtitles at the video object cadence, CMAF and LOCMAF, with paint-model
stpc/wvtc - Publisher priorities over raw QUIC: catalog, then audio and subtitles, then video
- MSE / EME and WebCodecs render pipelines in MOQ Player
- 10-second loops, wall-clock synchronized
- MSF / CMSF catalog per namespace
- moq-mi catalogless namespace
- Multiple namespaces announced from one publisher
Live Streaming Simulation
UTC-aligned streaming for consistent playback across all clients
🕐 Wall-clock Alignment
MoqLiveMock simulates a live MOQ streaming service with precise UTC time alignment. This ensures all subscribers receive synchronized content regardless of when they join or when the publisher was started.
Two-Level Time Alignment
seconds % 10 == 0.
This ensures every subscriber joining at the same wall-clock time receives the same content.
Unix_epoch_ms / 1000,
so group boundaries fall on exact second boundaries.
How to Use
Multiple ways to connect and stream content
🌐 Browser Player (Recommended)
Use MOQ Player (warp-player v0.16) for instant playback in Chrome, Edge, Firefox, or Safari 26.4+. No installation required. It speaks MoQ Transport draft-18, discovers what is on offer with SUBSCRIBE_NAMESPACE, and ships an MSE / EME engine that plays both CMAF and LOCMAF tracks (the LOCMAF decoder reconstructs CMAF chunks before they reach MSE) plus a WebCodecs engine for LOC and moq-mi tracks — selectable from the UI. The Subtitles / CC selector shows either the in-band CTA-608 captions, on either engine for AVC and HEVC tracks, or a subtitle track — TTML, WebVTT or their paint-model variants, in CMAF or LOCMAF.
💻 Command Line Client
For developers and advanced users, use mlmsub to stream from the terminal:
# Install the client go install github.com/Eyevinn/moqlivemock/cmd/mlmsub@latest # Stream clear CMAF (default namespace: mlm/cmsf/clear) mlmsub -addr moqlivemock.demo.osaas.io -videoname _avc -audioname _aac -muxout - | ffplay - # Dump the unified mlm/cmsf/clear catalog — each rendition appears as a CMAF track # and a LOCMAF track (the '_locmaf' suffix), sharing init data via initRef mlmsub -addr moqlivemock.demo.osaas.io -namespace mlm/cmsf/clear -catalogout - # Stream the LOCMAF variant — chunks are reconstructed into CMAF on the receiver mlmsub -addr moqlivemock.demo.osaas.io -namespace mlm/cmsf/clear -videoname _avc_locmaf -audioname _aac_locmaf -muxout - | ffplay - # Stream the AV1 rendition (CMAF packaging, AV1 + AAC) mlmsub -addr moqlivemock.demo.osaas.io -videoname _av1 -audioname _aac -muxout - | ffplay - # Stream LOCMAF under ClearKey/ECCP (cbcs) mlmsub -addr moqlivemock.demo.osaas.io -namespace mlm/cmsf/eccp-cbcs -videoname _eccp_locmaf -audioname _eccp_locmaf -muxout - | ffplay - # Write the LOCMAF TTML subtitle track to a file, expanded back to CMAF mlmsub -addr moqlivemock.demo.osaas.io -videoname NONE -audioname NONE -subsname subs_stpp_en_locmaf -subsout subs.mp4 # Stream LOC-packaged content (raw frames, AVC + AAC) and write to files mlmsub -addr moqlivemock.demo.osaas.io -namespace mlm/msf/clear -videoout video.h264 -audioout audio.aac # Discover announced namespaces on a relay mlmsub -addr moqlivemock.demo.osaas.io -discover # Stream with WebTransport (selected by the https:// scheme) and debug logging mlmsub -addr https://moqlivemock.demo.osaas.io:443 -loglevel debug -muxout - | ffplay -
The cmd/locmaf tool in moqlivemock includes a roundtrip subcommand
that encodes a fragmented MP4 through the LOCMAF encoder/decoder and verifies sample-level
fidelity — handy for running the LOCMAF wire-cost measurements yourself.
🔧 Run Your Own Server
Clone the repository and run your own MoqLiveMock instance:
# Clone and build git clone https://github.com/Eyevinn/moqlivemock cd moqlivemock go build ./cmd/mlmpub # Option 1: Using mkcert (recommended for development) mkcert -key-file key.pem -cert-file cert.pem localhost 127.0.0.1 ::1 mkcert -install ./mlmpub -cert cert.pem -key key.pem -addr localhost:4443 # Option 2: Using fingerprint (no cert installation needed) ./mlmpub -sideport 8081 # With in-band CTA-608 captions (this demo runs with -cc608) ./mlmpub -sideport 8081 -cc608 # Pick the CTA-608 presentation style (default: paint-on) ./mlmpub -sideport 8081 -cc608 -cc608mode roll-up2 # With the experimental paint-model subtitle tracks (stpc, wvtc) ./mlmpub -sideport 8081 -subsstpc en -subswvtc sv # With ClearKey/ECCP encryption ./mlmpub -sideport 8081 \ -kid 00112233445566778899aabbccddeeff \ -iv 00000000000000000000000000000001 \ -scheme cbcs -laurl http://localhost:8081/clearkey
When using fingerprint mode, connect with MOQ Player using:
• Server URL: https://localhost:4443/moq
• Fingerprint URL: http://localhost:8081/fingerprint
The -sideport enables an HTTP server for both /fingerprint and /clearkey endpoints.