diff --git a/gumble/gumble/handlers.go b/gumble/gumble/handlers.go index a0278da..be02013 100644 --- a/gumble/gumble/handlers.go +++ b/gumble/gumble/handlers.go @@ -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 } diff --git a/gumble/gumble/user.go b/gumble/gumble/user.go index d23ffe2..ef9546c 100644 --- a/gumble/gumble/user.go +++ b/gumble/gumble/user.go @@ -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