

大家好,我是Twitch的视频工程师,今晚我的演讲主题是SRT协议的内幕。在过去,我看过许多关于支持SRT功能的软解的精彩演讲以及它的各种潜能。但是今天,我将掀开幕布,看看SRT协议背后的东西。

此SRT(Secure Reliable Transport)非彼SRT(SubRip Subtitle:它是一种字幕格式),这个视频传输协议可以在具有挑战性的网络之下进行直播。它基于UDP的单播(一对一的形式),注重contribution而不是delivery。此外,它也带来亚秒可调的constant latency。
这么说吧,可调(tunable)意味着你可以配置协议并调整延迟,可以在数据包的丢失与延迟中做出权衡(trade-off)。一旦开始广播的时候,延迟即被锁定,所以不会因为不同的网络的情况而累积更多的延迟,同时,该系统也提供content encryption。
这么说吧,可调(tunable)意味着你可以配置协议并调整延迟,可以在数据包的丢失与延迟中做出权衡(trade-off)。一旦开始广播的时候,延迟即被锁定,所以不会因为不同的网络的情况而累积更多的延迟,同时,该系统也提供content encryption。

为什么我觉得SRT有趣?我们知道RTMP是公共互联网上直播视频的事实标准;但RTMP已经存在了很长一段时间,其标准在2012年最后一次更新过后就被放弃了。新的Codec标准诸如HEVC或AV1一般都没有RTMP标准支持。退一步来说,即使有人在RTMP中hack了这些Codec的支持,在移动网络上RTMP仍然工作的不大好。
SRT作为RTMP潜在替换技术的一种,最近正获得不错的增长势头。SRT联盟现在有250多名成员,而在最近的一些展会上,似乎每个展位都具有 SRT 联盟成员或 SRT-Ready贴纸。

SRT功能在VLC,Gstreamer和Ffmpeg中基本开箱即用,对于 OBS Studio 等工具则有些patches正在流程中。SRT 的源于一个称为 UDT 的旧协议。UDT在2001年创建,仍然在Source Forge上有网页,但UDT的设计目标是在公共网络上以最短时间传输大型的文件。

UDT开发者向IETF提交过几份草案去描述UDT工作原理。总共有四份草案,最终的IETF草案是在2010年发布的。之后,UDT的主要开发者继续在此协议工作了3年,其实现的最终版本停留在了2013年。

Haivision,一家编码器供应商,采用UDT且将它由file protocol变成一个live video协议。在2013年,他们首次在 IBC大会上使用了UDT,主要是为了演示HEVC的编码器。

过了四年,他们觉得自己的自定义协议可能不是创建interoperable ecosystem的最好方式。因此在2017年,他们开源了SRT。

Haivision 与Wowza 联合创建了SRT联盟,以促进发展及开发SRT。

2018年初,他们发布了SRT的v1.3.0更新版本。这是自最初的开源以来,对protocol最大型的修改。同时,其版权协议改成了MPL(Mozilla public license);重新把文件传输模式加了回来。

在2018年,他们也发布了SRT Technical Overview(SRT技术概述),但实际上更像规范(specification)。该文档共有89页,与此对应的RTMP Spec则是52页。该文档描述了各种细节信息,即使是一些竞争对手也承认SRT规范做得非常不错。

从高层看,SRT使用的一个双向UDP socket,它可以通过同一个socket复用数据和控制流。因为没有使用TCP,SRT自行实现了可靠性、有序性和拥塞控制。SRT使用 selective retransmit的方式处理数据包丢失,另外了基于标准加密原语(standard primitives)(而不是DTLS)构建了其独特的 “unique”加密系统,

SRT Data Payload支持可分片的有效负载(payload)。在实践中,我仅仅看过它与MPEG transport stream一起使用。整个传输流引入SRT包,每个传输流包都有自己的同步字节和传输流头。我确信这些sync byte 用以对抗丢包以及重新同步。
在1500-byte Ethernet MTU情况下,如果你试图放入188-byte的数据包,会发现并没有足够的空间可以容纳8个TS包,这也是使用7个TS包的原因。与此同时,这也可以给SRT Header留出足够的空间。

上图概述了SRT数据包的布局。初起是UDP header, 还有UDT header,实际上SRT header改自UDT header。

第一bit是0,表示的是数据包,之后是packet sequence number,它是从握手过程中确定的random value开始的,随后每一个packet值都会增加1。该值用于标识数据包、packet acknowledgement和数据包丢失消息。有个message number, message number从0算起,会在我的视频中每增加一条消息时候加一,但看起来没什么用,怎么回事呢?
我们可以看见一个微秒单位的时间戳,这是发送端的运行时间(elapsed time)。在接收端,它将这个packet从SRT的缓冲区中播放到下游的TST MUX RN 视频解码器中。这个实时视频的片段与顺序总会是“1 1 0”。1 1 指的的是独立编号,而消息编号是针对跨多个数据包分段的信息。但在实时视频模式下,我们只需要尽可能多地填充TS,我们称之为standalone message。
Ordering flag下的两个标志是encryption和retransmission flags;如果出现了什么问题,ordering flag可以将其信息无序交付。Encryption flag会提醒你正使用的key,而retransmit flag会告诉你这是否是第一次发送还是一个重复的步骤。Retransmitted packets 通常会保留原始序列号,原始信息号和原始时间戳。

SRT的核心理念是发送方和接收方都同意延迟缓冲时间,并且他们试图在数据包开始流出接收方时同步其内容。目前VLC支持现成的SRT,OBS也有了SRT的patch,发送方所创建的数据包,同时会将其放在延迟缓冲区,因为在网络中,该包到达接收方需要一段时间。

