HM代码分析–TAppEncoder
一.函数调用关系图
HM中的HEVC视频编码器TAppEncoder的函数调用关系图如下所示。
下面解释一下图中关键标记的含义。
1.函数背景色
函数在图中以方框的形式表现出来。不同的背景色标志了该函数不同的作用:
白色背景的函数:不加区分的普通内部函数。
黄色背景函数:滤波函数(Filter)。用于环路滤波,半像素插值,SSIM/PSNR的计算。
绿色背景的函数:CU编码函数(Encode)。通过对残差的DCT变换、量化等方式对CU进行编码。
紫色背景的函数:熵编码函数(Entropy Coding)。对CU编码后的数据进行CABAC熵编码。
浅蓝色背景函数:码率控制函数(Rate Control)。对码率进行控制的函数。
2.箭头线
箭头线标志了函数的调用关系:
黑色箭头线:不加区别的调用关系。
黄色箭头线:滤波函数(Filter)之间的调用关系。
绿色箭头线:CU编码函数(Encode)之间的调用关系。
紫色箭头线:熵编码函数(Entropy Coding)之间的调用关系。
3.下文记录结构图中的几个关键部分。
1)普通内部函数
普通内部函数指的是TAppEncoder中还没有进行分类的函数。例如:
编码器的main()函数中调用的TAppEncTop类的配置读取函数parseCfg()、编码函数encode()等。
编码器最主要的函数:TEncTop中的encode()、TEncGOP中的compressGOP()、TEncSlice的compressSlice()等。
2)CU编码函数
CU编码函数通过运动估计、DCT变换、量化等步骤对图像数据进行编码。编码的工作都是在TEncCu类中的compressCtu()中完成的。compressCtu()调用了xCompressCU()完成了CU的编码工作。xCompressCU()本身是一个递归调用的函数,其中的xCheckRDCostInter()完成了分析帧间CU编码代价的工作;其中的xCheckRDCostIntra()则完成了分析帧内CU编码代价的工作。
3)熵编码函数
熵编码函数使用CABAC的方式对CU编码后的数据进行熵编码。熵编码的工作都是在TEncCu类中的encodeCtu()中完成的。
4)环路滤波函数
环路滤波函数对重建帧数据进行滤波,去除方块效应和振铃效应。TComLoopFilter类的loopFilterPic()完成了去块效应滤波器的工作; TComSampleAdaptiveOffset类的SAOProcess()完成了去除振铃效应的SAO滤波器的工作。
5)码率控制函数
码率控制模块函数分布在lencod源代码不同的地方,包括rc_init_seq()、rc_init_GOP()、rc_init_frame()、rc_handle_mb()等。
二.TAppEncoder 具体流程
1.从编码端入手,在TAppEncoder中我们可以看到有一个encmain.cpp文件,这个文件即是编码的主函数。
在encmain.cpp中主要做了以下几件事:
其中TAppEncTop继承自TAppEncCfg,在父类TAppEncCfg中,定义了大量成员变量来保存参数,子类TAppEncTop中除了父类的成员外,还包括编码所需要的各种成员变量和成员方法。
encmain.cpp的代码(HM16.6):
<span style="font-size:14px;">int main(int argc, char* argv[])
{
TAppEncTop cTAppEncTop; //定义cTAppEncTop
// print information 打印信息并标准输出HM版本信息
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.create(); //创建encoder类,实际create是一个空函数,不做任何操作。
// parse configuration 解析cfg配置文件
try
{
if(!cTAppEncTop.parseCfg( argc, argv ))//如果cfg文件正确,会调用xCheckParameter函数,在命令行窗口中输出解析提示,并调用xPrintParameter来回显参数
{
cTAppEncTop.destroy(); //如果cfg文件错误,调用destroy函数,实际也是一个空函数,不做任何操作。
#if ENVIRONMENT_VARIABLE_DEBUG_AND_TEST
EnvVar::printEnvVar();
#endif
return 1;
}
}
catch (df::program_options_lite::ParseFailure &e) //异常处理,如果cfg文件错误时回显错误提示
{
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(); //调用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(); //调用destroy函数
return 0;
}</span>
2.在"encode"函数中,主要实现了读取YUV文件的数据、初始化工具对象例如:GOPEncoder、SliceEncoder、CUEncder……。在此函数里,还包括一个encode函数,调用CompressGOP函数来具体执行编码任务。
3.在CompressGOP函数中,完成了以下的功能:
- 1)InitGOP将文件的码流分成若干GOP以便后续程序能够顺利执行。
2)InitEncSlice创建编码的Slice。
3)preCompressSlice和CompressSlice两个函数,前者的作用是选择不同的lamuda进行编码(编码是调用了CompressCU函数,后续介绍),后者是在最好的lamuda下进行编码。
4)循环滤波, DB, ALF, SAO等
5)cabac。
下面跟踪下帧内预测:
4.在xCompressCU函数中(CompressCU函数的主体也是调用xComprssCU函数),先进行帧间预测xCheckRDCostMerge2Nx2N,xCheckRDCostInter等。在做完帧间预测后进行帧内预测,这是调用的函数是xCheckRDCostIntra,在xCompressCU函数的后续部分,还递归调用自身以实现对每个CU的编码。变换编码在encodeCoeff中实现,量化在xCheckIntraPCM完成。
5.xCheckRDCostIntra函数,主要完成帧内预测的任务,对亮度的预测使用estIntraPredQT,对色度使用estIntraPredChromaQT。
6.estIntraPredQT函数,在思想对亮度的处理和色度的处理是一样的,所以只介绍亮度的处理函数。在estIntraPredQT函数中,主要完成了RDCost的选择,在其中predIntraLumaAng函数实现了方向的预测;calcHAD函数计算了SATD;xModeBitsIntra函数计算编码的码率;xUpdateCandList更新了最好的RDCost所使用的模式。