HEVC-HM16.9源码学习(1)TEncCu::xCompressCU
通过划分结构关系,一个Slice划分成的SS可以包含若干个CTU。因此,在一个slice中,枚举每个CTU进入编码过程:
函数入口: TEncSlice::compressSlice的m_pcCuEncoder->compressCtu( pCtu );
调用 xCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 );
从CTU开始以四叉树的结构划分CU,递归的每次尝试各种模式和划分方法,记录一个最佳的方案并保存参数。
xCompressCU做的工作:
- 对当前CU计算最好代价;
- 对当前CU的子块继续递归调用xCompressCU;
源码以及注释:
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth DEBUG_STRING_FN_DECLARE(sDebug_), PartSize eParentPartSize )
{
TComPic* pcPic = rpcBestCU->getPic(); // 获取当前CU的图像
const TComPPS &pps=*(rpcTempCU->getSlice()->getPPS()); // 获取图像参数集
const TComSPS &sps=*(rpcTempCU->getSlice()->getSPS()); // 获取序列参数集
m_ppcOrigYuv[uiDepth]->copyFromPicYuv( pcPic->getPicYuvOrg(), rpcBestCU->getCtuRsAddr(), rpcBestCU->getZorderIdxInCtu() ); // 从图像中获取原始YUV数据
Bool doNotBlockPu = true; // 快速cbf标识(cbf模式见术语表)
Bool earlyDetectionSkipMode = false; //early skip早期跳出标识(early skip模式见术语表)
const UInt uiLPelX = rpcBestCU->getCUPelX(); // 最左端点x坐标
const UInt uiRPelX = uiLPelX + rpcBestCU->getWidth(0) - 1; // 最右端点x坐标
const UInt uiTPelY = rpcBestCU->getCUPelY(); // 最上端点y坐标
const UInt uiBPelY = uiTPelY + rpcBestCU->getHeight(0) - 1; // 最下端点y坐标
const UInt uiWidth = rpcBestCU->getWidth(0); // 当前CU块宽度
Int iBaseQP = xComputeQP( rpcBestCU, uiDepth );
// 传入当前CU和深度,计算对当前CU的QP;如果不是对每个CU自适应的改变QP,则直接用之前slice算出的QP
const UInt numberValidComponents = rpcBestCU->getPic()->getNumberValidComponents();
// 获取成分数量,如果色度格式是CHROMA_400,数量为1,反之为3(最大)
/* 【省略代码】根据当前深度、是否使用码率控制、是否使用TQB(TransquantBypass模式,见术语表)调整QP最大和最小的范围(iMinQP-iMaxQP) */
TComSlice * pcSlice = rpcTempCU->getPic()->getSlice(rpcTempCU->getPic()->getCurrSliceIdx()); // 获取当前所在slice
const Bool bBoundary = !( uiRPelX < sps.getPicWidthInLumaSamples() && uiBPelY < sps.getPicHeightInLumaSamples() ); // 当前CU块的右边界在整个图像的最右边 或者 下边界在整个图像最下边 则为TRUE(即在边界)
if ( !bBoundary ) // 如果不在边界
{
for (Int iQP=iMinQP; iQP<=iMaxQP; iQP++) // 在之前确定的QP范围中枚举QP
{
/* 【省略代码】如果是TransquantBypass模式(这里用bIsLosslessMode布尔型标识)且如果当前枚举到最小QP,将其改为lowestQP */
/* 【省略代码】如果是自适应改变QP,设置相关的对最小编码块大小取Log的值、色度QP偏移量索引*/
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
// 使用CTU四叉树子层的deltaQP初始化预测数据,根据深度设置CU的宽度和高度,对QP赋值
/* {做帧间预测, SKIP和2Nx2N} */
if( rpcBestCU->getSlice()->getSliceType() != I_SLICE )
{
/* {2Nx2N} */
if(m_pcEncCfg->getUseEarlySkipDetection()) // 使用early skip早期跳出模式
{
xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) ); // 尝试用普通模式进行预测
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode ); // rpcBestCU保存性能最优的预测方式下的参数,rpcTempCU是每次用于尝试划分预测的CU,每次做完后重新恢复初始化
}
/* {SKIP} */
xCheckRDCostMerge2Nx2N( rpcBestCU, rpcTempCU DEBUG_STRING_PASS_INTO(sDebug), &earlyDetectionSkipMode ); // 尝试用Merge模式进行预测,传入早期跳出标识,如果模式为skip则修改该布尔值
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
if(!m_pcEncCfg->getUseEarlySkipDetection())
{
/* {2Nx2N, NxN(?讲道理,真没找到这个NxN哪里做了)} */
xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) );
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
if(m_pcEncCfg->getUseCbfFastMode()) // 使用快速cbf模式
{
doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0; // 判断四叉树根节点的CBFlag如果为true,则不需要后续继续划分
}
}
}
}
if(!earlyDetectionSkipMode) // 如果之前没有设置提前跳出,继续尝试所有的划分方式
{
for (Int iQP=iMinQP; iQP<=iMaxQP; iQP++) // 枚举QP
{
/* 【省略代码】如果是TransquantBypass模式同上处理 */
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode ); // CU恢复初始化
/* {帧间预测, NxN, 2NxN, Nx2N} */