LV025-H264的RTP打包
好了,通过学习 RTP 和 RTCP 的基础知识,我们了解了 RTP 包的协议格式和主要负责的功能,也知道了 RTCP 的协议格式和其主要承担的责任。接下来我们就进入实际工程部分的知识了。
我相信通过前面课程的学习,你对 H264 的码流结构已经较为熟悉了,H264 是在工程中用得比较多的编码标准,所以这里我们以 H264 为例来讲讲实际工程开发中,我们怎么将 H264 码流打包成 RTP 包。
我们前面说了,H264 码流是放在 RTP 的有效载荷部分的。因此有效载荷前面的 RTP 头部跟码流本身是没有关系的,所以我们可以直接先将头部的字段填好就可以。接下来我们需要将 H264 码流填充到 RTP 有效载荷中去。
RTP H264 码流打包分为三种方式:分别是单 NALU 封包方式、组合封包方式、分片封包方式。顾名思义,单 NALU 封包方式是一个 NALU 打一个 RTP 包;而组合封包方式就是多个 NALU 打一个 RTP 包;分片封包方式则是一个 NALU 分开放在连续的多个 RTP 包中。下面我们来分别看一下各种打包方式是怎么样的。
Tips:对于音频来说,因为音频收到采样率的影响,单帧音频长度比较小,例如 16K 采样率,16bit 深度,40ms 一帧,无压缩的 PCM 数据长度是 1280 字节,压缩编码后会更小。一般一个 RTP 包就可以完成封装。在进行音频解封装的时候接收到一包音频的时候可以认为是一帧数据。
一、单 NALU 封包方式
单 NALU 封包方式非常简单。我们在 RTP 头部的后面,直接放置 NALU 数据即可。注意,根据 RTP 的规定,这里需要将 NALU 数据前面的起始码去除,不要将起始码也带入 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| Type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+为了让你更直观地理解这种打包方式,我给出了打包的示意图。具体如下所示:

这种打包方式适合于单个 RTP 包小于 1500 字节(MTU 大小)的时候。一般来说,一些 P 帧和 B 帧编码之后比较小,就可以使用这种打包方式。
Tips:
单个 NALU 数据长度小于 RTP 负载长度 - 4,可以进行单包封装,码流需要去掉起始码,NALU 包含 NALU Header。
对于 H265 来说,H265 和 H264 的 NALU 头不同,占用两个字节,单包的时候两者的格式是相同的,封装时只需要去掉 4 字节的起始码就可以。
二、组合封包方式
组合封包方式稍微复杂一些。它是将多个 NALU 放置在一个 RTP 包中。在 RTP 头部之后,且放置 NALU 数据之前,我们需要放置一个 1 字节的 STAP-A 的头部。其中,STAP-A Header 跟 NALU Header 的格式是一样的,只是 Type 字段的值不一样。因此,你可以参考 H264 码流结构课程中 NALU 小节来理解 STAP-A 的头部的格式。具体如下图所示:
其中,Type 的取值如下表所示。这里我需要提醒你一下,表中的 24 和 25 类型就是 STAP 组合封包方式。注意,我们这里只讲 STAP-A,这是因为 STAP-B 很少用到。
| Type | 类型 | 含义 |
|---|---|---|
| 24 | STAP-A | 单一时间的组合包 |
| 25 | STAP-B | 单一时间的组合包 |
| 26 | MTAP16 | 多个时间的组合包 |
| 27 | MTAP24 | 多个时间的组合包 |
| 28 | FU-A | 分片的单元 |
| 29 | FU-B | 分片的单元 |
放置完 STAP-A Header 之后,在每一个 NALU 的前面我们需要放置一个 2 字节的 size 字段,用于表示后面的 NALU 的大小。之后才是 NALU 的数据。记住同样需要去掉起始码。其格式如下:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+同样地,为了让你更直观地理解这种打包方式,我也给出了打包的示意图。具体如下所示:

