diff --git a/.github/hooks/pre-commit.sh b/.github/hooks/pre-commit.sh index cc318d7..d5a1ce5 100755 --- a/.github/hooks/pre-commit.sh +++ b/.github/hooks/pre-commit.sh @@ -10,3 +10,4 @@ exec 1>&2 .github/lint-disallowed-functions-in-library.sh +.github/lint-no-trailing-newline-in-log-messages.sh diff --git a/.github/lint-no-trailing-newline-in-log-messages.sh b/.github/lint-no-trailing-newline-in-log-messages.sh new file mode 100755 index 0000000..29cd4a2 --- /dev/null +++ b/.github/lint-no-trailing-newline-in-log-messages.sh @@ -0,0 +1,41 @@ +#!/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 + +files=$( + find "$SCRIPT_PATH/.." -name "*.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 +) + +if grep -E '\.(Trace|Debug|Info|Warn|Error)f?\("[^"]*\\n"\)?' $files | grep -v -e 'nolint'; then + echo "Log format strings should have trailing new-line" + exit 1 +fi \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..cec0d7c --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,40 @@ +name: "CodeQL" + +on: + workflow_dispatch: + schedule: + - cron: '23 5 * * 0' + pull_request: + branches: + - master + paths: + - '**.go' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + # The code in examples/ might intentionally do things like log credentials + # in order to show how the library is used, aid in debugging etc. We + # should ignore those for CodeQL scanning, and only focus on the package + # itself. + - name: Remove example code + run: | + rm -rf examples/ + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: 'go' + + - name: CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index 83e7065..c7a8404 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -16,6 +16,8 @@ on: jobs: checksecret: + permissions: + contents: none runs-on: ubuntu-latest outputs: is_PIONBOT_PRIVATE_KEY_set: ${{ steps.checksecret_job.outputs.is_PIONBOT_PRIVATE_KEY_set }} @@ -28,11 +30,13 @@ jobs: echo "::set-output name=is_PIONBOT_PRIVATE_KEY_set::${{ env.PIONBOT_PRIVATE_KEY != '' }}" generate-authors: + permissions: + contents: write needs: [checksecret] if: needs.checksecret.outputs.is_PIONBOT_PRIVATE_KEY_set == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: ref: ${{ github.head_ref }} fetch-depth: 0 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f096078..11b6336 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,6 +16,10 @@ on: - opened - edited - synchronize + +permissions: + contents: read + jobs: lint-commit-message: name: Metadata @@ -23,7 +27,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -36,16 +40,22 @@ jobs: - name: Functions run: .github/lint-disallowed-functions-in-library.sh + - name: Logging messages should not have trailing newlines + run: .github/lint-no-trailing-newline-in-log-messages.sh + lint-go: name: Go + permissions: + contents: read + pull-requests: read runs-on: ubuntu-latest strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: - version: v1.31 + version: v1.45.2 args: $GOLANGCI_LINT_EXRA_ARGS diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml index 46d2d04..0804642 100644 --- a/.github/workflows/renovate-go-mod-fix.yaml +++ b/.github/workflows/renovate-go-mod-fix.yaml @@ -15,12 +15,15 @@ on: branches: - renovate/* +permissions: + contents: write + jobs: go-mod-fix: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 - name: fix diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 43608f1..300fac6 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,18 +17,22 @@ on: pull_request: branches: - master + +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest strategy: matrix: - go: ["1.16", "1.17"] + go: ["1.17", "1.18"] fail-fast: false name: Go ${{ matrix.go }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -39,23 +43,36 @@ jobs: ${{ runner.os }}-amd64-go- - name: Setup Go - uses: actions/setup-go@v2 + 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 + run: go install github.com/ory/go-acc@latest + + - name: Set up gotestfmt + uses: haveyoudebuggedit/gotestfmt-action@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} # Avoid getting rate limited - name: Run test run: | TEST_BENCH_OPTION="-bench=." if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + set -euo pipefail go-acc -o cover.out ./... -- \ ${TEST_BENCH_OPTION} \ - -v -race + -json \ + -v -race 2>&1 | grep -v '^go: downloading' | tee /tmp/gotest.log | gotestfmt + + - name: Upload test log + uses: actions/upload-artifact@v2 + if: always() + with: + name: test-log-${{ matrix.go }} + path: /tmp/gotest.log + if-no-files-found: error - name: Run TEST_HOOK run: | @@ -64,7 +81,6 @@ jobs: - uses: codecov/codecov-action@v2 with: - file: ./cover.out name: codecov-umbrella fail_ci_if_error: true flags: go @@ -73,13 +89,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.16", "1.17"] + go: ["1.17", "1.18"] fail-fast: false name: Go i386 ${{ matrix.go }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -110,14 +126,14 @@ jobs: fail-fast: false name: WASM steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16.x' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -153,7 +169,6 @@ jobs: - uses: codecov/codecov-action@v2 with: - file: ./cover.out name: codecov-umbrella fail_ci_if_error: true flags: wasm diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 03b5189..fa52ce9 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -18,14 +18,17 @@ on: branches: - master +permissions: + contents: read + jobs: Check: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 - name: check run: | go mod download diff --git a/.gitignore b/.gitignore index 83db74b..f977e74 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ cover.out *.wasm examples/sfu-ws/cert.pem examples/sfu-ws/key.pem +wasm_exec.js diff --git a/.golangci.yml b/.golangci.yml index d6162c9..d7a88ec 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,14 +15,22 @@ linters-settings: linters: enable: - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + - bidichk # Checks for dangerous unicode character sequences - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context - deadcode # Finds unused code + - decorder # check declaration order and count of types, constants, variables and functions - 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 + - durationcheck # check for two durations multiplied together - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. + - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. - exhaustive # check exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables + - forcetypeassert # finds forced type assertions - gci # Gci control golang package import order and make it always deterministic. - gochecknoglobals # Checks that no globals are present in Go code - gochecknoinits # Checks that no init functions are present in Go code @@ -35,40 +43,62 @@ linters: - 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 + - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. - goprintffuncname # Checks that printf-like functions are named with `f` at the end - gosec # Inspects source code for security problems - gosimple # Linter for Go source code that specializes in simplifying a code - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - grouper # An analyzer to analyze expression groups. + - importas # Enforces consistent import aliases - ineffassign # Detects when assignments to existing variables are not used - misspell # Finds commonly misspelled English words in comments - nakedret # Finds naked returns in functions greater than a specified function length + - nilerr # Finds the code that returns nil even if it checks that the error is not nil. + - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. - noctx # noctx finds sending http request without context.Context - - scopelint # Scopelint checks for unpinned variables in go programs + - predeclared # find code that shadows one of Go's predeclared identifiers + - revive # golint replacement, finds style mistakes - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks - structcheck # Finds unused struct fields - stylecheck # Stylecheck is a replacement for golint + - tagliatelle # Checks the struct tags. + - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 + - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - varcheck # Finds unused global variables and constants + - wastedassign # wastedassign finds wasted assignment statements - whitespace # Tool for detection of leading and trailing whitespace disable: + - containedctx # containedctx is a linter that detects struct contained context.Context field + - cyclop # checks function and package cyclomatic complexity + - exhaustivestruct # Checks if all struct's fields are initialized + - forbidigo # Forbids identifiers - funlen # Tool for detection of long functions - gocyclo # Computes and checks the cyclomatic complexity of functions - godot # Check if comments end in a period - gomnd # An analyzer to detect magic numbers. + - ifshort # Checks that your code uses short syntax for if-statements whenever possible + - ireturn # Accept Interfaces, Return Concrete Types - lll # Reports long lines + - maintidx # maintidx measures the maintainability index of each function. + - makezero # Finds slice declarations with non-zero initial length - maligned # Tool to detect Go structs that would take less memory if their fields were sorted - nestif # Reports deeply nested if statements - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity - nolintlint # Reports ill-formed or insufficient nolint directives + - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test - prealloc # Finds slice declarations that could potentially be preallocated + - promlinter # Check Prometheus metrics naming via promlint - rowserrcheck # checks whether Err of rows is checked successfully - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. - testpackage # linter that makes you use a separate _test package + - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers + - varnamelen # checks that the length of a variable's name matches its scope + - wrapcheck # Checks that errors returned from external packages are wrapped - wsl # Whitespace Linter - Forces you to use empty lines! issues: diff --git a/AUTHORS.txt b/AUTHORS.txt index e44a836..b5c111f 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -3,6 +3,7 @@ # # This file is auto generated, using git to list all individuals contributors. # see `.github/generate-authors.sh` for the scripting +Aaron Boushley adwpc aler9 <46489434+aler9@users.noreply.github.com> Antoine Baché @@ -28,6 +29,7 @@ Michael Uti Raphael Derosso Pereira Rob Lofthouse Robin Raymond +Sean DuBois Sean DuBois Sean DuBois Simone Gotti diff --git a/abssendtimeextension.go b/abssendtimeextension.go index fc9731d..f0c6de3 100644 --- a/abssendtimeextension.go +++ b/abssendtimeextension.go @@ -15,7 +15,7 @@ type AbsSendTimeExtension struct { } // Marshal serializes the members to buffer. -func (t *AbsSendTimeExtension) Marshal() ([]byte, error) { +func (t AbsSendTimeExtension) Marshal() ([]byte, error) { return []byte{ byte(t.Timestamp & 0xFF0000 >> 16), byte(t.Timestamp & 0xFF00 >> 8), diff --git a/audiolevelextension.go b/audiolevelextension.go index f8701e1..ca44f28 100644 --- a/audiolevelextension.go +++ b/audiolevelextension.go @@ -36,7 +36,7 @@ type AudioLevelExtension struct { } // Marshal serializes the members to buffer -func (a *AudioLevelExtension) Marshal() ([]byte, error) { +func (a AudioLevelExtension) Marshal() ([]byte, error) { if a.Level > 127 { return nil, errAudioLevelOverflow } diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go new file mode 100644 index 0000000..7aa3a55 --- /dev/null +++ b/codecs/av1_packet.go @@ -0,0 +1,158 @@ +package codecs + +import ( + "github.com/pion/rtp/v2/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 +} diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go new file mode 100644 index 0000000..27eeb47 --- /dev/null +++ b/codecs/av1_packet_test.go @@ -0,0 +1,247 @@ +package codecs + +import ( + "errors" + "fmt" + "reflect" + "testing" + + "github.com/pion/rtp/v2/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") + } +} diff --git a/codecs/error.go b/codecs/error.go index 38ee907..7f72e7b 100644 --- a/codecs/error.go +++ b/codecs/error.go @@ -8,4 +8,7 @@ var ( 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") ) diff --git a/header_extension.go b/header_extension.go index cb38324..d010bb8 100644 --- a/header_extension.go +++ b/header_extension.go @@ -46,14 +46,14 @@ func (e *OneByteHeaderExtension) Set(id uint8, buf []byte) error { } extid := e.payload[n] >> 4 - len := int(e.payload[n]&^0xF0 + 1) + payloadLen := int(e.payload[n]&^0xF0 + 1) n++ if extid == id { - e.payload = append(e.payload[:n+1], append(buf, e.payload[n+1+len:]...)...) + e.payload = append(e.payload[:n+1], append(buf, e.payload[n+1+payloadLen:]...)...) return nil } - n += len + n += payloadLen } e.payload = append(e.payload, (id<<4 | uint8(len(buf)-1))) e.payload = append(e.payload, buf...) @@ -71,7 +71,7 @@ func (e *OneByteHeaderExtension) GetIDs() []uint8 { } extid := e.payload[n] >> 4 - len := int(e.payload[n]&^0xF0 + 1) + payloadLen := int(e.payload[n]&^0xF0 + 1) n++ if extid == headerExtensionIDReserved { @@ -79,7 +79,7 @@ func (e *OneByteHeaderExtension) GetIDs() []uint8 { } ids = append(ids, extid) - n += len + n += payloadLen } return ids } @@ -93,13 +93,13 @@ func (e *OneByteHeaderExtension) Get(id uint8) []byte { } extid := e.payload[n] >> 4 - len := int(e.payload[n]&^0xF0 + 1) + payloadLen := int(e.payload[n]&^0xF0 + 1) n++ if extid == id { - return e.payload[n : n+len] + return e.payload[n : n+payloadLen] } - n += len + n += payloadLen } return nil } @@ -113,13 +113,13 @@ func (e *OneByteHeaderExtension) Del(id uint8) error { } extid := e.payload[n] >> 4 - len := int(e.payload[n]&^0xF0 + 1) + payloadLen := int(e.payload[n]&^0xF0 + 1) if extid == id { - e.payload = append(e.payload[:n], e.payload[n+1+len:]...) + e.payload = append(e.payload[:n], e.payload[n+1+payloadLen:]...) return nil } - n += len + 1 + n += payloadLen + 1 } return errHeaderExtensionNotFound } @@ -135,12 +135,12 @@ func (e *OneByteHeaderExtension) Unmarshal(buf []byte) (int, error) { } // Marshal returns the extension payload. -func (e *OneByteHeaderExtension) Marshal() ([]byte, error) { +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) { +func (e OneByteHeaderExtension) MarshalTo(buf []byte) (int, error) { size := e.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer @@ -149,7 +149,7 @@ func (e *OneByteHeaderExtension) MarshalTo(buf []byte) (int, error) { } // MarshalSize returns the size of the extension payload. -func (e *OneByteHeaderExtension) MarshalSize() int { +func (e OneByteHeaderExtension) MarshalSize() int { return len(e.payload) } @@ -176,14 +176,14 @@ func (e *TwoByteHeaderExtension) Set(id uint8, buf []byte) error { extid := e.payload[n] n++ - len := int(e.payload[n]) + payloadLen := int(e.payload[n]) n++ if extid == id { - e.payload = append(e.payload[:n+2], append(buf, e.payload[n+2+len:]...)...) + e.payload = append(e.payload[:n+2], append(buf, e.payload[n+2+payloadLen:]...)...) return nil } - n += len + n += payloadLen } e.payload = append(e.payload, id, uint8(len(buf))) e.payload = append(e.payload, buf...) @@ -203,11 +203,11 @@ func (e *TwoByteHeaderExtension) GetIDs() []uint8 { extid := e.payload[n] n++ - len := int(e.payload[n]) + payloadLen := int(e.payload[n]) n++ ids = append(ids, extid) - n += len + n += payloadLen } return ids } @@ -223,13 +223,13 @@ func (e *TwoByteHeaderExtension) Get(id uint8) []byte { extid := e.payload[n] n++ - len := int(e.payload[n]) + payloadLen := int(e.payload[n]) n++ if extid == id { - return e.payload[n : n+len] + return e.payload[n : n+payloadLen] } - n += len + n += payloadLen } return nil } @@ -244,13 +244,13 @@ func (e *TwoByteHeaderExtension) Del(id uint8) error { extid := e.payload[n] - len := int(e.payload[n+1]) + payloadLen := int(e.payload[n+1]) if extid == id { - e.payload = append(e.payload[:n], e.payload[n+2+len:]...) + e.payload = append(e.payload[:n], e.payload[n+2+payloadLen:]...) return nil } - n += len + 2 + n += payloadLen + 2 } return errHeaderExtensionNotFound } @@ -266,12 +266,12 @@ func (e *TwoByteHeaderExtension) Unmarshal(buf []byte) (int, error) { } // Marshal returns the extension payload. -func (e *TwoByteHeaderExtension) Marshal() ([]byte, error) { +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) { +func (e TwoByteHeaderExtension) MarshalTo(buf []byte) (int, error) { size := e.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer @@ -280,7 +280,7 @@ func (e *TwoByteHeaderExtension) MarshalTo(buf []byte) (int, error) { } // MarshalSize returns the size of the extension payload. -func (e *TwoByteHeaderExtension) MarshalSize() int { +func (e TwoByteHeaderExtension) MarshalSize() int { return len(e.payload) } @@ -331,12 +331,12 @@ func (e *RawExtension) Unmarshal(buf []byte) (int, error) { } // Marshal returns the raw extension payload. -func (e *RawExtension) Marshal() ([]byte, error) { +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) { +func (e RawExtension) MarshalTo(buf []byte) (int, error) { size := e.MarshalSize() if size > len(buf) { return 0, io.ErrShortBuffer @@ -345,6 +345,6 @@ func (e *RawExtension) MarshalTo(buf []byte) (int, error) { } // MarshalSize returns the size of the extension when marshaled. -func (e *RawExtension) MarshalSize() int { +func (e RawExtension) MarshalSize() int { return len(e.payload) } diff --git a/packet.go b/packet.go index 1065e17..6528b65 100644 --- a/packet.go +++ b/packet.go @@ -159,16 +159,16 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit } extid := buf[n] >> 4 - len := int(buf[n]&^0xF0 + 1) + payloadLen := int(buf[n]&^0xF0 + 1) n++ if extid == extensionIDReserved { break } - extension := Extension{id: extid, payload: buf[n : n+len]} + extension := Extension{id: extid, payload: buf[n : n+payloadLen]} h.Extensions = append(h.Extensions, extension) - n += len + n += payloadLen } // RFC 8285 RTP Two Byte Header Extension @@ -183,12 +183,12 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit extid := buf[n] n++ - len := int(buf[n]) + payloadLen := int(buf[n]) n++ - extension := Extension{id: extid, payload: buf[n : n+len]} + extension := Extension{id: extid, payload: buf[n : n+payloadLen]} h.Extensions = append(h.Extensions, extension) - n += len + n += payloadLen } default: // RFC3550 Extension @@ -224,7 +224,7 @@ func (p *Packet) Unmarshal(buf []byte) error { } // Marshal serializes the header into bytes. -func (h *Header) Marshal() (buf []byte, err error) { +func (h Header) Marshal() (buf []byte, err error) { buf = make([]byte, h.MarshalSize()) n, err := h.MarshalTo(buf) @@ -235,7 +235,7 @@ func (h *Header) Marshal() (buf []byte, err error) { } // MarshalTo serializes the header and writes to the buffer. -func (h *Header) MarshalTo(buf []byte) (n int, err error) { +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 @@ -332,7 +332,7 @@ func (h *Header) MarshalTo(buf []byte) (n int, err error) { } // MarshalSize returns the size of the header once marshaled. -func (h *Header) MarshalSize() int { +func (h Header) MarshalSize() int { // NOTE: Be careful to match the MarshalTo() method. size := 12 + (len(h.CSRC) * csrcLength) @@ -401,10 +401,10 @@ func (h *Header) SetExtension(id uint8, payload []byte) error { //nolint:gocogni // No existing header extensions h.Extension = true - switch len := len(payload); { - case len <= 16: + switch payloadLen := len(payload); { + case payloadLen <= 16: h.ExtensionProfile = extensionProfileOneByte - case len > 16 && len < 256: + case payloadLen > 16 && payloadLen < 256: h.ExtensionProfile = extensionProfileTwoByte } @@ -457,7 +457,7 @@ func (h *Header) DelExtension(id uint8) error { } // Marshal serializes the packet into bytes. -func (p *Packet) Marshal() (buf []byte, err error) { +func (p Packet) Marshal() (buf []byte, err error) { buf = make([]byte, p.MarshalSize()) n, err := p.MarshalTo(buf) @@ -469,8 +469,7 @@ func (p *Packet) Marshal() (buf []byte, err error) { } // 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 +func (p Packet) MarshalTo(buf []byte) (n int, err error) { n, err = p.Header.MarshalTo(buf) if err != nil { return 0, err @@ -490,12 +489,12 @@ func (p *Packet) MarshalTo(buf []byte) (n int, err error) { } // MarshalSize returns the size of the packet once marshaled. -func (p *Packet) MarshalSize() int { +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 { +func (p Packet) Clone() *Packet { clone := &Packet{} clone.Header = p.Header.Clone() if p.Payload != nil { diff --git a/packet_test.go b/packet_test.go index 5d19c11..6067c28 100644 --- a/packet_test.go +++ b/packet_test.go @@ -251,6 +251,7 @@ func TestBasic(t *testing.T) { }}, }, Version: 2, + Padding: true, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, diff --git a/packetizer.go b/packetizer.go index dbf85ff..a4be143 100644 --- a/packetizer.go +++ b/packetizer.go @@ -67,6 +67,7 @@ func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet { SequenceNumber: p.Sequencer.NextSequenceNumber(), Timestamp: p.Timestamp, // Figure out how to do timestamps SSRC: p.SSRC, + CSRC: []uint32{}, }, Payload: pp, } diff --git a/packetizer_test.go b/packetizer_test.go index ed4b726..6beff6a 100644 --- a/packetizer_test.go +++ b/packetizer_test.go @@ -27,8 +27,13 @@ func TestPacketizer(t *testing.T) { 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)) - pktizer.(*packetizer).Timestamp = 45678 - pktizer.(*packetizer).timegen = func() time.Time { + p, ok := pktizer.(*packetizer) + if !ok { + t.Fatal("Failed to access packetizer") + } + + p.Timestamp = 45678 + p.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 } @@ -47,7 +52,7 @@ func TestPacketizer_AbsSendTime(t *testing.T) { SequenceNumber: 1234, Timestamp: 45678, SSRC: 0x1234ABCD, - CSRC: nil, + CSRC: []uint32{}, ExtensionProfile: 0xBEDE, Extensions: []Extension{ { @@ -66,3 +71,77 @@ func TestPacketizer_AbsSendTime(t *testing.T) { t.Errorf("Packetize failed\nexpected: %v\n got: %v", expected, packets[0]) } } + +func TestPacketizer_Roundtrip(t *testing.T) { + multiplepayload := make([]byte, 128) + packetizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewRandomSequencer()) + packets := packetizer.Packetize(multiplepayload, 1000) + + rawPkts := make([][]byte, 0, 1400) + for _, pkt := range packets { + raw, err := pkt.Marshal() + if err != nil { + t.Errorf("Packet Marshal failed: %v", err) + } + + rawPkts = append(rawPkts, raw) + } + + for ndx, raw := range rawPkts { + expectedPkt := packets[ndx] + pkt := &Packet{} + + err := pkt.Unmarshal(raw) + if err != nil { + t.Errorf("Packet Unmarshal failed: %v", err) + } + + if len(raw) != pkt.MarshalSize() { + t.Errorf("Packet sizes don't match, expected %d but got %d", len(raw), pkt.MarshalSize()) + } + if expectedPkt.MarshalSize() != pkt.MarshalSize() { + t.Errorf("Packet marshal sizes don't match, expected %d but got %d", expectedPkt.MarshalSize(), pkt.MarshalSize()) + } + + if expectedPkt.Version != pkt.Version { + t.Errorf("Packet versions don't match, expected %d but got %d", expectedPkt.Version, pkt.Version) + } + if expectedPkt.Padding != pkt.Padding { + t.Errorf("Packet versions don't match, expected %t but got %t", expectedPkt.Padding, pkt.Padding) + } + if expectedPkt.Extension != pkt.Extension { + t.Errorf("Packet versions don't match, expected %v but got %v", expectedPkt.Extension, pkt.Extension) + } + if expectedPkt.Marker != pkt.Marker { + t.Errorf("Packet versions don't match, expected %v but got %v", expectedPkt.Marker, pkt.Marker) + } + if expectedPkt.PayloadType != pkt.PayloadType { + t.Errorf("Packet versions don't match, expected %d but got %d", expectedPkt.PayloadType, pkt.PayloadType) + } + if expectedPkt.SequenceNumber != pkt.SequenceNumber { + t.Errorf("Packet versions don't match, expected %d but got %d", expectedPkt.SequenceNumber, pkt.SequenceNumber) + } + if expectedPkt.Timestamp != pkt.Timestamp { + t.Errorf("Packet versions don't match, expected %d but got %d", expectedPkt.Timestamp, pkt.Timestamp) + } + if expectedPkt.SSRC != pkt.SSRC { + t.Errorf("Packet versions don't match, expected %d but got %d", expectedPkt.SSRC, pkt.SSRC) + } + if !reflect.DeepEqual(expectedPkt.CSRC, pkt.CSRC) { + t.Errorf("Packet versions don't match, expected %v but got %v", expectedPkt.CSRC, pkt.CSRC) + } + if expectedPkt.ExtensionProfile != pkt.ExtensionProfile { + t.Errorf("Packet versions don't match, expected %d but got %d", expectedPkt.ExtensionProfile, pkt.ExtensionProfile) + } + if !reflect.DeepEqual(expectedPkt.Extensions, pkt.Extensions) { + t.Errorf("Packet versions don't match, expected %v but got %v", expectedPkt.Extensions, pkt.Extensions) + } + if !reflect.DeepEqual(expectedPkt.Payload, pkt.Payload) { + t.Errorf("Packet versions don't match, expected %v but got %v", expectedPkt.Payload, pkt.Payload) + } + + if !reflect.DeepEqual(expectedPkt, pkt) { + t.Errorf("Packets don't match, expected %v but got %v", expectedPkt, pkt) + } + } +} diff --git a/pkg/frame/av1.go b/pkg/frame/av1.go new file mode 100644 index 0000000..46fb831 --- /dev/null +++ b/pkg/frame/av1.go @@ -0,0 +1,44 @@ +// Package frame provides code to construct complete media frames from packetized media +package frame + +import "github.com/pion/rtp/v2/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 +} diff --git a/pkg/frame/av1_test.go b/pkg/frame/av1_test.go new file mode 100644 index 0000000..11538cf --- /dev/null +++ b/pkg/frame/av1_test.go @@ -0,0 +1,83 @@ +package frame + +import ( + "reflect" + "testing" + + "github.com/pion/rtp/v2/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) + } + } + } +} diff --git a/pkg/obu/leb128.go b/pkg/obu/leb128.go new file mode 100644 index 0000000..988a8f4 --- /dev/null +++ b/pkg/obu/leb128.go @@ -0,0 +1,66 @@ +// 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 +} diff --git a/pkg/obu/leb128_test.go b/pkg/obu/leb128_test.go new file mode 100644 index 0000000..42cf777 --- /dev/null +++ b/pkg/obu/leb128_test.go @@ -0,0 +1,39 @@ +package 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") + } +} diff --git a/transportccextension.go b/transportccextension.go index f9ffe4e..236af05 100644 --- a/transportccextension.go +++ b/transportccextension.go @@ -23,7 +23,7 @@ type TransportCCExtension struct { } // Marshal serializes the members to buffer -func (t *TransportCCExtension) Marshal() ([]byte, error) { +func (t TransportCCExtension) Marshal() ([]byte, error) { buf := make([]byte, transportCCExtensionSize) binary.BigEndian.PutUint16(buf[0:2], t.TransportSequence) return buf, nil