Skip to content

feat(moq-mux, libmoq): add CMSF muxer, demuxer, and C API#1408

Open
ksletmoe-aws wants to merge 1 commit into
moq-dev:mainfrom
ksletmoe-aws:cmsf-muxer
Open

feat(moq-mux, libmoq): add CMSF muxer, demuxer, and C API#1408
ksletmoe-aws wants to merge 1 commit into
moq-dev:mainfrom
ksletmoe-aws:cmsf-muxer

Conversation

@ksletmoe-aws
Copy link
Copy Markdown
Contributor

@ksletmoe-aws ksletmoe-aws commented May 14, 2026

Summary

Adds full CMSF (CMAF Streaming Format) support to the MoQ stack — a muxer (broadcast producer), demuxer (broadcast consumer), fMP4-to-CMSF bridge, and C FFI bindings.

What is CMSF?

CMSF is a profile of CMAF for live streaming over MoQ. It defines how moof+mdat fragments map to moq-lite groups/objects, how catalogs describe tracks with SAP types and init segments, and how group boundaries align with video keyframes.

Changes

New: CMSF Broadcast Producer (import/cmsf_broadcast.rs)

  • Multi-rendition publishing with automatic group lifecycle management
  • Video keyframes drive group boundaries; non-switching audio aligns to video
  • Audio pre-roll buffering (audio before first video keyframe is buffered and flushed)
  • Explicit group mode for caller-controlled boundaries (e.g. dual-pipeline redundancy)
  • SAP type derivation from codec config (H264/VP8 → Type 1, H265/AV1/VP9 → Type 2)
  • Jitter tracking from inter-sample PTS deltas, published in catalog
  • Publishes both MSF catalog (catalog) and optionally a hang catalog (catalog.json)

New: CMSF Demuxer (export/cmsf.rs)

  • CmsfTrackDemuxer: per-track async consumer yielding CmsfSegment
  • CmsfBroadcastDemuxer: broadcast-level coordinator managing catalog subscriptions
  • Handles catalog updates (track addition/removal), malformed frame protection
  • build_merged_init(): merges per-track init segments into a multi-track fMP4 init with track ID renumbering

New: moq-cli subscribe --output cmsf-fmp4

  • CMSF-aware fMP4 output that reads the MSF catalog directly (no hang catalog required)
  • Builds merged init segment from per-track init data, then passes through raw moof+mdat segments

New: fMP4 → CMSF Bridge (import/cmsf_fmp4.rs)

  • CmsfFmp4Importer: parses fragmented MP4 stdin, auto-detects codecs, feeds CMSF producer
  • Supports H.264, H.265, VP8, VP9, AV1 video and AAC, Opus audio
  • PublishDecoder::finish(): explicit cleanup on stdin EOF to close tracks/catalogs cleanly

New: Shared CMSF Parser (cmsf.rs)

  • parse_cmsf_metadata(): extracts PTS (with CTS offset), duration, keyframe from moof+mdat
  • parse_timescale(): extracts timescale from init segment

New: C FFI (libmoq/src/api.rs, libmoq/src/publish.rs)

  • moq_cmsf_create, moq_cmsf_add_video, moq_cmsf_add_audio, moq_cmsf_write, moq_cmsf_close

Refactored: fMP4 Parser (import/fmp4_parser.rs)

  • Extracted the moov/moof/mdat state machine into a reusable Fmp4Parser<S: Fmp4Sink> trait
  • Both the existing Fmp4 importer and new CmsfFmp4Importer share this parser
  • No behavioral changes to existing fMP4 import path

MSF Catalog Extensions (moq-msf)

  • Added generated_at, max_grp_sap_starting_type, max_obj_sap_starting_type fields
  • Backward compatible (all new fields are optional, old catalogs parse fine)

TypeScript (js/watch)

  • Video/audio decoders now fall back to init segment description when catalog lacks one (MSF/CMSF path)

Demo

  • Added just bbb-cmsf and just cmsf commands for publishing via CMSF format

Testing

  • 159 tests across moq-mux, libmoq, moq-msf, moq-cli — all passing
  • Unit tests cover: SAP type derivation, jitter calculation, group lifecycle, explicit/auto mode, catalog serialization, fMP4 parser, codec detection, malformed frame handling
  • Integration test with real media (bbb.mp4) verifying end-to-end codec detection, init segment generation, and fragment splitting

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds end-to-end CMSF support: extends MSF catalog schema with CMSF fields, adds CMSF parsing utilities, refactors the fMP4 decoder into a parser/sink, implements CMSF broadcast producer and per-track producers with grouping/SAP/jitter logic, provides an fMP4→CMSF importer and CMSF demuxers (including merged-init builder), exposes Rust Publish APIs and C FFI for CMSF, integrates CMSF into moq-cli and demo recipes, and updates JS decoders. The change includes unit and integration tests covering parsing, import, publish, demux, and error cases.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding CMSF muxer, demuxer, and C API support. It directly matches the changeset's primary objective.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, detailing what CMSF is, all major components added/changed, and testing coverage. It clearly describes parts of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (4)
rs/moq-mux/src/import/test/mod.rs (1)

