4. NAL单元类型详解:VCL与非VCL的“分工”

上一章我们把H.264码流拆成了一个个NAL单元。但光拆开还不够,你得知道每个单元是干嘛的。这就像你拿到一叠文件,得先分清哪些是合同正文,哪些是封面、目录和备注。

NAL单元分两大类:VCL NAL 和 非VCL NAL。VCL存的是真正的图像数据,非VCL存的是辅助信息。我刚开始学的时候,总觉得非VCL是“废话”,后来踩了坑才明白——没有这些“废话”,解码器根本不知道该怎么干活。

一句话总结:VCL是“肉”,非VCL是“骨架”。解码器先搭骨架,再填肉。

4.1 VCL NAL:图像数据的“搬运工”

VCL,全称Video Coding Layer。说白了,就是存像素压缩后的比特流。它又分两种:IDR图像 和 非IDR图像。

4.1.1 IDR图像(nal_unit_type = 5)

IDR,Instantaneous Decoding Refresh,瞬时解码刷新。这个名字很直白——它告诉解码器:“之前的数据全作废,从我开始重新来。”

我举个例子。你正在看视频,突然网络丢包,画面花了。这时候如果来了一个IDR帧,解码器会立刻清空参考帧缓冲区,重新开始解码。画面瞬间恢复清晰。

我的习惯:在直播推流中,我会强制每2秒插入一个IDR帧。这样即使网络抖动,客户端最多花2秒就能恢复画面。太频繁会浪费带宽,太稀疏则恢复慢。

IDR帧有几个关键特征:

  • 封闭GOP起点:它后面的帧不能参考它前面的帧。
  • 刷新参考帧列表:解码器会把所有参考帧标记为“不可用”。
  • 通常对应I帧:但注意,I帧不一定是IDR,IDR一定是I帧。

4.1.2 非IDR图像(nal_unit_type = 1)

非IDR图像,就是除了IDR之外的所有图像帧。包括P帧、B帧,以及非IDR的I帧。

它们的特点是:可以依赖之前的帧做参考。解码器会维护一个参考帧列表,非IDR帧从这个列表里找参考数据。

这里有个容易混淆的点:nal_unit_type = 1 不代表一定是P帧或B帧。它只是说“这个NAL单元存的是非IDR图像数据”。至于具体是I、P还是B,要看slice header里的slice_type字段。

我曾经踩过的坑:有一次做视频拼接,我把两个视频的IDR帧直接连在一起,没清空解码器状态。结果第二个视频的画面全是马赛克。后来才意识到,IDR虽然能刷新,但解码器内部的状态机需要正确复位。所以我现在做拼接时,会在两个IDR之间插入一个SPS/PPS,确保解码器“认识”新的参数。

4.2 非VCL NAL:解码器的“说明书”

非VCL NAL不存图像数据,但解码器离了它们寸步难行。常见的三种:SPS、PPS、SEI。

4.2.1 SPS(nal_unit_type = 7)

SPS,Sequence Parameter Set,序列参数集。它描述的是整个视频序列的全局参数。比如分辨率、帧率、编码档次、参考帧数量等。

你可以把它想象成视频的“身份证”。解码器拿到SPS,才知道这个视频是1080p还是720p,是Baseline还是High Profile。

SPS关键字段 含义 我的经验
profile_idc 编码档次(66=Baseline, 77=Main, 100=High) 做兼容性测试时,我常检查这个值
level_idc 编码级别(如30对应Level 3.0) 决定了最大分辨率和解码复杂度
pic_width_in_mbs_minus1 图像宽度(以宏块为单位) 实际宽度 = (这个值+1)*16
pic_height_in_map_units_minus1 图像高度 注意帧编码和场编码的区别
num_ref_frames 参考帧最大数量 设太大浪费内存,设太小影响压缩率

4.2.2 PPS(nal_unit_type = 8)

PPS,Picture Parameter Set,图像参数集。它描述的是单个图像(或一组图像)的编码参数。比如熵编码模式、初始量化参数、去块滤波开关等。

SPS和PPS的关系,有点像“全局配置”和“局部配置”。SPS定大框架,PPS做微调。

我建议:在封装MP4或TS时,SPS和PPS一定要放在文件头。解码器打开文件的第一件事就是找它们。如果找不到,画面直接黑屏。我曾经见过一个封装器把PPS放在了文件末尾,结果播放器前5秒全是绿屏。

4.2.3 SEI(nal_unit_type = 6)

SEI,Supplemental Enhancement Information,补充增强信息。它不参与解码过程,但可以携带一些“额外信息”。比如:

  • 时间戳(用于音视频同步)
  • 字幕信息
  • HDR元数据
  • 用户自定义数据

SEI是可选字段。解码器可以忽略它,但不会影响画面质量。不过,有些播放器会依赖SEI里的时间戳做同步,所以别乱删。

注意:SEI虽然不参与解码,但它的payload长度有限制。最大不能超过2^32-1字节。我见过有人往SEI里塞整张图片,结果码流直接崩了。SEI不是用来存大数据的。

4.3 VCL与非VCL的协作关系

现在我们把所有类型串起来,看看它们怎么配合工作。

NAL单元协作流程图 H.264 码流 NAL单元解析(nal_unit_type) VCL NAL(类型1,5) 非VCL NAL(类型6,7,8) IDR图像(类型5) 非IDR图像(类型1) SPS(类型7) PPS(类型8) SEI(类型6) 解码输出图像 VCL提供图像数据,非VCL提供解码参数。两者缺一不可。

你看这个流程:码流进来,先解析NAL类型。VCL负责“干活”——存图像数据;非VCL负责“指挥”——告诉解码器怎么干。两者配合,才能输出正确的画面。

4.4 常见NAL类型速查表

最后,我整理了一张速查表。你调试码流时,对着这个表查就行。

nal_unit_type 名称 类别 是否必须 作用
1 非IDR图像 VCL 是 存储非IDR帧的编码数据
5 IDR图像 VCL 是 存储IDR帧,刷新解码器状态
6 SEI 非VCL 否 携带补充信息(时间戳等)
7 SPS 非VCL 是 序列级参数(分辨率、档次等)
8 PPS 非VCL 是 图像级参数(量化参数等)
9 访问单元分隔符 非VCL 否 标记图像边界
10 序列结束 非VCL 否 标记序列结束
11 码流结束 非VCL 否 标记整个码流结束

我的调试技巧:当你拿到一个陌生的H.264码流,第一步就是遍历所有NAL单元,统计类型分布。如果发现没有SPS或PPS,那这个码流大概率是坏的。如果IDR帧间隔超过5秒,说明编码器配置可能有问题。