feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

View File

@@ -0,0 +1,8 @@
package nack
import "errors"
// ErrInvalidSize is returned by newReceiveLog/newSendBuffer, when an incorrect buffer size is supplied.
var ErrInvalidSize = errors.New("invalid buffer size")
var errPacketReleased = errors.New("could not retain packet, already released")

View File

@@ -0,0 +1,175 @@
package nack
import (
"math/rand"
"sync"
"time"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
)
// GeneratorInterceptorFactory is a interceptor.Factory for a GeneratorInterceptor
type GeneratorInterceptorFactory struct {
opts []GeneratorOption
}
// NewInterceptor constructs a new ReceiverInterceptor
func (g *GeneratorInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
i := &GeneratorInterceptor{
size: 512,
skipLastN: 0,
interval: time.Millisecond * 100,
receiveLogs: map[uint32]*receiveLog{},
close: make(chan struct{}),
log: logging.NewDefaultLoggerFactory().NewLogger("nack_generator"),
}
for _, opt := range g.opts {
if err := opt(i); err != nil {
return nil, err
}
}
if _, err := newReceiveLog(i.size); err != nil {
return nil, err
}
return i, nil
}
// GeneratorInterceptor interceptor generates nack feedback messages.
type GeneratorInterceptor struct {
interceptor.NoOp
size uint16
skipLastN uint16
interval time.Duration
m sync.Mutex
wg sync.WaitGroup
close chan struct{}
log logging.LeveledLogger
receiveLogs map[uint32]*receiveLog
receiveLogsMu sync.Mutex
}
// NewGeneratorInterceptor returns a new GeneratorInterceptorFactory
func NewGeneratorInterceptor(opts ...GeneratorOption) (*GeneratorInterceptorFactory, error) {
return &GeneratorInterceptorFactory{opts}, nil
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (n *GeneratorInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
n.m.Lock()
defer n.m.Unlock()
if n.isClosed() {
return writer
}
n.wg.Add(1)
go n.loop(writer)
return writer
}
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
// will be called once per rtp packet.
func (n *GeneratorInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
if !streamSupportNack(info) {
return reader
}
// error is already checked in NewGeneratorInterceptor
receiveLog, _ := newReceiveLog(n.size)
n.receiveLogsMu.Lock()
n.receiveLogs[info.SSRC] = receiveLog
n.receiveLogsMu.Unlock()
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(b, a)
if err != nil {
return 0, nil, err
}
if attr == nil {
attr = make(interceptor.Attributes)
}
header, err := attr.GetRTPHeader(b[:i])
if err != nil {
return 0, nil, err
}
receiveLog.add(header.SequenceNumber)
return i, attr, nil
})
}
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func (n *GeneratorInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
n.receiveLogsMu.Lock()
delete(n.receiveLogs, info.SSRC)
n.receiveLogsMu.Unlock()
}
// Close closes the interceptor
func (n *GeneratorInterceptor) Close() error {
defer n.wg.Wait()
n.m.Lock()
defer n.m.Unlock()
if !n.isClosed() {
close(n.close)
}
return nil
}
func (n *GeneratorInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
defer n.wg.Done()
senderSSRC := rand.Uint32() // #nosec
ticker := time.NewTicker(n.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
func() {
n.receiveLogsMu.Lock()
defer n.receiveLogsMu.Unlock()
for ssrc, receiveLog := range n.receiveLogs {
missing := receiveLog.missingSeqNumbers(n.skipLastN)
if len(missing) == 0 {
continue
}
nack := &rtcp.TransportLayerNack{
SenderSSRC: senderSSRC,
MediaSSRC: ssrc,
Nacks: rtcp.NackPairsFromSequenceNumbers(missing),
}
if _, err := rtcpWriter.Write([]rtcp.Packet{nack}, interceptor.Attributes{}); err != nil {
n.log.Warnf("failed sending nack: %+v", err)
}
}
}()
case <-n.close:
return
}
}
}
func (n *GeneratorInterceptor) isClosed() bool {
select {
case <-n.close:
return true
default:
return false
}
}

View File

@@ -0,0 +1,44 @@
package nack
import (
"time"
"github.com/pion/logging"
)
// GeneratorOption can be used to configure GeneratorInterceptor
type GeneratorOption func(r *GeneratorInterceptor) error
// GeneratorSize sets the size of the interceptor.
// Size must be one of: 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
func GeneratorSize(size uint16) GeneratorOption {
return func(r *GeneratorInterceptor) error {
r.size = size
return nil
}
}
// GeneratorSkipLastN sets the number of packets (n-1 packets before the last received packets) to ignore when generating
// nack requests.
func GeneratorSkipLastN(skipLastN uint16) GeneratorOption {
return func(r *GeneratorInterceptor) error {
r.skipLastN = skipLastN
return nil
}
}
// GeneratorLog sets a logger for the interceptor
func GeneratorLog(log logging.LeveledLogger) GeneratorOption {
return func(r *GeneratorInterceptor) error {
r.log = log
return nil
}
}
// GeneratorInterval sets the nack send interval for the interceptor
func GeneratorInterval(interval time.Duration) GeneratorOption {
return func(r *GeneratorInterceptor) error {
r.interval = interval
return nil
}
}

14
vendor/github.com/pion/interceptor/pkg/nack/nack.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// Package nack provides interceptors to implement sending and receiving negative acknowledgements
package nack
import "github.com/pion/interceptor"
func streamSupportNack(info *interceptor.StreamInfo) bool {
for _, fb := range info.RTCPFeedback {
if fb.Type == "nack" && fb.Parameter == "" {
return true
}
}
return false
}

View File

