美文网首页
(二十)x264_slicetype_analyse函数

(二十)x264_slicetype_analyse函数

作者: 奔向火星005 | 来源:发表于2018-09-22 14:25 被阅读0次

    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算法分析,最大关键帧间隔参数的处理等,有些我也不太明白,有机会再补充吧。

    相关文章

      网友评论

          本文标题:(二十)x264_slicetype_analyse函数

          本文链接:https://www.haomeiwen.com/subject/dofxoftx.html