简单了解的 HTML5 WebRTC

4,997 阅读6分钟
原文链接: github.com

WebRTC是Web Real-Time Communication(网页实时通信)。WebRTC 包含有三个组件:

  1. 访问用户摄像头及麦克风的 getUserMedia
  2. 穿越 NAT 及防火墙建立视频会话的 PeerConnection
  3. 在浏览器之间建立点对点数据通讯的 DataChannels

分别对应三个API接口:

  1. Network Stream API 代表媒体数据流
  2. RTCPeerConnection 一个RTCPeerConnection对象允许用户在两个浏览器之间直接通讯;
  3. Peer-to-peer Data API 一个在两个节点之间的双向的数据通道

浏览器对 WebRTC 的支持

最新支持成都可在 caniuse.com 上查询

  1. pc上浏览器的支持情况[pc浏览器对WebRTC的支持情况](gtms04.alicdn.com/tps/i4/TB1w…)
  2. Andriod chrome浏览器从29版开始支持webRTC,但是默认关闭,如果不能正常使用webRTC,请在chrome://flags,开启webRTC[移动端的浏览器支持情况](gtms04.alicdn.com/tps/i4/TB1h…)
  3. iOS 还不支持,不过苹果很快会支持

WebRTC API

  • MediaStream
    getUserMedia()与WebRTC相关,因为它是通向这组API的门户。它提供了访问用户本地相机/麦克风媒体流的手段。在移动设备上,目前只有andriod中webview内核为36或者大于36才支持(即只有andriod L或者更高版本),ios设备暂时还不支持。
  • 功能检测
    各家浏览器对getUserMedia支持不同,因此在使用该API之前需要检测用户浏览器是否支持getUserMedia
function hasGetUserMedia() {
    return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
}
  • 请求权限
    浏览器出于安全考虑,在请求网络摄像头或麦克风时,会弹出信息栏,让用户选择授予还是拒绝对其相机/麦克风的访问权限。
  • 使用方法
    getUserMedia()需要传递三个参数,第一个参数用于指定你要访问的媒体类型, 第二个参数是成功获取媒体数据流后的回调函数,第三个参数为获取媒体数据流失败的处理函数,相关API文档可以参考:getUserMedia
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.moZGetUserMedia || navigator.msGetUserMedia;
var video = document.querySelector('video');
navigator.getUserMedia({
    audio : true,
    video : true
    }, function (stream) {
            video.src = window.URL.creatObjectURL(stream);
    }, function (error) {
            console.log(error);
});

这样就可以通过video标签成功将视频流输出到页面上。

  • 截取视频截图
    可以通过canvas的API: ctx.drawImage(video, 0, 0)将video的某一帧绘制到canvas上,再通过canvas的canvas.toDataURL('image/png')将canvas绘制的东西转为图片。
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
navigator.getUserMedia({
     audio: true,
     video: true
}, function(stream) {
     video.src = window.URL.creatObjectURL(stream);
}, function(error) {
     console.log(error);
});
video.addEventListener('click', function() {
     ctx.drawImage(video, 0, 0);
     var img = new Image();
     img.src = canvas.toDataURL('image/png');
     document.appendChild(img);
}, false)
  • 给图片添加滤镜
    canvas 可以获取每一个像素点得颜色值(包括red, green, blue, alpha四个值),通过 canvas 的 API: ctx.getImageData() 获取到的所有像素点的颜色值,修改对应的像素值之后,可以通过 ctx.putImageData() 将像素点重新绘制到 canvas 中
var imgData = ctx.getImageData();
var filter = {
    // 灰度效果
    grayscale: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];
            d[i] = d[i + 1] = d[i + 2] = (r + g + b) / 3;
        }

        return pixels;
    },

    // 复古效果
    sepia: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];

            d[i] = (r * 0.393) + (g * 0.769) + (b * 0.189);
            d[i + 1] = (r * 0.349) + (g * 0.686) + (b * 0.168);
            d[i + 2] = (r * 0.272) + (g * 0.534) + (b * 0.131);
        }

        return pixels;
    },

    // 红色蒙版效果
    red: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];

            d[i] = (r + g + b) / 3;
            d[i + 1] = d[i + 2] = 0;
        }

        return pixels;
    },

    // 反转效果
    invert: function(pixels) {
        var d = pixels.data;

        for (var i = 0, len = d.length; i < len; i += 4) {
            var r = d[i],
                g = d[i + 1],
                b = d[i + 2];

            d[i] = 255 - r;
            d[i + 1] = 255 - g;
            d[i + 2] = 255 - b;
        }
        return pixels;
    }
};
ctx.putImageData(filter[type](imgData));

  • 音频处理
    navigator.getUserMedia()可以和web Audio API相结合,用来处理音频效果
