引言:站在风口上,猪都能飞起来。雷布斯的这句名言,已经被大家传的家喻户晓了,说起当下站在风口上的猪,除了丁老板的未央猪,这头实实在在的猪,视频直播应该可以算一个。今年各种直播平台,各个轮次的融资消息应接不暇。对于互联网技术从业者来说,RTC(Real Time Communication,实时通信)这个站着视频直播背后的技术也重新开始变得火热起来。
视频直播算是互联网应用的新领域,但实时通信技术却算不上是比较新的技术,传统的视频电话、会议系统等都是实时通信技术的应用,但互联网视频直播肯定有其新的特点,也就会有新的技术演进。
WebRTC
现在大多视频直播平台主要还是采用的RTMP(Real Time Messaging Protocol,实时消息传输协议)技术来实现,RTMP基于可靠的TCP传输,协议简单,开发成本低,跟flash等流媒体服务支持较好,还有一个重要的原因就是CDN支持良好,目前视频直播服务还是严重依赖CDN服务来实现的。当然其缺点也是因为其基于可靠的TCP协议,音视频传输的一个显著特点是数据量大,并且对实时性要求比较高,而传统的TCP协议是一个面向连接的协议,它的重传机制和拥塞控制机制都是不适用于实时传输的。基于TCP的音视频传输,传输控制严重依赖TCP协议本身的控制机制,网络成本较大又不够灵活,在弱网环境丢包率高的情况下导致直播卡顿明显,另外其本身的延时性不佳,对于实时性要求较高的应用场景就力不从心,比如基于其来实现现在热门的互动直播,就会很困难。
由于音视频技术本身的特性,其对网络丢包有一定程度的天然容忍性,使用UDP进行传输才是一个理想的选择。使用UDP协议开发者可以在传输层进行灵活的网络拥塞控制、流量反馈与控制,通过一些定制化的调优可以达到比较好的弱网优化效果,来保证更好的音视频传输流畅、低延时体验;不过反过来,这个优势的实现也就是开发成本了。这时该WebRTC出场了,WebRTC(Web Real-Time Communication,网页即时通信),是一个实时音视频通信的开发框架,其本身足够强大,不仅有基于UDP的数据传输,还包括ICE、STUN、TURN等实现信令交互、网络打洞等,其天然对浏览器的支持也是一个重要优点,背后是强大的Google在支撑,基于WebRTC,只需要简单的几个接口就可以实现一个拥有实时音视频通信功能的demo。
WebRTC框架
WebRTC的问题
话说到这里,仿佛Google已经再次拯救了世界,我们这些音视频狗好像可以过上活少钱多离家近的生活了一样。但是,但是功能不等于品质,由于UDP不是一个可靠的传输协议,在复杂的公网网络环境下,各种突发流量、偶尔的传输错误、网络抖动、超时等等都会引起丢包异常,都会在一定程度上影响音视频通信的质量,网上一篇介绍WebRTC的科普文中有一句话说到,“demo和实用之间还差着一万个WebRTC”,额,古人诚不我欺。
应对方法
不过又但是了,办法总是比困难多的,不然要我们干啥,怎么显得我们牛逼。 这里说说为解决上述所说的网络传输问题,在网络传输层可以做的一些事情,这些办法和事情包括NACK、jitterbuffer、带宽自适应、前向纠错编码(FEC)等,这些本身在WebRTC的实现中基本都包含,Google也算给指明了方向,只是我们在实际使用中,还是要弥补demo到实用之间的距离。
RTP/RTCP协议
在介绍这些措施之前,我们先看一下WebRTC中所使用的数据传输协议。前面说到WebRTC使用UDP协议进行音视频数据的传输,实际上使用的是RTP/RTCP协议。RTP协议是Internet上针对流媒体传输的基础协议,详细说明在互联网上传输音视频的标准数据包格式,它是一个应用型的传输层协议,位于UDP上,本身只保证实时数据的传输,不提供任何传输可靠性的保证和流量的拥塞控制机制,RTCP协议则负责流媒体的传输质量保证,提供流量控制和拥塞控制等机制。在RTP会话期间,各参与者周期性彼此发送RTCP报文,报文中包含各参与者数据发送和接收等统计信息,参与者可以根据报文中信息动态控制流媒体传输。在WebRTC项目中,RTP/RTCP作为传输模块的一部分,负责对发送端采集到的媒体数据进行进行封包,然后交给上层网络模块发送;在接收端RTP/RTCP模块收到上层模块的数据包后,进行解包操作,最后把负载发送到解码模块。因此可以说RTP/RTCP是WebRTC的重要基础。RFC3550/3551定义RTP/RTCP协议的基本内容,包括报文格式、传输规则等。除此之外,IETF还定义一系列扩展协议,包括RTP协议基于档次的扩展和RTCP协议基于报文类型的扩展,比如RFC4584等。
RTP/RTCP议栈示意图
NACK与重传
NACK(negative-acknowledge character),就是否定应答,与之对应的是TCP中的ACK(acknowledge character)。我们知道在TCP中,接收端对于收到的包都要进行应答即发送ACK包,发送端通过接受ACK包,来确定发送的包已经被成功接收,以此来保证网络包的传输可靠。RTP协议不来保证传输的可靠性,所以接收端也就不会发送ACK包,但是对于‘丢失’的包,没有收到的包,如果觉得这个包比较重要,可以给对端发送NACK包,来告诉对端,这个包我没有收到,你如果‘还有’的话,就给我重新发送一遍(Retransmission)。我们可以理解为,对于ACK机制来说,发送端没有收到ACK,我就重新发送,是一种push的方式,对于NACK来说,接收端没有收到,主动请求,是一种pull的模式。如果对于每个‘丢失’的包,都发起NACK的话,那也就和ACK没有大的不同了,但是正如前面描述中各种模棱两可的话所说,我们可以根据需要、根据策略,选择性的发起丢包重传,甚至可以选择完全不进行重传,而是通过其他的容错机制来进行保障,因为发起重传,也就意味着接收端要等待这个包,等待就会增加延时。比如在WebRTC的实现中,对于音频的传输,在收发两端进行协商的时候就约定了,音频包就不进行重传了,丢了就丢了吧。说到这里插一句话,我们这里说到的网络反馈和控制的机制,基本都是要收发两端进行协商的,比如这里说的重传,如果要支持的话,对于发送端来说,就需要一个发送端缓存,发送出去的包,需要暂留一段时间,不然即便收到了NACK包,也没办法重传了。对于接收端来说,我们前面说到的丢包,都加了引号,其实是说,这个“丢包”,有可能是真的丢了,也有可能是顺序乱了。我们知道网络包的到达,有时候不是一定严格按照包序到达的,我收到了很多较新的包了,某个旧的包还没有收到,我就认为它丢了,也没准儿一会儿它又到了。那么对于接收端,我要等多久,才认为它‘丢包’了呢?可以是根据乱序的偏移来决定发起重传,也可以根据预计到达时间已经超过一定时间了来发起重传。发起重传后,我得等这个重传的包吧,那我等多久呢?一般是等待一个rtt+jitter的时常,如果这个时间内还没有到达,可以选择丢弃不要了,也可以选择再次发起重传,可是也不能一直等下去吧,这个就要看下面要说到的jitterbuffer。
jitterbuffer
在网络传输中总是存在着抖动,由于网络拥塞、路由变化等等原因,导致网络包不是按时间均匀到达的,还有上面说的包不是按照包序有序到达,这时候就需要一个缓冲区,来缓存网络包,进行等待和排序,将这个网路‘抖动’给过滤掉,从而可以稳定有序的将包再发给后面的处理逻辑。假如是个固定长度的buffer,那么当缓冲区满了的时候,如果一个包还没有收到,那么就不会再等待这个包了。试想,在一定的网络抖动条件下,这个buffer设置的过小,就会导致发起的重传比较多,如果设置的过大,必然又会增加jitterbuffer中等待的延时,从而导致整个音视频通信延时的增加。所以理想的做法是,根据网络的状况,按照一定的策略,动态的来调整jitterbuffer的大小。比如在网络比较好的时候,设置小一点,这时对于‘丢包’尽快的发起重传,我们也是可以期望在较快时间内就得到的,从而降低延时;网络不好的时候,可以适当设置大一点,因为这时本来网络条件就较差,更多的重传只能是更加恶化网络环境。当然,仅仅是动态的jitterbuffer也是无法完全解决这个问题的,根本上还是应该根据网络状况来调整发送的码率,网络环境差的情况下,主动降低码率,通过减少网络负荷,来保证传输的流畅性。这就是下面要说的带宽自适应。
jitterbuffer示意图
带宽自适应
带宽自适应是指在音视频的收发过程中,根据网络带宽的变化,自动的来调整发送码率,来适应带宽的变化。在带宽足够的情况下,增加帧率和码率,提高音视频的质量,带来更好的通信体验。在带宽不足的情况下,主动降低码率或者帧率,保证通信的流畅性和可用性,也是带来更好的通信体验。带宽自适应的核心,就是如何准确的估计带宽。WebRTC在实现带宽自适应时采用了Google提出一个称为REMB(Receiver Estimated Max Bitrate,最大接收带宽估计)的带宽估计算法。这个算法的大概思路是在接收端根据丢包率或者延时情况维护一个状态机(参见下图)。以根据丢包率为例,在判断为overuse时,就根据一定的系数减少当前remb值,当判断为underuse时又根据增加系数来增加remb值;然后将这个值通过rtcp包发送给发送端,发送端根据该值来动态的调整码率。在发送端,WebRTC的实现中在调整码率时还会参考rtcp中的丢包率,当然这些我们都可以根据自己的策略进行修改。关于接收端(服务端)REMB带宽估计的实现,则是需要自己来实现,可以实现得更复杂和完善,来实现更佳准确的带宽估计,更佳敏捷的带宽自适应。
接收端带宽估计
REMB状态机
前向纠错编码
最后我们再来说一下FEC(Forward Error Correction,前向纠错),也叫前向纠错码,是通过增加冗余来增强容错性的一种方法。没有FEC的情况下,当接收端发现有包丢失时,需要通过发送NACK来发起重传,前文中我们已经说过,重传是会影响延时性的,而FEC则是在发送通过纠删码来增加一些冗余数据,这样接收端在数据丢失的情况下可以根据冗余数据来重建丢失的数据。通过增加FEC来避免和减少NACK/重传,从而减少丢包导致的延时。其缺点也是显然的,增加的FEC冗余数据占据了有效带宽,这又是一种取舍了,不过FEC的冗余度也是可以根据网络状况来动态的调整的。我们这里说的FEC是整个RTP传输层的,在使用WebRTC的过程中,还会发现,在WebRTC使用的音频opus编码中还有一个称为inband的FEC,就是在opus的编码过程中增加一定的FEC冗余,我想这也就是为什么我们前面说到的WebRTC中音频在协商的时候就不进行NACK重传了。
FEC示意图
小结
上面的介绍基本就是WebRTC中网络反馈与控制的方法和策略了,这些策略和方法相辅相成共同作用,才能使得WebRTC音视频通信拥有良好的体验和品质。