webRTC——浏览器里的音视频通话
背景
webRTC是Google在2010年收购GIP公司之后获得的一项技术。如下图所示,它提供了音视频的采集、处理(降噪,回声消除等)、编解码、传输等技术。
webRTC的目标是实现无需安装任何插件就可以通过浏览器进行P2P的实时音视频通话及文件传输,来看看Google的demo,是不是很酷?本文将带你分析webRTC的原理,并逐步编写一个简单的demo。
原理
如图,浏览器之间媒体流的传输是P2P的,但是这并不意味着webRTC不需要服务器支持。建立P2P视频连接需要的信息,如用来初始化通信的session信息,双方的ip、端口,视频分辨率,编解码格式等等,还是需要通过服务器来传输的。webRTC没有规定这些信息传输的机制,XHR、webSocket、Socket.io等都是可以的,因为Socket.io自带了房间的概念,便于双向视频的撮合,所以我在demo里选择了Socket.io。
当然,连接建立的过程不会这么简单。首先,提到P2P就绕不开NAT(Network Address Translation),webRTC使用ICE(Interactive Connectivity Establishment)框架,ICE是一种综合性的NAT穿越技术,它整合了STUN、TURN。当穿越网络时,ICE会先尝试STUN,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口从而建立UDP连接,如果失败了ICE就会再尝试TCP(先尝试HTTP,再尝试HTTPS),如果仍然失败就使用中继的TURN服务器。
再来看看建立连接过程中的具体步骤:
- 调用getUserMedia获取本地的MediaStreams;
- 从STUN获取自己的外网IP及端口,通过Signaling Server向对方发送Offer(SDP协议),并收到Answer;
- 同时webRTC会生成一些包含自己的内网、外网IP等信息的candidate,同样通过Signaling Server相互传输;
- 建立P2P连接,传输媒体信息。
API
- getUserMedia: 获取本地视频、音频,可以传入constraints调整分辨率、帧率,返回一个promise;
- RTCPeerConnection: 生成一个RTCPeerConnection实例,传输视频、音频流;
- createOffer: 会话发起方生成SDP Offer,包含了本地媒体流信息;
- setLocalDescription:在此方法被调用之前oncandidate事件不会被触发;
- setRemoteDescription: 接收到offer或者answer之后调用;
- addIceCandidate: 接收到icecandidate之后调用;
Steps
获取媒体流
var constraints = { audio: false, video: true }; navigator.mediaDevices.getUserMedia(constraints) .then(gotStream) .catch(function(e) { alert('getUserMedia() error: ' + e.name); }); function gotStream(stream) { localVideo.srcObeject = stream; localStream = stream; }
getUserMedia存在兼容性问题,需要在项目中引用webRTC官方给出的adapter.js。constraints还可以配置video的分辨率、帧率、对移动端还可以选择前后摄像头:
var constraints = { video: { width: { min:640, ideal: 1280, max: 1920 }, height: { min: 480 ideal: 720, max: 1080 }, facingMode: 'user' // 前置摄像头 } };
定义RTCPeerConnection
var serverConfig = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] }; function createPeerConnection() { var pc = new RTCPeerConnection(serverConfig); pc.onicecandidate = function(e) { if (e.candidate) { pc.addIceCandidate(e.candidate); } }; // 添加对方的媒体流 pc.onaddstream = function(e) { remoteVideo.srcObeject = e.stream; remoteStream = stream; }; }
由STUN、TURN配置生成对应的RTCPeerConnection实例,再定义相关的事件处理函数,如onicecandidate、onaddstream、onremovestream等。
创建连接
function start() { pc.addstream(localStream); if (isCaller) { pc.createOffer(function(sessionDescription) { pc.setLocalDescription(sessionDescription); send(sessionDescription); // 根据不同的Signaling方式实现 }); if (receiveAnswer) { pc.setRemoteDescription(answer.sessionDescription); } } else { if (receiveOffer) { pc.setRemoteDescription(offer.sessionDescription); } pc.createAnswer(function(sessionDescription) { pc.setLocalDescription(sessionDescription); send(sessionDescription); }); } }
必须先getUserMedia后才能生成sessionDescription,并且只有在setLocalDescription后onicecandidate事件才会触发。上面代码中的只是为了说明大致流程,实际项目中结合socket.io的事件更容易实现。
中断会话
function stop() { pc.stop(); pc = null; }
关于socket.io有关的代码本文没有贴出,详情可参考socket.io的用法。
可行性
按照上面的步骤可以成功地搭建webRTC的小demo,但是能否将webRTC运用到实际项目中去呢?下面从浏览器兼容性和webRTC本身的性能两个方面去分析。
兼容性
- IOS: 只有最新的ios11支持webRTC,且仅限safari浏览器,微信内置浏览器尚不支持getUserMedia,不支持DataChannel,视频编解码格式为H.264;
- Android: 安卓4.4以上(不含4.4),经测试各大手机厂商自带浏览器均不支持getUserMedia,但微信内置浏览器可以正常运行,另外61版本以上的Chrome for Android也都支持;
- PC: Chrome49以上,Firefox55以上,Edge支持,Safari只有11支持,IE不支持。
性能
诚然webRTC在回声消除,图像编解码等方面已经做得十分出色,但它在性能上的问题还是不可忽视的:
- 由于需要进行视频编解码,所以CPU占用率非常高,尤其是在移动设备上;
- 在移动设备上获取的视频分辨率有限,最高只能达到640 * 480;
- 带宽有限时,音视频质量较差,延时明显;
综上所述,虽然webRTC具有不需安装插件或者客户端,开源免费,强大的网络穿透能力,出色的音视频处理技术等等优点,但由于兼容性及性能上的问题,要投入到生产中还需要时间,主要是IOS11的普及以及CPU占用率和延时的问题。