文章图片
何为WebRTC?估计有很多同学已经听过相关名词,但是也只是云里雾里懵逼状态。说到WebRTC的应用场景,可能大概就会知道WebRTC可以用来做什么事情,例如近几年比较火热的直播&带货、视频会议、网课,或多或少在技术实现上都避不开使用WebRTC的相关技术。
本文将向大家介绍WebRTC相关技术内容,相信看完后,能对WebRTC有一个新的认识。
什么是WebRTC?
WebRTC (Web Real-Time Communication,网页实时通信)是谷歌的开源项目,提供了一套标准API,使Web应用在不需要借助任何插件的情况下可以直接提供实时音视频通信功能。同时,也无须关注音视频通信相关的技术,例如视频采集、编解码、网络实时传输等内容,可以方便的基于JavaScript快速实现音视频通讯应用。
WebRTC 由 IETF (Internet Engineering Task Force,互联网工程任务组) 和 W3C (World Wide Web Consortium,万维网联盟) 联合负责其标准化;
- IETF 定制 WebRTC 的互联网基础协议标准,该标准也被称为 RTC Web (Real-Time Communication in Web-browsers);
- W3C 则负责定制 WebRTC 的客户端 JavaScript API 接口的标准;
历史
- 它是谷歌2010年5月收购拥有编解码、回声消除等技术的Global IP Solutions公司而获得的一项技术,后改名为WebRTC;
- 2011年5月开放了工程的源代码,与相关机构 IETF 和 W3C 制定行业标准,组成了现有的 WebRTC 项目,在行业内得到了广泛的支持和应用,成为下一代视频通话的标准;
- 2012年1月,谷歌已经将这项技术集成到Chrome浏览器中;
- 2013年 6月,Mozilla Firefox发布22.0版本正式集成及支持WebRTC;
- 2017年11月,W3C WebRTC 1.0 草案正式定稿;
- 2021年1月,WebRTC 被 W3C 和 IETF 发布为正式标准;
- 社交平台,如视频聊天室应用
- 远程实时监控
- 在线教育、在线培训
- 会议系统
- Web IM
- 会议和联系中心之间的协作,如客户服务、呼叫中心
- 游戏娱乐,如双人对战游戏(如象棋这种双人对战游戏,每一步的数据服务器时不关心的,所以完全可以点对点发送)
- 屏幕共享
- 人脸检测识别
- ...
文章图片
说明
虽然作为上层开发者,对于音视频底层的技术可能难以接触和学习,但是还是有必要了解一下底层在音视频通信上都有哪些处理。
从上图可以看到,WebRTC可以大致分为三类层级,面向的用户或者开发者也不尽相同:
1. Web API 此层级面向Web开发者的APIs,框架包括了JavaScript、经过W3C认证的一套API标准。API 提供三个功能接口,分别是 MediaStream、RTCPeerConnection 和 RTCDataChannel,通过此APIs可以快速实现一个点对点的视频通话应用;
- MediaStream:用于浏览器采集或储存输出设备的实时音视频流(包含音频/视频数据),方便快速进行视频流的采集的渲染,是常用的API之一;
- RTCPeerConnection:此接口是WebRTC的核心接口,用来处理设备和设备之间稳定的连接和数据流通信;
- RTCDataChannel:此接口是WebRTC用于连接数据传输的通道,不仅仅是音/视频数据,还可以是任意的文本/文件等数据;
2. WebRTC C++ API C++ API 提供给浏览器厂商、平台 SDK 开发者使用的 C++ API,不同的平台可以通过各自的 C++ 接口调用能力,对其进行上层封装以满足跨平台的需求;
3. 浏览器厂商 上图中虚线部分即框架中包含浏览器厂商可自定义的扩展内容,包括视音频的采集/渲染/网络IO部分,例如可以快速实现浏览器视频截屏插件。
核心技术
上图可以看到分三层用户类别,但C++ API将是核心的内容,是由视频、音频、和传输三部分组成,其中传输又包含了众多的协议和方式;
1. Voice Engine(音频引擎) 音频引擎,包含一系列音频多媒体处理的框架,包括 Audio Codecs、NetEQ for voice、Acoustic Echo Canceller (AEC) 和 Noise Reduction (NR);
- Audio Codec:音频编解码器,目前支持的有:Opus、G.711 PCM (A-law)、G.711 PCM (μ-law)、G722、iLBC、iSAC,具体编码器特性详见MDN介绍;
- NetEQ for voice:自适应抖动控制算法以及语音包丢失隐藏算法,用于适应不断变化的网络环境,从而保持尽可能低的延迟,同时保持最高的语音质量;
- Acoustic Echo Canceller (AEC) :回声消除器,用于实时消除麦克风采集到的回声;
- Noise Reduction (NR):噪声抑制器,用于消除与相关 VoIP 的某些类型的背景噪音;
2. Video Engine(视频引擎)视频处理引擎,包含一系列视频处理的整体框架,从摄像头采集视频到视频信息网络传输再到视频显示整个完整过程的解决方案,包括 Video Codec、Video Jitter Buffer 和 Image Enhancement;
- Video Codec:视频编解码器,目前常见的的有:VP8、VP9 和 H.264 编解码,此处做个说明,主流的编解码器主要是H.264,不同的浏览器端涉及到版权影响,需要额外考虑不同编码器所造成的兼容情况;
- Video Jitter Buffer:视频抖动缓冲器,处理视频抖动和视频信息包丢失等问题;
- Image Enhancement:图像质量增强模块,用于对摄像头采集回来的图像进行处理,包括明暗度检测、颜色增强、降噪处理等;
3. Transport(数据传输模块)数据传输模块,WebRTC 对音视频进行 P2P 传输的核心模块,包括 SRTP、Multiplexing 和 P2P;
- SRTP:基于 UDP 的安全实时传输协议,用于音视频流传输;
- Multiplexing:多路复用技术,采用多路复用技术能把多个信号组合在一条物理信道上进行传输,减少对传输线路的数量消耗;
- P2P 是端对端传输技术,WebRTC 的 P2P 技术集成了 STUN、TURN 和 ICE,这些都是针对 UDP 的 NAT 的防火墙探测、穿越方式,为了保证双向的连接能够正确的获取对方的网络信息;
接下来,再深入理解一对一通话所涉及到的几个关键专业名词和技术解释,帮忙我们更好的为后续实现一对一通话打下基础。
信令服务器
信令服务器(Signal Server)是各个端能够实现会话信息共享的服务器,一般搭建在公网或者各个端都能互通的局域网中。
同时,主流的信令服务架构上,也会承载如下内容:
- 控制会话整个生命周期,包括不限于会话创建、控制(静音、设置会场主持人)、结束、鉴权等会议操作;
- 处理会话错误的消息;
- 传递网络信息、媒体协商信息、对方的公网IP、端口、内网IP及端口信息,即如下所说的SDP协议报文;
文章图片
实现方式:实现信令服务器的方式有很多,例如通过TCP、WebSocket、Socket.io等方式实现信令服务器。
信令消息(Signal Message):可以看作是各个端和服务器约定成俗的数据类型,一般来说,是对象数据格式,每个信令消息包含自己的Type信息、基础信息、会需要交换的数据信息。例如:
{
type: "answer",
meetingId: "xxx",
data: {
sdp: "xxx",
}
}
媒体协商(SDP:Session Description Protocol)
在P2P连接时,需要进行发送Offer和Answer应答,其中使用 SDP 来描述会话的媒体信息、编解码器信息、基本信息等描述内容,参与音视频通信的双方必须交换此信息,这种交换过程我们就可以理解为媒体协商;
为什么需要媒体协商? 不同端浏览器的媒体数据编解码数据格式不一致,为了让Peer1与Peer2进行视频互通,必须要保证两端都能正确的编解码,所以需要协商出一致的H264编解码器。
文章图片
SDP格式
Session description
v=(protocol version) 协议版本号
o=(originator and session identifier) 会话所有者有关的参数
s=(session name) 会话名称
i=* (session information) 会话信息
u=* (URI of description) 描述的URI
c=* (connection information -- not required if included in
all media) 连接信息 - 如果包括在内,则不需要所有媒体
b=* (zero or more bandwidth information lines) 所有带宽信息
One or more time descriptions ("t=" and "r=" lines;
see below)
z=* (time zone adjustments)时区调整
k=* (encryption key) 加密密钥
a=* (zero or more session attribute lines) 零个或多个会话属性行
Time description
t=(time the session is active) 会话活动的时间
r=* (zero or more repeat times) 零次或多次重复次数
Media description, if present
m=(media name and transport address) 媒体名称和传输地址
i=* (media title) 媒体title
c=* (connection information -- optional if included at
session level)连接信息 - 可选,如果包含在会话级别
b=* (zero or more bandwidth information lines) 带宽信息
k=* (encryption key) 加密密钥
a=* (zero or more media attribute lines) 媒体属性信息
SDP例子 下面是展示Safari浏览器协商出来的SDP会话报文内容:
v=0
s=-
t=0 0
a=group:BUNDLE 0 1
a=msid-semantic: WMS 88b1b161-a5b6-4b7e-8ba1-f8223f5eae8f
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 125 104
c=IN IP4
a=setup:actpass
a=mid:0
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=sendrecv
a=msid:88b1b161-a5b6-4b7e-8ba1-f8223f5eae8f db4ccf3c-14fe-495c-bee3-3ae55b2f83ea
受限文章篇幅,将在后续文章详细介绍SDP会话协议报文内容,此处只做简单的了解即可;
网络协商
上面说到,P2P 是端对端传输技术,所以为了连接两个应用端,就需要知道双方的网络信息,使用真实的公网ip进行UDP的连接,从而进行数据流的快速传递(注意和上文的信令服务器的区别)。
WebRTC P2P 技术集成了 STUN、TURN 和 ICE,这些都是针对 UDP 的 NAT 的防火墙探测、穿越方式,为了保证获取到各自真实的网络信息。
NAT
在现实网络环境中,大多数计算机主机都位于路由器、防火墙、NAT之后,只有少部分终端能够直接接入网络。我们希望网络中的两台设备或应用能够直接进行通信,即所谓的P2P通信,而不需要其他公共服务器的中转。由于主机可能位于防火墙、NAT之后,在进行P2P通信之前,我们需要进行检测以确认它们之间能否进行P2P通信以及如何通信。这种技术通常称为NAT穿透(NAT Traversal)。最常见的NAT穿透是基于UDP的技术;简单来说,NAT可以将本地IP和端口的映射到公网IP和端口,在 NAT 进行转换时,需要使用到 STUN 和 TURN;
STUN(Simple Traversal of UDP Through NATs) 【【音视频】WebRTC】简单的用 UDP 穿透 NAT,是个轻量级的协议,基于 UDP 的完整的穿透 NAT 的解决方案,它的作用是使位于 NAT 后的客户端找出自己的公网 IP 地址与端口号,方便进行端对端的P2P连接。
文章图片
WebRTC使用STUN获取服务IP和端口详细流程在这里不做说明,后续会在架构文章中解释整个探测的流程。此处我们所要知道的就是通过STUN Server我们可以快速获取外网IP进行P2P的端对端连接;
TURN(Traversal Using Relays around NAT) 使用中继穿透NAT,属于STUN的中继扩展。简单的说,TURN是通过通讯双方的“中间人”方式实现数据互通。
文章图片
ICE(Interactive Connectivity Establishment) 翻译过来的意思是:互动式连接建立。ICE 不是一种协议,它是一个框架,整合了 STUN 和 TURN,使各种 NAT 穿透技术可以实现统一。
ICE 会先尝试 STUN,查出自己本地IP和端口从而建立 UDP 连接,如果失败了 ICE 就会再尝试 TCP(先尝试 HTTP,再尝试 HTTPS),如果仍然失败就使用中继的 TURN 服务器。因此,ICE 可以实现在未知网络拓扑结构中的设备互连。
一对一通话流程 如上介绍了一对一通话基础涉及的相关技术名词,有了一定的理解后,我们再来查看下图展示的一对一WebRTC呼叫的时序图:
文章图片
解释一下上面的时序图:
- ClientA通过信令服务器建立会议,ClientB加入会议(Connect),形成连接;
- ClientA建立Peer连接,并添加本地的画面流数据(addTracks);
- ClientA建立Offer,通过信令服务器发送Offer SDP报文,等待Answer响应;
- ClientB接收到Offer后,触发setLocalDescription,触发“候选网络链路”收集,并通过信令返回ClientA Answer应答;
- ClientA接收到STUN Server Candidate信息(网络链路探测得到的内外网IP信息,端口等)后,通过信令服务器发送给ClientB;
- ClientB响应Candidate消息,并返回Clienta交换网络信息;
- 如果两个网络信息达成一致,则整个P2P的连接成功;
- 双方通过OnAddStream事件,获取到Peer通道上的MediaStream Track数据展示并播放;
关键API使用
- MediaStream
- RTCPeerConnection
- RTCDataChannel
MediaStream
MediaStream 是一个媒体内容的流,一个流包含几个轨道,比如视频和音频轨道。可以通过getUserMedia
方法获取 MediaStream 媒体流;
MeidaStream Play - 锐客网
效果如下:
文章图片
RTCPeerConnection
每个对等连接由 RTCPeerConnection 对象处理。类的构造函数传递单个 RTCConfiguration 对象作为其参数。
调用端:
async function makeCall() { // 此处signalingChannel代表信令服务器通道,监听远端message消息 signalingChannel.addEventListener('message', async message => { if (message.answer) { // 接收到Answer消息 const remoteDesc = new RTCSessionDescription(message.answer); // 设置描述 await peerConnection.setRemoteDescription(remoteDesc); } }); // 配置ICE STUN服务器 const configuration = { 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }] } // 创建PeerConnection const peerConnection = new RTCPeerConnection(configuration); // 创建Offer消息 const offer = await peerConnection.createOffer(); // 将Offer设置到本地描述中 await peerConnection.setLocalDescription(offer); // 通过信令服务区发送给接收端 signalingChannel.send({ 'offer': offer }); }
接收端:
// 创建PeerConnection const peerConnection = new RTCPeerConnection(configuration); // 监听信令的message消息 signalingChannel.addEventListener('message', async message => { if (message.offer) { // 接收到Offer消息,设置远端描述 peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer)); // 创建Answer消息 const answer = await peerConnection.createAnswer(); // 设置到本地描述 await peerConnection.setLocalDescription(answer); // 通过信令发送给发送端 signalingChannel.send({ 'answer': answer }); } });
ICE探测:
// 发送端 peerConnection.addEventListener('icecandidate', event => { if (event.candidate) { signalingChannel.send({'new-ice-candidate': event.candidate}); } }); // 接收端 signalingChannel.addEventListener('message', async message => { if (message.iceCandidate) { try { await peerConnection.addIceCandidate(message.iceCandidate); } catch (e) { console.error('Error adding received ice candidate', e); } } });
RTCDataChannel
通过RTCDataChannel可以发送任意数据,此处将演示基础的使用,在实际使用中,不建议使用此数据通道,可能受低带宽影响,存在数据丢失问题。
// 发送端 const peerConnection = new RTCPeerConnection(configuration); const dataChannel = peerConnection.createDataChannel(); dataChannel.send("hello world"); // 接收端 const peerConnection = new RTCPeerConnection(configuration); peerConnection.addEventListener('datachannel', event => { const dataChannel = event.channel; });
API部分只是简单的使用演示,再接下来的文章中,我们会详细的介绍每个API的详细使用方式。如果想提前自行学习了解,此处推荐查阅MDN WebRTC系列API内容;
如有问题,可在评论区留言进行交流;
参考
- Get Started with WebRTC
- webrtc.org
- WebRTC MDN APIs
- MediaStream MDN
- 开源工程WebRTC的技术原理和使用浅析
- 百科-WebRTC
- NAT穿越与ICE