131-271: 💤 Low value

Consider extracting magic numbers to named constants.

Several magic numbers appear throughout the new tests without clear semantic names:

  • 90000, 48000 (timescales)
  • 0x0200_0000 (appears multiple times, likely a SAP type flag)
  • 3003 (duration/timestamp)
  • 1024 (frame size)
  • 10, 3 (test iteration limits)

While test code context makes some of these clear, named constants would improve readability and maintainability, especially for the hex constant 0x0200_0000 which appears in multiple tests.

As per coding guidelines, "Avoid using magic numbers; use named constants instead."

💡 Example improvement
// At module level
const VIDEO_TIMESCALE: u32 = 90000;
const AUDIO_TIMESCALE: u32 = 48000;
const SAP_TYPE_1_FLAG: u32 = 0x0200_0000;  // SAP type 1 (closed GOP)
const TEST_FRAME_DURATION: u64 = 3003;
const TEST_FRAME_SIZE: u64 = 1024;

// Then use in tests:
let init_data = crate::cmsf::test_helpers::build_init_segment(VIDEO_TIMESCALE);
let frame = crate::cmsf::test_helpers::build_moof_mdat(0, TEST_FRAME_DURATION, SAP_TYPE_1_FLAG);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/import/test/mod.rs` around lines 131 - 271, Tests use several
unexplained magic numbers (timescales, SAP flag, durations, sizes, loop limits);
extract these into module-level named constants and replace literals in tests
like test_cmsf_producer_consumer_roundtrip,
test_cmsf_demuxer_skips_malformed_frames, and
test_cmsf_demuxer_end_reason_track_finished. Define constants (e.g.,
VIDEO_TIMESCALE, AUDIO_TIMESCALE, SAP_TYPE_1_FLAG, TEST_FRAME_DURATION,
TEST_FRAME_SIZE, MAX_ITERATIONS) and use them where calling
crate::cmsf::test_helpers::build_init_segment, build_moof_mdat, and in
loop/assert limits instead of hard-coded numbers to improve readability and
reuse.
rs/moq-mux/src/import/fmp4.rs (1)

447-453: ⚡ Quick win

Remove the em dashes from this doc comment.

This comment uses , which the repository prose rules forbid. Please replace them with commas or separate sentences.

As per coding guidelines, "No em dashes (—) in code, comments, doc comments, commit messages, or any prose. Use a period and start a new sentence, or use a comma/parenthesis if clauses are tightly bound".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/import/fmp4.rs` around lines 447 - 453, The doc comment
describing the AAC AudioSpecificConfig uses em dashes (—); update that comment
block (the lines mentioning audioObjectType, samplingFreqIndex,
samplingFrequency, and channelConfiguration) to remove all em dash characters
and replace them with commas or separate sentences per project prose rules so
the entries read like normal comma- or sentence-separated descriptions (e.g.,
"samplingFreqIndex (4 bits), index into the standard table, or 0xF" or break
into two sentences) and ensure the example line remains unchanged except for
replacing any em dash occurrences.
rs/moq-mux/src/import/cmsf_fmp4.rs (1)

99-101: ⚡ Quick win

Replace the em dashes in these doc comments.

The repository prose rules disallow in comments and doc comments. Please split these into separate sentences or use commas/parentheses instead.

As per coding guidelines, "No em dashes (—) in code, comments, doc comments, commit messages, or any prose. Use a period and start a new sentence, or use a comma/parenthesis if clauses are tightly bound".

