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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 173 additions and 65 deletions

View File

@ -1,19 +1,21 @@
package message
import (
"fmt"
"github.com/bluenviron/mediamtx/internal/rtmp/rawmessage"
)
// ExtendedSequenceStart is a sequence start extended message.
type ExtendedSequenceStart struct {
FourCC [4]byte
Config []byte
ChunkStreamID byte
MessageStreamID uint32
FourCC [4]byte
Config []byte
}
// Unmarshal implements Message.
func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error {
m.ChunkStreamID = raw.ChunkStreamID
m.MessageStreamID = raw.MessageStreamID
copy(m.FourCC[:], raw.Body[1:5])
m.Config = raw.Body[5:]
@ -22,5 +24,16 @@ func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error {
// Marshal implements Message.
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
}

View File

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

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)