RTP 协议介绍#
实时传输协议 RTP(Real-time Transport Protocol)是一个网络传输协议,它是由 IETF 的多媒体传输工作小组 1996 年在 RFC 1889 中公布的,后在 RFC3550 中进行更新。
它作为因特网标准在 RFC 3550 有详细说明。
RTP 协议详细说明了在互联网上传递音频和视频的标准数据包格式。它一开始被设计为一个多播协议,但后来被用在很多单播应用中。RTP 协议常用于流媒体系统(配合 RTSP 协议),视频会议和一键通(Push toTalk)系统(配合 H.323 或 SIP),使它成为 IP 电话产业的技术基础。RTP 协议和 RTP 控制协议 RTCP 一起使用,而且它是建立在用户数据报协议上的(UDP)。
RTP 和 RTCP#
-
数据传输协议 RTP 用于实时传输数据。该协议提供的信息包括:时间戳(用于同步)、序列号(用于丢包和重排序检测)、以及负载格式(用于说明数据的编码格式)。
-
控制协议 RTCP,用于 QoS 反馈和同步媒体流。相对于 RTP 来说,RTCP 所占的带宽非常小,通常只有 5%。
RTP 的优势#
提到流媒体传输、视频监控、视频会议、语音电话(VOIP),都离不开 RTP 协议的应用,但是为什么要使用 RTP 来进行流媒体的传输呢?为什么一定要用 RTP?
像 TCP 这样的可靠传输协议,通过超时和重传机制来保证传输数据流中的每一个 bit 的正确性,但这样会使得无论从协议的实现还是传输的过程都变得非常的复杂。而且,当传输过程中有数据丢失的时候,由于对数据丢失的检测(超时检测)和重传,会数据流的传输被迫暂停和延时。
RTP 协议是一种基于 UDP 的传输协议,RTP 本身并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制,它依靠 RTCP 提供这些服务。这样,对于那些丢失的数据包,不存在由于超时检测而带来的延时,同时,对于那些丢弃的包,也可以由上层根据其重要性来选择性的重传。
RTP 的协议层次#
流媒体体系结构
流媒体应用中典型的协议体系结构。
从图中可以看出,RTP 被划分在传输层,它建立在 UDP 上。同 UDP 协议一样,为了实现其实时传输功能,RTP 也有固定的封装形式。RTP 用来为端到端的实时传输提供时间信息和流同步,但并不保证服务质量。服务质量由 RTCP 来提供。
RTP 工作机制#
当应用程序建立一个 RTP 会话时,应用程序将确定一对目的传输地址。目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给 RTP 包,一个给 RTC P 包,使得 RTP/RTCP 数据能够正确发送。RTP 数据发向偶数的 UDP 端口,而对应的控制信号 RTCP 数据发向相邻的奇数 UDP 端口(偶数的 UDP 端口 + 1),这样就构成一个 UDP 端口对。RTP 的发送过程如下,接收过程则相反。
- RTP 协议从上层接收流媒体信息码流(如 H.263),封装成 RTP 数据包;RTCP 从上层接收控制信息,封装成 RTCP 控制包。
- RTP 将 RTP 数据包发往 UDP 端口对中偶数端口;RTCP 将 RTCP 控制包发往 UDP 端口对中的奇数端口。
RTP 分组只包含 RTP 数据,而控制是由 RTCP 协议提供。RTP 在 1025 到 65535 之间选择一个未使用的偶数 UDP 端口号,而在同一次会话中的 RTCP 则使用下一个奇数 UDP 端口号。端口号 5004 和 5005 分别用作 RTP 和 RTCP 的默认端口号。RTP 分组的首部格式如图 2 所示,其中前 12 个字节是必须的。
应用层#
RTP 应当是应用层的一部分。在应用的发送端,开发者必须编写用 RTP 封装分组的程序代码,然后把 RTP 分组交给 UDP。在接收端,RTP 分组通过 UDP 接口进入应用层后,还要利用开发者编写的程序代码从 RTP 分组中把应用数据块提取出来。
RTP 报文#
首先,我们看看 RTP 的包头。RTP 报文头格式(见 RFC3550 Page12):
- 版本号(V):2 比特,用来标志使用的 RTP 版本。
- 填充位(P):1 比特,如果该位置位,则该 RTP 包的尾部就包含附加的填充字节。
- 扩展位(X):1 比特,如果该位置位的话,RTP 固定头部后面就跟有一个扩展头部。
- CSRC 计数器(CC):4 比特,含有固定头部后面跟着的 CSRC 的数目。
- 标记位(M):1 比特,该位的解释由配置文档(Profile)来承担。对于在 RTP 以最小的控制配置文件下在音频和视频会议下运行的音频流,标记位设置为 1,表示在一段静默期后发送的第一个数据包,否则设置为 0。
- 载荷类型(PayloadType):7 比特,标识了 RTP 载荷的类型。
- 序列号(SN):16 比特,每发送一个 RTP 数据包,序列号增加 1。接收端可以据此检测丢包和重建包序列。
- 时间戳(Timestamp):2 比特,记录了该包中数据的第一个字节的采样时刻。
- 同步源标识符(SSRC):32 比特,同步源就是指 RTP 包流的来源。在同一个 RTP 会话中不能有两个相同的 SSRC 值。该标识符是随机选取的 RFC1889 推荐了 MD5 随机算法。
- 贡献源列表(CSRC List):0~15 项,每项 32 比特,用来标志对一个 RTP 混合器产生的新包有贡献的所有 RTP 包的源。由混合器将这些有贡献的 SSRC 标识符插入表中。SSRC 标识符都被列出来,以便接收端能正确指出交谈双方的身份。
RTP 扩展头结构#
若 RTP 固定头中的扩展比特位置 1(注意:如果有 CSRC 列表,则在 CSRC 列表之后),则一个长度可变的头扩展部分被加到 RTP 固定头之后。头扩展包含 16 比特的长度域,指示扩展项中 32 比特字的个数,不包括 4 个字节扩展头(因此零是有效值)。
RTP 固定头之后只允许有一个头扩展。为允许多个互操作实现独立生成不同的头扩展,或某种特定实现有多种不同的头扩展,扩展项的前 16 比特用以识别标识符或参数。这 16 比特的格式由具体实现的上层协议定义。基本的 RTP 说明并不定义任何头扩展本身。
RTP 会话#
当应用程序建立一个 RTP 会话时,应用程序将确定一对目的传输地址。目的传输地址由一个网络地址和一对端口组成,有两个端口:一个给 RTP 包,一个给 RTCP 包,使得 RTP/RTCP 数据能够正确发送。RTP 数据发向偶数的 UDP 端口,而对应的控制信号 RTCP 数据发向相邻的奇数 UDP 端口(偶数的 UDP 端口 + 1),这样就构成一个 UDP 端口对。
RTP 发送过程#
- RTP 协议从上层接收流媒体信息码流(如 H.263),封装成 RTP 数据包;RTCP 从上层接收控制信息,封装成 RTCP 控制包。
- RTP 数据包发往 UDP 端口对中偶数端口;RTCP 将 RTCP 控制包发往 UDP 端口对中的接收端口。
RTP profile 机制#
RTP 为具体的应用提供了非常大的灵活性,它将传输协议与具体的应用环境、具体的控制策略分开,传输协议本身只提供完成实时传输的机制,开发者可以根据不同的应用环境,自主选择合适的配置环境、以及合适的控制策略。
这里所说的控制策略指的是你可以根据自己特定的应用需求,来实现特定的一些 RTCP 控制算法,比如前面提到的丢包的检测算法、丢包的重传策略、一些视频会议应用中的控制方案等等(这些策略我可能将在后续的文章中进行描述)。
对于上面说的合适的配置环境,主要是指 RTP 的相关配置和负载格式的定义。RTP 协议为了广泛地支持各种多媒体格式(如 H.264, MPEG-4, MJPEG, MPEG),没有在协议中体现出具体的应用配置,而是通过 profile 配置文件以及负载类型格式说明文件的形式来提供。对于任何一种特定的应用,RTP 定义了一个 profile 文件以及相关的负载格式说明。
RTCP#
服务质量的监视与反馈、媒体间的同步,以及多播组中成员的标识。在 RTP 会话期间,各参与者周期性地传送 RTCP 包。RTCP 包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料,因此,各参与者可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。RTP 和 RTCP 配合使用,它们能以有效的反馈和最小的开销使传输效率最佳化,因而特别适合传送网上的实时数据。
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| IC | PT | Length |
+---------------+---------------+-------------------------------+
| |
| Format-specific information |
| |
| +-----------------------+
| | Padding if P = 1 |
+---------------------------------------+-----------------------+
各个字段的含义为:
- 版本号,固定为 2;
- 填充(Padding)标志位,1 表示有填充;
- 条目数量(Item Count,简称 IC),在 RTCP 包的内容是条目列表时,用来表明条目数量;其他情况可作为别的含义;
- 包类型(Packet Type,也简称 PT),rfc3550 定义了五种标准包类型,分别是 sender report (SR), receiver report (RR), source description (SDES), goodbye (BYE), application-specific message (APP);
- 长度,包头之后的内容总长度,单位是四字节,允许为 0;
RTCP 包不会单独的被传输,它需要打包在一起形成复合包(compound packets)进行传输。每一个复合包都会被一个底层的包封装(通常是 UDP/IP 包)用来传输。如果要对复合包进行加密,那么 RTCP 的包组的前缀通常是一个 32 位的随机数。复合包的结构如下图所示:
+---------------------------------------------------------------+
| |
| IP header |
| |
+---------------------------------------------------------------+
| |
| UDP header |
| |
+---------------------------------------------------------------+
| Random prefix (if encrypted) |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|V=2|P| IC | PT | Length |
+---------------+---------------+-------------------------------+
| | first
| | RTCP
| Format-specific information | packet
| |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|V=2|P| IC | PT | Length |
+---------------+---------------+-------------------------------+
| | second
| | RTCP
| Format-specific information | packet
| |
+---------------------------------------------------------------+
RTCP 也是用 UDP 来传送的,但 RTCP 封装的仅仅是一些控制信息,因而分组很短,所以可以将多个 RTCP 分组封装在一个 UDP 包中。RTCP 有如下五种分组类型。
类型 | 缩写表示 | 用途 |
---|---|---|
200 | SR(Sender Report) | 发送端报告 |
201 | RR(Receiver Report) | 接收端报告 |
202 | SDES(Source Description Items) | 源点描述 |
203 | BYE | 结束传输 |
204 | .APP | 特定应用 |
上述五种分组的封装大同小异,下面只讲述 SR 类型,而其它类型请参考 RFC3550。
发送端报告分组 SR(Sender Report)用来使发送端以多播方式向所有接收端报告发送情况。SR 分组的主要内容有:相应的 RTP 流的 SSRC,RTP 流中最新产生的 RTP 分组的时间戳和 NTP,RTP 流包含的分组数,RTP 流包含的字节数。SR 包的封装如图所示:
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| RC | PT=SR=200 | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+---------------------------------------------------------------+
| NTP timestamp |
| |
+---------------------------------------------------------------+
| RTP timestamp |
+---------------------------------------------------------------+
| Sender's packet count |
+---------------------------------------------------------------+
| Sender's octet count |
+---------------------------------------------------------------+
| Receiver report block(s) |
| |
- 版本(V):同 RTP 包头域。
- 填充(P):同 RTP 包头域。
- 接收报告计数器(RC):5 比特,该 SR 包中的接收报告块的数目,可以为零。
- 包类型(PT):8 比特,SR 包是 200。
- 长度域(Length):16 比特,其中存放的是该 SR 包以 32 比特为单位的总长度减一。
- 同步源(SSRC):SR 包发送者的同步源标识符。与对应 RTP 包中的 SSRC 一样。
- NTP Timestamp(Network time protocol):SR 包发送时的绝对时间值。NTP 的作用是同步不同的 RTP 媒体流。
- RTP Timestamp:与 NTP 时间戳对应,与 RTP 数据包中的 RTP 时间戳具有相同的单位和随机初始值。
- Sender's packet count:从开始发送包到产生这个 SR 包这段时间里,发送者发送的 RTP 数据包的总数。SSRC 改变时,这个域清零。
- Sender's octet count:从开始发送包到产生这个 SR 包这段时间里,发送者发送的净荷数据的总字节数(不包括头部和填充)。发送者改变其 SSRC 时,这个域要清零。
- 同步源 n 的 SSRC 标识符:该报告块中包含的是从该源接收到的包的统计信息。
- 丢失率 (Fraction Lost):表明从上一个 SR 或 RR 包发出以来从同步源 n (SSRC_n) 来的 RTP 数据包的丢失率。
- 累计的包丢失数目:从开始接收到 SSRC_n 的包到发送 SR, 从 SSRC_n 传过来的 RTP 数据包的丢失总数。
- 收到的扩展最大序列号:从 SSRC_n 收到的 RTP 数据包中最大的序列号,
- 接收抖动 (Interarrival jitter):RTP 数据包接受时间的统计方差估计
- 上次 SR 时间戳 (Last SR,LSR):取最近从 SSRC_n 收到的 SR 包中的 NTP 时间戳的中间 32 比特。如果目前还没收到 SR 包,则该域清零。
- 上次 SR 以来的延时 (Delay since last SR,DLSR):上次从 SSRC_n 收到 SR 包到发送本报告的延时。
RTP 时间戳#
时间戳反映了 RTP 分组中的数据的第一个字节的采样时刻,在一次会话开始时的时间戳初值也是随机选择的。即使是没有信号发送时,时间戳的数值也要随时间不 断的增加。接收端使用时间戳可准确知道应当在什么时间还原哪一个数据块,从而消除传输中的抖动。时间戳还可用来使视频应用中声音和图像同步。
在 RTP 协议中并没有规定时间戳的粒度,这取决于有效载荷的类型,如采样频率为 90000 HZ,那么时间戳单位为 1/90000, 如果每秒发送 30 帧,那么时间戳增量就是 90000/30 = 3000。
时间戳增量是发送第二个 RTP 包相距发送第一个 RTP 包时的时间间隔,如果是视频应该是发送每帧的间隔时间。
代码#
RTP header
/*
* RTP header
*/
typedef struct
{
#if 0 //BIG_ENDIA
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 */
#else
unsigned int cc:4; /* CSRC count */
unsigned int x:1; /* header extension flag */
unsigned int p:1; /* padding flag */
unsigned int version:2; /* protocol version */
unsigned int pt:7; /* payload type */
unsigned int m:1; /* marker bit */
unsigned int seq:16; /* sequence number */
#endif
u_int32 ts; /* timestamp */
u_int32 ssrc; /* synchronization source */
u_int32 csrc[1]; /* optional CSRC list */
} rtp_hdr_t;
RTCP Common header
/*
* RTCP common header word
*/
typedef struct {
#if 0 //BIG_ENDIA
unsigned int version:2; /* protocol version */
unsigned int p:1; /* padding flag */
unsigned int count:5; /* varies by packet type */
#else
unsigned int count:5; /* varies by packet type */
unsigned int p:1; /* padding flag */
unsigned int version:2; /* protocol version */
#endif
unsigned int pt:8; /* RTCP packet type */
unsigned short length; /* pkt len in words, w/o this word */
} rtcp_common_t;