H.266/VVC-VTM代码学习27-VTM中编码器主函数逻辑
H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习26-VTM中RDcost的计算与λ的设定(二)
下一篇:持续创作中…
目录
- H.266/VVC专栏传送
- 前言
- 一、简介
- 二、代码详解
- 三、流程分析
前言
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_OPTstd::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_TRACINGfprintf(stdout, "[ENABLE_TRACING] ");
#endif
#if ENABLE_SPLIT_PARALLELISMfprintf(stdout, "[SPLIT_PARALLEL (%d jobs)]", PARL_SPLIT_MAX_NUM_JOBS);
#endif
#if ENABLE_SPLIT_PARALLELISMconst 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, "]");
#endiffprintf(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 layerpcEncApp[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 valueif (argc <= i + 2){THROW("Command line parsing error: missing parameter after -lx\n");}numParams++;}// check if correct layer indexif (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_VALUESprintMacroSettings();
#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// eosbool eos = false;// 大循环直到编码完序列的所有帧才结束while (!eos){// read GOP// 编码前的预处理,主要作用有从输入视频中读取一帧,给帧分配缓存(存在EncLib的属性m_cListPic中),设置对应的CS// keepLoop用于记录当前GOP是否已经编码完,若编完则true,否则false,这里初始化为truebool keepLoop = true;// 小循环遍历当前GOP内的所有帧进行预处理while (keepLoop){for (auto &encApp: pcEncApp){#ifndef _DEBUGtry{#endif// 对当前GOP内的帧进行预处理的入口函数,keeploop是预处理函数的输出,若已处理完当前GOP所有帧则为true,否则为falsekeepLoop = 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 _DEBUGtry{#endif// 编码当前帧的入口函数,与预处理的函数一样,eeploop是输出,若已处理完当前GOP所有帧则为true,否则为falsekeepLoop = 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 timeclock_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_HDRMETRICSauto 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();
#elseauto encTime = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
#endif// 对encApp进行销毁for (auto &encApp: pcEncApp){encApp->destroyLib();// destroy application encoder class per layerencApp->destroy();delete encApp;}// 释放rom.cpp中定义的一些全局变量// destroy ROMdestroyROM();pcEncApp.clear();// 输出编码结束时间printf("\n finished @ %s", std::ctime(&endTime2));#if JVET_O0756_CALCULATE_HDRMETRICSprintf(" 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);
#elseprintf(" Total Time: %12.3f sec. [user] %12.3f sec. [elapsed]\n", (endClock - startClock) * 1.0 / CLOCKS_PER_SEC,encTime / 1000.0);
#endifreturn 0;
}
三、流程分析
从代码详解可以总结出,VTM编码器主函数的逻辑主要包括以下几个部分:
- 打印编码器信息
- 初始化rom.cpp中的全局变量
- 初始化各个layer参数,并通过调用函数读取传入主函数的参数(包括配置文件名等)
- 记录编码开始时间
- 遍历各帧进行预编码encodePrep(即一些参数的设定、codingStruct的创建等)
- 遍历各帧进行编码encode
- 记录编码结束时间和编码总时间
- 销毁内存占用
上一篇:H.266/VVC-VTM代码学习26-VTM中RDcost的计算与λ的设定(二)
下一篇:持续创作中…
H.266/VVC-VTM代码学习27-VTM中编码器主函数逻辑相关推荐
- H.266/VVC相关技术学习笔记4:HEVC和VVC中块划分的差别
关于H.265/HEVC和H.266/VVC中的块划分的区别: 一.HEVC中首先需要将一个图像固定划分为多个CTU. ① CTU的尺寸固定划分为64×64,一个CTU由一个亮度CTB和两个色度CTB ...
- H.266/VVC相关技术学习笔记21:帧间预测中五种Merge模式的熵编码方式
今天主要详细讲一下帧间预测中五种Merge模式的熵编码方式,以及对应的VTM的代码中的编码方式的实现.现阶段VTM6.0中Merge模式大致上分为五种,分别是Subblock_Merge.MMVD_M ...
- H.266/VVC相关技术学习笔记20:帧间预测技术中的MMVD技术(Merge mode with MVD)
今天介绍一下帧间预测技术中的MMVD技术(Merge mode with MVD),也称带有运动矢量差的融合技术,MMVD也属于基于Merge的技术中的一种,在解码端的语法元素中也属于Merge分支. ...
- H.266/VVC相关技术学习笔记18:帧间预测中的AMVR技术(自适应运动适量精度)
AMVR技术也称为自适应运动适量精度技术,就是在以前的HEVC中,MVD的精度只有一个默认的1/4像素精度,但是由于要适应不同分辨率的图像,仅仅使用一个精度去表示MVD是远远不够的,因此在VTM6.0 ...
- H.266/VVC相关技术学习笔记16:VTM6.0中的CIIP技术(帧内帧间联合预测)
今天讲一下目前VTM6.0版本中的CIIP技术,CIIP即为帧内帧间联合预测技术,这属于Merge系列的一个分支. 该技术需要先计算当前预测块的帧内预测值,即用Planar.DC.角度预测等传统的帧内 ...
- H.266/VVC SCC技术学习:帧内块拷贝(Intra block copy, IBC)
帧内块拷贝 (Intra block copy, IBC) 是 HEVC 针对屏幕内容编码(Screen content coding)序列的扩展工具,它显着提高了屏幕内容序列的编码效率. IBC 是 ...
- 【二十一】 H.266/VVC | 仿射运动估计AMVP候选列表的构建 | fillAffineMvpCand函数
/* 函数的作用: 构建Affine AMVP list候选列表 函数的参数说明: 1.PredictionUnit &pu 当前编码的PU 2.const RefPicList &e ...
- NENU - 字符串处理课后作业(问题A~问题H) 解析+参考代码(含有C++中的string函数库)
目录 问题 A: 4201 凯撒密码 题目描述 解析 参考代码 问题 B: 4202 子串 题目描述 解析 参考代码 问题 C: 4203 IBM减一 题目描述 解析 参考代码 问题 D: 4204 ...
- 腾讯开源国内首个H.266/VVC视频播放器
本文转载自腾讯多媒体实验室. 当今时代,人们对于多媒体的使用需求越来越大,尤其今年受疫情影响,人们更加依赖视频这一媒体形式办公.学习.娱乐和生活,全球互联网对带宽的需求也激增,导致画面质量不得不降低, ...
最新文章
- oracle 表查看依赖,oracle – 表依赖的递归查询不会像我想的那样递归
- cacti 安装过程笔记
- String类中的intern()方法详解
- 【C语言】控制台窗口图形界面编程(一)句柄和文本属性
- 送分题,ArrayList 的扩容机制了解吗?
- java共享租车信息管理系统jsp源码
- 使用遗传算法解决N皇后问题
- Atitit 语言的异常机制 目录 1. 异常处理,英文名为exceptional handling, 是代替日渐衰落的error code方法的新法,	1 2. 三种模式	1 2.1. 终止模式
- (转)Oracle数据库资料收藏
- 根据一学期的学习,谈谈你对软件工程学科的认识。
- 光波叠加matlab,光波的叠加教程.ppt
- 未检测到正确安装的网络适配器_网络适配器图标出现黄色感叹号的解决办法
- html5 图片上传进度条,html5异步上传图片显示上传文件进度条
- [Learn Android Studio 汉化教程]Reminders实验(一)
- POI Excel插入行,下面的行动态移动
- 极米投影、坚果投影、当贝投影,三大品牌对比来了
- 服务监控--zabbix
- 二、T100多角贸易实战
- cmd mvn compile报错_Apex英雄下载超慢还各种报错?我终于把这些问题给解决了
- 资深HR 告诉你到底怎么写一份好的简历