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 包中。接收者可以通过 payload 的第一个字节识别载荷结构,我们称它为 payload header。payload header 总是被格式化为 NALU header,也就是说它和 NALU header 的结构一致,各字段具有相同意义。
下面我们来分别看一下各种打包方式是怎么样的。
Tips:对于音频来说,因为音频收到采样率的影响,单帧音频长度比较小,例如 16K 采样率,16bit 深度,40ms 一帧,无压缩的 PCM 数据长度是 1280 字节,压缩编码后会更小。一般一个 RTP 包就可以完成封装。在进行音频解封装的时候接收到一包音频的时候可以认为是一帧数据。
二、封包方式
1. 单 NALU 封包方式
1.1 什么是单 NAL 单元包
单个 NAL 单元包是指,H.264 码流(NALU 序列)中的每个 NALU 都独立封装成一个 RTP 包,不拆分,不组合。
Tips:单个 NAL 单元数据包必须只包含一个 NAL 单元, 这意味着在单个 NAL 单元数据包中既不能使用聚合数据包, 也不能使用分片单元,即。按照 RTP 序列号顺序解封装单个 NAL 单元数据包而组成的 NAL 单元流必须符合 NAL 单元解码顺序。
1.2 数据格式
单 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 字节的起始码就可以。
2. 组合封包方式(STAP-A 包)
2.1 什么是组合包
组合包是指,H.264 码流(NALU 序列)中有若干 NALU 尺寸特别小,因此可以将多个 NALU 进行组合后封装进一个 RTP 包。
Tips:单次聚合数据包(STAP)在聚合所有共享相同 NALU 时间的 NAL 单元时使用。STAP-A 的有效载荷不包括 DON(Decoding Order Number) 区域 (STAP-B 包含), 至少由一个单次聚合单元组成。
2.2 数据格式
组合封包方式稍微复杂一些。它是将多个 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 的数据。记住 同样需要去掉起始码。下面是由两个单次聚合单元组成的 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 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 打包一般来说用组合封包方式的情况也很少。
3. 分片封包方式(FU-A 包)
3.1 什么是分片单元
分片封包就更复杂一些了,但却是我们经常用到的打包方式。使用 FU-A 意味着一个 NALU 大于 TCP/UDP 支持的最大传输大小即 MSS(有些地方也说是分片单元是指,H.264 码流(NALU 序列)中 NALU 长度超过 MTU 大小限制)。
MTU(IP_header + TCP_header/UDP_header) = 1500 bytes.
MSS = MTU - IP_header(20) - TCP_header(20)/UDP_heaer(8) = (TCP)1460/(UDP)1472 bytes.上一节介绍的交错帧会多 4 个字节头, 所以选取的分片基准大小最大为 1460 - 4 = 1456 bytes, or 1472-4 = 1468 bytes。有些会封装选取的基准值为 1376 bytes。
3.2 数据格式
它是将一个 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 流的格式
1. 数据格式
一般是下面这样的格式:

因为 RTP 包在封装和传输的时候是没有 rtp 包长度的,所以一般在传输的时候可以自己添加一个,例如定义一个 4 字节的头,前两个字节固定,后两个字节标识 rtp 包的长度。要是需要写到另外一个缓冲区,与其他进程交互还可以再自定义一个交互协议头,来快速定位 rtp 包,所以其实有时候看到的封装过程中可能得到的一包数据是这样的:
[与其他进程交互的4字节标识][带有rtp包长度的4字节标识][RTP 12字节标准头][RTP负载数据等]封装私有信息是流媒体开发中非常常见的需求,常用于传输自定义元数据、同步信令、时间戳或业务控制指令。一般可以有两种方式:
- RTP 头部扩展
适合封装少量且需要与当前 RTP 包强绑定的私有信息(如:视频帧时间戳、坐标信息、自定义序号等)。RFC 5285 - A General Mechanism for RTP Header Extensions 定义了这种扩展机制。
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|1| CC |M| PT | sequence number | <- X位(扩展标志)置1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| CSRC (zero or more) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0xBE | 0xDE | length=3 | <- 扩展头Profile(如0xBEDE代表One-Byte扩展)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ID | L=0 | private data1 | ID | L=1 | private data2 | <- 扩展元素(ID:局部唯一标识, L:数据长度-1)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| private data1 (续) | 0x00 填充 | | <- 填充至32位对齐
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Payload |
| (标准音视频码流数据) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- 使用自定义动态 Payload Type (Payload 复用)
如果需要传输大量的私有数据(几百字节甚至更大),且这些数据可以独立于音视频流存在,可以将私有数据当作一种独立的“媒体流”来传输。数据长度几乎无限制,完全自定义内部结构。但是无法通过标准媒体播放器播放,必须有自定义接收端解析;NAT 穿透和防火墙可能对未知 PT 类型不友好。
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=100 | sequence number | <- PT使用96-127之间的自定义值
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Private Payload | <- 完全自定义的私有数据结构
| (例如:TLV格式、JSON、Protobuf、二进制流) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+3. header 解析
buffer 中存放的是一帧完整的 rtp 包,最开始 12 字节为标准 rtp 头,可以通过下面的代码获取相关字段的值:
marker_bit = buffer[1] >> 7;
payload_type = buffer[1] & 0x7f;
sequence_number = (buffer[2] << 8) | (buffer[3]);
time_stamp = (buffer[4] << 24)
| (buffer[5] << 16)
| (buffer[6] << 8)
| (buffer[7]);
payload_buf = &buffer[12]; // 假设只有 12 字节标准头4. RTP 流示例
4.1 常见 RTP 头
首先要做的就是查找 rtp 流的数据头,怎么找?对于有 4 字节私有包头的,直接搜索规定的 4 字节中固定字段即可,要是没有的话,就根据 RTP 12 字节标准头来找了:
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+一般是以 80 60 开头,也有 80 E6 等,会组合出多种头,下面是之前遇到过的:
| hex | Binary V P X CC M PT | Version | Padding | eXtension | CSRC count | Marker | Payload Type | 说明 |
|---|---|---|---|---|---|---|---|---|
80 E6 | 10000000 11100110 | 2 | 0 | 0 | 0 | 1 | 0x66 (102) | 音频帧(G726) |
80 E0 | 10000000 11100000 | 2 | 0 | 0 | 0 | 1 | 0x60 (96) | 视频帧 |
80 60 | 10000000 01100000 | 2 | 0 | 0 | 0 | 0 | 0x60 (96) | 视频帧 |
80 88 | 10000000 10001000 | 2 | 0 | 0 | 0 | 1 | 0x08 (8) | 音频帧(G711A) |
90 E0 | 10010000 11100000 | 2 | 0 | 1 | 0 | 1 | 0x60 (96) | 视频帧(含扩展) |
90 60 | 10010000 01100000 | 2 | 0 | 1 | 0 | 0 | 0x60 (96) | 视频帧(含扩展) |
90 F0 | 10010000 11110000 | 2 | 0 | 1 | 0 | 1 | 0x70 (112) | 私有帧 |
A0 E0 | 10100000 11100000 | 2 | 1 | 0 | 0 | 1 | 0x60 (96) | 视频帧(有填充) |
A0 60 | 10100000 01100000 | 2 | 1 | 0 | 0 | 0 | 0x60 (96) | 视频帧(有填充) |
Tips:视频帧负载一般是 0x60,音频帧类型比较多,可用的值主要是自己约定,这里遇到过的一个 0x8 是表示 G711A,还有遇到过 AAC 是 0x68(102)。
4.2 一段 RTP 数据
这里可以截取一段 RTP 流,左侧是解封装之前的,右侧是解封装后,可以从右侧数据看到,最下面是一个 I 帧的包。

图中其实有多个 rtp 包,分别以 90F0(私有帧)、8088(音频帧)和 8060(视频帧)开头,第一个 90F0 的前面多出来个 01,应该是填充字节,因为我向上找的话,对应的包是 A0E0,表示这个 rtp 包是有填充的。

4.3 RTP 数据分析
我们从最下面的 I 帧的包开始看一下:
80 60 EC CC 73 9A 44 42 32 71 73 2B 7C 85 B8 00
20 00 00 DF BA 58 BB ED 79 79 15 9B DB 03 5B AF
0F 16 E9 1B CB 6B DD A2 3D 2E A2 72 F1 E2 36 93
F0 D9 F9 FD 0C 92 9E 1A 0E 39 07 0C 41 65 12 7B
57 4B 9B 9F 9B 37 71 BC 44 EF FA C7 F2 0A 66 5E
E5 E7 B2 DE 2D B4 BB D8 85 14 F6 44 01 C0 CC 54(1)80 60:1000 0000 0110 0000,版本号 10,填充位为 0,扩展位为 0,CSRC 计数器为 0,标记位 M 为 0(这不是所传输单元的最后一个包),载荷类型 PT 为 110 0000(0x60)一般是 H264 视频帧。
(2)EC CC:sequence number,即发送顺序。
(3)73 9a 44 42:时间戳,这里转换一下就是 1939489858。

我们找下一个 rtp 包的话,是 73 9a 44 42 发现是一样的,因为他们都是 80 60 开头,就说明这还是同一帧数据的不同 rtp 包,会看到包的序列号变成了 EC CD。我们一直往后找,会看到下面这个包,依然是 8060 开头,它的序列号已经递增到了 ED 34。

我们继续找下一个包:

下一个包是 A0 E0 开头,这表示是一个视频帧,有填充,并且是最后一个包,它的序列号是 ED 35。后面两个包是 90 F0 这是私有包,再往下是一个 80 88 开头的帧,这是一个音频帧,再往下是 80 60,是一个视频帧的包,序列号是 ED 36 就是刚在最后一个视频帧包的序列号加 1,时间戳是 73 9a 52 52,转换为十进制就是 1939493458。差值不是 40ms?其实是因为 rtp 包时间戳是以 1/90000s 为单位,所以这里还要除以 90 才能得到时间戳,有可能会有溢出,可以看后面时间戳相关的笔记。都除以 90 的话,我们这里直接相减就是 1939489858-1939493458 = 3600,除以 90 就刚好是 40ms。视频是 25fps 的,所以也可以对应的上。
(4)我们还继续看一开始 I 帧的第一个 rtp 包,前面分析到时间戳,我们直接跳到 payload,这里不考虑扩展头和 CSRCs,直接就是 12 字节 header+payload,这里显然是分包了,上面的码流是使用的分片分包。
80 60 EC CC 73 9A 44 42 32 71 73 2B 7C 85 B8 00
20 00 00 DF BA 58 BB ED 79 79 15 9B DB 03 5B AF这里我们看一下 FU indicator ,在这里就是第 13 字节 7c,就是 0111 1100,对照格式,这里 F 为 0,NRI 为 3,Type 为 11100 即 28(0x1c),就是 FU-A。
FU indicator FU header
+---------------+ +---------------+
|0|1|2|3|4|5|6|7| |0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|F|NRI| Type | |S|E|R| Type |
+---------------+ +---------------+接下来看 FU Header ,第 15 字节,是 85,就是 1000 0101,对照格式,这里 Type 为 00101,就是 5,查看 H264 码流 NALU Header 结构中的 Type 字段取值,就会发现这个是 IDR 帧。
前面有提到 FU indicator 的 F 和 NRI 其实就是和 NALU Header 的高 3bit 是相同的。所以我们可以得到这一帧的 NALU Header,就是 0110 0101,转化为十六进制就是 65,这就和解封装后的数据对上了:

四、小结
好了,以上就是这节课的主要内容。接下来我们来总结一下。
首先,我们一起讨论了 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 分片传输,而分片的缺点是丢了一个片,整包数据就废弃了