1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-03-09 15:49:59 +00:00

For regression test, add srs-bench to 3rdparty

This commit is contained in:
winlin 2021-03-04 13:23:01 +08:00
parent de87dd427d
commit 876210f6c9
1158 changed files with 256967 additions and 3 deletions

View file

@ -0,0 +1,24 @@
### JetBrains IDE ###
#####################
.idea/
### Emacs Temporary Files ###
#############################
*~
### Folders ###
###############
bin/
vendor/
node_modules/
### Files ###
#############
*.ivf
*.ogg
tags
cover.out
*.sw[poe]
*.wasm
examples/sfu-ws/cert.pem
examples/sfu-ws/key.pem

View file

@ -0,0 +1,89 @@
linters-settings:
govet:
check-shadowing: true
misspell:
locale: US
exhaustive:
default-signifies-exhaustive: true
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
linters:
enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bodyclose # checks whether HTTP response body is closed successfully
- deadcode # Finds unused code
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
- exhaustive # check exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- gci # Gci control golang package import order and make it always deterministic.
- gochecknoglobals # Checks that no globals are present in Go code
- gochecknoinits # Checks that no init functions are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- godox # Tool for detection of FIXME, TODO and other comment keywords
- goerr113 # Golang linter to check the errors handling expressions
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
- goheader # Checks is file header matches to pattern
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
- gosec # Inspects source code for security problems
- gosimple # Linter for Go source code that specializes in simplifying a code
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- ineffassign # Detects when assignments to existing variables are not used
- misspell # Finds commonly misspelled English words in comments
- nakedret # Finds naked returns in functions greater than a specified function length
- noctx # noctx finds sending http request without context.Context
- scopelint # Scopelint checks for unpinned variables in go programs
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
- structcheck # Finds unused struct fields
- stylecheck # Stylecheck is a replacement for golint
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
- unconvert # Remove unnecessary type conversions
- unparam # Reports unused function parameters
- unused # Checks Go code for unused constants, variables, functions and types
- varcheck # Finds unused global variables and constants
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- funlen # Tool for detection of long functions
- gocyclo # Computes and checks the cyclomatic complexity of functions
- godot # Check if comments end in a period
- gomnd # An analyzer to detect magic numbers.
- lll # Reports long lines
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted
- nestif # Reports deeply nested if statements
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
- nolintlint # Reports ill-formed or insufficient nolint directives
- prealloc # Finds slice declarations that could potentially be preallocated
- rowserrcheck # checks whether Err of rows is checked successfully
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
- testpackage # linter that makes you use a separate _test package
- wsl # Whitespace Linter - Forces you to use empty lines!
issues:
exclude-use-default: false
exclude-rules:
# Allow complex tests, better to be self contained
- path: _test\.go
linters:
- gocognit
# Allow complex main function in examples
- path: examples
text: "of func `main` is high"
linters:
- gocognit
run:
skip-dirs-use-default: false

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,49 @@
<h1 align="center">
<br>
Pion RTCP
<br>
</h1>
<h4 align="center">A Go implementation of RTCP</h4>
<p align="center">
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-rtcp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion RTCP"></a>
<a href="https://sourcegraph.com/github.com/pion/rtcp?badge"><img src="https://sourcegraph.com/github.com/pion/rtcp/-/badge.svg" alt="Sourcegraph Widget"></a>
<a href="https://pion.ly/slack"><img src="https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen" alt="Slack Widget"></a>
<br>
<a href="https://travis-ci.org/pion/rtcp"><img src="https://travis-ci.org/pion/rtcp.svg?branch=master" alt="Build Status"></a>
<a href="https://pkg.go.dev/github.com/pion/rtcp"><img src="https://godoc.org/github.com/pion/rtcp?status.svg" alt="GoDoc"></a>
<a href="https://codecov.io/gh/pion/rtcp"><img src="https://codecov.io/gh/pion/rtcp/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="https://goreportcard.com/report/github.com/pion/rtcp"><img src="https://goreportcard.com/badge/github.com/pion/rtcp" alt="Go Report Card"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
</p>
<br>
See [DESIGN.md](DESIGN.md) for an overview of features and future goals.
### Roadmap
The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones.
### Community
Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion).
We are always looking to support **your projects**. Please reach out if you have something to build!
If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly)
### Contributing
Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible:
* [Max Hawkins](https://github.com/maxhawkins) - *Original Author*
* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes*
* [Sean DuBois](https://github.com/Sean-Der) - *Linter fixes*
* [adwpc](https://github.com/adwpc)
* [Luke Curley](https://github.com/kixelated)
* [Hugo Arregui](https://github.com/hugoArregui)
* [Atsushi Watanabe](https://github.com/at-wat)
* [Juliusz Chroboczek](https://github.com/jech)
* [Gabor Pongracz](https://github.com/pongraczgabor87)
* [Simone Gotti](https://github.com/sgotti)
* [lllf](https://github.com/LittleLightLittleFire)
* [cnderrauber](https://github.com/cnderrauber)
### License
MIT License - see [LICENSE](LICENSE) for full text

View file

@ -0,0 +1,20 @@
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
coverage:
status:
project:
default:
# Allow decreasing 2% of total coverage to avoid noise.
threshold: 2%
patch:
default:
target: 70%
only_pulls: true
ignore:
- "examples/*"
- "examples/**/*"

View file

@ -0,0 +1,136 @@
package rtcp
// A CompoundPacket is a collection of RTCP packets transmitted as a single packet with
// the underlying protocol (for example UDP).
//
// To maximize the resolution of receiption statistics, the first Packet in a CompoundPacket
// must always be either a SenderReport or a ReceiverReport. This is true even if no data
// has been sent or received, in which case an empty ReceiverReport must be sent, and even
// if the only other RTCP packet in the compound packet is a Goodbye.
//
// Next, a SourceDescription containing a CNAME item must be included in each CompoundPacket
// to identify the source and to begin associating media for purposes such as lip-sync.
//
// Other RTCP packet types may follow in any order. Packet types may appear more than once.
type CompoundPacket []Packet
var _ Packet = (*CompoundPacket)(nil) // assert is a Packet
// Validate returns an error if this is not an RFC-compliant CompoundPacket.
func (c CompoundPacket) Validate() error {
if len(c) == 0 {
return errEmptyCompound
}
// SenderReport and ReceiverReport are the only types that
// are allowed to be the first packet in a compound datagram
switch c[0].(type) {
case *SenderReport, *ReceiverReport:
// ok
default:
return errBadFirstPacket
}
for _, pkt := range c[1:] {
switch p := pkt.(type) {
// If the number of RecetpionReports exceeds 31 additional ReceiverReports
// can be included here.
case *ReceiverReport:
continue
// A SourceDescription containing a CNAME must be included in every
// CompoundPacket.
case *SourceDescription:
var hasCNAME bool
for _, c := range p.Chunks {
for _, it := range c.Items {
if it.Type == SDESCNAME {
hasCNAME = true
}
}
}
if !hasCNAME {
return errMissingCNAME
}
return nil
// Other packets are not permitted before the CNAME
default:
return errPacketBeforeCNAME
}
}
// CNAME never reached
return errMissingCNAME
}
// CNAME returns the CNAME that *must* be present in every CompoundPacket
func (c CompoundPacket) CNAME() (string, error) {
var err error
if len(c) < 1 {
return "", errEmptyCompound
}
for _, pkt := range c[1:] {
sdes, ok := pkt.(*SourceDescription)
if ok {
for _, c := range sdes.Chunks {
for _, it := range c.Items {
if it.Type == SDESCNAME {
return it.Text, err
}
}
}
} else {
_, ok := pkt.(*ReceiverReport)
if !ok {
err = errPacketBeforeCNAME
}
}
}
return "", errMissingCNAME
}
// Marshal encodes the CompoundPacket as binary.
func (c CompoundPacket) Marshal() ([]byte, error) {
if err := c.Validate(); err != nil {
return nil, err
}
p := []Packet(c)
return Marshal(p)
}
// Unmarshal decodes a CompoundPacket from binary.
func (c *CompoundPacket) Unmarshal(rawData []byte) error {
out := make(CompoundPacket, 0)
for len(rawData) != 0 {
p, processed, err := unmarshal(rawData)
if err != nil {
return err
}
out = append(out, p)
rawData = rawData[processed:]
}
*c = out
if err := c.Validate(); err != nil {
return err
}
return nil
}
// DestinationSSRC returns the synchronization sources associated with this
// CompoundPacket's reception report.
func (c CompoundPacket) DestinationSSRC() []uint32 {
if len(c) == 0 {
return nil
}
return c[0].DestinationSSRC()
}

View file

@ -0,0 +1,39 @@
/*
Package rtcp implements encoding and decoding of RTCP packets according to RFCs 3550 and 5506.
RTCP is a sister protocol of the Real-time Transport Protocol (RTP). Its basic functionality
and packet structure is defined in RFC 3550. RTCP provides out-of-band statistics and control
information for an RTP session. It partners with RTP in the delivery and packaging of multimedia data,
but does not transport any media data itself.
The primary function of RTCP is to provide feedback on the quality of service (QoS)
in media distribution by periodically sending statistics information such as transmitted octet
and packet counts, packet loss, packet delay variation, and round-trip delay time to participants
in a streaming multimedia session. An application may use this information to control quality of
service parameters, perhaps by limiting flow, or using a different codec.
Decoding RTCP packets:
pkt, err := rtcp.Unmarshal(rtcpData)
// ...
switch p := pkt.(type) {
case *rtcp.CompoundPacket:
...
case *rtcp.PictureLossIndication:
...
default:
...
}
Encoding RTCP packets:
pkt := &rtcp.PictureLossIndication{
SenderSSRC: senderSSRC,
MediaSSRC: mediaSSRC
}
pliData, err := pkt.Marshal()
// ...
*/
package rtcp

View file

@ -0,0 +1,30 @@
package rtcp
import "errors"
var (
errWrongMarshalSize = errors.New("rtcp: wrong marshal size")
errInvalidTotalLost = errors.New("rtcp: invalid total lost count")
errInvalidHeader = errors.New("rtcp: invalid header")
errEmptyCompound = errors.New("rtcp: empty compound packet")
errBadFirstPacket = errors.New("rtcp: first packet in compound must be SR or RR")
errMissingCNAME = errors.New("rtcp: compound missing SourceDescription with CNAME")
errPacketBeforeCNAME = errors.New("rtcp: feedback packet seen before CNAME")
errTooManyReports = errors.New("rtcp: too many reports")
errTooManyChunks = errors.New("rtcp: too many chunks")
errTooManySources = errors.New("rtcp: too many sources")
errPacketTooShort = errors.New("rtcp: packet too short")
errWrongType = errors.New("rtcp: wrong packet type")
errSDESTextTooLong = errors.New("rtcp: sdes must be < 255 octets long")
errSDESMissingType = errors.New("rtcp: sdes item missing type")
errReasonTooLong = errors.New("rtcp: reason must be < 255 octets long")
errBadVersion = errors.New("rtcp: invalid packet version")
errWrongPadding = errors.New("rtcp: invalid padding value")
errWrongFeedbackType = errors.New("rtcp: wrong feedback message type")
errWrongPayloadType = errors.New("rtcp: wrong payload type")
errHeaderTooSmall = errors.New("rtcp: header length is too small")
errSSRCMustBeZero = errors.New("rtcp: media SSRC must be 0")
errMissingREMBidentifier = errors.New("missing REMB identifier")
errSSRCNumAndLengthMismatch = errors.New("SSRC num and length do not match")
errInvalidSizeOrStartIndex = errors.New("invalid size or startIndex")
)

View file

@ -0,0 +1,107 @@
package rtcp
import (
"encoding/binary"
"fmt"
)
// A FIREntry is a (SSRC, seqno) pair, as carried by FullIntraRequest.
type FIREntry struct {
SSRC uint32
SequenceNumber uint8
}
// The FullIntraRequest packet is used to reliably request an Intra frame
// in a video stream. See RFC 5104 Section 3.5.1. This is not for loss
// recovery, which should use PictureLossIndication (PLI) instead.
type FullIntraRequest struct {
SenderSSRC uint32
MediaSSRC uint32
FIR []FIREntry
}
const (
firOffset = 8
)
var _ Packet = (*FullIntraRequest)(nil)
// Marshal encodes the FullIntraRequest
func (p FullIntraRequest) Marshal() ([]byte, error) {
rawPacket := make([]byte, firOffset+(len(p.FIR)*8))
binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)
binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)
for i, fir := range p.FIR {
binary.BigEndian.PutUint32(rawPacket[firOffset+8*i:], fir.SSRC)
rawPacket[firOffset+8*i+4] = fir.SequenceNumber
}
h := p.Header()
hData, err := h.Marshal()
if err != nil {
return nil, err
}
return append(hData, rawPacket...), nil
}
// Unmarshal decodes the TransportLayerNack
func (p *FullIntraRequest) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if len(rawPacket) < (headerLength + int(4*h.Length)) {
return errPacketTooShort
}
if h.Type != TypePayloadSpecificFeedback || h.Count != FormatFIR {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
for i := headerLength + firOffset; i < (headerLength + int(h.Length*4)); i += 8 {
p.FIR = append(p.FIR, FIREntry{
binary.BigEndian.Uint32(rawPacket[i:]),
rawPacket[i+4],
})
}
return nil
}
// Header returns the Header associated with this packet.
func (p *FullIntraRequest) Header() Header {
return Header{
Count: FormatFIR,
Type: TypePayloadSpecificFeedback,
Length: uint16((p.len() / 4) - 1),
}
}
func (p *FullIntraRequest) len() int {
return headerLength + firOffset + len(p.FIR)*8
}
func (p *FullIntraRequest) String() string {
out := fmt.Sprintf("FullIntraRequest %x %x",
p.SenderSSRC, p.MediaSSRC)
for _, e := range p.FIR {
out += fmt.Sprintf(" (%x %v)", e.SSRC, e.SequenceNumber)
}
return out
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *FullIntraRequest) DestinationSSRC() []uint32 {
ssrcs := make([]uint32, 0, len(p.FIR))
for _, entry := range p.FIR {
ssrcs = append(ssrcs, entry.SSRC)
}
return ssrcs
}

View file

@ -0,0 +1,51 @@
// +build gofuzz
package rtcp
import (
"bytes"
"io"
)
// Fuzz implements a randomized fuzz test of the rtcp
// parser using go-fuzz.
//
// To run the fuzzer, first download go-fuzz:
// `go get github.com/dvyukov/go-fuzz/...`
//
// Then build the testing package:
// `go-fuzz-build github.com/pion/webrtc`
//
// And run the fuzzer on the corpus:
// ```
// mkdir workdir
//
// # optionally add a starter corpus of valid rtcp packets.
// # the corpus should be as compact and diverse as possible.
// cp -r ~/my-rtcp-packets workdir/corpus
//
// go-fuzz -bin=ase-fuzz.zip -workdir=workdir
// ````
func Fuzz(data []byte) int {
r := NewReader(bytes.NewReader(data))
for {
_, data, err := r.ReadPacket()
if err == io.EOF {
break
}
if err != nil {
return 0
}
packet, err := Unmarshal(data)
if err != nil {
return 0
}
if _, err := packet.Marshal(); err != nil {
return 0
}
}
return 1
}

View file

@ -0,0 +1,5 @@
module github.com/pion/rtcp
go 1.13
require github.com/stretchr/testify v1.6.1

View file

@ -0,0 +1,11 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,146 @@
package rtcp
import (
"encoding/binary"
)
// The Goodbye packet indicates that one or more sources are no longer active.
type Goodbye struct {
// The SSRC/CSRC identifiers that are no longer active
Sources []uint32
// Optional text indicating the reason for leaving, e.g., "camera malfunction" or "RTP loop detected"
Reason string
}
var _ Packet = (*Goodbye)(nil) // assert is a Packet
// Marshal encodes the Goodbye packet in binary
func (g Goodbye) Marshal() ([]byte, error) {
/*
* 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| SC | PT=BYE=203 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC/CSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* (opt) | length | reason for leaving ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, g.len())
packetBody := rawPacket[headerLength:]
if len(g.Sources) > countMax {
return nil, errTooManySources
}
for i, s := range g.Sources {
binary.BigEndian.PutUint32(packetBody[i*ssrcLength:], s)
}
if g.Reason != "" {
reason := []byte(g.Reason)
if len(reason) > sdesMaxOctetCount {
return nil, errReasonTooLong
}
reasonOffset := len(g.Sources) * ssrcLength
packetBody[reasonOffset] = uint8(len(reason))
copy(packetBody[reasonOffset+1:], reason)
}
hData, err := g.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the Goodbye packet from binary
func (g *Goodbye) Unmarshal(rawPacket []byte) error {
/*
* 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| SC | PT=BYE=203 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC/CSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* (opt) | length | reason for leaving ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
var header Header
if err := header.Unmarshal(rawPacket); err != nil {
return err
}
if header.Type != TypeGoodbye {
return errWrongType
}
if getPadding(len(rawPacket)) != 0 {
return errPacketTooShort
}
g.Sources = make([]uint32, header.Count)
reasonOffset := int(headerLength + header.Count*ssrcLength)
if reasonOffset > len(rawPacket) {
return errPacketTooShort
}
for i := 0; i < int(header.Count); i++ {
offset := headerLength + i*ssrcLength
g.Sources[i] = binary.BigEndian.Uint32(rawPacket[offset:])
}
if reasonOffset < len(rawPacket) {
reasonLen := int(rawPacket[reasonOffset])
reasonEnd := reasonOffset + 1 + reasonLen
if reasonEnd > len(rawPacket) {
return errPacketTooShort
}
g.Reason = string(rawPacket[reasonOffset+1 : reasonEnd])
}
return nil
}
// Header returns the Header associated with this packet.
func (g *Goodbye) Header() Header {
return Header{
Padding: false,
Count: uint8(len(g.Sources)),
Type: TypeGoodbye,
Length: uint16((g.len() / 4) - 1),
}
}
func (g *Goodbye) len() int {
srcsLength := len(g.Sources) * ssrcLength
reasonLength := len(g.Reason) + 1
l := headerLength + srcsLength + reasonLength
// align to 32-bit boundary
return l + getPadding(l)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (g *Goodbye) DestinationSSRC() []uint32 {
out := make([]uint32, len(g.Sources))
copy(out, g.Sources)
return out
}

View file

@ -0,0 +1,140 @@
package rtcp
import (
"encoding/binary"
)
// PacketType specifies the type of an RTCP packet
type PacketType uint8
// RTCP packet types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4
const (
TypeSenderReport PacketType = 200 // RFC 3550, 6.4.1
TypeReceiverReport PacketType = 201 // RFC 3550, 6.4.2
TypeSourceDescription PacketType = 202 // RFC 3550, 6.5
TypeGoodbye PacketType = 203 // RFC 3550, 6.6
TypeApplicationDefined PacketType = 204 // RFC 3550, 6.7 (unimplemented)
TypeTransportSpecificFeedback PacketType = 205 // RFC 4585, 6051
TypePayloadSpecificFeedback PacketType = 206 // RFC 4585, 6.3
)
// Transport and Payload specific feedback messages overload the count field to act as a message type. those are listed here
const (
FormatSLI uint8 = 2
FormatPLI uint8 = 1
FormatFIR uint8 = 4
FormatTLN uint8 = 1
FormatRRR uint8 = 5
FormatREMB uint8 = 15
//https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5
FormatTCC uint8 = 15
)
func (p PacketType) String() string {
switch p {
case TypeSenderReport:
return "SR"
case TypeReceiverReport:
return "RR"
case TypeSourceDescription:
return "SDES"
case TypeGoodbye:
return "BYE"
case TypeApplicationDefined:
return "APP"
case TypeTransportSpecificFeedback:
return "TSFB"
case TypePayloadSpecificFeedback:
return "PSFB"
default:
return string(p)
}
}
const rtpVersion = 2
// A Header is the common header shared by all RTCP packets
type Header struct {
// If the padding bit is set, this individual RTCP packet contains
// some additional padding octets at the end which are not part of
// the control information but are included in the length field.
Padding bool
// The number of reception reports, sources contained or FMT in this packet (depending on the Type)
Count uint8
// The RTCP packet type for this packet
Type PacketType
// The length of this RTCP packet in 32-bit words minus one,
// including the header and any padding.
Length uint16
}
const (
headerLength = 4
versionShift = 6
versionMask = 0x3
paddingShift = 5
paddingMask = 0x1
countShift = 0
countMask = 0x1f
countMax = (1 << 5) - 1
)
// Marshal encodes the Header in binary
func (h Header) Marshal() ([]byte, error) {
/*
* 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| RC | PT=SR=200 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, headerLength)
rawPacket[0] |= rtpVersion << versionShift
if h.Padding {
rawPacket[0] |= 1 << paddingShift
}
if h.Count > 31 {
return nil, errInvalidHeader
}
rawPacket[0] |= h.Count << countShift
rawPacket[1] = uint8(h.Type)
binary.BigEndian.PutUint16(rawPacket[2:], h.Length)
return rawPacket, nil
}
// Unmarshal decodes the Header from binary
func (h *Header) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < headerLength {
return errPacketTooShort
}
/*
* 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| RC | PT | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
version := rawPacket[0] >> versionShift & versionMask
if version != rtpVersion {
return errBadVersion
}
h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
h.Count = rawPacket[0] >> countShift & countMask
h.Type = PacketType(rawPacket[1])
h.Length = binary.BigEndian.Uint16(rawPacket[2:])
return nil
}

View file

@ -0,0 +1,115 @@
package rtcp
// Packet represents an RTCP packet, a protocol used for out-of-band statistics and control information for an RTP session
type Packet interface {
// DestinationSSRC returns an array of SSRC values that this packet refers to.
DestinationSSRC() []uint32
Marshal() ([]byte, error)
Unmarshal(rawPacket []byte) error
}
// Unmarshal takes an entire udp datagram (which may consist of multiple RTCP packets) and
// returns the unmarshaled packets it contains.
//
// If this is a reduced-size RTCP packet a feedback packet (Goodbye, SliceLossIndication, etc)
// will be returned. Otherwise, the underlying type of the returned packet will be
// CompoundPacket.
func Unmarshal(rawData []byte) ([]Packet, error) {
var packets []Packet
for len(rawData) != 0 {
p, processed, err := unmarshal(rawData)
if err != nil {
return nil, err
}
packets = append(packets, p)
rawData = rawData[processed:]
}
switch len(packets) {
// Empty packet
case 0:
return nil, errInvalidHeader
// Multiple Packets
default:
return packets, nil
}
}
// Marshal takes an array of Packets and serializes them to a single buffer
func Marshal(packets []Packet) ([]byte, error) {
out := make([]byte, 0)
for _, p := range packets {
data, err := p.Marshal()
if err != nil {
return nil, err
}
out = append(out, data...)
}
return out, nil
}
// unmarshal is a factory which pulls the first RTCP packet from a bytestream,
// and returns it's parsed representation, and the amount of data that was processed.
func unmarshal(rawData []byte) (packet Packet, bytesprocessed int, err error) {
var h Header
err = h.Unmarshal(rawData)
if err != nil {
return nil, 0, err
}
bytesprocessed = int(h.Length+1) * 4
if bytesprocessed > len(rawData) {
return nil, 0, errPacketTooShort
}
inPacket := rawData[:bytesprocessed]
switch h.Type {
case TypeSenderReport:
packet = new(SenderReport)
case TypeReceiverReport:
packet = new(ReceiverReport)
case TypeSourceDescription:
packet = new(SourceDescription)
case TypeGoodbye:
packet = new(Goodbye)
case TypeTransportSpecificFeedback:
switch h.Count {
case FormatTLN:
packet = new(TransportLayerNack)
case FormatRRR:
packet = new(RapidResynchronizationRequest)
case FormatTCC:
packet = new(TransportLayerCC)
default:
packet = new(RawPacket)
}
case TypePayloadSpecificFeedback:
switch h.Count {
case FormatPLI:
packet = new(PictureLossIndication)
case FormatSLI:
packet = new(SliceLossIndication)
case FormatREMB:
packet = new(ReceiverEstimatedMaximumBitrate)
case FormatFIR:
packet = new(FullIntraRequest)
default:
packet = new(RawPacket)
}
default:
packet = new(RawPacket)
}
err = packet.Unmarshal(inPacket)
return packet, bytesprocessed, err
}

View file

@ -0,0 +1,91 @@
package rtcp
import (
"encoding/binary"
"fmt"
)
// The PictureLossIndication packet informs the encoder about the loss of an undefined amount of coded video data belonging to one or more pictures
type PictureLossIndication struct {
// SSRC of sender
SenderSSRC uint32
// SSRC where the loss was experienced
MediaSSRC uint32
}
var _ Packet = (*PictureLossIndication)(nil) // assert is a Packet
const (
pliLength = 2
)
// Marshal encodes the PictureLossIndication in binary
func (p PictureLossIndication) Marshal() ([]byte, error) {
/*
* PLI does not require parameters. Therefore, the length field MUST be
* 2, and there MUST NOT be any Feedback Control Information.
*
* The semantics of this FB message is independent of the payload type.
*/
rawPacket := make([]byte, p.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody, p.SenderSSRC)
binary.BigEndian.PutUint32(packetBody[4:], p.MediaSSRC)
h := Header{
Count: FormatPLI,
Type: TypePayloadSpecificFeedback,
Length: pliLength,
}
hData, err := h.Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the PictureLossIndication from binary
func (p *PictureLossIndication) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + (ssrcLength * 2)) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypePayloadSpecificFeedback || h.Count != FormatPLI {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
return nil
}
// Header returns the Header associated with this packet.
func (p *PictureLossIndication) Header() Header {
return Header{
Count: FormatPLI,
Type: TypePayloadSpecificFeedback,
Length: pliLength,
}
}
func (p *PictureLossIndication) len() int {
return headerLength + ssrcLength*2
}
func (p *PictureLossIndication) String() string {
return fmt.Sprintf("PictureLossIndication %x %x", p.SenderSSRC, p.MediaSSRC)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *PictureLossIndication) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}

View file

@ -0,0 +1,88 @@
package rtcp
import (
"encoding/binary"
"fmt"
)
// The RapidResynchronizationRequest packet informs the encoder about the loss of an undefined amount of coded video data belonging to one or more pictures
type RapidResynchronizationRequest struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
}
var _ Packet = (*RapidResynchronizationRequest)(nil) // assert is a Packet
const (
rrrLength = 2
rrrHeaderLength = ssrcLength * 2
rrrMediaOffset = 4
)
// Marshal encodes the RapidResynchronizationRequest in binary
func (p RapidResynchronizationRequest) Marshal() ([]byte, error) {
/*
* RRR does not require parameters. Therefore, the length field MUST be
* 2, and there MUST NOT be any Feedback Control Information.
*
* The semantics of this FB message is independent of the payload type.
*/
rawPacket := make([]byte, p.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody, p.SenderSSRC)
binary.BigEndian.PutUint32(packetBody[rrrMediaOffset:], p.MediaSSRC)
hData, err := p.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the RapidResynchronizationRequest from binary
func (p *RapidResynchronizationRequest) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + (ssrcLength * 2)) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeTransportSpecificFeedback || h.Count != FormatRRR {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
return nil
}
func (p *RapidResynchronizationRequest) len() int {
return headerLength + rrrHeaderLength
}
// Header returns the Header associated with this packet.
func (p *RapidResynchronizationRequest) Header() Header {
return Header{
Count: FormatRRR,
Type: TypeTransportSpecificFeedback,
Length: rrrLength,
}
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *RapidResynchronizationRequest) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}
func (p *RapidResynchronizationRequest) String() string {
return fmt.Sprintf("RapidResynchronizationRequest %x %x", p.SenderSSRC, p.MediaSSRC)
}

View file

@ -0,0 +1,44 @@
package rtcp
import "fmt"
// RawPacket represents an unparsed RTCP packet. It's returned by Unmarshal when
// a packet with an unknown type is encountered.
type RawPacket []byte
var _ Packet = (*RawPacket)(nil) // assert is a Packet
// Marshal encodes the packet in binary.
func (r RawPacket) Marshal() ([]byte, error) {
return r, nil
}
// Unmarshal decodes the packet from binary.
func (r *RawPacket) Unmarshal(b []byte) error {
if len(b) < (headerLength) {
return errPacketTooShort
}
*r = b
var h Header
return h.Unmarshal(b)
}
// Header returns the Header associated with this packet.
func (r RawPacket) Header() Header {
var h Header
if err := h.Unmarshal(r); err != nil {
return Header{}
}
return h
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *RawPacket) DestinationSSRC() []uint32 {
return []uint32{}
}
func (r RawPacket) String() string {
out := fmt.Sprintf("RawPacket: %v", ([]byte)(r))
return out
}

View file

@ -0,0 +1,284 @@
package rtcp
import (
"bytes"
"encoding/binary"
"fmt"
"math/bits"
)
// ReceiverEstimatedMaximumBitrate contains the receiver's estimated maximum bitrate.
// see: https://tools.ietf.org/html/draft-alvestrand-rmcat-remb-03
type ReceiverEstimatedMaximumBitrate struct {
// SSRC of sender
SenderSSRC uint32
// Estimated maximum bitrate
Bitrate uint64
// SSRC entries which this packet applies to
SSRCs []uint32
}
var _ Packet = (*ReceiverEstimatedMaximumBitrate)(nil) // assert is a Packet
// Marshal serializes the packet and returns a byte slice.
func (p ReceiverEstimatedMaximumBitrate) Marshal() (buf []byte, err error) {
// Allocate a buffer of the exact output size.
buf = make([]byte, p.MarshalSize())
// Write to our buffer.
n, err := p.MarshalTo(buf)
if err != nil {
return nil, err
}
// This will always be true but just to be safe.
if n != len(buf) {
return nil, errWrongMarshalSize
}
return buf, nil
}
// MarshalSize returns the size of the packet when marshaled.
// This can be used in conjunction with `MarshalTo` to avoid allocations.
func (p ReceiverEstimatedMaximumBitrate) MarshalSize() (n int) {
return 20 + 4*len(p.SSRCs)
}
// MarshalTo serializes the packet to the given byte slice.
func (p ReceiverEstimatedMaximumBitrate) MarshalTo(buf []byte) (n int, err error) {
/*
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=15 | PT=206 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unique identifier 'R' 'E' 'M' 'B' |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Num SSRC | BR Exp | BR Mantissa |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC feedback |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
*/
size := p.MarshalSize()
if len(buf) < size {
return 0, errPacketTooShort
}
buf[0] = 143 // v=2, p=0, fmt=15
buf[1] = 206
// Length of this packet in 32-bit words minus one.
length := uint16((p.MarshalSize() / 4) - 1)
binary.BigEndian.PutUint16(buf[2:4], length)
binary.BigEndian.PutUint32(buf[4:8], p.SenderSSRC)
binary.BigEndian.PutUint32(buf[8:12], 0) // always zero
// ALL HAIL REMB
buf[12] = 'R'
buf[13] = 'E'
buf[14] = 'M'
buf[15] = 'B'
// Write the length of the ssrcs to follow at the end
buf[16] = byte(len(p.SSRCs))
// We can only encode 18 bits of information in the mantissa.
// The exponent lets us shift to the left up to 64 places (6-bits).
// We actually need a uint82 to encode the largest possible number,
// but uint64 should be good enough for 2.3 exabytes per second.
// So we need to truncate the bitrate and use the exponent for the shift.
// bitrate = mantissa * (1 << exp)
// Calculate the total shift based on the leading number of zeroes.
// This will be negative if there is no shift required.
shift := uint(64 - bits.LeadingZeros64(p.Bitrate))
var mantissa uint
var exp uint
if shift <= 18 {
// Fit everything in the mantissa because we can.
mantissa = uint(p.Bitrate)
exp = 0
} else {
// We can only use 18 bits of precision, so truncate.
mantissa = uint(p.Bitrate >> (shift - 18))
exp = shift - 18
}
// 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) | (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 {
binary.BigEndian.PutUint32(buf[n:n+4], ssrc)
n += 4
}
return n, nil
}
// Unmarshal reads a REMB packet from the given byte slice.
func (p *ReceiverEstimatedMaximumBitrate) Unmarshal(buf []byte) (err error) {
/*
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=15 | PT=206 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unique identifier 'R' 'E' 'M' 'B' |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Num SSRC | BR Exp | BR Mantissa |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC feedback |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
*/
// 20 bytes is the size of the packet with no SSRCs
if len(buf) < 20 {
return errPacketTooShort
}
// version must be 2
version := buf[0] >> 6
if version != 2 {
return fmt.Errorf("%w expected(2) actual(%d)", errBadVersion, version)
}
// padding must be unset
padding := (buf[0] >> 5) & 1
if padding != 0 {
return fmt.Errorf("%w expected(0) actual(%d)", errWrongPadding, padding)
}
// fmt must be 15
fmtVal := buf[0] & 31
if fmtVal != 15 {
return fmt.Errorf("%w expected(15) actual(%d)", errWrongFeedbackType, fmtVal)
}
// Must be payload specific feedback
if buf[1] != 206 {
return fmt.Errorf("%w expected(206) actual(%d)", errWrongPayloadType, buf[1])
}
// length is the number of 32-bit words, minus 1
length := binary.BigEndian.Uint16(buf[2:4])
size := int((length + 1) * 4)
// There's not way this could be legit
if size < 20 {
return errHeaderTooSmall
}
// Make sure the buffer is large enough.
if len(buf) < size {
return errPacketTooShort
}
// The sender SSRC is 32-bits
p.SenderSSRC = binary.BigEndian.Uint32(buf[4:8])
// The destination SSRC must be 0
media := binary.BigEndian.Uint32(buf[8:12])
if media != 0 {
return errSSRCMustBeZero
}
// REMB rules all around me
if !bytes.Equal(buf[12:16], []byte{'R', 'E', 'M', 'B'}) {
return errMissingREMBidentifier
}
// The next byte is the number of SSRC entries at the end.
num := int(buf[16])
// Now we know the expected size, make sure they match.
if size != 20+4*num {
return errSSRCNumAndLengthMismatch
}
// Get the 6-bit exponent value.
exp := buf[17] >> 2
// The remaining 2-bits plus the next 16-bits are the mantissa.
mantissa := uint64(buf[17]&3)<<16 | uint64(buf[18])<<8 | uint64(buf[19])
// bitrate = mantissa * 2^exp
if exp > 46 {
// NOTE: We intentionally truncate values so they fit in a uint64.
// Otherwise we would need a uint82.
// This is 2.3 exabytes per second, which should be good enough.
p.Bitrate = ^uint64(0)
} else {
p.Bitrate = mantissa << exp
}
// Clear any existing SSRCs
p.SSRCs = nil
// Loop over and parse the SSRC entires at the end.
// We already verified that size == num * 4
for n := 20; n < size; n += 4 {
ssrc := binary.BigEndian.Uint32(buf[n : n+4])
p.SSRCs = append(p.SSRCs, ssrc)
}
return nil
}
// Header returns the Header associated with this packet.
func (p *ReceiverEstimatedMaximumBitrate) Header() Header {
return Header{
Count: FormatREMB,
Type: TypePayloadSpecificFeedback,
Length: uint16((p.MarshalSize() / 4) - 1),
}
}
// 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 := float64(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]
return fmt.Sprintf("ReceiverEstimatedMaximumBitrate %x %.2f %s/s", p.SenderSSRC, bitrate, unit)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *ReceiverEstimatedMaximumBitrate) DestinationSSRC() []uint32 {
return p.SSRCs
}

View file

@ -0,0 +1,193 @@
package rtcp
import (
"encoding/binary"
"fmt"
)
// A ReceiverReport (RR) packet provides reception quality feedback for an RTP stream
type ReceiverReport struct {
// The synchronization source identifier for the originator of this RR packet.
SSRC uint32
// Zero or more reception report blocks depending on the number of other
// sources heard by this sender since the last report. Each reception report
// block conveys statistics on the reception of RTP packets from a
// single synchronization source.
Reports []ReceptionReport
// Extension contains additional, payload-specific information that needs to
// be reported regularly about the receiver.
ProfileExtensions []byte
}
var _ Packet = (*ReceiverReport)(nil) // assert is a Packet
const (
ssrcLength = 4
rrSSRCOffset = headerLength
rrReportOffset = rrSSRCOffset + ssrcLength
)
// Marshal encodes the ReceiverReport in binary
func (r ReceiverReport) Marshal() ([]byte, error) {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=RR=201 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of packet sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, r.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody, r.SSRC)
for i, rp := range r.Reports {
data, err := rp.Marshal()
if err != nil {
return nil, err
}
offset := ssrcLength + receptionReportLength*i
copy(packetBody[offset:], data)
}
if len(r.Reports) > countMax {
return nil, errTooManyReports
}
pe := make([]byte, len(r.ProfileExtensions))
copy(pe, r.ProfileExtensions)
// if the length of the profile extensions isn't devisible
// by 4, we need to pad the end.
for (len(pe) & 0x3) != 0 {
pe = append(pe, 0)
}
rawPacket = append(rawPacket, pe...)
hData, err := r.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the ReceiverReport from binary
func (r *ReceiverReport) Unmarshal(rawPacket []byte) error {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=RR=201 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of packet sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeReceiverReport {
return errWrongType
}
r.SSRC = binary.BigEndian.Uint32(rawPacket[rrSSRCOffset:])
for i := rrReportOffset; i < len(rawPacket) && len(r.Reports) < int(h.Count); i += receptionReportLength {
var rr ReceptionReport
if err := rr.Unmarshal(rawPacket[i:]); err != nil {
return err
}
r.Reports = append(r.Reports, rr)
}
r.ProfileExtensions = rawPacket[rrReportOffset+(len(r.Reports)*receptionReportLength):]
if uint8(len(r.Reports)) != h.Count {
return errInvalidHeader
}
return nil
}
func (r *ReceiverReport) len() int {
repsLength := 0
for _, rep := range r.Reports {
repsLength += rep.len()
}
return headerLength + ssrcLength + repsLength
}
// Header returns the Header associated with this packet.
func (r *ReceiverReport) Header() Header {
return Header{
Count: uint8(len(r.Reports)),
Type: TypeReceiverReport,
Length: uint16((r.len()/4)-1) + uint16(getPadding(len(r.ProfileExtensions))),
}
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *ReceiverReport) DestinationSSRC() []uint32 {
out := make([]uint32, len(r.Reports))
for i, v := range r.Reports {
out[i] = v.SSRC
}
return out
}
func (r ReceiverReport) String() string {
out := fmt.Sprintf("ReceiverReport from %x\n", r.SSRC)
out += "\tSSRC \tLost\tLastSequence\n"
for _, i := range r.Reports {
out += fmt.Sprintf("\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)
}
out += fmt.Sprintf("\tProfile Extension Data: %v\n", r.ProfileExtensions)
return out
}

View file

@ -0,0 +1,130 @@
package rtcp
import "encoding/binary"
// A ReceptionReport block conveys statistics on the reception of RTP packets
// from a single synchronization source.
type ReceptionReport struct {
// The SSRC identifier of the source to which the information in this
// reception report block pertains.
SSRC uint32
// The fraction of RTP data packets from source SSRC lost since the
// previous SR or RR packet was sent, expressed as a fixed point
// number with the binary point at the left edge of the field.
FractionLost uint8
// The total number of RTP data packets from source SSRC that have
// been lost since the beginning of reception.
TotalLost uint32
// The low 16 bits contain the highest sequence number received in an
// RTP data packet from source SSRC, and the most significant 16
// bits extend that sequence number with the corresponding count of
// sequence number cycles.
LastSequenceNumber uint32
// An estimate of the statistical variance of the RTP data packet
// interarrival time, measured in timestamp units and expressed as an
// unsigned integer.
Jitter uint32
// The middle 32 bits out of 64 in the NTP timestamp received as part of
// the most recent RTCP sender report (SR) packet from source SSRC. If no
// SR has been received yet, the field is set to zero.
LastSenderReport uint32
// The delay, expressed in units of 1/65536 seconds, between receiving the
// last SR packet from source SSRC and sending this reception report block.
// If no SR packet has been received yet from SSRC, the field is set to zero.
Delay uint32
}
const (
receptionReportLength = 24
fractionLostOffset = 4
totalLostOffset = 5
lastSeqOffset = 8
jitterOffset = 12
lastSROffset = 16
delayOffset = 20
)
// Marshal encodes the ReceptionReport in binary
func (r ReceptionReport) Marshal() ([]byte, error) {
/*
* 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
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
rawPacket := make([]byte, receptionReportLength)
binary.BigEndian.PutUint32(rawPacket, r.SSRC)
rawPacket[fractionLostOffset] = r.FractionLost
// pack TotalLost into 24 bits
if r.TotalLost >= (1 << 25) {
return nil, errInvalidTotalLost
}
tlBytes := rawPacket[totalLostOffset:]
tlBytes[0] = byte(r.TotalLost >> 16)
tlBytes[1] = byte(r.TotalLost >> 8)
tlBytes[2] = byte(r.TotalLost)
binary.BigEndian.PutUint32(rawPacket[lastSeqOffset:], r.LastSequenceNumber)
binary.BigEndian.PutUint32(rawPacket[jitterOffset:], r.Jitter)
binary.BigEndian.PutUint32(rawPacket[lastSROffset:], r.LastSenderReport)
binary.BigEndian.PutUint32(rawPacket[delayOffset:], r.Delay)
return rawPacket, nil
}
// Unmarshal decodes the ReceptionReport from binary
func (r *ReceptionReport) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < receptionReportLength {
return errPacketTooShort
}
/*
* 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
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
r.SSRC = binary.BigEndian.Uint32(rawPacket)
r.FractionLost = rawPacket[fractionLostOffset]
tlBytes := rawPacket[totalLostOffset:]
r.TotalLost = uint32(tlBytes[2]) | uint32(tlBytes[1])<<8 | uint32(tlBytes[0])<<16
r.LastSequenceNumber = binary.BigEndian.Uint32(rawPacket[lastSeqOffset:])
r.Jitter = binary.BigEndian.Uint32(rawPacket[jitterOffset:])
r.LastSenderReport = binary.BigEndian.Uint32(rawPacket[lastSROffset:])
r.Delay = binary.BigEndian.Uint32(rawPacket[delayOffset:])
return nil
}
func (r *ReceptionReport) len() int {
return receptionReportLength
}

View file

@ -0,0 +1,15 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": [
"gomodTidy"
],
"commitBody": "Generated by renovateBot",
"packageRules": [
{
"packagePatterns": ["^golang.org/x/"],
"schedule": ["on the first day of the month"]
}
]
}

View file

@ -0,0 +1,260 @@
package rtcp
import (
"encoding/binary"
"fmt"
)
// A SenderReport (SR) packet provides reception quality feedback for an RTP stream
type SenderReport struct {
// The synchronization source identifier for the originator of this SR packet.
SSRC uint32
// The wallclock time when this report was sent so that it may be used in
// combination with timestamps returned in reception reports from other
// receivers to measure round-trip propagation to those receivers.
NTPTime uint64
// Corresponds to the same time as the NTP timestamp (above), but in
// the same units and with the same random offset as the RTP
// timestamps in data packets. This correspondence may be used for
// intra- and inter-media synchronization for sources whose NTP
// timestamps are synchronized, and may be used by media-independent
// receivers to estimate the nominal RTP clock frequency.
RTPTime uint32
// The total number of RTP data packets transmitted by the sender
// since starting transmission up until the time this SR packet was
// generated.
PacketCount uint32
// The total number of payload octets (i.e., not including header or
// padding) transmitted in RTP data packets by the sender since
// starting transmission up until the time this SR packet was
// generated.
OctetCount uint32
// Zero or more reception report blocks depending on the number of other
// sources heard by this sender since the last report. Each reception report
// block conveys statistics on the reception of RTP packets from a
// single synchronization source.
Reports []ReceptionReport
// ProfileExtensions contains additional, payload-specific information that needs to
// be reported regularly about the sender.
ProfileExtensions []byte
}
var _ Packet = (*SenderReport)(nil) // assert is a Packet
const (
srHeaderLength = 24
srSSRCOffset = 0
srNTPOffset = srSSRCOffset + ssrcLength
ntpTimeLength = 8
srRTPOffset = srNTPOffset + ntpTimeLength
rtpTimeLength = 4
srPacketCountOffset = srRTPOffset + rtpTimeLength
srPacketCountLength = 4
srOctetCountOffset = srPacketCountOffset + srPacketCountLength
srOctetCountLength = 4
srReportOffset = srOctetCountOffset + srOctetCountLength
)
// Marshal encodes the SenderReport in binary
func (r SenderReport) Marshal() ([]byte, error) {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=SR=200 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* sender | NTP timestamp, most significant word |
* info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | NTP timestamp, least significant word |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RTP timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's packet count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's octet count |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
rawPacket := make([]byte, r.len())
packetBody := rawPacket[headerLength:]
binary.BigEndian.PutUint32(packetBody[srSSRCOffset:], r.SSRC)
binary.BigEndian.PutUint64(packetBody[srNTPOffset:], r.NTPTime)
binary.BigEndian.PutUint32(packetBody[srRTPOffset:], r.RTPTime)
binary.BigEndian.PutUint32(packetBody[srPacketCountOffset:], r.PacketCount)
binary.BigEndian.PutUint32(packetBody[srOctetCountOffset:], r.OctetCount)
offset := srHeaderLength
for _, rp := range r.Reports {
data, err := rp.Marshal()
if err != nil {
return nil, err
}
copy(packetBody[offset:], data)
offset += receptionReportLength
}
if len(r.Reports) > countMax {
return nil, errTooManyReports
}
copy(packetBody[offset:], r.ProfileExtensions)
hData, err := r.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the SenderReport from binary
func (r *SenderReport) Unmarshal(rawPacket []byte) error {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| RC | PT=SR=200 | length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SSRC of sender |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* sender | NTP timestamp, most significant word |
* info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | NTP timestamp, least significant word |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | RTP timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's packet count |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | sender's octet count |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_1 (SSRC of first source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 1 | fraction lost | cumulative number of packets lost |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | extended highest sequence number received |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | interarrival jitter |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | last SR (LSR) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | delay since last SR (DLSR) |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* report | SSRC_2 (SSRC of second source) |
* block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* 2 : ... :
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | profile-specific extensions |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < (headerLength + srHeaderLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeSenderReport {
return errWrongType
}
packetBody := rawPacket[headerLength:]
r.SSRC = binary.BigEndian.Uint32(packetBody[srSSRCOffset:])
r.NTPTime = binary.BigEndian.Uint64(packetBody[srNTPOffset:])
r.RTPTime = binary.BigEndian.Uint32(packetBody[srRTPOffset:])
r.PacketCount = binary.BigEndian.Uint32(packetBody[srPacketCountOffset:])
r.OctetCount = binary.BigEndian.Uint32(packetBody[srOctetCountOffset:])
offset := srReportOffset
for i := 0; i < int(h.Count); i++ {
rrEnd := offset + receptionReportLength
if rrEnd > len(packetBody) {
return errPacketTooShort
}
rrBody := packetBody[offset : offset+receptionReportLength]
offset = rrEnd
var rr ReceptionReport
if err := rr.Unmarshal(rrBody); err != nil {
return err
}
r.Reports = append(r.Reports, rr)
}
if offset < len(packetBody) {
r.ProfileExtensions = packetBody[offset:]
}
if uint8(len(r.Reports)) != h.Count {
return errInvalidHeader
}
return nil
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (r *SenderReport) DestinationSSRC() []uint32 {
out := make([]uint32, len(r.Reports)+1)
for i, v := range r.Reports {
out[i] = v.SSRC
}
out[len(r.Reports)] = r.SSRC
return out
}
func (r *SenderReport) len() int {
repsLength := 0
for _, rep := range r.Reports {
repsLength += rep.len()
}
return headerLength + srHeaderLength + repsLength + len(r.ProfileExtensions)
}
// Header returns the Header associated with this packet.
func (r *SenderReport) Header() Header {
return Header{
Count: uint8(len(r.Reports)),
Type: TypeSenderReport,
Length: uint16((r.len() / 4) - 1),
}
}
func (r SenderReport) String() string {
out := fmt.Sprintf("SenderReport from %x\n", r.SSRC)
out += fmt.Sprintf("\tNTPTime:\t%d\n", r.NTPTime)
out += fmt.Sprintf("\tRTPTIme:\t%d\n", r.RTPTime)
out += fmt.Sprintf("\tPacketCount:\t%d\n", r.PacketCount)
out += fmt.Sprintf("\tOctetCount:\t%d\n", r.OctetCount)
out += "\tSSRC \tLost\tLastSequence\n"
for _, i := range r.Reports {
out += fmt.Sprintf("\t%x\t%d/%d\t%d\n", i.SSRC, i.FractionLost, i.TotalLost, i.LastSequenceNumber)
}
out += fmt.Sprintf("\tProfile Extension Data: %v\n", r.ProfileExtensions)
return out
}

View file

@ -0,0 +1,115 @@
package rtcp
import (
"encoding/binary"
"fmt"
"math"
)
// SLIEntry represents a single entry to the SLI packet's
// list of lost slices.
type SLIEntry struct {
// ID of first lost slice
First uint16
// Number of lost slices
Number uint16
// ID of related picture
Picture uint8
}
// The SliceLossIndication packet informs the encoder about the loss of a picture slice
type SliceLossIndication struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
SLI []SLIEntry
}
var _ Packet = (*SliceLossIndication)(nil) // assert is a Packet
const (
sliLength = 2
sliOffset = 8
)
// Marshal encodes the SliceLossIndication in binary
func (p SliceLossIndication) Marshal() ([]byte, error) {
if len(p.SLI)+sliLength > math.MaxUint8 {
return nil, errTooManyReports
}
rawPacket := make([]byte, sliOffset+(len(p.SLI)*4))
binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)
binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)
for i, s := range p.SLI {
sli := ((uint32(s.First) & 0x1FFF) << 19) |
((uint32(s.Number) & 0x1FFF) << 6) |
(uint32(s.Picture) & 0x3F)
binary.BigEndian.PutUint32(rawPacket[sliOffset+(4*i):], sli)
}
hData, err := p.Header().Marshal()
if err != nil {
return nil, err
}
return append(hData, rawPacket...), nil
}
// Unmarshal decodes the SliceLossIndication from binary
func (p *SliceLossIndication) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if len(rawPacket) < (headerLength + int(4*h.Length)) {
return errPacketTooShort
}
if h.Type != TypeTransportSpecificFeedback || h.Count != FormatSLI {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
for i := headerLength + sliOffset; i < (headerLength + int(h.Length*4)); i += 4 {
sli := binary.BigEndian.Uint32(rawPacket[i:])
p.SLI = append(p.SLI, SLIEntry{
First: uint16((sli >> 19) & 0x1FFF),
Number: uint16((sli >> 6) & 0x1FFF),
Picture: uint8(sli & 0x3F),
})
}
return nil
}
func (p *SliceLossIndication) len() int {
return headerLength + sliOffset + (len(p.SLI) * 4)
}
// Header returns the Header associated with this packet.
func (p *SliceLossIndication) Header() Header {
return Header{
Count: FormatSLI,
Type: TypeTransportSpecificFeedback,
Length: uint16((p.len() / 4) - 1),
}
}
func (p *SliceLossIndication) String() string {
return fmt.Sprintf("SliceLossIndication %x %x %+v", p.SenderSSRC, p.MediaSSRC, p.SLI)
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *SliceLossIndication) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}

View file

@ -0,0 +1,352 @@
package rtcp
import (
"encoding/binary"
"fmt"
)
// SDESType is the item type used in the RTCP SDES control packet.
type SDESType uint8
// RTP SDES item types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5
const (
SDESEnd SDESType = iota // end of SDES list RFC 3550, 6.5
SDESCNAME // canonical name RFC 3550, 6.5.1
SDESName // user name RFC 3550, 6.5.2
SDESEmail // user's electronic mail address RFC 3550, 6.5.3
SDESPhone // user's phone number RFC 3550, 6.5.4
SDESLocation // geographic user location RFC 3550, 6.5.5
SDESTool // name of application or tool RFC 3550, 6.5.6
SDESNote // notice about the source RFC 3550, 6.5.7
SDESPrivate // private extensions RFC 3550, 6.5.8 (not implemented)
)
func (s SDESType) String() string {
switch s {
case SDESEnd:
return "END"
case SDESCNAME:
return "CNAME"
case SDESName:
return "NAME"
case SDESEmail:
return "EMAIL"
case SDESPhone:
return "PHONE"
case SDESLocation:
return "LOC"
case SDESTool:
return "TOOL"
case SDESNote:
return "NOTE"
case SDESPrivate:
return "PRIV"
default:
return string(s)
}
}
const (
sdesSourceLen = 4
sdesTypeLen = 1
sdesTypeOffset = 0
sdesOctetCountLen = 1
sdesOctetCountOffset = 1
sdesMaxOctetCount = (1 << 8) - 1
sdesTextOffset = 2
)
// A SourceDescription (SDES) packet describes the sources in an RTP stream.
type SourceDescription struct {
Chunks []SourceDescriptionChunk
}
var _ Packet = (*SourceDescription)(nil) // assert is a Packet
// Marshal encodes the SourceDescription in binary
func (s SourceDescription) Marshal() ([]byte, error) {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| SC | PT=SDES=202 | length |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_1 |
* 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_2 |
* 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
rawPacket := make([]byte, s.len())
packetBody := rawPacket[headerLength:]
chunkOffset := 0
for _, c := range s.Chunks {
data, err := c.Marshal()
if err != nil {
return nil, err
}
copy(packetBody[chunkOffset:], data)
chunkOffset += len(data)
}
if len(s.Chunks) > countMax {
return nil, errTooManyChunks
}
hData, err := s.Header().Marshal()
if err != nil {
return nil, err
}
copy(rawPacket, hData)
return rawPacket, nil
}
// Unmarshal decodes the SourceDescription from binary
func (s *SourceDescription) Unmarshal(rawPacket []byte) error {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* header |V=2|P| SC | PT=SDES=202 | length |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_1 |
* 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* chunk | SSRC/CSRC_2 |
* 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if h.Type != TypeSourceDescription {
return errWrongType
}
for i := headerLength; i < len(rawPacket); {
var chunk SourceDescriptionChunk
if err := chunk.Unmarshal(rawPacket[i:]); err != nil {
return err
}
s.Chunks = append(s.Chunks, chunk)
i += chunk.len()
}
if len(s.Chunks) != int(h.Count) {
return errInvalidHeader
}
return nil
}
func (s *SourceDescription) len() int {
chunksLength := 0
for _, c := range s.Chunks {
chunksLength += c.len()
}
return headerLength + chunksLength
}
// Header returns the Header associated with this packet.
func (s *SourceDescription) Header() Header {
return Header{
Count: uint8(len(s.Chunks)),
Type: TypeSourceDescription,
Length: uint16((s.len() / 4) - 1),
}
}
// A SourceDescriptionChunk contains items describing a single RTP source
type SourceDescriptionChunk struct {
// The source (ssrc) or contributing source (csrc) identifier this packet describes
Source uint32
Items []SourceDescriptionItem
}
// Marshal encodes the SourceDescriptionChunk in binary
func (s SourceDescriptionChunk) Marshal() ([]byte, error) {
/*
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC/CSRC_1 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
rawPacket := make([]byte, sdesSourceLen)
binary.BigEndian.PutUint32(rawPacket, s.Source)
for _, it := range s.Items {
data, err := it.Marshal()
if err != nil {
return nil, err
}
rawPacket = append(rawPacket, data...)
}
// The list of items in each chunk MUST be terminated by one or more null octets
rawPacket = append(rawPacket, uint8(SDESEnd))
// additional null octets MUST be included if needed to pad until the next 32-bit boundary
rawPacket = append(rawPacket, make([]byte, getPadding(len(rawPacket)))...)
return rawPacket, nil
}
// Unmarshal decodes the SourceDescriptionChunk from binary
func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {
/*
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | SSRC/CSRC_1 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | SDES items |
* | ... |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*/
if len(rawPacket) < (sdesSourceLen + sdesTypeLen) {
return errPacketTooShort
}
s.Source = binary.BigEndian.Uint32(rawPacket)
for i := 4; i < len(rawPacket); {
if pktType := SDESType(rawPacket[i]); pktType == SDESEnd {
return nil
}
var it SourceDescriptionItem
if err := it.Unmarshal(rawPacket[i:]); err != nil {
return err
}
s.Items = append(s.Items, it)
i += it.len()
}
return errPacketTooShort
}
func (s SourceDescriptionChunk) len() int {
len := sdesSourceLen
for _, it := range s.Items {
len += it.len()
}
len += sdesTypeLen // for terminating null octet
// align to 32-bit boundary
len += getPadding(len)
return len
}
// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.
type SourceDescriptionItem struct {
// The type identifier for this item. eg, SDESCNAME for canonical name description.
//
// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.
Type SDESType
// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.
Text string
}
func (s SourceDescriptionItem) len() int {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | CNAME=1 | length | user and domain name ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
return sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text))
}
// Marshal encodes the SourceDescriptionItem in binary
func (s SourceDescriptionItem) Marshal() ([]byte, error) {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | CNAME=1 | length | user and domain name ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if s.Type == SDESEnd {
return nil, errSDESMissingType
}
rawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen)
rawPacket[sdesTypeOffset] = uint8(s.Type)
txtBytes := []byte(s.Text)
octetCount := len(txtBytes)
if octetCount > sdesMaxOctetCount {
return nil, errSDESTextTooLong
}
rawPacket[sdesOctetCountOffset] = uint8(octetCount)
rawPacket = append(rawPacket, txtBytes...)
return rawPacket, nil
}
// Unmarshal decodes the SourceDescriptionItem from binary
func (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error {
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | CNAME=1 | length | user and domain name ...
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
if len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) {
return errPacketTooShort
}
s.Type = SDESType(rawPacket[sdesTypeOffset])
octetCount := int(rawPacket[sdesOctetCountOffset])
if sdesTextOffset+octetCount > len(rawPacket) {
return errPacketTooShort
}
txtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount]
s.Text = string(txtBytes)
return nil
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (s *SourceDescription) DestinationSSRC() []uint32 {
out := make([]uint32, len(s.Chunks))
for i, v := range s.Chunks {
out[i] = v.Source
}
return out
}
func (s *SourceDescription) String() string {
out := "Source Description:\n"
for _, c := range s.Chunks {
out += fmt.Sprintf("\t%x: %s\n", c.Source, c.Items)
}
return out
}

View file

@ -0,0 +1,560 @@
package rtcp
// Author: adwpc
import (
"encoding/binary"
"errors"
"fmt"
"math"
)
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5
// 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=15 | PT=205 | length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of packet sender |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SSRC of media source |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | base sequence number | packet status count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | reference time | fb pkt. count |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | packet chunk |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | packet chunk | recv delta | recv delta |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// . .
// . .
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | recv delta | recv delta | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// for packet status chunk
const (
// type of packet status chunk
TypeTCCRunLengthChunk = 0
TypeTCCStatusVectorChunk = 1
// len of packet status chunk
packetStatusChunkLength = 2
)
// type of packet status symbol and recv delta
const (
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.1
TypeTCCPacketNotReceived = uint16(iota)
TypeTCCPacketReceivedSmallDelta
TypeTCCPacketReceivedLargeDelta
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-7
// see Example 2: "packet received, w/o recv delta"
TypeTCCPacketReceivedWithoutDelta
)
// for status vector chunk
const (
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.4
TypeTCCSymbolSizeOneBit = 0
TypeTCCSymbolSizeTwoBit = 1
// Notice: RFC is wrong: "packet received" (0) and "packet not received" (1)
// if S == TypeTCCSymbolSizeOneBit, symbol list will be: TypeTCCPacketNotReceived TypeTCCPacketReceivedSmallDelta
// if S == TypeTCCSymbolSizeTwoBit, symbol list will be same as above:
)
func numOfBitsOfSymbolSize() map[uint16]uint16 {
return map[uint16]uint16{
TypeTCCSymbolSizeOneBit: 1,
TypeTCCSymbolSizeTwoBit: 2,
}
}
var _ Packet = (*TransportLayerCC)(nil) // assert is a Packet
var (
errPacketStatusChunkLength = errors.New("packet status chunk must be 2 bytes")
errDeltaExceedLimit = errors.New("delta exceed limit")
)
// PacketStatusChunk has two kinds:
// RunLengthChunk and StatusVectorChunk
type PacketStatusChunk interface {
Marshal() ([]byte, error)
Unmarshal(rawPacket []byte) error
}
// RunLengthChunk T=TypeTCCRunLengthChunk
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |T| S | Run Length |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type RunLengthChunk struct {
PacketStatusChunk
// T = TypeTCCRunLengthChunk
Type uint16
// S: type of packet status
// kind: TypeTCCPacketNotReceived or...
PacketStatusSymbol uint16
// RunLength: count of S
RunLength uint16
}
// Marshal ..
func (r RunLengthChunk) Marshal() ([]byte, error) {
chunk := make([]byte, 2)
// append 1 bit '0'
dst, err := setNBitsOfUint16(0, 1, 0, 0)
if err != nil {
return nil, err
}
// append 2 bit PacketStatusSymbol
dst, err = setNBitsOfUint16(dst, 2, 1, r.PacketStatusSymbol)
if err != nil {
return nil, err
}
// append 13 bit RunLength
dst, err = setNBitsOfUint16(dst, 13, 3, r.RunLength)
if err != nil {
return nil, err
}
binary.BigEndian.PutUint16(chunk, dst)
return chunk, nil
}
// Unmarshal ..
func (r *RunLengthChunk) Unmarshal(rawPacket []byte) error {
if len(rawPacket) != packetStatusChunkLength {
return errPacketStatusChunkLength
}
// record type
r.Type = TypeTCCRunLengthChunk
// get PacketStatusSymbol
// r.PacketStatusSymbol = uint16(rawPacket[0] >> 5 & 0x03)
r.PacketStatusSymbol = getNBitsFromByte(rawPacket[0], 1, 2)
// get RunLength
// r.RunLength = uint16(rawPacket[0]&0x1F)*256 + uint16(rawPacket[1])
r.RunLength = getNBitsFromByte(rawPacket[0], 3, 5)<<8 + uint16(rawPacket[1])
return nil
}
// StatusVectorChunk T=typeStatusVecotrChunk
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |T|S| symbol list |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type StatusVectorChunk struct {
PacketStatusChunk
// T = TypeTCCRunLengthChunk
Type uint16
// TypeTCCSymbolSizeOneBit or TypeTCCSymbolSizeTwoBit
SymbolSize uint16
// when SymbolSize = TypeTCCSymbolSizeOneBit, SymbolList is 14*1bit:
// TypeTCCSymbolListPacketReceived or TypeTCCSymbolListPacketNotReceived
// when SymbolSize = TypeTCCSymbolSizeTwoBit, SymbolList is 7*2bit:
// TypeTCCPacketNotReceived TypeTCCPacketReceivedSmallDelta TypeTCCPacketReceivedLargeDelta or typePacketReserved
SymbolList []uint16
}
// Marshal ..
func (r StatusVectorChunk) Marshal() ([]byte, error) {
chunk := make([]byte, 2)
// set first bit '1'
dst, err := setNBitsOfUint16(0, 1, 0, 1)
if err != nil {
return nil, err
}
// set second bit SymbolSize
dst, err = setNBitsOfUint16(dst, 1, 1, r.SymbolSize)
if err != nil {
return nil, err
}
numOfBits := numOfBitsOfSymbolSize()[r.SymbolSize]
// append 14 bit SymbolList
for i, s := range r.SymbolList {
index := numOfBits*uint16(i) + 2
dst, err = setNBitsOfUint16(dst, numOfBits, index, s)
if err != nil {
return nil, err
}
}
binary.BigEndian.PutUint16(chunk, dst)
// set SymbolList(bit8-15)
// chunk[1] = uint8(r.SymbolList) & 0x0f
return chunk, nil
}
// Unmarshal ..
func (r *StatusVectorChunk) Unmarshal(rawPacket []byte) error {
if len(rawPacket) != packetStatusChunkLength {
return errPacketStatusChunkLength
}
r.Type = TypeTCCStatusVectorChunk
r.SymbolSize = getNBitsFromByte(rawPacket[0], 1, 1)
if r.SymbolSize == TypeTCCSymbolSizeOneBit {
for i := uint16(0); i < 6; i++ {
r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[0], 2+i, 1))
}
for i := uint16(0); i < 8; i++ {
r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[1], i, 1))
}
return nil
}
if r.SymbolSize == TypeTCCSymbolSizeTwoBit {
for i := uint16(0); i < 3; i++ {
r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[0], 2+i*2, 2))
}
for i := uint16(0); i < 4; i++ {
r.SymbolList = append(r.SymbolList, getNBitsFromByte(rawPacket[1], i*2, 2))
}
return nil
}
r.SymbolSize = getNBitsFromByte(rawPacket[0], 2, 6)<<8 + uint16(rawPacket[1])
return nil
}
const (
// TypeTCCDeltaScaleFactor https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5
TypeTCCDeltaScaleFactor = 250
)
// RecvDelta are represented as multiples of 250us
// small delta is 1 byte: [063.75]ms = [0, 63750]us = [0, 255]*250us
// big delta is 2 bytes: [-8192.0, 8191.75]ms = [-8192000, 8191750]us = [-32768, 32767]*250us
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#section-3.1.5
type RecvDelta struct {
Type uint16
// us
Delta int64
}
// Marshal ..
func (r RecvDelta) Marshal() ([]byte, error) {
delta := r.Delta / TypeTCCDeltaScaleFactor
// small delta
if r.Type == TypeTCCPacketReceivedSmallDelta && delta >= 0 && delta <= math.MaxUint8 {
deltaChunk := make([]byte, 1)
deltaChunk[0] = byte(delta)
return deltaChunk, nil
}
// big delta
if r.Type == TypeTCCPacketReceivedLargeDelta && delta >= math.MinInt16 && delta <= math.MaxInt16 {
deltaChunk := make([]byte, 2)
binary.BigEndian.PutUint16(deltaChunk, uint16(delta))
return deltaChunk, nil
}
// overflow
return nil, errDeltaExceedLimit
}
// Unmarshal ..
func (r *RecvDelta) Unmarshal(rawPacket []byte) error {
chunkLen := len(rawPacket)
// must be 1 or 2 bytes
if chunkLen != 1 && chunkLen != 2 {
return errDeltaExceedLimit
}
if chunkLen == 1 {
r.Type = TypeTCCPacketReceivedSmallDelta
r.Delta = TypeTCCDeltaScaleFactor * int64(rawPacket[0])
return nil
}
r.Type = TypeTCCPacketReceivedLargeDelta
r.Delta = TypeTCCDeltaScaleFactor * int64(int16(binary.BigEndian.Uint16(rawPacket)))
return nil
}
const (
// the offset after header
baseSequenceNumberOffset = 8
packetStatusCountOffset = 10
referenceTimeOffset = 12
fbPktCountOffset = 15
packetChunkOffset = 16
)
// TransportLayerCC for sender-BWE
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01#page-5
type TransportLayerCC struct {
// header
Header Header
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
// Transport wide sequence of rtp extension
BaseSequenceNumber uint16
// PacketStatusCount
PacketStatusCount uint16
// ReferenceTime
ReferenceTime uint32
// FbPktCount
FbPktCount uint8
// PacketChunks
PacketChunks []PacketStatusChunk
// RecvDeltas
RecvDeltas []*RecvDelta
}
// Header returns the Header associated with this packet.
// func (t *TransportLayerCC) Header() Header {
// return t.Header
// return Header{
// Padding: true,
// Count: FormatTCC,
// Type: TypeTCCTransportSpecificFeedback,
// // https://tools.ietf.org/html/rfc4585#page-33
// Length: uint16((t.len() / 4) - 1),
// }
// }
func (t *TransportLayerCC) packetLen() uint16 {
n := uint16(headerLength + packetChunkOffset + len(t.PacketChunks)*2)
for _, d := range t.RecvDeltas {
delta := d.Delta / TypeTCCDeltaScaleFactor
// small delta
if delta >= 0 && delta <= math.MaxUint8 {
n++
} else {
n += 2
}
}
return n
}
// Len return total bytes with padding
func (t *TransportLayerCC) Len() uint16 {
n := t.packetLen()
// has padding
if n%4 != 0 {
n = (n/4 + 1) * 4
}
return n
}
func (t TransportLayerCC) String() string {
out := fmt.Sprintf("TransportLayerCC:\n\tHeader %v\n", t.Header)
out += fmt.Sprintf("TransportLayerCC:\n\tSender Ssrc %d\n", t.SenderSSRC)
out += fmt.Sprintf("\tMedia Ssrc %d\n", t.MediaSSRC)
out += fmt.Sprintf("\tBase Sequence Number %d\n", t.BaseSequenceNumber)
out += fmt.Sprintf("\tStatus Count %d\n", t.PacketStatusCount)
out += fmt.Sprintf("\tReference Time %d\n", t.ReferenceTime)
out += fmt.Sprintf("\tFeedback Packet Count %d\n", t.FbPktCount)
out += "\tPacketChunks "
for _, chunk := range t.PacketChunks {
out += fmt.Sprintf("%+v ", chunk)
}
out += "\n\tRecvDeltas "
for _, delta := range t.RecvDeltas {
out += fmt.Sprintf("%+v ", delta)
}
out += "\n"
return out
}
// Marshal encodes the TransportLayerCC in binary
func (t TransportLayerCC) Marshal() ([]byte, error) {
header, err := t.Header.Marshal()
if err != nil {
return nil, err
}
payload := make([]byte, t.Len()-headerLength)
binary.BigEndian.PutUint32(payload, t.SenderSSRC)
binary.BigEndian.PutUint32(payload[4:], t.MediaSSRC)
binary.BigEndian.PutUint16(payload[baseSequenceNumberOffset:], t.BaseSequenceNumber)
binary.BigEndian.PutUint16(payload[packetStatusCountOffset:], t.PacketStatusCount)
ReferenceTimeAndFbPktCount := appendNBitsToUint32(0, 24, t.ReferenceTime)
ReferenceTimeAndFbPktCount = appendNBitsToUint32(ReferenceTimeAndFbPktCount, 8, uint32(t.FbPktCount))
binary.BigEndian.PutUint32(payload[referenceTimeOffset:], ReferenceTimeAndFbPktCount)
for i, chunk := range t.PacketChunks {
b, err := chunk.Marshal()
if err != nil {
return nil, err
}
copy(payload[packetChunkOffset+i*2:], b)
}
recvDeltaOffset := packetChunkOffset + len(t.PacketChunks)*2
var i int
for _, delta := range t.RecvDeltas {
b, err := delta.Marshal()
if err == nil {
copy(payload[recvDeltaOffset+i:], b)
i++
if delta.Type == TypeTCCPacketReceivedLargeDelta {
i++
}
}
}
if t.Header.Padding {
payload[len(payload)-1] = uint8(t.Len() - t.packetLen())
}
return append(header, payload...), nil
}
// Unmarshal ..
func (t *TransportLayerCC) Unmarshal(rawPacket []byte) error { //nolint:gocognit
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
if err := t.Header.Unmarshal(rawPacket); err != nil {
return err
}
// https://tools.ietf.org/html/rfc4585#page-33
// header's length + payload's length
totalLength := 4 * (t.Header.Length + 1)
if totalLength <= headerLength+packetChunkOffset {
return errPacketTooShort
}
if len(rawPacket) < int(totalLength) {
return errPacketTooShort
}
if t.Header.Type != TypeTransportSpecificFeedback || t.Header.Count != FormatTCC {
return errWrongType
}
t.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
t.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
t.BaseSequenceNumber = binary.BigEndian.Uint16(rawPacket[headerLength+baseSequenceNumberOffset:])
t.PacketStatusCount = binary.BigEndian.Uint16(rawPacket[headerLength+packetStatusCountOffset:])
t.ReferenceTime = get24BitsFromBytes(rawPacket[headerLength+referenceTimeOffset : headerLength+referenceTimeOffset+3])
t.FbPktCount = rawPacket[headerLength+fbPktCountOffset]
packetStatusPos := uint16(headerLength + packetChunkOffset)
var processedPacketNum uint16
for processedPacketNum < t.PacketStatusCount {
if packetStatusPos+packetStatusChunkLength >= totalLength {
return errPacketTooShort
}
typ := getNBitsFromByte(rawPacket[packetStatusPos : packetStatusPos+1][0], 0, 1)
var iPacketStatus PacketStatusChunk
switch typ {
case TypeTCCRunLengthChunk:
packetStatus := &RunLengthChunk{Type: typ}
iPacketStatus = packetStatus
err := packetStatus.Unmarshal(rawPacket[packetStatusPos : packetStatusPos+2])
if err != nil {
return err
}
packetNumberToProcess := min(t.PacketStatusCount-processedPacketNum, packetStatus.RunLength)
if packetStatus.PacketStatusSymbol == TypeTCCPacketReceivedSmallDelta ||
packetStatus.PacketStatusSymbol == TypeTCCPacketReceivedLargeDelta {
for j := uint16(0); j < packetNumberToProcess; j++ {
t.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: packetStatus.PacketStatusSymbol})
}
}
processedPacketNum += packetNumberToProcess
case TypeTCCStatusVectorChunk:
packetStatus := &StatusVectorChunk{Type: typ}
iPacketStatus = packetStatus
err := packetStatus.Unmarshal(rawPacket[packetStatusPos : packetStatusPos+2])
if err != nil {
return err
}
if packetStatus.SymbolSize == TypeTCCSymbolSizeOneBit {
for j := 0; j < len(packetStatus.SymbolList); j++ {
if packetStatus.SymbolList[j] == TypeTCCPacketReceivedSmallDelta {
t.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: TypeTCCPacketReceivedSmallDelta})
}
}
}
if packetStatus.SymbolSize == TypeTCCSymbolSizeTwoBit {
for j := 0; j < len(packetStatus.SymbolList); j++ {
if packetStatus.SymbolList[j] == TypeTCCPacketReceivedSmallDelta || packetStatus.SymbolList[j] == TypeTCCPacketReceivedLargeDelta {
t.RecvDeltas = append(t.RecvDeltas, &RecvDelta{Type: packetStatus.SymbolList[j]})
}
}
}
processedPacketNum += uint16(len(packetStatus.SymbolList))
}
packetStatusPos += packetStatusChunkLength
t.PacketChunks = append(t.PacketChunks, iPacketStatus)
}
recvDeltasPos := packetStatusPos
for _, delta := range t.RecvDeltas {
if recvDeltasPos >= totalLength {
return errPacketTooShort
}
if delta.Type == TypeTCCPacketReceivedSmallDelta {
err := delta.Unmarshal(rawPacket[recvDeltasPos : recvDeltasPos+1])
if err != nil {
return err
}
recvDeltasPos++
}
if delta.Type == TypeTCCPacketReceivedLargeDelta {
err := delta.Unmarshal(rawPacket[recvDeltasPos : recvDeltasPos+2])
if err != nil {
return err
}
recvDeltasPos += 2
}
}
return nil
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (t TransportLayerCC) DestinationSSRC() []uint32 {
return []uint32{t.MediaSSRC}
}
func min(x, y uint16) uint16 {
if x < y {
return x
}
return y
}

