mirror of
https://github.com/aler9/rtsp-simple-server
synced 2025-10-06 16:17:59 +08:00
@@ -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
|
||||||
|
|
||||||
@@ -441,6 +450,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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user