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

时间:2013-05-23 11:01:26

标签: javascript node.js webrtc

我正在尝试使用WebRTC和node.js开发视频呼叫/会议应用程序。 目前,在视频通话期间无法控制带宽。有没有办法控制/减少带宽。 (就像我想让整个我的Web应用程序在视频会议上以150 kbps的速度工作)。

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

6 个答案:

答案 0 :(得分:26)

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

答案 1 :(得分:5)

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

答案 2 :(得分:4)

最新的答案

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

答案 3 :(得分:0)

昨天我做到了,它就像一个魅力!就我而言,这是为了防止视频通话过程中速度较慢和旧手机冻结!看看

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

答案 4 :(得分:-1)

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

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

答案 5 :(得分:-5)

WebRTC用于对等通信,您无法控制视频通话中的带宽。

在谷歌浏览器中,视频元素上有以下属性:

webkitVideoDecodedByteCount: 0
webkitAudioDecodedByteCount: 0

这些对于了解客户端解码视频的速度非常有用。在播放视频时,您将跟踪这些字节的增量,从而为客户端处理视频提供字节/秒。(SO thread

您应该使用Network Information API来了解带宽(它仍在实施中)。