SPS、PPS、VPS、Annex B、AVCC 到底是什么关系
前言:为什么很多人学了 H.264/H.265,还是一碰码流组织就发虚
如果你已经开始接触 H.264 / H.265,很快就会碰到下面这些词:
- SPS
- PPS
- VPS
- NALU
- Annex B
- AVCC
- extradata
很多人第一次看时都会有一种很强的混乱感。
因为这些词表面上都和“码流”有关,但它们又不在同一个层次:
- 有的是参数集
- 有的是码流组织方式
- 有的是容器里常见的存储形式
- 有的是解码器初始化依赖的信息
于是工程里就很容易出现这些典型状态:
- 明明都是 H.264,为什么这个流能解,那个流不能解
- 为什么 MP4 里的 H.264 和裸流里的 H.264 长得不一样
- 为什么有时候要把 AVCC 转成 Annex B
- 为什么某些流缺了 SPS/PPS 就完全起不来
- 为什么 FFmpeg 里会有 extradata、bitstream filter 这些东西
我觉得这里最容易乱的根本原因是:
很多人把“码流内容”和“码流组织方式”混在了一起。
所以这篇文章的目标,就是把这几件事拆清楚,再重新串起来。
重点讲明白:
- SPS / PPS / VPS 到底各自是什么
- 它们和普通视频数据 NALU 是什么关系
- Annex B 和 AVCC 到底差在哪
- 为什么 MP4、裸流、网络传输里经常需要做组织方式转换
- FFmpeg 工程里这些概念会落在哪些位置
而且这篇会尽量多放结构图、时序图和代码,不只停留在术语解释上。
一、先给一句总结:SPS / PPS / VPS 是“参数信息”,Annex B / AVCC 是“码流组织方式”
如果你现在只想先抓住一句最关键的话,那就是:
SPS / PPS / VPS 解决的是“解码器需要先知道什么参数”,而 Annex B / AVCC 解决的是“这些 NALU 在字节流里怎么组织”。
这两类东西看起来总同时出现,但它们不是一层含义。
可以先粗分成:
第一类:参数集
- SPS
- PPS
- VPS(H.265)
它们回答的是:
这段视频流是什么规格、该怎么初始化解码。
第二类:组织方式
- Annex B
- AVCC
它们回答的是:
这些 NALU 在字节流里是怎么切分和存放的。
这一步先分开,后面就会顺很多。
二、先把大框架放出来:一条 H.264/H.265 码流里到底有什么
先看一个总图。
flowchart TD
A[视频码流] --> B[NALU 序列]
B --> C1[参数集 NALU]
B --> C2[图像数据 NALU]
C1 --> D1[SPS]
C1 --> D2[PPS]
C1 --> D3[VPS(H265)]
C2 --> E1[IDR / I 帧相关]
C2 --> E2[P/B 帧相关 slice]
B --> F[字节组织方式]
F --> G1[Annex B]
F --> G2[AVCC/HVCC]
这张图最重要的作用,是先让你知道:
一条视频码流里,不只有“画面数据”,还有“解释画面该怎么解”的参数信息。
而且这些 NALU 还会以不同方式被组织起来。
三、NALU 是什么,为什么它是理解一切的入口
如果不先理解 NALU,后面很多概念都会悬空。
3.1 NALU 的本质
NALU 可以粗略理解成:
视频码流中的一个基本数据单元。
也就是:
- 一条 H.264/H.265 码流,并不是一整坨不可分的数据
- 它内部其实是由一个个 NALU 组成
这些 NALU 里,有些装的是参数,有些装的是图像相关数据。
3.2 为什么 NALU 很重要
因为后面你看到的这些东西,本质上都在围绕 NALU 发生:
- SPS / PPS / VPS 本身就是某些特定类型的 NALU
- slice 数据也在 NALU 里
- Annex B 和 AVCC 组织的,本质上也是 NALU
所以你可以把 NALU 先看成:
视频码流里的“基本块”。
四、SPS 到底是什么,它为什么像“全局说明书”
4.1 SPS 的本质
SPS 全称是 Sequence Parameter Set。
如果不按文档腔去记,我更建议你把它理解成:
一段视频序列的全局规格说明。
它通常会包含这类信息:
- 分辨率相关信息
- profile / level
- 帧编号、参考相关约束
- 色度格式相关信息
- 一些解码器需要预先知道的全局参数
4.2 为什么没有 SPS,解码器通常起不来
因为解码器得先知道:
- 这是个什么规格的视频
- 宽高大概怎么解释
- 后面 slice 应该按什么规则理解
如果连这些都不知道,后面的图像数据就很难正确还原。
所以从工程角度看,SPS 很像:
让解码器知道“这道题的题型是什么”的前置条件。
五、PPS 到底是什么,它为什么更像“当前解码规则补充”
5.1 PPS 的本质
PPS 全称是 Picture Parameter Set。
你可以把它理解成:
比 SPS 更贴近具体图像解码控制的一组参数。
它通常和:
- slice 的解析规则
- 熵编码模式
- 某些参考和控制参数
更相关。
5.2 SPS 和 PPS 的关系怎么记
我更建议这样记:
- SPS 更像全局说明书
- PPS 更像当前图像解码时的具体规则补充
你不用强行把它背成特别死的标准定义,但一定要知道:
它俩都不是“画面内容本身”,而是在给解码过程提供必要上下文。
六、VPS 又是什么,为什么 H.264 没这个词,H.265 有
6.1 VPS 的本质
VPS 全称是 Video Parameter Set。
它主要出现在 H.265 / HEVC 中。
你可以把它理解成:
比 SPS 更上一层的、面向更高层视频配置组织的参数集。
6.2 为什么 H.265 里会多这一层
因为 H.265 的整体结构和扩展能力比 H.264 更复杂,参数组织层次也更细。
所以从工程理解角度可以先记成:
- H.264 常说 SPS / PPS
- H.265 常说 VPS / SPS / PPS
6.3 一句话直觉记忆
可以这样粗记:
- VPS:更高层视频级参数
- SPS:序列级参数
- PPS:图像 / slice 解码控制相关参数
这个层级感先建立起来就够用了。
七、一张图把 VPS / SPS / PPS 的层级感放清楚
flowchart TD
A[H.265 视频参数层级] --> B[VPS]
B --> C[SPS]
C --> D[PPS]
D --> E[具体 slice / 图像数据]
而如果是 H.264,可以更接近:
flowchart TD
A[H.264 参数层级] --> B[SPS]
B --> C[PPS]
C --> D[具体 slice / 图像数据]
这两张图的目的不是让你死背层级,而是帮助你形成一个理解:
参数集本质上在给图像数据提供“解释上下文”。
八、Annex B 到底是什么,为什么很多裸流都长这个样子
接下来进入另一个非常高频的坑点:
Annex B。
8.1 Annex B 的本质
Annex B 可以理解成:
一种以起始码分隔 NALU 的字节流组织方式。
也就是说,在 Annex B 里,一个 NALU 的边界通常靠起始码来表示,比如:
1 | |
或者:
1 | |
8.2 为什么它常见于裸流
因为它非常适合“顺着字节流往前扫”的场景。
例如:
- 裸 H.264 / H.265 文件
- 某些传输场景
- 某些编码器直接吐出的数据
8.3 一张示意图看懂 Annex B
1 | |
所以你以后看到 Annex B,可以先直接记成:
靠起始码找 NALU 边界。
九、AVCC 到底是什么,为什么 MP4 里常见的是这种组织方式
9.1 AVCC 的本质
AVCC 可以理解成:
一种用长度前缀来表示 NALU 边界的组织方式。
也就是说,和 Annex B 不一样,它不是靠起始码,而是靠“前面先写长度”。
示意上更像:
1 | |
9.2 为什么 MP4 里常见的是 AVCC
因为在 MP4 这类容器里,长度前缀方式通常更适合封装和索引管理。
同时,参数集(比如 SPS/PPS)往往不会每次都直接混在每个包里,而常常被单独提取到更适合初始化解码器的位置,比如:
- 容器头信息
- codec extradata
所以你在 MP4 里看到的 H.264,通常和裸流里的 H.264 长得不一样。
这不是 codec 变了,而是:
组织方式变了。
十、一张图把 Annex B 和 AVCC 的差别一次讲清楚
flowchart LR
A[NALU 序列] --> B1[Annex B]
A --> B2[AVCC]
B1 --> C1[起始码分隔]
B1 --> C2[常见于裸流 / 某些传输链路]
B2 --> D1[长度前缀分隔]
B2 --> D2[常见于 MP4 / 容器组织]
你可以直接把这张图压成一句话:
- Annex B:看起始码找边界
- AVCC:看长度前缀找边界
这已经能解决大部分第一层理解问题。
十一、SPS / PPS / VPS 和 Annex B / AVCC 是怎么交叉在一起的
这部分非常关键。
因为很多人学到这里最大的困惑就是:
参数集和组织方式到底是怎么同时存在的?
答案是:
参数集本身也是 NALU,而 Annex B / AVCC 决定这些 NALU 怎么被排进字节流。
换句话说:
- SPS/PPS/VPS 是“内容类型”
- Annex B/AVCC 是“排布方式”
所以它们不是互斥关系,而是两层叠在一起。
11.1 一张交叉关系图
flowchart TD
A[NALU] --> B1[参数集 NALU]
A --> B2[图像数据 NALU]
B1 --> C1[SPS/PPS/VPS]
B2 --> C2[Slice / IDR / 非IDR]
A --> D[组织方式]
D --> E1[Annex B]
D --> E2[AVCC]
这张图一旦立住,后面很多事就容易理解了。
十二、为什么 MP4 里的 H.264/H.265 和裸流里的长得不一样
这个问题特别高频。
12.1 裸流里常见什么
裸流中,常见的是:
- Annex B 组织方式
- 参数集可能直接以内联 NALU 形式出现在码流里
12.2 MP4 里常见什么
MP4 里常见的是:
- AVCC(H.264)或 HVCC(H.265)形式
- 参数集常常被提取到 extradata / 容器头信息中
- 每个 packet 里主要放具体图像相关 NALU
所以看起来会像:
- 同样是 H.264
- 但字节布局完全不一样
这就是为什么你经常会遇到:
- 裸流能直接喂某些解码器
- MP4 拆出来的 packet 却还要进一步处理
十三、FFmpeg 工程里这些东西会落在哪些地方
这部分最实用。
13.1 extradata 是什么
FFmpeg 里,extradata 很多时候就是在保存 codec 初始化所需的重要参数信息。
比如:
- H.264 的 SPS / PPS
- H.265 的 VPS / SPS / PPS
你可以先粗记成:
extradata 是给解码器“先打底”的那部分信息。
13.2 为什么 demux 出来的 packet 不一定能直接送解码器
因为:
- 容器里可能是 AVCC/HVCC 风格
- 解码器或某些后续链路可能更希望看到 Annex B 风格
- 参数集可能在 extradata 里,而不在当前 packet 数据里
这时候就经常需要:
- parser
- bitstream filter
- 组织方式转换
13.3 一张 FFmpeg 里的关系图
flowchart LR
A[MP4/FLV/TS] --> B[Demux]
B --> C[AVPacket]
B --> D[extradata]
D --> E[SPS/PPS/VPS]
C --> F[AVCC/HVCC or Annex B]
F --> G[必要时做 bitstream 转换]
G --> H[送解码器]
这张图的核心是:
同一个 codec,在不同容器里出来的 packet 组织方式可能不同,而解码器初始化依赖的信息又可能在 extradata 里。
十四、为什么工程里经常要做 Annex B 和 AVCC 的转换
因为上下游预期不一致。
14.1 一个典型场景
比如:
- 输入是 MP4
- demux 出来是 AVCC 风格
- 但后续 mux 到 TS 或某些网络链路时,更需要 Annex B 风格
这时就需要转换。
14.2 再比如
- 某个编码器直接输出 Annex B
- 但你要写进 MP4
- MP4 更倾向于 AVCC/HVCC 组织方式
也要转换。
所以很多时候问题根本不是:
codec 对不对
而是:
码流组织方式对不对
这也是这篇文章最想帮你建立的工程意识之一。
十五、一段 FFmpeg 代码,怎么看 codec extradata
这段代码很有代表性。
1 | |
这段代码本身不复杂,但它的工程意义很大:
你要养成习惯,知道参数集信息未必在当前 packet 数据里,也可能提前放在 extradata 里。
十六、一段 FFmpeg 代码,怎么做 H.264 MP4 到 Annex B 的常见转换
FFmpeg 里常见会用到 bitstream filter,比如 h264_mp4toannexb。
1 | |
这段代码后面站着的实际问题就是:
- 输入组织方式和输出需求不一致
- 需要把 AVCC 风格转成 Annex B 风格
同理 H.265 也有对应的 bitstream filter。
十七、一张时序图看懂“容器 -> 解码器”之间为什么老要折腾一下
sequenceDiagram
participant File as MP4文件
participant Demux as Demux
participant Extra as extradata
participant BSF as Bitstream Filter
participant Dec as Decoder
File->>Demux: 读容器数据
Demux->>Extra: 提取 SPS/PPS/VPS
Demux->>BSF: 输出 AVCC/HVCC 风格 packet
BSF->>Dec: 转成解码器期望的 Annex B / 合法输入
Extra->>Dec: 初始化解码参数
这张时序图非常重要,因为它解释了一个工程现实:
解码前的工作,不只是“把 packet 喂进去”,而是先把初始化参数和组织方式都准备对。
十八、工程里最常见的几个坑
18.1 误区一:SPS/PPS/VPS 就是普通视频帧的一部分
不对。
它们也是 NALU,但它们承载的是参数信息,不是普通画面内容本身。
18.2 误区二:Annex B 和 AVCC 是两种不同编码格式
也不对。
它们不是不同 codec,而是同一 codec 下不同的字节流组织方式。
18.3 误区三:只要 codec 是 H.264,就一定能直接喂任何下游
不一定。
你还得看:
- 参数集有没有准备好
- 组织方式对不对
- 上下游预期是不是一致
18.4 误区四:解码失败一定是编码器问题
也不一定。
工程里很多“解不出来”的问题,根因其实是:
- SPS/PPS/VPS 缺失
- AVCC / Annex B 混用
- extradata 没传对
- packet 边界没处理对
十九、面试里怎么讲,才不像只会背几个缩写
如果面试官问:
SPS、PPS、VPS、Annex B、AVCC 你怎么理解?
我建议你按下面这个结构讲。
19.1 先分层
SPS、PPS、VPS 是参数集,用来给解码器提供必要初始化和解码上下文;Annex B 和 AVCC 则是码流中 NALU 的两种常见组织方式。
19.2 再讲关系
参数集本身也是 NALU,而 Annex B / AVCC 决定这些 NALU 在字节流里如何分隔和存储。Annex B 依赖起始码,AVCC 依赖长度前缀。
19.3 最后讲工程意义
在工程里,裸流、MP4、TS、网络流等场景中,这些组织方式可能不同,所以经常需要借助 extradata、parser 或 bitstream filter 做适配,否则即使 codec 本身是一样的,也可能出现解码失败或上下游不兼容。
如果你能这么讲,基本就不是“我背过这些缩写”,而是真的知道这些东西在系统里各自负责什么。
总结:真正要分清的,不是几个缩写,而是“参数信息”和“组织方式”这两层
回到这篇文章最开始的问题:
SPS、PPS、VPS、Annex B、AVCC 到底是什么关系?
我觉得最稳的主线就是:
- 一条 H.264/H.265 码流是由 NALU 组成的
- 其中有些 NALU 装的是参数集(SPS/PPS/VPS)
- 有些 NALU 装的是图像相关数据
- Annex B 和 AVCC 不是参数内容,而是这些 NALU 的组织方式
- FFmpeg 工程里,extradata、bitstream filter、demux / mux 都会围绕这两层做适配
只要这条线建立起来,后面你再看:
- 裸流和 MP4 的差异
- H.264 / H.265 packet 为什么长得不一样
- 为什么某些流缺 SPS/PPS 就起不来
- 为什么 FFmpeg 里老要 mp4toannexb
这些问题都会自然很多。
因为到那时你已经不再只是记住几个缩写,而是真的知道:
哪些是在告诉解码器“这是什么视频”,哪些是在告诉系统“这些 NALU 怎么排”。