var range = document.querySelector('input');
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var audioCtx = new AudioContext();
navigator.getUserMedia({
    audio: true
}, function(stream) {
    // 创建音频流
    var source = audioCtx.createMediaStreamSource(stream);
    // 双二阶滤波器
    var biquadFilter = audioCtx.createBiquadFilter();
    biquadFilter.type = 'lowshelf';
    biquadFilter.frequenc.value = 1000;
    biquadFilter.gain.value = range.value;

    source.connect(biquadFilter);
    biquadFilter.connect(audioCtx.destination);
}, function(error) {
    console.log(error);
});

RTCPeerConnection

RTCPeerConnection,用于peer跟peer之间呼叫和建立连接以便传输音视频数据流;

WebRTC是实现peer to peer的实时通信(可以两个或多个peer之间),在能够通信前peer跟peer之间必须建立连接,这是RTCPeerConnection的任务,为此需要借助一个信令服务器(signaling server)来进行,信令包括3种类型的信息:

  • Session control messages: 初始化和关闭通信,及报告错误;
  • Network configuration: 双方的IP地址和端口号(局域网内部IP地址需转换为外部的IP地址);
  • Media capabilities: 双方的浏览器支持使用何种codecs以及多高的视频分辨率。
var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
navigator.getUserMedia = navigator.getUserMedia ? "getUserMedia" :
    navigator.mozGetUserMedia ? "mozGetUserMedia" :
    navigator.webkitGetUserMedia ? "webkitGetUserMedia" : "getUserMedia";
var v = document.createElement("video");

// 创建信令
var pc = new PeerConnection();
pc.addStream(video);
pc.createOffer(function(desc) {
    pc.setLocalDescription(desc, function() {
        // send the offer to a server that can negotiate with a remote client
    });
})

// 创建回复
var pc = new PeerConnection();
pc.setRemoteDescription(new RTCSessionDescription(offer), function() {
    pc.createAnswer(function(answer) {
        pc.setLocalDescription(answer, function() {
            // send the answer to the remote connection
        });
    });
})

peer跟peer之间一旦建立连接就可以直接传输音视频数据流,并不需要借助第三方服务器中转。
具体文档可以查看:developer.mozilla.org/en-US/docs/…

RTCDataChannel

RTCDataChannel可以建立浏览器之间的点对点通讯。常用的通讯方式有webSocket, ajax和 Server Sent Events等方式,websocket虽然是双向通讯,但是无论是websocket还是ajax都是客户端和服务器之间的通讯,这就意味着你必须配置服务器才可以进行通讯。而RTCDATAChannel采用另外一种实现方式

  • 它使用webRTC的另外一个API:RTCPeerConnection,RTCPeerConnection无需经过服务器就可以提供点对点之间的通讯,避免服务器这个中间件
  • RTCDataChannel支持SCTP机制,SCTP实际上是一个面向连接的协议,但SCTP偶联的概念要比TCP的连接具有更广的概念,SCTP对TCP的缺陷进行了一些完善,使得信令传输具有更高的可靠性,SCTP的设计包括适当的拥塞控制、防止泛滥和伪装攻击、更优的实时性能和多归属性支持。

WebRTC并未规定使用何种信令机制和消息协议,象SIP、XMPP、XHR、WebSocket这些技术都可以用作WebRTC的信令通信。
除了信令服务器,peer跟peer建立连接还需要借助另一种服务器(称为STUN server)实现NAT/Firewall穿越,因为很多peer是处于私有局域网中,使用私有IP地址,必须转换为公有IP地址才能相互之间传输数据。这其中涉及到一些专业术语包括STUN、TURN、ICE等,其实我对这些概念也不是很理解。网上找到的WebRTC demo好象都用的是Google提供的STUN server。

参考文章:www.html5rocks.com/en/tutorial…
developer.mozilla.org/en-US/docs/…

WebRTC的目的是为了简化基于浏览器的实时数据通信的开发工作量,但实际应用编程还是有点复杂,尤其调用RTCPeerConnection必须对如何建立连接、交换信令的流程和细节有较深入的理解。因此我们可以使用已经封装好的WebRTC库,这些WebRTC库对原生的webRTC的API进行进一步的封装,包装成更简单的API接口。同时屏蔽了不同浏览器之间的差异。
目前网上主要有两种WebRTC的封装库:

最后是用WebRTC写的一个小demo:ccforward.github.io/demos/webrt…

关于 WebRTC 的相关文章推荐:www.html5rocks.com/en/tutorial…