最近在做一些较上层的内容,从老师和师兄师姐的博客里学到了很多,对VTM代码有了进一步的理解。VTM4.0代码整体结构如下,并会后面分别介绍几个主要函数的作用。
大体操作:以CTU为单位,对划分后的内容进行熵编码。
每个slice为一帧。一帧中对所有CTU(128*128)都进行编码(for循环每个CTU,分别调用compressCtu函数),具体来说主要是setlambda、ctrlQP、compressCtu、coding_tree_unit等操作。
H.266/VVC代码学习13:VTM4.0中的CU层操作(compressCtu 、 xCompressCU)
大体操作:完成对CTU的划分,根据RDCOST确定各种预测参数。
初始化CTU和当前上下文指针。调用两次xCompressCU函数,对CU进行两次操作:第一次xCompressCU只对亮度进行预测,第二次xCompressCU只对色度进行预测(如果是B帧或者P帧,则亮度和色度预测一起在第一个xCompressCU函数中完成?)。因此,帧内预测在亮度预测完成后才进行色度预测。
大体操作:进行CU的划分和模式的选择。
测试当前模式是什么,有如下几种可能,并进入对应的操作,通过计算对比可以得到各种具体模式的代价值,更新代价值。
以下即各种模式的入口:
1、帧间模式:xCheckRDCostInterIMV或 xCheckRDCostInter;
2、帧间Affine模式:xCheckRDCostAffineMerge2Nx2N;
3、帧间merge模式:xCheckRDCostMerge2Nx2N;
4、帧间merge的triangle模式:xCheckRDCostMergeTriangle2Nx2N;
5、帧内模式:xCheckRDCostIntra;
6、PCM模式:xCheckIntraPCM;
7、CPR模式:xCheckRDCostCPRMode;
8、CPR的merge模式:xCheckRDCostCPRModeMerge2Nx2N;
9、划分模式:xCheckModeSplit,调用xCheckModeSplit进行划分,会依据 “Depth” 的值及代价选择是否继续划分,这里会递归调用xCompressCU。
其他情况:错误,抛出异常。最后设置状态,记录最优CU信息。
大体操作:coding_tree_unit中初始化CTU,对亮度和色度的qp一起进行coding_tree,递归调用自身进行划分。
结束后coding_unit。
根据前面xCompressCU完成CU各种信息的编码。进入对应的编码操作:
1、如果有变换绕过标志:CABACWriter::cu_transquant_bypass_flag
2、如果有Skip标志:CABACWriter::cu_skip_flag
3、CABACWriter::pred_mode
4、如果有PCM标志:CABACWriter::pcm_flag
5、编码数据:
CABACWriter::cu_pred_data
CABACWriter::cu_residual
CABACWriter::end_of_ctu
int main(int argc, char* argv[])
{
/********************************* 打印信息并标准输出VVC版本信息 **********************************/
// print information
fprintf( stdout, "\n" );
#ifdef SVNREVISION
fprintf( stdout, "VVCSoftware: VTM Encoder Version %s (%s@r%s) ", NEXT_SOFTWARE_VERSION, SVNRELATIVEURL, SVNREVISION /*NV_VERSION*/ );
#else
fprintf( stdout, "VVCSoftware: VTM Encoder Version %s ", NEXT_SOFTWARE_VERSION /*NV_VERSION*/ );
#endif
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( "" ), "" );
df::program_options_lite::SilentReporter err;
df::program_options_lite::scanArgv( opts, argc, ( const TChar** ) 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_WPP_PARALLELISM
fprintf( stdout, "[WPP_PARALLEL]" );
#endif
#if ENABLE_WPP_PARALLELISM || 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" );
EncApp* pcEncApp = new EncApp;
// create application encoder class
pcEncApp->create();
/*********************************** 获取cfg信息 ******************************************/
// parse configuration
try
{
if(!pcEncApp->parseCfg( argc, argv ))//解析cfg
{
pcEncApp->destroy();
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
/************************************** 计时并开始编码 *********************************************/
// 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());
fprintf(stdout, " started @ %s", std::ctime(&startTime2) );
clock_t startClock = clock();
// call encoding function
#ifndef _DEBUG
try
{
#endif
pcEncApp->encode();////////////////////////////////开始视频编码!
#ifndef _DEBUG
}
catch( Exception &e )
{
std::cerr << e.what() << std::endl;
return 1;
}
catch( ... )
{
std::cerr << "Unspecified error occurred" << std::endl;
return 1;
}
#endif
// 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());
auto encTime = std::chrono::duration_cast<std::chrono::milliseconds>( endTime- startTime ).count();
// destroy application encoder class 销毁编码器
pcEncApp->destroy();
delete pcEncApp;
printf( "\n finished @ %s", std::ctime(&endTime2) );
printf(" Total Time: %12.3f sec. [user] %12.3f sec. [elapsed]\n",
(endClock - startClock) * 1.0 / CLOCKS_PER_SEC,
encTime / 1000.0);
return 0;
}
// ====================================================================================================================
// Public member functions
// ====================================================================================================================
/**
- create internal class
- initialize internal variable
- until the end of input YUV file, call encoding function in EncLib class
- delete allocated buffers
- destroy internal class
.
*/
Void EncApp::encode()
{
/**************************************** 打开比特流文件 ****************************************************/
m_bitstream.open(m_bitstreamFileName.c_str(), fstream::binary | fstream::out);//打开比特流文件
if (!m_bitstream)
{
EXIT( "failed to open bitstream file " << m_bitstreamFileName.c_str() << " for writing\n");
}
/**************************************** 初始化各种参数 ****************************************************/
std::list<PelUnitBuf*> recBufList;
// initialize internal class & member variables 初始化内部类和成员变量
xInitLibCfg(); //初始化编码器的参数,即cfg的内容
xCreateLib( recBufList);//创建视频源文件以及编码重建后的二进制视频文件和程序的连接,初始化GOP、Slice、CU的部分对象函数
xInitLib(m_isField); //初始化SPS、PPS,GOP、Slice、CU的部分对象函数,变换和量化类,编码器搜索类函数
printChromaFormat();//打印输入和输出的YUV格式
// main encoder loop
Int iNumEncoded = 0;//记录已编码帧数
Bool bEos = false;//控制编码是否结束
const InputColourSpaceConversion ipCSC = m_inputColourSpaceConvert;
const InputColourSpaceConversion snrCSC = (!m_snrInternalColourSpace) ? m_inputColourSpaceConvert : IPCOLOURSPACE_UNCHANGED;
PelStorage trueOrgPic;
PelStorage orgPic;
const Int sourceHeight = m_isField ? m_iSourceHeightOrg : m_iSourceHeight;
UnitArea unitArea( m_chromaFormatIDC, Area( 0, 0, m_iSourceWidth, sourceHeight ) );
orgPic.create( unitArea );
trueOrgPic.create( unitArea );
/****************************************** 对视频帧进行编码 **********************************************/
while ( !bEos ) // 由bEos控制
{
// read input YUV file 读取yuv文件
m_cVideoIOYuvInputFile.read( orgPic, trueOrgPic, ipCSC, m_aiPad, m_InputChromaFormatIDC, m_bClipInputVideoToRec709Range );
// increase number of received frames 接收到帧数自加
m_iFrameRcvd++;
bEos = (m_isField && (m_iFrameRcvd == (m_framesToBeEncoded >> 1) )) || ( !m_isField && (m_iFrameRcvd == m_framesToBeEncoded) );
/******************************* 文件读取完成刷新编码器中的图像队列 *************************************/
Bool flush = 0;
// if end of file (which is only detected on a read failure) flush the encoder of any queued pictures
if (m_cVideoIOYuvInputFile.isEof())
{
flush = true;
bEos = true;
m_iFrameRcvd--;
m_cEncLib.setFramesToBeEncoded(m_iFrameRcvd);
}
/******************************************* 对一帧进行编码 ***********************************************/
// call encoding function for one frame
if ( m_isField )
{
m_cEncLib.encode( bEos, flush ? 0 : &orgPic, flush ? 0 : &trueOrgPic, snrCSC, recBufList,
iNumEncoded, m_isTopFieldFirst );
}
else
{
m_cEncLib.encode( bEos, flush ? 0 : &orgPic, flush ? 0 : &trueOrgPic, snrCSC, recBufList,
iNumEncoded );
}
/******************************************* 写比特流文件 ***********************************************/
// write bistream to file if necessary
if ( iNumEncoded > 0 )
{
xWriteOutput( iNumEncoded, recBufList
);
}
// temporally skip frames
if( m_temporalSubsampleRatio > 1 )
{
m_cVideoIOYuvInputFile.skipFrames(m_temporalSubsampleRatio-1, m_iSourceWidth - m_aiPad[0], m_iSourceHeight - m_aiPad[1], m_InputChromaFormatIDC);
}
}
m_cEncLib.printSummary(m_isField); //打印总比特率信息
/*************************************** 析构并做总结 *****************************************/
// delete used buffers in encoder class
m_cEncLib.deletePicBuffer();//删除原始YUV缓冲区
for( auto &p : recBufList )
{
delete p;
}
recBufList.clear();
xDestroyLib();
m_bitstream.close();
printRateSummary(); //打印总比特率信息
return;
}
/**
- Application has picture buffer list with size of GOP + 1
- Picture buffer list acts like as ring buffer
- End of the list has the latest picture
.
\param flush cause encoder to encode a partial GOP
\param pcPicYuvOrg original YUV picture
\param pcPicYuvTrueOrg
\param snrCSC
\retval rcListPicYuvRecOut list of reconstruction YUV pictures
\retval accessUnitsOut list of output access units
\retval iNumEncoded number of encoded pictures
*/
Void EncLib::encode( Bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYuvTrueOrg, const InputColourSpaceConversion snrCSC, std::list<PelUnitBuf*>& rcListPicYuvRecOut,
Int& iNumEncoded )
{
//PROF_ACCUM_AND_START_NEW_SET( getProfilerPic(), P_GOP_LEVEL );
/************************************** 获取原始TUV,创建当前图像缓冲区 ***************************/
if (pcPicYuvOrg != NULL)
{
// get original YUV 获取原始TUV
Picture* pcPicCurr = NULL;
#if ER_CHROMA_QP_WCG_PPS
Int ppsID=-1; // Use default PPS ID
if (getWCGChromaQPControl().isEnabled())
{
ppsID=getdQPs()[ m_iPOCLast+1 ];
ppsID+=(getSwitchPOC() != -1 && (m_iPOCLast+1 >= getSwitchPOC())?1:0);
}
xGetNewPicBuffer( rcListPicYuvRecOut,
pcPicCurr, ppsID );//给当前图像分配新的缓冲区
#else
xGetNewPicBuffer( rcListPicYuvRecOut,
pcPicCurr, -1 ); // Uses default PPS ID. However, could be modified, for example, to use a PPS ID as a function of POC (m_iPOCLast+1)
#endif
{
const PPS *pPPS=(ppsID<0) ? m_ppsMap.getFirstPS() : m_ppsMap.getPS(ppsID);
const SPS *pSPS=m_spsMap.getPS(pPPS->getSPSId());
pcPicCurr->M_BUFS( 0, PIC_ORIGINAL ).swap( *pcPicYuvOrg );
pcPicCurr->finalInit( *pSPS, *pPPS );
}
pcPicCurr->poc = m_iPOCLast;
// compute image characteristics 计算图像特征
if ( getUseAdaptiveQP() )//自适应QP
{
AQpPreanalyzer::preanalyze( pcPicCurr );
}
}
if ((m_iNumPicRcvd == 0) || (!flush && (m_iPOCLast != 0) && (m_iNumPicRcvd != m_iGOPSize) && (m_iGOPSize != 0)))
{
iNumEncoded = 0;
return;
}
if ( m_RCEnableRateControl )//若使用,则初始化GOP
{
m_cRateCtrl.initRCGOP( m_iNumPicRcvd );
}
/******************************************* 压缩GOP *************************************************/
// compress GOP
m_cGOPEncoder.compressGOP( m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut,
false, false, snrCSC, m_printFrameMSE );
if ( m_RCEnableRateControl )
{
m_cRateCtrl.destroyRCGOP();
}
iNumEncoded = m_iNumPicRcvd;
m_iNumPicRcvd = 0;
m_uiNumAllPicCoded += iNumEncoded;
}
/**------------------------------------------------
Separate interlaced frame into two fields
-------------------------------------------------**/
Void separateFields(Pel* org, Pel* dstField, UInt stride, UInt width, UInt height, Bool isTop)
{
if (!isTop)
{
org += stride;
}
for (Int y = 0; y < height>>1; y++)
{
for (Int x = 0; x < width; x++)
{
dstField[x] = org[x];
}
dstField += stride;
org += stride*2;
}
}