HEVC码率控制浅析——HM代码阅读之一

HM的码率控制提案主要参考如下三篇:K0103,M0036,M0257。本文及后续文章将基于HM12.0进行讨论,且首先仅讨论K0103对应的代码,之后再陆续补充M0036,M0257对应的代码分析,这么做可能会使得剧情不会显得那么地跳跃,分析起来能够更好地被接受。

按照我的个人习惯,还是先分析HM中码率控制部分(以后简称RC)的总体框架吧。

跟RC有关的头文件和源文件为工程TLibEncoder中的TEncRateCtrl.h和TEncRateCtrl.cpp,其余的地方都是调用这两个文件中定义的函数或者变量。

在最顶层,TEncTop这个类定义了成员变量TEncRateCtrl m_cRateCtrl,类TEncGOP,TEncSlice以及TEncCu也分别定义了成员变量TEncRateCtrl *m_pcRateCtrl,但是请注意,这三个类的m_pcRateCtrl实际上都是指向TEncTop这个类的m_cRateCtrl,即它们本质上是同一个。这个从这三个类的成员函数init中对各成员变量的初始化可以看出来。

(1)初始化:

首先,在TEncTop::create()中,对整个序列都要用到的相关参数进行初始化

#if RATE_CONTROL_LAMBDA_DOMAIN
  if ( m_RCEnableRateControl )
  {
    m_cRateCtrl.init( m_framesToBeEncoded, m_RCTargetBitrate, m_iFrameRate, m_iGOPSize, m_iSourceWidth, m_iSourceHeight,
                      g_uiMaxCUWidth, g_uiMaxCUHeight, m_RCKeepHierarchicalBit, m_RCUseLCUSeparateModel, m_GOPList );
  }
#else
  m_cRateCtrl.create(getIntraPeriod(), getGOPSize(), getFrameRate(), getTargetBitrate(), getQP(), getNumLCUInUnit(), getSourceWidth(), getSourceHeight(), g_uiMaxCUWidth, g_uiMaxCUHeight);
#endif


其次,在TEncTop::encode()中,对整个GOP需要用到的相关参数进行初始化

#if RATE_CONTROL_LAMBDA_DOMAIN
  if ( m_RCEnableRateControl )
  {
    m_cRateCtrl.initRCGOP( m_iNumPicRcvd );
  }
#endif

接着,在TEncGOP::compressGOP()中,对每一幅picture需要用到的相关参数进行初始化

#if RATE_CONTROL_LAMBDA_DOMAIN
    Double lambda            = 0.0;
    Int actualHeadBits       = 0;
    Int actualTotalBits      = 0;
    Int estimatedBits        = 0;
    Int tmpBitsBeforeWriting = 0;
    if ( m_pcCfg->getUseRateCtrl() )
    {
      Int frameLevel = m_pcRateCtrl->getRCSeq()->getGOPID2Level( iGOPid );
      if ( pcPic->getSlice(0)->getSliceType() == I_SLICE )
      {
        frameLevel = 0;
      }
      m_pcRateCtrl->initRCPic( frameLevel ); //!< picture level 初始化
      estimatedBits = m_pcRateCtrl->getRCPic()->getTargetBits();
      
      Int sliceQP = m_pcCfg->getInitialQP();
      if ( ( pcSlice->getPOC() == 0 && m_pcCfg->getInitialQP() > 0 ) || ( frameLevel == 0 && m_pcCfg->getForceIntraQP() ) ) // QP is specified
      {
        Int    NumberBFrames = ( m_pcCfg->getGOPSize() - 1 );
        Double dLambda_scale = 1.0 - Clip3( 0.0, 0.5, 0.05*(Double)NumberBFrames );
        Double dQPFactor     = 0.57*dLambda_scale;
        Int    SHIFT_QP      = 12;
        Int    bitdepth_luma_qp_scale = 0;
        Double qp_temp = (Double) sliceQP + bitdepth_luma_qp_scale - SHIFT_QP;
        lambda = dQPFactor*pow( 2.0, qp_temp/3.0 );
      }
      else if ( frameLevel == 0 )   // intra case, but use the model
      {
#if RATE_CONTROL_INTRA
        m_pcSliceEncoder->calCostSliceI(pcPic);
#endif
        if ( m_pcCfg->getIntraPeriod() != 1 )   // do not refine allocated bits for all intra case
        {
          Int bits = m_pcRateCtrl->getRCSeq()->getLeftAverageBits();
#if RATE_CONTROL_INTRA
          bits = m_pcRateCtrl->getRCPic()->getRefineBitsForIntra( bits );
#else
          bits = m_pcRateCtrl->getRCSeq()->getRefineBitsForIntra( bits );
#endif
          if ( bits < 200 )
          {
            bits = 200;
          }
          m_pcRateCtrl->getRCPic()->setTargetBits( bits );
        }
        
        list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
#if RATE_CONTROL_INTRA
        m_pcRateCtrl->getRCPic()->getLCUInitTargetBits();
        lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
#else
        lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture );
#endif
        sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
      }
      else    // normal case
      {
        list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
#if RATE_CONTROL_INTRA
        lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
#else
        lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture );
#endif
        sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
      }
      
      sliceQP = Clip3( -pcSlice->getSPS()->getQpBDOffsetY(), MAX_QP, sliceQP );
      m_pcRateCtrl->getRCPic()->setPicEstQP( sliceQP );
      
      m_pcSliceEncoder->resetQP( pcPic, sliceQP, lambda );
    }
#endif


最后,在TEncSlice::compressSlice()中,对每一个LCU需要用到的相关参数进行初始化:

#if RATE_CONTROL_LAMBDA_DOMAIN
      Double oldLambda = m_pcRdCost->getLambda();
      if ( m_pcCfg->getUseRateCtrl() )
      {
        Int estQP        = pcSlice->getSliceQp();
        Double estLambda = -1.0;
        Double bpp       = -1.0;

#if M0036_RC_IMPROVEMENT
        if ( ( rpcPic->getSlice( 0 )->getSliceType() == I_SLICE && m_pcCfg->getForceIntraQP() ) || !m_pcCfg->getLCULevelRC() )
#else
        if ( rpcPic->getSlice( 0 )->getSliceType() == I_SLICE || !m_pcCfg->getLCULevelRC() )
#endif
        {
          estQP = pcSlice->getSliceQp();
        }
        else
        {
#if RATE_CONTROL_INTRA
          bpp = m_pcRateCtrl->getRCPic()->getLCUTargetBpp(pcSlice->getSliceType());
          if ( rpcPic->getSlice( 0 )->getSliceType() == I_SLICE)
          {
            estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambdaAndQP(bpp, pcSlice->getSliceQp(), &estQP);
          }
          else
          {
            estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambda( bpp );
            estQP     = m_pcRateCtrl->getRCPic()->getLCUEstQP    ( estLambda, pcSlice->getSliceQp() );
          }
#else
          bpp       = m_pcRateCtrl->getRCPic()->getLCUTargetBpp();
          estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambda( bpp );
          estQP     = m_pcRateCtrl->getRCPic()->getLCUEstQP    ( estLambda, pcSlice->getSliceQp() );
#endif

          estQP     = Clip3( -pcSlice->getSPS()->getQpBDOffsetY(), MAX_QP, estQP );

          m_pcRdCost->setLambda(estLambda);
#if M0036_RC_IMPROVEMENT
#if RDOQ_CHROMA_LAMBDA
          // set lambda for RDOQ
          Double weight=m_pcRdCost->getChromaWeight();
          m_pcTrQuant->setLambda( estLambda, estLambda / weight );
#else
          m_pcTrQuant->setLambda( estLambda );
#endif
#endif
        }

        m_pcRateCtrl->setRCQP( estQP );
        pcCU->getSlice()->setSliceQpBase( estQP );
      }
#endif


(2)参数值更新:

首先,在TEncSlice::compressSlice中,每编码完一个LCU,进行一次更新:

#if TICKET_1090_FIX
#if RATE_CONTROL_LAMBDA_DOMAIN
      if ( m_pcCfg->getUseRateCtrl() )
      {
#if !M0036_RC_IMPROVEMENT
        UInt SAD    = m_pcCuEncoder->getLCUPredictionSAD();
        Int height  = min( pcSlice->getSPS()->getMaxCUHeight(),pcSlice->getSPS()->getPicHeightInLumaSamples() - uiCUAddr / rpcPic->getFrameWidthInCU() * pcSlice->getSPS()->getMaxCUHeight() );
        Int width   = min( pcSlice->getSPS()->getMaxCUWidth(),pcSlice->getSPS()->getPicWidthInLumaSamples() - uiCUAddr % rpcPic->getFrameWidthInCU() * pcSlice->getSPS()->getMaxCUWidth() );
        Double MAD = (Double)SAD / (Double)(height * width);
        MAD = MAD * MAD;
        ( m_pcRateCtrl->getRCPic()->getLCU(uiCUAddr) ).m_MAD = MAD; //!< 注意:此处的MAD已经进行过K0103中公式的两个处理了。
#endif

        Int actualQP        = g_RCInvalidQPValue;
        Double actualLambda = m_pcRdCost->getLambda();
        Int actualBits      = pcCU->getTotalBits();
        Int numberOfEffectivePixels    = 0;
        for ( Int idx = 0; idx < rpcPic->getNumPartInCU(); idx++ )
        {
          if ( pcCU->getPredictionMode( idx ) != MODE_NONE && ( !pcCU->isSkipped( idx ) ) ) //!< 不考虑skip模式
          {
            numberOfEffectivePixels = numberOfEffectivePixels + 16;
            break;
          }
        }

        if ( numberOfEffectivePixels == 0 )
        {
          actualQP = g_RCInvalidQPValue;
        }
        else
        {
          actualQP = pcCU->getQP( 0 );
        }
        m_pcRdCost->setLambda(oldLambda);

#if RATE_CONTROL_INTRA
        m_pcRateCtrl->getRCPic()->updateAfterLCU( m_pcRateCtrl->getRCPic()->getLCUCoded(), actualBits, actualQP, actualLambda, 
          pcCU->getSlice()->getSliceType() == I_SLICE ? 0 : m_pcCfg->getLCULevelRC() );
#else
        m_pcRateCtrl->getRCPic()->updateAfterLCU( m_pcRateCtrl->getRCPic()->getLCUCoded(), actualBits, actualQP, actualLambda, m_pcCfg->getLCULevelRC() );
#endif
      }
#endif
#endif


接着,在TEncGOP::compressGOP中,一个slice编码完后,进行一次更新:

#if RATE_CONTROL_LAMBDA_DOMAIN
    if ( m_pcCfg->getUseRateCtrl() )
    {
#if !M0036_RC_IMPROVEMENT
      Double effectivePercentage = m_pcRateCtrl->getRCPic()->getEffectivePercentage();
#endif
      Double avgQP     = m_pcRateCtrl->getRCPic()->calAverageQP(); //!< arithmetic mean value for QP
      Double avgLambda = m_pcRateCtrl->getRCPic()->calAverageLambda(); //!< geometric mean value for lamda 
      if ( avgLambda < 0.0 )
      {
        avgLambda = lambda;
      }
#if M0036_RC_IMPROVEMENT
#if RATE_CONTROL_INTRA
      m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda, pcSlice->getSliceType());
#else
      m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda );
#endif
#else
      m_pcRateCtrl->getRCPic()->updateAfterPicture( actualHeadBits, actualTotalBits, avgQP, avgLambda, effectivePercentage );
#endif
      m_pcRateCtrl->getRCPic()->addToPictureLsit( m_pcRateCtrl->getPicList() );
      
      m_pcRateCtrl->getRCSeq()->updateAfterPic( actualTotalBits );
      if ( pcSlice->getSliceType() != I_SLICE )
      {
        m_pcRateCtrl->getRCGOP()->updateAfterPicture( actualTotalBits );
      }
      else    // for intra picture, the estimated bits are used to update the current status in the GOP
      {
        m_pcRateCtrl->getRCGOP()->updateAfterPicture( estimatedBits );
      }
    }
#else
    if(m_pcCfg->getUseRateCtrl())
    {
      UInt  frameBits = m_vRVM_RP[m_vRVM_RP.size()-1];
      m_pcRateCtrl->updataRCFrameStatus((Int)frameBits, pcSlice->getSliceType());
    }
#endif


最后,在TEncTop::encode()中,对当前GOP的码控变量进行了销毁。

至此,总体框架大体分析完毕,最后,简单地提一下TEncRateCtrl.h中定义的类的关系:TEncRateCtrl属于RC顶层的控制,负责整个RC的流程管理;TEncRCSeq负责序列级的RC管理;TEncRCGOP负责GOP级的RC管理;TEncRCPic负责picture级的RC管理;TRCLCU负责LCU级的RC管理。


原文地址:https://www.cnblogs.com/james1207/p/3299331.html