package teamspeak import ( "errors" "log" "sync" "sync/atomic" "time" ) const ( receiveSampleRate = 48000 receiveChannels = 2 receiveFrameSamplesPerChannel = 960 receiveFrameInterleavedSamples = receiveFrameSamplesPerChannel * receiveChannels receiveFrameBytes = receiveFrameInterleavedSamples * 2 receiveMaxDecodeSamplesPerChannel = 5760 receivePrebuffer = 3 receiveMaxPackets = 12 receiveMaxPLC = 3 ) // voiceDecoder decodes one remote Opus stream. It is intentionally private so // gomobile does not try to expose the decoder interface in the public AAR API. // Android production wiring will provide a libopus-backed implementation. type voiceDecoder interface { Decode(packet []byte) ([]int16, error) DecodeLost() ([]int16, error) Release() } type voiceDecoderFactory interface { NewvoiceDecoder(clientID int, codec int) (voiceDecoder, error) } type voicePacket struct { clientID int codec int sequence uint16 data []byte at time.Time } type audioCommand struct { kind string clientID int muted bool done chan struct{} } // receiveAudio owns all receive-side decoder, jitter, mute, speaking, and // mixing state on one goroutine. Its ingress method is safe for protocol // callback goroutines and never waits for decoding or playback. type receiveAudio struct { factory voiceDecoderFactory packets uint64 decoded uint64 mixed uint64 ingress chan voicePacket commands chan audioCommand stop chan struct{} done chan struct{} onPCM func([]byte) onSpeaking func(int, bool) onDrop func() stopOnce sync.Once startOnce sync.Once stateMu sync.Mutex started bool } func newReceiveAudio(factory voiceDecoderFactory, onPCM func([]byte), onSpeaking func(int, bool)) *receiveAudio { return &receiveAudio{ factory: factory, ingress: make(chan voicePacket, 64), commands: make(chan audioCommand, 32), stop: make(chan struct{}), done: make(chan struct{}), onPCM: onPCM, onSpeaking: onSpeaking, } } func (r *receiveAudio) start() { r.startOnce.Do(func() { log.Printf("[TSVoice] receive worker started factory=%T", r.factory) r.stateMu.Lock() r.started = true r.stateMu.Unlock() go r.run() }) } func (r *receiveAudio) stopAndWait() { r.stateMu.Lock() started := r.started r.stateMu.Unlock() if !started { return } r.stopOnce.Do(func() { close(r.stop) }) <-r.done } func (r *receiveAudio) enqueue(clientID, codec int, sequence uint16, data []byte) { if clientID <= 0 || (codec != 4 && codec != 5) || len(data) == 0 { return } packet := voicePacket{clientID: clientID, codec: codec, sequence: sequence, data: append([]byte(nil), data...), at: time.Now()} count := atomic.AddUint64(&r.packets, 1) if count <= 5 || count%250 == 0 { log.Printf("[TSVoice] ingress count=%d client=%d codec=%d sequence=%d payload=%d", count, clientID, codec, sequence, len(data)) } select { case r.ingress <- packet: default: if r.onDrop != nil { r.onDrop() } } } func (r *receiveAudio) command(cmd audioCommand) { select { case r.commands <- cmd: default: if cmd.done != nil { close(cmd.done) } } } func (r *receiveAudio) run() { defer close(r.done) timelines := make(map[int]*receiveTimeline) ticker := time.NewTicker(20 * time.Millisecond) defer ticker.Stop() for { select { case <-r.stop: for id, t := range timelines { t.release() if t.speaking && r.onSpeaking != nil { r.onSpeaking(id, false) } } return case cmd := <-r.commands: r.applyCommand(timelines, cmd) case p := <-r.ingress: r.accept(timelines, p) case now := <-ticker.C: // Drain a bounded amount before every playout deadline so ingress // bursts cannot indefinitely postpone a 20 ms output frame. for i := 0; i < 32; i++ { select { case cmd := <-r.commands: r.applyCommand(timelines, cmd) case p := <-r.ingress: r.accept(timelines, p) default: i = 32 } } r.tick(timelines, now) } } } func (r *receiveAudio) accept(timelines map[int]*receiveTimeline, p voicePacket) { t := timelines[p.clientID] if t == nil { if r.factory == nil { return } decoder, err := r.factory.NewvoiceDecoder(p.clientID, p.codec) if err != nil || decoder == nil { log.Printf("[TSVoice] decoder create failed client=%d codec=%d err=%v", p.clientID, p.codec, err) return } log.Printf("[TSVoice] decoder created client=%d codec=%d", p.clientID, p.codec) t = newReceiveTimeline(p.clientID, decoder) timelines[p.clientID] = t } t.push(p) } func (r *receiveAudio) applyCommand(timelines map[int]*receiveTimeline, cmd audioCommand) { switch cmd.kind { case "mute": if t := timelines[cmd.clientID]; t != nil { t.muted = cmd.muted } case "remove": if t := timelines[cmd.clientID]; t != nil { t.release() if t.speaking && r.onSpeaking != nil { r.onSpeaking(cmd.clientID, false) } delete(timelines, cmd.clientID) } case "clear": for id, t := range timelines { t.release() if t.speaking && r.onSpeaking != nil { r.onSpeaking(id, false) } delete(timelines, id) } } if cmd.done != nil { close(cmd.done) } } func (r *receiveAudio) tick(timelines map[int]*receiveTimeline, now time.Time) { mix := make([]int32, receiveFrameInterleavedSamples) for id, t := range timelines { if now.Sub(t.lastPacket) > 2*time.Second { t.release() if t.speaking && r.onSpeaking != nil { r.onSpeaking(id, false) } delete(timelines, id) continue } pcm, active := t.next() if active { count := atomic.AddUint64(&r.decoded, 1) if count <= 5 || count%250 == 0 { log.Printf("[TSVoice] decoded count=%d client=%d", count, id) } if t.lastReal { t.lastActive = now if !t.speaking { t.speaking = true if r.onSpeaking != nil { r.onSpeaking(id, true) } } } } if t.speaking && now.Sub(t.lastActive) > 400*time.Millisecond { t.speaking = false if r.onSpeaking != nil { r.onSpeaking(id, false) } } if !t.muted && len(pcm) == receiveFrameInterleavedSamples { for i, sample := range pcm { mix[i] += int32(sample) } } } if r.onPCM != nil && len(timelines) > 0 { count := atomic.AddUint64(&r.mixed, 1) if count <= 5 || count%250 == 0 { log.Printf("[TSVoice] mixed count=%d clients=%d", count, len(timelines)) } r.onPCM(mixPCM16(mix)) } } var errBadFrame = errors.New("voice decoder returned a non-20ms frame") func voiceCodecChannels(codec int) int { if codec == 5 { return receiveChannels } return 1 } func normalizeDecodedPCM(pcm []int16, channels int) []int16 { if channels == receiveChannels { return pcm } return monoToStereo(pcm) } func monoToStereo(mono []int16) []int16 { stereo := make([]int16, len(mono)*receiveChannels) for i, sample := range mono { stereo[i*receiveChannels], stereo[i*receiveChannels+1] = sample, sample } return stereo }