直播:后台 JWT 推流、前台画中画;WebRTC 服务与 Nginx WebSocket 代理

Made-with: Cursor
This commit is contained in:
whm
2026-03-25 15:00:14 +08:00
parent b83ec91b1a
commit 7811adca66
1050 changed files with 146524 additions and 37 deletions

View 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
}

View 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)
})
}

View 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())
}
}
}
}

View 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
}