@@ -0,0 +1,134 @@
package nack
import (
"fmt"
"sync"
)
type receiveLog struct {
packets []uint64
size uint16
end uint16
started bool
lastConsecutive uint16
m sync.RWMutex
}
func newReceiveLog(size uint16) (*receiveLog, error) {
allowedSizes := make([]uint16, 0)
correctSize := false
for i := 6; i < 16; i++ {
if size == 1<<i {
correctSize = true
break
}
allowedSizes = append(allowedSizes, 1<<i)
}
if !correctSize {
return nil, fmt.Errorf("%w: %d is not a valid size, allowed sizes: %v", ErrInvalidSize, size, allowedSizes)
}
return &receiveLog{
packets: make([]uint64, size/64),
size: size,
}, nil
}
func (s *receiveLog) add(seq uint16) {
s.m.Lock()
defer s.m.Unlock()
if !s.started {
s.setReceived(seq)
s.end = seq
s.started = true
s.lastConsecutive = seq
return
}
diff := seq - s.end
switch {
case diff == 0:
return
case diff < uint16SizeHalf:
// this means a positive diff, in other words seq > end (with counting for rollovers)
for i := s.end + 1; i != seq; i++ {
// clear packets between end and seq (these may contain packets from a "size" ago)
s.delReceived(i)
}
s.end = seq
if s.lastConsecutive+1 == seq {
s.lastConsecutive = seq
} else if seq-s.lastConsecutive > s.size {
s.lastConsecutive = seq - s.size
s.fixLastConsecutive() // there might be valid packets at the beginning of the buffer now
}
case s.lastConsecutive+1 == seq:
// negative diff, seq < end (with counting for rollovers)
s.lastConsecutive = seq
s.fixLastConsecutive() // there might be other valid packets after seq
}
s.setReceived(seq)
}
func (s *receiveLog) get(seq uint16) bool {
s.m.RLock()
defer s.m.RUnlock()
diff := s.end - seq
if diff >= uint16SizeHalf {
return false
}
if diff >= s.size {
return false
}
return s.getReceived(seq)
}
func (s *receiveLog) missingSeqNumbers(skipLastN uint16) []uint16 {
s.m.RLock()
defer s.m.RUnlock()
until := s.end - skipLastN
if until-s.lastConsecutive >= uint16SizeHalf {
// until < s.lastConsecutive (counting for rollover)
return nil
}
missingPacketSeqNums := make([]uint16, 0)
for i := s.lastConsecutive + 1; i != until+1; i++ {
if !s.getReceived(i) {
missingPacketSeqNums = append(missingPacketSeqNums, i)
}
}
return missingPacketSeqNums
}
func (s *receiveLog) setReceived(seq uint16) {
pos := seq % s.size
s.packets[pos/64] |= 1 << (pos % 64)
}
func (s *receiveLog) delReceived(seq uint16) {
pos := seq % s.size
s.packets[pos/64] &^= 1 << (pos % 64)
}
func (s *receiveLog) getReceived(seq uint16) bool {
pos := seq % s.size
return (s.packets[pos/64] & (1 << (pos % 64))) != 0
}
func (s *receiveLog) fixLastConsecutive() {
i := s.lastConsecutive + 1
for ; i != s.end+1 && s.getReceived(i); i++ {
// find all consecutive packets
}
s.lastConsecutive = i - 1
}

View File

@@ -0,0 +1,139 @@
package nack
import (
"sync"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// ResponderInterceptorFactory is a interceptor.Factory for a ResponderInterceptor
type ResponderInterceptorFactory struct {
opts []ResponderOption
}
// NewInterceptor constructs a new ResponderInterceptor
func (r *ResponderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
i := &ResponderInterceptor{
size: 8192,
log: logging.NewDefaultLoggerFactory().NewLogger("nack_responder"),
streams: map[uint32]*localStream{},
packetMan: newPacketManager(),
}
for _, opt := range r.opts {
if err := opt(i); err != nil {
return nil, err
}
}
if _, err := newSendBuffer(i.size); err != nil {
return nil, err
}
return i, nil
}
// ResponderInterceptor responds to nack feedback messages
type ResponderInterceptor struct {
interceptor.NoOp
size uint16
log logging.LeveledLogger
packetMan *packetManager
streams map[uint32]*localStream
streamsMu sync.Mutex
}
type localStream struct {
sendBuffer *sendBuffer
rtpWriter interceptor.RTPWriter
}
// NewResponderInterceptor returns a new ResponderInterceptorFactor
func NewResponderInterceptor(opts ...ResponderOption) (*ResponderInterceptorFactory, error) {
return &ResponderInterceptorFactory{opts}, nil
}
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
// change in the future. The returned method will be called once per packet batch.
func (n *ResponderInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(b, a)
if err != nil {
return 0, nil, err
}
if attr == nil {
attr = make(interceptor.Attributes)
}
pkts, err := attr.GetRTCPPackets(b[:i])
if err != nil {
return 0, nil, err
}
for _, rtcpPacket := range pkts {
nack, ok := rtcpPacket.(*rtcp.TransportLayerNack)
if !ok {
continue
}
go n.resendPackets(nack)
}
return i, attr, err
})
}
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
// will be called once per rtp packet.
func (n *ResponderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
if !streamSupportNack(info) {
return writer
}
// error is already checked in NewGeneratorInterceptor
sendBuffer, _ := newSendBuffer(n.size)
n.streamsMu.Lock()
n.streams[info.SSRC] = &localStream{sendBuffer: sendBuffer, rtpWriter: writer}
n.streamsMu.Unlock()
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
pkt, err := n.packetMan.NewPacket(header, payload)
if err != nil {
return 0, err
}
sendBuffer.add(pkt)
return writer.Write(header, payload, attributes)
})
}
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func (n *ResponderInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
n.streamsMu.Lock()
delete(n.streams, info.SSRC)
n.streamsMu.Unlock()
}
func (n *ResponderInterceptor) resendPackets(nack *rtcp.TransportLayerNack) {
n.streamsMu.Lock()
stream, ok := n.streams[nack.MediaSSRC]
n.streamsMu.Unlock()
if !ok {
return
}
for i := range nack.Nacks {
nack.Nacks[i].Range(func(seq uint16) bool {
if p := stream.sendBuffer.get(seq); p != nil {
if _, err := stream.rtpWriter.Write(p.Header(), p.Payload(), interceptor.Attributes{}); err != nil {
n.log.Warnf("failed resending nacked packet: %+v", err)
}
p.Release()
}
return true
})
}
}

