系列文章目录
【x264编码器】章节1——x264编码流程及基于x264的编码器demo
【x264编码器】章节2——x264的lookahead流程分析
【x265编码器】章节2——编码流程及基于x265的编码器demo
目录
一、x264的变换量化流程
x264的不同尺寸的变换过程,最终都会划分成4x4尺寸进行变换,变换量化总体的流程如下:
二、使用步骤
1.16x16宏块编码mb_encode_i16x16
对于16×16的亮度块,变换量化的过程包括两个部分:直流部分(DC)和交流部分(AC)。为了实现16×16亮度块的变换和量化,需要将其分为16个4×4的子块。与4×4模式不同的是,16×16模式首先从这16个4×4系数矩阵中提取出直流分量,并组成一个新的4×4矩阵。随后,对这个直流矩阵进行Hadamard变换,并进行量化处理。
代码如下:
static void mb_encode_i16x16( x264_t *h, int p, int i_qp )
{
pixel *p_src = h->mb.pic.p_fenc[p];//定义了指向输入帧和输出帧的指针p_src和p_dst
pixel *p_dst = h->mb.pic.p_fdec[p];
ALIGNED_ARRAY_64( dctcoef, dct4x4,[16],[16] );//用于存储DCT系数的数组dct4x4和dct_dc4x4
ALIGNED_ARRAY_64( dctcoef, dct_dc4x4,[16] );
int nz, block_cbp = 0;//非零系数的数量nz、块的CBP(coded block pattern)值
int decimate_score = h->mb.b_dct_decimate ? 0 : 9;//降采样得分decimate_score
int i_quant_cat = p ? CQM_4IC : CQM_4IY;//量化矩阵的类别i_quant_cat
int i_mode = h->mb.i_intra16x16_pred_mode;//16x16内预测模式i_mode
if( h->mb.b_lossless )//当前帧是无损模式
x264_predict_lossless_16x16( h, p, i_mode );//调用x264_predict_lossless_16x16函数进行16x16预测
else
h->predict_16x16[i_mode]( h->mb.pic.p_fdec[p] );
if( h->mb.b_lossless )
{ //如果当前帧是无损模式,执行无损编码的逻辑,遍历每个4x4块,计算非零系数的数量,更新非零系数计数数组和块的CBP值
for( int i = 0; i < 16; i++ )
{
int oe = block_idx_xy_fenc[i];
int od = block_idx_xy_fdec[i];
nz = h->zigzagf.sub_4x4ac( h->dct.luma4x4[16*p+i], p_src+oe, p_dst+od, &dct_dc4x4[block_idx_yx_1d[i]] );
h->mb.cache.non_zero_count[x264_scan8[16*p+i]] = nz;
block_cbp |= nz;
}
h->mb.i_cbp_luma |= block_cbp * 0xf;
h->mb.cache.non_zero_count[x264_scan8[LUMA_DC+p]] = array_non_zero( dct_dc4x4, 16 );
h->zigzagf.scan_4x4( h->dct.luma16x16_dc[p], dct_dc4x4 );
return;
}
CLEAR_16x16_NNZ( p );
//如果当前帧不是无损模式,执行有损编码的逻辑,先对16x16块进行DCT变换
h->dctf.sub16x16_dct( dct4x4, p_src, p_dst );
//如果启用了噪声减弱(noise reduction),则对DCT系数进行噪声减弱处理
if( h->mb.b_noise_reduction )
for( int idx = 0; idx < 16; idx++ )
h->quantf.denoise_dct( dct4x4[idx], h->nr_residual_sum[0], h->nr_offset[0], 16 );
//遍历每个4x4块,将DCT系数的直流分量存入dct_dc4x4数组,并将DCT系数的直流分量置零
for( int idx = 0; idx < 16; idx++ )
{
dct_dc4x4[block_idx_xy_1d[idx]] = dct4x4[idx][0];
dct4x4[idx][0] = 0;
}
if( h->mb.b_trellis )
{ //启用了Trellis量化,则使用循环遍历每个4x4块,调用x264_quant_4x4_trellis函数进行Trellis量化
for( int idx = 0; idx < 16; idx++ )
if( x264_quant_4x4_trellis( h, dct4x4[idx], i_quant_cat, i_qp, ctx_cat_plane[DCT_LUMA_AC][p], 1, !!p, idx ) )
{ //果返回值为真,表示该块经过Trellis量化后非零系数的数量发生了变化
block_cbp = 0xf;//将块的CBP值设置为0xf(即全部块都有非零系数
h->zigzagf.scan_4x4( h->dct.luma4x4[16*p+idx], dct4x4[idx] );//然后执行扫描、反量化和判定非零系数的操作
h->quantf.dequant_4x4( dct4x4[idx], h->dequant4_mf[i_quant_cat], i_qp );
if( decimate_score < 6 ) decimate_score += h->quantf.decimate_score15( h->dct.luma4x4[16*p+idx] );
h->mb.cache.non_zero_count[x264_scan8[16*p+idx]] = 1;
}
}
else
{
for( int i8x8 = 0; i8x8 < 4; i8x8++ )
{ //如果未启用Trellis量化,则使用循环遍历每个8x8块,调用h->quantf.quant_4x4x4函数进行普通的量化。该函数返回非零系数的数量
nz = h->quantf.quant_4x4x4( &dct4x4[i8x8*4], h->quant4_mf[i_quant_cat][i_qp], h->quant4_bias[i_quant_cat][i_qp] );
if( nz )
{
block_cbp = 0xf;//如果非零系数的数量大于0,将块的CBP值设置为0xf,然后执行扫描、反量化和判定非零系数的操作
FOREACH_BIT( idx, i8x8*4, nz )
{
h->zigzagf.scan_4x4( h->dct.luma4x4[16*p+idx], dct4x4[idx] );
h->quantf.dequant_4x4( dct4x4[idx], h->dequant4_mf[i_quant_cat], i_qp );
if( decimate_score < 6 ) decimate_score += h->quantf.decimate_score15( h->dct.luma4x4[16*p+idx] );
h->mb.cache.non_zero_count[x264_scan8[16*p+idx]] = 1;
}
}
}
}
/* Writing the 16 CBFs in an i16x16 block is quite costly, so decimation can save many bits. */
/* More useful with CAVLC, but still useful with CABAC. */
if( decimate_score < 6 )
{ //如果decimate_score小于6,表示减采样得分低于阈值,执行减采样的逻辑。首先将16x16块的非零系数数量清零,将块的CBP值设置为0,表示该块没有非零系数
CLEAR_16x16_NNZ( p );
block_cbp = 0;
}
else//将块的CBP值加入到h->mb.i_cbp_luma中,表示该块存在非零系数
h->mb.i_cbp_luma |= block_cbp;
//调用h->dctf.dct4x4dc函数对16x16块的直流分量进行DCT变换
h->dctf.dct4x4dc( dct_dc4x4 );
if( h->mb.b_trellis )
nz = x264_quant_luma_dc_trellis( h, dct_dc4x4, i_quant_cat, i_qp, ctx_cat_plane[DCT_LUMA_DC][p], 1, LUMA_DC+p );
else//调用h->quantf.quant_4x4_dc函数进行普通的直流分量量化。返回值nz表示非零系数的数量
nz = h->quantf.quant_4x4_dc( dct_dc4x4, h->quant4_mf[i_quant_cat][i_qp][0]>>1, h->quant4_bias[i_quant_cat][i_qp][0]<<1 );
//更新块的非零系数计数数组
h->mb.cache.non_zero_count[x264_scan8[LUMA_DC+p]] = nz;
if( nz )
{ //如果非零系数的数量大于0,执行扫描、反量化和判定非零系数的操作。将DCT系数按照扫描顺序重新排列,并将直流分量存入dct_dc4x4数组中
h->zigzagf.scan_4x4( h->dct.luma16x16_dc[p], dct_dc4x4 );
/* output samples to fdec */
h->dctf.idct4x4dc( dct_dc4x4 );
h->quantf.dequant_4x4_dc( dct_dc4x4, h->dequant4_mf[i_quant_cat], i_qp ); /* XXX not inversed */
if( block_cbp )
for( int i = 0; i < 16; i++ )
dct4x4[i][0] = dct_dc4x4[block_idx_xy_1d[i]];
}
/* put pixels to fdec */
if( block_cbp )//如果block_cbp(块的CBP值)为真,则调用h->dctf.add16x16_idct函数将反量化后的DCT系数加到输出帧p_dst中
h->dctf.add16x16_idct( p_dst, dct4x4 );
else if( nz )//如果block_cbp为假且非零系数的数量大于0,则调用h->dctf.add16x16_idct_dc函数将反量化后的直流分量加到输出帧p_dst中
h->dctf.add16x16_idct_dc( p_dst, dct_dc4x4 );
}
2.4x4变换sub4x4_dct
这段代码实现了将两个4x4块的像素差值进行DCT变换,并将结果存储在dct
数组中,代码如下:
static void sub4x4_dct( dctcoef dct[16], pixel *pix1, pixel *pix2 )
{
dctcoef d[16];
dctcoef tmp[16];//首先定义了一个临时数组d和一个中间结果数组tmp,用于存储计算过程中的中间结果
//通过调用pixel_sub_wxh函数,计算两个4x4块的像素差值,将结果存储在数组d中,pix1和pix2分别表示两个4x4块的像素数组,FENC_STRIDE和FDEC_STRIDE表示对应的步长
pixel_sub_wxh( d, 4, pix1, FENC_STRIDE, pix2, FDEC_STRIDE );
//使用两次循环遍历,分别对tmp数组和最终结果数组dct进行计算
for( int i = 0; i < 4; i++ )
{ //第一个循环遍历对tmp数组进行计算。对于每个4x4块中的每一行
int s03 = d[i*4+0] + d[i*4+3];//s03:第0列和第3列的和
int s12 = d[i*4+1] + d[i*4+2];//s12:第1列和第2列的和
int d03 = d[i*4+0] - d[i*4+3];//d03:第0列和第3列的差值
int d12 = d[i*4+1] - d[i*4+2];//d12:第1列和第2列的差值
//然后将这些值按照特定的公式计算得到中间结果,并存储在tmp数组中
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++ )
{ //第二个循环遍历对最终结果数组dct进行计算。对于每个4x4块中的每一列
int s03 = tmp[i*4+0] + tmp[i*4+3];//s03:第0行和第3行的和
int s12 = tmp[i*4+1] + tmp[i*4+2];//s12:第1行和第2行的和
int d03 = tmp[i*4+0] - tmp[i*4+3];//d03:第0行和第3行的差值
int d12 = tmp[i*4+1] - tmp[i*4+2];//d12:第1行和第2行的差值
//然后将这些值按照特定的公式计算得到最终的DCT系数,并存储在dct数组中
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;
}
}
3.量化quant_4x4x4
实现了对一个4x4x4的DCT系数数组进行量化操作,并返回记录非零系数情况的标志变量,代码如下:
static int quant_4x4x4( dctcoef dct[4][16], udctcoef mf[16], udctcoef bias[16] )
{
int nza = 0;//首先定义了一个变量nza用于记录非零系数的情况。初始值为0
for( int j = 0; j < 4; j++ )
{ //外层循环for遍历4个4x4块的DCT系数
int nz = 0;
for( int i = 0; i < 16; i++ )//内层循环for遍历每个4x4块中的16个DCT系数
QUANT_ONE( dct[j][i], mf[i], bias[i] );//在内层循环中,调用QUANT_ONE函数对每个DCT系数进行量化
nza |= (!!nz)<<j;//如果量化后的系数nz不为零,则将对应的位(!!nz)设置为1
}
return nza;//返回记录非零系数情况的变量nza
}
4.针对DC量化dct4x4dc
这段代码实现了对一个4x4块的DCT系数进行直流成分变换,并将结果存储在数组d
中,代码如下:
static void dct4x4dc( dctcoef d[16] )
{
dctcoef tmp[16];
for( int i = 0; i < 4; i++ )
{
int s01 = d[i*4+0] + d[i*4+1];
int d01 = d[i*4+0] - d[i*4+1];
int s23 = d[i*4+2] + d[i*4+3];
int d23 = d[i*4+2] - d[i*4+3];
tmp[0*4+i] = s01 + s23;
tmp[1*4+i] = s01 - s23;
tmp[2*4+i] = d01 - d23;
tmp[3*4+i] = d01 + d23;
}
for( int i = 0; i < 4; i++ )
{
int s01 = tmp[i*4+0] + tmp[i*4+1];
int d01 = tmp[i*4+0] - tmp[i*4+1];
int s23 = tmp[i*4+2] + tmp[i*4+3];
int d23 = tmp[i*4+2] - tmp[i*4+3];
d[i*4+0] = ( s01 + s23 + 1 ) >> 1;
d[i*4+1] = ( s01 - s23 + 1 ) >> 1;
d[i*4+2] = ( d01 - d23 + 1 ) >> 1;
d[i*4+3] = ( d01 + d23 + 1 ) >> 1;
}
}