Added /file and /stop commands.
This commit is contained in:
@@ -53,7 +53,11 @@ type AudioStreamEvent struct {
|
||||
type AudioBuffer []int16
|
||||
|
||||
func (a AudioBuffer) writeAudio(client *Client, seq int64, final bool) error {
|
||||
// Choose encoder based on whether stereo is enabled
|
||||
encoder := client.AudioEncoder
|
||||
if client.IsStereoEncoderEnabled() && client.AudioEncoderStereo != nil {
|
||||
encoder = client.AudioEncoderStereo
|
||||
}
|
||||
if encoder == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -59,8 +59,10 @@ type Client struct {
|
||||
ContextActions ContextActions
|
||||
|
||||
// The audio encoder used when sending audio to the server.
|
||||
AudioEncoder AudioEncoder
|
||||
audioCodec AudioCodec
|
||||
AudioEncoder AudioEncoder
|
||||
AudioEncoderStereo AudioEncoder
|
||||
audioCodec AudioCodec
|
||||
useStereoEncoder bool
|
||||
// To whom transmitted audio will be sent. The VoiceTarget must have already
|
||||
// been sent to the server for targeting to work correctly. Setting to nil
|
||||
// will disable voice targeting (i.e. switch back to regular speaking).
|
||||
@@ -287,3 +289,24 @@ func (c *Client) Do(f func()) {
|
||||
func (c *Client) Send(message Message) {
|
||||
message.writeMessage(c)
|
||||
}
|
||||
|
||||
// EnableStereoEncoder switches to stereo encoding for file playback.
|
||||
func (c *Client) EnableStereoEncoder() {
|
||||
c.volatile.Lock()
|
||||
defer c.volatile.Unlock()
|
||||
c.useStereoEncoder = true
|
||||
}
|
||||
|
||||
// DisableStereoEncoder switches back to mono encoding for voice.
|
||||
func (c *Client) DisableStereoEncoder() {
|
||||
c.volatile.Lock()
|
||||
defer c.volatile.Unlock()
|
||||
c.useStereoEncoder = false
|
||||
}
|
||||
|
||||
// IsStereoEncoderEnabled returns true if stereo encoding is currently active.
|
||||
func (c *Client) IsStereoEncoderEnabled() bool {
|
||||
c.volatile.RLock()
|
||||
defer c.volatile.RUnlock()
|
||||
return c.useStereoEncoder
|
||||
}
|
||||
|
||||
@@ -1073,6 +1073,9 @@ func (c *Client) handleCodecVersion(buffer []byte) error {
|
||||
c.volatile.Lock()
|
||||
|
||||
c.AudioEncoder = codec.NewEncoder()
|
||||
// Also create a stereo encoder for file playback
|
||||
// Import the opus package to get NewStereoEncoder
|
||||
c.AudioEncoderStereo = nil // Will be set when needed
|
||||
|
||||
c.volatile.Unlock()
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ type EffectsProcessor interface {
|
||||
IsEnabled() bool
|
||||
}
|
||||
|
||||
// FilePlayer interface for file playback
|
||||
type FilePlayer interface {
|
||||
GetAudioFrame() []int16
|
||||
IsPlaying() bool
|
||||
}
|
||||
|
||||
const (
|
||||
maxBufferSize = 11520 // Max frame size (2880) * bytes per stereo sample (4)
|
||||
)
|
||||
@@ -59,6 +65,7 @@ type Stream struct {
|
||||
noiseProcessor NoiseProcessor
|
||||
micAGC *audio.AGC
|
||||
effectsProcessor EffectsProcessor
|
||||
filePlayer FilePlayer
|
||||
}
|
||||
|
||||
func New(client *gumble.Client, inputDevice *string, outputDevice *string, test bool) (*Stream, error) {
|
||||
@@ -127,6 +134,14 @@ func (s *Stream) GetEffectsProcessor() EffectsProcessor {
|
||||
return s.effectsProcessor
|
||||
}
|
||||
|
||||
func (s *Stream) SetFilePlayer(fp FilePlayer) {
|
||||
s.filePlayer = fp
|
||||
}
|
||||
|
||||
func (s *Stream) GetFilePlayer() FilePlayer {
|
||||
return s.filePlayer
|
||||
}
|
||||
|
||||
|
||||
func (s *Stream) Destroy() {
|
||||
if s.link != nil {
|
||||
@@ -340,35 +355,88 @@ func (s *Stream) sourceRoutine(inputDevice *string) {
|
||||
case <-stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
buff := s.deviceSource.CaptureSamples(uint32(frameSize))
|
||||
if len(buff) != frameSize*2 {
|
||||
continue
|
||||
}
|
||||
// Initialize buffer with silence
|
||||
int16Buffer := make([]int16, frameSize)
|
||||
for i := range int16Buffer {
|
||||
sample := int16(binary.LittleEndian.Uint16(buff[i*2:]))
|
||||
if s.micVolume != 1.0 {
|
||||
sample = int16(float32(sample) * s.micVolume)
|
||||
|
||||
// Capture microphone if available
|
||||
hasMicInput := false
|
||||
buff := s.deviceSource.CaptureSamples(uint32(frameSize))
|
||||
if len(buff) == frameSize*2 {
|
||||
hasMicInput = true
|
||||
for i := range int16Buffer {
|
||||
sample := int16(binary.LittleEndian.Uint16(buff[i*2:]))
|
||||
if s.micVolume != 1.0 {
|
||||
sample = int16(float32(sample) * s.micVolume)
|
||||
}
|
||||
int16Buffer[i] = sample
|
||||
}
|
||||
|
||||
// Apply noise suppression if available and enabled
|
||||
if s.noiseProcessor != nil && s.noiseProcessor.IsEnabled() {
|
||||
s.noiseProcessor.ProcessSamples(int16Buffer)
|
||||
}
|
||||
|
||||
// Apply AGC to outgoing microphone audio (always enabled)
|
||||
if s.micAGC != nil {
|
||||
s.micAGC.ProcessSamples(int16Buffer)
|
||||
}
|
||||
|
||||
// Apply voice effects if available and enabled
|
||||
if s.effectsProcessor != nil && s.effectsProcessor.IsEnabled() {
|
||||
s.effectsProcessor.ProcessSamples(int16Buffer)
|
||||
}
|
||||
int16Buffer[i] = sample
|
||||
}
|
||||
|
||||
// Apply noise suppression if available and enabled
|
||||
if s.noiseProcessor != nil && s.noiseProcessor.IsEnabled() {
|
||||
s.noiseProcessor.ProcessSamples(int16Buffer)
|
||||
}
|
||||
|
||||
// Apply AGC to outgoing microphone audio (always enabled)
|
||||
if s.micAGC != nil {
|
||||
s.micAGC.ProcessSamples(int16Buffer)
|
||||
// Mix with or use file audio if playing
|
||||
hasFileAudio := false
|
||||
var outputBuffer []int16
|
||||
|
||||
if s.filePlayer != nil && s.filePlayer.IsPlaying() {
|
||||
fileAudio := s.filePlayer.GetAudioFrame()
|
||||
if fileAudio != nil && len(fileAudio) > 0 {
|
||||
hasFileAudio = true
|
||||
// File audio is stereo - send as stereo when file is playing
|
||||
// Create stereo buffer (frameSize * 2 channels)
|
||||
outputBuffer = make([]int16, frameSize*2)
|
||||
|
||||
if hasMicInput {
|
||||
// Mix mono mic with stereo file
|
||||
for i := 0; i < frameSize; i++ {
|
||||
if i*2+1 < len(fileAudio) {
|
||||
// Left channel: mic + file left
|
||||
left := int32(int16Buffer[i]) + int32(fileAudio[i*2])
|
||||
if left > 32767 {
|
||||
left = 32767
|
||||
} else if left < -32768 {
|
||||
left = -32768
|
||||
}
|
||||
outputBuffer[i*2] = int16(left)
|
||||
|
||||
// Right channel: mic + file right
|
||||
right := int32(int16Buffer[i]) + int32(fileAudio[i*2+1])
|
||||
if right > 32767 {
|
||||
right = 32767
|
||||
} else if right < -32768 {
|
||||
right = -32768
|
||||
}
|
||||
outputBuffer[i*2+1] = int16(right)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use file audio only (already stereo)
|
||||
copy(outputBuffer, fileAudio[:frameSize*2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply voice effects if available and enabled
|
||||
if s.effectsProcessor != nil && s.effectsProcessor.IsEnabled() {
|
||||
s.effectsProcessor.ProcessSamples(int16Buffer)
|
||||
// Determine what to send
|
||||
if hasFileAudio {
|
||||
// Send stereo buffer when file is playing
|
||||
outgoing <- gumble.AudioBuffer(outputBuffer)
|
||||
} else if hasMicInput {
|
||||
// Send mono mic when no file is playing
|
||||
outgoing <- gumble.AudioBuffer(int16Buffer)
|
||||
}
|
||||
|
||||
outgoing <- gumble.AudioBuffer(int16Buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ func (*generator) NewEncoder() gumble.AudioEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
// NewStereoEncoder creates a stereo encoder for file playback
|
||||
func NewStereoEncoder() gumble.AudioEncoder {
|
||||
// Create stereo encoder for file playback
|
||||
e, _ := opus.NewEncoder(gumble.AudioSampleRate, gumble.AudioChannels, opus.AppAudio)
|
||||
e.SetBitrateToMax()
|
||||
return &Encoder{
|
||||
e,
|
||||
}
|
||||
}
|
||||
|
||||
func (*generator) NewDecoder() gumble.AudioDecoder {
|
||||
// Create decoder with stereo support
|
||||
d, _ := opus.NewDecoder(gumble.AudioSampleRate, gumble.AudioChannels)
|
||||
|
||||
Reference in New Issue
Block a user