link to mediacommon (#223)

* move codecs and bits to mediacommon

* add SafeSetParams() to H264 and H265

* update README
This commit is contained in:
Alessandro Ros
2023-04-01 16:38:08 +02:00
committed by GitHub
parent af3ed2bd83
commit f905598d2e
287 changed files with 329 additions and 6381 deletions

View File

@@ -46,9 +46,6 @@ Features:
* Encode/decode format-specific frames into/from RTP packets. The following formats are supported: * Encode/decode format-specific frames into/from RTP packets. The following formats are supported:
* Video: H264, H265, M-JPEG, VP8, VP9 * Video: H264, H265, M-JPEG, VP8, VP9
* Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG4 Audio (AAC), Opus * Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG4 Audio (AAC), Opus
* Parse codec-specific elements. The following codecs are supported:
* Video: H264, H265, M-JPEG
* Audio: MPEG4 Audio (AAC)
## Table of contents ## Table of contents

View File

@@ -18,12 +18,12 @@ import (
"github.com/bluenviron/gortsplib/v3/pkg/auth" "github.com/bluenviron/gortsplib/v3/pkg/auth"
"github.com/bluenviron/gortsplib/v3/pkg/base" "github.com/bluenviron/gortsplib/v3/pkg/base"
"github.com/bluenviron/gortsplib/v3/pkg/codecs/mpeg4audio"
"github.com/bluenviron/gortsplib/v3/pkg/conn" "github.com/bluenviron/gortsplib/v3/pkg/conn"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/gortsplib/v3/pkg/headers" "github.com/bluenviron/gortsplib/v3/pkg/headers"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v3/pkg/media"
"github.com/bluenviron/gortsplib/v3/pkg/url" "github.com/bluenviron/gortsplib/v3/pkg/url"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
) )
func mustMarshalMedias(medias media.Medias) []byte { func mustMarshalMedias(medias media.Medias) []byte {

View File

@@ -5,9 +5,9 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v3" "github.com/bluenviron/gortsplib/v3"
"github.com/bluenviron/gortsplib/v3/pkg/codecs/mpeg4audio"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/gortsplib/v3/pkg/media" "github.com/bluenviron/gortsplib/v3/pkg/media"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp" "github.com/pion/rtp"
) )

View File

@@ -81,13 +81,11 @@ func main() {
defer h264RawDec.close() defer h264RawDec.close()
// if SPS and PPS are present into the SDP, send them to the decoder // if SPS and PPS are present into the SDP, send them to the decoder
sps := forma.SafeSPS() if forma.SPS != nil {
if sps != nil { h264RawDec.decode(forma.SPS)
h264RawDec.decode(sps)
} }
pps := forma.SafePPS() if forma.PPS != nil {
if pps != nil { h264RawDec.decode(forma.PPS)
h264RawDec.decode(pps)
} }
// setup a single media // setup a single media

View File

@@ -48,7 +48,7 @@ func main() {
rtpDec := forma.CreateDecoder() rtpDec := forma.CreateDecoder()
// setup H264->MPEGTS muxer // setup H264->MPEGTS muxer
mpegtsMuxer, err := newMPEGTSMuxer(forma.SafeSPS(), forma.SafePPS()) mpegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/asticode/go-astits" "github.com/asticode/go-astits"
"github.com/bluenviron/gortsplib/v3/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
) )
// mpegtsMuxer allows to save a H264 stream into a MPEG-TS file. // mpegtsMuxer allows to save a H264 stream into a MPEG-TS file.

View File

@@ -58,13 +58,11 @@ func main() {
defer h264RawDec.close() defer h264RawDec.close()
// if SPS and PPS are present into the SDP, send them to the decoder // if SPS and PPS are present into the SDP, send them to the decoder
sps := forma.SafeSPS() if forma.SPS != nil {
if sps != nil { h264RawDec.decode(forma.SPS)
h264RawDec.decode(sps)
} }
pps := forma.SafePPS() if forma.PPS != nil {
if pps != nil { h264RawDec.decode(forma.PPS)
h264RawDec.decode(pps)
} }
// setup a single media // setup a single media

View File

@@ -79,7 +79,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
rtpDec := forma.CreateDecoder() rtpDec := forma.CreateDecoder()
// setup H264->MPEGTS muxer // setup H264->MPEGTS muxer
mpegtsMuxer, err := newMPEGTSMuxer(forma.SafeSPS(), forma.SafePPS()) mpegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS)
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,

View File

@@ -8,7 +8,7 @@ import (
"time" "time"
"github.com/asticode/go-astits" "github.com/asticode/go-astits"
"github.com/bluenviron/gortsplib/v3/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
) )
// mpegtsMuxer allows to save a H264 stream into a MPEG-TS file. // mpegtsMuxer allows to save a H264 stream into a MPEG-TS file.

1
go.mod
View File

@@ -4,6 +4,7 @@ go 1.18
require ( require (
github.com/asticode/go-astits v1.11.0 github.com/asticode/go-astits v1.11.0
github.com/bluenviron/mediacommon v0.2.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/pion/rtcp v1.2.10 github.com/pion/rtcp v1.2.10
github.com/pion/rtp v0.0.0-20230107162714-c3ea6851e25b github.com/pion/rtp v0.0.0-20230107162714-c3ea6851e25b

2
go.sum
View File

@@ -2,6 +2,8 @@ github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflx
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astits v1.11.0 h1:GTHUXht0ZXAJXsVbsLIcyfHr1Bchi4QQwMARw2ZWAng= github.com/asticode/go-astits v1.11.0 h1:GTHUXht0ZXAJXsVbsLIcyfHr1Bchi4QQwMARw2ZWAng=
github.com/asticode/go-astits v1.11.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/asticode/go-astits v1.11.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
github.com/bluenviron/mediacommon v0.2.0 h1:XEuIr8FA5bfzjsQhrITd6ILgN9JCl0e0Cu8IVFEp5Hk=
github.com/bluenviron/mediacommon v0.2.0/go.mod h1:t0dqPsWUTchyvib0MhixIwXEgvDX4V9G+I0GzWLQRb8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -1,123 +0,0 @@
// 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
}

View File

@@ -1,88 +0,0 @@
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")
}

View File

@@ -1,26 +0,0 @@
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
}
}

View File

@@ -1,18 +0,0 @@
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)
}

View File

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

View File

@@ -1,134 +0,0 @@
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

@@ -1,127 +0,0 @@
//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)
})
}

View File

@@ -1,74 +0,0 @@
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

@@ -1,69 +0,0 @@
//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

@@ -1,193 +0,0 @@
package h264
import (
"fmt"
"time"
"github.com/bluenviron/gortsplib/v3/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

@@ -1,222 +0,0 @@
//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

@@ -1,33 +0,0 @@
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

@@ -1,65 +0,0 @@
//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)
})
}

View File

@@ -1,11 +0,0 @@
// 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

@@ -1,12 +0,0 @@
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

@@ -1,17 +0,0 @@
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

@@ -1,83 +0,0 @@
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

@@ -1,13 +0,0 @@
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"))
}

View File

@@ -1,728 +0,0 @@
package h264
import (
"fmt"
"github.com/bluenviron/gortsplib/v3/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))
}

View File

@@ -1,462 +0,0 @@
//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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More