x264部分函数解析

x264 Encode()函数解析
http://www.usr.cc/thread-51997-1-3.html
x264_macroblock_analyse解析
x264中bs.h文件部分函数解读
http://www.usr.cc/thread-52125-1-1.html

x264 Encode()函数解析
Encode()
参数:

Cli_opt_t opt;x264_param_t parm

局部变量:

i_frame,i_frame_tota,i_start,i_end,i_file,i_frame_size,i_progress



i_frame_total = p_get_frame_total( opt->hin );

    i_frame_total -= opt->i_seek;

    if( ( i_frame_total == 0 || param->i_frame_total < i_frame_total )

        && param->i_frame_total > 0 )

        i_frame_total = param->i_frame_total;

    param->i_frame_total = i_frame_total;

这段代码是实现,计算文件中的总共的帧数,并根据输入的参数初始帧的位置,对i_frame_total做出修正,i_frame_total -= opt->i_seek,然后再根据param->i_frame_total,对i_frame_total做出进一步的修正。总体来说, 就是对参数设置中的进行编码的帧数的总数进行修正和计算。



if( ( h = x264_encoder_open( param ) ) == NULL )

    {

        fprintf( stderr, "x264 [error]: x264_encoder_open failed\n" );

        p_close_infile( opt->hin );

        p_close_outfile( opt->hout );

        return -1;

    }



关键函数: x264_encoder_open( param ) 根据参数要求对encoder进行一系列的初始化,例如分配内存,值的初始化等。



    if( p_set_outfile_param( opt->hout, param ) )

    {

        fprintf( stderr, "x264 [error]: can't set outfile param\n" );

        p_close_infile( opt->hin );

        p_close_outfile( opt->hout );

        return -1;

    }



关键函数:p_set_outfile_param() 设置输出文件格式





上图展示的是pic的数据结构



  /* Create a new pic */

    x264_picture_alloc( &pic, X264_CSP_I420, param->i_width, param->i_height );



关键函数: x264_picture_alloc() 按照色度空间分配内存,并返回内存的首地址作为指针



    i_start = x264_mdate();



关键函数:x264_mdate()     用于编码用时的计算,设定起始时间



    /* Encode frames */

    for( i_frame = 0, i_file = 0, i_progress = 0;

         b_ctrl_c == 0 && (i_frame < i_frame_total || i_frame_total == 0); )

    {

        if( p_read_frame( &pic, opt->hin, i_frame + opt->i_seek ) )

            break;



p_read_frame() 按照h->hin提供的输入文件的地址,读入图像的内容到&pic提供的存储区的首地址



        pic.i_pts = (int64_t)i_frame * param->i_fps_den;  pic.i_pts的作用暂时无法理解



        if( opt->qpfile )

            parse_qpfile( opt, &pic, i_frame + opt->i_seek );



parse_qpfile() 为从指定的文件中读入qp的值留下的接口,qpfile为文件的首地址

        else

        {

            /* Do not force any parameters */

            pic.i_type = X264_TYPE_AUTO;

            pic.i_qpplus1 = 0;

        }



        i_file += Encode_frame( h, opt->hout, &pic );



encode_frame(     )核心函数,包括了编码的核心过程



        i_frame++;



        /* update status line (up to 1000 times per input file) */

用于显示整个编码过程的进度

        if( opt->b_progress && param->i_log_level < X264_LOG_DEBUG &&

            ( i_frame_total ? i_frame * 1000 / i_frame_total > i_progress

                            : i_frame % 10 == 0 ) )

        {

            int64_t i_elapsed = x264_mdate() - i_start;  编码使用的时间计算

            double fps = i_elapsed > 0 ? i_frame * 1000000. / i_elapsed : 0;    帧率的计算

            if( i_frame_total )

            {

                int eta = i_elapsed * (i_frame_total - i_frame) / ((int64_t)i_frame * 1000000);

                i_progress = i_frame * 1000 / i_frame_total;

                fprintf( stderr, "encoded frames: %d/%d (%.1f%%), %.2f fps, eta %d:%02d:%02d  \r",

                         i_frame, i_frame_total, (float)i_progress / 10, fps,

                         eta/3600, (eta/60)%60, eta%60 );

            }

            else

                fprintf( stderr, "encoded frames: %d, %.2f fps   \r", i_frame, fps );

            fflush( stderr ); // needed in windows

        }

    }

    /* Flush delayed B-frames */

    do {

        i_file +=

        i_frame_size = Encode_frame( h, opt->hout, NULL );  //从左至右的计算顺序

} while( i_frame_size );