View File

@@ -0,0 +1,23 @@
package nack
import "github.com/pion/logging"
// ResponderOption can be used to configure ResponderInterceptor
type ResponderOption func(s *ResponderInterceptor) error
// ResponderSize sets the size of the interceptor.
// Size must be one of: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768
func ResponderSize(size uint16) ResponderOption {
return func(r *ResponderInterceptor) error {
r.size = size
return nil
}
}
// ResponderLog sets a logger for the interceptor
func ResponderLog(log logging.LeveledLogger) ResponderOption {
return func(r *ResponderInterceptor) error {
r.log = log
return nil
}
}

View File

@@ -0,0 +1,105 @@
package nack
import (
"io"
"sync"
"github.com/pion/rtp"
)
const maxPayloadLen = 1460
type packetManager struct {
headerPool *sync.Pool
payloadPool *sync.Pool
}
func newPacketManager() *packetManager {
return &packetManager{
headerPool: &sync.Pool{
New: func() interface{} {
return &rtp.Header{}
},
},
payloadPool: &sync.Pool{
New: func() interface{} {
buf := make([]byte, maxPayloadLen)
return &buf
},
},
}
}
func (m *packetManager) NewPacket(header *rtp.Header, payload []byte) (*retainablePacket, error) {
if len(payload) > maxPayloadLen {
return nil, io.ErrShortBuffer
}
p := &retainablePacket{
onRelease: m.releasePacket,
// new packets have retain count of 1
count: 1,
}
p.header = m.headerPool.Get().(*rtp.Header)
*p.header = header.Clone()
if payload != nil {
p.buffer = m.payloadPool.Get().(*[]byte)
size := copy(*p.buffer, payload)
p.payload = (*p.buffer)[:size]
}
return p, nil
}
func (m *packetManager) releasePacket(header *rtp.Header, payload *[]byte) {
m.headerPool.Put(header)
if payload != nil {
m.payloadPool.Put(payload)
}
}
type retainablePacket struct {
onRelease func(*rtp.Header, *[]byte)
countMu sync.Mutex
count int
header *rtp.Header
buffer *[]byte
payload []byte
}
func (p *retainablePacket) Header() *rtp.Header {
return p.header
}
func (p *retainablePacket) Payload() []byte {
return p.payload
}
func (p *retainablePacket) Retain() error {
p.countMu.Lock()
defer p.countMu.Unlock()
if p.count == 0 {
// already released
return errPacketReleased
}
p.count++
return nil
}
func (p *retainablePacket) Release() {
p.countMu.Lock()
defer p.countMu.Unlock()
p.count--
if p.count == 0 {
// release back to pool
p.onRelease(p.header, p.buffer)
p.header = nil
p.buffer = nil
p.payload = nil
}
}

View File

@@ -0,0 +1,101 @@
package nack
import (
"fmt"
"sync"
)
const (
uint16SizeHalf = 1 << 15
)
type sendBuffer struct {
packets []*retainablePacket
size uint16
lastAdded uint16
started bool
m sync.RWMutex
}
func newSendBuffer(size uint16) (*sendBuffer, error) {
allowedSizes := make([]uint16, 0)
correctSize := false
for i := 0; i < 16; i++ {
if size == 1<<i {
correctSize = true
break
}
allowedSizes = append(allowedSizes, 1<<i)
}
if !correctSize {
return nil, fmt.Errorf("%w: %d is not a valid size, allowed sizes: %v", ErrInvalidSize, size, allowedSizes)
}
return &sendBuffer{
packets: make([]*retainablePacket, size),
size: size,
}, nil
}
func (s *sendBuffer) add(packet *retainablePacket) {
s.m.Lock()
defer s.m.Unlock()
seq := packet.Header().SequenceNumber
if !s.started {
s.packets[seq%s.size] = packet
s.lastAdded = seq
s.started = true
return
}
diff := seq - s.lastAdded
if diff == 0 {
return
} else if diff < uint16SizeHalf {
for i := s.lastAdded + 1; i != seq; i++ {
idx := i % s.size
prevPacket := s.packets[idx]
if prevPacket != nil {
prevPacket.Release()
}
s.packets[idx] = nil
}
}
idx := seq % s.size
prevPacket := s.packets[idx]
if prevPacket != nil {
prevPacket.Release()
}
s.packets[idx] = packet
s.lastAdded = seq
}
func (s *sendBuffer) get(seq uint16) *retainablePacket {
s.m.RLock()
defer s.m.RUnlock()
diff := s.lastAdded - seq
if diff >= uint16SizeHalf {
return nil
}
if diff >= s.size {
return nil
}
pkt := s.packets[seq%s.size]
if pkt != nil {
if pkt.Header().SequenceNumber != seq {
return nil
}
// already released
if err := pkt.Retain(); err != nil {
return nil
}
}
return pkt
}

View File

