x264使用示例 /** * @note x264的编码示例. * 使用x264的 (2013-07-03 10:40:12)

x264使用示例 /**  * @note x264的编码示例.  * 使用x264的

  (2013-07-03 10:40:12)
   
x264使用示例

   x264的编码示例.
   使用x264的版本为libx264-115
   1. 示例是个死循环,会源源不断的编码,然后将数据写文件.
   2. 示例的行为是:编码1000帧后,取空编码缓冲区,然后循环执行这两步.
 
 
#include <cassert>
#include <iostream>
#include <string>
#include "stdint.h"
extern "C"
{
#include "x264.h"
};
unsigned int g_uiPTSFactor = 0;
int iNal     = 0;
x264_nal_t* pNals = NULL;
int encode(x264_t* p264, x264_picture_t* pIn, x264_picture_t* pOut);
int main(int argc, char** argv)
{
  int iResult = 0;
  x264_t* pX264Handle     = NULL;
  x264_param_t* pX264Param = new x264_param_t;
  assert(pX264Param);
  // 配置参数
  // 使用默认参数
  x264_param_default(pX264Param);
  // cpuFlags
  pX264Param->i_threads   = X264_SYNC_LOOKAHEAD_AUTO;//* 取空缓冲区继续使用不死锁的保证.
  //* video Properties
  pX264Param->i_width     = 320; // 宽度.
  pX264Param->i_height   = 240; // 高度
  pX264Param->i_frame_total = 0; // 编码总帧数.不知道用0.
  pX264Param->i_keyint_max = 10;
  //* bitstream parameters
  pX264Param->i_bframe   = 5;
  pX264Param->b_open_gop   = 0;
  pX264Param->i_bframe_pyramid = 0;
  pX264Param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS;
 
  // 宽高比,有效果,但不是想要的.
  //pX264Param->vui.i_sar_width = 1080;
  //pX264Param->vui.i_sar_height = 720;

  // Log
  pX264Param->i_log_level   = X264_LOG_DEBUG;
  // Rate control Parameters
  pX264Param->rc.i_bitrate = 1024 * 10;// 码率(比特率,单位Kbps)
  // muxing parameters
  pX264Param->i_fps_den   = 1; // 帧率分母
  pX264Param->i_fps_num   = 25;// 帧率分子
  pX264Param->i_timebase_den = pX264Param->i_fps_num;
  pX264Param->i_timebase_num = pX264Param->i_fps_den;

  // 设置Profile.使用MainProfile
  x264_param_apply_profile(pX264Param, x264_profile_names[1]);

  // 打开编码器句柄,通过x264_encoder_parameters得到设置给X264
  // 的参数.通过x264_encoder_reconfig更新X264的参数
  pX264Handle = x264_encoder_open(pX264Param);
  assert(pX264Handle);

  // 获取整个流的PPS和SPS,不需要可以不调用.
  iResult = x264_encoder_headers(pX264Handle, &pNals, &iNal);
  assert(iResult >= 0);
  // PPS SPS 总共只有36B,如何解析出来呢?
  for (int i = 0; i < iNal; ++i)
  {
   switch (pNals[i].i_type)
   {
   case NAL_SPS:
     break;
   case   NAL_PPS:
     break;
   default:
     break;
   }
  }

  // 获取允许缓存的最大帧数.
  int iMaxFrames = x264_encoder_maximum_delayed_frames(pX264Handle);

  // 编码需要的参数.
  iNal = 0;
  pNals = NULL;
  x264_picture_t* pPicIn = new x264_picture_t;
  x264_picture_t* pPicOut = new x264_picture_t;

  x264_picture_init(pPicOut);
  x264_picture_alloc(pPicIn, X264_CSP_I420, pX264Param->i_width, pX264Param->i_height);
  pPicIn->img.i_csp = X264_CSP_I420;
  pPicIn->img.i_plane = 3;

  //创建文件,用于存储编码数据
  FILE* pFile = fopen("agnt.264", "wb");
  assert(pFile);

  //示例用编码数据.
  int iDataLen = pX264Param->i_width * pX264Param->i_height;
  uint8_t* data = new uint8_t[iDataLen];

  unsigned int   uiComponent = 0;
  while (++uiComponent)
  {
   //构建需要编码的源数据(YUV420色彩格式)
   ::memset(data, uiComponent, iDataLen);
   ::memcpy(pPicIn->img.plane[0], data, iDataLen);
   ::memcpy(pPicIn->img.plane[1], data, iDataLen/4 );
   ::memcpy(pPicIn->img.plane[2], data, iDataLen/4);

   if (uiComponent <= 1000)
   {
     pPicIn->i_pts = uiComponent + g_uiPTSFactor * 1000;
     encode(pX264Handle, pPicIn, pPicOut);
   }
   else
   {
     //将缓存的数据取出
     int iResult = encode(pX264Handle, NULL, pPicOut);
     if (0== iResult)
     {
       //break; //* 取空,跳出
       uiComponent = 0;
       ++g_uiPTSFactor;

       //{{ 这个解决不了取空缓冲区,再压缩无B帧的问题
       x264_encoder_reconfig(pX264Handle, pX264Param);
       x264_encoder_intra_refresh(pX264Handle);
       //* }} //
     }
   }

   //将编码数据写入文件.
   for (int i = 0; i < iNal; ++i)
   {
     fwrite(pNals[i].p_payload, 1, pNals[i].i_payload, pFile);
   }
  }
  //清除图像区域
  x264_picture_clean(pPicIn);
  x264_picture_clean(pPicOut);
  //关闭编码器句柄
  x264_encoder_close(pX264Handle);
  pX264Handle = NULL;

  delete pPicIn ;
  pPicIn = NULL;

  delete pPicOut;
  pPicOut = NULL;

  delete pX264Param;
  pX264Param = NULL;

  delete [] data;
  data = NULL;
  return 0;
}

int encode(x264_t* pX264Handle, x264_picture_t* pPicIn, x264_picture_t* pPicOut)
{

  int iResult     = 0;
   iResult = x264_encoder_encode(pX264Handle, &pNals, &iNal, pPicIn, pPicOut);
  if (0 == iResult)
  {
   std::cout<<"编码成功,但被缓存了."<<std::endl;
  }else
  if(iResult < 0)
  {
   std::cout<<"编码出错"<<std::endl;
  }else
  if (iResult > 0)
  {
   std::cout<<"得到编码数据"<<std::endl;
  }

  /{{ 作用不明
  unsigned char* pNal = NULL;
  for (int i = 0;i < iNal; ++i)
  {
  int iData = 1024 * 32;
  x264_nal_encode(pX264Handle, pNal,&pNals[i]);
  }
  //}}

  //获取X264中缓冲帧数.
  int iFrames = x264_encoder_delayed_frames(pX264Handle);
  std::cout<<"当前编码器中缓存数据:"<<iFrames<<"帧\n";
  return iFrames;
}



要搞清poc和frame的区别.

假设一个视频序列如下:

I B    B    P    B     B     P

我们编码是按I P B B P B B的顺序,这就是frame的编号.

而我们视频序列的播放序号是POC的序号,这里是乘以了2.

函数中先定义了如下三个参数:

int     i_nal_type;

nal存放的数据类型, 可以是sps,pps等多种.                  

int     i_nal_ref_idc;

nal的优先级,nal重要性的标志位.

前面两个参数虽然简单,但如果不参照标准,也不容易理解,所以标准中的句法表是很重要的,可以说是最关键的.

int     i_slice_type;

slice的类型,在x264中我的感觉好像一帧只有一个slice.如果确定了帧的类型,slice的类型也就确定了.

 

我们来看看编码器是如何区分读入的一帧是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 )//可以B帧编码时.

就有问题了.

注意我们编完I帧后碰到了一个B帧,这时我们先不对它进编码.而是采用frame = x264_encoder_frame_put_from_picture( h, h->frame_next, pic )函数将这个B帧放进h->frame_next中.

好,这里出现了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];

注意区分这3个数组.

同时还有下面4个函数(定义在\ENCODER\encoder.c中).

x264_encoder_frame_put_from_picture();

x264_encoder_frame_put();

x264_encoder_frame_get();

x264_frame_copy_picture();

这3个数组和4个函数可以说完成了整个帧的类型的判定问题.这个里面if ,else语句较多,容易使人迷惑.但我们只要把握下面一个观点就可以看清实质:在不对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帧(说成P帧前面的第一个B帧容易误解J)编码.

依此类推,把握好上面4个函数的调用流程和指针操作的用法,就可以将帧的类型判定这个问题搞明白了.

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帧的话,我们就更新参考帧列表.

我们看到了一个for循环,两个do—while循环.这是实现的关键,具体看代码,不好用语言说明白.

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 )//不是每次都要写SPS and PPS,只有碰见立即刷新片时才写.

    {

       

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

}

不过看下面那个函数(就进入了复杂的领域).

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指的是从宽度上说有多少个宏快.对于宽度也就是288 / 16 = 18

    {

        const int i_mb_y = mb_xy / h->sps->i_mb_width;

        const int i_mb_x = mb_xy % h->sps->i_mb_width;//这两个变量是定义宏块的位置.而不是指宏块中元素的位置.

 

       

        x264_macroblock_cache_load( h, i_mb_x, i_mb_y );//是把当前宏块的up宏块和left宏块的intra4x4_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 );

截止到这就已经完成编码的主要过程了,后面就是熵编码的过程了(我也没看到那,但认为前面才是编码的主要过程).下面对这个过程进行分析.

A.     x264_macroblock_cache_load( h, i_mb_x, i_mb_y );它是将要编码的宏块的周围的宏块的值读进来, 要想得到当前块的预测值,要先知道上面,左面的预测值,它的作用相当于jm93中的getneighbour函数.

B.      进入x264_macroblock_analyse( h )函数(定义在\Enc\analyse.c中,这里涉及到了函数指针数组,需要好好复习,个人认为这也是x264代码最为复杂的一个地方了).既然已经 将该宏块周围的宏块的值读了出来,我们就可以对该宏块进行分析了(其实主要就是通过计算sad值分析是否要将16*16的宏块进行分割和采用哪种分割方式合适).

看似很复杂,但我们只要把握一个东西就有利于理解了:

举个生活中的例子来说:

如果你有2元钱,你可以去买2袋1元钱的瓜子,也可以买一袋2元钱的瓜子,如果2袋1元钱的瓜子数量加起来比1袋2元钱的瓜子数量多,你肯定会买2袋1元的.反之你会去买那2元1袋的.

具体来说,对于一个16*16的块,

如果它是I帧的块,我们可以将它分割成16个4*4的块,如果这16个块的sad加起来小于按16*16的方式计算出来的sad值,我们就将这个16*16的块分成16个4*4的块进行编码(在计算每个4*4的块的最小sad值时已经知道它采用何种编码方式最佳了),否则采用16*16的方式编码(同样我们也已知道对它采用哪种编码方式最为合适了.

如果它是P帧或B帧的块,同样是循环套循环,但更为复杂了,可以看我在analyse.c中的注释.

这里还要注意的是提到了

x264_predict_t      predict_16x16[4+3];

typedef void (*x264_predict_t)( uint8_t *src, int i_stride );

这是函数指针数组,有很多对它的调用.

C.     退出x264_macroblock_analyse( h )函数,进入x264_macroblock_encode( )函数(定义在\ENCODER\macroblock.c中).

我拿宏块类型为I_16*16为例.

if( h->mb.i_type == I_16x16 )

    {

        const int i_mode = h->mb.i_intra16x16_pred_mode;

       

        h->predict_16x16[i_mode]( h->mb.pic.p_fdec[0], h->mb.pic.i_fdec[0] );//这两个参数的关系.

                                                                   //涉及到x264_predict_t(函数指针数组),声明在core/predict.h中,core/predict.c里有不同定义.

       

        x264_mb_encode_i16x16( h, i_qscale );//

       …     }

我们看到h->predict_16x16[i_mode]( h->mb.pic.p_fdec[0], h->mb.pic.i_fdec[0] );只调用了一次,这是因为在x264_macroblock_analyse( )中我们已经确定了采用4种方式中的哪种最合适.而在x264_macroblock_analyse( )中判定一个块是否为I_16*16,我们调用了四次.这是因为当时我们需要拿最小的sad值进行比较.

继续,是x264_mb_encode_i16x16( h, i_qscale )函数(定义在\ENCODER\macroblock.c中).在这个函数中我们就可以看到量化,zig-扫描等函数了,这些都是直来直去的,需要的只是我们的细心和对数学知识的掌握了

c)       到这里还没完,我们接着看

void x264_macroblock_encode( x264_t *h ){

…….前面省略.

执行到下面这条语句,看看下面是干啥的.

   

    i_qscale = i_chroma_qp_table[x264_clip3( i_qscale + h->pps->i_chroma_qp_index_offset, 0, 51 )];

    if( IS_INTRA( h->mb.i_type ) )

    {

        const int i_mode = h->mb.i_chroma_pred_mode;

       

        h->predict_8x8[i_mode]( h->mb.pic.p_fdec[1], h->mb.pic.i_fdec[1] );

        h->predict_8x8[i_mode]( h->mb.pic.p_fdec[2], h->mb.pic.i_fdec[2] );

       

        h->mb.i_chroma_pred_mode = x264_mb_pred_mode8x8_fix[i_mode];

    }

   

x264_mb_encode_8x8( h, !IS_INTRA( h->mb.i_type ), i_qscale );//对色度块进行编码了.

到这我们可以看到原来我们在这前面是对宏块中的亮度系数进行了编码,我们到上面那个函数才开始对色度系数进行编码.进入x264_mb_encode_8x8()函数看到for循环里面有个2可以证明是对2个色度系数进行编码,想法没错.

那下面这些又是干啥的呢?它们是计算cbp系数看需要对残差(包括ac,dc)中的哪个系数进行传输的.

   

    if( h->mb.i_type == I_16x16 )

    {

        h->mb.i_cbp_luma = 0x00;

        for( i = 0; i < 16; i++ )

        {

            const int nz = array_non_zero_count( h->dct.block[i].residual_ac, 15 );

            h->mb.cache.non_zero_count[x264_scan8[i]] = nz;

            if( nz > 0 )

            {

                h->mb.i_cbp_luma = 0x0f;

            }

        }

    }

    else

    {

        h->mb.i_cbp_luma = 0x00;

        for( i = 0; i < 16; i++ )

        {

            const int nz = array_non_zero_count( h->dct.block[i].luma4x4, 16 );//统计非0个数.

            h->mb.cache.non_zero_count[x264_scan8[i]] = nz;

            if( nz > 0 )

            {

                h->mb.i_cbp_luma |= 1 << (i/4);// 的意义.

            }

        }

    }

    //色度的cbp有3种方式.

    h->mb.i_cbp_chroma = 0x00;

    for( i = 0; i < 8; i++ )

    {

        const int nz = array_non_zero_count( h->dct.block[16+i].residual_ac, 15 );

        h->mb.cache.non_zero_count[x264_scan8[16+i]] = nz;

        if( nz > 0 )                      

        {

            h->mb.i_cbp_chroma = 0x02;   

        }

    }

    if( h->mb.i_cbp_chroma == 0x00 &&

        ( array_non_zero_count( h->dct.chroma_dc[0], 4 ) > 0 || array_non_zero_count( h->dct.chroma_dc[1], 4 ) ) > 0 )

    {

        h->mb.i_cbp_chroma = 0x01;   

    }

    if( h->param.b_cabac )

    {

        if( h->mb.i_type == I_16x16 && array_non_zero_count( h->dct.luma16x16_dc, 16 ) > 0 )

            i_cbp_dc = 0x01;

        else

            i_cbp_dc = 0x00;

        if( array_non_zero_count( h->dct.chroma_dc[0], 4 ) > 0 )

            i_cbp_dc |= 0x02;

        if( array_non_zero_count( h->dct.chroma_dc[1], 4 ) > 0 )

            i_cbp_dc |= 0x04;

    }

   

h->mb.cbp[h->mb.i_mb_xy] = (i_cbp_dc << 8) | (h->mb.i_cbp_chroma << 4) | h->mb.i_cbp_luma;

到这,基本上x264_macroblock_encode( h )(定义在Enc/encoder.c)基本上就分析完了.剩下的就是熵编码的部分了.以后的部分更需要的应该是耐心和数学知识吧,相对前面来说应该简单些.

l       总结:

1. 我对代码的理解应该还算比较深入,把代码的主线已经分析了出来,对代码中几个最难理解的地方(最难理解的地方就是帧的类型的判定,参考帧是如何管理的,一个16*16的块是采用到底需不需要分割,分割的话分成什么大小的,子块又采用何种预测方式, 这些实际上就是整个编码的主线.)基本上已经明白,但有些过分复杂的函数的实现(或者涉及数学知识较多的地方)还有待深入研究,但我相信沿着这条主线应该 能够继续深入下去,自己需要的是更多的时间和耐心. 自己需要的是更多的时间和耐心,争取以后能写出更详细更准确的流程分析,并尽量思考能改进的地方.

2.层次性,就像网络的7层结构一样,每一帧图像也可以分成很多层,只有对每层的语法结构(具体来说就是各个结构体中变量的意思)有了很好的理解,才有可能真正认清代码,这需要对标准认真研习.比如说量化参数,就在3个地方有定义,不读标准根本不会明白意思.

3. 很多过分复杂的东西不容易在本文中表达出来(比如说预测部分),只有通过自己的钻研才能真正悟到,直觉也很重要,还有就是信心了.看这种程序的收获就好像是真地肉眼看到了原子那样.

4.由于代码过分复杂,对某些函数的实现过程还没能彻底理解,比如说 x264_macroblock_cache_load()函数的具体实现过程,我只是知道它的功能,实现过程还有待认真理解.dct变换是如何实现的, 是如何计算残差的等等,这些都需要很多功夫,当然这里也需要大家的共同学习和交流.实现分工阅读不同代码部分并进行交流,才有可能对代码做到彻底的理解.


int x264_validate_levels( x264_t *h, int verbose )

{

    int ret = 0;

    int mbs = h->sps->i_mb_width * h->sps->i_mb_height;

    int dpb = mbs * 384 * h->sps->i_num_ref_frames;dpb为最大解码图像缓存区。

    int cbp_factor = h->sps->i_profile_idc==PROFILE_HIGH ? 5 : 4;

 

    const x264_level_t *l = x264_levels;

    while( l->level_idc != 0 && l->level_idc != h->param.i_level_idc )

        l++;

 

    if( l->frame_size < mbs

        || l->frame_size*8 < h->sps->i_mb_width * h->sps->i_mb_width

        || l->frame_size*8 < h->sps->i_mb_height * h->sps->i_mb_height )

        ERROR( "frame MB size (%dx%d) > level limit (%d)\n",

               h->sps->i_mb_width, h->sps->i_mb_height, l->frame_size );错误提示为输入宏块的大小大于设置的最大宏块的大小。

    if( dpb > l->dpb )

        ERROR( "DPB size (%d frames, %d bytes) > level limit (%d frames, %d bytes)\n",

                h->sps->i_num_ref_frames, dpb, (int)(l->dpb / (384*mbs)), l->dpb );

错误提示为最大解码缓存区大于设定的最大解码缓存区。

#define CHECK( name, limit, val ) \

    if( (val) > (limit) ) \

        ERROR( name " (%d) > level limit (%d)\n", (int)(val), (limit) );

下面分别为检测输入的VBV bitrate,VBV buffer,MV range,interlaced是否大于原设定的值的大小。

    CHECK( "VBV bitrate", (l->bitrate * cbp_factor) / 4, h->param.rc.i_vbv_max_bitrate );

    CHECK( "VBV buffer", (l->cpb * cbp_factor) / 4, h->param.rc.i_vbv_buffer_size );

    CHECK( "MV range", l->mv_range, h->param.analyse.i_mv_range );

    CHECK( "interlaced", !l->frame_only, h->param.b_interlaced );

 

    if( h->param.i_fps_den > 0 )

        CHECK( "MB rate", l->mbps, (int64_t)mbs * h->param.i_fps_num / h->param.i_fps_den );

检测最大块处理速率。

   

    return ret;最后返回值为1.

}


X264码率控制流程分析
码率控制的理论知识:

码率控制的目的和意义:
图 像通信中码率控制的目的:通过调节编码参数,控制单位时间内的编码视频流的数据量,以使产生的比特流符合各种应用的需求。视频压缩的效率和视频内容有很大 的关系,对于变化多样的画面,视频编码的输出的码流变化较大,在信道环境不好的时候就容易导致解码端显示的质量的不稳定。

率失真理论:
由于传输带宽和存储空间的限制,视频应用对压缩比有较高的要求。而无损编码较低的压缩比无法满足视频在实际应用中的需求。但如果给视频引入一定程度的失真,通常可以获得较高的压缩比。
率 失真理论对有损压缩编码下的失真和编码性能之间的关系的描述,为码率控制的研究提供了坚实的理论依据。率失真理论主旨是描述编码失真度和编码数据速率的关 系。该理论建立在图像是连续的基础上的,在有限数据速率下,由于存在量化误差,必然存在失真。当使用有损编码方法时,重建图像g(x,y)和原始图像 f(x,y)之间存在差异,失真度D的函数形式在理论上是可以根据需要自由选取的,在图像编码中,D常用均方差形式表示的,典型的率失真曲线。R(D)为 D的凸减函数。
对于怎么选择哪个函数的率失真效果更好,则是比较哪个函数的率失真函数更为接近典型的率失真函数的曲线。

x264 码率控制方法:采用的码率控制算法并没有采用拉格朗日代价函数来控制编码,而是使用一种更简单的方法,即利用半精度帧的SATD(sum of absolute transformed difference)作为模式选择的依据。SATD即将残差经哈德曼变换的4×4块的预测残差绝对值总和,可以将其看作简单的时频变换,其值在一定程度 上可以反映生成码流的大小。SATD是将残差经哈达曼变换4*4块的预测残差绝对值总和。自适应宏块层码率控制策略:X264的宏块没有任何码率控制的机 制,其在帧层得到一个QP后,属于该帧的所有宏块都用着统一的QP进行量化。

码率控制性能测度:
1、比特率误差|ABR-TBR|/TBR ,越小越好。
2、编码器性能。
3、缓冲区满度与TBL的匹配程度。
4、跳帧数。
5、PSNR波动越小越好。


x264中码率控制的流程(对于重点函数在下面有注释):

1.在进行编码时,Encode--->x264_encoder_open(主要是进行参数的修订设置,进行初始化)---->x264_ratecontrol_new
2.encode--->Encode_frame--->x264_encoder_encode--->x264_ratecontrol_slice_type
3.encode--->Encode_frame--->x264_encoder_encode--->x264_ratecontrol_start**************
4.encode--->Encode_frame--->x264_encoder_encode--->x264_ratecontrol_qp
5.encode--->Encode_frame--->x264_encoder_encode--->x264_slices_write--->x264_slice_write
--->x264_ratecontrol_mb********************
6.encode--->Encode_frame--->x264_encoder_encode--->x264_ratecontrol_end(在编完一帧过后)
7.在编完过后,encode--->x264_encoder_close---->ratecontrol summary/x264_ratecontrol_delete


函数注释:

在编码中所用的编码方式:
#define X264_RC_CQP                  0
#define X264_RC_CRF                  1
#define X264_RC_ABR                  2

1.
x264_ratecontrol_new( x264_t *h )
{   // 获取RC方式,FPS,bitrate,rc->buffer_rate,rc->buffer_size
// 在码率控制的时候会出现2pass,参数的初始化
rc = h->rc;
rc->b_abr = h->param.rc.i_rc_method != X264_RC_CQP && !h->param.rc.b_stat_read;
rc->b_2pass = h->param.rc.i_rc_method == X264_RC_ABR && h->param.rc.b_stat_read;
..........
if( h->param.rc.b_mb_tree )//这里设置mb_tree
{
h->param.rc.f_pb_factor = 1;
rc->qcompress = 1;
}
else
rc->qcompress = h->param.rc.f_qcompress;

..............
rc->ip_offset = 6.0 * log(h->param.rc.f_ip_factor) / log(2.0);
rc->pb_offset = 6.0 * log(h->param.rc.f_pb_factor) / log(2.0);
rc->qp_constant[SLICE_TYPE_P] = h->param.rc.i_qp_constant;
rc->qp_constant[SLICE_TYPE_I] = x264_clip3( h->param.rc.i_qp_constant - rc->ip_offset + 0.5, 0, 51 );
rc->qp_constant[SLICE_TYPE_B] = x264_clip3( h->param.rc.i_qp_constant + rc->pb_offset + 0.5, 0, 51 );

}

2.
int x264_ratecontrol_slice_type( x264_t *h, int frame_num )
{
//根据不同类型来获取不同的qp_constant
h->param.rc.i_qp_constant = (h->stat.i_frame_count[SLICE_TYPE_P] == 0) ? 24
: 1 + h->stat.f_frame_qp[SLICE_TYPE_P] / h->stat.i_frame_count[SLICE_TYPE_P];
rc->qp_constant[SLICE_TYPE_P] = x264_clip3( h->param.rc.i_qp_constant, 0, 51 );
rc->qp_constant[SLICE_TYPE_I] = x264_clip3( (int)( qscale2qp( qp2qscale( h->param.rc.i_qp_constant ) / fabs( h->param.rc.f_ip_factor )) + 0.5 ), 0, 51 );
rc->qp_constant[SLICE_TYPE_B] = x264_clip3( (int)( qscale2qp( qp2qscale( h->param.rc.i_qp_constant ) * fabs( h->param.rc.f_pb_factor )) + 0.5 ), 0, 51 );
}

3.
x264_ratecontrol_start( h, h->fenc->i_qpplus1, overhead*8 );
这个函数的目的就是在一帧的编码前就选择QP


x264_ratecontrol_start( h, h->fenc->i_qpplus1, overhead*8 );


对x264_ratecontrol_start函数的解析如下:


x264_zone_t *zone = get_zone( h, h->fenc->i_frame );//找到h->fenc->i_frame所在的zone
....................
//由各种不同的slice类型,vbv等等参数获取的q值
if( i_force_qp )
{
q = i_force_qp - 1;//
}
else if( rc->b_abr )
{
q = qscale2qp( rate_estimate_qscale( h ) );//下面有注解
}
else if( rc->b_2pass )
{
rce->new_qscale = rate_estimate_qscale( h );
q = qscale2qp( rce->new_qscale );
}
else
{
if( h->sh.i_type == SLICE_TYPE_B && h->fdec->b_kept_as_ref )
q = ( rc->qp_constant[ SLICE_TYPE_B ] + rc->qp_constant[ SLICE_TYPE_P ] ) / 2;
else
q = rc->qp_constant[ h->sh.i_type ];

if( zone )
{
if( zone->b_force_qp )
q += zone->i_qp - rc->qp_constant[SLICE_TYPE_P];
else
q -= 6*log(zone->f_bitrate_factor)/log(2);
}

//////////////////////////////////////////////////////////////////

static inline double qp2qscale(double qp)
{
return 0.85 * pow(2.0, ( qp - 12.0 ) / 6.0);
}
static inline double qscale2qp(double qscale)
{
return 12.0 + 6.0 * log(qscale/0.85) / log(2.0);
}
////////////////////////////////////////////////////////////////////////
rate_estimate_qscale( h )
// update qscale for 1 frame based on actual bits used so far(即根据所需BIT来计算qscale)
static float rate_estimate_qscale( x264_t *h )
{
//这里是分别针对B,P帧分别进行,因为I帧是已经设定
if( pict_type == SLICE_TYPE_B )
{
//这里B帧的q的大小是由参考帧求的
.....................
.....................
// 由predict_size获得帧的size
rcc->frame_size_planned = predict_size( rcc->pred_b_from_p, q, h->fref1[h->i_ref1-1]->i_satd );
x264_ratecontrol_set_estimated_size(h, rcc->frame_size_planned);
//////////////////////////
void x264_ratecontrol_set_estimated_size( x264_t *h, int bits )
{
x264_pthread_mutex_lock( &h->fenc->mutex );
h->rc->frame_size_estimated = bits;//
int x264_ratecontrol_end( x264_t *h, int bits )
{
///统计ipb类型的Mb的个数,并计算平均QP
h->fdec->f_qp_avg_rc = rc->qpa_rc /= h->mb.i_mb_count;
h->fdec->f_qp_avg_aq = rc->qpa_aq /= h->mb.i_mb_count;
}


7.
void x264_ratecontrol_summary( x264_t *h )
{
x264_ratecontrol_t *rc = h->rc;
//ABR
if( rc->b_abr && h->param.rc.i_rc_method == X264_RC_ABR && rc->cbr_decay > .9999 )
{
double base_cplx = h->mb.i_mb_count * (h->param.i_bframe ? 120 : 80);
double mbtree_offset = h->param.rc.b_mb_tree ? (1.0-h->param.rc.f_qcompress)*13.5 : 0;
x264_log( h, X264_LOG_INFO, "final ratefactor: %.2f\n",
qscale2qp( pow( base_cplx, 1 - rc->qcompress )
* rc->cplxr_sum / rc->wanted_bits_window ) - mbtree_offset );
}
}

/////////////////////////////
void x264_ratecontrol_delete( x264_t *h )///////释放RC开辟的空间

通过以上的流程总结x264码率控制的过程基本是有以下三步:
1.对码率控制的相关变量进行初始化,如,I,P,B的初始QP值,RC的方式,VBV的初始状态等等;
2.获取编码帧的复杂度,x264用SATD表示,对于采用的不同参数的码率控制的方式,由前面已编码的Bits,复杂度,目标比特的设置等一些条件来获取编码当前帧的qp值。
3.在编码过程中,由获得qp值得到预测的bits;


实验部分:
1.简单参数设置:
参数设置:
--frames 10 --qp 26 -o test.264 F:\.......\akiyo_qcif.yuv 176x144
其他的参数采用默认设置(在默认设置时采用的码率控制模型是X264_RC_CQP),所得的实验结果:
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile High, level 1.1
x264 [info]: frame I:1     Avg QP:23.00  size:  4189
x264 [info]: frame P:3     Avg QP:26.00  size:    62
x264 [info]: frame B:6     Avg QP:28.00  size:    38
x264 [info]: consecutive B-frames: 11.1%  0.0%  0.0% 88.9%
x264 [info]: mb I  I16..4:  3.0% 41.4% 55.6%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  3.0%  1.3%  1.7%  0.0%  0
.0%    skip:93.9%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  4.4%  0.2%  0.3%  direct:
0.7%  skip:94.4%  L0:56.0% L1:40.5% BI: 3.4%
x264 [info]: 8x8 transform intra:41.4% inter:25.9%
x264 [info]: coded y,uvDC,uvAC intra: 83.6% 81.8% 68.7% inter: 1.1% 0.1% 0.0%
x264 [info]: i16 v,h,dc,p: 100%  0%  0%  0%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 25% 30% 15%  4%  4%  4%  7%  5%  6%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 35% 18%  8%  5%  5% 10%  5%  8%  6%
x264 [info]: ref P L0: 88.9%  0.0% 11.1%
x264 [info]: kb/s:92.08

encoded 10 frames, 24.33 fps, 92.08 kb/s

2.改变码率控制的模型:
--frames 10 --qp 26 --crf 2 -o test.264 F:\......\akiyo_qcif.yuv 176x144
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile High, level 1.1
x264 [info]: frame I:1     Avg QP:10.00  size: 10246
x264 [info]: frame P:3     Avg QP:11.48  size:   847
x264 [info]: frame B:6     Avg QP:12.10  size:   172
x264 [info]: consecutive B-frames: 11.1%  0.0%  0.0% 88.9%
x264 [info]: mb I  I16..4:  1.0% 44.4% 54.5%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4: 30.0%  3.0%  4.7%  0.0%  0
.0%    skip:62.3%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  6.9%  1.0%  1.2%  direct:
4.0%  skip:86.9%  L0:34.7% L1:55.6% BI: 9.7%
x264 [info]: 8x8 transform intra:44.4% inter:34.8%
x264 [info]: coded y,uvDC,uvAC intra: 100.0% 99.0% 94.9% inter: 11.6% 7.6% 4.9%
x264 [info]: i16 v,h,dc,p: 100%  0%  0%  0%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 30% 33% 16%  3%  2%  3%  4%  4%  5%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 28% 12% 11%  5%  7% 11%  6% 12%  8%
x264 [info]: ref P L0: 95.8%  1.6%  2.7%
x264 [info]: ref B L0: 96.3%  3.7%
x264 [info]: kb/s:276.36

encoded 10 frames, 14.27 fps, 276.36 kb/s

针对1,2两个实验,所采用的RC模型不一样,1:X264_RC_CQP,2:X264_RC_CRF,其他参数的设置一样,从IPB的平均QP,编码Bits可以看出和对于实际的应用来说,CRF的效果不如CQP。

3.
--frames 10 --qp 26 --pass 1 -o test.264 F:\.....\bin\akiyo_qcif.yuv 176x144
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile Main, level 1.1
x264 [info]: frame I:1     Avg QP:23.00  size:  4068
x264 [info]: frame P:3     Avg QP:26.00  size:    59
x264 [info]: frame B:6     Avg QP:28.00  size:    31
x264 [info]: consecutive B-frames: 11.1%  0.0%  0.0% 88.9%
x264 [info]: mb I  I16..4: 15.2%  0.0% 84.8%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  7.1%  0.0%  0.0%  0.0%  0
.0%    skip:92.9%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  1.2%  0.0%  0.0%  direct:
1.5%  skip:97.3%  L0:100.0% L1: 0.0% BI: 0.0%
x264 [info]: coded y,uvDC,uvAC intra: 87.4% 77.8% 68.7% inter: 1.1% 0.1% 0.0%
x264 [info]: i16 v,h,dc,p: 47% 20% 27%  7%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 29% 27% 10%  5%  4%  8%  5%  6%  5%
x264 [info]: kb/s:88.58
encoded 10 frames, 52.63 fps, 88.58 kb/s


4.
--frames 10 --qp 26 --pass 2 -o test.264 F:\.....\bin\akiyo_qcif.yuv 176x144
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile High, level 1.1
x264 [info]: frame I:1     Avg QP:23.00  size:  4189
x264 [info]: frame P:3     Avg QP:26.00  size:    62
x264 [info]: frame B:6     Avg QP:28.00  size:    38
x264 [info]: consecutive B-frames: 11.1%  0.0%  0.0% 88.9%
x264 [info]: mb I  I16..4:  3.0% 41.4% 55.6%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  3.0%  1.3%  1.7%  0.0%  0
.0%    skip:93.9%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  4.4%  0.2%  0.3%  direct:
0.7%  skip:94.4%  L0:56.0% L1:40.5% BI: 3.4%
x264 [info]: 8x8 transform intra:41.4% inter:25.9%
x264 [info]: coded y,uvDC,uvAC intra: 83.6% 81.8% 68.7% inter: 1.1% 0.1% 0.0%
x264 [info]: i16 v,h,dc,p: 100%  0%  0%  0%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 25% 30% 15%  4%  4%  4%  7%  5%  6%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 35% 18%  8%  5%  5% 10%  5%  8%  6%
x264 [info]: ref P L0: 88.9%  0.0% 11.1%
x264 [info]: kb/s:92.08

encoded 10 frames, 27.70 fps, 92.08 kb/s

5.
--frames 10 --qp 26 --pass 3 -o test.264 F:\.....\bin\akiyo_qcif.yuv 176x144
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile High, level 1.1
x264 [info]: frame I:1     Avg QP:23.00  size:  4189
x264 [info]: frame P:3     Avg QP:26.00  size:    62
x264 [info]: frame B:6     Avg QP:28.00  size:    38
x264 [info]: consecutive B-frames: 11.1%  0.0%  0.0% 88.9%
x264 [info]: mb I  I16..4:  3.0% 41.4% 55.6%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  3.0%  1.3%  1.7%  0.0%  0
.0%    skip:93.9%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  4.4%  0.2%  0.3%  direct:
0.7%  skip:94.4%  L0:56.0% L1:40.5% BI: 3.4%
x264 [info]: 8x8 transform intra:41.4% inter:25.9%
x264 [info]: coded y,uvDC,uvAC intra: 83.6% 81.8% 68.7% inter: 1.1% 0.1% 0.0%
x264 [info]: i16 v,h,dc,p: 100%  0%  0%  0%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 25% 30% 15%  4%  4%  4%  7%  5%  6%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 35% 18%  8%  5%  5% 10%  5%  8%  6%
x264 [info]: ref P L0: 88.9%  0.0% 11.1%
x264 [info]: kb/s:92.08

encoded 10 frames, 25.64 fps, 92.08 kb/s

对于3,4,5是关于Pass的实验比较:
多次压缩码率控制
1
:第一次压缩,创建统计文件
2
:按建立的统计文件压缩并输出,不覆盖统计文件,
3
:按建立的统计文件压缩,优化统计文件
在想得到建好的效果的时候采用pass 2就可以了。

6.
--frames 10 --qp 26 --bitrate 64 -o test.264 F:\.....\bin\akiyo_qcif.yuv 176x144
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile High, level 1.1
x264 [info]: frame I:1     Avg QP:38.31  size:  1461
x264 [info]: frame P:3     Avg QP:42.00  size:    18
x264 [info]: frame B:6     Avg QP:45.00  size:    14
x264 [info]: consecutive B-frames: 11.1%  0.0%  0.0% 88.9%
x264 [info]: mb I  I16..4: 15.2% 68.7% 16.2%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4:  2.0%  0.0%  0.3%  0.0%  0
.0%    skip:97.6%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8:  0.2%  0.0%  0.0%  direct:
0.0%  skip:99.8%  L0: 0.0% L1:100.0% BI: 0.0%
x264 [info]: final ratefactor: 31.50
x264 [info]: 8x8 transform intra:68.7%
x264 [info]: coded y,uvDC,uvAC intra: 48.0% 61.6% 32.3% inter: 0.0% 0.0% 0.0%
x264 [info]: i16 v,h,dc,p: 33% 47%  7% 13%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 35% 17% 20%  3%  4%  7%  3%  7%  5%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 50% 14%  8%  5%  5%  5%  3%  6%  4%
x264 [info]: kb/s:31.94

encoded 10 frames, 31.25 fps, 31.94 kb/s

7.
--frames 250 --qp 26 --bitrate 64 -o test.264 F:\.....\bin\akiyo_qcif.yuv 176x144
x264 [info]: 176x144 @ 25.00 fps
x264 [info]: using cpu capabilities: MMX2 SSE2 Cache64 Slow_mod4_stack
x264 [info]: profile High, level 1.1
x264 [info]: frame I:1     Avg QP:34.62  size:  1779
x264 [info]: frame P:92    Avg QP:19.81  size:   569
x264 [info]: frame B:157   Avg QP:26.76  size:    53
x264 [info]: consecutive B-frames: 15.7%  0.0%  2.4% 81.9%
x264 [info]: mb I  I16..4: 14.1% 61.6% 24.2%
x264 [info]: mb P  I16..4:  0.0%  0.0%  0.0%  P16..4: 25.8%  9.4%  9.9%  0.0%  0
.0%    skip:54.8%
x264 [info]: mb B  I16..4:  0.0%  0.0%  0.0%  B16..8: 13.9%  0.7%  1.4%  direct:
1.1%  skip:83.0%  L0:16.6% L1:72.1% BI:11.3%
x264 [info]: final ratefactor: 18.97
x264 [info]: 8x8 transform intra:61.5% inter:40.4%
x264 [info]: coded y,uvDC,uvAC intra: 61.3% 65.4% 34.6% inter: 8.6% 6.8% 2.8%
x264 [info]: i16 v,h,dc,p: 57% 43%  0%  0%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 32% 22% 18%  4%  2%  7%  3%  7%  4%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 45% 10% 10%  5%  6%  7%  6%  6%  5%
x264 [info]: ref P L0: 87.6%  7.6%  4.8%
x264 [info]: ref B L0: 95.0%  5.0%
x264 [info]: kb/s:49.92

encoded 250 frames, 16.74 fps, 49.92 kb/s

6,7是针对不同的编码帧数来进行比较的,在编码帧数越多,带宽利用的效果就越好。
6,7是在设置了目标码率64kp/s时,采用的是ABR的RC模型,在设置了目标码率能够根据目标码率的大小改变QP大小,能够控制码率。

x264RC中的Macroblock Tree分析
Macroblock Tree是一个基于macroblock的qp控制方法。MB Tree针对的是每个MB进行处理。工作过程简单来说,是对于每个MB,向前预测一定数量的帧(该数量由rc-lookahead和keyint的较小值 决定)中该MB被参考的情况,根据引用次数的多寡,决定对该MB使用何种大小的qp进行quantization。而qp的大小与被参考次数成反比。
与Mbtree相关的参数
--qcomp qcomp有削弱mbtree强度的倾向,具体来说,qcomp的值越趋近于1(Constant Quantizer),

你可能感兴趣的:(x264使用示例 /** * @note x264的编码示例. * 使用x264的 (2013-07-03 10:40:12))