fix(moq-mux): author DTS for B-frame MPEG-TS export#1843
Conversation
The TS exporter set `dts: None` on every PES, so B-frame streams carried no decode timeline. Players saw PTS-only video with out-of-order decode times and needed `ffplay -fflags +igndts` to play (reproduced on a real CNN 1080i contribution feed through `moq pub | relay | moq sub`). MoQ groups and frames are delivered in decode order, so the only job is keeping DTS monotonic. A forward-only decode clock runs DTS_RESERVE ticks (0.25 s) behind the PTS and never goes backwards: a reordered frame whose PTS dips below the clock is nudged one tick past the last DTS. The reserve gives the reorder room to land under each frame's own PTS (DTS <= PTS) for broadcast B-frame spans; it is decode lead time, not presentation latency (frames still show at their PTS), so it is effectively free. No buffering, no estimation, no startup latency. PCR follows the decode clock too. The PES optional header grows by 5 bytes when it carries a DTS. No public API or wire/format change. Closes #1836 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
WalkthroughThe MPEG-TS muxer's export path gains DTS (Decode Timestamp) authoring for reordered video frames. Each 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
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. Comment |
There was a problem hiding this comment.
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/moq-mux/src/container/ts/export.rs`:
- Around line 939-949: In the `author_dts` function, add wrap-around detection
before the monotonic check at line 942. When `pts` is significantly smaller than
the stored previous DTS (indicating a 33-bit wrap-around has occurred), reset or
adjust the monotonic state to prevent DTS from exceeding the newly wrapped PTS
value. Use a signed distance comparison similar to the `PtsUnwrap` logic
mentioned in the comment (comparing against a HALF threshold) to detect when the
PTS wraps from a high value to a low value, then handle this case by resetting
the `last` state or adjusting the monotonic bump calculation to maintain the DTS
≤ PTS invariant across the wrap boundary.
🪄 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: a89082ce-6974-46b3-9fbd-0b197bdf09a1
📒 Files selected for processing (2)
rs/moq-mux/src/container/ts/export.rsrs/moq-mux/src/container/ts/export_test.rs
Run the decode clock in the continuous (unwrapped) 90 kHz tick domain that the source timestamps already use, and wrap into the 33-bit wire field only at emission. Previously the clock ran in wrapped 33-bit space, so a 24/7 feed broke at the ~26.5 h wrap: PTS dropped to ~0, the monotonic bump kept DTS high, and DTS > PTS. The clock now never wraps mid-stream, so DTS <= PTS holds across the boundary; PTS and DTS still wrap together on the wire. Also bump DTS_RESERVE from 0.25 s to 1 s so the reserve covers any broadcast B-frame structure. It is decode lead time, not presentation latency. Adds a wrap-boundary unit test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the fixed 1s DTS_RESERVE with a frame-based reserve so the advertised decode buffer scales with the frame rate instead of a flat wall-clock slab. DtsClock learns the frame duration from the smallest PTS gap (adjacent frames are one frame apart) and runs the decode clock RESERVE_FRAMES frame-durations behind the PTS. The reserve must exceed the stream's reorder depth, which open-GOP / B-pyramid structures push past the raw consecutive-B count: the kyrion 1080i contribution feed reorders 5 frames deep with only 3 consecutive B-frames. RESERVE_FRAMES = 8 covers that plus typical pyramids; a deeper stream still gets a monotonic DTS (the +igndts fix), just without a guaranteed DTS <= PTS. The reserve is decode lead time, not presentation latency (frames still show at their PTS). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ead) Revert the frame-scaled reserve to a fixed 16-tick (90 kHz) DTS_RESERVE: just a strict-monotonic nudge so the decode timeline stays strictly increasing (the +igndts fix) with negligible decode lead. Drops the frame-duration tracking. This trades the DTS <= PTS guarantee: at 16 ticks the reserve cannot cover the reorder span, so a B-frame's DTS sits above its own PTS. Monotonic DTS is what players need to schedule decoding; exact DTS <= PTS is the faithful wire-DTS follow-up. Tests assert monotonicity accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem
The MPEG-TS exporter (
rs/moq-mux/src/container/ts/export.rs) setdts: Noneon every PES, so B-frame streams carried no decode timeline. Players saw PTS-only video with out-of-order decode times and neededffplay -fflags +igndtsto play. Reproduced on a real CNN 1080i contribution feed throughmoq pub | relay | moq sub. Closes #1836.Fix
MoQ groups and frames are already delivered in decode order, so the only job is keeping DTS strictly increasing — no need to reconstruct decode order.
author_dtsruns a decode clock a hair (DTS_RESERVE, 16 ticks ≈ 0.18 ms) behind the PTS and never lets it go backwards: a reordered frame whose PTS dips below the clock is nudged one tick past the last DTS. That monotonic DTS is exactly what removes the+igndtsrequirement.write_pes, so PTS and DTS wrap together and the clock stays monotonic across the ~26.5 h boundary (caught in review).NonewhenDTS == PTS, so the PES only grows when a DTS is actually present.Scope of the guarantee
This authors a monotonic DTS, which is what the issue is about. The 16-tick reserve is deliberately near-zero (no added decode latency), so it does not guarantee
DTS ≤ PTS: a B-frame's DTS can sit above its own PTS by up to the reorder span. Lenient decoders (decode in stream order, present by PTS) are unaffected and no longer need+igndts; strictDTS ≤ PTSconformance is the faithful follow-up — the source TS already carries the exact DTS, which the importer currently discards, and carrying thatPTS − DTSoffset end-to-end (over the wire, to survivepub → relay → sub) is the dev-targeted change noted in #1836.Notes for reviewers
author_dtsand the per-tracklast_dtsare private), so this targetsmain.write_pessizes it accordingly.ptsonly and ignoresdts(verified); byte-exact round-trip tests pass.Test plan
author_dtsunit tests: strictly increasing across reordered (B-frame) decode order; the no-reorder case trails PTS by exactly the reserve; a 33-bit wrap-boundary test.kyrion_dirtystart.ts(H.264 1080i, ~86 B-frames), exports, and asserts the video PES carry a strictly increasing decode timeline.moq-muxsuite (247 tests),cargo clippy, andcargo fmtclean (via nix).(Written by Claude)