【x264】分析模块(analyse)的简单分析
参考:
雷霄骅博士,x264源代码简单分析:宏块分析(Analysis)部分-帧间宏块(Inter)
参数分析:
【x264】x264编码器参数配置
流程分析:
【x264】x264编码主流程简单分析
【x264】编码核心函数(x264_encoder_encode)的简单分析
【x264】分析模块(analyse)的简单分析—帧内预测
1. 分析模块(analyse)概述
在x264当中,编码器首先会将一帧图像分成若干个小的图像块(macro block, mb),每一个块当中包含若干个像素点,这些块又可以被称之为像素块。这种操作称之为块划分操作,这里的分析模块就是针对于每一个小的图像块进行处理的。
分析模块主要执行的任务是对宏块进行预测,预测的主要思想是以当前块的参考块作为依据,使用不同的预测模式,进行预测操作,获得一个预测块,随后将获得的预测块和当前块进行对比,获得一个差值,将这个差值进行变换、量化和编码,最后评估当前的预测模式是否是最合适的。在解码端,基于这个块的残差值和参考块的重建值,能够获取解码之后块的值,此外,由于帧的类型有所不同,预测的模式也有差异,例如I帧(Intra帧),P帧(Inter-Prediciton)还有B帧(Inter-Bi-Prediction),I帧当中的块只进行帧内预测,P帧中的块主要进行前向预测,B帧中的块主要进行前后向双向预测。但是,mb的预测方式和帧的类型并不是完全一致,有些P帧中的mb块也会进行帧内预测,但数量很少。
这里应该思考的问题有:
1.1 预测的意义
使用这个工具会考虑到一个问题,为什么要进行分析预测,随后计算编码残差以及进行变换量化呢?通过预测,能够获取一个预测块,如果预测的策略比较合适,那么预测块和原始块差异比较小,如果计算两者的残差值,并且将二者的残差值进行变换、量化和熵编码,这样码流文件就比较小。如果存储的是原始块,码流文件就会很大,这样不符合编码器想要高效压缩视频流的初衷。
值得注意的是,如果编码的帧是Intra帧,这一帧由于是后面很多帧的参考帧,使用了很多高性能的编码策略,所以这一帧的码流是比较大的。
1.2 预测的重要组成变量
在进行预测时,最重要的组成变量包括:图像块的原始像素、预测像素、残差像素和重建像素。那么在编解码前后,它们之间的关系是怎样的?
下面举例说明这几个变量在编解码前后的关系,假设现在使用帧内预测,已经编码了2个mb,分别记录为a和b,现在要编码的块为c,且3个块都位于当前帧的最上方的一行,如下所示。在这种情况下,b的参考块为a,c的参考块为b(参考块只能位于当前块的左侧或者上方),而a没有参考块。
// a b c
// …
// …
// …
…
在编码流程当中,如果a已经进行了编码,后续的b如果要参考a,会进行a的重建,获得一个重建的a_rec,b在进行预测时,参考的是重建的a_rec。在码流当中,存储b的信息时,存储的是残差信息而不是原始信息,解码b时必须依赖b的残差和a的重建。对于这样一个过程,有如下的梳理
org = original(原始像素)
res = residual(残差像素)
pre = predict(预测像素)
rec = reconstruction(重建像素)
(1)b的编解码
编码时:b_res = b_org - b_pre (b的残差 = b的原始 - b的预测)
解码时:b_rec = b_res + a_rec (b的重建 = b的残差 + a的重建)
可以看到,在解码时,b的重建是依赖于a的重建的;同理,有如下的情况:
(2)c的编解码
编码时:c_res = c_org - c_pre(c的残差 = c的原始 - c的预测)
解码时:c_rec = c_res + b_rec(c的重建 = c的残差 + b的重建)
结合上面的(1)和(2),进行移项,有:
b_org = b_res + b_pre
b_rec = b_res + a_rec
c_org = c_res + c_pre
c_rec = c_res + b_rec
由于编解码器的重要目标之一是保证编解码前后的像素之差较小,即b和c的org尽可能接近rec,结合上式有:
b_pre = a_rec
c_pre = b_rec
即b的预测值应该由a的重建值描述,c的预测值由b的重建值描述。换一种说法是,b的预测过程的参考块应该是a的重建块,而c的预测过程中的参考块应该是b的重建块。
如果用一段话来描述这一过程,我想可以这么来描述:
残差信息在经过处理之后会存储在码流当中,为了展示传输过来的图像,需要将当前图像的残差信息和前面图像的重建信息结合,获得当前的图像。同时,第一幅图像的原始编码是高质量编码的,且后面图像参考第一幅图像时进行了高质量的预测,所以能够获得很好的图像展示效果
1.3 预测的模式
预测的模式取决于帧的类型和实际编码的情况,一般而言,Intra帧当中的像素块只进行帧内预测,P帧进行前向参考,B帧进行前后向同时参考,其中P帧和B帧当中的像素块还有可能进行帧内预测。此外,在特殊情况下还会使用PCM模式,即直接存储像素而不进行变换量化。
由于同一个区域内的像素值比较接近,帧内预测可以使用一些特定的预测模式作为模板。其中,16x16的亮度块和8x8的色度块会使用的水平(horizontal)、垂直(vertical)、直流(DC)和平面(plane)模式,4x4的亮度块除了上述4种之外,还会增加水平向下、垂直向右等角度模式。相对比而言,帧间预测由于前后帧运动的方向不确定,不好给出一个预测的模板,使用了一种灵活的描述方式叫做运动向量,先在前后帧当中寻找一个比较接近的像素块,再用运动向量来描述参考块和当前块之间的差异,这个运动向量就是帧间预测的模式
2. 预测主函数(x264_macroblock_analyse)
进行预测的主函数入口位于x264_macroblock_analyse,其定义位于encoder/analyse.c当中。其主要的工作流程为:
- 码控获取qp
- 宏块分析的初始化
- 帧内预测
帧内预测通过一系列的预测模式,确定一个Intra mb的最佳模式。主要流程如下
(1) 从16×16的SAD,4个8×8的SAD和,16个4×4的SAD中选出最优方式(mb_analyse_intra)
(2)先考虑16x16块的损失,再与8x8和4x4的损失进行比较,选择一个最佳的 - P帧的帧间预测
帧间预测分为P帧和B帧,P帧的预测只考虑前向,B帧的预测考虑前向和后向。P帧的预测主要流程如下
(1)检测是否使用P-Skip模式,如果是则将mv设置为0,同时结束预测过程
(2)检查16x16的损失(mb_analyse_inter_p16x16)
(3)检查8x8的损失(mb_analyse_inter_p8x8)
(4)如果8x8的损失小于16x16,则执行8x8的分块处理;处理的数据源自于l0
(5)8x8块的子块的分析(mb_analyse_inter_p4x4)
(6)如果4x4小于8x8,则进行8x4以及4x8尺寸的检查(mb_analyse_inter_p4x8、mb_analyse_inter_p8x4)
(7)如果8x8的代价值小于16x16+16x8,则进行16x8和8x16尺寸的检查(mb_analyse_inter_p16x8、mb_analyse_inter_p8x16)
(8)亚像素精度估计(x264_me_refine_qpel),根据不同的划分方式,输入的cost也不同
(9)对色度分量检查是否进行帧间预测(mb_analyse_intra_chroma),否则就进行帧内预测(mb_analyse_intra)
(9)运动估计的亚像素rd优化(x264_me_refine_qpel_rd) - B帧的帧间预测(与P帧类似,但是预测的方向为前后两个方向)
- 从分析中更新MB(analyse_update_cache)
void x264_macroblock_analyse( x264_t *h )
{
x264_mb_analysis_t analysis;
int i_cost = COST_MAX;
// ----- 1.码控获取qp ----- //
h->mb.i_qp = x264_ratecontrol_mb_qp( h );
/* If the QP of this MB is within 1 of the previous MB, code the same QP as the previous MB,
* to lower the bit cost of the qp_delta. Don't do this if QPRD is enabled. */
if( h->param.rc.i_aq_mode && h->param.analyse.i_subpel_refine < 10 )
h->mb.i_qp = abs(h->mb.i_qp - h->mb.i_last_qp) == 1 ? h->mb.i_last_qp : h->mb.i_qp;
if( h->param.analyse.b_mb_info )
h->fdec->effective_qp[h->mb.i_mb_xy] = h->mb.i_qp; /* Store the real analysis QP. */
// ----- 2.宏块分析的初始化 ----- //
mb_analyse_init( h, &analysis, h->mb.i_qp );
/*--------------------------- Do the analysis ---------------------------*/
// ----- 3.帧内预测 ----- //
// 通过一系列的帧内预测模式,计算出代价最小的最优模式
if( h->sh.i_type == SLICE_TYPE_I )
{
intra_analysis:
// i_mbrd表示宏块的运动搜索过程中所使用的模式的数量
if( analysis.i_mbrd )
mb_init_fenc_cache( h, analysis.i_mbrd >= 2 );
// 进行帧内预测,从16×16的SAD,4个8×8的SAD和,16个4×4SAD中选出最优方式
mb_analyse_intra( h, &analysis, COST_MAX );
if( analysis.i_mbrd )
intra_rd( h, &analysis, COST_MAX ); // 计算使用的比特数量
i_cost = analysis.i_satd_i16x16;
h->mb.i_type = I_16x16;
// 检查4x4和8x8的开销是否更小
COPY2_IF_LT( i_cost, analysis.i_satd_i4x4, h->mb.i_type, I_4x4 );
COPY2_IF_LT( i_cost, analysis.i_satd_i8x8, h->mb.i_type, I_8x8 );
if( analysis.i_satd_pcm < i_cost )
h->mb.i_type = I_PCM;
else if( analysis.i_mbrd >= 2 ) // 如果模式数量大于等于2,则再进行一次refine
intra_rd_refine( h, &analysis );
}
else if( h->sh.i_type == SLICE_TYPE_P )
{ // ----- 4.P帧的预测 ----- //
int b_skip = 0;
// 预取一个参考帧的下几个宏块
h->mc.prefetch_ref( h->mb.pic.p_fref[0][0][h->mb.i_mb_x&3], h->mb.pic.i_stride[0], 0 );
analysis.b_try_skip = 0;
if( analysis.b_force_intra ) // 如果强制进行帧内预测
{
if( !h->param.analyse.b_psy )
{
mb_analyse_init_qp( h, &analysis, X264_MAX( h->mb.i_qp - h->mb.ip_offset, h->param.rc.i_qp_min ) );
goto intra_analysis;
}
}
else
{
/* Special fast-skip logic using information from mb_info. */
if( h->fdec->mb_info && (h->fdec->mb_info[h->mb.i_mb_xy]&X264_MBINFO_CONSTANT) )
{
if( !SLICE_MBAFF && (h->fdec->i_frame - h->fref[0][0]->i_frame) == 1 && !h->sh.b_weighted_pred &&
h->fref[0][0]->effective_qp[h->mb.i_mb_xy] <= h->mb.i_qp )
{
h->mb.i_partition = D_16x16;
/* Use the P-SKIP MV if we can... */
if( !M32(h->mb.cache.pskip_mv) )
{
b_skip = 1;
h->mb.i_type = P_SKIP;
}
/* Otherwise, just force a 16x16 block. */
else
{
h->mb.i_type = P_L0;
analysis.l0.me16x16.i_ref = 0;
M32( analysis.l0.me16x16.mv ) = 0;
}
goto skip_analysis;
}
/* Reset the information accordingly */
else if( h->param.analyse.b_mb_info_update )
h->fdec->mb_info[h->mb.i_mb_xy] &= ~X264_MBINFO_CONSTANT;
}
int skip_invalid = h->i_thread_frames > 1 && h->mb.cache.pskip_mv[1] > h->mb.mv_max_spel[1];
/* If the current macroblock is off the frame, just skip it. */
if( HAVE_INTERLACED && !MB_INTERLACED && h->mb.i_mb_y * 16 >= h->param.i_height && !skip_invalid )
b_skip = 1;
/* Fast P_SKIP detection */
// 快速P-Skip检测
else if( h->param.analyse.b_fast_pskip )
{
if( skip_invalid )
// FIXME don't need to check this if the reference frame is done
{}
else if( h->param.analyse.i_subpel_refine >= 3 )
analysis.b_try_skip = 1;
else if( h->mb.i_mb_type_left[0] == P_SKIP ||
h->mb.i_mb_type_top == P_SKIP ||
h->mb.i_mb_type_topleft == P_SKIP ||
h->mb.i_mb_type_topright == P_SKIP )
b_skip = x264_macroblock_probe_pskip( h );
}
}
h->mc.prefetch_ref( h->mb.pic.p_fref[0][0][h->mb.i_mb_x&3], h->mb.pic.i_stride[0], 1 );
// 检查是否是skip模式,如果不是则按顺序进行16x16、8x8、8x4(4x8)、4x4尺寸的检查
if( b_skip ) // 使用skip模式
{
h->mb.i_type = P_SKIP;
h->mb.i_partition = D_16x16;
assert( h->mb.cache.pskip_mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 );
skip_analysis:
/* Set up MVs for future predictors */
for( int i = 0; i < h->mb.pic.i_fref[0]; i++ )
M32( h->mb.mvr[0][i][h->mb.i_mb_xy] ) = 0;
}
else
{
const unsigned int flags = h->param.analyse.inter;
int i_type;
int i_partition;
int i_satd_inter, i_satd_intra;
// 为所有可能的MVS初始化一个lambda*nbits数组
mb_analyse_load_costs( h, &analysis );
// 检查16x16的损失
mb_analyse_inter_p16x16( h, &analysis );
if( h->mb.i_type == P_SKIP )
{
for( int i = 1; i < h->mb.pic.i_fref[0]; i++ )
M32( h->mb.mvr[0][i][h->mb.i_mb_xy] ) = 0;
return;
}
if( flags & X264_ANALYSE_PSUB16x16 )
{
if( h->param.analyse.b_mixed_references )
mb_analyse_inter_p8x8_mixed_ref( h, &analysis );
else
mb_analyse_inter_p8x8( h, &analysis ); // 检查8x8的损失
}
// 选择一个最好的帧间模式
/* Select best inter mode */
i_type = P_L0;
i_partition = D_16x16;
i_cost = analysis.l0.me16x16.cost;
// 如果8x8的损失小于16x16,则执行8x8的分块处理;处理的数据源自于l0
if( ( flags & X264_ANALYSE_PSUB16x16 ) && (!analysis.b_early_terminate ||
analysis.l0.i_cost8x8 < analysis.l0.me16x16.cost) )
{
i_type = P_8x8;
i_partition = D_8x8;
i_cost = analysis.l0.i_cost8x8;
/* Do sub 8x8 */
if( flags & X264_ANALYSE_PSUB8x8 )
{
for( int i = 0; i < 4; i++ )
{
//8x8块的子块的分析
/*
* 4x4
* +----+----+
* | | |
* +----+----+
* | | |
* +----+----+
*
*/
mb_analyse_inter_p4x4( h, &analysis, i );
int i_thresh8x4 = analysis.l0.me4x4[i][1].cost_mv + analysis.l0.me4x4[i][2].cost_mv;
// 如果4x4小于8x8,则进行8x4以及4x8尺寸的检查
if( !analysis.b_early_terminate || analysis.l0.i_cost4x4[i] < analysis.l0.me8x8[i].cost + i_thresh8x4 )
{
int i_cost8x8 = analysis.l0.i_cost4x4[i];
h->mb.i_sub_partition[i] = D_L0_4x4;
mb_analyse_inter_p8x4( h, &analysis, i );
COPY2_IF_LT( i_cost8x8, analysis.l0.i_cost8x4[i],
h->mb.i_sub_partition[i], D_L0_8x4 );
mb_analyse_inter_p4x8( h, &analysis, i );
COPY2_IF_LT( i_cost8x8, analysis.l0.i_cost4x8[i],
h->mb.i_sub_partition[i], D_L0_4x8 );
i_cost += i_cost8x8 - analysis.l0.me8x8[i].cost;
}
mb_cache_mv_p8x8( h, &analysis, i );
}
analysis.l0.i_cost8x8 = i_cost;
}
}
/* Now do 16x8/8x16 */
int i_thresh16x8 = analysis.l0.me8x8[1].cost_mv + analysis.l0.me8x8[2].cost_mv;
// 如果8x8的代价值小于16x16+16x8,则进行16x8和8x16尺寸的检查
if( ( flags & X264_ANALYSE_PSUB16x16 ) && (!analysis.b_early_terminate ||
analysis.l0.i_cost8x8 < analysis.l0.me16x16.cost + i_thresh16x8) )
{
int i_avg_mv_ref_cost = (analysis.l0.me8x8[2].cost_mv + analysis.l0.me8x8[2].i_ref_cost
+ analysis.l0.me8x8[3].cost_mv + analysis.l0.me8x8[3].i_ref_cost + 1) >> 1;
analysis.i_cost_est16x8[1] = analysis.i_satd8x8[0][2] + analysis.i_satd8x8[0][3] + i_avg_mv_ref_cost;
// 16x8宏块划分
mb_analyse_inter_p16x8( h, &analysis, i_cost );
COPY3_IF_LT( i_cost, analysis.l0.i_cost16x8, i_type, P_L0, i_partition, D_16x8 );
i_avg_mv_ref_cost = (analysis.l0.me8x8[1].cost_mv + analysis.l0.me8x8[1].i_ref_cost
+ analysis.l0.me8x8[3].cost_mv + analysis.l0.me8x8[3].i_ref_cost + 1) >> 1;
analysis.i_cost_est8x16[1] = analysis.i_satd8x8[0][1] + analysis.i_satd8x8[0][3] + i_avg_mv_ref_cost;
// 8x16宏块划分
mb_analyse_inter_p8x16( h, &analysis, i_cost );
COPY3_IF_LT( i_cost, analysis.l0.i_cost8x16, i_type, P_L0, i_partition, D_8x16 );
}
h->mb.i_partition = i_partition;
// 亚像素精度估计
/* refine qpel */
//FIXME mb_type costs?
if( analysis.i_mbrd || !h->mb.i_subpel_refine )
{
/* refine later */
}
else if( i_partition == D_16x16 )
{
x264_me_refine_qpel( h, &analysis.l0.me16x16 );
i_cost = analysis.l0.me16x16.cost;
}
else if( i_partition == D_16x8 )
{
x264_me_refine_qpel( h, &analysis.l0.me16x8[0] );
x264_me_refine_qpel( h, &analysis.l0.me16x8[1] );
i_cost = analysis.l0.me16x8[0].cost + analysis.l0.me16x8[1].cost;
}
else if( i_partition == D_8x16 )
{
x264_me_refine_qpel( h, &analysis.l0.me8x16[0] );
x264_me_refine_qpel( h, &analysis.l0.me8x16[1] );
i_cost = analysis.l0.me8x16[0].cost + analysis.l0.me8x16[1].cost;
}
else if( i_partition == D_8x8 )
{
i_cost = 0;
for( int i8x8 = 0; i8x8 < 4; i8x8++ )
{
switch( h->mb.i_sub_partition[i8x8] )
{
case D_L0_8x8:
x264_me_refine_qpel( h, &analysis.l0.me8x8[i8x8] );
i_cost += analysis.l0.me8x8[i8x8].cost;
break;
case D_L0_8x4:
x264_me_refine_qpel( h, &analysis.l0.me8x4[i8x8][0] );
x264_me_refine_qpel( h, &analysis.l0.me8x4[i8x8][1] );
i_cost += analysis.l0.me8x4[i8x8][0].cost +
analysis.l0.me8x4[i8x8][1].cost;
break;
case D_L0_4x8:
x264_me_refine_qpel( h, &analysis.l0.me4x8[i8x8][0] );
x264_me_refine_qpel( h, &analysis.l0.me4x8[i8x8][1] );
i_cost += analysis.l0.me4x8[i8x8][0].cost +
analysis.l0.me4x8[i8x8][1].cost;
break;
case D_L0_4x4:
x264_me_refine_qpel( h, &analysis.l0.me4x4[i8x8][0] );
x264_me_refine_qpel( h, &analysis.l0.me4x4[i8x8][1] );
x264_me_refine_qpel( h, &analysis.l0.me4x4[i8x8][2] );
x264_me_refine_qpel( h, &analysis.l0.me4x4[i8x8][3] );
i_cost += analysis.l0.me4x4[i8x8][0].cost +
analysis.l0.me4x4[i8x8][1].cost +
analysis.l0.me4x4[i8x8][2].cost +
analysis.l0.me4x4[i8x8][3].cost;
break;
default:
x264_log( h, X264_LOG_ERROR, "internal error (!8x8 && !4x4)\n" );
break;
}
}
}
// 是否进行色度分量的运动估计
if( h->mb.b_chroma_me )
{
if( CHROMA444 )
{
mb_analyse_intra( h, &analysis, i_cost );
mb_analyse_intra_chroma( h, &analysis );
}
else
{
mb_analyse_intra_chroma( h, &analysis );
mb_analyse_intra( h, &analysis, i_cost - analysis.i_satd_chroma );
}
analysis.i_satd_i16x16 += analysis.i_satd_chroma;
analysis.i_satd_i8x8 += analysis.i_satd_chroma;
analysis.i_satd_i4x4 += analysis.i_satd_chroma;
}
else
mb_analyse_intra( h, &analysis, i_cost ); // P Slice中也允许有Intra宏块,也要进行分析
i_satd_inter = i_cost;
i_satd_intra = X264_MIN3( analysis.i_satd_i16x16,
analysis.i_satd_i8x8,
analysis.i_satd_i4x4 );
if( analysis.i_mbrd )
{
// 速率失真最优QP选择
mb_analyse_p_rd( h, &analysis, X264_MIN(i_satd_inter, i_satd_intra) );
i_type = P_L0;
i_partition = D_16x16;
i_cost = analysis.l0.i_rd16x16;
COPY2_IF_LT( i_cost, analysis.l0.i_cost16x8, i_partition, D_16x8 );
COPY2_IF_LT( i_cost, analysis.l0.i_cost8x16, i_partition, D_8x16 );
COPY3_IF_LT( i_cost, analysis.l0.i_cost8x8, i_partition, D_8x8, i_type, P_8x8 );
h->mb.i_type = i_type;
h->mb.i_partition = i_partition;
if( i_cost < COST_MAX )
mb_analyse_transform_rd( h, &analysis, &i_satd_inter, &i_cost );
intra_rd( h, &analysis, i_satd_inter * 5/4 + 1 );
}
// 获取最小的损失
COPY2_IF_LT( i_cost, analysis.i_satd_i16x16, i_type, I_16x16 );
COPY2_IF_LT( i_cost, analysis.i_satd_i8x8, i_type, I_8x8 );
COPY2_IF_LT( i_cost, analysis.i_satd_i4x4, i_type, I_4x4 );
COPY2_IF_LT( i_cost, analysis.i_satd_pcm, i_type, I_PCM );
h->mb.i_type = i_type;
if( analysis.b_force_intra && !IS_INTRA(i_type) )
{
/* Intra masking: copy fdec to fenc and re-encode the block as intra in order to make it appear as if
* it was an inter block. */
analyse_update_cache( h, &analysis );
x264_macroblock_encode( h );
for( int p = 0; p < (CHROMA444 ? 3 : 1); p++ )
h->mc.copy[PIXEL_16x16]( h->mb.pic.p_fenc[p], FENC_STRIDE, h->mb.pic.p_fdec[p], FDEC_STRIDE, 16 );
if( !CHROMA444 )
{
int height = 16 >> CHROMA_V_SHIFT;
h->mc.copy[PIXEL_8x8] ( h->mb.pic.p_fenc[1], FENC_STRIDE, h->mb.pic.p_fdec[1], FDEC_STRIDE, height );
h->mc.copy[PIXEL_8x8] ( h->mb.pic.p_fenc[2], FENC_STRIDE, h->mb.pic.p_fdec[2], FDEC_STRIDE, height );
}
mb_analyse_init_qp( h, &analysis, X264_MAX( h->mb.i_qp - h->mb.ip_offset, h->param.rc.i_qp_min ) );
goto intra_analysis;
}
if( analysis.i_mbrd >= 2 && h->mb.i_type != I_PCM )
{
if( IS_INTRA( h->mb.i_type ) )
{
intra_rd_refine( h, &analysis );
}
else if( i_partition == D_16x16 )
{
x264_macroblock_cache_ref( h, 0, 0, 4, 4, 0, analysis.l0.me16x16.i_ref );
analysis.l0.me16x16.cost = i_cost;
x264_me_refine_qpel_rd( h, &analysis.l0.me16x16, analysis.i_lambda2, 0, 0 );
}
else if( i_partition == D_16x8 )
{
M32( h->mb.i_sub_partition ) = D_L0_8x8 * 0x01010101;
x264_macroblock_cache_ref( h, 0, 0, 4, 2, 0, analysis.l0.me16x8[0].i_ref );
x264_macroblock_cache_ref( h, 0, 2, 4, 2, 0, analysis.l0.me16x8[1].i_ref );
x264_me_refine_qpel_rd( h, &analysis.l0.me16x8[0], analysis.i_lambda2, 0, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me16x8[1], analysis.i_lambda2, 8, 0 );
}
else if( i_partition == D_8x16 )
{
M32( h->mb.i_sub_partition ) = D_L0_8x8 * 0x01010101;
x264_macroblock_cache_ref( h, 0, 0, 2, 4, 0, analysis.l0.me8x16[0].i_ref );
x264_macroblock_cache_ref( h, 2, 0, 2, 4, 0, analysis.l0.me8x16[1].i_ref );
x264_me_refine_qpel_rd( h, &analysis.l0.me8x16[0], analysis.i_lambda2, 0, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me8x16[1], analysis.i_lambda2, 4, 0 );
}
else if( i_partition == D_8x8 )
{
analyse_update_cache( h, &analysis );
for( int i8x8 = 0; i8x8 < 4; i8x8++ )
{
if( h->mb.i_sub_partition[i8x8] == D_L0_8x8 )
{
x264_me_refine_qpel_rd( h, &analysis.l0.me8x8[i8x8], analysis.i_lambda2, i8x8*4, 0 );
}
else if( h->mb.i_sub_partition[i8x8] == D_L0_8x4 )
{
x264_me_refine_qpel_rd( h, &analysis.l0.me8x4[i8x8][0], analysis.i_lambda2, i8x8*4+0, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me8x4[i8x8][1], analysis.i_lambda2, i8x8*4+2, 0 );
}
else if( h->mb.i_sub_partition[i8x8] == D_L0_4x8 )
{
x264_me_refine_qpel_rd( h, &analysis.l0.me4x8[i8x8][0], analysis.i_lambda2, i8x8*4+0, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me4x8[i8x8][1], analysis.i_lambda2, i8x8*4+1, 0 );
}
else if( h->mb.i_sub_partition[i8x8] == D_L0_4x4 )
{
x264_me_refine_qpel_rd( h, &analysis.l0.me4x4[i8x8][0], analysis.i_lambda2, i8x8*4+0, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me4x4[i8x8][1], analysis.i_lambda2, i8x8*4+1, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me4x4[i8x8][2], analysis.i_lambda2, i8x8*4+2, 0 );
x264_me_refine_qpel_rd( h, &analysis.l0.me4x4[i8x8][3], analysis.i_lambda2, i8x8*4+3, 0 );
}
}
}
}
}
}
else if( h->sh.i_type == SLICE_TYPE_B ) // B帧的预测
{ // ----- 5.B帧的帧间预测 ----- //
int i_bskip_cost = COST_MAX;
int b_skip = 0;
if( analysis.i_mbrd )
mb_init_fenc_cache( h, analysis.i_mbrd >= 2 );
h->mb.i_type = B_SKIP;
if( h->mb.b_direct_auto_write )
{
/* direct=auto heuristic: prefer whichever mode allows more Skip macroblocks */
for( int i = 0; i < 2; i++ )
{
int b_changed = 1;
h->sh.b_direct_spatial_mv_pred ^= 1;
analysis.b_direct_available = x264_mb_predict_mv_direct16x16( h, i && analysis.b_direct_available ? &b_changed : NULL );
if( analysis.b_direct_available )
{
if( b_changed )
{
x264_mb_mc( h );
b_skip = x264_macroblock_probe_bskip( h );
}
h->stat.frame.i_direct_score[ h->sh.b_direct_spatial_mv_pred ] += b_skip;
}
else
b_skip = 0;
}
}
else
analysis.b_direct_available = x264_mb_predict_mv_direct16x16( h, NULL );
analysis.b_try_skip = 0;
if( analysis.b_direct_available )
{
if( !h->mb.b_direct_auto_write )
x264_mb_mc( h );
/* If the current macroblock is off the frame, just skip it. */
if( HAVE_INTERLACED && !MB_INTERLACED && h->mb.i_mb_y * 16 >= h->param.i_height )
b_skip = 1;
else if( analysis.i_mbrd )
{
i_bskip_cost = ssd_mb( h );
/* 6 = minimum cavlc cost of a non-skipped MB */
b_skip = h->mb.b_skip_mc = i_bskip_cost <= ((6 * analysis.i_lambda2 + 128) >> 8);
}
else if( !h->mb.b_direct_auto_write )
{
/* Conditioning the probe on neighboring block types
* doesn't seem to help speed or quality. */
analysis.b_try_skip = x264_macroblock_probe_bskip( h );
if( h->param.analyse.i_subpel_refine < 3 )
b_skip = analysis.b_try_skip;
}
/* Set up MVs for future predictors */
if( b_skip )
{
for( int i = 0; i < h->mb.pic.i_fref[0]; i++ )
M32( h->mb.mvr[0][i][h->mb.i_mb_xy] ) = 0;
for( int i = 0; i < h->mb.pic.i_fref[1]; i++ )
M32( h->mb.mvr[1][i][h->mb.i_mb_xy] ) = 0;
}
}
if( !b_skip )
{
const unsigned int flags = h->param.analyse.inter;
int i_type;
int i_partition;
int i_satd_inter;
h->mb.b_skip_mc = 0;
h->mb.i_type = B_DIRECT;
mb_analyse_load_costs( h, &analysis );
/* select best inter mode */
/* direct must be first */
if( analysis.b_direct_available )
mb_analyse_inter_direct( h, &analysis );
// 进行16x16的预测
mb_analyse_inter_b16x16( h, &analysis );
if( h->mb.i_type == B_SKIP )
{
for( int i = 1; i < h->mb.pic.i_fref[0]; i++ )
M32( h->mb.mvr[0][i][h->mb.i_mb_xy] ) = 0;
for( int i = 1; i < h->mb.pic.i_fref[1]; i++ )
M32( h->mb.mvr[1][i][h->mb.i_mb_xy] ) = 0;
return;
}
i_type = B_L0_L0;
i_partition = D_16x16;
i_cost = analysis.l0.me16x16.cost;
COPY2_IF_LT( i_cost, analysis.l1.me16x16.cost, i_type, B_L1_L1 );
COPY2_IF_LT( i_cost, analysis.i_cost16x16bi, i_type, B_BI_BI );
COPY2_IF_LT( i_cost, analysis.i_cost16x16direct, i_type, B_DIRECT );
if( analysis.i_mbrd && analysis.b_early_terminate && analysis.i_cost16x16direct <= i_cost * 33/32 )
{
mb_analyse_b_rd( h, &analysis, i_cost );
if( i_bskip_cost < analysis.i_rd16x16direct &&
i_bskip_cost < analysis.i_rd16x16bi &&
i_bskip_cost < analysis.l0.i_rd16x16 &&
i_bskip_cost < analysis.l1.i_rd16x16 )
{
h->mb.i_type = B_SKIP;
analyse_update_cache( h, &analysis );
return;
}
}
if( flags & X264_ANALYSE_BSUB16x16 )
{
// 进行8x8的预测
if( h->param.analyse.b_mixed_references )
mb_analyse_inter_b8x8_mixed_ref( h, &analysis );
else
mb_analyse_inter_b8x8( h, &analysis );
COPY3_IF_LT( i_cost, analysis.i_cost8x8bi, i_type, B_8x8, i_partition, D_8x8 );
/* Try to estimate the cost of b16x8/b8x16 based on the satd scores of the b8x8 modes */
int i_cost_est16x8bi_total = 0, i_cost_est8x16bi_total = 0;
int i_mb_type, i_partition16x8[2], i_partition8x16[2];
for( int i = 0; i < 2; i++ )
{
int avg_l0_mv_ref_cost, avg_l1_mv_ref_cost;
int i_l0_satd, i_l1_satd, i_bi_satd, i_best_cost;
// 16x8
i_best_cost = COST_MAX;
i_l0_satd = analysis.i_satd8x8[0][i*2] + analysis.i_satd8x8[0][i*2+1];
i_l1_satd = analysis.i_satd8x8[1][i*2] + analysis.i_satd8x8[1][i*2+1];
i_bi_satd = analysis.i_satd8x8[2][i*2] + analysis.i_satd8x8[2][i*2+1];
avg_l0_mv_ref_cost = ( analysis.l0.me8x8[i*2].cost_mv + analysis.l0.me8x8[i*2].i_ref_cost
+ analysis.l0.me8x8[i*2+1].cost_mv + analysis.l0.me8x8[i*2+1].i_ref_cost + 1 ) >> 1;
avg_l1_mv_ref_cost = ( analysis.l1.me8x8[i*2].cost_mv + analysis.l1.me8x8[i*2].i_ref_cost
+ analysis.l1.me8x8[i*2+1].cost_mv + analysis.l1.me8x8[i*2+1].i_ref_cost + 1 ) >> 1;
COPY2_IF_LT( i_best_cost, i_l0_satd + avg_l0_mv_ref_cost, i_partition16x8[i], D_L0_8x8 );
COPY2_IF_LT( i_best_cost, i_l1_satd + avg_l1_mv_ref_cost, i_partition16x8[i], D_L1_8x8 );
COPY2_IF_LT( i_best_cost, i_bi_satd + avg_l0_mv_ref_cost + avg_l1_mv_ref_cost, i_partition16x8[i], D_BI_8x8 );
analysis.i_cost_est16x8[i] = i_best_cost;
// 8x16
i_best_cost = COST_MAX;
i_l0_satd = analysis.i_satd8x8[0][i] + analysis.i_satd8x8[0][i+2];
i_l1_satd = analysis.i_satd8x8[1][i] + analysis.i_satd8x8[1][i+2];
i_bi_satd = analysis.i_satd8x8[2][i] + analysis.i_satd8x8[2][i+2];
avg_l0_mv_ref_cost = ( analysis.l0.me8x8[i].cost_mv + analysis.l0.me8x8[i].i_ref_cost
+ analysis.l0.me8x8[i+2].cost_mv + analysis.l0.me8x8[i+2].i_ref_cost + 1 ) >> 1;
avg_l1_mv_ref_cost = ( analysis.l1.me8x8[i].cost_mv + analysis.l1.me8x8[i].i_ref_cost
+ analysis.l1.me8x8[i+2].cost_mv + analysis.l1.me8x8[i+2].i_ref_cost + 1 ) >> 1;
COPY2_IF_LT( i_best_cost, i_l0_satd + avg_l0_mv_ref_cost, i_partition8x16[i], D_L0_8x8 );
COPY2_IF_LT( i_best_cost, i_l1_satd + avg_l1_mv_ref_cost, i_partition8x16[i], D_L1_8x8 );
COPY2_IF_LT( i_best_cost, i_bi_satd + avg_l0_mv_ref_cost + avg_l1_mv_ref_cost, i_partition8x16[i], D_BI_8x8 );
analysis.i_cost_est8x16[i] = i_best_cost;
}
i_mb_type = B_L0_L0 + (i_partition16x8[0]>>2) * 3 + (i_partition16x8[1]>>2);
analysis.i_cost_est16x8[1] += analysis.i_lambda * i_mb_b16x8_cost_table[i_mb_type];
i_cost_est16x8bi_total = analysis.i_cost_est16x8[0] + analysis.i_cost_est16x8[1];
i_mb_type = B_L0_L0 + (i_partition8x16[0]>>2) * 3 + (i_partition8x16[1]>>2);
analysis.i_cost_est8x16[1] += analysis.i_lambda * i_mb_b16x8_cost_table[i_mb_type];
i_cost_est8x16bi_total = analysis.i_cost_est8x16[0] + analysis.i_cost_est8x16[1];
/* We can gain a little speed by checking the mode with the lowest estimated cost first */
int try_16x8_first = i_cost_est16x8bi_total < i_cost_est8x16bi_total;
if( try_16x8_first && (!analysis.b_early_terminate || i_cost_est16x8bi_total < i_cost) )
{
mb_analyse_inter_b16x8( h, &analysis, i_cost );
COPY3_IF_LT( i_cost, analysis.i_cost16x8bi, i_type, analysis.i_mb_type16x8, i_partition, D_16x8 );
}
if( !analysis.b_early_terminate || i_cost_est8x16bi_total < i_cost )
{
mb_analyse_inter_b8x16( h, &analysis, i_cost );
COPY3_IF_LT( i_cost, analysis.i_cost8x16bi, i_type, analysis.i_mb_type8x16, i_partition, D_8x16 );
}
if( !try_16x8_first && (!analysis.b_early_terminate || i_cost_est16x8bi_total < i_cost) )
{
mb_analyse_inter_b16x8( h, &analysis, i_cost );
COPY3_IF_LT( i_cost, analysis.i_cost16x8bi, i_type, analysis.i_mb_type16x8, i_partition, D_16x8 );
}
}
if( analysis.i_mbrd || !h->mb.i_subpel_refine )
{
/* refine later */
}
/* refine qpel */
else if( i_partition == D_16x16 )
{
analysis.l0.me16x16.cost -= analysis.i_lambda * i_mb_b_cost_table[B_L0_L0];
analysis.l1.me16x16.cost -= analysis.i_lambda * i_mb_b_cost_table[B_L1_L1];
if( i_type == B_L0_L0 )
{
x264_me_refine_qpel( h, &analysis.l0.me16x16 );
i_cost = analysis.l0.me16x16.cost
+ analysis.i_lambda * i_mb_b_cost_table[B_L0_L0];
}
else if( i_type == B_L1_L1 )
{
x264_me_refine_qpel( h, &analysis.l1.me16x16 );
i_cost = analysis.l1.me16x16.cost
+ analysis.i_lambda * i_mb_b_cost_table[B_L1_L1];
}
else if( i_type == B_BI_BI )
{
x264_me_refine_qpel( h, &analysis.l0.bi16x16 );
x264_me_refine_qpel( h, &analysis.l1.bi16x16 );
}
}
else if( i_partition == D_16x8 )
{
for( int i = 0; i < 2; i++ )
{
if( analysis.i_mb_partition16x8[i] != D_L1_8x8 )
x264_me_refine_qpel( h, &analysis.l0.me16x8[i] );
if( analysis.i_mb_partition16x8[i] != D_L0_8x8 )
x264_me_refine_qpel( h, &analysis.l1.me16x8[i] );
}
}
else if( i_partition == D_8x16 )
{
for( int i = 0; i < 2; i++ )
{
if( analysis.i_mb_partition8x16[i] != D_L1_8x8 )
x264_me_refine_qpel( h, &analysis.l0.me8x16[i] );
if( analysis.i_mb_partition8x16[i] != D_L0_8x8 )
x264_me_refine_qpel( h, &analysis.l1.me8x16[i] );
}
}
else if( i_partition == D_8x8 )
{
for( int i = 0; i < 4; i++ )
{
x264_me_t *m;
int i_part_cost_old;
int i_type_cost;
int i_part_type = h->mb.i_sub_partition[i];
int b_bidir = (i_part_type == D_BI_8x8);
if( i_part_type == D_DIRECT_8x8 )
continue;
if( x264_mb_partition_listX_table[0][i_part_type] )
{
m = &analysis.l0.me8x8[i];
i_part_cost_old = m->cost;
i_type_cost = analysis.i_lambda * i_sub_mb_b_cost_table[D_L0_8x8];
m->cost -= i_type_cost;
x264_me_refine_qpel( h, m );
if( !b_bidir )
analysis.i_cost8x8bi += m->cost + i_type_cost - i_part_cost_old;
}
if( x264_mb_partition_listX_table[1][i_part_type] )
{
m = &analysis.l1.me8x8[i];
i_part_cost_old = m->cost;
i_type_cost = analysis.i_lambda * i_sub_mb_b_cost_table[D_L1_8x8];
m->cost -= i_type_cost;
x264_me_refine_qpel( h, m );
if( !b_bidir )
analysis.i_cost8x8bi += m->cost + i_type_cost - i_part_cost_old;
}
/* TODO: update mvp? */
}
}
i_satd_inter = i_cost;
if( analysis.i_mbrd )
{
mb_analyse_b_rd( h, &analysis, i_satd_inter );
i_type = B_SKIP;
i_cost = i_bskip_cost;
i_partition = D_16x16;
COPY2_IF_LT( i_cost, analysis.l0.i_rd16x16, i_type, B_L0_L0 );
COPY2_IF_LT( i_cost, analysis.l1.i_rd16x16, i_type, B_L1_L1 );
COPY2_IF_LT( i_cost, analysis.i_rd16x16bi, i_type, B_BI_BI );
COPY2_IF_LT( i_cost, analysis.i_rd16x16direct, i_type, B_DIRECT );
COPY3_IF_LT( i_cost, analysis.i_rd16x8bi, i_type, analysis.i_mb_type16x8, i_partition, D_16x8 );
COPY3_IF_LT( i_cost, analysis.i_rd8x16bi, i_type, analysis.i_mb_type8x16, i_partition, D_8x16 );
COPY3_IF_LT( i_cost, analysis.i_rd8x8bi, i_type, B_8x8, i_partition, D_8x8 );
h->mb.i_type = i_type;
h->mb.i_partition = i_partition;
}
if( h->mb.b_chroma_me )
{
if( CHROMA444 )
{
mb_analyse_intra( h, &analysis, i_satd_inter );
mb_analyse_intra_chroma( h, &analysis );
}
else
{
mb_analyse_intra_chroma( h, &analysis );
mb_analyse_intra( h, &analysis, i_satd_inter - analysis.i_satd_chroma );
}
analysis.i_satd_i16x16 += analysis.i_satd_chroma;
analysis.i_satd_i8x8 += analysis.i_satd_chroma;
analysis.i_satd_i4x4 += analysis.i_satd_chroma;
}
else
mb_analyse_intra( h, &analysis, i_satd_inter );
if( analysis.i_mbrd )
{
mb_analyse_transform_rd( h, &analysis, &i_satd_inter, &i_cost );
intra_rd( h, &analysis, i_satd_inter * 17/16 + 1 );
}
COPY2_IF_LT( i_cost, analysis.i_satd_i16x16, i_type, I_16x16 );
COPY2_IF_LT( i_cost, analysis.i_satd_i8x8, i_type, I_8x8 );
COPY2_IF_LT( i_cost, analysis.i_satd_i4x4, i_type, I_4x4 );
COPY2_IF_LT( i_cost, analysis.i_satd_pcm, i_type, I_PCM );
h->mb.i_type = i_type;
h->mb.i_partition = i_partition;
if( analysis.i_mbrd >= 2 && IS_INTRA( i_type ) && i_type != I_PCM )
intra_rd_refine( h, &analysis );
if( h->mb.i_subpel_refine >= 5 )
refine_bidir( h, &analysis );
if( analysis.i_mbrd >= 2 && i_type > B_DIRECT && i_type < B_SKIP )
{
int i_biweight;
analyse_update_cache( h, &analysis );
if( i_partition == D_16x16 )
{
if( i_type == B_L0_L0 )
{
analysis.l0.me16x16.cost = i_cost;
x264_me_refine_qpel_rd( h, &analysis.l0.me16x16, analysis.i_lambda2, 0, 0 );
}
else if( i_type == B_L1_L1 )
{
analysis.l1.me16x16.cost = i_cost;
x264_me_refine_qpel_rd( h, &analysis.l1.me16x16, analysis.i_lambda2, 0, 1 );
}
else if( i_type == B_BI_BI )
{
i_biweight = h->mb.bipred_weight[analysis.l0.bi16x16.i_ref][analysis.l1.bi16x16.i_ref];
x264_me_refine_bidir_rd( h, &analysis.l0.bi16x16, &analysis.l1.bi16x16, i_biweight, 0, analysis.i_lambda2 );
}
}
else if( i_partition == D_16x8 )
{
for( int i = 0; i < 2; i++ )
{
h->mb.i_sub_partition[i*2] = h->mb.i_sub_partition[i*2+1] = analysis.i_mb_partition16x8[i];
if( analysis.i_mb_partition16x8[i] == D_L0_8x8 )
x264_me_refine_qpel_rd( h, &analysis.l0.me16x8[i], analysis.i_lambda2, i*8, 0 );
else if( analysis.i_mb_partition16x8[i] == D_L1_8x8 )
x264_me_refine_qpel_rd( h, &analysis.l1.me16x8[i], analysis.i_lambda2, i*8, 1 );
else if( analysis.i_mb_partition16x8[i] == D_BI_8x8 )
{
i_biweight = h->mb.bipred_weight[analysis.l0.me16x8[i].i_ref][analysis.l1.me16x8[i].i_ref];
x264_me_refine_bidir_rd( h, &analysis.l0.me16x8[i], &analysis.l1.me16x8[i], i_biweight, i*2, analysis.i_lambda2 );
}
}
}
else if( i_partition == D_8x16 )
{
for( int i = 0; i < 2; i++ )
{
h->mb.i_sub_partition[i] = h->mb.i_sub_partition[i+2] = analysis.i_mb_partition8x16[i];
if( analysis.i_mb_partition8x16[i] == D_L0_8x8 )
x264_me_refine_qpel_rd( h, &analysis.l0.me8x16[i], analysis.i_lambda2, i*4, 0 );
else if( analysis.i_mb_partition8x16[i] == D_L1_8x8 )
x264_me_refine_qpel_rd( h, &analysis.l1.me8x16[i], analysis.i_lambda2, i*4, 1 );
else if( analysis.i_mb_partition8x16[i] == D_BI_8x8 )
{
i_biweight = h->mb.bipred_weight[analysis.l0.me8x16[i].i_ref][analysis.l1.me8x16[i].i_ref];
x264_me_refine_bidir_rd( h, &analysis.l0.me8x16[i], &analysis.l1.me8x16[i], i_biweight, i, analysis.i_lambda2 );
}
}
}
else if( i_partition == D_8x8 )
{
for( int i = 0; i < 4; i++ )
{
if( h->mb.i_sub_partition[i] == D_L0_8x8 )
x264_me_refine_qpel_rd( h, &analysis.l0.me8x8[i], analysis.i_lambda2, i*4, 0 );
else if( h->mb.i_sub_partition[i] == D_L1_8x8 )
x264_me_refine_qpel_rd( h, &analysis.l1.me8x8[i], analysis.i_lambda2, i*4, 1 );
else if( h->mb.i_sub_partition[i] == D_BI_8x8 )
{
i_biweight = h->mb.bipred_weight[analysis.l0.me8x8[i].i_ref][analysis.l1.me8x8[i].i_ref];
x264_me_refine_bidir_rd( h, &analysis.l0.me8x8[i], &analysis.l1.me8x8[i], i_biweight, i, analysis.i_lambda2 );
}
}
}
}
}
}
// ----- 6.从分析中更新MB ----- //
analyse_update_cache( h, &analysis );
/* In rare cases we can end up qpel-RDing our way back to a larger partition size
* without realizing it. Check for this and account for it if necessary. */
if( analysis.i_mbrd >= 2 )
{
/* Don't bother with bipred or 8x8-and-below, the odds are incredibly low. */
static const uint8_t check_mv_lists[X264_MBTYPE_MAX] = {[P_L0]=1, [B_L0_L0]=1, [B_L1_L1]=2};
int list = check_mv_lists[h->mb.i_type] - 1;
if( list >= 0 && h->mb.i_partition != D_16x16 &&
M32( &h->mb.cache.mv[list][x264_scan8[0]] ) == M32( &h->mb.cache.mv[list][x264_scan8[12]] ) &&
h->mb.cache.ref[list][x264_scan8[0]] == h->mb.cache.ref[list][x264_scan8[12]] )
h->mb.i_partition = D_16x16;
}
if( !analysis.i_mbrd )
mb_analyse_transform( h );
if( analysis.i_mbrd == 3 && !IS_SKIP(h->mb.i_type) )
mb_analyse_qp_rd( h, &analysis );
h->mb.b_trellis = h->param.analyse.i_trellis;
h->mb.b_noise_reduction = h->mb.b_noise_reduction || (!!h->param.analyse.i_noise_reduction && !IS_INTRA( h->mb.i_type ));
if( !IS_SKIP(h->mb.i_type) && h->mb.i_psy_trellis && h->param.analyse.i_trellis == 1 )
psy_trellis_init( h, 0 );
if( h->mb.b_trellis == 1 || h->mb.b_noise_reduction )
h->mb.i_skip_intra = 0;
}
3. 帧间预测
帧间预测的主要思想是利用前面某一帧当中的像素块作为参考,指导当前帧中的像素块进行编码。首先,需要指定一个参考帧,假设是实时音视频场景,为了保证实时性,通常设定的帧结构类型为Intra P P … Intra P P,即间隔若干个P帧之后才会出现Intra帧,这若干个P帧会将前一个Intra帧作为参考帧;其次,要在参考帧当中查找一个和当前像素块比较相似的参考块,这个参考块在参考帧中的位置不一定与当前块在当前帧中的位置相同,这个查找的过程叫做运动估计,该过程会获得一个运动向量,这个运动向量的方向从当前块指向参考块;随后,根据查找到的运动向量来对当前块进行预测,能得到预测块,根据这个预测块就可以获得残差块,这个过程叫做运动补偿;最后,将残差信息进行编码,编码码流大小根据运动估计的效果而决定
在帧间预测过程中发现,有时两个块的运动变化不是很大,为了更加精细的描述运动程度,采用了像素插值的方法,在两个相邻像素之间通过插值的方法获取一些像素点,这样再来进行运动估计,描述的会更加精确,其最根本的目的是为了降低预测的残差。插值后有亚像素(1/2精度)、1/4像素精度和1/8像素精度,其中运动向量对亮度分量采用1/4像素精度,色度分量采用1/8像素精度
帧内预测主要关注的内容包括:
- 可变尺寸块
- 运动估计(又称运动搜索)
(1)菱形搜索(Diamond Search, DIA)
(2)六边形搜索(Hexagon Seach, HEX)
(3)非对称十字型多层次六边形格点搜索(UMH)
(4)连续消除法(Successive Elimination Algorithm, SEA) - 像素精度
- 帧内预测主要函数
(1)运动估计主函数(x264_me_search_ref)
(2)子像素运动搜索(refine_subpel)
3.1 可变尺寸块
如果当前的块使用帧间预测,那么每个宏块(16x16)可以分成成为不同的子块,大小包括16x16、16x8、8x16以及8x8,而8x8的子块还可以进一步的分割成为4x4的子块。
每个子块都有其自己的参考帧和运动向量,这些信息都会被写入到码流当中传输。
3.2 运动估计(又称运动搜索)
运动估计大体上可以分为两种类型:
- 全搜索:将搜索区域内所有像素块都进行遍历,查找一个和当前块最为匹配的像素块,这种方式速度很慢,一般不使用
- 快速搜索方法:通过一定的规则和策略进行搜索,速度较快,但匹配的不一定是最佳的像素块。快速搜索方法包括菱形搜索(DIA)、六边形搜索(HEX)、非对称十字型多层次六边形格点搜索(UMH)、连续消除法(ESA、TESA)
(1)菱形搜索(Diamond Search, DIA)
菱形搜索比较简单(使用的档位是superfast和ultrafast),采用两个模板,分别是大菱形搜索模板和小菱形模板,大菱形搜索模板有9个搜索点,小菱形搜索模板有5个搜索点。在x264当中,默认会使用半径为1的搜索模板。这里的1是整像素的,而不是亚像素或者1/4像素的。假设当前搜索的块为4x4,在进行搜索时,以当前块的左上角第一个像素为起点,按照上、下、左、右几个方向进行搜索,再加上中心点(中心点的位置与当前块的左上角第一个像素位置相同,区别在于这个中心点位于参考帧中),一共是5个搜索点,即5个搜索块,随后会将这5个4x4的块与当前的4x4的块计算sad或者satd。如果损失最小的点位于中心点,则结束搜索,否则以损失最小的点为起点,再进行一次搜索,直到损失最小的点位于中心点。小菱形模板如下所示
(2)六边形搜索(Hexagon Seach, HEX)
六边形搜索是x264默认使用的搜索方法(在medium、fast、faster和veryfast下都会使用),会使用六边形模板和正方形模板进行搜索,模板如下所示
进行六边形搜索时,分为两个步骤:(a)首先计算图中7个点所对应的损失,如果损失最小的点不在中心点,需再进行一次六边形搜索;(b)否则以中心点为起始,进行正方形的搜索
(3)非对称十字型多层次六边形格点搜索(Uneven-cross Multi-Hexagon-grid Search)
比较复杂,使用的档位是slower和veryslow,这里不讨论
(4)连续消除法(Successive Elimination Algorithm, SEA)
最为复杂,使用的档位是placebo,这里不讨论
在进行了上述整像素搜索之后,还会进行亚像素、1/4像素的微调(refine_subpel),不过从代码上看这里的微调,只会调用菱形搜索。
3.3 像素精度
通常来讲,对一张图像进行操作时,操作的内容是像素,但是在进行运动估计时,为了获得更好地效果,还会使用1/2像素和1/4像素进行。这里指的1/2和1/4是在像素之间进插值操作,如果是1/2像素,则是在两个像素之间插入1像素,如果是1/4像素,则是在两个像素之间插入3个像素,如下图所示
(1)1/2像素精度及插值
(2)1/4像素精度及插值
(3)1/8像素精度及插值
不同的像素精度对编码的质量有所影响,相同码率下,精度越高,质量(PSNR)就越好,但1/4像素精度到1/8像素精度带来的提升不明显,所以实际编码器中常用的是1/4像素精度
3.4 帧间预测主要函数(mb_analyse_inter_p16x16)
以16x16的帧间预测主函数为例,主要进行16x16块的帧间预测,主要的工作流程为:
- 设置搜索块大小为16x16
- 遍历所有参考帧
(1)加载半像素点(LOAD_HPELS)
(2)从相邻参考块中去获得一个预测的mv(x264_mb_predict_mv_16x16)
(3)运动搜索(x264_me_search_ref)
(4)存储mv,为将来相邻块的预测做准备 - 将mb放入cache中(x264_macroblock_cache_ref)
其中,运动搜索是帧间预测的最核心函数,执行了具体地搜索方法
static void mb_analyse_inter_p16x16( x264_t *h, x264_mb_analysis_t *a )
{
x264_me_t m;
int i_mvc; // mvc: mv candidate,这里表示mv的候选数量
ALIGNED_ARRAY_8( int16_t, mvc,[8],[2] );
int i_halfpel_thresh = INT_MAX;
int *p_halfpel_thresh = (a->b_early_terminate && h->mb.pic.i_fref[0]>1) ? &i_halfpel_thresh : NULL;
// ----- 1.设置为16x16的搜索块 ----- //
/* 16x16 Search on all ref frame */
m.i_pixel = PIXEL_16x16;
LOAD_FENC( &m, h->mb.pic.p_fenc, 0, 0 );
a->l0.me16x16.cost = INT_MAX;
for( int i_ref = 0; i_ref < h->mb.pic.i_fref[0]; i_ref++ )
{ // ------ 2.遍历所有参考帧 ----- //
m.i_ref_cost = REF_COST( 0, i_ref );
i_halfpel_thresh -= m.i_ref_cost;
/* search with ref */
// 加载半像素点的列表
// 参考列表的4个分量列表,包括yN(整点像素),yH(1/2水平内插),yV(1/2垂直内插), yHV(1/2斜对角内插)
// [12]: yN, yH, yV, yHV, (NV12 ? uv : I444 ? (uN, uH, uV, uHV, vN, ...))
LOAD_HPELS( &m, h->mb.pic.p_fref[0][i_ref], 0, i_ref, 0, 0 );
// p_fref_w是加了权重的整像素亮度分量
LOAD_WPELS( &m, h->mb.pic.p_fref_w[i_ref], 0, i_ref, 0, 0 );
// 从相邻参考块中去获得一个预测的mv
x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp );
if( h->mb.ref_blind_dupe == i_ref )
{
CP32( m.mv, a->l0.mvc[0][0] );
x264_me_refine_qpel_refdupe( h, &m, p_halfpel_thresh );
}
else
{
x264_mb_predict_mv_ref16x16( h, 0, i_ref, mvc, &i_mvc );
// 运动搜索
x264_me_search_ref( h, &m, mvc, i_mvc, p_halfpel_thresh );
}
// 存储mv,为将来相邻块的预测做准备
/* save mv for predicting neighbors */
CP32( h->mb.mvr[0][i_ref][h->mb.i_mb_xy], m.mv );
CP32( a->l0.mvc[i_ref][0], m.mv );
/* early termination
* SSD threshold would probably be better than SATD */
if( i_ref == 0
&& a->b_try_skip
&& m.cost-m.cost_mv < 300*a->i_lambda
&& abs(m.mv[0]-h->mb.cache.pskip_mv[0])
+ abs(m.mv[1]-h->mb.cache.pskip_mv[1]) <= 1
&& x264_macroblock_probe_pskip( h ) )
{
h->mb.i_type = P_SKIP;
analyse_update_cache( h, a );
assert( h->mb.cache.pskip_mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 );
return;
}
m.cost += m.i_ref_cost;
i_halfpel_thresh += m.i_ref_cost;
if( m.cost < a->l0.me16x16.cost )
h->mc.memcpy_aligned( &a->l0.me16x16, &m, sizeof(x264_me_t) );
}
// 将mb放入cache中
x264_macroblock_cache_ref( h, 0, 0, 4, 4, 0, a->l0.me16x16.i_ref );
assert( a->l0.me16x16.mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 );
h->mb.i_type = P_L0;
if( a->i_mbrd )
{
mb_init_fenc_cache( h, a->i_mbrd >= 2 || h->param.analyse.inter & X264_ANALYSE_PSUB8x8 );
if( a->l0.me16x16.i_ref == 0 && M32( a->l0.me16x16.mv ) == M32( h->mb.cache.pskip_mv ) && !a->b_force_intra )
{
h->mb.i_partition = D_16x16;
x264_macroblock_cache_mv_ptr( h, 0, 0, 4, 4, 0, a->l0.me16x16.mv );
a->l0.i_rd16x16 = rd_cost_mb( h, a->i_lambda2 );
if( !(h->mb.i_cbp_luma|h->mb.i_cbp_chroma) )
h->mb.i_type = P_SKIP;
}
}
}
3.4.1 获取预测的运动矢量(x264_mb_predict_mv_16x16)
该函数位于mvpred.c中,主要的功能是利用左侧、上方和右上方参考mb的mv来预测当前mb的mv
/*
关于264编码器中mvd的理解:
mvd是利用相邻参考mb使用的mv进行预测,再与当前mb的mv取差值得到的,这其中涉及到如何确定相邻mb的问题
(1)如果当前mb和相邻mb都是同一尺寸,例如
+---+---+---+
| A | B | C |
+---+---+---+
| D | E | F |
+---+---+---+
假设此时编码的mb为E,则左侧mb为D,上方mb为B,右上方mb为C
(2)如果当前mb和相邻mb不是同一尺寸,例如
+---+---+---+---+---+---+---+---+---+---+---+---+
| | | | | |
+ + + + + +
| | B | | | |
+ +(4 + + +---+---+---+---+
| | x | | | |
+ + 8)+ + + C(16x8) +
| | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+
| | A(8x4)| |
+---+---+---+---+ +
| | | |
+---+---+---+---+ E(16x16) +
| | | | | |
+---+---+---+---+ +
| | | | | |
+---+---+---+---+---+---+---+---+
mb的选择:
(a)如果E的左边不只一个split,取其中最上方的一个为A;
(b)如果E的上方不只一个split,取最左侧的一个为B
mvp的选择:
(a)传输split不包括16x8和8x16时,mvp为A、B、C分割MV的中值
(b)16x8的split,上面部分mvp由B预测,下面部分mvp由A预测
(c)8x16的split,左边部分mvp由A预测,右边部分mvp由C预测
(d)对于skipped的mb,MVP为A、B和C的中值
*/
void x264_mb_predict_mv_16x16( x264_t *h, int i_list, int i_ref, int16_t mvp[2] )
{
int i_refa = h->mb.cache.ref[i_list][X264_SCAN8_0 - 1];
int16_t *mv_a = h->mb.cache.mv[i_list][X264_SCAN8_0 - 1]; // 左侧mv
int i_refb = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8];
int16_t *mv_b = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8]; // 右侧mv
int i_refc = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8 + 4];
int16_t *mv_c = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8 + 4]; // 右上方mv
if( i_refc == -2 ) // 右上方的块无法使用
{
i_refc = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8 - 1];
mv_c = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8 - 1];
}
int i_count = (i_refa == i_ref) + (i_refb == i_ref) + (i_refc == i_ref);
// 如果可参考数量不只1个,则取均值
if( i_count > 1 )
{
median:
x264_median_mv( mvp, mv_a, mv_b, mv_c );
}
else if( i_count == 1 ) // 如果参考数量只有1个,则直接赋值
{
if( i_refa == i_ref )
CP32( mvp, mv_a );
else if( i_refb == i_ref )
CP32( mvp, mv_b );
else
CP32( mvp, mv_c );
}
else if( i_refb == -2 && i_refc == -2 && i_refa != -2 )
CP32( mvp, mv_a );
else
goto median;
}
3.4.2 运动估计主函数(x264_me_search_ref)
该函数主要执行的任务是找到一个最佳的预测mv,使得参考块和当前块的差异最小,主要的工作流程为:
- 检查1/4像素精度
(1)计算mvp的损失(1/4像素精度的mx和my的cost),损失存储在cost中
(2)计算mvc中各个mv的损失(从mvc中去掉为0的mv,同时去掉和pmv相同的mv,这样获得的一些mv是可以有效计算损失的)
(3)检查所有可用的mv所对应的cost,找到最小的
(4)将最佳的预测mv转换成为fullel,之后会进行整像素搜索 - 检查整像素
- 进行运动搜索
(1)检查菱形搜索(X264_ME_DIA),半径为1
(2)检查六边形搜索(X264_ME_HEX),半径为2。先检查六边形,再检查正方形
(3)不均匀交叉多六边形网格搜索(X264_ME_UMH)
(4)栅格搜索(X264_ME_ESA、X264_ME_TESA) - 进行子像素运动搜索,优化运动向量(refine_subpel)
void x264_me_search_ref( x264_t *h, x264_me_t *m, int16_t (*mvc)[2], int i_mvc, int *p_halfpel_thresh )
{
const int bw = x264_pixel_size[m->i_pixel].w;
const int bh = x264_pixel_size[m->i_pixel].h;
const int i_pixel = m->i_pixel;
const int stride = m->i_stride[0];
int i_me_range = h->param.analyse.i_me_range;
int bmx, bmy, bcost = COST_MAX;
int bpred_cost = COST_MAX;
int omx, omy, pmx, pmy;
pixel *p_fenc = m->p_fenc[0]; // 编码帧
pixel *p_fref_w = m->p_fref_w; // 带权重的参考帧
ALIGNED_ARRAY_32( pixel, pix,[16*16] );
ALIGNED_ARRAY_8( int16_t, mvc_temp,[16],[2] );
ALIGNED_ARRAY_16( int, costs,[16] );
int mv_x_min = h->mb.mv_limit_fpel[0][0];
int mv_y_min = h->mb.mv_limit_fpel[0][1];
int mv_x_max = h->mb.mv_limit_fpel[1][0];
int mv_y_max = h->mb.mv_limit_fpel[1][1];
/* Special version of pack to allow shortcuts in CHECK_MVRANGE */
#define pack16to32_mask2(mx,my) (((uint32_t)(mx)<<16)|((uint32_t)(my)&0x7FFF))
uint32_t mv_min = pack16to32_mask2( -mv_x_min, -mv_y_min );
uint32_t mv_max = pack16to32_mask2( mv_x_max, mv_y_max )|0x8000;
uint32_t pmv, bpred_mv = 0;
#define CHECK_MVRANGE(mx,my) (!(((pack16to32_mask2(mx,my) + mv_min) | (mv_max - pack16to32_mask2(mx,my))) & 0x80004000))
const uint16_t *p_cost_mvx = m->p_cost_mv - m->mvp[0];
const uint16_t *p_cost_mvy = m->p_cost_mv - m->mvp[1];
/* Try extra predictors if provided. If subme >= 3, check subpel predictors,
* otherwise round them to fullpel. */
// ----- 1.检查1/4像素精度 ----- //
if( h->mb.i_subpel_refine >= 3 )
{
/* Calculate and check the MVP first */
int bpred_mx = x264_clip3( m->mvp[0], SPEL(mv_x_min), SPEL(mv_x_max) );
int bpred_my = x264_clip3( m->mvp[1], SPEL(mv_y_min), SPEL(mv_y_max) );
pmv = pack16to32_mask( bpred_mx, bpred_my );
// #define FPEL(mv) (((mv)+2)>>2)
// 把1/4精度转换成为整像素精度(加2是为了四舍五入)
pmx = FPEL( bpred_mx );
pmy = FPEL( bpred_my );
// 计算1/4像素精度的mx和my的cost,损失存储在cost中
COST_MV_HPEL( bpred_mx, bpred_my, bpred_cost );
int pmv_cost = bpred_cost;
if( i_mvc > 0 ) // i_mvc是mv候选的数量
{
// 从mvc中去掉为0的mv,同时去掉和pmv相同的mv,这样获得的一些mv是可以有效计算损失的
/* Clip MV candidates and eliminate those equal to zero and pmv. */
int valid_mvcs = x264_predictor_clip( mvc_temp+2, mvc, i_mvc, h->mb.mv_limit_fpel, pmv );
if( valid_mvcs > 0 )
{
int i = 1, cost;
/* We stuff pmv here to branchlessly pick between pmv and the various
* MV candidates. [0] gets skipped in order to maintain alignment for
* x264_predictor_clip. */
M32( mvc_temp[1] ) = pmv;
bpred_cost <<= 4;
// 检查所有可用的mv所对应的cost,找到最小的
do
{
int mx = mvc_temp[i+1][0];
int my = mvc_temp[i+1][1];
COST_MV_HPEL( mx, my, cost );
COPY1_IF_LT( bpred_cost, (cost << 4) + i );
} while( ++i <= valid_mvcs );
bpred_mx = mvc_temp[(bpred_cost&15)+1][0];
bpred_my = mvc_temp[(bpred_cost&15)+1][1];
bpred_cost >>= 4;
}
}
// 将最佳的预测mv转换成为fullel,之后会进行整像素搜索
/* Round the best predictor back to fullpel and get the cost, since this is where
* we'll be starting the fullpel motion search. */
bmx = FPEL( bpred_mx );
bmy = FPEL( bpred_my );
bpred_mv = pack16to32_mask(bpred_mx, bpred_my);
if( bpred_mv&0x00030003 ) /* Only test if the tested predictor is actually subpel... */
COST_MV( bmx, bmy );
else /* Otherwise just copy the cost (we already know it) */
bcost = bpred_cost;
/* Test the zero vector if it hasn't been tested yet. */
if( pmv )
{
if( bmx|bmy ) COST_MV( 0, 0 );
}
/* If a subpel mv candidate was better than the zero vector, the previous
* fullpel check won't have gotten it even if the pmv was zero. So handle
* that possibility here. */
else
{
COPY3_IF_LT( bcost, pmv_cost, bmx, 0, bmy, 0 );
}
}
else
{ // ----- 2.检查整像素 ----- //
// 计算整像素mvp
/* Calculate and check the fullpel MVP first */
bmx = pmx = x264_clip3( FPEL(m->mvp[0]), mv_x_min, mv_x_max );
bmy = pmy = x264_clip3( FPEL(m->mvp[1]), mv_y_min, mv_y_max );
pmv = pack16to32_mask( bmx, bmy );
/* Because we are rounding the predicted motion vector to fullpel, there will be
* an extra MV cost in 15 out of 16 cases. However, when the predicted MV is
* chosen as the best predictor, it is often the case that the subpel search will
* result in a vector at or next to the predicted motion vector. Therefore, we omit
* the cost of the MV from the rounded MVP to avoid unfairly biasing against use of
* the predicted motion vector.
*
* Disclaimer: this is a post-hoc rationalization for why this hack works. */
bcost = h->pixf.fpelcmp[i_pixel]( p_fenc, FENC_STRIDE, &p_fref_w[bmy*stride+bmx], stride );
if( i_mvc > 0 )
{
/* Like in subme>=3, except we also round the candidates to fullpel. */
int valid_mvcs = x264_predictor_roundclip( mvc_temp+2, mvc, i_mvc, h->mb.mv_limit_fpel, pmv );
if( valid_mvcs > 0 )
{
int i = 1, cost;
M32( mvc_temp[1] ) = pmv;
bcost <<= 4;
do
{
int mx = mvc_temp[i+1][0];
int my = mvc_temp[i+1][1];
cost = h->pixf.fpelcmp[i_pixel]( p_fenc, FENC_STRIDE, &p_fref_w[my*stride+mx], stride ) + BITS_MVD( mx, my );
COPY1_IF_LT( bcost, (cost << 4) + i );
} while( ++i <= valid_mvcs );
bmx = mvc_temp[(bcost&15)+1][0];
bmy = mvc_temp[(bcost&15)+1][1];
bcost >>= 4;
}
}
/* Same as above, except the condition is simpler. */
if( pmv )
COST_MV( 0, 0 );
}
// ----- 3.进行运动搜索 ----- //
switch( h->mb.i_me_method )
{
case X264_ME_DIA:
{
// 菱形搜索,但是半径为1,使用整像素搜索方式
/* diamond search, radius 1 */
bcost <<= 4;
int i = i_me_range;
do
{
// 中心点为(bmx,bmy)
// 计算4个周围的点的开销,(0,-1), (0,1), (-1,0), (1,0)
// 结果存储在costs中,cost的计算可能是sad或者是satd
//COST_MV_X4_DIR( 0,-1, 0,1, -1,0, 1,0, costs )宏展开后代码如下所示
/*
* {
pixel *pix_base = p_fref_w + bmx + bmy*stride;
//调用像素比较函数
h->pixf.fpelcmp_x4[i_pixel]( p_fenc,
pix_base + (0) + (-1)*stride, //上
pix_base + (0) + (1)*stride, //下
pix_base + (-1) + (0)*stride, //左
pix_base + (1) + (0)*stride, //右
stride, costs ); // 这里计算时使用的是sad或者是satd
//得到4个点的开销,存储到costs[]数组
(costs)[0] += (p_cost_mvx[(bmx+(0))<<2] + p_cost_mvy[(bmy+(-1))<<2]);
(costs)[1] += (p_cost_mvx[(bmx+(0))<<2] + p_cost_mvy[(bmy+(1))<<2]);
(costs)[2] += (p_cost_mvx[(bmx+(-1))<<2] + p_cost_mvy[(bmy+(0))<<2]);
(costs)[3] += (p_cost_mvx[(bmx+(1))<<2] + p_cost_mvy[(bmy+(0))<<2]);
// 这里的p_cost_mvx和p_cost_mvy是预先存储的possible mv for lambda * n bits
// 将上面两个式子合并起来就是 sad + lambda * n, 即率失真损失
}
*/
/*
* 顺序
* 1
* 3 x 4
* 2
*/
COST_MV_X4_DIR( 0,-1, 0,1, -1,0, 1,0, costs );
COPY1_IF_LT( bcost, (costs[0]<<4)+1 );
COPY1_IF_LT( bcost, (costs[1]<<4)+3 );
COPY1_IF_LT( bcost, (costs[2]<<4)+4 );
COPY1_IF_LT( bcost, (costs[3]<<4)+12 );
if( !(bcost&15) )
break;
bmx -= (int32_t)((uint32_t)bcost<<28)>>30;
bmy -= (int32_t)((uint32_t)bcost<<30)>>30;
bcost &= ~15;
} while( --i && CHECK_MVRANGE(bmx, bmy) );
bcost >>= 4;
break;
}
case X264_ME_HEX:
{
me_hex2:
// 六边形搜索,半径为2
/* hexagon search, radius 2 */
#if 0
for( int i = 0; i < i_me_range/2; i++ )
{
omx = bmx; omy = bmy;
COST_MV( omx-2, omy );
COST_MV( omx-1, omy+2 );
COST_MV( omx+1, omy+2 );
COST_MV( omx+2, omy );
COST_MV( omx+1, omy-2 );
COST_MV( omx-1, omy-2 );
if( bmx == omx && bmy == omy )
break;
if( !CHECK_MVRANGE(bmx, bmy) )
break;
}
#else
/* equivalent to the above, but eliminates duplicate candidates */
/* hexagon */
COST_MV_X3_DIR( -2,0, -1, 2, 1, 2, costs );
COST_MV_X3_DIR( 2,0, 1,-2, -1,-2, costs+4 ); /* +4 for 16-byte alignment */
bcost <<= 3;
COPY1_IF_LT( bcost, (costs[0]<<3)+2 );
COPY1_IF_LT( bcost, (costs[1]<<3)+3 );
COPY1_IF_LT( bcost, (costs[2]<<3)+4 );
COPY1_IF_LT( bcost, (costs[4]<<3)+5 );
COPY1_IF_LT( bcost, (costs[5]<<3)+6 );
COPY1_IF_LT( bcost, (costs[6]<<3)+7 );
if( bcost&7 )
{
int dir = (bcost&7)-2;
bmx += hex2[dir+1][0];
bmy += hex2[dir+1][1];
/* half hexagon, not overlapping the previous iteration */
for( int i = (i_me_range>>1) - 1; i > 0 && CHECK_MVRANGE(bmx, bmy); i-- )
{
COST_MV_X3_DIR( hex2[dir+0][0], hex2[dir+0][1],
hex2[dir+1][0], hex2[dir+1][1],
hex2[dir+2][0], hex2[dir+2][1],
costs );
bcost &= ~7;
COPY1_IF_LT( bcost, (costs[0]<<3)+1 );
COPY1_IF_LT( bcost, (costs[1]<<3)+2 );
COPY1_IF_LT( bcost, (costs[2]<<3)+3 );
if( !(bcost&7) )
break;
dir += (bcost&7)-2;
dir = mod6m1[dir+1];
bmx += hex2[dir+1][0];
bmy += hex2[dir+1][1];
}
}
bcost >>= 3;
#endif
// 做正方形的调整
/* square refine */
bcost <<= 4;
COST_MV_X4_DIR( 0,-1, 0,1, -1,0, 1,0, costs );
COPY1_IF_LT( bcost, (costs[0]<<4)+1 );
COPY1_IF_LT( bcost, (costs[1]<<4)+2 );
COPY1_IF_LT( bcost, (costs[2]<<4)+3 );
COPY1_IF_LT( bcost, (costs[3]<<4)+4 );
COST_MV_X4_DIR( -1,-1, -1,1, 1,-1, 1,1, costs );
COPY1_IF_LT( bcost, (costs[0]<<4)+5 );
COPY1_IF_LT( bcost, (costs[1]<<4)+6 );
COPY1_IF_LT( bcost, (costs[2]<<4)+7 );
COPY1_IF_LT( bcost, (costs[3]<<4)+8 );
bmx += square1[bcost&15][0];
bmy += square1[bcost&15][1];
bcost >>= 4;
break;
}
case X264_ME_UMH:
{
// 不均匀交叉多六边形网格搜索
/* Uneven-cross Multi-Hexagon-grid Search
* as in JM, except with different early termination */
static const uint8_t pixel_size_shift[7] = { 0, 1, 1, 2, 3, 3, 4 };
int ucost1, ucost2;
int cross_start = 1;
/* refine predictors */
ucost1 = bcost;
DIA1_ITER( pmx, pmy );
if( pmx | pmy )
DIA1_ITER( 0, 0 );
if( i_pixel == PIXEL_4x4 )
goto me_hex2;
ucost2 = bcost;
if( (bmx | bmy) && ((bmx-pmx) | (bmy-pmy)) )
DIA1_ITER( bmx, bmy );
if( bcost == ucost2 )
cross_start = 3;
omx = bmx; omy = bmy;
/* early termination */
#define SAD_THRESH(v) ( bcost < ( v >> pixel_size_shift[i_pixel] ) )
if( bcost == ucost2 && SAD_THRESH(2000) )
{
COST_MV_X4( 0,-2, -1,-1, 1,-1, -2,0 );
COST_MV_X4( 2, 0, -1, 1, 1, 1, 0,2 );
if( bcost == ucost1 && SAD_THRESH(500) )
break;
if( bcost == ucost2 )
{
int range = (i_me_range>>1) | 1;
CROSS( 3, range, range );
COST_MV_X4( -1,-2, 1,-2, -2,-1, 2,-1 );
COST_MV_X4( -2, 1, 2, 1, -1, 2, 1, 2 );
if( bcost == ucost2 )
break;
cross_start = range + 2;
}
}
/* adaptive search range */
if( i_mvc )
{
/* range multipliers based on casual inspection of some statistics of
* average distance between current predictor and final mv found by ESA.
* these have not been tuned much by actual encoding. */
static const uint8_t range_mul[4][4] =
{
{ 3, 3, 4, 4 },
{ 3, 4, 4, 4 },
{ 4, 4, 4, 5 },
{ 4, 4, 5, 6 },
};
int mvd;
int sad_ctx, mvd_ctx;
int denom = 1;
if( i_mvc == 1 )
{
if( i_pixel == PIXEL_16x16 )
/* mvc is probably the same as mvp, so the difference isn't meaningful.
* but prediction usually isn't too bad, so just use medium range */
mvd = 25;
else
mvd = abs( m->mvp[0] - mvc[0][0] )
+ abs( m->mvp[1] - mvc[0][1] );
}
else
{
/* calculate the degree of agreement between predictors. */
/* in 16x16, mvc includes all the neighbors used to make mvp,
* so don't count mvp separately. */
denom = i_mvc - 1;
mvd = 0;
if( i_pixel != PIXEL_16x16 )
{
mvd = abs( m->mvp[0] - mvc[0][0] )
+ abs( m->mvp[1] - mvc[0][1] );
denom++;
}
mvd += x264_predictor_difference( mvc, i_mvc );
}
sad_ctx = SAD_THRESH(1000) ? 0
: SAD_THRESH(2000) ? 1
: SAD_THRESH(4000) ? 2 : 3;
mvd_ctx = mvd < 10*denom ? 0
: mvd < 20*denom ? 1
: mvd < 40*denom ? 2 : 3;
i_me_range = i_me_range * range_mul[mvd_ctx][sad_ctx] >> 2;
}
/* FIXME if the above DIA2/OCT2/CROSS found a new mv, it has not updated omx/omy.
* we are still centered on the same place as the DIA2. is this desirable? */
CROSS( cross_start, i_me_range, i_me_range>>1 );
COST_MV_X4( -2,-2, -2,2, 2,-2, 2,2 );
/* hexagon grid */
omx = bmx; omy = bmy;
const uint16_t *p_cost_omvx = p_cost_mvx + omx*4;
const uint16_t *p_cost_omvy = p_cost_mvy + omy*4;
int i = 1;
do
{
static const int8_t hex4[16][2] = {
{ 0,-4}, { 0, 4}, {-2,-3}, { 2,-3},
{-4,-2}, { 4,-2}, {-4,-1}, { 4,-1},
{-4, 0}, { 4, 0}, {-4, 1}, { 4, 1},
{-4, 2}, { 4, 2}, {-2, 3}, { 2, 3},
};
if( 4*i > X264_MIN4( mv_x_max-omx, omx-mv_x_min,
mv_y_max-omy, omy-mv_y_min ) )
{
for( int j = 0; j < 16; j++ )
{
int mx = omx + hex4[j][0]*i;
int my = omy + hex4[j][1]*i;
if( CHECK_MVRANGE(mx, my) )
COST_MV( mx, my );
}
}
else
{
int dir = 0;
pixel *pix_base = p_fref_w + omx + (omy-4*i)*stride;
int dy = i*stride;
#define SADS(k,x0,y0,x1,y1,x2,y2,x3,y3)\
h->pixf.fpelcmp_x4[i_pixel]( p_fenc,\
pix_base x0*i+(y0-2*k+4)*dy,\
pix_base x1*i+(y1-2*k+4)*dy,\
pix_base x2*i+(y2-2*k+4)*dy,\
pix_base x3*i+(y3-2*k+4)*dy,\
stride, costs+4*k );\
pix_base += 2*dy;
#define ADD_MVCOST(k,x,y) costs[k] += p_cost_omvx[x*4*i] + p_cost_omvy[y*4*i]
#define MIN_MV(k,x,y) COPY2_IF_LT( bcost, costs[k], dir, x*16+(y&15) )
SADS( 0, +0,-4, +0,+4, -2,-3, +2,-3 );
SADS( 1, -4,-2, +4,-2, -4,-1, +4,-1 );
SADS( 2, -4,+0, +4,+0, -4,+1, +4,+1 );
SADS( 3, -4,+2, +4,+2, -2,+3, +2,+3 );
ADD_MVCOST( 0, 0,-4 );
ADD_MVCOST( 1, 0, 4 );
ADD_MVCOST( 2,-2,-3 );
ADD_MVCOST( 3, 2,-3 );
ADD_MVCOST( 4,-4,-2 );
ADD_MVCOST( 5, 4,-2 );
ADD_MVCOST( 6,-4,-1 );
ADD_MVCOST( 7, 4,-1 );
ADD_MVCOST( 8,-4, 0 );
ADD_MVCOST( 9, 4, 0 );
ADD_MVCOST( 10,-4, 1 );
ADD_MVCOST( 11, 4, 1 );
ADD_MVCOST( 12,-4, 2 );
ADD_MVCOST( 13, 4, 2 );
ADD_MVCOST( 14,-2, 3 );
ADD_MVCOST( 15, 2, 3 );
MIN_MV( 0, 0,-4 );
MIN_MV( 1, 0, 4 );
MIN_MV( 2,-2,-3 );
MIN_MV( 3, 2,-3 );
MIN_MV( 4,-4,-2 );
MIN_MV( 5, 4,-2 );
MIN_MV( 6,-4,-1 );
MIN_MV( 7, 4,-1 );
MIN_MV( 8,-4, 0 );
MIN_MV( 9, 4, 0 );
MIN_MV( 10,-4, 1 );
MIN_MV( 11, 4, 1 );
MIN_MV( 12,-4, 2 );
MIN_MV( 13, 4, 2 );
MIN_MV( 14,-2, 3 );
MIN_MV( 15, 2, 3 );
#undef SADS
#undef ADD_MVCOST
#undef MIN_MV
if( dir )
{
bmx = omx + i*(dir>>4);
bmy = omy + i*((int32_t)((uint32_t)dir<<28)>>28);
}
}
} while( ++i <= i_me_range>>2 );
if( bmy <= mv_y_max && bmy >= mv_y_min && bmx <= mv_x_max && bmx >= mv_x_min )
goto me_hex2;
break;
}
case X264_ME_ESA:
case X264_ME_TESA:
{
const int min_x = X264_MAX( bmx - i_me_range, mv_x_min );
const int min_y = X264_MAX( bmy - i_me_range, mv_y_min );
const int max_x = X264_MIN( bmx + i_me_range, mv_x_max );
const int max_y = X264_MIN( bmy + i_me_range, mv_y_max );
/* SEA is fastest in multiples of 4 */
const int width = (max_x - min_x + 3) & ~3;
#if 0
/* plain old exhaustive search */
for( int my = min_y; my <= max_y; my++ )
for( int mx = min_x; mx < min_x + width; mx++ )
COST_MV( mx, my );
#else
/* successive elimination by comparing DC before a full SAD,
* because sum(abs(diff)) >= abs(diff(sum)). */
uint16_t *sums_base = m->integral;
ALIGNED_ARRAY_16( int, enc_dc,[4] );
int sad_size = i_pixel <= PIXEL_8x8 ? PIXEL_8x8 : PIXEL_4x4;
int delta = x264_pixel_size[sad_size].w;
int16_t *xs = h->scratch_buffer;
int xn;
uint16_t *cost_fpel_mvx = h->cost_mv_fpel[h->mb.i_qp][-m->mvp[0]&3] + (-m->mvp[0]>>2);
h->pixf.sad_x4[sad_size]( (pixel*)x264_zero, p_fenc, p_fenc+delta,
p_fenc+delta*FENC_STRIDE, p_fenc+delta+delta*FENC_STRIDE,
FENC_STRIDE, enc_dc );
if( delta == 4 )
sums_base += stride * (h->fenc->i_lines[0] + PADV*2);
if( i_pixel == PIXEL_16x16 || i_pixel == PIXEL_8x16 || i_pixel == PIXEL_4x8 )
delta *= stride;
if( i_pixel == PIXEL_8x16 || i_pixel == PIXEL_4x8 )
enc_dc[1] = enc_dc[2];
if( h->mb.i_me_method == X264_ME_TESA )
{
// ADS threshold, then SAD threshold, then keep the best few SADs, then SATD
mvsad_t *mvsads = (mvsad_t *)(xs + ((width+31)&~31) + 4);
int nmvsad = 0, limit;
int sad_thresh = i_me_range <= 16 ? 10 : i_me_range <= 24 ? 11 : 12;
int bsad = h->pixf.sad[i_pixel]( p_fenc, FENC_STRIDE, p_fref_w+bmy*stride+bmx, stride )
+ BITS_MVD( bmx, bmy );
for( int my = min_y; my <= max_y; my++ )
{
int i;
int ycost = p_cost_mvy[my*4];
if( bsad <= ycost )
continue;
bsad -= ycost;
xn = h->pixf.ads[i_pixel]( enc_dc, sums_base + min_x + my * stride, delta,
cost_fpel_mvx+min_x, xs, width, bsad * 17 >> 4 );
for( i = 0; i < xn-2; i += 3 )
{
pixel *ref = p_fref_w+min_x+my*stride;
ALIGNED_ARRAY_16( int, sads,[4] ); /* padded to [4] for asm */
h->pixf.sad_x3[i_pixel]( p_fenc, ref+xs[i], ref+xs[i+1], ref+xs[i+2], stride, sads );
for( int j = 0; j < 3; j++ )
{
int sad = sads[j] + cost_fpel_mvx[xs[i+j]];
if( sad < bsad*sad_thresh>>3 )
{
COPY1_IF_LT( bsad, sad );
mvsads[nmvsad].sad = sad + ycost;
mvsads[nmvsad].mv[0] = min_x+xs[i+j];
mvsads[nmvsad].mv[1] = my;
nmvsad++;
}
}
}
for( ; i < xn; i++ )
{
int mx = min_x+xs[i];
int sad = h->pixf.sad[i_pixel]( p_fenc, FENC_STRIDE, p_fref_w+mx+my*stride, stride )
+ cost_fpel_mvx[xs[i]];
if( sad < bsad*sad_thresh>>3 )
{
COPY1_IF_LT( bsad, sad );
mvsads[nmvsad].sad = sad + ycost;
mvsads[nmvsad].mv[0] = mx;
mvsads[nmvsad].mv[1] = my;
nmvsad++;
}
}
bsad += ycost;
}
limit = i_me_range >> 1;
sad_thresh = bsad*sad_thresh>>3;
while( nmvsad > limit*2 && sad_thresh > bsad )
{
int i = 0;
// halve the range if the domain is too large... eh, close enough
sad_thresh = (sad_thresh + bsad) >> 1;
while( i < nmvsad && mvsads[i].sad <= sad_thresh )
i++;
for( int j = i; j < nmvsad; j++ )
{
uint32_t sad;
if( WORD_SIZE == 8 && sizeof(mvsad_t) == 8 )
{
uint64_t mvsad = M64( &mvsads[i] ) = M64( &mvsads[j] );
#if WORDS_BIGENDIAN
mvsad >>= 32;
#endif
sad = mvsad;
}
else
{
sad = mvsads[j].sad;
CP32( mvsads[i].mv, mvsads[j].mv );
mvsads[i].sad = sad;
}
i += (sad - (sad_thresh+1)) >> 31;
}
nmvsad = i;
}
while( nmvsad > limit )
{
int bi = 0;
for( int i = 1; i < nmvsad; i++ )
if( mvsads[i].sad > mvsads[bi].sad )
bi = i;
nmvsad--;
if( sizeof( mvsad_t ) == sizeof( uint64_t ) )
CP64( &mvsads[bi], &mvsads[nmvsad] );
else
mvsads[bi] = mvsads[nmvsad];
}
for( int i = 0; i < nmvsad; i++ )
COST_MV( mvsads[i].mv[0], mvsads[i].mv[1] );
}
else
{
// just ADS and SAD
for( int my = min_y; my <= max_y; my++ )
{
int i;
int ycost = p_cost_mvy[my*4];
if( bcost <= ycost )
continue;
bcost -= ycost;
xn = h->pixf.ads[i_pixel]( enc_dc, sums_base + min_x + my * stride, delta,
cost_fpel_mvx+min_x, xs, width, bcost );
for( i = 0; i < xn-2; i += 3 )
COST_MV_X3_ABS( min_x+xs[i],my, min_x+xs[i+1],my, min_x+xs[i+2],my );
bcost += ycost;
for( ; i < xn; i++ )
COST_MV( min_x+xs[i], my );
}
}
#endif
}
break;
}
/* -> qpel mv */
uint32_t bmv = pack16to32_mask(bmx,bmy);
uint32_t bmv_spel = SPELx2(bmv);
if( h->mb.i_subpel_refine < 3 )
{
m->cost_mv = p_cost_mvx[bmx*4] + p_cost_mvy[bmy*4];
m->cost = bcost;
/* compute the real cost */
if( bmv == pmv ) m->cost += m->cost_mv;
M32( m->mv ) = bmv_spel;
}
else
{
M32(m->mv) = bpred_cost < bcost ? bpred_mv : bmv_spel;
m->cost = X264_MIN( bpred_cost, bcost );
}
// ----- 4.进行子像素运动搜索 ----- //
/* subpel refine */
if( h->mb.i_subpel_refine >= 2 )
{
int hpel = subpel_iterations[h->mb.i_subpel_refine][2];
int qpel = subpel_iterations[h->mb.i_subpel_refine][3];
refine_subpel( h, m, hpel, qpel, p_halfpel_thresh, 0 );
}
}
3.4.3 子像素运动搜索(refine_subpel)
子像素运动搜索能够很好地提升运动搜索精度,在进行搜索时,为了降低搜索的复杂度,使用的是菱形搜索,主要的工作流程为:
- 半像素精度菱形搜索
- 1/4像素精度的菱形搜索
static void refine_subpel( x264_t *h, x264_me_t *m, int hpel_iters, int qpel_iters, int *p_halfpel_thresh, int b_refine_qpel )
{
const int bw = x264_pixel_size[m->i_pixel].w;
const int bh = x264_pixel_size[m->i_pixel].h;
const uint16_t *p_cost_mvx = m->p_cost_mv - m->mvp[0];
const uint16_t *p_cost_mvy = m->p_cost_mv - m->mvp[1];
const int i_pixel = m->i_pixel;
const int b_chroma_me = h->mb.b_chroma_me && (i_pixel <= PIXEL_8x8 || CHROMA444);
int chromapix = h->luma2chroma_pixel[i_pixel];
int chroma_v_shift = CHROMA_V_SHIFT;
int mvy_offset = chroma_v_shift & MB_INTERLACED & m->i_ref ? (h->mb.i_mb_y & 1)*4 - 2 : 0;
ALIGNED_ARRAY_32( pixel, pix,[64*18] ); // really 17x17x2, but round up for alignment
ALIGNED_ARRAY_16( int, costs,[4] );
int bmx = m->mv[0];
int bmy = m->mv[1];
int bcost = m->cost;
int odir = -1, bdir;
// ----- 1.半像素菱形搜索 ----- //
/* halfpel diamond search */
if( hpel_iters )
{
/* try the subpel component of the predicted mv */
if( h->mb.i_subpel_refine < 3 )
{
int mx = x264_clip3( m->mvp[0], h->mb.mv_min_spel[0]+2, h->mb.mv_max_spel[0]-2 );
int my = x264_clip3( m->mvp[1], h->mb.mv_min_spel[1]+2, h->mb.mv_max_spel[1]-2 );
if( (mx-bmx)|(my-bmy) )
COST_MV_SAD( mx, my );
}
bcost <<= 6;
for( int i = hpel_iters; i > 0; i-- )
{
int omx = bmx, omy = bmy;
intptr_t stride = 64; // candidates are either all hpel or all qpel, so one stride is enough
pixel *src0, *src1, *src2, *src3;
src0 = h->mc.get_ref( pix, &stride, m->p_fref, m->i_stride[0], omx, omy-2, bw, bh+1, &m->weight[0] );
src2 = h->mc.get_ref( pix+32, &stride, m->p_fref, m->i_stride[0], omx-2, omy, bw+4, bh, &m->weight[0] );
src1 = src0 + stride;
src3 = src2 + 1;
h->pixf.fpelcmp_x4[i_pixel]( m->p_fenc[0], src0, src1, src2, src3, stride, costs );
costs[0] += p_cost_mvx[omx ] + p_cost_mvy[omy-2];
costs[1] += p_cost_mvx[omx ] + p_cost_mvy[omy+2];
costs[2] += p_cost_mvx[omx-2] + p_cost_mvy[omy ];
costs[3] += p_cost_mvx[omx+2] + p_cost_mvy[omy ];
COPY1_IF_LT( bcost, (costs[0]<<6)+2 );
COPY1_IF_LT( bcost, (costs[1]<<6)+6 );
COPY1_IF_LT( bcost, (costs[2]<<6)+16 );
COPY1_IF_LT( bcost, (costs[3]<<6)+48 );
if( !(bcost&63) )
break;
bmx -= (int32_t)((uint32_t)bcost<<26)>>29;
bmy -= (int32_t)((uint32_t)bcost<<29)>>29;
bcost &= ~63;
}
bcost >>= 6;
}
if( !b_refine_qpel && (h->pixf.mbcmp_unaligned[0] != h->pixf.fpelcmp[0] || b_chroma_me) )
{
bcost = COST_MAX;
COST_MV_SATD( bmx, bmy, -1 );
}
/* early termination when examining multiple reference frames */
if( p_halfpel_thresh )
{
if( (bcost*7)>>3 > *p_halfpel_thresh )
{
m->cost = bcost;
m->mv[0] = bmx;
m->mv[1] = bmy;
// don't need cost_mv
return;
}
else if( bcost < *p_halfpel_thresh )
*p_halfpel_thresh = bcost;
}
// ----- 2.1/4像素精度的菱形搜索 ----- //
/* quarterpel diamond search */
if( h->mb.i_subpel_refine != 1 )
{
bdir = -1;
for( int i = qpel_iters; i > 0; i-- )
{
if( bmy <= h->mb.mv_min_spel[1] || bmy >= h->mb.mv_max_spel[1] || bmx <= h->mb.mv_min_spel[0] || bmx >= h->mb.mv_max_spel[0] )
break;
odir = bdir;
int omx = bmx, omy = bmy;
COST_MV_SATD( omx, omy - 1, 0 );
COST_MV_SATD( omx, omy + 1, 1 );
COST_MV_SATD( omx - 1, omy, 2 );
COST_MV_SATD( omx + 1, omy, 3 );
if( (bmx == omx) & (bmy == omy) )
break;
}
}
/* Special simplified case for subme=1 */
else if( bmy > h->mb.mv_min_spel[1] && bmy < h->mb.mv_max_spel[1] && bmx > h->mb.mv_min_spel[0] && bmx < h->mb.mv_max_spel[0] )
{
int omx = bmx, omy = bmy;
/* We have to use mc_luma because all strides must be the same to use fpelcmp_x4 */
h->mc.mc_luma( pix , 64, m->p_fref, m->i_stride[0], omx, omy-1, bw, bh, &m->weight[0] );
h->mc.mc_luma( pix+16, 64, m->p_fref, m->i_stride[0], omx, omy+1, bw, bh, &m->weight[0] );
h->mc.mc_luma( pix+32, 64, m->p_fref, m->i_stride[0], omx-1, omy, bw, bh, &m->weight[0] );
h->mc.mc_luma( pix+48, 64, m->p_fref, m->i_stride[0], omx+1, omy, bw, bh, &m->weight[0] );
h->pixf.fpelcmp_x4[i_pixel]( m->p_fenc[0], pix, pix+16, pix+32, pix+48, 64, costs );
costs[0] += p_cost_mvx[omx ] + p_cost_mvy[omy-1];
costs[1] += p_cost_mvx[omx ] + p_cost_mvy[omy+1];
costs[2] += p_cost_mvx[omx-1] + p_cost_mvy[omy ];
costs[3] += p_cost_mvx[omx+1] + p_cost_mvy[omy ];
bcost <<= 4;
COPY1_IF_LT( bcost, (costs[0]<<4)+1 );
COPY1_IF_LT( bcost, (costs[1]<<4)+3 );
COPY1_IF_LT( bcost, (costs[2]<<4)+4 );
COPY1_IF_LT( bcost, (costs[3]<<4)+12 );
bmx -= (int32_t)((uint32_t)bcost<<28)>>30;
bmy -= (int32_t)((uint32_t)bcost<<30)>>30;
bcost >>= 4;
}
m->cost = bcost;
m->mv[0] = bmx;
m->mv[1] = bmy;
m->cost_mv = p_cost_mvx[bmx] + p_cost_mvy[bmy];
}
4.小结
由于P帧是编码时最多的帧类型,所以帧间预测是编码器当中减少码流大小的重要技术。通过良好的预测技术,能够降低编码块的残差,从而有效的降低编码码流。预测技术当中,涉及到不同尺寸块的预测,如16x16、16x8、8x16等,涉及到不同的精度,如整像素,亚像素,1/4像素,涉及到不同的搜索方式,如菱形搜索,六边形搜索等,这一系列工具使用的其根本的目的是都是减小写入码流中的残差信息
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen