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

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

28
server/vendor/github.com/pion/rtp/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
### 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
wasm_exec.js

125
server/vendor/github.com/pion/rtp/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,125 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
linters-settings:
govet:
enable:
- shadow
misspell:
locale: US
exhaustive:
default-signifies-exhaustive: true
gomodguard:
blocked:
modules:
- github.com/pkg/errors:
recommendations:
- errors
forbidigo:
forbid:
- ^fmt.Print(f|ln)?$
- ^log.(Panic|Fatal|Print)(f|ln)?$
- ^os.Exit$
- ^panic$
- ^print(ln)?$
linters:
enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bidichk # Checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
- contextcheck # check the function whether use a non-inherited context
- decorder # check declaration order and count of types, constants, variables and functions
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection
- durationcheck # check for two durations multiplied together
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.
- exhaustive # check exhaustiveness of enum switch statements
- exportloopref # checks for pointers to enclosing loop variables
- forbidigo # Forbids identifiers
- forcetypeassert # finds forced type assertions
- 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
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
- 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
- grouper # An analyzer to analyze expression groups.
- importas # Enforces consistent import aliases
- ineffassign # Detects when assignments to existing variables are not used
- misspell # Finds commonly misspelled English words in comments
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
- noctx # noctx finds sending http request without context.Context
- predeclared # find code that shadows one of Go's predeclared identifiers
- revive # golint replacement, finds style mistakes
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
- stylecheck # Stylecheck is a replacement for golint
- tagliatelle # Checks the struct tags.
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
- 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
- wastedassign # wastedassign finds wasted assignment statements
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- containedctx # containedctx is a linter that detects struct contained context.Context field
- cyclop # checks function and package cyclomatic complexity
- exhaustivestruct # Checks if all struct's fields are initialized
- 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.
- ifshort # Checks that your code uses short syntax for if-statements whenever possible
- ireturn # Accept Interfaces, Return Concrete Types
- lll # Reports long lines
- maintidx # maintidx measures the maintainability index of each function.
- makezero # Finds slice declarations with non-zero initial length
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted
- nakedret # Finds naked returns in functions greater than a specified function length
- 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
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
- prealloc # Finds slice declarations that could potentially be preallocated
- promlinter # Check Prometheus metrics naming via promlint
- 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
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
- varnamelen # checks that the length of a variable's name matches its scope
- wrapcheck # Checks that errors returned from external packages are wrapped
- wsl # Whitespace Linter - Forces you to use empty lines!
issues:
exclude-use-default: false
exclude-dirs-use-default: false
exclude-rules:
# Allow complex tests and examples, better to be self contained
- path: (examples|main\.go|_test\.go)
linters:
- forbidigo
- gocognit
# Allow forbidden identifiers in CLI commands
- path: cmd
linters:
- forbidigo

5
server/vendor/github.com/pion/rtp/.goreleaser.yml generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
builds:
- skip: true

9
server/vendor/github.com/pion/rtp/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright (c) <year> <copyright holders>
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.

35
server/vendor/github.com/pion/rtp/README.md generated vendored Normal file
View File

@@ -0,0 +1,35 @@
<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>
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/pion/rtp/test.yaml">
<a href="https://pkg.go.dev/github.com/pion/rtp"><img src="https://pkg.go.dev/badge/github.com/pion/rtp.svg" alt="Go Reference"></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 [Slack](https://pion.ly/slack).
Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news.
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
### License
MIT License - see [LICENSE](LICENSE) for full text

View File

@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"encoding/binary"
"time"
)
const (
absCaptureTimeExtensionSize = 8
absCaptureTimeExtendedExtensionSize = 16
)
// AbsCaptureTimeExtension is a extension payload format in
// http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time
// 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=7 | absolute capture timestamp (bit 0-23) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | absolute capture timestamp (bit 24-55) |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ... (56-63) |
// +-+-+-+-+-+-+-+-+
type AbsCaptureTimeExtension struct {
Timestamp uint64
EstimatedCaptureClockOffset *int64
}
// Marshal serializes the members to buffer.
func (t AbsCaptureTimeExtension) Marshal() ([]byte, error) {
if t.EstimatedCaptureClockOffset != nil {
buf := make([]byte, 16)
binary.BigEndian.PutUint64(buf[0:8], t.Timestamp)
binary.BigEndian.PutUint64(buf[8:16], uint64(*t.EstimatedCaptureClockOffset))
return buf, nil
}
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf[0:8], t.Timestamp)
return buf, nil
}
// Unmarshal parses the passed byte slice and stores the result in the members.
func (t *AbsCaptureTimeExtension) Unmarshal(rawData []byte) error {
if len(rawData) < absCaptureTimeExtensionSize {
return errTooSmall
}
t.Timestamp = binary.BigEndian.Uint64(rawData[0:8])
if len(rawData) >= absCaptureTimeExtendedExtensionSize {
offset := int64(binary.BigEndian.Uint64(rawData[8:16]))
t.EstimatedCaptureClockOffset = &offset
}
return nil
}
// CaptureTime produces the estimated time.Time represented by this extension.
func (t AbsCaptureTimeExtension) CaptureTime() time.Time {
return toTime(t.Timestamp)
}
// EstimatedCaptureClockOffsetDuration produces the estimated time.Duration represented by this extension.
func (t AbsCaptureTimeExtension) EstimatedCaptureClockOffsetDuration() *time.Duration {
if t.EstimatedCaptureClockOffset == nil {
return nil
}
offset := *t.EstimatedCaptureClockOffset
negative := false
if offset < 0 {
offset = -offset
negative = true
}
duration := time.Duration(offset/(1<<32))*time.Second + time.Duration((offset&0xFFFFFFFF)*1e9/(1<<32))*time.Nanosecond
if negative {
duration = -duration
}
return &duration
}
// NewAbsCaptureTimeExtension makes new AbsCaptureTimeExtension from time.Time.
func NewAbsCaptureTimeExtension(captureTime time.Time) *AbsCaptureTimeExtension {
return &AbsCaptureTimeExtension{
Timestamp: toNtpTime(captureTime),
}
}
// NewAbsCaptureTimeExtensionWithCaptureClockOffset makes new AbsCaptureTimeExtension from time.Time and a clock offset.
func NewAbsCaptureTimeExtensionWithCaptureClockOffset(captureTime time.Time, captureClockOffset time.Duration) *AbsCaptureTimeExtension {
ns := captureClockOffset.Nanoseconds()
negative := false
if ns < 0 {
ns = -ns
negative = true
}
lsb := (ns / 1e9) & 0xFFFFFFFF
msb := (((ns % 1e9) * (1 << 32)) / 1e9) & 0xFFFFFFFF
offset := (lsb << 32) | msb
if negative {
offset = -offset
}
return &AbsCaptureTimeExtension{
Timestamp: toNtpTime(captureTime),
EstimatedCaptureClockOffset: &offset,
}
}

View File

@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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,63 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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
}

22
server/vendor/github.com/pion/rtp/codecov.yml generated vendored Normal file
View File

@@ -0,0 +1,22 @@
#
# DO NOT EDIT THIS FILE
#
# It is automatically copied from https://github.com/pion/.goassets repository.
#
# SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
# SPDX-License-Identifier: MIT
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,85 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package obu implements tools for working with the Open Bitstream Unit.
package obu
import "errors"
const (
sevenLsbBitmask = uint(0b01111111)
msbBitmask = uint(0b10000000)
)
// ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read
var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished")
// EncodeLEB128 encodes a uint as LEB128
func EncodeLEB128(in uint) (out uint) {
for {
// Copy seven bits from in and discard
// what we have copied from in
out |= (in & sevenLsbBitmask)
in >>= 7
// If we have more bits to encode set MSB
// otherwise we are done
if in != 0 {
out |= msbBitmask
out <<= 8
} else {
return out
}
}
}
func decodeLEB128(in uint) (out uint) {
for {
// Take 7 LSB from in
out |= (in & sevenLsbBitmask)
// Discard the MSB
in >>= 8
if in == 0 {
return out
}
out <<= 7
}
}
// ReadLeb128 scans an buffer and decodes a Leb128 value.
// If the end of the buffer is reached and all MSB are set
// an error is returned
func ReadLeb128(in []byte) (uint, uint, error) {
var encodedLength uint
for i := range in {
encodedLength |= uint(in[i])
if in[i]&byte(msbBitmask) == 0 {
return decodeLEB128(encodedLength), uint(i + 1), nil
}
// Make more room for next read
encodedLength <<= 8
}
return 0, 0, ErrFailedToReadLEB128
}
// WriteToLeb128 writes a uint to a LEB128 encoded byte slice.
func WriteToLeb128(in uint) []byte {
b := make([]byte, 10)
for i := 0; i < len(b); i++ {
b[i] = byte(in & 0x7f)
in >>= 7
if in == 0 {
return b[:i+1]
}
b[i] |= 0x80
}
return b // unreachable
}

204
server/vendor/github.com/pion/rtp/codecs/av1_packet.go generated vendored Normal file
View File

@@ -0,0 +1,204 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
import (
"github.com/pion/rtp/codecs/av1/obu"
)
const (
zMask = byte(0b10000000)
zBitshift = 7
yMask = byte(0b01000000)
yBitshift = 6
wMask = byte(0b00110000)
wBitshift = 4
nMask = byte(0b00001000)
nBitshift = 3
obuFrameTypeMask = byte(0b01111000)
obuFrameTypeBitshift = 3
obuFameTypeSequenceHeader = 1
av1PayloaderHeadersize = 1
leb128Size = 1
)
// AV1Payloader payloads AV1 packets
type AV1Payloader struct {
sequenceHeader []byte
}
// Payload fragments a AV1 packet across one or more byte arrays
// See AV1Packet for description of AV1 Payload Header
func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) {
payloadDataIndex := 0
payloadDataRemaining := len(payload)
// Payload Data and MTU is non-zero
if mtu <= 0 || payloadDataRemaining <= 0 {
return payloads
}
// Cache Sequence Header and packetize with next payload
frameType := (payload[0] & obuFrameTypeMask) >> obuFrameTypeBitshift
if frameType == obuFameTypeSequenceHeader {
p.sequenceHeader = payload
return
}
for payloadDataRemaining > 0 {
obuCount := byte(1)
metadataSize := av1PayloaderHeadersize
if len(p.sequenceHeader) != 0 {
obuCount++
metadataSize += leb128Size + len(p.sequenceHeader)
}
out := make([]byte, min(int(mtu), payloadDataRemaining+metadataSize))
outOffset := av1PayloaderHeadersize
out[0] = obuCount << wBitshift
if obuCount == 2 {
// This Payload contain the start of a Coded Video Sequence
out[0] ^= nMask
out[1] = byte(obu.EncodeLEB128(uint(len(p.sequenceHeader))))
copy(out[2:], p.sequenceHeader)
outOffset += leb128Size + len(p.sequenceHeader)
p.sequenceHeader = nil
}
outBufferRemaining := len(out) - outOffset
copy(out[outOffset:], payload[payloadDataIndex:payloadDataIndex+outBufferRemaining])
payloadDataRemaining -= outBufferRemaining
payloadDataIndex += outBufferRemaining
// Does this Fragment contain an OBU that started in a previous payload
if len(payloads) > 0 {
out[0] ^= zMask
}
// This OBU will be continued in next Payload
if payloadDataRemaining != 0 {
out[0] ^= yMask
}
payloads = append(payloads, out)
}
return payloads
}
// AV1Packet represents a depacketized AV1 RTP Packet
/*
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |Z|Y| W |N|-|-|-|
* +-+-+-+-+-+-+-+-+
**/
// https://aomediacodec.github.io/av1-rtp-spec/#44-av1-aggregation-header
type AV1Packet struct {
// Z: MUST be set to 1 if the first OBU element is an
// OBU fragment that is a continuation of an OBU fragment
// from the previous packet, and MUST be set to 0 otherwise.
Z bool
// Y: MUST be set to 1 if the last OBU element is an OBU fragment
// that will continue in the next packet, and MUST be set to 0 otherwise.
Y bool
// W: two bit field that describes the number of OBU elements in the packet.
// This field MUST be set equal to 0 or equal to the number of OBU elements
// contained in the packet. If set to 0, each OBU element MUST be preceded by
// a length field. If not set to 0 (i.e., W = 1, 2 or 3) the last OBU element
// MUST NOT be preceded by a length field. Instead, the length of the last OBU
// element contained in the packet can be calculated as follows:
// Length of the last OBU element =
// length of the RTP payload
// - length of aggregation header
// - length of previous OBU elements including length fields
W byte
// N: MUST be set to 1 if the packet is the first packet of a coded video sequence, and MUST be set to 0 otherwise.
N bool
// Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one.
// AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements
OBUElements [][]byte
videoDepacketizer
}
// Unmarshal parses the passed byte slice and stores the result in the AV1Packet this method is called upon
func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) {
if payload == nil {
return nil, errNilPacket
} else if len(payload) < 2 {
return nil, errShortPacket
}
p.Z = ((payload[0] & zMask) >> zBitshift) != 0
p.Y = ((payload[0] & yMask) >> yBitshift) != 0
p.N = ((payload[0] & nMask) >> nBitshift) != 0
p.W = (payload[0] & wMask) >> wBitshift
if p.Z && p.N {
return nil, errIsKeyframeAndFragment
}
if !p.zeroAllocation {
obuElements, err := p.parseBody(payload[1:])
if err != nil {
return nil, err
}
p.OBUElements = obuElements
}
return payload[1:], nil
}
func (p *AV1Packet) parseBody(payload []byte) ([][]byte, error) {
if p.OBUElements != nil {
return p.OBUElements, nil
}
obuElements := [][]byte{}
var obuElementLength, bytesRead uint
currentIndex := uint(0)
for i := 1; ; i++ {
if currentIndex == uint(len(payload)) {
break
}
// If W bit is set the last OBU Element will have no length header
if byte(i) == p.W {
bytesRead = 0
obuElementLength = uint(len(payload)) - currentIndex
} else {
var err error
obuElementLength, bytesRead, err = obu.ReadLeb128(payload[currentIndex:])
if err != nil {
return nil, err
}
}
currentIndex += bytesRead
if uint(len(payload)) < currentIndex+obuElementLength {
return nil, errShortPacket
}
obuElements = append(obuElements, payload[currentIndex:currentIndex+obuElementLength])
currentIndex += obuElementLength
}
return obuElements, nil
}

