您的位置:首页 > 其它

x264部分函数解析

2012-10-30 12:27 260 查看
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)。

static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )

{

while( i_count > 0 )

{

if( s->p >= s->p_end )

{

break;

}

i_count--;

if( ( i_bits >> i_count )&0x01 )

{

*s->p |= 1 << ( s->i_left - 1 );

}

else

{

*s->p &= ~( 1 << ( s->i_left - 1 ) );

}

s->i_left--;

if( s->i_left == 0 )

{

s->p++;

s->i_left = 8;

}

}

}

函数功能:

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。



static inline void bs_write1( bs_t *s, uint32_t i_bits )

{

if( s->p < s->p_end )

{

if( i_bits&0x01 )

{

*s->p |= 1 <<( s->i_left-1);

}

else

{

*s->p &= ~( 1 << (s->i_left-1) );

}

s->i_left--;

if( s->i_left == 0 )

{

s->p++;

s->i_left = 8;

}

}

}

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位。



static inline void bs_write( bs_t *s, int i_count, uint32_t i_bits )

{

if( s->p >= s->p_end - 4 )

return;

while( i_count > 0 )

{

if( i_count < 32 )

i_bits &= (1<<i_count)-1;

if( i_count < s->i_left )

{

*s->p = (*s->p << i_count) | i_bits;

s->i_left -= i_count;

break;

}

else

{

*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;

}

}

}

流程简介:

首先判断是否有空闲的存储空间供新的码流写入,若剩余不足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。



static inline void bs_write1( bs_t *s, uint32_t i_bit )

{

if( s->p < s->p_end )

{

*s->p <<= 1;

*s->p |= i_bit;

s->i_left--;

if( s->i_left == 0 )

{

s->p++;

s->i_left = 8;

}

}

}

思路:直接写入*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类型返回。



static inline uint32_t bs_read( bs_t *s, int i_count )

{

static uint32_t i_mask[33] ={0x00,

0x01, 0x03, 0x07, 0x0f,

0x1f, 0x3f, 0x7f, 0xff,

0x1ff, 0x3ff, 0x7ff, 0xfff,

0x1fff, 0x3fff, 0x7fff, 0xffff,

0x1ffff, 0x3ffff, 0x7ffff, 0xfffff,

0x1fffff, 0x3fffff, 0x7fffff, 0xffffff,

0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff,

0x1fffffff,0x3fffffff,0x7fffffff,0xffffffff};

int i_shr;

uint32_t i_result = 0;

while( i_count > 0 )

{

if( s->p >= s->p_end )

{

break;

}

if( ( i_shr = s->i_left - i_count ) >= 0 )

{

/* more in the buffer than requested */

i_result |= ( *s->p >> i_shr )&i_mask[i_count];

s->i_left -= i_count;

if( s->i_left == 0 )

{

s->p++;

s->i_left = 8;

}

return( i_result );

}

else

{

/* less in the buffer than requested */

i_result |= (*s->p&i_mask[s->i_left]) << -i_shr;

i_count -= s->i_left;

s->p++;

s->i_left = 8;

}

}

return( i_result );

}

思路:若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类型返回。



static inline uint32_t bs_read1( bs_t *s )

{

if( s->p < s->p_end )

{

unsigned int i_result;

s->i_left--;

i_result = ( *s->p >> s->i_left )&0x01;

if( s->i_left == 0 )

{

s->p++;

s->i_left = 8;

}

return i_result;

}

return 0;

}

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

3 指数哥伦布编码ue(v)

1)static inline void bs_write_ue( bs_t *s, unsigned int val )

该函数的作用是:将val以哥伦布编码ue(v)方式编码并写入s中。



static inline void bs_write_ue( bs_t *s, unsigned int val )

{

int i_size = 0;

static const int i_size0_255[256] =

{

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,

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,

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,

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,

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,

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,

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,

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

};

if( val == 0 )

{

bs_write1( s, 1 );

}

else

{

unsigned int tmp = ++val;

if( tmp >= 0x00010000 )//2^16=256*256

{

i_size += 16;

tmp >>= 16;

}

if( tmp >= 0x100 )//2^8=256

{

i_size += 8;

tmp >>= 8;

}

i_size += i_size0_255[tmp];

bs_write( s, 2 * i_size - 1, val );

}

}

思路:当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中解码并读出一个语法元素值。



static inline int bs_read_ue( bs_t *s )

{

int i = 0;

while( bs_read1( s ) == 0 && s->p < s->p_end && i < 32 )

{

i++;

}

return( ( 1 << i) - 1 + bs_read( s, i ) );

}

思路:从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)编码后所对应的位。



static inline int bs_size_ue( unsigned int val )

{

static const int i_size0_254[255] =

{

1, 3, 3, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7,

9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,

11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,

11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,13,13,13,13,13,13,

13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,

13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,

13,13,13,13,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,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,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

};

if( val < 255 )

{

return i_size0_254[val];

}

else

{

int i_size = 0;

val++;

if( val >= 0x10000 )

{

i_size += 32;

val = (val >> 16) - 1;

}

if( val >= 0x100 )

{

i_size += 16;

val = (val >> 8) - 1;

}

return i_size0_254[val] + i_size;

}

}

思路:其实就是计算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)编码的写入。



static inline void bs_write_se( bs_t *s, int val )

{

bs_write_ue( s, val <= 0 ? -val * 2 : val * 2 - 1);

}

思路: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)编码的读取。



static inline int bs_read_se( bs_t *s )

{

int val = bs_read_ue( s );

return val&0x01 ? (val+1)/2 : -(val/2);

}

思路:直接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)编码的位数计算。



static inline int bs_size_se( int val )

{

return bs_size_ue( val <= 0 ? -val * 2 : val * 2 - 1);

}

注:原理同ue(v)。

5 指数哥伦布编码te(v)

1)static inline void bs_write_te( bs_t *s, int x, int val )

该函数的作用是:通过ue(v)编码实现te(v)编码的写入。



static inline void bs_write_te( bs_t *s, int x, int val )

{

if( x == 1 )

{

bs_write1( s, 1&~val );

}

else if( x > 1 )

{

bs_write_ue( s, val );

}

}

思路:当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)编码的读取。



static inline int bs_read_te( bs_t *s, int x )

{

if( x == 1 )

{

return 1 - bs_read1( s );

}

else if( x > 1 )

{

return bs_read_ue( s );

}

return 0;

}

思路:当x=1时,直接读出一位,然后取反;

当x>1时,读取方式同ue(v)。

3)static inline int bs_size_te( int x, int val )

该函数的作用是:通过ue(v)编码计算位数的方式实现te(v)编码的位数计算。



static inline int bs_size_te( int x, int val )

{

if( x == 1 )

{

return 1;

}

else if( x > 1 )

{

return bs_size_ue( val );

}

return 0;

}

思路:当x=1时,直接为1;

当x>1时,同ue(v)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: