什么是WebRTC
WebRTC(Web Real-Time Communication),顾名思义,是基于Web技术上实现实时通信技术。例如QQ视频,Facetime,但不需要在浏览器上安装任何的插件,根据其标准以及HTML5标签和JavaScript语法就可以实现这些功能。
WebRTC最大的特点有两点:浏览器与浏览器之间的对等连接,即一个浏览器通过标准协议与另一个浏览器进行实时通信;另一个特点是WebRTC提供了信令服务器,该服务器在浏览器和对等连接另一端之间提供信令通道,关于这一点下面会讲到。目前是1.0版本,之后可能会有更多的特性。
一个最简单的WebRTC结构,但实际上情况比这个复杂更多,例如多个浏览器互相建立对等连接的对方会话。
如何使用WebRTC
建立一个简单的WebRTC会话,只需要以下四个步骤:
1)获取本地媒体。window.navigator.getUserMedia
,获取单个本地MediaStream
;
2)在浏览器和对等端(其他浏览器)建立连接,简单来说就是不通过服务器通信,而是浏览器与浏览器之间的媒体连接。new RTCPeerConnection(c)
,简单的说明下c为ICE打洞通过NAT设备和防火墙需要的配置信息;
3)将媒体和数据通道关联至此连接,new RTCSessionDescription
;
4)交换会话描述;
5)关闭连接。
结构图如以下左边图,若添加了信令通道,则如右边图。
这里简单说下3)跟4)。当建立完连接之后,本地以及远程都会通过RTCPeerConnection
交换RTCSessionDescription
,而RTCSessionDescription
的意义是为了两端之间协商如何建立媒体信息,例如编解码器。交换完毕之后,即可建立媒体或数据会话。此时,两个浏览器开始打洞(穿透NAT设备和防火墙)。打洞完毕之后,即可开始协商RTCSessionDescription
交换的信息。为了确保安全,交换密钥。最后开始媒体或数据通话。
WebRTC的体系结构如下,有必要提醒的是,浏览器A发起的1、2和浏览器B发起的3、4是不排先后的。而且实际上的体系结构是根据需求改变的,而并非以下图的一成不变。
- 浏览器A向服务器请求网页;
- 服务器返回携带WebRTC应用的网页给浏览器A;
- 浏览器B向服务器请求网页;
- 服务器返回携带同A一样内容的网页给浏览器B;
- 浏览器A想与浏览器B通信,于是通过服务器,将描述对象(offer, 提议)发送给服务器;
- 服务器将浏览器A发送过来的描述对象发送给浏览器B;
- 浏览器B接收到提议之后,也将自己的描述对象(answer, 应答)发送到服务器;
- 服务器接收到浏览器B的描述对象之后发送给浏览器A;
- 浏览器A接收到浏览器B的应答之后,两端开始打洞,根据两边的描述对象来确定最佳的访问方式;
- 打完洞之后,就开始协商密钥以确保之后的会话安全;
- 两端开始互相传输视频,语音以及数据。
目前兼容情况(2018-04)
本地媒体
MediaStreamTrack,是WebRTC的基本单位,可以是自源的原始媒体,也可以是浏览器转换过的。例如,MediaStreamTrack可以是摄像头以高分辨率录制的低采样率版本。MediaStreamTrack中的readyState有new、live、ended
,可通过enabled=false
禁用。而MediaStream是MediaStreamTrack的集合,目前通过window.navigator.getUserMedia
获取的或使用对等连接接收的流,未来说不定还有其他的获取方式。MediaStreamTrack - Web APIs | MDN
下面有简单的代码示例下如何获取本地媒体。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25navigator.getUserMedia({'audio': true, 'video': true}, gotUserMedia, didntGetUserMedia);
function didntGetUserMedia(s) {}
function gotUserMedia(s) {
var videoEle = document.getElementById('videoEle');
videoEle.srcObject = s; // 目前还无法使用Blob
console.log(s.getVideoTracks(), s.getAudioTracks());
// 约束,是指浏览器会将媒体流尽可能的满足约束的条件,如宽高,比例,帧率等约束条件。
var constraints = {
width: {min: 640, ideal: 1280},
height: {min: 480, ideal: 720},
advanced: [
{width: 1920, height: 1280},
{aspectRatio: 1.333}
]
};
var t = (s.getVideoTracks())[0];
t.applyConstraints(constraints).then((v) => {
// t.getSettings:返回所有设定的约束值
// t.getConstraints:返回当前约束的值
console.log(t.getSettings(), t.getConstraints());
});
}
而s.getTracks()
的信息如下图。
通过以上代码可以看出gotUserMedia
可以正常的返回音视频轨道,但是还无法正常的播放。
信令(signaling)
其作用主要体现以下几点:
- 协商媒体功能和设置(必备);
- 标识和验证会话参与者的身份;
- 控制媒体会话、指示进度、更改会话和终止会话;
- 当会话双方同时尝试建立或更改会话时,实施双占用分解。
媒体协商
因为必备,所以来简单说明下。这一步主要的工作是在参与对等连接的两个浏览器之间交换SDP(Session Description Protocol)对象中包含的信息,例如音视频数据、编解码器以及有关的带宽信息等。此外还用于交换候选地址,便于ICE打洞。候选地址表示浏览器可从中接收潜在的媒体数据包的IP地址和UDP端口。另外还必须在信令通道交换密钥。只有交换完候选地址才能开始ICE打洞,之后才能建立对等连接。
信令传输
常见的信令传输有:HTTP轮询、WebSocket和数据通道。由于前两种比较常见,我们就只说说数据通道的原理。
在最初建立数据通道时,是无法传输WebRTC信令的,因为数据通道也需要信令才能建立,所以一开始,是通过HTTP或者WebSocket来传输最开始的信令的。当两个浏览器之间建立数据通道之后,它会提供直接的低延迟连接,这时候就可以处理来往的信令了。
1 | // 伪代码 |
对等媒体
对等媒体,指的就是两个浏览器之间能够直接建立视频、音频和数据连接,能够节约带宽。但必须采用特殊的协议,如STUN服务器和TURN服务器。不然会受到NAT和防火墙的拦截。
对等媒体和网络地址转换(NAT)
所谓NAT,简单来说就是将私有地址转换成公有地址的一种方式,例如内网IP:内网端口 -> 外网IP:外网端口。常见设备如路由器。所以说,每个处于NAT的网络都是独立的,网络与网络之间的访问都是通过NAT来访问的。
在上面说过对等媒体会受到NAT的影响,就是因为端对端的连接不像我们常用的C/S结构,能够遍历NAT,因此我们需要穿过它,如STUN服务器和TURN服务器。
对等连接和提议/应答协商
对等连接
RTCPeerConnection
是WebRTC用于两端之间建立媒体与数据连接的API。其API是绑定在JSEP(JavaScript Session Establishment Protocol)协议上来进行媒体协商的,而且该JSEP基本上由浏览器处理。值得说明的一点,WebRTC的对等连接并非是TCP意义上的连接。而是一组路径建立进程(ICE)以及一个可确定应建立哪些媒体和数据路径的协商器。
1 | var pc = new RTCPeerConnection({ |
提议/应答协商
提议/应答的主要意义就是确保双方都知道要发送和接收的媒体类型,以及如何正确解码和处理媒体流。例如:要使用的一个或多个解码器、解码器的参数、用于媒体加密和身份验证的密钥信息等。这样双方就可以达成一致,方便后续工作的处理。RTCSessionDescription
,来表示提议和应答。
1 | // 将自己的会话描述告诉本地浏览器 |
上面的伪代码存在一个问题,那就是一端只能是接收方,而另一端只能是发送方。这样的话,万一发送方跟接收方的角色对调则会发生问题。好在WebRTC提供了如下的解决方法:
1 | // 创建提议 |
整个过程的伪代码:
1 | // A浏览器 |
数据通道
WebRTC数据通道是在浏览器之间,绕过服务器来直接交换数据的。在此之前我们都是借助WebSocket或HTTP来满足需求。而现在,我们可以通过WebRTC数据通道来传输,而且数据通道支持流量大、延迟低的连接。
WebRTC数据通道是基于WebSocket建立的,也具有send方法和onmessage方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26pc = new RTCPeerConnection();
// dc-1为标签,创建完之后会导致对端收到RTCDataChannelEvent
dc = pc.createDataChannel('dc-1');
// 可在对端处理该事件
pc = new RTCPeerConnection();
pc.ondatachannel = function(e) {
dc = e.channel;
}
// send / onmessage
dc.send('string type msg');
dc.send(new Blob(['blob message'], {type: 'text/plain'}));
dc.send(new ArrayBuffer(32));
dc.send(new uInt8Array([1,2,3]));
dc.onmessage = function(e) {
console.log('Received message' + e.data);
}
// .createDataChannel(),第二个参数为控制配置
// 限制通道在第一次数据传输失败时重新传输数据的次数
pc.createDataChannel('', {maxRetransimits:3});
// 限制通道允许重试操作持续的毫秒数
pc.createDataChannel('', {maxRetransimitTime:30});
// ps:maxRetransimits、maxRetransimitTime是互斥的
// 不止这两个配置
// order: 接收顺序,false则为接收顺序混乱也无所谓
更多例子:WebRTC samples