5
server/vendor/github.com/pion/rtp/codecs/codecs.go generated vendored Normal file
View File

@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package codecs implements codec specific RTP payloader/depayloaders
package codecs

39
server/vendor/github.com/pion/rtp/codecs/common.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
func min(a, b int) int {
if a < b {
return a
}
return b
}
// audioDepacketizer is a mixin for audio codec depacketizers
type audioDepacketizer struct{}
func (d *audioDepacketizer) IsPartitionTail(_ bool, _ []byte) bool {
return true
}
func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool {
return true
}
// videoDepacketizer is a mixin for video codec depacketizers
type videoDepacketizer struct {
zeroAllocation bool
}
func (d *videoDepacketizer) IsPartitionTail(marker bool, _ []byte) bool {
return marker
}
// SetZeroAllocation enables Zero Allocation mode for the depacketizer
// By default the Depacketizers will allocate as they parse. These allocations
// are needed for Metadata and other optional values. If you don't need this information
// enabling SetZeroAllocation gives you higher performance at a reduced feature set.
func (d *videoDepacketizer) SetZeroAllocation(zeroAllocation bool) {
d.zeroAllocation = zeroAllocation
}

17
server/vendor/github.com/pion/rtp/codecs/error.go generated vendored Normal file
View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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")
// AV1 Errors
errIsKeyframeAndFragment = errors.New("bits Z and N are set. Not possible to have OBU be tail fragment and be keyframe")
)

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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 uint16, payload []byte) [][]byte {
var out [][]byte
if payload == nil || mtu == 0 {
return out
}
for len(payload) > int(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,25 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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 uint16, payload []byte) [][]byte {
var out [][]byte
if payload == nil || mtu == 0 {
return out
}
for len(payload) > int(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)
}

297
server/vendor/github.com/pion/rtp/codecs/h264_packet.go generated vendored Normal file
View File

@@ -0,0 +1,297 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
import (
"bytes"
"encoding/binary"
"fmt"
)
// H264Payloader payloads H264 packets
type H264Payloader struct {
spsNalu, ppsNalu []byte
}
const (
stapaNALUType = 24
fuaNALUType = 28
fubNALUType = 29
spsNALUType = 7
ppsNALUType = 8
audNALUType = 9
fillerNALUType = 12
fuaHeaderSize = 2
stapaHeaderSize = 1
stapaNALULengthSize = 2
naluTypeBitmask = 0x1F
naluRefIdcBitmask = 0x60
fuStartBitmask = 0x80
fuEndBitmask = 0x40
outputStapAHeader = 0x78
)
// nolint:gochecknoglobals
var (
naluStartCode = []byte{0x00, 0x00, 0x01}
annexbNALUStartCode = []byte{0x00, 0x00, 0x00, 0x01}
)
func emitNalus(nals []byte, emit func([]byte)) {
start := 0
length := len(nals)
for start < length {
end := bytes.Index(nals[start:], annexbNALUStartCode)
offset := 4
if end == -1 {
end = bytes.Index(nals[start:], naluStartCode)
offset = 3
}
if end == -1 {
emit(nals[start:])
break
}
emit(nals[start : start+end])
// next NAL start position
start += end + offset
}
}
// Payload fragments a H264 packet across one or more byte arrays
func (p *H264Payloader) Payload(mtu uint16, 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
switch {
case naluType == audNALUType || naluType == fillerNALUType:
return
case naluType == spsNALUType:
p.spsNalu = nalu
return
case naluType == ppsNALUType:
p.ppsNalu = nalu
return
case p.spsNalu != nil && p.ppsNalu != nil:
// Pack current NALU with SPS and PPS as STAP-A
spsLen := make([]byte, 2)
binary.BigEndian.PutUint16(spsLen, uint16(len(p.spsNalu)))
ppsLen := make([]byte, 2)
binary.BigEndian.PutUint16(ppsLen, uint16(len(p.ppsNalu)))
stapANalu := []byte{outputStapAHeader}
stapANalu = append(stapANalu, spsLen...)
stapANalu = append(stapANalu, p.spsNalu...)
stapANalu = append(stapANalu, ppsLen...)
stapANalu = append(stapANalu, p.ppsNalu...)
if len(stapANalu) <= int(mtu) {
out := make([]byte, len(stapANalu))
copy(out, stapANalu)
payloads = append(payloads, out)
}
p.spsNalu = nil
p.ppsNalu = nil
}
// Single NALU
if len(nalu) <= int(mtu) {
out := make([]byte, len(nalu))
copy(out, nalu)
payloads = append(payloads, out)
return
}
// FU-A
maxFragmentSize := int(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.
// According to the RFC, the first octet is skipped due to redundant information
naluIndex := 1
naluLength := len(nalu) - naluIndex
naluRemaining := naluLength
if min(maxFragmentSize, naluRemaining) <= 0 {
return
}
for naluRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, naluRemaining)
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 naluRemaining == naluLength {
// Set start bit
out[1] |= 1 << 7
} else if naluRemaining-currentFragmentSize == 0 {
// Set end bit
out[1] |= 1 << 6
}
copy(out[fuaHeaderSize:], nalu[naluIndex:naluIndex+currentFragmentSize])
payloads = append(payloads, out)
naluRemaining -= currentFragmentSize
naluIndex += currentFragmentSize
}
})
return payloads
}
// H264Packet represents the H264 header that is stored in the payload of an RTP Packet
type H264Packet struct {
IsAVC bool
fuaBuffer []byte
videoDepacketizer
}
func (p *H264Packet) doPackaging(buf, nalu []byte) []byte {
if p.IsAVC {
buf = binary.BigEndian.AppendUint32(buf, uint32(len(nalu)))
buf = append(buf, nalu...)
return buf
}
buf = append(buf, annexbNALUStartCode...)
buf = append(buf, nalu...)
return buf
}
// IsDetectedFinalPacketInSequence returns true of the packet passed in has the
// marker bit set indicated the end of a packet sequence
func (p *H264Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool {
return rtpPacketMarketBit
}
// 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 p.zeroAllocation {
return payload, nil
}
return p.parseBody(payload)
}
func (p *H264Packet) parseBody(payload []byte) ([]byte, error) {
if len(payload) == 0 {
return nil, fmt.Errorf("%w: %d <=0", 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 p.doPackaging(nil, 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 = p.doPackaging(result, payload[currOffset:currOffset+naluSize])
currOffset += naluSize
}
return result, nil
case naluType == fuaNALUType:
if len(payload) < fuaHeaderSize {
return nil, errShortPacket
}
if p.fuaBuffer == nil {
p.fuaBuffer = []byte{}
}
p.fuaBuffer = append(p.fuaBuffer, payload[fuaHeaderSize:]...)
if payload[1]&fuEndBitmask != 0 {
naluRefIdc := payload[0] & naluRefIdcBitmask
fragmentedNaluType := payload[1] & naluTypeBitmask
nalu := append([]byte{}, naluRefIdc|fragmentedNaluType)
nalu = append(nalu, p.fuaBuffer...)
p.fuaBuffer = nil
return p.doPackaging(nil, nalu), nil
}
return []byte{}, nil
}
return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType)
}
// H264PartitionHeadChecker checks H264 partition head.
//
// Deprecated: replaced by H264Packet.IsPartitionHead()
type H264PartitionHeadChecker struct{}
// IsPartitionHead checks if this is the head of a packetized nalu stream.
//
// Deprecated: replaced by H264Packet.IsPartitionHead()
func (*H264PartitionHeadChecker) IsPartitionHead(packet []byte) bool {
return (&H264Packet{}).IsPartitionHead(packet)
}
// IsPartitionHead checks if this is the head of a packetized nalu stream.
func (*H264Packet) IsPartitionHead(payload []byte) bool {
if len(payload) < 2 {
return false
}
if payload[0]&naluTypeBitmask == fuaNALUType ||
payload[0]&naluTypeBitmask == fubNALUType {
return payload[1]&fuStartBitmask != 0
}
return true
}

828
server/vendor/github.com/pion/rtp/codecs/h265_packet.go generated vendored Normal file
View File

