文档目录

一、从原始帧→编码流的四个阶段

原始 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 可以用游程编码压缩