mirror of
https://github.com/bluenviron/mediacommon.git
synced 2025-09-26 21:01:14 +08:00
initial commit
This commit is contained in:
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
50
.github/workflows/issue-lock.yml
vendored
Normal file
50
.github/workflows/issue-lock.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: issue-lock
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '40 15 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
issue-lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const { repo: { owner, repo } } = context;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
for await (const res of github.paginate.iterator(
|
||||||
|
github.rest.issues.listForRepo, {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
state: 'closed',
|
||||||
|
})) {
|
||||||
|
for (const issue of res.data) {
|
||||||
|
if (issue.locked) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((now - new Date(issue.updated_at)) < 1000*60*60*24*31*6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
body: 'This issue is being locked automatically because it has been closed for more than 6 months.\n'
|
||||||
|
+ 'Please open a new issue in case you encounter a similar problem.',
|
||||||
|
});
|
||||||
|
|
||||||
|
github.rest.issues.lock({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
36
.github/workflows/lint.yml
vendored
Normal file
36
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
golangci-lint:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: "1.19"
|
||||||
|
|
||||||
|
- uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
version: v1.50.1
|
||||||
|
|
||||||
|
go-mod-tidy:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: "1.20"
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
go mod tidy
|
||||||
|
git diff --exit-code
|
30
.github/workflows/test.yml
vendored
Normal file
30
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: ["1.18", "1.19", "1.20"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- run: sudo apt update && sudo apt install -y libavformat-dev libswscale-dev
|
||||||
|
|
||||||
|
- run: make test-nodocker
|
||||||
|
|
||||||
|
- if: matrix.go == '1.19'
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/coverage*.txt
|
28
.golangci.yml
Normal file
28
.golangci.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- bodyclose
|
||||||
|
- dupl
|
||||||
|
- exportloopref
|
||||||
|
- gochecknoinits
|
||||||
|
- gocritic
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- misspell
|
||||||
|
- lll
|
||||||
|
- prealloc
|
||||||
|
- revive
|
||||||
|
- unconvert
|
||||||
|
- whitespace
|
||||||
|
disable:
|
||||||
|
- errcheck
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- fieldalignment
|
||||||
|
- reflectvaluecompare
|
||||||
|
- shadow
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 aler9
|
||||||
|
|
||||||
|
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.
|
25
Makefile
Normal file
25
Makefile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
BASE_IMAGE = golang:1.20.1-alpine3.17
|
||||||
|
LINT_IMAGE = golangci/golangci-lint:v1.50.1
|
||||||
|
|
||||||
|
.PHONY: $(shell ls)
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "usage: make [action]"
|
||||||
|
@echo ""
|
||||||
|
@echo "available actions:"
|
||||||
|
@echo ""
|
||||||
|
@echo " mod-tidy run go mod tidy"
|
||||||
|
@echo " format format source files"
|
||||||
|
@echo " test run tests"
|
||||||
|
@echo " test-highlevel run high-level tests"
|
||||||
|
@echo " lint run linter"
|
||||||
|
@echo " bench run benchmarks"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
blank :=
|
||||||
|
define NL
|
||||||
|
|
||||||
|
$(blank)
|
||||||
|
endef
|
||||||
|
|
||||||
|
include scripts/*.mk
|
16
README.md
Normal file
16
README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# mediabase
|
||||||
|
|
||||||
|
[](https://github.com/bluenviron/mediabase/actions?query=workflow:test)
|
||||||
|
[](https://github.com/bluenviron/mediabase/actions?query=workflow:lint)
|
||||||
|
[](https://goreportcard.com/report/github.com/bluenviron/mediabase)
|
||||||
|
[](https://app.codecov.io/gh/bluenviron/mediabase/branch/main)
|
||||||
|
[](https://pkg.go.dev/github.com/bluenviron/mediabase/v3#pkg-index)
|
||||||
|
|
||||||
|
Definitions and functions shared between gortsplib, gohlslib and mediamtx.
|
||||||
|
|
||||||
|
In particular:
|
||||||
|
|
||||||
|
* [Codec definitions](pkg/codecs)
|
||||||
|
* [Codec utilities](pkg/codecs)
|
||||||
|
* [Format utilities](pkg/formats)
|
||||||
|
* [Bit reader and writer](pkg/bits)
|
11
go.mod
Normal file
11
go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module github.com/bluenviron/mediabase
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.8.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
17
go.sum
Normal file
17
go.sum
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
123
pkg/bits/read.go
Normal file
123
pkg/bits/read.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// Package bits contains functions to read/write bits from/to buffers.
|
||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasSpace checks whether buffer has space for N bits.
|
||||||
|
func HasSpace(buf []byte, pos int, n int) error {
|
||||||
|
if n > ((len(buf) * 8) - pos) {
|
||||||
|
return fmt.Errorf("not enough bits")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBits reads N bits.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBitsUnsafe reads N bits.
|
||||||
|
func ReadBitsUnsafe(buf []byte, pos *int, n int) uint64 {
|
||||||
|
v := uint64(0)
|
||||||
|
|
||||||
|
res := 8 - (*pos & 0x07)
|
||||||
|
if n < res {
|
||||||
|
v := uint64((buf[*pos>>0x03] >> (res - n)) & (1<<n - 1))
|
||||||
|
*pos += n
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
v = (v << res) | uint64(buf[*pos>>0x03]&(1<<res-1))
|
||||||
|
*pos += res
|
||||||
|
n -= res
|
||||||
|
|
||||||
|
for n >= 8 {
|
||||||
|
v = (v << 8) | uint64(buf[*pos>>0x03])
|
||||||
|
*pos += 8
|
||||||
|
n -= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
v = (v << n) | uint64(buf[*pos>>0x03]>>(8-n))
|
||||||
|
*pos += n
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadGolombUnsigned reads an unsigned golomb-encoded value.
|
||||||
|
func ReadGolombUnsigned(buf []byte, pos *int) (uint32, error) {
|
||||||
|
buflen := len(buf)
|
||||||
|
leadingZeroBits := uint32(0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if (buflen*8 - *pos) == 0 {
|
||||||
|
return 0, fmt.Errorf("not enough bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01
|
||||||
|
*pos++
|
||||||
|
if b != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
leadingZeroBits++
|
||||||
|
if leadingZeroBits > 32 {
|
||||||
|
return 0, fmt.Errorf("invalid value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buflen*8 - *pos) < int(leadingZeroBits) {
|
||||||
|
return 0, fmt.Errorf("not enough bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
codeNum := uint32(0)
|
||||||
|
|
||||||
|
for n := leadingZeroBits; n > 0; n-- {
|
||||||
|
b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01
|
||||||
|
*pos++
|
||||||
|
codeNum |= uint32(b) << (n - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeNum = (1 << leadingZeroBits) - 1 + codeNum
|
||||||
|
|
||||||
|
return codeNum, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadGolombSigned reads a signed golomb-encoded value.
|
||||||
|
func ReadGolombSigned(buf []byte, pos *int) (int32, error) {
|
||||||
|
v, err := ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vi := int32(v)
|
||||||
|
if (vi & 0x01) != 0 {
|
||||||
|
return (vi + 1) / 2, nil
|
||||||
|
}
|
||||||
|
return -vi / 2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFlag reads a boolean flag.
|
||||||
|
func ReadFlag(buf []byte, pos *int) (bool, error) {
|
||||||
|
err := HasSpace(buf, *pos, 1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReadFlagUnsafe(buf, pos), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFlagUnsafe reads a boolean flag.
|
||||||
|
func ReadFlagUnsafe(buf []byte, pos *int) bool {
|
||||||
|
b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01
|
||||||
|
*pos++
|
||||||
|
return b == 1
|
||||||
|
}
|
88
pkg/bits/read_test.go
Normal file
88
pkg/bits/read_test.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadBits(t *testing.T) {
|
||||||
|
buf := []byte{0xA8, 0xC7, 0xD6, 0xAA, 0xBB, 0x10}
|
||||||
|
pos := 0
|
||||||
|
v, _ := ReadBits(buf, &pos, 6)
|
||||||
|
require.Equal(t, uint64(0x2a), v)
|
||||||
|
v, _ = ReadBits(buf, &pos, 6)
|
||||||
|
require.Equal(t, uint64(0x0c), v)
|
||||||
|
v, _ = ReadBits(buf, &pos, 6)
|
||||||
|
require.Equal(t, uint64(0x1f), v)
|
||||||
|
v, _ = ReadBits(buf, &pos, 8)
|
||||||
|
require.Equal(t, uint64(0x5a), v)
|
||||||
|
v, _ = ReadBits(buf, &pos, 20)
|
||||||
|
require.Equal(t, uint64(0xaaec4), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadBitsError(t *testing.T) {
|
||||||
|
buf := []byte{0xA8}
|
||||||
|
pos := 0
|
||||||
|
_, err := ReadBits(buf, &pos, 6)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = ReadBits(buf, &pos, 6)
|
||||||
|
require.EqualError(t, err, "not enough bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadGolombUnsigned(t *testing.T) {
|
||||||
|
buf := []byte{0x38}
|
||||||
|
pos := 0
|
||||||
|
v, _ := ReadGolombUnsigned(buf, &pos)
|
||||||
|
require.Equal(t, uint32(6), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadGolombUnsignedErrors(t *testing.T) {
|
||||||
|
buf := []byte{0x00}
|
||||||
|
pos := 0
|
||||||
|
_, err := ReadGolombUnsigned(buf, &pos)
|
||||||
|
require.EqualError(t, err, "not enough bits")
|
||||||
|
|
||||||
|
buf = []byte{0x00, 0x01}
|
||||||
|
pos = 0
|
||||||
|
_, err = ReadGolombUnsigned(buf, &pos)
|
||||||
|
require.EqualError(t, err, "not enough bits")
|
||||||
|
|
||||||
|
buf = []byte{0x00, 0x00, 0x00, 0x00, 0x01}
|
||||||
|
pos = 0
|
||||||
|
_, err = ReadGolombUnsigned(buf, &pos)
|
||||||
|
require.EqualError(t, err, "invalid value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadGolombSigned(t *testing.T) {
|
||||||
|
buf := []byte{0x38}
|
||||||
|
pos := 0
|
||||||
|
v, _ := ReadGolombSigned(buf, &pos)
|
||||||
|
require.Equal(t, int32(-3), v)
|
||||||
|
|
||||||
|
buf = []byte{0b00100100}
|
||||||
|
pos = 0
|
||||||
|
v, _ = ReadGolombSigned(buf, &pos)
|
||||||
|
require.Equal(t, int32(2), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadGolombSignedErrors(t *testing.T) {
|
||||||
|
buf := []byte{0x00}
|
||||||
|
pos := 0
|
||||||
|
_, err := ReadGolombSigned(buf, &pos)
|
||||||
|
require.EqualError(t, err, "not enough bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadFlag(t *testing.T) {
|
||||||
|
buf := []byte{0xFF}
|
||||||
|
pos := 0
|
||||||
|
v, _ := ReadFlag(buf, &pos)
|
||||||
|
require.Equal(t, true, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadFlagError(t *testing.T) {
|
||||||
|
buf := []byte{}
|
||||||
|
pos := 0
|
||||||
|
_, err := ReadFlag(buf, &pos)
|
||||||
|
require.EqualError(t, err, "not enough bits")
|
||||||
|
}
|
26
pkg/bits/write.go
Normal file
26
pkg/bits/write.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package bits
|
||||||
|
|
||||||
|
// WriteBits writes N bits.
|
||||||
|
func WriteBits(buf []byte, pos *int, bits uint64, n int) {
|
||||||
|
res := 8 - (*pos & 0x07)
|
||||||
|
if n < res {
|
||||||
|
buf[*pos>>0x03] |= byte(bits << (res - n))
|
||||||
|
*pos += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[*pos>>3] |= byte(bits >> (n - res))
|
||||||
|
*pos += res
|
||||||
|
n -= res
|
||||||
|
|
||||||
|
for n >= 8 {
|
||||||
|
buf[*pos>>3] = byte(bits >> (n - 8))
|
||||||
|
*pos += 8
|
||||||
|
n -= 8
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
buf[*pos>>3] = byte((bits & (1<<n - 1)) << (8 - n))
|
||||||
|
*pos += n
|
||||||
|
}
|
||||||
|
}
|
18
pkg/bits/write_test.go
Normal file
18
pkg/bits/write_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package bits
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteBits(t *testing.T) {
|
||||||
|
buf := make([]byte, 6)
|
||||||
|
pos := 0
|
||||||
|
WriteBits(buf, &pos, uint64(0x2a), 6)
|
||||||
|
WriteBits(buf, &pos, uint64(0x0c), 6)
|
||||||
|
WriteBits(buf, &pos, uint64(0x1f), 6)
|
||||||
|
WriteBits(buf, &pos, uint64(0x5a), 8)
|
||||||
|
WriteBits(buf, &pos, uint64(0xaaec4), 20)
|
||||||
|
require.Equal(t, []byte{0xA8, 0xC7, 0xD6, 0xAA, 0xBB, 0x10}, buf)
|
||||||
|
}
|
2
pkg/codecs/codecs.go
Normal file
2
pkg/codecs/codecs.go
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Package codecs contains codec definitions and utilities.
|
||||||
|
package codecs
|
134
pkg/codecs/h264/annexb.go
Normal file
134
pkg/codecs/h264/annexb.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AnnexBUnmarshal decodes NALUs from the Annex-B stream format.
|
||||||
|
func AnnexBUnmarshal(byts []byte) ([][]byte, error) {
|
||||||
|
bl := len(byts)
|
||||||
|
initZeroCount := 0
|
||||||
|
start := 0
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for {
|
||||||
|
if start >= bl || start >= 4 {
|
||||||
|
return nil, fmt.Errorf("initial delimiter not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch initZeroCount {
|
||||||
|
case 0, 1:
|
||||||
|
if byts[start] != 0 {
|
||||||
|
return nil, fmt.Errorf("initial delimiter not found")
|
||||||
|
}
|
||||||
|
initZeroCount++
|
||||||
|
|
||||||
|
case 2, 3:
|
||||||
|
switch byts[start] {
|
||||||
|
case 1:
|
||||||
|
start++
|
||||||
|
break outer
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("initial delimiter not found")
|
||||||
|
}
|
||||||
|
initZeroCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
|
||||||
|
zeroCount := 0
|
||||||
|
n := 0
|
||||||
|
|
||||||
|
for i := start; i < bl; i++ {
|
||||||
|
switch byts[i] {
|
||||||
|
case 0:
|
||||||
|
zeroCount++
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if zeroCount == 2 || zeroCount == 3 {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
zeroCount = 0
|
||||||
|
|
||||||
|
default:
|
||||||
|
zeroCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n + 1) > MaxNALUsPerGroup {
|
||||||
|
return nil, fmt.Errorf("NALU count (%d) exceeds maximum allowed (%d)",
|
||||||
|
n+1, MaxNALUsPerGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([][]byte, n+1)
|
||||||
|
pos := 0
|
||||||
|
start = initZeroCount + 1
|
||||||
|
zeroCount = 0
|
||||||
|
delimStart := 0
|
||||||
|
|
||||||
|
for i := start; i < bl; i++ {
|
||||||
|
switch byts[i] {
|
||||||
|
case 0:
|
||||||
|
if zeroCount == 0 {
|
||||||
|
delimStart = i
|
||||||
|
}
|
||||||
|
zeroCount++
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
if zeroCount == 2 || zeroCount == 3 {
|
||||||
|
l := delimStart - start
|
||||||
|
if l == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid NALU")
|
||||||
|
}
|
||||||
|
if l > MaxNALUSize {
|
||||||
|
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", l, MaxNALUSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[pos] = byts[start:delimStart]
|
||||||
|
pos++
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
zeroCount = 0
|
||||||
|
|
||||||
|
default:
|
||||||
|
zeroCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l := bl - start
|
||||||
|
if l == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid NALU")
|
||||||
|
}
|
||||||
|
if l > MaxNALUSize {
|
||||||
|
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", l, MaxNALUSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret[pos] = byts[start:bl]
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func annexBMarshalSize(nalus [][]byte) int {
|
||||||
|
n := 0
|
||||||
|
for _, nalu := range nalus {
|
||||||
|
n += 4 + len(nalu)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnexBMarshal encodes NALUs into the Annex-B stream format.
|
||||||
|
func AnnexBMarshal(nalus [][]byte) ([]byte, error) {
|
||||||
|
buf := make([]byte, annexBMarshalSize(nalus))
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
for _, nalu := range nalus {
|
||||||
|
pos += copy(buf[pos:], []byte{0x00, 0x00, 0x00, 0x01})
|
||||||
|
pos += copy(buf[pos:], nalu)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
127
pkg/codecs/h264/annexb_test.go
Normal file
127
pkg/codecs/h264/annexb_test.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var casesAnnexB = []struct {
|
||||||
|
name string
|
||||||
|
encin []byte
|
||||||
|
encout []byte
|
||||||
|
dec [][]byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"2 zeros",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x01, 0xaa, 0xbb, 0x00, 0x00, 0x01,
|
||||||
|
0xcc, 0xdd, 0x00, 0x00, 0x01, 0xee, 0xff,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xcc, 0xdd,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xee, 0xff,
|
||||||
|
},
|
||||||
|
[][]byte{
|
||||||
|
{0xaa, 0xbb},
|
||||||
|
{0xcc, 0xdd},
|
||||||
|
{0xee, 0xff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"3 zeros",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xcc, 0xdd,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xee, 0xff,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xcc, 0xdd,
|
||||||
|
0x00, 0x00, 0x00, 0x01, 0xee, 0xff,
|
||||||
|
},
|
||||||
|
[][]byte{
|
||||||
|
{0xaa, 0xbb},
|
||||||
|
{0xcc, 0xdd},
|
||||||
|
{0xee, 0xff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// used by Apple inside HLS test streams
|
||||||
|
"2 or 3 zeros",
|
||||||
|
[]byte{
|
||||||
|
0, 0, 0, 1, 9, 240,
|
||||||
|
0, 0, 0, 1, 39, 66, 224, 21, 169, 24, 60, 23, 252, 184, 3, 80, 96, 16, 107, 108, 43, 94, 247, 192, 64,
|
||||||
|
0, 0, 0, 1, 40, 222, 9, 200,
|
||||||
|
0, 0, 1, 6, 0, 7, 131, 236, 119, 0, 0, 0, 0, 1, 3, 0, 64, 128,
|
||||||
|
0, 0, 1, 6, 5, 17, 3, 135, 244, 78, 205, 10, 75, 220, 161, 148, 58, 195, 212, 155, 23, 31, 0, 128,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0, 0, 0, 1, 9, 240,
|
||||||
|
0, 0, 0, 1, 39, 66, 224, 21, 169, 24, 60, 23, 252, 184, 3, 80, 96, 16, 107, 108, 43, 94, 247, 192, 64,
|
||||||
|
0, 0, 0, 1, 40, 222, 9, 200,
|
||||||
|
0, 0, 0, 1, 6, 0, 7, 131, 236, 119, 0, 0, 0, 0, 1, 3, 0, 64, 128,
|
||||||
|
0, 0, 0, 1, 6, 5, 17, 3, 135, 244, 78, 205, 10, 75, 220, 161, 148, 58, 195, 212, 155, 23, 31, 0, 128,
|
||||||
|
},
|
||||||
|
[][]byte{
|
||||||
|
{9, 240},
|
||||||
|
{39, 66, 224, 21, 169, 24, 60, 23, 252, 184, 3, 80, 96, 16, 107, 108, 43, 94, 247, 192, 64},
|
||||||
|
{40, 222, 9, 200},
|
||||||
|
{6, 0, 7, 131, 236, 119, 0, 0, 0, 0, 1, 3, 0, 64, 128},
|
||||||
|
{6, 5, 17, 3, 135, 244, 78, 205, 10, 75, 220, 161, 148, 58, 195, 212, 155, 23, 31, 0, 128},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnexBUnmarshal(t *testing.T) {
|
||||||
|
for _, ca := range casesAnnexB {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
dec, err := AnnexBUnmarshal(ca.encin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ca.dec, dec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnexBMarshal(t *testing.T) {
|
||||||
|
for _, ca := range casesAnnexB {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
enc, err := AnnexBMarshal(ca.dec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ca.encout, enc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAnnexBUnmarshal(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
AnnexBUnmarshal([]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x00, 0x00, 0x00, 0x01,
|
||||||
|
0x01, 0x02, 0x03, 0x04,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzAnnexBUnmarshal(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte) {
|
||||||
|
AnnexBUnmarshal(b)
|
||||||
|
})
|
||||||
|
}
|
74
pkg/codecs/h264/avcc.go
Normal file
74
pkg/codecs/h264/avcc.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AVCCUnmarshal decodes NALUs from the AVCC stream format.
|
||||||
|
func AVCCUnmarshal(buf []byte) ([][]byte, error) {
|
||||||
|
bl := len(buf)
|
||||||
|
pos := 0
|
||||||
|
var ret [][]byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
if (bl - pos) < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid length")
|
||||||
|
}
|
||||||
|
|
||||||
|
l := int(uint32(buf[pos])<<24 | uint32(buf[pos+1])<<16 | uint32(buf[pos+2])<<8 | uint32(buf[pos+3]))
|
||||||
|
pos += 4
|
||||||
|
|
||||||
|
if l == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid NALU")
|
||||||
|
}
|
||||||
|
|
||||||
|
if l > MaxNALUSize {
|
||||||
|
return nil, fmt.Errorf("NALU size (%d) is too big (maximum is %d)", l, MaxNALUSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(ret) + 1) > MaxNALUsPerGroup {
|
||||||
|
return nil, fmt.Errorf("NALU count (%d) exceeds maximum allowed (%d)",
|
||||||
|
len(ret)+1, MaxNALUsPerGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bl - pos) < l {
|
||||||
|
return nil, fmt.Errorf("invalid length")
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append(ret, buf[pos:pos+l])
|
||||||
|
pos += l
|
||||||
|
|
||||||
|
if (bl - pos) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func avccMarshalSize(nalus [][]byte) int {
|
||||||
|
n := 0
|
||||||
|
for _, nalu := range nalus {
|
||||||
|
n += 4 + len(nalu)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// AVCCMarshal encodes NALUs into the AVCC stream format.
|
||||||
|
func AVCCMarshal(nalus [][]byte) ([]byte, error) {
|
||||||
|
buf := make([]byte, avccMarshalSize(nalus))
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
for _, nalu := range nalus {
|
||||||
|
naluLen := len(nalu)
|
||||||
|
buf[pos] = byte(naluLen >> 24)
|
||||||
|
buf[pos+1] = byte(naluLen >> 16)
|
||||||
|
buf[pos+2] = byte(naluLen >> 8)
|
||||||
|
buf[pos+3] = byte(naluLen)
|
||||||
|
pos += 4
|
||||||
|
|
||||||
|
pos += copy(buf[pos:], nalu)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
69
pkg/codecs/h264/avcc_test.go
Normal file
69
pkg/codecs/h264/avcc_test.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var casesAVCC = []struct {
|
||||||
|
name string
|
||||||
|
enc []byte
|
||||||
|
dec [][]byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"single",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x03,
|
||||||
|
0xaa, 0xbb, 0xcc,
|
||||||
|
},
|
||||||
|
[][]byte{
|
||||||
|
{0xaa, 0xbb, 0xcc},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multiple",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00, 0x02,
|
||||||
|
0xaa, 0xbb,
|
||||||
|
0x00, 0x00, 0x00, 0x02,
|
||||||
|
0xcc, 0xdd,
|
||||||
|
0x00, 0x00, 0x00, 0x02,
|
||||||
|
0xee, 0xff,
|
||||||
|
},
|
||||||
|
[][]byte{
|
||||||
|
{0xaa, 0xbb},
|
||||||
|
{0xcc, 0xdd},
|
||||||
|
{0xee, 0xff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAVCCUnmarshal(t *testing.T) {
|
||||||
|
for _, ca := range casesAVCC {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
dec, err := AVCCUnmarshal(ca.enc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ca.dec, dec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAVCCMarshal(t *testing.T) {
|
||||||
|
for _, ca := range casesAVCC {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
enc, err := AVCCMarshal(ca.dec)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ca.enc, enc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzAVCCUnmarshal(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte) {
|
||||||
|
AVCCUnmarshal(b)
|
||||||
|
})
|
||||||
|
}
|
193
pkg/codecs/h264/dts_extractor.go
Normal file
193
pkg/codecs/h264/dts_extractor.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/bluenviron/mediabase/pkg/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPictureOrderCount(buf []byte, sps *SPS) (uint32, error) {
|
||||||
|
if len(buf) < 6 {
|
||||||
|
return 0, fmt.Errorf("not enough bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = EmulationPreventionRemove(buf[:6])
|
||||||
|
|
||||||
|
buf = buf[1:]
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
_, err := bits.ReadGolombUnsigned(buf, &pos) // first_mb_in_slice
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = bits.ReadGolombUnsigned(buf, &pos) // slice_type
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = bits.ReadGolombUnsigned(buf, &pos) // pic_parameter_set_id
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = bits.ReadBits(buf, &pos, int(sps.Log2MaxFrameNumMinus4+4)) // frame_num
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sps.FrameMbsOnlyFlag {
|
||||||
|
return 0, fmt.Errorf("frame_mbs_only_flag = 0 is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
picOrderCntLsb, err := bits.ReadBits(buf, &pos, int(sps.Log2MaxPicOrderCntLsbMinus4+4))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint32(picOrderCntLsb), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPictureOrderCount(au [][]byte, sps *SPS) (uint32, error) {
|
||||||
|
for _, nalu := range au {
|
||||||
|
typ := NALUType(nalu[0] & 0x1F)
|
||||||
|
if typ == NALUTypeNonIDR {
|
||||||
|
poc, err := getPictureOrderCount(nalu, sps)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return poc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("POC not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPictureOrderCountDiff(poc1 uint32, poc2 uint32, sps *SPS) int32 {
|
||||||
|
diff := int32(poc1) - int32(poc2)
|
||||||
|
switch {
|
||||||
|
case diff < -((1 << (sps.Log2MaxPicOrderCntLsbMinus4 + 3)) - 1):
|
||||||
|
diff += (1 << (sps.Log2MaxPicOrderCntLsbMinus4 + 4))
|
||||||
|
|
||||||
|
case diff > ((1 << (sps.Log2MaxPicOrderCntLsbMinus4 + 3)) - 1):
|
||||||
|
diff -= (1 << (sps.Log2MaxPicOrderCntLsbMinus4 + 4))
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTSExtractor allows to extract DTS from PTS.
|
||||||
|
type DTSExtractor struct {
|
||||||
|
spsp *SPS
|
||||||
|
prevDTSFilled bool
|
||||||
|
prevDTS time.Duration
|
||||||
|
expectedPOC uint32
|
||||||
|
reorderedFrames int
|
||||||
|
pauseDTS int
|
||||||
|
pocIncrement int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDTSExtractor allocates a DTSExtractor.
|
||||||
|
func NewDTSExtractor() *DTSExtractor {
|
||||||
|
return &DTSExtractor{
|
||||||
|
pocIncrement: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DTSExtractor) extractInner(au [][]byte, pts time.Duration) (time.Duration, error) {
|
||||||
|
idrPresent := false
|
||||||
|
|
||||||
|
for _, nalu := range au {
|
||||||
|
typ := NALUType(nalu[0] & 0x1F)
|
||||||
|
switch typ {
|
||||||
|
case NALUTypeSPS:
|
||||||
|
var spsp SPS
|
||||||
|
err := spsp.Unmarshal(nalu)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("invalid SPS: %v", err)
|
||||||
|
}
|
||||||
|
d.spsp = &spsp
|
||||||
|
|
||||||
|
case NALUTypeIDR:
|
||||||
|
idrPresent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.spsp == nil {
|
||||||
|
return 0, fmt.Errorf("SPS not received yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.spsp.PicOrderCntType == 2 {
|
||||||
|
return pts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.spsp.PicOrderCntType == 1 {
|
||||||
|
return 0, fmt.Errorf("pic_order_cnt_type = 1 is not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if idrPresent {
|
||||||
|
d.expectedPOC = 0
|
||||||
|
d.reorderedFrames = 0
|
||||||
|
d.pauseDTS = 0
|
||||||
|
d.pocIncrement = 2
|
||||||
|
return pts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.expectedPOC += uint32(d.pocIncrement)
|
||||||
|
d.expectedPOC &= ((1 << (d.spsp.Log2MaxPicOrderCntLsbMinus4 + 4)) - 1)
|
||||||
|
|
||||||
|
if d.pauseDTS > 0 {
|
||||||
|
d.pauseDTS--
|
||||||
|
return d.prevDTS + 1*time.Millisecond, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
poc, err := findPictureOrderCount(au, d.spsp)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.pocIncrement == 2 && (poc%2) != 0 {
|
||||||
|
d.pocIncrement = 1
|
||||||
|
d.expectedPOC /= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
pocDiff := int(getPictureOrderCountDiff(poc, d.expectedPOC, d.spsp)) + d.reorderedFrames*d.pocIncrement
|
||||||
|
|
||||||
|
if pocDiff < 0 {
|
||||||
|
return 0, fmt.Errorf("invalid POC")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pocDiff == 0 {
|
||||||
|
return pts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
reorderedFrames := (pocDiff - d.reorderedFrames*d.pocIncrement) / d.pocIncrement
|
||||||
|
if reorderedFrames > d.reorderedFrames {
|
||||||
|
d.pauseDTS = (reorderedFrames - d.reorderedFrames - 1)
|
||||||
|
d.reorderedFrames = reorderedFrames
|
||||||
|
return d.prevDTS + 1*time.Millisecond, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.prevDTS + ((pts - d.prevDTS) * time.Duration(d.pocIncrement) / time.Duration(pocDiff+d.pocIncrement)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract extracts the DTS of a access unit.
|
||||||
|
func (d *DTSExtractor) Extract(au [][]byte, pts time.Duration) (time.Duration, error) {
|
||||||
|
dts, err := d.extractInner(au, pts)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dts > pts {
|
||||||
|
return 0, fmt.Errorf("DTS is greater than PTS")
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.prevDTSFilled && dts <= d.prevDTS {
|
||||||
|
return 0, fmt.Errorf("DTS is not monotonically increasing, was %v, now is %v",
|
||||||
|
d.prevDTS, dts)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.prevDTS = dts
|
||||||
|
d.prevDTSFilled = true
|
||||||
|
|
||||||
|
return dts, err
|
||||||
|
}
|
222
pkg/codecs/h264/dts_extractor_test.go
Normal file
222
pkg/codecs/h264/dts_extractor_test.go
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDTSExtractor(t *testing.T) {
|
||||||
|
type sequenceSample struct {
|
||||||
|
nalus [][]byte
|
||||||
|
dts time.Duration
|
||||||
|
pts time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ca := range []struct {
|
||||||
|
name string
|
||||||
|
sequence []sequenceSample
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"with timing info",
|
||||||
|
[]sequenceSample{
|
||||||
|
{
|
||||||
|
[][]byte{
|
||||||
|
{ // SPS
|
||||||
|
0x67, 0x64, 0x00, 0x28, 0xac, 0xd9, 0x40, 0x78,
|
||||||
|
0x02, 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00,
|
||||||
|
0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60,
|
||||||
|
0xc6, 0x58,
|
||||||
|
},
|
||||||
|
{ // IDR
|
||||||
|
0x65, 0x88, 0x84, 0x00, 0x33, 0xff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
33333333333 * time.Nanosecond,
|
||||||
|
33333333333 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x41, 0x9a, 0x21, 0x6c, 0x45, 0xff}},
|
||||||
|
33366666666 * time.Nanosecond,
|
||||||
|
33366666666 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x41, 0x9a, 0x42, 0x3c, 0x21, 0x93}},
|
||||||
|
33400000000 * time.Nanosecond,
|
||||||
|
33400000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x41, 0x9a, 0x63, 0x49, 0xe1, 0x0f}},
|
||||||
|
33433333333 * time.Nanosecond,
|
||||||
|
33433333333 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x41, 0x9a, 0x86, 0x49, 0xe1, 0x0f}},
|
||||||
|
33434333333 * time.Nanosecond,
|
||||||
|
33533333333 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x41, 0x9e, 0xa5, 0x42, 0x7f, 0xf9}},
|
||||||
|
33435333333 * time.Nanosecond,
|
||||||
|
33500000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x01, 0x9e, 0xc4, 0x69, 0x13, 0xff}},
|
||||||
|
33466666666 * time.Nanosecond,
|
||||||
|
33466666666 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x41, 0x9a, 0xc8, 0x4b, 0xa8, 0x42}},
|
||||||
|
33499999999 * time.Nanosecond,
|
||||||
|
33600000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no timing info",
|
||||||
|
[]sequenceSample{
|
||||||
|
{
|
||||||
|
[][]byte{
|
||||||
|
{ // SPS
|
||||||
|
0x27, 0x64, 0x00, 0x20, 0xac, 0x52, 0x18, 0x0f,
|
||||||
|
0x01, 0x17, 0xef, 0xff, 0x00, 0x01, 0x00, 0x01,
|
||||||
|
0x6a, 0x02, 0x02, 0x03, 0x6d, 0x85, 0x6b, 0xde,
|
||||||
|
0xf8, 0x08,
|
||||||
|
},
|
||||||
|
{ // IDR
|
||||||
|
0x25, 0xb8, 0x08, 0x02, 0x1f, 0xff,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
850000000 * time.Nanosecond,
|
||||||
|
850000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe1, 0x05, 0xc7, 0x38, 0xbf}},
|
||||||
|
866666667 * time.Nanosecond,
|
||||||
|
866666667 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe2, 0x09, 0xa1, 0xce, 0x0b}},
|
||||||
|
883333334 * time.Nanosecond,
|
||||||
|
883333334 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe3, 0x0d, 0xb1, 0xce, 0x02}},
|
||||||
|
900000000 * time.Nanosecond,
|
||||||
|
900000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe4, 0x11, 0x90, 0x73, 0x80}},
|
||||||
|
916666667 * time.Nanosecond,
|
||||||
|
916666667 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe5, 0x19, 0x0e, 0x70, 0x01}},
|
||||||
|
917666667 * time.Nanosecond,
|
||||||
|
950000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x01, 0xa9, 0x85, 0x7c, 0x93, 0xff}},
|
||||||
|
933333334 * time.Nanosecond,
|
||||||
|
933333334 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe6, 0x1d, 0x0e, 0x70, 0x01}},
|
||||||
|
950000000 * time.Nanosecond,
|
||||||
|
966666667 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe7, 0x21, 0x0e, 0x70, 0x01}},
|
||||||
|
966666667 * time.Nanosecond,
|
||||||
|
983333334 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe8, 0x25, 0x0e, 0x70, 0x01}},
|
||||||
|
983333333 * time.Nanosecond,
|
||||||
|
1000000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xe9, 0x29, 0x0e, 0x70, 0x01}},
|
||||||
|
1000000000 * time.Nanosecond,
|
||||||
|
1016666667 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x21, 0xea, 0x31, 0x0e, 0x70, 0x01}},
|
||||||
|
1016666666 * time.Nanosecond,
|
||||||
|
1050000000 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x01, 0xaa, 0xcb, 0x7c, 0x93, 0xff}},
|
||||||
|
1033333334 * time.Nanosecond,
|
||||||
|
1033333334 * time.Nanosecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"poc increment = 1",
|
||||||
|
[]sequenceSample{
|
||||||
|
{
|
||||||
|
[][]byte{
|
||||||
|
{ // SPS
|
||||||
|
0x67, 0x64, 0x00, 0x2a, 0xac, 0x2c, 0x6a, 0x81,
|
||||||
|
0xe0, 0x08, 0x9f, 0x96, 0x6e, 0x02, 0x02, 0x02,
|
||||||
|
0x80, 0x00, 0x03, 0x84, 0x00, 0x00, 0xaf, 0xc8,
|
||||||
|
0x02,
|
||||||
|
},
|
||||||
|
{ // IDR
|
||||||
|
0x65, 0xb8, 0x00, 0x00, 0x0b, 0xc8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
61 * time.Millisecond,
|
||||||
|
61 * time.Millisecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x61, 0xe0, 0x20, 0x00, 0x39, 0x37}},
|
||||||
|
101 * time.Millisecond,
|
||||||
|
101 * time.Millisecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x61, 0xe0, 0x40, 0x00, 0x59, 0x37}},
|
||||||
|
141 * time.Millisecond,
|
||||||
|
141 * time.Millisecond,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[][]byte{{0x61, 0xe0, 0x60, 0x00, 0x79, 0x37}},
|
||||||
|
181 * time.Millisecond,
|
||||||
|
181 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
ex := NewDTSExtractor()
|
||||||
|
for _, sample := range ca.sequence {
|
||||||
|
dts, err := ex.Extract(sample.nalus, sample.pts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, sample.dts, dts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzDTSExtractor(f *testing.F) {
|
||||||
|
ex := NewDTSExtractor()
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte, p uint64) {
|
||||||
|
if len(b) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ex.Extract([][]byte{
|
||||||
|
{ // SPS
|
||||||
|
0x27, 0x64, 0x00, 0x20, 0xac, 0x52, 0x18, 0x0f,
|
||||||
|
0x01, 0x17, 0xef, 0xff, 0x00, 0x01, 0x00, 0x01,
|
||||||
|
0x6a, 0x02, 0x02, 0x03, 0x6d, 0x85, 0x6b, 0xde,
|
||||||
|
0xf8, 0x08,
|
||||||
|
},
|
||||||
|
b,
|
||||||
|
}, time.Duration(p))
|
||||||
|
})
|
||||||
|
}
|
33
pkg/codecs/h264/emulation_prevention.go
Normal file
33
pkg/codecs/h264/emulation_prevention.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
// EmulationPreventionRemove removes emulation prevention bytes from a NALU.
|
||||||
|
func EmulationPreventionRemove(nalu []byte) []byte {
|
||||||
|
// 0x00 0x00 0x03 0x00 -> 0x00 0x00 0x00
|
||||||
|
// 0x00 0x00 0x03 0x01 -> 0x00 0x00 0x01
|
||||||
|
// 0x00 0x00 0x03 0x02 -> 0x00 0x00 0x02
|
||||||
|
// 0x00 0x00 0x03 0x03 -> 0x00 0x00 0x03
|
||||||
|
|
||||||
|
l := len(nalu)
|
||||||
|
n := l
|
||||||
|
|
||||||
|
for i := 2; i < l; i++ {
|
||||||
|
if nalu[i-2] == 0 && nalu[i-1] == 0 && nalu[i] == 3 {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]byte, n)
|
||||||
|
pos := 0
|
||||||
|
start := 0
|
||||||
|
|
||||||
|
for i := 2; i < l; i++ {
|
||||||
|
if nalu[i-2] == 0 && nalu[i-1] == 0 && nalu[i] == 3 {
|
||||||
|
pos += copy(ret[pos:], nalu[start:i])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(ret[pos:], nalu[start:])
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
65
pkg/codecs/h264/emulation_prevention_test.go
Normal file
65
pkg/codecs/h264/emulation_prevention_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmulationPreventionRemove(t *testing.T) {
|
||||||
|
for _, ca := range []struct {
|
||||||
|
name string
|
||||||
|
unproc []byte
|
||||||
|
proc []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"base",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x01,
|
||||||
|
0x00, 0x00, 0x02,
|
||||||
|
0x00, 0x00, 0x03,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x03, 0x01,
|
||||||
|
0x00, 0x00, 0x03, 0x02,
|
||||||
|
0x00, 0x00, 0x03, 0x03,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"double emulation byte",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x03,
|
||||||
|
0x00, 0x00, 0x03, 0x00,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"terminal emulation byte",
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00,
|
||||||
|
},
|
||||||
|
[]byte{
|
||||||
|
0x00, 0x00, 0x03,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
unproc := EmulationPreventionRemove(ca.proc)
|
||||||
|
require.Equal(t, ca.unproc, unproc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzEmulationPreventionRemove(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte) {
|
||||||
|
EmulationPreventionRemove(b)
|
||||||
|
})
|
||||||
|
}
|
11
pkg/codecs/h264/h264.go
Normal file
11
pkg/codecs/h264/h264.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Package h264 contains utilities to work with the H264 codec.
|
||||||
|
package h264
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MaxNALUSize is the maximum size of a NALU.
|
||||||
|
// with a 250 Mbps H264 video, the maximum NALU size is 2.2MB
|
||||||
|
MaxNALUSize = 3 * 1024 * 1024
|
||||||
|
|
||||||
|
// MaxNALUsPerGroup is the maximum number of NALUs per group.
|
||||||
|
MaxNALUsPerGroup = 20
|
||||||
|
)
|
12
pkg/codecs/h264/idrpresent.go
Normal file
12
pkg/codecs/h264/idrpresent.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
// IDRPresent check if there's an IDR inside provided NALUs.
|
||||||
|
func IDRPresent(nalus [][]byte) bool {
|
||||||
|
for _, nalu := range nalus {
|
||||||
|
typ := NALUType(nalu[0] & 0x1F)
|
||||||
|
if typ == NALUTypeIDR {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
17
pkg/codecs/h264/idrpresent_test.go
Normal file
17
pkg/codecs/h264/idrpresent_test.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIDRPresent(t *testing.T) {
|
||||||
|
require.Equal(t, true, IDRPresent([][]byte{
|
||||||
|
{0x05},
|
||||||
|
{0x07},
|
||||||
|
}))
|
||||||
|
require.Equal(t, false, IDRPresent([][]byte{
|
||||||
|
{0x01},
|
||||||
|
}))
|
||||||
|
}
|
83
pkg/codecs/h264/nalu_type.go
Normal file
83
pkg/codecs/h264/nalu_type.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NALUType is the type of a NALU.
|
||||||
|
type NALUType uint8
|
||||||
|
|
||||||
|
// NALU types.
|
||||||
|
const (
|
||||||
|
NALUTypeNonIDR NALUType = 1
|
||||||
|
NALUTypeDataPartitionA NALUType = 2
|
||||||
|
NALUTypeDataPartitionB NALUType = 3
|
||||||
|
NALUTypeDataPartitionC NALUType = 4
|
||||||
|
NALUTypeIDR NALUType = 5
|
||||||
|
NALUTypeSEI NALUType = 6
|
||||||
|
NALUTypeSPS NALUType = 7
|
||||||
|
NALUTypePPS NALUType = 8
|
||||||
|
NALUTypeAccessUnitDelimiter NALUType = 9
|
||||||
|
NALUTypeEndOfSequence NALUType = 10
|
||||||
|
NALUTypeEndOfStream NALUType = 11
|
||||||
|
NALUTypeFillerData NALUType = 12
|
||||||
|
NALUTypeSPSExtension NALUType = 13
|
||||||
|
NALUTypePrefix NALUType = 14
|
||||||
|
NALUTypeSubsetSPS NALUType = 15
|
||||||
|
NALUTypeReserved16 NALUType = 16
|
||||||
|
NALUTypeReserved17 NALUType = 17
|
||||||
|
NALUTypeReserved18 NALUType = 18
|
||||||
|
NALUTypeSliceLayerWithoutPartitioning NALUType = 19
|
||||||
|
NALUTypeSliceExtension NALUType = 20
|
||||||
|
NALUTypeSliceExtensionDepth NALUType = 21
|
||||||
|
NALUTypeReserved22 NALUType = 22
|
||||||
|
NALUTypeReserved23 NALUType = 23
|
||||||
|
|
||||||
|
// additional NALU types for RTP/H264
|
||||||
|
NALUTypeSTAPA NALUType = 24
|
||||||
|
NALUTypeSTAPB NALUType = 25
|
||||||
|
NALUTypeMTAP16 NALUType = 26
|
||||||
|
NALUTypeMTAP24 NALUType = 27
|
||||||
|
NALUTypeFUA NALUType = 28
|
||||||
|
NALUTypeFUB NALUType = 29
|
||||||
|
)
|
||||||
|
|
||||||
|
var naluTypeLabels = map[NALUType]string{
|
||||||
|
NALUTypeNonIDR: "NonIDR",
|
||||||
|
NALUTypeDataPartitionA: "DataPartitionA",
|
||||||
|
NALUTypeDataPartitionB: "DataPartitionB",
|
||||||
|
NALUTypeDataPartitionC: "DataPartitionC",
|
||||||
|
NALUTypeIDR: "IDR",
|
||||||
|
NALUTypeSEI: "SEI",
|
||||||
|
NALUTypeSPS: "SPS",
|
||||||
|
NALUTypePPS: "PPS",
|
||||||
|
NALUTypeAccessUnitDelimiter: "AccessUnitDelimiter",
|
||||||
|
NALUTypeEndOfSequence: "EndOfSequence",
|
||||||
|
NALUTypeEndOfStream: "EndOfStream",
|
||||||
|
NALUTypeFillerData: "FillerData",
|
||||||
|
NALUTypeSPSExtension: "SPSExtension",
|
||||||
|
NALUTypePrefix: "Prefix",
|
||||||
|
NALUTypeSubsetSPS: "SubsetSPS",
|
||||||
|
NALUTypeReserved16: "Reserved16",
|
||||||
|
NALUTypeReserved17: "Reserved17",
|
||||||
|
NALUTypeReserved18: "Reserved18",
|
||||||
|
NALUTypeSliceLayerWithoutPartitioning: "SliceLayerWithoutPartitioning",
|
||||||
|
NALUTypeSliceExtension: "SliceExtension",
|
||||||
|
NALUTypeSliceExtensionDepth: "SliceExtensionDepth",
|
||||||
|
NALUTypeReserved22: "Reserved22",
|
||||||
|
NALUTypeReserved23: "Reserved23",
|
||||||
|
NALUTypeSTAPA: "STAP-A",
|
||||||
|
NALUTypeSTAPB: "STAP-B",
|
||||||
|
NALUTypeMTAP16: "MTAP-16",
|
||||||
|
NALUTypeMTAP24: "MTAP-24",
|
||||||
|
NALUTypeFUA: "FU-A",
|
||||||
|
NALUTypeFUB: "FU-B",
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer.
|
||||||
|
func (nt NALUType) String() string {
|
||||||
|
if l, ok := naluTypeLabels[nt]; ok {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unknown (%d)", nt)
|
||||||
|
}
|
13
pkg/codecs/h264/nalu_type_test.go
Normal file
13
pkg/codecs/h264/nalu_type_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNALUType(t *testing.T) {
|
||||||
|
require.NotEqual(t, true, strings.HasPrefix(NALUType(10).String(), "unknown"))
|
||||||
|
require.Equal(t, true, strings.HasPrefix(NALUType(50).String(), "unknown"))
|
||||||
|
}
|
728
pkg/codecs/h264/sps.go
Normal file
728
pkg/codecs/h264/sps.go
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bluenviron/mediabase/pkg/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxRefFrames = 255
|
||||||
|
)
|
||||||
|
|
||||||
|
func readScalingList(buf []byte, pos *int, size int) ([]int32, bool, error) {
|
||||||
|
lastScale := int32(8)
|
||||||
|
nextScale := int32(8)
|
||||||
|
scalingList := make([]int32, size)
|
||||||
|
var useDefaultScalingMatrixFlag bool
|
||||||
|
|
||||||
|
for j := 0; j < size; j++ {
|
||||||
|
if nextScale != 0 {
|
||||||
|
deltaScale, err := bits.ReadGolombSigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nextScale = (lastScale + deltaScale + 256) % 256
|
||||||
|
useDefaultScalingMatrixFlag = (j == 0 && nextScale == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextScale == 0 {
|
||||||
|
scalingList[j] = lastScale
|
||||||
|
} else {
|
||||||
|
scalingList[j] = nextScale
|
||||||
|
}
|
||||||
|
|
||||||
|
lastScale = scalingList[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
return scalingList, useDefaultScalingMatrixFlag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPS_HRD is a hypotetical reference decoder.
|
||||||
|
type SPS_HRD struct { //nolint:revive
|
||||||
|
CpbCntMinus1 uint32
|
||||||
|
BitRateScale uint8
|
||||||
|
CpbSizeScale uint8
|
||||||
|
BitRateValueMinus1 []uint32
|
||||||
|
CpbSizeValueMinus1 []uint32
|
||||||
|
CbrFlag []bool
|
||||||
|
InitialCpbRemovalDelayLengthMinus1 uint8
|
||||||
|
CpbRemovalDelayLengthMinus1 uint8
|
||||||
|
DpbOutputDelayLengthMinus1 uint8
|
||||||
|
TimeOffsetLength uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SPS_HRD) unmarshal(buf []byte, pos *int) error {
|
||||||
|
var err error
|
||||||
|
h.CpbCntMinus1, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bits.HasSpace(buf, *pos, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.BitRateScale = uint8(bits.ReadBitsUnsafe(buf, pos, 4))
|
||||||
|
h.CpbSizeScale = uint8(bits.ReadBitsUnsafe(buf, pos, 4))
|
||||||
|
|
||||||
|
for i := uint32(0); i <= h.CpbCntMinus1; i++ {
|
||||||
|
v, err := bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.BitRateValueMinus1 = append(h.BitRateValueMinus1, v)
|
||||||
|
|
||||||
|
v, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.CpbSizeValueMinus1 = append(h.CpbSizeValueMinus1, v)
|
||||||
|
|
||||||
|
vb, err := bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.CbrFlag = append(h.CbrFlag, vb)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bits.HasSpace(buf, *pos, 5+5+5+5)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.InitialCpbRemovalDelayLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, pos, 5))
|
||||||
|
h.CpbRemovalDelayLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, pos, 5))
|
||||||
|
h.DpbOutputDelayLengthMinus1 = uint8(bits.ReadBitsUnsafe(buf, pos, 5))
|
||||||
|
h.TimeOffsetLength = uint8(bits.ReadBitsUnsafe(buf, pos, 5))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPS_TimingInfo is a timing info.
|
||||||
|
type SPS_TimingInfo struct { //nolint:revive
|
||||||
|
NumUnitsInTick uint32
|
||||||
|
TimeScale uint32
|
||||||
|
FixedFrameRateFlag bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *SPS_TimingInfo) unmarshal(buf []byte, pos *int) error {
|
||||||
|
err := bits.HasSpace(buf, *pos, 32+32+1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumUnitsInTick = uint32(bits.ReadBitsUnsafe(buf, pos, 32))
|
||||||
|
t.TimeScale = uint32(bits.ReadBitsUnsafe(buf, pos, 32))
|
||||||
|
t.FixedFrameRateFlag = bits.ReadFlagUnsafe(buf, pos)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPS_BitstreamRestriction are bitstream restriction infos.
|
||||||
|
type SPS_BitstreamRestriction struct { //nolint:revive
|
||||||
|
MotionVectorsOverPicBoundariesFlag bool
|
||||||
|
MaxBytesPerPicDenom uint32
|
||||||
|
MaxBitsPerMbDenom uint32
|
||||||
|
Log2MaxMvLengthHorizontal uint32
|
||||||
|
Log2MaxMvLengthVertical uint32
|
||||||
|
MaxNumReorderFrames uint32
|
||||||
|
MaxDecFrameBuffering uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SPS_BitstreamRestriction) unmarshal(buf []byte, pos *int) error {
|
||||||
|
var err error
|
||||||
|
r.MotionVectorsOverPicBoundariesFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MaxBytesPerPicDenom, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MaxBitsPerMbDenom, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Log2MaxMvLengthHorizontal, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Log2MaxMvLengthVertical, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MaxNumReorderFrames, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.MaxDecFrameBuffering, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPS_VUI is a video usability information.
|
||||||
|
type SPS_VUI struct { //nolint:revive
|
||||||
|
AspectRatioInfoPresentFlag bool
|
||||||
|
|
||||||
|
// AspectRatioInfoPresentFlag == true
|
||||||
|
AspectRatioIdc uint8
|
||||||
|
SarWidth uint16
|
||||||
|
SarHeight uint16
|
||||||
|
|
||||||
|
OverscanInfoPresentFlag bool
|
||||||
|
|
||||||
|
// OverscanInfoPresentFlag == true
|
||||||
|
OverscanAppropriateFlag bool
|
||||||
|
VideoSignalTypePresentFlag bool
|
||||||
|
|
||||||
|
// VideoSignalTypePresentFlag == true
|
||||||
|
VideoFormat uint8
|
||||||
|
VideoFullRangeFlag bool
|
||||||
|
ColourDescriptionPresentFlag bool
|
||||||
|
|
||||||
|
// ColourDescriptionPresentFlag == true
|
||||||
|
ColourPrimaries uint8
|
||||||
|
TransferCharacteristics uint8
|
||||||
|
MatrixCoefficients uint8
|
||||||
|
|
||||||
|
ChromaLocInfoPresentFlag bool
|
||||||
|
|
||||||
|
// ChromaLocInfoPresentFlag == true
|
||||||
|
ChromaSampleLocTypeTopField uint32
|
||||||
|
ChromaSampleLocTypeBottomField uint32
|
||||||
|
|
||||||
|
TimingInfo *SPS_TimingInfo
|
||||||
|
NalHRD *SPS_HRD
|
||||||
|
VclHRD *SPS_HRD
|
||||||
|
|
||||||
|
LowDelayHrdFlag bool
|
||||||
|
PicStructPresentFlag bool
|
||||||
|
BitstreamRestriction *SPS_BitstreamRestriction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *SPS_VUI) unmarshal(buf []byte, pos *int) error {
|
||||||
|
var err error
|
||||||
|
v.AspectRatioInfoPresentFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.AspectRatioInfoPresentFlag {
|
||||||
|
tmp, err := bits.ReadBits(buf, pos, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.AspectRatioIdc = uint8(tmp)
|
||||||
|
|
||||||
|
if v.AspectRatioIdc == 255 { // Extended_SAR
|
||||||
|
err := bits.HasSpace(buf, *pos, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.SarWidth = uint16(bits.ReadBitsUnsafe(buf, pos, 16))
|
||||||
|
v.SarHeight = uint16(bits.ReadBitsUnsafe(buf, pos, 16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.OverscanInfoPresentFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.OverscanInfoPresentFlag {
|
||||||
|
v.OverscanAppropriateFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.VideoSignalTypePresentFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.VideoSignalTypePresentFlag {
|
||||||
|
err := bits.HasSpace(buf, *pos, 5)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.VideoFormat = uint8(bits.ReadBitsUnsafe(buf, pos, 3))
|
||||||
|
v.VideoFullRangeFlag = bits.ReadFlagUnsafe(buf, pos)
|
||||||
|
v.ColourDescriptionPresentFlag = bits.ReadFlagUnsafe(buf, pos)
|
||||||
|
|
||||||
|
if v.ColourDescriptionPresentFlag {
|
||||||
|
err := bits.HasSpace(buf, *pos, 24)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.ColourPrimaries = uint8(bits.ReadBitsUnsafe(buf, pos, 8))
|
||||||
|
v.TransferCharacteristics = uint8(bits.ReadBitsUnsafe(buf, pos, 8))
|
||||||
|
v.MatrixCoefficients = uint8(bits.ReadBitsUnsafe(buf, pos, 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.ChromaLocInfoPresentFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.ChromaLocInfoPresentFlag {
|
||||||
|
v.ChromaSampleLocTypeTopField, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.ChromaSampleLocTypeBottomField, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timingInfoPresentFlag, err := bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if timingInfoPresentFlag {
|
||||||
|
v.TimingInfo = &SPS_TimingInfo{}
|
||||||
|
err := v.TimingInfo.unmarshal(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nalHrdParametersPresentFlag, err := bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if nalHrdParametersPresentFlag {
|
||||||
|
v.NalHRD = &SPS_HRD{}
|
||||||
|
err := v.NalHRD.unmarshal(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vclHrdParametersPresentFlag, err := bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vclHrdParametersPresentFlag {
|
||||||
|
v.VclHRD = &SPS_HRD{}
|
||||||
|
err := v.VclHRD.unmarshal(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nalHrdParametersPresentFlag || vclHrdParametersPresentFlag {
|
||||||
|
v.LowDelayHrdFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.PicStructPresentFlag, err = bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bitstreamRestrictionFlag, err := bits.ReadFlag(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bitstreamRestrictionFlag {
|
||||||
|
v.BitstreamRestriction = &SPS_BitstreamRestriction{}
|
||||||
|
err := v.BitstreamRestriction.unmarshal(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPS_FrameCropping is the frame cropping part of a SPS.
|
||||||
|
type SPS_FrameCropping struct { //nolint:revive
|
||||||
|
LeftOffset uint32
|
||||||
|
RightOffset uint32
|
||||||
|
TopOffset uint32
|
||||||
|
BottomOffset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SPS_FrameCropping) unmarshal(buf []byte, pos *int) error {
|
||||||
|
var err error
|
||||||
|
c.LeftOffset, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RightOffset, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.TopOffset, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.BottomOffset, err = bits.ReadGolombUnsigned(buf, pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPS is a H264 sequence parameter set.
|
||||||
|
type SPS struct {
|
||||||
|
ProfileIdc uint8
|
||||||
|
ConstraintSet0Flag bool
|
||||||
|
ConstraintSet1Flag bool
|
||||||
|
ConstraintSet2Flag bool
|
||||||
|
ConstraintSet3Flag bool
|
||||||
|
ConstraintSet4Flag bool
|
||||||
|
ConstraintSet5Flag bool
|
||||||
|
LevelIdc uint8
|
||||||
|
ID uint32
|
||||||
|
|
||||||
|
// only for selected ProfileIdcs
|
||||||
|
ChromeFormatIdc uint32
|
||||||
|
SeparateColourPlaneFlag bool
|
||||||
|
BitDepthLumaMinus8 uint32
|
||||||
|
BitDepthChromaMinus8 uint32
|
||||||
|
QpprimeYZeroTransformBypassFlag bool
|
||||||
|
|
||||||
|
// seqScalingListPresentFlag == true
|
||||||
|
ScalingList4x4 [][]int32
|
||||||
|
UseDefaultScalingMatrix4x4Flag []bool
|
||||||
|
ScalingList8x8 [][]int32
|
||||||
|
UseDefaultScalingMatrix8x8Flag []bool
|
||||||
|
|
||||||
|
Log2MaxFrameNumMinus4 uint32
|
||||||
|
PicOrderCntType uint32
|
||||||
|
|
||||||
|
// PicOrderCntType == 0
|
||||||
|
Log2MaxPicOrderCntLsbMinus4 uint32
|
||||||
|
|
||||||
|
// PicOrderCntType == 1
|
||||||
|
DeltaPicOrderAlwaysZeroFlag bool
|
||||||
|
OffsetForNonRefPic int32
|
||||||
|
OffsetForTopToBottomField int32
|
||||||
|
OffsetForRefFrames []int32
|
||||||
|
|
||||||
|
MaxNumRefFrames uint32
|
||||||
|
GapsInFrameNumValueAllowedFlag bool
|
||||||
|
PicWidthInMbsMinus1 uint32
|
||||||
|
PicHeightInMapUnitsMinus1 uint32
|
||||||
|
FrameMbsOnlyFlag bool
|
||||||
|
|
||||||
|
// FrameMbsOnlyFlag == false
|
||||||
|
MbAdaptiveFrameFieldFlag bool
|
||||||
|
|
||||||
|
Direct8x8InferenceFlag bool
|
||||||
|
FrameCropping *SPS_FrameCropping
|
||||||
|
VUI *SPS_VUI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes a SPS from bytes.
|
||||||
|
func (s *SPS) Unmarshal(buf []byte) error {
|
||||||
|
buf = EmulationPreventionRemove(buf)
|
||||||
|
|
||||||
|
if len(buf) < 4 {
|
||||||
|
return fmt.Errorf("not enough bits")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ProfileIdc = buf[1]
|
||||||
|
s.ConstraintSet0Flag = (buf[2] >> 7) == 1
|
||||||
|
s.ConstraintSet1Flag = (buf[2] >> 6 & 0x01) == 1
|
||||||
|
s.ConstraintSet2Flag = (buf[2] >> 5 & 0x01) == 1
|
||||||
|
s.ConstraintSet3Flag = (buf[2] >> 4 & 0x01) == 1
|
||||||
|
s.ConstraintSet4Flag = (buf[2] >> 3 & 0x01) == 1
|
||||||
|
s.ConstraintSet5Flag = (buf[2] >> 2 & 0x01) == 1
|
||||||
|
s.LevelIdc = buf[3]
|
||||||
|
|
||||||
|
buf = buf[4:]
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
var err error
|
||||||
|
s.ID, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.ProfileIdc {
|
||||||
|
case 100, 110, 122, 244, 44, 83, 86, 118, 128, 138, 139, 134, 135:
|
||||||
|
s.ChromeFormatIdc, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.ChromeFormatIdc == 3 {
|
||||||
|
s.SeparateColourPlaneFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.SeparateColourPlaneFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.BitDepthLumaMinus8, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.BitDepthChromaMinus8, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.QpprimeYZeroTransformBypassFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
seqScalingMatrixPresentFlag, err := bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if seqScalingMatrixPresentFlag {
|
||||||
|
var lim int
|
||||||
|
if s.ChromeFormatIdc != 3 {
|
||||||
|
lim = 8
|
||||||
|
} else {
|
||||||
|
lim = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < lim; i++ {
|
||||||
|
seqScalingListPresentFlag, err := bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if seqScalingListPresentFlag {
|
||||||
|
if i < 6 {
|
||||||
|
scalingList, useDefaultScalingMatrixFlag, err := readScalingList(buf, &pos, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ScalingList4x4 = append(s.ScalingList4x4, scalingList)
|
||||||
|
s.UseDefaultScalingMatrix4x4Flag = append(s.UseDefaultScalingMatrix4x4Flag,
|
||||||
|
useDefaultScalingMatrixFlag)
|
||||||
|
} else {
|
||||||
|
scalingList, useDefaultScalingMatrixFlag, err := readScalingList(buf, &pos, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.ScalingList8x8 = append(s.ScalingList8x8, scalingList)
|
||||||
|
s.UseDefaultScalingMatrix8x8Flag = append(s.UseDefaultScalingMatrix8x8Flag,
|
||||||
|
useDefaultScalingMatrixFlag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
s.ChromeFormatIdc = 0
|
||||||
|
s.SeparateColourPlaneFlag = false
|
||||||
|
s.BitDepthLumaMinus8 = 0
|
||||||
|
s.BitDepthChromaMinus8 = 0
|
||||||
|
s.QpprimeYZeroTransformBypassFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Log2MaxFrameNumMinus4, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PicOrderCntType, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch s.PicOrderCntType {
|
||||||
|
case 0:
|
||||||
|
s.Log2MaxPicOrderCntLsbMinus4, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.DeltaPicOrderAlwaysZeroFlag = false
|
||||||
|
s.OffsetForNonRefPic = 0
|
||||||
|
s.OffsetForTopToBottomField = 0
|
||||||
|
s.OffsetForRefFrames = nil
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
s.Log2MaxPicOrderCntLsbMinus4 = 0
|
||||||
|
|
||||||
|
s.DeltaPicOrderAlwaysZeroFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.OffsetForNonRefPic, err = bits.ReadGolombSigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.OffsetForTopToBottomField, err = bits.ReadGolombSigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
numRefFramesInPicOrderCntCycle, err := bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if numRefFramesInPicOrderCntCycle > maxRefFrames {
|
||||||
|
return fmt.Errorf("num_ref_frames_in_pic_order_cnt_cycle exceeds %d", maxRefFrames)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.OffsetForRefFrames = make([]int32, numRefFramesInPicOrderCntCycle)
|
||||||
|
for i := uint32(0); i < numRefFramesInPicOrderCntCycle; i++ {
|
||||||
|
v, err := bits.ReadGolombSigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.OffsetForRefFrames[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
s.Log2MaxPicOrderCntLsbMinus4 = 0
|
||||||
|
s.DeltaPicOrderAlwaysZeroFlag = false
|
||||||
|
s.OffsetForNonRefPic = 0
|
||||||
|
s.OffsetForTopToBottomField = 0
|
||||||
|
s.OffsetForRefFrames = nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid pic_order_cnt_type: %d", s.PicOrderCntType)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.MaxNumRefFrames, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.GapsInFrameNumValueAllowedFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PicWidthInMbsMinus1, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PicHeightInMapUnitsMinus1, err = bits.ReadGolombUnsigned(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.FrameMbsOnlyFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.FrameMbsOnlyFlag {
|
||||||
|
s.MbAdaptiveFrameFieldFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.MbAdaptiveFrameFieldFlag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Direct8x8InferenceFlag, err = bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
frameCroppingFlag, err := bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if frameCroppingFlag {
|
||||||
|
s.FrameCropping = &SPS_FrameCropping{}
|
||||||
|
err := s.FrameCropping.unmarshal(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.FrameCropping = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vuiParametersPresentFlag, err := bits.ReadFlag(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vuiParametersPresentFlag {
|
||||||
|
s.VUI = &SPS_VUI{}
|
||||||
|
err := s.VUI.unmarshal(buf, &pos)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.VUI = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Width returns the video width.
|
||||||
|
func (s SPS) Width() int {
|
||||||
|
if s.FrameCropping != nil {
|
||||||
|
return int(((s.PicWidthInMbsMinus1 + 1) * 16) - (s.FrameCropping.LeftOffset+s.FrameCropping.RightOffset)*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int((s.PicWidthInMbsMinus1 + 1) * 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the video height.
|
||||||
|
func (s SPS) Height() int {
|
||||||
|
f := uint32(0)
|
||||||
|
if s.FrameMbsOnlyFlag {
|
||||||
|
f = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.FrameCropping != nil {
|
||||||
|
return int(((2 - f) * (s.PicHeightInMapUnitsMinus1 + 1) * 16) -
|
||||||
|
(s.FrameCropping.TopOffset+s.FrameCropping.BottomOffset)*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int((2 - f) * (s.PicHeightInMapUnitsMinus1 + 1) * 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FPS returns the frames per second of the video.
|
||||||
|
func (s SPS) FPS() float64 {
|
||||||
|
if s.VUI == nil || s.VUI.TimingInfo == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return float64(s.VUI.TimingInfo.TimeScale) / (2 * float64(s.VUI.TimingInfo.NumUnitsInTick))
|
||||||
|
}
|
462
pkg/codecs/h264/sps_test.go
Normal file
462
pkg/codecs/h264/sps_test.go
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
//go:build go1.18
|
||||||
|
// +build go1.18
|
||||||
|
|
||||||
|
package h264
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSPSUnmarshal(t *testing.T) {
|
||||||
|
for _, ca := range []struct {
|
||||||
|
name string
|
||||||
|
byts []byte
|
||||||
|
sps SPS
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
fps float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"352x288",
|
||||||
|
[]byte{
|
||||||
|
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
|
||||||
|
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
|
||||||
|
0x00, 0x03, 0x00, 0x3d, 0x08,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 12,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
Log2MaxFrameNumMinus4: 6,
|
||||||
|
PicOrderCntType: 2,
|
||||||
|
MaxNumRefFrames: 1,
|
||||||
|
GapsInFrameNumValueAllowedFlag: true,
|
||||||
|
PicWidthInMbsMinus1: 21,
|
||||||
|
PicHeightInMapUnitsMinus1: 17,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1,
|
||||||
|
TimeScale: 30,
|
||||||
|
FixedFrameRateFlag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
352,
|
||||||
|
288,
|
||||||
|
15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1280x720",
|
||||||
|
[]byte{
|
||||||
|
0x67, 0x64, 0x00, 0x1f, 0xac, 0xd9, 0x40, 0x50,
|
||||||
|
0x05, 0xbb, 0x01, 0x6c, 0x80, 0x00, 0x00, 0x03,
|
||||||
|
0x00, 0x80, 0x00, 0x00, 0x1e, 0x07, 0x8c, 0x18,
|
||||||
|
0xcb,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 31,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
Log2MaxPicOrderCntLsbMinus4: 2,
|
||||||
|
MaxNumRefFrames: 4,
|
||||||
|
PicWidthInMbsMinus1: 79,
|
||||||
|
PicHeightInMapUnitsMinus1: 44,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
AspectRatioInfoPresentFlag: true,
|
||||||
|
AspectRatioIdc: 1,
|
||||||
|
VideoSignalTypePresentFlag: true,
|
||||||
|
VideoFormat: 5,
|
||||||
|
VideoFullRangeFlag: true,
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1,
|
||||||
|
TimeScale: 60,
|
||||||
|
},
|
||||||
|
BitstreamRestriction: &SPS_BitstreamRestriction{
|
||||||
|
MotionVectorsOverPicBoundariesFlag: true,
|
||||||
|
Log2MaxMvLengthHorizontal: 11,
|
||||||
|
Log2MaxMvLengthVertical: 11,
|
||||||
|
MaxNumReorderFrames: 2,
|
||||||
|
MaxDecFrameBuffering: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1280,
|
||||||
|
720,
|
||||||
|
30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1920x1080 baseline",
|
||||||
|
[]byte{
|
||||||
|
0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02,
|
||||||
|
0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04,
|
||||||
|
0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 66,
|
||||||
|
ConstraintSet0Flag: true,
|
||||||
|
ConstraintSet1Flag: true,
|
||||||
|
LevelIdc: 40,
|
||||||
|
PicOrderCntType: 2,
|
||||||
|
MaxNumRefFrames: 3,
|
||||||
|
PicWidthInMbsMinus1: 119,
|
||||||
|
PicHeightInMapUnitsMinus1: 67,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
FrameCropping: &SPS_FrameCropping{
|
||||||
|
BottomOffset: 4,
|
||||||
|
},
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1,
|
||||||
|
TimeScale: 60,
|
||||||
|
},
|
||||||
|
BitstreamRestriction: &SPS_BitstreamRestriction{
|
||||||
|
MotionVectorsOverPicBoundariesFlag: true,
|
||||||
|
Log2MaxMvLengthHorizontal: 11,
|
||||||
|
Log2MaxMvLengthVertical: 11,
|
||||||
|
MaxDecFrameBuffering: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1920,
|
||||||
|
1080,
|
||||||
|
30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1920x1080 nvidia",
|
||||||
|
[]byte{
|
||||||
|
0x67, 0x64, 0x00, 0x28, 0xac, 0xd9, 0x40, 0x78,
|
||||||
|
0x02, 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00,
|
||||||
|
0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60,
|
||||||
|
0xc6, 0x58,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 40,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
Log2MaxPicOrderCntLsbMinus4: 2,
|
||||||
|
MaxNumRefFrames: 4,
|
||||||
|
PicWidthInMbsMinus1: 119,
|
||||||
|
PicHeightInMapUnitsMinus1: 67,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
FrameCropping: &SPS_FrameCropping{
|
||||||
|
BottomOffset: 4,
|
||||||
|
},
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1,
|
||||||
|
TimeScale: 60,
|
||||||
|
},
|
||||||
|
BitstreamRestriction: &SPS_BitstreamRestriction{
|
||||||
|
MotionVectorsOverPicBoundariesFlag: true,
|
||||||
|
Log2MaxMvLengthHorizontal: 11,
|
||||||
|
Log2MaxMvLengthVertical: 11,
|
||||||
|
MaxNumReorderFrames: 2,
|
||||||
|
MaxDecFrameBuffering: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1920,
|
||||||
|
1080,
|
||||||
|
30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1920x1080",
|
||||||
|
[]byte{
|
||||||
|
0x67, 0x64, 0x00, 0x29, 0xac, 0x13, 0x31, 0x40,
|
||||||
|
0x78, 0x04, 0x47, 0xde, 0x03, 0xea, 0x02, 0x02,
|
||||||
|
0x03, 0xe0, 0x00, 0x00, 0x03, 0x00, 0x20, 0x00,
|
||||||
|
0x00, 0x06, 0x52, // 0x80,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 41,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
Log2MaxFrameNumMinus4: 8,
|
||||||
|
Log2MaxPicOrderCntLsbMinus4: 5,
|
||||||
|
MaxNumRefFrames: 4,
|
||||||
|
PicWidthInMbsMinus1: 119,
|
||||||
|
PicHeightInMapUnitsMinus1: 33,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
FrameCropping: &SPS_FrameCropping{
|
||||||
|
BottomOffset: 2,
|
||||||
|
},
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
AspectRatioInfoPresentFlag: true,
|
||||||
|
AspectRatioIdc: 1,
|
||||||
|
OverscanInfoPresentFlag: true,
|
||||||
|
OverscanAppropriateFlag: true,
|
||||||
|
VideoSignalTypePresentFlag: true,
|
||||||
|
VideoFormat: 5,
|
||||||
|
ColourDescriptionPresentFlag: true,
|
||||||
|
ColourPrimaries: 1,
|
||||||
|
TransferCharacteristics: 1,
|
||||||
|
MatrixCoefficients: 1,
|
||||||
|
ChromaLocInfoPresentFlag: true,
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1,
|
||||||
|
TimeScale: 50,
|
||||||
|
FixedFrameRateFlag: true,
|
||||||
|
},
|
||||||
|
PicStructPresentFlag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1920,
|
||||||
|
1084,
|
||||||
|
25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hikvision",
|
||||||
|
[]byte{103, 100, 0, 32, 172, 23, 42, 1, 64, 30, 104, 64, 0, 1, 194, 0, 0, 87, 228, 33},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 32,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
Log2MaxPicOrderCntLsbMinus4: 4,
|
||||||
|
MaxNumRefFrames: 1,
|
||||||
|
PicWidthInMbsMinus1: 79,
|
||||||
|
PicHeightInMapUnitsMinus1: 59,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
Log2MaxFrameNumMinus4: 10,
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1800,
|
||||||
|
TimeScale: 90000,
|
||||||
|
FixedFrameRateFlag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1280,
|
||||||
|
960,
|
||||||
|
25,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scaling matrix",
|
||||||
|
[]byte{
|
||||||
|
103, 100, 0, 50, 173, 132, 1, 12, 32, 8, 97, 0, 67, 8, 2,
|
||||||
|
24, 64, 16, 194, 0, 132, 59, 80, 20, 0, 90, 211,
|
||||||
|
112, 16, 16, 20, 0, 0, 3, 0, 4, 0, 0, 3, 0, 162, 16,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 50,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
ScalingList4x4: [][]int32{
|
||||||
|
{
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
16, 16, 16, 16, 16, 16, 16, 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
UseDefaultScalingMatrix4x4Flag: []bool{
|
||||||
|
false, false, false, false, false, false,
|
||||||
|
},
|
||||||
|
Log2MaxFrameNumMinus4: 6,
|
||||||
|
PicOrderCntType: 2,
|
||||||
|
MaxNumRefFrames: 1,
|
||||||
|
GapsInFrameNumValueAllowedFlag: true,
|
||||||
|
PicWidthInMbsMinus1: 159,
|
||||||
|
PicHeightInMapUnitsMinus1: 89,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
VideoSignalTypePresentFlag: true,
|
||||||
|
VideoFormat: 5,
|
||||||
|
VideoFullRangeFlag: true,
|
||||||
|
ColourDescriptionPresentFlag: true,
|
||||||
|
ColourPrimaries: 1,
|
||||||
|
TransferCharacteristics: 1,
|
||||||
|
MatrixCoefficients: 1,
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1,
|
||||||
|
TimeScale: 40,
|
||||||
|
FixedFrameRateFlag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
2560,
|
||||||
|
1440,
|
||||||
|
20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1920x1080 nvenc hrd",
|
||||||
|
[]byte{
|
||||||
|
103, 100, 0, 42, 172, 44, 172, 7,
|
||||||
|
128, 34, 126, 92, 5, 168, 8, 8,
|
||||||
|
10, 0, 0, 7, 208, 0, 3, 169,
|
||||||
|
129, 192, 0, 0, 76, 75, 0, 0,
|
||||||
|
38, 37, 173, 222, 92, 20,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 100,
|
||||||
|
LevelIdc: 42,
|
||||||
|
ChromeFormatIdc: 1,
|
||||||
|
Log2MaxFrameNumMinus4: 4,
|
||||||
|
Log2MaxPicOrderCntLsbMinus4: 4,
|
||||||
|
MaxNumRefFrames: 2,
|
||||||
|
PicWidthInMbsMinus1: 119,
|
||||||
|
PicHeightInMapUnitsMinus1: 67,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
FrameCropping: &SPS_FrameCropping{
|
||||||
|
BottomOffset: 4,
|
||||||
|
},
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
AspectRatioInfoPresentFlag: true,
|
||||||
|
AspectRatioIdc: 1,
|
||||||
|
VideoSignalTypePresentFlag: true,
|
||||||
|
VideoFormat: 5,
|
||||||
|
ColourDescriptionPresentFlag: true,
|
||||||
|
ColourPrimaries: 1,
|
||||||
|
TransferCharacteristics: 1,
|
||||||
|
MatrixCoefficients: 1,
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1000,
|
||||||
|
TimeScale: 120000,
|
||||||
|
FixedFrameRateFlag: true,
|
||||||
|
},
|
||||||
|
NalHRD: &SPS_HRD{
|
||||||
|
BitRateValueMinus1: []uint32{39061},
|
||||||
|
CpbSizeValueMinus1: []uint32{156249},
|
||||||
|
CbrFlag: []bool{true},
|
||||||
|
InitialCpbRemovalDelayLengthMinus1: 23,
|
||||||
|
CpbRemovalDelayLengthMinus1: 15,
|
||||||
|
DpbOutputDelayLengthMinus1: 5,
|
||||||
|
TimeOffsetLength: 24,
|
||||||
|
},
|
||||||
|
PicStructPresentFlag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1920,
|
||||||
|
1080,
|
||||||
|
60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1920x1080 hikvision nal hrd + vcl hrd",
|
||||||
|
[]byte{
|
||||||
|
103, 77, 0, 41, 154, 100, 3, 192,
|
||||||
|
17, 63, 46, 2, 220, 4, 4, 5,
|
||||||
|
0, 0, 3, 3, 232, 0, 0, 195,
|
||||||
|
80, 232, 96, 0, 186, 180, 0, 2,
|
||||||
|
234, 196, 187, 203, 141, 12, 0, 23,
|
||||||
|
86, 128, 0, 93, 88, 151, 121, 112,
|
||||||
|
160,
|
||||||
|
},
|
||||||
|
SPS{
|
||||||
|
ProfileIdc: 77,
|
||||||
|
LevelIdc: 41,
|
||||||
|
Log2MaxFrameNumMinus4: 5,
|
||||||
|
Log2MaxPicOrderCntLsbMinus4: 5,
|
||||||
|
MaxNumRefFrames: 1,
|
||||||
|
PicWidthInMbsMinus1: 119,
|
||||||
|
PicHeightInMapUnitsMinus1: 67,
|
||||||
|
FrameMbsOnlyFlag: true,
|
||||||
|
Direct8x8InferenceFlag: true,
|
||||||
|
FrameCropping: &SPS_FrameCropping{
|
||||||
|
BottomOffset: 4,
|
||||||
|
},
|
||||||
|
VUI: &SPS_VUI{
|
||||||
|
AspectRatioInfoPresentFlag: true,
|
||||||
|
AspectRatioIdc: 1,
|
||||||
|
VideoSignalTypePresentFlag: true,
|
||||||
|
VideoFormat: 5,
|
||||||
|
VideoFullRangeFlag: true,
|
||||||
|
ColourDescriptionPresentFlag: true,
|
||||||
|
ColourPrimaries: 1,
|
||||||
|
TransferCharacteristics: 1,
|
||||||
|
MatrixCoefficients: 1,
|
||||||
|
TimingInfo: &SPS_TimingInfo{
|
||||||
|
NumUnitsInTick: 1000,
|
||||||
|
TimeScale: 50000,
|
||||||
|
FixedFrameRateFlag: true,
|
||||||
|
},
|
||||||
|
NalHRD: &SPS_HRD{
|
||||||
|
BitRateScale: 4,
|
||||||
|
CpbSizeScale: 3,
|
||||||
|
BitRateValueMinus1: []uint32{11948},
|
||||||
|
CpbSizeValueMinus1: []uint32{95585},
|
||||||
|
CbrFlag: []bool{false},
|
||||||
|
InitialCpbRemovalDelayLengthMinus1: 23,
|
||||||
|
CpbRemovalDelayLengthMinus1: 15,
|
||||||
|
DpbOutputDelayLengthMinus1: 5,
|
||||||
|
TimeOffsetLength: 24,
|
||||||
|
},
|
||||||
|
VclHRD: &SPS_HRD{
|
||||||
|
BitRateScale: 4,
|
||||||
|
CpbSizeScale: 3,
|
||||||
|
BitRateValueMinus1: []uint32{11948},
|
||||||
|
CpbSizeValueMinus1: []uint32{95585},
|
||||||
|
CbrFlag: []bool{false},
|
||||||
|
InitialCpbRemovalDelayLengthMinus1: 23,
|
||||||
|
CpbRemovalDelayLengthMinus1: 15,
|
||||||
|
DpbOutputDelayLengthMinus1: 5,
|
||||||
|
TimeOffsetLength: 24,
|
||||||
|
},
|
||||||
|
PicStructPresentFlag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1920,
|
||||||
|
1080,
|
||||||
|
25,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(ca.name, func(t *testing.T) {
|
||||||
|
var sps SPS
|
||||||
|
err := sps.Unmarshal(ca.byts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ca.sps, sps)
|
||||||
|
require.Equal(t, ca.width, sps.Width())
|
||||||
|
require.Equal(t, ca.height, sps.Height())
|
||||||
|
require.Equal(t, ca.fps, sps.FPS())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSPSUnmarshal(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var sps SPS
|
||||||
|
sps.Unmarshal([]byte{
|
||||||
|
103, 77, 0, 41, 154, 100, 3, 192,
|
||||||
|
17, 63, 46, 2, 220, 4, 4, 5,
|
||||||
|
0, 0, 3, 3, 232, 0, 0, 195,
|
||||||
|
80, 232, 96, 0, 186, 180, 0, 2,
|
||||||
|
234, 196, 187, 203, 141, 12, 0, 23,
|
||||||
|
86, 128, 0, 93, 88, 151, 121, 112,
|
||||||
|
160,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzSPSUnmarshal(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, b []byte) {
|
||||||
|
var sps SPS
|
||||||
|
sps.Unmarshal(b)
|
||||||
|
})
|
||||||
|
}
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x00\x00\x00\x00")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x000\x00\x00")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x00\x00\x00\x00")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x00\x000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x00\x00\x01")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x00\x00\x01\x00\x00\x01")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x00\x00")
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("A0\x00\x0000")
|
||||||
|
uint64(122)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("'000\xdc10")
|
||||||
|
uint64(23)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("A00\x0000")
|
||||||
|
uint64(122)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("A\x0001\x000")
|
||||||
|
uint64(188)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("A00000")
|
||||||
|
uint64(122)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("%")
|
||||||
|
uint64(14)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("\x01\f")
|
||||||
|
uint64(60)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("A\x00\x00\x0000")
|
||||||
|
uint64(70)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("A\x0001B0")
|
||||||
|
uint64(188)
|
@@ -0,0 +1,3 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("'000%9000")
|
||||||
|
uint64(23)
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000\xf31Y08\xf7000000000\xd7")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A\xff2\xff0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000B19")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z0010")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf60000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000772.B001")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z00002")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf6A")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z00$A0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000017Y1")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A10")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000777Z\x0e2\x0e000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A\xff0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000%0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z007A")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000017Y0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000177A000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z007B000000771B$00")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000\xff2")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000%A")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A\xff2")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf6017")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z0017")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf61")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00001\x17\x0000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A\xff0\xff0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z002")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000X")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000072")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000017\xf60")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000017\xf61")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("000017Y00")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000\xf310\xff00007000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf68")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000\xff\xff\xff0a1000")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A72")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A\xff2&0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000\xf310\xff00007")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00000A 0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf67")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000177Z100")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000\xf30")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0000A78X0")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("00007\xb6\xf60")
|
@@ -0,0 +1,2 @@
|
|||||||
|
go test fuzz v1
|
||||||
|
[]byte("0z00d")
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user