From 0f34831c5be9a2e18d130411661600c86955197c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Tue, 11 Aug 2026 20:15:49 -0400 Subject: [PATCH] Attempt to fix a bug that can randomly drop audio streams. --- gumble/gumble/audio.go | 2 ++ gumble/gumble/audiocodec.go | 1 + gumble/gumble/handlers.go | 1 + gumble/gumbleopenal/stream.go | 31 ++++++++++++++++++++---------- gumble/gumbleopenal/stream_test.go | 18 +++++++++++++++++ gumble/opus/opus.go | 4 ++++ 6 files changed, 47 insertions(+), 10 deletions(-) diff --git a/gumble/gumble/audio.go b/gumble/gumble/audio.go index 59b090f..236c34f 100644 --- a/gumble/gumble/audio.go +++ b/gumble/gumble/audio.go @@ -86,6 +86,8 @@ type AudioPacket struct { Sender *User Target *VoiceTarget Final bool + // Channels is the number of interleaved PCM channels in AudioBuffer. + Channels int // LocallyMuted and LocalMuteGeneration snapshot the receiver's local mute // state when the packet entered the playback queue. LocallyMuted bool diff --git a/gumble/gumble/audiocodec.go b/gumble/gumble/audiocodec.go index 9f4883e..b6b2d1d 100644 --- a/gumble/gumble/audiocodec.go +++ b/gumble/gumble/audiocodec.go @@ -50,6 +50,7 @@ type AudioEncoder interface { // AudioDecoder decodes an encoded byte slice to a chunk of PCM audio samples. type AudioDecoder interface { ID() int + Channels() int Decode(data []byte, frameSize int) ([]int16, error) Reset() } diff --git a/gumble/gumble/handlers.go b/gumble/gumble/handlers.go index d2e91da..5198b1a 100644 --- a/gumble/gumble/handlers.go +++ b/gumble/gumble/handlers.go @@ -152,6 +152,7 @@ func (c *Client) handleUDPTunnel(buffer []byte) error { Client: c, Sender: user, Final: final, + Channels: decoder.Channels(), LocallyMuted: locallyMuted, LocalMuteGeneration: localMuteGeneration, Target: &VoiceTarget{ diff --git a/gumble/gumbleopenal/stream.go b/gumble/gumbleopenal/stream.go index d0ab3d1..d7c3a5e 100644 --- a/gumble/gumbleopenal/stream.go +++ b/gumble/gumbleopenal/stream.go @@ -41,6 +41,23 @@ func shouldStartPlayback(state openal.State, queued int32, final bool) bool { return state != openal.Playing && queued > 0 && (queued >= playbackPrebufferBuffers || final) } +func playbackLayout(packet *gumble.AudioPacket) (openal.Format, int, bool) { + if packet == nil || len(packet.AudioBuffer) == 0 { + return 0, 0, false + } + switch packet.Channels { + case gumble.AudioMonoChannels: + return openal.FormatMono16, len(packet.AudioBuffer), true + case gumble.AudioChannels: + if len(packet.AudioBuffer)%gumble.AudioChannels != 0 { + return 0, 0, false + } + return openal.FormatStereo16, len(packet.AudioBuffer) / gumble.AudioChannels, true + default: + return 0, 0, false + } +} + type playbackCommand struct { gain float32 muted bool @@ -550,14 +567,14 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { } var boost uint16 = uint16(1) - samples := len(packet.AudioBuffer) - if samples == 0 { + if len(packet.AudioBuffer) == 0 { if shouldStartPlayback(source.State(), source.BuffersQueued(), packet.Final) { source.Play() } continue } - if samples > cap(raw)/2 { + format, samples, validLayout := playbackLayout(packet) + if !validLayout || len(packet.AudioBuffer) > cap(raw)/2 { continue } @@ -569,13 +586,7 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) { recordBuffer = make([]int16, len(packet.AudioBuffer)*gumble.AudioChannels) } - // Check if sample count suggests stereo data - isStereo := samples > gumble.AudioDefaultFrameSize && samples%2 == 0 - format := openal.FormatMono16 - if isStereo { - format = openal.FormatStereo16 - samples = samples / 2 - } + isStereo := packet.Channels == gumble.AudioChannels rawPtr := 0 if isStereo { diff --git a/gumble/gumbleopenal/stream_test.go b/gumble/gumbleopenal/stream_test.go index 7651aa8..e67b38b 100644 --- a/gumble/gumbleopenal/stream_test.go +++ b/gumble/gumbleopenal/stream_test.go @@ -32,6 +32,24 @@ func TestShouldStartPlayback(t *testing.T) { } } +func TestPlaybackLayoutUsesExplicitStereoChannelCount(t *testing.T) { + packet := &gumble.AudioPacket{ + Channels: gumble.AudioChannels, + AudioBuffer: make(gumble.AudioBuffer, gumble.AudioDefaultFrameSize), + } + + format, frames, ok := playbackLayout(packet) + if !ok { + t.Fatal("expected a valid short stereo packet") + } + if format != openal.FormatStereo16 { + t.Fatalf("format = %v, want stereo", format) + } + if frames != gumble.AudioDefaultFrameSize/gumble.AudioChannels { + t.Fatalf("frames = %d, want %d", frames, gumble.AudioDefaultFrameSize/gumble.AudioChannels) + } +} + func TestSendOutgoingAudioStopsWhileSendIsBlocked(t *testing.T) { stop := make(chan struct{}) outgoing := make(chan gumble.AudioBuffer) diff --git a/gumble/opus/opus.go b/gumble/opus/opus.go index f0976e4..49d5473 100644 --- a/gumble/opus/opus.go +++ b/gumble/opus/opus.go @@ -88,6 +88,10 @@ func (*Decoder) ID() int { return ID } +func (d *Decoder) Channels() int { + return d.channels +} + func (d *Decoder) Decode(data []byte, frameSize int) ([]int16, error) { // Allocate buffer for stereo - frameSize is per channel pcm := make([]int16, frameSize*gumble.AudioChannels)