【问题标题】:How to control bandwidth in WebRTC video call?如何控制 WebRTC 视频通话中的带宽?
【发布时间】:2013-05-23 11:01:25
【问题描述】:

我正在尝试使用 WebRTC 和 node.js 开发视频通话/会议应用程序。 目前没有在视频通话期间控制带宽的设施。有什么方法可以控制/减少带宽。 (就像我想让整个 Web 应用程序在视频会议时以 150 kbps 的速度运行)。

非常感谢任何建议。 提前致谢。

【问题讨论】:

  • WebRTC 是一种点对点技术,服务器只是协商用户之间的介绍。你介意澄清一下你想限制什么/谁的带宽吗?

标签: javascript node.js webrtc


【解决方案1】:

试试this demo。您可以在会话描述中注入带宽属性 (b=AS):

audioBandwidth = 50;
videoBandwidth = 256;
function setBandwidth(sdp) {
    sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + audioBandwidth + '\r\n');
    sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + videoBandwidth + '\r\n');
    return sdp;
}


// ----------------------------------------------------------

peer.createOffer(function (sessionDescription) {
        sessionDescription.sdp = setBandwidth(sessionDescription.sdp);
        peer.setLocalDescription(sessionDescription);
    }, null, constraints);

peer.createAnswer(function (sessionDescription) {
        sessionDescription.sdp = setBandwidth(sessionDescription.sdp);
        peer.setLocalDescription(sessionDescription);
    }, null, constraints);

b=AS 已经存在于 data m-line 的 sdp 中;它的默认值为50

2015 年 9 月 23 日更新

这里有一个库,可以完全控制音频/视频轨道的比特率:

// here is how to use it
var bandwidth = {
    screen: 300, // 300kbits minimum
    audio: 50,   // 50kbits  minimum
    video: 256   // 256kbits (both min-max)
};
var isScreenSharing = false;

sdp = BandwidthHandler.setApplicationSpecificBandwidth(sdp, bandwidth, isScreenSharing);
sdp = BandwidthHandler.setVideoBitrates(sdp, {
    min: bandwidth.video,
    max: bandwidth.video
});
sdp = BandwidthHandler.setOpusAttributes(sdp);

这是库代码。它很大,但很有效!

// BandwidthHandler.js

var BandwidthHandler = (function() {
    function setBAS(sdp, bandwidth, isScreen) {
        if (!!navigator.mozGetUserMedia || !bandwidth) {
            return sdp;
        }

        if (isScreen) {
            if (!bandwidth.screen) {
                console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.');
            } else if (bandwidth.screen < 300) {
                console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.');
            }
        }

        // if screen; must use at least 300kbs
        if (bandwidth.screen && isScreen) {
            sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
            sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n');
        }

        // remove existing bandwidth lines
        if (bandwidth.audio || bandwidth.video || bandwidth.data) {
            sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
        }

        if (bandwidth.audio) {
            sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n');
        }

        if (bandwidth.video) {
            sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (isScreen ? bandwidth.screen : bandwidth.video) + '\r\n');
        }

        return sdp;
    }

    // Find the line in sdpLines that starts with |prefix|, and, if specified,
    // contains |substr| (case-insensitive search).
    function findLine(sdpLines, prefix, substr) {
        return findLineInRange(sdpLines, 0, -1, prefix, substr);
    }

    // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
    // and, if specified, contains |substr| (case-insensitive search).
    function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
        var realEndLine = endLine !== -1 ? endLine : sdpLines.length;
        for (var i = startLine; i < realEndLine; ++i) {
            if (sdpLines[i].indexOf(prefix) === 0) {
                if (!substr ||
                    sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
                    return i;
                }
            }
        }
        return null;
    }

    // Gets the codec payload type from an a=rtpmap:X line.
    function getCodecPayloadType(sdpLine) {
        var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
        var result = sdpLine.match(pattern);
        return (result && result.length === 2) ? result[1] : null;
    }

    function setVideoBitrates(sdp, params) {
        params = params || {};
        var xgoogle_min_bitrate = params.min;
        var xgoogle_max_bitrate = params.max;

        var sdpLines = sdp.split('\r\n');

        // VP8
        var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000');
        var vp8Payload;
        if (vp8Index) {
            vp8Payload = getCodecPayloadType(sdpLines[vp8Index]);
        }

        if (!vp8Payload) {
            return sdp;
        }

        var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000');
        var rtxPayload;
        if (rtxIndex) {
            rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]);
        }

        if (!rtxIndex) {
            return sdp;
        }

        var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString());
        if (rtxFmtpLineIndex !== null) {
            var appendrtxNext = '\r\n';
            appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228');
            sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext);
            sdp = sdpLines.join('\r\n');
        }

        return sdp;
    }

    function setOpusAttributes(sdp, params) {
        params = params || {};

        var sdpLines = sdp.split('\r\n');

        // Opus
        var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000');
        var opusPayload;
        if (opusIndex) {
            opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
        }

        if (!opusPayload) {
            return sdp;
        }

        var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
        if (opusFmtpLineIndex === null) {
            return sdp;
        }

        var appendOpusNext = '';
        appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1');
        appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1');

        if (typeof params.maxaveragebitrate != 'undefined') {
            appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8);
        }

        if (typeof params.maxplaybackrate != 'undefined') {
            appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8);
        }

        if (typeof params.cbr != 'undefined') {
            appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1');
        }

        if (typeof params.useinbandfec != 'undefined') {
            appendOpusNext += '; useinbandfec=' + params.useinbandfec;
        }

        if (typeof params.usedtx != 'undefined') {
            appendOpusNext += '; usedtx=' + params.usedtx;
        }

        if (typeof params.maxptime != 'undefined') {
            appendOpusNext += '\r\na=maxptime:' + params.maxptime;
        }

        sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext);

        sdp = sdpLines.join('\r\n');
        return sdp;
    }

    return {
        setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) {
            return setBAS(sdp, bandwidth, isScreen);
        },
        setVideoBitrates: function(sdp, params) {
            return setVideoBitrates(sdp, params);
        },
        setOpusAttributes: function(sdp, params) {
            return setOpusAttributes(sdp, params);
        }
    };
})();

