第一章 前言
第三章 有关 SPS 和 PPS 的一切
第四章 有关 Slice 的一切
第七章 帧间编码
第八章 残差的熵编码: CAVLC 和 CABAC
我们在上一小结中,阐述了 NALU 的第一个字节里面的内容。我们了解到在第一个字节内,低 5 位表示 nal_unit_type ,也就是 NALU 的类型,那么本节,我们就来详细看看这些类型
我们在这里再次列举一下 NALU 的类型有哪些:
nal_unit_type | NALU 类型 | |
---|---|---|
0 | 未定义 | |
1 | 非 IDR SLICE | slice_layer_without_partitioning_rbsp( ) |
2 | 非 IDR SLICE,采用 A 类数据划分片段 | slice_data_partition_a_layer_rbsp( ) |
3 | 非 IDR SLICE,采用 B 类数据划分片段 | slice_data_partition_b_layer_rbsp( ) |
4 | 非 IDR SLICE,采用 C 类数据划分片段 | slice_data_partition_c_layer_rbsp( ) |
5 | IDR SLICE | slice_layer_without_partitioning_rbsp( ) |
6 | 补充增强信息 SEI | sei_rbsp( ) |
7 | 序列参数集 SPS | seq_parameter_set_rbsp( ) |
8 | 图像参数集 PPS | pic_parameter_set_rbsp( ) |
9 | 分隔符 | access_unit_delimiter_rbsp( ) |
10 | 序列结束符 | end_of_seq_rbsp( ) |
11 | 码流结束符 | end_of_stream_rbsp( ) |
12 | 填充数据 | filler_data_rbsp( ) |
13 | 序列参数扩展集 | seq_parameter_set_extension_rbsp( ) |
14~18 | 保留 | |
19 | 未分割的辅助编码图像的编码条带 | slice_layer_without_partitioning_rbsp( ) |
20~23 | 保留 | |
24~31 | 未指定 |
这些类型有的常用,有的并不是很常见。我们本次先来看看常见的。
我们可以先看一下我们上一小节写的样例程序,执行他,并观察一下输出结果。如下图:
注意图中用红线标出来的 nal_unit_type 的值。在这个视频文件中,我们发现前几个 NALU 的 type 分别是 7,8,6,5,1,1,1,1......
如果把他们形象得表示出来,那么他们应该是这样的:
其实这里出现的这五种类型,就是最常见到的五种类型。我们先来看 nalutype 等于 7 的时候。
通过查表,我们知道 nalutype 等于 7 的时候,这个 nalu 被称之为序列参数集,一般我们把它简称为 SPS。
当 nalutype 等于 8 的时候,这个 nalu 被称之为图像参数集,一般我们把它简称为 PPS。SPS 和 PPS 中存放了解码过程中所需要的各种参数,是 H.264 解码的前置条件,没有 SPS 和 PPS,视频将会无法解码,所以在解码的时候,我们总是首先把 SPS 和 PPS 传给解码器,供解码器初始化。我们会在后续章节中详细介绍 SPS 和 PPS。
当 nalutype 等于 5 的时候,这个 nalu 被称之为 IDR 帧,也叫做关键帧(确切的说,这里其实应该不叫做帧,应该叫做 slice,有的翻译把它叫做条带,大部分情况下,一个 slice 就是一帧,但是会存在例外的情况,这里不做过多纠结,之后的内容先管他叫做 Slice,在这里,Slice 和其他资料里的帧是相同的概念)。IDR Slice 一定是一个 I Slice,而 I Slice 却不一定是一个 IDR Slice。
提到 IDR Slice,将不得不提到与之关联的另外一个概念,GOP (Group of picture),从字面意思来看,GOP 就是指一组 Slice,但是在 GOP 的具体定义上,笔者发现了好多个版本的定义,这里列出一些常见的,供诸位参考。
Open-GOP, Open-GOP 由一个 I Slice 开始,Open 是指这个 GOP 是开放的,在这个 GOP 中的 P Slice 和 B Slice 可以参考这个 GOP 开始的 I Slice 之前的 Slice。
Close-GOP,Close-GOP 由一个 I Slice 开始,Close 是指这个 GOP 是封闭的,在这个 GOP 中的 P Slice 和 B Slice 不可以参考这个 GOP 开始的 I Slice 之前的 Slice。
其实过度纠结 GOP 的定义并没有太多实际的意义,GOP 的概念其实也不是 H.264 标准的概念,而是在生产中,大家总结出来的一个方便理解的概念,对其进行过度解读,反而会造成不必要的误解。
而 IDR Slice 的一个关键特性在于,当解码器拿到一个 IDR 帧的时候,意味着解码进入了一个全新的阶段,之前缓存的解码信息可以清空掉,解码器相当于完全重置。这对于传输不稳定的流媒体来说至关重要,在一个 IDR 帧之后,上一个 GOP 里的错误不会蔓延到当前 GOP。这就是为什么 IDR 帧通常只在流媒体格式(RTMP,RTP)中被强调,而在类似 MP4 之类的储存格式中,因为不必担心丢包等,所以往往只有开头一个 IDR 帧。
当 nalutype 等于 1 的时候,这个 nalu 被称之为 Slice,这里就是除了 IDR Slice 以外,其他的视频数据存放的地方了。其实,除了 IDR 的特殊属性之外,IDR Slice 和 普通的 Slice 在组成和解码原理上,几乎都是一模一样的。
当 nalutype 等于 6 的时候,这个 nalu 被称之为 SEI,也就是自定义信息,如果你想在码流中放入一些别的额外信息,就可以写到 SEI 信息里面。不过要注意的是,SEI 信息的重要性级别并不高,有些时候会在转码,转封装的过程中被忽略掉。
判断 nalutype,有很多资料上说,判断 NALU 的类型,直接看 NALU 的第一个字节,如果第一个字节是 0x67 ,则是 SPS,如果第一个字节是 0x68,则是 PPS。这显然是错误的,我们在上一小节(NALU 的第一个字节)中说过,NALU 的第一个字节包含 1 位的禁止位,2 位的重要性,剩下的 5 位,才是 nalutype。如果按照整个字节进行计算,虽然在大多数常见下是可以判断正确的(大部分场景下重要性都是相同的),但是无法覆盖全部案例。
判断 I P B,很多资料里面写,如何判断一个 NALU 是不是 I Slice,只要判断其 nalutype 是否等于 5 就好了。这也是错误的,我们已经介绍过,IDR Slice 一定是一个 I Slice,而 I Slice 却不一定是一个 IDR Slice,那么即使这个 NALU 的 nalutype 等于 1,他也有可能是 I Slice。那么应该判断这个 Slice 是 I 还是 P 还是 B 呢?其实根据目前读到的数据来说,我们是判断不出来的,我们在后续章节中读取 Slice 的时候,再返回头来解决这个问题。
643401840@qq.com
2022-03-10 23:25:08
zekun@sina.cn
2021-12-23 11:08:27
simp00@163.com
2021-12-06 21:21:49
632212519@qq.com
2021-02-01 11:47:23