Also applies to: 233-235

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/import/cmsf_fmp4.rs` around lines 99 - 101, Replace all em
dashes in the doc comments with periods or commas and split clauses into
separate sentences; specifically update the NOTE comment that currently reads
"All video tracks are assigned `alt_group: Some(1)` — this assumes a single ABR
ladder where all video renditions are switchable. Multi-angle or independent
video tracks are not supported via the fMP4 import path." (and the similar
comment at the other occurrence on lines 233-235) so it becomes two sentences or
uses a comma/parenthesis (e.g., "All video tracks are assigned `alt_group:
Some(1)`. This assumes a single ABR ladder where all video renditions are
switchable. Multi-angle or independent video tracks are not supported via the
fMP4 import path."). Ensure no em dash characters remain in cmsf_fmp4.rs doc
comments.
rs/moq-mux/src/import/cmsf_types.rs (1)

40-44: ⚡ Quick win

Replace the em dashes in the new comments.

The added doc/test comments on these lines use em dashes, which conflicts with the repo prose rule. Please switch them to commas, parentheses, or separate sentences.

As per coding guidelines, **/*.{rs,ts,tsx,js,jsx,md}: No em dashes (—) in code, comments, doc comments, commit messages, or any prose. Use a period and start a new sentence, or use a comma/parenthesis if clauses are tightly bound.

Also applies to: 51-55, 516-516

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/import/cmsf_types.rs` around lines 40 - 44, Replace all em
dashes in the doc comments in cmsf_types.rs with commas, parentheses, or
separate sentences; specifically update the doc block that begins "A CMSF Object
— the moof+mdat payload..." and the sentence that references
CmsfBroadcastProducer::write() to remove the em dash and rephrase using a comma
or period, and also fix the other doc/test comments in the same file (the
subsequent comment block around the same description and the comment near the
end of the file that currently uses an em dash) so no em dash remains anywhere
in those comment blocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@js/watch/src/audio/decoder.ts`:
- Around line 274-277: The isConfigSupported probe (the function that checks
supported(config)) must mirror runCmafDecoder's fallback: when config.codec !==
"opus" and config.description is absent, use the init segment's description as
the codec description before probing support. Update
isConfigSupported/supported(config) to obtain description = config.description ?
Util.Hex.toBytes(config.description) : init.description (keeping the opus case
returning undefined) and pass that into the support probe so CMAF audio without
catalog description is treated the same way as runCmafDecoder.

In `@js/watch/src/video/decoder.ts`:
- Around line 365-369: The comment above the description assignment contains an
em dash; remove the em dash and split into two sentences per guidelines. Edit
the comment that mentions "MSF/CMSF catalogs don't carry a separate description
field — the codec description..." so it becomes two sentences (e.g., "MSF/CMSF
catalogs don't carry a separate description field. The codec description (avcC,
hvcC, etc.) lives inside the init segment's moov box.") and leave the following
code using this.config.description, Util.Hex.toBytes, and init.description
unchanged.

In `@rs/libmoq/src/publish.rs`:
- Around line 122-123: The conversion currently does an unchecked cast (`tid.0
as u32`) that can silently truncate large TrackId values; replace it with a
checked conversion and checked add: use u32::try_from(tid.0)? (or
TryFrom::try_from(tid.0)?) to convert tid.0 to u32, then use
.checked_add(1).ok_or(...) to detect overflow before calling Id::try_from on the
incremented value; apply the same fix to the other occurrence that uses `tid.0
as u32` (the second instance around lines 151–152) so both use checked
conversion + checked_add before Id::try_from.

In `@rs/moq-cli/src/publish.rs`:
- Line 47: The inline comment containing "CMSF manages its own catalog tracks —
skip catalog::Producer." uses an em dash which violates the prose rules; edit
that comment near the publish.rs code (around the CMSF/catalog::Producer
mention) to remove the em dash and rephrase using a period or comma—for example:
"CMSF manages its own catalog tracks. Skip catalog::Producer."—so the comment no
longer contains an em dash.

In `@rs/moq-mux/src/cmsf.rs`:
- Around line 67-76: The loop currently coercing missing durations to zero will
hide incomplete metadata; change the logic in the fragment-duration calculation
(around traf.tfhd.default_sample_duration, trun.entries and entry.duration) to
treat "no default and no entry.duration" as an error instead of using 0:
validate each entry's duration by computing duration_value =
entry.duration.or(traf.tfhd.default_sample_duration).ok_or(<meaningful error>)?
and propagate/return that error from the surrounding function (or convert to a
Result) so incomplete sample durations are rejected rather than silently counted
as zero.
- Around line 23-48: Add the #[non_exhaustive] attribute to the public error
enum CmsfError so future variants are non-breaking: place #[non_exhaustive]
immediately above the existing #[derive(...)]/pub enum CmsfError declaration and
run cargo check to ensure no local exhaustive matches require wildcard updates
(e.g., update any pattern matches on CmsfError to include a catch-all `_` if
needed).

In `@rs/moq-mux/src/export/cmsf.rs`:
- Around line 226-243: The current loop over self.tracks.values_mut() always
walks the HashMap in the same order so one ready track can monopolize output;
change polling to use a round-robin cursor or ready queue instead. Add a cursor
field (e.g., round_robin_index or last_polled_key) to the demuxer struct and
iterate tracks starting from that cursor (wrap around) when calling
state.demuxer.poll_next(waiter), advancing the cursor to the next track after
each poll and updating it when you return a segment or when a track becomes
inactive, or alternatively maintain a VecDeque of active track keys and pop/push
keys to rotate them after polling; update uses of self.tracks and
state.demuxer.poll_next accordingly so each call continues from the next track
rather than the HashMap's fixed order.
- Around line 113-116: The code in the poll loop currently treats
Poll::Ready(Err(_)) from poll_read_frame() as a normal group boundary by
clearing self.current_group and continuing; instead, preserve and propagate the
error or mark the track ended: when poll_read_frame() returns Err(e) in the
block handling group reads, do not just set self.current_group = None and
continue—instead set the track end reason (e.g. self.end_reason =
Some(EndReason::TransportError(e))) and stop the track (e.g. call the track-stop
logic used elsewhere, or return Poll::Ready(Err(e)) so the caller sees the
transport error). Update the branch handling Poll::Ready(Err(_)) to either
return the error up from poll_read_frame() or to set end_reason and invoke the
existing track shutdown/stop method instead of silently skipping the rest of the
group.

In `@rs/moq-mux/src/import/cmsf_broadcast.rs`:
- Around line 229-235: The code currently calls self.flush_audio_buffer() /
self.start_non_switching_audio_groups() inside the is_video_keyframe branch,
which causes audio rotation to run for every video rendition; instead add a
broadcast-level latch keyed to the shared GOP/boundary (e.g. introduce a field
like last_broadcast_boundary_id or rotated_for_boundary flag on the struct) and
compute a unique boundary id from the shared group transition; then in the
is_video_keyframe branch only perform flush_audio_buffer() or
start_non_switching_audio_groups() when that boundary id differs from the
latched value, and update the latch after rotating so subsequent renditions at
the same broadcast boundary skip rotation (leave references to
first_video_group, flush_audio_buffer(), start_non_switching_audio_groups(), and
is_video_keyframe to locate the change).
- Around line 154-171: The explicit non-switching audio branches return early
after calling track.track_object_sap(...) and track.update_jitter(...), skipping
the shared republish/catalog update logic found in the republish check at Lines
223-227; update these early-return paths (the blocks around the shown diff plus
the similar blocks at 181-190 and 324-337) to invoke the same
republish/catalog-update logic before returning (either by calling the same
function/routine used in the 223-227 republish check or by extracting that logic
into a helper and calling it), so MSF/hang catalogs get their SAP and jitter
metadata refreshed when fast-path audio is processed.

In `@rs/moq-mux/src/import/fmp4_parser.rs`:
- Around line 181-183: The emitted moof currently only carries resolved sample
flags while discarding resolved default_sample_duration and default_sample_size
from the trex resolution; update the code paths that build the emitted fragment
(the logic around default_sample_duration, default_sample_size, and
default_sample_flags in the fmp4 parsing/rewrite) to copy the resolved
default_sample_duration (and default_sample_size where appropriate) into the
emitted moof/sample entries so downstream parsers can recover per-sample timing;
ensure the same fix is applied to the other mirrored block around lines
referenced (the second occurrence that currently handles trex defaults at
291-304) so both places preserve the resolved defaults in the output fragment.
- Around line 227-239: The PTS calculation for each trun entry can produce a
negative value before casting to u64 (using dts and entry.cts), which currently
wraps to a huge timestamp; change the logic in the loop over trun.entries (where
pts is computed) to compute the sum as a signed i64, clamp it to zero if
negative, and then cast to u64 (e.g., compute let adjusted = dts as i64 +
entry.cts.unwrap_or_default() as i64; use 0 when adjusted < 0, otherwise
adjusted as u64) so negative CTS-adjusted PTSs do not wrap and corrupt
ordering/jitter.

---

Nitpick comments:
In `@rs/moq-mux/src/import/cmsf_fmp4.rs`:
- Around line 99-101: Replace all em dashes in the doc comments with periods or
commas and split clauses into separate sentences; specifically update the NOTE
comment that currently reads "All video tracks are assigned `alt_group: Some(1)`
— this assumes a single ABR ladder where all video renditions are switchable.
Multi-angle or independent video tracks are not supported via the fMP4 import
path." (and the similar comment at the other occurrence on lines 233-235) so it
becomes two sentences or uses a comma/parenthesis (e.g., "All video tracks are
assigned `alt_group: Some(1)`. This assumes a single ABR ladder where all video
renditions are switchable. Multi-angle or independent video tracks are not
supported via the fMP4 import path."). Ensure no em dash characters remain in
cmsf_fmp4.rs doc comments.

In `@rs/moq-mux/src/import/cmsf_types.rs`:
- Around line 40-44: Replace all em dashes in the doc comments in cmsf_types.rs
with commas, parentheses, or separate sentences; specifically update the doc
block that begins "A CMSF Object — the moof+mdat payload..." and the sentence
that references CmsfBroadcastProducer::write() to remove the em dash and
rephrase using a comma or period, and also fix the other doc/test comments in
the same file (the subsequent comment block around the same description and the
comment near the end of the file that currently uses an em dash) so no em dash
remains anywhere in those comment blocks.

In `@rs/moq-mux/src/import/fmp4.rs`:
- Around line 447-453: The doc comment describing the AAC AudioSpecificConfig
uses em dashes (—); update that comment block (the lines mentioning
audioObjectType, samplingFreqIndex, samplingFrequency, and channelConfiguration)
to remove all em dash characters and replace them with commas or separate
sentences per project prose rules so the entries read like normal comma- or
sentence-separated descriptions (e.g., "samplingFreqIndex (4 bits), index into
the standard table, or 0xF" or break into two sentences) and ensure the example
line remains unchanged except for replacing any em dash occurrences.

In `@rs/moq-mux/src/import/test/mod.rs`:
- Around line 131-271: Tests use several unexplained magic numbers (timescales,
SAP flag, durations, sizes, loop limits); extract these into module-level named
constants and replace literals in tests like
test_cmsf_producer_consumer_roundtrip, test_cmsf_demuxer_skips_malformed_frames,
and test_cmsf_demuxer_end_reason_track_finished. Define constants (e.g.,
VIDEO_TIMESCALE, AUDIO_TIMESCALE, SAP_TYPE_1_FLAG, TEST_FRAME_DURATION,
TEST_FRAME_SIZE, MAX_ITERATIONS) and use them where calling
crate::cmsf::test_helpers::build_init_segment, build_moof_mdat, and in
loop/assert limits instead of hard-coded numbers to improve readability and
reuse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 507de74b-a73a-460b-989f-cb7a02b564e2

📥 Commits

Reviewing files that changed from the base of the PR and between 3363c04 and 8e8dca0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • demo/pub/justfile
  • js/watch/src/audio/decoder.ts
  • js/watch/src/video/decoder.ts
  • rs/libmoq/src/api.rs
  • rs/libmoq/src/publish.rs
  • rs/moq-cli/src/publish.rs
  • rs/moq-msf/src/lib.rs
  • rs/moq-mux/Cargo.toml
  • rs/moq-mux/src/catalog/producer.rs
  • rs/moq-mux/src/cmsf.rs
  • rs/moq-mux/src/export/cmsf.rs
  • rs/moq-mux/src/export/mod.rs
  • rs/moq-mux/src/import/cmsf_broadcast.rs
  • rs/moq-mux/src/import/cmsf_fmp4.rs
  • rs/moq-mux/src/import/cmsf_types.rs
  • rs/moq-mux/src/import/fmp4.rs
  • rs/moq-mux/src/import/fmp4_parser.rs
  • rs/moq-mux/src/import/mod.rs
  • rs/moq-mux/src/import/test/mod.rs
  • rs/moq-mux/src/lib.rs

Comment thread js/watch/src/audio/decoder.ts Outdated
Comment thread js/watch/src/video/decoder.ts Outdated
Comment thread rs/libmoq/src/publish.rs Outdated
Comment thread rs/moq-cli/src/publish.rs
Comment thread rs/moq-mux/src/cmsf.rs
Comment thread rs/moq-mux/src/export/cmsf.rs
Comment thread rs/moq-mux/src/import/cmsf_broadcast.rs
Comment thread rs/moq-mux/src/import/cmsf_broadcast.rs
Comment thread rs/moq-mux/src/import/fmp4_parser.rs
Comment thread rs/moq-mux/src/import/fmp4_parser.rs
@ksletmoe-aws ksletmoe-aws force-pushed the cmsf-muxer branch 5 times, most recently from bbec117 to c0710d3 Compare May 14, 2026 22:06
@ksletmoe-aws ksletmoe-aws marked this pull request as draft May 14, 2026 23:12
@ksletmoe-aws ksletmoe-aws force-pushed the cmsf-muxer branch 5 times, most recently from 2a7f431 to 4764893 Compare May 15, 2026 17:15
@ksletmoe-aws ksletmoe-aws marked this pull request as ready for review May 15, 2026 17:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rs/moq-mux/src/export/mod.rs (1)

1-6: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update module doc to reflect both exports and remove em dash.

The module documentation only describes the Fmp4 functionality but the module now also exports the cmsf module. Additionally, line 5 contains an em dash (—) which violates the coding guideline. As per coding guidelines, no em dashes in doc comments; use a period or comma instead. As per coding guidelines, comments must reflect the current state of the code.

📝 Proposed fix
 //! Subscribe to a moq broadcast and decode media frames.
 //!
-//! [`Fmp4`] subscribes to a broadcast, decodes every track via
+//! ## Fmp4
+//!
+//! [`Fmp4`] subscribes to a broadcast, decodes every track via  
 //! [`Consumer<Hang>`](crate::container::Consumer), and yields a single fMP4 / CMAF byte
-//! stream — the merged init segment followed by moof+mdat fragments in
+//! stream. The merged init segment is followed by moof+mdat fragments in
 //! timestamp order across tracks.
+//!
+//! ## CMSF
+//!
+//! The [`cmsf`] module provides [`CmsfTrackDemuxer`](cmsf::CmsfTrackDemuxer) and 
+//! [`CmsfBroadcastDemuxer`](cmsf::CmsfBroadcastDemuxer) for consuming CMSF broadcasts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rs/moq-mux/src/export/mod.rs` around lines 1 - 6, Module docs only describe
Fmp4 and include an em dash; update the module-level comment in mod.rs to
mention both exported items (Fmp4 and the cmsf module) and remove the em dash by
replacing it with a period or comma. Edit the top doc comment so it briefly
states that the module exports Fmp4 (which subscribes to a broadcast and decodes
tracks via Consumer<Hang>) and also exports the cmsf module, and ensure
punctuation follows guidelines (no em dash).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rs/moq-cli/src/subscribe.rs`:
- Around line 61-64: The CLI currently constructs CmsfBroadcastDemuxer
unconditionally causing --max-latency to be silently ignored in CmsfFmp4 mode;
update subscribe.rs to detect when the selected mode is CmsfFmp4 (or when
creating moq_mux::export::cmsf::CmsfBroadcastDemuxer via
CmsfBroadcastDemuxer::new(self.broadcast)) and if a non-zero/max-latency value
is provided, return an explicit error (or print a clear warning and exit)
informing the user that --max-latency is not supported for CmsfFmp4 and
suggesting using CmsfTrackDemuxer or omitting the flag; this makes the failure
fast and prevents unbounded segment accumulation until demuxer-side skipping is
implemented.
- Line 67: Replace the explicit map_err stringification that discards error
sources with anyhow::Context on the demuxer calls: locate usages like
demuxer.ready().await.map_err(|e| anyhow::anyhow!("{e}")) and replace them with
demuxer.ready().await.context("failed to wait for demuxer ready") (and do the
same for the other occurrences on lines using .map_err(...) around
demuxer.open(...) and demuxer.subscribe(...)); this preserves the original error
chain while adding a clear contextual message; keep the context strings
descriptive and specific to the failing operation names (e.g., "opening
demuxer", "subscribing to demuxer").

---

Outside diff comments:
In `@rs/moq-mux/src/export/mod.rs`:
- Around line 1-6: Module docs only describe Fmp4 and include an em dash; update
the module-level comment in mod.rs to mention both exported items (Fmp4 and the
cmsf module) and remove the em dash by replacing it with a period or comma. Edit
the top doc comment so it briefly states that the module exports Fmp4 (which
subscribes to a broadcast and decodes tracks via Consumer<Hang>) and also
exports the cmsf module, and ensure punctuation follows guidelines (no em dash).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa2c7286-23fd-4f2e-9061-3d7d90987c55

📥 Commits

Reviewing files that changed from the base of the PR and between 8e8dca0 and 4764893.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • demo/pub/justfile
  • js/watch/src/audio/decoder.ts
  • js/watch/src/video/decoder.ts
  • rs/libmoq/src/api.rs
  • rs/libmoq/src/publish.rs
  • rs/moq-cli/src/publish.rs
  • rs/moq-cli/src/subscribe.rs
  • rs/moq-msf/src/lib.rs
  • rs/moq-mux/Cargo.toml
  • rs/moq-mux/src/catalog/producer.rs
  • rs/moq-mux/src/cmsf.rs
  • rs/moq-mux/src/export/cmsf.rs
  • rs/moq-mux/src/export/mod.rs
  • rs/moq-mux/src/import/cmsf_broadcast.rs
  • rs/moq-mux/src/import/cmsf_fmp4.rs
  • rs/moq-mux/src/import/cmsf_types.rs
  • rs/moq-mux/src/import/fmp4.rs
  • rs/moq-mux/src/import/fmp4_parser.rs
  • rs/moq-mux/src/import/mod.rs
  • rs/moq-mux/src/import/test/mod.rs
  • rs/moq-mux/src/lib.rs
✅ Files skipped from review due to trivial changes (1)
  • rs/moq-mux/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (18)
  • rs/moq-mux/src/lib.rs
  • js/watch/src/audio/decoder.ts
  • rs/moq-mux/src/catalog/producer.rs
  • demo/pub/justfile
  • rs/moq-mux/src/import/test/mod.rs
  • rs/moq-msf/src/lib.rs
  • rs/moq-mux/src/import/mod.rs
  • js/watch/src/video/decoder.ts
  • rs/moq-mux/src/cmsf.rs
  • rs/libmoq/src/api.rs
  • rs/moq-cli/src/publish.rs
  • rs/libmoq/src/publish.rs
  • rs/moq-mux/src/import/cmsf_types.rs
  • rs/moq-mux/src/import/fmp4_parser.rs
  • rs/moq-mux/src/import/cmsf_broadcast.rs
  • rs/moq-mux/src/export/cmsf.rs
  • rs/moq-mux/src/import/cmsf_fmp4.rs
  • rs/moq-mux/src/import/fmp4.rs

Comment thread rs/moq-cli/src/subscribe.rs Outdated
Comment thread rs/moq-cli/src/subscribe.rs Outdated
@ksletmoe-aws ksletmoe-aws force-pushed the cmsf-muxer branch 2 times, most recently from c257441 to f33c0c2 Compare May 15, 2026 22:17
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rs/libmoq/src/api.rs`:
- Around line 375-383: The function moq_cmsf_create currently calls
state.publish.cmsf_create before calling state.origin.publish, which can leak a
producer if origin.publish fails; fix this by reordering the operations so you
call state.origin.publish(origin_id, path, /*consumer placeholder?*/ ) first (or
perform whatever checks needed) and only call state.publish.cmsf_create() after
origin.publish returns Ok, or if you must create the producer first then ensure
you immediately rollback/close it (via the publish API) when
state.origin.publish returns Err; update moq_cmsf_create to either (a) move
state.publish.cmsf_create after state.origin.publish, or (b) call the
appropriate state.publish cleanup method with the returned id/consumer on error
from state.origin.publish to avoid leaving an unreachable producer.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d873f067-2e1c-4904-b650-a3fa58375a0f

