HEVC码率控制TEncRCPic

TEncRCPic帧级别码率控制

先开看成员属性

补充:m_totalCostIntra是指该帧(I帧)的帧内所有LCU的代价总和,即该帧的复杂度(SATD),该值通过TEncSlice::calCostSliceI函数计算,同时LCU的m_costIntra参数也在该函数中赋值

m_remainingCostIntra是指该帧(I帧)的帧内所有还未编码LCU的代价总和,在getLCUInitTargetBits函数中将m_remainingCostIntra赋值为m_totalCostIntra

m_LCUs是一个数组,结构体TRLCU记录该帧中所有LCU的参数

struct TRCLCU
{
  Int m_actualBits; //LCU实际比特
  Int m_QP;     // QP of skip mode is set to g_RCInvalidQPValue
  Int m_targetBits;//LCU的目标比特
  Double m_lambda; 
  Double m_bitWeight; //根该LCU的根据BU分配的bits,m_targetBits由该参数进行修正,但非I帧修正才用这个
  Int m_numberOfPixel;//像素总量
  Double m_costIntra; //I帧该LCU编码的代价,I帧修正用这个
  Int m_targetBitsLeft;//包括该LCU在内和之后的LCU编码需要的总bits,在getLCUInitTargetBits函数中进行初始化
};

构造函数和析构函数还是初始化为0 

再开看create函数

Void create( TEncRCSeq* encRCSeq, TEncRCGOP* encRCGOP, Int frameLevel, list<TEncRCPic*>& listPreviousPictures );

传参有个frameLevel,这个是帧级别,是在TEncRateCtrl的init函数中进行了指定

Void TEncRateCtrl::initRCPic( Int frameLevel )
{
  m_encRCPic = new TEncRCPic;
  m_encRCPic->create( m_encRCSeq, m_encRCGOP, frameLevel, m_listRCPictures );
}

xEstPicTargetBits是计算该帧的目标比特,xEstPicHeaderBits是该帧的预估头部bits,后面介绍

// at least allocate 100 bits for picture data,每帧至少分配100bits

从encRCSeq获取一些参数赋值给该类的成员属性

picWidthInLCU和picHeightInLCU是计算宽和高的LCU数量

注意,这里的剩余bit要减去m_estHeaderBits

 m_bitsLeft       -= m_estHeaderBits;

这里同时对LCU的所有参数进行了初始化

currWidth和currHeight是因为最右边和最小的LCU不一定是最大LCU,即64X64,计算一下该LCU的像素总量。

xEstPicTargetBits

xEstPicTargetBits函数,计算该帧的目标bits对视频序列中剩余的帧数大于16时,对分配为当前帧的目标比特数进行调整。

算法原理

xEstPicHeaderBits函数,计算预估头bits

记录和当前帧处于同一level的帧有多少,以及它们的头信息花费的真正的比特数之和,求平均值。

 getRefineBitsForIntra

在create函数中通过xEstPicTargetBits函数获取了该帧的目标比特,但对于I帧编码形式,在计算I帧目标比特时需要进行修正,先调用calCostSliceI函数计算I帧的SATD复杂度,即m_totalCostIntra。再调用getLeftAverageBits获取该序列平均帧比特(对于IPPP编码来说就是该视频的平均比特),然后调用getRefineBitsForIntra,传参就是平均比特进行修正,获得该I帧真正的目标比特。调用setTargetBits设置该I帧真正的比特。

