package teamspeak import "time" type receiveTimeline struct { clientID int decoder voiceDecoder packets map[uint16]voicePacket expected uint16 initialized bool ready bool plc int lossWait int muted bool speaking bool starved bool hasPlayed bool lastPacket time.Time lastActive time.Time pending []int16 lastReal bool } const ( receiveLossWaitTicks = 2 receiveTalkSpurtGap = 400 * time.Millisecond receiveMaxPending = receiveFrameInterleavedSamples * 12 ) func newReceiveTimeline(clientID int, decoder voiceDecoder) *receiveTimeline { return &receiveTimeline{clientID: clientID, decoder: decoder, packets: make(map[uint16]voicePacket)} } func (t *receiveTimeline) release() { if t.decoder != nil { t.decoder.Release() t.decoder = nil } } func seqAhead(a, b uint16) bool { return int16(a-b) > 0 } func (t *receiveTimeline) resetForTalkSpurt(sequence uint16) { clear(t.packets) t.pending = t.pending[:0] t.expected = sequence t.initialized = true t.ready = false t.plc = 0 t.lossWait = 0 t.starved = false } func (t *receiveTimeline) push(p voicePacket) { if _, exists := t.packets[p.sequence]; exists { return } // A quiet gap or an exhausted timeline starts a new packet epoch. This is // deliberately checked before the normal forward-window rejection so a // resumed talk spurt cannot be trapped behind an obsolete expected cursor. if t.initialized && !t.lastPacket.IsZero() && (p.at.Sub(t.lastPacket) >= receiveTalkSpurtGap || (t.hasPlayed && t.starved && seqAhead(p.sequence, t.expected) && int(uint16(p.sequence-t.expected)) > receiveMaxPackets)) { t.resetForTalkSpurt(p.sequence) } if !t.initialized { t.expected = p.sequence t.initialized = true } if p.sequence != t.expected && !seqAhead(p.sequence, t.expected) { return } if seqAhead(p.sequence, t.expected) && int(uint16(p.sequence-t.expected)) > receiveMaxPackets { return } if len(t.packets) >= receiveMaxPackets { return } t.packets[p.sequence] = p t.lastPacket = p.at if len(t.packets) >= receivePrebuffer { t.ready = true } } func (t *receiveTimeline) takeFrame(pcm []int16) ([]int16, bool) { if len(pcm) == 0 { return nil, false } if len(t.pending)+len(pcm) > receiveMaxPending { // Keep the newest decoded audio when a decoder returns a long burst. over := len(t.pending) + len(pcm) - receiveMaxPending if over >= len(t.pending) { t.pending = t.pending[:0] } else { t.pending = t.pending[over:] } } t.pending = append(t.pending, pcm...) if len(t.pending) < receiveFrameInterleavedSamples { return nil, false } frame := append([]int16(nil), t.pending[:receiveFrameInterleavedSamples]...) t.pending = t.pending[receiveFrameInterleavedSamples:] return frame, true } func (t *receiveTimeline) popPending() ([]int16, bool) { if len(t.pending) < receiveFrameInterleavedSamples { return nil, false } frame := append([]int16(nil), t.pending[:receiveFrameInterleavedSamples]...) t.pending = t.pending[receiveFrameInterleavedSamples:] return frame, true } func (t *receiveTimeline) earliestPacket() (uint16, bool) { var earliest uint16 found := false for sequence := range t.packets { if !found || seqAhead(earliest, sequence) { earliest, found = sequence, true } } return earliest, found } // next returns at most one fixed output frame. It never advances expected while // the packet buffer is empty: an empty buffer is normal talk-spurt silence, not // confirmed packet loss. lastReal distinguishes decoded audio from PLC for UI. func (t *receiveTimeline) next() ([]int16, bool) { t.lastReal = false if frame, ok := t.popPending(); ok { t.starved = false t.lastReal = true return frame, true } if !t.ready || t.decoder == nil { t.starved = true return nil, false } if p, ok := t.packets[t.expected]; ok { delete(t.packets, t.expected) t.expected++ t.plc = 0 t.lossWait = 0 pcm, err := t.decoder.Decode(p.data) if err != nil { t.starved = true return nil, false } frame, active := t.takeFrame(pcm) if active { t.starved = false t.hasPlayed = true t.lastReal = true } return frame, active } // Do not PLC ordinary silence. A later packet is evidence of a gap. if _, found := t.earliestPacket(); !found { t.starved = true return nil, false } if t.lossWait < receiveLossWaitTicks { t.lossWait++ t.starved = true return nil, false } if t.plc < receiveMaxPLC { t.plc++ t.lossWait = 0 t.expected++ pcm, err := t.decoder.DecodeLost() if err != nil { t.starved = true return nil, false } frame, active := t.takeFrame(pcm) if active { // PLC preserves an existing speaking state but never starts one. t.starved = false } return frame, active } if earliest, found := t.earliestPacket(); found { t.expected = earliest t.plc = 0 t.lossWait = 0 t.starved = false return t.next() } t.starved = true return nil, false } func mixPCM16(acc []int32) []byte { out := make([]byte, receiveFrameBytes) for i, sample := range acc { if sample > 32767 { sample = 32767 } else if sample < -32768 { sample = -32768 } out[2*i], out[2*i+1] = byte(int16(sample)), byte(int16(sample)>>8) } return out }