流媒体传输协议之 RTP

引言

本系列文章将整理各路流媒体传输协议,包括RTP/RTCP,RTMP,希望通过深入理解各个流媒体传输协议的设计细节,对今后流媒体部分的开发工作有一定的启发。

介绍

RTP,即real-time transport protocol(实时传输协议),为实时传输交互的音频和视频提供了端到端传输服务。其中包括载荷的类型确认,序列编码,时间戳和传输监控功能。一般应用都是基于UDP协议,来使用RTP的多路技术以及验和服务。然而,RTP还可以与其它适合的协议并用,如果底层网络支持多路分发,RTP还可以将数据传输给多个目标。
需要注意的是RTP不提供任何机制以保证数据的实时性和QOS(quality-of-service),而是依赖底层的服务来提供这些功能,RTP既不保证传输的可靠性也不保证无序传输,同时也不假定底层网络是可信任的和有序的。接收端可以利用RTP中的序列号排序收到的报文。
RTP与RTCP

  • 实时传输协议(RTP),传输具有实时特性的数据
  • RTP控制协议(RTCP),监控QOS和传递会话中参与者的信息。它没有明确的成员控制功能和Session建立过程,但这些对一个相对宽松的Session控制来说已经足够了,它没有必要包含一个应用的所有控制功能。

RTP代表了一种新型协议,它遵循Application level framing 和 Integrated layer processing。即RTP可以比较容易的拓展以传递某些特定需要的内容,而且可以比较容易地集成进某个应用,而不是作为一个独立的补充层。RTP协议被故意地设计成不完整的协议框架。

RTP的使用场景

下面的例子描述了RTP的部分特性,选择的例子是用来阐明基于RTP的应用的基本操作,而不是说RTP仅能用于此类应用。

简单的多播音频会议

一个小组要通过网络开一个音频会议,他们用了IP多播服务。基于某种分配机制,小组得到了一个多播组地址和一对端口,其中一个端口是用来传输音频数据的,另一个是用来传输RTCP报文的。这个组播地址和端口发给了所有与会者。如果想要引入一些安全策略,可以对数据报文和控制报文加密,然后把加密时用到的密钥分发给与会者。
这个音频会议软件,可能会一直发送时长为20ms的音频数据包。每个实际音频数据包,都以RTP头数据开始,然后再以UDP协议封装并发送。RTP包的头部标识了该包的数据类型,以便消息发送器来改变数据的编码。例如,针对低带宽的与会者进行一些调节,或者对网络拥堵作出反应。
像UDP这类包类型的网络,偶尔会丢包,乱序,延迟不定长时间。为了解决这类意外情况,RTP包中包含了时间信息和序列号,这样接受者就可以通过它们重排数据包的时序。在这个例子中,我们就可以按顺序地播放每个20ms的音频数据。在会议中对每个数据源的RTP报文时序重排都是独立进行的。接受者也可以通过序列号来确定丢失了多少报文。
因为这个小组开会期间,会有一些人加入或退出这个网络会议,所以我们需要知道具体是谁加入了会议,以及他们有没有正常地接收到音频数据。出于这个目的,每个网络会议的客户端都会周期性的通过RTCP端口报告使用者的名字以及自己接受数据的情况,如果有人接受数据不正常,可能就需要对应的改变编码。而且,除了用户的名字之外,还会有一些别的信息,用来控制带宽限制。当有人从视频会议中退出时,还需要发送一个RTCP BYE报文。

音频和视频会议

如果这个会议既要传输音频又要传输视频的话,它们会以独立的RTP Session传输。也就是说,负责音频传输的部分和负责视频传输的部分会通过不同的组播地址(和端口对)分别传输各自的RTP报文和RTCP报文。在RTP协议这一层,音频和视频Session并没有被组合到一起。我们期望与会者用同一个名字来建立音频和视频Session,这样这两个Session就能联系起来了。
RTP协议之所以这样设计,一个原因是某些与会者可以选择只接受某一种类型的数据(只接受Audio)。即便Audio数据和Video数据是独立分发的,但是我们仍然可以通过参考RTCP协议中时间信息来同步播放它们。

Mixers & Translators

到目前为止,我们都是假设所有的与会者想要接收同意格式的媒体数据。但是这显然不太合适,考虑一下,可能某些与会者可能网速相对比较慢,而其他人网速却比较快。对于这种情况,我们不应该强迫所有人都用低带宽并降低音频编码的质量,而是使用RTP级别的中继节点(Mixer)来给周围低带宽用户分发低带宽消耗的数据。这个Mixer将接收到的不同与会者的音频数据同步,并将它们耦合到一个单一流中,然后将这个流用低带宽消耗的编码方案进行压缩,最后发送给那些低带宽的与会者。Mixer可以在RTP头部写一些特殊内容,来表明该Mixer包具体耦合了哪些与会者,这样,接收到该Mixer包的人就能确定当前说话的人是谁了。
此外,有些与会者可能处于在应用级防火墙后,无法仅通过IP组播访问。这种情况下Mixer就没有什么意义了,他们需要另一类RTP级别的中继(Translator)。我们需要两个Translator,安装在防火墙的两面,外面的Translator将收到的所有组播报文,通过一个安全连接传输给防火墙里面的Translator。然后,防火墙里的Translator再将这些报文分发给内网的与会者。

层编码

多媒体应用可以根据接受者的能力或者网络拥堵的情况调整传输速率。许多实现将码率控制的责任放在了发送者。这和组播模式不太兼容,因为各个不同的数据接受者会有不同的带宽情况,这就会产生木桶效应,即带宽最差的接受者会拖垮整个会议的通讯质量。
因此,带宽自适应的工作应该放到接受者这里,发送者需要拆分出面向不同带宽与会者的媒体流(500K,2M,5M),它们分别对应了不同的组播地址,数据的接收者根据自己的带宽情况,选择加入适合的组播。

定义

  • RTP payload:RTP包中传输的数据,比如音频采样数据或者压缩过的视频数据。
  • RTP packet:由定长RTP头部,数据来源者的列表,RTP payload组成的数据包。一些下层协议可能会自己定义RTP的封装格式。一般来说,一个下层协议包只包含一个RTP包,但是也有可能多个RTP包被合并到一起。
  • RTCP packet:RTP控制报文,由定长的RTC头部开始,之后会跟着一些结构化的元素,它们在RTCP发挥不同功能时,会有不同的结构。通常多个RTCP包会被合在一起,通过一个下层协议包一起发送。
  • Port:传输层协议中用来区分某一主机下不同应用的抽象。RTP协议依赖更底层网络提供端口机制,继而提供多播的RTP和RTCP报文。
  • Transport address:网络地址和端口的组合,用来定位传输层的节点。
  • RTC media type:一个RTP Session中所用到的所有payload类型的合集。
  • Multimedia session: 视频会议组中同时工作的一组RTP session。例如,视频会议中的Audio session和Video session。
  • RTP session:一组参与者利用RTP来通讯的组合。一个参与者可以同时加入到多个RTP session中。在Multimedia session中,除非特意将多媒体编码进同一数据流,否则,每个数据流会通过不同的RTP Session传输。与会者通过Transport address来区分不同的RTP session。同一RTP session的不同与会者会共享同一个Transport address,也可能每个与会者都有自己的Transport address。在单播的情况时,一个与会者可能用同一对端口(RTP&RTCP)来接受所有其他与会者的数据,也可能对不同的与会者采用不同的端口对(RTP&RTCP)。
  • Synchronization source (SSRC):RTP报文流的一个Source,由RTP头中定义的32-bit的SSRC identifier来标识,这样做是为了不依赖网络地址。同一个SSRC中发送的所有包都具有同一时序和序列号间隔,因此接收者可以通过SSRC将收到的数据包分组并排序。一个信号源(麦克风,摄像头,Mixer)的报文流会有由一个SSRC的发送器发送。一个SSRC可能会随着时间的变化,改变其数据格式,例如音频编码。SSRC的身份识别码都是随机生成的,但是必须保证整个RTP session中该身份识别码不会重复,这些工作是通过RTCP来完成的。如果一个与会者在一个RTP session中发送不同的媒体数据流,那么每个流的SSRC必须不同。
  • Contributing source (CSRC):RTP Mixer所混合的所有数据对应的SSRC的列表。Mixer会将一个SSRC列表写入RTP头中,该列表包含了这个混合报文中包含的所有来源SSRC。
  • End system:一个生成RTP payload和消费收到的RTP payload的应用。一个End system可以扮演一个或者多个SSRC角色,但是通常是一个。
  • Mixer:一个中介系统,它接受一个或多个Source的数据,随后它可能会改变这些数据的格式,并将它们合并为一个新的RTP packet。因为,多个输入源的时序通常来说都不一致,所以Mixer通常会同步不同源的时间,并生成一个自己的时序来处理合并数据流。所有从Mixer输出的数据包都会标记上该Mixer的SSRC。
  • Translator:一个中介系统,它会转发RTP packet但是不改变其原本的SSRC。
  • Monitor:一个在RTP session中接收RTCP报文的应用,它会总结数据被接受的报告,并为当前分发系统评估QOS,诊断错误,长期统计。Monitor可以集成进会议应用中,也可以是独立的第三方应用,只接受RTCP报文,但是什么都不发送。
  • Non-RTP means:为了让RTP提供可用服务而加入的协议或者机制。特别是在多媒体会议中,需要一种控制协议来分发组播地址和加密密钥,协调加密算法,定义RTP payload格式和RTP payload类型的动态映射。

字节序,数据对齐,时间格式

所有的整数字段都使用网络字节序(大端序),除了特别声明,数字常量由十进制表示。
所有头部数据都会根据其数据的原始长度进行对齐,比如,16-bit的数据会对齐到偶数偏移,32-bit的数据会对齐到可被4整除的偏移。此外,用0来作为填充字节。
Wallclock time(绝对日期和时间)是用网络时间协议(NTP)的时间格式来表示,即从1900年一月一日0点到现在的秒数。NTP的时间戳使用了64-bit的无符号固定小数点的形式表示,其中头32-bit用来表示整数部分,后32-bit用来表示小数部分。RTP的时间格式采用了NTP的简化版,他只用了NTP的64-bit数据的中间32-bit,即前16-bit表示整数,后16-bit表示小数。
NTP时间戳到2036年就会循环回0,但是因为RTP只会使用不同NTP时间的差值,所以这不会有什么影响。只要一对时间戳都在同一个循环周期里,直接用模块化的架构相减或者比较就可以,NTP的循环问题就不重要了。

RTP数据传输协议

RTP的定长头字段

RTP头的格式如下:
rtp-header
上图中前96-bit的数据是每个RTP包都有的部分,CSRC部分只有Mixer发送的报文才会有。这些字段的意义如下:

  • Version(V):2 bits
    RTP版本号,现在用的是2。(第一个RTP草案用的1)
  • Padding(P):1 bit
    如果设置了该字段,报文的末尾会包含一个或多个填充字节,这些填充字节不是payload的内容。最后一个填充字节标识了总共需要忽略多少个填充字节(包括自己)。Padding可能会被一些加密算法使用,因为有些加密算法需要定长的数据块。Padding也可能被一些更下层的协议使用,用来一次发送多个RTP包。
  • Extension(X):1 bit
    如果设置了该字段,那么头数据后跟着一个拓展数据。
  • CSRC count(CC):4 bits
    CSRC列表的长度。
  • Marker(M):1 bit
    Marker会在预设中进行定义(预设和RTP的关系可以参考rfc3551,我的理解是预设是对RTP的补充,以达到某一类实际使用场景的需要),在报文流中用它来划分每一帧的边界。预设中可能会定义附加的marker,或者移除Marker来拓展payload type字段的长度。
  • Payload type(PT): 7 bits
    该字段定义RTP payload的格式和他在预设中的意义。上层应用可能会定义一个(静态的类型码<->payload格式)映射关系。也可以用RTP协议外的方式来动态地定义payload类型。在一个RTP session中payload类型可能会改变,但是不应该用payload类型来区分不同的媒体流,正如之前所说,不同的媒体流应该通过不同session分别传输。
  • Sequence number:16 bits
    每发送一个RTP包该序列号+1,RTP包的接受者可以通过它来确定丢包情况并且利用它来重排包的顺序。这个字段的初始值应该是随机的,这会让known-plaintext更加困难。
  • Timestamp:32 bits
    时间戳反映了RTP数据包生成第一块数据时的时刻。这个时间戳必须恒定地线性增长,因为它会被用来同步数据包和计算网络抖动,此外这个时钟解决方案必须有足够的精度,像是一个视频帧只有一个时钟嘀嗒这样是肯定不够的。如果RTP包是周期性的生成的话,通常会使用采样时钟而不是系统时钟,例如音频传输中每个RTP报文包含20ms的音频数据,那么相邻的下一个RTP报文的时间戳就是增加20ms而不是获取系统时间。
    和序列号一样时间戳的初始值也应该是随机的,而且如果多个RTP包是一次性生成的,那它们就会有相同的时间戳。
    不同媒体流的时间戳可能以不同的步幅增长,它们通常都是独立的,具有随机的偏移。这些时间戳虽然足以重建单一媒体流的时序,但是直接比较多个媒体流的时间戳是没办法进行同步的。每一时间戳都会和参考时钟(wallclock)组成时间对<timestamp,wallclockTime>,而且需要同步的不同流会共用同一个参考时钟,通过对比不同流的时间对<timestamp,wallclockTime>,就能计算出不同流的时间戳偏移量。这个时间对<timestamp,wallclockTime>并不是和每个RTP包一同发送,而是通过RTCP协议,以一个相对较低的频率进行共享。
  • SSRC:32 bits
    该字段用来确定数据的发送源。这个身份标识应该随机生成,并且要保证同一个RTP session中没有重复的SSRC。虽然SSRC冲突的概率很小,但是每个RTP客户端都应该时刻警惕,如果发现冲突就要去解决。
  • CSRC list:0 ~ 15 items, 32 bits each
    CSRC list表示对该payload数据做出贡献的所有SSRC。这个字段包含的SSRC数量由CC字段定义。如果有超过15个SSRC,只有15个可以被记录。

RTP Session多路复用

在RTP中,多路复用由目标传输地址(address:port)提供,不同的RTP session有不同的传输地址。
独立的音频和视频流不应该包含在同一个RTP session中,也不应该通过payload类型和SSRC来区分不同的流。如果用同一个SSRC发送了不同的数据流,会引入如下问题:

  1. 假设2个音频流共享了一个RTP session,并且用了同一个SSRC,如果其中一个要改变编码,这就导致了payload类型的改变,但是协议中没有提供方法来让接受者知道具体是哪个音频流改变了编码。
  2. 一个SSRC只有一个对应的时序和序列号,如果多个流有不同的时钟周期的话,就需要不同的时序。而且还不能用序列号来确认是哪个流丢包了。
  3. RTCP 发送者报告和接受者报告只描述了时序和序列号而不包含payload类型数据。
  4. Mixer无法将不兼容的两个流合并
  5. 如果一个RTP session中包含了多个媒体流后就会失去如下优势:
    1. 使用不同的网络路径或者分配网络资源
    2. 只接受某一种媒体数据(网络较差时只接受audio)
    3. 接收方对不同的媒体类型做不同的处理

不同的流使用不同的SSRC但是仍然用同一个RTP session发送确实可以解决前三个问题,但是仍然无法解决后两个问题。

预设可能对RTP头的改动

现有的这些RTP报文头对一般应用来说已经足够了。如果有需要,头字段可以根据预设进行一些修改,但仍要保证检测和统计功能的正常使用。

