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,53 @@
<h1 align="center">
<br>
Pion RTP
<br>
</h1>
<h4 align="center">A Go implementation of RTP</h4>
<p align="center">
<a href="https://pion.ly"><img src="https://img.shields.io/badge/pion-rtp-gray.svg?longCache=true&colorB=brightgreen" alt="Pion RTP"></a>
<a href="https://sourcegraph.com/github.com/pion/rtp?badge"><img src="https://sourcegraph.com/github.com/pion/rtp/-/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/rtp"><img src="https://travis-ci.org/pion/rtp.svg?branch=master" alt="Build Status"></a>
<a href="https://pkg.go.dev/github.com/pion/rtp"><img src="https://godoc.org/github.com/pion/rtp?status.svg" alt="GoDoc"></a>
<a href="https://codecov.io/gh/pion/rtp"><img src="https://codecov.io/gh/pion/rtp/branch/master/graph/badge.svg" alt="Coverage Status"></a>
<a href="https://goreportcard.com/report/github.com/pion/rtp"><img src="https://goreportcard.com/badge/github.com/pion/rtp" 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>
### 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:
* [John Bradley](https://github.com/kc5nra) - *Original Author*
* [Sean DuBois](https://github.com/Sean-Der) - *Original Author*
* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes*
* [Michael MacDonald](https://github.com/mjmac)
* [Luke Curley](https://github.com/kixelated) *Performance*
* [Antoine Baché](https://github.com/Antonito) *Fixed crashes*
* [Hugo Arregui](https://github.com/hugoArregui)
* [Raphael Derosso Pereira](https://github.com/raphaelpereira)
* [Atsushi Watanabe](https://github.com/at-wat)
* [adwpc](https://github.com/adwpc) *add transport-cc extension*
* [Bao Nguyen](https://github.com/sysbot) *add VP9 noop, bug fixes.
* [Tarrence van As](https://github.com/tarrencev) *add audio level extension*
* [Simone Gotti](https://github.com/sgotti)
* [Guilherme Souza](https://github.com/gqgs)
* [Rob Lofthouse](https://github.com/roblofthouse)
* [Kazuyuki Honda](https://github.com/hakobera)
* [Haiyang Wang](https://github.com/ocean2811)
* [lxb](https://github.com/lxb531)
### License
MIT License - see [LICENSE](LICENSE) for full text

View file

@ -0,0 +1,78 @@
package rtp
import (
"time"
)
const (
absSendTimeExtensionSize = 3
)
// AbsSendTimeExtension is a extension payload format in
// http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
type AbsSendTimeExtension struct {
Timestamp uint64
}
// Marshal serializes the members to buffer.
func (t *AbsSendTimeExtension) Marshal() ([]byte, error) {
return []byte{
byte(t.Timestamp & 0xFF0000 >> 16),
byte(t.Timestamp & 0xFF00 >> 8),
byte(t.Timestamp & 0xFF),
}, nil
}
// Unmarshal parses the passed byte slice and stores the result in the members.
func (t *AbsSendTimeExtension) Unmarshal(rawData []byte) error {
if len(rawData) < absSendTimeExtensionSize {
return errTooSmall
}
t.Timestamp = uint64(rawData[0])<<16 | uint64(rawData[1])<<8 | uint64(rawData[2])
return nil
}
// Estimate absolute send time according to the receive time.
// Note that if the transmission delay is larger than 64 seconds, estimated time will be wrong.
func (t *AbsSendTimeExtension) Estimate(receive time.Time) time.Time {
receiveNTP := toNtpTime(receive)
ntp := receiveNTP&0xFFFFFFC000000000 | (t.Timestamp&0xFFFFFF)<<14
if receiveNTP < ntp {
// Receive time must be always later than send time
ntp -= 0x1000000 << 14
}
return toTime(ntp)
}
// NewAbsSendTimeExtension makes new AbsSendTimeExtension from time.Time.
func NewAbsSendTimeExtension(sendTime time.Time) *AbsSendTimeExtension {
return &AbsSendTimeExtension{
Timestamp: toNtpTime(sendTime) >> 14,
}
}
func toNtpTime(t time.Time) uint64 {
var s uint64
var f uint64
u := uint64(t.UnixNano())
s = u / 1e9
s += 0x83AA7E80 // offset in seconds between unix epoch and ntp epoch
f = u % 1e9
f <<= 32
f /= 1e9
s <<= 32
return s | f
}
func toTime(t uint64) time.Time {
s := t >> 32
f := t & 0xFFFFFFFF
f *= 1e9
f >>= 32
s -= 0x83AA7E80
u := s*1e9 + f
return time.Unix(0, int64(u))
}

View file

@ -0,0 +1,60 @@
package rtp
import (
"errors"
)
const (
// audioLevelExtensionSize One byte header size
audioLevelExtensionSize = 1
)
var errAudioLevelOverflow = errors.New("audio level overflow")
// AudioLevelExtension is a extension payload format described in
// https://tools.ietf.org/html/rfc6464
//
// Implementation based on:
// https://chromium.googlesource.com/external/webrtc/+/e2a017725570ead5946a4ca8235af27470ca0df9/webrtc/modules/rtp_rtcp/source/rtp_header_extensions.cc#49
//
// One byte format:
// 0 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=0 |V| level |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Two byte format:
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | len=1 |V| level | 0 (pad) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type AudioLevelExtension struct {
Level uint8
Voice bool
}
// Marshal serializes the members to buffer
func (a *AudioLevelExtension) Marshal() ([]byte, error) {
if a.Level > 127 {
return nil, errAudioLevelOverflow
}
voice := uint8(0x00)
if a.Voice {
voice = 0x80
}
buf := make([]byte, audioLevelExtensionSize)
buf[0] = voice | a.Level
return buf, nil
}
// Unmarshal parses the passed byte slice and stores the result in the members
func (a *AudioLevelExtension) Unmarshal(rawData []byte) error {
if len(rawData) < audioLevelExtensionSize {
return errTooSmall
}
a.Level = rawData[0] & 0x7F
a.Voice = rawData[0]&0x80 != 0
return nil
}

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,2 @@
// Package codecs implements codec specific RTP payloader/depayloaders
package codecs

View file

@ -0,0 +1,8 @@
package codecs
func min(a, b int) int {
if a < b {
return a
}
return b
}

View file

@ -0,0 +1,11 @@
package codecs
import "errors"
var (
errShortPacket = errors.New("packet is not large enough")
errNilPacket = errors.New("invalid nil packet")
errTooManyPDiff = errors.New("too many PDiff")
errTooManySpatialLayers = errors.New("too many spatial layers")
errUnhandledNALUType = errors.New("NALU Type is unhandled")
)

View file

@ -0,0 +1,22 @@
package codecs
// G711Payloader payloads G711 packets
type G711Payloader struct{}
// Payload fragments an G711 packet across one or more byte arrays
func (p *G711Payloader) Payload(mtu int, payload []byte) [][]byte {
var out [][]byte
if payload == nil || mtu <= 0 {
return out
}
for len(payload) > mtu {
o := make([]byte, mtu)
copy(o, payload[:mtu])
payload = payload[mtu:]
out = append(out, o)
}
o := make([]byte, len(payload))
copy(o, payload)
return append(out, o)
}

View file

@ -0,0 +1,22 @@
package codecs
// G722Payloader payloads G722 packets
type G722Payloader struct{}
// Payload fragments an G722 packet across one or more byte arrays
func (p *G722Payloader) Payload(mtu int, payload []byte) [][]byte {
var out [][]byte
if payload == nil || mtu <= 0 {
return out
}
for len(payload) > mtu {
o := make([]byte, mtu)
copy(o, payload[:mtu])
payload = payload[mtu:]
out = append(out, o)
}
o := make([]byte, len(payload))
copy(o, payload)
return append(out, o)
}

View file

@ -0,0 +1,205 @@
package codecs
import (
"encoding/binary"
"fmt"
)
// H264Payloader payloads H264 packets
type H264Payloader struct{}
const (
stapaNALUType = 24
fuaNALUType = 28
fuaHeaderSize = 2
stapaHeaderSize = 1
stapaNALULengthSize = 2
naluTypeBitmask = 0x1F
naluRefIdcBitmask = 0x60
fuaStartBitmask = 0x80
)
func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} }
func emitNalus(nals []byte, emit func([]byte)) {
nextInd := func(nalu []byte, start int) (indStart int, indLen int) {
zeroCount := 0
for i, b := range nalu[start:] {
if b == 0 {
zeroCount++
continue
} else if b == 1 {
if zeroCount >= 2 {
return start + i - zeroCount, zeroCount + 1
}
}
zeroCount = 0
}
return -1, -1
}
nextIndStart, nextIndLen := nextInd(nals, 0)
if nextIndStart == -1 {
emit(nals)
} else {
for nextIndStart != -1 {
prevStart := nextIndStart + nextIndLen
nextIndStart, nextIndLen = nextInd(nals, prevStart)
if nextIndStart != -1 {
emit(nals[prevStart:nextIndStart])
} else {
// Emit until end of stream, no end indicator found
emit(nals[prevStart:])
}
}
}
}
// Payload fragments a H264 packet across one or more byte arrays
func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte {
var payloads [][]byte
if len(payload) == 0 {
return payloads
}
emitNalus(payload, func(nalu []byte) {
if len(nalu) == 0 {
return
}
naluType := nalu[0] & naluTypeBitmask
naluRefIdc := nalu[0] & naluRefIdcBitmask
if naluType == 9 || naluType == 12 {
return
}
// Single NALU
if len(nalu) <= mtu {
out := make([]byte, len(nalu))
copy(out, nalu)
payloads = append(payloads, out)
return
}
// FU-A
maxFragmentSize := mtu - fuaHeaderSize
// The FU payload consists of fragments of the payload of the fragmented
// NAL unit so that if the fragmentation unit payloads of consecutive
// FUs are sequentially concatenated, the payload of the fragmented NAL
// unit can be reconstructed. The NAL unit type octet of the fragmented
// NAL unit is not included as such in the fragmentation unit payload,
// but rather the information of the NAL unit type octet of the
// fragmented NAL unit is conveyed in the F and NRI fields of the FU
// indicator octet of the fragmentation unit and in the type field of
// the FU header. An FU payload MAY have any number of octets and MAY
// be empty.
naluData := nalu
// According to the RFC, the first octet is skipped due to redundant information
naluDataIndex := 1
naluDataLength := len(nalu) - naluDataIndex
naluDataRemaining := naluDataLength
if min(maxFragmentSize, naluDataRemaining) <= 0 {
return
}
for naluDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, naluDataRemaining)
out := make([]byte, fuaHeaderSize+currentFragmentSize)
// +---------------+
// |0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+
// |F|NRI| Type |
// +---------------+
out[0] = fuaNALUType
out[0] |= naluRefIdc
// +---------------+
// |0|1|2|3|4|5|6|7|
// +-+-+-+-+-+-+-+-+
// |S|E|R| Type |
// +---------------+
out[1] = naluType
if naluDataRemaining == naluDataLength {
// Set start bit
out[1] |= 1 << 7
} else if naluDataRemaining-currentFragmentSize == 0 {
// Set end bit
out[1] |= 1 << 6
}
copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize])
payloads = append(payloads, out)
naluDataRemaining -= currentFragmentSize
naluDataIndex += currentFragmentSize
}
})
return payloads
}
// H264Packet represents the H264 header that is stored in the payload of an RTP Packet
type H264Packet struct {
}
// Unmarshal parses the passed byte slice and stores the result in the H264Packet this method is called upon
func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) {
if payload == nil {
return nil, errNilPacket
} else if len(payload) <= 2 {
return nil, fmt.Errorf("%w: %d <= 2", errShortPacket, len(payload))
}
// NALU Types
// https://tools.ietf.org/html/rfc6184#section-5.4
naluType := payload[0] & naluTypeBitmask
switch {
case naluType > 0 && naluType < 24:
return append(annexbNALUStartCode(), payload...), nil
case naluType == stapaNALUType:
currOffset := int(stapaHeaderSize)
result := []byte{}
for currOffset < len(payload) {
naluSize := int(binary.BigEndian.Uint16(payload[currOffset:]))
currOffset += stapaNALULengthSize
if len(payload) < currOffset+naluSize {
return nil, fmt.Errorf("%w STAP-A declared size(%d) is larger than buffer(%d)", errShortPacket, naluSize, len(payload)-currOffset)
}
result = append(result, annexbNALUStartCode()...)
result = append(result, payload[currOffset:currOffset+naluSize]...)
currOffset += naluSize
}
return result, nil
case naluType == fuaNALUType:
if len(payload) < fuaHeaderSize {
return nil, errShortPacket
}
if payload[1]&fuaStartBitmask != 0 {
naluRefIdc := payload[0] & naluRefIdcBitmask
fragmentedNaluType := payload[1] & naluTypeBitmask
// Take a copy of payload since we are mutating it.
payloadCopy := append([]byte{}, payload...)
payloadCopy[fuaHeaderSize-1] = naluRefIdc | fragmentedNaluType
return append(annexbNALUStartCode(), payloadCopy[fuaHeaderSize-1:]...), nil
}
return payload[fuaHeaderSize:], nil
}
return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType)
}

View file

@ -0,0 +1,44 @@
package codecs
// OpusPayloader payloads Opus packets
type OpusPayloader struct{}
// Payload fragments an Opus packet across one or more byte arrays
func (p *OpusPayloader) Payload(mtu int, payload []byte) [][]byte {
if payload == nil {
return [][]byte{}
}
out := make([]byte, len(payload))
copy(out, payload)
return [][]byte{out}
}
// OpusPacket represents the Opus header that is stored in the payload of an RTP Packet
type OpusPacket struct {
Payload []byte
}
// Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon
func (p *OpusPacket) Unmarshal(packet []byte) ([]byte, error) {
if packet == nil {
return nil, errNilPacket
} else if len(packet) == 0 {
return nil, errShortPacket
}
p.Payload = packet
return packet, nil
}
// OpusPartitionHeadChecker checks Opus partition head
type OpusPartitionHeadChecker struct{}
// IsPartitionHead checks whether if this is a head of the Opus partition
func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool {
p := &OpusPacket{}
if _, err := p.Unmarshal(packet); err != nil {
return false
}
return true
}

View file

@ -0,0 +1,143 @@
package codecs
// VP8Payloader payloads VP8 packets
type VP8Payloader struct{}
const (
vp8HeaderSize = 1
)
// Payload fragments a VP8 packet across one or more byte arrays
func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte {
/*
* https://tools.ietf.org/html/rfc7741#section-4.2
*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |X|R|N|S|R| PID | (REQUIRED)
* +-+-+-+-+-+-+-+-+
* X: |I|L|T|K| RSV | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* I: |M| PictureID | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* L: | TL0PICIDX | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* T/K: |TID|Y| KEYIDX | (OPTIONAL)
* +-+-+-+-+-+-+-+-+
* S: Start of VP8 partition. SHOULD be set to 1 when the first payload
* octet of the RTP packet is the beginning of a new VP8 partition,
* and MUST NOT be 1 otherwise. The S bit MUST be set to 1 for the
* first packet of each encoded frame.
*/
maxFragmentSize := mtu - vp8HeaderSize
payloadData := payload
payloadDataRemaining := len(payload)
payloadDataIndex := 0
var payloads [][]byte
// Make sure the fragment/payload size is correct
if min(maxFragmentSize, payloadDataRemaining) <= 0 {
return payloads
}
for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
out := make([]byte, vp8HeaderSize+currentFragmentSize)
if payloadDataRemaining == len(payload) {
out[0] = 0x10
}
copy(out[vp8HeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
return payloads
}
// VP8Packet represents the VP8 header that is stored in the payload of an RTP Packet
type VP8Packet struct {
// Required Header
X uint8 /* extended controlbits present */
N uint8 /* (non-reference frame) when set to 1 this frame can be discarded */
S uint8 /* start of VP8 partition */
PID uint8 /* partition index */
// Optional Header
I uint8 /* 1 if PictureID is present */
L uint8 /* 1 if TL0PICIDX is present */
T uint8 /* 1 if TID is present */
K uint8 /* 1 if KEYIDX is present */
PictureID uint16 /* 8 or 16 bits, picture ID */
TL0PICIDX uint8 /* 8 bits temporal level zero index */
Payload []byte
}
// Unmarshal parses the passed byte slice and stores the result in the VP8Packet this method is called upon
func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) {
if payload == nil {
return nil, errNilPacket
}
payloadLen := len(payload)
if payloadLen < 4 {
return nil, errShortPacket
}
payloadIndex := 0
p.X = (payload[payloadIndex] & 0x80) >> 7
p.N = (payload[payloadIndex] & 0x20) >> 5
p.S = (payload[payloadIndex] & 0x10) >> 4
p.PID = payload[payloadIndex] & 0x07
payloadIndex++
if p.X == 1 {
p.I = (payload[payloadIndex] & 0x80) >> 7
p.L = (payload[payloadIndex] & 0x40) >> 6
p.T = (payload[payloadIndex] & 0x20) >> 5
p.K = (payload[payloadIndex] & 0x10) >> 4
payloadIndex++
}
if p.I == 1 { // PID present?
if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit
payloadIndex += 2
} else {
payloadIndex++
}
}
if p.L == 1 {
payloadIndex++
}
if p.T == 1 || p.K == 1 {
payloadIndex++
}
if payloadIndex >= payloadLen {
return nil, errShortPacket
}
p.Payload = payload[payloadIndex:]
return p.Payload, nil
}
// VP8PartitionHeadChecker checks VP8 partition head
type VP8PartitionHeadChecker struct{}
// IsPartitionHead checks whether if this is a head of the VP8 partition
func (*VP8PartitionHeadChecker) IsPartitionHead(packet []byte) bool {
p := &VP8Packet{}
if _, err := p.Unmarshal(packet); err != nil {
return false
}
return p.S == 1
}

View file

@ -0,0 +1,385 @@
package codecs
import (
"github.com/pion/randutil"
)
// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals
// VP9Payloader payloads VP9 packets
type VP9Payloader struct {
pictureID uint16
initialized bool
// InitialPictureIDFn is a function that returns random initial picture ID.
InitialPictureIDFn func() uint16
}
const (
vp9HeaderSize = 3 // Flexible mode 15 bit picture ID
maxSpatialLayers = 5
maxVP9RefPics = 3
)
// Payload fragments an VP9 packet across one or more byte arrays
func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte {
/*
* https://www.ietf.org/id/draft-ietf-payload-vp9-10.txt
*
* Flexible mode (F=1)
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |I|P|L|F|B|E|V|-| (REQUIRED)
* +-+-+-+-+-+-+-+-+
* I: |M| PICTURE ID | (REQUIRED)
* +-+-+-+-+-+-+-+-+
* M: | EXTENDED PID | (RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)
* +-+-+-+-+-+-+-+-+ -\
* P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times
* +-+-+-+-+-+-+-+-+ -/
* V: | SS |
* | .. |
* +-+-+-+-+-+-+-+-+
*
* Non-flexible mode (F=0)
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |I|P|L|F|B|E|V|-| (REQUIRED)
* +-+-+-+-+-+-+-+-+
* I: |M| PICTURE ID | (RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* M: | EXTENDED PID | (RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* | TL0PICIDX | (CONDITIONALLY REQUIRED)
* +-+-+-+-+-+-+-+-+
* V: | SS |
* | .. |
* +-+-+-+-+-+-+-+-+
*/
if !p.initialized {
if p.InitialPictureIDFn == nil {
p.InitialPictureIDFn = func() uint16 {
return uint16(globalMathRandomGenerator.Intn(0x7FFF))
}
}
p.pictureID = p.InitialPictureIDFn() & 0x7FFF
p.initialized = true
}
if payload == nil {
return [][]byte{}
}
maxFragmentSize := mtu - vp9HeaderSize
payloadDataRemaining := len(payload)
payloadDataIndex := 0
if min(maxFragmentSize, payloadDataRemaining) <= 0 {
return [][]byte{}
}
var payloads [][]byte
for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
out := make([]byte, vp9HeaderSize+currentFragmentSize)
out[0] = 0x90 // F=1 I=1
if payloadDataIndex == 0 {
out[0] |= 0x08 // B=1
}
if payloadDataRemaining == currentFragmentSize {
out[0] |= 0x04 // E=1
}
out[1] = byte(p.pictureID>>8) | 0x80
out[2] = byte(p.pictureID)
copy(out[vp9HeaderSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
p.pictureID++
if p.pictureID >= 0x8000 {
p.pictureID = 0
}
return payloads
}
// VP9Packet represents the VP9 header that is stored in the payload of an RTP Packet
type VP9Packet struct {
// Required header
I bool // PictureID is present
P bool // Inter-picture predicted frame
L bool // Layer indices is present
F bool // Flexible mode
B bool // Start of a frame
E bool // End of a frame
V bool // Scalability structure (SS) data present
// Recommended headers
PictureID uint16 // 7 or 16 bits, picture ID
// Conditionally recommended headers
TID uint8 // Temporal layer ID
U bool // Switching up point
SID uint8 // Spatial layer ID
D bool // Inter-layer dependency used
// Conditionally required headers
PDiff []uint8 // Reference index (F=1)
TL0PICIDX uint8 // Temporal layer zero index (F=0)
// Scalability structure headers
NS uint8 // N_S + 1 indicates the number of spatial layers present in the VP9 stream
Y bool // Each spatial layer's frame resolution present
G bool // PG description present flag.
NG uint8 // N_G indicates the number of pictures in a Picture Group (PG)
Width []uint16
Height []uint16
PGTID []uint8 // Temporal layer ID of pictures in a Picture Group
PGU []bool // Switching up point of pictures in a Picture Group
PGPDiff [][]uint8 // Reference indecies of pictures in a Picture Group
Payload []byte
}
// Unmarshal parses the passed byte slice and stores the result in the VP9Packet this method is called upon
func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) {
if packet == nil {
return nil, errNilPacket
}
if len(packet) < 1 {
return nil, errShortPacket
}
p.I = packet[0]&0x80 != 0
p.P = packet[0]&0x40 != 0
p.L = packet[0]&0x20 != 0
p.F = packet[0]&0x10 != 0
p.B = packet[0]&0x08 != 0
p.E = packet[0]&0x04 != 0
p.V = packet[0]&0x02 != 0
pos := 1
var err error
if p.I {
pos, err = p.parsePictureID(packet, pos)
if err != nil {
return nil, err
}
}
if p.L {
pos, err = p.parseLayerInfo(packet, pos)
if err != nil {
return nil, err
}
}
if p.F && p.P {
pos, err = p.parseRefIndices(packet, pos)
if err != nil {
return nil, err
}
}
if p.V {
pos, err = p.parseSSData(packet, pos)
if err != nil {
return nil, err
}
}
p.Payload = packet[pos:]
return p.Payload, nil
}
// Picture ID:
//
// +-+-+-+-+-+-+-+-+
// I: |M| PICTURE ID | M:0 => picture id is 7 bits.
// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits.
// M: | EXTENDED PID |
// +-+-+-+-+-+-+-+-+
//
func (p *VP9Packet) parsePictureID(packet []byte, pos int) (int, error) {
if len(packet) <= pos {
return pos, errShortPacket
}
p.PictureID = uint16(packet[pos] & 0x7F)
if packet[pos]&0x80 != 0 {
pos++
if len(packet) <= pos {
return pos, errShortPacket
}
p.PictureID = p.PictureID<<8 | uint16(packet[pos])
}
pos++
return pos, nil
}
func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) {
pos, err := p.parseLayerInfoCommon(packet, pos)
if err != nil {
return pos, err
}
if p.F {
return pos, nil
}
return p.parseLayerInfoNonFlexibleMode(packet, pos)
}
// Layer indices (flexible mode):
//
// +-+-+-+-+-+-+-+-+
// L: | T |U| S |D|
// +-+-+-+-+-+-+-+-+
//
func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) {
if len(packet) <= pos {
return pos, errShortPacket
}
p.TID = packet[pos] >> 5
p.U = packet[pos]&0x10 != 0
p.SID = (packet[pos] >> 1) & 0x7
p.D = packet[pos]&0x01 != 0
if p.SID >= maxSpatialLayers {
return pos, errTooManySpatialLayers
}
pos++
return pos, nil
}
// Layer indices (non-flexible mode):
//
// +-+-+-+-+-+-+-+-+
// L: | T |U| S |D|
// +-+-+-+-+-+-+-+-+
// | TL0PICIDX |
// +-+-+-+-+-+-+-+-+
//
func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) {
if len(packet) <= pos {
return pos, errShortPacket
}
p.TL0PICIDX = packet[pos]
pos++
return pos, nil
}
// Reference indices:
//
// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index
// P,F: | P_DIFF |N| up to 3 times has to be specified.
// +-+-+-+-+-+-+-+-+ N=1: An additional P_DIFF follows
// current P_DIFF.
//
func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) {
for {
if len(packet) <= pos {
return pos, errShortPacket
}
p.PDiff = append(p.PDiff, packet[pos]>>1)
if packet[pos]&0x01 == 0 {
break
}
if len(p.PDiff) >= maxVP9RefPics {
return pos, errTooManyPDiff
}
pos++
}
pos++
return pos, nil
}
// Scalability structure (SS):
//
// +-+-+-+-+-+-+-+-+
// V: | N_S |Y|G|-|-|-|
// +-+-+-+-+-+-+-+-+ -|
// Y: | WIDTH | (OPTIONAL) .
// + + .
// | | (OPTIONAL) .
// +-+-+-+-+-+-+-+-+ . N_S + 1 times
// | HEIGHT | (OPTIONAL) .
// + + .
// | | (OPTIONAL) .
// +-+-+-+-+-+-+-+-+ -|
// G: | N_G | (OPTIONAL)
// +-+-+-+-+-+-+-+-+ -|
// N_G: | T |U| R |-|-| (OPTIONAL) .
// +-+-+-+-+-+-+-+-+ -| . N_G times
// | P_DIFF | (OPTIONAL) . R times .
// +-+-+-+-+-+-+-+-+ -| -|
//
func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) {
if len(packet) <= pos {
return pos, errShortPacket
}
p.NS = packet[pos] >> 5
p.Y = packet[pos]&0x10 != 0
p.G = (packet[pos]>>1)&0x7 != 0
pos++
NS := p.NS + 1
p.NG = 0
if p.Y {
p.Width = make([]uint16, NS)
p.Height = make([]uint16, NS)
for i := 0; i < int(NS); i++ {
p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1])
pos += 2
p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1])
pos += 2
}
}
if p.G {
p.NG = packet[pos]
pos++
}
for i := 0; i < int(p.NG); i++ {
p.PGTID = append(p.PGTID, packet[pos]>>5)
p.PGU = append(p.PGU, packet[pos]&0x10 != 0)
R := (packet[pos] >> 2) & 0x3
pos++
p.PGPDiff = append(p.PGPDiff, []uint8{})
for j := 0; j < int(R); j++ {
p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos])
pos++
}
}
return pos, nil
}
// VP9PartitionHeadChecker checks VP9 partition head
type VP9PartitionHeadChecker struct{}
// IsPartitionHead checks whether if this is a head of the VP9 partition
func (*VP9PartitionHeadChecker) IsPartitionHead(packet []byte) bool {
p := &VP9Packet{}
if _, err := p.Unmarshal(packet); err != nil {
return false
}
return p.B
}

