x264代码剖析(十三):核心算法之帧间预测函数x264_mb_analyse_inter_*()

x264代码剖析(十三):核心算法之帧间预测函数x264_mb_analyse_inter_*()

 

        帧间预测是指利用视频时间域相关性,使用临近已编码图像像素预测当前图像的像素,以达到有效去除视频时域冗余的目的。由于视频序列通常包括较强的时域相关性,因此预测残差值接近于0,将残差信号作为后续模块的输入进行变换、量化、扫描及熵编码,可实现对视频信号的高效压缩。

 

        本文将重点讨论基本档次支持的P片帧间预测工具以及主要和扩展档次支持的B片和加权预测等帧间预测工具,最后分析了帧间预测函数的主要功能。

 

1、可变尺寸块运动补偿

 

        在H.264中,每个16*16大小的宏块可以有4种分割方式:一个16*16,两个16*8,两个8*16,四个8*8,其运动补偿也有相应的四种,如下图所示。

        而8*8模式的每个子宏块还可以进一步用4种方式再进行分割:一个8*8,两个8*4,两个4*8,四个4*4,其运动补偿也有相应的四种,如下图所示。

        这些分割和宏块大大提高了各宏块之间的关联性,这种分割下的运动补偿称为树状结构运动补偿。每个分割或子宏块都有一个独立的运动补偿,每个运动矢量MV必须被编码和传输,分割的选择也需编码到压缩比特流中。对于大的分割尺寸而言,MV选择和分割类型只需要少量的比特来表征,但是运动补偿残差在多细节区域能量将非常高;对于小的尺寸而言,分割运动补偿残差能量低,但是需要较多的比特来表征MV和分割选择,分割尺寸的选择影响了压缩性能。整体而言,大的分割尺寸适合于平坦区域,而小的尺寸适合于多细节区域。

 

2、运动矢量

 

        帧间编码宏块的每个分割或者子宏块都是从参考图像的某一相同尺寸区域预测而得。两者之间的差异(MV),对于亮度成分采用1/4像素精度,而对于色度成分则采用1/8像素精度。亚像素位置的亮度和色度像素并不存在于参考图像中,需利用临近已编码点进行内插而得。如果MV的垂直和水平分量为整数,则参考块相应像素实际存在,如果其中一个或两个为分数,则预测像素要通过参考帧中相应像素内插获得。

 

3、MV预测

 

        每个分割MV的编码需要相当的比特数,特别是使用小的分割尺寸时。为减少传输比特数,利用临近分割的MV较强的相关性,MV可由临近已编码分割的MV预测而得。预测矢量MV(p)基于已计算MV和MVD(预测与当前的差异),并被编码和传送。MV(p)取决于运动补偿尺寸和临近MV的有无。

 

        举例说明:如下图,E为当前宏块或宏块分割子宏块,A/B/C分别为E的左、上、右上方的三个相对应块。如果E的左边不止一个分割,取其最上的一个为A;上方不止一个分割时,取其最左边一个为B,如右下图所示,左下图显示了所有分割有相同尺寸时的临近分割选择,右下图给出了不同尺寸时的临近分割的选择。

其中:

(1)、传输分割不包括16*8和8*16时,MV(p)为A/B/C分割MV的中值;

(2)、16*8分割,上面部分MV(p)由B预测,下面部分由A预测;

(3)、8*16分割,左面部分MV(p)由A预测,右面部分由C预测;

(4)、Skipped宏块,MV(p)为A/B/C分割MV的中值。

 

        当已传送块不存在时,MV(p)选择需重新调整。在解码端,MV(p)以相同的方式形成并加到MVD上,对于跳跃宏块来说,不存在MVD,运动补偿宏块也由MV直接生成。

 

4、B片预测

 

        B片中的帧间编码宏块的每个子块都是由一个或两个参考图像预测而得,该参考图像在当前图像的前面或后面。参考图像存储于编解码器中,其选择有多种方式。下图显示了三种方式:一个前向一个后向、两个前向、两个后向。

4.1、参考图像

 

        B片用到了两个已编码图像列表:list0和list1,包括短期和长期两种。这两个列表都可包含前向和后向的已编码图像,图像的显示顺序排列采用图像序号POC表示。其中:

List0:最近前向图像标为index0,接着是其余前向图像(POC递减顺序)以及后向图像(POC递增顺序)。

