x264详细讲解

来源:互联网 发布:建信优化成长基金净值 编辑:程序博客网 时间:2024/05/17 10:42

在学习过程中感觉此篇文章很好,转载一下方便自己以后学习,非常感觉作者,解决我对各结构不了解的困扰

http://blog.csdn.net/xingyu19871124/article/details/7671634


一、帧级编码分析

定位到x264_encoder_encode这个函数,这个函数应该是H264编码最上层的函数,实现编码一帧视频。在进行下一步分析之前有必要了解,控制X264编码的全局性结构体x264_t,这个结构体控制着视频一帧一帧的编码,包括中间参考帧管理、码率控制、全局参数等一些重要参数和结构体。
下面是x264_t这个结构体的定义(这里仅对几个关键的结构和变量进行分析):

[cpp] view plaincopy
  1. struct x264_t  
  2. {  
  3.     x264_param_t    param;//编码器编码参数,包括量化步长、编码级别等等一些参数  
  4.   
  5. ...........  
  6.     int             i_frame;//编码帧号,用于计算POC(picture of count 标识视频帧的解码顺序)  
  7. ...........  
  8.     int             i_nal_type;     /* Nal 单元的类型,可以查看编码标准,有哪几种类型,需要理解的类型有:不分区(一帧作为一个片)非IDR图像的片;片分区A、片分区B、片分区C、IDR图像中的片、序列参数集、图像参数集 */  
  9.   
  10.     int             i_nal_ref_idc;  /* Nal 单元的优先级别<span style="background-color: rgb(255, 255, 255); ">取值范围[0,1,2,3],值越大表示优先级越高,此Nal单元就越重要</span>*/  
  11.   
  12.     /* We use only one SPS(序列参数集) and one PPS(图像参数集) */  
  13.     x264_sps_t      sps_array[1];//结构体的数组  
  14.     x264_sps_t      *sps;  
  15.     x264_pps_t      pps_array[1];  
  16.     x264_pps_t      *pps;  
  17.     int             i_idr_pic_id;  
  18.   
  19. ......  
  20.   
  21.     struct  
  22.     {  
  23. //这个结构体涉及到X264编码过程中的帧管理,理解这个结构体中的变量在编码标准的理论意义是非常重要的  
  24.         x264_frame_t *current[X264_BFRAME_MAX*4+3];/*已确定帧类型,待编码帧,每一个GOP在编码前,每一帧的类型在编码前已经确定。当进行编码时,从这里取出一帧数据。*/  
  25.         x264_frame_t *next[X264_BFRAME_MAX*4+3];//尚未确定帧类型的待编码帧,当确定后,会将此数组中的帧转移到current数组中去。  
  26.         x264_frame_t *unused[X264_BFRAME_MAX*4 + X264_THREAD_MAX*2 + 16+4];/*这个数组用于回收那些在编码中分配的frame空间,当有新的需要时,直接拿过来用,不用重新分配新的空间,提高效率*/  
  27.         /* For adaptive B decision */  
  28.         x264_frame_t *last_nonb;  
  29.   
  30.         /* frames used for reference + sentinels */  
  31.         x264_frame_t *reference[16+2];//参考帧队列,注意参考帧都是重建帧  
  32.   
  33.         int i_last_idr; /* 上一次刷新关键帧的帧号,配合前面的i_frame,可以用来计算POC */  
  34.   
  35.         int i_input;    /* Number of input frames already accepted *///frames结构体中i_input指示当前输入的帧的(播放顺序)序号。  
  36.   
  37.         int i_max_dpb;  /* 分配解码图像缓冲的最大数量(DPB) */  
  38.         int i_max_ref0;//最大前向参考帧数量  
  39.         int i_max_ref1;//最大后向参考帧数量  
  40.         int i_delay;    /* Number of frames buffered for B reordering */  
  41.         //i_delay设置为由B帧个数(线程个数)确定的帧缓冲延迟,在多线程情况下为i_delay = i_bframe + i_threads - 1。  
  42.         //而判断B帧缓冲填充是否足够则通过条件判断:h->frames.i_input <= h->frames.i_delay + 1 - h->param.i_threads。  
  43.         int b_have_lowres;  /* Whether 1/2 resolution luma planes are being used */  
  44.         int b_have_sub8x8_esa;  
  45.     } frames;//指示和控制帧编码过程的结构  
  46.   
  47.     /* current frame being encoded */  
  48.     x264_frame_t    *fenc;//指向当前编码帧  
  49.   
  50.     /* frame being reconstructed */  
  51.     x264_frame_t    *fdec;//指向当前重建帧,重建帧的帧号要比当前编码帧的帧号小1  
  52.   
  53.     /* references lists */  
  54.     int             i_ref0;//前向参考帧的数量  
  55.     x264_frame_t    *fref0[16+3];     /* 存放前向参考帧的数组(注意参考帧均是重建帧) */  
  56.     int             i_ref1;//后向参考帧的数量  
  57.     x264_frame_t    *fref1[16+3];     /* 存放后向参考帧的数组*/  
  58.     int             b_ref_reorder[2];  
  59. ........  
  60. };  

定位到x264_encoder_encode这个函数,这个函数应该是H264编码最上层的函数,实现编码的帧级处理(如何进行参考帧管理、帧类型确定等等)。
 下面对x264_encoder_encode中几个关键函数以及关键部分进行分析:
1、x264_reference_update这个函数主要完成参考帧的更新,H.264的帧间预测需要使用参考帧,参考帧使用的都是已编码后的重建帧,每编码一帧的同时会重建此帧作为参考帧,在编码下一帧时,将此重建帧加入到参考帧队列中。函数实现如下:
[cpp] view plaincopy
  1. static inline void x264_reference_update( x264_t *h )  
  2. {  
  3.     int i;  
  4.   
  5.     if( h->fdec->i_frame >= 0 )//重建帧帧数大于等于零时  
  6.         h->i_frame++;//当前编码帧的帧号要比重建帧的帧号大1  
  7.   
  8.     if( !h->fdec->b_kept_as_ref )/*如果重建帧不作为参考帧(不作为参考帧,当然不用加入参考帧队列了)*/  
  9.     {//when b frame is not used as reference frame  
  10.         if( h->param.i_threads > 1 )  
  11.         {  
  12.             x264_frame_push_unused( h, h->fdec );  
  13.             h->fdec = x264_frame_pop_unused( h );  
  14.         }  
  15.         return;//if b-frame is not used as reference, return   
  16.     }  
  17.   
  18.     /* move lowres(低分辨率) copy of the image to the ref frame */  
  19.     for( i = 0; i < 4; i++)  
  20.     {/*暂时还不知道干嘛的*/  
  21.         XCHG( uint8_t*, h->fdec->lowres[i], h->fenc->lowres[i] );  
  22.         XCHG( uint8_t*, h->fdec->buffer_lowres[i], h->fenc->buffer_lowres[i] );  
  23.     }  
  24.   
  25.     /* adaptive B decision needs a pointer, since it can't use the ref lists */  
  26.     if( h->sh.i_type != SLICE_TYPE_B )  
  27.         h->frames.last_nonb = h->fdec;  
  28.   
  29.     /* move frame in the buffer */  
  30.     x264_frame_push( h->frames.reference, h->fdec );/*把重建帧放入参考队列中*/  
  31.     if( h->frames.reference[h->frames.i_max_dpb] )/*如果参考帧的个数大于解码图像缓存的最大数(decoded picture buffer(DPB))*/  
  32.         x264_frame_push_unused( h, x264_frame_shift( h->frames.reference ) );/*取出参考队列中第一个参考重建帧,并放入暂时不用帧队列中*/  
  33.     h->fdec = x264_frame_pop_unused( h );/*从暂时不用帧队列中,取出一帧作为新的重建帧buf*/  
  34. }  
