2023-07-30 21:15:22 +00:00
|
|
|
package rtmp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
2023-07-30 21:15:22 +00:00
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
|
2023-08-05 12:47:20 +00:00
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio"
|
2023-07-30 21:15:22 +00:00
|
|
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
|
|
|
|
|
2024-02-24 18:11:42 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/amf0"
|
2023-10-26 19:46:18 +00:00
|
|
|
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/h264conf"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message"
|
2023-07-30 21:15:22 +00:00
|
|
|
)
|
|
|
|
|
2024-01-07 16:02:22 +00:00
|
|
|
func audioRateRTMPToInt(v uint8) int {
|
|
|
|
switch v {
|
|
|
|
case message.Rate5512:
|
|
|
|
return 5512
|
|
|
|
case message.Rate11025:
|
|
|
|
return 11025
|
|
|
|
case message.Rate22050:
|
|
|
|
return 22050
|
|
|
|
default:
|
|
|
|
return 44100
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func audioRateIntToRTMP(v int) uint8 {
|
|
|
|
switch v {
|
|
|
|
case 5512:
|
|
|
|
return message.Rate5512
|
2023-07-30 21:15:22 +00:00
|
|
|
case 11025:
|
2024-01-07 16:02:22 +00:00
|
|
|
return message.Rate11025
|
2023-07-30 21:15:22 +00:00
|
|
|
case 22050:
|
2024-01-07 16:02:22 +00:00
|
|
|
return message.Rate22050
|
2023-07-30 21:15:22 +00:00
|
|
|
default:
|
2024-01-07 16:02:22 +00:00
|
|
|
return message.Rate44100
|
2023-07-30 21:15:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-21 15:29:13 +00:00
|
|
|
func mpeg1AudioChannels(m mpeg1audio.ChannelMode) bool {
|
|
|
|
return m != mpeg1audio.ChannelModeMono
|
2023-07-30 21:15:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Writer is a wrapper around Conn that provides utilities to mux outgoing data.
|
|
|
|
type Writer struct {
|
|
|
|
conn *Conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewWriter allocates a Writer.
|
2023-08-26 16:54:28 +00:00
|
|
|
func NewWriter(conn *Conn, videoTrack format.Format, audioTrack format.Format) (*Writer, error) {
|
2023-07-30 21:15:22 +00:00
|
|
|
w := &Writer{
|
|
|
|
conn: conn,
|
|
|
|
}
|
|
|
|
|
|
|
|
err := w.writeTracks(videoTrack, audioTrack)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return w, nil
|
|
|
|
}
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format) error {
|
2023-07-30 21:15:22 +00:00
|
|
|
err := w.conn.Write(&message.DataAMF0{
|
|
|
|
ChunkStreamID: 4,
|
|
|
|
MessageStreamID: 0x1000000,
|
|
|
|
Payload: []interface{}{
|
|
|
|
"@setDataFrame",
|
|
|
|
"onMetaData",
|
2024-02-24 18:11:42 +00:00
|
|
|
amf0.Object{
|
2023-07-30 21:15:22 +00:00
|
|
|
{
|
2024-02-24 18:11:42 +00:00
|
|
|
Key: "videodatarate",
|
|
|
|
Value: float64(0),
|
2023-07-30 21:15:22 +00:00
|
|
|
},
|
|
|
|
{
|
2024-02-24 18:11:42 +00:00
|
|
|
Key: "videocodecid",
|
|
|
|
Value: func() float64 {
|
2023-07-30 21:15:22 +00:00
|
|
|
switch videoTrack.(type) {
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.H264:
|
2023-07-30 21:15:22 +00:00
|
|
|
return message.CodecH264
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
{
|
2024-02-24 18:11:42 +00:00
|
|
|
Key: "audiodatarate",
|
|
|
|
Value: float64(0),
|
2023-07-30 21:15:22 +00:00
|
|
|
},
|
|
|
|
{
|
2024-02-24 18:11:42 +00:00
|
|
|
Key: "audiocodecid",
|
|
|
|
Value: func() float64 {
|
2023-07-30 21:15:22 +00:00
|
|
|
switch audioTrack.(type) {
|
2023-08-26 16:54:28 +00:00
|
|
|
case *format.MPEG1Audio:
|
2023-08-05 12:47:20 +00:00
|
|
|
return message.CodecMPEG1Audio
|
2023-07-30 21:15:22 +00:00
|
|
|
|
2023-09-21 15:21:18 +00:00
|
|
|
case *format.MPEG4Audio:
|
2023-07-30 21:15:22 +00:00
|
|
|
return message.CodecMPEG4Audio
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-26 16:54:28 +00:00
|
|
|
if videoTrack, ok := videoTrack.(*format.H264); ok {
|
2023-07-30 21:15:22 +00:00
|
|
|
// write decoder config only if SPS and PPS are available.
|
|
|
|
// if they're not available yet, they're sent later.
|
|
|
|
if sps, pps := videoTrack.SafeParams(); sps != nil && pps != nil {
|
|
|
|
buf, _ := h264conf.Conf{
|
|
|
|
SPS: sps,
|
|
|
|
PPS: pps,
|
|
|
|
}.Marshal()
|
|
|
|
|
|
|
|
err = w.conn.Write(&message.Video{
|
|
|
|
ChunkStreamID: message.VideoChunkStreamID,
|
|
|
|
MessageStreamID: 0x1000000,
|
|
|
|
Codec: message.CodecH264,
|
|
|
|
IsKeyFrame: true,
|
|
|
|
Type: message.VideoTypeConfig,
|
|
|
|
Payload: buf,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var audioConfig *mpeg4audio.AudioSpecificConfig
|
|
|
|
|
2023-09-21 15:21:18 +00:00
|
|
|
if track, ok := audioTrack.(*format.MPEG4Audio); ok {
|
|
|
|
audioConfig = track.GetConfig()
|
2023-07-30 21:15:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if audioConfig != nil {
|
|
|
|
enc, err := audioConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = w.conn.Write(&message.Audio{
|
|
|
|
ChunkStreamID: message.AudioChunkStreamID,
|
|
|
|
MessageStreamID: 0x1000000,
|
|
|
|
Codec: message.CodecMPEG4Audio,
|
2024-01-07 16:02:22 +00:00
|
|
|
Rate: message.Rate44100,
|
|
|
|
Depth: message.Depth16,
|
2024-01-21 15:29:13 +00:00
|
|
|
IsStereo: true,
|
2023-07-30 21:15:22 +00:00
|
|
|
AACType: message.AudioAACTypeConfig,
|
|
|
|
Payload: enc,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteH264 writes H264 data.
|
|
|
|
func (w *Writer) WriteH264(pts time.Duration, dts time.Duration, idrPresent bool, au [][]byte) error {
|
|
|
|
avcc, err := h264.AVCCMarshal(au)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return w.conn.Write(&message.Video{
|
|
|
|
ChunkStreamID: message.VideoChunkStreamID,
|
|
|
|
MessageStreamID: 0x1000000,
|
|
|
|
Codec: message.CodecH264,
|
|
|
|
IsKeyFrame: idrPresent,
|
|
|
|
Type: message.VideoTypeAU,
|
|
|
|
Payload: avcc,
|
|
|
|
DTS: dts,
|
|
|
|
PTSDelta: pts - dts,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriteMPEG4Audio writes MPEG-4 Audio data.
|
|
|
|
func (w *Writer) WriteMPEG4Audio(pts time.Duration, au []byte) error {
|
|
|
|
return w.conn.Write(&message.Audio{
|
|
|
|
ChunkStreamID: message.AudioChunkStreamID,
|
|
|
|
MessageStreamID: 0x1000000,
|
|
|
|
Codec: message.CodecMPEG4Audio,
|
2024-01-07 16:02:22 +00:00
|
|
|
Rate: message.Rate44100,
|
|
|
|
Depth: message.Depth16,
|
2024-01-21 15:29:13 +00:00
|
|
|
IsStereo: true,
|
2023-07-30 21:15:22 +00:00
|
|
|
AACType: message.AudioAACTypeAU,
|
|
|
|
Payload: au,
|
|
|
|
DTS: pts,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-08-05 12:47:20 +00:00
|
|
|
// WriteMPEG1Audio writes MPEG-1 Audio data.
|
|
|
|
func (w *Writer) WriteMPEG1Audio(pts time.Duration, h *mpeg1audio.FrameHeader, frame []byte) error {
|
2023-07-30 21:15:22 +00:00
|
|
|
return w.conn.Write(&message.Audio{
|
|
|
|
ChunkStreamID: message.AudioChunkStreamID,
|
|
|
|
MessageStreamID: 0x1000000,
|
2023-08-05 12:47:20 +00:00
|
|
|
Codec: message.CodecMPEG1Audio,
|
2024-01-07 16:02:22 +00:00
|
|
|
Rate: audioRateIntToRTMP(h.SampleRate),
|
|
|
|
Depth: message.Depth16,
|
2024-01-21 15:29:13 +00:00
|
|
|
IsStereo: mpeg1AudioChannels(h.ChannelMode),
|
2023-07-30 21:15:22 +00:00
|
|
|
Payload: frame,
|
|
|
|
DTS: pts,
|
|
|
|
})
|
|
|
|
}
|