Browse Source

webrtc: allow setting Opus bitrate (#1908) (#1985)

pull/1989/head
Alessandro Ros 3 years ago committed by GitHub
parent
commit
4aef466103
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      internal/core/webrtc_http_server.go
  2. 1
      internal/core/webrtc_manager.go
  3. 248
      internal/core/webrtc_pc.go
  4. 5
      internal/core/webrtc_publish_index.html
  5. 76
      internal/core/webrtc_session.go

1
internal/core/webrtc_http_server.go

@ -304,6 +304,7 @@ func (s *webRTCHTTPServer) onRequest(ctx *gin.Context) {
videoCodec: ctx.Query("video_codec"), videoCodec: ctx.Query("video_codec"),
audioCodec: ctx.Query("audio_codec"), audioCodec: ctx.Query("audio_codec"),
videoBitrate: ctx.Query("video_bitrate"), videoBitrate: ctx.Query("video_bitrate"),
audioBitrate: ctx.Query("audio_bitrate"),
}) })
if res.err != nil { if res.err != nil {
if res.errStatusCode != 0 { if res.errStatusCode != 0 {

1
internal/core/webrtc_manager.go

@ -137,6 +137,7 @@ type webRTCSessionNewReq struct {
videoCodec string videoCodec string
audioCodec string audioCodec string
videoBitrate string videoBitrate string
audioBitrate string
res chan webRTCSessionNewRes res chan webRTCSessionNewRes
} }

248
internal/core/webrtc_pc.go

@ -11,6 +11,82 @@ import (
"github.com/bluenviron/mediamtx/internal/logger" "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 { type peerConnection struct {
*webrtc.PeerConnection *webrtc.PeerConnection
stateChangeMutex sync.Mutex stateChangeMutex sync.Mutex
@ -49,157 +125,43 @@ func newPeerConnection(
mediaEngine := &webrtc.MediaEngine{} mediaEngine := &webrtc.MediaEngine{}
if videoCodec != "" || audioCodec != "" { if videoCodec != "" || audioCodec != "" {
switch videoCodec { codec, ok := videoCodecs[videoCodec]
case "av1": if ok {
err := mediaEngine.RegisterCodec( for _, params := range codec {
webrtc.RTPCodecParameters{ err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeVideo)
RTPCodecCapability: webrtc.RTPCodecCapability{ if err != nil {
MimeType: webrtc.MimeTypeAV1, return nil, err
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
} }
} }
switch audioCodec { codec, ok = audioCodecs[audioCodec]
case "opus": if ok {
err := mediaEngine.RegisterCodec( for _, params := range codec {
webrtc.RTPCodecParameters{ err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeAudio)
RTPCodecCapability: webrtc.RTPCodecCapability{ if err != nil {
MimeType: webrtc.MimeTypeOpus, return nil, err
ClockRate: 48000, }
Channels: 2,
SDPFmtpLine: "minptime=10;useinbandfec=1",
},
PayloadType: 111,
},
webrtc.RTPCodecTypeAudio)
if err != nil {
return nil, err
} }
}
case "g722": } else { // register all codecs
err := mediaEngine.RegisterCodec( for _, codec := range videoCodecs {
webrtc.RTPCodecParameters{ for _, params := range codec {
RTPCodecCapability: webrtc.RTPCodecCapability{ err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeVideo)
MimeType: webrtc.MimeTypeG722, if err != nil {
ClockRate: 8000, return nil, err
}, }
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": for _, codec := range audioCodecs {
err := mediaEngine.RegisterCodec( for _, params := range codec {
webrtc.RTPCodecParameters{ err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeAudio)
RTPCodecCapability: webrtc.RTPCodecCapability{ if err != nil {
MimeType: webrtc.MimeTypePCMA, return nil, err
ClockRate: 8000, }
},
PayloadType: 8,
},
webrtc.RTPCodecTypeAudio)
if err != nil {
return nil, err
} }
} }
} else {
// register all codecs
err := mediaEngine.RegisterDefaultCodecs()
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
}
} }
interceptorRegistry := &interceptor.Registry{} interceptorRegistry := &interceptor.Registry{}

5
internal/core/webrtc_publish_index.html

@ -72,6 +72,9 @@ select {
<div id="bitrate_line"> <div id="bitrate_line">
video bitrate (kbps): video bitrate (kbps):
<input id="video_bitrate" type="text" value="10000" /> <input id="video_bitrate" type="text" value="10000" />
audio bitrate (kbps):
<input id="audio_bitrate" type="text" value="32" />
</div> </div>
<div id="submit_line"> <div id="submit_line">
<button id="publish_confirm">publish</button> <button id="publish_confirm">publish</button>
@ -222,11 +225,13 @@ class Transmitter {
const videoCodec = document.getElementById('video_codec').value; const videoCodec = document.getElementById('video_codec').value;
const audioCodec = document.getElementById('audio_codec').value; const audioCodec = document.getElementById('audio_codec').value;
const videoBitrate = document.getElementById('video_bitrate').value; const videoBitrate = document.getElementById('video_bitrate').value;
const audioBitrate = document.getElementById('audio_bitrate').value;
const p = new URLSearchParams(window.location.search); const p = new URLSearchParams(window.location.search);
p.set('video_codec', videoCodec); p.set('video_codec', videoCodec);
p.set('audio_codec', audioCodec); p.set('audio_codec', audioCodec);
p.set('video_bitrate', videoBitrate); p.set('video_bitrate', videoBitrate);
p.set('audio_bitrate', audioBitrate);
fetch(new URL('whip', window.location.href) + '?' + p.toString(), { fetch(new URL('whip', window.location.href) + '?' + p.toString(), {
method: 'POST', method: 'POST',

76
internal/core/webrtc_session.go

@ -48,28 +48,76 @@ func mediasOfIncomingTracks(tracks []*webRTCIncomingTrack) media.Medias {
return ret 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 var sd sdp.SessionDescription
err := sd.Unmarshal([]byte(offer.SDP)) err := sd.Unmarshal([]byte(offer.SDP))
if err != nil { if err != nil {
return return err
} }
for _, media := range sd.MediaDescriptions { if videoBitrateStr != "" {
if media.MediaName.Media == "video" { videoBitrate, err := strconv.ParseUint(videoBitrateStr, 10, 31)
media.Bandwidth = []sdp.Bandwidth{{ if err != nil {
Type: "TIAS", return err
Bandwidth: value, }
}}
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() enc, err := sd.Marshal()
if err != nil { if err != nil {
return return err
} }
offer.SDP = string(enc) offer.SDP = string(enc)
return nil
} }
func gatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, error) { func gatherOutgoingTracks(medias media.Medias) ([]*webRTCOutgoingTrack, error) {
@ -320,13 +368,9 @@ func (s *webRTCSession) runPublish() (int, error) {
tmp := pc.LocalDescription() tmp := pc.LocalDescription()
answer = *tmp answer = *tmp
if s.req.videoBitrate != "" { err = editAnswer(&answer, s.req.videoBitrate, s.req.audioBitrate)
tmp, err := strconv.ParseUint(s.req.videoBitrate, 10, 31) if err != nil {
if err != nil { return http.StatusBadRequest, err
return http.StatusBadRequest, err
}
insertTias(&answer, tmp*1024)
} }
err = s.writeAnswer(&answer) err = s.writeAnswer(&answer)

Loading…
Cancel
Save