From 69967d691877778d05064b47d808331de27f85ac Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Thu, 23 Sep 2021 17:37:35 +0200 Subject: [PATCH] add h264 utilities --- go.mod | 1 + go.sum | 7 ++ pkg/h264/annexb.go | 79 +++++++++++++++++++++ pkg/h264/annexb_test.go | 115 +++++++++++++++++++++++++++++++ pkg/h264/anticompetition.go | 90 ++++++++++++++++++++++++ pkg/h264/anticompetition_test.go | 47 +++++++++++++ pkg/h264/avcc.go | 55 +++++++++++++++ pkg/h264/avcc_test.go | 85 +++++++++++++++++++++++ pkg/h264/dtsestimator.go | 61 ++++++++++++++++ pkg/h264/dtsestimator_test.go | 32 +++++++++ pkg/h264/h264.go | 2 + pkg/h264/nalutype.go | 69 +++++++++++++++++++ pkg/h264/nalutype_test.go | 13 ++++ pkg/rtpaac/decoder.go | 2 +- pkg/rtph264/decoder.go | 8 ++- pkg/rtph264/nalutype.go | 90 ++++++++---------------- pkg/rtph264/nalutype_test.go | 6 +- pkg/rtph264/rtph264_test.go | 2 +- 18 files changed, 695 insertions(+), 69 deletions(-) create mode 100644 pkg/h264/annexb.go create mode 100644 pkg/h264/annexb_test.go create mode 100644 pkg/h264/anticompetition.go create mode 100644 pkg/h264/anticompetition_test.go create mode 100644 pkg/h264/avcc.go create mode 100644 pkg/h264/avcc_test.go create mode 100644 pkg/h264/dtsestimator.go create mode 100644 pkg/h264/dtsestimator_test.go create mode 100644 pkg/h264/h264.go create mode 100644 pkg/h264/nalutype.go create mode 100644 pkg/h264/nalutype_test.go diff --git a/go.mod b/go.mod index 98e91d9c..6a3504c0 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index e7f19a90..513a9c18 100644 --- a/go.sum +++ b/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= diff --git a/pkg/h264/annexb.go b/pkg/h264/annexb.go new file mode 100644 index 00000000..f0837dc0 --- /dev/null +++ b/pkg/h264/annexb.go @@ -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 +} diff --git a/pkg/h264/annexb_test.go b/pkg/h264/annexb_test.go new file mode 100644 index 00000000..10d416b7 --- /dev/null +++ b/pkg/h264/annexb_test.go @@ -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) + }) + } +} diff --git a/pkg/h264/anticompetition.go b/pkg/h264/anticompetition.go new file mode 100644 index 00000000..3dcc7359 --- /dev/null +++ b/pkg/h264/anticompetition.go @@ -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 +} diff --git a/pkg/h264/anticompetition_test.go b/pkg/h264/anticompetition_test.go new file mode 100644 index 00000000..214dca46 --- /dev/null +++ b/pkg/h264/anticompetition_test.go @@ -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) + }) + } +} diff --git a/pkg/h264/avcc.go b/pkg/h264/avcc.go new file mode 100644 index 00000000..c7a1979a --- /dev/null +++ b/pkg/h264/avcc.go @@ -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 +} diff --git a/pkg/h264/avcc_test.go b/pkg/h264/avcc_test.go new file mode 100644 index 00000000..401d0e34 --- /dev/null +++ b/pkg/h264/avcc_test.go @@ -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) + }) + } +} diff --git a/pkg/h264/dtsestimator.go b/pkg/h264/dtsestimator.go new file mode 100644 index 00000000..e4f564c5 --- /dev/null +++ b/pkg/h264/dtsestimator.go @@ -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 +} diff --git a/pkg/h264/dtsestimator_test.go b/pkg/h264/dtsestimator_test.go new file mode 100644 index 00000000..1965f328 --- /dev/null +++ b/pkg/h264/dtsestimator_test.go @@ -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) +} diff --git a/pkg/h264/h264.go b/pkg/h264/h264.go new file mode 100644 index 00000000..537f00fd --- /dev/null +++ b/pkg/h264/h264.go @@ -0,0 +1,2 @@ +// Package h264 contains utilities to work with H264 streams. +package h264 diff --git a/pkg/h264/nalutype.go b/pkg/h264/nalutype.go new file mode 100644 index 00000000..5c6b7166 --- /dev/null +++ b/pkg/h264/nalutype.go @@ -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) +} diff --git a/pkg/h264/nalutype_test.go b/pkg/h264/nalutype_test.go new file mode 100644 index 00000000..8602e0ac --- /dev/null +++ b/pkg/h264/nalutype_test.go @@ -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")) +} diff --git a/pkg/rtpaac/decoder.go b/pkg/rtpaac/decoder.go index 4e5673c0..50b8635c 100644 --- a/pkg/rtpaac/decoder.go +++ b/pkg/rtpaac/decoder.go @@ -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 diff --git a/pkg/rtph264/decoder.go b/pkg/rtph264/decoder.go index c7d35963..44d572c8 100644 --- a/pkg/rtph264/decoder.go +++ b/pkg/rtph264/decoder.go @@ -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 diff --git a/pkg/rtph264/nalutype.go b/pkg/rtph264/nalutype.go index fbd50e95..9779d478 100644 --- a/pkg/rtph264/nalutype.go +++ b/pkg/rtph264/nalutype.go @@ -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) } diff --git a/pkg/rtph264/nalutype_test.go b/pkg/rtph264/nalutype_test.go index 787a7dc4..068fba2d 100644 --- a/pkg/rtph264/nalutype_test.go +++ b/pkg/rtph264/nalutype_test.go @@ -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")) } diff --git a/pkg/rtph264/rtph264_test.go b/pkg/rtph264/rtph264_test.go index e3734b53..ec88fb28 100644 --- a/pkg/rtph264/rtph264_test.go +++ b/pkg/rtph264/rtph264_test.go @@ -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) {