mirror of
https://github.com/aler9/gortsplib
synced 2025-10-04 14:52:46 +08:00
add h264 utilities
This commit is contained in:
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module github.com/aler9/gortsplib
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/asticode/go-astits v1.9.0
|
||||
github.com/icza/bitio v1.0.0
|
||||
github.com/pion/rtcp v1.2.4
|
||||
github.com/pion/rtp v1.6.1
|
||||
|
7
go.sum
7
go.sum
@@ -1,3 +1,7 @@
|
||||
github.com/asticode/go-astikit v0.20.0 h1:+7N+J4E4lWx2QOkRdOf6DafWJMv6O4RRfgClwQokrH8=
|
||||
github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
|
||||
github.com/asticode/go-astits v1.9.0 h1:69cilL0/7uwsxdGNQgwmVBu6JP0aMicXSm91ukJDjgQ=
|
||||
github.com/asticode/go-astits v1.9.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/icza/bitio v1.0.0 h1:squ/m1SHyFeCA6+6Gyol1AxV9nmPPlJFT8c2vKdj3U8=
|
||||
@@ -12,9 +16,11 @@ github.com/pion/rtp v1.6.1 h1:2Y2elcVBrahYnHKN2X7rMHX/r1R4TEBMP1LaVu/wNhk=
|
||||
github.com/pion/rtp v1.6.1/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
|
||||
github.com/pion/sdp/v3 v3.0.2 h1:UNnSPVaMM+Pdu/mR9UvAyyo6zkdYbKeuOooCwZvTl/g=
|
||||
github.com/pion/sdp/v3 v3.0.2/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
|
||||
github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b h1:k+E048sYJHyVnsr1GDrRZWQ32D2C7lWs9JRc0bel53A=
|
||||
@@ -27,5 +33,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
79
pkg/h264/annexb.go
Normal file
79
pkg/h264/annexb.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DecodeAnnexB decodes NALUs from the Annex-B stream format.
|
||||
func DecodeAnnexB(byts []byte) ([][]byte, error) {
|
||||
bl := len(byts)
|
||||
|
||||
// check initial delimiter
|
||||
n := func() int {
|
||||
if bl < 3 || byts[0] != 0x00 || byts[1] != 0x00 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if byts[2] == 0x01 {
|
||||
return 3
|
||||
}
|
||||
|
||||
if bl < 4 || byts[2] != 0x00 || byts[3] != 0x01 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 4
|
||||
}()
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("input doesn't start with a delimiter")
|
||||
}
|
||||
|
||||
var ret [][]byte
|
||||
zeros := 0
|
||||
start := n
|
||||
delimStart := 0
|
||||
|
||||
for i := n; i < bl; i++ {
|
||||
switch byts[i] {
|
||||
case 0:
|
||||
if zeros == 0 {
|
||||
delimStart = i
|
||||
}
|
||||
zeros++
|
||||
|
||||
case 1:
|
||||
if zeros == 2 || zeros == 3 {
|
||||
nalu := byts[start:delimStart]
|
||||
if len(nalu) == 0 {
|
||||
return nil, fmt.Errorf("empty NALU")
|
||||
}
|
||||
ret = append(ret, nalu)
|
||||
start = i + 1
|
||||
}
|
||||
zeros = 0
|
||||
|
||||
default:
|
||||
zeros = 0
|
||||
}
|
||||
}
|
||||
|
||||
nalu := byts[start:bl]
|
||||
if len(nalu) == 0 {
|
||||
return nil, fmt.Errorf("empty NALU")
|
||||
}
|
||||
ret = append(ret, nalu)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// EncodeAnnexB encodes NALUs into the Annex-B stream format.
|
||||
func EncodeAnnexB(nalus [][]byte) ([]byte, error) {
|
||||
var ret []byte
|
||||
|
||||
for _, nalu := range nalus {
|
||||
ret = append(ret, []byte{0x00, 0x00, 0x00, 0x01}...)
|
||||
ret = append(ret, nalu...)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
115
pkg/h264/annexb_test.go
Normal file
115
pkg/h264/annexb_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var casesAnnexB = []struct {
|
||||
name string
|
||||
encin []byte
|
||||
encout []byte
|
||||
dec [][]byte
|
||||
}{
|
||||
{
|
||||
"2 zeros, single",
|
||||
[]byte{0x00, 0x00, 0x01, 0xaa, 0xbb},
|
||||
[]byte{0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb},
|
||||
[][]byte{
|
||||
{0xaa, 0xbb},
|
||||
},
|
||||
},
|
||||
{
|
||||
"2 zeros, multiple",
|
||||
[]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, single",
|
||||
[]byte{0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb},
|
||||
[]byte{0x00, 0x00, 0x00, 0x01, 0xaa, 0xbb},
|
||||
[][]byte{
|
||||
{0xaa, 0xbb},
|
||||
},
|
||||
},
|
||||
{
|
||||
"3 zeros, multiple",
|
||||
[]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},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAnnexBDecode(t *testing.T) {
|
||||
for _, ca := range casesAnnexB {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
dec, err := DecodeAnnexB(ca.encin)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.dec, dec)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnexBEncode(t *testing.T) {
|
||||
for _, ca := range casesAnnexB {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
enc, err := EncodeAnnexB(ca.dec)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.encout, enc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnexBDecodeError(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
enc []byte
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
[]byte{},
|
||||
},
|
||||
{
|
||||
"missing initial delimiter",
|
||||
[]byte{0xaa, 0xbb},
|
||||
},
|
||||
{
|
||||
"empty initial",
|
||||
[]byte{0x00, 0x00, 0x01},
|
||||
},
|
||||
{
|
||||
"empty 2nd",
|
||||
[]byte{0x00, 0x00, 0x01, 0xaa, 0x00, 0x00, 0x01},
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
_, err := DecodeAnnexB(ca.enc)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
90
pkg/h264/anticompetition.go
Normal file
90
pkg/h264/anticompetition.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package h264
|
||||
|
||||
// AntiCompetitionAdd adds the anti-competition bytes to a NALU.
|
||||
func AntiCompetitionAdd(nalu []byte) []byte {
|
||||
var ret []byte
|
||||
step := 0
|
||||
start := 0
|
||||
|
||||
for i, b := range nalu {
|
||||
switch step {
|
||||
case 0:
|
||||
if b == 0 {
|
||||
step++
|
||||
}
|
||||
|
||||
case 1:
|
||||
if b == 0 {
|
||||
step++
|
||||
} else {
|
||||
step = 0
|
||||
}
|
||||
|
||||
case 2:
|
||||
switch b {
|
||||
case 3, 2, 1, 0:
|
||||
ret = append(ret, nalu[start:i-2]...)
|
||||
ret = append(ret, []byte{0x00, 0x00, 0x03, b}...)
|
||||
step = 0
|
||||
start = i + 1
|
||||
|
||||
default:
|
||||
step = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, nalu[start:]...)
|
||||
return ret
|
||||
}
|
||||
|
||||
// AntiCompetitionRemove removes the anti-competition bytes from a NALU.
|
||||
func AntiCompetitionRemove(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
|
||||
|
||||
var ret []byte
|
||||
step := 0
|
||||
start := 0
|
||||
|
||||
for i, b := range nalu {
|
||||
switch step {
|
||||
case 0:
|
||||
if b == 0 {
|
||||
step++
|
||||
}
|
||||
|
||||
case 1:
|
||||
if b == 0 {
|
||||
step++
|
||||
} else {
|
||||
step = 0
|
||||
}
|
||||
|
||||
case 2:
|
||||
if b == 3 {
|
||||
step++
|
||||
} else {
|
||||
step = 0
|
||||
}
|
||||
|
||||
case 3:
|
||||
switch b {
|
||||
case 3, 2, 1, 0:
|
||||
ret = append(ret, nalu[start:i-3]...)
|
||||
ret = append(ret, []byte{0x00, 0x00, b}...)
|
||||
step = 0
|
||||
start = i + 1
|
||||
|
||||
default:
|
||||
step = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, nalu[start:]...)
|
||||
|
||||
return ret
|
||||
}
|
47
pkg/h264/anticompetition_test.go
Normal file
47
pkg/h264/anticompetition_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var casesAntiCompetition = []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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestAntiCompetitionAdd(t *testing.T) {
|
||||
for _, ca := range casesAntiCompetition {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
proc := AntiCompetitionAdd(ca.unproc)
|
||||
require.Equal(t, ca.proc, proc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAntiCompetitionRemove(t *testing.T) {
|
||||
for _, ca := range casesAntiCompetition {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
unproc := AntiCompetitionRemove(ca.proc)
|
||||
require.Equal(t, ca.unproc, unproc)
|
||||
})
|
||||
}
|
||||
}
|
55
pkg/h264/avcc.go
Normal file
55
pkg/h264/avcc.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DecodeAVCC decodes NALUs from the AVCC stream format.
|
||||
func DecodeAVCC(byts []byte) ([][]byte, error) {
|
||||
var ret [][]byte
|
||||
|
||||
for len(byts) > 0 {
|
||||
if len(byts) < 4 {
|
||||
return nil, fmt.Errorf("invalid length")
|
||||
}
|
||||
|
||||
le := binary.BigEndian.Uint32(byts)
|
||||
byts = byts[4:]
|
||||
|
||||
if len(byts) < int(le) {
|
||||
return nil, fmt.Errorf("invalid length")
|
||||
}
|
||||
|
||||
ret = append(ret, byts[:le])
|
||||
byts = byts[le:]
|
||||
}
|
||||
|
||||
if len(ret) == 0 {
|
||||
return nil, fmt.Errorf("no NALUs decoded")
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// EncodeAVCC encodes NALUs into the AVCC stream format.
|
||||
func EncodeAVCC(nalus [][]byte) ([]byte, error) {
|
||||
le := 0
|
||||
for _, nalu := range nalus {
|
||||
le += 4 + len(nalu)
|
||||
}
|
||||
|
||||
ret := make([]byte, le)
|
||||
pos := 0
|
||||
|
||||
for _, nalu := range nalus {
|
||||
ln := len(nalu)
|
||||
binary.BigEndian.PutUint32(ret[pos:], uint32(ln))
|
||||
pos += 4
|
||||
|
||||
copy(ret[pos:], nalu)
|
||||
pos += ln
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
85
pkg/h264/avcc_test.go
Normal file
85
pkg/h264/avcc_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
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 TestAVCCDecode(t *testing.T) {
|
||||
for _, ca := range casesAVCC {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
dec, err := DecodeAVCC(ca.enc)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.dec, dec)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAVCCEncode(t *testing.T) {
|
||||
for _, ca := range casesAVCC {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
enc, err := EncodeAVCC(ca.dec)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.enc, enc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAVCCDecodeError(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
enc []byte
|
||||
}{
|
||||
{
|
||||
"empty",
|
||||
[]byte{},
|
||||
},
|
||||
{
|
||||
"invalid length",
|
||||
[]byte{0x01},
|
||||
},
|
||||
{
|
||||
"invalid length",
|
||||
[]byte{0x00, 0x00, 0x00, 0x03},
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
_, err := DecodeAVCC(ca.enc)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
61
pkg/h264/dtsestimator.go
Normal file
61
pkg/h264/dtsestimator.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// DTSEstimator is a DTS estimator.
|
||||
type DTSEstimator struct {
|
||||
initializing int
|
||||
prevDTS time.Duration
|
||||
prevPTS time.Duration
|
||||
prevPrevPTS time.Duration
|
||||
}
|
||||
|
||||
// NewDTSEstimator allocates a DTSEstimator.
|
||||
func NewDTSEstimator() *DTSEstimator {
|
||||
return &DTSEstimator{
|
||||
initializing: 2,
|
||||
}
|
||||
}
|
||||
|
||||
// Feed provides PTS to the estimator, and returns the estimated DTS.
|
||||
func (d *DTSEstimator) Feed(pts time.Duration) time.Duration {
|
||||
switch d.initializing {
|
||||
case 2:
|
||||
d.initializing--
|
||||
return 0
|
||||
|
||||
case 1:
|
||||
d.initializing--
|
||||
d.prevPTS = pts
|
||||
d.prevDTS = time.Millisecond
|
||||
return time.Millisecond
|
||||
}
|
||||
|
||||
dts := func() time.Duration {
|
||||
// P or I frame
|
||||
if pts > d.prevPTS {
|
||||
// previous frame was B
|
||||
// use the DTS of the previous frame
|
||||
if d.prevPTS < d.prevPrevPTS {
|
||||
return d.prevPTS
|
||||
}
|
||||
|
||||
// previous frame was P or I
|
||||
// use two frames ago plus a small quantity
|
||||
// to avoid non-monotonous DTS with B-frames
|
||||
return d.prevPrevPTS + time.Millisecond
|
||||
}
|
||||
|
||||
// B Frame
|
||||
// increase by a small quantity
|
||||
return d.prevDTS + time.Millisecond
|
||||
}()
|
||||
|
||||
d.prevPrevPTS = d.prevPTS
|
||||
d.prevPTS = pts
|
||||
d.prevDTS = dts
|
||||
|
||||
return dts
|
||||
}
|
32
pkg/h264/dtsestimator_test.go
Normal file
32
pkg/h264/dtsestimator_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDTSEstimator(t *testing.T) {
|
||||
est := NewDTSEstimator()
|
||||
|
||||
// initial state
|
||||
dts := est.Feed(0)
|
||||
require.Equal(t, time.Duration(0), dts)
|
||||
|
||||
// b-frame
|
||||
dts = est.Feed(1*time.Second - 200*time.Millisecond)
|
||||
require.Equal(t, time.Millisecond, dts)
|
||||
|
||||
// b-frame
|
||||
dts = est.Feed(1*time.Second - 400*time.Millisecond)
|
||||
require.Equal(t, 2*time.Millisecond, dts)
|
||||
|
||||
// p-frame
|
||||
dts = est.Feed(1 * time.Second)
|
||||
require.Equal(t, 1*time.Second-400*time.Millisecond, dts)
|
||||
|
||||
// p-frame
|
||||
dts = est.Feed(1*time.Second + 200*time.Millisecond)
|
||||
require.Equal(t, 1*time.Second-399*time.Millisecond, dts)
|
||||
}
|
2
pkg/h264/h264.go
Normal file
2
pkg/h264/h264.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package h264 contains utilities to work with H264 streams.
|
||||
package h264
|
69
pkg/h264/nalutype.go
Normal file
69
pkg/h264/nalutype.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// NALUType is the type of a NALU.
|
||||
type NALUType uint8
|
||||
|
||||
// standard 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
|
||||
)
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (nt NALUType) String() string {
|
||||
if l, ok := naluTypelabels[nt]; ok {
|
||||
return l
|
||||
}
|
||||
return fmt.Sprintf("unknown (%d)", nt)
|
||||
}
|
13
pkg/h264/nalutype_test.go
Normal file
13
pkg/h264/nalutype_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package h264
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNALUType(t *testing.T) {
|
||||
require.NotEqual(t, true, strings.HasPrefix(NALUType(10).String(), "unknown"))
|
||||
require.Equal(t, true, strings.HasPrefix(NALUType(50).String(), "unknown"))
|
||||
}
|
@@ -38,7 +38,7 @@ func (d *Decoder) decodeTimestamp(ts uint32) time.Duration {
|
||||
// It returns the AUs and the PTS of the first AU.
|
||||
// The PTS of subsequent AUs can be calculated by adding time.Second*1000/clockRate.
|
||||
func (d *Decoder) Decode(byts []byte) ([][]byte, time.Duration, error) {
|
||||
pkt := rtp.Packet{}
|
||||
var pkt rtp.Packet
|
||||
err := pkt.Unmarshal(byts)
|
||||
if err != nil {
|
||||
d.isDecodingFragmented = false
|
||||
|
@@ -9,6 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/pion/rtp"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
)
|
||||
|
||||
// ErrMorePacketsNeeded is returned when more packets are needed.
|
||||
@@ -55,7 +57,7 @@ func (d *Decoder) decodeTimestamp(ts uint32) time.Duration {
|
||||
// Decode decodes NALUs from a RTP/H264 packet.
|
||||
// It returns the decoded NALUs and their PTS.
|
||||
func (d *Decoder) Decode(byts []byte) ([][]byte, time.Duration, error) {
|
||||
pkt := rtp.Packet{}
|
||||
var pkt rtp.Packet
|
||||
err := pkt.Unmarshal(byts)
|
||||
if err != nil {
|
||||
d.isDecodingFragmented = false
|
||||
@@ -197,13 +199,13 @@ func (d *Decoder) ReadSPSPPS(r io.Reader) ([]byte, []byte, error) {
|
||||
|
||||
for _, nalu := range nalus {
|
||||
switch naluType(nalu[0] & 0x1F) {
|
||||
case naluTypeSPS:
|
||||
case naluType(h264.NALUTypeSPS):
|
||||
sps = append([]byte(nil), nalu...)
|
||||
if sps != nil && pps != nil {
|
||||
return sps, pps, nil
|
||||
}
|
||||
|
||||
case naluTypePPS:
|
||||
case naluType(h264.NALUTypePPS):
|
||||
pps = append([]byte(nil), nalu...)
|
||||
if sps != nil && pps != nil {
|
||||
return sps, pps, nil
|
||||
|
@@ -1,77 +1,43 @@
|
||||
package rtph264
|
||||
|
||||
// naluType is the type of a NALU.
|
||||
type naluType uint8
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// NALU types, augmented for RTP.
|
||||
"github.com/aler9/gortsplib/pkg/h264"
|
||||
)
|
||||
|
||||
type naluType h264.NALUType
|
||||
|
||||
// additional NALU types for RTP/H264.
|
||||
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
|
||||
naluTypeSTAPA naluType = 24
|
||||
naluTypeSTAPB naluType = 25
|
||||
naluTypeMTAP16 naluType = 26
|
||||
naluTypeMTAP24 naluType = 27
|
||||
naluTypeFUA naluType = 28
|
||||
naluTypeFUB naluType = 29
|
||||
naluTypeSTAPA naluType = 24
|
||||
naluTypeSTAPB naluType = 25
|
||||
naluTypeMTAP16 naluType = 26
|
||||
naluTypeMTAP24 naluType = 27
|
||||
naluTypeFUA naluType = 28
|
||||
naluTypeFUB naluType = 29
|
||||
)
|
||||
|
||||
var naluLabels = 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: "STAPA",
|
||||
naluTypeSTAPB: "STAPB",
|
||||
naluTypeMTAP16: "MTAP16",
|
||||
naluTypeMTAP24: "MTAP24",
|
||||
naluTypeFUA: "FUA",
|
||||
naluTypeFUB: "FUB",
|
||||
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 {
|
||||
p := h264.NALUType(nt).String()
|
||||
if !strings.HasPrefix(p, "unknown") {
|
||||
return p
|
||||
}
|
||||
|
||||
if l, ok := naluLabels[nt]; ok {
|
||||
return l
|
||||
}
|
||||
return "unknown"
|
||||
|
||||
return fmt.Sprintf("unknown (%d)", nt)
|
||||
}
|
||||
|
@@ -1,12 +1,14 @@
|
||||
package rtph264
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNALUType(t *testing.T) {
|
||||
require.NotEqual(t, "unknown", naluType(10).String())
|
||||
require.Equal(t, "unknown", naluType(50).String())
|
||||
require.NotEqual(t, true, strings.HasPrefix(naluType(10).String(), "unknown"))
|
||||
require.NotEqual(t, true, strings.HasPrefix(naluType(26).String(), "unknown"))
|
||||
require.Equal(t, true, strings.HasPrefix(naluType(50).String(), "unknown"))
|
||||
}
|
||||
|
@@ -431,7 +431,7 @@ func TestDecodeErrors(t *testing.T) {
|
||||
0x80, 0xe0, 0x44, 0xed, 0x88, 0x77, 0x6a, 0x15,
|
||||
0x9d, 0xbb, 0x78, 0x12, byte(naluTypeMTAP16),
|
||||
}},
|
||||
"packet type not supported (MTAP16)",
|
||||
"packet type not supported (MTAP-16)",
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user