-
-
Notifications
You must be signed in to change notification settings - Fork 128
Expand file tree
/
Copy pathaudio_playout.go
More file actions
124 lines (111 loc) · 3.21 KB
/
Copy pathaudio_playout.go
File metadata and controls
124 lines (111 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package meowcaller
const (
audioPlayoutPrefillSamples = 2 * FrameSamples
audioPlayoutMaxGapSamples = 2 * SampleRate
)
type audioPlayoutFrame struct {
timestamp uint32
pcm []float32
}
// audioPlayoutBuffer converts packet-oriented decoder output into a continuous
// RTP-timestamped PCM stream. Holding two packet intervals gives the downstream
// real-time track enough headroom for normal relay jitter.
type audioPlayoutBuffer struct {
pending *audioPlayoutFrame
prefill [][]float32
prefillSamples int
started bool
}
func newAudioPlayoutBuffer() *audioPlayoutBuffer {
return &audioPlayoutBuffer{}
}
func (p *audioPlayoutBuffer) Push(timestamp uint32, frame []float32, sink AudioSink) (startedNow bool, err error) {
if p.pending == nil {
p.pending = &audioPlayoutFrame{timestamp: timestamp, pcm: frame}
return false, nil
}
delta := timestamp - p.pending.timestamp
if delta == 0 || delta > audioPlayoutMaxGapSamples {
p.reset(timestamp, frame)
return false, nil
}
ready := alignAudioFrame(p.pending.pcm, int(delta))
p.pending = &audioPlayoutFrame{timestamp: timestamp, pcm: frame}
if p.started && sink != nil {
return false, sink.WriteFrame(ready)
}
if p.started {
p.started = false
p.prefill = nil
p.prefillSamples = 0
}
p.prefill = append(p.prefill, ready)
p.prefillSamples += len(ready)
for p.prefillSamples > audioPlayoutMaxGapSamples && len(p.prefill) > 1 {
p.prefillSamples -= len(p.prefill[0])
p.prefill = p.prefill[1:]
}
if sink == nil || p.prefillSamples < audioPlayoutPrefillSamples {
return false, nil
}
for len(p.prefill) > 0 {
buffered := p.prefill[0]
if err = sink.WriteFrame(buffered); err != nil {
return false, err
}
p.prefillSamples -= len(buffered)
p.prefill = p.prefill[1:]
}
p.started = true
p.prefill = nil
return true, nil
}
func (p *audioPlayoutBuffer) Flush(sink AudioSink) error {
if !p.started || p.pending == nil || sink == nil {
return nil
}
err := sink.WriteFrame(p.pending.pcm)
p.pending = nil
return err
}
func (p *audioPlayoutBuffer) Drain(sink AudioSink) error {
// Source of truth: https://github.com/purpshell/meowcaller/blob/fabad4acce2147da4e40c1e8c6a1643053ae8c59/datasheets/group-audio-mixer.md#L20-L27
frames := append([][]float32(nil), p.prefill...)
if p.pending != nil {
frames = append(frames, p.pending.pcm)
}
p.pending = nil
p.prefill = nil
p.prefillSamples = 0
p.started = false
if sink == nil {
return nil
}
for _, frame := range frames {
if err := sink.WriteFrame(frame); err != nil {
return err
}
}
return nil
}
func (p *audioPlayoutBuffer) reset(timestamp uint32, frame []float32) {
p.pending = &audioPlayoutFrame{timestamp: timestamp, pcm: frame}
p.prefill = nil
p.prefillSamples = 0
p.started = false
}
func alignAudioFrame(frame []float32, samples int) []float32 {
// Real-time sinks advance while no packets arrive. Preserve a
// decoder's natural multi-frame output, but do not replay an elapsed RTP gap
// as queued silence when the next packet finally arrives.
maxSamples := max(len(frame), FrameSamples)
if samples > maxSamples {
samples = maxSamples
}
if samples == len(frame) {
return frame
}
aligned := make([]float32, samples)
copy(aligned, frame)
return aligned
}