From 0bd95536ebc07e3e1746042ebffd1654100bccb3 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 1 Aug 2022 10:31:53 -0400 Subject: [PATCH 001/102] Add experimental header extensions Add AbsCaptureTimeExtension and PlayoutDelayExtension implementations. Both of these are experimental RTP header extensions defined in libwebrtc. --- abscapturetimeextension.go | 87 +++++++++++++++++++++++++++++++++ abscapturetimeextension_test.go | 43 ++++++++++++++++ playoutdelayextension.go | 47 ++++++++++++++++++ playoutdelayextension_test.go | 70 ++++++++++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 abscapturetimeextension.go create mode 100644 abscapturetimeextension_test.go create mode 100644 playoutdelayextension.go create mode 100644 playoutdelayextension_test.go diff --git a/abscapturetimeextension.go b/abscapturetimeextension.go new file mode 100644 index 0000000..f3005e9 --- /dev/null +++ b/abscapturetimeextension.go @@ -0,0 +1,87 @@ +package rtp + +import ( + "encoding/binary" + "time" +) + +const ( + absCaptureTimeExtensionSize = 8 + absCaptureTimeExtendedExtensionSize = 16 +) + +// AbsCaptureTimeExtension is a extension payload format in +// http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=7 | absolute capture timestamp (bit 0-23) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | absolute capture timestamp (bit 24-55) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... (56-63) | +// +-+-+-+-+-+-+-+-+ +type AbsCaptureTimeExtension struct { + Timestamp uint64 + EstimatedCaptureClockOffset *int64 +} + +// Marshal serializes the members to buffer. +func (t AbsCaptureTimeExtension) Marshal() ([]byte, error) { + if t.EstimatedCaptureClockOffset != nil { + buf := make([]byte, 16) + binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) + binary.BigEndian.PutUint64(buf[8:16], uint64(*t.EstimatedCaptureClockOffset)) + return buf, nil + } + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) + return buf, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members. +func (t *AbsCaptureTimeExtension) Unmarshal(rawData []byte) error { + if len(rawData) < absCaptureTimeExtensionSize { + return errTooSmall + } + t.Timestamp = binary.BigEndian.Uint64(rawData[0:8]) + if len(rawData) >= absCaptureTimeExtendedExtensionSize { + offset := int64(binary.BigEndian.Uint64(rawData[8:16])) + t.EstimatedCaptureClockOffset = &offset + } + return nil +} + +// CaptureTime produces the estimated time.Time represented by this extension. +func (t AbsCaptureTimeExtension) CaptureTime() time.Time { + return toTime(t.Timestamp) +} + +// EstimatedCaptureClockOffsetDuration produces the estimated time.Duration represented by this extension. +func (t AbsCaptureTimeExtension) EstimatedCaptureClockOffsetDuration() *time.Duration { + if t.EstimatedCaptureClockOffset == nil { + return nil + } + offset := *t.EstimatedCaptureClockOffset + duration := time.Duration(offset/(1<<32))*time.Second + time.Duration((offset&0xFFFFFFFF)*1e9/(1<<32))*time.Nanosecond + return &duration +} + +// NewAbsCaptureTimeExtension makes new AbsCaptureTimeExtension from time.Time. +func NewAbsCaptureTimeExtension(captureTime time.Time) *AbsCaptureTimeExtension { + return &AbsCaptureTimeExtension{ + Timestamp: toNtpTime(captureTime), + } +} + +// NewAbsCaptureTimeExtensionWithCaptureClockOffset makes new AbsCaptureTimeExtension from time.Time and a clock offset. +func NewAbsCaptureTimeExtensionWithCaptureClockOffset(captureTime time.Time, captureClockOffset time.Duration) *AbsCaptureTimeExtension { + ns := captureClockOffset.Nanoseconds() + lsb := (ns / 1e9) & 0xFFFFFFFF + msb := (((ns % 1e9) * (1 << 32)) / 1e9) & 0xFFFFFFFF + offset := (lsb << 32) | msb + return &AbsCaptureTimeExtension{ + Timestamp: toNtpTime(captureTime), + EstimatedCaptureClockOffset: &offset, + } +} diff --git a/abscapturetimeextension_test.go b/abscapturetimeextension_test.go new file mode 100644 index 0000000..d5b9062 --- /dev/null +++ b/abscapturetimeextension_test.go @@ -0,0 +1,43 @@ +package rtp + +import ( + "testing" + "time" +) + +func TestAbsCaptureTimeExtension_Roundtrip(t *testing.T) { + t0 := time.Now() + e1 := NewAbsCaptureTimeExtension(t0) + b1, err1 := e1.Marshal() + if err1 != nil { + t.Fatal(err1) + } + var o1 AbsCaptureTimeExtension + if err := o1.Unmarshal(b1); err != nil { + t.Fatal(err) + } + dt1 := o1.CaptureTime().Sub(t0).Seconds() + if dt1 < -0.001 || dt1 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o1.CaptureTime(), dt1) + } + if o1.EstimatedCaptureClockOffsetDuration() != nil { + t.Fatalf("duration differs, want nil got %d", o1.EstimatedCaptureClockOffsetDuration()) + } + + e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, 1250*time.Millisecond) + b2, err2 := e2.Marshal() + if err2 != nil { + t.Fatal(err2) + } + var o2 AbsCaptureTimeExtension + if err := o2.Unmarshal(b2); err != nil { + t.Fatal(err) + } + dt2 := o1.CaptureTime().Sub(t0).Seconds() + if dt2 < -0.001 || dt2 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o2.CaptureTime(), dt2) + } + if *o2.EstimatedCaptureClockOffsetDuration() != 1250*time.Millisecond { + t.Fatalf("duration differs, want 250ms got %d", *o2.EstimatedCaptureClockOffsetDuration()) + } +} diff --git a/playoutdelayextension.go b/playoutdelayextension.go new file mode 100644 index 0000000..e508503 --- /dev/null +++ b/playoutdelayextension.go @@ -0,0 +1,47 @@ +package rtp + +import ( + "encoding/binary" + "errors" +) + +const ( + playoutDelayExtensionSize = 3 + playoutDelayMaxValue = (1 << 12) - 1 +) + +var errPlayoutDelayInvalidValue = errors.New("invalid playout delay value") + +// PlayoutDelayExtension is a extension payload format in +// http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 | MIN delay | MAX delay | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type PlayoutDelayExtension struct { + minDelay, maxDelay uint16 +} + +// Marshal serializes the members to buffer +func (p PlayoutDelayExtension) Marshal() ([]byte, error) { + if p.minDelay > playoutDelayMaxValue || p.maxDelay > playoutDelayMaxValue { + return nil, errPlayoutDelayInvalidValue + } + + return []byte{ + byte(p.minDelay >> 4), + byte(p.minDelay<<4) | byte(p.maxDelay>>8), + byte(p.maxDelay), + }, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members +func (p *PlayoutDelayExtension) Unmarshal(rawData []byte) error { + if len(rawData) < playoutDelayExtensionSize { + return errTooSmall + } + p.minDelay = binary.BigEndian.Uint16(rawData[0:2]) >> 4 + p.maxDelay = binary.BigEndian.Uint16(rawData[1:3]) & 0x0FFF + return nil +} diff --git a/playoutdelayextension_test.go b/playoutdelayextension_test.go new file mode 100644 index 0000000..6757b14 --- /dev/null +++ b/playoutdelayextension_test.go @@ -0,0 +1,70 @@ +package rtp + +import ( + "bytes" + "errors" + "testing" +) + +func TestPlayoutDelayExtensionTooSmall(t *testing.T) { + t1 := PlayoutDelayExtension{} + + var rawData []byte + + if err := t1.Unmarshal(rawData); !errors.Is(err, errTooSmall) { + t.Fatal("err != errTooSmall") + } +} + +func TestPlayoutDelayExtensionTooLarge(t *testing.T) { + t1 := PlayoutDelayExtension{minDelay: 1 << 12, maxDelay: 1 << 12} + + if _, err := t1.Marshal(); !errors.Is(err, errPlayoutDelayInvalidValue) { + t.Fatal("err != errPlayoutDelayInvalidValue") + } +} + +func TestPlayoutDelayExtension(t *testing.T) { + t1 := PlayoutDelayExtension{} + + rawData := []byte{ + 0x01, 0x01, 0x00, + } + + if err := t1.Unmarshal(rawData); err != nil { + t.Fatal("Unmarshal error on extension data") + } + + t2 := PlayoutDelayExtension{ + minDelay: 1 << 4, maxDelay: 1 << 8, + } + + if t1 != t2 { + t.Error("Unmarshal failed") + } + + dstData, _ := t2.Marshal() + if !bytes.Equal(dstData, rawData) { + t.Error("Marshal failed") + } +} + +func TestPlayoutDelayExtensionExtraBytes(t *testing.T) { + t1 := PlayoutDelayExtension{} + + rawData := []byte{ + 0x01, 0x01, 0x00, 0xff, 0xff, + } + + if err := t1.Unmarshal(rawData); err != nil { + t.Fatal("Unmarshal error on extension data") + } + + t2 := PlayoutDelayExtension{ + minDelay: 1 << 4, maxDelay: 1 << 8, + } + + if t1 != t2 { + t.Error("Unmarshal failed") + } +} From d13e8b61b96c5985359fd6cd6d88a3115b22cedd Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:00:44 +0000 Subject: [PATCH 002/102] Update CI configs to v0.8.1 Update lint scripts and CI configs. --- .github/generate-authors.sh | 23 +++++++-------- .github/install-hooks.sh | 6 ++-- .github/lint-commit-message.sh | 4 +-- .../lint-disallowed-functions-in-library.sh | 29 ++++++++----------- .github/lint-filename.sh | 8 ++--- ...int-no-trailing-newline-in-log-messages.sh | 24 +++++++-------- .github/workflows/release.yml | 25 ++++++++++++++++ .github/workflows/test.yaml | 6 ++-- .github/workflows/tidy-check.yaml | 2 ++ .goreleaser.yml | 2 ++ renovate.json | 25 ++-------------- 11 files changed, 75 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .goreleaser.yml diff --git a/.github/generate-authors.sh b/.github/generate-authors.sh index 182e4f5..6152721 100755 --- a/.github/generate-authors.sh +++ b/.github/generate-authors.sh @@ -12,10 +12,10 @@ set -e SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -AUTHORS_PATH="$GITHUB_WORKSPACE/AUTHORS.txt" +GIT_WORKDIR=${GITHUB_WORKSPACE:-$(git rev-parse --show-toplevel)} +AUTHORS_PATH="${GIT_WORKDIR}/AUTHORS.txt" -if [ -f ${SCRIPT_PATH}/.ci.conf ] -then +if [ -f ${SCRIPT_PATH}/.ci.conf ]; then . ${SCRIPT_PATH}/.ci.conf fi @@ -31,8 +31,7 @@ EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion B CONTRIBUTORS=() shouldBeIncluded () { - for i in "${EXCLUDED_CONTRIBUTORS[@]}" - do + for i in "${EXCLUDED_CONTRIBUTORS[@]}"; do if [[ $1 =~ "$i" ]]; then return 1 fi @@ -42,25 +41,23 @@ shouldBeIncluded () { 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") +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' + 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 + for i in "${CONTRIBUTORS[@]}"; do + echo "$i" >> ${AUTHORS_PATH} done exit 0 fi diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh index 73d20a4..cd899d4 100755 --- a/.github/install-hooks.sh +++ b/.github/install-hooks.sh @@ -11,6 +11,6 @@ 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" +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" diff --git a/.github/lint-commit-message.sh b/.github/lint-commit-message.sh index 010a332..2beb31d 100755 --- a/.github/lint-commit-message.sh +++ b/.github/lint-commit-message.sh @@ -58,7 +58,7 @@ if [ "$#" -eq 1 ]; then 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)" + for COMMIT in $(git rev-list --no-merges origin/master..); do + lint_commit_message "$(git log --format="%B" -n 1 ${COMMIT})" done fi diff --git a/.github/lint-disallowed-functions-in-library.sh b/.github/lint-disallowed-functions-in-library.sh index 8ce5d09..9dd988f 100755 --- a/.github/lint-disallowed-functions-in-library.sh +++ b/.github/lint-disallowed-functions-in-library.sh @@ -13,36 +13,31 @@ 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 +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" \ +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 + | while read FILE; do + EXCLUDED=false + for EXCLUDE_DIRECTORY in ${EXCLUDE_DIRECTORIES}; do + if [[ ${FILE} == */${EXCLUDE_DIRECTORY}/* ]]; then + EXCLUDED=true break fi done - $excluded || echo "$file" + ${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" +for DISALLOWED_FUNCTION in "${DISALLOWED_FUNCTIONS[@]}"; do + if grep -e "\s${DISALLOWED_FUNCTION}" ${FILES} | grep -v -e 'nolint'; then + echo "${DISALLOWED_FUNCTION} may only be used in example code" exit 1 fi done diff --git a/.github/lint-filename.sh b/.github/lint-filename.sh index 81b3f14..3e7d1b9 100755 --- a/.github/lint-filename.sh +++ b/.github/lint-filename.sh @@ -14,11 +14,11 @@ 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") +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" + 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 diff --git a/.github/lint-no-trailing-newline-in-log-messages.sh b/.github/lint-no-trailing-newline-in-log-messages.sh index 29cd4a2..f0dad59 100755 --- a/.github/lint-no-trailing-newline-in-log-messages.sh +++ b/.github/lint-no-trailing-newline-in-log-messages.sh @@ -13,29 +13,25 @@ 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 +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 +FILES=$( + find "${SCRIPT_PATH}/.." -name "*.go" \ + | while read FILE; do + EXCLUDED=false + for EXCLUDE_DIRECTORY in ${EXCLUDE_DIRECTORIES}; do + if [[ $file == */${EXCLUDE_DIRECTORY}/* ]]; then + EXCLUDED=true break fi done - $excluded || echo "$file" + ${EXCLUDED} || echo "${FILE}" done ) -if grep -E '\.(Trace|Debug|Info|Warn|Error)f?\("[^"]*\\n"\)?' $files | grep -v -e 'nolint'; then +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/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b5b64e0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: release +on: + push: + tags: + - 'v*' + +jobs: + release: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-go@v3 + with: + go-version: '1.18' # auto-update/latest-go-version + - name: Build and release + uses: goreleaser/goreleaser-action@v3 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 300fac6..93d0406 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.17", "1.18"] + go: ["1.17", "1.18"] # auto-update/supported-go-version-list fail-fast: false name: Go ${{ matrix.go }} steps: @@ -89,7 +89,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.17", "1.18"] + go: ["1.17", "1.18"] # auto-update/supported-go-version-list fail-fast: false name: Go i386 ${{ matrix.go }} steps: @@ -145,7 +145,7 @@ jobs: - 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 + GO_VERSION: 1.17 # auto-update/latest-go-version - name: Set Go Root run: echo "GOROOT=${HOME}/go" >> $GITHUB_ENV diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index fa52ce9..ff2ef50 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -29,6 +29,8 @@ jobs: uses: actions/checkout@v3 - name: Setup Go uses: actions/setup-go@v3 + with: + go-version: 1.17 # auto-update/latest-go-version - name: check run: | go mod download diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..2caa5fb --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,2 @@ +builds: +- skip: true diff --git a/renovate.json b/renovate.json index f161405..f1bb98c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,27 +1,6 @@ { + "$schema": "https://docs.renovatebot.com/renovate-schema.json", "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" + "github>pion/renovate-config" ] } From 8516abc948daffecb28fffb72d23bc08182bbfe0 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Fri, 6 Jan 2023 20:50:53 +0100 Subject: [PATCH 003/102] Fix parsing of VP8 packets with degenerate header All of the fields in the VP8 header except the first byte are optional. We used to reject VP8 packets smaller than 4 bytes, which is incorrect. There are two cases where such packets may appear on the wire. GStreamer's WebRTC implementation generates VP8 streams with no picture id, and one-byte headers. It will occasionally generate packets that are below 4 bytes, and which we used to reject. The second use case is more theoretical. According to RFC 7741 Section 4.4, a packetizer may ignore VP8 partition boundaries. If it splits a packet outside of a partition boundary, it may generate a packet with S=0 and a one-byte header. --- codecs/vp8_packet.go | 48 +++++++++++++++++++++++++++------------ codecs/vp8_packet_test.go | 41 ++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index cd86929..b831dd3 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -127,12 +127,11 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { payloadLen := len(payload) - if payloadLen < 4 { - return nil, errShortPacket - } - payloadIndex := 0 + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.X = (payload[payloadIndex] & 0x80) >> 7 p.N = (payload[payloadIndex] & 0x20) >> 5 p.S = (payload[payloadIndex] & 0x10) >> 4 @@ -141,14 +140,25 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { payloadIndex++ if p.X == 1 { + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.I = (payload[payloadIndex] & 0x80) >> 7 p.L = (payload[payloadIndex] & 0x40) >> 6 p.T = (payload[payloadIndex] & 0x20) >> 5 p.K = (payload[payloadIndex] & 0x10) >> 4 payloadIndex++ + } else { + p.I = 0 + p.L = 0 + p.T = 0 + p.K = 0 } if p.I == 1 { // PID present? + if payloadIndex >= payloadLen { + return nil, errShortPacket + } if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1]) payloadIndex += 2 @@ -156,35 +166,43 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { p.PictureID = uint16(payload[payloadIndex]) payloadIndex++ } - } - - if payloadIndex >= payloadLen { - return nil, errShortPacket + } else { + p.PictureID = 0 } if p.L == 1 { + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.TL0PICIDX = payload[payloadIndex] payloadIndex++ - } - - if payloadIndex >= payloadLen { - return nil, errShortPacket + } else { + p.TL0PICIDX = 0 } if p.T == 1 || p.K == 1 { + if payloadIndex >= payloadLen { + return nil, errShortPacket + } if p.T == 1 { p.TID = payload[payloadIndex] >> 6 p.Y = (payload[payloadIndex] >> 5) & 0x1 + } else { + p.TID = 0 + p.Y = 0 } if p.K == 1 { p.KEYIDX = payload[payloadIndex] & 0x1F + } else { + p.KEYIDX = 0 } payloadIndex++ + } else { + p.TID = 0 + p.Y = 0 + p.KEYIDX = 0 } - if payloadIndex >= payloadLen { - return nil, errShortPacket - } p.Payload = payload[payloadIndex:] return p.Payload, nil } diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index b173cfb..e7a9610 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -27,15 +27,6 @@ func TestVP8Packet_Unmarshal(t *testing.T) { 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 { @@ -65,11 +56,11 @@ func TestVP8Packet_Unmarshal(t *testing.T) { // 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 raw == nil { + t.Fatal("Result shouldn't be nil in case of success") } - if !errors.Is(err, errShortPacket) { - t.Fatal("Error should be:", errShortPacket) + if err != nil { + t.Fatal("Error should be nil in case of success") } // Header size, X and L @@ -107,6 +98,30 @@ func TestVP8Packet_Unmarshal(t *testing.T) { if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } + + // According to RFC 7741 Section 4.4, the packetizer need not pay + // attention to partition boundaries. In that case, it may + // produce packets with minimal headers. + + // The next two have been witnessed in nature. + _, err = pck.Unmarshal([]byte{0x00}) + if err != nil { + t.Errorf("Empty packet with trivial header: %v", err) + } + _, err = pck.Unmarshal([]byte{0x00, 0x2a, 0x94}) + if err != nil { + t.Errorf("Non-empty packet with trivial header: %v", err) + } + + // The following two were invented. + _, err = pck.Unmarshal([]byte{0x80, 0x00}) + if err != nil { + t.Errorf("Empty packet with trivial extension: %v", err) + } + _, err = pck.Unmarshal([]byte{0x80, 0x80, 42}) + if err != nil { + t.Errorf("Header with PictureID: %v", err) + } } func TestVP8Payloader_Payload(t *testing.T) { From c66a2542973d53445b4748c141f190827317a319 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 10 Jan 2023 07:53:54 +0000 Subject: [PATCH 004/102] Update CI configs to v0.9.0 Update lint scripts and CI configs. --- .github/generate-authors.sh | 12 +++++++++--- .github/workflows/release.yml | 2 +- .github/workflows/test.yaml | 12 ++++++------ .github/workflows/tidy-check.yaml | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/generate-authors.sh b/.github/generate-authors.sh index 6152721..5468af8 100755 --- a/.github/generate-authors.sh +++ b/.github/generate-authors.sh @@ -12,8 +12,9 @@ set -e SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -GIT_WORKDIR=${GITHUB_WORKSPACE:-$(git rev-parse --show-toplevel)} -AUTHORS_PATH="${GIT_WORKDIR}/AUTHORS.txt" +if [ -z "${AUTHORS_PATH}" ]; then + AUTHORS_PATH="$GITHUB_WORKSPACE/AUTHORS.txt" +fi if [ -f ${SCRIPT_PATH}/.ci.conf ]; then . ${SCRIPT_PATH}/.ci.conf @@ -41,7 +42,12 @@ shouldBeIncluded () { IFS=$'\n' #Only split on newline -for CONTRIBUTOR in $(git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf); do +for CONTRIBUTOR in $( + ( + git log --format='%aN <%aE>' + git log --format='%(trailers:key=Co-authored-by)' | sed -n 's/^[^:]*:\s*//p' + ) | LC_ALL=C.UTF-8 sort -uf +); do if shouldBeIncluded ${CONTRIBUTOR}; then CONTRIBUTORS+=("${CONTRIBUTOR}") fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b5b64e0..ac2fe3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: with: go-version: '1.18' # auto-update/latest-go-version - name: Build and release - uses: goreleaser/goreleaser-action@v3 + uses: goreleaser/goreleaser-action@v4 with: version: latest args: release --rm-dist diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 93d0406..0d9125f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.17", "1.18"] # auto-update/supported-go-version-list + go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false name: Go ${{ matrix.go }} steps: @@ -67,7 +67,7 @@ jobs: -v -race 2>&1 | grep -v '^go: downloading' | tee /tmp/gotest.log | gotestfmt - name: Upload test log - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: always() with: name: test-log-${{ matrix.go }} @@ -79,7 +79,7 @@ jobs: if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v3 with: name: codecov-umbrella fail_ci_if_error: true @@ -89,7 +89,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.17", "1.18"] # auto-update/supported-go-version-list + go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false name: Go i386 ${{ matrix.go }} steps: @@ -145,7 +145,7 @@ jobs: - 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 # auto-update/latest-go-version + GO_VERSION: '1.19' # auto-update/latest-go-version - name: Set Go Root run: echo "GOROOT=${HOME}/go" >> $GITHUB_ENV @@ -167,7 +167,7 @@ jobs: -exec="${GO_JS_WASM_EXEC}" \ -v ./... - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v3 with: name: codecov-umbrella fail_ci_if_error: true diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index ff2ef50..50cc17a 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -30,7 +30,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: 1.17 # auto-update/latest-go-version + go-version: '1.19' # auto-update/latest-go-version - name: check run: | go mod download From fdab6b1e8d959f9da6acbaa27bdccce18f372588 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Fri, 20 Jan 2023 02:59:40 +0000 Subject: [PATCH 005/102] Update CI configs to v0.10.2 Update lint scripts and CI configs. --- .github/.gitignore | 1 + .github/fetch-scripts.sh | 29 ++++ .github/generate-authors.sh | 69 -------- .github/hooks/commit-msg.sh | 11 -- .github/hooks/pre-commit.sh | 13 -- .github/hooks/pre-push.sh | 13 -- .github/install-hooks.sh | 12 +- .github/lint-commit-message.sh | 64 -------- .../lint-disallowed-functions-in-library.sh | 43 ----- .github/lint-filename.sh | 24 --- ...int-no-trailing-newline-in-log-messages.sh | 37 ----- .github/workflows/codeql-analysis.yml | 40 ++--- .github/workflows/generate-authors.yml | 66 +------- .github/workflows/lint.yaml | 47 +----- .github/workflows/release.yml | 33 ++-- .github/workflows/renovate-go-mod-fix.yaml | 36 ----- .github/workflows/renovate-go-sum-fix.yaml | 22 +++ .github/workflows/test.yaml | 153 ++---------------- .github/workflows/tidy-check.yaml | 27 +--- AUTHORS.txt | 2 +- 20 files changed, 109 insertions(+), 633 deletions(-) create mode 100644 .github/.gitignore create mode 100755 .github/fetch-scripts.sh delete mode 100755 .github/generate-authors.sh delete mode 100755 .github/hooks/commit-msg.sh delete mode 100755 .github/hooks/pre-commit.sh delete mode 100755 .github/hooks/pre-push.sh delete mode 100755 .github/lint-commit-message.sh delete mode 100755 .github/lint-disallowed-functions-in-library.sh delete mode 100755 .github/lint-filename.sh delete mode 100755 .github/lint-no-trailing-newline-in-log-messages.sh delete mode 100644 .github/workflows/renovate-go-mod-fix.yaml create mode 100644 .github/workflows/renovate-go-sum-fix.yaml diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..44d7f86 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +.goassets diff --git a/.github/fetch-scripts.sh b/.github/fetch-scripts.sh new file mode 100755 index 0000000..d7e848b --- /dev/null +++ b/.github/fetch-scripts.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# 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 -eu + +SCRIPT_PATH="$(realpath "$(dirname "$0")")" +GOASSETS_PATH="${SCRIPT_PATH}/.goassets" + +GOASSETS_REF=${GOASSETS_REF:-master} + +if [ -d "${GOASSETS_PATH}" ]; then + if ! git -C "${GOASSETS_PATH}" diff --exit-code; then + echo "${GOASSETS_PATH} has uncommitted changes" >&2 + exit 1 + fi + git -C "${GOASSETS_PATH}" fetch origin + git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} + git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} +else + git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" +fi diff --git a/.github/generate-authors.sh b/.github/generate-authors.sh deleted file mode 100755 index 5468af8..0000000 --- a/.github/generate-authors.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/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 ) -if [ -z "${AUTHORS_PATH}" ]; then - AUTHORS_PATH="$GITHUB_WORKSPACE/AUTHORS.txt" -fi - -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>' - git log --format='%(trailers:key=Co-authored-by)' | sed -n 's/^[^:]*:\s*//p' - ) | 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 diff --git a/.github/hooks/commit-msg.sh b/.github/hooks/commit-msg.sh deleted file mode 100755 index 8213dc2..0000000 --- a/.github/hooks/commit-msg.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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 diff --git a/.github/hooks/pre-commit.sh b/.github/hooks/pre-commit.sh deleted file mode 100755 index d5a1ce5..0000000 --- a/.github/hooks/pre-commit.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 -.github/lint-no-trailing-newline-in-log-messages.sh diff --git a/.github/hooks/pre-push.sh b/.github/hooks/pre-push.sh deleted file mode 100755 index bfe65bc..0000000 --- a/.github/hooks/pre-push.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh index cd899d4..57b7798 100755 --- a/.github/install-hooks.sh +++ b/.github/install-hooks.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # DO NOT EDIT THIS FILE @@ -9,8 +9,10 @@ # https://github.com/pion/.goassets instead of this repository. # -SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +SCRIPT_PATH="$(realpath "$(dirname "$0")")" -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" +. ${SCRIPT_PATH}/fetch-scripts.sh + +cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" +cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" +cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" diff --git a/.github/lint-commit-message.sh b/.github/lint-commit-message.sh deleted file mode 100755 index 2beb31d..0000000 --- a/.github/lint-commit-message.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/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 diff --git a/.github/lint-disallowed-functions-in-library.sh b/.github/lint-disallowed-functions-in-library.sh deleted file mode 100755 index 9dd988f..0000000 --- a/.github/lint-disallowed-functions-in-library.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/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 EXCLUDE_DIRECTORY in ${EXCLUDE_DIRECTORIES}; do - if [[ ${FILE} == */${EXCLUDE_DIRECTORY}/* ]]; then - EXCLUDED=true - break - fi - done - ${EXCLUDED} || echo "${FILE}" - done -) - -for DISALLOWED_FUNCTION in "${DISALLOWED_FUNCTIONS[@]}"; do - if grep -e "\s${DISALLOWED_FUNCTION}" ${FILES} | grep -v -e 'nolint'; then - echo "${DISALLOWED_FUNCTION} may only be used in example code" - exit 1 - fi -done diff --git a/.github/lint-filename.sh b/.github/lint-filename.sh deleted file mode 100755 index 3e7d1b9..0000000 --- a/.github/lint-filename.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/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 diff --git a/.github/lint-no-trailing-newline-in-log-messages.sh b/.github/lint-no-trailing-newline-in-log-messages.sh deleted file mode 100755 index f0dad59..0000000 --- a/.github/lint-no-trailing-newline-in-log-messages.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/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 EXCLUDE_DIRECTORY in ${EXCLUDE_DIRECTORIES}; do - if [[ $file == */${EXCLUDE_DIRECTORY}/* ]]; 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 index cec0d7c..bb44904 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,4 +1,15 @@ -name: "CodeQL" +# +# 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: CodeQL on: workflow_dispatch: @@ -12,29 +23,4 @@ on: 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 + uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index c7a8404..9fc78bc 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -9,69 +9,13 @@ # https://github.com/pion/.goassets instead of this repository. # -name: generate-authors +name: Generate Authors on: pull_request: jobs: - checksecret: - permissions: - contents: none - 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: - permissions: - contents: write - 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) + generate: + uses: pion/.goassets/.github/workflows/generate-authors.reusable.yml@master + secrets: + token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 11b6336..e72c716 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -12,50 +12,7 @@ name: Lint on: pull_request: - types: - - opened - - edited - - synchronize - -permissions: - contents: read 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 - - - 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@v3 - - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.45.2 - args: $GOLANGCI_LINT_EXRA_ARGS + lint: + uses: pion/.goassets/.github/workflows/lint.reusable.yml@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac2fe3c..a07c292 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,15 @@ -name: release +# +# 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: Release on: push: tags: @@ -6,20 +17,6 @@ on: jobs: release: - permissions: - contents: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: actions/setup-go@v3 - with: - go-version: '1.18' # auto-update/latest-go-version - - name: Build and release - uses: goreleaser/goreleaser-action@v4 - with: - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: pion/.goassets/.github/workflows/release.reusable.yml@master + with: + go-version: '1.19' # auto-update/latest-go-version diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml deleted file mode 100644 index 0804642..0000000 --- a/.github/workflows/renovate-go-mod-fix.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# -# 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/* - -permissions: - contents: write - -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 diff --git a/.github/workflows/renovate-go-sum-fix.yaml b/.github/workflows/renovate-go-sum-fix.yaml new file mode 100644 index 0000000..5a9af36 --- /dev/null +++ b/.github/workflows/renovate-go-sum-fix.yaml @@ -0,0 +1,22 @@ +# +# 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: Fix go.sum +on: + push: + branches: + - renovate/* + +jobs: + fix: + uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master + secrets: + token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0d9125f..834a158 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,162 +13,29 @@ name: Test on: push: branches: - - master + - master pull_request: - branches: - - master - -permissions: - contents: read jobs: test: - runs-on: ubuntu-latest + uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false - name: Go ${{ matrix.go }} - steps: - - uses: actions/checkout@v3 - - - uses: actions/cache@v3 - 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 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} \ - -json \ - -v -race 2>&1 | grep -v '^go: downloading' | tee /tmp/gotest.log | gotestfmt - - - name: Upload test log - uses: actions/upload-artifact@v3 - if: always() - with: - name: test-log-${{ matrix.go }} - path: /tmp/gotest.log - if-no-files-found: error - - - 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@v3 - with: - name: codecov-umbrella - fail_ci_if_error: true - flags: go + with: + go-version: ${{ matrix.go }} test-i386: - runs-on: ubuntu-latest + uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: go: ['1.19', '1.18'] # auto-update/supported-go-version-list fail-fast: false - name: Go i386 ${{ matrix.go }} - steps: - - uses: actions/checkout@v3 - - - uses: actions/cache@v3 - 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 ./... + with: + go-version: ${{ matrix.go }} 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@v3 - 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.19' # auto-update/latest-go-version - - - 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@v3 - with: - name: codecov-umbrella - fail_ci_if_error: true - flags: wasm + uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master + with: + go-version: '1.19' # auto-update/latest-go-version diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 50cc17a..29c5cb8 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -12,31 +12,12 @@ name: Go mod tidy on: pull_request: - branches: - - master push: branches: - master -permissions: - contents: read - jobs: - Check: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - - name: Setup Go - uses: actions/setup-go@v3 - with: - go-version: '1.19' # auto-update/latest-go-version - - name: check - run: | - go mod download - go mod tidy - if ! git diff --exit-code - then - echo "Not go mod tidied" - exit 1 - fi + tidy: + uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master + with: + go-version: '1.19' # auto-update/latest-go-version diff --git a/AUTHORS.txt b/AUTHORS.txt index b5c111f..fa22766 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -2,7 +2,7 @@ # 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 +# see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting Aaron Boushley adwpc aler9 <46489434+aler9@users.noreply.github.com> From cf0f4079f8a0ed6599d33298112356020f5f1e01 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Mon, 23 Jan 2023 08:17:37 +0000 Subject: [PATCH 006/102] Update CI configs to v0.10.3 Update lint scripts and CI configs. --- .golangci.yml | 3 --- AUTHORS.txt | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d7a88ec..48696f1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,7 +18,6 @@ linters: - 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()) @@ -60,7 +59,6 @@ linters: - 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 @@ -69,7 +67,6 @@ linters: - 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: diff --git a/AUTHORS.txt b/AUTHORS.txt index fa22766..cbd302c 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -36,3 +36,6 @@ Simone Gotti Tarrence van As wangzixiang Woodrow Douglass + +# List of contributors not appearing in Git history + From 9f0d1455a4951040c5dbba690c7214440248810c Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sat, 7 Jan 2023 17:11:11 +0100 Subject: [PATCH 007/102] Fix multiple crashes when using VP9 depacketizer Multiple optional fields of the VP9 RTP header are read regardless of the buffer size. This can easily lead to multiple crashes. This PR add the necessary checks in order to avoid crashes. --- codecs/vp9_packet.go | 79 ++++++++++++++++++++++++++------------- codecs/vp9_packet_test.go | 16 ++++++++ 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 917e630..533a3b1 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -207,12 +207,15 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { // Picture ID: // -// +-+-+-+-+-+-+-+-+ +// +-+-+-+-+-+-+-+-+ +// // I: |M| PICTURE ID | M:0 => picture id is 7 bits. -// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 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 @@ -245,10 +248,11 @@ func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { // 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 @@ -269,12 +273,13 @@ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { // Layer indices (non-flexible mode): // -// +-+-+-+-+-+-+-+-+ +// +-+-+-+-+-+-+-+-+ +// // L: | T |U| S |D| -// +-+-+-+-+-+-+-+-+ -// | TL0PICIDX | -// +-+-+-+-+-+-+-+-+ // +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -287,11 +292,12 @@ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, // Reference indices: // -// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// +-+-+-+-+-+-+-+-+ 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. // +// +-+-+-+-+-+-+-+-+ 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 { @@ -313,24 +319,30 @@ func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { // Scalability structure (SS): // -// +-+-+-+-+-+-+-+-+ +// +-+-+-+-+-+-+-+-+ +// // V: | N_S |Y|G|-|-|-| -// +-+-+-+-+-+-+-+-+ -| +// +// +-+-+-+-+-+-+-+-+ -| +// // Y: | WIDTH | (OPTIONAL) . -// + + . -// | | (OPTIONAL) . -// +-+-+-+-+-+-+-+-+ . N_S + 1 times -// | HEIGHT | (OPTIONAL) . -// + + . -// | | (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 . -// +-+-+-+-+-+-+-+-+ -| -| // +// +-+-+-+-+-+-+-+-+ -| . 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 @@ -348,6 +360,10 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { p.Width = make([]uint16, NS) p.Height = make([]uint16, NS) for i := 0; i < int(NS); i++ { + if len(packet) <= (pos + 3) { + return pos, errShortPacket + } + p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) @@ -356,17 +372,30 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { } if p.G { + if len(packet) <= pos { + return pos, errShortPacket + } + p.NG = packet[pos] pos++ } for i := 0; i < int(p.NG); i++ { + if len(packet) <= pos { + return pos, errShortPacket + } + p.PGTID = append(p.PGTID, packet[pos]>>5) p.PGU = append(p.PGU, packet[pos]&0x10 != 0) R := (packet[pos] >> 2) & 0x3 pos++ p.PGPDiff = append(p.PGPDiff, []uint8{}) + + if len(packet) <= (pos + int(R) - 1) { + return pos, errShortPacket + } + for j := 0; j < int(R); j++ { p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos]) pos++ diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index 65861c6..f2c5e2d 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -167,6 +167,22 @@ func TestVP9Packet_Unmarshal(t *testing.T) { Payload: []byte{}, }, }, + "ScalabilityMissingWidth": { + b: []byte("200"), + err: errShortPacket, + }, + "ScalabilityMissingNG": { + b: []byte("b00200000000"), + err: errShortPacket, + }, + "ScalabilityMissingTemporalLayerIDs": { + b: []byte("20B0"), + err: errShortPacket, + }, + "ScalabilityMissingReferenceIndices": { + b: []byte("20B007"), + err: errShortPacket, + }, } for name, c := range cases { c := c From 648b9ad7ff1d13d18a2fb3cd5980d758b8fca3c2 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 12 Feb 2023 21:08:48 +0100 Subject: [PATCH 008/102] Fix compatibility with gofmt This prevents gofmt -w -s from messing up existing code. Text with multiple spaces is now wrapped in long comments (/* */), that are not affected by gofmt. --- codecs/av1_packet.go | 12 +-- codecs/h265_packet.go | 184 ++++++++++++++++++++++-------------------- codecs/vp9_packet.go | 100 ++++++++++------------- 3 files changed, 145 insertions(+), 151 deletions(-) diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 7aa3a55..56fc4c2 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -69,12 +69,12 @@ func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) { } // AV1Packet represents a depacketized AV1 RTP Packet -// -// 0 1 2 3 4 5 6 7 -// +-+-+-+-+-+-+-+-+ -// |Z|Y| W |N|-|-|-| -// +-+-+-+-+-+-+-+-+ -// +/* +* 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 diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 6f0490d..58a6c7d 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -32,11 +32,13 @@ const ( // 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 | -// +-------------+-----------------+ +/* +* +---------------+---------------+ +* |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 { @@ -95,18 +97,19 @@ func (h H265NALUHeader) IsPACIPacket() bool { // // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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. @@ -184,19 +187,19 @@ func (p *H265SingleNALUnitPacket) isH265Packet() {} // // 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 | -// | | -// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | : -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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 @@ -222,18 +225,18 @@ func (u H265AggregationUnitFirst) NalUnit() []byte { } // 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 | -// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | : -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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 @@ -259,18 +262,19 @@ func (u H265AggregationUnit) NalUnit() []byte { } // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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 @@ -395,11 +399,13 @@ const ( ) // H265FragmentationUnitHeader is a H265 FU Header -// +---------------+ -// |0|1|2|3|4|5|6|7| -// +-+-+-+-+-+-+-+-+ -// |S|E| FuType | -// +---------------+ +/* +* +---------------+ +* |0|1|2|3|4|5|6|7| +* +-+-+-+-+-+-+-+-+ +* |S|E| FuType | +* +---------------+ +**/ type H265FragmentationUnitHeader uint8 // S represents the start of a fragmented NAL unit. @@ -421,20 +427,20 @@ func (h H265FragmentationUnitHeader) FuType() uint8 { } // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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. @@ -521,22 +527,22 @@ func (p *H265FragmentationUnitPacket) isH265Packet() {} // // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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. diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 533a3b1..07db705 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -206,16 +206,13 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { } // Picture ID: -// -// +-+-+-+-+-+-+-+-+ -// -// I: |M| PICTURE ID | M:0 => picture id is 7 bits. -// -// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. -// -// M: | EXTENDED PID | -// -// +-+-+-+-+-+-+-+-+ +/* +* +-+-+-+-+-+-+-+-+ +* 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 @@ -247,12 +244,11 @@ func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { } // Layer indices (flexible mode): -// -// +-+-+-+-+-+-+-+-+ -// -// L: | T |U| S |D| -// -// +-+-+-+-+-+-+-+-+ +/* +* +-+-+-+-+-+-+-+-+ +* L: | T |U| S |D| +* +-+-+-+-+-+-+-+-+ +**/ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -272,14 +268,13 @@ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { } // Layer indices (non-flexible mode): -// -// +-+-+-+-+-+-+-+-+ -// -// L: | T |U| S |D| -// -// +-+-+-+-+-+-+-+-+ -// | TL0PICIDX | -// +-+-+-+-+-+-+-+-+ +/* +* +-+-+-+-+-+-+-+-+ +* L: | T |U| S |D| +* +-+-+-+-+-+-+-+-+ +* | TL0PICIDX | +* +-+-+-+-+-+-+-+-+ +**/ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -291,13 +286,12 @@ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, } // 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. +/* +* +-+-+-+-+-+-+-+-+ 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 { @@ -318,31 +312,25 @@ func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { } // 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 . -// +-+-+-+-+-+-+-+-+ -| -| +/* +* +-+-+-+-+-+-+-+-+ +* 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 From 0e2d3fee736375987792d023c7851e526651b9fb Mon Sep 17 00:00:00 2001 From: kawaway Date: Thu, 2 Feb 2023 17:41:03 +0900 Subject: [PATCH 009/102] Fix errShortPacket with H264 EOS NALUs Remove checks since payload size can be less than 2. Only FU-A explicitly accesses the second byte, which is not affected because a separate boundary value check is performed. --- codecs/h264_packet.go | 6 ++---- codecs/h264_packet_test.go | 12 ++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 11a82fe..3eda4de 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -211,10 +211,8 @@ func (p *H264Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bo // 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)) + if len(payload) == 0 { + return nil, fmt.Errorf("%w: %d <=0", errShortPacket, len(payload)) } // NALU Types diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index 47826d7..fd77a3e 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -115,8 +115,16 @@ func TestH264Packet_Unmarshal(t *testing.T) { 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{}); err == nil { + t.Fatal("Unmarshal did not fail on []byte{}") + } + + if _, err := pkt.Unmarshal([]byte{0xFC}); err == nil { + t.Fatal("Unmarshal accepted a FU-A packet that is too small for a payload and header") + } + + if _, err := pkt.Unmarshal([]byte{0x0A}); err != nil { + t.Fatal("Unmarshaling end of sequence(NALU Type : 10) should succeed") } if _, err := pkt.Unmarshal([]byte{0xFF, 0x00, 0x00}); err == nil { From c3fa0c341d91030a33a56f00a7c7fa55adb9bcdb Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sat, 11 Mar 2023 13:03:48 +0100 Subject: [PATCH 010/102] Cleanup common sections in README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ce04599..ad77866 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Sourcegraph Widget Slack Widget
- Build Status - GoDoc + GitHub Workflow Status + Go Reference Coverage Status Go Report Card License: MIT @@ -21,14 +21,15 @@ 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). +Pion has an active community on the [Slack](https://pion.ly/slack). -We are always looking to support **your projects**. Please reach out if you have something to build! +Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. +We are always looking to support **your projects**. Please reach out if you have something to build! If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) ### Contributing -Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt) ### License -MIT License - see [LICENSE](LICENSE) for full text +MIT License - see [LICENSE](LICENSE) for full text \ No newline at end of file From 61a078e7128453a812616d14ad8939c88ed0d02d Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 13 Mar 2023 09:46:01 +0100 Subject: [PATCH 011/102] Update AUTHORS.txt --- AUTHORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index cbd302c..38685a9 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -19,6 +19,7 @@ Haiyang Wang Hugo Arregui John Bradley Juliusz Chroboczek +kawaway Kazuyuki Honda Kevin Wang Luke Curley @@ -33,6 +34,7 @@ Sean DuBois Sean DuBois Sean DuBois Simone Gotti +Steffen Vogel Tarrence van As wangzixiang Woodrow Douglass From 30f0c25021caf62e25303198820219027da42603 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 5 Apr 2023 08:42:43 +0200 Subject: [PATCH 012/102] Harmonize sections in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad77866..37b7cc0 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ We are always looking to support **your projects**. Please reach out if you have 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: [AUTHORS.txt](./AUTHORS.txt) +Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt) ### License MIT License - see [LICENSE](LICENSE) for full text \ No newline at end of file From df40cb1645a3bffac64f6953efd8fdac9ce4a015 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 18 Apr 2023 10:37:16 +0000 Subject: [PATCH 013/102] Update CI configs to v0.10.7 Update lint scripts and CI configs. --- .github/.gitignore | 3 +++ .github/fetch-scripts.sh | 2 ++ .github/install-hooks.sh | 2 ++ .github/workflows/codeql-analysis.yml | 2 ++ .github/workflows/generate-authors.yml | 2 ++ .github/workflows/lint.yaml | 2 ++ .github/workflows/release.yml | 4 +++- .github/workflows/renovate-go-sum-fix.yaml | 2 ++ .github/workflows/reuse.yml | 22 ++++++++++++++++++ .github/workflows/test.yaml | 8 ++++--- .github/workflows/tidy-check.yaml | 4 +++- .gitignore | 3 +++ .golangci.yml | 26 ++++++++++++++++++++-- .goreleaser.yml | 3 +++ .reuse/dep5 | 7 ++++++ codecov.yml | 2 ++ 16 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/reuse.yml create mode 100644 .reuse/dep5 diff --git a/.github/.gitignore b/.github/.gitignore index 44d7f86..c3421a1 100644 --- a/.github/.gitignore +++ b/.github/.gitignore @@ -1 +1,4 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + .goassets diff --git a/.github/fetch-scripts.sh b/.github/fetch-scripts.sh index d7e848b..f333841 100755 --- a/.github/fetch-scripts.sh +++ b/.github/fetch-scripts.sh @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT set -eu diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh index 57b7798..8aa34be 100755 --- a/.github/install-hooks.sh +++ b/.github/install-hooks.sh @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT SCRIPT_PATH="$(realpath "$(dirname "$0")")" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bb44904..ea9b825 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: CodeQL diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index 9fc78bc..ec7446c 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Generate Authors diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index e72c716..5dd3a99 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Lint on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a07c292..01227e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Release on: @@ -19,4 +21,4 @@ jobs: release: uses: pion/.goassets/.github/workflows/release.reusable.yml@master with: - go-version: '1.19' # auto-update/latest-go-version + go-version: '1.20' # auto-update/latest-go-version diff --git a/.github/workflows/renovate-go-sum-fix.yaml b/.github/workflows/renovate-go-sum-fix.yaml index 5a9af36..b7bb1b4 100644 --- a/.github/workflows/renovate-go-sum-fix.yaml +++ b/.github/workflows/renovate-go-sum-fix.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Fix go.sum on: diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml new file mode 100644 index 0000000..8633a12 --- /dev/null +++ b/.github/workflows/reuse.yml @@ -0,0 +1,22 @@ +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +name: REUSE Compliance Check + +on: + push: + pull_request: + +jobs: + lint: + uses: pion/.goassets/.github/workflows/reuse.reusable.yml@master diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 834a158..31aada4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Test on: @@ -21,7 +23,7 @@ jobs: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: - go: ['1.19', '1.18'] # auto-update/supported-go-version-list + go: ['1.20', '1.19'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -30,7 +32,7 @@ jobs: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: - go: ['1.19', '1.18'] # auto-update/supported-go-version-list + go: ['1.20', '1.19'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -38,4 +40,4 @@ jobs: test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: - go-version: '1.19' # auto-update/latest-go-version + go-version: '1.20' # auto-update/latest-go-version diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 29c5cb8..4d346d4 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Go mod tidy on: @@ -20,4 +22,4 @@ jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: - go-version: '1.19' # auto-update/latest-go-version + go-version: '1.20' # auto-update/latest-go-version diff --git a/.gitignore b/.gitignore index f977e74..6e2f206 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + ### JetBrains IDE ### ##################### .idea/ diff --git a/.golangci.yml b/.golangci.yml index 48696f1..826254a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + linters-settings: govet: check-shadowing: true @@ -10,7 +13,15 @@ linters-settings: modules: - github.com/pkg/errors: recommendations: - - errors + - errors + forbidigo: + forbid: + - Fatal(f|ln)?$ + - ^fmt.Print(f|ln)?$ + - ^log.Print(f|ln)?$ + - ^os.Exit$ + - ^panic$ + - ^print(ln)?$ linters: enable: @@ -29,6 +40,7 @@ linters: - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. - exhaustive # check exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # Forbids identifiers - forcetypeassert # finds forced type assertions - gci # Gci control golang package import order and make it always deterministic. - gochecknoglobals # Checks that no globals are present in Go code @@ -73,7 +85,6 @@ linters: - 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 @@ -105,12 +116,23 @@ issues: - path: _test\.go linters: - gocognit + - forbidigo # Allow complex main function in examples - path: examples text: "of func `main` is high" linters: - gocognit + + # Allow forbidden identifiers in examples + - path: examples + linters: + - forbidigo + + # Allow forbidden identifiers in CLI commands + - path: cmd + linters: + - forbidigo run: skip-dirs-use-default: false diff --git a/.goreleaser.yml b/.goreleaser.yml index 2caa5fb..30093e9 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,2 +1,5 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + builds: - skip: true diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..ccc710a --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,7 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Pion +Source: https://github.com/pion/ + +Files: README.md AUTHORS.txt renovate.json +Copyright: 2023 The Pion community +License: MIT diff --git a/codecov.yml b/codecov.yml index 085200a..263e4d4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,6 +3,8 @@ # # It is automatically copied from https://github.com/pion/.goassets repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT coverage: status: From 1a23b62560763ac23b3f31966688bc0b38db09d8 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 5 May 2023 14:41:20 +0200 Subject: [PATCH 014/102] Make repo REUSE compliant --- .reuse/dep5 | 6 +++++- LICENSE | 22 +--------------------- LICENSES/MIT.txt | 9 +++++++++ abscapturetimeextension.go | 3 +++ abscapturetimeextension_test.go | 3 +++ abssendtimeextension.go | 3 +++ abssendtimeextension_test.go | 3 +++ audiolevelextension.go | 3 +++ audiolevelextension_test.go | 3 +++ codecs/av1_packet.go | 3 +++ codecs/av1_packet_test.go | 3 +++ codecs/codecs.go | 3 +++ codecs/common.go | 3 +++ codecs/common_test.go | 3 +++ codecs/error.go | 3 +++ codecs/g711_packet.go | 3 +++ codecs/g711_packet_test.go | 3 +++ codecs/g722_packet.go | 3 +++ codecs/g722_packet_test.go | 3 +++ codecs/h264_packet.go | 3 +++ codecs/h264_packet_test.go | 3 +++ codecs/h265_packet.go | 3 +++ codecs/h265_packet_test.go | 3 +++ codecs/opus_packet.go | 3 +++ codecs/opus_packet_test.go | 3 +++ codecs/vp8_packet.go | 3 +++ codecs/vp8_packet_test.go | 3 +++ codecs/vp9_packet.go | 3 +++ codecs/vp9_packet_test.go | 3 +++ depacketizer.go | 3 +++ error.go | 3 +++ header_extension.go | 3 +++ header_extension_test.go | 3 +++ packet.go | 3 +++ packet_test.go | 3 +++ packetizer.go | 3 +++ packetizer_test.go | 3 +++ partitionheadchecker.go | 3 +++ pkg/frame/av1.go | 3 +++ pkg/frame/av1_test.go | 3 +++ pkg/obu/leb128.go | 3 +++ pkg/obu/leb128_test.go | 3 +++ playoutdelayextension.go | 3 +++ playoutdelayextension_test.go | 3 +++ rand.go | 3 +++ rtp.go | 3 +++ sequencer.go | 3 +++ transportccextension.go | 3 +++ transportccextension_test.go | 3 +++ 49 files changed, 153 insertions(+), 22 deletions(-) mode change 100644 => 120000 LICENSE create mode 100644 LICENSES/MIT.txt diff --git a/.reuse/dep5 b/.reuse/dep5 index ccc710a..afb63d7 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -2,6 +2,10 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ -Files: README.md AUTHORS.txt renovate.json +Files: README.md **/README.md AUTHORS.txt renovate.json go.mod go.sum Copyright: 2023 The Pion community License: MIT + +Files: testdata/fuzz/* **/testdata/fuzz/* api/*.txt +Copyright: 2023 The Pion community +License: CC0-1.0 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index ab60297..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..31ff787 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +LICENSES/MIT.txt \ No newline at end of file diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +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. diff --git a/abscapturetimeextension.go b/abscapturetimeextension.go index f3005e9..56b783d 100644 --- a/abscapturetimeextension.go +++ b/abscapturetimeextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/abscapturetimeextension_test.go b/abscapturetimeextension_test.go index d5b9062..15c434a 100644 --- a/abscapturetimeextension_test.go +++ b/abscapturetimeextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/abssendtimeextension.go b/abssendtimeextension.go index f0c6de3..fff38e9 100644 --- a/abssendtimeextension.go +++ b/abssendtimeextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/abssendtimeextension_test.go b/abssendtimeextension_test.go index 16243fd..16834d9 100644 --- a/abssendtimeextension_test.go +++ b/abssendtimeextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/audiolevelextension.go b/audiolevelextension.go index ca44f28..f180c8d 100644 --- a/audiolevelextension.go +++ b/audiolevelextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/audiolevelextension_test.go b/audiolevelextension_test.go index b1ad89f..9c651a0 100644 --- a/audiolevelextension_test.go +++ b/audiolevelextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 56fc4c2..b074ab5 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 27eeb47..bb3ce76 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/codecs.go b/codecs/codecs.go index 0e07897..cd1c891 100644 --- a/codecs/codecs.go +++ b/codecs/codecs.go @@ -1,2 +1,5 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package codecs implements codec specific RTP payloader/depayloaders package codecs diff --git a/codecs/common.go b/codecs/common.go index af5632a..8746550 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs func min(a, b int) int { diff --git a/codecs/common_test.go b/codecs/common_test.go index 5cbbd3c..3487276 100644 --- a/codecs/common_test.go +++ b/codecs/common_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/error.go b/codecs/error.go index 7f72e7b..2083ef4 100644 --- a/codecs/error.go +++ b/codecs/error.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import "errors" diff --git a/codecs/g711_packet.go b/codecs/g711_packet.go index 7ab68b2..4afda55 100644 --- a/codecs/g711_packet.go +++ b/codecs/g711_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // G711Payloader payloads G711 packets diff --git a/codecs/g711_packet_test.go b/codecs/g711_packet_test.go index 36ee729..75fa6a8 100644 --- a/codecs/g711_packet_test.go +++ b/codecs/g711_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs //nolint:dupl import ( diff --git a/codecs/g722_packet.go b/codecs/g722_packet.go index 13e17b6..ae6672d 100644 --- a/codecs/g722_packet.go +++ b/codecs/g722_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // G722Payloader payloads G722 packets diff --git a/codecs/g722_packet_test.go b/codecs/g722_packet_test.go index cc75e6f..f15f81c 100644 --- a/codecs/g722_packet_test.go +++ b/codecs/g722_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs //nolint:dupl import ( diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 3eda4de..5dc3a2e 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index fd77a3e..8e3b221 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 58a6c7d..2a194fd 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 2172abe..1c5f96a 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index cd5ea33..b7cf3ad 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // OpusPayloader payloads Opus packets diff --git a/codecs/opus_packet_test.go b/codecs/opus_packet_test.go index e68ff57..665d17b 100644 --- a/codecs/opus_packet_test.go +++ b/codecs/opus_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index b831dd3..e2d1e24 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // VP8Payloader payloads VP8 packets diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index e7a9610..bb09ef1 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 07db705..093cde5 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index f2c5e2d..97e176b 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/depacketizer.go b/depacketizer.go index c66d2e3..0439a53 100644 --- a/depacketizer.go +++ b/depacketizer.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload diff --git a/error.go b/error.go index 5458c6f..4df5d2a 100644 --- a/error.go +++ b/error.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/header_extension.go b/header_extension.go index d010bb8..fe54215 100644 --- a/header_extension.go +++ b/header_extension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/header_extension_test.go b/header_extension_test.go index 8aba946..bac3bd0 100644 --- a/header_extension_test.go +++ b/header_extension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packet.go b/packet.go index 6528b65..ef4e5d8 100644 --- a/packet.go +++ b/packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packet_test.go b/packet_test.go index 6067c28..3044c0d 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packetizer.go b/packetizer.go index a4be143..c9e3a93 100644 --- a/packetizer.go +++ b/packetizer.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packetizer_test.go b/packetizer_test.go index 6beff6a..c7e91c4 100644 --- a/packetizer_test.go +++ b/packetizer_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/partitionheadchecker.go b/partitionheadchecker.go index 6ec2a76..6f05aa5 100644 --- a/partitionheadchecker.go +++ b/partitionheadchecker.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp // PartitionHeadChecker is the interface that checks whether the packet is keyframe or not diff --git a/pkg/frame/av1.go b/pkg/frame/av1.go index 46fb831..1a13a5a 100644 --- a/pkg/frame/av1.go +++ b/pkg/frame/av1.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package frame provides code to construct complete media frames from packetized media package frame diff --git a/pkg/frame/av1_test.go b/pkg/frame/av1_test.go index 11538cf..3f9d9da 100644 --- a/pkg/frame/av1_test.go +++ b/pkg/frame/av1_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package frame import ( diff --git a/pkg/obu/leb128.go b/pkg/obu/leb128.go index 988a8f4..f0734f0 100644 --- a/pkg/obu/leb128.go +++ b/pkg/obu/leb128.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package obu implements tools for working with the "Open Bitstream Unit" package obu diff --git a/pkg/obu/leb128_test.go b/pkg/obu/leb128_test.go index 42cf777..f92fff4 100644 --- a/pkg/obu/leb128_test.go +++ b/pkg/obu/leb128_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package obu import ( diff --git a/playoutdelayextension.go b/playoutdelayextension.go index e508503..3882731 100644 --- a/playoutdelayextension.go +++ b/playoutdelayextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/playoutdelayextension_test.go b/playoutdelayextension_test.go index 6757b14..8810466 100644 --- a/playoutdelayextension_test.go +++ b/playoutdelayextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/rand.go b/rand.go index ee85523..3ddddd1 100644 --- a/rand.go +++ b/rand.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/rtp.go b/rtp.go index b66b2e4..5487232 100644 --- a/rtp.go +++ b/rtp.go @@ -1,2 +1,5 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package rtp provides RTP packetizer and depacketizer package rtp diff --git a/sequencer.go b/sequencer.go index 2b4a507..8ad2cfd 100644 --- a/sequencer.go +++ b/sequencer.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/transportccextension.go b/transportccextension.go index 236af05..c2a998c 100644 --- a/transportccextension.go +++ b/transportccextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/transportccextension_test.go b/transportccextension_test.go index 5eb6967..5a06f2c 100644 --- a/transportccextension_test.go +++ b/transportccextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( From 62977fe426416735c85ec06dade5083da8766e51 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 5 May 2023 14:43:38 +0200 Subject: [PATCH 015/102] Fix golangci-lint warnings --- codecs/av1_packet_test.go | 3 +-- codecs/common.go | 6 +++--- codecs/opus_packet.go | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index bb3ce76..3761c3a 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -5,7 +5,6 @@ package codecs import ( "errors" - "fmt" "reflect" "testing" @@ -52,7 +51,7 @@ func TestAV1_Unmarshal_Error(t *testing.T) { 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)) + t.Fatalf("Expected error(%s) but got (%s)", test.expectedError, err) } } } diff --git a/codecs/common.go b/codecs/common.go index 8746550..5da8aaf 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -13,17 +13,17 @@ func min(a, b int) int { // audioDepacketizer is a mixin for audio codec depacketizers type audioDepacketizer struct{} -func (d *audioDepacketizer) IsPartitionTail(marker bool, payload []byte) bool { +func (d *audioDepacketizer) IsPartitionTail(_ bool, _ []byte) bool { return true } -func (d *audioDepacketizer) IsPartitionHead(payload []byte) bool { +func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool { return true } // videoDepacketizer is a mixin for video codec depacketizers type videoDepacketizer struct{} -func (d *videoDepacketizer) IsPartitionTail(marker bool, payload []byte) bool { +func (d *videoDepacketizer) IsPartitionTail(marker bool, _ []byte) bool { return marker } diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index b7cf3ad..d8d441e 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -7,7 +7,7 @@ package codecs type OpusPayloader struct{} // Payload fragments an Opus packet across one or more byte arrays -func (p *OpusPayloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *OpusPayloader) Payload(_ uint16, payload []byte) [][]byte { if payload == nil { return [][]byte{} } From c5a4d4fcd644be2ff85132e019a77bd918a78aad Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Thu, 18 May 2023 19:15:48 +0000 Subject: [PATCH 016/102] Update CI configs to v0.10.8 Update lint scripts and CI configs. --- .golangci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 826254a..4e3eddf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,9 +16,8 @@ linters-settings: - errors forbidigo: forbid: - - Fatal(f|ln)?$ - ^fmt.Print(f|ln)?$ - - ^log.Print(f|ln)?$ + - ^log.(Panic|Fatal|Print)(f|ln)?$ - ^os.Exit$ - ^panic$ - ^print(ln)?$ From c5ca73a6fa1244ceb419fc021a39f41e6c44c47b Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Fri, 19 May 2023 07:46:35 +0000 Subject: [PATCH 017/102] Update CI configs to v0.10.9 Update lint scripts and CI configs. --- .reuse/dep5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index afb63d7..c8b3dfa 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ -Files: README.md **/README.md AUTHORS.txt renovate.json go.mod go.sum +Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum .eslintrc.json package.json examples/examples.json Copyright: 2023 The Pion community License: MIT From 6e39a73a41e3716d2f1defe4ce3c7d027c2f154a Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 12 Jun 2023 21:37:20 +0200 Subject: [PATCH 018/102] Revert to 4e87540 (last commit before v2) --- .github/.gitignore | 4 - .github/assert-contributors.sh | 61 ++ .github/fetch-scripts.sh | 31 - .github/hooks/commit-msg.sh | 11 + .github/hooks/pre-commit.sh | 12 + .github/hooks/pre-push.sh | 13 + .github/install-hooks.sh | 14 +- .github/lint-commit-message.sh | 64 ++ .../lint-disallowed-functions-in-library.sh | 48 + .github/lint-filename.sh | 24 + .github/workflows/codeql-analysis.yml | 28 - .github/workflows/generate-authors.yml | 23 - .github/workflows/lint.yaml | 55 +- .github/workflows/release.yml | 24 - .github/workflows/renovate-go-mod-fix.yaml | 33 + .github/workflows/renovate-go-sum-fix.yaml | 24 - .github/workflows/reuse.yml | 22 - .github/workflows/test.yaml | 148 ++- .github/workflows/tidy-check.yaml | 24 +- .gitignore | 4 - .golangci.yml | 60 +- .goreleaser.yml | 5 - .reuse/dep5 | 11 - AUTHORS.txt | 43 - LICENSE | 22 +- LICENSES/MIT.txt | 9 - README.md | 35 +- abscapturetimeextension.go | 90 -- abscapturetimeextension_test.go | 46 - abssendtimeextension.go | 5 +- abssendtimeextension_test.go | 3 - audiolevelextension.go | 5 +- audiolevelextension_test.go | 3 - codecov.yml | 2 - codecs/av1_packet.go | 161 ---- codecs/av1_packet_test.go | 249 ----- codecs/codecs.go | 3 - codecs/common.go | 21 - codecs/common_test.go | 3 - codecs/error.go | 6 - codecs/g711_packet.go | 9 +- codecs/g711_packet_test.go | 18 +- codecs/g722_packet.go | 9 +- codecs/g722_packet_test.go | 18 +- codecs/h264_packet.go | 75 +- codecs/h264_packet_test.go | 58 +- codecs/h265_packet.go | 828 ---------------- codecs/h265_packet_test.go | 880 ------------------ codecs/opus_packet.go | 22 +- codecs/opus_packet_test.go | 20 +- codecs/vp8_packet.go | 111 +-- codecs/vp8_packet_test.go | 164 +--- codecs/vp9_packet.go | 135 ++- codecs/vp9_packet_test.go | 36 +- depacketizer.go | 11 +- error.go | 3 - go.mod | 2 +- header_extension.go | 353 ------- header_extension_test.go | 305 ------ packet.go | 204 ++-- packet_test.go | 306 +----- packetizer.go | 12 +- packetizer_test.go | 95 +- partitionheadchecker.go | 3 - pkg/frame/av1.go | 47 - pkg/frame/av1_test.go | 86 -- pkg/obu/leb128.go | 69 -- pkg/obu/leb128_test.go | 42 - playoutdelayextension.go | 50 - playoutdelayextension_test.go | 73 -- rand.go | 3 - renovate.json | 17 +- rtp.go | 3 - sequencer.go | 3 - transportccextension.go | 5 +- transportccextension_test.go | 3 - 76 files changed, 910 insertions(+), 4617 deletions(-) delete mode 100644 .github/.gitignore create mode 100755 .github/assert-contributors.sh delete mode 100755 .github/fetch-scripts.sh create mode 100755 .github/hooks/commit-msg.sh create mode 100755 .github/hooks/pre-commit.sh create mode 100755 .github/hooks/pre-push.sh create mode 100755 .github/lint-commit-message.sh create mode 100755 .github/lint-disallowed-functions-in-library.sh create mode 100755 .github/lint-filename.sh delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 .github/workflows/generate-authors.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/renovate-go-mod-fix.yaml delete mode 100644 .github/workflows/renovate-go-sum-fix.yaml delete mode 100644 .github/workflows/reuse.yml delete mode 100644 .goreleaser.yml delete mode 100644 .reuse/dep5 delete mode 100644 AUTHORS.txt mode change 120000 => 100644 LICENSE delete mode 100644 LICENSES/MIT.txt delete mode 100644 abscapturetimeextension.go delete mode 100644 abscapturetimeextension_test.go delete mode 100644 codecs/av1_packet.go delete mode 100644 codecs/av1_packet_test.go delete mode 100644 codecs/h265_packet.go delete mode 100644 codecs/h265_packet_test.go delete mode 100644 header_extension.go delete mode 100644 header_extension_test.go delete mode 100644 pkg/frame/av1.go delete mode 100644 pkg/frame/av1_test.go delete mode 100644 pkg/obu/leb128.go delete mode 100644 pkg/obu/leb128_test.go delete mode 100644 playoutdelayextension.go delete mode 100644 playoutdelayextension_test.go diff --git a/.github/.gitignore b/.github/.gitignore deleted file mode 100644 index c3421a1..0000000 --- a/.github/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -.goassets diff --git a/.github/assert-contributors.sh b/.github/assert-contributors.sh new file mode 100755 index 0000000..12e6afe --- /dev/null +++ b/.github/assert-contributors.sh @@ -0,0 +1,61 @@ +#!/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 ) + +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') +# 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 + +MISSING_CONTRIBUTORS=() + +shouldBeIncluded () { + for i in "${EXCLUDED_CONTRIBUTORS[@]}" + do + if [ "$i" == "$1" ] ; then + return 1 + fi + done + return 0 +} + + +IFS=$'\n' #Only split on newline +for contributor in $(git log --format='%aN' | sort -u) +do + if shouldBeIncluded $contributor; then + if ! grep -q "$contributor" "$SCRIPT_PATH/../README.md"; then + MISSING_CONTRIBUTORS+=("$contributor") + fi + fi +done +unset IFS + +if [ ${#MISSING_CONTRIBUTORS[@]} -ne 0 ]; then + echo "Please add the following contributors to the README" + for i in "${MISSING_CONTRIBUTORS[@]}" + do + echo "$i" + done + exit 1 +fi diff --git a/.github/fetch-scripts.sh b/.github/fetch-scripts.sh deleted file mode 100755 index f333841..0000000 --- a/.github/fetch-scripts.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -set -eu - -SCRIPT_PATH="$(realpath "$(dirname "$0")")" -GOASSETS_PATH="${SCRIPT_PATH}/.goassets" - -GOASSETS_REF=${GOASSETS_REF:-master} - -if [ -d "${GOASSETS_PATH}" ]; then - if ! git -C "${GOASSETS_PATH}" diff --exit-code; then - echo "${GOASSETS_PATH} has uncommitted changes" >&2 - exit 1 - fi - git -C "${GOASSETS_PATH}" fetch origin - git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} - git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} -else - git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" -fi diff --git a/.github/hooks/commit-msg.sh b/.github/hooks/commit-msg.sh new file mode 100755 index 0000000..8213dc2 --- /dev/null +++ b/.github/hooks/commit-msg.sh @@ -0,0 +1,11 @@ +#!/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 diff --git a/.github/hooks/pre-commit.sh b/.github/hooks/pre-commit.sh new file mode 100755 index 0000000..cc318d7 --- /dev/null +++ b/.github/hooks/pre-commit.sh @@ -0,0 +1,12 @@ +#!/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 diff --git a/.github/hooks/pre-push.sh b/.github/hooks/pre-push.sh new file mode 100755 index 0000000..7cb2365 --- /dev/null +++ b/.github/hooks/pre-push.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# +# DO NOT EDIT THIS FILE DIRECTLY +# +# It is automatically copied from https://github.com/pion/.goassets repository. +# + +set -e + +.github/assert-contributors.sh + +exit 0 diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh index 8aa34be..73d20a4 100755 --- a/.github/install-hooks.sh +++ b/.github/install-hooks.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # DO NOT EDIT THIS FILE @@ -8,13 +8,9 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT -SCRIPT_PATH="$(realpath "$(dirname "$0")")" +SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -. ${SCRIPT_PATH}/fetch-scripts.sh - -cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" -cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" -cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" +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" diff --git a/.github/lint-commit-message.sh b/.github/lint-commit-message.sh new file mode 100755 index 0000000..010a332 --- /dev/null +++ b/.github/lint-commit-message.sh @@ -0,0 +1,64 @@ +#!/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 diff --git a/.github/lint-disallowed-functions-in-library.sh b/.github/lint-disallowed-functions-in-library.sh new file mode 100755 index 0000000..8ce5d09 --- /dev/null +++ b/.github/lint-disallowed-functions-in-library.sh @@ -0,0 +1,48 @@ +#!/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 diff --git a/.github/lint-filename.sh b/.github/lint-filename.sh new file mode 100755 index 0000000..81b3f14 --- /dev/null +++ b/.github/lint-filename.sh @@ -0,0 +1,24 @@ +#!/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 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index ea9b825..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,28 +0,0 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -name: CodeQL - -on: - workflow_dispatch: - schedule: - - cron: '23 5 * * 0' - pull_request: - branches: - - master - paths: - - '**.go' - -jobs: - analyze: - uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml deleted file mode 100644 index ec7446c..0000000 --- a/.github/workflows/generate-authors.yml +++ /dev/null @@ -1,23 +0,0 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -name: Generate Authors - -on: - pull_request: - -jobs: - generate: - uses: pion/.goassets/.github/workflows/generate-authors.reusable.yml@master - secrets: - token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 5dd3a99..8824c34 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,20 +1,43 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - name: Lint on: pull_request: - + types: + - opened + - edited + - synchronize jobs: - lint: - uses: pion/.goassets/.github/workflows/lint.reusable.yml@master + lint-commit-message: + name: Metadata + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Commit Message + run: .github/lint-commit-message.sh + + - name: File names + run: .github/lint-filename.sh + + - name: Contributors + run: .github/assert-contributors.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@v2 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: v1.31 + args: $GOLANGCI_LINT_EXRA_ARGS diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 01227e2..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,24 +0,0 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -name: Release -on: - push: - tags: - - 'v*' - -jobs: - release: - uses: pion/.goassets/.github/workflows/release.reusable.yml@master - with: - go-version: '1.20' # auto-update/latest-go-version diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml new file mode 100644 index 0000000..46d2d04 --- /dev/null +++ b/.github/workflows/renovate-go-mod-fix.yaml @@ -0,0 +1,33 @@ +# +# 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@v2 + 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 diff --git a/.github/workflows/renovate-go-sum-fix.yaml b/.github/workflows/renovate-go-sum-fix.yaml deleted file mode 100644 index b7bb1b4..0000000 --- a/.github/workflows/renovate-go-sum-fix.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -name: Fix go.sum -on: - push: - branches: - - renovate/* - -jobs: - fix: - uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master - secrets: - token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml deleted file mode 100644 index 8633a12..0000000 --- a/.github/workflows/reuse.yml +++ /dev/null @@ -1,22 +0,0 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -name: REUSE Compliance Check - -on: - push: - pull_request: - -jobs: - lint: - uses: pion/.goassets/.github/workflows/reuse.reusable.yml@master diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 31aada4..5b7a43b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,43 +1,139 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - name: Test on: push: branches: - - master + - master pull_request: - + branches: + - master jobs: test: - uses: pion/.goassets/.github/workflows/test.reusable.yml@master + runs-on: ubuntu-latest strategy: matrix: - go: ['1.20', '1.19'] # auto-update/supported-go-version-list + go: ["1.15", "1.16"] fail-fast: false - with: - go-version: ${{ matrix.go }} + name: Go ${{ matrix.go }} + steps: + - uses: actions/checkout@v2 + + - 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@v2 + 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: | + go-acc -o cover.out ./... -- \ + -bench=. \ + -v -race + + - uses: codecov/codecov-action@v1 + with: + file: ./cover.out + name: codecov-umbrella + fail_ci_if_error: true + flags: go test-i386: - uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master + runs-on: ubuntu-latest strategy: matrix: - go: ['1.20', '1.19'] # auto-update/supported-go-version-list + go: ["1.15", "1.16"] fail-fast: false - with: - go-version: ${{ matrix.go }} + name: Go i386 ${{ matrix.go }} + steps: + - uses: actions/checkout@v2 + + - 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: - uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master - with: - go-version: '1.20' # auto-update/latest-go-version + runs-on: ubuntu-latest + strategy: + fail-fast: false + name: WASM + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '12.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.16 + + - 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: | + GOOS=js GOARCH=wasm $GOPATH/bin/go test \ + -coverprofile=cover.out -covermode=atomic \ + -exec="${GO_JS_WASM_EXEC}" \ + -v ./... + + - uses: codecov/codecov-action@v1 + 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 4d346d4..03b5189 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -8,18 +8,30 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT name: Go mod tidy on: pull_request: + branches: + - master push: branches: - master jobs: - tidy: - uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master - with: - go-version: '1.20' # auto-update/latest-go-version + Check: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Setup Go + uses: actions/setup-go@v2 + - name: check + run: | + go mod download + go mod tidy + if ! git diff --exit-code + then + echo "Not go mod tidied" + exit 1 + fi diff --git a/.gitignore b/.gitignore index 6e2f206..83db74b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - ### JetBrains IDE ### ##################### .idea/ @@ -25,4 +22,3 @@ 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 4e3eddf..d6162c9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - linters-settings: govet: check-shadowing: true @@ -13,34 +10,19 @@ linters-settings: modules: - github.com/pkg/errors: recommendations: - - errors - forbidigo: - forbid: - - ^fmt.Print(f|ln)?$ - - ^log.(Panic|Fatal|Print)(f|ln)?$ - - ^os.Exit$ - - ^panic$ - - ^print(ln)?$ + - errors linters: enable: - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers - - bidichk # Checks for dangerous unicode character sequences - bodyclose # checks whether HTTP response body is closed successfully - - contextcheck # check the function whether use a non-inherited context - - decorder # check declaration order and count of types, constants, variables and functions + - 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 - - durationcheck # check for two durations multiplied together - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases - - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. - - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. - - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. - exhaustive # check exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables - - forbidigo # Forbids identifiers - - forcetypeassert # finds forced type assertions - gci # Gci control golang package import order and make it always deterministic. - gochecknoglobals # Checks that no globals are present in Go code - gochecknoinits # Checks that no init functions are present in Go code @@ -53,59 +35,40 @@ 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 - - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. + - 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 - - 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 - - predeclared # find code that shadows one of Go's predeclared identifiers - - revive # golint replacement, finds style mistakes + - 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 - - tagliatelle # Checks the struct tags. - - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 - - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - - wastedassign # wastedassign finds wasted assignment statements + - varcheck # Finds unused global variables and constants - 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 - 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: @@ -115,23 +78,12 @@ issues: - path: _test\.go linters: - gocognit - - forbidigo # Allow complex main function in examples - path: examples text: "of func `main` is high" linters: - gocognit - - # Allow forbidden identifiers in examples - - path: examples - linters: - - forbidigo - - # Allow forbidden identifiers in CLI commands - - path: cmd - linters: - - forbidigo run: skip-dirs-use-default: false diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index 30093e9..0000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -builds: -- skip: true diff --git a/.reuse/dep5 b/.reuse/dep5 deleted file mode 100644 index c8b3dfa..0000000 --- a/.reuse/dep5 +++ /dev/null @@ -1,11 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Pion -Source: https://github.com/pion/ - -Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum .eslintrc.json package.json examples/examples.json -Copyright: 2023 The Pion community -License: MIT - -Files: testdata/fuzz/* **/testdata/fuzz/* api/*.txt -Copyright: 2023 The Pion community -License: CC0-1.0 diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index 38685a9..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,43 +0,0 @@ -# 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 https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting -Aaron Boushley -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 -kawaway -Kazuyuki Honda -Kevin Wang -Luke Curley -lxb -Michael MacDonald -Michael MacDonald -Michael Uti -Raphael Derosso Pereira -Rob Lofthouse -Robin Raymond -Sean DuBois -Sean DuBois -Sean DuBois -Simone Gotti -Steffen Vogel -Tarrence van As -wangzixiang -Woodrow Douglass - -# List of contributors not appearing in Git history - diff --git a/LICENSE b/LICENSE deleted file mode 120000 index 31ff787..0000000 --- a/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSES/MIT.txt \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt deleted file mode 100644 index 2071b23..0000000 --- a/LICENSES/MIT.txt +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) - -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. diff --git a/README.md b/README.md index 37b7cc0..22b4ece 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Sourcegraph Widget Slack Widget
- GitHub Workflow Status - Go Reference + Build Status + GoDoc Coverage Status Go Report Card License: MIT @@ -21,15 +21,36 @@ The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. ### Community -Pion has an active community on the [Slack](https://pion.ly/slack). - -Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. +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: [AUTHORS.txt](./AUTHORS.txt) +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: + +* [John Bradley](https://github.com/kc5nra) - *Original Author* +* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* +* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes* +* [Michael MacDonald](https://github.com/mjmac) +* [Luke Curley](https://github.com/kixelated) *Performance* +* [Antoine Baché](https://github.com/Antonito) *Fixed crashes* +* [Hugo Arregui](https://github.com/hugoArregui) +* [Raphael Derosso Pereira](https://github.com/raphaelpereira) +* [Atsushi Watanabe](https://github.com/at-wat) +* [adwpc](https://github.com/adwpc) *add transport-cc extension* +* [Bao Nguyen](https://github.com/sysbot) *add VP9 noop, bug fixes. +* [Tarrence van As](https://github.com/tarrencev) *add audio level extension* +* [Simone Gotti](https://github.com/sgotti) +* [Guilherme Souza](https://github.com/gqgs) +* [Rob Lofthouse](https://github.com/roblofthouse) +* [Kazuyuki Honda](https://github.com/hakobera) +* [Haiyang Wang](https://github.com/ocean2811) +* [lxb](https://github.com/lxb531) +* [Robin Raymond](https://github.com/robin-raymond) +* [debiandebiandebian](https://github.com/debiandebiandebian) +* [Juliusz Chroboczek](https://github.com/jech) ### License -MIT License - see [LICENSE](LICENSE) for full text \ No newline at end of file +MIT License - see [LICENSE](LICENSE) for full text diff --git a/abscapturetimeextension.go b/abscapturetimeextension.go deleted file mode 100644 index 56b783d..0000000 --- a/abscapturetimeextension.go +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package rtp - -import ( - "encoding/binary" - "time" -) - -const ( - absCaptureTimeExtensionSize = 8 - absCaptureTimeExtendedExtensionSize = 16 -) - -// AbsCaptureTimeExtension is a extension payload format in -// http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | len=7 | absolute capture timestamp (bit 0-23) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | absolute capture timestamp (bit 24-55) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ... (56-63) | -// +-+-+-+-+-+-+-+-+ -type AbsCaptureTimeExtension struct { - Timestamp uint64 - EstimatedCaptureClockOffset *int64 -} - -// Marshal serializes the members to buffer. -func (t AbsCaptureTimeExtension) Marshal() ([]byte, error) { - if t.EstimatedCaptureClockOffset != nil { - buf := make([]byte, 16) - binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) - binary.BigEndian.PutUint64(buf[8:16], uint64(*t.EstimatedCaptureClockOffset)) - return buf, nil - } - buf := make([]byte, 8) - binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) - return buf, nil -} - -// Unmarshal parses the passed byte slice and stores the result in the members. -func (t *AbsCaptureTimeExtension) Unmarshal(rawData []byte) error { - if len(rawData) < absCaptureTimeExtensionSize { - return errTooSmall - } - t.Timestamp = binary.BigEndian.Uint64(rawData[0:8]) - if len(rawData) >= absCaptureTimeExtendedExtensionSize { - offset := int64(binary.BigEndian.Uint64(rawData[8:16])) - t.EstimatedCaptureClockOffset = &offset - } - return nil -} - -// CaptureTime produces the estimated time.Time represented by this extension. -func (t AbsCaptureTimeExtension) CaptureTime() time.Time { - return toTime(t.Timestamp) -} - -// EstimatedCaptureClockOffsetDuration produces the estimated time.Duration represented by this extension. -func (t AbsCaptureTimeExtension) EstimatedCaptureClockOffsetDuration() *time.Duration { - if t.EstimatedCaptureClockOffset == nil { - return nil - } - offset := *t.EstimatedCaptureClockOffset - duration := time.Duration(offset/(1<<32))*time.Second + time.Duration((offset&0xFFFFFFFF)*1e9/(1<<32))*time.Nanosecond - return &duration -} - -// NewAbsCaptureTimeExtension makes new AbsCaptureTimeExtension from time.Time. -func NewAbsCaptureTimeExtension(captureTime time.Time) *AbsCaptureTimeExtension { - return &AbsCaptureTimeExtension{ - Timestamp: toNtpTime(captureTime), - } -} - -// NewAbsCaptureTimeExtensionWithCaptureClockOffset makes new AbsCaptureTimeExtension from time.Time and a clock offset. -func NewAbsCaptureTimeExtensionWithCaptureClockOffset(captureTime time.Time, captureClockOffset time.Duration) *AbsCaptureTimeExtension { - ns := captureClockOffset.Nanoseconds() - lsb := (ns / 1e9) & 0xFFFFFFFF - msb := (((ns % 1e9) * (1 << 32)) / 1e9) & 0xFFFFFFFF - offset := (lsb << 32) | msb - return &AbsCaptureTimeExtension{ - Timestamp: toNtpTime(captureTime), - EstimatedCaptureClockOffset: &offset, - } -} diff --git a/abscapturetimeextension_test.go b/abscapturetimeextension_test.go deleted file mode 100644 index 15c434a..0000000 --- a/abscapturetimeextension_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package rtp - -import ( - "testing" - "time" -) - -func TestAbsCaptureTimeExtension_Roundtrip(t *testing.T) { - t0 := time.Now() - e1 := NewAbsCaptureTimeExtension(t0) - b1, err1 := e1.Marshal() - if err1 != nil { - t.Fatal(err1) - } - var o1 AbsCaptureTimeExtension - if err := o1.Unmarshal(b1); err != nil { - t.Fatal(err) - } - dt1 := o1.CaptureTime().Sub(t0).Seconds() - if dt1 < -0.001 || dt1 > 0.001 { - t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o1.CaptureTime(), dt1) - } - if o1.EstimatedCaptureClockOffsetDuration() != nil { - t.Fatalf("duration differs, want nil got %d", o1.EstimatedCaptureClockOffsetDuration()) - } - - e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, 1250*time.Millisecond) - b2, err2 := e2.Marshal() - if err2 != nil { - t.Fatal(err2) - } - var o2 AbsCaptureTimeExtension - if err := o2.Unmarshal(b2); err != nil { - t.Fatal(err) - } - dt2 := o1.CaptureTime().Sub(t0).Seconds() - if dt2 < -0.001 || dt2 > 0.001 { - t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o2.CaptureTime(), dt2) - } - if *o2.EstimatedCaptureClockOffsetDuration() != 1250*time.Millisecond { - t.Fatalf("duration differs, want 250ms got %d", *o2.EstimatedCaptureClockOffsetDuration()) - } -} diff --git a/abssendtimeextension.go b/abssendtimeextension.go index fff38e9..fc9731d 100644 --- a/abssendtimeextension.go +++ b/abssendtimeextension.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -18,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/abssendtimeextension_test.go b/abssendtimeextension_test.go index 16834d9..16243fd 100644 --- a/abssendtimeextension_test.go +++ b/abssendtimeextension_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( diff --git a/audiolevelextension.go b/audiolevelextension.go index f180c8d..f8701e1 100644 --- a/audiolevelextension.go +++ b/audiolevelextension.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -39,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/audiolevelextension_test.go b/audiolevelextension_test.go index 9c651a0..b1ad89f 100644 --- a/audiolevelextension_test.go +++ b/audiolevelextension_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( diff --git a/codecov.yml b/codecov.yml index 263e4d4..085200a 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,8 +3,6 @@ # # It is automatically copied from https://github.com/pion/.goassets repository. # -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT coverage: status: diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go deleted file mode 100644 index b074ab5..0000000 --- a/codecs/av1_packet.go +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -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 deleted file mode 100644 index 3761c3a..0000000 --- a/codecs/av1_packet_test.go +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package codecs - -import ( - "errors" - "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.Fatalf("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/codecs.go b/codecs/codecs.go index cd1c891..0e07897 100644 --- a/codecs/codecs.go +++ b/codecs/codecs.go @@ -1,5 +1,2 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - // Package codecs implements codec specific RTP payloader/depayloaders package codecs diff --git a/codecs/common.go b/codecs/common.go index 5da8aaf..39336d2 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs func min(a, b int) int { @@ -9,21 +6,3 @@ func min(a, b int) int { } return b } - -// audioDepacketizer is a mixin for audio codec depacketizers -type audioDepacketizer struct{} - -func (d *audioDepacketizer) IsPartitionTail(_ bool, _ []byte) bool { - return true -} - -func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool { - return true -} - -// videoDepacketizer is a mixin for video codec depacketizers -type videoDepacketizer struct{} - -func (d *videoDepacketizer) IsPartitionTail(marker bool, _ []byte) bool { - return marker -} diff --git a/codecs/common_test.go b/codecs/common_test.go index 3487276..5cbbd3c 100644 --- a/codecs/common_test.go +++ b/codecs/common_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( diff --git a/codecs/error.go b/codecs/error.go index 2083ef4..38ee907 100644 --- a/codecs/error.go +++ b/codecs/error.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import "errors" @@ -11,7 +8,4 @@ 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/codecs/g711_packet.go b/codecs/g711_packet.go index 4afda55..a74876f 100644 --- a/codecs/g711_packet.go +++ b/codecs/g711_packet.go @@ -1,19 +1,16 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs // G711Payloader payloads G711 packets type G711Payloader struct{} // Payload fragments an G711 packet across one or more byte arrays -func (p *G711Payloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *G711Payloader) Payload(mtu int, payload []byte) [][]byte { var out [][]byte - if payload == nil || mtu == 0 { + if payload == nil || mtu <= 0 { return out } - for len(payload) > int(mtu) { + for len(payload) > mtu { o := make([]byte, mtu) copy(o, payload[:mtu]) payload = payload[mtu:] diff --git a/codecs/g711_packet_test.go b/codecs/g711_packet_test.go index 75fa6a8..d260a06 100644 --- a/codecs/g711_packet_test.go +++ b/codecs/g711_packet_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs //nolint:dupl import ( @@ -48,9 +45,20 @@ func TestG711Payloader(t *testing.T) { } payload := []byte{0x90, 0x90, 0x90} + // Nil payload + res := p.Payload(-1, nil) + if len(res) != 0 { + t.Fatal("Generated payload should be empty") + } + + // Negative MTU, small payload + res = p.Payload(-1, payload) + if len(res) != 0 { + t.Fatal("Generated payload should be empty") + } // 0 MTU, small payload - res := p.Payload(0, payload) + res = p.Payload(0, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } @@ -62,7 +70,7 @@ func TestG711Payloader(t *testing.T) { } // Positive MTU, small payload - res = p.Payload(uint16(len(payload)-1), payload) + res = p.Payload(len(payload)-1, payload) if len(res) != len(payload)-1 { t.Fatal("Generated payload should be the same smaller than original payload size") } diff --git a/codecs/g722_packet.go b/codecs/g722_packet.go index ae6672d..70c9883 100644 --- a/codecs/g722_packet.go +++ b/codecs/g722_packet.go @@ -1,19 +1,16 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs // G722Payloader payloads G722 packets type G722Payloader struct{} // Payload fragments an G722 packet across one or more byte arrays -func (p *G722Payloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *G722Payloader) Payload(mtu int, payload []byte) [][]byte { var out [][]byte - if payload == nil || mtu == 0 { + if payload == nil || mtu <= 0 { return out } - for len(payload) > int(mtu) { + for len(payload) > mtu { o := make([]byte, mtu) copy(o, payload[:mtu]) payload = payload[mtu:] diff --git a/codecs/g722_packet_test.go b/codecs/g722_packet_test.go index f15f81c..8d1198b 100644 --- a/codecs/g722_packet_test.go +++ b/codecs/g722_packet_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs //nolint:dupl import ( @@ -48,9 +45,20 @@ func TestG722Payloader(t *testing.T) { } payload := []byte{0x90, 0x90, 0x90} + // Nil payload + res := p.Payload(-1, nil) + if len(res) != 0 { + t.Fatal("Generated payload should be empty") + } + + // Negative MTU, small payload + res = p.Payload(-1, payload) + if len(res) != 0 { + t.Fatal("Generated payload should be empty") + } // 0 MTU, small payload - res := p.Payload(0, payload) + res = p.Payload(0, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } @@ -62,7 +70,7 @@ func TestG722Payloader(t *testing.T) { } // Positive MTU, small payload - res = p.Payload(uint16(len(payload)-1), payload) + res = p.Payload(len(payload)-1, payload) if len(res) != len(payload)-1 { t.Fatal("Generated payload should be the same smaller than original payload size") } diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 5dc3a2e..7a22642 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( @@ -9,18 +6,12 @@ import ( ) // H264Payloader payloads H264 packets -type H264Payloader struct { - spsNalu, ppsNalu []byte -} +type H264Payloader struct{} const ( - stapaNALUType = 24 - fuaNALUType = 28 - fubNALUType = 29 - spsNALUType = 7 - ppsNALUType = 8 - audNALUType = 9 - fillerNALUType = 12 + stapaNALUType = 24 + fuaNALUType = 28 + fubNALUType = 29 fuaHeaderSize = 2 stapaHeaderSize = 1 @@ -30,8 +21,6 @@ const ( naluRefIdcBitmask = 0x60 fuStartBitmask = 0x80 fuEndBitmask = 0x40 - - outputStapAHeader = 0x78 ) func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } @@ -72,7 +61,7 @@ func emitNalus(nals []byte, emit func([]byte)) { } // Payload fragments a H264 packet across one or more byte arrays -func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte { var payloads [][]byte if len(payload) == 0 { return payloads @@ -86,40 +75,12 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { 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 + if naluType == 9 || naluType == 12 { 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) { + if len(nalu) <= mtu { out := make([]byte, len(nalu)) copy(out, nalu) payloads = append(payloads, out) @@ -127,7 +88,7 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { } // FU-A - maxFragmentSize := int(mtu) - fuaHeaderSize + maxFragmentSize := mtu - fuaHeaderSize // The FU payload consists of fragments of the payload of the fragmented // NAL unit so that if the fragmentation unit payloads of consecutive @@ -192,8 +153,6 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { type H264Packet struct { IsAVC bool fuaBuffer []byte - - videoDepacketizer } func (p *H264Packet) doPackaging(nalu []byte) []byte { @@ -214,8 +173,10 @@ func (p *H264Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bo // 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 len(payload) == 0 { - return nil, fmt.Errorf("%w: %d <=0", errShortPacket, len(payload)) + if payload == nil { + return nil, errNilPacket + } else if len(payload) <= 2 { + return nil, fmt.Errorf("%w: %d <= 2", errShortPacket, len(payload)) } // NALU Types @@ -268,18 +229,18 @@ func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType) } -// H264PartitionHeadChecker is obsolete +// H264PartitionHeadChecker checks H264 partition head 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 { +func (*H264PartitionHeadChecker) IsPartitionHead(packet []byte) bool { + if packet == nil || len(packet) < 2 { return false } - if payload[0]&naluTypeBitmask == fuaNALUType || - payload[0]&naluTypeBitmask == fubNALUType { - return payload[1]&fuStartBitmask != 0 + if packet[0]&naluTypeBitmask == fuaNALUType || + packet[0]&naluTypeBitmask == fubNALUType { + return packet[1]&fuStartBitmask != 0 } return true diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index 8e3b221..4ee7fd2 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( @@ -118,16 +115,8 @@ func TestH264Packet_Unmarshal(t *testing.T) { t.Fatal("Unmarshal did not fail on nil payload") } - if _, err := pkt.Unmarshal([]byte{}); err == nil { - t.Fatal("Unmarshal did not fail on []byte{}") - } - - if _, err := pkt.Unmarshal([]byte{0xFC}); err == nil { - t.Fatal("Unmarshal accepted a FU-A packet that is too small for a payload and header") - } - - if _, err := pkt.Unmarshal([]byte{0x0A}); err != nil { - t.Fatal("Unmarshaling end of sequence(NALU Type : 10) should succeed") + 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 { @@ -191,68 +180,45 @@ func TestH264Packet_Unmarshal(t *testing.T) { } } -func TestH264IsPartitionHead(t *testing.T) { - h264 := H264Packet{} +func TestH264PartitionHeadChecker_IsPartitionHead(t *testing.T) { + h264PartitionHeadChecker := H264PartitionHeadChecker{} - if h264.IsPartitionHead(nil) { + if h264PartitionHeadChecker.IsPartitionHead(nil) { t.Fatal("nil must not be a partition head") } emptyNalu := []byte{} - if h264.IsPartitionHead(emptyNalu) { + if h264PartitionHeadChecker.IsPartitionHead(emptyNalu) { t.Fatal("empty nalu must not be a partition head") } singleNalu := []byte{1, 0} - if h264.IsPartitionHead(singleNalu) == false { + if h264PartitionHeadChecker.IsPartitionHead(singleNalu) == false { t.Fatal("single nalu must be a partition head") } stapaNalu := []byte{stapaNALUType, 0} - if h264.IsPartitionHead(stapaNalu) == false { + if h264PartitionHeadChecker.IsPartitionHead(stapaNalu) == false { t.Fatal("stapa nalu must be a partition head") } fuaStartNalu := []byte{fuaNALUType, fuStartBitmask} - if h264.IsPartitionHead(fuaStartNalu) == false { + if h264PartitionHeadChecker.IsPartitionHead(fuaStartNalu) == false { t.Fatal("fua start nalu must be a partition head") } fuaEndNalu := []byte{fuaNALUType, fuEndBitmask} - if h264.IsPartitionHead(fuaEndNalu) { + if h264PartitionHeadChecker.IsPartitionHead(fuaEndNalu) { t.Fatal("fua end nalu must not be a partition head") } fubStartNalu := []byte{fubNALUType, fuStartBitmask} - if h264.IsPartitionHead(fubStartNalu) == false { + if h264PartitionHeadChecker.IsPartitionHead(fubStartNalu) == false { t.Fatal("fub start nalu must be a partition head") } fubEndNalu := []byte{fubNALUType, fuEndBitmask} - if h264.IsPartitionHead(fubEndNalu) { + if h264PartitionHeadChecker.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") - } -} diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go deleted file mode 100644 index 2a194fd..0000000 --- a/codecs/h265_packet.go +++ /dev/null @@ -1,828 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package codecs - -import ( - "encoding/binary" - "errors" - "fmt" -) - -// -// Errors -// - -var ( - errH265CorruptedPacket = errors.New("corrupted h265 packet") - errInvalidH265PacketType = errors.New("invalid h265 packet type") -) - -// -// Network Abstraction Unit Header implementation -// - -const ( - // sizeof(uint16) - h265NaluHeaderSize = 2 - // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 - h265NaluAggregationPacketType = 48 - // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 - h265NaluFragmentationUnitType = 49 - // https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 - h265NaluPACIPacketType = 50 -) - -// H265NALUHeader is a H265 NAL Unit Header -// https://datatracker.ietf.org/doc/html/rfc7798#section-1.1.4 -/* -* +---------------+---------------+ -* |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* |F| Type | LayerID | TID | -* +-------------+-----------------+ -**/ -type H265NALUHeader uint16 - -func newH265NALUHeader(highByte, lowByte uint8) H265NALUHeader { - return H265NALUHeader((uint16(highByte) << 8) | uint16(lowByte)) -} - -// F is the forbidden bit, should always be 0. -func (h H265NALUHeader) F() bool { - return (uint16(h) >> 15) != 0 -} - -// Type of NAL Unit. -func (h H265NALUHeader) Type() uint8 { - // 01111110 00000000 - const mask = 0b01111110 << 8 - return uint8((uint16(h) & mask) >> (8 + 1)) -} - -// IsTypeVCLUnit returns whether or not the NAL Unit type is a VCL NAL unit. -func (h H265NALUHeader) IsTypeVCLUnit() bool { - // Type is coded on 6 bits - const msbMask = 0b00100000 - return (h.Type() & msbMask) == 0 -} - -// LayerID should always be 0 in non-3D HEVC context. -func (h H265NALUHeader) LayerID() uint8 { - // 00000001 11111000 - const mask = (0b00000001 << 8) | 0b11111000 - return uint8((uint16(h) & mask) >> 3) -} - -// TID is the temporal identifier of the NAL unit +1. -func (h H265NALUHeader) TID() uint8 { - const mask = 0b00000111 - return uint8(uint16(h) & mask) -} - -// IsAggregationPacket returns whether or not the packet is an Aggregation packet. -func (h H265NALUHeader) IsAggregationPacket() bool { - return h.Type() == h265NaluAggregationPacketType -} - -// IsFragmentationUnit returns whether or not the packet is a Fragmentation Unit packet. -func (h H265NALUHeader) IsFragmentationUnit() bool { - return h.Type() == h265NaluFragmentationUnitType -} - -// IsPACIPacket returns whether or not the packet is a PACI packet. -func (h H265NALUHeader) IsPACIPacket() bool { - return h.Type() == h265NaluPACIPacketType -} - -// -// Single NAL Unit Packet implementation -// - -// H265SingleNALUnitPacket represents a NALU packet, containing exactly one NAL unit. -/* -* 0 1 2 3 -* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | PayloadHdr | DONL (conditional) | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | | -* | NAL unit payload data | -* | | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | :...OPTIONAL RTP padding | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.1 -type H265SingleNALUnitPacket struct { - // payloadHeader is the header of the H265 packet. - payloadHeader H265NALUHeader - // donl is a 16-bit field, that may or may not be present. - donl *uint16 - // payload of the fragmentation unit. - payload []byte - - mightNeedDONL bool -} - -// WithDONL can be called to specify whether or not DONL might be parsed. -// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. -func (p *H265SingleNALUnitPacket) WithDONL(value bool) { - p.mightNeedDONL = value -} - -// Unmarshal parses the passed byte slice and stores the result in the H265SingleNALUnitPacket this method is called upon. -func (p *H265SingleNALUnitPacket) Unmarshal(payload []byte) ([]byte, error) { - // sizeof(headers) - const totalHeaderSize = h265NaluHeaderSize - if payload == nil { - return nil, errNilPacket - } else if len(payload) <= totalHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) - } - - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket - } - if payloadHeader.IsFragmentationUnit() || payloadHeader.IsPACIPacket() || payloadHeader.IsAggregationPacket() { - return nil, errInvalidH265PacketType - } - - payload = payload[2:] - - if p.mightNeedDONL { - // sizeof(uint16) - if len(payload) <= 2 { - return nil, errShortPacket - } - - donl := (uint16(payload[0]) << 8) | uint16(payload[1]) - p.donl = &donl - payload = payload[2:] - } - - p.payloadHeader = payloadHeader - p.payload = payload - - return nil, nil -} - -// PayloadHeader returns the NALU header of the packet. -func (p *H265SingleNALUnitPacket) PayloadHeader() H265NALUHeader { - return p.payloadHeader -} - -// DONL returns the DONL of the packet. -func (p *H265SingleNALUnitPacket) DONL() *uint16 { - return p.donl -} - -// Payload returns the Fragmentation Unit packet payload. -func (p *H265SingleNALUnitPacket) Payload() []byte { - return p.payload -} - -func (p *H265SingleNALUnitPacket) isH265Packet() {} - -// -// Aggregation Packets implementation -// - -// H265AggregationUnitFirst represent the First Aggregation Unit in an AP. -/* -* 0 1 2 3 -* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* : DONL (conditional) | NALU size | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | NALU size | | -* +-+-+-+-+-+-+-+-+ NAL unit | -* | | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | : -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 -type H265AggregationUnitFirst struct { - donl *uint16 - nalUnitSize uint16 - nalUnit []byte -} - -// DONL field, when present, specifies the value of the 16 least -// significant bits of the decoding order number of the aggregated NAL -// unit. -func (u H265AggregationUnitFirst) DONL() *uint16 { - return u.donl -} - -// NALUSize represents the size, in bytes, of the NalUnit. -func (u H265AggregationUnitFirst) NALUSize() uint16 { - return u.nalUnitSize -} - -// NalUnit payload. -func (u H265AggregationUnitFirst) NalUnit() []byte { - return u.nalUnit -} - -// H265AggregationUnit represent the an Aggregation Unit in an AP, which is not the first one. -/* -* 0 1 2 3 -* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* : DOND (cond) | NALU size | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | | -* | NAL unit | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | : -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 -type H265AggregationUnit struct { - dond *uint8 - nalUnitSize uint16 - nalUnit []byte -} - -// DOND field plus 1 specifies the difference between -// the decoding order number values of the current aggregated NAL unit -// and the preceding aggregated NAL unit in the same AP. -func (u H265AggregationUnit) DOND() *uint8 { - return u.dond -} - -// NALUSize represents the size, in bytes, of the NalUnit. -func (u H265AggregationUnit) NALUSize() uint16 { - return u.nalUnitSize -} - -// NalUnit payload. -func (u H265AggregationUnit) NalUnit() []byte { - return u.nalUnit -} - -// H265AggregationPacket represents an Aggregation packet. -/* -* 0 1 2 3 -* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | PayloadHdr (Type=48) | | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | -* | | -* | two or more aggregation units | -* | | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | :...OPTIONAL RTP padding | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.2 -type H265AggregationPacket struct { - firstUnit *H265AggregationUnitFirst - otherUnits []H265AggregationUnit - - mightNeedDONL bool -} - -// WithDONL can be called to specify whether or not DONL might be parsed. -// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. -func (p *H265AggregationPacket) WithDONL(value bool) { - p.mightNeedDONL = value -} - -// Unmarshal parses the passed byte slice and stores the result in the H265AggregationPacket this method is called upon. -func (p *H265AggregationPacket) Unmarshal(payload []byte) ([]byte, error) { - // sizeof(headers) - const totalHeaderSize = h265NaluHeaderSize - if payload == nil { - return nil, errNilPacket - } else if len(payload) <= totalHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) - } - - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket - } - if !payloadHeader.IsAggregationPacket() { - return nil, errInvalidH265PacketType - } - - // First parse the first aggregation unit - payload = payload[2:] - firstUnit := &H265AggregationUnitFirst{} - - if p.mightNeedDONL { - if len(payload) < 2 { - return nil, errShortPacket - } - - donl := (uint16(payload[0]) << 8) | uint16(payload[1]) - firstUnit.donl = &donl - - payload = payload[2:] - } - if len(payload) < 2 { - return nil, errShortPacket - } - firstUnit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) - payload = payload[2:] - - if len(payload) < int(firstUnit.nalUnitSize) { - return nil, errShortPacket - } - - firstUnit.nalUnit = payload[:firstUnit.nalUnitSize] - payload = payload[firstUnit.nalUnitSize:] - - // Parse remaining Aggregation Units - var units []H265AggregationUnit - for { - unit := H265AggregationUnit{} - - if p.mightNeedDONL { - if len(payload) < 1 { - break - } - - dond := payload[0] - unit.dond = &dond - - payload = payload[1:] - } - - if len(payload) < 2 { - break - } - unit.nalUnitSize = (uint16(payload[0]) << 8) | uint16(payload[1]) - payload = payload[2:] - - if len(payload) < int(unit.nalUnitSize) { - break - } - - unit.nalUnit = payload[:unit.nalUnitSize] - payload = payload[unit.nalUnitSize:] - - units = append(units, unit) - } - - // There need to be **at least** two Aggregation Units (first + another one) - if len(units) == 0 { - return nil, errShortPacket - } - - p.firstUnit = firstUnit - p.otherUnits = units - - return nil, nil -} - -// FirstUnit returns the first Aggregated Unit of the packet. -func (p *H265AggregationPacket) FirstUnit() *H265AggregationUnitFirst { - return p.firstUnit -} - -// OtherUnits returns the all the other Aggregated Unit of the packet (excluding the first one). -func (p *H265AggregationPacket) OtherUnits() []H265AggregationUnit { - return p.otherUnits -} - -func (p *H265AggregationPacket) isH265Packet() {} - -// -// Fragmentation Unit implementation -// - -const ( - // sizeof(uint8) - h265FragmentationUnitHeaderSize = 1 -) - -// H265FragmentationUnitHeader is a H265 FU Header -/* -* +---------------+ -* |0|1|2|3|4|5|6|7| -* +-+-+-+-+-+-+-+-+ -* |S|E| FuType | -* +---------------+ -**/ -type H265FragmentationUnitHeader uint8 - -// S represents the start of a fragmented NAL unit. -func (h H265FragmentationUnitHeader) S() bool { - const mask = 0b10000000 - return ((h & mask) >> 7) != 0 -} - -// E represents the end of a fragmented NAL unit. -func (h H265FragmentationUnitHeader) E() bool { - const mask = 0b01000000 - return ((h & mask) >> 6) != 0 -} - -// FuType MUST be equal to the field Type of the fragmented NAL unit. -func (h H265FragmentationUnitHeader) FuType() uint8 { - const mask = 0b00111111 - return uint8(h) & mask -} - -// H265FragmentationUnitPacket represents a single Fragmentation Unit packet. -/* -* 0 1 2 3 -* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | PayloadHdr (Type=49) | FU header | DONL (cond) | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| -* | DONL (cond) | | -* |-+-+-+-+-+-+-+-+ | -* | FU payload | -* | | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | :...OPTIONAL RTP padding | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.3 -type H265FragmentationUnitPacket struct { - // payloadHeader is the header of the H265 packet. - payloadHeader H265NALUHeader - // fuHeader is the header of the fragmentation unit - fuHeader H265FragmentationUnitHeader - // donl is a 16-bit field, that may or may not be present. - donl *uint16 - // payload of the fragmentation unit. - payload []byte - - mightNeedDONL bool -} - -// WithDONL can be called to specify whether or not DONL might be parsed. -// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. -func (p *H265FragmentationUnitPacket) WithDONL(value bool) { - p.mightNeedDONL = value -} - -// Unmarshal parses the passed byte slice and stores the result in the H265FragmentationUnitPacket this method is called upon. -func (p *H265FragmentationUnitPacket) Unmarshal(payload []byte) ([]byte, error) { - // sizeof(headers) - const totalHeaderSize = h265NaluHeaderSize + h265FragmentationUnitHeaderSize - if payload == nil { - return nil, errNilPacket - } else if len(payload) <= totalHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) - } - - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket - } - if !payloadHeader.IsFragmentationUnit() { - return nil, errInvalidH265PacketType - } - - fuHeader := H265FragmentationUnitHeader(payload[2]) - payload = payload[3:] - - if fuHeader.S() && p.mightNeedDONL { - // sizeof(uint16) - if len(payload) <= 2 { - return nil, errShortPacket - } - - donl := (uint16(payload[0]) << 8) | uint16(payload[1]) - p.donl = &donl - payload = payload[2:] - } - - p.payloadHeader = payloadHeader - p.fuHeader = fuHeader - p.payload = payload - - return nil, nil -} - -// PayloadHeader returns the NALU header of the packet. -func (p *H265FragmentationUnitPacket) PayloadHeader() H265NALUHeader { - return p.payloadHeader -} - -// FuHeader returns the Fragmentation Unit Header of the packet. -func (p *H265FragmentationUnitPacket) FuHeader() H265FragmentationUnitHeader { - return p.fuHeader -} - -// DONL returns the DONL of the packet. -func (p *H265FragmentationUnitPacket) DONL() *uint16 { - return p.donl -} - -// Payload returns the Fragmentation Unit packet payload. -func (p *H265FragmentationUnitPacket) Payload() []byte { - return p.payload -} - -func (p *H265FragmentationUnitPacket) isH265Packet() {} - -// -// PACI implementation -// - -// H265PACIPacket represents a single H265 PACI packet. -/* -* 0 1 2 3 -* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | PayloadHdr (Type=50) |A| cType | PHSsize |F0..2|Y| -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | Payload Header Extension Structure (PHES) | -* |=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=| -* | | -* | PACI payload: NAL unit | -* | . . . | -* | | -* | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -* | :...OPTIONAL RTP padding | -* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -**/ -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.4.4 -type H265PACIPacket struct { - // payloadHeader is the header of the H265 packet. - payloadHeader H265NALUHeader - - // Field which holds value for `A`, `cType`, `PHSsize`, `F0`, `F1`, `F2` and `Y` fields. - paciHeaderFields uint16 - - // phes is a header extension, of byte length `PHSsize` - phes []byte - - // Payload contains NAL units & optional padding - payload []byte -} - -// PayloadHeader returns the NAL Unit Header. -func (p *H265PACIPacket) PayloadHeader() H265NALUHeader { - return p.payloadHeader -} - -// A copies the F bit of the PACI payload NALU. -func (p *H265PACIPacket) A() bool { - const mask = 0b10000000 << 8 - return (p.paciHeaderFields & mask) != 0 -} - -// CType copies the Type field of the PACI payload NALU. -func (p *H265PACIPacket) CType() uint8 { - const mask = 0b01111110 << 8 - return uint8((p.paciHeaderFields & mask) >> (8 + 1)) -} - -// PHSsize indicates the size of the PHES field. -func (p *H265PACIPacket) PHSsize() uint8 { - const mask = (0b00000001 << 8) | 0b11110000 - return uint8((p.paciHeaderFields & mask) >> 4) -} - -// F0 indicates the presence of a Temporal Scalability support extension in the PHES. -func (p *H265PACIPacket) F0() bool { - const mask = 0b00001000 - return (p.paciHeaderFields & mask) != 0 -} - -// F1 must be zero, reserved for future extensions. -func (p *H265PACIPacket) F1() bool { - const mask = 0b00000100 - return (p.paciHeaderFields & mask) != 0 -} - -// F2 must be zero, reserved for future extensions. -func (p *H265PACIPacket) F2() bool { - const mask = 0b00000010 - return (p.paciHeaderFields & mask) != 0 -} - -// Y must be zero, reserved for future extensions. -func (p *H265PACIPacket) Y() bool { - const mask = 0b00000001 - return (p.paciHeaderFields & mask) != 0 -} - -// PHES contains header extensions. Its size is indicated by PHSsize. -func (p *H265PACIPacket) PHES() []byte { - return p.phes -} - -// Payload is a single NALU or NALU-like struct, not including the first two octets (header). -func (p *H265PACIPacket) Payload() []byte { - return p.payload -} - -// TSCI returns the Temporal Scalability Control Information extension, if present. -func (p *H265PACIPacket) TSCI() *H265TSCI { - if !p.F0() || p.PHSsize() < 3 { - return nil - } - - tsci := H265TSCI((uint32(p.phes[0]) << 16) | (uint32(p.phes[1]) << 8) | uint32(p.phes[0])) - return &tsci -} - -// Unmarshal parses the passed byte slice and stores the result in the H265PACIPacket this method is called upon. -func (p *H265PACIPacket) Unmarshal(payload []byte) ([]byte, error) { - // sizeof(headers) - const totalHeaderSize = h265NaluHeaderSize + 2 - if payload == nil { - return nil, errNilPacket - } else if len(payload) <= totalHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), totalHeaderSize) - } - - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket - } - if !payloadHeader.IsPACIPacket() { - return nil, errInvalidH265PacketType - } - - paciHeaderFields := (uint16(payload[2]) << 8) | uint16(payload[3]) - payload = payload[4:] - - p.paciHeaderFields = paciHeaderFields - headerExtensionSize := p.PHSsize() - - if len(payload) < int(headerExtensionSize)+1 { - p.paciHeaderFields = 0 - return nil, errShortPacket - } - - p.payloadHeader = payloadHeader - - if headerExtensionSize > 0 { - p.phes = payload[:headerExtensionSize] - } - - payload = payload[headerExtensionSize:] - p.payload = payload - - return nil, nil -} - -func (p *H265PACIPacket) isH265Packet() {} - -// -// Temporal Scalability Control Information -// - -// H265TSCI is a Temporal Scalability Control Information header extension. -// Reference: https://datatracker.ietf.org/doc/html/rfc7798#section-4.5 -type H265TSCI uint32 - -// TL0PICIDX see RFC7798 for more details. -func (h H265TSCI) TL0PICIDX() uint8 { - const m1 = 0xFFFF0000 - const m2 = 0xFF00 - return uint8((((h & m1) >> 16) & m2) >> 8) -} - -// IrapPicID see RFC7798 for more details. -func (h H265TSCI) IrapPicID() uint8 { - const m1 = 0xFFFF0000 - const m2 = 0x00FF - return uint8(((h & m1) >> 16) & m2) -} - -// S see RFC7798 for more details. -func (h H265TSCI) S() bool { - const m1 = 0xFF00 - const m2 = 0b10000000 - return (uint8((h&m1)>>8) & m2) != 0 -} - -// E see RFC7798 for more details. -func (h H265TSCI) E() bool { - const m1 = 0xFF00 - const m2 = 0b01000000 - return (uint8((h&m1)>>8) & m2) != 0 -} - -// RES see RFC7798 for more details. -func (h H265TSCI) RES() uint8 { - const m1 = 0xFF00 - const m2 = 0b00111111 - return uint8((h&m1)>>8) & m2 -} - -// -// H265 Packet interface -// - -type isH265Packet interface { - isH265Packet() -} - -var ( - _ isH265Packet = (*H265FragmentationUnitPacket)(nil) - _ isH265Packet = (*H265PACIPacket)(nil) - _ isH265Packet = (*H265SingleNALUnitPacket)(nil) - _ isH265Packet = (*H265AggregationPacket)(nil) -) - -// -// Packet implementation -// - -// H265Packet represents a H265 packet, stored in the payload of an RTP packet. -type H265Packet struct { - packet isH265Packet - mightNeedDONL bool - - videoDepacketizer -} - -// WithDONL can be called to specify whether or not DONL might be parsed. -// DONL may need to be parsed if `sprop-max-don-diff` is greater than 0 on the RTP stream. -func (p *H265Packet) WithDONL(value bool) { - p.mightNeedDONL = value -} - -// Unmarshal parses the passed byte slice and stores the result in the H265Packet this method is called upon -func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { - if payload == nil { - return nil, errNilPacket - } else if len(payload) <= h265NaluHeaderSize { - return nil, fmt.Errorf("%w: %d <= %v", errShortPacket, len(payload), h265NaluHeaderSize) - } - - payloadHeader := newH265NALUHeader(payload[0], payload[1]) - if payloadHeader.F() { - return nil, errH265CorruptedPacket - } - - switch { - case payloadHeader.IsPACIPacket(): - decoded := &H265PACIPacket{} - if _, err := decoded.Unmarshal(payload); err != nil { - return nil, err - } - - p.packet = decoded - - case payloadHeader.IsFragmentationUnit(): - decoded := &H265FragmentationUnitPacket{} - decoded.WithDONL(p.mightNeedDONL) - - if _, err := decoded.Unmarshal(payload); err != nil { - return nil, err - } - - p.packet = decoded - - case payloadHeader.IsAggregationPacket(): - decoded := &H265AggregationPacket{} - decoded.WithDONL(p.mightNeedDONL) - - if _, err := decoded.Unmarshal(payload); err != nil { - return nil, err - } - - p.packet = decoded - - default: - decoded := &H265SingleNALUnitPacket{} - decoded.WithDONL(p.mightNeedDONL) - - if _, err := decoded.Unmarshal(payload); err != nil { - return nil, err - } - - p.packet = decoded - } - - return nil, nil -} - -// Packet returns the populated packet. -// Must be casted to one of: -// - *H265SingleNALUnitPacket -// - *H265FragmentationUnitPacket -// - *H265AggregationPacket -// - *H265PACIPacket -// nolint:golint -func (p *H265Packet) Packet() isH265Packet { - return p.packet -} - -// IsPartitionHead checks if this is the head of a packetized nalu stream. -func (*H265Packet) IsPartitionHead(payload []byte) bool { - if len(payload) < 3 { - return false - } - - if H265NALUHeader(binary.BigEndian.Uint16(payload[0:2])).Type() == h265NaluFragmentationUnitType { - return H265FragmentationUnitHeader(payload[2]).S() - } - - return true -} diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go deleted file mode 100644 index 1c5f96a..0000000 --- a/codecs/h265_packet_test.go +++ /dev/null @@ -1,880 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package 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 -} diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index d8d441e..2ab4de7 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -1,13 +1,10 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs // OpusPayloader payloads Opus packets type OpusPayloader struct{} // Payload fragments an Opus packet across one or more byte arrays -func (p *OpusPayloader) Payload(_ uint16, payload []byte) [][]byte { +func (p *OpusPayloader) Payload(mtu int, payload []byte) [][]byte { if payload == nil { return [][]byte{} } @@ -20,8 +17,12 @@ func (p *OpusPayloader) Payload(_ uint16, payload []byte) [][]byte { // OpusPacket represents the Opus header that is stored in the payload of an RTP Packet type OpusPacket struct { Payload []byte +} - audioDepacketizer +// IsDetectedFinalPacketInSequence returns true as all opus packets are always +// final in a sequence +func (p *OpusPacket) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { + return true } // Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon @@ -36,5 +37,14 @@ func (p *OpusPacket) Unmarshal(packet []byte) ([]byte, error) { return packet, nil } -// OpusPartitionHeadChecker is obsolete +// OpusPartitionHeadChecker checks Opus partition head type OpusPartitionHeadChecker struct{} + +// IsPartitionHead checks whether if this is a head of the Opus partition +func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool { + p := &OpusPacket{} + if _, err := p.Unmarshal(packet); err != nil { + return false + } + return true +} diff --git a/codecs/opus_packet_test.go b/codecs/opus_packet_test.go index 665d17b..5366a6b 100644 --- a/codecs/opus_packet_test.go +++ b/codecs/opus_packet_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( @@ -55,6 +52,12 @@ func TestOpusPayloader_Payload(t *testing.T) { t.Fatal("Generated payload should be the 1") } + // Negative 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 { @@ -62,10 +65,15 @@ func TestOpusPayloader_Payload(t *testing.T) { } } -func TestOpusIsPartitionHead(t *testing.T) { - opus := &OpusPacket{} +func TestOpusPartitionHeadChecker_IsPartitionHead(t *testing.T) { + checker := &OpusPartitionHeadChecker{} + t.Run("SmallPacket", func(t *testing.T) { + if checker.IsPartitionHead([]byte{}) { + t.Fatal("Small packet should not be the head of a new partition") + } + }) t.Run("NormalPacket", func(t *testing.T) { - if !opus.IsPartitionHead([]byte{0x00, 0x00}) { + if !checker.IsPartitionHead([]byte{0x00, 0x00}) { t.Fatal("All OPUS RTP packet should be the head of a new partition") } }) diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index e2d1e24..b602ef5 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -1,12 +1,8 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs // VP8Payloader payloads VP8 packets type VP8Payloader struct { - EnablePictureID bool - pictureID uint16 + pictureID uint16 } const ( @@ -14,7 +10,7 @@ const ( ) // Payload fragments a VP8 packet across one or more byte arrays -func (p *VP8Payloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte { /* * https://tools.ietf.org/html/rfc7741#section-4.2 * @@ -37,17 +33,15 @@ func (p *VP8Payloader) Payload(mtu uint16, payload []byte) [][]byte { */ usingHeaderSize := vp8HeaderSize - if p.EnablePictureID { - switch { - case p.pictureID == 0: - case p.pictureID < 128: - usingHeaderSize = vp8HeaderSize + 2 - default: - usingHeaderSize = vp8HeaderSize + 3 - } + switch { + case p.pictureID == 0: + case p.pictureID < 128: + usingHeaderSize = vp8HeaderSize + 2 + default: + usingHeaderSize = vp8HeaderSize + 3 } - maxFragmentSize := int(mtu) - usingHeaderSize + maxFragmentSize := mtu - usingHeaderSize payloadData := payload payloadDataRemaining := len(payload) @@ -68,19 +62,17 @@ func (p *VP8Payloader) Payload(mtu uint16, payload []byte) [][]byte { 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) - } + 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]) @@ -118,8 +110,12 @@ type VP8Packet struct { KEYIDX uint8 /* 5 bits temporal key frame index */ Payload []byte +} - videoDepacketizer +// IsDetectedFinalPacketInSequence returns true of the packet passed in has the +// marker bit set indicated the end of a packet sequence +func (p *VP8Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { + return rtpPacketMarketBit } // Unmarshal parses the passed byte slice and stores the result in the VP8Packet this method is called upon @@ -130,11 +126,12 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { payloadLen := len(payload) - payloadIndex := 0 - - if payloadIndex >= payloadLen { + 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 @@ -143,25 +140,14 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { payloadIndex++ if p.X == 1 { - if payloadIndex >= payloadLen { - return nil, errShortPacket - } p.I = (payload[payloadIndex] & 0x80) >> 7 p.L = (payload[payloadIndex] & 0x40) >> 6 p.T = (payload[payloadIndex] & 0x20) >> 5 p.K = (payload[payloadIndex] & 0x10) >> 4 payloadIndex++ - } else { - p.I = 0 - p.L = 0 - p.T = 0 - p.K = 0 } if p.I == 1 { // PID present? - if payloadIndex >= payloadLen { - return nil, errShortPacket - } if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1]) payloadIndex += 2 @@ -169,54 +155,47 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { p.PictureID = uint16(payload[payloadIndex]) payloadIndex++ } - } else { - p.PictureID = 0 + } + + if payloadIndex >= payloadLen { + return nil, errShortPacket } if p.L == 1 { - if payloadIndex >= payloadLen { - return nil, errShortPacket - } p.TL0PICIDX = payload[payloadIndex] payloadIndex++ - } else { - p.TL0PICIDX = 0 + } + + if payloadIndex >= payloadLen { + return nil, errShortPacket } if p.T == 1 || p.K == 1 { - if payloadIndex >= payloadLen { - return nil, errShortPacket - } if p.T == 1 { p.TID = payload[payloadIndex] >> 6 p.Y = (payload[payloadIndex] >> 5) & 0x1 - } else { - p.TID = 0 - p.Y = 0 } if p.K == 1 { p.KEYIDX = payload[payloadIndex] & 0x1F - } else { - p.KEYIDX = 0 } payloadIndex++ - } else { - p.TID = 0 - p.Y = 0 - p.KEYIDX = 0 } + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.Payload = payload[payloadIndex:] return p.Payload, nil } -// VP8PartitionHeadChecker is obsolete +// VP8PartitionHeadChecker checks VP8 partition head 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 { +func (*VP8PartitionHeadChecker) IsPartitionHead(packet []byte) bool { + p := &VP8Packet{} + if _, err := p.Unmarshal(packet); err != nil { return false } - return (payload[0] & 0x10) != 0 + return p.S == 1 } diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index bb09ef1..1c062c3 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -1,11 +1,7 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( "errors" - "reflect" "testing" ) @@ -30,6 +26,15 @@ func TestVP8Packet_Unmarshal(t *testing.T) { 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 { @@ -59,11 +64,11 @@ func TestVP8Packet_Unmarshal(t *testing.T) { // Header size, X and I, PID 16bits raw, err = pck.Unmarshal([]byte{0x80, 0x80, 0x81, 0x00}) - if raw == nil { - t.Fatal("Result shouldn't be nil in case of success") + if raw != nil { + t.Fatal("Result should be nil in case of error") } - if err != nil { - t.Fatal("Error should be nil in case of success") + if !errors.Is(err, errShortPacket) { + t.Fatal("Error should be:", errShortPacket) } // Header size, X and L @@ -101,139 +106,52 @@ func TestVP8Packet_Unmarshal(t *testing.T) { if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } +} - // According to RFC 7741 Section 4.4, the packetizer need not pay - // attention to partition boundaries. In that case, it may - // produce packets with minimal headers. +func TestVP8Payloader_Payload(t *testing.T) { + pck := VP8Payloader{} + payload := []byte{0x90, 0x90, 0x90} - // The next two have been witnessed in nature. - _, err = pck.Unmarshal([]byte{0x00}) - if err != nil { - t.Errorf("Empty packet with trivial header: %v", err) - } - _, err = pck.Unmarshal([]byte{0x00, 0x2a, 0x94}) - if err != nil { - t.Errorf("Non-empty packet with trivial header: %v", err) + // Positive MTU, nil payload + res := pck.Payload(1, nil) + if len(res) != 0 { + t.Fatal("Generated payload should be empty") } - // The following two were invented. - _, err = pck.Unmarshal([]byte{0x80, 0x00}) - if err != nil { - t.Errorf("Empty packet with trivial extension: %v", err) - } - _, err = pck.Unmarshal([]byte{0x80, 0x80, 42}) - if err != nil { - t.Errorf("Header with PictureID: %v", err) + // 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 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") - } + // Negative MTU, small payload + res = pck.Payload(-1, payload) + 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") - } - }) + // Positive MTU, small payload + res = pck.Payload(2, payload) + if len(res) != len(payload) { + t.Fatal("Generated payload should be the same size as original payload size") + } } -func TestVP8IsPartitionHead(t *testing.T) { - vp8 := &VP8Packet{} +func TestVP8PartitionHeadChecker_IsPartitionHead(t *testing.T) { + checker := &VP8PartitionHeadChecker{} t.Run("SmallPacket", func(t *testing.T) { - if vp8.IsPartitionHead([]byte{0x00}) { + if checker.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}) { + if !checker.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}) { + if checker.IsPartitionHead([]byte{0x00, 0x00, 0x00, 0x00}) { t.Fatal("Packet without S flag should not be the head of a new partition") } }) diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 093cde5..0396554 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( @@ -26,14 +23,14 @@ const ( ) // Payload fragments an VP9 packet across one or more byte arrays -func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte { /* - * https://www.ietf.org/id/draft-ietf-payload-vp9-13.txt + * https://www.ietf.org/id/draft-ietf-payload-vp9-10.txt * * Flexible mode (F=1) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ - * |I|P|L|F|B|E|V|Z| (REQUIRED) + * |I|P|L|F|B|E|V|-| (REQUIRED) * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | (REQUIRED) * +-+-+-+-+-+-+-+-+ @@ -50,7 +47,7 @@ func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { * Non-flexible mode (F=0) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ - * |I|P|L|F|B|E|V|Z| (REQUIRED) + * |I|P|L|F|B|E|V|-| (REQUIRED) * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | (RECOMMENDED) * +-+-+-+-+-+-+-+-+ @@ -78,7 +75,7 @@ func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { return [][]byte{} } - maxFragmentSize := int(mtu) - vp9HeaderSize + maxFragmentSize := mtu - vp9HeaderSize payloadDataRemaining := len(payload) payloadDataIndex := 0 @@ -124,7 +121,6 @@ type VP9Packet struct { 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 @@ -151,8 +147,12 @@ type VP9Packet struct { PGPDiff [][]uint8 // Reference indecies of pictures in a Picture Group Payload []byte +} - videoDepacketizer +// IsDetectedFinalPacketInSequence returns true of the packet passed in has the +// marker bit set indicated the end of a packet sequence +func (p *VP9Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { + return rtpPacketMarketBit } // Unmarshal parses the passed byte slice and stores the result in the VP9Packet this method is called upon @@ -171,7 +171,6 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { 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 @@ -209,13 +208,13 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { } // Picture ID: -/* -* +-+-+-+-+-+-+-+-+ -* I: |M| PICTURE ID | M:0 => picture id is 7 bits. -* +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. -* M: | EXTENDED PID | -* +-+-+-+-+-+-+-+-+ -**/ +// +// +-+-+-+-+-+-+-+-+ +// 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 @@ -247,11 +246,11 @@ func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { } // Layer indices (flexible mode): -/* -* +-+-+-+-+-+-+-+-+ -* L: | T |U| S |D| -* +-+-+-+-+-+-+-+-+ -**/ +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -271,13 +270,13 @@ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { } // Layer indices (non-flexible mode): -/* -* +-+-+-+-+-+-+-+-+ -* L: | T |U| S |D| -* +-+-+-+-+-+-+-+-+ -* | TL0PICIDX | -* +-+-+-+-+-+-+-+-+ -**/ +// +// +-+-+-+-+-+-+-+-+ +// L: | T |U| S |D| +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ +// func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -289,12 +288,12 @@ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, } // 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. -**/ +// +// +-+-+-+-+-+-+-+-+ 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 { @@ -315,25 +314,25 @@ func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { } // 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 . -* +-+-+-+-+-+-+-+-+ -| -| -**/ +// +// +-+-+-+-+-+-+-+-+ +// 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 @@ -351,10 +350,6 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { p.Width = make([]uint16, NS) p.Height = make([]uint16, NS) for i := 0; i < int(NS); i++ { - if len(packet) <= (pos + 3) { - return pos, errShortPacket - } - p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) @@ -363,30 +358,17 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { } if p.G { - if len(packet) <= pos { - return pos, errShortPacket - } - p.NG = packet[pos] pos++ } for i := 0; i < int(p.NG); i++ { - if len(packet) <= pos { - return pos, errShortPacket - } - p.PGTID = append(p.PGTID, packet[pos]>>5) p.PGU = append(p.PGU, packet[pos]&0x10 != 0) R := (packet[pos] >> 2) & 0x3 pos++ p.PGPDiff = append(p.PGPDiff, []uint8{}) - - if len(packet) <= (pos + int(R) - 1) { - return pos, errShortPacket - } - for j := 0; j < int(R); j++ { p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos]) pos++ @@ -396,13 +378,14 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { return pos, nil } -// VP9PartitionHeadChecker is obsolete +// VP9PartitionHeadChecker checks VP9 partition head 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 { +func (*VP9PartitionHeadChecker) IsPartitionHead(packet []byte) bool { + p := &VP9Packet{} + if _, err := p.Unmarshal(packet); err != nil { return false } - return (payload[0] & 0x08) != 0 + return p.B } diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index 97e176b..d226324 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package codecs import ( @@ -170,22 +167,6 @@ func TestVP9Packet_Unmarshal(t *testing.T) { Payload: []byte{}, }, }, - "ScalabilityMissingWidth": { - b: []byte("200"), - err: errShortPacket, - }, - "ScalabilityMissingNG": { - b: []byte("b00200000000"), - err: errShortPacket, - }, - "ScalabilityMissingTemporalLayerIDs": { - b: []byte("20B0"), - err: errShortPacket, - }, - "ScalabilityMissingReferenceIndices": { - b: []byte("20B007"), - err: errShortPacket, - }, } for name, c := range cases { c := c @@ -224,7 +205,7 @@ func TestVP9Payloader_Payload(t *testing.T) { cases := map[string]struct { b [][]byte - mtu uint16 + mtu int res [][]byte }{ "NilPayload": { @@ -237,6 +218,11 @@ func TestVP9Payloader_Payload(t *testing.T) { mtu: 1, res: [][]byte{}, }, + "NegativeMTU": { + b: [][]byte{{0x00, 0x00}}, + mtu: -1, + res: [][]byte{}, + }, "OnePacket": { b: [][]byte{{0x01, 0x02}}, mtu: 10, @@ -318,18 +304,18 @@ func TestVP9Payloader_Payload(t *testing.T) { }) } -func TestVP9IsPartitionHead(t *testing.T) { - vp9 := &VP9Packet{} +func TestVP9PartitionHeadChecker_IsPartitionHead(t *testing.T) { + checker := &VP9PartitionHeadChecker{} t.Run("SmallPacket", func(t *testing.T) { - if vp9.IsPartitionHead([]byte{}) { + if checker.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}) { + if !checker.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}) { + if checker.IsPartitionHead([]byte{0x10, 0x00, 0x00}) { t.Error("VP9 RTP packet without B flag should not be head of a new partition") } }) diff --git a/depacketizer.go b/depacketizer.go index 0439a53..e578d71 100644 --- a/depacketizer.go +++ b/depacketizer.go @@ -1,16 +1,7 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload type Depacketizer interface { + IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool 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 } diff --git a/error.go b/error.go index 4df5d2a..5458c6f 100644 --- a/error.go +++ b/error.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( diff --git a/go.mod b/go.mod index 19a0cc0..412ae63 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/pion/rtp/v2 +module github.com/pion/rtp go 1.13 diff --git a/header_extension.go b/header_extension.go deleted file mode 100644 index fe54215..0000000 --- a/header_extension.go +++ /dev/null @@ -1,353 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package rtp - -import ( - "encoding/binary" - "fmt" - "io" -) - -const ( - headerExtensionProfileOneByte = 0xBEDE - headerExtensionProfileTwoByte = 0x1000 - headerExtensionIDReserved = 0xF -) - -// HeaderExtension represents an RTP extension header. -type HeaderExtension interface { - Set(id uint8, payload []byte) error - GetIDs() []uint8 - Get(id uint8) []byte - Del(id uint8) error - - Unmarshal(buf []byte) (int, error) - Marshal() ([]byte, error) - MarshalTo(buf []byte) (int, error) - MarshalSize() int -} - -// OneByteHeaderExtension is an RFC8285 one-byte header extension. -type OneByteHeaderExtension struct { - payload []byte -} - -// Set sets the extension payload for the specified ID. -func (e *OneByteHeaderExtension) Set(id uint8, buf []byte) error { - if id < 1 || id > 14 { - return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderIDRange, id) - } - if len(buf) > 16 { - return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderSize, len(buf)) - } - - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] >> 4 - payloadLen := int(e.payload[n]&^0xF0 + 1) - n++ - - if extid == id { - e.payload = append(e.payload[:n+1], append(buf, e.payload[n+1+payloadLen:]...)...) - return nil - } - n += payloadLen - } - e.payload = append(e.payload, (id<<4 | uint8(len(buf)-1))) - e.payload = append(e.payload, buf...) - binary.BigEndian.PutUint16(e.payload[2:4], binary.BigEndian.Uint16(e.payload[2:4])+1) - return nil -} - -// GetIDs returns the available IDs. -func (e *OneByteHeaderExtension) GetIDs() []uint8 { - ids := make([]uint8, 0, binary.BigEndian.Uint16(e.payload[2:4])) - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] >> 4 - payloadLen := int(e.payload[n]&^0xF0 + 1) - n++ - - if extid == headerExtensionIDReserved { - break - } - - ids = append(ids, extid) - n += payloadLen - } - return ids -} - -// Get returns the payload of the extension with the given ID. -func (e *OneByteHeaderExtension) Get(id uint8) []byte { - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] >> 4 - payloadLen := int(e.payload[n]&^0xF0 + 1) - n++ - - if extid == id { - return e.payload[n : n+payloadLen] - } - n += payloadLen - } - return nil -} - -// Del deletes the extension with the specified ID. -func (e *OneByteHeaderExtension) Del(id uint8) error { - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] >> 4 - payloadLen := int(e.payload[n]&^0xF0 + 1) - - if extid == id { - e.payload = append(e.payload[:n], e.payload[n+1+payloadLen:]...) - return nil - } - n += payloadLen + 1 - } - return errHeaderExtensionNotFound -} - -// Unmarshal parses the extension payload. -func (e *OneByteHeaderExtension) Unmarshal(buf []byte) (int, error) { - profile := binary.BigEndian.Uint16(buf[0:2]) - if profile != headerExtensionProfileOneByte { - return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2]) - } - e.payload = buf - return len(buf), nil -} - -// Marshal returns the extension payload. -func (e OneByteHeaderExtension) Marshal() ([]byte, error) { - return e.payload, nil -} - -// MarshalTo writes the extension payload to the given buffer. -func (e OneByteHeaderExtension) MarshalTo(buf []byte) (int, error) { - size := e.MarshalSize() - if size > len(buf) { - return 0, io.ErrShortBuffer - } - return copy(buf, e.payload), nil -} - -// MarshalSize returns the size of the extension payload. -func (e OneByteHeaderExtension) MarshalSize() int { - return len(e.payload) -} - -// TwoByteHeaderExtension is an RFC8285 two-byte header extension. -type TwoByteHeaderExtension struct { - payload []byte -} - -// Set sets the extension payload for the specified ID. -func (e *TwoByteHeaderExtension) Set(id uint8, buf []byte) error { - if id < 1 || id > 255 { - return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id) - } - if len(buf) > 255 { - return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderSize, len(buf)) - } - - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] - n++ - - payloadLen := int(e.payload[n]) - n++ - - if extid == id { - e.payload = append(e.payload[:n+2], append(buf, e.payload[n+2+payloadLen:]...)...) - return nil - } - n += payloadLen - } - e.payload = append(e.payload, id, uint8(len(buf))) - e.payload = append(e.payload, buf...) - binary.BigEndian.PutUint16(e.payload[2:4], binary.BigEndian.Uint16(e.payload[2:4])+1) - return nil -} - -// GetIDs returns the available IDs. -func (e *TwoByteHeaderExtension) GetIDs() []uint8 { - ids := make([]uint8, 0, binary.BigEndian.Uint16(e.payload[2:4])) - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] - n++ - - payloadLen := int(e.payload[n]) - n++ - - ids = append(ids, extid) - n += payloadLen - } - return ids -} - -// Get returns the payload of the extension with the given ID. -func (e *TwoByteHeaderExtension) Get(id uint8) []byte { - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] - n++ - - payloadLen := int(e.payload[n]) - n++ - - if extid == id { - return e.payload[n : n+payloadLen] - } - n += payloadLen - } - return nil -} - -// Del deletes the extension with the specified ID. -func (e *TwoByteHeaderExtension) Del(id uint8) error { - for n := 4; n < len(e.payload); { - if e.payload[n] == 0x00 { // padding - n++ - continue - } - - extid := e.payload[n] - - payloadLen := int(e.payload[n+1]) - - if extid == id { - e.payload = append(e.payload[:n], e.payload[n+2+payloadLen:]...) - return nil - } - n += payloadLen + 2 - } - return errHeaderExtensionNotFound -} - -// Unmarshal parses the extension payload. -func (e *TwoByteHeaderExtension) Unmarshal(buf []byte) (int, error) { - profile := binary.BigEndian.Uint16(buf[0:2]) - if profile != headerExtensionProfileTwoByte { - return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2]) - } - e.payload = buf - return len(buf), nil -} - -// Marshal returns the extension payload. -func (e TwoByteHeaderExtension) Marshal() ([]byte, error) { - return e.payload, nil -} - -// MarshalTo marshals the extension to the given buffer. -func (e TwoByteHeaderExtension) MarshalTo(buf []byte) (int, error) { - size := e.MarshalSize() - if size > len(buf) { - return 0, io.ErrShortBuffer - } - return copy(buf, e.payload), nil -} - -// MarshalSize returns the size of the extension payload. -func (e TwoByteHeaderExtension) MarshalSize() int { - return len(e.payload) -} - -// RawExtension represents an RFC3550 header extension. -type RawExtension struct { - payload []byte -} - -// Set sets the extension payload for the specified ID. -func (e *RawExtension) Set(id uint8, payload []byte) error { - if id != 0 { - return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id) - } - e.payload = payload - return nil -} - -// GetIDs returns the available IDs. -func (e *RawExtension) GetIDs() []uint8 { - return []uint8{0} -} - -// Get returns the payload of the extension with the given ID. -func (e *RawExtension) Get(id uint8) []byte { - if id == 0 { - return e.payload - } - return nil -} - -// Del deletes the extension with the specified ID. -func (e *RawExtension) Del(id uint8) error { - if id == 0 { - e.payload = nil - return nil - } - return fmt.Errorf("%w actual(%d)", errRFC3550HeaderIDRange, id) -} - -// Unmarshal parses the extension from the given buffer. -func (e *RawExtension) Unmarshal(buf []byte) (int, error) { - profile := binary.BigEndian.Uint16(buf[0:2]) - if profile == headerExtensionProfileOneByte || profile == headerExtensionProfileTwoByte { - return 0, fmt.Errorf("%w actual(%x)", errHeaderExtensionNotFound, buf[0:2]) - } - e.payload = buf - return len(buf), nil -} - -// Marshal returns the raw extension payload. -func (e RawExtension) Marshal() ([]byte, error) { - return e.payload, nil -} - -// MarshalTo marshals the extension to the given buffer. -func (e RawExtension) MarshalTo(buf []byte) (int, error) { - size := e.MarshalSize() - if size > len(buf) { - return 0, io.ErrShortBuffer - } - return copy(buf, e.payload), nil -} - -// MarshalSize returns the size of the extension when marshaled. -func (e RawExtension) MarshalSize() int { - return len(e.payload) -} diff --git a/header_extension_test.go b/header_extension_test.go deleted file mode 100644 index bac3bd0..0000000 --- a/header_extension_test.go +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package 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") - } -} diff --git a/packet.go b/packet.go index ef4e5d8..b237b0a 100644 --- a/packet.go +++ b/packet.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -16,11 +13,13 @@ type Extension struct { } // Header represents an RTP packet header +// NOTE: PayloadOffset is populated by Marshal/Unmarshal and should not be modified type Header struct { Version uint8 Padding bool Extension bool Marker bool + PayloadOffset int PayloadType uint8 SequenceNumber uint16 Timestamp uint32 @@ -31,10 +30,11 @@ type Header struct { } // Packet represents an RTP Packet +// NOTE: Raw is populated by Marshal/Unmarshal and should not be modified type Packet struct { Header - Payload []byte - PaddingSize byte + Raw []byte + Payload []byte } const ( @@ -77,11 +77,10 @@ func (p Packet) String() string { 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) +// Unmarshal parses the passed byte slice and stores the result in the Header this method is called upon +func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit + if len(rawPacket) < headerLength { + return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(rawPacket), headerLength) } /* @@ -99,32 +98,31 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - 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) + h.Version = rawPacket[0] >> versionShift & versionMask + h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0 + h.Extension = (rawPacket[0] >> extensionShift & extensionMask) > 0 + nCSRC := int(rawPacket[0] & ccMask) if cap(h.CSRC) < nCSRC || h.CSRC == nil { h.CSRC = make([]uint32, nCSRC) } else { h.CSRC = h.CSRC[:nCSRC] } - n = csrcOffset + (nCSRC * csrcLength) - if len(buf) < n { - return n, fmt.Errorf("size %d < %d: %w", len(buf), n, - errHeaderSizeInsufficient) + currOffset := csrcOffset + (nCSRC * csrcLength) + if len(rawPacket) < currOffset { + return fmt.Errorf("size %d < %d: %w", len(rawPacket), currOffset, errHeaderSizeInsufficient) } - h.Marker = (buf[1] >> markerShift & markerMask) > 0 - h.PayloadType = buf[1] & ptMask + h.Marker = (rawPacket[1] >> markerShift & markerMask) > 0 + h.PayloadType = rawPacket[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]) + h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength]) + h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength]) + h.SSRC = binary.BigEndian.Uint32(rawPacket[ssrcOffset : ssrcOffset+ssrcLength]) for i := range h.CSRC { offset := csrcOffset + (i * csrcLength) - h.CSRC[i] = binary.BigEndian.Uint32(buf[offset:]) + h.CSRC[i] = binary.BigEndian.Uint32(rawPacket[offset:]) } if h.Extensions != nil { @@ -132,21 +130,21 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit } if h.Extension { - if expected := n + 4; len(buf) < expected { - return n, fmt.Errorf("size %d < %d: %w", - len(buf), expected, + if expected := currOffset + 4; len(rawPacket) < expected { + return fmt.Errorf("size %d < %d: %w", + len(rawPacket), expected, errHeaderSizeInsufficientForExtension, ) } - h.ExtensionProfile = binary.BigEndian.Uint16(buf[n:]) - n += 2 - extensionLength := int(binary.BigEndian.Uint16(buf[n:])) * 4 - n += 2 + h.ExtensionProfile = binary.BigEndian.Uint16(rawPacket[currOffset:]) + currOffset += 2 + extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4 + currOffset += 2 - if expected := n + extensionLength; len(buf) < expected { - return n, fmt.Errorf("size %d < %d: %w", - len(buf), expected, + if expected := currOffset + extensionLength; len(rawPacket) < expected { + return fmt.Errorf("size %d < %d: %w", + len(rawPacket), expected, errHeaderSizeInsufficientForExtension, ) } @@ -154,91 +152,87 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit switch h.ExtensionProfile { // RFC 8285 RTP One Byte Header Extension case extensionProfileOneByte: - end := n + extensionLength - for n < end { - if buf[n] == 0x00 { // padding - n++ + end := currOffset + extensionLength + for currOffset < end { + if rawPacket[currOffset] == 0x00 { // padding + currOffset++ continue } - extid := buf[n] >> 4 - payloadLen := int(buf[n]&^0xF0 + 1) - n++ + extid := rawPacket[currOffset] >> 4 + len := int(rawPacket[currOffset]&^0xF0 + 1) + currOffset++ if extid == extensionIDReserved { break } - extension := Extension{id: extid, payload: buf[n : n+payloadLen]} + extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]} h.Extensions = append(h.Extensions, extension) - n += payloadLen + currOffset += len } // RFC 8285 RTP Two Byte Header Extension case extensionProfileTwoByte: - end := n + extensionLength - for n < end { - if buf[n] == 0x00 { // padding - n++ + end := currOffset + extensionLength + for currOffset < end { + if rawPacket[currOffset] == 0x00 { // padding + currOffset++ continue } - extid := buf[n] - n++ + extid := rawPacket[currOffset] + currOffset++ - payloadLen := int(buf[n]) - n++ + len := int(rawPacket[currOffset]) + currOffset++ - extension := Extension{id: extid, payload: buf[n : n+payloadLen]} + extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]} h.Extensions = append(h.Extensions, extension) - n += payloadLen + currOffset += len } default: // RFC3550 Extension - if len(buf) < n+extensionLength { - return n, fmt.Errorf("%w: %d < %d", - errHeaderSizeInsufficientForExtension, len(buf), n+extensionLength) + if len(rawPacket) < currOffset+extensionLength { + return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, len(rawPacket), currOffset+extensionLength) } - extension := Extension{id: 0, payload: buf[n : n+extensionLength]} + extension := Extension{id: 0, payload: rawPacket[currOffset : currOffset+extensionLength]} h.Extensions = append(h.Extensions, extension) - n += len(h.Extensions[0].payload) + currOffset += len(h.Extensions[0].payload) } } - return n, nil + + h.PayloadOffset = currOffset + + return 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 { +// Unmarshal parses the passed byte slice and stores the result in the Packet this method is called upon +func (p *Packet) Unmarshal(rawPacket []byte) error { + if err := p.Header.Unmarshal(rawPacket); err != nil { return err } - 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] + + p.Payload = rawPacket[p.PayloadOffset:] + p.Raw = rawPacket return nil } // 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) 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) { +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 @@ -259,8 +253,7 @@ func (h Header) MarshalTo(buf []byte) (n int, err error) { return 0, io.ErrShortBuffer } - // The first byte contains the version, padding bit, extension bit, - // and csrc size. + // 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 @@ -331,11 +324,13 @@ func (h Header) MarshalTo(buf []byte) (n int, err error) { } } + h.PayloadOffset = n + return n, nil } // 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) @@ -404,10 +399,10 @@ func (h *Header) SetExtension(id uint8, payload []byte) error { //nolint:gocogni // No existing header extensions h.Extension = true - switch payloadLen := len(payload); { - case payloadLen <= 16: + switch len := len(payload); { + case len <= 16: h.ExtensionProfile = extensionProfileOneByte - case payloadLen > 16 && payloadLen < 256: + case len > 16 && len < 256: h.ExtensionProfile = extensionProfileTwoByte } @@ -460,7 +455,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) @@ -472,59 +467,24 @@ 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) { +func (p *Packet) MarshalTo(buf []byte) (n int, err error) { n, err = p.Header.MarshalTo(buf) if err != nil { return 0, err } // Make sure the buffer is large enough to hold the packet. - if n+len(p.Payload)+int(p.PaddingSize) > len(buf) { + if n+len(p.Payload) > 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 - } + p.Raw = buf[:n+m] - return n + m + int(p.PaddingSize), nil + return n + m, nil } // MarshalSize returns the size of the packet once marshaled. -func (p Packet) MarshalSize() int { - return p.Header.MarshalSize() + len(p.Payload) + 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 +func (p *Packet) MarshalSize() int { + return p.Header.MarshalSize() + len(p.Payload) } diff --git a/packet_test.go b/packet_test.go index 3044c0d..a1cfa8e 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -25,7 +22,6 @@ func TestBasic(t *testing.T) { } parsedPacket := &Packet{ Header: Header{ - Padding: false, Marker: true, Extension: true, ExtensionProfile: 1, @@ -35,14 +31,15 @@ func TestBasic(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 20, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, }, - Payload: rawPkt[20:], - PaddingSize: 0, + Payload: rawPkt[20:], + Raw: rawPkt, } // Unmarshal to the used Packet should work as well. @@ -60,216 +57,21 @@ func TestBasic(t *testing.T) { t.Errorf("wrong computed marshal size") } + if p.PayloadOffset != 20 { + t.Errorf("wrong payload offset: %d != %d", p.PayloadOffset, 20) + } + 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, - Padding: true, - 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) + if p.PayloadOffset != 20 { + t.Errorf("wrong payload offset: %d != %d", p.PayloadOffset, 20) + } + }) } } @@ -332,6 +134,7 @@ func TestRFC8285OneByteExtension(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 18, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -339,6 +142,7 @@ func TestRFC8285OneByteExtension(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[20:], + Raw: rawPkt, } dstData, _ := p.Marshal() @@ -394,6 +198,7 @@ func TestRFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -401,6 +206,7 @@ func TestRFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[20:], + Raw: rawPkt, } dstData, _ := p.Marshal() @@ -517,6 +323,7 @@ func TestRFC8285OneByteMultipleExtensions(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -524,6 +331,7 @@ func TestRFC8285OneByteMultipleExtensions(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[28:], + Raw: rawPkt, } dstData, _ := p.Marshal() @@ -559,6 +367,7 @@ func TestRFC8285TwoByteExtension(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 42, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -566,6 +375,7 @@ func TestRFC8285TwoByteExtension(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[44:], + Raw: rawPkt, } dstData, _ := p.Marshal() @@ -660,6 +470,7 @@ func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 40, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -667,6 +478,7 @@ func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[40:], + Raw: rawPkt, } dstData, _ := p.Marshal() @@ -685,6 +497,7 @@ func TestRFC8285GetExtensionReturnsNilWhenExtensionsDisabled(t *testing.T) { Marker: true, Extension: false, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -716,6 +529,7 @@ func TestRFC8285DelExtension(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -765,6 +579,7 @@ func TestRFC8285GetExtensionIDs(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -800,6 +615,7 @@ func TestRFC8285GetExtensionIDsReturnsErrorWhenExtensionsDisabled(t *testing.T) Marker: true, Extension: false, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -825,6 +641,7 @@ func TestRFC8285DelExtensionReturnsErrorWhenExtensionsDisabled(t *testing.T) { Marker: true, Extension: false, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -850,6 +667,7 @@ func TestRFC8285OneByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) Marker: true, Extension: false, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -892,6 +710,7 @@ func TestRFC8285OneByteSetExtensionShouldSetCorrectExtensionProfileFor16ByteExte Marker: true, Extension: false, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -933,6 +752,7 @@ func TestRFC8285OneByteSetExtensionShouldUpdateExistingExension(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -973,6 +793,7 @@ func TestRFC8285OneByteSetExtensionShouldErrorWhenInvalidIDProvided(t *testing.T }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1030,6 +851,7 @@ func TestRFC8285OneByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1057,6 +879,7 @@ func TestRFC8285TwoByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) Marker: true, Extension: false, Version: 2, + PayloadOffset: 31, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1108,6 +931,7 @@ func TestRFC8285TwoByteSetExtensionShouldUpdateExistingExension(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1151,6 +975,7 @@ func TestRFC8285TwoByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1208,6 +1033,7 @@ func TestRFC3550SetExtensionShouldErrorWhenNonZero(t *testing.T) { }}, }, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1239,6 +1065,7 @@ func TestRFC3550SetExtensionShouldRaiseErrorWhenSettingNonzeroID(t *testing.T) { Extension: true, ExtensionProfile: 0x1111, Version: 2, + PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1307,7 +1134,7 @@ func TestUnmarshal_ErrorHandling(t *testing.T) { testCase := testCase t.Run(name, func(t *testing.T) { h := &Header{} - _, err := h.Unmarshal(testCase.input) + err := h.Unmarshal(testCase.input) if !errors.Is(err, testCase.err) { t.Errorf("Expected error: %v, got: %v", testCase.err, err) } @@ -1326,6 +1153,9 @@ func TestRoundtrip(t *testing.T) { if err := p.Unmarshal(rawPkt); err != nil { t.Fatal(err) } + if !bytes.Equal(rawPkt, p.Raw) { + t.Errorf("p.Raw must be same as rawPkt.\n p.Raw: %+v,\nrawPkt: %+v", p.Raw, 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, @@ -1339,6 +1169,9 @@ func TestRoundtrip(t *testing.T) { if !bytes.Equal(rawPkt, buf) { t.Errorf("buf must be same as rawPkt.\n buf: %+v,\nrawPkt: %+v", buf, rawPkt) } + if !bytes.Equal(rawPkt, p.Raw) { + t.Errorf("p.Raw must be same as rawPkt.\n p.Raw: %+v,\nrawPkt: %+v", p.Raw, 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, @@ -1346,59 +1179,6 @@ func TestRoundtrip(t *testing.T) { } } -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, diff --git a/packetizer.go b/packetizer.go index c9e3a93..edde1e1 100644 --- a/packetizer.go +++ b/packetizer.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -9,7 +6,7 @@ import ( // Payloader payloads a byte array for use as rtp.Packet payloads type Payloader interface { - Payload(mtu uint16, payload []byte) [][]byte + Payload(mtu int, payload []byte) [][]byte } // Packetizer packetizes a payload @@ -20,12 +17,13 @@ type Packetizer interface { } type packetizer struct { - MTU uint16 + MTU int PayloadType uint8 SSRC uint32 Payloader Payloader Sequencer Sequencer Timestamp uint32 + ClockRate uint32 extensionNumbers struct { // put extension numbers in here. If they're 0, the extension is disabled (0 is not a legal extension number) AbsSendTime int // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time } @@ -33,7 +31,7 @@ type packetizer struct { } // NewPacketizer returns a new instance of a Packetizer for a specific payloader -func NewPacketizer(mtu uint16, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer) Packetizer { +func NewPacketizer(mtu int, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32) Packetizer { return &packetizer{ MTU: mtu, PayloadType: pt, @@ -41,6 +39,7 @@ func NewPacketizer(mtu uint16, pt uint8, ssrc uint32, payloader Payloader, seque Payloader: payloader, Sequencer: sequencer, Timestamp: globalMathRandomGenerator.Uint32(), + ClockRate: clockRate, timegen: time.Now, } } @@ -70,7 +69,6 @@ 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 c7e91c4..5d2331a 100644 --- a/packetizer_test.go +++ b/packetizer_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -9,13 +6,13 @@ import ( "testing" "time" - "github.com/pion/rtp/v2/codecs" + "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()) + packetizer := NewPacketizer(100, 98, 0x1234ABCD, &codecs.G722Payloader{}, NewRandomSequencer(), 90000) packets := packetizer.Packetize(multiplepayload, 2000) if len(packets) != 2 { @@ -29,14 +26,9 @@ 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)) - p, ok := pktizer.(*packetizer) - if !ok { - t.Fatal("Failed to access packetizer") - } - - p.Timestamp = 45678 - p.timegen = func() time.Time { + 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 } @@ -51,11 +43,12 @@ func TestPacketizer_AbsSendTime(t *testing.T) { Padding: false, Extension: true, Marker: true, + PayloadOffset: 0, // not set by Packetize() at now PayloadType: 98, SequenceNumber: 1234, Timestamp: 45678, SSRC: 0x1234ABCD, - CSRC: []uint32{}, + CSRC: nil, ExtensionProfile: 0xBEDE, Extensions: []Extension{ { @@ -74,77 +67,3 @@ 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/partitionheadchecker.go b/partitionheadchecker.go index 6f05aa5..6ec2a76 100644 --- a/partitionheadchecker.go +++ b/partitionheadchecker.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp // PartitionHeadChecker is the interface that checks whether the packet is keyframe or not diff --git a/pkg/frame/av1.go b/pkg/frame/av1.go deleted file mode 100644 index 1a13a5a..0000000 --- a/pkg/frame/av1.go +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -// 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 deleted file mode 100644 index 3f9d9da..0000000 --- a/pkg/frame/av1_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -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 deleted file mode 100644 index f0734f0..0000000 --- a/pkg/obu/leb128.go +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -// Package obu implements tools for working with the "Open Bitstream Unit" -package obu - -import "errors" - -const ( - sevenLsbBitmask = uint(0b01111111) - msbBitmask = uint(0b10000000) -) - -// ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read -var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished") - -// EncodeLEB128 encodes a uint as LEB128 -func EncodeLEB128(in uint) (out uint) { - for { - // Copy seven bits from in and discard - // what we have copied from in - out |= (in & sevenLsbBitmask) - in >>= 7 - - // If we have more bits to encode set MSB - // otherwise we are done - if in != 0 { - out |= msbBitmask - out <<= 8 - } else { - return out - } - } -} - -func decodeLEB128(in uint) (out uint) { - for { - // Take 7 LSB from in - out |= (in & sevenLsbBitmask) - - // Discard the MSB - in >>= 8 - if in == 0 { - return out - } - - out <<= 7 - } -} - -// ReadLeb128 scans an buffer and decodes a Leb128 value. -// If the end of the buffer is reached and all MSB are set -// an error is returned -func ReadLeb128(in []byte) (uint, uint, error) { - var encodedLength uint - - for i := range in { - encodedLength |= uint(in[i]) - - if in[i]&byte(msbBitmask) == 0 { - return decodeLEB128(encodedLength), uint(i + 1), nil - } - - // Make more room for next read - encodedLength <<= 8 - } - - return 0, 0, ErrFailedToReadLEB128 -} diff --git a/pkg/obu/leb128_test.go b/pkg/obu/leb128_test.go deleted file mode 100644 index f92fff4..0000000 --- a/pkg/obu/leb128_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -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/playoutdelayextension.go b/playoutdelayextension.go deleted file mode 100644 index 3882731..0000000 --- a/playoutdelayextension.go +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package rtp - -import ( - "encoding/binary" - "errors" -) - -const ( - playoutDelayExtensionSize = 3 - playoutDelayMaxValue = (1 << 12) - 1 -) - -var errPlayoutDelayInvalidValue = errors.New("invalid playout delay value") - -// PlayoutDelayExtension is a extension payload format in -// http://www.webrtc.org/experiments/rtp-hdrext/playout-delay -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | ID | len=2 | MIN delay | MAX delay | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -type PlayoutDelayExtension struct { - minDelay, maxDelay uint16 -} - -// Marshal serializes the members to buffer -func (p PlayoutDelayExtension) Marshal() ([]byte, error) { - if p.minDelay > playoutDelayMaxValue || p.maxDelay > playoutDelayMaxValue { - return nil, errPlayoutDelayInvalidValue - } - - return []byte{ - byte(p.minDelay >> 4), - byte(p.minDelay<<4) | byte(p.maxDelay>>8), - byte(p.maxDelay), - }, nil -} - -// Unmarshal parses the passed byte slice and stores the result in the members -func (p *PlayoutDelayExtension) Unmarshal(rawData []byte) error { - if len(rawData) < playoutDelayExtensionSize { - return errTooSmall - } - p.minDelay = binary.BigEndian.Uint16(rawData[0:2]) >> 4 - p.maxDelay = binary.BigEndian.Uint16(rawData[1:3]) & 0x0FFF - return nil -} diff --git a/playoutdelayextension_test.go b/playoutdelayextension_test.go deleted file mode 100644 index 8810466..0000000 --- a/playoutdelayextension_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package rtp - -import ( - "bytes" - "errors" - "testing" -) - -func TestPlayoutDelayExtensionTooSmall(t *testing.T) { - t1 := PlayoutDelayExtension{} - - var rawData []byte - - if err := t1.Unmarshal(rawData); !errors.Is(err, errTooSmall) { - t.Fatal("err != errTooSmall") - } -} - -func TestPlayoutDelayExtensionTooLarge(t *testing.T) { - t1 := PlayoutDelayExtension{minDelay: 1 << 12, maxDelay: 1 << 12} - - if _, err := t1.Marshal(); !errors.Is(err, errPlayoutDelayInvalidValue) { - t.Fatal("err != errPlayoutDelayInvalidValue") - } -} - -func TestPlayoutDelayExtension(t *testing.T) { - t1 := PlayoutDelayExtension{} - - rawData := []byte{ - 0x01, 0x01, 0x00, - } - - if err := t1.Unmarshal(rawData); err != nil { - t.Fatal("Unmarshal error on extension data") - } - - t2 := PlayoutDelayExtension{ - minDelay: 1 << 4, maxDelay: 1 << 8, - } - - if t1 != t2 { - t.Error("Unmarshal failed") - } - - dstData, _ := t2.Marshal() - if !bytes.Equal(dstData, rawData) { - t.Error("Marshal failed") - } -} - -func TestPlayoutDelayExtensionExtraBytes(t *testing.T) { - t1 := PlayoutDelayExtension{} - - rawData := []byte{ - 0x01, 0x01, 0x00, 0xff, 0xff, - } - - if err := t1.Unmarshal(rawData); err != nil { - t.Fatal("Unmarshal error on extension data") - } - - t2 := PlayoutDelayExtension{ - minDelay: 1 << 4, maxDelay: 1 << 8, - } - - if t1 != t2 { - t.Error("Unmarshal failed") - } -} diff --git a/rand.go b/rand.go index 3ddddd1..ee85523 100644 --- a/rand.go +++ b/rand.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( diff --git a/renovate.json b/renovate.json index f1bb98c..f84608c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,19 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "github>pion/renovate-config" + "config:base" + ], + "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"] + } ] } diff --git a/rtp.go b/rtp.go index 5487232..b66b2e4 100644 --- a/rtp.go +++ b/rtp.go @@ -1,5 +1,2 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - // Package rtp provides RTP packetizer and depacketizer package rtp diff --git a/sequencer.go b/sequencer.go index 8ad2cfd..2b4a507 100644 --- a/sequencer.go +++ b/sequencer.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( diff --git a/transportccextension.go b/transportccextension.go index c2a998c..f9ffe4e 100644 --- a/transportccextension.go +++ b/transportccextension.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( @@ -26,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 diff --git a/transportccextension_test.go b/transportccextension_test.go index 5a06f2c..5eb6967 100644 --- a/transportccextension_test.go +++ b/transportccextension_test.go @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - package rtp import ( From d8a4cf08d3fe6871a2296077f4027d13b2e57072 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Fri, 2 Jul 2021 02:17:02 +0000 Subject: [PATCH 019/102] Update CI configs to v0.5.4 Update lint scripts and CI configs. --- ...rt-contributors.sh => generate-authors.sh} | 29 ++++++----- .github/hooks/pre-push.sh | 2 +- .github/workflows/generate-authors.yml | 48 +++++++++++++++++++ .github/workflows/lint.yaml | 3 -- AUTHORS.txt | 30 ++++++++++++ README.md | 22 --------- 6 files changed, 96 insertions(+), 38 deletions(-) rename .github/{assert-contributors.sh => generate-authors.sh} (58%) create mode 100644 .github/workflows/generate-authors.yml create mode 100644 AUTHORS.txt diff --git a/.github/assert-contributors.sh b/.github/generate-authors.sh similarity index 58% rename from .github/assert-contributors.sh rename to .github/generate-authors.sh index 12e6afe..182e4f5 100755 --- a/.github/assert-contributors.sh +++ b/.github/generate-authors.sh @@ -12,6 +12,7 @@ set -e SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +AUTHORS_PATH="$GITHUB_WORKSPACE/AUTHORS.txt" if [ -f ${SCRIPT_PATH}/.ci.conf ] then @@ -21,18 +22,18 @@ fi # # DO NOT EDIT THIS # -EXCLUDED_CONTRIBUTORS+=('John R. Bradley' 'renovate[bot]' 'Renovate Bot' 'Pion Bot') +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 -MISSING_CONTRIBUTORS=() +CONTRIBUTORS=() shouldBeIncluded () { for i in "${EXCLUDED_CONTRIBUTORS[@]}" do - if [ "$i" == "$1" ] ; then + if [[ $1 =~ "$i" ]]; then return 1 fi done @@ -41,21 +42,25 @@ shouldBeIncluded () { IFS=$'\n' #Only split on newline -for contributor in $(git log --format='%aN' | sort -u) +for contributor in $(git log --format='%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf) do if shouldBeIncluded $contributor; then - if ! grep -q "$contributor" "$SCRIPT_PATH/../README.md"; then - MISSING_CONTRIBUTORS+=("$contributor") - fi + CONTRIBUTORS+=("$contributor") fi done unset IFS -if [ ${#MISSING_CONTRIBUTORS[@]} -ne 0 ]; then - echo "Please add the following contributors to the README" - for i in "${MISSING_CONTRIBUTORS[@]}" +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" + echo "$i" >> $AUTHORS_PATH done - exit 1 + exit 0 fi diff --git a/.github/hooks/pre-push.sh b/.github/hooks/pre-push.sh index 7cb2365..bfe65bc 100755 --- a/.github/hooks/pre-push.sh +++ b/.github/hooks/pre-push.sh @@ -8,6 +8,6 @@ set -e -.github/assert-contributors.sh +.github/generate-authors.sh exit 0 diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml new file mode 100644 index 0000000..a0a7478 --- /dev/null +++ b/.github/workflows/generate-authors.yml @@ -0,0 +1,48 @@ +name: generate-authors + +on: + pull_request: + +jobs: + generate-authors: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + 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)" + + - uses: stefanzweifel/git-auto-commit-action@v4 + if: ${{ steps.git-status-output.outputs.msg != '0' }} + with: + commit_message: ${{ steps.last-commit-message.outputs.msg }} + commit_author: ${{ steps.last-commit-author.outputs.msg }} + commit_options: '--amend --no-edit' + push_options: '--force' + skip_fetch: true diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 8824c34..bc44c3a 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -22,9 +22,6 @@ jobs: - name: File names run: .github/lint-filename.sh - - name: Contributors - run: .github/assert-contributors.sh - - name: Functions run: .github/lint-disallowed-functions-in-library.sh diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 0000000..5e5c680 --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,30 @@ +# 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é +Atsushi Watanabe +Bao Nguyen +debiandebiandebian +ffmiyo +Guilherme +Haiyang Wang +Hugo Arregui +John Bradley +Juliusz Chroboczek +Kazuyuki Honda +Luke Curley +lxb +Michael MacDonald +Michael MacDonald +Raphael Derosso Pereira +Rob Lofthouse +Robin Raymond +Sean DuBois +Sean DuBois +Simone Gotti +Tarrence van As +Woodrow Douglass diff --git a/README.md b/README.md index 22b4ece..ce04599 100644 --- a/README.md +++ b/README.md @@ -30,27 +30,5 @@ If you need commercial support or don't want to use public methods you can conta ### Contributing Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: -* [John Bradley](https://github.com/kc5nra) - *Original Author* -* [Sean DuBois](https://github.com/Sean-Der) - *Original Author* -* [Woodrow Douglass](https://github.com/wdouglass) *RTCP, RTP improvements, G.722 support, Bugfixes* -* [Michael MacDonald](https://github.com/mjmac) -* [Luke Curley](https://github.com/kixelated) *Performance* -* [Antoine Baché](https://github.com/Antonito) *Fixed crashes* -* [Hugo Arregui](https://github.com/hugoArregui) -* [Raphael Derosso Pereira](https://github.com/raphaelpereira) -* [Atsushi Watanabe](https://github.com/at-wat) -* [adwpc](https://github.com/adwpc) *add transport-cc extension* -* [Bao Nguyen](https://github.com/sysbot) *add VP9 noop, bug fixes. -* [Tarrence van As](https://github.com/tarrencev) *add audio level extension* -* [Simone Gotti](https://github.com/sgotti) -* [Guilherme Souza](https://github.com/gqgs) -* [Rob Lofthouse](https://github.com/roblofthouse) -* [Kazuyuki Honda](https://github.com/hakobera) -* [Haiyang Wang](https://github.com/ocean2811) -* [lxb](https://github.com/lxb531) -* [Robin Raymond](https://github.com/robin-raymond) -* [debiandebiandebian](https://github.com/debiandebiandebian) -* [Juliusz Chroboczek](https://github.com/jech) - ### License MIT License - see [LICENSE](LICENSE) for full text From 9cfc943eb072ad6d3f2d0fa4b6db00e1d58210b2 Mon Sep 17 00:00:00 2001 From: Atsushi Watanabe Date: Thu, 8 Jul 2021 09:46:14 +0900 Subject: [PATCH 020/102] Make VP8 PictureID optional In RFC 7741, PictureID is not mandatory and required only if SLI is used. --- codecs/vp8_packet.go | 41 +++++++------ codecs/vp8_packet_test.go | 120 ++++++++++++++++++++++++++++++-------- 2 files changed, 118 insertions(+), 43 deletions(-) diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index b602ef5..24e7200 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -2,7 +2,8 @@ package codecs // VP8Payloader payloads VP8 packets type VP8Payloader struct { - pictureID uint16 + EnablePictureID bool + pictureID uint16 } const ( @@ -33,12 +34,14 @@ func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte { */ usingHeaderSize := vp8HeaderSize - switch { - case p.pictureID == 0: - case p.pictureID < 128: - usingHeaderSize = vp8HeaderSize + 2 - default: - usingHeaderSize = vp8HeaderSize + 3 + if p.EnablePictureID { + switch { + case p.pictureID == 0: + case p.pictureID < 128: + usingHeaderSize = vp8HeaderSize + 2 + default: + usingHeaderSize = vp8HeaderSize + 3 + } } maxFragmentSize := mtu - usingHeaderSize @@ -62,17 +65,19 @@ func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte { out[0] = 0x10 first = false } - 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) + 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]) diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index 1c062c3..37d160d 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -2,6 +2,7 @@ package codecs import ( "errors" + "reflect" "testing" ) @@ -109,33 +110,102 @@ func TestVP8Packet_Unmarshal(t *testing.T) { } func TestVP8Payloader_Payload(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") - } + testCases := map[string]struct { + payloader VP8Payloader + mtu int + 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") + } - // Negative MTU, small payload - res = pck.Payload(-1, payload) - 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") + } - // Positive MTU, small payload - res = pck.Payload(2, payload) - if len(res) != len(payload) { - t.Fatal("Generated payload should be the same size as original payload size") - } + // Negative MTU, small payload + res = pck.Payload(-1, payload) + if len(res) != 0 { + t.Fatal("Generated payload should be empty") + } + }) } func TestVP8PartitionHeadChecker_IsPartitionHead(t *testing.T) { From b019f080dc1866b3795c9efbf77f5b04cc00e0de Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 13 Jul 2021 19:53:38 +0000 Subject: [PATCH 021/102] Update CI configs to v0.5.6 Update lint scripts and CI configs. --- .github/workflows/generate-authors.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index a0a7478..e619025 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -4,7 +4,21 @@ 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@v2 From bd30a997e417c8ea5c773f928471c64989f8ef3b Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Mon, 17 May 2021 15:29:37 +0200 Subject: [PATCH 022/102] Add Z bit to VP9 unmarshaller This was added in draft-ietf-payload-vp9-10. --- codecs/vp9_packet.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 0396554..9d05340 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -25,12 +25,12 @@ const ( // Payload fragments an VP9 packet across one or more byte arrays func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte { /* - * https://www.ietf.org/id/draft-ietf-payload-vp9-10.txt + * 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|-| (REQUIRED) + * |I|P|L|F|B|E|V|Z| (REQUIRED) * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | (REQUIRED) * +-+-+-+-+-+-+-+-+ @@ -47,7 +47,7 @@ func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte { * Non-flexible mode (F=0) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ - * |I|P|L|F|B|E|V|-| (REQUIRED) + * |I|P|L|F|B|E|V|Z| (REQUIRED) * +-+-+-+-+-+-+-+-+ * I: |M| PICTURE ID | (RECOMMENDED) * +-+-+-+-+-+-+-+-+ @@ -121,6 +121,7 @@ type VP9Packet struct { 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 @@ -171,6 +172,7 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { 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 From 3cf5c5ee8bce81f26edaecb3098156da4a98a987 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Wed, 21 Jul 2021 00:26:37 +0000 Subject: [PATCH 023/102] Update CI configs to v0.5.9 Update lint scripts and CI configs. --- .github/workflows/generate-authors.yml | 25 +++++++++---- .github/workflows/lint.yaml | 11 ++++++ .github/workflows/test.yaml | 52 ++++++++++++++++++-------- renovate.json | 7 ++++ 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index e619025..83e7065 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -1,3 +1,14 @@ +# +# 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: @@ -52,11 +63,11 @@ jobs: run: | echo "::set-output name=msg::$(git status -s | wc -l)" - - uses: stefanzweifel/git-auto-commit-action@v4 + - name: Commit and push if: ${{ steps.git-status-output.outputs.msg != '0' }} - with: - commit_message: ${{ steps.last-commit-message.outputs.msg }} - commit_author: ${{ steps.last-commit-author.outputs.msg }} - commit_options: '--amend --no-edit' - push_options: '--force' - skip_fetch: true + 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) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index bc44c3a..f096078 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,3 +1,14 @@ +# +# 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: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5b7a43b..7a72c2c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,3 +1,14 @@ +# +# 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: @@ -39,9 +50,17 @@ jobs: - name: Run test run: | + TEST_BENCH_OPTION="-bench=." + if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi + go-acc -o cover.out ./... -- \ - -bench=. \ - -v -race + ${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@v1 with: @@ -73,17 +92,17 @@ jobs: 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 ./... + -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 @@ -126,10 +145,11 @@ jobs: - 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 ./... + -coverprofile=cover.out -covermode=atomic \ + -exec="${GO_JS_WASM_EXEC}" \ + -v ./... - uses: codecov/codecov-action@v1 with: diff --git a/renovate.json b/renovate.json index f84608c..08c1e39 100644 --- a/renovate.json +++ b/renovate.json @@ -15,5 +15,12 @@ "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" ] } From ae7623a791c9c1f25dfb8a003f44eecabadd9fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20Bach=C3=A9?= Date: Fri, 25 Jun 2021 10:44:59 +0200 Subject: [PATCH 024/102] Implement a H265/HEVC packet decoder Relates to #87 --- codecs/h265_packet.go | 803 +++++++++++++++++++++++++++++++++++ codecs/h265_packet_test.go | 844 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1647 insertions(+) create mode 100644 codecs/h265_packet.go create mode 100644 codecs/h265_packet_test.go diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go new file mode 100644 index 0000000..a4ae843 --- /dev/null +++ b/codecs/h265_packet.go @@ -0,0 +1,803 @@ +package codecs + +import ( + "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 +} + +// 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 +} diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go new file mode 100644 index 0000000..7c25a9c --- /dev/null +++ b/codecs/h265_packet_test.go @@ -0,0 +1,844 @@ +package 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 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 +} From 3b47cb1f3c30e7fee25a75dd543a215d6efb9dbb Mon Sep 17 00:00:00 2001 From: Michael Uti Date: Thu, 28 Jan 2021 20:35:01 +0100 Subject: [PATCH 025/102] Make MTU a uint16 A MTU can never be negative so this removes a class of runtime errors. --- AUTHORS.txt | 2 ++ codecs/g711_packet.go | 4 ++-- codecs/g711_packet_test.go | 15 ++------------- codecs/g722_packet.go | 6 +++--- codecs/g722_packet_test.go | 15 ++------------- codecs/h264_packet.go | 6 +++--- codecs/opus_packet.go | 2 +- codecs/opus_packet_test.go | 6 ------ codecs/vp8_packet.go | 4 ++-- codecs/vp8_packet_test.go | 8 +------- codecs/vp9_packet.go | 4 ++-- codecs/vp9_packet_test.go | 7 +------ packetizer.go | 6 +++--- 13 files changed, 24 insertions(+), 61 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 5e5c680..0a0c424 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -6,6 +6,7 @@ adwpc aler9 <46489434+aler9@users.noreply.github.com> Antoine Baché +Antoine Baché Atsushi Watanabe Bao Nguyen debiandebiandebian @@ -20,6 +21,7 @@ Luke Curley lxb Michael MacDonald Michael MacDonald +Michael Uti Raphael Derosso Pereira Rob Lofthouse Robin Raymond diff --git a/codecs/g711_packet.go b/codecs/g711_packet.go index a74876f..2348a79 100644 --- a/codecs/g711_packet.go +++ b/codecs/g711_packet.go @@ -4,13 +4,13 @@ package codecs type G711Payloader struct{} // Payload fragments an G711 packet across one or more byte arrays -func (p *G711Payloader) Payload(mtu int, payload []byte) [][]byte { +func (p *G711Payloader) Payload(mtu uint16, payload []byte) [][]byte { var out [][]byte if payload == nil || mtu <= 0 { return out } - for len(payload) > mtu { + for len(payload) > int(mtu) { o := make([]byte, mtu) copy(o, payload[:mtu]) payload = payload[mtu:] diff --git a/codecs/g711_packet_test.go b/codecs/g711_packet_test.go index d260a06..36ee729 100644 --- a/codecs/g711_packet_test.go +++ b/codecs/g711_packet_test.go @@ -45,20 +45,9 @@ func TestG711Payloader(t *testing.T) { } payload := []byte{0x90, 0x90, 0x90} - // Nil payload - res := p.Payload(-1, nil) - if len(res) != 0 { - t.Fatal("Generated payload should be empty") - } - - // Negative MTU, small payload - res = p.Payload(-1, payload) - if len(res) != 0 { - t.Fatal("Generated payload should be empty") - } // 0 MTU, small payload - res = p.Payload(0, payload) + res := p.Payload(0, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } @@ -70,7 +59,7 @@ func TestG711Payloader(t *testing.T) { } // Positive MTU, small payload - res = p.Payload(len(payload)-1, 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") } diff --git a/codecs/g722_packet.go b/codecs/g722_packet.go index 70c9883..13e17b6 100644 --- a/codecs/g722_packet.go +++ b/codecs/g722_packet.go @@ -4,13 +4,13 @@ package codecs type G722Payloader struct{} // Payload fragments an G722 packet across one or more byte arrays -func (p *G722Payloader) Payload(mtu int, payload []byte) [][]byte { +func (p *G722Payloader) Payload(mtu uint16, payload []byte) [][]byte { var out [][]byte - if payload == nil || mtu <= 0 { + if payload == nil || mtu == 0 { return out } - for len(payload) > mtu { + for len(payload) > int(mtu) { o := make([]byte, mtu) copy(o, payload[:mtu]) payload = payload[mtu:] diff --git a/codecs/g722_packet_test.go b/codecs/g722_packet_test.go index 8d1198b..cc75e6f 100644 --- a/codecs/g722_packet_test.go +++ b/codecs/g722_packet_test.go @@ -45,20 +45,9 @@ func TestG722Payloader(t *testing.T) { } payload := []byte{0x90, 0x90, 0x90} - // Nil payload - res := p.Payload(-1, nil) - if len(res) != 0 { - t.Fatal("Generated payload should be empty") - } - - // Negative MTU, small payload - res = p.Payload(-1, payload) - if len(res) != 0 { - t.Fatal("Generated payload should be empty") - } // 0 MTU, small payload - res = p.Payload(0, payload) + res := p.Payload(0, payload) if len(res) != 0 { t.Fatal("Generated payload should be empty") } @@ -70,7 +59,7 @@ func TestG722Payloader(t *testing.T) { } // Positive MTU, small payload - res = p.Payload(len(payload)-1, 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") } diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 7a22642..a1e0381 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -61,7 +61,7 @@ func emitNalus(nals []byte, emit func([]byte)) { } // Payload fragments a H264 packet across one or more byte arrays -func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte { +func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { var payloads [][]byte if len(payload) == 0 { return payloads @@ -80,7 +80,7 @@ func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte { } // Single NALU - if len(nalu) <= mtu { + if len(nalu) <= int(mtu) { out := make([]byte, len(nalu)) copy(out, nalu) payloads = append(payloads, out) @@ -88,7 +88,7 @@ func (p *H264Payloader) Payload(mtu int, payload []byte) [][]byte { } // FU-A - maxFragmentSize := mtu - fuaHeaderSize + 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 diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index 2ab4de7..dfae73c 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -4,7 +4,7 @@ package codecs type OpusPayloader struct{} // Payload fragments an Opus packet across one or more byte arrays -func (p *OpusPayloader) Payload(mtu int, payload []byte) [][]byte { +func (p *OpusPayloader) Payload(mtu uint16, payload []byte) [][]byte { if payload == nil { return [][]byte{} } diff --git a/codecs/opus_packet_test.go b/codecs/opus_packet_test.go index 5366a6b..1c09a5a 100644 --- a/codecs/opus_packet_test.go +++ b/codecs/opus_packet_test.go @@ -52,12 +52,6 @@ func TestOpusPayloader_Payload(t *testing.T) { t.Fatal("Generated payload should be the 1") } - // Negative 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 { diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index 24e7200..d9d986d 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -11,7 +11,7 @@ const ( ) // Payload fragments a VP8 packet across one or more byte arrays -func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte { +func (p *VP8Payloader) Payload(mtu uint16, payload []byte) [][]byte { /* * https://tools.ietf.org/html/rfc7741#section-4.2 * @@ -44,7 +44,7 @@ func (p *VP8Payloader) Payload(mtu int, payload []byte) [][]byte { } } - maxFragmentSize := mtu - usingHeaderSize + maxFragmentSize := int(mtu) - usingHeaderSize payloadData := payload payloadDataRemaining := len(payload) diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index 37d160d..7bfe0b6 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -112,7 +112,7 @@ func TestVP8Packet_Unmarshal(t *testing.T) { func TestVP8Payloader_Payload(t *testing.T) { testCases := map[string]struct { payloader VP8Payloader - mtu int + mtu uint16 payload [][]byte expected [][][]byte }{ @@ -199,12 +199,6 @@ func TestVP8Payloader_Payload(t *testing.T) { if len(res) != 0 { t.Fatal("Generated payload should be empty") } - - // Negative MTU, small payload - res = pck.Payload(-1, payload) - if len(res) != 0 { - t.Fatal("Generated payload should be empty") - } }) } diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 9d05340..75cf4eb 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -23,7 +23,7 @@ const ( ) // Payload fragments an VP9 packet across one or more byte arrays -func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte { +func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { /* * https://www.ietf.org/id/draft-ietf-payload-vp9-13.txt * @@ -75,7 +75,7 @@ func (p *VP9Payloader) Payload(mtu int, payload []byte) [][]byte { return [][]byte{} } - maxFragmentSize := mtu - vp9HeaderSize + maxFragmentSize := int(mtu) - vp9HeaderSize payloadDataRemaining := len(payload) payloadDataIndex := 0 diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index d226324..a1f44af 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -205,7 +205,7 @@ func TestVP9Payloader_Payload(t *testing.T) { cases := map[string]struct { b [][]byte - mtu int + mtu uint16 res [][]byte }{ "NilPayload": { @@ -218,11 +218,6 @@ func TestVP9Payloader_Payload(t *testing.T) { mtu: 1, res: [][]byte{}, }, - "NegativeMTU": { - b: [][]byte{{0x00, 0x00}}, - mtu: -1, - res: [][]byte{}, - }, "OnePacket": { b: [][]byte{{0x01, 0x02}}, mtu: 10, diff --git a/packetizer.go b/packetizer.go index edde1e1..19f6c75 100644 --- a/packetizer.go +++ b/packetizer.go @@ -6,7 +6,7 @@ import ( // Payloader payloads a byte array for use as rtp.Packet payloads type Payloader interface { - Payload(mtu int, payload []byte) [][]byte + Payload(mtu uint16, payload []byte) [][]byte } // Packetizer packetizes a payload @@ -17,7 +17,7 @@ type Packetizer interface { } type packetizer struct { - MTU int + MTU uint16 PayloadType uint8 SSRC uint32 Payloader Payloader @@ -31,7 +31,7 @@ type packetizer struct { } // NewPacketizer returns a new instance of a Packetizer for a specific payloader -func NewPacketizer(mtu int, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32) Packetizer { +func NewPacketizer(mtu uint16, pt uint8, ssrc uint32, payloader Payloader, sequencer Sequencer, clockRate uint32) Packetizer { return &packetizer{ MTU: mtu, PayloadType: pt, From 3c1e1ed5ddf6dc2645addb6448dda2096d56345f Mon Sep 17 00:00:00 2001 From: baiyufei Date: Fri, 8 Jan 2021 19:50:46 +0800 Subject: [PATCH 026/102] Pack SPS and PPS in one STAP-A packet Chrome need SPS and PPS in one STAP-A, and timestamp of STAP-A should be same with next I-Frame. Otherwise Chrome will discard SPS and PPS before receiving I frame. --- codecs/h264_packet.go | 46 +++++++++++++++++++++++++++++++++----- codecs/h264_packet_test.go | 23 +++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index a1e0381..40d207f 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -6,12 +6,18 @@ import ( ) // H264Payloader payloads H264 packets -type H264Payloader struct{} +type H264Payloader struct { + spsNalu, ppsNalu []byte +} const ( - stapaNALUType = 24 - fuaNALUType = 28 - fubNALUType = 29 + stapaNALUType = 24 + fuaNALUType = 28 + fubNALUType = 29 + spsNALUType = 7 + ppsNALUType = 8 + audNALUType = 9 + fillerNALUType = 12 fuaHeaderSize = 2 stapaHeaderSize = 1 @@ -21,6 +27,8 @@ const ( naluRefIdcBitmask = 0x60 fuStartBitmask = 0x80 fuEndBitmask = 0x40 + + outputStapAHeader = 0x78 ) func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } @@ -75,8 +83,36 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { naluType := nalu[0] & naluTypeBitmask naluRefIdc := nalu[0] & naluRefIdcBitmask - if naluType == 9 || naluType == 12 { + 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 diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index 4ee7fd2..608b020 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -222,3 +222,26 @@ func TestH264PartitionHeadChecker_IsPartitionHead(t *testing.T) { 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") + } +} From 6033c9af0acda36a86b3f38a0c65b3d02e8c595b Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Sun, 22 Aug 2021 22:21:32 +0200 Subject: [PATCH 027/102] Implement partition head in Depacketizer We used to do partition head checking in PartitionHeadChecker and partition tail checking in Depacketizer, which is confusing and error-prone. Move all checking into the Depacketizer itself. --- codecs/common.go | 18 ++++++++++++++++++ codecs/h264_packet.go | 21 ++++++++++++++++----- codecs/h264_packet_test.go | 20 ++++++++++---------- codecs/opus_packet.go | 20 ++++++++------------ codecs/opus_packet_test.go | 11 +++-------- codecs/vp8_packet.go | 22 +++++++++++++--------- codecs/vp8_packet_test.go | 10 +++++----- codecs/vp9_packet.go | 24 ++++++++++++++---------- codecs/vp9_packet_test.go | 10 +++++----- depacketizer.go | 8 +++++++- 10 files changed, 99 insertions(+), 65 deletions(-) diff --git a/codecs/common.go b/codecs/common.go index 39336d2..af5632a 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -6,3 +6,21 @@ func min(a, b int) int { } 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 +} diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 40d207f..f14e4f6 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -189,6 +189,8 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { type H264Packet struct { IsAVC bool fuaBuffer []byte + + videoDepacketizer } func (p *H264Packet) doPackaging(nalu []byte) []byte { @@ -265,18 +267,27 @@ func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { return nil, fmt.Errorf("%w: %d", errUnhandledNALUType, naluType) } -// H264PartitionHeadChecker checks H264 partition head +// H264PartitionHeadChecker checks H264 partition head. +// +// Deprecated: replaced by H264Packet.IsPartitionHead() type H264PartitionHeadChecker struct{} // IsPartitionHead checks if this is the head of a packetized nalu stream. +// +// Deprecated: replaced by H264Packet.IsPartitionHead() func (*H264PartitionHeadChecker) IsPartitionHead(packet []byte) bool { - if packet == nil || len(packet) < 2 { + return (&H264Packet{}).IsPartitionHead(packet) +} + +// IsPartitionHead checks if this is the head of a packetized nalu stream. +func (*H264Packet) IsPartitionHead(payload []byte) bool { + if len(payload) < 2 { return false } - if packet[0]&naluTypeBitmask == fuaNALUType || - packet[0]&naluTypeBitmask == fubNALUType { - return packet[1]&fuStartBitmask != 0 + if payload[0]&naluTypeBitmask == fuaNALUType || + payload[0]&naluTypeBitmask == fubNALUType { + return payload[1]&fuStartBitmask != 0 } return true diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index 608b020..47826d7 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -180,45 +180,45 @@ func TestH264Packet_Unmarshal(t *testing.T) { } } -func TestH264PartitionHeadChecker_IsPartitionHead(t *testing.T) { - h264PartitionHeadChecker := H264PartitionHeadChecker{} +func TestH264IsPartitionHead(t *testing.T) { + h264 := H264Packet{} - if h264PartitionHeadChecker.IsPartitionHead(nil) { + if h264.IsPartitionHead(nil) { t.Fatal("nil must not be a partition head") } emptyNalu := []byte{} - if h264PartitionHeadChecker.IsPartitionHead(emptyNalu) { + if h264.IsPartitionHead(emptyNalu) { t.Fatal("empty nalu must not be a partition head") } singleNalu := []byte{1, 0} - if h264PartitionHeadChecker.IsPartitionHead(singleNalu) == false { + if h264.IsPartitionHead(singleNalu) == false { t.Fatal("single nalu must be a partition head") } stapaNalu := []byte{stapaNALUType, 0} - if h264PartitionHeadChecker.IsPartitionHead(stapaNalu) == false { + if h264.IsPartitionHead(stapaNalu) == false { t.Fatal("stapa nalu must be a partition head") } fuaStartNalu := []byte{fuaNALUType, fuStartBitmask} - if h264PartitionHeadChecker.IsPartitionHead(fuaStartNalu) == false { + if h264.IsPartitionHead(fuaStartNalu) == false { t.Fatal("fua start nalu must be a partition head") } fuaEndNalu := []byte{fuaNALUType, fuEndBitmask} - if h264PartitionHeadChecker.IsPartitionHead(fuaEndNalu) { + if h264.IsPartitionHead(fuaEndNalu) { t.Fatal("fua end nalu must not be a partition head") } fubStartNalu := []byte{fubNALUType, fuStartBitmask} - if h264PartitionHeadChecker.IsPartitionHead(fubStartNalu) == false { + if h264.IsPartitionHead(fubStartNalu) == false { t.Fatal("fub start nalu must be a partition head") } fubEndNalu := []byte{fubNALUType, fuEndBitmask} - if h264PartitionHeadChecker.IsPartitionHead(fubEndNalu) { + if h264.IsPartitionHead(fubEndNalu) { t.Fatal("fub end nalu must not be a partition head") } } diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index dfae73c..dfcfe99 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -17,12 +17,8 @@ func (p *OpusPayloader) Payload(mtu uint16, payload []byte) [][]byte { // OpusPacket represents the Opus header that is stored in the payload of an RTP Packet type OpusPacket struct { Payload []byte -} -// IsDetectedFinalPacketInSequence returns true as all opus packets are always -// final in a sequence -func (p *OpusPacket) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { - return true + audioDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the OpusPacket this method is called upon @@ -37,14 +33,14 @@ func (p *OpusPacket) Unmarshal(packet []byte) ([]byte, error) { return packet, nil } -// OpusPartitionHeadChecker checks Opus partition head +// OpusPartitionHeadChecker checks Opus partition head. +// +// Deprecated: replaced by OpusPacket.IsPartitionHead() type OpusPartitionHeadChecker struct{} -// IsPartitionHead checks whether if this is a head of the Opus partition +// IsPartitionHead checks whether if this is a head of the Opus partition. +// +// Deprecated: replaced by OpusPacket.IsPartitionHead() func (*OpusPartitionHeadChecker) IsPartitionHead(packet []byte) bool { - p := &OpusPacket{} - if _, err := p.Unmarshal(packet); err != nil { - return false - } - return true + return (&OpusPacket{}).IsPartitionHead(packet) } diff --git a/codecs/opus_packet_test.go b/codecs/opus_packet_test.go index 1c09a5a..e68ff57 100644 --- a/codecs/opus_packet_test.go +++ b/codecs/opus_packet_test.go @@ -59,15 +59,10 @@ func TestOpusPayloader_Payload(t *testing.T) { } } -func TestOpusPartitionHeadChecker_IsPartitionHead(t *testing.T) { - checker := &OpusPartitionHeadChecker{} - t.Run("SmallPacket", func(t *testing.T) { - if checker.IsPartitionHead([]byte{}) { - t.Fatal("Small packet should not be the head of a new partition") - } - }) +func TestOpusIsPartitionHead(t *testing.T) { + opus := &OpusPacket{} t.Run("NormalPacket", func(t *testing.T) { - if !checker.IsPartitionHead([]byte{0x00, 0x00}) { + if !opus.IsPartitionHead([]byte{0x00, 0x00}) { t.Fatal("All OPUS RTP packet should be the head of a new partition") } }) diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index d9d986d..2eb7bb8 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -115,12 +115,8 @@ type VP8Packet struct { KEYIDX uint8 /* 5 bits temporal key frame index */ Payload []byte -} -// IsDetectedFinalPacketInSequence returns true of the packet passed in has the -// marker bit set indicated the end of a packet sequence -func (p *VP8Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { - return rtpPacketMarketBit + videoDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the VP8Packet this method is called upon @@ -194,13 +190,21 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { } // VP8PartitionHeadChecker checks VP8 partition head +// +// Deprecated: replaced by VP8Packet.IsPartitionHead() type VP8PartitionHeadChecker struct{} -// IsPartitionHead checks whether if this is a head of the VP8 partition +// IsPartitionHead checks whether if this is a head of the VP8 partition. +// +// Deprecated: replaced by VP8Packet.IsPartitionHead() func (*VP8PartitionHeadChecker) IsPartitionHead(packet []byte) bool { - p := &VP8Packet{} - if _, err := p.Unmarshal(packet); err != nil { + return (&VP8Packet{}).IsPartitionHead(packet) +} + +// IsPartitionHead checks whether if this is a head of the VP8 partition +func (*VP8Packet) IsPartitionHead(payload []byte) bool { + if len(payload) < 1 { return false } - return p.S == 1 + return (payload[0] & 0x10) != 0 } diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index 7bfe0b6..b173cfb 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -202,20 +202,20 @@ func TestVP8Payloader_Payload(t *testing.T) { }) } -func TestVP8PartitionHeadChecker_IsPartitionHead(t *testing.T) { - checker := &VP8PartitionHeadChecker{} +func TestVP8IsPartitionHead(t *testing.T) { + vp8 := &VP8Packet{} t.Run("SmallPacket", func(t *testing.T) { - if checker.IsPartitionHead([]byte{0x00}) { + 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 !checker.IsPartitionHead([]byte{0x10, 0x00, 0x00, 0x00}) { + 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 checker.IsPartitionHead([]byte{0x00, 0x00, 0x00, 0x00}) { + if vp8.IsPartitionHead([]byte{0x00, 0x00, 0x00, 0x00}) { t.Fatal("Packet without S flag should not be the head of a new partition") } }) diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 75cf4eb..f33c515 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -148,12 +148,8 @@ type VP9Packet struct { PGPDiff [][]uint8 // Reference indecies of pictures in a Picture Group Payload []byte -} -// IsDetectedFinalPacketInSequence returns true of the packet passed in has the -// marker bit set indicated the end of a packet sequence -func (p *VP9Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool { - return rtpPacketMarketBit + videoDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the VP9Packet this method is called upon @@ -380,14 +376,22 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { return pos, nil } -// VP9PartitionHeadChecker checks VP9 partition head +// VP9PartitionHeadChecker checks VP9 partition head. +// +// Deprecated: replaced by VP9Packet.IsPartitionHead() type VP9PartitionHeadChecker struct{} -// IsPartitionHead checks whether if this is a head of the VP9 partition +// IsPartitionHead checks whether if this is a head of the VP9 partition. +// +// Deprecated: replaced by VP9Packet.IsPartitionHead() func (*VP9PartitionHeadChecker) IsPartitionHead(packet []byte) bool { - p := &VP9Packet{} - if _, err := p.Unmarshal(packet); err != nil { + return (&VP9Packet{}).IsPartitionHead(packet) +} + +// IsPartitionHead checks whether if this is a head of the VP9 partition +func (*VP9Packet) IsPartitionHead(payload []byte) bool { + if len(payload) < 1 { return false } - return p.B + return (payload[0] & 0x08) != 0 } diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index a1f44af..65861c6 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -299,18 +299,18 @@ func TestVP9Payloader_Payload(t *testing.T) { }) } -func TestVP9PartitionHeadChecker_IsPartitionHead(t *testing.T) { - checker := &VP9PartitionHeadChecker{} +func TestVP9IsPartitionHead(t *testing.T) { + vp9 := &VP9Packet{} t.Run("SmallPacket", func(t *testing.T) { - if checker.IsPartitionHead([]byte{}) { + 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 !checker.IsPartitionHead([]byte{0x18, 0x00, 0x00}) { + if !vp9.IsPartitionHead([]byte{0x18, 0x00, 0x00}) { t.Error("VP9 RTP packet with B flag should be head of a new partition") } - if checker.IsPartitionHead([]byte{0x10, 0x00, 0x00}) { + if vp9.IsPartitionHead([]byte{0x10, 0x00, 0x00}) { t.Error("VP9 RTP packet without B flag should not be head of a new partition") } }) diff --git a/depacketizer.go b/depacketizer.go index e578d71..c66d2e3 100644 --- a/depacketizer.go +++ b/depacketizer.go @@ -2,6 +2,12 @@ package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload type Depacketizer interface { - IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bool 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 } From 8265264881c223990b9424d00fa4c4d05ad6e8a6 Mon Sep 17 00:00:00 2001 From: boks1971 Date: Fri, 22 Oct 2021 00:21:38 +0530 Subject: [PATCH 028/102] Fix unmarshal of packets with padding WebRTC clients use padding only packets as probe packets for bandwidth estimation. As padding was not removed from the payload, downstream code trying to parse padding as valid media payload results in erroneous parsing Testing: -------- - Add unit tests for different padding scenarios - Check that ion-sfu does not try to parse padding only packets as valid video payload. --- AUTHORS.txt | 2 + packet.go | 11 +++++- packet_test.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 0a0c424..52d7bd9 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -8,7 +8,9 @@ aler9 <46489434+aler9@users.noreply.github.com> Antoine Baché Antoine Baché Atsushi Watanabe +baiyufei Bao Nguyen +boks1971 debiandebiandebian ffmiyo Guilherme diff --git a/packet.go b/packet.go index b237b0a..2958b34 100644 --- a/packet.go +++ b/packet.go @@ -214,8 +214,17 @@ func (p *Packet) Unmarshal(rawPacket []byte) error { return err } - p.Payload = rawPacket[p.PayloadOffset:] + end := len(rawPacket) + if p.Header.Padding { + end -= int(rawPacket[end-1]) + } + if end < p.PayloadOffset { + return errTooSmall + } + + p.Payload = rawPacket[p.PayloadOffset:end] p.Raw = rawPacket + return nil } diff --git a/packet_test.go b/packet_test.go index a1cfa8e..d906170 100644 --- a/packet_test.go +++ b/packet_test.go @@ -22,6 +22,7 @@ func TestBasic(t *testing.T) { } parsedPacket := &Packet{ Header: Header{ + Padding: false, Marker: true, Extension: true, ExtensionProfile: 1, @@ -73,6 +74,108 @@ func TestBasic(t *testing.T) { } }) } + + // 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{}, + PayloadOffset: 20, + }, + Payload: rawPkt[20:21], + Raw: rawPkt, + } + 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{}, + PayloadOffset: 20, + }, + Payload: []byte{}, + Raw: rawPkt, + } + 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{}, + } + 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) + } } func TestExtension(t *testing.T) { From 597a8bb046408b716ce003a99e68d9309a3ea972 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 16 Nov 2021 03:09:02 +0000 Subject: [PATCH 029/102] Update CI configs to v0.6.0 Update lint scripts and CI configs. --- renovate.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 08c1e39..f161405 100644 --- a/renovate.json +++ b/renovate.json @@ -1,6 +1,7 @@ { "extends": [ - "config:base" + "config:base", + ":disableDependencyDashboard" ], "postUpdateOptions": [ "gomodTidy" From 919adef7124e90913ea45ede69bdf998042c508e Mon Sep 17 00:00:00 2001 From: boks1971 Date: Mon, 22 Nov 2021 11:10:56 +0530 Subject: [PATCH 030/102] Add PaddingSize to rtp.Packet When a packet has padding, the padding size information is lost during unmarshal. Add a PaddingSize field to Packet structure. Also support marshalling on an RTP packet with padding. Testing: -------- Modified/added unit tests. --- packet.go | 16 ++++--- packet_test.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/packet.go b/packet.go index 2958b34..a77fcc7 100644 --- a/packet.go +++ b/packet.go @@ -34,7 +34,8 @@ type Header struct { type Packet struct { Header Raw []byte - Payload []byte + Payload []byte + PaddingSize byte } const ( @@ -216,7 +217,8 @@ func (p *Packet) Unmarshal(rawPacket []byte) error { end := len(rawPacket) if p.Header.Padding { - end -= int(rawPacket[end-1]) + p.PaddingSize = rawPacket[end-1] + end -= int(p.PaddingSize) } if end < p.PayloadOffset { return errTooSmall @@ -477,23 +479,27 @@ 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 n, err = p.Header.MarshalTo(buf) if err != nil { return 0, err } // Make sure the buffer is large enough to hold the packet. - if n+len(p.Payload) > len(buf) { + if n+len(p.Payload)+int(p.PaddingSize) > len(buf) { return 0, io.ErrShortBuffer } m := copy(buf[n:], p.Payload) p.Raw = buf[:n+m] + if p.Header.Padding { + buf[n+m+int(p.PaddingSize-1)] = p.PaddingSize + } - return n + m, nil + 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) + return p.Header.MarshalSize() + len(p.Payload) + int(p.PaddingSize) } diff --git a/packet_test.go b/packet_test.go index d906170..a2a5b7d 100644 --- a/packet_test.go +++ b/packet_test.go @@ -39,8 +39,9 @@ func TestBasic(t *testing.T) { SSRC: 476325762, CSRC: []uint32{}, }, - Payload: rawPkt[20:], - Raw: rawPkt, + Payload: rawPkt[20:], + Raw: rawPkt, + PaddingSize: 0, } // Unmarshal to the used Packet should work as well. @@ -99,8 +100,9 @@ func TestBasic(t *testing.T) { CSRC: []uint32{}, PayloadOffset: 20, }, - Payload: rawPkt[20:21], - Raw: rawPkt, + Payload: rawPkt[20:21], + Raw: rawPkt, + PaddingSize: 4, } if err := p.Unmarshal(rawPkt); err != nil { t.Error(err) @@ -132,8 +134,9 @@ func TestBasic(t *testing.T) { CSRC: []uint32{}, PayloadOffset: 20, }, - Payload: []byte{}, - Raw: rawPkt, + Payload: []byte{}, + Raw: rawPkt, + PaddingSize: 5, } if err := p.Unmarshal(rawPkt); err != nil { t.Error(err) @@ -167,7 +170,8 @@ func TestBasic(t *testing.T) { SSRC: 476325762, CSRC: []uint32{}, }, - Payload: []byte{}, + Payload: []byte{}, + PaddingSize: 0, } err := p.Unmarshal(rawPkt) if err == nil { @@ -176,6 +180,107 @@ func TestBasic(t *testing.T) { 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) { From 5055564b562385f328075cc1a5429dbc1c4e86c9 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Sun, 28 Nov 2021 18:43:05 +0000 Subject: [PATCH 031/102] Update CI configs to v0.6.2 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7a72c2c..8affe32 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.15", "1.16"] + go: ["1.16", "1.17"] fail-fast: false name: Go ${{ matrix.go }} steps: @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.15", "1.16"] + go: ["1.16", "1.17"] fail-fast: false name: Go i386 ${{ matrix.go }} steps: From 8a0dd172cea5388c7d2fccefcedaa0778f1c812f Mon Sep 17 00:00:00 2001 From: wangzixiang Date: Thu, 25 Nov 2021 23:25:01 +0800 Subject: [PATCH 032/102] Replace <= with == since mtu cannot be negative MTU was changed to an unsigned integer --- AUTHORS.txt | 1 + codecs/g711_packet.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 52d7bd9..809e85c 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -31,4 +31,5 @@ Sean DuBois Sean DuBois Simone Gotti Tarrence van As +wangzixiang Woodrow Douglass diff --git a/codecs/g711_packet.go b/codecs/g711_packet.go index 2348a79..7ab68b2 100644 --- a/codecs/g711_packet.go +++ b/codecs/g711_packet.go @@ -6,7 +6,7 @@ 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 { + if payload == nil || mtu == 0 { return out } From 4c550643fce215b5dbea4e056cbbb59e812474cd Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Sun, 19 Dec 2021 17:16:36 +0000 Subject: [PATCH 033/102] Update CI configs to v0.6.4 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8affe32..9f6ef84 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -62,7 +62,7 @@ jobs: if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi if [ -n "${TEST_HOOK}" ]; then ${TEST_HOOK}; fi - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 with: file: ./cover.out name: codecov-umbrella @@ -151,7 +151,7 @@ jobs: -exec="${GO_JS_WASM_EXEC}" \ -v ./... - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 with: file: ./cover.out name: codecov-umbrella From 26d9f39440e41e5c97def6a802650b2eb0710a1f Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sun, 2 Jan 2022 19:17:34 -0500 Subject: [PATCH 034/102] Adds zero-copy header extensions This change adds an interface and impl for zero-copy extensions. --- header_extension.go | 350 +++++++++++++++++++++++++++++++++++++++ header_extension_test.go | 302 +++++++++++++++++++++++++++++++++ 2 files changed, 652 insertions(+) create mode 100644 header_extension.go create mode 100644 header_extension_test.go diff --git a/header_extension.go b/header_extension.go new file mode 100644 index 0000000..cb38324 --- /dev/null +++ b/header_extension.go @@ -0,0 +1,350 @@ +package rtp + +import ( + "encoding/binary" + "fmt" + "io" +) + +const ( + headerExtensionProfileOneByte = 0xBEDE + headerExtensionProfileTwoByte = 0x1000 + headerExtensionIDReserved = 0xF +) + +// HeaderExtension represents an RTP extension header. +type HeaderExtension interface { + Set(id uint8, payload []byte) error + GetIDs() []uint8 + Get(id uint8) []byte + Del(id uint8) error + + Unmarshal(buf []byte) (int, error) + Marshal() ([]byte, error) + MarshalTo(buf []byte) (int, error) + MarshalSize() int +} + +// OneByteHeaderExtension is an RFC8285 one-byte header extension. +type OneByteHeaderExtension struct { + payload []byte +} + +// Set sets the extension payload for the specified ID. +func (e *OneByteHeaderExtension) Set(id uint8, buf []byte) error { + if id < 1 || id > 14 { + return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderIDRange, id) + } + if len(buf) > 16 { + return fmt.Errorf("%w actual(%d)", errRFC8285OneByteHeaderSize, len(buf)) + } + + for n := 4; n < len(e.payload); { + if e.payload[n] == 0x00 { // padding + n++ + continue + } + + extid := e.payload[n] >> 4 + 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) +} diff --git a/header_extension_test.go b/header_extension_test.go new file mode 100644 index 0000000..8aba946 --- /dev/null +++ b/header_extension_test.go @@ -0,0 +1,302 @@ +package 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") + } +} From 9745ae515e54e7f350c8718604419fefabd4a560 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 2 Feb 2022 22:11:48 -0500 Subject: [PATCH 035/102] Add IsPartitionHead and IsPartitionTail to H265 This change makes the H265 packet satisfy the depacketizer interface allowing H265 streams to be decoded with SampleBuilder. --- codecs/h265_packet.go | 16 ++++++++++++++++ codecs/h265_packet_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index a4ae843..6f0490d 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -1,6 +1,7 @@ package codecs import ( + "encoding/binary" "errors" "fmt" ) @@ -727,6 +728,8 @@ var ( type H265Packet struct { packet isH265Packet mightNeedDONL bool + + videoDepacketizer } // WithDONL can be called to specify whether or not DONL might be parsed. @@ -801,3 +804,16 @@ func (p *H265Packet) Unmarshal(payload []byte) ([]byte, error) { 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 +} diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 7c25a9c..2172abe 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -812,6 +812,39 @@ func TestH265_Packet(t *testing.T) { } } +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. From f3627cbaddcbcc6e7ee00769a55998ba2fae7b49 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Sun, 6 Feb 2022 18:18:27 +0000 Subject: [PATCH 036/102] Update CI configs to v0.6.6 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9f6ef84..43608f1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -115,7 +115,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '12.x' + node-version: '16.x' - uses: actions/cache@v2 with: @@ -129,7 +129,7 @@ jobs: - 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.16 + GO_VERSION: 1.17 - name: Set Go Root run: echo "GOROOT=${HOME}/go" >> $GITHUB_ENV From 182ae427edcf8ece4e2debbe87114bffecd70147 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Sun, 27 Feb 2022 00:15:14 +0000 Subject: [PATCH 037/102] Update CI configs to v0.6.7 Update lint scripts and CI configs. --- .github/workflows/lint.yaml | 2 +- .github/workflows/test.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index f096078..e72f62d 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -45,7 +45,7 @@ jobs: - uses: actions/checkout@v2 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: version: v1.31 args: $GOLANGCI_LINT_EXRA_ARGS diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 43608f1..9081655 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -113,7 +113,7 @@ jobs: - uses: actions/checkout@v2 - name: Use Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16.x' From 8148b768b5235c9947a985cd01256d2830392226 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Wed, 2 Mar 2022 18:48:59 +0000 Subject: [PATCH 038/102] Update CI configs to v0.6.8 Update lint scripts and CI configs. --- .github/workflows/generate-authors.yml | 2 +- .github/workflows/lint.yaml | 4 ++-- .github/workflows/renovate-go-mod-fix.yaml | 2 +- .github/workflows/test.yaml | 8 ++++---- .github/workflows/tidy-check.yaml | 4 ++-- .gitignore | 1 + 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index 83e7065..9a80a48 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -32,7 +32,7 @@ jobs: 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 e72f62d..438443f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml index 46d2d04..5991822 100644 --- a/.github/workflows/renovate-go-mod-fix.yaml +++ b/.github/workflows/renovate-go-mod-fix.yaml @@ -20,7 +20,7 @@ jobs: 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 9081655..cd788c9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,7 +26,7 @@ jobs: fail-fast: false name: Go ${{ matrix.go }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/cache@v2 with: @@ -39,7 +39,7 @@ jobs: ${{ runner.os }}-amd64-go- - name: Setup Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v3 with: go-version: ${{ matrix.go }} @@ -77,7 +77,7 @@ jobs: fail-fast: false name: Go i386 ${{ matrix.go }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/cache@v2 with: @@ -110,7 +110,7 @@ jobs: fail-fast: false name: WASM steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 03b5189..3ab2c35 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -23,9 +23,9 @@ jobs: 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 From 7e578c67ecedc5953df04f2ad8f488aa66b3955e Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Tue, 8 Mar 2022 16:42:25 +0100 Subject: [PATCH 039/102] Remove pointer from constant functions Marshal, MarshalTo, MarshalSize, Clone functions shouldn't have a pointer, since they're constant. This convention is used by pion/rtcp and the standard library, and prevents coding errors. --- abssendtimeextension.go | 2 +- audiolevelextension.go | 2 +- header_extension.go | 18 +++++++++--------- packet.go | 12 ++++++------ transportccextension.go | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) 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/header_extension.go b/header_extension.go index cb38324..c143ac1 100644 --- a/header_extension.go +++ b/header_extension.go @@ -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) } @@ -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 a77fcc7..a3613b0 100644 --- a/packet.go +++ b/packet.go @@ -231,7 +231,7 @@ func (p *Packet) Unmarshal(rawPacket []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) @@ -243,7 +243,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 @@ -341,7 +341,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) @@ -466,7 +466,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) @@ -478,7 +478,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) { +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 { @@ -500,6 +500,6 @@ 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) } 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 From 7be36e8e8700c19e656b51cc90bbe6537197ef0a Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Mon, 21 Mar 2022 17:12:09 +0000 Subject: [PATCH 040/102] Add AV1 Support Implements Marshal and Unmarshal support. Marshal doesn't pack multiple OBUs into one packet and could be improved. --- .github/workflows/test.yaml | 2 - codecs/av1_frame.go | 41 ++++++ codecs/av1_frame_test.go | 81 ++++++++++++ codecs/av1_packet.go | 158 +++++++++++++++++++++++ codecs/av1_packet_test.go | 247 ++++++++++++++++++++++++++++++++++++ codecs/error.go | 3 + pkg/obu/leb128.go | 66 ++++++++++ pkg/obu/leb128_test.go | 39 ++++++ 8 files changed, 635 insertions(+), 2 deletions(-) create mode 100644 codecs/av1_frame.go create mode 100644 codecs/av1_frame_test.go create mode 100644 codecs/av1_packet.go create mode 100644 codecs/av1_packet_test.go create mode 100644 pkg/obu/leb128.go create mode 100644 pkg/obu/leb128_test.go diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cd788c9..01ddafa 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -64,7 +64,6 @@ jobs: - uses: codecov/codecov-action@v2 with: - file: ./cover.out name: codecov-umbrella fail_ci_if_error: true flags: go @@ -153,7 +152,6 @@ jobs: - uses: codecov/codecov-action@v2 with: - file: ./cover.out name: codecov-umbrella fail_ci_if_error: true flags: wasm diff --git a/codecs/av1_frame.go b/codecs/av1_frame.go new file mode 100644 index 0000000..6f0db4c --- /dev/null +++ b/codecs/av1_frame.go @@ -0,0 +1,41 @@ +package codecs + +// AV1Frame 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. +// AV1Frame 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 AV1Frame 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 *AV1Frame) 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 AV1Packet and returns fully constructed frames +func (f *AV1Frame) ReadFrames(pkt *AV1Packet) ([][]byte, error) { + OBUs := [][]byte{} + isFirstOBUFragment := pkt.Z + + for i := range pkt.OBUElements { + OBUs = f.pushOBUElement(&isFirstOBUFragment, pkt.OBUElements[i], OBUs) + } + + if pkt.Y { + // 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/codecs/av1_frame_test.go b/codecs/av1_frame_test.go new file mode 100644 index 0000000..544ba7d --- /dev/null +++ b/codecs/av1_frame_test.go @@ -0,0 +1,81 @@ +package codecs + +import ( + "reflect" + "testing" +) + +// 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 := &AV1Frame{} + frames, err := f.ReadFrames(&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 = &AV1Frame{} + frames, err = f.ReadFrames(&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 = &AV1Frame{} + frames, err = f.ReadFrames(&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(&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 AV1Frame 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 := &AV1Payloader{} + f := &AV1Frame{} + for _, originalFrame := range frames { + for _, payload := range payloader.Payload(mtu, originalFrame) { + rtpPacket := &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/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/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") + } +} From e57513f9fd7b316fb077a159921172465c8b2636 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Sun, 10 Apr 2022 23:48:58 -0400 Subject: [PATCH 041/102] Fix AV1 /v2 imports Missed when cherry-picking from master --- codecs/av1_packet.go | 2 +- codecs/av1_packet_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 7aa3a55..120a904 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -1,7 +1,7 @@ package codecs import ( - "github.com/pion/rtp/v2/pkg/obu" + "github.com/pion/rtp/pkg/obu" ) const ( diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 27eeb47..06533f1 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -6,7 +6,7 @@ import ( "reflect" "testing" - "github.com/pion/rtp/v2/pkg/obu" + "github.com/pion/rtp/pkg/obu" ) func TestAV1_Marshal(t *testing.T) { From 5ac507d0e9043eeb3a4d287189ae71e5d15f5a11 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Mon, 11 Apr 2022 09:50:53 -0400 Subject: [PATCH 042/102] Move AV1 Frame to new frame pkg Codecs only contains RTP Payload processing --- AUTHORS.txt | 1 + codecs/av1_frame.go => pkg/frame/av1.go | 17 +++++++----- .../frame/av1_test.go | 26 ++++++++++--------- 3 files changed, 25 insertions(+), 19 deletions(-) rename codecs/av1_frame.go => pkg/frame/av1.go (62%) rename codecs/av1_frame_test.go => pkg/frame/av1_test.go (77%) diff --git a/AUTHORS.txt b/AUTHORS.txt index 809e85c..a7bc7a0 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -27,6 +27,7 @@ Michael Uti Raphael Derosso Pereira Rob Lofthouse Robin Raymond +Sean DuBois Sean DuBois Sean DuBois Simone Gotti diff --git a/codecs/av1_frame.go b/pkg/frame/av1.go similarity index 62% rename from codecs/av1_frame.go rename to pkg/frame/av1.go index 6f0db4c..f46c141 100644 --- a/codecs/av1_frame.go +++ b/pkg/frame/av1.go @@ -1,16 +1,19 @@ -package codecs +// Package frame provides code to construct complete media frames from packetized media +package frame -// AV1Frame represents a collection of OBUs given a stream of AV1 Packets. +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. -// AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements. This structure +// 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 AV1Frame struct { +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 *AV1Frame) pushOBUElement(isFirstOBUFragment *bool, obuElement []byte, obuList [][]byte) [][]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 @@ -23,8 +26,8 @@ func (f *AV1Frame) pushOBUElement(isFirstOBUFragment *bool, obuElement []byte, o return append(obuList, obuElement) } -// ReadFrames processes the AV1Packet and returns fully constructed frames -func (f *AV1Frame) ReadFrames(pkt *AV1Packet) ([][]byte, error) { +// ReadFrames processes the codecs.AV1Packet and returns fully constructed frames +func (f *AV1) ReadFrames(pkt *codecs.AV1Packet) ([][]byte, error) { OBUs := [][]byte{} isFirstOBUFragment := pkt.Z diff --git a/codecs/av1_frame_test.go b/pkg/frame/av1_test.go similarity index 77% rename from codecs/av1_frame_test.go rename to pkg/frame/av1_test.go index 544ba7d..9129eaf 100644 --- a/codecs/av1_frame_test.go +++ b/pkg/frame/av1_test.go @@ -1,8 +1,10 @@ -package codecs +package frame import ( "reflect" "testing" + + "github.com/pion/rtp/codecs" ) // First is Fragment (and no buffer) @@ -10,31 +12,31 @@ import ( // OBU spread across 3 packets func TestAV1_ReadFrames(t *testing.T) { // First is Fragment of OBU, but no OBU Elements is cached - f := &AV1Frame{} - frames, err := f.ReadFrames(&AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}}) + 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 = &AV1Frame{} - frames, err = f.ReadFrames(&AV1Packet{OBUElements: [][]byte{{0x01}}}) + 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 = &AV1Frame{} - frames, err = f.ReadFrames(&AV1Packet{Y: true, OBUElements: [][]byte{{0x00}}}) + 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(&AV1Packet{Z: true, OBUElements: [][]byte{{0x01}}}) + 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}}) { @@ -42,7 +44,7 @@ func TestAV1_ReadFrames(t *testing.T) { } } -// Marshal some AV1 Frames to RTP, assert that AV1Frame can get them back in the original format +// 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{ @@ -62,11 +64,11 @@ func TestAV1_ReadFrames_E2E(t *testing.T) { frames[len(frames)-1] = append(frames[len(frames)-1], []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}...) } - payloader := &AV1Payloader{} - f := &AV1Frame{} + payloader := &codecs.AV1Payloader{} + f := &AV1{} for _, originalFrame := range frames { for _, payload := range payloader.Payload(mtu, originalFrame) { - rtpPacket := &AV1Packet{} + rtpPacket := &codecs.AV1Packet{} if _, err := rtpPacket.Unmarshal(payload); err != nil { t.Fatal(err) } From fe205b2777da42b42d05a600bbeb7466e56411d7 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Tue, 12 Apr 2022 12:13:32 -0400 Subject: [PATCH 043/102] Add bounds check to AV1 Frame Only take from OBUs slice if greater then 0 --- pkg/frame/av1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/frame/av1.go b/pkg/frame/av1.go index f46c141..30525ab 100644 --- a/pkg/frame/av1.go +++ b/pkg/frame/av1.go @@ -35,7 +35,7 @@ func (f *AV1) ReadFrames(pkt *codecs.AV1Packet) ([][]byte, error) { OBUs = f.pushOBUElement(&isFirstOBUFragment, pkt.OBUElements[i], OBUs) } - if pkt.Y { + 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] From 78185d38ec450bcf921983ddec7f06859db62e21 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Wed, 13 Apr 2022 09:04:15 +0000 Subject: [PATCH 044/102] Update CI configs to v0.7.0 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 01ddafa..b397ef4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -43,19 +43,30 @@ jobs: with: go-version: ${{ matrix.go }} - - name: Setup go-acc - run: | - go get github.com/ory/go-acc - git checkout go.mod go.sum + - 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 - go-acc -o cover.out ./... -- \ + set -euo pipefail + go test -coverprofile=cover.out -covermode=atomic \ ${TEST_BENCH_OPTION} \ - -v -race + -json \ + -v -race \ + ./... 2>&1 | 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: | From f9d0169ca22d27001a06c6ac1137324fad06d99c Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Wed, 13 Apr 2022 15:59:58 +0000 Subject: [PATCH 045/102] Update CI configs to v0.7.1 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b397ef4..66305d1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -43,6 +43,11 @@ jobs: with: go-version: ${{ matrix.go }} + - name: Setup go-acc + run: | + go get github.com/ory/go-acc + git checkout go.mod go.sum + - name: Set up gotestfmt uses: haveyoudebuggedit/gotestfmt-action@v2 with: @@ -54,11 +59,10 @@ jobs: if [ -f .github/.ci.conf ]; then . .github/.ci.conf; fi set -euo pipefail - go test -coverprofile=cover.out -covermode=atomic \ + go-acc -o cover.out ./... -- \ ${TEST_BENCH_OPTION} \ -json \ - -v -race \ - ./... 2>&1 | tee /tmp/gotest.log | gotestfmt + -v -race 2>&1 | tee /tmp/gotest.log | gotestfmt - name: Upload test log uses: actions/upload-artifact@v2 From 4d482fdf59d340198db13dc88ad61e16cb27c300 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Sun, 24 Apr 2022 02:57:09 +0000 Subject: [PATCH 046/102] Update CI configs to v0.7.2 Update lint scripts and CI configs. --- .github/workflows/lint.yaml | 2 +- .golangci.yml | 34 ++++++++++++++++++++++++++-- header_extension.go | 44 ++++++++++++++++++------------------- packet.go | 18 +++++++-------- packetizer_test.go | 9 ++++++-- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 438443f..d284b19 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -47,5 +47,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.31 + version: v1.45.2 args: $GOLANGCI_LINT_EXRA_ARGS 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/header_extension.go b/header_extension.go index c143ac1..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 } @@ -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 } diff --git a/packet.go b/packet.go index a3613b0..b5a071a 100644 --- a/packet.go +++ b/packet.go @@ -161,16 +161,16 @@ func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit } extid := rawPacket[currOffset] >> 4 - len := int(rawPacket[currOffset]&^0xF0 + 1) + payloadLen := int(rawPacket[currOffset]&^0xF0 + 1) currOffset++ if extid == extensionIDReserved { break } - extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]} + extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+payloadLen]} h.Extensions = append(h.Extensions, extension) - currOffset += len + currOffset += payloadLen } // RFC 8285 RTP Two Byte Header Extension @@ -185,12 +185,12 @@ func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit extid := rawPacket[currOffset] currOffset++ - len := int(rawPacket[currOffset]) + payloadLen := int(rawPacket[currOffset]) currOffset++ - extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+len]} + extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+payloadLen]} h.Extensions = append(h.Extensions, extension) - currOffset += len + currOffset += payloadLen } default: // RFC3550 Extension @@ -410,10 +410,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 } diff --git a/packetizer_test.go b/packetizer_test.go index 5d2331a..c120919 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), 90000) - 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 } From abb1453ebe02d748f1cbdd8b80c84b2a970f828a Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Mon, 21 Mar 2022 17:12:09 +0000 Subject: [PATCH 047/102] Update CI configs to v0.6.9 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 66305d1..80f75d0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -93,7 +93,7 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod @@ -131,7 +131,7 @@ jobs: with: node-version: '16.x' - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: | ~/go/pkg/mod From 6ef1c2cfe2a473308d033402cdfa1199d2e66a79 Mon Sep 17 00:00:00 2001 From: Aaron Boushley Date: Wed, 20 Apr 2022 22:20:01 -0700 Subject: [PATCH 048/102] Set CSRC as a empty (not nil) slice by default Allows packetizer marshal / unmarshal to be consistent Resolves #79 --- packet.go | 2 +- packetizer.go | 1 + packetizer_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/packet.go b/packet.go index b5a071a..d802d20 100644 --- a/packet.go +++ b/packet.go @@ -33,7 +33,7 @@ type Header struct { // NOTE: Raw is populated by Marshal/Unmarshal and should not be modified type Packet struct { Header - Raw []byte + Raw []byte Payload []byte PaddingSize byte } diff --git a/packetizer.go b/packetizer.go index 19f6c75..5d73257 100644 --- a/packetizer.go +++ b/packetizer.go @@ -69,6 +69,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 c120919..66d614c 100644 --- a/packetizer_test.go +++ b/packetizer_test.go @@ -53,7 +53,7 @@ func TestPacketizer_AbsSendTime(t *testing.T) { SequenceNumber: 1234, Timestamp: 45678, SSRC: 0x1234ABCD, - CSRC: nil, + CSRC: []uint32{}, ExtensionProfile: 0xBEDE, Extensions: []Extension{ { @@ -72,3 +72,81 @@ 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(), 90000) + 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) + } + + pkt.PayloadOffset = 0 + pkt.PaddingSize = 0 + pkt.Raw = nil + + if !reflect.DeepEqual(expectedPkt, pkt) { + t.Errorf("Packets don't match, expected %v but got %v", expectedPkt, pkt) + } + } +} From a51bc4fcc0f526fcecfbc01ab476af76cdf0970c Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 1 Aug 2022 10:31:53 -0400 Subject: [PATCH 049/102] Add experimental header extensions Add AbsCaptureTimeExtension and PlayoutDelayExtension implementations. Both of these are experimental RTP header extensions defined in libwebrtc. --- abscapturetimeextension.go | 87 +++++++++++++++++++++++++++++++++ abscapturetimeextension_test.go | 43 ++++++++++++++++ playoutdelayextension.go | 47 ++++++++++++++++++ playoutdelayextension_test.go | 70 ++++++++++++++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 abscapturetimeextension.go create mode 100644 abscapturetimeextension_test.go create mode 100644 playoutdelayextension.go create mode 100644 playoutdelayextension_test.go diff --git a/abscapturetimeextension.go b/abscapturetimeextension.go new file mode 100644 index 0000000..f3005e9 --- /dev/null +++ b/abscapturetimeextension.go @@ -0,0 +1,87 @@ +package rtp + +import ( + "encoding/binary" + "time" +) + +const ( + absCaptureTimeExtensionSize = 8 + absCaptureTimeExtendedExtensionSize = 16 +) + +// AbsCaptureTimeExtension is a extension payload format in +// http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=7 | absolute capture timestamp (bit 0-23) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | absolute capture timestamp (bit 24-55) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ... (56-63) | +// +-+-+-+-+-+-+-+-+ +type AbsCaptureTimeExtension struct { + Timestamp uint64 + EstimatedCaptureClockOffset *int64 +} + +// Marshal serializes the members to buffer. +func (t AbsCaptureTimeExtension) Marshal() ([]byte, error) { + if t.EstimatedCaptureClockOffset != nil { + buf := make([]byte, 16) + binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) + binary.BigEndian.PutUint64(buf[8:16], uint64(*t.EstimatedCaptureClockOffset)) + return buf, nil + } + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf[0:8], t.Timestamp) + return buf, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members. +func (t *AbsCaptureTimeExtension) Unmarshal(rawData []byte) error { + if len(rawData) < absCaptureTimeExtensionSize { + return errTooSmall + } + t.Timestamp = binary.BigEndian.Uint64(rawData[0:8]) + if len(rawData) >= absCaptureTimeExtendedExtensionSize { + offset := int64(binary.BigEndian.Uint64(rawData[8:16])) + t.EstimatedCaptureClockOffset = &offset + } + return nil +} + +// CaptureTime produces the estimated time.Time represented by this extension. +func (t AbsCaptureTimeExtension) CaptureTime() time.Time { + return toTime(t.Timestamp) +} + +// EstimatedCaptureClockOffsetDuration produces the estimated time.Duration represented by this extension. +func (t AbsCaptureTimeExtension) EstimatedCaptureClockOffsetDuration() *time.Duration { + if t.EstimatedCaptureClockOffset == nil { + return nil + } + offset := *t.EstimatedCaptureClockOffset + duration := time.Duration(offset/(1<<32))*time.Second + time.Duration((offset&0xFFFFFFFF)*1e9/(1<<32))*time.Nanosecond + return &duration +} + +// NewAbsCaptureTimeExtension makes new AbsCaptureTimeExtension from time.Time. +func NewAbsCaptureTimeExtension(captureTime time.Time) *AbsCaptureTimeExtension { + return &AbsCaptureTimeExtension{ + Timestamp: toNtpTime(captureTime), + } +} + +// NewAbsCaptureTimeExtensionWithCaptureClockOffset makes new AbsCaptureTimeExtension from time.Time and a clock offset. +func NewAbsCaptureTimeExtensionWithCaptureClockOffset(captureTime time.Time, captureClockOffset time.Duration) *AbsCaptureTimeExtension { + ns := captureClockOffset.Nanoseconds() + lsb := (ns / 1e9) & 0xFFFFFFFF + msb := (((ns % 1e9) * (1 << 32)) / 1e9) & 0xFFFFFFFF + offset := (lsb << 32) | msb + return &AbsCaptureTimeExtension{ + Timestamp: toNtpTime(captureTime), + EstimatedCaptureClockOffset: &offset, + } +} diff --git a/abscapturetimeextension_test.go b/abscapturetimeextension_test.go new file mode 100644 index 0000000..d5b9062 --- /dev/null +++ b/abscapturetimeextension_test.go @@ -0,0 +1,43 @@ +package rtp + +import ( + "testing" + "time" +) + +func TestAbsCaptureTimeExtension_Roundtrip(t *testing.T) { + t0 := time.Now() + e1 := NewAbsCaptureTimeExtension(t0) + b1, err1 := e1.Marshal() + if err1 != nil { + t.Fatal(err1) + } + var o1 AbsCaptureTimeExtension + if err := o1.Unmarshal(b1); err != nil { + t.Fatal(err) + } + dt1 := o1.CaptureTime().Sub(t0).Seconds() + if dt1 < -0.001 || dt1 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o1.CaptureTime(), dt1) + } + if o1.EstimatedCaptureClockOffsetDuration() != nil { + t.Fatalf("duration differs, want nil got %d", o1.EstimatedCaptureClockOffsetDuration()) + } + + e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, 1250*time.Millisecond) + b2, err2 := e2.Marshal() + if err2 != nil { + t.Fatal(err2) + } + var o2 AbsCaptureTimeExtension + if err := o2.Unmarshal(b2); err != nil { + t.Fatal(err) + } + dt2 := o1.CaptureTime().Sub(t0).Seconds() + if dt2 < -0.001 || dt2 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o2.CaptureTime(), dt2) + } + if *o2.EstimatedCaptureClockOffsetDuration() != 1250*time.Millisecond { + t.Fatalf("duration differs, want 250ms got %d", *o2.EstimatedCaptureClockOffsetDuration()) + } +} diff --git a/playoutdelayextension.go b/playoutdelayextension.go new file mode 100644 index 0000000..e508503 --- /dev/null +++ b/playoutdelayextension.go @@ -0,0 +1,47 @@ +package rtp + +import ( + "encoding/binary" + "errors" +) + +const ( + playoutDelayExtensionSize = 3 + playoutDelayMaxValue = (1 << 12) - 1 +) + +var errPlayoutDelayInvalidValue = errors.New("invalid playout delay value") + +// PlayoutDelayExtension is a extension payload format in +// http://www.webrtc.org/experiments/rtp-hdrext/playout-delay +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | ID | len=2 | MIN delay | MAX delay | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +type PlayoutDelayExtension struct { + minDelay, maxDelay uint16 +} + +// Marshal serializes the members to buffer +func (p PlayoutDelayExtension) Marshal() ([]byte, error) { + if p.minDelay > playoutDelayMaxValue || p.maxDelay > playoutDelayMaxValue { + return nil, errPlayoutDelayInvalidValue + } + + return []byte{ + byte(p.minDelay >> 4), + byte(p.minDelay<<4) | byte(p.maxDelay>>8), + byte(p.maxDelay), + }, nil +} + +// Unmarshal parses the passed byte slice and stores the result in the members +func (p *PlayoutDelayExtension) Unmarshal(rawData []byte) error { + if len(rawData) < playoutDelayExtensionSize { + return errTooSmall + } + p.minDelay = binary.BigEndian.Uint16(rawData[0:2]) >> 4 + p.maxDelay = binary.BigEndian.Uint16(rawData[1:3]) & 0x0FFF + return nil +} diff --git a/playoutdelayextension_test.go b/playoutdelayextension_test.go new file mode 100644 index 0000000..6757b14 --- /dev/null +++ b/playoutdelayextension_test.go @@ -0,0 +1,70 @@ +package rtp + +import ( + "bytes" + "errors" + "testing" +) + +func TestPlayoutDelayExtensionTooSmall(t *testing.T) { + t1 := PlayoutDelayExtension{} + + var rawData []byte + + if err := t1.Unmarshal(rawData); !errors.Is(err, errTooSmall) { + t.Fatal("err != errTooSmall") + } +} + +func TestPlayoutDelayExtensionTooLarge(t *testing.T) { + t1 := PlayoutDelayExtension{minDelay: 1 << 12, maxDelay: 1 << 12} + + if _, err := t1.Marshal(); !errors.Is(err, errPlayoutDelayInvalidValue) { + t.Fatal("err != errPlayoutDelayInvalidValue") + } +} + +func TestPlayoutDelayExtension(t *testing.T) { + t1 := PlayoutDelayExtension{} + + rawData := []byte{ + 0x01, 0x01, 0x00, + } + + if err := t1.Unmarshal(rawData); err != nil { + t.Fatal("Unmarshal error on extension data") + } + + t2 := PlayoutDelayExtension{ + minDelay: 1 << 4, maxDelay: 1 << 8, + } + + if t1 != t2 { + t.Error("Unmarshal failed") + } + + dstData, _ := t2.Marshal() + if !bytes.Equal(dstData, rawData) { + t.Error("Marshal failed") + } +} + +func TestPlayoutDelayExtensionExtraBytes(t *testing.T) { + t1 := PlayoutDelayExtension{} + + rawData := []byte{ + 0x01, 0x01, 0x00, 0xff, 0xff, + } + + if err := t1.Unmarshal(rawData); err != nil { + t.Fatal("Unmarshal error on extension data") + } + + t2 := PlayoutDelayExtension{ + minDelay: 1 << 4, maxDelay: 1 << 8, + } + + if t1 != t2 { + t.Error("Unmarshal failed") + } +} From 0aced6a2b19ae631ba57335a8bd344d76e609c45 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sat, 7 Jan 2023 17:11:11 +0100 Subject: [PATCH 050/102] Fix multiple crashes when using VP9 depacketizer Multiple optional fields of the VP9 RTP header are read regardless of the buffer size. This can easily lead to multiple crashes. This PR add the necessary checks in order to avoid crashes. --- codecs/vp9_packet.go | 79 ++++++++++++++++++++++++++------------- codecs/vp9_packet_test.go | 16 ++++++++ 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index f33c515..a5233f3 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -207,12 +207,15 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { // Picture ID: // -// +-+-+-+-+-+-+-+-+ +// +-+-+-+-+-+-+-+-+ +// // I: |M| PICTURE ID | M:0 => picture id is 7 bits. -// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 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 @@ -245,10 +248,11 @@ func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { // 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 @@ -269,12 +273,13 @@ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { // Layer indices (non-flexible mode): // -// +-+-+-+-+-+-+-+-+ +// +-+-+-+-+-+-+-+-+ +// // L: | T |U| S |D| -// +-+-+-+-+-+-+-+-+ -// | TL0PICIDX | -// +-+-+-+-+-+-+-+-+ // +// +-+-+-+-+-+-+-+-+ +// | TL0PICIDX | +// +-+-+-+-+-+-+-+-+ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -287,11 +292,12 @@ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, // Reference indices: // -// +-+-+-+-+-+-+-+-+ P=1,F=1: At least one reference index +// +-+-+-+-+-+-+-+-+ 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. // +// +-+-+-+-+-+-+-+-+ 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 { @@ -313,24 +319,30 @@ func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { // Scalability structure (SS): // -// +-+-+-+-+-+-+-+-+ +// +-+-+-+-+-+-+-+-+ +// // V: | N_S |Y|G|-|-|-| -// +-+-+-+-+-+-+-+-+ -| +// +// +-+-+-+-+-+-+-+-+ -| +// // Y: | WIDTH | (OPTIONAL) . -// + + . -// | | (OPTIONAL) . -// +-+-+-+-+-+-+-+-+ . N_S + 1 times -// | HEIGHT | (OPTIONAL) . -// + + . -// | | (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 . -// +-+-+-+-+-+-+-+-+ -| -| // +// +-+-+-+-+-+-+-+-+ -| . 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 @@ -348,6 +360,10 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { p.Width = make([]uint16, NS) p.Height = make([]uint16, NS) for i := 0; i < int(NS); i++ { + if len(packet) <= (pos + 3) { + return pos, errShortPacket + } + p.Width[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) pos += 2 p.Height[i] = uint16(packet[pos])<<8 | uint16(packet[pos+1]) @@ -356,17 +372,30 @@ func (p *VP9Packet) parseSSData(packet []byte, pos int) (int, error) { } if p.G { + if len(packet) <= pos { + return pos, errShortPacket + } + p.NG = packet[pos] pos++ } for i := 0; i < int(p.NG); i++ { + if len(packet) <= pos { + return pos, errShortPacket + } + p.PGTID = append(p.PGTID, packet[pos]>>5) p.PGU = append(p.PGU, packet[pos]&0x10 != 0) R := (packet[pos] >> 2) & 0x3 pos++ p.PGPDiff = append(p.PGPDiff, []uint8{}) + + if len(packet) <= (pos + int(R) - 1) { + return pos, errShortPacket + } + for j := 0; j < int(R); j++ { p.PGPDiff[i] = append(p.PGPDiff[i], packet[pos]) pos++ diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index 65861c6..f2c5e2d 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -167,6 +167,22 @@ func TestVP9Packet_Unmarshal(t *testing.T) { Payload: []byte{}, }, }, + "ScalabilityMissingWidth": { + b: []byte("200"), + err: errShortPacket, + }, + "ScalabilityMissingNG": { + b: []byte("b00200000000"), + err: errShortPacket, + }, + "ScalabilityMissingTemporalLayerIDs": { + b: []byte("20B0"), + err: errShortPacket, + }, + "ScalabilityMissingReferenceIndices": { + b: []byte("20B007"), + err: errShortPacket, + }, } for name, c := range cases { c := c From d182aaf4e137672279f4745c5d0d67bbfeebd5f0 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Fri, 6 Jan 2023 20:50:53 +0100 Subject: [PATCH 051/102] Fix parsing of VP8 packets with degenerate header All of the fields in the VP8 header except the first byte are optional. We used to reject VP8 packets smaller than 4 bytes, which is incorrect. There are two cases where such packets may appear on the wire. GStreamer's WebRTC implementation generates VP8 streams with no picture id, and one-byte headers. It will occasionally generate packets that are below 4 bytes, and which we used to reject. The second use case is more theoretical. According to RFC 7741 Section 4.4, a packetizer may ignore VP8 partition boundaries. If it splits a packet outside of a partition boundary, it may generate a packet with S=0 and a one-byte header. --- codecs/vp8_packet.go | 48 +++++++++++++++++++++++++++------------ codecs/vp8_packet_test.go | 41 ++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index 2eb7bb8..f4608d0 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -127,12 +127,11 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { payloadLen := len(payload) - if payloadLen < 4 { - return nil, errShortPacket - } - payloadIndex := 0 + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.X = (payload[payloadIndex] & 0x80) >> 7 p.N = (payload[payloadIndex] & 0x20) >> 5 p.S = (payload[payloadIndex] & 0x10) >> 4 @@ -141,14 +140,25 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { payloadIndex++ if p.X == 1 { + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.I = (payload[payloadIndex] & 0x80) >> 7 p.L = (payload[payloadIndex] & 0x40) >> 6 p.T = (payload[payloadIndex] & 0x20) >> 5 p.K = (payload[payloadIndex] & 0x10) >> 4 payloadIndex++ + } else { + p.I = 0 + p.L = 0 + p.T = 0 + p.K = 0 } if p.I == 1 { // PID present? + if payloadIndex >= payloadLen { + return nil, errShortPacket + } if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1]) payloadIndex += 2 @@ -156,35 +166,43 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { p.PictureID = uint16(payload[payloadIndex]) payloadIndex++ } - } - - if payloadIndex >= payloadLen { - return nil, errShortPacket + } else { + p.PictureID = 0 } if p.L == 1 { + if payloadIndex >= payloadLen { + return nil, errShortPacket + } p.TL0PICIDX = payload[payloadIndex] payloadIndex++ - } - - if payloadIndex >= payloadLen { - return nil, errShortPacket + } else { + p.TL0PICIDX = 0 } if p.T == 1 || p.K == 1 { + if payloadIndex >= payloadLen { + return nil, errShortPacket + } if p.T == 1 { p.TID = payload[payloadIndex] >> 6 p.Y = (payload[payloadIndex] >> 5) & 0x1 + } else { + p.TID = 0 + p.Y = 0 } if p.K == 1 { p.KEYIDX = payload[payloadIndex] & 0x1F + } else { + p.KEYIDX = 0 } payloadIndex++ + } else { + p.TID = 0 + p.Y = 0 + p.KEYIDX = 0 } - if payloadIndex >= payloadLen { - return nil, errShortPacket - } p.Payload = payload[payloadIndex:] return p.Payload, nil } diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index b173cfb..e7a9610 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -27,15 +27,6 @@ func TestVP8Packet_Unmarshal(t *testing.T) { 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 { @@ -65,11 +56,11 @@ func TestVP8Packet_Unmarshal(t *testing.T) { // 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 raw == nil { + t.Fatal("Result shouldn't be nil in case of success") } - if !errors.Is(err, errShortPacket) { - t.Fatal("Error should be:", errShortPacket) + if err != nil { + t.Fatal("Error should be nil in case of success") } // Header size, X and L @@ -107,6 +98,30 @@ func TestVP8Packet_Unmarshal(t *testing.T) { if !errors.Is(err, errShortPacket) { t.Fatal("Error should be:", errShortPacket) } + + // According to RFC 7741 Section 4.4, the packetizer need not pay + // attention to partition boundaries. In that case, it may + // produce packets with minimal headers. + + // The next two have been witnessed in nature. + _, err = pck.Unmarshal([]byte{0x00}) + if err != nil { + t.Errorf("Empty packet with trivial header: %v", err) + } + _, err = pck.Unmarshal([]byte{0x00, 0x2a, 0x94}) + if err != nil { + t.Errorf("Non-empty packet with trivial header: %v", err) + } + + // The following two were invented. + _, err = pck.Unmarshal([]byte{0x80, 0x00}) + if err != nil { + t.Errorf("Empty packet with trivial extension: %v", err) + } + _, err = pck.Unmarshal([]byte{0x80, 0x80, 42}) + if err != nil { + t.Errorf("Header with PictureID: %v", err) + } } func TestVP8Payloader_Payload(t *testing.T) { From 2aa7175b923769da2f0f1d1700668469e1b5a78b Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 12 Feb 2023 21:08:48 +0100 Subject: [PATCH 052/102] Fix compatibility with gofmt This prevents gofmt -w -s from messing up existing code. Text with multiple spaces is now wrapped in long comments (/* */), that are not affected by gofmt. --- codecs/av1_packet.go | 12 +-- codecs/h265_packet.go | 184 ++++++++++++++++++++++-------------------- codecs/vp9_packet.go | 100 ++++++++++------------- 3 files changed, 145 insertions(+), 151 deletions(-) diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 120a904..80cc513 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -69,12 +69,12 @@ func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) { } // AV1Packet represents a depacketized AV1 RTP Packet -// -// 0 1 2 3 4 5 6 7 -// +-+-+-+-+-+-+-+-+ -// |Z|Y| W |N|-|-|-| -// +-+-+-+-+-+-+-+-+ -// +/* +* 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 diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 6f0490d..58a6c7d 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -32,11 +32,13 @@ const ( // 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 | -// +-------------+-----------------+ +/* +* +---------------+---------------+ +* |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 { @@ -95,18 +97,19 @@ func (h H265NALUHeader) IsPACIPacket() bool { // // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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. @@ -184,19 +187,19 @@ func (p *H265SingleNALUnitPacket) isH265Packet() {} // // 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 | -// | | -// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | : -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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 @@ -222,18 +225,18 @@ func (u H265AggregationUnitFirst) NalUnit() []byte { } // 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 | -// | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | : -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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 @@ -259,18 +262,19 @@ func (u H265AggregationUnit) NalUnit() []byte { } // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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 @@ -395,11 +399,13 @@ const ( ) // H265FragmentationUnitHeader is a H265 FU Header -// +---------------+ -// |0|1|2|3|4|5|6|7| -// +-+-+-+-+-+-+-+-+ -// |S|E| FuType | -// +---------------+ +/* +* +---------------+ +* |0|1|2|3|4|5|6|7| +* +-+-+-+-+-+-+-+-+ +* |S|E| FuType | +* +---------------+ +**/ type H265FragmentationUnitHeader uint8 // S represents the start of a fragmented NAL unit. @@ -421,20 +427,20 @@ func (h H265FragmentationUnitHeader) FuType() uint8 { } // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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. @@ -521,22 +527,22 @@ func (p *H265FragmentationUnitPacket) isH265Packet() {} // // 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 | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// +/* +* 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. diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index a5233f3..8cc3b68 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -206,16 +206,13 @@ func (p *VP9Packet) Unmarshal(packet []byte) ([]byte, error) { } // Picture ID: -// -// +-+-+-+-+-+-+-+-+ -// -// I: |M| PICTURE ID | M:0 => picture id is 7 bits. -// -// +-+-+-+-+-+-+-+-+ M:1 => picture id is 15 bits. -// -// M: | EXTENDED PID | -// -// +-+-+-+-+-+-+-+-+ +/* +* +-+-+-+-+-+-+-+-+ +* 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 @@ -247,12 +244,11 @@ func (p *VP9Packet) parseLayerInfo(packet []byte, pos int) (int, error) { } // Layer indices (flexible mode): -// -// +-+-+-+-+-+-+-+-+ -// -// L: | T |U| S |D| -// -// +-+-+-+-+-+-+-+-+ +/* +* +-+-+-+-+-+-+-+-+ +* L: | T |U| S |D| +* +-+-+-+-+-+-+-+-+ +**/ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -272,14 +268,13 @@ func (p *VP9Packet) parseLayerInfoCommon(packet []byte, pos int) (int, error) { } // Layer indices (non-flexible mode): -// -// +-+-+-+-+-+-+-+-+ -// -// L: | T |U| S |D| -// -// +-+-+-+-+-+-+-+-+ -// | TL0PICIDX | -// +-+-+-+-+-+-+-+-+ +/* +* +-+-+-+-+-+-+-+-+ +* L: | T |U| S |D| +* +-+-+-+-+-+-+-+-+ +* | TL0PICIDX | +* +-+-+-+-+-+-+-+-+ +**/ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, error) { if len(packet) <= pos { return pos, errShortPacket @@ -291,13 +286,12 @@ func (p *VP9Packet) parseLayerInfoNonFlexibleMode(packet []byte, pos int) (int, } // 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. +/* +* +-+-+-+-+-+-+-+-+ 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 { @@ -318,31 +312,25 @@ func (p *VP9Packet) parseRefIndices(packet []byte, pos int) (int, error) { } // 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 . -// +-+-+-+-+-+-+-+-+ -| -| +/* +* +-+-+-+-+-+-+-+-+ +* 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 From b94e3b9a476871a0634c8615622b818124bd5e2b Mon Sep 17 00:00:00 2001 From: kawaway Date: Thu, 2 Feb 2023 17:41:03 +0900 Subject: [PATCH 053/102] Fix errShortPacket with H264 EOS NALUs Remove checks since payload size can be less than 2. Only FU-A explicitly accesses the second byte, which is not affected because a separate boundary value check is performed. --- codecs/h264_packet.go | 6 ++---- codecs/h264_packet_test.go | 12 ++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index f14e4f6..7ffa9b2 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -211,10 +211,8 @@ func (p *H264Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bo // 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)) + if len(payload) == 0 { + return nil, fmt.Errorf("%w: %d <=0", errShortPacket, len(payload)) } // NALU Types diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index 47826d7..fd77a3e 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -115,8 +115,16 @@ func TestH264Packet_Unmarshal(t *testing.T) { 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{}); err == nil { + t.Fatal("Unmarshal did not fail on []byte{}") + } + + if _, err := pkt.Unmarshal([]byte{0xFC}); err == nil { + t.Fatal("Unmarshal accepted a FU-A packet that is too small for a payload and header") + } + + if _, err := pkt.Unmarshal([]byte{0x0A}); err != nil { + t.Fatal("Unmarshaling end of sequence(NALU Type : 10) should succeed") } if _, err := pkt.Unmarshal([]byte{0xFF, 0x00, 0x00}); err == nil { From dcc94b2f56bf1ef830cab81e15c3f7f8eb508c82 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sat, 11 Mar 2023 13:03:48 +0100 Subject: [PATCH 054/102] Cleanup common sections in README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ce04599..ad77866 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Sourcegraph Widget Slack Widget
- Build Status - GoDoc + GitHub Workflow Status + Go Reference Coverage Status Go Report Card License: MIT @@ -21,14 +21,15 @@ 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). +Pion has an active community on the [Slack](https://pion.ly/slack). -We are always looking to support **your projects**. Please reach out if you have something to build! +Follow the [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. +We are always looking to support **your projects**. Please reach out if you have something to build! If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) ### Contributing -Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: +Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contributing)** to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt) ### License -MIT License - see [LICENSE](LICENSE) for full text +MIT License - see [LICENSE](LICENSE) for full text \ No newline at end of file From 2c12670cee4cb1085a31fc6887597833fea43a5a Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 13 Mar 2023 09:46:01 +0100 Subject: [PATCH 055/102] Update AUTHORS.txt --- AUTHORS.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index a7bc7a0..a722a99 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -18,6 +18,7 @@ Haiyang Wang Hugo Arregui John Bradley Juliusz Chroboczek +kawaway Kazuyuki Honda Luke Curley lxb @@ -31,6 +32,7 @@ Sean DuBois Sean DuBois Sean DuBois Simone Gotti +Steffen Vogel Tarrence van As wangzixiang Woodrow Douglass From 613867298786249017759ec191bcc268d5097fd0 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 5 Apr 2023 08:42:43 +0200 Subject: [PATCH 056/102] Harmonize sections in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad77866..37b7cc0 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ We are always looking to support **your projects**. Please reach out if you have 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: [AUTHORS.txt](./AUTHORS.txt) +Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible: [AUTHORS.txt](./AUTHORS.txt) ### License MIT License - see [LICENSE](LICENSE) for full text \ No newline at end of file From 37af8d405e9060ab6ab28513dad95caa1191df8a Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 18 Apr 2023 10:37:16 +0000 Subject: [PATCH 057/102] Update CI configs to v0.10.7 Update lint scripts and CI configs. --- .github/.gitignore | 4 ++ .github/fetch-scripts.sh | 31 +++++++++++ .github/install-hooks.sh | 2 + .github/workflows/codeql-analysis.yml | 28 ++++++++++ .github/workflows/generate-authors.yml | 2 + .github/workflows/lint.yaml | 2 + .github/workflows/release.yml | 24 +++++++++ .github/workflows/renovate-go-sum-fix.yaml | 24 +++++++++ .github/workflows/reuse.yml | 22 ++++++++ .github/workflows/test.yaml | 60 +++------------------- .github/workflows/tidy-check.yaml | 22 +++----- .gitignore | 3 ++ .golangci.yml | 26 +++++++++- .goreleaser.yml | 5 ++ .reuse/dep5 | 7 +++ codecov.yml | 2 + 16 files changed, 193 insertions(+), 71 deletions(-) create mode 100644 .github/.gitignore create mode 100755 .github/fetch-scripts.sh create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/renovate-go-sum-fix.yaml create mode 100644 .github/workflows/reuse.yml create mode 100644 .goreleaser.yml create mode 100644 .reuse/dep5 diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..c3421a1 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +.goassets diff --git a/.github/fetch-scripts.sh b/.github/fetch-scripts.sh new file mode 100755 index 0000000..f333841 --- /dev/null +++ b/.github/fetch-scripts.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +set -eu + +SCRIPT_PATH="$(realpath "$(dirname "$0")")" +GOASSETS_PATH="${SCRIPT_PATH}/.goassets" + +GOASSETS_REF=${GOASSETS_REF:-master} + +if [ -d "${GOASSETS_PATH}" ]; then + if ! git -C "${GOASSETS_PATH}" diff --exit-code; then + echo "${GOASSETS_PATH} has uncommitted changes" >&2 + exit 1 + fi + git -C "${GOASSETS_PATH}" fetch origin + git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} + git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} +else + git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" +fi diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh index 73d20a4..59cd271 100755 --- a/.github/install-hooks.sh +++ b/.github/install-hooks.sh @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..ea9b825 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,28 @@ +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +name: CodeQL + +on: + workflow_dispatch: + schedule: + - cron: '23 5 * * 0' + pull_request: + branches: + - master + paths: + - '**.go' + +jobs: + analyze: + uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index 9a80a48..c45e21f 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: generate-authors diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index d284b19..e1bc94f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Lint on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..01227e2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,24 @@ +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +name: Release +on: + push: + tags: + - 'v*' + +jobs: + release: + uses: pion/.goassets/.github/workflows/release.reusable.yml@master + with: + go-version: '1.20' # auto-update/latest-go-version diff --git a/.github/workflows/renovate-go-sum-fix.yaml b/.github/workflows/renovate-go-sum-fix.yaml new file mode 100644 index 0000000..b7bb1b4 --- /dev/null +++ b/.github/workflows/renovate-go-sum-fix.yaml @@ -0,0 +1,24 @@ +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +name: Fix go.sum +on: + push: + branches: + - renovate/* + +jobs: + fix: + uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master + secrets: + token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml new file mode 100644 index 0000000..8633a12 --- /dev/null +++ b/.github/workflows/reuse.yml @@ -0,0 +1,22 @@ +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +name: REUSE Compliance Check + +on: + push: + pull_request: + +jobs: + lint: + uses: pion/.goassets/.github/workflows/reuse.reusable.yml@master diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 80f75d0..16d4c66 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Test on: @@ -22,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.16", "1.17"] + go: ['1.20', '1.19'] # auto-update/supported-go-version-list fail-fast: false name: Go ${{ matrix.go }} steps: @@ -87,7 +89,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: ["1.16", "1.17"] + go: ['1.20', '1.19'] # auto-update/supported-go-version-list fail-fast: false name: Go i386 ${{ matrix.go }} steps: @@ -119,54 +121,6 @@ jobs: -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@v3 - 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 + uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master + with: + go-version: '1.20' # auto-update/latest-go-version diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 3ab2c35..79565dd 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -8,6 +8,8 @@ # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT name: Go mod tidy on: @@ -19,19 +21,7 @@ on: - 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 + tidy: + uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master + with: + go-version: '1.20' # auto-update/latest-go-version diff --git a/.gitignore b/.gitignore index f977e74..6e2f206 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + ### JetBrains IDE ### ##################### .idea/ diff --git a/.golangci.yml b/.golangci.yml index d7a88ec..3cf9ffd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + linters-settings: govet: check-shadowing: true @@ -10,7 +13,15 @@ linters-settings: modules: - github.com/pkg/errors: recommendations: - - errors + - errors + forbidigo: + forbid: + - Fatal(f|ln)?$ + - ^fmt.Print(f|ln)?$ + - ^log.Print(f|ln)?$ + - ^os.Exit$ + - ^panic$ + - ^print(ln)?$ linters: enable: @@ -30,6 +41,7 @@ linters: - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. - exhaustive # check exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # Forbids identifiers - forcetypeassert # finds forced type assertions - gci # Gci control golang package import order and make it always deterministic. - gochecknoglobals # Checks that no globals are present in Go code @@ -76,7 +88,6 @@ linters: - 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 @@ -108,12 +119,23 @@ issues: - path: _test\.go linters: - gocognit + - forbidigo # Allow complex main function in examples - path: examples text: "of func `main` is high" linters: - gocognit + + # Allow forbidden identifiers in examples + - path: examples + linters: + - forbidigo + + # Allow forbidden identifiers in CLI commands + - path: cmd + linters: + - forbidigo run: skip-dirs-use-default: false diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..30093e9 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +builds: +- skip: true diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..ccc710a --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,7 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Pion +Source: https://github.com/pion/ + +Files: README.md AUTHORS.txt renovate.json +Copyright: 2023 The Pion community +License: MIT diff --git a/codecov.yml b/codecov.yml index 085200a..263e4d4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,6 +3,8 @@ # # It is automatically copied from https://github.com/pion/.goassets repository. # +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT coverage: status: From 89a87bdb9a13357c9b5d048a1e9dffd5297aef67 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 5 May 2023 14:41:20 +0200 Subject: [PATCH 058/102] Make repo REUSE compliant --- .reuse/dep5 | 6 +++++- LICENSE | 22 +--------------------- LICENSES/MIT.txt | 9 +++++++++ abscapturetimeextension.go | 3 +++ abscapturetimeextension_test.go | 3 +++ abssendtimeextension.go | 3 +++ abssendtimeextension_test.go | 3 +++ audiolevelextension.go | 3 +++ audiolevelextension_test.go | 3 +++ codecs/av1_packet.go | 3 +++ codecs/av1_packet_test.go | 3 +++ codecs/codecs.go | 3 +++ codecs/common.go | 3 +++ codecs/common_test.go | 3 +++ codecs/error.go | 3 +++ codecs/g711_packet.go | 3 +++ codecs/g711_packet_test.go | 3 +++ codecs/g722_packet.go | 3 +++ codecs/g722_packet_test.go | 3 +++ codecs/h264_packet.go | 3 +++ codecs/h264_packet_test.go | 3 +++ codecs/h265_packet.go | 3 +++ codecs/h265_packet_test.go | 3 +++ codecs/opus_packet.go | 3 +++ codecs/opus_packet_test.go | 3 +++ codecs/vp8_packet.go | 3 +++ codecs/vp8_packet_test.go | 3 +++ codecs/vp9_packet.go | 3 +++ codecs/vp9_packet_test.go | 3 +++ depacketizer.go | 3 +++ error.go | 3 +++ header_extension.go | 3 +++ header_extension_test.go | 3 +++ packet.go | 3 +++ packet_test.go | 3 +++ packetizer.go | 3 +++ packetizer_test.go | 3 +++ partitionheadchecker.go | 3 +++ pkg/frame/av1.go | 3 +++ pkg/frame/av1_test.go | 3 +++ pkg/obu/leb128.go | 3 +++ pkg/obu/leb128_test.go | 3 +++ playoutdelayextension.go | 3 +++ playoutdelayextension_test.go | 3 +++ rand.go | 3 +++ rtp.go | 3 +++ sequencer.go | 3 +++ transportccextension.go | 3 +++ transportccextension_test.go | 3 +++ 49 files changed, 153 insertions(+), 22 deletions(-) mode change 100644 => 120000 LICENSE create mode 100644 LICENSES/MIT.txt diff --git a/.reuse/dep5 b/.reuse/dep5 index ccc710a..afb63d7 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -2,6 +2,10 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ -Files: README.md AUTHORS.txt renovate.json +Files: README.md **/README.md AUTHORS.txt renovate.json go.mod go.sum Copyright: 2023 The Pion community License: MIT + +Files: testdata/fuzz/* **/testdata/fuzz/* api/*.txt +Copyright: 2023 The Pion community +License: CC0-1.0 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index ab60297..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..31ff787 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +LICENSES/MIT.txt \ No newline at end of file diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +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. diff --git a/abscapturetimeextension.go b/abscapturetimeextension.go index f3005e9..56b783d 100644 --- a/abscapturetimeextension.go +++ b/abscapturetimeextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/abscapturetimeextension_test.go b/abscapturetimeextension_test.go index d5b9062..15c434a 100644 --- a/abscapturetimeextension_test.go +++ b/abscapturetimeextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/abssendtimeextension.go b/abssendtimeextension.go index f0c6de3..fff38e9 100644 --- a/abssendtimeextension.go +++ b/abssendtimeextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/abssendtimeextension_test.go b/abssendtimeextension_test.go index 16243fd..16834d9 100644 --- a/abssendtimeextension_test.go +++ b/abssendtimeextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/audiolevelextension.go b/audiolevelextension.go index ca44f28..f180c8d 100644 --- a/audiolevelextension.go +++ b/audiolevelextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/audiolevelextension_test.go b/audiolevelextension_test.go index b1ad89f..9c651a0 100644 --- a/audiolevelextension_test.go +++ b/audiolevelextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 80cc513..01aa89b 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 06533f1..8c0a8f4 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/codecs.go b/codecs/codecs.go index 0e07897..cd1c891 100644 --- a/codecs/codecs.go +++ b/codecs/codecs.go @@ -1,2 +1,5 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package codecs implements codec specific RTP payloader/depayloaders package codecs diff --git a/codecs/common.go b/codecs/common.go index af5632a..8746550 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs func min(a, b int) int { diff --git a/codecs/common_test.go b/codecs/common_test.go index 5cbbd3c..3487276 100644 --- a/codecs/common_test.go +++ b/codecs/common_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/error.go b/codecs/error.go index 7f72e7b..2083ef4 100644 --- a/codecs/error.go +++ b/codecs/error.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import "errors" diff --git a/codecs/g711_packet.go b/codecs/g711_packet.go index 7ab68b2..4afda55 100644 --- a/codecs/g711_packet.go +++ b/codecs/g711_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // G711Payloader payloads G711 packets diff --git a/codecs/g711_packet_test.go b/codecs/g711_packet_test.go index 36ee729..75fa6a8 100644 --- a/codecs/g711_packet_test.go +++ b/codecs/g711_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs //nolint:dupl import ( diff --git a/codecs/g722_packet.go b/codecs/g722_packet.go index 13e17b6..ae6672d 100644 --- a/codecs/g722_packet.go +++ b/codecs/g722_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // G722Payloader payloads G722 packets diff --git a/codecs/g722_packet_test.go b/codecs/g722_packet_test.go index cc75e6f..f15f81c 100644 --- a/codecs/g722_packet_test.go +++ b/codecs/g722_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs //nolint:dupl import ( diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 7ffa9b2..17ca1bb 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index fd77a3e..8e3b221 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/h265_packet.go b/codecs/h265_packet.go index 58a6c7d..2a194fd 100644 --- a/codecs/h265_packet.go +++ b/codecs/h265_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/h265_packet_test.go b/codecs/h265_packet_test.go index 2172abe..1c5f96a 100644 --- a/codecs/h265_packet_test.go +++ b/codecs/h265_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index dfcfe99..367b63c 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // OpusPayloader payloads Opus packets diff --git a/codecs/opus_packet_test.go b/codecs/opus_packet_test.go index e68ff57..665d17b 100644 --- a/codecs/opus_packet_test.go +++ b/codecs/opus_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index f4608d0..5d0a654 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs // VP8Payloader payloads VP8 packets diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index e7a9610..bb09ef1 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 8cc3b68..6a6e0b0 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index f2c5e2d..97e176b 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package codecs import ( diff --git a/depacketizer.go b/depacketizer.go index c66d2e3..0439a53 100644 --- a/depacketizer.go +++ b/depacketizer.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload diff --git a/error.go b/error.go index 5458c6f..4df5d2a 100644 --- a/error.go +++ b/error.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/header_extension.go b/header_extension.go index d010bb8..fe54215 100644 --- a/header_extension.go +++ b/header_extension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/header_extension_test.go b/header_extension_test.go index 8aba946..bac3bd0 100644 --- a/header_extension_test.go +++ b/header_extension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packet.go b/packet.go index d802d20..c2e527e 100644 --- a/packet.go +++ b/packet.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packet_test.go b/packet_test.go index a2a5b7d..0ccd26c 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packetizer.go b/packetizer.go index 5d73257..c412732 100644 --- a/packetizer.go +++ b/packetizer.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/packetizer_test.go b/packetizer_test.go index 66d614c..a4ac3b8 100644 --- a/packetizer_test.go +++ b/packetizer_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/partitionheadchecker.go b/partitionheadchecker.go index 6ec2a76..6f05aa5 100644 --- a/partitionheadchecker.go +++ b/partitionheadchecker.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp // PartitionHeadChecker is the interface that checks whether the packet is keyframe or not diff --git a/pkg/frame/av1.go b/pkg/frame/av1.go index 30525ab..0dadde6 100644 --- a/pkg/frame/av1.go +++ b/pkg/frame/av1.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package frame provides code to construct complete media frames from packetized media package frame diff --git a/pkg/frame/av1_test.go b/pkg/frame/av1_test.go index 9129eaf..aa6c73c 100644 --- a/pkg/frame/av1_test.go +++ b/pkg/frame/av1_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package frame import ( diff --git a/pkg/obu/leb128.go b/pkg/obu/leb128.go index 988a8f4..f0734f0 100644 --- a/pkg/obu/leb128.go +++ b/pkg/obu/leb128.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package obu implements tools for working with the "Open Bitstream Unit" package obu diff --git a/pkg/obu/leb128_test.go b/pkg/obu/leb128_test.go index 42cf777..f92fff4 100644 --- a/pkg/obu/leb128_test.go +++ b/pkg/obu/leb128_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package obu import ( diff --git a/playoutdelayextension.go b/playoutdelayextension.go index e508503..3882731 100644 --- a/playoutdelayextension.go +++ b/playoutdelayextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/playoutdelayextension_test.go b/playoutdelayextension_test.go index 6757b14..8810466 100644 --- a/playoutdelayextension_test.go +++ b/playoutdelayextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/rand.go b/rand.go index ee85523..3ddddd1 100644 --- a/rand.go +++ b/rand.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/rtp.go b/rtp.go index b66b2e4..5487232 100644 --- a/rtp.go +++ b/rtp.go @@ -1,2 +1,5 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + // Package rtp provides RTP packetizer and depacketizer package rtp diff --git a/sequencer.go b/sequencer.go index 2b4a507..8ad2cfd 100644 --- a/sequencer.go +++ b/sequencer.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/transportccextension.go b/transportccextension.go index 236af05..c2a998c 100644 --- a/transportccextension.go +++ b/transportccextension.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( diff --git a/transportccextension_test.go b/transportccextension_test.go index 5eb6967..5a06f2c 100644 --- a/transportccextension_test.go +++ b/transportccextension_test.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + package rtp import ( From 2db826a3db197af634e191d719b954137aefae09 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 5 May 2023 14:43:38 +0200 Subject: [PATCH 059/102] Fix golangci-lint warnings --- codecs/av1_packet_test.go | 3 +-- codecs/common.go | 6 +++--- codecs/opus_packet.go | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 8c0a8f4..746353f 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -5,7 +5,6 @@ package codecs import ( "errors" - "fmt" "reflect" "testing" @@ -52,7 +51,7 @@ func TestAV1_Unmarshal_Error(t *testing.T) { 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)) + t.Fatalf("Expected error(%s) but got (%s)", test.expectedError, err) } } } diff --git a/codecs/common.go b/codecs/common.go index 8746550..5da8aaf 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -13,17 +13,17 @@ func min(a, b int) int { // audioDepacketizer is a mixin for audio codec depacketizers type audioDepacketizer struct{} -func (d *audioDepacketizer) IsPartitionTail(marker bool, payload []byte) bool { +func (d *audioDepacketizer) IsPartitionTail(_ bool, _ []byte) bool { return true } -func (d *audioDepacketizer) IsPartitionHead(payload []byte) bool { +func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool { return true } // videoDepacketizer is a mixin for video codec depacketizers type videoDepacketizer struct{} -func (d *videoDepacketizer) IsPartitionTail(marker bool, payload []byte) bool { +func (d *videoDepacketizer) IsPartitionTail(marker bool, _ []byte) bool { return marker } diff --git a/codecs/opus_packet.go b/codecs/opus_packet.go index 367b63c..00ee903 100644 --- a/codecs/opus_packet.go +++ b/codecs/opus_packet.go @@ -7,7 +7,7 @@ package codecs type OpusPayloader struct{} // Payload fragments an Opus packet across one or more byte arrays -func (p *OpusPayloader) Payload(mtu uint16, payload []byte) [][]byte { +func (p *OpusPayloader) Payload(_ uint16, payload []byte) [][]byte { if payload == nil { return [][]byte{} } From 2ff897db99108584ff34448038e26e74dce45236 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Thu, 18 May 2023 19:15:48 +0000 Subject: [PATCH 060/102] Update CI configs to v0.10.8 Update lint scripts and CI configs. --- .golangci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 3cf9ffd..056488c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,9 +16,8 @@ linters-settings: - errors forbidigo: forbid: - - Fatal(f|ln)?$ - ^fmt.Print(f|ln)?$ - - ^log.Print(f|ln)?$ + - ^log.(Panic|Fatal|Print)(f|ln)?$ - ^os.Exit$ - ^panic$ - ^print(ln)?$ From 5549a8e1197fef6094f6c840bca12f20d8f8346a Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Fri, 19 May 2023 07:46:35 +0000 Subject: [PATCH 061/102] Update CI configs to v0.10.9 Update lint scripts and CI configs. --- .reuse/dep5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index afb63d7..c8b3dfa 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ -Files: README.md **/README.md AUTHORS.txt renovate.json go.mod go.sum +Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum .eslintrc.json package.json examples/examples.json Copyright: 2023 The Pion community License: MIT From 8fc8b94a807be3b8049bf0d520c098341be450de Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:42:40 +0200 Subject: [PATCH 062/102] Sync asset files with .goassets --- .github/generate-authors.sh | 66 ------------- .github/hooks/commit-msg.sh | 11 --- .github/hooks/pre-commit.sh | 12 --- .github/hooks/pre-push.sh | 13 --- .github/install-hooks.sh | 12 ++- .github/lint-commit-message.sh | 64 ------------ .../lint-disallowed-functions-in-library.sh | 48 --------- .github/lint-filename.sh | 24 ----- .github/workflows/generate-authors.yml | 62 +----------- .github/workflows/lint.yaml | 39 +------- .github/workflows/renovate-go-mod-fix.yaml | 33 ------- .github/workflows/test.yaml | 99 ++----------------- .github/workflows/tidy-check.yaml | 2 - .golangci.yml | 3 - AUTHORS.txt | 7 +- renovate.json | 25 +---- 16 files changed, 31 insertions(+), 489 deletions(-) delete mode 100755 .github/generate-authors.sh delete mode 100755 .github/hooks/commit-msg.sh delete mode 100755 .github/hooks/pre-commit.sh delete mode 100755 .github/hooks/pre-push.sh delete mode 100755 .github/lint-commit-message.sh delete mode 100755 .github/lint-disallowed-functions-in-library.sh delete mode 100755 .github/lint-filename.sh delete mode 100644 .github/workflows/renovate-go-mod-fix.yaml diff --git a/.github/generate-authors.sh b/.github/generate-authors.sh deleted file mode 100755 index 182e4f5..0000000 --- a/.github/generate-authors.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/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 diff --git a/.github/hooks/commit-msg.sh b/.github/hooks/commit-msg.sh deleted file mode 100755 index 8213dc2..0000000 --- a/.github/hooks/commit-msg.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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 diff --git a/.github/hooks/pre-commit.sh b/.github/hooks/pre-commit.sh deleted file mode 100755 index cc318d7..0000000 --- a/.github/hooks/pre-commit.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/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 diff --git a/.github/hooks/pre-push.sh b/.github/hooks/pre-push.sh deleted file mode 100755 index bfe65bc..0000000 --- a/.github/hooks/pre-push.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/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 diff --git a/.github/install-hooks.sh b/.github/install-hooks.sh index 59cd271..8aa34be 100755 --- a/.github/install-hooks.sh +++ b/.github/install-hooks.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # DO NOT EDIT THIS FILE @@ -11,8 +11,10 @@ # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT -SCRIPT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) +SCRIPT_PATH="$(realpath "$(dirname "$0")")" -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" +. ${SCRIPT_PATH}/fetch-scripts.sh + +cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" +cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" +cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" diff --git a/.github/lint-commit-message.sh b/.github/lint-commit-message.sh deleted file mode 100755 index 010a332..0000000 --- a/.github/lint-commit-message.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/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 diff --git a/.github/lint-disallowed-functions-in-library.sh b/.github/lint-disallowed-functions-in-library.sh deleted file mode 100755 index 8ce5d09..0000000 --- a/.github/lint-disallowed-functions-in-library.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/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 diff --git a/.github/lint-filename.sh b/.github/lint-filename.sh deleted file mode 100755 index 81b3f14..0000000 --- a/.github/lint-filename.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/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 diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml index c45e21f..ec7446c 100644 --- a/.github/workflows/generate-authors.yml +++ b/.github/workflows/generate-authors.yml @@ -11,65 +11,13 @@ # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT -name: generate-authors +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) + generate: + uses: pion/.goassets/.github/workflows/generate-authors.reusable.yml@master + secrets: + token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index e1bc94f..5dd3a99 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -14,40 +14,7 @@ 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.45.2 - args: $GOLANGCI_LINT_EXRA_ARGS +jobs: + lint: + uses: pion/.goassets/.github/workflows/lint.reusable.yml@master diff --git a/.github/workflows/renovate-go-mod-fix.yaml b/.github/workflows/renovate-go-mod-fix.yaml deleted file mode 100644 index 5991822..0000000 --- a/.github/workflows/renovate-go-mod-fix.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# -# 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 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 16d4c66..31aada4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,110 +15,27 @@ name: Test on: push: branches: - - master + - master pull_request: - branches: - - master + jobs: test: - runs-on: ubuntu-latest + uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: go: ['1.20', '1.19'] # auto-update/supported-go-version-list fail-fast: false - name: Go ${{ matrix.go }} - steps: - - uses: actions/checkout@v3 - - - uses: actions/cache@v3 - 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: 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} \ - -json \ - -v -race 2>&1 | 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: | - 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 + with: + go-version: ${{ matrix.go }} test-i386: - runs-on: ubuntu-latest + uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: go: ['1.20', '1.19'] # auto-update/supported-go-version-list fail-fast: false - name: Go i386 ${{ matrix.go }} - steps: - - uses: actions/checkout@v3 - - - uses: actions/cache@v3 - 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 ./... + with: + go-version: ${{ matrix.go }} test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 79565dd..4d346d4 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -14,8 +14,6 @@ name: Go mod tidy on: pull_request: - branches: - - master push: branches: - master diff --git a/.golangci.yml b/.golangci.yml index 056488c..4e3eddf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,7 +28,6 @@ linters: - 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()) @@ -71,7 +70,6 @@ linters: - 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 @@ -80,7 +78,6 @@ linters: - 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: diff --git a/AUTHORS.txt b/AUTHORS.txt index a722a99..38685a9 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -2,7 +2,8 @@ # 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 +# see https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting +Aaron Boushley adwpc aler9 <46489434+aler9@users.noreply.github.com> Antoine Baché @@ -20,6 +21,7 @@ John Bradley Juliusz Chroboczek kawaway Kazuyuki Honda +Kevin Wang Luke Curley lxb Michael MacDonald @@ -36,3 +38,6 @@ Steffen Vogel Tarrence van As wangzixiang Woodrow Douglass + +# List of contributors not appearing in Git history + diff --git a/renovate.json b/renovate.json index f161405..f1bb98c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,27 +1,6 @@ { + "$schema": "https://docs.renovatebot.com/renovate-schema.json", "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" + "github>pion/renovate-config" ] } From a11a4603368b65e52ae3055d9b8ed15d4d87a0ea Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 12 Jun 2023 22:52:00 +0200 Subject: [PATCH 063/102] Minimize changes --- packet.go | 115 +++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/packet.go b/packet.go index c2e527e..f1f12a6 100644 --- a/packet.go +++ b/packet.go @@ -81,10 +81,10 @@ func (p Packet) String() string { return out } -// Unmarshal parses the passed byte slice and stores the result in the Header this method is called upon -func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit - if len(rawPacket) < headerLength { - return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(rawPacket), headerLength) +// Unmarshal parses the passed byte slice and stores the result in the Header. +func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit + if len(buf) < headerLength { + return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), headerLength) } /* @@ -102,31 +102,31 @@ func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ - h.Version = rawPacket[0] >> versionShift & versionMask - h.Padding = (rawPacket[0] >> paddingShift & paddingMask) > 0 - h.Extension = (rawPacket[0] >> extensionShift & extensionMask) > 0 - nCSRC := int(rawPacket[0] & ccMask) + 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] } - currOffset := csrcOffset + (nCSRC * csrcLength) - if len(rawPacket) < currOffset { - return fmt.Errorf("size %d < %d: %w", len(rawPacket), currOffset, errHeaderSizeInsufficient) + n := csrcOffset + (nCSRC * csrcLength) + if len(buf) < n { + return fmt.Errorf("size %d < %d: %w", len(buf), n, errHeaderSizeInsufficient) } - h.Marker = (rawPacket[1] >> markerShift & markerMask) > 0 - h.PayloadType = rawPacket[1] & ptMask + h.Marker = (buf[1] >> markerShift & markerMask) > 0 + h.PayloadType = buf[1] & ptMask - h.SequenceNumber = binary.BigEndian.Uint16(rawPacket[seqNumOffset : seqNumOffset+seqNumLength]) - h.Timestamp = binary.BigEndian.Uint32(rawPacket[timestampOffset : timestampOffset+timestampLength]) - h.SSRC = binary.BigEndian.Uint32(rawPacket[ssrcOffset : ssrcOffset+ssrcLength]) + 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(rawPacket[offset:]) + h.CSRC[i] = binary.BigEndian.Uint32(buf[offset:]) } if h.Extensions != nil { @@ -134,21 +134,21 @@ func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit } if h.Extension { - if expected := currOffset + 4; len(rawPacket) < expected { + if expected := n + 4; len(buf) < expected { return fmt.Errorf("size %d < %d: %w", - len(rawPacket), expected, + len(buf), expected, errHeaderSizeInsufficientForExtension, ) } - h.ExtensionProfile = binary.BigEndian.Uint16(rawPacket[currOffset:]) - currOffset += 2 - extensionLength := int(binary.BigEndian.Uint16(rawPacket[currOffset:])) * 4 - currOffset += 2 + h.ExtensionProfile = binary.BigEndian.Uint16(buf[n:]) + n += 2 + extensionLength := int(binary.BigEndian.Uint16(buf[n:])) * 4 + n += 2 - if expected := currOffset + extensionLength; len(rawPacket) < expected { + if expected := n + extensionLength; len(buf) < expected { return fmt.Errorf("size %d < %d: %w", - len(rawPacket), expected, + len(buf), expected, errHeaderSizeInsufficientForExtension, ) } @@ -156,79 +156,79 @@ func (h *Header) Unmarshal(rawPacket []byte) error { //nolint:gocognit switch h.ExtensionProfile { // RFC 8285 RTP One Byte Header Extension case extensionProfileOneByte: - end := currOffset + extensionLength - for currOffset < end { - if rawPacket[currOffset] == 0x00 { // padding - currOffset++ + end := n + extensionLength + for n < end { + if buf[n] == 0x00 { // padding + n++ continue } - extid := rawPacket[currOffset] >> 4 - payloadLen := int(rawPacket[currOffset]&^0xF0 + 1) - currOffset++ + extid := buf[n] >> 4 + payloadLen := int(buf[n]&^0xF0 + 1) + n++ if extid == extensionIDReserved { break } - extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+payloadLen]} + extension := Extension{id: extid, payload: buf[n : n+payloadLen]} h.Extensions = append(h.Extensions, extension) - currOffset += payloadLen + n += payloadLen } // RFC 8285 RTP Two Byte Header Extension case extensionProfileTwoByte: - end := currOffset + extensionLength - for currOffset < end { - if rawPacket[currOffset] == 0x00 { // padding - currOffset++ + end := n + extensionLength + for n < end { + if buf[n] == 0x00 { // padding + n++ continue } - extid := rawPacket[currOffset] - currOffset++ + extid := buf[n] + n++ - payloadLen := int(rawPacket[currOffset]) - currOffset++ + payloadLen := int(buf[n]) + n++ - extension := Extension{id: extid, payload: rawPacket[currOffset : currOffset+payloadLen]} + extension := Extension{id: extid, payload: buf[n : n+payloadLen]} h.Extensions = append(h.Extensions, extension) - currOffset += payloadLen + n += payloadLen } default: // RFC3550 Extension - if len(rawPacket) < currOffset+extensionLength { - return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, len(rawPacket), currOffset+extensionLength) + if len(buf) < n+extensionLength { + return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, len(buf), n+extensionLength) } - extension := Extension{id: 0, payload: rawPacket[currOffset : currOffset+extensionLength]} + extension := Extension{id: 0, payload: buf[n : n+extensionLength]} h.Extensions = append(h.Extensions, extension) - currOffset += len(h.Extensions[0].payload) + n += len(h.Extensions[0].payload) } } - h.PayloadOffset = currOffset + h.PayloadOffset = n return nil } -// Unmarshal parses the passed byte slice and stores the result in the Packet this method is called upon -func (p *Packet) Unmarshal(rawPacket []byte) error { - if err := p.Header.Unmarshal(rawPacket); err != nil { +// Unmarshal parses the passed byte slice and stores the result in the Packet. +func (p *Packet) Unmarshal(buf []byte) error { + if err := p.Header.Unmarshal(buf); err != nil { return err } - end := len(rawPacket) + end := len(buf) if p.Header.Padding { - p.PaddingSize = rawPacket[end-1] + p.PaddingSize = buf[end-1] end -= int(p.PaddingSize) } if end < p.PayloadOffset { return errTooSmall } - p.Payload = rawPacket[p.PayloadOffset:end] - p.Raw = rawPacket + p.Payload = buf[p.PayloadOffset:end] + p.Raw = buf return nil } @@ -267,7 +267,8 @@ func (h Header) MarshalTo(buf []byte) (n int, err error) { return 0, io.ErrShortBuffer } - // The first byte contains the version, padding bit, extension bit, and csrc size + // 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 From e65559104628969141b713695242ff281e0918b7 Mon Sep 17 00:00:00 2001 From: ffmiyo Date: Wed, 3 Feb 2021 22:00:23 +0800 Subject: [PATCH 064/102] Implement packet clone method Clone performs deep copy on a packet to produce equivalent but independently mutable packet. Fixes #88 --- packet.go | 33 +++++++++++++++++++++++++++++++ packet_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/packet.go b/packet.go index f1f12a6..26a087e 100644 --- a/packet.go +++ b/packet.go @@ -507,3 +507,36 @@ func (p Packet) MarshalTo(buf []byte) (n int, err error) { 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 +} diff --git a/packet_test.go b/packet_test.go index 0ccd26c..40cb1a0 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1390,6 +1390,59 @@ func TestRoundtrip(t *testing.T) { } } +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, From b85163e9119485d36960f16e74f76746d25ce147 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 12 Jun 2023 23:21:51 +0200 Subject: [PATCH 065/102] Fix Packet.Raw population --- packet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packet.go b/packet.go index 26a087e..2796a09 100644 --- a/packet.go +++ b/packet.go @@ -482,7 +482,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) { +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 { From 5d59489081b941427ab4362c846175942728d328 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 12 Jun 2023 23:23:03 +0200 Subject: [PATCH 066/102] Bump Go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 412ae63..753a4d7 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/pion/rtp -go 1.13 +go 1.19 require github.com/pion/randutil v0.1.0 From 417b628b8a91ca76aa5b783363ab0a86b254049a Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Sun, 16 Jul 2023 14:44:09 -0400 Subject: [PATCH 067/102] Fix API breakage on Header.Unmarshal Return (int, error) and not just error --- packet.go | 22 +++++++++++++--------- packet_test.go | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packet.go b/packet.go index 2796a09..a47e5fc 100644 --- a/packet.go +++ b/packet.go @@ -82,9 +82,10 @@ func (p Packet) String() string { } // Unmarshal parses the passed byte slice and stores the result in the Header. -func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit +// 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 fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), headerLength) + return 0, fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficient, len(buf), headerLength) } /* @@ -112,9 +113,10 @@ func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit h.CSRC = h.CSRC[:nCSRC] } - n := csrcOffset + (nCSRC * csrcLength) + n = csrcOffset + (nCSRC * csrcLength) if len(buf) < n { - return fmt.Errorf("size %d < %d: %w", len(buf), n, errHeaderSizeInsufficient) + return n, fmt.Errorf("size %d < %d: %w", len(buf), n, + errHeaderSizeInsufficient) } h.Marker = (buf[1] >> markerShift & markerMask) > 0 @@ -135,7 +137,7 @@ func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit if h.Extension { if expected := n + 4; len(buf) < expected { - return fmt.Errorf("size %d < %d: %w", + return n, fmt.Errorf("size %d < %d: %w", len(buf), expected, errHeaderSizeInsufficientForExtension, ) @@ -147,7 +149,7 @@ func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit n += 2 if expected := n + extensionLength; len(buf) < expected { - return fmt.Errorf("size %d < %d: %w", + return n, fmt.Errorf("size %d < %d: %w", len(buf), expected, errHeaderSizeInsufficientForExtension, ) @@ -198,7 +200,8 @@ func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit default: // RFC3550 Extension if len(buf) < n+extensionLength { - return fmt.Errorf("%w: %d < %d", errHeaderSizeInsufficientForExtension, 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]} @@ -209,12 +212,13 @@ func (h *Header) Unmarshal(buf []byte) error { //nolint:gocognit h.PayloadOffset = n - return nil + return n, nil } // Unmarshal parses the passed byte slice and stores the result in the Packet. func (p *Packet) Unmarshal(buf []byte) error { - if err := p.Header.Unmarshal(buf); err != nil { + _, err := p.Header.Unmarshal(buf) + if err != nil { return err } diff --git a/packet_test.go b/packet_test.go index 40cb1a0..be13efb 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1345,7 +1345,7 @@ func TestUnmarshal_ErrorHandling(t *testing.T) { testCase := testCase t.Run(name, func(t *testing.T) { h := &Header{} - err := h.Unmarshal(testCase.input) + _, err := h.Unmarshal(testCase.input) if !errors.Is(err, testCase.err) { t.Errorf("Expected error: %v, got: %v", testCase.err, err) } From bbb0c03111efb5414ca2487d901fc3d94de5b5a0 Mon Sep 17 00:00:00 2001 From: Sean Date: Sun, 16 Jul 2023 18:45:36 +0000 Subject: [PATCH 068/102] Update AUTHORS.txt --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index 38685a9..e50023f 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -32,6 +32,7 @@ Rob Lofthouse Robin Raymond Sean DuBois Sean DuBois +Sean DuBois Sean DuBois Simone Gotti Steffen Vogel From a2b2381dce1aec1795f1337abddd0f7e86ec19cf Mon Sep 17 00:00:00 2001 From: Sean Date: Sun, 16 Jul 2023 18:46:03 +0000 Subject: [PATCH 069/102] Update AUTHORS.txt --- AUTHORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.txt b/AUTHORS.txt index e50023f..727aaaa 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -30,6 +30,7 @@ Michael Uti Raphael Derosso Pereira Rob Lofthouse Robin Raymond +Sean Sean DuBois Sean DuBois Sean DuBois From cde5bca7bfd535ff601d98fdee2c03d6e372196c Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 16 Jul 2023 23:10:52 +0200 Subject: [PATCH 070/102] Add non-breaking version of #160 --- packetizer.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packetizer.go b/packetizer.go index c412732..7ecebde 100644 --- a/packetizer.go +++ b/packetizer.go @@ -20,13 +20,16 @@ type Packetizer interface { } type packetizer struct { - MTU uint16 - PayloadType uint8 - SSRC uint32 - Payloader Payloader - Sequencer Sequencer - Timestamp uint32 - ClockRate uint32 + MTU uint16 + PayloadType uint8 + SSRC uint32 + Payloader Payloader + Sequencer Sequencer + Timestamp uint32 + + // Deprecated: will be removed in a future version. + ClockRate uint32 + extensionNumbers struct { // put extension numbers in here. If they're 0, the extension is disabled (0 is not a legal extension number) AbsSendTime int // http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time } From 259db6fbd3319fb62da55144b7cb2b80ad6e583c Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 16 Jul 2023 23:27:16 +0200 Subject: [PATCH 071/102] Readd non-breaking version of #119 --- packet.go | 23 ++++++++++------------- packet_test.go | 45 --------------------------------------------- packetizer_test.go | 3 --- 3 files changed, 10 insertions(+), 61 deletions(-) diff --git a/packet.go b/packet.go index a47e5fc..500c60f 100644 --- a/packet.go +++ b/packet.go @@ -16,13 +16,11 @@ type Extension struct { } // Header represents an RTP packet header -// NOTE: PayloadOffset is populated by Marshal/Unmarshal and should not be modified type Header struct { Version uint8 Padding bool Extension bool Marker bool - PayloadOffset int PayloadType uint8 SequenceNumber uint16 Timestamp uint32 @@ -30,15 +28,19 @@ type Header struct { CSRC []uint32 ExtensionProfile uint16 Extensions []Extension + + // Deprecated: will be removed in a future version. + PayloadOffset int } // Packet represents an RTP Packet -// NOTE: Raw is populated by Marshal/Unmarshal and should not be modified type Packet struct { Header - Raw []byte Payload []byte PaddingSize byte + + // Deprecated: will be removed in a future version. + Raw []byte } const ( @@ -210,14 +212,12 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit } } - h.PayloadOffset = n - return n, nil } // Unmarshal parses the passed byte slice and stores the result in the Packet. func (p *Packet) Unmarshal(buf []byte) error { - _, err := p.Header.Unmarshal(buf) + n, err := p.Header.Unmarshal(buf) if err != nil { return err } @@ -227,12 +227,11 @@ func (p *Packet) Unmarshal(buf []byte) error { p.PaddingSize = buf[end-1] end -= int(p.PaddingSize) } - if end < p.PayloadOffset { + if end < n { return errTooSmall } - p.Payload = buf[p.PayloadOffset:end] - p.Raw = buf + p.Payload = buf[n:end] return nil } @@ -343,8 +342,6 @@ func (h Header) MarshalTo(buf []byte) (n int, err error) { } } - h.PayloadOffset = n - return n, nil } @@ -499,7 +496,7 @@ func (p *Packet) MarshalTo(buf []byte) (n int, err error) { } m := copy(buf[n:], p.Payload) - p.Raw = buf[:n+m] + if p.Header.Padding { buf[n+m+int(p.PaddingSize-1)] = p.PaddingSize } diff --git a/packet_test.go b/packet_test.go index be13efb..bc8f97a 100644 --- a/packet_test.go +++ b/packet_test.go @@ -35,7 +35,6 @@ func TestBasic(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 20, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -43,7 +42,6 @@ func TestBasic(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[20:], - Raw: rawPkt, PaddingSize: 0, } @@ -62,20 +60,12 @@ func TestBasic(t *testing.T) { t.Errorf("wrong computed marshal size") } - if p.PayloadOffset != 20 { - t.Errorf("wrong payload offset: %d != %d", p.PayloadOffset, 20) - } - 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) } - - if p.PayloadOffset != 20 { - t.Errorf("wrong payload offset: %d != %d", p.PayloadOffset, 20) - } }) } @@ -101,10 +91,8 @@ func TestBasic(t *testing.T) { Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, - PayloadOffset: 20, }, Payload: rawPkt[20:21], - Raw: rawPkt, PaddingSize: 4, } if err := p.Unmarshal(rawPkt); err != nil { @@ -135,10 +123,8 @@ func TestBasic(t *testing.T) { Timestamp: 3653407706, SSRC: 476325762, CSRC: []uint32{}, - PayloadOffset: 20, }, Payload: []byte{}, - Raw: rawPkt, PaddingSize: 5, } if err := p.Unmarshal(rawPkt); err != nil { @@ -345,7 +331,6 @@ func TestRFC8285OneByteExtension(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 18, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -353,7 +338,6 @@ func TestRFC8285OneByteExtension(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[20:], - Raw: rawPkt, } dstData, _ := p.Marshal() @@ -409,7 +393,6 @@ func TestRFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -417,7 +400,6 @@ func TestRFC8285OneByteTwoExtensionOfTwoBytes(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[20:], - Raw: rawPkt, } dstData, _ := p.Marshal() @@ -534,7 +516,6 @@ func TestRFC8285OneByteMultipleExtensions(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -542,7 +523,6 @@ func TestRFC8285OneByteMultipleExtensions(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[28:], - Raw: rawPkt, } dstData, _ := p.Marshal() @@ -578,7 +558,6 @@ func TestRFC8285TwoByteExtension(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 42, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -586,7 +565,6 @@ func TestRFC8285TwoByteExtension(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[44:], - Raw: rawPkt, } dstData, _ := p.Marshal() @@ -681,7 +659,6 @@ func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 40, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -689,7 +666,6 @@ func TestRFC8285TwoByteMultipleExtensionsWithLargeExtension(t *testing.T) { CSRC: []uint32{}, }, Payload: rawPkt[40:], - Raw: rawPkt, } dstData, _ := p.Marshal() @@ -708,7 +684,6 @@ func TestRFC8285GetExtensionReturnsNilWhenExtensionsDisabled(t *testing.T) { Marker: true, Extension: false, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -740,7 +715,6 @@ func TestRFC8285DelExtension(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -790,7 +764,6 @@ func TestRFC8285GetExtensionIDs(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -826,7 +799,6 @@ func TestRFC8285GetExtensionIDsReturnsErrorWhenExtensionsDisabled(t *testing.T) Marker: true, Extension: false, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -852,7 +824,6 @@ func TestRFC8285DelExtensionReturnsErrorWhenExtensionsDisabled(t *testing.T) { Marker: true, Extension: false, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -878,7 +849,6 @@ func TestRFC8285OneByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) Marker: true, Extension: false, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -921,7 +891,6 @@ func TestRFC8285OneByteSetExtensionShouldSetCorrectExtensionProfileFor16ByteExte Marker: true, Extension: false, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -963,7 +932,6 @@ func TestRFC8285OneByteSetExtensionShouldUpdateExistingExension(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1004,7 +972,6 @@ func TestRFC8285OneByteSetExtensionShouldErrorWhenInvalidIDProvided(t *testing.T }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1062,7 +1029,6 @@ func TestRFC8285OneByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1090,7 +1056,6 @@ func TestRFC8285TwoByteSetExtensionShouldEnableExensionsWhenAdding(t *testing.T) Marker: true, Extension: false, Version: 2, - PayloadOffset: 31, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1142,7 +1107,6 @@ func TestRFC8285TwoByteSetExtensionShouldUpdateExistingExension(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1186,7 +1150,6 @@ func TestRFC8285TwoByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1244,7 +1207,6 @@ func TestRFC3550SetExtensionShouldErrorWhenNonZero(t *testing.T) { }}, }, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1276,7 +1238,6 @@ func TestRFC3550SetExtensionShouldRaiseErrorWhenSettingNonzeroID(t *testing.T) { Extension: true, ExtensionProfile: 0x1111, Version: 2, - PayloadOffset: 26, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, @@ -1364,9 +1325,6 @@ func TestRoundtrip(t *testing.T) { if err := p.Unmarshal(rawPkt); err != nil { t.Fatal(err) } - if !bytes.Equal(rawPkt, p.Raw) { - t.Errorf("p.Raw must be same as rawPkt.\n p.Raw: %+v,\nrawPkt: %+v", p.Raw, 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, @@ -1380,9 +1338,6 @@ func TestRoundtrip(t *testing.T) { if !bytes.Equal(rawPkt, buf) { t.Errorf("buf must be same as rawPkt.\n buf: %+v,\nrawPkt: %+v", buf, rawPkt) } - if !bytes.Equal(rawPkt, p.Raw) { - t.Errorf("p.Raw must be same as rawPkt.\n p.Raw: %+v,\nrawPkt: %+v", p.Raw, 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, diff --git a/packetizer_test.go b/packetizer_test.go index a4ac3b8..78f8220 100644 --- a/packetizer_test.go +++ b/packetizer_test.go @@ -51,7 +51,6 @@ func TestPacketizer_AbsSendTime(t *testing.T) { Padding: false, Extension: true, Marker: true, - PayloadOffset: 0, // not set by Packetize() at now PayloadType: 98, SequenceNumber: 1234, Timestamp: 45678, @@ -144,9 +143,7 @@ func TestPacketizer_Roundtrip(t *testing.T) { t.Errorf("Packet versions don't match, expected %v but got %v", expectedPkt.Payload, pkt.Payload) } - pkt.PayloadOffset = 0 pkt.PaddingSize = 0 - pkt.Raw = nil if !reflect.DeepEqual(expectedPkt, pkt) { t.Errorf("Packets don't match, expected %v but got %v", expectedPkt, pkt) From 2ba7dc3b92642628381ee19f8e79858adb556eb7 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Thu, 20 Jul 2023 15:59:49 -0400 Subject: [PATCH 072/102] Update AV1 Payloader to work with libwebrtc Don't use leb128 with long fragments. Just set the W bit. Cache the SequenceHeader and deliver it with the subsequent frame. --- codecs/av1_packet.go | 71 +++++++++++++++++++++++----------- codecs/av1_packet_test.go | 81 ++++++++++++++++++++++++++++++--------- 2 files changed, 112 insertions(+), 40 deletions(-) diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 01aa89b..3f4761d 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -20,52 +20,79 @@ const ( nMask = byte(0b00001000) nBitshift = 3 + obuFrameTypeMask = byte(0b01111000) + obuFrameTypeBitshift = 3 + + obuFameTypeSequenceHeader = 1 + av1PayloaderHeadersize = 1 + + leb128Size = 1 ) // AV1Payloader payloads AV1 packets -type AV1Payloader struct{} +type AV1Payloader struct { + sequenceHeader []byte +} // Payload fragments a AV1 packet across one or more byte arrays // See AV1Packet for description of AV1 Payload Header func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) { - maxFragmentSize := int(mtu) - av1PayloaderHeadersize - 2 - payloadDataRemaining := len(payload) payloadDataIndex := 0 + payloadDataRemaining := len(payload) - // Make sure the fragment/payload size is correct - if min(maxFragmentSize, payloadDataRemaining) <= 0 { + // Payload Data and MTU is non-zero + if mtu <= 0 || payloadDataRemaining <= 0 { return payloads } + // Cache Sequence Header and packetize with next payload + frameType := (payload[0] & obuFrameTypeMask) >> obuFrameTypeBitshift + if frameType == obuFameTypeSequenceHeader { + p.sequenceHeader = payload + return + } + for payloadDataRemaining > 0 { - currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) - leb128Size := 1 - if currentFragmentSize >= 127 { - leb128Size = 2 + obuCount := byte(1) + metadataSize := av1PayloaderHeadersize + if len(p.sequenceHeader) != 0 { + obuCount++ + metadataSize += leb128Size + len(p.sequenceHeader) } - 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) - } + out := make([]byte, min(int(mtu), payloadDataRemaining+metadataSize)) + outOffset := av1PayloaderHeadersize + out[0] = obuCount << wBitshift - copy(out[av1PayloaderHeadersize+leb128Size:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) - payloads = append(payloads, out) + if obuCount == 2 { + // This Payload contain the start of a Coded Video Sequence + out[0] ^= nMask + + out[1] = byte(obu.EncodeLEB128(uint(len(p.sequenceHeader)))) + copy(out[2:], p.sequenceHeader) - payloadDataRemaining -= currentFragmentSize - payloadDataIndex += currentFragmentSize + outOffset += leb128Size + len(p.sequenceHeader) + + p.sequenceHeader = nil + } - if len(payloads) > 1 { + outBufferRemaining := len(out) - outOffset + copy(out[outOffset:], payload[payloadDataIndex:payloadDataIndex+outBufferRemaining]) + payloadDataRemaining -= outBufferRemaining + payloadDataIndex += outBufferRemaining + + // Does this Fragment contain an OBU that started in a previous payload + if len(payloads) > 0 { out[0] ^= zMask } + + // This OBU will be continued in next Payload if payloadDataRemaining != 0 { out[0] ^= yMask } + + payloads = append(payloads, out) } return payloads diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 746353f..711996c 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -4,6 +4,7 @@ package codecs import ( + "bytes" "errors" "reflect" "testing" @@ -12,28 +13,72 @@ import ( ) func TestAV1_Marshal(t *testing.T) { - const mtu = 5 + p := &AV1Payloader{} - 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 + t.Run("Unfragmented OBU", func(t *testing.T) { + OBU := []byte{0x00, 0x01, 0x2, 0x3, 0x4, 0x5} + payloads := p.Payload(100, OBU) - 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) + if len(payloads) != 1 || len(payloads[0]) != 7 { + t.Fatal("Expected one unfragmented Payload") } - } - p := &AV1Payloader{} - zeroMtuPayload := p.Payload(0, []byte{0x0A, 0x0B, 0x0C}) - if zeroMtuPayload != nil { - t.Fatal("Unexpected output from zero MTU AV1 Payloader") - } + if payloads[0][0] != 0x10 { + t.Fatal("Only W bit should be set") + } + + if !bytes.Equal(OBU, payloads[0][1:]) { + t.Fatal("OBU modified during packetization") + } + }) + + t.Run("Fragmented OBU", func(t *testing.T) { + OBU := []byte{0x00, 0x01, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} + payloads := p.Payload(4, OBU) + + if len(payloads) != 3 || len(payloads[0]) != 4 || len(payloads[1]) != 4 || len(payloads[2]) != 4 { + t.Fatal("Expected three fragmented Payload") + } + + if payloads[0][0] != 0x10|yMask { + t.Fatal("W and Y bit should be set") + } + + if payloads[1][0] != 0x10|yMask|zMask { + t.Fatal("W, Y and Z bit should be set") + } + + if payloads[2][0] != 0x10|zMask { + t.Fatal("W and Z bit should be set") + } + + if !bytes.Equal(OBU[0:3], payloads[0][1:]) || !bytes.Equal(OBU[3:6], payloads[1][1:]) || !bytes.Equal(OBU[6:9], payloads[2][1:]) { + t.Fatal("OBU modified during packetization") + } + }) + + t.Run("Sequence Header Caching", func(t *testing.T) { + sequenceHeaderFrame := []byte{0xb, 0xA, 0xB, 0xC} + normalFrame := []byte{0x0, 0x1, 0x2, 0x3} + + payloads := p.Payload(100, sequenceHeaderFrame) + if len(payloads) != 0 { + t.Fatal("Sequence Header was not properly cached") + } + + payloads = p.Payload(100, normalFrame) + if len(payloads) != 1 { + t.Fatal("Expected one payload") + } + + if payloads[0][0] != 0x20|nMask { + t.Fatal("W and N bit should be set") + } + + if !bytes.Equal(sequenceHeaderFrame, payloads[0][2:6]) || !bytes.Equal(normalFrame, payloads[0][6:10]) { + t.Fatal("OBU modified during packetization") + } + }) } func TestAV1_Unmarshal_Error(t *testing.T) { From ce70bc1f60606144797c16302c11d4e5ebd44c08 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 25 Jul 2023 09:50:25 +0200 Subject: [PATCH 073/102] Replace symlinked license file with copy to fix godoc --- LICENSE | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) mode change 120000 => 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 120000 index 31ff787..0000000 --- a/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSES/MIT.txt \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +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. From 7c80fd2e6d1bc061b8373fcb1e3d17a21b093de8 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Fri, 28 Jul 2023 21:18:06 +0200 Subject: [PATCH 074/102] Fix race condition in Packet.MarshalTo() This was already fixed by #168 but got lost in #227. in SFUs, in order to distribute a packet to all clients, MarshalTo() is called in parallel by multiple routines, causing a race condition because the padding flag is dynamically set inside MarshalTo(). This is particular annoying when running automated tests. This PR fixes the issue by removing this write operation as discussed in #168. --- packet.go | 1 - packet_test.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packet.go b/packet.go index 500c60f..7aebb14 100644 --- a/packet.go +++ b/packet.go @@ -484,7 +484,6 @@ 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 n, err = p.Header.MarshalTo(buf) if err != nil { return 0, err diff --git a/packet_test.go b/packet_test.go index bc8f97a..3044c0d 100644 --- a/packet_test.go +++ b/packet_test.go @@ -254,6 +254,7 @@ func TestBasic(t *testing.T) { }}, }, Version: 2, + Padding: true, PayloadType: 96, SequenceNumber: 27023, Timestamp: 3653407706, From 3fbe548e1455a13e204cb6ed568534d011e4619f Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Sat, 5 Aug 2023 17:47:49 +0000 Subject: [PATCH 075/102] Update CI configs to v0.10.11 Update lint scripts and CI configs. --- .reuse/dep5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.reuse/dep5 b/.reuse/dep5 index c8b3dfa..717f0c1 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ -Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum .eslintrc.json package.json examples/examples.json +Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples/examples.json Copyright: 2023 The Pion community License: MIT From 67d2b3e2b870b0956df55288b3035e49bbb9e13b Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Mon, 14 Aug 2023 21:20:46 +0200 Subject: [PATCH 076/102] Move pkg into codecs/av1 Fixes #221 As previously discussed, the pkg directory is not used in Pion projects. --- codecs/av1/frame/av1.go | 47 ++++++++++++++++++ {pkg => codecs/av1}/frame/av1_test.go | 0 codecs/av1/obu/leb128.go | 69 ++++++++++++++++++++++++++ {pkg => codecs/av1}/obu/leb128_test.go | 0 codecs/av1_packet.go | 2 +- codecs/av1_packet_test.go | 2 +- pkg/frame/av1.go | 44 +++------------- pkg/obu/leb128.go | 63 +++++------------------ 8 files changed, 137 insertions(+), 90 deletions(-) create mode 100644 codecs/av1/frame/av1.go rename {pkg => codecs/av1}/frame/av1_test.go (100%) create mode 100644 codecs/av1/obu/leb128.go rename {pkg => codecs/av1}/obu/leb128_test.go (100%) diff --git a/codecs/av1/frame/av1.go b/codecs/av1/frame/av1.go new file mode 100644 index 0000000..1e001a3 --- /dev/null +++ b/codecs/av1/frame/av1.go @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +// 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 +} diff --git a/pkg/frame/av1_test.go b/codecs/av1/frame/av1_test.go similarity index 100% rename from pkg/frame/av1_test.go rename to codecs/av1/frame/av1_test.go diff --git a/codecs/av1/obu/leb128.go b/codecs/av1/obu/leb128.go new file mode 100644 index 0000000..38ce090 --- /dev/null +++ b/codecs/av1/obu/leb128.go @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +// Package obu implements tools for working with the Open Bitstream Unit. +package obu + +import "errors" + +const ( + sevenLsbBitmask = uint(0b01111111) + msbBitmask = uint(0b10000000) +) + +// ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read +var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished") + +// EncodeLEB128 encodes a uint as LEB128 +func EncodeLEB128(in uint) (out uint) { + for { + // Copy seven bits from in and discard + // what we have copied from in + out |= (in & sevenLsbBitmask) + in >>= 7 + + // If we have more bits to encode set MSB + // otherwise we are done + if in != 0 { + out |= msbBitmask + out <<= 8 + } else { + return out + } + } +} + +func decodeLEB128(in uint) (out uint) { + for { + // Take 7 LSB from in + out |= (in & sevenLsbBitmask) + + // Discard the MSB + in >>= 8 + if in == 0 { + return out + } + + out <<= 7 + } +} + +// ReadLeb128 scans an buffer and decodes a Leb128 value. +// If the end of the buffer is reached and all MSB are set +// an error is returned +func ReadLeb128(in []byte) (uint, uint, error) { + var encodedLength uint + + for i := range in { + encodedLength |= uint(in[i]) + + if in[i]&byte(msbBitmask) == 0 { + return decodeLEB128(encodedLength), uint(i + 1), nil + } + + // Make more room for next read + encodedLength <<= 8 + } + + return 0, 0, ErrFailedToReadLEB128 +} diff --git a/pkg/obu/leb128_test.go b/codecs/av1/obu/leb128_test.go similarity index 100% rename from pkg/obu/leb128_test.go rename to codecs/av1/obu/leb128_test.go diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 3f4761d..3a78b90 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -4,7 +4,7 @@ package codecs import ( - "github.com/pion/rtp/pkg/obu" + "github.com/pion/rtp/codecs/av1/obu" ) const ( diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 711996c..2a65c4f 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -9,7 +9,7 @@ import ( "reflect" "testing" - "github.com/pion/rtp/pkg/obu" + "github.com/pion/rtp/codecs/av1/obu" ) func TestAV1_Marshal(t *testing.T) { diff --git a/pkg/frame/av1.go b/pkg/frame/av1.go index 0dadde6..73edea6 100644 --- a/pkg/frame/av1.go +++ b/pkg/frame/av1.go @@ -1,47 +1,17 @@ // SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT -// Package frame provides code to construct complete media frames from packetized media +// Package frame is deprecated. package frame -import "github.com/pion/rtp/codecs" +import ( + "github.com/pion/rtp/codecs/av1/frame" +) // 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 -} +// +// Deprecated: moved into codecs/av1/frame. +type AV1 = frame.AV1 diff --git a/pkg/obu/leb128.go b/pkg/obu/leb128.go index f0734f0..e1d9c8c 100644 --- a/pkg/obu/leb128.go +++ b/pkg/obu/leb128.go @@ -1,69 +1,30 @@ // SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT -// Package obu implements tools for working with the "Open Bitstream Unit" +// Package obu is deprecated. package obu -import "errors" - -const ( - sevenLsbBitmask = uint(0b01111111) - msbBitmask = uint(0b10000000) +import ( + "github.com/pion/rtp/codecs/av1/obu" ) // ErrFailedToReadLEB128 indicates that a buffer ended before a LEB128 value could be successfully read -var ErrFailedToReadLEB128 = errors.New("payload ended before LEB128 was finished") +// +// Deprecated: moved into codecs/av1/obu. +var ErrFailedToReadLEB128 = obu.ErrFailedToReadLEB128 // EncodeLEB128 encodes a uint as LEB128 +// +// Deprecated: moved into codecs/av1/obu. 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 - } + return obu.EncodeLEB128(in) } // 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 +// +// Deprecated: moved into codecs/av1/obu. 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 + return obu.ReadLeb128(in) } From bfe92b95ae6f7195e58eb777dc1d860e511216fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A8=BE=E6=98=8E=E5=8D=8E?= <565209960@qq.com> Date: Wed, 20 Sep 2023 21:30:29 +0800 Subject: [PATCH 077/102] Optimize the performance of H264 packaging NALU parsing was generating slices as it went. This updates emitNalus to instead use bytes.Index which is more performant --- codecs/h264_packet.go | 55 +++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 17ca1bb..e53cea0 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -4,6 +4,7 @@ package codecs import ( + "bytes" "encoding/binary" "fmt" ) @@ -34,40 +35,32 @@ const ( outputStapAHeader = 0x78 ) -func annexbNALUStartCode() []byte { return []byte{0x00, 0x00, 0x00, 0x01} } +// nolint:gochecknoglobals +var ( + naluStartCode = []byte{0x00, 0x00, 0x01} + annexbNALUStartCode = []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 + start := 0 + length := len(nals) + + for start < length { + end := bytes.Index(nals[start:], annexbNALUStartCode) + offset := 4 + if end == -1 { + end = bytes.Index(nals[start:], naluStartCode) + offset = 3 } - 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:]) - } + if end == -1 { + emit(nals[start:]) + break } + + emit(nals[start : start+end]) + + // next NAL start position + start += end + offset } } @@ -203,7 +196,7 @@ func (p *H264Packet) doPackaging(nalu []byte) []byte { return append(naluLength, nalu...) } - return append(annexbNALUStartCode(), nalu...) + return append(annexbNALUStartCode, nalu...) } // IsDetectedFinalPacketInSequence returns true of the packet passed in has the From 930bd859f2cae6196e1def460c6474089f5b738b Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Wed, 4 Oct 2023 12:18:12 -0400 Subject: [PATCH 078/102] Remove redundant state in H264 Packetization No code changes. Just removes an additional variable --- codecs/h264_packet.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index e53cea0..583e189 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -133,18 +133,17 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { // 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 + naluIndex := 1 + naluLength := len(nalu) - naluIndex + naluRemaining := naluLength - if min(maxFragmentSize, naluDataRemaining) <= 0 { + if min(maxFragmentSize, naluRemaining) <= 0 { return } - for naluDataRemaining > 0 { - currentFragmentSize := min(maxFragmentSize, naluDataRemaining) + for naluRemaining > 0 { + currentFragmentSize := min(maxFragmentSize, naluRemaining) out := make([]byte, fuaHeaderSize+currentFragmentSize) // +---------------+ @@ -162,19 +161,19 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { // +---------------+ out[1] = naluType - if naluDataRemaining == naluDataLength { + if naluRemaining == naluLength { // Set start bit out[1] |= 1 << 7 - } else if naluDataRemaining-currentFragmentSize == 0 { + } else if naluRemaining-currentFragmentSize == 0 { // Set end bit out[1] |= 1 << 6 } - copy(out[fuaHeaderSize:], naluData[naluDataIndex:naluDataIndex+currentFragmentSize]) + copy(out[fuaHeaderSize:], nalu[naluIndex:naluIndex+currentFragmentSize]) payloads = append(payloads, out) - naluDataRemaining -= currentFragmentSize - naluDataIndex += currentFragmentSize + naluRemaining -= currentFragmentSize + naluIndex += currentFragmentSize } }) From 7dc2af56736b663e76f1400ba403532ba590bceb Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Thu, 9 Nov 2023 10:53:12 -0500 Subject: [PATCH 079/102] Combine OneByte and TwoByte extension parsing Reduces duplicated logic and increase coverage. Missing tests for bounds check on Padding and Payload Sizes Co-Authored-By: Juho Nurminen --- AUTHORS.txt | 3 +++ packet.go | 69 ++++++++++++++++++++++---------------------------- packet_test.go | 28 ++++++++++++++++++++ 3 files changed, 61 insertions(+), 39 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 727aaaa..f8dbb32 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -18,6 +18,7 @@ Guilherme Haiyang Wang Hugo Arregui John Bradley +Juho Nurminen Juliusz Chroboczek kawaway Kazuyuki Honda @@ -31,6 +32,7 @@ Raphael Derosso Pereira Rob Lofthouse Robin Raymond Sean +Sean Sean DuBois Sean DuBois Sean DuBois @@ -40,6 +42,7 @@ Steffen Vogel Tarrence van As wangzixiang Woodrow Douglass +訾明华 <565209960@qq.com> # List of contributors not appearing in Git history diff --git a/packet.go b/packet.go index 7aebb14..af88af3 100644 --- a/packet.go +++ b/packet.go @@ -149,64 +149,55 @@ func (h *Header) Unmarshal(buf []byte) (n int, err error) { //nolint:gocognit n += 2 extensionLength := int(binary.BigEndian.Uint16(buf[n:])) * 4 n += 2 + extensionEnd := n + extensionLength - if expected := n + extensionLength; len(buf) < expected { - return n, fmt.Errorf("size %d < %d: %w", - len(buf), expected, - errHeaderSizeInsufficientForExtension, - ) + if len(buf) < extensionEnd { + return n, fmt.Errorf("size %d < %d: %w", len(buf), extensionEnd, errHeaderSizeInsufficientForExtension) } - switch h.ExtensionProfile { - // RFC 8285 RTP One Byte Header Extension - case extensionProfileOneByte: - end := n + extensionLength - for n < end { + if h.ExtensionProfile == extensionProfileOneByte || h.ExtensionProfile == extensionProfileTwoByte { + var ( + extid uint8 + payloadLen int + ) + + for n < extensionEnd { if buf[n] == 0x00 { // padding n++ continue } - extid := buf[n] >> 4 - payloadLen := int(buf[n]&^0xF0 + 1) - n++ + if h.ExtensionProfile == extensionProfileOneByte { + extid = buf[n] >> 4 + payloadLen = int(buf[n]&^0xF0 + 1) + n++ - if extid == extensionIDReserved { - break - } + if extid == extensionIDReserved { + break + } + } else { + extid = buf[n] + n++ - extension := Extension{id: extid, payload: buf[n : n+payloadLen]} - h.Extensions = append(h.Extensions, extension) - n += payloadLen - } + if len(buf) <= n { + return n, fmt.Errorf("size %d < %d: %w", len(buf), n, errHeaderSizeInsufficientForExtension) + } - // RFC 8285 RTP Two Byte Header Extension - case extensionProfileTwoByte: - end := n + extensionLength - for n < end { - if buf[n] == 0x00 { // padding + payloadLen = int(buf[n]) n++ - continue } - extid := buf[n] - n++ - - payloadLen := int(buf[n]) - n++ + if extensionPayloadEnd := n + payloadLen; len(buf) <= extensionPayloadEnd { + return n, fmt.Errorf("size %d < %d: %w", len(buf), extensionPayloadEnd, errHeaderSizeInsufficientForExtension) + } extension := Extension{id: extid, payload: buf[n : n+payloadLen]} h.Extensions = append(h.Extensions, extension) n += payloadLen } - - 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]} + } else { + // RFC3550 Extension + extension := Extension{id: 0, payload: buf[n:extensionEnd]} h.Extensions = append(h.Extensions, extension) n += len(h.Extensions[0].payload) } diff --git a/packet_test.go b/packet_test.go index 3044c0d..98dfb40 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1192,6 +1192,34 @@ func TestRFC8285TwoByteSetExtensionShouldErrorWhenPayloadTooLarge(t *testing.T) } } +func TestRFC8285Padding(t *testing.T) { + header := &Header{} + + for _, payload := range [][]byte{ + { + 0b00010000, // header.Extension = true + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // SequenceNumber, Timestamp, SSRC + 0xBE, 0xDE, // header.ExtensionProfile = extensionProfileOneByte + 0, 1, // extensionLength + 0, 0, 0, // padding + 1, // extid + }, + { + 0b00010000, // header.Extension = true + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // SequenceNumber, Timestamp, SSRC + 0x10, 0x00, // header.ExtensionProfile = extensionProfileOneByte + 0, 1, // extensionLength + 0, 0, 0, // padding + 1, // extid + }, + } { + _, err := header.Unmarshal(payload) + if !errors.Is(err, errHeaderSizeInsufficientForExtension) { + t.Fatal("Expected errHeaderSizeInsufficientForExtension") + } + } +} + func TestRFC3550SetExtensionShouldErrorWhenNonZero(t *testing.T) { payload := []byte{ // Payload From 5fb93805d93bb5e3589469b2780eaaf38c5ed53d Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Tue, 2 Jan 2024 11:43:10 -0500 Subject: [PATCH 080/102] Remove 'Generate Authors' workflow pion/.goassets#185 --- .github/workflows/generate-authors.yml | 23 ------------ AUTHORS.txt | 48 -------------------------- 2 files changed, 71 deletions(-) delete mode 100644 .github/workflows/generate-authors.yml delete mode 100644 AUTHORS.txt diff --git a/.github/workflows/generate-authors.yml b/.github/workflows/generate-authors.yml deleted file mode 100644 index ec7446c..0000000 --- a/.github/workflows/generate-authors.yml +++ /dev/null @@ -1,23 +0,0 @@ -# -# 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. -# -# SPDX-FileCopyrightText: 2023 The Pion community -# SPDX-License-Identifier: MIT - -name: Generate Authors - -on: - pull_request: - -jobs: - generate: - uses: pion/.goassets/.github/workflows/generate-authors.reusable.yml@master - secrets: - token: ${{ secrets.PIONBOT_PRIVATE_KEY }} diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index f8dbb32..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,48 +0,0 @@ -# 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 https://github.com/pion/.goassets/blob/master/scripts/generate-authors.sh for the scripting -Aaron Boushley -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 -Juho Nurminen -Juliusz Chroboczek -kawaway -Kazuyuki Honda -Kevin Wang -Luke Curley -lxb -Michael MacDonald -Michael MacDonald -Michael Uti -Raphael Derosso Pereira -Rob Lofthouse -Robin Raymond -Sean -Sean -Sean DuBois -Sean DuBois -Sean DuBois -Sean DuBois -Simone Gotti -Steffen Vogel -Tarrence van As -wangzixiang -Woodrow Douglass -訾明华 <565209960@qq.com> - -# List of contributors not appearing in Git history - From 83ef1446c4934e78074b6d0248baa4751f46d07f Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 2 Jan 2024 19:16:03 +0000 Subject: [PATCH 081/102] Update CI configs to v0.11.0 Update lint scripts and CI configs. --- .golangci.yml | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 4e3eddf..6dd80c8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,7 +29,6 @@ linters: - bodyclose # checks whether HTTP response body is closed successfully - contextcheck # check the function whether use a non-inherited context - decorder # check declaration order and count of types, constants, variables and functions - - 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 @@ -63,7 +62,6 @@ linters: - 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 @@ -81,6 +79,7 @@ linters: - wastedassign # wastedassign finds wasted assignment statements - whitespace # Tool for detection of leading and trailing whitespace disable: + - depguard # Go linter that checks if package imports are in a list of acceptable packages - containedctx # containedctx is a linter that detects struct contained context.Context field - cyclop # checks function and package cyclomatic complexity - exhaustivestruct # Checks if all struct's fields are initialized @@ -94,6 +93,7 @@ linters: - maintidx # maintidx measures the maintainability index of each function. - makezero # Finds slice declarations with non-zero initial length - maligned # Tool to detect Go structs that would take less memory if their fields were sorted + - nakedret # Finds naked returns in functions greater than a specified function length - nestif # Reports deeply nested if statements - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity - nolintlint # Reports ill-formed or insufficient nolint directives @@ -111,22 +111,11 @@ linters: issues: exclude-use-default: false exclude-rules: - # Allow complex tests, better to be self contained - - path: _test\.go + # Allow complex tests and examples, better to be self contained + - path: (examples|main\.go|_test\.go) linters: - - gocognit - forbidigo - - # Allow complex main function in examples - - path: examples - text: "of func `main` is high" - linters: - gocognit - - # Allow forbidden identifiers in examples - - path: examples - linters: - - forbidigo # Allow forbidden identifiers in CLI commands - path: cmd From 314bd8ed85d7c7611dc67c5279f2b0d522ce3e88 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Fri, 5 Jan 2024 00:04:59 +0000 Subject: [PATCH 082/102] Update CI configs to v0.11.3 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 4 ++-- .github/workflows/tidy-check.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 31aada4..c8294ef 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,7 +23,7 @@ jobs: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: - go: ['1.20', '1.19'] # auto-update/supported-go-version-list + go: ['1.21', '1.20'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -32,7 +32,7 @@ jobs: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: - go: ['1.20', '1.19'] # auto-update/supported-go-version-list + go: ['1.21', '1.20'] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 4d346d4..33d6b50 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -22,4 +22,4 @@ jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: - go-version: '1.20' # auto-update/latest-go-version + go-version: '1.21' # auto-update/latest-go-version From 78da5a2169f8f40819ba7847d62c3e71abb75549 Mon Sep 17 00:00:00 2001 From: Yutaka Takeda Date: Tue, 26 Dec 2023 14:12:32 -0800 Subject: [PATCH 083/102] Add VLA extention header parser Added VLA parser, builder and unit tests. --- codecs/av1/obu/leb128.go | 16 + codecs/av1/obu/leb128_test.go | 33 +++ vlaextension.go | 360 +++++++++++++++++++++++ vlaextension_test.go | 532 ++++++++++++++++++++++++++++++++++ 4 files changed, 941 insertions(+) create mode 100644 vlaextension.go create mode 100644 vlaextension_test.go diff --git a/codecs/av1/obu/leb128.go b/codecs/av1/obu/leb128.go index 38ce090..f5fcbf6 100644 --- a/codecs/av1/obu/leb128.go +++ b/codecs/av1/obu/leb128.go @@ -67,3 +67,19 @@ func ReadLeb128(in []byte) (uint, uint, error) { return 0, 0, ErrFailedToReadLEB128 } + +// WriteToLeb128 writes a uint to a LEB128 encoded byte slice. +func WriteToLeb128(in uint) []byte { + b := make([]byte, 10) + + for i := 0; i < len(b); i++ { + b[i] = byte(in & 0x7f) + in >>= 7 + if in == 0 { + return b[:i+1] + } + b[i] |= 0x80 + } + + return b // unreachable +} diff --git a/codecs/av1/obu/leb128_test.go b/codecs/av1/obu/leb128_test.go index f92fff4..2b2336a 100644 --- a/codecs/av1/obu/leb128_test.go +++ b/codecs/av1/obu/leb128_test.go @@ -4,7 +4,10 @@ package obu import ( + "encoding/hex" "errors" + "fmt" + "math" "testing" ) @@ -40,3 +43,33 @@ func TestReadLeb128(t *testing.T) { t.Fatal("ReadLeb128 on a buffer with all MSB set should fail") } } + +func TestWriteToLeb128(t *testing.T) { + type testVector struct { + value uint + leb128 string + } + testVectors := []testVector{ + {150, "9601"}, + {240, "f001"}, + {400, "9003"}, + {720, "d005"}, + {1200, "b009"}, + {999999, "bf843d"}, + {0, "00"}, + {math.MaxUint32, "ffffffff0f"}, + } + + runTest := func(t *testing.T, v testVector) { + b := WriteToLeb128(v.value) + if v.leb128 != hex.EncodeToString(b) { + t.Errorf("Expected %s, got %s", v.leb128, hex.EncodeToString(b)) + } + } + + for _, v := range testVectors { + t.Run(fmt.Sprintf("encode %d", v.value), func(t *testing.T) { + runTest(t, v) + }) + } +} diff --git a/vlaextension.go b/vlaextension.go new file mode 100644 index 0000000..e10820a --- /dev/null +++ b/vlaextension.go @@ -0,0 +1,360 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package rtp + +import ( + "encoding/binary" + "errors" + "fmt" + "strings" + + "github.com/pion/rtp/codecs/av1/obu" +) + +var ( + ErrVLATooShort = errors.New("VLA payload too short") // ErrVLATooShort is returned when payload is too short + ErrVLAInvalidStreamCount = errors.New("invalid RTP stream count in VLA") // ErrVLAInvalidStreamCount is returned when RTP stream count is invalid + ErrVLAInvalidStreamID = errors.New("invalid RTP stream ID in VLA") // ErrVLAInvalidStreamID is returned when RTP stream ID is invalid + ErrVLAInvalidSpatialID = errors.New("invalid spatial ID in VLA") // ErrVLAInvalidSpatialID is returned when spatial ID is invalid + ErrVLADuplicateSpatialID = errors.New("duplicate spatial ID in VLA") // ErrVLADuplicateSpatialID is returned when spatial ID is invalid + ErrVLAInvalidTemporalLayer = errors.New("invalid temporal layer in VLA") // ErrVLAInvalidTemporalLayer is returned when temporal layer is invalid +) + +// SpatialLayer is a spatial layer in VLA. +type SpatialLayer struct { + RTPStreamID int + SpatialID int + TargetBitrates []int // target bitrates per temporal layer + + // Following members are valid only when HasResolutionAndFramerate is true + Width int + Height int + Framerate int +} + +// VLA is a Video Layer Allocation (VLA) extension. +// See https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/video-layers-allocation00 +type VLA struct { + RTPStreamID int // 0-origin RTP stream ID (RID) this allocation is sent on (0..3) + RTPStreamCount int // Number of RTP streams (1..4) + ActiveSpatialLayer []SpatialLayer + HasResolutionAndFramerate bool +} + +type vlaMarshalingContext struct { + slMBs [4]uint8 + sls [4][4]*SpatialLayer + commonSLBM uint8 + encodedTargetBitrates [][]byte + requiredLen int +} + +func (v VLA) preprocessForMashaling(ctx *vlaMarshalingContext) error { + for i := 0; i < len(v.ActiveSpatialLayer); i++ { + sl := v.ActiveSpatialLayer[i] + if sl.RTPStreamID < 0 || sl.RTPStreamID >= v.RTPStreamCount { + return fmt.Errorf("invalid RTP streamID %d:%w", sl.RTPStreamID, ErrVLAInvalidStreamID) + } + if sl.SpatialID < 0 || sl.SpatialID >= 4 { + return fmt.Errorf("invalid spatial ID %d: %w", sl.SpatialID, ErrVLAInvalidSpatialID) + } + if len(sl.TargetBitrates) == 0 || len(sl.TargetBitrates) > 4 { + return fmt.Errorf("invalid temporal layer count %d: %w", len(sl.TargetBitrates), ErrVLAInvalidTemporalLayer) + } + ctx.slMBs[sl.RTPStreamID] |= 1 << sl.SpatialID + if ctx.sls[sl.RTPStreamID][sl.SpatialID] != nil { + return fmt.Errorf("duplicate spatial layer: %w", ErrVLADuplicateSpatialID) + } + ctx.sls[sl.RTPStreamID][sl.SpatialID] = &sl + } + return nil +} + +func (v VLA) encodeTargetBitrates(ctx *vlaMarshalingContext) { + for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ { + for spatialID := 0; spatialID < 4; spatialID++ { + if sl := ctx.sls[rtpStreamID][spatialID]; sl != nil { + for _, kbps := range sl.TargetBitrates { + leb128 := obu.WriteToLeb128(uint(kbps)) + ctx.encodedTargetBitrates = append(ctx.encodedTargetBitrates, leb128) + ctx.requiredLen += len(leb128) + } + } + } + } +} + +func (v VLA) analyzeVLAForMarshaling() (*vlaMarshalingContext, error) { + // Validate RTPStreamCount + if v.RTPStreamCount <= 0 || v.RTPStreamCount > 4 { + return nil, ErrVLAInvalidStreamCount + } + // Validate RTPStreamID + if v.RTPStreamID < 0 || v.RTPStreamID >= v.RTPStreamCount { + return nil, ErrVLAInvalidStreamID + } + + ctx := &vlaMarshalingContext{} + err := v.preprocessForMashaling(ctx) + if err != nil { + return nil, err + } + + ctx.commonSLBM = commonSLBMValues(ctx.slMBs[:]) + + // RID, NS, sl_bm fields + if ctx.commonSLBM != 0 { + ctx.requiredLen = 1 + } else { + ctx.requiredLen = 3 + } + + // #tl fields + ctx.requiredLen += (len(v.ActiveSpatialLayer)-1)/4 + 1 + + v.encodeTargetBitrates(ctx) + + if v.HasResolutionAndFramerate { + ctx.requiredLen += len(v.ActiveSpatialLayer) * 5 + } + + return ctx, nil +} + +// Marshal encodes VLA into a byte slice. +func (v VLA) Marshal() ([]byte, error) { + ctx, err := v.analyzeVLAForMarshaling() + if err != nil { + return nil, err + } + + payload := make([]byte, ctx.requiredLen) + offset := 0 + + // RID, NS, sl_bm fields + payload[offset] = byte(v.RTPStreamID<<6) | byte(v.RTPStreamCount-1)<<4 | ctx.commonSLBM + + if ctx.commonSLBM == 0 { + offset++ + for streamID := 0; streamID < v.RTPStreamCount; streamID++ { + if streamID%2 == 0 { + payload[offset+streamID/2] |= ctx.slMBs[streamID] << 4 + } else { + payload[offset+streamID/2] |= ctx.slMBs[streamID] + } + } + offset += (v.RTPStreamCount - 1) / 2 + } + + // #tl fields + offset++ + var temporalLayerIndex int + for rtpStreamID := 0; rtpStreamID < v.RTPStreamCount; rtpStreamID++ { + for spatialID := 0; spatialID < 4; spatialID++ { + if sl := ctx.sls[rtpStreamID][spatialID]; sl != nil { + if temporalLayerIndex >= 4 { + temporalLayerIndex = 0 + offset++ + } + payload[offset] |= byte(len(sl.TargetBitrates)-1) << (2 * (3 - temporalLayerIndex)) + temporalLayerIndex++ + } + } + } + + // Target bitrate fields + offset++ + for _, encodedKbps := range ctx.encodedTargetBitrates { + encodedSize := len(encodedKbps) + copy(payload[offset:], encodedKbps) + offset += encodedSize + } + + // Resolution & framerate fields + if v.HasResolutionAndFramerate { + for _, sl := range v.ActiveSpatialLayer { + binary.BigEndian.PutUint16(payload[offset+0:], uint16(sl.Width-1)) + binary.BigEndian.PutUint16(payload[offset+2:], uint16(sl.Height-1)) + payload[offset+4] = byte(sl.Framerate) + offset += 5 + } + } + + return payload, nil +} + +func commonSLBMValues(slMBs []uint8) uint8 { + var common uint8 + for i := 0; i < len(slMBs); i++ { + if slMBs[i] == 0 { + continue + } + if common == 0 { + common = slMBs[i] + continue + } + if slMBs[i] != common { + return 0 + } + } + return common +} + +type vlaUnmarshalingContext struct { + payload []byte + offset int + slBMField uint8 + slBMs [4]uint8 +} + +func (ctx *vlaUnmarshalingContext) checkRemainingLen(requiredLen int) bool { + return len(ctx.payload)-ctx.offset >= requiredLen +} + +func (v *VLA) unmarshalSpatialLayers(ctx *vlaUnmarshalingContext) error { + if !ctx.checkRemainingLen(1) { + return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) + } + v.RTPStreamID = int(ctx.payload[ctx.offset] >> 6 & 0b11) + v.RTPStreamCount = int(ctx.payload[ctx.offset]>>4&0b11) + 1 + + // sl_bm fields + ctx.slBMField = ctx.payload[ctx.offset] & 0b1111 + ctx.offset++ + + if ctx.slBMField != 0 { + for streamID := 0; streamID < v.RTPStreamCount; streamID++ { + ctx.slBMs[streamID] = ctx.slBMField + } + } else { + if !ctx.checkRemainingLen((v.RTPStreamCount-1)/2 + 1) { + return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) + } + // slX_bm fields + for streamID := 0; streamID < v.RTPStreamCount; streamID++ { + var bm uint8 + if streamID%2 == 0 { + bm = ctx.payload[ctx.offset+streamID/2] >> 4 & 0b1111 + } else { + bm = ctx.payload[ctx.offset+streamID/2] & 0b1111 + } + ctx.slBMs[streamID] = bm + } + ctx.offset += 1 + (v.RTPStreamCount-1)/2 + } + + return nil +} + +func (v *VLA) unmarshalTemporalLayers(ctx *vlaUnmarshalingContext) error { + if !ctx.checkRemainingLen(1) { + return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) + } + + var temporalLayerIndex int + for streamID := 0; streamID < v.RTPStreamCount; streamID++ { + for spatialID := 0; spatialID < 4; spatialID++ { + if ctx.slBMs[streamID]&(1<= 4 { + temporalLayerIndex = 0 + ctx.offset++ + if !ctx.checkRemainingLen(1) { + return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) + } + } + tlCount := int(ctx.payload[ctx.offset]>>(2*(3-temporalLayerIndex))&0b11) + 1 + temporalLayerIndex++ + sl := SpatialLayer{ + RTPStreamID: streamID, + SpatialID: spatialID, + TargetBitrates: make([]int, tlCount), + } + v.ActiveSpatialLayer = append(v.ActiveSpatialLayer, sl) + } + } + ctx.offset++ + + // target bitrates + for i, sl := range v.ActiveSpatialLayer { + for j := range sl.TargetBitrates { + kbps, n, err := obu.ReadLeb128(ctx.payload[ctx.offset:]) + if err != nil { + return err + } + if !ctx.checkRemainingLen(int(n)) { + return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) + } + v.ActiveSpatialLayer[i].TargetBitrates[j] = int(kbps) + ctx.offset += int(n) + } + } + + return nil +} + +func (v *VLA) unmarshalResolutionAndFramerate(ctx *vlaUnmarshalingContext) error { + if !ctx.checkRemainingLen(len(v.ActiveSpatialLayer) * 5) { + return fmt.Errorf("failed to unmarshal VLA (offset=%d): %w", ctx.offset, ErrVLATooShort) + } + + v.HasResolutionAndFramerate = true + + for i := range v.ActiveSpatialLayer { + v.ActiveSpatialLayer[i].Width = int(binary.BigEndian.Uint16(ctx.payload[ctx.offset+0:])) + 1 + v.ActiveSpatialLayer[i].Height = int(binary.BigEndian.Uint16(ctx.payload[ctx.offset+2:])) + 1 + v.ActiveSpatialLayer[i].Framerate = int(ctx.payload[ctx.offset+4]) + ctx.offset += 5 + } + + return nil +} + +// Unmarshal decodes VLA from a byte slice. +func (v *VLA) Unmarshal(payload []byte) (int, error) { + ctx := &vlaUnmarshalingContext{ + payload: payload, + } + + err := v.unmarshalSpatialLayers(ctx) + if err != nil { + return ctx.offset, err + } + + // #tl fields (build the list ActiveSpatialLayer at the same time) + err = v.unmarshalTemporalLayers(ctx) + if err != nil { + return ctx.offset, err + } + + if len(ctx.payload) == ctx.offset { + return ctx.offset, nil + } + + // resolution & framerate (optional) + err = v.unmarshalResolutionAndFramerate(ctx) + if err != nil { + return ctx.offset, err + } + + return ctx.offset, nil +} + +// String makes VLA printable. +func (v VLA) String() string { + out := fmt.Sprintf("RID:%d,RTPStreamCount:%d", v.RTPStreamID, v.RTPStreamCount) + var slOut []string + for _, sl := range v.ActiveSpatialLayer { + out2 := fmt.Sprintf("RTPStreamID:%d", sl.RTPStreamID) + out2 += fmt.Sprintf(",TargetBitrates:%v", sl.TargetBitrates) + if v.HasResolutionAndFramerate { + out2 += fmt.Sprintf(",Resolution:(%d,%d)", sl.Width, sl.Height) + out2 += fmt.Sprintf(",Framerate:%d", sl.Framerate) + } + slOut = append(slOut, out2) + } + out += fmt.Sprintf(",ActiveSpatialLayers:{%s}", strings.Join(slOut, ",")) + return out +} diff --git a/vlaextension_test.go b/vlaextension_test.go new file mode 100644 index 0000000..b9b8066 --- /dev/null +++ b/vlaextension_test.go @@ -0,0 +1,532 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package rtp + +import ( + "bytes" + "encoding/hex" + "errors" + "reflect" + "testing" +) + +func TestVLAMarshal(t *testing.T) { + requireNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } + } + + t.Run("3 streams no resolution and framerate", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 3, + ActiveSpatialLayer: []SpatialLayer{ + { + RTPStreamID: 0, + SpatialID: 0, + TargetBitrates: []int{150}, + }, + { + RTPStreamID: 1, + SpatialID: 0, + TargetBitrates: []int{240, 400}, + }, + { + RTPStreamID: 2, + SpatialID: 0, + TargetBitrates: []int{720, 1200}, + }, + }, + } + + bytesActual, err := vla.Marshal() + requireNoError(t, err) + bytesExpected, err := hex.DecodeString("21149601f0019003d005b009") + requireNoError(t, err) + if !bytes.Equal(bytesExpected, bytesActual) { + t.Fatalf("expected %s, actual %s", hex.EncodeToString(bytesExpected), hex.EncodeToString(bytesActual)) + } + }) + + t.Run("3 streams with resolution and framerate", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 2, + RTPStreamCount: 3, + ActiveSpatialLayer: []SpatialLayer{ + { + RTPStreamID: 0, + SpatialID: 0, + TargetBitrates: []int{150}, + Width: 320, + Height: 180, + Framerate: 30, + }, + { + RTPStreamID: 1, + SpatialID: 0, + TargetBitrates: []int{240, 400}, + Width: 640, + Height: 360, + Framerate: 30, + }, + { + RTPStreamID: 2, + SpatialID: 0, + TargetBitrates: []int{720, 1200}, + Width: 1280, + Height: 720, + Framerate: 30, + }, + }, + HasResolutionAndFramerate: true, + } + + bytesActual, err := vla.Marshal() + requireNoError(t, err) + bytesExpected, err := hex.DecodeString("a1149601f0019003d005b009013f00b31e027f01671e04ff02cf1e") + requireNoError(t, err) + if !bytes.Equal(bytesExpected, bytesActual) { + t.Fatalf("expected %s, actual %s", hex.EncodeToString(bytesExpected), hex.EncodeToString(bytesActual)) + } + }) + + t.Run("Negative RTPStreamCount", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: -1, + ActiveSpatialLayer: []SpatialLayer{}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidStreamCount) { + t.Fatal("expected ErrVLAInvalidRTPStreamCount") + } + }) + + t.Run("RTPStreamCount too large", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 5, + ActiveSpatialLayer: []SpatialLayer{{}, {}, {}, {}, {}}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidStreamCount) { + t.Fatal("expected ErrVLAInvalidRTPStreamCount") + } + }) + + t.Run("Negative RTPStreamID", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: -1, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{}}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidStreamID) { + t.Fatalf("expected ErrVLAInvalidRTPStreamID, actual %v", err) + } + }) + + t.Run("RTPStreamID to large", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 1, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{}}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidStreamID) { + t.Fatalf("expected ErrVLAInvalidRTPStreamID: %v", err) + } + }) + + t.Run("Invalid stream ID in the spatial layer", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: -1, + }}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidStreamID) { + t.Fatalf("expected ErrVLAInvalidStreamID: %v", err) + } + vla = &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: 1, + }}, + } + _, err = vla.Marshal() + if !errors.Is(err, ErrVLAInvalidStreamID) { + t.Fatalf("expected ErrVLAInvalidStreamID: %v", err) + } + }) + + t.Run("Invalid spatial ID in the spatial layer", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: 0, + SpatialID: -1, + }}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidSpatialID) { + t.Fatalf("expected ErrVLAInvalidSpatialID: %v", err) + } + vla = &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: 0, + SpatialID: 5, + }}, + } + _, err = vla.Marshal() + if !errors.Is(err, ErrVLAInvalidSpatialID) { + t.Fatalf("expected ErrVLAInvalidSpatialID: %v", err) + } + }) + + t.Run("Invalid temporal layer in the spatial layer", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: 0, + SpatialID: 0, + TargetBitrates: []int{}, + }}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLAInvalidTemporalLayer) { + t.Fatalf("expected ErrVLAInvalidTemporalLayer: %v", err) + } + vla = &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: 0, + SpatialID: 0, + TargetBitrates: []int{100, 200, 300, 400, 500}, + }}, + } + _, err = vla.Marshal() + if !errors.Is(err, ErrVLAInvalidTemporalLayer) { + t.Fatalf("expected ErrVLAInvalidTemporalLayer: %v", err) + } + }) + + t.Run("Duplicate spatial ID in the spatial layer", func(t *testing.T) { + vla := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 1, + ActiveSpatialLayer: []SpatialLayer{{ + RTPStreamID: 0, + SpatialID: 0, + TargetBitrates: []int{100}, + }, { + RTPStreamID: 0, + SpatialID: 0, + TargetBitrates: []int{200}, + }}, + } + _, err := vla.Marshal() + if !errors.Is(err, ErrVLADuplicateSpatialID) { + t.Fatalf("expected ErrVLADuplicateSpatialID: %v", err) + } + }) +} + +func TestVLAUnmarshal(t *testing.T) { + requireEqualInt := func(t *testing.T, expected, actual int) { + if expected != actual { + t.Fatalf("expected %d, actual %d", expected, actual) + } + } + requireNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } + } + requireTrue := func(t *testing.T, val bool) { + if !val { + t.Fatal("expected true") + } + } + requireFalse := func(t *testing.T, val bool) { + if val { + t.Fatal("expected false") + } + } + + t.Run("3 streams no resolution and framerate", func(t *testing.T) { + // two layer ("low", "high") + b, err := hex.DecodeString("21149601f0019003d005b009") + requireNoError(t, err) + if err != nil { + t.Fatal("failed to decode input data") + } + + vla := &VLA{} + n, err := vla.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + + requireEqualInt(t, 0, vla.RTPStreamID) + requireEqualInt(t, 3, vla.RTPStreamCount) + requireEqualInt(t, 3, len(vla.ActiveSpatialLayer)) + + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].SpatialID) + requireEqualInt(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) + requireEqualInt(t, 150, vla.ActiveSpatialLayer[0].TargetBitrates[0]) + + requireEqualInt(t, 1, vla.ActiveSpatialLayer[1].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[1].SpatialID) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) + requireEqualInt(t, 240, vla.ActiveSpatialLayer[1].TargetBitrates[0]) + requireEqualInt(t, 400, vla.ActiveSpatialLayer[1].TargetBitrates[1]) + + requireFalse(t, vla.HasResolutionAndFramerate) + + requireEqualInt(t, 2, vla.ActiveSpatialLayer[2].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[2].SpatialID) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer[2].TargetBitrates)) + requireEqualInt(t, 720, vla.ActiveSpatialLayer[2].TargetBitrates[0]) + requireEqualInt(t, 1200, vla.ActiveSpatialLayer[2].TargetBitrates[1]) + }) + + t.Run("3 streams with resolution and framerate", func(t *testing.T) { + b, err := hex.DecodeString("a1149601f0019003d005b009013f00b31e027f01671e04ff02cf1e") + requireNoError(t, err) + + vla := &VLA{} + n, err := vla.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + + requireEqualInt(t, 2, vla.RTPStreamID) + requireEqualInt(t, 3, vla.RTPStreamCount) + + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].SpatialID) + requireEqualInt(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) + requireEqualInt(t, 150, vla.ActiveSpatialLayer[0].TargetBitrates[0]) + + requireEqualInt(t, 1, vla.ActiveSpatialLayer[1].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[1].SpatialID) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) + requireEqualInt(t, 240, vla.ActiveSpatialLayer[1].TargetBitrates[0]) + requireEqualInt(t, 400, vla.ActiveSpatialLayer[1].TargetBitrates[1]) + + requireEqualInt(t, 2, vla.ActiveSpatialLayer[2].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[2].SpatialID) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer[2].TargetBitrates)) + requireEqualInt(t, 720, vla.ActiveSpatialLayer[2].TargetBitrates[0]) + requireEqualInt(t, 1200, vla.ActiveSpatialLayer[2].TargetBitrates[1]) + + requireTrue(t, vla.HasResolutionAndFramerate) + + requireEqualInt(t, 320, vla.ActiveSpatialLayer[0].Width) + requireEqualInt(t, 180, vla.ActiveSpatialLayer[0].Height) + requireEqualInt(t, 30, vla.ActiveSpatialLayer[0].Framerate) + requireEqualInt(t, 640, vla.ActiveSpatialLayer[1].Width) + requireEqualInt(t, 360, vla.ActiveSpatialLayer[1].Height) + requireEqualInt(t, 30, vla.ActiveSpatialLayer[1].Framerate) + requireEqualInt(t, 1280, vla.ActiveSpatialLayer[2].Width) + requireEqualInt(t, 720, vla.ActiveSpatialLayer[2].Height) + requireEqualInt(t, 30, vla.ActiveSpatialLayer[2].Framerate) + }) + + t.Run("2 streams", func(t *testing.T) { + // two layer ("low", "high") + b, err := hex.DecodeString("1110c801d005b009") + requireNoError(t, err) + + vla := &VLA{} + n, err := vla.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + + requireEqualInt(t, 0, vla.RTPStreamID) + requireEqualInt(t, 2, vla.RTPStreamCount) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer)) + + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].SpatialID) + requireEqualInt(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) + requireEqualInt(t, 200, vla.ActiveSpatialLayer[0].TargetBitrates[0]) + + requireEqualInt(t, 1, vla.ActiveSpatialLayer[1].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[1].SpatialID) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) + requireEqualInt(t, 720, vla.ActiveSpatialLayer[1].TargetBitrates[0]) + requireEqualInt(t, 1200, vla.ActiveSpatialLayer[1].TargetBitrates[1]) + + requireFalse(t, vla.HasResolutionAndFramerate) + }) + + t.Run("3 streams mid paused with resolution and framerate", func(t *testing.T) { + b, err := hex.DecodeString("601010109601d005b009013f00b31e04ff02cf1e") + requireNoError(t, err) + + vla := &VLA{} + n, err := vla.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + + requireEqualInt(t, 1, vla.RTPStreamID) + requireEqualInt(t, 3, vla.RTPStreamCount) + + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[0].SpatialID) + requireEqualInt(t, 1, len(vla.ActiveSpatialLayer[0].TargetBitrates)) + requireEqualInt(t, 150, vla.ActiveSpatialLayer[0].TargetBitrates[0]) + + requireEqualInt(t, 2, vla.ActiveSpatialLayer[1].RTPStreamID) + requireEqualInt(t, 0, vla.ActiveSpatialLayer[1].SpatialID) + requireEqualInt(t, 2, len(vla.ActiveSpatialLayer[1].TargetBitrates)) + requireEqualInt(t, 720, vla.ActiveSpatialLayer[1].TargetBitrates[0]) + requireEqualInt(t, 1200, vla.ActiveSpatialLayer[1].TargetBitrates[1]) + + requireTrue(t, vla.HasResolutionAndFramerate) + + requireEqualInt(t, 320, vla.ActiveSpatialLayer[0].Width) + requireEqualInt(t, 180, vla.ActiveSpatialLayer[0].Height) + requireEqualInt(t, 30, vla.ActiveSpatialLayer[0].Framerate) + requireEqualInt(t, 1280, vla.ActiveSpatialLayer[1].Width) + requireEqualInt(t, 720, vla.ActiveSpatialLayer[1].Height) + requireEqualInt(t, 30, vla.ActiveSpatialLayer[1].Framerate) + }) + + t.Run("extra 1", func(t *testing.T) { + b, err := hex.DecodeString("a0001040ac02f403") + requireNoError(t, err) + + vla := &VLA{} + n, err := vla.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + }) + + t.Run("extra 2", func(t *testing.T) { + b, err := hex.DecodeString("a00010409405cc08") + requireNoError(t, err) + + vla := &VLA{} + n, err := vla.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + }) +} + +func TestVLAMarshalThenUnmarshal(t *testing.T) { + requireEqualInt := func(t *testing.T, expected, actual int) { + if expected != actual { + t.Fatalf("expected %d, actual %d", expected, actual) + } + } + requireNoError := func(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } + } + + t.Run("multiple spatial layers", func(t *testing.T) { + var spatialLayers []SpatialLayer + for streamID := 0; streamID < 3; streamID++ { + for spatialID := 0; spatialID < 4; spatialID++ { + spatialLayers = append(spatialLayers, SpatialLayer{ + RTPStreamID: streamID, + SpatialID: spatialID, + TargetBitrates: []int{150, 200}, + Width: 320, + Height: 180, + Framerate: 30, + }) + } + } + + vla0 := &VLA{ + RTPStreamID: 2, + RTPStreamCount: 3, + ActiveSpatialLayer: spatialLayers, + HasResolutionAndFramerate: true, + } + + b, err := vla0.Marshal() + requireNoError(t, err) + + vla1 := &VLA{} + n, err := vla1.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + + if !reflect.DeepEqual(vla0, vla1) { + t.Fatalf("expected %v, actual %v", vla0, vla1) + } + }) + + t.Run("different spatial layer bitmasks", func(t *testing.T) { + var spatialLayers []SpatialLayer + for streamID := 0; streamID < 4; streamID++ { + for spatialID := 0; spatialID < streamID+1; spatialID++ { + spatialLayers = append(spatialLayers, SpatialLayer{ + RTPStreamID: streamID, + SpatialID: spatialID, + TargetBitrates: []int{150, 200}, + Width: 320, + Height: 180, + Framerate: 30, + }) + } + } + + vla0 := &VLA{ + RTPStreamID: 0, + RTPStreamCount: 4, + ActiveSpatialLayer: spatialLayers, + HasResolutionAndFramerate: true, + } + + b, err := vla0.Marshal() + requireNoError(t, err) + if b[0]&0x0f != 0 { + t.Error("expects sl_bm to be 0") + } + if b[1] != 0x13 { + t.Error("expects sl0_bm,sl1_bm to be b0001,b0011") + } + if b[2] != 0x7f { + t.Error("expects sl1_bm,sl2_bm to be b0111,b1111") + } + t.Logf("b: %s", hex.EncodeToString(b)) + + vla1 := &VLA{} + n, err := vla1.Unmarshal(b) + requireNoError(t, err) + requireEqualInt(t, len(b), n) + + if !reflect.DeepEqual(vla0, vla1) { + t.Fatalf("expected %v, actual %v", vla0, vla1) + } + }) +} + +func FuzzVLAUnmarshal(f *testing.F) { + f.Add([]byte{0}) + f.Add([]byte("70")) + + f.Fuzz(func(t *testing.T, data []byte) { + vla := &VLA{} + _, err := vla.Unmarshal(data) + if err != nil { + t.Skip() // If the function returns an error, we skip the test case + } + }) +} From 410c5824dbb85774f20bc8f74f3fa2da41e51ff4 Mon Sep 17 00:00:00 2001 From: Yutaka Takeda Date: Sat, 9 Mar 2024 14:56:46 -0800 Subject: [PATCH 084/102] Fix a bug in AbsCpatureTimeExtension --- abscapturetimeextension.go | 16 +++++ abscapturetimeextension_test.go | 106 ++++++++++++++++++++++---------- 2 files changed, 89 insertions(+), 33 deletions(-) diff --git a/abscapturetimeextension.go b/abscapturetimeextension.go index 56b783d..5e96cff 100644 --- a/abscapturetimeextension.go +++ b/abscapturetimeextension.go @@ -66,7 +66,15 @@ func (t AbsCaptureTimeExtension) EstimatedCaptureClockOffsetDuration() *time.Dur return nil } offset := *t.EstimatedCaptureClockOffset + negative := false + if offset < 0 { + offset = -offset + negative = true + } duration := time.Duration(offset/(1<<32))*time.Second + time.Duration((offset&0xFFFFFFFF)*1e9/(1<<32))*time.Nanosecond + if negative { + duration = -duration + } return &duration } @@ -80,9 +88,17 @@ func NewAbsCaptureTimeExtension(captureTime time.Time) *AbsCaptureTimeExtension // NewAbsCaptureTimeExtensionWithCaptureClockOffset makes new AbsCaptureTimeExtension from time.Time and a clock offset. func NewAbsCaptureTimeExtensionWithCaptureClockOffset(captureTime time.Time, captureClockOffset time.Duration) *AbsCaptureTimeExtension { ns := captureClockOffset.Nanoseconds() + negative := false + if ns < 0 { + ns = -ns + negative = true + } lsb := (ns / 1e9) & 0xFFFFFFFF msb := (((ns % 1e9) * (1 << 32)) / 1e9) & 0xFFFFFFFF offset := (lsb << 32) | msb + if negative { + offset = -offset + } return &AbsCaptureTimeExtension{ Timestamp: toNtpTime(captureTime), EstimatedCaptureClockOffset: &offset, diff --git a/abscapturetimeextension_test.go b/abscapturetimeextension_test.go index 15c434a..038e677 100644 --- a/abscapturetimeextension_test.go +++ b/abscapturetimeextension_test.go @@ -9,38 +9,78 @@ import ( ) func TestAbsCaptureTimeExtension_Roundtrip(t *testing.T) { - t0 := time.Now() - e1 := NewAbsCaptureTimeExtension(t0) - b1, err1 := e1.Marshal() - if err1 != nil { - t.Fatal(err1) - } - var o1 AbsCaptureTimeExtension - if err := o1.Unmarshal(b1); err != nil { - t.Fatal(err) - } - dt1 := o1.CaptureTime().Sub(t0).Seconds() - if dt1 < -0.001 || dt1 > 0.001 { - t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o1.CaptureTime(), dt1) - } - if o1.EstimatedCaptureClockOffsetDuration() != nil { - t.Fatalf("duration differs, want nil got %d", o1.EstimatedCaptureClockOffsetDuration()) - } + t.Run("positive captureClockOffset", func(t *testing.T) { + t0 := time.Now() + e1 := NewAbsCaptureTimeExtension(t0) + b1, err1 := e1.Marshal() + if err1 != nil { + t.Fatal(err1) + } + var o1 AbsCaptureTimeExtension + if err := o1.Unmarshal(b1); err != nil { + t.Fatal(err) + } + dt1 := o1.CaptureTime().Sub(t0).Seconds() + if dt1 < -0.001 || dt1 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o1.CaptureTime(), dt1) + } + if o1.EstimatedCaptureClockOffsetDuration() != nil { + t.Fatalf("duration differs, want nil got %d", o1.EstimatedCaptureClockOffsetDuration()) + } - e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, 1250*time.Millisecond) - b2, err2 := e2.Marshal() - if err2 != nil { - t.Fatal(err2) - } - var o2 AbsCaptureTimeExtension - if err := o2.Unmarshal(b2); err != nil { - t.Fatal(err) - } - dt2 := o1.CaptureTime().Sub(t0).Seconds() - if dt2 < -0.001 || dt2 > 0.001 { - t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o2.CaptureTime(), dt2) - } - if *o2.EstimatedCaptureClockOffsetDuration() != 1250*time.Millisecond { - t.Fatalf("duration differs, want 250ms got %d", *o2.EstimatedCaptureClockOffsetDuration()) - } + e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, 1250*time.Millisecond) + b2, err2 := e2.Marshal() + if err2 != nil { + t.Fatal(err2) + } + var o2 AbsCaptureTimeExtension + if err := o2.Unmarshal(b2); err != nil { + t.Fatal(err) + } + dt2 := o1.CaptureTime().Sub(t0).Seconds() + if dt2 < -0.001 || dt2 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o2.CaptureTime(), dt2) + } + if *o2.EstimatedCaptureClockOffsetDuration() != 1250*time.Millisecond { + t.Fatalf("duration differs, want 250ms got %d", *o2.EstimatedCaptureClockOffsetDuration()) + } + }) + + // This test can verify the for for the issue 247 + t.Run("negative captureClockOffset", func(t *testing.T) { + t0 := time.Now() + e1 := NewAbsCaptureTimeExtension(t0) + b1, err1 := e1.Marshal() + if err1 != nil { + t.Fatal(err1) + } + var o1 AbsCaptureTimeExtension + if err := o1.Unmarshal(b1); err != nil { + t.Fatal(err) + } + dt1 := o1.CaptureTime().Sub(t0).Seconds() + if dt1 < -0.001 || dt1 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o1.CaptureTime(), dt1) + } + if o1.EstimatedCaptureClockOffsetDuration() != nil { + t.Fatalf("duration differs, want nil got %d", o1.EstimatedCaptureClockOffsetDuration()) + } + + e2 := NewAbsCaptureTimeExtensionWithCaptureClockOffset(t0, -250*time.Millisecond) + b2, err2 := e2.Marshal() + if err2 != nil { + t.Fatal(err2) + } + var o2 AbsCaptureTimeExtension + if err := o2.Unmarshal(b2); err != nil { + t.Fatal(err) + } + dt2 := o1.CaptureTime().Sub(t0).Seconds() + if dt2 < -0.001 || dt2 > 0.001 { + t.Fatalf("timestamp differs, want %v got %v (dt=%f)", t0, o2.CaptureTime(), dt2) + } + if *o2.EstimatedCaptureClockOffsetDuration() != -250*time.Millisecond { + t.Fatalf("duration differs, want -250ms got %v", *o2.EstimatedCaptureClockOffsetDuration()) + } + }) } From c52c1e79bffe943d48ff777b54f29243e9f78b30 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Sat, 16 Mar 2024 20:05:04 -0400 Subject: [PATCH 085/102] Fix broken link in README.md AUTHORS.txt has been deleted --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 37b7cc0..c84621c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ We are always looking to support **your projects**. Please reach out if you have 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: [AUTHORS.txt](./AUTHORS.txt) +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 \ No newline at end of file +MIT License - see [LICENSE](LICENSE) for full text From 057eda35a6580f672dc827cfafae06adc5e151ac Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Thu, 14 Mar 2024 21:33:23 +0200 Subject: [PATCH 086/102] Add static RTP PayloadTypes as a constant Defined in IANA [0] [0] https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml --- payload_types.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 payload_types.go diff --git a/payload_types.go b/payload_types.go new file mode 100644 index 0000000..a9bf227 --- /dev/null +++ b/payload_types.go @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2024 The Pion community +// SPDX-License-Identifier: MIT + +package rtp + +// https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +// https://en.wikipedia.org/wiki/RTP_payload_formats + +// Audio Payload Types as defined in https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +const ( + // PayloadTypePCMU is a payload type for ITU-T G.711 PCM μ-Law audio 64 kbit/s (RFC 3551). + PayloadTypePCMU = 0 + // PayloadTypeGSM is a payload type for European GSM Full Rate audio 13 kbit/s (GSM 06.10). + PayloadTypeGSM = 3 + // PayloadTypeG723 is a payload type for ITU-T G.723.1 audio (RFC 3551). + PayloadTypeG723 = 4 + // PayloadTypeDVI4_8000 is a payload type for IMA ADPCM audio 32 kbit/s (RFC 3551). + PayloadTypeDVI4_8000 = 5 + // PayloadTypeDVI4_16000 is a payload type for IMA ADPCM audio 64 kbit/s (RFC 3551). + PayloadTypeDVI4_16000 = 6 + // PayloadTypeLPC is a payload type for Experimental Linear Predictive Coding audio 5.6 kbit/s (RFC 3551). + PayloadTypeLPC = 7 + // PayloadTypePCMA is a payload type for ITU-T G.711 PCM A-Law audio 64 kbit/s (RFC 3551). + PayloadTypePCMA = 8 + // PayloadTypeG722 is a payload type for ITU-T G.722 audio 64 kbit/s (RFC 3551). + PayloadTypeG722 = 9 + // PayloadTypeL16Stereo is a payload type for Linear PCM 16-bit Stereo audio 1411.2 kbit/s, uncompressed (RFC 3551). + PayloadTypeL16Stereo = 10 + // PayloadTypeL16Mono is a payload type for Linear PCM 16-bit audio 705.6 kbit/s, uncompressed (RFC 3551). + PayloadTypeL16Mono = 11 + // PayloadTypeQCELP is a payload type for Qualcomm Code Excited Linear Prediction (RFC 2658, RFC 3551). + PayloadTypeQCELP = 12 + // PayloadTypeCN is a payload type for Comfort noise (RFC 3389). + PayloadTypeCN = 13 + // PayloadTypeMPA is a payload type for MPEG-1 or MPEG-2 audio only (RFC 3551, RFC 2250). + PayloadTypeMPA = 14 + // PayloadTypeG728 is a payload type for ITU-T G.728 audio 16 kbit/s (RFC 3551). + PayloadTypeG728 = 15 + // PayloadTypeDVI4_11025 is a payload type for IMA ADPCM audio 44.1 kbit/s (RFC 3551). + PayloadTypeDVI4_11025 = 16 + // PayloadTypeDVI4_22050 is a payload type for IMA ADPCM audio 88.2 kbit/s (RFC 3551). + PayloadTypeDVI4_22050 = 17 + // PayloadTypeG729 is a payload type for ITU-T G.729 and G.729a audio 8 kbit/s (RFC 3551, RFC 3555). + PayloadTypeG729 = 18 +) + +// Video Payload Types as defined in https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml +const ( + // PayloadTypeCELLB is a payload type for Sun CellB video (RFC 2029). + PayloadTypeCELLB = 25 + // PayloadTypeJPEG is a payload type for JPEG video (RFC 2435). + PayloadTypeJPEG = 26 + // PayloadTypeNV is a payload type for Xerox PARC's Network Video (nv, RFC 3551). + PayloadTypeNV = 28 + // PayloadTypeH261 is a payload type for ITU-T H.261 video (RFC 4587). + PayloadTypeH261 = 31 + // PayloadTypeMPV is a payload type for MPEG-1 and MPEG-2 video (RFC 2250). + PayloadTypeMPV = 32 + // PayloadTypeMP2T is a payload type for MPEG-2 transport stream (RFC 2250). + PayloadTypeMP2T = 33 + // PayloadTypeH263 is a payload type for H.263 video, first version (1996, RFC 3551, RFC 2190). + PayloadTypeH263 = 34 +) + +const ( + // PayloadTypeFirstDynamic is a first non-static payload type. + PayloadTypeFirstDynamic = 35 +) From 39052f8c2cb3824d479703048fe4419e1a9b0678 Mon Sep 17 00:00:00 2001 From: Alex Pokotilo Date: Thu, 15 Feb 2024 16:24:52 +0600 Subject: [PATCH 087/102] Add padding support to Packetizer To add padding-only samples call GeneratePadding --- packetizer.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packetizer.go b/packetizer.go index 7ecebde..7a6a46d 100644 --- a/packetizer.go +++ b/packetizer.go @@ -15,6 +15,7 @@ type Payloader interface { // Packetizer packetizes a payload type Packetizer interface { Packetize(payload []byte, samples uint32) []*Packet + GeneratePadding(samples uint32) []*Packet EnableAbsSendTime(value int) SkipSamples(skippedSamples uint32) } @@ -98,6 +99,38 @@ func (p *packetizer) Packetize(payload []byte, samples uint32) []*Packet { return packets } +// GeneratePadding returns required padding-only packages +func (p *packetizer) GeneratePadding(samples uint32) []*Packet { + // Guard against an empty payload + if samples == 0 { + return nil + } + + packets := make([]*Packet, samples) + + for i := 0; i < int(samples); i++ { + pp := make([]byte, 255) + pp[254] = 255 + + packets[i] = &Packet{ + Header: Header{ + Version: 2, + Padding: true, + Extension: false, + Marker: false, + PayloadType: p.PayloadType, + SequenceNumber: p.Sequencer.NextSequenceNumber(), + Timestamp: p.Timestamp, // Use latest timestamp + SSRC: p.SSRC, + CSRC: []uint32{}, + }, + Payload: pp, + } + } + + return packets +} + // SkipSamples causes a gap in sample count between Packetize requests so the // RTP payloads produced have a gap in timestamps func (p *packetizer) SkipSamples(skippedSamples uint32) { From a18e24dc84b5797056cb45c7cab0a6aff0118f32 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:59:37 +0000 Subject: [PATCH 088/102] Update CI configs to v0.11.4 Update lint scripts and CI configs. --- .github/workflows/api.yaml | 20 ++++++++++++++++++++ .github/workflows/release.yml | 2 +- .github/workflows/test.yaml | 6 +++--- .github/workflows/tidy-check.yaml | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/api.yaml diff --git a/.github/workflows/api.yaml b/.github/workflows/api.yaml new file mode 100644 index 0000000..1032179 --- /dev/null +++ b/.github/workflows/api.yaml @@ -0,0 +1,20 @@ +# +# 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. +# +# SPDX-FileCopyrightText: 2023 The Pion community +# SPDX-License-Identifier: MIT + +name: API +on: + pull_request: + +jobs: + check: + uses: pion/.goassets/.github/workflows/api.reusable.yml@master diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01227e2..0e72ea4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,4 +21,4 @@ jobs: release: uses: pion/.goassets/.github/workflows/release.reusable.yml@master with: - go-version: '1.20' # auto-update/latest-go-version + go-version: "1.22" # auto-update/latest-go-version diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c8294ef..ad6eb90 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,7 +23,7 @@ jobs: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: - go: ['1.21', '1.20'] # auto-update/supported-go-version-list + go: ["1.22", "1.21"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -32,7 +32,7 @@ jobs: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: - go: ['1.21', '1.20'] # auto-update/supported-go-version-list + go: ["1.22", "1.21"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -40,4 +40,4 @@ jobs: test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: - go-version: '1.20' # auto-update/latest-go-version + go-version: "1.22" # auto-update/latest-go-version diff --git a/.github/workflows/tidy-check.yaml b/.github/workflows/tidy-check.yaml index 33d6b50..417e730 100644 --- a/.github/workflows/tidy-check.yaml +++ b/.github/workflows/tidy-check.yaml @@ -22,4 +22,4 @@ jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: - go-version: '1.21' # auto-update/latest-go-version + go-version: "1.22" # auto-update/latest-go-version From 14c61dc0359c379f52978826c66bfcd9d90f7ff7 Mon Sep 17 00:00:00 2001 From: Uladzimir Filipchenkau Date: Fri, 29 Mar 2024 16:08:30 +0300 Subject: [PATCH 089/102] Fix out of range access in VP8 Unmarshal When unmarshaling 0x81, 0x81, 0x94 panic: runtime error: index out of range [3] with length 3 --- codecs/vp8_packet.go | 5 ++++- codecs/vp8_packet_test.go | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index 5d0a654..4ddd15b 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -123,7 +123,7 @@ type VP8Packet struct { } // 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) { +func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { //nolint: gocognit if payload == nil { return nil, errNilPacket } @@ -163,6 +163,9 @@ func (p *VP8Packet) Unmarshal(payload []byte) ([]byte, error) { return nil, errShortPacket } if payload[payloadIndex]&0x80 > 0 { // M == 1, PID is 16bit + if payloadIndex+1 >= payloadLen { + return nil, errShortPacket + } p.PictureID = (uint16(payload[payloadIndex]&0x7F) << 8) | uint16(payload[payloadIndex+1]) payloadIndex += 2 } else { diff --git a/codecs/vp8_packet_test.go b/codecs/vp8_packet_test.go index bb09ef1..3abb520 100644 --- a/codecs/vp8_packet_test.go +++ b/codecs/vp8_packet_test.go @@ -106,7 +106,7 @@ func TestVP8Packet_Unmarshal(t *testing.T) { // attention to partition boundaries. In that case, it may // produce packets with minimal headers. - // The next two have been witnessed in nature. + // The next three have been witnessed in nature. _, err = pck.Unmarshal([]byte{0x00}) if err != nil { t.Errorf("Empty packet with trivial header: %v", err) @@ -115,6 +115,13 @@ func TestVP8Packet_Unmarshal(t *testing.T) { if err != nil { t.Errorf("Non-empty packet with trivial header: %v", err) } + raw, err = pck.Unmarshal([]byte{0x81, 0x81, 0x94}) + if raw != nil { + t.Fatal("Result should be nil in case of error") + } + if !errors.Is(err, errShortPacket) { + t.Fatal("Error should be:", errShortPacket) + } // The following two were invented. _, err = pck.Unmarshal([]byte{0x80, 0x00}) From 0a5cc325a1e78c11760212ae70a96692d69a3cfd Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:43:05 +0000 Subject: [PATCH 090/102] Update CI configs to v0.11.7 Update lint scripts and CI configs. --- .golangci.yml | 7 +++---- .reuse/dep5 | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6dd80c8..e06de4d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,8 @@ linters-settings: govet: - check-shadowing: true + enable: + - shadow misspell: locale: US exhaustive: @@ -110,6 +111,7 @@ linters: issues: exclude-use-default: false + exclude-dirs-use-default: false exclude-rules: # Allow complex tests and examples, better to be self contained - path: (examples|main\.go|_test\.go) @@ -121,6 +123,3 @@ issues: - path: cmd linters: - forbidigo - -run: - skip-dirs-use-default: false diff --git a/.reuse/dep5 b/.reuse/dep5 index 717f0c1..eb7fac2 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -2,7 +2,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ -Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples/examples.json +Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json Copyright: 2023 The Pion community License: MIT From 74a9dc74432a406dcfdb70db77ce7fb5a2a2f496 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Tue, 9 Apr 2024 03:11:05 +0000 Subject: [PATCH 091/102] Update CI configs to v0.11.12 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ad6eb90..08e4272 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -27,6 +27,7 @@ jobs: fail-fast: false with: go-version: ${{ matrix.go }} + secrets: inherit test-i386: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master @@ -41,3 +42,4 @@ jobs: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: go-version: "1.22" # auto-update/latest-go-version + secrets: inherit From 32ee92e2ed7f27ed64dd59ba37ec6aefba2c6ca1 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Tue, 16 Apr 2024 00:01:34 +0200 Subject: [PATCH 092/102] Convert H264Packet.doPackaging to append style Not a big deal, avoids a couple of allocations for every STAP-A packet. --- codecs/h264_packet.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 583e189..49c37da 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -188,14 +188,16 @@ type H264Packet struct { videoDepacketizer } -func (p *H264Packet) doPackaging(nalu []byte) []byte { +func (p *H264Packet) doPackaging(buf, nalu []byte) []byte { if p.IsAVC { - naluLength := make([]byte, 4) - binary.BigEndian.PutUint32(naluLength, uint32(len(nalu))) - return append(naluLength, nalu...) + buf = binary.BigEndian.AppendUint32(buf, uint32(len(nalu))) + buf = append(buf, nalu...) + return buf } - return append(annexbNALUStartCode, nalu...) + buf = append(buf, annexbNALUStartCode...) + buf = append(buf, nalu...) + return buf } // IsDetectedFinalPacketInSequence returns true of the packet passed in has the @@ -215,7 +217,7 @@ func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { naluType := payload[0] & naluTypeBitmask switch { case naluType > 0 && naluType < 24: - return p.doPackaging(payload), nil + return p.doPackaging(nil, payload), nil case naluType == stapaNALUType: currOffset := int(stapaHeaderSize) @@ -228,7 +230,7 @@ func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { 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])...) + result = p.doPackaging(result, payload[currOffset:currOffset+naluSize]) currOffset += naluSize } return result, nil @@ -251,7 +253,7 @@ func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { nalu := append([]byte{}, naluRefIdc|fragmentedNaluType) nalu = append(nalu, p.fuaBuffer...) p.fuaBuffer = nil - return p.doPackaging(nalu), nil + return p.doPackaging(nil, nalu), nil } return []byte{}, nil From a663858e3acea2672f69a94a1edbb9ad4ccfa583 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Tue, 16 Apr 2024 00:32:22 +0200 Subject: [PATCH 093/102] Add SetZeroAllocation SetZeroAllocation enables a higher performance depacketizer, with a reduced feature set. Currently only enabled for H264. By default, the H264Packet.Unmarshal performs reassembly of FU-A NALUs. Not only is this ineficient, it also assumes that no packet reordering or packet loss ever happens. --- codecs/common.go | 12 +++++++++++- codecs/h264_packet.go | 8 ++++++++ depacketizer.go | 4 ++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/codecs/common.go b/codecs/common.go index 5da8aaf..e807a0a 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -22,8 +22,18 @@ func (d *audioDepacketizer) IsPartitionHead(_ []byte) bool { } // videoDepacketizer is a mixin for video codec depacketizers -type videoDepacketizer struct{} +type videoDepacketizer struct { + zeroAllocation bool +} func (d *videoDepacketizer) IsPartitionTail(marker bool, _ []byte) bool { return marker } + +// SetZeroAllocation enables Zero Allocation mode for the depacketizer +// By default the Depacketizers will allocate as they parse. These allocations +// are needed for Metadata and other optional values. If you don't need this information +// enabling SetZeroAllocation gives you higher performance at a reduced feature set. +func (d *videoDepacketizer) SetZeroAllocation(zeroAllocation bool) { + d.zeroAllocation = zeroAllocation +} diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 49c37da..3021fc0 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -208,6 +208,14 @@ func (p *H264Packet) IsDetectedFinalPacketInSequence(rtpPacketMarketBit bool) bo // Unmarshal parses the passed byte slice and stores the result in the H264Packet this method is called upon func (p *H264Packet) Unmarshal(payload []byte) ([]byte, error) { + if p.zeroAllocation { + return payload, nil + } + + return p.parseBody(payload) +} + +func (p *H264Packet) parseBody(payload []byte) ([]byte, error) { if len(payload) == 0 { return nil, fmt.Errorf("%w: %d <=0", errShortPacket, len(payload)) } diff --git a/depacketizer.go b/depacketizer.go index 0439a53..d10ad89 100644 --- a/depacketizer.go +++ b/depacketizer.go @@ -5,11 +5,15 @@ package rtp // Depacketizer depacketizes a RTP payload, removing any RTP specific data from the payload type Depacketizer interface { + // Unmarshal parses the RTP payload and returns media. + // Metadata may be stored on the Depacketizer itself Unmarshal(packet []byte) ([]byte, error) + // Checks if the packet is at the beginning of a partition. This // should return false if the result could not be determined, in // which case the caller will detect timestamp discontinuities. IsPartitionHead(payload []byte) bool + // Checks if the packet is at the end of a partition. This should // return false if the result could not be determined. IsPartitionTail(marker bool, payload []byte) bool From aa48ccf32248081ba80601bc0594329ec261386b Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Mon, 15 Apr 2024 18:38:21 +0200 Subject: [PATCH 094/102] Add ZeroAllocation support to AV1Packet Make parsing of the OBU list in AV1Packet optional. This enables a higher performance depacketizer, with a reduced feature set. --- codecs/av1_packet.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index 3a78b90..bcea690 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -134,6 +134,8 @@ type AV1Packet struct { // Each AV1 RTP Packet is a collection of OBU Elements. Each OBU Element may be a full OBU, or just a fragment of one. // AV1Frame provides the tools to construct a collection of OBUs from a collection of OBU Elements OBUElements [][]byte + + videoDepacketizer } // Unmarshal parses the passed byte slice and stores the result in the AV1Packet this method is called upon @@ -153,13 +155,26 @@ func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) { return nil, errIsKeyframeAndFragment } - currentIndex := uint(1) - p.OBUElements = [][]byte{} + if !p.zeroAllocation { + obuElements, err := p.parseBody(payload[1:]) + if err != nil { + return nil, err + } + p.OBUElements = obuElements + } + + return payload[1:], nil +} + +func (p *AV1Packet) parseBody(payload []byte) ([][]byte, error) { + if p.OBUElements != nil { + return p.OBUElements, nil + } + + obuElements := [][]byte{} - var ( - obuElementLength, bytesRead uint - err error - ) + var obuElementLength, bytesRead uint + currentIndex := uint(0) for i := 1; ; i++ { if currentIndex == uint(len(payload)) { break @@ -170,6 +185,7 @@ func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) { bytesRead = 0 obuElementLength = uint(len(payload)) - currentIndex } else { + var err error obuElementLength, bytesRead, err = obu.ReadLeb128(payload[currentIndex:]) if err != nil { return nil, err @@ -180,9 +196,9 @@ func (p *AV1Packet) Unmarshal(payload []byte) ([]byte, error) { if uint(len(payload)) < currentIndex+obuElementLength { return nil, errShortPacket } - p.OBUElements = append(p.OBUElements, payload[currentIndex:currentIndex+obuElementLength]) + obuElements = append(obuElements, payload[currentIndex:currentIndex+obuElementLength]) currentIndex += obuElementLength } - return payload[1:], nil + return obuElements, nil } From 12646b601d88f448fe5474c6ab9e746f99ae7447 Mon Sep 17 00:00:00 2001 From: Juliusz Chroboczek Date: Mon, 15 Apr 2024 20:59:18 +0200 Subject: [PATCH 095/102] Add tests for SetZeroAllocation Assert that H264, VP8, VP9 and AV1 don't do any allocation. At this time H265 does allocate. --- codecs/av1_packet_test.go | 1 + codecs/common_test.go | 227 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) diff --git a/codecs/av1_packet_test.go b/codecs/av1_packet_test.go index 2a65c4f..7df5f55 100644 --- a/codecs/av1_packet_test.go +++ b/codecs/av1_packet_test.go @@ -102,6 +102,7 @@ func TestAV1_Unmarshal_Error(t *testing.T) { } func TestAV1_Unmarshal(t *testing.T) { + // nolint: dupl av1Payload := []byte{ 0x68, 0x0c, 0x08, 0x00, 0x00, 0x00, 0x2c, 0xd6, 0xd3, 0x0c, 0xd5, 0x02, 0x00, 0x80, diff --git a/codecs/common_test.go b/codecs/common_test.go index 3487276..7f0e38a 100644 --- a/codecs/common_test.go +++ b/codecs/common_test.go @@ -23,3 +23,230 @@ func TestCommon_Min(t *testing.T) { t.Fatal("Error: 3 == 3") } } + +func TestZeroAllocations(t *testing.T) { + type unmarshaller interface { + Unmarshal(data []byte) ([]byte, error) + } + type tst struct { + packet unmarshaller + data []byte + } + tests := []tst{ + { + packet: &VP8Packet{}, + data: []byte{ + 0x00, 0x11, 0x22, 0x33, + 0x44, 0x55, 0x66, 0x90, + }, + }, { + packet: &VP9Packet{}, + data: []byte{0xA0, 0x02, 0x23, 0x01, 0xAA}, + }, { + packet: &H264Packet{}, + data: []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, + }, + }, { + packet: &AV1Packet{}, + // nolint: dupl + data: []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, + }, + }, + } + + type zeroAllocation interface { + SetZeroAllocation(zeroAllocation bool) + } + + for _, test := range tests { + allocs := testing.AllocsPerRun(10, func() { + if d, ok := test.packet.(zeroAllocation); ok { + d.SetZeroAllocation(true) + } + _, err := test.packet.Unmarshal(test.data) + if err != nil { + t.Errorf("Unmarshal failed: %v", err) + } + }) + + if allocs != 0 { + t.Errorf("%T: %v allocs", test.packet, allocs) + } + } +} From bc5124c9d0d0b027de8bf84f1d50bb39619b1728 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sat, 27 Apr 2024 20:37:37 +0200 Subject: [PATCH 096/102] Fix VP9 decoding on iOS The current implementation of the VP9 payloader produces payloads that are not compatible with iOS. This is because the payloader provides only the muxing strategy called "flexible mode". According to the VP9 RFC draft, there are two ways to wrap VP9 frames into RTP packets: the "flexible mode" and the "non-flexible mode", with the latter being the preferred one for live-streaming applications. In particular, all browsers encodes VP9 RTP packets in the "non-flexible mode", while iOS supports decoding RTP packets in this mode only, and this is probably a problem shared by other implementations. This patch improves the VP9 payloader by adding support for the "non-flexible mode". The "flexible mode" is retained and a flag is provided to perform the switch between the two modes. --- codecs/vp9/bits.go | 65 +++++++++++ codecs/vp9/header.go | 221 ++++++++++++++++++++++++++++++++++++++ codecs/vp9/header_test.go | 85 +++++++++++++++ codecs/vp9_packet.go | 152 +++++++++++++++++++++----- codecs/vp9_packet_test.go | 135 +++++++++++++++++------ 5 files changed, 598 insertions(+), 60 deletions(-) create mode 100644 codecs/vp9/bits.go create mode 100644 codecs/vp9/header.go create mode 100644 codecs/vp9/header_test.go diff --git a/codecs/vp9/bits.go b/codecs/vp9/bits.go new file mode 100644 index 0000000..a6a3c1f --- /dev/null +++ b/codecs/vp9/bits.go @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package vp9 + +import "errors" + +var errNotEnoughBits = errors.New("not enough bits") + +func hasSpace(buf []byte, pos int, n int) error { + if n > ((len(buf) * 8) - pos) { + return errNotEnoughBits + } + return nil +} + +func readFlag(buf []byte, pos *int) (bool, error) { + err := hasSpace(buf, *pos, 1) + if err != nil { + return false, err + } + + return readFlagUnsafe(buf, pos), nil +} + +func readFlagUnsafe(buf []byte, pos *int) bool { + b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01 + *pos++ + return b == 1 +} + +func readBits(buf []byte, pos *int, n int) (uint64, error) { + err := hasSpace(buf, *pos, n) + if err != nil { + return 0, err + } + + return readBitsUnsafe(buf, pos, n), nil +} + +func readBitsUnsafe(buf []byte, pos *int, n int) uint64 { + res := 8 - (*pos & 0x07) + if n < res { + v := uint64((buf[*pos>>0x03] >> (res - n)) & (1<>0x03] & (1<= 8 { + v = (v << 8) | uint64(buf[*pos>>0x03]) + *pos += 8 + n -= 8 + } + + if n > 0 { + v = (v << n) | uint64(buf[*pos>>0x03]>>(8-n)) + *pos += n + } + + return v +} diff --git a/codecs/vp9/header.go b/codecs/vp9/header.go new file mode 100644 index 0000000..30e15bc --- /dev/null +++ b/codecs/vp9/header.go @@ -0,0 +1,221 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +// Package vp9 contains a VP9 header parser. +package vp9 + +import ( + "errors" +) + +var ( + errInvalidFrameMarker = errors.New("invalid frame marker") + errWrongFrameSyncByte0 = errors.New("wrong frame_sync_byte_0") + errWrongFrameSyncByte1 = errors.New("wrong frame_sync_byte_1") + errWrongFrameSyncByte2 = errors.New("wrong frame_sync_byte_2") +) + +// HeaderColorConfig is the color_config member of an header. +type HeaderColorConfig struct { + TenOrTwelveBit bool + BitDepth uint8 + ColorSpace uint8 + ColorRange bool + SubsamplingX bool + SubsamplingY bool +} + +func (c *HeaderColorConfig) unmarshal(profile uint8, buf []byte, pos *int) error { + if profile >= 2 { + var err error + c.TenOrTwelveBit, err = readFlag(buf, pos) + if err != nil { + return err + } + + if c.TenOrTwelveBit { + c.BitDepth = 12 + } else { + c.BitDepth = 10 + } + } else { + c.BitDepth = 8 + } + + tmp, err := readBits(buf, pos, 3) + if err != nil { + return err + } + c.ColorSpace = uint8(tmp) + + if c.ColorSpace != 7 { + var err error + c.ColorRange, err = readFlag(buf, pos) + if err != nil { + return err + } + + if profile == 1 || profile == 3 { + err := hasSpace(buf, *pos, 3) + if err != nil { + return err + } + + c.SubsamplingX = readFlagUnsafe(buf, pos) + c.SubsamplingY = readFlagUnsafe(buf, pos) + *pos++ + } else { + c.SubsamplingX = true + c.SubsamplingY = true + } + } else { + c.ColorRange = true + + if profile == 1 || profile == 3 { + c.SubsamplingX = false + c.SubsamplingY = false + + err := hasSpace(buf, *pos, 1) + if err != nil { + return err + } + *pos++ + } + } + + return nil +} + +// HeaderFrameSize is the frame_size member of an header. +type HeaderFrameSize struct { + FrameWidthMinus1 uint16 + FrameHeightMinus1 uint16 +} + +func (s *HeaderFrameSize) unmarshal(buf []byte, pos *int) error { + err := hasSpace(buf, *pos, 32) + if err != nil { + return err + } + + s.FrameWidthMinus1 = uint16(readBitsUnsafe(buf, pos, 16)) + s.FrameHeightMinus1 = uint16(readBitsUnsafe(buf, pos, 16)) + return nil +} + +// Header is a VP9 Frame header. +// Specification: +// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf +type Header struct { + Profile uint8 + ShowExistingFrame bool + FrameToShowMapIdx uint8 + NonKeyFrame bool + ShowFrame bool + ErrorResilientMode bool + ColorConfig *HeaderColorConfig + FrameSize *HeaderFrameSize +} + +// Unmarshal decodes a Header. +func (h *Header) Unmarshal(buf []byte) error { + pos := 0 + + err := hasSpace(buf, pos, 4) + if err != nil { + return err + } + + frameMarker := readBitsUnsafe(buf, &pos, 2) + if frameMarker != 2 { + return errInvalidFrameMarker + } + + profileLowBit := uint8(readBitsUnsafe(buf, &pos, 1)) + profileHighBit := uint8(readBitsUnsafe(buf, &pos, 1)) + h.Profile = profileHighBit<<1 + profileLowBit + + if h.Profile == 3 { + err = hasSpace(buf, pos, 1) + if err != nil { + return err + } + pos++ + } + + h.ShowExistingFrame, err = readFlag(buf, &pos) + if err != nil { + return err + } + + if h.ShowExistingFrame { + var tmp uint64 + tmp, err = readBits(buf, &pos, 3) + if err != nil { + return err + } + h.FrameToShowMapIdx = uint8(tmp) + return nil + } + + err = hasSpace(buf, pos, 3) + if err != nil { + return err + } + + h.NonKeyFrame = readFlagUnsafe(buf, &pos) + h.ShowFrame = readFlagUnsafe(buf, &pos) + h.ErrorResilientMode = readFlagUnsafe(buf, &pos) + + if !h.NonKeyFrame { + err := hasSpace(buf, pos, 24) + if err != nil { + return err + } + + frameSyncByte0 := uint8(readBitsUnsafe(buf, &pos, 8)) + if frameSyncByte0 != 0x49 { + return errWrongFrameSyncByte0 + } + + frameSyncByte1 := uint8(readBitsUnsafe(buf, &pos, 8)) + if frameSyncByte1 != 0x83 { + return errWrongFrameSyncByte1 + } + + frameSyncByte2 := uint8(readBitsUnsafe(buf, &pos, 8)) + if frameSyncByte2 != 0x42 { + return errWrongFrameSyncByte2 + } + + h.ColorConfig = &HeaderColorConfig{} + err = h.ColorConfig.unmarshal(h.Profile, buf, &pos) + if err != nil { + return err + } + + h.FrameSize = &HeaderFrameSize{} + err = h.FrameSize.unmarshal(buf, &pos) + if err != nil { + return err + } + } + + return nil +} + +// Width returns the video width. +func (h Header) Width() uint16 { + if h.FrameSize == nil { + return 0 + } + return h.FrameSize.FrameWidthMinus1 + 1 +} + +// Height returns the video height. +func (h Header) Height() uint16 { + if h.FrameSize == nil { + return 0 + } + return h.FrameSize.FrameHeightMinus1 + 1 +} diff --git a/codecs/vp9/header_test.go b/codecs/vp9/header_test.go new file mode 100644 index 0000000..41fb46a --- /dev/null +++ b/codecs/vp9/header_test.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package vp9 + +import ( + "reflect" + "testing" +) + +func TestHeaderUnmarshal(t *testing.T) { + cases := []struct { + name string + byts []byte + sh Header + width uint16 + height uint16 + }{ + { + "chrome webrtc", + []byte{ + 0x82, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, + 0x34, 0x30, 0x38, 0x24, 0x1c, 0x19, 0x40, 0x18, + 0x03, 0x40, 0x5f, 0xb4, + }, + Header{ + ShowFrame: true, + ColorConfig: &HeaderColorConfig{ + BitDepth: 8, + SubsamplingX: true, + SubsamplingY: true, + }, + FrameSize: &HeaderFrameSize{ + FrameWidthMinus1: 1919, + FrameHeightMinus1: 803, + }, + }, + 1920, + 804, + }, + { + "vp9 sample", + []byte{ + 0x82, 0x49, 0x83, 0x42, 0x40, 0xef, 0xf0, 0x86, + 0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, + 0x00, 0x00, 0x00, 0x01, + }, + Header{ + ShowFrame: true, + ColorConfig: &HeaderColorConfig{ + BitDepth: 8, + ColorSpace: 2, + SubsamplingX: true, + SubsamplingY: true, + }, + FrameSize: &HeaderFrameSize{ + FrameWidthMinus1: 3839, + FrameHeightMinus1: 2159, + }, + }, + 3840, + 2160, + }, + } + + for _, ca := range cases { + t.Run(ca.name, func(t *testing.T) { + var sh Header + err := sh.Unmarshal(ca.byts) + if err != nil { + t.Fatal("unexpected error") + } + + if !reflect.DeepEqual(ca.sh, sh) { + t.Fatalf("expected %#+v, got %#+v", ca.sh, sh) + } + if ca.width != sh.Width() { + t.Fatalf("unexpected width") + } + if ca.height != sh.Height() { + t.Fatalf("unexpected height") + } + }) + } +} diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 6a6e0b0..98920be 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -5,6 +5,7 @@ package codecs import ( "github.com/pion/randutil" + "github.com/pion/rtp/codecs/vp9" ) // Use global random generator to properly seed by crypto grade random. @@ -12,24 +13,50 @@ var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:goch // VP9Payloader payloads VP9 packets type VP9Payloader struct { - pictureID uint16 - initialized bool + // whether to use flexible mode or non-flexible mode. + FlexibleMode bool // InitialPictureIDFn is a function that returns random initial picture ID. InitialPictureIDFn func() uint16 + + pictureID uint16 + initialized bool } const ( - 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 { + if !p.initialized { + if p.InitialPictureIDFn == nil { + p.InitialPictureIDFn = func() uint16 { + return uint16(globalMathRandomGenerator.Intn(0x7FFF)) + } + } + p.pictureID = p.InitialPictureIDFn() & 0x7FFF + p.initialized = true + } + + var payloads [][]byte + if p.FlexibleMode { + payloads = p.payloadFlexible(mtu, payload) + } else { + payloads = p.payloadNonFlexible(mtu, payload) + } + + p.pictureID++ + if p.pictureID >= 0x8000 { + p.pictureID = 0 + } + + return payloads +} + +func (p *VP9Payloader) payloadFlexible(mtu uint16, payload []byte) [][]byte { /* - * https://www.ietf.org/id/draft-ietf-payload-vp9-13.txt - * * Flexible mode (F=1) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ @@ -46,7 +73,45 @@ func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { * V: | SS | * | .. | * +-+-+-+-+-+-+-+-+ - * + */ + + headerSize := 3 + maxFragmentSize := int(mtu) - headerSize + payloadDataRemaining := len(payload) + payloadDataIndex := 0 + var payloads [][]byte + + if min(maxFragmentSize, payloadDataRemaining) <= 0 { + return [][]byte{} + } + + for payloadDataRemaining > 0 { + currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) + out := make([]byte, headerSize+currentFragmentSize) + + out[0] = 0x90 // F=1, I=1 + if payloadDataIndex == 0 { + out[0] |= 0x08 // B=1 + } + if payloadDataRemaining == currentFragmentSize { + out[0] |= 0x04 // E=1 + } + + out[1] = byte(p.pictureID>>8) | 0x80 + out[2] = byte(p.pictureID) + + copy(out[headerSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) + payloads = append(payloads, out) + + payloadDataRemaining -= currentFragmentSize + payloadDataIndex += currentFragmentSize + } + + return payloads +} + +func (p *VP9Payloader) payloadNonFlexible(mtu uint16, payload []byte) [][]byte { + /* * Non-flexible mode (F=0) * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ @@ -65,51 +130,80 @@ func (p *VP9Payloader) Payload(mtu uint16, payload []byte) [][]byte { * +-+-+-+-+-+-+-+-+ */ - if !p.initialized { - if p.InitialPictureIDFn == nil { - p.InitialPictureIDFn = func() uint16 { - return uint16(globalMathRandomGenerator.Intn(0x7FFF)) - } - } - p.pictureID = p.InitialPictureIDFn() & 0x7FFF - p.initialized = true - } - if payload == nil { + var h vp9.Header + err := h.Unmarshal(payload) + if err != 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 { + var headerSize int + if !h.NonKeyFrame && payloadDataIndex == 0 { + headerSize = 3 + 8 + } else { + headerSize = 3 + } + + maxFragmentSize := int(mtu) - headerSize currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) - out := make([]byte, vp9HeaderSize+currentFragmentSize) + if currentFragmentSize <= 0 { + return [][]byte{} + } + + out := make([]byte, headerSize+currentFragmentSize) + + out[0] = 0x80 | 0x01 // I=1, Z=1 - out[0] = 0x90 // F=1 I=1 + if h.NonKeyFrame { + out[0] |= 0x40 // P=1 + } if payloadDataIndex == 0 { out[0] |= 0x08 // B=1 } if payloadDataRemaining == currentFragmentSize { out[0] |= 0x04 // E=1 } + out[1] = byte(p.pictureID>>8) | 0x80 out[2] = byte(p.pictureID) - copy(out[vp9HeaderSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) + off := 3 + + if !h.NonKeyFrame && payloadDataIndex == 0 { + out[0] |= 0x02 // V=1 + out[off] = 0x10 | 0x08 // N_S=0, Y=1, G=1 + off++ + + width := h.Width() + out[off] = byte(width >> 8) + off++ + out[off] = byte(width & 0xFF) + off++ + + height := h.Height() + out[off] = byte(height >> 8) + off++ + out[off] = byte(height & 0xFF) + off++ + + out[off] = 0x01 // N_G=1 + off++ + + out[off] = 1<<4 | 1<<2 // TID=0, U=1, R=1 + off++ + + out[off] = 0x01 // P_DIFF=1 + } + + copy(out[headerSize:], payload[payloadDataIndex:payloadDataIndex+currentFragmentSize]) payloads = append(payloads, out) payloadDataRemaining -= currentFragmentSize payloadDataIndex += currentFragmentSize } - p.pictureID++ - if p.pictureID >= 0x8000 { - p.pictureID = 0 - } return payloads } diff --git a/codecs/vp9_packet_test.go b/codecs/vp9_packet_test.go index 97e176b..9591f22 100644 --- a/codecs/vp9_packet_test.go +++ b/codecs/vp9_packet_test.go @@ -5,7 +5,6 @@ package codecs import ( "errors" - "fmt" "math/rand" "reflect" "testing" @@ -223,62 +222,134 @@ func TestVP9Payloader_Payload(t *testing.T) { } cases := map[string]struct { - b [][]byte - mtu uint16 - res [][]byte + b [][]byte + flexible bool + mtu uint16 + res [][]byte }{ - "NilPayload": { - b: [][]byte{nil}, - mtu: 100, - res: [][]byte{}, + "flexible NilPayload": { + b: [][]byte{nil}, + flexible: true, + mtu: 100, + res: [][]byte{}, }, - "SmallMTU": { - b: [][]byte{{0x00, 0x00}}, - mtu: 1, - res: [][]byte{}, + "flexible SmallMTU": { + b: [][]byte{{0x00, 0x00}}, + flexible: true, + mtu: 1, + res: [][]byte{}, }, - "OnePacket": { - b: [][]byte{{0x01, 0x02}}, - mtu: 10, + "flexible OnePacket": { + b: [][]byte{{0x01, 0x02}}, + flexible: true, + mtu: 10, res: [][]byte{ {0x9C, rands[0][0], rands[0][1], 0x01, 0x02}, }, }, - "TwoPackets": { - b: [][]byte{{0x01, 0x02}}, - mtu: 4, + "flexible TwoPackets": { + b: [][]byte{{0x01, 0x02}}, + flexible: true, + 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, + "flexible ThreePackets": { + b: [][]byte{{0x01, 0x02, 0x03}}, + flexible: true, + 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, + "flexible TwoFramesFourPackets": { + b: [][]byte{{0x01, 0x02, 0x03}, {0x04}}, + flexible: true, + 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}, }, }, + "non-flexible NilPayload": { + b: [][]byte{nil}, + mtu: 100, + res: [][]byte{}, + }, + "non-flexible SmallMTU": { + b: [][]byte{{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}}, + mtu: 1, + res: [][]byte{}, + }, + "non-flexible OnePacket key frame": { + b: [][]byte{{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}}, + mtu: 20, + res: [][]byte{{ + 0x8f, 0xa1, 0xf4, 0x18, 0x07, 0x80, 0x03, 0x24, + 0x01, 0x14, 0x01, 0x82, 0x49, 0x83, 0x42, 0x00, + 0x77, 0xf0, 0x32, 0x34, + }}, + }, + "non-flexible TwoPackets key frame": { + b: [][]byte{{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}}, + mtu: 12, + res: [][]byte{ + { + 0x8b, 0xa1, 0xf4, 0x18, 0x07, 0x80, 0x03, 0x24, + 0x01, 0x14, 0x01, 0x82, + }, + { + 0x85, 0xa1, 0xf4, 0x49, 0x83, 0x42, 0x00, 0x77, + 0xf0, 0x32, 0x34, + }, + }, + }, + "non-flexible ThreePackets key frame": { + b: [][]byte{{ + 0x82, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, + 0x34, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, + }}, + mtu: 12, + res: [][]byte{ + { + 0x8b, 0xa1, 0xf4, 0x18, 0x07, 0x80, 0x03, 0x24, + 0x01, 0x14, 0x01, 0x82, + }, + { + 0x81, 0xa1, 0xf4, 0x49, 0x83, 0x42, 0x00, 0x77, + 0xf0, 0x32, 0x34, 0x01, + }, + { + 0x85, 0xa1, 0xf4, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, + }, + }, + }, + "non-flexible OnePacket non key frame": { + b: [][]byte{{0x86, 0x0, 0x40, 0x92, 0xe1, 0x31, 0x42, 0x8c, 0xc0, 0x40}}, + mtu: 20, + res: [][]byte{{ + 0xcd, 0xa1, 0xf4, 0x86, 0x00, 0x40, 0x92, 0xe1, + 0x31, 0x42, 0x8c, 0xc0, 0x40, + }}, + }, } + 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) { + t.Run(name, func(t *testing.T) { + pck := VP9Payloader{ + FlexibleMode: c.flexible, + InitialPictureIDFn: func() uint16 { + return uint16(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec + }, + } + res := [][]byte{} for _, b := range c.b { res = append(res, pck.Payload(c.mtu, b)...) @@ -288,8 +359,10 @@ func TestVP9Payloader_Payload(t *testing.T) { } }) } + t.Run("PictureIDOverflow", func(t *testing.T) { pck := VP9Payloader{ + FlexibleMode: true, InitialPictureIDFn: func() uint16 { return uint16(rand.New(rand.NewSource(0)).Int31n(0x7FFF)) //nolint:gosec }, From 0967ee9be20ea1da55274e2d1f7a2198a1cdc1c9 Mon Sep 17 00:00:00 2001 From: sirzooro Date: Sat, 6 Jul 2024 12:29:01 +0200 Subject: [PATCH 097/102] Fix RTP padding length validation Added validation of RTP padding length in received packets. Also check for zero padding length when marshaling. --- error.go | 2 ++ packet.go | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/error.go b/error.go index 4df5d2a..ac3ece4 100644 --- a/error.go +++ b/error.go @@ -21,4 +21,6 @@ var ( errRFC8285TwoByteHeaderSize = errors.New("header extension payload must be 255bytes or less for RFC 5285 two byte extensions") errRFC3550HeaderIDRange = errors.New("header extension id must be 0 for non-RFC 5285 extensions") + + errInvalidRTPPadding = errors.New("invalid RTP padding") ) diff --git a/packet.go b/packet.go index af88af3..61d341d 100644 --- a/packet.go +++ b/packet.go @@ -215,6 +215,9 @@ func (p *Packet) Unmarshal(buf []byte) error { end := len(buf) if p.Header.Padding { + if end <= n { + return errTooSmall + } p.PaddingSize = buf[end-1] end -= int(p.PaddingSize) } @@ -475,6 +478,10 @@ 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) { + if p.Header.Padding && p.PaddingSize == 0 { + return 0, errInvalidRTPPadding + } + n, err = p.Header.MarshalTo(buf) if err != nil { return 0, err From f7f1a0535c8b1d432452c38150f215568e5bbb33 Mon Sep 17 00:00:00 2001 From: Redi Kim Date: Thu, 2 May 2024 08:47:55 +0900 Subject: [PATCH 098/102] Add an index check to prevent out of index --- codecs/h264_packet.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 3021fc0..973a831 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -231,7 +231,11 @@ func (p *H264Packet) parseBody(payload []byte) ([]byte, error) { currOffset := int(stapaHeaderSize) result := []byte{} for currOffset < len(payload) { - naluSize := int(binary.BigEndian.Uint16(payload[currOffset:])) + naluSizeBytes := payload[currOffset:] + if len(naluSizeBytes) < stapaNALULengthSize { + break + } + naluSize := int(binary.BigEndian.Uint16(naluSizeBytes)) currOffset += stapaNALULengthSize if len(payload) < currOffset+naluSize { From 378ef6fd2293f19b402da4829ddce5258053e8f4 Mon Sep 17 00:00:00 2001 From: Redi Kim Date: Thu, 2 May 2024 12:18:13 +0900 Subject: [PATCH 099/102] Add test case for broken second nalu in STAP-A --- codecs/h264_packet_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/codecs/h264_packet_test.go b/codecs/h264_packet_test.go index 8e3b221..ca8a92c 100644 --- a/codecs/h264_packet_test.go +++ b/codecs/h264_packet_test.go @@ -109,6 +109,9 @@ func TestH264Packet_Unmarshal(t *testing.T) { 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} + singlePayloadWithBrokenSecondNALU := []byte{0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8, 0x00} + singlePayloadWithBrokenSecondNALUUnmarshaled := []byte{0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8} + singlePayloadWithBrokenSecondUnmarshaledAVC := []byte{0x00, 0x00, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11, 0xa8} incompleteSinglePayloadMultiNALU := []byte{0x78, 0x00, 0x0f, 0x67, 0x42, 0xc0, 0x1f, 0x1a, 0x32, 0x35, 0x01, 0x40, 0x7a, 0x40, 0x3c, 0x22, 0x11} @@ -189,6 +192,20 @@ func TestH264Packet_Unmarshal(t *testing.T) { } else if !reflect.DeepEqual(res, singlePayloadMultiNALUUnmarshaledAVC) { t.Fatal("Failed to unmarshal a single packet with multiple NALUs into avc stream") } + + res, err = pkt.Unmarshal(singlePayloadWithBrokenSecondNALU) + if err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(res, singlePayloadWithBrokenSecondNALUUnmarshaled) { + t.Fatal("Failed to unmarshal a single packet with broken second NALUs") + } + + res, err = avcPkt.Unmarshal(singlePayloadWithBrokenSecondNALU) + if err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual(res, singlePayloadWithBrokenSecondUnmarshaledAVC) { + t.Fatal("Failed to unmarshal a single packet with broken second NALUs into avc stream") + } } func TestH264IsPartitionHead(t *testing.T) { From 4aac9829f06c01901fdde9870d68c24611e29ce2 Mon Sep 17 00:00:00 2001 From: Kevin Caffrey Date: Tue, 6 Aug 2024 14:43:57 -0400 Subject: [PATCH 100/102] Fix rare SRTP loss decode failure As described in https://webrtc-review.googlesource.com/c/src/+/358360 there can be a problem when the sequence number starts near the rollover point and there is packet loss. As linked to in that issue, libsrtp recommends having the starting sequence number be less than 2^15 to avoid that problem. --- sequencer.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sequencer.go b/sequencer.go index 8ad2cfd..7aa7daa 100644 --- a/sequencer.go +++ b/sequencer.go @@ -4,7 +4,6 @@ package rtp import ( - "math" "sync" ) @@ -14,11 +13,18 @@ type Sequencer interface { RollOverCount() uint64 } +// maxInitialRandomSequenceNumber is the maximum value used for the initial sequence +// number when using NewRandomSequencer(). +// This uses only half the potential sequence number space to avoid issues decrypting +// SRTP when the sequence number starts near the rollover and there is packet loss. +// See https://webrtc-review.googlesource.com/c/src/+/358360 +const maxInitialRandomSequenceNumber = 1<<15 - 1 + // NewRandomSequencer returns a new sequencer starting from a random sequence // number func NewRandomSequencer() Sequencer { return &sequencer{ - sequenceNumber: uint16(globalMathRandomGenerator.Intn(math.MaxUint16)), + sequenceNumber: uint16(globalMathRandomGenerator.Intn(maxInitialRandomSequenceNumber)), } } From c442fc82cc1be9b9eda1e3d98179142a4810f6d3 Mon Sep 17 00:00:00 2001 From: Pion <59523206+pionbot@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:18:44 +0000 Subject: [PATCH 101/102] Update CI configs to v0.11.15 Update lint scripts and CI configs. --- .github/workflows/test.yaml | 6 +++--- .golangci.yml | 8 ++++---- codecs/av1_packet.go | 2 +- codecs/common.go | 2 +- codecs/common_test.go | 6 +++--- codecs/h264_packet.go | 4 ++-- codecs/vp8_packet.go | 4 ++-- codecs/vp9_packet.go | 6 +++--- header_extension.go | 2 +- packet.go | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 08e4272..b024289 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -23,7 +23,7 @@ jobs: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: - go: ["1.22", "1.21"] # auto-update/supported-go-version-list + go: ["1.23", "1.22"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -33,7 +33,7 @@ jobs: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: - go: ["1.22", "1.21"] # auto-update/supported-go-version-list + go: ["1.23", "1.22"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} @@ -41,5 +41,5 @@ jobs: test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: - go-version: "1.22" # auto-update/latest-go-version + go-version: "1.23" # auto-update/latest-go-version secrets: inherit diff --git a/.golangci.yml b/.golangci.yml index e06de4d..a3235be 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT +run: + timeout: 5m + linters-settings: govet: enable: @@ -48,7 +51,7 @@ linters: - 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 + - err113 # 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 @@ -83,17 +86,14 @@ linters: - depguard # Go linter that checks if package imports are in a list of acceptable packages - containedctx # containedctx is a linter that detects struct contained context.Context field - cyclop # checks function and package cyclomatic complexity - - exhaustivestruct # Checks if all struct's fields are initialized - funlen # Tool for detection of long functions - gocyclo # Computes and checks the cyclomatic complexity of functions - godot # Check if comments end in a period - gomnd # An analyzer to detect magic numbers. - - ifshort # Checks that your code uses short syntax for if-statements whenever possible - ireturn # Accept Interfaces, Return Concrete Types - lll # Reports long lines - maintidx # maintidx measures the maintainability index of each function. - makezero # Finds slice declarations with non-zero initial length - - maligned # Tool to detect Go structs that would take less memory if their fields were sorted - nakedret # Finds naked returns in functions greater than a specified function length - nestif # Reports deeply nested if statements - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity diff --git a/codecs/av1_packet.go b/codecs/av1_packet.go index bcea690..84c6c99 100644 --- a/codecs/av1_packet.go +++ b/codecs/av1_packet.go @@ -61,7 +61,7 @@ func (p *AV1Payloader) Payload(mtu uint16, payload []byte) (payloads [][]byte) { metadataSize += leb128Size + len(p.sequenceHeader) } - out := make([]byte, min(int(mtu), payloadDataRemaining+metadataSize)) + out := make([]byte, minInt(int(mtu), payloadDataRemaining+metadataSize)) outOffset := av1PayloaderHeadersize out[0] = obuCount << wBitshift diff --git a/codecs/common.go b/codecs/common.go index e807a0a..5b7dd14 100644 --- a/codecs/common.go +++ b/codecs/common.go @@ -3,7 +3,7 @@ package codecs -func min(a, b int) int { +func minInt(a, b int) int { if a < b { return a } diff --git a/codecs/common_test.go b/codecs/common_test.go index 7f0e38a..4c1f393 100644 --- a/codecs/common_test.go +++ b/codecs/common_test.go @@ -8,17 +8,17 @@ import ( ) func TestCommon_Min(t *testing.T) { - res := min(1, -1) + res := minInt(1, -1) if res != -1 { t.Fatal("Error: -1 < 1") } - res = min(1, 2) + res = minInt(1, 2) if res != 1 { t.Fatal("Error: 1 < 2") } - res = min(3, 3) + res = minInt(3, 3) if res != 3 { t.Fatal("Error: 3 == 3") } diff --git a/codecs/h264_packet.go b/codecs/h264_packet.go index 973a831..227cb9d 100644 --- a/codecs/h264_packet.go +++ b/codecs/h264_packet.go @@ -138,12 +138,12 @@ func (p *H264Payloader) Payload(mtu uint16, payload []byte) [][]byte { naluLength := len(nalu) - naluIndex naluRemaining := naluLength - if min(maxFragmentSize, naluRemaining) <= 0 { + if minInt(maxFragmentSize, naluRemaining) <= 0 { return } for naluRemaining > 0 { - currentFragmentSize := min(maxFragmentSize, naluRemaining) + currentFragmentSize := minInt(maxFragmentSize, naluRemaining) out := make([]byte, fuaHeaderSize+currentFragmentSize) // +---------------+ diff --git a/codecs/vp8_packet.go b/codecs/vp8_packet.go index 4ddd15b..87560ab 100644 --- a/codecs/vp8_packet.go +++ b/codecs/vp8_packet.go @@ -56,12 +56,12 @@ func (p *VP8Payloader) Payload(mtu uint16, payload []byte) [][]byte { var payloads [][]byte // Make sure the fragment/payload size is correct - if min(maxFragmentSize, payloadDataRemaining) <= 0 { + if minInt(maxFragmentSize, payloadDataRemaining) <= 0 { return payloads } first := true for payloadDataRemaining > 0 { - currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) + currentFragmentSize := minInt(maxFragmentSize, payloadDataRemaining) out := make([]byte, usingHeaderSize+currentFragmentSize) if first { diff --git a/codecs/vp9_packet.go b/codecs/vp9_packet.go index 98920be..e2ffab7 100644 --- a/codecs/vp9_packet.go +++ b/codecs/vp9_packet.go @@ -81,12 +81,12 @@ func (p *VP9Payloader) payloadFlexible(mtu uint16, payload []byte) [][]byte { payloadDataIndex := 0 var payloads [][]byte - if min(maxFragmentSize, payloadDataRemaining) <= 0 { + if minInt(maxFragmentSize, payloadDataRemaining) <= 0 { return [][]byte{} } for payloadDataRemaining > 0 { - currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) + currentFragmentSize := minInt(maxFragmentSize, payloadDataRemaining) out := make([]byte, headerSize+currentFragmentSize) out[0] = 0x90 // F=1, I=1 @@ -149,7 +149,7 @@ func (p *VP9Payloader) payloadNonFlexible(mtu uint16, payload []byte) [][]byte { } maxFragmentSize := int(mtu) - headerSize - currentFragmentSize := min(maxFragmentSize, payloadDataRemaining) + currentFragmentSize := minInt(maxFragmentSize, payloadDataRemaining) if currentFragmentSize <= 0 { return [][]byte{} } diff --git a/header_extension.go b/header_extension.go index fe54215..cdb16bd 100644 --- a/header_extension.go +++ b/header_extension.go @@ -163,7 +163,7 @@ type TwoByteHeaderExtension struct { // Set sets the extension payload for the specified ID. func (e *TwoByteHeaderExtension) Set(id uint8, buf []byte) error { - if id < 1 || id > 255 { + if id < 1 { return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id) } if len(buf) > 255 { diff --git a/packet.go b/packet.go index 61d341d..e74d48d 100644 --- a/packet.go +++ b/packet.go @@ -383,7 +383,7 @@ func (h *Header) SetExtension(id uint8, payload []byte) error { //nolint:gocogni } // RFC 8285 RTP Two Byte Header Extension case extensionProfileTwoByte: - if id < 1 || id > 255 { + if id < 1 { return fmt.Errorf("%w actual(%d)", errRFC8285TwoByteHeaderIDRange, id) } if len(payload) > 255 { From a21194ecfb5362261a0dc4af1f68e4a8944df345 Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Mon, 26 Aug 2024 11:32:47 -0400 Subject: [PATCH 102/102] Update go.mod version to 1.20 Relates to pion/webrtc#2869 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 753a4d7..3b37fe0 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/pion/rtp -go 1.19 +go 1.20 require github.com/pion/randutil v0.1.0