mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 07:06:58 +08:00
link to mediacommon (#223)
* move codecs and bits to mediacommon * add SafeSetParams() to H264 and H265 * update README
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
1
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||||
|
123
pkg/bits/read.go
123
pkg/bits/read.go
@@ -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
|
|
||||||
}
|
|
@@ -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")
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
}
|
|
@@ -1,2 +0,0 @@
|
|||||||
// Package codecs contains codec-specific utilities.
|
|
||||||
package codecs
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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))
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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
|
|
||||||
)
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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},
|
|
||||||
}))
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
}
|
|
@@ -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"))
|
|
||||||
}
|
|
@@ -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))
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x00\x00\x00\x00")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x000\x00\x00")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x00\x00\x00\x00")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x00\x000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x00\x00\x01")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x00\x00\x01\x00\x00\x01")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x00\x00")
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("A0\x00\x0000")
|
|
||||||
uint64(122)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("'000\xdc10")
|
|
||||||
uint64(23)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("A00\x0000")
|
|
||||||
uint64(122)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("A\x0001\x000")
|
|
||||||
uint64(188)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("A00000")
|
|
||||||
uint64(122)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("%")
|
|
||||||
uint64(14)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("\x01\f")
|
|
||||||
uint64(60)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("A\x00\x00\x0000")
|
|
||||||
uint64(70)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("A\x0001B0")
|
|
||||||
uint64(188)
|
|
@@ -1,3 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("'000%9000")
|
|
||||||
uint64(23)
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000\xf31Y08\xf7000000000\xd7")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A\xff2\xff0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000B19")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z0010")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf60000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000772.B001")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z00002")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf6A")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z00$A0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000017Y1")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A10")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000777Z\x0e2\x0e000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A\xff0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000%0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z007A")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000017Y0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000177A000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z007B000000771B$00")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000\xff2")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000%A")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A\xff2")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf6017")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z0017")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf61")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00001\x17\x0000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A\xff0\xff0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z002")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000X")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000072")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000017\xf60")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000017\xf61")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("000017Y00")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000\xf310\xff00007000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf68")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000\xff\xff\xff0a1000")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A72")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A\xff2&0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000\xf310\xff00007")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00000A 0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf67")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000177Z100")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000\xf30")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0000A78X0")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("00007\xb6\xf60")
|
|
@@ -1,2 +0,0 @@
|
|||||||
go test fuzz v1
|
|
||||||
[]byte("0z00d")
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user