H.264/H.265 核心原理:从基础世界观到编解码流程与码流结构

一、先从一个问题开始:原始视频为什么这么大

如果你直接看一帧没有压缩的视频图像,它的数据量其实是很夸张的。

假设一段 1080p 视频:

  • 分辨率:1920 × 1080
  • 帧率:30fps
  • 像素格式按 RGB 24bit 算

一帧图像的数据量大约是:

1
1920 × 1080 × 3 ≈ 6 MB

30fps 的话,1 秒就是:

1
6 MB × 30 ≈ 180 MB/s

一秒钟原始视频数据可能是上百 MB。这还只是 1080p,如果分辨率更高、帧率更高,数据量会继续往上冲。

如果不做压缩,立刻会遇到几个现实问题:

  • 存储空间扛不住
  • 网络带宽扛不住
  • 传输延迟扛不住
  • 终端解码和处理成本也很高

因此,视频编码本质上不是”锦上添花”,而是让视频在真实世界里可存、可传、可播放。

视频压缩到底在压什么

压缩不是无中生有地把数据缩小,而是尽量去掉那些”没必要重复表达”的信息。视频编码能压得动,主要靠利用三类冗余。

空间冗余:同一帧图像里,相邻像素往往很相似。比如一大片蓝天、一堵墙、一张脸的大面积肤色区域,像素值通常不会毫无规律地乱跳。编码器不需要把每个像素都当成完全独立的信息来存。

时间冗余:视频不是一堆完全无关的图片。相邻两帧之间大量内容其实是连续的。编码器不需要每一帧都完整重写一遍,而是可以记录哪些区域和上一帧差不多、哪些区域发生了位移、哪些地方真正有变化。

视觉冗余:人眼对亮度更敏感,对色度细节和某些高频细节的容忍度更高。编码器可以在人没那么容易察觉的地方做更激进的压缩。

一句话:在尽量不明显影响主观观感的前提下,去掉空间、时间和视觉上的重复信息。


二、什么是 H.264,什么是 H.265

H.264(AVC, Advanced Video Coding)和 H.265(HEVC, High Efficiency Video Coding)都是视频压缩标准——两套规则,规定视频应该怎样被压缩、压缩后的码流应该怎样组织、解码器应该怎样还原图像。

2.1 两者关系

它们是前后代关系,不是互斥关系。

  • H.264:更成熟、更兼容、更普及。长期占据直播、点播、视频会议、监控、移动端播放、网页视频等大量场景。
  • H.265:压缩更强,但复杂度更高、生态更挑环境。核心目标就是比 H.264 更高压缩效率——相近画质下码率更低,或相近码率下画质更好。

真实的工程问题不是”哪个先进”,而是在这个具体场景里哪个更合适。

2.2 核心区别

维度 H.264 H.265
压缩效率 基准 通常再省 30%-50% 码率
编码复杂度 相对低,实现成熟 更高,更吃算力
解码复杂度 低,硬件支持广泛 更高,终端要求高
兼容性 极广,生态完备 较复杂,链路需要验证
典型场景 低延迟直播、兼容性优先 4K、带宽紧张、存储成本敏感

H.265 的优势不是白赚的,它用更复杂的计算(更灵活的分块、更复杂的预测机制)换来了更高的压缩率。

2.3 为什么今天两者并存

兼容性永远是大问题。视频系统往往是一条链路:编码端→传输→服务器→播放器→终端设备→硬件解码能力。只要链路里有一段对 H.265 支持没那么稳,整个系统就会倾向保守。

其次,硬件支持和算力成本:H.265 编码和解码都更复杂,终端设备、芯片平台、硬件编解码器支持不足时,功耗高、延迟高、播放不稳定。

最后,场景需求不一样。有些场景非常在意”码率能不能再低一点”(存储密集型监控、4K 分发、带宽紧张链路),有些场景更在乎稳定、兼容、低延迟、实现简单。


三、编解码必须掌握的基础概念

3.1 帧、分辨率、帧率

视频本质上是一系列连续播放的图像。每一张就是一帧。

分辨率描述一帧图像有多少像素(如 1280×720、1920×1080、3840×2160)。分辨率越高,单帧数据量通常越大。

帧率表示每秒播放多少帧(如 24fps、30fps、60fps)。帧率越高画面越流畅,但单位时间内处理和传输的数据也越多。

3.2 为什么编码前会从 RGB 走向 YUV

RGB 很直观,适合显示。但视频编码里更常见的是 YUV。

YUV 可以粗略理解成:

  • Y:亮度
  • U / V:色度

人眼对亮度更敏感,对色度没那么敏感。所以编码器可以对色度做更强的压缩,而不明显影响主观观感。

4:2:0、4:2:2、4:4:4 描述的是色度采样方式。最常见的是 4:2:0——色度分量被更稀疏地采样,减少数据量。FFmpeg 里经常看到的 yuv420p,就是一种非常主流、兼顾压缩效率和兼容性的像素格式。

3.3 GOP 和 I/P/B 帧

GOP(Group of Pictures)是一组图像的组织结构。比如一段序列可能长这样:I B B P B B P B B P。帧与帧之间存在参考关系,GOP 结构会影响码率、画质、随机访问能力和延迟。

  • I 帧:可近似理解为关键帧,更多依赖帧内信息,可以相对独立解码。
  • P 帧:参考前面的帧,只记录变化部分,更多利用时间冗余。
  • B 帧:可参考前后帧,能进一步提高压缩效率,但会让编码和解码链路更复杂,也带来时序和缓存上的额外处理。

3.4 码率、画质、延迟

码率描述单位时间内编码器输出了多少数据。码率越高,画质更容易保住,但带宽和存储成本也更高。

画质是主观感受和客观指标共同作用的结果。编码器的目标就是在有限码率下尽量保住画质。

在实时场景里,延迟往往和 B 帧有无、GOP 长度、编码器缓存、码控策略、网络传输一起被权衡。这三者本来就是强绑定的。

3.5 Profile 与 Level

Profile(档次) 定义了编码器支持哪些编码工具集。不同 Profile 对应不同的压缩能力和复杂度:

  • Baseline Profile:支持 I/P 帧、CAVLC,不支持 B 帧和 CABAC。复杂度最低,常用于低延迟、低算力的场景(视频会议、移动端)。
  • Main Profile:支持 I/P/B 帧、CABAC、加权预测等。是 H.264 的主流档次,广泛用于广播和消费级视频。
  • High Profile:在 Main 基础上增加了 8×8 变换、自定义量化矩阵、编码器更灵活的量化控制。压缩效率更高,是高清视频和蓝光的主流选择。

H.265 的档次体系更复杂——引入了 Main、Main 10(支持 10bit 色深)、Main Still Picture 等档次,编码单元更多、灵活性更强。

Level(级别) 规定了分辨率、帧率、码率的上限。比如 Level 4.0 通常对应 1080p@30fps、最大码率约 20Mbps;Level 5.1 对应 4K@30fps。解码器通过解析 SPS 中的 Profile 和 Level 字段,可以判断自己是否有能力解码该码流——这是兼容性匹配的核心机制。

3.6 率失真优化(RDO)

率失真优化是编码器在宏块或编码单元级别做决策的核心工具。编码器在”压缩更狠(更低码率)”和”画质更好(更低失真)”之间做权衡,目标函数通常是:

1
J = D + λ × R
  • D(Distortion,失真)
  • R(Rate,所需比特数)
  • λ(拉格朗日乘子,编码器根据当前编码策略自动调整)

比如在帧内预测时,编码器会尝试多种预测模式,计算每种模式的 RD cost(失真+码率×λ),选择 cost 最小的那一种。量化时同样:QP 越大,λ 越大,编码器倾向更激进的压缩。

所以 RDO 不是某一个固定的模块,而是贯穿编码器决策过程的思维方式——每个选择都不是孤立的,而是码率和失真的联合优化。

3.7 码率控制模式(CBR / VBR / CRF)

编码器怎么决定最终输出多大的码流?这就是码率控制策略的事。

CBR(Constant Bit Rate,恒定码率):编码器尽量保持输出码率稳定在设定值附近。适合带宽严格受限的传输链路(如传统的视频会议、IPTV),但在场景复杂度剧烈变化时画质会波动。

VBR(Variable Bit Rate,可变码率):允许码率在设定范围内波动。简单场景用较少比特,复杂场景用更多比特。画质更稳定,适合存储和点播场景。

CRF(Constant Rate Factor,恒定质量因子):用户设定一个质量目标值(如 FFmpeg 中的 -crf 23,范围 0-51,越低质量越好)。编码器自动调整 QP 来维持恒定的主观质量,不直接控制最终码率大小。适合对”最终文件多大”不敏感、但对”每一帧看起来都差不多好”有要求的场景。

工程中常见搭配:直播场景常用 CBR 或带缓冲控制的 VBR,点播场景偏好 VBR 或 CRF。


四、编解码流程概览

编码器的总任务可以压缩成一句话:尽量减少视频里的冗余信息,把原始帧变成更小的压缩码流。

