webRTC——浏览器里的音视频通话

3,535 阅读4分钟

背景

webRTC是Google在2010年收购GIP公司之后获得的一项技术。如下图所示,它提供了音视频的采集、处理(降噪,回声消除等)、编解码、传输等技术。

webRTC结构

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穿越技术,它整合了STUNTURN。当穿越网络时,ICE会先尝试STUN,查出自己位于哪种类型的NAT之后以及NAT为某一个本地端口所绑定的Internet端端口从而建立UDP连接,如果失败了ICE就会再尝试TCP(先尝试HTTP,再尝试HTTPS),如果仍然失败就使用中继的TURN服务器。

ICE

再来看看建立连接过程中的具体步骤:

  1. 调用getUserMedia获取本地的MediaStreams;
  2. 从STUN获取自己的外网IP及端口,通过Signaling Server向对方发送Offer(SDP协议),并收到Answer;
  3. 同时webRTC会生成一些包含自己的内网、外网IP等信息的candidate,同样通过Signaling Server相互传输;
  4. 建立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占用率和延时的问题。

参考文章