@@ -0,0 +1,828 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
import (
"encoding/binary"
"errors"
"fmt"
)
//
// Errors
//
var (
errH265CorruptedPacket = errors.New("corrupted h265 packet")
errInvalidH265PacketType = errors.New("invalid h265 packet type")
)
//
// Network Abstraction Unit Header implementation
//
const (
// sizeof(uint16)
h265NaluHeaderSize = 2
// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
h265NaluAggregationPacketType = 48
// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
h265NaluFragmentationUnitType = 49
// https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4
h265NaluPACIPacketType = 50
)
// H265NALUHeader is a H265 NAL Unit Header
// https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4
/*
* +---------------+---------------+
* |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |F| Type | LayerID | TID |
* +-------------+-----------------+
**/
type H265NALUHeader uint16
func newH265NALUHeader(highByte, lowByte uint8) H265NALUHeader {
return H265NALUHeader((uint16(highByte) << 8) | uint16(lowByte))
}
// F is the forbidden bit, should always be 0.
func (h H265NALUHeader) F() bool {
return (uint16(h) >> 15) != 0
}
// Type of NAL Unit.
func (h H265NALUHeader) Type() uint8 {
// 01111110 00000000
const mask = 0b01111110 << 8
return uint8((uint16(h) & mask) >> (8 + 1))
}
// IsTypeVCLUnit returns whether or not the NAL Unit type is a VCL NAL unit.
func (h H265NALUHeader) IsTypeVCLUnit() bool {
// Type is coded on 6 bits
const msbMask = 0b00100000
return (h.Type() & msbMask) == 0
}
// LayerID should always be 0 in non-3D HEVC context.
func (h H265NALUHeader) LayerID() uint8 {
// 00000001 11111000
const mask = (0b00000001 << 8) | 0b11111000
return uint8((uint16(h) & mask) >> 3)
}
// TID is the temporal identifier of the NAL unit +1.
func (h H265NALUHeader) TID() uint8 {
const mask = 0b00000111
return uint8(uint16(h) & mask)
}
// IsAggregationPacket returns whether or not the packet is an Aggregation packet.
func (h H265NALUHeader) IsAggregationPacket() bool {
return h.Type() == h265NaluAggregationPacketType
}
// IsFragmentationUnit returns whether or not the packet is a Fragmentation Unit packet.
func (h H265NALUHeader) IsFragmentationUnit() bool {
return h.Type() == h265NaluFragmentationUnitType
}
// IsPACIPacket returns whether or not the packet is a PACI packet.
func (h H265NALUHeader) IsPACIPacket() bool {
return h.Type() == h265NaluPACIPacketType
}
//
// Single NAL Unit Packet implementation
//
// H265SingleNALUnitPacket represents a NALU packet, containing exactly one NAL unit.
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PayloadHdr | DONL (conditional) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | NAL unit payload data |
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | :...OPTIONAL RTP padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**/
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.1
type H265SingleNALUnitPacket struct {
// payloadHeader is the header of the H265 packet.
payloadHeader H265NALUHeader
// donl is a 16-bit field, that may or may not be present.
donl *uint16
// payload of the fragmentation unit.
payload []byte
mightNeedDONL bool
}
// WithDONL can be called to specify whether or not DONL might be parsed.
// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream.
func (p *H265SingleNALUnitPacket) WithDONL(value bool) {
p.mightNeedDONL = value
}
// Unmarshal parses the passed byte slice and stores the result in the H265SingleNALUnitPacket this method is called upon.
func (p *H265SingleNALUnitPacket) Unmarshal(payload []byte) ([]byte, error) {
// sizeof(headers)
const totalHeaderSize = h265NaluHeaderSize
if payload == nil {
return nil, errNilPacket
} else if len(payload) <= totalHeaderSize {
return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize)
}
payloadHeader := newH265NALUHeader(payload[0], payload[1])
if payloadHeader.F() {
return nil, errH265CorruptedPacket
}
if payloadHeader.IsFragmentationUnit() || payloadHeader.IsPACIPacket() || payloadHeader.IsAggregationPacket() {
return nil, errInvalidH265PacketType
}
payload = payload[2:]
if p.mightNeedDONL {
// sizeof(uint16)
if len(payload) <= 2 {
return nil, errShortPacket
}
donl := (uint16(payload[0]) << 8) | uint16(payload[1])
p.donl = &donl
payload = payload[2:]
}
p.payloadHeader = payloadHeader
p.payload = payload
return nil, nil
}
// PayloadHeader returns the NALU header of the packet.
func (p *H265SingleNALUnitPacket) PayloadHeader() H265NALUHeader {
return p.payloadHeader
}
// DONL returns the DONL of the packet.
func (p *H265SingleNALUnitPacket) DONL() *uint16 {
return p.donl
}
// Payload returns the Fragmentation Unit packet payload.
func (p *H265SingleNALUnitPacket) Payload() []byte {
return p.payload
}
func (p *H265SingleNALUnitPacket) isH265Packet() {}
//
// Aggregation Packets implementation
//
// H265AggregationUnitFirst represent the First Aggregation Unit in an AP.
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : DONL (conditional) | NALU size |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | NALU size | |
* +-+-+-+-+-+-+-+-+ NAL unit |
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | :
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**/
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
type H265AggregationUnitFirst struct {
donl *uint16
nalUnitSize uint16
nalUnit []byte
}
// DONL field, when present, specifies the value of the 16 least
// significant bits of the decoding order number of the aggregated NAL
// unit.
func (u H265AggregationUnitFirst) DONL() *uint16 {
return u.donl
}
// NALUSize represents the size, in bytes, of the NalUnit.
func (u H265AggregationUnitFirst) NALUSize() uint16 {
return u.nalUnitSize
}
// NalUnit payload.
func (u H265AggregationUnitFirst) NalUnit() []byte {
return u.nalUnit
}
// H265AggregationUnit represent the an Aggregation Unit in an AP, which is not the first one.
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* : DOND (cond) | NALU size |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | |
* | NAL unit |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | :
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**/
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
type H265AggregationUnit struct {
dond *uint8
nalUnitSize uint16
nalUnit []byte
}
// DOND field plus 1 specifies the difference between
// the decoding order number values of the current aggregated NAL unit
// and the preceding aggregated NAL unit in the same AP.
func (u H265AggregationUnit) DOND() *uint8 {
return u.dond
}
// NALUSize represents the size, in bytes, of the NalUnit.
func (u H265AggregationUnit) NALUSize() uint16 {
return u.nalUnitSize
}
// NalUnit payload.
func (u H265AggregationUnit) NalUnit() []byte {
return u.nalUnit
}
// H265AggregationPacket represents an Aggregation packet.
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PayloadHdr (Type=48) | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
* | |
* | two or more aggregation units |
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | :...OPTIONAL RTP padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**/
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2
type H265AggregationPacket struct {
firstUnit *H265AggregationUnitFirst
otherUnits []H265AggregationUnit
mightNeedDONL bool
}
// WithDONL can be called to specify whether or not DONL might be parsed.
// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream.
func (p *H265AggregationPacket) WithDONL(value bool) {
p.mightNeedDONL = value
}
// Unmarshal parses the passed byte slice and stores the result in the H265AggregationPacket this method is called upon.
func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) {
// sizeof(headers)
const totalHeaderSize = h265NaluHeaderSize
if payload == nil {
return nil, errNilPacket
} else if len(payload) <= totalHeaderSize {
return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize)
}
payloadHeader := newH265NALUHeader(payload[0], payload[1])
if payloadHeader.F() {
return nil, errH265CorruptedPacket
}
if !payloadHeader.IsAggregationPacket() {
return nil, errInvalidH265PacketType
}
// First parse the first aggregation unit
payload = payload[2:]
firstUnit := &H265AggregationUnitFirst{}
if p.mightNeedDONL {
if len(payload) < 2 {
return nil, errShortPacket
}
donl := (uint16(payload[0]) << 8) | uint16(payload[1])
firstUnit.donl = &donl
payload = payload[2:]
}
if len(payload) < 2 {
return nil, errShortPacket
}
firstUnit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1])
payload = payload[2:]
if len(payload) < int(firstUnit.nalUnitSize) {
return nil, errShortPacket
}
firstUnit.nalUnit = payload[:firstUnit.nalUnitSize]
payload = payload[firstUnit.nalUnitSize:]
// Parse remaining Aggregation Units
var units []H265AggregationUnit
for {
unit := H265AggregationUnit{}
if p.mightNeedDONL {
if len(payload) < 1 {
break
}
dond := payload[0]
unit.dond = &dond
payload = payload[1:]
}
if len(payload) < 2 {
break
}
unit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1])
payload = payload[2:]
if len(payload) < int(unit.nalUnitSize) {
break
}
unit.nalUnit = payload[:unit.nalUnitSize]
payload = payload[unit.nalUnitSize:]
units = append(units, unit)
}
// There need to be **at least** two Aggregation Units (first + another one)
if len(units) == 0 {
return nil, errShortPacket
}
p.firstUnit = firstUnit
p.otherUnits = units
return nil, nil
}
// FirstUnit returns the first Aggregated Unit of the packet.
func (p *H265AggregationPacket) FirstUnit() *H265AggregationUnitFirst {
return p.firstUnit
}
// OtherUnits returns the all the other Aggregated Unit of the packet (excluding the first one).
func (p *H265AggregationPacket) OtherUnits() []H265AggregationUnit {
return p.otherUnits
}
func (p *H265AggregationPacket) isH265Packet() {}
//
// Fragmentation Unit implementation
//
const (
// sizeof(uint8)
h265FragmentationUnitHeaderSize = 1
)
// H265FragmentationUnitHeader is a H265 FU Header
/*
* +---------------+
* |0|1|2|3|4|5|6|7|
* +-+-+-+-+-+-+-+-+
* |S|E| FuType |
* +---------------+
**/
type H265FragmentationUnitHeader uint8
// S represents the start of a fragmented NAL unit.
func (h H265FragmentationUnitHeader) S() bool {
const mask = 0b10000000
return ((h & mask) >> 7) != 0
}
// E represents the end of a fragmented NAL unit.
func (h H265FragmentationUnitHeader) E() bool {
const mask = 0b01000000
return ((h & mask) >> 6) != 0
}
// FuType MUST be equal to the field Type of the fragmented NAL unit.
func (h H265FragmentationUnitHeader) FuType() uint8 {
const mask = 0b00111111
return uint8(h) & mask
}
// H265FragmentationUnitPacket represents a single Fragmentation Unit packet.
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PayloadHdr (Type=49) | FU header | DONL (cond) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
* | DONL (cond) | |
* |-+-+-+-+-+-+-+-+ |
* | FU payload |
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | :...OPTIONAL RTP padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**/
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3
type H265FragmentationUnitPacket struct {
// payloadHeader is the header of the H265 packet.
payloadHeader H265NALUHeader
// fuHeader is the header of the fragmentation unit
fuHeader H265FragmentationUnitHeader
// donl is a 16-bit field, that may or may not be present.
donl *uint16
// payload of the fragmentation unit.
payload []byte
mightNeedDONL bool
}
// WithDONL can be called to specify whether or not DONL might be parsed.
// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream.
func (p *H265FragmentationUnitPacket) WithDONL(value bool) {
p.mightNeedDONL = value
}
// Unmarshal parses the passed byte slice and stores the result in the H265FragmentationUnitPacket this method is called upon.
func (p *H265FragmentationUnitPacket) Unmarshal(payload []byte) ([]byte, error) {
// sizeof(headers)
const totalHeaderSize = h265NaluHeaderSize + h265FragmentationUnitHeaderSize
if payload == nil {
return nil, errNilPacket
} else if len(payload) <= totalHeaderSize {
return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize)
}
payloadHeader := newH265NALUHeader(payload[0], payload[1])
if payloadHeader.F() {
return nil, errH265CorruptedPacket
}
if !payloadHeader.IsFragmentationUnit() {
return nil, errInvalidH265PacketType
}
fuHeader := H265FragmentationUnitHeader(payload[2])
payload = payload[3:]
if fuHeader.S() && p.mightNeedDONL {
// sizeof(uint16)
if len(payload) <= 2 {
return nil, errShortPacket
}
donl := (uint16(payload[0]) << 8) | uint16(payload[1])
p.donl = &donl
payload = payload[2:]
}
p.payloadHeader = payloadHeader
p.fuHeader = fuHeader
p.payload = payload
return nil, nil
}
// PayloadHeader returns the NALU header of the packet.
func (p *H265FragmentationUnitPacket) PayloadHeader() H265NALUHeader {
return p.payloadHeader
}
// FuHeader returns the Fragmentation Unit Header of the packet.
func (p *H265FragmentationUnitPacket) FuHeader() H265FragmentationUnitHeader {
return p.fuHeader
}
// DONL returns the DONL of the packet.
func (p *H265FragmentationUnitPacket) DONL() *uint16 {
return p.donl
}
// Payload returns the Fragmentation Unit packet payload.
func (p *H265FragmentationUnitPacket) Payload() []byte {
return p.payload
}
func (p *H265FragmentationUnitPacket) isH265Packet() {}
//
// PACI implementation
//
// H265PACIPacket represents a single H265 PACI packet.
/*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | PayloadHdr (Type=50) |A| cType | PHSsize |F0..2|Y|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Payload Header Extension Structure (PHES) |
* |=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=|
* | |
* | PACI payload: NAL unit |
* | . . . |
* | |
* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | :...OPTIONAL RTP padding |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
**/
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4
type H265PACIPacket struct {
// payloadHeader is the header of the H265 packet.
payloadHeader H265NALUHeader
// Field which holds value for `A`, `cType`, `PHSsize`, `F0`, `F1`, `F2` and `Y` fields.
paciHeaderFields uint16
// phes is a header extension, of byte length `PHSsize`
phes []byte
// Payload contains NAL units & optional padding
payload []byte
}
// PayloadHeader returns the NAL Unit Header.
func (p *H265PACIPacket) PayloadHeader() H265NALUHeader {
return p.payloadHeader
}
// A copies the F bit of the PACI payload NALU.
func (p *H265PACIPacket) A() bool {
const mask = 0b10000000 << 8
return (p.paciHeaderFields & mask) != 0
}
// CType copies the Type field of the PACI payload NALU.
func (p *H265PACIPacket) CType() uint8 {
const mask = 0b01111110 << 8
return uint8((p.paciHeaderFields & mask) >> (8 + 1))
}
// PHSsize indicates the size of the PHES field.
func (p *H265PACIPacket) PHSsize() uint8 {
const mask = (0b00000001 << 8) | 0b11110000
return uint8((p.paciHeaderFields & mask) >> 4)
}
// F0 indicates the presence of a Temporal Scalability support extension in the PHES.
func (p *H265PACIPacket) F0() bool {
const mask = 0b00001000
return (p.paciHeaderFields & mask) != 0
}
// F1 must be zero, reserved for future extensions.
func (p *H265PACIPacket) F1() bool {
const mask = 0b00000100
return (p.paciHeaderFields & mask) != 0
}
// F2 must be zero, reserved for future extensions.
func (p *H265PACIPacket) F2() bool {
const mask = 0b00000010
return (p.paciHeaderFields & mask) != 0
}
// Y must be zero, reserved for future extensions.
func (p *H265PACIPacket) Y() bool {
const mask = 0b00000001
return (p.paciHeaderFields & mask) != 0
}
// PHES contains header extensions. Its size is indicated by PHSsize.
func (p *H265PACIPacket) PHES() []byte {
return p.phes
}
// Payload is a single NALU or NALU-like struct, not including the first two octets (header).
func (p *H265PACIPacket) Payload() []byte {
return p.payload
}
// TSCI returns the Temporal Scalability Control Information extension, if present.
func (p *H265PACIPacket) TSCI() *H265TSCI {
if !p.F0() || p.PHSsize() < 3 {
return nil
}
tsci := H265TSCI((uint32(p.phes[0]) << 16) | (uint32(p.phes[1]) << 8) | uint32(p.phes[0]))
return &tsci
}
// Unmarshal parses the passed byte slice and stores the result in the H265PACIPacket this method is called upon.
func (p *H265PACIPacket) Unmarshal(payload []byte) ([]byte, error) {
// sizeof(headers)
const totalHeaderSize = h265NaluHeaderSize + 2
if payload == nil {
return nil, errNilPacket
} else if len(payload) <= totalHeaderSize {
return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize)
}
payloadHeader := newH265NALUHeader(payload[0], payload[1])
if payloadHeader.F() {
return nil, errH265CorruptedPacket
}
if !payloadHeader.IsPACIPacket() {
return nil, errInvalidH265PacketType
}
paciHeaderFields := (uint16(payload[2]) << 8) | uint16(payload[3])
payload = payload[4:]
p.paciHeaderFields = paciHeaderFields
headerExtensionSize := p.PHSsize()
if len(payload) < int(headerExtensionSize)+1 {
p.paciHeaderFields = 0
return nil, errShortPacket
}
p.payloadHeader = payloadHeader
if headerExtensionSize > 0 {
p.phes = payload[:headerExtensionSize]
}
payload = payload[headerExtensionSize:]
p.payload = payload
return nil, nil
}
func (p *H265PACIPacket) isH265Packet() {}
//
// Temporal Scalability Control Information
//
// H265TSCI is a Temporal Scalability Control Information header extension.
// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.5
type H265TSCI uint32
// TL0PICIDX see RFC7798 for more details.
func (h H265TSCI) TL0PICIDX() uint8 {
const m1 = 0xFFFF0000
const m2 = 0xFF00
return uint8((((h & m1) >> 16) & m2) >> 8)
}
// IrapPicID see RFC7798 for more details.
func (h H265TSCI) IrapPicID() uint8 {
const m1 = 0xFFFF0000
const m2 = 0x00FF
return uint8(((h & m1) >> 16) & m2)
}
// S see RFC7798 for more details.
func (h H265TSCI) S() bool {
const m1 = 0xFF00
const m2 = 0b10000000
return (uint8((h&m1)>>8) & m2) != 0
}
// E see RFC7798 for more details.
func (h H265TSCI) E() bool {
const m1 = 0xFF00
const m2 = 0b01000000
return (uint8((h&m1)>>8) & m2) != 0
}
// RES see RFC7798 for more details.
func (h H265TSCI) RES() uint8 {
const m1 = 0xFF00
const m2 = 0b00111111
return uint8((h&m1)>>8) & m2
}
//
// H265 Packet interface
//
type isH265Packet interface {
isH265Packet()
}
var (
_ isH265Packet = (*H265FragmentationUnitPacket)(nil)
_ isH265Packet = (*H265PACIPacket)(nil)
_ isH265Packet = (*H265SingleNALUnitPacket)(nil)
_ isH265Packet = (*H265AggregationPacket)(nil)
)
//
// Packet implementation
//
// H265Packet represents a H265 packet, stored in the payload of an RTP packet.
type H265Packet struct {
packet isH265Packet
mightNeedDONL bool
videoDepacketizer
}
// WithDONL can be called to specify whether or not DONL might be parsed.
// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream.
func (p *H265Packet) WithDONL(value bool) {
p.mightNeedDONL = value
}
// Unmarshal parses the passed byte slice and stores the result in the H265Packet this method is called upon
func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) {
if payload == nil {
return nil, errNilPacket
} else if len(payload) <= h265NaluHeaderSize {
return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), h265NaluHeaderSize)
}
payloadHeader := newH265NALUHeader(payload[0], payload[1])
if payloadHeader.F() {
return nil, errH265CorruptedPacket
}
switch {
case payloadHeader.IsPACIPacket():
decoded := &H265PACIPacket{}
if _, err := decoded.Unmarshal(payload); err != nil {
return nil, err
}
p.packet = decoded
case payloadHeader.IsFragmentationUnit():
decoded := &H265FragmentationUnitPacket{}
decoded.WithDONL(p.mightNeedDONL)
if _, err := decoded.Unmarshal(payload); err != nil {
return nil, err
}
p.packet = decoded
case payloadHeader.IsAggregationPacket():
decoded := &H265AggregationPacket{}
decoded.WithDONL(p.mightNeedDONL)
if _, err := decoded.Unmarshal(payload); err != nil {
return nil, err
}
p.packet = decoded
default:
decoded := &H265SingleNALUnitPacket{}
decoded.WithDONL(p.mightNeedDONL)
if _, err := decoded.Unmarshal(payload); err != nil {
return nil, err
}
p.packet = decoded
}
return nil, nil
}
// Packet returns the populated packet.
// Must be casted to one of:
// - *H265SingleNALUnitPacket
// - *H265FragmentationUnitPacket
// - *H265AggregationPacket
// - *H265PACIPacket
// nolint:golint
func (p *H265Packet) Packet() isH265Packet {
return p.packet
}
// IsPartitionHead checks if this is the head of a packetized nalu stream.
func (*H265Packet) IsPartitionHead(payload []byte) bool {
if len(payload) < 3 {
return false
}
if H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])).Type() == h265NaluFragmentationUnitType {
return H265FragmentationUnitHeader(payload[2]).S()
}
return true
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
// OpusPayloader payloads Opus packets
type OpusPayloader struct{}
// Payload fragments an Opus packet across one or more byte arrays
func (p *OpusPayloader) Payload(_ uint16, 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
audioDepacketizer
}
// 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.
//
// Deprecated: replaced by OpusPacket.IsPartitionHead()
type OpusPartitionHeadChecker struct{}
// IsPartitionHead checks whether if this is a head of the Opus partition.
//
// Deprecated: replaced by OpusPacket.IsPartitionHead()
func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool {
return (&OpusPacket{}).IsPartitionHead(packet)
}