RTP头拓展

RTP提供了一个拓展机制,让上层应用可以将自定义的信息存储在RTP报文头。如果上层应用收到了无法识别的头部拓展数据,它们会忽略它。
值得一提的是,这个头部拓展是有一些限制的。如果附加信息只对某些payload格式才有意义,那么最好还是别把这些信息放到头部拓展中,而是放到payload部分。
rtp-header-extension
如果RTP Header中的X位设置为1,那么Header后必须跟着一个不定长度的拓展块,紧跟着CSRC list(如果有的话)。拓展部分的头部包含一个16-bit的数据来描述拓展块包含多少个32-bit字(不包括拓展部分的头部)。因为RTP头部后面只能连接一个拓展块,考虑到有些应用可能会有多种类型的拓展块,所以拓展块的头16-bit留给开发者去自定义一些参数。

RTP控制协议

同一个Session所有参与者会周期性地发送控制报文,RTP控制协议就是通过这种方式进行的,和RTP数据的传播一样采用了组播的机制。下层协议必须提供数据包和控制报文的多路复用功能,例如使用独立的UDP端口分别传输数据和控制报文。RTCP协议具有如下四大功能:

  1. 最主要的功能是反馈数据分发的质量。这也是RTP作为一个传输协议来说最关键的功能,而且它和流量控制,拥塞控制息息相关。反馈信息可能会直接影响自适应编码的控制。发送反馈报告给所有的参与者可以让它们评估遇到的数据分发问题是个人问题还是全局问题。通过IP组播这样的分发机制,像网络提供商这样的机构即便不加入到这个RTP session中也能收到反馈信息,它们会扮演一个第三方监测者的角色去确认数据分发问题。这个反馈的功能无论是RTCP的发送者还是接受者都会进行报告。
  2. RTCP还会给每个RTP source带一个不变的传输层身份识别符(CNAME),因为SSRC可能会中途改变(程序重启),所以接受者需要这个CNAME来持续追踪每个与会者。而且,接受者可以通过CNAME来将同一个与会者的所有数据流联系在一起,比如同步音频和视频。单个媒体内部的数据同步也需要NTP和RTP时间戳,这些数据都在数据发送者发送的RTCP报文中。
  3. 因为前两个功能需要所有的与会者都发送RTCP报文,所以需要适当的控制报文发送的频率以至于RTP协议可以在大量客户端一同加入时也能正常工作。通过每个参与者都广播控制报文的方式,每个人都能独立地计算出参与者的总数。
  4. 还有一个可有可无的功能,RTCP可以用来共享小量的session控制信息,例如辨认参与者的身份。它通常来说,会被与那些管理比较松散的Session使用。RTCP可以作为一个方便的与其他参与者沟通的通道,但是你也别期望RTCP可以满足一个应用的所有传输控制需求,这类需求往往是通过一个更高层的session控制协议来满足。

这四个功能中,前三个应该会被所有应用场景使用(IP组播机制下)。RTP应用的设计者应该避免自己的应用只能工作在单播模式,RTP应用应该设计成可拓展的,要考虑大量使用者并发时的情况。此外,RTCP的传输应该根据发送者和接受者角色的不同而分别进行控制,例如一些单项连接,接受者的反馈信息就发不出来。
提醒:像是指定源组播路由(SSM),只有一个人可以发送数据,其他接受者不能用组播来和其他人直接通讯。对于这种情况,建议完全关闭接受者的原始RTCP功能,然后为这个SSM设定一个RTCP的适配器,来接受所有的反馈。

RTCP包格式

RTCP定义了许多包类型来传输不同的控制信息:

  • SR:发送者报告,发送者数据发送和接受的统计。
  • RR:接收者报告,只接受数据的节点的接受统计。
  • SDES:Source描述,包括CNAME
  • BYE:表示退出
  • APP:上层应用自定义

每个RTCP包都有一个和RTP类似的固定格式的头,后面跟着长度不定的结构化数据,在不同RTCP类型时,这些结构化数据各不一样,但是它们必须都要32-bit对齐。RTCP的头部是定长的,而且在头部有一个字段来描述这个RTCP数据的长度,因此RTCP可以被复合成一组一同发送,还不需要任何分隔符来分割出单个的RTCP包。下层协议可能会根据自己的情况决定将多少个RTCP报文复合在一起组成一个复合包。
复合包中的每个独立的RTCP报文都是无序的,而且可能会被随意复合。为了让协议的功能正常运作,会有如下限制:

  • 接受统计(SR|RR)的发送频率需要达到带宽的最大限制,因此每个周期发送的RTCP复合包都需要包含一个这类报文。
  • 一个新来的接受者需要尽可能快地得到数据源的CNAME,因为它要用CNAME来确定每个数据源分别对应哪个人,并将数据源联系在一起进行同步,所以每个RTCP复合包必须包含SDES CNAME(除非这个复合包被拆成两半一半加密,一般明文,这部分后面会介绍)。
  • 复合包中包类型的数量需要限制,这可以减少其他发错的包或者不相关的包被识别成RTCP包的可能性,还能增加第一个字中固定比特的数量。

因此,一个复合包中至少需要含有2种类型的RTCP报文,它的格式如下:

  • Encryption prefix:当且仅当这个复合包需要加密的时,那复合包在头部插入一个随机的32-bit数。如果加密算法需要填充数据的话,需要填充到复合包中的最后一个RTCP包后。
  • SS 或 RR:复合包中第一个RTCP包必须是一个报告报文,这可以加速报文头部数据的校验。即便没有RTP数据的发送和接受也要有一个报告报文,这种情况下必须发送一个空的RR报文,并且即便是这个复合包中的其他RTCP报文是BYE也要这么做。
  • Additional RRs:如果接受的RTP数据来自超过31个不同的源,前31个接受报告会写进SR或者RR报文中,多出来的接受报告应该紧跟着默认的报告报文(SR或RR)。
  • SDES:SDES包必须包含CNAME,每个复合包必须包含一个SDES包。如果上层应用有需要,也可以加入一些别的SDES报文,这视带宽限制而定。
  • BYE或APP:其他RTCP包类型(包括协议中还未定义的),可能以任意顺序跟在SDES后面,但是希望BYE包写在最后面(BYE包需要和SSRC/CSRC一同发送)。

一个单独的RTP参与者应该在一个报告周期中只发送一个复合RTCP包,该周期每个参与者应该视带宽情况来估算,除非一个复合包被拆分加密。如果数据发送者的数量太多,以至于除了增加MTU这个方法之外,没办法将所有RR报文塞进一个复合包时,那么一次只会将部分RR数据塞进这个复合包,其他的数据就不发送了。当然,为了让所有源的接受情况都得到报告,会在多个周期内以环的形式循环共享所有源的接受情况。
为了减少数据包的开销,一般建议Translator和Mixer无论何时都能将多个源的RTCP报文复合成一个复合包。下图展示的就是一个Mixer生成的复合包的例子:
rtp-compound-packet
如果一个复合包的长度超过了下层网络协议的MTU的话,这个复合包会被拆分成多个更小的复合包分别发送。这不会对RTCP的带宽估计产生任何影响,因为即便Mixer的复合包被拆分成了多个更小的复合包,但是这个些更小的复合包也要满足”每个复合包都要包含SS或RR”这一条件,所以每个更小的复合包至少也对应了一个参与者,这样Mixer生成的复合包就和它收到的RTCP包数量基本匹配,甚至更少。
如果某一客户端收到了它无法解析的RTCP类型的包,那它应该忽略这个包。附加的RTCP包类型会通过IANA进行注册。

RTCP传输周期

