此系列是为了记录自己学习VTM10.0的过程和锻炼表达能力,主要是从解码端进行入手。由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步。
poc:帧的播放顺序
pcListPic:存放着解码出来的帧
bitstreamFile和bytestream:解码端的输入码流,一个是以比特为单位,另一个是以字节为单位
xCreateDecLib():函数包含着解码器类的创建和初始化,存在ROM上变量的初始化,量化和变换相关的初始化
m_iPOCLastDisplay += m_iSkipFrame :不确定
loopFiltered:标记是否已经环路滤波
bPicSkipped:是否跳过解码上一个NALU所在的图像
isEosPresentInPu:判断前一个NALU是否是EOS
进入循环只要bitstreamFile有效,就进行NALU解码
这里有两个重要的flag,bNewPicture和bNewAccessUnit
bNewPicture:将要解码的NALU是否是一帧中的第一个NALU
bNewAccessUnit:将要解码的NALU是否是AU中第一个NALU
bNewPicture为false进入第一个分支,具体参考2.1节
本节分支2:满足以下条件之一
要解码的NALU是一帧中的第一个NALU
eof
上一个NALU的类型是EOS
如果同时满足目前的解码过程不处于CLVS中的第一个slice且上一个NALU所处的帧未被跳过解码则进行一些操作,具体参考2.2节
如果同时满足目前的解码过程处于CLVS中的第一个slice则标志着解码过程进入一帧中的第一个slice。
说明:m_cDecLib.setFirstSliceInPicture (true)会使bNewPicture判断为False
本节分支3:存储的帧不为空,则进行一些操作,具体参考2.3节
之后还有四个分支和之前两个flag有关,由于能力有限就不展开了
只要解码的NALU不是一帧中的第一个NALU就可进入此分支
AnnexBStats stats = AnnexBStats();//JVET-S2001中AnnexB有关的信息 // 将字节流的下一个NALU的所有比特流信息存入NALU类中的m_Bitstream的m_fifo,将统计信息存入stats,具体过程可以参考JVET-S2001中的AnnexB byteStreamNALUnit(bytestream, nalu.getBitstream().getFifo(), stats); // 读取NALU头信息,参考JVET-S2001 7.3.1.2 P83 read(nalu); // 判断是否是IDR图像中的第一个slice if(m_cDecLib.getFirstSliceInPicture() &&//是否是图片中的第一个slice,在解码器类初始化时设置为true (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP)) {//分支1 m_newCLVS[nalu.m_nuhLayerId] = true; // m_newCLVS标记是否是一个新的CLVS xFlushOutput(pcListPic, nalu.m_nuhLayerId);//将pcListPic中存有的图片清空,并写入文件 } if (m_cDecLib.getFirstSliceInPicture() && nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA && isEosPresentInPu) {//分支2 // 在EOS后面紧接着的CRA图像是CLVSS m_newCLVS[nalu.m_nuhLayerId] = true; } else if (m_cDecLib.getFirstSliceInPicture() && nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA && !isEosPresentInPu) { // 如果CRA图像前面不是EOS,那CRA图像就不是CLVSS m_newCLVS[nalu.m_nuhLayerId] = false; } // temporal_id应该小于cfg中的m_iMaxTemporalLayer,同时nuh_layer_id应该在cfg的m_targetDecLayerIdSet中 if( ( m_iMaxTemporalLayer < 0 || nalu.m_temporalId <= m_iMaxTemporalLayer ) && xIsNaluWithinTargetDecLayerIdSet( &nalu ) ) {//分支3 } else//不满足条件,跳过解码此图像 { bPicSkipped = true; } if (nalu.m_nalUnitType == NAL_UNIT_EOS) {//分支4 isEosPresentInPu = true;//当NALU的类型为EOS,将isEosPresentInPu设置为true m_newCLVS[nalu.m_nuhLayerId] = true; //The presence of EOS means that the next picture is the beginning of new CLVS }byteStreamNALUnit():主要是将字节流掐头去尾,详细过程参考JVET-S2001中AnnexB一章,这里不再展开
read():读取NALU的头信息,相应格式在JVET-S2001 7.3.1.2 P83
本小节分支1:判断是否进入IDR图像中的第一个slice解码过程中,主要是由解码器类来决定。如果是则意味着进入新的CLVS,并将之前缓存的帧清除
本小节分支2:只有当前一个NALU是EOS(end of sequence)时,当前CRA图像才意味着进入新的CLVS
本小节分支3:是整个函数中最重要的分支,包含调用解码器类解码的过程。但是需要满足NALU的时域层在输出范围内,多图像层也在输出范围内。不满足就跳过解码。具体参考2.1.1节
本小节分支4:当前解码NALU为EOS类型时,就将isEosPresentInPu设置为true。并意味着下一个NALU就是CLVS的开始
本小节的分支1:前一个NALU所在的图像是被跳过解码的,当前要解码NALU所在的图像不是被跳过解码的。当前NALU的类型又恰巧是VCL(除去保留的),又很恰巧这是AU中第一个VCL类型的NALU。那么就要调用解码器类进行以下三步操作
resetAccessUnitNals()
resetAccessUnitApsNals()
resetAccessUnitPicInfo()
都是跟AU相关的,没有跟进去看,具体啥作用也不知道。同时也要把bPicSkipped设置为false。
m_cDecLib.decode():调用解码器类进行解码的函数,需要另开篇幅仔细描述的。
本小节分支2:如果解码过的NALU类型是VPS(video parameter set),还需要提取一些信息。
只要不是eof并且已经滤波那么执行以下操作
m_cDecLib.executeLoopFilters():调用解码器类进行环路滤波m_cDecLib.finishPicture():结束编码一帧并放入pcListPic中如果上一个NALU的类型是EOS,那还需要将loopFiltered设置为true,并标记解码过程处于CLVS中的第一个slice
之后还有一些与IRAP和GDR相关的操作,没有跟进去看,具体啥作用也不知道。
本节分支1:如果存在输出文件名,且输出文件流未打开。则取pcListPic中的第一张图的BitDepths作为以后输出的比特位数。然后打开相应的输出文件流
之后三个分支都与将重构图像写入文件有关,分别是当:
如果要解码的NALU是图像中的第一个NALU
上一个NALU类型是EOS
是C.5.2.3定义的情况
第二种情况还要标记解码过程未进入一帧中的第一个slice
xFlushOutput():清空之前的缓存帧
m_cDecLib.getNumberOfChecksumErrorsDetected():统计checksum errors的数量,并将其返回
m_cDecLib.deletePicBuffer():清除解码器类的picture buffer
xDestroyDecLib():摧毁解码器类
destroyROM():清除存放在ROM的变量
