先解释下,运动补偿和带权预测
1 运动补偿 ,h264编码器为了让宏块的运动预测更加精确,通过差值的方法,将像素最大差值到1/4像素的方式,生成新的宏块,从而减少运动残差。
2 带权预测,h264在做宏块差值的时候,首先以帧为单位,计算当前编码帧和被参考帧的平均luma(亮度),chroma(色度)的平均值,然后计算平均值比值,将这个比值作为权重,先将参考帧的被参考块做一次比值乘法运算,让其更接近当前编码宏块。举例来说:参考宏块平均值255,编码宏块平均值130,先将参考宏块乘以130/255,然后再和编码宏块做差值运算,这样得到的结果更接近,更容易压缩。
(1) 运动补偿代码流程
...先是做了宏块分析...找到了参考帧,计算好了mv
void x264_macroblock_encode( x264_t *h )
{//按照编码的像素格式的不同,用不同的函数完成宏编码
if( CHROMA444 )
macroblock_encode_internal( h, 3, 0 );
else if( CHROMA_FORMAT )
macroblock_encode_internal( h, 1, 1 );
else
macroblock_encode_internal( h, 1, 0 );
}
static ALWAYS_INLINE void macroblock_encode_internal( x264_t *h, int plane_count, int chroma )
{
int i_qp = h->mb.i_qp;
int b_decimate = h->mb.b_dct_decimate;
int b_force_no_skip = 0;
int nz;
h->mb.i_cbp_luma = 0;
for( int p = 0; p < plane_count; p++ )
h->mb.cache.non_zero_count[x264_scan8[LUMA_DC+p]] = 0;
if( h->mb.i_type == I_PCM )// PCM encoder
{
/* if PCM is chosen, we need to store reconstructed frame data */
for( int p = 0; p < plane_count; p++ )
h->mc.copy[PIXEL_16x16]( h->mb.pic.p_fdec[p], FDEC_STRIDE, h->mb.pic.p_fenc[p], FENC_STRIDE, 16 );
if( chroma )
{
int height = 16 >> CHROMA_V_SHIFT;
h->mc.copy[PIXEL_8x8] ( h->mb.pic.p_fdec[1], FDEC_STRIDE, h->mb.pic.p_fenc[1], FENC_STRIDE, height );
h->mc.copy[PIXEL_8x8] ( h->mb.pic.p_fdec[2], FDEC_STRIDE, h->mb.pic.p_fenc[2], FENC_STRIDE, height );
}
// 如果是PCM。则我们直接把fenc 存储到fdec重建帧中
return;
}
//上面PCM模式的忽略
for( int p = 0; p < plane_count; p++ )
h->mc.mc_luma( h->mb.pic.p_fdec[p], FDEC_STRIDE,
&h->mb.pic.p_fref[0][0][p*4], h->mb.pic.i_stride[p],
mvx, mvy, 16, 16, &h->sh.weight[0][p] );// 计算 luma 亮度mc
//编码第一步,先把参考宏块拷贝到重建帧宏块对应的位置里面
static void mc_luma( pixel *dst, intptr_t i_dst_stride,
pixel *src[4], intptr_t i_src_stride,
int mvx, int mvy,
int i_width, int i_height, const x264_weight_t *weight )
{
int qpel_idx = ((mvy&3)<<2) + (mvx&3);//计算mv以1/4像素为单位的偏移,图中黑色部分是真实的像素,框框都是插值出来的半像素,1/4个像素大小
int offset = (mvy>>2)*i_src_stride + (mvx>>2);
pixel *src1 = src[x264_hpel_ref0[qpel_idx]] + offset + ((mvy&3) == 3) * i_src_stride;
if( qpel_idx & 5 ) /* qpel interpolation needed 这里表示需要精确到1/4像素,因为前面做lookahead的时候已经做了1/2像素差值,这里要精确到1/4,所以需要再插值一次 */
{
pixel *src2 = src[x264_hpel_ref1[qpel_idx]] + offset + ((mvx&3) == 3);
pixel_avg( dst, i_dst_stride, src1, i_src_stride,
src2, i_src_stride, i_width, i_height );// 算 1/4 像素
if( weight->weightfn )
mc_weight( dst, i_dst_stride, dst, i_dst_stride, weight, i_width, i_height );
}
else if( weight->weightfn )
{
mc_weight( dst, i_dst_stride, src1, i_src_stride, weight, i_width, i_height );
}
else
{// 复制像素数据
mc_copy( src1, i_src_stride, dst, i_dst_stride, i_width, i_height );
}
}
//这是亮度参考宏块拷贝到重建宏块的过程
2 带权预测
mc_weight( dst, i_dst_stride, src1, i_src_stride, weight, i_width, i_height );
static void mc_weight( pixel *dst, intptr_t i_dst_stride, pixel *src, intptr_t i_src_stride,
const x264_weight_t *weight, int i_width, int i_height )
{
//加权预测,将参考块乘以个weight,然后再存入fdec 重建块中,然后再和编码块做差值,得到最终结果
int offset = weight->i_offset << (BIT_DEPTH-8);
int scale = weight->i_scale;//缩放系数
int denom = weight->i_denom;
if( denom >= 1 )
{
for( int y = 0; y < i_height; y++, dst += i_dst_stride, src += i_src_stride )
for( int x = 0; x < i_width; x++ )
opscale( x );
}
else
{
for( int y = 0; y < i_height; y++, dst += i_dst_stride, src += i_src_stride )
for( int x = 0; x < i_width; x++ )
opscale_noden( x );
}
}
x264_encoder_encode()-->
weighted_pred_init()-->
void x264_weight_scale_plane( x264_t *h, pixel *dst, intptr_t i_dst_stride, pixel *src, intptr_t i_src_stride,
int i_width, int i_height, x264_weight_t *w )// 计算权重w的两个系数
{
for( int i = 0; i < 3; i++ )//循环每一个plane 对应YUV
{
for( int j = 0; j < h->i_ref[0]; j++ )// 参考序列中的每一个参考帧都计算一次
{
x264_weight_scale_plane( h, dst, stride, src, stride, width, height, &h->sh.weight[j][0] );
void x264_weight_scale_plane( x264_t *h, pixel *dst, intptr_t i_dst_stride, pixel *src, intptr_t i_src_stride,
int i_width, int i_height, x264_weight_t *w )
{
/* Weight horizontal strips of height 16. This was found to be the optimal height
* in terms of the cache loads. */
while( i_height > 0 )
{
int x;
for( x = 0; x < i_width-8; x += 16 )
w->weightfn[16>>2]( dst+x, i_dst_stride, src+x, i_src_stride, w, X264_MIN( i_height, 16 ) );
if( x < i_width )
w->weightfn[ 8>>2]( dst+x, i_dst_stride, src+x, i_src_stride, w, X264_MIN( i_height, 16 ) );
i_height -= 16;
dst += 16 * i_dst_stride;
src += 16 * i_src_stride;
}
}
最终的weightfn用汇编实现的,具体代码后面再详细分析
}
}
typedef struct x264_weight_t
{
/* aligning the first member is a gcc hack to force the struct to be
* 16 byte aligned, as well as force sizeof(struct) to be a multiple of 16 */
ALIGNED_16( int16_t cachea[8] );
int16_t cacheb[8];
int32_t i_denom;
int32_t i_scale;
int32_t i_offset;
weight_fn_t *weightfn;
} ALIGNED_16( x264_weight_t );
带权预测运算过程
#define opscale(x) dst[x] = x264_clip_pixel( ((src[x] * scale + (1<<(denom - 1))) >> denom) + offset ) 这个计算公式暂时还没理解
#define opscale_noden(x) dst[x] = x264_clip_pixel( src[x] * scale + offset )
这里大致介绍了这两个东西的基本含义和流程,详细公式含义后续再研究。