From 5b92bd56990fe1a135f382626b09b0eacceb95f1 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Tue, 5 Sep 2023 19:09:50 +0200 Subject: [PATCH] fix compatibility with RTMP Dahua cameras (#2289) (#769) (#2298) --- go.sum | 2 + internal/rtmp/reader.go | 130 +++--- internal/rtmp/reader_test.go | 776 ++++++++++++++++------------------- 3 files changed, 424 insertions(+), 484 deletions(-) diff --git a/go.sum b/go.sum index 61b71cba..2437f0f6 100644 --- a/go.sum +++ b/go.sum @@ -254,6 +254,7 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -265,6 +266,7 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/rtmp/reader.go b/internal/rtmp/reader.go index 13cff599..eeb23bac 100644 --- a/internal/rtmp/reader.go +++ b/internal/rtmp/reader.go @@ -2,7 +2,6 @@ package rtmp import ( "bytes" - "errors" "fmt" "time" @@ -33,6 +32,67 @@ type OnDataMPEG4AudioFunc func(pts time.Duration, au []byte) // OnDataMPEG1AudioFunc is the prototype of the callback passed to OnDataMPEG1Audio(). type OnDataMPEG1AudioFunc func(pts time.Duration, frame []byte) +func hasVideo(md flvio.AMFMap) (bool, error) { + v, ok := md.GetV("videocodecid") + if !ok { + // some Dahua cameras send width and height without videocodecid + if v, ok := md.GetV("width"); ok { + if vf, ok := v.(float64); ok && vf != 0 { + return true, nil + } + } + return false, nil + } + + switch vt := v.(type) { + case float64: + switch vt { + case 0: + return false, nil + + case message.CodecH264, float64(message.FourCCAV1), + float64(message.FourCCVP9), float64(message.FourCCHEVC): + return true, nil + } + + case string: + if vt == "avc1" || vt == "hvc1" || vt == "av01" { + return true, nil + } + } + + return false, fmt.Errorf("unsupported video codec: %v", v) +} + +func hasAudio(md flvio.AMFMap, audioTrack *format.Format) (bool, error) { + v, ok := md.GetV("audiocodecid") + if !ok { + return false, nil + } + + switch vt := v.(type) { + case float64: + switch vt { + case 0: + return false, nil + + case message.CodecMPEG1Audio: + *audioTrack = &format.MPEG1Audio{} + return true, nil + + case message.CodecMPEG4Audio: + return true, nil + } + + case string: + if vt == "mp4a" { + return true, nil + } + } + + return false, fmt.Errorf("unsupported audio codec %v", v) +} + func h265FindNALU(array []mp4.HEVCNaluArray, typ h265.NALUType) []byte { for _, entry := range array { if entry.NaluType == byte(typ) && entry.NumNalus == 1 && @@ -74,8 +134,6 @@ func trackFromAACDecoderConfig(data []byte) (format.Format, error) { }, nil } -var errEmptyMetadata = errors.New("metadata is empty") - func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, format.Format, error) { if len(payload) != 1 { return nil, nil, fmt.Errorf("invalid metadata") @@ -89,69 +147,18 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma var videoTrack format.Format var audioTrack format.Format - hasVideo, err := func() (bool, error) { - v, ok := md.GetV("videocodecid") - if !ok { - return false, nil - } - - switch vt := v.(type) { - case float64: - switch vt { - case 0: - return false, nil - - case message.CodecH264, float64(message.FourCCAV1), - float64(message.FourCCVP9), float64(message.FourCCHEVC): - return true, nil - } - - case string: - if vt == "avc1" || vt == "hvc1" || vt == "av01" { - return true, nil - } - } - - return false, fmt.Errorf("unsupported video codec: %v", v) - }() + hasVideo, err := hasVideo(md) if err != nil { return nil, nil, err } - hasAudio, err := func() (bool, error) { - v, ok := md.GetV("audiocodecid") - if !ok { - return false, nil - } - - switch vt := v.(type) { - case float64: - switch vt { - case 0: - return false, nil - - case message.CodecMPEG1Audio: - audioTrack = &format.MPEG1Audio{} - return true, nil - - case message.CodecMPEG4Audio: - return true, nil - } - - case string: - if vt == "mp4a" { - return true, nil - } - } - - return false, fmt.Errorf("unsupported audio codec %v", v) - }() + hasAudio, err := hasAudio(md, &audioTrack) if err != nil { return nil, nil, err } if !hasVideo && !hasAudio { - return nil, nil, errEmptyMetadata + return nil, nil, fmt.Errorf("metadata doesn't contain any track") } for { @@ -422,15 +429,6 @@ func (r *Reader) readTracks() (format.Format, format.Format, error) { if s, ok := payload[0].(string); ok && s == "onMetaData" { videoTrack, audioTrack, err := tracksFromMetadata(r.conn, payload[1:]) if err != nil { - if err == errEmptyMetadata { - msg, err := r.conn.Read() - if err != nil { - return nil, nil, err - } - - return tracksFromMessages(r.conn, msg) - } - return nil, nil, err } diff --git a/internal/rtmp/reader_test.go b/internal/rtmp/reader_test.go index e4cef065..cae91092 100644 --- a/internal/rtmp/reader_test.go +++ b/internal/rtmp/reader_test.go @@ -106,6 +106,7 @@ func TestReadTracks(t *testing.T) { name string videoTrack format.Format audioTrack format.Format + messages []message.Message }{ { "video+audio", @@ -126,6 +127,66 @@ func TestReadTracks(t *testing.T) { IndexLength: 3, IndexDeltaLength: 3, }, + []message.Message{ + &message.DataAMF0{ + ChunkStreamID: 4, + MessageStreamID: 1, + Payload: []interface{}{ + "@setDataFrame", + "onMetaData", + flvio.AMFMap{ + { + K: "videodatarate", + V: float64(0), + }, + { + K: "videocodecid", + V: float64(message.CodecH264), + }, + { + K: "audiodatarate", + V: float64(0), + }, + { + K: "audiocodecid", + V: float64(message.CodecMPEG4Audio), + }, + }, + }, + }, + &message.Video{ + ChunkStreamID: message.VideoChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecH264, + IsKeyFrame: true, + Type: message.VideoTypeConfig, + Payload: func() []byte { + buf, _ := h264conf.Conf{ + SPS: h264SPS, + PPS: h264PPS, + }.Marshal() + return buf + }(), + }, + &message.Audio{ + ChunkStreamID: message.AudioChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecMPEG4Audio, + Rate: flvio.SOUND_44Khz, + Depth: flvio.SOUND_16BIT, + Channels: flvio.SOUND_STEREO, + AACType: message.AudioAACTypeConfig, + Payload: func() []byte { + enc, err := mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }.Marshal() + require.NoError(t, err) + return enc + }(), + }, + }, }, { "video", @@ -136,9 +197,51 @@ func TestReadTracks(t *testing.T) { PacketizationMode: 1, }, nil, + []message.Message{ + &message.DataAMF0{ + ChunkStreamID: 4, + MessageStreamID: 1, + Payload: []interface{}{ + "@setDataFrame", + "onMetaData", + flvio.AMFMap{ + { + K: "videodatarate", + V: float64(0), + }, + { + K: "videocodecid", + V: float64(message.CodecH264), + }, + { + K: "audiodatarate", + V: float64(0), + }, + { + K: "audiocodecid", + V: float64(0), + }, + }, + }, + }, + &message.Video{ + ChunkStreamID: message.VideoChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecH264, + IsKeyFrame: true, + Type: message.VideoTypeConfig, + Payload: func() []byte { + buf, _ := h264conf.Conf{ + SPS: h264SPS, + PPS: h264PPS, + }.Marshal() + return buf + }(), + }, + }, }, { - "metadata without codec id, video+audio", + "issue mediamtx/386 (missing metadata), video+audio", &format.H264{ PayloadTyp: 96, SPS: h264SPS, @@ -156,39 +259,57 @@ func TestReadTracks(t *testing.T) { IndexLength: 3, IndexDeltaLength: 3, }, - }, - { - "metadata without codec id, video only", - &format.H264{ - PayloadTyp: 96, - SPS: h264SPS, - PPS: h264PPS, - PacketizationMode: 1, - }, - nil, - }, - { - "missing metadata, video+audio", - &format.H264{ - PayloadTyp: 96, - SPS: h264SPS, - PPS: h264PPS, - PacketizationMode: 1, - }, - &format.MPEG4Audio{ - PayloadTyp: 96, - Config: &mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, + []message.Message{ + &message.Video{ + ChunkStreamID: message.VideoChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecH264, + IsKeyFrame: true, + Type: message.VideoTypeConfig, + Payload: func() []byte { + buf, _ := h264conf.Conf{ + SPS: h264SPS, + PPS: h264PPS, + }.Marshal() + return buf + }(), + }, + &message.Video{ + ChunkStreamID: message.VideoChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecH264, + IsKeyFrame: true, + Type: message.VideoTypeConfig, + Payload: func() []byte { + buf, _ := h264conf.Conf{ + SPS: h264SPS, + PPS: h264PPS, + }.Marshal() + return buf + }(), + }, + &message.Audio{ + ChunkStreamID: message.AudioChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecMPEG4Audio, + Rate: flvio.SOUND_44Khz, + Depth: flvio.SOUND_16BIT, + Channels: flvio.SOUND_STEREO, + AACType: message.AudioAACTypeConfig, + Payload: func() []byte { + enc, err := mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }.Marshal() + require.NoError(t, err) + return enc + }(), }, - SizeLength: 13, - IndexLength: 3, - IndexDeltaLength: 3, }, }, { - "missing metadata, audio", + "issue mediamtx/386 (missing metadata), audio", nil, &format.MPEG4Audio{ PayloadTyp: 96, @@ -201,6 +322,45 @@ func TestReadTracks(t *testing.T) { IndexLength: 3, IndexDeltaLength: 3, }, + []message.Message{ + &message.Audio{ + ChunkStreamID: message.AudioChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecMPEG4Audio, + Rate: flvio.SOUND_44Khz, + Depth: flvio.SOUND_16BIT, + Channels: flvio.SOUND_STEREO, + AACType: message.AudioAACTypeConfig, + Payload: func() []byte { + enc, err := mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }.Marshal() + require.NoError(t, err) + return enc + }(), + }, + &message.Audio{ + ChunkStreamID: message.AudioChunkStreamID, + MessageStreamID: 0x1000000, + Codec: message.CodecMPEG4Audio, + Rate: flvio.SOUND_44Khz, + Depth: flvio.SOUND_16BIT, + Channels: flvio.SOUND_STEREO, + AACType: message.AudioAACTypeConfig, + Payload: func() []byte { + enc, err := mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }.Marshal() + require.NoError(t, err) + return enc + }(), + DTS: 1 * time.Second, + }, + }, }, { "obs studio pre 29.1 h265", @@ -221,43 +381,8 @@ func TestReadTracks(t *testing.T) { IndexLength: 3, IndexDeltaLength: 3, }, - }, - { - "xplit broadcaster", - &format.H265{ - PayloadTyp: 96, - VPS: h265VPS, - SPS: h265SPS, - PPS: h265PPS, - }, - nil, - }, - { - "obs 30", - &format.H265{ - PayloadTyp: 96, - VPS: h265VPS, - SPS: h265SPS, - PPS: h265PPS, - }, - nil, - }, - { - "ffmpeg av1", - &format.AV1{ - PayloadTyp: 96, - }, - nil, - }, - } { - t.Run(ca.name, func(t *testing.T) { - var buf bytes.Buffer - bc := bytecounter.NewReadWriter(&buf) - mrw := message.NewReadWriter(bc, bc, true) - - switch ca.name { - case "video+audio": - err := mrw.Write(&message.DataAMF0{ + []message.Message{ + &message.DataAMF0{ ChunkStreamID: 4, MessageStreamID: 1, Payload: []interface{}{ @@ -282,221 +407,24 @@ func TestReadTracks(t *testing.T) { }, }, }, - }) - require.NoError(t, err) - - buf, _ := h264conf.Conf{ - SPS: h264SPS, - PPS: h264PPS, - }.Marshal() - - err = mrw.Write(&message.Video{ - ChunkStreamID: message.VideoChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecH264, - IsKeyFrame: true, - Type: message.VideoTypeConfig, - Payload: buf, - }) - require.NoError(t, err) - - enc, err := mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }.Marshal() - require.NoError(t, err) - - err = mrw.Write(&message.Audio{ - ChunkStreamID: message.AudioChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecMPEG4Audio, - Rate: flvio.SOUND_44Khz, - Depth: flvio.SOUND_16BIT, - Channels: flvio.SOUND_STEREO, - AACType: message.AudioAACTypeConfig, - Payload: enc, - }) - require.NoError(t, err) - - case "video": - err := mrw.Write(&message.DataAMF0{ - ChunkStreamID: 4, - MessageStreamID: 1, - Payload: []interface{}{ - "@setDataFrame", - "onMetaData", - flvio.AMFMap{ - { - K: "videodatarate", - V: float64(0), - }, - { - K: "videocodecid", - V: float64(message.CodecH264), - }, - { - K: "audiodatarate", - V: float64(0), - }, - { - K: "audiocodecid", - V: float64(0), - }, - }, - }, - }) - require.NoError(t, err) - - buf, _ := h264conf.Conf{ - SPS: h264SPS, - PPS: h264PPS, - }.Marshal() - - err = mrw.Write(&message.Video{ - ChunkStreamID: message.VideoChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecH264, - IsKeyFrame: true, - Type: message.VideoTypeConfig, - Payload: buf, - }) - require.NoError(t, err) - - case "metadata without codec id, video+audio": - err := mrw.Write(&message.DataAMF0{ - ChunkStreamID: 4, - MessageStreamID: 1, - Payload: []interface{}{ - "@setDataFrame", - "onMetaData", - flvio.AMFMap{ - { - K: "width", - V: float64(2688), - }, - { - K: "height", - V: float64(1520), - }, - { - K: "framerate", - V: float64(0o25), - }, - }, - }, - }) - require.NoError(t, err) - - buf, _ := h264conf.Conf{ - SPS: h264SPS, - PPS: h264PPS, - }.Marshal() - - err = mrw.Write(&message.Video{ - ChunkStreamID: message.VideoChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecH264, - IsKeyFrame: true, - Type: message.VideoTypeConfig, - Payload: buf, - }) - require.NoError(t, err) - - enc, err := mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }.Marshal() - require.NoError(t, err) - - err = mrw.Write(&message.Audio{ - ChunkStreamID: message.AudioChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecMPEG4Audio, - Rate: flvio.SOUND_44Khz, - Depth: flvio.SOUND_16BIT, - Channels: flvio.SOUND_STEREO, - AACType: message.AudioAACTypeConfig, - Payload: enc, - }) - require.NoError(t, err) - - case "metadata without codec id, video only": - err := mrw.Write(&message.DataAMF0{ - ChunkStreamID: 4, - MessageStreamID: 1, - Payload: []interface{}{ - "@setDataFrame", - "onMetaData", - flvio.AMFMap{ - { - K: "width", - V: float64(2688), - }, - { - K: "height", - V: float64(1520), - }, - { - K: "framerate", - V: float64(0o25), - }, - }, - }, - }) - require.NoError(t, err) - - buf, _ := h264conf.Conf{ - SPS: h264SPS, - PPS: h264PPS, - }.Marshal() - - err = mrw.Write(&message.Video{ - ChunkStreamID: message.VideoChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecH264, - IsKeyFrame: true, - Type: message.VideoTypeConfig, - Payload: buf, - }) - require.NoError(t, err) - - err = mrw.Write(&message.Video{ + }, + &message.Video{ ChunkStreamID: message.VideoChunkStreamID, MessageStreamID: 0x1000000, Codec: message.CodecH264, IsKeyFrame: true, Type: message.VideoTypeAU, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - DTS: 1 * time.Second, - }) - require.NoError(t, err) - - case "missing metadata, video+audio": - buf, _ := h264conf.Conf{ - SPS: h264SPS, - PPS: h264PPS, - }.Marshal() - - err := mrw.Write(&message.Video{ - ChunkStreamID: message.VideoChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecH264, - IsKeyFrame: true, - Type: message.VideoTypeConfig, - Payload: buf, - }) - require.NoError(t, err) - - enc, err := mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }.Marshal() - require.NoError(t, err) - - err = mrw.Write(&message.Audio{ + Payload: func() []byte { + avcc, err := h264.AVCCMarshal([][]byte{ + h265VPS, + h265SPS, + h265PPS, + }) + require.NoError(t, err) + return avcc + }(), + }, + &message.Audio{ ChunkStreamID: message.AudioChunkStreamID, MessageStreamID: 0x1000000, Codec: message.CodecMPEG4Audio, @@ -504,110 +432,29 @@ func TestReadTracks(t *testing.T) { Depth: flvio.SOUND_16BIT, Channels: flvio.SOUND_STEREO, AACType: message.AudioAACTypeConfig, - Payload: enc, - }) - require.NoError(t, err) - - case "missing metadata, audio": - enc, err := mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }.Marshal() - require.NoError(t, err) - - err = mrw.Write(&message.Audio{ - ChunkStreamID: message.AudioChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecMPEG4Audio, - Rate: flvio.SOUND_44Khz, - Depth: flvio.SOUND_16BIT, - Channels: flvio.SOUND_STEREO, - AACType: message.AudioAACTypeConfig, - Payload: enc, - }) - require.NoError(t, err) - - err = mrw.Write(&message.Audio{ - ChunkStreamID: message.AudioChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecMPEG4Audio, - Rate: flvio.SOUND_44Khz, - Depth: flvio.SOUND_16BIT, - Channels: flvio.SOUND_STEREO, - AACType: message.AudioAACTypeConfig, - Payload: enc, - DTS: 1 * time.Second, - }) - require.NoError(t, err) - - case "obs studio pre 29.1 h265": - err := mrw.Write(&message.DataAMF0{ - ChunkStreamID: 4, - MessageStreamID: 1, - Payload: []interface{}{ - "@setDataFrame", - "onMetaData", - flvio.AMFMap{ - { - K: "videodatarate", - V: float64(0), - }, - { - K: "videocodecid", - V: float64(message.CodecH264), - }, - { - K: "audiodatarate", - V: float64(0), - }, - { - K: "audiocodecid", - V: float64(message.CodecMPEG4Audio), - }, - }, - }, - }) - require.NoError(t, err) - - avcc, err := h264.AVCCMarshal([][]byte{ - h265VPS, - h265SPS, - h265PPS, - }) - require.NoError(t, err) - - err = mrw.Write(&message.Video{ - ChunkStreamID: message.VideoChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecH264, - IsKeyFrame: true, - Type: message.VideoTypeAU, - Payload: avcc, - }) - require.NoError(t, err) - - enc, err := mpeg4audio.Config{ - Type: 2, - SampleRate: 44100, - ChannelCount: 2, - }.Marshal() - require.NoError(t, err) - - err = mrw.Write(&message.Audio{ - ChunkStreamID: message.AudioChunkStreamID, - MessageStreamID: 0x1000000, - Codec: message.CodecMPEG4Audio, - Rate: flvio.SOUND_44Khz, - Depth: flvio.SOUND_16BIT, - Channels: flvio.SOUND_STEREO, - AACType: message.AudioAACTypeConfig, - Payload: enc, - }) - require.NoError(t, err) - - case "xplit broadcaster": - err := mrw.Write(&message.DataAMF0{ + Payload: func() []byte { + enc, err := mpeg4audio.Config{ + Type: 2, + SampleRate: 44100, + ChannelCount: 2, + }.Marshal() + require.NoError(t, err) + return enc + }(), + }, + }, + }, + { + "issue mediamtx/2232 (xsplit broadcaster)", + &format.H265{ + PayloadTyp: 96, + VPS: h265VPS, + SPS: h265SPS, + PPS: h265PPS, + }, + nil, + []message.Message{ + &message.DataAMF0{ ChunkStreamID: 4, MessageStreamID: 1, Payload: []interface{}{ @@ -632,23 +479,31 @@ func TestReadTracks(t *testing.T) { }, }, }, - }) - require.NoError(t, err) - - var buf bytes.Buffer - _, err = mp4.Marshal(&buf, hvcc, mp4.Context{}) - require.NoError(t, err) - - err = mrw.Write(&message.ExtendedSequenceStart{ + }, + &message.ExtendedSequenceStart{ ChunkStreamID: 4, MessageStreamID: 0x1000000, FourCC: message.FourCCHEVC, - Config: buf.Bytes(), - }) - require.NoError(t, err) - - case "obs 30": - err := mrw.Write(&message.DataAMF0{ + Config: func() []byte { + var buf bytes.Buffer + _, err = mp4.Marshal(&buf, hvcc, mp4.Context{}) + require.NoError(t, err) + return buf.Bytes() + }(), + }, + }, + }, + { + "obs 30", + &format.H265{ + PayloadTyp: 96, + VPS: h265VPS, + SPS: h265SPS, + PPS: h265PPS, + }, + nil, + []message.Message{ + &message.DataAMF0{ ChunkStreamID: 4, MessageStreamID: 1, Payload: []interface{}{ @@ -673,23 +528,28 @@ func TestReadTracks(t *testing.T) { }, }, }, - }) - require.NoError(t, err) - - var buf bytes.Buffer - _, err = mp4.Marshal(&buf, hvcc, mp4.Context{}) - require.NoError(t, err) - - err = mrw.Write(&message.ExtendedSequenceStart{ + }, + &message.ExtendedSequenceStart{ ChunkStreamID: 4, MessageStreamID: 0x1000000, FourCC: message.FourCCHEVC, - Config: buf.Bytes(), - }) - require.NoError(t, err) - - case "ffmpeg av1": - err := mrw.Write(&message.DataAMF0{ + Config: func() []byte { + var buf bytes.Buffer + _, err = mp4.Marshal(&buf, hvcc, mp4.Context{}) + require.NoError(t, err) + return buf.Bytes() + }(), + }, + }, + }, + { + "ffmpeg av1", + &format.AV1{ + PayloadTyp: 96, + }, + nil, + []message.Message{ + &message.DataAMF0{ ChunkStreamID: 4, MessageStreamID: 1, Payload: []interface{}{ @@ -730,14 +590,8 @@ func TestReadTracks(t *testing.T) { }, }, }, - }) - require.NoError(t, err) - - var buf bytes.Buffer - _, err = mp4.Marshal(&buf, hvcc, mp4.Context{}) - require.NoError(t, err) - - err = mrw.Write(&message.ExtendedSequenceStart{ + }, + &message.ExtendedSequenceStart{ ChunkStreamID: 6, MessageStreamID: 0x1000000, FourCC: message.FourCCAV1, @@ -746,7 +600,93 @@ func TestReadTracks(t *testing.T) { 0x00, 0x42, 0xab, 0xbf, 0xc3, 0x70, 0x0b, 0xe0, 0x01, }, - }) + }, + }, + }, + { + "issue mediamtx/2289 (missing videocodecid)", + &format.H264{ + PayloadTyp: 96, + SPS: []byte{ + 0x67, 0x64, 0x00, 0x1f, 0xac, 0x2c, 0x6a, 0x81, + 0x40, 0x16, 0xe9, 0xb8, 0x28, 0x08, 0x2a, 0x00, + 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, + 0xc9, 0x08, + }, + PPS: []byte{0x68, 0xee, 0x31, 0xb2, 0x1b}, + PacketizationMode: 1, + }, + &format.MPEG4Audio{ + PayloadTyp: 96, + Config: &mpeg4audio.Config{ + Type: 2, + SampleRate: 48000, + ChannelCount: 1, + }, + SizeLength: 13, + IndexLength: 3, + IndexDeltaLength: 3, + }, + []message.Message{ + &message.DataAMF0{ + ChunkStreamID: 4, + MessageStreamID: 1, + Payload: []interface{}{ + "@setDataFrame", + "onMetaData", + flvio.AMFMap{ + { + K: "width", + V: float64(1280), + }, + { + K: "height", + V: float64(720), + }, + { + K: "framerate", + V: float64(30), + }, + { + K: "audiocodecid", + V: float64(10), + }, + }, + }, + }, + &message.Video{ + ChunkStreamID: 0x15, + MessageStreamID: 0x1000000, + Codec: 0x7, + IsKeyFrame: true, + Payload: []uint8{ + 0x01, 0x64, 0x00, 0x1f, 0xff, 0xe1, 0x00, 0x1a, + 0x67, 0x64, 0x00, 0x1f, 0xac, 0x2c, 0x6a, 0x81, + 0x40, 0x16, 0xe9, 0xb8, 0x28, 0x08, 0x2a, 0x00, + 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, + 0xc9, 0x08, 0x01, 0x00, 0x05, 0x68, 0xee, 0x31, + 0xb2, 0x1b, + }, + }, + &message.Audio{ + ChunkStreamID: 0x14, + MessageStreamID: 0x1000000, + Codec: 0xa, + Rate: 0x3, + Depth: 0x1, + Channels: 0x1, + Payload: []uint8{0x11, 0x88}, + }, + }, + }, + } { + t.Run(ca.name, func(t *testing.T) { + var buf bytes.Buffer + bc := bytecounter.NewReadWriter(&buf) + mrw := message.NewReadWriter(bc, bc, true) + + for _, msg := range ca.messages { + err := mrw.Write(msg) require.NoError(t, err) }