发送方不断生成数据包,接收方最终获得数据包。发送方再不断地生成,接收方也继续接收。如此往复。

这里展示了一个不妙的情况,上图的packet 3已被丢失,到目前为止,没人发现了数据的差错。本来期待packet 3,竟然收取了packet 4,它随即生成negative acknowledgement packet,将packet 4 放入缓冲区,为packet 3 留下一个空位(hole)。
发送方继续分发packet,直到下一个packet送达。突然间sender得到NACK,它发现接受者丢失了packet 3,因为它把之前发送的数据包都保存在了buffer,结果,发送方重新发送数据包3,它继续生成数据包;接收方继续接收数据包。注意,此时发送者的buffer现在已装满了数据包。

Packet 1被吐出,直接被丢掉。接收端终于收取到packet 3,它马上填补之前的空洞(hole),操作恢复正常。接收端buffer最终填满了指定的packet,随后把packet 1给了 TS deuxers 和解码器。

这是协议概述中Nak的表示图。首先有SRT header,因为它是一个控制包所以1开头,最后是丢失包的列表。
每一个丢失list包含一个或多个条目。每个条目要么是一个single packet,要么是一个范围(range),你可在一条消息内说丢失了packet 5、9以及11直到15的所有包。

除了negative acknowledgment,SRT也有positive acknowledgment。接收器每10毫秒发送一次acknowledgment。每一次收到 ACK,发送者立即确认ACK以响应之前的ACK,你可以据此估算往返时间。如果确认之间的数据速率超过64个数据包,则接收器将发送lightweight acknowledgement。此Ack不会被重新确认,也不包含Ack所接受的元数据类型。

RTT有点不寻常,因为似乎没有办法在不启动新广播的情况下调整延迟缓冲区的大小,所以对于广播场景有些限制。

以上是acknowledgement packet所显示的Ack/AckAck包。最重要的是最后接收到的packet包序列号,当然还有的是往返时间,往返时间的方差,缓冲统计available size和packets,数据包接受速率及数据接收速率。在lightweight Ack,你只需得到序列号,Ack的acknowledgment将会显示它正在确认的Ack,以便于你知道在正确的时间戳做出扣除。

我们都知道握手(handshake)非常简单。在这个GIF中,Bill Maher 不断地误读Snoop Dogg (史努比。狗狗)的手势,Snoop Dogg也不断地尝试配合Bill,这场景是body language misinterpretation的一个例子,我认为它适合隐喻SRT handshake的过程。

SRT在sender和receiver之间有四次handshakes(因为后向兼容,所以所有版本都支持)。V4 和V5的rendezvous handshake (汇合握手)比较特殊,不在这次讲解。
V5 以及v4最大的区别在于数据包交换的数量。v4共有四次往返;在v5只有两次往返。

图中是packets的布局,其核心思想是左边的v4使用了未修改的UDT包加上SRT扩展,接着是一个包含所需延迟和初始序列号的SRT握手包,其后的密钥素材用于对于数据有效载荷进行加密,右边的v5则更将这些信息smush (合拼) 到一个包中(指V5直接修改了原始UDT包的布局)。

之后,我们将看到两方尝试v5握手,发起方创建handshake induction packet (握手感应包)。

其版本号设置为4,但cookie字段并未设置,它将提示初始端在短时间内获得cookie,使得响应端不必处理混乱的数据包,而是需要解析其数据包以将某些内容发送回去;实际上,响应端接收到该包之后,创建一个版本5的induction packet,并设置Cookie。

此时,v4 initiator将忽略v5,并继续填充v4以及重复改cookie。但是,如果initiator是通过v5运行,所以它会在version字段中填写 v5程序,加上SRT handshake extension values包括延迟值等。
Initiator可能在握手的第二个包产生stream ID,首先填充加密握手,然后responder会使用latency value 和cyypto handshake进行响应。

在于Stream ID,这是在handshake的第二个包中所发送的可选标识符,因为第一个包是有可能被抛弃掉的。在此会有个application-specific parameter,用于通知你initiator想干什么。
这与RTMP形成一些对比,在RTMP中,你执行TCP握手和RTMP握手。然后,执行带宽估计之后调用一组RPCs来设置RTMP媒体流。

我之前提到过SRT不使用DTLS。它以自定义方式使用industry standard primitives,可看见它受到到了DVB scrambling的重大影响。DVB是欧洲广播标准,keying material是由共享密码短语生成。随着这些retransmits和密钥旋转,一次有两个密钥有效。你有一个偶数键和一个奇数键,在这两个键中你交替使用哪一个,你就在更新。如果你得到重发,你可以参考旧的密钥。

规范中的小注释说:‘嘿,全数据包加密看起来是最安全的选择,但实际上,加密header在暴力破解时候却有点脆弱。最初的MPEG TS 同步字节,其设计可能是不让你把TS头加密。事实上,我们会尝试使用快速的key rotation来获得更高的加密强度。

你可以使用Wireshark 来分析包,我们会有个加密数据包,有效载荷的第一个字节是12(十六进制)。你可能已知道如果是一个未加密的TS 同步字节,那它将是47(十六进制)。

如果想进一步了解其中的协议,你可以前往SRT GitHub repository,以及technical overview Wireshark的SRT解析器。

如果你想了解更多关于SRT生态系统,或者有关于SRT的产品或信息请前往srtalliance.org。