57
vendor/github.com/pion/interceptor/pkg/twcc/header_extension_interceptor.go
generated
vendored
Normal file
57
vendor/github.com/pion/interceptor/pkg/twcc/header_extension_interceptor.go
generated
vendored
Normal 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)
|
||||
})
|
||||
}
|
||||
189
vendor/github.com/pion/interceptor/pkg/twcc/sender_interceptor.go
generated
vendored
Normal file
189
vendor/github.com/pion/interceptor/pkg/twcc/sender_interceptor.go
generated
vendored
Normal 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
274
vendor/github.com/pion/interceptor/pkg/twcc/twcc.go
generated
vendored
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user