一、从原始帧→编码流的四个阶段
原始 YUV 帧
│
▼
① 预测(Prediction)
帧内预测:用当前帧已编码的区域预测当前块(I 帧适用)
帧间预测:用前一帧搜索最相似的块,记录位移向量(P 帧适用)
输出 = 残差块(原始块 - 预测块)
│
▼
② 变换(DCT)
把残差块从"空间域"转成"频域"
DCT 后:低频系数(左上角)很大 → 人眼敏感
高频系数(右下角)很小 → 人眼不敏感
│
▼
③ 量化(Quantization)
把 DCT 系数除以量化步长 → 高频系数变成 0
"丢掉人眼不敏感的信息"——这是有损压缩的核心
│
▼
④ 熵编码(Entropy Coding)
Huffman / 算术编码 → 无损压缩
CABAC(H.264 默认):比 CAVLC 压缩率高 10%,但计算量大 3x
│
▼
H.264 码流
二、手写简化 DCT(了解原理即可)
真正的编码器用整数 DCT(如 HEVC 的 4x4 DCT),这里用简化版展示原理:
#include <cmath>
#include <vector>
// 简化 4x4 DCT(实际编码器用整数近似版)
// 输入:4x4 空间域残差块
// 输出:4x4 频域系数块
void dct_4x4(const int input[4][4], float output[4][4]) {
const float pi = 3.14159265f;
for (int u = 0; u < 4; u++) {
for (int v = 0; v < 4; v++) {
float sum = 0.0f;
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
float cu = (u == 0) ? 1.0f / sqrtf(2) : 1.0f;
float cv = (v == 0) ? 1.0f / sqrtf(2) : 1.0f;
sum += cu * cv * input[x][y] *
cosf((2 * x + 1) * u * pi / 8) *
cosf((2 * y + 1) * v * pi / 8);
}
}
output[u][v] = sum / 4.0f;
}
}
}
// 演示:平坦块 vs 边缘块
void demo_dct() {
// 情况 1:平坦块(如蓝天,像素值接近)
int flat_block[4][4] = {
{128, 129, 127, 128},
{129, 128, 128, 127},
{127, 128, 129, 128},
{128, 127, 128, 129}
};
// 情况 2:边缘块(如文字边缘,像素突变)
int edge_block[4][4] = {
{ 10, 10, 10, 200},
{ 10, 10, 10, 200},
{ 10, 10, 10, 200},
{200, 200, 200, 200}
};
float dct_flat[4][4], dct_edge[4][4];
dct_4x4(flat_block, dct_flat);
dct_4x4(edge_block, dct_edge);
auto print_dct = [](const char* name, float coef[4][4]) {
printf("%s:\n", name);
for (int u = 0; u < 4; u++) {
for (int v = 0; v < 4; v++) {
printf("%8.1f ", coef[u][v]);
}
printf("\n");
}
printf("\n");
};
print_dct("平坦块 DCT", dct_flat);
print_dct("边缘块 DCT", dct_edge);
}
// 输出:
// 平坦块 DCT:
// 511.0 -0.5 0.8 -0.3
// -1.2 0.0 0.0 0.0
// 0.0 0.0 0.0 0.0
// -0.2 0.0 0.0 0.0
// → DC 系数(左上角)很大,其他基本为 0
// → 量化后:只保留 DC,其他全部变 0 → 极小的数据量!
//
// 边缘块 DCT:
// 840.0 -70.0 0.0 0.0
// -140.0 93.4 0.0 0.0
// 0.0 0.0 0.0 0.0
// 0.0 0.0 0.0 0.0
// → 高频分量比平坦块多很多,但相比原始数据仍然大幅压缩
关键理解:
- 平坦区域:DCT 后只有 DC 系数(左上角)非零 → 只需要 1 个数就能代表 16 个像素
- 边缘区域:DCT 后还有几个 AC 系数 → 但量化后大部分变 0 → zigzag 扫描后连续的 0 可以用游程编码压缩