文档状态:
本文档为Internet社区指定了一个Internet标准跟踪协议,并要求讨论和提出改进建议。请参阅当前版本的“互联网官方协议标准”(STD 1),以了解该协议的标准化说明和状态。本备忘录的分发是无限制的。
版权记录:
版权所有(C)互联网协会(2003)。保留所有权利。
摘要:
本备忘录描述了实时传输协议RTP。RTP提供端到端网络传输功能,适用于多播或单播网络业务中传输实时数据(如音频、视频或仿真数据)的应用。RTP不解决资源预留问题,也不保证实时服务的服务质量。控制协议(RTCP)对数据传输进行了增强,以允许以一种可扩展到大型多播网络的方式监控数据传输,并提供最小的控制和识别功能。RTP和RTCP被设计成独立于底层传输层和网络层。该协议支持使用rtp级别的翻译程序和混频器。
本备忘录中的大部分内容与已经被废止的RFC 1889完全相同。网络上的数据包格式没有变化,只有控制协议如何使用的规则和算法发生了变化。最大的变化是对可伸缩定时器算法的改进,用于计算何时发送RTCP包,以便在许多参与者同时加入会话时最小化超过预期速率的传输。
目录
1. 介绍
1.1 术语
2. RTP使用场景
2.1 简单多播音频会议
2.2 音频和视频会议
2.3 混频器和转换器
2.4 分层编码
3. 重要术语
4. 字节顺序,对齐,和时间格式
5. RTP数据传输协议
5.1 RTP固定头域
5.2 多路复用RTP会话
5.3 RTP头的特定配置文件修改
5.3.1 RTP头扩展
6. RTP控制协议--RTCP
6.1 RTCP报文格式
6.2 RTCP传输间隔
6.2.1 保持会话成员的数量
6.3 RTCP报文收发规则
6.3.1 计算RTCP传输间隔
6.3.2 初始化
6.3.3 接收RTP或Non-BYE RTCP报文
6.3.4 接收RTCP BYE报文
6.3.5 定时退出SSRC
6.3.6 传输定时器过期
6.3.7 发送BYE报文
6.3.8 更新we_sent
6.3.9 分配源描述带宽
6.4 发送者和接收者报告
6.4.1 SR:发送者报告RTCP报文
6.4.2 RR:接收者报告RTCP报文
6.4.3 扩展发送者和接收者报告
6.4.4 分析发送者和接收者报告
6.5 SDES:RTCP报文-源描述
6.5.1 CNAME:SDES项-规范端点标识符
6.5.2 NAME:SDES项-用户名
6.5.3 EMAIL:SDES项-电子邮件地址
6.5.4 PHONE:SDES项-电话号码
6.5.5 LOC:SDES项-用户地理位置
6.5.6 TOOL:SDES项-应用程序或工具名称
6.5.7 NOTE:SDES项-注意事项/状态
6.5.8 PRIV:SDES项-私有扩展
6.6 BYE:RTCP报文-结束
6.7 APP:RTCP报文-应用程序定义
7. RTP转换器和混合器
7.1 一般说明
7.2 转换器中的RTCP处理
7.3 混频器中的RTCP处理
7.4 级联混频器
8. SSRC标识符的分配和使用
8.1 冲突的概率
8.2 冲突解决和循环检测
8.3 和分层编码一起使用
9. 安全性
9.1 保密性
9.2 身份验证和消息完整性
10. 拥塞控制
11. 网络和传输协议上的RTP
12. 协议常量摘要
12.1 RTCP报文类型
12.2. SEDS类型
13. RTP配置文件和有效负载格式规范
14. 安全性考虑
15. IANA的一些考虑
16. 知识产权声明
17. 致谢
附录A -算法
A.1 RTP数据头有效性检查
A.2 RTCP头合法性检查
A.3 判断预期和丢失包数
A.4 生成RTCP SDES报文
A.5 解析RTCP SDES报文
A.6 生成随机32位标识符
A.7 计算RTCP传输周期
A.8 估算接受间隔抖动
附录B - 相对于RFC 1889的变更
本备忘录规定了实时传输协议(RTP),该协议为具有实时特性的数据(如交互式音频和视频)提供端到端传输服务。这些服务包括有效载荷类型识别、序列编号、时间戳和交付监控。应用程序通常在UDP之上运行RTP来利用它的多路复用和校验和服务;这两个协议都提供了部分传输协议功能。然而,RTP可以与其他合适的底层网络或传输协议一起使用(参见第11节)。如果底层网络提供,RTP支持使用组播分发将数据传输到多个目的地。
请注意,RTP本身并不提供任何机制来确保及时交付或提供其他服务质量保证,而是依赖于底层服务来做到这一点。它不保证发送或防止乱序发送,也不假设底层网络是可靠的,按顺序发送数据包的。RTP中包含的序列号允许接收方重新构造发送方的包序列,而且序列号也可以用来确定包的正确位置,例如在视频解码中,不必按顺序解码包。
虽然RTP主要是为了满足多参与者多媒体会议的需要而设计的,但它并不局限于特定的应用程序。连续数据的存储、交互式分布式仿真、活动徽章以及控制和测量应用也可以使用RTP。
本文档定义了RTP,包括两个紧密联系的部分:
RTP是一种新型的协议,遵循了Clark和Tennenhouse[10]提出的应用层框架和集成层处理的原则。也就是说,RTP旨在具有延展性,以提供特定应用程序所需的信息,并且常常集成到应用程序处理中,而不是作为一个单独的层实现。RTP是一个故意不完整的协议框架。本文档指定了适用于RTP的所有应用程序预期通用的那些功能。在传统协议中,可以通过使协议更通用或添加需要解析的选项机制来容纳额外的功能,而RTP与传统协议不同,它旨在根据需要通过修改和/或添加报头来进行定制。示例见第5.3节和6.4.3节。
因此,除本文件外,还有完整的说明书特定应用程序的RTP需要一个或多个伴生对象文件(见第13节):
[11]中有关于实时服务及其实现算法的讨论,以及关于一些RTP设计决策的背景讨论。
本文档中的关键字“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”的解释应与BCP 14、RFC 2119[2]中描述的一致,并表示符合RTP实现的需求级别。
以下部分描述了RTP使用的一些方面。选择这些示例是为了说明使用RTP的应用程序的基本操作,而不是限制RTP的用途。在这些例子中,RTP是在IP和UDP之上进行的,并遵循RFC 3551中指定的音频和视频配置文件建立的约定。
IETF的一个工作组开会讨论最新的协议文件,使用Internet的IP多播服务进行语音通信。工作组主席通过某种分配机制获得组播组地址和端口对。一个端口用于音频数据,另一个端口用于RTCP (control)报文。此地址和端口信息被分发给预期的参与者。如果需要隐私,数据和控制数据包可以按照9.1节的规定进行加密,在这种情况下,还必须生成和分发加密密钥。这些分配和分发机制的确切细节超出了RTP的范围。
每个会议参与者使用的音频会议应用程序以小块(例如20毫秒)的持续时间发送音频数据。每个音频数据块之前都有一个RTP报头;RTP报头和数据依次包含在UDP包中。RTP报头表示每个分组包含什么类型的音频编码(如PCM、ADPCM或LPC),以便发送方可以在会议期间更改编码,例如,容纳通过低带宽链路连接的新参与者或对网络拥塞的迹象作出反应。
与其他分组网络一样,Internet偶尔也会丢失和重新排序分组,并以可变的时间延迟它们。为了应对这些缺陷,RTP报头包含时间信息和序列号,允许接收机重建源产生的时间,因此在本例中,扬声器每20毫秒连续播放一段音频。对会议中的每个RTP信息源分别进行此时间重构。接收方还可以使用序列号来估计丢失了多少包。
由于工作组成员在会议期间加入或离开,因此随时知道谁在参与以及他们接收音频数据的情况是很有用的。为此,会议中音频应用程序的每个实例都会周期性地在RTCP(控制)端口上广播接收报告和用户名。接收报告指示当前扬声器被接收的情况,并可用于控制自适应编码。除了用户名,其他识别信息也可能包括在带宽限制的控制之下。站点在离开会议时发送RTCP BYE包(章节6.6)。
如果在会议中同时使用音频和视频媒体,它们将作为单独的RTP会话进行传输。也就是说,每个媒体使用两个不同的UDP端口对和/或多播地址传输单独的RTP和RTCP数据包。在音频和视频会话之间没有RTP级别的直接耦合,只是参与这两个会话的用户应该在RTCP包中使用相同的区别(规范)名称,以便会话可以关联。
这种分离的一个动机是,允许会议的一些参与者只接收一种媒介(如果他们选择的话)。进一步解释见5.2节。尽管存在分离,但是可以使用RTCP包中携带的音频和视频的时间信息实现源音频和视频的同步回放
到目前为止,我们假设所有站点都希望接收相同格式的媒体数据。然而,这可能并不总是合适的。考虑这样一种情况:一个区域的参与者通过低速连接连接到享受高速网络访问的大多数会议参与者。与强迫每个人使用低带宽、低质量的音频编码不同,一个称为混频器的rtp级中继可以放置在低带宽区域附近。这个混合器重新同步传入的音频包以重构由发送端产生的恒定的20ms间隔,将这些重构的音频流混合成单个流,将音频编码转换为低带宽的编码,并在低速链路上转发低带宽的包流。这些数据包可以单播给单个收件人,也可以在不同地址上多播给多个收件人。RTP报头包括用于混频器的一种方法,以识别贡献于混合包的源,以便在接收端提供正确的说话者指示。
音频会议的一些预期参与者可能使用高带宽链路连接,但可能无法通过IP多播直接到达。例如,它们可能位于不允许任何IP数据包通过的应用程序级防火墙之后。对于这些站点,混合可能是不必要的,在这种情况下,可以使用另一种类型的rtp级中继,称为转换器。安装了两个转换器,一个在防火墙的两侧,外部的一个将通过安全连接到防火墙内的转换器接收到的所有多播数据包汇集在一起。防火墙内的转换器将它们作为多播包再次发送到限制在站点内部网络的多播组。
混合器和转换器可以为各种目的而设计。一个例子是视频混合器,它将单个人的图像缩放到单独的视频流中,并将它们合成到一个视频流中,以模拟群体场景。转换的其他例子包括一组只讲IP/UDP的主机与一组只懂ST-II的主机之间的连接,或者对来自单个源的视频流进行逐包编码转换,而无需重新同步或混合。混合器和转换器的操作细节见第7节。
多媒体应用程序应该能够调整传输速率,以匹配接收器的容量或适应网络拥塞。许多实现将速率自适应的责任放在源代码上。由于异构接收器的带宽要求相互冲突,这种方法不适用于组播传输。结果往往是一个最小公分母的方案,其中网络网格中的最小管道决定了整个现场多媒体“广播”的质量和保真度。
相反,速率适应的责任可以放在接收器通过结合分层编码和分层传输系统。在RTP over IP多播环境中,源可以跨多个RTP会话(每个会话在自己的多播组上进行)对分层表示的信号的递进层进行分条。接收方可以适应网络的异构性,并通过只加入适当的组播组子集来控制其接收带宽。
使用分层编码的RTP的详细信息见6.3.9节、8.3节和11节。
RTP payload:RTP以数据包形式传输的数据,如音频样本或压缩视频数据。有效负载的格式和解释超出了本文的范围。
RTP packet:一个数据包,由固定的RTP报头、一个可能为空的贡献源列表(见下文)和有效负载数据组成。一些底层协议可能需要定义RTP包的封装。通常底层协议的一个包包含一个RTP包,但如果封装方法允许,也可以包含几个RTP包(参见第11节)。
RTCP packet:一种控制报文,由类似于RTP数据报文的固定报头部分组成,后面跟着根据RTCP报文类型而变化的结构化元素组成。格式在第6节中定义。通常,多个RTCP包作为一个复合RTCP包在底层协议的单个包中一起发送;这是由每个RTCP报文固定报头中的长度字段启用的。
Port:传输协议用来区分给定主机中的多个目的地的抽象。TCP/IP协议使用小正整数识别端口。[12] OSI传输层使用的传输选择器(TSEL)相当于端口。RTP依赖于底层协议来提供一些机制,比如端口来复用会话中的RTP和RTCP数据包。
Transport address:网络地址和端口的组合,用于标识传输级端点,例如IP地址和UDP端口。数据包从一个源传输地址传输到一个目的传输地址。
RTP media type:RTP媒体类型是可以在单个RTP会话中携带的有效负载类型的集合。RTP配置文件为RTP负载类型分配RTP媒体类型。
Multimedia session:在一组共同的参与者之间的一组并发RTP会话。例如,视频会议(多媒体会议)可能包含音频RTP和视频RTP。
RTP session:一组与RTP通信的参与者之间的关联。一个参与者可以同时参与多个RTP会话。在多媒体会话中,除非编码本身将多种媒体多路复用到单个数据流中,否则每种媒体通常在单独的RTP会话中与自己的RTCP数据包一起携带。一个参与者通过使用不同的目的传输地址对接收不同的会话来区分多个RTP会话,其中一个传输地址对包括一个网络地址和一对用于RTP和RTCP的端口。RTP会话中的所有参与者可以共享一个共同的目的传输地址对,如IP多播的情况,或对每个参与者可能是不同的,如在单独的单播网络地址和端口对的情况。在单播的情况下,一个参与者可以使用相同的端口对接收来自所有其他参与者的端口,也可以为每个参与者使用不同的端口对。
RTP会话的显著特性是,每个会话都维护一个完整的、独立的SSRC标识符空间(定义见下面)。一个RTP会话中包含的参与者集包括那些可以接收任何一个参与者(RTP中的SSRC或CSRC(也在下面定义)或RTCP中传输的SSRC标识符的参与者。例如,考虑一个使用UDP单播实现的三方会议,每个参与者从另外两个单独的端口对上接收数据。如果每个参与者只将从其他参与者接收到的数据的RTCP反馈发送给该参与者,那么会议由三个单独的点对点RTP会话组成。如果每个参与者都向其他两个参与者提供关于其接收另一个参与者的RTCP反馈,则会议由一个多方RTP会话组成。后一种情况模拟了三个参与者之间的IP多播通信可能发生的行为。
RTP框架允许这里定义的变体,但是特定的控制协议或应用程序设计通常会对这些变体施加约束。
Synchronization source (SSRC):RTP信息流的源,由RTP报头中携带的32位数字SSRC标识符标识,以便不依赖于网络地址。来自同步源的所有数据包都是同一时间和序列号空间的一部分,因此接收方根据同步源对数据包进行分组,以便回放。同步源的例子包括从一个信号源(如麦克风或摄像机)或RTP混频器派生的信息流的发送端。同步源可以随时间改变其数据格式,例如音频编码。SSRC标识符是一个随机选择的值,意味着在一个特定的RTP会话中全局唯一(参见第8节)。一个参与者不需要对一个多媒体会话中的所有RTP会话使用相同的SSRC标识符;SSRC标识符的绑定是通过RTCP提供的(参见章节6.5.1)。如果一个参与者在一个RTP会话中产生多个流,例如从不同的摄像机,每个必须被识别为不同的SSRC。
Contributing source (CSRC):RTP信息流的一个源,它是由RTP混频器产生的合并流的一部分(见下文)。混合器将生成特定信息包的源的SSRC标识符列表插入到该信息包的RTP报头中。这个名单被称为CSRC名单。一个示例应用程序是音频会议,其中混频器指示所有的说话者,他们的语音被组合起来产生出数据包,允许接收者指示当前的说话者,即使所有音频数据包包含相同的SSRC标识符(混频器的标识符)。
End system:应用程序生成要以RTP包发送的内容,并/或使用接收到的RTP包的内容。在一个特定的RTP会话中,一个终端系统可以充当一个或多个同步源,但通常只有一个。
Mixer:一个中间系统从一个或多个源接收RTP包,可能改变数据格式,以某种方式组合这些包,然后转发一个新的RTP包。由于多个输入源之间的时间通常不会同步,混合器将在流之间进行时间调整,并为合并后的流生成自己的时间。因此,所有来自混合器的数据包将被标识为将混合器作为它们的同步源。
Translator:一种中间系统,它转发RTP包时保持同步源标识符不变。转换器的例子包括不混合编码的转换设备、从多播到单播的复制器以及防火墙中的应用程序级过滤器。
Monitor:一种应用程序,它接收RTP会话中参与者发送的RTCP数据包,特别是接收报告,并为分布监控、故障诊断和长期统计评估当前的服务质量。监视功能可能内置到参与会话的应用程序中,但也可能是一个单独的应用程序,它不参与或不发送或接收RTP数据包(因为它们在一个单独的端口上)。这些被称为第三方监视器。也可以允许第三方监视器接收RTP数据包,但不发送RTCP数据包,或者被计入会话。
Non-RTP means:除了RTP之外,可能需要提供可用服务的协议和机制。特别是,对于多媒体会议,控制协议可以分发用于加密的多播地址和密钥,协商要使用的加密算法,并定义RTP负载类型值和它们所表示的负载格式之间的动态映射,用于那些没有预定义负载类型值的格式。此类协议的示例包括会话发起协议(SIP) (RFC 3261[13])、国际电联建议H.323[14]和使用SDP (RFC 2327[15])的应用程序,如RTSP (RFC 2326[16])。对于简单的应用程序,也可以使用电子邮件或会议数据库。这些协议和机制的规范超出了本文档的范围。
所有的整数字段都是按网络字节顺序携带的,也就是说,最重要的字节(八位字节)先携带。这种字节顺序通常被称为大端序。传输顺序在[3]中有详细描述。除非另有说明,数字常量是十进制的(以10为基数)。
所有报头数据按其自然长度对齐,即,16位字段按偶数偏移对齐,32位字段按可被4整除的偏移对齐,等等。指定为填充的八位元的值为零。
Wallclock时间(绝对日期和时间)使用网络时间协议(NTP)的时间戳格式表示,相对于1900年1月1日[4]的0h UTC,以秒为单位。全分辨率NTP时间戳是一个64位无符号定点数字,前32位为整数部分,后32位为小数部分。在一些适合使用更紧凑表示的字段中,只使用中间的32位;也就是整数部分的低16位和小数部分的高16位。整数部分的高16位必须独立确定。
为了使用RTP,一个实现不需要运行网络时间协议。可以使用其他时间源,或者根本不使用(请参见6.4.1节中对NTP时间戳字段的描述)。然而,运行NTP对于同步来自不同主机的流可能是有用的。
NTP时间戳将在2036年的某个时候绕到零,但对于RTP而言,只使用对NTP时间戳之间的差异。只要可以假定时间戳对之间的时间间隔在68年以内,使用模算术进行减法和比较就会使环绕变得无关重要。
RTP报头的格式如下:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|X| CC |M| PT | sequence number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | synchronization source (SSRC) identifier | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | contributing source (CSRC) identifiers | | .... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
前12个字节出现在每个RTP包中,而CSRC标识符列表只在混频器插入时出现。这些字段有以下含义:
version (V): 2 bits
该字段标识RTP的版本。本规范定义的版本为2(值1用于RTP的第一个草案版本,值0用于最初在“vat”音频工具中实现的协议)。
padding (P): 1 bit
如果设置了填充位,则数据包在末尾包含一个或多个不属于有效负载的附加填充字节。填充的最后八位元包含应该忽略多少个填充八位元的计数,包括其本身。某些具有固定块大小的加密算法或在较低层协议数据单元中携带多个RTP包时可能需要填充。
extension (X): 1位
如果设置了扩展位,固定的报头必须紧跟一个报头扩展名,格式见章节5.3.1。
CSRC count (CC): 4 bits
CSRC的计数包含了固定标题后面的CSRC标识符的数量。
marker (M): 1 bit
标记的解释由一个配置文件定义。它的目的是允许在包流中标记诸如帧边界之类的重要事件。配置文件可以定义额外的标记位,或者通过改变负载类型字段的位数来指定没有标记位(参见章节5.3)。
payload type (PT): 7 bits
该字段标识RTP有效负载的格式,并确定应用程序对其的解释。配置文件可以指定负载类型代码到负载格式的默认静态映射。附加的负载类型代码可以通过非rtp方式动态定义(参见第3节)。音频和视频的一组默认映射在附带的RFC 3551[1]中指定。一个RTP源可以在一个会话中改变负载类型,但是这个字段不应该用于复用单独的媒体流(见章节5.2)。接收方必须忽略其无法理解的负载类型的数据包。
sequence number: 16 bits
对于发送的每个RTP数据包,序列号递增1,并且可被接收端用于检测包丢失和恢复包序列。序列号的初始值应该是随机的(不可预测的),从而使对加密的已知明文攻击更加困难,即使源本身没有按照9.1节中的方法进行加密,因为包可能会通过一个进行加密的转换器。在[17]中讨论了选择不可预测数字的技术。
timestamp: 32 bits
时间戳反映了RTP数据包第一个八位元的采样瞬间。采样瞬间必须从时钟中得到,该时钟在时间上单调和线性递增,以允许同步和抖动计算(参见6.4.1节)。时钟的分辨率必须足以满足所需的同步精度和测量数据包到达抖动(每个视频帧一个节拍通常是不够的)。时钟频率取决于作为负载携带的数据的格式,在定义格式的配置文件或负载格式规范中静态指定,或者可以通过非rtp方式定义的负载格式动态指定。如果RTP包是周期性生成的,则使用从采样时钟中确定的标称采样瞬间,而不是系统时钟的读数。例如,对于固定速率的音频,时间戳时钟可能会为每个采样周期增加1。如果音频应用程序从输入设备读取包含160个采样周期的块,则时间戳将为每个这样的块增加160个,无论该块是以包的形式传输还是以静默方式丢弃。
与序列号一样,时间戳的初始值应该是随机的。几个连续的RTP包将有相等的时间戳,如果它们(逻辑上)是一次生成的,例如,属于同一个视频帧。连续的RTP包可能包含时间戳,如果数据不是按照采样的顺序传输,比如在MPEG插值视频帧的情况下,时间戳就不是单调的。(传输的数据包的序列号仍然是单调的。)
来自不同媒体流的RTP时间戳可能以不同的速率前进,并且通常具有独立的随机偏移。因此,尽管这些时间戳足以重建单个流的时间,但直接比较来自不同媒体的RTP时间戳对于同步是无效的。相反,对于每个介质,RTP时间戳与采样瞬间相关,通过将其与来自参考时钟(wallclock)的时间戳配对,该参考时钟表示与RTP时间戳相对应的数据被采样的时间。参考时钟被所有要同步的媒体共享。时间戳对并不是在每个数据包中传输,而是在RTCP SR数据包中以较低的速率传输,如章节6.4所述。
采样瞬间被选择为RTP时间戳的参考点,因为它是传输端点已知的,并且对所有媒体都有一个共同的定义,独立于编码延迟或其他处理。目的是允许同时采样的所有媒体的同步显示。
传输存储数据的应用程序,而不是实时采样的数据,通常使用从wallclock时间派生的虚拟表示时间轴,以确定存储数据中的下一帧或每个媒体的其他单元何时应该显示。在这种情况下,RTP时间戳将反映每个单元的表示时间。也就是说,每个单元的RTP时间戳将与该单元在虚拟表示时间轴上变为当前状态的wallclock时间相关。根据接收方的决定,实际的陈述发生在一段时间之后。
以预先录制的视频的现场音频解说为例说明了选取采样瞬间作为参考点的意义。在这种情况下,视频将在本地呈现给解说员看,并将同时使用RTP传输。在RTP中传输的视频帧的“采样瞬间”将通过将其时间戳引用到视频帧呈现给叙述者时的wallclock时间来建立。通过引用音频采样时相同的wallclock时间来建立包含叙述者讲话的音频RTP包的采样瞬间。如果两台主机上的参考时钟通过某种方式(如NTP)同步,音频和视频甚至可能由不同的主机传输。然后,通过使用RTCP SR包中的时间戳对关联音频和视频包的RTP时间戳,接收器可以同步音频和视频包的表示。
SSRC: 32 bits
SSRC字段标识同步源。这个标识符应该随机选择,目的是在同一个RTP会话中没有两个同步源具有相同的SSRC标识符。附录a .6给出了一个生成随机标识符的算法示例。尽管多个源选择相同标识符的概率很低,但所有RTP实现都必须准备好检测和解决冲突。第8节描述了冲突的概率,以及一种基于SSRC标识符的唯一性来解决冲突和检测rtp级转发循环的机制。如果一个源改变了它的源传输地址,它也必须选择一个新的SSRC标识符来避免被解释为一个循环源(见章节8.2)。
CSRC list: 0 to 15 items, 32 bits each
CSRC列表确定了该数据包中有效载荷的来源。标识符的数量由CC字段给出。如果有超过15个来源,只能识别出15个。CSRC标识由混频器插入(见第7.1节),使用贡献源的SSRC标识。例如,对于音频包,列出了混合在一起创建包的所有源的SSRC标识符,允许在接收端正确地指示说话者。
为了高效的协议处理,应该尽量减少多路复用点的数量,如集成层处理设计原则[10]中所述。在RTP中,多路复用由每个RTP会话不同的目的传输地址(网络地址和端口号)提供。例如,在一个由单独编码的音频和视频媒体组成的电话会议中,每个媒体都应该在一个单独的RTP会话中携带,并具有自己的目标传输地址。
单独的音频和视频流不应该在单个RTP会话中携带,也不应该根据负载类型或SSRC字段进行解复用。使用不同的RTP媒体类型但使用相同的SSRC将会引入几个问题:
对每种媒体使用不同的SSRC,但在相同的RTP会话中发送它们可以避免前三个问题,但不能避免后两个问题。
另一方面,使用不同的SSRC值在一个RTP会话中复用相同介质的多个相关源是多播会话的规范。上面列出的问题并不适用:例如,一个RTP混频器可以组合多个音频源,并且相同的处理方法适用于所有这些源。在最后两个问题不适用的其他场景中,使用不同的SSRC值复用相同介质的流也可能是合适的。
现有的RTP数据包头被认为对于RTP可能支持的所有应用程序类所需的公共功能集是完整的。然而,为了与ALF设计原则保持一致,可以通过修改或添加在概要文件规范中定义的内容来裁剪header,同时仍然允许独立于概要文件的监视和记录工具发挥作用。
如果在所有概要文件中都需要额外的功能,那么应该定义一个新的RTP版本来对固定的头文件进行永久的更改。
它提供了一种扩展机制,允许各个实现试验新的与负载格式无关的功能,这些功能需要在RTP数据包报头中携带额外的信息。此机制的设计目的是为了使头扩展可能被其他未扩展的互操作实现忽略。
请注意,此头扩展仅用于有限的使用。这种机制的大多数潜在用途最好采用另一种方式,即使用前一节中描述的方法。例如,对固定头的特定概要文件扩展的处理成本较低,因为它不是有条件的,也不在变量位置。特定有效载荷格式所需的附加信息不应使用此头扩展,但应在数据包的有效载荷部分携带。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | defined by profile | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | header extension | | .... |
如果RTP报头中的X位为1,则必须在RTP报头中添加可变长度的报头扩展名,如果存在,则在CSRC列表之后。头扩展包含一个16位长度的字段,用于计算扩展中32位字的数量,不包括4位的扩展头(因此0是有效长度)。只有一个扩展可以被追加到RTP数据头。为了允许多个互操作实现对每个实验独立使用不同的头扩展,或者允许一个特定的实现使用一种以上的头扩展,头扩展的前16位是开放的,用于区分标识符或参数。这16位的格式由配置文件规范定义,实现在该规范下运行。这个RTP规范本身没有定义任何报头扩展。
RTP控制协议RTCP (RTP control protocol)采用与数据包相同的分发机制,将控制报文定期发送给会话中的所有参与者。底层协议必须提供数据和控制数据包的多路复用,例如使用单独的UDP端口号。RTCP有四个功能:
功能1-3应该在所有环境中使用,特别是在IP多播环境中。RTP应用程序设计者应该避免只能在单播模式下工作且不能扩展到更大的数字的机制。对于发送方和接收方而言,RTCP的传输可能被单独控制,如6.2节所述,以应对无法从接收方获得反馈的单向链路等情况。
非规范注释:在称为源特定组播(SSM)的组播路由方法中,每个“信道”(一个源地址,组地址对)只有一个发送方,接收者(信道源除外)不能使用组播直接与其他信道成员通信。此处的建议仅通过6.2节中完全关闭接收端RTCP的选项来容纳SSM。未来的工作将明确RTCP对SSM的适应,以便能够维护来自接收方的反馈。
该规范定义了几种RTCP包类型,用于携带各种控制信息:
SR:发送方报告,用于来自活动发送方的参与者的传输和接收统计信息
RR:接收方报告,用于接收非活动发送方的参与者的统计信息,并结合SR用于报告超过31个来源的活动发送方
SDES:源描述项,包括CNAME
BYE:表示参与结束
APP:特定于应用程序的功能
每个RTCP数据包都以一个固定的部分开始,类似于RTP数据包的固定部分,后面是结构化的元素,根据数据包的类型,这些元素的长度可能是可变的,但必须以32位的边界结束。在每个包的固定部分包括对齐要求和长度字段,使RTCP包“可堆叠”。多个RTCP包可以在没有任何分隔符的情况下连接起来,形成一个复合RTCP包,该包在较低层协议(例如UDP)的单个包中发送。在复合包中没有明确的单个RTCP包的计数,因为底层协议期望提供一个总长度来确定复合包的结束。
复合包中的每一个RTCP包都可以独立处理,对包的顺序或组合没有要求。然而,为了执行协议的功能,需要施加以下约束:
因此,所有的RTCP包必须以至少两个独立包组成的复合包发送,格式如下:
Encryption prefix:当且仅当按照9.1节的方法对复合报文进行加密时,必须在每个传输的复合报文前加上一个随机的32位重画数作为前缀。如果加密需要填充,则必须在复合包的最后一个包中添加填充。
SR or RR:复合包中的第一个RTCP包必须始终是一个报告包,以便于附录a .2中所述的报头验证。即使没有发送或接收数据,在这种情况下也必须发送空RR,即使复合包中唯一的其他RTCP包是BYE,也是如此。
Additional RRs:如果正在报告接收统计信息的源的数量超过31个(一个SR或RR包的数量),则附加的RR包应该在初始报告包之后。
SDES:一个包含CNAME项的SDES报文必须包含在每个复合RTCP报文中,除非章节9.1中提到。如果特定应用程序需要,其他源描述项可以根据带宽限制(见章节6.3.9)选择性地包括在内。
BYE or APP:其他RTCP数据包类型,包括那些尚未定义的,可以以任何顺序紧随其后,除了BYE应该是与给定的SSRC/CSRC一起发送的最后一个数据包。报文类型可能出现多次。
每个RTP参与者应该在每个报告间隔内只发送一个复合RTCP包,以便正确估计每个参与者的RTCP带宽(参见章节6.2),除非按照章节9.1将复合RTCP包分开进行部分加密。如果源太多,无法在不超过网络路径最大传输单元(MTU)的情况下,将所有必要的RR报文放入一个复合RTCP报文中,则在每个间隔中只应包含能够放入一个MTU的子集。应该跨多个间隔轮询选择子集,以便报告所有源。
建议转换器和混合器在可行的情况下将来自多个源的RTCP包合并成一个复合包,以分摊包开销(见第7节)。图1显示了一个可能由混合器产生的RTCP复合包的示例。如果一个复合包的总长度超过网络路径的MTU,则应将其分割为多个较短的复合包,以底层协议的单独包的形式进行传输。这不会影响RTCP带宽估计,因为每个复合包至少代表一个不同的参与者。注意,每个复合报文必须以SR或RR报文开头。
实现应该忽略未知类型的传入RTCP数据包。如第15节所述,附加的RTCP数据包类型可以注册到Internet Assigned Numbers Authority (IANA)。
if encrypted: random 32-bit integer | |[--------- packet --------][---------- packet ----------][-packet-] | | receiver chunk chunk V reports item item item item -------------------------------------------------------------------- R[SR #sendinfo #site1#site2][SDES #CNAME PHONE #CNAME LOC][BYE##why] -------------------------------------------------------------------- | | |<----------------------- compound packet ----------------------->| |<-------------------------- UDP packet ------------------------->| #: SSRC/CSRC identifier
图1:RTCP复合数据包的示例
RTP旨在允许应用程序自动扩展会话大小,从几个参与者到数千个参与者。例如,在一个音频会议中,数据流量本质上是自我限制的,因为每次只有一或两个人发言,因此,在多播分布中,任何给定链接上的数据速率都保持相对恒定,与参与者的数量无关。但是,控制流量不是自我限制的。如果每个参与者的接收报告以固定的速率发送,控制流量将随着参与者的数量线性增长。因此,必须通过动态计算RTCP包传输之间的间隔来降低速率。
对于每个会话,假设数据流量受一个被称为“会话带宽”的聚合限制的约束,将在参与者之间分配。这个带宽可能会被保留,并由网络执行限制。如果没有预留,则可能存在其他约束(取决于环境),这些约束为会话使用“合理的”最大限制,即会话带宽。可以根据会话的某些开销或可用网络带宽的先验知识来选择会话带宽。它在某种程度上独立于媒体编码,但编码选择可能受到会话带宽的限制。通常,会话带宽是预期并发活动的发送方的标称带宽的总和。对于电话会议音频,这个数字通常是一个发送者的带宽。对于分层编码,每一层都是一个单独的RTP会话,具有自己的会话带宽参数。
会话带宽参数期望由会话管理应用程序在调用媒体应用程序时提供,但是媒体应用程序可以为为会话选择的编码设置基于单发送方数据带宽的默认值。应用程序还可以基于多播范围规则或其他标准强制带宽限制。为了计算出相同的RTCP时间间隔,所有参与者的会话带宽必须使用相同的值。
控制和数据流量的带宽计算包括底层传输和网络协议(例如,UDP和IP),因为这是资源预留系统需要知道的。应用程序还可以知道正在使用哪些协议。链路级报头不包括在计算中,因为包在传输过程中将被封装成不同的链路级报头。
控制流量应该被限制在会话带宽的一小部分:小到传输协议承载数据的主要功能不受影响;已知,以便控制流量可以包含在给定的资源保留协议的带宽规范中,并使每个参与者可以独立计算其份额。控制流量带宽是数据流量的会话带宽之外的带宽。建议为RTCP增加的会话带宽的比例固定为5%。也建议将1/4的RTCP带宽用于发送数据的参与者,这样在有大量接收方但只有少量发送方的会话中,新加入的参与者将更快地接收到发送站点的CNAME。当发送方所占比例大于1/4时,发送方将获得自己的RTCP全带宽份额。虽然间隔计算中的这些常数和其他常数的值不是关键的,但会话中的所有参与者必须使用相同的值,以便计算相同的间隔。因此,对于特定的配置文件,这些常量应该是固定的。
配置文件可以指定控制流量带宽为会话的单独参数,而不是会话带宽的严格百分比。如果使用单独的参数,则自适应速率应用程序可以将RTCP带宽设置为与“典型”数据带宽一致,但低于“会话带宽”参数指定的最大带宽。
该配置文件可进一步指定,对于作为活动数据发送方的参与者和非活动数据发送方的参与者,控制流量带宽可分为两个单独的会话参数;我们将这两个参数称为S和R。根据RTCP带宽的1/4专用于数据发送方的建议,这两个参数的推荐默认值将分别为1.25%和3.75%。当发送方所占比例大于参与者的S/(S+R)时,发送方得到各参数之和中各自所占的比例。通过使用两个参数,可以将非数据发送方的RTCP带宽设置为零,同时保持数据发送方的RTCP带宽为非零,以便发送方报告仍然可以用于媒体间同步,从而完全关闭特定会话的RTCP接收报告。不建议关闭RTCP接收报告,因为在第6节开始时列出的功能需要它们,特别是接收质量反馈和拥塞控制。但是,对于操作在单向链路上的系统,或者不需要接收质量或接收方活跃度反馈的会话,以及有其他方法避免拥塞的会话,这样做可能是合适的。
计算得到的复合RTCP报文之间的传输间隔也应该有一个下界,以避免在参与者数量较少时,流量没有按照大数定律平滑时,出现超过允许带宽的突发报文。它还可以防止在网络分区等暂态宕机期间报告间隔过小,从而在分区恢复时延迟调整。在应用程序启动时,应该在发送第一个复合RTCP包之前设置一个延迟,以便有时间从其他参与者接收RTCP包,这样报告间隔将更快地收敛到正确的值。此延迟可以设置为最小间隔的一半,以便更快地通知新参与者的出现。固定最小间隔的推荐值为5秒。
一个实现可以将最小RTCP间隔缩放到一个较小的值,这个值与会话带宽参数成反比,并具有以下限制:
第6.3节和附录A.7中描述的算法是为了满足本节中概述的目标而设计的。计算发送复合RTCP报文的时间间隔,划分各参与者允许的控制流量带宽。这允许应用程序为小型会话提供快速响应,例如,在小型会话中,所有参与者的标识都很重要,但应用程序可以自动适应大型会话。该算法包含以下特征:
此算法可用于所有参与者都允许发送的会话。此时,会话带宽参数为单个发送方带宽与参与者数量的乘积,RTCP带宽为其5%。
算法操作的细节将在下面的章节中给出。附录A.7给出了一个示例实现。
RTCP报文间隔的计算依赖于参与会话的站点数量的估计。当新的站点被听到时,它们会被添加到计数中,并且每个站点的一个条目应该被创建在一个由SSRC或CSRC标识符索引的表中(见章节8.2)来跟踪它们。在收到携带新SSRC的多个包之前(参见附录a .1),或者在收到包含该SSRC的CNAME的SDES RTCP包之前,新条目可能被认为是无效的。当收到带有相应SSRC标识符的RTCP BYE报文时,表中的表项可能被删除,除非一些掉线的数据包可能在该BYE之后到达并导致表项被重新创建。相反,应该将条目标记为已接收到BYE,然后在适当的延迟后删除。
一个参与者可以标记另一个站点不活跃,或者如果该站点还没有生效,或者在一小段RTCP报告间隔(建议5)内没有收到RTP或RTCP报文,则删除该站点。这提供了针对数据包丢失的一些健壮性。为了使该超时正常工作,所有站点必须具有此乘数的相同值,并且必须为RTCP报告间隔计算大致相同的值。因此,对于特定的配置文件,这个倍增器应该是固定的。
对于参与者数量非常大的会话,维护一个表来存储所有参与者的SSRC标识符和状态信息可能是不切实际的。实现可以使用SSRC采样,如[21]中所述,以减少存储需求。一个实现可以使用任何其他具有类似性能的算法。一个关键的要求是,所考虑的任何算法都不应该大大低估组的大小,尽管它可能会高估。
这里概述了如何发送和接收RTCP数据包时应该做什么的规则。允许在组播环境或多点单播环境中进行操作的实现必须满足章节6.2的要求。这样的实现可以使用本节定义的算法来满足这些要求,也可以使用其他一些算法,只要它提供相同或更好的性能。约束于两方单播操作的实现仍然应该使用RTCP传输间隔的随机化,以避免在同一环境下运行的多个实例的非预期同步,但可以省略第6.3.3节中的“定时器重新考虑”和“反向重新考虑”算法。6.3.6 6.3.7。
要执行这些规则,会话参与者必须维护多个状态块:
tp:最后一次发送RTCP数据包;
tc:当前时间;
tn:RTCP包的下一个预定传输时间;
pmembers:重新计算了在tn时刻估计的会话成员数;
members:会议成员人数的最新估计;
senders:会话中发送方数量的最新估计;
rtcp_bw:目标RTCP带宽,即该会话的所有成员将用于RTCP数据包的总带宽,单位为每秒字节。这将是在启动时提供给应用程序的“会话带宽”参数的指定部分。
we_sent:如果应用程序在发送第2个之前的RTCP报告之后发送了数据,则为true。
avg_rtcp_size:该参与者发送和接收的所有RTCP包的平均复合RTCP包大小(以字节为单位)。大小包括较低层的传输和网络协议头(例如,UDP和IP),如章节6.2所述。
initial:如果应用程序还没有发送RTCP数据包,则为true。
这些规则中的许多都利用了数据包传输之间的“计算间隔”。这个间隔将在下一节中描述。
为了保持可伸缩性,来自会话参与者的数据包之间的平均间隔应该随着组的大小而伸缩。这个区间称为计算区间。它是通过组合上面描述的多个状态块而得到的。计算区间T的确定如下:
这个过程产生的间隔是随机的,但平均来说,至少有25%的RTCP带宽给发送方,其余的给接收方。如果发送方组成超过四分之一的成员,该程序平均在所有参与者中平均分配带宽。
加入会议时,参与者初始化tp为0,tc为0,发送方为0,pmembers 1,成员1,we_sent为false, rtcp_bw会话指定分数的带宽,初始为true, avg_rtcp_size第一服务器包的可能的大小,应用程序将后构造。然后计算计算出的时间间隔T,第一个包被调度到时间tn = T。这意味着设置了一个传输定时器,在时间T到期。注意,应用程序可以使用任何想要的方法来实现这个定时器。
参与者将自己的SSRC添加到成员表中。
当从一个SSRC不在成员表中的参与者收到RTP或RTCP报文时,SSRC被添加到表中,并且当参与者通过验证后,members的值被更新,如6.2.1节所述。在一个已验证的RTP数据包中,每个CSRC都发生相同的处理。
当接收到某个参与者的RTP报文时,如果该参与者的SSRC不在发送方表中,则SSRC被添加到发送方表中,并更新发送方的值。
对于每一个收到的复合RTCP报文,都会更新avg_rtcp_size的值:
avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size
其中packet_size是刚刚接收到的RTCP数据包的大小。
除了第6.3.7节中描述的RTCP BYE要传输的情况外,如果接收到的是RTCP BYE报文,则根据成员表检查SSRC。如果存在,则从表中删除条目,并更新成员的值。然后根据发送者表检查SSRC。如果存在,则从表中删除条目,并更新发件人的值。
此外,为了使RTCP报文的传输速率更能适应组成员关系的变化,当收到一个将成员数量减少到小于pmembers的BYE报文时,应该执行以下的“反向复议”算法
当大型会话的大多数参与者立即离开,但仍有一些参与者存在时,由于过早超时,此算法不能防止组大小估计在短时间内错误地降为零。该算法确实使估计值更快地返回到正确的值。这种情况很不寻常,其后果也无害,因此这一问题仅被视为次要问题。
每隔一段时间,参与者必须检查是否有其他参与者超时。为此,参与者为接收方计算确定性(不含随机因子)的计算区间Td,即,使用we_sent false。在时间tc - MTd (M是超时倍率,默认为5)之后没有发送RTP或RTCP数据包的任何其他会话成员将被超时。这意味着它的SSRC将从成员列表中删除,并更新成员。对发件人列表执行类似的检查。从时间tc - 2T(最近两个RTCP报告间隔内)开始,发送方列表中任何没有发送过RTP包的成员将从发送方列表中删除,并更新发送方。
如果任何成员超时,则应执行第6.3.4节中描述的反向复议算法。
每个RTCP传输间隔至少需要检查一次。
当报文发送定时器超时时,参与者进行如下操作:
如果是RTCP报文,初始值设置为FALSE。此外,avg_rtcp_size的值也会更新:
avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size
其中packet_size是刚刚传输的RTCP数据包的大小。
当一个参与者想要离开一个会话时,一个BYE包被传输来通知其他参与者这个事件。当有多个参与者离开系统时,为了避免BYE报文泛滥,当参与者选择离开时,如果成员数大于50,则必须执行以下算法。该算法取代成员变量的正常角色来计数BYE包:
这允许立即发送BYE包,但控制了它们的总带宽使用。在最坏的情况下,这可能导致RTCP控制包使用两倍于正常带宽(10%)—非BYE RTCP包使用5%,BYE使用5%。
不希望等待上述机制来允许BYE包传输的参与者可以根本不发送BYE就离开组。那个参与者最终会被其他小组成员计时。
当参与者决定离开时,如果估计的群组成员小于50,则可以立即发送一个BYE包。或者,参与者可以选择执行上述BYE回退算法。
无论哪种情况,一个从未发送RTP或RTCP报文的参与者在离开该组时都不能发送BYE报文。
如果参与者最近发送过RTP包,变量we_sent包含true,否则为false。此决定使用与管理发件人表中列出的其他参与者集相同的机制。如果参与者在we_sent为false时发送了一个RTP包,它将自己添加到sender表中并将we_sent设置为true。为了尽可能减少发送SR报文之前的延迟,应该使用章节6.3.4中描述的反向复议算法。每发送一个RTP包,这个包的传输时间就在表中保持。然后对参与者应用正常的发送者超时算法——如果RTP包在tc - 2T之后没有传输,参与者从发送者表中删除自己,减少发送者计数,并设置we_sent为false。
除了必需的CNAME项之外,该规范还定义了几个源描述(SDES)项,例如NAME(个人名称)和EMAIL(电子邮件地址)。它还提供了一种方法来定义新的特定于应用程序的RTCP数据包类型。应用程序在为这些附加信息分配控制带宽时应该谨慎,因为它会减慢接收报告和CNAME发送的速度,从而影响协议的性能。建议将分配给单个参与者的RTCP带宽的20%用于携带附加信息。此外,并不打算将所有SDES项目都包括在每个应用程序中。应该根据它们的效用分配带宽的一部分。与其动态地估计这些分数,不如根据项目的典型长度将百分比静态地转换为报告间隔计数。
例如,一个应用程序可能被设计为只发送CNAME、NAME和EMAIL,而不发送任何其他的。NAME可能被赋予比EMAIL更高的优先级,因为NAME将在应用程序的用户界面中连续显示,而EMAIL将只在请求时显示。在每个RTCP时间间隔,发送一个带有CNAME项的RR报文和一个SDES报文。对于以最小间隔操作的小型会话,平均为每5秒一次。每隔3个间隔(15秒),SDES包中将包含一个额外的项。8次中有7次是NAME项,每8次(2分钟)是EMAIL项。
当多个应用程序通过一个通用CNAME为每个参与者使用跨应用程序绑定协同操作时,例如在一个由每个媒体的RTP会话组成的多媒体会议中,额外的SDES信息可以仅在一个RTP会话中发送。其他会话将只携带CNAME项。特别地,这种方法应该应用于分层编码方案的多个会话(参见第2.4节)。
RTP接收方使用RTCP报告包提供接收质量反馈,根据接收方是否也是发送方,RTCP报告包可以采取两种形式之一。发送方报告(SR)和接收方报告(RR)表单之间的唯一区别,除了包类型代码之外,是发送方报告包含一个20字节的发送方信息部分,供活动的发送方使用。如果站点在发布最后一个或上一个报告的间隔时间内发送了任何数据包,则发出SR,否则发出RR。
SR和RR表单都包含零个或多个接收报告块,每个报告块代表自上次报告以来接收到RTP数据包的每个同步源。报告不针对CSRC列表中列出的消息来源发布。每个接收报告块提供关于从该块中指定的特定来源接收的数据的统计信息。由于一个SR或RR包最多可以容纳31个接收报告块,因此需要在初始SR或RR包之后对额外的RR包进行堆叠,以包含从上次报告开始的间隔内听到的所有源的接收报告。如果源太多,不能在不超过网络路径MTU的情况下,将所有必要的RR报文放入一个复合RTCP报文中,那么在每个间隔中只应包含能够放入一个MTU的子集。应该跨多个间隔轮询选择子集,以便报告所有源。
接下来的部分定义了这两个报告的格式,如果应用程序需要额外的反馈信息,如何以特定于配置文件的方式对它们进行扩展,以及如何使用报告。转换器和混音器接收报告的细节见第7节。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ header |V=2|P| RC | PT=SR=200 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC of sender | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ sender | NTP timestamp, most significant word | info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | NTP timestamp, least significant word | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | RTP timestamp | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | sender's packet count | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | sender's octet count | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ report | SSRC_1 (SSRC of first source) | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1 | fraction lost | cumulative number of packets lost | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | extended highest sequence number received | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | interarrival jitter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | last SR (LSR) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | delay since last SR (DLSR) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ report | SSRC_2 (SSRC of second source) | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 2 : ... : +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | profile-specific extensions | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
发送者报告包由三个部分组成,如果定义,可能后面跟着第四个配置文件特定扩展部分。第一部分header长度为8字节。这些字段有以下含义:
version (V): 2 bits
RTP的版本号,RTCP报文和RTP数据报文的版本号相同。本规范定义的版本为二(2)。
padding (P): 1 bit
如果设置了填充位,这个RTCP包在末尾包含一些额外的填充字节,这些字节不是控制信息的一部分,但包含在长度字段中。填充的最后字节是应该忽略多少个填充字节的计数,包括它本身(它将是4的倍数)。某些具有固定块大小的加密算法可能需要填充。在复合RTCP包中,只需要对单个包进行填充,因为复合包作为一个整体对9.1节中的方法进行了加密。因此,填充必须只添加到最后一个单独的包,如果添加到该包,填充位必须只在该包上设置。该约定有助于附录A.2中所述的报头有效性检查,并允许检测来自某些早期实现的数据包,这些包错误地设置了第一个单独数据包的填充位,并在最后一个单独数据包中添加了填充。
reception report count (RC): 5 bits
此数据包中包含的接收报告块的数量。值为0时有效。
packet type (PT): 8 bits
包含常量200,以识别为RTCP SR包。
length: 16 bits
这个RTCP包的长度(32位字减1),包括报头和任何填充。(1的偏移量使0成为有效长度,从而避免了扫描复合RTCP数据包时可能出现的无限循环,而计数32位单词则避免了对4的倍数进行有效性检查。)
SSRC: 32 bits
同步源标识符为该SR报文的发起者。
第二部分,发送方信息,长度为20字节,存在于每个发送方报告包中。它总结了来自这个发送者的数据传输。这些字段有以下含义:
NTP timestamp: 64 bits
指示发送此报告时的wallclock时间(参见第4节),以便它可以与从其他接收方返回的接收报告中的时间戳结合使用,以度量到这些接收方的往返传播。接收器应该预料到时间戳的测量精度可能会被限制到远低于NTP时间戳的分辨率。时间戳的测量不确定度没有显示,因为它可能是未知的。在没有wallclock时间概念但有一些系统特定时钟(如“系统正常运行时间”)的系统上,发送方可以使用该时钟作为参考来计算相对的NTP时间戳。选择一个常用的时钟是很重要的,这样如果使用不同的实现来产生多媒体会话的单个流,那么所有的实现都将使用相同的时钟。直到2036年,相对时间戳和绝对时间戳的高位将会不同,因此(无效)比较将显示出很大的差异;到那时,人们希望不再需要相对的时间戳。没有wallclock或elapsed time概念的发送端可以将NTP时间戳设置为0。
RTP timestamp: 32 bits
与NTP时间戳(上面)相同,但与数据包中的RTP时间戳具有相同的单位和随机偏移。这种通信可以用于NTP时间戳被同步的源的内部和媒体间同步,也可以被媒体独立的接收器用来估计名义RTP时钟频率。注意,在大多数情况下,这个时间戳不等于任何相邻数据包中的RTP时间戳。相反,它必须从相应的NTP时间戳计算出来,使用RTP时间戳计数器和实时时间之间的关系,这是通过定期检查某个采样瞬间的时钟时间来维护的。
sender's packet count: 32 bits
发送端从开始传输到该SR包产生的RTP数据包总数。如果发送方更改了其SSRC标识符,则应重置计数。
sender's octet count: 32 bits
发送方在RTP数据包中从开始传输到该SR数据包生成的总有效字节数(即,不包括报头或填充)。如果发送方更改了其SSRC标识符,则应重置计数。此字段可用于估计平均有效负载数据速率。
第三部分包含零个或多个接收报告块,具体取决于该发送方自上次报告以来听到的其他来源的数量。每个接收报告块传递从单个同步源接收RTP包的统计信息。当一个源由于冲突而改变了它的SSRC标识符时,接收器不应该携带统计信息。这些统计数据:
SSRC_n (source identifier): 32 bits
此接收报告块中的信息所属的源的SSRC标识符。
fraction lost: 8 bits
自上一个SR或RR报文发送以来,来自源SSRC_n的RTP数据包丢失的百分比,表示为一个固定的点数,二进制点在字段的左边。(这相当于将损失部分乘以256后取整数部分。)这个分数定义为丢失的数据包数除以期望的数据包数,定义见下一段。一个实现如附录A.3所示。如果由于重复而丢失为负,则丢失的分数设置为零。请注意,接收方无法确定在最后一个包接收后是否有任何包丢失,而且如果在最后一个报告间隔期间从该源发送的所有包都丢失了,则不会为该源发出接收报告块。
cumulative number of packets lost: 24 bits
从源SSRC_n开始接收以来丢失的RTP数据包的总数。这个数字被定义为期望的包数减去实际收到的包数,其中收到的包数包括任何延迟或重复的包。因此,延迟到达的数据包不被计算为丢失,如果有重复,丢失可能是负的。预期的数据包数定义为下一个定义的扩展的最后接收序列号减去初始接收序列号。这可以按照附录A.3计算。
extended highest sequence number received: 32 bits
低16位包含从源SSRC_n接收到的RTP数据包中最大的序列号,最高的16位将该序列号扩展为相应的序列号循环次数,可以根据附录A.1的算法进行维护。请注意,如果同一会话中的不同接收者的开始时间显著不同,那么它们将对序列号生成不同的扩展。
interarrival jitter: 32 bits
RTP数据包到达时间间隔的统计方差的估计,以时间戳单位度量,并表示为无符号整数。到达间抖动J被定义为一对包在接收端与发送端之间的包间距差D的平均偏差(平滑的绝对值)。如下式所示,这相当于两个包的“相对传输时间”的差值;相对传输时间是数据包在到达时的RTP时间戳与接收方时钟之间的差值,用相同的单位来测量。
如果Si是数据包i的RTP时间戳,Ri是数据包i到达的RTP时间戳单位,那么对于两个数据包i和j, D可以表示为
D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si)
根据公式,当每个数据包i从源SSRC_n接收时,应该连续计算到达间抖动,根据到达的顺序(不一定是顺序),使用该数据包和前一个数据包i-1的差值D
J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16
每当发出接收报告时,就对J的当前值进行采样。
抖动计算必须符合这里指定的公式,以便允许独立于配置文件的监视器对来自不同实现的报告作出有效的解释。该算法是最优的一阶估计器,增益参数1/16在保持合理的收敛速率[22]的同时,具有良好的降噪比。附录A.8显示了一个示例实现。关于传输前不同数据包持续时间和延迟的影响的讨论,请参见6.4.4节。
last SR timestamp (LSR): 32 bits
从源SSRC_n收到的最新RTCP发送者报告(SR)报文的64位NTP时间戳中的中间32位(见章节4)。如果还没有接收到SR,该字段置零。
delay since last SR (DLSR): 32 bits
从源SSRC_n接收最后一个SR包到发送此接收报告块之间的延迟,单位为1/65536秒。如果没有收到来自SSRC_n的SR报文,则将DLSR字段置零。
让SSRC_r表示发出此接收方报告的接收方。Source SSRC_n可以通过记录接收到该接收报告块的时间A来计算到SSRC_r的往返传播延迟。它使用LSR last timestamp (LSR时间戳)字段计算A-LSR往返的总时间,然后减去该字段,就得到了往返的传播延迟(A -LSR - DLSR)。图2说明了这一点。时间以32位字段的十六进制表示和等效的浮点十进制表示两种形式显示。冒号表示32位字段被分成16位整数部分和16位小数部分。
这可以用作到集群接收器距离的近似测量,尽管有些链路有非常不对称的延迟。
[10 Nov 1995 11:33:25.125 UTC] [10 Nov 1995 11:33:36.5 UTC] n SR(n) A=b710:8000 (46864.500 s) ----------------------------------------------------------------> v ^ ntp_sec =0xb44db705 v ^ dlsr=0x0005:4000 ( 5.250s) ntp_frac=0x20000000 v ^ lsr =0xb705:2000 (46853.125s) (3024992005.125 s) v ^ r v ^ RR(n) ----------------------------------------------------------------> |<-DLSR->| (5.250 s) A 0xb710:8000 (46864.500 s) DLSR -0x0005:4000 ( 5.250 s) LSR -0xb705:2000 (46853.125 s) ------------------------------- delay 0x0006:2000 ( 6.125 s)
图2:往返时间计算的示例
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ header |V=2|P| RC | PT=RR=201 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC of packet sender | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ report | SSRC_1 (SSRC of first source) | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1 | fraction lost | cumulative number of packets lost | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | extended highest sequence number received | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | interarrival jitter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | last SR (LSR) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | delay since last SR (DLSR) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ report | SSRC_2 (SSRC of second source) | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 2 : ... : +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | profile-specific extensions | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
接收方报告(RR)报文的格式与接收方报告(SR)报文的格式相同,不同之处是报文类型字段包含常量201,且发送方信息的5个单词被省略(包括NTP和RTP时间戳、发送方的报文数和字节数)。其余字段的含义与SR报文相同。
当没有数据传输或接收报告时,一个空的RR报文(RC = 0)必须放在复合RTCP报文的头部。
如果需要定期报告有关发送方或接收方的附加信息,则配置文件应该定义发送方报告和接收方报告的特定配置文件扩展。应该优先使用该方法,而不是定义另一种RTCP数据包类型,因为它需要较少的开销:
扩展是发送方或接收方报告包中的第四个部分,它位于接收报告块(如果有)之后。如果需要额外的发送方信息,那么对于发送方报告,它将首先包含在扩展部分中,但是对于接收方报告,它将不存在。如果要包括关于接收方的信息,该数据的结构应该是一个与现有的接收报告块数组并行的块数组;即块的数量由RC字段表示。
预计接收质量反馈不仅对发送方有用,而且对其他接收方和第三方监测方也有用。发送方可以根据反馈修改其传输;接受者可以确定问题是局部的、区域的还是全局的;网络管理人员可以使用与配置文件无关的监视器,它只接收RTCP数据包,而不接收相应的RTP数据包,以评估其用于组播分发的网络性能。
在发送方信息和接收方报告块中都使用累积计数,以便可以计算任何两个报告之间的差异,以便在短时间和长时间内进行度量,并提供防止报告丢失的弹性。最近收到的两个报告之间的差异可以用来估计最近的分发质量。包括NTP时间戳,以便可以根据两个报告之间的时间间隔的差异计算费率。由于时间戳与数据编码的时钟速率无关,因此可以实现与编码和配置文件无关的质量监视器。
一个计算示例是两个接收报告之间的丢包率。累计丢失包数的差值表示在该时间间隔内丢失的包数。所接收到的扩展的上一个序列号的差值给出了在间隔期间预期的包数。这两者的比值就是在这个时间间隔内的丢包率。如果两个报告是连续的,这个比率应该等于丢失的分数字段,否则可能不等于。每秒的损失率可以通过将损失率除以NTP时间戳的差值(单位为秒)得到。收到的包数等于期望的包数减去丢失的包数。所期望的数据包数量也可用于判断任何损失估计的统计有效性。例如,5包丢1包的重要性小于1000包丢200包。
从发送方信息,第三方监控器可以在不接收数据的情况下计算一个间隔内的平均有效负载数据速率和平均包速率。取两者的比率就得到了平均有效载荷大小。如果可以假设包丢失与包大小无关,那么特定接收方接收的包数量乘以平均负载大小(或相应的包大小)就给出了该接收方可用的表观吞吐量。
除了允许使用不同报表之间的差异进行长期丢包度量的累计计数外,分数丢失字段还提供单个报表的短期度量。当会话的大小扩大到足够大,以至于接收状态信息可能不会为所有接收者保留,或者报告之间的间隔变得足够长,以至于可能只从某个特定接收者接收到一个报告时,这一点就变得更加重要。
到达间抖动字段提供了网络拥塞的第二个短期度量。丢包跟踪持续的拥塞,而抖动测量跟踪瞬时拥塞。抖动度量可以在拥塞导致包丢失之前就表明拥塞。到达间抖动字段只是报告时抖动的快照,不打算定量地进行处理。相反,它的目的是在一段时间内比较来自一个接收机或来自多个接收机(例如,在同一时间内,在单个网络内)的多个报告。为了在不同的接收机之间进行比较,根据所有接收机的相同公式计算抖动是很重要的。
因为抖动计算是基于RTP时间戳的,RTP时间戳代表了数据包中的第一个数据被采样的瞬间,在这个采样瞬间和数据包被传输的时间之间的任何延迟变化都会影响计算的结果抖动。这种延迟的变化会发生在持续时间不同的音频包中。它也会发生在视频编码中,因为一帧的所有数据包的时间戳是相同的,但这些数据包不是同时传输的。直到传输的延迟变化确实降低了作为网络本身行为的度量的抖动计算的准确性,但考虑到接收缓冲区必须容纳它是适当的。当抖动计算用作比较测量时,由于延迟变化直到传输的(常数)分量减去,这样网络抖动分量的变化就可以观察到,除非它是相对较小的。如果变化很小,那么它很可能是无关紧要的。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ header |V=2|P| SC | PT=SDES=202 | length | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ chunk | SSRC/CSRC_1 | 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SDES items | | ... | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ chunk | SSRC/CSRC_2 | 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SDES items | | ... | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
SDES包是一个三层结构,由一个报头和零个或多个块组成,每个块由描述该块中标识的源的项组成。这些项目将在后续部分中单独描述。
version (V),padding (P),length:
对SR报文的描述(参见6.4.1节)。
packet type (PT):8 bits
包含常量202,将其标识为RTCP SDES包。
source count (SC):5 bits
这个SDES包中包含的SSRC/CSRC块的数量。值为0是有效的,但无用。
每个区块包含一个SSRC/CSRC标识符,后面跟着一个由零个或多个项目组成的列表,其中包含有关SSRC/CSRC的信息。每个块从一个32位边界开始。每个条目包含一个8位类型字段,一个描述文本长度的8位字节计数(因此,不包括这个2位的头),以及文本本身。注意,文本不能超过255字节,但这与限制RTCP带宽消耗的需要是一致的。
文本按照RFC 2279[5]中指定的UTF-8编码进行编码。US-ASCII是这种编码的一个子集,不需要额外的编码。多字节编码的存在是通过将字符的最高位设置为值1来表示的。
项是连续的,即项不是单独填充到32位边界。文本不以空终止,因为一些多字节编码包含空字节。每个块中的项列表必须以一个或多个空字节结束,其中第一个字节被解释为0类型的项,表示列表的结束。空项类型字节后面没有长度字节,但是如果需要填充到下一个32位边界,则必须包含额外的空字节。注意,这个填充与RTCP报头中的P位表示的填充是分开的。具有0项的块(4个空字节)是有效的,但无用。
终端系统发送一个SDES包,其中包含自己的源标识符(与固定RTP报头中的SSRC相同)。一个混合器发送一个SDES包,其中包含一个块,用于接收来自它的SDES信息的每个贡献源,或者如果有超过31个这样的源,则发送多个完整的以上格式的SDES包(参见第7节)。
下面几节将描述目前定义的SDES项目。只有CNAME项为必填项。这里显示的一些项可能只对特定的配置文件有用,但是项类型都是从一个公共空间分配的,以促进共享使用并简化独立于配置文件的应用程序。如第15节所述,通过向IANA注册类型号,可以在配置文件中定义其他项目。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | CNAME=1 | length | user and domain name ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
CNAME标识符具有以下属性:
因此,在可能的情况下,CNAME应该通过算法派生,而不是手工输入。为了满足这些要求,除非配置文件指定了替代语法或语义,否则应该使用以下格式。CNAME项的格式应该是“user@host”,或者是“host”,如果用户名在单用户系统中不可用的话。对于这两种格式,“host”都是根据RFC 1034[6]、RFC 1035[7]和RFC 1123章节2.1[8]规定的规则格式化的实时数据来源的主机的完全限定域名;或RTP通信接口上主机数字地址的标准ASCII表示。例如,IPv4的标准ASCII表示是“点分十进制”,也称为点四分位,对于IPv6,地址在文本上表示为用冒号分隔的一组十六进制数字(RFC 3513[23]中有详细的变化)。其他地址类型期望具有相互唯一的ASCII表示。完全限定域名对于观测者来说更方便,并且可以避免另外发送name项的需要,但是在某些操作环境中可能很难或不可能可靠地获得。可能在这种环境中运行的应用程序应该使用地址的ASCII表示。
例如:[email protected]、[email protected]或doe@2201:056D::112E:144A:1E24。在没有用户名的系统上,示例将是"sleepy.example.com"、"192.0.2.89"或"2201:056D::112E:144A:1E24"。
用户名应该是像“finger”或“talk”这样的程序可以使用的形式,也就是说,它通常是登录名而不是个人名。主机名不一定与参与者的电子邮件地址中的主机名相同。
如果应用程序允许用户从一台主机生成多个源,则此语法不会为每个源提供惟一标识符。这样的应用程序将不得不依赖SSRC来进一步识别源,或者该应用程序的配置文件将不得不为CNAME标识符指定额外的语法。
如果每个应用程序独立创建它的CNAME,那么产生的CNAME可能不完全相同,因为需要跨属于一组相关RTP会话中的一个参与者的多个媒体工具提供绑定。如果需要跨媒体绑定,可能需要协调工具在外部配置每个工具的CNAME值相同。
应用程序编写者应该知道,私有网络地址分配,比如RFC 1918[24]中提出的Net-10分配,可能会创建全局不唯一的网络地址。如果具有私有地址且没有到公共互联网的直接IP连接的主机通过RTP级别的转换器将其RTP包转发到公共互联网,那么这将导致非惟一的CNAMEs。(参见RFC 1627[25]。)为了处理这种情况,应用程序可以提供一种配置惟一CNAME的方法,但是如果需要防止私有地址被暴露,那么将CNAMEs从私有地址转换为公共地址的责任就落在了转译人员身上。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | NAME=2 | length | common name of source ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
这是用来描述消息来源的真实名称,例如,“John Doe, Bit Recycler”。它可以是用户想要的任何形式。对于诸如会议这样的应用程序,这种形式的名称可能最适合显示在与会者列表中,因此除了CNAME之外,这种形式的名称可能发送得最频繁。简介可以建立这样的优先级。NAME值至少在会话期间保持不变。不应指望它在会议的所有参与者中是唯一的。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | EMAIL=3 | length | email address of source ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
按照RFC 2822[9]格式格式化,例如“[email protected]”。期望EMAIL值在会话期间保持不变。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | PHONE=4 | length | phone number of source ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
电话号码应该用加号来代替国际接入码。例如,美国的一个号码是“+1 908 555 1212”。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | LOC=5 | length | geographic location of site ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
根据应用程序的不同,该项目适用于不同程度的详细信息。对于会议应用程序,像“Murray Hill, New Jersey”这样的字符串可能就足够了,而对于活动的徽章系统,像“Room 2A244, AT&T BL MH”这样的字符串可能比较合适。细节的程度留给实现和/或用户,但格式和内容可以由配置文件规定。LOC值预计在会话期间保持不变,移动主机除外。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | TOOL=6 | length |name/version of source appl. ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
一个字符串,给出生成流的应用程序的名称和可能的版本,例如"videotool 1.2"。此信息可能用于调试目的,并且类似于Mailer或Mail-System-Version SMTP报头。预期TOOL值在会话期间保持不变。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | NOTE=7 | length | note about the source ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
对于该项建议使用以下语义,但是这些语义或其他语义可以由配置文件显式定义。NOTE项用于描述源的当前状态的瞬态消息,例如,“在电话上,不能通话”。或者,在研讨会中,这个项目可以用来传达演讲的标题。它应该只用于携带异常信息,不应该被所有参与者例行地包括在内,因为这会减慢接收报告和CNAME发送的速度,从而损害协议的性能。特别是,它不应该作为一个项目包含在用户的配置文件中,也不应该像每日引用那样自动生成。
因为当NOTE项处于活动状态时,显示它可能很重要,其他非CNAME项(如NAME)的传输速率可能会降低,以便NOTE项可以占用RTCP带宽的那一部分。当暂态消息变为非活动状态时,NOTE项应该继续以相同的重复频率发送几次,但使用长度为0的字符串向接收方发出信号。然而,如果没有以小倍数的重复频率(或者20-30 RTCP间隔)接收到NOTE项,接收者也应该认为它是不活动的。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | PRIV=8 | length | prefix length |prefix string... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ... | value string ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
此项用于定义实验性或特定于应用程序的SDES扩展。项目包含一个前缀,前缀由一个长度字符串对组成,后面是填充项目剩余部分并携带所需信息的值字符串。前缀长度字段为8位。前缀字符串是定义PRIV项的人选择的名称,该PRIV项相对于应用程序可能接收到的其他PRIV项来说是唯一的。如果需要,应用程序创建者可以选择使用应用程序名称加上额外的子类型标识。或者,建议其他人根据他们所代表的实体选择一个名称,然后协调该名称在该实体中的使用。
注意,前缀会占用项目总长度255字节内的一些空间,因此前缀应该尽可能短。这个功能和受限的RTCP带宽不应该过载;它并不是为了满足所有应用程序的所有控制通信需求。
IANA不会注册SDES PRIV前缀。如果某种形式的PRIV项目被证明是通用的,那么应该给它分配一个常规的SDES项目类型,并在IANA中注册,这样就不需要前缀。这简化了使用,提高了传输效率。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P| SC | PT=BYE=203 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC/CSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ : ... : +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ (opt) | length | reason for leaving ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
BYE报文表示一个或多个源不再活动。
version (V), padding (P), length:
对SR报文的描述(参见6.4.1节)。
packet type (PT):8 bits
包含常量203,以识别为RTCP BYE包。
source count (SC): 5 bits
该BYE包中包含的SSRC/CSRC标识符的数量。计数值为0是有效的,但无用。
当BYE包应该被发送的规则在章节6.3.7和8.2中指定。
如果一个BYE报文被一个混合器接收到,混合器应该转发SSRC/CSRC标识不变的BYE报文。如果一个混合器关闭,它应该发送一个BYE包,列出它处理的所有贡献源,以及它自己的SSRC标识符。可选地,BYE包可以包括一个8位的字节计数,后面跟着许多表示离开原因的八位文本,例如,“相机故障”或“RTP循环检测到”。该字符串具有与SDES相同的编码。如果该字符串将报文填充到下一个32位边界,则该字符串不以空结尾。如果不是,BYE包必须用空字节填充到下一个32位边界。这个填充与RTCP报头中的P位表示的填充是分开的。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P| subtype | PT=APP=204 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC/CSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | name (ASCII) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | application-dependent data ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
APP包用于实验用途,开发新应用程序和新功能,不需要注册包类型值。名字无法识别的APP包应该被忽略。在测试之后,如果更广泛的使用是合理的,建议重新定义每个APP包,不带子类型和名称字段,并使用RTCP包类型向IANA注册。
version (V), padding (P), length:
对SR报文的描述(参见6.4.1节)。
subtype:5 bits
可以用作一个子类型,允许一组APP包在一个唯一的名称下定义,或用于任何应用程序依赖的数据。
packet type (PT):8 bits
包含常量204,以识别为RTCP APP包。
name: 4 octets
定义一组APP数据包的人选择的一个名称,该名称与该应用程序可能接收到的其他APP数据包相比是唯一的。应用程序创建者可以选择使用应用程序名称,然后协调子类型值的分配给希望为应用程序定义新包类型的其他人。或者,建议其他人根据他们所代表的实体选择一个名称,然后协调该名称在该实体中的使用。名称被解释为四个ASCII字符的序列,大写和小写字符被视为不同的。
application-dependent data: variable length
应用程序相关的数据可能出现也可能不出现在APP数据包中。它由应用程序解释,而不是RTP本身。必须是32位的倍数。
除了终端系统之外,RTP还支持“转换器”和“混合器”的概念,在RTP级别上,它们可以被视为“中间系统”。虽然这种支持增加了一些协议的复杂性,但对这些功能的需求已经通过在Internet上组播音频和视频应用的实验明确了。在2.3节中给出的使用转换器和混合器的例子源于防火墙和低带宽连接的存在,这两者都可能会继续存在。
RTP转换器/混合器连接两个或多个传输层“云”。通常,每个云都是由一个公共网络和传输协议(例如IP/UDP)加上一个多播地址和传输级目的端口或一对单播地址和端口定义的。(网络级协议转换程序,例如IPv4到IPv6,可能存在于云中,对RTP来说是不可见的。)一个系统可以作为许多RTP会话的转换器或混合器,但每个都被认为是逻辑上独立的实体。
为了避免在安装转换器或混合器时产生循环,必须遵守以下规则:
类似地,所有可以通过一个或多个RTP转换器或混频器进行通信的RTP端系统共享相同的SSRC空间,即,在所有这些端系统中,SSRC标识符必须是唯一的。第8.2节描述了冲突解析算法,该算法使SSRC标识符保持唯一并检测循环。
可能有许多种专为不同目的和应用而设计的转换器和混合器。例如添加或删除加密,更改数据的编码或底层协议,或在多播地址和一个或多个单播地址之间进行复制。转换器和混合器的区别在于,转换器分别处理来自不同来源的数据流,而混合器将它们组合起来形成一个新的流
Translator:转发RTP包与他们的SSRC标识符完整;这使得接收方能够识别单个源,即使来自所有源的信息包都经过同一个转换器并携带该转换器的网络源地址。有些类型的转换器将不受影响地传递数据,但其他类型的转换器可能会更改数据的编码,从而更改RTP数据有效负载类型和时间戳。如果多个数据包被重新编码成一个,或者反之亦然,转换器必须为输出数据包分配新的序列号。入站包流中的损失可能导致出站序列号中的相应间隙。接收方无法检测到转换器的存在,除非他们通过其他方式知道原始源使用了什么有效负载类型或传输地址。
Mixer:接收来自一个或多个源的RTP数据包流,可能改变数据格式,以某种方式合并流,然后转发合并后的流。由于多个输入源之间的时序通常不会同步,混频器会在流之间进行时序调整,并对合并后的流产生自己的时序,因此它是同步源。因此,由混频器转发的所有数据包必须用混频器自己的SSRC标识符标记。为了保持组成混合数据包的原始源的身份,混频器应该将它们的SSRC标识符插入到数据包固定RTP头之后的CSRC标识符列表中。混频器本身也是某个数据包的来源,它应该在该数据包的CSRC列表中明确地包含自己的SSRC标识符。
对于一些应用,混合器不识别CSRC名单中的来源是可以接受的。然而,这带来了危险,即涉及这些源的循环无法被检测到。
对于音频等应用程序,混频器相对于转换器的优势在于,即使在输入端有多个源活动时,输出带宽也仅限于一个源的带宽。这对于低带宽链路可能很重要。缺点是,在输出端的接收器没有任何控制源被传递或静音,除非一些机制实现了对混频器的远程控制。混频器的同步信息再生也意味着接收端无法对原始流进行媒体间的同步。多媒体混音器就可以做到。
[E1] [E6] | | E1:17 | E6:15 | | | E6:15 V M1:48 (1,17) M1:48 (1,17) V M1:48 (1,17) (M1)------------->-----------------> -------------->[E7] ^ ^ E4:47 ^ E4:47 E2:1 | E4:47 | | M3:89 (64,45) | | | [E2] [E4] M3:89 (64,45) | | legend: [E3] --------->(M2)----------->(M3)------------| [End system] E3:64 M2:12 (64) ^ (Mixer) | E5:45 | [E5] source: SSRC (CSRCs) ------------------->
图3:带有终端系统、混频器和转换器的示例RTP网络
图3显示了一系列混合器和转换器,以说明它们对SSRC和CSRC标识符的影响。在图中,端部系统显示为矩形(命名为E),转换为三角形(命名为T),混频器为椭圆形(命名为M)。48(1,17)”指定一个来自混频器M1的报文,由M1的(随机)SSRC值48和两个CSRC标识符1和17来识别,这两个标识符复制自E1和E2报文的SSRC标识符。
除了转发数据包外,可能经过修改的混合器和转换器也必须处理RTCP数据包。在许多情况下,它们将分解从端系统接收到的复合RTCP包,以聚合SDES信息并修改SR或RR包。此信息的重传可能由包的到达或由混合器和转换器本身的RTCP间隔定时器触发。
一个不修改数据包的转换器,例如一个只在多播地址和单播地址之间复制的转换器,也可以简单地转发未修改的RTCP数据包。以某种方式转换有效负载的转换器必须在SR和RR信息中进行相应的转换,以便仍然反映数据的特征和接收质量。这些转换器绝不能简单地转发RTCP数据包。一般情况下,转换器不应该将来自不同源的SR和RR报文聚合到一个包中,因为这样会降低基于LSR和DLSR字段的传播延迟测量的准确性。
SR sender information:转换器不生成自己的发送方信息,而是将从一个云接收到的SR包转发给其他云。SSRC保持不变,但如果转换需要,必须修改发送者信息。如果一个转换器改变了数据编码,它必须改变“发送者的字节数”字段。如果它还将几个数据包组合成一个输出数据包,它必须更改“发送方的数据包计数”字段。如果改变时间戳的频率,则必须改变SR报文中的“RTP时间戳”字段。
SR/RR reception report blocks:转换器将从一个云接收到的接收报告转发给其他云。请注意,这些流的方向与数据相反。SSRC完好无损。如果一个转换器将几个数据包组合成一个输出数据包,因此改变了序列号,它必须对丢包字段和“扩展的最后序列号”字段进行反向操作。这可能很复杂。在极端情况下,可能没有有意义的方式转换接待报告,转换器可能根本不传递接待报告,也可能在自身接待的基础上形成综合报告。一般的规则是做对特定转换有意义的事情。
转换器不需要自己的SSRC标识符,但可以选择分配一个标识符,以便发送关于其收到的内容的报告。这些将被发送到所有连接的云,每一个都对应于发送到该云的数据流的转换,因为接收报告通常是组播到所有参与者。
SDES:转换器通常不改变从一个云接收到的SDES信息转发给其他云,但是,例如,如果带宽有限,MAY决定过滤非CNAME的SDES信息。CNAME必须被转发,以允许SSRC标识符冲突检测工作。生成自己RR包的转换器必须将关于自己的SDES CNAME信息发送到发送RR包的同一块云。
BYE:转换器转发BYE包不变。即将停止转发数据包的转换器应该向每个连接的云发送一个BYE包,其中包含之前转发到该云的所有SSRC标识符,如果它发送自己的报告,则包括转换器自己的SSRC标识符。
APP:转换器不改变APP数据包的转发。
由于混合器生成自己的新数据流,它根本不通过SR或RR报文,而是为双方生成新信息。
SR sender information:混合器不通过来自它混合的源的发送方信息,因为源流的特性在混合中丢失了。作为同步源,混合器应该生成自己的SR报文,其中包含混合数据流的发送端信息,并以与混合流相同的方向发送。
SR/RR reception report blocks:混合器为每个云中的源生成自己的接收报告,并只将它们发送到同一个云。它绝对不能将这些接收报告发送到其他云,也绝对不能将一个云的接收报告转发给其他云,因为源不是那里的SSRC(只有CSRC)。
SDES:混合器通常不改变从一个云接收到的SDES信息,而将其转发给其他云,但是,例如,如果带宽有限,MAY决定过滤非CNAME的SDES信息。CNAME必须被转发,以允许SSRC标识符冲突检测工作。(由混频器生成的证监会列表中的标识符可能与终端系统生成的SSRC标识符发生冲突。)一个混合器必须发送关于自己的SDES CNAME信息到它发送SR或RR报文的同一个云。
由于混合器不转发SR或RR包,它们通常会从复合RTCP包中提取SDES包。为了最小化开销,来自SDES包的块可以被聚合成一个单独的SDES包,然后该包被堆叠在来自混合器的SR或RR包上。聚合SDES包的混合器将比单个源使用更多的RTCP带宽,因为复合包将更长,但这是合适的,因为混合器代表多个源。类似地,当SDES包被接收时,通过它们的混合器将以高于单一源速率传输RTCP包,但同样,这是正确的,因为包来自多个源。混合器两端的RTCP报文速率可能不同。
没有插入CSRC标识符的混合器也可以避免转发SDES cname。在这种情况下,两个云中的SSRC标识符空间是独立的。如前所述,这种操作模式会造成无法检测到循环的危险。
BYE:混频器必须转发BYE包。即将停止转发数据包的混合器应该向每个连接的云发送一个BYE包,其中包含之前被转发到该云的所有SSRC标识符,如果它发送自己的报告,则包括混合器自己的SSRC标识符。
APP:混频器对APP包的处理是特定于应用程序的。
一个RTP会话可能包括一个混合器和转换器的集合,如图3所示。如果两个混频器级联,例如图中的M2和M3,则由混频器接收的数据包可能已经被混合,并可能包含具有多个标识符的CSRC列表。第二个混合器应该使用已经混合的输入数据包中的CSRC标识符和未混合的输入数据包中的SSRC标识符,为输出数据包构建CSRC列表。这显示在混合器M3的输出电弧标记为M3:89(64,45)在图中。在没有级联的混频器的情况下,如果产生的CSRC列表有超过15个标识符,则剩余的不能包括在内。
在RTP报头和RTCP报文的各个字段中携带的SSRC标识符是一个随机的32位数字,在一个RTP会话中必须是全局唯一的。谨慎选择数字是至关重要的,这样在同一网络或在同一时间开始的参与者就不可能选择相同的数字。
使用本地网络地址(例如IPv4地址)作为标识符是不够的,因为地址可能不是唯一的。由于RTP转换器和混频器支持具有不同地址空间的多个网络之间的互操作,因此在两个地址空间内的地址分配模式可能会导致比随机分配更高的冲突率。
在一个主机上运行的多个源也会发生冲突。
仅仅通过调用random()而没有仔细初始化状态来获得SSRC标识符也是不够的。附录a .6给出了一个如何生成随机标识符的示例。
由于标识符是随机选择的,两个或多个源可能会选择相同的数字。当所有源同时启动时,冲突发生的概率最高,例如当由某些会话管理事件自动触发时。如果N是源的数量,L是标识符的长度(这里是32位),两个源独立选择相同值的概率可以近似为大N[26]为1 - exp(-N**2 / 2**(L+1))。对于N=1000,概率大约是10**-4。
典型的冲突概率比最坏情况低得多。当一个新源加入一个RTP会话,其中所有其他源都已经有惟一标识符时,冲突的概率仅为所使用空间的数字的一部分。同样,如果N是源的数量,L是标识符的长度,冲突的概率是N / 2**L。对于N=1000,概率大约是2*10**-7。
冲突的概率进一步降低,因为一个新源在发送它的第一个包(数据或控制)之前有机会从其他参与者接收包。如果新源保持对其他参与者的跟踪(通过SSRC标识符),那么在发送它的第一个包之前,新源可以验证它的标识符与任何已经接收到的标识符不冲突,或者再次选择。
尽管SSRC标识符冲突的概率很低,但所有RTP实现都必须准备好检测冲突并采取适当的行动来解决它们。如果一个源在任何时候发现另一个源使用与自己相同的SSRC标识符,它必须为旧的标识符发送一个RTCP BYE包,并随机选择另一个。(如下面所述,在循环的情况下,此步骤只执行一次。)如果接收方发现其他两个源发生冲突,当不同的源传输地址或CNAMEs可以检测到这一点时,它可能会保留一个源的数据包,而丢弃另一个源的数据包。这两个来源有望解决冲突,这样情况就不会持续下去。
因为随机SSRC标识符对于每个RTP会话都是全局唯一的,所以它们也可以用来检测可能由混频器或转换器引入的循环。循环会导致数据和控制信息的重复(未修改的或混合的),如以下示例所示
一个源可能发现它自己的信息包正在被循环,或者来自另一个源的信息包正在被循环(第三方循环)。在随机选择源标识符的过程中,循环和冲突都会导致包以相同的SSRC标识符到达,但是不同的源传输地址(可能是发包的终端系统或中间系统的源传输地址)。
因此,如果一个源改变了它的源传输地址,它也可以选择一个新的SSRC标识符来避免被解释为一个循环源。(这不是必须的,因为在某些应用程序中RTP源可能期望在会话期间改变地址。)请注意,如果一个转换器重新启动,并因此改变了它转发数据包的源传输地址(例如,改变UDP源端口号),那么所有这些数据包对接收者来说都将是循环的,因为SSRC标识符是由原始源应用的,不会改变。这个问题可以通过在重启期间保持源传输地址固定来避免,但在任何情况下都将在接收端超时后解决。
如果数据包的所有副本都经过转换器或混合器,则使用源传输地址无法检测发生在转换器或混合器远端的循环或冲突,然而,当来自两个RTCP SDES包的块包含相同的SSRC标识符但cname不同时,冲突仍然可能被检测到。
为了检测和解决这些冲突,RTP实现必须包含一个类似于下面描述的算法,尽管实现可能会为来自冲突的第三方源的数据包选择不同的策略。下面描述的算法忽略来自新源或循环的与已建立的源冲突的数据包。它通过为旧的标识符发送RTCP BYE并选择一个新标识符来解决与参与者自己的SSRC标识符的冲突。然而,当冲突是由参与者自己的数据包循环引起时,算法只会选择一个新的标识符,然后忽略来自循环源传输地址的数据包。这是为了避免BYE包泛滥。
该算法要求保持一个按源标识符索引的表,并包含使用该标识符接收的第一个RTP包和第一个RTCP包的源传输地址,以及该源的其他状态。需要两个源传输地址,例如,RTP和RTCP报文的UDP源端口号可能不同。然而,可以假定两个源传输地址中的网络地址是相同的。
在RTP或RTCP数据包中收到的每个SSRC或CSRC标识符都在源标识符表中查找,以便处理该数据或控制信息。数据包的源传输地址与表中相应的源传输地址进行比较,如果它们不匹配,就检测出循环或冲突。对于控制包,每个具有自己SSRC标识符的元素(例如SDES块)需要单独的查找。(接收报告块中的SSRC标识符是一个例外,因为它标识了记者听到的源,并且该SSRC标识符与记者发送的RTCP包的源传输地址无关。)如果没有找到SSRC或CSRC,则创建一个新条目。当收到带有相应SSRC标识符的RTCP BYE报文并通过匹配的源传输地址进行验证,或者在相当长的时间内没有报文到达时(参见章节6.2.1),这些表项将被删除。
请注意,如果同一主机上的两个源在接收端开始操作时使用相同的源标识符进行传输,那么很可能收到的第一个RTP包来自其中一个源,而收到的第一个RTCP包来自另一个源。这将导致错误的RTCP信息与RTP数据相关联,但这种情况应该足够罕见且无害,可以忽略它。
为了跟踪参与者自己的数据包的循环,实现还必须保持一个发现冲突的源传输地址(而不是标识符)的单独列表。在源标识符表中,必须保留两个源传输地址,以便分别跟踪冲突的RTP和RTCP数据包。注意,冲突的地址列表应该很短,通常为空。这个列表中的每个元素都存储源地址以及最近收到冲突数据包的时间。当一个源的报文在10个RTCP报告间隔内没有冲突报文到达时(见章节6.2),该元素可以从列表中删除。
对于所示的算法,假设参与者自己的源标识符和状态包含在源标识符表中。可以重新构造算法,首先与参与者自己的源标识符进行单独的比较。
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;
}
}
在该算法中,来自新冲突源地址的数据包将被忽略,而来自原始源地址的数据包将被保留。如果在一段时间内没有来自原始源的数据包到达,表项将超时,新源将能够接管。如果原始源检测到冲突并移动到新的源标识符,则可能会发生这种情况,但在通常情况下,将从原始源接收一个RTCP BYE包来删除状态,而无需等待超时。
如果原始源地址是通过混频器接收的(即,作为CSRC的一员),后来又直接接收相同的源,那么最好建议接收方切换到新的源地址,除非混频器中的其他源将丢失。此外,对于一些应用程序(如电话),其中一些源(如移动实体)可能在RTP会话过程中改变地址,RTP实现应该修改碰撞检测算法以接受来自新源传输地址的数据包。为了防止真正发生冲突时地址之间的切换,算法应该包含一些检测这种情况并避免切换的方法。
当由于冲突而选择一个新的SSRC标识符时,应该首先在源标识符表中查找候选标识符,看看它是否已经被其他源使用。如果是,则必须生成另一个候选,并重复该过程。
数据包到多播目的地的循环可能导致严重的网络洪泛。所有的混合器和转换器都必须实现一个像这里这样的循环检测算法,这样它们才能打破循环。这应该将多余的通信量限制为不超过原始通信量的一个副本,这样可以允许会话继续,以便找到并修复循环的原因。然而,在极端情况下,混频器或转换器不能正确地打破循环,导致高流量级别,终端系统可能需要完全停止传输数据或控制包。这个决定可能取决于应用程序。应该适当地指出错误条件。在一段很长的随机时间(按分钟的顺序)后,可以周期性地再次尝试传输。
对于在单独的RTP会话上传输的分层编码(见章节2.4),一个单一的SSRC标识符空间应该跨所有层的会话使用,核心(基础)层应该用于SSRC标识符分配和冲突解决。当一个源发现它发生碰撞时,它只在基础层上传输RTCP BYE包,但将所有层的SSRC标识符更改为新值。
底层协议最终可能提供RTP应用程序所需的所有安全服务,包括身份验证、完整性和机密性。这些服务已在[27]中为IP指定。由于最初使用RTP的音频和视频应用程序需要在这些服务用于IP层之前提供保密服务,因此定义了下一节中描述的保密服务,以便与RTP和RTCP一起使用。这里包含的描述是为了编纂现有的实践。RTP的新应用程序可以实现RTP特定的保密服务以实现向后兼容性,并且/或者它们可以实现替代的安全服务。用于此保密服务的RTP协议的开销很低,因此,如果将来该服务被其他服务废弃,损失也很小。
另外,将来还可能为RTP定义其他服务、服务的其他实现和其他算法。具体来说,一个名为SRTP(安全实时传输协议)[28]的RTP配置文件正在开发中,以提供RTP有效载荷的机密性,同时保持RTP报头清晰,以便链路级报头压缩算法仍然可以运行。预计SRTP将成为许多应用的正确选择。SRTP基于高级加密标准AES (Advanced Encryption Standard),提供比这里描述的服务更强的安全性。没有人说这里介绍的方法适合于特定的安全需求。配置文件可以指定应用程序应该提供哪些服务和算法,并可以提供关于它们的适当使用的指导。
密钥分发和证书不在本文档的讨论范围之内。
保密性意味着只有预期的接收方可以解码接收到的数据包;对于其他报文,报文中不包含任何有用信息。内容的机密性是通过加密来实现的。
当需要按照本节所述的方法对RTP或RTCP进行加密时,将在单个低层分组中封装传输的所有字节作为一个单元进行加密。对于RTCP,为每个单元重绘的32位随机数必须在加密前加在该单元的前面。对于RTP,没有前缀;相反,序列号和时间戳字段用随机偏移值初始化。由于随机性差,这被认为是一个弱初始化向量(IV)。此外,如果随后的领域,SSRC,可以被攻击者操纵,这是加密方法的进一步弱点。
对于RTCP,实现可以将复合RTCP包中的各个RTCP包隔离为两个单独的复合RTCP包,一个加密,一个明文发送。例如,SDES信息可能会被加密,而接收报告则是明文发送的,以适应不知道加密密钥的第三方监视器。在这个例子中,如图4所示,SDES信息必须附加到一个没有报告(和随机数)的RR包中,以满足所有复合RTCP包都以SR或RR包开始的要求。在加密的或未加密的数据包中都需要SDES CNAME项,但不是两者都需要。不应该在两个包中携带相同的SDES信息,因为这可能会破坏加密。
UDP packet UDP packet ----------------------------- ------------------------------ [random][RR][SDES #CNAME ...] [SR #senderinfo #site1 #site2] ----------------------------- ------------------------------ encrypted not encrypted #: SSRC identifier
图4:加密和非加密RTCP数据包
加密的存在和正确密钥的使用由接收方通过报头或有效负载有效性检查来确认。在附录A.1和A.2中给出了RTP和RTCP头的有效性检查的例子。
为了与RFC 1889中RTP初始规范的现有实现保持一致,默认的加密算法是密码块链(CBC)模式下的数据加密标准(DES)算法,如RFC 1423章节1.1[29]所述。除了填充到8位元的倍数是按照5.1节中对P位的描述来表示的。初始化向量是零,因为随机值是在RTP头中提供的,或由复合RTCP包的随机前缀提供的。有关CBC初始化向量的使用,请参见[30]。
支持这里指定的加密方法的实现应该始终支持CBC模式下的DES算法作为该方法的默认密码,以最大限度地提高互操作性。选择这种方法的原因是,它被证明是简单实用的实验音频和视频工具在互联网上的操作。然而,后来发现DES太容易损坏。
建议使用三重des等更强的加密算法来替代默认算法。此外,安全的CBC模式要求每个包的第一个块与与密码块大小相同的随机独立的IV异或。对于RTCP,这是(部分)通过在每个包前面加上一个32位随机数来实现的,这个随机数是为每个包独立选择的。对于RTP,时间戳和序列号从随机值开始,但连续的数据包不会被独立随机。应该注意的是,这两种情况(RTP和RTCP)的随机性都是有限的。高安全性应用程序应该考虑其他更常规的保护方法。其他加密算法可以通过非rtp方式动态地为会话指定。特别是,正在开发的基于AES的SRTP配置文件[28]将考虑已知的明文和CBC明文操作问题,这将是未来的正确选择。
作为IP级别或RTP级别加密的替代方案,配置文件可以为加密编码定义额外的有效负载类型。这些编码必须指定如何处理填充和加密的其他方面。此方法只允许对数据进行加密,而对需要加密的应用程序保留未加密的头信息。对于同时处理解密和解码的硬件设备,它可能特别有用。对于希望对RTP和低层报头进行链路级压缩,并且有效负载(而不是地址)的保密性足以防止压缩的应用程序,它也很有价值。
没有在RTP级别定义身份验证和消息完整性服务,因为如果没有密钥管理基础设施,这些服务将无法直接实现。认证和完整性服务预计将由低层协议提供。
Internet上使用的所有传输协议都需要以某种方式解决拥塞控制问题[31]。RTP也不例外,但由于通过RTP传输的数据通常是无弹性的(以固定或控制的速率生成),因此RTP中控制拥塞的方法可能与其他传输协议(如TCP)非常不同。从某种意义上说,无弹性降低了拥塞的风险,因为RTP流不会像TCP流那样扩展到消耗所有可用带宽。然而,不弹性也意味着当发生拥塞时,RTP流不能任意减少它在网络上的负载来消除拥塞。
由于RTP可以在许多不同的环境中用于各种各样的应用程序,所以没有单一的拥塞控制机制可以适用于所有的应用程序。因此,拥塞控制应该在每个RTP配置文件中适当地定义。对于一些配置文件,它可能足够包括一个适用性声明,将该概要文件的使用限制在通过工程避免拥塞的环境中。对于其他配置文件,可能需要特定的方法,例如基于RTCP反馈的数据速率调整。
本节描述在特定的网络和传输协议中携带RTP包的具体问题。以下规则适用,除非被本规范之外的协议特定定义取代。
RTP依赖于底层协议来提供RTP数据和RTCP控制流的解复用。对于UDP和类似的协议,RTP应该使用偶数目的端口号,相应的RTCP流应该使用下一个更高的(奇数)目的端口号。对于将单个端口号作为参数并从该数字派生出RTP和RTCP端口对的应用程序,如果提供的是奇数,则应用程序应该将该数字替换为下一个更小的(偶数)数字,以用作端口对的基数。对于通过显式的、单独的参数(使用信令协议或其他方法)指定RTP和RTCP目的端口号的应用程序,尽管仍然鼓励使用偶数/奇数端口对,但应用程序可以不考虑端口号为偶数/奇数和连续的限制。RTP和RTCP的端口号不能相同,因为RTP依赖端口号来解复用RTP数据和RTCP控制流。
在单播会话中,双方都需要确定接收RTP和RTCP报文的端口对。两个参与者可以使用相同的端口对。参与者绝对不能假设传入的RTP或RTCP报文的源端口可以作为传出的RTP或RTCP报文的目的端口。当RTP数据包在两个方向发送时,每个参与者的RTCP SR报文必须发送到其他参与者指定的接收RTCP的端口。RTCP SR数据包将发送端信息(用于传出数据)和接收端报告信息(用于传入数据)结合起来。如果一方没有主动发送数据(参见章节6.4),则发送RTCP RR报文。
建议分层编码应用程序(参见章节2.4)使用一组连续的端口号。端口号必须是不同的,因为现有操作系统中普遍存在这样的缺陷,即不能使用带有多个组播地址的同一端口,而对于单播,只允许使用一个地址。因此,对于第n层,数据端口为P + 2n,控制端口为P + 2n + 1。当使用IP组播时,地址也必须是不同的,因为组播路由和组成员关系是在一个地址粒度上管理的。但是,不能假定分配连续的IP多播地址,因为一些组可能需要不同的作用域,因此可能从不同的地址范围分配。
上一段与SDP规范RFC 2327[15]冲突,RFC 2327[15]认为在同一会话描述中指定多个地址和多个端口是非法的,因为地址和端口的关联可能是不明确的。在RFC 2327的修订版中,这个限制将被放宽,允许使用一对一映射指定相同数量的地址和端口。
RTP数据包不包含长度字段或其他描述,因此RTP依赖于底层协议来提供长度指示。RTP数据包的最大长度仅受底层协议的限制。
如果RTP包要在提供连续字节流而不是消息(包)抽象的底层协议中携带,则必须定义RTP包的封装以提供帧机制。如果底层协议可能包含填充,以便无法确定RTP有效载荷的范围,则也需要帧。这里没有定义框架机制。
配置文件可以指定一个帧方法,即使在提供帧的协议中携带RTP,以便允许在一个较低层协议数据单元中携带多个RTP数据包,例如UDP数据包。在一个网络或传输包中携带多个RTP包可以减少报头开销,并可以简化不同流之间的同步。
本节包含本规范中定义的常量的摘要清单。
RTP有效负载类型(PT)常量是在配置文件中定义的,而不是在本文档中定义的。然而,RTP报头中包含标记位和负载类型的八位必须避免保留值200和201(十进制),以便在附录A.1中描述的报头验证过程中将RTP包与RTCP SR和RR包类型区分开来。对于一个标记位和一个7位有效载荷类型字段的标准定义(如本规范所示),这个限制意味着有效载荷类型72和73被保留。
abbrev. name value SR sender report 200 RR receiver report 201 SDES source description 202 BYE goodbye 203 APP application-defined 204
这些类型值选择在200-204之间,用于改进RTCP数据包的报头有效性检查,与RTP数据包或其他不相关的数据包相比。对比RTCP报文类型字段和RTP报头对应的字节,这个范围对应于标记位为1(它通常不在数据包中)和标准负载类型字段的高位为1(因为静态负载类型通常在低半部分定义)。由于全0和全1是常见的数据模式,所以这个范围也被选择为从0到255的数字距离。
由于所有复合RTCP报文必须以SR或RR开头,所以这些代码被选择为偶数/奇数对,以便RTCP有效性检查测试掩码和值的最大位数。
附加的RTCP数据包类型可以通过IANA注册(参见第15节)。
abbrev. name value END end of SDES list 0 CNAME canonical name 1 NAME user name 2 EMAIL user's electronic mail address 3 PHONE user's phone number 4 LOC geographic user location 5 TOOL name of application or tool 6 NOTE notice about the source 7 PRIV private extensions 8
其他SDES类型可以通过IANA注册(见第15节)。
一个特定应用程序的完整RTP规范将需要一个或多个这里描述的两种类型的配套文档:配置文件和有效负载格式规范。
RTP可以用于各种需求略有不同的应用程序。通过允许在主协议规范中有多种选择,然后在单独的配置文件中为特定的环境和应用程序类别选择适当的选择或定义扩展,从而提供了适应这些需求的灵活性。在一个特定的RTP会话中,一个应用程序通常只在一个配置文件下运行,因此在RTP协议本身中并没有明确的指示正在使用哪个配置文件。音频和视频应用程序的配置文件可以在RFC 3551中找到。配置文件的标题通常是“RTP配置文件…”。
第二种类型的附带文档是有效载荷格式规范,它定义了如何在RTP中携带特定类型的有效载荷数据,例如H.261编码视频。这些文档的标题通常是“XYZ音频/视频编码的RTP有效载荷格式”。负载格式在多个配置文件下可能是有用的,因此可以独立于任何特定概要文件定义负载格式。然后,如果需要,配置文件文档负责将该格式的默认映射分配给负载类型值。
在这个规范中,已经为一个概要文件中可能的定义确定了以下项目,但是这个列表并不是详尽的:
RTP data header:RTP数据报头中包含标记位和负载类型字段的八位可以由配置文件重新定义以适应不同的需求,例如使用更多或更少的标记位(第5.3节,第18页)。
Payload types:假设包含有效负载类型字段,配置文件通常会定义一组有效负载格式(例如,媒体编码)和这些格式到有效负载类型值的默认静态映射。一些有效负载格式可以通过引用不同的有效负载格式规范来定义。对于定义的每个负载类型,配置文件必须指定要使用的RTP时间戳时钟速率(第5.1节)。
RTP data header additions:如果需要跨配置文件的应用程序类的一些独立于负载类型的附加功能,则可以将附加字段添加到固定RTP数据头(第5.3节)。
RTP data header extensions:RTP数据头扩展结构的前16位内容必须定义,如果在特定于实现的扩展的配置文件中允许使用该机制(第5.3.1节)。
RTCP packet types:可以定义新的特定于应用程序类的RTCP数据包类型并向IANA注册。
RTCP report interval:配置文件应该指定使用6.2节中建议的用于计算RTCP报告间隔的常数的值。它们是会话带宽中的RTCP部分、最小报告间隔以及发送方和接收方之间的带宽分割。一个概要文件可以指定替代值,如果它们已经被证明以可伸缩的方式工作。
SR/RR extension:如果有需要定期报告的关于发送方或接收方的附加信息,则可以为RTCP SR和RR包定义扩展节(第6.4.3节)。
SDES use:配置文件可以指定RTCP SDES项传输或完全排除的相对优先级(章节6.3.9);CNAME项的替代语法或语义(章节6.5.1);LOC项目的格式(第6.5.5节);注释项(章节6.5.7)的语义和使用;或向IANA注册新的SDES项目类型。
Security:配置文件可以指定应用程序应该提供哪些安全服务和算法,并可以提供关于它们的适当使用的指导(第9节,第65页)。
String-to-key mapping:配置文件可以指定如何将用户提供的密码或密码短语映射为加密密钥。
Congestion:配置文件应该指定适合该配置文件的拥塞控制行为。
Underlying protocol:可能需要使用特定的底层网络或传输层协议来传送RTP数据包。
Transport mapping:RTP和RTCP到传输层地址的映射,例如UDP端口,与第11节中定义的标准映射不同,可以指定。
Encapsulation:RTP数据包的封装可以定义为允许多个RTP数据包在一个较低层数据包中携带,或在尚未这样做的底层协议上提供帧(第11节,第69页)。
并不期望每个应用程序都需要一个新的配置文件。在一个应用程序类中,为了促进应用程序之间的互操作,最好是扩展现有的配置文件,而不是创建一个新的配置文件,因为每个应用程序通常只在一个配置文件下运行。简单的扩展,例如附加负载类型值或RTCP包类型的定义,可以通过通过IANA注册它们并在配置文件的附录或负载格式规范中发布它们的描述来完成。
RTP有与底层协议相同的安全责任。例如,冒名顶替者可以伪造源地址或目的网络地址,或者更改报头或有效负载。在RTCP中,CNAME和NAME信息可以用来模拟另一个参与者。此外,RTP可以通过IP多播发送,这没有提供直接的方法让发送方知道所有接收方发送的数据,因此没有隐私措施。不管正确与否,用户对音频和视频通信的隐私问题可能比他们对更传统的网络通信[33]更敏感。因此,在RTP中使用安全机制很重要。这些机制已在第9节中讨论。
RTP级别的转换器或混合器可以用于允许RTP流量到达防火墙后的主机。在设计和安装这些设备以及允许RTP应用程序在防火墙后使用这些设备时,应遵循适当的防火墙安全原则和实践,这些原则和实践超出了本文的范围。
额外的RTCP包类型和SDES项目类型可以通过Internet Assigned Numbers Authority (IANA)注册。由于这些数字空间很小,允许无限制地注册新值将是不谨慎的。为便于审查请求并促进在多个应用程序之间共享使用新类型,注册新值的请求必须记录在RFC或其他永久性和随时可获得的参考文件中,如另一个合作标准组织(如ITU-T)的产品。根据“指定专家”的意见,也可接受其他请求。(专家联系方式请联系IANA)
RTP配置文件规范应该以“RTP/xxx”的形式向IANA注册一个配置文件名称,其中xxx是配置文件标题的简短缩写。这些名称供高级控制协议使用,如会话描述协议(SDP), RFC 2327[15],用来引用传输方法。
IETF没有立场的有效性或任何知识产权或其他权利的范围可能声称属于实现或使用本文档中描述的技术或任何许可在这种权利的程度可能会或可能不会是可用的;它也不代表它已作出任何努力来确定任何这种权利。关于IETF在标准轨道和标准相关文件中关于权利的程序的信息可以在BCP-11中找到。索赔权利的副本用于发布和任何保证可用的许可证,或尝试的结果获得的一般许可证或许可使用这种所有权的实现者或用户可以从IETF秘书处获得此规范。
IETF邀请任何有兴趣的一方提请其注意任何版权、专利或专利申请,或其他可能涉及实施本标准可能需要的技术的专有权利。请将资料寄给IETF执行主任。
这份文档是基于Stephen Casner和Colin Perkins主持的IETF音频/视频传输工作组内部的讨论。目前的协议起源于网络语音协议和分组视频协议(Danny Cohen和Randy Cole),以及由vat应用程序实现的协议(Van Jacobson和Steve McCanne)。Christian Huitema为随机标识符生成器提供了思路。Jonathan Rosenberg对该定时器重新考虑算法进行了广泛的分析和仿真。增加的分层编码是由Michael Speer和Steve McCanne指定的。
我们提供了RTP发送方和接收方算法方面的C代码示例。可能有其他实现方法在特定的操作环境中速度更快,或者具有其他优点。这些实现说明仅供参考,旨在阐明RTP规范。
下面的定义用于所有的例子;为了清晰和简洁,结构定义只对32位的大端(最重要的八位优先)架构有效。位域假定是按照大端位顺序紧密封装的,没有额外的填充。需要进行一些修改才能构造一个可移植的实现。
/*
* rtp.h -- RTP header file
*/
#include
/*
* The type definitions below are valid for 32-bit architectures and
* may have to be adjusted for 16- or 64-bit architectures.
*/
typedef unsigned char u_int8;
typedef unsigned short u_int16;
typedef unsigned int u_int32;
typedef short int16;
/*
* Current protocol version.
*/
#define RTP_VERSION 2
#define RTP_SEQ_MOD (1<<16)
#define RTP_MAX_SDES 255 /* maximum text length for SDES */
typedef enum {
RTCP_SR = 200,
RTCP_RR = 201,
RTCP_SDES = 202,
RTCP_BYE = 203,
RTCP_APP = 204
} rtcp_type_t;
typedef enum {
RTCP_SDES_END = 0,
RTCP_SDES_CNAME = 1,
RTCP_SDES_NAME = 2,
RTCP_SDES_EMAIL = 3,
RTCP_SDES_PHONE = 4,
RTCP_SDES_LOC = 5,
RTCP_SDES_TOOL = 6,
RTCP_SDES_NOTE = 7,
RTCP_SDES_PRIV = 8
} rtcp_sdes_type_t;
/*
* RTP data header
*/
typedef struct {
unsigned int version:2; /* protocol version */
unsigned int p:1; /* padding flag */
unsigned int x:1; /* header extension flag */
unsigned int cc:4; /* CSRC count */
unsigned int m:1; /* marker bit */
unsigned int pt:7; /* payload type */
unsigned int seq:16; /* sequence number */
u_int32 ts; /* timestamp */
u_int32 ssrc; /* synchronization source */
u_int32 csrc[1]; /* optional CSRC list */
} rtp_hdr_t;
/*
* RTCP common header word
*/
typedef struct {
unsigned int version:2; /* protocol version */
unsigned int p:1; /* padding flag */
unsigned int count:5; /* varies by packet type */
unsigned int pt:8; /* RTCP packet type */
u_int16 length; /* pkt len in words, w/o this word */
} rtcp_common_t;
/*
* Big-endian mask for version, padding bit and packet type pair
*/
#define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe)
#define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR)
/*
* Reception report block
*/
typedef struct {
u_int32 ssrc; /* data source being reported */
unsigned int fraction:8; /* fraction lost since last SR/RR */
int lost:24; /* cumul. no. pkts lost (signed!) */
u_int32 last_seq; /* extended last seq. no. received */
u_int32 jitter; /* interarrival jitter */
u_int32 lsr; /* last SR packet from this source */
u_int32 dlsr; /* delay since last SR packet */
} rtcp_rr_t;
/*
* SDES item
*/
typedef struct {
u_int8 type; /* type of item (rtcp_sdes_type_t) */
u_int8 length; /* length of item (in octets) */
char data[1]; /* text, not null-terminated */
} rtcp_sdes_item_t;
/*
* One RTCP packet
*/
typedef struct {
rtcp_common_t common; /* common header */
union {
/* sender report (SR) */
struct {
u_int32 ssrc; /* sender generating this report */
u_int32 ntp_sec; /* NTP timestamp */
u_int32 ntp_frac;
u_int32 rtp_ts; /* RTP timestamp */
u_int32 psent; /* packets sent */
u_int32 osent; /* octets sent */
rtcp_rr_t rr[1]; /* variable-length list */
} sr;
/* reception report (RR) */
struct {
u_int32 ssrc; /* receiver generating this report */
rtcp_rr_t rr[1]; /* variable-length list */
} rr;
/* source description (SDES) */
struct rtcp_sdes {
u_int32 src; /* first SSRC/CSRC */
rtcp_sdes_item_t item[1]; /* list of SDES items */
} sdes;
/* BYE */
struct {
u_int32 src[1]; /* list of sources */
/* can't express trailing text for reason */
} bye;
} r;
} rtcp_t;
typedef struct rtcp_sdes rtcp_sdes_t;
/*
* Per-source state information
*/
typedef struct {
u_int16 max_seq; /* highest seq. number seen */
u_int32 cycles; /* shifted count of seq. number cycles */
u_int32 base_seq; /* base seq number */
u_int32 bad_seq; /* last 'bad' seq number + 1 */
u_int32 probation; /* sequ. packets till source is valid */
u_int32 received; /* packets received */
u_int32 expected_prior; /* packet expected at last interval */
u_int32 received_prior; /* packet received at last interval */
u_int32 transit; /* relative trans time for prev pkt */
u_int32 jitter; /* estimated jitter */
/* ... */
} source;
RTP接收方应该检查传入数据包的RTP报头的有效性,因为它们可能是加密的,或者可能来自碰巧地址错误的不同应用程序。类似地,如果启用了根据第9节中描述的方法进行的加密,则需要进行报头有效性检查以验证传入数据包已被正确解密,尽管报头有效性检查的失败(例如,未知的有效负载类型)不一定表明解密失败。
只有弱有效性检查是可能的RTP数据包来自一个从未听到过的源:
最后三种检查有点复杂,而且不总是可能的,只留下前两种总共只有几个位。如果信息包中的SSRC标识符是以前接收到的,那么信息包可能是有效的,检查序列号是否在预期范围内可以提供进一步的验证。如果SSRC标识符以前没有出现过,那么携带该标识符的数据包可能被认为是无效的,直到其中一小部分带有连续序列号的数据包到达。如果产生的延迟是可接受的,那么这些无效数据包可能会被丢弃,或者在验证完成后被存储并交付。
下面所示的例程update_seq确保只有在顺序接收了MIN_SEQUENTIAL数据包之后,源才被声明为有效。它还验证新接收到的包的序列号seq,并更新结构中s所指向的包源的序列状态。
当第一次听到一个新的源时,也就是说,它的SSRC标识符不在表中(参见8.2节),并且为它分配了每个源的状态,s->probation被设置为在声明源有效(参数MIN_SEQUENTIAL)之前所需的顺序包数,并初始化其他变量
init_seq(s, seq); s->max_seq = seq - 1; s->probation = MIN_SEQUENTIAL;
一个非零的s->probation标志着源还没有有效,所以这个状态可能会在一个短超时后被丢弃,而不是在一个长超时后,如章节6.2.1所述。
在一个源被认为是有效的之后,如果序列号在s->max_seq之前不大于MAX_DROPOUT,在SSS之后不大于MAX_MISORDER,那么序列号就被认为是有效的。如果新序列号在对RTP序列号范围(16位)取模后的max_seq之前,但小于max_seq,则它已经被换行,并且(移位的)序列号周期的计数增加。返回值1表示有效的序列号。
否则,将返回值0,表示验证失败,并存储错误的序列号加1。如果接收到的下一个包带有下一个更高的序列号,它被认为是一个新包序列的有效开始,可能是由扩展退出或源重启引起的。由于可能会错过多个完整序列号循环,丢包统计信息将被重置。
显示了参数的典型值,基于在50包/秒下2秒的最大无序时间和1分钟的最大丢失。dropout参数MAX_DROPOUT应该是16位序列号空间的一小部分,以便提供一个合理的概率,即重新启动后的新序列号不会落在重新启动前序列号的可接受范围内。
void init_seq(source *s, u_int16 seq)
{
s->base_seq = seq;
s->max_seq = seq;
s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */
s->cycles = 0;
s->received = 0;
s->received_prior = 0;
s->expected_prior = 0;
/* other initialization */
}
int update_seq(source *s, u_int16 seq)
{
u_int16 udelta = seq - s->max_seq;
const int MAX_DROPOUT = 3000;
const int MAX_MISORDER = 100;
const int MIN_SEQUENTIAL = 2;
/*
* Source is not valid until MIN_SEQUENTIAL packets with
* sequential sequence numbers have been received.
*/
if (s->probation) {
/* packet is in sequence */
if (seq == s->max_seq + 1) {
s->probation--;
s->max_seq = seq;
if (s->probation == 0) {
init_seq(s, seq);
s->received++;
return 1;
}
} else {
s->probation = MIN_SEQUENTIAL - 1;
s->max_seq = seq;
}
return 0;
} else if (udelta < MAX_DROPOUT) {
/* in order, with permissible gap */
if (seq < s->max_seq) {
/*
* Sequence number wrapped - count another 64K cycle.
*/
s->cycles += RTP_SEQ_MOD;
}
s->max_seq = seq;
} else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) {
/* the sequence number made a very large jump */
if (seq == s->bad_seq) {
/*
* Two sequential packets -- assume that the other side
* restarted without telling us so just re-sync
* (i.e., pretend this was the first packet).
*/
init_seq(s, seq);
}
else {
s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1);
return 0;
}
} else {
/* duplicate or reordered packet */
}
s->received++;
return 1;
}
合法性检查可以更强,要求顺序中有两个以上的数据包。缺点是大量的初始包将被丢弃(或在队列中延迟),高的包丢失率可能会阻止验证。但是,由于RTCP报头验证比较强,如果一个源的RTCP报文比数据报文先收到,则可以调整计数,使顺序只需要两个报文。如果允许初始数据丢失几秒钟,应用程序可以选择丢弃来自某个源的所有数据包,直到从该源接收到有效的RTCP数据包。
根据应用程序和编码的不同,算法可能会利用关于有效负载格式的额外知识进行进一步的验证。对于所有数据包的时间戳增量相同的负载类型,可以使用序列号差值从从同一源收到的前一个数据包预测时间戳值(假设负载类型没有变化)。
一个强的“快速路径”检查是可能的,因为一个新收到的RTP数据包的头中的前四个字节很有可能与来自同一SSRC的前一个数据包相同,只是序列号增加了1。类似地,在通常一次从一个源接收数据的应用程序中,单条目缓存可以用于更快的SSRC查找。
以下检查应该应用于RTCP报文。
下面的代码片段执行所有这些检查。由于可能存在未知的数据包类型,因此不针对后续数据包检查数据包类型。
u_int32 len; /* length of compound RTCP packet in words */
rtcp_t *r; /* RTCP header */
rtcp_t *end; /* end of compound RTCP packet */
if ((*(u_int16 *)r & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
/* something wrong with packet format */
}
end = (rtcp_t *)((u_int32 *)r + len);
do r = (rtcp_t *)((u_int32 *)r + r->common.length + 1);
while (r < end && r->common.version == 2);
if (r != end) {
/* something wrong with packet format */
}
为了计算丢包率,需要知道从每个源预期和实际接收到的RTP包的数量,使用下面代码中通过指针引用的结构源中定义的每个源状态信息。接收到的包的数量只是包到达时的数量,包括任何延迟或重复的包。接收端可以通过接收到的最大序列号(s->max_seq)和接收到的第一个序列号(s->base_seq)之间的差值来计算期望的包数。由于序列号只有16位,并且会环绕,所以有必要使用(移位的)序列号环绕计数(s->cycles)来扩展最高的序列号。在附录A.1中,RTP头有效性检查例程维护接收的包数和循环数。
extended_max = s->cycles + s->max_seq; expected = extended_max - s->base_seq + 1;
丢失的包数定义为期望的包数减去实际收到的包数:
lost = expected - s->received;
由于这个有符号的数字以24位的形式携带,对于正损耗应该夹紧在0x7fffff,对于负损耗应该夹紧在0x800000,而不是缠绕在一起。
在上一个报告间隔期间(因为发送了前一个SR或RR包)丢失的包的比例是根据整个间隔期间预期和接收包计数的差异计算的,其中expected_prior和received_prior是生成前一个接收报告时保存的值:
expected_interval = expected - s->expected_prior; s->expected_prior = expected; received_interval = s->received - s->received_prior; s->received_prior = s->received; lost_interval = expected_interval - received_interval; if (expected_interval == 0 || lost_interval <= 0) fraction = 0; else fraction = (lost_interval << 8) / expected_interval;
所得到的分数是一个8位的定点数,其中二进制点位于左边。
这个函数将一个SDES块构建到缓冲区b中,由数组类型、值和长度中提供的argc项组成。它返回一个指向b中下一个可用位置的指针。
char *rtp_write_sdes(char *b, u_int32 src, int argc,
rtcp_sdes_type_t type[], char *value[],
int length[])
{
rtcp_sdes_t *s = (rtcp_sdes_t *)b;
rtcp_sdes_item_t *rsp;
int i;
int len;
int pad;
/* SSRC header */
s->src = src;
rsp = &s->item[0];
/* SDES items */
for (i = 0; i < argc; i++) {
rsp->type = type[i];
len = length[i];
if (len > RTP_MAX_SDES) {
/* invalid length, may want to take other action */
len = RTP_MAX_SDES;
}
rsp->length = len;
memcpy(rsp->data, value[i], len);
rsp = (rtcp_sdes_item_t *)&rsp->data[len];
}
/* terminate with end marker and pad to next 4-octet boundary */
len = ((char *) rsp) - b;
pad = 4 - (len & 0x3);
b = (char *) rsp;
while (pad--) *b++ = RTCP_SDES_END;
return b;
}
这个函数解析一个SDES包,调用函数find_member()来查找指向给定SSRC标识符的会话成员信息的指针,并调用函数member_sdes()来存储该成员的新SDES信息。这个函数需要一个指向RTCP报头的指针。
void rtp_read_sdes(rtcp_t *r)
{
int count = r->common.count;
rtcp_sdes_t *sd = &r->r.sdes;
rtcp_sdes_item_t *rsp, *rspn;
rtcp_sdes_item_t *end = (rtcp_sdes_item_t *)
((u_int32 *)r + r->common.length + 1);
source *s;
while (--count >= 0) {
rsp = &sd->item[0];
if (rsp >= end) break;
s = find_member(sd->src);
for (; rsp->type; rsp = rspn ) {
rspn = (rtcp_sdes_item_t *)((char*)rsp+rsp->length+2);
if (rspn >= end) {
rsp = rspn;
break;
}
member_sdes(s, rsp->type, rsp->data, rsp->length);
}
sd = (rtcp_sdes_t *)
((u_int32 *)sd + (((char *)rsp - (char *)sd) >> 2)+1);
}
if (count >= 0) {
/* invalid packet format */
}
}
下面的子例程使用RFC 1321[32]中发布的MD5例程生成一个随机的32位标识符。系统例程可能不会出现在所有的操作系统上,但它们应该作为可能使用的信息类型的提示。其他适当的系统调用包括
“直播”视频或音频样本也是一个很好的随机数来源,但必须小心避免使用关闭的麦克风或黑屏的摄像机作为来源[17]。
建议使用此或类似的例程为产生RTCP周期的随机数生成器生成初始种子(如附录a .7所示),以生成序列号和时间戳的初始值,并生成SSRC值。由于这个例程可能是cpu密集型的,因此直接使用它来生成RTCP周期是不合适的,因为可预测性不是问题。注意,除非为type参数提供了不同的值,否则这个例程对重复调用产生相同的结果,直到系统时钟的值发生变化。
/*
* Generate a random 32-bit quantity.
*/
#include /* u_long */
#include /* gettimeofday() */
#include /* get..() */
#include /* printf() */
#include /* clock() */
#include /* uname() */
#include "global.h" /* from RFC 1321 */
#include "md5.h" /* from RFC 1321 */
#define MD_CTX MD5_CTX
#define MDInit MD5Init
#define MDUpdate MD5Update
#define MDFinal MD5Final
static u_long md_32(char *string, int length)
{
MD_CTX context;
union {
char c[16];
u_long x[4];
} digest;
u_long r;
int i;
MDInit (&context);
MDUpdate (&context, string, length);
MDFinal ((unsigned char *)&digest, &context);
r = 0;
for (i = 0; i < 3; i++) {
r ^= digest.x[i];
}
return r;
} /* md_32 */
/*
* Return random unsigned 32-bit quantity. Use 'type' argument if
* you need to generate several different values in close succession.
*/
u_int32 random32(int type)
{
struct {
int type;
struct timeval tv;
clock_t cpu;
pid_t pid;
u_long hid;
uid_t uid;
gid_t gid;
struct utsname name;
} s;
gettimeofday(&s.tv, 0);
uname(&s.name);
s.type = type;
s.cpu = clock();
s.pid = getpid();
s.hid = gethostid();
s.uid = getuid();
s.gid = getgid();
/* also: system uptime */
return md_32((char *)&s, sizeof(s));
} /* random32 */
以下功能实现了6.2节介绍的RTCP传输和接收规则。这些规则在几个函数中编码
OnExpire()和OnReceive()都有事件e作为参数。这是该参与者的下一个预定事件,可以是RTCP报告,也可以是BYE包。假设以下函数可用:
这些函数必须进行扩展,以便实现允许将发送方和非发送方的RTCP带宽分数指定为显式参数,而不是指定为25%和75%的固定值。如果其中一个参数为零,则rtcp_interval()的扩展实现将需要避免除零。
double rtcp_interval(int members,
int senders,
double rtcp_bw,
int we_sent,
double avg_rtcp_size,
int initial)
{
/*
* Minimum average time between RTCP packets from this site (in
* seconds). This time prevents the reports from `clumping' when
* sessions are small and the law of large numbers isn't helping
* to smooth out the traffic. It also keeps the report interval
* from becoming ridiculously small during transient outages like
* a network partition.
*/
double const RTCP_MIN_TIME = 5.;
/*
* Fraction of the RTCP bandwidth to be shared among active
* senders. (This fraction was chosen so that in a typical
* session with one or two active senders, the computed report
* time would be roughly equal to the minimum report time so that
* we don't unnecessarily slow down receiver reports.) The
* receiver fraction must be 1 - the sender fraction.
*/
double const RTCP_SENDER_BW_FRACTION = 0.25;
double const RTCP_RCVR_BW_FRACTION = (1-RTCP_SENDER_BW_FRACTION);
/*
/* To compensate for "timer reconsideration" converging to a
* value below the intended average.
*/
double const COMPENSATION = 2.71828 - 1.5;
double t; /* interval */
double rtcp_min_time = RTCP_MIN_TIME;
int n; /* no. of members for computation */
/*
* Very first call at application start-up uses half the min
* delay for quicker notification while still allowing some time
* before reporting for randomization and to learn about other
* sources so the report interval will converge to the correct
* interval more quickly.
*/
if (initial) {
rtcp_min_time /= 2;
}
/*
* Dedicate a fraction of the RTCP bandwidth to senders unless
* the number of senders is large enough that their share is
* more than that fraction.
*/
n = members;
if (senders <= members * RTCP_SENDER_BW_FRACTION) {
if (we_sent) {
rtcp_bw *= RTCP_SENDER_BW_FRACTION;
n = senders;
} else {
rtcp_bw *= RTCP_RCVR_BW_FRACTION;
n -= senders;
}
}
/*
* The effective number of sites times the average packet size is
* the total number of octets sent when each site sends a report.
* Dividing this by the effective bandwidth gives the time
* interval over which those packets must be sent in order to
* meet the bandwidth target, with a minimum enforced. In that
* time interval we send one report so this time is also our
* average time between reports.
*/
t = avg_rtcp_size * n / rtcp_bw;
if (t < rtcp_min_time) t = rtcp_min_time;
/*
* To avoid traffic bursts from unintended synchronization with
* other sites, we then pick our actual next report interval as a
* random number uniformly distributed between 0.5*t and 1.5*t.
*/
t = t * (drand48() + 0.5);
t = t / COMPENSATION;
return t;
}
void OnExpire(event e,
int members,
int senders,
double rtcp_bw,
int we_sent,
double *avg_rtcp_size,
int *initial,
time_tp tc,
time_tp *tp,
int *pmembers)
{
/* This function is responsible for deciding whether to send an
* RTCP report or BYE packet now, or to reschedule transmission.
* It is also responsible for updating the pmembers, initial, tp,
* and avg_rtcp_size state variables. This function should be
* called upon expiration of the event timer used by Schedule().
*/
double t; /* Interval */
double tn; /* Next transmit time */
/* In the case of a BYE, we use "timer reconsideration" to
* reschedule the transmission of the BYE if necessary */
if (TypeOfEvent(e) == EVENT_BYE) {
t = rtcp_interval(members,
senders,
rtcp_bw,
we_sent,
*avg_rtcp_size,
*initial);
tn = *tp + t;
if (tn <= tc) {
SendBYEPacket(e);
exit(1);
} else {
Schedule(tn, e);
}
} else if (TypeOfEvent(e) == EVENT_REPORT) {
t = rtcp_interval(members,
senders,
rtcp_bw,
we_sent,
*avg_rtcp_size,
*initial);
tn = *tp + t;
if (tn <= tc) {
SendRTCPReport(e);
*avg_rtcp_size = (1./16.)*SentPacketSize(e) +
(15./16.)*(*avg_rtcp_size);
*tp = tc;
/* We must redraw the interval. Don't reuse the
one computed above, since its not actually
distributed the same, as we are conditioned
on it being small enough to cause a packet to
be sent */
t = rtcp_interval(members,
senders,
rtcp_bw,
we_sent,
*avg_rtcp_size,
*initial);
Schedule(t+tc,e);
*initial = 0;
} else {
Schedule(tn, e);
}
*pmembers = members;
}
}
void OnReceive(packet p,
event e,
int *members,
int *pmembers,
int *senders,
double *avg_rtcp_size,
double *tp,
double tc,
double tn)
{
/* What we do depends on whether we have left the group, and are
* waiting to send a BYE (TypeOfEvent(e) == EVENT_BYE) or an RTCP
* report. p represents the packet that was just received. */
if (PacketType(p) == PACKET_RTCP_REPORT) {
if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
AddMember(p);
*members += 1;
}
*avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) +
(15./16.)*(*avg_rtcp_size);
} else if (PacketType(p) == PACKET_RTP) {
if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
AddMember(p);
*members += 1;
}
if (NewSender(p) && (TypeOfEvent(e) == EVENT_REPORT)) {
AddSender(p);
*senders += 1;
}
} else if (PacketType(p) == PACKET_BYE) {
*avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) +
(15./16.)*(*avg_rtcp_size);
if (TypeOfEvent(e) == EVENT_REPORT) {
if (NewSender(p) == FALSE) {
RemoveSender(p);
*senders -= 1;
}
if (NewMember(p) == FALSE) {
RemoveMember(p);
*members -= 1;
}
if (*members < *pmembers) {
tn = tc +
(((double) *members)/(*pmembers))*(tn - tc);
*tp = tc -
(((double) *members)/(*pmembers))*(tc - *tp);
/* Reschedule the next report for time tn */
Reschedule(tn, e);
*pmembers = *members;
}
} else if (TypeOfEvent(e) == EVENT_BYE) {
*members += 1;
}
}
}
下面的代码片段实现了第6.4.1节给出的算法,用于计算要插入到接收报告的到达间抖动字段中的RTP数据到达间时间的统计方差的估计值。输入是r->ts(来自传入数据包的时间戳)和arrival(相同单位的当前时间)。这里的点状态为源;其中,s->transit为前一个报文的相对传输时间,s->jitter为估计抖动值。接收报告的抖动字段以时间戳单位度量,并表示为无符号整数,但抖动估计保持为浮点数。当每个数据包到达时,抖动估计被更新
int transit = arrival - r->ts; int d = transit - s->transit; s->transit = transit; if (d < 0) d = -d; s->jitter += (1./16.) * ((double)d - s->jitter);
当为该成员生成接收报告块(rr点所在的块)时,将返回当前抖动估计:
rr->jitter = (u_int32) s->jitter;
或者,抖动估计可以保持为整数,但缩放以减少舍入误差。除了最后一行之外,计算过程是相同的:
s->jitter += d - ((s->jitter + 8) >> 4);
在这种情况下,接收报告的估计抽样为:
rr->jitter = s->jitter >> 4;
这个RFC的大部分与RFC 1889是相同的。网络上的包格式没有变化,只有控制如何使用协议的规则和算法的变化。最大的变化是对计算何时发送RTCP数据包的可伸缩计时器算法的增强:
需要注意的是,只有当会话参与者的数量很大(数千)并且大多数参与者同时加入或离开时,这些增强才会产生显著的效果。这使得在现有网络中进行测试变得困难。但是,本文对该算法进行了深入的分析和仿真,以验证其性能。此外,该增强算法被设计为与RFC 1889中的算法进行互操作,以便在步进连接期间减少多余RTCP带宽的程度与实现该增强算法的参与者的比例成正比。两种算法的互操作已在实际网络上进行了实验验证。
其他功能上的变化包括:
非功能性的变化: