Skip to content

fix(ts)!: run the export one delay behind its source - #5417

Closed
t0ms wants to merge 1 commit into
moq-dev:quest/m1/ts-atsc-ac3from
t0ms:quest/m1/tstd/send-ahead
Closed

t0ms wants to merge 1 commit into
moq-dev:quest/m1/ts-atsc-ac3from
t0ms:quest/m1/tstd/send-ahead

Conversation

@t0ms

@t0ms t0ms commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #5306; draft until it merges.

Problem

moq export ts held each frame for --delay, then sent it up to another --delay ahead of its decode time to fill the receiver's buffers. Its output therefore trailed the source by about twice the delay, where an SRT receiver at the same latency trails by the latency.

Approach

  • A frame is released at anchor + DTS + delay less its send-ahead. The jitter buffer orders frames on keys a delay past those release times (decode + delay - send-ahead), a uniform shift that never saturates at the start of the timeline.
  • Each PID's send-ahead is the delay, capped by its decoder buffer's reach: EB from the SPS NAL/VCL HRD (CPB over bit rate) or the level's MaxCPB over MaxBR for video, B for audio (about 14 ms, under one 25 ms slot, so none).
  • The schedule releases a unit no earlier than its send-ahead before its due slot, and settles a slot once every frame that could ride it has left the jitter buffer. The slot clock keeps running while the jitter buffer still holds frames, so a group skipped mid-burst no longer stalls the output.
  • Padded output whose video declares a longer HRD buffer delay than the delay fails at the first keyframe, naming the --delay it needs. A unit that still misses its slot names the delay or the multiplex rate.
  • --delay defaults to 1 s, which a broadcast-sized CPB (9 Mbit at 9 Mb/s) needs. SRT egress muxes at its latency or 1 s, whichever is longer.

Impact

  • moq export ts --delay: default 500 ms -> 1 s, and the delay now bounds the whole trail, send-ahead included.
  • moq export ts: padded HRD video whose buffer delay exceeds --delay fails with raise the delay to <d>, where it used to run.
  • Sources that send no track ahead of its decode time (RTMP, browsers) trail by the delay plus the video's send-ahead: 2 s at the new default, 1 s with --delay 500ms.
  • moq-srt egress: the export's delay is the SRT latency or 1 s, whichever is longer. Server::bind keeps its signature.
  • moq_mux TS Export::with_delay: same signature, the delay now includes the send-ahead, and the error above is new.
  • test/ts/pcr-timing.py --drift-ms defaults to 1000, the new delay.
  • No wire change.

Results

Two feeds of a broadcast capture through one relay at 100 ms RTT, 40 s per cell, delivery latency from the source tap to the egress tap (median / p95 of each feed):

--delay main b581c27e #5306 a5640abe this branch
1 s 1897 / 1969, 1931 / 2003 ms 1878 / 1950, 1919 / 1991 ms 1232 / 1305, 1253 / 1325 ms
2 s 3896 / 3966, 3896 / 3966 ms 3881 / 3956, 3974 / 4049 ms 2140 / 2212, 2140 / 2211 ms

Over 300 s at --delay 1s, main reads 1877 and 1911 ms and this branch 1183 ms for both feeds. No video was lost and no frame missed its deadline in any cell. What remains above the delay is consistent with this capture sending its video less far ahead than the cap allows, so the clock anchors on the video. just test ts --hrd --strict passes at the 1 s default (EB peaks at 100 %, worst buffer delay 1.05 s), and with --delay 500ms the export stops with MPEG-TS video on PID 256 declares a 1.000s decoder buffer delay (its HRD's CPB over its bit rate), past what a 500ms delay sends it ahead; raise the delay to 1s.

New tests: padded_video_needs_the_delay_its_hrd_declares (an SPS with a 9 Mbit CBR NAL HRD is refused at 500 ms and runs on time at 1 s), sps_hrd_reads_the_nal_hrd, the schedule's reach and refusal cases, and a_joiner_runs_at_the_delay_from_its_first_output tightened to one delay.

Alternatives

  • Anchor every feed at exactly the delay. A source that sends video at its decode time would then leave each picture no time to arrive before its release, so such feeds trail by the send-ahead on top instead.
  • Give audio a send-ahead from its B buffer. It holds about 14 ms at Rx, under one slot, so it would change nothing.
  • Keep SRT egress at the SRT latency alone (500 ms by default). Broadcast-sized CPBs would then be refused on every default egress; a separate egress option adds surface for the same default.

Follow-ups

  • The release clock's source-rate estimate (drift_ppm on the TS export release clock line) is noisier on audio/video sources whose video arrives less far ahead than its cap. The video then has the least slack, so the steering follows its best phase in each 2 s step, which moves with the pictures in that step. On the broadcast capture it still reads about -1,100 ppm 60 s into a loop of the clip, where fix(ts): schedule padded output at exact decoder times #5306 settles within 10 ppm. The applied drift is slew-limited to 0.075 Hz/s, so output timing is unchanged in these runs. Steering on each track's own envelope would settle it.

(Written by Claude Opus 5.5)


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

`moq export ts` held each frame for `--delay` and then sent it up to another
`--delay` ahead of its decode time, so its output trailed the source by about
twice the delay. A frame is now due at its release time: anchor + DTS + delay
less its send-ahead, where the send-ahead comes out of the same delay and is
capped by the PID's decoder buffer (EB from the SPS HRD or the level, B for
audio). Padded video whose HRD declares a longer buffer delay than the delay
fails the export, naming the `--delay` it needs.

The default `--delay` rises from 500 ms to 1 s, which fits a broadcast-sized
CPB, and SRT egress muxes at its latency or 1 s, whichever is longer.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated deleted the branch moq-dev:quest/m1/ts-atsc-ac3 October 13, 2026 22:08
@kixelated kixelated closed this Oct 13, 2026
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