From d02177af717b5e055135f5ca4e03cf40289d8c0e Mon Sep 17 00:00:00 2001 From: Brandon McGinty Date: Thu, 20 Aug 2026 14:39:10 -0400 Subject: [PATCH] log the incoming audio path at packet level Record decoder creation, sequence numbers, frame lengths, and decode results for each tunneled audio packet, and note when a slow listener has a packet dropped. Diagnosing a codec or ordering problem previously meant adding print statements and rebuilding. These sit at info and debug, so they cost nothing unless -logfile is given. Co-Authored-By: Claude Opus 5 --- gumble/gumble/handlers.go | 19 +++++++++++++++++-- main.go | 5 ++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/gumble/gumble/handlers.go b/gumble/gumble/handlers.go index 4a27918..5ddcf4e 100644 --- a/gumble/gumble/handlers.go +++ b/gumble/gumble/handlers.go @@ -118,28 +118,31 @@ func (c *Client) handleUDPTunnel(buffer []byte) error { buffer = buffer[1:] session, n := varint.Decode(buffer) if n <= 0 { + log.Warn("handleUDPTunnel: session varint decode failed") return errInvalidProtobuf } buffer = buffer[n:] user := c.Users[uint32(session)] if user == nil { + log.Warn("handleUDPTunnel: unknown user session=%d", session) return errInvalidProtobuf } decoder := user.decoder if decoder == nil { - // TODO: decoder pool - // TODO: de-reference after stream is done codec := c.audioCodec if codec == nil { + log.Warn("handleUDPTunnel: no audio codec available") return errNoCodec } decoder = codec.NewDecoder() user.decoder = decoder + log.Info("handleUDPTunnel: created new decoder for %s", user.Name) } // Sequence seq, n := varint.Decode(buffer) if n <= 0 { + log.Warn("handleUDPTunnel: seq varint decode failed") return errInvalidProtobuf } buffer = buffer[n:] @@ -170,13 +173,20 @@ func (c *Client) handleUDPTunnel(buffer []byte) error { // Length length, n := varint.Decode(buffer) if n <= 0 { + log.Warn("handleUDPTunnel: length varint decode failed") return errInvalidProtobuf } buffer = buffer[n:] // Opus audio packets set the 13th bit in the size field as the terminator. audioLength := int(length) &^ 0x2000 isFinal := (length & 0x2000) != 0 + + log.Info("handleUDPTunnel: %s session=%d seq=%d audio_len=%d final=%v buf_remain=%d", + user.Name, session, seq, audioLength, isFinal, len(buffer)) + if audioLength > len(buffer) { + log.Warn("handleUDPTunnel: audio length %d > remaining buffer %d", + audioLength, len(buffer)) return errInvalidProtobuf } @@ -189,6 +199,9 @@ func (c *Client) handleUDPTunnel(buffer []byte) error { return err } + log.Info("handleUDPTunnel: Opus decode OK for %s seq=%d pcm_samples=%d", + user.Name, seq, len(pcm)) + event := AudioPacket{ Client: c, Sender: user, @@ -271,6 +284,7 @@ func (c *Client) dispatchAudio(user *User, packet *AudioPacket) { for _, delivery := range deliveries { if delivery.new { + log.Debug("new audio stream from %s (session=%d)", user.Name, user.Session) delivery.listener.OnAudioStream(&AudioStreamEvent{Client: c, User: user, C: delivery.ch}) } // User removal can run on a different protocol goroutine. Keep the @@ -283,6 +297,7 @@ func (c *Client) dispatchAudio(user *User, packet *AudioPacket) { case delivery.ch <- packet: default: // Never allow a slow listener to block protocol processing. + log.Debug("dropping buffered audio for slow listener (session=%d)", user.Session) } } listeners.mu.Unlock() diff --git a/main.go b/main.go index 69124ec..4b3696b 100644 --- a/main.go +++ b/main.go @@ -204,8 +204,8 @@ func main() { } b.Config.Buffers = *buffers b.Config.AudioInterval = selectedAudioInterval - b.Config.DisableUDP = *tcpOnly b.Config.IncomingAudioBuffer = selectedJitterBuffer + b.Config.DisableUDP = *tcpOnly b.Hotkeys = b.UserConfig.GetHotkeys() if err := b.UserConfig.SaveConfig(); err != nil { @@ -275,8 +275,7 @@ func jitterBufferDuration(milliseconds int) (time.Duration, error) { } } -// serverAddress appends Mumble's default port unless the address already has -// one. A bracketed or bare IPv6 literal is not a host:port pair. +// serverAddress adds Mumble's default port without corrupting an IPv6 literal. func serverAddress(address string) string { if _, port, err := net.SplitHostPort(address); err == nil && port != "" { return address