x264_slicetype_analyse对一个lookahead队列的帧类型进行分析,通过算法来决定每帧的类型,源码如下:
void x264_slicetype_analyse( x264_t *h, int intra_minigop )
{
x264_mb_analysis_t a;
x264_frame_t *frames[X264_LOOKAHEAD_MAX+3] = { NULL, };
int num_frames, orig_num_frames, keyint_limit, framecnt;
int i_max_search = X264_MIN( h->lookahead->next.i_size, X264_LOOKAHEAD_MAX );
int vbv_lookahead = h->param.rc.i_vbv_buffer_size && h->param.rc.i_lookahead;
/* For determinism we should limit the search to the number of frames lookahead has for sure
* in h->lookahead->next.list buffer, except at the end of stream.
* For normal calls with (intra_minigop == 0) that is h->lookahead->i_slicetype_length + 1 frames.
* And for I-frame calls (intra_minigop != 0) we already removed intra_minigop frames from there. */
if( h->param.b_deterministic )
i_max_search = X264_MIN( i_max_search, h->lookahead->i_slicetype_length + 1 - intra_minigop );
int keyframe = !!intra_minigop;
assert( h->frames.b_have_lowres );
if( !h->lookahead->last_nonb ) //上一个非b帧,第一次是return的
return;
frames[0] = h->lookahead->last_nonb; //frames[0]指向上一次的非B帧
for( framecnt = 0; framecnt < i_max_search; framecnt++ ) {
printf("next.list:%d. \n", h->lookahead->next.list[framecnt]->i_type);
frames[framecnt+1] = h->lookahead->next.list[framecnt]; //frames[]依次指向 lookahead->next链表中的帧
}
x264_lowres_context_init( h, &a ); //初始化一些量化系数等
if( !framecnt ) //除非全是关键帧,才会进来,因为当framecnt为0,说明上面的循环没有进行,即i_max_search==0,没有帧间编码,全是关键帧
{
if( h->param.rc.b_mb_tree ) //对关键帧进行mb-tree算法分析
x264_macroblock_tree( h, &a, frames, 0, keyframe );
return;
}
keyint_limit = h->param.i_keyint_max - frames[0]->i_frame + h->lookahead->i_last_keyframe - 1;
orig_num_frames = num_frames = h->param.b_intra_refresh ? framecnt : X264_MIN( framecnt, keyint_limit );
/* This is important psy-wise: if we have a non-scenecut keyframe,
* there will be significant visual artifacts if the frames just before
* go down in quality due to being referenced less, despite it being
* more RD-optimal. */
//这个不是很明白:这在心理视觉优化指导中很重要:如果我们有一个非场景切换的关键帧,如果这些帧因被很少参考,这将对视觉产生重大影响,即使它经过了更多的RD优化?
if( (h->param.analyse.b_psy && h->param.rc.b_mb_tree) || vbv_lookahead )
num_frames = framecnt;
else if( h->param.b_open_gop && num_frames < framecnt )
num_frames++;
else if( num_frames == 0 )
{
frames[1]->i_type = X264_TYPE_I;
return;
}
if( IS_X264_TYPE_AUTO_OR_I( frames[1]->i_type ) &&
h->param.i_scenecut_threshold && scenecut( h, &a, frames, 0, 1, 1, orig_num_frames, i_max_search ) )
{
if( frames[1]->i_type == X264_TYPE_AUTO ) //如果是场景切换,则当做I帧
frames[1]->i_type = X264_TYPE_I;
return;
}
/* Replace forced keyframes with I/IDR-frames */
for( int j = 1; j <= num_frames; j++ )
{
if( frames[j]->i_type == X264_TYPE_KEYFRAME )
frames[j]->i_type = h->param.b_open_gop ? X264_TYPE_I : X264_TYPE_IDR;
}
/* Close GOP at IDR-frames */
for( int j = 2; j <= num_frames; j++ )
{
if( frames[j]->i_type == X264_TYPE_IDR && IS_X264_TYPE_AUTO_OR_B( frames[j-1]->i_type ) )
frames[j-1]->i_type = X264_TYPE_P;
}
int num_analysed_frames = num_frames;
int reset_start;
if( h->param.i_bframe )
{
if( h->param.i_bframe_adaptive == X264_B_ADAPT_TRELLIS ) //暂不分析此算法
{
if( num_frames > 1 )
{
char best_paths[X264_BFRAME_MAX+1][X264_LOOKAHEAD_MAX+1] = {"","P"};
int best_path_index = num_frames % (X264_BFRAME_MAX+1);
/* Perform the frametype analysis. */
for( int j = 2; j <= num_frames; j++ )
x264_slicetype_path( h, &a, frames, j, best_paths );
/* Load the results of the analysis into the frame types. */
for( int j = 1; j < num_frames; j++ )
{
if( best_paths[best_path_index][j-1] != 'B' )
{
if( IS_X264_TYPE_AUTO_OR_B( frames[j]->i_type ) )
frames[j]->i_type = X264_TYPE_P;
}
else
{
if( frames[j]->i_type == X264_TYPE_AUTO )
frames[j]->i_type = X264_TYPE_B;
}
}
}
}
else if( h->param.i_bframe_adaptive == X264_B_ADAPT_FAST ) //B帧快速匹配算法
{
int last_nonb = 0;
int num_bframes = h->param.i_bframe;
char path[X264_LOOKAHEAD_MAX+1];
for( int j = 1; j < num_frames; j++ )
{
if( j-1 > 0 && IS_X264_TYPE_B( frames[j-1]->i_type ) ) //
num_bframes--;
else
{
last_nonb = j-1;
num_bframes = h->param.i_bframe;
}
if( !num_bframes ) //如果连续B帧的数量超过最大值,则该帧要设为P帧
{
if( IS_X264_TYPE_AUTO_OR_B( frames[j]->i_type ) )
frames[j]->i_type = X264_TYPE_P;
continue;
}
if( frames[j]->i_type != X264_TYPE_AUTO ) // 已经判断过的就跳过
continue;
if( IS_X264_TYPE_B( frames[j+1]->i_type ) ) //j帧还未确定,j+1帧什么时候确定的?
{
frames[j]->i_type = X264_TYPE_P;
continue;
}
int bframes = j - last_nonb - 1;
memset( path, 'B', bframes );
strcpy( path+bframes, "PP" ); //假设接下来两帧都是P帧,计算帧间损耗
int cost_p = x264_slicetype_path_cost( h, &a, frames+last_nonb, path, COST_MAX );
strcpy( path+bframes, "BP" ); //假设接下来是一个B帧一个P帧,计算帧间损耗
int cost_b = x264_slicetype_path_cost( h, &a, frames+last_nonb, path, cost_p );
if( cost_b < cost_p ) //以损耗大小决定是B帧还是P帧
frames[j]->i_type = X264_TYPE_B;
else
frames[j]->i_type = X264_TYPE_P;
}
}
else
{
int num_bframes = h->param.i_bframe;
for( int j = 1; j < num_frames; j++ )
{
if( !num_bframes )
{
if( IS_X264_TYPE_AUTO_OR_B( frames[j]->i_type ) )
frames[j]->i_type = X264_TYPE_P;
}
else if( frames[j]->i_type == X264_TYPE_AUTO )
{
if( IS_X264_TYPE_B( frames[j+1]->i_type ) )
frames[j]->i_type = X264_TYPE_P;
else
frames[j]->i_type = X264_TYPE_B;
}
if( IS_X264_TYPE_B( frames[j]->i_type ) )
num_bframes--;
else
num_bframes = h->param.i_bframe;
}
}
if( IS_X264_TYPE_AUTO_OR_B( frames[num_frames]->i_type ) )
frames[num_frames]->i_type = X264_TYPE_P;
int num_bframes = 0;
while( num_bframes < num_frames && IS_X264_TYPE_B( frames[num_bframes+1]->i_type ) ) {
num_bframes++; //计算连续B帧的shu'l
}
/* Check scenecut on the first minigop. */
for( int j = 1; j < num_bframes+1; j++ )
{
if( frames[j]->i_forced_type == X264_TYPE_AUTO && IS_X264_TYPE_AUTO_OR_I( frames[j+1]->i_forced_type ) &&
h->param.i_scenecut_threshold && scenecut( h, &a, frames, j, j+1, 0, orig_num_frames, i_max_search ) )
{
frames[j]->i_type = X264_TYPE_P;
num_analysed_frames = j;
break;
}
}
reset_start = keyframe ? 1 : X264_MIN( num_bframes+2, num_analysed_frames+1 );
}
else
{
for( int j = 1; j <= num_frames; j++ )
if( IS_X264_TYPE_AUTO_OR_B( frames[j]->i_type ) )
frames[j]->i_type = X264_TYPE_P;
reset_start = !keyframe + 1;
}
/* Perform the actual macroblock tree analysis.
* Don't go farther than the maximum keyframe interval; this helps in short GOPs. */
if( h->param.rc.b_mb_tree ) //mb-tree分析
x264_macroblock_tree( h, &a, frames, X264_MIN(num_frames, h->param.i_keyint_max), keyframe );
/* Enforce keyframe limit. */
if( !h->param.b_intra_refresh ) //最大关键帧限制的处理? 还不太清楚...
{
int last_keyframe = h->lookahead->i_last_keyframe;
int last_possible = 0;
for( int j = 1; j <= num_frames; j++ )
{
x264_frame_t *frm = frames[j];
int keyframe_dist = frm->i_frame - last_keyframe;
if( IS_X264_TYPE_AUTO_OR_I( frm->i_forced_type ) )
{
if( h->param.b_open_gop || !IS_X264_TYPE_B( frames[j-1]->i_forced_type ) )
last_possible = j;
}
if( keyframe_dist >= h->param.i_keyint_max )
{
if( last_possible != 0 && last_possible != j )
{
j = last_possible;
frm = frames[j];
keyframe_dist = frm->i_frame - last_keyframe;
}
last_possible = 0;
if( frm->i_type != X264_TYPE_IDR )
frm->i_type = h->param.b_open_gop ? X264_TYPE_I : X264_TYPE_IDR;
}
if( frm->i_type == X264_TYPE_I && keyframe_dist >= h->param.i_keyint_min )
{
if( h->param.b_open_gop )
{
last_keyframe = frm->i_frame;
if( h->param.b_bluray_compat )
{
// Use bluray order
int bframes = 0;
while( bframes < j-1 && IS_X264_TYPE_B( frames[j-1-bframes]->i_type ) )
bframes++;
last_keyframe -= bframes;
}
}
else if( frm->i_forced_type != X264_TYPE_I )
frm->i_type = X264_TYPE_IDR;
}
if( frm->i_type == X264_TYPE_IDR )
{
last_keyframe = frm->i_frame;
if( j > 1 && IS_X264_TYPE_B( frames[j-1]->i_type ) )
frames[j-1]->i_type = X264_TYPE_P;
}
}
}
if( vbv_lookahead )
x264_vbv_lookahead( h, &a, frames, num_frames, keyframe );
/* Restore frametypes for all frames that haven't actually been decided yet. */
for( int j = reset_start; j <= num_frames; j++ )
frames[j]->i_type = frames[j]->i_forced_type;
#if HAVE_OPENCL
x264_opencl_slicetype_end( h );
#endif
}
虽然代码很长,但是逻辑还是比较清晰,大概有如下几步:
1.首先判断h->lookahead->last_nonb,即上一个非B帧是否存在,如不存在,则退出;
2.h->lookahead->next.list队列中的帧依次放到frames队列,如下图:
analyse00.png
3.判断frames[1]是否是场景切换,如果是,则把它设为I帧,并退出;
4.根据h->param.i_bframe_adaptive参数选择算法,决定后续每帧的类型,这里以X264_B_ADAPT_FAST为例进行说明:再贴一下代码:
else if( h->param.i_bframe_adaptive == X264_B_ADAPT_FAST )
{
int last_nonb = 0;
int num_bframes = h->param.i_bframe;
char path[X264_LOOKAHEAD_MAX+1];
for( int j = 1; j < num_frames; j++ )
{
if( j-1 > 0 && IS_X264_TYPE_B( frames[j-1]->i_type ) ) //
num_bframes--;
else
{
last_nonb = j-1;
num_bframes = h->param.i_bframe;
}
if( !num_bframes ) //如果连续B帧的数量超过最大值,则该帧要设为P帧
{
if( IS_X264_TYPE_AUTO_OR_B( frames[j]->i_type ) )
frames[j]->i_type = X264_TYPE_P;
continue;
}
if( frames[j]->i_type != X264_TYPE_AUTO ) // 已经判断过的就跳过
continue;
if( IS_X264_TYPE_B( frames[j+1]->i_type ) ) //j帧还未确定,j+1帧什么时候确定的?
{
frames[j]->i_type = X264_TYPE_P;
continue;
}
int bframes = j - last_nonb - 1;
memset( path, 'B', bframes );
strcpy( path+bframes, "PP" ); //假设接下来两帧都是P帧,计算他们的帧间损耗
int cost_p = x264_slicetype_path_cost( h, &a, frames+last_nonb, path, COST_MAX );
strcpy( path+bframes, "BP" ); //假设接下来一个B帧一个P帧,计算他们的帧间损耗
int cost_b = x264_slicetype_path_cost( h, &a, frames+last_nonb, path, cost_p );
if( cost_b < cost_p ) //按损耗大小来决定帧类型
frames[j]->i_type = X264_TYPE_B;
else
frames[j]->i_type = X264_TYPE_P;
}
}
如代码所示,X264_B_ADAPT_FAST算法循环遍历frames队列,每次假设连续两帧的类型为PP,或者BP,然后计算相应假设的帧间损耗,如果PP时的损耗大于BP,也就是说,接下来的一帧选择为B帧,总损耗会更小,应该把该帧预设为B帧。反之亦然。注意还有个变量h->param.i_bframe,它表示一个非B帧后面最多可以有多少个连续的B帧,当连续B帧数量达到该值时,下一帧必须要设为P帧。举个例子辅助理解,假如frames队列数量为10,i_bframe为3,所有的B帧损耗都小于P帧,则结果大概是下图的样子:
analyse01.png
5.还有别的一些处理,如mb-tree算法分析,最大关键帧间隔参数的处理等,有些我也不太明白,有机会再补充吧。
网友评论