diff --git a/README.md b/README.md index 4259f269..2a37c7ba 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ Features: * Utilities * Parse RTSP elements * Encode/decode format-specific frames into/from RTP packets. The following formats are supported: - * Video: H264, H265, M-JPEG, VP8, VP9 - * Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG-2 audio (MP3), MPEG-4 audio (AAC), Opus + * Video: H264, H265, M-JPEG, VP8, VP9, MPEG-4 Video (H263, DivX) + * Audio: G711 (PCMA, PCMU), G722, LPCM, MPEG-2 Audio (MP3), MPEG-4 Audio (AAC), Opus ## Table of contents diff --git a/pkg/formats/mpeg2_audio.go b/pkg/formats/mpeg2_audio.go index 024fc3ae..5822de7a 100644 --- a/pkg/formats/mpeg2_audio.go +++ b/pkg/formats/mpeg2_audio.go @@ -6,7 +6,7 @@ import ( "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg2audio" ) -// MPEG2Audio is a RTP format that uses a MPEG-1 or MPEG-2 audio codec. +// MPEG2Audio is a RTP format that uses a MPEG-1 or MPEG-2 Audio codec. // Specification: https://datatracker.ietf.org/doc/html/rfc2250 type MPEG2Audio struct{} diff --git a/pkg/formats/mpeg4_audio_generic.go b/pkg/formats/mpeg4_audio_generic.go index 9da7ff63..bdd36187 100644 --- a/pkg/formats/mpeg4_audio_generic.go +++ b/pkg/formats/mpeg4_audio_generic.go @@ -15,7 +15,7 @@ import ( // MPEG4Audio is an alias for MPEG4AudioGeneric. type MPEG4Audio = MPEG4AudioGeneric -// MPEG4AudioGeneric is a RTP format that uses a MPEG-4 audio codec. +// MPEG4AudioGeneric is a RTP format that uses a MPEG-4 Audio codec. // Specification: https://datatracker.ietf.org/doc/html/rfc3640 type MPEG4AudioGeneric struct { PayloadTyp uint8 diff --git a/pkg/formats/mpeg4_audio_latm.go b/pkg/formats/mpeg4_audio_latm.go index 4e80fd34..dd9c28d2 100644 --- a/pkg/formats/mpeg4_audio_latm.go +++ b/pkg/formats/mpeg4_audio_latm.go @@ -9,7 +9,7 @@ import ( "github.com/pion/rtp" ) -// MPEG4AudioLATM is a RTP format that uses a MPEG-4 audio codec. +// MPEG4AudioLATM is a RTP format that uses a MPEG-4 Audio codec. // Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.3 type MPEG4AudioLATM struct { PayloadTyp uint8 diff --git a/pkg/formats/mpeg4_video_es.go b/pkg/formats/mpeg4_video_es.go index 3c308714..bacbf219 100644 --- a/pkg/formats/mpeg4_video_es.go +++ b/pkg/formats/mpeg4_video_es.go @@ -7,12 +7,14 @@ import ( "strings" "github.com/pion/rtp" + + "github.com/bluenviron/gortsplib/v3/pkg/formats/rtpmpeg4video" ) // MPEG4Video is an alias for MPEG4VideoES. type MPEG4Video = MPEG4VideoES -// MPEG4VideoES is a RTP format that uses the video codec defined in MPEG-4 part 2. +// MPEG4VideoES is a RTP format that uses a MPEG-4 Video codec. // Specification: https://datatracker.ietf.org/doc/html/rfc6416#section-7.1 type MPEG4VideoES struct { PayloadTyp uint8 @@ -83,3 +85,19 @@ func (f *MPEG4VideoES) FMTP() map[string]string { func (f *MPEG4VideoES) PTSEqualsDTS(*rtp.Packet) bool { return true } + +// CreateDecoder creates a decoder able to decode the content of the format. +func (f *MPEG4VideoES) CreateDecoder() *rtpmpeg4video.Decoder { + d := &rtpmpeg4video.Decoder{} + d.Init() + return d +} + +// CreateEncoder creates an encoder able to encode the content of the format. +func (f *MPEG4VideoES) CreateEncoder() *rtpmpeg4video.Encoder { + e := &rtpmpeg4video.Encoder{ + PayloadType: f.PayloadTyp, + } + e.Init() + return e +} diff --git a/pkg/formats/mpeg4_video_es_test.go b/pkg/formats/mpeg4_video_es_test.go index 14a81bf3..228aa8c1 100644 --- a/pkg/formats/mpeg4_video_es_test.go +++ b/pkg/formats/mpeg4_video_es_test.go @@ -17,3 +17,19 @@ func TestMPEG4VideoESAttributes(t *testing.T) { require.Equal(t, 90000, format.ClockRate()) require.Equal(t, true, format.PTSEqualsDTS(&rtp.Packet{})) } + +func TestMPEG4VideoESDecEncoder(t *testing.T) { + format := &MPEG4VideoES{ + PayloadTyp: 96, + } + + enc := format.CreateEncoder() + 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() + byts, _, err := dec.Decode(pkts[0]) + require.NoError(t, err) + require.Equal(t, []byte{0x01, 0x02, 0x03, 0x04}, byts) +} diff --git a/pkg/formats/rtph264/decoder.go b/pkg/formats/rtph264/decoder.go index 7fe64e0a..f7b48d83 100644 --- a/pkg/formats/rtph264/decoder.go +++ b/pkg/formats/rtph264/decoder.go @@ -52,7 +52,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) } -// Decode decodes NALUs from a RTP/H264 packet. +// 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") @@ -167,7 +167,7 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { return nalus, d.timeDecoder.Decode(pkt.Timestamp), nil } -// DecodeUntilMarker decodes NALUs from a RTP/H264 packet and puts them in a buffer. +// DecodeUntilMarker decodes NALUs from a RTP packet and puts them in a buffer. // When a packet has the marker flag (meaning that all the NALUs with the same PTS have // been received), the buffer is returned. func (d *Decoder) DecodeUntilMarker(pkt *rtp.Packet) ([][]byte, time.Duration, error) { diff --git a/pkg/formats/rtph264/encoder_test.go b/pkg/formats/rtph264/encoder_test.go index 81f90b0a..3f3ce3b2 100644 --- a/pkg/formats/rtph264/encoder_test.go +++ b/pkg/formats/rtph264/encoder_test.go @@ -3,7 +3,6 @@ package rtph264 import ( "bytes" "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -28,7 +27,6 @@ func mergeBytes(vals ...[]byte) []byte { var cases = []struct { name string nalus [][]byte - pts time.Duration pkts []*rtp.Packet }{ { @@ -39,7 +37,6 @@ var cases = []struct { bytes.Repeat([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 8), ), }, - 25 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -47,7 +44,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289528607, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -65,7 +62,6 @@ var cases = []struct { bytes.Repeat([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 512), ), }, - 55 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -73,7 +69,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -88,7 +84,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -104,7 +100,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17647, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -131,7 +127,6 @@ var cases = []struct { 0x00, 0x00, 0x6d, 0x40, }, }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -177,7 +172,6 @@ var cases = []struct { bytes.Repeat([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 175), ), }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -227,7 +221,6 @@ var cases = []struct { {0x09, 0xF0}, {0x09, 0xF0}, }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -297,7 +290,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.nalus, ca.pts) + pkts, err := e.Encode(ca.nalus, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) diff --git a/pkg/formats/rtph265/decoder.go b/pkg/formats/rtph265/decoder.go index e8c0657e..065e20bb 100644 --- a/pkg/formats/rtph265/decoder.go +++ b/pkg/formats/rtph265/decoder.go @@ -50,7 +50,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) } -// Decode decodes NALUs from a RTP/H265 packet. +// 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)") @@ -159,7 +159,7 @@ func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { return nalus, d.timeDecoder.Decode(pkt.Timestamp), nil } -// DecodeUntilMarker decodes NALUs from a RTP/H265 packet and puts them in a buffer. +// DecodeUntilMarker decodes NALUs from a RTP packet and puts them in a buffer. // When a packet has the marker flag (meaning that all the NALUs with the same PTS have // been received), the buffer is returned. func (d *Decoder) DecodeUntilMarker(pkt *rtp.Packet) ([][]byte, time.Duration, error) { diff --git a/pkg/formats/rtph265/encoder_test.go b/pkg/formats/rtph265/encoder_test.go index 84d1e827..cf9561be 100644 --- a/pkg/formats/rtph265/encoder_test.go +++ b/pkg/formats/rtph265/encoder_test.go @@ -3,7 +3,6 @@ package rtph265 import ( "bytes" "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -28,13 +27,11 @@ func mergeBytes(vals ...[]byte) []byte { var cases = []struct { name string nalus [][]byte - pts time.Duration pkts []*rtp.Packet }{ { "single", [][]byte{{0x01, 0x02, 0x03, 0x04, 0x05}}, - 25 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -42,7 +39,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289528607, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, @@ -56,7 +53,6 @@ var cases = []struct { {0x08, 0x08}, {0x09, 0x09}, }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -79,7 +75,6 @@ var cases = []struct { [][]byte{ bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 1024), }, - 55 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -87,7 +82,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -102,7 +97,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -116,7 +111,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17647, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -148,7 +143,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.nalus, ca.pts) + pkts, err := e.Encode(ca.nalus, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) diff --git a/pkg/formats/rtplpcm/encoder_test.go b/pkg/formats/rtplpcm/encoder_test.go index 57324400..a2d6a83b 100644 --- a/pkg/formats/rtplpcm/encoder_test.go +++ b/pkg/formats/rtplpcm/encoder_test.go @@ -3,7 +3,6 @@ package rtplpcm import ( "bytes" "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -12,13 +11,11 @@ import ( var cases = []struct { name string samples []byte - pts time.Duration pkts []*rtp.Packet }{ { "single", []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, - 25 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -26,7 +23,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527557, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, @@ -36,7 +33,6 @@ var cases = []struct { { "splitted", bytes.Repeat([]byte{0x41, 0x42, 0x43}, 680), - 25 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -44,7 +40,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527557, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: bytes.Repeat([]byte{0x41, 0x42, 0x43}, 486), @@ -55,7 +51,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289527800, + Timestamp: 2289526600, SSRC: 0x9dbb7812, }, Payload: bytes.Repeat([]byte{0x41, 0x42, 0x43}, 194), @@ -87,7 +83,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.samples, ca.pts) + pkts, err := e.Encode(ca.samples, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) diff --git a/pkg/formats/rtpmjpeg/decoder.go b/pkg/formats/rtpmjpeg/decoder.go index db052870..4d3da2c5 100644 --- a/pkg/formats/rtpmjpeg/decoder.go +++ b/pkg/formats/rtpmjpeg/decoder.go @@ -119,7 +119,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) } -// Decode decodes an image from a RTP/M-JPEG packet. +// Decode decodes an image from a RTP packet. func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { byts := pkt.Payload diff --git a/pkg/formats/rtpmjpeg/decoder_test.go b/pkg/formats/rtpmjpeg/decoder_test.go index a5747895..e9444b87 100644 --- a/pkg/formats/rtpmjpeg/decoder_test.go +++ b/pkg/formats/rtpmjpeg/decoder_test.go @@ -14,13 +14,12 @@ func TestDecode(t *testing.T) { d.Init() for _, pkt := range ca.pkts { - image, pts, err := d.Decode(pkt) + image, _, err := d.Decode(pkt) if err == ErrMorePacketsNeeded { continue } require.NoError(t, err) - require.Equal(t, ca.pts, pts) require.Equal(t, ca.image, image) } }) diff --git a/pkg/formats/rtpmjpeg/encoder_test.go b/pkg/formats/rtpmjpeg/encoder_test.go index 143f8785..2af524db 100644 --- a/pkg/formats/rtpmjpeg/encoder_test.go +++ b/pkg/formats/rtpmjpeg/encoder_test.go @@ -2,7 +2,6 @@ package rtpmjpeg import ( "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -11,7 +10,6 @@ import ( var cases = []struct { name string image []byte - pts time.Duration pkts []*rtp.Packet }{ { @@ -276,7 +274,6 @@ var cases = []struct { 0x59, 0x54, 0xda, 0xb6, 0xb3, 0x2e, 0xb3, 0x7f, 0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9, }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -527,7 +524,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.image, ca.pts) + pkts, err := e.Encode(ca.image, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) diff --git a/pkg/formats/rtpmpeg2audio/decoder.go b/pkg/formats/rtpmpeg2audio/decoder.go index 3fd17bc4..d34489ca 100644 --- a/pkg/formats/rtpmpeg2audio/decoder.go +++ b/pkg/formats/rtpmpeg2audio/decoder.go @@ -30,7 +30,7 @@ func joinFragments(fragments [][]byte, size int) []byte { return ret } -// Decoder is a RTP/MPEG2-audio decoder. +// Decoder is a RTP/MPEG-2 Audio decoder. // Specification: https://datatracker.ietf.org/doc/html/rfc2250 type Decoder struct { timeDecoder *rtptime.Decoder @@ -45,7 +45,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(90000) } -// Decode decodes frames from a RTP/MPEG2-audio packet. +// Decode decodes frames from a RTP packet. func (d *Decoder) Decode(pkt *rtp.Packet) ([][]byte, time.Duration, error) { if len(pkt.Payload) < 5 { d.fragments = d.fragments[:0] // discard pending fragmented packets diff --git a/pkg/formats/rtpmpeg2audio/encoder.go b/pkg/formats/rtpmpeg2audio/encoder.go index 41454a28..8f35d64c 100644 --- a/pkg/formats/rtpmpeg2audio/encoder.go +++ b/pkg/formats/rtpmpeg2audio/encoder.go @@ -28,7 +28,7 @@ func lenAggregated(frames [][]byte, frame []byte) int { return l } -// Encoder is a RTP/MPEG2-audio encoder. +// Encoder is a RTP/MPEG-2 Audio encoder. // Specification: https://datatracker.ietf.org/doc/html/rfc2250 type Encoder struct { // SSRC of packets (optional). @@ -73,7 +73,7 @@ func (e *Encoder) Init() { e.timeEncoder = rtptime.NewEncoder(90000, *e.InitialTimestamp) } -// Encode encodes frames into RTP/MPEG2-audio packets. +// Encode encodes frames into RTP packets. func (e *Encoder) Encode(frames [][]byte, pts time.Duration) ([]*rtp.Packet, error) { var rets []*rtp.Packet var batch [][]byte diff --git a/pkg/formats/rtpmpeg2audio/rtpmpeg2audio.go b/pkg/formats/rtpmpeg2audio/rtpmpeg2audio.go index a2082c09..f895e2c0 100644 --- a/pkg/formats/rtpmpeg2audio/rtpmpeg2audio.go +++ b/pkg/formats/rtpmpeg2audio/rtpmpeg2audio.go @@ -1,2 +1,2 @@ -// Package rtpmpeg2audio contains a RTP/MPEG2-audio decoder and encoder. +// Package rtpmpeg2audio contains a RTP/MPEG-2 Audio decoder and encoder. package rtpmpeg2audio diff --git a/pkg/formats/rtpmpeg4audio/decoder.go b/pkg/formats/rtpmpeg4audio/decoder.go index b8d1d481..cbff3c72 100644 --- a/pkg/formats/rtpmpeg4audio/decoder.go +++ b/pkg/formats/rtpmpeg4audio/decoder.go @@ -24,7 +24,7 @@ func joinFragments(fragments [][]byte, size int) []byte { return ret } -// Decoder is a RTP/MPEG4-audio decoder. +// Decoder is a RTP/MPEG-4 Audio decoder. // Specification: https://datatracker.ietf.org/doc/html/rfc3640 type Decoder struct { // sample rate of input packets. @@ -51,7 +51,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(d.SampleRate) } -// Decode decodes AUs from a RTP/MPEG4-audio packet. +// 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) { diff --git a/pkg/formats/rtpmpeg4audio/encoder.go b/pkg/formats/rtpmpeg4audio/encoder.go index 366339e4..eb9f6d64 100644 --- a/pkg/formats/rtpmpeg4audio/encoder.go +++ b/pkg/formats/rtpmpeg4audio/encoder.go @@ -81,7 +81,7 @@ func (e *Encoder) Init() { e.timeEncoder = rtptime.NewEncoder(e.SampleRate, *e.InitialTimestamp) } -// Encode encodes AUs into RTP/MPEG4-audio packets. +// 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 diff --git a/pkg/formats/rtpmpeg4audio/encoder_test.go b/pkg/formats/rtpmpeg4audio/encoder_test.go index dd5afde7..f89ff2ce 100644 --- a/pkg/formats/rtpmpeg4audio/encoder_test.go +++ b/pkg/formats/rtpmpeg4audio/encoder_test.go @@ -3,7 +3,6 @@ package rtpmpeg4audio import ( "bytes" "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -31,7 +30,6 @@ var cases = []struct { indexLength int indexDeltaLength int aus [][]byte - pts time.Duration pkts []*rtp.Packet }{ { @@ -87,7 +85,6 @@ var cases = []struct { 0xaf, 0x7, }, }, - 20 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -95,7 +92,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{ @@ -158,7 +155,6 @@ var cases = []struct { {0x04, 0x05, 0x06, 0x07}, {0x08, 0x09, 0x0A, 0x0B}, }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -177,7 +173,7 @@ var cases = []struct { }, }, }, - { + { //nolint:dupl "fragmented", 13, 3, @@ -185,7 +181,6 @@ var cases = []struct { [][]byte{ bytes.Repeat([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 512), }, - 0, []*rtp.Packet{ //nolint:dupl { Header: rtp.Header{ @@ -242,7 +237,6 @@ var cases = []struct { {0x08, 0x09, 0x0A, 0x0B}, bytes.Repeat([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 256), }, - 0, []*rtp.Packet{ { Header: rtp.Header{ @@ -297,7 +291,6 @@ var cases = []struct { [][]byte{ {0x01, 0x02, 0x03, 0x04}, }, - 20 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -305,7 +298,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{ @@ -323,7 +316,6 @@ var cases = []struct { [][]byte{ {0x01, 0x02, 0x03, 0x04}, }, - 20 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -331,7 +323,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{ @@ -350,7 +342,6 @@ var cases = []struct { {0x01, 0x02, 0x03, 0x04}, {0x05, 0x06, 0x07, 0x08}, }, - 20 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -358,7 +349,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{ @@ -377,7 +368,6 @@ var cases = []struct { [][]byte{ bytes.Repeat([]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 512), }, - 20 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -385,7 +375,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -400,7 +390,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -415,7 +405,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17647, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -425,7 +415,7 @@ var cases = []struct { }, }, }, - { + { //nolint:dupl "fragmented, custom sized, padded", 13, 0, @@ -433,7 +423,6 @@ var cases = []struct { [][]byte{ bytes.Repeat([]byte{0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, 512), }, - 20 * time.Millisecond, []*rtp.Packet{ //nolint:dupl { Header: rtp.Header{ @@ -441,7 +430,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -455,7 +444,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -469,7 +458,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17647, - Timestamp: 2289527317, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes( @@ -505,7 +494,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.aus, ca.pts) + pkts, err := e.Encode(ca.aus, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) diff --git a/pkg/formats/rtpmpeg4audio/rtpmpeg4audio.go b/pkg/formats/rtpmpeg4audio/rtpmpeg4audio.go index e8f1e0cb..6698abbd 100644 --- a/pkg/formats/rtpmpeg4audio/rtpmpeg4audio.go +++ b/pkg/formats/rtpmpeg4audio/rtpmpeg4audio.go @@ -1,2 +1,2 @@ -// Package rtpmpeg4audio contains a RTP/MPEG4-audio decoder and encoder. +// Package rtpmpeg4audio contains a RTP/MPEG-4 Audio decoder and encoder. package rtpmpeg4audio diff --git a/pkg/formats/rtpmpeg4video/decoder.go b/pkg/formats/rtpmpeg4video/decoder.go new file mode 100644 index 00000000..c71b09d2 --- /dev/null +++ b/pkg/formats/rtpmpeg4video/decoder.go @@ -0,0 +1,72 @@ +package rtpmpeg4video + +import ( + "errors" + "fmt" + "time" + + "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") + +const ( + maxFrameSize = 1 * 1024 * 1024 +) + +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 Video decoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416 +type Decoder struct { + timeDecoder *rtptime.Decoder + fragments [][]byte + fragmentedSize int +} + +// Init initializes the decoder. +func (d *Decoder) Init() { + d.timeDecoder = rtptime.NewDecoder(90000) +} + +// Decode decodes a frame from a RTP packet. +func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { + var frame []byte + + if len(d.fragments) == 0 { + if pkt.Marker { + frame = pkt.Payload + } else { + d.fragmentedSize = len(pkt.Payload) + d.fragments = append(d.fragments, pkt.Payload) + return nil, 0, ErrMorePacketsNeeded + } + } else { + d.fragmentedSize += len(pkt.Payload) + if d.fragmentedSize > maxFrameSize { + d.fragments = d.fragments[:0] // discard pending fragmented packets + return nil, 0, fmt.Errorf("frame size (%d) is too big (maximum is %d)", d.fragmentedSize, maxFrameSize) + } + + d.fragments = append(d.fragments, pkt.Payload) + + if !pkt.Marker { + return nil, 0, ErrMorePacketsNeeded + } + + frame = joinFragments(d.fragments, d.fragmentedSize) + d.fragments = d.fragments[:0] + } + + return frame, d.timeDecoder.Decode(pkt.Timestamp), nil +} diff --git a/pkg/formats/rtpmpeg4video/decoder_test.go b/pkg/formats/rtpmpeg4video/decoder_test.go new file mode 100644 index 00000000..14c7e67d --- /dev/null +++ b/pkg/formats/rtpmpeg4video/decoder_test.go @@ -0,0 +1,62 @@ +package rtpmpeg4video + +import ( + "testing" + + "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{} + d.Init() + + var frame []byte + var err error + + for _, pkt := range ca.pkts { + frame, _, err = d.Decode(pkt) + if err == ErrMorePacketsNeeded { + continue + } + + require.NoError(t, err) + } + + require.Equal(t, ca.frame, frame) + }) + } +} + +func FuzzDecoder(f *testing.F) { + f.Fuzz(func(t *testing.T, a []byte, am bool, b []byte, bm bool) { + d := &Decoder{} + 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/rtpmpeg4video/encoder.go b/pkg/formats/rtpmpeg4video/encoder.go new file mode 100644 index 00000000..d0975186 --- /dev/null +++ b/pkg/formats/rtpmpeg4video/encoder.go @@ -0,0 +1,111 @@ +package rtpmpeg4video + +import ( + "crypto/rand" + "time" + + "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/MPEG-4 Video encoder. +// Specification: https://datatracker.ietf.org/doc/html/rfc6416 +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 + + 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(90000, *e.InitialTimestamp) +} + +// Encode encodes a frame into RTP packets. +func (e *Encoder) Encode(frame []byte, pts time.Duration) ([]*rtp.Packet, error) { + availPerPacket := e.PayloadMaxSize + le := len(frame) + packetCount := le / availPerPacket + lastPacketSize := le % availPerPacket + if lastPacketSize > 0 { + packetCount++ + } + + pos := 0 + ret := make([]*rtp.Packet, packetCount) + encPTS := e.timeEncoder.Encode(pts) + + for i := range ret { + var le int + if i != (packetCount - 1) { + le = availPerPacket + } else { + le = lastPacketSize + } + + payload := make([]byte, le) + pos += copy(payload, frame[pos:]) + + ret[i] = &rtp.Packet{ + Header: rtp.Header{ + Version: rtpVersion, + PayloadType: e.PayloadType, + SequenceNumber: e.sequenceNumber, + Timestamp: encPTS, + SSRC: *e.SSRC, + Marker: (i == len(ret)-1), + }, + Payload: payload, + } + + e.sequenceNumber++ + } + + return ret, nil +} diff --git a/pkg/formats/rtpmpeg4video/encoder_test.go b/pkg/formats/rtpmpeg4video/encoder_test.go new file mode 100644 index 00000000..e86554de --- /dev/null +++ b/pkg/formats/rtpmpeg4video/encoder_test.go @@ -0,0 +1,101 @@ +package rtpmpeg4video + +import ( + "bytes" + "testing" + + "github.com/pion/rtp" + "github.com/stretchr/testify/require" +) + +var cases = []struct { + name string + frame []byte + pkts []*rtp.Packet +}{ + { + "single", + []byte{0x01, 0x02, 0x03, 0x04}, + []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 17645, + Timestamp: 2289526357, + SSRC: 0x9dbb7812, + }, + Payload: []byte{ + 0x01, 0x02, 0x03, 0x04, + }, + }, + }, + }, + { + "fragmented", + bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 150/4), + []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: false, + PayloadType: 96, + SequenceNumber: 17645, + Timestamp: 2289526357, + SSRC: 0x9dbb7812, + }, + Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 100/4), + }, + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 17646, + Timestamp: 2289526357, + SSRC: 0x9dbb7812, + }, + Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 50/4), + }, + }, + }, +} + +func TestEncode(t *testing.T) { + for _, ca := range cases { + t.Run(ca.name, func(t *testing.T) { + e := &Encoder{ + PayloadType: 96, + SSRC: func() *uint32 { + v := uint32(0x9dbb7812) + return &v + }(), + InitialSequenceNumber: func() *uint16 { + v := uint16(0x44ed) + return &v + }(), + InitialTimestamp: func() *uint32 { + v := uint32(0x88776655) + return &v + }(), + PayloadMaxSize: 100, + } + e.Init() + + pkts, err := e.Encode(ca.frame, 0) + require.NoError(t, err) + require.Equal(t, ca.pkts, pkts) + }) + } +} + +func TestEncodeRandomInitialState(t *testing.T) { + e := &Encoder{ + PayloadType: 96, + } + 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/rtpmpeg4video/rtpmpeg4video.go b/pkg/formats/rtpmpeg4video/rtpmpeg4video.go new file mode 100644 index 00000000..1acba5b7 --- /dev/null +++ b/pkg/formats/rtpmpeg4video/rtpmpeg4video.go @@ -0,0 +1,2 @@ +// Package rtpmpeg4video contains a RTP/MPEG-4 Video decoder and encoder. +package rtpmpeg4video diff --git a/pkg/formats/rtpsimpleaudio/encoder_test.go b/pkg/formats/rtpsimpleaudio/encoder_test.go index 6243a122..6b19843d 100644 --- a/pkg/formats/rtpsimpleaudio/encoder_test.go +++ b/pkg/formats/rtpsimpleaudio/encoder_test.go @@ -2,7 +2,6 @@ package rtpsimpleaudio import ( "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -11,20 +10,18 @@ import ( var cases = []struct { name string frame []byte - pts time.Duration pkt *rtp.Packet }{ { "single", []byte{0x01, 0x02, 0x03, 0x04}, - 25 * time.Millisecond, &rtp.Packet{ Header: rtp.Header{ Version: 2, Marker: false, PayloadType: 0, SequenceNumber: 17645, - Timestamp: 2289526557, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{0x01, 0x02, 0x03, 0x04}, @@ -53,7 +50,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkt, err := e.Encode(ca.frame, ca.pts) + pkt, err := e.Encode(ca.frame, 0) require.NoError(t, err) require.Equal(t, ca.pkt, pkt) }) diff --git a/pkg/formats/rtpvp8/decoder.go b/pkg/formats/rtpvp8/decoder.go index b85af485..44d7ea8c 100644 --- a/pkg/formats/rtpvp8/decoder.go +++ b/pkg/formats/rtpvp8/decoder.go @@ -43,7 +43,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) } -// Decode decodes a VP8 frame from a RTP/VP8 packet. +// Decode decodes a VP8 frame from a RTP packet. func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { var vpkt codecs.VP8Packet _, err := vpkt.Unmarshal(pkt.Payload) diff --git a/pkg/formats/rtpvp8/encoder_test.go b/pkg/formats/rtpvp8/encoder_test.go index 72cd9333..f99d0071 100644 --- a/pkg/formats/rtpvp8/encoder_test.go +++ b/pkg/formats/rtpvp8/encoder_test.go @@ -3,7 +3,6 @@ package rtpvp8 import ( "bytes" "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -28,13 +27,11 @@ func mergeBytes(vals ...[]byte) []byte { var cases = []struct { name string frame []byte - pts time.Duration pkts []*rtp.Packet }{ { "single", []byte{0x01, 0x02, 0x03, 0x04}, - 25 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -42,7 +39,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289528607, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{0x10, 0x01, 0x02, 0x03, 0x04}, @@ -52,7 +49,6 @@ var cases = []struct { { "fragmented", bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 4096/4), - 55 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -60,7 +56,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes([]byte{0x10}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 364), []byte{0x01, 0x02, 0x03}), @@ -71,7 +67,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes([]byte{0x00, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 364), []byte{0x01, 0x02}), @@ -82,7 +78,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17647, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes([]byte{0x00, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 294)), @@ -111,7 +107,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.frame, ca.pts) + pkts, err := e.Encode(ca.frame, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) }) diff --git a/pkg/formats/rtpvp9/decoder.go b/pkg/formats/rtpvp9/decoder.go index c45c679c..46fb9fb3 100644 --- a/pkg/formats/rtpvp9/decoder.go +++ b/pkg/formats/rtpvp9/decoder.go @@ -43,7 +43,7 @@ func (d *Decoder) Init() { d.timeDecoder = rtptime.NewDecoder(rtpClockRate) } -// Decode decodes a VP9 frame from a RTP/VP9 packet. +// Decode decodes a VP9 frame from a RTP packet. func (d *Decoder) Decode(pkt *rtp.Packet) ([]byte, time.Duration, error) { var vpkt codecs.VP9Packet _, err := vpkt.Unmarshal(pkt.Payload) diff --git a/pkg/formats/rtpvp9/encoder_test.go b/pkg/formats/rtpvp9/encoder_test.go index 7a94527a..473ad2cb 100644 --- a/pkg/formats/rtpvp9/encoder_test.go +++ b/pkg/formats/rtpvp9/encoder_test.go @@ -3,7 +3,6 @@ package rtpvp9 import ( "bytes" "testing" - "time" "github.com/pion/rtp" "github.com/stretchr/testify/require" @@ -28,13 +27,11 @@ func mergeBytes(vals ...[]byte) []byte { var cases = []struct { name string frame []byte - pts time.Duration pkts []*rtp.Packet }{ { "single", []byte{0x01, 0x02, 0x03, 0x04}, - 25 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -42,7 +39,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289528607, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: []byte{0x9c, 0xb5, 0xaf, 0x01, 0x02, 0x03, 0x04}, @@ -52,7 +49,6 @@ var cases = []struct { { "fragmented", bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 4096/4), - 55 * time.Millisecond, []*rtp.Packet{ { Header: rtp.Header{ @@ -60,7 +56,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17645, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes([]byte{0x98, 0xb5, 0xaf}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 364), []byte{0x01}), @@ -71,7 +67,7 @@ var cases = []struct { Marker: false, PayloadType: 96, SequenceNumber: 17646, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes([]byte{0x90, 0xb5, 0xaf, 0x02, 0x03, 0x04}, @@ -83,7 +79,7 @@ var cases = []struct { Marker: true, PayloadType: 96, SequenceNumber: 17647, - Timestamp: 2289531307, + Timestamp: 2289526357, SSRC: 0x9dbb7812, }, Payload: mergeBytes([]byte{0x94, 0xb5, 0xaf, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 295)), @@ -116,7 +112,7 @@ func TestEncode(t *testing.T) { } e.Init() - pkts, err := e.Encode(ca.frame, ca.pts) + pkts, err := e.Encode(ca.frame, 0) require.NoError(t, err) require.Equal(t, ca.pkts, pkts) })