View file

@ -0,0 +1,174 @@
package rtcp
import (
"encoding/binary"
"fmt"
"math"
)
// PacketBitmap shouldn't be used like a normal integral,
// so it's type is masked here. Access it with PacketList().
type PacketBitmap uint16
// NackPair is a wire-representation of a collection of
// Lost RTP packets
type NackPair struct {
// ID of lost packets
PacketID uint16
// Bitmask of following lost packets
LostPackets PacketBitmap
}
// The TransportLayerNack packet informs the encoder about the loss of a transport packet
// IETF RFC 4585, Section 6.2.1
// https://tools.ietf.org/html/rfc4585#section-6.2.1
type TransportLayerNack struct {
// SSRC of sender
SenderSSRC uint32
// SSRC of the media source
MediaSSRC uint32
Nacks []NackPair
}
var _ Packet = (*TransportLayerNack)(nil) // assert is a Packet
// NackPairsFromSequenceNumbers generates a slice of NackPair from a list of SequenceNumbers
// This handles generating the proper values for PacketID/LostPackets
func NackPairsFromSequenceNumbers(sequenceNumbers []uint16) (pairs []NackPair) {
if len(sequenceNumbers) == 0 {
return []NackPair{}
}
nackPair := &NackPair{PacketID: sequenceNumbers[0]}
for i := 1; i < len(sequenceNumbers); i++ {
m := sequenceNumbers[i]
if m-nackPair.PacketID > 16 {
pairs = append(pairs, *nackPair)
nackPair = &NackPair{PacketID: m}
continue
}
nackPair.LostPackets |= 1 << (m - nackPair.PacketID - 1)
}
pairs = append(pairs, *nackPair)
return
}
// Range calls f sequentially for each sequence number covered by n.
// If f returns false, Range stops the iteration.
func (n *NackPair) Range(f func(seqno uint16) bool) {
more := f(n.PacketID)
if !more {
return
}
b := n.LostPackets
for i := uint16(0); b != 0; i++ {
if (b & (1 << i)) != 0 {
b &^= (1 << i)
more = f(n.PacketID + i + 1)
if !more {
return
}
}
}
}
// PacketList returns a list of Nack'd packets that's referenced by a NackPair
func (n *NackPair) PacketList() []uint16 {
out := make([]uint16, 0, 17)
n.Range(func(seqno uint16) bool {
out = append(out, seqno)
return true
})
return out
}
const (
tlnLength = 2
nackOffset = 8
)
// Marshal encodes the TransportLayerNack in binary
func (p TransportLayerNack) Marshal() ([]byte, error) {
if len(p.Nacks)+tlnLength > math.MaxUint8 {
return nil, errTooManyReports
}
rawPacket := make([]byte, nackOffset+(len(p.Nacks)*4))
binary.BigEndian.PutUint32(rawPacket, p.SenderSSRC)
binary.BigEndian.PutUint32(rawPacket[4:], p.MediaSSRC)
for i := 0; i < len(p.Nacks); i++ {
binary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i):], p.Nacks[i].PacketID)
binary.BigEndian.PutUint16(rawPacket[nackOffset+(4*i)+2:], uint16(p.Nacks[i].LostPackets))
}
h := p.Header()
hData, err := h.Marshal()
if err != nil {
return nil, err
}
return append(hData, rawPacket...), nil
}
// Unmarshal decodes the TransportLayerNack from binary
func (p *TransportLayerNack) Unmarshal(rawPacket []byte) error {
if len(rawPacket) < (headerLength + ssrcLength) {
return errPacketTooShort
}
var h Header
if err := h.Unmarshal(rawPacket); err != nil {
return err
}
if len(rawPacket) < (headerLength + int(4*h.Length)) {
return errPacketTooShort
}
if h.Type != TypeTransportSpecificFeedback || h.Count != FormatTLN {
return errWrongType
}
p.SenderSSRC = binary.BigEndian.Uint32(rawPacket[headerLength:])
p.MediaSSRC = binary.BigEndian.Uint32(rawPacket[headerLength+ssrcLength:])
for i := headerLength + nackOffset; i < (headerLength + int(h.Length*4)); i += 4 {
p.Nacks = append(p.Nacks, NackPair{
binary.BigEndian.Uint16(rawPacket[i:]),
PacketBitmap(binary.BigEndian.Uint16(rawPacket[i+2:])),
})
}
return nil
}
func (p *TransportLayerNack) len() int {
return headerLength + nackOffset + (len(p.Nacks) * 4)
}
// Header returns the Header associated with this packet.
func (p *TransportLayerNack) Header() Header {
return Header{
Count: FormatTLN,
Type: TypeTransportSpecificFeedback,
Length: uint16((p.len() / 4) - 1),
}
}
func (p TransportLayerNack) String() string {
out := fmt.Sprintf("TransportLayerNack from %x\n", p.SenderSSRC)
out += fmt.Sprintf("\tMedia Ssrc %x\n", p.MediaSSRC)
out += "\tID\tLostPackets\n"
for _, i := range p.Nacks {
out += fmt.Sprintf("\t%d\t%b\n", i.PacketID, i.LostPackets)
}
return out
}
// DestinationSSRC returns an array of SSRC values that this packet refers to.
func (p *TransportLayerNack) DestinationSSRC() []uint32 {
return []uint32{p.MediaSSRC}
}