i_file记录了每次编完一帧后,输出的文件的大小的变化,其实也可以看作是文件指针的移动,以byte为单位



Encode_frame( h, opt->hout, NULL )      这个函数的作用还不知道



    i_end = x264_mdate();

    x264_picture_clean( &pic );

    x264_encoder_close( h );

    fprintf( stderr, "\n" );



    if( b_ctrl_c )

        fprintf( stderr, "aborted at input frame %d\n", opt->i_seek + i_frame );



    p_close_infile( opt->hin );

p_close_outfile( opt->hout );





这段代码的作用是在完成了编码过程后,对善后工作进行清理,如释放内存等



    if( i_frame > 0 )

    {

        double fps = (double)i_frame * (double)1000000 /

                     (double)( i_end - i_start );



        fprintf( stderr, "encoded %d frames, %.2f fps, %.2f kb/s\n", i_frame, fps,

                 (double) i_file * 8 * param->i_fps_num /

                 ( (double) param->i_fps_den * i_frame * 1000 ) );

}

显示编码的统计结果



    return 0;

}





x264_macroblock_analyse解析
x264_macroblock_analyse1、void x264_macroblock_analyse( x264_t *h )

首先初始化函数,然后进入一个选择语句

if( h->sh.i_type == SLICE_TYPE_I )

{…}

else if( h->sh.i_type == SLICE_TYPE_P )

{…}

else if( h->sh.i_type == SLICE_TYPE_B )

{…}

很明显,这是对不同类型的块采用不同的处理步骤,我们就进入SLICE_TYPE_P深入分析一下吧,因为P类型包含有I模式的检测,SLICE_TYPE_B只是SLICE_TYPE_P的简单延伸而已。

if( h->param.analyse.b_fast_pskip )

        {

            if( h->param.analyse.i_subpel_refine >= 3 )

                analysis.b_try_pskip = 1;

            else if( h->mb.i_mb_type_left == P_SKIP ||

                     h->mb.i_mb_type_top == P_SKIP ||

                     h->mb.i_mb_type_topleft == P_SKIP ||

                     h->mb.i_mb_type_topright == P_SKIP )

                b_skip = x264_macroblock_probe_pskip( h );

        }

条件进入后首先判断skip模式,如果满足skip模式则判断结束,否则继续下面的判断。


x264_mb_analyse_inter_p16x16( h, &analysis );

            if( flags & X264_ANALYSE_PSUB16x16 )

            {

                if( h->param.analyse.b_mixed_references )

                    x264_mb_analyse_inter_p8x8_mixed_ref( h, &analysis );               

                else

                    x264_mb_analyse_inter_p8x8( h, &analysis );

            }

分析16×16和8×8模式。模式代价值分别保存在和analysis.l0.me16x16.cost 、analysis.l0.i_cost8x8中。

if( ( flags & X264_ANALYSE_PSUB16x16 ) && analysis.l0.i_cost8x8 < analysis.l0.me16x16.cost )

                     {……}

如果8×8代价值小于16×16,则进行子宏块分割的判断。

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

                    {

                        x264_mb_analyse_inter_p4x4( h, &analysis, i );

                        if( analysis.l0.i_cost4x4[i] < analysis.l0.me8x8[i].cost )

                        { ………}

依次对4个子宏块(8×8)进行处理,x264_mb_analyse_inter_p4x4()函数实际上是得到4个4×4块的代价和analysis.l0.i_cost4x4[i]。如果4×4模式优于8×8模式,才进行8×8块的细分割。细分割代码分析略。

if( ( flags & X264_ANALYSE_PSUB16x16 ) && analysis.l0.i_cost8x8 < analysis.l0.me16x16.cost + i_thresh16x8 )

            {

                x264_mb_analyse_inter_p16x8( h, &analysis );

                ……..

                x264_mb_analyse_inter_p8x16( h, &analysis );

                ……...

            }

紧接着检测16×8和8×16模式

x264_me_refine_qpel( h, &analysis.l0.me16x16 );

帧间模式选择后,对该模式进行亚象素精细搜索。以进一步减少误差。值得注意的是,在前面每个模式的检测时,也要进行亚象素搜索,见 x264_me_search_ref()函数的最后几行。这里的亚象素搜索是在前面基础上再进行精细搜索的。二者亚象素搜索(包括半象素和1/4象素) 的次数由subpel_iterations[i][4]确定,而i由编译参数subme确定,看运行帮助:

-m, --subme <integer>       Subpixel motion estimation quality: 1=fast, 5=best

实际上subme就是决定模式选择前后亚象素估计的点数。Subme越大,压缩效率越好,计算量越大。

x264_mb_analyse_intra( h, &analysis, i_cost );

            if( h->mb.b_chroma_me && !analysis.b_mbrd &&

                ( analysis.i_sad_i16x16 < i_cost

               || analysis.i_sad_i8x8 < i_cost

               || analysis.i_sad_i4x4 < i_cost ))

            {

                x264_mb_analyse_intra_chroma( h, &analysis );

                analysis.i_sad_i16x16 += analysis.i_sad_i8x8chroma;

                analysis.i_sad_i8x8 += analysis.i_sad_i8x8chroma;

                analysis.i_sad_i4x4 += analysis.i_sad_i8x8chroma;

            }

分析宏块的帧内编码,包括亮度和色度。亮度的16×16、8×8、4×4代价分别存在analysis.i_sad_i16x16、 analysis.i_sad_i8x8、analysis.i_sad_i4x4中,色度代价存在analysis.i_sad_i8x8chroma 中。

i_intra_type = I_16x16;

            i_intra_cost = analysis.i_sad_i16x16;

            if( analysis.i_sad_i8x8 < i_intra_cost )

            {

                i_intra_type = I_8x8;

                i_intra_cost = analysis.i_sad_i8x8;

            }

            if( analysis.i_sad_i4x4 < i_intra_cost )

            {

                i_intra_type = I_4x4;

                i_intra_cost = analysis.i_sad_i4x4;

            }

            if( i_intra_cost < i_cost )

            {

                i_type = i_intra_type;

                i_cost = i_intra_cost;

            }

比较得到最佳的帧内预测模式。

if( i_intra_cost < i_cost )

            {

                i_type = i_intra_type;

                i_cost = i_intra_cost;

            }

帧内代价与帧间代价比较,得到最佳的预测模式。

整个P类型就分析完了,然后看看条件跳出以后执行什么

if( !analysis.b_mbrd )

        x264_mb_analyse_transform( h );

判断变换的时候是采用8×8变换还是4×4变换。


2、static inline int x264_macroblock_probe_pskip( x264_t *h )

该函数直接调用了x264_macroblock_probe_skip(h, 0);看看里边有什么东东。

if( !b_bidir )

    {

        x264_mb_predict_mv_pskip( h, mvp );

        mvp[0] = x264_clip3( mvp[0], h->mb.mv_min[0], h->mb.mv_max[0] );

        mvp[1] = x264_clip3( mvp[1], h->mb.mv_min[1], h->mb.mv_max[1] );

        h->mc.mc_luma( h->mb.pic.p_fref[0][0], h->mb.pic.i_stride[0],

                       h->mb.pic.p_fdec[0],    FDEC_STRIDE,

                       mvp[0], mvp[1], 16, 16 );

    }

先得到预测矢量MVp,然后对MVp进行饱和处理,再进行相应的运动补偿。

h->dctf.sub16x16_dct( dct4x4, h->mb.pic.p_fenc[0], FENC_STRIDE,

                                  h->mb.pic.p_fdec[0], FDEC_STRIDE );

for( i8x8 = 0, i_decimate_mb = 0; i8x8 < 4; i8x8++ )

    {

       

        for( i4x4 = 0; i4x4 < 4; i4x4++ )

        {

            const int idx = i8x8 * 4 + i4x4;


            quant_4x4( h, dct4x4[idx], (int(*)[4][4])def_quant4_mf, i_qp, 0 );

            scan_zigzag_4x4full( dctscan, dct4x4[idx] );


            i_decimate_mb += x264_mb_decimate_score( dctscan, 16 );


            if( i_decimate_mb >= 6 )

            {

               

                return 0;

            }

        }

    }

进行dct变换(注意是4×4变换,不是8×8!!!),然后对每个8×8块中的4×4对进行量化,zigzag扫描,得到8×8块的 i_decimate_mb值。如果量化后系数中只有零星的非零系数,且都是1或-1,i_decimate_mb就比较小。 if(i_decimate_mb<6),可以将系数全变为0。注意,其他模式下的残差编码也用到了该处理过程。

程序后面是对色度进行处理,与亮度类似,不进行讨论。


3、static void x264_mb_analyse_inter_p16x16 ( x264_t *h, x264_mb_analysis_t *a )

直接看核心吧。

for( i_ref = 0; i_ref < h->i_ref0; i_ref++ )

    {

循环搜索搜索每个参考帧

x264_mb_predict_mv_16x16( h, 0, i_ref, m.mvp );

得到MVp,

x264_mb_predict_mv_ref16x16( h, 0, i_ref, mvc, &i_mvc );

得到邻块的MV、前一帧对应位置的MV,可用来预测搜索起点,加速运动估计。

x264_me_search_ref( h, &m, mvc, i_mvc, p_halfpel_thresh );

运动估计函数。

再接着就是一个多参考帧的中止判断,略。


4、void x264_me_search_ref( x264_t *h, x264_me_t *m, int (*mvc)[2], int i_mvc, int *p_halfpel_thresh )

bmx = pmx = x264_clip3( ( m->mvp[0] + 2 ) >> 2, mv_x_min, mv_x_max );

    bmy = pmy = x264_clip3( ( m->mvp[1] + 2 ) >> 2, mv_y_min, mv_y_max );

    bcost = COST_MAX;

    COST_MV( pmx, pmy );

    bcost -= p_cost_mvx[ bmx<<2 ] + p_cost_mvy[ bmy<<2 ];

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

    {

        const int mx = x264_clip3( ( mvc[i][0] + 2 ) >> 2, mv_x_min, mv_x_max );

        const int my = x264_clip3( ( mvc[i][1] + 2 ) >> 2, mv_y_min, mv_y_max );

        if( mx != bmx || my != bmy )

            COST_MV( mx, my );

    }

    COST_MV( 0, 0 );

先检测MVp点,再检测其他预测矢量,最后检测原点(0,0)。注意mvp[0]保留的是1/4精度,所以除以4就变成了整象素精度。


然后就是具体的搜索算法。

case 菱形搜索:用小菱形模板反复搜索。菱形算法还有大模板搜索,这里没用到。

case 六边形:先用六边形模板反复搜索,粗匹配。然后用小菱形模板搜索一次,得到最终的整象素运动矢量(是默认选项)

case UMHexagonS:

case 连续消除法(SEA) 全搜索法的快速运算。

if( h->mb.i_subpel_refine >= 2 )

    {

        int hpel = subpel_iterations[h->mb.i_subpel_refine][2];

        int qpel = subpel_iterations[h->mb.i_subpel_refine][3];

        refine_subpel( h, m, hpel, qpel, p_halfpel_thresh, 0 );

    }

亚象素搜索,在x264_macroblock_analyse()函数我已经介绍过了


5、void x264_me_refine_qpel( x264_t *h, x264_me_t *m )

该函数一开始得到半象素、1/4象素搜索的次数(菱形小模板),分别为hpel、q hpel,然后调用refine_subpel(),去看看!

if( hpel_iters )

    {

        int mx = x264_clip3( m->mvp[0], h->mb.mv_min_spel[0], h->mb.mv_max_spel[0] );

        int my = x264_clip3( m->mvp[1], h->mb.mv_min_spel[1], h->mb.mv_max_spel[1] );

        if( mx != bmx || my != bmy )

            COST_MV_SAD( mx, my, -1 );

    }

检测MVp的小数精度。

for( i = hpel_iters; i > 0; i-- )

    {

        odir = bdir;

        omx = bmx;

        omy = bmy;

        COST_MV_SAD( omx, omy - 2, 0 );

        COST_MV_SAD( omx, omy + 2, 1 );

        COST_MV_SAD( omx - 2, omy, 2 );

        COST_MV_SAD( omx + 2, omy, 3 );

        if( bmx == omx && bmy == omy )

            break;

    }

对半象素精度进行hpel_iters次小菱形搜索。后面有1/4象素精度的qpel_iters次小模板搜索,略。


6、static uint8_t *get_ref( uint8_t *src[4], int i_src_stride, uint8_t *dst,    int * i_dst_stride, int mvx,int mvy, int i_width, int i_height )

该函数得到亚象素搜索时参考块的指针。

src1、src2分别指向半象素精度块。1/4搜索时需要临时插值,就是pixel_avg()函数的只能功能。

值得注意的是变量correction的作用,当作是1/4插值时的偏移量吧。N个人问过我,其实结合1/4象素插值,仔细推导一下就出来了。


7、static void x264_mb_analyse_intra( x264_t *h, x264_mb_analysis_t *a, int i_cost_inter )

依次检测Intra_16x16、Intra4x4、Intra8x8的最佳模式。

值得注意,if(subme<=1)

h->pixf.mbcmp是求sad值

else

h->pixf.mbcmp是求satd值

另外,对与Intra4x4、Intra8x8,此时就要进行真正的变换量化、反变换反量化、重建,因为要为后续的块做参考。而且,就算其系数值很小,也不能改变cbp,切记切记。

具体分析略。


8、static inline void x264_mb_analyse_transform( x264_t *h )

就是对残差进行4x4、8x8的satd变换,比较绝对和值,值较小对应的尺寸用于变换的尺寸。

本文来自CSDN博客,转载请标明出处: http://blog.csdn.net/qingxinguayu/archive/2007/06/28/1670140.aspx







x264中bs.h文件部分函数解读
1 写入码流函数
1) static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
该函数的作用是:向s里写入i_bits流的后i_count位,s以字节为单位,所以够8个位就写下个(注意i_bits的单位是uint32_t)。

  1. static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
  2. {
  3.     while( i_count > 0 )
  4.     {
  5.         if( s->p >= s->p_end )
  6.         {
  7.             break;
  8.         }
  9.         i_count--;
  10.         if( ( i_bits >> i_count )&0x01 )
  11.         {
  12.             *s->p |= 1 << ( s->i_left - 1 );
  13.         }
  14.         else
  15.         {
  16.             *s->p &= ~( 1 << ( s->i_left - 1 ) );
  17.         }
  18.         s->i_left--;
  19.         if( s->i_left == 0 )
  20.         {
  21.             s->p++;
  22.             s->i_left = 8;
  23.         }
  24.     }
  25. }

函数功能:
i_count是循环的次数即要写入的位数,i_bits是要编码的数,i_left是当前空闲码流的位数即记录当前字节的空余位数。将i_bits编为 i_count位的码流,每次循环,i_count和i_left都会减1,i_count和i_left并不一定相等。当i_left==0时,s-& gt;p指针指向下一个码流单元,i_left更新为8。

函数流程:
首先判断i_count是否大于0,如果是,则判断s->p是否大于s->p_end,若是则终止,否则,可以写入码流。这个条件是在判断是否有空闲的存储空间供新的码流写入。
若可以写码流,则i_count--,表明可以进行写一位的操作。注意,写i_bits是逐位写入的。
if( ( i_bits >> i_count )&0x01 )是用来判断当前要写入的i_bits位是0还是1,从而选择不同的算法来写入这个码流。如果当前要写入的是0,则选择*s->p &= ~( 1 << ( s->i_left - 1 ) )来把这个0写入码流;如果当前要写入的是1,则选择*s->p |= 1 << ( s->i_left - 1 )来把这个1写入码流。
   
写完一位码流后,初始的i_left被新写入的bit占了一位,所以i_left的值-1.
   这时判断I_left是否等于0,如果i_left还大于0,表示当前的存储单元中还有剩余的空间供码流写入,则直接进入下一次循环。如果 i_left==0时,表示当前存储单元已被写满,所以s->p指针指向下一个存储单元,i_left更新为8。这时再进入下一循环。
   
在进入下一循环的时候,先判断i_count的值,如果非零,程序继续;如果为0,表示码流已经全部写入,程序终止。

注:该函数是采用一个while循环,一次写入一个位,循环i_count次将i_bits的前i_count位写入s中。

2)static inline void bs_write1( bs_t *s, uint32_t i_bits )
该函数的作用是:将i_bits流的后1位写入s。

  1. static inline void bs_write1( bs_t *s, uint32_t i_bits )
  2. {
  3.     if( s->p < s->p_end )
  4.     {    
  5.         if( i_bits&0x01 )
  6.         {
  7.             *s->p |= 1 <<( s->i_left-1);
  8.         }
  9.         else
  10.         {
  11.             *s->p &= ~( 1 << (s->i_left-1) );
  12.         }
  13.          s->i_left--;
  14.         if( s->i_left == 0 )
  15.         {
  16.             s->p++;
  17.             s->i_left = 8;
  18.         }
  19.     }
  20. }


