源码阅读 x264 - 离散余弦变换

x264x264_dct_init 函数中初始化了与离散余弦变换有关的函数,本文分析部分离散余弦变换函数的实现

DCT 变换的核心理念就是把图像的低频信息(对应大面积平坦区域)变换到系数矩阵的左上角,而把高频信息变换到系数矩阵的右下角,之后在压缩时就可以去除掉人眼不敏感的高频信息(位于矩阵右下角的系数),从而达到压缩数据的目的。

关于 DCT 的计算,都是通过 x264_dct_init 函数内定义的 DCTIDCT 函数完成的,定义如下:

void x264_dct_init(uint32_t cpu, x264_dct_function_t *dctf)
{
    dctf->sub4x4_dct    = sub4x4_dct;
    dctf->add4x4_idct   = add4x4_idct;

    dctf->sub8x8_dct    = sub8x8_dct;
    dctf->sub8x8_dct_dc = sub8x8_dct_dc;
    dctf->add8x8_idct   = add8x8_idct;
    dctf->add8x8_idct_dc = add8x8_idct_dc;

    /* 此处省略大量平台的汇编函数初始化代码 */
    ......

dct 函数命名规律

从源代码可以看出,x264_dct_init() 初始化了一系列的 DCT 变换的函数,这些 DCT 函数名称有如下规律:

  1. DCT 函数名称前面有 sub,代表对两块像素相减得到残差之后,再进行 DCT 变换。
  2. DCT 反变换函数名称前面有 add,代表将 DCT 反变换之后的残差数据叠加到预测数据上。
  3. dct8 为结尾的函数使用了 8x8DCT,其余函数是用的都是 4x4DCT

sub4x4_dct

sub4x4_dct() 可以将两块 4x4 的图像相减求残差后,进行 DCT 变换,源代码如下:

/* 
 * 4x4 DCT 变换
 * 注意首先获取 pix1 和 pix2 两块数据的残差,然后再进行变换
 * 返回 dct[16]
 */
static void sub4x4_dct(dctcoef dct[16], pixel *pix1, pixel *pix2 ) {
    dctcoef d[16];
    dctcoef tmp[16];

    pixel_sub_wxh(d, 4, pix1, FENC_STRIDE, pix2, FDEC_STRIDE);

    for (int i = 0; i < 4; i++) {
        int s03 = d[i*4+0] + d[i*4+3];
        int s12 = d[i*4+1] + d[i*4+2];
        int d03 = d[i*4+0] - d[i*4+3];
        int d12 = d[i*4+1] - d[i*4+2];

        tmp[0*4+i] =   s03 +   s12;
        tmp[1*4+i] = 2*d03 +   d12;
        tmp[2*4+i] =   s03 -   s12;
        tmp[3*4+i] =   d03 - 2*d12;
    }

    for (int i = 0; i < 4; i++) {
        int s03 = tmp[i*4+0] + tmp[i*4+3];
        int s12 = tmp[i*4+1] + tmp[i*4+2];
        int d03 = tmp[i*4+0] - tmp[i*4+3];
        int d12 = tmp[i*4+1] - tmp[i*4+2];

        dct[i*4+0] =   s03 +   s12;
        dct[i*4+1] = 2*d03 +   d12;
        dct[i*4+2] =   s03 -   s12;
        dct[i*4+3] =   d03 - 2*d12;
    }
}

源代码中涉及函数:

/*
 * 求残差用 注意求的是一个方块形像素
 *
 * 参数的含义如下:
 * diff:输出的残差数据
 * i_size:方块的大小
 * pix1:输入数据 1
 * i_pix1:输入数据 1 一行像素大小(stride)
 * pix2:输入数据 2
 * i_pix2:输入数据 2 一行像素大小(stride)
 */
static inline void pixel_sub_wxh( dctcoef *diff, int i_size,
                                  pixel *pix1, int i_pix1, pixel *pix2, int i_pix2 ) {
    for (int y = 0; y < i_size; y++) {
        for(int x = 0; x < i_size; x++)
            diff[x + y * i_size] = pix1[x] - pix2[x];
        pix1 += i_pix1;
        pix2 += i_pix2;
    }
}

add4x4_idct

add4x4_idct() 可以将残差数据进行 DCT 反变换,并将变换后得到的残差像素数据叠加到预测数据上,源代码如下:

static ALWAYS_INLINE pixel x264_clip_pixel(int x) {
    return ((x & ~PIXEL_MAX) ? (-x)>>31 & PIXEL_MAX : x );
}

static void add4x4_idct(pixel *p_dst, dctcoef dct[16] ) {
    dctcoef d[16];
    dctcoef tmp[16];

    for (int i = 0; i < 4; i++) {
        int s02 =  dct[0*4+i]     +  dct[2*4+i];
        int d02 =  dct[0*4+i]     -  dct[2*4+i];
        int s13 =  dct[1*4+i]     + (dct[3*4+i]>>1);
        int d13 = (dct[1*4+i]>>1) -  dct[3*4+i];

        tmp[i*4+0] = s02 + s13;
        tmp[i*4+1] = d02 + d13;
        tmp[i*4+2] = d02 - d13;
        tmp[i*4+3] = s02 - s13;
    }

    for (int i = 0; i < 4; i++) {
        int s02 =  tmp[0*4+i]     +  tmp[2*4+i];
        int d02 =  tmp[0*4+i]     -  tmp[2*4+i];
        int s13 =  tmp[1*4+i]     + (tmp[3*4+i]>>1);
        int d13 = (tmp[1*4+i]>>1) -  tmp[3*4+i];

        d[0*4+i] = ( s02 + s13 + 32 ) >> 6;
        d[1*4+i] = ( d02 + d13 + 32 ) >> 6;
        d[2*4+i] = ( d02 - d13 + 32 ) >> 6;
        d[3*4+i] = ( s02 - s13 + 32 ) >> 6;
    }


    for (int y = 0; y < 4; y++) {
        for (int x = 0; x < 4; x++)
            p_dst[x] = x264_clip_pixel( p_dst[x] + d[y*4+x] );
        p_dst += FDEC_STRIDE;
    }
}

参考资料