流媒体传输协议之 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 头的格式如下:
image.png上图中前 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): 7bits,该字段定义 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)组成时间对,而且需要同步的不同流会共用同一个参考时钟,通过对比不同流的时间对,就能计算出不同流的时间戳偏移量。这个时间对并不是和每个 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 中包含了多个媒体流后就会失去如下优势:

  • 使用不同的网络路径或者分配网络资源
  • 只接收某一种媒体数据(网络较差时只接收 audio)
  • 接收方对不同的媒体类型做不同的处理


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


预设可能对 RTP 头的改动


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


RTP 头拓展


RTP 提供了一个拓展机制,让上层应用可以将自定义的信息存储在 RTP 报文头。如果上层应用收到了无法识别的头部拓展数据,它们会忽略它。值得一提的是,这个头部拓展是有一些限制的。如果附加信息只对某些 payload 格式才有意义,那么最好还是别把这些信息放到头部拓展中,而是放到 payload 部分。image.gif

image.png

如果 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 生成的复合包的例子:

image.png如果一个复合包的长度超过了下层网络协议的 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_BW2. 如果某个参与者一个 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 报文image.gif

image.png

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,计算示意图如下:image.png

RR 报文image.gif

image.png

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


发送 / 接收报文的拓展

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


分析发送报告和接收报告

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

SDES 报文


image.png

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

  • version (V),padding (P),length: 和上面一样。


  • packet type (PT): 8 bits,202 表示 SDES 类型。


  • source count (SC): 5 bits,SSRC/CSRC 块的数量。


每一个块中都包含多个描述内容,这些描述内容都是 32-bit 对齐的,其中前 8-bit 描述了类型,接着 8-bit 描述了信息长度(不包含前 16-bit),然后信息内容。注意信息部分不能超过 255 BYTE,这和前面的很多工作类似是为了约束 RTCP 的带宽。


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


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

image.png

image.gifCNAME 有如下特征:


  • 因为 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:用户名image.png

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


EMAIL:电子邮箱地址image.png

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


PHONE:电话号码image.gifimage.png

电话号码需要以国际访问码开头,eg: "+1 908 555 1212"。


LOC:用户地理地址image.gifimage.png

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


TOOL:应用名或工具名image.gifimage.png

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


NOTE:提醒 / 状态image.gifimage.png

用来发送暂时性的消息描述当前状态。eg: "on the phone, can't talk"。


PRIV:自定义拓展image.gifimage.png

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


BYE 报文image.gifimage.png

BYE 报文表示一个或多个流媒体源不再活跃。

  • 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-bit,用空字节填补空缺。


APP:应用定义的 RTCP 报文


image.png

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 中,但是这样可能就无法发现循环网络包。image.png

上图是一个 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 的计算出现问题(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:视上层应用。


瀑布型 Mixerimage.gif

image.png

一个 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(-N2 / 2(L+1)),当 N=1000 时,碰撞率大概是 10**-4。通常来说,实际的碰撞率会比上述的最坏情况要低。通常一个新节点加入时,其他节点已经有了自己的唯一 SSRC,这时候碰撞的概率只是生成的新 SSRC 在这些现有 SSRC 之中的可能性。这时候碰撞率是 是 N/2**L。当 N=1000 时,碰撞率大约是 2*10**-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。

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 协议使用的 Data Encryption Standard (DES) 算法,使用 cipher block chaining (CBC) 模式,这需要数据填充到 64-bit 对齐。密码算法使用零作为初始向量,因为 RTCP 报文中已经有一个随机前缀了。

image.pngRTP 之所以选择这个默认协议是因为它用起来很容易,但是因为 DES 太容易破解了。所以推荐预设中使用更健壮的加密算法来替换这个默认方案,例如 Triple-DES。这些算法普遍需要一个随机初始化块,RTCP 使用了 32-bit 的随机数作为前缀,RTP 使用了时间戳和序列号的随机偏移,可是相邻的 RTP 报文之间的随机性就很差。需要注意的是,无论是 RTCP 还是 RTP,它们的随机性都有限。加密型更好的应用,需要考虑更多的保密措施。例如 SRTP 配置文件,就基于 AES 来加密,它的加密方案就更完备,选择这个预设来使用 RTP 就挺不错的。


前面提到过也可以用 IP 级的加密方案或者 RTP 级的加密,一些预设可能会定义别的 payload 类型来加密。这种方案,可能只加密 payload 部分而头部分使用明文,因为只有 payload 部分才是应用真正需要的内容。这可能对硬件设备来说非常有用,它既处理解密过程,又处理解码过程。


身份认证和消息完整性


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


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


RTP 需要下层协议提供多路复用机制。对于 UDP 这类应用,推荐 RTP 应该使用一个偶数端口传输数据,和它相关的 RTCP 流应该是用高一位的奇数端口。在单播模式下,每个参与者都需要一对端口来传输 RTP 和 RTCP 报文。两个参与者可能使用相同的端口。绝对不能以接收到的报文网络地址直接作为目标地址发送报文。


建议层编码模式是,使用相邻的端口,因此对于层 N 来说,数据端口是 P+2N,控制端口是 P+2N+1。对于 IP 组播来说,可能不会得到相邻的组播地址。


RTP 数据报文没有描述报文长度的信息。所以 RTP 报文依赖下层协议提供长度标识。所以一个 RTP 报文的最大长度由下层协议限制。


如果 RTP 报文使用的下层协议是流传输协议的话,必须定义一套数据帧分割机制。


参考


[1] rfc3550

阅读作者的更多文章,关注作者个人公众号:贝贝猫技术分享

作者的个人博客:https://www.beikejiedeliulangmao.top/


「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

image.png

相关文章
|
1月前
|
编解码 网络协议 程序员
【RTP 传输协议】实时视频传输的艺术:深入探索 RTP 协议及其在 C++ 中的实现
【RTP 传输协议】实时视频传输的艺术:深入探索 RTP 协议及其在 C++ 中的实现
69 0
|
25天前
|
编解码 网络协议 网络性能优化
RTP/RTCP 协议讲解
RTP/RTCP 协议讲解
53 0
|
3月前
|
编解码 Linux C语言
实现一个传输h.264的rtsp服务器
实现一个传输h.264的rtsp服务器
51 0
|
3月前
|
编解码 网络协议 流计算
RTSP协议介绍
RTSP协议介绍
88 0
|
3月前
|
Linux C语言
RTSP协议的实现
RTSP协议的实现
30 0
|
4月前
|
存储 网络协议 视频直播
音视频学习之rtsp学习rtp协议的理解(rtp)
音视频学习之rtsp学习rtp协议的理解(rtp)
50 0
|
9月前
|
移动开发 监控 网络协议
视频流传输协议
视频流传输协议
119 0
|
9月前
|
编解码 应用服务中间件 nginx
RTSP协议转换RTMP直播协议
RTSP协议转换RTMP直播协议
444 1
|
缓存 网络协议 算法
|
编解码 网络性能优化 网络协议