拆成具体的步骤:

flowchart TD
    A[原始视频帧] --> B[分块]
    B --> C[帧内 / 帧间预测]
    C --> D[得到残差]
    D --> E[变换]
    E --> F[量化]
    F --> G[熵编码]
    G --> H[压缩码流输出]

解码器大体上是编码的逆过程:

flowchart TD
    A[压缩码流] --> B[熵解码]
    B --> C[反量化]
    C --> D[反变换]
    D --> E[重建预测块]
    E --> F[输出图像帧]
    F --> G[作为后续参考帧]

五、分块机制

如果直接把一整帧图像当成一个整体去处理,计算和建模都非常困难。所以视频编码首先会把图像分成更小的块,再逐块处理。

5.1 H.264 的宏块

H.264 里一个非常经典的单位就是宏块(Macroblock),常见大小是 16×16。编码器围绕这个粒度做预测、变换、编码。当然 H.264 里也有更细粒度的子分块,但先把”宏块是主要基本处理单元”记住就够了。

5.2 H.265 的 CTU / CU / PU / TU

H.265 的分块设计更灵活,引入了层次结构:

  • CTU(Coding Tree Unit):编码树单元,可以看成更大的根块
  • CU(Coding Unit):编码单元,可以递归划分
  • PU(Prediction Unit):预测单元,决定预测方式
  • TU(Transform Unit):变换单元,决定变换粒度

核心理解不在这四个缩写的罗列,而在于:H.265 允许编码器按更灵活、更自适应的方式去切图像块。平坦区域适合大块表示,边缘复杂区域适合细分成小块。这也是 H.265 压缩效率更高的重要来源之一。


六、帧内预测:利用空间冗余

帧内预测的核心思想:不直接把这块像素原封不动写出去,而是先猜它大概长什么样,再只记录”猜得不准的差异”。

编码器会根据左边、上边、邻近已经编码好的区域,预测当前块大概应该长什么样。然后真正要编码的就是:

1
实际值 - 预测值 = 残差

如果预测得好,残差就小,后面压起来就更容易。

不同的”猜法”就是不同的预测模式:

  • 有些模式偏水平延展
  • 有些偏垂直延展
  • 有些偏斜向延展
  • 有些做更复杂的平滑估计

本质就是:编码器在尝试不同方向和方式,找到最适合当前块纹理结构的预测方式。


七、帧间预测:利用时间冗余

帧内预测在同一帧里找相似性,帧间预测在不同帧之间找相似性。

7.1 运动估计与运动补偿

运动估计(Motion Estimation):编码器尝试找当前块在参考帧里最像哪一块。

运动补偿(Motion Compensation):用这个”最像的块”作为预测结果,再编码真实块和预测块之间的差异。

flowchart LR
    A[当前块] --> B[在参考帧中搜索]
    B --> C[最佳匹配块]
    C --> D[运动矢量 MV]
    D --> E[预测块]

可以简单理解为:先去前后帧里找相似块,再用位移 + 残差来表示当前块。

7.2 I 帧、P 帧、B 帧的预测方式

帧类型 依赖关系 特点
I 帧 不依赖其他帧,只做帧内预测 独立性最强,压缩率较低
P 帧 参考之前的参考帧 只保存相对过去的变化
B 帧 可参考前后参考帧 压缩率更高,但时序更复杂

I 帧不做帧间预测,只看当前帧内部已编码过的邻近像素块来预测。一个区域如果是平滑渐变或明显边缘延续,周围像素就能给出相当好的估计。

P 帧会去历史参考帧里搜索——当前块能不能在过去的某一帧里找到一个非常像的块?如果能,只需要记录运动矢量 MV(从哪一帧、哪个位置偏移过来)和残差。

B 帧可以同时参考前后参考帧(准确地说,B 帧可以参考前向和后向参考图像)。好处是压缩率更高,代价是编码逻辑更复杂、参考关系更复杂、缓存更多、延迟更高。所以低延迟场景常常会尽量少用甚至不用 B 帧。


八、残差、变换与量化

不管帧内还是帧间预测,都会产生残差(residual):

1
真实块 - 预测块

8.1 为什么要做变换

变换的目标是把空间域里的残差转换到更适合压缩的表示形式——把”像素差异”换一种表达方式,让它更容易出现大量接近 0 的值和更集中的能量分布,这样后面量化和熵编码就更容易奏效。

H.264 使用整数变换(工程化的 DCT 思想),变换后低频能量更集中,高频分量更容易变小。而高频细节在视觉上并不重要,给量化留下发力的空间。

8.2 量化是有损压缩的核心来源

