fix(audio): decouple Opus decode cadence from playback callback period
on_playback() was passing miniaudio's hardware playback-callback frame count to opus_decode()'s max_samples, instead of the decoder's fixed frame size (960 samples @ 20ms/48kHz). Since real packets decode to more samples than the (often smaller, e.g. ~480 on default low-latency WASAPI) hardware period, opus_decode returned OPUS_BUFFER_TOO_SMALL on nearly every callback -- packets were received/decrypted/jitter-buffered correctly but never decoded into audible PCM. Result: control-plane events and VAD worked, but zero audio in headphones. mix_for_test()'s white-box test masked this since it always called on_playback with frames == frame_samples, the one case where the bug is invisible. Fix: RemoteStream gained a small ring buffer (init_ring/push_ring/ pop_ring) that decouples decode cadence from playback-callback cadence. on_playback now tops the ring up by decoding whole Opus frames (always decoder.frame_samples(), never the hardware frame count) and drains exactly what the callback asks for, silence-padding (PLC) on underrun. Side effect: also fixes playout_ts, which was advancing by the wrong unit (hardware frames instead of decoded samples) -- it now tracks correctly against jitter-buffer timestamps. ctest --test-dir build/m1-dev: 12/12 green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
20
PROGRESS.md
20
PROGRESS.md
@@ -10,6 +10,26 @@ up instantly. Newest status at the top.
|
||||
|
||||
## ▶ Where we left off / next action
|
||||
|
||||
- **Done:** **Fixed silent-playback bug in `AudioEngine::on_playback`** (2026-06-16, found via
|
||||
live manual test: two `vccli --voice` clients, control-plane events and VAD all correct, but
|
||||
zero audible output). Root cause: `opus_decode()`'s `max_samples` was being passed the
|
||||
*hardware playback callback's* frame count (miniaudio's own choice, frequently smaller than
|
||||
one Opus frame — e.g. ~480 samples on default low-latency WASAPI periods), instead of the
|
||||
decoder's fixed frame size (960 @ 20ms/48kHz). Since the real packet almost always decodes to
|
||||
more samples than that, `opus_decode` returned `OPUS_BUFFER_TOO_SMALL` on nearly every
|
||||
callback — frames were correctly received/decrypted/jitter-buffered, just never decoded into
|
||||
audible PCM. `mix_for_test()`'s white-box test masked this because it always called
|
||||
`on_playback` with `frames == frame_samples`, the one case where the bug is invisible.
|
||||
Fix: `RemoteStream` (`core/src/audio/audio_engine.h`) gained a small ring buffer
|
||||
(`init_ring`/`push_ring`/`pop_ring`) that decouples decode cadence from playback-callback
|
||||
cadence — `on_playback` (`core/src/audio/audio_engine.cpp`) now tops the ring up by decoding
|
||||
whole Opus frames (`decoder.frame_samples()`, never the hardware `frames`) and drains exactly
|
||||
`frames` samples-per-channel from it each callback, silence-padding (PLC) on underrun. Also
|
||||
fixes a latent `playout_ts` bug: it now advances by the actual decoded sample count per Opus
|
||||
frame, not by the hardware callback's (unrelated) frame count, which was the wrong unit for
|
||||
jitter-buffer timestamp comparisons. `ctest --test-dir build/m1-dev` — 12/12 green (run via
|
||||
PowerShell; Git Bash exec gotcha for these binaries, see `docs/building.md`). **Not yet
|
||||
confirmed audible by ear** — pending the user re-running their live two-`vccli` test.
|
||||
- **Done:** **Post-M3 follow-up — device enumeration, VAD/PTT gate, stereo playback, WASAPI
|
||||
loopback** ✓ complete (2026-06-16). Closes all three items M3 explicitly carried forward as
|
||||
out of scope (see the dated section below for the full file-by-file change list).
|
||||
|
||||
Reference in New Issue
Block a user