Browse Source

allow RTMP streaming with codecid=av01 or hvc1 (#2232)

* allow RTMP streaming with codecid=av01 or hvc1

Prior to this change, when trying to stream AV1 over enhanced RTMP using
XSplit Broadcaster, the server was refusing the content with
"unsupported video codec: av01" message.

* add tests

---------

Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
pull/2234/head
Xavier Hallade 2 years ago committed by GitHub
parent
commit
accfc49f9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      internal/rtmp/message/extended_sequence_start.go
  2. 2
      internal/rtmp/reader.go
  3. 213
      internal/rtmp/reader_test.go

23
internal/rtmp/message/extended_sequence_start.go

@ -1,19 +1,21 @@
package message package message
import ( import (
"fmt"
"github.com/bluenviron/mediamtx/internal/rtmp/rawmessage" "github.com/bluenviron/mediamtx/internal/rtmp/rawmessage"
) )
// ExtendedSequenceStart is a sequence start extended message. // ExtendedSequenceStart is a sequence start extended message.
type ExtendedSequenceStart struct { type ExtendedSequenceStart struct {
FourCC [4]byte ChunkStreamID byte
Config []byte MessageStreamID uint32
FourCC [4]byte
Config []byte
} }
// Unmarshal implements Message. // Unmarshal implements Message.
func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error { func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error {
m.ChunkStreamID = raw.ChunkStreamID
m.MessageStreamID = raw.MessageStreamID
copy(m.FourCC[:], raw.Body[1:5]) copy(m.FourCC[:], raw.Body[1:5])
m.Config = raw.Body[5:] m.Config = raw.Body[5:]
@ -22,5 +24,16 @@ func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error {
// Marshal implements Message. // Marshal implements Message.
func (m ExtendedSequenceStart) Marshal() (*rawmessage.Message, error) { func (m ExtendedSequenceStart) Marshal() (*rawmessage.Message, error) {
return nil, fmt.Errorf("TODO") body := make([]byte, 5+len(m.Config))
body[0] = 0b10000000 | byte(ExtendedTypeSequenceStart)
copy(body[1:5], m.FourCC[:])
copy(body[5:], m.Config)
return &rawmessage.Message{
ChunkStreamID: m.ChunkStreamID,
Type: uint8(TypeVideo),
MessageStreamID: m.MessageStreamID,
Body: body,
}, nil
} }

2
internal/rtmp/reader.go

@ -103,7 +103,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
} }
case string: case string:
if vt == "avc1" { if vt == "avc1" || vt == "hvc1" || vt == "av01" {
return true, nil return true, nil
} }
} }

213
internal/rtmp/reader_test.go