RTP的设计理念是它要能根据session参与者的人数增加而进行自适应处理。例如,音频会议中同一时刻说话的一般也就那么一两个人(这就从内部限制了音频数据的传输),那么可以认为组播数据分发所用到的带宽资源和与会人数无关。控制信息的发送和音频数据的传输不同,每个人都会不停的发送RTCP报文,如果每个参与者的接受报告以同一个周期发送的话,RTCP报文传输所消耗的资源会随着与会人数的增加而线性增加。因此,当与会人数增加时,RTCP报文的发送间隔应该相应的动态地增大。
对每个session来说,会有一个总的带宽限制(Session bandwidth),它会被分配给每个独立的与会者。整个网络的带宽可能会有所保留,并从网络层面强制限制Session的带宽。如果网络的带宽没有保留的话,也可能会有一些别的限制,不过这些都跟网络环境有关,总之最后会得出一个靠谱的Session最大带宽。Session带宽可能会通过实际会消耗的网络资源进行评估,或者中途根据session的剩余可用带宽来变化。这些都和媒体数据的编码无关,但是会根据带宽的限制来选择具体使用哪种编码。通常来说,会预估session中有多少参与者会同时发送数据,然后根据同时发送这类数据大概需要多少带宽这种方式来评估session的带宽。在音频会议中,通常来说就是一个音频发送者所需要的带宽(一般同一时间只会有一个人说话)。对于分层编码这种情况,每一层都在一个独立的RTP session中,这些session都有自己独立的带宽限制。
在RTP session中应该有一个管理应用来调整session带宽,但是那些音频会议应用可能会基于session中选用的编码格式,假设只有一个发送者发送数据,给自己设定一个默认的带宽限制。这个音频会议应用可能也会受到组播网络(或其他因素)的带宽限制。同一个session的所有参与者必须使用统一的session带宽限制,因为只有这样大家才是以一个相同的频率发送RTCP包。
Session带宽评估过程需要考虑到下层的传输层和网络层是否有一些资源保留机制。而且上层应用也需要知道RTP下层使用了什么协议,但是不需要知道数据链路层及以下的协议,因为从数据链路层开始数据包的头就各不相同了。
控制报文的传输应该只使用Session带宽中很小的一部分,这样媒体数据的传输才会不受影响。建议RTCP传输使用session带宽的5%,媒体数据发送者至少要占用1/4的RTCP带宽,因为这样做的话,新加进来的人可以更快的收到媒体数据发送者的CNAME。在某些预设中,如果发送者的数量超过1/4可能会完全关闭接受报告,虽然RTP协议标准并不推荐这样做,但是那些只有单向链路的系统或者不需要接受者反馈的系统一般是这么做的。
RTCP报文的传输间隔一般都会稍微长一点,这样,当参与者的数量陡增时,报文的数量就不会超过带宽限制太多。当一个应用启动时,它应该等一段时间(一般是最小RTCP报文间隔的一半)再发送第一个RTCP报文,这样这可让发送间隔的计算更快的收敛。推荐RTCP报文发送的最小间隔是5秒。
RTP的上层应用可能会使用更短的RTCP发送间隔,但是也会遵循如下原则:

  • 对于组播形式的Session,只有数据发送者会使用更短的RTCP发送间隔。
  • 对于单播形式的Session,无论是发送者还是接受者都有可能使用更短的RTCP间隔,并且它们发送初始RTCP前可能不会等待一段时间。
  • 所有的Session都应该根据最小RTCP发送间隔来确定参与者的超时时间。
  • 推荐的最小RTCP发送间隔时间使用”360 kb/ session带宽(kb/s)”这种方式计算。这样当session带宽大于72kb/s时,RTCP发送间隔会小于5秒。

此外,为了让RTCP能在大型session中正常运行,现有的算法还具有如下特点:

  • RTCP报文发送间隔随着session参与者的人数增加而线性地降低。
  • RTCP发包间隔通常会随机缩放0.5~1.5倍,这样做是为了避免大量的参与者同时发送RTCP报文。
  • RTCP复合包中包含的控制报文数据会根据收发包情况动态变化
  • 因为RTCP报文间隔是根据已知的session参与者情况计算的,所以当有新的人要加入到session时,可能会错估整个session的规模,而是用了较短的RTCP间隔,尤其是当大批量的人一齐加入session时这种现象更加明显。所以,可能会有一个”发送时机重整”算法,它实现了一个简单的撤回机制,可以在session规模持续增长时,适当的撤回一些RTCP报文。
  • 当有人通过发送BYE报文或者因为超时退出Session时, RTCP的发送间隔应该缩短。
  • BYE报文和其他RTCP报文相比,有一些特殊的地方。当有人想要退出,并发送BYE报文时,它可以在下一个发送周期到来之前就发送。当然,如果一大批人同时退出时,也会受到前面提到的RTCP报文撤回机制的影响。

维护Session成员的数量

我们已经知道了,计算RTCP发送间隔是需要清楚整个Session中成员数量的,当一个新的节点被监听到时,它就会被加入到Session总数中,并且大家要把它加入到一个SSRC(CSRC)身份识别表中然后持续追踪。大家只有收到这个新节点的多个数据包,或者收到他的SDES包(CNAME)时才觉得这个新节点是靠谱的。当某个节点发了一个BYE之后,它的信息可能就会被大家删了,但是考虑到可能有丢包或者网络拥堵的情况,所以大家会先把它标记为”收到BYE”,然后等一段时间,如果还没收到它的别的报文,这时候才会把它删了。
如果一个节点超过一个RTCP周期都没收到另一个节点的报文,它可能就会将其标记为不活跃,或者删了它,这就需要丢包的情况尽可能别发生。但是不丢包是不可能的,所以大家一般会将RTCP传输间隔乘以一个系数(大于1的数)作为超时时间。
对于那些参与者很超级多的Session,可能没法去维护一个SSRC表来存储所有参与者的信息。通常大家都会简化这个SSRC表,但是需要注意的是无论怎么简化这个表都不能低估了参与者的总数,可以允许高估参与者总数。

RTCP报文的收发规则

首先,无论是组播还是多个节点的单播都必须遵循前面提到的RTCP间隔。为了正常完成RTCP报文的收发操作,Session中的每个参与者都会维护如下信息:

  • TP:最后RTCP报文的发送时间
  • TC:当前时间
  • TN:下一个要发送报文的时间点
  • P-Members:计算上一个TN时参考的session成员总数
  • Members:当前的session成员总是
  • Senders:数据发送者总数
  • RTCP_BW:RTCP的目标带宽
  • WE_Sent: 从倒数第二个RTCP报文发送后,到现在为止,是否发送过数据
  • AVG_RTCP_Size:平均RTCP复合包大小,包括传输层和网络层的头
  • initial:是否一个RTCP报文都没发过

计算RTCP发送间隔

为了让RTP协议具有可伸缩性,RTCP的发送间隔需要随着Session总人数的变化而适当的缩放。结合上述的部分状态,我们按如下方式计算RTCP报文间隔:

  1. 如果媒体流发送者的数量小于总人数的25%时,这个间隔和当前节点是否是媒体流发送者有关(通过WE_Sent判断)。如果是媒体流发送者,计算公式为:Senders * AVG_RTCP_Size / (25% * RTCP_BW),如果是媒体流的接收者,计算公式为:(Members - Senders)* AVG_RTCP_Size / (75% * RTCP_BW)。当媒体流发送者的数量超过25%时,发送者和接受者会被同等对待,即它们的RTCP周期公式为:Members * AVG_RTCP_Size / RTCP_BW
  2. 如果某个参与者一个RTCP包都还没发送,最小发送间隔间隔(Tmin)为2.5秒,否则为5秒。
  3. 决定的发送间隔(Td)会是第一步计算的值和Tmin中较大的那个。
  4. 发包时会在Td的基础上随机缩放0.5~1.5倍
  5. 最终这个间隔还要除以e-3/2=1.21828,这是为了弥补因为”发送时机重整”算法带来的影响(因为这个算法会导致最终RTCP使用的实际带宽比预计使用的带宽低)。

