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.
|
// the previous connection rather than allocating another.
|
||||||
if b.stereoEncoder == nil {
|
if b.stereoEncoder == nil {
|
||||||
b.stereoEncoder = opus.NewStereoEncoder()
|
b.stereoEncoder = opus.NewStereoEncoder()
|
||||||
|
|||||||
+11
-4
@@ -52,15 +52,22 @@ type AudioStreamEvent struct {
|
|||||||
// AudioBuffer is a slice of PCM audio samples.
|
// AudioBuffer is a slice of PCM audio samples.
|
||||||
type AudioBuffer []int16
|
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 {
|
func (a AudioBuffer) writeAudio(client *Client, seq int64, final bool) error {
|
||||||
// Encoding shares mutable codec state with server-configuration and file
|
// Encoding shares mutable codec state with server-configuration and file
|
||||||
// playback changes. Keep the client read lock through Encode and Reset so a
|
// 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.
|
// stereo encoder cannot be replaced or reset while it is in use.
|
||||||
client.volatile.RLock()
|
client.volatile.RLock()
|
||||||
encoder := client.AudioEncoder
|
encoder := client.encoderForAudioBuffer(a)
|
||||||
if client.useStereoEncoder && client.AudioEncoderStereo != nil {
|
|
||||||
encoder = client.AudioEncoderStereo
|
|
||||||
}
|
|
||||||
if encoder == nil {
|
if encoder == nil {
|
||||||
client.volatile.RUnlock()
|
client.volatile.RUnlock()
|
||||||
return nil
|
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)
|
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) {
|
func (c *Client) SetStereoEncoder(encoder AudioEncoder) {
|
||||||
c.volatile.Lock()
|
c.volatile.Lock()
|
||||||
defer c.volatile.Unlock()
|
defer c.volatile.Unlock()
|
||||||
@@ -458,9 +458,8 @@ func (c *Client) UDPActive() bool {
|
|||||||
return c.udpActive
|
return c.udpActive
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableStereoEncoder switches back to mono encoding for voice and
|
// DisableStereoEncoder clears file playback mode and resets the stereo
|
||||||
// resets the stereo encoder so stale state does not bleed into the
|
// encoder. Stereo microphone frames still select it by channel count.
|
||||||
// next file playback.
|
|
||||||
func (c *Client) DisableStereoEncoder() {
|
func (c *Client) DisableStereoEncoder() {
|
||||||
c.volatile.Lock()
|
c.volatile.Lock()
|
||||||
defer c.volatile.Unlock()
|
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)
|
recorder.RecordAudioFrame(recorderOutgoingSource, outputBuffer, true)
|
||||||
}
|
}
|
||||||
} else if hasMicInput {
|
} else if hasMicInput {
|
||||||
// Send mic when no file is playing. If the microphone is
|
// Preserve the capture device's channel layout for encoding.
|
||||||
// stereo, downmix to mono since Mumble voice transmission
|
outgoing <- gumble.AudioBuffer(int16Buffer)
|
||||||
// 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)
|
|
||||||
if recorder := s.getRecorder(); recorder != nil {
|
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 (
|
const (
|
||||||
ID = 4
|
ID = 4
|
||||||
VoiceChannels = 1 // Force mono for voice transmission
|
VoiceChannels = 1 // Channel count for the mono encoder
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -27,7 +27,7 @@ func (*generator) ID() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (*generator) NewEncoder() gumble.AudioEncoder {
|
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)
|
e, _ := opus.NewEncoder(gumble.AudioSampleRate, VoiceChannels, opus.AppVoIP)
|
||||||
return &Encoder{
|
return &Encoder{
|
||||||
Encoder: e,
|
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 {
|
func NewStereoEncoder() gumble.AudioEncoder {
|
||||||
// Create stereo encoder for file playback
|
|
||||||
e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio)
|
e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio)
|
||||||
return &Encoder{
|
return &Encoder{
|
||||||
Encoder: e,
|
Encoder: e,
|
||||||
|
|||||||
Reference in New Issue
Block a user