234
server/vendor/github.com/pion/rtp/codecs/vp8_packet.go generated vendored Normal file
View File

@@ -0,0 +1,234 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
// VP8Payloader payloads VP8 packets
type VP8Payloader struct {
EnablePictureID bool
pictureID uint16
}
const (
vp8HeaderSize = 1
)
// Payload fragments a VP8 packet across one or more byte arrays
func (p *VP8Payloader) Payload(mtu uint16, 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.
*/
usingHeaderSize := vp8HeaderSize
if p.EnablePictureID {
switch {
case p.pictureID == 0:
case p.pictureID < 128:
usingHeaderSize = vp8HeaderSize + 2
default:
usingHeaderSize = vp8HeaderSize + 3
}
}
maxFragmentSize := int(mtu) - usingHeaderSize
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
}
first := true
for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
out := make([]byte, usingHeaderSize+currentFragmentSize)
if first {
out[0] = 0x10
first = false
}
if p.EnablePictureID {
switch usingHeaderSize {
case vp8HeaderSize:
case vp8HeaderSize + 2:
out[0] |= 0x80
out[1] |= 0x80
out[2] |= uint8(p.pictureID & 0x7F)
case vp8HeaderSize + 3:
out[0] |= 0x80
out[1] |= 0x80
out[2] |= 0x80 | uint8((p.pictureID>>8)&0x7F)
out[3] |= uint8(p.pictureID & 0xFF)
}
}
copy(out[usingHeaderSize:], payloadData[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
p.pictureID++
p.pictureID &= 0x7FFF
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 control bits present */
N uint8 /* when set to 1 this frame can be discarded */
S uint8 /* start of VP8 partition */
PID uint8 /* partition index */
// Extended control bits
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 */
// Optional extension
PictureID uint16 /* 8 or 16 bits, picture ID */
TL0PICIDX uint8 /* 8 bits temporal level zero index */
TID uint8 /* 2 bits temporal layer index */
Y uint8 /* 1 bit layer sync bit */
KEYIDX uint8 /* 5 bits temporal key frame index */
Payload []byte
videoDepacketizer
}
// 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) { //nolint: gocognit
if payload == nil {
return nil, errNilPacket
}
payloadLen := len(payload)
payloadIndex := 0
if payloadIndex >= payloadLen {
return nil, errShortPacket
}
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 {
if payloadIndex >= payloadLen {
return nil, errShortPacket
}
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++
} else {
p.I = 0
p.L = 0
p.T = 0
p.K = 0
}
if p.I == 1 { // PID present?
if payloadIndex >= payloadLen {
return nil, errShortPacket
}
if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit
if payloadIndex+1 >= payloadLen {
return nil, errShortPacket
}
p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1])
payloadIndex += 2
} else {
p.PictureID = uint16(payload[payloadIndex])
payloadIndex++
}
} else {
p.PictureID = 0
}
if p.L == 1 {
if payloadIndex >= payloadLen {
return nil, errShortPacket
}
p.TL0PICIDX = payload[payloadIndex]
payloadIndex++
} else {
p.TL0PICIDX = 0
}
if p.T == 1 || p.K == 1 {
if payloadIndex >= payloadLen {
return nil, errShortPacket
}
if p.T == 1 {
p.TID = payload[payloadIndex] >> 6
p.Y = (payload[payloadIndex] >> 5) & 0x1
} else {
p.TID = 0
p.Y = 0
}
if p.K == 1 {
p.KEYIDX = payload[payloadIndex] & 0x1F
} else {
p.KEYIDX = 0
}
payloadIndex++
} else {
p.TID = 0
p.Y = 0
p.KEYIDX = 0
}
p.Payload = payload[payloadIndex:]
return p.Payload, nil
}
// VP8PartitionHeadChecker checks VP8 partition head
//
// Deprecated: replaced by VP8Packet.IsPartitionHead()
type VP8PartitionHeadChecker struct{}
// IsPartitionHead checks whether if this is a head of the VP8 partition.
//
// Deprecated: replaced by VP8Packet.IsPartitionHead()
func (*VP8PartitionHeadChecker) IsPartitionHead(packet []byte) bool {
return (&VP8Packet{}).IsPartitionHead(packet)
}
// IsPartitionHead checks whether if this is a head of the VP8 partition
func (*VP8Packet) IsPartitionHead(payload []byte) bool {
if len(payload) < 1 {
return false
}
return (payload[0] & 0x10) != 0
}