@@ -0,0 +1,182 @@
package report
import (
"sync"
"time"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
)
// ReceiverInterceptorFactory is a interceptor.Factory for a ReceiverInterceptor
type ReceiverInterceptorFactory struct {
opts []ReceiverOption
}
// NewInterceptor constructs a new ReceiverInterceptor
func (r *ReceiverInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
i := &ReceiverInterceptor{
interval: 1 * time.Second,
now: time.Now,
log: logging.NewDefaultLoggerFactory().NewLogger("receiver_interceptor"),
close: make(chan struct{}),
}
for _, opt := range r.opts {
if err := opt(i); err != nil {
return nil, err
}
}
return i, nil
}
// NewReceiverInterceptor returns a new ReceiverInterceptorFactory
func NewReceiverInterceptor(opts ...ReceiverOption) (*ReceiverInterceptorFactory, error) {
return &ReceiverInterceptorFactory{opts}, nil
}
// ReceiverInterceptor interceptor generates receiver reports.
type ReceiverInterceptor struct {
interceptor.NoOp
interval time.Duration
now func() time.Time
streams sync.Map
log logging.LeveledLogger
m sync.Mutex
wg sync.WaitGroup
close chan struct{}
}
func (r *ReceiverInterceptor) isClosed() bool {
select {
case <-r.close:
return true
default:
return false
}
}
// Close closes the interceptor.
func (r *ReceiverInterceptor) Close() error {
defer r.wg.Wait()
r.m.Lock()
defer r.m.Unlock()
if !r.isClosed() {
close(r.close)
}
return nil
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (r *ReceiverInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
r.m.Lock()
defer r.m.Unlock()
if r.isClosed() {
return writer
}
r.wg.Add(1)
go r.loop(writer)
return writer
}
func (r *ReceiverInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
defer r.wg.Done()
ticker := time.NewTicker(r.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
now := r.now()
r.streams.Range(func(key, value interface{}) bool {
stream := value.(*receiverStream)
var pkts []rtcp.Packet
pkts = append(pkts, stream.generateReport(now))
if _, err := rtcpWriter.Write(pkts, interceptor.Attributes{}); err != nil {
r.log.Warnf("failed sending: %+v", err)
}
return true
})
case <-r.close:
return
}
}
}
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
// will be called once per rtp packet.
func (r *ReceiverInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
stream := newReceiverStream(info.SSRC, info.ClockRate)
r.streams.Store(info.SSRC, stream)
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(b, a)
if err != nil {
return 0, nil, err
}
if attr == nil {
attr = make(interceptor.Attributes)
}
header, err := attr.GetRTPHeader(b[:i])
if err != nil {
return 0, nil, err
}
stream.processRTP(r.now(), header)
return i, attr, nil
})
}
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
func (r *ReceiverInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
r.streams.Delete(info.SSRC)
}
// BindRTCPReader lets you modify any incoming RTCP packets. It is called once per sender/receiver, however this might
// change in the future. The returned method will be called once per packet batch.
func (r *ReceiverInterceptor) BindRTCPReader(reader interceptor.RTCPReader) interceptor.RTCPReader {
return interceptor.RTCPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(b, a)
if err != nil {
return 0, nil, err
}
if attr == nil {
attr = make(interceptor.Attributes)
}
pkts, err := attr.GetRTCPPackets(b[:i])
if err != nil {
return 0, nil, err
}
for _, pkt := range pkts {
if sr, ok := (pkt).(*rtcp.SenderReport); ok {
value, ok := r.streams.Load(sr.SSRC)
if !ok {
continue
}
stream := value.(*receiverStream)
stream.processSenderReport(r.now(), sr)
}
}
return i, attr, nil
})
}

View File

@@ -0,0 +1,34 @@
package report
import (
"time"
"github.com/pion/logging"
)
// ReceiverOption can be used to configure ReceiverInterceptor.
type ReceiverOption func(r *ReceiverInterceptor) error
// ReceiverLog sets a logger for the interceptor.
func ReceiverLog(log logging.LeveledLogger) ReceiverOption {
return func(r *ReceiverInterceptor) error {
r.log = log
return nil
}
}
// ReceiverInterval sets send interval for the interceptor.
func ReceiverInterval(interval time.Duration) ReceiverOption {
return func(r *ReceiverInterceptor) error {
r.interval = interval
return nil
}
}
// ReceiverNow sets an alternative for the time.Now function.
func ReceiverNow(f func() time.Time) ReceiverOption {
return func(r *ReceiverInterceptor) error {
r.now = f
return nil
}
}

View File

