webrtc的前世今生、编译方法、行业应用、最佳实践等技术与产业类的文章在网上卷帙浩繁,重复的内容我不再赘述。对我来讲,webrtc的概念可以有三个角度去解释:
(1).一个W3C和IETF制定的标准,约定了Web间实时音视频通信机制,通过该标准可开发基于浏览器的、无插件的web多媒体应用(一般是js),该标准仅设定了点对点无中心的实时会话场景,没有强制约束信令协议与内容,没有要求有媒体处理的中心服务器,主要目标是形成开发者与浏览器厂商良好的生态环境,并积极向HTML5靠拢争取被纳入进去;
(2).一群音视频算法和网络适应性算法,这些算法囊括了视频会议几乎所有的核心技术,包括音视频的采集、编解码、网络传输、播放显示、安全等功能,还提供了操作系统系统调用跨平台封装的实现,包含Windows,Linux,Mac,Android,iOS;
(3).一个开源工程,核心由c++实现,可通过修改、封装、提取代码等方式实现一套视频会议系统,客户端可实现为Web js、App或Windows应用程序等多种形式,服务端可实现包括业务外的所有服务,包括媒体服务、信令服务、穿墙服务、中继服务等等,这些服务稍微调整后可轻易支持分布式部署、容器部署、云部署等。
对webrtc的理解与使用,我认为有三个境界:
(1).能搭建一个简易的视频会议系统,其中客户端部分可以这样做: Windows端Mac端Linux(x86)端在自带的peerconnection client或libjingle改一下(取决于信令是http还是sip家族信令);Android/iOS端在apprtc或licodeAndroidClient及Licode-ErizoClientIOS改一下;web端用webrtc 自带js api实现一下。服务端部分可以这样做:信令服务器在apprtc的collider改一下;穿墙服务器用自带的stun server,turn server部署一下;中继服务器在自带的relay server改一下;媒体服务器在kurentos、licode、jitsi、Intel Collaboration Suite for WebRTC或janus改一下;如果需要和传统的SIP体系互通则在webrtc2sip改一下;如果需要关注实时通信过程中的延时、丢包、接通率、掉线率等质量问题,可以部署callstats来进行专业监测;把所有服务都再部署于云平台的多个虚拟主机上或阿里云的容器云服务,完成以上操作就搭建起初级规模的云上视频会议系统原型了。
(2).能提取、理解并使用webrtc的算法模块,即或将算法模块融入到自己的代码中,或将自己的算法添加至webrtc里作为开源贡献或自己产品的差异性优势。值得提取的算法模块包括音视频编解码与处理框架(vp8、vp9、voice engine、video engine),音视频采集呈现封装、音视频预处理(NetEq、NS、AEC、Video Jitter Buffer)、网络适应性(GCC算法、ARQ、FEC)、安全性(Dtls、Srtp/Srtcp)等,自己可添加的有视频编码模块(x265、nvenc、intel qsv、xavs2、以及其他定制化的网络传输策略)。
(3).能够结合基于rtmp-cdn/p2p等直播技术,构建既支持交互互动,又支持海量围观,可商用、能运营、易扩展、全兼容的音视频PaaS服务平台,完成一个多媒体通信的终极产品。
Licode是达到第一境界技术层面所需要了解的开源工程,它从webrtc代码中提取出了SFU/MCU媒体服务所用到的音视频、媒体传输、信令的功能代码,结合libnice与libav实现了ICE与媒体转发功能,并封装成了可被调用的js API,此外还用js实现了包含全局管理服务、数据库服务、业务逻辑服务与信令媒体的调用服务的业务代码。Licode实现了webrtc开源工程没直接实现的中心侧的媒体服务功能,并提供了简单的业务模型与分布式部署方式,从而得到了多媒体通信工程师的广泛关注。但是Licode的相关文档与资料非常少,故本文是在我个人观察运行调试代码后总结所写的,不正确之处希望在留言中能够得以指正。
Licode的系统架构如下,有客户端和服务端,客户端包括ErizoClient和NuveClient,分别用来进行信令与音视频交互和业务操作,类似于终端和会控集成在一起;服务端包括Nuve、ErizoController、ErizoAgent和MessageBus,分别用来实现业务服务与全局管理服务、信令服务、媒体服务和服务通信,其中ErizoController类似于MC、ErizoAgent类似于MP。
图1 系统架构图
客户端与服务端的交互流程如下图所示,其中客户端代码集中在N.API.js与Client.js上,服务端代码集中在nuve.js与erizoController.js上。
图2 信令交互图
本系统组成仅指服务端的系统组成,包括Nuve业务管理服务,ErizoController信令服务,ErizoAgent媒体服务,ErizoJS媒体转发实体。个人觉得代码在文件命名与文件组织结构上比较乱,初学者不易理解,如erizo_controller文件夹包括了除nuve以及webrtc的js api之外的所有js代码,不仅仅是erizoController,此外webrtc核心部分命名为erizo,也让人与整个系统相混淆。系统组成如下图所示,一个Nuve管理多个ErizoController(以下简称EC),一个EC管理多个ErizoAgent(以下简称EA),一个EA管理多个ErizoJS(以下简称EJ),一个EJ就是一个SFU,封装了C++实现的webrtc、libav与libnice。
图3 服务端系统组成图
图4 Nuve业务管理服务
图5 Nuve各模块用途及对应文件
此外,这篇文章Licode(二):Nuve源码分析 - CSDN博客 对Nuve做了更详细的分析。
图6 ErizoController信令服务
图7 ErizoController各模块用途及对应文件
图8 ErizoAgent媒体服务
图9 ErizoAgent各模块用途及对应文件
图10 ErizoJS媒体转发实体
图11 ErizoJS各模块用途及对应文件
由于webrtc工程代码本身非常庞大,编译工具也很独特,Licode并未全部引入,仅仅抽取了webrtc的部分代码,如下图所示
图12 webrtc各组件用途及对应目录
由此可见,所抽取的代码没有webrtc自带的音视频处理框架(voice engine/video engine/media engine),这是由于SFC只在RTP层进行转发并不解码,只需要check一下媒体流属性(如I帧头)即可。抽取代码的亮点在于将webrtc强大的抵抗网络时延和丢包的网络适应性算法和协议都提炼出来了,可以供广大研究视频传输的网络适应方向的开发者们单独学习、实验并快速集成到自己非webrtc-based的产品中来。
webrtc的网络适应性手段包括丢包重传(ARQ)、前向纠错(FEC)和拥塞控制(GCC),其中自动码率(ARC)在GCC中。对网络适应性有兴趣的朋友可对照代码看如下几篇博文:
(1). 丢包重传 (ARQ)
RFC4585 RTP/AVPF 传输层反馈;
RFC4588 4585补充 重传包格式;
RFC5104 4585补充 负载层反馈;
RFC5124 RTP/SAVPF
RFC5450 4585补充 Jitter报告
(2). 前向纠错 (FEC)
RFC2198 WebRTC中的前向纠错编码 - Red Packet
RFC5109 WebRTC 的前向纠错编码 - XOR FEC
(3). 带宽检测 (BWE)
传统的基于RTCP RR报文中丢包数来进行带宽检测的算法目前被认为是最low的了,因为属于事后处理;先进的算法是需要事前感知的(突发情况除外),假定带宽是逐渐变窄的,根据信号估计理论可以在网络链路发生丢包以前就监测到网络拥塞,
GCC草案
REMB草案
GCC算法与函数调用 WebRTC基于GCC的拥塞控制(上) - 算法分析 WebRTC基于GCC的拥塞控制(下) - 实现分析
带宽估计 WebRTC之带宽控制部分学习(1) ------基本demo的介绍
带宽检测 WebRTC 中的带宽侦测 - CSDN博客
接收缓冲区延迟估计 WebRTC视频接收缓冲区基于KalmanFilter的延迟模型
libav是从ffmpeg分离出的开发者发布的开源工程,与早期ffmpeg有很大相似性,对于仅调用api的开发者来说就简单看成是代码量更小、第三方依赖库更少、编译更简单的轻量级ffmpeg吧。这个库在Licode编译过程中有处问题,Licode使用版本较旧的libav的接口,其中avcodec_alloc_frame和avcodec_free_frame都已经遗弃,改为av_frame_alloc/av_frame_free才可编译通过。
libnice库基于ICE协议实现的网络层库,Licode使用libnice库来实现端到端的ICE连接和数据流发送接收,以及candidates(候选地址)和SDP(媒体描述文件)的相互交换。
libsrtp库主要是是用来加密rtp/rtcp的。
网络收发或一个SFU实现,并非简单的从一个源地址收到若干个数据包再复制多份发送给多个目的地址,正如3.6所描述,除了需要堆栈式的ICE地址转换、DTLS/SRTP的解密,还需要流水线式的RTP/RTCP的丢包检测与重传、FEC处理、带宽检测与估计、包缓冲区调整以及平滑发送等若干个复杂步骤,这个流程在Licode代码中是通过Pipeline-Handler这样的架构实现的,由于webrtc仅提供底层算法并未提供SFU架构,所以我认为这种架构是Licode最重要的关键技术,可供未来想成为软件架构师的开发者参考学习。日后我也再详细分析这个架构,看看是否能抽象出一个新的设计模式来。
整个网络收发的层级模型图如下图所示,其中每一层右侧标注出了关键类。
图13 网络收发层级模型
Pipeline的类图如下图
图14 Pipeline相关类图
对Pipeline来说,需要全局管理、长时处理或非逐个包处理的流程,实现对象叫Service,包括Handler的管理、RTCP的计算与处理、当前状态的处理、网络质量的管理以及包缓冲的管理都是Service;而需要每次逐包处理的流程,实现对象叫Handler,包括19项Handler,其代码都位于erizo\src\erizo\rtp目录下
//新增全局处理的Service
pipeline_->addService(handler_manager_);
pipeline_->addService(rtcp_processor_);
pipeline_->addService(stats_);
pipeline_->addService(quality_manager_);
pipeline_->addService(packet_buffer_);
//新增单次处理的Handler
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared());
pipeline_->addFront(std::make_shared(this));
Licode的系统设计如3.1所述,Nuve管理多个EC,EC管理多个EA,EA启动多个EJ进程;在资源上,Nuve负责EC的负载均摊,EC负责EA和EJ的负载均摊(毕竟EJ在EA内,EA除了监控OS性能并未太多其他工作),故Nuve需要EC分布式保活,EC需要EA分布式保活。Licode实现比较简单,代码可参考CloudHandler.js与ecCloudHandler.js。
例如Nuve每启动一个EC立刻分配id,并加入到ECList中,并在DB中写入id作为key,且在该key对应的value上进行累加,此后Nuve的秒级循环中不断check这个value并继续累加,如果超过阈值,则认为该EC失效,将此EC从list中删除。Nuve同时提供keepalive接口,EC通过RPC调用此接口不断对数据库中本id的value清零,从而实现了分布式保活。EC与EA间方法一样。
这里定义的资源有三个含义:设备资源(设备简单分为发布设备、接收设备、收发设备)、内容资源(某地的实时视频或已录制视频)以及server处理能力余量(例如还能转发的路数,还能容纳的新成员)。
Licode的publish-subscribe模型将信令交互、内容管理(源站管理)、媒体分发三者分开,从而可实现多维度的资源管理。
Client 1 发的第一个信令仅起到了一个资源注册的作用,即room中存在Client 1的id了;
Client 1 发的第二个信令表明该id可以发布新内容了,内容为Stream 1;
Client 2 同理,room记录了Client 2的id与Stream 2;
Client 1发的第三个信令表明对Client 2的Stream 2内容感兴趣开始订阅了,于是启动了SFU过程;
Client 3加入room后,可以发布资源(Stream3),可以订阅资源(Stream1、Stream2),也可以什么都不做,因为任何在该room中注册的设备都可以pubilsh内容资源或subscirbe已publish的内容资源。完全解耦带来的好处是内容资源同设备资源一样也可以轻易管理了,在启动SFU过程中,server处理能力余量也能得到较好的管理。
H.323是紧耦合的,信令交互后必须开启媒体交互(电路交换的思维,信道容量有限,申请资源赶紧用,用完赶紧释放),对内容管理过于依赖于会议逻辑的实现,为了实现更灵活的互听互看,其逻辑会堆砌的很复杂。
然而好的方法也不是万能的,publish-subscribe的完全解耦虽带来极大的可扩展性,但对于某些传统业务更复杂了。比如MCU两个终端互看,就需要启动两个erizoJS,互相订阅发布,根据负载均摊的算法,可能两个erizoJS都不在一个erizoAgent里(不在同一台物理机或虚拟机上)。MCU轮询模式也稍复杂,轮询者需要不断的subscribe room中的发布者,发布者也要不断的去remove、add,切换速度比接收所有流+I Frame Check的传统方式要慢的多。
本文先介绍了自己对webrtc的概念理解与使用参考,接下来从系统架构、交互流程、系统组成与模块划分几个角度对Licode进行概要设计级别的分析,最后对自己觉得Licode比较有特色的三大技术,即网络收发流水线架构技术、分布式保活技术与资源管理技术进行了浅层次的解释。文中表述内容可能会有不准确之处,希望能与读者们交流并及时改正。