(二十)x264_slicetype_analyse函数

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队列,如下图:


(二十)x264_slicetype_analyse函数_第1张图片
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帧,则结果大概是下图的样子:


(二十)x264_slicetype_analyse函数_第2张图片
analyse01.png

5.还有别的一些处理,如mb-tree算法分析,最大关键帧间隔参数的处理等,有些我也不太明白,有机会再补充吧。

你可能感兴趣的:((二十)x264_slicetype_analyse函数)