mirror of
https://github.com/bluenviron/mediamtx.git
synced 2025-12-25 04:22:00 -08:00
parent
608b373468
commit
5b92bd5699
3 changed files with 424 additions and 484 deletions
2
go.sum
2
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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue