diff --git a/quest/m1/audio-jitter-target/README.md b/quest/m1/audio-jitter-target/README.md index 45763853e68..0d7975602a1 100644 --- a/quest/m1/audio-jitter-target/README.md +++ b/quest/m1/audio-jitter-target/README.md @@ -36,10 +36,10 @@ the native decode path too, asserting the same target series the browser's ## Required -- [Opus isolated-ring target](/quest/m1/audio-jitter-target/opus-isolated-target.md) - the isolated ring holds the target at near-zero jitter - [Audio rename](/quest/m1/audio-jitter-target/audio-rename.md) - a renamed audio track continues the outgoing arrival estimate, so playback neither skips nor stalls - [Watch](/quest/m1/audio-jitter-target/watch.md) - the browser's measured target, proven on Chrome and Safari against the public relay - [AAC bursty budgets](/quest/m1/audio-jitter-target/aac-bursty-budgets.md) - post-fix nightlies confirm AAC no longer re-stalls, and its budgets are tightened from them +- [Opus near-zero budgets](/quest/m1/audio-jitter-target/opus-near-zero-budgets.md) - post-flush nightlies confirm the first row no longer stalls, and its budgets are tightened from them ## Closes diff --git a/quest/m1/audio-jitter-target/opus-isolated-target.md b/quest/m1/audio-jitter-target/opus-isolated-target.md deleted file mode 100644 index 3f075623ae1..00000000000 --- a/quest/m1/audio-jitter-target/opus-isolated-target.md +++ /dev/null @@ -1,63 +0,0 @@ -# [S] The isolated ring holds the target at near-zero jitter - -## Goal - -On the `near-zero` network profile, Opus on the isolated (SharedArrayBuffer) -ring settles at the same playout target as the postMessage ring, with no -underrun episodes, in the nightly audio-quality rows. - -## Plan - -Measured by [#5442](https://github.com/moq-dev/moq/pull/5442) over the 7 -scheduled nightlies of 2026-10-07 to 2026-10-13: Opus near-zero on the -isolated ring reached a `target_ms_p95` of 100 to 800 ms with underrun -episodes of about 1 s, while the postMessage ring on the same profile held at -60 ms. A network with almost no jitter should not grow the target, so the -first suspect is the isolated page's path rather than the estimator. - -Start after #5442 merges: it uploads the nightly artifacts and carries the -measured budgets this quest tightens. - -Decided 2026-10-13 (maintainer): a child of the audio jitter target line, -reproducible from the harness now, so Watch's manual proof runs after it. - -Reproduce it first, from the nightly artifacts and then in -`js/watch/src/audio/replay.ts` or a ring-level test on a simulated clock. -The target and the underruns may have separate causes. The target follows -`consumer.spread`, which `js/hang/src/container/consumer.ts` records from -transport arrivals before the ring, so a ring defect cannot inflate it: -compare the captured arrivals and configuration of the two rows first, for -something on the cross-origin isolated page that delays when frames are -observed. A ring that under-reports its fill, reads the worklet's position -late, or misses a resize explains underruns only. Fix each at the source with -a regression that fails without it, then tighten the isolated near-zero -ceilings in `test/audio-quality/budgets.json` from re-measured nightly runs. - -Public API: none expected. Wire: none. - -The 2026-10-15 ARM comparison still reproduces the defect with Opus warmup -#5569 included. [The untraced controls](https://github.com/moq-dev/moq/actions/runs/38807783281) -run `isolated,plain` and then `plain,isolated`, each in a fresh harness run. -Isolated-first reaches a 560 ms target with two underrun episodes; the other -three rows hold 60 ms with no episodes. Configuration is identical within -each pair, including 80 ms codec warmup. Row order remains a confound. - -The failing row's arrival gaps coincide with 227–320 ms main-thread long -tasks. A [GC profile](https://github.com/moq-dev/moq/actions/runs/38809803022) -reproduces the pauses with no recorded GC work overlapping the longest tasks. -A [sampled CPU profile](https://github.com/moq-dev/moq/actions/runs/38811094714) -has 100–315 ms sampling gaps across the pauses, with boundary samples in -`Stream.Reader` awaiting native `ReadableStreamDefaultReader.read()`. This -locates the browser read/scheduling boundary but does not establish its cause. -Recorded arrival replay produces the same target with either ring. - -Use `--capture` to preserve arrivals and native long tasks; `--trace` also -saves GC events and a sampled CPU profile. Tracing can perturb playback, so -compare separate untraced controls and never calibrate budgets from traced -rows. The original fix and budget-tightening goals remain open; the diagnostic -change does not alter the estimator, ring, or ceilings. - -## Related - -- [Watch](/quest/m1/audio-jitter-target/watch.md) - the manual browser proof that follows this fix -- [AAC bursty budgets](/quest/m1/audio-jitter-target/aac-bursty-budgets.md) - the other defect the same measurement found, fixed and awaiting its nightlies diff --git a/quest/m1/audio-jitter-target/opus-near-zero-budgets.md b/quest/m1/audio-jitter-target/opus-near-zero-budgets.md new file mode 100644 index 00000000000..ea866cd165e --- /dev/null +++ b/quest/m1/audio-jitter-target/opus-near-zero-budgets.md @@ -0,0 +1,32 @@ +# [XS] Tighten the Opus near-zero browser budgets after the build flush + +## Goal + +Seven scheduled nightlies on `main` after the harness began flushing its build +before the first row show the Opus `near-zero` `isolated` row holding the +same 60 ms target as the `plain` row with no underrun episodes, and +`test/audio-quality/budgets.json` carries ceilings re-measured from them. + +## Plan + +Waits on the nightlies: check the `audio-quality-run` artifacts of the +scheduled Nightly runs on `main` (`gh run download -n audio-quality-run`) +once seven have run since the fix merged. Never dispatch the nightly by hand. + +The stall was never the isolated ring. That row is simply the matrix's first, +and a cold `cargo build` left about 800 MB dirty when it started. On the ARM +runner each periodic writeback burst (100 to 280 MB, about 10 s apart) froze +the page's main thread for 100 to 320 ms until the backlog drained, about 30 s +in. A branch experiment put the plain ring first after a cold build, and it +failed the same way (540 ms target, the same 10 s long-task clusters). +Isolated-first, with the build flushed beforehand, held 60 ms. + +Re-measure the Opus near-zero rows by the rule in the budgets note, and drop +the note's sentence that budgets the isolated row's one-second drops as +measured. If the first row still shows long-task clusters, compare +`.longtasks.json` against the runner's disk activity before changing the +player. + +## Related + +- [AAC bursty budgets](/quest/m1/audio-jitter-target/aac-bursty-budgets.md) - the same re-measure for the AAC rows, on its own fix's nightlies diff --git a/quest/m1/audio-jitter-target/watch.md b/quest/m1/audio-jitter-target/watch.md index 95d830c3d67..48f9e95c3f2 100644 --- a/quest/m1/audio-jitter-target/watch.md +++ b/quest/m1/audio-jitter-target/watch.md @@ -55,11 +55,8 @@ Decided 2026-10-15: keep this proof in m1 with the existing playback and A/V clock work. The estimator defaults on; deferring watch-worker does not defer validation of the current player. -## Required - -- [Opus isolated-ring target](/quest/m1/audio-jitter-target/opus-isolated-target.md) - the isolated ring holds the target at near-zero jitter - ## Related - [A/V clock](/quest/m1/av-clock.md) - reshapes `SyncInput` around the per-track target this line produces - [AAC bursty budgets](/quest/m1/audio-jitter-target/aac-bursty-budgets.md) - confirms the AAC reorder fix on the nightlies, no gate on this proof +- [Opus near-zero budgets](/quest/m1/audio-jitter-target/opus-near-zero-budgets.md) - confirms the build-flush fix on the nightlies, no gate on this proof diff --git a/quest/m1/browser-rename-check.md b/quest/m1/browser-rename-check.md index 2321be6555b..e4212a96405 100644 --- a/quest/m1/browser-rename-check.md +++ b/quest/m1/browser-rename-check.md @@ -59,4 +59,4 @@ racing the old track's finish (`stream aborted by the remote server`). - [Rust identity enforcement](/quest/m1/catalog-identity-rs.md) - the same contract for the Rust publishers - [Capture channel count and cold-start tone](/quest/m1/publish-audio-channel-count.md) - already owns the unpinned cold-start audio-tone symptom -- [Isolated Opus target](/quest/m1/audio-jitter-target/opus-isolated-target.md) - related playout stalls, with no shared cause established +- [Isolated Opus target (#5807)](https://github.com/moq-dev/moq/pull/5807) - related playout stalls, with no shared cause established diff --git a/test/audio-quality/run.sh b/test/audio-quality/run.sh index 3bd6e349bc3..b93241f95b1 100755 --- a/test/audio-quality/run.sh +++ b/test/audio-quality/run.sh @@ -243,6 +243,12 @@ if [[ ${#ROWS[@]} -gt 0 ]]; then bunx vite build --outDir "$HARNESS_RUN/page" --emptyOutDir ) + # A cold build leaves hundreds of MB dirty, and on the nightly runner each periodic writeback + # burst stalls the page's main thread for ~300 ms, so the first row would grade the build's + # flush instead of the player. Flush it before any row plays. + echo "flushing the build to disk..." + sync + # ── relay ─────────────────────────────────────────────────────────────── harness_port relay RELAY_PORT="$HARNESS_PORT"