Restore ability to transmit in stereo.
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user