zoukankan      html  css  js  c++  java
  • 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管理。


  • 相关阅读:
    结对-结对编程项目作业名称-结对项目总结
    课后作业 -团队项目编程进度
    团队-象棋游戏-模块开发过程
    团队-象棋游戏-模块测试过程
    结队-结队编程项目作业名称-项目进度
    课后作业-阅读任务-阅读笔记-3
    团队-象棋游戏-需求分析
    团队-象棋游戏-成员简介及分工
    课后作业 -团队项目编程进度
    团队编程 象棋游戏 开发文档
  • 原文地址:https://www.cnblogs.com/james1207/p/3299331.html
Copyright © 2011-2022 走看看