此系列是为了记录自己学习VTM10.0的过程,目前正在看编码端。主要的参考文档有JVET-S2001-vH和JVET-S2002-v1。由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步。
上一篇博文提到(VTM10.0代码学习13)帧内模式是分亮度和色度处理的,这篇博文就先讲处理亮度的函数estIntraPredLumaQT()。这个函数会先用化简版的RDcost对帧内的亮度模式进行一个筛选,然后对筛选出来的模式执行完整的RDcost计算,来得到最佳的帧内亮度预测模式。
1. 预备变量
CodingStructure &cs = *cu.cs;
const SPS &sps = *cs.sps;
const uint32_t uiWidthBit = floorLog2(partitioner.currArea().lwidth() );
const uint32_t uiHeightBit = floorLog2(partitioner.currArea().lheight());
// Lambda calculation at equivalent Qp of 4 is recommended because at that Qp, the quantization divisor is 1.
const double sqrtLambdaForFirstPass = m_pcRdCost->getMotionLambda( ) * FRAC_BITS_SCALE;
uiWidthBit和uiHeightBit:分别表示宽度和高度是2的几次方
sqrtLambdaForFirstPass:化简版RDcost的lambda
const TempCtx ctxStart ( m_CtxCache, m_CABACEstimator->getCtx() );
const TempCtx ctxStartMipFlag ( m_CtxCache, SubCtx( Ctx::MipFlag, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStartIspMode ( m_CtxCache, SubCtx( Ctx::ISPMode, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStartPlanarFlag ( m_CtxCache, SubCtx( Ctx::IntraLumaPlanarFlag, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStartIntraMode(m_CtxCache, SubCtx(Ctx::IntraLumaMpmFlag, m_CABACEstimator->getCtx()));
const TempCtx ctxStartMrlIdx ( m_CtxCache, SubCtx( Ctx::MultiRefLineIdx, m_CABACEstimator->getCtx() ) );
CABAC相关的变量
bool LFNSTLoadFlag = sps.getUseLFNST() && cu.lfnstIdx != 0;
bool LFNSTSaveFlag = sps.getUseLFNST() && cu.lfnstIdx == 0;
LFNSTSaveFlag &= sps.getUseIntraMTS() ? cu.mtsFlag == 0 : true;
const uint32_t lfnstIdx = cu.lfnstIdx;
double costInterCU = findInterCUCost( cu );
const int width = partitioner.currArea().lwidth();
const int height = partitioner.currArea().lheight();
uint8_t mtsUsageFlag = 0;
const int maxSizeEMT = MTS_INTRA_MAX_CU_SIZE;
if( width <= maxSizeEMT && height <= maxSizeEMT && sps.getUseIntraMTS() )
{
mtsUsageFlag = ( sps.getUseLFNST() && cu.mtsFlag == 1 ) ? 2 : 1;
}
if( width * height < 64 && !m_pcEncCfg->getUseFastLFNST() )
{
mtsUsageFlag = 0;
}
LFNSTLoadFlag:为True表示需要加载LFNST相关变量
LFNSTSaveFlag:为True表示需要保存LFNST相关变量
costInterCU:当前CU的帧间模式的RDcost,这个不确定
mtsUsageFlag:为0表示当前CU不允许开启MTS,为1表示当前CU允许开启MTS且正在测试变换为DCT2的情况,为2表示当前CU允许开启MTS且正在测试变换为非DCT2的情况
double bestCurrentCost = bestCostSoFar;//亮度分量目前最佳RDcost
bool ispCanBeUsed = sps.getUseISP() && cu.mtsFlag == 0 && cu.lfnstIdx == 0 && CU::canUseISP(width, height, cu.cs->sps->getMaxTbSize());//当前CU是否可以开启ISP
bool saveDataForISP = ispCanBeUsed;
bool testISP = ispCanBeUsed;
if ( saveDataForISP )
{
//reset the intra modes lists variables
m_ispCandListHor.clear();
m_ispCandListVer.clear();
}
if( testISP )
{
//reset the variables used for the tests
m_regIntraRDListWithCosts.clear();
int numTotalPartsHor = (int)width >> floorLog2(CU::getISPSplitDim(width, height, TU_1D_VERT_SPLIT));//ISP划分后垂直切几块
int numTotalPartsVer = (int)height >> floorLog2(CU::getISPSplitDim(width, height, TU_1D_HORZ_SPLIT));//ISP划分后水平切几块
m_ispTestedModes[0].init( numTotalPartsHor, numTotalPartsVer );
//the total number of subpartitions is modified to take into account the cases where LFNST cannot be combined with ISP due to size restrictions
//初始化开启LFNST下ISP的情况
numTotalPartsHor = sps.getUseLFNST() && CU::canUseLfnstWithISP(cu.Y(), HOR_INTRA_SUBPARTITIONS) ? numTotalPartsHor : 0;
numTotalPartsVer = sps.getUseLFNST() && CU::canUseLfnstWithISP(cu.Y(), VER_INTRA_SUBPARTITIONS) ? numTotalPartsVer : 0;
for (int j = 1; j < NUM_LFNST_NUM_PER_SET; j++)
{
m_ispTestedModes[j].init(numTotalPartsHor, numTotalPartsVer);
}
}
bestCurrentCost:亮度分量目前最佳RDcost
ispCanBeUsed:当前CU是否可以开启ISP
后面两个if都是用来初始化ISP相关变量
static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> uiHadModeList;
static_vector<double, FAST_UDI_MAX_RDMODE_NUM> CandCostList;
static_vector<double, FAST_UDI_MAX_RDMODE_NUM> CandHadList;
auto &pu = *cu.firstPU;
bool validReturn = false;
CandHadList.clear();
CandCostList.clear();
uiHadModeList.clear();
uiHadModeList:存储着以SAD/SATD为指标的模式候选列表的模式信息,按指标从小往大排
CandCostList:存储着以化简版RDcost为指标的模式候选列表的RDcost信息,按指标从小往大排
CandHadList:存储着以SAD/SATD为指标的模式候选列表的SAD/SATD信息,按指标从小往大排
validReturn:为True表示成功选出亮度最佳模式
这里解释一下SATD,SAD大家都知道是什么意思,SATD就是在预测值和原始值都进行hadamard变换之后再进行SAD计算。为什么要这么做呢,因为化简版的RDcost的R只包括表示预测模式所花费的比特数,那表示残差的比特数光靠SAD是体现不出来的。大多数CU都要进行变换把残差转换到频域,SAD是时域的准则。所以SAD小不一定会代表残差经过VVC的编码方式出来的比特数会小,这时就需要SATD了。
int numModesAvailable = NUM_LUMA_MODE; // total number of Intra modes
const bool fastMip = sps.getUseMIP() && m_pcEncCfg->getUseFastMIP();//是否使用MIP的快速算法
const bool mipAllowed = sps.getUseMIP() && isLuma(partitioner.chType) && ((cu.lfnstIdx == 0) || allowLfnstWithMip(cu.firstPU->lumaSize()));//是否允许开启MIP
const bool testMip = mipAllowed && !(cu.lwidth() > (8 * cu.lheight()) || cu.lheight() > (8 * cu.lwidth()));//是否测试MIP
const bool supportedMipBlkSize = pu.lwidth() <= MIP_MAX_WIDTH && pu.lheight() <= MIP_MAX_HEIGHT;//当前块大小是否支持开启MIP
static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> uiRdModeList;//存储着以化简版RDcost为指标的模式候选列表的模式信息,按指标从小往大排
int numModesForFullRD = 3;//以化简版RDcost为指标的模式候选列表的长度
numModesForFullRD = g_aucIntraModeNumFast_UseMPM_2D[uiWidthBit - MIN_CU_LOG2][uiHeightBit - MIN_CU_LOG2];
numModesAvailable:亮度预测的大部分模式,即DC+Planar+65种角度模式
fastMip:是否使用MIP的快速算法,默认是不开启的
testMip:是否测试MIP
supportedMipBlkSize:当前块大小是否支持开启MIP
uiRdModeList:存储着以化简版RDcost为指标的模式候选列表的模式信息,按指标从小往大排
numModesForFullRD:以化简版RDcost为指标的模式候选列表的长度,这个是会变动的
2. 用化简版RDcost筛选出模式候选列表
if (mtsUsageFlag != 2)
{
}
else // mtsUsage = 2 (here we potentially reduce the number of modes that will be full-RD checked)
{
if ((m_pcEncCfg->getUseFastLFNST() || !cu.slice->isIntra()) && m_bestModeCostValid[lfnstIdx])
{
numModesForFullRD = 0;
double thresholdSkipMode = 1.0 + ((cu.lfnstIdx > 0) ? 0.1 : 1.0) * (1.4 / sqrt((double) (width * height)));
// Skip checking the modes with much larger R-D cost than the best mode
for (int i = 0; i < m_savedNumRdModes[lfnstIdx]; i++)
{
if (m_modeCostStore[lfnstIdx][i] <= thresholdSkipMode * m_be