fix: reset Opus decoder on packet loss and decode errors

Track per-user audio sequence numbers to detect UDP packet loss gaps.
When a sequence discontinuity is detected (loss, reorder, or burst gap),
reset the Opus decoder state to prevent permanent audio corruption.

Also reset the decoder when Decode() returns an error, instead of
leaving the decoder in a corrupted state that produces static/popping
for the remainder of the session.

This fixes the 'random static/popping from user b but no other clients
hear it' symptom, which occurs when one client experiences packet loss
affecting only its own per-user decoder instance.
This commit is contained in:
Brandon McGinty (deepseek)
2026-08-08 19:15:53 -04:00
committed by Brandon McGinty
parent 30a02eba14
commit dfa184211e
2 changed files with 26 additions and 2 deletions
+21 -2
View File
@@ -113,13 +113,30 @@ func (c *Client) handleUDPTunnel(buffer []byte) error {
}
// Sequence
// TODO: use in jitter buffer
_, n = varint.Decode(buffer)
seq, n := varint.Decode(buffer)
if n <= 0 {
return errInvalidProtobuf
}
buffer = buffer[n:]
// Detect sequence gaps (packet loss) and reset the decoder to prevent
// permanent audio corruption from state desync.
// Mumble uses a monotonically increasing sequence that wraps at MaxInt32.
if user.audioSequenceValid {
// Only treat as discontinuity if the gap is small enough to be loss
// rather than a legitimate wrap-around or restart.
gap := seq - user.audioSequence
if gap > 1 && gap < 100 {
decoder.Reset()
} else if gap < 0 && gap > -100 {
// Reordered packet — reset to be safe, since the decoder
// state depends on correct frame ordering.
decoder.Reset()
}
}
user.audioSequence = seq
user.audioSequenceValid = true
// Length
length, n := varint.Decode(buffer)
if n <= 0 {
@@ -134,6 +151,8 @@ func (c *Client) handleUDPTunnel(buffer []byte) error {
pcm, err := decoder.Decode(buffer[:audioLength], AudioMaximumFrameSize)
if err != nil {
// Decode failure indicates corrupted decoder state; reset and drop.
decoder.Reset()
return err
}
+5
View File
@@ -53,6 +53,11 @@ type User struct {
client *Client
decoder AudioDecoder
// audioSequence tracks the last UDP audio packet sequence number for
// this user, used to detect packet loss and reset the Opus decoder.
audioSequence int64
audioSequenceValid bool
AudioSource *openal.Source
Boost uint16
Volume float32