Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const (

// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5
FormatTCC uint8 = 15
// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.1
FormatTMMBR = 3
// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.2
FormatTMMBN = 4
)

func (p PacketType) String() string {
Expand Down
4 changes: 4 additions & 0 deletions packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) {
packet = new(TransportLayerCC)
case FormatCCFB:
packet = new(CCFeedbackReport)
case FormatTMMBR:
packet = new(TMMBR)
case FormatTMMBN:
packet = new(TMMBN)
default:
packet = new(RawPacket)
}
Expand Down
18 changes: 18 additions & 0 deletions packet_stringifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,24 @@ func TestPrint(t *testing.T) {
"\t\t\tType: 1\n" +
"\t\t\tDelta: 37000\n",
},
{
&TMMBR{
SenderSSRC: 0x902f9e2e,
Entries: []TMMBREntry{
{0x902f9e2e, 9812743},
{0xdeadbeef, 8435793},
},
},
"rtcp.TMMBR:\n" +
"\tSenderSSRC: 2419039790\n" +
"\tEntries:\n" +
"\t\t0:\n" +
"\t\t\tMediaSSRC: 2419039790\n" +
"\t\t\tBitrate: 9.812743e+06\n" +
"\t\t1:\n" +
"\t\t\tMediaSSRC: 3735928559\n" +
"\t\t\tBitrate: 8.435793e+06\n",
},
{
&TransportLayerNack{
SenderSSRC: 0x902f9e2e,
Expand Down
70 changes: 6 additions & 64 deletions receiver_estimated_maximum_bitrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"bytes"
"encoding/binary"
"fmt"
"math"
)

// ReceiverEstimatedMaximumBitrate contains the receiver's estimated maximum bitrate.
Expand Down Expand Up @@ -49,7 +48,6 @@ func (p ReceiverEstimatedMaximumBitrate) MarshalSize() int {

// MarshalTo serializes the packet to the given byte slice.
func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error) {
const bitratemax = 0x3FFFFp+63
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
Expand Down Expand Up @@ -93,35 +91,11 @@ func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error
// Write the length of the ssrcs to follow at the end
buf[16] = byte(len(p.SSRCs))

exp := 0
bitrate := p.Bitrate

if bitrate >= bitratemax {
bitrate = bitratemax
}

if bitrate < 0 {
return 0, errInvalidBitrate
}

for bitrate >= (1 << 18) {
bitrate /= 2.0
exp++
}

if exp >= (1 << 6) {
return 0, errInvalidBitrate
err = putBitrate(p.Bitrate, buf[17:20])
if err != nil {
return 0, err
}

mantissa := uint(math.Floor(float64(bitrate)))

// We can't quite use the binary package because
// a) it's a uint24 and b) the exponent is only 6-bits
// Just trust me; this is big-endian encoding.
buf[17] = byte(exp<<2) | byte(mantissa>>16)
buf[18] = byte(mantissa >> 8)
buf[19] = byte(mantissa)

// Write the SSRCs at the very end.
n = 20
for _, ssrc := range p.SSRCs {
Expand All @@ -136,7 +110,6 @@ func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error
//
//nolint:cyclop
func (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err error) {
const mantissamax = 0x7FFFFF
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
Expand Down Expand Up @@ -220,24 +193,7 @@ func (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err error) {
return errSSRCNumAndLengthMismatch
}

// Get the 6-bit exponent value.
exp := buf[17] >> 2
exp += 127 // bias for IEEE754
exp += 23 // IEEE754 biases the decimal to the left, abs-send-time biases it to the right

// The remaining 2-bits plus the next 16-bits are the mantissa.
mantissa := uint32(buf[17]&3)<<16 | uint32(buf[18])<<8 | uint32(buf[19])

if mantissa != 0 {
// ieee754 requires an implicit leading bit
for (mantissa & (mantissamax + 1)) == 0 {
exp--
mantissa *= 2
}
}

// bitrate = mantissa * 2^exp
p.Bitrate = math.Float32frombits((uint32(exp) << 23) | (mantissa & mantissamax))
p.Bitrate = loadBitrate(buf[17:20])

// Clear any existing SSRCs
p.SSRCs = nil
Expand All @@ -263,22 +219,8 @@ func (p *ReceiverEstimatedMaximumBitrate) Header() Header {

// String prints the REMB packet in a human-readable format.
func (p *ReceiverEstimatedMaximumBitrate) String() string {
// Keep a table of powers to units for fast conversion.
bitUnits := []string{"b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb"}

// Do some unit conversions because b/s is far too difficult to read.
bitrate := p.Bitrate
powers := 0

// Keep dividing the bitrate until it's under 1000
for bitrate >= 1000.0 && powers < len(bitUnits) {
bitrate /= 1000.0
powers++
}

unit := bitUnits[powers] //nolint:gosec // powers is bounded by loop condition

return fmt.Sprintf("ReceiverEstimatedMaximumBitrate %x %.2f %s/s", p.SenderSSRC, bitrate, unit)
unit := bitrateUnit(p.Bitrate)
return fmt.Sprintf("ReceiverEstimatedMaximumBitrate %x %.2f %s/s", p.SenderSSRC, p.Bitrate, unit)
}

// DestinationSSRC returns an array of SSRC values that this packet refers to.
Expand Down
148 changes: 148 additions & 0 deletions tmmbn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-FileCopyrightText: 2025 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package rtcp

import (
"encoding/binary"
"fmt"
"strings"
)

// TMMBN represents a Temporary Maximum Media Stream Bit Rate Notification packet
// as defined in RFC 5104, section 4.2.2.
type TMMBN struct {
// SSRC of the sender
SenderSSRC uint32

// List of TMMBN entries
Entries []TMMBNEntry
}

// TMMBNEntry represents a single entry in TMMBN packet
type TMMBNEntry struct {
// SSRC of media source this entry applies to
MediaSSRC uint32

// Estimated maximum bitrate
Bitrate float32
}

// Marshal encodes the TMMBN packet in binary format
func (p TMMBN) Marshal() ([]byte, error) {
/*
TMMBN packet format (RFC 5104):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| FMT=4 | PT = 205 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of RTCP packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FCI SSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| MxTBR Exp | MxTBR Mantissa |Measured Overhead|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
*/

packetSize := p.MarshalSize()
rawPacket := make([]byte, packetSize)

header := p.Header()
headerBuf, err := header.Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, headerBuf)

body := rawPacket[headerLength:]
binary.BigEndian.PutUint32(body, p.SenderSSRC)
// Media SSRC is always 0
// https://www.rfc-editor.org/rfc/rfc5104.html#section-4.2.1.2
binary.BigEndian.PutUint32(body[ssrcLength:], 0)

// Write each FCI entry
for i, entry := range p.Entries {
offset := ssrcLength*2 + i*(2*ssrcLength)
binary.BigEndian.PutUint32(body[offset:], entry.MediaSSRC)

err = putBitrate(entry.Bitrate, body[offset+ssrcLength:])
if err != nil {
return nil, err
}
}

return rawPacket, nil
}

// Unmarshal decodes the TMMBN packet from binary data
func (p *TMMBN) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < headerLength+ssrcLength*2 {
return errPacketTooShort
}

var header Header
if err := header.Unmarshal(rawPacket); err != nil {
return err
}

expectedSize := int((header.Length + 1) * 4)
if len(rawPacket) < expectedSize {
return errBadLength
}

if header.Type != TypeTransportSpecificFeedback || header.Count != FormatTMMBN {
return errWrongType
}

body := rawPacket[headerLength:]
p.SenderSSRC = binary.BigEndian.Uint32(body)

entryCount := int((header.Length - 2) / 2)
p.Entries = make([]TMMBNEntry, entryCount)

for i := 0; i < entryCount; i++ {
offset := ssrcLength*2 + i*(2*ssrcLength)
entry := &p.Entries[i]
entry.MediaSSRC = binary.BigEndian.Uint32(body[offset:])
entry.Bitrate = loadBitrate(body[offset+ssrcLength:])
}

return nil
}

// MarshalSize returns the size of the packet when marshaled
func (p *TMMBN) MarshalSize() int {
return headerLength + ssrcLength*2 + len(p.Entries)*(2*ssrcLength)
}

func (p *TMMBN) Header() Header {
return Header{
Count: FormatTMMBN,
Type: TypeTransportSpecificFeedback,
Length: uint16((p.MarshalSize() / 4) - 1), //nolint:gosec // G115
}
}

// String prints the TMMBN packet in a human-readable format.
func (p *TMMBN) String() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("TMMBN from %x:\n", p.SenderSSRC))
for i, entry := range p.Entries {
unit := bitrateUnit(entry.Bitrate)
sb.WriteString(fmt.Sprintf(" entry %d: media=%x, bitrate=%.2f %s/s\n", i, entry.MediaSSRC, entry.Bitrate, unit))
}
return sb.String()
}

// DestinationSSRC returns SSRCs this packet applies to
func (p *TMMBN) DestinationSSRC() []uint32 {
ssrcs := make([]uint32, len(p.Entries))
for i, entry := range p.Entries {
ssrcs[i] = entry.MediaSSRC
}
return ssrcs
}
Loading