对H.264编码标准一直停留在理解原理的基础上,对于一个实际投入使用的编码器是如何构建起来一直感觉很神秘,于是决定在理解理论的基础上潜心于编码器实现框架。关于开源的H264编码器有很多,JMVC,T264、X264,这里选择X264,因为网上关于X264源码分析资源很多。X264编码器是一个开源的经过优化的高性能H.264编码器,目前最新的源码在本人的I5处理器的PC机上,编码1920x1080分辨率的视频序列在使用ultrafast配置的情况下,可以实现160fps左右的编码速度。
这里对源码分析并没有选择最新的源码,而是选用2009年2月份的版本,原因有二:一是这个版本是从网上下载下来的,已经是一个建立好的VS2008工程,对于像我这种用惯IDE调试的程序员来说,这可以大大提高源码阅读速度;二是虽然X264源码虽然几乎每天都有更新,但是从这个版本以后,最大的改动基本是针对多Slice编码的支持,其他都是在输入输出方面的一些改动,从这个版本学习可以较快进入编码部分的学习;三是这个版本当中已经有别人的很多的注释,便于自己理解;
一般将编码分为帧级、片级、宏块级编码,依次从上到下。下面就从这三个级分析X264编码流程:
一、帧级编码分析
定位到x264_encoder_encode这个函数,这个函数应该是H264编码最上层的函数,实现编码一帧视频。在进行下一步分析之前有必要了解,控制X264编码的全局性结构体x264_t,这个结构体控制着视频一帧一帧的编码,包括中间参考帧管理、码率控制、全局参数等一些重要参数和结构体。
下面是x264_t这个结构体的定义(这里仅对几个关键的结构和变量进行分析):
- struct x264_t
- {
- x264_param_t param;
-
- ...........
- int i_frame;
- ...........
- int i_nal_type;
-
- int i_nal_ref_idc;
-
-
- x264_sps_t sps_array[1];
- x264_sps_t *sps;
- x264_pps_t pps_array[1];
- x264_pps_t *pps;
- int i_idr_pic_id;
-
- ......
-
- struct
- {
-
- x264_frame_t *current[X264_BFRAME_MAX*4+3];
- x264_frame_t *next[X264_BFRAME_MAX*4+3];
- x264_frame_t *unused[X264_BFRAME_MAX*4 + X264_THREAD_MAX*2 + 16+4];
-
- x264_frame_t *last_nonb;
-
-
- x264_frame_t *reference[16+2];
-
- int i_last_idr;
-
- int i_input; //frames结构体中i_input指示当前输入的帧的(播放顺序)序号。
-
- int i_max_dpb;
- int i_max_ref0;
- int i_max_ref1;
- int i_delay;
-
-
- int b_have_lowres;
- int b_have_sub8x8_esa;
- } frames;
-
-
- x264_frame_t *fenc;
-
-
- x264_frame_t *fdec;
-
-
- int i_ref0;
- x264_frame_t *fref0[16+3];
- int i_ref1;
- x264_frame_t *fref1[16+3];
- int b_ref_reorder[2];
- ........
- };
定位到x264_encoder_encode这个函数,这个函数应该是H264编码最上层的函数,实现编码的帧级处理(如何进行参考帧管理、帧类型确定等等)。
下面对x264_encoder_encode中几个关键函数以及关键部分进行分析:
1、x264_reference_update这个函数主要完成参考帧的更新,H.264的帧间预测需要使用参考帧,参考帧使用的都是已编码后的重建帧,每编码一帧的同时会重建此帧作为参考帧,在编码下一帧时,将此重建帧加入到参考帧队列中。函数实现如下:
- static inline void x264_reference_update( x264_t *h )
- {
- int i;
-
- if( h->fdec->i_frame >= 0 )
- h->i_frame++;
-
- if( !h->fdec->b_kept_as_ref )
- {
- if( h->param.i_threads > 1 )
- {
- x264_frame_push_unused( h, h->fdec );
- h->fdec = x264_frame_pop_unused( h );
- }
- return;
- }
-
-
- for( i = 0; i < 4; i++)
- {
- XCHG( uint8_t*, h->fdec->lowres[i], h->fenc->lowres[i] );
- XCHG( uint8_t*, h->fdec->buffer_lowres[i], h->fenc->buffer_lowres[i] );
- }
-
-
- if( h->sh.i_type != SLICE_TYPE_B )
- h->frames.last_nonb = h->fdec;
-
-
- x264_frame_push( h->frames.reference, h->fdec );
- if( h->frames.reference[h->frames.i_max_dpb] )
- x264_frame_push_unused( h, x264_frame_shift( h->frames.reference ) );
- h->fdec = x264_frame_pop_unused( h );
- }
2、帧排序部分,在H.264标准中采用编码顺序与显示顺序不同的编码方式,对于一个已经确定帧类型的待编码序列:IBBPBBP在编码时需要先排序为IPBBPBB,然后进行编码。在X264代码中,实现在如下部分:
-
- x264_stack_align( x264_slicetype_decide, h );
-
-
- while( IS_X264_TYPE_B( h->frames.next[bframes]->i_type ) )
- bframes++;
- x264_frame_push( h->frames.current, x264_frame_shift( &h->frames.next[bframes] ) );
-
- if( h->param.b_bframe_pyramid && bframes > 1 )
- {
- x264_frame_t *mid = x264_frame_shift( &h->frames.next[bframes/2] );
- mid->i_type = X264_TYPE_BREF;
- x264_frame_push( h->frames.current, mid );
- bframes--;
- }
- while( bframes-- )
- x264_frame_push( h->frames.current, x264_frame_shift( h->frames.next ) );
下面就可以从h->frames.current队列中取出第一帧放入h->fenc中
- h->fenc = x264_frame_shift( h->frames.current );
然后就开始编码,首先当前编码帧(h->fenc)的类型,设定slice类型,这里就不解释了,关于IDR帧,执行了x264_reference_reset这个函数将参考帧队列清空。
接着进行相关参数的赋值,这里主要对POC的计算强调一下:
- h->fenc->i_poc = 2 * (h->fenc->i_frame - h->frames.i_last_idr);
3、重建参考帧列表(x264_reference_build_list),即将参考帧列表中的参考帧分为前向参考帧和后向参考帧,并根据POC进行参考帧排序。函数具体实现如下:
- static inline void x264_reference_build_list( x264_t *h, int i_poc )
- {
- int i;
- int b_ok;
-
-
- h->i_ref0 = 0;
- h->i_ref1 = 0;
- for( i = 0; h->frames.reference[i]; i++ )
- {
- if( h->frames.reference[i]->i_poc < i_poc )
- {
- h->fref0[h->i_ref0++] = h->frames.reference[i];
- }
- else if( h->frames.reference[i]->i_poc > i_poc )
- {
- h->fref1[h->i_ref1++] = h->frames.reference[i];
- }
- }
-
-
- do
- {
- b_ok = 1;
- for( i = 0; i < h->i_ref0 - 1; i++ )
- {
- if( h->fref0[i]->i_poc < h->fref0[i+1]->i_poc )
- {
- XCHG( x264_frame_t*, h->fref0[i], h->fref0[i+1] );
- b_ok = 0;
- break;
- }
- }
- } while( !b_ok );
-
- do
- {
- b_ok = 1;
- for( i = 0; i < h->i_ref1 - 1; i++ )
- {
- if( h->fref1[i]->i_poc > h->fref1[i+1]->i_poc )
- {
- XCHG( x264_frame_t*, h->fref1[i], h->fref1[i+1] );
- b_ok = 0;
- break;
- }
- }
- } while( !b_ok );
-
-
-
- h->b_ref_reorder[0] =
- h->b_ref_reorder[1] = 0;
- if( h->sh.i_type == SLICE_TYPE_P )
- {
- for( i = 0; i < h->i_ref0 - 1; i++ )
- if( h->fref0[i]->i_frame_num < h->fref0[i+1]->i_frame_num )
- {
- h->b_ref_reorder[0] = 1;
- break;
- }
- }
-
- h->i_ref1 = X264_MIN( h->i_ref1, h->frames.i_max_ref1 );
- h->i_ref0 = X264_MIN( h->i_ref0, h->frames.i_max_ref0 );
- h->i_ref0 = X264_MIN( h->i_ref0, h->param.i_frame_reference );
- assert( h->i_ref0 + h->i_ref1 <= 16 );
- h->mb.pic.i_fref[0] = h->i_ref0;
- h->mb.pic.i_fref[1] = h->i_ref1;
- }
4、初始化比特流,写入SPS以及PPS信息后就开始进行片级编码。
- if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers )
- {
-
- if( h->fenc->i_frame == 0 )
- {
-
- <span style="white-space:pre"> </span>x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );
- x264_sei_version_write( h, &h->out.bs );
- x264_nal_end( h );
- }
-
-
- 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_slices_write( h );
二、片级编码分析
未完!待续。。。
http://blog.csdn.net/xingyu19871124/article/details/7671634