mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 04:07:07 +00:00
76 lines
1.9 KiB
Go
76 lines
1.9 KiB
Go
package audio
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/pion/interceptor/pkg/jitterbuffer"
|
|
"github.com/pion/rtp"
|
|
)
|
|
|
|
// pion's jitterbuffer + late-drop on Push + skip-ahead on persistent gap;
|
|
// PopOrSkip reports gap counts so the caller can run PLC over them
|
|
type Playout struct {
|
|
mu sync.Mutex
|
|
jb *jitterbuffer.JitterBuffer
|
|
started bool // first Pop succeeded -> headSeq is meaningful
|
|
headSeq uint16 // expected next seq
|
|
// caps the scan in PopOrSkip's slow path
|
|
MaxSkip uint16
|
|
}
|
|
|
|
var ErrEmpty = errors.New("playout: empty")
|
|
|
|
// primePackets=2 is ~40ms at 20ms framing
|
|
func NewPlayout(primePackets uint16) *Playout {
|
|
if primePackets == 0 {
|
|
primePackets = 2
|
|
}
|
|
return &Playout{
|
|
jb: jitterbuffer.New(jitterbuffer.WithMinimumPacketCount(primePackets)),
|
|
MaxSkip: 64,
|
|
}
|
|
}
|
|
|
|
// late packets (seq behind headSeq) are dropped
|
|
func (p *Playout) Push(pkt *rtp.Packet) {
|
|
if pkt == nil {
|
|
return
|
|
}
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
// int16 cast -> signed seq distance
|
|
if p.started && int16(pkt.SequenceNumber-p.headSeq) < 0 {
|
|
return
|
|
}
|
|
p.jb.Push(pkt)
|
|
}
|
|
|
|
// returns (next playable packet, gap count to PLC over, err);
|
|
// ErrEmpty means priming or genuinely empty
|
|
func (p *Playout) PopOrSkip() (*rtp.Packet, int, error) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if pkt, err := p.jb.Pop(); err == nil {
|
|
p.started = true
|
|
p.headSeq = pkt.SequenceNumber + 1
|
|
return pkt, 0, nil
|
|
} else if errors.Is(err, jitterbuffer.ErrPopWhileBuffering) {
|
|
return nil, 0, ErrEmpty
|
|
}
|
|
|
|
// scan forward for the next available packet
|
|
head := p.jb.PlayoutHead()
|
|
for i := uint16(1); i <= p.MaxSkip; i++ {
|
|
pkt, err := p.jb.PopAtSequence(head + i)
|
|
if err == nil {
|
|
// PopAtSequence advances by 1; head must skip past the gap
|
|
p.jb.SetPlayoutHead(pkt.SequenceNumber + 1)
|
|
p.started = true
|
|
p.headSeq = pkt.SequenceNumber + 1
|
|
return pkt, int(i), nil
|
|
}
|
|
}
|
|
return nil, 0, ErrEmpty
|
|
}
|