RTP:一种实时应用的传输协议 (RFC-3550)

RFC文档链接

摘要

本文描述了实时传输协议RTP。RTP提供端到端网络传输功能,适用于通过多播或单播网络服务传输实时数据(如音频、视频或模拟数据)的应用程序。RTP不能解决资源预留问题,也不能保证实时服务的服务质量。数据传输通过控制协议(RTCP)进行扩展,以允许以可扩展到大型多播网络的方式监控数据传输,并提供最小的控制和识别功能。RTP和RTCP设计为独立于底层传输层和网络层。该协议支持使用RTP级转换器和混频器。

本文中的大部分文本与已废弃的 RFC 1889 相同。 线路上的数据包格式没有变化,只是改变了控制协议使用方式的规则和算法。 最大的变化是对可扩展定时器算法的增强,用于计算何时发送 RTCP 数据包,以便在许多参与者同时加入会话时最大限度地减少超出预期速率的传输。

1、简介

本文规定了实时传输协议(RTP),该协议为具有实时特性的数据(如交互式音频和视频)提供端到端交付服务。这些服务包括有效载荷类型识别、序列编号、时间戳和交付监控。应用程序通常在UDP之上运行RTP,以利用其多路复用和校验和服务;这两个协议都提供了传输协议功能的一部分。然而,RTP可与其他合适的底层网络或传输协议一起使用(见第10节)。如果底层网络提供,RTP支持使用多播分发将数据传输到多个目的地。

请注意,RTP本身不提供任何机制来确保及时交付或提供其他服务质量保证,而是依赖较低层的服务来实现。它不保证交付或防止无序交付,也不认为底层网络是可靠的并按顺序交付数据包。RTP中包括的序列号允许接收机重构发送方的分组序列,但序列号也可用于确定分组的正确位置,例如在视频解码中,而不必按顺序解码分组。

虽然RTP主要是为了满足多参与者多媒体会议的需求而设计的,但它并不局限于特定的应用。连续数据存储、交互式分布式仿真、主动徽章以及控制和测量应用程序也可能适用RTP。

本文件定义了RTP,由两个紧密相连的部分组成:

  • 实时传输协议(real-time transport protocol,RTP),用于传输具有实时属性的数据。
  • RTP控制协议(RTCP),用于监控服务质量,并在正在进行的会话中传递有关参与者的信息。RTCP的后一个方面可能足以用于“松散控制”会话,即没有明确的成员控制和设置,但它不一定旨在支持应用程序的所有控制通信需求。此功能可能完全或部分包含在单独的会话控制协议中,这超出了本文档的范围。

RTP代表了一种新型的协议,它遵循了Clark和Tennenhouse[10]提出的应用层成帧和集成层处理的原则。也就是说,RTP旨在提供特定应用程序所需的信息,并且通常会集成到应用程序处理中,而不是作为一个单独的层来实现。RTP是一个不完整的协议框架。本文件规定了RTP适用于所有应用程序的通用功能。与传统协议不同,在传统协议中,可以通过使协议更通用或添加需要解析的选项机制来容纳附加功能,RTP旨在根据需要通过修改和/或添加报头来定制。第5.3节和第6.4.3节给出了示例。

因此,除本文件外,特定应用的完整RTP规范还需要一份或多份配套文件(见第13节):

  • 配置文件规范文档,定义一组有效负载类型代码及其到有效负载格式的映射(例如,媒体编码)。配置文件还可以定义特定于特定应用程序类别的RTP扩展或修改。通常,一个应用程序只能在一个配置文件下运行。音频和视频数据的配置文件可在配套的RFC 3551 附录[1]中找到。
  • 有效载荷格式规范文档,定义了在RTP中如何携带特定的有效载荷,如音频或视频编码。

关于实时服务及其实现算法的讨论,以及一些RTP设计决策的背景讨论,见 附录[11]。

1.1、术语

略。

2、RTP使用场景

以下各节介绍RTP使用的一些方面。选择这些示例是为了说明使用RTP的应用程序的基本操作,而不是限制RTP的用途。在这些示例中,RTP是在IP和UDP之上进行的,并遵循配套RFC 3551中指定的音频和视频配置文件建立的约定。

2.1、简单多播音频会议

IETF的一个工作组开会讨论最新的协议文件,使用互联网的IP多播服务进行语音通信。通过某种分配机制,工作组主席获得一个多播组地址和一对端口。一个端口用于音频数据,另一个用于控制(RTCP)数据包。此地址和端口信息将分发给预期的参与者。如果需要隐私,可以按照第9.1节的规定对数据包和控制包进行加密,在这种情况下,还必须生成并分发加密密钥。这些分配和分配机制的具体细节超出了RTP的范围。

每个会议参与者使用的音频会议应用程序都会发送持续时间为20ms的小块音频数据。每个音频数据块前面都有一个RTP头;RTP报头和数据依次包含在UDP数据包中。RTP报头指示每个数据包中包含何种类型的音频编码(例如PCM、ADPCM或LPC),以便发送方可以在会议期间更改编码,例如,容纳通过低带宽链路连接的新参与者,或对网络拥塞的指示作出反应。

与其他分组网络一样,互联网偶尔会丢失和重新排序分组,并将其延迟不同的时间。为了应对这些损伤,RTP报头包含定时信息和序列号,该序列号允许接收机重构源产生的定时,因此在本例中,扬声器每隔20毫秒连续播放一次音频块。会议中的每个RTP数据包源都会分别进行定时重建。接收机还可以使用序列号来估计丢失了多少数据包。

由于工作组成员在会议期间加入和离开,因此了解谁在任何时候参加会议以及他们接收音频数据的情况非常有用。为此,会议中音频应用程序的每个实例定期在RTCP(控制)端口上多播一个接收报告及其用户名称。接收报告指示当前扬声器的接收情况,并可用于控制自适应编码。除用户名外,还可以包括其他识别信息,但需遵守控制带宽限制。站点在离开会议时发送RTCP字节数据包(第6.6节)。

2.2、音频和视频会议

如果在一个会议中同时使用音频和视频媒体,它们将作为单独的RTP会话传输。也就是说,每个媒体使用两个不同的UDP端口对或组播地址传输单独的RTP和RTCP数据包。在RTP级别上,音频和视频会话之间没有直接耦合,除了参与两个会话的用户应该在两个会话的RTCP包中使用相同的可区分的(规范的)名称,以便会话可以关联。

这种分离的一个动机是允许一些与会者在他们选择的情况下只接收一种媒体。进一步的解释见第5.2节。尽管分离,一个源的音频和视频的同步播放可以使用在两个会话的RTCP包中携带的时间信息来实现。

2.3、混频器和转换器

到目前为止,我们假设所有站点都希望接收相同格式的媒体数据。然而,这可能并不总是合适的。考虑这样一种情况,一个区域的与会者通过低速链接连接到大多数享受高速网络访问的会议与会者。与其强迫每个人使用低带宽、低质量的音频编码,不如在低带宽区域附近放置一个称为混频器的rtp级中继。该混频器重新同步传入的音频包,以重建由发送方产生的恒定20ms间隔,将这些重建的音频流混合成单个流,将音频编码转换为低带宽的编码,并通过低速链路转发低带宽的包流。这些包可以向单个收件人进行单播,也可以在不同地址向多个收件人进行多播。该RTP报头包括一种用于混频器的方法,用于识别构成混合包的源,以便能够在接收端提供正确的通话者指示。

音频会议的一些预期参与者可能连接到高带宽链路,但可能无法通过IP多播直接到达。例如,它们可能位于不允许任何IP包通过的应用程序级防火墙之后。对于这些站点,混合可能没有必要,在这种情况下,可以使用另一种称为转换器的rtp级中继。可以在防火墙两侧分别安装一个转换器,外侧转换器将所有多播包通过安全连接转入内侧转换器,内侧转换器再转发给内部网的一个多播组。

混频器和转换器可以设计成用于各种目的。比如,一个视频混频器在测量多个不同视频流中各人的单独影像后,将它们组合成一个单一视频流来模拟群组场景。又如,在只用IP/UDP和只用ST_II的两个主机群之间通过转换建立连接。再如,在没有重新同步或混频时,用逐包的编码转换来自各个独立源的视频流。混频器和转换器的操作细节见章节7。

2.4、分层编码

多媒体应用程序应该能够调整传输速率,以匹配接收器的容量或适应网络拥塞。许多实现将速率适应性的责任放在源上。由于不同接收器对带宽要求的冲突,这种方法在多播传输中不能很好地工作。这经常导致最小公分母的场景,网格中最小的管道支配了全部实况多媒体“广播”的质量和保真度。

相反地,可以把分层编码和分层传输系统组合起来,从而把调适速率的责任放在接收端。在IP多播之上的RTP上下文中,对一个横跨多个RTP会话(每个会话在独自多播组上开展)的分级表示信号(a hierarchically represented signal),源能够把它的分层(layers)分割成条。 接收方仅需合并适当的多播组子集,就能适应异种网络和控制接收带宽。

使用分层编码的RTP的详细信息在第6.3.9、8.3和11节中给出。

3、定义

RTP负载(RTP payload):在包中通过RTP传输的数据,例如音频样本或压缩的视频数据。有效载荷格式和解释不在本文档的范围。

RTP包(RTP packet):由固定的RTP报头、可能为空的贡献源(contributing sources)列表(见下文)和有效负载数据组成的数据包。一些底层协议可能需要定义RTP包的封装。通常,底层协议的一个包包含一个RTP包,但如果封装方法允许,可能包含几个RTP包(见第11节)。

RTCP包(RTCP packet):一种控制包,由类似于RTP数据包的固定头部分组成,后面跟着根据RTCP数据包类型不同而变化的结构化元素。格式在第6节中定义。通常,多个RTCP数据包被作为一个复合RTCP数据包发送到底层协议的单个数据包中;这是由每个RTCP报文的固定报头中的长度字段启用的。

端口(Port):“传输协议用来区分给定主机内的多个目的地的抽象。TCP/IP协议使用小的正整数标识端口。” 附录[12] OSI传输层使用的传输选择器(TSEL)相当于端口。RTP依赖于底层协议提供一些机制,如端口来复用会话中的RTP和RTCP数据包。

传输地址(Transport address):标识传输级端点的网络地址和端口的组合,例如IP地址和UDP端口。报文从一个源传输地址传输到一个目的传输地址。

RTP媒体类型(RTP media type):RTP媒体类型是可以在单个RTP会话中携带的有效负载类型的集合。RTP配置文件将RTP媒体类型分配给RTP有效负载类型。

多媒体会话(Multimedia session):一组共同的参与者之间的并发RTP会话。例如,一个视频会议(多媒体会话)可能包含一个音频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包的同步源标识完好无损的情况下转发RTP包。转换器的例子,像不混合编码的转换设备、从多播到单播的复制器以及防火墙中的应用程序级过滤器。

监视器(Monitor):一个应用程序,接收RTP会话中参与者发送的RTCP报文,特别是接收报告,并评估当前服务质量,用于分布监控、故障诊断和长期统计。监视功能可能内置在参与会话的应用程序中,但也可能是一个单独的应用程序,该应用程序不参与会话,也不发送或接收RTP数据包(因为它们在单独的端口上)。这些被称为第三方监控器。第三方监视器也可以接收RTP数据包,但不发送RTCP数据包,或者在会话中计数。

非RTP方式(Non-RTP means):除了RTP之外,可能还需要协议和机制来提供可用的服务。特别是,对于多媒体会议,控制协议可以分发多播地址和加密密钥,协商要使用的加密算法,并定义RTP有效载荷类型值和它们表示的有效载荷格式之间的动态映射,这些格式没有预定义的有效载荷类型值。此类协议的例子包括会话发起协议(SIP) (RFC 3261 附录[13])、ITU Recommendation H.323 附录[14]和使用SDP的应用程序(RFC 2327 附录[15]),如RTSP (RFC 2326 附录[16])。对于简单的应用程序,还可以使用电子邮件或会议数据库。这些协议和机制的规范不在本文档的范围。

4、字节序、对齐方式和时间格式

所有整数字段都按照网络字节顺序,也就是说,最重要的字节(octet)在前面。这种字节顺序通常被称为大端顺序。传输顺序在 附录[3]中详细描述。除非另有说明,数字常量都是十进制的(以10为基数)。

所有报头数据都按其自然长度对齐,即,16位字段按偶数偏移量对齐,32位字段按可被4整除的偏移量对齐,等等。指定为填充的字节的值为零。

Wallclock时间(绝对日期和时间)使用网络时间协议(NTP)的时间戳格式表示,它以秒为单位,相对于UTC在1900年1月1日的0h 附录[4]。完整分辨率的NTP时间戳是一个64位的无符号定点数,整数部分在前32位,小数部分在后32位。在某些领域,更紧凑的表示是合适的,只使用中间的32位;也就是整数部分的低16位和小数部分的高16位。整数部分的高16位必须独立确定。

为了使用RTP,实现并不需要运行网络时间协议。可以使用其他时间源,或者根本没有时间源(请参见第6.4.1节中NTP时间戳字段的描述)。然而,运行NTP可能对同步从不同主机传输的流有用。

NTP时间戳将在2036年的某个时间结束为零,但对于RTP来说,只使用对NTP时间戳之间的差异。只要这对时间戳可以假定彼此的时间间隔在68年以内,那么使用模块化算法进行减法和比较就会使概括变得不相关。

5、RTP数据传输协议

