Files
rtsp-simple-server/internal/core/webrtc_outgoing_track.go
Alessandro Ros e0fb11040e
Some checks reported warnings
lint / code (push) Has been cancelled
lint / mod-tidy (push) Has been cancelled
lint / apidocs (push) Has been cancelled
test / test64 (push) Has been cancelled
test / test32 (push) Has been cancelled
test / test_highlevel (push) Has been cancelled
move units into dedicated package (#2245)
needed by #2244
2023-08-25 18:11:02 +02:00

378 lines
7.4 KiB
Go

package core
import (
"context"
"fmt"
"time"
"github.com/bluenviron/gortsplib/v3/pkg/formats"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpav1"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8"
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9"
"github.com/bluenviron/gortsplib/v3/pkg/media"
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer"
"github.com/pion/webrtc/v3"
"github.com/bluenviron/mediamtx/internal/stream"
"github.com/bluenviron/mediamtx/internal/unit"
)
type webRTCOutgoingTrack struct {
sender *webrtc.RTPSender
media *media.Media
format formats.Format
track *webrtc.TrackLocalStaticRTP
cb func(unit.Unit) error
}
func newWebRTCOutgoingTrackVideo(medias media.Medias) (*webRTCOutgoingTrack, error) {
var av1Format *formats.AV1
videoMedia := medias.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, tunit.PTS)
if err != nil {
return nil //nolint:nilerr
}
for _, pkt := range packets {
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
}
return nil
},
}, nil
}
var vp9Format *formats.VP9
videoMedia = medias.FindFormat(&vp9Format)
if videoMedia != nil {
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, tunit.PTS)
if err != nil {
return nil //nolint:nilerr
}
for _, pkt := range packets {
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
}
return nil
},
}, nil
}
var vp8Format *formats.VP8
videoMedia = medias.FindFormat(&vp8Format)
if videoMedia != nil {
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, tunit.PTS)
if err != nil {
return nil //nolint:nilerr
}
for _, pkt := range packets {
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
}
return nil
},
}, nil
}
var h264Format *formats.H264
videoMedia = medias.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
}
var lastPTS time.Duration
firstNALUReceived := false
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 !firstNALUReceived {
firstNALUReceived = true
lastPTS = tunit.PTS
} 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, tunit.PTS)
if err != nil {
return nil //nolint:nilerr
}
for _, pkt := range packets {
webRTCTrak.WriteRTP(pkt) //nolint:errcheck
}
return nil
},
}, nil
}
return nil, nil
}
func newWebRTCOutgoingTrackAudio(medias media.Medias) (*webRTCOutgoingTrack, error) {
var opusFormat *formats.Opus
audioMedia := medias.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 *formats.G722
audioMedia = medias.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 *formats.G711
audioMedia = medias.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(
ctx context.Context,
r reader,
stream *stream.Stream,
ringBuffer *ringbuffer.RingBuffer,
writeError chan error,
) {
// 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(r, t.media, t.format, func(u unit.Unit) {
ringBuffer.Push(func() {
err := t.cb(u)
if err != nil {
select {
case writeError <- err:
case <-ctx.Done():
}
}
})
})
}