源码阅读 x264 - 量化

x264x264_quant_init 函数中初始化了量化有关的函数,本文分析部分量化函数的实现

量化是 H.264 视频压缩编码中对视频质量影响最大的地方,也是会导致信息丢失的地方。量化的原理可以表示为下面公式:

\[ FQ = round(y / Qstep) \]

其中:

  • \(y\) 为输入样本点编码
  • \(Qstep\) 为量化步长
  • \(FQ\)\(y\) 的量化值
  • \(round()\) 为取整函数(其输出为与输入实数最近的整数)

其相反过程,即反量化为:

\[ y' = FQ \times Qstep \]

如果 Qstep 较大,则量化值 FQ 取值较小,其相应的编码长度较小,但是但反量化时损失较多的图像细节信息。简而言之,Qstep 越大,视频压缩编码后体积越小,视频质量越差。

H.264 中,量化步长 Qstep 共有 52 个值,如下表所示。其中 QP 是量化参数,是量化步长的序号。当 QP 取最小值 0 时代表最精细的量化,当 QP 取最大值 51 时代表最粗糙的量化。QP 每增加 6,Qstep 增加一倍。

x264_quant_init

void x264_quant_init(x264_t *h, uint32_t cpu, x264_quant_function_t *pf)
{
    pf->quant_8x8 = quant_8x8;                              // 针对 8x8DCT 的量化
    pf->quant_4x4 = quant_4x4;                              // 量化 4x4=16 个
    pf->quant_4x4x4 = quant_4x4x4;                          // 处理 4 个 4x4 的块
    //Intra16x16 中,16 个 DC 系数 Hadamard 变换后对的它们量化
    pf->quant_4x4_dc = quant_4x4_dc;
    pf->quant_2x2_dc = quant_2x2_dc;

    pf->dequant_4x4 = dequant_4x4;                          // 反量化 4x4=16 个
    pf->dequant_4x4_dc = dequant_4x4_dc;                    // 处理 4 个 4x4 的块
    pf->dequant_8x8 = dequant_8x8;                          // 针对 8x8DCT 的反量化
    // Intra16x16 中,16 个 DC 系数 Hadamard 变换后对的它们反量化
    pf->idct_dequant_2x4_dc = idct_dequant_2x4_dc;
    pf->idct_dequant_2x4_dconly = idct_dequant_2x4_dconly;

    pf->optimize_chroma_2x2_dc = optimize_chroma_2x2_dc;
    pf->optimize_chroma_2x4_dc = optimize_chroma_2x4_dc;

    pf->denoise_dct = denoise_dct;
    pf->decimate_score15 = decimate_score15;
    pf->decimate_score16 = decimate_score16;
    pf->decimate_score64 = decimate_score64;

    pf->coeff_last4 = coeff_last4;
    pf->coeff_last8 = coeff_last8;
    pf->coeff_last[DCT_LUMA_AC] = coeff_last15;
    pf->coeff_last[DCT_LUMA_4x4] = coeff_last16;
    pf->coeff_last[DCT_LUMA_8x8] = coeff_last64;
    pf->coeff_level_run4 = coeff_level_run4;
    pf->coeff_level_run8 = coeff_level_run8;
    pf->coeff_level_run[DCT_LUMA_AC] = coeff_level_run15;
    pf->coeff_level_run[DCT_LUMA_4x4] = coeff_level_run16;
    /* 此处省略大量平台的汇编函数初始化代码 */
    ......

quant_4x4

quant_4x4() 用于对 4x4DCT 残差矩阵进行量化。该函数的定义位于 common/quant.c,如下所示。

/* 功能:quant_4x4() 用于对 4x4 的 DCT 残差矩阵进行量化 */
// 输入输出都是 dct[16]
static int quant_4x4(dctcoef dct[16], udctcoef mf[16], udctcoef bias[16] ) {
    int nz = 0;
    for (int i = 0; i < 16; i++)
        QUANT_ONE(dct[i], mf[i], bias[i] );
    return !!nz;
}

/* 其中 QUANT_ONE 定义如下 */
#define QUANT_ONE(coef, mf, f) { \
    if((coef) > 0 ) (coef) = (f + (coef)) * (mf) >> 16; \
    else (coef) = - ((f - (coef)) * (mf) >> 16); \
    nz |= (coef); \
}

其中,QUANT_ONE() 完成了一个 DCT 系数的量化工作,从 QUANT_ONE() 的定义可以看出,它实现了上文提到的 H.264 标准中的量化公式:

\[ \left | Z_{ij}\right | = (\left | W_{ij}\right | \cdot MF + f) >> qbits \]

可以看出 quant_4x4() 循环 16 次调用了 QUANT_ONE() 完成了量化工作。并且将 DCT 系数值,MF 值,bias 偏移值直接传递给了该宏。

quant_4x4x4

quant_4x4x4() 用于对 4 个 4x4 的 DCT 残差矩阵进行量化。该函数的定义位于 common/quant.c,从 quant_4x4x4() 的定义可以看出,该函数相当于调用了 4 次 quant_4x4() 函数。对应的代码分析如下:

/* 对 4 个 4x4 的 DCT 残差矩阵进行量化, 从 quant_4x4x4() 的定义可以看出,该函数相当于调用了 4 次 quant_4x4() 函数 */
static int quant_4x4x4(dctcoef dct[4][16], udctcoef mf[16], udctcoef bias[16] ) {
    int nza = 0;
    for (int j = 0; j < 4; j++) {
        int nz = 0;
        for (int i = 0; i < 16; i++)
            QUANT_ONE(dct[j][i], mf[i], bias[i] );
        nza |= (!!nz)<<j;
    }
    return nza;
}

quant_8x8

quant_8x8() 函数的定义位于 common/quant.c 中,定义如下:

static int quant_8x8(dctcoef dct[64], udctcoef mf[64], udctcoef bias[64] ) {
    int nz = 0;
    for (int i = 0; i < 64; i++)
        QUANT_ONE(dct[i], mf[i], bias[i] );
    return !!nz;
}

参考资料