📥 Commits

Reviewing files that changed from the base of the PR and between c257441 and f33c0c2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • demo/pub/justfile
  • js/watch/src/audio/decoder.ts
  • js/watch/src/video/decoder.ts
  • rs/libmoq/src/api.rs
  • rs/libmoq/src/publish.rs
  • rs/moq-cli/src/publish.rs
  • rs/moq-cli/src/subscribe.rs
  • rs/moq-msf/src/lib.rs
  • rs/moq-mux/Cargo.toml
  • rs/moq-mux/src/catalog/producer.rs
  • rs/moq-mux/src/cmsf.rs
  • rs/moq-mux/src/export/cmsf.rs
  • rs/moq-mux/src/export/mod.rs
  • rs/moq-mux/src/import/cmsf_broadcast.rs
  • rs/moq-mux/src/import/cmsf_fmp4.rs
  • rs/moq-mux/src/import/cmsf_types.rs
  • rs/moq-mux/src/import/fmp4.rs
  • rs/moq-mux/src/import/fmp4_parser.rs
  • rs/moq-mux/src/import/mod.rs
  • rs/moq-mux/src/import/test/mod.rs
  • rs/moq-mux/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (20)
  • rs/moq-mux/src/lib.rs
  • js/watch/src/audio/decoder.ts
  • rs/moq-mux/src/export/mod.rs
  • rs/moq-mux/src/catalog/producer.rs
  • rs/moq-cli/src/subscribe.rs
  • rs/moq-cli/src/publish.rs
  • rs/moq-mux/src/import/mod.rs
  • rs/moq-mux/Cargo.toml
  • rs/moq-msf/src/lib.rs
  • js/watch/src/video/decoder.ts
  • demo/pub/justfile
  • rs/moq-mux/src/import/cmsf_fmp4.rs
  • rs/moq-mux/src/import/test/mod.rs
  • rs/moq-mux/src/cmsf.rs
  • rs/moq-mux/src/import/fmp4_parser.rs
  • rs/moq-mux/src/export/cmsf.rs
  • rs/moq-mux/src/import/cmsf_types.rs
  • rs/libmoq/src/publish.rs
  • rs/moq-mux/src/import/cmsf_broadcast.rs
  • rs/moq-mux/src/import/fmp4.rs

