Restore ability to transmit in stereo.

This commit is contained in:
Storm Dragon
2026-09-29 01:10:35 -04:00
parent c1722c2491
commit abf6729ce9
6 changed files with 55 additions and 27 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ func (b *Barnard) connect(reconnect bool) bool {
}
})
// Initialize stereo encoder for file playback, reusing the one built for
// Initialize stereo encoder for two-channel capture and file playback, reusing the one built for
// the previous connection rather than allocating another.
if b.stereoEncoder == nil {
b.stereoEncoder = opus.NewStereoEncoder()
+11 -4
View File
@@ -52,15 +52,22 @@ type AudioStreamEvent struct {
// AudioBuffer is a slice of PCM audio samples.
type AudioBuffer []int16
// encoderForAudioBuffer is called with volatile held.
func (client *Client) encoderForAudioBuffer(a AudioBuffer) AudioEncoder {
frameSize := client.Config.AudioFrameSize()
if client.AudioEncoderStereo != nil && (len(a) == frameSize*AudioChannels ||
(client.useStereoEncoder && len(a) != frameSize)) {
return client.AudioEncoderStereo
}
return client.AudioEncoder
}
func (a AudioBuffer) writeAudio(client *Client, seq int64, final bool) error {
// Encoding shares mutable codec state with server-configuration and file
// playback changes. Keep the client read lock through Encode and Reset so a
// stereo encoder cannot be replaced or reset while it is in use.
client.volatile.RLock()
encoder := client.AudioEncoder
if client.useStereoEncoder && client.AudioEncoderStereo != nil {
encoder = client.AudioEncoderStereo
}
encoder := client.encoderForAudioBuffer(a)
if encoder == nil {
client.volatile.RUnlock()
return nil
+34
View File
@@ -0,0 +1,34 @@
package gumble
import (
"testing"
"time"
)
type testAudioEncoder struct{ channels int }
func (*testAudioEncoder) ID() int { return 4 }
func (*testAudioEncoder) Encode([]int16, int, int) ([]byte, error) { return nil, nil }
func (*testAudioEncoder) Reset() {}
func TestEncoderForAudioBufferPreservesStereoMicrophone(t *testing.T) {
for _, interval := range []time.Duration{10 * time.Millisecond, 20 * time.Millisecond, 40 * time.Millisecond, 60 * time.Millisecond} {
config := NewConfig()
config.AudioInterval = interval
mono, stereo := &testAudioEncoder{channels: 1}, &testAudioEncoder{channels: 2}
client := &Client{Config: config, AudioEncoder: mono, AudioEncoderStereo: stereo}
frameSize := config.AudioFrameSize()
if got := client.encoderForAudioBuffer(make(AudioBuffer, frameSize*AudioChannels)); got != stereo {
t.Errorf("%v: stereo microphone selected mono encoder", interval)
}
client.useStereoEncoder = true
if got := client.encoderForAudioBuffer(make(AudioBuffer, frameSize)); got != mono {
t.Errorf("%v: mono microphone selected stereo encoder during file playback", interval)
}
client.useStereoEncoder = false
if got := client.encoderForAudioBuffer(make(AudioBuffer, frameSize)); got != mono {
t.Errorf("%v: mono microphone selected stereo encoder", interval)
}
}
}
+3 -4
View File
@@ -399,7 +399,7 @@ func (c *Client) Send(message Message) {
message.writeMessage(c)
}
// SetStereoEncoder installs the encoder used for stereo file playback.
// SetStereoEncoder installs the encoder used for stereo capture and file playback.
func (c *Client) SetStereoEncoder(encoder AudioEncoder) {
c.volatile.Lock()
defer c.volatile.Unlock()
@@ -458,9 +458,8 @@ func (c *Client) UDPActive() bool {
return c.udpActive
}
// DisableStereoEncoder switches back to mono encoding for voice and
// resets the stereo encoder so stale state does not bleed into the
// next file playback.
// DisableStereoEncoder clears file playback mode and resets the stereo
// encoder. Stereo microphone frames still select it by channel count.
func (c *Client) DisableStereoEncoder() {
c.volatile.Lock()
defer c.volatile.Unlock()
+3 -14
View File
@@ -1064,21 +1064,10 @@ func (s *Stream) sourceRoutine(inputDevice *string, stop chan bool, done chan st
recorder.RecordAudioFrame(recorderOutgoingSource, outputBuffer, true)
}
} else if hasMicInput {
// Send mic when no file is playing. If the microphone is
// stereo, downmix to mono since Mumble voice transmission
// uses mono Opus encoding.
outBuf := int16Buffer
if s.sourceChannels == 2 {
monoBuf := make([]int16, frameSize)
for i := 0; i < frameSize; i++ {
// Average left and right channels
monoBuf[i] = int16((int32(int16Buffer[i*2]) + int32(int16Buffer[i*2+1])) / 2)
}
outBuf = monoBuf
}
outgoing <- gumble.AudioBuffer(outBuf)
// Preserve the capture device's channel layout for encoding.
outgoing <- gumble.AudioBuffer(int16Buffer)
if recorder := s.getRecorder(); recorder != nil {
recorder.RecordAudioFrame(recorderOutgoingSource, outBuf, false)
recorder.RecordAudioFrame(recorderOutgoingSource, int16Buffer, s.sourceChannels == 2)
}
}
}
+3 -4
View File
@@ -10,7 +10,7 @@ var Codec gumble.AudioCodec
const (
ID = 4
VoiceChannels = 1 // Force mono for voice transmission
VoiceChannels = 1 // Channel count for the mono encoder
)
func init() {
@@ -27,7 +27,7 @@ func (*generator) ID() int {
}
func (*generator) NewEncoder() gumble.AudioEncoder {
// Force mono for voice transmission
// Create the mono encoder for single-channel capture.
e, _ := opus.NewEncoder(gumble.AudioSampleRate, VoiceChannels, opus.AppVoIP)
return &Encoder{
Encoder: e,
@@ -35,9 +35,8 @@ func (*generator) NewEncoder() gumble.AudioEncoder {
}
}
// NewStereoEncoder creates a stereo encoder for file playback
// NewStereoEncoder creates a stereo encoder for capture and file playback.
func NewStereoEncoder() gumble.AudioEncoder {
// Create stereo encoder for file playback
e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio)
return &Encoder{
Encoder: e,