Browse Source

rtmp: fix publishing screen with iOS StreamLabs (#2352) (#2611)

pull/2616/head
Alessandro Ros 2 years ago committed by GitHub
parent
commit
7fb3b749d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 82
      internal/protocols/rtmp/reader.go
  2. 83
      internal/protocols/rtmp/reader_test.go

82
internal/protocols/rtmp/reader.go

@ -17,6 +17,10 @@ import (
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message" "github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
) )
const (
analyzePeriod = 1 * time.Second
)
// OnDataAV1Func is the prototype of the callback passed to OnDataAV1(). // OnDataAV1Func is the prototype of the callback passed to OnDataAV1().
type OnDataAV1Func func(pts time.Duration, tu [][]byte) type OnDataAV1Func func(pts time.Duration, tu [][]byte)
@ -161,6 +165,9 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
return nil, nil, fmt.Errorf("metadata doesn't contain any track") return nil, nil, fmt.Errorf("metadata doesn't contain any track")
} }
firstReceived := false
var startTime time.Duration
for { for {
if (!hasVideo || videoTrack != nil) && if (!hasVideo || videoTrack != nil) &&
(!hasAudio || audioTrack != nil) { (!hasAudio || audioTrack != nil) {
@ -172,22 +179,27 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
return nil, nil, err return nil, nil, err
} }
switch tmsg := msg.(type) { switch msg := msg.(type) {
case *message.Video: case *message.Video:
if !hasVideo { if !hasVideo {
return nil, nil, fmt.Errorf("unexpected video packet") return nil, nil, fmt.Errorf("unexpected video packet")
} }
if !firstReceived {
firstReceived = true
startTime = msg.DTS
}
if videoTrack == nil { if videoTrack == nil {
if tmsg.Type == message.VideoTypeConfig { if msg.Type == message.VideoTypeConfig {
videoTrack, err = trackFromH264DecoderConfig(tmsg.Payload) videoTrack, err = trackFromH264DecoderConfig(msg.Payload)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// format used by OBS < 29.1 to publish H265 // format used by OBS < 29.1 to publish H265
} else if tmsg.Type == message.VideoTypeAU && tmsg.IsKeyFrame { } else if msg.Type == message.VideoTypeAU && msg.IsKeyFrame {
nalus, err := h264.AVCCUnmarshal(tmsg.Payload) nalus, err := h264.AVCCUnmarshal(msg.Payload)
if err != nil { if err != nil {
if err == h264.ErrAVCCNoNALUs { if err == h264.ErrAVCCNoNALUs {
continue continue
@ -225,12 +237,17 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
} }
} }
// video was found, but audio was not
if videoTrack != nil && (msg.DTS-startTime) >= analyzePeriod {
return videoTrack, nil, nil
}
case *message.ExtendedSequenceStart: case *message.ExtendedSequenceStart:
if videoTrack == nil { if videoTrack == nil {
switch tmsg.FourCC { switch msg.FourCC {
case message.FourCCHEVC: case message.FourCCHEVC:
var hvcc mp4.HvcC var hvcc mp4.HvcC
_, err := mp4.Unmarshal(bytes.NewReader(tmsg.Config), uint64(len(tmsg.Config)), &hvcc, mp4.Context{}) _, err := mp4.Unmarshal(bytes.NewReader(msg.Config), uint64(len(msg.Config)), &hvcc, mp4.Context{})
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("invalid H265 configuration: %v", err) return nil, nil, fmt.Errorf("invalid H265 configuration: %v", err)
} }
@ -251,7 +268,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
case message.FourCCAV1: case message.FourCCAV1:
var av1c mp4.Av1C var av1c mp4.Av1C
_, err := mp4.Unmarshal(bytes.NewReader(tmsg.Config), uint64(len(tmsg.Config)), &av1c, mp4.Context{}) _, err := mp4.Unmarshal(bytes.NewReader(msg.Config), uint64(len(msg.Config)), &av1c, mp4.Context{})
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("invalid AV1 configuration: %v", err) return nil, nil, fmt.Errorf("invalid AV1 configuration: %v", err)
} }
@ -268,7 +285,7 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
default: // VP9 default: // VP9
var vpcc mp4.VpcC var vpcc mp4.VpcC
_, err := mp4.Unmarshal(bytes.NewReader(tmsg.Config), uint64(len(tmsg.Config)), &vpcc, mp4.Context{}) _, err := mp4.Unmarshal(bytes.NewReader(msg.Config), uint64(len(msg.Config)), &vpcc, mp4.Context{})
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("invalid VP9 configuration: %v", err) return nil, nil, fmt.Errorf("invalid VP9 configuration: %v", err)
} }
@ -285,9 +302,9 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
} }
if audioTrack == nil && if audioTrack == nil &&
tmsg.Codec == message.CodecMPEG4Audio && msg.Codec == message.CodecMPEG4Audio &&
tmsg.AACType == message.AudioAACTypeConfig { msg.AACType == message.AudioAACTypeConfig {
audioTrack, err = trackFromAACDecoderConfig(tmsg.Payload) audioTrack, err = trackFromAACDecoderConfig(msg.Payload)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -297,24 +314,24 @@ func tracksFromMetadata(conn *Conn, payload []interface{}) (format.Format, forma
} }
func tracksFromMessages(conn *Conn, msg message.Message) (format.Format, format.Format, error) { func tracksFromMessages(conn *Conn, msg message.Message) (format.Format, format.Format, error) {
var startTime *time.Duration firstReceived := false
var startTime time.Duration
var videoTrack format.Format var videoTrack format.Format
var audioTrack format.Format var audioTrack format.Format
// analyze 1 second of packets
outer: outer:
for { for {
switch tmsg := msg.(type) { switch msg := msg.(type) {
case *message.Video: case *message.Video:
if startTime == nil { if !firstReceived {
v := tmsg.DTS firstReceived = true
startTime = &v startTime = msg.DTS
} }
if tmsg.Type == message.VideoTypeConfig { if msg.Type == message.VideoTypeConfig {
if videoTrack == nil { if videoTrack == nil {
var err error var err error
videoTrack, err = trackFromH264DecoderConfig(tmsg.Payload) videoTrack, err = trackFromH264DecoderConfig(msg.Payload)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -326,20 +343,20 @@ outer:
} }
} }
if (tmsg.DTS - *startTime) >= 1*time.Second { if (msg.DTS - startTime) >= analyzePeriod {
break outer break outer
} }
case *message.Audio: case *message.Audio:
if startTime == nil { if !firstReceived {
v := tmsg.DTS firstReceived = true
startTime = &v startTime = msg.DTS
} }
if tmsg.AACType == message.AudioAACTypeConfig { if msg.AACType == message.AudioAACTypeConfig {
if audioTrack == nil { if audioTrack == nil {
var err error var err error
audioTrack, err = trackFromAACDecoderConfig(tmsg.Payload) audioTrack, err = trackFromAACDecoderConfig(msg.Payload)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -351,7 +368,7 @@ outer:
} }
} }
if (tmsg.DTS - *startTime) >= 1*time.Second { if (msg.DTS - startTime) >= analyzePeriod {
break outer break outer
} }
} }
@ -395,11 +412,10 @@ func NewReader(conn *Conn) (*Reader, error) {
} }
func (r *Reader) readTracks() (format.Format, format.Format, error) { func (r *Reader) readTracks() (format.Format, format.Format, error) {
msg, err := func() (message.Message, error) {
for { for {
msg, err := r.conn.Read() msg, err := r.conn.Read()
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// skip play start and data start // skip play start and data start
@ -414,13 +430,6 @@ func (r *Reader) readTracks() (format.Format, format.Format, error) {
} }
} }
return msg, nil
}
}()
if err != nil {
return nil, nil, err
}
if data, ok := msg.(*message.DataAMF0); ok && len(data.Payload) >= 1 { if data, ok := msg.(*message.DataAMF0); ok && len(data.Payload) >= 1 {
payload := data.Payload payload := data.Payload
@ -442,6 +451,7 @@ func (r *Reader) readTracks() (format.Format, format.Format, error) {
return tracksFromMessages(r.conn, msg) return tracksFromMessages(r.conn, msg)
} }
}
// Tracks returns detected tracks // Tracks returns detected tracks
func (r *Reader) Tracks() (format.Format, format.Format) { func (r *Reader) Tracks() (format.Format, format.Format) {

83
internal/protocols/rtmp/reader_test.go

@ -679,6 +679,89 @@ func TestReadTracks(t *testing.T) {
}, },
}, },
}, },
{
"issue mediamtx/2352 (missing audio)",
&format.H264{
PayloadTyp: 96,
SPS: h264SPS,
PPS: h264PPS,
PacketizationMode: 1,
},
nil,
[]message.Message{
&message.DataAMF0{
ChunkStreamID: 8,
MessageStreamID: 0x1000000,
Payload: []interface{}{
"@setDataFrame",
"onMetaData",
flvio.AMFMap{
{
K: "audiodatarate",
V: float64(128),
},
{
K: "framerate",
V: float64(30),
},
{
K: "videocodecid",
V: float64(7),
},
{
K: "videodatarate",
V: float64(2500),
},
{
K: "audiocodecid",
V: float64(10),
},
{
K: "height",
V: float64(720),
},
{
K: "width",
V: float64(1280),
},
},
},
},
&message.Video{
ChunkStreamID: message.VideoChunkStreamID,
MessageStreamID: 0x1000000,
Codec: message.CodecH264,
IsKeyFrame: true,
Type: message.VideoTypeConfig,
Payload: func() []byte {
buf, _ := h264conf.Conf{
SPS: h264SPS,
PPS: h264PPS,
}.Marshal()
return buf
}(),
},
&message.Video{
ChunkStreamID: message.VideoChunkStreamID,
MessageStreamID: 0x1000000,
Codec: 0x7,
IsKeyFrame: true,
Payload: []uint8{
5,
},
},
&message.Video{
ChunkStreamID: message.VideoChunkStreamID,
MessageStreamID: 0x1000000,
Codec: 0x7,
IsKeyFrame: true,
DTS: 2 * time.Second,
Payload: []uint8{
5,
},
},
},
},
} { } {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
var buf bytes.Buffer var buf bytes.Buffer

Loading…
Cancel
Save