NodeJS 如何在WebRTC视频通话中控制带宽?

to94eoyn  于 2022-11-29  发布在  Node.js
关注(0)|答案(9)|浏览(202)

我正在尝试使用WebRTC和node.js开发一个视频通话/会议应用程序。现在还没有在视频通话期间控制带宽的设施。有没有任何方法来控制/减少带宽。(就像我想让我的整个Web应用程序在视频会议时以150 kbps的速度工作)。
如有任何建议,我们将不胜感激。

sbtkgmzw

sbtkgmzw1#

尝试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-lineSDP中;其默认值为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);
        }
    };
})();

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

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
});
hpcdzsge

hpcdzsge2#

更新的答案

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(或类似的音频)标记并在下面附加带宽参数

f4t66c6m

f4t66c6m3#

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

3lxsmp7m

3lxsmp7m4#

我的答案不是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"));
6l7fqoea

6l7fqoea5#

这取决于你使用的SFU媒体服务器,但简而言之,你的媒体服务器需要告诉客户端浏览器它应该发送的最大比特率,通过在 answer SDP中设置带宽属性,以及在它定期发送的REMB消息中设置带宽属性。
REMB(receiver estimated maximum bitrate)分别适用于音频和视频流(至少在我测试过的桌面Chrome和Firefox上是这样的)。所以如果REMB设置为75 kps,并且你有一个音频流和一个视频流,那么每个流都将限制在75 kps,总传输比特率为150 kps。你应该使用chrome://webrtc-internals来测试和验证这一点。
如果您使用OPUS作为音频编解码器,您可以通过在 answer SDP中设置maxaveragebitrate属性来单独控制音频带宽。设置此属性将覆盖REMB值(在Chrome上验证)。因此,您可以将音频比特率设置为16 kps,将视频比特率(通过REMB)设置为134 kps,以使组合传输比特率为150。
注意REMB是由服务器发送的,因此服务器需要支持它。其他SDP属性可以在客户端通过修改收到的 answer SDP来操作,然后将其传递给setRemoteDescription()。
这是我基于网上调查的有限理解,并不是基于对技术堆栈的深入了解,所以请持保留态度。

kyvafyod

kyvafyod6#

您还应该能够在流(see this demo)上使用带宽限制,但它似乎不起作用,即使在最新的canary(29.0.1529.3)中也是如此。
discuss-webrtc邮件列表上有一些关于基于SDP的方法的讨论,它链接到WebRTC bug 1846。

q5lcpyga

q5lcpyga7#

我昨天做了它,它的工作就像一个魅力!在我的情况下,它是需要防止缓慢和旧手机得到冻结在视频通话!看看

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);
});
mnemlml8

mnemlml88#

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

lsmepo6l

lsmepo6l9#

看看这个,这个对我有用。
通过getSenders()控制您的比特率,在对等体连接后,您可以设置您的最大比特率。
这种方法允许您在不重新协商的情况下控制比特率。因此,您可以在通话过程中更改流媒体质量。

//bandwidth => "unlimited", 75 kbps, 250 kbps, 1000 kbps, 2000 kbps
    var bandwidth = 75;
    
    const sender = pc1.getSenders()[0];
    const parameters = sender.getParameters();
    if (!parameters.encodings) {
      parameters.encodings = [{}];
    }
    if (bandwidth === 'unlimited') {
      delete parameters.encodings[0].maxBitrate;
    } else {
      parameters.encodings[0].maxBitrate = bandwidth * 1000;
    }
    sender.setParameters(parameters)
        .then(() => {
          // on success
        })
        .catch(e => console.error(e));

Reference code & demo

相关问题