pax_global_header00006660000000000000000000000064142253225240014513gustar00rootroot0000000000000052 comment=f47320ba42ace6b75f1f579fc4404c26cf2b0dce rtp-1.7.13/000077500000000000000000000000001422532252400124115ustar00rootroot00000000000000rtp-1.7.13/.github/000077500000000000000000000000001422532252400137515ustar00rootroot00000000000000rtp-1.7.13/.github/generate-authors.sh000077500000000000000000000031401422532252400175630ustar00rootroot00000000000000#!/usr/bin/env bash # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # set -e SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) AUTHORS_PATH="$GITHUB_WORKSPACE/AUTHORS.txt" if [ -f ${SCRIPT_PATH}/.ci.conf ] then . ${SCRIPT_PATH}/.ci.conf fi # # DO NOT EDIT THIS # EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot' 'pionbot') # If you want to exclude a name from all repositories, send a PR to # https://github.com/pion/.goassets instead of this repository. # If you want to exclude a name only from this repository, # add EXCLUDED_CONTRIBUTORS=('name') to .github/.ci.conf CONTRIBUTORS=() shouldBeIncluded () { for i in "${EXCLUDED_CONTRIBUTORS[@]}" do if [[ $1 =~ "$i" ]]; then return 1 fi done return 0 } IFS=$'\n' #Only split on newline for contributor in $(git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf) do if shouldBeIncluded $contributor; then CONTRIBUTORS+=("$contributor") fi done unset IFS if [ ${#CONTRIBUTORS[@]} -ne 0 ]; then cat >$AUTHORS_PATH <<-'EOH' # Thank you to everyone that made Pion possible. If you are interested in contributing # we would love to have you https://github.com/pion/webrtc/wiki/Contributing # # This file is auto generated, using git to list all individuals contributors. # see `.github/generate-authors.sh` for the scripting EOH for i in "${CONTRIBUTORS[@]}" do echo "$i" >> $AUTHORS_PATH done exit 0 fi rtp-1.7.13/.github/hooks/000077500000000000000000000000001422532252400150745ustar00rootroot00000000000000rtp-1.7.13/.github/hooks/commit-msg.sh000077500000000000000000000002671422532252400175140ustar00rootroot00000000000000#!/usr/bin/env bash # # DO NOT EDIT THIS FILE DIRECTLY # # It is automatically copied from https://github.com/pion/.goassets repository. # set -e .github/lint-commit-message.sh $1 rtp-1.7.13/.github/hooks/pre-commit.sh000077500000000000000000000003331422532252400175060ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE DIRECTLY # # It is automatically copied from https://github.com/pion/.goassets repository. # # Redirect output to stderr. exec 1>&2 .github/lint-disallowed-functions-in-library.sh rtp-1.7.13/.github/hooks/pre-push.sh000077500000000000000000000002571422532252400172020ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE DIRECTLY # # It is automatically copied from https://github.com/pion/.goassets repository. # set -e .github/generate-authors.sh exit 0 rtp-1.7.13/.github/install-hooks.sh000077500000000000000000000010361422532252400170770ustar00rootroot00000000000000#!/bin/bash # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) cp "$SCRIPT_PATH/hooks/commit-msg.sh" "$SCRIPT_PATH/../.git/hooks/commit-msg" cp "$SCRIPT_PATH/hooks/pre-commit.sh" "$SCRIPT_PATH/../.git/hooks/pre-commit" cp "$SCRIPT_PATH/hooks/pre-push.sh" "$SCRIPT_PATH/../.git/hooks/pre-push" rtp-1.7.13/.github/lint-commit-message.sh000077500000000000000000000035641422532252400201760ustar00rootroot00000000000000#!/usr/bin/env bash # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # set -e display_commit_message_error() { cat << EndOfMessage $1 ------------------------------------------------- The preceding commit message is invalid it failed '$2' of the following checks * Separate subject from body with a blank line * Limit the subject line to 50 characters * Capitalize the subject line * Do not end the subject line with a period * Wrap the body at 72 characters EndOfMessage exit 1 } lint_commit_message() { if [[ "$(echo "$1" | awk 'NR == 2 {print $1;}' | wc -c)" -ne 1 ]]; then display_commit_message_error "$1" 'Separate subject from body with a blank line' fi if [[ "$(echo "$1" | head -n1 | awk '{print length}')" -gt 50 ]]; then display_commit_message_error "$1" 'Limit the subject line to 50 characters' fi if [[ ! $1 =~ ^[A-Z] ]]; then display_commit_message_error "$1" 'Capitalize the subject line' fi if [[ "$(echo "$1" | awk 'NR == 1 {print substr($0,length($0),1)}')" == "." ]]; then display_commit_message_error "$1" 'Do not end the subject line with a period' fi if [[ "$(echo "$1" | awk '{print length}' | sort -nr | head -1)" -gt 72 ]]; then display_commit_message_error "$1" 'Wrap the body at 72 characters' fi } if [ "$#" -eq 1 ]; then if [ ! -f "$1" ]; then echo "$0 was passed one argument, but was not a valid file" exit 1 fi lint_commit_message "$(sed -n '/# Please enter the commit message for your changes. Lines starting/q;p' "$1")" else for commit in $(git rev-list --no-merges origin/master..); do lint_commit_message "$(git log --format="%B" -n 1 $commit)" done fi rtp-1.7.13/.github/lint-disallowed-functions-in-library.sh000077500000000000000000000023571422532252400234660ustar00rootroot00000000000000#!/usr/bin/env bash # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # set -e # Disallow usages of functions that cause the program to exit in the library code SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) if [ -f ${SCRIPT_PATH}/.ci.conf ] then . ${SCRIPT_PATH}/.ci.conf fi EXCLUDE_DIRECTORIES=${DISALLOWED_FUNCTIONS_EXCLUDED_DIRECTORIES:-"examples"} DISALLOWED_FUNCTIONS=('os.Exit(' 'panic(' 'Fatal(' 'Fatalf(' 'Fatalln(' 'fmt.Println(' 'fmt.Printf(' 'log.Print(' 'log.Println(' 'log.Printf(' 'print(' 'println(') files=$( find "$SCRIPT_PATH/.." -name "*.go" \ | grep -v -e '^.*_test.go$' \ | while read file do excluded=false for ex in $EXCLUDE_DIRECTORIES do if [[ $file == */$ex/* ]] then excluded=true break fi done $excluded || echo "$file" done ) for disallowedFunction in "${DISALLOWED_FUNCTIONS[@]}" do if grep -e "\s$disallowedFunction" $files | grep -v -e 'nolint'; then echo "$disallowedFunction may only be used in example code" exit 1 fi done rtp-1.7.13/.github/lint-filename.sh000077500000000000000000000012041422532252400170310ustar00rootroot00000000000000#!/usr/bin/env bash # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # set -e SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) GO_REGEX="^[a-zA-Z][a-zA-Z0-9_]*\.go$" find "$SCRIPT_PATH/.." -name "*.go" | while read fullpath; do filename=$(basename -- "$fullpath") if ! [[ $filename =~ $GO_REGEX ]]; then echo "$filename is not a valid filename for Go code, only alpha, numbers and underscores are supported" exit 1 fi done rtp-1.7.13/.github/workflows/000077500000000000000000000000001422532252400160065ustar00rootroot00000000000000rtp-1.7.13/.github/workflows/generate-authors.yml000066400000000000000000000046551422532252400220200ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: generate-authors on: pull_request: jobs: checksecret: runs-on: ubuntu-latest outputs: is_PIONBOT_PRIVATE_KEY_set: ${{ steps.checksecret_job.outputs.is_PIONBOT_PRIVATE_KEY_set }} steps: - id: checksecret_job env: PIONBOT_PRIVATE_KEY: ${{ secrets.PIONBOT_PRIVATE_KEY }} run: | echo "is_PIONBOT_PRIVATE_KEY_set: ${{ env.PIONBOT_PRIVATE_KEY != '' }}" echo "::set-output name=is_PIONBOT_PRIVATE_KEY_set::${{ env.PIONBOT_PRIVATE_KEY != '' }}" generate-authors: needs: [checksecret] if: needs.checksecret.outputs.is_PIONBOT_PRIVATE_KEY_set == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: ref: ${{ github.head_ref }} fetch-depth: 0 token: ${{ secrets.PIONBOT_PRIVATE_KEY }} - name: Generate the authors file run: .github/generate-authors.sh - name: Add the authors file to git run: git add AUTHORS.txt - name: Get last commit message id: last-commit-message run: | COMMIT_MSG=$(git log -1 --pretty=%B) COMMIT_MSG="${COMMIT_MSG//'%'/'%25'}" COMMIT_MSG="${COMMIT_MSG//$'\n'/'%0A'}" COMMIT_MSG="${COMMIT_MSG//$'\r'/'%0D'}" echo "::set-output name=msg::$COMMIT_MSG" - name: Get last commit author id: last-commit-author run: | echo "::set-output name=msg::$(git log -1 --pretty='%aN <%ae>')" - name: Check if AUTHORS.txt file has changed id: git-status-output run: | echo "::set-output name=msg::$(git status -s | wc -l)" - name: Commit and push if: ${{ steps.git-status-output.outputs.msg != '0' }} run: | git config user.email $(echo "${{ steps.last-commit-author.outputs.msg }}" | sed 's/\(.\+\) <\(\S\+\)>/\2/') git config user.name $(echo "${{ steps.last-commit-author.outputs.msg }}" | sed 's/\(.\+\) <\(\S\+\)>/\1/') git add AUTHORS.txt git commit --amend --no-edit git push --force https://github.com/${GITHUB_REPOSITORY} $(git symbolic-ref -q --short HEAD) rtp-1.7.13/.github/workflows/lint.yaml000066400000000000000000000022541422532252400176430ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Lint on: pull_request: types: - opened - edited - synchronize jobs: lint-commit-message: name: Metadata runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Commit Message run: .github/lint-commit-message.sh - name: File names run: .github/lint-filename.sh - name: Functions run: .github/lint-disallowed-functions-in-library.sh lint-go: name: Go runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: version: v1.31 args: $GOLANGCI_LINT_EXRA_ARGS rtp-1.7.13/.github/workflows/renovate-go-mod-fix.yaml000066400000000000000000000015471422532252400224700ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: go-mod-fix on: push: branches: - renovate/* jobs: go-mod-fix: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v3 with: fetch-depth: 2 - name: fix uses: at-wat/go-sum-fix-action@v0 with: git_user: Pion Bot git_email: 59523206+pionbot@users.noreply.github.com github_token: ${{ secrets.PIONBOT_PRIVATE_KEY }} commit_style: squash push: force rtp-1.7.13/.github/workflows/test.yaml000066400000000000000000000101171422532252400176510ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Test on: push: branches: - master pull_request: branches: - master jobs: test: runs-on: ubuntu-latest strategy: matrix: go: ["1.16", "1.17"] fail-fast: false name: Go ${{ matrix.go }} steps: - uses: actions/checkout@v3 - uses: actions/cache@v2 with: path: | ~/go/pkg/mod ~/go/bin ~/.cache key: ${{ runner.os }}-amd64-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-amd64-go- - name: Setup Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} - name: Setup go-acc run: | go get github.com/ory/go-acc git checkout go.mod go.sum - name: Run test run: | TEST_BENCH_OPTION="-bench=." if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi go-acc -o cover.out ./... -- \ ${TEST_BENCH_OPTION} \ -v -race - name: Run TEST_HOOK run: | if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi - uses: codecov/codecov-action@v2 with: name: codecov-umbrella fail_ci_if_error: true flags: go test-i386: runs-on: ubuntu-latest strategy: matrix: go: ["1.16", "1.17"] fail-fast: false name: Go i386 ${{ matrix.go }} steps: - uses: actions/checkout@v3 - uses: actions/cache@v2 with: path: | ~/go/pkg/mod ~/.cache key: ${{ runner.os }}-i386-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-i386-go- - name: Run test run: | mkdir -p $HOME/go/pkg/mod $HOME/.cache docker run \ -u $(id -u):$(id -g) \ -e "GO111MODULE=on" \ -e "CGO_ENABLED=0" \ -v $GITHUB_WORKSPACE:/go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ -v $HOME/go/pkg/mod:/go/pkg/mod \ -v $HOME/.cache:/.cache \ -w /go/src/github.com/pion/$(basename $GITHUB_WORKSPACE) \ i386/golang:${{matrix.go}}-alpine \ /usr/local/go/bin/go test \ ${TEST_EXTRA_ARGS:-} \ -v ./... test-wasm: runs-on: ubuntu-latest strategy: fail-fast: false name: WASM steps: - uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 with: node-version: '16.x' - uses: actions/cache@v2 with: path: | ~/go/pkg/mod ~/.cache key: ${{ runner.os }}-wasm-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-wasm-go- - name: Download Go run: curl -sSfL https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz | tar -C ~ -xzf - env: GO_VERSION: 1.17 - name: Set Go Root run: echo "GOROOT=${HOME}/go" >> $GITHUB_ENV - name: Set Go Path run: echo "GOPATH=${HOME}/go" >> $GITHUB_ENV - name: Set Go Path run: echo "GO_JS_WASM_EXEC=${GOROOT}/misc/wasm/go_js_wasm_exec" >> $GITHUB_ENV - name: Insall NPM modules run: yarn install - name: Run Tests run: | if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi GOOS=js GOARCH=wasm $GOPATH/bin/go test \ -coverprofile=cover.out -covermode=atomic \ -exec="${GO_JS_WASM_EXEC}" \ -v ./... - uses: codecov/codecov-action@v2 with: name: codecov-umbrella fail_ci_if_error: true flags: wasm rtp-1.7.13/.github/workflows/tidy-check.yaml000066400000000000000000000015231422532252400207170ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # name: Go mod tidy on: pull_request: branches: - master push: branches: - master jobs: Check: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v3 - name: check run: | go mod download go mod tidy if ! git diff --exit-code then echo "Not go mod tidied" exit 1 fi rtp-1.7.13/.gitignore000066400000000000000000000004661422532252400144070ustar00rootroot00000000000000### 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 rtp-1.7.13/.golangci.yml000066400000000000000000000123561422532252400150040ustar00rootroot00000000000000linters-settings: govet: check-shadowing: true misspell: locale: US exhaustive: default-signifies-exhaustive: true gomodguard: blocked: modules: - github.com/pkg/errors: recommendations: - errors linters: enable: - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers - bodyclose # checks whether HTTP response body is closed successfully - deadcode # Finds unused code - depguard # Go linter that checks if package imports are in a list of acceptable packages - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) - dupl # Tool for code clone detection - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases - exhaustive # check exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables - gci # Gci control golang package import order and make it always deterministic. - gochecknoglobals # Checks that no globals are present in Go code - gochecknoinits # Checks that no init functions are present in Go code - gocognit # Computes and checks the cognitive complexity of functions - goconst # Finds repeated strings that could be replaced by a constant - gocritic # The most opinionated Go source code linter - godox # Tool for detection of FIXME, TODO and other comment keywords - goerr113 # Golang linter to check the errors handling expressions - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification - gofumpt # Gofumpt checks whether code was gofumpt-ed. - goheader # Checks is file header matches to pattern - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports - golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. - goprintffuncname # Checks that printf-like functions are named with `f` at the end - gosec # Inspects source code for security problems - gosimple # Linter for Go source code that specializes in simplifying a code - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - ineffassign # Detects when assignments to existing variables are not used - misspell # Finds commonly misspelled English words in comments - nakedret # Finds naked returns in functions greater than a specified function length - noctx # noctx finds sending http request without context.Context - scopelint # Scopelint checks for unpinned variables in go programs - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks - structcheck # Finds unused struct fields - stylecheck # Stylecheck is a replacement for golint - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - varcheck # Finds unused global variables and constants - whitespace # Tool for detection of leading and trailing whitespace disable: - funlen # Tool for detection of long functions - gocyclo # Computes and checks the cyclomatic complexity of functions - godot # Check if comments end in a period - gomnd # An analyzer to detect magic numbers. - lll # Reports long lines - maligned # Tool to detect Go structs that would take less memory if their fields were sorted - nestif # Reports deeply nested if statements - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity - nolintlint # Reports ill-formed or insufficient nolint directives - prealloc # Finds slice declarations that could potentially be preallocated - rowserrcheck # checks whether Err of rows is checked successfully - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. - testpackage # linter that makes you use a separate _test package - wsl # Whitespace Linter - Forces you to use empty lines! issues: exclude-use-default: false exclude-rules: # Allow complex tests, better to be self contained - path: _test\.go linters: - gocognit # Allow complex main function in examples - path: examples text: "of func `main` is high" linters: - gocognit run: skip-dirs-use-default: false rtp-1.7.13/AUTHORS.txt000066400000000000000000000026611422532252400143040ustar00rootroot00000000000000# Thank you to everyone that made Pion possible. If you are interested in contributing # we would love to have you https://github.com/pion/webrtc/wiki/Contributing # # This file is auto generated, using git to list all individuals contributors. # see `.github/generate-authors.sh` for the scripting adwpc aler9 <46489434+aler9@users.noreply.github.com> Antoine Baché Antoine Baché Atsushi Watanabe baiyufei Bao Nguyen boks1971 debiandebiandebian ffmiyo Guilherme Haiyang Wang Hugo Arregui John Bradley Juliusz Chroboczek Kazuyuki Honda Luke Curley lxb Michael MacDonald Michael MacDonald Michael Uti Raphael Derosso Pereira Rob Lofthouse Robin Raymond Sean DuBois Sean DuBois Sean DuBois Simone Gotti Tarrence van As wangzixiang Woodrow Douglass rtp-1.7.13/LICENSE000066400000000000000000000020411422532252400134130ustar00rootroot00000000000000MIT License Copyright (c) 2018 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rtp-1.7.13/README.md000066400000000000000000000041651422532252400136760ustar00rootroot00000000000000


Pion RTP

A Go implementation of RTP

Pion RTP Sourcegraph Widget Slack Widget
Build Status GoDoc Coverage Status Go Report Card License: MIT