List1:最近后向图像标为index0,接着是其余后向图像(POC递增顺序)以及前向图像(POC递减顺序)。

 

        举例说明:一个H.264解码器存储了6幅短期参考图像。其POC分别为:123,125,126,128,129和130。当前图像为127。所有6幅短期参考图像在list0和list1中都标为“用作参考”,如下表所示。

4.2、预测模式选择

 

        B片的预测方式包括:宏块分割方式、双向选择方式、参考列表选择方式等等。具体说,
B片中宏块分割可由多种预测方式中的一种实现,如直接模式、利用list0的运动补偿模式、利用list1的运动补偿模式或者利用list0和list1的双向运动补偿模式。每个分割可选择各自的不同的预测模式如下表。如果8×8分割被使用,每个8×8分割所选择的模式适用于分割中的所有亚分割。

        下图给出一个例子,左边的两个16×8分割分别使用List0和双向预测模式,而右边的 4 个8×8分割分别采用直接、list0、list1和双向预测四种模式。

4.3、双向预测

 

        所谓双向预测就是参考块是由list0和list1的参考图像得出的,从list0和list1分别得出两个运动补偿参考区域(需要两个MV),而预测块的像素取list0和list1相应像素的平均值。

 

4.4、直接预测

 

        直接预测模式编码的B片宏块或宏块分割不传送MV。而是由解码器计算list0和 list1 中已编码MV,得出解码残差像素的双向预测运动补偿。B片中的skipped宏块便由解码器用直接模式重建而得。

 

        直接预测模式具体可分为空域和时域两种模式,片头会指明将用时间还是空间方式计算直接模式或其分割的矢量。

        在空间模式中, list0 和 list1 预测矢量计算如下:如果第一幅 list1 参考图像的 co-located MB 或分割有一个 MV 幅度上小于±1/2 亮度像素,其一个或两个预测矢量置为 0;否则预测 list0 和 list1 矢量用以计算双向运动补偿。

        在时间模式中,计算步骤如下:

1)、找出 list1 图像 co-located MB 或分割相应的 list0 参考图像。该 list0 参考作为当前 MB 或分割的 list0 参考;

2)、找出 list1 图像 co-located MB 或分割相应的 list0MV;

3)、计算当前图像和 list1 图像的 POC 的 MV,作为新的 list1 MV1;

4)、计算当前图像和 list0 图像的 POC 的 MV,作为新的 list0 MV0。

        这些模式在预测参考宏块或分割不提供或帧内编码等情况下需作出调整。举例:当前宏块 list1 参考在当前帧两幅图像后出现,如下图所示。List1参考 co-located MB有一MV(+2.5,+5),指向list0参考图像(出现于当前图像3幅图像前)。解码器分别计算指向
list1和 list0 的MV1(-1,-2)和MV0(+1.5,+3)。

5、加权预测

 

        加权预测是一种用来修正 P 或
B 片中运动补偿预测像素方法。 H.264 中由 3 中加权预测类型:

1)、P 片宏块“explicit”加权预测;

2)、B 片宏块“explicit”加权预测;

3)、B 片宏块“implicit”加权预测;

        每个预测像素 pred0(i,j)和 pred1(i,j)在运动补偿之前通过加权系数 ω0 和 ω1 修正。在“explicit”类型中,加权系数由编码器决定并在片头中传输。在“implicit”类型中,系数 ω0 和 ω1 由相应 list0 和 list1 参考图像的时间位置推出。大的系数用于时间上接近当前图像的情况,小的则用于时间上远离当前图像的情况。

        可见, H.264 采用树状结构的运动补偿技术,提高了预测能力。特别是,小块预测提高了模型处理更好的运动能够描述的能力,产生更好的图像质量。 H.264 运动向量的精度提高到 1/4 像素(亮度),运动补偿算法的预测能力得到进一步提高。 H.264 还提供多参考帧可选模式,这将产生更好的视频质量和效率更高的视频编码。相对于 1 帧参考, 5 个参考帧可以节约 5%~10%的比特率,且有助于比特流的恢复。当然,并不是说参考帧越多越好,经实验,考虑到缓冲区的能力和编码器的效率,目前一般都选取3~5个参考帧。

 

6、帧间预测函数x264_mb_analyse_inter_*()

 