2、帧排序部分,在H.264标准中采用编码顺序与显示顺序不同的编码方式,对于一个已经确定帧类型的待编码序列:IBBPBBP在编码时需要先排序为IPBBPBB,然后进行编码。在X264代码中,实现在如下部分:
[cpp] view plaincopy
  1. //确定帧的类型  
  2.       x264_stack_align( x264_slicetype_decide, h );/*通过x264_slicetype_decide函数来确定决定h-frames.next[]中每一帧的类型*/  
  3.   
  4.       /* 3: move some B-frames and 1 non-B to encode queue 这里来完成帧排序,还是有点巧妙的*/  
  5.       while( IS_X264_TYPE_B( h->frames.next[bframes]->i_type ) )  
  6.           bframes++;/*注意这个循环查找的作用,一方面可以确定第一个非B帧之前B帧的数量,也可以定位出第一个非B帧的位置*/  
  7.       x264_frame_push( h->frames.current, x264_frame_shift( &h->frames.next[bframes] ) );/*取出第一个非B帧,并放到current指针第一个位置:通过这两步完成帧排序的(例如BBP->PBB)*/  
  8.       /* FIXME: when max B-frames > 3, BREF may no longer be centered after GOP closing */  
  9.       if( h->param.b_bframe_pyramid && bframes > 1 )  
  10.       {  
  11.           x264_frame_t *mid = x264_frame_shift( &h->frames.next[bframes/2] );  
  12.           mid->i_type = X264_TYPE_BREF;  
  13.           x264_frame_push( h->frames.current, mid );  
  14.           bframes--;  
  15.       }  
  16.       while( bframes-- )  
  17.           x264_frame_push( h->frames.current, x264_frame_shift( h->frames.next ) ); /*然后依次取出B帧,并放到current队列中*/  
下面就可以从h->frames.current队列中取出第一帧放入h->fenc中
[cpp] view plaincopy
  1. h->fenc = x264_frame_shift( h->frames.current );//从当前编码帧中取出第一帧,作为当前编码帧  
然后就开始编码,首先当前编码帧(h->fenc)的类型,设定slice类型,这里就不解释了,关于IDR帧,执行了x264_reference_reset这个函数将参考帧队列清空。
接着进行相关参数的赋值,这里主要对POC的计算强调一下:
[cpp] view plaincopy
  1. h->fenc->i_poc = 2 * (h->fenc->i_frame - h->frames.i_last_idr);//考虑到场编码,POC每帧增长为2,如果是场编码POC增长为1  
3、重建参考帧列表(x264_reference_build_list),即将参考帧列表中的参考帧分为前向参考帧和后向参考帧,并根据POC进行参考帧排序。函数具体实现如下:
[cpp] view plaincopy
  1. static inline void x264_reference_build_list( x264_t *h, int i_poc )  
  2. {  
  3.     int i;  
  4.     int b_ok;  
  5.   
  6.     /* build ref list 0/1 */  
  7.     h->i_ref0 = 0;//前向参考帧索引  
  8.     h->i_ref1 = 0;//后向参考帧索引  
  9.     for( i = 0; h->frames.reference[i]; i++ )  
  10.     {//注意这里都是指针操作  
  11.         if( h->frames.reference[i]->i_poc < i_poc )  
  12.         {//小于当前帧POC的,放到前向参考帧列表中  
  13.             h->fref0[h->i_ref0++] = h->frames.reference[i];  
  14.         }  
  15.         else if( h->frames.reference[i]->i_poc > i_poc )  
  16.         {//大于当前帧POC的,放到后向参考帧列表中  
  17.             h->fref1[h->i_ref1++] = h->frames.reference[i];  
  18.         }  
  19.     }  
  20.   
  21.     /* Order ref0 from higher to lower poc */  
  22.     do  
  23.     {/*采用冒泡排序(不知道使用dowhile+for循环与双重for循环有什么优势),对参考帧按照POC从高到低进行排序*/  
  24.         b_ok = 1;  
  25.         for( i = 0; i < h->i_ref0 - 1; i++ )  
  26.         {  
  27.             if( h->fref0[i]->i_poc < h->fref0[i+1]->i_poc )  
  28.             {  
  29.                 XCHG( x264_frame_t*, h->fref0[i], h->fref0[i+1] );  
  30.                 b_ok = 0;  
  31.                 break;  
  32.             }  
  33.         }  
  34.     } while( !b_ok );  
  35.     /* Order ref1 from lower to higher poc (bubble sort) for B-frame */  
  36.     do  
  37.     {  
  38.         b_ok = 1;  
  39.         for( i = 0; i < h->i_ref1 - 1; i++ )  
  40.         {  
  41.             if( h->fref1[i]->i_poc > h->fref1[i+1]->i_poc )  
  42.             {  
  43.                 XCHG( x264_frame_t*, h->fref1[i], h->fref1[i+1] );  
  44.                 b_ok = 0;  
  45.                 break;  
  46.             }  
  47.         }  
  48.     } while( !b_ok );  
  49.   
  50.     /* In the standard, a P-frame's ref list is sorted by frame_num. 
  51.      * We use POC, but check whether explicit reordering is needed */  
  52.     h->b_ref_reorder[0] =  
  53.     h->b_ref_reorder[1] = 0;  
  54.     if( h->sh.i_type == SLICE_TYPE_P )  
  55.     {  
  56.         for( i = 0; i < h->i_ref0 - 1; i++ )  
  57.             if( h->fref0[i]->i_frame_num < h->fref0[i+1]->i_frame_num )  
  58.             {  
  59.                 h->b_ref_reorder[0] = 1;  
  60.                 break;  
  61.             }  
  62.     }  
  63.   
  64.     h->i_ref1 = X264_MIN( h->i_ref1, h->frames.i_max_ref1 );  
  65.     h->i_ref0 = X264_MIN( h->i_ref0, h->frames.i_max_ref0 );  
  66.     h->i_ref0 = X264_MIN( h->i_ref0, h->param.i_frame_reference ); // if reconfig() has lowered the limit  
  67.     assert( h->i_ref0 + h->i_ref1 <= 16 );  
  68.     h->mb.pic.i_fref[0] = h->i_ref0;//为什么参考帧选择这两个,还没有搞懂  
  69.     h->mb.pic.i_fref[1] = h->i_ref1;  
  70. }  
