mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-10-24 08:03:34 +08:00
support publishing AV1/H265 with OBS 30 (#2217) (#2234)
Some checks reported warnings
lint / code (push) Has been cancelled
lint / mod-tidy (push) Has been cancelled
lint / apidocs (push) Has been cancelled
test / test64 (push) Has been cancelled
test / test32 (push) Has been cancelled
test / test_highlevel (push) Has been cancelled
Some checks reported warnings
lint / code (push) Has been cancelled
lint / mod-tidy (push) Has been cancelled
lint / apidocs (push) Has been cancelled
test / test64 (push) Has been cancelled
test / test32 (push) Has been cancelled
test / test_highlevel (push) Has been cancelled
This commit is contained in:
@@ -77,15 +77,19 @@ func (m *Audio) Unmarshal(raw *rawmessage.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m Audio) Marshal() (*rawmessage.Message, error) {
|
||||
func (m Audio) marshalBodySize() int {
|
||||
var l int
|
||||
if m.Codec == CodecMPEG1Audio {
|
||||
l = 1 + len(m.Payload)
|
||||
} else {
|
||||
l = 2 + len(m.Payload)
|
||||
}
|
||||
body := make([]byte, l)
|
||||
return l
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m Audio) Marshal() (*rawmessage.Message, error) {
|
||||
body := make([]byte, m.marshalBodySize())
|
||||
|
||||
body[0] = m.Codec<<4 | m.Rate<<2 | m.Depth<<1 | m.Channels
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ type ExtendedCodedFrames struct {
|
||||
ChunkStreamID byte
|
||||
DTS time.Duration
|
||||
MessageStreamID uint32
|
||||
FourCC [4]byte
|
||||
FourCC FourCC
|
||||
PTSDelta time.Duration
|
||||
Payload []byte
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func (m *ExtendedCodedFrames) Unmarshal(raw *rawmessage.Message) error {
|
||||
m.ChunkStreamID = raw.ChunkStreamID
|
||||
m.DTS = raw.Timestamp
|
||||
m.MessageStreamID = raw.MessageStreamID
|
||||
copy(m.FourCC[:], raw.Body[1:5])
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
|
||||
if m.FourCC == FourCCHEVC {
|
||||
m.PTSDelta = time.Duration(uint32(raw.Body[5])<<16|uint32(raw.Body[6])<<8|uint32(raw.Body[7])) * time.Millisecond
|
||||
@@ -38,18 +38,25 @@ func (m *ExtendedCodedFrames) Unmarshal(raw *rawmessage.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m ExtendedCodedFrames) Marshal() (*rawmessage.Message, error) {
|
||||
func (m ExtendedCodedFrames) marshalBodySize() int {
|
||||
var l int
|
||||
if m.FourCC == FourCCHEVC {
|
||||
l = 8 + len(m.Payload)
|
||||
} else {
|
||||
l = 5 + len(m.Payload)
|
||||
}
|
||||
body := make([]byte, l)
|
||||
return l
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m ExtendedCodedFrames) Marshal() (*rawmessage.Message, error) {
|
||||
body := make([]byte, m.marshalBodySize())
|
||||
|
||||
body[0] = 0b10000000 | byte(ExtendedTypeCodedFrames)
|
||||
copy(body[1:5], m.FourCC[:])
|
||||
body[1] = uint8(m.FourCC >> 24)
|
||||
body[2] = uint8(m.FourCC >> 16)
|
||||
body[3] = uint8(m.FourCC >> 8)
|
||||
body[4] = uint8(m.FourCC)
|
||||
|
||||
if m.FourCC == FourCCHEVC {
|
||||
tmp := uint32(m.PTSDelta / time.Millisecond)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/rtmp/rawmessage"
|
||||
@@ -11,27 +12,38 @@ type ExtendedFramesX struct {
|
||||
ChunkStreamID byte
|
||||
DTS time.Duration
|
||||
MessageStreamID uint32
|
||||
FourCC [4]byte
|
||||
FourCC FourCC
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// Unmarshal implements Message.
|
||||
func (m *ExtendedFramesX) Unmarshal(raw *rawmessage.Message) error {
|
||||
if len(raw.Body) < 6 {
|
||||
return fmt.Errorf("not enough bytes")
|
||||
}
|
||||
|
||||
m.ChunkStreamID = raw.ChunkStreamID
|
||||
m.DTS = raw.Timestamp
|
||||
m.MessageStreamID = raw.MessageStreamID
|
||||
copy(m.FourCC[:], raw.Body[1:5])
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
m.Payload = raw.Body[5:]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m ExtendedFramesX) marshalBodySize() int {
|
||||
return 5 + len(m.Payload)
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m ExtendedFramesX) Marshal() (*rawmessage.Message, error) {
|
||||
body := make([]byte, 5+len(m.Payload))
|
||||
body := make([]byte, m.marshalBodySize())
|
||||
|
||||
body[0] = 0b10000000 | byte(ExtendedTypeFramesX)
|
||||
copy(body[1:5], m.FourCC[:])
|
||||
body[1] = uint8(m.FourCC >> 24)
|
||||
body[2] = uint8(m.FourCC >> 16)
|
||||
body[3] = uint8(m.FourCC >> 8)
|
||||
body[4] = uint8(m.FourCC)
|
||||
copy(body[5:], m.Payload)
|
||||
|
||||
return &rawmessage.Message{
|
||||
|
||||
@@ -8,12 +8,16 @@ import (
|
||||
|
||||
// ExtendedMetadata is a metadata extended message.
|
||||
type ExtendedMetadata struct {
|
||||
FourCC [4]byte
|
||||
FourCC FourCC
|
||||
}
|
||||
|
||||
// Unmarshal implements Message.
|
||||
func (m *ExtendedMetadata) Unmarshal(raw *rawmessage.Message) error {
|
||||
copy(m.FourCC[:], raw.Body[1:5])
|
||||
if len(raw.Body) != 5 {
|
||||
return fmt.Errorf("invalid body size")
|
||||
}
|
||||
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
|
||||
return fmt.Errorf("ExtendedMetadata is not implemented yet")
|
||||
}
|
||||
|
||||
@@ -8,12 +8,16 @@ import (
|
||||
|
||||
// ExtendedMPEG2TSSequenceStart is a MPEG2-TS sequence start extended message.
|
||||
type ExtendedMPEG2TSSequenceStart struct {
|
||||
FourCC [4]byte
|
||||
FourCC FourCC
|
||||
}
|
||||
|
||||
// Unmarshal implements Message.
|
||||
func (m *ExtendedMPEG2TSSequenceStart) Unmarshal(raw *rawmessage.Message) error {
|
||||
copy(m.FourCC[:], raw.Body[1:5])
|
||||
if len(raw.Body) != 5 {
|
||||
return fmt.Errorf("invalid body size")
|
||||
}
|
||||
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
|
||||
return fmt.Errorf("ExtendedMPEG2TSSequenceStart is not implemented yet")
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// ExtendedSequenceEnd is a sequence end extended message.
|
||||
type ExtendedSequenceEnd struct {
|
||||
FourCC [4]byte
|
||||
FourCC FourCC
|
||||
}
|
||||
|
||||
// Unmarshal implements Message.
|
||||
@@ -17,7 +17,7 @@ func (m *ExtendedSequenceEnd) Unmarshal(raw *rawmessage.Message) error {
|
||||
return fmt.Errorf("invalid body size")
|
||||
}
|
||||
|
||||
copy(m.FourCC[:], raw.Body[1:5])
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/bluenviron/mediamtx/internal/rtmp/rawmessage"
|
||||
)
|
||||
|
||||
@@ -8,26 +10,37 @@ import (
|
||||
type ExtendedSequenceStart struct {
|
||||
ChunkStreamID byte
|
||||
MessageStreamID uint32
|
||||
FourCC [4]byte
|
||||
FourCC FourCC
|
||||
Config []byte
|
||||
}
|
||||
|
||||
// Unmarshal implements Message.
|
||||
func (m *ExtendedSequenceStart) Unmarshal(raw *rawmessage.Message) error {
|
||||
if len(raw.Body) < 6 {
|
||||
return fmt.Errorf("not enough bytes")
|
||||
}
|
||||
|
||||
m.ChunkStreamID = raw.ChunkStreamID
|
||||
m.MessageStreamID = raw.MessageStreamID
|
||||
copy(m.FourCC[:], raw.Body[1:5])
|
||||
m.FourCC = FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
m.Config = raw.Body[5:]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m ExtendedSequenceStart) marshalBodySize() int {
|
||||
return 5 + len(m.Config)
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m ExtendedSequenceStart) Marshal() (*rawmessage.Message, error) {
|
||||
body := make([]byte, 5+len(m.Config))
|
||||
body := make([]byte, m.marshalBodySize())
|
||||
|
||||
body[0] = 0b10000000 | byte(ExtendedTypeSequenceStart)
|
||||
copy(body[1:5], m.FourCC[:])
|
||||
body[1] = uint8(m.FourCC >> 24)
|
||||
body[2] = uint8(m.FourCC >> 16)
|
||||
body[3] = uint8(m.FourCC >> 8)
|
||||
body[4] = uint8(m.FourCC)
|
||||
copy(body[5:], m.Config)
|
||||
|
||||
return &rawmessage.Message{
|
||||
|
||||
@@ -61,13 +61,13 @@ const (
|
||||
)
|
||||
|
||||
// FourCC is an identifier of a video codec.
|
||||
type FourCC [4]byte
|
||||
type FourCC uint32
|
||||
|
||||
// video codec identifiers.
|
||||
var (
|
||||
FourCCAV1 FourCC = [4]byte{'a', 'v', '0', '1'}
|
||||
FourCCVP9 FourCC = [4]byte{'v', 'p', '0', '9'}
|
||||
FourCCHEVC FourCC = [4]byte{'h', 'v', 'c', '1'}
|
||||
FourCCAV1 FourCC = 'a'<<24 | 'v'<<16 | '0'<<8 | '1'
|
||||
FourCCVP9 FourCC = 'v'<<24 | 'p'<<16 | '0'<<8 | '9'
|
||||
FourCCHEVC FourCC = 'h'<<24 | 'v'<<16 | 'c'<<8 | '1'
|
||||
)
|
||||
|
||||
// Message is a message.
|
||||
|
||||
@@ -70,8 +70,7 @@ func allocateMessage(raw *rawmessage.Message) (Message, error) {
|
||||
}
|
||||
|
||||
if (raw.Body[0] & 0b10000000) != 0 {
|
||||
var fourCC [4]byte
|
||||
copy(fourCC[:], raw.Body[1:5])
|
||||
fourCC := FourCC(raw.Body[1])<<24 | FourCC(raw.Body[2])<<16 | FourCC(raw.Body[3])<<8 | FourCC(raw.Body[4])
|
||||
|
||||
switch fourCC {
|
||||
case FourCCAV1, FourCCVP9, FourCCHEVC:
|
||||
|
||||
@@ -232,6 +232,20 @@ var readWriterCases = []struct {
|
||||
0x0a, 0x01, 0x02, 0x03,
|
||||
},
|
||||
},
|
||||
{
|
||||
"extended sequence start",
|
||||
&ExtendedSequenceStart{
|
||||
ChunkStreamID: 4,
|
||||
MessageStreamID: 0x1000000,
|
||||
FourCC: FourCCHEVC,
|
||||
Config: []byte{0x01, 0x02, 0x03},
|
||||
},
|
||||
[]byte{
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09,
|
||||
0x01, 0x00, 0x00, 0x00, 0x80, 0x68, 0x76, 0x63,
|
||||
0x31, 0x01, 0x02, 0x03,
|
||||
},
|
||||
},
|
||||
{
|
||||
"extended coded frames",
|
||||
&ExtendedCodedFrames{
|
||||
|
||||
@@ -74,9 +74,13 @@ func (m *Video) Unmarshal(raw *rawmessage.Message) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Video) marshalBodySize() int {
|
||||
return 5 + len(m.Payload)
|
||||
}
|
||||
|
||||
// Marshal implements Message.
|
||||
func (m Video) Marshal() (*rawmessage.Message, error) {
|
||||
body := make([]byte, 5+len(m.Payload))
|
||||
body := make([]byte, m.marshalBodySize())
|
||||
|
||||
if m.IsKeyFrame {
|
||||
body[0] = flvio.FRAME_KEY << 4
|
||||
|
||||
@@ -98,7 +98,8 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (formats.Format, form
|
||||
case 0:
|
||||
return false, nil
|
||||
|
||||
case message.CodecH264:
|
||||
case message.CodecH264, float64(message.FourCCAV1),
|
||||
float64(message.FourCCVP9), float64(message.FourCCHEVC):
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,58 @@ func TestReadTracks(t *testing.T) {
|
||||
0x44, 0x01, 0xc0, 0xf7, 0xc0, 0xcc, 0x90,
|
||||
}
|
||||
|
||||
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,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
videoTrack formats.Format
|
||||
@@ -180,6 +232,16 @@ func TestReadTracks(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"obs 30",
|
||||
&formats.H265{
|
||||
PayloadTyp: 96,
|
||||
VPS: h265VPS,
|
||||
SPS: h265SPS,
|
||||
PPS: h265PPS,
|
||||
},
|
||||
nil,
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
@@ -566,57 +628,46 @@ func TestReadTracks(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var spsp h265.SPS
|
||||
err = spsp.Unmarshal(h265SPS)
|
||||
var buf bytes.Buffer
|
||||
_, err = mp4.Marshal(&buf, hvcc, mp4.Context{})
|
||||
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{
|
||||
err = mrw.Write(&message.ExtendedSequenceStart{
|
||||
ChunkStreamID: 4,
|
||||
MessageStreamID: 0x1000000,
|
||||
FourCC: message.FourCCHEVC,
|
||||
Config: buf.Bytes(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
case "obs 30":
|
||||
err := mrw.Write(&message.DataAMF0{
|
||||
ChunkStreamID: 4,
|
||||
MessageStreamID: 1,
|
||||
Payload: []interface{}{
|
||||
"@setDataFrame",
|
||||
"onMetaData",
|
||||
flvio.AMFMap{
|
||||
{
|
||||
NaluType: byte(h265.NALUType_VPS_NUT),
|
||||
NumNalus: 1,
|
||||
Nalus: []mp4.HEVCNalu{{
|
||||
Length: uint16(len(h265VPS)),
|
||||
NALUnit: h265VPS,
|
||||
}},
|
||||
K: "videodatarate",
|
||||
V: float64(0),
|
||||
},
|
||||
{
|
||||
NaluType: byte(h265.NALUType_SPS_NUT),
|
||||
NumNalus: 1,
|
||||
Nalus: []mp4.HEVCNalu{{
|
||||
Length: uint16(len(h265SPS)),
|
||||
NALUnit: h265SPS,
|
||||
}},
|
||||
K: "videocodecid",
|
||||
V: float64(message.FourCCHEVC),
|
||||
},
|
||||
{
|
||||
NaluType: byte(h265.NALUType_PPS_NUT),
|
||||
NumNalus: 1,
|
||||
Nalus: []mp4.HEVCNalu{{
|
||||
Length: uint16(len(h265PPS)),
|
||||
NALUnit: h265PPS,
|
||||
}},
|
||||
K: "audiodatarate",
|
||||
V: float64(0),
|
||||
},
|
||||
{
|
||||
K: "audiocodecid",
|
||||
V: float64(0),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = mp4.Marshal(&buf, hvcc, mp4.Context{})
|
||||
|
||||
@@ -14,9 +14,9 @@ readTimeout: 10s
|
||||
# Timeout of write operations.
|
||||
writeTimeout: 10s
|
||||
# Number of read buffers.
|
||||
# A higher value allows a wider throughput, a lower value allows to save RAM.
|
||||
# A higher value allows to increase throughput, a lower value allows to save RAM.
|
||||
readBufferCount: 512
|
||||
# Maximum size of payload of outgoing UDP packets.
|
||||
# Maximum size of outgoing UDP packets.
|
||||
# This can be decreased to avoid fragmentation on networks with a low UDP MTU.
|
||||
udpMaxPayloadSize: 1472
|
||||
|
||||
|
||||
Reference in New Issue
Block a user