帧间预测的帧类型大多是P帧或B帧,也有可能有I帧,对于P或B帧,则进行如下的流程:

a)、调用x264_macroblock_probe_pskip()分析是否为Skip宏块,如果是的话则不再进行下面分析。

b)、调用x264_mb_analyse_inter_p16x16()分析P16x16帧间预测的代价。

c)、调用x264_mb_analyse_inter_p8x8()分析P8x8帧间预测的代价。

d)、如果P8x8代价值小于P16x16,则依次对4个8x8的子宏块分割进行判断:

    i、调用x264_mb_analyse_inter_p4x4()分析P4x4帧间预测的代价。

    ii、如果P4x4代价值小于P8x8,则调用
x264_mb_analyse_inter_p8x4()和x264_mb_analyse_inter_p4x8()分析P8x4和P4x8帧间预测的代价。

e)、如果P8x8代价值小于P16x16,调用x264_mb_analyse_inter_p16x8()和x264_mb_analyse_inter_p8x16()分析P16x8和P8x16帧间预测的代价。

f)、此外还要调用x264_mb_analyse_intra(),检查当前宏块作为Intra宏块编码的代价是否小于作为P宏块编码的代价(P
Slice中也允许有Intra宏块)。

 

        以x264_mb_analyse_inter_p16x16()函数为例,其函数关系图如下所示:

        下面同样以x264_mb_analyse_inter_p16x16()为例分析P16x16宏块的帧间预测方式。该函数的定义位于encoder\analyse.c,代码如下:

/******************************************************************/
/******************************************************************/
/*
======Analysed by RuiDong Fang
======Csdn Blog:http://blog.csdn.net/frd2009041510
======Date:2016.03.17
 */
/******************************************************************/
/******************************************************************/