Comment thread rs/libmoq/src/api.rs
Implements the full CMSF (Common Media Server Format) pipeline:

Import (publish):
- CmsfFmp4Importer: bridges fMP4 input to CMSF broadcast output
- CmsfBroadcastProducer: multi-rendition publisher with keyframe-aligned
  groups, audio pre-roll buffering, and SAP type derivation
- Fmp4Parser: shared fMP4 parsing infrastructure (ftyp/moov/moof+mdat)

Export (subscribe):
- CmsfBroadcastDemuxer: broadcast-level coordinator managing MSF catalog
  subscriptions and per-track demuxers
- CmsfTrackDemuxer: per-track async wrapper yielding CmsfSegment
- build_merged_init: merges per-track init segments into multi-track fMP4
- moq-cli: --output cmsf-fmp4 subscribe format (reads MSF catalog)

Also includes moq-msf CMSF fields, libmoq C API updates, and moq-gst
sink integration.
@ksletmoe-aws
Copy link
Copy Markdown
Contributor Author

Fixed in the latest push. moq_cmsf_create now rolls back the producer via cmsf_close(id) if origin.publish() fails, preventing the leaked handle.

Copy link
Copy Markdown
Collaborator

@kixelated kixelated left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ksletmoe-aws what's the end goal here? it's a lot of changes...