@@ -0,0 +1,159 @@
package report
import (
"math/rand"
"sync"
"time"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
type receiverStream struct {
ssrc uint32
receiverSSRC uint32
clockRate float64
m sync.Mutex
size uint16
packets []uint64
started bool
seqnumCycles uint16
lastSeqnum uint16
lastReportSeqnum uint16
lastRTPTimeRTP uint32
lastRTPTimeTime time.Time
jitter float64
lastSenderReport uint32
lastSenderReportTime time.Time
totalLost uint32
}
func newReceiverStream(ssrc uint32, clockRate uint32) *receiverStream {
receiverSSRC := rand.Uint32() // #nosec
return &receiverStream{
ssrc: ssrc,
receiverSSRC: receiverSSRC,
clockRate: float64(clockRate),
size: 128,
packets: make([]uint64, 128),
}
}
func (stream *receiverStream) processRTP(now time.Time, pktHeader *rtp.Header) {
stream.m.Lock()
defer stream.m.Unlock()
if !stream.started { // first frame
stream.started = true
stream.setReceived(pktHeader.SequenceNumber)
stream.lastSeqnum = pktHeader.SequenceNumber
stream.lastReportSeqnum = pktHeader.SequenceNumber - 1
stream.lastRTPTimeRTP = pktHeader.Timestamp
stream.lastRTPTimeTime = now
} else { // following frames
stream.setReceived(pktHeader.SequenceNumber)
diff := int32(pktHeader.SequenceNumber) - int32(stream.lastSeqnum)
if diff > 0 || diff < -0x0FFF {
// overflow
if diff < -0x0FFF {
stream.seqnumCycles++
}
// set missing packets as missing
for i := stream.lastSeqnum + 1; i != pktHeader.SequenceNumber; i++ {
stream.delReceived(i)
}
stream.lastSeqnum = pktHeader.SequenceNumber
}
// compute jitter
// https://tools.ietf.org/html/rfc3550#page-39
D := now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate -
(float64(pktHeader.Timestamp) - float64(stream.lastRTPTimeRTP))
if D < 0 {
D = -D
}
stream.jitter += (D - stream.jitter) / 16
stream.lastRTPTimeRTP = pktHeader.Timestamp
stream.lastRTPTimeTime = now
}
}
func (stream *receiverStream) setReceived(seq uint16) {
pos := seq % stream.size
stream.packets[pos/64] |= 1 << (pos % 64)
}
func (stream *receiverStream) delReceived(seq uint16) {
pos := seq % stream.size
stream.packets[pos/64] &^= 1 << (pos % 64)
}
func (stream *receiverStream) getReceived(seq uint16) bool {
pos := seq % stream.size
return (stream.packets[pos/64] & (1 << (pos % 64))) != 0
}
func (stream *receiverStream) processSenderReport(now time.Time, sr *rtcp.SenderReport) {
stream.m.Lock()
defer stream.m.Unlock()
stream.lastSenderReport = uint32(sr.NTPTime >> 16)
stream.lastSenderReportTime = now
}
func (stream *receiverStream) generateReport(now time.Time) *rtcp.ReceiverReport {
stream.m.Lock()
defer stream.m.Unlock()
totalSinceReport := stream.lastSeqnum - stream.lastReportSeqnum
totalLostSinceReport := func() uint32 {
if stream.lastSeqnum == stream.lastReportSeqnum {
return 0
}
ret := uint32(0)
for i := stream.lastReportSeqnum + 1; i != stream.lastSeqnum; i++ {
if !stream.getReceived(i) {
ret++
}
}
return ret
}()
stream.totalLost += totalLostSinceReport
// allow up to 24 bits
if totalLostSinceReport > 0xFFFFFF {
totalLostSinceReport = 0xFFFFFF
}
if stream.totalLost > 0xFFFFFF {
stream.totalLost = 0xFFFFFF
}
r := &rtcp.ReceiverReport{
SSRC: stream.receiverSSRC,
Reports: []rtcp.ReceptionReport{
{
SSRC: stream.ssrc,
LastSequenceNumber: uint32(stream.seqnumCycles)<<16 | uint32(stream.lastSeqnum),
LastSenderReport: stream.lastSenderReport,
FractionLost: uint8(float64(totalLostSinceReport*256) / float64(totalSinceReport)),
TotalLost: stream.totalLost,
Delay: func() uint32 {
if stream.lastSenderReportTime.IsZero() {
return 0
}
return uint32(now.Sub(stream.lastSenderReportTime).Seconds() * 65536)
}(),
Jitter: uint32(stream.jitter),
},
},
}
stream.lastReportSeqnum = stream.lastSeqnum
return r
}

View File

@@ -0,0 +1,2 @@
// Package report provides interceptors to implement sending sender and receiver reports.
package report

View File

@@ -0,0 +1,150 @@
package report
import (
"sync"
"time"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtcp"
"github.com/pion/rtp"
)
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
type SenderInterceptorFactory struct {
opts []SenderOption
}
// NewInterceptor constructs a new SenderInterceptor
func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
i := &SenderInterceptor{
interval: 1 * time.Second,
now: time.Now,
log: logging.NewDefaultLoggerFactory().NewLogger("sender_interceptor"),
close: make(chan struct{}),
}
for _, opt := range s.opts {
if err := opt(i); err != nil {
return nil, err
}
}
return i, nil
}
// NewSenderInterceptor returns a new SenderInterceptorFactory
func NewSenderInterceptor(opts ...SenderOption) (*SenderInterceptorFactory, error) {
return &SenderInterceptorFactory{opts}, nil
}
// SenderInterceptor interceptor generates sender reports.
type SenderInterceptor struct {
interceptor.NoOp
interval time.Duration
now func() time.Time
streams sync.Map
log logging.LeveledLogger
m sync.Mutex
wg sync.WaitGroup
close chan struct{}
}
func (s *SenderInterceptor) isClosed() bool {
select {
case <-s.close:
return true
default:
return false
}
}
// Close closes the interceptor.
func (s *SenderInterceptor) Close() error {
defer s.wg.Wait()
s.m.Lock()
defer s.m.Unlock()
if !s.isClosed() {
close(s.close)
}
return nil
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
s.m.Lock()
defer s.m.Unlock()
if s.isClosed() {
return writer
}
s.wg.Add(1)
go s.loop(writer)
return writer
}
func (s *SenderInterceptor) loop(rtcpWriter interceptor.RTCPWriter) {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
now := s.now()
s.streams.Range(func(key, value interface{}) bool {
ssrc := key.(uint32)
stream := value.(*senderStream)
stream.m.Lock()
defer stream.m.Unlock()
sr := &rtcp.SenderReport{
SSRC: ssrc,
NTPTime: ntpTime(now),
RTPTime: stream.lastRTPTimeRTP + uint32(now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate),
PacketCount: stream.packetCount,
OctetCount: stream.octetCount,
}
if _, err := rtcpWriter.Write([]rtcp.Packet{sr}, interceptor.Attributes{}); err != nil {
s.log.Warnf("failed sending: %+v", err)
}
return true
})
case <-s.close:
return
}
}
}
// BindLocalStream lets you modify any outgoing RTP packets. It is called once for per LocalStream. The returned method
// will be called once per rtp packet.
func (s *SenderInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
stream := newSenderStream(info.ClockRate)
s.streams.Store(info.SSRC, stream)
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, a interceptor.Attributes) (int, error) {
stream.processRTP(s.now(), header, payload)
return writer.Write(header, payload, a)
})
}
func ntpTime(t time.Time) uint64 {
// seconds since 1st January 1900
s := (float64(t.UnixNano()) / 1000000000) + 2208988800
// higher 32 bits are the integer part, lower 32 bits are the fractional part
integerPart := uint32(s)
fractionalPart := uint32((s - float64(integerPart)) * 0xFFFFFFFF)
return uint64(integerPart)<<32 | uint64(fractionalPart)
}