这种打包方式适合于单个 NALU 很小的时候。因此,我们将多个 NALU 打包到一起也小于 1500 字节的时候就可以使用。但是由于一般多个视频帧加到一起还小于 1500 的情况比较少,所以视频数据的 RTP 打包一般来说用组合封包方式的情况也很少。
三、分片封包方式
分片封包就更复杂一些了,但却是我们经常用到的打包方式。
它是将一个 NALU 分开打包在连续的多个 RTP 包中。因此,我们首先需要一个 1 字节的 FU indicator 来表示当前 RTP 包是不是分片封包方式,再用一个 1 字节的 FU Header 来表示当前这个 RTP 包是不是 NALU 的第一个包,是不是 NALU 的最后一个包,以及 NALU 的类型。
为什么需要表示是不是第一个包以及是不是最后一个包呢?这是因为一个 NALU 被分开放在多个 RTP 包中,我们需要知道哪个是第一个 NALU 分片,哪个是最后一个 NALU 分片,以及哪些是中间分片。这样我们才能组成一个完整的 NALU。
那你可能会问,NALU 不是已经在 NALU Header 中有了 NALU Type 字段吗?为什么 FU Header 中还要有 NALU Type 呢?这是因为分片封包时需要去掉 NALU Header。因此,我们需要通过 FU Header 中的 NALU Type 得到 NALU 的类型。
因为我们一般只使用 FU-A,所以接下来讲述的将是 FU-A 的分片封包方式。
FU indicator 的格式如下所示(RFC 6184)
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+FU indicator type 字段的值为 28 和 29 时,分别表示 FU- A 和 FU-B。第 RFC 6184 - 5.3 节描述了 F 位的使用。NRI 字段的值必须根据 NALU 单元中 NRI 字段的值来设置。也就是说,前面的 F 和 NRI 其实就是和 NALU Header 的高 3bit 是相同的。
FU Header 格式如下所示(RFC 6184):
The FU header has the following format:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+这里简单解释一下各字段的含义:
- S(start):起始位,占 1bit,为 1 则表示是 NALU 的第一个 RTP 包。
- E(end):结束位,占 1bit,为 1 则表示是 NALU 的最后一个 RTP 包。
- R:预留位,占 1bit。
- Type:占 5bits,表示 NALU 类型,这里和 NALU Header 的 Type 字段一致。
分片打包的格式如下:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A分片打包的示意图如下:

这种打包方式主要用于将 NALU 数据打包成一个 RTP 包时大小大于 1500 字节的时候,这是经常使用的视频 RTP 打包方法。
好了,以上就是三种打包方式。我们怎么选择使用哪种方式打包呢?一般来说,我们在一个 H264 码流中会混合使用多种 RTP 打包方式。一般来说,对于小的 P 帧、B 帧还有 SPS、PPS 我们可以使用单个 NALU 封包方式。而对于大的 I 帧、P 帧或 B 帧,我们使用分片封包方式。当然,你可以根据实际情况进行选择。
Tips:对于 H265 来说,它的 NALU Header 占用 2 个字节,所以在单个 NALU 需要多个 rtp 包的时候,FU indicator 占用 2 个字节,FU Header 也一样。
四、小结
好了,以上就是这节课的主要内容。接下来我们来总结一下。
首先,我们一起讨论了 RTP 协议和 RTCP 协议的主要作用。RTP 协议用来封装音视频数据,并且将音视频数据和一些基本信息打包到 RTP 包中传输到接收端。而 RTCP 协议则辅助 RTP 协议使用,其中一个主要的功能就是用来统计 RTP 包的发送情况,比如说丢包率和具体哪些 RTP 包在网络发送的过程中丢失了。RTCP 包将这些信息收集起来发送给 RTP 包的发送端。
然后,我们说明了 RTP 和 RTCP 协议是带宽预测和拥塞控制的基础,并且重点强调了 RTCP 协议本身只统计信息,而带宽预测和拥塞控制算法是需要我们自己实现的,RTCP 协议本身并没有这个功能。
最后,我们介绍了 H264 的 RTP 打包方式,总共有三种,分别是单 NALU 封包方式、组合封包方式和分片封包方式。
- 单 NALU 封包方式,一般适合 NALU 大小比较小,且打包出来的 RTP 大小小于 1500 字节的时候使用。
- 组合封包方式,适合多个 NALU 都很小,且合并在一起打包的 RTP 包小于 1500 字节的时候使用。
- 分片打包,则适合 NALU 比较大的情况,且打包成一个 RTP 包其大小会大于 1500 字节的时候使用。
这几种打包方式不是说只能选择一种,在一个 RTP 流中是可以存在多种打包方式的,即可以混合使用。
最后再一次强调,这节课和 H264 码流结构那节课都是非常重要的。它们在实际视频开发的过程中会经常用到,希望你可以熟练掌握。
思考题:为什么我们在选择 RTP 打包方式的时候,需要根据 NALU 大小是不是大于 1500 字节(MTU)来选择?
评论区答案:不超过 1500 主要是因为 Udp 协议的 MTU 为 1500,超过了会导致 Udp 分片传输,而分片的缺点是丢了一个片,整包数据就废弃了