引言
WEBRTC 于 2021 年 1 月被 W3C 和 IETF 发布为正式标准后,已经应用在了很多方面,有些还做出了很不错的产品。下面是最近学习 WEBRTC 过程中一些浅薄的知识总结,如有错误,欢迎指正。全文阅读大概需要 15-20 分钟。
全文将按如下的流程展开:
- 介绍一下 WEBRTC 的历史和目标
- 简单讲解几个关键概念和工作流程
- 常用的 API 和示例代码
- 应用场景
WebRTC 是什么?
WebRTC 是 Web 实时通信(Real-Time Communication)的缩写,是由一家名为 Gobal IP Solutions(简称 GIPS)的瑞典公司开发的。Google 在 2011 年收购了 GIPS,并将其源代码开源。然后又与 IETF 和 W3C 的相关标准机构合作,以确保行业达成共识。其中:
- W3C 组织:定义浏览器 API。
- IETF 标准组织:定义其所需的协议,数据,安全性等手段。
简单来说,WebRTC 是一个可以在 Web 应用程序中实现音频,视频和数据的 实时通信的开源项目。
在实时通信中,音视频的采集和处理是一个很复杂的过程。比如音视频流的编解码、降噪和回声消除等,但是在 WebRTC 中,这一切都交由浏览器的底层封装来完成。我们可以直接拿到优化后的媒体流,然后将其输出到本地屏幕和扬声器,或者转发给其对等端。
我们可以在不需要任何第三方插件的情况下,实现一个浏览器到浏览器的点对点(P2P)连接,从而进行音视频实时通信。
WEBRTC 所用的技术大多已经实现了,WEBRTC 是把这些技术组合了起来。也可以说 WebRTC 是一组其他技术的集合体。
工作流程是什么样?
简单介绍后,我想你的脑海中已经大概知道 WEBRTC 是做什么的了,下面我们一起探究一下这个过程中发生了什么。我们可以将其分为四个步骤:
- 信令(Signaling)
- 连接(Connecting)
- 安全加密(Securing)
- 通信(Communicating)
这四个步骤依次发生。上一个步骤必须 100% 成功,随后的步骤才能开始。
信令(Signaling)
首先,需要了解的一个概念是信令。
信令是在两个设备之间发送控制信息以确定通信协议、信道、媒体编解码器和格式以及数据传输方法以及任何所需的路由信息的过程。
为什么需要了解这个概念?
虽然 WEBRTC 是 P2P 的,但并不代表不需要服务端的参与。当一个 WebRTC Agent 被创建时,它对其他的对等节 一无所知。它不知道它将与谁联系,也不知道它们将发送些什么!所以在信令阶段需要双向通信服务辅助信息交换。 交换信令消息后,WebRTC Agent 才可以直接相互通信。
格式
信令 的格式基于 SDP (Session Description Protocol) 规范,例如:
v=0
o=alice 2890844526 2890844526 IN IP4 host.anywhere.com
s=
c=IN IP4 host.anywhere.com
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
m=video 51372 RTP/AVP 31
a=rtpmap:31 H261/90000
m=video 53000 RTP/AVP 32
a=rtpmap:32 MPV/90000
-------------- 下面的内容为字段解释 ---------------
v - Version,版本,版本,应等于 0。
o - Origin,源,包含一个唯一 ID,用于重新协商。
s - Session Name,会话名称,应等于-。
t - Timing,时间,应等于 0 0。
m - Media Description(m=<media> <port> <proto> <fmt> ...),媒体描述,下面有详细说明。
a - Attribute,属性,一个自由文本字段,这是 WebRTC 中最常见的行。
c - Connection Data,连接数据,应等于 IN IP4 0.0.0.0。
-
v
,o
,s
,c
,t
虽然被定义,但他们不对 WebRTC 会话产生影响。 -
上面的代码里有三个媒体描述。第一个是
audio
即音频类型,数据映射到 PCMU 编解码器。后两个是video
即视频类型,分别映射到 H261 和 MPV 编解码器。下图是一个实际的示例:
内容
信令 的内容包含以下几个方面:
-
控制消息:用于设置、打开、关闭通信通道并处理错误。
-
为了建立连接所需的信息:设备间能够彼此交谈所需的 IP 寻址和端口信息。
-
媒体能力协商:交互双方可以理解哪些编解码器和媒体数据格式?这些都需要在 WebRTC 会议开始之前达成一致。
传输
信令 的传输没有规范,可以选择 Websocket 或者 xhr 甚至电子邮件。如果你愿意接受延迟,甚至可以把信令数据打印出来,飞鸽传书给另一个人,等对方手动输入,并用相同的方式返回给你信令数据。
流程
信令的使用和传输流程如下图所示,假如 Amy 向 Bob 发送了一个音视频邀约:
- 呼叫端创建 Offer 信息,并存储到本地,再发送给信令服务器,信令服务器转发给接收端
- 接收端收到 Offer 后,先存储远端描述,然后创建 Answer 信息。同样,先保存 Answer 到本地,再返回给信令服务器,信令服务器转发给呼叫端。
- 呼叫端拿到 Answer 后,再设置到远端描述
什么是 Offer 和 Answer?
WebRTC 使用 Offer/Answer 模型。这指的是,一个 WebRTC Agent 发出 “Offer” 以开始呼叫,如果另一个 WebRTC Agent 愿意接受 “Offer” 的内容,它会响应 “Answer”。
这使得应答者有机会拒绝媒体描述中的某些不支持的编解码器,也是两个 peer 互相理解他们希望交换何种格式的方式。
现在,两个 WebRTC Agent 知道足够的详细信息可以尝试相互连接了。
连接(Connecting)
接下来,WebRTC 将使用另一种成熟的技术,称为 ICE(交互式连接建立)。
ICE 允许在两个 Agent 之间建立连接。这些 Agent 可以在同一网络上,也可以在世界的另一端。ICE 是无需中央服务器即可建立直接连接的解决方案。
既然要直接建立连接,就必然需要解决例如 NAT 地址转换、防火墙、网络协议不同、IP 版本不同等问题。ICE 使用STUN 协议以及 TURN 协议来进行穿越。
NAT 穿越
现在的网络设备,很多是基于 NAT 技术使用同一个公网 IP 和不同的端口对外提供访问通道的。如果需要建立直接连接,就需要知道对方的公网 IP 和端口,以及对方的网络类型,比如是直接在公网可访问,还是经过了 NAT 设备。
STUN Server 做了什么?
-
接受客户端的请求,并且把客户端的公网 IP、Port 封装到 ICE Candidate 中。
-
通过一个复杂的机制(后文会讲),得到客户端的 NAT 类型。
简而言之,就是向公网的一个 STUN 服务器发送 Binding Request,公网的这个服务器能看到你的公网 IP 和端口,并测试你是否在 NAT 设备后面,以及 NAT 的类型。
判断 NAT 类型
RFC3489 中将 NAT 的实现分为四大类:
Full Cone NAT | 完全锥形 NAT | 无论什么IP地址访问,都不会被NAT墙掉(这种基本很少) |
---|---|---|
Restricted Cone NAT | 限制锥形 NAT | 理解为 IP 限制,Port不限制 |
Port Restricted Cone NAT | 端口限制锥形 NAT, | IP+Port 限制 |
Symmetric NAT | 对称 NAT | IP+Port 限制,同时对外的公网Port是不停的变化的比如A是一个对称NAT,那么A给B发信息,经过NAT映射到一个Port:10000,A给C发信息,经过NAT映射到一个Port:10001,这样会导致一个问题,我们服务器根本无法协调进行NAT打洞。 |
假设 B 是 客户端 ,C 是 STUN 服务器,C 有两个 IP 分别为 IP1 和 IP2(至于为什么要两个IP,接着往下看):
第一步:判断 客户端 是否在 NAT 后
B 向 C 的 IP1:PORT1
端口发送一个 UDP 包。C 收到这个包后,会把它收到包的源 IP 和 PORT 写到 UDP 包中,然后把此包通过 IP1:PORT1
发还给 B。这个 IP 和 port 也就是 NAT 的外网 IP 和 PORT。
B 收到后,拿返回的 IP 和自己的 IP 对比一下,如果一致,说明 B 就是用的公网 IP,没有用 NAT,如果不一致则继续探测防火墙类型。
第二步:判断是否处于Full Cone Nat 下:
B 还是向 IP1:PORT1
端口发送请求,但是要求 C 用 IP2:PORT2
返回结果。
如果 B 收到了,说明 NAT 来者不拒,不对数据包进行任何过滤,这也就是STUN标准中的 Full Cone NAT。
第三步:判断是否处于对称 NAT 下
根据对称 NAT 的规则,当目的地址的 IP 和 PORT 有任何一个改变,那么 NAT 都会重新分配一个port使用。所以 B 再向 IP2:PORT2
请求,返回的端口和第一次如果不一致,说明就是对称 NAT。
第四步:判断是处于 Restrict Cone NAT 还是 Port Restrict NAT 之下
B 向 IP1:PORT1
发请求,要求 C 换一个端口返回。
如果 B 收到了,那也就意味着只要 IP 相同,即使 PORT 不同,NAT 也允许 UDP 包通过。显然这是 Restrict Cone NAT。如果没收到,意味着 IP 和 PORT 都必须相同,就是 Port Restrict NAT.
完成了这些 STUN Server 就会把这些基本信息发送回客户端,然后根据 NAT 类型,来判断是否需要 TURN 服务器协调进行下一步工作。
当你的两个 peer 的 NAT 类型不兼容,或者双方使用不同协议时,就需要使用 TURN server,过程如上图所示。TURN server 也可以被用于保护隐私的目的,如果通过 TURN server 进行所有通讯,客户的真实地址在对端是被隐藏的。
候选地址选择
ICE 通过上面讲到的流程来找出两个 peer 之间所有可能的路由,这些路由被称为 Candidate Pair(候选地址对)
,也就是本地地址和远程地址的配对。这就是 STUN 和 TURN 在 ICE 中发挥作用的地方。这些地址可以是你的本地 IP 地址,NAT 映射
或中继传输地址
。通信双方需要收集它们要使用的所有地址,交换这些地址,然后尝试连接。
通常来说,发送 offer 的 peer 是控制中
的一方。控制中
的 Agent 和受控中
的 Agent 都开始在每个候选地址对上发送流量数据。
每个收到流量数据的候选地址对
,会被提升为有效候选地址
对。
接下来,控制中
的 Agent 将指定一个有效候选地址
对,这就是提名候选地址对
。
然后,控制中
的 Agent 和受控中
的 Agent 再尝试进行一轮双向通信。如果成功,则提名候选地址对
将成为选定的候选地址对
!它将被用于后面的会话中。
安全加密(Securing)
现在我们有了双向通信(基于 ICE),我们需要建立安全的通信,这是基于 WebRTC 前已有的两种协议完成的。
第一个协议是 DTLS(数据报传输层安全性),即基于 UDP 的 TLS。
第二种协议是 SRTP(安全实时传输协议)。
首先,WebRTC 通过在 ICE 建立的连接上进行 DTLS 握手来进行连接。与 HTTPS 不同,WebRTC 不使用中央授权来颁发证书。相反,WebRTC 只是判断通过 DTLS 交换的证书是否与通过信令共享的签名相符。然后,此 DTLS 连接可以被用于传输 DataChannel 消息。
WebRTC 使用两个预先存在的协议,数据报传输层安全(Datagram Transport Layer Security / DTLS)和 安全实时传输协议(Secure Real-time Transport Protocol / SRTP)。
DTLS 与 TLS 的区别仅在与其使用 UDP 而不是 TCP 作为其传输层。这也意味着 DTLS 协议必须处理不可靠的数据传输。SRTP 是专为安全的交换媒体数据而设计的。相对于 DTLS 而言,使用 SRTP 对传输媒体数据有一些优化。
通信(Communicating)
现在,我们有了两个具有安全的双向通信功能的 WebRTC Agent。让我们开始通信!跟前面一样,我们使用两个现有的协议:RTP(实时传输协议)和 SCTP(流控制传输协议)。我们使用 RTP 来交换用 SRTP 加密过的媒体数据,使用 SCTP 发送和接收那些用 DTLS 加密过的 DataChannel 消息。
整体流程图
常用 API
WebRTC 提供了一些 API 供我们使用,在实时音视频通信的过程中,我们主要用到以下三个:
- getUserMedia:获取音频和视频流(MediaStream)
- RTCPeerConnection:点对点通信
- RTCDataChannel:数据通信
简单看一下三个 API 的使用方式:
GetUserMedia
用来获取设备的媒体流(即 MediaStream)。它可以接受一个约束对象 constraints 作为参数,用来指定需要获取到什么样的媒体流。此要求可以非常宽泛(音频和/或视频),也可以非常具体(最低相机分辨率或确切设备 ID 等)。
navigator.mediaDevices.getUserMedia({ audio: true, video: true }) // 同时获取到音频和视频 .then(stream => { // 获取到优化后的媒体流 let video = document.querySelector('#rtc');
video.srcObject = stream;
}).catch(err => {});
RTCPeerConnection
创建点对点连接的 API,是我们实现音视频实时通信的关键。创建时,需要传入一个配置对象,包含 iceServers 字段
new RTCPeerConnection({
iceServers: [
{ url: "stun:stun.l.google.com:19302" }, // 谷歌的公共服务
{
urls: "turn:***",
credential: "***",
username: "***",
},
],
});
RTCDataChannel
创建数据通道,用来交换任意类型的数据
let dataChannel = pc.createDataChannel("MyApp Channel");
dataChannel.addEventListener("open", (event) => {
beginTransmission(dataChannel);
});
代码示例
下面是一个简单的 WRBRTC 视频通话示例,代码语言为 JavaScript,安装依赖后启动,会看到下图所示的日志,可以参照上面所讲的内容对照理解整体流程:
有哪些应用场景?
电话视频会议
WebRTC 为媒体提供拥塞控制和自适应比特率,随着网络条件的变化,用户仍将获得最佳体验,开发人员不必编写任何其他代码来处理这些情况。
由于可以对视频做处理,所以也支持例如添加滤镜、背景替换、AR 等功能。
Facebook Messenger、Discord、Amazon Chime、Google Meet/Hangout/Duo,都是基于 WebRTC 的视频通讯工具、视频会议应用。
音视频发布、消费平台
浏览器中的 WebRTC 使得用户可以轻松发布视频。这样用户不需要下载新的客户端。 任何具有 Web 浏览器的平台都可以发布视频。发布者可以发送多个音轨 / 视频流,并可以随时对其进行修改或删除。传统协议中每个连接只允许一个音频或一个视频流,与之相比,这是一个巨大的改进。
在线白板、投屏
利用 captureStream
API 可以直接捕获 canvas 的画面,然后通过 WEBRTC 以流的形式传输给对方,就很容易的实现了在线白板功能。
this.localstream = canvasDom.captureStream();
利用 getDisplayMedia
API 可以直接捕获其他浏览器 tab、窗口或屏幕的画面(例如飞书投屏时的选择),下面是核心代码:
navigator.mediaDevices.getDisplayMedia(options)
.then(handleSuccess, handleError);
handleSuccess(stream) {
video.srcObject = stream;
}
云游戏
2020 年,云游戏已经上线了。它的实现有赖于 WebRTC。 Stadia(Google 的云游戏平台)已于 2019 年底推出,但 2020 年初才正式在浏览器得以支持。其云游戏搭载 VP9,提供 4k、HDR 图像和环绕声体验。这些都会通过 WebRTC 进行传输。