mirror of
https://github.com/bluenviron/mediacommon.git
synced 2025-09-26 12:51:10 +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