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.
364 lines
7.3 KiB
364 lines
7.3 KiB
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) |
|
}
|
|
|