bs_write1()相当于bs_write(bs_t *s,1, uint32_t i_bits) 就是只写入1 bit码流。

函数流程:
首先判断s->p是否大于s->p_end,若是则终止,否则,可以写入码流。
if(  i_bits &0x01 )是用来判断当前要写入的i_bits位是0还是1,如果当前要写入的是1,则选择*s->p |= 1 <<( s->i_left-1)来把这个1写入码流;如果当前要写入的是0,则选择            *s->p &= ~( 1 << (s->i_left-1) )来把这个0写入码流。
   
写完一位码流后,初始的i_left被新写入的bit占了一位,所以i_left的值-1.
这时判断I_left是否等于0,如果i_left==0时,表示当前存储单元已被写满,所以s->p指针指向下一个存储单元,i_left更新为8。这时再进入下一循环。
   
注:上面两个write函数是从网上找的,一次写入一个位数,先判断要写入的位是0或1,再直接写入0或1,比较繁琐但是便于理解,故此附上以供参考。以下介绍的都是来自x264中的源码。

3)static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
该函数的作用是:向s里写入i_bits流的后i_count位。

  1. static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )
  2. {
  3.     if( s->p >= s->p_end - 4 )
  4.         return;
  5.     while( i_count > 0 )
  6.     {
  7.         if( i_count < 32 )
  8.             i_bits &= (1<<i_count)-1;
  9.         if( i_count < s->i_left )
  10.         {
  11.             *s->p = (*s->p << i_count) | i_bits;
  12.             s->i_left -= i_count;
  13.             break;
  14.         }
  15.         else
  16.         {
  17.             *s->p = (*s->p << s->i_left) | (i_bits >> (i_count - s->i_left));
  18.             i_count -= s->i_left;
  19.             s->p++;
  20.             s->i_left = 8;
  21.         }
  22.     }
  23. }


