学习笔记(一)(x264编码流程)
</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( ¶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;
/* 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 );
到这就已经完成编码的主要过程了,后面就是熵编码的过程了.