SRT协议是基于UDT的传输协议,保留了UDT的核心思想和机制,抗丢包能力强,适用于复杂的网络。在LiveVideoStack线上分享中,新浪音视频架构师 施维对SRT协议的原理、优缺点特性以及在流媒体中的应用进行了详细解析。
文 / 施维整理 / LiveVideoStack视频回放 https://www2.tutormeetplus.com/v2/render/playback?mode=playback&token=a1564111ef934005b4b1acf2105128e3
毋庸置疑,现今存量最大的直播协议是RTMP,但随着新技术的不断发展与使用场景的不断拓展,继续使用RTMP会令人感到有些力不从心。RTMP协议的缺陷主要有以下四个方面:RTMP协议缺陷
首先,RTMP协议太老,且最后一次更新是在2012年;同时HEVC/H.265/AV1等视频格式都没有官方定义,以至于需要国内CDN厂商自行定义。RTMP连接过程较长,由于RTMP基于TCP(TCP存在三次握手),除此之外,其本身又存在c0/s0到c2/s2的三次握手,再加上connection,createstream,play/publish,总地来说RTMP完成一次建连需要进行9次会话,用于PC端勉强能够接受,对于移动端网络质量的要求则很高。RTMP的拥塞控制完全依赖传输层,即完全依赖于TCP传输层的拥塞控制算法来进行拥塞管理,几乎没有什么优化;RTMP本身基于TCP传输,无法提供带宽自适应的算法。在此背景下,众多厂商开始着手提供一些新的直播协议供行业参考。如QUIC、SRT等。本次我们将重点讲述SRT的特点与应用。
Haivision联手Wowza在UDT的基础上针对音视频实时性提出了SRT协议。SRT是基于UDT的协议(UDT协议是基于UDP的传输协议,在IETF已经提交了4个版本),具有非常良好的丢包重传机制,丢包重传的控制消息非常丰富,同时支持ACK、ACKACK、NACK。我们都知道音视频对于时间这一点非常在意,而SRT基于时间的报文发送,使其具有良好的防止流量突发的能力。SRT对上层提供了丰富的拥塞控制统计信息,包括RTT、丢包率、inflight、send/receive bitrate等。利用这些丰富的信息,我们可以实现带宽预测,并根据带宽的变化在编码层去做自适应动态编码与拥塞控制。
上图可以涵盖SRT的基本思想:对比编码后的视音频码流(左侧绿色折线“Source”)与经过公网传输后的码流(红色折线“NetworkTransmission”),可以看到编码后的源视音频码流具有固定的帧间隔与一定特性的可变比特率,但经过公网传输后的码流,其帧间隔变得不固定且码率特性也被完全改变,解码这样的信号是一项十分艰巨的挑战,甚至根本无法解码。而如果使用加入纠错的SRT协议进行公共互联网传输,尽管编码后的视音频码流经过公网传输帧间隔变得不固定,但由于SRT协议封装中包含精准的时间戳,解码接收端可以通过该时间戳重现固定的帧间隔。更重要的是,通过规定延时量,同时划定了send buffer(发送端缓冲区)与receive buffer(接收端缓冲区),来自接收端的反馈信号(可以理解为后向纠错)通过一系列的设置、纠错、流量控制,使编码后的视音频码流拥有与原码流几乎一样的码率特性。Encoder编码器处理完成的数据会被输入send buffer,send buffer会对数据进行时间校验,也就是按照一定的时间顺序编码并通过internet发送至receivebuffer,receive buffer按照报文上的时间戳一点点处理,确保生成的数据与源码流基本一致。整个SRT协议的思想基于编码,具有抗丢包、抗拥塞、抗抖动等特性。
SRT的报文基础也就是其交互过程依次为以上五个步骤:握手(Handshake)、重要信息(Capability)、媒体(Media)、控制信息(Control)与关闭(Shutdown)。与RTMP的九次握手不同,SRT的建连过程仅需两个RTT。也就是Handshake Request与Capability Announce。Caller与Listener之间的进行的Handshake Request是一个简化版的握手,用以提高效率;握手之后Caller与Listener间进行重要信息的交换,整个SRT的第一项关键步骤就是Caller与Listener在这里交换了延时量与缓冲区的大小;随后Caller与Listener之间开始媒体数据的传输,同时也传输了为恢复帧间隔而封装的精准时间戳;SRT的第二项关键步骤是Listener向Caller同步控制信息,用以对抗公网中的抖动、丢包等一系列突发状况;最后,待至媒体数据传输完毕之后关闭传输。
SRT的报文格式较为简单,分为数据报文与控制报文。上图展示了数据报文的数据结构,观察结构我们可以发现上方两层为UDP部分,而在这之下是UDT部分。如果初始化为0,则认为其是数据报文。FF表示报文的序列, 0b10是分片的第一个报文,0b00是分片的中间报文,0b01是分片的最后一个报文,0b11表示单个报文并没有分片。KK表示是否加密,R表示是否属于重传报文,Timestamp表示时间戳,Destination Socket ID是SRT自定义的一个socket id。由该数据结构我们可以看出:SRT的数据报文拥有精准的32位时间戳,package sequence number绝对够用,且结构非常简单。下图展示的则是控制报文的数据结构,右侧表格展示了控制报文的常规类型,其中值得重点关注的有ACK、NACK、ACKACK等。
SRT在接收和发送端都有receive buffer与send buffer,send Buffer严格按照时间戳间隔来发送,定时器默认10毫秒。
丢包重传最常见的便是ACK机制。以上图为例,假设发送端缓冲区发送五个数据包:1、2、3、4、5给接收端缓冲区,接收端缓冲区成功接收到这些数据包之后会向发送端缓冲区发送ACK——肯定应答已表示数据包被成功接收,发送端接收到ACK——肯定应答之后回收空间,删除1、2、3、4、5这五个数据包并准备发送数据包6。send buffer完全按照时间戳间隔处理数据,在确定的间隔(与ACKs,ACKACKs 和 Round Trip Time相关),接收方发送ACK给发送方,使得发送方把收到ack的packet从sender buffer中移除,其在buffer中的空间点将被回收。接收端的receivebuffer中存在Latency Window也就是延迟窗口,其作用为按照时间戳一点点上送数据。并严格按照时间戳检测,检测周期默认为10毫秒。
接收端在收到数据包后会向发送端反馈表示成功接收的ACK,例如上图左侧,接收端在收到第十一个数据包后向发送端反馈ACK(11),而发送端在收到来自接收端的ACK(11)之后会向对端再发送一个ACKACK用以表示收到ACK(11)。这一过程最大的意义在于可让接收端计算出RTT,ACK发送的时间与对应ACKACK收到的时间之差就是RTT——Round Trip Time(RTT)是时间的度量,表示报文一个来回的耗时。SRT不能测量单方向的耗时,所以只能用RTT/2来表示单方向耗时。一个ACK(从接收方)会触发ACKACK(从发送方)的发送,几乎不带其他延时。RTT可评估当前网络质量,RTT高表示整个网络的延迟很大,RTT低则说明网络延迟较低。RTT由接收端计算而出,计算得出的RTT会通过ACK发送至发送端,发送端就可获知当前网络的质量如何。带宽情况是不断变化的,因此RTT也是一个随网络环境不断变化的实时动态值。
ACK报文包含了丰富的关键信息。其中Last Acknowledge Packet Sequence Number表示当前收到第几个包,RTT信息便于发送端评估网络的质量,RTT的变化表示网络变化情况。Available Buffer Size表示接收端缓存可用率,SRT是可以用作文件传输的,Available Buffer Size对文件传输的拥塞控制非常有用。当文件传输过快时接收端的内存缓存无法成功接收,此时发送端除了考虑网络还需要考虑接收端的性能与缓存是否有足够的空间与能力在短时间内接收庞大的文件。Packets Receiving Rate表示每秒钟的收包率,这里统计的是包的个数;而ReceivingRate表示接收包的比特率。总而言之,接收端以10毫秒为周期向发送端发送ACK,其中包括网络RTT信息、接收端缓存信息、接收端比特率等关键数据,其中以上三个数据至关重要,直接反映出发送端到接收端之间的网络环境状态。
常规情况下QUIC或TCP仅提供ACK方式,也就是接收端向发送端反馈哪些包成功接收;而WebRTC的RTP或RTCP常用选择NACK,也就是接收端向发送端反馈哪些包没有成功接收。NACK回传的是接收端没收到的数据包的列表。SRT同时支持ACK与NACK,这样设定的原因在我看来是带宽抢占的强势。例如,接收端向发送端发送ACK表示该数据包已经成功接收,不排除该ACK报文本身丢失,导致发送端以为数据报文丢失而要超时重发,实际接收端对该数据包已经成功接收了。还有一种情况是:NACK的周期性发送可能会造成发送端多发报文,例如发送端成功发送5号与6号数据包,却一直未收到来自接收端的ACK消息,当达到重发的时间时发送端再次发送5号与6号数据包,在此之后发送端收到了来自接收端的NACK消息,表示接收端未成功接收5号数据包与6号数据包;于是发送端第三次发送5号数据包与6号数据包。一个周期下来,发送端发了两次数据包。从协议原理角度分析,我们发现 SRT在丢包过程中对带宽的消耗比QUIC与其他协议要高。 一旦出现丢包,SRT的重传要多于其他协议,SRT以此来抢占带宽从而达到音视频的同步效果。
按时间发送是一个标准的音视频传输特性。我们知道编码器发送是以编码器的速度向外发送数据,但有时编码器会太乐观,完全按照编码输出向外发送会导致输出超过预期过高的比特率。在这种情况下SRT Packet就不会足够快地输出,因为SRT会最终被过低的错误配置影响到。
SRT中包含三个配置选项:INPUTBW表示编码器输入带宽,MAXBW表示最大带宽,以及OVERHEAD(%)表示过载率,配置原则如上图所示:如果不配置INPUTBW与OVERHEAD(%)而仅配置MAXBW,则当前编码器输出的比特率是也就是MAXBW;如果不配置MAXBW与INPUTBW而仅配置OVERHEAD(%),则当前编码器输出的比特率是(1080+)/100,默认为25%;第三种情况是不配置MAXBW而配置OVERHEAD(%)与INPUTBW,最大输出比特率便为(100+)/100。SRT最大的特点便是可配置。在有配置的情况下按照配置来做,在无配置的情况下按实际测量的比特率来做。常规情况下我们不选择进行配置,尤其是针对互联网而言,因为单一配置无法保证能够在不同时段应对不同境况的网络。通常我们选择使用实际测量出的编码比特率计算:*(1080+)/100。
接下来我们讨论一下SRT的流控。我们知道在丢包重传机制下,如果报文在网络传输中丢失,则发送端会重新发送。例如在某网络环境下发送端与接收端之间的带宽仅有1M,现在由于背景流量与噪声等影响,原来1M的带宽减少100k变成了900k,此时就会出现10%的丢包;发送端未收到来自接收端的ACK或者收到NACK认为出现丢包,于是尝试重传这10%的数据;但实际上发送端与接收端之间的带宽就剩下900k了,而加上丢包重传的发送量是1.1M,带宽变窄发送的流量不降反增,就会导致网络情况越来越糟糕,最后造成网络拥塞卡顿频繁,最后网络崩溃。SRT单纯地丢包重传不能解决网络拥塞的问题,出现网络拥塞的最好解决方案是限流,降低带宽流量压力。
SRT的拥塞控制过于简单,仅在congctrl.cpp中实现。其包括以下几个变量:M_iFlowWindowSize表示接收方的缓存大小,m_dCWndSize等于1秒内发送的bytes数据 / (RTT + 10),sendbufer表示发送方未发送buffer大小。其中这里的1秒内发送的bytes数据,具体是指一个RTT内发送的数据。总结算法如下:m_iFlowWindowSize用以衡量接收方缓存是否耗尽,同时监测发送方每RTT发送数据size是否大于发送buffer大小。接收方缓存一般在传输文件时起作用。在我看来,SRT在拥塞控制方面做得还远远不够。
SRT在快速连接方面有明显优势,两次握手成功即可建连;SRT的丢包重传策略出色,ACK、ACKACK、NACK等提供了丰富的控制消息,还有RTT、Receive比特率等。但是同时我们经过测试也发现SRT在丢包时,发送数据的带宽占用率还是有些大,丢包率越高发送带宽占用越大。SRT按照时间发送音视频数据,根据实际的编码比特率来发送音视频数据;但SRT的拥塞控制过于简单,只针对接收方缓存是否有能力接收与编码比特率是否发送过快。
我们推荐从编码器到推流至边缘节点的部分使用SRT。其目的主要有两个:提高源流质量与基于SRT自适应比特率编码,发送端和接收端配合从而解决最后一公里的推流问题。是自己对srt的理解。SRS支持 SRT的接收端推流,边缘SRS在接收到来自推流端的SRT推流之后会将其转为RTMP并分发给中央节点,而后所有的边缘节点都可以进行RTMP拉流。
作为一个传输协议,SRT的一个弊端在于给出一个未定义的地址,我们不清楚这究竟是推流地址还是拉流地址,那么如何进行匹配?
为方便SRT编码器推流,SRS4.0支持编码器的简单配置。如服务器IP、服务器Port以及streamid。根据SRT的官方文档,SRT通过StreamID进行标识,简配地址格式如上图所示,带有vhost虚拟主机配置的地址格式如下图所示,其中m=publish/request表示推/拉流地址。显而易见的是,对比RTMP,SRT地址的可读性并不好,地址长而冗杂。
测试各种网络情况下的SRT我们不难发现,丢包率增加导致带宽消耗增加,网络状况不良或发生拥塞时,发送端会发送更多的数据,这便会导致网络状况愈发恶化,丢包率变得更高,并以此恶性循环;除此之外,RTT增加也会导致延时增加,一样会导致丢包率增加,带宽消耗更大。这里我们提出的解决方案是预测网络带宽——通过当前的send bitrate、RTT、inflight等数据预测网络带宽;同时动态调整编码比特率,根据预测的带宽动态调整编码比特率1来适应实时的带宽,避免发生拥塞并提高视频流畅度。
上图展示的就是谷歌拥塞控制算法GCC的架构图。如图所示:发送端将报文发给接收端,接收端由几个部分组成,其中计算接收报文Delay,也就是计算基于D(i,j) = (Rj-Sj) - (Ri-Si) 的变化的导数,得到m(ti)。除此之外,接收端设置的卡曼滤波算法会计算出一个门限值,通过该卡曼滤波的计算与比对,决定是增加码率还是降低码率,最终得出下一次网络带宽的预估值(Ar),并将数据返回给发送方。
基于SRT自适应码率编码的关键参数如上图左侧所示:rtt_min表示1s内的RTT最小值,send_bitrate_max表示1s内最大的发送bitrate,常规情况我们大概200~300毫秒统计一次;inflight表示已经发送出去但还未接收到ACK的报文的个数,这种情况可能意味着报文还在传输链路的途中;BDP(bandwidth-delay product) BDP = send_bitrate_max*rtt_min表示一个RTT内发送的最大字节数。如果BDP大于(1.2 x inflight)表示网络状况良好,可以增加编码码率;如果BDP小于(0.8 x inflight)则意味着网络状况不佳需要减少编码码率,其他情况则维持现有编码码率不变。这样便能较为有效地避免网络拥塞的情况发生。
我们以测试实例来验证效果:通过Internet推流,来自于美国的SRT编码器的数据推至杭州的节点,建立SRS4.0。该测试开源地址如图中所示,使用FFmpeg配置编码码率为1000kbps,传输过程中根据实际出口带宽动态调整编码码率。由上图右侧图线1我们可以看到自适应码率最高可达到1400kbps,有良好的自适应效果;实际测试感受来看,视频播放平滑无明显卡顿,即便偶尔出现带宽抖动也能够迅速恢复。
接下来我们对比SRT与QUIC,总结二者特点。
SRT1.4的优缺点可以简单概括为以下内容:SRT的优点在于基于音视频按照时间戳进行收发,可有效保证音视频,同时ACK/ACKACK/NACK多种丢包纠正机制可有效降低延时与丢包率。SRT对上层提供丰富的传输层数据信息如RTT、lost packet rate、receive rate等。当然,SRT的缺点也不容忽视,如SRT的拥塞控制过于简单,需要在传输层合入BBR算法,原生SRT不支持连接迁移等。基于以上特点,我认为SRT更加适合编码器到最近节点的传输,也就是通过SRT探测出的RTT等相关信息实现自适应码率编码;除此之外,SRT也适合网络节点固定、网络情况固定的环境,合理配置lantency、send/recv buffer、ohead rate等SRT参数可达到事半功倍的效果。
QUIC的优点是连接快,同时具有可插拔的拥塞控制,包括CUBIC、BBR;另外在丢包重传方面,QUIC拥有更多的ACK block,最大可达256个,并且对于RTT的计算更加准确(QUIC中存在独立的Packet number,包括重传包在内的Packet number也都不相同,非常有利于RTT的精确计算);最后还有QUIC支持连接迁移。当然,QUIC同样存在缺点:第一,报文头大在发送报文中所占比例较高。第二,丢包重传只有ACK原生;第三,QUIC不支持丢包,一旦出现丢包,若没有在timeout前恢复就会断开连接。基于以上优缺点,我认为QUIC更适合运用在网络丢包率较高的环境,因为QUIC具有0RTT快速连接能力、丢包重传中ACK回复的block较大并且在拥塞控制方面表现非常优秀。除此之外QUIC也适合用于长距离传输当中,因为网络传输RTT较高,QUIC在连接断开后重连是0RTT,传输数据更加高效。
Mediago具有支持QUIC协议来传输RTMP直播流的特性,如RTMP over TCP推拉流、RTMP over QUIC推拉流以及对FLV的支持。服务器之间则支持基于TCP服务器间RTMP回源与基于QUIC服务器间RTMP回源,上图中有测试链接,感兴趣的朋友可以自己尝试一下。