View file

@ -0,0 +1,38 @@
package rtcp
// getPadding Returns the padding required to make the length a multiple of 4
func getPadding(len int) int {
if len%4 == 0 {
return 0
}
return 4 - (len % 4)
}
// setNBitsOfUint16 will truncate the value to size, left-shift to startIndex position and set
func setNBitsOfUint16(src, size, startIndex, val uint16) (uint16, error) {
if startIndex+size > 16 {
return 0, errInvalidSizeOrStartIndex
}
// truncate val to size bits
val &= (1 << size) - 1
return src | (val << (16 - size - startIndex)), nil
}
// appendBit32 will left-shift and append n bits of val
func appendNBitsToUint32(src, n, val uint32) uint32 {
return (src << n) | (val & (0xFFFFFFFF >> (32 - n)))
}
// getNBit get n bits from 1 byte, begin with a position
func getNBitsFromByte(b byte, begin, n uint16) uint16 {
endShift := 8 - (begin + n)
mask := (0xFF >> begin) & uint8(0xFF<<endShift)
return uint16(b&mask) >> endShift
}
// get24BitFromBytes get 24bits from `[3]byte` slice
func get24BitsFromBytes(b []byte) uint32 {
return uint32(b[0])<<16 + uint32(b[1])<<8 + uint32(b[2])
}