x265探索与研究(九):compressFrame()函数
compressFrame()函数是一个功能繁杂且分析难度较大的函数,主要包括时间戳的初始化工作、access unit的设计、加权预测技术、运动参考帧的估计、当前Slice的QP值确定、熵编码相关信息配置、并行计算与否及其空间的申请、SEI相关配置、线程控制、CTU分析、Multi-pass Encoding、滤波与去噪处理等等,其中最重要的就是调用了encodeSlice()函数。
compressFrame()函数中调用的主要函数如下图所示:
下面给出compressFrame()函数的代码分析:
/*=============================================================*/
/*
====== Analysed by: RuiDong Fang
====== Csdn Blog: http://blog.csdn.net/frd2009041510
====== Date: 2016.04.18
====== Funtion: compressFrame()函数,编码一帧。
*/
/*=============================================================*/
void FrameEncoder::compressFrame()
{
///1、初始化一些变量
ProfileScopeEvent(frameThread); //帧线程的档次范围
m_startCompressTime = x265_mdate(); //编码的开始时间戳(timestamp时间戳 when frame encoder is given a frame)
m_totalActiveWorkerCount = 0; //统计m_activeWorkerCount的和,即经过CTU压缩后统计,进入帧前初始化为0(sum of m_activeWorkerCount sampled at end of each CTU)
m_activeWorkerCountSamples = 0; //当前帧已经编码分析完毕的CTU个数(count of times m_activeWorkerCount was sampled (think vbv restarts))
m_totalWorkerElapsedTime = 0; //初始化所有CTU编码滤波占用的时间(total elapsed time spent by worker threads processing CTUs)
m_totalNoWorkerTime = 0; //初始化当前帧编码占用的时间(total elapsed time without any active worker threads)
m_countRowBlocks = 0; //正在运行的CTU行因为上一行没有完成完毕而强制退出的个数,在帧编码前初始化为0(count of workers forced to abandon a row because of top dependency)
m_allRowsAvailableTime = 0; //初始化当前帧所有CTU行准备好的时间点(timestamp when all reference dependencies are resolved)
m_stallStartTime = 0; //初始化正在进行编码的rows个数为0时的时间点(timestamp when worker count becomes 0)
m_completionCount = 0;
m_bAllRowsStop = false; //是否将所有CTU的编码停止,在每帧进入前初始化为false,在CTU编码决策中需要重新编码时将置为true
m_vbvResetTriggerRow = -1; //需要重新编码的CTU行号,每帧开始编码前初始化为-1
m_SSDY = m_SSDU = m_SSDV = 0;
m_ssim = 0;
m_ssimCnt = 0;
memset(&(m_frame->m_encData->m_frameStats), 0, sizeof(m_frame->m_encData->m_frameStats)); //将当前帧的统计信息初始化为0
///2、存取单元access unit
/* Emit access unit delimiter unless this is the first frame and the user is
* not repeating headers (since AUD is supposed to be the first NAL in the access
* unit) */
//当是第一帧时,需要给出一个定界符
Slice* slice = m_frame->m_encData->m_slice; //获取当前slice
if (m_param->bEnableAccessUnitDelimiters && (m_frame->m_poc || m_param->bRepeatHeaders))
{
m_bs.resetBits();
m_entropyCoder.setBitstream(&m_bs);
m_entropyCoder.codeAUD(*slice);
m_bs.writeByteAlignment();
m_nalList.serialize(NAL_UNIT_ACCESS_UNIT_DELIMITER, m_bs);
}
if (m_frame->m_lowres.bKeyframe && m_param->bRepeatHeaders)
m_top->getStreamHeaders(m_nalList, m_entropyCoder, m_bs); //====================get stream headers
///3、加权预测
// Weighted Prediction parameters estimation.
bool bUseWeightP = slice->m_sliceType == P_SLICE && slice->m_pps->bUseWeightPred; //P帧加权预测标志位
bool bUseWeightB = slice->m_sliceType == B_SLICE && slice->m_pps->bUseWeightedBiPred; //B帧加权预测标志位
//加权预测的参数估计
if (bUseWeightP || bUseWeightB) //使能加权预测
{
#if DETAILED_CU_STATS
m_cuStats.countWeightAnalyze++;
ScopedElapsedTime time(m_cuStats.weightAnalyzeTime);
#endif
WeightAnalysis wa(*this); //用于多线程加权分析
if (m_pool && wa.tryBondPeers(*this, 1)) //从当前job中拥有核并且sleep状态的核可以触发多线程,如果没有可用核则在当前线程中完成进入else
/* use an idle worker for weight analysis */
wa.waitForExit(); //一直等待到任务全部完成,这里等待的是核释放,内核释放了任务也就完成了
else
weightAnalyse(*slice, *m_frame, *m_param); //====================加权预测分析(每个list的第一帧分析加权与否,其它不加权)
}
else
slice->disableWeights(); //不