Padding/probe packets use a sequence number but carry no audio. They were treated as lost, so each one played an invented 20 ms frame (a glitch) and pushed the buffer a frame deeper for drift correction to remove again. Signature in the first live test: lost ~= drift-drops (74 vs 72 per minute). They are now skipped and counted as 'padding'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
1.5 KiB
Plaintext
45 lines
1.5 KiB
Plaintext
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/pion/rtp"
|
|
"gopkg.in/hraban/opus.v2"
|
|
)
|
|
|
|
func TestPadding(t *testing.T) {
|
|
for _, name := range []string{"empty payload", "other payload type"} {
|
|
enc, _ := opus.NewEncoder(rate, channels, opus.AppAudio)
|
|
dec, _ := opus.NewDecoder(rate, channels)
|
|
jb := newJitterBuffer(dec, 111)
|
|
pcm := make([]int16, frameSamps*channels)
|
|
buf := make([]byte, 4000)
|
|
seq, ts := uint16(0), 0.0
|
|
frames := 0
|
|
// 10 minutes of playout; audio packet every 20 ms, plus a padding packet every 40 seqs.
|
|
for tms := 0.0; tms < 600000; tms += 20 {
|
|
for ts <= tms {
|
|
if seq%40 == 39 {
|
|
p := &rtp.Packet{Header: rtp.Header{SequenceNumber: seq, PayloadType: 111}}
|
|
if name != "empty payload" {
|
|
p.PayloadType, p.Payload = 13, []byte{0x40}
|
|
}
|
|
jb.push(p)
|
|
seq++
|
|
continue
|
|
}
|
|
for k := range pcm { pcm[k] = int16(6000 * math.Sin(float64(frames*frameSamps+k/2)*2*math.Pi*220/rate)) }
|
|
n, _ := enc.Encode(pcm, buf)
|
|
jb.push(&rtp.Packet{Header: rtp.Header{SequenceNumber: seq, PayloadType: 111}, Payload: append([]byte(nil), buf[:n]...)})
|
|
seq++; frames++; ts += 20
|
|
}
|
|
jb.next()
|
|
}
|
|
c, d, tg := jb.snapshot()
|
|
fmt.Printf("%-20s padding=%d lost=%d fec=%d concealed=%d drift-drops=%d inserted=%d underruns=%d buffer=%d/%dms\n", name, c.Padding, c.Lost, c.FEC, c.PLC, c.Dropped, c.Inserted, c.Underruns, d, tg)
|
|
if c.Lost+c.PLC+c.Dropped+c.Underruns != 0 { t.Errorf("%s: padding still causes damage", name) }
|
|
}
|
|
}
|