MENU

【HEVC学習】3. HMのmain関数とTAppEncCfg::parseCfg関数

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デフォルト値を整理すると、以下のような状態になっています。

配列要素Nom_POC
表示順序
m_referencePics
参照ピクチャの相対位置 ※()内の数字はm_POC – 相対位置
m_numRefPics
参照ピクチャ数
m_usedByCurrPic
各々の参照ピクチャが当該フレームに参照される場合は1
08-8 ( 0) -12 ( -4) -16 ( -8)31 0 0
14-4 ( 0) -8 ( -4) 4 ( 8)31 0 1
22-2 ( 0) -6 ( -4) 2 ( 4) 6 ( 8)41 0 1 1
31-1 ( 0) 1 ( 2) 3 ( 4) 7 ( 8)41 1 1 1
43-1 ( 2) -3 ( 0) 1 ( 4) 5 ( 8)41 1 1 1
56-2 ( 4) -6 ( 0) 2 ( 8)31 1 1
65-1 ( 4) -5 ( 0) 1 ( 6) 3 ( 8)41 1 1 1
77-1 ( 6) -3 ( 4) -7 ( 0) 1 ( 8)41 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等の内部変数の値が生成されます。

配列要素Nom_POCm_referencePicsm_numRefPicsm_usedByCurrPicm_interRPSPredictionm_deltaRPSm_refIdcm_numRefIdc
08-8 ( 0) -12 ( -4) -16 ( -8)31 0 1000
14-4 ( 0) -8 ( -4) 4 ( 8)31 1 1141 1 0 14
22-2 ( 0) -6 ( -4) 2 ( 4) 6 ( 8)41 1 1 1121 1 1 14
31-1 ( 0) 1 ( 2) 3 ( 4) 7 ( 8)41 1 1 1111 0 1 1 15
43-1 ( 2) -3 ( 0) 1 ( 4) 5 ( 8)41 1 1 11-21 1 1 1 05
56-2 ( 4) -6 ( 0) 2 ( 8)31 1 11-30 1 1 1 05
65-1 ( 4) -5 ( 0) 1 ( 6) 3 ( 8)41 1 1 1111 1 1 14
77-1 ( 6) -3 ( 4) -7 ( 0) 1 ( 8)41 1 1 11-21 1 1 1 05
88-8 ( 0)11000
94-4 ( 0) 4 ( 8)21 1141 12
102-2 ( 0) 2 ( 4) 6 ( 8)31 1 1121 1 13
  • URLをコピーしました!

この記事を書いた人

映像処理ハードウェアの研究開発をしています。ASIC, FPGA, 機械学習などの話題に興味があります。このブログでは、自分が最近勉強したことなどを中心にマイペースに発信していきます。

目次