diff --git a/quest/m1/README.md b/quest/m1/README.md index 5cd69305a1..14a665f59c 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 @@ -74,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-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 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.