213 lines
5.1 KiB
Go
213 lines
5.1 KiB
Go
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
|
||
|
|
}
|