View File

@@ -0,0 +1,34 @@
package report
import (
"time"
"github.com/pion/logging"
)
// SenderOption can be used to configure SenderInterceptor.
type SenderOption func(r *SenderInterceptor) error
// SenderLog sets a logger for the interceptor.
func SenderLog(log logging.LeveledLogger) SenderOption {
return func(r *SenderInterceptor) error {
r.log = log
return nil
}
}
// SenderInterval sets send interval for the interceptor.
func SenderInterval(interval time.Duration) SenderOption {
return func(r *SenderInterceptor) error {
r.interval = interval
return nil
}
}
// SenderNow sets an alternative for the time.Now function.
func SenderNow(f func() time.Time) SenderOption {
return func(r *SenderInterceptor) error {
r.now = f
return nil
}
}

View File

@@ -0,0 +1,37 @@
package report
import (
"sync"
"time"
"github.com/pion/rtp"
)
type senderStream struct {
clockRate float64
m sync.Mutex
// data from rtp packets
lastRTPTimeRTP uint32
lastRTPTimeTime time.Time
packetCount uint32
octetCount uint32
}
func newSenderStream(clockRate uint32) *senderStream {
return &senderStream{
clockRate: float64(clockRate),
}
}
func (stream *senderStream) processRTP(now time.Time, header *rtp.Header, payload []byte) {
stream.m.Lock()
defer stream.m.Unlock()
// always update time to minimize errors
stream.lastRTPTimeRTP = header.Timestamp
stream.lastRTPTimeTime = now
stream.packetCount++
stream.octetCount += uint32(len(payload))
}

View File

@@ -0,0 +1,57 @@
package twcc
import (
"sync/atomic"
"github.com/pion/interceptor"
"github.com/pion/rtp"
)
// HeaderExtensionInterceptorFactory is a interceptor.Factory for a HeaderExtensionInterceptor
type HeaderExtensionInterceptorFactory struct{}
// NewInterceptor constructs a new HeaderExtensionInterceptor
func (h *HeaderExtensionInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
return &HeaderExtensionInterceptor{}, nil
}
// NewHeaderExtensionInterceptor returns a HeaderExtensionInterceptorFactory
func NewHeaderExtensionInterceptor() (*HeaderExtensionInterceptorFactory, error) {
return &HeaderExtensionInterceptorFactory{}, nil
}
// HeaderExtensionInterceptor adds transport wide sequence numbers as header extension to each RTP packet
type HeaderExtensionInterceptor struct {
interceptor.NoOp
nextSequenceNr uint32
}
const transportCCURI = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01"
// BindLocalStream returns a writer that adds a rtp.TransportCCExtension
// header with increasing sequence numbers to each outgoing packet.
func (h *HeaderExtensionInterceptor) BindLocalStream(info *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
var hdrExtID uint8
for _, e := range info.RTPHeaderExtensions {
if e.URI == transportCCURI {
hdrExtID = uint8(e.ID)
break
}
}
if hdrExtID == 0 { // Don't add header extension if ID is 0, because 0 is an invalid extension ID
return writer
}
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
sequenceNumber := atomic.AddUint32(&h.nextSequenceNr, 1) - 1
tcc, err := (&rtp.TransportCCExtension{TransportSequence: uint16(sequenceNumber)}).Marshal()
if err != nil {
return 0, err
}
err = header.SetExtension(hdrExtID, tcc)
if err != nil {
return 0, err
}
return writer.Write(header, payload, attributes)
})
}

View File