初始化

当一个人刚加入到Session中时,tp=0,tc=0,senders=0,p-members=0,members=1,we_sent=false,
rtcp_bw = 5% * session带宽,initial=true,avg_rtcp_size=之后会发送的RTCP包的大小,然后计算发送间隔T时,会根据上述初始状态进行计算,并以此作为参考发送第一个包,最后将自己的SSRC加入到成员列表中。

接受RTP和Non-BYE RTCP包

当RTP或者RTCP包被另一个人(A)接收到了,如果对A来说这个包的SSRC他没见过,那么他就会将其加入到SSRC表中,并更新session总人数(Members)。对每个CSRC也会做同样的操作。
如果收到了一个RTP报文,并且其对应的SSRC没在发送者SSRC表中,那他就会把它加进发送者SSRC表中,并更新发送者的总数(Senders)。
当每个复合RTCP报文被接受到时,平均RTCP报文大小(AVG_RTCP_Size)的状态就会更新,更新公式为:AVG_RTCP_Size = (1 / 16) * last_rtcp_package_size + (15 / 16) * previous_avg_rtcp_size

接受RTCP BYE报文

如果接收到了RTCP BYE报文,会在成员列表中确认一下,如果有对应的SSRC项,就会把他移除并更新成员总数(Members)。同时也会在发送者SSRC表中做类似的操作,如果找到了就删除它并更新发送者总数(Senders)。
此外,为了让RTCP的传输率跟随Session中人数的变化而动态变化,如下算法会在收到BYE报文时执行:

  1. TN按照如下公式更新:TN = TC + (Members / P-Members) * (TN - TC)
  2. TP按照如下公式更新:TP = TC - (Members / P-Members) * (TC - TP)
  3. 下一个RTCP报文按照新的TN指示发送(比原来发的更早了)
  4. 将P-Members设置成Members的值

这个算法没有考虑到一个意外情况,那就是当一大波人(并不是所有人)同时退出Session时,会导致RTCP的周期降到一个非常小的值,这样可能出现错误的Timeout判断,最终它会导致整个Session的总人数降到0。但是,这种情况一般来说很少发生,所以大家都觉得它问题不是很大。

SSRC的超时

我们需要偶尔确认一下是不是太久没收到某个与会者的报文了,一般来说每个RTCP周期内都必须确认。如果发现了超时,就需要将这个SSRC从成员列表(Members & Senders)中移除,并更新当前人数。

  • Member表: 一般超过5个发送周期(不考虑随机缩放因素)未收到某人的消息,会被确定为超时。
  • Sender表: 一般是2个发送周期。

如果某个成员被确定为超时,上一步介绍的算法就操作起来了。

发送倒计时

我们已经知道,每个RTCP都是周期性的发送的,当发送完一个RTCP报文时,就会根据TN新建一个倒计时,每次当倒计时归零时就会重复如下操作:

  1. 计算传输周期T,引入随机缩放因素
  2. 如果TP + T <= TC,立即发送一个RTCP报文,并将TP设定为TC,TN设定为TC + T,下一个倒计时会在TN时刻归零。如果TP + T > TC,就不发送了RTCP报文,计算TN = TC + T后,然后重设一个定时器在TN归零。
  3. P-Members设定为Members

如果发送了RTCP报文,initial会被设定为FALSE,AVG_RTCP_Size会按如下方式更新:
AVG_RTCP_Size = (1 / 16) * last_rtcp_package_size + (15 / 16) * previous_avg_rtcp_size

发送BYE报文

当某个人想要退出Session时,他就会发一个BYE报文给其他人。为了防止一大帮人同时退出Session时出现BYE报文井喷的情况,所以当Session人数超过50时,会按下述方式操作:

  1. 当一个参与者想要离开时,TP会设置成TC,Members和P-Members会设置成1,initial设置成1,we_send设置成false,senders设置成0,avg_rtcp_size设置成复合BYE报文的大小。然后计算RTCP发送间隔T,下个BYE报文会在TN = TC + T后发送。
  2. 每当这个要离开的人收到了别人的BYE报文时,Members就会增加1,无论这个人是否在成员列表中。Members的数量只有收到BYE报文时才增加,其他报文都不管。同样,avg_rtcp_size也只管收到的BYE报文的大小。Senders数量也不变。
  3. 对了BYE报文来说,除了状态值的维护套路变了,发送逻辑和前面提到的都一样

通过上述方案,即可以让BYE报文正确地发送,还能控制整体带宽。最差的情况下,也只会导致RTCP报文传输占用10%的Session总带宽。
有些参与者可能不想按照上述的方式发送BYE报文,他们可能什么也不发就离开了。这类情况会被Timeout机制hold住。
如果一个参与者要离开时,session的总人数小于50,他可能会直接发送一个BYE报文,也可能按照上述方案来进行。
此外还有一个无论如何都要遵循的规则,如果一个参与者一个RTP报文或者RTCP报文都没发送过的话,那他离开Session时绝对不能发送BYE报文。

更新WE_Sent

当某个参与者最近发送过一个RTP后,他就会将WE_Sent置为true并将自己加入到Senders表中,否则如果超过两个RTCP发送周期的时间内都没发送过RTP报文,那他就会将自己从Sender表中移除,并将WE_Sent置为false。

SDES类报文的带宽分配

SDES报文中除了必需要有的CNAME之外,还有一些别的信息,比如NAME(个人名称),EMAIL(email地址)等。上层应用也可以自定义的一些报文类型,但是要小心别付加了太多的自定义信息以至于拖慢了整个RTCP协议的运转。建议这些附加内容的带宽占用不要超过整个RTCP协议带宽的20%。此外,也不要觉得每个上层应用都会包含所有的SDES内容。上层应用要根据实际使用的情况给这些内容分配一定的带宽,一般来说他们会通过控制发送间隔来控制这部分的带宽。
比如,一个应用的SDES可能只包含CNAME,NAME,和EMAIL,其中NAME可能就会比EMAIL分配更多的带宽。因为NAME会一直显示出来,而EMAIL可能只在点击查看的时候才显示。在每个RTCP发送周期里,SDES中都会包含CNAME。如果假设RTCP周期是5秒的话,可能每15秒SDES才会附带一个除CNAME以外的信息,以2分钟为例,其中7次附带的是NAME信息,1次附带的是EMAIL信息。

Sender & Receiver报告

RTP使用Sender报告(SR)和Receiver报告(RR)来反馈数据的接受质量,如果是媒体数据的发送者那就会发送SR,否则发送RR。这两类报文是通过头部的报文类型识别码来做区分的。SR相对于RR来说多了20byte的Sender相关信息,除此之外其他内容都是一样的。

SR报文

rtcp-sr
SR报文包含三个部分,第一个部分是头部,有8 BYTE,各个字段的含义如下:

  • version (V): 2 bits
    RTP协议版本
  • padding (P): 1 bit
    是否包含填充,最后一个填充字节标识了总共需要忽略多少个填充字节(包括自己)。Padding可能会被一些加密算法使用,因为有些加密算法需要定长的数据块。在复合包中,只有最后一个RTCP包需要添加填充。
  • reception report count (RC): 5 bits
    有多少个接受报告。可以为0。
  • packet type (PT): 8 bits
    200表示SR报文。
  • length: 16 bits
    报文长度(按32-bit字统计),包含头部和填充字节。
  • SSRC: 32 bits
    身份定位符。

第二部分是发送者信息,包含20 BYTE的数据,总结了这个发送的的传输统计,各个字段的含义如下:

  • NTP timestamp: 64 bits
    Wallclock time,用于计算RTT。
  • RTP timestamp: 32 bits
    RTP时间戳,基于NTP的某一随机偏移量。用于媒体数据内同步。
  • sender’s packet count: 32 bits
    这个SSRC总共发送了多少包
  • sender’s octet count: 32 bits
    这个SSRC总共发送了多少BYTE的数据。