65
server/vendor/github.com/pion/rtp/codecs/vp9/bits.go generated vendored Normal file
View File

@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package vp9
import "errors"
var errNotEnoughBits = errors.New("not enough bits")
func hasSpace(buf []byte, pos int, n int) error {
if n > ((len(buf) * 8) - pos) {
return errNotEnoughBits
}
return nil
}
func readFlag(buf []byte, pos *int) (bool, error) {
err := hasSpace(buf, *pos, 1)
if err != nil {
return false, err
}
return readFlagUnsafe(buf, pos), nil
}
func readFlagUnsafe(buf []byte, pos *int) bool {
b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01
*pos++
return b == 1
}
func readBits(buf []byte, pos *int, n int) (uint64, error) {
err := hasSpace(buf, *pos, n)
if err != nil {
return 0, err
}
return readBitsUnsafe(buf, pos, n), nil
}
func readBitsUnsafe(buf []byte, pos *int, n int) uint64 {
res := 8 - (*pos & 0x07)
if n < res {
v := uint64((buf[*pos>>0x03] >> (res - n)) & (1<<n - 1))
*pos += n
return v
}
v := uint64(buf[*pos>>0x03] & (1<<res - 1))
*pos += res
n -= res
for n >= 8 {
v = (v << 8) | uint64(buf[*pos>>0x03])
*pos += 8
n -= 8
}
if n > 0 {
v = (v << n) | uint64(buf[*pos>>0x03]>>(8-n))
*pos += n
}
return v
}

221
server/vendor/github.com/pion/rtp/codecs/vp9/header.go generated vendored Normal file
View File

@@ -0,0 +1,221 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package vp9 contains a VP9 header parser.
package vp9
import (
"errors"
)
var (
errInvalidFrameMarker = errors.New("invalid frame marker")
errWrongFrameSyncByte0 = errors.New("wrong frame_sync_byte_0")
errWrongFrameSyncByte1 = errors.New("wrong frame_sync_byte_1")
errWrongFrameSyncByte2 = errors.New("wrong frame_sync_byte_2")
)
// HeaderColorConfig is the color_config member of an header.
type HeaderColorConfig struct {
TenOrTwelveBit bool
BitDepth uint8
ColorSpace uint8
ColorRange bool
SubsamplingX bool
SubsamplingY bool
}
func (c *HeaderColorConfig) unmarshal(profile uint8, buf []byte, pos *int) error {
if profile >= 2 {
var err error
c.TenOrTwelveBit, err = readFlag(buf, pos)
if err != nil {
return err
}
if c.TenOrTwelveBit {
c.BitDepth = 12
} else {
c.BitDepth = 10
}
} else {
c.BitDepth = 8
}
tmp, err := readBits(buf, pos, 3)
if err != nil {
return err
}
c.ColorSpace = uint8(tmp)
if c.ColorSpace != 7 {
var err error
c.ColorRange, err = readFlag(buf, pos)
if err != nil {
return err
}
if profile == 1 || profile == 3 {
err := hasSpace(buf, *pos, 3)
if err != nil {
return err
}
c.SubsamplingX = readFlagUnsafe(buf, pos)
c.SubsamplingY = readFlagUnsafe(buf, pos)
*pos++
} else {
c.SubsamplingX = true
c.SubsamplingY = true
}
} else {
c.ColorRange = true
if profile == 1 || profile == 3 {
c.SubsamplingX = false
c.SubsamplingY = false
err := hasSpace(buf, *pos, 1)
if err != nil {
return err
}
*pos++
}
}
return nil
}
// HeaderFrameSize is the frame_size member of an header.
type HeaderFrameSize struct {
FrameWidthMinus1 uint16
FrameHeightMinus1 uint16
}
func (s *HeaderFrameSize) unmarshal(buf []byte, pos *int) error {
err := hasSpace(buf, *pos, 32)
if err != nil {
return err
}
s.FrameWidthMinus1 = uint16(readBitsUnsafe(buf, pos, 16))
s.FrameHeightMinus1 = uint16(readBitsUnsafe(buf, pos, 16))
return nil
}
// Header is a VP9 Frame header.
// Specification:
// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf
type Header struct {
Profile uint8
ShowExistingFrame bool
FrameToShowMapIdx uint8
NonKeyFrame bool
ShowFrame bool
ErrorResilientMode bool
ColorConfig *HeaderColorConfig
FrameSize *HeaderFrameSize
}
// Unmarshal decodes a Header.
func (h *Header) Unmarshal(buf []byte) error {
pos := 0
err := hasSpace(buf, pos, 4)
if err != nil {
return err
}
frameMarker := readBitsUnsafe(buf, &pos, 2)
if frameMarker != 2 {
return errInvalidFrameMarker
}
profileLowBit := uint8(readBitsUnsafe(buf, &pos, 1))
profileHighBit := uint8(readBitsUnsafe(buf, &pos, 1))
h.Profile = profileHighBit<<1 + profileLowBit
if h.Profile == 3 {
err = hasSpace(buf, pos, 1)
if err != nil {
return err
}
pos++
}
h.ShowExistingFrame, err = readFlag(buf, &pos)
if err != nil {
return err
}
if h.ShowExistingFrame {
var tmp uint64
tmp, err = readBits(buf, &pos, 3)
if err != nil {
return err
}
h.FrameToShowMapIdx = uint8(tmp)
return nil
}
err = hasSpace(buf, pos, 3)
if err != nil {
return err
}
h.NonKeyFrame = readFlagUnsafe(buf, &pos)
h.ShowFrame = readFlagUnsafe(buf, &pos)
h.ErrorResilientMode = readFlagUnsafe(buf, &pos)
if !h.NonKeyFrame {
err := hasSpace(buf, pos, 24)
if err != nil {
return err
}
frameSyncByte0 := uint8(readBitsUnsafe(buf, &pos, 8))
if frameSyncByte0 != 0x49 {
return errWrongFrameSyncByte0
}
frameSyncByte1 := uint8(readBitsUnsafe(buf, &pos, 8))
if frameSyncByte1 != 0x83 {
return errWrongFrameSyncByte1
}
frameSyncByte2 := uint8(readBitsUnsafe(buf, &pos, 8))
if frameSyncByte2 != 0x42 {
return errWrongFrameSyncByte2
}
h.ColorConfig = &HeaderColorConfig{}
err = h.ColorConfig.unmarshal(h.Profile, buf, &pos)
if err != nil {
return err
}
h.FrameSize = &HeaderFrameSize{}
err = h.FrameSize.unmarshal(buf, &pos)
if err != nil {
return err
}
}
return nil
}
// Width returns the video width.
func (h Header) Width() uint16 {
if h.FrameSize == nil {
return 0
}
return h.FrameSize.FrameWidthMinus1 + 1
}
// Height returns the video height.
func (h Header) Height() uint16 {
if h.FrameSize == nil {
return 0
}
return h.FrameSize.FrameHeightMinus1 + 1
}

511
server/vendor/github.com/pion/rtp/codecs/vp9_packet.go generated vendored Normal file
View File