@@ -0,0 +1,189 @@
package twcc
import (
"math/rand"
"sync"
"time"
"github.com/pion/interceptor"
"github.com/pion/logging"
"github.com/pion/rtp"
)
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
type SenderInterceptorFactory struct {
opts []Option
}
// NewInterceptor constructs a new SenderInterceptor
func (s *SenderInterceptorFactory) NewInterceptor(id string) (interceptor.Interceptor, error) {
i := &SenderInterceptor{
log: logging.NewDefaultLoggerFactory().NewLogger("twcc_sender_interceptor"),
packetChan: make(chan packet),
close: make(chan struct{}),
interval: 100 * time.Millisecond,
startTime: time.Now(),
}
for _, opt := range s.opts {
err := opt(i)
if err != nil {
return nil, err
}
}
return i, nil
}
// NewSenderInterceptor returns a new SenderInterceptorFactory configured with the given options.
func NewSenderInterceptor(opts ...Option) (*SenderInterceptorFactory, error) {
return &SenderInterceptorFactory{opts: opts}, nil
}
// SenderInterceptor sends transport wide congestion control reports as specified in:
// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
type SenderInterceptor struct {
interceptor.NoOp
log logging.LeveledLogger
m sync.Mutex
wg sync.WaitGroup
close chan struct{}
interval time.Duration
startTime time.Time
recorder *Recorder
packetChan chan packet
}
// An Option is a function that can be used to configure a SenderInterceptor
type Option func(*SenderInterceptor) error
// SendInterval sets the interval at which the interceptor
// will send new feedback reports.
func SendInterval(interval time.Duration) Option {
return func(s *SenderInterceptor) error {
s.interval = interval
return nil
}
}
// BindRTCPWriter lets you modify any outgoing RTCP packets. It is called once per PeerConnection. The returned method
// will be called once per packet batch.
func (s *SenderInterceptor) BindRTCPWriter(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
s.m.Lock()
defer s.m.Unlock()
s.recorder = NewRecorder(rand.Uint32()) // #nosec
if s.isClosed() {
return writer
}
s.wg.Add(1)
go s.loop(writer)
return writer
}
type packet struct {
hdr *rtp.Header
sequenceNumber uint16
arrivalTime int64
ssrc uint32
}
// BindRemoteStream lets you modify any incoming RTP packets. It is called once for per RemoteStream. The returned method
// will be called once per rtp packet.
func (s *SenderInterceptor) BindRemoteStream(info *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
var hdrExtID uint8
for _, e := range info.RTPHeaderExtensions {
if e.URI == transportCCURI {
hdrExtID = uint8(e.ID)
break
}
}
if hdrExtID == 0 { // Don't try to read header extension if ID is 0, because 0 is an invalid extension ID
return reader
}
return interceptor.RTPReaderFunc(func(buf []byte, attributes interceptor.Attributes) (int, interceptor.Attributes, error) {
i, attr, err := reader.Read(buf, attributes)
if err != nil {
return 0, nil, err
}
if attr == nil {
attr = make(interceptor.Attributes)
}
header, err := attr.GetRTPHeader(buf[:i])
if err != nil {
return 0, nil, err
}
var tccExt rtp.TransportCCExtension
if ext := header.GetExtension(hdrExtID); ext != nil {
err = tccExt.Unmarshal(ext)
if err != nil {
return 0, nil, err
}
s.packetChan <- packet{
hdr: header,
sequenceNumber: tccExt.TransportSequence,
arrivalTime: time.Since(s.startTime).Microseconds(),
ssrc: info.SSRC,
}
}
return i, attr, nil
})
}
// Close closes the interceptor.
func (s *SenderInterceptor) Close() error {
defer s.wg.Wait()
s.m.Lock()
defer s.m.Unlock()
if !s.isClosed() {
close(s.close)
}
return nil
}
func (s *SenderInterceptor) isClosed() bool {
select {
case <-s.close:
return true
default:
return false
}
}
func (s *SenderInterceptor) loop(w interceptor.RTCPWriter) {
defer s.wg.Done()
ticker := time.NewTicker(s.interval)
for {
select {
case <-s.close:
ticker.Stop()
return
case p := <-s.packetChan:
s.recorder.Record(p.ssrc, p.sequenceNumber, p.arrivalTime)
case <-ticker.C:
// build and send twcc
pkts := s.recorder.BuildFeedbackPacket()
if pkts == nil {
continue
}
if _, err := w.Write(pkts, nil); err != nil {
s.log.Error(err.Error())
}
}
}
}

274
vendor/github.com/pion/interceptor/pkg/twcc/twcc.go generated vendored Normal file
View File