/************====== x264_mb_analyse_inter_p16x16()函数 ======************/
/*
功能:帧间预测:16*16大小的P Slice
*/
static void x264_mb_analyse_inter_p16x16( x264_t *h, x264_mb_analysis_t *a )
{
    //运动估计相关的信息
    //后面的初始化工作主要是对该结构体赋值
	x264_me_t m;
    int i_mvc;
    ALIGNED_4( int16_t mvc[8][2] );
    int i_halfpel_thresh = INT_MAX;
    int *p_halfpel_thresh = (a->b_early_terminate && h->mb.pic.i_fref[0]>1) ? &i_halfpel_thresh : NULL;

    /* 16x16 Search on all ref frame */
    m.i_pixel = PIXEL_16x16;//设定像素分块大小
    LOAD_FENC( &m, h->mb.pic.p_fenc, 0, 0 );

    a->l0.me16x16.cost = INT_MAX;

	//循环搜索所有的参考帧
    //i_ref
    //mb.pic.i_fref[0]存储了参考帧的个数
    for( int i_ref = 0; i_ref < h->mb.pic.i_fref[0]; i_ref++ )
    {
        m.i_ref_cost = REF_COST( 0, i_ref );
        i_halfpel_thresh -= m.i_ref_cost;

        /* search with ref */
		//加载半像素点的列表
        //参考列表的4个分量列表,包括yN(整点像素),yH(1/2水平内插),yV(1/2垂直内插), yHV(1/2斜对角内插)
        LOAD_HPELS( &m, h->mb.pic.p_fref[0][i_ref], 0, i_ref, 0, 0 );
        LOAD_WPELS( &m, h->mb.pic.p_fref_w[i_ref], 0, i_ref, 0, 0 );

        x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp );	//////////////////获得预测的运动矢量MV(通过取中值)

        if( h->mb.ref_blind_dupe == i_ref )
        {
            CP32( m.mv, a->l0.mvc[0][0] );
            x264_me_refine_qpel_refdupe( h, &m, p_halfpel_thresh );
        }
        else
        {
            x264_mb_predict_mv_ref16x16( h, 0, i_ref, mvc, &i_mvc );
            x264_me_search_ref( h, &m, mvc, i_mvc, p_halfpel_thresh );	////////////////关键:运动估计(搜索参考帧)
        }

        /* save mv for predicting neighbors */
        CP32( h->mb.mvr[0][i_ref][h->mb.i_mb_xy], m.mv );
        CP32( a->l0.mvc[i_ref][0], m.mv );

        /* early termination
         * SSD threshold would probably be better than SATD */
        if( i_ref == 0
            && a->b_try_skip
            && m.cost-m.cost_mv < 300*a->i_lambda
            &&  abs(m.mv[0]-h->mb.cache.pskip_mv[0])
              + abs(m.mv[1]-h->mb.cache.pskip_mv[1]) <= 1
            && x264_macroblock_probe_pskip( h ) )
        {
            h->mb.i_type = P_SKIP;
            x264_analyse_update_cache( h, a );
            assert( h->mb.cache.pskip_mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 );
            return;
        }

        m.cost += m.i_ref_cost;
        i_halfpel_thresh += m.i_ref_cost;

        if( m.cost < a->l0.me16x16.cost )
            h->mc.memcpy_aligned( &a->l0.me16x16, &m, sizeof(x264_me_t) );
    }

    x264_macroblock_cache_ref( h, 0, 0, 4, 4, 0, a->l0.me16x16.i_ref );
    assert( a->l0.me16x16.mv[1] <= h->mb.mv_max_spel[1] || h->i_thread_frames == 1 );

    h->mb.i_type = P_L0;
    if( a->i_mbrd )
    {
        x264_mb_init_fenc_cache( h, a->i_mbrd >= 2 || h->param.analyse.inter & X264_ANALYSE_PSUB8x8 );
        if( a->l0.me16x16.i_ref == 0 && M32( a->l0.me16x16.mv ) == M32( h->mb.cache.pskip_mv ) && !a->b_force_intra )
        {
            h->mb.i_partition = D_16x16;
            x264_macroblock_cache_mv_ptr( h, 0, 0, 4, 4, 0, a->l0.me16x16.mv );
            a->l0.i_rd16x16 = x264_rd_cost_mb( h, a->i_lambda2 );
            if( !(h->mb.i_cbp_luma|h->mb.i_cbp_chroma) )
                h->mb.i_type = P_SKIP;
        }
    }
}

        从代码中可以看出,函数x264_mb_analyse_inter_p16x16()首先初始化了x264_me_t结构体相关的信息,接着x264_mb_predict_mv_16x16()函数获得预测的运动矢量MV,然后调用x264_me_search_ref()进行运动估计,最后统计运动估计的开销。其中x264_me_search_ref()完成了运动搜索的流程,相对比较复杂,是帧间预测的重点。

        总而言之,在H.264中,对于P帧,每个16*16大小的宏块可以有4种分割方式:一个16*16(对应于x264_mb_analyse_inter_p16x16函数),两个16*8(对应于x264_mb_analyse_inter_p16x8函数),两个8*16(对应于x264_mb_analyse_inter_p8x16函数),四个8*8(对应于x264_mb_analyse_inter_p8x8函数);而8*8模式的每个子宏块还可以进一步用4种方式再进行分割:一个8*8(对应于x264_mb_analyse_inter_p8x8函数),两个8*4(对应于x264_mb_analyse_inter_p8x4函数),两个4*8(对应于x264_mb_analyse_inter_p4x8函数),四个4*4(对应于x264_mb_analyse_inter_p4x4函数)。对于B帧,每个16*16大小的宏块也可以有4种分割方式:一个16*16(对应于x264_mb_analyse_inter_b16x16函数),两个16*8(对应于x264_mb_analyse_inter_b16x8函数),两个8*16(对应于x264_mb_analyse_inter_b8x16函数),四个8*8(对应于x264_mb_analyse_inter_b8x8函数)。

时间: 2024-09-22 08:32:33

x264代码剖析(十三):核心算法之帧间预测函数x264_mb_analyse_inter_*()的相关文章

x264代码剖析笔记

x264代码剖析笔记         x264的基本框架仍是采用基于预测+变换的混合编码框架,如下图所示,主要包括:帧内预测.帧间预测.变换与量化.熵编码.滤波等.         下面列举了x264代码剖析的系列文章: <x264代码剖析(一):图文详解x264在Windows平台上的搭建> <x264代码剖析(二):如何编译运行x264以及x264代码基本框架> <x264代码剖析(三):主函数main().解析函数parse()与编码函数encode()> <

x264代码剖析(十八):核心算法之滤波