流程简介:
首先判断是否有空闲的存储空间供新的码流写入,若剩余不足4个字节的空间,则认为空间不够,直接返回(uint32_t是32位,要4个字节的存储空间); i_count是否大于0,如果是,则可以写入码流。这个条件是在判断是否该开始或继续写入码流。

若可以写码流,若i_count<32,则i_bits &= (1<<i_count)-1,将不需要写入的位置0。注意,该函数写i_bits不是逐位写入的。
如果要写入的位数i_count < 本字节空余的位数s->i_left,则一次性将i_count位写入:             *s->p = (*s->p << i_count) | i_bits且s->i_left -= i_count,然后完成写入break;
否则,则先写入i_left位:*s->p = (*s->p << s->i_left) | (i_bits >> (i_count - s->i_left)),i_count -= s->i_left(要写入的数减少),s->p++(进入s的下一个字节),i_left重新置位为8。
while循环直至写入i_count位。

注:该函数同1)小节中介绍的函数作用一样,不过效率要高。后面函数流程就不再详细介绍了,只说明大概思路。

4)static inline void bs_write1( bs_t *s, uint32_t i_bit )
该函数的作用是:将i_bits流的后1位写入s。

  1. static inline void bs_write1( bs_t *s, uint32_t i_bit )
  2. {
  3.     if( s->p < s->p_end )
  4.     {
  5.         *s->p <<= 1;
  6.         *s->p |= i_bit;
  7.         s->i_left--;
  8.         if( s->i_left == 0 )
  9.         {
  10.             s->p++;
  11.             s->i_left = 8;
  12.         }
  13.     }
  14. }