@@ -0,0 +1,511 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package codecs
import (
"github.com/pion/randutil"
"github.com/pion/rtp/codecs/vp9"
)
// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals
// VP9Payloader payloads VP9 packets
type VP9Payloader struct {
// whether to use flexible mode or non-flexible mode.
FlexibleMode bool
// InitialPictureIDFn is a function that returns random initial picture ID.
InitialPictureIDFn func() uint16
pictureID uint16
initialized bool
}
const (
maxSpatialLayers = 5
maxVP9RefPics = 3
)
// Payload fragments an VP9 packet across one or more byte arrays
func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte {
if !p.initialized {
if p.InitialPictureIDFn == nil {
p.InitialPictureIDFn = func() uint16 {
return uint16(globalMathRandomGenerator.Intn(0x7FFF))
}
}
p.pictureID = p.InitialPictureIDFn() & 0x7FFF
p.initialized = true
}
var payloads [][]byte
if p.FlexibleMode {
payloads = p.payloadFlexible(mtu, payload)
} else {
payloads = p.payloadNonFlexible(mtu, payload)
}
p.pictureID++
if p.pictureID >= 0x8000 {
p.pictureID = 0
}
return payloads
}
func (p *VP9Payloader) payloadFlexible(mtu uint16, payload []byte) [][]byte {
/*
* Flexible mode (F=1)
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |I|P|L|F|B|E|V|Z| (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 |
* | .. |
* +-+-+-+-+-+-+-+-+
*/
headerSize := 3
maxFragmentSize := int(mtu) - headerSize
payloadDataRemaining := len(payload)
payloadDataIndex := 0
var payloads [][]byte
if min(maxFragmentSize, payloadDataRemaining) <= 0 {
return [][]byte{}
}
for payloadDataRemaining > 0 {
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
out := make([]byte, headerSize+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[headerSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
return payloads
}
func (p *VP9Payloader) payloadNonFlexible(mtu uint16, payload []byte) [][]byte {
/*
* Non-flexible mode (F=0)
* 0 1 2 3 4 5 6 7
* +-+-+-+-+-+-+-+-+
* |I|P|L|F|B|E|V|Z| (REQUIRED)
* +-+-+-+-+-+-+-+-+
* I: |M| PICTURE ID | (RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* M: | EXTENDED PID | (RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED)
* +-+-+-+-+-+-+-+-+
* | TL0PICIDX | (CONDITIONALLY REQUIRED)
* +-+-+-+-+-+-+-+-+
* V: | SS |
* | .. |
* +-+-+-+-+-+-+-+-+
*/
var h vp9.Header
err := h.Unmarshal(payload)
if err != nil {
return [][]byte{}
}
payloadDataRemaining := len(payload)
payloadDataIndex := 0
var payloads [][]byte
for payloadDataRemaining > 0 {
var headerSize int
if !h.NonKeyFrame && payloadDataIndex == 0 {
headerSize = 3 + 8
} else {
headerSize = 3
}
maxFragmentSize := int(mtu) - headerSize
currentFragmentSize := min(maxFragmentSize, payloadDataRemaining)
if currentFragmentSize <= 0 {
return [][]byte{}
}
out := make([]byte, headerSize+currentFragmentSize)
out[0] = 0x80 | 0x01 // I=1, Z=1
if h.NonKeyFrame {
out[0] |= 0x40 // P=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)
off := 3
if !h.NonKeyFrame && payloadDataIndex == 0 {
out[0] |= 0x02 // V=1
out[off] = 0x10 | 0x08 // N_S=0, Y=1, G=1
off++
width := h.Width()
out[off] = byte(width >> 8)
off++
out[off] = byte(width & 0xFF)
off++
height := h.Height()
out[off] = byte(height >> 8)
off++
out[off] = byte(height & 0xFF)
off++
out[off] = 0x01 // N_G=1
off++
out[off] = 1<<4 | 1<<2 // TID=0, U=1, R=1
off++
out[off] = 0x01 // P_DIFF=1
}
copy(out[headerSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize])
payloads = append(payloads, out)
payloadDataRemaining -= currentFragmentSize
payloadDataIndex += currentFragmentSize
}
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
Z bool // Not a reference frame for upper spatial layers
// 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
videoDepacketizer
}
// 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
p.Z = packet[0]&0x01 != 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++ {
if len(packet) <= (pos + 3) {
return pos, errShortPacket
}
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 {
if len(packet) <= pos {
return pos, errShortPacket
}
p.NG = packet[pos]
pos++
}
for i := 0; i < int(p.NG); i++ {
if len(packet) <= pos {
return pos, errShortPacket
}
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{})
if len(packet) <= (pos + int(R) - 1) {
return pos, errShortPacket
}
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.
//
// Deprecated: replaced by VP9Packet.IsPartitionHead()
type VP9PartitionHeadChecker struct{}
// IsPartitionHead checks whether if this is a head of the VP9 partition.
//
// Deprecated: replaced by VP9Packet.IsPartitionHead()
func (*VP9PartitionHeadChecker) IsPartitionHead(packet []byte) bool {
return (&VP9Packet{}).IsPartitionHead(packet)
}
// IsPartitionHead checks whether if this is a head of the VP9 partition
func (*VP9Packet) IsPartitionHead(payload []byte) bool {
if len(payload) < 1 {
return false
}
return (payload[0] & 0x08) != 0
}

20
server/vendor/github.com/pion/rtp/depacketizer.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
// Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload
type Depacketizer interface {
// Unmarshal parses the RTP payload and returns media.
// Metadata may be stored on the Depacketizer itself
Unmarshal(packet []byte) ([]byte, error)
// Checks if the packet is at the beginning of a partition. This
// should return false if the result could not be determined, in
// which case the caller will detect timestamp discontinuities.
IsPartitionHead(payload []byte) bool
// Checks if the packet is at the end of a partition. This should
// return false if the result could not be determined.
IsPartitionTail(marker bool, payload []byte) bool
}

26
server/vendor/github.com/pion/rtp/error.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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")
errInvalidRTPPadding = errors.New("invalid RTP padding")
)

353
server/vendor/github.com/pion/rtp/header_extension.go generated vendored Normal file
View File

@@ -0,0 +1,353 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"encoding/binary"
"fmt"
"io"
)
const (
headerExtensionProfileOneByte = 0xBEDE
headerExtensionProfileTwoByte = 0x1000
headerExtensionIDReserved = 0xF
)
// HeaderExtension represents an RTP extension header.
type HeaderExtension interface {
Set(id uint8, payload []byte) error
GetIDs() []uint8
Get(id uint8) []byte
Del(id uint8) error
Unmarshal(buf []byte) (int, error)
Marshal() ([]byte, error)
MarshalTo(buf []byte) (int, error)
MarshalSize() int
}
// OneByteHeaderExtension is an RFC8285 one-byte header extension.
type OneByteHeaderExtension struct {
payload []byte
}
// Set sets the extension payload for the specified ID.
func (e *OneByteHeaderExtension) Set(id uint8, buf []byte) error {
if id < 1 || id > 14 {
return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderIDRange, id)
}
if len(buf) > 16 {
return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderSize, len(buf))
}
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n] >> 4
payloadLen := int(e.payload[n]&^0xF0 + 1)
n++
if extid == id {
e.payload = append(e.payload[:n+1], append(buf, e.payload[n+1+payloadLen:]...)...)
return nil
}
n += payloadLen
}
e.payload = append(e.payload, (id<<4 | uint8(len(buf)-1)))
e.payload = append(e.payload, buf...)
binary.BigEndian.PutUint16(e.payload[2:4], binary.BigEndian.Uint16(e.payload[2:4])+1)
return nil
}
// GetIDs returns the available IDs.
func (e *OneByteHeaderExtension) GetIDs() []uint8 {
ids := make([]uint8, 0, binary.BigEndian.Uint16(e.payload[2:4]))
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n] >> 4
payloadLen := int(e.payload[n]&^0xF0 + 1)
n++
if extid == headerExtensionIDReserved {
break
}
ids = append(ids, extid)
n += payloadLen
}
return ids
}
// Get returns the payload of the extension with the given ID.
func (e *OneByteHeaderExtension) Get(id uint8) []byte {
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n] >> 4
payloadLen := int(e.payload[n]&^0xF0 + 1)
n++
if extid == id {
return e.payload[n : n+payloadLen]
}
n += payloadLen
}
return nil
}
// Del deletes the extension with the specified ID.
func (e *OneByteHeaderExtension) Del(id uint8) error {
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n] >> 4
payloadLen := int(e.payload[n]&^0xF0 + 1)
if extid == id {
e.payload = append(e.payload[:n], e.payload[n+1+payloadLen:]...)
return nil
}
n += payloadLen + 1
}
return errHeaderExtensionNotFound
}
// Unmarshal parses the extension payload.
func (e *OneByteHeaderExtension) Unmarshal(buf []byte) (int, error) {
profile := binary.BigEndian.Uint16(buf[0:2])
if profile != headerExtensionProfileOneByte {
return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2])
}
e.payload = buf
return len(buf), nil
}
// Marshal returns the extension payload.
func (e OneByteHeaderExtension) Marshal() ([]byte, error) {
return e.payload, nil
}
// MarshalTo writes the extension payload to the given buffer.
func (e OneByteHeaderExtension) MarshalTo(buf []byte) (int, error) {
size := e.MarshalSize()
if size > len(buf) {
return 0, io.ErrShortBuffer
}
return copy(buf, e.payload), nil
}
// MarshalSize returns the size of the extension payload.
func (e OneByteHeaderExtension) MarshalSize() int {
return len(e.payload)
}
// TwoByteHeaderExtension is an RFC8285 two-byte header extension.
type TwoByteHeaderExtension struct {
payload []byte
}
// Set sets the extension payload for the specified ID.
func (e *TwoByteHeaderExtension) Set(id uint8, buf []byte) error {
if id < 1 || id > 255 {
return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id)
}
if len(buf) > 255 {
return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderSize, len(buf))
}
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n]
n++
payloadLen := int(e.payload[n])
n++
if extid == id {
e.payload = append(e.payload[:n+2], append(buf, e.payload[n+2+payloadLen:]...)...)
return nil
}
n += payloadLen
}
e.payload = append(e.payload, id, uint8(len(buf)))
e.payload = append(e.payload, buf...)
binary.BigEndian.PutUint16(e.payload[2:4], binary.BigEndian.Uint16(e.payload[2:4])+1)
return nil
}
// GetIDs returns the available IDs.
func (e *TwoByteHeaderExtension) GetIDs() []uint8 {
ids := make([]uint8, 0, binary.BigEndian.Uint16(e.payload[2:4]))
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n]
n++
payloadLen := int(e.payload[n])
n++
ids = append(ids, extid)
n += payloadLen
}
return ids
}
// Get returns the payload of the extension with the given ID.
func (e *TwoByteHeaderExtension) Get(id uint8) []byte {
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n]
n++
payloadLen := int(e.payload[n])
n++
if extid == id {
return e.payload[n : n+payloadLen]
}
n += payloadLen
}
return nil
}
// Del deletes the extension with the specified ID.
func (e *TwoByteHeaderExtension) Del(id uint8) error {
for n := 4; n < len(e.payload); {
if e.payload[n] == 0x00 { // padding
n++
continue
}
extid := e.payload[n]
payloadLen := int(e.payload[n+1])
if extid == id {
e.payload = append(e.payload[:n], e.payload[n+2+payloadLen:]...)
return nil
}
n += payloadLen + 2
}
return errHeaderExtensionNotFound
}
// Unmarshal parses the extension payload.
func (e *TwoByteHeaderExtension) Unmarshal(buf []byte) (int, error) {
profile := binary.BigEndian.Uint16(buf[0:2])
if profile != headerExtensionProfileTwoByte {
return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2])
}
e.payload = buf
return len(buf), nil
}
// Marshal returns the extension payload.
func (e TwoByteHeaderExtension) Marshal() ([]byte, error) {
return e.payload, nil
}
// MarshalTo marshals the extension to the given buffer.
func (e TwoByteHeaderExtension) MarshalTo(buf []byte) (int, error) {
size := e.MarshalSize()
if size > len(buf) {
return 0, io.ErrShortBuffer
}
return copy(buf, e.payload), nil
}
// MarshalSize returns the size of the extension payload.
func (e TwoByteHeaderExtension) MarshalSize() int {
return len(e.payload)
}
// RawExtension represents an RFC3550 header extension.
type RawExtension struct {
payload []byte
}
// Set sets the extension payload for the specified ID.
func (e *RawExtension) Set(id uint8, payload []byte) error {
if id != 0 {
return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id)
}
e.payload = payload
return nil
}
// GetIDs returns the available IDs.
func (e *RawExtension) GetIDs() []uint8 {
return []uint8{0}
}
// Get returns the payload of the extension with the given ID.
func (e *RawExtension) Get(id uint8) []byte {
if id == 0 {
return e.payload
}
return nil
}
// Del deletes the extension with the specified ID.
func (e *RawExtension) Del(id uint8) error {
if id == 0 {
e.payload = nil
return nil
}
return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id)
}
// Unmarshal parses the extension from the given buffer.
func (e *RawExtension) Unmarshal(buf []byte) (int, error) {
profile := binary.BigEndian.Uint16(buf[0:2])
if profile == headerExtensionProfileOneByte || profile == headerExtensionProfileTwoByte {
return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2])
}
e.payload = buf
return len(buf), nil
}
// Marshal returns the raw extension payload.
func (e RawExtension) Marshal() ([]byte, error) {
return e.payload, nil
}
// MarshalTo marshals the extension to the given buffer.
func (e RawExtension) MarshalTo(buf []byte) (int, error) {
size := e.MarshalSize()
if size > len(buf) {
return 0, io.ErrShortBuffer
}
return copy(buf, e.payload), nil
}
// MarshalSize returns the size of the extension when marshaled.
func (e RawExtension) MarshalSize() int {
return len(e.payload)
}

540
server/vendor/github.com/pion/rtp/packet.go generated vendored Normal file
View File

