initial commit

This commit is contained in:
aler9
2023-04-01 10:51:06 +02:00
commit fc0693be1c
265 changed files with 6293 additions and 0 deletions

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

50
.github/workflows/issue-lock.yml vendored Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
/coverage*.txt

28
.golangci.yml Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,16 @@
# mediabase
[![Test](https://github.com/bluenviron/mediabase/workflows/test/badge.svg)](https://github.com/bluenviron/mediabase/actions?query=workflow:test)
[![Lint](https://github.com/bluenviron/mediabase/workflows/lint/badge.svg)](https://github.com/bluenviron/mediabase/actions?query=workflow:lint)
[![Go Report Card](https://goreportcard.com/badge/github.com/bluenviron/mediabase)](https://goreportcard.com/report/github.com/bluenviron/mediabase)
[![CodeCov](https://codecov.io/gh/bluenviron/mediabase/branch/main/graph/badge.svg)](https://app.codecov.io/gh/bluenviron/mediabase/branch/main)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/bluenviron/mediabase/v3)](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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
// Package codecs contains codec definitions and utilities.
package codecs

134
pkg/codecs/h264/annexb.go Normal file
View 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
}

View 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
View 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
}

View 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)
})
}

View 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
}

View 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))
})
}

View 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
}

View 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
View 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
)

View 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
}

View 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},
}))
}

View 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)
}

View 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
View 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
View 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)
})
}

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x000\x00\x00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x00\x00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x01")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00\x01\x00\x00\x01")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("\x00\x00")

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("A0\x00\x0000")
uint64(122)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("'000\xdc10")
uint64(23)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("A00\x0000")
uint64(122)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("A\x0001\x000")
uint64(188)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("A00000")
uint64(122)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("%")
uint64(14)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("\x01\f")
uint64(60)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("A\x00\x00\x0000")
uint64(70)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("A\x0001B0")
uint64(188)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("'000%9000")
uint64(23)

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\xf31Y08\xf7000000000\xd7")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A\xff2\xff0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000B19")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z0010")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf60000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000772.B001")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z00002")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf6A")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z00$A0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000017Y1")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A10")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000777Z\x0e2\x0e000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A\xff0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000%0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z007A")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000017Y0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000177A000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z007B000000771B$00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000\xff2")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000%A")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A\xff2")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf6017")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z0017")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf61")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00001\x17\x0000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A\xff0\xff0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0z002")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000X")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000072")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000017\xf60")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000017\xf61")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("000017Y00")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\xf310\xff00007000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf68")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000\xff\xff\xff0a1000")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A72")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A\xff2&0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\xf310\xff00007")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00000A 0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf67")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000177Z100")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000\xf30")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0000A78X0")

View File

@@ -0,0 +1,2 @@
go test fuzz v1
[]byte("00007\xb6\xf60")

View File

@@ -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