x264开源编码-供初学参考???(转)

来源:百度文库 编辑:神马文学网 时间:2024/04/29 07:13:57
经过一段时间的学习我对h264也有了一个初步的大体的了解,今天在这里说一下h264中x264的开源code的编码的解析并附一张我自己画的流程图便于大家理解,又不对的地方清大家指教一二,偶必定三顾茅庐寻得真理。:)
首先我们进入x264.c中的main函数.
刚开始是读取默认参数,如果你设置了参数的话会修改param的.
i_ret = Encode( ¶m, fin, fout );
这条语句使过程进入x264.c中的Encode函数.(这个函数就是x264的编码程序)
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 )
}
这段调用了fseek()函数,对输入的视频文件计算其总帧数。
B.     函数h = x264_encoder_open( param)对不正确的参数进行修改,并对各结构体参数和cabac编码,预测等需要的参数进行初始化.然后才能进行下一步的编码。
C.    函数 pic = x264_picture_new( h );定义在\CORE\common.c中.
此函数的作用是分给能容纳sizeof(x264_picture_t)字节数的空间,然后进行初始化.
这里说明一下x264_picture_t和x264_frame_t的区别.前者是说明一个视频序列中每帧的特点.后者存放每帧实际的象素值.
D.       调用fread()函数一次读入一帧,分亮度和色度分别读取.这里要看到c语言中的File文件有一个文件位置指示器,调用fread()函数会使文件指示器自动移位,这就是一帧一帧读取的实现过程.
for( i_frame = 0, i_file = 0; i_ctrl_c == 0 ; i_frame++ )
{
int        i_nal;
x264_nal_t  *nal;
int        i;
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\n” );
}
……
}
E.     进入x264_encoder_encode( h, &nal,&i_nal, pic )函数,该函数定义在/Enc/encoder.c中.
函数中先定义了如下三个参数:
int    i_nal_type;   nal存放的数据类型,可以是sps,pps等多种.
int    i_nal_ref_idc;  nal的优先级,nal重要性的标志位.
int    i_slice_type;  slice的类型的
这里先说明一下:我们假设一个视频序列如下:
I  B  B P B B  P
我们编码是按I P  B B  P B  B的顺序,这就是frame的编号
但是编码器如何来区分他们并把他们重新排序呢?
我们来看看编码器是如何区分读入的一帧是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)//判断h是否为B帧然后对其进行下一步操作.
我们编完I帧后碰到了一个B帧,这时我们先不对它进编码.而是采用frame=        x264_encoder_frame_put_from_picture( h,h->frame_next, pic)函数将这个B帧放进h->frame_next中.
在h中同时定义了下面几个帧数组用以实现帧的管理.
x264_frame_t  *bframe_current[X264_BFRAME_MAX];
x264_frame_t   *frame_next[X264_BFRAME_MAX+1];  //这个是定义下一个帧,但不一定是B帧.
x264_frame_t   *frame_unused[X264_BFRAME_MAX+1];
同时还有下面4个函数(定义在\ENCODER\encoder.c中).
x264_encoder_frame_put_from_picture();
x264_encoder_frame_put() ();
x264_encoder_frame_get();
x264_frame_copy_picture();
这3个数组和4个函数可以说完成了整个帧的类型的判定问题.在不对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帧编码.依此类推.(帧的有关理解学习笔记(二))
F.    建立参考帧列表的操作,这里调用了函数x264_reference_build_list( h,h->fdec->i_poc );(定义在\ENCODER\encoder.c中).
光调用这个函数是不行的,它是和后面的这个函数(如下)一起配合工作的.
if( i_nal_ref_idc != NAL_PRIORITY_DISPOSABLE )//判断为B帧.
{
x264_reference_update( h );
}
If条件是判断当前帧是否是B帧,如果是的话就不更新参考列表,因为B帧本来就不能作为参考帧嘛!如果是I帧或P帧的话,就更新参考帧列表.
G.    下面是写slice的操作.
h->out.i_nal = 0;//out的声明在bs.h中.
bs_init( &h->out.bs,h->out.p_bitstream,h->out.i_bitstream );//空出8位.
if( i_nal_type == NAL_SLICE_IDR )
{
x264_nal_start( h, NAL_SPS,NAL_PRIORITY_HIGHEST );
x264_sps_write( &h->out.bs,h->sps );
x264_nal_end( h );
x264_nal_start( h, NAL_PPS,NAL_PRIORITY_HIGHEST );
x264_pps_write( &h->out.bs,h->pps );
x264_nal_end( h );
x264_slice_write()(定义在\ENCODER\encoder.c中),这里面是编码的最主要部分..
下面这个循环,它是采用for循环对一帧图像的所有块依次进行编码.
for( mb_xy = 0, i_skip = 0; mb_xy sps->i_mb_width *h->sps->i_mb_height; mb_xy++)//h->sps->i_mb_width指的是从宽度上说有多少个宏快.   {
const int i_mb_y = mb_xy /h->sps->i_mb_width;
constinti_mb_x = mb_xy %h->sps->i_mb_width;//这两个变量是定义宏块的位置..
x264_macroblock_cache_load( h, i_mb_x, i_mb_y);//是把当前宏块的up宏块和left宏块的intra4×4_pred_mode,non_zero_count加载进来,放到一个数组里面,这个数组用来直接得到当前宏块的左侧和上面宏块的相关值.要想得到当前块的预测值,要先知道上面,左面的预测值,它的目的是替代getneighbour函数.
TIMER_START(i_mtime_analyse);
x264_macroblock_analyse( h);//定义在analyse.h中.
TIMER_STOP(i_mtime_analyse);
TIMER_START( i_mtime_encode );
x264_macroblock_encode( h );//定义在Enc/encoder.c中.
TIMER_STOP( i_mtime_encode );
到这就已经完成编码的主要过程了,后面就是熵编码的过程了.