HEVC位元速率控制程式碼追蹤(一)(HEVC code tracing-Rate control)
阿新 • • 發佈:2019-01-28
接下來的一段時間將對位元速率控制程式碼追蹤這一塊的學習心得和領悟做個簡要的記錄,在將來的某一天,這些博文將讓我清晰地看到自己的成長。
位元速率控制的關鍵兩個步驟:(1)位元速率分配(2)目標位元速率的實現
所以在看程式碼的時候也儘量將步驟與程式碼對應起來,分塊閱讀,整合思考與優化。
在位元速率控制的兩個步驟中又可以依據操作物件(層次)的不同,分別從GOP,picture,lcu這三個方面去看程式。在對功能函式進行解讀之前,我先來看下在整個解決方案中,程式是如何一層一層的呼叫下來的。
這是main函式裡面的一段程式碼,也是真正編碼功能開始的入口。// call encoding function cTAppEncTop.encode();//cTAppEncTop是TAppEncTop的物件,該語句的含義是呼叫成員函式。。encode是類TAppEncTop的成員函式
// call encoding function for one frame if ( m_isField ) m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded, m_isTopFieldFirst ); else m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded );
這是Void TAppEncTop::encode()裡面的一段程式碼,呼叫類TEncTop的成員函式encode。下面進入到這個類成員函式中去。到這算是從APP類來到了lib類工程當中。
<span style="color:#ff6666;">Void TEncTop::encode</span>( Bool flush, TComPicYuv* pcPicYuvOrg, TComPicYuv* pcPicYuvTrueOrg, const InputColourSpaceConversion snrCSC, TComList<TComPicYuv*>& rcListPicYuvRecOut, std::list<AccessUnit>& accessUnitsOut, Int& iNumEncoded ) { if (pcPicYuvOrg != NULL) { // get original YUV TComPic* pcPicCurr = NULL; xGetNewPicBuffer( pcPicCurr ); pcPicYuvOrg->copyToPic( pcPicCurr->getPicYuvOrg() ); pcPicYuvTrueOrg->copyToPic( pcPicCurr->getPicYuvTrueOrg() ); // compute image characteristics if ( getUseAdaptiveQP() ) { m_cPreanalyzer.xPreanalyze( dynamic_cast<TEncPic*>( pcPicCurr ) ); } } if ((m_iNumPicRcvd == 0) || (!flush && (m_iPOCLast != 0) && (m_iNumPicRcvd != m_iGOPSize) && (m_iGOPSize != 0))) { iNumEncoded = 0; return; } //該工程中關於位元速率控制部分 <span style="color:#ff0000;"> if ( m_RCEnableRateControl )//在GOP層初始化</span> { m_cRateCtrl.initRCGOP( m_iNumPicRcvd );//呼叫TEncRatectrl.cpp中的initRCGOP函式 } // compress GOP m_cGOPEncoder.compressGOP(m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, accessUnitsOut, false, false, snrCSC, m_printFrameMSE); if ( m_RCEnableRateControl )//釋放 { m_cRateCtrl.destroyRCGOP(); } iNumEncoded = m_iNumPicRcvd; m_iNumPicRcvd = 0; m_uiNumAllPicCoded += iNumEncode;
}
在TEncTop.cpp中有兩個<span style="font-family: Arial, Helvetica, sans-serif;"><span style="color:#ff0000;">Void TEncTop::encode,上面的是在啟用位元速率控制的情況下,對GOP層的初始化以及呼叫compressGOP函式。</span></span>
<pre name="code" class="cpp"><span style="color:#ff0000;">Void TEncTop::encode</span>(Bool flush, TComPicYuv* pcPicYuvOrg, TComPicYuv* pcPicYuvTrueOrg, const InputColourSpaceConversion snrCSC, TComList<TComPicYuv*>& rcListPicYuvRecOut, std::list<AccessUnit>& accessUnitsOut, Int& iNumEncoded, Bool isTff)
{
iNumEncoded = 0;
for (Int fieldNum=0; fieldNum<2; fieldNum++)
{
if (pcPicYuvOrg)
{
/* -- field initialization -- */
const Bool isTopField=isTff==(fieldNum==0);
TComPic *pcField;
xGetNewPicBuffer( pcField );
pcField->setReconMark (false); // where is this normally?
if (fieldNum==1) // where is this normally?
{
TComPicYuv* rpcPicYuvRec;
// org. buffer
if ( rcListPicYuvRecOut.size() >= (UInt)m_iGOPSize+1 ) // need to maintain field 0 in list of RecOuts while processing field 1. Hence +1 on m_iGOPSize.
{
rpcPicYuvRec = rcListPicYuvRecOut.popFront();
}
else
{
rpcPicYuvRec = new TComPicYuv;
rpcPicYuvRec->create( m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, g_uiMaxCUWidth, g_uiMaxCUHeight, g_uiMaxCUDepth);
}
rcListPicYuvRecOut.pushBack( rpcPicYuvRec );
}
pcField->getSlice(0)->setPOC( m_iPOCLast ); // superfluous?
pcField->getPicYuvRec()->setBorderExtension(false);// where is this normally?
pcField->setTopField(isTopField); // interlaced requirement
for (UInt componentIndex = 0; componentIndex < pcPicYuvOrg->getNumberValidComponents(); componentIndex++)
{
const ComponentID component = ComponentID(componentIndex);
const UInt stride = pcPicYuvOrg->getStride(component);
separateFields((pcPicYuvOrg->getBuf(component) + pcPicYuvOrg->getMarginX(component) + (pcPicYuvOrg->getMarginY(component) * stride)),
pcField->getPicYuvOrg()->getAddr(component),
pcPicYuvOrg->getStride(component),
pcPicYuvOrg->getWidth(component),
pcPicYuvOrg->getHeight(component),
isTopField);
separateFields((pcPicYuvTrueOrg->getBuf(component) + pcPicYuvTrueOrg->getMarginX(component) + (pcPicYuvTrueOrg->getMarginY(component) * stride)),
pcField->getPicYuvTrueOrg()->getAddr(component),
pcPicYuvTrueOrg->getStride(component),
pcPicYuvTrueOrg->getWidth(component),
pcPicYuvTrueOrg->getHeight(component),
isTopField);
}
// compute image characteristics
if ( getUseAdaptiveQP() )//自適應的QP值
{
m_cPreanalyzer.xPreanalyze( dynamic_cast<TEncPic*>( pcField ) );
}
}
if ( m_iNumPicRcvd && ((flush&&fieldNum==1) || (m_iPOCLast/2)==0 || m_iNumPicRcvd==m_iGOPSize ) )
{
// compress GOP經過前面的一系列的初始化,呼叫之後,這裡才正式進入到壓縮的功能函式
m_cGOPEncoder.compressGOP(m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, accessUnitsOut, true, isTff, snrCSC, m_printFrameMSE);
iNumEncoded += m_iNumPicRcvd;
m_uiNumAllPicCoded += m_iNumPicRcvd;
m_iNumPicRcvd = 0;
}
}
}
一般情況下,輸入的系列都是分為GOP,再以GOP為物件進行壓縮,所以後續的各種操作都將被compressGOP呼叫。這裡GOPEncoder是定義的一個類TEncGOP的物件,所以m_cGOPEncoder.compressGOP就是呼叫類TEncGOP的成員函式compressGOP。下面進入到compressGOP函式。這個函式非常的大,但是從我們位元速率控制的角度來講只需要關注幾段程式碼就可以。
//位元速率控制啟用,lambda域的提案演算法
Double lambda = 0.0;//定義lambda,並且賦初值
Int actualHeadBits = 0;//標頭檔案資訊
Int actualTotalBits = 0;//總資訊
Int estimatedBits = 0;//預測位元數
Int tmpBitsBeforeWriting = 0;//
if ( m_pcCfg->getUseRateCtrl() )//啟用位元速率控制->的作用相當於點操作和指標解引操作的兩步之和
{
Int frameLevel = m_pcRateCtrl->getRCSeq()->getGOPID2Level( iGOPid );//定義framrlevel這個變數
if ( pcPic->getSlice(0)->getSliceType() == I_SLICE )//如果為I片
{
frameLevel = 0;
}//這裡的意思是,如果為I片的話就是預設幀層次為0
m_pcRateCtrl->initRCPic( frameLevel );//m_pcRateCtrl為指向類TEncRateCtrl的指標,這條語句的作用是呼叫類TEncRateCtrl的成員函式initRCPic
estimatedBits = m_pcRateCtrl->getRCPic()->getTargetBits();//就是等於getTargetBits()的返回值Int getTargetBits() { return m_targetBits; }
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 );//B幀數=GOP大小-1 因為第一幀 為I幀
Double dLambda_scale = 1.0 - Clip3( 0.0, 0.5, 0.05*(Double)NumberBFrames );//定義一個double型的變數,且計算初始值
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幀內預測
{
m_pcSliceEncoder->calCostSliceI(pcPic);
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 );
if ( bits < 200 )
{
bits = 200;
}
m_pcRateCtrl->getRCPic()->setTargetBits( bits );
}
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
{
list<TEncRCPic*> listPreviousPicture = m_pcRateCtrl->getPicList();
lambda = m_pcRateCtrl->getRCPic()->estimatePicLambda( listPreviousPicture, pcSlice->getSliceType());
sliceQP = m_pcRateCtrl->getRCPic()->estimatePicQP( lambda, listPreviousPicture );
}
sliceQP = Clip3( -pcSlice->getSPS()->getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, sliceQP );
m_pcRateCtrl->getRCPic()->setPicEstQP( sliceQP );
m_pcSliceEncoder->resetQP( pcPic, sliceQP, lambda );
}//到這裡slice層的位元速率控制初始化結束