H.265/HEVC(High Efficiency Video Coding)のリファレンスソフトウェア(HM)のエンコーダのmain関数と、configファイルをパースするTAppEncCfg::parseCfg関数について学んでいきます。
main関数
source/App/TAppEncoder/encmain.cppにエンコーダのmain関数があります。cTAppEncTop.parseCfg()でconfigファイルをパース、内部パラメータの生成を行い、cTAppEncTop.encode()でエンコードを実行します。
int main(int argc, char* argv[])
{
TAppEncTop cTAppEncTop;
// print information
fprintf( stdout, "\n" );
fprintf( stdout, "HM software: Encoder Version [%s] (including RExt)", NV_VERSION );
fprintf( stdout, NVM_ONOS );
fprintf( stdout, NVM_COMPILEDBY );
fprintf( stdout, NVM_BITS );
fprintf( stdout, "\n\n" );
// create application encoder class
// cTAppEncTopクラスの作成. 関数の中身は空っぽ
cTAppEncTop.create();
// parse configuration
try
{
if(!cTAppEncTop.parseCfg( argc, argv ))
{
cTAppEncTop.destroy();
#if ENVIRONMENT_VARIABLE_DEBUG_AND_TEST
EnvVar::printEnvVar();
#endif
return 1;
}
}
catch (df::program_options_lite::ParseFailure &e)
{
std::cerr << "Error parsing option \""<< e.arg <<"\" with argument \""<< e.val <<"\"." << std::endl;
return 1;
}
#if PRINT_MACRO_VALUES
printMacroSettings();
#endif
#if ENVIRONMENT_VARIABLE_DEBUG_AND_TEST
EnvVar::printEnvVarInUse();
#endif
// starting time
Double dResult;
clock_t lBefore = clock();
// call encoding function
cTAppEncTop.encode();
// ending time
dResult = (Double)(clock()-lBefore) / CLOCKS_PER_SEC;
printf("\n Total Time: %12.3f sec.\n", dResult);
// destroy application encoder class
cTAppEncTop.destroy();
return 0;
}
TAppEncCfg::parseCfg()
main関数で生成されるcTAppEncTop(TAppEncTopクラス)は親クラスTAppEncCfgの子クラスです。parseCfg()はこのTAppEncCfgクラスの関数です。1500行以上の非常に長い関数ですが、configファイルのパースと、cTAppEncTopの内部変数を初期化する大部分を占めています。
Bool TAppEncCfg::parseCfg( Int argc, TChar* argv[] )
{
...
// check validity of input parameters
xCheckParameter();
// compute actual CU depth with respect to config depth and max transform size
UInt uiAddCUDepth = 0;
while( (m_uiMaxCUWidth>>m_uiMaxCUDepth) > ( 1 << ( m_uiQuadtreeTULog2MinSize + uiAddCUDepth ) ) )
{
uiAddCUDepth++;
}
m_uiMaxTotalCUDepth = m_uiMaxCUDepth + uiAddCUDepth + getMaxCUDepthOffset(m_chromaFormatIDC, m_uiQuadtreeTULog2MinSize); // if minimum TU larger than 4x4, allow for additional part indices for 4:2:2 SubTUs.
m_uiLog2DiffMaxMinCodingBlockSize = m_uiMaxCUDepth - 1;
// print-out parameters
xPrintParameter();
return true;
}
parseCfg()内の最後の方でコールされるxCheckParameter()も900行以上の長い関数です。内部変数の値をチェックしていくとともに、関数の途中でGOP(Group Of Pictures)構造を保持するリスト(m_GOPList)を生成します。ここでは、m_GOPListの生成に着目してコードを見ていきます。
m_GOPListのデフォルト値は、GOP構造が記述されたconfigファイルから決定されます。
...
#======== Coding Structure =============
IntraPeriod : 32 # Period of I-Frame ( -1 = only first)
DecodingRefreshType : 1 # Random Accesss 0:none, 1:CRA, 2:IDR, 3:Recovery Point SEI
GOPSize : 8 # GOP Size (number of B slice = GOPSize-1)
# Type POC QPoffset QPOffsetModelOff QPOffsetModelScale CbQPoffset CrQPoffset QPfactor tcOffsetDiv2 betaOffsetDiv2 temporal_id #ref_pics_active #ref_pics reference pictures predict deltaRPS #ref_idcs reference idcs
Frame1: B 8 1 0.0 0.0 0 0 0.442 0 0 0 2 3 -8 -12 -16 0
Frame2: B 4 2 0.0 0.0 0 0 0.3536 0 0 1 2 3 -4 -8 4 1 4 4 1 1 0 1
Frame3: B 2 3 0.0 0.0 0 0 0.3536 0 0 2 2 4 -2 -6 2 6 1 2 4 1 1 1 1
Frame4: B 1 4 0.0 0.0 0 0 0.68 0 0 3 2 4 -1 1 3 7 1 1 5 1 0 1 1 1
Frame5: B 3 4 0.0 0.0 0 0 0.68 0 0 3 2 4 -1 -3 1 5 1 -2 5 1 1 1 1 0
Frame6: B 6 3 0.0 0.0 0 0 0.3536 0 0 2 2 3 -2 -6 2 1 -3 5 0 1 1 1 0
Frame7: B 5 4 0.0 0.0 0 0 0.68 0 0 3 2 4 -1 -5 1 3 1 1 4 1 1 1 1
Frame8: B 7 4 0.0 0.0 0 0 0.68 0 0 3 2 4 -1 -3 -7 1 1 -2 5 1 1 1 1 0
...
上記のconfigファイルがparseCfg()内でパースされ、m_GOPListにデフォルト値として格納されています。
Bool TAppEncCfg::parseCfg( Int argc, TChar* argv[] )
{
...
for(Int i=1; i<MAX_GOP+1; i++)
{
std::ostringstream cOSS;
cOSS<<"Frame"<<i;
opts.addOptions()(cOSS.str(), m_GOPList[i-1], GOPEntry());
}
...
}
configファイル反映後のm_GOPListデフォルト値を整理すると、以下のような状態になっています。
配列要素No | m_POC 表示順序 | m_referencePics 参照ピクチャの相対位置 ※()内の数字はm_POC – 相対位置 | m_numRefPics 参照ピクチャ数 | m_usedByCurrPic 各々の参照ピクチャが当該フレームに参照される場合は1 |
---|---|---|---|---|
0 | 8 | -8 ( 0) -12 ( -4) -16 ( -8) | 3 | 1 0 0 |
1 | 4 | -4 ( 0) -8 ( -4) 4 ( 8) | 3 | 1 0 1 |
2 | 2 | -2 ( 0) -6 ( -4) 2 ( 4) 6 ( 8) | 4 | 1 0 1 1 |
3 | 1 | -1 ( 0) 1 ( 2) 3 ( 4) 7 ( 8) | 4 | 1 1 1 1 |
4 | 3 | -1 ( 2) -3 ( 0) 1 ( 4) 5 ( 8) | 4 | 1 1 1 1 |
5 | 6 | -2 ( 4) -6 ( 0) 2 ( 8) | 3 | 1 1 1 |
6 | 5 | -1 ( 4) -5 ( 0) 1 ( 6) 3 ( 8) | 4 | 1 1 1 1 |
7 | 7 | -1 ( 6) -3 ( 4) -7 ( 0) 1 ( 8) | 4 | 1 1 1 1 |
xCheckParameter()の以下の処理でm_GOPList内の各エントリの内容がチェックされ、参照ピクチャのPOCがIピクチャより前を指してしまうエントリに対して修正が施され、新たなエントリが追加されます。追加されたエントリ数はm_extraRPSsとして保持されます。
Void TAppEncCfg::xCheckParameter()
{
...
Bool verifiedGOP=false;
Bool errorGOP=false;
Int checkGOP=1;
Int numRefs = m_isField ? 2 : 1;
Int refList[MAX_NUM_REF_PICS+1];
refList[0]=0;
if(m_isField)
{
refList[1] = 1;
}
Bool isOK[MAX_GOP];
for(Int i=0; i<MAX_GOP; i++)
{
isOK[i]=false;
}
...
m_extraRPSs=0;
//start looping through frames in coding order until we can verify that the GOP structure is correct.
while(!verifiedGOP&&!errorGOP)
{
//configファイルに記述したGOPのインデックスを算出
//ループが回るたびに0, 1, 2, 3, ... と1ずつインクリメント
//checkGOPは1オリジンのチェックするGOPのインデックス数でwhileループが終了までインクリメントし続ける
Int curGOP = (checkGOP-1)%m_iGOPSize;
//チェックするGOPのインデックスに対応するPOC(Picture Order Count)を算出
//上記configの場合, ループが回るたびに8, 4, 2, 1, 3, 6, 5, 7, 16, 12, ... と遷移する
Int curPOC = ((checkGOP-1)/m_iGOPSize)*m_iGOPSize + m_GOPList[curGOP].m_POC;
if(m_GOPList[curGOP].m_POC<0)
{
printf("\nError: found fewer Reference Picture Sets than GOPSize\n");
errorGOP=true;
}
else
{
//check that all reference pictures are available, or have a POC < 0 meaning they might be available in the next GOP.
Bool beforeI = false;
//curGOPで指定したフレームの参照ピクチャを全てサーチ
for(Int i = 0; i< m_GOPList[curGOP].m_numRefPics; i++)
{
//参照ピクチャのPOCを算出
Int absPOC = curPOC+m_GOPList[curGOP].m_referencePics[i];
if(absPOC < 0)
{
//算出した参照ピクチャのPOCの値が一つでも0未満であればbeforeI=trueとする
beforeI=true;
}
else
{
Bool found=false;
//numRefsはrefListに追加した参照ピクチャの数. 初期状態はnumRefs=1 (refList[0]=0)
for(Int j=0; j<numRefs; j++)
{
//参照ピクチャがrefList内に存在するか判定
if(refList[j]==absPOC)
{
found=true;
for(Int k=0; k<m_iGOPSize; k++)
{
//参照ピクチャがrefList内に存在する場合, 参照ピクチャのPOCに対応するGOPエントリを検出
if(absPOC%m_iGOPSize == m_GOPList[k].m_POC%m_iGOPSize)
{
//参照ピクチャのtemporalIdと現フレームのtemporalIdが同じ場合, 参照ピクチャに対応するGOPエントリのm_refPicをtrueにする
if(m_GOPList[k].m_temporalId==m_GOPList[curGOP].m_temporalId)
{
m_GOPList[k].m_refPic = true;
}
//参照ピクチャのtemporalIdが現フレームのtempralId以下の場合, 参照ピクチャに対応するGOPエントリのm_usedByCurrPicに1にする
m_GOPList[curGOP].m_usedByCurrPic[i]=m_GOPList[k].m_temporalId<=m_GOPList[curGOP].m_temporalId;
}
}
}
} //for(Int j=0; j<numRefs; j++)
if(!found)
{
printf("\nError: ref pic %d is not available for GOP frame %d\n",m_GOPList[curGOP].m_referencePics[i],curGOP+1);
errorGOP=true;
}
}
} //for(Int i = 0; i< m_GOPList[curGOP].m_numRefPics; i++)
//参照ピクチャのPOCが全て0以上である(beforeI=false)か判定
if(!beforeI&&!errorGOP)
{
//all ref frames were present
if(!isOK[curGOP])
{
numOK++;
isOK[curGOP]=true;
//numOKがGOPサイズ分揃ったらverifiedGOP=trueにしてwhileループ終了する
if(numOK==m_iGOPSize)
{
verifiedGOP=true;
}
}
} //if(!beforeI&&!errorGOP)
else
{
//create a new GOPEntry for this frame containing all the reference pictures that were available (POC > 0)
#if DPB_ENCODER_USAGE_CHECK
assert(m_iGOPSize+m_extraRPSs < MAX_GOP);
#endif
//参照ピクチャのPOCの値が一つでも0未満であれば, m_GOPListにエントリを追加
//curGOPで指定したm_GOPListの値を全てコピー
m_GOPList[m_iGOPSize+m_extraRPSs]=m_GOPList[curGOP];
Int newRefs=0;
//curGOPで指定したフレームの参照ピクチャを全てサーチ
for(Int i = 0; i< m_GOPList[curGOP].m_numRefPics; i++)
{
//参照ピクチャのPOCを算出
Int absPOC = curPOC+m_GOPList[curGOP].m_referencePics[i];
//参照ピクチャのPOCの値が0未満のものは除き, 値が0以上のPOCに関する情報のみ配列の先頭に詰めて格納する
//newRefsは値が参照ピクチャのPOCの値が0以上の有効な参照ピクチャ数を示す
if(absPOC>=0)
{
m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[newRefs]=m_GOPList[curGOP].m_referencePics[i];
m_GOPList[m_iGOPSize+m_extraRPSs].m_usedByCurrPic[newRefs]=m_GOPList[curGOP].m_usedByCurrPic[i];
newRefs++;
}
}
//L0とL1リストのサイズをコピー
Int numPrefRefs = m_GOPList[curGOP].m_numRefPicsActive;
//これまでに処理したGOPエントリを逆順に(符号化順で最新のものから)サーチ
for(Int offset = -1; offset>-checkGOP; offset--)
{
//step backwards in coding order and include any extra available pictures we might find useful to replace the ones with POC < 0.
Int offGOP = (checkGOP-1+offset)%m_iGOPSize;
Int offPOC = ((checkGOP-1+offset)/m_iGOPSize)*m_iGOPSize + m_GOPList[offGOP].m_POC;
//現フレームのtemporalId以下のピクチャのみ以下の処理を行う
if(offPOC>=0&&m_GOPList[offGOP].m_temporalId<=m_GOPList[curGOP].m_temporalId)
{
Bool newRef=false;
//refListにサーチ対象のPOC(offPOC)が存在したらnewRef=trueにする
//numRefsはrefListに追加した参照ピクチャの数
for(Int i=0; i<numRefs; i++)
{
if(refList[i]==offPOC)
{
newRef=true;
}
}
//サーチ対象のPOC(offPOC)が参照ピクチャである場合はnewRef=falseにする
//offPOC-curPOCは現ピクチャから見た参照ピクチャの相対的な位置を示す
for(Int i=0; i<newRefs; i++)
{
if(m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[i]==offPOC-curPOC)
{
newRef=false;
}
}
//サーチ対象のPOC(offPOC)がrefListに存在し, かつ参照ピクチャでない場合
//今回のconfigではif文中は実行されない
if(newRef)
{
Int insertPoint=newRefs;
//this picture can be added, find appropriate place in list and insert it.
if(m_GOPList[offGOP].m_temporalId==m_GOPList[curGOP].m_temporalId)
{
m_GOPList[offGOP].m_refPic = true;
}
//サーチ対象のPOC(offPOC)の配列への挿入位置を決定
for(Int j=0; j<newRefs; j++)
{
if(m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[j]<offPOC-curPOC||m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[j]>0)
{
insertPoint = j;
break;
}
}
//決定した挿入位置(insertPoint)にL0/L1のリストが埋まるまでoffPOCを挿入する
Int prev = offPOC-curPOC;
Int prevUsed = m_GOPList[offGOP].m_temporalId<=m_GOPList[curGOP].m_temporalId;
for(Int j=insertPoint; j<newRefs+1; j++)
{
Int newPrev = m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[j];
Int newUsed = m_GOPList[m_iGOPSize+m_extraRPSs].m_usedByCurrPic[j];
m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[j]=prev;
m_GOPList[m_iGOPSize+m_extraRPSs].m_usedByCurrPic[j]=prevUsed;
prevUsed=newUsed;
prev=newPrev;
}
newRefs++;
} //if(newRef)
} //if(offPOC>=0&&m_GOPList[offGOP].m_temporalId<=m_GOPList[curGOP].m_temporalId)
//L0/L1のリストが埋まったらループ終了
if(newRefs>=numPrefRefs)
{
break;
}
} //for(Int offset = -1; offset>-checkGOP; offset--)
m_GOPList[m_iGOPSize+m_extraRPSs].m_numRefPics=newRefs;
m_GOPList[m_iGOPSize+m_extraRPSs].m_POC = curPOC;
if (m_extraRPSs == 0)
{
m_GOPList[m_iGOPSize+m_extraRPSs].m_interRPSPrediction = 0;
m_GOPList[m_iGOPSize+m_extraRPSs].m_numRefIdc = 0;
}
else
{
//符号化順で1つ前のGOPエントリのインデックス
Int rIdx = m_iGOPSize + m_extraRPSs - 1;
//符号化順で1つ前のGOPエントリのPOC
Int refPOC = m_GOPList[rIdx].m_POC;
//符号化順で1つ前のGOPエントリの参照ピクチャ数
Int refPics = m_GOPList[rIdx].m_numRefPics;
Int newIdc=0;
//1つ前のGOPエントリの参照ピクチャを全てサーチ
for(Int i = 0; i<= refPics; i++)
{
//absPOCrefは1つ前のGOPエントリの参照ピクチャのPOCを示す
Int deltaPOC = ((i != refPics)? m_GOPList[rIdx].m_referencePics[i] : 0); // check if the reference abs POC is >= 0
Int absPOCref = refPOC+deltaPOC;
Int refIdc = 0;
//追加するGOPエントリの参照ピクチャを全てサーチ
for (Int j = 0; j < m_GOPList[m_iGOPSize+m_extraRPSs].m_numRefPics; j++)
{
//追加するGOPエントリの参照ピクチャが, 1つ前のGOPエントリの参照ピクチャと同じ場合
//absPOCref-curPOCは現ピクチャから見た1つ前のGOPエントリの参照ピクチャの相対的な位置を示す
if ( (absPOCref - curPOC) == m_GOPList[m_iGOPSize+m_extraRPSs].m_referencePics[j])
{
//当該参照ピクチャが現フレームに参照されるか判定
if (m_GOPList[m_iGOPSize+m_extraRPSs].m_usedByCurrPic[j])
{
//現フレームで参照されるピクチャである場合はrefIdc=1
refIdc = 1;
}
else
{
//現フレームで参照されるピクチャでない(将来のフレームでのみ参照される)場合はrefIdc=2
refIdc = 2;
}
}
}
m_GOPList[m_iGOPSize+m_extraRPSs].m_refIdc[newIdc]=refIdc;
newIdc++;
} //for(Int i = 0; i<= refPics; i++)
m_GOPList[m_iGOPSize+m_extraRPSs].m_interRPSPrediction = 1;
m_GOPList[m_iGOPSize+m_extraRPSs].m_numRefIdc = newIdc;
m_GOPList[m_iGOPSize+m_extraRPSs].m_deltaRPS = refPOC - m_GOPList[m_iGOPSize+m_extraRPSs].m_POC;
} //if (m_extraRPSs == 0) else
curGOP=m_iGOPSize+m_extraRPSs;
m_extraRPSs++;
} //if(!beforeI&&!errorGOP) else
//最終的に決定された参照ピクチャをrefListに格納
numRefs=0;
for(Int i = 0; i< m_GOPList[curGOP].m_numRefPics; i++)
{
Int absPOC = curPOC+m_GOPList[curGOP].m_referencePics[i];
if(absPOC >= 0)
{
refList[numRefs]=absPOC;
numRefs++;
}
}
//refListにcurPOCも追加
refList[numRefs]=curPOC;
numRefs++;
} //if(m_GOPList[curGOP].m_POC<0)
checkGOP++;
} //while(!verifiedGOP&&!errorGOP)
...
}
xCheckParameter()により、m_GOPListは以下のような状態となります。配列の要素8~10の3つのエントリが追加され(m_extraRPSs = 3)、m_deltaRPSやm_refIdc等の内部変数の値が生成されます。
配列要素No | m_POC | m_referencePics | m_numRefPics | m_usedByCurrPic | m_interRPSPrediction | m_deltaRPS | m_refIdc | m_numRefIdc |
---|---|---|---|---|---|---|---|---|
0 | 8 | -8 ( 0) -12 ( -4) -16 ( -8) | 3 | 1 0 1 | 0 | 0 | 0 | |
1 | 4 | -4 ( 0) -8 ( -4) 4 ( 8) | 3 | 1 1 1 | 1 | 4 | 1 1 0 1 | 4 |
2 | 2 | -2 ( 0) -6 ( -4) 2 ( 4) 6 ( 8) | 4 | 1 1 1 1 | 1 | 2 | 1 1 1 1 | 4 |
3 | 1 | -1 ( 0) 1 ( 2) 3 ( 4) 7 ( 8) | 4 | 1 1 1 1 | 1 | 1 | 1 0 1 1 1 | 5 |
4 | 3 | -1 ( 2) -3 ( 0) 1 ( 4) 5 ( 8) | 4 | 1 1 1 1 | 1 | -2 | 1 1 1 1 0 | 5 |
5 | 6 | -2 ( 4) -6 ( 0) 2 ( 8) | 3 | 1 1 1 | 1 | -3 | 0 1 1 1 0 | 5 |
6 | 5 | -1 ( 4) -5 ( 0) 1 ( 6) 3 ( 8) | 4 | 1 1 1 1 | 1 | 1 | 1 1 1 1 | 4 |
7 | 7 | -1 ( 6) -3 ( 4) -7 ( 0) 1 ( 8) | 4 | 1 1 1 1 | 1 | -2 | 1 1 1 1 0 | 5 |
8 | 8 | -8 ( 0) | 1 | 1 | 0 | 0 | 0 | |
9 | 4 | -4 ( 0) 4 ( 8) | 2 | 1 1 | 1 | 4 | 1 1 | 2 |
10 | 2 | -2 ( 0) 2 ( 4) 6 ( 8) | 3 | 1 1 1 | 1 | 2 | 1 1 1 | 3 |