My thought is that we should support both hang/MSF via an intermediate catalog/playlist representation, so we don't copy-paste so much of this shared CMAF code. The hang catalog is the initial intermediate representation but that was just to avoid boilerplate. I really don't want two divergent implementations that could share a huge amount of code.

We definitely need some arg like --catalog msf to download catalog instead of catalog.json. Otherwise, we should be able to share the rest of the pipeline. MSF has some differences (ex. render_group, sap stuff) that could go into the intermediate representation, get ignored entirely, or get stubbed out for now (ex. render_group seems hard-coded?).

And yeah I think you need another library for building CMAF in C. I'm a bit worried about polluting the public API with something so limited. If you need the ability to set more specific fields on a per-track basis, the existing C API could be updated instead? Currently we try to auto-detect a lot of stuff from the raw codec bitstream (ex. width/height).

const description = this.config.description ? Util.Hex.toBytes(this.config.description) : undefined;
// MSF/CMSF catalogs don't carry a separate description field — the codec
// description (avcC, hvcC, etc.) lives inside the init segment's moov box.
const description = this.config.description ? Util.Hex.toBytes(this.config.description) : init.description;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should move the description so it's specific to the hang "legacy" container. Should be a separate PR of course.

Comment thread rs/libmoq/src/api.rs
/// # Safety
/// - All pointer+length pairs must be valid.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn moq_cmsf_add_video(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of arguments, this feels like it should be a struct.

