Browse Source

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

pull/1989/head
Alessandro Ros 2 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) { @@ -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 {

1
internal/core/webrtc_manager.go

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

248
internal/core/webrtc_pc.go

@ -11,6 +11,82 @@ import ( @@ -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,157 +125,43 @@ func newPeerConnection( @@ -49,157 +125,43 @@ 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
codec, ok = audioCodecs[audioCodec]
if ok {
for _, params := range codec {
err := mediaEngine.RegisterCodec(params, 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
}
} else { // register all codecs
for _, codec := range videoCodecs {
for _, params := range codec {
err := mediaEngine.RegisterCodec(params, webrtc.RTPCodecTypeVideo)
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
for _, codec := range audioCodecs {
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
}
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{}

5
internal/core/webrtc_publish_index.html

@ -72,6 +72,9 @@ select { @@ -72,6 +72,9 @@ select {
<div id="bitrate_line">
video bitrate (kbps):
<input id="video_bitrate" type="text" value="10000" />
audio bitrate (kbps):
<input id="audio_bitrate" type="text" value="32" />
</div>
<div id="submit_line">
<button id="publish_confirm">publish</button>
@ -222,11 +225,13 @@ class Transmitter { @@ -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',

76
internal/core/webrtc_session.go

@ -48,28 +48,76 @@ func mediasOfIncomingTracks(tracks []*webRTCIncomingTrack) media.Medias { @@ -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) { @@ -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)

Loading…
Cancel
Save