我正在尝试使用 WebRTC 和 node.js 开发视频通话/会议应用程序。目前没有在视频通话期间控制带宽的设施。有什么方法可以控制/减少带宽。(就像我想让整个 Web 应用程序在视频会议时以 150 kbps 的速度工作)。
任何建议都受到高度赞赏。提前致谢。
我正在尝试使用 WebRTC 和 node.js 开发视频通话/会议应用程序。目前没有在视频通话期间控制带宽的设施。有什么方法可以控制/减少带宽。(就像我想让整个 Web 应用程序在视频会议时以 150 kbps 的速度工作)。
任何建议都受到高度赞赏。提前致谢。
试试这个演示。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
已经存在于 sdp 中data m-line
;它的默认值为50
.
这是一个库,可以完全控制音频/视频轨道的比特率:
// 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
});
更新的答案
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
(或类似的音频)标签并在下面附加带宽参数
不确定这是否有帮助,但您可以通过约束限制来自 getUserMedia 的视频分辨率:请参阅simpl.info/getusermedia/constraints/上的演示。
我的答案不适用于 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"));
这取决于您使用的 SFU 媒体服务器。但简而言之,您的媒体服务器需要通过在应答SDP 以及它定期发送的 REMB 消息中设置带宽属性来告诉客户端浏览器它应该发送的最大比特率。
REMB(接收器估计的最大比特率)分别适用于音频和视频流(至少在我测试的桌面 Chrome 和 Firefox 上)。因此,如果 REMB 设置为 75kps,并且您有一个音频和一个视频流,那么每个流将限制为 75kps,总传输比特率为 150kps。您应该使用 chrome://webrtc-internals 来测试和验证这一点。
如果您使用 OPUS 作为音频编解码器,您可以通过在answer SDP 中设置 maxaveragebitrate 属性来单独控制音频带宽。设置此属性将覆盖 REMB 值(已在 Chrome 上验证)。因此,您可以将音频比特率设置为 16kps,将视频比特率(通过 REMB)设置为 134kps,以实现 150 的组合传输比特率。
请注意,REMB 是由您的服务器发送的,因此您的服务器需要支持这一点。其他 SDP 属性可以在客户端通过修改您收到的答案SDP 来操作,就在将其传递给 setRemoteDescription() 之前。
这是我基于在线研究的有限理解。它不是基于对技术堆栈的深入了解,所以请持保留态度。
您还应该能够在流上使用带宽限制(请参阅此演示),但它似乎不起作用,即使在最新的金丝雀 (29.0.1529.3) 中也是如此。
在讨论 webrtc邮件列表中有一些关于基于 SDP 的方法的讨论,该列表链接到 WebRTC 错误 1846。
我昨天做了它,它就像一个魅力!就我而言,需要防止慢速和旧手机在视频通话期间冻结!看一看
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);
});
我建议按照此处所述更改maxBitrate
属性值https://stackoverflow.com/a/71223675/1199820