zoukankan      html  css  js  c++  java
  • x264源代码分析-转

    相关说明:

    1.     使用版本:  x264-cvs-2004-05-11

    2.     这次的分析基本上已经将代码中最难理解的部分做了阐释,对代码的主线也做了剖析,如果这个主线理解了,就容易设置几个区间,进行分工阅读,将各个区间击破了.

    3.     需要学习的知识:

    a)       编码器的工作流程.

    b)      H.264的码流结构,像x264_sps_t,x264_pps_t等参数的定义基本上都完全符合标准文档中参数集的定义,抓住主要参数,次要参数也应该有所了解.

    c)       数学知识,对dct变换等与数学相关的知识的编程实现要有较好理解.

    d)      C语言的知识.涉及到c语言的较多不经常用的特性,如函数指针数组,移位运算,结构体的嵌套定义等.

    e)       耐心,对h.264的复杂性要有清醒的认识.

    3.参考资料:

    a)       新一代视频压缩编码标准-h.264/avc  毕厚杰主编,人民邮电出版社.

    b)      网上的流媒体论坛,百度,google等搜索引擎.

    4. 阅读代码的方法:

    a)       较好的方法是利用vc的调试器,如果对某个函数感兴趣,可以将断点设置在它的前面.然后采用step into,step over等方法进去该函数一步步分析.当然本身要对程序执行流程要有较清楚认识,不然不知道何时step into,何时step over.

    b)      建议应该先对照标准弄清各个结构体成员的意义.

    源代码主要过程分析:

    1.       进入x264.c中的main函数.

    刚开始是读取默认参数,如果你设置了参数的话会修改param的.

       i_ret = Encode( &param, fin, fout );

    这条语句使过程进入x264.c中的Encode函数.

    2.       X.264的encode函数.

    A.     i_frame_total = 0;

    if( !fseek( fyuv, 0, SEEK_END ) )

         {

            int64_t i_size = ftell( fyuv );

            fseek( fyuv, 0, SEEK_SET );

            i_frame_total = i_size / ( param->i_width * param->i_height * 3 / 2 )

    }

    上面这段计算出输入文件的总帧数.

    B.      h = x264_encoder_open( param )这个函数是对不正确的参数进行修改,并对各结构体参数和cabac编码,预测等需要的参数进行初始化.

    C.     pic = x264_picture_new( h );

    该函数定义在COREcommon.c中.首先分给能容纳sizeof(x264_picture_t)字节数的空间,然后进行初始化.

          这里看一下x264_picture_t和x264_frame_t的区别.前者是说明一个视频序列中每帧的特点.后者存放每帧实际的象素值.注意区分.

    D.     for( i_frame = 0, i_file = 0; i_ctrl_c == 0 ; i_frame++ )

        {

            int         i_nal;

            x264_nal_t  *nal;

     int         i;

    /* read a frame */

            if( fread( pic->plane[0], 1, param->i_width * param->i_height, fyuv ) <= 0 ||

                fread( pic->plane[1], 1, param->i_width * param->i_height / 4, fyuv ) <= 0 ||

                fread( pic->plane[2], 1, param->i_width * param->i_height / 4, fyuv ) <= 0 )

            {

                break;

            }

        //文件位置指示器自己变化了.

            if( x264_encoder_encode( h, &nal, &i_nal, pic ) < 0 )

            {

                fprintf( stderr, "x264_encoder_encode failed " );

            }

            ……

    }

    凡是出现for循环的地方都应该认真对待,这是我的一个体会,也是进入分层结构认真分析的好方法.

    fread()函数一次读入一帧,分亮度和色度分别读取.这里要看到c语言中的File文件有一个文件位置指示器,调用fread()函数会使文件指示器自动移位,这就是一帧一帧读取的实现过程.

    E.      然后进入x264_encoder_encode( h, &nal, &i_nal, pic )函数,该函数定义在/Enc/encoder.c中.

    开始进入比较复杂的地方了.

    这个函数前面有一段注释(如下):

    ****************************************************************************

     * x264_encoder_encode:

     *  XXX: i_poc   : is the poc of the current given picture

     *       i_frame : is the number of the frame being coded

     *  ex:  type frame poc

     *       I      0   2*0//poc是实际的帧的位置.

     *       P      1   2*3//frame是编码的顺序.

     *       B      2   2*1

     *       B      3   2*2

     *       P      4   2*6

     *       B      5   2*4

     *       B      6   2*5

     ****************************************************************************/

    要搞清poc和frame的区别.

    假设一个视频序列如下:

    I     B    B    P     B     B     P

    我们编码是按I  P  B  B  P  B  B的顺序,这就是frame的编号.

    而我们视频序列的播放序号是POC的序号,这里是乘以了2.

    函数中先定义了如下三个参数:

    int     i_nal_type; 

    nal存放的数据类型, 可以是sps,pps等多种.                  

    int     i_nal_ref_idc;

    nal的优先级,nal重要性的标志位.

    前面两个参数虽然简单,但如果不参照标准,也不容易理解,所以标准中的句法表是很重要的,可以说是最关键的.

    int     i_slice_type;

    slice的类型,在x264中我的感觉好像一帧只有一个slice.如果确定了帧的类型,slice的类型也就确定了.

    我们来看看编码器是如何区分读入的一帧是I帧,P帧,或者B帧,这个过程需要好好理解.

    还以I     B  B  P    B   B     P为例.

    if( h->i_frame % (h->param.i_iframe * h->param.i_idrframe) == 0 ){

    确定这是立即刷新片.

    }

             这里很好理解.

    但到了if( h->param.i_bframe > 0 )//可以B帧编码时.

    就有问题了.

    注意我们编完I帧后碰到了一个B帧,这时我们先不对它进编码.而是采用frame = x264_encoder_frame_put_from_picture( h, h->frame_next, pic )函数将这个B帧放进h->frame_next中.

    好,这里出现了h->frame_next,在h中同时定义了下面几个帧数组用以实现帧的管理.

    x264_frame_t   *bframe_current[X264_BFRAME_MAX]; /* store the sequence of b frame being encoded */

        x264_frame_t    *frame_next[X264_BFRAME_MAX+1];   /* store the next sequence of frames to be encoded *///搞清意义,下一个帧,而不一定是B帧.

        x264_frame_t    *frame_unused[X264_BFRAME_MAX+1]; /* store unused frames */

    注意区分这3个数组.

    同时还有下面4个函数(定义在ENCODERencoder.c中).

    x264_encoder_frame_put_from_picture();

    x264_encoder_frame_put();

    x264_encoder_frame_get();

    x264_frame_copy_picture();

    这3个数组和4个函数可以说完成了整个帧的类型的判定问题.这个里面if ,else语句较多,容易使人迷惑.但我们只要把握下面一个观点就可以看清实质:在不对P帧进行编码之前,我们不对B帧进行编码,只是把B帧放进缓冲区(就是前面提到的数组).

    比如视频序列:I     B  B  P  B  B  P

    先确立第一个帧的类型,然后进行编码.然后是2个B帧,我们把它放进缓冲区数组.然后是P帧,我们可以判定它的类型并进行编码.同时,我们将缓冲区的B帧放进h->bframe_current[i],不过这时P帧前的两个B帧并没有编码.当读到P帧后面的第一个B帧时,我们实际上才将h->bframe_current数组中的第一个B帧编码,也就是将在I帧后面的第一个B帧(说成P帧前面的第一个B帧容易误解J)编码.

    依此类推,把握好上面4个函数的调用流程和指针操作的用法,就可以将帧的类型判定这个问题搞明白了.

    F.      然后是速率控制(先不说这个,因为它对编码的流程影响不大),看看建立参考帧列表的操作,也就是

    x264_reference_build_list( h, h->fdec->i_poc ); (定义在ENCODERencoder.c中).

    光看这个函数是不行的,它是和后面的这个函数(如下)一起配合工作的.

    if( i_nal_ref_idc != NAL_PRIORITY_DISPOSABLE )//B帧时.

        {

            x264_reference_update( h );

    }

         If条件是判断当前帧是否是B帧,如果是的话就不更新参考列表,因为B帧本来就不能作为参考帧嘛!如果是I帧或P帧的话,我们就更新参考帧列表.

    我们看到了一个for循环,两个do—while循环.这是实现的关键,具体看代码,不好用语言说明白.

    G.     进入另一个复杂的领域:写slice的操作,刚开使挺简单,如我下面的注释.

    /* ---------------------- Write the bitstream -------------------------- */

        /* Init bitstream context */

        h->out.i_nal = 0;//out的声明在bs.h中.

        bs_init( &h->out.bs, h->out.p_bitstream, h->out.i_bitstream );//空出8位.

        /* Write SPS and PPS */

        if( i_nal_type == NAL_SLICE_IDR )//不是每次都要写SPS and PPS,只有碰见立即刷新片时才写.

        {

            /* generate sequence parameters */

            x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );

            x264_sps_write( &h->out.bs, h->sps );

            x264_nal_end( h );

            /* generate picture parameters */

            x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );

            x264_pps_write( &h->out.bs, h->pps );

            x264_nal_end( h );

    }

    不过看下面那个函数(就进入了复杂的领域).

    H.     x264_slice_write()(定义在ENCODERencoder.c中),这里面是编码的最主要部分,下面仔细分析.

    前面不说,看下面这个循环,它是采用for循环对一帧图像的所有块依次进行编码.

    for( mb_xy = 0, i_skip = 0; mb_xy < h->sps->i_mb_width * h->sps->i_mb_height; mb_xy++ )//h->sps->i_mb_width指的是从宽度上说有多少个宏快.对于宽度也就是288 / 16 = 18

        {

            const int i_mb_y = mb_xy / h->sps->i_mb_width;

            const int i_mb_x = mb_xy % h->sps->i_mb_width;//这两个变量是定义宏块的位置.而不是指宏块中元素的位置.

            /* load cache */

            x264_macroblock_cache_load( h, i_mb_x, i_mb_y );//是把当前宏块的up宏块和left宏块的intra4x4_pred_mode,non_zero_count加载进来,放到一个数组里面,这个数组用来直接得到当前宏块的左侧和上面宏块的相关值.要想得到当前块的预测值,要先知道上面,左面的预测值,它的目的是替代getneighbour函数.

    /* analyse parameters

             * Slice I: choose I_4x4 or I_16x16 mode

             * Slice P: choose between using P mode or intra (4x4 or 16x16)

             * */

            TIMER_START( i_mtime_analyse );

            x264_macroblock_analyse( h );//定义在analyse.h中.

            TIMER_STOP( i_mtime_analyse );

            /* encode this macrobock -> be carefull it can change the mb type to P_SKIP if needed */

            TIMER_START( i_mtime_encode );

            x264_macroblock_encode( h );//定义在Enc/encoder.c中.

            TIMER_STOP( i_mtime_encode );

    截止到这就已经完成编码的主要过程了,后面就是熵编码的过程了(我也没看到那,但认为前面才是编码的主要过程).下面对这个过程进行分析.

    A.     x264_macroblock_cache_load( h, i_mb_x, i_mb_y );它是将要编码的宏块的周围的宏块的值读进来, 要想得到当前块的预测值,要先知道上面,左面的预测值,它的作用相当于jm93中的getneighbour函数.

    B.      进入x264_macroblock_analyse( h )函数(定义在Encanalyse.c中,这里涉及到了函数指针数组,需要好好复习,个人认为这也是x264代码最为复杂的一个地方了).既然已经将该宏块周围的宏块的值读了出来,我们就可以对该宏块进行分析了(其实主要就是通过计算sad值分析是否要将16*16的宏块进行分割和采用哪种分割方式合适).

    看似很复杂,但我们只要把握一个东西就有利于理解了:

    举个生活中的例子来说:

    如果你有2元钱,你可以去买2袋1元钱的瓜子,也可以买一袋2元钱的瓜子,如果2袋1元钱的瓜子数量加起来比1袋2元钱的瓜子数量多,你肯定会买2袋1元的.反之你会去买那2元1袋的.

    具体来说,对于一个16*16的块,

    如果它是I帧的块,我们可以将它分割成16个4*4的块,如果这16个块的sad加起来小于按16*16的方式计算出来的sad值,我们就将这个16*16的块分成16个4*4的块进行编码(在计算每个4*4的块的最小sad值时已经知道它采用何种编码方式最佳了),否则采用16*16的方式编码(同样我们也已知道对它采用哪种编码方式最为合适了.

    如果它是P帧或B帧的块,同样是循环套循环,但更为复杂了,可以看我在analyse.c中的注释.

    这里还要注意的是提到了

    x264_predict_t      predict_16x16[4+3];

    typedef void (*x264_predict_t)( uint8_t *src, int i_stride );

    这是函数指针数组,有很多对它的调用.

    C.     退出x264_macroblock_analyse( h )函数,进入x264_macroblock_encode( )函数(定义在ENCODERmacroblock.c中).

    我拿宏块类型为I_16*16为例.

    if( h->mb.i_type == I_16x16 )

        {

            const int i_mode = h->mb.i_intra16x16_pred_mode;

            /* do the right prediction */

            h->predict_16x16[i_mode]( h->mb.pic.p_fdec[0], h->mb.pic.i_fdec[0] );//这两个参数的关系.

                                                                       //涉及到x264_predict_t(函数指针数组),声明在core/predict.h中,core/predict.c里有不同定义.

            /* encode the 16x16 macroblock */

            x264_mb_encode_i16x16( h, i_qscale );//

    /* fix the pred mode value */

           …     }

    我们看到h->predict_16x16[i_mode]( h->mb.pic.p_fdec[0], h->mb.pic.i_fdec[0] );只调用了一次,这是因为在x264_macroblock_analyse(  )中我们已经确定了采用4种方式中的哪种最合适.而在x264_macroblock_analyse(  )中判定一个块是否为I_16*16,我们调用了四次.这是因为当时我们需要拿最小的sad值进行比较.

    继续,是x264_mb_encode_i16x16( h, i_qscale )函数(定义在ENCODERmacroblock.c中).在这个函数中我们就可以看到量化,zig-扫描等函数了,这些都是直来直去的,需要的只是我们的细心和对数学知识的掌握了

    c)       到这里还没完,我们接着看

    void x264_macroblock_encode( x264_t *h ){

    …….前面省略.

    执行到下面这条语句,看看下面是干啥的.

        /* encode chroma */

        i_qscale = i_chroma_qp_table[x264_clip3( i_qscale + h->pps->i_chroma_qp_index_offset, 0, 51 )];

        if( IS_INTRA( h->mb.i_type ) )

        {

            const int i_mode = h->mb.i_chroma_pred_mode;

            /* do the right prediction */

            h->predict_8x8[i_mode]( h->mb.pic.p_fdec[1], h->mb.pic.i_fdec[1] );

            h->predict_8x8[i_mode]( h->mb.pic.p_fdec[2], h->mb.pic.i_fdec[2] );

            /* fix the pred mode value */

            h->mb.i_chroma_pred_mode = x264_mb_pred_mode8x8_fix[i_mode];

        }

        /* encode the 8x8 blocks */

    x264_mb_encode_8x8( h, !IS_INTRA( h->mb.i_type ), i_qscale );//对色度块进行编码了.

    到这我们可以看到原来我们在这前面是对宏块中的亮度系数进行了编码,我们到上面那个函数才开始对色度系数进行编码.进入x264_mb_encode_8x8()函数看到for循环里面有个2可以证明是对2个色度系数进行编码,想法没错.

    那下面这些又是干啥的呢?它们是计算cbp系数看需要对残差(包括ac,dc)中的哪个系数进行传输的.

        /* Calculate the Luma/Chroma patern and non_zero_count */

        if( h->mb.i_type == I_16x16 )

        {

            h->mb.i_cbp_luma = 0x00;

            for( i = 0; i < 16; i++ )

            {

                const int nz = array_non_zero_count( h->dct.block[i].residual_ac, 15 );

                h->mb.cache.non_zero_count[x264_scan8[i]] = nz;

                if( nz > 0 )

                {

                    h->mb.i_cbp_luma = 0x0f;

                }

            }

        }

        else

        {

            h->mb.i_cbp_luma = 0x00;

            for( i = 0; i < 16; i++ )

            {

                const int nz = array_non_zero_count( h->dct.block[i].luma4x4, 16 );//统计非0个数.

                h->mb.cache.non_zero_count[x264_scan8[i]] = nz;

                if( nz > 0 )

                {

                    h->mb.i_cbp_luma |= 1 << (i/4);// %16的意义.

                }

            }

        }

        /* Calculate the chroma patern *///色度的cbp有3种方式.

        h->mb.i_cbp_chroma = 0x00;

        for( i = 0; i < 8; i++ )

        {

            const int nz = array_non_zero_count( h->dct.block[16+i].residual_ac, 15 );

            h->mb.cache.non_zero_count[x264_scan8[16+i]] = nz;

            if( nz > 0 )                      

            {

                h->mb.i_cbp_chroma = 0x02;    /* dc+ac (we can't do only ac) */

            }

        }

        if( h->mb.i_cbp_chroma == 0x00 &&

            ( array_non_zero_count( h->dct.chroma_dc[0], 4 ) > 0 || array_non_zero_count( h->dct.chroma_dc[1], 4 ) ) > 0 )

        {

            h->mb.i_cbp_chroma = 0x01;    /* dc only */

        }

        if( h->param.b_cabac )

        {

            if( h->mb.i_type == I_16x16 && array_non_zero_count( h->dct.luma16x16_dc, 16 ) > 0 )

                i_cbp_dc = 0x01;

            else

                i_cbp_dc = 0x00;

            if( array_non_zero_count( h->dct.chroma_dc[0], 4 ) > 0 )

                i_cbp_dc |= 0x02;

            if( array_non_zero_count( h->dct.chroma_dc[1], 4 ) > 0 )

                i_cbp_dc |= 0x04;

        }

        /* store cbp */

    h->mb.cbp[h->mb.i_mb_xy] = (i_cbp_dc << 8) | (h->mb.i_cbp_chroma << 4) | h->mb.i_cbp_luma;

    到这,基本上x264_macroblock_encode( h )(定义在Enc/encoder.c)基本上就分析完了.剩下的就是熵编码的部分了.以后的部分更需要的应该是耐心和数学知识吧,相对前面来说应该简单些.

    l       总结:

    1. 我对代码的理解应该还算比较深入,把代码的主线已经分析了出来,对代码中几个最难理解的地方(最难理解的地方就是帧的类型的判定,参考帧是如何管理的,一个16*16的块是采用到底需不需要分割,分割的话分成什么大小的,子块又采用何种预测方式,这些实际上就是整个编码的主线.)基本上已经明白,但有些过分复杂的函数的实现(或者涉及数学知识较多的地方)还有待深入研究,但我相信沿着这条主线应该能够继续深入下去,自己需要的是更多的时间和耐心. 自己需要的是更多的时间和耐心,争取以后能写出更详细更准确的流程分析,并尽量思考能改进的地方.

    2.层次性,就像网络的7层结构一样,每一帧图像也可以分成很多层,只有对每层的语法结构(具体来说就是各个结构体中变量的意思)有了很好的理解,才有可能真正认清代码,这需要对标准认真研习.比如说量化参数,就在3个地方有定义,不读标准根本不会明白意思.

    3. 很多过分复杂的东西不容易在本文中表达出来(比如说预测部分),只有通过自己的钻研才能真正悟到,直觉也很重要,还有就是信心了.看这种程序的收获就好像是真地肉眼看到了原子那样.

    4.由于代码过分复杂,对某些函数的实现过程还没能彻底理解,比如说x264_macroblock_cache_load(

    )函数的具体实现过程,我只是知道它的功能,实现过程还有待认真理解.dct变换是如何实现的,是如何计算残差的等等,这些都需要很多功夫,当然这里也需要大家的共同学习和交流.实现分工阅读不同代码部分并进行交流,才有可能对代码做到彻底的理解.

    -----------------------------------------------------------------------------------------------------------------------------

    x264代码分析


    Main(int argc,char *argv[]);
    为了方便起见,不妨改写为:
    Main(void){
    ......
        int argc=5;
        char *argv[]={
           "main","-o","test.264","foreman.yuv","352x288"   
        };      
         ......

    ====================================
    1.x264_param_default( &param );
    这部分设置编码参数的缺省值
    附部分变量的意义:
        param->i_csp          = X264_CSP_I420; // 设置输入的视频采样的格式
    param->vui.i_sar_width = 0;  //VUI:video usability information 
        param->i_fps_num       = 10; //帧率
        param->i_fps_den       = 1;  //用两个整型的数的比值,来表示帧率
        /* Encoder parameters */
        param->i_frame_reference = 1; //参考帧的最大帧数。
        param->i_bframe = 0;          //两个参考帧之间的B帧数目。
        param->b_deblocking_filter = 1; //去块效应相关
        param->b_cabac = 0;            //cabac的开关
        param->i_cabac_init_idc = -1;
        param->rc.b_cbr = 1;           //constant bitrate 恒定码率控制模式
        param->rc.i_bitrate = 0;       //默认的码率
        param->rc.i_rc_buffer_size = 0;   //buffer的大小
        param->rc.i_rc_init_buffer = 0;   //
    param->rc.i_rc_sens = 100;       ///* rate control sensitivity
    param->rc.i_rc_method = X264_RC_NONE;  //码率控制,CQP(恒定质量)、//CRF(恒定码率)、ABR(平均码率)
    param->rc.i_qp_constant = 26; //qp的初始值,最大最小的qp值,
        param->rc.i_qp_min = 10;           //最小的qp值
        param->rc.i_qp_max = 51;          //最大的qp值
    param->rc.i_qp_step = 4;       //qp[步长step。
        param->rc.f_ip_factor = 1.4;  //ip--i帧p帧的qp的差值
        param->rc.f_pb_factor = 1.3;  //pb--p帧b帧的qp的差值
    /* Log */ //整个param的一个log文件
        /*analyse */
    param->analyse.intra = X264_ANALYSE_I4x4 | X264_ANALYSE_I8x8;  
    //桢内分析
    param->analyse.inter = X264_ANALYSE_I4x4 | X264_ANALYSE_I8x8                           | X264_ANALYSE_PSUB16x16 | X264_ANALYSE_BSUB16x16; 
    //桢间分析
    param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_SPATIAL; 
    //预测模式
    param->analyse.i_me_method = X264_ME_HEX;      //运动估计模式
    param->analyse.i_me_range = 16;              //运动估计范围
        param->analyse.i_subpel_refine = 5;
        param->analyse.b_chroma_me = 1;
        param->analyse.i_mv_range_thread = -1;
        param->analyse.i_mv_range = -1; // set from level_idc
        param->analyse.i_direct_8x8_inference = -1; // set from level_idc
        param->analyse.i_chroma_qp_offset = 0;
        param->analyse.b_fast_pskip = 1;
        param->analyse.b_dct_decimate = 1;
        param->analyse.i_luma_deadzone[0] = 21;
        param->analyse.i_luma_deadzone[1] = 11;
        param->analyse.b_psnr = 1;
        param->analyse.b_ssim = 1;
        param->i_cqm_preset = X264_CQM_FLAT;  //自定义量化矩阵(CQM),初始化量化模式为flat
    2.Parse( argc, argv, &param, &opt ) ;
       解析函数int c = getopt_long( argc, argv, "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw",
       long_options, &long_options_index);
       // 得到入口地址的向量与方式的选择  
       _getopt_internal (argc, argv, optstring, longopts, longind, long_only);                  
       //解析入口地址向量
    3.Encode( &param, &opt ) ;
        3.1.x264_encoder_open( param ){
        ……
         x264_t *h = x264_malloc( sizeof( x264_t ) );    //为h分配空间
        /* Create a copy of param */
    memcpy( &h->param, param, sizeof( x264_param_t ) );
    ……
    3.1.1.x264_validate_parameters( h );  //验证参数
    /* VUI */
    /* Init x264_t */
        h->sps = &h->sps_array[0];
    3.1.2.x264_sps_init( h->sps, h->param.i_sps_id, &h->param );
    typedef struct
    {
        int i_id;       //本序列参数集的id号
        int i_profile_idc;   //指明所用的profile
        int i_level_idc;     //指明所用的level
        int b_constraint_set0;   //其值等于时,表示必须遵从附录A.2.1所指明的所有约束条件
        int b_constraint_set1;   //其值等于时,表示必须遵从附录A.2.2所指明的所有约束条件
        int b_constraint_set2;   //其值等于时,表示必须遵从附录A.2.3所指明的所有约束条件
        int i_log2_max_frame_num;   //这个句法元素只要是为读取frame_num服务的
        int i_poc_type;   //指明poc的编码方法,poc标识图像的播放顺序
        /* poc 0 */
        int i_log2_max_poc_lsb;   //指明了变量i_poc_lsb的max值
        /* poc 1 */
        int b_delta_pic_order_always_zero;    //其值为时,i_delta_poc[0]和i_delta_poc[1]不在片头出现,并且它们的默认值为;当本句法元素为时,上述两个句法元素将在片头出现
        int i_offset_for_non_ref_pic;         //用于计算非参考帧或场的poc
        int i_offset_for_top_to_bottom_field; //用于计算帧的底场的poc
        int i_num_ref_frames_in_poc_cycle;    //被用来解码poc
        int i_offset_for_ref_frame[256];      //当i_poc_type=时用于解码poc,本句法元素对循环i_num_ref_frames_in_poc_cycle中的每一个元素指定一个偏移
        int i_num_ref_frames;       //指定参考帧队列可能达到的最大长度,解码器根据这个句法元素的值开辟存储区,这个存储区用于存放已解码的参考帧
        int b_gaps_in_frame_num_value_allowed;   //这个句法元素等于时,表示允许句法元素frame_num可以不连续;当传输信道堵塞时,允许丢弃若干帧
        int i_mb_width;       //图像的宽度,以宏块为单位
        int i_mb_height;      //图像的高度,以宏块为单位
        int b_frame_mbs_only;     //本句法元素等于时,表示本序列中所有的图像编码模式都是帧,没有其他编码模式存在;当为时,表示本序列中图像的编码模式可能是帧,也可能是场或帧场自适应
        int b_mb_adaptive_frame_field;   //指明本序列是否属于帧场自适应模式
        int b_direct8x8_inference;     //指明b片的直接和skip模式下运动矢量的预测方法
        int b_crop;        //指明解码器是否要将图像裁剪后输出,如果是的话,后面紧跟的四个句法元素分别指出左、右、上、下裁剪的宽度
        struct
        {
           int i_left;
           int i_right;
           int i_top;
           int i_bottom;
        } crop;
        int b_vui;   //指明vui子结构是否出现在码流中
        struct
        {
            ......    //省略
           /* FIXME to complete */ 
        } vui;
    int b_qpprime_y_zero_transform_bypass;
    } x264_sps_t;
    3.1.3.x264_pps_init( h->pps, h->param.i_sps_id, &h->param, h->sps);
    typedef struct
    {
        int i_id;      //本参数集的序号,在片头被引用
        int i_sps_id;  //本图像参数集所引用的序列参数集的序号
        int b_cabac;   //0时使用cavlc,时使用cabac
        int b_pic_order;  //poc的三种计算方法在片层还各需要用一些句法元素作为参数;当等于时,表示在片头会有句句法元素指明这些参数;当为时,表示片头不会给出这些参数
        int i_num_slice_groups;   //图像中片组的个数
        int i_num_ref_idx_l0_active;    //指明目前参考帧队列的长度,即有多少各参考帧(短期和长期),用于list0
        int i_num_ref_idx_l1_active;    //指明目前参考帧队列的长度,即有多少各参考帧(短期和长期),用于list1
        int b_weighted_pred;        //指明是否允许p和sp片的加权预测
        int b_weighted_bipred;      //指明是否允许b片的加权预测
        int i_pic_init_qp;          //亮度分量的量化参数的初始值
        int i_pic_init_qs;          //亮度分量的量化参数的初始值,用于SP和SI
        int i_chroma_qp_index_offset;    //色度分量的量化参数是根据亮度分量的量化参数计算出来的,本句法元素用以指明计算时用到的参数
        int b_deblocking_filter_control;   //编码器可以通过句法元素显式地控制去块滤波的强度
        int b_constrained_intra_pred;      //在p和b片中,帧内编码的宏块的邻近宏块可能是采用的帧间编码
        int b_redundant_pic_cnt;          
        int b_transform_8x8_mode;
        int i_cqm_preset;
        const uint8_t *scaling_list[6]; /* could be 8, but we don't allow separate Cb/Cr lists */
    } x264_pps_t;
    x264_validate_levels( h );  
    //////3.1.4.x264_cqm_init( x264_t *h ){
    ……
    h->quant4_mf[i] = x264_malloc(52*size*sizeof(uint16_t) );
        h->dequant4_mf[i] = x264_malloc( 6*size*sizeof(int) );
    h->unquant4_mf[i] = x264_malloc(52*size*sizeof(int) );
    ……
    h->quant4_bias[i] = x264_malloc(52*size*sizeof(uint16_t) );
    ……

    //////3.1.4 end
    /* Init frames. */
    /* init CPU functions */
    h->thread[i] = x264_malloc( sizeof(x264_t) );
    h->thread[i]->fdec = x264_frame_pop_unused( h );
    h->thread[i]->out.p_bitstream = x264_malloc( h->out.i_bitstream );
    //定义要做nal的那些码流的地方,i_bitstream是大小,p_bitstream是指针
    3.1.5 x264_frame_pop_unused( h ){
        x264_frame_t *frame;
        if( h->frames.unused[0] )
            frame = x264_frame_pop( h->frames.unused );  //从unused队列中//取出最后位置的一个桢
        else
            frame = x264_frame_new( h );        //分配一帧所需要的空间
            ///////3.1.5.1 x264_frame_new( h ){
            ……
    for( i = 0; i < 3; i++ )      //分配frame的yuv空间,带边框

    int i_divh = 1;
    int i_divw = 1;
    if( i > 0 )
    {
    if( h->param.i_csp == X264_CSP_I420 )
    i_divh = i_divw = 2;
    else if( h->param.i_csp == X264_CSP_I422 )
    i_divw = 2;
    }

    frame->i_stride[i] = i_stride / i_divw;
    frame->i_lines[i] = i_lines / i_divh;
    CHECKED_MALLOC( frame->buffer[i],
    frame->i_stride[i] * ( frame->i_lines[i] + 2*i_padv / i_divh ) );
    frame->plane[i] = ((uint8_t*)frame->buffer[i]) +
    frame->i_stride[i] * i_padv / i_divh + PADH / i_divw;
    //plane指向除去buffer中图像边框的yuv真实数据
    ……
            }
    ///////3.1.5.1 end
    }
        assert( frame->i_reference_count == 0 );
        frame->i_reference_count = 1;
    return frame;

    if( x264_macroblock_cache_init( h->thread[i] ) < 0 ) return NULL;
    3.1.6.x264_macroblock_cache_init( x264_t *h ){
    //分配x264_t结构体下子结构体mb对应的qp、cbp、skipbp、mb_transform_size、intra4x4_pred_mode、non_zero_count等等在宏块编码时用于控制和传输等用到的表!
    ……
    CHECKED_MALLOC( h->mb.qp, i_mb_count * sizeof(int8_t) );
        CHECKED_MALLOC( h->mb.cbp, i_mb_count * sizeof(int16_t) );
        CHECKED_MALLOC( h->mb.skipbp, i_mb_count * sizeof(int8_t) );
        CHECKED_MALLOC( h->mb.mb_transform_size, i_mb_count * sizeof(int8_t) );
        /* 0 -> 3 top(4), 4 -> 6 : left(3) */
        CHECKED_MALLOC( h->mb.intra4x4_pred_mode, i_mb_count * 7 * sizeof(int8_t) );
        /* all coeffs */
        CHECKED_MALLOC( h->mb.non_zero_count, i_mb_count * 24 * sizeof(uint8_t) );
        //以4*4块为单位,24=4*4+2*2+2*2(yuv)
        CHECKED_MALLOC( h->mb.nnz_backup, h->sps->i_mb_width * 4 * 16 * sizeof(uint8_t) );
        if( h->param.b_cabac )
        {
            CHECKED_MALLOC( h->mb.chroma_pred_mode, i_mb_count * sizeof(int8_t) );
            CHECKED_MALLOC( h->mb.mvd[0], 2*16 * i_mb_count * sizeof(int16_t) );
    //mvd[0]指x方向,2*16中的2指前向和后向,16表示mv以4*4为单位
            CHECKED_MALLOC( h->mb.mvd[1], 2*16 * i_mb_count * sizeof(int16_t) );
        }
        for( i=0; i<2; i++ )
        {
            int i_refs = X264_MIN(16, (i ? 1 : h->param.i_frame_reference) + h->param.b_bframe_pyramid) << h->param.b_interlaced;
            for( j=0; j < i_refs; j++ )
                CHECKED_MALLOC( h->mb.mvr[i][j], 2 * i_mb_count * sizeof(int16_t) );
        }
        for( i=0; i<=h->param.b_interlaced; i++ )
            for( j=0; j<3; j++ )
            {
                CHECKED_MALLOC( h->mb.intra_border_backup[i][j], h->fdec->i_stride[j] );
                h->mb.intra_border_backup[i][j] += 8;
            }
    ……

    ///////3.1.6 end
    3.1.7
    if( x264_ratecontrol_new( h ) < 0 ) return NULL;
    //码率控制初始化
    ……
    return h;

  • 相关阅读:
    流畅 最好用的远程桌面推荐下?
    高性能远程桌面Splashtop 居家办公首选软件
    ShareConnect即将寿终正寝 Splashtop远程桌面会是最好的替代品
    详解Springboot中自定义SpringMVC配置
    Java中使用RSA算法加密
    Docker搭建MySQL主从复制
    SpringMVC中RequestContextHolder获取请求信息
    详解Redis持久化(RDB和AOF)
    Redis事务
    Springboot CORS跨域访问
  • 原文地址:https://www.cnblogs.com/xumaojun/p/8543060.html
Copyright © 2011-2022 走看看