fix(audio): seed/re-sync playout clock so VAD/PTT gaps don't silence playback
RemoteStream::playout_ts was seeded to 0 and only advanced inside the decode loop (including on every PLC iteration), so it free-ran at ~1x wall-clock regardless of whether the sender was transmitting. The sender's frame timestamps only advance while it actually sends (the VAD/PTT gate returns before ls.timestamp += samples). Across a late join or any VAD/PTT silence gap the two clocks diverged without bound; once past the jitter buffer's 500 ms late-drop window every real frame was dropped-as-late (clock ahead) or never-due (clock behind) -> permanent silence, while the talk indicator (driven by push_recv_frame, independent of the jitter buffer) stayed lit. Add JitterBuffer::peek_front_ts() (try-lock, RT-safe) and seed/re-sync playout_ts to the earliest buffered frame on the first frame and whenever it has drifted past +/-200/500 ms. This seeds startup and recovers after every silence gap. New regression test test_playout_resync free-runs the clock ~2 s past the drop window, pushes a ts=0 frame, and asserts audible output: fails (energy=0) without the fix, passes with it. ctest --preset m1-dev: 14/14. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
22
PROGRESS.md
22
PROGRESS.md
@@ -10,6 +10,28 @@ up instantly. Newest status at the top.
|
||||
|
||||
## ▶ Where we left off / next action
|
||||
|
||||
- **Done:** **Fixed a *second* silent-playback bug — the playout clock free-ran and drifted off
|
||||
the stream** (2026-06-17, reported live: both `vccli` and the Windows client showed `talking=1/0`
|
||||
correctly on VAD/PTT, mic + screen-share were recognized by peers, but nothing was audible).
|
||||
Root cause: `RemoteStream::playout_ts` was only ever seeded to `0` and then advanced one Opus
|
||||
frame per playback callback **via the PLC path too** (`core/src/audio/audio_engine.cpp`
|
||||
`on_playback`), so it free-ran at ~1× wall-clock regardless of whether the sender was
|
||||
transmitting. The sender's frame timestamps only advance while it actually sends (the VAD/PTT
|
||||
gate in `core/src/core/client.cpp` returns before `ls.timestamp += samples`). Across a late join
|
||||
or any VAD/PTT silence gap the two clocks diverged without bound; once past the jitter buffer's
|
||||
500 ms late-drop window, every real frame was dropped-as-late (clock ahead) or never-due (clock
|
||||
behind) → permanent silence, while the talk indicator (driven by `push_recv_frame`, independent
|
||||
of the jitter buffer) stayed lit. The M3 E2E test missed it because clients there talked
|
||||
continuously right after joining, keeping the clocks aligned. **Fix:** `JitterBuffer` gained
|
||||
`peek_front_ts()` (try-lock, RT-safe); `on_playback` now seeds/re-syncs `playout_ts` to the
|
||||
earliest buffered frame on the first frame and whenever it has drifted past ±200/500 ms
|
||||
(`kResyncAheadSamples`/`kResyncBehindSamples`), which both seeds startup and recovers after every
|
||||
silence gap. New regression test `test_playout_resync` (`tests/test_vad_ptt_devices.cpp`):
|
||||
free-runs the clock ~2 s past the drop window, pushes a `ts=0` frame, asserts audible output —
|
||||
verified to fail (energy=0) with the fix disabled, pass (energy≈15M) with it. `ctest --test-dir
|
||||
build/m1-dev` — **14/14 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 test.
|
||||
- **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
|
||||
|
||||
Reference in New Issue
Block a user