@ -5,8 +5,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/abema/go-mp4"
"github.com/bluenviron/gortsplib/v3/pkg/formats" "github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio" "github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -17,16 +19,37 @@ import (
) )
func TestReadTracks(t *testing.T) { func TestReadTracks(t *testing.T) {
sps := []byte{ h264SPS := []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0, 0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
0x00, 0x03, 0x00, 0x3d, 0x08, 0x00, 0x03, 0x00, 0x3d, 0x08,
} }
pps := []byte{ h264PPS := []byte{
0x68, 0xee, 0x3c, 0x80, 0x68, 0xee, 0x3c, 0x80,
} }
h265VPS := []byte{
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00,
0x03, 0x00, 0x00, 0x03, 0x00, 0x7b, 0xac, 0x09,
}
h265SPS := []byte{
0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00,
0x03, 0x00, 0x7b, 0xa0, 0x03, 0xc0, 0x80, 0x11,
0x07, 0xcb, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3,
0x01, 0x6a, 0x02, 0x02, 0x02, 0x08, 0x00, 0x00,
0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0xe3,
0x00, 0x2e, 0xf2, 0x88, 0x00, 0x09, 0x89, 0x60,
0x00, 0x04, 0xc4, 0xb4, 0x20,
}
h265PPS := []byte{
0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
}
for _, ca := range []struct { for _, ca := range []struct {
name string name string
videoTrack formats.Format videoTrack formats.Format
@ -36,8 +59,8 @@ func TestReadTracks(t *testing.T) {
"video+audio", "video+audio",
&formats.H264{ &formats.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
&formats.MPEG4Audio{ &formats.MPEG4Audio{
@ -56,8 +79,8 @@ func TestReadTracks(t *testing.T) {
"video", "video",
&formats.H264{ &formats.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
nil, nil,
@ -66,8 +89,8 @@ func TestReadTracks(t *testing.T) {
"metadata without codec id, video+audio", "metadata without codec id, video+audio",
&formats.H264{ &formats.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
&formats.MPEG4Audio{ &formats.MPEG4Audio{
@ -86,8 +109,8 @@ func TestReadTracks(t *testing.T) {
"metadata without codec id, video only", "metadata without codec id, video only",
&formats.H264{ &formats.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
nil, nil,
@ -96,8 +119,8 @@ func TestReadTracks(t *testing.T) {
"missing metadata, video+audio", "missing metadata, video+audio",
&formats.H264{ &formats.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
PacketizationMode: 1, PacketizationMode: 1,
}, },
&formats.MPEG4Audio{ &formats.MPEG4Audio{
@ -131,24 +154,9 @@ func TestReadTracks(t *testing.T) {
"obs studio pre 29.1 h265", "obs studio pre 29.1 h265",
&formats.H265{ &formats.H265{
PayloadTyp: 96, PayloadTyp: 96,
VPS: []byte{ VPS: h265VPS,
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, SPS: h265SPS,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, PPS: h265PPS,
0x03, 0x00, 0x00, 0x03, 0x00, 0x7b, 0xac, 0x09,
},
SPS: []byte{
0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00,
0x03, 0x00, 0x7b, 0xa0, 0x03, 0xc0, 0x80, 0x11,
0x07, 0xcb, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3,
0x01, 0x6a, 0x02, 0x02, 0x02, 0x08, 0x00, 0x00,
0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0xe3,
0x00, 0x2e, 0xf2, 0x88, 0x00, 0x09, 0x89, 0x60,
0x00, 0x04, 0xc4, 0xb4, 0x20,
},
PPS: []byte{
0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
},
}, },
&formats.MPEG4Audio{ &formats.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
@ -162,6 +170,16 @@ func TestReadTracks(t *testing.T) {
IndexDeltaLength: 3, IndexDeltaLength: 3,
}, },
}, },
{
"xplit broadcaster",
&formats.H265{
PayloadTyp: 96,
VPS: h265VPS,
SPS: h265SPS,
PPS: h265PPS,
},
nil,
},
} { } {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer
@ -199,8 +217,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
buf, _ := h264conf.Conf{ buf, _ := h264conf.Conf{
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
}.Marshal() }.Marshal()
err = mrw.Write(&message.Video{ err = mrw.Write(&message.Video{
@ -262,8 +280,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
buf, _ := h264conf.Conf{ buf, _ := h264conf.Conf{
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
}.Marshal() }.Marshal()
err = mrw.Write(&message.Video{ err = mrw.Write(&message.Video{
@ -302,8 +320,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
buf, _ := h264conf.Conf{ buf, _ := h264conf.Conf{
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
}.Marshal() }.Marshal()
err = mrw.Write(&message.Video{ err = mrw.Write(&message.Video{
@ -361,8 +379,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
buf, _ := h264conf.Conf{ buf, _ := h264conf.Conf{
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
}.Marshal() }.Marshal()
err = mrw.Write(&message.Video{ err = mrw.Write(&message.Video{
@ -388,8 +406,8 @@ func TestReadTracks(t *testing.T) {
case "missing metadata, video+audio": case "missing metadata, video+audio":
buf, _ := h264conf.Conf{ buf, _ := h264conf.Conf{
SPS: sps, SPS: h264SPS,
PPS: pps, PPS: h264PPS,
}.Marshal() }.Marshal()
err := mrw.Write(&message.Video{ err := mrw.Write(&message.Video{
@ -484,25 +502,9 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
avcc, err := h264.AVCCMarshal([][]byte{ avcc, err := h264.AVCCMarshal([][]byte{
{ // VPS h265VPS,
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40, h265SPS,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, h265PPS,
0x03, 0x00, 0x00, 0x03, 0x00, 0x7b, 0xac, 0x09,
},
{ // SPS
0x42, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00,
0x03, 0x00, 0x7b, 0xa0, 0x03, 0xc0, 0x80, 0x11,
0x07, 0xcb, 0x96, 0xb4, 0xa4, 0x25, 0x92, 0xe3,
0x01, 0x6a, 0x02, 0x02, 0x02, 0x08, 0x00, 0x00,
0x03, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0xe3,
0x00, 0x2e, 0xf2, 0x88, 0x00, 0x09, 0x89, 0x60,
0x00, 0x04, 0xc4, 0xb4, 0x20,
},
{
// PPS
0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
},
}) })
require.NoError(t, err) require.NoError(t, err)
@ -534,6 +536,99 @@ func TestReadTracks(t *testing.T) {
Payload: enc, Payload: enc,
}) })
require.NoError(t, err) require.NoError(t, err)
case "xplit broadcaster":
err := mrw.Write(&message.DataAMF0{
ChunkStreamID: 4,
MessageStreamID: 1,
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{
K: "videodatarate",
V: float64(0),
},
{
K: "videocodecid",
V: "hvc1",
},
{
K: "audiodatarate",
V: float64(0),
},
{
K: "audiocodecid",
V: float64(0),
},
},
},
})
require.NoError(t, err)
var spsp h265.SPS
err = spsp.Unmarshal(h265SPS)
require.NoError(t, err)
hvcc := &mp4.HvcC{
ConfigurationVersion: 1,
GeneralProfileIdc: spsp.ProfileTierLevel.GeneralProfileIdc,
GeneralProfileCompatibility: spsp.ProfileTierLevel.GeneralProfileCompatibilityFlag,
GeneralConstraintIndicator: [6]uint8{
h265SPS[7], h265SPS[8], h265SPS[9],
h265SPS[10], h265SPS[11], h265SPS[12],
},
GeneralLevelIdc: spsp.ProfileTierLevel.GeneralLevelIdc,
// MinSpatialSegmentationIdc
// ParallelismType
ChromaFormatIdc: uint8(spsp.ChromaFormatIdc),
BitDepthLumaMinus8: uint8(spsp.BitDepthLumaMinus8),
BitDepthChromaMinus8: uint8(spsp.BitDepthChromaMinus8),
// AvgFrameRate
// ConstantFrameRate
NumTemporalLayers: 1,
// TemporalIdNested
LengthSizeMinusOne: 3,
NumOfNaluArrays: 3,
NaluArrays: []mp4.HEVCNaluArray{
{
NaluType: byte(h265.NALUType_VPS_NUT),
NumNalus: 1,
Nalus: []mp4.HEVCNalu{{
Length: uint16(len(h265VPS)),
NALUnit: h265VPS,
}},
},
{
NaluType: byte(h265.NALUType_SPS_NUT),
NumNalus: 1,
Nalus: []mp4.HEVCNalu{{
Length: uint16(len(h265SPS)),
NALUnit: h265SPS,
}},
},
{
NaluType: byte(h265.NALUType_PPS_NUT),
NumNalus: 1,
Nalus: []mp4.HEVCNalu{{
Length: uint16(len(h265PPS)),
NALUnit: h265PPS,
}},
},
},
}
var buf bytes.Buffer
_, err = mp4.Marshal(&buf, hvcc, mp4.Context{})
require.NoError(t, err)
err = mrw.Write(&message.ExtendedSequenceStart{
ChunkStreamID: 4,
MessageStreamID: 0x1000000,
FourCC: message.FourCCHEVC,
Config: buf.Bytes(),
})
require.NoError(t, err)
} }
c := newNoHandshakeConn(&buf) c := newNoHandshakeConn(&buf)

Loading…
Cancel
Save