diff --git a/README.md b/README.md index c8f569ef..43a85852 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,9 @@ Features: * Generate RTCP sender reports * Utilities * Parse RTSP elements - * Encode/decode format-specific frames into/from RTP packets. The following formats are supported: + * Encode/decode codec-specific frames into/from RTP packets. The following codecs are supported: * Video: AV1, VP9, VP8, H265, H264, MPEG-4 Video (H263, Xvid), M-JPEG - * Audio: Opus, MPEG-4 Audio (AAC), MPEG-1/2 Audio (MP3), G722, G711 (PCMA, PCMU), LPCM + * Audio: Opus, MPEG-4 Audio (AAC) (two formats: MPEG4-Generic and MP4A-LATM), MPEG-1/2 Audio (MP3), G722, G711 (PCMA, PCMU), LPCM ## Table of contents diff --git a/examples/client-read-format-g711/main.go b/examples/client-read-format-g711/main.go index 430e2834..cf5c8ead 100644 --- a/examples/client-read-format-g711/main.go +++ b/examples/client-read-format-g711/main.go @@ -44,7 +44,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-g722/main.go b/examples/client-read-format-g722/main.go index 2b26385c..e60a27d7 100644 --- a/examples/client-read-format-g722/main.go +++ b/examples/client-read-format-g722/main.go @@ -44,7 +44,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-h264-convert-to-jpeg/main.go b/examples/client-read-format-h264-convert-to-jpeg/main.go index d55d746a..8fe2d2c0 100644 --- a/examples/client-read-format-h264-convert-to-jpeg/main.go +++ b/examples/client-read-format-h264-convert-to-jpeg/main.go @@ -71,7 +71,10 @@ func main() { } // setup RTP/H264 -> H264 decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup H264 -> raw frames decoder h264RawDec, err := newH264Decoder() diff --git a/examples/client-read-format-h264-save-to-disk/main.go b/examples/client-read-format-h264-save-to-disk/main.go index 42e18428..708daf3e 100644 --- a/examples/client-read-format-h264-save-to-disk/main.go +++ b/examples/client-read-format-h264-save-to-disk/main.go @@ -45,7 +45,10 @@ func main() { } // setup RTP/H264 -> H264 decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup H264 -> MPEG-TS muxer mpegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS) diff --git a/examples/client-read-format-h264/main.go b/examples/client-read-format-h264/main.go index 0f80fb06..46b2855c 100644 --- a/examples/client-read-format-h264/main.go +++ b/examples/client-read-format-h264/main.go @@ -48,7 +48,10 @@ func main() { } // setup RTP/H264 -> H264 decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup H264 -> raw frames decoder h264RawDec, err := newH264Decoder() diff --git a/examples/client-read-format-h265/main.go b/examples/client-read-format-h265/main.go index d409bacb..5b006265 100644 --- a/examples/client-read-format-h265/main.go +++ b/examples/client-read-format-h265/main.go @@ -45,7 +45,10 @@ func main() { } // setup RTP/H265 -> H265 decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-lpcm/main.go b/examples/client-read-format-lpcm/main.go index 094e32d0..8fc2f31f 100644 --- a/examples/client-read-format-lpcm/main.go +++ b/examples/client-read-format-lpcm/main.go @@ -44,7 +44,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-mjpeg/main.go b/examples/client-read-format-mjpeg/main.go index af421b19..1b560b7a 100644 --- a/examples/client-read-format-mjpeg/main.go +++ b/examples/client-read-format-mjpeg/main.go @@ -48,7 +48,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-mpeg4audio-save-to-disk/main.go b/examples/client-read-format-mpeg4audio-save-to-disk/main.go index 825dc044..79fd9895 100644 --- a/examples/client-read-format-mpeg4audio-save-to-disk/main.go +++ b/examples/client-read-format-mpeg4audio-save-to-disk/main.go @@ -44,7 +44,10 @@ func main() { } // setup RTP/MPEG4-audio -> MPEG4-audio decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup MPEG4-audio -> MPEG-TS muxer mpegtsMuxer, err := newMPEGTSMuxer(forma.Config) diff --git a/examples/client-read-format-mpeg4audio/main.go b/examples/client-read-format-mpeg4audio/main.go index 600e1cad..c529e450 100644 --- a/examples/client-read-format-mpeg4audio/main.go +++ b/examples/client-read-format-mpeg4audio/main.go @@ -44,7 +44,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-opus/main.go b/examples/client-read-format-opus/main.go index 01656540..58c826cc 100644 --- a/examples/client-read-format-opus/main.go +++ b/examples/client-read-format-opus/main.go @@ -44,7 +44,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-vp8/main.go b/examples/client-read-format-vp8/main.go index c5ad323a..055f88a4 100644 --- a/examples/client-read-format-vp8/main.go +++ b/examples/client-read-format-vp8/main.go @@ -45,7 +45,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/client-read-format-vp9/main.go b/examples/client-read-format-vp9/main.go index 05d71a5e..318fc619 100644 --- a/examples/client-read-format-vp9/main.go +++ b/examples/client-read-format-vp9/main.go @@ -45,7 +45,10 @@ func main() { } // create decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup a single media _, err = c.Setup(medi, baseURL, 0, 0) diff --git a/examples/server-h264-save-to-disk/main.go b/examples/server-h264-save-to-disk/main.go index 56444ddb..c9b21c8d 100644 --- a/examples/server-h264-save-to-disk/main.go +++ b/examples/server-h264-save-to-disk/main.go @@ -76,7 +76,10 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) ( } // setup RTP/H264 -> H264 decoder - rtpDec := forma.CreateDecoder() + rtpDec, err := forma.CreateDecoder2() + if err != nil { + panic(err) + } // setup H264 -> MPEGTS muxer mpegtsMuxer, err := newMPEGTSMuxer(forma.SPS, forma.PPS) diff --git a/pkg/formats/av1.go b/pkg/formats/av1.go index 47eac772..b3697c87 100644 --- a/pkg/formats/av1.go +++ b/pkg/formats/av1.go @@ -98,17 +98,43 @@ func (f *AV1) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *AV1) CreateDecoder() *rtpav1.Decoder { - d := &rtpav1.Decoder{} - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *AV1) CreateDecoder2() (*rtpav1.Decoder, error) { + d := &rtpav1.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *AV1) CreateEncoder() *rtpav1.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *AV1) CreateEncoder2() (*rtpav1.Encoder, error) { e := &rtpav1.Encoder{ PayloadType: f.PayloadTyp, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/av1_test.go b/pkg/formats/av1_test.go index 1f638435..06e1511a 100644 --- a/pkg/formats/av1_test.go +++ b/pkg/formats/av1_test.go @@ -19,12 +19,16 @@ func TestAV1Attributes(t *testing.T) { func TestAV1DecEncoder(t *testing.T) { format := &AV1{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) diff --git a/pkg/formats/format_test.go b/pkg/formats/format_test.go index d2febb72..4f84d08d 100644 --- a/pkg/formats/format_test.go +++ b/pkg/formats/format_test.go @@ -281,7 +281,7 @@ var casesFormat = []struct { PayloadTyp: 96, ProfileLevelID: 1, Bitrate: intPtr(64000), - CPresent: boolPtr(false), + CPresent: false, Config: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ @@ -319,7 +319,7 @@ var casesFormat = []struct { &MPEG4AudioLATM{ PayloadTyp: 110, ProfileLevelID: 15, - CPresent: boolPtr(false), + CPresent: false, SBREnabled: boolPtr(true), Config: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ @@ -358,7 +358,7 @@ var casesFormat = []struct { &MPEG4AudioLATM{ PayloadTyp: 110, ProfileLevelID: 44, - CPresent: boolPtr(false), + CPresent: false, SBREnabled: boolPtr(true), Bitrate: intPtr(64000), Config: &mpeg4audio.StreamMuxConfig{ @@ -401,7 +401,7 @@ var casesFormat = []struct { PayloadTyp: 110, ProfileLevelID: 48, Bitrate: intPtr(64000), - CPresent: boolPtr(false), + CPresent: false, Config: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ @@ -438,7 +438,7 @@ var casesFormat = []struct { &MPEG4AudioLATM{ PayloadTyp: 110, ProfileLevelID: 30, - CPresent: boolPtr(false), + CPresent: false, Config: &mpeg4audio.StreamMuxConfig{ Programs: []*mpeg4audio.StreamMuxConfigProgram{{ Layers: []*mpeg4audio.StreamMuxConfigLayer{{ @@ -911,17 +911,7 @@ func TestUnmarshalMPEG4AudioLATMErrors(t *testing.T) { require.Error(t, err) _, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{ - "object": "aaa", - }) - require.Error(t, err) - - _, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{ - "object": "120", - }) - require.Error(t, err) - - _, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{ - "cpresent": "aaa", + "cpresent": "0", }) require.Error(t, err) @@ -930,11 +920,6 @@ func TestUnmarshalMPEG4AudioLATMErrors(t *testing.T) { }) require.Error(t, err) - _, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{ - "sbr-enabled": "aaa", - }) - require.Error(t, err) - _, err = Unmarshal("audio", 96, "MP4A-LATM/48000/2", map[string]string{ "profile-level-id": "15", "object": "2", diff --git a/pkg/formats/g711.go b/pkg/formats/g711.go index f201499b..b1ef815b 100644 --- a/pkg/formats/g711.go +++ b/pkg/formats/g711.go @@ -55,20 +55,46 @@ func (f *G711) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *G711) CreateDecoder() *rtpsimpleaudio.Decoder { - d := &rtpsimpleaudio.Decoder{ - SampleRate: 8000, - } - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *G711) CreateDecoder2() (*rtpsimpleaudio.Decoder, error) { + d := &rtpsimpleaudio.Decoder{ + SampleRate: 8000, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *G711) CreateEncoder() *rtpsimpleaudio.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *G711) CreateEncoder2() (*rtpsimpleaudio.Encoder, error) { e := &rtpsimpleaudio.Encoder{ PayloadType: f.PayloadType(), SampleRate: 8000, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/g711_test.go b/pkg/formats/g711_test.go index bbfc3a15..d7d72d91 100644 --- a/pkg/formats/g711_test.go +++ b/pkg/formats/g711_test.go @@ -23,12 +23,16 @@ func TestG711Attributes(t *testing.T) { func TestG711DecEncoder(t *testing.T) { format := &G711{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkt.PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkt) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) diff --git a/pkg/formats/g722.go b/pkg/formats/g722.go index 192b342d..75fcb000 100644 --- a/pkg/formats/g722.go +++ b/pkg/formats/g722.go @@ -45,20 +45,46 @@ func (f *G722) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *G722) CreateDecoder() *rtpsimpleaudio.Decoder { - d := &rtpsimpleaudio.Decoder{ - SampleRate: 8000, - } - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *G722) CreateDecoder2() (*rtpsimpleaudio.Decoder, error) { + d := &rtpsimpleaudio.Decoder{ + SampleRate: 8000, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *G722) CreateEncoder() *rtpsimpleaudio.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *G722) CreateEncoder2() (*rtpsimpleaudio.Encoder, error) { e := &rtpsimpleaudio.Encoder{ PayloadType: 9, SampleRate: 8000, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/g722_test.go b/pkg/formats/g722_test.go index 4827a7fc..8e81729b 100644 --- a/pkg/formats/g722_test.go +++ b/pkg/formats/g722_test.go @@ -17,12 +17,16 @@ func TestG722Attributes(t *testing.T) { func TestG722DecEncoder(t *testing.T) { format := &G722{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkt.PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkt) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) diff --git a/pkg/formats/h264.go b/pkg/formats/h264.go index 37526d29..68ca9324 100644 --- a/pkg/formats/h264.go +++ b/pkg/formats/h264.go @@ -170,22 +170,48 @@ func (f *H264) PTSEqualsDTS(pkt *rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *H264) CreateDecoder() *rtph264.Decoder { - d := &rtph264.Decoder{ - PacketizationMode: f.PacketizationMode, - } - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *H264) CreateDecoder2() (*rtph264.Decoder, error) { + d := &rtph264.Decoder{ + PacketizationMode: f.PacketizationMode, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *H264) CreateEncoder() *rtph264.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *H264) CreateEncoder2() (*rtph264.Encoder, error) { e := &rtph264.Encoder{ PayloadType: f.PayloadTyp, PacketizationMode: f.PacketizationMode, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } // SafeSetParams sets the codec parameters. diff --git a/pkg/formats/h264_test.go b/pkg/formats/h264_test.go index 8e947035..5f5c8335 100644 --- a/pkg/formats/h264_test.go +++ b/pkg/formats/h264_test.go @@ -47,12 +47,16 @@ func TestH264PTSEqualsDTS(t *testing.T) { func TestH264DecEncoder(t *testing.T) { format := &H264{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) diff --git a/pkg/formats/h265.go b/pkg/formats/h265.go index 9c3727b3..667b603c 100644 --- a/pkg/formats/h265.go +++ b/pkg/formats/h265.go @@ -109,22 +109,48 @@ func (f *H265) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *H265) CreateDecoder() *rtph265.Decoder { - d := &rtph265.Decoder{ - MaxDONDiff: f.MaxDONDiff, - } - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *H265) CreateDecoder2() (*rtph265.Decoder, error) { + d := &rtph265.Decoder{ + MaxDONDiff: f.MaxDONDiff, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *H265) CreateEncoder() *rtph265.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *H265) CreateEncoder2() (*rtph265.Encoder, error) { e := &rtph265.Encoder{ PayloadType: f.PayloadTyp, MaxDONDiff: f.MaxDONDiff, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } // SafeSetParams sets the codec parameters. diff --git a/pkg/formats/h265_test.go b/pkg/formats/h265_test.go index c96f30fc..9ef1d9c6 100644 --- a/pkg/formats/h265_test.go +++ b/pkg/formats/h265_test.go @@ -34,12 +34,16 @@ func TestH265Attributes(t *testing.T) { func TestH265DecEncoder(t *testing.T) { format := &H265{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) diff --git a/pkg/formats/lpcm.go b/pkg/formats/lpcm.go index b08728d0..aaab3ab7 100644 --- a/pkg/formats/lpcm.go +++ b/pkg/formats/lpcm.go @@ -97,24 +97,50 @@ func (f *LPCM) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *LPCM) CreateDecoder() *rtplpcm.Decoder { + d, _ := f.CreateDecoder2() + return d +} + +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *LPCM) CreateDecoder2() (*rtplpcm.Decoder, error) { d := &rtplpcm.Decoder{ BitDepth: f.BitDepth, SampleRate: f.SampleRate, ChannelCount: f.ChannelCount, } - d.Init() - return d + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil } // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *LPCM) CreateEncoder() *rtplpcm.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *LPCM) CreateEncoder2() (*rtplpcm.Encoder, error) { e := &rtplpcm.Encoder{ PayloadType: f.PayloadTyp, BitDepth: f.BitDepth, SampleRate: f.SampleRate, ChannelCount: f.ChannelCount, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/lpcm_test.go b/pkg/formats/lpcm_test.go index 605c8e7d..b32e1f0d 100644 --- a/pkg/formats/lpcm_test.go +++ b/pkg/formats/lpcm_test.go @@ -27,12 +27,16 @@ func TestLPCMDecEncoder(t *testing.T) { ChannelCount: 2, } - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) diff --git a/pkg/formats/mjpeg.go b/pkg/formats/mjpeg.go index f627dc23..1f7a374d 100644 --- a/pkg/formats/mjpeg.go +++ b/pkg/formats/mjpeg.go @@ -45,15 +45,41 @@ func (f *MJPEG) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *MJPEG) CreateDecoder() *rtpmjpeg.Decoder { - d := &rtpmjpeg.Decoder{} - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *MJPEG) CreateDecoder2() (*rtpmjpeg.Decoder, error) { + d := &rtpmjpeg.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *MJPEG) CreateEncoder() *rtpmjpeg.Encoder { - e := &rtpmjpeg.Encoder{} - e.Init() + e, _ := f.CreateEncoder2() return e } + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *MJPEG) CreateEncoder2() (*rtpmjpeg.Encoder, error) { + e := &rtpmjpeg.Encoder{} + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/pkg/formats/mjpeg_test.go b/pkg/formats/mjpeg_test.go index e5fb970d..a78a06a7 100644 --- a/pkg/formats/mjpeg_test.go +++ b/pkg/formats/mjpeg_test.go @@ -278,12 +278,16 @@ func TestMJPEGDecEncoder(t *testing.T) { 0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9, } - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode(b, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + var byts []byte for _, pkt := range pkts { byts, _, _ = dec.Decode(pkt) diff --git a/pkg/formats/mpeg2_audio.go b/pkg/formats/mpeg2_audio.go index 4f8fdd4f..3a8e7c87 100644 --- a/pkg/formats/mpeg2_audio.go +++ b/pkg/formats/mpeg2_audio.go @@ -45,15 +45,41 @@ func (f *MPEG2Audio) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *MPEG2Audio) CreateDecoder() *rtpmpeg2audio.Decoder { - d := &rtpmpeg2audio.Decoder{} - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *MPEG2Audio) CreateDecoder2() (*rtpmpeg2audio.Decoder, error) { + d := &rtpmpeg2audio.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *MPEG2Audio) CreateEncoder() *rtpmpeg2audio.Encoder { - e := &rtpmpeg2audio.Encoder{} - e.Init() + e, _ := f.CreateEncoder2() return e } + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *MPEG2Audio) CreateEncoder2() (*rtpmpeg2audio.Encoder, error) { + e := &rtpmpeg2audio.Encoder{} + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/pkg/formats/mpeg2_audio_test.go b/pkg/formats/mpeg2_audio_test.go index 782588eb..ed45e862 100644 --- a/pkg/formats/mpeg2_audio_test.go +++ b/pkg/formats/mpeg2_audio_test.go @@ -17,7 +17,9 @@ func TestMPEG2AudioAttributes(t *testing.T) { func TestMPEG2AudioDecEncoder(t *testing.T) { format := &MPEG2Audio{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([][]byte{{ 0xff, 0xfb, 0x14, 0x64, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, @@ -35,7 +37,9 @@ func TestMPEG2AudioDecEncoder(t *testing.T) { require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, [][]byte{{ diff --git a/pkg/formats/mpeg4_audio_generic.go b/pkg/formats/mpeg4_audio_generic.go index cb0ead86..74be3d23 100644 --- a/pkg/formats/mpeg4_audio_generic.go +++ b/pkg/formats/mpeg4_audio_generic.go @@ -6,10 +6,10 @@ import ( "strconv" "strings" + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/pion/rtp" "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audio" - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" ) // MPEG4Audio is an alias for MPEG4AudioGeneric. @@ -170,19 +170,40 @@ func (f *MPEG4AudioGeneric) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *MPEG4AudioGeneric) CreateDecoder() *rtpmpeg4audio.Decoder { + d, _ := f.CreateDecoder2() + return d +} + +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *MPEG4AudioGeneric) CreateDecoder2() (*rtpmpeg4audio.Decoder, error) { d := &rtpmpeg4audio.Decoder{ SampleRate: f.Config.SampleRate, SizeLength: f.SizeLength, IndexLength: f.IndexLength, IndexDeltaLength: f.IndexDeltaLength, } - d.Init() - return d + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil } // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *MPEG4AudioGeneric) CreateEncoder() *rtpmpeg4audio.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *MPEG4AudioGeneric) CreateEncoder2() (*rtpmpeg4audio.Encoder, error) { e := &rtpmpeg4audio.Encoder{ PayloadType: f.PayloadTyp, SampleRate: f.Config.SampleRate, @@ -190,6 +211,11 @@ func (f *MPEG4AudioGeneric) CreateEncoder() *rtpmpeg4audio.Encoder { IndexLength: f.IndexLength, IndexDeltaLength: f.IndexDeltaLength, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/mpeg4_audio_generic_test.go b/pkg/formats/mpeg4_audio_generic_test.go index 32d7e649..5737d4b9 100644 --- a/pkg/formats/mpeg4_audio_generic_test.go +++ b/pkg/formats/mpeg4_audio_generic_test.go @@ -39,12 +39,16 @@ func TestMPEG4AudioGenericDecEncoder(t *testing.T) { IndexDeltaLength: 3, } - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([][]byte{{0x01, 0x02, 0x03, 0x04}}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, [][]byte{{0x01, 0x02, 0x03, 0x04}}, byts) diff --git a/pkg/formats/mpeg4_audio_latm.go b/pkg/formats/mpeg4_audio_latm.go index 1c7a5359..ad070ce0 100644 --- a/pkg/formats/mpeg4_audio_latm.go +++ b/pkg/formats/mpeg4_audio_latm.go @@ -7,6 +7,8 @@ import ( "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiolatm" ) // MPEG4AudioLATM is a RTP format that uses a MPEG-4 Audio codec. @@ -15,7 +17,7 @@ type MPEG4AudioLATM struct { PayloadTyp uint8 ProfileLevelID int Bitrate *int - CPresent *bool + CPresent bool Config *mpeg4audio.StreamMuxConfig SBREnabled *bool } @@ -25,7 +27,10 @@ func (f *MPEG4AudioLATM) unmarshal( _ string, fmtp map[string]string, ) error { f.PayloadTyp = payloadType - f.ProfileLevelID = 30 // default value defined by specification + + // default value set by specification + f.ProfileLevelID = 30 + f.CPresent = true for key, val := range fmtp { switch key { @@ -47,8 +52,7 @@ func (f *MPEG4AudioLATM) unmarshal( f.Bitrate = &v case "cpresent": - v := (val == "1") - f.CPresent = &v + f.CPresent = (val == "1") case "config": enc, err := hex.DecodeString(val) @@ -68,8 +72,14 @@ func (f *MPEG4AudioLATM) unmarshal( } } - if f.Config == nil { - return fmt.Errorf("config is missing") + if f.CPresent { + if f.Config != nil { + return fmt.Errorf("config and cpresent can't be used at the same time") + } + } else { + if f.Config == nil { + return fmt.Errorf("config is missing") + } } return nil @@ -125,12 +135,10 @@ func (f *MPEG4AudioLATM) FMTP() map[string]string { fmtp["bitrate"] = strconv.FormatInt(int64(*f.Bitrate), 10) } - if f.CPresent != nil { - if *f.CPresent { - fmtp["cpresent"] = "1" - } else { - fmtp["cpresent"] = "0" - } + if f.CPresent { + fmtp["cpresent"] = "1" + } else { + fmtp["cpresent"] = "0" } if f.SBREnabled != nil { @@ -148,3 +156,48 @@ func (f *MPEG4AudioLATM) FMTP() map[string]string { func (f *MPEG4AudioLATM) PTSEqualsDTS(*rtp.Packet) bool { return true } + +// CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. +func (f *MPEG4AudioLATM) CreateDecoder() *rtpmpeg4audiolatm.Decoder { + d, _ := f.CreateDecoder2() + return d +} + +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *MPEG4AudioLATM) CreateDecoder2() (*rtpmpeg4audiolatm.Decoder, error) { + d := &rtpmpeg4audiolatm.Decoder{ + Config: f.Config, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + +// CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. +func (f *MPEG4AudioLATM) CreateEncoder() *rtpmpeg4audiolatm.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *MPEG4AudioLATM) CreateEncoder2() (*rtpmpeg4audiolatm.Encoder, error) { + e := &rtpmpeg4audiolatm.Encoder{ + PayloadType: f.PayloadTyp, + Config: f.Config, + } + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil +} diff --git a/pkg/formats/mpeg4_audio_latm_test.go b/pkg/formats/mpeg4_audio_latm_test.go index de9f1820..be1c4ac3 100644 --- a/pkg/formats/mpeg4_audio_latm_test.go +++ b/pkg/formats/mpeg4_audio_latm_test.go @@ -29,3 +29,36 @@ func TestMPEG4AudioLATMAttributes(t *testing.T) { require.Equal(t, 44100, format.ClockRate()) require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) } + +func TestMPEG4AudioLATMDecEncoder(t *testing.T) { + format := &MPEG4AudioLATM{ + PayloadTyp: 96, + ProfileLevelID: 1, + Config: &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.Config{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + } + + enc, err := format.CreateEncoder2() + require.NoError(t, err) + + pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) + require.NoError(t, err) + require.Equal(t, format.PayloadType(), pkts[0].PayloadType) + + dec, err := format.CreateDecoder2() + require.NoError(t, err) + + byts, _, err := dec.Decode(pkts[0]) + require.NoError(t, err) + require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) +} diff --git a/pkg/formats/mpeg4_video_es.go b/pkg/formats/mpeg4_video_es.go index 896b4fdb..ed1faeca 100644 --- a/pkg/formats/mpeg4_video_es.go +++ b/pkg/formats/mpeg4_video_es.go @@ -87,17 +87,43 @@ func (f *MPEG4VideoES) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *MPEG4VideoES) CreateDecoder() *rtpmpeg4video.Decoder { - d := &rtpmpeg4video.Decoder{} - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *MPEG4VideoES) CreateDecoder2() (*rtpmpeg4video.Decoder, error) { + d := &rtpmpeg4video.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *MPEG4VideoES) CreateEncoder() *rtpmpeg4video.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *MPEG4VideoES) CreateEncoder2() (*rtpmpeg4video.Encoder, error) { e := &rtpmpeg4video.Encoder{ PayloadType: f.PayloadTyp, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/mpeg4_video_es_test.go b/pkg/formats/mpeg4_video_es_test.go index 228aa8c1..c2cdce2b 100644 --- a/pkg/formats/mpeg4_video_es_test.go +++ b/pkg/formats/mpeg4_video_es_test.go @@ -23,12 +23,16 @@ func TestMPEG4VideoESDecEncoder(t *testing.T) { PayloadTyp: 96, } - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) diff --git a/pkg/formats/opus.go b/pkg/formats/opus.go index 07a99d8f..601b2ae0 100644 --- a/pkg/formats/opus.go +++ b/pkg/formats/opus.go @@ -87,20 +87,46 @@ func (f *Opus) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *Opus) CreateDecoder() *rtpsimpleaudio.Decoder { - d := &rtpsimpleaudio.Decoder{ - SampleRate: 48000, - } - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *Opus) CreateDecoder2() (*rtpsimpleaudio.Decoder, error) { + d := &rtpsimpleaudio.Decoder{ + SampleRate: 48000, + } + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *Opus) CreateEncoder() *rtpsimpleaudio.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *Opus) CreateEncoder2() (*rtpsimpleaudio.Encoder, error) { e := &rtpsimpleaudio.Encoder{ PayloadType: f.PayloadTyp, SampleRate: 48000, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/opus_test.go b/pkg/formats/opus_test.go index edd10c91..4dae72ff 100644 --- a/pkg/formats/opus_test.go +++ b/pkg/formats/opus_test.go @@ -20,12 +20,16 @@ func TestOpusAttributes(t *testing.T) { func TestOpusDecEncoder(t *testing.T) { format := &Opus{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkt, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkt.PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkt) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) diff --git a/pkg/formats/rtpav1/decoder.go b/pkg/formats/rtpav1/decoder.go index 58be68eb..0f6502d4 100644 --- a/pkg/formats/rtpav1/decoder.go +++ b/pkg/formats/rtpav1/decoder.go @@ -45,8 +45,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(90000) + return nil } // Decode decodes OBUs from a RTP packet. diff --git a/pkg/formats/rtpav1/encoder.go b/pkg/formats/rtpav1/encoder.go index 8f9e04a4..4c5fdbec 100644 --- a/pkg/formats/rtpav1/encoder.go +++ b/pkg/formats/rtpav1/encoder.go @@ -47,7 +47,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -66,6 +66,7 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp) + return nil } // Encode encodes OBUs into RTP packets. diff --git a/pkg/formats/rtph264/decoder.go b/pkg/formats/rtph264/decoder.go index f6544861..42ca1b8a 100644 --- a/pkg/formats/rtph264/decoder.go +++ b/pkg/formats/rtph264/decoder.go @@ -49,16 +49,17 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { + if d.PacketizationMode >= 2 { + return fmt.Errorf("PacketizationMode >= 2 is not supported") + } + d.timeDecoder = rtptime.NewDecoder(rtpClockRate) + return nil } // Decode decodes NALUs from a RTP packet. func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { - if d.PacketizationMode >= 2 { - return nil, 0, fmt.Errorf("PacketizationMode >= 2 is not supported") - } - if len(pkt.Payload) < 1 { d.fragments = d.fragments[:0] // discard pending fragments return nil, 0, fmt.Errorf("payload is too short") @@ -113,7 +114,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { } nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} - d.fragments = d.fragments[:0] case h264.NALUTypeSTAPA: diff --git a/pkg/formats/rtph264/decoder_test.go b/pkg/formats/rtph264/decoder_test.go index c4a899c2..b52c600c 100644 --- a/pkg/formats/rtph264/decoder_test.go +++ b/pkg/formats/rtph264/decoder_test.go @@ -21,15 +21,16 @@ func TestDecode(t *testing.T) { clone := pkt.Clone() addNALUs, _, err := d.Decode(pkt) + + // test input integrity + require.Equal(t, clone, pkt) + if err == ErrMorePacketsNeeded { continue } require.NoError(t, err) nalus = append(nalus, addNALUs...) - - // test input integrity - require.Equal(t, clone, pkt) } require.Equal(t, ca.nalus, nalus) diff --git a/pkg/formats/rtph264/encoder.go b/pkg/formats/rtph264/encoder.go index d07eb928..fb81ccc5 100644 --- a/pkg/formats/rtph264/encoder.go +++ b/pkg/formats/rtph264/encoder.go @@ -50,7 +50,11 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { + if e.PacketizationMode >= 2 { + return fmt.Errorf("PacketizationMode >= 2 is not supported") + } + if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -69,14 +73,11 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp) + return nil } // Encode encodes NALUs into RTP/H264 packets. func (e *Encoder) Encode(nalus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { - if e.PacketizationMode >= 2 { - return nil, fmt.Errorf("PacketizationMode >= 2 is not supported") - } - var rets []*rtp.Packet var batch [][]byte diff --git a/pkg/formats/rtph265/decoder.go b/pkg/formats/rtph265/decoder.go index 0c248bde..7f4ad5f7 100644 --- a/pkg/formats/rtph265/decoder.go +++ b/pkg/formats/rtph265/decoder.go @@ -47,16 +47,17 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { + if d.MaxDONDiff != 0 { + return fmt.Errorf("MaxDONDiff != 0 is not supported (yet)") + } + d.timeDecoder = rtptime.NewDecoder(rtpClockRate) + return nil } // Decode decodes NALUs from a RTP packet. func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { - if d.MaxDONDiff != 0 { - return nil, 0, fmt.Errorf("MaxDONDiff != 0 is not supported (yet)") - } - if len(pkt.Payload) < 2 { d.fragments = d.fragments[:0] // discard pending fragments return nil, 0, fmt.Errorf("payload is too short") @@ -143,7 +144,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { } nalus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} - d.fragments = d.fragments[:0] case h265.NALUType_PACI: diff --git a/pkg/formats/rtph265/decoder_test.go b/pkg/formats/rtph265/decoder_test.go index 6fd61f8e..8b0deec4 100644 --- a/pkg/formats/rtph265/decoder_test.go +++ b/pkg/formats/rtph265/decoder_test.go @@ -20,15 +20,16 @@ func TestDecode(t *testing.T) { clone := pkt.Clone() addNALUs, _, err := d.Decode(pkt) + + // test input integrity + require.Equal(t, clone, pkt) + if err == ErrMorePacketsNeeded { continue } require.NoError(t, err) nalus = append(nalus, addNALUs...) - - // test input integrity - require.Equal(t, clone, pkt) } require.Equal(t, ca.nalus, nalus) diff --git a/pkg/formats/rtph265/encoder.go b/pkg/formats/rtph265/encoder.go index 78c9920b..e79e0435 100644 --- a/pkg/formats/rtph265/encoder.go +++ b/pkg/formats/rtph265/encoder.go @@ -50,7 +50,11 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { + if e.MaxDONDiff != 0 { + return fmt.Errorf("MaxDONDiff != 0 is not supported (yet)") + } + if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -69,14 +73,11 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp) + return nil } // Encode encodes NALUs into RTP/H265 packets. func (e *Encoder) Encode(nalus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { - if e.MaxDONDiff != 0 { - return nil, fmt.Errorf("MaxDONDiff != 0 is not supported (yet)") - } - var rets []*rtp.Packet var batch [][]byte diff --git a/pkg/formats/rtplpcm/decoder.go b/pkg/formats/rtplpcm/decoder.go index 8fde00ff..79fc3466 100644 --- a/pkg/formats/rtplpcm/decoder.go +++ b/pkg/formats/rtplpcm/decoder.go @@ -21,9 +21,10 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(d.SampleRate) d.sampleSize = d.BitDepth * d.ChannelCount / 8 + return nil } // Decode decodes audio samples from a RTP packet. diff --git a/pkg/formats/rtplpcm/encoder.go b/pkg/formats/rtplpcm/encoder.go index cc2a6ec4..dffc4850 100644 --- a/pkg/formats/rtplpcm/encoder.go +++ b/pkg/formats/rtplpcm/encoder.go @@ -53,7 +53,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -74,6 +74,16 @@ func (e *Encoder) Init() { e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp) e.sampleSize = e.BitDepth * e.ChannelCount / 8 e.maxPayloadSize = (e.PayloadMaxSize / e.sampleSize) * e.sampleSize + return nil +} + +func (e *Encoder) packetCount(slen int) int { + n := (slen / e.maxPayloadSize) + if (slen % e.maxPayloadSize) != 0 { + n++ + } + + return n } // Encode encodes audio samples into RTP packets. @@ -83,12 +93,8 @@ func (e *Encoder) Encode(samples []byte, pts time.Duration) ([]*rtp.Packet, erro return nil, fmt.Errorf("invalid samples") } - n := (slen / e.maxPayloadSize) - if (slen % e.maxPayloadSize) != 0 { - n++ - } - - ret := make([]*rtp.Packet, n) + packetCount := e.packetCount(slen) + ret := make([]*rtp.Packet, packetCount) i := 0 pos := 0 payloadSize := e.maxPayloadSize diff --git a/pkg/formats/rtpmjpeg/decoder.go b/pkg/formats/rtpmjpeg/decoder.go index d19d9f3e..4176b560 100644 --- a/pkg/formats/rtpmjpeg/decoder.go +++ b/pkg/formats/rtpmjpeg/decoder.go @@ -115,8 +115,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) + return nil } // Decode decodes an image from a RTP packet. @@ -181,7 +182,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { } data := joinFragments(d.fragments, d.fragmentsSize) - d.fragments = d.fragments[:0] d.fragmentsSize = 0 diff --git a/pkg/formats/rtpmjpeg/encoder.go b/pkg/formats/rtpmjpeg/encoder.go index 30153d0e..26f80f6c 100644 --- a/pkg/formats/rtpmjpeg/encoder.go +++ b/pkg/formats/rtpmjpeg/encoder.go @@ -47,7 +47,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -66,6 +66,7 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp) + return nil } // Encode encodes an image into RTP/M-JPEG packets. diff --git a/pkg/formats/rtpmpeg2audio/decoder.go b/pkg/formats/rtpmpeg2audio/decoder.go index f9663a02..d76f2f66 100644 --- a/pkg/formats/rtpmpeg2audio/decoder.go +++ b/pkg/formats/rtpmpeg2audio/decoder.go @@ -41,8 +41,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(90000) + return nil } // Decode decodes frames from a RTP packet. @@ -124,7 +125,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { } frames = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} - d.fragments = d.fragments[:0] d.fragmentsSize = 0 } diff --git a/pkg/formats/rtpmpeg2audio/encoder.go b/pkg/formats/rtpmpeg2audio/encoder.go index d5e32aed..3d801352 100644 --- a/pkg/formats/rtpmpeg2audio/encoder.go +++ b/pkg/formats/rtpmpeg2audio/encoder.go @@ -52,7 +52,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -71,6 +71,7 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp) + return nil } // Encode encodes frames into RTP packets. diff --git a/pkg/formats/rtpmpeg4audio/decoder.go b/pkg/formats/rtpmpeg4audio/decoder.go index c4af7133..03177a59 100644 --- a/pkg/formats/rtpmpeg4audio/decoder.go +++ b/pkg/formats/rtpmpeg4audio/decoder.go @@ -2,240 +2,12 @@ package rtpmpeg4audio import ( "errors" - "fmt" - "time" - "github.com/pion/rtp" - - "github.com/bluenviron/gortsplib/v3/pkg/rtptime" - "github.com/bluenviron/mediacommon/pkg/bits" - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiogeneric" ) -// ErrMorePacketsNeeded is returned when more packets are needed. +// ErrMorePacketsNeeded is an alis for rtpmpeg4audiogeneric.ErrMorePacketsNeeded. var ErrMorePacketsNeeded = errors.New("need more packets") -func joinFragments(fragments [][]byte, size int) []byte { - ret := make([]byte, size) - n := 0 - for _, p := range fragments { - n += copy(ret[n:], p) - } - return ret -} - -// Decoder is a RTP/MPEG-4 Audio decoder. -// Specification: https://datatracker.ietf.org/doc/html/rfc3640 -type Decoder struct { - // sample rate of input packets. - SampleRate int - - // The number of bits in which the AU-size field is encoded in the AU-header. - SizeLength int - - // The number of bits in which the AU-Index is encoded in the first AU-header. - IndexLength int - - // The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header. - IndexDeltaLength int - - timeDecoder *rtptime.Decoder - firstAUParsed bool - adtsMode bool - fragments [][]byte - fragmentsSize int -} - -// Init initializes the decoder. -func (d *Decoder) Init() { - d.timeDecoder = rtptime.NewDecoder(d.SampleRate) -} - -// Decode decodes AUs from a RTP packet. -// It returns the AUs and the PTS of the first AU. -// The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate. -func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { - if len(pkt.Payload) < 2 { - d.fragments = d.fragments[:0] // discard pending fragments - return nil, 0, fmt.Errorf("payload is too short") - } - - // AU-headers-length (16 bits) - headersLen := int(uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1])) - if headersLen == 0 { - d.fragments = d.fragments[:0] // discard pending fragments - return nil, 0, fmt.Errorf("invalid AU-headers-length") - } - payload := pkt.Payload[2:] - - // AU-headers - dataLens, err := d.readAUHeaders(payload, headersLen) - if err != nil { - d.fragments = d.fragments[:0] // discard pending fragments - return nil, 0, err - } - - pos := (headersLen / 8) - if (headersLen % 8) != 0 { - pos++ - } - payload = payload[pos:] - - var aus [][]byte - - if len(d.fragments) == 0 { - if pkt.Marker { - // AUs - aus = make([][]byte, len(dataLens)) - for i, dataLen := range dataLens { - if len(payload) < int(dataLen) { - return nil, 0, fmt.Errorf("payload is too short") - } - - aus[i] = payload[:dataLen] - payload = payload[dataLen:] - } - } else { - if len(dataLens) != 1 { - return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU") - } - - if len(payload) < int(dataLens[0]) { - return nil, 0, fmt.Errorf("payload is too short") - } - - d.fragmentsSize = int(dataLens[0]) - d.fragments = append(d.fragments, payload[:dataLens[0]]) - return nil, 0, ErrMorePacketsNeeded - } - } else { - // we are decoding a fragmented AU - if len(dataLens) != 1 { - d.fragments = d.fragments[:0] // discard pending fragments - return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU") - } - - if len(payload) < int(dataLens[0]) { - d.fragments = d.fragments[:0] // discard pending fragments - return nil, 0, fmt.Errorf("payload is too short") - } - - d.fragmentsSize += int(dataLens[0]) - if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize { - d.fragments = d.fragments[:0] // discard pending fragments - return nil, 0, fmt.Errorf("AU size (%d) is too big, maximum is %d", d.fragmentsSize, mpeg4audio.MaxAccessUnitSize) - } - - d.fragments = append(d.fragments, payload[:dataLens[0]]) - - if !pkt.Marker { - return nil, 0, ErrMorePacketsNeeded - } - - aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} - - d.fragments = d.fragments[:0] - } - - aus, err = d.removeADTS(aus) - if err != nil { - return nil, 0, err - } - - return aus, d.timeDecoder.Decode(pkt.Timestamp), nil -} - -func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) { - firstRead := false - - count := 0 - for i := 0; i < headersLen; { - if i == 0 { - i += d.SizeLength - i += d.IndexLength - } else { - i += d.SizeLength - i += d.IndexDeltaLength - } - count++ - } - - dataLens := make([]uint64, count) - - pos := 0 - i := 0 - - for headersLen > 0 { - dataLen, err := bits.ReadBits(buf, &pos, d.SizeLength) - if err != nil { - return nil, err - } - headersLen -= d.SizeLength - - if !firstRead { - firstRead = true - if d.IndexLength > 0 { - auIndex, err := bits.ReadBits(buf, &pos, d.IndexLength) - if err != nil { - return nil, err - } - headersLen -= d.IndexLength - - if auIndex != 0 { - return nil, fmt.Errorf("AU-index different than zero is not supported") - } - } - } else if d.IndexDeltaLength > 0 { - auIndexDelta, err := bits.ReadBits(buf, &pos, d.IndexDeltaLength) - if err != nil { - return nil, err - } - headersLen -= d.IndexDeltaLength - - if auIndexDelta != 0 { - return nil, fmt.Errorf("AU-index-delta different than zero is not supported") - } - } - - dataLens[i] = dataLen - i++ - } - - return dataLens, nil -} - -// some cameras wrap AUs into ADTS -func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) { - if !d.firstAUParsed { - d.firstAUParsed = true - - if len(aus) == 1 && len(aus[0]) >= 2 { - if aus[0][0] == 0xFF && (aus[0][1]&0xF0) == 0xF0 { - var pkts mpeg4audio.ADTSPackets - err := pkts.Unmarshal(aus[0]) - if err == nil && len(pkts) == 1 { - d.adtsMode = true - aus[0] = pkts[0].AU - } - } - } - } else if d.adtsMode { - if len(aus) != 1 { - return nil, fmt.Errorf("multiple AUs in ADTS mode are not supported") - } - - var pkts mpeg4audio.ADTSPackets - err := pkts.Unmarshal(aus[0]) - if err != nil { - return nil, fmt.Errorf("unable to decode ADTS: %s", err) - } - - if len(pkts) != 1 { - return nil, fmt.Errorf("multiple ADTS packets are not supported") - } - - aus[0] = pkts[0].AU - } - - return aus, nil -} +// Decoder is an alias for rtpmpeg4audiogeneric.Decoder. +type Decoder = rtpmpeg4audiogeneric.Decoder diff --git a/pkg/formats/rtpmpeg4audio/encoder.go b/pkg/formats/rtpmpeg4audio/encoder.go index a6f269a7..844e90ca 100644 --- a/pkg/formats/rtpmpeg4audio/encoder.go +++ b/pkg/formats/rtpmpeg4audio/encoder.go @@ -1,269 +1,8 @@ package rtpmpeg4audio import ( - "crypto/rand" - "time" - - "github.com/pion/rtp" - - "github.com/bluenviron/gortsplib/v3/pkg/rtptime" - "github.com/bluenviron/mediacommon/pkg/bits" - "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4audiogeneric" ) -const ( - rtpVersion = 2 -) - -func randUint32() uint32 { - var b [4]byte - rand.Read(b[:]) - return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) -} - -// Encoder is a RTP/MPEG4-audio encoder. -// Specification: https://datatracker.ietf.org/doc/html/rfc3640 -type Encoder struct { - // payload type of packets. - PayloadType uint8 - - // SSRC of packets (optional). - // It defaults to a random value. - SSRC *uint32 - - // initial sequence number of packets (optional). - // It defaults to a random value. - InitialSequenceNumber *uint16 - - // initial timestamp of packets (optional). - // It defaults to a random value. - InitialTimestamp *uint32 - - // maximum size of packet payloads (optional). - // It defaults to 1460. - PayloadMaxSize int - - // sample rate of packets. - SampleRate int - - // The number of bits in which the AU-size field is encoded in the AU-header. - SizeLength int - - // The number of bits in which the AU-Index is encoded in the first AU-header. - IndexLength int - - // The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header. - IndexDeltaLength int - - sequenceNumber uint16 - timeEncoder *rtptime.Encoder -} - -// Init initializes the encoder. -func (e *Encoder) Init() { - if e.SSRC == nil { - v := randUint32() - e.SSRC = &v - } - if e.InitialSequenceNumber == nil { - v := uint16(randUint32()) - e.InitialSequenceNumber = &v - } - if e.InitialTimestamp == nil { - v := randUint32() - e.InitialTimestamp = &v - } - if e.PayloadMaxSize == 0 { - e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) - } - - e.sequenceNumber = *e.InitialSequenceNumber - e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp) -} - -// Encode encodes AUs into RTP packets. -func (e *Encoder) Encode(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { - var rets []*rtp.Packet - var batch [][]byte - - // split AUs into batches - for _, au := range aus { - if e.lenAggregated(batch, au) <= e.PayloadMaxSize { - // add to existing batch - batch = append(batch, au) - } else { - // write last batch - if batch != nil { - pkts, err := e.writeBatch(batch, pts) - if err != nil { - return nil, err - } - rets = append(rets, pkts...) - pts += time.Duration(len(batch)) * mpeg4audio.SamplesPerAccessUnit * time.Second / time.Duration(e.SampleRate) - } - - // initialize new batch - batch = [][]byte{au} - } - } - - // write last batch - pkts, err := e.writeBatch(batch, pts) - if err != nil { - return nil, err - } - rets = append(rets, pkts...) - - return rets, nil -} - -func (e *Encoder) writeBatch(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { - if len(aus) != 1 || e.lenAggregated(aus, nil) < e.PayloadMaxSize { - return e.writeAggregated(aus, pts) - } - - return e.writeFragmented(aus[0], pts) -} - -func (e *Encoder) writeFragmented(au []byte, pts time.Duration) ([]*rtp.Packet, error) { - auHeadersLen := e.SizeLength + e.IndexLength - auHeadersLenBytes := auHeadersLen / 8 - if (auHeadersLen % 8) != 0 { - auHeadersLenBytes++ - } - - avail := e.PayloadMaxSize - 2 - auHeadersLenBytes - le := len(au) - packetCount := le / avail - lastPacketSize := le % avail - if lastPacketSize > 0 { - packetCount++ - } - - ret := make([]*rtp.Packet, packetCount) - encPTS := e.timeEncoder.Encode(pts) - - for i := range ret { - var le int - if i != (packetCount - 1) { - le = avail - } else { - le = lastPacketSize - } - - payload := make([]byte, 2+auHeadersLenBytes+le) - - // AU-headers-length - payload[0] = byte(auHeadersLen >> 8) - payload[1] = byte(auHeadersLen) - - // AU-headers - pos := 0 - bits.WriteBits(payload[2:], &pos, uint64(le), e.SizeLength) - bits.WriteBits(payload[2:], &pos, 0, e.IndexLength) - - // AU - copy(payload[2+auHeadersLenBytes:], au[:le]) - au = au[le:] - - ret[i] = &rtp.Packet{ - Header: rtp.Header{ - Version: rtpVersion, - PayloadType: e.PayloadType, - SequenceNumber: e.sequenceNumber, - Timestamp: encPTS, - SSRC: *e.SSRC, - Marker: (i == (packetCount - 1)), - }, - Payload: payload, - } - - e.sequenceNumber++ - } - - return ret, nil -} - -func (e *Encoder) lenAggregated(aus [][]byte, addAU []byte) int { - ret := 2 // AU-headers-length - - // AU-headers - auHeadersLen := 0 - i := 0 - for range aus { - if i == 0 { - auHeadersLen += e.SizeLength + e.IndexLength - } else { - auHeadersLen += e.SizeLength + e.IndexDeltaLength - } - i++ - } - if addAU != nil { - if i == 0 { - auHeadersLen += e.SizeLength + e.IndexLength - } else { - auHeadersLen += e.SizeLength + e.IndexDeltaLength - } - } - ret += auHeadersLen / 8 - if (auHeadersLen % 8) != 0 { - ret++ - } - - // AU - for _, au := range aus { - ret += len(au) - } - ret += len(addAU) - - return ret -} - -func (e *Encoder) writeAggregated(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { - payload := make([]byte, e.lenAggregated(aus, nil)) - - // AU-headers - written := 0 - pos := 0 - for i, au := range aus { - bits.WriteBits(payload[2:], &pos, uint64(len(au)), e.SizeLength) - written += e.SizeLength - if i == 0 { - bits.WriteBits(payload[2:], &pos, 0, e.IndexLength) - written += e.IndexLength - } else { - bits.WriteBits(payload[2:], &pos, 0, e.IndexDeltaLength) - written += e.IndexDeltaLength - } - } - pos = 2 + (written / 8) - if (written % 8) != 0 { - pos++ - } - - // AU-headers-length - payload[0] = byte(written >> 8) - payload[1] = byte(written) - - // AUs - for _, au := range aus { - auLen := copy(payload[pos:], au) - pos += auLen - } - - pkt := &rtp.Packet{ - Header: rtp.Header{ - Version: rtpVersion, - PayloadType: e.PayloadType, - SequenceNumber: e.sequenceNumber, - Timestamp: e.timeEncoder.Encode(pts), - SSRC: *e.SSRC, - Marker: true, - }, - Payload: payload, - } - - e.sequenceNumber++ - - return []*rtp.Packet{pkt}, nil -} +// Encoder is an alias for rtpmpeg4audiogeneric.Encoder. +type Encoder = rtpmpeg4audiogeneric.Encoder diff --git a/pkg/formats/rtpmpeg4audiogeneric/decoder.go b/pkg/formats/rtpmpeg4audiogeneric/decoder.go new file mode 100644 index 00000000..12bf8da2 --- /dev/null +++ b/pkg/formats/rtpmpeg4audiogeneric/decoder.go @@ -0,0 +1,241 @@ +package rtpmpeg4audiogeneric + +import ( + "errors" + "fmt" + "time" + + "github.com/bluenviron/mediacommon/pkg/bits" + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v3/pkg/rtptime" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/MPEG-4 Audio decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +type Decoder struct { + // sample rate of input packets. + SampleRate int + + // The number of bits in which the AU-size field is encoded in the AU-header. + SizeLength int + + // The number of bits in which the AU-Index is encoded in the first AU-header. + IndexLength int + + // The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header. + IndexDeltaLength int + + timeDecoder *rtptime.Decoder + firstAUParsed bool + adtsMode bool + fragments [][]byte + fragmentsSize int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + d.timeDecoder = rtptime.NewDecoder(d.SampleRate) + return nil +} + +// Decode decodes AUs from a RTP packet. +// It returns the AUs and the PTS of the first AU. +// The PTS of subsequent AUs can be calculated by adding time.Second*mpeg4audio.SamplesPerAccessUnit/clockRate. +func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { + if len(pkt.Payload) < 2 { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, fmt.Errorf("payload is too short") + } + + // AU-headers-length (16 bits) + headersLen := int(uint16(pkt.Payload[0])<<8 | uint16(pkt.Payload[1])) + if headersLen == 0 { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, fmt.Errorf("invalid AU-headers-length") + } + payload := pkt.Payload[2:] + + // AU-headers + dataLens, err := d.readAUHeaders(payload, headersLen) + if err != nil { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, err + } + + pos := (headersLen / 8) + if (headersLen % 8) != 0 { + pos++ + } + payload = payload[pos:] + + var aus [][]byte + + if len(d.fragments) == 0 { + if pkt.Marker { + // AUs + aus = make([][]byte, len(dataLens)) + for i, dataLen := range dataLens { + if len(payload) < int(dataLen) { + return nil, 0, fmt.Errorf("payload is too short") + } + + aus[i] = payload[:dataLen] + payload = payload[dataLen:] + } + } else { + if len(dataLens) != 1 { + return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU") + } + + if len(payload) < int(dataLens[0]) { + return nil, 0, fmt.Errorf("payload is too short") + } + + d.fragmentsSize = int(dataLens[0]) + d.fragments = append(d.fragments, payload[:dataLens[0]]) + return nil, 0, ErrMorePacketsNeeded + } + } else { + // we are decoding a fragmented AU + if len(dataLens) != 1 { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, fmt.Errorf("a fragmented packet can only contain one AU") + } + + if len(payload) < int(dataLens[0]) { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, fmt.Errorf("payload is too short") + } + + d.fragmentsSize += int(dataLens[0]) + if d.fragmentsSize > mpeg4audio.MaxAccessUnitSize { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, fmt.Errorf("AU size (%d) is too big, maximum is %d", d.fragmentsSize, mpeg4audio.MaxAccessUnitSize) + } + + d.fragments = append(d.fragments, payload[:dataLens[0]]) + + if !pkt.Marker { + return nil, 0, ErrMorePacketsNeeded + } + + aus = [][]byte{joinFragments(d.fragments, d.fragmentsSize)} + d.fragments = d.fragments[:0] + } + + aus, err = d.removeADTS(aus) + if err != nil { + return nil, 0, err + } + + return aus, d.timeDecoder.Decode(pkt.Timestamp), nil +} + +func (d *Decoder) readAUHeaders(buf []byte, headersLen int) ([]uint64, error) { + firstRead := false + + count := 0 + for i := 0; i < headersLen; { + if i == 0 { + i += d.SizeLength + i += d.IndexLength + } else { + i += d.SizeLength + i += d.IndexDeltaLength + } + count++ + } + + dataLens := make([]uint64, count) + + pos := 0 + i := 0 + + for headersLen > 0 { + dataLen, err := bits.ReadBits(buf, &pos, d.SizeLength) + if err != nil { + return nil, err + } + headersLen -= d.SizeLength + + if !firstRead { + firstRead = true + if d.IndexLength > 0 { + auIndex, err := bits.ReadBits(buf, &pos, d.IndexLength) + if err != nil { + return nil, err + } + headersLen -= d.IndexLength + + if auIndex != 0 { + return nil, fmt.Errorf("AU-index different than zero is not supported") + } + } + } else if d.IndexDeltaLength > 0 { + auIndexDelta, err := bits.ReadBits(buf, &pos, d.IndexDeltaLength) + if err != nil { + return nil, err + } + headersLen -= d.IndexDeltaLength + + if auIndexDelta != 0 { + return nil, fmt.Errorf("AU-index-delta different than zero is not supported") + } + } + + dataLens[i] = dataLen + i++ + } + + return dataLens, nil +} + +// some cameras wrap AUs into ADTS +func (d *Decoder) removeADTS(aus [][]byte) ([][]byte, error) { + if !d.firstAUParsed { + d.firstAUParsed = true + + if len(aus) == 1 && len(aus[0]) >= 2 { + if aus[0][0] == 0xFF && (aus[0][1]&0xF0) == 0xF0 { + var pkts mpeg4audio.ADTSPackets + err := pkts.Unmarshal(aus[0]) + if err == nil && len(pkts) == 1 { + d.adtsMode = true + aus[0] = pkts[0].AU + } + } + } + } else if d.adtsMode { + if len(aus) != 1 { + return nil, fmt.Errorf("multiple AUs in ADTS mode are not supported") + } + + var pkts mpeg4audio.ADTSPackets + err := pkts.Unmarshal(aus[0]) + if err != nil { + return nil, fmt.Errorf("unable to decode ADTS: %s", err) + } + + if len(pkts) != 1 { + return nil, fmt.Errorf("multiple ADTS packets are not supported") + } + + aus[0] = pkts[0].AU + } + + return aus, nil +} diff --git a/pkg/formats/rtpmpeg4audio/decoder_test.go b/pkg/formats/rtpmpeg4audiogeneric/decoder_test.go similarity index 98% rename from pkg/formats/rtpmpeg4audio/decoder_test.go rename to pkg/formats/rtpmpeg4audiogeneric/decoder_test.go index e78270a9..61e8e0de 100644 --- a/pkg/formats/rtpmpeg4audio/decoder_test.go +++ b/pkg/formats/rtpmpeg4audiogeneric/decoder_test.go @@ -1,4 +1,4 @@ -package rtpmpeg4audio +package rtpmpeg4audiogeneric import ( "testing" @@ -24,15 +24,16 @@ func TestDecode(t *testing.T) { clone := pkt.Clone() addAUs, _, err := d.Decode(pkt) + + // test input integrity + require.Equal(t, clone, pkt) + if err == ErrMorePacketsNeeded { continue } require.NoError(t, err) aus = append(aus, addAUs...) - - // test input integrity - require.Equal(t, clone, pkt) } require.Equal(t, ca.aus, aus) diff --git a/pkg/formats/rtpmpeg4audiogeneric/encoder.go b/pkg/formats/rtpmpeg4audiogeneric/encoder.go new file mode 100644 index 00000000..85c92bfb --- /dev/null +++ b/pkg/formats/rtpmpeg4audiogeneric/encoder.go @@ -0,0 +1,270 @@ +package rtpmpeg4audiogeneric + +import ( + "crypto/rand" + "time" + + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v3/pkg/rtptime" + "github.com/bluenviron/mediacommon/pkg/bits" + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" +) + +const ( + rtpVersion = 2 +) + +func randUint32() uint32 { + var b [4]byte + rand.Read(b[:]) + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) +} + +// Encoder is a RTP/MPEG4-audio encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc3640 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // initial timestamp of packets (optional). + // It defaults to a random value. + InitialTimestamp *uint32 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + // sample rate of packets. + SampleRate int + + // The number of bits in which the AU-size field is encoded in the AU-header. + SizeLength int + + // The number of bits in which the AU-Index is encoded in the first AU-header. + IndexLength int + + // The number of bits in which the AU-Index-delta field is encoded in any non-first AU-header. + IndexDeltaLength int + + sequenceNumber uint16 + timeEncoder *rtptime.Encoder +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.SSRC == nil { + v := randUint32() + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v := uint16(randUint32()) + e.InitialSequenceNumber = &v + } + if e.InitialTimestamp == nil { + v := randUint32() + e.InitialTimestamp = &v + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) + } + + e.sequenceNumber = *e.InitialSequenceNumber + e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp) + return nil +} + +// Encode encodes AUs into RTP packets. +func (e *Encoder) Encode(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { + var rets []*rtp.Packet + var batch [][]byte + + // split AUs into batches + for _, au := range aus { + if e.lenAggregated(batch, au) <= e.PayloadMaxSize { + // add to existing batch + batch = append(batch, au) + } else { + // write last batch + if batch != nil { + pkts, err := e.writeBatch(batch, pts) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + pts += time.Duration(len(batch)) * mpeg4audio.SamplesPerAccessUnit * time.Second / time.Duration(e.SampleRate) + } + + // initialize new batch + batch = [][]byte{au} + } + } + + // write last batch + pkts, err := e.writeBatch(batch, pts) + if err != nil { + return nil, err + } + rets = append(rets, pkts...) + + return rets, nil +} + +func (e *Encoder) writeBatch(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { + if len(aus) != 1 || e.lenAggregated(aus, nil) < e.PayloadMaxSize { + return e.writeAggregated(aus, pts) + } + + return e.writeFragmented(aus[0], pts) +} + +func (e *Encoder) writeFragmented(au []byte, pts time.Duration) ([]*rtp.Packet, error) { + auHeadersLen := e.SizeLength + e.IndexLength + auHeadersLenBytes := auHeadersLen / 8 + if (auHeadersLen % 8) != 0 { + auHeadersLenBytes++ + } + + avail := e.PayloadMaxSize - 2 - auHeadersLenBytes + le := len(au) + packetCount := le / avail + lastPacketSize := le % avail + if lastPacketSize > 0 { + packetCount++ + } + + ret := make([]*rtp.Packet, packetCount) + encPTS := e.timeEncoder.Encode(pts) + + for i := range ret { + var le int + if i != (packetCount - 1) { + le = avail + } else { + le = lastPacketSize + } + + payload := make([]byte, 2+auHeadersLenBytes+le) + + // AU-headers-length + payload[0] = byte(auHeadersLen >> 8) + payload[1] = byte(auHeadersLen) + + // AU-headers + pos := 0 + bits.WriteBits(payload[2:], &pos, uint64(le), e.SizeLength) + bits.WriteBits(payload[2:], &pos, 0, e.IndexLength) + + // AU + copy(payload[2+auHeadersLenBytes:], au[:le]) + au = au[le:] + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: encPTS, + SSRC: *e.SSRC, + Marker: (i == (packetCount - 1)), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} + +func (e *Encoder) lenAggregated(aus [][]byte, addAU []byte) int { + ret := 2 // AU-headers-length + + // AU-headers + auHeadersLen := 0 + i := 0 + for range aus { + if i == 0 { + auHeadersLen += e.SizeLength + e.IndexLength + } else { + auHeadersLen += e.SizeLength + e.IndexDeltaLength + } + i++ + } + if addAU != nil { + if i == 0 { + auHeadersLen += e.SizeLength + e.IndexLength + } else { + auHeadersLen += e.SizeLength + e.IndexDeltaLength + } + } + ret += auHeadersLen / 8 + if (auHeadersLen % 8) != 0 { + ret++ + } + + // AU + for _, au := range aus { + ret += len(au) + } + ret += len(addAU) + + return ret +} + +func (e *Encoder) writeAggregated(aus [][]byte, pts time.Duration) ([]*rtp.Packet, error) { + payload := make([]byte, e.lenAggregated(aus, nil)) + + // AU-headers + written := 0 + pos := 0 + for i, au := range aus { + bits.WriteBits(payload[2:], &pos, uint64(len(au)), e.SizeLength) + written += e.SizeLength + if i == 0 { + bits.WriteBits(payload[2:], &pos, 0, e.IndexLength) + written += e.IndexLength + } else { + bits.WriteBits(payload[2:], &pos, 0, e.IndexDeltaLength) + written += e.IndexDeltaLength + } + } + pos = 2 + (written / 8) + if (written % 8) != 0 { + pos++ + } + + // AU-headers-length + payload[0] = byte(written >> 8) + payload[1] = byte(written) + + // AUs + for _, au := range aus { + auLen := copy(payload[pos:], au) + pos += auLen + } + + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: e.timeEncoder.Encode(pts), + SSRC: *e.SSRC, + Marker: true, + }, + Payload: payload, + } + + e.sequenceNumber++ + + return []*rtp.Packet{pkt}, nil +} diff --git a/pkg/formats/rtpmpeg4audio/encoder_test.go b/pkg/formats/rtpmpeg4audiogeneric/encoder_test.go similarity index 99% rename from pkg/formats/rtpmpeg4audio/encoder_test.go rename to pkg/formats/rtpmpeg4audiogeneric/encoder_test.go index 3867a279..24662c53 100644 --- a/pkg/formats/rtpmpeg4audio/encoder_test.go +++ b/pkg/formats/rtpmpeg4audiogeneric/encoder_test.go @@ -1,4 +1,4 @@ -package rtpmpeg4audio +package rtpmpeg4audiogeneric import ( "bytes" diff --git a/pkg/formats/rtpmpeg4audiogeneric/rtpmpeg4audio_generic.go b/pkg/formats/rtpmpeg4audiogeneric/rtpmpeg4audio_generic.go new file mode 100644 index 00000000..4ae065fd --- /dev/null +++ b/pkg/formats/rtpmpeg4audiogeneric/rtpmpeg4audio_generic.go @@ -0,0 +1,2 @@ +// Package rtpmpeg4audiogeneric contains a RTP/MPEG-4 Audio decoder and encoder. +package rtpmpeg4audiogeneric diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/0133a2a9bd638ceb b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/0133a2a9bd638ceb similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/0133a2a9bd638ceb rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/0133a2a9bd638ceb diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/079a435e5445c35b b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/079a435e5445c35b similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/079a435e5445c35b rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/079a435e5445c35b diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/571808e383aba6a5 b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/571808e383aba6a5 similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/571808e383aba6a5 rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/571808e383aba6a5 diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/8f3e197fb87140b2 b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/8f3e197fb87140b2 similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/8f3e197fb87140b2 rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/8f3e197fb87140b2 diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/9418bac23c1c4f72 b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/9418bac23c1c4f72 similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/9418bac23c1c4f72 rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/9418bac23c1c4f72 diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/97f31764971cea9c b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/97f31764971cea9c similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/97f31764971cea9c rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/97f31764971cea9c diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/a68704dd42c38106 b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/a68704dd42c38106 similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/a68704dd42c38106 rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/a68704dd42c38106 diff --git a/pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/c7480ec341b553d6 b/pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/c7480ec341b553d6 similarity index 100% rename from pkg/formats/rtpmpeg4audio/testdata/fuzz/FuzzDecoder/c7480ec341b553d6 rename to pkg/formats/rtpmpeg4audiogeneric/testdata/fuzz/FuzzDecoder/c7480ec341b553d6 diff --git a/pkg/formats/rtpmpeg4audiolatm/decoder.go b/pkg/formats/rtpmpeg4audiolatm/decoder.go new file mode 100644 index 00000000..af76d2f5 --- /dev/null +++ b/pkg/formats/rtpmpeg4audiolatm/decoder.go @@ -0,0 +1,116 @@ +package rtpmpeg4audiolatm + +import ( + "errors" + "fmt" + "time" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v3/pkg/rtptime" +) + +// ErrMorePacketsNeeded is returned when more packets are needed. +var ErrMorePacketsNeeded = errors.New("need more packets") + +func joinFragments(fragments [][]byte, size int) []byte { + ret := make([]byte, size) + n := 0 + for _, p := range fragments { + n += copy(ret[n:], p) + } + return ret +} + +// Decoder is a RTP/MPEG-4 Audio decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type Decoder struct { + // StreamMuxConfig. + Config *mpeg4audio.StreamMuxConfig + + timeDecoder *rtptime.Decoder + fragments [][]byte + fragmentsSize int + fragmentsExpected int +} + +// Init initializes the decoder. +func (d *Decoder) Init() error { + if d.Config == nil || len(d.Config.Programs) != 1 || len(d.Config.Programs[0].Layers) != 1 { + return fmt.Errorf("unsupported StreamMuxConfig") + } + + d.timeDecoder = rtptime.NewDecoder(d.Config.Programs[0].Layers[0].AudioSpecificConfig.SampleRate) + return nil +} + +func decodePayloadLengthInfo(buf []byte) (int, int, error) { + lb := len(buf) + l := 0 + n := 0 + + for { + if (lb - n) == 0 { + return 0, 0, fmt.Errorf("not enough bytes") + } + + b := buf[n] + n++ + l += int(b) + + if b != 0xFF { + break + } + } + + return l, n, nil +} + +// Decode decodes an AU from a RTP packet. +// It returns the AU and its PTS. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { + var au []byte + buf := pkt.Payload + + if len(d.fragments) == 0 { + pl, n, err := decodePayloadLengthInfo(buf) + if err != nil { + return nil, 0, err + } + + buf = buf[n:] + bl := len(buf) + + if pl <= bl { + au = buf[:pl] + // there could be other data, due to otherDataPresent. Ignore it. + } else { + if pl > mpeg4audio.MaxAccessUnitSize { + d.fragments = d.fragments[:0] // discard pending fragments + return nil, 0, fmt.Errorf("AU size (%d) is too big, maximum is %d", pl, mpeg4audio.MaxAccessUnitSize) + } + + d.fragments = append(d.fragments, buf) + d.fragmentsSize = pl + d.fragmentsExpected = pl - bl + return nil, 0, ErrMorePacketsNeeded + } + } else { + bl := len(buf) + + if d.fragmentsExpected > bl { + d.fragments = append(d.fragments, buf) + d.fragmentsExpected -= bl + return nil, 0, ErrMorePacketsNeeded + } + + d.fragments = append(d.fragments, buf[:d.fragmentsExpected]) + // there could be other data, due to otherDataPresent. Ignore it. + + au = joinFragments(d.fragments, d.fragmentsSize) + d.fragments = d.fragments[:0] + } + + return au, d.timeDecoder.Decode(pkt.Timestamp), nil +} diff --git a/pkg/formats/rtpmpeg4audiolatm/decoder_test.go b/pkg/formats/rtpmpeg4audiolatm/decoder_test.go new file mode 100644 index 00000000..c21052fd --- /dev/null +++ b/pkg/formats/rtpmpeg4audiolatm/decoder_test.go @@ -0,0 +1,84 @@ +package rtpmpeg4audiolatm + +import ( + "testing" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + "github.com/stretchr/testify/require" +) + +func TestDecode(t *testing.T) { + for _, ca := range cases { + t.Run(ca.name, func(t *testing.T) { + d := &Decoder{ + Config: ca.config, + } + d.Init() + + var au []byte + var err error + + for _, pkt := range ca.pkts { + clone := pkt.Clone() + + au, _, err = d.Decode(pkt) + + // test input integrity + require.Equal(t, clone, pkt) + + if err == ErrMorePacketsNeeded { + continue + } + + require.NoError(t, err) + } + + require.Equal(t, ca.au, au) + }) + } +} + +func FuzzDecoder(f *testing.F) { + f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) { + d := &Decoder{ + Config: &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + } + d.Init() + + d.Decode(&rtp.Packet{ + Header: rtp.Header{ + Version: 2, + Marker: am, + PayloadType: 96, + SequenceNumber: 17645, + Timestamp: 2289527317, + SSRC: 0x9dbb7812, + }, + Payload: a, + }) + + d.Decode(&rtp.Packet{ + Header: rtp.Header{ + Version: 2, + Marker: bm, + PayloadType: 96, + SequenceNumber: 17646, + Timestamp: 2289527317, + SSRC: 0x9dbb7812, + }, + Payload: b, + }) + }) +} diff --git a/pkg/formats/rtpmpeg4audiolatm/encoder.go b/pkg/formats/rtpmpeg4audiolatm/encoder.go new file mode 100644 index 00000000..adbdd1b6 --- /dev/null +++ b/pkg/formats/rtpmpeg4audiolatm/encoder.go @@ -0,0 +1,156 @@ +package rtpmpeg4audiolatm + +import ( + "crypto/rand" + "fmt" + "time" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v3/pkg/rtptime" +) + +const ( + rtpVersion = 2 +) + +func randUint32() uint32 { + var b [4]byte + rand.Read(b[:]) + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]) +} + +// Encoder is a RTP/MPEG4-audio encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 +type Encoder struct { + // payload type of packets. + PayloadType uint8 + + // StreamMuxConfig. + Config *mpeg4audio.StreamMuxConfig + + // SSRC of packets (optional). + // It defaults to a random value. + SSRC *uint32 + + // initial sequence number of packets (optional). + // It defaults to a random value. + InitialSequenceNumber *uint16 + + // initial timestamp of packets (optional). + // It defaults to a random value. + InitialTimestamp *uint32 + + // maximum size of packet payloads (optional). + // It defaults to 1460. + PayloadMaxSize int + + sequenceNumber uint16 + timeEncoder *rtptime.Encoder +} + +// Init initializes the encoder. +func (e *Encoder) Init() error { + if e.Config == nil || len(e.Config.Programs) != 1 || len(e.Config.Programs[0].Layers) != 1 { + return fmt.Errorf("unsupported StreamMuxConfig") + } + + if e.SSRC == nil { + v := randUint32() + e.SSRC = &v + } + if e.InitialSequenceNumber == nil { + v := uint16(randUint32()) + e.InitialSequenceNumber = &v + } + if e.InitialTimestamp == nil { + v := randUint32() + e.InitialTimestamp = &v + } + if e.PayloadMaxSize == 0 { + e.PayloadMaxSize = 1460 // 1500 (UDP MTU) - 20 (IP header) - 8 (UDP header) - 12 (RTP header) + } + + e.sequenceNumber = *e.InitialSequenceNumber + e.timeEncoder = rtptime.NewEncoder(e.Config.Programs[0].Layers[0].AudioSpecificConfig.SampleRate, *e.InitialTimestamp) + return nil +} + +func payloadLengthInfoLen(auLen int) int { + return auLen/255 + 1 +} + +func payloadLengthInfo(plil int, auLen int, buf []byte) { + for i := 0; i < plil; i++ { + buf[i] = 255 + } + buf[plil-1] = byte(auLen % 255) +} + +func (e *Encoder) packetCount(auLen int, plil int) int { + totalLen := plil + auLen + packetCount := totalLen / e.PayloadMaxSize + lastPacketSize := totalLen % e.PayloadMaxSize + if lastPacketSize > 0 { + packetCount++ + } + return packetCount +} + +// Encode encodes AUs into RTP packets. +func (e *Encoder) Encode(au []byte, pts time.Duration) ([]*rtp.Packet, error) { + auLen := len(au) + plil := payloadLengthInfoLen(auLen) + packetCount := e.packetCount(auLen, plil) + + avail := e.PayloadMaxSize - plil + ret := make([]*rtp.Packet, packetCount) + encPTS := e.timeEncoder.Encode(pts) + + for i := range ret { + var final bool + var l int + + if len(au) < avail { + l = len(au) + final = true + } else { + l = avail + final = false + } + + var payload []byte + + if i == 0 { + payload = make([]byte, plil+l) + payloadLengthInfo(plil, auLen, payload) + copy(payload[plil:], au[:l]) + } else { + payload = au[:l] + } + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: encPTS, + SSRC: *e.SSRC, + Marker: final, + }, + Payload: payload, + } + + e.sequenceNumber++ + + if final { + break + } + + au = au[l:] + avail = e.PayloadMaxSize + } + + return ret, nil +} diff --git a/pkg/formats/rtpmpeg4audiolatm/encoder_test.go b/pkg/formats/rtpmpeg4audiolatm/encoder_test.go new file mode 100644 index 00000000..59c421c5 --- /dev/null +++ b/pkg/formats/rtpmpeg4audiolatm/encoder_test.go @@ -0,0 +1,177 @@ +package rtpmpeg4audiolatm + +import ( + "bytes" + "testing" + + "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" + "github.com/pion/rtp" + "github.com/stretchr/testify/require" +) + +func uint16Ptr(v uint16) *uint16 { + return &v +} + +func uint32Ptr(v uint32) *uint32 { + return &v +} + +func mergeBytes(vals ...[]byte) []byte { + size := 0 + for _, v := range vals { + size += len(v) + } + res := make([]byte, size) + + pos := 0 + for _, v := range vals { + n := copy(res[pos:], v) + pos += n + } + + return res +} + +var cases = []struct { + name string + config *mpeg4audio.StreamMuxConfig + au []byte + pkts []*rtp.Packet +}{ + { + "single", + &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + []byte{1, 2, 3, 4}, + []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 17645, + Timestamp: 2289526357, + SSRC: 2646308882, + }, + Payload: []byte{ + 0x04, 0x01, 0x02, 0x03, 0x04, + }, + }, + }, + }, + { + "fragmented", + &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 512), + []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: false, + PayloadType: 96, + SequenceNumber: 17645, + Timestamp: 2289526357, + SSRC: 2646308882, + }, + Payload: mergeBytes( + bytes.Repeat([]byte{0xff}, 16), + []byte{0x10}, + bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 180), + []byte{0, 1, 2}, + ), + }, + { + Header: rtp.Header{ + Version: 2, + Marker: false, + PayloadType: 96, + SequenceNumber: 17646, + Timestamp: 2289526357, + SSRC: 2646308882, + }, + Payload: mergeBytes( + []byte{3, 4, 5, 6, 7}, + bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 181), + []byte{0, 1, 2, 3, 4, 5, 6}, + ), + }, + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 17647, + Timestamp: 2289526357, + SSRC: 2646308882, + }, + Payload: mergeBytes( + []byte{7}, + bytes.Repeat([]byte{0, 1, 2, 3, 4, 5, 6, 7}, 149), + ), + }, + }, + }, +} + +func TestEncode(t *testing.T) { + for _, ca := range cases { + t.Run(ca.name, func(t *testing.T) { + e := &Encoder{ + PayloadType: 96, + Config: ca.config, + SSRC: uint32Ptr(0x9dbb7812), + InitialSequenceNumber: uint16Ptr(0x44ed), + InitialTimestamp: uint32Ptr(0x88776655), + } + e.Init() + + pkts, err := e.Encode(ca.au, 0) + require.NoError(t, err) + require.Equal(t, ca.pkts, pkts) + }) + } +} + +func TestEncodeRandomInitialState(t *testing.T) { + e := &Encoder{ + PayloadType: 96, + Config: &mpeg4audio.StreamMuxConfig{ + Programs: []*mpeg4audio.StreamMuxConfigProgram{{ + Layers: []*mpeg4audio.StreamMuxConfigLayer{{ + AudioSpecificConfig: &mpeg4audio.AudioSpecificConfig{ + Type: 2, + SampleRate: 48000, + ChannelCount: 2, + }, + LatmBufferFullness: 255, + }}, + }}, + }, + } + e.Init() + require.NotEqual(t, nil, e.SSRC) + require.NotEqual(t, nil, e.InitialSequenceNumber) + require.NotEqual(t, nil, e.InitialTimestamp) +} diff --git a/pkg/formats/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go b/pkg/formats/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go new file mode 100644 index 00000000..9521630a --- /dev/null +++ b/pkg/formats/rtpmpeg4audiolatm/rtpmpeg4audio_latm.go @@ -0,0 +1,2 @@ +// Package rtpmpeg4audiolatm contains a RTP/MPEG-4 Audio decoder and encoder. +package rtpmpeg4audiolatm diff --git a/pkg/formats/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b b/pkg/formats/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b new file mode 100644 index 00000000..bd06f9a9 --- /dev/null +++ b/pkg/formats/rtpmpeg4audiolatm/testdata/fuzz/FuzzDecoder/079a435e5445c35b @@ -0,0 +1,5 @@ +go test fuzz v1 +[]byte("") +bool(false) +[]byte("") +bool(false) diff --git a/pkg/formats/rtpmpeg4video/decoder.go b/pkg/formats/rtpmpeg4video/decoder.go index 4cd64bad..7a93f4e2 100644 --- a/pkg/formats/rtpmpeg4video/decoder.go +++ b/pkg/formats/rtpmpeg4video/decoder.go @@ -35,8 +35,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(90000) + return nil } // Decode decodes a frame from a RTP packet. diff --git a/pkg/formats/rtpmpeg4video/encoder.go b/pkg/formats/rtpmpeg4video/encoder.go index a2fc5f10..4b2ba8c0 100644 --- a/pkg/formats/rtpmpeg4video/encoder.go +++ b/pkg/formats/rtpmpeg4video/encoder.go @@ -46,7 +46,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -65,17 +65,23 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp) + return nil +} + +func packetCount(avail, le int) int { + packetCount := le / avail + lastPacketSize := le % avail + if lastPacketSize > 0 { + packetCount++ + } + return packetCount } // Encode encodes a frame into RTP packets. func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error) { avail := e.PayloadMaxSize le := len(frame) - packetCount := le / avail - lastPacketSize := le % avail - if lastPacketSize > 0 { - packetCount++ - } + packetCount := packetCount(avail, le) pos := 0 ret := make([]*rtp.Packet, packetCount) @@ -86,7 +92,7 @@ func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error) if i != (packetCount - 1) { le = avail } else { - le = lastPacketSize + le = len(frame[pos:]) } ret[i] = &rtp.Packet{ @@ -101,6 +107,7 @@ func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error) Payload: frame[pos : pos+le], } + pos += le e.sequenceNumber++ } diff --git a/pkg/formats/rtpsimpleaudio/decoder.go b/pkg/formats/rtpsimpleaudio/decoder.go index db4d05d8..4876f9a6 100644 --- a/pkg/formats/rtpsimpleaudio/decoder.go +++ b/pkg/formats/rtpsimpleaudio/decoder.go @@ -16,8 +16,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(d.SampleRate) + return nil } // Decode decodes an audio frame from a RTP packet. diff --git a/pkg/formats/rtpsimpleaudio/encoder.go b/pkg/formats/rtpsimpleaudio/encoder.go index 155e8e95..af397621 100644 --- a/pkg/formats/rtpsimpleaudio/encoder.go +++ b/pkg/formats/rtpsimpleaudio/encoder.go @@ -48,7 +48,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -67,6 +67,7 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp) + return nil } // Encode encodes an audio frame into a RTP packet. diff --git a/pkg/formats/rtpvp8/decoder.go b/pkg/formats/rtpvp8/decoder.go index 60cd6b00..584c2fc6 100644 --- a/pkg/formats/rtpvp8/decoder.go +++ b/pkg/formats/rtpvp8/decoder.go @@ -39,8 +39,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) + return nil } // Decode decodes a VP8 frame from a RTP packet. @@ -90,7 +91,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { } frame = joinFragments(d.fragments, n) - d.fragments = d.fragments[:0] } diff --git a/pkg/formats/rtpvp8/encoder.go b/pkg/formats/rtpvp8/encoder.go index ba505733..c11783b0 100644 --- a/pkg/formats/rtpvp8/encoder.go +++ b/pkg/formats/rtpvp8/encoder.go @@ -49,7 +49,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -68,6 +68,7 @@ func (e *Encoder) Init() { e.sequenceNumber = *e.InitialSequenceNumber e.timeEncoder = rtptime.NewEncoder(rtpClockRate, *e.InitialTimestamp) + return nil } // Encode encodes a VP8 frame into RTP/VP8 packets. diff --git a/pkg/formats/rtpvp9/decoder.go b/pkg/formats/rtpvp9/decoder.go index d8b3ecd1..f1e66f29 100644 --- a/pkg/formats/rtpvp9/decoder.go +++ b/pkg/formats/rtpvp9/decoder.go @@ -39,8 +39,9 @@ type Decoder struct { } // Init initializes the decoder. -func (d *Decoder) Init() { +func (d *Decoder) Init() error { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) + return nil } // Decode decodes a VP9 frame from a RTP packet. @@ -85,7 +86,6 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { } frame = joinFragments(d.fragments, n) - d.fragments = d.fragments[:0] } diff --git a/pkg/formats/rtpvp9/encoder.go b/pkg/formats/rtpvp9/encoder.go index 89f143e1..23fbe315 100644 --- a/pkg/formats/rtpvp9/encoder.go +++ b/pkg/formats/rtpvp9/encoder.go @@ -53,7 +53,7 @@ type Encoder struct { } // Init initializes the encoder. -func (e *Encoder) Init() { +func (e *Encoder) Init() error { if e.SSRC == nil { v := randUint32() e.SSRC = &v @@ -80,6 +80,8 @@ func (e *Encoder) Init() { e.vp.InitialPictureIDFn = func() uint16 { return *e.InitialPictureID } + + return nil } // Encode encodes a VP9 frame into RTP/VP9 packets. diff --git a/pkg/formats/vp8.go b/pkg/formats/vp8.go index 9ff69772..7f05d84f 100644 --- a/pkg/formats/vp8.go +++ b/pkg/formats/vp8.go @@ -86,17 +86,43 @@ func (f *VP8) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *VP8) CreateDecoder() *rtpvp8.Decoder { - d := &rtpvp8.Decoder{} - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *VP8) CreateDecoder2() (*rtpvp8.Decoder, error) { + d := &rtpvp8.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *VP8) CreateEncoder() *rtpvp8.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *VP8) CreateEncoder2() (*rtpvp8.Encoder, error) { e := &rtpvp8.Encoder{ PayloadType: f.PayloadTyp, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/vp8_test.go b/pkg/formats/vp8_test.go index 259e8c74..0ad8d45a 100644 --- a/pkg/formats/vp8_test.go +++ b/pkg/formats/vp8_test.go @@ -19,12 +19,16 @@ func TestVP8ttributes(t *testing.T) { func TestVP8DecEncoder(t *testing.T) { format := &VP8{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) diff --git a/pkg/formats/vp9.go b/pkg/formats/vp9.go index e7661bc2..832fd9cc 100644 --- a/pkg/formats/vp9.go +++ b/pkg/formats/vp9.go @@ -98,17 +98,43 @@ func (f *VP9) PTSEqualsDTS(*rtp.Packet) bool { } // CreateDecoder creates a decoder able to decode the content of the format. +// +// Deprecated: this has been replaced by CreateDecoder2() that can also return an error. func (f *VP9) CreateDecoder() *rtpvp9.Decoder { - d := &rtpvp9.Decoder{} - d.Init() + d, _ := f.CreateDecoder2() return d } +// CreateDecoder2 creates a decoder able to decode the content of the format. +func (f *VP9) CreateDecoder2() (*rtpvp9.Decoder, error) { + d := &rtpvp9.Decoder{} + + err := d.Init() + if err != nil { + return nil, err + } + + return d, nil +} + // CreateEncoder creates an encoder able to encode the content of the format. +// +// Deprecated: this has been replaced by CreateEncoder2() that can also return an error. func (f *VP9) CreateEncoder() *rtpvp9.Encoder { + e, _ := f.CreateEncoder2() + return e +} + +// CreateEncoder2 creates an encoder able to encode the content of the format. +func (f *VP9) CreateEncoder2() (*rtpvp9.Encoder, error) { e := &rtpvp9.Encoder{ PayloadType: f.PayloadTyp, } - e.Init() - return e + + err := e.Init() + if err != nil { + return nil, err + } + + return e, nil } diff --git a/pkg/formats/vp9_test.go b/pkg/formats/vp9_test.go index fb4cff35..480a9e3a 100644 --- a/pkg/formats/vp9_test.go +++ b/pkg/formats/vp9_test.go @@ -19,12 +19,16 @@ func TestVP9Attributes(t *testing.T) { func TestVP9DecEncoder(t *testing.T) { format := &VP9{} - enc := format.CreateEncoder() + enc, err := format.CreateEncoder2() + require.NoError(t, err) + pkts, err := enc.Encode([]byte{0x01, 0x02, 0x03, 0x04}, 0) require.NoError(t, err) require.Equal(t, format.PayloadType(), pkts[0].PayloadType) - dec := format.CreateDecoder() + dec, err := format.CreateDecoder2() + require.NoError(t, err) + byts, _, err := dec.Decode(pkts[0]) require.NoError(t, err) require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts)