golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
5.0 KiB
217 lines
5.0 KiB
package rtmp |
|
|
|
import ( |
|
"time" |
|
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format" |
|
"github.com/bluenviron/mediacommon/pkg/codecs/h264" |
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg1audio" |
|
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" |
|
"github.com/notedit/rtmp/format/flv/flvio" |
|
|
|
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/h264conf" |
|
"github.com/bluenviron/mediamtx/internal/protocols/rtmp/message" |
|
) |
|
|
|
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 |
|
case 11025: |
|
return message.Rate11025 |
|
case 22050: |
|
return message.Rate22050 |
|
default: |
|
return message.Rate44100 |
|
} |
|
} |
|
|
|
func mpeg1AudioChannels(m mpeg1audio.ChannelMode) uint8 { |
|
if m == mpeg1audio.ChannelModeMono { |
|
return message.ChannelsMono |
|
} |
|
return message.ChannelsStereo |
|
} |
|
|
|
// Writer is a wrapper around Conn that provides utilities to mux outgoing data. |
|
type Writer struct { |
|
conn *Conn |
|
} |
|
|
|
// NewWriter allocates a Writer. |
|
func NewWriter(conn *Conn, videoTrack format.Format, audioTrack format.Format) (*Writer, error) { |
|
w := &Writer{ |
|
conn: conn, |
|
} |
|
|
|
err := w.writeTracks(videoTrack, audioTrack) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return w, nil |
|
} |
|
|
|
func (w *Writer) writeTracks(videoTrack format.Format, audioTrack format.Format) error { |
|
err := w.conn.Write(&message.DataAMF0{ |
|
ChunkStreamID: 4, |
|
MessageStreamID: 0x1000000, |
|
Payload: []interface{}{ |
|
"@setDataFrame", |
|
"onMetaData", |
|
flvio.AMFMap{ |
|
{ |
|
K: "videodatarate", |
|
V: float64(0), |
|
}, |
|
{ |
|
K: "videocodecid", |
|
V: func() float64 { |
|
switch videoTrack.(type) { |
|
case *format.H264: |
|
return message.CodecH264 |
|
|
|
default: |
|
return 0 |
|
} |
|
}(), |
|
}, |
|
{ |
|
K: "audiodatarate", |
|
V: float64(0), |
|
}, |
|
{ |
|
K: "audiocodecid", |
|
V: func() float64 { |
|
switch audioTrack.(type) { |
|
case *format.MPEG1Audio: |
|
return message.CodecMPEG1Audio |
|
|
|
case *format.MPEG4Audio: |
|
return message.CodecMPEG4Audio |
|
|
|
default: |
|
return 0 |
|
} |
|
}(), |
|
}, |
|
}, |
|
}, |
|
}) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if videoTrack, ok := videoTrack.(*format.H264); ok { |
|
// 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 |
|
|
|
if track, ok := audioTrack.(*format.MPEG4Audio); ok { |
|
audioConfig = track.GetConfig() |
|
} |
|
|
|
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, |
|
Rate: message.Rate44100, |
|
Depth: message.Depth16, |
|
Channels: message.ChannelsStereo, |
|
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, |
|
Rate: message.Rate44100, |
|
Depth: message.Depth16, |
|
Channels: message.ChannelsStereo, |
|
AACType: message.AudioAACTypeAU, |
|
Payload: au, |
|
DTS: pts, |
|
}) |
|
} |
|
|
|
// WriteMPEG1Audio writes MPEG-1 Audio data. |
|
func (w *Writer) WriteMPEG1Audio(pts time.Duration, h *mpeg1audio.FrameHeader, frame []byte) error { |
|
return w.conn.Write(&message.Audio{ |
|
ChunkStreamID: message.AudioChunkStreamID, |
|
MessageStreamID: 0x1000000, |
|
Codec: message.CodecMPEG1Audio, |
|
Rate: audioRateIntToRTMP(h.SampleRate), |
|
Depth: message.Depth16, |
|
Channels: mpeg1AudioChannels(h.ChannelMode), |
|
Payload: frame, |
|
DTS: pts, |
|
}) |
|
}
|
|
|