解码部分的研究不像编码端那样需要精雕细琢,但如果想研究一个内容划分或选择模式等的最终结果是怎样,那么应该从解码端入手。下面让我们来学习解码端的框架及最上层的三个函数。
1 main()
输出时间等信息,调用pcDecApp->decode()
开始解码过程。
int main(int argc, char* argv[])
{
int returnCode = EXIT_SUCCESS;
// print information 打印信息
fprintf( stdout, "\n" );
fprintf( stdout, "VVCSoftware: VTM Decoder Version %s ", VTM_VERSION );
fprintf( stdout, NVM_ONOS );
fprintf( stdout, NVM_COMPILEDBY );
fprintf( stdout, NVM_BITS );
#if ENABLE_SIMD_OPT
std::string SIMD;
df::program_options_lite::Options optsSimd;
optsSimd.addOptions()( "SIMD", SIMD, string( "" ), "" );
df::program_options_lite::SilentReporter err;
df::program_options_lite::scanArgv( optsSimd, argc, ( const char** ) argv, err );
fprintf( stdout, "[SIMD=%s] ", read_x86_extension( SIMD ) );
#endif
#if ENABLE_TRACING
fprintf( stdout, "[ENABLE_TRACING] " );
#endif
fprintf( stdout, "\n" );
DecApp *pcDecApp = new DecApp;//创建解码器
// parse configuration 读取.cfg
if(!pcDecApp->parseCfg( argc, argv ))
{
returnCode = EXIT_FAILURE;
return returnCode;
}
// starting time 记录开始时间
double dResult;
clock_t lBefore = clock();
// call decoding function 调用解码函数
#ifndef _DEBUG
try
{
#endif // !_DEBUG
if( 0 != pcDecApp->decode() )解码函数
{
printf( "\n\n***ERROR*** A decoding mismatch occured: signalled md5sum does not match\n" );
returnCode = EXIT_FAILURE;
}
#ifndef _DEBUG
}
catch( Exception &e )
{
std::cerr << e.what() << std::endl;
returnCode = EXIT_FAILURE;
}
catch( ... )
{
std::cerr << "Unspecified error occurred" << std::endl;
returnCode = EXIT_FAILURE;
}
#endif
// ending time 记录结束时间
dResult = (double)(clock()-lBefore) / CLOCKS_PER_SEC;
printf("\n Total Time: %12.3f sec.\n", dResult);
delete pcDecApp;
return returnCode;
}
2 DecApp::decode()
读取比特流,创建和初始化,进行NAL设计及检查。调用DecLib::decode()
进行一帧的解码,调用DecLib::executeLoopFilters()
对解码出的一帧进行环路滤波,随后结束,进行一系列析构操作。
uint32_t DecApp::decode()
{
int poc;//帧数
PicList* pcListPic = NULL;//解码帧序列
/****** 读取流文件 *****/
ifstream bitstreamFile(m_bitstreamFileName.c_str(), ifstream::in | ifstream::binary);
if (!bitstreamFile)
{
EXIT( "Failed to open bitstream file " << m_bitstreamFileName.c_str() << " for reading" ) ;
}
InputByteStream bytestream(bitstreamFile);
if (!m_outputDecodedSEIMessagesFilename.empty() && m_outputDecodedSEIMessagesFilename!="-")
{
m_seiMessageFileStream.open(m_outputDecodedSEIMessagesFilename.c_str(), std::ios::out);
if (!m_seiMessageFileStream.is_open() || !m_seiMessageFileStream.good())
{
EXIT( "Unable to open file "<< m_outputDecodedSEIMessagesFilename.c_str() << " for writing decoded SEI messages");
}
}
// create & initialize internal classes
/****** 创建解码库和内部类 *****/
xCreateDecLib();
m_iPOCLastDisplay += m_iSkipFrame; // set the last displayed POC correctly for skip forward.
// clear contents of colour-remap-information-SEI output file
if (!m_colourRemapSEIFileName.empty())
{
std::ofstream ofile(m_colourRemapSEIFileName.c_str());
if (!ofile.good() || !ofile.is_open())
{
EXIT( "Unable to open file " << m_colourRemapSEIFileName.c_str() << " for writing colour-remap-information-SEI video");
}
}
// main decoder loop
/****** 主要的解码过程 ******/
bool openedReconFile = false; // reconstruction file not yet opened. (must be performed after SPS is seen)
bool loopFiltered = false;
while (!!bitstreamFile)//只要有流输入,就进行这层大循环
{
/* location serves to work around a design fault in the decoder, whereby
* the process of reading a new slice that is the first slice of a new frame
* requires the DecApp::decode() method to be called again with the same
* nal unit. */
#if RExt__DECODER_DEBUG_STATISTICS
CodingStatistics& stat = CodingStatistics::GetSingletonInstance();
CHECK(m_statMode < STATS__MODE_NONE || m_statMode > STATS__MODE_ALL, "Wrong coding statistics output mode");
stat.m_mode = m_statMode;
CodingStatistics::CodingStatisticsData* backupStats = new CodingStatistics::CodingStatisticsData(CodingStatistics::GetStatistics());
#endif
#if RExt__DECODER_DEBUG_BIT_STATISTICS
streampos location = bitstreamFile.tellg() - streampos(bytestream.GetNumBufferedBytes());
#else
streampos location = bitstreamFile.tellg();
#endif
AnnexBStats stats = AnnexBStats();
InputNALUnit nalu;
byteStreamNALUnit(bytestream, nalu.getBitstream().getFifo(), stats);//进行NAL层(网络适配层)的处理
// call actual decoding function
bool bNewPicture = false;
if (nalu.getBitstream().getFifo().empty())
{
/* this can happen if the following occur:
* - empty input file
* - two back-to-back start_code_prefixes
* - start_code_prefix immediately followed by EOF
*/
msg( ERROR, "Warning: Attempt to decode an empty NAL unit\n");
}
else
{
read(nalu);//读取nalu
if( (m_iMaxTemporalLayer >= 0 && nalu.m_temporalId > m_iMaxTemporalLayer) || !isNaluWithinTargetDecLayerIdSet(&nalu) )
{
bNewPicture = false;//如果nalu不正常,即没有新的图片输入
}
else
{
bNewPicture = m_cDecLib.decode(nalu, m_iSkipFrame, m_iPOCLastDisplay);/新的一帧输入,解码一帧
if (bNewPicture)//解码完成,后续进行一系列析构
{
bitstreamFile.clear();
/* location points to the current nalunit payload[1] due to the
* need for the annexB parser to read three extra bytes.
* [1] except for the first NAL unit in the file
* (but bNewPicture doesn't happen then) */
#if RExt__DECODER_DEBUG_BIT_STATISTICS
bitstreamFile.seekg(location);
bytestream.reset();
CodingStatistics::SetStatistics(*backupStats);
#else
bitstreamFile.seekg(location-streamoff(3));
bytestream.reset();
#endif
}
}
}
/*************** 环路滤波部分 ***************/
if( ( bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS ) &&
!m_cDecLib.getFirstSliceInSequence() )//如果不是第一帧,则应进行环路滤波
{
if (!loopFiltered || bitstreamFile)
{
m_cDecLib.executeLoopFilters();//环路滤波,目前内部有DBF,SAO,ALF
m_cDecLib.finishPicture( poc, pcListPic );//结束一帧
}
loopFiltered = (nalu.m_nalUnitType == NAL_UNIT_EOS);
if (nalu.m_nalUnitType == NAL_UNIT_EOS)
{
m_cDecLib.setFirstSliceInSequence(true);
}
}
else if ( (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS ) &&
m_cDecLib.getFirstSliceInSequence () )//如果是第一帧,将其设置为第一帧
{
m_cDecLib.setFirstSliceInPicture (true);
}
/************* 如果图片序列有内容,则处理输出后进行析构 **************/
if( pcListPic )
{
if ( (!m_reconFileName.empty()) && (!openedReconFile) )
{
const BitDepths &bitDepths=pcListPic->front()->cs->sps->getBitDepths(); // use bit depths of first reconstructed picture.
for( uint32_t channelType = 0; channelType < MAX_NUM_CHANNEL_TYPE; channelType++ )
{
if( m_outputBitDepth[channelType] == 0 )
{
m_outputBitDepth[channelType] = bitDepths.recon[channelType];
}
}
if (m_packedYUVMode && (m_outputBitDepth[CH_L] != 10 && m_outputBitDepth[CH_L] != 12))
{
EXIT ("Invalid output bit-depth for packed YUV output, aborting\n");
}
m_cVideoIOYuvReconFile.open( m_reconFileName, true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon ); // write mode
openedReconFile = true;
}
// write reconstruction to file
if( bNewPicture )
{
xWriteOutput( pcListPic, nalu.m_temporalId );
}
if ( (bNewPicture || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA) && m_cDecLib.getNoOutputPriorPicsFlag() )
{
m_cDecLib.checkNoOutputPriorPics( pcListPic );
m_cDecLib.setNoOutputPriorPicsFlag (false);
}
if ( bNewPicture &&
#if !JVET_M0101_HLS
( 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_BLA_N_LP
|| nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_RADL
|| nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_LP ) )
#else
( nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL
|| nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP) )
#endif
{
xFlushOutput( pcListPic );
}
if (nalu.m_nalUnitType == NAL_UNIT_EOS)
{
xWriteOutput( pcListPic, nalu.m_temporalId );
m_cDecLib.setFirstSliceInPicture (false);
}
// write reconstruction to file -- for additional bumping as defined in C.5.2.3
#if !JVET_M0101_HLS
if(!bNewPicture && nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_TRAIL_N && nalu.m_nalUnitType <= NAL_UNIT_RESERVED_VCL31)
#else
if (!bNewPicture && nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_TRAIL && nalu.m_nalUnitType <= NAL_UNIT_RESERVED_VCL15)
#endif
{
xWriteOutput( pcListPic, nalu.m_temporalId );
}
}
#if RExt__DECODER_DEBUG_STATISTICS
delete backupStats;
#endif
}
xFlushOutput( pcListPic );
// get the number of checksum errors
uint32_t nRet = m_cDecLib.getNumberOfChecksumErrorsDetected();
// delete buffers
m_cDecLib.deletePicBuffer();
// destroy internal classes
xDestroyDecLib();
#if RExt__DECODER_DEBUG_STATISTICS
CodingStatistics::DestroyInstance();
#endif
destroyROM();
return nRet;
}
3 DecLib::decode()
根据不同的NAL类型进行不同类型的解码,可以看出是对nalu.m_nalUnitType的各种case。对于其中一些case会调用xDecodeSlice()
解码一帧图像,这些是我们后续用得到的,其余均返回false。
bool DecLib::decode(InputNALUnit& nalu, int& iSkipFrame, int& iPOCLastDisplay)
{
bool ret;
// ignore all NAL units of layers > 0
if (nalu.m_nuhLayerId > 0)
{
msg( WARNING, "Warning: found NAL unit with nuh_layer_id equal to %d. Ignoring.\n", nalu.m_nuhLayerId);
return false;
}
switch (nalu.m_nalUnitType)
{
#if HEVC_VPS
case NAL_UNIT_VPS:
xDecodeVPS( nalu );
return false;
#endif
case NAL_UNIT_SPS:
xDecodeSPS( nalu );
return false;
case NAL_UNIT_PPS:
xDecodePPS( nalu );
return false;
case NAL_UNIT_APS:
xDecodeAPS(nalu);
return false;
case NAL_UNIT_PREFIX_SEI:
// Buffer up prefix SEI messages until SPS of associated VCL is known.
m_prefixSEINALUs.push_back(new InputNALUnit(nalu));
return false;
case NAL_UNIT_SUFFIX_SEI:
if (m_pcPic)
{
m_seiReader.parseSEImessage( &(nalu.getBitstream()), m_pcPic->SEIs, nalu.m_nalUnitType, m_parameterSetManager.getActiveSPS(), m_pDecodedSEIOutputStream );
}
else
{
msg( NOTICE, "Note: received suffix SEI but no picture currently active.\n");
}
return false;
#if !JVET_M0101_HLS
case NAL_UNIT_CODED_SLICE_TRAIL_R:
case NAL_UNIT_CODED_SLICE_TRAIL_N:
case NAL_UNIT_CODED_SLICE_TSA_R:
case NAL_UNIT_CODED_SLICE_TSA_N:
case NAL_UNIT_CODED_SLICE_STSA_R:
case NAL_UNIT_CODED_SLICE_STSA_N:
case NAL_UNIT_CODED_SLICE_BLA_W_LP:
case NAL_UNIT_CODED_SLICE_BLA_W_RADL:
case NAL_UNIT_CODED_SLICE_BLA_N_LP:
case NAL_UNIT_CODED_SLICE_IDR_W_RADL:
case NAL_UNIT_CODED_SLICE_IDR_N_LP:
case NAL_UNIT_CODED_SLICE_CRA:
case NAL_UNIT_CODED_SLICE_RADL_N:
case NAL_UNIT_CODED_SLICE_RADL_R:
case NAL_UNIT_CODED_SLICE_RASL_N:
case NAL_UNIT_CODED_SLICE_RASL_R:
#else
case NAL_UNIT_CODED_SLICE_TRAIL:
case NAL_UNIT_CODED_SLICE_STSA:
case NAL_UNIT_CODED_SLICE_IDR_W_RADL:
case NAL_UNIT_CODED_SLICE_IDR_N_LP:
case NAL_UNIT_CODED_SLICE_CRA:
case NAL_UNIT_CODED_SLICE_RADL:
case NAL_UNIT_CODED_SLICE_RASL:
#endif
ret = xDecodeSlice(nalu, iSkipFrame, iPOCLastDisplay);
#if JVET_J0090_MEMORY_BANDWITH_MEASURE
if ( ret )
{
m_cacheModel.reportFrame( );
m_cacheModel.accumulateFrame( );
m_cacheModel.clear( );
}
#endif
return ret;
case NAL_UNIT_EOS:
m_associatedIRAPType = NAL_UNIT_INVALID;
m_pocCRA = 0;
m_pocRandomAccess = MAX_INT;
m_prevPOC = MAX_INT;
m_prevSliceSkipped = false;
m_skippedPOC = 0;
return false;
case NAL_UNIT_ACCESS_UNIT_DELIMITER:
{
AUDReader audReader;
uint32_t picType;
audReader.parseAccessUnitDelimiter(&(nalu.getBitstream()),picType);
msg( NOTICE, "Note: found NAL_UNIT_ACCESS_UNIT_DELIMITER\n");
return false;
}
case NAL_UNIT_EOB:
return false;
case NAL_UNIT_FILLER_DATA:
{
FDReader fdReader;
uint32_t size;
fdReader.parseFillerData(&(nalu.getBitstream()),size);
msg( NOTICE, "Note: found NAL_UNIT_FILLER_DATA with %u bytes payload.\n", size);
return false;
}
#if !JVET_M0101_HLS
case NAL_UNIT_RESERVED_VCL_N10:
case NAL_UNIT_RESERVED_VCL_R11:
case NAL_UNIT_RESERVED_VCL_N12:
case NAL_UNIT_RESERVED_VCL_R13:
case NAL_UNIT_RESERVED_VCL_N14:
case NAL_UNIT_RESERVED_VCL_R15:
case NAL_UNIT_RESERVED_IRAP_VCL22:
case NAL_UNIT_RESERVED_IRAP_VCL23:
case NAL_UNIT_RESERVED_VCL24:
case NAL_UNIT_RESERVED_VCL25:
case NAL_UNIT_RESERVED_VCL26:
case NAL_UNIT_RESERVED_VCL27:
case NAL_UNIT_RESERVED_VCL28:
case NAL_UNIT_RESERVED_VCL29:
case NAL_UNIT_RESERVED_VCL30:
case NAL_UNIT_RESERVED_VCL31:
#if !HEVC_VPS
case NAL_UNIT_RESERVED_32:
#endif
#else
case NAL_UNIT_RESERVED_VCL_4:
case NAL_UNIT_RESERVED_VCL_5:
case NAL_UNIT_RESERVED_VCL_6:
case NAL_UNIT_RESERVED_VCL_7:
case NAL_UNIT_RESERVED_IRAP_VCL11:
case NAL_UNIT_RESERVED_IRAP_VCL12:
case NAL_UNIT_RESERVED_IRAP_VCL13:
case NAL_UNIT_RESERVED_VCL14:
#if !HEVC_VPS
case NAL_UNIT_RESERVED_VCL15:
#endif
#endif
msg( NOTICE, "Note: found reserved VCL NAL unit.\n");
xParsePrefixSEIsForUnknownVCLNal();
return false;
#if !JVET_M0101_HLS
case NAL_UNIT_RESERVED_NVCL41:
case NAL_UNIT_RESERVED_NVCL42:
case NAL_UNIT_RESERVED_NVCL43:
case NAL_UNIT_RESERVED_NVCL44:
case NAL_UNIT_RESERVED_NVCL45:
case NAL_UNIT_RESERVED_NVCL46:
case NAL_UNIT_RESERVED_NVCL47:
#else
case NAL_UNIT_RESERVED_NVCL16:
case NAL_UNIT_RESERVED_NVCL26:
case NAL_UNIT_RESERVED_NVCL27:
#endif
msg( NOTICE, "Note: found reserved NAL unit.\n");
return false;
#if !JVET_M0101_HLS
case NAL_UNIT_UNSPECIFIED_48:
case NAL_UNIT_UNSPECIFIED_49:
case NAL_UNIT_UNSPECIFIED_50:
case NAL_UNIT_UNSPECIFIED_51:
case NAL_UNIT_UNSPECIFIED_52:
case NAL_UNIT_UNSPECIFIED_53:
case NAL_UNIT_UNSPECIFIED_54:
case NAL_UNIT_UNSPECIFIED_55:
case NAL_UNIT_UNSPECIFIED_56:
case NAL_UNIT_UNSPECIFIED_57:
case NAL_UNIT_UNSPECIFIED_58:
case NAL_UNIT_UNSPECIFIED_59:
case NAL_UNIT_UNSPECIFIED_60:
case NAL_UNIT_UNSPECIFIED_61:
case NAL_UNIT_UNSPECIFIED_62:
case NAL_UNIT_UNSPECIFIED_63:
#else
case NAL_UNIT_UNSPECIFIED_28:
case NAL_UNIT_UNSPECIFIED_29:
case NAL_UNIT_UNSPECIFIED_30:
case NAL_UNIT_UNSPECIFIED_31:
#endif
msg( NOTICE, "Note: found unspecified NAL unit.\n");
return false;
default:
THROW( "Invalid NAL unit type" );
break;
}
return false;
}
4 和编码框架的联系
编码端和解码端内容是基本对应的,名字也是基本对应的,过程也基本是互逆的。
1.编码端比解码端多了一个compressGOP()函数,可见编码会进行GOP中一组帧的操作,而解码不会,解码只对每一帧操作。
2.编码端compressSlice()下多了一个encodeCtus函数,它先调用compressCtu进行编码过程,再调用coding_tree_unit输出比特流。而解码端decompressSlice()函数先调用coding_tree_unit完成比特流的解析,再调用decompressCtu()完成解码过程。