@@ -0,0 +1,274 @@
// Package twcc provides interceptors to implement transport wide congestion control.
package twcc
import (
"math"
"github.com/pion/rtcp"
)
type pktInfo struct {
sequenceNumber uint32
arrivalTime int64
}
// Recorder records incoming RTP packets and their delays and creates
// transport wide congestion control feedback reports as specified in
// https://datatracker.ietf.org/doc/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
type Recorder struct {
receivedPackets []pktInfo
cycles uint32
lastSequenceNumber uint16
senderSSRC uint32
mediaSSRC uint32
fbPktCnt uint8
}
// NewRecorder creates a new Recorder which uses the given senderSSRC in the created
// feedback packets.
func NewRecorder(senderSSRC uint32) *Recorder {
return &Recorder{
receivedPackets: []pktInfo{},
senderSSRC: senderSSRC,
}
}
// Record marks a packet with mediaSSRC and a transport wide sequence number sequenceNumber as received at arrivalTime.
func (r *Recorder) Record(mediaSSRC uint32, sequenceNumber uint16, arrivalTime int64) {
r.mediaSSRC = mediaSSRC
if sequenceNumber < 0x0fff && (r.lastSequenceNumber&0xffff) > 0xf000 {
r.cycles += 1 << 16
}
r.receivedPackets = insertSorted(r.receivedPackets, pktInfo{
sequenceNumber: r.cycles | uint32(sequenceNumber),
arrivalTime: arrivalTime,
})
r.lastSequenceNumber = sequenceNumber
}
func insertSorted(list []pktInfo, element pktInfo) []pktInfo {
if len(list) == 0 {
return append(list, element)
}
for i := len(list) - 1; i >= 0; i-- {
if list[i].sequenceNumber < element.sequenceNumber {
list = append(list, pktInfo{})
copy(list[i+2:], list[i+1:])
list[i+1] = element
return list
}
if list[i].sequenceNumber == element.sequenceNumber {
list[i] = element
return list
}
}
// element.sequenceNumber is between 0 and first ever received sequenceNumber
return append([]pktInfo{element}, list...)
}
// BuildFeedbackPacket creates a new RTCP packet containing a TWCC feedback report.
func (r *Recorder) BuildFeedbackPacket() []rtcp.Packet {
feedback := newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
r.fbPktCnt++
if len(r.receivedPackets) < 2 {
r.receivedPackets = []pktInfo{}
return []rtcp.Packet{feedback.getRTCP()}
}
feedback.setBase(uint16(r.receivedPackets[0].sequenceNumber&0xffff), r.receivedPackets[0].arrivalTime)
var pkts []rtcp.Packet
for _, pkt := range r.receivedPackets {
ok := feedback.addReceived(uint16(pkt.sequenceNumber&0xffff), pkt.arrivalTime)
if !ok {
pkts = append(pkts, feedback.getRTCP())
feedback = newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
r.fbPktCnt++
feedback.addReceived(uint16(pkt.sequenceNumber&0xffff), pkt.arrivalTime)
}
}
r.receivedPackets = []pktInfo{}
pkts = append(pkts, feedback.getRTCP())
return pkts
}
type feedback struct {
rtcp *rtcp.TransportLayerCC
baseSequenceNumber uint16
refTimestamp64MS int64
lastTimestampUS int64
nextSequenceNumber uint16
sequenceNumberCount uint16
len int
lastChunk chunk
chunks []rtcp.PacketStatusChunk
deltas []*rtcp.RecvDelta
}
func newFeedback(senderSSRC, mediaSSRC uint32, count uint8) *feedback {
return &feedback{
rtcp: &rtcp.TransportLayerCC{
SenderSSRC: senderSSRC,
MediaSSRC: mediaSSRC,
FbPktCount: count,
},
}
}
func (f *feedback) setBase(sequenceNumber uint16, timeUS int64) {
f.baseSequenceNumber = sequenceNumber
f.nextSequenceNumber = f.baseSequenceNumber
f.refTimestamp64MS = timeUS / 64e3
f.lastTimestampUS = f.refTimestamp64MS * 64e3
}
func (f *feedback) getRTCP() *rtcp.TransportLayerCC {
f.rtcp.PacketStatusCount = f.sequenceNumberCount
f.rtcp.ReferenceTime = uint32(f.refTimestamp64MS)
f.rtcp.BaseSequenceNumber = f.baseSequenceNumber
for len(f.lastChunk.deltas) > 0 {
f.chunks = append(f.chunks, f.lastChunk.encode())
}
f.rtcp.PacketChunks = append(f.rtcp.PacketChunks, f.chunks...)
f.rtcp.RecvDeltas = f.deltas
padLen := 20 + len(f.rtcp.PacketChunks)*2 + f.len // 4 bytes header + 16 bytes twcc header + 2 bytes for each chunk + length of deltas
padding := padLen%4 != 0
for padLen%4 != 0 {
padLen++
}
f.rtcp.Header = rtcp.Header{
Count: rtcp.FormatTCC,
Type: rtcp.TypeTransportSpecificFeedback,
Padding: padding,
Length: uint16((padLen / 4) - 1),
}
return f.rtcp
}
func (f *feedback) addReceived(sequenceNumber uint16, timestampUS int64) bool {
deltaUS := timestampUS - f.lastTimestampUS
delta250US := deltaUS / 250
if delta250US < math.MinInt16 || delta250US > math.MaxInt16 { // delta doesn't fit into 16 bit, need to create new packet
return false
}
for ; f.nextSequenceNumber != sequenceNumber; f.nextSequenceNumber++ {
if !f.lastChunk.canAdd(rtcp.TypeTCCPacketNotReceived) {
f.chunks = append(f.chunks, f.lastChunk.encode())
}
f.lastChunk.add(rtcp.TypeTCCPacketNotReceived)
f.sequenceNumberCount++
}
var recvDelta uint16
switch {
case delta250US >= 0 && delta250US <= 0xff:
f.len++
recvDelta = rtcp.TypeTCCPacketReceivedSmallDelta
default:
f.len += 2
recvDelta = rtcp.TypeTCCPacketReceivedLargeDelta
}
if !f.lastChunk.canAdd(recvDelta) {
f.chunks = append(f.chunks, f.lastChunk.encode())
}
f.lastChunk.add(recvDelta)
f.deltas = append(f.deltas, &rtcp.RecvDelta{
Type: recvDelta,
Delta: deltaUS,
})
f.lastTimestampUS = timestampUS
f.sequenceNumberCount++
f.nextSequenceNumber++
return true
}
const (
maxRunLengthCap = 0x1fff // 13 bits
maxOneBitCap = 14 // bits
maxTwoBitCap = 7 // bits
)
type chunk struct {
hasLargeDelta bool
hasDifferentTypes bool
deltas []uint16
}
func (c *chunk) canAdd(delta uint16) bool {
if len(c.deltas) < maxTwoBitCap {
return true
}
if len(c.deltas) < maxOneBitCap && !c.hasLargeDelta && delta != rtcp.TypeTCCPacketReceivedLargeDelta {
return true
}
if len(c.deltas) < maxRunLengthCap && !c.hasDifferentTypes && delta == c.deltas[0] {
return true
}
return false
}
func (c *chunk) add(delta uint16) {
c.deltas = append(c.deltas, delta)
c.hasLargeDelta = c.hasLargeDelta || delta == rtcp.TypeTCCPacketReceivedLargeDelta
c.hasDifferentTypes = c.hasDifferentTypes || delta != c.deltas[0]
}
func (c *chunk) encode() rtcp.PacketStatusChunk {
if !c.hasDifferentTypes {
defer c.reset()
return &rtcp.RunLengthChunk{
PacketStatusSymbol: c.deltas[0],
RunLength: uint16(len(c.deltas)),
}
}
if len(c.deltas) == maxOneBitCap {
defer c.reset()
return &rtcp.StatusVectorChunk{
SymbolSize: rtcp.TypeTCCSymbolSizeOneBit,
SymbolList: c.deltas,
}
}
minCap := min(maxTwoBitCap, len(c.deltas))
svc := &rtcp.StatusVectorChunk{
SymbolSize: rtcp.TypeTCCSymbolSizeTwoBit,
SymbolList: c.deltas[:minCap],
}
c.deltas = c.deltas[minCap:]
c.hasDifferentTypes = false
c.hasLargeDelta = false
if len(c.deltas) > 0 {
tmp := c.deltas[0]
for _, d := range c.deltas {
if tmp != d {
c.hasDifferentTypes = true
}
if d == rtcp.TypeTCCPacketReceivedLargeDelta {
c.hasLargeDelta = true
}
}
}
return svc
}
func (c *chunk) reset() {
c.deltas = []uint16{}
c.hasLargeDelta = false
c.hasDifferentTypes = false
}
func min(a, b int) int {
if a < b {
return a
}
return b
}