mirror of
https://github.com/bluenviron/mediamtx
synced 2025-01-09 16:29:29 +00:00
73ddb21e63
* implement native recording (#1399) * support saving VP9 tracks * support saving MPEG-1 audio tracks * switch segment when codec parameters change * allow to disable recording on a path basis * allow disabling recording cleaner * support recording MPEG-1/2/4 video tracks * add microseconds to file names * add tests
365 lines
7.3 KiB
Go
365 lines
7.3 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/bluenviron/gortsplib/v4/pkg/description"
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpav1"
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp8"
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format/rtpvp9"
|
|
"github.com/pion/webrtc/v3"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/asyncwriter"
|
|
"github.com/bluenviron/mediamtx/internal/stream"
|
|
"github.com/bluenviron/mediamtx/internal/unit"
|
|
)
|
|
|
|
type webRTCOutgoingTrack struct {
|
|
sender *webrtc.RTPSender
|
|
media *description.Media
|
|
format format.Format
|
|
track *webrtc.TrackLocalStaticRTP
|
|
cb func(unit.Unit) error
|
|
}
|
|
|
|
func newWebRTCOutgoingTrackVideo(desc *description.Session) (*webRTCOutgoingTrack, error) {
|
|
var av1Format *format.AV1
|
|
videoMedia := desc.FindFormat(&av1Format)
|
|
|
|
if videoMedia != nil {
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: webrtc.MimeTypeAV1,
|
|
ClockRate: 90000,
|
|
},
|
|
"av1",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encoder := &rtpav1.Encoder{
|
|
PayloadType: 105,
|
|
PayloadMaxSize: webrtcPayloadMaxSize,
|
|
}
|
|
err = encoder.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: videoMedia,
|
|
format: av1Format,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
tunit := u.(*unit.AV1)
|
|
|
|
if tunit.TU == nil {
|
|
return nil
|
|
}
|
|
|
|
packets, err := encoder.Encode(tunit.TU)
|
|
if err != nil {
|
|
return nil //nolint:nilerr
|
|
}
|
|
|
|
for _, pkt := range packets {
|
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var vp9Format *format.VP9
|
|
videoMedia = desc.FindFormat(&vp9Format)
|
|
|
|
if videoMedia != nil { //nolint:dupl
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: webrtc.MimeTypeVP9,
|
|
ClockRate: uint32(vp9Format.ClockRate()),
|
|
},
|
|
"vp9",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encoder := &rtpvp9.Encoder{
|
|
PayloadType: 96,
|
|
PayloadMaxSize: webrtcPayloadMaxSize,
|
|
}
|
|
err = encoder.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: videoMedia,
|
|
format: vp9Format,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
tunit := u.(*unit.VP9)
|
|
|
|
if tunit.Frame == nil {
|
|
return nil
|
|
}
|
|
|
|
packets, err := encoder.Encode(tunit.Frame)
|
|
if err != nil {
|
|
return nil //nolint:nilerr
|
|
}
|
|
|
|
for _, pkt := range packets {
|
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var vp8Format *format.VP8
|
|
videoMedia = desc.FindFormat(&vp8Format)
|
|
|
|
if videoMedia != nil { //nolint:dupl
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: webrtc.MimeTypeVP8,
|
|
ClockRate: uint32(vp8Format.ClockRate()),
|
|
},
|
|
"vp8",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encoder := &rtpvp8.Encoder{
|
|
PayloadType: 96,
|
|
PayloadMaxSize: webrtcPayloadMaxSize,
|
|
}
|
|
err = encoder.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: videoMedia,
|
|
format: vp8Format,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
tunit := u.(*unit.VP8)
|
|
|
|
if tunit.Frame == nil {
|
|
return nil
|
|
}
|
|
|
|
packets, err := encoder.Encode(tunit.Frame)
|
|
if err != nil {
|
|
return nil //nolint:nilerr
|
|
}
|
|
|
|
for _, pkt := range packets {
|
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var h264Format *format.H264
|
|
videoMedia = desc.FindFormat(&h264Format)
|
|
|
|
if videoMedia != nil {
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: webrtc.MimeTypeH264,
|
|
ClockRate: uint32(h264Format.ClockRate()),
|
|
},
|
|
"h264",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encoder := &rtph264.Encoder{
|
|
PayloadType: 96,
|
|
PayloadMaxSize: webrtcPayloadMaxSize,
|
|
}
|
|
err = encoder.Init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
firstReceived := false
|
|
var lastPTS time.Duration
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: videoMedia,
|
|
format: h264Format,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
tunit := u.(*unit.H264)
|
|
|
|
if tunit.AU == nil {
|
|
return nil
|
|
}
|
|
|
|
if !firstReceived {
|
|
firstReceived = true
|
|
} else if tunit.PTS < lastPTS {
|
|
return fmt.Errorf("WebRTC doesn't support H264 streams with B-frames")
|
|
}
|
|
lastPTS = tunit.PTS
|
|
|
|
packets, err := encoder.Encode(tunit.AU)
|
|
if err != nil {
|
|
return nil //nolint:nilerr
|
|
}
|
|
|
|
for _, pkt := range packets {
|
|
pkt.Timestamp = tunit.RTPPackets[0].Timestamp
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func newWebRTCOutgoingTrackAudio(desc *description.Session) (*webRTCOutgoingTrack, error) {
|
|
var opusFormat *format.Opus
|
|
audioMedia := desc.FindFormat(&opusFormat)
|
|
|
|
if audioMedia != nil {
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: webrtc.MimeTypeOpus,
|
|
ClockRate: uint32(opusFormat.ClockRate()),
|
|
Channels: 2,
|
|
},
|
|
"opus",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: audioMedia,
|
|
format: opusFormat,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
for _, pkt := range u.GetRTPPackets() {
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var g722Format *format.G722
|
|
audioMedia = desc.FindFormat(&g722Format)
|
|
|
|
if audioMedia != nil {
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: webrtc.MimeTypeG722,
|
|
ClockRate: uint32(g722Format.ClockRate()),
|
|
},
|
|
"g722",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: audioMedia,
|
|
format: g722Format,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
for _, pkt := range u.GetRTPPackets() {
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var g711Format *format.G711
|
|
audioMedia = desc.FindFormat(&g711Format)
|
|
|
|
if audioMedia != nil {
|
|
var mtyp string
|
|
if g711Format.MULaw {
|
|
mtyp = webrtc.MimeTypePCMU
|
|
} else {
|
|
mtyp = webrtc.MimeTypePCMA
|
|
}
|
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP(
|
|
webrtc.RTPCodecCapability{
|
|
MimeType: mtyp,
|
|
ClockRate: uint32(g711Format.ClockRate()),
|
|
},
|
|
"g711",
|
|
webrtcStreamID,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &webRTCOutgoingTrack{
|
|
media: audioMedia,
|
|
format: g711Format,
|
|
track: webRTCTrak,
|
|
cb: func(u unit.Unit) error {
|
|
for _, pkt := range u.GetRTPPackets() {
|
|
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (t *webRTCOutgoingTrack) start(
|
|
stream *stream.Stream,
|
|
writer *asyncwriter.Writer,
|
|
) {
|
|
// read incoming RTCP packets to make interceptors work
|
|
go func() {
|
|
buf := make([]byte, 1500)
|
|
for {
|
|
_, _, err := t.sender.Read(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
stream.AddReader(writer, t.media, t.format, t.cb)
|
|
}
|