5.1、RTP固定头字段

RTP报头的格式如下:

RTP:一种实时应用的传输协议 (RFC-3550)_第1张图片

前12个字节出现在每个RTP包中,而CSRC列表只有在由混合器插入时才出现。字段的含义如下 :

版本号(version (V)):2 bits

该字段标识RTP版本。本规范定义的版本是2。(RTP的第一个草案版本使用1,在“vat”音频工具中最初实现的协议使用0。)

填充(padding (P)):1 bit

如果设置了填充位,则包在末尾包含一个或多个额外的填充字节,这些字节不属于负载的一部分。填充的最后八位元包含了应该忽略多少填充八位元的计数,包括它本身。一些具有固定块大小的加密算法或在底层协议数据单元中携带多个RTP包可能需要填充。

扩展(extension (X)):1 bit

如果设置了扩展位,固定报头后面必须紧跟一个报头扩展,格式见章节5.3.1。

CSRC计数(CSRC count (CC)):4 bits

CSRC计数包含固定头后面的CSRC的数量。

标记(marker (M)):1 bit

标记的解释由概要文件(profile)定义。它的目的是允许重要的事件被标记在比特流中,如帧边界。一个概要文件可以定义额外的标记位,或者通过改变负载类型字段中的位数来指定不存在标记位(见章节5.3)。

负载类型(payload type (PT)):7 bits

该字段标识RTP有效负载的格式,并决定应用程序对其的解释。一个概要文件(profile)可以指定负载类型代码到负载格式的默认静态映射。额外的有效负载类型代码可以通过非rtp方式动态定义(参见第3节)。音频和视频的一组默认映射在RFC 3551 附录[1]中指定。RTP源可以在会话期间改变有效负载类型,但是这个字段不应该用于多路复用单独的媒体流(见章节5.2)。

接收方必须忽略其不能理解的有效载荷类型的数据包。

序列号(sequence number):16 bits

每发送一个RTP数据包,序列号加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 time派生的虚拟表示时间轴来确定何时应该展示存储数据中的每个媒体的下一帧或其他单元。在这种情况下,RTP时间戳将反映每个单元的呈现时间。也就是说,每个单元的RTP时间戳将与wallclock时间相关,此时该单元成为虚拟表示时间线上的当前值。实际呈现发生在接收方确定的一段时间之后。

以预先录制的视频插播实时音频旁白为例,说明了选择采样时刻作为参考点的意义。在这种情况下,视频将在本地呈现,供叙述者观看,并同时使用RTP传输。在RTP中传输的视频帧的“采样时间”将通过引用其时间戳来建立,该时间戳是指视频帧呈现给旁白者时的wallclock时间。包含叙述者语音的音频RTP包的采样时间将通过引用采样音频时相同的wallclock时间来建立。如果两台主机上的参考时钟通过一些方法(如NTP)同步,那么音频和视频甚至可以由不同的主机传输。接收端可以使用RTCP SR包中的时间戳对关联音频和视频包的RTP时间戳,从而同步音频和视频包的呈现。

同步源(SSRC):32 bits

SSRC字段用来标识同步源。这个标识符应该随机选择,目的是在同一个RTP会话中没有两个同步源具有相同的SSRC标识符。附录A.6给出了一个生成随机标识符的示例算法。尽管多个源选择相同标识符的概率很低,但所有RTP实现都必须准备好检测和解决冲突。第8节描述了冲突的概率,以及一种解决冲突和基于SSRC标识符唯一性检测rtp级别转发环路的机制。如果一个源改变了它的源传输地址,它也必须选择一个新的SSRC标识符,以避免被解释为一个循环源(见章节8.2)。

参与源列表(CSRC list):0 到 15 个, 每个32 bits

CSRC列表标识了此数据包中包含的负载的贡献源。标识符的数量由CC字段给出。如果有超过15个贡献源,则只能确定15个。CSRC标识符由mixers插入(见7.1节),使用参与源的SSRC标识符。例如,对于音频包,将所有源的SSRC标识符混合在一起创建一个包,从而允许接收端给出正确的说话者指示。

5.2、多路复用RTP会话

为了实现高效的协议处理,应尽量减少复用点的数量,如集成层处理设计原则 附录[10]所述。在RTP中,多路复用是由每个RTP会话不同的目的传输地址(网络地址和端口号)提供的。例如,在一个由音频和视频媒体分别编码的电话会议中,每个媒体应该在一个单独的RTP会话中携带它自己的目的传输地址。

独立的音频和视频流不应该在单个RTP会话中进行,并根据负载类型或SSRC字段进行解复用。交错使用不同RTP媒体类型但相同的SSRC的包会带来几个问题:

  1. 例如,如果两个音频流共享相同的RTP会话和相同的SSRC值,其中一个要更改编码,从而获得不同的RTP有效负载类型,将没有通用的方法来识别哪个流更改了编码。
  2. SSRC被定义为标识单个时间和序列号空间。如果媒体时钟速率不同,交叉使用多个有效载荷类型将需要不同的时间空间,并且需要不同的序列号空间来告诉哪个有效载荷类型遭受包丢失。
  3. RTCP发送方和接收方报告(见章节6.4)只能为每个SSRC描述一个时间和序列号空间,并且不携带有效负载类型字段。
  4. RTP混频器将无法将不兼容媒体的交织流组合成一个流。
  5. 在一个RTP会话中承载多个媒体会排除:使用不同的网络路径或网络资源分配(如果合适);如果需要,接收媒体的子集,例如,如果视频将超过可用带宽,则仅接收音频;以及对不同媒体使用单独进程的接收器实现,而使用单独的RTP会话允许单个或多个进程实现。

为每个媒体使用不同的SSRC,但在相同的RTP会话中发送它们将避免前三个问题,但不能避免后两个问题。

另一方面,使用不同的SSRC值在一个RTP会话中复用同一媒体的多个相关源是组播会话的规范。

上面列出的问题并不适用:例如,RTP混音器可以组合多个音频源,而相同的处理方法适用于所有的音频源。在最后两个问题不适用的其他场景中,它也适用于使用不同SSRC值的相同媒体的多路流。

5.3、对RTP报头的特定于配置文件的修改

对于RTP可能支持的所有应用程序类所需的共同功能集,现有的RTP数据包头被认为是完整的。然而,为了与ALF设计原则保持一致,可以通过在概要文件规范中定义的修改或添加来裁剪头部,同时仍然允许与概要文件独立的监视和记录工具发挥作用。

  • 标记位和有效负载类型字段携带特定于配置文件的信息,但它们是在固定头中分配的,因为许多应用程序预计需要它们,否则可能需要添加另一个32位字来保存它们。包含这些字段的八位字节可以通过配置文件重新定义,以适应不同的要求,例如使用更多或更少的标记位。如果有任何标记位,一个应该位于八位字节的最高有效位,因为与配置文件独立的监视器可能能够观察到丢包模式和标记位之间的相关性。
  • 特定有效载荷格式所需的附加信息,如视频编码,应在包的有效载荷部分中携带。这可能始终存在于有效负载部分开始处,也可能由数据模式中的保留值表示。
  • 如果特定类型的应用程序需要独立于负载格式的附加功能,这些应用程序操作的配置文件应该定义附加的固定字段,紧跟在现有固定头的SSRC字段之后。这些应用程序将能够快速和直接访问附加字段,而独立于配置文件的监视器或记录器仍然可以通过解释前12个字节来处理RTP包。

如果发现所有概要文件都需要通用的附加功能,那么应该定义一个新版本的RTP来对固定头进行永久更改。

5.3.1、RTP报头扩展

RTP提供了一种扩展机制,允许在RTP数据包头中传输与有效载荷格式无关的功能的附加信息。这种机制的设计是为了使报头扩展可以被其他没有被扩展的互操作实现忽略。

请注意,这个报头扩展仅用于有限的使用。这种机制的大多数潜在用途最好是通过另一种方式实现,即使用前一节中描述的方法。例如,特定于配置文件的固定头扩展的处理成本更低,因为它既不是有条件的,也不是在可变位置。特定有效载荷格式所需的附加信息不应该使用这个报头扩展,但应该在数据包的有效载荷部分携带。

RTP:一种实时应用的传输协议 (RFC-3550)_第2张图片

如果RTP报头中的X位是1,则必须在RTP报头中追加一个变长报头扩展,如果存在,紧跟在CSRC列表之后。报头扩展包含一个16位长度的字段,计算扩展中32比特位字的数量,不包括四个字节的扩展报头(因此0是有效长度)。RTP数据头之后只能有一个扩展头。为了允许使用不同的报头扩展进行多个互操作实现,或者允许一个特定的实现使用多种类型的报头扩展,扩展头的前16位是用于区分标识符或参数。这16位的格式是由实现操作所依据的概要规范定义的。这个RTP规范本身没有定义任何报头扩展。

6、RTP控制协议——RTCP

RTP控制协议RTCP (RTP control protocol)采用与数据报文相同的分发机制,定期向会话中的所有参与者发送控制报文。底层协议必须提供数据和控制数据包的多路复用,例如使用UDP单独的端口号。RTCP执行四种功能:

  1. 其主要功能是对数据分发质量提供反馈。这是RTP作为传输协议角色的一个组成部分,并且与其他传输协议的流量和拥塞控制功能相关(见第10节关于拥塞控制的要求)。反馈可能直接用于自适应编码的控制 附录[18,19],但IP组播实验表明,从接收端获得反馈来诊断分发中的故障也是至关重要的。向所有参与者发送接收反馈报告可以让观察问题的人评估这些问题是局部的还是全局的。有了像IP多播这样的分发机制,像网络服务提供商这样不参与会话的实体也可以接收反馈信息,并充当第三方监控器来诊断网络问题。这个反馈功能是由RTCP发送方和接收方报告执行的,下面将在6.4节中描述。
  2. RTCP为RTP源携带一个称为规范名或CNAME的持续传输级标识符,第6.5.1节。由于SSRC标识符可能会在冲突被发现或程序重新启动时改变,接收者需要CNAME来跟踪每个参与者。接收器也可能需要CNAME来关联来自一组相关RTP会话中给定参与者的多个数据流,例如同步音频和视频。跨媒体同步还需要数据发送者在RTCP数据包中包含的NTP和RTP时间戳。
  3. 前两个功能要求所有的参与者发送RTCP包,因此必须控制速率,以便RTP扩展到大量的参与者。通过让每个参与者将自己的控制包发送给所有其他参与者,每个参与者都可以独立地观察参与者的数量。这个数字用于计算数据包发送的速率,如6.2节所解释的那样。
  4. 第四,可选功能是传递最小的会话控制信息,例如在用户界面中显示的参与者标识。这在“松散控制”会话中最有用,在这种会话中,参与者进入和离开时没有成员控制或参数协商。RTCP作为一种方便的通道,可以到达所有的参与者,但它不一定要支持应用程序的所有控制通信需求。可能需要更高级别的会话控制协议,这不在本文档的范围。

功能1-3应该在所有环境中使用,特别是在IP多播环境中。RTP应用程序设计者应该避免那些只能在单播模式下工作并且不能扩展到更大数量的机制。RTCP的传输可以对发送方和接收方分别进行控制,如第6.2节所述,用于接收方不可能反馈的单向链路。

非规范性注释:在称为源特定组播(SSM)的组播路由方法中,每个“通道”(一个源地址、组地址对)只有一个发送方,而接收者(通道源除外)不能使用组播直接与其他通道成员通信。这里的建议仅通过章节6.2中完全关闭接收器RTCP的选项来适应SSM。未来的工作将指定RTCP对SSM的适应,以便能够维护来自接收者的反馈。

6.1、RTCP报文格式

该规范定义了几种RTCP报文类型来承载各种控制信息:

SR:发送者报告,用于来自当前发送者的参与者的传输和接收统计。

RR:接收方报告,用于来自非当前发送方的参与者的接收统计数据,并结合SR用于报告31个以上源的活动发送方。

SDES:源描述项,包括CNAME。

BYE:表示参与结束。

APP:特定于应用程序的功能。

每个RTCP报文都以一个固定的部分开始,类似于RTP数据报文的部分,然后是结构化的元素,根据报文类型可以是可变长度的,但必须在32位边界上结束。对齐要求和每个数据包的固定部分包含的长度字段,使RTCP数据包“可堆叠”。多个RTCP包可以连接在一起,不需要任何分隔符,形成一个复合的RTCP包,在底层协议中发送,例如UDP。在复合包中没有明确的单个RTCP包的计数,因为底层协议期望提供一个总长度来确定复合包的结束。

复合报文中的每一个单独的RTCP报文都可以独立处理,对报文的顺序或组合没有任何要求。但是,为了执行协议的功能,需要施加以下约束:

  • 接收统计信息(SR或RR)在带宽允许下应该尽可能频繁地发送,以最大限度地提高统计信息的分辨率,因此每个周期性传输的复合RTCP包必须包括一个报告包。
  • 新的接收者需要尽快收到源的CNAME来识别源,并开始关联媒体已实现唇形同步,所以每个复合包还必须包括SDES CNAME,除非复合包被分割并如9.1节所述进行部分加密。
  • 必须限制可能首先出现在复合包中的包类型的数量,以增加第一个字中的常量比特位的数量,以及成功验证RTCP包的概率(区分错误地址的RTP数据包或其他不相关的数据包)。

因此,所有的RTCP报文必须以至少两个单独报文组成的复合报文的形式发送,格式如下:

加密前缀:当且仅当复合报文要按照9.1节的方法加密时,它必须被一个32位随机数作为前缀。如果加密需要填充,则必须将其添加到复合包的最后一个包中。

SR or RR:复合报文中的第一个RTCP报文必须总是一个报告报文,以方便报头验证,如附录A .2所述。在这种情况下,必须发送一个空的RR,即使复合报文中唯一的另一个RTCP报文是BYE。

额外的RR:如果正在报告接收统计信息的源的数量超过31个,这个数量封装到一个SR或RR数据包,附加的RR数据包应该在初始报告数据包之后。

SDES:每个复合RTCP数据包中必须包括包含CNAME项目的SDES数据包,除非如第9.1节另有说明。如果特定应用需要,根据带宽限制(见第6.3.9节),可以选择包括其他源描述项。

BYE 或者 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报文开头。

RTP:一种实时应用的传输协议 (RFC-3550)_第3张图片

一个实现应该忽略未知类型的RTCP报文。额外的RTCP包类型可以在IANA (Internet Assigned Numbers Authority)注册,如章节15所述。

6.2、RTCP传输间隔

RTP旨在允许应用程序自动扩展会话大小,从几个参与者到数千人。例如,在音频会议中,数据流量本质上是自我限制的,因为一次只有一两个人发言,因此使用多播分发,任何给定链路上的数据速率相对恒定,不受参与者数量的影响。但是,控制流量不是自限的。如果每个参与者的接收报告以恒定的速率发送,控制流量将随着参与者的数量线性增长。因此,必须通过动态计算RTCP数据包传输间隔来降低速率。

对于每个会话,假设数据流量受到一个称为“会话带宽”的聚合限制,该限制将在参与者之间分配。这个带宽可能被保留,并且由网络强制执行。如果没有预留,则可能存在其他限制(取决于环境),这些限制为会话建立“合理的”最大使用值,即会话带宽。会话带宽的选择可以基于某些开销或会话可用网络带宽的先验知识。它在一定程度上独立于媒体编码,但编码的选择可能受到会话带宽的限制。通常,会话带宽是预期并发活跃的发送方的名义带宽的总和。对于电话会议音频,这个数字通常是一个发送方的带宽。对于分层编码,每一层是一个单独的RTP会话,具有自己的会话带宽参数。

它在一定程度上独立于媒体编码,但编码的选择可能受到会话带宽的限制。通常,会话带宽是预期并发活动的发送方的名义带宽的总和。对于电话会议音频,这个数字通常是一个发送方的带宽。对于分层编码,每一层是一个单独的RTP会话,具有自己的会话带宽参数。

控制和数据流量的带宽计算包括较低层的传输和网络协议(例如UDP和IP),因为这是资源预留系统需要知道的。应用程序还可以知道正在使用这些协议中的哪些。链路级报头不包括在计算中,因为数据包在传输时将用不同的链路级报头进行封装。

控制流量应该被限制在会话带宽的一个小而已知的部分:小到传输协议携带数据的主要功能不会受到损害;已知,以便控制流量可以包含在给资源预留协议的带宽规范中,并且每个参与者可以独立计算其份额。控制流量带宽是在会话带宽的基础上增加数据流量的带宽。建议RTCP增加的会话带宽比例固定在5%。建议将RTCP带宽的1/4分配给正在发送数据的参与者,这样在接收方数量多,发送方数量少的会话中,新加入的参与者将更快地收到发送站点的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间隔缩放到与会话带宽参数成反比的较小值,但有以下限制:

  • 对于组播会话,只有活跃的数据发送方可以使用减小后的最小值来计算复合RTCP报文的传输间隔。
  • 对于单播会话,降低后的值也可以被非活跃数据发送者使用,并且在发送初始复合RTCP报文之前的延迟可能为零。
  • 对于所有的会话,在计算参与者超时时间时,应该使用固定的最小值(见章节6.3.5),以便不使用减少的值传输RTCP报文的实现不会被其他参与者提前超时。
  • 减少的最小值(以秒为单位)的建议值是360除以会话带宽(以千位/秒为单位)。对于大于72 kb/s的带宽,该最小值小于5秒。

第6.3节和附录A.7中描述的算法旨在实现本节概述的目标。它计算发送复合RTCP数据包之间的间隔,以在参与者之间分配允许的控制流量带宽。这使得应用程序能够为小型会话提供快速响应,例如,在小型会话中,所有参与者的身份识别都很重要,但会自动适应大型会话。该算法具有以下特点:

  • RTCP数据包之间的计算间隔与组中的成员数成线性关系。正是这一线性因素允许在所有成员之间求和时保持恒定的控制流量。
  • RTCP数据包之间的间隔在计算间隔的[0.5,1.5]倍范围内随机变化,以避免所有参与者的意外同步 附录[20]。加入会话后发送的第一个RTCP数据包也会因最小RTCP间隔一半的随机变化而延迟。
  • 计算平均复合RTCP数据包大小的动态估计,包括所有接收和发送的数据包,以自动适应所携带的控制信息量的变化。
  • 由于计算的间隔取决于观察到的组成员的数量,当一个新用户加入一个现有会话时,或者许多用户同时加入一个新会话时,可能会出现不良的启动效果。这些新用户最初对组成员的估计不正确,因此他们的RTCP传输间隔将太短。如果多个用户同时加入会话,这个问题可能会很严重。为了解决这个问题,采用了一种称为“计时器重新考虑”的算法。该算法实现了一种简单的退避机制,当组大小增加时,该机制会导致用户阻止RTCP数据包传输。
  • 当用户使用BYE或超时离开会话时,组成员关系会减少,因此计算的间隔应该会减少。一个“反向重新考虑”算法被用来允许成员更快地减少他们的间隔,以响应组成员减少。
  • BYE数据包的处理方式与其他RTCP数据包不同。当用户离开组并希望发送BYE数据包时,可以在下一个预定RTCP数据包之前发送。但是,BYEs的传输遵循回退算法,当大量成员同时离开会话时,避免了BYE报文的泛洪。

此算法可用于允许所有参与者发送消息的会话。在这种情况下,会话带宽参数是单个发送方的带宽乘以参与者的数量的乘积,RTCP带宽是其5%。

算法操作的细节将在下面的章节中给出。附录A.7给出了一个示例实现。

6.2.1、维护会话成员的数量

RTCP数据包间隔的计算取决于参与会话的站点数量的估计。当听到新的站点时,它们会被添加到计数中,每个站点都应该在一个由SSRC或CSC标识符索引的表中创建一个条目(见第8.2节),以跟踪它们。在收到携带新SSRC的多个数据包(见附录A.1)或收到包含该SSRC CNAME的SDES RTCP数据包之前,新条目可能被视为无效。当收到带有相应SSRC标识符的RTCP BYE报文时,表中的表项可能被删除,除非一些掉线的数据包可能在该BYE之后到达并导致表项被重新创建。相反,应该将条目标记为已接收到BYE,然后在适当的延迟后删除。

如果在少量RTCP报告间隔内(建议5次)未收到RTP或RTCP数据包,参与者可将另一个站点标记为非活动,或将其删除(如果尚未生效)。这提供了一些抗数据包丢失的健壮性。所有站点必须具有相同的乘数值,并且必须为RTCP报告间隔计算大致相同的值,以使超时正常工作。因此,对于特定的配置文件,这个乘数应该是固定的。

对于参与者数量非常大的会话,维护一个表来存储所有参与者的SSRC标识符和状态信息可能是不切实际的。如[21]所述,一种实现可以使用SSRC采样来减少存储需求。实现可以使用具有类似性能的任何其他算法。一个关键要求是,所考虑的任何算法都不应严重低估组大小,尽管它可能会高估。

6.3、RTCP报文收发规则

这里概述了如何发送和接收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。

这些规则中的许多都利用了数据包传输之间的“计算间隔”。这个间隔将在下一节中描述。

6.3.1、计算RTCP传输周期

为了保持可伸缩性,来自会话参与者的数据包之间的平均间隔应随组大小而变化。该间隔称为计算间隔。它是通过组合上述多个状态片段而获得的。然后,计算的间隔T确定如下:

  1. 如果发送方的数量小于或等于成员(成员)的25%,则间隔取决于参与者是否为发送方(基于we_sent的值)。如果参与者是发送方(we_sent true),则常数C为RTCP平均包大小(avg_rtcp_size)除以RTCP带宽(rtcp_bw)的25%,常数n为发送方数量。如果we_sent不为true,常量C设置为RTCP报文平均大小除以RTCP带宽的75%。常数n被设置为接收方(成员-发送方)的数量。如果发送方数量大于25%,则发送方和接收方一起处理。常数C为RTCP报文平均大小除以RTCP总带宽,n为成员总数。正如6.2节所述,RTP配置文件可以指定RTCP带宽可以明确定义为两个单独的参数(称为S和R),用于发送方和非发送方的参与者。在这种情况下,25%的分数变成了S/(S+R), 75%的分数变成了R/(S+R)。注意,如果R为零,发送者的百分比永远不会大于S/(S+R),并且实现必须避免除以零。
  2. 如果参与者还没有发送RTCP报文(变量initial为true),则常量Tmin设置为2.5秒,否则设置为5秒。
  3. 决定性计算间隔(deterministic calculated interval) Td = max(Tmin, n*C) 。
  4. 计算间隔 (calculated interval)T 设为均匀分布在决定性计算间隔 Td的0.5 ~ 1.5倍之间的一个数字。
  5. T=T/(e-3/2)≈T/1.21828,补偿时间重估算法,使之收敛到比计算出的平均RTCP带宽小的一个值。

这一过程会产生一个随机的间隔,但平均而言,该间隔将至少25%的RTCP带宽提供给发送方,其余部分提供给接收方。如果发送者占成员的四分之一以上,则此过程平均在所有参与者之间平均分配带宽。

6.3.2、初始化

加入会议时,参与者初始化tp=0,tc=0,senders=0,pmembers=1,members=1,we_sent=false, rtcp_bw=会话指定分数的带宽,initial=true, avg_rtcp_size=应用程序稍后将构造的第一个RTCP包的可能大小。。然后计算计算时间间隔 T,第一个包被调度到时间tn = T。这意味着设置了一个传输定时器,在时间T后唤醒。注意,应用程序可以使用任何想要的方法来实现这个定时器。

参与者将自己的SSRC添加到成员表中。

6.3.3、接收RTP或 非bye的RTCP报文

当收到一个参与者的RTP或RTCP包时,若其SSRC不在成员列表中,将其SSRC加入列表;若此参与者被确认有效(如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.4、接收RTCP BYE包

除了第6.3.7节中描述的RTCP BYE要传输的情况外,如果接收到的是RTCP BYE报文,则根据成员表检查SSRC。如果存在,则从表中删除条目,并更新成员的值。然后根据发送者列表检查SSRC。如果存在,则从表中删除条目,并更新发送者的值。

另外,为使RTCP包的发送速率与组中人数变化更加协调,当收到一个BYE包使得members的值pmembers时,下面的逆向重估算法应当执行:

  • tn的值按以下公式更新:tn = tc + (members/pmembers) * (tn - tc)
  • tp的值按以下公式更新:tp = tc - (members/pmembers) * (tc - tp).
  • 下一个RTCP包将在时刻tn 被发送,比更新前更早一些。
  • pmembers的更新:pmembers=members。

这个算法并没有防止组的大小被错误的在短时间内估计为0的情况。如:在一个较多人数的会话中,多数参与者几乎同时离开而少数几个参与者没有离开的情况。这个算法并没有使估计迅速返回正确的值。因为这种情况较罕见,且影响不大。

6.3.5、SSRC超时

在随机的时间间隔中,一个参与者必须检测其他参与者是否已经超时。为此,对接收者(we_sent为false),要计算决定性时间间隔Td,如果从时刻Tc-M*Td(M为超时因子,默认为5秒)开始,未发送过RTP或RTCP包,则超时。其SSRC将被从列表中移除,成员被更新。在发送者列表中也要进行类似的检测。发送者列表中,任何从时间tc-2T(在最后两个RTCP报告时间间隔内)未发送RTP包的发送者,其SSRC从发送者列表中移除,列表更新。
如果有成员超时,应该执行6.3.4节中的逆向检测算法。每个参与者在一个RTCP包发送时间间隔内至少要进行一次这样的检测。

6.3.6、传输定时器过期

当报文发送定时器超时时,参与者进行如下操作:

  • 传输间隔T的计算方法如6.3.1节所述,包括随机因子。
  • 如果tp+T小于或等于tc,则传输RTCP数据包。tp设置为tc,然后按照上一步计算T的值,tn设置为tc+T。传输计时器设置为在tn时再次过期。如果tp+T大于tc,tn设置为tp+T。不传输RTCP数据包。传输计时器设置为在时间tn时过期。
  • pmembers设置为members。

如果是RTCP报文,initial设置为FALSE。此外,avg_rtcp_size的值也会更新:

avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size

其中packet_size是刚刚传输的RTCP数据包的大小。

6.3.7、发送BYE报文

当一个参与者离开会话时,应发送BYE包,通知其他参与者。为避免大量参与者同时离开系统时,大量BYE包的发送,若会话人数超过50,则参与者在要离开会话时,应执行下面的算法。这个算法实际上“篡夺”了一般可变成员的角色来统计BYE包:

  • 当参与者决定离开系统时,tp=tc ; members=1; pmembers=1; sinitial=1; we_sent=false; senders=0;avg_rtcp_size设置为复合BYE包的大小;计算“计算时间间隔”T;tn=tc+T;(BYE包预计在时刻tn被发送)。
  • 每当从另外一个参与者接收到BYE包时,成员人数加1。不管此成员是否存在于成员列表中,也不管SSRC采样何时使用及BYE包的SSRC是否包含在采样之中。当收到其他RTCP报文或RTP报文时,成员值不递增,但只对BYE报文递增。类似地,avg_rtcp_size只对接收到的BYE包进行更新。当RTP数据包到达时,发送者senders不更新;它仍然是0。
  • 然后,BYE包的传输遵循如上所示的传输常规RTCP包的规则。

这允许立即发送BYE包,但控制了它们的总带宽使用。在最坏的情况下,这可能导致RTCP控制包使用两倍于正常带宽(10%)—非BYE RTCP包使用5%,BYE使用5%。

一个参与者若不想用上面的机制进行RTCP包的发送,可以直接离开会话,而根本不发送BYE包。他会被其他参与者因超时而删除。

一个参与者想离开会话时,如果组中的人数会计数目小于50,则参与者可以直接发送BYE包。

无论哪种情况,一个从未发送RTP或RTCP报文的参与者在离开该组时都不能发送BYE报文。

6.3.8、更新 we_sent

如果参与者最近发送了RTP数据包,则我们发送的变量为true,否则为false。通过使用与管理senders表中列出的其他参与者集相同的机制来确定。如果参与者在we_sent为false时发送了一个RTP包,它将自己添加到sender表中并将we_sent设置为true。为了尽可能减少发送SR报文之前的延迟,应该使用章节6.3.4中描述的逆向重估算法(reverse reconsideration algorithm)。每次发送另一个RTP数据包时,该数据包的传输时间都保存在表中。然后,对参与者应用正常的发送方超时算法——如果自时间tc-2T以来没有发送RTP数据包,参与者将自己从发送方表中移除,减少发送方计数,并将we_sent设置为false。

6.3.9、源描述带宽的分配

除了必需的CNAME项之外,该规范还定义了几个源描述(SDES)项,例如NAME(个人名称)和EMAIL(电子邮件地址)。它还提供了一种方法来定义新的特定于应用程序的RTCP数据包类型。应用程序在为这些附加信息分配控制带宽时应该谨慎,因为它会减慢接收报告和CNAME发送的速度,从而影响协议的性能。建议分配给一个参与者用于传输这些额外信息的带宽不超过总的RTCP带宽的20%。另外,并非所有的源描述项都将包含进每一个应用程序中。包含进应用程序的源描述项应根据其用途分配给相应的带宽百分比。建议不要动态会计这些百分比,而应根据一个源描述项的典型长度将所占带宽的百分比的转化为报告间隔。

例如,一个应用程序可能被设计为只发送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节)。

