一、为什么需要 H.264?
原始视频数据量巨大
1080P@30fps NV12 格式:
1920 × 1080 × 1.5 字节/像素 × 30 帧/秒 = 93.3 MB/s
= 5.6 GB/分钟 = 336 GB/小时
H.264 的三大目标
| 目标 | 说明 |
|---|---|
| 高压缩率 | 压缩到原来的 1/100 ~ 1/200 |
| 高画质 | 人眼几乎看不出画质损失 |
| 高兼容性 | 支持从低端手机到4K蓝光播放器 |
四种冗余(编码的理论基础)
H.264 的压缩思路是针对四种冗余分别处理:
| 冗余类型 | 含义 | H.264 的处理方式 |
|---|---|---|
| 空间冗余 | 同一帧内相邻像素相似(如蓝天) | 帧内预测(9种方向预测模式) |
| 时间冗余 | 相邻帧内容大部分相同(如静止背景) | 帧间预测(运动估计+补偿) |
| 编码冗余 | 符号出现概率不同 | 熵编码(CABAC/CAVLC) |
| 视觉冗余 | 人眼对色度、高频细节不敏感 | 色度子采样(4:2:0)、量化(丢弃高频) |
二、H.264 编码器完整流程(核心)
H.264 编码器完整流程
【预处理阶段】
原始帧 (RGB 或 YUV 4:4:4)
↓
色度子采样 (YUV 4:4:4 → YUV 4:2:0)
↓
【编码器内部】
YUV 4:2:0 帧输入
↓
① 帧类型决策 (I/P/B)
↓
② 分块
基本单元:宏块 (16×16 像素)
可细分为:16×16、16×8、8×16、8×8、8×4、4×8、4×4
↓
┌─────────────────────┴─────────────────────┐
↓ ↓
【I帧:帧内预测】 【P/B帧:帧间预测】
• 用相邻已编码像素预测 • 运动估计(搜索最佳匹配位置)
• 4×4:9种方向预测模式 • 1/4 像素精度运动矢量
• 16×16:4种预测模式 • 多参考帧(最多 16 帧)
• 色度:4种预测模式 • 色度复用亮度运动矢量
• 残差 = 原始块 - 预测块 • 残差 = 原始块 - 补偿块
↓ ↓
└─────────────────────┬─────────────────────┘
↓
③ 残差计算:原始块 - 预测块
↓
④ DCT 变换(整数近似变换)
空间域(像素值)→ 频率域(频率系数)
能量集中到左上角(低频),右下角(高频)数值小
完全可逆,不丢失任何信息
↓
⑤ 量化(Quantization)
★ 唯一的有损压缩环节 ★
系数 ÷ 量化步长(QP 控制),高频系数因数值小被归零
QP 每增加 6,码率约减半
↓
量化后的系数
│ │
↓ ↓
【前向编码路径】 【重建路径】
(对外输出) (模拟解码器,内部闭环)
│ │
↓ ↓
⑥ 熵编码 ⑦ 反量化
CAVLC(低复杂度) 恢复 DCT 系数(有损版本)
CABAC(高压缩率) ↓
↓ ⑧ 反 DCT
⑦ NAL 打包 频率域 → 空间域
+ SPS ↓
+ PPS ⑨ 加回预测值
↓ 得到重建帧(有损版本)
↓ ↓
↓ ⑩ ★ 环路滤波(去块滤波)★
↓ 消除块边界不连续
↓ ↓
↓ ⑪ 存入参考帧列表
↓ ↓
↓ 供后续 P/B 帧参考
↓ (形成编码闭环)
↓
★ H.264 裸流输出 ★
(传输给解码器/存储)
【关键设计总结】
前向编码路径:原始帧 → 预测 → 残差 → DCT → 量化 → 熵编码 → NAL打包 → 码流输出
重建路径:量化结果 → 反量化 → 反DCT → 加预测值 → 环路滤波 → 参考帧列表
两者关系:并行执行,共享量化结果;前向路径对外,重建路径对内
环路滤波位置:重建路径的最后一步(反DCT → 加预测值 → ★环路滤波★ → 参考帧)
色度子采样位置:编码器外部预处理,不参与编码环路
三、前向编码路径(生成码流)
3.1 帧类型与 GOP
I/P/B 帧对比:
| 帧类型 | 编码方式 | 体积 | 依赖 | 延迟 |
|---|---|---|---|---|
| I帧 | 仅用自身帧内信息编码 | 最大(基准) | 无 | 无 |
| P帧 | 前向参考(参考前面已编码帧) | I帧的 1/3~1/5 | 依赖前面的I/P帧 | 无 |
| B帧 | 双向参考(参考前后帧) | I帧的 1/5~1/10 | 依赖前后帧 | 有 |
IDR帧 vs 普通I帧(面试高频):
| 类型 | 行为 | 作用 |
|---|---|---|
| IDR帧 | I帧 + 清空参考帧列表 | GOP的起点,随机访问点,错误恢复点 |
| 普通I帧 | I帧但不清空参考帧列表 | 不中断参考链,压缩率更高 |
GOP 大小权衡:
| GOP 大小 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 小(如 15-30) | 随机访问快、抗丢包好 | 码率高 | 视频会议、直播 |
| 大(如 120-300) | 码率低 | 随机访问慢、丢包恢复慢 | 监控存储、点播 |
DTS vs PTS(必考):
有B帧时,编码顺序 ≠ 显示顺序:
示例(IBBPBB):
显示顺序: I(0) B(1) B(2) P(3) B(4) B(5)
编码顺序: I(0) P(3) B(1) B(2) B(4) B(5)
↑
P帧必须先编码,因为B帧要参考它
DTS = 解码时间戳(解码器输入顺序)
PTS = 显示时间戳(播放器输出顺序)
3.2 帧内预测(去空间冗余)
利用同一帧内已编码的相邻像素预测当前块。
| 块大小 | 预测模式数 | 说明 |
|---|---|---|
| 4×4 | 9种 | 8种方向 + DC(平均值) |
| 8×8 | 9种 | 同4×4 |
| 16×16 | 4种 | 垂直、水平、DC、平面 |
3.3 帧间预测(去时间冗余)
利用相邻帧之间的相似性,只记录运动矢量和残差。
| 参数 | 说明 |
|---|---|
| 运动矢量精度 | 1/4像素(子像素插值) |
| 参考帧数量 | 最多16帧 |
| 分块模式 | 16×16, 16×8, 8×16, 8×8, 8×4, 4×8, 4×4 |
3.4 DCT变换与量化
DCT变换:将像素从空间域转换到频率域,实现能量集中。自然图像的能量集中在低频区域(左上角),高频区域系数值很小。DCT本身是完全可逆的,不丢失任何信息。
量化(唯一的有损环节) :用量化步长除以DCT系数并取整。高频系数因为数值小,量化后被归零,从而实现了有损压缩。
- 用 QP(量化参数)控制量化步长
- QP 每增加 6,码率约减半
- QP 小 → 保留更多细节 → 画质好 → 码率高
- QP 大 → 丢弃更多高频 → 画质差 → 码率低
3.5 熵编码
| 编码方式 | 复杂度 | 压缩率 | 适用场景 |
|---|---|---|---|
| CAVLC | 低 | 一般 | Baseline Profile(低功耗设备) |
| CABAC | 高 | 高(比CAVLC高10-15%) | Main/High Profile(追求画质) |
3.6 NAL Unit 结构(码流的最小单元)
H.264码流由一系列NAL Unit组成:
[NALU] [NALU] [NALU] [NALU] ...
每个NAL Unit = NAL Header + NAL Payload
NAL Header(1字节):(MSB->LSB顺序)
+---+---+---+---+---+---+---+---+
| 0 | NRI | Type |
+---+---+---+---+---+---+---+---+
F 1-2位 5位
F (1 bit) :禁止位,必须为 0
NRI (2 bits) :NAL 参考优先级(值越大越重要)
Type (5 bits):NAL 单元类型(0-31)
Type值(常考):
┌──────┬─────────────────────────────────────────┐
│ 1 │ 非IDR图像的Slice(普通P/B帧) │
│ 5 │ IDR图像的Slice(关键帧) │
│ 6 │ SEI(补充增强信息:时间戳、字幕等) │
│ 7 │ SPS(序列参数集)★极其重要 │
│ 8 │ PPS(图像参数集)★极其重要 │
│ 9 │ Access Unit Delimiter(访问单元分隔符) │
└──────┴─────────────────────────────────────────┘
SPS(Sequence Parameter Set)包含:
- Profile/Level(基线/主/高级)
- 分辨率(宽高)
- 帧率
- 参考帧数量
- 比特深度
PPS(Picture Parameter Set)包含:
- 熵编码模式(CABAC/CAVLC)
- 切片组结构
- 量化参数初始值
- 去块滤波参数
重要性:
没有SPS/PPS → 解码器无法初始化 → 什么都解不出来
SPS/PPS通常在连接建立时发送一次,或附在每个IDR帧前面
3.7 Annex-B vs AVCC(两种封装格式)
Annex-B格式(流式传输用,RTSP/RTMP常见):
用Start Code分隔NAL Unit
[Start Code] [NALU1] [Start Code] [NALU2] [Start Code] [NALU3] ...
Start Code有两种:
3字节:0x00 0x00 0x01 (短起始码)
4字节:0x00 0x00 0x00 0x01 (长起始码,更常见)
实际码流举例(xxd看到的):
00 00 00 01 67 ← Start Code + SPS(type=7)
00 00 00 01 68 ← Start Code + PPS(type=8)
00 00 00 01 65 ← Start Code + IDR(type=5)
00 00 00 01 61 ← Start Code + Non-IDR(type=1)
AVCC格式(文件存储用,MP4容器内):
用长度前缀分隔NAL Unit
[4字节长度] [NALU1] [4字节长度] [NALU2] ...
实际数据:
00 00 00 12 [NALU_18字节] ← 长度=0x12=18
00 00 00 0A [NALU_10字节] ← 长度=0x0A=10
区别总结:
┌─────────┬────────────────┬────────────────┐
│ │ Annex-B │ AVCC │
├─────────┼────────────────┼────────────────┤
│ 分隔方式 │ Start Code │ 长度前缀 │
│ 用途 │ TS/RTSP/RTMP │ MP4/FLV容器 │
│ SPS/PPS │ 嵌在码流中 │ 存在extradata中 │
│ 转换 │ FFmpeg的 │ FFmpeg的 │
│ │ h264_mp4toannexb│ 自动处理 │
└─────────┴────────────────┴────────────────┘
面试常问:RTSP流和MP4文件里H.264的封装有什么区别?
答:RTSP用Annex-B,Start Code分隔;MP4用AVCC,长度前缀分隔。
四、重建路径(模拟解码器)⭐ 核心
4.1 为什么需要重建路径?
核心原因:编码器和解码器必须同步。
【错误做法:用原始帧做参考】
编码器:原始I帧 → 参考 → 编码P帧 → 质量高
解码器:有损I帧 → 参考 → 解码P帧 → 质量差
结果:编解码不同步,画面错误累积!
【正确做法:用重建路径模拟解码器】
编码器:原始I帧 → 模拟解码 → 有损I帧 → 参考 → 编码P帧
解码器:有损I帧 → 参考 → 解码P帧
结果:编解码完全同步,无误差累积!
4.2 重建路径的完整流程
┌─────────────────────────────────────────────────────────────────────┐
│ 重建路径(编码器内部) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 量化后的残差系数(来自前向路径) │
│ ↓ │
│ 反量化(恢复DCT系数,但已是有损的) │
│ ↓ │
│ 反DCT(频率域→空间域,得到"重建残差") │
│ ↓ │
│ 加回预测值(得到"重建帧") │
│ ↓ │
│ ★环路滤波(去块滤波)★ │
│ ↓ │
│ 存入参考帧列表(供后续P/B帧参考) │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.3 环路滤波(去块效应)
为什么需要:基于块的编码(DCT+量化)会导致块边界不连续,产生"块效应"。
H.264 的处理:在重建路径的最后一步做去块滤波。
| 特性 | 说明 |
|---|---|
| 位置 | 重建帧之后、存入参考帧列表之前 |
| 作用 | 消除块边界不连续,提升画质 |
| 为什么在环路内 | 滤波后的帧作为后续帧参考 → 预测更准 → 压缩率更高 |
H.265 的增强:在去块滤波基础上,新增 SAO(样点自适应偏移) 和 ALF(自适应环路滤波) 。
五、为什么这叫"闭环"?
┌──────────────────────────────────────────────────┐
│ 编码器内部 │
│ │
原始帧 → 前向路径 → 码流输出 │
│ ↓ │
│ 重建路径(模拟解码器) │
│ ↓ │
│ 重建帧(有损,但和解码器一致) │
│ ↓ │
│ 环路滤波 │
│ ↓ │
└───────┼──────────────────────────────────────────┘
↓
参考帧列表
↓
┌─────────────┴─────────────┐
↓ ↓
编码下一个P帧 解码器解码时
参考这个重建帧 参考的是同一个帧
↓ ↓
残差更小 画面完全同步
↓ ↓
压缩率更高 无误差累积
这就是"闭环":编码器模拟解码器 → 生成参考帧 → 反馈给编码器
六、码率控制
CBR(Constant Bit Rate,恒定码率):
┌──────────────────────────────────┐
│ 码率 │
│ ─────────────────────── 2Mbps │
│ │
└──────────────────────────────────┘
适用场景:带宽固定(监控、直播)
特点:码率稳定,但复杂场景画质下降
VBR(Variable Bit Rate,可变码率):
┌──────────────────────────────────┐
│ 码率 │
│ /\ /\ │
│ / \ /\ / \ │
│ / _/ _/ __ │
│ │
└──────────────────────────────────┘
适用场景:本地录像、离线存储
特点:复杂场景分配更多码率,画质更均匀
CRF(Constant Rate Factor,恒定质量因子):
不设目标码率,设目标质量(0~51,越小越好)
18=视觉无损 23=默认 28=可接受
适用场景:离线转码
关键参数:
码率:2Mbps(1080P监控常用) 4Mbps(高清)
GOP:30~60(1~2秒)
Profile:Baseline(无B帧,延迟低)
Main(有B帧,压缩率好)
High(压缩率最好,但复杂度最高)
Level:决定最大分辨率/帧率/码率上限
七、Profile 与 Level
| Profile | 特性 | 适用场景 |
|---|---|---|
| Baseline | 无B帧、无CABAC | 视频会议、低功耗设备 |
| Main | 有B帧、有CABAC | 流媒体、广播 |
| High | 所有工具 + 8×8变换 | 蓝光、高清存储 ★最常用 |
| Level | 最大分辨率 | 最大码率 |
|---|---|---|
| 4.0 | 1080P@30fps | 20 Mbps |
| 4.1 | 1080P@60fps | 50 Mbps |
| 5.1 | 4K@30fps | 240 Mbps |
八、面试标准回答(精简版)
H.264 的核心是什么?
H.264 采用混合编码框架,通过四种手段去除视频冗余:
- 帧内预测(去空间冗余):用相邻像素预测当前块
- 帧间预测(去时间冗余):用运动估计找匹配块
- DCT+量化(去视觉冗余):空间转频域,丢弃高频细节
- 熵编码(去编码冗余):CABAC/CAVLC压缩码流
关键设计:编码器内部有重建路径(反量化→反DCT→加预测值→环路滤波→参考帧列表),模拟解码器行为,保证编解码同步,同时滤波后的高质量参考帧反馈给编码器,形成闭环,提升压缩效率。
面试附加问题
Q1:色度子采样为什么用 4:2:0 而不是 4:4:4?
答:人眼对亮度(Y)的敏感度远高于色度(U/V)。4:2:0 在水平和垂直方向对色度减半采样,数据量减少 50%,但人眼几乎察觉不到画质差异。它平衡了画质与压缩率,是目前最广泛使用的采样格式。
Q2:I帧和IDR帧有什么区别?
答:IDR 帧是一种特殊的 I 帧。普通 I 帧只进行帧内编码,不刷新参考帧列表;IDR 帧在 I 帧的基础上,会清空参考帧列表,使之前的帧不再被后续帧参考,起到“断点”作用。
Q3:CAVLC 和 CABAC 如何选择?
答:
- CAVLC:复杂度低,适合实时通信、低功耗设备(Baseline Profile)
- CABAC:压缩率高 10-15%,适合流媒体、高清存储(Main/High Profile)
- 在 RK3588 等嵌入式平台上,硬件编码器通常同时支持两者,可以画质优先时用 CABAC,性能优先时用 CAVLC。
Q4:GOP 大小如何选择?
| 场景 | 推荐 GOP | 理由 |
|---|---|---|
| 视频会议 | 15-30 | 低延迟,抗丢包 |
| 直播 | 30-60 | 平衡延迟与压缩率 |
| 监控录像 | 120-300 | 强调压缩率,容忍高延迟 |
| 点播视频 | 60-120 | 兼顾随机访问与压缩率 |
Q5:SPS 和 PPS 是每个 NAL Unit 都有吗?
答:不是。 SPS 和 PPS 只在码流开头、每个 GOP 开头、或编码参数变化时出现一次。解码器收到后会缓存起来,后续帧直接使用。如果每帧都带 SPS/PPS,会浪费码率,因为分辨率、帧率等参数在视频流中通常不变。
Q6:如何判断当前设备是否支持 H.264 硬件编码?
答:
# 在 Linux 上查看硬件编码支持
vainfo # VA-API 支持
v4l2-ctl --list-formats-ext # V4L2 支持
# RK3588 上使用 MPP 硬件编码
ffmpeg -encoders | grep h264 | grep rkmpp