@@ -0,0 +1,540 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"encoding/binary"
"fmt"
"io"
)
// Extension RTP Header extension
type Extension struct {
id uint8
payload []byte
}
// Header represents an RTP packet header
type Header struct {
Version uint8
Padding bool
Extension bool
Marker bool
PayloadType uint8
SequenceNumber uint16
Timestamp uint32
SSRC uint32
CSRC []uint32
ExtensionProfile uint16
Extensions []Extension
// Deprecated: will be removed in a future version.
PayloadOffset int
}
// Packet represents an RTP Packet
type Packet struct {
Header
Payload []byte
PaddingSize byte
// Deprecated: will be removed in a future version.
Raw []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.
// It returns the number of bytes read n and any error.
func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit
if len(buf) < headerLength {
return 0, fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), 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 = buf[0] >> versionShift & versionMask
h.Padding = (buf[0] >> paddingShift & paddingMask) > 0
h.Extension = (buf[0] >> extensionShift & extensionMask) > 0
nCSRC := int(buf[0] & ccMask)
if cap(h.CSRC) < nCSRC || h.CSRC == nil {
h.CSRC = make([]uint32, nCSRC)
} else {
h.CSRC = h.CSRC[:nCSRC]
}
n = csrcOffset + (nCSRC * csrcLength)
if len(buf) < n {
return n, fmt.Errorf("size %d < %d: %w", len(buf), n,
errHeaderSizeInsufficient)
}
h.Marker = (buf[1] >> markerShift & markerMask) > 0
h.PayloadType = buf[1] & ptMask
h.SequenceNumber = binary.BigEndian.Uint16(buf[seqNumOffset : seqNumOffset+seqNumLength])
h.Timestamp = binary.BigEndian.Uint32(buf[timestampOffset : timestampOffset+timestampLength])
h.SSRC = binary.BigEndian.Uint32(buf[ssrcOffset : ssrcOffset+ssrcLength])
for i := range h.CSRC {
offset := csrcOffset + (i * csrcLength)
h.CSRC[i] = binary.BigEndian.Uint32(buf[offset:])
}
if h.Extensions != nil {
h.Extensions = h.Extensions[:0]
}
if h.Extension {
if expected := n + 4; len(buf) < expected {
return n, fmt.Errorf("size %d < %d: %w",
len(buf), expected,
errHeaderSizeInsufficientForExtension,
)
}
h.ExtensionProfile = binary.BigEndian.Uint16(buf[n:])
n += 2
extensionLength := int(binary.BigEndian.Uint16(buf[n:])) * 4
n += 2
extensionEnd := n + extensionLength
if len(buf) < extensionEnd {
return n, fmt.Errorf("size %d < %d: %w", len(buf), extensionEnd, errHeaderSizeInsufficientForExtension)
}
if h.ExtensionProfile == extensionProfileOneByte || h.ExtensionProfile == extensionProfileTwoByte {
var (
extid uint8
payloadLen int
)
for n < extensionEnd {
if buf[n] == 0x00 { // padding
n++
continue
}
if h.ExtensionProfile == extensionProfileOneByte {
extid = buf[n] >> 4
payloadLen = int(buf[n]&^0xF0 + 1)
n++
if extid == extensionIDReserved {
break
}
} else {
extid = buf[n]
n++
if len(buf) <= n {
return n, fmt.Errorf("size %d < %d: %w", len(buf), n, errHeaderSizeInsufficientForExtension)
}
payloadLen = int(buf[n])
n++
}
if extensionPayloadEnd := n + payloadLen; len(buf) <= extensionPayloadEnd {
return n, fmt.Errorf("size %d < %d: %w", len(buf), extensionPayloadEnd, errHeaderSizeInsufficientForExtension)
}
extension := Extension{id: extid, payload: buf[n : n+payloadLen]}
h.Extensions = append(h.Extensions, extension)
n += payloadLen
}
} else {
// RFC3550 Extension
extension := Extension{id: 0, payload: buf[n:extensionEnd]}
h.Extensions = append(h.Extensions, extension)
n += len(h.Extensions[0].payload)
}
}
return n, nil
}
// Unmarshal parses the passed byte slice and stores the result in the Packet.
func (p *Packet) Unmarshal(buf []byte) error {
n, err := p.Header.Unmarshal(buf)
if err != nil {
return err
}
end := len(buf)
if p.Header.Padding {
if end <= n {
return errTooSmall
}
p.PaddingSize = buf[end-1]
end -= int(p.PaddingSize)
}
if end < n {
return errTooSmall
}
p.Payload = buf[n:end]
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++
}
}
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 payloadLen := len(payload); {
case payloadLen <= 16:
h.ExtensionProfile = extensionProfileOneByte
case payloadLen > 16 && payloadLen < 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) {
if p.Header.Padding && p.PaddingSize == 0 {
return 0, errInvalidRTPPadding
}
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)+int(p.PaddingSize) > len(buf) {
return 0, io.ErrShortBuffer
}
m := copy(buf[n:], p.Payload)
if p.Header.Padding {
buf[n+m+int(p.PaddingSize-1)] = p.PaddingSize
}
return n + m + int(p.PaddingSize), nil
}
// MarshalSize returns the size of the packet once marshaled.
func (p Packet) MarshalSize() int {
return p.Header.MarshalSize() + len(p.Payload) + int(p.PaddingSize)
}
// Clone returns a deep copy of p.
func (p Packet) Clone() *Packet {
clone := &Packet{}
clone.Header = p.Header.Clone()
if p.Payload != nil {
clone.Payload = make([]byte, len(p.Payload))
copy(clone.Payload, p.Payload)
}
clone.PaddingSize = p.PaddingSize
return clone
}
// Clone returns a deep copy h.
func (h Header) Clone() Header {
clone := h
if h.CSRC != nil {
clone.CSRC = make([]uint32, len(h.CSRC))
copy(clone.CSRC, h.CSRC)
}
if h.Extensions != nil {
ext := make([]Extension, len(h.Extensions))
for i, e := range h.Extensions {
ext[i] = e
if e.payload != nil {
ext[i].payload = make([]byte, len(e.payload))
copy(ext[i].payload, e.payload)
}
}
clone.Extensions = ext
}
return clone
}

138
server/vendor/github.com/pion/rtp/packetizer.go generated vendored Normal file
View File

@@ -0,0 +1,138 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"time"
)
// Payloader payloads a byte array for use as rtp.Packet payloads
type Payloader interface {
Payload(mtu uint16, payload []byte) [][]byte
}
// Packetizer packetizes a payload
type Packetizer interface {
Packetize(payload []byte, samples uint32) []*Packet
GeneratePadding(samples uint32) []*Packet
EnableAbsSendTime(value int)
SkipSamples(skippedSamples uint32)
}
type packetizer struct {
MTU uint16
PayloadType uint8
SSRC uint32
Payloader Payloader
Sequencer Sequencer
Timestamp uint32
// Deprecated: will be removed in a future version.
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 uint16, 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,
CSRC: []uint32{},
},
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
}
// GeneratePadding returns required padding-only packages
func (p *packetizer) GeneratePadding(samples uint32) []*Packet {
// Guard against an empty payload
if samples == 0 {
return nil
}
packets := make([]*Packet, samples)
for i := 0; i < int(samples); i++ {
pp := make([]byte, 255)
pp[254] = 255
packets[i] = &Packet{
Header: Header{
Version: 2,
Padding: true,
Extension: false,
Marker: false,
PayloadType: p.PayloadType,
SequenceNumber: p.Sequencer.NextSequenceNumber(),
Timestamp: p.Timestamp, // Use latest timestamp
SSRC: p.SSRC,
CSRC: []uint32{},
},
Payload: pp,
}
}
return packets
}
// SkipSamples causes a gap in sample count between Packetize requests so the
// RTP payloads produced have a gap in timestamps
func (p *packetizer) SkipSamples(skippedSamples uint32) {
p.Timestamp += skippedSamples
}

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
// PartitionHeadChecker is the interface that checks whether the packet is keyframe or not
type PartitionHeadChecker interface {
IsPartitionHead([]byte) bool
}

68
server/vendor/github.com/pion/rtp/payload_types.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// SPDX-FileCopyrightText: 2024 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml
// https://en.wikipedia.org/wiki/RTP_payload_formats
// Audio Payload Types as defined in https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml
const (
// PayloadTypePCMU is a payload type for ITU-T G.711 PCM μ-Law audio 64 kbit/s (RFC 3551).
PayloadTypePCMU = 0
// PayloadTypeGSM is a payload type for European GSM Full Rate audio 13 kbit/s (GSM 06.10).
PayloadTypeGSM = 3
// PayloadTypeG723 is a payload type for ITU-T G.723.1 audio (RFC 3551).
PayloadTypeG723 = 4
// PayloadTypeDVI4_8000 is a payload type for IMA ADPCM audio 32 kbit/s (RFC 3551).
PayloadTypeDVI4_8000 = 5
// PayloadTypeDVI4_16000 is a payload type for IMA ADPCM audio 64 kbit/s (RFC 3551).
PayloadTypeDVI4_16000 = 6
// PayloadTypeLPC is a payload type for Experimental Linear Predictive Coding audio 5.6 kbit/s (RFC 3551).
PayloadTypeLPC = 7
// PayloadTypePCMA is a payload type for ITU-T G.711 PCM A-Law audio 64 kbit/s (RFC 3551).
PayloadTypePCMA = 8
// PayloadTypeG722 is a payload type for ITU-T G.722 audio 64 kbit/s (RFC 3551).
PayloadTypeG722 = 9
// PayloadTypeL16Stereo is a payload type for Linear PCM 16-bit Stereo audio 1411.2 kbit/s, uncompressed (RFC 3551).
PayloadTypeL16Stereo = 10
// PayloadTypeL16Mono is a payload type for Linear PCM 16-bit audio 705.6 kbit/s, uncompressed (RFC 3551).
PayloadTypeL16Mono = 11
// PayloadTypeQCELP is a payload type for Qualcomm Code Excited Linear Prediction (RFC 2658, RFC 3551).
PayloadTypeQCELP = 12
// PayloadTypeCN is a payload type for Comfort noise (RFC 3389).
PayloadTypeCN = 13
// PayloadTypeMPA is a payload type for MPEG-1 or MPEG-2 audio only (RFC 3551, RFC 2250).
PayloadTypeMPA = 14
// PayloadTypeG728 is a payload type for ITU-T G.728 audio 16 kbit/s (RFC 3551).
PayloadTypeG728 = 15
// PayloadTypeDVI4_11025 is a payload type for IMA ADPCM audio 44.1 kbit/s (RFC 3551).
PayloadTypeDVI4_11025 = 16
// PayloadTypeDVI4_22050 is a payload type for IMA ADPCM audio 88.2 kbit/s (RFC 3551).
PayloadTypeDVI4_22050 = 17
// PayloadTypeG729 is a payload type for ITU-T G.729 and G.729a audio 8 kbit/s (RFC 3551, RFC 3555).
PayloadTypeG729 = 18
)
// Video Payload Types as defined in https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml
const (
// PayloadTypeCELLB is a payload type for Sun CellB video (RFC 2029).
PayloadTypeCELLB = 25
// PayloadTypeJPEG is a payload type for JPEG video (RFC 2435).
PayloadTypeJPEG = 26
// PayloadTypeNV is a payload type for Xerox PARC's Network Video (nv, RFC 3551).
PayloadTypeNV = 28
// PayloadTypeH261 is a payload type for ITU-T H.261 video (RFC 4587).
PayloadTypeH261 = 31
// PayloadTypeMPV is a payload type for MPEG-1 and MPEG-2 video (RFC 2250).
PayloadTypeMPV = 32
// PayloadTypeMP2T is a payload type for MPEG-2 transport stream (RFC 2250).
PayloadTypeMP2T = 33
// PayloadTypeH263 is a payload type for H.263 video, first version (1996, RFC 3551, RFC 2190).
PayloadTypeH263 = 34
)
const (
// PayloadTypeFirstDynamic is a first non-static payload type.
PayloadTypeFirstDynamic = 35
)

View File

@@ -0,0 +1,50 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"encoding/binary"
"errors"
)
const (
playoutDelayExtensionSize = 3
playoutDelayMaxValue = (1 << 12) - 1
)
var errPlayoutDelayInvalidValue = errors.New("invalid playout delay value")
// PlayoutDelayExtension is a extension payload format in
// http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
// 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=2 | MIN delay | MAX delay |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type PlayoutDelayExtension struct {
minDelay, maxDelay uint16
}
// Marshal serializes the members to buffer
func (p PlayoutDelayExtension) Marshal() ([]byte, error) {
if p.minDelay > playoutDelayMaxValue || p.maxDelay > playoutDelayMaxValue {
return nil, errPlayoutDelayInvalidValue
}
return []byte{
byte(p.minDelay >> 4),
byte(p.minDelay<<4) | byte(p.maxDelay>>8),
byte(p.maxDelay),
}, nil
}
// Unmarshal parses the passed byte slice and stores the result in the members
func (p *PlayoutDelayExtension) Unmarshal(rawData []byte) error {
if len(rawData) < playoutDelayExtensionSize {
return errTooSmall
}
p.minDelay = binary.BigEndian.Uint16(rawData[0:2]) >> 4
p.maxDelay = binary.BigEndian.Uint16(rawData[1:3]) & 0x0FFF
return nil
}

11
server/vendor/github.com/pion/rtp/rand.go generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"github.com/pion/randutil"
)
// Use global random generator to properly seed by crypto grade random.
var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals

6
server/vendor/github.com/pion/rtp/renovate.json generated vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>pion/renovate-config"
]
}

5
server/vendor/github.com/pion/rtp/rtp.go generated vendored Normal file
View File

@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package rtp provides RTP packetizer and depacketizer
package rtp

60
server/vendor/github.com/pion/rtp/sequencer.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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,42 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
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
}

360
server/vendor/github.com/pion/rtp/vlaextension.go generated vendored Normal file
View File