6.4、发送方和接收方报告

RTP接收方使用RTCP报告包提供接收质量反馈,根据接收方是否也是发送方,RTCP报告包可以采取两种形式之一。发送方报告(SR)和接收方报告(RR)之间的唯一区别,除了包类型代码之外,是发送方报告包含一个20字节的发送方信息部分,供活动的发送方使用。如果站点在发布最后一个或上一个报告的间隔时间内发送了任何数据包,则发出SR,否则发出RR。

SR和RR报告都包含零个或多个接收报告块,每个报告块代表自上次报告以来接收到RTP数据包的每个同步源。报告不发送给CSRC列表中的贡献源。每个接收报告块提供从特定数据源接收到数据的统计信息。由于一个SR或RR包最多可以容纳31个接收报告块,因此需要在初始SR或RR包之后对额外的RR包进行堆叠,以包含从上次报告开始的间隔内听到的所有源的接收报告。如果数据源太多,致使若把所有的RR包放到同一个RTCP复合包中会超出网络的MTU。那么就在一个周期内选择上面RR包的一部分以不超过MTU。应该跨多个间隔轮询选择子集,对所有的数据源就都发送接收报告。

接下来的部分定义了这两个报告的格式,如果应用程序需要额外的反馈信息,如何以特定于概要文件的方式对它们进行扩展,以及如何使用报告。转换器和混音器的接受者报告的细节见第7节。

6.4.1、SR:发送者报告RTCP包

RTP:一种实时应用的传输协议 (RFC-3550)_第4张图片

发送者报告包由三个部分组成,如果定义了,可能后面跟着第四个配置文件特定扩展部分。

第一部分头部长度为8字节。这些字段有以下含义:

版本(V):2 bits

RTP的版本号,RTCP报文和RTP数据报文的版本号相同。本规范定义的版本为2。

填充(P):1 bit

若设置填充比特,该RTCP包在末端包含一些附加填充比特,并不是控制信息的基本部分。填充的最后一个比特统计了多少个字节必须被忽略,包括它自己。填充可能会用于需要固定长度块的加密算法。在复合RTCP包中,只需要对单个包进行填充,因为复合包作为一个整体用第9.1节中的方法进行了加密。因此,填充必须只添加到最后一个单独的包,如果添加到该包,填充位必须只在该包上设置。该约定有助于附录A.2中所述的报头有效性检查,并允许检测来自某些早期实现的数据包,这些包错误地设置了第一个单独数据包的填充位,并在最后一个单独数据包中添加了填充。

接收报告数(RC):5 bits

此数据包中包含的接收报告块的数量。0也是有效值。

包类型(PT):8 bits

RTCP SR包 为200。

长度(length):16 bits

该RTCP包的长度减1。其单位是32比特字,包括头和任何填充字节。(偏移量1保证零值有效,避免了在扫描RTCP包长度时可能发生的无限循环,同时以32比特为单位避免了对以4为倍数的有效性检测。)

SSRC: 32 bits

同步源标识符为该SR报文的发起者。

第二部分,发送方信息,长度为20字节,存在于每个发送方报告包中。它总结了来自这个发送者的数据传输。这些字段有以下含义:

NTP时间戳(NTP timestamp): 64 bits

指示发送此报告时的wallclock时间(参见第4节),以便它可以与从其他接收方返回的接收报告中的时间戳结合使用,以度量到这些接收方的往返传播。接收者应让NTP时间戳的精度远大于其他时间戳的精度。时间戳测量的不确定性不可知,因此也无需指示。在没有wallclock时间概念但有一些系统特定时钟(如“系统正常运行时间”)的系统上,发送方可以使用该时钟作为参考来计算相对的NTP时间戳。选择一个常用的时钟是很重要的,这样如果使用不同的实现来产生多媒体会话的单个流,那么所有的实现都将使用相同的时钟。直到2036年,相对时间戳和绝对时间戳的高位将会不同,因此(无效)比较将显示出很大的差异;到那时,人们希望不再需要相对的时间戳。没有wallclock或elapsed time概念的发送方可以将NTP时间戳设置为0。

RTP时间戳(RTP timestamp):32 bits

与NTP时间戳(上面)相同,但与数据包中的RTP时间戳具有相同的单位和偏移量。这个一致性可以用来让NTP时间标志已经同步的源之间进行媒体内/间同步,还可以让与媒体无关的接收者估计名义RTP时钟频率。注意,在大多数情况下,这个时间戳不等于任何相邻数据包中的RTP时间戳。相反,它必须从相应的NTP时间戳计算出来,使用RTP时间戳计数器和实时时间之间的关系,这是通过定期检查某个采样瞬间的时钟时间来维护的。

RTP数据包数量(sender’s packet count):32 bits

发送方从开始传输到该SR包产生的RTP数据包总数。如果发送方更改了其SSRC标识符,则应重置计数。

RTP数据包负载字节数(sender’s octet count):32 bits

从开始传输到此SR包产生时该发送者在RTP数据包发送的字节总数(不包括头和填充)。若发送者改变SSRC识别符,该计数器重设。此域可以用来估计平均的负载数据发送速率。

第三部分:零到多个接收报告块。块数等于从上一个报告以来该发送者侦听到的其它源(不包括自身)的数目。每个接收报告块传输从某个同步源来的数据包的接收统计信息。若数据源因冲突而改变其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数据包到达时间间隔的统计方差的估计,以时间戳单位度量,并表示为无符号整数。到达时间抖动定义为一对包中接收者相对发送者的时间间隔差值的平均偏差(平滑后的绝对值)。如下式所示,这相当于两个包的“相对传输时间”的差值;相对传输时间是数据包在到达时的RTP时间戳与接收方时钟之间的差值,用相同的单位来测量。

如果Si是数据包i的RTP时间戳,Ri是数据包i到达的RTP时间戳单位,那么对于两个数据包i和j, D可以表示为:

D(i,j)  =  (Rj - Ri) - (Sj - Si)  =  (Rj - Sj) - (Ri - Si)

到达时刻抖动可以在收到从源SSRC_n来的每个数据包i后连续计算。利用该包和前一包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节。

最新SR时间戳(last SR timestamp LSR): 32 bits

从源SSRC_n收到的最新RTCP发送者报告(SR)报文的64位NTP时间戳中的中间32位(见章节4)。如果还没有接收到SR,该字段置零。

自最新SR以来的延迟(delay since last SR DLSR):32 bits

从源SSRC_n接收最后一个SR包到发送此接收报告块之间的延迟,单位为1/65536秒。如果没有收到来自SSRC_n的SR报文,则将DLSR字段置零。

假设SSRC_r为发出此接收报告块的接收者。源SSRC_n可以通过记录收到此接收报告块的时刻A来计算到SSRC_r的环路传输时延。可以利用最新的SR时间标志(LSR)计算整个环路时间(A-LSR),然后减去此DLSR域得到环路传输的时延(A - LSR- DLSR)。如图2所示。时间以32位字段的十六进制表示和等效的浮点十进制表示两种形式显示。冒号表示32位字段被分成16位整数部分和16位小数部分。

RTP:一种实时应用的传输协议 (RFC-3550)_第5张图片

可以用此来近似测量到一组接收者的距离,尽管有些连接可能有非常不对称的时延。

6.4.2、RR:接收者报告包

RTP:一种实时应用的传输协议 (RFC-3550)_第6张图片

接收者报告包(RR)与发送者报告包基本相同,除了包类型域包含常数201和没有发送者信息的5个字(NTP和RTP时间标志和发送者包和字节计数)。余下区域与SR包意义相同。若没有发送和接收据报告,在RTCP复合包头部加入空的RR包(RC=0)。

6.4.3、扩展发送方和接收方报告

如果有额外的关于发送者和接收者的信息要周期性的,描述文件(profile)应该定义接收者报告和发送者报告描述文件扩展。此时,应采用这里的办法,而不是定义另外的RTCP包。因为这种办法需要的头部信息更少。

  • 报文中字节数更少(没有RTCP报头或SSRC字段)
  • 更简单、更快的解析,因为在该描述文件(profile)下运行的应用程序将被编程为在接收报告之后总是希望扩展字段位于直接可访问的位置。

扩展是发送方或接收方报告包中的第四个部分,它位于接收报告块(如果有)之后。如果需要额外的发送方信息,那么对于发送方报告,它将首先包含在扩展部分中,但是对于接收方报告,它将不存在。如果要包括关于接收方的信息,它应该以块数组的方式放到接收报告块的后面。即这些块也应被计入RC字段中。

6.4.4、分析发送者和接收者报告

接收质量反馈不仅对发送者有用,而且对于其它接收者和第三方监视器也有作用。发送者可以基于反馈修正发送信息量;接收者可以判断问题是本地的,区域内的还是全局的;网络管理者可以利用与协议无关的监视器(只接收RTCP包而不接收相应的RTP包)去评估多点传送网络的性能。

在发送者信息和接收者报告块中都连续统计丢包数,因此可以计算任何两个报告块中的差别。在短时间和长时间内都可以进行测算,并提供抵御报告丢失的恢复性。最近收到的两个包之间差值可以评估当前传输质量。包中有NTP时间戳,可以用两个报告间隔的差值计算传输速率。由于时间戳独立于数据编码的时钟速率,因此可以实现与编码及协议独立的质量监视。

一个计算示例是两个接收报告之间的丢包率。累计丢包率(cumulative number of packets lost)的差值表示在该时间间隔内丢失的包数。接收到的扩展最高序列号(extended highest sequence number received)的差值表示在时间间隔内预期的包数。丢包率=此间隔内丢失的包/此间隔内期望收到的包。如果此值与丢包率(fraction lost)字段中的值相同,说明包是连续的;若否,说明包不是连续的。每秒的丢包率可以用丢包率除以NTP时间戳的差值得到,单位为秒。接收的报文数等于期望的报文数减去丢失的报文数。所期望的数据包数量也可用于判断任何损失估计的统计有效性。例如,丢包5分之1的重要性小于1000分之200的重要性。

从发送方的信息,第三方监视器可以在不接收数据的情况下计算一段时间内的平均有效载荷数据速率和平均包速率。两个值的比就是平均负载大小(平均每个包的负载大小)。(即:平均负载大小=平均负载数据发送速率/平均发包率。)若能假定丢包与包的大小无关,那么某个特定接收者收到的包数乘以平均负载大小(或相应的包大小)就得出接收者可得到的外在吞吐量。

除了累计计数允许利用报告间差值进行长期包损测量外,单个报告的“丢包比例”字段提供一个短时测量数据。当会话规模增加到无法为所有接收者保存接收状态信息,或者报告间隔变得足够长以至于从一个特定接收者只能收到一个报告时,短时测量数据变得更重要。

