Skip to content

fix(mux): settle catalog estimates before they churn - #7161

Merged
kixelated merged 8 commits into
mainfrom
quest/m1/catalog-estimates
Oct 29, 2026
Merged

kixelated merged 8 commits into
mainfrom
quest/m1/catalog-estimates

Conversation

@kixelated

@kixelated kixelated commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #7067

Implements quest/m1/catalog-estimates.md and deletes it.

Summary

Hang quantizes jitter and delay rises. Rendition::estimate now takes a Reading: the Estimate plus a frame period. A measured jitter or delay rise republishes only once it reaches the next whole frame duration above the published value. The step is 1/framerate when the config has one, otherwise the shortest timestamp advance the estimator has seen. The value published is the one measured at that moment. The once-a-second throttle still applies on top, and until a period is known it is the only pacing. Measurement itself is unchanged.

MSF declares only hinted estimates, freezes them, and deduplicates. A new Msf state, kept under the catalog lock:

  • MSF declares only the bitrate, framerate, jitter and delay a publisher hinted, meaning the fields a rendition's config carried at set or that supply_bitrate supplied. A field with no hint stays absent from MSF, whatever hang measures. MSF publishes alongside hang from the start and never waits for a track to be measured, so demand-driven publishers can't deadlock it (see Decisions).
  • It records each MSF track's estimated fields the first time the track is declared, and rewrites later catalogs to those values (MSF-01 §5.3).
  • It skips writing an MSF group whose serialized catalog matches the last one. Hang already dedupes through moq_json.

Hang's catalog keeps its measured estimates.

Impact

  • MSF now requires hints for bitrate and framerate (and jitter and delay). A publisher that wants them in MSF must hint them: on the config, through a VideoHint, or through the moq-c and moq-ffi video hints. Audio through moq-c and moq-ffi has no hint, so its MSF tracks carry no bitrate. Hang viewers are unaffected.
  • On the wire there are fewer catalog groups, and MSF estimate fields stay at their first declaration.
  • Public API: none.

Tests

New tests in catalog/producer.rs:

  • a_creeping_delay_republishes_once_per_frame: delay creeping in 1 ms steps against a 10 ms period publishes 10, 20 and 30 only.
  • a_framerate_sets_the_delay_step
  • identical_msf_content_publishes_once
  • an_unhinted_track_declares_msf_at_once: an unhinted video track with no media written, as a demand-driven publisher's is, appears in MSF right away with no bitrate, framerate, jitter or delay. They stay absent after hang measures 25 fps and a bitrate.
  • a_hinted_track_carries_its_hints: hinted bitrate and framerate appear in MSF. A later measured delay and a supply_bitrate change reach hang but leave MSF as first declared.

Local checks (latest head):

  • cargo fmt --check, cargo clippy --workspace --all-targets --all-features -D warnings: clean.
  • cargo test -p moq-mux -p moq-audio -p moq-ffi -p moq-c --all-features: passes after merging main.
  • cargo test --workspace --all-features --no-fail-fast (before the merge): passes except moq-clock's paused_runtimes_own_their_anchors, which fails on main too, and kio's eviction_frees_whatever_readers_do, which fails only in workspace runs.

Decisions

  1. MSF is hints-only for estimates. Raised by Codex. An earlier head held MSF back until every unhinted track was measured. Demand-driven publishers (moq-video capture, moq-audio's on-demand mode, the moq-c and moq-ffi demand APIs) produce no media until someone subscribes. An MSF-only viewer finds their tracks only through MSF, so neither side moved. The maintainer decided (2026-10-28): "just stop detecting bitrate/framerate for MSF. Hints are required for MSF."
    • Publish MSF at once, then fill in each track's estimates once it is measured.
    • Leave out only the unmeasured tracks.
    • Demand-driven publishers opt out and settle at set.
    • Bounded wait.
    • ✅ Stop detecting bitrate and framerate for MSF; hints are required. The hold and its settle tracking are deleted.
  2. Unhinted video doesn't wait for its framerate window before MSF publishes (2026-10-28). Superseded by decision 1: MSF no longer waits for anything, and unhinted video declares no MSF framerate either way.
    • Keep the extra wait.
    • ✅ Follow the plan literally.

Choices made without asking

  • Hints-only covers jitter and delay too, not just bitrate and framerate. With no hold, a measured jitter or delay would freeze at whatever happened to be measured when the track was first declared, usually nothing. Declaring only hints makes all four fields behave the same way.
  • Hints apply to every rendition that measures its own estimates, including a custom RenditionConfig in ext, through its set_estimate. A rendition that doesn't measure (text, or a config written through mutate) goes to MSF as the catalog has it. An edit made through modify to a measuring rendition's estimate fields reaches hang but not MSF.
  • Quantize the trigger, not the value. Hang publishes the measured value once it crosses a step, rather than rounding up to the step. This avoids adding up to a frame of latency floor for every viewer. Once a period is known, a sub-frame value is held until it reaches one frame.
  • Frame duration is the shortest timestamp advance when no framerate is known, not the average. Media gaps (a timestamp jump, Opus DTX) inflate an average; the TS split-AAC jitter test caught this. For B-frame video with no framerate, the step is the reorder span. 1% slack keeps an exact one-frame reorder from missing its step to rounding.
  • The freeze covers all four fields on every MSF track, including references no rendition owns. This matches §5.3, which fixes declared attributes. With hints only, it matters when a hint changes, as supply_bitrate does.
  • flush_if_ready now emits under the state lock, as Guard::publish already did, because the MSF state lives there.

Suggested follow-ups

  • Catalog group timestamps can go backwards. Catalog groups are stamped with Timestamp::now(), so a publish from a different clock context fails with TimestampMismatch if it lands earlier than the previous group. The moq-ffi paused-time test hit this with an extra finish-time hang publish, which an earlier head removed. Stamping with max(now, last) would be more robust.

Two earlier follow-ups no longer apply. "Successor tracks hold MSF back" went with the hold. "Extension sections still churn MSF" is gone too, because hints now apply to ext renditions.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 3 commits October 28, 2026 15:43
Hang republishes a measured jitter or delay rise only once it reaches the
next whole frame duration above the published value: 1/framerate when the
config has one, else the shortest timestamp advance measured.

MSF waits until every unhinted video and audio track has closed a group
after a second of media (and measured its framerate window, for unhinted
video), declares its estimates once, freezes them, and skips writing a
catalog identical to the last one. A finished track stops holding MSF back.

Closes #7067

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Catalog estimates settle before they churn fix(mux): settle catalog estimates before they churn Oct 29, 2026
kixelated and others added 2 commits October 28, 2026 17:20
MSF publishes once every unhinted track has closed its first group and
carried a second of media, with no extra wait for the video framerate
window. Unhinted video now declares no framerate in MSF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review October 29, 2026 00:20
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 11 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f828f49e-317f-4129-9fe7-e1ffa0eb5fa0
Commits

Reviewing files that changed from the base of the PR and between 487759f and 980af56.

Files selected for processing: 11
  • quest/m1/README.md
  • quest/m1/catalog-estimates.md
  • rs/moq-c/src/api.rs
  • rs/moq-ffi/src/media.rs
  • rs/moq-mux/src/catalog/data.rs
  • rs/moq-mux/src/catalog/estimate.rs
  • rs/moq-mux/src/catalog/mod.rs
  • rs/moq-mux/src/catalog/producer.rs
  • rs/moq-mux/src/catalog/tracks.rs
  • rs/moq-mux/src/container/fmp4/import.rs
  • rs/moq-mux/src/container/producer.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-29T01:00:57.791325Z 980af56 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: bed9baf

P2 — Release an existing MSF hold when set supplies all estimates (rs/moq-mux/src/catalog/tracks.rs:694-697). The public container::Producer::set can replace an initially unhinted config with a fully hinted one before its measurement window closes. This sets self.settled = true but leaves its previously inserted key in State.msf.unsettled. Subsequent estimates no longer call settle, and even finish cannot clear it because Rendition::settle returns early for an already-settled rendition. While that enabled track stays listed, the entire MSF catalog remains withheld indefinitely. Clear the catalog-side hold when set transitions to settled, before the corresponding publish; add a regression for unhinted set → fully hinted set → continued media/finish.

Direction: keeping Hang's live estimates separate from MSF's first-declaration values and deduplicating serialized MSF output is sound, without adding configuration. The two copies of settlement state need to transition together.

Verification: static review of the changed estimator/catalog code and public container lifecycle, with an independent review of the gate path. No tests or playback checks run.

Replacing an unhinted config with one hinting every estimated field marked
the rendition settled but left the catalog waiting on it, withholding MSF
for as long as the track stayed listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Fixed the P2 from the automated review in 945ceae. set now syncs the catalog's MSF hold with the rendition's settled state both ways, so a fully hinted replacement releases it and the following commit publishes MSF. Regression test: a_fully_hinted_replacement_releases_msf.

(Written by Claude Opus 5.5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 945ceaedab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-mux/src/catalog/producer.rs Resolved

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: 945ceae

The prior P2 MSF settlement-hold finding is fixed: set now synchronizes the catalog-side hold with the rendition's settled flag before committing, and the new regression covers an unhinted → fully hinted replacement publishing MSF immediately. No new actionable bugs found in this delta.

Direction: a small, appropriate fix that keeps the existing settlement model rather than adding another publication path.

Verification: static review of the single-commit delta from bed9baf and surrounding set/settle code. No tests or playback checks run.

@kixelated
kixelated marked this pull request as draft October 29, 2026 00:34
kixelated and others added 2 commits October 28, 2026 17:57
MSF held its catalog back until every unhinted track was measured, which
deadlocks demand-driven publishers: they produce no media until a viewer
subscribes, and an MSF viewer finds their tracks only through MSF. MSF now
declares only the estimates a publisher hinted and publishes at once; hang
keeps its measured estimates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review October 29, 2026 01:00

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: 980af56

No new actionable bugs found in the hints-only MSF redesign. Removing the measurement gate addresses the independent P1 demand-driven discovery deadlock; the earlier P2 stranded settlement hold is also no longer applicable because settlement tracking is gone. The new undemanded-track regression checks immediate discovery without estimates, while hinted fields remain frozen after first declaration.

Direction: publishing the track list immediately and keeping live measurements in Hang is simpler than coordinating settlement with demand. Requiring explicit MSF hints is a meaningful compatibility choice, now documented in the bindings and PR impact. Hint registration/removal and supply_bitrate retain that separation.

Verification: static review of the substantive catalog delta since 945ceae, surrounding publication paths, tests and binding documentation, accounting for the main merge separately. No tests or playback checks run.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Landing this head (980af56) on the maintainer's approval.

  • Decision: the maintainer approved making jitter and delay hints-only in MSF too, with no measurement gate before publishing.
  • Review: the automated OpenAI review of this head (posted from the maintainer's account) found nothing actionable. The earlier P2 (a stranded MSF settlement hold) and the P1 (a demand-driven discovery deadlock) no longer apply after the hints-only redesign. Codex had posted no findings on this head when this was checked.
  • Checks: all 12 pass, and quest check --base origin/main passes.

(Written by Claude Opus 5.5)

@kixelated
kixelated added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit 5df6b85 Oct 29, 2026
12 checks passed
@kixelated
kixelated deleted the quest/m1/catalog-estimates branch October 29, 2026 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A catalog describes what a track is: let encoders state it, and keep channel measurements out

1 participant