@@ -0,0 +1,360 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package rtp
import (
"encoding/binary"
"errors"
"fmt"
"strings"
"github.com/pion/rtp/codecs/av1/obu"
)
var (
ErrVLATooShort = errors.New("VLA payload too short") // ErrVLATooShort is returned when payload is too short
ErrVLAInvalidStreamCount = errors.New("invalid RTP stream count in VLA") // ErrVLAInvalidStreamCount is returned when RTP stream count is invalid
ErrVLAInvalidStreamID = errors.New("invalid RTP stream ID in VLA") // ErrVLAInvalidStreamID is returned when RTP stream ID is invalid
ErrVLAInvalidSpatialID = errors.New("invalid spatial ID in VLA") // ErrVLAInvalidSpatialID is returned when spatial ID is invalid
ErrVLADuplicateSpatialID = errors.New("duplicate spatial ID in VLA") // ErrVLADuplicateSpatialID is returned when spatial ID is invalid
ErrVLAInvalidTemporalLayer = errors.New("invalid temporal layer in VLA") // ErrVLAInvalidTemporalLayer is returned when temporal layer is invalid
)
// SpatialLayer is a spatial layer in VLA.
type SpatialLayer struct {
RTPStreamID int
SpatialID int
TargetBitrates []int // target bitrates per temporal layer
// Following members are valid only when HasResolutionAndFramerate is true
Width int
Height int
Framerate int
}
// VLA is a Video Layer Allocation (VLA) extension.
// See https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00
type VLA struct {
RTPStreamID int // 0-origin RTP stream ID (RID) this allocation is sent on (0..3)
RTPStreamCount int // Number of RTP streams (1..4)
ActiveSpatialLayer []SpatialLayer
HasResolutionAndFramerate bool
}
type vlaMarshalingContext struct {
slMBs [4]uint8
sls [4][4]*SpatialLayer
commonSLBM uint8
encodedTargetBitrates [][]byte
requiredLen int
}
func (v VLA) preprocessForMashaling(ctx *vlaMarshalingContext) error {
for i := 0; i < len(v.ActiveSpatialLayer); i++ {
sl := v.ActiveSpatialLayer[i]
if sl.RTPStreamID < 0 || sl.RTPStreamID >= v.RTPStreamCount {
return fmt.Errorf("invalid RTP streamID %d:%w", sl.RTPStreamID, ErrVLAInvalidStreamID)
}
if sl.SpatialID < 0 || sl.SpatialID >= 4 {
return fmt.Errorf("invalid spatial ID %d: %w", sl.SpatialID, ErrVLAInvalidSpatialID)
}
if len(sl.TargetBitrates) == 0 || len(sl.TargetBitrates) > 4 {
return fmt.Errorf("invalid temporal layer count %d: %w", len(sl.TargetBitrates), ErrVLAInvalidTemporalLayer)
}
ctx.slMBs[sl.RTPStreamID] |= 1 << sl.SpatialID
if ctx.sls[sl.RTPStreamID][sl.SpatialID] != nil {
return fmt.Errorf("duplicate spatial layer: %w", ErrVLADuplicateSpatialID)
}
ctx.sls[sl.RTPStreamID][sl.SpatialID] = &sl
}
return nil
}
func (v VLA) encodeTargetBitrates(ctx *vlaMarshalingContext) {
for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ {
for spatialID := 0; spatialID < 4; spatialID++ {
if sl := ctx.sls[rtpStreamID][spatialID]; sl != nil {
for _, kbps := range sl.TargetBitrates {
leb128 := obu.WriteToLeb128(uint(kbps))
ctx.encodedTargetBitrates = append(ctx.encodedTargetBitrates, leb128)
ctx.requiredLen += len(leb128)
}
}
}
}
}
func (v VLA) analyzeVLAForMarshaling() (*vlaMarshalingContext, error) {
// Validate RTPStreamCount
if v.RTPStreamCount <= 0 || v.RTPStreamCount > 4 {
return nil, ErrVLAInvalidStreamCount
}
// Validate RTPStreamID
if v.RTPStreamID < 0 || v.RTPStreamID >= v.RTPStreamCount {
return nil, ErrVLAInvalidStreamID
}
ctx := &vlaMarshalingContext{}
err := v.preprocessForMashaling(ctx)
if err != nil {
return nil, err
}
ctx.commonSLBM = commonSLBMValues(ctx.slMBs[:])
// RID, NS, sl_bm fields
if ctx.commonSLBM != 0 {
ctx.requiredLen = 1
} else {
ctx.requiredLen = 3
}
// #tl fields
ctx.requiredLen += (len(v.ActiveSpatialLayer)-1)/4 + 1
v.encodeTargetBitrates(ctx)
if v.HasResolutionAndFramerate {
ctx.requiredLen += len(v.ActiveSpatialLayer) * 5
}
return ctx, nil
}
// Marshal encodes VLA into a byte slice.
func (v VLA) Marshal() ([]byte, error) {
ctx, err := v.analyzeVLAForMarshaling()
if err != nil {
return nil, err
}
payload := make([]byte, ctx.requiredLen)
offset := 0
// RID, NS, sl_bm fields
payload[offset] = byte(v.RTPStreamID<<6) | byte(v.RTPStreamCount-1)<<4 | ctx.commonSLBM
if ctx.commonSLBM == 0 {
offset++
for streamID := 0; streamID < v.RTPStreamCount; streamID++ {
if streamID%2 == 0 {
payload[offset+streamID/2] |= ctx.slMBs[streamID] << 4
} else {
payload[offset+streamID/2] |= ctx.slMBs[streamID]
}
}
offset += (v.RTPStreamCount - 1) / 2
}
// #tl fields
offset++
var temporalLayerIndex int
for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ {
for spatialID := 0; spatialID < 4; spatialID++ {
if sl := ctx.sls[rtpStreamID][spatialID]; sl != nil {
if temporalLayerIndex >= 4 {
temporalLayerIndex = 0
offset++
}
payload[offset] |= byte(len(sl.TargetBitrates)-1) << (2 * (3 - temporalLayerIndex))
temporalLayerIndex++
}
}
}
// Target bitrate fields
offset++
for _, encodedKbps := range ctx.encodedTargetBitrates {
encodedSize := len(encodedKbps)
copy(payload[offset:], encodedKbps)
offset += encodedSize
}
// Resolution & framerate fields
if v.HasResolutionAndFramerate {
for _, sl := range v.ActiveSpatialLayer {
binary.BigEndian.PutUint16(payload[offset+0:], uint16(sl.Width-1))
binary.BigEndian.PutUint16(payload[offset+2:], uint16(sl.Height-1))
payload[offset+4] = byte(sl.Framerate)
offset += 5
}
}
return payload, nil
}
func commonSLBMValues(slMBs []uint8) uint8 {
var common uint8
for i := 0; i < len(slMBs); i++ {
if slMBs[i] == 0 {
continue
}
if common == 0 {
common = slMBs[i]
continue
}
if slMBs[i] != common {
return 0
}
}
return common
}
type vlaUnmarshalingContext struct {
payload []byte
offset int
slBMField uint8
slBMs [4]uint8
}
func (ctx *vlaUnmarshalingContext) checkRemainingLen(requiredLen int) bool {
return len(ctx.payload)-ctx.offset >= requiredLen
}
func (v *VLA) unmarshalSpatialLayers(ctx *vlaUnmarshalingContext) error {
if !ctx.checkRemainingLen(1) {
return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort)
}
v.RTPStreamID = int(ctx.payload[ctx.offset] >> 6 & 0b11)
v.RTPStreamCount = int(ctx.payload[ctx.offset]>>4&0b11) + 1
// sl_bm fields
ctx.slBMField = ctx.payload[ctx.offset] & 0b1111
ctx.offset++
if ctx.slBMField != 0 {
for streamID := 0; streamID < v.RTPStreamCount; streamID++ {
ctx.slBMs[streamID] = ctx.slBMField
}
} else {
if !ctx.checkRemainingLen((v.RTPStreamCount-1)/2 + 1) {
return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort)
}
// slX_bm fields
for streamID := 0; streamID < v.RTPStreamCount; streamID++ {
var bm uint8
if streamID%2 == 0 {
bm = ctx.payload[ctx.offset+streamID/2] >> 4 & 0b1111
} else {
bm = ctx.payload[ctx.offset+streamID/2] & 0b1111
}
ctx.slBMs[streamID] = bm
}
ctx.offset += 1 + (v.RTPStreamCount-1)/2
}
return nil
}
func (v *VLA) unmarshalTemporalLayers(ctx *vlaUnmarshalingContext) error {
if !ctx.checkRemainingLen(1) {
return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort)
}
var temporalLayerIndex int
for streamID := 0; streamID < v.RTPStreamCount; streamID++ {
for spatialID := 0; spatialID < 4; spatialID++ {
if ctx.slBMs[streamID]&(1<<spatialID) == 0 {
continue
}
if temporalLayerIndex >= 4 {
temporalLayerIndex = 0
ctx.offset++
if !ctx.checkRemainingLen(1) {
return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort)
}
}
tlCount := int(ctx.payload[ctx.offset]>>(2*(3-temporalLayerIndex))&0b11) + 1
temporalLayerIndex++
sl := SpatialLayer{
RTPStreamID: streamID,
SpatialID: spatialID,
TargetBitrates: make([]int, tlCount),
}
v.ActiveSpatialLayer = append(v.ActiveSpatialLayer, sl)
}
}
ctx.offset++
// target bitrates
for i, sl := range v.ActiveSpatialLayer {
for j := range sl.TargetBitrates {
kbps, n, err := obu.ReadLeb128(ctx.payload[ctx.offset:])
if err != nil {
return err
}
if !ctx.checkRemainingLen(int(n)) {
return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort)
}
v.ActiveSpatialLayer[i].TargetBitrates[j] = int(kbps)
ctx.offset += int(n)
}
}
return nil
}
func (v *VLA) unmarshalResolutionAndFramerate(ctx *vlaUnmarshalingContext) error {
if !ctx.checkRemainingLen(len(v.ActiveSpatialLayer) * 5) {
return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort)
}
v.HasResolutionAndFramerate = true
for i := range v.ActiveSpatialLayer {
v.ActiveSpatialLayer[i].Width = int(binary.BigEndian.Uint16(ctx.payload[ctx.offset+0:])) + 1
v.ActiveSpatialLayer[i].Height = int(binary.BigEndian.Uint16(ctx.payload[ctx.offset+2:])) + 1
v.ActiveSpatialLayer[i].Framerate = int(ctx.payload[ctx.offset+4])
ctx.offset += 5
}
return nil
}
// Unmarshal decodes VLA from a byte slice.
func (v *VLA) Unmarshal(payload []byte) (int, error) {
ctx := &vlaUnmarshalingContext{
payload: payload,
}
err := v.unmarshalSpatialLayers(ctx)
if err != nil {
return ctx.offset, err
}
// #tl fields (build the list ActiveSpatialLayer at the same time)
err = v.unmarshalTemporalLayers(ctx)
if err != nil {
return ctx.offset, err
}
if len(ctx.payload) == ctx.offset {
return ctx.offset, nil
}
// resolution & framerate (optional)
err = v.unmarshalResolutionAndFramerate(ctx)
if err != nil {
return ctx.offset, err
}
return ctx.offset, nil
}
// String makes VLA printable.
func (v VLA) String() string {
out := fmt.Sprintf("RID:%d,RTPStreamCount:%d", v.RTPStreamID, v.RTPStreamCount)
var slOut []string
for _, sl := range v.ActiveSpatialLayer {
out2 := fmt.Sprintf("RTPStreamID:%d", sl.RTPStreamID)
out2 += fmt.Sprintf(",TargetBitrates:%v", sl.TargetBitrates)
if v.HasResolutionAndFramerate {
out2 += fmt.Sprintf(",Resolution:(%d,%d)", sl.Width, sl.Height)
out2 += fmt.Sprintf(",Framerate:%d", sl.Framerate)
}
slOut = append(slOut, out2)
}
out += fmt.Sprintf(",ActiveSpatialLayers:{%s}", strings.Join(slOut, ","))
return out
}