View file

@ -0,0 +1,6 @@
package rtp
// Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload
type Depacketizer interface {
Unmarshal(packet []byte) ([]byte, error)
}

View file

@ -0,0 +1,21 @@
package rtp
import (
"errors"
)
var (
errHeaderSizeInsufficient = errors.New("RTP header size insufficient")
errHeaderSizeInsufficientForExtension = errors.New("RTP header size insufficient for extension")
errTooSmall = errors.New("buffer too small")
errHeaderExtensionsNotEnabled = errors.New("h.Extension not enabled")
errHeaderExtensionNotFound = errors.New("extension not found")
errRFC8285OneByteHeaderIDRange = errors.New("header extension id must be between 1 and 14 for RFC 5285 one byte extensions")
errRFC8285OneByteHeaderSize = errors.New("header extension payload must be 16bytes or less for RFC 5285 one byte extensions")
errRFC8285TwoByteHeaderIDRange = errors.New("header extension id must be between 1 and 255 for RFC 5285 two byte extensions")
errRFC8285TwoByteHeaderSize = errors.New("header extension payload must be 255bytes or less for RFC 5285 two byte extensions")
errRFC3550HeaderIDRange = errors.New("header extension id must be 0 for non-RFC 5285 extensions")
)

View file

@ -0,0 +1,5 @@
module github.com/pion/rtp
go 1.13
require github.com/pion/randutil v0.1.0

View file

@ -0,0 +1,2 @@
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=

View file

@ -0,0 +1,490 @@
package rtp
import (
"encoding/binary"
"fmt"
"io"
)
// Extension RTP Header extension
type Extension struct {
id uint8
payload []byte
}
// Header represents an RTP packet header
// NOTE: PayloadOffset is populated by Marshal/Unmarshal and should not be modified
type Header struct {
Version uint8
Padding bool
Extension bool
Marker bool
PayloadOffset int
PayloadType uint8
SequenceNumber uint16
Timestamp uint32
SSRC uint32
CSRC []uint32
ExtensionProfile uint16
Extensions []Extension
}
// Packet represents an RTP Packet
// NOTE: Raw is populated by Marshal/Unmarshal and should not be modified
type Packet struct {
Header
Raw []byte
Payload []byte
}
const (
headerLength = 4
versionShift = 6
versionMask = 0x3
paddingShift = 5
paddingMask = 0x1
extensionShift = 4
extensionMask = 0x1
extensionProfileOneByte = 0xBEDE
extensionProfileTwoByte = 0x1000
extensionIDReserved = 0xF
ccMask = 0xF
markerShift = 7
markerMask = 0x1
ptMask = 0x7F
seqNumOffset = 2
seqNumLength = 2
timestampOffset = 4
timestampLength = 4
ssrcOffset = 8
ssrcLength = 4
csrcOffset = 12
csrcLength = 4
)
// String helps with debugging by printing packet information in a readable way
func (p Packet) String() string {
out := "RTP PACKET:\n"
out += fmt.Sprintf("\tVersion: %v\n", p.Version)
out += fmt.Sprintf("\tMarker: %v\n", p.Marker)
out += fmt.Sprintf("\tPayload Type: %d\n", p.PayloadType)
out += fmt.Sprintf("\tSequence Number: %d\n", p.SequenceNumber)
out += fmt.Sprintf("\tTimestamp: %d\n", p.Timestamp)
out += fmt.Sprintf("\tSSRC: %d (%x)\n", p.SSRC, p.SSRC)
out += fmt.Sprintf("\tPayload Length: %d\n", len(p.Payload))
return out
}
// Unmarshal parses the passed byte slice and stores the result in the Header this method is called upon
func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit
if len(rawPacket) < headerLength {
return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(rawPacket), headerLength)
}
/*
* 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|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | contributing source (CSRC) identifiers |
* | .... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
h.Version = rawPacket[0] >> versionShift & versionMask
h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0
h.Extension = (rawPacket[0] >> extensionShift & extensionMask) > 0
nCSRC := int(rawPacket[0] & ccMask)
if cap(h.CSRC) < nCSRC || h.CSRC == nil {
h.CSRC = make([]uint32, nCSRC)
} else {
h.CSRC = h.CSRC[:nCSRC]
}
currOffset := csrcOffset + (nCSRC * csrcLength)
if len(rawPacket) < currOffset {
return fmt.Errorf("size %d < %d: %w", len(rawPacket), currOffset, errHeaderSizeInsufficient)
}
h.Marker = (rawPacket[1] >> markerShift & markerMask) > 0
h.PayloadType = rawPacket[1] & ptMask
h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength])
h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength])
h.SSRC = binary.BigEndian.Uint32(rawPacket[ssrcOffset : ssrcOffset+ssrcLength])
for i := range h.CSRC {
offset := csrcOffset + (i * csrcLength)
h.CSRC[i] = binary.BigEndian.Uint32(rawPacket[offset:])
}
if h.Extensions != nil {
h.Extensions = h.Extensions[:0]
}
if h.Extension {
if expected := currOffset + 4; len(rawPacket) < expected {
return fmt.Errorf("size %d < %d: %w",
len(rawPacket), expected,
errHeaderSizeInsufficientForExtension,
)
}
h.ExtensionProfile = binary.BigEndian.Uint16(rawPacket[currOffset:])
currOffset += 2
extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4
currOffset += 2
if expected := currOffset + extensionLength; len(rawPacket) < expected {
return fmt.Errorf("size %d < %d: %w",
len(rawPacket), expected,
errHeaderSizeInsufficientForExtension,
)
}
switch h.ExtensionProfile {
// RFC 8285 RTP One Byte Header Extension
case extensionProfileOneByte:
end := currOffset + extensionLength
for currOffset < end {
if rawPacket[currOffset] == 0x00 { // padding
currOffset++
continue
}
extid := rawPacket[currOffset] >> 4
len := int(rawPacket[currOffset]&^0xF0 + 1)
currOffset++
if extid == extensionIDReserved {
break
}
extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]}
h.Extensions = append(h.Extensions, extension)
currOffset += len
}
// RFC 8285 RTP Two Byte Header Extension
case extensionProfileTwoByte:
end := currOffset + extensionLength
for currOffset < end {
if rawPacket[currOffset] == 0x00 { // padding
currOffset++
continue
}
extid := rawPacket[currOffset]
currOffset++
len := int(rawPacket[currOffset])
currOffset++
extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]}
h.Extensions = append(h.Extensions, extension)
currOffset += len
}
default: // RFC3550 Extension
if len(rawPacket) < currOffset+extensionLength {
return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, len(rawPacket), currOffset+extensionLength)
}
extension := Extension{id: 0, payload: rawPacket[currOffset : currOffset+extensionLength]}
h.Extensions = append(h.Extensions, extension)
currOffset += len(h.Extensions[0].payload)
}
}
h.PayloadOffset = currOffset
return nil
}
// Unmarshal parses the passed byte slice and stores the result in the Packet this method is called upon
func (p *Packet) Unmarshal(rawPacket []byte) error {
if err := p.Header.Unmarshal(rawPacket); err != nil {
return err
}
p.Payload = rawPacket[p.PayloadOffset:]
p.Raw = rawPacket
return nil
}
// Marshal serializes the header into bytes.
func (h *Header) Marshal() (buf []byte, err error) {
buf = make([]byte, h.MarshalSize())
n, err := h.MarshalTo(buf)
if err != nil {
return nil, err
}
return buf[:n], nil
}
// MarshalTo serializes the header and writes to the buffer.
func (h *Header) 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|X| CC |M| PT | sequence number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | timestamp |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | synchronization source (SSRC) identifier |
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
* | contributing source (CSRC) identifiers |
* | .... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
size := h.MarshalSize()
if size > len(buf) {
return 0, io.ErrShortBuffer
}
// The first byte contains the version, padding bit, extension bit, and csrc size
buf[0] = (h.Version << versionShift) | uint8(len(h.CSRC))
if h.Padding {
buf[0] |= 1 << paddingShift
}
if h.Extension {
buf[0] |= 1 << extensionShift
}
// The second byte contains the marker bit and payload type.
buf[1] = h.PayloadType
if h.Marker {
buf[1] |= 1 << markerShift
}
binary.BigEndian.PutUint16(buf[2:4], h.SequenceNumber)
binary.BigEndian.PutUint32(buf[4:8], h.Timestamp)
binary.BigEndian.PutUint32(buf[8:12], h.SSRC)
n = 12
for _, csrc := range h.CSRC {
binary.BigEndian.PutUint32(buf[n:n+4], csrc)
n += 4
}
if h.Extension {
extHeaderPos := n
binary.BigEndian.PutUint16(buf[n+0:n+2], h.ExtensionProfile)
n += 4
startExtensionsPos := n
switch h.ExtensionProfile {
// RFC 8285 RTP One Byte Header Extension
case extensionProfileOneByte:
for _, extension := range h.Extensions {
buf[n] = extension.id<<4 | (uint8(len(extension.payload)) - 1)
n++
n += copy(buf[n:], extension.payload)
}
// RFC 8285 RTP Two Byte Header Extension
case extensionProfileTwoByte:
for _, extension := range h.Extensions {
buf[n] = extension.id
n++
buf[n] = uint8(len(extension.payload))
n++
n += copy(buf[n:], extension.payload)
}
default: // RFC3550 Extension
extlen := len(h.Extensions[0].payload)
if extlen%4 != 0 {
// the payload must be in 32-bit words.
return 0, io.ErrShortBuffer
}
n += copy(buf[n:], h.Extensions[0].payload)
}
// calculate extensions size and round to 4 bytes boundaries
extSize := n - startExtensionsPos
roundedExtSize := ((extSize + 3) / 4) * 4
binary.BigEndian.PutUint16(buf[extHeaderPos+2:extHeaderPos+4], uint16(roundedExtSize/4))
// add padding to reach 4 bytes boundaries
for i := 0; i < roundedExtSize-extSize; i++ {
buf[n] = 0
n++
}
}
h.PayloadOffset = n
return n, nil
}
// MarshalSize returns the size of the header once marshaled.
func (h *Header) MarshalSize() int {
// NOTE: Be careful to match the MarshalTo() method.
size := 12 + (len(h.CSRC) * csrcLength)
if h.Extension {
extSize := 4
switch h.ExtensionProfile {
// RFC 8285 RTP One Byte Header Extension
case extensionProfileOneByte:
for _, extension := range h.Extensions {
extSize += 1 + len(extension.payload)
}
// RFC 8285 RTP Two Byte Header Extension
case extensionProfileTwoByte:
for _, extension := range h.Extensions {
extSize += 2 + len(extension.payload)
}
default:
extSize += len(h.Extensions[0].payload)
}
// extensions size must have 4 bytes boundaries
size += ((extSize + 3) / 4) * 4
}
return size
}
// SetExtension sets an RTP header extension
func (h *Header) SetExtension(id uint8, payload []byte) error { //nolint:gocognit
if h.Extension {
switch h.ExtensionProfile {
// RFC 8285 RTP One Byte Header Extension
case extensionProfileOneByte:
if id < 1 || id > 14 {
return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderIDRange, id)
}
if len(payload) > 16 {
return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderSize, len(payload))
}
// RFC 8285 RTP Two Byte Header Extension
case extensionProfileTwoByte:
if id < 1 || id > 255 {
return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id)
}
if len(payload) > 255 {
return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderSize, len(payload))
}
default: // RFC3550 Extension
if id != 0 {
return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id)
}
}
// Update existing if it exists else add new extension
for i, extension := range h.Extensions {
if extension.id == id {
h.Extensions[i].payload = payload
return nil
}
}
h.Extensions = append(h.Extensions, Extension{id: id, payload: payload})
return nil
}
// No existing header extensions
h.Extension = true
switch len := len(payload); {
case len <= 16:
h.ExtensionProfile = extensionProfileOneByte
case len > 16 && len < 256:
h.ExtensionProfile = extensionProfileTwoByte
}
h.Extensions = append(h.Extensions, Extension{id: id, payload: payload})
return nil
}
// GetExtensionIDs returns an extension id array
func (h *Header) GetExtensionIDs() []uint8 {
if !h.Extension {
return nil
}
if len(h.Extensions) == 0 {
return nil
}
ids := make([]uint8, 0, len(h.Extensions))
for _, extension := range h.Extensions {
ids = append(ids, extension.id)
}
return ids
}
// GetExtension returns an RTP header extension
func (h *Header) GetExtension(id uint8) []byte {
if !h.Extension {
return nil
}
for _, extension := range h.Extensions {
if extension.id == id {
return extension.payload
}
}
return nil
}
// DelExtension Removes an RTP Header extension
func (h *Header) DelExtension(id uint8) error {
if !h.Extension {
return errHeaderExtensionsNotEnabled
}
for i, extension := range h.Extensions {
if extension.id == id {
h.Extensions = append(h.Extensions[:i], h.Extensions[i+1:]...)
return nil
}
}
return errHeaderExtensionNotFound
}
// Marshal serializes the packet into bytes.
func (p *Packet) Marshal() (buf []byte, err error) {
buf = make([]byte, p.MarshalSize())
n, err := p.MarshalTo(buf)
if err != nil {
return nil, err
}
return buf[:n], nil
}
// MarshalTo serializes the packet and writes to the buffer.
func (p *Packet) MarshalTo(buf []byte) (n int, err error) {
n, err = p.Header.MarshalTo(buf)
if err != nil {
return 0, err
}
// Make sure the buffer is large enough to hold the packet.
if n+len(p.Payload) > len(buf) {
return 0, io.ErrShortBuffer
}
m := copy(buf[n:], p.Payload)
p.Raw = buf[:n+m]
return n + m, nil
}
// MarshalSize returns the size of the packet once marshaled.
func (p *Packet) MarshalSize() int {
return p.Header.MarshalSize() + len(p.Payload)
}

View file

@ -0,0 +1,91 @@
package rtp
import (
"time"
)
// Payloader payloads a byte array for use as rtp.Packet payloads
type Payloader interface {
Payload(mtu int, payload []byte) [][]byte
}
// Packetizer packetizes a payload
type Packetizer interface {
Packetize(payload []byte, samples uint32) []*Packet
EnableAbsSendTime(value int)
}
type packetizer struct {
MTU int
PayloadType uint8
SSRC uint32
Payloader Payloader
Sequencer Sequencer
Timestamp uint32
ClockRate uint32
extensionNumbers struct { // put extension numbers in here. If they're 0, the extension is disabled (0 is not a legal extension number)
AbsSendTime int // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
}
timegen func() time.Time
}
// NewPacketizer returns a new instance of a Packetizer for a specific payloader
func NewPacketizer(mtu int, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32) Packetizer {
return &packetizer{
MTU: mtu,
PayloadType: pt,
SSRC: ssrc,
Payloader: payloader,
Sequencer: sequencer,
Timestamp: globalMathRandomGenerator.Uint32(),
ClockRate: clockRate,
timegen: time.Now,
}
}
func (p *packetizer) EnableAbsSendTime(value int) {
p.extensionNumbers.AbsSendTime = value
}
// Packetize packetizes the payload of an RTP packet and returns one or more RTP packets
func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet {
// Guard against an empty payload
if len(payload) == 0 {
return nil
}
payloads := p.Payloader.Payload(p.MTU-12, payload)
packets := make([]*Packet, len(payloads))
for i, pp := range payloads {
packets[i] = &Packet{
Header: Header{
Version: 2,
Padding: false,
Extension: false,
Marker: i == len(payloads)-1,
PayloadType: p.PayloadType,
SequenceNumber: p.Sequencer.NextSequenceNumber(),
Timestamp: p.Timestamp, // Figure out how to do timestamps
SSRC: p.SSRC,
},
Payload: pp,
}
}
p.Timestamp += samples
if len(packets) != 0 && p.extensionNumbers.AbsSendTime != 0 {
sendTime := NewAbsSendTimeExtension(p.timegen())
// apply http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
b, err := sendTime.Marshal()
if err != nil {
return nil // never happens
}
err = packets[len(packets)-1].SetExtension(uint8(p.extensionNumbers.AbsSendTime), b)
if err != nil {
return nil // never happens
}
}
return packets
}

View file

@ -0,0 +1,6 @@
package rtp
// PartitionHeadChecker is the interface that checks whether the packet is keyframe or not
type PartitionHeadChecker interface {
IsPartitionHead([]byte) bool
}

View file

@ -0,0 +1,8 @@
package rtp
import (
"github.com/pion/randutil"
)
// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals

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,2 @@
// Package rtp provides RTP packetizer and depacketizer
package rtp

View file

@ -0,0 +1,57 @@
package rtp
import (
"math"
"sync"
)
// Sequencer generates sequential sequence numbers for building RTP packets
type Sequencer interface {
NextSequenceNumber() uint16
RollOverCount() uint64
}
// NewRandomSequencer returns a new sequencer starting from a random sequence
// number
func NewRandomSequencer() Sequencer {
return &sequencer{
sequenceNumber: uint16(globalMathRandomGenerator.Intn(math.MaxUint16)),
}
}
// NewFixedSequencer returns a new sequencer starting from a specific
// sequence number
func NewFixedSequencer(s uint16) Sequencer {
return &sequencer{
sequenceNumber: s - 1, // -1 because the first sequence number prepends 1
}
}
type sequencer struct {
sequenceNumber uint16
rollOverCount uint64
mutex sync.Mutex
}
// NextSequenceNumber increment and returns a new sequence number for
// building RTP packets
func (s *sequencer) NextSequenceNumber() uint16 {
s.mutex.Lock()
defer s.mutex.Unlock()
s.sequenceNumber++
if s.sequenceNumber == 0 {
s.rollOverCount++
}
return s.sequenceNumber
}
// RollOverCount returns the amount of times the 16bit sequence number
// has wrapped
func (s *sequencer) RollOverCount() uint64 {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.rollOverCount
}

View file

@ -0,0 +1,39 @@
package rtp
import (
"encoding/binary"
)
const (
// transport-wide sequence
transportCCExtensionSize = 2
)
// TransportCCExtension is a extension payload format in
// https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01
// 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
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | 0xBE | 0xDE | length=1 |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ID | L=1 |transport-wide sequence number | zero padding |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type TransportCCExtension struct {
TransportSequence uint16
}
// Marshal serializes the members to buffer
func (t *TransportCCExtension) Marshal() ([]byte, error) {
buf := make([]byte, transportCCExtensionSize)
binary.BigEndian.PutUint16(buf[0:2], t.TransportSequence)
return buf, nil
}
// Unmarshal parses the passed byte slice and stores the result in the members
func (t *TransportCCExtension) Unmarshal(rawData []byte) error {
if len(rawData) < transportCCExtensionSize {
return errTooSmall
}
t.TransportSequence = binary.BigEndian.Uint16(rawData[0:2])
return nil
}