思路:直接写入*s->p <<= 1;
             *s->p |= i_bit;

2 读出码流函数bs_read
1)static inline uint32_t bs_read( bs_t *s, int i_count )
该函数的作用是:从s中读出i_count位,并将其做为uint32_t类型返回。

  1. static inline uint32_t bs_read( bs_t *s, int i_count )
  2. {
  3.      static uint32_t i_mask[33] ={0x00,
  4.                                   0x01,      0x03,      0x07,      0x0f,
  5.                                   0x1f,      0x3f,      0x7f,      0xff,
  6.                                   0x1ff,     0x3ff,     0x7ff,     0xfff,
  7.                                   0x1fff,    0x3fff,    0x7fff,    0xffff,
  8.                                   0x1ffff,   0x3ffff,   0x7ffff,   0xfffff,
  9.                                   0x1fffff,  0x3fffff,  0x7fffff,  0xffffff,
  10.                                   0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff,
  11.                                   0x1fffffff,0x3fffffff,0x7fffffff,0xffffffff};
  12.     int      i_shr;
  13.     uint32_t i_result = 0;

  14.     while( i_count > 0 )
  15.     {
  16.         if( s->p >= s->p_end )
  17.         {
  18.             break;
  19.         }

  20.         if( ( i_shr = s->i_left - i_count ) >= 0 )
  21.         {
  22.             /* more in the buffer than requested */
  23.             i_result |= ( *s->p >> i_shr )&i_mask[i_count];
  24.             s->i_left -= i_count;
  25.             if( s->i_left == 0 )
  26.             {
  27.                 s->p++;
  28.                 s->i_left = 8;
  29.             }
  30.             return( i_result );
  31.         }
  32.         else
  33.         {
  34.             /* less in the buffer than requested */
  35.            i_result |= (*s->p&i_mask[s->i_left]) << -i_shr;
  36.            i_count  -= s->i_left;
  37.            s->p++;
  38.            s->i_left = 8;
  39.         }
  40.     }
  41.     return( i_result );
  42. }


