如今作为开发者,对于音视频相关的技术已经越来越无法回避。音视频信息连接着数字世界与物理世界,不同于其他数据,音视频数据人眼可读而机器不可读,这给开发与调试带来了很大难度,而对应开发出的成果也比单纯的结构化数据要有趣的多。音视频方面的框架与协议众多,而WebRTC的低延迟及点对点通信能力非常强大,相当与给自己在网络中打开了眼睛,不由地让人联想到草稚素子融入互联网后无处不在的能力。于是打算从它开始学习音视频领域编程知识,在此之前我几乎没有学习过任何这个领域的知识。在阅读了很久的文档并初步实现了一个1对1视频聊天的demo后,感觉在网上关于WebRTC有用的资料并不多,很多入门介绍都是东拼西凑,内容雷同,连官方文档都凌乱不堪,最后发现MDN的文档非常详实。在此做一个关于WebRTC的介绍,巩固自己知识的同时,希望能让同样想学习这项技术的人少走一些弯路。
WebRTC是什么
开始学习后的第一大问题是:WebRTC究竟是什么?它究竟是一个协议(如http)还是一组工具(如java NIO、netty)还是其他的什么东西?
MDN文档中的原文如下:
WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。
WebRTC包含了若干相互关联的API和协议以达到这个目标。
这个描述并不能让人明白WebRTC究竟是什么,但这东西在这么有限的篇幅下确实不好描述。
我来尝试给出一个口语版本的介绍:
WebRTC是一套用来建立实时音视频通讯的解决方案,这套解决方案目前主要被各大主流浏览器支持并实现出来。于是有一套公认的javascript API可以运行在各大现代浏览器上。但这套API无法独立实现实时音视频通讯,它需要开发者自己补全流程中的端对端沟通(信令系统)逻辑,补全后开发者就可以让浏览器具备实时音视频通讯的能力了。
与其说WebRTC是“一套”方案,不如说是“半套”方案,因为负责端到端沟通的信令系统需要你一行行自己实现。打个不太恰当的比方,WebRTC就好像一家做汽车变速箱的公司,你如果想用这个东西,就得把发动机(信令系统)正确地连接到这个变速箱上,如果你想让车跑起来,就还需要自己安装轮子(video标签)和外壳(前端样式)。乍一看会觉得很失望——就这?才半套方案?但仔细想想,作为浏览器厂商指定的方案不可能替开发者解决端到端的沟通问题,他们必须开放给开发者自己解决。另外需要知道的是,并不是只有浏览器实现了WebRTC方案,有很多其他语音实现了相应的SDK,但各大浏览器厂商是其最大的支持者。
WebRTC如何工作及相关核心概念
那么WebRTC如何实现点对点的试试音视频通讯呢?大概这几个步骤:
-.打开本地的音视频设备。
1.呼叫方(浏览器端)初始化连接并描述自己的音视频设备信息。
2.呼叫方将描述信息通过某种方式发送给接听方(另一个浏览器端)。
3.接听方接收呼叫方的描述信息并生成自己的设备描述信息。
4.接收方将自己的设备描述信息通过某种方式发送回呼叫方。
5.[自动] 呼叫方及接听方通过某种方式互相传递自己的网络地址(会有多个地址)。
6.[自动] 通讯双方选定一个对方地址,并建立通道来传输音视频数据。
-.在界面上渲染音视频数据。
上文中的1到6步描述了webRTC建立连接的核心步骤。下面我们详细解释:
首先呼叫方需要新建一个WebRTC连接对象(PeerConnection),这个连接对象可以针对浏览器所在的环境生成音视频设备的描述信息(createOffer)。这个描述信息有一个成熟的格式标准——DSP(Session Description Protocol)。在第一步中DSP中的内容是呼叫方对自己媒体设备信息的描述,这个描述称为offer。
包含有offer的DSP生成以后就需要把它传递给接听方,这个负责在两个对等方间传递信息的功能在WebRTC中叫做“信令”。由于呼叫方和接收方只是两个浏览器实例,那么一个关键问题出现了:如何在网络中找到接听方并给他传递信息呢?答案是:“自己解决”。WebRTC虽然没有(也不应该)给出信令的实现,但是给出了一些建议,比如基于WebSocket或者HTTP。于是就出现了一个非常合理的选型——socket.io,只看名字就知道它具有管理websocket连接的能力。值得一提的是在它的说明文档中强调socket.io并不是一个WebSocket的简单封装,因为它在不支持WebSocket的环境下依然可以通过HTTP的long range方式实现类似WebSocket的效果。我们现在已经确定使用socket.io来搭建信令系统了。那么实现方式简单来讲就是呼叫方与接收方都连接到服务器后就可以感知到对方的存在并获取对方的ID号,于呼叫方就可以使用接听方的ID号发起一个呼叫事件,事件经过服务器传给接听方后,接听方就得到了呼叫方包裹在DSP报文中的offer信息。第2步完成。
第3步与第1步相似,但接收方需要先将呼叫方的offer纳入到自己的WebRTC连接中,然后新建一个应答(answer),这个应答信息与offer相似,并且也被转化称DSP格式。
第4步与第2步相似,接收方将包含有answer的DSP通过信令系统发送给接收方。
从第5步起就是WebRTC自动完成的了,WebRTC使用一种叫做ICE(Interactive Connectivity Establishment)的技术,同过这项技术,连接端可以感知到自己在网络中的地址及WebRTC连接对应的端口号,这些信息获取后,会继续通过信令系统传递到连接的另一端。通常情况下一个连接的网络地址有很多个,比如说若干个内网地址和一个公网地址,内网地址取决于你机器的网卡数量,公网地址是否能获取到取决于你具体的互联网接入方式,这部分比较复杂,下文会再讲。
第6步就是在第5步中拿到的各个网络地址选择出一个最稳定的地址,如果选定成功,那么也代表着点对点的WebRTC连接已经成功地建立了起来,这时候两端的WebRTC端点会自动将音视频数据传递给连接上的另一端并触发连接的OnTrack事件,这时候我们手中就已经拿到了一个视频流对象,把这个视频流对象赋值给页面上对应的video元素我们就可以看到画面了。
到此,WebRTC的点对点连接就已经开始工作了,我们已经走完了一个基本的WebRTC流程。同时我们可以看到WebRTC所说的点对点连接仅仅针对WebRTC连接本身,我们例子中的信令系统是需要依赖中心服务器的,当然你也可以使用其他有中心或无中心的技术实现信令传递。
上文的描述中还有几个在点对点连接过程中涉及的更深入的概念没有介绍,下面列出来简单做一下解释:
NAT:Network Address Translation,网络地址转换,这是计算机网络的重要概念,也是现实中各个运营商普遍使用的技术,几乎所有家庭网络都使用NAT方式进行互联网接入,更详细的介绍可以自行搜索。这项技术节省了公网IP地址,但增加了网络的复杂度,使内网中的设备无法方便的在公网上被访问到,下面的STUN和TURN技术都是用来解决这个问题的,类似的技术统称为内网穿透技术。
STUN:全称Session Traversal Utilities for NAT,这就是上文提到的帮助连接端获得其公网地址的服务,公网中有几个公共的地址为所有人免费提供这个服务,这个服务的原理到不难理解——使用一个连接端对在公网上的服务发起一个请求后,服务端就可以看到这个端点在公网上的出口地址及端口,服务端把这组信息返回给连接端即可。这就像在网络中的GPS系统。
TURN:全称Traversal Using Relays around NAT,在现实网络环境中,STUN服务可能会失效,因为有很多网络是被管理员严格限制的,这时就可以使用TURN服务来建立WebRTC连接,它同样会给出一个公网地址,但这个地址并不是端点自己的地址,而是TURN服务器所在地的地址,它帮端点代理了公网连接,代价是所有的音视频流量都要经过TURN服务器。
WebRTC最简单的用法是上文所讲的点对点方式,点对点方式在1对1场景下是非常棒的,因为流量直接传递,延迟降到了最低,而且不需要担心中转点监听。但是其缺点也很明显,首先在复杂网络下的ICE协商可能会很困难,且有可能要依赖TURN中转,其次,在多方会议场景下几乎是不可能使用这种方式的,因为连接数会成几何方式倍增,无论是网络还是终端都无法承受这种压力。所以WebRTC的还提供了其他的连接形式。下面介绍一下WebRTC的三种组网方式:
MESH:看名字就知道,网状连接,就是上文讲的点对点直接组网,这在小规模环境下问题不大,但多方会议就几乎无法使用了。
SFU:全称Selective Forwarding Unit,相比与MESH中所有终端都互联,SFU网络中的终端只与SFU服务器建立连接,如果三方在进行通话,则每一方都会有一路输出连接和两路输入连接,SFU服务器得到输出连接后帮助输出端分发给其他端,这相较与MESH网络省去了每个视频发送端的N-1个输出连接。这个方案节省了上行带宽,且保留了灵活性,是一个均衡的方案。
MCU:全称MultiPoint Control Unit,这种方案相较与SFU更进一步,将所有端点上传的流混合成一个流,把这一个流下发给所有人,每个端点只有一个连接,既负责上传也负责下载。这种方案的好处非常明显,所有终端的开销达到了最小,且所有终端看到的画面完全相同。但缺点同样明显,MCU需要合并多媒体流,这需要很强的性能且缺乏灵活性。MCU方案是大规模商业化方案中最成熟的。
开发难点
从上文可以看到,WebRTC的流程比较长,涉及的概念众多,对于刚开始接触的初学者来说会有一段时间非常迷茫,这需要很大的耐心来仔细梳理。
此外连接建立初期的信令系统需要完全由开发者自行实现。仅webwocket建立连接就足够入门者喝一壶的,建立wss连接需要后端服务有https能力,这就需要你自己生成一套自签名证书,然后用某种方式把你的后端服务变成https的。这些都弄完了你才能开始处理流程中的细节。
除了信令系统的流程外,WebRTC连接本身也需要很多回调函数来处理数据和流程,程序写出来后你会发现所有核心逻辑都写在回调函数里,你甚至很难知道自己的代码应该从哪读起。网上为数不多的样例也都是动辄几百行的大段代码,没办法,WebRTC就是这样。
最后,因为数据载体是多媒体流,所以你没办法通过简单的debug和打印来判断其中的内容是否符合预期,这给调试带来了很大困难,在开发的前期你只能摸黑前进,单元测试是不可能的,你只能都写完以后一起调试。chrome的调试工具帮助很大,在地址栏上输入chrome://webrtc-internals/就可以进入,刚看到他是我觉得它又丑又复杂,但不得不说在程序遇到问题是只能它能帮你。
参考资料
网上的资料良莠不齐,连官方网站上都没什么好教程,下面是我找到的几个高质量文章,对我帮助很大,感谢作者。