From 4aef466103f008245beaa43f8f5ab0f262ce4832 Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Tue, 27 Jun 2023 22:15:50 +0200 Subject: [PATCH] webrtc: allow setting Opus bitrate (#1908) (#1985) --- internal/core/webrtc_http_server.go | 1 + internal/core/webrtc_manager.go | 1 + internal/core/webrtc_pc.go | 250 ++++++++++-------------- internal/core/webrtc_publish_index.html | 5 + internal/core/webrtc_session.go | 76 +++++-- 5 files changed, 173 insertions(+), 160 deletions(-) diff --git a/internal/core/webrtc_http_server.go b/internal/core/webrtc_http_server.go index 4d4a5606..f7edd842 100644 --- a/internal/core/webrtc_http_server.go +++ b/internal/core/webrtc_http_server.go @@ -304,6 +304,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) { videoCodec: ctx.Query("video_codec"), audioCodec: ctx.Query("audio_codec"), videoBitrate: ctx.Query("video_bitrate"), + audioBitrate: ctx.Query("audio_bitrate"), }) if res.err != nil { if res.errStatusCode != 0 { diff --git a/internal/core/webrtc_manager.go b/internal/core/webrtc_manager.go index c29f45bf..23f85bad 100644 --- a/internal/core/webrtc_manager.go +++ b/internal/core/webrtc_manager.go @@ -137,6 +137,7 @@ type webRTCSessionNewReq struct { videoCodec string audioCodec string videoBitrate string + audioBitrate string res chan webRTCSessionNewRes } diff --git a/internal/core/webrtc_pc.go b/internal/core/webrtc_pc.go index 14a35dcb..e2a28ecb 100644 --- a/internal/core/webrtc_pc.go +++ b/internal/core/webrtc_pc.go @@ -11,6 +11,82 @@ import ( "github.com/bluenviron/mediamtx/internal/logger" ) +var videoCodecs = map[string][]webrtc.RTPCodecParameters{ + "av1": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeAV1, + ClockRate: 90000, + }, + PayloadType: 96, + }}, + "vp9": { + { + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeVP9, + ClockRate: 90000, + SDPFmtpLine: "profile-id=0", + }, + PayloadType: 96, + }, + { + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeVP9, + ClockRate: 90000, + SDPFmtpLine: "profile-id=1", + }, + PayloadType: 96, + }, + }, + "vp8": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeVP8, + ClockRate: 90000, + }, + PayloadType: 96, + }}, + "h264": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeH264, + ClockRate: 90000, + SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", + }, + PayloadType: 96, + }}, +} + +var audioCodecs = map[string][]webrtc.RTPCodecParameters{ + "opus": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeOpus, + ClockRate: 48000, + Channels: 2, + SDPFmtpLine: "minptime=10;useinbandfec=1", + }, + PayloadType: 111, + }}, + "g722": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypeG722, + ClockRate: 8000, + }, + PayloadType: 9, + }}, + "pcmu": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypePCMU, + ClockRate: 8000, + }, + PayloadType: 0, + }}, + "pcma": {{ + RTPCodecCapability: webrtc.RTPCodecCapability{ + MimeType: webrtc.MimeTypePCMA, + ClockRate: 8000, + }, + PayloadType: 8, + }}, +} + type peerConnection struct { *webrtc.PeerConnection stateChangeMutex sync.Mutex @@ -49,156 +125,42 @@ func newPeerConnection( mediaEngine := &webrtc.MediaEngine{} if videoCodec != "" || audioCodec != "" { - switch videoCodec { - case "av1": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeAV1, - ClockRate: 90000, - }, - PayloadType: 96, - }, - webrtc.RTPCodecTypeVideo) - if err != nil { - return nil, err - } - - case "vp9": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeVP9, - ClockRate: 90000, - SDPFmtpLine: "profile-id=0", - }, - PayloadType: 96, - }, - webrtc.RTPCodecTypeVideo) - if err != nil { - return nil, err - } - - err = mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeVP9, - ClockRate: 90000, - SDPFmtpLine: "profile-id=1", - }, - PayloadType: 96, - }, - webrtc.RTPCodecTypeVideo) - if err != nil { - return nil, err - } - - case "vp8": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeVP8, - ClockRate: 90000, - }, - PayloadType: 96, - }, - webrtc.RTPCodecTypeVideo) - if err != nil { - return nil, err - } - - case "h264": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeH264, - ClockRate: 90000, - SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", - }, - PayloadType: 96, - }, - webrtc.RTPCodecTypeVideo) - if err != nil { - return nil, err + codec, ok := videoCodecs[videoCodec] + if ok { + for _, params := range codec { + err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeVideo) + if err != nil { + return nil, err + } } } - switch audioCodec { - case "opus": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeOpus, - ClockRate: 48000, - Channels: 2, - SDPFmtpLine: "minptime=10;useinbandfec=1", - }, - PayloadType: 111, - }, - webrtc.RTPCodecTypeAudio) - if err != nil { - return nil, err - } - - case "g722": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeG722, - ClockRate: 8000, - }, - PayloadType: 9, - }, - webrtc.RTPCodecTypeAudio) - if err != nil { - return nil, err - } - - case "pcmu": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypePCMU, - ClockRate: 8000, - }, - PayloadType: 0, - }, - webrtc.RTPCodecTypeAudio) - if err != nil { - return nil, err - } - - case "pcma": - err := mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypePCMA, - ClockRate: 8000, - }, - PayloadType: 8, - }, - webrtc.RTPCodecTypeAudio) - if err != nil { - return nil, err + codec, ok = audioCodecs[audioCodec] + if ok { + for _, params := range codec { + err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeAudio) + if err != nil { + return nil, err + } } } - } else { - // register all codecs - err := mediaEngine.RegisterDefaultCodecs() - if err != nil { - return nil, err + } else { // register all codecs + for _, codec := range videoCodecs { + for _, params := range codec { + err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeVideo) + if err != nil { + return nil, err + } + } } - err = mediaEngine.RegisterCodec( - webrtc.RTPCodecParameters{ - RTPCodecCapability: webrtc.RTPCodecCapability{ - MimeType: webrtc.MimeTypeAV1, - ClockRate: 90000, - }, - PayloadType: 105, - }, - webrtc.RTPCodecTypeVideo) - if err != nil { - return nil, err + + for _, codec := range audioCodecs { + for _, params := range codec { + err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeAudio) + if err != nil { + return nil, err + } + } } } diff --git a/internal/core/webrtc_publish_index.html b/internal/core/webrtc_publish_index.html index 6792d4ce..44adf3dc 100644 --- a/internal/core/webrtc_publish_index.html +++ b/internal/core/webrtc_publish_index.html @@ -72,6 +72,9 @@ select {
video bitrate (kbps): + + audio bitrate (kbps): +
@@ -222,11 +225,13 @@ class Transmitter { const videoCodec = document.getElementById('video_codec').value; const audioCodec = document.getElementById('audio_codec').value; const videoBitrate = document.getElementById('video_bitrate').value; + const audioBitrate = document.getElementById('audio_bitrate').value; const p = new URLSearchParams(window.location.search); p.set('video_codec', videoCodec); p.set('audio_codec', audioCodec); p.set('video_bitrate', videoBitrate); + p.set('audio_bitrate', audioBitrate); fetch(new URL('whip', window.location.href) + '?' + p.toString(), { method: 'POST', diff --git a/internal/core/webrtc_session.go b/internal/core/webrtc_session.go index cd736553..be961448 100644 --- a/internal/core/webrtc_session.go +++ b/internal/core/webrtc_session.go @@ -48,28 +48,76 @@ func mediasOfIncomingTracks(tracks []*webRTCIncomingTrack) media.Medias { return ret } -func insertTias(offer *webrtc.SessionDescription, value uint64) { +func findOpusPayloadFormat(attributes []sdp.Attribute) int { + for _, attr := range attributes { + if attr.Key == "rtpmap" && strings.Contains(attr.Value, "opus/") { + parts := strings.SplitN(attr.Value, " ", 2) + pl, err := strconv.ParseUint(parts[0], 10, 31) + if err == nil { + return int(pl) + } + } + } + return 0 +} + +func editAnswer(offer *webrtc.SessionDescription, videoBitrateStr string, audioBitrateStr string) error { var sd sdp.SessionDescription err := sd.Unmarshal([]byte(offer.SDP)) if err != nil { - return + return err } - for _, media := range sd.MediaDescriptions { - if media.MediaName.Media == "video" { - media.Bandwidth = []sdp.Bandwidth{{ - Type: "TIAS", - Bandwidth: value, - }} + if videoBitrateStr != "" { + videoBitrate, err := strconv.ParseUint(videoBitrateStr, 10, 31) + if err != nil { + return err + } + + for _, media := range sd.MediaDescriptions { + if media.MediaName.Media == "video" { + media.Bandwidth = []sdp.Bandwidth{{ + Type: "TIAS", + Bandwidth: videoBitrate * 1024, + }} + break + } + } + } + + if audioBitrateStr != "" { + audioBitrate, err := strconv.ParseUint(audioBitrateStr, 10, 31) + if err != nil { + return err + } + + for _, media := range sd.MediaDescriptions { + if media.MediaName.Media == "audio" { + pl := findOpusPayloadFormat(media.Attributes) + if pl != 0 { + for i, attr := range media.Attributes { + if attr.Key == "fmtp" && strings.HasPrefix(attr.Value, strconv.FormatInt(int64(pl), 10)+" ") { + media.Attributes[i] = sdp.Attribute{ + Key: "fmtp", + Value: strconv.FormatInt(int64(pl), 10) + " stereo=1;sprop-stereo=1;maxaveragebitrate=" + + strconv.FormatUint(audioBitrate*1024, 10), + } + } + } + } + + break + } } } enc, err := sd.Marshal() if err != nil { - return + return err } offer.SDP = string(enc) + return nil } func gatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, error) { @@ -320,13 +368,9 @@ func (s *webRTCSession) runPublish() (int, error) { tmp := pc.LocalDescription() answer = *tmp - if s.req.videoBitrate != "" { - tmp, err := strconv.ParseUint(s.req.videoBitrate, 10, 31) - if err != nil { - return http.StatusBadRequest, err - } - - insertTias(&answer, tmp*1024) + err = editAnswer(&answer, s.req.videoBitrate, s.req.audioBitrate) + if err != nil { + return http.StatusBadRequest, err } err = s.writeAnswer(&answer)