Repository navigation
fix(watch): subscribe auto audio at the estimator's ceiling, replay through the estimator - #4117
Merged
kixelated merged 2 commits intoSep 25, 2026
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hrough the estimator In "auto" the audio subscription's max age followed the playout target, so the transport dropped every group later than the target before the container consumer could observe it, and the estimate crept up one bucket at a time while those frames went unplayed. Subscribe with at least the 2 s ceiling, as moq play does, and keep the target as the consumer's local skip budget. replay.test.ts now sizes the rings from the estimator itself, measured through a transport subscription and container consumer on a stubbed clock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
marked this pull request as ready for review
September 25, 2026 13:36
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
kixelated
merged commit Sep 25, 2026
157996f
into
quest/main/audio-jitter-target/README
3 checks passed
Collaborator
Author
|
No code changes. Marked the draft ready. Codex completed on (Written by Grok 4.7) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Works /quest/m0/audio-jitter-target/watch.md. Draft: the quest's manual Chrome/Safari run and the recorded-trace replay remain, and neither can be done from here.
Problem
In
"auto"the audio subscription's max age followed the playout target. The transport drops groups older than that budget beforeContainer.Consumersees them, both at the relay and in the receiver's ownTrack.Subscriber. So when a flush started after the target had settled, the estimator never saw the late frames. The target crept up one 20 ms bucket at a time, and the frames it should have measured went unplayed.Replaying a minute of paced audio followed by 100 ms flushes through the real subscription and consumer:
replay.test.tsalso sized the rings from a p95 approximation instead of from the estimator.Approach
audio/decoder.ts: in"auto"the subscription asks formax(sync.out.maxAge, AUTO_MAX_AGE), whereAUTO_MAX_AGEis the estimator's 2 s ceiling. This matchesmoq play --delay auto.Container.Consumerkeepssync.out.maxAgeas its local skip budget, since it observes each frame before applying it, so playback latency is unchanged.replay.test.ts:measure()feeds a trace throughTrack.Producer->subscribe->Container.Consumeron a stubbedperformance.now. Both rings are now sized from the target that settles, and every ring case now asserts zero underruns and zero skips (previously up to 2 underruns were allowed). Two new cases pin the budget behavior.doc/concept/audio-jitter.mdstates the rule for both implementations.Findings that are not code changes:
demo/websets no delay andjs/watchstores no preference, so a fresh tile runs in auto (checked in code, not in a browser).gh release view rt-audio-traces-2026-09-06 -R fperex/moqreports "release not found", and thedebug/rt-audiobranch only has per-run summaries.Impact
AUTO_MAX_AGElives in the internalaudio/latency.tsmodule, which is not re-exported."auto", the audio subscription's max age is now at least 2 s (audio only; video is unchanged). Under congestion, up to 2 s of stale audio groups are delivered instead of being reset. That is a few KB at Opus rates, and the ring drops anything already behind the playhead.Syncis untouched. This change only readssync.in.delayandsync.out.maxAge, so it does not affect the open question on quest(audio-jitter-target): Audio jitter target #3952 about whether feat(watch): size the auto delay from the measured arrival estimator #3954'sSyncsemantics belong ondev.Alternatives
Sync.out.maxAgeitself in auto. Rejected:Sync.received()also uses it as the lookahead cap, so this would let playback drift up to 2 s behind the live edge.Sync.Follow-ups
/quest/m1/audio-quality-harness/browser.md.quest/main/audio-jitter-target/README, butquest branchnow names itquest/m0/.... This PR usesquest/m0/audio-jitter-target/watchand targets the existing line branch.main. Mergingmainin conflicts injs/watch/src/sync.ts(andrs/moq-cli), so I left that for whoever settles theSyncquestion.(written by Claude Opus 5.5)
🤖 Generated with Claude Code