思路:若i_count>0且s流并未结束,则开始或继续读取码流;
     若s当前字节中剩余位数大于等于要读取的位数i_count,则直接读取;
     若s当前字节中剩余位数小于要读取的位数i_count,则读取剩余位,进入s下一字节继续读取。
注:写入s时,i_left表示s当前字节还没被写入的位,若一个新的字节,则i_left=8;
    读取s时,i_left表示s当前字节还没被读取的位,若一个新的字节,则i_left=8。
    注意两者的区别和联系。

2)static inline uint32_t bs_read1( bs_t *s )
该函数的作用是:从s中读出1位,并将其做为uint32_t类型返回。

  1. static inline uint32_t bs_read1( bs_t *s )
  2. {
  3.     if( s->p < s->p_end )
  4.     {
  5.         unsigned int i_result;

  6.         s->i_left--;
  7.         i_result = ( *s->p >> s->i_left )&0x01;
  8.         if( s->i_left == 0 )
  9.         {
  10.             s->p++;
  11.             s->i_left = 8;
  12.         }
  13.         return i_result;
  14.     }

  15.     return 0;
  16. }


思路:若s流并未结束,则读取一位。

3 指数哥伦布编码ue(v)
1)static inline void bs_write_ue( bs_t *s, unsigned int val )
该函数的作用是:将val以哥伦布编码ue(v)方式编码并写入s中。

  1. static inline void bs_write_ue( bs_t *s, unsigned int val )
  2. {
  3.     int i_size = 0;
  4.     static const int i_size0_255[256] =
  5.     {
  6.         1,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
  7.         6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
  8.         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
  9.         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
  10.         8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
  11.         8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
  12.         8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
  13.         8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
  14.     };

  15.     if( val == 0 )
  16.     {
  17.         bs_write1( s, 1 );
  18.     }
  19.     else
  20.     {
  21.         unsigned int tmp = ++val;

  22.         if( tmp >= 0x00010000 )//2^16=256*256
  23.         {
  24.             i_size += 16;
  25.             tmp >>= 16;
  26.         }
  27.         if( tmp >= 0x100 )//2^8=256
  28.         {
  29.             i_size += 8;
  30.             tmp >>= 8;
  31.         }
  32.         i_size += i_size0_255[tmp];

  33.         bs_write( s, 2 * i_size - 1, val );
  34.     }
  35. }


思路:当val=0时,写入1;
      当val!=0时:该函数主要是通过i_size0_255[256]表计算出一个i_size,然后写入val的2*i_size-1位,注意
     tmp=++val,实际写入的val比传入的val大1。
例:when val=9 ,val!=0, so tmp=10;
tmp<0x00010000 and tmp<0x100, so i_size=0+i_size0_255[10]=4;
tmp=10=00001010  and 2*i_size-1=7 ,so bs_write(s,7,00001010);
由上所述:当val=9时,写入s的字串为:0001010

2)static inline int bs_read_ue( bs_t *s )
该函数的作用是:从s中解码并读出一个语法元素值。

  1. static inline int bs_read_ue( bs_t *s )

  2. {
  3.     int i = 0;

  4.     while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )
  5.     {
  6.         i++;
  7.     }
  8.     return( ( 1 << i) - 1 + bs_read( s, i ) );
  9. }


思路:从s的当前位读取并计数,直至读取到1为止;
while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )这个循环用i记录了s当前位置到1为止的0的个数,并丢弃读到的第一个1;
返回2^i-1+bs_read(s,i)。
例:当s字节中存放的是0001010时,1前有3个0,所以i=3;
返回的是:2^i-1+bs_read(s,i)即:8-1+010=9

