学习笔记(一)(x264编码流程)

  • Post author:
  • Post category:其他





学习笔记(一)(x264编码流程)




<script type=text/javascript>
</script> <script src=”http://pagead2.googlesyndication.com/pagead/show_ads.js” type=text/javascript> </script> <script> window.google_render_ad(); </script>

经过一段时间的学习我对h264也有了一个初步的大体的了解,今天在这里说一下h264中x264的开源code的编码的解析并附一张我自己画的流程图便于大家理解,又不对的地方清大家指教一二,偶必定三顾茅庐寻得真理。:)

首先我们 进入x264.c中的main函数.

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

i_ret = Encode( &param, 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;

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

同时还有下面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的操作.

/* 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 )

{


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

x264_slice_write()(定义在/ENCODER/encoder.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指的是从宽度上说有多少个宏快.    {


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宏块的intra4×4_pred_mode,non_zero_count加载进来,放到一个数组里面,这个数组用来直接得到当前宏块的左侧和上面宏块的相关值.要想得到当前块的预测值,要先知道上面,左面的预测值,它的目的是替代getneighbour函数.

/* analyse parameters

* Slice I: choose I_4×4 or I_16×16 mode

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

* */

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

到这就已经完成编码的主要过程了,后面就是熵编码的过程了.