x264代码剖析(十八):核心算法之滤波           H.264/MPEG-4 AVC视频编码标准中,在编解码器反变换量化后,图像会出现方块效应,主要原因是:1)基于块的帧内和帧间预测残差的DCT变换,变换系数的量化过程相对粗糙,因而反量化过程恢复的变换系数有误差,会造成在图像块边界上的视觉不连续:2)运动补偿可能是从不是同一帧的不同位置上内插样点数据复制而来,因为运动补偿块的匹配不可能是绝对准确的,所以就会在复制块的边界上产生数据不连续:3)参考帧中的存在的不连续也被复制到需要补偿的图

x264代码剖析(十七):核心算法之熵编码(Entropy Encoding)

x264代码剖析(十七):核心算法之熵编码(Entropy Encoding)   熵编码是无损压缩编码方法,它生产的码流可以经解码无失真地恢复出原始数据.熵编码是建立在随机过程的统计特性基础上的.本文对熵编码中的CAVLC(基于上下文自适应的可变长编码)和CABAC(基于上下文的自适应二进制算术熵编码)进行简单介绍,并给出x264中熵编码对应的代码分析.     在H.264的CAVLC中,通过根据已编码句法元素的情况,动态调整编码中使用的码表,取得了极高的压缩比.CAVLC用于亮度和色度残差

x264代码剖析(十五):核心算法之宏块编码中的变换编码

x264代码剖析(十五):核心算法之宏块编码中的变换编码           为了进一步节省图像的传输码率,需要对图像进行压缩,通常采用变换编码及量化来消除图像中的相关性以减少图像编码的动态范围.本文主要介绍变换编码的相关内容,并给出x264中变换编码的代码分析.   1.变换编码           变换编码将图像时域信号变换成频域信号,在频域中图像信号能量大部分集中在低频区域,相对时域信号,码率有较大的下降. H.264对图像或预测残差采用4×4整数离散余弦变换技术,避免了以往标准中使用的通

x264代码剖析(十):x264核心算法框架

x264代码剖析(十):x264核心算法框架           在正式介绍H.264/AVC核心编码算法之前,首先分析一下其编码结构或者编码流程,后续我们可以根据编码的各个模块分别进行介绍,这样有利于对H.264/AVC视频编码算法的更深入了解.           H.264并没有明确规定一个编解码器如何实现,而是规定了一个编码后的视频比特流的句法和比特流的解码方法,在实现上有较大的灵活性.           H.264/AVC编码器的功能组成如下图所示,编码器仍旧采用变换和预测的混合编码

x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode()

x264代码剖析(十四):核心算法之宏块编码函数x264_macroblock_encode()           宏块编码函数x264_macroblock_encode()是完成变换与量化的主要函数,而x264_macroblock_encode()调用了x264_macroblock_encode_internal()函数,在x264_macroblock_encode_internal()函数中,主要完成了如下功能:   x264_macroblock_encode_skip():编码

x264代码剖析(十一):核心算法之宏块分析函数x264_macroblock_analyse()

x264代码剖析(十一):核心算法之宏块分析函数x264_macroblock_analyse()           x264的 x264_slice_write()函数中调用了宏块分析函数x264_macroblock_analyse(),该模块主要完成2大任务:一是对于帧内宏块,分析帧内预测模式:二是对于帧间宏块,进行运动估计,分析帧间预测模式.           如下图所示是x264_macroblock_analyse()的函数关系图.         从图中可以总结出x264_ma

x264代码剖析(十六):核心算法之宏块编码中的量化编码

x264代码剖析(十六):核心算法之宏块编码中的量化编码           为了进一步节省图像的传输码率,需要对图像进行压缩,通常采用变换编码及量化来消除图像中的相关性以减少图像编码的动态范围.本文主要介绍量化的相关内容,并给出x264中量化编码的代码分析.   1.量化编码           量化过程就是根据图像的动态范围大小确定量化参数,既保留图像必要的细节,又可以减少码流.在图像编码中,变换编码和量化从原理上讲是两个独立的过程.但在H.264中,将两个过程中的乘法合二为一,并进一步采用

x264代码剖析(七):encode()函数之x264_encoder_encode()函数

x264代码剖析(七):encode()函数之x264_encoder_encode()函数           encode()函数是x264的主干函数,主要包括x264_encoder_open()函数.x264_encoder_headers()函数.x264_encoder_encode()函数与x264_encoder_close()函数四大部分,其中x264_encoder_encode()函数是其核心部分,具体的H.264视频编码算法均在此模块.上两篇博文主要分析了x264_enc