以下是如何设置高级作品比特率参数:

sdp = BandwidthHandler.setOpusAttributes(sdp, {
    'stereo': 0, // to disable stereo (to force mono audio)
    'sprop-stereo': 1,
    'maxaveragebitrate': 500 * 1024 * 8, // 500 kbits
    'maxplaybackrate': 500 * 1024 * 8, // 500 kbits
    'cbr': 0, // disable cbr
    'useinbandfec': 1, // use inband fec
    'usedtx': 1, // use dtx
    'maxptime': 3
});

【讨论】:

  • 你一年多以前就给出了答案,这仍然有效吗?我一直试图让它工作但没有成功。而不是使用链接上的库,我只是使用您发布的代码。
  • 它仍然可以在 chrome 中用于“视频轨道”。 b=AS for audio-tracks 有已知的 chrome 错误,已在 Chrome-39 中修复。对于 chrome 38 及更早版本 [for audio-tracks],您可以将 SDP 修改为“maxaveragebitrate”,因为您可以看到一些 sn-ps here
  • 感谢您的回复。您将使用什么方法来验证有限的带宽?我的测试显示没有变化。带宽的有效值是多少?低于 30 我似乎完全失去了视频。再次感谢。
  • webrtc-pedia 页面列出了 VP8 和 Opus 编解码器的一些最小/最大比特率值。顺便说一句,检查可用带宽并不容易。 Ref-1/Ref-2。顺便说一句,VP8 支持的最低带宽为 20kbits,Opus 最低支持带宽为 30kbits。
  • 是否也可以限制远程描述的带宽,从而迫使对等方降低其带宽?我的用例:如果我与 iPad 3 等旧设备通信,我只能使用 240p 和 10 帧速率。否则,连接会很糟糕。但我不想再次设置整个 getUserMedia() 东西,因为它会导致 Firefox 再次请求许可。
【解决方案2】:

更新的答案

const videobitrate = 20000;
var offer = pc.localDescription;
// Set bandwidth for video
offer.sdp = offer.sdp.replace(/(m=video.*\r\n)/g, `$1b=AS:${videobitrate}\r\n`);
pc.setLocalDescription(offer);

解释:a=mid:video 不是保证标签。对于仅接收视频,您可能看不到它或看到 a=mid:0。一般来说,最好寻找m=video xxxx xxxx(或类似的音频)标签并在下面附加带宽参数

【讨论】:

  • 这有效吗?根据规范 (tools.ietf.org/html/draft-ietf-rtcweb-jsep-26),“m= 行必须紧跟 c= 行”。我想b= 应该低于那个值?
  • 这对我来说适用于 chrome 和 webrtc 原生 api。 YMMV 作为 webrtc 本身就是一个快速发展的标准。
  • 今天可能是最好的解决方案。但我尝试在测试 webrtc 广播(来自一台电脑但不同的浏览器)中设置任何 videobitrate 值,从 10 到 20000 - 眼睛没有检测到任何质量变化
  • 使用webrtc stats api来探测带宽而不是依靠眼睛。更高质量的帧需要带宽,但仅靠带宽并不能保证质量。如果带宽似乎正确但观看体验不佳,请检查源和编码器。
  • @Brad 是对的,请参阅此链接以获得更完整的答案:webrtchacks.com/limit-webrtc-bandwidth-sdp
【解决方案3】:

不确定这是否有帮助,但您可以通过约束限制 getUserMedia 的视频分辨率:请参阅simpl.info/getusermedia/constraints/ 的演示。

【讨论】:

  • 是否可以在对话过程中更改视频分辨率。 PS:如果有帮助,我有 kurento 设置。虽然我正在寻找 SFU 解决方案而不是 MCU
  • @bugwheels94 是的,使用applyConstraints
【解决方案4】:

