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值 }