4、初始化比特流,写入SPS以及PPS信息后就开始进行片级编码。
[cpp] view plaincopy
  1.     if( i_nal_type == NAL_SLICE_IDR && h->param.b_repeat_headers )  
  2.     {/*SPS和PPS是解码需要用到的信息,因此只有解码器解析了SPS和PPS信息 
  3.      才能进行解码,这就是为什么在每个IDR帧前写入这些信息*/  
  4.         if( h->fenc->i_frame == 0 )  
  5.         {//仅仅在第一针写入sei信息  
  6.             /* identify ourself */  
  7.     <span style="white-space:pre">  </span>x264_nal_start( h, NAL_SEI, NAL_PRIORITY_DISPOSABLE );/*开始整理nal。*/  
  8.             x264_sei_version_write( h, &h->out.bs );//写sei信息  
  9.             x264_nal_end( h );  
  10.         }  
  11.   
  12.         /* generate sequence parameters */  
  13.         x264_nal_start( h, NAL_SPS, NAL_PRIORITY_HIGHEST );/*开始整理nal。一个nal单元的首地址被赋值, 
  14. <span style="white-space:pre">          </span>将要处理此新的nal单元;设置nal的优先权和类型。*/  
  15.         x264_sps_write( &h->out.bs, h->sps );/*写SPS信息。 
  16.             将序列参数集sps写进位流结构中h->out.bs 
  17.             不是每次都要写SPS and PPS,只有碰见立即刷新片(NAL_SLICE_IDR)时才写*/  
  18.         x264_nal_end( h );/*结束nal,整理nal 
  19.             (1)输出新nal单元的地址 
  20.             (2)自增表示下一个新nal单元的序号*/  
  21.   
  22.         /* generate picture parameters */  
  23.         x264_nal_start( h, NAL_PPS, NAL_PRIORITY_HIGHEST );/*开始整理nal。 
  24.                 一个nal单元的首地址被赋值,将要处理此新的nal单元;设置nal的优先权和类型。*/  
  25.         x264_pps_write( &h->out.bs, h->pps );/*写PPS信息。 
  26.             将序列参数集sps写进位流结构中h->out.bs 
  27.             不是每次都要写SPS and PPS,只有碰见立即刷新片(NAL_SLICE_IDR)时才写*/  
  28.         x264_nal_end( h );/*结束nal,整理nal 
  29.             (1)输出新nal单元的地址 
  30.             (2)自增表示下一个新nal单元的序号*/  
  31.     }  
接着就开始片级编码x264_slices_write( h );

二、片级编码分析


0 0
原创粉丝点击