到达时间抖动(interarrival jitter)字段提供了网络拥塞的第二个短期度量。丢包跟踪持续的拥塞,而抖动测量跟踪短时间的拥塞。抖动测量可以在导致包丢失之前指示拥塞。由于到达间隔抖动字段仅仅是发送报告时刻抖动的一个快照,因此需要在一个网络内在一段时间内分析来自某个接收者的报告,或者分析来自多个接收者的报告。为了允许跨接收者进行比较,根据所有接收者的统一公式计算抖动是很重要的。

因为抖动计算是基于RTP时间戳,它表示数据包中的第一个数据被采样的瞬间,在采样瞬间和数据包传输时间之间的任何延迟变化都会影响计算出来的结果抖动。对于不同持续时间的音频分组,将发生延迟的这种变化。对于视频编码也会发生这种情况,因为一帧的所有数据包的时间戳都是相同的,但这些数据包不是同时传输的。传输前延迟的变化确实降低了抖动计算的准确性,抖动计算是衡量网络自身行为的一种手段,但考虑到接收器缓冲区必须容纳它,将其包括在内是适当的。当抖动计算用作比较度量时,由于传输延迟变化而产生的(常数)分量减去,这样就可以观察到网络抖动分量的变化,除非它相对较小。如果变化很小,那么它很可能是无关紧要的。

6.5、SEDS:源描述RTCP包

RTP:一种实时应用的传输协议 (RFC-3550)_第7张图片

SDES包是一个三层结构,由一个报头和零个或多个块组成,每个块由描述该块中标识的源的项组成。这些项目将在后续部分中单独描述。

版本(V), 填充(P), 长度(length)

见SR报文的描述(参见6.4.1节)。

包类型(PT): 8 bits

常量202,将其标识为RTCP SDES包。

