/* Output buffers are separated by 128 bytes to avoid false sharing of cachelines
* in multithreaded lookahead. */
#define PAD_SIZE 32
/* cost_est, cost_est_aq, intra_mbs, num rows */
#define NUM_INTS 4
#define COST_EST 0
#define COST_EST_AQ 1
#define INTRA_MBS 2
#define NUM_ROWS 3
#define ROW_SATD (NUM_INTS + (h->mb.i_mb_y - h->i_threadslice_start))
// output_inter, output_intra 是各自指向的同一块
// 内存块的两个不同部分。每一部分由四部分构成
// cost_est, cost_est_aq, intra_mbs, num_rows
static void x264_slicetype_mb_cost( x264_t *h, x264_mb_analysis_t *a,
x264_frame_t **frames, int p0, int p1, int b,
int dist_scale_factor, int do_search[2], const x264_weight_t *w,
int *output_inter, int *output_intra )
{
// b 是带编码的帧的索引,
// p0是前向参考帧索引,
// p1是后向参考帧索引
x264_frame_t *fref0 = frames[p0];
x264_frame_t *fref1 = frames[p1];
x264_frame_t *fenc = frames[b];
// 是否待编码的帧作为B帧来编码,
// 如果预测编码的帧为B帧的编码代价, p1 != b, 且 b < p1
const int b_bidir = (b < p1);
// 获取预测宏块的 x, y 坐标(以16x16宏块为单位)
const int i_mb_x = h->mb.i_mb_x;
const int i_mb_y = h->mb.i_mb_y;
// 以16x16宏块为单位的跨度
const int i_mb_stride = h->mb.i_mb_width;
// 二维坐标转换为一维线性坐标
const int i_mb_xy = i_mb_x + i_mb_y * i_mb_stride;
// 待编码子像素平面的跨度(以像素为单位)
const int i_stride = fenc->i_stride_lowres;
// i_mb_x, i_mb_y 是宏块坐标(以16x16宏块为单位)
// lowres保存的是半尺寸的orig, H, V, HV像素插值,
// 即他们的尺寸只有原图像的一半
// i_pel_offset 就是计算的 i_mb_x, i_mb_y宏块
// 在lowres对应的首像素坐标
const int i_pel_offset = 8 * (i_mb_x + i_mb_y * i_stride);
// 双向预测权重
const int i_bipred_weight = h->param.analyse.b_weighted_bipred ? 64 - (dist_scale_factor>>2) : 32;
// int16_t (*lowres_mvs[2][X264_BFRAME_MAX+1])[2];
// b - p0 - 1 表示前向参考距
// p1 - b - 1 表示后向参考距
// 猜测, [0][b - p0 - 1] 表示前向参考帧距的mv(P帧)
// [1][p1 - b - 1] 表示后向参考帧距的mv (B帧)
int16_t (*fenc_mvs[2])[2] = { &fenc->lowres_mvs[0][b-p0-1][i_mb_xy], &fenc->lowres_mvs[1][p1-b-1][i_mb_xy] };
// int *lowres_mv_costs[2][X264_BFRAME_MAX+1];
// b - p0 - 1 表示前向参考距
// p1 - b - 1 表示后向参考距
// 猜测, [0][b - p0 - 1] 表示前向参考帧距的mv代价(P帧)
// [1][p1 - b - 1] 表示后向参考帧距的mv代价 (B帧)
int (*fenc_costs[2]) = { &fenc->lowres_mv_costs[0][b-p0-1][i_mb_xy], &fenc->lowres_mv_costs[1][p1-b-1][i_mb_xy] };
// 根据i_mb_x, i_mb_y的坐标来确定是否宏块积分
int b_frame_score_mb = (i_mb_x > 0 && i_mb_x < h->mb.i_mb_width - 1 &&
i_mb_y > 0 && i_mb_y < h->mb.i_mb_height - 1) ||
h->mb.i_mb_width <= 2 || h->mb.i_mb_height <= 2;
// 声明变量 pixel pixl[9*32];
ALIGNED_ARRAY_16( pixel, pix1,[9*FDEC_STRIDE] );
// pix2 = pix1 + 8; 即偏移8个像素
pixel *pix2 = pix1+8;
// 声明两个预测结构体变量
x264_me_t m[2];
// 初始化i_bcost = COST_MAX
// list_used = 0;
int i_bcost = COST_MAX;
int list_used = 0;
/* A small, arbitrary bias to avoid VBV problems caused by zero-residual lookahead blocks. */
int lowres_penalty = 4;
// 定位Y平面
h->mb.pic.p_fenc[0] = h->mb.pic.fenc_buf;
// MC_COPY( 8 )
// h->mc.copy[PIXEL_8X8] = mc_copy_w8
// #define MC_COPY(W) \
// static void mc_copy_w##W( pixel *dst, intptr_t i_dst, pixel *src, intptr_t i_src, int i_height ) \
// { \
// mc_copy( src, i_src, dst, i_dst, W, i_height ); \
// }
// static void mc_copy( pixel *src, intptr_t i_src_stride, pixel *dst, intptr_t i_dst_stride, int i_width, int i_height )
// {
// for( int y = 0; y < i_height; y++ )
// {
// memcpy( dst, src, i_width * sizeof(pixel) );
// src += i_src_stride;
// dst += i_dst_stride;
// }
// }
// fenc->lowres[0], [1],[2],[3]分别对应整像素,
// 水平1/2像素, 垂直1/2像素, 斜对角1/2像素平面
// 将lowres orig 对应i_mb_x, i_mb_y宏块
// 的宏块像素拷贝到 h->mb.pic.p_fenc[0],
// 是一个8x8像素块的拷贝
h->mc.copy[PIXEL_8x8]( h->mb.pic.p_fenc[0], FENC_STRIDE, &fenc->lowres[0][i_pel_offset], i_stride, 8 );
// 如果p0 == p1, 那么待编码帧将被作I帧来编码
if( p0 == p1 )
goto lowres_intra_mb;
// no need for h->mb.mv_min[]
// 设定一些限制
h->mb.mv_limit_fpel[0][0] = -8*h->mb.i_mb_x - 4;
h->mb.mv_limit_fpel[1][0] = 8*( h->mb.i_mb_width - h->mb.i_mb_x - 1 ) + 4;
h->mb.mv_min_spel[0] = 4*( h->mb.mv_limit_fpel[0][0] - 8 );
h->mb.mv_max_spel[0] = 4*( h->mb.mv_limit_fpel[1][0] + 8 );
if( h->mb.i_mb_x >= h->mb.i_mb_width - 2 )
{
h->mb.mv_limit_fpel[0][1] = -8*h->mb.i_mb_y - 4;
h->mb.mv_limit_fpel[1][1] = 8*( h->mb.i_mb_height - h->mb.i_mb_y - 1 ) + 4;
h->mb.mv_min_spel[1] = 4*( h->mb.mv_limit_fpel[0][1] - 8 );
h->mb.mv_max_spel[1] = 4*( h->mb.mv_limit_fpel[1][1] + 8 );
}
#define LOAD_HPELS_LUMA(dst, src) \
{ \
(dst)[0] = &(src)[0][i_pel_offset]; \
(dst)[1] = &(src)[1][i_pel_offset]; \
(dst)[2] = &(src)[2][i_pel_offset]; \
(dst)[3] = &(src)[3][i_pel_offset]; \
}
#define LOAD_WPELS_LUMA(dst,src) \
(dst) = &(src)[i_pel_offset];
#define CLIP_MV( mv ) \
{ \
mv[0] = x264_clip3( mv[0], h->mb.mv_min_spel[0], h->mb.mv_max_spel[0] ); \
mv[1] = x264_clip3( mv[1], h->mb.mv_min_spel[1], h->mb.mv_max_spel[1] ); \
}
#define TRY_BIDIR( mv0, mv1, penalty ) \
{ \
int i_cost; \
if( h->param.analyse.i_subpel_refine <= 1 ) \
{ \
int hpel_idx1 = (((mv0)[0]&2)>>1) + ((mv0)[1]&2); \
int hpel_idx2 = (((mv1)[0]&2)>>1) + ((mv1)[1]&2); \
pixel *src1 = m[0].p_fref[hpel_idx1] + ((mv0)[0]>>2) + ((mv0)[1]>>2) * m[0].i_stride[0]; \
pixel *src2 = m[1].p_fref[hpel_idx2] + ((mv1)[0]>>2) + ((mv1)[1]>>2) * m[1].i_stride[0]; \
h->mc.avg[PIXEL_8x8]( pix1, 16, src1, m[0].i_stride[0], src2, m[1].i_stride[0], i_bipred_weight ); \
} \
else \
{ \
intptr_t stride1 = 16, stride2 = 16; \
pixel *src1, *src2; \
// h->mc.get_ref = get_ref
// 1/4像素预插值
// 依据mv0得到插值平面src1,
// 依据mv1得到插值平面src2
src1 = h->mc.get_ref( pix1, &stride1, m[0].p_fref, m[0].i_stride[0], \
(mv0)[0], (mv0)[1], 8, 8, w ); \
src2 = h->mc.get_ref( pix2, &stride2, m[1].p_fref, m[1].i_stride[0], \
(mv1)[0], (mv1)[1], 8, 8, w ); \
// h->mc.avg[PIXEL_8X8] = pixel_avg_8x8
// 进行插值,并将插值结果保存在pix1
h->mc.avg[PIXEL_8x8]( pix1, 16, src1, stride1, src2, stride2, i_bipred_weight ); \
} \
// h->pixf.mbcmp[PIXEL_8X8] = x264_pixel_satd_8x8
// 求m[0].p_fenc[0] 与插值宏块之间的satd
i_cost = penalty * a->i_lambda + h->pixf.mbcmp[PIXEL_8x8]( \
m[0].p_fenc[0], FENC_STRIDE, pix1, 16 ); \
// 如果代价更小,则替换i_bcost, list_used
COPY2_IF_LT( i_bcost, i_cost, list_used, 3 ); \
}
m[0].i_pixel = PIXEL_8x8;
m[0].p_cost_mv = a->p_cost_mv;
m[0].i_stride[0] = i_stride;
m[0].p_fenc[0] = h->mb.pic.p_fenc[0];
m[0].weight = w;
m[0].i_ref = 0;
// 将前向参考帧的lowres orig, h, v, hv平面
// 对应i_mb_x, i_mb_y宏块的lowres宏块地址
// 分别赋给m[0].p_fref[0], [1], [2], [3]
LOAD_HPELS_LUMA( m[0].p_fref, fref0->lowres );
// 将上述orig 对应到i_mb_x, i_mb_y宏块的地址
// 赋给 p_fref_w
m[0].p_fref_w = m[0].p_fref[0];
// 如果w[0].weightfn不为0, 则将fenc->weighted[0]
// 对应i_mb_x, i_mb_y宏块的地址赋值给p_fref_w
if( w[0].weightfn )
LOAD_WPELS_LUMA( m[0].p_fref_w, fenc->weighted[0] );
if( b_bidir )
{
// mvr 指向前后向参考帧距的宏块(i_mb_xy)的运动向量
int16_t *mvr = fref1->lowres_mvs[0][p1-p0-1][i_mb_xy];
// 声明 int16_t dmv[2][2];
ALIGNED_ARRAY_8( int16_t, dmv,[2],[2] );
m[1].i_pixel = PIXEL_8x8;
m[1].p_cost_mv = a->p_cost_mv;
m[1].i_stride[0] = i_stride;
m[1].p_fenc[0] = h->mb.pic.p_fenc[0];
m[1].i_ref = 0;
m[1].weight = x264_weight_none;
// 将后向参考帧的lowres orig, h, v, hv平面
// 对应i_mb_x, i_mb_y宏块的lowres宏块地址
// 分别赋给m[0].p_fref[0], [1], [2], [3]
LOAD_HPELS_LUMA( m[1].p_fref, fref1->lowres );
// 将m[1].pfref[0], 即lowres orig对应
// i_mb_xy宏块在lowres orig地址
// 赋值给m[1].p_fref_w
m[1].p_fref_w = m[1].p_fref[0];
// dmv 表示直接预测
// 直接预测就是采用对应参考图像宏块的mv,
// 然后根据图像的poc距离得到的
// dist_scale_factor = ( ((b-p0) << 8) + ((p1-p0) >> 1) ) / (p1-p0);
dmv[0][0] = ( mvr[0] * dist_scale_factor + 128 ) >> 8;
dmv[0][1] = ( mvr[1] * dist_scale_factor + 128 ) >> 8;
dmv[1][0] = dmv[0][0] - mvr[0];
dmv[1][1] = dmv[0][1] - mvr[1];
CLIP_MV( dmv[0] );
CLIP_MV( dmv[1] );
if( h->param.analyse.i_subpel_refine <= 1 )
M64( dmv ) &= ~0x0001000100010001ULL; /* mv & ~1 */
TRY_BIDIR( dmv[0], dmv[1], 0 );
if( M64( dmv ) )
{
int i_cost;
// 在前向参考宏块, 和后向参考宏块之间进行插值
// h->mc.avg[PIXEL_8X8] = pixel_avg_8x8
// 进行插值,并将插值结果保存在pix1
h->mc.avg[PIXEL_8x8]( pix1, 16, m[0].p_fref[0], m[0].i_stride[0], m[1].p_fref[0], m[1].i_stride[0], i_bipred_weight );
// h->pixf.mbcmp[PIXEL_8X8] = x264_pixel_satd_8x8
// 求m[0].p_fenc[0] 与插值宏块之间的satd
i_cost = h->pixf.mbcmp[PIXEL_8x8]( m[0].p_fenc[0], FENC_STRIDE, pix1, 16 );
COPY2_IF_LT( i_bcost, i_cost, list_used, 3 );
}
}
// do_search[0] 前向参考搜索
// do_search[1] 后向参考搜索
for( int l = 0; l < 1 + b_bidir; l++ )
{
if( do_search[l] )
{
int i_mvc = 0;
int16_t (*fenc_mv)[2] = fenc_mvs[l];
ALIGNED_4( int16_t mvc[4][2] );
/* Reverse-order MV prediction. */
M32( mvc[0] ) = 0;
M32( mvc[2] ) = 0;
// 下面这段代码, 我猜是取两端的mv, 然后取中值
#define MVC(mv) { CP32( mvc[i_mvc], mv ); i_mvc++; }
if( i_mb_x < h->mb.i_mb_width - 1 )
MVC( fenc_mv[1] );
if( i_mb_y < h->i_threadslice_end - 1 )
{
MVC( fenc_mv[i_mb_stride] );
if( i_mb_x > 0 )
MVC( fenc_mv[i_mb_stride-1] );
if( i_mb_x < h->mb.i_mb_width - 1 )
MVC( fenc_mv[i_mb_stride+1] );
}
#undef MVC
if( i_mvc <= 1 )
CP32( m[l].mvp, mvc[0] );
else
x264_median_mv( m[l].mvp, mvc[0], mvc[1], mvc[2] );
/* Fast skip for cases of near-zero residual. Shortcut: don't bother except in the mv0 case,
* since anything else is likely to have enough residual to not trigger the skip. */
if( !M32( m[l].mvp ) )
{
// h->pixf.mbcmp[PIXEL_8x8] = x264_pixel_satd_8x8
// 求(lowres)编码帧与其前向参考帧(lowres)之间的8x8宏块的satd
m[l].cost = h->pixf.mbcmp[PIXEL_8x8]( m[l].p_fenc[0], FENC_STRIDE, m[l].p_fref[0], m[l].i_stride[0] );
if( m[l].cost < 64 )
{
M32( m[l].mv ) = 0;
goto skip_motionest;
}
}
x264_me_search( h, &m[l], mvc, i_mvc );
m[l].cost -= a->p_cost_mv[0]; // remove mvcost from skip mbs
if( M32( m[l].mv ) )
m[l].cost += 5 * a->i_lambda;
skip_motionest:
CP32( fenc_mvs[l], m[l].mv );
*fenc_costs[l] = m[l].cost;
}
else
{
CP32( m[l].mv, fenc_mvs[l] );
m[l].cost = *fenc_costs[l];
}
COPY2_IF_LT( i_bcost, m[l].cost, list_used, l+1 );
}
if( b_bidir && ( M32( m[0].mv ) || M32( m[1].mv ) ) )
TRY_BIDIR( m[0].mv, m[1].mv, 5 );
lowres_intra_mb:
if( !fenc->b_intra_calculated )
{
// pixel edge[36];
ALIGNED_ARRAY_16( pixel, edge,[36] );
// pix 指向 ???
pixel *pix = &pix1[8+FDEC_STRIDE];
// 源指向fenc->lowres[0][i_pel_offset];
pixel *src = &fenc->lowres[0][i_pel_offset];
// intra惩罚指数
const int intra_penalty = 5 * a->i_lambda;
int satds[3];
int pixoff = 4 / sizeof(pixel);
/* Avoid store forwarding stalls by writing larger chunks */
// 拷贝宏块的左,顶, 顶右像素
memcpy( pix-FDEC_STRIDE, src-i_stride, 16 * sizeof(pixel) );
for( int i = -1; i < 8; i++ )
M32( &pix[i*FDEC_STRIDE-pixoff] ) = M32( &src[i*i_stride-pixoff] );
// 为什么调用的是色度预测???
// h->pixf.intra_mbcmp_x3_8x8c = x264_intra_satd_x3_8x8c
// INTRA_MBCMP(satd, 8x8, dc, h, v, c,, _c )
// #define INTRA_MBCMP( mbcmp, size, pred1, pred2, pred3, chroma, cpu, cpu2 )\
// void x264_intra_##mbcmp##_x3_##size##chroma##cpu( pixel *fenc, pixel *fdec, int res[3] )\
// {\
// x264_predict_##size##chroma##_##pred1##cpu2( fdec );\
// res[0] = x264_pixel_##mbcmp##_##size##cpu( fdec, FDEC_STRIDE, fenc, FENC_STRIDE );\
// x264_predict_##size##chroma##_##pred2##cpu2( fdec );\
// res[1] = x264_pixel_##mbcmp##_##size##cpu( fdec, FDEC_STRIDE, fenc, FENC_STRIDE );\
// x264_predict_##size##chroma##_##pred3##cpu2( fdec );\
// res[2] = x264_pixel_##mbcmp##_##size##cpu( fdec, FDEC_STRIDE, fenc, FENC_STRIDE );\
// }
// 作三次不同的预测, 并与待编码帧进行预测求satd,
// 将三个satd的结果保存在satds数组中
h->pixf.intra_mbcmp_x3_8x8c( h->mb.pic.p_fenc[0], pix, satds );
// 求其中最小的cost
int i_icost = X264_MIN3( satds[0], satds[1], satds[2] );
if( h->param.analyse.i_subpel_refine > 1 )
{
// h->predict_8x8c[I_PRED_CHROMA] = x264_predict_8x8c_p_c
// 预测色度, 色度平面预测法(4:2:0)
h->predict_8x8c[I_PRED_CHROMA_P]( pix );
// h->pixf.mbcmp[PIXEL_8x8] = x264_pixel_satd_8x8
int satd = h->pixf.mbcmp[PIXEL_8x8]( pix, FDEC_STRIDE, h->mb.pic.p_fenc[0], FENC_STRIDE );
// 取小者
i_icost = X264_MIN( i_icost, satd );
// h->predict_8x8_filter = x264_predict_8x8_filter_c
// 从源宏块抽取出边l0,...,l7; lt, t0,...,t15
// 并保存在edge
h->predict_8x8_filter( pix, edge, ALL_NEIGHBORS, ALL_NEIGHBORS );
for( int i = 3; i < 9; i++ )
{ // h->predict_8x8[i] = x264_predict_8x8_ddl_c 等
// 用其他方法进行预测
h->predict_8x8[i]( pix, edge );
// h->pixf.mbcmp[PIXEL_8x8] = x264_pixel_satd_8x8
// 求satd
satd = h->pixf.mbcmp[PIXEL_8x8]( pix, FDEC_STRIDE, h->mb.pic.p_fenc[0], FENC_STRIDE );
取小者
i_icost = X264_MIN( i_icost, satd );
}
}
i_icost += intra_penalty + lowres_penalty;
// 保存当前宏块的i_icost
fenc->i_intra_cost[i_mb_xy] = i_icost;
int i_icost_aq = i_icost;
if( h->param.rc.i_aq_mode ) // 调整i_icost_aq
i_icost_aq = (i_icost_aq * fenc->i_inv_qscale_factor[i_mb_xy] + 128) >> 8;
// 保存到output_intra内存块
output_intra[ROW_SATD] += i_icost_aq;
if( b_frame_score_mb )
{
// 保存到est, est_aq
output_intra[COST_EST] += i_icost;
output_intra[COST_EST_AQ] += i_icost_aq;
}
}
i_bcost += lowres_penalty;
/* forbid intra-mbs in B-frames, because it's rare and not worth checking */
/* FIXME: Should we still forbid them now that we cache intra scores? */
if( !b_bidir )
{
int i_icost = fenc->i_intra_cost[i_mb_xy];
int b_intra = i_icost < i_bcost;
if( b_intra )
{
i_bcost = i_icost;
list_used = 0;
}
// 统计有多少个intra_mb 编码模式
if( b_frame_score_mb )
output_inter[INTRA_MBS] += b_intra;
}
/* In an I-frame, we've already added the results above in the intra section. */
// 统计非I帧数据
if( p0 != p1 )
{
int i_bcost_aq = i_bcost;
if( h->param.rc.i_aq_mode )
i_bcost_aq = (i_bcost_aq * fenc->i_inv_qscale_factor[i_mb_xy] + 128) >> 8;
output_inter[ROW_SATD] += i_bcost_aq;
if( b_frame_score_mb )
{
/* Don't use AQ-weighted costs for slicetype decision, only for ratecontrol. */
output_inter[COST_EST] += i_bcost;
output_inter[COST_EST_AQ] += i_bcost_aq;
}
}
// 保存最小代价
fenc->lowres_costs[b-p0][p1-b][i_mb_xy] = X264_MIN( i_bcost, LOWRES_COST_MASK ) + (list_used << LOWRES_COST_SHIFT);
}