[Recording] Reset a speaker's Opus decoder when their SSRC changes #322
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: MEDIUM. Found in the August 2026 session lifecycle review (#319). Required companion to the wall-clock anchoring fix.
The defect
The sink keeps one
discord.opus.Decoderper user id (recording.py:170), but Discord assigns a new SSRC when a user leaves and rejoins a voice channel, or when the voice server fails over or changes region. Opus is a stateful codec: feeding a new stream through a decoder carrying state from the previous one garbles the first frames after the discontinuity.Today this is masked, because the entire absence is compressed out of the track anyway. Once gaps are filled, rejoins become common, visible, and audible — and garbled audio at exactly the moment someone rejoins is prime material for a mis-transcription.
Evidence
bot/questboard_bot/cogs/recording.py:168-170— decoder keyed byuser.id, created once, never resetuser.idtoo, so audio after a rejoin appends to the same filevoice_client.py:243-255/gateway.py:176— SSRC removal and remapping on member disconnect and reconnectvoice_client.py:71-74— a bot channel move destroys all decodersProposed fix
Track the last-seen SSRC per user id. When a packet arrives with a different SSRC than the one recorded for that user, discard and recreate that user's decoder before decoding. Log the reset at INFO with the old and new SSRC so rejoin frequency is observable.
Also handle the interleaving case: the jitter-buffer tail of the old SSRC can drain after the new stream has started. With wall-clock anchoring both are placed correctly by time, so the only requirement is that the decoder used matches the SSRC of the packet being decoded — keep a decoder per
(uid, ssrc)if that proves simpler than resetting.Acceptance criteria
Shipped — closing as part of a v3.11.5 bookkeeping sweep.
Evidence at
bot/questboard_bot/cogs/recording.py:261, in the decode path:The decoder is reset when a speaker's SSRC changes, so a mid-session renegotiation no longer feeds Opus frames to a decoder holding the previous stream's state.