And do you need this C API? IDK I feel like you might be better off using a different library to produce CMAF and pass it to the existing media endpoints. I reaaaaallly don't want to modify this function each time someone needs some new box added (ex. DRM).

Comment thread rs/libmoq/src/publish.rs
bitrate: u64,
framerate: f64,
timescale: u64,
init_segment: &[u8],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these fields could be inferred by the codec + init segment. But yeah it's more work.

Comment thread rs/libmoq/src/publish.rs
&mut self,
id: Id,
codec: &str,
description: Option<&[u8]>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want both the description and the init segment. They contain overlapping information.

(the description is a subset of the init segment)

Comment thread rs/libmoq/src/publish.rs
Id::try_from(tid.0 as u32 + 1)
}

#[allow(clippy::too_many_arguments)]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lul yeah it should be a struct at least.

let mut stdout = tokio::io::stdout();

if self.args.max_latency != Duration::from_millis(500) {
anyhow::bail!(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(


let ftyp = ftyp_data.ok_or(CmsfError::NoMoov)?;

// Renumber track IDs sequentially to avoid collisions when merging
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not be reusing the same init segment for multiple tracks. track_id=1 makes a lot of sense; MoQ requires demuxing.


// mvhd.timescale is the movie-level timescale (not per-track). Use 1000 (ms)
// which is the conventional value for fragmented MP4 with no movie-level duration.
let timescale = 1000;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conventional? Definitely not for audio

Comment thread rs/moq-msf/src/lib.rs
pub alt_group: Option<u32>,

/// Maximum SAP type at the start of any group (CMSF §3.4).
pub max_grp_sap_starting_type: Option<u32>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain these to me? I don't really follow the purpose in CMSF.

mod av01;
mod avc1;
mod avc3;
pub(crate) mod cmsf_broadcast;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO make a cmsf module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants