H.266/VVC-VTM代码学习27-VTM中编码器主函数逻辑

Posted liaojq2020

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了H.266/VVC-VTM代码学习27-VTM中编码器主函数逻辑相关的知识,希望对你有一定的参考价值。

H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习26-VTM中RDcost的计算与λ的设定(二)
下一篇:持续创作中…

目录

前言

VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。

本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。

VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)

一、简介

VTM编码器主函数即EncoderApp的encmain.cpp文件中main函数,该函数通过调用下层函数实现编码参数的读取和配置、逐帧进行编码、以及编码后的数据处理及信息输出的功能。

作为编码端最上层的实现,了解编码器主函数逻辑可以很好地帮助我们对编码端流程有宏观上的掌握。同时,大量在VTM上的研究工作需要对编码端进行修改,对编码器主函数流程的熟悉可以帮助我们更好地在有修改VTM代码需求时进行定位。

二、代码详解

int main(int argc, char *argv[])

  // print information
  // 打印编码信息(编码器版本平台等)
  fprintf(stdout, "\\n");
  fprintf(stdout, "VVCSoftware: VTM Encoder Version %s ", VTM_VERSION);
  fprintf(stdout, NVM_ONOS);
  fprintf(stdout, NVM_COMPILEDBY);
  fprintf(stdout, NVM_BITS);
#if ENABLE_SIMD_OPT
  std::string SIMD;
  df::program_options_lite::Options opts;
  opts.addOptions()("SIMD", SIMD, string(""), "")("c", df::program_options_lite::parseConfigFile, "");
  df::program_options_lite::SilentReporter err;
  df::program_options_lite::scanArgv(opts, argc, (const char **) argv, err);
  fprintf(stdout, "[SIMD=%s] ", read_x86_extension(SIMD));
#endif
#if ENABLE_TRACING
  fprintf(stdout, "[ENABLE_TRACING] ");
#endif
#if ENABLE_SPLIT_PARALLELISM
  fprintf(stdout, "[SPLIT_PARALLEL (%d jobs)]", PARL_SPLIT_MAX_NUM_JOBS);
#endif
#if ENABLE_SPLIT_PARALLELISM
  const char *waitPolicy = getenv("OMP_WAIT_POLICY");
  const char *maxThLim = getenv("OMP_THREAD_LIMIT");
  fprintf(stdout, waitPolicy ? "[OMP: WAIT_POLICY=%s," : "[OMP: WAIT_POLICY=,", waitPolicy);
  fprintf(stdout, maxThLim ? "THREAD_LIMIT=%s" : "THREAD_LIMIT=", maxThLim);
  fprintf(stdout, "]");
#endif
  fprintf(stdout, "\\n");

  std::fstream bitstream;
  EncLibCommon encLibCommon;

  std::vector<EncApp *> pcEncApp(1);
  bool resized = false;
  int layerIdx = 0;

  // 初始化rom.cpp中的一些全局变量
  initROM();
  TComHash::initBlockSizeToIndex();

  char **layerArgv = new char *[argc];
  
  // 遍历layers创建pcEncApp并初始化各个layer的参数
  do
  
    pcEncApp[layerIdx] = new EncApp(bitstream, &encLibCommon);
    // create application encoder class per layer
    pcEncApp[layerIdx]->create();

    // parse configuration per layer
    // 为当前遍历到的layer初始化配置
    try
    
      int j = 0;
      for (int i = 0; i < argc; i++)
      
        if (argv[i][0] == '-' && argv[i][1] == 'l')
        
          if (argc <= i + 1)
          
            THROW("Command line parsing error: missing parameter after -lx\\n");
          
          int numParams = 1;   // count how many parameters are consumed
          // check for long parameters, which start with "--"
          const std::string param = argv[i + 1];
          if (param.rfind("--", 0) != 0)
          
            // only short parameters have a second parameter for the value
            if (argc <= i + 2)
            
              THROW("Command line parsing error: missing parameter after -lx\\n");
            
            numParams++;
          
          // check if correct layer index
          if (argv[i][2] == std::to_string(layerIdx).c_str()[0])
          
            layerArgv[j] = argv[i + 1];
            if (numParams > 1)
            
              layerArgv[j + 1] = argv[i + 2];
            
            j += numParams;
          
          i += numParams;
        
        else
        
          layerArgv[j] = argv[i];
          j++;
        
      
      
      // 解析输入的参数
      if (!pcEncApp[layerIdx]->parseCfg(j, layerArgv))
      
        pcEncApp[layerIdx]->destroy();
        return 1;
      
    
    catch (df::program_options_lite::ParseFailure &e)
    
      std::cerr << "Error parsing option \\"" << e.arg << "\\" with argument \\"" << e.val << "\\"." << std::endl;
      return 1;
    

    pcEncApp[layerIdx]->createLib(layerIdx);

    if (!resized)
    
      pcEncApp.resize(pcEncApp[layerIdx]->getMaxLayers());
      resized = true;
    

    layerIdx++;
   while (layerIdx < pcEncApp.size());
  // 结束对各个layer的遍历初始化

  delete[] layerArgv;

  // 若上面的遍历过程中发现的layer数量超过一个
  if (layerIdx > 1)
  
    VPS *vps = pcEncApp[0]->getVPS();
    // check chroma format and bit-depth for dependent layers
    // 遍历layer检查chroma format和位深
    for (uint32_t i = 0; i < layerIdx; i++)
    
      int curLayerChromaFormatIdc = pcEncApp[i]->getChromaFormatIDC();
      int curLayerBitDepth = pcEncApp[i]->getBitDepth();
      for (uint32_t j = 0; j < layerIdx; j++)
      
        if (vps->getDirectRefLayerFlag(i, j))
        
          int refLayerChromaFormatIdcInVPS = pcEncApp[j]->getChromaFormatIDC();
          CHECK(curLayerChromaFormatIdc != refLayerChromaFormatIdcInVPS,
                "The chroma formats of the current layer and the reference layer are different");
          int refLayerBitDepthInVPS = pcEncApp[j]->getBitDepth();
          CHECK(curLayerBitDepth != refLayerBitDepthInVPS,
                "The bit-depth of the current layer and the reference layer are different");
        
      
    
  

#if PRINT_MACRO_VALUES
  printMacroSettings();
#endif
  // *********  从此处开始计算编码时间  *********
  // starting time
  // 记录开始编码的时间
  auto startTime = std::chrono::steady_clock::now();
  std::time_t startTime2 = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  // 向log中打印开始编码时间
  fprintf(stdout, " started @ %s", std::ctime(&startTime2));
  clock_t startClock = clock();

  // call encoding function per layer
  // eos
  bool eos = false;

  // 大循环直到编码完序列的所有帧才结束
  while (!eos)
  
    // read GOP
    // 编码前的预处理,主要作用有从输入视频中读取一帧,给帧分配缓存(存在EncLib的属性m_cListPic中),设置对应的CS
    // keepLoop用于记录当前GOP是否已经编码完,若编完则true,否则false,这里初始化为true
    bool keepLoop = true;
    // 小循环遍历当前GOP内的所有帧进行预处理
    while (keepLoop)
    
      for (auto &encApp: pcEncApp)
      
#ifndef _DEBUG
        try
        
#endif
          // 对当前GOP内的帧进行预处理的入口函数,keeploop是预处理函数的输出,若已处理完当前GOP所有帧则为true,否则为false
          keepLoop = encApp->encodePrep(eos);
#ifndef _DEBUG
        
        // 若预处理过程中出错则报错
        catch (Exception &e)
        
          std::cerr << e.what() << std::endl;
          return EXIT_FAILURE;
        
        // 内存出错则报错
        catch (const std::bad_alloc &e)
        
          std::cout << "Memory allocation failed: " << e.what() << std::endl;
          return EXIT_FAILURE;
        
#endif
      
    

    // encode GOP
    // 正式编码开始
    keepLoop = true;
    // 小循环遍历当前GOP内的所有帧进行编码
    while (keepLoop)
    
      for (auto &encApp: pcEncApp)
      
#ifndef _DEBUG
        try
        
#endif
          // 编码当前帧的入口函数,与预处理的函数一样,eeploop是输出,若已处理完当前GOP所有帧则为true,否则为false
          keepLoop = encApp->encode();
#ifndef _DEBUG
        
        // 若编码过程中出错则报错
        catch (Exception &e)
        
          std::cerr << e.what() << std::endl;
          return EXIT_FAILURE;
        
        // 内存出错则报错
        catch (const std::bad_alloc &e)
        
          std::cout << "Memory allocation failed: " << e.what() << std::endl;
          return EXIT_FAILURE;
        
#endif
      
    // 循环遍历编码完当前GOP所有帧
  // 循环遍历编码完当前序列所有GOP
  // 此时记录编码结束的时间
  // ending time
  clock_t endClock = clock();
  auto endTime = std::chrono::steady_clock::now();
  std::time_t endTime2 = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
#if JVET_O0756_CALCULATE_HDRMETRICS
  auto metricTime = pcEncApp[0]->getMetricTime();

  for (int layerIdx = 1; layerIdx < pcEncApp.size(); layerIdx++)
  
    metricTime += pcEncApp[layerIdx]->getMetricTime();
  
  // 计算编码总时间
  auto totalTime = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
  auto encTime = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime - metricTime).count();
  auto metricTimeuser = std::chrono::duration_cast<std::chrono::milliseconds>(metricTime).count();
#else
  auto encTime = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
#endif
  // 对encApp进行销毁
  for (auto &encApp: pcEncApp)
  
    encApp->destroyLib();

    // destroy application encoder class per layer
    encApp->destroy();

    delete encApp;
  
  // 释放rom.cpp中定义的一些全局变量
  // destroy ROM
  destroyROM();

  pcEncApp.clear();

  // 输出编码结束时间
  printf("\\n finished @ %s", std::ctime(&endTime2));

#if JVET_O0756_CALCULATE_HDRMETRICS
  printf(" Encoding Time (Total Time): %12.3f ( %12.3f ) sec. [user] %12.3f ( %12.3f ) sec. [elapsed]\\n",
         ((endClock - startClock) * 1.0 / CLOCKS_PER_SEC) - (metricTimeuser / 1000.0),
         (endClock - startClock) * 1.0 / CLOCKS_PER_SEC, encTime / 1000.0, totalTime / 1000.0);
#else
  printf(" Total Time: %12.3f sec. [user] %12.3f sec. [elapsed]\\n", (endClock - startClock) * 1.0 / CLOCKS_PER_SEC,
         encTime / 1000.0);
#endif

  return 0;

三、流程分析

从代码详解可以总结出,VTM编码器主函数的逻辑主要包括以下几个部分:

  1. 打印编码器信息
  2. 初始化rom.cpp中的全局变量
  3. 初始化各个layer参数,并通过调用函数读取传入主函数的参数(包括配置文件名等)
  4. 记录编码开始时间
  5. 遍历各帧进行预编码encodePrep(即一些参数的设定、codingStruct的创建等)
  6. 遍历各帧进行编码encode
  7. 记录编码结束时间和编码总时间
  8. 销毁内存占用

上一篇:H.266/VVC-VTM代码学习26-VTM中RDcost的计算与λ的设定(二)
下一篇:持续创作中…

VTM10.0帧内之CCLM技术

为了减少分量间的冗余,提出分量间线性模型预测技术,基本思想是假设亮度分量和色度分量间存在某种线性关系,根据此线性关系实现亮度分量到色度分量的预测。
在这里插入图片描述
VVC中CCLM步骤如下:
1.使用特定位置的四个点。
在这里插入图片描述

  1. 对同位亮度块的 上述4个位置 以及 块内的所有 亮度样本进行下采样以获得和色度样本一一对应的亮度样本。以CCLM为例:

在这里插入图片描述
VTM10.0亮度下采样的代码:
用于生成包括参考像素和块内像素的下采样亮度值。

// LumaRecPixels   获得亮度的重建值
void IntraPrediction::xGetLumaRecPixels(const PredictionUnit &pu, CompArea chromaArea)
{
  int iDstStride 

以上是关于H.266/VVC-VTM代码学习27-VTM中编码器主函数逻辑的主要内容,如果未能解决你的问题,请参考以下文章

使用命令行在 Visual Studio 中编译为发行版

spring中编程式事务控制

怎么在windows中编iphone\itouch程序。最好能是win7环境

在 mingw-w64/msys2 中编​​译的应用程序,“应用程序无法正确启动(0xc00007b”

三菱plc中编好的指令如何删除

R语言入门学习笔记 - 对R软件的认识