量化是对某些值做更粗粒度处理,丢掉一部分细节精度,换取更高压缩率。这是经典的 trade-off:

  • 量化更狠 → 文件更小,画质更差
  • 量化更轻 → 文件更大,画质更好

QP(Quantization Parameter)是控制量化强度的核心参数。QP 越大,压缩越狠,失真越大;QP 越小,画质越好,码率越高。

8.3 率失真优化如何指导量化决策

RDO 在量化阶段的体现是:编码器不会简单地对所有块都用同一个 QP,而是在不同编码模式下计算 RD cost,选择失真和码率加权后的最优解。码率控制策略(CBR/VBR/CRF)则是在更高层次协调 QP 的分配,确保整体输出满足带宽或质量要求。


九、熵编码

经过预测、变换、量化之后,数据里通常已经有很多 0,也出现了明显的统计规律。熵编码继续压干统计冗余。

它不负责理解图像语义,而是用更短的比特表示更常见的信息,用更长的比特表示更少见的信息。

H.264 里两种主流熵编码:

  • CAVLC:更简单
  • CABAC:压缩效率通常更高,但复杂度也更高

H.265 更偏向用 CABAC,所以能进一步压榨码率。


十、环路滤波

由于分块编码和量化,图像边界上可能出现明显的块状痕迹(块效应)。如果直接把这种”粗糙重建结果”拿去当参考帧,后续预测效果也会受影响。

所以编码器在重建图像上做后处理:

  • 去块滤波(Deblocking Filter):在 H.264 和 H.265 中都存在
  • SAO(Sample Adaptive Offset):H.265 里更常见

这一步不是”可有可无的美颜”,而是编码闭环的一部分——改善重建图像质量,让后续参考帧更稳定。


十一、解码流程

解码器不是”把文件直接显示出来”,而是沿着编码的反方向把画面一步步恢复出来:

flowchart LR
    A[H.264 码流] --> B[解析 NALU]
    B --> C[熵解码]
    C --> D[反量化]
    D --> E[反变换]
    E --> F[残差]
    F --> G[预测与运动补偿]
    G --> H[重建帧]
    H --> I[去块滤波]
    I --> J[输出 YUV]

解码器做的事:

  1. 解析 NALU,识别出 SPS、PPS、Slice 等,建立解码上下文
  2. 熵解码,还原量化后的系数、运动信息、模式信息
  3. 反量化与反变换,恢复残差块
  4. 帧内预测或运动补偿恢复重建块:预测块 + 残差块 = 重建块
  5. 去块滤波后输出重建帧

量化已经丢了信息,所以解码器恢复的是”重建图像”,不是原始无损图像。


十二、码流结构:压缩结果最终长什么样

12.1 NALU 是什么

NALU(Network Abstraction Layer Unit)是视频码流中的基本组织单元。压缩结果不是一整坨没有边界的二进制,而是被切分成一个个有类型、有边界意义的数据单元。

为什么需要 NAL 层?因为编码器产生的信息还要被传输、存储、解封装、交给解码器识别,需要一种统一组织方式把不同类型的信息块区分开。

12.2 SPS / PPS / VPS

  • SPS(Sequence Parameter Set):序列级参数集,包含分辨率、编码级别、Profile/Level 等对整个视频序列都很关键的全局解码参数。
  • PPS(Picture Parameter Set):图像级参数集,补充更具体的编码控制信息。
  • VPS(Video Parameter Set):H.265 里更常出现的高层视频参数集。

解码器如果不知道这些参数,就无法正确解释后面的码流。很多花屏、解码失败、播放器打不开的问题,最后都可能和参数集缺失或不一致有关。

12.3 Annex B 与 AVCC

Annex B:用起始码分隔 NALU。典型标记:

1
2
00 00 00 01
00 00 01

AVCC:不用起始码,而是用长度字段描述后面这个 NALU 有多长。

为什么区分这两种?因为不同封装格式、播放器、解封装器、FFmpeg 模块,对输入码流的期望可能不同。有时候问题不是编码标准不一样,而是码流组织格式不一样——这就是工程里常见”Annex B 转 AVCC”和”AVCC 转 Annex B”的来源。

12.4 裸流与封装格式

初学者特别容易把这两层混在一起。

  • 码流层:裸 H.264、裸 H.265。关心 NALU 怎么组织、参数集在哪里、码流边界怎么表示。
  • 容器/封装层:MP4、FLV、TS、MKV。关心音视频流怎么一起装、时间戳怎么管理、元数据怎么组织。
