背景
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' // 前置摄像头
}
};
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 占用率和延时的问题。
作者:sakuragizhx
原文:https://segmentfault.com/a/1190000011767066