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>
This commit is contained in:
Xavier Hallade
2023-08-22 22:35:09 +02:00
committed by GitHub
parent c4409026e6
commit accfc49f9c
3 changed files with 173 additions and 65 deletions

View File

@@ -5,8 +5,10 @@ import (
"testing"
"time"
"github.com/abema/go-mp4"
"github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/notedit/rtmp/format/flv/flvio"
"github.com/stretchr/testify/require"
@@ -17,16 +19,37 @@ import (
)
func TestReadTracks(t *testing.T) {
sps := []byte{
h264SPS := []byte{
0x67, 0x64, 0x00, 0x0c, 0xac, 0x3b, 0x50, 0xb0,
0x4b, 0x42, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
0x00, 0x03, 0x00, 0x3d, 0x08,
}
pps := []byte{
h264PPS := []byte{
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 {
name string
videoTrack formats.Format
@@ -36,8 +59,8 @@ func TestReadTracks(t *testing.T) {
"video+audio",
&formats.H264{
PayloadTyp: 96,
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
PacketizationMode: 1,
},
&formats.MPEG4Audio{
@@ -56,8 +79,8 @@ func TestReadTracks(t *testing.T) {
"video",
&formats.H264{
PayloadTyp: 96,
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
PacketizationMode: 1,
},
nil,
@@ -66,8 +89,8 @@ func TestReadTracks(t *testing.T) {
"metadata without codec id, video+audio",
&formats.H264{
PayloadTyp: 96,
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
PacketizationMode: 1,
},
&formats.MPEG4Audio{
@@ -86,8 +109,8 @@ func TestReadTracks(t *testing.T) {
"metadata without codec id, video only",
&formats.H264{
PayloadTyp: 96,
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
PacketizationMode: 1,
},
nil,
@@ -96,8 +119,8 @@ func TestReadTracks(t *testing.T) {
"missing metadata, video+audio",
&formats.H264{
PayloadTyp: 96,
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
PacketizationMode: 1,
},
&formats.MPEG4Audio{
@@ -131,24 +154,9 @@ func TestReadTracks(t *testing.T) {
"obs studio pre 29.1 h265",
&formats.H265{
PayloadTyp: 96,
VPS: []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,
},
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,
},
VPS: h265VPS,
SPS: h265SPS,
PPS: h265PPS,
},
&formats.MPEG4Audio{
PayloadTyp: 96,
@@ -162,6 +170,16 @@ func TestReadTracks(t *testing.T) {
IndexDeltaLength: 3,
},
},
{
"xplit broadcaster",
&formats.H265{
PayloadTyp: 96,
VPS: h265VPS,
SPS: h265SPS,
PPS: h265PPS,
},
nil,
},
} {
t.Run(ca.name, func(t *testing.T) {
var buf bytes.Buffer
@@ -199,8 +217,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err)
buf, _ := h264conf.Conf{
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
}.Marshal()
err = mrw.Write(&message.Video{
@@ -262,8 +280,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err)
buf, _ := h264conf.Conf{
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
}.Marshal()
err = mrw.Write(&message.Video{
@@ -302,8 +320,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err)
buf, _ := h264conf.Conf{
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
}.Marshal()
err = mrw.Write(&message.Video{
@@ -361,8 +379,8 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err)
buf, _ := h264conf.Conf{
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
}.Marshal()
err = mrw.Write(&message.Video{
@@ -388,8 +406,8 @@ func TestReadTracks(t *testing.T) {
case "missing metadata, video+audio":
buf, _ := h264conf.Conf{
SPS: sps,
PPS: pps,
SPS: h264SPS,
PPS: h264PPS,
}.Marshal()
err := mrw.Write(&message.Video{
@@ -484,25 +502,9 @@ func TestReadTracks(t *testing.T) {
require.NoError(t, err)
avcc, err := h264.AVCCMarshal([][]byte{
{ // VPS
0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x40,
0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00,
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,
},
h265VPS,
h265SPS,
h265PPS,
})
require.NoError(t, err)
@@ -534,6 +536,99 @@ func TestReadTracks(t *testing.T) {
Payload: enc,
})
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)