flowchart LR
    A[原始 YUV 帧] --> B[H264 编码器]
    B --> C[H264 码流]
    C --> D[MP4 / FLV / MKV 容器]

H.264 的最终输出是比特流,不是 MP4 文件。MP4 是封装格式。


十三、为什么编码器一定要缓存历史帧

P 帧和 B 帧不是独立存在的,必须依赖参考图像。没有缓存,预测就无从谈起。

关键理解:编码器缓存的不是原始输入帧,而是重建帧。解码器缓存的也是重建帧。

为什么?因为编码器和解码器后面做预测时,必须看到完全一致的参考图像。如果编码器拿原始图做参考、解码器拿重建图做参考,两边预测结果会逐步漂移,最终完全对不上。

所以编码器内部有一条本地重建路径:

flowchart TD
    A[原始帧] --> B[预测]
    B --> C[残差]
    C --> D[变换与量化]
    D --> E[熵编码]
    D --> F[反量化和反变换]
    F --> G[加回预测]
    G --> H[重建帧]
    H --> I[参考缓存]

这条路径说明:编码器一边生成输出码流,一边在本地”模拟解码”。模拟出来的重建帧进入参考缓存,之后的 P/B 帧预测都基于这些重建帧进行。编码器内部其实也有一个小型解码环

13.1 帧的依赖关系

  • I 帧:相对独立,编码后重建帧进入参考缓存,供后续 P/B 帧使用
  • P 帧:从参考缓存中找历史帧做运动估计,记录 MV + 残差
  • B 帧:可以同时参考前后参考帧,参考管理更复杂,编码顺序和显示顺序可能不同

13.2 IDR 帧——缓存重置

IDR 帧是 I 帧的一种特殊类型。它的意义是:从这个点开始,后续参考关系不再依赖它之前的画面。解码器可以安全丢弃旧参考缓存,丢包、误码、漂移不容易继续向后扩散。

IDR 帧会让参考链路重新开始,相当于一次缓存重置。 这也是播放器 seek、直播恢复、丢包后重新对齐时非常关键的点。

13.3 GOP 中的帧关系

一个典型的 GOP 结构:

flowchart LR
    A[IDR] --> B[B]
    B --> C[B]
    C --> D[P]
    D --> E[B]
    E --> F[B]
    F --> G[P]
    G --> H[下一个 IDR]

从显示顺序看:I → B → B → P → B → B → P → IDR

但编码顺序往往需要重排序——因为 B 帧需要前后参考都准备好才能编码/解码。所以要区分显示顺序编码顺序/传输顺序,这也是 B 帧比 P 帧更复杂的核心原因之一。


十四、把整条链路收在一起

flowchart TD
    A[原始 YUV 帧] --> B[切成宏块/块]
    B --> C[判断帧类型]
    C --> D[帧内预测]
    C --> E[帧间预测]
    D --> F[残差]
    E --> F[残差]
    F --> G[变换]
    G --> H[量化]
    H --> I[熵编码]
    I --> J[NALU 码流输出]
    H --> K[反量化]
    K --> L[反变换]
    L --> M[重建帧]
    M --> N[参考缓存]
    N --> E

这张图把两条线放在了一起:

  • 上面是输出码流的主压缩链:分块→预测→残差→变换→量化→熵编码→NALU
  • 下面是编码器内部本地重建并写入参考缓存的链:量化结果→反量化→反变换→重建→参考缓存

H.264/H.265 的核心,不只是”把视频压小”,而是通过分块、预测、变换、量化、熵编码和码流组织,构建出一套既能高效压缩、又能稳定解码的完整体系。

如果压缩成一版面试答案:

  1. 编码主线:原始 YUV 帧按块划分,根据帧类型做帧内或帧间预测→只编残差→变换、量化、熵编码→打包为 NALU 比特流
  2. 解码主线:解析 NALU→熵解码→反量化→反变换→恢复残差→预测/运动补偿重建→滤波输出
  3. 缓存主线:P/B 帧必须依赖参考图像,编解码器两端都缓存重建帧;IDR 帧重启参考链;RDO 贯穿每个决策点的率失真权衡

H.264 不是”把每一帧单独压成更小的图片”,而是把一串连续画面之间的关系编码进了码流里。真正理解了这一点,I/P/B 帧、GOP、运动估计、参考帧缓存、IDR 刷新、Profile/Level、RDO 和码率控制这些概念就会自然连起来。


H.264/H.265 核心原理:从基础世界观到编解码流程与码流结构
https://breaker505.github.io/2026/03/26/h264-h265-core-principles/
作者
爱发呆的鱼
发布于
2026年3月26日
许可协议