留个疑问,那计算I帧的目标比特还有什么意义吗?反正都要修正?修正也没有用到I帧通过xEstPicTargetBits计算得到的目标比特

 else if ( frameLevel == 0 )   // intra case, but use the model
      {
        m_pcSliceEncoder->calCostSliceI(pcPic); // TODO: This only analyses the first slice segment - what about the others?

        if ( m_pcCfg->getIntraPeriod() != 1 )   // do not refine allocated bits for all intra case
        {
          Int bits = m_pcRateCtrl->getRCSeq()->getLeftAverageBits();
          bits = m_pcRateCtrl->getRCPic()->getRefineBitsForIntra( bits );
...
...
 m_pcRateCtrl->getRCPic()->setTargetBits( bits );

这是HM16.9中的I帧目标比特修正,之前的版本是

bpp

bpp>0.2

0.2≥bpp>0.1

其他

修正Rtarget

5XRtarget

7XRtarget

10XRtarget

calCostSliceI函数

for( UInt ctuTsAddr = startCtuTsAddr, ctuRsAddr = pcPic->getPicSym()->getCtuTsToRsAddrMap( startCtuTsAddr);
       ctuTsAddr < boundingCtuTsAddr;
       ctuRsAddr = pcPic->getPicSym()->getCtuTsToRsAddrMap(++ctuTsAddr) )
  {
    // initialize CU encoder
    TComDataCU* pCtu = pcPic->getCtu( ctuRsAddr );
    pCtu->initCtu( pcPic, ctuRsAddr );

    Int height  = min( sps.getMaxCUHeight(),sps.getPicHeightInLumaSamples() - ctuRsAddr / pcPic->getFrameWidthInCtus() * sps.getMaxCUHeight() );
    Int width   = min( sps.getMaxCUWidth(), sps.getPicWidthInLumaSamples()  - ctuRsAddr % pcPic->getFrameWidthInCtus() * sps.getMaxCUWidth() );

    Int iSumHad = m_pcCuEncoder->updateCtuDataISlice(pCtu, width, height);

    (m_pcRateCtrl->getRCPic()->getLCU(ctuRsAddr)).m_costIntra=(iSumHad+offset)>>shift;
    iSumHadSlice += (m_pcRateCtrl->getRCPic()->getLCU(ctuRsAddr)).m_costIntra;

  }
  m_pcRateCtrl->getRCPic()->setTotalIntraCost(iSumHadSlice);

代码主体部分如上,updateCtuDataISlice函数计算出该LCU的m_costIntra基于哈达玛变换的SATD,然后赋值给该LCU的帧内编码代价m_costIntra,for循环接受把所有LCU的m_costIntra加起来就是该I帧的帧内代价。计算出该帧内代价后就可以通过getRefineBitsForIntra函数计算出I帧修正比特。

estimatePicLambda函数

上面已经计算出了I帧和非I帧的目标比特,自然就知道该帧的bpp,调用该函数计算出计算帧的预估lambda,同时这个函数也对一个图像中所有的LCU的码率控制参数进行初始化,后面的代码是先计算帧的Lamda,再估计帧QP

在I帧情况下调用了getLCUInitTargetBits函数,该函数的作用是初始化LCU的m_targetBitsLeft参数,用于后续的I帧LCU层码率分配更新,同时该函数还有一个重要点,就是把上面calCostSliceI函数计算得到的m_totalCostIntra赋值给m_remainingCostIntra

    {  //I帧
        list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
        m_pcRateCtrl->getRCPic()->getLCUInitTargetBits();
        lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
        sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
      }
      else    // normal case 非I帧
      {
        list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
        lambda  = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
        sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
      }

 

先估计当前帧的lambda,I帧和P帧有不同的估计算法,底下的代码都是为了约束当前帧的lambda

 lastLevelLambda记录和当前帧处于同一level的帧的lambda

lastPicLambda记录上一帧的lambda

 启用LCU的分层模式,同一个图片层的不同LCU的参数是不同的,所以要遍历整个图像层的所有LCU ,分配不同的参数

 

该LCU的BU(基本单元)所占比重初始化,LCU的权重计算使用的是模型参数alpha和beta。

m_bitWeight刚开始是指该LCU的BU比重,后来计算后变成了该LCU根据BU比重所分配的比特,我对m_bitWeight的理解是根据比重计算的每个BU的初始比特,没有考虑该帧内的编码情况。有的BU可能多一点。有的可能少一点,只是一个初始化,并不是该BU的目标比特,后续会在compressSlice函数中调用getLCUTargetBpp进行修正。

BU的比重是

 estimatePicQP函数,预估QP,上面已经预估了Lambda

之后的代码同样是对QP的约束

 getLCUTargetBpp

xEstPicTargetBits是计算该帧的目标比特,那getLCUTargetBpp就是计算该帧下LCU的目标比特。

但说计算其实也不准确,LCU在estimatePicLambda函数中已经计算了BU的比特,但只是简单的分配,并未考虑编码过程中实际和目标的差距,getLCUTargetBpp我更认为是根据该帧剩余比特、剩余LCU动态调整目标比特的过程。

该函数计算了LCU的目标比特,同时计算LCU的bpp值,通过该bpp得到该CLU的estLambda。

下面这段程序的目的是为了获得LCU层的目标比特数Bpp,那么说先要获得图片层的各单元的总的目标比特数,再除以整个LCU层的总像素数。
分两种情况:I帧和非I帧。
(1)当为I帧时,考虑getLCU(LCUIdx).m_costIntra这个变量,他是表征各索引LCU块的costIntra(我的理解是帧内比重的意思),
在帧内又有两种情况,剩余的总的costIntra>0.1时,就要考虑各个LCU块的比重,并根据比重对各个LCU块分配比特。当剩余的比重小于0.1时,就对
剩余的块平均分配剩余的比特数。
(2)当为非I帧时,使用bitWeight表示各个LCU块的比重(这里bitWeight其实是根据BU分配的比特),求出各个块的分配到的比特数avgBits 。
两种情况的Bpp都是通过下面的公式求得:bpp = ( Double )avgBits/( Double )m_LCUs[ LCUIdx ].m_numberOfPixel;

Int   LCUIdx    = getLCUCoded() =  m_numberOfLCU - m_LCULeft

总LCU数量 未编码的LCU数量 = 已经编码的LCU数量 =正在编码的LCU的索引

先看看I帧情况

Int noOfLCUsLeft = m_numberOfLCU - LCUIdx + 1;

noOfLCUsLeft=总LCU数量-已经编码的LCU数量+1 = 未编码LCU+1

Int bitrateWindow = min(4,noOfLCUsLeft);//比特率窗口,4和noOfLCUsLeft取最小

Double MAD      = getLCU(LCUIdx).m_costIntra;//编号为LCUIdx的LCU块的比重值为MAD

if (m_remainingCostIntra > 0.1 )//帧内未编码LCU占总比重

{

Double weightedBitsLeft =(m_bitsLeft*bitrateWindow+(m_bitsLeft-getLCU(LCUIdx).m_targetBitsLeft)*noOfLCUsLeft)/(Double)bitrateWindow;

//weightedBitsLeft其实是修正后的剩余比特,简化一下该等式

weightedBitsLeft=

m_bitsLeft(m_bitsLeftgetLCU(LCUIdx).m_targetBitsLeft)*noOfLCUsLeft)/(Double)bitrateWindow

bitsLeft> targetBitsLeft时说明后续用不了那么多bits,该帧剩余比特可以加一点

反之bitsLeft<targetBitsLeft时说明该帧的剩余比特不够用了,该帧剩余比特减一点

avgBits = Int( MAD*weightedBitsLeft/m_remainingCostIntra );//就是剩余的比特数*当前块在总块中的比重

m_remainingCostIntra -= MAD;// m_remainingCostIntra进行更新

m_LCUs[ LCUIdx ].m_targetBits = avgBits; //更新该LCU的目标比特

}

后续的P帧也是类似情况,需要对剩余比特avgBits进行修正

for ( Int i=LCUIdx; i<m_numberOfLCU; i++ )

    {

      totalWeight += m_LCUs[i].m_bitWeight;

     }

 Int realInfluenceLCU =min( g_RCLCUSmoothWindowSize, getLCULeft() );

 avgBits = (Int)( m_LCUs[LCUIdx].m_bitWeight - ( totalWeight - m_bitsLeft ) / realInfluenceLCU + 0.5 );

其中m_bitWeight代表:为每个BU(基本单元)分配的初始目标bit数。上式中为何会有减号后一项?这是因为HM会根据上一个LCU实际所用bits数来更新下一个LCU的目标bits(动态调整)。(若初始剩余比特totalWeight > 实际剩余比特m_bitsLeft,说明前面编码过的LCU分配多了,则应该减少下一个LCU的目标bits。否则若还按照m_bitWeight分配目标bits,则会导致总bits数分完后还有剩余LCU未编码。)realInfluenceLCU是平滑窗口,即平滑比特波动。+0.5达到向上取整的目的。

可以参考该帖HEVC帧间码率控制https://www.codetd.com/article/922772

 LCU块的bpp由以上函数已经知道,根据bpp计算CLU的预估lambda

函数getLCUEstLambda

后续都是约束

 //for Lambda clip, LCU level clip
  Double clipNeighbourLambda = -1.0;
  for ( Int i=LCUIdx - 1; i>=0; i-- )
  {
    if ( m_LCUs[i].m_lambda > 0 )
    {
      clipNeighbourLambda = m_LCUs[i].m_lambda;
      break;
    }
  }

  if ( clipNeighbourLambda > 0.0 )
  {
    estLambda = Clip3( clipNeighbourLambda * pow( 2.0, -1.0/3.0 ), clipNeighbourLambda * pow( 2.0, 1.0/3.0 ), estLambda );
  }

  if ( clipPicLambda > 0.0 )
  {
    estLambda = Clip3( clipPicLambda * pow( 2.0, -2.0/3.0 ), clipPicLambda * pow( 2.0, 2.0/3.0 ), estLambda );
  }
  else
  {
    estLambda = Clip3( 10.0, 1000.0, estLambda );
  }

  if ( estLambda < 0.1 )
  {
    estLambda = 0.1;
  }

  return estLambda;

函数getLCUEstQP

同理,知道LCU的lambda后计算QP,不再赘述

updateAfterCTU是编码完一个LCU后更新参数、

 updateAfterPicture编码完成一帧后的参数更新

addToPictureLsit是把编码完的帧加入list中

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值