源计数(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位边界。文本不以空结尾,因为一些多字节编码包含空字节。每个块中的项列表必须以一个或多个null字节结束,其中第一个字节被解释为一个0类型的项,表示列表的结束。空项类型的八位字节后面没有长度八位字节,但如果需要填充到下一个32位边界,则必须包含额外的空八位字节。请注意,此填充与RTCP标头中的P位所指示的填充是分开的。具有零项(四个空八位字节)的块有效但无用。

终端系统发送一个包含自己的源标识符的SDES包(与固定RTP报头中的SSRC相同)。一个混合器发送一个SDES包,其中包含它接收SDES信息的每个贡献源的一个块,或者如果有超过31个这样的源,则发送多个完整的以上格式的SDES包(参见第7节)。

下一节将介绍当前定义的SDES项目。只有CNAME项是必需的。此处显示的某些项目可能仅对特定概要文件有用,但项目类型都是从一个公共空间分配的,以促进共享使用并简化与概要文件无关的应用程序。如第15节所述,通过向IANA注册类型号,可以在概要文件中定义其他项目。

6.5.1、CNAME: Canonical End-Point Identifier SDES Item

RTP:一种实时应用的传输协议 (RFC-3550)_第8张图片

CNAME标识符具有以下属性:

  • 由于如果发现冲突或重新启动程序,随机分配的SSRC标识符可能会更改,因此必须包含CNAME项,以提供从SSRC标识符到保持不变的源(发送方或接收方)标识符的绑定。
  • 与SSRC标识符一样,CNAME标识符在一个RTP会话的所有参与者中也应该是唯一的。
  • 为了在一组相关RTP会话中一个参与者使用的多个媒体工具之间提供绑定,应该为该参与者固定CNAME。
  • 为了便于第三方监控,CNAME应该适合于程序或个人定位源。

因此,在可能的情况下,CNAME应该从算法中派生出来,而不是手动输入。为了满足这些要求,除非概要文件指定了替代语法或语义,否则应该使用以下格式。CNAME项的格式应该是“user@host”,如果用户名在单用户系统中不可用,也可以是"host"。对于这两种格式,“host”是实时数据来源主机的完全限定域名,按照RFC 1034【6】、RFC 1035【7】和RFC 1123【8】第2.1节规定的规则进行格式化;或用于RTP通信的接口上主机数字地址的标准ASCII表示。例如,IP版本4地址的标准ASCII表示是“点分十进制”,也被称为点四分位,对于IP版本6,地址在文本上表示为一组用冒号分隔的十六进制数字(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包转发到公共互联网,这将导致非惟一的cname。(参见RFC 1627[25]。)为了处理这种情况,应用程序可以提供一种方法来配置唯一的CNAME,但是如果需要防止私有地址被暴露,那么将CNAME从私有地址转换为公共地址的重担就落在了转换程序的肩上。

6.5.2、NAME: User Name SDES Item

RTP:一种实时应用的传输协议 (RFC-3550)_第9张图片

这是用来描述消息来源的真实名称,例如,“John Doe, Bit Recycler”。它可以是用户想要的任何形式。对于像会议这样的应用程序,这种形式的名称可能是最适合在参与者列表中显示的,因此除了CNAME之外,NAME发送频率最高。 配置文件可以建立这样的优先级。NAME值至少在会话期间保持不变。不应指望它在会议的所有参与者中是独一无二的。

6.5.3、EMAIL: Electronic Mail Address SDES Item

RTP:一种实时应用的传输协议 (RFC-3550)_第10张图片

电子邮件地址按照RFC 2822 [9] 进行格式化,例如,“John。[email protected]“。在会话期间,电子邮件值应保持不变。

6.5.4、PHONE: Phone Number SDES Item

RTP:一种实时应用的传输协议 (RFC-3550)_第11张图片

电话号码的格式应该用加号取代国际接入码。例如,“+1 908 555 1212”表示美国的一个数字。

6.5.5、LOC: Geographic User Location SDES Item

根据应用程序的不同,这一项适用于不同程度的细节。对于会议应用程序,像“Murray Hill, New Jersey”这样的字符串可能就足够了,而对于活跃的徽章系统,像“Room 2A244, AT&T BL MH”这样的字符串可能是合适的。详细程度由实现或用户决定,但格式和内容可能由概要文件规定。LOC值预计在会话期间保持不变,移动主机除外。 

6.5.6、TOOL: Application or Tool Name SDES Item

一个字符串,给出生成流的应用程序的名称和可能的版本,例如"videotool 1.2"。此信息对于调试目的可能很有用,并且类似于Mailer或Mail-System-Version SMTP报头。预计TOOL值在会话期间保持不变。

6.5.7、NOTE: Notice/Status SDES Item

RTP:一种实时应用的传输协议 (RFC-3550)_第12张图片

建议对此项使用以下语义,但这些语义或其他语义可以由概要文件显式定义。NOTE项用于描述源当前状态的瞬态消息,例如,“on The phone, can t talk”。或者,在研讨会上,NOTE项可以用来传达演讲的标题。 它只应用于携带特殊信息,不应被所有参与者例行包括,因为这会减慢接收报告和CNAME的发送速度,从而影响协议的性能。特别是,它不应作为项目包含在用户配置文件中,也不应作为每日引用自动生成。

由于NOTE项在活跃时显示可能很重要,因此其他非CNAME项(如NAME)的传输速率可能会降低,以便NOTE项可以占用RTCP带宽的那一部分。当暂态消息变为非活动状态时,NOTE项应该继续以相同的重复频率发送几次,但字符串长度为0,以向接收方发出信号。然而,如果在几次重复频率或20-30 RTCP间隔内没有收到NOTE项,接收者也应该认为它是不活动的。

6.5.8、PRIV: Private Extensions SDES Item

RTP:一种实时应用的传输协议 (RFC-3550)_第13张图片

此项用于定义实验性或特定于应用程序的SDES扩展。条目包含一个前缀,由一个长度-字符串对组成,后面是填充条目剩余部分并携带所需信息的value string。 prefix length字段的长度为8位。prefix string是由定义PRIV项的人员选择的名称,该名称相对于此应用程序可能接收的其他PRIV项是唯一的。如果需要,应用程序创建者可以选择使用应用程序名称加上额外的子类型标识或者,建议其他人根据他们所代表的实体选择一个名称,然后协调该名称在该实体中的使用。

注意,前缀在项目的总长度255字节内占用一些空间,因此前缀应该尽可能短。这个功能和受限的RTCP带宽不应该超载;它并不是为了满足所有应用程序的所有控制通信要求。IANA不会注册SDES PRIV前缀。如果某种形式的PRIV项目被证明是通用的,那么应该给它分配一个常规的SDES项目类型,并在IANA上注册,这样就不需要前缀了。这简化了使用,提高了传输效率。

6.6、BYE:离开RTCP报文

RTP:一种实时应用的传输协议 (RFC-3550)_第14张图片

BYE包表示一个或多个源不再活跃。 

版本(V), 填充(P), 长度(length)

见SR报文的描述(参见6.4.1节)。

包类型(PT): 8 bits

常量203,以识别为RTCP BYE包。

源计数(SC):5 bits

该BYE包中包含的SSRC/CSRC标识符的数量。计数值为零是有效的,但无用。

BYE包何时应该被发送的规则在章节6.3.7和8.2中指定。

BYE包表明一个或多个源将要离开。如果混频器收到BYE包,混频器应当发送这个BYE包,并保持SSRC/CSRC不变。如果混频器关闭,应向它处理的贡献源列表中的所有SSRC,包括它自己的SSRC发送BYE包。BYE包可能会有选择的包含8个字节的统计字段,其后跟上几个字节的文本表明离开的原因,例如,"camera malfunction" 或者 "RTP loop detected"。文本字符串编码格式和SDES中描述的相同。如果该字符串将报文填充到下一个32位边界,则该字符串不以空结尾。如果不是,BYE包必须用空字节填充到下一个32位边界。这个填充与RTCP报头中的P位所表示的填充是分开的。

6.7、APP:应用程序自定义的RTCP 报文

RTP:一种实时应用的传输协议 (RFC-3550)_第15张图片

该应用程序包旨在作为开发新应用程序和新功能的实验使用,无需数据包类型值注册。应忽略名称无法识别的应用程序包。测试后,如果有理由更广泛地使用,建议重新定义每个应用程序数据包,不使用子类型和名称字段,并使用RTCP数据包类型向IANA注册。

版本(V), 填充(P), 长度(length)

见SR报文的描述(参见6.4.1节)。

包类型(PT): 8 bits

常量204,以识别为RTCP APP包。

子类型(subtype):5 bits

可以用作一个子类型,允许一组APP数据包在一个唯一的名称下定义,或用于任何应用程序依赖的数据。

名称(name):4 bits

定义一组APP数据包的人选择的一个名称,该名称相对于该应用程序可能接收的其他APP数据包而言是唯一的。应用程序创建者可以选择使用应用程序名称,然后将子类型值分配给希望为应用程序定义新数据包类型的其他人。或者,建议其他人根据他们所代表的实体选择一个名称,然后协调该名称在该实体中的使用。名称被解释为四个ASCII字符的序列,大写和小写字符被视为不同的。

应用数据(application-dependent data):可变长度

应用程序相关数据可能会出现在应用程序数据包中,也可能不会出现在应用程序数据包中。它由应用程序解释,而不是RTP本身。它必须是32位长的倍数。

7、RTP 转换器和混频器

除了终端系统之外,RTP还支持“转换器”和“混频器”的概念,这可以被认为是RTP级别的“中间系统”。虽然这种支持增加了协议的复杂性,但通过在Internet上组播音频和视频应用的实验,明确了对这些功能的需求。第2.3节中给出的转换器和混频器的示例使用源于防火墙和低带宽连接的存在,这两种情况都可能会继续存在。

7.1、整体概述

RTP转换器/混频器连接两个或多个传输级“云”。通常,每个云是这么定义的,由一个公共网络和传输协议(例如IP/UDP)加上一个多播地址和传输级目的端口或一对单播地址和端口。(网络级协议转换程序,例如IPV4到IPV6,可能在云中对RTP不可见。)一个系统可以作为许多RTP会话的转换器或混频器,但每个都被认为是逻辑上独立的实体。

为了避免在安装转换器或混频器时产生循环,必须遵守以下规则:

  • 参与一个RTP会话的翻译器和混频器连接的每个云必须在这些参数中至少有一个(协议、地址、端口)与其他所有云不同,或者必须在网络级别与其他云隔离。
  • 第一个规则的衍生规则是,绝对不能有多个翻译器或混频器并行连接,除非通过某种安排,它们划分了要转发的源集。

类似地,所有可以通过一个或多个RTP转换器或混频器进行通信的RTP端系统共享相同的SSRC空间,也就是说,SSRC标识符在所有这些端系统中必须是唯一的。第8.2节描述了冲突解决算法,通过该算法SSRC标识符保持唯一并检测循环。

针对不同的用途和应用,可能有许多种类的转换器和混频器。例如,添加或删除加密,更改数据编码或底层协议,或在多播地址和一个或多个单播地址之间进行复制。转换器和混频器之间的区别在于,转换器分别通过来自不同来源的数据流,而混频器将它们组合起来形成一个新的数据流。

转换器:完整转发RTP包(包括SSRC标识符),这使得接收方能够识别单个源,即使来自所有源的信息包都经过同一个转换器并携带转换器的网络源地址。有些类型的转换器将完全不动地通过数据,但其他类型的转换器可能会改变数据的编码,从而改变RTP数据有效负载类型和时间戳。如果多个数据包被重新编码成一个,或者反之亦然,转换器必须为输出数据包分配新的序列号。在入方向的包流中丢失可能导致出方向的序列号产生相应的缺失。接收器无法检测到转换器的存在,除非他们通过其他方式知道原始源使用了什么有效负载类型或传输地址。

混频器:接收来自一个或多个源的RTP数据包流,可能改变数据格式,以某种方式合并流,然后转发合并流。由于多个输入源之间的时间通常不会同步,混频器会在流之间进行时间调整,并为合并后的流生成自己的时间,因此它是同步源。因此,由混频器转发的所有数据包必须用混频器自己的SSRC标识符标记。为了保持产生混合数据包的原始源的身份,混频器应该将它们的SSRC标识符插入到CSRC标识符列表中,在数据包的固定RTP头之后。混频器本身也是某个信息包的贡献源,它应该在该信息包的CSRC列表中显式地包括它自己的SSRC标识符。对于某些应用,混频器不识别CSRC列表中的来源是可以接受的。然而,这就带来了这样的危险:涉及这些源的循环无法被检测到。

对于像音频这样的应用程序,混频器相对于转换器的优势在于,即使在输入端有多个源激活时,输出带宽也仅限于一个源的带宽。这对于低带宽链路可能很重要。缺点是,在输出端的接收器没有任何方式控制源通过或静音,除非实现了一些机制的远程控制混频器。通过混频器再生同步信息也意味着接收端无法对原始流进行媒体间同步。一个多媒体混音器就可以做到。

RTP:一种实时应用的传输协议 (RFC-3550)_第16张图片

图3显示了一系列混频器和转换器,以说明它们对SSRC和CSRC标识符的影响。在图中,终端系统显示为矩形(命名为E),转换器为三角形(命名为T),混频器为椭圆形(命名为M)。“M1: 48(1,17)”表示一个来自混频器M1的报文,由M1 s(随机)SSRC值48和两个CSRC标识符1和17来标识,这两个标识符是从E1和E2的报文的SSRC标识符复制而来的。 

7.2、转换器中的RTCP处理

除了转发可能已修改的数据包外,转换器和混频器还必须处理RTCP数据包。在许多情况下,它们将从终端系统接收到的复合RTCP报文拆开,以聚合SDES信息并修改SR或RR报文。此信息的重新传输可能由数据包到达或转换器/混频器本身的RTCP间隔计时器触发。

一个不修改数据包的转换器,例如只在多播地址和单播地址之间复制的转换器,也可以简单地转发未修改的RTCP数据包。以某种方式转换有效负载的转换器必须在SR和RR信息中进行相应的转换,以便它仍然反映数据的特性和接收质量。这些转换器不能简单地转发RTCP数据包。通常,转换器不应将来自不同来源的SR和RR数据包聚合为一个数据包,因为这将降低基于LSR和DLSR字段的传播延迟测量的准确性。

SR发送者信息:转换器不会生成自己的发送者信息,而是将从一个云接收到的SR数据包转发给其他云。SSRC保持不变,但如果需要,必须修改发送者信息。如果转换器更改数据编码,则必须更改“发送方字节计数”字段。如果它还将多个数据包组合成一个输出包,则必须更改“发送方的数据包计数”字段。如果更改时间戳频率,则必须更改SR数据包中的“RTP时间戳”字段。

SR/RR接收报告块:转换器将从一个云接收到的接收报告转发给其他云。请注意,这些流的方向与数据的方向相反。SSRC保持不变。如果转换器将多个数据包组合成一个输出包,并因此更改序列号,则必须对“数据包丢失”字段和“扩展的最后序列号”字段进行反向操作。这可能很复杂。在极端情况下,可能没有有意义的方法来转换接收报告,因此,转换器可能根本不会传递任何接收报告,或者传递基于自身接收的综合报告。一般规则是做对特定转换有意义的事情。

转换器不需要自己的SSRC标识符,但可以选择分配一个,以便发送有关其接收内容的报告。这些将被发送到所有连接的云,每个云对应于发送到该云的数据流的转换,因为接收报告通常是多播给所有参与者的。

SDES:转换器通常将从一个云接收到的SDES信息转发给其他云,但如果带宽有限,则可能会决定过滤非CNAME SDES信息。必须转发CNAME以允许SSRC标识符冲突检测工作。生成自己的RR数据包的转换器必须将有关自身的SDES CNAME信息发送到发送这些RR数据包的云。

BYE:转换器转发BYE数据包时保持不变。即将停止转发数据包的转换器应向每个连接的云发送BYE数据包,其中包含之前转发到该云的所有SSRC标识符,如果转换器发送了自己的报告,则包括转换器自己的SSRC标识符。

APP:转换器转发APP包时保持不变。

7.3、混频器中的RTCP处理

由于混频器生成自己的新数据流,因此它根本不转发SR或RR数据包,而是为双方生成新信息。

SR发送者信息:混频器不会传递来自其混合的源的发送方信息,因为源流的特征在混频中丢失。作为同步源,混频器应该生成自己的SR包,其中包含关于混合数据流的发送方信息,并以与混合流相同的方向发送它们。

SR/RR接收报告块:混频器为每个云中的源生成自己的接收报告,并仅将其发送到同一个云中。它不得将这些接收报告发送给其他云,也不得将接收报告从一个云转发给其他云,因为报告的源不会是SSRC(仅CSRC)。

SDES:混频器通常将从一个云接收到的SDES信息转发给其他云,但如果带宽有限,则可能会决定过滤非CNAME SDES信息。必须转发CNAME以允许SSRC标识符冲突检测工作。(混频器生成的CSC列表中的标识符可能与终端系统生成的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数据包的处理是特定于应用程序的。

7.4、级联混频器

RTP会话可涉及如图3所示的混频器和转换器的集合。如果两个混频器级联,例如图中的M2和M3,则混频器接收的分组可能已经混合,并且可能包括具有多个标识符的csc列表。第二个混合器应使用来自已混合输入数据包的CSRC标识符和来自未混合输入数据包的SSRC标识符为传出数据包构建CSRC列表。图中标记为M3:89(64,45)的混合器M3的输出弧显示了这一点。与未级联的混合器一样,如果生成的CSRC列表具有15个以上的标识符,则不能包括其余标识符。

8、SSRC标识符分配和使用

RTP报头和RTCP数据包各个字段中携带的SSRC标识符是一个随机的32位数字,要求在RTP会话中全局唯一。至关重要的是,要谨慎选择号码,以便同一网络上或同时开始的参与者不太可能选择相同的号码。

仅使用本地网络地址(如IPv4地址)作为标识符是不够的,因为地址可能不唯一。由于RTP转换器和混频器支持具有不同地址空间的多个网络之间的互操作,因此两个空间内的地址分配模式可能会导致比随机分配更高的冲突率。

在一台主机上运行的多个源也会发生冲突。

仅仅通过调用random()而没有仔细初始化状态来获得SSRC标识符也是不够的。附录a .6给出了一个如何生成随机标识符的示例。

8.1、冲突概率

由于标识符是随机选择的,因此两个或多个源可能会选择相同的数字。当所有源同时启动时(例如,当某些会话管理事件自动触发时),发生冲突的可能性最大。如果N是源的数量,L是标识符的长度(此处为32位),则两个源独立选取相同值的概率可近似为1-exp( -N**2 / 2**(L+1)) 附录【26】。对于N=1000,概率大约为10**-4。

典型冲突概率远低于上述最坏情况。当一个新源加入一个RTP会话,其中所有其他源都已经具有唯一标识符时,冲突的概率只是使用的数字的分数。同样,如果N是源的数量,L是标识符的长度,冲突的概率是N/2^L。对于N=1000,概率大概是2*10**-7。

新源在发送其第一个数据包(数据或控制)之前有机会从其他参与者接收数据包,从而进一步降低了冲突的概率。如果新源保持对其他参与者的跟踪(通过SSRC标识符),那么在发送它的第一个包之前,新源可以验证它的标识符与任何已经接收到的标识符不冲突,或者再次选择。

8.2、冲突解决和循环检测

虽然SSRC标识符冲突的概率很低,但所有RTP实现必须准备好检测冲突并采取适当的行动来解决它们。如果一个源在任何时候发现另一个源使用与它自己的相同的SSRC标识符,它必须为旧的标识符发送一个RTCP BYE包,并随机选择另一个标识符。(正如下面解释的,这个步骤只在循环中执行一次。) 如果接收者发现其他两个源发生冲突,则当不同的源传输地址或CNAME可以检测到冲突时,它可以保留其中一个源的数据包,并丢弃另一个源的数据包。预计这两个来源将解决冲突,使情况不会持续下去。

因为随机SSRC标识符对每个RTP会话保持全局唯一,它们还可以用于检测可能由混频器或转换器引入的循环。循环会导致数据和控制信息的重复,要么是未修改的,要么是混合的,如下面的例子所示:

  • 转换器可能会直接或通过转换器链错误地将数据包转发到接收该数据包的同一多播组。在这种情况下,来自不同网络源的同一数据包多次出现。
  • 两个并行设置错误的转换器(即,两侧都有相同的多播组)都会将数据包从一个多播组转发到另一个多播组。单向转换器将产生两份副本;双向转换器将形成一个循环。
  • 混频器可以通过直接或通过另一个混频器或转换器将报文发送到它接收数据包的同一传输目的地来结束循环。在这种情况下,源可能在数据包中显示为SSRC,在混合数据包中显示为CSRC。

源可能会发现自己的数据包正在循环,或者来自另一个源的数据包正在循环(第三方循环)。源标识符随机选择中的循环和冲突都会导致到达的数据包具有相同的SSRC标识符,但具有不同的源传输地址,这可能是发起数据包的终端系统或中间系统的传输地址。

因此,如果一个源改变了它的源传输地址,它也可以选择一个新的SSRC标识符来避免被解释为一个循环源。(这不是必须的,因为在某些应用程序中RTP源可能期望在会话期间改变地址。)请注意,如果转换器重新启动,并因此更改其转发数据包的源传输地址(例如,更改UDP源端口号),则所有这些数据包在接收器看来都将循环,因为SSRC标识符由原始源应用,并且不会更改。这个问题可以通过在重启期间保持源传输地址固定来避免,但在任何情况下都将在接收端超时后解决。

如果数据包的所有副本都经过转换器或混频器,则无法使用源传输地址检测转换器或混频器远端发生的循环或冲突,但是,当来自两个RTCP SDES数据包的数据块包含相同的SSRC标识符但不同的CNAME时,仍可能检测到冲突。

为了检测和解决这些冲突,RTP实现必须包含一个类似于下面描述的算法,尽管实现可能会选择不同的策略来保存来自冲突的第三方源的数据包。下面描述的算法忽略来自新源或循环的数据包,这些数据包与已建立的源冲突。它通过为旧标识符发送RTCP BYE并选择新标识符来解决与参与者自己的SSRC标识符的冲突。然而,当冲突是由参与者自己的数据包的循环引起时,该算法将只选择一次新标识符,然后忽略来自循环源传输地址的数据包。这是避免BYE数据包泛滥所必需的。

该算法要求保持一个按源标识符索引的表,并包含使用该标识符接收的第一个RTP包和第一个RTCP包的源传输地址,以及该源的其他状态。由于RTP和RTCP数据包上的UDP源端口号可能不同,因此需要两个源传输地址。然而,可以假设网络地址在两个源传输地址中是相同的。

在源标识符表中查找RTP或RTCP数据包中接收的每个SSRC或CSC标识符,以便处理该数据或控制信息。将数据包中的源传输地址与表中相应的源传输地址进行比较,以检测循环或冲突(如果它们不匹配)。对于控制包,每个具有自己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或CSRC标识符) {
    创建一个新条目,存储数据或控制源传输地址、SSRC或CSRC以及其他状态;
}
/* 在表中找到标识符 */
else if (表条目是在收到控制数据包时创建的,这是第一个数据包,反之亦然) {
    存储来自此包的源传输地址;
}
else if (数据包的源传输地址与此标识符的表条目中保存的地址不匹配) {
    /* 指示标识符冲突或循环 */
    if (源标识符不是参与者自己的) {
        /* 可选错误计数器步骤 */
        if (源标识符来自一个RTCP SDES块,其中包含一个与表项中的CNAME不同的CNAME项) {
            统计第三方冲突;
        }
        else {
            统计第三方循环;
        }
        中止数据包或控制元件的处理;
        /* 可能会选择不同的政策来保持新的来源 */
    }
    /* 参与者自己的包的冲突或循环 */
    else if (源传输地址位于冲突数据或控制源传输地址列表中) {
        /* 可选错误计数器步骤 */
        if (源标识符不是来自包含CNAME项的RTCP SDES块,或者CNAME是参与者自己的) {
            统计自身循环流量的发生次数;
        }
        在冲突的地址列表条目中标记当前时间;
        中断数据包或控制元件的处理;
    }
    /* 新建冲突,更改SSRC标识符 */ 
    else {
        记录冲突的发生;
        在冲突的数据或控制源传输地址列表中创建一个新条目,并标记当前时间;
        发送一个带有旧SSRC标识符的RTCP BYE包;
        选择一个新的SSRC标识符;
        在源标识符表中创建一个新条目,使用旧的SSRC加上正在处理的数据或控制数据包的源传输地址;
    }
}

在该算法中,来自新冲突源地址的报文将被忽略,而原始源地址的报文将被保留。如果在一段时间内没有来自原始源的数据包到达,表项将超时,新源将能够接管。如果原始源检测到冲突并移动到新的源标识符,则可能会发生这种情况,但在通常情况下,将从原始源接收一个RTCP BYE包来删除状态,而无需等待超时。

如果原始源地址是通过混频器接收的(即,CSRC),并且随后直接接收到相同的源,则最好建议接收者切换到新的源地址,除非混频中的其他源会丢失。此外,对于一些应用程序,如电话,其中一些源(如移动实体)可能会在RTP会话过程中改变地址,RTP实现应该修改冲突检测算法,以接受来自新源传输地址的数据包。为了防止真正发生冲突时地址之间的切换,算法应该包含一些方法来检测这种情况并避免切换。

当由于冲突而选择新的SSRC标识符时,应首先在源标识符表中查找候选标识符,以查看它是否已被其他源使用。如果是这样,则必须生成另一个候选对象并重复该过程。

数据包循环到多播目的地可能会导致严重的网络洪泛。所有混频器和转换器都必须实现像这里这样的循环检测算法,以便它们能够打破循环。这应该将多余的通信量限制为不超过原始通信量的一个副本,这样可以允许会话继续,以便找到并修复循环的原因。然而,在极端情况下,混频器或转换器不能正确地打破循环,导致高流量级别,可能需要终端系统完全停止传输数据或控制包。这个决定可能取决于应用程序。应该适当地指出错误条件。传输可能会在一段很长的随机时间(按分钟的顺序)后周期性地再次尝试。

8.3、与分层编码一起使用

对于在单独RTP会话上传输的分层编码(见第2.4节),应在所有层的会话中使用单个SSRC标识符空间,核心(基本)层应用于SSRC标识符分配和冲突解决。当源发现发生冲突时,它只在基本层传输RTCP BYE数据包,但在所有层将SSRC标识符更改为新值。

9、安全性

底层协议最终可能提供RTP应用程序所需的所有安全服务,包括身份验证、完整性和机密性。这些服务已经在 附录[27]中为IP指定。由于使用RTP的初始音频和视频应用程序在IP层提供此类服务之前需要保密服务,因此定义了下一节中描述的保密服务,以便与RTP和RTCP一起使用。这里包含的描述是为了编纂现有的实践。RTP的新应用程序可以实现RTP特定的保密服务以实现向后兼容性,它们可以实现替代的安全服务。这个保密服务在RTP协议上的开销很低,所以如果这个服务在将来被其他服务废弃,损失也很小。

另外,其他服务、服务的其他实现和其他算法也可能在将来为RTP定义。具体而言,正在开发一种称为安全实时传输协议(SRTP)[28]的RTP协议,以提供RTP有效载荷的机密性,同时保持RTP报头处于透明状态,以便链路级报头压缩算法仍然可以运行。预计SRTP将是许多应用的正确选择。SRTP基于高级加密标准(AES),比这里描述的服务提供更强的安全性。没有人声称此处提供的方法适用于特定的安全需求。协议文档可以指定应用程序应该提供哪些服务和算法,并可以为它们的适当使用提供指导。

密钥分发和证书不在本文档的讨论范围之内。

9.1、保密性

保密性意味着只有预期的接收方可以解码接收到的数据包;对于其他接收者,报文不包含任何有用的信息。内容的机密性是通过加密实现的。

当需要根据本节规定的方法对RTP或RTCP进行加密时,将封装在一个较低层数据包中传输的所有八位字节作为一个单元进行加密。对于RTCP,在加密之前,必须为每个单元重新绘制一个32位随机数。对于RTP,没有前缀;相反,序列号和时间戳字段用随机偏移量初始化。由于随机性差,这被认为是一个弱初始化向量(IV)。此外,如果后续字段SSRC可以被敌人操纵,则加密方法还有进一步的弱点。

对于RTCP,一个实现可以将复合RTCP包中的各个RTCP包分离成两个单独的复合RTCP包,一个加密,一个以明文发送。例如,在接收报告以明文方式发送时,可以对SDES信息进行加密,以适应不知道加密密钥的第三方监视器。在这个例子中,如图4所示,SDES信息必须附加到一个没有报告(和随机数)的RR报文,以满足所有复合RTCP报文都以一个SR或RR报文开始的要求。加密或未加密的数据包中都需要SDES CNAME项,但不能同时包含这两个项。两个数据包中不应携带相同的SDES信息,因为这可能会影响加密。

RTP:一种实时应用的传输协议 (RFC-3550)_第17张图片

通过头部或有效载荷的有效性检查,接收方确认加密的存在和正确密钥的使用。在附录A.1和A.2中给出了RTP和RTCP头的有效性检查的例子。

为了与RFC 1889中RTP初始规范的现有实现保持一致,默认加密算法是密码块链(CBC)模式下的数据加密标准(DES)算法,如RFC 1423【29】第1.1节所述,除了填充到8个八位字节的倍数,如第5.1节中的P位所述。初始化向量为零,因为随机值是在RTP标头中提供的,或者是由复合RTCP数据包的随机前缀提供的。有关使用CBC初始化向量的详细信息,请参阅 附录[30]。 

支持这里指定的加密方法的实现应该始终支持CBC模式的DES算法作为该方法的默认密码,以最大限度地实现互操作性。选择这种方法的原因是它在网络上操作的实验音频和视频工具中被证明是简单实用的。然而,后来发现DES太容易损坏。

建议使用较强的加密算法(如Triple-DES)来替代默认算法。此外,安全的CBC模式要求每个包的第一个块与一个随机的、独立的、与密码块大小相同的IV进行异或。对于RTCP(部分),这是通过在每个包的前面加上一个32位随机数字来实现的,这个数字是为每个包独立选择的。对于RTP,时间戳和序列号从随机值开始,但连续的数据包不会被独立随机。需要注意的是,这两种情况(RTP和RTCP)的随机性都是有限的。高安全性的应用程序应该考虑其他更常规的保护手段。其他加密算法可以通过非rtp方式动态地为会话指定。特别是,正在开发的基于AES的SRTP协议[28]考虑了已知的明文和CBC明文操作问题,这将是未来的正确选择。

作为上述IP级或RTP级加密的替代方案,协议作为上述IP级或RTP级加密的替代方案,配置文件可以为加密编码定义额外的有效负载类型。文件可以为加密编码定义额外的有效负载类型。它对于同时处理解密和解码的硬件设备可能特别有用。对于需要RTP和较低层报头的链路级压缩且有效负载(但不是地址)的机密性足够的应用程序来说,这也是很有价值的,因为报头的加密影响了压缩。

9.2、身份验证和消息完整性

没有在RTP级别定义身份验证和消息完整性服务,因为如果没有密钥管理基础设施,这些服务将无法直接实现。认证和完整性服务预计将由较低层的协议提供。

10、拥塞控制

Internet上使用的所有传输协议都需要以某种方式解决拥塞控制问题 附录[31]。RTP也不例外,但是由于通过RTP传输的数据通常是无弹性的(以固定或控制的速率生成),RTP中控制拥塞的方法可能与其他传输协议(如TCP)有很大的不同。在某种意义上,无弹性降低了拥塞的风险,因为RTP流不会像TCP流那样扩展到消耗所有可用带宽。然而,无弹性也意味着当发生拥塞时,RTP流不能随意减少它在网络上的负载来消除拥塞。

由于RTP可以在许多不同的环境中用于各种各样的应用程序,所以没有一种单一的拥塞控制机制可以适用于所有的应用程序。因此,拥塞控制应该在每个RTP协议文件中合适地定义。对于一些协议文件,它可能足够包括一个适用性声明,限制该协议文件在通过工程避免拥塞的环境中的使用。对于其他协议文件,可能需要特定的方法,比如基于RTCP反馈的数据速率调整。

11、网络和传输协议上的RTP

本节描述在特定的网络和传输协议中携带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]冲突,SDP认为在同一会话描述中指定多个地址和多个端口是非法的,因为地址和端口的关联可能是不明确的。在RFC 2327的修订版中,这一限制将被放宽,允许使用一对一映射指定相同数量的地址和端口。

RTP数据包不包含长度字段或其他描述,因此RTP依赖底层协议提供长度指示。RTP报文的最大长度仅受底层协议的限制。

如果要在提供连续八位字节流而非消息(包)抽象的基础协议中承载RTP包,则必须定义RTP包的封装以提供成帧机制。如果底层协议可能包含填充,从而无法确定RTP有效载荷的范围,则也需要帧。这里没有定义成帧机制。

协议文件可以指定一种帧方法,即使在提供帧的协议中携带RTP,以便允许在一个较低层协议数据单元(如UDP包)中携带多个RTP包。在一个网络或传输包中携带多个RTP包可以减少报头开销,并可以简化不同流之间的同步。

12、协议常量摘要

本节包含本规范中定义的常量的摘要清单。

RTP有效负载类型(PT)常量是在其他协议文件中定义的,而不是在本文档中定义的。但是,RTP报头中包含标记位和负载类型的字节必须避免保留值200和201(十进制),以便在附录A.1中描述的报头验证过程中区分RTP报文和RTCP SR和RR报文类型。对于本协议中所示的一个标记位和7位有效负载类型字段的标准定义,此限制意味着保留有效负载类型72和73。

12.1、RTCP数据包类型

缩写 名字
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节)。

