From 3addb18d60aba28c160e949f57b11d026375f56b Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 5 Oct 2026 23:45:08 -0700 Subject: [PATCH 1/3] quest: claim opus-dtx-timestamps Co-Authored-By: Claude Opus 5.5 From 85a065d585505ec8d22f65d6c80d2199836e9b4d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 5 Oct 2026 23:52:31 -0700 Subject: [PATCH 2/3] quest: delete opus-dtx-timestamps after a measured no-go Chromium's AudioEncoder gives no way to map an Opus chunk back to its input under DTX: timestamps count emitted samples, duration is always the frame length, metadata only carries decoderConfig, flush() pads and emits an extra chunk, and the Ogg format is unsupported. Firefox keeps the timeline. DTX stays off for voice. Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 1 - quest/m1/opus-dtx-timestamps.md | 37 --------------------------------- 2 files changed, 38 deletions(-) delete mode 100644 quest/m1/opus-dtx-timestamps.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 5cd69305a1..78578929e6 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -51,7 +51,6 @@ blocks. The quests that gated m0 lines moved under them. - [Delete the live marker](/quest/m1/announce-live-removal.md) - announce streams yield only route events; the `Live` marker and its hold machinery are gone - [WebTransport close backends](/quest/m1/wt-close-backends.md) - web-transport-noq and -quinn keep the session alive while closing, and iroh's client reads a capsule close - [Enabled flag](/quest/m1/catalog-enabled.md) - one `enabled` rendition flag replaces `stalled` and pausing, and the flapping encoder-lag detector is deleted -- [DTX timestamps](/quest/m1/opus-dtx-timestamps.md) - Opus DTX keeps the capture timeline, so voice enables it by default again - [Spent capture budget errors](/quest/m1/capture-exhausted-error.md) - a camera or microphone that runs out of retries sets `out.error` instead of failing silently - [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - every stream group in a lite subscription arrives or is dropped by name, and lite-07 drops its stream count for it - [#2991](/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md) - one dynamic producer per track name in both languages, with the sequence namespace surviving a replacement diff --git a/quest/m1/opus-dtx-timestamps.md b/quest/m1/opus-dtx-timestamps.md deleted file mode 100644 index a209006b1a..0000000000 --- a/quest/m1/opus-dtx-timestamps.md +++ /dev/null @@ -1,37 +0,0 @@ -# [M] Opus DTX keeps the capture timeline - -## Goal - -Opus DTX is correct in `@moq/publish` and on again by default for voice: -every encoded chunk is published at its input's capture time even when the -encoder suppresses silent frames, and a listener plays it on that timeline, so -voice saves bandwidth during silence without skewing jitter estimates. - -## Plan - -Chromium stamps encoder output as the first input timestamp plus the samples -it emitted, so suppressed frames pull later chunks earlier. DTX is off by -default but still opt-in through `OpusConfig.usedtx` and the demo's checkbox, -both of which drift today. Once the timeline holds, restore `usedtx: true` in -the voice defaults (`opusKindDefaults`). - -Decided (2026-10-04): - -- Research first: whether WebCodecs exposes enough to map an output chunk - back to its input frame (chunk duration, a per-frame input queue, chunk - metadata) in Chromium, Firefox, and Safari. -- The listener is in scope: `js/watch/src/audio/decoder.ts` inserts audio at - the AudioDecoder's output timestamp, and if that also counts samples, a - correct publisher still collapses at playback. -- A measured no-go deletes this quest and DTX stays off. Record why in the - deleting PR. - -Open for the research: whether silence is absent frames, as WebRTC sends, or -the empty DTX frame every 20 ms that Rust publishes today. Absent frames save -groups as well as bytes. - -Open for a no-go: whether `usedtx` then stays as a warned opt-in or is removed. - -## Related - -- [Opus backend](/quest/m2/audio-opus-backend.md) - DTX on the native side From 87d8a6bc9df79fbe71e9c955dbe6642847bb0c80 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Mon, 5 Oct 2026 23:54:47 -0700 Subject: [PATCH 3/3] quest: plan removing usedtx after the DTX no-go Co-Authored-By: Claude Opus 5.5 --- quest/m1/README.md | 1 + quest/m1/opus-usedtx-removal.md | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 quest/m1/opus-usedtx-removal.md diff --git a/quest/m1/README.md b/quest/m1/README.md index 78578929e6..14a665f59c 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -73,6 +73,7 @@ blocks. The quests that gated m0 lines moved under them. - [Auth client CA](/quest/m1/relay-auth-client-ca.md) - `auth::Config::validate` and `init` take the client-CA flag, so no caller can skip the check - [TS stats module](/quest/m1/ts-stats-module.md) - the TS stats types move under `ts::stats` as `Snapshot` and `Stream`, with an owned `track` - [Audio capture without ALSA link](/quest/m1/capture-alsa-link.md) - moq-audio capture and playback build on Linux without linking libasound +- [Remove Opus DTX](/quest/m1/opus-usedtx-removal.md) - `@moq/publish` drops `usedtx`, since Chromium shifts the capture timeline under DTX - [Capture by default](/quest/m1/capture-default.md) - moq-video and moq-audio build `capture` by default, so pre-merge checks test it and the capture gate goes away - [Ship capture and playback](/quest/m1/cli-packaging.md) - a released moq binary can capture and play, which no distribution currently enables - [Deploy moq.sh](/quest/m1/moq-sh-deploy.md) - the first manual deploy serves the installer at moq.sh, and the first `release` run proves the CI token diff --git a/quest/m1/opus-usedtx-removal.md b/quest/m1/opus-usedtx-removal.md new file mode 100644 index 0000000000..f217265f31 --- /dev/null +++ b/quest/m1/opus-usedtx-removal.md @@ -0,0 +1,27 @@ +# [XS] Remove Opus DTX from @moq/publish + +## Goal + +`OpusConfig.usedtx` is gone from `@moq/publish`, and the demo's DTX checkbox +with it, so no publisher can turn on browser Opus DTX. Breaking for +`@moq/publish`. + +## Plan + +Decided 2026-10-06 after the measured no-go in +[#4902](https://github.com/moq-dev/moq/pull/4902): with DTX on, Chromium +stamps each Opus chunk as the first input's timestamp plus the samples it +emitted, so every suppressed frame pulls later chunks earlier (14 s of input +ended at 8.9 s). No WebCodecs output (duration, metadata, `flush()`, Ogg) +recovers the dropped span, and Chromium's decoder collapses timestamp gaps on +the listener too. Firefox keeps the timeline; Safari ignores `usedtx`. + +- Remove `usedtx` from `OpusConfig` (`js/publish/src/audio/encoder.ts`) and + its test, and the checkbox and `opusDtx` signal in `demo/web/src/publish.ts` + and `publish.html`. +- Update `doc/lib/js` wherever it names the option. +- Silence suppression for voice, if a customer ever wants it, is new work: + encode without DTX and skip silent frames with our own voice detection, or a + WASM libopus encoder ([Opus backend](/quest/m2/audio-opus-backend.md)). + +Public API: removes `OpusConfig.usedtx` from `@moq/publish`. Wire: none.