直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理
Made-with: Cursor
This commit is contained in:
15
server/vendor/github.com/pion/interceptor/pkg/nack/errors.go
generated
vendored
Normal file
15
server/vendor/github.com/pion/interceptor/pkg/nack/errors.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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")
|
||||
errFailedToCastHeaderPool = errors.New("could not access header pool, failed cast")
|
||||
errFailedToCastPayloadPool = errors.New("could not access payload pool, failed cast")
|
||||
)
|
||||
216
server/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go
generated
vendored
Normal file
216
server/vendor/github.com/pion/interceptor/pkg/nack/generator_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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(_ string) (interceptor.Interceptor, error) {
|
||||
i := &GeneratorInterceptor{
|
||||
size: 512,
|
||||
skipLastN: 0,
|
||||
maxNacksPerPacket: 0,
|
||||
interval: time.Millisecond * 100,
|
||||
receiveLogs: map[uint32]*receiveLog{},
|
||||
nackCountLogs: map[uint32]map[uint16]uint16{},
|
||||
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
|
||||
maxNacksPerPacket uint16
|
||||
interval time.Duration
|
||||
m sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
log logging.LeveledLogger
|
||||
nackCountLogs map[uint32]map[uint16]uint16
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (n *GeneratorInterceptor) UnbindRemoteStream(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
|
||||
}
|
||||
|
||||
// nolint:gocognit
|
||||
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 || n.nackCountLogs[ssrc] == nil {
|
||||
n.nackCountLogs[ssrc] = map[uint16]uint16{}
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredMissing := []uint16{}
|
||||
if n.maxNacksPerPacket > 0 {
|
||||
for _, missingSeq := range missing {
|
||||
if n.nackCountLogs[ssrc][missingSeq] < n.maxNacksPerPacket {
|
||||
filteredMissing = append(filteredMissing, missingSeq)
|
||||
}
|
||||
n.nackCountLogs[ssrc][missingSeq]++
|
||||
}
|
||||
} else {
|
||||
filteredMissing = missing
|
||||
}
|
||||
|
||||
nack := &rtcp.TransportLayerNack{
|
||||
SenderSSRC: senderSSRC,
|
||||
MediaSSRC: ssrc,
|
||||
Nacks: rtcp.NackPairsFromSequenceNumbers(filteredMissing),
|
||||
}
|
||||
|
||||
for nackSeq := range n.nackCountLogs[ssrc] {
|
||||
isMissing := false
|
||||
for _, missingSeq := range missing {
|
||||
if missingSeq == nackSeq {
|
||||
isMissing = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isMissing {
|
||||
delete(n.nackCountLogs[ssrc], nackSeq)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredMissing) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
56
server/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go
generated
vendored
Normal file
56
server/vendor/github.com/pion/interceptor/pkg/nack/generator_option.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// GeneratorMaxNacksPerPacket sets the maximum number of NACKs sent per missing packet, e.g. if set to 2, a missing
|
||||
// packet will only be NACKed at most twice. If set to 0 (default), max number of NACKs is unlimited
|
||||
func GeneratorMaxNacksPerPacket(maxNacks uint16) GeneratorOption {
|
||||
return func(r *GeneratorInterceptor) error {
|
||||
r.maxNacksPerPacket = maxNacks
|
||||
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
|
||||
}
|
||||
}
|
||||
17
server/vendor/github.com/pion/interceptor/pkg/nack/nack.go
generated
vendored
Normal file
17
server/vendor/github.com/pion/interceptor/pkg/nack/nack.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// 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
|
||||
}
|
||||
138
server/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go
generated
vendored
Normal file
138
server/vendor/github.com/pion/interceptor/pkg/nack/receive_log.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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++ { //nolint:revive
|
||||
// find all consecutive packets
|
||||
}
|
||||
|
||||
s.lastConsecutive = i - 1
|
||||
}
|
||||
149
server/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go
generated
vendored
Normal file
149
server/vendor/github.com/pion/interceptor/pkg/nack/responder_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type packetFactory interface {
|
||||
NewPacket(header *rtp.Header, payload []byte) (*retainablePacket, error)
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new ResponderInterceptor
|
||||
func (r *ResponderInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &ResponderInterceptor{
|
||||
size: 1024,
|
||||
log: logging.NewDefaultLoggerFactory().NewLogger("nack_responder"),
|
||||
streams: map[uint32]*localStream{},
|
||||
}
|
||||
|
||||
for _, opt := range r.opts {
|
||||
if err := opt(i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if i.packetFactory == nil {
|
||||
i.packetFactory = newPacketManager()
|
||||
}
|
||||
|
||||
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
|
||||
packetFactory packetFactory
|
||||
|
||||
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.packetFactory.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
|
||||
})
|
||||
}
|
||||
}
|
||||
35
server/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go
generated
vendored
Normal file
35
server/vendor/github.com/pion/interceptor/pkg/nack/responder_option.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// DisableCopy bypasses copy of underlying packets. It should be used when
|
||||
// you are not re-using underlying buffers of packets that have been written
|
||||
func DisableCopy() ResponderOption {
|
||||
return func(s *ResponderInterceptor) error {
|
||||
s.packetFactory = &noOpPacketFactory{}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
132
server/vendor/github.com/pion/interceptor/pkg/nack/retainable_packet.go
generated
vendored
Normal file
132
server/vendor/github.com/pion/interceptor/pkg/nack/retainable_packet.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
var ok bool
|
||||
p.header, ok = m.headerPool.Get().(*rtp.Header)
|
||||
if !ok {
|
||||
return nil, errFailedToCastHeaderPool
|
||||
}
|
||||
|
||||
*p.header = header.Clone()
|
||||
|
||||
if payload != nil {
|
||||
p.buffer, ok = m.payloadPool.Get().(*[]byte)
|
||||
if !ok {
|
||||
return nil, errFailedToCastPayloadPool
|
||||
}
|
||||
|
||||
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 noOpPacketFactory struct{}
|
||||
|
||||
func (f *noOpPacketFactory) NewPacket(header *rtp.Header, payload []byte) (*retainablePacket, error) {
|
||||
return &retainablePacket{
|
||||
onRelease: f.releasePacket,
|
||||
count: 1,
|
||||
header: header,
|
||||
payload: payload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *noOpPacketFactory) releasePacket(_ *rtp.Header, _ *[]byte) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
104
server/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go
generated
vendored
Normal file
104
server/vendor/github.com/pion/interceptor/pkg/nack/send_buffer.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
}
|
||||
184
server/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go
generated
vendored
Normal file
184
server/vendor/github.com/pion/interceptor/pkg/report/receiver_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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(_ 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(_, value interface{}) bool {
|
||||
if stream, ok := value.(*receiverStream); !ok {
|
||||
r.log.Warnf("failed to cast ReceiverInterceptor stream")
|
||||
} else if _, err := rtcpWriter.Write([]rtcp.Packet{stream.generateReport(now)}, 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
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindRemoteStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (r *ReceiverInterceptor) UnbindRemoteStream(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
|
||||
}
|
||||
|
||||
if stream, ok := value.(*receiverStream); !ok {
|
||||
r.log.Warnf("failed to cast ReceiverInterceptor stream")
|
||||
} else {
|
||||
stream.processSenderReport(r.now(), sr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i, attr, nil
|
||||
})
|
||||
}
|
||||
37
server/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go
generated
vendored
Normal file
37
server/vendor/github.com/pion/interceptor/pkg/report/receiver_option.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
169
server/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go
generated
vendored
Normal file
169
server/vendor/github.com/pion/interceptor/pkg/report/receiver_stream.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
const (
|
||||
// packetsPerHistoryEntry represents how many packets are in the bitmask for
|
||||
// each entry in the `packets` slice in the receiver stream. Because we use
|
||||
// a uint64, we can keep track of 64 packets per entry.
|
||||
packetsPerHistoryEntry = 64
|
||||
)
|
||||
|
||||
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 := pktHeader.SequenceNumber - stream.lastSeqnum
|
||||
if diff > 0 && diff < (1<<15) {
|
||||
// wrap around
|
||||
if pktHeader.SequenceNumber < stream.lastSeqnum {
|
||||
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 * packetsPerHistoryEntry)
|
||||
stream.packets[pos/packetsPerHistoryEntry] |= 1 << (pos % packetsPerHistoryEntry)
|
||||
}
|
||||
|
||||
func (stream *receiverStream) delReceived(seq uint16) {
|
||||
pos := seq % (stream.size * packetsPerHistoryEntry)
|
||||
stream.packets[pos/packetsPerHistoryEntry] &^= 1 << (pos % packetsPerHistoryEntry)
|
||||
}
|
||||
|
||||
func (stream *receiverStream) getReceived(seq uint16) bool {
|
||||
pos := seq % (stream.size * packetsPerHistoryEntry)
|
||||
return (stream.packets[pos/packetsPerHistoryEntry] & (1 << (pos % packetsPerHistoryEntry))) != 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
|
||||
}
|
||||
5
server/vendor/github.com/pion/interceptor/pkg/report/report.go
generated
vendored
Normal file
5
server/vendor/github.com/pion/interceptor/pkg/report/report.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package report provides interceptors to implement sending sender and receiver reports.
|
||||
package report
|
||||
151
server/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go
generated
vendored
Normal file
151
server/vendor/github.com/pion/interceptor/pkg/report/sender_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/logging"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// TickerFactory is a factory to create new tickers
|
||||
type TickerFactory func(d time.Duration) Ticker
|
||||
|
||||
// SenderInterceptorFactory is a interceptor.Factory for a SenderInterceptor
|
||||
type SenderInterceptorFactory struct {
|
||||
opts []SenderOption
|
||||
}
|
||||
|
||||
// NewInterceptor constructs a new SenderInterceptor
|
||||
func (s *SenderInterceptorFactory) NewInterceptor(_ string) (interceptor.Interceptor, error) {
|
||||
i := &SenderInterceptor{
|
||||
interval: 1 * time.Second,
|
||||
now: time.Now,
|
||||
newTicker: func(d time.Duration) Ticker {
|
||||
return &timeTicker{time.NewTicker(d)}
|
||||
},
|
||||
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
|
||||
newTicker TickerFactory
|
||||
streams sync.Map
|
||||
log logging.LeveledLogger
|
||||
m sync.Mutex
|
||||
wg sync.WaitGroup
|
||||
close chan struct{}
|
||||
started chan struct{}
|
||||
|
||||
useLatestPacket bool
|
||||
}
|
||||
|
||||
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 := s.newTicker(s.interval)
|
||||
defer ticker.Stop()
|
||||
if s.started != nil {
|
||||
// This lets us synchronize in tests to know whether the loop has begun or not.
|
||||
// It only happens if started was initialized, which should not occur in non-tests.
|
||||
close(s.started)
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ticker.Ch():
|
||||
now := s.now()
|
||||
s.streams.Range(func(_, value interface{}) bool {
|
||||
if stream, ok := value.(*senderStream); !ok {
|
||||
s.log.Warnf("failed to cast SenderInterceptor stream")
|
||||
} else if _, err := rtcpWriter.Write([]rtcp.Packet{stream.generateReport(now)}, 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.SSRC, info.ClockRate, s.useLatestPacket)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindLocalStream is called when the Stream is removed. It can be used to clean up any data related to that track.
|
||||
func (s *SenderInterceptor) UnbindLocalStream(info *interceptor.StreamInfo) {
|
||||
s.streams.Delete(info.SSRC)
|
||||
}
|
||||
63
server/vendor/github.com/pion/interceptor/pkg/report/sender_option.go
generated
vendored
Normal file
63
server/vendor/github.com/pion/interceptor/pkg/report/sender_option.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// SenderTicker sets an alternative for the time.NewTicker function.
|
||||
func SenderTicker(f TickerFactory) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.newTicker = f
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SenderUseLatestPacket sets the interceptor to always use the latest packet, even
|
||||
// if it appears to be out-of-order.
|
||||
func SenderUseLatestPacket() SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.useLatestPacket = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// enableStartTracking is used by tests to synchronize whether the loop() has begun
|
||||
// and it's safe to start sending ticks to the ticker.
|
||||
func enableStartTracking(startedCh chan struct{}) SenderOption {
|
||||
return func(r *SenderInterceptor) error {
|
||||
r.started = startedCh
|
||||
return nil
|
||||
}
|
||||
}
|
||||
65
server/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go
generated
vendored
Normal file
65
server/vendor/github.com/pion/interceptor/pkg/report/sender_stream.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pion/interceptor/internal/ntp"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
type senderStream struct {
|
||||
ssrc uint32
|
||||
clockRate float64
|
||||
m sync.Mutex
|
||||
|
||||
useLatestPacket bool
|
||||
|
||||
// data from rtp packets
|
||||
lastRTPTimeRTP uint32
|
||||
lastRTPTimeTime time.Time
|
||||
lastRTPSN uint16
|
||||
packetCount uint32
|
||||
octetCount uint32
|
||||
}
|
||||
|
||||
func newSenderStream(ssrc uint32, clockRate uint32, useLatestPacket bool) *senderStream {
|
||||
return &senderStream{
|
||||
ssrc: ssrc,
|
||||
clockRate: float64(clockRate),
|
||||
useLatestPacket: useLatestPacket,
|
||||
}
|
||||
}
|
||||
|
||||
func (stream *senderStream) processRTP(now time.Time, header *rtp.Header, payload []byte) {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
diff := header.SequenceNumber - stream.lastRTPSN
|
||||
if stream.useLatestPacket || stream.packetCount == 0 || (diff > 0 && diff < (1<<15)) {
|
||||
// Told to consider every packet, or this was the first packet, or it's in-order
|
||||
stream.lastRTPSN = header.SequenceNumber
|
||||
stream.lastRTPTimeRTP = header.Timestamp
|
||||
stream.lastRTPTimeTime = now
|
||||
}
|
||||
|
||||
stream.packetCount++
|
||||
stream.octetCount += uint32(len(payload))
|
||||
}
|
||||
|
||||
func (stream *senderStream) generateReport(now time.Time) *rtcp.SenderReport {
|
||||
stream.m.Lock()
|
||||
defer stream.m.Unlock()
|
||||
|
||||
return &rtcp.SenderReport{
|
||||
SSRC: stream.ssrc,
|
||||
NTPTime: ntp.ToNTP(now),
|
||||
RTPTime: stream.lastRTPTimeRTP + uint32(now.Sub(stream.lastRTPTimeTime).Seconds()*stream.clockRate),
|
||||
PacketCount: stream.packetCount,
|
||||
OctetCount: stream.octetCount,
|
||||
}
|
||||
}
|
||||
20
server/vendor/github.com/pion/interceptor/pkg/report/ticker.go
generated
vendored
Normal file
20
server/vendor/github.com/pion/interceptor/pkg/report/ticker.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package report
|
||||
|
||||
import "time"
|
||||
|
||||
// Ticker is an interface for *time.Ticker for use with the SenderTicker option.
|
||||
type Ticker interface {
|
||||
Ch() <-chan time.Time
|
||||
Stop()
|
||||
}
|
||||
|
||||
type timeTicker struct {
|
||||
*time.Ticker
|
||||
}
|
||||
|
||||
func (t *timeTicker) Ch() <-chan time.Time {
|
||||
return t.C
|
||||
}
|
||||
192
server/vendor/github.com/pion/interceptor/pkg/twcc/arrival_time_map.go
generated
vendored
Normal file
192
server/vendor/github.com/pion/interceptor/pkg/twcc/arrival_time_map.go
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package twcc
|
||||
|
||||
const (
|
||||
minCapacity = 128
|
||||
maxNumberOfPackets = 1 << 15
|
||||
)
|
||||
|
||||
// packetArrivalTimeMap is adapted from Chrome's implementation of TWCC, and keeps track
|
||||
// of the arrival times of packets. It is used by the TWCC interceptor to build feedback
|
||||
// packets.
|
||||
// See https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/webrtc/modules/remote_bitrate_estimator/packet_arrival_map.h;drc=b5cd13bb6d5d157a5fbe3628b2dd1c1e106203c6
|
||||
type packetArrivalTimeMap struct {
|
||||
// arrivalTimes is a circular buffer, where the packet with sequence number sn is stored
|
||||
// in slot sn % len(arrivalTimes)
|
||||
arrivalTimes []int64
|
||||
|
||||
// The unwrapped sequence numbers for the range of valid sequence numbers in arrivalTimes.
|
||||
// beginSequenceNumber is inclusive, and endSequenceNumber is exclusive.
|
||||
beginSequenceNumber, endSequenceNumber int64
|
||||
}
|
||||
|
||||
// AddPacket records the fact that the packet with sequence number sequenceNumber arrived
|
||||
// at arrivalTime.
|
||||
func (m *packetArrivalTimeMap) AddPacket(sequenceNumber int64, arrivalTime int64) {
|
||||
if m.arrivalTimes == nil {
|
||||
// First packet
|
||||
m.reallocate(minCapacity)
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
m.endSequenceNumber = sequenceNumber + 1
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
return
|
||||
}
|
||||
|
||||
if sequenceNumber >= m.beginSequenceNumber && sequenceNumber < m.endSequenceNumber {
|
||||
// The packet is within the buffer, no need to resize.
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
return
|
||||
}
|
||||
|
||||
if sequenceNumber < m.beginSequenceNumber {
|
||||
// The packet goes before the current buffer. Expand to add packet,
|
||||
// but only if it fits within the maximum number of packets.
|
||||
newSize := int(m.endSequenceNumber - sequenceNumber)
|
||||
if newSize > maxNumberOfPackets {
|
||||
// Don't expand the buffer back for this packet, as it would remove newer received
|
||||
// packets.
|
||||
return
|
||||
}
|
||||
m.adjustToSize(newSize)
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
m.setNotReceived(sequenceNumber+1, m.beginSequenceNumber)
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
return
|
||||
}
|
||||
|
||||
// The packet goes after the buffer.
|
||||
newEndSequenceNumber := sequenceNumber + 1
|
||||
|
||||
if newEndSequenceNumber >= m.endSequenceNumber+maxNumberOfPackets {
|
||||
// All old packets have to be removed.
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
m.endSequenceNumber = newEndSequenceNumber
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
return
|
||||
}
|
||||
|
||||
if m.beginSequenceNumber < newEndSequenceNumber-maxNumberOfPackets {
|
||||
// Remove oldest entries.
|
||||
m.beginSequenceNumber = newEndSequenceNumber - maxNumberOfPackets
|
||||
}
|
||||
|
||||
m.adjustToSize(int(newEndSequenceNumber - m.beginSequenceNumber))
|
||||
|
||||
// Packets can be received out of order. If this isn't the next expected packet,
|
||||
// add enough placeholders to fill the gap.
|
||||
m.setNotReceived(m.endSequenceNumber, sequenceNumber)
|
||||
m.endSequenceNumber = newEndSequenceNumber
|
||||
m.arrivalTimes[m.index(sequenceNumber)] = arrivalTime
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) setNotReceived(startInclusive, endExclusive int64) {
|
||||
for sn := startInclusive; sn < endExclusive; sn++ {
|
||||
m.arrivalTimes[m.index(sn)] = -1
|
||||
}
|
||||
}
|
||||
|
||||
// BeginSequenceNumber returns the first valid sequence number in the map.
|
||||
func (m *packetArrivalTimeMap) BeginSequenceNumber() int64 {
|
||||
return m.beginSequenceNumber
|
||||
}
|
||||
|
||||
// EndSequenceNumber returns the first sequence number after the last valid sequence number in the map.
|
||||
func (m *packetArrivalTimeMap) EndSequenceNumber() int64 {
|
||||
return m.endSequenceNumber
|
||||
}
|
||||
|
||||
// FindNextAtOrAfter returns the sequence number and timestamp of the first received packet that has a sequence number
|
||||
// greator or equal to sequenceNumber.
|
||||
func (m *packetArrivalTimeMap) FindNextAtOrAfter(sequenceNumber int64) (foundSequenceNumber int64, arrivalTime int64, ok bool) {
|
||||
for sequenceNumber = m.Clamp(sequenceNumber); sequenceNumber < m.endSequenceNumber; sequenceNumber++ {
|
||||
if t := m.get(sequenceNumber); t >= 0 {
|
||||
return sequenceNumber, t, true
|
||||
}
|
||||
}
|
||||
return -1, -1, false
|
||||
}
|
||||
|
||||
// EraseTo erases all elements from the beginning of the map until sequenceNumber.
|
||||
func (m *packetArrivalTimeMap) EraseTo(sequenceNumber int64) {
|
||||
if sequenceNumber < m.beginSequenceNumber {
|
||||
return
|
||||
}
|
||||
if sequenceNumber >= m.endSequenceNumber {
|
||||
// Erase all.
|
||||
m.beginSequenceNumber = m.endSequenceNumber
|
||||
return
|
||||
}
|
||||
// Remove some
|
||||
m.beginSequenceNumber = sequenceNumber
|
||||
m.adjustToSize(int(m.endSequenceNumber - m.beginSequenceNumber))
|
||||
}
|
||||
|
||||
// RemoveOldPackets removes packets from the beginning of the map as long as they are before
|
||||
// sequenceNumber and with an age older than arrivalTimeLimit.
|
||||
func (m *packetArrivalTimeMap) RemoveOldPackets(sequenceNumber int64, arrivalTimeLimit int64) {
|
||||
checkTo := min64(sequenceNumber, m.endSequenceNumber)
|
||||
for m.beginSequenceNumber < checkTo && m.get(m.beginSequenceNumber) <= arrivalTimeLimit {
|
||||
m.beginSequenceNumber++
|
||||
}
|
||||
m.adjustToSize(int(m.endSequenceNumber - m.beginSequenceNumber))
|
||||
}
|
||||
|
||||
// HasReceived returns whether a packet with the sequence number has been received.
|
||||
func (m *packetArrivalTimeMap) HasReceived(sequenceNumber int64) bool {
|
||||
return m.get(sequenceNumber) >= 0
|
||||
}
|
||||
|
||||
// Clamp returns sequenceNumber clamped to [beginSequenceNumber, endSequenceNumber]
|
||||
func (m *packetArrivalTimeMap) Clamp(sequenceNumber int64) int64 {
|
||||
if sequenceNumber < m.beginSequenceNumber {
|
||||
return m.beginSequenceNumber
|
||||
}
|
||||
if m.endSequenceNumber < sequenceNumber {
|
||||
return m.endSequenceNumber
|
||||
}
|
||||
return sequenceNumber
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) get(sequenceNumber int64) int64 {
|
||||
if sequenceNumber < m.beginSequenceNumber || sequenceNumber >= m.endSequenceNumber {
|
||||
return -1
|
||||
}
|
||||
return m.arrivalTimes[m.index(sequenceNumber)]
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) index(sequenceNumber int64) int {
|
||||
// Sequence number might be negative, and we always guarantee that arrivalTimes
|
||||
// length is a power of 2, so it's easier to use "&" instead of "%"
|
||||
return int(sequenceNumber & int64(m.capacity()-1))
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) adjustToSize(newSize int) {
|
||||
if newSize > m.capacity() {
|
||||
newCapacity := m.capacity()
|
||||
for newCapacity < newSize {
|
||||
newCapacity *= 2
|
||||
}
|
||||
m.reallocate(newCapacity)
|
||||
}
|
||||
if m.capacity() > max(minCapacity, newSize*4) {
|
||||
newCapacity := m.capacity()
|
||||
for newCapacity >= 2*max(newSize, minCapacity) {
|
||||
newCapacity /= 2
|
||||
}
|
||||
m.reallocate(newCapacity)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) capacity() int {
|
||||
return len(m.arrivalTimes)
|
||||
}
|
||||
|
||||
func (m *packetArrivalTimeMap) reallocate(newCapacity int) {
|
||||
newBuffer := make([]int64, newCapacity)
|
||||
for sn := m.beginSequenceNumber; sn < m.endSequenceNumber; sn++ {
|
||||
newBuffer[int(sn&(int64(newCapacity-1)))] = m.get(sn)
|
||||
}
|
||||
m.arrivalTimes = newBuffer
|
||||
}
|
||||
66
server/vendor/github.com/pion/interceptor/pkg/twcc/header_extension_interceptor.go
generated
vendored
Normal file
66
server/vendor/github.com/pion/interceptor/pkg/twcc/header_extension_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package twcc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
var errHeaderIsNil = errors.New("header is nil")
|
||||
|
||||
// HeaderExtensionInterceptorFactory is a interceptor.Factory for a HeaderExtensionInterceptor
|
||||
type HeaderExtensionInterceptorFactory struct{}
|
||||
|
||||
// NewInterceptor constructs a new HeaderExtensionInterceptor
|
||||
func (h *HeaderExtensionInterceptorFactory) NewInterceptor(_ 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
|
||||
}
|
||||
if header == nil {
|
||||
return 0, errHeaderIsNil
|
||||
}
|
||||
err = header.SetExtension(hdrExtID, tcc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return writer.Write(header, payload, attributes)
|
||||
})
|
||||
}
|
||||
207
server/vendor/github.com/pion/interceptor/pkg/twcc/sender_interceptor.go
generated
vendored
Normal file
207
server/vendor/github.com/pion/interceptor/pkg/twcc/sender_interceptor.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package twcc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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
|
||||
}
|
||||
|
||||
var errClosed = errors.New("interceptor is closed")
|
||||
|
||||
// NewInterceptor constructs a new SenderInterceptor
|
||||
func (s *SenderInterceptorFactory) NewInterceptor(_ 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
|
||||
}
|
||||
|
||||
p := packet{
|
||||
hdr: header,
|
||||
sequenceNumber: tccExt.TransportSequence,
|
||||
arrivalTime: time.Since(s.startTime).Microseconds(),
|
||||
ssrc: info.SSRC,
|
||||
}
|
||||
select {
|
||||
case <-s.close:
|
||||
return 0, nil, errClosed
|
||||
case s.packetChan <- p:
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
select {
|
||||
case <-s.close:
|
||||
return
|
||||
case p := <-s.packetChan:
|
||||
s.recorder.Record(p.ssrc, p.sequenceNumber, p.arrivalTime)
|
||||
}
|
||||
|
||||
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 len(pkts) == 0 {
|
||||
continue
|
||||
}
|
||||
if _, err := w.Write(pkts, nil); err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
366
server/vendor/github.com/pion/interceptor/pkg/twcc/twcc.go
generated
vendored
Normal file
366
server/vendor/github.com/pion/interceptor/pkg/twcc/twcc.go
generated
vendored
Normal file
@@ -0,0 +1,366 @@
|
||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package twcc provides interceptors to implement transport wide congestion control.
|
||||
package twcc
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/pion/interceptor/internal/sequencenumber"
|
||||
"github.com/pion/rtcp"
|
||||
)
|
||||
|
||||
const (
|
||||
packetWindowMicroseconds = 500_000
|
||||
maxMissingSequenceNumbers = 0x7FFE
|
||||
)
|
||||
|
||||
// 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 {
|
||||
arrivalTimeMap packetArrivalTimeMap
|
||||
|
||||
sequenceUnwrapper sequencenumber.Unwrapper
|
||||
|
||||
// startSequenceNumber is the first sequence number that will be included in the the
|
||||
// next feedback packet.
|
||||
startSequenceNumber *int64
|
||||
|
||||
senderSSRC uint32
|
||||
mediaSSRC uint32
|
||||
fbPktCnt uint8
|
||||
|
||||
packetsHeld int
|
||||
}
|
||||
|
||||
// NewRecorder creates a new Recorder which uses the given senderSSRC in the created
|
||||
// feedback packets.
|
||||
func NewRecorder(senderSSRC uint32) *Recorder {
|
||||
return &Recorder{
|
||||
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
|
||||
|
||||
// "Unwrap" the sequence number to get a monotonically increasing sequence number that
|
||||
// won't wrap around after math.MaxUint16.
|
||||
unwrappedSN := r.sequenceUnwrapper.Unwrap(sequenceNumber)
|
||||
r.maybeCullOldPackets(unwrappedSN, arrivalTime)
|
||||
if r.startSequenceNumber == nil || unwrappedSN < *r.startSequenceNumber {
|
||||
r.startSequenceNumber = &unwrappedSN
|
||||
}
|
||||
|
||||
// We are only interested in the first time a packet is received.
|
||||
if r.arrivalTimeMap.HasReceived(unwrappedSN) {
|
||||
return
|
||||
}
|
||||
|
||||
r.arrivalTimeMap.AddPacket(unwrappedSN, arrivalTime)
|
||||
r.packetsHeld++
|
||||
|
||||
// Limit the range of sequence numbers to send feedback for.
|
||||
if *r.startSequenceNumber < r.arrivalTimeMap.BeginSequenceNumber() {
|
||||
sn := r.arrivalTimeMap.BeginSequenceNumber()
|
||||
r.startSequenceNumber = &sn
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Recorder) maybeCullOldPackets(sequenceNumber int64, arrivalTime int64) {
|
||||
if r.startSequenceNumber != nil && *r.startSequenceNumber >= r.arrivalTimeMap.EndSequenceNumber() && arrivalTime >= packetWindowMicroseconds {
|
||||
r.arrivalTimeMap.RemoveOldPackets(sequenceNumber, arrivalTime-packetWindowMicroseconds)
|
||||
}
|
||||
}
|
||||
|
||||
// PacketsHeld returns the number of received packets currently held by the recorder
|
||||
func (r *Recorder) PacketsHeld() int {
|
||||
return r.packetsHeld
|
||||
}
|
||||
|
||||
// BuildFeedbackPacket creates a new RTCP packet containing a TWCC feedback report.
|
||||
func (r *Recorder) BuildFeedbackPacket() []rtcp.Packet {
|
||||
if r.startSequenceNumber == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
endSN := r.arrivalTimeMap.EndSequenceNumber()
|
||||
var feedbacks []rtcp.Packet
|
||||
for *r.startSequenceNumber < endSN {
|
||||
feedback := r.maybeBuildFeedbackPacket(*r.startSequenceNumber, endSN)
|
||||
if feedback == nil {
|
||||
break
|
||||
}
|
||||
feedbacks = append(feedbacks, feedback.getRTCP())
|
||||
|
||||
// NOTE: we don't erase packets from the history in case they need to be resent
|
||||
// after a reordering. They will be removed instead in Record when they get too
|
||||
// old.
|
||||
}
|
||||
r.packetsHeld = 0
|
||||
return feedbacks
|
||||
}
|
||||
|
||||
// maybeBuildFeedbackPacket builds a feedback packet starting from startSN (inclusive) until
|
||||
// endSN (exclusive).
|
||||
func (r *Recorder) maybeBuildFeedbackPacket(beginSeqNumInclusive, endSeqNumExclusive int64) *feedback {
|
||||
// NOTE: The logic of this method is inspired by the implementation in Chrome.
|
||||
// See https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/webrtc/modules/remote_bitrate_estimator/remote_estimator_proxy.cc;l=276;drc=b5cd13bb6d5d157a5fbe3628b2dd1c1e106203c6
|
||||
startSNInclusive, endSNExclusive := r.arrivalTimeMap.Clamp(beginSeqNumInclusive), r.arrivalTimeMap.Clamp(endSeqNumExclusive)
|
||||
|
||||
// Create feedback on demand, as we don't yet know if there are packets in the range that have been
|
||||
// received.
|
||||
var fb *feedback
|
||||
|
||||
nextSequenceNumber := beginSeqNumInclusive
|
||||
|
||||
for seq := startSNInclusive; seq < endSNExclusive; seq++ {
|
||||
foundSeq, arrivalTime, ok := r.arrivalTimeMap.FindNextAtOrAfter(seq)
|
||||
seq = foundSeq
|
||||
if !ok || seq >= endSNExclusive {
|
||||
break
|
||||
}
|
||||
|
||||
if fb == nil {
|
||||
fb = newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
|
||||
r.fbPktCnt++
|
||||
|
||||
// It should be possible to add seq to this new packet.
|
||||
// If the difference between seq and beginSeqNumInclusive is too large, discard
|
||||
// reporting too old missing packets.
|
||||
baseSequenceNumber := max64(beginSeqNumInclusive, seq-maxMissingSequenceNumbers)
|
||||
|
||||
// baseSequenceNumber is the expected first sequence number. This is known,
|
||||
// but we may not have actually received it, so the base time should be the time
|
||||
// of the first received packet in the feedback.
|
||||
fb.setBase(uint16(baseSequenceNumber), arrivalTime)
|
||||
|
||||
if !fb.addReceived(uint16(seq), arrivalTime) {
|
||||
// Could not add a single received packet to the feedback.
|
||||
// This is unexpected to actually occur, but if it does, we'll
|
||||
// try again after skipping any missing packets.
|
||||
// NOTE: It's fine that we already incremented fbPktCnt, as in essence
|
||||
// we did actually "skip" a feedback (and this matches Chrome's behavior).
|
||||
r.startSequenceNumber = &seq
|
||||
return nil
|
||||
}
|
||||
} else if !fb.addReceived(uint16(seq), arrivalTime) {
|
||||
// Could not add timestamp. Packet may be full. Return
|
||||
// and try again with a fresh packet.
|
||||
break
|
||||
}
|
||||
|
||||
nextSequenceNumber = seq + 1
|
||||
}
|
||||
|
||||
r.startSequenceNumber = &nextSequenceNumber
|
||||
return fb
|
||||
}
|
||||
|
||||
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
|
||||
var delta250US int64
|
||||
if deltaUS >= 0 {
|
||||
delta250US = (deltaUS + rtcp.TypeTCCDeltaScaleFactor/2) / rtcp.TypeTCCDeltaScaleFactor
|
||||
} else {
|
||||
delta250US = (deltaUS - rtcp.TypeTCCDeltaScaleFactor/2) / rtcp.TypeTCCDeltaScaleFactor
|
||||
}
|
||||
if delta250US < math.MinInt16 || delta250US > math.MaxInt16 { // delta doesn't fit into 16 bit, need to create new packet
|
||||
return false
|
||||
}
|
||||
deltaUSRounded := delta250US * rtcp.TypeTCCDeltaScaleFactor
|
||||
|
||||
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: deltaUSRounded,
|
||||
})
|
||||
f.lastTimestampUS += deltaUSRounded
|
||||
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 max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max64(a, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min64(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user