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 @@ @@ -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 { @@ -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
}

2
internal/rtmp/reader.go

@ -103,7 +103,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form @@ -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
}
}

213
internal/rtmp/reader_test.go

@ -5,8 +5,10 @@ import ( @@ -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 ( @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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)

Loading…
Cancel
Save