WebRTC学习笔记(一)|基于开源WebRTC,从0到1实现实时音视频聊天功能

实习公司一直开展的项目突然停滞了,因为一个老板的需求我们一直无法满足:

  • 老板想用现有的WebRTC技术,我们使用了现有的技术,但是效果不够好;
  • 使用别的公司的技术但是收费又太贵了,初创公司没有那么多钱;
  • 初创公司没有技术leader,第一批员工大部分都是实习员工;

别问我为啥不跑路,说多了都是泪。

借着这个机会,好好学习一下webRTC技术,同时学会怎么阅读golang库的源码。

零基础入门:基于开源WebRTC,从0到1实现实时音视频聊天功能

知识准备

音视频理论基础

什么是WebRTC

WebRTC学习资源

技术组成

简单来说,WebRTC 是一个可以在 Web 应用程序中实现音频,视频和数据的实时通信的开源项目。在实时通信中,音视频的采集和处理是一个很复杂的过程。比如音视频流的编解码、降噪和回声消除等,但是在 WebRTC 中,这一切都交由浏览器的底层封装来完成。我们可以直接拿到优化后的媒体流,然后将其输出到本地屏幕和扬声器,或者转发给其对等端。

img
  • WebAPI层:面向开发者提供标准API(javascirpt),前端应用通过这一层接入使用WebRTC能力。
  • C++ API层:面向浏览器开发者,使浏览器制造商能够轻松地实现Web API方案。
  • 音频引擎(VoiceEngine):音频引擎是一系列音频多媒体处理的框架,包括从视频采集卡到网络传输端等整个解决方案。
    • iSAC/iLBC/Opus等编解码
    • NetEQ语音信号处理
    • 回声消除和降噪
  • 视频引擎(VideoEngine): 是一系列视频处理的整体框架,从摄像头采集视频、视频信息网络传输到视频显示整个完整过程的解决方案。
    • VP8编解码
    • jitter buffer:动态抖动缓冲
    • Image enhancements:图像增益
  • 传输(Transport):传输 / 会话层,会话协商 + NAT穿透组件
    • RTP 实时协议
    • P2P传输 STUN+TRUN+ICE实现的网络穿越

虽然浏览器给我们解决了大部分音视频处理问题,但是从浏览器请求音频和视频时,我们还是需要特别注意流的大小和质量。因为即便硬件能够捕获高清质量流,CPU 和带宽也不一定可以跟上,这也是我们在建立多个对等连接时,不得不考虑的问题。

WebRTC的P2P通信原理

技术难点

P2P即点对点通信。

要实现两个不同的网络环境(具有麦克风、摄像头设备)的客户端(可能是不同的Web浏览器或者手机APP)之间的音视频通信的问题:

  1. 怎么知道彼此的存在也就是如何发现对方?
  2. 彼此音视频编码能力如何沟通?
  3. 音视频数据如何传输,怎么让对方看得到自己?

怎么知道彼此的存在

WebRTC虽然支持端对端通信,但是这不意味着WebRTC不再需要服务器。

P2P通信过程中,双方需要交换一些元数据如媒体信息、网络数据等信息,通常把这一过程叫做信令(signaling)。

对应的服务器既为信令服务器,也被称为房间服务器。因为它不仅可以交换彼此的媒体信息和网络信息,也可以管理网络信息。

如:

  1. 通知彼此who加入了房间
  2. who离开了房间
  3. 告诉第三方房间人数是否已满可以加入房间。

为了避免出现冗余,并最大限度提高与已有技术的兼容性,WebTRTC并没有规定信令和协议。在本文后面的实践章节会基于Node.js实现一个信令服务器。

彼此音视频编码能力如何沟通

不同浏览器对于音视频的编解码能力是不同的。为了保证双方都可以正确的编解码,最简单的方法就是使用客户端支持编码方式的交集

在WebRTC中有一个专门的协议,Session Description Protocol(SDP),可以用于描述上述这类信息。

参与音视频通讯的双方想要了解对方支持的媒体格式,必须交换SDP信息,而交换SDP的过程,通常被称为媒体协商。

音视频数据如何传输,怎么能让对方看见自己

其问题的本质是网络协商的过程,即参与音视频实时通信的双方要了解彼此之间的网络状况,这样才能找到一条互相通讯的链路。

理想的网络情况是每个浏览器都有自己的公网IP地址,这样的话就可以直接进行点对点连接。

然而大部分情况是我们的设备基本上都是处于某个局域网内,需要Network Address Translation(NAT),网络地址转换才能和外部的ip地址通信。

什么是ICE?

