源码阅读 x264 - 离散余弦变换
x264
中 x264_dct_init
函数中初始化了与离散余弦变换有关的函数,本文分析部分离散余弦变换函数的实现
DCT 变换的核心理念就是把图像的低频信息(对应大面积平坦区域)变换到系数矩阵的左上角,而把高频信息变换到系数矩阵的右下角,之后在压缩时就可以去除掉人眼不敏感的高频信息(位于矩阵右下角的系数),从而达到压缩数据的目的。
关于 DCT 的计算,都是通过 x264_dct_init
函数内定义的 DCT
和 IDCT
函数完成的,定义如下:
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
函数名称有如下规律:
DCT
函数名称前面有sub
,代表对两块像素相减得到残差之后,再进行DCT
变换。DCT
反变换函数名称前面有add
,代表将DCT
反变换之后的残差数据叠加到预测数据上。- 以
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;
}
}