第三部分可能什么都没有,也可能有多个接收报告,这取决的上次报告以后收到了多少个Sender的数据。每个报告块统计了一个SSRC的包数。具体内容如下:

  • SSRC_n (source identifier): 32 bits
    这个信息块对应的SSRC。
  • fraction lost: 8 bits
    上次SR或RR发送后到目前为止的丢包率。
  • cumulative number of packets lost: 24 bits
    整体过程的丢包总数。
  • extended highest sequence number received: 32 bits
    低16-bit是收到的最新的RTP报文序列号,高16-bit是序列号循环的次数。
  • interarrival jitter: 32 bits
    RTP数据报文抵达时间的抖动。如果Si代表i包中包含的RTP时间戳,Ri代表i包被接受时的RTP时间戳,那两个包i和j的到达时间抖动算法如下:
    D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)
    我们在计算这个抖动时,要结合每个包的抖动,来计算一个平均值,计算平均值的方案如下:
    J(i) = J(i-1) * (15 / 16) + (|D(i-1,i)|)/16
  • last SR timestamp (LSR): 32 bits
    该SSRC最后一个RTCP报文(SR)中带的NTP时间。
  • delay since last SR (DLSR): 32 bits
    从该SSSR最后一个RTCP报文(SR)被收到以来经过的时间。

数据的发送者可以通过当前时间A,接收到RR部分中的LSR和DLSR来计算RTT,计算示意图如下:
rtp-rtt

RR报文

rtp-rr
接受报告的格式和发送报文格式一样,只不过它在头部中用201表示这是一个RR报文。此外RR报文中不含有上述SR报文中的第二部分。如果RR报文是空的那么需要在头部标明RC=0

发送/接受报文的拓展

一些预设可能根据自己的需求,要在接受报告和发送报告中附加一些信息。那么这些附加内容应该在SR或者RR的结尾之后。如果这些内容只有发送者相关,那么RR中就不包含这些信息。

分析发送报告和接受报告

这些接受质量的报告信息可能不光只有发送者要使用,接受者或者第三方监控器也会使用。发送者可能根据接受质量调整自己的传输策略。接受者可以根据这个信息来确定自己遇到的问题是本地网络的问题还是整个Session的问题。网络的管理者可以根据这些信息来评估整个网络环境的情况。

SDES报文

rtp-sdes
SDES是一个三级结构,它包含一个头和0个或多个数据块,每一个数据块对应了一个SSRC或CSRC,它又由多个描述字段组成。头部的信息如下:

  • version (V), padding (P), length:
    和上面一样
  • packet type (PT): 8 bits
    202表示SDES类型
  • source count (SC): 5 bits
    SSRC/CSRC块的数量。

每一个块中都包含多个描述内容,这些描述内容都是32对齐的,其中前8-bit描述了类型,接着8-bit描述了信息长度(不包含前16-bit),然后信息内容。注意信息部分不能超过255 BYTE,这和前面的很多工作类似是为了约束RTCP的带宽。
描述的文本内容是UTF-8编码的。如果要使用多字节的编码,需要在醒目的地方表示用的什么的编码。
各个描述部分是没有中间分隔的,所以要用空字节来填充以达到对齐的效果。注意这里的填充和RTCP头部的P不是一个概念。
末端节点发送的SDES包含他自己的数据源标识。而Mixer发送的SDES包含多个CSRC,如果CSRC的数量超过了31个,会拆分成多个SDES报文。
SDES的所有类型会在后面一一介绍。其中只有CNAME是强制要有的。可能有一些类型的的描述只有部分预设才会使用。但是这些内容都是在一个共通的地方来记载,以防止不同的预设使用的描述类型发生冲突。如果要注册新的类型,需要通过IANA注册。

CNAME:权威的末端节点身份标识

rtp-cname
CNAME有如下特征:

  • 因为SSRC在许多意外情况下会重新生成,所以CNAME被用来绑定旧的SSRC和新的SSRC,来保持数据源的连续。
  • 和SSRC一样,CNAME也需要保证唯一性(同一个Session中)
  • 为了让同一个参与者的多个SSRC绑定在一起,我们需要CNAME是固定的
  • 为了让第三方监控用起来方便,CNAME应该即方便程序使用,也要设计成可读的,可以根据它确认来源

因此CNAME应该通过算法来生成而不是手动生成。为了满足如上需要,一般来说是按照如下的格式来描述CNAME:

  • “user@host” eg: “doe@192.0.2.89“ or “doe@2201:056D::112E:144A:1E24”
  • “host”, 如果是单用户系统,获取不到user时只使用host。eg: “sleepy.example.com”,”192.0.2.89” or “2201:056D::112E:144A:1E24”

有些人可能会发现,如果上述的host使用的是子网地址的话,就没办法保证整个Session的唯一性了,通常这类没有直接IP的使用者是通过一个RTP级别的Translator来访问公共网络。这个Translator会处理从私有地址到公网地址的转换工作。

NAME:用户名

rtp-name
这个是描述数据源的真实名字,eg:”John Doe, Bit Recycler”。整个Session过程中希望这个值不变。全Session不需要唯一。

EMAIL:电子邮箱地址

rtp-email
电子邮箱地址,eg: “John.Doe@example.com“。整个Session过程中希望这个值不变。

PHONE:电话号码

rtp-phone
电话号码需要以国际访问码开头,eg: “+1 908 555 1212”

LOC:用户地理地址

rtp-loc
视应用不同,详细程度会各不相同。

TOOL:应用名或工具名

rtp-tool
带版本号的应用名,可以用来DEBUG。

NOTE:提醒/状态

rtp-note
用来发送暂时性的消息描述当前状态。eg: “on the phone, can’t talk”

PRIV:自定义拓展

rtp-priv
上层应用自定义的格式。一般都是用过一个前缀描述消息类型,然后后面跟着消息正文。

BYE报文

rtp-bye
BYE报文表示一个或者多个SSRC已经走远了。

  • version (V), padding (P), length:
    同上
  • packet type (PT): 8 bits
    203表示BYE报文
  • source count (SC): 5 bits
    退出Session的SSRC的数量

如果BYE报文被Mixer收到了,Mixer应该啥都不改动,就发给下一节点。如果Mixer关闭了,它要发送一个包含它管理的所有SSRC的BYE报文。BYE报文中可能也会跟着带一些离开原因的描述。这些描述和SDES中带的描述类似,需要32对齐,用空字节填补空缺。

APP:应用定义的RTCP报文

rtp-app
APP报文一般用于实验性的功能和开发。如果识别到了不认识NAME那么上层应用一般都会忽略它。如果开发或者测试功能稳定了,一般是要通过IANA注册一个新的RTCP报文类型。

  • version (V), padding (P), length:
    同上
  • subtype: 5 bits
    APP报文子类型,一般是上层应用定义。
  • packet type (PT): 8 bits
    204表示APP类型的RTCP报文。
  • name: 4 octets
    一般是应用名,防止subtype冲突。
  • application-dependent data: variable length
    和上层应用相关的内容,需要32-bit对齐。

RTP Translator & Mixer

作为末端节点的补充,RTP引入了Translator和Mixer的概念,它们是RTP层的中间件。虽然这多少增加了协议的复杂度,但是它们对音视频通话应用来说它们还是很关键的,因为他们能解决防火墙问题和低带宽连接的问题。

描述

一个RTP Translator/Mixer 连接至少两个传输层的用户组。通常来说,这里提到的用户组是公共网络的概念,传输层协议会为其生成一个组播地址(ip:port)。网络层协议,像是IPv4和IPv6对RTP协议来说是隐藏的。一个系统可能会有多个Translator和Mixer(多个Session),它们中的每一个都可以看作是一个用户组的逻辑分割。
为了避免创建在创建Translator和Mixer造成了网络包循环,必须遵循下列规则:

  • 每个通过连接Translator和Mixer而加入Session的用户组,要么需要网络层隔离,要么最少互相知道知道(protocol,address,port)中的一个。
  • 由上一个规则推广的话,各个用户组绝对不能同时连接多个Translator或者Mixer,除非有某种机制能保证他们之间数据被阻断

Translator:在不改变RTP报文SSRC的条件下,向后传播该报文,正因为如此,报文的接受者才能识别到Translator转发后的报文到底是来自哪个人。有些Translator可能直接转发报文,不做任何改动,也有可能改变数据编码,payload类型和时间戳。如果多个数据报文被重新编码并合并到一起的话,Translator必须为这类报文指定一个组新的序列号。这样,输入报文的丢失就会导致输出报文的断层。数据的接受者一般是不知道Translator的存在的,除非通过payload类型的不同或者传输层报文的源地址来判断。
Mixer:从一个或多个数据源那里接收数据,随后可能会改变数据的格式,然后将这些数据合并,并传递给下家。因为多个数据源的时序并不一定是同步的,所以Mixer需要整合各个数据源的时序关系,并将其映射到自己的一套时序上,所以Mixer也是一个SSRC,所有通过Mixer的报文必须打上该Mixer的SSRC。为了表示这些数据的原始数据源,一般会通过CSRC列表来记录。有些Mixer可能自己也是一个原始数据源,所以他自己的SSRC也会出现在CSRC列表中。有些应用可能不希望Mixer的SSRC出现在CSRC中,但是这样可能就无法发现循环网络包。

rtp-translator-mixer
上图是一个Mixers和Translators连接的例子。[]代表末端节点,()代表Mixer,<>代表Translator,”M1:48(1, 17)”表示Mixer1的报文,48是Mixer1的SSRC,括号里的1,17是CSRC,它合并了E1:17和E2:1这两个节点的数据。

Translator处理RTCP

除了要转发数据包,进行数据包的更改,Translator和Mixer也要发送RTCP报文。在很多情况下,它会将收到的末端节点的RTCP报文合并到复合包中。当再次收到这些包时或者自己的RTCP周期到时,它会将复合包发送出去。
有的Translator可能对收到的RTCP报文不做任何改动,只是简单的转发这个包。如果这个Translator改变了报文数据的payload,它必须对SR或者RR做相关的改动。通常来说,Translator不能将多个数据源的SR和RR合并,因为这样会导致RTT的计算出现问题(LSR,DLSR)。

  • SR中的发送者信息: Translator不会创建自己的发送者信息,它会将收到SR传给下家。其中SSRC不会发生任何改动,但是发送者信息有必要的话一定要做适当的改动。如果Translator改变了数据编码,那”byte count”字段就要更改。如果他将多个数据报文合并,那它需要修改”sender’s packet count”字段。如果它改变了时间频率,那就需要修改”RTP timestamp”。
  • SR/RR中的接受者信息:SSRC不会发生任何改动,如果Translator改变了序列号,那就需要修改”extended last sequence number”,在某些极端情况下,它可能完全没有接受反馈,或者根据接收到的SR/RR来构建自己的接受报告。
    一般情况下Translator是不需要自己的SSRC的,但是如果是为了表示自己的数据接受情况,它可能也会生成自己的SSRC,并将这些RTCP报文发送过所有的连接者。
  • SDES:一般Translator收到SDES后会什么都不改就发给下家,但是也有可能为了节约带宽筛掉CNAME之外的信息的,如果Translator要发送自己的RR信息,那它一定要发送一个自己的SDES给所有连接者。
  • BYE:无改动转发,如果Translator有自己的SSRC也要发送自己的BYE。
  • APP:无改动转发。

Mixer处理RTCP

因为Mixer会生成自己的数据流,所以他不会转发经过他的SR和RR而是为连接双方发送自己的SR和RR报文。

  • SR的发送者信息:Mixer不转发数据来源的发送信息。它会生成自己的发送者信息并把它发送给下家。
  • SR/RR中的接受者信息:Mixer会生成自己的接受信息,然后发送给所有数据来源,它绝对不能做接受报告的转发工作,或者把自己的接收信息发给错误的对象。
  • SDES:Mixers通常会不做任何改动就转发SDES信息,但是也有可能为了节约带宽过滤除了CNAME之外的其他信息。Mixer必须发送自己的SDES报文。通常,Mixer会将多个收到的SDES打包一起发送。
  • BYE:Mixer必须转发BYE报文。如果Mixer要退出时,它会将所有数据来源的SSRC放进BYE报文,也包括自己的SSRC。
  • APP:视上层应用。

瀑布型Mixer

rtp-translator-mixer
一个RTP Session可能包含多个Mixer和Translator,就像上图一样。如果Mixer是瀑布型的,就像M2和M3,一个Mixer收到的数据可能是已经合并过的,它有自己的CSRC列表。那么第二个Mixer需要将之前的CSRC和自己接受的所有SSRC合并。就像图中M3的输出是M3:89(64,45)。

SSRC的分配和使用

前面已经说过SSRC是一个随机的32-bit数,它需要在整个Session内保证唯一性。所以同一个网络下的参与者在刚加入Session时使用不同的SSRC至关重要。
我们不能简单的用本地的网络地址,因为可能不唯一。也不能不考虑初始状态而简单地调一个随机数函数。

碰撞的可能性

因为SSRC是随机选择的,这就可能多个数据源选用了相同的SSRC。如果大家是同时加入Session的话,这个碰撞的几率就更高。如果SSRC的数量是N,L是SSRC的数据长度(这里是32),那么碰撞的可能性是1 - exp(-N**2 / 2**(L+1)),当N=1000时,碰撞率大概是10**-4。

通常来说,实际的碰撞率会比上述的最坏情况要低。通常一个新节点加入时,其他节点已经有了自己的唯一SSRC,这时候碰撞的概率只是生成的新SSRC在这些现有SSRC之中的可能性。这时候碰撞率是N/2**L。当N=1000时,碰撞率大约是210*-7。
因为新加入的节点会先接受一段时间的报文然后才发送自己的第一个报文,所以在它生成SSRC时可以避开已知的SSRC,这也有效的降低了碰撞的几率。

碰撞的解决方案和循环的发现

通常来说SSRC碰撞的可能性很小,所有的RTP实现必须有发现冲突的机制,并在发现冲突时作出适当的处理。如果数据源发现了任何一个别的数据源和自己使用同一个SSRC,它必须用原来的SSRC发送一个BYE报文,然后选用一个新的SSRC。如果一个数据的接受者发现了多个数据源的SSRC碰撞了(通过传输地址或者CNAME),那么它会只接受其中一个人的报文,丢弃另一个人的所有报文。
因为整个Session中的SSRC是唯一的,所以它也可以被用来发现环型报文。环形报文会导致数据的重复以及控制信息的重复。

  • Translator可能会错误地将报文发送回该报文来的地方。
  • 两个Translator错误地同时启动,它们两个都会转发同样的数据。
  • Mixer可能会错误地将合并报文发送回这些报文来的地方。

一个数据源可能发现自己的或者别人的报文被循环发送了。无论是报文循环还是SSRC的碰撞都会导致同一个现象,即SSRC相同但是传输地址不同的报文。因此,如果数据源改变了自己的传输地址,那它就需要同时改变自己的SSRC来避免被检测成环形报文。有一个需要注意的内容是,如果一个Translator再重启的过程中改变了自己的传输地址,那么这个Translator转发的所有数据都会被检测成环。这类情况的解决方案一般有如下两个:

  • 重启的时候不改变传输地址
  • 接受者的超时机制

