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变换,比较绝对和值,值较小对应的尺寸用于变换的尺寸。
本文来自优快云博客,转载请标明出处: http://blog.youkuaiyun.com/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)。
函数功能:
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。
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位。
流程简介:
首先判断是否有空闲的存储空间供新的码流写入,若剩余不足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。
思路:直接写入*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类型返回。
思路:若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类型返回。
思路:若s流并未结束,则读取一位。
3 指数哥伦布编码ue(v)
1)static inline void bs_write_ue( bs_t *s, unsigned int val )
该函数的作用是:将val以哥伦布编码ue(v)方式编码并写入s中。
思路:当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中解码并读出一个语法元素值。
思路:从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)编码后所对应的位。
思路:其实就是计算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)编码的写入。
思路: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)编码的读取。
思路:直接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)编码的位数计算。
注:原理同ue(v)。
5 指数哥伦布编码te(v)
1)static inline void bs_write_te( bs_t *s, int x, int val )
该函数的作用是:通过ue(v)编码实现te(v)编码的写入。
思路:当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)编码的读取。
思路:当x=1时,直接读出一位,然后取反;
当x>1时,读取方式同ue(v)。
3)static inline int bs_size_te( int x, int val )
该函数的作用是:通过ue(v)编码计算位数的方式实现te(v)编码的位数计算。
思路:当x=1时,直接为1;
当x>1时,同ue(v)。
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变换,比较绝对和值,值较小对应的尺寸用于变换的尺寸。
本文来自优快云博客,转载请标明出处: http://blog.youkuaiyun.com/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)。