From 653d10fb75b98dda13e835e0dbcc4caef34a5db1 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Fri, 16 Jan 2026 14:03:13 +0100 Subject: [PATCH] use the same filtering process for every codec (#5324) this allows to apply features that were previously implemented for single codecs (like RTP packet resizing), to any codec, and simplifies future development. --- internal/codecprocessor/ac3.go | 100 -- internal/codecprocessor/av1.go | 137 -- internal/codecprocessor/av1_test.go | 32 - internal/codecprocessor/g711.go | 98 -- internal/codecprocessor/g711_test.go | 70 - internal/codecprocessor/generic.go | 47 - internal/codecprocessor/generic_test.go | 53 - internal/codecprocessor/h264.go | 311 ----- internal/codecprocessor/h264_test.go | 307 ----- internal/codecprocessor/h265.go | 343 ----- internal/codecprocessor/h265_test.go | 295 ---- internal/codecprocessor/klv.go | 96 -- internal/codecprocessor/klv_test.go | 80 -- internal/codecprocessor/lpcm.go | 98 -- internal/codecprocessor/lpcm_test.go | 39 - internal/codecprocessor/mjpeg.go | 101 -- internal/codecprocessor/mpeg1_audio.go | 100 -- internal/codecprocessor/mpeg1_video.go | 110 -- internal/codecprocessor/mpeg4_audio.go | 103 -- internal/codecprocessor/mpeg4_audio_latm.go | 101 -- internal/codecprocessor/mpeg4_video.go | 159 --- internal/codecprocessor/mpeg4_video_test.go | 67 - internal/codecprocessor/opus.go | 105 -- internal/codecprocessor/opus_test.go | 139 -- internal/codecprocessor/processor.go | 185 --- internal/codecprocessor/processor_test.go | 105 -- .../FuzzRTPH264ExtractSPSPPS/048b606517c23baf | 2 - .../FuzzRTPH264ExtractSPSPPS/32e7782636603e29 | 2 - .../FuzzRTPH264ExtractSPSPPS/caf81e9797b19c76 | 2 - .../FuzzRTPH264ExtractSPSPPS/f428976a5b2917c0 | 2 - .../FuzzRTPH265ExtractParams/353ba911ad2dc191 | 2 - .../FuzzRTPH265ExtractParams/3c3a72c00adac0b3 | 2 - .../FuzzRTPH265ExtractParams/582528ddfad69eb5 | 2 - .../FuzzRTPH265ExtractParams/c4389a565e828050 | 2 - internal/codecprocessor/vp8.go | 101 -- internal/codecprocessor/vp9.go | 101 -- internal/core/path.go | 18 +- internal/defs/path.go | 22 +- internal/protocols/hls/to_stream_test.go | 20 +- internal/protocols/rtmp/from_stream_test.go | 56 +- internal/protocols/rtsp/to_stream.go | 7 +- internal/protocols/webrtc/from_stream_test.go | 62 +- internal/protocols/webrtc/to_stream.go | 7 +- internal/recorder/format_fmp4.go | 67 +- internal/recorder/recorder_test.go | 70 +- internal/servers/hls/server_test.go | 31 +- internal/servers/rtmp/conn.go | 8 +- internal/servers/rtmp/server_test.go | 20 +- internal/servers/rtsp/server_test.go | 30 +- internal/servers/rtsp/session.go | 10 +- internal/servers/srt/conn.go | 10 +- internal/servers/srt/server_test.go | 20 +- internal/servers/webrtc/server_test.go | 26 +- internal/servers/webrtc/session.go | 10 +- internal/staticsources/hls/source.go | 5 +- internal/staticsources/mpegts/source.go | 6 +- internal/staticsources/rpicamera/source.go | 27 +- internal/staticsources/rtmp/source.go | 6 +- internal/staticsources/rtp/source.go | 12 +- internal/staticsources/rtsp/source.go | 6 +- internal/staticsources/srt/source.go | 6 +- internal/staticsources/webrtc/source.go | 6 +- internal/stream/format_updater.go | 113 ++ internal/stream/rtp_decoder.go | 366 +++++ internal/stream/rtp_encoder.go | 382 ++++++ internal/stream/stream.go | 66 +- internal/stream/stream_format.go | 188 ++- internal/stream/stream_media.go | 34 +- internal/stream/stream_test.go | 1188 ++++++++++++++++- internal/stream/unit_remuxer.go | 181 +++ internal/test/static_source_parent.go | 11 +- 71 files changed, 2765 insertions(+), 3931 deletions(-) delete mode 100644 internal/codecprocessor/ac3.go delete mode 100644 internal/codecprocessor/av1.go delete mode 100644 internal/codecprocessor/av1_test.go delete mode 100644 internal/codecprocessor/g711.go delete mode 100644 internal/codecprocessor/g711_test.go delete mode 100644 internal/codecprocessor/generic.go delete mode 100644 internal/codecprocessor/generic_test.go delete mode 100644 internal/codecprocessor/h264.go delete mode 100644 internal/codecprocessor/h264_test.go delete mode 100644 internal/codecprocessor/h265.go delete mode 100644 internal/codecprocessor/h265_test.go delete mode 100644 internal/codecprocessor/klv.go delete mode 100644 internal/codecprocessor/klv_test.go delete mode 100644 internal/codecprocessor/lpcm.go delete mode 100644 internal/codecprocessor/lpcm_test.go delete mode 100644 internal/codecprocessor/mjpeg.go delete mode 100644 internal/codecprocessor/mpeg1_audio.go delete mode 100644 internal/codecprocessor/mpeg1_video.go delete mode 100644 internal/codecprocessor/mpeg4_audio.go delete mode 100644 internal/codecprocessor/mpeg4_audio_latm.go delete mode 100644 internal/codecprocessor/mpeg4_video.go delete mode 100644 internal/codecprocessor/mpeg4_video_test.go delete mode 100644 internal/codecprocessor/opus.go delete mode 100644 internal/codecprocessor/opus_test.go delete mode 100644 internal/codecprocessor/processor.go delete mode 100644 internal/codecprocessor/processor_test.go delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/048b606517c23baf delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/32e7782636603e29 delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/caf81e9797b19c76 delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/f428976a5b2917c0 delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/353ba911ad2dc191 delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/3c3a72c00adac0b3 delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/582528ddfad69eb5 delete mode 100644 internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/c4389a565e828050 delete mode 100644 internal/codecprocessor/vp8.go delete mode 100644 internal/codecprocessor/vp9.go create mode 100644 internal/stream/format_updater.go create mode 100644 internal/stream/rtp_decoder.go create mode 100644 internal/stream/rtp_encoder.go create mode 100644 internal/stream/unit_remuxer.go diff --git a/internal/codecprocessor/ac3.go b/internal/codecprocessor/ac3.go deleted file mode 100644 index aec0f580..00000000 --- a/internal/codecprocessor/ac3.go +++ /dev/null @@ -1,100 +0,0 @@ -package codecprocessor - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type ac3 struct { - RTPMaxPayloadSize int - Format *format.AC3 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpac3.Encoder - decoder *rtpac3.Decoder - randomStart uint32 -} - -func (t *ac3) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *ac3) createEncoder() error { - t.encoder = &rtpac3.Encoder{ - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *ac3) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadAC3)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *ac3) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frames, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpac3.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpac3.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadAC3(frames) - } - - return nil -} diff --git a/internal/codecprocessor/av1.go b/internal/codecprocessor/av1.go deleted file mode 100644 index f9450555..00000000 --- a/internal/codecprocessor/av1.go +++ /dev/null @@ -1,137 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1" - mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type av1 struct { - RTPMaxPayloadSize int - Format *format.AV1 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpav1.Encoder - decoder *rtpav1.Decoder - randomStart uint32 -} - -func (t *av1) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *av1) createEncoder() error { - t.encoder = &rtpav1.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *av1) remuxTemporalUnit(tu unit.PayloadAV1) unit.PayloadAV1 { - n := 0 - - for _, obu := range tu { - typ := mcav1.OBUType((obu[0] >> 3) & 0b1111) - - if typ == mcav1.OBUTypeTemporalDelimiter { - continue - } - n++ - } - - if n == 0 { - return nil - } - - filteredTU := make([][]byte, n) - i := 0 - - for _, obu := range tu { - typ := mcav1.OBUType((obu[0] >> 3) & 0b1111) - - if typ == mcav1.OBUTypeTemporalDelimiter { - continue - } - - filteredTU[i] = obu - i++ - } - - return filteredTU -} - -func (t *av1) ProcessUnit(u *unit.Unit) error { //nolint:dupl - u.Payload = t.remuxTemporalUnit(u.Payload.(unit.PayloadAV1)) - - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadAV1)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *av1) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - tu, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpav1.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpav1.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = t.remuxTemporalUnit(tu) - } - - return nil -} diff --git a/internal/codecprocessor/av1_test.go b/internal/codecprocessor/av1_test.go deleted file mode 100644 index e3775dab..00000000 --- a/internal/codecprocessor/av1_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - mcav1 "github.com/bluenviron/mediacommon/v2/pkg/codecs/av1" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/stretchr/testify/require" -) - -func TestAV1RemoveTUD(t *testing.T) { - forma := &format.AV1{} - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - u := &unit.Unit{ - PTS: 30000, - Payload: unit.PayloadAV1{ - {byte(mcav1.OBUTypeTemporalDelimiter) << 3}, - {5}, - }, - } - - err = p.ProcessUnit(u) - require.NoError(t, err) - - require.Equal(t, unit.PayloadAV1{ - {5}, - }, u.Payload) -} diff --git a/internal/codecprocessor/g711.go b/internal/codecprocessor/g711.go deleted file mode 100644 index 3787ef79..00000000 --- a/internal/codecprocessor/g711.go +++ /dev/null @@ -1,98 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type g711 struct { - RTPMaxPayloadSize int - Format *format.G711 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtplpcm.Encoder - decoder *rtplpcm.Decoder - randomStart uint32 -} - -func (t *g711) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *g711) createEncoder() error { - t.encoder = &rtplpcm.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadType(), - BitDepth: 8, - ChannelCount: t.Format.ChannelCount, - } - return t.encoder.Init() -} - -func (t *g711) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadG711)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *g711) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - samples, err := t.decoder.Decode(pkt) - if err != nil { - return err - } - - u.Payload = unit.PayloadG711(samples) - } - - return nil -} diff --git a/internal/codecprocessor/g711_test.go b/internal/codecprocessor/g711_test.go deleted file mode 100644 index d08f2b8f..00000000 --- a/internal/codecprocessor/g711_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" -) - -func TestG711ProcessUnit(t *testing.T) { - t.Run("alaw", func(t *testing.T) { - forma := &format.G711{ - PayloadTyp: 8, - MULaw: false, - SampleRate: 8000, - ChannelCount: 1, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - unit := &unit.Unit{ - Payload: unit.PayloadG711{1, 2, 3, 4}, - } - - err = p.ProcessUnit(unit) - require.NoError(t, err) - require.Equal(t, []*rtp.Packet{{ - Header: rtp.Header{ - Version: 2, - PayloadType: 8, - SequenceNumber: unit.RTPPackets[0].SequenceNumber, - Timestamp: unit.RTPPackets[0].Timestamp, - SSRC: unit.RTPPackets[0].SSRC, - }, - Payload: []byte{1, 2, 3, 4}, - }}, unit.RTPPackets) - }) - - t.Run("mulaw", func(t *testing.T) { - forma := &format.G711{ - PayloadTyp: 0, - MULaw: true, - SampleRate: 8000, - ChannelCount: 1, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - unit := &unit.Unit{ - Payload: unit.PayloadG711{1, 2, 3, 4}, - } - - err = p.ProcessUnit(unit) - require.NoError(t, err) - require.Equal(t, []*rtp.Packet{{ - Header: rtp.Header{ - Version: 2, - PayloadType: 0, - SequenceNumber: unit.RTPPackets[0].SequenceNumber, - Timestamp: unit.RTPPackets[0].Timestamp, - SSRC: unit.RTPPackets[0].SSRC, - }, - Payload: []byte{1, 2, 3, 4}, - }}, unit.RTPPackets) - }) -} diff --git a/internal/codecprocessor/generic.go b/internal/codecprocessor/generic.go deleted file mode 100644 index 8145e64f..00000000 --- a/internal/codecprocessor/generic.go +++ /dev/null @@ -1,47 +0,0 @@ -package codecprocessor - -import ( - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type generic struct { - RTPMaxPayloadSize int - Format format.Format - GenerateRTPPackets bool - Parent logger.Writer -} - -func (t *generic) initialize() error { - if t.GenerateRTPPackets { - return fmt.Errorf("we don't know how to generate RTP packets of format %T", t.Format) - } - - return nil -} - -func (t *generic) ProcessUnit(_ *unit.Unit) error { - return fmt.Errorf("using a generic unit without RTP is not supported") -} - -func (t *generic) ProcessRTPPacket( - u *unit.Unit, - _ bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - return nil -} diff --git a/internal/codecprocessor/generic_test.go b/internal/codecprocessor/generic_test.go deleted file mode 100644 index 990fab2b..00000000 --- a/internal/codecprocessor/generic_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" -) - -func TestGenericProcessRTPPacket(t *testing.T) { - forma := &format.Generic{ - PayloadTyp: 96, - RTPMa: "private/90000", - } - err := forma.Init() - require.NoError(t, err) - - p, err := New(1450, forma, false, nil) - require.NoError(t, err) - - pkt := &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 123, - Timestamp: 45343, - SSRC: 563423, - Padding: true, - }, - Payload: []byte{1, 2, 3, 4}, - PaddingSize: 20, - } - - u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - // check that padding has been removed - require.Equal(t, &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 123, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: []byte{1, 2, 3, 4}, - }, pkt) -} diff --git a/internal/codecprocessor/h264.go b/internal/codecprocessor/h264.go deleted file mode 100644 index 98350f98..00000000 --- a/internal/codecprocessor/h264.go +++ /dev/null @@ -1,311 +0,0 @@ -package codecprocessor - -import ( - "bytes" - "errors" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtph264" - mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -// H264-related parameters -var ( - H264DefaultSPS = []byte{ // 1920x1080 baseline - 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, - 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, - 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, - } - - H264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08} -) - -// extract SPS and PPS without decoding RTP packets -func rtpH264ExtractParams(payload []byte) ([]byte, []byte) { - if len(payload) < 1 { - return nil, nil - } - - typ := mch264.NALUType(payload[0] & 0x1F) - - switch typ { - case mch264.NALUTypeSPS: - return payload, nil - - case mch264.NALUTypePPS: - return nil, payload - - case mch264.NALUTypeSTAPA: - payload = payload[1:] - var sps []byte - var pps []byte - - for len(payload) > 0 { - if len(payload) < 2 { - break - } - - size := uint16(payload[0])<<8 | uint16(payload[1]) - payload = payload[2:] - - if size == 0 { - break - } - - if int(size) > len(payload) { - return nil, nil - } - - nalu := payload[:size] - payload = payload[size:] - - typ = mch264.NALUType(nalu[0] & 0x1F) - - switch typ { - case mch264.NALUTypeSPS: - sps = nalu - - case mch264.NALUTypePPS: - pps = nalu - } - } - - return sps, pps - - default: - return nil, nil - } -} - -type h264 struct { - RTPMaxPayloadSize int - Format *format.H264 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtph264.Encoder - decoder *rtph264.Decoder - randomStart uint32 -} - -func (t *h264) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder(nil, nil) - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *h264) createEncoder( - ssrc *uint32, - initialSequenceNumber *uint16, -) error { - t.encoder = &rtph264.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - SSRC: ssrc, - InitialSequenceNumber: initialSequenceNumber, - PacketizationMode: t.Format.PacketizationMode, - } - return t.encoder.Init() -} - -func (t *h264) updateTrackParametersFromRTPPacket(payload []byte) { - sps, pps := rtpH264ExtractParams(payload) - - if (sps != nil && !bytes.Equal(sps, t.Format.SPS)) || - (pps != nil && !bytes.Equal(pps, t.Format.PPS)) { - if sps == nil { - sps = t.Format.SPS - } - if pps == nil { - pps = t.Format.PPS - } - t.Format.SafeSetParams(sps, pps) - } -} - -func (t *h264) updateTrackParametersFromAU(au unit.PayloadH264) { - sps := t.Format.SPS - pps := t.Format.PPS - update := false - - for _, nalu := range au { - typ := mch264.NALUType(nalu[0] & 0x1F) - - switch typ { - case mch264.NALUTypeSPS: - if !bytes.Equal(nalu, sps) { - sps = nalu - update = true - } - - case mch264.NALUTypePPS: - if !bytes.Equal(nalu, pps) { - pps = nalu - update = true - } - } - } - - if update { - t.Format.SafeSetParams(sps, pps) - } -} - -func (t *h264) remuxAccessUnit(au unit.PayloadH264) unit.PayloadH264 { - isKeyFrame := false - n := 0 - - for _, nalu := range au { - typ := mch264.NALUType(nalu[0] & 0x1F) - - switch typ { - case mch264.NALUTypeSPS, mch264.NALUTypePPS: - continue - - case mch264.NALUTypeAccessUnitDelimiter: - continue - - case mch264.NALUTypeIDR: - if !isKeyFrame { - isKeyFrame = true - - // prepend parameters - if t.Format.SPS != nil && t.Format.PPS != nil { - n += 2 - } - } - } - n++ - } - - if n == 0 { - return nil - } - - filteredAU := make([][]byte, n) - i := 0 - - if isKeyFrame && t.Format.SPS != nil && t.Format.PPS != nil { - filteredAU[0] = t.Format.SPS - filteredAU[1] = t.Format.PPS - i = 2 - } - - for _, nalu := range au { - typ := mch264.NALUType(nalu[0] & 0x1F) - - switch typ { - case mch264.NALUTypeSPS, mch264.NALUTypePPS: - continue - - case mch264.NALUTypeAccessUnitDelimiter: - continue - } - - filteredAU[i] = nalu - i++ - } - - return filteredAU -} - -func (t *h264) ProcessUnit(u *unit.Unit) error { - t.updateTrackParametersFromAU(u.Payload.(unit.PayloadH264)) - u.Payload = t.remuxAccessUnit(u.Payload.(unit.PayloadH264)) - - if !u.NilPayload() { - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH264)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - } - - return nil -} - -func (t *h264) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - t.updateTrackParametersFromRTPPacket(pkt.Payload) - - if t.encoder == nil { - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - // RTP packets exceed maximum size: start re-encoding them - if len(pkt.Payload) > t.RTPMaxPayloadSize { - t.Parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones") - - v1 := pkt.SSRC - v2 := pkt.SequenceNumber - err := t.createEncoder(&v1, &v2) - if err != nil { - return err - } - } - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - au, err := t.decoder.Decode(pkt) - - if t.encoder != nil { - u.RTPPackets = nil - } - - if err != nil { - if errors.Is(err, rtph264.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtph264.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = t.remuxAccessUnit(au) - } - - // encode into RTP - if t.encoder != nil && !u.NilPayload() { - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH264)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, newPKT := range u.RTPPackets { - newPKT.Timestamp = pkt.Timestamp - } - } - - return nil -} diff --git a/internal/codecprocessor/h264_test.go b/internal/codecprocessor/h264_test.go deleted file mode 100644 index fb20b5d5..00000000 --- a/internal/codecprocessor/h264_test.go +++ /dev/null @@ -1,307 +0,0 @@ -package codecprocessor - -import ( - "bytes" - "fmt" - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type testLogger struct { - cb func(level logger.Level, format string, args ...any) -} - -func (l *testLogger) Log(level logger.Level, format string, args ...any) { - l.cb(level, format, args...) -} - -// Logger returns a dummy logger. -func Logger(cb func(logger.Level, string, ...any)) logger.Writer { - return &testLogger{cb: cb} -} - -func TestH264RemoveAUD(t *testing.T) { - forma := &format.H264{} - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - u := &unit.Unit{ - PTS: 30000, - Payload: unit.PayloadH264{ - {9, 24}, // AUD - {5, 1}, // IDR - }, - } - - err = p.ProcessUnit(u) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH264{ - {5, 1}, // IDR - }, u.Payload) -} - -func TestH264AddParams(t *testing.T) { - forma := &format.H264{} - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - u1 := &unit.Unit{ - PTS: 30000, - Payload: unit.PayloadH264{ - {7, 4, 5, 6}, // SPS - {8, 1}, // PPS - {5, 1}, // IDR - }, - } - - err = p.ProcessUnit(u1) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH264{ - {7, 4, 5, 6}, // SPS - {8, 1}, // PPS - {5, 1}, // IDR - }, u1.Payload) - - u2 := &unit.Unit{ - PTS: 30000 * 2, - Payload: unit.PayloadH264{ - {5, 2}, // IDR - }, - } - - err = p.ProcessUnit(u2) - require.NoError(t, err) - - // test that params have been added to the SDP - require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS) - require.Equal(t, []byte{8, 1}, forma.PPS) - - // test that params have been added to the frame - require.Equal(t, unit.PayloadH264{ - {7, 4, 5, 6}, // SPS - {8, 1}, // PPS - {5, 2}, // IDR - }, u2.Payload) - - // test that timestamp has increased - require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp) -} - -func TestH264ProcessEmptyUnit(t *testing.T) { - forma := &format.H264{ - PayloadTyp: 96, - PacketizationMode: 1, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - unit := &unit.Unit{ - Payload: unit.PayloadH264{ - {0x07, 0x01, 0x02, 0x03}, // SPS - {0x08, 0x01, 0x02}, // PPS - }, - } - - err = p.ProcessUnit(unit) - require.NoError(t, err) - - // if all NALUs have been removed, no RTP packets shall be generated. - require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets) -} - -func TestH264RTPExtractParams(t *testing.T) { - for _, ca := range []string{"standard", "aggregated"} { - t.Run(ca, func(t *testing.T) { - forma := &format.H264{ - PayloadTyp: 96, - PacketizationMode: 1, - } - - p, err := New(1450, forma, false, nil) - require.NoError(t, err) - - enc, err := forma.CreateEncoder() - require.NoError(t, err) - - pkts, err := enc.Encode([][]byte{{byte(mch264.NALUTypeIDR)}}) - require.NoError(t, err) - - u := &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, true) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH264{ - {byte(mch264.NALUTypeIDR)}, - }, u.Payload) - - if ca == "standard" { - pkts, err = enc.Encode([][]byte{{7, 4, 5, 6}}) // SPS - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - pkts, err = enc.Encode([][]byte{{8, 1}}) // PPS - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - } else { - pkts, err = enc.Encode([][]byte{ - {7, 4, 5, 6}, // SPS - {8, 1}, // PPS - }) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - } - - require.Equal(t, []byte{7, 4, 5, 6}, forma.SPS) - require.Equal(t, []byte{8, 1}, forma.PPS) - - pkts, err = enc.Encode([][]byte{{byte(mch264.NALUTypeIDR)}}) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, true) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH264{ - {0x07, 4, 5, 6}, - {0x08, 1}, - {byte(mch264.NALUTypeIDR)}, - }, u.Payload) - }) - } -} - -func TestH264RTPOversized(t *testing.T) { - forma := &format.H264{ - PayloadTyp: 96, - SPS: []byte{0x01, 0x02, 0x03, 0x04}, - PPS: []byte{0x01, 0x02, 0x03, 0x04}, - PacketizationMode: 1, - } - - logged := false - - p, err := New(1460, forma, false, - Logger(func(_ logger.Level, s string, i ...any) { - require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...)) - logged = true - })) - require.NoError(t, err) - - var out []*rtp.Packet //nolint:prealloc - - for _, pkt := range []*rtp.Packet{ - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 123, - Timestamp: 45343, - SSRC: 563423, - Padding: true, - }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }, - { - Header: rtp.Header{ - Version: 2, - Marker: false, - PayloadType: 96, - SequenceNumber: 124, - Timestamp: 45343, - SSRC: 563423, - Padding: true, - }, - Payload: append([]byte{0x1c, 0b10000000}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 2000/4)...), - }, - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 125, - Timestamp: 45343, - SSRC: 563423, - Padding: true, - }, - Payload: []byte{0x1c, 0b01000000, 0x01, 0x02, 0x03, 0x04}, - }, - } { - u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - out = append(out, u.RTPPackets...) - } - - require.Equal(t, []*rtp.Packet{ - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 123, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }, - { - Header: rtp.Header{ - Version: 2, - Marker: false, - PayloadType: 96, - SequenceNumber: 124, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: append( - append([]byte{0x1c, 0x80}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 364)...), - []byte{0x01, 0x02}..., - ), - }, - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 125, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: append( - []byte{0x1c, 0x40, 0x03, 0x04}, - bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 136)..., - ), - }, - }, out) - - require.True(t, logged) -} - -func FuzzRTPH264ExtractParams(f *testing.F) { - f.Fuzz(func(_ *testing.T, b []byte) { - rtpH264ExtractParams(b) - }) -} diff --git a/internal/codecprocessor/h265.go b/internal/codecprocessor/h265.go deleted file mode 100644 index 7583ba8d..00000000 --- a/internal/codecprocessor/h265.go +++ /dev/null @@ -1,343 +0,0 @@ -package codecprocessor - -import ( - "bytes" - "errors" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtph265" - mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -// H265-related parameters -var ( - H265DefaultVPS = []byte{ - 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20, - 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24, - } - - H265DefaultSPS = []byte{ - 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, - 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, - 0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d, - 0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88, - 0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9, - 0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc, - 0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a, - 0x02, 0x02, 0x02, 0x01, - } - - H265DefaultPPS = []byte{ - 0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40, - } -) - -// extract VPS, SPS and PPS without decoding RTP packets -func rtpH265ExtractParams(payload []byte) ([]byte, []byte, []byte) { - if len(payload) < 2 { - return nil, nil, nil - } - - typ := mch265.NALUType((payload[0] >> 1) & 0b111111) - - switch typ { - case mch265.NALUType_VPS_NUT: - return payload, nil, nil - - case mch265.NALUType_SPS_NUT: - return nil, payload, nil - - case mch265.NALUType_PPS_NUT: - return nil, nil, payload - - case mch265.NALUType_AggregationUnit: - payload = payload[2:] - var vps []byte - var sps []byte - var pps []byte - - for len(payload) > 0 { - if len(payload) < 2 { - break - } - - size := uint16(payload[0])<<8 | uint16(payload[1]) - payload = payload[2:] - - if size == 0 { - break - } - - if int(size) > len(payload) { - return nil, nil, nil - } - - nalu := payload[:size] - payload = payload[size:] - - typ = mch265.NALUType((nalu[0] >> 1) & 0b111111) - - switch typ { - case mch265.NALUType_VPS_NUT: - vps = nalu - - case mch265.NALUType_SPS_NUT: - sps = nalu - - case mch265.NALUType_PPS_NUT: - pps = nalu - } - } - - return vps, sps, pps - - default: - return nil, nil, nil - } -} - -type h265 struct { - RTPMaxPayloadSize int - Format *format.H265 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtph265.Encoder - decoder *rtph265.Decoder - randomStart uint32 -} - -func (t *h265) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder(nil, nil) - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *h265) createEncoder( - ssrc *uint32, - initialSequenceNumber *uint16, -) error { - t.encoder = &rtph265.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - SSRC: ssrc, - InitialSequenceNumber: initialSequenceNumber, - MaxDONDiff: t.Format.MaxDONDiff, - } - return t.encoder.Init() -} - -func (t *h265) updateTrackParametersFromRTPPacket(payload []byte) { - vps, sps, pps := rtpH265ExtractParams(payload) - - if (vps != nil && !bytes.Equal(vps, t.Format.VPS)) || - (sps != nil && !bytes.Equal(sps, t.Format.SPS)) || - (pps != nil && !bytes.Equal(pps, t.Format.PPS)) { - if vps == nil { - vps = t.Format.VPS - } - if sps == nil { - sps = t.Format.SPS - } - if pps == nil { - pps = t.Format.PPS - } - t.Format.SafeSetParams(vps, sps, pps) - } -} - -func (t *h265) updateTrackParametersFromAU(au unit.PayloadH265) { - vps := t.Format.VPS - sps := t.Format.SPS - pps := t.Format.PPS - update := false - - for _, nalu := range au { - typ := mch265.NALUType((nalu[0] >> 1) & 0b111111) - - switch typ { - case mch265.NALUType_VPS_NUT: - if !bytes.Equal(nalu, t.Format.VPS) { - vps = nalu - update = true - } - - case mch265.NALUType_SPS_NUT: - if !bytes.Equal(nalu, t.Format.SPS) { - sps = nalu - update = true - } - - case mch265.NALUType_PPS_NUT: - if !bytes.Equal(nalu, t.Format.PPS) { - pps = nalu - update = true - } - } - } - - if update { - t.Format.SafeSetParams(vps, sps, pps) - } -} - -func (t *h265) remuxAccessUnit(au unit.PayloadH265) unit.PayloadH265 { - isKeyFrame := false - n := 0 - - for _, nalu := range au { - typ := mch265.NALUType((nalu[0] >> 1) & 0b111111) - - switch typ { - case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT: - continue - - case mch265.NALUType_AUD_NUT: - continue - - case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT: - if !isKeyFrame { - isKeyFrame = true - - // prepend parameters - if t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil { - n += 3 - } - } - } - n++ - } - - if n == 0 { - return nil - } - - filteredAU := make([][]byte, n) - i := 0 - - if isKeyFrame && t.Format.VPS != nil && t.Format.SPS != nil && t.Format.PPS != nil { - filteredAU[0] = t.Format.VPS - filteredAU[1] = t.Format.SPS - filteredAU[2] = t.Format.PPS - i = 3 - } - - for _, nalu := range au { - typ := mch265.NALUType((nalu[0] >> 1) & 0b111111) - - switch typ { - case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT: - continue - - case mch265.NALUType_AUD_NUT: - continue - } - - filteredAU[i] = nalu - i++ - } - - return filteredAU -} - -func (t *h265) ProcessUnit(u *unit.Unit) error { //nolint:dupl - t.updateTrackParametersFromAU(u.Payload.(unit.PayloadH265)) - u.Payload = t.remuxAccessUnit(u.Payload.(unit.PayloadH265)) - - if !u.NilPayload() { - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH265)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - } - - return nil -} - -func (t *h265) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - t.updateTrackParametersFromRTPPacket(pkt.Payload) - - if t.encoder == nil { - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - // RTP packets exceed maximum size: start re-encoding them - if len(pkt.Payload) > t.RTPMaxPayloadSize { - t.Parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones") - - v1 := pkt.SSRC - v2 := pkt.SequenceNumber - err := t.createEncoder(&v1, &v2) - if err != nil { - return err - } - } - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil || t.encoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - au, err := t.decoder.Decode(pkt) - - if t.encoder != nil { - u.RTPPackets = nil - } - - if err != nil { - if errors.Is(err, rtph265.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtph265.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = t.remuxAccessUnit(au) - } - - // encode into RTP - if t.encoder != nil && !u.NilPayload() { - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadH265)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, newPKT := range u.RTPPackets { - newPKT.Timestamp = pkt.Timestamp - } - } - - return nil -} diff --git a/internal/codecprocessor/h265_test.go b/internal/codecprocessor/h265_test.go deleted file mode 100644 index 7b094d74..00000000 --- a/internal/codecprocessor/h265_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package codecprocessor - -import ( - "bytes" - "fmt" - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -func TestH265RemoveAUD(t *testing.T) { - forma := &format.H265{} - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - u := &unit.Unit{ - PTS: 30000, - Payload: unit.PayloadH265{ - {byte(mch265.NALUType_AUD_NUT) << 1, 0}, - {byte(mch265.NALUType_CRA_NUT) << 1, 0}, - }, - } - - err = p.ProcessUnit(u) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH265{ - {byte(mch265.NALUType_CRA_NUT) << 1, 0}, - }, u.Payload) -} - -func TestH265AddParams(t *testing.T) { - forma := &format.H265{} - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - u1 := &unit.Unit{ - PTS: 30000, - Payload: unit.PayloadH265{ - {byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, - {byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, - {byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, - {byte(mch265.NALUType_CRA_NUT) << 1, 0}, - }, - } - - err = p.ProcessUnit(u1) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH265{ - {byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, - {byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, - {byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, - {byte(mch265.NALUType_CRA_NUT) << 1, 0}, - }, u1.Payload) - - u2 := &unit.Unit{ - PTS: 30000 * 2, - Payload: unit.PayloadH265{ - {byte(mch265.NALUType_CRA_NUT) << 1, 1}, - }, - } - - err = p.ProcessUnit(u2) - require.NoError(t, err) - - // test that params have been added to the SDP - require.Equal(t, []byte{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, forma.VPS) - require.Equal(t, []byte{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS) - require.Equal(t, []byte{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS) - - // test that params have been added to the frame - require.Equal(t, unit.PayloadH265{ - {byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, - {byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, - {byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, - {byte(mch265.NALUType_CRA_NUT) << 1, 1}, - }, u2.Payload) - - // test that timestamp has increased - require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp) -} - -func TestH265ProcessEmptyUnit(t *testing.T) { - forma := &format.H265{ - PayloadTyp: 96, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - unit := &unit.Unit{ - Payload: unit.PayloadH265{ - {byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12}, // VPS - {byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15}, // SPS - {byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18}, // PPS - }, - } - - err = p.ProcessUnit(unit) - require.NoError(t, err) - - // if all NALUs have been removed, no RTP packets shall be generated. - require.Equal(t, []*rtp.Packet(nil), unit.RTPPackets) -} - -func TestH265RTPExtractParams(t *testing.T) { - for _, ca := range []string{"standard", "aggregated"} { - t.Run(ca, func(t *testing.T) { - forma := &format.H265{ - PayloadTyp: 96, - } - - p, err := New(1450, forma, false, nil) - require.NoError(t, err) - - enc, err := forma.CreateEncoder() - require.NoError(t, err) - - pkts, err := enc.Encode([][]byte{{byte(mch265.NALUType_CRA_NUT) << 1, 0}}) - require.NoError(t, err) - - u := &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, true) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH265{ - {byte(mch265.NALUType_CRA_NUT) << 1, 0}, - }, u.Payload) - - if ca == "standard" { - pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}}) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}}) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}}) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - } else { - pkts, err = enc.Encode([][]byte{ - {byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, - {byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, - {byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, - }) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - } - - require.Equal(t, []byte{byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, forma.VPS) - require.Equal(t, []byte{byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, forma.SPS) - require.Equal(t, []byte{byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, forma.PPS) - - pkts, err = enc.Encode([][]byte{{byte(mch265.NALUType_CRA_NUT) << 1, 0}}) - require.NoError(t, err) - - u = &unit.Unit{RTPPackets: []*rtp.Packet{pkts[0]}} - err = p.ProcessRTPPacket(u, true) - require.NoError(t, err) - - require.Equal(t, unit.PayloadH265{ - {byte(mch265.NALUType_VPS_NUT) << 1, 1, 2, 3}, - {byte(mch265.NALUType_SPS_NUT) << 1, 4, 5, 6}, - {byte(mch265.NALUType_PPS_NUT) << 1, 7, 8, 9}, - {byte(mch265.NALUType_CRA_NUT) << 1, 0}, - }, u.Payload) - }) - } -} - -func TestH265RTPOversized(t *testing.T) { - forma := &format.H265{ - PayloadTyp: 96, - VPS: []byte{byte(mch265.NALUType_VPS_NUT) << 1, 10, 11, 12}, - SPS: []byte{byte(mch265.NALUType_SPS_NUT) << 1, 13, 14, 15}, - PPS: []byte{byte(mch265.NALUType_PPS_NUT) << 1, 16, 17, 18}, - } - - logged := false - - p, err := New(1460, forma, false, - Logger(func(_ logger.Level, s string, i ...any) { - require.Equal(t, "RTP packets are too big, remuxing them into smaller ones", fmt.Sprintf(s, i...)) - logged = true - })) - require.NoError(t, err) - - var out []*rtp.Packet //nolint:prealloc - - for _, pkt := range []*rtp.Packet{ - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 123, - Timestamp: 45343, - SSRC: 563423, - Padding: true, - }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }, - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 124, - Timestamp: 45343, - SSRC: 563423, - Padding: true, - }, - Payload: bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 2000/4), - }, - } { - u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - out = append(out, u.RTPPackets...) - } - - require.Equal(t, []*rtp.Packet{ - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 123, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }, - { - Header: rtp.Header{ - Version: 2, - Marker: false, - PayloadType: 96, - SequenceNumber: 124, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: append( - append([]byte{0x63, 0x02, 0x80, 0x03, 0x04}, bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 363)...), - []byte{0x01, 0x02, 0x03}..., - ), - }, - { - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 125, - Timestamp: 45343, - SSRC: 563423, - }, - Payload: append( - []byte{0x63, 0x02, 0x40, 0x04}, - bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x04}, 135)..., - ), - }, - }, out) - - require.True(t, logged) -} - -func FuzzRTPH265ExtractParams(f *testing.F) { - f.Fuzz(func(_ *testing.T, b []byte) { - rtpH265ExtractParams(b) - }) -} diff --git a/internal/codecprocessor/klv.go b/internal/codecprocessor/klv.go deleted file mode 100644 index c0428fe4..00000000 --- a/internal/codecprocessor/klv.go +++ /dev/null @@ -1,96 +0,0 @@ -package codecprocessor - -import ( - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type klv struct { - RTPMaxPayloadSize int - Format *format.KLV - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpklv.Encoder - decoder *rtpklv.Decoder - randomStart uint32 -} - -func (t *klv) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *klv) createEncoder() error { - t.encoder = &rtpklv.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *klv) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadKLV)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *klv) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - un, err := t.decoder.Decode(pkt) - if err != nil { - return err - } - - u.Payload = unit.PayloadKLV(un) - } - - return nil -} diff --git a/internal/codecprocessor/klv_test.go b/internal/codecprocessor/klv_test.go deleted file mode 100644 index f2d1fb45..00000000 --- a/internal/codecprocessor/klv_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" -) - -func TestKlvCreateEncoder(t *testing.T) { - forma := &format.KLV{ - PayloadTyp: 96, - } - p, err := New(1472, forma, false, nil) - require.NoError(t, err) - - klvProc := p.(*klv) - err = klvProc.createEncoder() - require.NoError(t, err) -} - -func TestKlvProcessUnit(t *testing.T) { - forma := &format.KLV{ - PayloadTyp: 96, - } - p, err := New(1472, forma, true, nil) - require.NoError(t, err) - - // create test Unit - when := int64(5000000000) // 5 seconds in nanoseconds - u := unit.Unit{ - RTPPackets: nil, - PTS: when, - Payload: unit.PayloadKLV{1, 2, 3, 4}, - } - uu := &u - - // process the unit - err = p.ProcessUnit(uu) - require.NoError(t, err) -} - -func TestKlvProcessRTPPacket(t *testing.T) { - forma := &format.KLV{ - PayloadTyp: 96, - } - p, err := New(1472, forma, false, nil) - require.NoError(t, err) - - pkt := &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 3446, - Timestamp: 175349, - SSRC: 563423, - Padding: true, - }, - Payload: []byte{1, 2, 3, 4}, - PaddingSize: 20, - } - u := &unit.Unit{RTPPackets: []*rtp.Packet{pkt}} - err = p.ProcessRTPPacket(u, false) - require.NoError(t, err) - - require.Equal(t, &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 96, - SequenceNumber: 3446, - Timestamp: 175349, - SSRC: 563423, - }, - Payload: []byte{1, 2, 3, 4}, - }, pkt) -} diff --git a/internal/codecprocessor/lpcm.go b/internal/codecprocessor/lpcm.go deleted file mode 100644 index 82ac7fac..00000000 --- a/internal/codecprocessor/lpcm.go +++ /dev/null @@ -1,98 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type lpcm struct { - RTPMaxPayloadSize int - Format *format.LPCM - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtplpcm.Encoder - decoder *rtplpcm.Decoder - randomStart uint32 -} - -func (t *lpcm) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *lpcm) createEncoder() error { - t.encoder = &rtplpcm.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - BitDepth: t.Format.BitDepth, - ChannelCount: t.Format.ChannelCount, - } - return t.encoder.Init() -} - -func (t *lpcm) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadLPCM)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *lpcm) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - samples, err := t.decoder.Decode(pkt) - if err != nil { - return err - } - - u.Payload = unit.PayloadLPCM(samples) - } - - return nil -} diff --git a/internal/codecprocessor/lpcm_test.go b/internal/codecprocessor/lpcm_test.go deleted file mode 100644 index 4a100823..00000000 --- a/internal/codecprocessor/lpcm_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" -) - -func TestLPCMProcessUnit(t *testing.T) { - forma := &format.LPCM{ - PayloadTyp: 96, - BitDepth: 16, - SampleRate: 44100, - ChannelCount: 2, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - unit := &unit.Unit{ - Payload: unit.PayloadLPCM{1, 2, 3, 4}, - } - - err = p.ProcessUnit(unit) - require.NoError(t, err) - require.Equal(t, []*rtp.Packet{{ - Header: rtp.Header{ - Version: 2, - PayloadType: 96, - SequenceNumber: unit.RTPPackets[0].SequenceNumber, - Timestamp: unit.RTPPackets[0].Timestamp, - SSRC: unit.RTPPackets[0].SSRC, - }, - Payload: []byte{1, 2, 3, 4}, - }}, unit.RTPPackets) -} diff --git a/internal/codecprocessor/mjpeg.go b/internal/codecprocessor/mjpeg.go deleted file mode 100644 index b9c2be95..00000000 --- a/internal/codecprocessor/mjpeg.go +++ /dev/null @@ -1,101 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type mjpeg struct { - RTPMaxPayloadSize int - Format *format.MJPEG - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpmjpeg.Encoder - decoder *rtpmjpeg.Decoder - randomStart uint32 -} - -func (t *mjpeg) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *mjpeg) createEncoder() error { - t.encoder = &rtpmjpeg.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - } - return t.encoder.Init() -} - -func (t *mjpeg) ProcessUnit(u *unit.Unit) error { //nolint:dupl - // encode into RTP - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMJPEG)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *mjpeg) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frame, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpmjpeg.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpmjpeg.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadMJPEG(frame) - } - - return nil -} diff --git a/internal/codecprocessor/mpeg1_audio.go b/internal/codecprocessor/mpeg1_audio.go deleted file mode 100644 index 2a19fae9..00000000 --- a/internal/codecprocessor/mpeg1_audio.go +++ /dev/null @@ -1,100 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type mpeg1Audio struct { - RTPMaxPayloadSize int - Format *format.MPEG1Audio - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpmpeg1audio.Encoder - decoder *rtpmpeg1audio.Decoder - randomStart uint32 -} - -func (t *mpeg1Audio) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *mpeg1Audio) createEncoder() error { - t.encoder = &rtpmpeg1audio.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - } - return t.encoder.Init() -} - -func (t *mpeg1Audio) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG1Audio)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *mpeg1Audio) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frames, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpmpeg1audio.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadMPEG1Audio(frames) - } - - return nil -} diff --git a/internal/codecprocessor/mpeg1_video.go b/internal/codecprocessor/mpeg1_video.go deleted file mode 100644 index 8923c379..00000000 --- a/internal/codecprocessor/mpeg1_video.go +++ /dev/null @@ -1,110 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -// MPEG-1 video related parameters -var ( - MPEG1VideoDefaultConfig = []byte{ - 0x00, 0x00, 0x01, 0xb3, 0x78, 0x04, 0x38, 0x35, - 0xff, 0xff, 0xe0, 0x18, 0x00, 0x00, 0x01, 0xb5, - 0x14, 0x4a, 0x00, 0x01, 0x00, 0x00, - } -) - -type mpeg1Video struct { - RTPMaxPayloadSize int - Format *format.MPEG1Video - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpmpeg1video.Encoder - decoder *rtpmpeg1video.Decoder - randomStart uint32 -} - -func (t *mpeg1Video) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *mpeg1Video) createEncoder() error { - t.encoder = &rtpmpeg1video.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - } - return t.encoder.Init() -} - -func (t *mpeg1Video) ProcessUnit(u *unit.Unit) error { //nolint:dupl - // encode into RTP - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG1Video)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *mpeg1Video) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frame, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpmpeg1video.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpmpeg1video.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadMPEG1Video(frame) - } - - return nil -} diff --git a/internal/codecprocessor/mpeg4_audio.go b/internal/codecprocessor/mpeg4_audio.go deleted file mode 100644 index b4d01816..00000000 --- a/internal/codecprocessor/mpeg4_audio.go +++ /dev/null @@ -1,103 +0,0 @@ -package codecprocessor - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type mpeg4Audio struct { - RTPMaxPayloadSize int - Format *format.MPEG4Audio - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpmpeg4audio.Encoder - decoder *rtpmpeg4audio.Decoder - randomStart uint32 -} - -func (t *mpeg4Audio) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *mpeg4Audio) createEncoder() error { - t.encoder = &rtpmpeg4audio.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - SizeLength: t.Format.SizeLength, - IndexLength: t.Format.IndexLength, - IndexDeltaLength: t.Format.IndexDeltaLength, - } - return t.encoder.Init() -} - -func (t *mpeg4Audio) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4Audio)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *mpeg4Audio) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - aus, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadMPEG4Audio(aus) - } - - return nil -} diff --git a/internal/codecprocessor/mpeg4_audio_latm.go b/internal/codecprocessor/mpeg4_audio_latm.go deleted file mode 100644 index 9a511955..00000000 --- a/internal/codecprocessor/mpeg4_audio_latm.go +++ /dev/null @@ -1,101 +0,0 @@ -package codecprocessor - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type mpeg4AudioLATM struct { - RTPMaxPayloadSize int - Format *format.MPEG4AudioLATM - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpfragmented.Encoder - decoder *rtpfragmented.Decoder - randomStart uint32 -} - -func (t *mpeg4AudioLATM) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *mpeg4AudioLATM) createEncoder() error { - t.encoder = &rtpfragmented.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *mpeg4AudioLATM) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4AudioLATM)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *mpeg4AudioLATM) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - el, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadMPEG4AudioLATM(el) - } - - return nil -} diff --git a/internal/codecprocessor/mpeg4_video.go b/internal/codecprocessor/mpeg4_video.go deleted file mode 100644 index 4cf7a9fc..00000000 --- a/internal/codecprocessor/mpeg4_video.go +++ /dev/null @@ -1,159 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "bytes" - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented" - "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -// MPEG-4 video related parameters -var ( - MPEG4VideoDefaultConfig = []byte{ - 0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01, - 0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x88, 0x00, - 0xf5, 0x3c, 0x04, 0x87, 0x14, 0x63, 0x00, 0x00, - 0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38, - 0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30, - } -) - -type mpeg4Video struct { - RTPMaxPayloadSize int - Format *format.MPEG4Video - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpfragmented.Encoder - decoder *rtpfragmented.Decoder - randomStart uint32 -} - -func (t *mpeg4Video) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *mpeg4Video) createEncoder() error { - t.encoder = &rtpfragmented.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *mpeg4Video) updateTrackParameters(frame unit.PayloadMPEG4Video) { - if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) { - end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) - if end < 0 { - return - } - conf := frame[:end+4] - - if !bytes.Equal(conf, t.Format.Config) { - t.Format.SafeSetParams(conf) - } - } -} - -func (t *mpeg4Video) remuxFrame(frame unit.PayloadMPEG4Video) unit.PayloadMPEG4Video { - // remove config - if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) { - end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) - if end >= 0 { - frame = frame[end+4:] - } - } - - // add config - if bytes.Contains(frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) { - f := make([]byte, len(t.Format.Config)+len(frame)) - n := copy(f, t.Format.Config) - copy(f[n:], frame) - frame = f - } - - if len(frame) == 0 { - return nil - } - - return frame -} - -func (t *mpeg4Video) ProcessUnit(u *unit.Unit) error { //nolint:dupl - t.updateTrackParameters(u.Payload.(unit.PayloadMPEG4Video)) - u.Payload = t.remuxFrame(u.Payload.(unit.PayloadMPEG4Video)) - - if !u.NilPayload() { - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadMPEG4Video)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - } - - return nil -} - -func (t *mpeg4Video) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - t.updateTrackParameters(pkt.Payload) - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frame, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = t.remuxFrame(frame) - } - - return nil -} diff --git a/internal/codecprocessor/mpeg4_video_test.go b/internal/codecprocessor/mpeg4_video_test.go deleted file mode 100644 index db66e398..00000000 --- a/internal/codecprocessor/mpeg4_video_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/stretchr/testify/require" -) - -func TestMPEG4VideoProcessUnit(t *testing.T) { - forma := &format.MPEG4Video{ - PayloadTyp: 96, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - u1 := &unit.Unit{ - PTS: 30000, - Payload: unit.PayloadMPEG4Video{ - 0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode), - 0, 0, 1, 0xFF, - 0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode), - 0, 0, 1, 0xF0, - }, - } - - err = p.ProcessUnit(u1) - require.NoError(t, err) - - require.Equal(t, unit.PayloadMPEG4Video{ - 0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode), - 0, 0, 1, 0xFF, - 0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode), - 0, 0, 1, 0xF0, - }, u1.Payload) - - u2 := &unit.Unit{ - PTS: 30000 * 2, - Payload: unit.PayloadMPEG4Video{ - 0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode), - 0, 0, 1, 0xF1, - }, - } - - err = p.ProcessUnit(u2) - require.NoError(t, err) - - // test that params have been added to the SDP - require.Equal(t, []byte{ - 0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode), - 0, 0, 1, 0xFF, - }, forma.Config) - - // test that params have been added to the frame - require.Equal(t, unit.PayloadMPEG4Video{ - 0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode), - 0, 0, 1, 0xFF, - 0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode), - 0, 0, 1, 0xF1, - }, u2.Payload) - - // test that timestamp has increased - require.Equal(t, u1.RTPPackets[0].Timestamp+30000, u2.RTPPackets[0].Timestamp) -} diff --git a/internal/codecprocessor/opus.go b/internal/codecprocessor/opus.go deleted file mode 100644 index 055d1748..00000000 --- a/internal/codecprocessor/opus.go +++ /dev/null @@ -1,105 +0,0 @@ -package codecprocessor - -import ( - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio" - mcopus "github.com/bluenviron/mediacommon/v2/pkg/codecs/opus" - "github.com/pion/rtp" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type opus struct { - RTPMaxPayloadSize int - Format *format.Opus - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpsimpleaudio.Encoder - decoder *rtpsimpleaudio.Decoder - randomStart uint32 -} - -func (t *opus) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *opus) createEncoder() error { - t.encoder = &rtpsimpleaudio.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *opus) ProcessUnit(u *unit.Unit) error { //nolint:dupl - var rtpPackets []*rtp.Packet //nolint:prealloc - pts := u.PTS - - for _, packet := range u.Payload.(unit.PayloadOpus) { - pkt, err := t.encoder.Encode(packet) - if err != nil { - return err - } - - pkt.Timestamp += t.randomStart + uint32(pts) - - rtpPackets = append(rtpPackets, pkt) - pts += mcopus.PacketDuration2(packet) - } - - u.RTPPackets = rtpPackets - - return nil -} - -func (t *opus) ProcessRTPPacket( - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - packet, err := t.decoder.Decode(pkt) - if err != nil { - return err - } - - u.Payload = unit.PayloadOpus{packet} - } - - return nil -} diff --git a/internal/codecprocessor/opus_test.go b/internal/codecprocessor/opus_test.go deleted file mode 100644 index e1f3e4e9..00000000 --- a/internal/codecprocessor/opus_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediamtx/internal/unit" - "github.com/pion/rtp" - "github.com/stretchr/testify/require" -) - -func TestOpusProcessUnit(t *testing.T) { - forma := &format.Opus{ - PayloadTyp: 96, - ChannelCount: 2, - } - - p, err := New(1450, forma, true, nil) - require.NoError(t, err) - - unit := &unit.Unit{ - Payload: unit.PayloadOpus{ - { - 0xfc, 0x1e, 0x61, 0x96, 0xfc, 0xf7, 0x9b, 0x23, - 0x5b, 0xc9, 0x56, 0xad, 0x05, 0x12, 0x2f, 0x6c, - 0xc0, 0x0c, 0x2c, 0x17, 0x7b, 0x1a, 0xde, 0x1b, - 0x37, 0x89, 0xc5, 0xbb, 0x34, 0xbb, 0x1c, 0x74, - 0x7c, 0x18, 0x0a, 0xde, 0xa1, 0x2b, 0x86, 0x1d, - 0x60, 0xa2, 0xb6, 0xce, 0xe7, 0x0e, 0x17, 0x1b, - 0xc7, 0xd4, 0xd1, 0x2a, 0x68, 0x1f, 0x05, 0x2b, - 0x22, 0x80, 0x68, 0x12, 0x0c, 0x45, 0xbc, 0x3a, - 0xd2, 0x1b, 0xf2, 0x8a, 0x77, 0x5f, 0x2b, 0x34, - 0x97, 0x34, 0x09, 0x6d, 0x05, 0x5f, 0x48, 0x0c, - 0x45, 0xb5, 0xae, 0x2a, 0x90, 0x21, 0xda, 0xfb, - 0x5b, 0x10, - }, - { - 0xfc, 0x1d, 0x61, 0x96, 0xfa, 0x7a, 0x90, 0x59, - 0xb7, 0x10, 0xd7, 0x03, 0x84, 0x27, 0x3f, 0x52, - 0x9f, 0xd7, 0x38, 0x9f, 0xbc, 0xff, 0x7d, 0x62, - 0xe8, 0x60, 0x64, 0x54, 0x9d, 0x1a, 0xd1, 0x7c, - 0x1a, 0x0a, 0x76, 0xd2, 0x30, 0x9b, 0xbe, 0xc7, - 0x5d, 0x37, 0x42, 0xe7, 0xdd, 0xfc, 0xc7, 0x03, - 0xab, 0x90, 0xd9, 0x9b, 0xad, 0xf4, 0x88, 0xd3, - 0x81, 0xbf, 0xd0, 0x68, 0x10, 0x0c, 0x46, 0xa4, - 0xe8, 0x83, 0xd9, 0x6b, 0x7a, 0x25, 0xed, 0x81, - 0xf6, 0x92, 0x14, 0x70, 0x6c, 0x48, 0x0c, 0x45, - 0xb4, 0xf2, 0x61, 0x9b, 0xd7, 0x62, 0x58, 0x87, - }, - { - 0xfc, 0x1e, 0x61, 0x96, 0xfa, 0x7f, 0x61, 0x51, - 0x2f, 0x10, 0x81, 0x22, 0x01, 0x81, 0x46, 0x5e, - 0xbd, 0xb0, 0x87, 0xcb, 0x0a, 0xa6, 0xd6, 0xd3, - 0xec, 0x7c, 0x9e, 0xf5, 0x07, 0x5a, 0x07, 0x1b, - 0x7c, 0x19, 0x0a, 0xde, 0xa1, 0x38, 0xe4, 0x51, - 0x7f, 0x54, 0x6f, 0x91, 0x9f, 0xda, 0x2b, 0x40, - 0x80, 0x36, 0xeb, 0xe3, 0xc2, 0x58, 0x12, 0x55, - 0x80, 0x65, 0x14, 0x68, 0x12, 0x0c, 0x46, 0xa4, - 0xdc, 0x6e, 0x62, 0x79, 0xf9, 0x09, 0x28, 0x11, - 0xab, 0xab, 0xd3, 0x84, 0x7d, 0x93, 0x34, 0x48, - 0x0c, 0x46, 0x9a, 0x0c, 0xb0, 0xfe, 0x7a, 0x5b, - 0x22, 0x85, 0x4a, 0x73, 0x0d, 0xd0, - }, - }, - } - - err = p.ProcessUnit(unit) - require.NoError(t, err) - require.Equal(t, []*rtp.Packet{ - { - Header: rtp.Header{ - Version: 2, - PayloadType: 96, - SequenceNumber: unit.RTPPackets[0].SequenceNumber, - Timestamp: unit.RTPPackets[0].Timestamp, - SSRC: unit.RTPPackets[0].SSRC, - }, - Payload: []byte{ - 0xfc, 0x1e, 0x61, 0x96, 0xfc, 0xf7, 0x9b, 0x23, - 0x5b, 0xc9, 0x56, 0xad, 0x05, 0x12, 0x2f, 0x6c, - 0xc0, 0x0c, 0x2c, 0x17, 0x7b, 0x1a, 0xde, 0x1b, - 0x37, 0x89, 0xc5, 0xbb, 0x34, 0xbb, 0x1c, 0x74, - 0x7c, 0x18, 0x0a, 0xde, 0xa1, 0x2b, 0x86, 0x1d, - 0x60, 0xa2, 0xb6, 0xce, 0xe7, 0x0e, 0x17, 0x1b, - 0xc7, 0xd4, 0xd1, 0x2a, 0x68, 0x1f, 0x05, 0x2b, - 0x22, 0x80, 0x68, 0x12, 0x0c, 0x45, 0xbc, 0x3a, - 0xd2, 0x1b, 0xf2, 0x8a, 0x77, 0x5f, 0x2b, 0x34, - 0x97, 0x34, 0x09, 0x6d, 0x05, 0x5f, 0x48, 0x0c, - 0x45, 0xb5, 0xae, 0x2a, 0x90, 0x21, 0xda, 0xfb, - 0x5b, 0x10, - }, - }, - { - Header: rtp.Header{ - Version: 2, - PayloadType: 96, - SequenceNumber: unit.RTPPackets[0].SequenceNumber + 1, - Timestamp: unit.RTPPackets[0].Timestamp + 960, - SSRC: unit.RTPPackets[0].SSRC, - }, - Payload: []byte{ - 0xfc, 0x1d, 0x61, 0x96, 0xfa, 0x7a, 0x90, 0x59, - 0xb7, 0x10, 0xd7, 0x03, 0x84, 0x27, 0x3f, 0x52, - 0x9f, 0xd7, 0x38, 0x9f, 0xbc, 0xff, 0x7d, 0x62, - 0xe8, 0x60, 0x64, 0x54, 0x9d, 0x1a, 0xd1, 0x7c, - 0x1a, 0x0a, 0x76, 0xd2, 0x30, 0x9b, 0xbe, 0xc7, - 0x5d, 0x37, 0x42, 0xe7, 0xdd, 0xfc, 0xc7, 0x03, - 0xab, 0x90, 0xd9, 0x9b, 0xad, 0xf4, 0x88, 0xd3, - 0x81, 0xbf, 0xd0, 0x68, 0x10, 0x0c, 0x46, 0xa4, - 0xe8, 0x83, 0xd9, 0x6b, 0x7a, 0x25, 0xed, 0x81, - 0xf6, 0x92, 0x14, 0x70, 0x6c, 0x48, 0x0c, 0x45, - 0xb4, 0xf2, 0x61, 0x9b, 0xd7, 0x62, 0x58, 0x87, - }, - }, - { - Header: rtp.Header{ - Version: 2, - PayloadType: 96, - SequenceNumber: unit.RTPPackets[0].SequenceNumber + 2, - Timestamp: unit.RTPPackets[0].Timestamp + 960*2, - SSRC: unit.RTPPackets[0].SSRC, - }, - Payload: []byte{ - 0xfc, 0x1e, 0x61, 0x96, 0xfa, 0x7f, 0x61, 0x51, - 0x2f, 0x10, 0x81, 0x22, 0x01, 0x81, 0x46, 0x5e, - 0xbd, 0xb0, 0x87, 0xcb, 0x0a, 0xa6, 0xd6, 0xd3, - 0xec, 0x7c, 0x9e, 0xf5, 0x07, 0x5a, 0x07, 0x1b, - 0x7c, 0x19, 0x0a, 0xde, 0xa1, 0x38, 0xe4, 0x51, - 0x7f, 0x54, 0x6f, 0x91, 0x9f, 0xda, 0x2b, 0x40, - 0x80, 0x36, 0xeb, 0xe3, 0xc2, 0x58, 0x12, 0x55, - 0x80, 0x65, 0x14, 0x68, 0x12, 0x0c, 0x46, 0xa4, - 0xdc, 0x6e, 0x62, 0x79, 0xf9, 0x09, 0x28, 0x11, - 0xab, 0xab, 0xd3, 0x84, 0x7d, 0x93, 0x34, 0x48, - 0x0c, 0x46, 0x9a, 0x0c, 0xb0, 0xfe, 0x7a, 0x5b, - 0x22, 0x85, 0x4a, 0x73, 0x0d, 0xd0, - }, - }, - }, unit.RTPPackets) -} diff --git a/internal/codecprocessor/processor.go b/internal/codecprocessor/processor.go deleted file mode 100644 index c99e6d81..00000000 --- a/internal/codecprocessor/processor.go +++ /dev/null @@ -1,185 +0,0 @@ -// Package codecprocessor contains codec-specific processing. -package codecprocessor - -import ( - "crypto/rand" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -func randUint32() (uint32, error) { - var b [4]byte - _, err := rand.Read(b[:]) - if err != nil { - return 0, err - } - return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil -} - -// Processor is the codec-specific part of the processing that happens inside stream.Stream. -type Processor interface { - // process a Unit. - ProcessUnit(*unit.Unit) error - - // process a RTP packet. - ProcessRTPPacket( - u *unit.Unit, - hasNonRTSPReaders bool, - ) error - - initialize() error -} - -// New allocates a Processor. -func New( - rtpMaxPayloadSize int, - forma format.Format, - generateRTPPackets bool, - parent logger.Writer, -) (Processor, error) { - var proc Processor - - switch forma := forma.(type) { - case *format.AV1: - proc = &av1{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.VP9: - proc = &vp9{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.VP8: - proc = &vp8{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.H265: - proc = &h265{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.H264: - proc = &h264{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.MPEG4Video: - proc = &mpeg4Video{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.MPEG1Video: - proc = &mpeg1Video{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.MJPEG: - proc = &mjpeg{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.Opus: - proc = &opus{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.KLV: - proc = &klv{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.MPEG4Audio: - proc = &mpeg4Audio{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.MPEG4AudioLATM: - proc = &mpeg4AudioLATM{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.MPEG1Audio: - proc = &mpeg1Audio{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.AC3: - proc = &ac3{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.G711: - proc = &g711{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - case *format.LPCM: - proc = &lpcm{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - - default: - proc = &generic{ - RTPMaxPayloadSize: rtpMaxPayloadSize, - Format: forma, - GenerateRTPPackets: generateRTPPackets, - Parent: parent, - } - } - - err := proc.initialize() - return proc, err -} diff --git a/internal/codecprocessor/processor_test.go b/internal/codecprocessor/processor_test.go deleted file mode 100644 index fd33e5e5..00000000 --- a/internal/codecprocessor/processor_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package codecprocessor - -import ( - "testing" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/stretchr/testify/require" -) - -func TestNew(t *testing.T) { - for _, ca := range []struct { - name string - in format.Format - out Processor - }{ - { - "av1", - &format.AV1{}, - &av1{}, - }, - { - "vp9", - &format.VP9{}, - &vp9{}, - }, - { - "vp8", - &format.VP8{}, - &vp8{}, - }, - { - "h265", - &format.H265{}, - &h265{}, - }, - { - "h264", - &format.H264{}, - &h264{}, - }, - { - "mpeg4 video", - &format.MPEG4Video{}, - &mpeg4Video{}, - }, - { - "mpeg1 video", - &format.MPEG1Video{}, - &mpeg1Video{}, - }, - { - "mpeg1 mjpeg", - &format.MPEG1Audio{}, - &mpeg1Audio{}, - }, - { - "opus", - &format.Opus{}, - &opus{}, - }, - { - "mpeg4 audio", - &format.MPEG4Audio{}, - &mpeg4Audio{}, - }, - { - "mpeg1 audio", - &format.MPEG1Audio{}, - &mpeg1Audio{}, - }, - { - "ac3", - &format.AC3{}, - &ac3{}, - }, - { - "g711", - &format.G711{}, - &g711{}, - }, - { - "lpcm", - &format.LPCM{}, - &lpcm{}, - }, - { - "klv", - &format.KLV{ - PayloadTyp: 96, - }, - &klv{}, - }, - { - "generic", - &format.Generic{}, - &generic{}, - }, - } { - t.Run(ca.name, func(t *testing.T) { - p, err := New(1450, ca.in, false, nil) - require.NoError(t, err) - require.IsType(t, ca.out, p) - }) - } -} diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/048b606517c23baf b/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/048b606517c23baf deleted file mode 100644 index d4bdeb53..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/048b606517c23baf +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("800") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/32e7782636603e29 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/32e7782636603e29 deleted file mode 100644 index c487badc..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/32e7782636603e29 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("8\x00\x00") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/caf81e9797b19c76 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/caf81e9797b19c76 deleted file mode 100644 index 67322c70..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/caf81e9797b19c76 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/f428976a5b2917c0 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/f428976a5b2917c0 deleted file mode 100644 index 9756ef63..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH264ExtractSPSPPS/f428976a5b2917c0 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("80") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/353ba911ad2dc191 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/353ba911ad2dc191 deleted file mode 100644 index 955f0420..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/353ba911ad2dc191 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("a00") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/3c3a72c00adac0b3 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/3c3a72c00adac0b3 deleted file mode 100644 index 79752f98..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/3c3a72c00adac0b3 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("a0\x00\x00") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/582528ddfad69eb5 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/582528ddfad69eb5 deleted file mode 100644 index a96f5599..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/582528ddfad69eb5 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("0") diff --git a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/c4389a565e828050 b/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/c4389a565e828050 deleted file mode 100644 index 81d331a2..00000000 --- a/internal/codecprocessor/testdata/fuzz/FuzzRTPH265ExtractParams/c4389a565e828050 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("a000") diff --git a/internal/codecprocessor/vp8.go b/internal/codecprocessor/vp8.go deleted file mode 100644 index bd4a21eb..00000000 --- a/internal/codecprocessor/vp8.go +++ /dev/null @@ -1,101 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type vp8 struct { - RTPMaxPayloadSize int - Format *format.VP8 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpvp8.Encoder - decoder *rtpvp8.Decoder - randomStart uint32 -} - -func (t *vp8) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *vp8) createEncoder() error { - t.encoder = &rtpvp8.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *vp8) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadVP8)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *vp8) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frame, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpvp8.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpvp8.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadVP8(frame) - } - - return nil -} diff --git a/internal/codecprocessor/vp9.go b/internal/codecprocessor/vp9.go deleted file mode 100644 index c03ab8af..00000000 --- a/internal/codecprocessor/vp9.go +++ /dev/null @@ -1,101 +0,0 @@ -package codecprocessor //nolint:dupl - -import ( - "errors" - "fmt" - - "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9" - - "github.com/bluenviron/mediamtx/internal/logger" - "github.com/bluenviron/mediamtx/internal/unit" -) - -type vp9 struct { - RTPMaxPayloadSize int - Format *format.VP9 - GenerateRTPPackets bool - Parent logger.Writer - - encoder *rtpvp9.Encoder - decoder *rtpvp9.Decoder - randomStart uint32 -} - -func (t *vp9) initialize() error { - if t.GenerateRTPPackets { - err := t.createEncoder() - if err != nil { - return err - } - - t.randomStart, err = randUint32() - if err != nil { - return err - } - } - - return nil -} - -func (t *vp9) createEncoder() error { - t.encoder = &rtpvp9.Encoder{ - PayloadMaxSize: t.RTPMaxPayloadSize, - PayloadType: t.Format.PayloadTyp, - } - return t.encoder.Init() -} - -func (t *vp9) ProcessUnit(u *unit.Unit) error { //nolint:dupl - pkts, err := t.encoder.Encode(u.Payload.(unit.PayloadVP9)) - if err != nil { - return err - } - u.RTPPackets = pkts - - for _, pkt := range u.RTPPackets { - pkt.Timestamp += t.randomStart + uint32(u.PTS) - } - - return nil -} - -func (t *vp9) ProcessRTPPacket( //nolint:dupl - u *unit.Unit, - hasNonRTSPReaders bool, -) error { - pkt := u.RTPPackets[0] - - // remove padding - pkt.Padding = false - pkt.PaddingSize = 0 - - if len(pkt.Payload) > t.RTPMaxPayloadSize { - return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", - len(pkt.Payload), t.RTPMaxPayloadSize) - } - - // decode from RTP - if hasNonRTSPReaders || t.decoder != nil { - if t.decoder == nil { - var err error - t.decoder, err = t.Format.CreateDecoder() - if err != nil { - return err - } - } - - frame, err := t.decoder.Decode(pkt) - if err != nil { - if errors.Is(err, rtpvp9.ErrNonStartingPacketAndNoPrevious) || - errors.Is(err, rtpvp9.ErrMorePacketsNeeded) { - return nil - } - return err - } - - u.Payload = unit.PayloadVP9(frame) - } - - return nil -} diff --git a/internal/core/path.go b/internal/core/path.go index 955dc9fd..ca586b9c 100644 --- a/internal/core/path.go +++ b/internal/core/path.go @@ -379,7 +379,7 @@ func (pa *path) doReloadConf(newConf *conf.Path) { } func (pa *path) doSourceStaticSetReady(req defs.PathSourceStaticSetReadyReq) { - err := pa.setReady(req.Desc, req.GenerateRTPPackets, req.FillNTP) + err := pa.setReady(req.Desc, req.UseRTPPackets, req.ReplaceNTP) if err != nil { req.Res <- defs.PathSourceStaticSetReadyRes{Err: err} return @@ -476,7 +476,7 @@ func (pa *path) doAddPublisher(req defs.PathAddPublisherReq) { pa.source = req.Author pa.publisherQuery = req.AccessRequest.Query - err := pa.setReady(req.Desc, req.GenerateRTPPackets, req.FillNTP) + err := pa.setReady(req.Desc, req.UseRTPPackets, req.ReplaceNTP) if err != nil { pa.source = nil req.Res <- defs.PathAddPublisherRes{Err: err} @@ -688,14 +688,14 @@ func (pa *path) onDemandPublisherStop(reason string) { pa.onDemandPublisherState = pathOnDemandStateInitial } -func (pa *path) setReady(desc *description.Session, generateRTPPackets bool, fillNTP bool) error { +func (pa *path) setReady(desc *description.Session, useRTPPackets bool, replaceNTP bool) error { pa.stream = &stream.Stream{ - WriteQueueSize: pa.writeQueueSize, - RTPMaxPayloadSize: pa.rtpMaxPayloadSize, - Desc: desc, - GenerateRTPPackets: generateRTPPackets, - FillNTP: fillNTP, - Parent: pa.source, + Desc: desc, + UseRTPPackets: useRTPPackets, + WriteQueueSize: pa.writeQueueSize, + RTPMaxPayloadSize: pa.rtpMaxPayloadSize, + ReplaceNTP: replaceNTP, + Parent: pa.source, } err := pa.stream.Initialize() if err != nil { diff --git a/internal/defs/path.go b/internal/defs/path.go index bac443c6..b774af5c 100644 --- a/internal/defs/path.go +++ b/internal/defs/path.go @@ -64,13 +64,13 @@ type PathAddPublisherRes struct { // PathAddPublisherReq contains arguments of AddPublisher(). type PathAddPublisherReq struct { - Author Publisher - Desc *description.Session - GenerateRTPPackets bool - FillNTP bool - ConfToCompare *conf.Path - AccessRequest PathAccessRequest - Res chan PathAddPublisherRes + Author Publisher + Desc *description.Session + UseRTPPackets bool + ReplaceNTP bool + ConfToCompare *conf.Path + AccessRequest PathAccessRequest + Res chan PathAddPublisherRes } // PathRemovePublisherReq contains arguments of RemovePublisher(). @@ -107,10 +107,10 @@ type PathSourceStaticSetReadyRes struct { // PathSourceStaticSetReadyReq contains arguments of SetReady(). type PathSourceStaticSetReadyReq struct { - Desc *description.Session - GenerateRTPPackets bool - FillNTP bool - Res chan PathSourceStaticSetReadyRes + Desc *description.Session + UseRTPPackets bool + ReplaceNTP bool + Res chan PathSourceStaticSetReadyRes } // PathSourceStaticSetNotReadyReq contains arguments of SetNotReady(). diff --git a/internal/protocols/hls/to_stream_test.go b/internal/protocols/hls/to_stream_test.go index 4040d0f0..5492a7c4 100644 --- a/internal/protocols/hls/to_stream_test.go +++ b/internal/protocols/hls/to_stream_test.go @@ -108,23 +108,31 @@ func TestToStream(t *testing.T) { }}, medias) strm = &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err2 = strm.Initialize() require.NoError(t, err2) + n := 0 + r.OnData( medias[0], medias[0].Formats[0], func(u *unit.Unit) error { - if !u.NilPayload() { + switch n { + case 0: + require.True(t, u.NilPayload()) + case 1: require.Equal(t, time.Date(2018, 0o5, 20, 8, 17, 15, 0, time.UTC), u.NTP) close(done) + default: + t.Error("should not happen") } + n++ return nil }) diff --git a/internal/protocols/rtmp/from_stream_test.go b/internal/protocols/rtmp/from_stream_test.go index ac15dd64..b70b99f6 100644 --- a/internal/protocols/rtmp/from_stream_test.go +++ b/internal/protocols/rtmp/from_stream_test.go @@ -12,7 +12,6 @@ import ( "github.com/bluenviron/gortmplib/pkg/codecs" "github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/format" - "github.com/bluenviron/mediamtx/internal/codecprocessor" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/test" @@ -20,6 +19,37 @@ import ( "github.com/stretchr/testify/require" ) +var ( + h265DefaultVPS = []byte{ + 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20, + 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24, + } + + h265DefaultSPS = []byte{ + 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, + 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d, + 0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88, + 0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9, + 0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc, + 0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a, + 0x02, 0x02, 0x02, 0x01, + } + + h265DefaultPPS = []byte{ + 0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40, + } + + h264DefaultSPS = []byte{ // 1920x1080 baseline + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, + 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + } + + h264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08} +) + func TestFromStream(t *testing.T) { h265VPS := []byte{ 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, @@ -449,13 +479,13 @@ func TestFromStream(t *testing.T) { }, expectedTracks: []*gortmplib.Track{ {Codec: &codecs.H265{ - VPS: codecprocessor.H265DefaultVPS, - SPS: codecprocessor.H265DefaultSPS, - PPS: codecprocessor.H265DefaultPPS, + VPS: h265DefaultVPS, + SPS: h265DefaultSPS, + PPS: h265DefaultPPS, }}, {Codec: &codecs.H264{ - SPS: codecprocessor.H264DefaultSPS, - PPS: codecprocessor.H264DefaultPPS, + SPS: h264DefaultSPS, + PPS: h264DefaultPPS, }}, {Codec: &codecs.VP9{}}, {Codec: &codecs.AV1{}}, @@ -494,8 +524,8 @@ func TestFromStream(t *testing.T) { strm.WriteUnit(medias[1], medias[1].Formats[0], &unit.Unit{ Payload: unit.PayloadH264{ - codecprocessor.H264DefaultSPS, - codecprocessor.H264DefaultPPS, + h264DefaultSPS, + h264DefaultPPS, {5, 2}, // IDR }, }) @@ -532,11 +562,11 @@ func TestFromStream(t *testing.T) { medias := tc.medias strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) diff --git a/internal/protocols/rtsp/to_stream.go b/internal/protocols/rtsp/to_stream.go index 658a14a8..97f5da81 100644 --- a/internal/protocols/rtsp/to_stream.go +++ b/internal/protocols/rtsp/to_stream.go @@ -10,6 +10,7 @@ import ( "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/stream" + "github.com/bluenviron/mediamtx/internal/unit" "github.com/pion/rtp" ) @@ -82,7 +83,11 @@ func ToStream( return } - (*strm).WriteRTPPacket(cmedi, cforma, pkt, ntp, pts) + (*strm).WriteUnit(cmedi, cforma, &unit.Unit{ + PTS: pts, + NTP: ntp, + RTPPackets: []*rtp.Packet{pkt}, + }) }) } } diff --git a/internal/protocols/webrtc/from_stream_test.go b/internal/protocols/webrtc/from_stream_test.go index daabec66..5ee02e08 100644 --- a/internal/protocols/webrtc/from_stream_test.go +++ b/internal/protocols/webrtc/from_stream_test.go @@ -11,6 +11,7 @@ import ( "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/stream" "github.com/bluenviron/mediamtx/internal/test" + "github.com/bluenviron/mediamtx/internal/unit" "github.com/pion/rtp" "github.com/stretchr/testify/require" ) @@ -85,8 +86,6 @@ func TestFromStream(t *testing.T) { func TestFromStreamResampleOpus(t *testing.T) { strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, Desc: &description.Session{Medias: []*description.Media{ { Type: description.MediaTypeAudio, @@ -95,8 +94,11 @@ func TestFromStreamResampleOpus(t *testing.T) { }}, }, }}, - GenerateRTPPackets: true, - Parent: test.NilLogger, + UseRTPPackets: true, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + ReplaceNTP: false, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -149,29 +151,37 @@ func TestFromStreamResampleOpus(t *testing.T) { strm.AddReader(r) defer strm.RemoveReader(r) - strm.WriteRTPPacket(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 111, - SequenceNumber: 1123, - Timestamp: 45343, - SSRC: 563424, - }, - Payload: []byte{1}, - }, time.Now(), 0) + strm.WriteUnit(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &unit.Unit{ + PTS: 0, + NTP: time.Now(), + RTPPackets: []*rtp.Packet{{ + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 111, + SequenceNumber: 1123, + Timestamp: 45343, + SSRC: 563424, + }, + Payload: []byte{1}, + }}, + }) - strm.WriteRTPPacket(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &rtp.Packet{ - Header: rtp.Header{ - Version: 2, - Marker: true, - PayloadType: 111, - SequenceNumber: 1124, - Timestamp: 45343, - SSRC: 563424, - }, - Payload: []byte{1}, - }, time.Now(), 0) + strm.WriteUnit(strm.Desc.Medias[0], strm.Desc.Medias[0].Formats[0], &unit.Unit{ + PTS: 0, + NTP: time.Now(), + RTPPackets: []*rtp.Packet{{ + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 111, + SequenceNumber: 1124, + Timestamp: 45343, + SSRC: 563424, + }, + Payload: []byte{1}, + }}, + }) err = pc1.GatherIncomingTracks() require.NoError(t, err) diff --git a/internal/protocols/webrtc/to_stream.go b/internal/protocols/webrtc/to_stream.go index 983567be..920f4a2f 100644 --- a/internal/protocols/webrtc/to_stream.go +++ b/internal/protocols/webrtc/to_stream.go @@ -12,6 +12,7 @@ import ( "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/stream" + "github.com/bluenviron/mediamtx/internal/unit" "github.com/pion/rtp" "github.com/pion/webrtc/v4" ) @@ -196,7 +197,11 @@ func ToStream( return } - (*strm).WriteRTPPacket(medi, forma, pkt, ntp, pts) + (*strm).WriteUnit(medi, forma, &unit.Unit{ + PTS: pts, + NTP: ntp, + RTPPackets: []*rtp.Packet{pkt}, + }) } medias = append(medias, medi) diff --git a/internal/recorder/format_fmp4.go b/internal/recorder/format_fmp4.go index 854adbb3..5670ef1b 100644 --- a/internal/recorder/format_fmp4.go +++ b/internal/recorder/format_fmp4.go @@ -21,15 +21,60 @@ import ( "github.com/bluenviron/mediacommon/v2/pkg/formats/fmp4" mcodecs "github.com/bluenviron/mediacommon/v2/pkg/formats/mp4/codecs" - "github.com/bluenviron/mediamtx/internal/codecprocessor" "github.com/bluenviron/mediamtx/internal/defs" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/unit" ) -var av1DefaultSequenceHeader = []byte{ - 8, 0, 0, 0, 66, 167, 191, 228, 96, 13, 0, 64, -} +var ( + av1DefaultSequenceHeader = []byte{ + 8, 0, 0, 0, 66, 167, 191, 228, 96, 13, 0, 64, + } + + h265DefaultVPS = []byte{ + 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x02, 0x20, + 0x00, 0x00, 0x03, 0x00, 0xb0, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x7b, 0x18, 0xb0, 0x24, + } + + h265DefaultSPS = []byte{ + 0x42, 0x01, 0x01, 0x02, 0x20, 0x00, 0x00, 0x03, + 0x00, 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x7b, 0xa0, 0x07, 0x82, 0x00, 0x88, 0x7d, + 0xb6, 0x71, 0x8b, 0x92, 0x44, 0x80, 0x53, 0x88, + 0x88, 0x92, 0xcf, 0x24, 0xa6, 0x92, 0x72, 0xc9, + 0x12, 0x49, 0x22, 0xdc, 0x91, 0xaa, 0x48, 0xfc, + 0xa2, 0x23, 0xff, 0x00, 0x01, 0x00, 0x01, 0x6a, + 0x02, 0x02, 0x02, 0x01, + } + + h265DefaultPPS = []byte{ + 0x44, 0x01, 0xc0, 0x25, 0x2f, 0x05, 0x32, 0x40, + } + + h264DefaultSPS = []byte{ // 1920x1080 baseline + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, + 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + } + + h264DefaultPPS = []byte{0x08, 0x06, 0x07, 0x08} + + mpeg4VideoDefaultConfig = []byte{ + 0x00, 0x00, 0x01, 0xb0, 0x01, 0x00, 0x00, 0x01, + 0xb5, 0x89, 0x13, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x20, 0x00, 0xc4, 0x8d, 0x88, 0x00, + 0xf5, 0x3c, 0x04, 0x87, 0x14, 0x63, 0x00, 0x00, + 0x01, 0xb2, 0x4c, 0x61, 0x76, 0x63, 0x35, 0x38, + 0x2e, 0x31, 0x33, 0x34, 0x2e, 0x31, 0x30, 0x30, + } + + mpeg1VideoDefaultConfig = []byte{ + 0x00, 0x00, 0x01, 0xb3, 0x78, 0x04, 0x38, 0x35, + 0xff, 0xff, 0xe0, 0x18, 0x00, 0x00, 0x01, 0xb5, + 0x14, 0x4a, 0x00, 0x01, 0x00, 0x00, + } +) func mpeg1audioChannelCount(cm mpeg1audio.ChannelMode) int { switch cm { @@ -276,9 +321,9 @@ func (f *formatFMP4) initialize() bool { case *rtspformat.H265: vps, sps, pps := forma.SafeParams() if vps == nil || sps == nil || pps == nil { - vps = codecprocessor.H265DefaultVPS - sps = codecprocessor.H265DefaultSPS - pps = codecprocessor.H265DefaultPPS + vps = h265DefaultVPS + sps = h265DefaultSPS + pps = h265DefaultPPS } codec := &mcodecs.H265{ @@ -361,8 +406,8 @@ func (f *formatFMP4) initialize() bool { case *rtspformat.H264: sps, pps := forma.SafeParams() if sps == nil || pps == nil { - sps = codecprocessor.H264DefaultSPS - pps = codecprocessor.H264DefaultPPS + sps = h264DefaultSPS + pps = h264DefaultPPS } codec := &mcodecs.H264{ @@ -438,7 +483,7 @@ func (f *formatFMP4) initialize() bool { config := forma.SafeParams() if config == nil { - config = codecprocessor.MPEG4VideoDefaultConfig + config = mpeg4VideoDefaultConfig } codec := &mcodecs.MPEG4Video{ @@ -496,7 +541,7 @@ func (f *formatFMP4) initialize() bool { case *rtspformat.MPEG1Video: codec := &mcodecs.MPEG1Video{ - Config: codecprocessor.MPEG1VideoDefaultConfig, + Config: mpeg1VideoDefaultConfig, } track := addTrack(forma, codec) diff --git a/internal/recorder/recorder_test.go b/internal/recorder/recorder_test.go index de4f8bb1..8ef2b1ae 100644 --- a/internal/recorder/recorder_test.go +++ b/internal/recorder/recorder_test.go @@ -130,11 +130,11 @@ func TestRecorder(t *testing.T) { for _, ca := range []string{"fmp4", "mpegts"} { t.Run(ca, func(t *testing.T) { strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -418,11 +418,11 @@ func TestRecorderFMP4NegativeInitialDTS(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -507,11 +507,11 @@ func TestRecorderFMP4NegativeDTSDiff(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -601,11 +601,11 @@ func TestRecorderSkipTracksPartial(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -663,11 +663,11 @@ func TestRecorderSkipTracksFull(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -727,11 +727,11 @@ func TestRecorderFMP4SegmentSwitch(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -850,11 +850,11 @@ func TestRecorderTimeDriftDetector(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) diff --git a/internal/servers/hls/server_test.go b/internal/servers/hls/server_test.go index d257df73..a68990c7 100644 --- a/internal/servers/hls/server_test.go +++ b/internal/servers/hls/server_test.go @@ -185,11 +185,12 @@ func TestServerRead(t *testing.T) { }} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + ReplaceNTP: false, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -406,11 +407,11 @@ func TestServerDirectory(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err = strm.Initialize() require.NoError(t, err) @@ -455,11 +456,11 @@ func TestServerDynamicAlwaysRemux(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) diff --git a/internal/servers/rtmp/conn.go b/internal/servers/rtmp/conn.go index 04209f21..ddcc4997 100644 --- a/internal/servers/rtmp/conn.go +++ b/internal/servers/rtmp/conn.go @@ -238,10 +238,10 @@ func (c *conn) runPublish() error { var path defs.Path path, strm, err = c.pathManager.AddPublisher(defs.PathAddPublisherReq{ - Author: c, - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - FillNTP: true, + Author: c, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + ReplaceNTP: true, AccessRequest: defs.PathAccessRequest{ Name: pathName, Query: c.rconn.URL.RawQuery, diff --git a/internal/servers/rtmp/server_test.go b/internal/servers/rtmp/server_test.go index d6abebbd..e566521f 100644 --- a/internal/servers/rtmp/server_test.go +++ b/internal/servers/rtmp/server_test.go @@ -76,11 +76,11 @@ func TestServerPublish(t *testing.T) { require.Equal(t, "mypass", req.AccessRequest.Credentials.Pass) strm = &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: req.Desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: req.Desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -206,11 +206,11 @@ func TestServerRead(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) diff --git a/internal/servers/rtsp/server_test.go b/internal/servers/rtsp/server_test.go index 71123f12..17d863e1 100644 --- a/internal/servers/rtsp/server_test.go +++ b/internal/servers/rtsp/server_test.go @@ -87,11 +87,11 @@ func TestServerPublish(t *testing.T) { require.True(t, req.AccessRequest.SkipAuth) strm = &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: req.Desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: req.Desc, + UseRTPPackets: true, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -175,11 +175,11 @@ func TestServerRead(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -319,11 +319,11 @@ func TestServerRedirect(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: true, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) diff --git a/internal/servers/rtsp/session.go b/internal/servers/rtsp/session.go index 565f0a02..78e97c76 100644 --- a/internal/servers/rtsp/session.go +++ b/internal/servers/rtsp/session.go @@ -311,11 +311,11 @@ func (s *session) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, e // onRecord is called by rtspServer. func (s *session) onRecord(_ *gortsplib.ServerHandlerOnRecordCtx) (*base.Response, error) { path, stream, err := s.pathManager.AddPublisher(defs.PathAddPublisherReq{ - Author: s, - Desc: s.rsession.AnnouncedDescription(), - GenerateRTPPackets: false, - FillNTP: !s.pathConf.UseAbsoluteTimestamp, - ConfToCompare: s.pathConf, + Author: s, + Desc: s.rsession.AnnouncedDescription(), + UseRTPPackets: true, + ReplaceNTP: !s.pathConf.UseAbsoluteTimestamp, + ConfToCompare: s.pathConf, AccessRequest: defs.PathAccessRequest{ Name: s.rsession.Path()[1:], Query: s.rsession.Query(), diff --git a/internal/servers/srt/conn.go b/internal/servers/srt/conn.go index b52e0e7f..bf56b2b7 100644 --- a/internal/servers/srt/conn.go +++ b/internal/servers/srt/conn.go @@ -219,11 +219,11 @@ func (c *conn) runPublishReader(sconn srt.Conn, streamID *streamID, pathConf *co var path defs.Path path, strm, err = c.pathManager.AddPublisher(defs.PathAddPublisherReq{ - Author: c, - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - FillNTP: true, - ConfToCompare: pathConf, + Author: c, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + ReplaceNTP: true, + ConfToCompare: pathConf, AccessRequest: defs.PathAccessRequest{ Name: streamID.path, Query: streamID.query, diff --git a/internal/servers/srt/server_test.go b/internal/servers/srt/server_test.go index e2ae3c7f..f3756bb1 100644 --- a/internal/servers/srt/server_test.go +++ b/internal/servers/srt/server_test.go @@ -66,11 +66,11 @@ func TestServerPublish(t *testing.T) { require.True(t, req.AccessRequest.SkipAuth) strm = &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: req.Desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: req.Desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -181,11 +181,11 @@ func TestServerRead(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) diff --git a/internal/servers/webrtc/server_test.go b/internal/servers/webrtc/server_test.go index 2b1d217a..2759b01c 100644 --- a/internal/servers/webrtc/server_test.go +++ b/internal/servers/webrtc/server_test.go @@ -217,11 +217,11 @@ func TestServerPublish(t *testing.T) { require.True(t, req.AccessRequest.SkipAuth) strm = &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: req.Desc, - GenerateRTPPackets: true, - Parent: test.NilLogger, + Desc: req.Desc, + UseRTPPackets: true, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -466,11 +466,11 @@ func TestServerRead(t *testing.T) { desc := &description.Session{Medias: ca.medias} strm := &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: ca.unit.Payload != nil, - Parent: test.NilLogger, + Desc: desc, + UseRTPPackets: (ca.unit.Payload == nil), + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: test.NilLogger, } err := strm.Initialize() require.NoError(t, err) @@ -537,7 +537,11 @@ func TestServerRead(t *testing.T) { if ca.unit.Payload == nil { clone := *ca.unit.RTPPackets[0] - strm.WriteRTPPacket(desc.Medias[0], desc.Medias[0].Formats[0], &clone, time.Time{}, 0) + strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.Unit{ + PTS: 0, + NTP: time.Time{}, + RTPPackets: []*rtp.Packet{&clone}, + }) } else { strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], r.Interface().(*unit.Unit)) } diff --git a/internal/servers/webrtc/session.go b/internal/servers/webrtc/session.go index 75a869a6..68f91c9f 100644 --- a/internal/servers/webrtc/session.go +++ b/internal/servers/webrtc/session.go @@ -242,11 +242,11 @@ func (s *session) runPublish() (int, error) { var path defs.Path path, strm, err = s.pathManager.AddPublisher(defs.PathAddPublisherReq{ - Author: s, - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: false, - FillNTP: !pathConf.UseAbsoluteTimestamp, - ConfToCompare: pathConf, + Author: s, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: true, + ReplaceNTP: !pathConf.UseAbsoluteTimestamp, + ConfToCompare: pathConf, AccessRequest: defs.PathAccessRequest{ Name: s.req.pathName, Query: s.req.httpRequest.URL.RawQuery, diff --git a/internal/staticsources/hls/source.go b/internal/staticsources/hls/source.go index b3096b80..b4c3d0ac 100644 --- a/internal/staticsources/hls/source.go +++ b/internal/staticsources/hls/source.go @@ -97,8 +97,9 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { } res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + ReplaceNTP: false, }) if res.Err != nil { return res.Err diff --git a/internal/staticsources/mpegts/source.go b/internal/staticsources/mpegts/source.go index e377b133..c94ef84e 100644 --- a/internal/staticsources/mpegts/source.go +++ b/internal/staticsources/mpegts/source.go @@ -121,9 +121,9 @@ func (s *Source) runReader(nc net.Conn) error { } res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - FillNTP: true, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + ReplaceNTP: true, }) if res.Err != nil { return res.Err diff --git a/internal/staticsources/rpicamera/source.go b/internal/staticsources/rpicamera/source.go index 8c3ab90c..ffab452a 100644 --- a/internal/staticsources/rpicamera/source.go +++ b/internal/staticsources/rpicamera/source.go @@ -158,8 +158,9 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error { initializeStream := func() { if strm == nil { res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: false, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: true, + ReplaceNTP: false, }) if res.Err != nil { panic("should not happen") @@ -189,7 +190,11 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error { for _, pkt := range pkts { pkt.Timestamp = uint32(pts) - strm.WriteRTPPacket(medi, medi.Formats[0], pkt, ntp, pts) + strm.WriteUnit(medi, medi.Formats[0], &unit.Unit{ + PTS: pts, + NTP: ntp, + RTPPackets: []*rtp.Packet{pkt}, + }) } } @@ -216,7 +221,11 @@ func (s *Source) runPrimary(params defs.StaticSourceRunParams) error { for _, pkt := range pkts { pkt.Timestamp = uint32(pts) pkt.PayloadType = 96 - strm.WriteRTPPacket(mediaSecondary, mediaSecondary.Formats[0], pkt, ntp, pts) + strm.WriteUnit(mediaSecondary, mediaSecondary.Formats[0], &unit.Unit{ + PTS: pts, + NTP: ntp, + RTPPackets: []*rtp.Packet{pkt}, + }) } } } @@ -275,8 +284,8 @@ func (s *Source) runSecondary(params defs.StaticSourceRunParams) error { } res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: []*description.Media{media}}, - GenerateRTPPackets: false, + Desc: &description.Session{Medias: []*description.Media{media}}, + UseRTPPackets: true, }) if res.Err != nil { return res.Err @@ -296,7 +305,11 @@ func (s *Source) runSecondary(params defs.StaticSourceRunParams) error { } newPkt.PayloadType = 26 - res.Stream.WriteRTPPacket(media, media.Formats[0], newPkt, u.NTP, u.PTS) + res.Stream.WriteUnit(media, media.Formats[0], &unit.Unit{ + PTS: u.PTS, + NTP: u.NTP, + RTPPackets: []*rtp.Packet{newPkt}, + }) return nil }) diff --git a/internal/staticsources/rtmp/source.go b/internal/staticsources/rtmp/source.go index 772b82a4..03219053 100644 --- a/internal/staticsources/rtmp/source.go +++ b/internal/staticsources/rtmp/source.go @@ -113,9 +113,9 @@ func (s *Source) runReader(conn *gortmplib.Client) error { } res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - FillNTP: true, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + ReplaceNTP: true, }) if res.Err != nil { return res.Err diff --git a/internal/staticsources/rtp/source.go b/internal/staticsources/rtp/source.go index 74602eca..40fc308e 100644 --- a/internal/staticsources/rtp/source.go +++ b/internal/staticsources/rtp/source.go @@ -18,6 +18,7 @@ import ( "github.com/bluenviron/mediamtx/internal/protocols/udp" "github.com/bluenviron/mediamtx/internal/protocols/unix" "github.com/bluenviron/mediamtx/internal/stream" + "github.com/bluenviron/mediamtx/internal/unit" "github.com/pion/rtp" ) @@ -175,9 +176,9 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error { if strm == nil { res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: desc, - GenerateRTPPackets: false, - FillNTP: true, + Desc: desc, + UseRTPPackets: true, + ReplaceNTP: true, }) if res.Err != nil { return res.Err @@ -207,7 +208,10 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error { continue } - strm.WriteRTPPacket(media.desc, forma.desc, pkt, time.Time{}, pts) + strm.WriteUnit(media.desc, forma.desc, &unit.Unit{ + PTS: pts, + RTPPackets: []*rtp.Packet{pkt}, + }) } } } diff --git a/internal/staticsources/rtsp/source.go b/internal/staticsources/rtsp/source.go index 766840e6..cb03c843 100644 --- a/internal/staticsources/rtsp/source.go +++ b/internal/staticsources/rtsp/source.go @@ -244,9 +244,9 @@ func (s *Source) runInner(c *gortsplib.Client, u *base.URL, pathConf *conf.Path) s) res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: desc2, - GenerateRTPPackets: false, - FillNTP: !pathConf.UseAbsoluteTimestamp, + Desc: desc2, + UseRTPPackets: true, + ReplaceNTP: !pathConf.UseAbsoluteTimestamp, }) if res.Err != nil { return res.Err diff --git a/internal/staticsources/srt/source.go b/internal/staticsources/srt/source.go index a370af8c..4545e30e 100644 --- a/internal/staticsources/srt/source.go +++ b/internal/staticsources/srt/source.go @@ -106,9 +106,9 @@ func (s *Source) runReader(sconn srt.Conn) error { } res := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - FillNTP: true, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: false, + ReplaceNTP: true, }) if res.Err != nil { return res.Err diff --git a/internal/staticsources/webrtc/source.go b/internal/staticsources/webrtc/source.go index 848724fe..fb216dad 100644 --- a/internal/staticsources/webrtc/source.go +++ b/internal/staticsources/webrtc/source.go @@ -76,9 +76,9 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { } rres := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{ - Desc: &description.Session{Medias: medias}, - GenerateRTPPackets: true, - FillNTP: !params.Conf.UseAbsoluteTimestamp, + Desc: &description.Session{Medias: medias}, + UseRTPPackets: true, + ReplaceNTP: !params.Conf.UseAbsoluteTimestamp, }) if rres.Err != nil { client.Close() //nolint:errcheck diff --git a/internal/stream/format_updater.go b/internal/stream/format_updater.go new file mode 100644 index 00000000..152fc893 --- /dev/null +++ b/internal/stream/format_updater.go @@ -0,0 +1,113 @@ +package stream + +import ( + "bytes" + + "github.com/bluenviron/gortsplib/v5/pkg/format" + mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" + mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video" + "github.com/bluenviron/mediamtx/internal/unit" +) + +type formatUpdater func(format.Format, unit.Payload) + +func formatUpdaterH265(forma format.Format, payload unit.Payload) { + formatH265 := forma.(*format.H265) + au := payload.(unit.PayloadH265) + + vps, sps, pps := formatH265.VPS, formatH265.SPS, formatH265.PPS + update := false + + for _, nalu := range au { + typ := mch265.NALUType((nalu[0] >> 1) & 0b111111) + + switch typ { + case mch265.NALUType_VPS_NUT: + if !bytes.Equal(nalu, formatH265.VPS) { + vps = nalu + update = true + } + + case mch265.NALUType_SPS_NUT: + if !bytes.Equal(nalu, formatH265.SPS) { + sps = nalu + update = true + } + + case mch265.NALUType_PPS_NUT: + if !bytes.Equal(nalu, formatH265.PPS) { + pps = nalu + update = true + } + } + } + + if update { + formatH265.SafeSetParams(vps, sps, pps) + } +} + +func formatUpdaterH264(forma format.Format, payload unit.Payload) { + formatH264 := forma.(*format.H264) + au := payload.(unit.PayloadH264) + + sps, pps := formatH264.SPS, formatH264.PPS + update := false + + for _, nalu := range au { + typ := mch264.NALUType(nalu[0] & 0x1F) + + switch typ { + case mch264.NALUTypeSPS: + if !bytes.Equal(nalu, sps) { + sps = nalu + update = true + } + + case mch264.NALUTypePPS: + if !bytes.Equal(nalu, pps) { + pps = nalu + update = true + } + } + } + + if update { + formatH264.SafeSetParams(sps, pps) + } +} + +func formatUpdaterMPEG4Video(forma format.Format, payload unit.Payload) { + formatMPEG4Video := forma.(*format.MPEG4Video) + frame := payload.(unit.PayloadMPEG4Video) + + if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) { + end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) + if end < 0 { + return + } + conf := frame[:end+4] + + if !bytes.Equal(conf, formatMPEG4Video.Config) { + formatMPEG4Video.SafeSetParams(conf) + } + } +} + +func newFormatUpdater(forma format.Format) formatUpdater { + switch forma.(type) { + case *format.H265: + return formatUpdaterH265 + + case *format.H264: + return formatUpdaterH264 + + case *format.MPEG4Video: + return formatUpdaterMPEG4Video + + default: + return formatUpdater(func(_ format.Format, _ unit.Payload) { + }) + } +} diff --git a/internal/stream/rtp_decoder.go b/internal/stream/rtp_decoder.go new file mode 100644 index 00000000..432a11c4 --- /dev/null +++ b/internal/stream/rtp_decoder.go @@ -0,0 +1,366 @@ +package stream + +import ( + "errors" + + "github.com/bluenviron/gortsplib/v5/pkg/format" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtph264" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtph265" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9" + "github.com/bluenviron/mediamtx/internal/unit" + "github.com/pion/rtp" +) + +type rtpDecoder interface { + decode(*rtp.Packet) (unit.Payload, error) +} + +type rtpDecoderAV1 rtpav1.Decoder + +func (d *rtpDecoderAV1) decode(pkt *rtp.Packet) (unit.Payload, error) { + tu, err := (*rtpav1.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpav1.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtpav1.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadAV1(tu), nil +} + +type rtpDecoderVP9 rtpvp9.Decoder + +func (d *rtpDecoderVP9) decode(pkt *rtp.Packet) (unit.Payload, error) { + frame, err := (*rtpvp9.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpvp9.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtpvp9.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadVP9(frame), nil +} + +type rtpDecoderVP8 rtpvp8.Decoder + +func (d *rtpDecoderVP8) decode(pkt *rtp.Packet) (unit.Payload, error) { + frame, err := (*rtpvp8.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpvp8.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtpvp8.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadVP8(frame), nil +} + +type rtpDecoderH265 rtph265.Decoder + +func (d *rtpDecoderH265) decode(pkt *rtp.Packet) (unit.Payload, error) { + au, err := (*rtph265.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtph265.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtph265.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadH265(au), nil +} + +type rtpDecoderH264 rtph264.Decoder + +func (d *rtpDecoderH264) decode(pkt *rtp.Packet) (unit.Payload, error) { + au, err := (*rtph264.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtph264.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtph264.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadH264(au), nil +} + +type rtpDecoderMPEG4Video rtpfragmented.Decoder + +func (d *rtpDecoderMPEG4Video) decode(pkt *rtp.Packet) (unit.Payload, error) { + frame, err := (*rtpfragmented.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadMPEG4Video(frame), nil +} + +type rtpDecoderMPEG1Video rtpmpeg1video.Decoder + +func (d *rtpDecoderMPEG1Video) decode(pkt *rtp.Packet) (unit.Payload, error) { + frame, err := (*rtpmpeg1video.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpmpeg1video.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtpmpeg1video.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadMPEG1Video(frame), nil +} + +type rtpDecoderMJPEG rtpmjpeg.Decoder + +func (d *rtpDecoderMJPEG) decode(pkt *rtp.Packet) (unit.Payload, error) { + frame, err := (*rtpmjpeg.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpmjpeg.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtpmjpeg.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadMJPEG(frame), nil +} + +type rtpDecoderOpus rtpsimpleaudio.Decoder + +func (d *rtpDecoderOpus) decode(pkt *rtp.Packet) (unit.Payload, error) { + packet, err := (*rtpsimpleaudio.Decoder)(d).Decode(pkt) + if err != nil { + return nil, err + } + + return unit.PayloadOpus{packet}, nil +} + +type rtpDecoderMPEG4Audio rtpmpeg4audio.Decoder + +func (d *rtpDecoderMPEG4Audio) decode(pkt *rtp.Packet) (unit.Payload, error) { + aus, err := (*rtpmpeg4audio.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpmpeg4audio.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadMPEG4Audio(aus), nil +} + +type rtpDecoderMPEG4AudioLATM rtpfragmented.Decoder + +func (d *rtpDecoderMPEG4AudioLATM) decode(pkt *rtp.Packet) (unit.Payload, error) { + payload, err := (*rtpfragmented.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpfragmented.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadMPEG4AudioLATM(payload), nil +} + +type rtpDecoderMPEG1Audio rtpmpeg1audio.Decoder + +func (d *rtpDecoderMPEG1Audio) decode(pkt *rtp.Packet) (unit.Payload, error) { + frames, err := (*rtpmpeg1audio.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpmpeg1audio.ErrNonStartingPacketAndNoPrevious) || + errors.Is(err, rtpmpeg1audio.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadMPEG1Audio(frames), nil +} + +type rtpDecoderAC3 rtpac3.Decoder + +func (d *rtpDecoderAC3) decode(pkt *rtp.Packet) (unit.Payload, error) { + frames, err := (*rtpac3.Decoder)(d).Decode(pkt) + if err != nil { + if errors.Is(err, rtpac3.ErrMorePacketsNeeded) { + return nil, nil + } + return nil, err + } + + return unit.PayloadAC3(frames), nil +} + +type rtpDecoderG711 rtplpcm.Decoder + +func (d *rtpDecoderG711) decode(pkt *rtp.Packet) (unit.Payload, error) { + samples, err := (*rtplpcm.Decoder)(d).Decode(pkt) + if err != nil { + return nil, err + } + + return unit.PayloadG711(samples), nil +} + +type rtpDecoderLPCM rtplpcm.Decoder + +func (d *rtpDecoderLPCM) decode(pkt *rtp.Packet) (unit.Payload, error) { + samples, err := (*rtplpcm.Decoder)(d).Decode(pkt) + if err != nil { + return nil, err + } + + return unit.PayloadLPCM(samples), nil +} + +type rtpDecoderKLV rtpklv.Decoder + +func (d *rtpDecoderKLV) decode(pkt *rtp.Packet) (unit.Payload, error) { + payload, err := (*rtpklv.Decoder)(d).Decode(pkt) + if err != nil { + return nil, err + } + + return unit.PayloadKLV(payload), nil +} + +func newRTPDecoder(forma format.Format) (rtpDecoder, error) { + switch forma := forma.(type) { + case *format.AV1: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderAV1)(wrapped), nil + + case *format.VP9: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderVP9)(wrapped), nil + + case *format.VP8: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderVP8)(wrapped), nil + + case *format.H265: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderH265)(wrapped), nil + + case *format.H264: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderH264)(wrapped), nil + + case *format.MPEG4Video: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderMPEG4Video)(wrapped), nil + + case *format.MPEG1Video: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderMPEG1Video)(wrapped), nil + + case *format.MJPEG: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderMJPEG)(wrapped), nil + + case *format.Opus: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderOpus)(wrapped), nil + + case *format.MPEG4Audio: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderMPEG4Audio)(wrapped), nil + + case *format.MPEG4AudioLATM: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderMPEG4AudioLATM)(wrapped), nil + + case *format.MPEG1Audio: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderMPEG1Audio)(wrapped), nil + + case *format.AC3: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderAC3)(wrapped), nil + + case *format.G711: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderG711)(wrapped), nil + + case *format.LPCM: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderLPCM)(wrapped), nil + + case *format.KLV: + wrapped, err := forma.CreateDecoder() + if err != nil { + return nil, err + } + return (*rtpDecoderKLV)(wrapped), nil + + default: + return nil, nil + } +} diff --git a/internal/stream/rtp_encoder.go b/internal/stream/rtp_encoder.go new file mode 100644 index 00000000..ca32063a --- /dev/null +++ b/internal/stream/rtp_encoder.go @@ -0,0 +1,382 @@ +package stream + +import ( + "github.com/bluenviron/gortsplib/v5/pkg/format" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpac3" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpav1" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpfragmented" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtph264" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtph265" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpklv" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtplpcm" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmjpeg" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1audio" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg1video" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpmpeg4audio" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpsimpleaudio" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp8" + "github.com/bluenviron/gortsplib/v5/pkg/format/rtpvp9" + mcopus "github.com/bluenviron/mediacommon/v2/pkg/codecs/opus" + "github.com/bluenviron/mediamtx/internal/unit" + "github.com/pion/rtp" +) + +func ptrOf[T any](v T) *T { + return &v +} + +type rtpEncoder interface { + encode(unit.Payload) ([]*rtp.Packet, error) +} + +type rtpEncoderH265 rtph265.Encoder + +func (e *rtpEncoderH265) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtph265.Encoder)(e).Encode(payload.(unit.PayloadH265)) +} + +type rtpEncoderH264 rtph264.Encoder + +func (e *rtpEncoderH264) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtph264.Encoder)(e).Encode(payload.(unit.PayloadH264)) +} + +type rtpEncoderAV1 rtpav1.Encoder + +func (e *rtpEncoderAV1) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpav1.Encoder)(e).Encode(payload.(unit.PayloadAV1)) +} + +type rtpEncoderVP9 rtpvp9.Encoder + +func (e *rtpEncoderVP9) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpvp9.Encoder)(e).Encode(payload.(unit.PayloadVP9)) +} + +type rtpEncoderVP8 rtpvp8.Encoder + +func (e *rtpEncoderVP8) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpvp8.Encoder)(e).Encode(payload.(unit.PayloadVP8)) +} + +type rtpEncoderMPEG4Video rtpfragmented.Encoder + +func (e *rtpEncoderMPEG4Video) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpfragmented.Encoder)(e).Encode(payload.(unit.PayloadMPEG4Video)) +} + +type rtpEncoderMPEG1Video rtpmpeg1video.Encoder + +func (e *rtpEncoderMPEG1Video) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpmpeg1video.Encoder)(e).Encode(payload.(unit.PayloadMPEG1Video)) +} + +type rtpEncoderMJPEG rtpmjpeg.Encoder + +func (e *rtpEncoderMJPEG) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpmjpeg.Encoder)(e).Encode(payload.(unit.PayloadMJPEG)) +} + +type rtpEncoderOpus rtpsimpleaudio.Encoder + +func (e *rtpEncoderOpus) encode(payload unit.Payload) ([]*rtp.Packet, error) { + pts := int64(0) + packets := make([]*rtp.Packet, len(payload.(unit.PayloadOpus))) + + for i, packet := range payload.(unit.PayloadOpus) { + pkt, err := (*rtpsimpleaudio.Encoder)(e).Encode(packet) + if err != nil { + return nil, err + } + + pkt.Timestamp += uint32(pts) + pts += mcopus.PacketDuration2(packet) + packets[i] = pkt + } + + return packets, nil +} + +type rtpEncoderMPEG4Audio rtpmpeg4audio.Encoder + +func (e *rtpEncoderMPEG4Audio) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpmpeg4audio.Encoder)(e).Encode(payload.(unit.PayloadMPEG4Audio)) +} + +type rtpEncoderMPEG4AudioLATM rtpfragmented.Encoder + +func (e *rtpEncoderMPEG4AudioLATM) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpfragmented.Encoder)(e).Encode(payload.(unit.PayloadMPEG4AudioLATM)) +} + +type rtpEncoderMPEG1Audio rtpmpeg1audio.Encoder + +func (e *rtpEncoderMPEG1Audio) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpmpeg1audio.Encoder)(e).Encode(payload.(unit.PayloadMPEG1Audio)) +} + +type rtpEncoderAC3 rtpac3.Encoder + +func (e *rtpEncoderAC3) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpac3.Encoder)(e).Encode(payload.(unit.PayloadAC3)) +} + +type rtpEncoderG711 rtplpcm.Encoder + +func (e *rtpEncoderG711) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtplpcm.Encoder)(e).Encode(payload.(unit.PayloadG711)) +} + +type rtpEncoderLPCM rtplpcm.Encoder + +func (e *rtpEncoderLPCM) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtplpcm.Encoder)(e).Encode(payload.(unit.PayloadLPCM)) +} + +type rtpEncoderKLV rtpklv.Encoder + +func (e *rtpEncoderKLV) encode(payload unit.Payload) ([]*rtp.Packet, error) { + return (*rtpklv.Encoder)(e).Encode(payload.(unit.PayloadKLV)) +} + +func newRTPEncoder( + forma format.Format, + rtpMaxPayloadSize int, + ssrc *uint32, + initialSequenceNumber *uint16, +) (rtpEncoder, error) { + switch forma := forma.(type) { + case *format.H265: + wrapped := &rtph265.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + MaxDONDiff: forma.MaxDONDiff, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderH265)(wrapped), nil + + case *format.H264: + wrapped := &rtph264.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + PacketizationMode: forma.PacketizationMode, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderH264)(wrapped), nil + + case *format.AV1: + wrapped := &rtpav1.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderAV1)(wrapped), nil + + case *format.VP9: + wrapped := &rtpvp9.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + InitialPictureID: ptrOf(uint16(0x35af)), + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderVP9)(wrapped), nil + + case *format.VP8: + wrapped := &rtpvp8.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderVP8)(wrapped), nil + + case *format.MPEG4Video: + wrapped := &rtpfragmented.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderMPEG4Video)(wrapped), nil + + case *format.MPEG1Video: + wrapped := &rtpmpeg1video.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderMPEG1Video)(wrapped), nil + + case *format.MJPEG: + wrapped := &rtpmjpeg.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderMJPEG)(wrapped), nil + + case *format.Opus: + wrapped := &rtpsimpleaudio.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderOpus)(wrapped), nil + + case *format.MPEG4Audio: + wrapped := &rtpmpeg4audio.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + SizeLength: forma.SizeLength, + IndexLength: forma.IndexLength, + IndexDeltaLength: forma.IndexDeltaLength, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderMPEG4Audio)(wrapped), nil + + case *format.MPEG4AudioLATM: + wrapped := &rtpfragmented.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderMPEG4AudioLATM)(wrapped), nil + + case *format.MPEG1Audio: + wrapped := &rtpmpeg1audio.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderMPEG1Audio)(wrapped), nil + + case *format.AC3: + wrapped := &rtpac3.Encoder{ + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderAC3)(wrapped), nil + + case *format.G711: + wrapped := &rtplpcm.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadType(), + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + BitDepth: 8, + ChannelCount: forma.ChannelCount, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderG711)(wrapped), nil + + case *format.LPCM: + wrapped := &rtplpcm.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + BitDepth: forma.BitDepth, + ChannelCount: forma.ChannelCount, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderLPCM)(wrapped), nil + + case *format.KLV: + wrapped := &rtpklv.Encoder{ + PayloadMaxSize: rtpMaxPayloadSize, + PayloadType: forma.PayloadTyp, + SSRC: ssrc, + InitialSequenceNumber: initialSequenceNumber, + } + err := wrapped.Init() + if err != nil { + return nil, err + } + + return (*rtpEncoderKLV)(wrapped), nil + + default: + return nil, nil + } +} diff --git a/internal/stream/stream.go b/internal/stream/stream.go index 805cd668..467d6c30 100644 --- a/internal/stream/stream.go +++ b/internal/stream/stream.go @@ -17,14 +17,14 @@ import ( ) // Stream is a media stream. -// It stores tracks, readers and allows to write data to readers, converting it when needed. +// It stores tracks, readers and allows to write data to readers, remuxing it when needed. type Stream struct { - WriteQueueSize int - RTPMaxPayloadSize int - Desc *description.Session - GenerateRTPPackets bool - FillNTP bool - Parent logger.Writer + Desc *description.Session + UseRTPPackets bool + WriteQueueSize int + RTPMaxPayloadSize int + ReplaceNTP bool + Parent logger.Writer bytesReceived *uint64 bytesSent *uint64 @@ -56,12 +56,15 @@ func (s *Stream) Initialize() error { for _, media := range s.Desc.Medias { s.medias[media] = &streamMedia{ - rtpMaxPayloadSize: s.RTPMaxPayloadSize, - media: media, - generateRTPPackets: s.GenerateRTPPackets, - fillNTP: s.FillNTP, - processingErrors: s.processingErrors, - parent: s.Parent, + media: media, + useRTPPackets: s.UseRTPPackets, + rtpMaxPayloadSize: s.RTPMaxPayloadSize, + replaceNTP: s.ReplaceNTP, + onBytesReceived: s.onBytesReceived, + onBytesSent: s.onBytesSent, + writeRTSP: s.writeRTSP, + processingErrors: s.processingErrors, + parent: s.Parent, } err := s.medias[media].initialize() if err != nil { @@ -193,22 +196,27 @@ func (s *Stream) WriteUnit(medi *description.Media, forma format.Format, u *unit s.mutex.RLock() defer s.mutex.RUnlock() - sf.writeUnit(s, medi, u) + sf.writeUnit(u) } -// WriteRTPPacket writes a RTP packet. -func (s *Stream) WriteRTPPacket( - medi *description.Media, - forma format.Format, - pkt *rtp.Packet, - ntp time.Time, - pts int64, -) { - sm := s.medias[medi] - sf := sm.formats[forma] - - s.mutex.RLock() - defer s.mutex.RUnlock() - - sf.writeRTPPacket(s, medi, pkt, ntp, pts) +func (s *Stream) onBytesReceived(v uint64) { + atomic.AddUint64(s.bytesReceived, v) +} + +func (s *Stream) onBytesSent(v uint64) { + atomic.AddUint64(s.bytesSent, v) +} + +func (s *Stream) writeRTSP(medi *description.Media, pkts []*rtp.Packet, ntp time.Time) { + if s.rtspStream != nil { + for _, pkt := range pkts { + s.rtspStream.WritePacketRTPWithNTP(medi, pkt, ntp) //nolint:errcheck + } + } + + if s.rtspsStream != nil { + for _, pkt := range pkts { + s.rtspsStream.WritePacketRTPWithNTP(medi, pkt, ntp) //nolint:errcheck + } + } } diff --git a/internal/stream/stream_format.go b/internal/stream/stream_format.go index b829201f..ff221976 100644 --- a/internal/stream/stream_format.go +++ b/internal/stream/stream_format.go @@ -1,14 +1,14 @@ package stream import ( - "sync/atomic" + "crypto/rand" + "fmt" "time" "github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/format" "github.com/pion/rtp" - "github.com/bluenviron/mediamtx/internal/codecprocessor" "github.com/bluenviron/mediamtx/internal/errordumper" "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/ntpestimator" @@ -23,98 +23,172 @@ func unitSize(u *unit.Unit) uint64 { return n } -type streamFormat struct { - rtpMaxPayloadSize int - format format.Format - generateRTPPackets bool - fillNTP bool - processingErrors *errordumper.Dumper - parent logger.Writer +func randUint32() (uint32, error) { + var b [4]byte + _, err := rand.Read(b[:]) + if err != nil { + return 0, err + } + return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]), nil +} - proc codecprocessor.Processor - ntpEstimator *ntpestimator.Estimator - onDatas map[*Reader]OnDataFunc +type streamFormat struct { + format format.Format + media *description.Media + useRTPPackets bool + rtpMaxPayloadSize int + replaceNTP bool + processingErrors *errordumper.Dumper + onBytesReceived func(uint64) + onBytesSent func(uint64) + writeRTSP func(*description.Media, []*rtp.Packet, time.Time) + parent logger.Writer + + rtpDecoder rtpDecoder + formatUpdater formatUpdater + unitRemuxer unitRemuxer + rtpEncoder rtpEncoder + ptsOffset uint32 + ntpEstimator *ntpestimator.Estimator + onDatas map[*Reader]OnDataFunc } func (sf *streamFormat) initialize() error { sf.onDatas = make(map[*Reader]OnDataFunc) - var err error - sf.proc, err = codecprocessor.New(sf.rtpMaxPayloadSize, sf.format, sf.generateRTPPackets, sf.parent) - if err != nil { - return err + if sf.useRTPPackets { + var err error + sf.rtpDecoder, err = newRTPDecoder(sf.format) + if err != nil { + return err + } } - sf.ntpEstimator = &ntpestimator.Estimator{ - ClockRate: sf.format.ClockRate(), + sf.formatUpdater = newFormatUpdater(sf.format) + sf.unitRemuxer = newUnitRemuxer(sf.format) + + if !sf.useRTPPackets { + var err error + sf.rtpEncoder, err = newRTPEncoder(sf.format, sf.rtpMaxPayloadSize, nil, nil) + if err != nil { + return err + } + + if sf.rtpEncoder == nil { + return fmt.Errorf("RTP encoder not available for format %T", sf.format) + } + + sf.ptsOffset, err = randUint32() + if err != nil { + return err + } + } + + if sf.replaceNTP { + sf.ntpEstimator = &ntpestimator.Estimator{ + ClockRate: sf.format.ClockRate(), + } } return nil } -func (sf *streamFormat) writeUnit(s *Stream, medi *description.Media, u *unit.Unit) { - err := sf.proc.ProcessUnit(u) +func (sf *streamFormat) writeUnit(u *unit.Unit) { + err := sf.writeUnitInner(u) if err != nil { sf.processingErrors.Add(err) return } - - sf.writeUnitInner(s, medi, u) } -func (sf *streamFormat) writeRTPPacket( - s *Stream, - medi *description.Media, - pkt *rtp.Packet, - ntp time.Time, - pts int64, -) { - hasNonRTSPReaders := len(sf.onDatas) > 0 +func (sf *streamFormat) writeUnitInner(u *unit.Unit) error { + if sf.useRTPPackets { + if len(u.RTPPackets) != 1 { + panic("should not happen") + } + if !u.NilPayload() { + panic("should not happen") + } - u := &unit.Unit{ - PTS: pts, - NTP: ntp, - RTPPackets: []*rtp.Packet{pkt}, + if sf.rtpDecoder != nil { + var err error + u.Payload, err = sf.rtpDecoder.decode(u.RTPPackets[0]) + if err != nil { + return err + } + } + + if sf.rtpEncoder == nil { + for _, pkt := range u.RTPPackets { + if len(pkt.Payload) > sf.rtpMaxPayloadSize { + var err error + sf.rtpEncoder, err = newRTPEncoder(sf.format, sf.rtpMaxPayloadSize, ptrOf(pkt.SSRC), ptrOf(pkt.SequenceNumber)) + if err != nil { + return err + } + + if sf.rtpEncoder == nil { + return fmt.Errorf("RTP payload size (%d) is greater than maximum allowed (%d)", + len(pkt.Payload), sf.rtpMaxPayloadSize) + } + + sf.ptsOffset = pkt.Timestamp - uint32(u.PTS) + + sf.parent.Log(logger.Info, "RTP packets are too big, remuxing them into smaller ones") + break + } + } + } + + if sf.rtpEncoder != nil { + u.RTPPackets = nil + } + } else { + if len(u.RTPPackets) != 0 { + panic("should not happen") + } + if u.NilPayload() { + panic("should not happen") + } } - err := sf.proc.ProcessRTPPacket(u, hasNonRTSPReaders) - if err != nil { - sf.processingErrors.Add(err) - return + if !u.NilPayload() { + sf.formatUpdater(sf.format, u.Payload) + + u.Payload = sf.unitRemuxer(sf.format, u.Payload) + + if sf.rtpEncoder != nil && !u.NilPayload() { + var err error + u.RTPPackets, err = sf.rtpEncoder.encode(u.Payload) + if err != nil { + return err + } + + for _, pkt := range u.RTPPackets { + pkt.Timestamp += sf.ptsOffset + uint32(u.PTS) + } + } } - sf.writeUnitInner(s, medi, u) -} - -func (sf *streamFormat) writeUnitInner(s *Stream, medi *description.Media, u *unit.Unit) { - if sf.fillNTP { + if sf.replaceNTP { u.NTP = sf.ntpEstimator.Estimate(u.PTS) } size := unitSize(u) + sf.onBytesReceived(size) - atomic.AddUint64(s.bytesReceived, size) - - if s.rtspStream != nil { - for _, pkt := range u.RTPPackets { - s.rtspStream.WritePacketRTPWithNTP(medi, pkt, u.NTP) //nolint:errcheck - } - } - - if s.rtspsStream != nil { - for _, pkt := range u.RTPPackets { - s.rtspsStream.WritePacketRTPWithNTP(medi, pkt, u.NTP) //nolint:errcheck - } - } + sf.writeRTSP(sf.media, u.RTPPackets, u.NTP) for sr, onData := range sf.onDatas { csr := sr cOnData := onData sr.push(func() error { if !csr.SkipBytesSent { - atomic.AddUint64(s.bytesSent, size) + sf.onBytesSent(size) } return cOnData(u) }) } + + return nil } diff --git a/internal/stream/stream_media.go b/internal/stream/stream_media.go index 0d9f43d1..c5933e96 100644 --- a/internal/stream/stream_media.go +++ b/internal/stream/stream_media.go @@ -1,19 +1,25 @@ package stream import ( + "time" + "github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/format" "github.com/bluenviron/mediamtx/internal/errordumper" "github.com/bluenviron/mediamtx/internal/logger" + "github.com/pion/rtp" ) type streamMedia struct { - rtpMaxPayloadSize int - media *description.Media - generateRTPPackets bool - fillNTP bool - processingErrors *errordumper.Dumper - parent logger.Writer + media *description.Media + useRTPPackets bool + rtpMaxPayloadSize int + replaceNTP bool + onBytesReceived func(uint64) + onBytesSent func(uint64) + writeRTSP func(*description.Media, []*rtp.Packet, time.Time) + processingErrors *errordumper.Dumper + parent logger.Writer formats map[format.Format]*streamFormat } @@ -23,12 +29,16 @@ func (sm *streamMedia) initialize() error { for _, forma := range sm.media.Formats { sf := &streamFormat{ - rtpMaxPayloadSize: sm.rtpMaxPayloadSize, - format: forma, - generateRTPPackets: sm.generateRTPPackets, - fillNTP: sm.fillNTP, - processingErrors: sm.processingErrors, - parent: sm.parent, + format: forma, + media: sm.media, + useRTPPackets: sm.useRTPPackets, + rtpMaxPayloadSize: sm.rtpMaxPayloadSize, + replaceNTP: sm.replaceNTP, + processingErrors: sm.processingErrors, + onBytesReceived: sm.onBytesReceived, + onBytesSent: sm.onBytesSent, + writeRTSP: sm.writeRTSP, + parent: sm.parent, } err := sf.initialize() if err != nil { diff --git a/internal/stream/stream_test.go b/internal/stream/stream_test.go index 255e2fd2..559a13d3 100644 --- a/internal/stream/stream_test.go +++ b/internal/stream/stream_test.go @@ -5,10 +5,17 @@ import ( "github.com/bluenviron/gortsplib/v5/pkg/description" "github.com/bluenviron/gortsplib/v5/pkg/format" + "github.com/bluenviron/mediamtx/internal/logger" "github.com/bluenviron/mediamtx/internal/unit" + "github.com/pion/rtp" "github.com/stretchr/testify/require" ) +type nilLogger struct{} + +func (nilLogger) Log(logger.Level, string, ...any) { +} + func TestStream(t *testing.T) { desc := &description.Session{Medias: []*description.Media{ { @@ -22,10 +29,10 @@ func TestStream(t *testing.T) { }} strm := &Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, } err := strm.Initialize() require.NoError(t, err) @@ -69,10 +76,9 @@ func TestStreamSkipBytesSent(t *testing.T) { }} strm := &Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: desc, - GenerateRTPPackets: true, + Desc: desc, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, } err := strm.Initialize() require.NoError(t, err) @@ -104,3 +110,1169 @@ func TestStreamSkipBytesSent(t *testing.T) { require.Equal(t, uint64(14), strm.BytesReceived()) require.Equal(t, uint64(0), strm.BytesSent()) } + +func TestStreamResizeOversizedRTPPackets(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{ + { + Type: description.MediaTypeVideo, + Formats: []format.Format{&format.H264{ + SPS: []byte{ // 1920x1080 baseline + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, + 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + }, + PPS: []byte{0x08, 0x06, 0x07, 0x08}, + }}, + }, + }} + + strm := &Stream{ + Desc: desc, + UseRTPPackets: true, + WriteQueueSize: 512, + RTPMaxPayloadSize: 400, + Parent: &nilLogger{}, + } + err := strm.Initialize() + require.NoError(t, err) + defer strm.Close() + + r := &Reader{} + + recv := make(chan *unit.Unit) + n := 0 + + r.OnData(desc.Medias[0], desc.Medias[0].Formats[0], func(u *unit.Unit) error { + switch n { + case 0: + case 1: + recv <- u + default: + t.Error("should not happen") + } + n++ + return nil + }) + + strm.AddReader(r) + defer strm.RemoveReader(r) + + strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.Unit{ + PTS: 90000, + RTPPackets: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 122, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{1, 2, 3, 4}, + }, + }, + }) + + oversizedPayload := make([]byte, 1000) + for i := range oversizedPayload { + oversizedPayload[i] = byte(i % 256) + } + + strm.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.Unit{ + PTS: 90000, + RTPPackets: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: oversizedPayload, + }, + }, + }) + + received := <-recv + + require.Equal(t, 3, len(received.RTPPackets)) + + for i, pkt := range received.RTPPackets { + require.Equal(t, 123+uint16(i), pkt.SequenceNumber) + require.Equal(t, uint32(45343), pkt.Timestamp) + } + + totalPayloadSize := 0 + for _, pkt := range received.RTPPackets { + require.LessOrEqual(t, len(pkt.Payload), 400) + totalPayloadSize += len(pkt.Payload) + } + + require.Equal(t, 1005, totalPayloadSize) +} + +func TestStreamUpdateFormatParams(t *testing.T) { + for _, ca := range []string{"h264", "h265", "mpeg4video"} { + t.Run(ca, func(t *testing.T) { + var desc *description.Session + var media *description.Media + var forma format.Format + var u *unit.Unit + + switch ca { + case "h264": + sps := []byte{ + 0x67, 0x64, 0x00, 0x20, 0xac, 0xd9, 0x40, 0x78, + 0x02, 0x27, 0xe5, 0x9a, 0x80, 0x80, 0x80, 0xa0, + } + pps := []byte{0x08, 0x07, 0x08, 0x09} + + formatH264 := &format.H264{ + SPS: []byte{0x67, 0x42, 0xc0, 0x28}, + PPS: []byte{0x08, 0x06}, + } + + desc = &description.Session{Medias: []*description.Media{{ + Type: description.MediaTypeVideo, + Formats: []format.Format{formatH264}, + }}} + media = desc.Medias[0] + forma = formatH264 + + u = &unit.Unit{ + PTS: 90000, + Payload: unit.PayloadH264{ + sps, // New SPS + pps, // New PPS + {5, 1}, // IDR + }, + } + + case "h265": + vps := []byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xfe} + sps := []byte{0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x04} + pps := []byte{0x44, 0x01, 0xc1, 0x73, 0xd1, 0x8a} + + formatH265 := &format.H265{ + VPS: []byte{0x40, 0x01, 0x0c}, + SPS: []byte{0x42, 0x01, 0x01}, + PPS: []byte{0x44, 0x01, 0xc1}, + } + + desc = &description.Session{Medias: []*description.Media{{ + Type: description.MediaTypeVideo, + Formats: []format.Format{formatH265}, + }}} + media = desc.Medias[0] + forma = formatH265 + + u = &unit.Unit{ + PTS: 90000, + Payload: unit.PayloadH265{ + vps, // New VPS + sps, // New SPS + pps, // New PPS + {0x26, 0x01, 0x01, 0x02}, // IDR + }, + } + + case "mpeg4video": + config := []byte{ + 0x00, 0x00, 0x01, 0xb0, // Visual Object Sequence Start + 0x02, 0x00, 0x00, 0x01, 0xb5, 0x8a, + 0x14, 0x00, 0x00, 0x01, 0x00, + } + + formatMPEG4Video := &format.MPEG4Video{ + Config: []byte{0x00, 0x00, 0x01, 0xb0, 0x01}, + } + + desc = &description.Session{Medias: []*description.Media{{ + Type: description.MediaTypeVideo, + Formats: []format.Format{formatMPEG4Video}, + }}} + media = desc.Medias[0] + forma = formatMPEG4Video + + frame := make([]byte, 0, len(config)+20) + frame = append(frame, config...) + frame = append(frame, []byte{0x00, 0x00, 0x01, 0xb3}...) // Group of VOP + frame = append(frame, []byte{0x01, 0x02, 0x03, 0x04}...) + + u = &unit.Unit{ + PTS: 90000, + Payload: unit.PayloadMPEG4Video(frame), + } + } + + strm := &Stream{ + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + } + err := strm.Initialize() + require.NoError(t, err) + defer strm.Close() + + r := &Reader{} + recv := make(chan struct{}) + + r.OnData(media, forma, func(_ *unit.Unit) error { + close(recv) + return nil + }) + + strm.AddReader(r) + defer strm.RemoveReader(r) + + strm.WriteUnit(media, forma, u) + <-recv + + // Verify that format parameters were updated + switch ca { + case "h264": + formatH264 := forma.(*format.H264) + require.Equal(t, []byte{ + 0x67, 0x64, 0x00, 0x20, 0xac, 0xd9, 0x40, 0x78, + 0x02, 0x27, 0xe5, 0x9a, 0x80, 0x80, 0x80, 0xa0, + }, formatH264.SPS) + require.Equal(t, []byte{0x08, 0x07, 0x08, 0x09}, formatH264.PPS) + + case "h265": + formatH265 := forma.(*format.H265) + require.Equal(t, []byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xfe}, formatH265.VPS) + require.Equal(t, []byte{0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x04}, formatH265.SPS) + require.Equal(t, []byte{0x44, 0x01, 0xc1, 0x73, 0xd1, 0x8a}, formatH265.PPS) + + case "mpeg4video": + formatMPEG4Video := forma.(*format.MPEG4Video) + require.Equal(t, []byte{ + 0x00, 0x00, 0x01, 0xb0, + 0x02, 0x00, 0x00, 0x01, 0xb5, 0x8a, + 0x14, 0x00, 0x00, 0x01, 0x00, + }, formatMPEG4Video.Config) + } + }) + } +} + +var casesDecodeEncode = []struct { + name string + format format.Format + encoded []*rtp.Packet + decoded unit.Payload +}{ + { + name: "av1", + format: &format.AV1{ + PayloadTyp: 96, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + 0x10, + 0x02, // Size = 2 + 0x01, 0x02, // OBU data + }, + }, + }, + decoded: unit.PayloadAV1{ + {0x02, 0x01, 0x02}, // Size byte included with OBU data + }, + }, + { + name: "vp9", + format: &format.VP9{ + PayloadTyp: 96, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + 0x8f, 0xb5, 0xaf, 0x18, 0x07, 0x80, 0x03, 0x24, + 0x01, 0x14, 0x01, 0x82, 0x49, 0x83, 0x42, 0x00, + 0x77, 0xf0, 0x32, 0x34, + }, + }, + }, + decoded: unit.PayloadVP9{0x82, 0x49, 0x83, 0x42, 0x0, 0x77, 0xf0, 0x32, 0x34}, + }, + { + name: "vp8", + format: &format.VP8{ + PayloadTyp: 96, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + 0x10, // X=0, R=0, N=0, S=1, PartID=0 + 0x01, 0x02, 0x03, 0x04, + }, + }, + }, + decoded: unit.PayloadVP8{0x01, 0x02, 0x03, 0x04}, + }, + { + name: "h265", + format: &format.H265{ + PayloadTyp: 96, + VPS: []byte{0x40, 0x01, 0x0c, 0x01, 0xff, 0xfe}, + SPS: []byte{0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x04}, + PPS: []byte{0x44, 0x01, 0xc1, 0x73, 0xd1, 0x8a}, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + 0x60, 0x01, 0x00, 0x06, 0x40, 0x01, 0x0c, 0x01, + 0xff, 0xfe, 0x00, 0x08, 0x42, 0x01, 0x01, 0x01, + 0x60, 0x00, 0x00, 0x04, 0x00, 0x06, 0x44, 0x01, + 0xc1, 0x73, 0xd1, 0x8a, 0x00, 0x05, 0x26, 0x01, + 0x01, 0x02, 0x03, + }, + }, + }, + decoded: unit.PayloadH265{ + {0x40, 0x01, 0x0c, 0x01, 0xff, 0xfe}, // VPS + {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x04}, // SPS + {0x44, 0x01, 0xc1, 0x73, 0xd1, 0x8a}, // PPS + {0x26, 0x01, 0x01, 0x02, 0x03}, // IDR + }, + }, + { + name: "h264", + format: &format.H264{ + PayloadTyp: 96, + SPS: []byte{ + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, + 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + }, + PPS: []byte{0x08, 0x06, 0x07, 0x08}, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + 0x18, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9, + 0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, + 0x3c, 0x60, 0xc9, 0x20, 0x00, 0x04, 0x08, 0x06, + 0x07, 0x08, 0x00, 0x05, 0x05, 0x01, 0x02, 0x03, + 0x04, + }, + }, + }, + decoded: unit.PayloadH264{ + { + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, + 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + }, // SPS + {0x08, 0x06, 0x07, 0x08}, // PPS + {0x05, 0x01, 0x02, 0x03, 0x04}, // IDR + }, + }, + { + name: "mpeg4video", + format: &format.MPEG4Video{ + PayloadTyp: 96, + Config: []byte{0x00, 0x00, 0x01, 0xb0, 0x01}, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{0x00, 0x01, 0x02, 0x03, 0x04}, + }, + }, + decoded: unit.PayloadMPEG4Video{0x00, 0x01, 0x02, 0x03, 0x04}, + }, + { + name: "mpeg1video", + format: &format.MPEG1Video{}, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, // Marker indicates complete frame + PayloadType: 32, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + // MPEG-1 Video RTP header (4 bytes) + 0x00, // MBZ=0, T=0 (MPEG-1) + 0x00, // TR (temporal reference) - low 8 bits + 0x18, // AN=0, N=0, S=0 (no sequence header), B=1, E=1 (complete slice), FBV=0, BFC=0, FFV=0, FFC=0 + 0x00, // FFC (continued) + // MPEG-1 Video data (slice or frame data) + 0x00, 0x00, 0x01, 0x01, // Slice start code + 0x01, 0x02, 0x03, 0x04, // Slice data + }, + }, + }, + decoded: unit.PayloadMPEG1Video{ + // Only the video data after the 4-byte RTP header + 0x00, 0x00, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x04, + }, + }, + { + name: "mjpeg", + format: &format.MJPEG{}, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + PayloadType: 26, + }, + Payload: []byte{ + 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf0, 0x87, + 0x00, 0x00, 0x00, 0x80, 0x0d, 0x09, 0x0a, 0x0b, + 0x0a, 0x08, 0x0d, 0x0b, 0x0a, 0x0b, 0x0e, 0x0e, + 0x0d, 0x0f, 0x13, 0x20, 0x15, 0x13, 0x12, 0x12, + 0x13, 0x27, 0x1c, 0x1e, 0x17, 0x20, 0x2e, 0x29, + 0x31, 0x30, 0x2e, 0x29, 0x2d, 0x2c, 0x33, 0x3a, + 0x4a, 0x3e, 0x33, 0x36, 0x46, 0x37, 0x2c, 0x2d, + 0x40, 0x57, 0x41, 0x46, 0x4c, 0x4e, 0x52, 0x53, + 0x52, 0x32, 0x3e, 0x5a, 0x61, 0x5a, 0x50, 0x60, + 0x4a, 0x51, 0x52, 0x4f, 0x0e, 0x0e, 0x0e, 0x13, + 0x11, 0x13, 0x26, 0x15, 0x15, 0x26, 0x4f, 0x35, + 0x2d, 0x35, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x92, 0x8a, 0x28, 0xaf, + 0x54, 0xf2, 0x42, 0x8a, 0x28, 0xa0, 0x02, 0x96, + 0x92, 0x96, 0x80, 0x0a, 0x4a, 0x75, 0x25, 0x02, + 0x12, 0x8a, 0x5a, 0x28, 0x18, 0x94, 0x52, 0xd1, + 0x40, 0x09, 0x45, 0x2d, 0x14, 0x08, 0x29, 0x69, + 0x29, 0x68, 0x00, 0xa5, 0xa4, 0xa5, 0xa0, 0x02, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x04, + 0xa5, 0xa2, 0x8a, 0x00, 0x5a, 0x28, 0xa2, 0x80, + 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, + 0x12, 0x8a, 0x5a, 0x28, 0x24, 0x29, 0x69, 0x29, + 0x68, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x8a, + 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa4, 0xa5, + 0xa4, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a, + 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a, + 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a, + 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a, + 0x28, 0xa0, 0x02, 0x96, 0x92, 0x96, 0x80, 0x0a, + 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a, + 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a, + 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a, + 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x0a, + 0x28, 0xa2, 0x81, 0x85, 0x14, 0x51, 0x40, 0x05, + 0x14, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40, 0x05, + 0x14, 0x52, 0xd0, 0x01, 0x45, 0x14, 0x50, 0x01, + 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, + 0x45, 0x2d, 0x14, 0x00, 0x94, 0xb4, 0x51, 0x40, + 0x05, 0x14, 0x52, 0xd0, 0x02, 0x51, 0x4b, 0x45, + 0x00, 0x25, 0x2d, 0x14, 0x50, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x20, 0xa5, 0xa4, + 0xa5, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, 0x8a, + 0x28, 0xa0, 0x02, 0x8a, 0x5a, 0x28, 0x18, 0x94, + 0xb4, 0x51, 0x40, 0xc2, 0x8a, 0x28, 0xa0, 0x05, + 0xa2, 0x92, 0x9d, 0x40, 0x05, 0x14, 0x51, 0x48, + 0x02, 0x8a, 0x28, 0xa4, 0x01, 0x4b, 0x49, 0x4b, + 0x40, 0x05, 0x14, 0x51, 0x40, 0x05, 0x14, 0xb4, + 0x50, 0x02, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x2d, + 0x14, 0x50, 0x03, 0xa8, 0xa2, 0x8a, 0x00, 0x28, + 0xa2, 0x8a, 0x00, 0x5a, 0x29, 0x29, 0x68, 0x00, + 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x96, 0x8a, 0x06, + 0x25, 0x14, 0xb4, 0x50, 0x01, 0x45, 0x14, 0x50, + 0x02, 0xd2, 0xd2, 0x52, 0xd0, 0x20, 0xa2, 0x8a, + 0x28, 0x01, 0x68, 0xa2, 0x8a, 0x40, 0x14, 0x51, + 0x45, 0x30, 0x0a, 0x5a, 0x4a, 0x5a, 0x06, 0x14, + 0x51, 0x45, 0x02, 0x0a, 0x28, 0xa5, 0xa0, 0x62, + 0x51, 0x4b, 0x45, 0x00, 0x2d, 0x14, 0x51, 0x48, + 0x02, 0x8a, 0x28, 0xa0, 0x61, 0x45, 0x14, 0x50, + 0x03, 0xa8, 0xa2, 0x8a, 0x06, 0x2d, 0x14, 0x51, + 0x48, 0x02, 0x8a, 0x28, 0xa4, 0x30, 0xa2, 0x8a, + 0x2a, 0x80, 0x28, 0xa2, 0x8a, 0x00, 0x28, 0xa2, + 0x8a, 0x92, 0x45, 0xa5, 0xa2, 0x96, 0x82, 0x82, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x05, + 0xa2, 0x8a, 0x29, 0x80, 0x52, 0xd2, 0x52, 0xd0, + 0x01, 0x45, 0x14, 0x50, 0x01, 0x4e, 0xa2, 0x8a, + 0x43, 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa4, + 0xa4, 0x31, 0x68, 0xa4, 0xf3, 0x62, 0xff, 0x00, + 0x9e, 0xd1, 0x7e, 0xea, 0x9f, 0xfe, 0xb6, 0xa4, + 0x62, 0x52, 0x53, 0xa9, 0x28, 0x01, 0x28, 0xa2, + 0x6f, 0xdd, 0x7f, 0xaf, 0xa5, 0xa0, 0x62, 0x51, + 0x4b, 0x45, 0x00, 0x25, 0x14, 0xbf, 0xba, 0xff, + 0x00, 0xae, 0xb4, 0xea, 0x60, 0x36, 0x9d, 0x49, + 0x49, 0x34, 0xb1, 0x45, 0xfe, 0xbe, 0x6f, 0x2a, + 0x98, 0x0f, 0xa2, 0xb9, 0xbd, 0x0f, 0x59, 0x97, + 0x54, 0xf1, 0x2d, 0xd7, 0xfc, 0xf9, 0xd7, 0x49, + 0x52, 0x30, 0xac, 0x7d, 0x5b, 0x54, 0xd5, 0x74, + 0xbf, 0xdf, 0x41, 0x67, 0x15, 0xd4, 0x35, 0x63, + 0x56, 0xd5, 0x22, 0xd2, 0xe0, 0xf3, 0xbc, 0x99, + 0x65, 0xae, 0x4b, 0x51, 0xf1, 0x44, 0x52, 0xff, + 0x00, 0xc7, 0x97, 0x9b, 0xe4, 0xd0, 0x69, 0x4c, + 0x76, 0xa3, 0xe2, 0xd9, 0xa5, 0x82, 0x29, 0xb4, + 0xbf, 0xdd, 0x79, 0xbf, 0xeb, 0x22, 0xaa, 0x7e, + 0x1e, 0xd5, 0x3e, 0xc1, 0x3f, 0xfa, 0x9f, 0xfa, + 0xe9, 0x25, 0x61, 0xd6, 0x9e, 0x87, 0xf6, 0xbf, + 0xed, 0x68, 0xbc, 0x8a, 0x82, 0xcf, 0x4e, 0x86, + 0x5f, 0x36, 0x0a, 0x92, 0x9b, 0xff, 0x00, 0x5d, + 0xeb, 0x3a, 0x6d, 0x53, 0xfd, 0x3f, 0xec, 0x76, + 0x3f, 0xbd, 0xff, 0x00, 0xa6, 0xbf, 0xc1, 0x54, + 0x66, 0x69, 0x51, 0x50, 0x4b, 0x2c, 0x51, 0x7e, + 0xe7, 0xce, 0xf3, 0x66, 0xff, 0x00, 0xa6, 0x75, + 0x35, 0x00, 0x2d, 0x65, 0x6a, 0xde, 0x23, 0xd3, + 0xec, 0x3f, 0xe5, 0xb7, 0x9b, 0x34, 0x5f, 0xf2, + 0xce, 0xb4, 0x7e, 0xd5, 0x69, 0x2f, 0xfc, 0xbe, + 0x45, 0x5e, 0x7b, 0xe2, 0x7d, 0x07, 0xec, 0x1e, + 0x6e, 0xa5, 0xf6, 0xc8, 0xa5, 0xf3, 0x6a, 0xc0, + 0xdf, 0xa2, 0x9d, 0x45, 0x6c, 0x72, 0x0d, 0xa5, + 0xa5, 0xa2, 0x80, 0x12, 0x96, 0x8a, 0x28, 0x10, + 0x51, 0x4b, 0x45, 0x00, 0x25, 0x14, 0xb4, 0x50, + 0x02, 0x51, 0x4b, 0x45, 0x03, 0x12, 0x8a, 0x5a, + 0x28, 0x10, 0x94, 0xb4, 0x51, 0x40, 0x05, 0x14, + 0x51, 0x40, 0x05, 0x14, 0xb4, 0x50, 0x02, 0x51, + 0x4b, 0x45, 0x00, 0x14, 0x51, 0x4b, 0x40, 0x84, + 0xa2, 0x8a, 0x28, 0x00, 0xa2, 0x96, 0x8a, 0x00, + 0x4a, 0x29, 0x68, 0xa0, 0x04, 0xa2, 0x96, 0x8a, + 0x00, 0x4a, 0x29, 0x68, 0xa0, 0x41, 0x45, 0x14, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x49, 0x4b, 0x45, 0x00, 0x25, 0x14, + 0xb4, 0x50, 0x01, 0x45, 0x14, 0x50, 0x30, 0xa4, + 0xa5, 0xa2, 0x81, 0x09, 0x45, 0x2d, 0x14, 0x00, + 0x94, 0x52, 0xd1, 0x40, 0x09, 0x45, 0x2d, 0x14, + 0x00, 0x94, 0x52, 0xd1, 0x40, 0x05, 0x14, 0xb4, + 0x94, 0x0c, 0x28, 0xa2, 0x8a, 0x04, 0x14, 0x51, + 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, + 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x52, + 0xd1, 0x40, 0xc4, 0xa2, 0x96, 0x8a, 0x04, 0x25, + 0x14, 0xb4, 0x50, 0x31, 0x28, 0xa5, 0xa2, 0x80, + 0x12, 0x8a, 0x5a, 0x4a, 0x00, 0x5a, 0x28, 0xa2, + 0x80, 0x0a, 0x29, 0x68, 0xa0, 0x04, 0xa2, 0x96, + 0x8a, 0x00, 0x4a, 0x29, 0x68, 0xa0, 0x04, 0xa2, + 0x9d, 0x45, 0x03, 0x1b, 0x45, 0x3a, 0x8a, 0x00, + 0x6d, 0x14, 0xea, 0x28, 0x10, 0x94, 0x52, 0xd1, + 0x40, 0x09, 0x4b, 0x45, 0x14, 0x08, 0x28, 0xa2, + 0x8a, 0x06, 0x14, 0x52, 0xd1, 0x40, 0x09, 0x45, + 0x2d, 0x2d, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x21, + 0x28, 0xa5, 0xa2, 0x81, 0x89, 0x4b, 0x45, 0x14, + 0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x4b, 0x45, + 0x00, 0x25, 0x14, 0xb4, 0x50, 0x01, 0x45, 0x2d, + 0x14, 0x0c, 0x28, 0xa2, 0x8a, 0x00, 0x4a, 0x5a, + 0x5a, 0x28, 0x01, 0x28, 0xa5, 0xa2, 0x90, 0x05, + 0x14, 0x51, 0x40, 0x05, 0x14, 0xb4, 0x52, 0x01, + 0x28, 0xa5, 0xa2, 0x80, 0x0a, 0x28, 0xa7, 0x50, + 0x03, 0x68, 0xa7, 0x51, 0x40, 0x0d, 0xa7, 0x51, + 0x45, 0x00, 0x14, 0x51, 0x4b, 0x40, 0x09, 0x45, + 0x2d, 0x2d, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x02, + 0x51, 0x4b, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, + 0x14, 0x51, 0x45, 0x00, 0x14, 0xb4, 0x53, 0xa8, + 0x18, 0xda, 0x5a, 0x5a, 0x28, 0x10, 0x94, 0xb4, + 0x51, 0x40, 0xc2, 0x8a, 0x5a, 0x29, 0x00, 0x94, + 0x52, 0xd1, 0x40, 0x05, 0x14, 0x51, 0x4c, 0x02, + 0x96, 0x8a, 0x29, 0x00, 0x52, 0xd2, 0x52, 0xd0, + 0x01, 0x45, 0x14, 0x50, 0x02, 0xd1, 0x45, 0x14, + 0x0c, 0x28, 0xa2, 0x96, 0x80, 0x0a, 0x28, 0xa2, + 0x81, 0x85, 0x2d, 0x14, 0x50, 0x01, 0x45, 0x14, + 0xb4, 0x80, 0x4a, 0x29, 0x68, 0xa0, 0x62, 0xd1, + 0x45, 0x14, 0xc0, 0x28, 0xa5, 0xa2, 0x90, 0x0d, + 0xa5, 0xa5, 0xa2, 0x80, 0x0a, 0x5a, 0x28, 0xa4, + 0x01, 0x45, 0x14, 0xb4, 0x00, 0x94, 0x52, 0xd1, + 0x40, 0x05, 0x14, 0x51, 0x4c, 0x02, 0x8a, 0x29, + 0x68, 0x01, 0x28, 0xa5, 0xa2, 0x80, 0x16, 0x8a, + 0x28, 0xa4, 0x30, 0xa7, 0x55, 0x7b, 0xbb, 0x5f, + 0xb7, 0xc1, 0xe4, 0xff, 0x00, 0xaa, 0xac, 0x49, + 0xb4, 0x6d, 0x57, 0x4b, 0xf3, 0x66, 0xd2, 0xef, + 0x3c, 0xd8, 0x69, 0x17, 0x4c, 0xe9, 0x2b, 0x07, + 0xc4, 0x32, 0xcb, 0x75, 0xa4, 0xff, 0x00, 0xc4, + 0xae, 0x68, 0xbf, 0x75, 0xfe, 0xb2, 0xb9, 0x09, + 0xb5, 0x9d, 0x42, 0x5f, 0x36, 0xcf, 0xce, 0xff, + 0x00, 0x5b, 0x56, 0x3c, 0x3d, 0x6b, 0xfd, 0xa9, + 0xab, 0x4b, 0x67, 0x3f, 0xfc, 0xf3, 0xff, 0x00, + 0x96, 0x74, 0x8d, 0xbd, 0x99, 0x9b, 0x0d, 0xfc, + 0xb2, 0xf9, 0xbf, 0xeb, 0x7f, 0x7b, 0x5a, 0xf0, + 0xf8, 0x8e, 0xee, 0x2d, 0x27, 0xec, 0x70, 0x7f, + 0xdf, 0xca, 0x66, 0xa3, 0xe1, 0x7f, 0xb0, 0x79, + 0xbe, 0x46, 0xa5, 0x17, 0xfd, 0x73, 0x92, 0xb0, + 0xe1, 0xa8, 0x0f, 0x66, 0x76, 0x90, 0xf8, 0xa2, + 0x29, 0x6c, 0x3c, 0x9f, 0xde, 0xf9, 0xde, 0x5f, + 0xef, 0x24, 0xaa, 0x76, 0x9e, 0x2d, 0xbb, 0x8b, + 0x49, 0xf2, 0x7f, 0xe5, 0xb5, 0x62, 0x5a, 0x5f, + 0xcb, 0x6b, 0xfe, 0xa3, 0xfe, 0x5a, 0xd1, 0xe5, + 0x45, 0xff, + }, + }, + { + Header: rtp.Header{ + Version: 2, + PayloadType: 26, + Marker: true, + }, + Payload: []byte{ + 0x00, 0x00, 0x05, 0x1e, 0x01, 0xff, 0xf0, 0x87, + 0x00, 0x2c, 0x29, 0x0f, 0xd9, 0x97, 0xff, 0x00, + 0xb6, 0x65, 0xba, 0x82, 0x5f, 0xb7, 0x4d, 0xe6, + 0xcd, 0xff, 0x00, 0x2c, 0xe2, 0xae, 0xa7, 0x49, + 0xd5, 0x3f, 0xe2, 0x9a, 0xf3, 0xa7, 0xff, 0x00, + 0x5d, 0x15, 0x70, 0xb4, 0x43, 0xfb, 0xdf, 0xf5, + 0xf3, 0x4b, 0xff, 0x00, 0x5c, 0xe8, 0xf6, 0x83, + 0xf6, 0x67, 0xa1, 0x68, 0x7a, 0xcc, 0x5a, 0xcf, + 0xfd, 0x32, 0x9b, 0xfe, 0x79, 0xd5, 0xeb, 0xbb, + 0xab, 0x4b, 0x08, 0x3f, 0xd3, 0xab, 0xcd, 0x66, + 0xba, 0xfb, 0x57, 0xfd, 0x32, 0x9a, 0x2a, 0x96, + 0xd2, 0xff, 0x00, 0xcd, 0xbf, 0xf3, 0xb5, 0x49, + 0xbc, 0xdf, 0x2b, 0xfe, 0x59, 0xd3, 0x27, 0xd9, + 0x9a, 0x30, 0xdd, 0x7d, 0xab, 0x5e, 0xfb, 0x1d, + 0x8c, 0xde, 0x55, 0x9c, 0xb2, 0x79, 0x95, 0xd3, + 0x6a, 0x3a, 0xa7, 0xd9, 0x75, 0xdb, 0x5b, 0x39, + 0xff, 0x00, 0xd4, 0xcb, 0xff, 0x00, 0x2d, 0x2b, + 0xcf, 0xbe, 0xdf, 0xe5, 0x6a, 0xdf, 0x6c, 0x83, + 0xfe, 0x59, 0x54, 0xda, 0xb6, 0xb3, 0x2e, 0xb3, + 0x7f, 0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9, + }, + }, + }, + decoded: unit.PayloadMJPEG{ + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0d, + 0x09, 0x0a, 0x0b, 0x0a, 0x08, 0x0d, 0x0b, 0x0a, + 0x0b, 0x0e, 0x0e, 0x0d, 0x0f, 0x13, 0x20, 0x15, + 0x13, 0x12, 0x12, 0x13, 0x27, 0x1c, 0x1e, 0x17, + 0x20, 0x2e, 0x29, 0x31, 0x30, 0x2e, 0x29, 0x2d, + 0x2c, 0x33, 0x3a, 0x4a, 0x3e, 0x33, 0x36, 0x46, + 0x37, 0x2c, 0x2d, 0x40, 0x57, 0x41, 0x46, 0x4c, + 0x4e, 0x52, 0x53, 0x52, 0x32, 0x3e, 0x5a, 0x61, + 0x5a, 0x50, 0x60, 0x4a, 0x51, 0x52, 0x4f, 0x01, + 0x0e, 0x0e, 0x0e, 0x13, 0x11, 0x13, 0x26, 0x15, + 0x15, 0x26, 0x4f, 0x35, 0x2d, 0x35, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0xff, 0xc0, 0x00, 0x11, 0x08, 0x04, 0x38, 0x07, + 0x80, 0x03, 0x00, 0x22, 0x00, 0x01, 0x11, 0x01, + 0x02, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x1f, 0x00, + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, + 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, + 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, + 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, + 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, + 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, + 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, + 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, + 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, + 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, 0x1f, 0x01, + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, + 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, + 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, + 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, + 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, + 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, + 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, + 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, + 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, + 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, + 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, + 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, + 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, + 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x0c, 0x03, + 0x00, 0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3f, + 0x00, 0x92, 0x8a, 0x28, 0xaf, 0x54, 0xf2, 0x42, + 0x8a, 0x28, 0xa0, 0x02, 0x96, 0x92, 0x96, 0x80, + 0x0a, 0x4a, 0x75, 0x25, 0x02, 0x12, 0x8a, 0x5a, + 0x28, 0x18, 0x94, 0x52, 0xd1, 0x40, 0x09, 0x45, + 0x2d, 0x14, 0x08, 0x29, 0x69, 0x29, 0x68, 0x00, + 0xa5, 0xa4, 0xa5, 0xa0, 0x02, 0x8a, 0x28, 0xa0, + 0x02, 0x8a, 0x28, 0xa0, 0x04, 0xa5, 0xa2, 0x8a, + 0x00, 0x5a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, + 0x80, 0x0a, 0x28, 0xa2, 0x80, 0x12, 0x8a, 0x5a, + 0x28, 0x24, 0x29, 0x69, 0x29, 0x68, 0x00, 0xa2, + 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, + 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, + 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, + 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, + 0x8a, 0x28, 0x00, 0xa2, 0x8a, 0x28, 0x00, 0xa2, + 0x8a, 0x28, 0x00, 0xa4, 0xa5, 0xa4, 0xa0, 0x02, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, + 0x96, 0x92, 0x96, 0x80, 0x0a, 0x28, 0xa2, 0x80, + 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, + 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, + 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x80, + 0x0a, 0x28, 0xa2, 0x80, 0x0a, 0x28, 0xa2, 0x81, + 0x85, 0x14, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40, + 0x05, 0x14, 0x51, 0x40, 0x05, 0x14, 0x52, 0xd0, + 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, + 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, 0x2d, 0x14, + 0x00, 0x94, 0xb4, 0x51, 0x40, 0x05, 0x14, 0x52, + 0xd0, 0x02, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x2d, + 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, + 0x14, 0x50, 0x20, 0xa5, 0xa4, 0xa5, 0xa0, 0x02, + 0x8a, 0x28, 0xa0, 0x02, 0x8a, 0x28, 0xa0, 0x02, + 0x8a, 0x5a, 0x28, 0x18, 0x94, 0xb4, 0x51, 0x40, + 0xc2, 0x8a, 0x28, 0xa0, 0x05, 0xa2, 0x92, 0x9d, + 0x40, 0x05, 0x14, 0x51, 0x48, 0x02, 0x8a, 0x28, + 0xa4, 0x01, 0x4b, 0x49, 0x4b, 0x40, 0x05, 0x14, + 0x51, 0x40, 0x05, 0x14, 0xb4, 0x50, 0x02, 0x51, + 0x4b, 0x45, 0x00, 0x25, 0x2d, 0x14, 0x50, 0x03, + 0xa8, 0xa2, 0x8a, 0x00, 0x28, 0xa2, 0x8a, 0x00, + 0x5a, 0x29, 0x29, 0x68, 0x00, 0xa2, 0x8a, 0x28, + 0x00, 0xa2, 0x96, 0x8a, 0x06, 0x25, 0x14, 0xb4, + 0x50, 0x01, 0x45, 0x14, 0x50, 0x02, 0xd2, 0xd2, + 0x52, 0xd0, 0x20, 0xa2, 0x8a, 0x28, 0x01, 0x68, + 0xa2, 0x8a, 0x40, 0x14, 0x51, 0x45, 0x30, 0x0a, + 0x5a, 0x4a, 0x5a, 0x06, 0x14, 0x51, 0x45, 0x02, + 0x0a, 0x28, 0xa5, 0xa0, 0x62, 0x51, 0x4b, 0x45, + 0x00, 0x2d, 0x14, 0x51, 0x48, 0x02, 0x8a, 0x28, + 0xa0, 0x61, 0x45, 0x14, 0x50, 0x03, 0xa8, 0xa2, + 0x8a, 0x06, 0x2d, 0x14, 0x51, 0x48, 0x02, 0x8a, + 0x28, 0xa4, 0x30, 0xa2, 0x8a, 0x2a, 0x80, 0x28, + 0xa2, 0x8a, 0x00, 0x28, 0xa2, 0x8a, 0x92, 0x45, + 0xa5, 0xa2, 0x96, 0x82, 0x82, 0x8a, 0x28, 0xa0, + 0x02, 0x8a, 0x28, 0xa0, 0x05, 0xa2, 0x8a, 0x29, + 0x80, 0x52, 0xd2, 0x52, 0xd0, 0x01, 0x45, 0x14, + 0x50, 0x01, 0x4e, 0xa2, 0x8a, 0x43, 0x0a, 0x28, + 0xa2, 0x80, 0x0a, 0x28, 0xa4, 0xa4, 0x31, 0x68, + 0xa4, 0xf3, 0x62, 0xff, 0x00, 0x9e, 0xd1, 0x7e, + 0xea, 0x9f, 0xfe, 0xb6, 0xa4, 0x62, 0x52, 0x53, + 0xa9, 0x28, 0x01, 0x28, 0xa2, 0x6f, 0xdd, 0x7f, + 0xaf, 0xa5, 0xa0, 0x62, 0x51, 0x4b, 0x45, 0x00, + 0x25, 0x14, 0xbf, 0xba, 0xff, 0x00, 0xae, 0xb4, + 0xea, 0x60, 0x36, 0x9d, 0x49, 0x49, 0x34, 0xb1, + 0x45, 0xfe, 0xbe, 0x6f, 0x2a, 0x98, 0x0f, 0xa2, + 0xb9, 0xbd, 0x0f, 0x59, 0x97, 0x54, 0xf1, 0x2d, + 0xd7, 0xfc, 0xf9, 0xd7, 0x49, 0x52, 0x30, 0xac, + 0x7d, 0x5b, 0x54, 0xd5, 0x74, 0xbf, 0xdf, 0x41, + 0x67, 0x15, 0xd4, 0x35, 0x63, 0x56, 0xd5, 0x22, + 0xd2, 0xe0, 0xf3, 0xbc, 0x99, 0x65, 0xae, 0x4b, + 0x51, 0xf1, 0x44, 0x52, 0xff, 0x00, 0xc7, 0x97, + 0x9b, 0xe4, 0xd0, 0x69, 0x4c, 0x76, 0xa3, 0xe2, + 0xd9, 0xa5, 0x82, 0x29, 0xb4, 0xbf, 0xdd, 0x79, + 0xbf, 0xeb, 0x22, 0xaa, 0x7e, 0x1e, 0xd5, 0x3e, + 0xc1, 0x3f, 0xfa, 0x9f, 0xfa, 0xe9, 0x25, 0x61, + 0xd6, 0x9e, 0x87, 0xf6, 0xbf, 0xed, 0x68, 0xbc, + 0x8a, 0x82, 0xcf, 0x4e, 0x86, 0x5f, 0x36, 0x0a, + 0x92, 0x9b, 0xff, 0x00, 0x5d, 0xeb, 0x3a, 0x6d, + 0x53, 0xfd, 0x3f, 0xec, 0x76, 0x3f, 0xbd, 0xff, + 0x00, 0xa6, 0xbf, 0xc1, 0x54, 0x66, 0x69, 0x51, + 0x50, 0x4b, 0x2c, 0x51, 0x7e, 0xe7, 0xce, 0xf3, + 0x66, 0xff, 0x00, 0xa6, 0x75, 0x35, 0x00, 0x2d, + 0x65, 0x6a, 0xde, 0x23, 0xd3, 0xec, 0x3f, 0xe5, + 0xb7, 0x9b, 0x34, 0x5f, 0xf2, 0xce, 0xb4, 0x7e, + 0xd5, 0x69, 0x2f, 0xfc, 0xbe, 0x45, 0x5e, 0x7b, + 0xe2, 0x7d, 0x07, 0xec, 0x1e, 0x6e, 0xa5, 0xf6, + 0xc8, 0xa5, 0xf3, 0x6a, 0xc0, 0xdf, 0xa2, 0x9d, + 0x45, 0x6c, 0x72, 0x0d, 0xa5, 0xa5, 0xa2, 0x80, + 0x12, 0x96, 0x8a, 0x28, 0x10, 0x51, 0x4b, 0x45, + 0x00, 0x25, 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, + 0x45, 0x03, 0x12, 0x8a, 0x5a, 0x28, 0x10, 0x94, + 0xb4, 0x51, 0x40, 0x05, 0x14, 0x51, 0x40, 0x05, + 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, 0x45, 0x00, + 0x14, 0x51, 0x4b, 0x40, 0x84, 0xa2, 0x8a, 0x28, + 0x00, 0xa2, 0x96, 0x8a, 0x00, 0x4a, 0x29, 0x68, + 0xa0, 0x04, 0xa2, 0x96, 0x8a, 0x00, 0x4a, 0x29, + 0x68, 0xa0, 0x41, 0x45, 0x14, 0x50, 0x01, 0x45, + 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, + 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, + 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, + 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x45, + 0x14, 0x50, 0x01, 0x45, 0x14, 0x50, 0x01, 0x49, + 0x4b, 0x45, 0x00, 0x25, 0x14, 0xb4, 0x50, 0x01, + 0x45, 0x14, 0x50, 0x30, 0xa4, 0xa5, 0xa2, 0x81, + 0x09, 0x45, 0x2d, 0x14, 0x00, 0x94, 0x52, 0xd1, + 0x40, 0x09, 0x45, 0x2d, 0x14, 0x00, 0x94, 0x52, + 0xd1, 0x40, 0x05, 0x14, 0xb4, 0x94, 0x0c, 0x28, + 0xa2, 0x8a, 0x04, 0x14, 0x51, 0x45, 0x00, 0x14, + 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, + 0x51, 0x45, 0x00, 0x14, 0x52, 0xd1, 0x40, 0xc4, + 0xa2, 0x96, 0x8a, 0x04, 0x25, 0x14, 0xb4, 0x50, + 0x31, 0x28, 0xa5, 0xa2, 0x80, 0x12, 0x8a, 0x5a, + 0x4a, 0x00, 0x5a, 0x28, 0xa2, 0x80, 0x0a, 0x29, + 0x68, 0xa0, 0x04, 0xa2, 0x96, 0x8a, 0x00, 0x4a, + 0x29, 0x68, 0xa0, 0x04, 0xa2, 0x9d, 0x45, 0x03, + 0x1b, 0x45, 0x3a, 0x8a, 0x00, 0x6d, 0x14, 0xea, + 0x28, 0x10, 0x94, 0x52, 0xd1, 0x40, 0x09, 0x4b, + 0x45, 0x14, 0x08, 0x28, 0xa2, 0x8a, 0x06, 0x14, + 0x52, 0xd1, 0x40, 0x09, 0x45, 0x2d, 0x2d, 0x00, + 0x25, 0x14, 0xb4, 0x50, 0x21, 0x28, 0xa5, 0xa2, + 0x81, 0x89, 0x4b, 0x45, 0x14, 0x00, 0x51, 0x45, + 0x14, 0x00, 0x51, 0x4b, 0x45, 0x00, 0x25, 0x14, + 0xb4, 0x50, 0x01, 0x45, 0x2d, 0x14, 0x0c, 0x28, + 0xa2, 0x8a, 0x00, 0x4a, 0x5a, 0x5a, 0x28, 0x01, + 0x28, 0xa5, 0xa2, 0x90, 0x05, 0x14, 0x51, 0x40, + 0x05, 0x14, 0xb4, 0x52, 0x01, 0x28, 0xa5, 0xa2, + 0x80, 0x0a, 0x28, 0xa7, 0x50, 0x03, 0x68, 0xa7, + 0x51, 0x40, 0x0d, 0xa7, 0x51, 0x45, 0x00, 0x14, + 0x51, 0x4b, 0x40, 0x09, 0x45, 0x2d, 0x2d, 0x00, + 0x25, 0x14, 0xb4, 0x50, 0x02, 0x51, 0x4b, 0x45, + 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, + 0x00, 0x14, 0xb4, 0x53, 0xa8, 0x18, 0xda, 0x5a, + 0x5a, 0x28, 0x10, 0x94, 0xb4, 0x51, 0x40, 0xc2, + 0x8a, 0x5a, 0x29, 0x00, 0x94, 0x52, 0xd1, 0x40, + 0x05, 0x14, 0x51, 0x4c, 0x02, 0x96, 0x8a, 0x29, + 0x00, 0x52, 0xd2, 0x52, 0xd0, 0x01, 0x45, 0x14, + 0x50, 0x02, 0xd1, 0x45, 0x14, 0x0c, 0x28, 0xa2, + 0x96, 0x80, 0x0a, 0x28, 0xa2, 0x81, 0x85, 0x2d, + 0x14, 0x50, 0x01, 0x45, 0x14, 0xb4, 0x80, 0x4a, + 0x29, 0x68, 0xa0, 0x62, 0xd1, 0x45, 0x14, 0xc0, + 0x28, 0xa5, 0xa2, 0x90, 0x0d, 0xa5, 0xa5, 0xa2, + 0x80, 0x0a, 0x5a, 0x28, 0xa4, 0x01, 0x45, 0x14, + 0xb4, 0x00, 0x94, 0x52, 0xd1, 0x40, 0x05, 0x14, + 0x51, 0x4c, 0x02, 0x8a, 0x29, 0x68, 0x01, 0x28, + 0xa5, 0xa2, 0x80, 0x16, 0x8a, 0x28, 0xa4, 0x30, + 0xa7, 0x55, 0x7b, 0xbb, 0x5f, 0xb7, 0xc1, 0xe4, + 0xff, 0x00, 0xaa, 0xac, 0x49, 0xb4, 0x6d, 0x57, + 0x4b, 0xf3, 0x66, 0xd2, 0xef, 0x3c, 0xd8, 0x69, + 0x17, 0x4c, 0xe9, 0x2b, 0x07, 0xc4, 0x32, 0xcb, + 0x75, 0xa4, 0xff, 0x00, 0xc4, 0xae, 0x68, 0xbf, + 0x75, 0xfe, 0xb2, 0xb9, 0x09, 0xb5, 0x9d, 0x42, + 0x5f, 0x36, 0xcf, 0xce, 0xff, 0x00, 0x5b, 0x56, + 0x3c, 0x3d, 0x6b, 0xfd, 0xa9, 0xab, 0x4b, 0x67, + 0x3f, 0xfc, 0xf3, 0xff, 0x00, 0x96, 0x74, 0x8d, + 0xbd, 0x99, 0x9b, 0x0d, 0xfc, 0xb2, 0xf9, 0xbf, + 0xeb, 0x7f, 0x7b, 0x5a, 0xf0, 0xf8, 0x8e, 0xee, + 0x2d, 0x27, 0xec, 0x70, 0x7f, 0xdf, 0xca, 0x66, + 0xa3, 0xe1, 0x7f, 0xb0, 0x79, 0xbe, 0x46, 0xa5, + 0x17, 0xfd, 0x73, 0x92, 0xb0, 0xe1, 0xa8, 0x0f, + 0x66, 0x76, 0x90, 0xf8, 0xa2, 0x29, 0x6c, 0x3c, + 0x9f, 0xde, 0xf9, 0xde, 0x5f, 0xef, 0x24, 0xaa, + 0x76, 0x9e, 0x2d, 0xbb, 0x8b, 0x49, 0xf2, 0x7f, + 0xe5, 0xb5, 0x62, 0x5a, 0x5f, 0xcb, 0x6b, 0xfe, + 0xa3, 0xfe, 0x5a, 0xd1, 0xe5, 0x45, 0xff, 0x00, + 0x2c, 0x29, 0x0f, 0xd9, 0x97, 0xff, 0x00, 0xb6, + 0x65, 0xba, 0x82, 0x5f, 0xb7, 0x4d, 0xe6, 0xcd, + 0xff, 0x00, 0x2c, 0xe2, 0xae, 0xa7, 0x49, 0xd5, + 0x3f, 0xe2, 0x9a, 0xf3, 0xa7, 0xff, 0x00, 0x5d, + 0x15, 0x70, 0xb4, 0x43, 0xfb, 0xdf, 0xf5, 0xf3, + 0x4b, 0xff, 0x00, 0x5c, 0xe8, 0xf6, 0x83, 0xf6, + 0x67, 0xa1, 0x68, 0x7a, 0xcc, 0x5a, 0xcf, 0xfd, + 0x32, 0x9b, 0xfe, 0x79, 0xd5, 0xeb, 0xbb, 0xab, + 0x4b, 0x08, 0x3f, 0xd3, 0xab, 0xcd, 0x66, 0xba, + 0xfb, 0x57, 0xfd, 0x32, 0x9a, 0x2a, 0x96, 0xd2, + 0xff, 0x00, 0xcd, 0xbf, 0xf3, 0xb5, 0x49, 0xbc, + 0xdf, 0x2b, 0xfe, 0x59, 0xd3, 0x27, 0xd9, 0x9a, + 0x30, 0xdd, 0x7d, 0xab, 0x5e, 0xfb, 0x1d, 0x8c, + 0xde, 0x55, 0x9c, 0xb2, 0x79, 0x95, 0xd3, 0x6a, + 0x3a, 0xa7, 0xd9, 0x75, 0xdb, 0x5b, 0x39, 0xff, + 0x00, 0xd4, 0xcb, 0xff, 0x00, 0x2d, 0x2b, 0xcf, + 0xbe, 0xdf, 0xe5, 0x6a, 0xdf, 0x6c, 0x83, 0xfe, + 0x59, 0x54, 0xda, 0xb6, 0xb3, 0x2e, 0xb3, 0x7f, + 0xe7, 0x7f, 0xaa, 0xff, 0xff, 0xd9, + }, + }, + { + name: "mpeg4audio", + format: &format.MPEG4Audio{ + PayloadTyp: 96, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + // AU-headers-length: 16 bits (2 bytes) = 16 bits of headers + 0x00, 0x10, + // AU-header: 13 bits size + 3 bits index + // size=4 (13 bits): 0000000000100 + // index=0 (3 bits): 000 + // Combined: 0000000000100000 = 0x0020 + 0x00, 0x20, + // AU data + 0x01, 0x02, 0x03, 0x04, + }, + }, + }, + decoded: unit.PayloadMPEG4Audio{ + {0x01, 0x02, 0x03, 0x04}, + }, + }, + { + name: "opus", + format: &format.Opus{ + PayloadTyp: 96, + ChannelCount: 2, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: false, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }, + }, + decoded: unit.PayloadOpus{ + {0x01, 0x02, 0x03, 0x04}, + }, + }, + { + name: "g711", + format: &format.G711{ + MULaw: true, + SampleRate: 8000, + ChannelCount: 1, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: false, + PayloadType: 0, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }, + }, + decoded: unit.PayloadG711{0x01, 0x02, 0x03, 0x04}, + }, + { + name: "lpcm", + format: &format.LPCM{ + PayloadTyp: 96, + BitDepth: 16, + SampleRate: 48000, + ChannelCount: 2, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: false, + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }, + }, + decoded: unit.PayloadLPCM{0x01, 0x02, 0x03, 0x04}, + }, + { + name: "klv", + format: &format.KLV{ + PayloadTyp: 96, + }, + encoded: []*rtp.Packet{ + { + Header: rtp.Header{ + Version: 2, + Marker: true, // Marker bit indicates complete KLV unit + PayloadType: 96, + SequenceNumber: 123, + Timestamp: 45343, + SSRC: 563423, + }, + Payload: []byte{ + // KLV Universal Label Key (16 bytes) - starts with 0x060e2b34 + 0x06, 0x0e, 0x2b, 0x34, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + // Length (1 byte, short form: 4 bytes of data) + 0x04, + // Value (4 bytes) + 0x01, 0x02, 0x03, 0x04, + }, + }, + }, + decoded: unit.PayloadKLV{ + // Complete KLV unit: Universal Label Key + Length + Value + 0x06, 0x0e, 0x2b, 0x34, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x04, + 0x01, 0x02, 0x03, 0x04, + }, + }, +} + +func TestStreamDecode(t *testing.T) { + for _, ca := range casesDecodeEncode { + t.Run(ca.name, func(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{{ + Formats: []format.Format{ca.format}, + }}} + + strm := &Stream{ + Desc: desc, + UseRTPPackets: true, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: &nilLogger{}, + } + err := strm.Initialize() + require.NoError(t, err) + defer strm.Close() + + r := &Reader{} + recv := make(chan *unit.Unit) + + r.OnData(desc.Medias[0], ca.format, func(u *unit.Unit) error { + if !u.NilPayload() { + recv <- u + close(recv) + } + return nil + }) + + strm.AddReader(r) + defer strm.RemoveReader(r) + + for _, pkt := range ca.encoded { + strm.WriteUnit(desc.Medias[0], ca.format, &unit.Unit{ + RTPPackets: []*rtp.Packet{pkt}, + }) + } + + received := <-recv + require.Equal(t, ca.decoded, received.Payload) + }) + } +} + +func TestStreamEncode(t *testing.T) { + for _, ca := range casesDecodeEncode { + t.Run(ca.name, func(t *testing.T) { + desc := &description.Session{Medias: []*description.Media{{ + Formats: []format.Format{ca.format}, + }}} + + strm := &Stream{ + Desc: desc, + UseRTPPackets: false, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + Parent: &nilLogger{}, + } + err := strm.Initialize() + require.NoError(t, err) + defer strm.Close() + + r := &Reader{} + recv := make(chan struct{}) + + r.OnData(desc.Medias[0], ca.format, func(u *unit.Unit) error { + for i := range min(len(u.RTPPackets), len(ca.encoded)) { + u.RTPPackets[i].Timestamp = ca.encoded[i].Timestamp + u.RTPPackets[i].SequenceNumber = ca.encoded[i].SequenceNumber + u.RTPPackets[i].SSRC = ca.encoded[i].SSRC + } + require.Equal(t, ca.encoded, u.RTPPackets) + close(recv) + return nil + }) + + strm.AddReader(r) + defer strm.RemoveReader(r) + + strm.WriteUnit(desc.Medias[0], ca.format, &unit.Unit{ + Payload: ca.decoded, + }) + + <-recv + }) + } +} diff --git a/internal/stream/unit_remuxer.go b/internal/stream/unit_remuxer.go new file mode 100644 index 00000000..9b90a7c6 --- /dev/null +++ b/internal/stream/unit_remuxer.go @@ -0,0 +1,181 @@ +package stream + +import ( + "bytes" + + "github.com/bluenviron/gortsplib/v5/pkg/format" + mch264 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h264" + mch265 "github.com/bluenviron/mediacommon/v2/pkg/codecs/h265" + "github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4video" + "github.com/bluenviron/mediamtx/internal/unit" +) + +type unitRemuxer func(format.Format, unit.Payload) unit.Payload + +func unitRemuxerH265(forma format.Format, payload unit.Payload) unit.Payload { + formatH265 := forma.(*format.H265) + au := payload.(unit.PayloadH265) + + isKeyFrame := false + n := 0 + + for _, nalu := range au { + typ := mch265.NALUType((nalu[0] >> 1) & 0b111111) + + switch typ { + case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT: + continue + + case mch265.NALUType_AUD_NUT: + continue + + case mch265.NALUType_IDR_W_RADL, mch265.NALUType_IDR_N_LP, mch265.NALUType_CRA_NUT: + if !isKeyFrame { + isKeyFrame = true + + // prepend parameters + if formatH265.VPS != nil && formatH265.SPS != nil && formatH265.PPS != nil { + n += 3 + } + } + } + n++ + } + + if n == 0 { + return unit.PayloadH265(nil) + } + + filteredAU := make([][]byte, n) + i := 0 + + if isKeyFrame && formatH265.VPS != nil && formatH265.SPS != nil && formatH265.PPS != nil { + filteredAU[0] = formatH265.VPS + filteredAU[1] = formatH265.SPS + filteredAU[2] = formatH265.PPS + i = 3 + } + + for _, nalu := range au { + typ := mch265.NALUType((nalu[0] >> 1) & 0b111111) + + switch typ { + case mch265.NALUType_VPS_NUT, mch265.NALUType_SPS_NUT, mch265.NALUType_PPS_NUT: + continue + + case mch265.NALUType_AUD_NUT: + continue + } + + filteredAU[i] = nalu + i++ + } + + return unit.PayloadH265(filteredAU) +} + +func unitRemuxerH264(forma format.Format, payload unit.Payload) unit.Payload { + formatH264 := forma.(*format.H264) + au := payload.(unit.PayloadH264) + + isKeyFrame := false + n := 0 + + for _, nalu := range au { + typ := mch264.NALUType(nalu[0] & 0x1F) + + switch typ { + case mch264.NALUTypeSPS, mch264.NALUTypePPS: + continue + + case mch264.NALUTypeAccessUnitDelimiter: + continue + + case mch264.NALUTypeIDR: + if !isKeyFrame { + isKeyFrame = true + + // prepend parameters + if formatH264.SPS != nil && formatH264.PPS != nil { + n += 2 + } + } + } + n++ + } + + if n == 0 { + return unit.PayloadH264(nil) + } + + filteredAU := make([][]byte, n) + i := 0 + + if isKeyFrame && formatH264.SPS != nil && formatH264.PPS != nil { + filteredAU[0] = formatH264.SPS + filteredAU[1] = formatH264.PPS + i = 2 + } + + for _, nalu := range au { + typ := mch264.NALUType(nalu[0] & 0x1F) + + switch typ { + case mch264.NALUTypeSPS, mch264.NALUTypePPS: + continue + + case mch264.NALUTypeAccessUnitDelimiter: + continue + } + + filteredAU[i] = nalu + i++ + } + + return unit.PayloadH264(filteredAU) +} + +func unitRemuxerMPEG4Video(forma format.Format, payload unit.Payload) unit.Payload { + formatMPEG4Video := forma.(*format.MPEG4Video) + frame := payload.(unit.PayloadMPEG4Video) + + // remove config + if bytes.HasPrefix(frame, []byte{0, 0, 1, byte(mpeg4video.VisualObjectSequenceStartCode)}) { + end := bytes.Index(frame[4:], []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) + if end >= 0 { + frame = frame[end+4:] + } + } + + // add config + if bytes.Contains(frame, []byte{0, 0, 1, byte(mpeg4video.GroupOfVOPStartCode)}) { + f := make([]byte, len(formatMPEG4Video.Config)+len(frame)) + n := copy(f, formatMPEG4Video.Config) + copy(f[n:], frame) + frame = f + } + + if len(frame) == 0 { + return unit.PayloadMPEG4Video(nil) + } + + return frame +} + +func newUnitRemuxer(forma format.Format) unitRemuxer { + switch forma.(type) { + case *format.H265: + return unitRemuxerH265 + + case *format.H264: + return unitRemuxerH264 + + case *format.MPEG4Video: + return unitRemuxerMPEG4Video + + default: + return unitRemuxer(func(_ format.Format, payload unit.Payload) unit.Payload { + return payload + }) + } +} diff --git a/internal/test/static_source_parent.go b/internal/test/static_source_parent.go index d3b8c220..c50fda04 100644 --- a/internal/test/static_source_parent.go +++ b/internal/test/static_source_parent.go @@ -30,11 +30,12 @@ func (p *StaticSourceParent) Close() { // SetReady implements parent. func (p *StaticSourceParent) SetReady(req defs.PathSourceStaticSetReadyReq) defs.PathSourceStaticSetReadyRes { p.stream = &stream.Stream{ - WriteQueueSize: 512, - RTPMaxPayloadSize: 1450, - Desc: req.Desc, - GenerateRTPPackets: req.GenerateRTPPackets, - Parent: p, + Desc: req.Desc, + UseRTPPackets: req.UseRTPPackets, + WriteQueueSize: 512, + RTPMaxPayloadSize: 1450, + ReplaceNTP: req.ReplaceNTP, + Parent: p, } err := p.stream.Initialize() if err != nil {