此系列是為了記錄自己學習VTM10.0的程序和鍛煉表達能力,主要是從解碼端進行入手,由于本人水平有限,出現的錯誤懇請大家指正,歡迎與大家一起交流進步,
1.初始化
int poc;//picture order count
PicList* pcListPic = NULL;//存有圖片的線性表
ifstream bitstreamFile(m_bitstreamFileName.c_str(), ifstream::in | ifstream::binary);//c_str()將string型別轉換為c語言的字串,in代表輸入,binary代表為二進制模式,創建一個檔案輸入位元流,
InputByteStream bytestream(bitstreamFile);//將位元流轉為位元組流
// 創建解碼器類
xCreateDecLib();
//舍棄RAP的前置影像中為RASL,更新the last displayed POC?
m_iPOCLastDisplay += m_iSkipFrame; // set the last displayed POC correctly for skip forward.
bool loopFiltered[MAX_VPS_LAYERS] = { false };//標記是否已進行環路濾波
bool bPicSkipped = false;//表示是否跳過解碼影像
bool isEosPresentInPu = false;//表示前一個NALU所在的PU是否是Eos
poc:幀的播放順序
pcListPic:存放著解碼出來的幀
bitstreamFile和bytestream:解碼端的輸入碼流,一個是以位元為單位,另一個是以位元組為單位
xCreateDecLib():函式包含著解碼器類的創建和初始化,存在ROM上變數的初始化,量化和變換相關的初始化
m_iPOCLastDisplay += m_iSkipFrame :不確定
loopFiltered:標記是否已經環路濾波
bPicSkipped:是否跳過解碼上一個NALU所在的影像
isEosPresentInPu:判斷前一個NALU是否是EOS
2.回圈進行NALU解碼
while (!!bitstreamFile)
{
//創建NALU類
InputNALUnit nalu;
nalu.m_nalUnitType = NAL_UNIT_INVALID;
bool bNewPicture = m_cDecLib.isNewPicture(&bitstreamFile, &bytestream);//將要解碼的NALU是否是影像中的第一個NALU
bool bNewAccessUnit = bNewPicture && m_cDecLib.isNewAccessUnit( bNewPicture, &bitstreamFile, &bytestream );//將要解碼的NALU是否是新的一幀中的第一個NALU,同時也是新的AU中的第一個NALU
if(!bNewPicture)
{//分支1
}
if ((bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS) && !m_cDecLib.getFirstSliceInSequence(nalu.m_nuhLayerId) && !bPicSkipped)
{//分支2
//滿足不是跳過解碼的影像,同時滿足不是sequence中的第一個slice,同時滿足以下至少一個條件:1)將要解碼的NALU是影像中的第一個NALU;2)位元流檔案eof?;3)上一個NALU的型別是EOS
}
else if ( (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS ) &&
m_cDecLib.getFirstSliceInSequence(nalu.m_nuhLayerId))//在下一個NALU所在的slice將是sequence中的第一個slice的情況下,同時滿足以下至少一個條件:1)將要解碼的NALU是影像中的第一個NALU;
{ //2)位元流檔案eof?;3)上一個NALU的型別是EOS,則下一個NALU所在的slice也是picture中的第一個slice,
m_cDecLib.setFirstSliceInPicture (true);
}
if( pcListPic )
{//分支3
}
if( bNewPicture )
{
}
if (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS)
{
}
if (bNewAccessUnit || !bitstreamFile)
{
}
if(bNewAccessUnit)
{
}
}
進入回圈只要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有關,由于能力有限就不展開了
2.1 if(!bNewPicture)
只要解碼的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的開始
2.1.1 分支3
if (bPicSkipped)
{
if ((nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_TRAIL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_STSA) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_RASL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_RADL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_GDR))
{//滿足前一個NALU所在的影像是被跳過解碼的,且當前NALU的nal_unit_type屬于VCL(除去保留的)
//分支1
if (m_cDecLib.isSliceNaluFirstInAU(true, nalu))//圖片中的第一個VCL型別的NALU是否是AU中的第一個VCL型別的NALU
{
//清除一些AU相關的快取資訊
m_cDecLib.resetAccessUnitNals();
m_cDecLib.resetAccessUnitApsNals();
m_cDecLib.resetAccessUnitPicInfo();
}
bPicSkipped = false;
}
}
m_cDecLib.decode(nalu, m_iSkipFrame, m_iPOCLastDisplay, m_targetOlsIdx);//呼叫解碼器類進行解碼NALU
if (nalu.m_nalUnitType == NAL_UNIT_VPS)//如果NALU型別是VPS,則提取一些資訊
{//分支2
m_cDecLib.deriveTargetOutputLayerSet( m_targetOlsIdx );
m_targetDecLayerIdSet = m_cDecLib.getVPS()->m_targetLayerIdSet;//更新需要解碼圖片的nuh_layer_id集
m_targetOutputLayerIdSet = m_cDecLib.getVPS()->m_targetOutputLayerIdSet;//更新需要輸出圖片的nuh_layer_id集
}
本小節的分支1:前一個NALU所在的影像是被跳過解碼的,當前要解碼NALU所在的影像不是被跳過解碼的,當前NALU的型別又恰巧是VCL(除去保留的),又很恰巧這是AU中第一個VCL型別的NALU,那么就要呼叫解碼器類進行以下三步操作
-
resetAccessUnitNals()
-
resetAccessUnitApsNals()
-
resetAccessUnitPicInfo()
都是跟AU相關的,沒有跟進去看,具體啥作用也不知道,同時也要把bPicSkipped設定為false,
m_cDecLib.decode():呼叫解碼器類進行解碼的函式,需要另開篇幅仔細描述的,
本小節分支2:如果解碼過的NALU型別是VPS(video parameter set),還需要提取一些資訊,
2.2 if((bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS)…)
if (!loopFiltered[nalu.m_nuhLayerId] || bitstreamFile)
{//滿足以下至少一個條件:1)eof且還未進行環路濾波?;2)將要解碼的NALU是影像中的第一個NALU;3)上一個NALU的型別是EOS
m_cDecLib.executeLoopFilters();//呼叫解碼器類進行環路濾波
m_cDecLib.finishPicture(poc, pcListPic, INFO, m_newCLVS[nalu.m_nuhLayerId]);//一張影像解碼完后的一些操作?
}
loopFiltered[nalu.m_nuhLayerId] = (nalu.m_nalUnitType == NAL_UNIT_EOS);//如果NALU的型別為EOS,則將loopFiltered設定為true
if (nalu.m_nalUnitType == NAL_UNIT_EOS)
{
m_cDecLib.setFirstSliceInSequence(true, nalu.m_nuhLayerId);//如果NALU的型別為EOS,下一個NALU所在的slice將是sequence中的第一個slice
}
//影像解碼完成后有關于IRAP和GDR的操作
m_cDecLib.updateAssociatedIRAP();
m_cDecLib.updatePrevGDRInSameLayer();
m_cDecLib.updatePrevIRAPAndGDRSubpic();
只要不是eof并且已經濾波那么執行以下操作
- m_cDecLib.executeLoopFilters():呼叫解碼器類進行環路濾波
- m_cDecLib.finishPicture():結束編碼一幀并放入pcListPic中
如果上一個NALU的型別是EOS,那還需要將loopFiltered設定為true,并標記解碼程序處于CLVS中的第一個slice
之后還有一些與IRAP和GDR相關的操作,沒有跟進去看,具體啥作用也不知道,
2.3 if( pcListPic )
if( !m_reconFileName.empty() && !m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].isOpen() )//存在m_reconFileName,且m_cVideoIOYuvReconFile不能使用
{//分支1
// 使用pcListPic中的第一張圖的BitDepths作為m_outputBitDepth
const BitDepths &bitDepths=pcListPic->front()->cs->sps->getBitDepths();
for( uint32_t channelType = 0; channelType < MAX_NUM_CHANNEL_TYPE; channelType++ )
{
if( m_outputBitDepth[channelType] == 0 )
{
m_outputBitDepth[channelType] = bitDepths.recon[channelType];
}
}
std::string reconFileName = m_reconFileName;
if( ( m_cDecLib.getVPS() != nullptr && ( m_cDecLib.getVPS()->getMaxLayers() == 1 || xIsNaluWithinTargetOutputLayerIdSet( &nalu ) ) ) || m_cDecLib.getVPS() == nullptr )
{//要么不存在VPS,要么當VPS存在的時候滿足以下條件之一:1)最大允許層等于1;2)上一個NALU的nuh_layer_id在m_targetOutputLayerIdSet中
m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].open( reconFileName, true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon ); // 將檔案流設定為write mode
}
}
// write reconstruction to file
if( bNewPicture )//如果要解碼的NALU是影像中的第一個NALU,將重構影像寫入檔案
{
xWriteOutput( pcListPic, nalu.m_temporalId );
}
if (nalu.m_nalUnitType == NAL_UNIT_EOS)//如果上一個NALU型別是EOS,將重構影像寫入檔案,將m_bFirstSliceInPicture設定為false
{
xWriteOutput( pcListPic, nalu.m_temporalId );
m_cDecLib.setFirstSliceInPicture (false);
}
// write reconstruction to file -- for additional bumping as defined in C.5.2.3
if (!bNewPicture && ((nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_TRAIL && nalu.m_nalUnitType <= NAL_UNIT_RESERVED_IRAP_VCL_12)
|| (nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_IDR_W_RADL && nalu.m_nalUnitType <= NAL_UNIT_CODED_SLICE_GDR)))
{
xWriteOutput( pcListPic, nalu.m_temporalId );
}
本節分支1:如果存在輸出檔案名,且輸出檔案流未打開,則取pcListPic中的第一張圖的BitDepths作為以后輸出的位元位數,然后打開相應的輸出檔案流
之后三個分支都與將重構影像寫入檔案有關,分別是當:
-
如果要解碼的NALU是影像中的第一個NALU
-
上一個NALU型別是EOS
-
是C.5.2.3定義的情況
第二種情況還要標記解碼程序未進入一幀中的第一個slice
3. 收尾
xFlushOutput( pcListPic );//結束解碼,清空pcListPic
// get the number of checksum errors
uint32_t nRet = m_cDecLib.getNumberOfChecksumErrorsDetected();
// delete buffers
m_cDecLib.deletePicBuffer();
// destroy internal classes
xDestroyDecLib();
destroyROM();//清除存放在ROM的變數
xFlushOutput():清空之前的快取幀
m_cDecLib.getNumberOfChecksumErrorsDetected():統計checksum errors的數量,并將其回傳
m_cDecLib.deletePicBuffer():清除解碼器類的picture buffer
xDestroyDecLib():摧毀解碼器類
destroyROM():清除存放在ROM的變數
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/166951.html
標籤:其他
上一篇:RTMP推流組件EasyRTMP-iOS版本真機運行報Unable to install “EasyRTMP“錯誤問題解決
下一篇:Java再次入門之排序