12.2、SDES类型

缩写 名字
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
NOTE private extensions 8

其他的SDES类型可以通过IANA注册(参见第15节)。

13、RTP配置文件和有效负载格式规范

一个特定应用程序的完整RTP规范需要一个或多个此处描述的两种类型的配套文档:配置文件(profile documents)和有效负载格式规范(payload format specifications)。

RTP可以用于各种不同需求的应用程序。通过允许在主协议规范中进行多种选择,然后在单独的配置文档中为特定的环境和应用程序类别选择适当的选择或定义扩展,从而提供了适应这些需求的灵活性。通常,在一个特定的RTP会话中,一个应用程序将只在一个配置文件下运行,因此在RTP协议本身中没有明确的指示使用哪个配置文件。音频和视频应用程序的配置文件可以在RFC 3551中找到。概要文件通常被命名为“RTP某某某配置文件”。

第二种类型的附带文档是有效载荷格式规范,它定义了如何在RTP中携带特定类型的有效载荷数据,例如H.261视频编码。这些文档通常被命名为“XYZ音频/视频编码的RTP有效载荷格式”。负载格式在多个配置文件下可能是有用的,因此可以独立于任何特定配置文件定义负载格式。如果需要,配置文件文档负责将该格式的默认映射分配给有效负载类型值。

在这个规范中,已经为配置文件中可能的定义确定了以下项目,但是这个列表并不是全面的。

RTP数据头(RTP data header):

RTP数据头中包含标记位和有效载荷类型字段的八位字节可以通过配置文件重新定义,以适应不同的要求,例如使用更多或更少的标记位(第5.3节)。

有效负载类型(Payload types):

假设包含有效载荷类型字段,配置文件通常会定义一组有效载荷格式(例如,媒体编码)和这些格式到有效载荷类型值的默认静态映射。一些有效载荷格式可以通过引用不同的有效载荷格式规范来定义。对于定义的每个负载类型,配置文件必须指定要使用的RTP时间戳时钟速率(第5.1节)。

附加RTP数据头(RTP data header additions):

如果在配置文件s类应用程序中需要一些独立于有效负载类型的附加功能,则可以将附加字段附加到固定RTP数据头(第5.3节)。

RTP数据头扩展(RTP data header extensions):

如果在特定于实现的扩展配置文件下允许使用RTP数据头扩展结构的前16位的内容,则必须定义该结构的内容(第5.3.1节)。

RTCP数据包类型(RTCP packet types):

可以定义新的特定于应用程序类的RTCP数据包类型,并向IANA注册。

RTCP报告间隔(RTCP report interval):

配置文件应规定将使用第6.2节中建议的用于计算RTCP报告间隔的常数值。这些是会话带宽的RTCP小数部分、最小报告间隔以及发送方和接收方之间的带宽分配。配置文件可以指定备用值,前提是它们已被证明以可伸缩的方式工作。

SR/RR扩展(SR/RR extension):

如果有关于发送方或接收方的额外信息需要定期报告,则可为RTCP SR和RR数据包定义扩展部分(第6.4.3节)。

SDES使用(SDES use):

概要文件可规定要传输或完全排除的RTCP SDES项目的相对优先级(第6.3.9节);CNAME项的替代语法或语义(第6.5.1节);LOC项的格式(第6.5.5节);NOTE 项的语义和用法(第6.5.7节);或向IANA注册的新SDES项目类型。

安全性(Security):

概要文件可以指定应用程序应该提供哪些安全服务和算法,并可以提供关于其适当使用的指导(第9节)。

字符串到key值的映射(String-to-key mapping):

配置文件可以指定如何将用户提供的密码或密码短语映射到加密密钥。

拥塞(Congestion):

配置文件应该指定适合该配置文件的拥塞控制行为。

底层协议(Underlying protocol):

可能需要使用特定的底层网络或传输层协议来携带RTP包。

传输映射(Transport mapping):

可以指定RTP和RTCP到传输级地址(例如UDP端口)的映射,而不是第11节中定义的标准映射。

封装(Encapsulation):

RTP数据包的封装可定义为允许在一个较低层数据包中承载多个RTP数据包,或在尚未这样做的底层协议上提供帧(第11节)。