我的答案不是针对 node.js,但也许有人在开发原生手机应用程序(iOS、android)时坚持控制 webrtc 带宽。

因此,至少在 iOS 的 GoogleWebRTC (1.1.31999) 版本和 Android 的 org.webrtc:google-webrtc:1.0.22672 版本中,PeerConnection 实例。

对于 iOS:

let updateBitrateSuccessful = pc.setBweMinBitrateBps(300000, currentBitrateBps: 1000000, maxBitrateBps: 3000000)
print("Update rtc connection bitrate " + (updateBitrateSuccessful ? "successful" : "failed"))

分别适用于 Android:

boolean updateBitrateSuccessful = pc.setBitrate(300000, 1000000, 3000000);
Log.d("AppLog", "Update rtc connection bitrate " + (updateBitrateSuccessful ? "successful" : "failed"));

【讨论】:

  • 我认为带宽估计和视频比特率存在一些混淆。 AS:SDP 标志可用于增加或减少视频比特率。 pc.setBitrate 调用可用于降低或提高网络比特率。默认情况下,网络比特率将自动适应 AS 标志,除非通过 setBitrate 调用手动限制。
【解决方案5】:

这取决于您使用的 SFU 媒体服务器。但简而言之,您的媒体服务器需要通过在 answer SDP 以及它定期发送的 REMB 消息中设置带宽属性来告诉客户端浏览器它应该发送的最大比特率。

REMB(接收器估计的最大比特率)分别适用于音频和视频流(至少在我测试的桌面 Chrome 和 Firefox 上)。因此,如果 REMB 设置为 75kps,并且您有一个音频和一个视频流,那么每个流将限制为 75kps,总传输比特率为 150kps。您应该使用 chrome://webrtc-internals 来测试和验证这一点。

如果您使用 OPUS 作为音频编解码器,您可以通过在 answer SDP 中设置 maxaveragebitrate 属性来单独控制音频带宽。设置此属性将覆盖 REMB 值(已在 Chrome 上验证)。因此,您可以将音频比特率设置为 16kps,将视频比特率(通过 REMB)设置为 134kps,以实现 150 的组合传输比特率。

请注意,REMB 是由您的服务器发送的,因此您的服务器需要支持这一点。其他 SDP 属性可以在客户端通过修改您收到的 answer SDP 进行操作,然后将其传递给 setRemoteDescription()。

这是我基于在线研究的有限理解。它不是基于对技术堆栈的深入了解,因此请持保留态度。

【讨论】:

    【解决方案6】:

    您还应该能够对流 (see this demo) 使用带宽限制,但它似乎不起作用,即使在最新的金丝雀 (29.0.1529.3) 中也是如此。

    discuss-webrtc 邮件列表中讨论了基于 SDP 的方法,该邮件列表链接到 WebRTC 错误 1846。

    【讨论】:

      【解决方案7】:

      我昨天做了它,它就像一个魅力!就我而言,需要防止慢速和旧手机在视频通话期间冻结!看看

      function handle_offer_sdp(offer) {
          let sdp = offer.sdp.split('\r\n');//convert to an concatenable array
          let new_sdp = '';
          let position = null;
          sdp = sdp.slice(0, -1); //remove the last comma ','
          for(let i = 0; i < sdp.length; i++) {//look if exists already a b=AS:XXX line
              if(sdp[i].match(/b=AS:/)) {
                  position = i; //mark the position
              }
          }
          if(position) {
              sdp.splice(position, 1);//remove if exists
          }
          for(let i = 0; i < sdp.length; i++) {
              if(sdp[i].match(/m=video/)) {//modify and add the new lines for video
                  new_sdp += sdp[i] + '\r\n' + 'b=AS:' + '128' + '\r\n';
              }
              else {
                  if(sdp[i].match(/m=audio/)) { //modify and add the new lines for audio
                      new_sdp += sdp[i] + '\r\n' + 'b=AS:' + 64 + '\r\n';
                  }
                  else {
                      new_sdp += sdp[i] + '\r\n';
                  }
              }
          }
          return new_sdp; //return the new sdp
      }
      pc.createOffer(function(offer) {
          offer.sdp = handle_offer_sdp(offer); //invoke function saving the new sdp
          pc.setLocalDescription(offer);
      }, function(error) {
          console.log('error -> ' + error);
      });
      

      【讨论】:

      • 嗨,通过操作 SDP 设置比特率与通过 sender.setParams 设置发送方的最大比特率有什么影响?谢谢,
      • 嗨,我这样做的相关原因是旧手机或低性能手机,根据我的经验,在 webRTC 会话中设置最大比特率会导致旧手机或低性能手机冻结视频,他们可以在高清或高帧率方面表现不佳。
      【解决方案8】:

      我建议更改maxBitrate 属性的值,如此处所述https://stackoverflow.com/a/71223675/1199820

      【讨论】:

        猜你喜欢
        • 2018-09-11
        • 2020-02-19
        • 1970-01-01
        • 1970-01-01
        • 2017-06-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多