### Roadmap The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. ### Community Pion has an active community on the [Golang Slack](https://invite.slack.golangbridge.org/). Sign up and join the **#pion** channel for discussions and support. You can also use [Pion mailing list](https://groups.google.com/forum/#!forum/pion). We are always looking to support **your projects**. Please reach out if you have something to build! If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) ### Contributing Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: ### License MIT License - see [LICENSE](LICENSE) for full text rtp-1.7.13/abssendtimeextension.go000066400000000000000000000035151422532252400171770ustar00rootroot00000000000000package 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)) } rtp-1.7.13/abssendtimeextension_test.go000066400000000000000000000044331422532252400202360ustar00rootroot00000000000000package rtp import ( "testing" "time" ) const absSendTimeResolution = 3800 * time.Nanosecond func TestNtpConversion(t *testing.T) { loc := time.FixedZone("UTC-5", -5*60*60) tests := []struct { t time.Time n uint64 }{ {t: time.Date(1985, time.June, 23, 4, 0, 0, 0, loc), n: 0xa0c65b1000000000}, {t: time.Date(1999, time.December, 31, 23, 59, 59, 500000, loc), n: 0xbc18084f0020c49b}, {t: time.Date(2019, time.March, 27, 13, 39, 30, 8675309, loc), n: 0xe04641e202388b88}, } for i, in := range tests { out := toNtpTime(in.t) if out != in.n { t.Errorf("[%d] Converted NTP time from time.Time differs, expected: %d, got: %d", i, in.n, out, ) } } for i, in := range tests { out := toTime(in.n) diff := in.t.Sub(out) if diff < -absSendTimeResolution || absSendTimeResolution < diff { t.Errorf("[%d] Converted time.Time from NTP time differs, expected: %v, got: %v", i, in.t.UTC(), out.UTC(), ) } } } func TestAbsSendTimeExtension_Roundtrip(t *testing.T) { tests := []AbsSendTimeExtension{ { Timestamp: 123456, }, { Timestamp: 654321, }, } for i, in := range tests { b, err := in.Marshal() if err != nil { t.Fatal(err) } var out AbsSendTimeExtension if err = out.Unmarshal(b); err != nil { t.Fatal(err) } if in.Timestamp != out.Timestamp { t.Errorf("[%d] Timestamp differs, expected: %d, got: %d", i, in.Timestamp, out.Timestamp) } } } func TestAbsSendTimeExtension_Estimate(t *testing.T) { tests := []struct { sendNTP uint64 receiveNTP uint64 }{ // FFFFFFC000000000 mask of second {0xa0c65b1000100000, 0xa0c65b1001000000}, // not carried {0xa0c65b3f00000000, 0xa0c65b4001000000}, // carried during transmission } for i, in := range tests { inTime := toTime(in.sendNTP) send := &AbsSendTimeExtension{in.sendNTP >> 14} b, err := send.Marshal() if err != nil { t.Fatal(err) } var received AbsSendTimeExtension if err = received.Unmarshal(b); err != nil { t.Fatal(err) } estimated := received.Estimate(toTime(in.receiveNTP)) diff := estimated.Sub(inTime) if diff < -absSendTimeResolution || absSendTimeResolution < diff { t.Errorf("[%d] Estimated time differs, expected: %v, estimated: %v (receive time: %v)", i, inTime.UTC(), estimated.UTC(), toTime(in.receiveNTP).UTC(), ) } } } rtp-1.7.13/audiolevelextension.go000066400000000000000000000032321422532252400170260ustar00rootroot00000000000000package 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 } rtp-1.7.13/audiolevelextension_test.go000066400000000000000000000024721422532252400200720ustar00rootroot00000000000000package rtp import ( "bytes" "errors" "testing" ) func TestAudioLevelExtensionTooSmall(t *testing.T) { a := AudioLevelExtension{} rawData := []byte{} if err := a.Unmarshal(rawData); !errors.Is(err, errTooSmall) { t.Fatal("err != errTooSmall") } } func TestAudioLevelExtensionVoiceTrue(t *testing.T) { a1 := AudioLevelExtension{} rawData := []byte{ 0x88, } if err := a1.Unmarshal(rawData); err != nil { t.Fatal("Unmarshal error on extension data") } a2 := AudioLevelExtension{ Level: 8, Voice: true, } if a1 != a2 { t.Error("Unmarshal failed") } dstData, _ := a2.Marshal() if !bytes.Equal(dstData, rawData) { t.Error("Marshal failed") } } func TestAudioLevelExtensionVoiceFalse(t *testing.T) { a1 := AudioLevelExtension{} rawData := []byte{ 0x8, } if err := a1.Unmarshal(rawData); err != nil { t.Fatal("Unmarshal error on extension data") } a2 := AudioLevelExtension{ Level: 8, Voice: false, } if a1 != a2 { t.Error("Unmarshal failed") } dstData, _ := a2.Marshal() if !bytes.Equal(dstData, rawData) { t.Error("Marshal failed") } } func TestAudioLevelExtensionLevelOverflow(t *testing.T) { a := AudioLevelExtension{ Level: 128, Voice: false, } if _, err := a.Marshal(); !errors.Is(err, errAudioLevelOverflow) { t.Fatal("err != errAudioLevelOverflow") } } rtp-1.7.13/codecov.yml000066400000000000000000000005521422532252400145600ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # coverage: status: project: default: # Allow decreasing 2% of total coverage to avoid noise. threshold: 2% patch: default: target: 70% only_pulls: true ignore: - "examples/*" - "examples/**/*" rtp-1.7.13/codecs/000077500000000000000000000000001422532252400136515ustar00rootroot00000000000000rtp-1.7.13/codecs/av1_packet.go000066400000000000000000000106711422532252400162230ustar00rootroot00000000000000package codecs import ( "github.com/pion/rtp/pkg/obu" ) const ( zMask = byte(0b10000000) zBitshift = 7 yMask = byte(0b01000000) yBitshift = 6 wMask = byte(0b00110000) wBitshift = 4 nMask = byte(0b00001000) nBitshift = 3 av1PayloaderHeadersize = 1 ) // AV1Payloader payloads AV1 packets type AV1Payloader struct{} // 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) { maxFragmentSize := int(mtu) - av1PayloaderHeadersize - 2 payloadDataRemaining := len(payload) payloadDataIndex := 0 // Make sure the fragment/payload size is correct if min(maxFragmentSize, payloadDataRemaining) <= 0 { return payloads } for payloadDataRemaining > 0 { currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) leb128Size := 1 if currentFragmentSize >= 127 { leb128Size = 2 } out := make([]byte, av1PayloaderHeadersize+leb128Size+currentFragmentSize) leb128Value := obu.EncodeLEB128(uint(currentFragmentSize)) if leb128Size == 1 { out[1] = byte(leb128Value) } else { out[1] = byte(leb128Value >> 8) out[2] = byte(leb128Value) } copy(out[av1PayloaderHeadersize+leb128Size:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) payloads = append(payloads, out) payloadDataRemaining -= currentFragmentSize payloadDataIndex += currentFragmentSize if len(payloads) > 1 { out[0] ^= zMask } if payloadDataRemaining != 0 { out[0] ^= yMask } } 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 } // 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 } currentIndex := uint(1) p.OBUElements = [][]byte{} var ( obuElementLength, bytesRead uint err error ) 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 { obuElementLength, bytesRead, err = obu.ReadLeb128(payload[currentIndex:]) if err != nil { return nil, err } } currentIndex += bytesRead if uint(len(payload)) < currentIndex+obuElementLength { return nil, errShortPacket } p.OBUElements = append(p.OBUElements, payload[currentIndex:currentIndex+obuElementLength]) currentIndex += obuElementLength } return payload[1:], nil } rtp-1.7.13/codecs/av1_packet_test.go000066400000000000000000000220551422532252400172610ustar00rootroot00000000000000package codecs import ( "errors" "fmt" "reflect" "testing" "github.com/pion/rtp/pkg/obu" ) func TestAV1_Marshal(t *testing.T) { const mtu = 5 for _, test := range []struct { input []byte output [][]byte }{ {[]byte{0x01}, [][]byte{{0x00, 0x01, 0x01}}}, {[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x04, 0x05}, [][]byte{{0x40, 0x02, 0x00, 0x01}, {0xc0, 0x02, 0x02, 0x03}, {0xc0, 0x02, 0x04, 0x04}, {0x80, 0x01, 0x05}}}, } { test := test p := &AV1Payloader{} if payloads := p.Payload(mtu, test.input); !reflect.DeepEqual(payloads, test.output) { t.Fatalf("Expected(%02x) did not equal actual(%02x)", test.output, payloads) } } p := &AV1Payloader{} zeroMtuPayload := p.Payload(0, []byte{0x0A, 0x0B, 0x0C}) if zeroMtuPayload != nil { t.Fatal("Unexpected output from zero MTU AV1 Payloader") } } func TestAV1_Unmarshal_Error(t *testing.T) { for _, test := range []struct { expectedError error input []byte }{ {errNilPacket, nil}, {errShortPacket, []byte{0x00}}, {errIsKeyframeAndFragment, []byte{byte(0b10001000), 0x00}}, {obu.ErrFailedToReadLEB128, []byte{byte(0b10000000), 0xFF, 0xFF}}, {errShortPacket, []byte{byte(0b10000000), 0xFF, 0x0F, 0x00, 0x00}}, } { test := test av1Pkt := &AV1Packet{} if _, err := av1Pkt.Unmarshal(test.input); !errors.Is(err, test.expectedError) { t.Fatal(fmt.Sprintf("Expected error(%s) but got (%s)", test.expectedError, err)) } } } func TestAV1_Unmarshal(t *testing.T) { av1Payload := []byte{ 0x68, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x2c, 0xd6, 0xd3, 0x0c, 0xd5, 0x02, 0x00, 0x80, 0x30, 0x10, 0xc3, 0xc0, 0x07, 0xff, 0xff, 0xf8, 0xb7, 0x30, 0xc0, 0x00, 0x00, 0x88, 0x17, 0xf9, 0x0c, 0xcf, 0xc6, 0x7b, 0x9c, 0x0d, 0xda, 0x55, 0x82, 0x82, 0x67, 0x2f, 0xf0, 0x07, 0x26, 0x5d, 0xf6, 0xc6, 0xe3, 0x12, 0xdd, 0xf9, 0x71, 0x77, 0x43, 0xe6, 0xba, 0xf2, 0xce, 0x36, 0x08, 0x63, 0x92, 0xac, 0xbb, 0xbd, 0x26, 0x4c, 0x05, 0x52, 0x91, 0x09, 0xf5, 0x37, 0xb5, 0x18, 0xbe, 0x5c, 0x95, 0xb1, 0x2c, 0x13, 0x27, 0x81, 0xc2, 0x52, 0x8c, 0xaf, 0x27, 0xca, 0xf2, 0x93, 0xd6, 0x2e, 0x46, 0x32, 0xed, 0x71, 0x87, 0x90, 0x1d, 0x0b, 0x84, 0x46, 0x7f, 0xd1, 0x57, 0xc1, 0x0d, 0xc7, 0x5b, 0x41, 0xbb, 0x8a, 0x7d, 0xe9, 0x2c, 0xae, 0x36, 0x98, 0x13, 0x39, 0xb9, 0x0c, 0x66, 0x47, 0x05, 0xa2, 0xdf, 0x55, 0xc4, 0x09, 0xab, 0xe4, 0xfb, 0x11, 0x52, 0x36, 0x27, 0x88, 0x86, 0xf3, 0x4a, 0xbb, 0xef, 0x40, 0xa7, 0x85, 0x2a, 0xfe, 0x92, 0x28, 0xe4, 0xce, 0xce, 0xdc, 0x4b, 0xd0, 0xaa, 0x3c, 0xd5, 0x16, 0x76, 0x74, 0xe2, 0xfa, 0x34, 0x91, 0x4f, 0xdc, 0x2b, 0xea, 0xae, 0x71, 0x36, 0x74, 0xe1, 0x2a, 0xf3, 0xd3, 0x53, 0xe8, 0xec, 0xd6, 0x63, 0xf6, 0x6a, 0x75, 0x95, 0x68, 0xcc, 0x99, 0xbe, 0x17, 0xd8, 0x3b, 0x87, 0x5b, 0x94, 0xdc, 0xec, 0x32, 0x09, 0x18, 0x4b, 0x37, 0x58, 0xb5, 0x67, 0xfb, 0xdf, 0x66, 0x6c, 0x16, 0x9e, 0xba, 0x72, 0xc6, 0x21, 0xac, 0x02, 0x6d, 0x6b, 0x17, 0xf9, 0x68, 0x22, 0x2e, 0x10, 0xd7, 0xdf, 0xfb, 0x24, 0x69, 0x7c, 0xaf, 0x11, 0x64, 0x80, 0x7a, 0x9d, 0x09, 0xc4, 0x1f, 0xf1, 0xd7, 0x3c, 0x5a, 0xc2, 0x2c, 0x8e, 0xf5, 0xff, 0xee, 0xc2, 0x7c, 0xa1, 0xe4, 0xcb, 0x1c, 0x6d, 0xd8, 0x15, 0x0e, 0x40, 0x36, 0x85, 0xe7, 0x04, 0xbb, 0x64, 0xca, 0x6a, 0xd9, 0x21, 0x8e, 0x95, 0xa0, 0x83, 0x95, 0x10, 0x48, 0xfa, 0x00, 0x54, 0x90, 0xe9, 0x81, 0x86, 0xa0, 0x4a, 0x6e, 0xbe, 0x9b, 0xf0, 0x73, 0x0a, 0x17, 0xbb, 0x57, 0x81, 0x17, 0xaf, 0xd6, 0x70, 0x1f, 0xe8, 0x6d, 0x32, 0x59, 0x14, 0x39, 0xd8, 0x1d, 0xec, 0x59, 0xe4, 0x98, 0x4d, 0x44, 0xf3, 0x4f, 0x7b, 0x47, 0xd9, 0x92, 0x3b, 0xd9, 0x5c, 0x98, 0xd5, 0xf1, 0xc9, 0x8b, 0x9d, 0xb1, 0x65, 0xb3, 0xe1, 0x87, 0xa4, 0x6a, 0xcc, 0x42, 0x96, 0x66, 0xdb, 0x5f, 0xf9, 0xe1, 0xa1, 0x72, 0xb6, 0x05, 0x02, 0x1f, 0xa3, 0x14, 0x3e, 0xfe, 0x99, 0x7f, 0xeb, 0x42, 0xcf, 0x76, 0x09, 0x19, 0xd2, 0xd2, 0x99, 0x75, 0x1c, 0x67, 0xda, 0x4d, 0xf4, 0x87, 0xe5, 0x55, 0x8b, 0xed, 0x01, 0x82, 0xf6, 0xd6, 0x1c, 0x5c, 0x05, 0x96, 0x96, 0x79, 0xc1, 0x61, 0x87, 0x74, 0xcd, 0x29, 0x83, 0x27, 0xae, 0x47, 0x87, 0x36, 0x34, 0xab, 0xc4, 0x73, 0x76, 0x58, 0x1b, 0x4a, 0xec, 0x0e, 0x4c, 0x2f, 0xb1, 0x76, 0x08, 0x7f, 0xaf, 0xfa, 0x6d, 0x8c, 0xde, 0xe4, 0xae, 0x58, 0x87, 0xe7, 0xa0, 0x27, 0x05, 0x0d, 0xf5, 0xa7, 0xfb, 0x2a, 0x75, 0x33, 0xd9, 0x3b, 0x65, 0x60, 0xa4, 0x13, 0x27, 0xa5, 0xe5, 0x1b, 0x83, 0x78, 0x7a, 0xd7, 0xec, 0x0c, 0xed, 0x8b, 0xe6, 0x4e, 0x8f, 0xfe, 0x6b, 0x5d, 0xbb, 0xa8, 0xee, 0x38, 0x81, 0x6f, 0x09, 0x23, 0x08, 0x8f, 0x07, 0x21, 0x09, 0x39, 0xf0, 0xf8, 0x03, 0x17, 0x24, 0x2a, 0x22, 0x44, 0x84, 0xe1, 0x5c, 0xf3, 0x4f, 0x20, 0xdc, 0xc1, 0xe7, 0xeb, 0xbc, 0x0b, 0xfb, 0x7b, 0x20, 0x66, 0xa4, 0x27, 0xe2, 0x01, 0xb3, 0x5f, 0xb7, 0x47, 0xa1, 0x88, 0x4b, 0x8c, 0x47, 0xda, 0x36, 0x98, 0x60, 0xd7, 0x46, 0x92, 0x0b, 0x7e, 0x5b, 0x4e, 0x34, 0x50, 0x12, 0x67, 0x50, 0x8d, 0xe7, 0xc9, 0xe4, 0x96, 0xef, 0xae, 0x2b, 0xc7, 0xfa, 0x36, 0x29, 0x05, 0xf5, 0x92, 0xbd, 0x62, 0xb7, 0xbb, 0x90, 0x66, 0xe0, 0xad, 0x14, 0x3e, 0xe7, 0xb4, 0x24, 0xf3, 0x04, 0xcf, 0x22, 0x14, 0x86, 0xa4, 0xb8, 0xfb, 0x83, 0x56, 0xce, 0xaa, 0xb4, 0x87, 0x5a, 0x9e, 0xf2, 0x0b, 0xaf, 0xad, 0x40, 0xe1, 0xb5, 0x5c, 0x6b, 0xa7, 0xee, 0x9f, 0xbb, 0x1a, 0x68, 0x4d, 0xc3, 0xbf, 0x22, 0x4d, 0xbe, 0x58, 0x52, 0xc9, 0xcc, 0x0d, 0x88, 0x04, 0xf1, 0xf8, 0xd4, 0xfb, 0xd6, 0xad, 0xcf, 0x13, 0x84, 0xd6, 0x2f, 0x90, 0x0c, 0x5f, 0xb4, 0xe2, 0xd8, 0x29, 0x26, 0x8d, 0x7c, 0x6b, 0xab, 0x91, 0x91, 0x3c, 0x25, 0x39, 0x9c, 0x86, 0x08, 0x39, 0x54, 0x59, 0x0d, 0xa4, 0xa8, 0x31, 0x9f, 0xa3, 0xbc, 0xc2, 0xcb, 0xf9, 0x30, 0x49, 0xc3, 0x68, 0x0e, 0xfc, 0x2b, 0x9f, 0xce, 0x59, 0x02, 0xfa, 0xd4, 0x4e, 0x11, 0x49, 0x0d, 0x93, 0x0c, 0xae, 0x57, 0xd7, 0x74, 0xdd, 0x13, 0x1a, 0x15, 0x79, 0x10, 0xcc, 0x99, 0x32, 0x9b, 0x57, 0x6d, 0x53, 0x75, 0x1f, 0x6d, 0xbb, 0xe4, 0xbc, 0xa9, 0xd4, 0xdb, 0x06, 0xe7, 0x09, 0xb0, 0x6f, 0xca, 0xb3, 0xb1, 0xed, 0xc5, 0x0b, 0x8d, 0x8e, 0x70, 0xb0, 0xbf, 0x8b, 0xad, 0x2f, 0x29, 0x92, 0xdd, 0x5a, 0x19, 0x3d, 0xca, 0xca, 0xed, 0x05, 0x26, 0x25, 0xee, 0xee, 0xa9, 0xdd, 0xa0, 0xe3, 0x78, 0xe0, 0x56, 0x99, 0x2f, 0xa1, 0x3f, 0x07, 0x5e, 0x91, 0xfb, 0xc4, 0xb3, 0xac, 0xee, 0x07, 0xa4, 0x6a, 0xcb, 0x42, 0xae, 0xdf, 0x09, 0xe7, 0xd0, 0xbb, 0xc6, 0xd4, 0x38, 0x58, 0x7d, 0xb4, 0x45, 0x98, 0x38, 0x21, 0xc8, 0xc1, 0x3c, 0x81, 0x12, 0x7e, 0x37, 0x03, 0xa8, 0xcc, 0xf3, 0xf9, 0xd9, 0x9d, 0x8f, 0xc1, 0xa1, 0xcc, 0xc1, 0x1b, 0xe3, 0xa8, 0x93, 0x91, 0x2c, 0x0a, 0xe8, 0x1f, 0x28, 0x13, 0x44, 0x07, 0x68, 0x5a, 0x8f, 0x27, 0x41, 0x18, 0xc9, 0x31, 0xc4, 0xc1, 0x71, 0xe2, 0xf0, 0xc4, 0xf4, 0x1e, 0xac, 0x29, 0x49, 0x2f, 0xd0, 0xc0, 0x98, 0x13, 0xa6, 0xbc, 0x5e, 0x34, 0x28, 0xa7, 0x30, 0x13, 0x8d, 0xb4, 0xca, 0x91, 0x26, 0x6c, 0xda, 0x35, 0xb5, 0xf1, 0xbf, 0x3f, 0x35, 0x3b, 0x87, 0x37, 0x63, 0x40, 0x59, 0x73, 0x49, 0x06, 0x59, 0x04, 0xe0, 0x84, 0x16, 0x3a, 0xe8, 0xc4, 0x28, 0xd1, 0xf5, 0x11, 0x9c, 0x34, 0xf4, 0x5a, 0xc0, 0xf8, 0x67, 0x47, 0x1c, 0x90, 0x63, 0xbc, 0x06, 0x39, 0x2e, 0x8a, 0xa5, 0xa0, 0xf1, 0x6b, 0x41, 0xb1, 0x16, 0xbd, 0xb9, 0x50, 0x78, 0x72, 0x91, 0x8e, 0x8c, 0x99, 0x0f, 0x7d, 0x99, 0x7e, 0x77, 0x36, 0x85, 0x87, 0x1f, 0x2e, 0x47, 0x13, 0x55, 0xf8, 0x07, 0xba, 0x7b, 0x1c, 0xaa, 0xbf, 0x20, 0xd0, 0xfa, 0xc4, 0xe1, 0xd0, 0xb3, 0xe4, 0xf4, 0xf9, 0x57, 0x8d, 0x56, 0x19, 0x4a, 0xdc, 0x4c, 0x83, 0xc8, 0xf1, 0x30, 0xc0, 0xb5, 0xdf, 0x67, 0x25, 0x58, 0xd8, 0x09, 0x41, 0x37, 0x2e, 0x0b, 0x47, 0x2b, 0x86, 0x4b, 0x73, 0x38, 0xf0, 0xa0, 0x6b, 0x83, 0x30, 0x80, 0x3e, 0x46, 0xb5, 0x09, 0xc8, 0x6d, 0x3e, 0x97, 0xaa, 0x70, 0x4e, 0x8c, 0x75, 0x29, 0xec, 0x8a, 0x37, 0x4a, 0x81, 0xfd, 0x92, 0xf1, 0x29, 0xf0, 0xe8, 0x9d, 0x8c, 0xb4, 0x39, 0x2d, 0x67, 0x06, 0xcd, 0x5f, 0x25, 0x02, 0x30, 0xbb, 0x6b, 0x41, 0x93, 0x55, 0x1e, 0x0c, 0xc9, 0x6e, 0xb5, 0xd5, 0x9f, 0x80, 0xf4, 0x7d, 0x9d, 0x8a, 0x0d, 0x8d, 0x3b, 0x15, 0x14, 0xc9, 0xdf, 0x03, 0x9c, 0x78, 0x39, 0x4e, 0xa0, 0xdc, 0x3a, 0x1b, 0x8c, 0xdf, 0xaa, 0xed, 0x25, 0xda, 0x60, 0xdd, 0x30, 0x64, 0x09, 0xcc, 0x94, 0x53, 0xa1, 0xad, 0xfd, 0x9e, 0xe7, 0x65, 0x15, 0xb8, 0xb1, 0xda, 0x9a, 0x28, 0x80, 0x51, 0x88, 0x93, 0x92, 0xe3, 0x03, 0xdf, 0x70, 0xba, 0x1b, 0x59, 0x3b, 0xb4, 0x8a, 0xb6, 0x0b, 0x0a, 0xa8, 0x48, 0xdf, 0xcc, 0x74, 0x4c, 0x71, 0x80, 0x08, 0xec, 0xc8, 0x8a, 0x73, 0xf5, 0x0e, 0x3d, 0xec, 0x16, 0xf6, 0x32, 0xfd, 0xf3, 0x6b, 0xba, 0xa9, 0x65, 0xd1, 0x87, 0xe2, 0x56, 0xcd, 0xde, 0x2c, 0xa4, 0x1b, 0x25, 0x81, 0xb2, 0xed, 0xea, 0xe9, 0x11, 0x07, 0xf5, 0x17, 0xd0, 0xca, 0x5d, 0x07, 0xb9, 0xb2, 0xa9, 0xa9, 0xee, 0x42, 0x33, 0x93, 0x21, 0x30, 0x5e, 0xd2, 0x58, 0xfd, 0xdd, 0x73, 0x0d, 0xb2, 0x93, 0x58, 0x77, 0x78, 0x40, 0x69, 0xba, 0x3c, 0x95, 0x1c, 0x61, 0xc6, 0xc6, 0x97, 0x1c, 0xef, 0x4d, 0x91, 0x0a, 0x42, 0x91, 0x1d, 0x14, 0x93, 0xf5, 0x78, 0x41, 0x32, 0x8a, 0x0a, 0x43, 0xd4, 0x3e, 0x6b, 0xb0, 0xd8, 0x0e, 0x04, } av1Pkt := &AV1Packet{} if _, err := av1Pkt.Unmarshal(av1Payload); err != nil { t.Fatal(err) } if !reflect.DeepEqual(av1Pkt, &AV1Packet{ Z: false, Y: true, W: 2, N: true, OBUElements: [][]byte{ av1Payload[2:14], av1Payload[14:], }, }) { t.Fatal("AV1 Unmarshal didn't store the expected results in the packet") } } rtp-1.7.13/codecs/codecs.go000066400000000000000000000001261422532252400154370ustar00rootroot00000000000000// Package codecs implements codec specific RTP payloader/depayloaders package codecs rtp-1.7.13/codecs/common.go000066400000000000000000000010421422532252400154650ustar00rootroot00000000000000package 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(marker bool, payload []byte) bool { return true } func (d *audioDepacketizer) IsPartitionHead(payload []byte) bool { return true } // videoDepacketizer is a mixin for video codec depacketizers type videoDepacketizer struct{} func (d *videoDepacketizer) IsPartitionTail(marker bool, payload []byte) bool { return marker } rtp-1.7.13/codecs/common_test.go000066400000000000000000000004131422532252400165250ustar00rootroot00000000000000package codecs import ( "testing" ) func TestCommon_Min(t *testing.T) { res := min(1, -1) if res != -1 { t.Fatal("Error: -1 < 1") } res = min(1, 2) if res != 1 { t.Fatal("Error: 1 < 2") } res = min(3, 3) if res != 3 { t.Fatal("Error: 3 == 3") } } rtp-1.7.13/codecs/error.go000066400000000000000000000007541422532252400153370ustar00rootroot00000000000000package 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") ) rtp-1.7.13/codecs/g711_packet.go000066400000000000000000000007631422532252400162140ustar00rootroot00000000000000package 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) } rtp-1.7.13/codecs/g711_packet_test.go000066400000000000000000000030501422532252400172430ustar00rootroot00000000000000package codecs //nolint:dupl import ( "bytes" "crypto/rand" "math" "testing" ) func TestG711Payloader(t *testing.T) { p := G711Payloader{} const ( testlen = 10000 testmtu = 1500 ) // generate random 8-bit g722 samples samples := make([]byte, testlen) _, err := rand.Read(samples) if err != nil { t.Fatal("RNG Error: ", err) } // make a copy, for payloader input samplesIn := make([]byte, testlen) copy(samplesIn, samples) // split our samples into payloads payloads := p.Payload(testmtu, samplesIn) outcnt := int(math.Ceil(float64(testlen) / testmtu)) if len(payloads) != outcnt { t.Fatalf("Generated %d payloads instead of %d", len(payloads), outcnt) } if !bytes.Equal(samplesIn, samples) { t.Fatal("Modified input samples") } samplesOut := bytes.Join(payloads, []byte{}) if !bytes.Equal(samplesIn, samplesOut) { t.Fatal("Output samples don't match") } payload := []byte{0x90, 0x90, 0x90} // 0 MTU, small payload res := p.Payload(0, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, small payload res = p.Payload(1, payload) if len(res) != len(payload) { t.Fatal("Generated payload should be the same size as original payload size") } // Positive MTU, small payload res = p.Payload(uint16(len(payload)-1), payload) if len(res) != len(payload)-1 { t.Fatal("Generated payload should be the same smaller than original payload size") } // Positive MTU, small payload res = p.Payload(10, payload) if len(res) != 1 { t.Fatal("Generated payload should be 1") } } rtp-1.7.13/codecs/g722_packet.go000066400000000000000000000007631422532252400162160ustar00rootroot00000000000000package 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) } rtp-1.7.13/codecs/g722_packet_test.go000066400000000000000000000030501422532252400172450ustar00rootroot00000000000000package codecs //nolint:dupl import ( "bytes" "crypto/rand" "math" "testing" ) func TestG722Payloader(t *testing.T) { p := G722Payloader{} const ( testlen = 10000 testmtu = 1500 ) // generate random 8-bit g722 samples samples := make([]byte, testlen) _, err := rand.Read(samples) if err != nil { t.Fatal("RNG Error: ", err) } // make a copy, for payloader input samplesIn := make([]byte, testlen) copy(samplesIn, samples) // split our samples into payloads payloads := p.Payload(testmtu, samplesIn) outcnt := int(math.Ceil(float64(testlen) / testmtu)) if len(payloads) != outcnt { t.Fatalf("Generated %d payloads instead of %d", len(payloads), outcnt) } if !bytes.Equal(samplesIn, samples) { t.Fatal("Modified input samples") } samplesOut := bytes.Join(payloads, []byte{}) if !bytes.Equal(samplesIn, samplesOut) { t.Fatal("Output samples don't match") } payload := []byte{0x90, 0x90, 0x90} // 0 MTU, small payload res := p.Payload(0, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, small payload res = p.Payload(1, payload) if len(res) != len(payload) { t.Fatal("Generated payload should be the same size as original payload size") } // Positive MTU, small payload res = p.Payload(uint16(len(payload)-1), payload) if len(res) != len(payload)-1 { t.Fatal("Generated payload should be the same smaller than original payload size") } // Positive MTU, small payload res = p.Payload(10, payload) if len(res) != 1 { t.Fatal("Generated payload should be 1") } } rtp-1.7.13/codecs/h264_packet.go000066400000000000000000000164041422532252400162170ustar00rootroot00000000000000package codecs import ( "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 ) func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } func emitNalus(nals []byte, emit func([]byte)) { nextInd := func(nalu []byte, start int) (indStart int, indLen int) { zeroCount := 0 for i, b := range nalu[start:] { if b == 0 { zeroCount++ continue } else if b == 1 { if zeroCount >= 2 { return start + i - zeroCount, zeroCount + 1 } } zeroCount = 0 } return -1, -1 } nextIndStart, nextIndLen := nextInd(nals, 0) if nextIndStart == -1 { emit(nals) } else { for nextIndStart != -1 { prevStart := nextIndStart + nextIndLen nextIndStart, nextIndLen = nextInd(nals, prevStart) if nextIndStart != -1 { emit(nals[prevStart:nextIndStart]) } else { // Emit until end of stream, no end indicator found emit(nals[prevStart:]) } } } } // Payload fragments a H264 packet across one or more byte arrays func (p *H264Payloader) Payload(mtu 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. naluData := nalu // According to the RFC, the first octet is skipped due to redundant information naluDataIndex := 1 naluDataLength := len(nalu) - naluDataIndex naluDataRemaining := naluDataLength if min(maxFragmentSize, naluDataRemaining) <= 0 { return } for naluDataRemaining > 0 { currentFragmentSize := min(maxFragmentSize, naluDataRemaining) out := make([]byte, fuaHeaderSize+currentFragmentSize) // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |F|NRI| Type | // +---------------+ out[0] = fuaNALUType out[0] |= naluRefIdc // +---------------+ // |0|1|2|3|4|5|6|7| // +-+-+-+-+-+-+-+-+ // |S|E|R| Type | // +---------------+ out[1] = naluType if naluDataRemaining == naluDataLength { // Set start bit out[1] |= 1 << 7 } else if naluDataRemaining-currentFragmentSize == 0 { // Set end bit out[1] |= 1 << 6 } copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize]) payloads = append(payloads, out) naluDataRemaining -= currentFragmentSize naluDataIndex += currentFragmentSize } }) return payloads } // H264Packet represents the H264 header that is stored in the payload of an RTP Packet type H264Packet struct { IsAVC bool fuaBuffer []byte videoDepacketizer } func (p *H264Packet) doPackaging(nalu []byte) []byte { if p.IsAVC { naluLength := make([]byte, 4) binary.BigEndian.PutUint32(naluLength, uint32(len(nalu))) return append(naluLength, nalu...) } return append(annexbNALUStartCode(), nalu...) } // 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 payload == nil { return nil, errNilPacket } else if len(payload) <= 2 { return nil, fmt.Errorf("%w: %d <= 2", errShortPacket, len(payload)) } // NALU Types // https://tools.ietf.org/html/rfc6184#section-5.4 naluType := payload[0] & naluTypeBitmask switch { case naluType > 0 && naluType < 24: return p.doPackaging(payload), nil case naluType == stapaNALUType: currOffset := int(stapaHeaderSize) result := []byte{} for currOffset < len(payload) { naluSize := int(binary.BigEndian.Uint16(payload[currOffset:])) currOffset += stapaNALULengthSize if len(payload) < currOffset+naluSize { return nil, fmt.Errorf("%w STAP-A declared size(%d) is larger than buffer(%d)", errShortPacket, naluSize, len(payload)-currOffset) } result = append(result, p.doPackaging(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(nalu), nil } return []byte{}, nil } return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType) } // H264PartitionHeadChecker is obsolete type H264PartitionHeadChecker struct{} // 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 } rtp-1.7.13/codecs/h264_packet_test.go000066400000000000000000000172161422532252400172600ustar00rootroot00000000000000package codecs import ( "reflect" "testing" ) func TestH264Payloader_Payload(t *testing.T) { pck := H264Payloader{} smallpayload := []byte{0x90, 0x90, 0x90} multiplepayload := []byte{0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x01, 0x90} largepayload := []byte{0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15} largePayloadPacketized := [][]byte{ {0x1c, 0x80, 0x01, 0x02, 0x03}, {0x1c, 0x00, 0x04, 0x05, 0x06}, {0x1c, 0x00, 0x07, 0x08, 0x09}, {0x1c, 0x00, 0x10, 0x11, 0x12}, {0x1c, 0x40, 0x13, 0x14, 0x15}, } // Positive MTU, nil payload res := pck.Payload(1, nil) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, empty payload res = pck.Payload(1, []byte{}) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, empty NAL res = pck.Payload(1, []byte{0x00, 0x00, 0x01}) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Negative MTU, small payload res = pck.Payload(0, smallpayload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // 0 MTU, small payload res = pck.Payload(0, smallpayload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, small payload res = pck.Payload(1, smallpayload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, small payload res = pck.Payload(5, smallpayload) if len(res) != 1 { t.Fatal("Generated payload shouldn't be empty") } if len(res[0]) != len(smallpayload) { t.Fatal("Generated payload should be the same size as original payload size") } // Multiple NALU in a single payload res = pck.Payload(5, multiplepayload) if len(res) != 2 { t.Fatal("2 nal units should be broken out") } for i := 0; i < 2; i++ { if len(res[i]) != 1 { t.Fatalf("Payload %d of 2 is packed incorrectly", i+1) } } // Large Payload split across multiple RTP Packets res = pck.Payload(5, largepayload) if !reflect.DeepEqual(res, largePayloadPacketized) { t.Fatal("FU-A packetization failed") } // Nalu type 9 or 12 res = pck.Payload(5, []byte{0x09, 0x00, 0x00}) if len(res) != 0 { t.Fatal("Generated payload should be empty") } } func TestH264Packet_Unmarshal(t *testing.T) { singlePayload := []byte{0x90, 0x90, 0x90} singlePayloadUnmarshaled := []byte{0x00, 0x00, 0x00, 0x01, 0x90, 0x90, 0x90} singlePayloadUnmarshaledAVC := []byte{0x00, 0x00, 0x00, 0x03, 0x90, 0x90, 0x90} largepayload := []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15} largepayloadAVC := []byte{0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15} largePayloadPacketized := [][]byte{ {0x1c, 0x80, 0x01, 0x02, 0x03}, {0x1c, 0x00, 0x04, 0x05, 0x06}, {0x1c, 0x00, 0x07, 0x08, 0x09}, {0x1c, 0x00, 0x10, 0x11, 0x12}, {0x1c, 0x40, 0x13, 0x14, 0x15}, } singlePayloadMultiNALU := []byte{0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8} singlePayloadMultiNALUUnmarshaled := []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x01, 0x68, 0x1a, 0x34, 0xe3, 0xc8} singlePayloadMultiNALUUnmarshaledAVC := []byte{0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00, 0x00, 0x00, 0x05, 0x68, 0x1a, 0x34, 0xe3, 0xc8} incompleteSinglePayloadMultiNALU := []byte{0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11} pkt := H264Packet{} avcPkt := H264Packet{IsAVC: true} if _, err := pkt.Unmarshal(nil); err == nil { t.Fatal("Unmarshal did not fail on nil payload") } if _, err := pkt.Unmarshal([]byte{0x00, 0x00}); err == nil { t.Fatal("Unmarshal accepted a packet that is too small for a payload and header") } if _, err := pkt.Unmarshal([]byte{0xFF, 0x00, 0x00}); err == nil { t.Fatal("Unmarshal accepted a packet with a NALU Type we don't handle") } if _, err := pkt.Unmarshal(incompleteSinglePayloadMultiNALU); err == nil { t.Fatal("Unmarshal accepted a STAP-A packet with insufficient data") } res, err := pkt.Unmarshal(singlePayload) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(res, singlePayloadUnmarshaled) { t.Fatal("Unmarshaling a single payload shouldn't modify the payload") } res, err = avcPkt.Unmarshal(singlePayload) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(res, singlePayloadUnmarshaledAVC) { t.Fatal("Unmarshaling a single payload into avc stream shouldn't modify the payload") } largePayloadResult := []byte{} for i := range largePayloadPacketized { res, err = pkt.Unmarshal(largePayloadPacketized[i]) if err != nil { t.Fatal(err) } largePayloadResult = append(largePayloadResult, res...) } if !reflect.DeepEqual(largePayloadResult, largepayload) { t.Fatal("Failed to unmarshal a large payload") } largePayloadResultAVC := []byte{} for i := range largePayloadPacketized { res, err = avcPkt.Unmarshal(largePayloadPacketized[i]) if err != nil { t.Fatal(err) } largePayloadResultAVC = append(largePayloadResultAVC, res...) } if !reflect.DeepEqual(largePayloadResultAVC, largepayloadAVC) { t.Fatal("Failed to unmarshal a large payload into avc stream") } res, err = pkt.Unmarshal(singlePayloadMultiNALU) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(res, singlePayloadMultiNALUUnmarshaled) { t.Fatal("Failed to unmarshal a single packet with multiple NALUs") } res, err = avcPkt.Unmarshal(singlePayloadMultiNALU) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(res, singlePayloadMultiNALUUnmarshaledAVC) { t.Fatal("Failed to unmarshal a single packet with multiple NALUs into avc stream") } } func TestH264IsPartitionHead(t *testing.T) { h264 := H264Packet{} if h264.IsPartitionHead(nil) { t.Fatal("nil must not be a partition head") } emptyNalu := []byte{} if h264.IsPartitionHead(emptyNalu) { t.Fatal("empty nalu must not be a partition head") } singleNalu := []byte{1, 0} if h264.IsPartitionHead(singleNalu) == false { t.Fatal("single nalu must be a partition head") } stapaNalu := []byte{stapaNALUType, 0} if h264.IsPartitionHead(stapaNalu) == false { t.Fatal("stapa nalu must be a partition head") } fuaStartNalu := []byte{fuaNALUType, fuStartBitmask} if h264.IsPartitionHead(fuaStartNalu) == false { t.Fatal("fua start nalu must be a partition head") } fuaEndNalu := []byte{fuaNALUType, fuEndBitmask} if h264.IsPartitionHead(fuaEndNalu) { t.Fatal("fua end nalu must not be a partition head") } fubStartNalu := []byte{fubNALUType, fuStartBitmask} if h264.IsPartitionHead(fubStartNalu) == false { t.Fatal("fub start nalu must be a partition head") } fubEndNalu := []byte{fubNALUType, fuEndBitmask} if h264.IsPartitionHead(fubEndNalu) { t.Fatal("fub end nalu must not be a partition head") } } func TestH264Payloader_Payload_SPS_and_PPS_handling(t *testing.T) { pck := H264Payloader{} expected := [][]byte{ {0x78, 0x00, 0x03, 0x07, 0x00, 0x01, 0x00, 0x03, 0x08, 0x02, 0x03}, {0x05, 0x04, 0x05}, } // When packetizing SPS and PPS are emitted with following NALU res := pck.Payload(1500, []byte{0x07, 0x00, 0x01}) if len(res) != 0 { t.Fatal("Generated payload should be empty") } res = pck.Payload(1500, []byte{0x08, 0x02, 0x03}) if len(res) != 0 { t.Fatal("Generated payload should be empty") } if !reflect.DeepEqual(pck.Payload(1500, []byte{0x05, 0x04, 0x05}), expected) { t.Fatal("SPS and PPS aren't packed together") } } rtp-1.7.13/codecs/h265_packet.go000066400000000000000000000574551422532252400162330ustar00rootroot00000000000000package 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 } rtp-1.7.13/codecs/h265_packet_test.go000066400000000000000000000656241422532252400172670ustar00rootroot00000000000000package codecs import ( "reflect" "testing" ) func TestH265_NALU_Header(t *testing.T) { tt := [...]struct { RawHeader []byte FBit bool Type uint8 LayerID uint8 TID uint8 IsAP bool IsFU bool IsPACI bool }{ // FBit { RawHeader: []byte{0x80, 0x00}, Type: 0, LayerID: 0, TID: 0, FBit: true, }, // VPS_NUT { RawHeader: []byte{0x40, 0x01}, Type: 32, LayerID: 0, TID: 1, }, // SPS_NUT { RawHeader: []byte{0x42, 0x01}, Type: 33, LayerID: 0, TID: 1, }, // PPS_NUT { RawHeader: []byte{0x44, 0x01}, Type: 34, LayerID: 0, TID: 1, }, // PREFIX_SEI_NUT { RawHeader: []byte{0x4e, 0x01}, Type: 39, LayerID: 0, TID: 1, }, // Fragmentation Unit { RawHeader: []byte{0x62, 0x01}, Type: h265NaluFragmentationUnitType, LayerID: 0, TID: 1, IsFU: true, }, } for _, cur := range tt { header := newH265NALUHeader(cur.RawHeader[0], cur.RawHeader[1]) if header.F() != cur.FBit { t.Fatal("invalid F bit") } if header.Type() != cur.Type { t.Fatal("invalid Type") } // For any type < 32, NAL is a VLC NAL unit. if header.IsTypeVCLUnit() != (header.Type() < 32) { t.Fatal("invalid IsTypeVCLUnit") } if header.IsAggregationPacket() != cur.IsAP { t.Fatal("invalid Type (aggregation packet)") } if header.IsFragmentationUnit() != cur.IsFU { t.Fatal("invalid Type (fragmentation unit)") } if header.IsPACIPacket() != cur.IsPACI { t.Fatal("invalid Type (PACI)") } if header.LayerID() != cur.LayerID { t.Fatal("invalid LayerID") } if header.TID() != cur.TID { t.Fatal("invalid TID") } } } func TestH265_FU_Header(t *testing.T) { tt := [...]struct { header H265FragmentationUnitHeader S bool E bool Type uint8 }{ // Start | IDR_W_RADL { header: H265FragmentationUnitHeader(0x93), S: true, E: false, Type: 19, }, // Continuation | IDR_W_RADL { header: H265FragmentationUnitHeader(0x13), S: false, E: false, Type: 19, }, // End | IDR_W_RADL { header: H265FragmentationUnitHeader(0x53), S: false, E: true, Type: 19, }, // Start | TRAIL_R { header: H265FragmentationUnitHeader(0x81), S: true, E: false, Type: 1, }, // Continuation | TRAIL_R { header: H265FragmentationUnitHeader(0x01), S: false, E: false, Type: 1, }, // End | TRAIL_R { header: H265FragmentationUnitHeader(0x41), S: false, E: true, Type: 1, }, } for _, cur := range tt { if cur.header.S() != cur.S { t.Fatal("invalid S field") } if cur.header.E() != cur.E { t.Fatal("invalid E field") } if cur.header.FuType() != cur.Type { t.Fatal("invalid FuType field") } } } func TestH265_SingleNALUnitPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedPacket *H265SingleNALUnitPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type '49' in H265NALUHeader { Raw: []byte{0x62, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, // Type '50' in H265NALUHeader { Raw: []byte{0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, { Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, ExpectedPacket: &H265SingleNALUnitPacket{ payloadHeader: newH265NALUHeader(0x01, 0x01), payload: []byte{0xab, 0xcd, 0xef}, }, }, // DONL, payload too small { Raw: []byte{0x01, 0x01, 0x93, 0xaf}, ExpectedErr: errShortPacket, WithDONL: true, }, { Raw: []byte{0x01, 0x01, 0xaa, 0xbb, 0xcc}, ExpectedPacket: &H265SingleNALUnitPacket{ payloadHeader: newH265NALUHeader(0x01, 0x01), donl: uint16ptr((uint16(0xaa) << 8) | uint16(0xbb)), payload: []byte{0xcc}, }, WithDONL: true, }, } for _, cur := range tt { parsed := &H265SingleNALUnitPacket{} if cur.WithDONL { parsed.WithDONL(cur.WithDONL) } // Just for code coverage sake parsed.isH265Packet() _, err := parsed.Unmarshal(cur.Raw) if cur.ExpectedErr != nil && err == nil { t.Fatal("should error") } else if cur.ExpectedErr == nil && err != nil { t.Fatal("should not error") } if cur.ExpectedPacket == nil { continue } if cur.ExpectedPacket.PayloadHeader() != parsed.PayloadHeader() { t.Fatal("invalid payload header") } if cur.ExpectedPacket.DONL() != nil && (*parsed.DONL() != *cur.ExpectedPacket.DONL()) { t.Fatal("invalid DONL") } if !reflect.DeepEqual(cur.ExpectedPacket.Payload(), parsed.Payload()) { t.Fatal("invalid payload") } } } func TestH265_AggregationPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedPacket *H265AggregationPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type '48' in H265NALUHeader { Raw: []byte{0xE0, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00, 0x1}, ExpectedErr: errShortPacket, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00}, ExpectedErr: errShortPacket, WithDONL: true, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00, 0x1}, ExpectedErr: errShortPacket, WithDONL: true, }, // Small payload { Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x02}, ExpectedErr: errShortPacket, WithDONL: true, }, // Single Aggregation Unit { Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, ExpectedErr: errShortPacket, WithDONL: true, }, // Incomplete second Aggregation Unit { Raw: []byte{ 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, // DONL 0x00, }, ExpectedErr: errShortPacket, WithDONL: true, }, // Incomplete second Aggregation Unit { Raw: []byte{ 0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, // DONL, NAL Unit size (2 bytes) 0x00, 0x55, 0x55, }, ExpectedErr: errShortPacket, WithDONL: true, }, // Valid Second Aggregation Unit { Raw: []byte{ 0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, // DONL, NAL Unit size (2 bytes), Payload 0x77, 0x00, 0x01, 0xaa, }, WithDONL: true, ExpectedPacket: &H265AggregationPacket{ firstUnit: &H265AggregationUnitFirst{ donl: uint16ptr(0xccdd), nalUnitSize: 2, nalUnit: []byte{0xff, 0xee}, }, otherUnits: []H265AggregationUnit{ { dond: uint8ptr(0x77), nalUnitSize: 1, nalUnit: []byte{0xaa}, }, }, }, }, } for _, cur := range tt { parsed := &H265AggregationPacket{} if cur.WithDONL { parsed.WithDONL(cur.WithDONL) } // Just for code coverage sake parsed.isH265Packet() _, err := parsed.Unmarshal(cur.Raw) if cur.ExpectedErr != nil && err == nil { t.Fatal("should error") } else if cur.ExpectedErr == nil && err != nil { t.Fatal("should not error") } if cur.ExpectedPacket == nil { continue } if cur.ExpectedPacket.FirstUnit() != nil { if parsed.FirstUnit().NALUSize() != cur.ExpectedPacket.FirstUnit().NALUSize() { t.Fatal("invalid first unit NALUSize") } if cur.ExpectedPacket.FirstUnit().DONL() != nil && *cur.ExpectedPacket.FirstUnit().DONL() != *parsed.FirstUnit().DONL() { t.Fatal("invalid first unit DONL") } if !reflect.DeepEqual(cur.ExpectedPacket.FirstUnit().NalUnit(), parsed.FirstUnit().NalUnit()) { t.Fatal("invalid first unit NalUnit") } } if len(cur.ExpectedPacket.OtherUnits()) != len(parsed.OtherUnits()) { t.Fatal("number of other units mismatch") } for ndx, unit := range cur.ExpectedPacket.OtherUnits() { if parsed.OtherUnits()[ndx].NALUSize() != unit.NALUSize() { t.Fatal("invalid unit NALUSize") } if unit.DOND() != nil && *unit.DOND() != *parsed.OtherUnits()[ndx].DOND() { t.Fatal("invalid unit DOND") } if !reflect.DeepEqual(unit.NalUnit(), parsed.OtherUnits()[ndx].NalUnit()) { t.Fatal("invalid first unit NalUnit") } } if !reflect.DeepEqual(cur.ExpectedPacket.OtherUnits(), parsed.OtherUnits()) { t.Fatal("invalid payload") } } } func TestH265_FragmentationUnitPacket(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedFU *H265FragmentationUnitPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type not '49' in H265NALUHeader { Raw: []byte{0x40, 0x01, 0x93, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, { Raw: []byte{0x62, 0x01, 0x93, 0xaf}, ExpectedFU: &H265FragmentationUnitPacket{ payloadHeader: newH265NALUHeader(0x62, 0x01), fuHeader: H265FragmentationUnitHeader(0x93), donl: nil, payload: []byte{0xaf}, }, }, { Raw: []byte{0x62, 0x01, 0x93, 0xcc}, WithDONL: true, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, WithDONL: true, ExpectedFU: &H265FragmentationUnitPacket{ payloadHeader: newH265NALUHeader(0x62, 0x01), fuHeader: H265FragmentationUnitHeader(0x93), donl: uint16ptr((uint16(0xcc) << 8) | uint16(0xdd)), payload: []byte{0xaf, 0x0d, 0x5a}, }, }, } for _, cur := range tt { parsed := &H265FragmentationUnitPacket{} if cur.WithDONL { parsed.WithDONL(cur.WithDONL) } // Just for code coverage sake parsed.isH265Packet() _, err := parsed.Unmarshal(cur.Raw) if cur.ExpectedErr != nil && err == nil { t.Fatal("should error") } else if cur.ExpectedErr == nil && err != nil { t.Fatal("should not error") } if cur.ExpectedFU == nil { continue } if parsed.PayloadHeader() != cur.ExpectedFU.PayloadHeader() { t.Fatal("invalid payload header") } if parsed.FuHeader() != cur.ExpectedFU.FuHeader() { t.Fatal("invalid FU header") } if cur.ExpectedFU.DONL() != nil && (*parsed.DONL() != *cur.ExpectedFU.DONL()) { t.Fatal("invalid DONL") } if !reflect.DeepEqual(parsed.Payload(), cur.ExpectedFU.Payload()) { t.Fatal("invalid Payload") } } } func TestH265_TemporalScalabilityControlInformation(t *testing.T) { tt := [...]struct { Value H265TSCI ExpectedTL0PICIDX uint8 ExpectedIrapPicID uint8 ExpectedS bool ExpectedE bool ExpectedRES uint8 }{ {}, { Value: H265TSCI((uint32(0xCA) << 24) | (uint32(0xFE) << 16)), ExpectedTL0PICIDX: 0xCA, ExpectedIrapPicID: 0xFE, }, { Value: H265TSCI(uint32(1) << 15), ExpectedS: true, }, { Value: H265TSCI(uint32(1) << 14), ExpectedE: true, }, { Value: H265TSCI(uint32(0x0A) << 8), ExpectedRES: 0x0A, }, // Sets RES, and force sets S and E to 0. { Value: H265TSCI((uint32(0xAA) << 8) & ^(uint32(1) << 15) & ^(uint32(1) << 14)), ExpectedRES: 0xAA & 0b00111111, }, } for _, cur := range tt { if cur.Value.TL0PICIDX() != cur.ExpectedTL0PICIDX { t.Fatal("invalid TL0PICIDX") } if cur.Value.IrapPicID() != cur.ExpectedIrapPicID { t.Fatal("invalid IrapPicID") } if cur.Value.S() != cur.ExpectedS { t.Fatal("invalid S") } if cur.Value.E() != cur.ExpectedE { t.Fatal("invalid E") } if cur.Value.RES() != cur.ExpectedRES { t.Fatal("invalid RES") } } } func TestH265_PACI_Packet(t *testing.T) { tt := [...]struct { Raw []byte ExpectedFU *H265PACIPacket ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Type not '50' in H265NALUHeader { Raw: []byte{0x40, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, // Invalid header extension size { Raw: []byte{0x64, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errInvalidH265PacketType, }, // No Header Extension { Raw: []byte{0x64, 0x01, 0x64, 0x00, 0xab, 0xcd, 0xef}, ExpectedFU: &H265PACIPacket{ payloadHeader: newH265NALUHeader(0x64, 0x01), paciHeaderFields: (uint16(0x64) << 8) | uint16(0x00), phes: nil, payload: []byte{0xab, 0xcd, 0xef}, }, }, // Header Extension 1 byte { Raw: []byte{0x64, 0x01, 0x64, 0x10, 0xff, 0xab, 0xcd, 0xef}, ExpectedFU: &H265PACIPacket{ payloadHeader: newH265NALUHeader(0x64, 0x01), paciHeaderFields: (uint16(0x64) << 8) | uint16(0x10), phes: []byte{0xff}, payload: []byte{0xab, 0xcd, 0xef}, }, }, // Header Extension TSCI { Raw: []byte{0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef}, ExpectedFU: &H265PACIPacket{ payloadHeader: newH265NALUHeader(0x64, 0x01), paciHeaderFields: (uint16(0x64) << 8) | uint16(0b00111000), phes: []byte{0xaa, 0xbb, 0x80}, payload: []byte{0xab, 0xcd, 0xef}, }, }, } for _, cur := range tt { parsed := &H265PACIPacket{} _, err := parsed.Unmarshal(cur.Raw) // Just for code coverage sake parsed.isH265Packet() if cur.ExpectedErr != nil && err == nil { t.Fatal("should error") } else if cur.ExpectedErr == nil && err != nil { t.Fatal("should not error") } if cur.ExpectedFU == nil { continue } if cur.ExpectedFU.PayloadHeader() != parsed.PayloadHeader() { t.Fatal("invalid PayloadHeader") } if cur.ExpectedFU.A() != parsed.A() { t.Fatal("invalid A") } if cur.ExpectedFU.CType() != parsed.CType() { t.Fatal("invalid CType") } if cur.ExpectedFU.PHSsize() != parsed.PHSsize() { t.Fatal("invalid PHSsize") } if cur.ExpectedFU.F0() != parsed.F0() { t.Fatal("invalid F0") } if cur.ExpectedFU.F1() != parsed.F1() { t.Fatal("invalid F1") } if cur.ExpectedFU.F2() != parsed.F2() { t.Fatal("invalid F2") } if cur.ExpectedFU.Y() != parsed.Y() { t.Fatal("invalid Y") } if !reflect.DeepEqual(cur.ExpectedFU.PHES(), parsed.PHES()) { t.Fatal("invalid PHES") } if !reflect.DeepEqual(cur.ExpectedFU.Payload(), parsed.Payload()) { t.Fatal("invalid Payload") } if cur.ExpectedFU.TSCI() != nil && (*cur.ExpectedFU.TSCI() != *parsed.TSCI()) { t.Fatal("invalid TSCI") } } } func TestH265_Packet(t *testing.T) { tt := [...]struct { Raw []byte WithDONL bool ExpectedPacketType reflect.Type ExpectedErr error }{ { Raw: nil, ExpectedErr: errNilPacket, }, { Raw: []byte{}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x62, 0x01, 0x93}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x64, 0x01, 0x93, 0xaf}, ExpectedErr: errShortPacket, }, { Raw: []byte{0x01, 0x01}, WithDONL: true, ExpectedErr: errShortPacket, }, // FBit enabled in H265NALUHeader { Raw: []byte{0x80, 0x01, 0x93, 0xaf, 0xaf, 0xaf, 0xaf}, ExpectedErr: errH265CorruptedPacket, }, // Valid H265SingleNALUnitPacket { Raw: []byte{0x01, 0x01, 0xab, 0xcd, 0xef}, ExpectedPacketType: reflect.TypeOf((*H265SingleNALUnitPacket)(nil)), }, // Invalid H265SingleNALUnitPacket { Raw: []byte{0x01, 0x01, 0x93, 0xaf}, ExpectedErr: errShortPacket, WithDONL: true, }, // Valid H265PACIPacket { Raw: []byte{0x64, 0x01, 0x64, 0b00111000, 0xaa, 0xbb, 0x80, 0xab, 0xcd, 0xef}, ExpectedPacketType: reflect.TypeOf((*H265PACIPacket)(nil)), }, // Valid H265FragmentationUnitPacket { Raw: []byte{0x62, 0x01, 0x93, 0xcc, 0xdd, 0xaf, 0x0d, 0x5a}, ExpectedPacketType: reflect.TypeOf((*H265FragmentationUnitPacket)(nil)), WithDONL: true, }, // Valid H265AggregationPacket { Raw: []byte{0x60, 0x01, 0xcc, 0xdd, 0x00, 0x02, 0xff, 0xee, 0x77, 0x00, 0x01, 0xaa}, ExpectedPacketType: reflect.TypeOf((*H265AggregationPacket)(nil)), WithDONL: true, }, // Invalid H265AggregationPacket { Raw: []byte{0x60, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00}, ExpectedErr: errShortPacket, WithDONL: true, }, } for _, cur := range tt { pck := &H265Packet{} if cur.WithDONL { pck.WithDONL(true) } _, err := pck.Unmarshal(cur.Raw) if cur.ExpectedErr != nil && err == nil { t.Fatal("should error") } else if cur.ExpectedErr == nil && err != nil { t.Fatal("should not error") } if cur.ExpectedErr != nil { continue } if reflect.TypeOf(pck.Packet()) != cur.ExpectedPacketType { t.Fatal("invalid packet type") } } } func TestH265IsPartitionHead(t *testing.T) { h265 := H265Packet{} if h265.IsPartitionHead(nil) { t.Fatal("nil must not be a partition head") } emptyNalu := []byte{} if h265.IsPartitionHead(emptyNalu) { t.Fatal("empty nalu must not be a partition head") } singleNalu := []byte{0x01, 0x01, 0xab, 0xcd, 0xef} if h265.IsPartitionHead(singleNalu) == false { t.Fatal("single nalu must be a partition head") } fbitNalu := []byte{0x80, 0x00, 0x00} if h265.IsPartitionHead(fbitNalu) == false { t.Fatal("fbit nalu must be a partition head") } fuStartNalu := []byte{0x62, 0x01, 0x93} if h265.IsPartitionHead(fuStartNalu) == false { t.Fatal("fu start nalu must be a partition head") } fuEndNalu := []byte{0x62, 0x01, 0x53} if h265.IsPartitionHead(fuEndNalu) { t.Fatal("fu end nalu must not be a partition head") } } func TestH265_Packet_Real(t *testing.T) { // Tests decoding of real H265 payloads extracted from a Wireshark dump. tt := [...]string{ "\x40\x01\x0c\x01\xff\xff\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xac\x09", "\x42\x01\x01\x01\x60\x00\x00\x03\x00\xb0\x00\x00\x03\x00\x00\x03\x00\x7b\xa0\x03\xc0\x80\x10\xe5\x8d\xae\x49\x32\xf4\xdc\x04\x04\x04\x02", "\x44\x01\xc0\xf2\xf0\x3c\x90", "\x4e\x01\xe5\x04\x61\x0c\x00\x00\x80", "\x62\x01\x93\xaf\x0d\x5a\xfe\x67\x77\x29\xc0\x74\xf3\x57\x4c\x16\x94\xaa\x7c\x2a\x64\x5f\xe9\xa5\xb7\x2a\xa3\x95\x9d\x94\xa7\xb4\xd3\xc4\x4a\xb1\xb7\x69\xca\xbe\x75\xc5\x64\xa8\x97\x4b\x8a\xbf\x7e\xf0\x0f\xc3\x22\x60\x67\xab\xae\x96\xd6\x99\xca\x7a\x8d\x35\x93\x1a\x67\x60\xe7\xbe\x7e\x13\x95\x3c\xe0\x11\xc1\xc1\xa7\x48\xef\xf7\x7b\xb0\xeb\x35\x49\x81\x4e\x4e\x54\xf7\x31\x6a\x38\xa1\xa7\x0c\xd6\xbe\x3b\x25\xba\x08\x19\x0b\x49\xfd\x90\xbb\x73\x7a\x45\x8c\xb9\x73\x43\x04\xc5\x5f\xda\x0f\xd5\x70\x4c\x11\xee\x72\xb8\x6a\xb4\x95\x62\x64\xb6\x23\x14\x7e\xdb\x0e\xa5\x0f\x86\x31\xe4\xd1\x64\x56\x43\xf6\xb7\xe7\x1b\x93\x4a\xeb\xd0\xa6\xe3\x1f\xce\xda\x15\x67\x05\xb6\x77\x36\x8b\x27\x5b\xc6\xf2\x95\xb8\x2b\xcc\x9b\x0a\x03\x05\xbe\xc3\xd3\x85\xf5\x69\xb6\x19\x1f\x63\x2d\x8b\x65\x9e\xc3\x9d\xd2\x44\xb3\x7c\x86\x3b\xea\xa8\x5d\x02\xe5\x40\x03\x20\x76\x48\xff\xf6\x2b\x0d\x18\xd6\x4d\x49\x70\x1a\x5e\xb2\x89\xca\xec\x71\x41\x79\x4e\x94\x17\x0c\x57\x51\x55\x14\x61\x40\x46\x4b\x3e\x17\xb2\xc8\xbd\x1c\x06\x13\x91\x72\xf8\xc8\xfc\x6f\xb0\x30\x9a\xec\x3b\xa6\xc9\x33\x0b\xa5\xe5\xf4\x65\x7a\x29\x8b\x76\x62\x81\x12\xaf\x20\x4c\xd9\x21\x23\x9e\xeb\xc9\x0e\x5b\x29\x35\x7f\x41\xcd\xce\xa1\xc4\xbe\x01\x30\xb9\x11\xc3\xb1\xe4\xce\x45\xd2\x5c\xb3\x1e\x69\x78\xba\xb1\x72\xe4\x88\x54\xd8\x5d\xd0\xa8\x3a\x74\xad\xe5\xc7\xc1\x59\x7c\x78\x15\x26\x37\x3d\x50\xae\xb3\xa4\x5b\x6c\x7d\x65\x66\x85\x4d\x16\x9a\x67\x74\xad\x55\x32\x3a\x84\x85\x0b\x6a\xeb\x24\x97\xb4\x20\x4d\xca\x41\x61\x7a\xd1\x7b\x60\xdb\x7f\xd5\x61\x22\xcf\xd1\x7e\x4c\xf3\x85\xfd\x13\x63\xe4\x9d\xed\xac\x13\x0a\xa0\x92\xb7\x34\xde\x65\x0f\xd9\x0f\x9b\xac\xe2\x47\xe8\x5c\xb3\x11\x8e\xc6\x08\x19\xd0\xb0\x85\x52\xc8\x5c\x1b\x08\x0a\xce\xc9\x6b\xa7\xef\x95\x2f\xd0\xb8\x63\xe5\x4c\xd4\xed\x6e\x87\xe9\xd4\x0a\xe6\x11\x44\x63\x00\x94\x18\xe9\x28\xba\xcf\x92\x43\x06\x59\xdd\x37\x4f\xd3\xef\x9d\x31\x5e\x9b\x48\xf9\x1f\x3e\x7b\x95\x3a\xbd\x1f\x71\x55\x0c\x06\xf9\x86\xf8\x3d\x39\x16\x50\xb3\x21\x11\x19\x6f\x70\xa9\x48\xe8\xbb\x0a\x11\x23\xf8\xab\xfe\x44\xe0\xbb\xe8\x64\xfa\x85\xe4\x02\x55\x88\x41\xc6\x30\x7f\x10\xad\x75\x02\x4b\xef\xe1\x0b\x06\x3c\x10\x49\x83\xf9\xd1\x3e\x3e\x67\x86\x4c\xf8\x9d\xde\x5a\xc4\xc8\xcf\xb6\xf4\xb0\xd3\x34\x58\xd4\x7b\x4d\xd3\x37\x63\xb2\x48\x8a\x7e\x20\x00\xde\xb4\x42\x8f\xda\xe9\x43\x9e\x0c\x16\xce\x79\xac\x2c\x70\xc1\x89\x05\x36\x62\x6e\xd9\xbc\xfb\x63\xc6\x79\x89\x3c\x90\x89\x2b\xd1\x8c\xe0\xc2\x54\xc7\xd6\xb4\xe8\x9e\x96\x55\x6e\x7b\xd5\x7f\xac\xd4\xa7\x1c\xa0\xdf\x01\x30\xad\xc0\x9f\x69\x06\x10\x43\x7f\xf4\x5d\x62\xa3\xea\x73\xf2\x14\x79\x19\x13\xea\x59\x14\x79\xa8\xe7\xce\xce\x44\x25\x13\x41\x18\x57\xdd\xce\xe4\xbe\xcc\x20\x80\x29\x71\x73\xa7\x7c\x86\x39\x76\xf4\xa7\x1c\x63\x24\x21\x93\x1e\xb5\x9a\x5c\x8a\x9e\xda\x8b\x9d\x88\x97\xfc\x98\x7d\x26\x74\x04\x1f\xa8\x10\x4f\x45\xcd\x46\xe8\x28\xe4\x8e\x59\x67\x63\x4a\xcf\x1e\xed\xdd\xbb\x79\x2f\x8d\x94\xab\xfc\xdb\xc5\x79\x1a\x4d\xcd\x53\x41\xdf\xd1\x7a\x8f\x46\x3e\x1f\x79\x88\xe3\xee\x9f\xc4\xc1\xe6\x2e\x89\x4d\x28\xc9\xca\x28\xc2\x0a\xc5\xc7\xf1\x22\xcd\xb3\x36\xfa\xe3\x7e\xa6\xcd\x95\x55\x5e\x0e\x1a\x75\x7f\x65\x27\xd3\x37\x4f\x23\xc5\xab\x49\x68\x4e\x02\xb5\xbf\xd7\x95\xc0\x78\x67\xbc\x1a\xe9\xae\x6f\x44\x58\x8a\xc2\xce\x42\x98\x4e\x77\xc7\x2a\xa0\xa7\x7d\xe4\x3b\xd1\x20\x82\x1a\xd3\xe2\xc7\x76\x5d\x06\x46\xb5\x24\xd7\xfb\x57\x63\x2b\x19\x51\x48\x65\x6d\xfb\xe0\x98\xd1\x14\x0e\x17\x64\x29\x34\x6f\x6e\x66\x9e\x8d\xc9\x89\x49\x69\xee\x74\xf3\x35\xe6\x8b\x67\x56\x95\x7f\x1b\xe9\xed\x8c\x0f\xe2\x19\x59\xbf\x03\x35\x55\x3c\x04\xbc\x40\x52\x90\x10\x08\xad\xa7\x65\xe0\x31\xcb\xcf\x3d\xd4\x62\x68\x01\x0d\xed\xf5\x28\x64\x2d\xaa\x7c\x99\x15\x8d\x70\x32\x53\xb8\x9d\x0a\x3c\xbf\x91\x02\x04\xd0\xee\x87\xce\x04\xcc\x3e\xa8\x20\xfd\x97\xdf\xbf\x4a\xbc\xfc\xc9\x7c\x77\x21\xcc\x23\x6f\x59\x38\xd8\xd9\xa0\x0e\xb1\x23\x4e\x04\x3f\x14\x9e\xcc\x05\x54\xab\x20\x69\xed\xa4\xd5\x1d\xb4\x1b\x52\xed\x6a\xea\xeb\x7f\xd1\xbc\xfd\x75\x20\xa0\x1c\x59\x8c\x5a\xa1\x2a\x70\x64\x11\xb1\x7b\xc1\x24\x80\x28\x51\x4c\x94\xa1\x95\x64\x72\xe8\x90\x67\x38\x74\x2b\xab\x38\x46\x12\x71\xce\x19\x98\x98\xf7\x89\xd4\xfe\x2f\x2a\xc5\x61\x20\xd0\xa4\x1a\x51\x3c\x82\xc8\x18\x31\x7a\x10\xe8\x1c\xc6\x95\x5a\xa0\x82\x88\xce\x8f\x4b\x47\x85\x7e\x89\x95\x95\x52\x1e\xac\xce\x45\x57\x61\x38\x97\x2b\x62\xa5\x14\x6f\xc3\xaa\x6c\x35\x83\xc9\xa3\x1e\x30\x89\xf4\xb1\xea\x4f\x39\xde\xde\xc7\x46\x5c\x0e\x85\x41\xec\x6a\xa4\xcb\xee\x70\x9c\x57\xd9\xf4\xa1\xc3\x9c\x2a\x0a\xf0\x5d\x58\xb0\xae\xd4\xdc\xc5\x6a\xa8\x34\xfa\x23\xef\xef\x08\x39\xc3\x3d\xea\x11\x6e\x6a\xe0\x1e\xd0\x52\xa8\xc3\x6e\xc9\x1c\xfc\xd0\x0c\x4c\xea\x0d\x82\xcb\xdd\x29\x1a\xc4\x4f\x6e\xa3\x4d\xcb\x7a\x38\x77\xe5\x15\x6e\xad\xfa\x9d\x2f\x02\xb6\x39\x84\x3a\x60\x8f\x71\x9f\x92\xe5\x24\x4f\xbd\x18\x49\xd5\xef\xbf\x70\xfb\xd1\x4c\x2e\xfc\x2f\x36\xf3\x00\x31\x2e\x90\x18\xcc\xf4\x71\xb9\xe4\xf9\xbe\xcb\x5e\xff\xf3\xe7\xf8\xca\x03\x60\x66\xb3\xc9\x5a\xf9\x74\x09\x02\x57\xb6\x90\x94\xfc\x41\x35\xdc\x35\x3f\x32\x7a\xa6\xa5\xcd\x8a\x8f\xc8\x3d\xc8\x81\xc3\xec\x37\x74\x86\x61\x41\x0d\xc5\xe2\xc8\x0c\x84\x2b\x3b\x71\x58\xde\x1b\xe3\x20\x65\x2e\x76\xf4\x98\xd8\xaa\x78\xe6\xeb\xb8\x85\x0d\xa0\xd0\xf5\x57\x64\x01\x58\x55\x82\xd5\x0f\x2d\x9c\x3e\x2a\xa0\x7e\xaf\x42\xf3\x37\xd1\xb3\xaf\xda\x5b\xa9\xda\xe3\x89\x5d\xf1\xca\xa5\x12\x3d\xe7\x91\x95\x53\x21\x72\xca\x7f\xf6\x79\x59\x21\xcf\x30\x18\xfb\x78\x55\x40\x59\xc3\xf9\xf1\xdd\x58\x44\x5e\x83\x11\x5c\x2d\x1d\x91\xf6\x01\x3d\x3f\xd4\x33\x81\x66\x6c\x40\x7a\x9d\x70\x10\x58\xe6\x53\xad\x85\x11\x99\x3e\x4b\xbc\x31\xc6\x78\x9d\x79\xc5\xde\x9f\x2e\x43\xfa\x76\x84\x2f\xfd\x28\x75\x12\x48\x25\xfd\x15\x8c\x29\x6a\x91\xa4\x63\xc0\xa2\x8c\x41\x3c\xf1\xb0\xf8\xdf\x66\xeb\xbd\x14\x88\xa9\x81\xa7\x35\xc4\x41\x40\x6c\x10\x3f\x09\xbd\xb5\xd3\x7a\xee\x4b\xd5\x86\xff\x36\x03\x6b\x78\xde", "\x62\x01\x53\x8a\xe9\x25\xe1\x06\x09\x8e\xba\x12\x74\x87\x09\x9a\x95\xe4\x86\x62\x2b\x4b\xf9\xa6\x2e\x7b\x35\x43\xf7\x39\x99\x0f\x3b\x6f\xfd\x1a\x6e\x23\x54\x70\xb5\x1d\x10\x1c\x63\x40\x96\x99\x41\xb6\x96\x0b\x70\x98\xec\x17\xb0\xaa\xdc\x4a\xab\xe8\x3b\xb7\x6b\x00\x1c\x5b\xc3\xe0\xa2\x8b\x7c\x17\xc8\x92\xc9\xb0\x92\xb6\x70\x84\x95\x30", "\x4e\x01\xe5\x04\x35\xac\x00\x00\x80", "\x62\x01\x41\xb0\x75\x5c\x27\x46\xef\x8a\xe7\x1d\x50\x38\xb2\x13\x33\xe0\x79\x35\x1b\xc2\xb5\x79\x73\xe7\xc2\x6f\xb9\x1a\x8c\x21\x0e\xa9\x54\x17\x6c\x41\xab\xc8\x16\x57\xec\x5e\xeb\x89\x3b\xa9\x90\x8c\xff\x4d\x46\x8b\xf0\xd9\xc0\xd0\x51\xcf\x8b\x88\xf1\x5f\x1e\x9e\xc1\xb9\x1f\xe3\x06\x45\x35\x8a\x47\xe8\x9a\xf2\x4f\x19\x4c\xf8\xce\x68\x1b\x63\x34\x11\x75\xea\xe5\xb1\x0f\x38\xcc\x05\x09\x8b\x3e\x2b\x88\x84\x9d\xc5\x03\xc3\xc0\x90\x32\xe2\x45\x69\xb1\xe5\xf7\x68\x6b\x16\x90\xa0\x40\xe6\x18\x74\xd8\x68\xf3\x34\x38\x99\xf2\x6c\xb7\x1a\x35\x21\xca\x52\x56\x4c\x7f\xb2\xa3\xd5\xb8\x40\x50\x48\x3e\xdc\xdf\x0b\xf5\x54\x5a\x15\x1a\xe2\xc3\xb4\x94\xda\x3f\xb5\x34\xa2\xca\xbc\x2f\xe0\xa4\xe5\x69\xf4\xbf\x62\x4d\x15\x21\x1b\x11\xfc\x39\xaa\x86\x74\x96\x63\xfd\x07\x53\x26\xf6\x34\x72\xeb\x14\x37\x98\x0d\xf4\x68\x91\x2c\x6b\x46\x83\x88\x82\x04\x8b\x9f\xb8\x32\x73\x75\x8b\xf9\xac\x71\x42\xd1\x2d\xb4\x28\x28\xf5\x78\xe0\x32\xf3\xe1\xfc\x43\x6b\xf9\x92\xf7\x48\xfe\x7f\xc0\x17\xbd\xfd\xba\x2f\x58\x6f\xee\x84\x03\x18\xce\xb0\x9d\x8d\xeb\x22\xf1\xfc\xb1\xcf\xff\x2f\xb2\x9f\x6c\xe5\xb4\x69\xdc\xdd\x20\x93\x00\x30\xad\x56\x04\x66\x7e\xa3\x3c\x18\x4b\x43\x66\x00\x27\x1e\x1c\x09\x11\xd8\xf4\x8a\x9e\xc5\x6a\x94\xe5\xae\x0b\x8a\xbe\x84\xda\xe5\x44\x7f\x38\x1c\xe7\xbb\x03\x19\x66\xe1\x5d\x1d\xc1\xbd\x3d\xc6\xb7\xe3\xff\x7f\x8e\xff\x1e\xf6\x9e\x6f\x58\x27\x74\x65\xef\x02\x5d\xa4\xde\x27\x7f\x51\xe3\x4b\x9e\x3f\x79\x83\xbd\x1b\x8f\x0d\x77\xfb\xbc\xc5\x9f\x15\xa7\x4e\x05\x8a\x24\x97\x66\xb2\x7c\xf6\xe1\x84\x54\xdb\x39\x5e\xf6\x1b\x8f\x05\x73\x1d\xb6\x8e\xd7\x09\x9a\xc5\x92\x80", } for _, cur := range tt { pck := &H265Packet{} _, err := pck.Unmarshal([]byte(cur)) if err != nil { t.Fatal("invalid packet type") } } } func uint8ptr(v uint8) *uint8 { return &v } func uint16ptr(v uint16) *uint16 { return &v } rtp-1.7.13/codecs/opus_packet.go000066400000000000000000000016101422532252400165130ustar00rootroot00000000000000package codecs // OpusPayloader payloads Opus packets type OpusPayloader struct{} // Payload fragments an Opus packet across one or more byte arrays func (p *OpusPayloader) Payload(mtu 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 is obsolete type OpusPartitionHeadChecker struct{} rtp-1.7.13/codecs/opus_packet_test.go000066400000000000000000000030211422532252400175500ustar00rootroot00000000000000package codecs import ( "errors" "testing" ) func TestOpusPacket_Unmarshal(t *testing.T) { pck := OpusPacket{} // Nil packet raw, err := pck.Unmarshal(nil) if raw != nil { t.Fatal("Result should be nil in case of error") } if err == nil || err.Error() != errNilPacket.Error() { t.Fatal("Error should be:", errNilPacket) } // Empty packet raw, err = pck.Unmarshal([]byte{}) if raw != nil { t.Fatal("Result should be nil in case of error") } if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } // Normal packet raw, err = pck.Unmarshal([]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } } func TestOpusPayloader_Payload(t *testing.T) { pck := OpusPayloader{} payload := []byte{0x90, 0x90, 0x90} // Positive MTU, nil payload res := pck.Payload(1, nil) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, small payload res = pck.Payload(1, payload) if len(res) != 1 { t.Fatal("Generated payload should be the 1") } // Positive MTU, small payload res = pck.Payload(2, payload) if len(res) != 1 { t.Fatal("Generated payload should be the 1") } } func TestOpusIsPartitionHead(t *testing.T) { opus := &OpusPacket{} t.Run("NormalPacket", func(t *testing.T) { if !opus.IsPartitionHead([]byte{0x00, 0x00}) { t.Fatal("All OPUS RTP packet should be the head of a new partition") } }) } rtp-1.7.13/codecs/vp8_packet.go000066400000000000000000000116341422532252400162510ustar00rootroot00000000000000package 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) { if payload == nil { return nil, errNilPacket } payloadLen := len(payload) if payloadLen < 4 { return nil, errShortPacket } payloadIndex := 0 p.X = (payload[payloadIndex] & 0x80) >> 7 p.N = (payload[payloadIndex] & 0x20) >> 5 p.S = (payload[payloadIndex] & 0x10) >> 4 p.PID = payload[payloadIndex] & 0x07 payloadIndex++ if p.X == 1 { p.I = (payload[payloadIndex] & 0x80) >> 7 p.L = (payload[payloadIndex] & 0x40) >> 6 p.T = (payload[payloadIndex] & 0x20) >> 5 p.K = (payload[payloadIndex] & 0x10) >> 4 payloadIndex++ } if p.I == 1 { // PID present? if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1]) payloadIndex += 2 } else { p.PictureID = uint16(payload[payloadIndex]) payloadIndex++ } } if payloadIndex >= payloadLen { return nil, errShortPacket } if p.L == 1 { p.TL0PICIDX = payload[payloadIndex] payloadIndex++ } if payloadIndex >= payloadLen { return nil, errShortPacket } if p.T == 1 || p.K == 1 { if p.T == 1 { p.TID = payload[payloadIndex] >> 6 p.Y = (payload[payloadIndex] >> 5) & 0x1 } if p.K == 1 { p.KEYIDX = payload[payloadIndex] & 0x1F } payloadIndex++ } if payloadIndex >= payloadLen { return nil, errShortPacket } p.Payload = payload[payloadIndex:] return p.Payload, nil } // VP8PartitionHeadChecker is obsolete type VP8PartitionHeadChecker struct{} // 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 } rtp-1.7.13/codecs/vp8_packet_test.go000066400000000000000000000122501422532252400173030ustar00rootroot00000000000000package codecs import ( "errors" "reflect" "testing" ) func TestVP8Packet_Unmarshal(t *testing.T) { pck := VP8Packet{} // Nil packet raw, err := pck.Unmarshal(nil) if raw != nil { t.Fatal("Result should be nil in case of error") } if !errors.Is(err, errNilPacket) { t.Fatal("Error should be:", errNilPacket) } // Nil payload raw, err = pck.Unmarshal([]byte{}) if raw != nil { t.Fatal("Result should be nil in case of error") } if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } // Payload smaller than header size raw, err = pck.Unmarshal([]byte{0x00, 0x11, 0x22}) if raw != nil { t.Fatal("Result should be nil in case of error") } if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } // Normal payload raw, err = pck.Unmarshal([]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x90}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } // Header size, only X raw, err = pck.Unmarshal([]byte{0x80, 0x00, 0x00, 0x00}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } // Header size, X and I raw, err = pck.Unmarshal([]byte{0x80, 0x80, 0x00, 0x00}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } // Header size, X and I, PID 16bits raw, err = pck.Unmarshal([]byte{0x80, 0x80, 0x81, 0x00}) if raw != nil { t.Fatal("Result should be nil in case of error") } if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } // Header size, X and L raw, err = pck.Unmarshal([]byte{0x80, 0x40, 0x00, 0x00}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } // Header size, X and T raw, err = pck.Unmarshal([]byte{0x80, 0x20, 0x00, 0x00}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } // Header size, X and K raw, err = pck.Unmarshal([]byte{0x80, 0x10, 0x00, 0x00}) if raw == nil { t.Fatal("Result shouldn't be nil in case of success") } if err != nil { t.Fatal("Error should be nil in case of success") } // Header size, all flags raw, err = pck.Unmarshal([]byte{0xff, 0xff, 0x00, 0x00}) if raw != nil { t.Fatal("Result should be nil in case of error") } if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } } func TestVP8Payloader_Payload(t *testing.T) { testCases := map[string]struct { payloader VP8Payloader mtu uint16 payload [][]byte expected [][][]byte }{ "WithoutPictureID": { payloader: VP8Payloader{}, mtu: 2, payload: [][]byte{ {0x90, 0x90, 0x90}, {0x91, 0x91}, }, expected: [][][]byte{ {{0x10, 0x90}, {0x00, 0x90}, {0x00, 0x90}}, {{0x10, 0x91}, {0x00, 0x91}}, }, }, "WithPictureID_1byte": { payloader: VP8Payloader{ EnablePictureID: true, pictureID: 0x20, }, mtu: 5, payload: [][]byte{ {0x90, 0x90, 0x90}, {0x91, 0x91}, }, expected: [][][]byte{ { {0x90, 0x80, 0x20, 0x90, 0x90}, {0x80, 0x80, 0x20, 0x90}, }, { {0x90, 0x80, 0x21, 0x91, 0x91}, }, }, }, "WithPictureID_2bytes": { payloader: VP8Payloader{ EnablePictureID: true, pictureID: 0x120, }, mtu: 6, payload: [][]byte{ {0x90, 0x90, 0x90}, {0x91, 0x91}, }, expected: [][][]byte{ { {0x90, 0x80, 0x81, 0x20, 0x90, 0x90}, {0x80, 0x80, 0x81, 0x20, 0x90}, }, { {0x90, 0x80, 0x81, 0x21, 0x91, 0x91}, }, }, }, } for name, testCase := range testCases { testCase := testCase t.Run(name, func(t *testing.T) { pck := testCase.payloader for i := range testCase.payload { res := pck.Payload(testCase.mtu, testCase.payload[i]) if !reflect.DeepEqual(testCase.expected[i], res) { t.Fatalf("Generated packet[%d] differs, expected: %v, got: %v", i, testCase.expected[i], res) } } }) } t.Run("Error", func(t *testing.T) { pck := VP8Payloader{} payload := []byte{0x90, 0x90, 0x90} // Positive MTU, nil payload res := pck.Payload(1, nil) if len(res) != 0 { t.Fatal("Generated payload should be empty") } // Positive MTU, small payload // MTU of 1 results in fragment size of 0 res = pck.Payload(1, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } }) } func TestVP8IsPartitionHead(t *testing.T) { vp8 := &VP8Packet{} t.Run("SmallPacket", func(t *testing.T) { if vp8.IsPartitionHead([]byte{0x00}) { t.Fatal("Small packet should not be the head of a new partition") } }) t.Run("SFlagON", func(t *testing.T) { if !vp8.IsPartitionHead([]byte{0x10, 0x00, 0x00, 0x00}) { t.Fatal("Packet with S flag should be the head of a new partition") } }) t.Run("SFlagOFF", func(t *testing.T) { if vp8.IsPartitionHead([]byte{0x00, 0x00, 0x00, 0x00}) { t.Fatal("Packet without S flag should not be the head of a new partition") } }) } rtp-1.7.13/codecs/vp9_packet.go000066400000000000000000000231001422532252400162410ustar00rootroot00000000000000package codecs import ( "github.com/pion/randutil" ) // Use global random generator to properly seed by crypto grade random. var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals // VP9Payloader payloads VP9 packets type VP9Payloader struct { pictureID uint16 initialized bool // InitialPictureIDFn is a function that returns random initial picture ID. InitialPictureIDFn func() uint16 } const ( vp9HeaderSize = 3 // Flexible mode 15 bit picture ID maxSpatialLayers = 5 maxVP9RefPics = 3 ) // Payload fragments an VP9 packet across one or more byte arrays func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { /* * https://www.ietf.org/id/draft-ietf-payload-vp9-13.txt * * 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 | * | .. | * +-+-+-+-+-+-+-+-+ * * 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 | * | .. | * +-+-+-+-+-+-+-+-+ */ if !p.initialized { if p.InitialPictureIDFn == nil { p.InitialPictureIDFn = func() uint16 { return uint16(globalMathRandomGenerator.Intn(0x7FFF)) } } p.pictureID = p.InitialPictureIDFn() & 0x7FFF p.initialized = true } if payload == nil { return [][]byte{} } maxFragmentSize := int(mtu) - vp9HeaderSize payloadDataRemaining := len(payload) payloadDataIndex := 0 if min(maxFragmentSize, payloadDataRemaining) <= 0 { return [][]byte{} } var payloads [][]byte for payloadDataRemaining > 0 { currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) out := make([]byte, vp9HeaderSize+currentFragmentSize) out[0] = 0x90 // F=1 I=1 if payloadDataIndex == 0 { out[0] |= 0x08 // B=1 } if payloadDataRemaining == currentFragmentSize { out[0] |= 0x04 // E=1 } out[1] = byte(p.pictureID>>8) | 0x80 out[2] = byte(p.pictureID) copy(out[vp9HeaderSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) payloads = append(payloads, out) payloadDataRemaining -= currentFragmentSize payloadDataIndex += currentFragmentSize } p.pictureID++ if p.pictureID >= 0x8000 { p.pictureID = 0 } return payloads } // VP9Packet represents the VP9 header that is stored in the payload of an RTP Packet type VP9Packet struct { // Required header I bool // PictureID is present P bool // Inter-picture predicted frame L bool // Layer indices is present F bool // Flexible mode B bool // Start of a frame E bool // End of a frame V bool // Scalability structure (SS) data present 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++ { p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 } } if p.G { p.NG = packet[pos] pos++ } for i := 0; i < int(p.NG); i++ { p.PGTID = append(p.PGTID, packet[pos]>>5) p.PGU = append(p.PGU, packet[pos]&0x10 != 0) R := (packet[pos] >> 2) & 0x3 pos++ p.PGPDiff = append(p.PGPDiff, []uint8{}) for j := 0; j < int(R); j++ { p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos]) pos++ } } return pos, nil } // VP9PartitionHeadChecker is obsolete type VP9PartitionHeadChecker struct{} // 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 } rtp-1.7.13/codecs/vp9_packet_test.go000066400000000000000000000163161422532252400173130ustar00rootroot00000000000000package codecs import ( "errors" "fmt" "math/rand" "reflect" "testing" ) func TestVP9Packet_Unmarshal(t *testing.T) { cases := map[string]struct { b []byte pkt VP9Packet err error }{ "Nil": { b: nil, err: errNilPacket, }, "Empty": { b: []byte{}, err: errShortPacket, }, "NonFlexible": { b: []byte{0x00, 0xAA}, pkt: VP9Packet{ Payload: []byte{0xAA}, }, }, "NonFlexiblePictureID": { b: []byte{0x80, 0x02, 0xAA}, pkt: VP9Packet{ I: true, PictureID: 0x02, Payload: []byte{0xAA}, }, }, "NonFlexiblePictureIDExt": { b: []byte{0x80, 0x81, 0xFF, 0xAA}, pkt: VP9Packet{ I: true, PictureID: 0x01FF, Payload: []byte{0xAA}, }, }, "NonFlexiblePictureIDExt_ShortPacket0": { b: []byte{0x80, 0x81}, err: errShortPacket, }, "NonFlexiblePictureIDExt_ShortPacket1": { b: []byte{0x80}, err: errShortPacket, }, "NonFlexibleLayerIndicePictureID": { b: []byte{0xA0, 0x02, 0x23, 0x01, 0xAA}, pkt: VP9Packet{ I: true, L: true, PictureID: 0x02, TID: 0x01, SID: 0x01, D: true, TL0PICIDX: 0x01, Payload: []byte{0xAA}, }, }, "FlexibleLayerIndicePictureID": { b: []byte{0xB0, 0x02, 0x23, 0x01, 0xAA}, pkt: VP9Packet{ F: true, I: true, L: true, PictureID: 0x02, TID: 0x01, SID: 0x01, D: true, Payload: []byte{0x01, 0xAA}, }, }, "NonFlexibleLayerIndicePictureID_ShortPacket0": { b: []byte{0xA0, 0x02, 0x23}, err: errShortPacket, }, "NonFlexibleLayerIndicePictureID_ShortPacket1": { b: []byte{0xA0, 0x02}, err: errShortPacket, }, "FlexiblePictureIDRefIndex": { b: []byte{0xD0, 0x02, 0x03, 0x04, 0xAA}, pkt: VP9Packet{ I: true, P: true, F: true, PictureID: 0x02, PDiff: []uint8{0x01, 0x02}, Payload: []byte{0xAA}, }, }, "FlexiblePictureIDRefIndex_TooManyPDiff": { b: []byte{0xD0, 0x02, 0x03, 0x05, 0x07, 0x09, 0x10, 0xAA}, err: errTooManyPDiff, }, "FlexiblePictureIDRefIndexNoPayload": { b: []byte{0xD0, 0x02, 0x03, 0x04}, pkt: VP9Packet{ I: true, P: true, F: true, PictureID: 0x02, PDiff: []uint8{0x01, 0x02}, Payload: []byte{}, }, }, "FlexiblePictureIDRefIndex_ShortPacket0": { b: []byte{0xD0, 0x02, 0x03}, err: errShortPacket, }, "FlexiblePictureIDRefIndex_ShortPacket1": { b: []byte{0xD0, 0x02}, err: errShortPacket, }, "FlexiblePictureIDRefIndex_ShortPacket2": { b: []byte{0xD0}, err: errShortPacket, }, "ScalabilityStructureResolutionsNoPayload": { b: []byte{ 0x0A, (1 << 5) | (1 << 4), // NS:1 Y:1 G:0 640 >> 8, 640 & 0xff, 360 >> 8, 360 & 0xff, 1280 >> 8, 1280 & 0xff, 720 >> 8, 720 & 0xff, }, pkt: VP9Packet{ B: true, V: true, NS: 1, Y: true, G: false, NG: 0, Width: []uint16{640, 1280}, Height: []uint16{360, 720}, Payload: []byte{}, }, }, "ScalabilityStructureNoPayload": { b: []byte{ 0x0A, (1 << 5) | (0 << 4) | (1 << 3), // NS:1 Y:0 G:1 2, (0 << 5) | (1 << 4) | (0 << 2), // T:0 U:1 R:0 - (2 << 5) | (0 << 4) | (1 << 2), // T:2 U:0 R:1 - 33, }, pkt: VP9Packet{ B: true, V: true, NS: 1, Y: false, G: true, NG: 2, PGTID: []uint8{0, 2}, PGU: []bool{true, false}, PGPDiff: [][]uint8{{}, {33}}, Payload: []byte{}, }, }, } for name, c := range cases { c := c t.Run(name, func(t *testing.T) { p := VP9Packet{} raw, err := p.Unmarshal(c.b) if c.err == nil { if raw == nil { t.Error("Result shouldn't be nil in case of success") } if err != nil { t.Error("Error should be nil in case of success") } if !reflect.DeepEqual(c.pkt, p) { t.Errorf("Unmarshalled packet expected to be:\n %v\ngot:\n %v", c.pkt, p) } } else { if raw != nil { t.Error("Result should be nil in case of error") } if !errors.Is(err, c.err) { t.Errorf("Error should be '%v', got '%v'", c.err, err) } } }) } } func TestVP9Payloader_Payload(t *testing.T) { r0 := int(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec var rands [][2]byte for i := 0; i < 10; i++ { rands = append(rands, [2]byte{byte(r0>>8) | 0x80, byte(r0 & 0xFF)}) r0++ } cases := map[string]struct { b [][]byte mtu uint16 res [][]byte }{ "NilPayload": { b: [][]byte{nil}, mtu: 100, res: [][]byte{}, }, "SmallMTU": { b: [][]byte{{0x00, 0x00}}, mtu: 1, res: [][]byte{}, }, "OnePacket": { b: [][]byte{{0x01, 0x02}}, mtu: 10, res: [][]byte{ {0x9C, rands[0][0], rands[0][1], 0x01, 0x02}, }, }, "TwoPackets": { b: [][]byte{{0x01, 0x02}}, mtu: 4, res: [][]byte{ {0x98, rands[0][0], rands[0][1], 0x01}, {0x94, rands[0][0], rands[0][1], 0x02}, }, }, "ThreePackets": { b: [][]byte{{0x01, 0x02, 0x03}}, mtu: 4, res: [][]byte{ {0x98, rands[0][0], rands[0][1], 0x01}, {0x90, rands[0][0], rands[0][1], 0x02}, {0x94, rands[0][0], rands[0][1], 0x03}, }, }, "TwoFramesFourPackets": { b: [][]byte{{0x01, 0x02, 0x03}, {0x04}}, mtu: 5, res: [][]byte{ {0x98, rands[0][0], rands[0][1], 0x01, 0x02}, {0x94, rands[0][0], rands[0][1], 0x03}, {0x9C, rands[1][0], rands[1][1], 0x04}, }, }, } for name, c := range cases { pck := VP9Payloader{ InitialPictureIDFn: func() uint16 { return uint16(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec }, } c := c t.Run(fmt.Sprintf("%s_MTU%d", name, c.mtu), func(t *testing.T) { res := [][]byte{} for _, b := range c.b { res = append(res, pck.Payload(c.mtu, b)...) } if !reflect.DeepEqual(c.res, res) { t.Errorf("Payloaded packet expected to be:\n %v\ngot:\n %v", c.res, res) } }) } t.Run("PictureIDOverflow", func(t *testing.T) { pck := VP9Payloader{ InitialPictureIDFn: func() uint16 { return uint16(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec }, } pPrev := VP9Packet{} for i := 0; i < 0x8000; i++ { res := pck.Payload(4, []byte{0x01}) p := VP9Packet{} _, err := p.Unmarshal(res[0]) if err != nil { t.Fatalf("Unexpected error: %v", err) } if i > 0 { if pPrev.PictureID == 0x7FFF { if p.PictureID != 0 { t.Errorf("Picture ID next to 0x7FFF must be 0, got %d", p.PictureID) } } else if pPrev.PictureID+1 != p.PictureID { t.Errorf("Picture ID next must be incremented by 1: %d -> %d", pPrev.PictureID, p.PictureID) } } pPrev = p } }) } func TestVP9IsPartitionHead(t *testing.T) { vp9 := &VP9Packet{} t.Run("SmallPacket", func(t *testing.T) { if vp9.IsPartitionHead([]byte{}) { t.Fatal("Small packet should not be the head of a new partition") } }) t.Run("NormalPacket", func(t *testing.T) { if !vp9.IsPartitionHead([]byte{0x18, 0x00, 0x00}) { t.Error("VP9 RTP packet with B flag should be head of a new partition") } if vp9.IsPartitionHead([]byte{0x10, 0x00, 0x00}) { t.Error("VP9 RTP packet without B flag should not be head of a new partition") } }) } rtp-1.7.13/depacketizer.go000066400000000000000000000011161422532252400154110ustar00rootroot00000000000000package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload type Depacketizer interface { 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 } rtp-1.7.13/error.go000066400000000000000000000020531422532252400140710ustar00rootroot00000000000000package 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") ) rtp-1.7.13/go.mod000066400000000000000000000001151422532252400135140ustar00rootroot00000000000000module github.com/pion/rtp go 1.13 require github.com/pion/randutil v0.1.0 rtp-1.7.13/go.sum000066400000000000000000000002471422532252400135470ustar00rootroot00000000000000github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= rtp-1.7.13/header_extension.go000066400000000000000000000176671422532252400163050ustar00rootroot00000000000000package 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 len := int(e.payload[n]&^0xF0 + 1) n++ if extid == id { e.payload = append(e.payload[:n+1], append(buf, e.payload[n+1+len:]...)...) return nil } n += len } 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 len := int(e.payload[n]&^0xF0 + 1) n++ if extid == headerExtensionIDReserved { break } ids = append(ids, extid) n += len } 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 len := int(e.payload[n]&^0xF0 + 1) n++ if extid == id { return e.payload[n : n+len] } n += len } 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 len := int(e.payload[n]&^0xF0 + 1) if extid == id { e.payload = append(e.payload[:n], e.payload[n+1+len:]...) return nil } n += len + 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++ len := int(e.payload[n]) n++ if extid == id { e.payload = append(e.payload[:n+2], append(buf, e.payload[n+2+len:]...)...) return nil } n += len } 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++ len := int(e.payload[n]) n++ ids = append(ids, extid) n += len } 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++ len := int(e.payload[n]) n++ if extid == id { return e.payload[n : n+len] } n += len } 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] len := int(e.payload[n+1]) if extid == id { e.payload = append(e.payload[:n], e.payload[n+2+len:]...) return nil } n += len + 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) } rtp-1.7.13/header_extension_test.go000066400000000000000000000231051422532252400173240ustar00rootroot00000000000000package rtp import ( "bytes" "encoding/hex" "testing" ) func TestHeaderExtension_RFC8285OneByteExtension(t *testing.T) { p := &OneByteHeaderExtension{} rawPkt := []byte{ 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } if _, err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestHeaderExtension_RFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { p := &OneByteHeaderExtension{} // 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=0 | data | ID | L=0 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0xBE, 0xDE, 0x00, 0x01, 0x10, 0xAA, 0x20, 0xBB, } if _, err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.Get(1) ext1Expect := []byte{0xAA} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %+v, Expected: %+v", ext1, ext1Expect) } ext2 := p.Get(2) ext2Expect := []byte{0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %+v, Expected: %+v", ext2, ext2Expect) } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestHeaderExtension_RFC8285OneByteMultipleExtensionsWithPadding(t *testing.T) { p := &OneByteHeaderExtension{} // 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=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=1 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | 0 (pad) | 0 (pad) | ID | L=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x00, 0x00, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, } if _, err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.Get(1) ext1Expect := []byte{0xAA} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect) } ext2 := p.Get(2) ext2Expect := []byte{0xBB, 0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect) } ext3 := p.Get(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} if !bytes.Equal(ext3, ext3Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect) } dstBuf := map[string][]byte{ "CleanBuffer": make([]byte, 1000), "DirtyBuffer": make([]byte, 1000), } for i := range dstBuf["DirtyBuffer"] { dstBuf["DirtyBuffer"][i] = 0xFF } for name, buf := range dstBuf { buf := buf t.Run(name, func(t *testing.T) { n, err := p.MarshalTo(buf) if err != nil { t.Fatal(err) } if !bytes.Equal(buf[:n], rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(buf[:n]), hex.Dump(rawPkt)) } }) } } func TestHeaderExtension_RFC8285TwoByteExtension(t *testing.T) { p := &TwoByteHeaderExtension{} rawPkt := []byte{ 0x10, 0x00, 0x00, 0x07, 0x05, 0x18, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, } if _, err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestHeaderExtension_RFC8285TwoByteMultipleExtensionsWithPadding(t *testing.T) { p := &TwoByteHeaderExtension{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | 0 (pad) | ID=3 | L=4 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x00, 0x03, 0x04, 0xCC, 0xCC, 0xCC, 0xCC, } if _, err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.Get(1) ext1Expect := []byte{} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect) } ext2 := p.Get(2) ext2Expect := []byte{0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect) } ext3 := p.Get(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} if !bytes.Equal(ext3, ext3Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect) } } func TestHeaderExtension_RFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { p := &TwoByteHeaderExtension{} // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | ID=3 | L=17 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x10, 0x00, 0x00, 0x06, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x03, 0x11, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, } if _, err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.Get(1) ext1Expect := []byte{} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect) } ext2 := p.Get(2) ext2Expect := []byte{0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect) } ext3 := p.Get(3) ext3Expect := []byte{ 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, } if !bytes.Equal(ext3, ext3Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect) } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled: %+v,\nrawPkt: %+v", dstData, rawPkt) } } func TestHeaderExtension_RFC8285OneByteDelExtension(t *testing.T) { p := &OneByteHeaderExtension{} if _, err := p.Unmarshal([]byte{0xBE, 0xDE, 0x00, 0x00}); err != nil { t.Fatal("Unmarshal err for valid extension") } if err := p.Set(1, []byte{0xBB}); err != nil { t.Fatal("Set err for valid extension") } ext := p.Get(1) if ext == nil { t.Error("Extension should exist") } err := p.Del(1) if err != nil { t.Error("Should successfully delete extension") } ext = p.Get(1) if ext != nil { t.Error("Extension should not exist") } err = p.Del(1) if err == nil { t.Error("Should return error when deleting extension that doesnt exist") } } func TestHeaderExtension_RFC8285TwoByteDelExtension(t *testing.T) { p := &TwoByteHeaderExtension{} if _, err := p.Unmarshal([]byte{0x10, 0x00, 0x00, 0x00}); err != nil { t.Fatal("Unmarshal err for valid extension") } if err := p.Set(1, []byte{0xBB}); err != nil { t.Fatal("Set err for valid extension") } ext := p.Get(1) if ext == nil { t.Error("Extension should exist") } err := p.Del(1) if err != nil { t.Error("Should successfully delete extension") } ext = p.Get(1) if ext != nil { t.Error("Extension should not exist") } err = p.Del(1) if err == nil { t.Error("Should return error when deleting extension that doesnt exist") } } rtp-1.7.13/packet.go000066400000000000000000000334221422532252400142130ustar00rootroot00000000000000package 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 } // Packet represents an RTP Packet type Packet struct { Header Payload []byte PaddingSize 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 if expected := n + extensionLength; len(buf) < expected { return n, fmt.Errorf("size %d < %d: %w", len(buf), expected, errHeaderSizeInsufficientForExtension, ) } switch h.ExtensionProfile { // RFC 8285 RTP One Byte Header Extension case extensionProfileOneByte: end := n + extensionLength for n < end { if buf[n] == 0x00 { // padding n++ continue } extid := buf[n] >> 4 len := int(buf[n]&^0xF0 + 1) n++ if extid == extensionIDReserved { break } extension := Extension{id: extid, payload: buf[n : n+len]} h.Extensions = append(h.Extensions, extension) n += len } // RFC 8285 RTP Two Byte Header Extension case extensionProfileTwoByte: end := n + extensionLength for n < end { if buf[n] == 0x00 { // padding n++ continue } extid := buf[n] n++ len := int(buf[n]) n++ extension := Extension{id: extid, payload: buf[n : n+len]} h.Extensions = append(h.Extensions, extension) n += len } default: // RFC3550 Extension if len(buf) < n+extensionLength { return n, fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, len(buf), n+extensionLength) } extension := Extension{id: 0, payload: buf[n : n+extensionLength]} 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 { 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 len := len(payload); { case len <= 16: h.ExtensionProfile = extensionProfileOneByte case len > 16 && len < 256: h.ExtensionProfile = extensionProfileTwoByte } h.Extensions = append(h.Extensions, Extension{id: id, payload: payload}) return nil } // GetExtensionIDs returns an extension id array func (h *Header) GetExtensionIDs() []uint8 { if !h.Extension { return nil } if len(h.Extensions) == 0 { return nil } ids := make([]uint8, 0, len(h.Extensions)) for _, extension := range h.Extensions { ids = append(ids, extension.id) } return ids } // GetExtension returns an RTP header extension func (h *Header) GetExtension(id uint8) []byte { if !h.Extension { return nil } for _, extension := range h.Extensions { if extension.id == id { return extension.payload } } return nil } // DelExtension Removes an RTP Header extension func (h *Header) DelExtension(id uint8) error { if !h.Extension { return errHeaderExtensionsNotEnabled } for i, extension := range h.Extensions { if extension.id == id { h.Extensions = append(h.Extensions[:i], h.Extensions[i+1:]...) return nil } } return errHeaderExtensionNotFound } // Marshal serializes the packet into bytes. func (p Packet) Marshal() (buf []byte, err error) { buf = make([]byte, p.MarshalSize()) n, err := p.MarshalTo(buf) if err != nil { return nil, err } return buf[:n], nil } // MarshalTo serializes the packet and writes to the buffer. func (p Packet) MarshalTo(buf []byte) (n int, err error) { p.Header.Padding = p.PaddingSize != 0 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 } rtp-1.7.13/packet_test.go000066400000000000000000001133441422532252400152540ustar00rootroot00000000000000package rtp import ( "bytes" "encoding/hex" "errors" "fmt" "reflect" "testing" ) func TestBasic(t *testing.T) { p := &Packet{} if err := p.Unmarshal([]byte{}); err == nil { t.Fatal("Unmarshal did not error on zero length packet") } rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } parsedPacket := &Packet{ Header: Header{ Padding: false, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[20:], PaddingSize: 0, } // Unmarshal to the used Packet should work as well. for i := 0; i < 2; i++ { t.Run(fmt.Sprintf("Run%d", i+1), func(t *testing.T) { if err := p.Unmarshal(rawPkt); err != nil { t.Error(err) } else if !reflect.DeepEqual(p, parsedPacket) { t.Errorf("TestBasic unmarshal: got %#v, want %#v", p, parsedPacket) } if parsedPacket.Header.MarshalSize() != 20 { t.Errorf("wrong computed header marshal size") } else if parsedPacket.MarshalSize() != len(rawPkt) { t.Errorf("wrong computed marshal size") } raw, err := p.Marshal() if err != nil { t.Error(err) } else if !reflect.DeepEqual(raw, rawPkt) { t.Errorf("TestBasic marshal: got %#v, want %#v", raw, rawPkt) } }) } // packet with padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x04, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[20:21], PaddingSize: 4, } if err := p.Unmarshal(rawPkt); err != nil { t.Error(err) } else if !reflect.DeepEqual(p, parsedPacket) { t.Errorf("TestBasic padding unmarshal: got %#v, want %#v", p, parsedPacket) } // packet with only padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x05, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: []byte{}, PaddingSize: 5, } if err := p.Unmarshal(rawPkt); err != nil { t.Error(err) } else if !reflect.DeepEqual(p, parsedPacket) { t.Errorf("TestBasic padding only unmarshal: got %#v, want %#v", p, parsedPacket) } if len(p.Payload) != 0 { t.Errorf("Unmarshal of padding only packet has payload of non-zero length: %d", len(p.Payload)) } // packet with excessive padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x06, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: []byte{}, PaddingSize: 0, } err := p.Unmarshal(rawPkt) if err == nil { t.Fatal("Unmarshal did not error on packet with excessive padding") } if !errors.Is(err, errTooSmall) { t.Errorf("Expected error: %v, got: %v", errTooSmall, err) } // marshal packet with padding rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x00, 0x00, 0x00, 0x04, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[20:21], PaddingSize: 4, } buf, err := parsedPacket.Marshal() if err != nil { t.Error(err) } if !reflect.DeepEqual(buf, rawPkt) { t.Errorf("TestBasic padding marshal: got %#v, want %#v", buf, rawPkt) } // marshal packet with padding only rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x05, } parsedPacket = &Packet{ Header: Header{ Padding: true, Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: []byte{}, PaddingSize: 5, } buf, err = parsedPacket.Marshal() if err != nil { t.Error(err) } if !reflect.DeepEqual(buf, rawPkt) { t.Errorf("TestBasic padding marshal: got %#v, want %#v", buf, rawPkt) } // marshal packet with padding only without setting Padding explicitly in Header rawPkt = []byte{ 0xb0, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x05, } parsedPacket = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: []byte{}, PaddingSize: 5, } buf, err = parsedPacket.Marshal() if err != nil { t.Error(err) } if !reflect.DeepEqual(buf, rawPkt) { t.Errorf("TestBasic padding marshal: got %#v, want %#v", buf, rawPkt) } } func TestExtension(t *testing.T) { p := &Packet{} missingExtensionPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, } if err := p.Unmarshal(missingExtensionPkt); err == nil { t.Fatal("Unmarshal did not error on packet with missing extension data") } invalidExtensionLengthPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x99, 0x99, 0x99, 0x99, } if err := p.Unmarshal(invalidExtensionLengthPkt); err == nil { t.Fatal("Unmarshal did not error on packet with invalid extension length") } p = &Packet{ Header: Header{ Extension: true, ExtensionProfile: 3, Extensions: []Extension{ {0, []byte{ 0, }}, }, }, Payload: []byte{}, } if _, err := p.Marshal(); err == nil { t.Fatal("Marshal did not error on packet with invalid extension length") } } func TestRFC8285OneByteExtension(t *testing.T) { p := &Packet{} rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } if err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } p = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {5, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[20:], } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestRFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { p := &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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=0 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x10, 0xAA, 0x20, 0xBB, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } if err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.GetExtension(1) ext1Expect := []byte{0xAA} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %+v, Expected: %+v", ext1, ext1Expect) } ext2 := p.GetExtension(2) ext2Expect := []byte{0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %+v, Expected: %+v", ext2, ext2Expect) } // Test Marshal p = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, {2, []byte{ 0xBB, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[20:], } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestRFC8285OneByteMultipleExtensionsWithPadding(t *testing.T) { p := &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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0xBE | 0xDE | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID | L=0 | data | ID | L=1 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | 0 (pad) | 0 (pad) | ID | L=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x00, 0x00, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } if err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.GetExtension(1) ext1Expect := []byte{0xAA} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect) } ext2 := p.GetExtension(2) ext2Expect := []byte{0xBB, 0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect) } ext3 := p.GetExtension(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} if !bytes.Equal(ext3, ext3Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect) } rawPktReMarshal := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, // padding is moved to the end by re-marshaling // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } dstBuf := map[string][]byte{ "CleanBuffer": make([]byte, 1000), "DirtyBuffer": make([]byte, 1000), } for i := range dstBuf["DirtyBuffer"] { dstBuf["DirtyBuffer"][i] = 0xFF } for name, buf := range dstBuf { buf := buf t.Run(name, func(t *testing.T) { n, err := p.MarshalTo(buf) if err != nil { t.Fatal(err) } if !bytes.Equal(buf[:n], rawPktReMarshal) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(buf[:n]), hex.Dump(rawPktReMarshal)) } }) } } func TestRFC8285OneByteMultipleExtensions(t *testing.T) { // 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=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | data | ID=2 | L=1 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | ID=3 | L=3 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x03, 0x10, 0xAA, 0x21, 0xBB, 0xBB, 0x33, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, 0x00, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, {2, []byte{ 0xBB, 0xBB, }}, {3, []byte{ 0xCC, 0xCC, 0xCC, 0xCC, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[28:], } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestRFC8285TwoByteExtension(t *testing.T) { p := &Packet{} rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x07, 0x05, 0x18, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } if err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } p = &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1000, Extensions: []Extension{ {5, []byte{ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[44:], } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled:\n%s\nrawPkt:\n%s", hex.Dump(dstData), hex.Dump(rawPkt)) } } func TestRFC8285TwoByteMultipleExtensionsWithPadding(t *testing.T) { p := &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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | 0 (pad) | ID=3 | L=4 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x03, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x00, 0x03, 0x04, 0xCC, 0xCC, 0xCC, 0xCC, 0x98, 0x36, 0xbe, 0x88, 0x9e, } if err := p.Unmarshal(rawPkt); err != nil { t.Fatal("Unmarshal err for valid extension") } ext1 := p.GetExtension(1) ext1Expect := []byte{} if !bytes.Equal(ext1, ext1Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext1, ext1Expect) } ext2 := p.GetExtension(2) ext2Expect := []byte{0xBB} if !bytes.Equal(ext2, ext2Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext2, ext2Expect) } ext3 := p.GetExtension(3) ext3Expect := []byte{0xCC, 0xCC, 0xCC, 0xCC} if !bytes.Equal(ext3, ext3Expect) { t.Errorf("Extension has incorrect data. Got: %v+, Expected: %v+", ext3, ext3Expect) } } func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { // 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 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 0x10 | 0x00 | length=3 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | ID=1 | L=0 | ID=2 | L=1 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | data | ID=3 | L=17 | data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // ...data... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x10, 0x00, 0x00, 0x06, 0x01, 0x00, 0x02, 0x01, 0xBB, 0x03, 0x11, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1000, Extensions: []Extension{ {1, []byte{}}, {2, []byte{ 0xBB, }}, {3, []byte{ 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: rawPkt[40:], } dstData, _ := p.Marshal() if !bytes.Equal(dstData, rawPkt) { t.Errorf("Marshal failed raw \nMarshaled: %+v,\nrawPkt: %+v", dstData, rawPkt) } } func TestRFC8285GetExtensionReturnsNilWhenExtensionsDisabled(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } err := p.GetExtension(1) if err != nil { t.Error("Should return nil on GetExtension when h.Extension: false") } } func TestRFC8285DelExtension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } ext := p.GetExtension(1) if ext == nil { t.Error("Extension should exist") } err := p.DelExtension(1) if err != nil { t.Error("Should successfully delete extension") } ext = p.GetExtension(1) if ext != nil { t.Error("Extension should not exist") } err = p.DelExtension(1) if err == nil { t.Error("Should return error when deleting extension that doesnt exist") } } func TestRFC8285GetExtensionIDs(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, {2, []byte{ 0xBB, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } ids := p.GetExtensionIDs() if ids == nil { t.Error("Extension should exist") } if len(ids) != len(p.Extensions) { t.Errorf("The number of IDs should be equal to the number of extensions,want=%d,have=%d", len(p.Extensions), len(ids)) } for _, id := range ids { ext := p.GetExtension(id) if ext == nil { t.Error("Extension should exist") } } } func TestRFC8285GetExtensionIDsReturnsErrorWhenExtensionsDisabled(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } ids := p.GetExtensionIDs() if ids != nil { t.Error("Should return nil on GetExtensionIDs when h.Extensions is nil") } } func TestRFC8285DelExtensionReturnsErrorWhenExtensionsDisabled(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } err := p.DelExtension(1) if err == nil { t.Error("Should return error on DelExtension when h.Extension: false") } } func TestRFC8285OneByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } extension := []byte{0xAA, 0xAA} err := p.SetExtension(1, extension) if err != nil { t.Error("Error setting extension") } if p.Extension != true { t.Error("Extension should be set to true") } if p.ExtensionProfile != 0xBEDE { t.Error("Extension profile should be set to 0xBEDE") } if len(p.Extensions) != 1 { t.Error("Extensions should be set to 1") } if !bytes.Equal(p.GetExtension(1), extension) { t.Error("Extension value is not set") } } func TestRFC8285OneByteSetExtensionShouldSetCorrectExtensionProfileFor16ByteExtension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } extension := []byte{ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, } err := p.SetExtension(1, extension) if err != nil { t.Error("Error setting extension") } if p.ExtensionProfile != 0xBEDE { t.Error("Extension profile should be set to 0xBEDE") } } func TestRFC8285OneByteSetExtensionShouldUpdateExistingExension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } if !bytes.Equal(p.GetExtension(1), []byte{0xAA}) { t.Error("Extension value not initialize properly") } extension := []byte{0xBB} err := p.SetExtension(1, extension) if err != nil { t.Error("Error setting extension") } if !bytes.Equal(p.GetExtension(1), extension) { t.Error("Extension value was not set") } } func TestRFC8285OneByteSetExtensionShouldErrorWhenInvalidIDProvided(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } if p.SetExtension(0, []byte{0xBB}) == nil { t.Error("SetExtension did not error on invalid id") } if p.SetExtension(15, []byte{0xBB}) == nil { t.Error("SetExtension did not error on invalid id") } } func TestRFC8285OneByteExtensionTermianteProcessingWhenReservedIDEncountered(t *testing.T) { p := &Packet{} reservedIDPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0xF0, 0xAA, 0x98, 0x36, 0xbe, 0x88, 0x9e, } if err := p.Unmarshal(reservedIDPkt); err != nil { t.Error("Unmarshal error on packet with reserved extension id") } if len(p.Extensions) != 0 { t.Error("Extensions should be empty for invalid id") } payload := reservedIDPkt[17:] if !bytes.Equal(p.Payload, payload) { t.Errorf("p.Payload must be same as payload.\n p.Payload: %+v,\n payload: %+v", p.Payload, payload, ) } } func TestRFC8285OneByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } if p.SetExtension(1, []byte{ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, }) == nil { t.Error("SetExtension did not error on too large payload") } } func TestRFC8285TwoByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: false, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } extension := []byte{ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, } err := p.SetExtension(1, extension) if err != nil { t.Error("Error setting extension") } if p.Extension != true { t.Error("Extension should be set to true") } if p.ExtensionProfile != 0x1000 { t.Error("Extension profile should be set to 0xBEDE") } if len(p.Extensions) != 1 { t.Error("Extensions should be set to 1") } if !bytes.Equal(p.GetExtension(1), extension) { t.Error("Extension value is not set") } } func TestRFC8285TwoByteSetExtensionShouldUpdateExistingExension(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1000, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } if !bytes.Equal(p.GetExtension(1), []byte{0xAA}) { t.Error("Extension value not initialize properly") } extension := []byte{ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, } err := p.SetExtension(1, extension) if err != nil { t.Error("Error setting extension") } if !bytes.Equal(p.GetExtension(1), extension) { t.Error("Extension value was not set") } } func TestRFC8285TwoByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0xBEDE, Extensions: []Extension{ {1, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } if p.SetExtension(1, []byte{ 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, }) == nil { t.Error("SetExtension did not error on too large payload") } } func TestRFC3550SetExtensionShouldErrorWhenNonZero(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1111, Extensions: []Extension{ {0, []byte{ 0xAA, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } expect := []byte{0xBB} if p.SetExtension(0, expect) != nil { t.Error("SetExtension should not error on valid id") } actual := p.GetExtension(0) if !bytes.Equal(actual, expect) { t.Error("p.GetExtension returned incorrect value.") } } func TestRFC3550SetExtensionShouldRaiseErrorWhenSettingNonzeroID(t *testing.T) { payload := []byte{ // Payload 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Header: Header{ Marker: true, Extension: true, ExtensionProfile: 0x1111, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, Payload: payload, } if p.SetExtension(1, []byte{0xBB}) == nil { t.Error("SetExtension did not error on invalid id") } } func TestUnmarshal_ErrorHandling(t *testing.T) { cases := map[string]struct { input []byte err error }{ "ShortHeader": { input: []byte{ 0x80, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, // SSRC (one byte missing) }, err: errHeaderSizeInsufficient, }, "MissingCSRC": { input: []byte{ 0x81, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC }, err: errHeaderSizeInsufficient, }, "MissingExtension": { input: []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC }, err: errHeaderSizeInsufficientForExtension, }, "MissingExtensionData": { input: []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC 0xBE, 0xDE, 0x00, 0x03, // specified to have 3 extensions, but actually not }, err: errHeaderSizeInsufficientForExtension, }, "MissingExtensionDataPayload": { input: []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, // timestamp 0x1c, 0x64, 0x27, 0x82, // SSRC 0xBE, 0xDE, 0x00, 0x01, // have 1 extension 0x12, 0x00, // length of the payload is expected to be 3, but actually have only 1 }, err: errHeaderSizeInsufficientForExtension, }, } for name, testCase := range cases { testCase := testCase t.Run(name, func(t *testing.T) { h := &Header{} _, err := h.Unmarshal(testCase.input) if !errors.Is(err, testCase.err) { t.Errorf("Expected error: %v, got: %v", testCase.err, err) } }) } } func TestRoundtrip(t *testing.T) { rawPkt := []byte{ 0x00, 0x10, 0x23, 0x45, 0x12, 0x34, 0x45, 0x67, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, } payload := rawPkt[12:] p := &Packet{} if err := p.Unmarshal(rawPkt); err != nil { t.Fatal(err) } if !bytes.Equal(payload, p.Payload) { t.Errorf("p.Payload must be same as payload.\n payload: %+v,\np.Payload: %+v", payload, p.Payload, ) } buf, err := p.Marshal() if err != nil { t.Fatal(err) } if !bytes.Equal(rawPkt, buf) { t.Errorf("buf must be same as rawPkt.\n buf: %+v,\nrawPkt: %+v", buf, rawPkt) } if !bytes.Equal(payload, p.Payload) { t.Errorf("p.Payload must be same as payload.\n payload: %+v,\np.Payload: %+v", payload, p.Payload, ) } } func TestCloneHeader(t *testing.T) { h := Header{ Marker: true, Extension: true, ExtensionProfile: 1, Extensions: []Extension{ {0, []byte{ 0xFF, 0xFF, 0xFF, 0xFF, }}, }, Version: 2, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, } clone := h.Clone() if !reflect.DeepEqual(h, clone) { t.Errorf("Cloned clone does not match the original") } h.CSRC = append(h.CSRC, 1) if len(clone.CSRC) == len(h.CSRC) { t.Errorf("Expected CSRC to be unchanged") } h.Extensions[0].payload[0] = 0x1F if clone.Extensions[0].payload[0] == 0x1F { t.Errorf("Expected Extensions to be unchanged") } } func TestClonePacket(t *testing.T) { rawPkt := []byte{ 0x90, 0xe0, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0xBE, 0xDE, 0x00, 0x01, 0x50, 0xAA, 0x00, 0x00, 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{ Payload: rawPkt[20:], } clone := p.Clone() if !reflect.DeepEqual(p, clone) { t.Errorf("Cloned Packet does not match the original") } p.Payload[0] = 0x1F if clone.Payload[0] == 0x1F { t.Errorf("Expected Payload to be unchanged") } } func BenchmarkMarshal(b *testing.B) { rawPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{} err := p.Unmarshal(rawPkt) if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err = p.Marshal() if err != nil { b.Fatal(err) } } } func BenchmarkMarshalTo(b *testing.B) { rawPkt := []byte{ 0x90, 0x60, 0x69, 0x8f, 0xd9, 0xc2, 0x93, 0xda, 0x1c, 0x64, 0x27, 0x82, 0x00, 0x01, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x98, 0x36, 0xbe, 0x88, 0x9e, } p := &Packet{} err := p.Unmarshal(rawPkt) if err != nil { b.Fatal(err) } buf := [100]byte{} b.ResetTimer() for i := 0; i < b.N; i++ { _, err = p.MarshalTo(buf[:]) if err != nil { b.Fatal(err) } } } func BenchmarkUnmarshal(b *testing.B) { pkt := Packet{ Header: Header{ Extension: true, CSRC: []uint32{1, 2}, ExtensionProfile: extensionProfileTwoByte, Extensions: []Extension{ {id: 1, payload: []byte{3, 4}}, {id: 2, payload: []byte{5, 6}}, }, }, Payload: []byte{ 0x07, 0x08, 0x09, 0x0a, }, } rawPkt, errMarshal := pkt.Marshal() if errMarshal != nil { b.Fatal(errMarshal) } b.Run("SharedStruct", func(b *testing.B) { p := &Packet{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { if err := p.Unmarshal(rawPkt); err != nil { b.Fatal(err) } } }) b.Run("NewStruct", func(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { p := &Packet{} if err := p.Unmarshal(rawPkt); err != nil { b.Fatal(err) } } }) } rtp-1.7.13/packetizer.go000066400000000000000000000053301422532252400151020ustar00rootroot00000000000000package 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 EnableAbsSendTime(value int) SkipSamples(skippedSamples uint32) } type packetizer struct { MTU uint16 PayloadType uint8 SSRC uint32 Payloader Payloader Sequencer Sequencer Timestamp uint32 ClockRate uint32 extensionNumbers struct { // put extension numbers in here. If they're 0, the extension is disabled (0 is not a legal extension number) AbsSendTime int // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time } timegen func() time.Time } // NewPacketizer returns a new instance of a Packetizer for a specific payloader func NewPacketizer(mtu 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, }, 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 } // 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 } rtp-1.7.13/packetizer_test.go000066400000000000000000000036501422532252400161440ustar00rootroot00000000000000package rtp import ( "fmt" "reflect" "testing" "time" "github.com/pion/rtp/codecs" ) func TestPacketizer(t *testing.T) { multiplepayload := make([]byte, 128) // use the G722 payloader here, because it's very simple and all 0s is valid G722 data. packetizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewRandomSequencer(), 90000) packets := packetizer.Packetize(multiplepayload, 2000) if len(packets) != 2 { packetlengths := "" for i := 0; i < len(packets); i++ { packetlengths += fmt.Sprintf("Packet %d length %d\n", i, len(packets[i].Payload)) } t.Fatalf("Generated %d packets instead of 2\n%s", len(packets), packetlengths) } } func TestPacketizer_AbsSendTime(t *testing.T) { // use the G722 payloader here, because it's very simple and all 0s is valid G722 data. pktizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewFixedSequencer(1234), 90000) pktizer.(*packetizer).Timestamp = 45678 pktizer.(*packetizer).timegen = func() time.Time { return time.Date(1985, time.June, 23, 4, 0, 0, 0, time.FixedZone("UTC-5", -5*60*60)) // (0xa0c65b1000000000>>14) & 0xFFFFFF = 0x400000 } pktizer.EnableAbsSendTime(1) payload := []byte{0x11, 0x12, 0x13, 0x14} packets := pktizer.Packetize(payload, 2000) expected := &Packet{ Header: Header{ Version: 2, Padding: false, Extension: true, Marker: true, PayloadType: 98, SequenceNumber: 1234, Timestamp: 45678, SSRC: 0x1234ABCD, CSRC: nil, ExtensionProfile: 0xBEDE, Extensions: []Extension{ { id: 1, payload: []byte{0x40, 0, 0}, }, }, }, Payload: []byte{0x11, 0x12, 0x13, 0x14}, } if len(packets) != 1 { t.Fatalf("Generated %d packets instead of 1", len(packets)) } if !reflect.DeepEqual(expected, packets[0]) { t.Errorf("Packetize failed\nexpected: %v\n got: %v", expected, packets[0]) } } rtp-1.7.13/partitionheadchecker.go000066400000000000000000000002561422532252400171230ustar00rootroot00000000000000package rtp // PartitionHeadChecker is the interface that checks whether the packet is keyframe or not type PartitionHeadChecker interface { IsPartitionHead([]byte) bool } rtp-1.7.13/pkg/000077500000000000000000000000001422532252400131725ustar00rootroot00000000000000rtp-1.7.13/pkg/frame/000077500000000000000000000000001422532252400142645ustar00rootroot00000000000000rtp-1.7.13/pkg/frame/av1.go000066400000000000000000000030541422532252400153040ustar00rootroot00000000000000// Package frame provides code to construct complete media frames from packetized media package frame import "github.com/pion/rtp/codecs" // AV1 represents a collection of OBUs given a stream of AV1 Packets. // Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one. // AV1 provides the tools to construct a collection of OBUs from a collection of OBU Elements. This structure // contains an internal cache and should be used for the entire RTP Stream. type AV1 struct { // Buffer for fragmented OBU. If ReadFrames is called on a RTP Packet // that doesn't contain a fully formed OBU obuBuffer []byte } func (f *AV1) pushOBUElement(isFirstOBUFragment *bool, obuElement []byte, obuList [][]byte) [][]byte { if *isFirstOBUFragment { *isFirstOBUFragment = false // Discard pushed because we don't have a fragment to combine it with if f.obuBuffer == nil { return obuList } obuElement = append(f.obuBuffer, obuElement...) f.obuBuffer = nil } return append(obuList, obuElement) } // ReadFrames processes the codecs.AV1Packet and returns fully constructed frames func (f *AV1) ReadFrames(pkt *codecs.AV1Packet) ([][]byte, error) { OBUs := [][]byte{} isFirstOBUFragment := pkt.Z for i := range pkt.OBUElements { OBUs = f.pushOBUElement(&isFirstOBUFragment, pkt.OBUElements[i], OBUs) } if pkt.Y && len(OBUs) > 0 { // Take copy of OBUElement that is being cached f.obuBuffer = append(f.obuBuffer, append([]byte{}, OBUs[len(OBUs)-1]...)...) OBUs = OBUs[:len(OBUs)-1] } return OBUs, nil } rtp-1.7.13/pkg/frame/av1_test.go000066400000000000000000000050711422532252400163440ustar00rootroot00000000000000package frame import ( "reflect" "testing" "github.com/pion/rtp/codecs" ) // First is Fragment (and no buffer) // Self contained OBU // OBU spread across 3 packets func TestAV1_ReadFrames(t *testing.T) { // First is Fragment of OBU, but no OBU Elements is cached f := &AV1{} frames, err := f.ReadFrames(&codecs.AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}}) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(frames, [][]byte{}) { t.Fatalf("No frames should be generated, %v", frames) } f = &AV1{} frames, err = f.ReadFrames(&codecs.AV1Packet{OBUElements: [][]byte{{0x01}}}) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(frames, [][]byte{{0x01}}) { t.Fatalf("One frame should be generated, %v", frames) } f = &AV1{} frames, err = f.ReadFrames(&codecs.AV1Packet{Y: true, OBUElements: [][]byte{{0x00}}}) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(frames, [][]byte{}) { t.Fatalf("No frames should be generated, %v", frames) } frames, err = f.ReadFrames(&codecs.AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}}) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(frames, [][]byte{{0x00, 0x01}}) { t.Fatalf("One frame should be generated, %v", frames) } } // Marshal some AV1 Frames to RTP, assert that AV1 can get them back in the original format func TestAV1_ReadFrames_E2E(t *testing.T) { const mtu = 1500 frames := [][]byte{ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, {0x00, 0x01}, {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}, {0x00, 0x01}, } frames = append(frames, []byte{}) for i := 0; i <= 5; i++ { frames[len(frames)-1] = append(frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}...) } frames = append(frames, []byte{}) for i := 0; i <= 500; i++ { frames[len(frames)-1] = append(frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}...) } payloader := &codecs.AV1Payloader{} f := &AV1{} for _, originalFrame := range frames { for _, payload := range payloader.Payload(mtu, originalFrame) { rtpPacket := &codecs.AV1Packet{} if _, err := rtpPacket.Unmarshal(payload); err != nil { t.Fatal(err) } decodedFrame, err := f.ReadFrames(rtpPacket) if err != nil { t.Fatal(err) } else if len(decodedFrame) != 0 && !reflect.DeepEqual(originalFrame, decodedFrame[0]) { t.Fatalf("Decode(%02x) and Original(%02x) are not equal", decodedFrame[0], originalFrame) } } } } rtp-1.7.13/pkg/obu/000077500000000000000000000000001422532252400137575ustar00rootroot00000000000000rtp-1.7.13/pkg/obu/leb128.go000066400000000000000000000025531422532252400153100ustar00rootroot00000000000000// 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 } rtp-1.7.13/pkg/obu/leb128_test.go000066400000000000000000000015061422532252400163440ustar00rootroot00000000000000package obu import ( "errors" "testing" ) func TestLEB128(t *testing.T) { for _, test := range []struct { Value uint Encoded uint }{ {0, 0}, {5, 5}, {999999, 0xBF843D}, } { test := test encoded := EncodeLEB128(test.Value) if encoded != test.Encoded { t.Fatalf("Actual(%d) did not equal expected(%d)", encoded, test.Encoded) } decoded := decodeLEB128(encoded) if decoded != test.Value { t.Fatalf("Actual(%d) did not equal expected(%d)", decoded, test.Value) } } } func TestReadLeb128(t *testing.T) { if _, _, err := ReadLeb128(nil); !errors.Is(err, ErrFailedToReadLEB128) { t.Fatal("ReadLeb128 on a nil buffer should return an error") } if _, _, err := ReadLeb128([]byte{0xFF}); !errors.Is(err, ErrFailedToReadLEB128) { t.Fatal("ReadLeb128 on a buffer with all MSB set should fail") } } rtp-1.7.13/rand.go000066400000000000000000000003321422532252400136620ustar00rootroot00000000000000package rtp import ( "github.com/pion/randutil" ) // Use global random generator to properly seed by crypto grade random. var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals rtp-1.7.13/renovate.json000066400000000000000000000011661422532252400151330ustar00rootroot00000000000000{ "extends": [ "config:base", ":disableDependencyDashboard" ], "postUpdateOptions": [ "gomodTidy" ], "commitBody": "Generated by renovateBot", "packageRules": [ { "matchUpdateTypes": ["minor", "patch", "pin", "digest"], "automerge": true }, { "packagePatterns": ["^golang.org/x/"], "schedule": ["on the first day of the month"] } ], "ignorePaths": [ ".github/workflows/generate-authors.yml", ".github/workflows/lint.yaml", ".github/workflows/renovate-go-mod-fix.yaml", ".github/workflows/test.yaml", ".github/workflows/tidy-check.yaml" ] } rtp-1.7.13/rtp.go000066400000000000000000000001041422532252400135400ustar00rootroot00000000000000// Package rtp provides RTP packetizer and depacketizer package rtp rtp-1.7.13/sequencer.go000066400000000000000000000023351422532252400147350ustar00rootroot00000000000000package 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 } rtp-1.7.13/transportccextension.go000066400000000000000000000024361422532252400172440ustar00rootroot00000000000000package 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 } rtp-1.7.13/transportccextension_test.go000066400000000000000000000020071422532252400202750ustar00rootroot00000000000000package rtp import ( "bytes" "errors" "testing" ) func TestTransportCCExtensionTooSmall(t *testing.T) { t1 := TransportCCExtension{} rawData := []byte{} if err := t1.Unmarshal(rawData); !errors.Is(err, errTooSmall) { t.Fatal("err != errTooSmall") } } func TestTransportCCExtension(t *testing.T) { t1 := TransportCCExtension{} rawData := []byte{ 0x00, 0x02, } if err := t1.Unmarshal(rawData); err != nil { t.Fatal("Unmarshal error on extension data") } t2 := TransportCCExtension{ TransportSequence: 2, } if t1 != t2 { t.Error("Unmarshal failed") } dstData, _ := t2.Marshal() if !bytes.Equal(dstData, rawData) { t.Error("Marshal failed") } } func TestTransportCCExtensionExtraBytes(t *testing.T) { t1 := TransportCCExtension{} rawData := []byte{ 0x00, 0x02, 0x00, 0xff, 0xff, } if err := t1.Unmarshal(rawData); err != nil { t.Fatal("Unmarshal error on extension data") } t2 := TransportCCExtension{ TransportSequence: 2, } if t1 != t2 { t.Error("Unmarshal failed") } }