预计并非每个应用程序都需要新的配置文件。在一个应用程序类中,最好扩展现有配置文件,而不是创建新的配置文件,以便于应用程序之间的互操作,因为每个应用程序通常只在一个配置文件下运行。通过IANA注册其他有效负载类型值或RTCP数据包类型,并将其描述发布在配置文件的附录或有效负载格式规范中,可以实现简单的扩展,如定义其他有效负载类型值或RTCP数据包类型。

14、安全注意事项

RTP有与底层协议相同的安全问题。例如,冒名顶替者可以伪造源或目的网络地址,或者更改报头或有效负载。在RTCP中,CNAME和NAME信息可以用来模拟另一个参与者。此外,RTP可以通过IP多播发送,这没有提供直接的方法让发送者知道所有接收发送的数据,因此没有隐私措施。无论正确与否,用户对音频和视频通信的隐私问题可能比他们对更传统的网络通信[33]更敏感。因此,在RTP中使用安全机制很重要。这些机制将在第9节中讨论。

RTP级转换器或混频器可用于允许RTP流量到达防火墙后面的主机。在设计和安装这些设备以及允许在防火墙后使用RTP应用程序时,应遵循适当的防火墙安全原则和实践,这些原则和实践超出了本文件的范围。

15、IANA注意事项

其他RTCP数据包类型和SDES项目类型可通过互联网分配号码管理局(IANA)注册。由于这些数字空间很小,允许无约束地注册新值将不是明智之举。为方便审查请求并促进在多个应用程序之间共享使用新类型,注册新值的请求必须在RFC或其他永久性和随时可获得的参考文件中进行记录,如另一个合作标准组织(如ITU-T)的产品。根据“指定专家”的意见,也可接受其他请求。(联系IANA获取当前专家的联系方式。)

RTP配置文件规范应该以“RTP/xxx”的形式向IANA注册一个配置文件名称,其中xxx是配置文件标题的简短缩写。这些名称用于更高级的控制协议,如会话描述协议(SDP), RFC 2327[15],以引用传输方法。

16、知识产权声明

略。

17、致谢

略。

附录 A - 算法

我们提供了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;

附录 A.1、RTP数据头有效性检查

RTP接收方应该检查传入数据包的RTP报头的有效性,因为它们可能是加密的,或者可能来自碰巧地址错误的不同应用程序。类似地,如果启用了根据第9节中所述方法的加密,则需要报头有效性检查来验证传入数据包已被正确解密,尽管报头有效性检查的失败(例如,未知负载类型)不一定表明解密失败。

对于来自以前没有监听到过的源的RTP数据包,只能进行弱有效性检查:

  • RTP版本字段必须等于2。
  • 有效载荷类型必须是已知的,特别是它不能等于SR或RR。
  • 如果设置了P位,那么报文的最后一个字节必须包含一个有效的字节数,特别是小于总报文长度减去报头大小。
  • 如果配置文件没有指定可以使用头扩展机制,X位必须为零。否则,扩展长度字段必须小于总包大小减去固定的报头长度和填充。
  • 数据包的长度必须与CC和负载类型一致(如果负载有一个已知的长度)。

后三种检查有些复杂,而且不总是可能的,只留下前两种总共只有几个比特。如果信息包中的SSRC标识符是以前接收到的,那么信息包可能是有效的,并检查序列号是否在预期范围内提供了进一步的验证。如果SSRC标识符以前没有出现过,那么携带该标识符的数据包可能会被认为是无效的,直到其中一小部分带着连续的序列号到达。如果产生的延迟是可接受的,则一旦实现验证,这些无效数据包可能会被丢弃,或者它们可能会被存储和传递。

下面所示的update_seq程序确保只有在顺序接收了MIN_SEQUENTIAL数据包之后,源才被声明为有效。它还验证新收到的信息包的序列号seq,并更新结构中信息包源的序列状态。

当第一次监听到一个新的源时,即其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,也不超过s->MAX_seq之后的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查找。

A.2、RTCP报头有效性检查

以下检查应该应用于RTCP报文:

  • RTP版本字段必须等于2。
  • 复合报文中第一个RTCP报文的载荷类型字段必须等于SR或RR。
  • 复合RTCP包的第一个包的填充位(P)应为零,因为只有在需要时才对最后一个包进行填充。
  • 每个RTCP报文的长度字段必须加起来等于接收到的复合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 */
}

A.3、确定预期和丢失的数据包数

为了计算数据包丢失率,需要知道期望的和实际从每个源接收到的RTP数据包的数量,使用下面代码中通过指针s引用的结构源中定义的每个源状态信息。接收到的包的数量只是包到达时的计数,包括任何延迟或重复的包。接收端可以通过接收到的最大序列号(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位的小数。

A.4、生成RTCP SDES数据包

此函数将一个SDES块构建到缓冲区b中,缓冲区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;
}

A.5、解析RTCP SDES数据包

这个函数解析一个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 */
    }
}

A.6、生成随机32位标识符

下面的子程序使用RFC1321[32]中发布的MD5程序生成一个随机的32位标识符。系统程序可能不是在所有操作系统上都有,但它们应该作为可能使用的信息类型的提示。其他适当的系统调用包括:

  • getdomainname()
  • getwd()
  • getrusage()

“实时”视频或音频样本也是一个很好的随机数来源,但必须小心避免使用关闭的麦克风或盲摄像头作为来源【17】。

建议使用此或类似程序为产生RTCP周期的随机数生成器生成初始种子(如附录a.7所示),为序列号和时间戳生成初始值,并生成SSRC值。由于此程序可能是CPU密集型的,因此直接使用它来生成RTCP周期是不合适的,因为可预测性不是问题。请注意,除非为类型参数提供不同的值,否则此程序在重复调用时会产生相同的结果,直到系统时钟的值发生变化。

/*
* 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 */

A.7、计算RTCP传输间隔

以下功能实现了6.2节中介绍的RTCP传输和接收规则。这些规则在几个函数中实现:

  • rtcp_interval()计算确定性的计算间隔,以秒为单位。参数的定义见第6.3节。
  • OnExpire()当RTCP传输计时器过期时调用。
  • OnReceive()在接收到RTCP报文时被调用。

OnExpire()和OnReceive()都有事件e作为参数。这是该参与者的下一个预定事件,可以是RTCP报告,也可以是BYE包。假设以下函数可用:

  • Schedule(time t, event e)将事件e安排在时间t发生。当时间t到达时,将以e作为参数调用OnExpire函数。
  • Reschedule(time t, event e)将先前调度的事件e重新调度为时间t。
  • SendRTCPReport(event e)发送RTCP报告。
  • SendBYEPacket(event e)发送BYE报文。
  • TypeOfEvent(event e) 如果正在处理的事件是要发送的BYE包,返回EVENT_BYE;否则返回EVENT_REPORT。
  • PacketType(p) 如果报文p是RTCP报告,则返回PACKET_RTCP_REPORT。如果是报文p是RTCP BYE报告,则返回PACKET_BYE。如果报文p是RTP报文,则返回PACKET_RTP。
  • ReceivedPacketSize()和SentPacketSize()返回报文的字节大小。
  • NewMember(p) 如果发送报文p的参与者不在成员列表中,则返回1,否则返回0。注意,这个功能对于一个完整的实现是不够的,因为RTP包中的每个CSRC标识符和BYE包中的每个SSRC都应该被处理。
  • NewSender(p) 如果发送包p的参与者不在成员列表的发送者子列表中,则返回1,否则返回0。
  • AddMember()和removmember()用于从成员列表中添加或删除参与者。
  • AddSender()和RemoveSender()用于从成员列表的发送者子列表中添加和删除参与者。

必须扩展这些功能,以实现将发送方和非发送方的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;
        }
    }
}

A.8、估计到达间抖动

下面的代码片段实现了第6.4.1节中给出的算法,用于计算将插入到接收报告的到达间抖动字段中的RTP数据到达间时间的统计方差的估计。输入是r->ts,即来自传入数据包的时间戳,以及到达时间,即以相同单位表示的当前时间。这里s指向源的状态,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;

或者,抖动估计可以保持为一个整数,但缩放以减少舍入误差。除了最后一行之外,其他计算过程是相同的:

int transit = arrival - r->ts;
int d = transit - s->transit;
s->transit = transit;
if (d < 0) d = -d;
s->jitter += d - ((s->jitter + 8) >> 4);

在这种情况下,接收报告的估计抽样如下:

rr->jitter = s->jitter >> 4;

附录B - RFC 1889的变更

见英文文档

参考

规范性引用文件

[1] Schulzrinne, H. and S. Casner, "RTP Profile for Audio and Video Conferences with Minimal Control", RFC 3551, July 2003.
[2] Bradner, S., "Key Words for Use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
[3] Postel, J., "Internet Protocol", STD 5, RFC 791, September 1981.
[4] Mills, D., "Network Time Protocol (Version 3) Specification, Implementation and Analysis", RFC 1305, March 1992.
[5] Yergeau, F., "UTF-8, a Transformation Format of ISO 10646", RFC 2279, January 1998.
[6] Mockapetris, P., "Domain Names - Concepts and Facilities", STD 13, RFC 1034, November 1987.
[7] Mockapetris, P., "Domain Names - Implementation and Specification", STD 13, RFC 1035, November 1987.
[8] Braden, R., "Requirements for Internet Hosts - Application and Support", STD 3, RFC 1123, October 1989.
[9] Resnick, P., "Internet Message Format", RFC 2822, April 2001.

资料性参考

[10] Clark, D. and D. Tennenhouse, "Architectural Considerations for a New Generation of Protocols," in SIGCOMM Symposium on Communications Architectures and Protocols , (Philadelphia, Pennsylvania), pp. 200--208, IEEE Computer Communications Review, Vol. 20(4), September 1990.
[11] Schulzrinne, H., "Issues in designing a transport protocol for audio and video conferences and other multiparticipant real-time applications." expired Internet Draft, October 1993.

[12] Comer, D., Internetworking with TCP/IP , vol. 1. Englewood Cliffs, New Jersey: Prentice Hall, 1991.
[13] Rosenberg, J., Schulzrinne, H., Camarillo, G., Johnston, A., Peterson, J., Sparks, R., Handley, M. and E. Schooler, "SIP: Session Initiation Protocol", RFC 3261, June 2002.
[14] International Telecommunication Union, "Visual telephone systems and equipment for local area networks which provide a non-guaranteed quality of service", Recommendation H.323,
Telecommunication Standardization Sector of ITU, Geneva, Switzerland, July 2003.
[15] Handley, M. and V. Jacobson, "SDP: Session Description Protocol", RFC 2327, April 1998.
[16] Schulzrinne, H., Rao, A. and R. Lanphier, "Real Time Streaming Protocol (RTSP)", RFC 2326, April 1998.
[17] Eastlake 3rd, D., Crocker, S. and J. Schiller, "Randomness Recommendations for Security", RFC 1750, December 1994.
[18] Bolot, J.-C., Turletti, T. and I. Wakeman, "Scalable Feedback Control for Multicast Video Distribution in the Internet", in SIGCOMM Symposium on Communications Architectures and Protocols, (London, England), pp. 58--67, ACM, August 1994.
[19] Busse, I., Deffner, B. and H. Schulzrinne, "Dynamic QoS Control of Multimedia Applications Based on RTP", Computer Communications , vol. 19, pp. 49--58, January 1996.
[20] Floyd, S. and V. Jacobson, "The Synchronization of Periodic Routing Messages", in SIGCOMM Symposium on Communications Architectures and Protocols (D. P. Sidhu, ed.), (San Francisco, California), pp. 33--44, ACM, September 1993. Also in [34].
[21] Rosenberg, J. and H. Schulzrinne, "Sampling of the Group Membership in RTP", RFC 2762, February 2000.
[22] Cadzow, J., Foundations of Digital Signal Processing and Data Analysis New York, New York: Macmillan, 1987.
[23] Hinden, R. and S. Deering, "Internet Protocol Version 6 (IPv6) Addressing Architecture", RFC 3513, April 2003.
[24] Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. and E. Lear, "Address Allocation for Private Internets", RFC 1918, February 1996.

[25] Lear, E., Fair, E., Crocker, D. and T. Kessler, "Network 10 Considered Harmful (Some Practices Shouldn’t be Codified)", RFC 1627, July 1994.
[26] Feller, W., An Introduction to Probability Theory and its Applications, vol. 1. New York, New York: John Wiley and Sons, third ed., 1968.
[27] Kent, S. and R. Atkinson, "Security Architecture for the Internet Protocol", RFC 2401, November 1998.
[28] Baugher, M., Blom, R., Carrara, E., McGrew, D., Naslund, M., Norrman, K. and D. Oran, "Secure Real-time Transport Protocol", Work in Progress, April 2003.
[29] Balenson, D., "Privacy Enhancement for Internet Electronic Mail: Part III", RFC 1423, February 1993.
[30] Voydock, V. and S. Kent, "Security Mechanisms in High-Level Network Protocols", ACM Computing Surveys, vol. 15, pp. 135-171, June 1983.
[31] Floyd, S., "Congestion Control Principles", BCP 41, RFC 2914, September 2000.
[32] Rivest, R., "The MD5 Message-Digest Algorithm", RFC 1321, April 1992.
[33] Stubblebine, S., "Security Services for Multimedia Conferencing", in 16th National Computer Security Conference, (Baltimore, Maryland), pp. 391--395, September 1993.
[34] Floyd, S. and V. Jacobson, "The Synchronization of Periodic Routing Messages", IEEE/ACM Transactions on Networking, vol. 2, pp. 122--136, April 1994.

你可能感兴趣的:(RFC,rfc)