3)static inline int bs_size_ue( unsigned int val )
该函数的作用是:计算val经过ue(v)编码后所对应的位。

  1. static inline int bs_size_ue( unsigned int val )
  2. {
  3.     static const int i_size0_254[255] =
  4.     {
  5.         1, 3, 3, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7,
  6.         9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
  7.         11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
  8.         11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
  9.         13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
  10.         13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
  11.         13,13,13,13,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
  12.         15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
  13.         15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
  14.         15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
  15.         15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
  16.         15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15
  17.     };

  18.     if( val < 255 )
  19.     {
  20.         return i_size0_254[val];
  21.     }
  22.     else
  23.     {
  24.         int i_size = 0;

  25.         val++;

  26.         if( val >= 0x10000 )
  27.         {
  28.             i_size += 32;
  29.             val = (val >> 16) - 1;
  30.         }
  31.         if( val >= 0x100 )
  32.         {
  33.             i_size += 16;
  34.             val = (val >> 8) - 1;
  35.         }
  36.         return i_size0_254[val] + i_size;
  37.     }
  38. }


思路:其实就是计算bs_write_ue( bs_t *s, unsigned int val )函数中的2*i_size-1的值。

4 指数哥伦布编码se(v)
1)static inline void bs_write_se( bs_t *s, int val )
该函数的作用是:通过ue(v)编码实现se(v)编码的写入。

  1. static inline void bs_write_se( bs_t *s, int val )
  2. {
  3.     bs_write_ue( s, val <= 0 ? -val * 2 : val * 2 - 1);
  4. }


思路:se(v)表示有符号指数哥伦布编码,当val<=0时,codenum=-val*2;否则,codenum=val*2-1。
     然后,bs_write_ue(s,codenum);
注:在标准中的codenum只是一个假设的中间值,就像在函数中设置的一个变量一样,val才是要得到的语法元素值。
    在ue(v)编码中,val=codenum,标准中也这样描述:“如果语法元素编码为ue(v),语法元素值等于codeNum。”
    在se(v)编码中,val与codenum的关系如下:当val<=0时,codenum=-val*2;否则,codenum=val*2-1(参看标准9.1.1小节中的表9-3)。


2)static inline int bs_read_se( bs_t *s )
该函数的作用是:通过ue(v)编码实现se(v)编码的读取。

  1. static inline int bs_read_se( bs_t *s )
  2. {
  3.     int val = bs_read_ue( s );

  4.     return val&0x01 ? (val+1)/2 : -(val/2);
  5. }


思路:直接bs_read_ue( s )读取出来的实际上是codenum的值,因为“当val<=0时,codenum=-val*2;否则,codenum=val*2-1。
”,所以当codenum为奇数即 codenum&0x01>0 时,val=(codenum+1)/2,否则val=-(codenum/2)。


3)static inline int bs_size_se( int val )
该函数的作用是:通过ue(v)编码计算位数的方式实现se(v)编码的位数计算。

  1. static inline int bs_size_se( int val )
  2. {
  3.     return bs_size_ue( val <= 0 ? -val * 2 : val * 2 - 1);
  4. }

注:原理同ue(v)。

5 指数哥伦布编码te(v)
1)static inline void bs_write_te( bs_t *s, int x, int val )
该函数的作用是:通过ue(v)编码实现te(v)编码的写入。

  1. static inline void bs_write_te( bs_t *s, int x, int val )
  2. {
  3.     if( x == 1 )
  4.     {
  5.         bs_write1( s, 1&~val );
  6.     }
  7.     else if( x > 1 )
  8.     {
  9.         bs_write_ue( s, val );
  10.     }
  11. }


思路:当x=1时,将val最后一位的取反,然后写入;
      当x>1时,编码方式同ue(v)。
注:x表示语法元素的范围。注意参考标准中关于te(v)与ue(v)关系的描述。
   

2)static inline int bs_read_te( bs_t *s, int x )
该函数的作用是:通过ue(v)编码实现te(v)编码的读取。

  1. static inline int bs_read_te( bs_t *s, int x )
  2. {
  3.     if( x == 1 )
  4.     {
  5.         return 1 - bs_read1( s );
  6.     }
  7.     else if( x > 1 )
  8.     {
  9.         return bs_read_ue( s );
  10.     }
  11.     return 0;
  12. }


思路:当x=1时,直接读出一位,然后取反;
      当x>1时,读取方式同ue(v)。


3)static inline int bs_size_te( int x, int val )
该函数的作用是:通过ue(v)编码计算位数的方式实现te(v)编码的位数计算。

  1. static inline int bs_size_te( int x, int val )
  2. {
  3.     if( x == 1 )
  4.     {
  5.         return 1;
  6.     }
  7.     else if( x > 1 )
  8.     {
  9.         return bs_size_ue( val );
  10.     }
  11.     return 0;
  12. }


思路:当x=1时,直接为1;
     当x>1时,同ue(v)。

你可能感兴趣的:(x264部分函数解析)