如果循环或者碰撞发生在离Translator和Mixer很远的地方,我们就不能通过传输地址来发现。但是我们仍然可以通过CNAME的不同来发现SSRC碰撞。
为了解决上述问题,RTP的实现必须包含一个类似如下的算法。这个算法不包括多个数据源SSRC碰撞的情况,这类情况通常下都是先用原来的SSRC发送一个BYE然后重新选择一个新的SSRC。
这个算法需要维护一个SSRC和传输地址的映射关系。因为RTP的数据和RTCP传输使用的是两个不同的端口,所以一个SSRC对应的是两个传输地址。
每次收到RTP报文和RTCP报文都会将其SSRC和CSRC在上述的表中进行比对。如果发现了传输地址对不上的情况,我们就可以说发现了一个循环或者碰撞。对于RTCP数据来说,可能每个数据块都有自己独立的SSRC,比如SDES数据,对于这种情况就需要分别比对。如果没有在表中找到这个SSRC或者CSRC,就需要新添加一项。当收到BYE报文时,需要先比对这个BYE的传输地址,如果传输地址匹配上了,就将这一项从表中删除。或者基于超时机制,将超时的数据从表中移除。
为了追踪自己的数据报文循环情况,必须维护另一个列表,这个表存储冲突报文的传输地址和收到该报文的时间。如果超过10个RTCP周期都没有收到这个传输地址的冲突报文,就将该项从表中删除。
下面的算法还假设参与者自己的SSRC和状态都包含在SSRC表中,它会先比对自己的SSRC。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
if (SSRC or CSRC identifier is not found in the source
identifier table) {
create a new entry storing the data or control source
transport address, the SSRC or CSRC and other state;
}

/* Identifier is found in the table */

else if (table entry was created on receipt of a control packet
and this is the first data packet or vice versa) {
store the source transport address from this packet;
}
else if (source transport address from the packet does not match
the one saved in the table entry for this identifier) {
/* An identifier collision or a loop is indicated */

if (source identifier is not the participant's own) {
/* OPTIONAL error counter step */
if (source identifier is from an RTCP SDES chunk
containing a CNAME item that differs from the CNAME
in the table entry) {
count a third-party collision;
} else {
count a third-party loop;
}
abort processing of data packet or control element;
/* MAY choose a different policy to keep new source */
}

/* A collision or loop of the participant's own packets */

else if (source transport address is found in the list of
conflicting data or control source transport
addresses) {
/* OPTIONAL error counter step */
if (source identifier is not from an RTCP SDES chunk
containing a CNAME item or CNAME is the
participant's own) {
count occurrence of own traffic looped;
}
mark current time in conflicting address list entry;
abort processing of data packet or control element;
}

/* New collision, change SSRC identifier */

else {
log occurrence of a collision;
create a new entry in the conflicting data or control
source transport address list and mark current time;
send an RTCP BYE packet with the old SSRC identifier;
choose a new SSRC identifier;
create a new entry in the source identifier table with
the old SSRC plus the source transport address from
the data or control packet being processed;
}
}

层级编码

对于不同Session的层级编码传输,一般都是所有层都使用同一个SSRC,如果其中某一层发现了SSRC冲突,那么只改变这一层的SSRC,而且他层的SSRC不做改变。

安全

下层协议可能会提供RTP应用所需要的所有安全服务,包括认证,数据完整性,数据保密性。这些服务在IP协议中都有解决方案。因为Audio和Video初始化过程中需要数据加密,而这时候IP协议这一层的安全服务还没有提供。所以,RTP需要实现一个RTP专用的保密服务。这个保密服务是非常轻量级的,而且保密部分的服务向后兼容,以后可以随时进行更换。或者,某些预设会提供这部分加密服务,比如SRTP(Secure Real-time Transport Protocol),SRTP是基于Advanced Encryption Standard (AES)提供了一个比RTP默认加密服务更强大的实现。

保密性

保密性是指我们的报文只希望一些特定的接受者可以解码成明文,而其他人只能得到无用的信息,保密性是通过加密编码来提供的。
当需要为RTP和RTCP报文提供加密服务时,所有传输的内容都会在下层报文那里进行加密。对于RTCP来说,需要一个32-bit的随机数作为前缀。而RTP报文不需要前缀,取而代之的是随机序列号和时间戳偏移。因为随机部分很少,所以可以说这是一个非常弱的初始向量。此外,SSRC也可被破解者修改,这是这个加密方案的另一个薄弱的环节。
对于RTCP来说,可能会将一个复合包分成两批,第一批加密,后一批明文发送。例如,SDES部分的信息可能加密,而接受报告部分不加密就发送出去,因为只有这样那些第三方监控器才能在不知道密钥的情况下统计网络状况。如下图所示,SDES信息必须跟在一个空的RR后,并且要有一个随机前缀。
rtp-encryption
RTP协议使用的Data Encryption Standard (DES)算法,使用cipher block chaining(CBC)模式,这需要数据填充到64-bit对齐。密码算法使用零作为初始向量,因为RTCP报文中已经有一个随机前缀了。
RTP之所以选择这个默认协议是因为它用起来很容易,但是因为DES太容易破解了。所以推荐预设中使用更健壮的加密算法来替换这个默认方案,例如Triple-DES。这些算法普遍需要一个随机初始化块,RTCP使用了32-bit的随机数作为前缀,RTP使用了时间戳和序列号的随机偏移,可是相邻的RTP报文之间的随机性就很差。需要注意的是,无论是RTCP还是RTP,它们的随机性都有限。加密型更好的应用,需要考虑更多的保密措施。例如SRTP配置文件,就基于AES来加密,它的加密方案就更完备,选择这个预设来使用RTP就挺不错的。
前面提到过也可以用IP级的加密方案或者RTP级的加密,一些预设可能会定义别的payload类型来加密。这种方案,可能只加密payload部分而头部分使用明文,因为只有payload部分才是应用真正需要的内容。这可能对硬件设备来说非常有用,它即处理解密过程,又处理解码过程。

身份认证和消息完整性

RTP协议这一层没有身份认证和消息完整性服务,因为有些上层服务可能没有认证就能使用。而消息完整性服务依赖下层协议来实现。

拥塞控制

所有的网络传输协议都需要拥塞控制,RTP也不例外,但是因为RTP的数据传输不会发生特别大的激变(固定频率发送,控制了带宽),所以RTP的拥塞控制会和TCP这类的实现有很大差别,RTP不会像TCP那样占用所有带宽。RTP的这种数据传输模式虽然降低了拥塞的风险,但是一旦发生了拥塞,也不能莽莽地减少网络负载。
因为RTP面向的是各种各样的上层应用,没有一个拥塞控制机制能适用于所有的情况。因此,拥塞控制部分应该定义在预设中,或者上层应用中。在有些预设中,会根据RTCP的反馈来适当的调整数据传输带宽。

RTP下的网络层和传输层协议

RTP需要下层协议提供多路复用机制。对于UDP这类应用,推荐RTP应该使用一个偶数端口传输数据,和它相关的RTCP流应该是用高一位的奇数端口。在单播模式下,每个参与者都需要一对端口来传输RTP和RTCP报文。两个参与者可能使用相同的端口。绝对不能以接收到的报文网络地址直接作为目标地址发送报文。
建议层编码模式是,使用相邻的端口,因此对于层N来说,数据端口是P+2N,控制端口是P+2N+1。对于IP组播来说,可能不会得到相邻的组播地址。
RTP数据报文没有描述报文长度的信息。所以RTP报文依赖下层协议提供长度标识。所以一个RTP报文的最大长度由下层协议限制。
如果RTP报文使用的下层协议是流传输协议的话,必须定义一套数据帧分割机制。

参考内容

[1] rfc3550

贝克街的流浪猫 wechat
您的打赏将鼓励我继续分享!
  • 本文作者: 贝克街的流浪猫
  • 本文链接: https://www.beikejiedeliulangmao.top/protocol/rtp/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 创作声明: 本文基于上述所有参考内容进行创作,其中可能涉及复制、修改或者转换,图片均来自网络,如有侵权请联系我,我会第一时间进行删除。