Interactive Connectivity Establishment(ICE),交互式连接建立。ICE不是一种协议,而是整合了STUN和TURN两种协议的框架。

其中:STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序),它允许位于NAT或多重NAT后的客户端找出自己对应的公网IP地址和端口,也就是俗称的P2P打洞。

但是,如果NAT类型对称型的话,那么就无法打动成功。这是TURN就派上用场了。TURN(Traversal Using Replays around NAT)是STUN/RFC5389的一个扩展协议在其基础上添加了Replay(中继)功能。

简单说,其目的就是解决对称型NAT无法穿越的问题,在STUN分配公网失败以后,可以通过TURN服务器请求公网IP地址作为中继地址。

在WebRCT中有三种类型的ICE候选者,分别是:

  • 主机候选者:表示的是本地局域网内的IP地址和端口,他是三个候选者中优先级最高的,也就是WebRTC底层首先会尝试本地局域网建立连接。
  • 反射候选者:表示的是获取NAT内主机的外网IP地址和端口,其优先级低于主机候选者。也就是说当WebRTC尝试本地连接不通时,会尝试通过反射候选者获得的IP地址和端口进行连接;
  • 中级候选者:表示的是中继服务器的IP地址和端,即通过服务器中转媒体数据,当WebRTC客户端通信双方无法穿越P2P时,为了保证双方可以通讯,此时只能通过服务器中转来保证服务质量。

从上图可以看出:在非本地局域网内WebRTC通过STUN Server获得自己的外网IP和端口,然后通过信令服务器与远端的WebRTC交换网络信息,之后双方就可以尝试建立P2P连接了。当NAT穿越不成功时,则会通过Relay Server(TURN)中转。

值得一提的是:在WebRTC中网络信息通常用candidate来描述,而上图中的STUN server和Replay server也可以同一个server。本文使用继承了STUN(打洞)和TURN(中继)的开源项目coturn。

综上对三个问题的解释,我们可以用下图来说明WebRTC实现点对点的通信的基本原理。

简而言之:就是通过WebRTC提供的API获取各段的媒体信息SDP以及网络信息candidate,并通过信令服务器交换,进而建立了两端的连接通道完成实时视频语音通话。

WebRTC几个重要API

音视频采集API

音视频采集 API,即 MediaDevices.getUserMedia()

1
2
3
4
5
6
7
8
9
10
11
12
const constraints = {
video: true,
audio: true

};
// 非安全模式(非https/localhost)下 navigator.mediaDevices 会返回 undefined
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
document.querySelector('video').srcObject = stream;
} catch (error) {
console.error(error);
}

获取音视频设备输入输出列表

获取音视频设备输入输出列表API,即 MediaDevices.enumerateDevices()

1
2
3
4
5
6
7
8
try {
const devices = await navigator.mediaDevices.enumerateDevices();
this.videoinputs = devices.filter(device => device.kind === 'videoinput');
this.audiooutputs = devices.filter(device => device.kind === 'audiooutput');
this.audioinputs = devices.filter(device => device.kind === 'audioinput');
} catch (error) {
console.error(error);
}

创建点对点连接

RTCPeerConnection 作为创建点对点连接的 API,是我们实现音视频实时通信的关键。

在本文的实践章节中,主要运用到了以下方法。

媒体协商方法:

重要事件:

P2P通信中一个最重要的环节就是交换媒体信息。

原理图:

从上图不难发现,整个媒体协过程也可以简化为三个步骤对应上述四个媒体协商方法。

  • 呼叫端Amy创建Offer(createOffer)并将offer消息(内容是呼叫段Amy的SDP信息)通过信令服务器传送给接收端Bob,同时调用setLocalDescription将含有本地SDP的Offer保存起来;
  • 接收端Bob收到对端的Offer信息调用setRemoteDescription方法将含有对端SDP信息的Offer保存起来,并创建Answer(createAnswer)并将Answer消息(内容是接受端Bob的SDP消息)通过信令服务器传送给呼叫端Amy;
  • 呼叫端Amy收到对端的Answer消息后调用setRemoteDescription方法将对端SDP信息的Answer保存起来。

经过上述三个步骤,则完成了P2P通信过程中的媒体协商部分。

实际上:在呼叫端以及接收端调用setLocalDescription他同时也开始了收集各端自己的网络信息(candidate),然后各端通过监听事件(onicecandidate)收集到了各自的candidate并通过信令服务器传输给对端,进而打通了P2P的网络通道,并通过监听onaddstream事件拿到了对方的视频流进而完成了整个视频通话过程。

动手编码实践

代码操作的部分可以可参考下文中的原文链接

参考

http://www.52im.net/thread-3680-1-1.html#2