H264以算法复杂度增加为代价在以下几个方向做了极大的改进:
1、可变大小的图像分块已经帧间、帧内编码
2、基于1/4(亮度)和1/8(色度)像素精度的运动估计
3、多预测参考帧的使用
4、4x4的整数变换
5、先进的熵编码技术
6、去方块滤波器
在以上技术的改进中,帧间编码采用了多模式的块匹配运动估计技术(BMME),其中包括16x16、16x8、8x16、8x8、8x4、4x8、4x4共7种宏块模式。全搜索(FS)算法是BMME最直接的实现方法。FS算法遍历了分块的每一种模式,运算律巨大。在对H264各编码模块计算量的统计结果中,多模式的BMME占据了整个编码的大约70%的计算量。在H264原算法中,没有对图像特性进行分析,每一幅图像都要进行 7种模式预测,而且每个模式下的每个子宏块也都要进行运动估计,最后利用率失真算法对所有可能模式进行比较,选出最佳预测模式。
typedef struct
{
/* input */
int i_pixel; /* PIXEL_WxH */
int lm; /* lambda motion */
uint8_t *p_fref;//参考帧
int i_fref;//参考帧i_stride
uint8_t *p_img;//原始像素
int i_img;//原始像素i_stride
int mvp[2];//运动矢量预测值
int b_mvc;
int mvc[2];
/* output */
int cost; /* satd + lm * nbits */
int mv[2];//搜索到的运动矢量mv
} x264_me_t;
typedef struct
{
/* 16x16 */
int i_ref;//list中参考图像的序号
x264_me_t me16x16;//16x16的运动矢量相关参数
/* 8x8 */
int i_cost8x8;//8x8时的运动矢量最优sad值
x264_me_t me8x8[4];//4个8x8的运动矢量相关参数
/* Sub 4x4 */
int i_cost4x4[4]; /* cost per 8x8 partition */
x264_me_t me4x4[4][4];
/* Sub 8x4 */
int i_cost8x4[4]; /* cost per 8x8 partition */
x264_me_t me8x4[4][2];
/* Sub 4x8 */
int i_cost4x8[4]; /* cost per 8x8 partition */
x264_me_t me4x8[4][4];
/* 16x8 */
int i_cost16x8;
x264_me_t me16x8[2];
/* 8x16 */
int i_cost8x16;
x264_me_t me8x16[2];
} x264_mb_analysis_list_t;
typedef struct
{
/* conduct the analysis using this lamda and QP */
int i_lambda;//拉马拉的值,用于码率控制
int i_qp;//量化参数qp值
/* I: Intra part */
/* Luma part 16x16 and 4x4 modes stats */
int i_sad_i16x16;//16x16帧内预测的最优sad值
int i_predict16x16;//帧内预测的预测模式
int i_sad_i4x4;//4x4帧内预测的最优sad值
int i_predict4x4[4][4];//16个4x4宏块的帧内预测模式
/* Chroma part */
int i_sad_i8x8;//色度的帧内预测sad值
int i_predict8x8;//色度的帧内预测模式,和16x16一样,一共4种
/* II: Inter part P/B frame */
x264_mb_analysis_list_t l0;//list0的帧间预测
x264_mb_analysis_list_t l1;//list1的帧间预测
int i_cost16x16bi; /* used the same ref and mv as l0 and l1 (at least for now) */
} x264_mb_analysis_t;
static void x264_mb_analyse_inter_p16x16( x264_t *h, x264_mb_analysis_t *a )
{
x264_me_t m;//运动矢量
int i_ref;
/* 16x16 Search on all ref frame */
m.i_pixel = PIXEL_16x16;//16x16宏块
m.lm = a->i_lambda;//拉马拉的值
m.p_img = h->mb.pic.p_img[0];//原始像素
m.i_img = h->mb.pic.i_img[0];//原始像素i_stride
m.i_fref = h->mb.pic.i_fdec[0];//参考帧的i_stride
m.b_mvc = 0;
// m.mvc[0] = 0;
// m.mvc[1] = 0;
/* ME for ref 0 */
m.p_fref = h->mb.pic.p_fref[0][0][0];//第一个0:参考帧list0;第二个0:参考帧序号;第三个0:Y分量
x264_mb_predict_mv_16x16( h, 0, 0, m.mvp );//先根据之前的预测得到运动矢量搜索的起点
x264_me_search( h, &m );//运动矢量搜索得到mv
a->l0.i_ref = 0;//参考帧索引
a->l0.me16x16 = m;
for( i_ref = 1; i_ref < h->i_ref0; i_ref++ )//搜索所有的参考帧,i_ref0为参考帧个数
{
/* search with ref */
m.p_fref = h->mb.pic.p_fref[0][i_ref][0];//获取第i_ref索引的参考帧的像素指针
x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp );//获取搜索的起始位置
x264_me_search( h, &m );//搜索最佳mv
/* add ref cost */
m.cost += m.lm * bs_size_te( h->sh.i_num_ref_idx_l0_active - 1, i_ref );//i_num_ref_idx_l0_active为参考帧数量
if( m.cost < a->l0.me16x16.cost )//比较当前参考帧索引的sad与前面计算得到的最优sad值
{
a->l0.i_ref = i_ref;//最优sad对应的参考索引
a->l0.me16x16 = m;//保存运动矢量
}
}
/* Set global ref, needed for all others modes */
x264_macroblock_cache_ref( h, 0, 0, 4, 4, 0, a->l0.i_ref );
}
/*最优mv搜索,只在[-16, 16]正方形范围内进行搜索*/
void x264_me_search( x264_t *h, x264_me_t *m )
{
const int i_pixel = m->i_pixel;
int bcost;//sad值
int bmx, bmy;//当前的搜索位置
uint8_t *p_fref = m->p_fref;//参考像素指针
/* init with mvp */
bmx = x264_clip3( m->mvp[0]>>2, -16, 16 );//搜索范围在-16到16
bmy = x264_clip3( m->mvp[1]>>2, -16, 16 );
p_fref = &m->p_fref[bmy * m->i_fref + bmx];//获取参考帧(bmx, bmy)处的指针
bcost = h->pixf.sad[i_pixel]( m->p_img, m->i_img, p_fref, m->i_fref );//首先计算初始(bmx, bmy)处时,16x16宏块的sad值
/* try a candidate if provided */
if( m->b_mvc )//16x16宏块时,b_mvc=0
{
const int mx = m->mvc[0] >> 2;
const int my = m->mvc[1] >> 2;
uint8_t *p_fref2 = &m->p_fref[my*m->i_fref+mx];
int cost = h->pixf.sad[i_pixel]( m->p_img, m->i_img, p_fref2, m->i_fref ) +
m->lm * ( bs_size_se( m->mvc[0] - m->mvp[0] ) + bs_size_se( m->mvc[1] - m->mvp[1] ) );
if( cost < bcost )
{
bmx = mx;
bmy = my;
bcost = cost;
p_fref = p_fref2;
}
}
/* diamond */
for( ;; )//开始进行搜索,获取最佳匹配mv
{
int best = 0;
int cost[4];//用于记录上下左右像素位置的sad值
if( bmx < -16 ||bmx > 16 || bmy < -16 || bmy > 16)//搜索范围为[-16, 16]的正方形搜索范围,x264只搜索[-16, 16]正方形的范围
break;
#define COST_MV( c, dx, dy ) \
(c) = h->pixf.sad[i_pixel]( m->p_img, m->i_img, \
&p_fref[(dy)*m->i_fref+(dx)], m->i_fref ) + \
m->lm * ( bs_size_se(((bmx+(dx))<<2) - m->mvp[0] ) + \
bs_size_se(((bmy+(dy))<<2) - m->mvp[1] ) )//计算sad值,&p_fref[(dy)*m->i_fref+(dx)为最左上角像素
COST_MV( cost[0], 0, -1 );//计算以上侧像素为起始点的sad值
COST_MV( cost[1], 0, 1 );//计算以下侧像素为起始点的sad值
COST_MV( cost[2], -1, 0 );//计算以左侧像素为起始点的sad值
COST_MV( cost[3], 1, 0 );//计算以右侧像素为起始点的sad值
#undef COST_MV
/*得到最优sad值*/
if( cost[1] < cost[0] ) best = 1;
if( cost[2] < cost[best] ) best = 2;
if( cost[3] < cost[best] ) best = 3;
if( bcost <= cost[best] )//如果当前的mv比上下左右的sat都要优,得到最优mv,跳出循环
break;
bcost = cost[best];
if( best == 0 ) {//如果上侧像素为最优mv,那么继续向上搜索
bmy--;
p_fref -= m->i_fref;
} else if( best == 1 ) {//如果下侧像素为最优mv,那么继续向下搜索
bmy++;
p_fref += m->i_fref;
} else if( best == 2 ) {//如果左侧像素为最优mv,那么继续向左搜索
bmx--;
p_fref--;
} else if( best == 3 ) {//如果右侧像素为最优mv,那么继续向右搜索
bmx++;
p_fref++;
}
}
/* -> qpel mv */
m->mv[0] = bmx << 2;//转换为1/4像素单位
m->mv[1] = bmy << 2;
/* compute the real cost */
m->cost = h->pixf.satd[i_pixel]( m->p_img, m->i_img, p_fref, m->i_fref ) +
m->lm * ( bs_size_se( m->mv[0] - m->mvp[0] ) +
bs_size_se( m->mv[1] - m->mvp[1] ) );//计算得到最优sad值
}