status-go/vendor/github.com/pion/interceptor/pkg/twcc/twcc.go

277 lines
7.1 KiB
Go

// 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/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 {
if len(r.receivedPackets) < 2 {
return nil
}
feedback := newFeedback(r.senderSSRC, r.mediaSSRC, r.fbPktCnt)
r.fbPktCnt++
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
}