此系列是为了记录自己学习VTM10.0的过程,目前正在看编码端。主要的参考文档有JVET-S2001-vH和JVET-S2002-v1。由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步。
上一篇博文(VTM10.0代码学习9)提到compressGOP(),这个函数由于涉及到slice的设置,十分地冗长,足足有1700多行。在解码端也有类似的部分,就在本系列的第二篇博文(VTM10.0代码学习2)提到。刚看VTM没多久的我又怎能啃下这块硬骨头,就战略性跳过很多内容了。现在稍微有些底气来说说这块,但我应该写得很烂,大家见谅啊。
1. 初始设置
OutputBitstream *pcBitstreamRedirect;//将码流装入VCL-NALU之前暂存的地方
pcBitstreamRedirect = new OutputBitstream;
Picture* scaledRefPic[MAX_NUM_REF] = {
};
xInitGOP( iPOCLast, iNumPicRcvd, isField, isEncodeLtRef );
pcBitstreamRedirect:将码流装入VCL-NALU之前暂存的地方
scaledRefPic:存储着缩放后的参考帧
xInitGOP:设置m_iGopSize,表示当前GOP的大小,除了第一帧是1的情况外,是恒定的即使当前GOP没有读取到足够的帧数
m_pcCfg->setEncodedFlag( iGOPid, false );
auto beforeTime = std::chrono::steady_clock::now();
setEncodedFlag():将m_RPLList0、m_RPLList1、m_GOPList中的m_isEncoded设置为false,表示当前帧还未编码。m_GOPList里面存着一个GOP内每个帧的一些信息(例如POC、I/B/P帧等等),这些都是直接从cfg文件中得到的。m_RPLList0和m_RPLList1相对侧重于存储每个帧有关参考帧列表1和0的信息。
now():记录一下起始时间
int iTimeOffset;//表示当前编码帧是当前GOP播放顺序的第几帧
int pocCurr;//表示当前编码帧的POC
if(iPOCLast == 0)
{
pocCurr=0;
iTimeOffset = 1;
}
else
{
pocCurr = iPOCLast - iNumPicRcvd + m_pcCfg->getGOPEntry(iGOPid).m_POC;
iTimeOffset = m_pcCfg->getGOPEntry(iGOPid).m_POC;
}
if (pocCurr >= m_pcCfg->getFramesToBeEncoded())
{
continue;
}
iTimeOffset:表示当前编码帧的POC相对于当前GOP播放顺序第一帧的POC的偏移
pocCurr:表示当前编码帧的POC
最后一个if分支:表示如果当前编码帧的POC超出要编码的帧数,跳过之后的步骤(因为我省略了外循环语句,所以看上去有些奇怪)
if( getNalUnitType(pocCurr, m_iLastIDR, isField) == NAL_UNIT_CODED_SLICE_IDR_W_RADL || getNalUnitType(pocCurr, m_iLastIDR, isField) == NAL_UNIT_CODED_SLICE_IDR_N_LP )
{
m_iLastIDR = pocCurr;//更新编码顺序之前最近IDR帧的POC
}
xGetBuffer( rcListPic, rcListPicYuvRecOut,
iNumPicRcvd, iTimeOffset, pcPic, pocCurr, isField );//相当于初始化指针pcPic,设置重建帧指针的指向
picHeader = pcPic->cs->picHeader;
picHeader->setSPSId( pcPic->cs->pps->getSPSId() );
picHeader->setPPSId( pcPic->cs->pps->getPPSId() );
picHeader->setSplitConsOverrideFlag(false);
// initial two flags to be false
picHeader->setPicInterSliceAllowedFlag(false);
picHeader->setPicIntraSliceAllowedFlag(false);
if分支:如果当前帧的NALU类型是IDR则更新m_iLastIDR,表示编码顺序之前最近IDR帧的POC。要想知道如何决定帧的NALU类型就得看函数getNalUnitType(),这里就不再展开
xGetBuffer():相当于初始化指针pcPic,设置重建帧指针的指向
后面进行PictureHeader有关的初始设置
const int picWidth = pcPic->cs->pps->getPicWidthInLumaSamples();//帧的宽度
const int picHeight = pcPic->cs->pps->getPicHeightInLumaSamples();//帧的高度
const int maxCUWidth = pcPic->cs->sps->getMaxCUWidth();//CU最大允许宽度
const int maxCUHeight = pcPic->cs->sps->getMaxCUHeight();//CU最大允许高度
const ChromaFormat chromaFormatIDC = pcPic->cs->sps->getChromaFormatIdc();//色度采样格式
const int maxTotalCUDepth = floorLog2(maxCUWidth) - pcPic->cs->sps->getLog2MinCodingBlockSize();//最大CU深度
pcPic->createTempBuffers( pcPic->cs->pps->pcv->maxCUWidth );//创建预测值和残差值的buffer
pcPic->cs->createCoeffs((bool)pcPic->cs->sps->getPLTMode());//创建和TU有关的buffer
pcPic->clearSliceBuffer();//清空slices的buffer
pcPic->allocateNewSlice();//分配一个新的slice
m_pcSliceEncoder->setSliceSegmentIdx(0);//设置m_uiSliceSegmentIdx,表示当前编码slice的Index
m_pcSliceEncoder->initEncSlice(pcPic, iPOCLast, pocCurr, iGOPid, pcSlice, isField, isEncodeLtRef, m_pcEncLib->getLayerId() );//初始化slice
createTempBuffers():创建预测值和残差值的buffer
createCoeffs():创建和TU有关的buffer
clearSliceBuffer():清空slices的buffer
allocateNewSlice():分配一个新的slice
setSliceSegmentIdx():设置m_uiSliceSegmentIdx,表示当前编码slice的Index
initEncSlice():初始化slice,是需要不停翻看的函数
2.有关slice的设置
pcSlice->setLastIDR(m_iLastIDR);
pcSlice->setIndependentSliceIdx(0);
if(pcSlice->getSliceType()==B_SLICE&&m_pcCfg->getGOPEntry(iGOPid).m_sliceType=='P')
{
pcSlice->setSliceType(P_SLICE);
}
if(pcSlice->getSliceType()==B_SLICE&&m_pcCfg->getGOPEntry(iGOPid).m_sliceType=='I')
{
pcSlice->setSliceType(I_SLICE);
}
pcSlice->setTLayer(m_pcCfg->getGOPEntry(iGOPid).m_temporalId);
pcSlice->setNalUnitType(getNalUnitType(