diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d8b3d739475db9ea920c6a9f90fd74684722892..8a256bce55a8bbdefede7f7cd213cb422765aee1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ add_subdirectory( "source/App/DecoderAnalyserApp" ) add_subdirectory( "source/App/DecoderApp" ) add_subdirectory( "source/App/EncoderApp" ) add_subdirectory( "source/App/SEIRemovalApp" ) +add_subdirectory( "source/App/SEIFilmGrainApp" ) add_subdirectory( "source/App/Parcat" ) add_subdirectory( "source/App/StreamMergeApp" ) add_subdirectory( "source/App/BitstreamExtractorApp" ) diff --git a/cfg/sei_vui/film_grain_characteristics_analysis.cfg b/cfg/sei_vui/film_grain_characteristics_analysis.cfg new file mode 100644 index 0000000000000000000000000000000000000000..1fa5f970965e6b04c0d7f0f2aa3b16bd5e806896 --- /dev/null +++ b/cfg/sei_vui/film_grain_characteristics_analysis.cfg @@ -0,0 +1,12 @@ +#======== Film grain characteristics SEI message ===================== +SEIFGCEnabled : 1 +SEIFGCAnalysisEnabled : 1 +SEIFGCCancelFlag : 0 +SEIFGCPersistenceFlag : 1 +SEIFGCPerPictureSEI : 0 +SEIFGCModelID : 0 +SEIFGCSepColourDescPresentFlag : 0 +SEIFGCBlendingModeID : 0 +SEIFGCCompModelPresentComp0 : 1 +SEIFGCCompModelPresentComp1 : 0 +SEIFGCCompModelPresentComp2 : 0 \ No newline at end of file diff --git a/cfg/sei_vui/film_grain_characterstics.cfg b/cfg/sei_vui/film_grain_characterstics.cfg index d8c9c739fc3c7d9597fad4246fe61c879d25a823..9489f59f10a9c02b32fcd7b6447c3481ca4c95ec 100644 --- a/cfg/sei_vui/film_grain_characterstics.cfg +++ b/cfg/sei_vui/film_grain_characterstics.cfg @@ -1,11 +1,28 @@ #======== Film grain characteristics SEI message ===================== -SEIFGCEnabled : 1 -SEIFGCCancelFlag : 0 -SEIFGCPersistenceFlag : 1 -SEIFGCModelID : 0 # 0: frequency filtering; 1: auto-regression; 2-3 are reserved -SEIFGCSepColourDescPresentFlag : 0 # if not 0, need to specify separate colour description (not implemented in current encoder cmd line) -SEIFGCBlendingModeID : 0 # 0: additive; 1: multipliciative -SEIFGCLog2ScaleFactor : 0 -SEIFGCCompModelPresentComp0 : 0 # if not 0, need to specify model for comp 0 (not implemented in current encoder cmd line) -SEIFGCCompModelPresentComp1 : 0 # if not 0, need to specify model for comp 1 (not implemented in current encoder cmd line) -SEIFGCCompModelPresentComp2 : 0 # if not 0, need to specify model for comp 2 (not implemented in current encoder cmd line) +SEIFGCEnabled : 1 # enable to use FGC SEI message. +SEIFGCAnalysisEnabled : 0 # enable film grain analysis to estimate grain parameters. +SEIFGCCancelFlag : 0 # for SMPTE-RDD5: the value must be 0 +SEIFGCPersistenceFlag : 1 # for SMPTE-RDD5: the value must be 0; When FGC SEI frequency is once per I-period (SEIFGCPerPictureSEI is 0), than SEIFGCPersistenceFlag 1 +SEIFGCPerPictureSEI : 0 # for SMPTE-RDD5: the value must be 1 (0: FGC SEI is inserted once per I-period; 1: FGC SEI is inserted once per picture) +SEIFGCModelID : 0 # for SMPTE-RDD5: the value must be 0 (0: frequency filtering; 1: auto-regression; 2-3 are reserved) +SEIFGCSepColourDescPresentFlag : 0 # for SMPTE-RDD5: the value must be 0 (if not 0, need to specify separate colour description (not implemented in current encoder cmd line)) +SEIFGCBlendingModeID : 0 # for SMPTE-RDD5: the value must be 0 (0: additive; 1: multipliciative) +SEIFGCLog2ScaleFactor : 2 +SEIFGCCompModelPresentComp0 : 1 # if not 0, need to specify model for comp 0; otherwise default parameters will be used. +SEIFGCCompModelPresentComp1 : 1 # if not 0, need to specify model for comp 1; otherwise default parameters will be used. +SEIFGCCompModelPresentComp2 : 1 # if not 0, need to specify model for comp 2; otherwise default parameters will be used. +SEIFGCNumIntensityIntervalMinus1Comp0 : 0 # Number of intensity intervals minus 1 for comp 0 +SEIFGCNumIntensityIntervalMinus1Comp1 : 0 # Number of intensity intervals minus 1 for comp 1 +SEIFGCNumIntensityIntervalMinus1Comp2 : 0 # Number of intensity intervals minus 1 for comp 2 +SEIFGCNumModelValuesMinus1Comp0 : 2 # Number of model values minus 1 for comp 0 +SEIFGCNumModelValuesMinus1Comp1 : 2 # Number of model values minus 1 for comp 1 +SEIFGCNumModelValuesMinus1Comp2 : 2 # Number of model values minus 1 for comp 2 +SEIFGCIntensityIntervalLowerBoundComp0 : 10 # Lower bound of intensity interval for comp 0 (for SMPTE-RDD5, non-overlapping interval) +SEIFGCIntensityIntervalLowerBoundComp1 : 60 # Lower bound of intensity interval for comp 1 (for SMPTE-RDD5, non-overlapping interval) +SEIFGCIntensityIntervalLowerBoundComp2 : 60 # Lower bound of intensity interval for comp 2 (for SMPTE-RDD5, non-overlapping interval) +SEIFGCIntensityIntervalUpperBoundComp0 : 250 # Upper bound of intensity interval for comp 0 (for SMPTE-RDD5, non-overlapping interval) +SEIFGCIntensityIntervalUpperBoundComp1 : 200 # Upper bound of intensity interval for comp 1 (for SMPTE-RDD5, non-overlapping interval) +SEIFGCIntensityIntervalUpperBoundComp2 : 250 # Upper bound of intensity interval for comp 2 (for SMPTE-RDD5, non-overlapping interval) +SEIFGCCompModelValuesComp0 : 16 8 8 # model values for each intensity interval for comp 0 (sigma, h, v (h,v might be inferred based on number of model value)) +SEIFGCCompModelValuesComp1 : 24 12 12 # model values for each intensity interval for comp 1 (sigma, h, v (h,v might be inferred based on number of model value)) +SEIFGCCompModelValuesComp2 : 40 3 3 # model values for each intensity interval for comp 2 (sigma, h, v (h,v might be inferred based on number of model value)) \ No newline at end of file diff --git a/doc/software-manual.tex b/doc/software-manual.tex index ed485f550f152cd5b7ddf99332ad03fbc407b8dd..b5363bbc8dea5496ac8df77fab81dde4b8f8b035 100644 --- a/doc/software-manual.tex +++ b/doc/software-manual.tex @@ -3659,7 +3659,11 @@ SEI messages. \begin{OptionTableNoShorthand}{Film grain characteristics SEI message encoder parameters}{tab:sei-film-grain} \Option{SEIFGCEnabled} & \Default{0} & -Enables or disables the insertion of the film grain characteristics SEI message. +Control generation of the film grain characteristics SEI message. +\\ +\Option{SEIFGCAnalysisEnabled} & +\Default{0} & +Control adaptive film grain parameter estimation - film grain analysis. \\ \Option{SEIFGCCancelFlag} & \Default{0} & @@ -3669,6 +3673,10 @@ Specifies the persistence of any previous film grain characteristics SEI message \Default{1} & Specifies the persistence of the film grain characteristics SEI message for the current layer. \\ +\Option{SEIFGCPerPictureSEI} & +\Default{0} & +Film Grain SEI is added for each picture as speciffied in RDD5 to ensure bit accurate synthesis in tricky mode. +\\ \Option{SEIFGCModelID} & \Default{0} & Specifies the film grain simulation model. @@ -3707,6 +3715,66 @@ Specifies the presence of film grain modelling on colour component 1. \Default{0} & Specifies the presence of film grain modelling on colour component 2. \\ +\Option{SEIFGCNumIntensityIntervalMinus1Comp0} & +\Default{0} & +Specifies the number of intensity intervals minus1 on colour component 0. +\\ +\Option{SEIFGCNumIntensityIntervalMinus1Comp1} & +\Default{0} & +Specifies the number of intensity intervals minus1 on colour component 1. +\\ +\Option{SEIFGCNumIntensityIntervalMinus1Comp2} & +\Default{0} & +Specifies the number of intensity intervals minus1 on colour component 2. +\\ +\Option{SEIFGCNumModelValuesMinus1Comp0} & +\Default{0} & +Specifies the number of component model values minus1 on colour component 0. +\\ +\Option{SEIFGCNumModelValuesMinus1Comp1} & +\Default{0} & +Specifies the number of component model values minus1 on colour component 1. +\\ +\Option{SEIFGCNumModelValuesMinus1Comp2} & +\Default{0} & +Specifies the number of component model values minus1 on colour component 2. +\\ +\Option{SEIFGCIntensityIntervalLowerBoundComp0} & +\Default{0} & +Specifies the lower bound for the intensity intervals on colour component 0. +\\ +\Option{SEIFGCIntensityIntervalLowerBoundComp1} & +\Default{0} & +Specifies the lower bound for the intensity intervals on colour component 1. +\\ +\Option{SEIFGCIntensityIntervalLowerBoundComp2} & +\Default{0} & +Specifies the lower bound for the intensity intervals on colour component 2. +\\ +\Option{SEIFGCIntensityIntervalUpperBoundComp0} & +\Default{0} & +Specifies the upper bound for the intensity intervals on colour component 0. +\\ +\Option{SEIFGCIntensityIntervalUpperBoundComp1} & +\Default{0} & +Specifies the upper bound for the intensity intervals on colour component 1. +\\ +\Option{SEIFGCIntensityIntervalUpperBoundComp2} & +\Default{0} & +Specifies the upper bound for the intensity intervals on colour component 2. +\\ +\Option{SEIFGCCompModelValuesComp0} & +\Default{0} & +Specifies the component model values on colour component 0. +\\ +\Option{SEIFGCCompModelValuesComp1} & +\Default{0} & +Specifies the component model values on colour component 1. +\\ +\Option{SEIFGCCompModelValuesComp2} & +\Default{0} & +Specifies the component model values on colour component 2. +\\ \end{OptionTableNoShorthand} \begin{OptionTableNoShorthand}{Tone mapping information SEI message encoder parameters}{tab:sei-tone-mapping-info} diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp index a74a9d79ed0cbfc8cc34634080c77635d356a1e9..d9867bea98455cc09689789a0aaa5f2dd218974b 100644 --- a/source/App/DecoderApp/DecApp.cpp +++ b/source/App/DecoderApp/DecApp.cpp @@ -48,7 +48,6 @@ #endif #include "CommonLib/dtrace_codingstruct.h" - //! \ingroup DecoderApp //! \{ @@ -410,6 +409,56 @@ uint32_t DecApp::decode() m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].setBitdepthShift(channelType, reconBitdepth - fileBitdepth); } } + +#if JVET_X0048_X0103_FILM_GRAIN + if (!m_SEIFGSFileName.empty() && !m_videoIOYuvSEIFGSFile[nalu.m_nuhLayerId].isOpen()) + { + 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"); + } + + std::string SEIFGSFileName = m_SEIFGSFileName; + if (m_SEIFGSFileName.compare("/dev/null") && m_cDecLib.getVPS() != nullptr && m_cDecLib.getVPS()->getMaxLayers() > 1 && xIsNaluWithinTargetOutputLayerIdSet(&nalu)) + { + size_t pos = SEIFGSFileName.find_last_of('.'); + std::string layerString = std::string(".layer") + std::to_string(nalu.m_nuhLayerId); + if (pos != string::npos) + { + SEIFGSFileName.insert(pos, layerString); + } + else + { + SEIFGSFileName.append(layerString); + } + } + if ((m_cDecLib.getVPS() != nullptr && (m_cDecLib.getVPS()->getMaxLayers() == 1 || xIsNaluWithinTargetOutputLayerIdSet(&nalu))) || m_cDecLib.getVPS() == nullptr) + { + m_videoIOYuvSEIFGSFile[nalu.m_nuhLayerId].open(SEIFGSFileName, true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon); // write mode + } + } + // update file bitdepth shift if recon bitdepth changed between sequences + for (uint32_t channelType = 0; channelType < MAX_NUM_CHANNEL_TYPE; channelType++) + { + int reconBitdepth = pcListPic->front()->cs->sps->getBitDepth((ChannelType) channelType); + int fileBitdepth = m_videoIOYuvSEIFGSFile[nalu.m_nuhLayerId].getFileBitdepth(channelType); + int bitdepthShift = m_videoIOYuvSEIFGSFile[nalu.m_nuhLayerId].getBitdepthShift(channelType); + if (fileBitdepth + bitdepthShift != reconBitdepth) + { + m_videoIOYuvSEIFGSFile[nalu.m_nuhLayerId].setBitdepthShift(channelType, reconBitdepth - fileBitdepth); + } + } +#endif + if (!m_SEICTIFileName.empty() && !m_cVideoIOYuvSEICTIFile[nalu.m_nuhLayerId].isOpen()) { const BitDepths& bitDepths = pcListPic->front()->cs->sps->getBitDepths(); // use bit depths of first reconstructed picture. @@ -606,6 +655,15 @@ void DecApp::xDestroyDecLib() recFile.second.close(); } } +#if JVET_X0048_X0103_FILM_GRAIN + if (!m_SEIFGSFileName.empty()) + { + for (auto &recFile: m_videoIOYuvSEIFGSFile) + { + recFile.second.close(); + } + } +#endif if (!m_SEICTIFileName.empty()) { for (auto& recFile : m_cVideoIOYuvSEICTIFile) @@ -771,6 +829,29 @@ void DecApp::xWriteOutput( PicList* pcListPic, uint32_t tId ) NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range ); } } +#if JVET_X0048_X0103_FILM_GRAIN + // Perform FGS on decoded frame and write to output FGS file + if (!m_SEIFGSFileName.empty()) + { + const Window& conf = pcPic->getConformanceWindow(); + const SPS* sps = pcPic->cs->sps; + ChromaFormat chromaFormatIDC = sps->getChromaFormatIdc(); + if (m_upscaledOutput) + { + m_videoIOYuvSEIFGSFile[pcPic->layerId].writeUpscaledPicture(*sps, *pcPic->cs->pps, pcPic->getDisplayBufFG(), m_outputColourSpaceConvert, m_packedYUVMode, m_upscaledOutput, NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range); + } + else + { + m_videoIOYuvSEIFGSFile[pcPic->layerId].write(pcPic->getRecoBuf().get(COMPONENT_Y).width, pcPic->getRecoBuf().get(COMPONENT_Y).height, pcPic->getDisplayBufFG(), + m_outputColourSpaceConvert, m_packedYUVMode, + conf.getWindowLeftOffset() * SPS::getWinUnitX(chromaFormatIDC), + conf.getWindowRightOffset() * SPS::getWinUnitX(chromaFormatIDC), + conf.getWindowTopOffset() * SPS::getWinUnitY(chromaFormatIDC), + conf.getWindowBottomOffset() * SPS::getWinUnitY(chromaFormatIDC), + NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range); + } + } +#endif // Perform CTI on decoded frame and write to output CTI file if (!m_SEICTIFileName.empty()) { @@ -924,6 +1005,29 @@ void DecApp::xFlushOutput( PicList* pcListPic, const int layerId ) NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range ); } } +#if JVET_X0048_X0103_FILM_GRAIN + // Perform FGS on decoded frame and write to output FGS file + if (!m_SEIFGSFileName.empty()) + { + const Window& conf = pcPic->getConformanceWindow(); + const SPS* sps = pcPic->cs->sps; + ChromaFormat chromaFormatIDC = sps->getChromaFormatIdc(); + if (m_upscaledOutput) + { + m_videoIOYuvSEIFGSFile[pcPic->layerId].writeUpscaledPicture(*sps, *pcPic->cs->pps, pcPic->getDisplayBufFG(), m_outputColourSpaceConvert, m_packedYUVMode, m_upscaledOutput, NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range); + } + else + { + m_videoIOYuvSEIFGSFile[pcPic->layerId].write(pcPic->getRecoBuf().get(COMPONENT_Y).width, pcPic->getRecoBuf().get(COMPONENT_Y).height, pcPic->getDisplayBufFG(), + m_outputColourSpaceConvert, m_packedYUVMode, + conf.getWindowLeftOffset() * SPS::getWinUnitX(chromaFormatIDC), + conf.getWindowRightOffset() * SPS::getWinUnitX(chromaFormatIDC), + conf.getWindowTopOffset() * SPS::getWinUnitY(chromaFormatIDC), + conf.getWindowBottomOffset() * SPS::getWinUnitY(chromaFormatIDC), + NUM_CHROMA_FORMAT, m_bClipOutputVideoToRec709Range); + } + } +#endif // Perform CTI on decoded frame and write to output CTI file if (!m_SEICTIFileName.empty()) { diff --git a/source/App/DecoderApp/DecApp.h b/source/App/DecoderApp/DecApp.h index 33ffaa51912863ce9b30fa3ff00b92e4059cd3f9..fa6fd4d9b01877d3bcf8dc0547fb6d939a046152 100644 --- a/source/App/DecoderApp/DecApp.h +++ b/source/App/DecoderApp/DecApp.h @@ -61,6 +61,9 @@ private: // class interface DecLib m_cDecLib; ///< decoder class std::unordered_map<int, VideoIOYuv> m_cVideoIOYuvReconFile; ///< reconstruction YUV class +#if JVET_X0048_X0103_FILM_GRAIN + std::unordered_map<int, VideoIOYuv> m_videoIOYuvSEIFGSFile; ///< reconstruction YUV with FGS class +#endif std::unordered_map<int, VideoIOYuv> m_cVideoIOYuvSEICTIFile; ///< reconstruction YUV with CTI class // for output control diff --git a/source/App/DecoderApp/DecAppCfg.cpp b/source/App/DecoderApp/DecAppCfg.cpp index 616d65fd10938aa22e5b7812c90f78455f6f01ca..5e702600efa466fde2b97ab38d5bb6d3bb3ce0b3 100644 --- a/source/App/DecoderApp/DecAppCfg.cpp +++ b/source/App/DecoderApp/DecAppCfg.cpp @@ -97,6 +97,9 @@ bool DecAppCfg::parseCfg( int argc, char* argv[] ) ("TarDecLayerIdSetFile,l", cfg_TargetDecLayerIdSetFile, string(""), "targetDecLayerIdSet file name. The file should include white space separated LayerId values to be decoded. Omitting the option or a value of -1 in the file decodes all layers.") ("SEIColourRemappingInfoFilename", m_colourRemapSEIFileName, string(""), "Colour Remapping YUV output file name. If empty, no remapping is applied (ignore SEI message)\n") ("SEICTIFilename", m_SEICTIFileName, string(""), "CTI YUV output file name. If empty, no Colour Transform is applied (ignore SEI message)\n") +#if JVET_X0048_X0103_FILM_GRAIN + ("SEIFGSFilename", m_SEIFGSFileName, string(""), "FGS YUV output file name. If empty, no film grain is applied (ignore SEI message)\n") +#endif ("SEIAnnotatedRegionsInfoFilename", m_annotatedRegionsSEIFileName, string(""), "Annotated regions output file name. If empty, no object information will be saved (ignore SEI message)\n") ("OutputDecodedSEIMessagesFilename", m_outputDecodedSEIMessagesFilename, string(""), "When non empty, output decoded SEI messages to the indicated file. If file is '-', then output to stdout\n") #if JVET_S0257_DUMP_360SEI_MESSAGE @@ -260,6 +263,9 @@ DecAppCfg::DecAppCfg() , m_decodedNoDisplaySEIEnabled(false) , m_colourRemapSEIFileName() , m_SEICTIFileName() +#if JVET_X0048_X0103_FILM_GRAIN +, m_SEIFGSFileName() +#endif , m_annotatedRegionsSEIFileName() , m_targetDecLayerIdSet() , m_outputDecodedSEIMessagesFilename() diff --git a/source/App/DecoderApp/DecAppCfg.h b/source/App/DecoderApp/DecAppCfg.h index c8a735ca5ee195f2dda4db70e3992cb0e458efc0..897b85a17cf168d2bcfe818e5a4401bb633b220a 100644 --- a/source/App/DecoderApp/DecAppCfg.h +++ b/source/App/DecoderApp/DecAppCfg.h @@ -73,6 +73,9 @@ protected: bool m_decodedNoDisplaySEIEnabled; ///< Enable(true)/disable(false) writing only pictures that get displayed based on the no display SEI message std::string m_colourRemapSEIFileName; ///< output Colour Remapping file name std::string m_SEICTIFileName; ///< output Recon with CTI file name +#if JVET_X0048_X0103_FILM_GRAIN + std::string m_SEIFGSFileName; ///< output file name for reconstructed sequence with film grain +#endif std::string m_annotatedRegionsSEIFileName; ///< annotated regions file name std::vector<int> m_targetDecLayerIdSet; ///< set of LayerIds to be included in the sub-bitstream extraction process. std::string m_outputDecodedSEIMessagesFilename; ///< filename to output decoded SEI messages to. If '-', then use stdout. If empty, do not output details. diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp index fd349f1bfe77bb778542fbe569902ca65479ad2c..dc1693493d333f8c581e453bec172f8a7b13d726 100644 --- a/source/App/EncoderApp/EncApp.cpp +++ b/source/App/EncoderApp/EncApp.cpp @@ -1000,8 +1000,25 @@ void EncApp::xInitLibCfg() m_cEncLib.setFilmGrainCharactersticsSEISepColourDescPresent (m_fgcSEISepColourDescPresentFlag); m_cEncLib.setFilmGrainCharactersticsSEIBlendingModeID ((uint8_t)m_fgcSEIBlendingModeID); m_cEncLib.setFilmGrainCharactersticsSEILog2ScaleFactor ((uint8_t)m_fgcSEILog2ScaleFactor); +#if JVET_X0048_X0103_FILM_GRAIN + m_cEncLib.setFilmGrainAnalysisEnabled (m_fgcSEIAnalysisEnabled); + m_cEncLib.setFilmGrainCharactersticsSEIPerPictureSEI (m_fgcSEIPerPictureSEI); +#endif for (int i = 0; i < MAX_NUM_COMPONENT; i++) { m_cEncLib.setFGCSEICompModelPresent (m_fgcSEICompModelPresent[i], i); +#if JVET_X0048_X0103_FILM_GRAIN + if (m_fgcSEICompModelPresent[i]) { + m_cEncLib.setFGCSEINumIntensityIntervalMinus1 ((uint8_t)m_fgcSEINumIntensityIntervalMinus1[i], i); + m_cEncLib.setFGCSEINumModelValuesMinus1 ((uint8_t)m_fgcSEINumModelValuesMinus1[i], i); + for (int j = 0; j <= m_fgcSEINumIntensityIntervalMinus1[i]; j++) { + m_cEncLib.setFGCSEIIntensityIntervalLowerBound ((uint8_t)m_fgcSEIIntensityIntervalLowerBound[i][j], i, j); + m_cEncLib.setFGCSEIIntensityIntervalUpperBound ((uint8_t)m_fgcSEIIntensityIntervalUpperBound[i][j], i, j); + for (int k = 0; k <= m_fgcSEINumModelValuesMinus1[i]; k++) { + m_cEncLib.setFGCSEICompModelValue (m_fgcSEICompModelValue[i][j][k], i, j, k); + } + } + } +#endif } // content light level m_cEncLib.setCLLSEIEnabled (m_cllSEIEnabled); @@ -1326,6 +1343,13 @@ void EncApp::createLib( const int layerIdx ) m_filteredOrgPic = new PelStorage; m_filteredOrgPic->create( unitArea ); } +#if JVET_X0048_X0103_FILM_GRAIN + if ( m_fgcSEIAnalysisEnabled ) + { + m_filteredOrgPicForFG = new PelStorage; + m_filteredOrgPicForFG->create( unitArea ); + } +#endif if( !m_bitstream.is_open() ) { @@ -1355,6 +1379,25 @@ void EncApp::createLib( const int layerIdx ) m_inputColourSpaceConvert, m_iQP, m_gopBasedTemporalFilterStrengths, m_gopBasedTemporalFilterFutureReference ); } +#if JVET_X0048_X0103_FILM_GRAIN + if ( m_fgcSEIAnalysisEnabled ) + { + bool temporalFilterFutureReference = 1; + int filteredFrame = 0; + + if ( m_iIntraPeriod < 1 ) + filteredFrame = 2 * m_iFrameRate; + else + filteredFrame = m_iIntraPeriod; + + map<int, double> filteredFramesAndStrengths = { { filteredFrame, 1.5 } }; // TODO: adjust MCTF and MCTF strenght + + m_temporalFilterForFG.init( m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_sourceWidth, + sourceHeight, m_sourcePadding, m_bClipInputVideoToRec709Range, m_inputFileName, + m_chromaFormatIDC, m_inputColourSpaceConvert, m_iQP, filteredFramesAndStrengths, + temporalFilterFutureReference ); + } + #endif } void EncApp::destroyLib() @@ -1388,6 +1431,14 @@ void EncApp::destroyLib() m_filteredOrgPic->destroy(); delete m_filteredOrgPic; } +#if JVET_X0048_X0103_FILM_GRAIN + if (m_fgcSEIAnalysisEnabled) + { + m_filteredOrgPicForFG->destroy(); + delete m_filteredOrgPicForFG; + m_filteredOrgPicForFG = nullptr; + } +#endif #if EXTENSION_360_VIDEO delete m_ext360; #endif @@ -1415,11 +1466,27 @@ bool EncApp::encodePrep( bool& eos ) m_cVideoIOYuvInputFile.read( *m_orgPic, *m_trueOrgPic, ipCSC, m_sourcePadding, m_InputChromaFormatIDC, m_bClipInputVideoToRec709Range ); #endif - if( m_gopBasedTemporalFilterEnabled ) +#if JVET_X0048_X0103_FILM_GRAIN + if (m_fgcSEIAnalysisEnabled) + { + m_filteredOrgPicForFG->copyFrom(*m_orgPic); + } + if (m_gopBasedTemporalFilterEnabled) + { + m_temporalFilter.filter(m_orgPic, m_iFrameRcvd); + m_filteredOrgPic->copyFrom(*m_orgPic); + } + if (m_fgcSEIAnalysisEnabled) + { + m_temporalFilterForFG.filter(m_filteredOrgPicForFG, m_iFrameRcvd); + } +#else + if (m_gopBasedTemporalFilterEnabled) { - m_temporalFilter.filter( m_orgPic, m_iFrameRcvd ); + m_temporalFilter.filter(m_orgPic, m_iFrameRcvd); m_filteredOrgPic->copyFrom(*m_orgPic); } +#endif // increase number of received frames m_iFrameRcvd++; @@ -1444,7 +1511,11 @@ bool EncApp::encodePrep( bool& eos ) } else { +#if JVET_X0048_X0103_FILM_GRAIN + keepDoing = m_cEncLib.encodePrep( eos, m_flush ? 0 : m_orgPic, m_flush ? 0 : m_trueOrgPic, m_flush ? 0 : m_filteredOrgPic, m_flush ? 0 : m_filteredOrgPicForFG, snrCSC, m_recBufList, m_numEncoded ); +#else keepDoing = m_cEncLib.encodePrep( eos, m_flush ? 0 : m_orgPic, m_flush ? 0 : m_trueOrgPic, m_flush ? 0 : m_filteredOrgPic, snrCSC, m_recBufList, m_numEncoded ); +#endif } return keepDoing; diff --git a/source/App/EncoderApp/EncApp.h b/source/App/EncoderApp/EncApp.h index 93323155cbd315d53df070b81d48de64bbf6ae3d..a4763d3cf0f8d0607af12da08ec08a035970e374 100644 --- a/source/App/EncoderApp/EncApp.h +++ b/source/App/EncoderApp/EncApp.h @@ -102,6 +102,10 @@ private: TExt360AppEncTop* m_ext360; #endif EncTemporalFilter m_temporalFilter; +#if JVET_X0048_X0103_FILM_GRAIN + PelStorage* m_filteredOrgPicForFG; + EncTemporalFilter m_temporalFilterForFG; +#endif bool m_flush; public: diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp index 4eaba3123570eb37eb624b2792fc24a367a72caa..ac0d1ead93d1b286b24dcb0420128a96a3cfcd02 100644 --- a/source/App/EncoderApp/EncAppCfg.cpp +++ b/source/App/EncoderApp/EncAppCfg.cpp @@ -370,6 +370,7 @@ static inline istream& operator >> (istream &in, ScalingListMode &mode) return readStrToEnum(strToScalingListMode, sizeof(strToScalingListMode)/sizeof(*strToScalingListMode), in, mode); } +#if !JVET_X0048_X0103_FILM_GRAIN template <class T> struct SMultiValueInput { @@ -393,6 +394,7 @@ struct SMultiValueInput istream& readValues(std::istream &in); }; +#endif template <class T> static inline istream& operator >> (std::istream &in, SMultiValueInput<T> &values) @@ -759,6 +761,18 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) int warnUnknowParameter = 0; +#if JVET_X0048_X0103_FILM_GRAIN + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalLowerBoundComp0 (0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalLowerBoundComp1 (0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalLowerBoundComp2 (0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalUpperBoundComp0 (0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalUpperBoundComp1 (0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalUpperBoundComp2 (0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEICompModelValueComp0 (0, 65535, 0, 256 * 6); + SMultiValueInput<uint32_t> cfg_FgcSEICompModelValueComp1 (0, 65535, 0, 256 * 6); + SMultiValueInput<uint32_t> cfg_FgcSEICompModelValueComp2 (0, 65535, 0, 256 * 6); +#endif + #if ENABLE_TRACING string sTracingRule; string sTracingFile; @@ -1453,7 +1467,25 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) ("SEIFGCCompModelPresentComp0", m_fgcSEICompModelPresent[0], false, "Specifies the presence of film grain modelling on colour component 0.") ("SEIFGCCompModelPresentComp1", m_fgcSEICompModelPresent[1], false, "Specifies the presence of film grain modelling on colour component 1.") ("SEIFGCCompModelPresentComp2", m_fgcSEICompModelPresent[2], false, "Specifies the presence of film grain modelling on colour component 2.") - +#if JVET_X0048_X0103_FILM_GRAIN + ("SEIFGCAnalysisEnabled", m_fgcSEIAnalysisEnabled, false, "Control adaptive film grain parameter estimation - film grain analysis") + ("SEIFGCPerPictureSEI", m_fgcSEIPerPictureSEI, false, "Film Grain SEI is added for each picture as speciffied in RDD5 to ensure bit accurate synthesis in tricky mode") + ("SEIFGCNumIntensityIntervalMinus1Comp0", m_fgcSEINumIntensityIntervalMinus1[0], 0u, "Specifies the number of intensity intervals minus1 on colour component 0.") + ("SEIFGCNumIntensityIntervalMinus1Comp1", m_fgcSEINumIntensityIntervalMinus1[1], 0u, "Specifies the number of intensity intervals minus1 on colour component 1.") + ("SEIFGCNumIntensityIntervalMinus1Comp2", m_fgcSEINumIntensityIntervalMinus1[2], 0u, "Specifies the number of intensity intervals minus1 on colour component 2.") + ("SEIFGCNumModelValuesMinus1Comp0", m_fgcSEINumModelValuesMinus1[0], 0u, "Specifies the number of component model values minus1 on colour component 0.") + ("SEIFGCNumModelValuesMinus1Comp1", m_fgcSEINumModelValuesMinus1[1], 0u, "Specifies the number of component model values minus1 on colour component 1.") + ("SEIFGCNumModelValuesMinus1Comp2", m_fgcSEINumModelValuesMinus1[2], 0u, "Specifies the number of component model values minus1 on colour component 2.") + ("SEIFGCIntensityIntervalLowerBoundComp0", cfg_FgcSEIIntensityIntervalLowerBoundComp0, cfg_FgcSEIIntensityIntervalLowerBoundComp0, "Specifies the lower bound for the intensity intervals on colour component 0.") + ("SEIFGCIntensityIntervalLowerBoundComp1", cfg_FgcSEIIntensityIntervalLowerBoundComp1, cfg_FgcSEIIntensityIntervalLowerBoundComp1, "Specifies the lower bound for the intensity intervals on colour component 1.") + ("SEIFGCIntensityIntervalLowerBoundComp2", cfg_FgcSEIIntensityIntervalLowerBoundComp2, cfg_FgcSEIIntensityIntervalLowerBoundComp2, "Specifies the lower bound for the intensity intervals on colour component 2.") + ("SEIFGCIntensityIntervalUpperBoundComp0", cfg_FgcSEIIntensityIntervalUpperBoundComp0, cfg_FgcSEIIntensityIntervalUpperBoundComp0, "Specifies the upper bound for the intensity intervals on colour component 0.") + ("SEIFGCIntensityIntervalUpperBoundComp1", cfg_FgcSEIIntensityIntervalUpperBoundComp1, cfg_FgcSEIIntensityIntervalUpperBoundComp1, "Specifies the upper bound for the intensity intervals on colour component 1.") + ("SEIFGCIntensityIntervalUpperBoundComp2", cfg_FgcSEIIntensityIntervalUpperBoundComp2, cfg_FgcSEIIntensityIntervalUpperBoundComp2, "Specifies the upper bound for the intensity intervals on colour component 2.") + ("SEIFGCCompModelValuesComp0", cfg_FgcSEICompModelValueComp0, cfg_FgcSEICompModelValueComp0, "Specifies the component model values on colour component 0.") + ("SEIFGCCompModelValuesComp1", cfg_FgcSEICompModelValueComp1, cfg_FgcSEICompModelValueComp1, "Specifies the component model values on colour component 1.") + ("SEIFGCCompModelValuesComp2", cfg_FgcSEICompModelValueComp2, cfg_FgcSEICompModelValueComp2, "Specifies the component model values on colour component 2.") +#endif // content light level SEI ("SEICLLEnabled", m_cllSEIEnabled, false, "Control generation of the content light level SEI message") ("SEICLLMaxContentLightLevel", m_cllSEIMaxContentLevel, 0u, "When not equal to 0, specifies an upper bound on the maximum light level among all individual samples in a 4:4:4 representation " @@ -2608,6 +2640,91 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) m_masteringDisplay.whitePoint[idx] = uint16_t((cfg_DisplayWhitePointCode.values.size() > idx) ? cfg_DisplayWhitePointCode.values[idx] : 0); } } +#if JVET_X0048_X0103_FILM_GRAIN + // set sei film grain parameters. + CHECK(!m_fgcSEIEnabled && m_fgcSEIAnalysisEnabled, "FGC SEI must be enabled in order to perform film grain analysis!"); + if (m_fgcSEIEnabled) + { + if (m_iQP < 17 && m_fgcSEIAnalysisEnabled == true) { + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: Film Grain Estimation is disabled for Qp<17! FGC SEI will use default parameters for film grain! *\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIAnalysisEnabled = false; + } + if (m_iIntraPeriod < 1) { // low delay configuration + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: For low delay configuration, FGC SEI is inserted for first frame only!*\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIPerPictureSEI = false; + m_fgcSEIPersistenceFlag = true; + } + else if (m_iIntraPeriod == 1) { // all intra configuration + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: For Intra Period = 1, FGC SEI is inserted per frame!*\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIPerPictureSEI = true; + m_fgcSEIPersistenceFlag = false; + } + if (!m_fgcSEIPerPictureSEI && !m_fgcSEIPersistenceFlag) { + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: SEIPerPictureSEI is set to 0, SEIPersistenceFlag needs to be set to 1! *\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIPersistenceFlag = true; + } + else if (m_fgcSEIPerPictureSEI && m_fgcSEIPersistenceFlag) { + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: SEIPerPictureSEI is set to 1, SEIPersistenceFlag needs to be set to 0! *\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIPersistenceFlag = false; + } + uint32_t numModelCtr; + if (m_fgcSEICompModelPresent[0]) + { + numModelCtr = 0; + for (uint8_t i = 0; i <= m_fgcSEINumIntensityIntervalMinus1[0]; i++) + { + m_fgcSEIIntensityIntervalLowerBound[0][i] = uint32_t((cfg_FgcSEIIntensityIntervalLowerBoundComp0.values.size() > i) ? cfg_FgcSEIIntensityIntervalLowerBoundComp0.values[i] : 10); + m_fgcSEIIntensityIntervalUpperBound[0][i] = uint32_t((cfg_FgcSEIIntensityIntervalUpperBoundComp0.values.size() > i) ? cfg_FgcSEIIntensityIntervalUpperBoundComp0.values[i] : 250); + for (uint8_t j = 0; j <= m_fgcSEINumModelValuesMinus1[0]; j++) + { + m_fgcSEICompModelValue[0][i][j] = uint32_t((cfg_FgcSEICompModelValueComp0.values.size() > numModelCtr) ? cfg_FgcSEICompModelValueComp0.values[numModelCtr] : 24); + numModelCtr++; + } + } + } + if (m_fgcSEICompModelPresent[1]) + { + numModelCtr = 0; + for (uint8_t i = 0; i <= m_fgcSEINumIntensityIntervalMinus1[1]; i++) + { + m_fgcSEIIntensityIntervalLowerBound[1][i] = uint32_t((cfg_FgcSEIIntensityIntervalLowerBoundComp1.values.size() > i) ? cfg_FgcSEIIntensityIntervalLowerBoundComp1.values[i] : 60); + m_fgcSEIIntensityIntervalUpperBound[1][i] = uint32_t((cfg_FgcSEIIntensityIntervalUpperBoundComp1.values.size() > i) ? cfg_FgcSEIIntensityIntervalUpperBoundComp1.values[i] : 200); + + for (uint8_t j = 0; j <= m_fgcSEINumModelValuesMinus1[1]; j++) + { + m_fgcSEICompModelValue[1][i][j] = uint32_t((cfg_FgcSEICompModelValueComp1.values.size() > numModelCtr) ? cfg_FgcSEICompModelValueComp1.values[numModelCtr] : 16); + numModelCtr++; + } + } + } + if (m_fgcSEICompModelPresent[2]) + { + numModelCtr = 0; + for (uint8_t i = 0; i <= m_fgcSEINumIntensityIntervalMinus1[2]; i++) + { + m_fgcSEIIntensityIntervalLowerBound[2][i] = uint32_t((cfg_FgcSEIIntensityIntervalLowerBoundComp2.values.size() > i) ? cfg_FgcSEIIntensityIntervalLowerBoundComp2.values[i] : 60); + m_fgcSEIIntensityIntervalUpperBound[2][i] = uint32_t((cfg_FgcSEIIntensityIntervalUpperBoundComp2.values.size() > i) ? cfg_FgcSEIIntensityIntervalUpperBoundComp2.values[i] : 250); + + for (uint8_t j = 0; j <= m_fgcSEINumModelValuesMinus1[2]; j++) + { + m_fgcSEICompModelValue[2][i][j] = uint32_t((cfg_FgcSEICompModelValueComp2.values.size() > numModelCtr) ? cfg_FgcSEICompModelValueComp2.values[numModelCtr] : 12); + numModelCtr++; + } + } + } + m_fgcSEILog2ScaleFactor = m_fgcSEILog2ScaleFactor ? m_fgcSEILog2ScaleFactor : 2; + } +#endif if (m_ctiSEIEnabled) { CHECK(!m_ctiSEICrossComponentFlag && m_ctiSEICrossComponentInferred, "CTI CrossComponentFlag is 0, but CTI CrossComponentInferred is 1 (must be 0 for CrossComponentFlag 0)"); diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h index 90725a554e92ce69012522d759f3c4a8cff324f3..7428c281218e4c82b3f56c35e75383f459286dd0 100644 --- a/source/App/EncoderApp/EncAppCfg.h +++ b/source/App/EncoderApp/EncAppCfg.h @@ -546,6 +546,15 @@ protected: uint32_t m_fgcSEIBlendingModeID; uint32_t m_fgcSEILog2ScaleFactor; bool m_fgcSEICompModelPresent[MAX_NUM_COMPONENT]; +#if JVET_X0048_X0103_FILM_GRAIN + bool m_fgcSEIAnalysisEnabled; + bool m_fgcSEIPerPictureSEI; + uint32_t m_fgcSEINumModelValuesMinus1 [MAX_NUM_COMPONENT]; + uint32_t m_fgcSEINumIntensityIntervalMinus1 [MAX_NUM_COMPONENT]; + uint32_t m_fgcSEIIntensityIntervalLowerBound [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; + uint32_t m_fgcSEIIntensityIntervalUpperBound [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; + uint32_t m_fgcSEICompModelValue [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES]; +#endif // content light level SEI bool m_cllSEIEnabled; uint32_t m_cllSEIMaxContentLevel; diff --git a/source/App/SEIFilmGrainApp/CMakeLists.txt b/source/App/SEIFilmGrainApp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..1c26c4668c74e2aae14b747fdfcc9b0009cbca36 --- /dev/null +++ b/source/App/SEIFilmGrainApp/CMakeLists.txt @@ -0,0 +1,64 @@ +# executable +set( EXE_NAME SEIFilmGrainApp ) + +# get source files +file( GLOB SRC_FILES "*.cpp" ) + +# get include files +file( GLOB INC_FILES "*.h" ) + +# get additional libs for gcc on Ubuntu systems +if( CMAKE_SYSTEM_NAME STREQUAL "Linux" ) + if( CMAKE_CXX_COMPILER_ID STREQUAL "GNU" ) + if( USE_ADDRESS_SANITIZER ) + set( ADDITIONAL_LIBS asan ) + endif() + endif() +endif() + +# NATVIS files for Visual Studio +if( MSVC ) + file( GLOB NATVIS_FILES "../../VisualStudio/*.natvis" ) +endif() + +# add executable +add_executable( ${EXE_NAME} ${SRC_FILES} ${INC_FILES} ${NATVIS_FILES} ) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +if( SET_ENABLE_TRACING ) + if( ENABLE_TRACING ) + target_compile_definitions( ${EXE_NAME} PUBLIC ENABLE_TRACING=1 ) + else() + target_compile_definitions( ${EXE_NAME} PUBLIC ENABLE_TRACING=0 ) + endif() +endif() + +if( CMAKE_COMPILER_IS_GNUCC AND BUILD_STATIC ) + set( ADDITIONAL_LIBS ${ADDITIONAL_LIBS} -static -static-libgcc -static-libstdc++ ) + target_compile_definitions( ${EXE_NAME} PUBLIC ENABLE_WPP_STATIC_LINK=1 ) +endif() + +target_link_libraries( ${EXE_NAME} CommonLib EncoderLib DecoderLib Utilities ${ADDITIONAL_LIBS} ) + +# lldb custom data formatters +if( XCODE ) + add_dependencies( ${EXE_NAME} Install${PROJECT_NAME}LldbFiles ) +endif() + +if( CMAKE_SYSTEM_NAME STREQUAL "Linux" ) + add_custom_command( TARGET ${EXE_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy + $<$<CONFIG:Debug>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/SEIFilmGrainApp> + $<$<CONFIG:Release>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/SEIFilmGrainApp> + $<$<CONFIG:RelWithDebInfo>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO}/SEIFilmGrainApp> + $<$<CONFIG:MinSizeRel>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL}/SEIFilmGrainApp> + $<$<CONFIG:Debug>:${CMAKE_SOURCE_DIR}/bin/SEIFilmGrainAppStaticd> + $<$<CONFIG:Release>:${CMAKE_SOURCE_DIR}/bin/SEIFilmGrainAppStatic> + $<$<CONFIG:RelWithDebInfo>:${CMAKE_SOURCE_DIR}/bin/SEIFilmGrainAppStaticp> + $<$<CONFIG:MinSizeRel>:${CMAKE_SOURCE_DIR}/bin/SEIFilmGrainAppStaticm> ) +endif() + +# example: place header files in different folders +source_group( "Natvis Files" FILES ${NATVIS_FILES} ) + +# set the folder where to place the projects +set_target_properties( ${EXE_NAME} PROPERTIES FOLDER app LINKER_LANGUAGE CXX ) diff --git a/source/App/SEIFilmGrainApp/SEIFilmGrainApp.cpp b/source/App/SEIFilmGrainApp/SEIFilmGrainApp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..15a07861569ba75b45526a22503f6712c2cb5932 --- /dev/null +++ b/source/App/SEIFilmGrainApp/SEIFilmGrainApp.cpp @@ -0,0 +1,282 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainApp.cpp + \brief Decoder application class +*/ + +#include <list> +#include <vector> +#include <stdio.h> +#include <fcntl.h> + +#include "SEIFilmGrainApp.h" +#include "DecoderLib/AnnexBread.h" +#include "EncoderLib/AnnexBwrite.h" + +#if JVET_X0048_X0103_FILM_GRAIN +//! \ingroup SEIFilmGrainApp +//! \{ + +// ==================================================================================================================== +// Constructor / destructor / initialization / destroy +// ==================================================================================================================== + +SEIFilmGrainApp::SEIFilmGrainApp() +{ +} + +// ==================================================================================================================== +// Public member functions +// ==================================================================================================================== + +/** + - create internal class + - initialize internal class + - until the end of the bitstream, call decoding function in SEIFilmGrainApp class + - delete allocated buffers + - destroy internal class + - returns the number of mismatching pictures + */ + +void read2(InputNALUnit& nalu) +{ + InputBitstream& bs = nalu.getBitstream(); + + nalu.m_forbiddenZeroBit = bs.read(1); // forbidden zero bit + nalu.m_nuhReservedZeroBit = bs.read(1); // nuh_reserved_zero_bit + nalu.m_nuhLayerId = bs.read(6); // nuh_layer_id + nalu.m_nalUnitType = (NalUnitType)bs.read(5); // nal_unit_type + nalu.m_temporalId = bs.read(3) - 1; // nuh_temporal_id_plus1 +} + +void SEIFilmGrainApp::setSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics *pFgcParameters) +{ + // Set SEI message parameters read from command line options + pFgcParameters->m_filmGrainCharacteristicsCancelFlag = m_fgcSEICancelFlag; + pFgcParameters->m_filmGrainCharacteristicsPersistenceFlag = m_fgcSEIPersistenceFlag; + pFgcParameters->m_separateColourDescriptionPresentFlag = m_fgcSEISepColourDescPresentFlag; + pFgcParameters->m_filmGrainModelId = m_fgcSEIModelID; + pFgcParameters->m_blendingModeId = m_fgcSEIBlendingModeID; + pFgcParameters->m_log2ScaleFactor = m_fgcSEILog2ScaleFactor; + for (int i = 0; i < MAX_NUM_COMPONENT; i++) + { + pFgcParameters->m_compModel[i].presentFlag = m_fgcSEICompModelPresent[i]; + if (pFgcParameters->m_compModel[i].presentFlag) + { + pFgcParameters->m_compModel[i].numModelValues = 1 + m_fgcSEINumModelValuesMinus1[i]; + pFgcParameters->m_compModel[i].numIntensityIntervals = 1 + m_fgcSEINumIntensityIntervalMinus1[i]; + pFgcParameters->m_compModel[i].intensityValues.resize( pFgcParameters->m_compModel[i].numIntensityIntervals ); + for (int j = 0; j < pFgcParameters->m_compModel[i].numIntensityIntervals; j++) + { + pFgcParameters->m_compModel[i].intensityValues[j].intensityIntervalLowerBound = m_fgcSEIIntensityIntervalLowerBound[i][j]; + pFgcParameters->m_compModel[i].intensityValues[j].intensityIntervalUpperBound = m_fgcSEIIntensityIntervalUpperBound[i][j]; + pFgcParameters->m_compModel[i].intensityValues[j].compModelValue.resize( pFgcParameters->m_compModel[i].numModelValues ); + for (int k = 0; k < pFgcParameters->m_compModel[i].numModelValues; k++) + { + pFgcParameters->m_compModel[i].intensityValues[j].compModelValue[k] = m_fgcSEICompModelValue[i][j][k]; + } + } + } + } +} + +void SEIFilmGrainApp::printSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics *pFgcParameters) +{ + fprintf(stdout, "--------------------------------------\n"); + fprintf(stdout, "fg_characteristics_cancel_flag = %d\n", pFgcParameters->m_filmGrainCharacteristicsCancelFlag); + fprintf(stdout, "fg_model_id = %d\n", pFgcParameters->m_filmGrainModelId); + fprintf(stdout, "fg_separate_colour_description_present_flag = %d\n", pFgcParameters->m_separateColourDescriptionPresentFlag); + fprintf(stdout, "fg_blending_mode_id = %d\n", pFgcParameters->m_blendingModeId); + fprintf(stdout, "fg_log2_scale_factor = %d\n", pFgcParameters->m_log2ScaleFactor); + for (int c = 0; c < MAX_NUM_COMPONENT; c++) + { + fprintf(stdout, "fg_comp_model_present_flag[c] = %d\n", pFgcParameters->m_compModel[c].presentFlag); + } + for (int c = 0; c < MAX_NUM_COMPONENT; c++) + { + if (pFgcParameters->m_compModel[c].presentFlag) + { + fprintf(stdout, "num_intensity_intervals_minus1[c] = %d\n", pFgcParameters->m_compModel[c].numIntensityIntervals - 1); + fprintf(stdout, "fg_num_model_values_minus1[c] = %d\n", pFgcParameters->m_compModel[c].numModelValues - 1); + for (int i = 0; i < pFgcParameters->m_compModel[c].numIntensityIntervals; i++) + { + fprintf(stdout, "fg_intensity_interval_lower_bound[c][i] = %d\n", pFgcParameters->m_compModel[c].intensityValues[i].intensityIntervalLowerBound); + fprintf(stdout, "fg_intensity_interval_upper_bound[c][i] = %d\n", pFgcParameters->m_compModel[c].intensityValues[i].intensityIntervalUpperBound); + for (int j = 0; j < pFgcParameters->m_compModel[c].numModelValues; j++) + { + fprintf(stdout, "fg_comp_model_value[c][i][j] = %d\n", pFgcParameters->m_compModel[c].intensityValues[i].compModelValue[j]); + } + } + } + } + fprintf(stdout, "fg_characteristics_persistence_flag = %d\n", pFgcParameters->m_filmGrainCharacteristicsPersistenceFlag); + fprintf(stdout, "--------------------------------------\n"); +} + +uint32_t SEIFilmGrainApp::process() +{ + ifstream bitstreamFileIn(m_bitstreamFileNameIn.c_str(), ifstream::in | ifstream::binary); + if (!bitstreamFileIn) + { + EXIT( "failed to open bitstream file " << m_bitstreamFileNameIn.c_str() << " for reading" ) ; + } + + ofstream bitstreamFileOut(m_bitstreamFileNameOut.c_str(), ifstream::out | ifstream::binary); + + InputByteStream bytestream(bitstreamFileIn); + + bitstreamFileIn.clear(); + bitstreamFileIn.seekg( 0, ios::beg ); + + int NALUcount = 0; + int SEIcount = 0; + + while (!!bitstreamFileIn) + { + /* 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 SEIFilmGrainApp::process() method to be called again with the same + * nal unit. */ + AnnexBStats stats = AnnexBStats(); + + InputNALUnit nalu; + byteStreamNALUnit(bytestream, nalu.getBitstream().getFifo(), stats); + + bool writeNALU = true; + bool removeSEI = false; + bool insertSEI = false; + + // call actual decoding function + 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 + */ + std::cerr << "Warning: Attempt to process an empty NAL unit" << std::endl; + } + else + { + read2(nalu); + NALUcount++; + SEIMessages SEIs; + VPS *vps = nullptr; + + if (nalu.m_nalUnitType == NAL_UNIT_PPS && m_seiFilmGrainOption == 2) + { + fprintf(stdout, "Option %d: Insert FGC SEI message ...\n", m_seiFilmGrainOption); + SEIFilmGrainCharacteristics *sei = new SEIFilmGrainCharacteristics; + setSEIFilmGrainCharacteristics(sei); + if (m_seiFilmGrainPrint) + { + printSEIFilmGrainCharacteristics(sei); + } + SEIs.push_back(sei); + insertSEI = true; + } // end PPS UnitType + + if (nalu.m_nalUnitType == NAL_UNIT_PREFIX_SEI && m_parameterSetManager.getActiveSPS()) + { + // parse FGC SEI + m_seiReader.parseSEImessage(&(nalu.getBitstream()), SEIs, nalu.m_nalUnitType, nalu.m_nuhLayerId, nalu.m_temporalId, vps, m_parameterSetManager.getActiveSPS(), m_hrd, &std::cout); + + int payloadType = 0; + std::list<SEI*>::iterator message; + SEIFilmGrainCharacteristics *fgcParameters; + for (message = SEIs.begin(); message != SEIs.end(); ++message) + { + SEIcount++; + payloadType = (*message)->payloadType(); + if (payloadType == SEI::FILM_GRAIN_CHARACTERISTICS) + { + if (m_seiFilmGrainOption == 1) // remove FGC SEI + { + fprintf(stdout, "Option %d: Remove FGC SEI message ...\n", m_seiFilmGrainOption); + removeSEI = true; + } + else if (m_seiFilmGrainOption == 3) // rewrite FGC SEI + { + fprintf(stdout, "Option %d: Rewrite FGC SEI message ...\n", m_seiFilmGrainOption); + fgcParameters = static_cast<SEIFilmGrainCharacteristics*>(*message); + setSEIFilmGrainCharacteristics(fgcParameters); + if (m_seiFilmGrainPrint) + { + printSEIFilmGrainCharacteristics(fgcParameters); + } + removeSEI = true; + insertSEI = true; + } + } // end FGC SEI + } + } // end SEI UnitType + + // write Nal Unit + if (writeNALU && !removeSEI && bitstreamFileOut) + { + int iNumZeros = stats.m_numLeadingZero8BitsBytes + stats.m_numZeroByteBytes + stats.m_numStartCodePrefixBytes - 1; + char ch = 0; + for (int i = 0; i < iNumZeros; i++) { bitstreamFileOut.write(&ch, 1); } + ch = 1; bitstreamFileOut.write(&ch, 1); + bitstreamFileOut.write((const char*)nalu.getBitstream().getFifo().data(), nalu.getBitstream().getFifo().size()); + } + + // write FGC SEI + if (writeNALU && insertSEI && bitstreamFileOut) + { + const bool useLongStartCode = (nalu.m_nalUnitType == NAL_UNIT_OPI || nalu.m_nalUnitType == NAL_UNIT_DCI || nalu.m_nalUnitType == NAL_UNIT_VPS || nalu.m_nalUnitType == NAL_UNIT_SPS + || nalu.m_nalUnitType == NAL_UNIT_PPS || nalu.m_nalUnitType == NAL_UNIT_PREFIX_APS || nalu.m_nalUnitType == NAL_UNIT_SUFFIX_APS); + SEIMessages currentMessages = extractSeisByType(SEIs, SEI::FILM_GRAIN_CHARACTERISTICS); + OutputNALUnit outNalu(NAL_UNIT_PREFIX_SEI, nalu.m_nuhLayerId, nalu.m_temporalId); + m_seiWriter.writeSEImessages(outNalu.m_Bitstream, currentMessages, m_hrd, false, nalu.m_temporalId); + NALUnitEBSP naluWithHeader(outNalu); + writeAnnexBNalUnit(bitstreamFileOut, naluWithHeader, useLongStartCode); + } + } + + } // end bitstreamFileIn + + if (m_seiFilmGrainOption) + { + fprintf(stdout, "\n\n========================= SUMMARY =============================== \n"); + fprintf(stdout, " Total NALU count: %d \n", NALUcount); + fprintf(stdout, " Total SEI count : %d \n", SEIcount); + fprintf(stdout, " FGC SEI process : %d \n", m_seiFilmGrainOption); + fprintf(stdout, "================================================================= \n"); + } + return 0; +} + +//! \} +#endif diff --git a/source/App/SEIFilmGrainApp/SEIFilmGrainApp.h b/source/App/SEIFilmGrainApp/SEIFilmGrainApp.h new file mode 100644 index 0000000000000000000000000000000000000000..54d25dc225ef98da24303b933e6df5a2c2f894e0 --- /dev/null +++ b/source/App/SEIFilmGrainApp/SEIFilmGrainApp.h @@ -0,0 +1,88 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainApp.h + \brief Decoder application class (header) +*/ + +#ifndef __SEIFILMGRAINAPP__ +#define __SEIFILMGRAINAPP__ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include <stdio.h> +#include <fstream> +#include <iostream> +#include "CommonLib/CommonDef.h" +#include "SEIFilmGrainAppCfg.h" +#include "DecoderLib/VLCReader.h" +#include "EncoderLib/VLCWriter.h" +#include "DecoderLib/NALread.h" +#include "EncoderLib/NALwrite.h" +#include "DecoderLib/SEIread.h" +#include "EncoderLib/SEIwrite.h" + +using namespace std; + +#if JVET_X0048_X0103_FILM_GRAIN +// ==================================================================================================================== +// Class definition +// ==================================================================================================================== + +/// decoder application class +class SEIFilmGrainApp : public SEIFilmGrainAppCfg +{ + +public: + SEIFilmGrainApp(); + virtual ~SEIFilmGrainApp() {} + + uint32_t process (); ///< main decoding function + +protected: + ParameterSetManager m_parameterSetManager; + SEIReader m_seiReader; + SEIWriter m_seiWriter; + + int m_vpsId; + HRD m_hrd; + + void setSEIFilmGrainCharacteristics ( SEIFilmGrainCharacteristics *pFgcParameters ); + void printSEIFilmGrainCharacteristics( SEIFilmGrainCharacteristics *pFgcParameters ); +}; +#endif + +#endif // __SEIFILMGRAINAPP__ + diff --git a/source/App/SEIFilmGrainApp/SEIFilmGrainAppCfg.cpp b/source/App/SEIFilmGrainApp/SEIFilmGrainAppCfg.cpp new file mode 100644 index 0000000000000000000000000000000000000000..16534cc611d0d729a6e586594566124737453dc0 --- /dev/null +++ b/source/App/SEIFilmGrainApp/SEIFilmGrainAppCfg.cpp @@ -0,0 +1,332 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainAppCfg.cpp + \brief Decoder configuration class +*/ + +#include <cstdio> +#include <cstring> +#include <string> +#include "SEIFilmGrainAppCfg.h" +#include "Utilities/program_options_lite.h" +#if ENABLE_TRACING +#include "CommonLib/dtrace_next.h" +#endif + +using namespace std; +namespace po = df::program_options_lite; + +#if JVET_X0048_X0103_FILM_GRAIN +//! \ingroup SEIFilmGrainApp +//! \{ + +template <class T> +static inline istream& operator >> (std::istream &in, SMultiValueInput<T> &values) +{ + return values.readValues(in); +} + +template <class T> +T SMultiValueInput<T>::readValue(const char *&pStr, bool &bSuccess) +{ + T val = T(); + std::string s(pStr); + std::replace(s.begin(), s.end(), ',', ' '); // make comma separated into space separated + std::istringstream iss(s); + iss >> val; + bSuccess = !iss.fail() // check nothing has gone wrong + && !(val<minValIncl || val>maxValIncl) // check value is within range + && (int)iss.tellg() != 0 // check we've actually read something + && (iss.eof() || iss.peek() == ' '); // check next character is a space, or eof + pStr += (iss.eof() ? s.size() : (std::size_t)iss.tellg()); + return val; +} + +template <class T> +istream& SMultiValueInput<T>::readValues(std::istream &in) +{ + values.clear(); + string str; + while (!in.eof()) + { + string tmp; in >> tmp; str += " " + tmp; + } + if (!str.empty()) + { + const char *pStr = str.c_str(); + // soak up any whitespace + for (; isspace(*pStr); pStr++); + + while (*pStr != 0) + { + bool bSuccess = true; + T val = readValue(pStr, bSuccess); + if (!bSuccess) + { + in.setstate(ios::failbit); + break; + } + + if (maxNumValuesIncl != 0 && values.size() >= maxNumValuesIncl) + { + in.setstate(ios::failbit); + break; + } + values.push_back(val); + // soak up any whitespace and up to 1 comma. + for (; isspace(*pStr); pStr++); + if (*pStr == ',') + { + pStr++; + } + for (; isspace(*pStr); pStr++); + } + } + if (values.size() < minNumValuesIncl) + { + in.setstate(ios::failbit); + } + return in; +} + +// ==================================================================================================================== +// Public member functions +// ==================================================================================================================== + +/** \param argc number of arguments + \param argv array of arguments + */ +bool SEIFilmGrainAppCfg::parseCfg( int argc, char* argv[] ) +{ +#if ENABLE_TRACING + bool printTracingChannelsList; + std::string tracingFile; + std::string tracingRule; +#endif + + bool do_help = false; + int warnUnknowParameter = 0; + + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalLowerBoundComp0(0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalLowerBoundComp1(0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalLowerBoundComp2(0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalUpperBoundComp0(0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalUpperBoundComp1(0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEIIntensityIntervalUpperBoundComp2(0, 255, 0, 256); + SMultiValueInput<uint32_t> cfg_FgcSEICompModelValueComp0(0, 65535, 0, 256 * 6); + SMultiValueInput<uint32_t> cfg_FgcSEICompModelValueComp1(0, 65535, 0, 256 * 6); + SMultiValueInput<uint32_t> cfg_FgcSEICompModelValueComp2(0, 65535, 0, 256 * 6); + + po::Options opts; + opts.addOptions() + + ("help", do_help, false, "this help text") + ("c", po::parseConfigFile, "film grain configuration file name") + ("BitstreamFileIn,b", m_bitstreamFileNameIn, string(""), "bitstream input file name") + ("BitstreamFileOut,o", m_bitstreamFileNameOut, string(""), "bitstream output file name") + ("SEIFilmGrainOption", m_seiFilmGrainOption, 0, "process FGC SEI option (0:disable, 1:remove, 2:insert, 3:change)" ) + ("SEIFilmGrainPrint", m_seiFilmGrainPrint, false, "print output film grain characteristics SEI message (1:enable)") +// film grain characteristics SEI + ("SEIFGCEnabled", m_fgcSEIEnabled, false, "Control generation of the film grain characteristics SEI message") + ("SEIFGCAnalysisEnabled", m_fgcSEIAnalysisEnabled, false, "Control adaptive film grain parameter estimation - film grain analysis") + ("SEIFGCCancelFlag", m_fgcSEICancelFlag, true, "Specifies the persistence of any previous film grain characteristics SEI message in output order.") + ("SEIFGCPersistenceFlag", m_fgcSEIPersistenceFlag, false, "Specifies the persistence of the film grain characteristics SEI message for the current layer.") + ("SEIFGCPerPictureSEI", m_fgcSEIPerPictureSEI, false, "Film Grain SEI is added for each picture as speciffied in RDD5 to ensure bit accurate synthesis in tricky mode") + ("SEIFGCModelID", m_fgcSEIModelID, 0u, "Specifies the film grain simulation model. 0: frequency filtering; 1: auto-regression.") + ("SEIFGCSepColourDescPresentFlag", m_fgcSEISepColourDescPresentFlag, false, "Specifies the presence of a distinct colour space description for the film grain characteristics specified in the SEI message.") + ("SEIFGCBlendingModeID", m_fgcSEIBlendingModeID, 0u, "Specifies the blending mode used to blend the simulated film grain with the decoded images. 0: additive; 1: multiplicative.") + ("SEIFGCLog2ScaleFactor", m_fgcSEILog2ScaleFactor, 0u, "Specifies a scale factor used in the film grain characterization equations.") + ("SEIFGCCompModelPresentComp0", m_fgcSEICompModelPresent[0], false, "Specifies the presence of film grain modelling on colour component 0.") + ("SEIFGCCompModelPresentComp1", m_fgcSEICompModelPresent[1], false, "Specifies the presence of film grain modelling on colour component 1.") + ("SEIFGCCompModelPresentComp2", m_fgcSEICompModelPresent[2], false, "Specifies the presence of film grain modelling on colour component 2.") + ("SEIFGCNumIntensityIntervalMinus1Comp0", m_fgcSEINumIntensityIntervalMinus1[0], 0u, "Specifies the number of intensity intervals minus1 on colour component 0.") + ("SEIFGCNumIntensityIntervalMinus1Comp1", m_fgcSEINumIntensityIntervalMinus1[1], 0u, "Specifies the number of intensity intervals minus1 on colour component 1.") + ("SEIFGCNumIntensityIntervalMinus1Comp2", m_fgcSEINumIntensityIntervalMinus1[2], 0u, "Specifies the number of intensity intervals minus1 on colour component 2.") + ("SEIFGCNumModelValuesMinus1Comp0", m_fgcSEINumModelValuesMinus1[0], 0u, "Specifies the number of component model values minus1 on colour component 0.") + ("SEIFGCNumModelValuesMinus1Comp1", m_fgcSEINumModelValuesMinus1[1], 0u, "Specifies the number of component model values minus1 on colour component 1.") + ("SEIFGCNumModelValuesMinus1Comp2", m_fgcSEINumModelValuesMinus1[2], 0u, "Specifies the number of component model values minus1 on colour component 2.") + ("SEIFGCIntensityIntervalLowerBoundComp0", cfg_FgcSEIIntensityIntervalLowerBoundComp0, cfg_FgcSEIIntensityIntervalLowerBoundComp0, "Specifies the lower bound for the intensity intervals on colour component 0.") + ("SEIFGCIntensityIntervalLowerBoundComp1", cfg_FgcSEIIntensityIntervalLowerBoundComp1, cfg_FgcSEIIntensityIntervalLowerBoundComp1, "Specifies the lower bound for the intensity intervals on colour component 1.") + ("SEIFGCIntensityIntervalLowerBoundComp2", cfg_FgcSEIIntensityIntervalLowerBoundComp2, cfg_FgcSEIIntensityIntervalLowerBoundComp2, "Specifies the lower bound for the intensity intervals on colour component 2.") + ("SEIFGCIntensityIntervalUpperBoundComp0", cfg_FgcSEIIntensityIntervalUpperBoundComp0, cfg_FgcSEIIntensityIntervalUpperBoundComp0, "Specifies the upper bound for the intensity intervals on colour component 0.") + ("SEIFGCIntensityIntervalUpperBoundComp1", cfg_FgcSEIIntensityIntervalUpperBoundComp1, cfg_FgcSEIIntensityIntervalUpperBoundComp1, "Specifies the upper bound for the intensity intervals on colour component 1.") + ("SEIFGCIntensityIntervalUpperBoundComp2", cfg_FgcSEIIntensityIntervalUpperBoundComp2, cfg_FgcSEIIntensityIntervalUpperBoundComp2, "Specifies the upper bound for the intensity intervals on colour component 2.") + ("SEIFGCCompModelValuesComp0", cfg_FgcSEICompModelValueComp0, cfg_FgcSEICompModelValueComp0, "Specifies the component model values on colour component 0.") + ("SEIFGCCompModelValuesComp1", cfg_FgcSEICompModelValueComp1, cfg_FgcSEICompModelValueComp1, "Specifies the component model values on colour component 1.") + ("SEIFGCCompModelValuesComp2", cfg_FgcSEICompModelValueComp2, cfg_FgcSEICompModelValueComp2, "Specifies the component model values on colour component 2.") + +#if ENABLE_TRACING + ("TraceChannelsList", printTracingChannelsList, false, "List all available tracing channels" ) + ("TraceRule", tracingRule, string(""), "Tracing rule (ex: \"D_CABAC:poc==8\" or \"D_REC_CB_LUMA:poc==8\")" ) + ("TraceFile", tracingFile, string(""), "Tracing file" ) +#endif + ("WarnUnknowParameter,w", warnUnknowParameter, 0, "warn for unknown configuration parameters instead of failing") + ; + + po::setDefaults(opts); + po::ErrorReporter err; + const list<const char*>& argv_unhandled = po::scanArgv(opts, argc, (const char**) argv, err); + + for (list<const char*>::const_iterator it = argv_unhandled.begin(); it != argv_unhandled.end(); it++) + { + std::cerr << "Unhandled argument ignored: "<< *it << std::endl; + } + + if (argc == 1 || do_help) + { + po::doHelp(cout, opts); + return false; + } + +#if ENABLE_TRACING + g_trace_ctx = tracing_init(tracingFile, tracingRule); + if (printTracingChannelsList && g_trace_ctx) + { + std::string channelsList; + g_trace_ctx->getChannelsList(channelsList); + msg(INFO, "\nAvailable tracing channels:\n\n%s\n", channelsList.c_str()); + } + DTRACE_UPDATE(g_trace_ctx, std::make_pair("final", 1)); +#endif + + if (err.is_errored) + { + if (!warnUnknowParameter) + { + /* errors have already been reported to stderr */ + return false; + } + } + + if (m_bitstreamFileNameIn.empty()) + { + std::cerr << "No input file specified, aborting" << std::endl; + return false; + } + if (m_bitstreamFileNameOut.empty()) + { + std::cerr << "No output file specified, aborting" << std::endl; + return false; + } + + // set sei film grain parameters. + if (m_fgcSEIEnabled) + { + if (m_fgcSEIAnalysisEnabled) { + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: SEIFGCAnalysisEnabled needs to be set to 0! *\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIAnalysisEnabled = false; + } + if (!m_fgcSEIPerPictureSEI && !m_fgcSEIPersistenceFlag) { + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: SEIPerPictureSEI is set to 0, SEIPersistenceFlag needs to be set to 1! *\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIPersistenceFlag = true; + } + else if (m_fgcSEIPerPictureSEI && m_fgcSEIPersistenceFlag) { + msg(WARNING, "*************************************************************************\n"); + msg(WARNING, "* WARNING: SEIPerPictureSEI is set to 1, SEIPersistenceFlag needs to be set to 0! *\n"); + msg(WARNING, "*************************************************************************\n"); + m_fgcSEIPersistenceFlag = false; + } + m_fgcSEILog2ScaleFactor = m_fgcSEILog2ScaleFactor ? m_fgcSEILog2ScaleFactor : 2; + + uint32_t numModelCtr; + if (m_fgcSEICompModelPresent[0]) + { + numModelCtr = 0; + for (uint8_t i = 0; i <= m_fgcSEINumIntensityIntervalMinus1[0]; i++) + { + m_fgcSEIIntensityIntervalLowerBound[0][i] = uint32_t((cfg_FgcSEIIntensityIntervalLowerBoundComp0.values.size() > i) ? cfg_FgcSEIIntensityIntervalLowerBoundComp0.values[i] : 10); + m_fgcSEIIntensityIntervalUpperBound[0][i] = uint32_t((cfg_FgcSEIIntensityIntervalUpperBoundComp0.values.size() > i) ? cfg_FgcSEIIntensityIntervalUpperBoundComp0.values[i] : 250); + for (uint8_t j = 0; j <= m_fgcSEINumModelValuesMinus1[0]; j++) + { + m_fgcSEICompModelValue[0][i][j] = uint32_t((cfg_FgcSEICompModelValueComp0.values.size() > numModelCtr) ? cfg_FgcSEICompModelValueComp0.values[numModelCtr] : 24); + numModelCtr++; + } + } + } + if (m_fgcSEICompModelPresent[1]) + { + numModelCtr = 0; + for (uint8_t i = 0; i <= m_fgcSEINumIntensityIntervalMinus1[1]; i++) + { + m_fgcSEIIntensityIntervalLowerBound[1][i] = uint32_t((cfg_FgcSEIIntensityIntervalLowerBoundComp1.values.size() > i) ? cfg_FgcSEIIntensityIntervalLowerBoundComp1.values[i] : 60); + m_fgcSEIIntensityIntervalUpperBound[1][i] = uint32_t((cfg_FgcSEIIntensityIntervalUpperBoundComp1.values.size() > i) ? cfg_FgcSEIIntensityIntervalUpperBoundComp1.values[i] : 200); + + for (uint8_t j = 0; j <= m_fgcSEINumModelValuesMinus1[1]; j++) + { + m_fgcSEICompModelValue[1][i][j] = uint32_t((cfg_FgcSEICompModelValueComp1.values.size() > numModelCtr) ? cfg_FgcSEICompModelValueComp1.values[numModelCtr] : 16); + numModelCtr++; + } + } + } + if (m_fgcSEICompModelPresent[2]) + { + numModelCtr = 0; + for (uint8_t i = 0; i <= m_fgcSEINumIntensityIntervalMinus1[2]; i++) + { + m_fgcSEIIntensityIntervalLowerBound[2][i] = uint32_t((cfg_FgcSEIIntensityIntervalLowerBoundComp2.values.size() > i) ? cfg_FgcSEIIntensityIntervalLowerBoundComp2.values[i] : 60); + m_fgcSEIIntensityIntervalUpperBound[2][i] = uint32_t((cfg_FgcSEIIntensityIntervalUpperBoundComp2.values.size() > i) ? cfg_FgcSEIIntensityIntervalUpperBoundComp2.values[i] : 250); + + for (uint8_t j = 0; j <= m_fgcSEINumModelValuesMinus1[2]; j++) + { + m_fgcSEICompModelValue[2][i][j] = uint32_t((cfg_FgcSEICompModelValueComp2.values.size() > numModelCtr) ? cfg_FgcSEICompModelValueComp2.values[numModelCtr] : 12); + numModelCtr++; + } + } + } + } + + return true; +} + +SEIFilmGrainAppCfg::SEIFilmGrainAppCfg() +: m_bitstreamFileNameIn() +, m_bitstreamFileNameOut() +, m_seiFilmGrainOption() +, m_seiFilmGrainPrint(false) +{ +} + +SEIFilmGrainAppCfg::~SEIFilmGrainAppCfg() +{ +#if ENABLE_TRACING + tracing_uninit(g_trace_ctx); +#endif +} + +//! \} +#endif \ No newline at end of file diff --git a/source/App/SEIFilmGrainApp/SEIFilmGrainAppCfg.h b/source/App/SEIFilmGrainApp/SEIFilmGrainAppCfg.h new file mode 100644 index 0000000000000000000000000000000000000000..a51f14321aa7ff857845231ffb31e3f669beef63 --- /dev/null +++ b/source/App/SEIFilmGrainApp/SEIFilmGrainAppCfg.h @@ -0,0 +1,93 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainAppCfg.h + \brief Decoder configuration class (header) +*/ + +#ifndef __SEIFILMGRAINAPPCFG__ +#define __SEIFILMGRAINAPPCFG__ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "CommonLib/CommonDef.h" +#include <vector> + +#if JVET_X0048_X0103_FILM_GRAIN +//! \ingroup SEIFilmGrainApp +//! \{ + +// ==================================================================================================================== +// Class definition +// ==================================================================================================================== + +/// Decoder configuration class +class SEIFilmGrainAppCfg +{ +protected: + std::string m_bitstreamFileNameIn; ///< output bitstream file name + std::string m_bitstreamFileNameOut; ///< input bitstream file name + int m_seiFilmGrainOption; ///< process FGC SEI option: 0:disable, 1:remove, 2:insert, 3:change + bool m_seiFilmGrainPrint; ///< print output FGC SEI message. 1:enable + // film grain characterstics sei + bool m_fgcSEIEnabled; + bool m_fgcSEIAnalysisEnabled; + bool m_fgcSEICancelFlag; + bool m_fgcSEIPersistenceFlag; + bool m_fgcSEIPerPictureSEI; + uint32_t m_fgcSEIModelID; + bool m_fgcSEISepColourDescPresentFlag; + uint32_t m_fgcSEIBlendingModeID; + uint32_t m_fgcSEILog2ScaleFactor; + bool m_fgcSEICompModelPresent [MAX_NUM_COMPONENT]; + uint32_t m_fgcSEINumModelValuesMinus1 [MAX_NUM_COMPONENT]; + uint32_t m_fgcSEINumIntensityIntervalMinus1 [MAX_NUM_COMPONENT]; + uint32_t m_fgcSEIIntensityIntervalLowerBound [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; + uint32_t m_fgcSEIIntensityIntervalUpperBound [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; + uint32_t m_fgcSEICompModelValue [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES]; + +public: + SEIFilmGrainAppCfg(); + virtual ~SEIFilmGrainAppCfg(); + + bool parseCfg ( int argc, char* argv[] ); ///< initialize option class from configuration +}; + +//! \} +#endif + +#endif // __SEIFILMGRAINAPPCFG__ + + diff --git a/source/App/SEIFilmGrainApp/SEIFilmGrainMain.cpp b/source/App/SEIFilmGrainApp/SEIFilmGrainMain.cpp new file mode 100644 index 0000000000000000000000000000000000000000..995434858e61f329298f0991e6bd0fafbec4d9c6 --- /dev/null +++ b/source/App/SEIFilmGrainApp/SEIFilmGrainMain.cpp @@ -0,0 +1,119 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainMain.cpp + \brief Decoder application main +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include "SEIFilmGrainApp.h" +#include "program_options_lite.h" + +//! \ingroup SEIFilmGrainApp +//! \{ + +// ==================================================================================================================== +// Main function +// ==================================================================================================================== + +int main(int argc, char* argv[]) +{ + int returnCode = EXIT_SUCCESS; +#if JVET_X0048_X0103_FILM_GRAIN + // print information + fprintf( stdout, "\n" ); + fprintf( stdout, "VVCSoftware: SEIFilmGrainApp 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" ); + + SEIFilmGrainApp *pcSEIApp = new SEIFilmGrainApp; + // parse configuration + if(!pcSEIApp->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 != pcSEIApp->process() ) + { + 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 pcSEIApp; +#endif + return returnCode; +} + +//! \} diff --git a/source/Lib/CommonLib/CodingStructure.h b/source/Lib/CommonLib/CodingStructure.h index 9fcfe29b20e89efbe2c5c423588368be915e6f30..61829c87aa31a176b99bf2d39c8810cee6605a86 100644 --- a/source/Lib/CommonLib/CodingStructure.h +++ b/source/Lib/CommonLib/CodingStructure.h @@ -55,6 +55,9 @@ enum PictureType PIC_ORIGINAL, PIC_TRUE_ORIGINAL, PIC_FILTERED_ORIGINAL, +#if JVET_X0048_X0103_FILM_GRAIN + PIC_FILTERED_ORIGINAL_FG, +#endif PIC_PREDICTION, PIC_RESIDUAL, PIC_ORG_RESI, diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h index ffbca486194a9703ae47c34d682fb999289a6d33..e166a89d7d1167b838348ddf052ceafd090ba9de 100644 --- a/source/Lib/CommonLib/CommonDef.h +++ b/source/Lib/CommonLib/CommonDef.h @@ -473,6 +473,18 @@ static const int DELTA_QP_ACT[4] = { -5, 1, 3, 1 }; static const int MAX_TSRC_RICE = 8; ///<Maximum supported TSRC Rice parameter static const int MIN_TSRC_RICE = 1; ///<Minimum supported TSRC Rice parameter static const int MAX_CTI_LUT_SIZE = 64; ///<Maximum colour transform LUT size for CTI SEI +#if JVET_X0048_X0103_FILM_GRAIN +static const int MAX_NUM_INTENSITIES = 256; ///<Maximum number of intensity intervals supported in FGC SEI +static const int MAX_NUM_MODEL_VALUES = 6; ///<Maximum number of model values supported in FGC SEI +static const int MAX_ALLOWED_MODEL_VALUES = 3; +static const int MAX_ALLOWED_COMP_MODEL_PAIRS = 10; +static const int MAX_STANDARD_DEVIATION = 255; // for 8-bit format; for higher bit depths, internal scaling is performed +static const int DATA_BASE_SIZE = 64; +static const int BLK_8 = 8; +static const int BLK_16 = 16; +static const int BLK_32 = 32; +static const int BIT_DEPTH_8 = 8; +#endif // ==================================================================================================================== // Macro functions // ==================================================================================================================== diff --git a/source/Lib/CommonLib/Picture.cpp b/source/Lib/CommonLib/Picture.cpp index 675e817aab9399fd635e64daa6c660a69809f257..c1fe07791a75a6bfee085ae2956f98c27f058ce9 100644 --- a/source/Lib/CommonLib/Picture.cpp +++ b/source/Lib/CommonLib/Picture.cpp @@ -75,9 +75,18 @@ Picture::Picture() layerId = NOT_VALID; numSlices = 1; unscaledPic = nullptr; +#if JVET_X0048_X0103_FILM_GRAIN + m_isMctfFiltered = false; + m_grainCharacteristic = NULL; + m_grainBuf = NULL; +#endif } +#if JVET_X0048_X0103_FILM_GRAIN +void Picture::create( const ChromaFormat &_chromaFormat, const Size &size, const unsigned _maxCUSize, const unsigned _margin, const bool _decoder, const int _layerId, const bool gopBasedTemporalFilterEnabled, const bool fgcSEIAnalysisEnabled ) +#else void Picture::create( const ChromaFormat &_chromaFormat, const Size &size, const unsigned _maxCUSize, const unsigned _margin, const bool _decoder, const int _layerId, const bool gopBasedTemporalFilterEnabled ) +#endif { layerId = _layerId; UnitArea::operator=( UnitArea( _chromaFormat, Area( Position{ 0, 0 }, size ) ) ); @@ -94,6 +103,12 @@ void Picture::create( const ChromaFormat &_chromaFormat, const Size &size, const { M_BUFS( 0, PIC_FILTERED_ORIGINAL ). create( _chromaFormat, a ); } +#if JVET_X0048_X0103_FILM_GRAIN + if ( fgcSEIAnalysisEnabled ) + { + M_BUFS( 0, PIC_FILTERED_ORIGINAL_FG ).create( _chromaFormat, a ); + } +#endif } #if !KEEP_PRED_AND_RESI_SIGNALS m_ctuArea = UnitArea( _chromaFormat, Area( Position{ 0, 0 }, Size( _maxCUSize, _maxCUSize ) ) ); @@ -140,6 +155,9 @@ void Picture::destroy() m_spliceIdx = NULL; } m_invColourTransfBuf = NULL; +#if JVET_X0048_X0103_FILM_GRAIN + m_grainBuf = NULL; +#endif } void Picture::createTempBuffers( const unsigned _maxCUSize ) @@ -1221,6 +1239,69 @@ void Picture::addPictureToHashMapForInter() } } } + +#if JVET_X0048_X0103_FILM_GRAIN +void Picture::createGrainSynthesizer(bool firstPictureInSequence, SEIFilmGrainSynthesizer *grainCharacteristics, PelStorage *grainBuf, int width, int height, ChromaFormat fmt, int bitDepth) +{ + m_grainCharacteristic = grainCharacteristics; + m_grainBuf = grainBuf; + + // Padding to make wd and ht multiple of max fgs window size(64) + int paddedWdFGS = ((width - 1) | 0x3F) + 1 - width; + int paddedHtFGS = ((height - 1) | 0x3F) + 1 - height; + m_padValue = (paddedWdFGS > paddedHtFGS) ? paddedWdFGS : paddedHtFGS; + + if (firstPictureInSequence) + { + // Create and initialize the Film Grain Synthesizer + m_grainCharacteristic->create(width, height, fmt, bitDepth, 1); + + // Frame level PelStorage buffer created to blend Film Grain Noise into it + m_grainBuf->create(chromaFormat, Area(0, 0, width, height), 0, m_padValue, 0, false); + + m_grainCharacteristic->fgsInit(); + } +} + +PelUnitBuf Picture::getDisplayBufFG(bool wrap) +{ + int payloadType = 0; + std::list<SEI *>::iterator message; + + for (message = SEIs.begin(); message != SEIs.end(); ++message) + { + payloadType = (*message)->payloadType(); + if (payloadType == SEI::FILM_GRAIN_CHARACTERISTICS) + { + m_grainCharacteristic->m_errorCode = -1; + *m_grainCharacteristic->m_fgcParameters = *static_cast<SEIFilmGrainCharacteristics *>(*message); + /* Validation of Film grain characteristic parameters for the constrains of SMPTE-RDD5*/ + m_grainCharacteristic->m_errorCode = m_grainCharacteristic->grainValidateParams(); + break; + } + } + + if (FGS_SUCCESS == m_grainCharacteristic->m_errorCode) + { + m_grainBuf->copyFrom(getRecoBuf()); + m_grainBuf->extendBorderPel(m_padValue); // Padding to make wd and ht multiple of max fgs window size(64) + + m_grainCharacteristic->m_poc = getPOC(); + m_grainCharacteristic->grainSynthesizeAndBlend(m_grainBuf, slices[0]->getIdrPicFlag()); + + return *m_grainBuf; + } + else + { + if (payloadType == SEI::FILM_GRAIN_CHARACTERISTICS) + { + msg(WARNING, "Film Grain synthesis is not performed. Error code: 0x%x \n", m_grainCharacteristic->m_errorCode); + } + return M_BUFS(scheduler.getSplitPicId(), wrap ? PIC_RECON_WRAP : PIC_RECONSTRUCTION); + } +} +#endif + void Picture::createColourTransfProcessor(bool firstPictureInSequence, SEIColourTransformApply* ctiCharacteristics, PelStorage* ctiBuf, int width, int height, ChromaFormat fmt, int bitDepth) { m_colourTranfParams = ctiCharacteristics; diff --git a/source/Lib/CommonLib/Picture.h b/source/Lib/CommonLib/Picture.h index 2b6c9d732806bf978afa1839ee3a1584d81781fc..f68a8959d144e3631cf306b4927c04d8a4ed3b63 100644 --- a/source/Lib/CommonLib/Picture.h +++ b/source/Lib/CommonLib/Picture.h @@ -50,7 +50,9 @@ #include "MCTS.h" #include "SEIColourTransform.h" #include <deque> - +#if JVET_X0048_X0103_FILM_GRAIN +#include "SEIFilmGrainSynthesizer.h" +#endif class SEI; class AQpLayer; @@ -64,11 +66,25 @@ struct Picture : public UnitArea uint32_t margin; Picture(); - void create( const ChromaFormat &_chromaFormat, const Size &size, const unsigned _maxCUSize, const unsigned margin, const bool bDecoder, const int layerId, const bool gopBasedTemporalFilterEnabled = false ); +#if JVET_X0048_X0103_FILM_GRAIN + void create(const ChromaFormat &_chromaFormat, const Size &size, const unsigned _maxCUSize, const unsigned margin, const bool bDecoder, const int layerId, const bool gopBasedTemporalFilterEnabled = false, const bool fgcSEIAnalysisEnabled = false); +#else + void create(const ChromaFormat &_chromaFormat, const Size &size, const unsigned _maxCUSize, const unsigned margin, const bool bDecoder, const int layerId, const bool gopBasedTemporalFilterEnabled = false); +#endif void destroy(); void createTempBuffers( const unsigned _maxCUSize ); void destroyTempBuffers(); + +#if JVET_X0048_X0103_FILM_GRAIN + int m_padValue; + bool m_isMctfFiltered; + SEIFilmGrainSynthesizer* m_grainCharacteristic; + PelStorage* m_grainBuf; + void createGrainSynthesizer(bool firstPictureInSequence, SEIFilmGrainSynthesizer* grainCharacteristics, PelStorage* grainBuf, int width, int height, ChromaFormat fmt, int bitDepth); + PelUnitBuf getDisplayBufFG (bool wrap = false); +#endif + SEIColourTransformApply* m_colourTranfParams; PelStorage* m_invColourTransfBuf; void createColourTransfProcessor(bool firstPictureInSequence, SEIColourTransformApply* ctiCharacteristics, PelStorage* ctiBuf, int width, int height, ChromaFormat fmt, int bitDepth); diff --git a/source/Lib/CommonLib/SEI.h b/source/Lib/CommonLib/SEI.h index 8cfdb46d354b09697e4e80ce8be674c29ecff0e4..932123e2a1192f73378f6ba34b137ec4e70614cf 100644 --- a/source/Lib/CommonLib/SEI.h +++ b/source/Lib/CommonLib/SEI.h @@ -801,6 +801,9 @@ public: { bool presentFlag; uint8_t numModelValues; +#if JVET_X0048_X0103_FILM_GRAIN + uint8_t numIntensityIntervals; +#endif std::vector<CompModelIntensityValues> intensityValues; }; diff --git a/source/Lib/CommonLib/SEIFilmGrainAnalyzer.cpp b/source/Lib/CommonLib/SEIFilmGrainAnalyzer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e9efe946928fdc71e2a78523467b43f2fc1a6d40 --- /dev/null +++ b/source/Lib/CommonLib/SEIFilmGrainAnalyzer.cpp @@ -0,0 +1,1873 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainAnalyzer.cpp + \brief SMPTE RDD5 based film grain analysis functionality from SEI messages +*/ + +#include "SEIFilmGrainAnalyzer.h" + +#if JVET_X0048_X0103_FILM_GRAIN + +// ==================================================================================================================== +// Edge detection - Canny +// ==================================================================================================================== +const int Canny::m_gx[3][3]{ { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } }; +const int Canny::m_gy[3][3]{ { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } }; + +const int Canny::m_gauss5x5[5][5]{ { 2, 4, 5, 4, 2 }, + { 4, 9, 12, 9, 4 }, + { 5, 12, 15, 12, 5 }, + { 4, 9, 12, 9, 4 }, + { 2, 4, 5, 4, 2 } }; + +Canny::Canny() +{ + // init(); +} + +Canny::~Canny() +{ + // uninit(); +} + +void Canny::gradient(PelStorage *buff1, PelStorage *buff2, unsigned int width, unsigned int height, + unsigned int convWidthS, unsigned int convHeightS, unsigned int bitDepth, ComponentID compID) +{ + /* + buff1 - magnitude; buff2 - orientation (Only luma in buff2) + */ + + // 360 degrees are split into the 8 equal parts; edge direction is quantized + const double edge_threshold_22_5 = 22.5; + const double edge_threshold_67_5 = 67.5; + const double edge_threshold_112_5 = 112.5; + const double edge_threshold_157_5 = 157.5; + + const int maxClpRange = (1 << bitDepth) - 1; + const int padding = convWidthS / 2; + + // tmp buffs + PelStorage tmpBuf1, tmpBuf2; + tmpBuf1.create(CHROMA_400, Area(0, 0, width, height)); + tmpBuf2.create(CHROMA_400, Area(0, 0, width, height)); + + buff1->get(compID).extendBorderPel(padding, padding); + + // Gx + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + int acc = 0; + for (int x = 0; x < convWidthS; x++) + { + for (int y = 0; y < convHeightS; y++) + { + acc += (buff1->get(compID).at(x - convWidthS / 2 + i, y - convHeightS / 2 + j) * m_gx[x][y]); + } + } + tmpBuf1.Y().at(i, j) = acc; + } + } + + // Gy + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + int acc = 0; + for (int x = 0; x < convWidthS; x++) + { + for (int y = 0; y < convHeightS; y++) + { + acc += (buff1->get(compID).at(x - convWidthS / 2 + i, y - convHeightS / 2 + j) * m_gy[x][y]); + } + } + tmpBuf2.Y().at(i, j) = acc; + } + } + + // magnitude + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + Pel tmp = (Pel)((abs(tmpBuf1.Y().at(i, j)) + abs(tmpBuf2.Y().at(i, j))) / 2); + buff1->get(compID).at(i, j) = (Pel) Clip3((Pel) 0, (Pel) maxClpRange, tmp); + } + } + + // edge direction - quantized edge directions + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + double theta = (atan2(tmpBuf1.Y().at(i, j), tmpBuf2.Y().at(i, j)) * 180) / PI; + + /* Convert actual edge direction to approximate value - quantize directions */ + if (((-edge_threshold_22_5 < theta) && (theta <= edge_threshold_22_5)) || ((edge_threshold_157_5 < theta) || (theta <= -edge_threshold_157_5))) + buff2->get(ComponentID(0)).at(i, j) = 0; + if (((-edge_threshold_157_5 < theta) && (theta <= -edge_threshold_112_5)) || ((edge_threshold_22_5 < theta) && (theta <= edge_threshold_67_5))) + buff2->get(ComponentID(0)).at(i, j) = 45; + if (((-edge_threshold_112_5 < theta) && (theta <= -edge_threshold_67_5)) || ((edge_threshold_67_5 < theta) && (theta <= edge_threshold_112_5))) + buff2->get(ComponentID(0)).at(i, j) = 90; + if (((-edge_threshold_67_5 < theta) && (theta <= -edge_threshold_22_5)) || ((edge_threshold_112_5 < theta) && (theta <= edge_threshold_157_5))) + buff2->get(ComponentID(0)).at(i, j) = 135; + } + } + + buff1->get(compID).extendBorderPel(padding, padding); // extend border for the next steps + tmpBuf1.destroy(); + tmpBuf2.destroy(); +} + +void Canny::suppressNonMax(PelStorage *buff1, PelStorage *buff2, unsigned int width, unsigned int height, + ComponentID compID) +{ + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + int rowShift = 0, colShift = 0; + + switch (buff2->get(ComponentID(0)).at(i, j)) + { + case 0: + rowShift = 1; + colShift = 0; + break; + case 45: + rowShift = 1; + colShift = 1; + break; + case 90: + rowShift = 0; + colShift = 1; + break; + case 135: + rowShift = -1; + colShift = 1; + break; + default: THROW("Unsupported gradient direction."); break; + } + + Pel pelCurrent = buff1->get(compID).at(i, j); + Pel pelEdgeDirectionTop = buff1->get(compID).at(i + rowShift, j + colShift); + Pel pelEdgeDirectionBottom = buff1->get(compID).at(i - rowShift, j - colShift); + if ((pelCurrent < pelEdgeDirectionTop) || (pelCurrent < pelEdgeDirectionBottom)) + { + buff2->get(ComponentID(0)).at(i, j) = 0; // supress + } + else + { + buff2->get(ComponentID(0)).at(i, j) = buff1->get(compID).at(i, j); // keep + } + } + } + buff1->get(compID).copyFrom(buff2->get(ComponentID(0))); +} + +void Canny::doubleThreshold(PelStorage *buff, unsigned int width, unsigned int height, + /*unsigned int windowSizeRatio,*/ unsigned int bitDepth, ComponentID compID) +{ + Pel strongPel = ((Pel) 1 << bitDepth) - 1; + Pel weekPel = ((Pel) 1 << (bitDepth - 1)) - 1; + + Pel highThreshold = 0; + Pel lowThreshold = strongPel; + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + highThreshold = std::max<Pel>(highThreshold, buff->get(compID).at(i, j)); + } + } + + // global low and high threshold + lowThreshold = (Pel)(m_lowThresholdRatio * highThreshold); + highThreshold = + Clip3(0, (1 << bitDepth) - 1, + m_highThresholdRatio * lowThreshold); // Canny recommended a upper:lower ratio between 2:1 and 3:1. + + // strong, week, supressed + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + if (buff->get(compID).at(i, j) > highThreshold) + buff->get(compID).at(i, j) = strongPel; + else if (buff->get(compID).at(i, j) <= highThreshold && buff->get(compID).at(i, j) > lowThreshold) + buff->get(compID).at(i, j) = weekPel; + else + buff->get(compID).at(i, j) = 0; + } + } + + buff->get(compID).extendBorderPel(1, 1); // extend one pixel on each side for the next step +} + +void Canny::edgeTracking(PelStorage *buff, unsigned int width, unsigned int height, unsigned int windowWidth, + unsigned int windowHeight, unsigned int bitDepth, ComponentID compID) +{ + Pel strongPel = ((Pel) 1 << bitDepth) - 1; + Pel weekPel = ((Pel) 1 << (bitDepth - 1)) - 1; + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + if (buff->get(compID).at(i, j) == weekPel) + { + bool strong = false; + + for (int x = 0; x < windowWidth; x++) + { + for (int y = 0; y < windowHeight; y++) + { + if (buff->get(compID).at(x - windowWidth / 2 + i, y - windowHeight / 2 + j) == strongPel) + { + strong = true; + break; + } + } + } + + if (strong) + { + buff->get(compID).at(i, j) = strongPel; + } + else + { + buff->get(compID).at(i, j) = 0; // supress + } + } + } + } +} + +void Canny::detect_edges(const PelStorage *orig, PelStorage *dest, unsigned int uiBitDepth, ComponentID compID) +{ + /* No noise reduction - Gaussian blur is skipped; + Gradient calculation; + Non-maximum suppression; + Double threshold; + Edge Tracking by Hysteresis.*/ + + unsigned int width = orig->get(compID).width, + height = orig->get(compID).height; // Width and Height of current frame + unsigned int convWidthS = m_convWidthS, + convHeightS = m_convHeightS; // Pixel's row and col positions for Sobel filtering + unsigned int bitDepth = uiBitDepth; + + // tmp buff + PelStorage orientationBuf; + orientationBuf.create(CHROMA_400, Area(0, 0, width, height)); + + dest->get(compID).copyFrom(orig->getBuf(compID)); // we skip blur in canny detector to catch as much as possible edges and textures + + /* Gradient calculation */ + gradient(dest, &orientationBuf, width, height, convWidthS, convHeightS, bitDepth, compID); + + /* Non - maximum suppression */ + suppressNonMax(dest, &orientationBuf, width, height, compID); + + /* Double threshold */ + doubleThreshold(dest, width, height, /*4,*/ bitDepth, compID); + + /* Edge Tracking by Hysteresis */ + edgeTracking(dest, width, height, convWidthS, convHeightS, bitDepth, compID); + + orientationBuf.destroy(); +} + +// ==================================================================================================================== +// Morphologigal operations - Dilation and Erosion +// ==================================================================================================================== +Morph::Morph() +{ + // init(); +} + +Morph::~Morph() +{ + // uninit(); +} + +int Morph::dilation(PelStorage *buff, unsigned int bitDepth, ComponentID compID, int numIter, int iter) +{ + if (iter == numIter) + return iter; + + unsigned int width = buff->get(compID).width, + height = buff->get(compID).height; // Width and Height of current frame + unsigned int windowSize = m_kernelSize; + unsigned int padding = windowSize / 2; + + Pel strongPel = ((Pel) 1 << bitDepth) - 1; + + PelStorage tmpBuf; + tmpBuf.create(CHROMA_400, Area(0, 0, width, height)); + tmpBuf.bufs[0].copyFrom(buff->get(compID)); + + buff->get(compID).extendBorderPel(padding, padding); + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + bool strong = false; + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + if (buff->get(compID).at(x - windowSize / 2 + i, y - windowSize / 2 + j) == strongPel) + { + strong = true; + break; + } + } + } + if (strong) + { + tmpBuf.get(ComponentID(0)).at(i, j) = strongPel; + } + } + } + + buff->get(compID).copyFrom(tmpBuf.bufs[0]); + tmpBuf.destroy(); + + iter++; + + iter = dilation(buff, bitDepth, compID, numIter, iter); + + return iter; +} + +int Morph::erosion(PelStorage *buff, unsigned int bitDepth, ComponentID compID, int numIter, int iter) +{ + if (iter == numIter) + return iter; + + unsigned int width = buff->get(compID).width, + height = buff->get(compID).height; // Width and Height of current frame + unsigned int windowSize = m_kernelSize; + unsigned int padding = windowSize / 2; + + PelStorage tmpBuf; + tmpBuf.create(CHROMA_400, Area(0, 0, width, height)); + tmpBuf.bufs[0].copyFrom(buff->get(compID)); + + buff->get(compID).extendBorderPel(padding, padding); + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + bool week = false; + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + if (buff->get(compID).at(x - windowSize / 2 + i, y - windowSize / 2 + j) == 0) + { + week = true; + break; + } + } + } + if (week) + { + tmpBuf.get(ComponentID(0)).at(i, j) = 0; + } + } + } + + buff->get(compID).copyFrom(tmpBuf.bufs[0]); + tmpBuf.destroy(); + + iter++; + + iter = erosion(buff, bitDepth, compID, numIter, iter); + + return iter; +} + +// ==================================================================================================================== +// Film Grain Analysis Functions +// ==================================================================================================================== +FGAnalyser::FGAnalyser() +{ + // init(); +} + +FGAnalyser::~FGAnalyser() +{ + // uninit(); +} + +// initialize film grain parameters +void FGAnalyser::init(const int width, const int height, const ChromaFormat inputChroma, + const BitDepths &inputBitDepths, const bool doAnalysis[]) +{ + m_log2ScaleFactor = 2; + for (int i = 0; i < MAX_NUM_COMPONENT; i++) + { + m_compModel[i].presentFlag = true; + m_compModel[i].numModelValues = 1; + m_compModel[i].numIntensityIntervals = 1; + m_compModel[i].intensityValues.resize(MAX_ALLOWED_COMP_MODEL_PAIRS); + for (int j = 0; j < MAX_ALLOWED_COMP_MODEL_PAIRS; j++) + { + m_compModel[i].intensityValues[j].intensityIntervalLowerBound = 10; + m_compModel[i].intensityValues[j].intensityIntervalUpperBound = 250; + m_compModel[i].intensityValues[j].compModelValue.resize(MAX_ALLOWED_MODEL_VALUES); + for (int k = 0; k < m_compModel[i].numModelValues; k++) + { + m_compModel[i].intensityValues[j].compModelValue[k] = 26 - 13 * (toChannelType((ComponentID) i)); // half intensity for chroma. Provided value is default value, manually tuned. + } + } + m_doAnalysis[i] = doAnalysis[i]; + } + + // initialize picture parameters and create buffers + m_chromaFormatIDC = inputChroma; + m_bitDepths = inputBitDepths; + + int margin = m_edgeDetector.m_convWidthG / 2; // set margin for padding for filtering + + if (!m_originalBuf) + { + m_originalBuf = new PelStorage; + m_originalBuf->create(inputChroma, Area(0, 0, width, height), 0, margin, 0, false); + } + if (!m_workingBuf) + { + m_workingBuf = new PelStorage; + m_workingBuf->create(inputChroma, Area(0, 0, width, height), 0, margin, 0, false); + } + if (!m_maskBuf) + { + m_maskBuf = new PelStorage; + m_maskBuf->create(inputChroma, Area(0, 0, width, height), 0, margin, 0, false); + } +} + +// initialize buffers with real data +void FGAnalyser::initBufs(Picture *pic) +{ + m_originalBuf->copyFrom(pic->getTrueOrigBuf()); // original is here + if (pic->m_isMctfFiltered) + { + m_workingBuf->copyFrom(pic->m_bufs[PIC_FILTERED_ORIGINAL_FG]); // filtered frame for film grain analysis is in here + } + else + { + THROW("ERROR: To estimate film grain parameters, you need to have filtered frame, e.g., MCTF filtered frame.\n"); + } +} + +// delete picture buffers +void FGAnalyser::destroy() +{ + if (m_originalBuf != nullptr) { + m_originalBuf->destroy(); + delete m_originalBuf; + m_originalBuf = nullptr; + } + if (m_workingBuf != nullptr) + { + m_workingBuf->destroy(); + delete m_workingBuf; + m_workingBuf = nullptr; + } + if (m_maskBuf != nullptr) + { + m_maskBuf->destroy(); + delete m_maskBuf; + m_maskBuf = nullptr; + } +} + +// main functions for film grain analysis +void FGAnalyser::estimate_grain(Picture *pic) +{ + // find mask + findMask(); + + // estimate parameters + estimate_grain_parameters(); +} + +// find flat and low complexity regions of the frame +void FGAnalyser::findMask() +{ + const int width = m_workingBuf->Y().width; + const int height = m_workingBuf->Y().height; + const int newWidth2 = m_workingBuf->Y().width / 2; + const int newHeight2 = m_workingBuf->Y().height / 2; + const int newWidth4 = m_workingBuf->Y().width / 4; + const int newHeight4 = m_workingBuf->Y().height / 4; + const unsigned padding = m_edgeDetector.m_convWidthG / 2; // for filtering + + // create tmp buffs + PelStorage *workingBufSubsampled2 = new PelStorage; + PelStorage *maskSubsampled2 = new PelStorage; + PelStorage *workingBufSubsampled4 = new PelStorage; + PelStorage *maskSubsampled4 = new PelStorage; + PelStorage *maskUpsampled = new PelStorage; + + workingBufSubsampled2->create(m_workingBuf->chromaFormat, Area(0, 0, newWidth2, newHeight2), 0, padding, 0, false); + maskSubsampled2->create(m_maskBuf->chromaFormat, Area(0, 0, newWidth2, newHeight2), 0, padding, 0, false); + workingBufSubsampled4->create(m_workingBuf->chromaFormat, Area(0, 0, newWidth4, newHeight4), 0, padding, 0, false); + maskSubsampled4->create(m_maskBuf->chromaFormat, Area(0, 0, newWidth4, newHeight4), 0, padding, 0, false); + maskUpsampled->create(m_maskBuf->chromaFormat, Area(0, 0, width, height), 0, padding, 0, false); + + for (int compIdx = 0; compIdx < getNumberValidComponents(m_chromaFormatIDC); compIdx++) + { + ComponentID compID = ComponentID(compIdx); + ChannelType channelId = toChannelType(compID); + int bitDepth = m_bitDepths[channelId]; + + if (!m_doAnalysis[compID]) + continue; + + // subsample original picture + subsample(*m_workingBuf, *workingBufSubsampled2, compID, 2, padding); + subsample(*m_workingBuf, *workingBufSubsampled4, compID, 4, padding); + + // full resolution + m_edgeDetector.detect_edges(m_workingBuf, m_maskBuf, bitDepth, compID); + suppressLowIntensity(*m_workingBuf, *m_maskBuf, bitDepth, compID); + m_morphOperation.dilation(m_maskBuf, bitDepth, compID, 1); + + // subsampled 2 + m_edgeDetector.detect_edges(workingBufSubsampled2, maskSubsampled2, bitDepth, compID); + suppressLowIntensity(*workingBufSubsampled2, *maskSubsampled2, bitDepth, compID); + m_morphOperation.dilation(maskSubsampled2, bitDepth, compID, 1); + + // upsample, combine maskBuf and maskUpsampled + upsample(*maskSubsampled2, *maskUpsampled, compID, 2); + combineMasks(*m_maskBuf, *maskUpsampled, compID); + + // subsampled 4 + m_edgeDetector.detect_edges(workingBufSubsampled4, maskSubsampled4, bitDepth, compID); + suppressLowIntensity(*workingBufSubsampled4, *maskSubsampled4, bitDepth, compID); + m_morphOperation.dilation(maskSubsampled4, bitDepth, compID, 1); + + // upsample, combine maskBuf and maskUpsampled + upsample(*maskSubsampled4, *maskUpsampled, compID, 4); + combineMasks(*m_maskBuf, *maskUpsampled, compID); + + // final dilation to fill the holes + same number of erosion + // m_morphOperation.erosion (maskBuf, bitDepth, compID, 1); + m_morphOperation.dilation(m_maskBuf, bitDepth, compID, 2); + m_morphOperation.erosion(m_maskBuf, bitDepth, compID, 1); + } + + workingBufSubsampled2->destroy(); + maskSubsampled2->destroy(); + workingBufSubsampled4->destroy(); + maskSubsampled4->destroy(); + maskUpsampled->destroy(); + + delete workingBufSubsampled2; + workingBufSubsampled2 = nullptr; + delete maskSubsampled2; + maskSubsampled2 = nullptr; + delete workingBufSubsampled4; + workingBufSubsampled4 = nullptr; + delete maskSubsampled4; + maskSubsampled4 = nullptr; + delete maskUpsampled; + maskUpsampled = nullptr; +} + +void FGAnalyser::suppressLowIntensity(const PelStorage &buff1, PelStorage &buff2, unsigned int bitDepth, + ComponentID compID) +{ + // buff1 - intensity values ( luma or chroma samples); buff2 - mask + + int width = buff2.get(compID).width; + int height = buff2.get(compID).height; + Pel maxIntensity = ((Pel) 1 << bitDepth) - 1; + Pel lowIntensityThreshold = (Pel)(m_lowIntensityRatio * maxIntensity); + + // strong, week, supressed + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + if (buff1.get(compID).at(i, j) < lowIntensityThreshold) + buff2.get(compID).at(i, j) = maxIntensity; + } + } +} + +void FGAnalyser::subsample(const PelStorage &input, PelStorage &output, ComponentID compID, const int factor, const int padding) const +{ + const int newWidth = input.get(compID).width / factor; + const int newHeight = input.get(compID).height / factor; + + const Pel *srcRow = input.get(compID).buf; + const int srcStride = input.get(compID).stride; + Pel * dstRow = output.get(compID).buf; // output is tmp buffer with only one component for binary mask + const int dstStride = output.get(compID).stride; + + for (int y = 0; y < newHeight; y++, srcRow += factor * srcStride, dstRow += dstStride) + { + const Pel *inRow = srcRow; + const Pel *inRowBelow = srcRow + srcStride; + Pel * target = dstRow; + + for (int x = 0; x < newWidth; x++) + { + target[x] = (inRow[0] + inRowBelow[0] + inRow[1] + inRowBelow[1] + 2) >> 2; + inRow += factor; + inRowBelow += factor; + } + } + + if (padding) + { + output.get(compID).extendBorderPel(padding, padding); + } +} + +void FGAnalyser::upsample(const PelStorage &input, PelStorage &output, ComponentID compID, const int factor, + const int padding) const +{ + // binary mask upsampling + // use simple replication of pixels + + const int width = input.get(compID).width; + const int height = input.get(compID).height; + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + Pel currentPel = input.get(compID).at(i, j); + + for (int x = 0; x < factor; x++) + { + for (int y = 0; y < factor; y++) + { + output.get(compID).at(i * factor + x, j * factor + y) = currentPel; + } + } + } + } + + if (padding) + { + output.get(compID).extendBorderPel(padding, padding); + } +} + +void FGAnalyser::combineMasks(PelStorage &buff1, PelStorage &buff2, ComponentID compID) +{ + const int width = buff1.get(compID).width; + const int height = buff1.get(compID).height; + + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + buff1.get(compID).at(i, j) = (buff1.get(compID).at(i, j) | buff2.get(compID).at(i, j)); + } + } +} + +int FGAnalyser::denoise(Picture *pic) +{ + if (pic->m_isMctfFiltered) + return 0; + + // add custom denoising algorithm + return 1; +} + +// estimate cut-off frequencies and scaling factors for different intensity intervals +void FGAnalyser::estimate_grain_parameters() +{ + PelStorage *tmpBuff = new PelStorage; // tmpBuff is diference between original and filtered => film grain estimate + tmpBuff->create(m_workingBuf->chromaFormat, Area(0, 0, m_workingBuf->Y().width, m_workingBuf->Y().height), 0, 0, 0, false); + tmpBuff->copyFrom(*m_workingBuf); // workingBuf is filtered image + tmpBuff->subtract(*m_originalBuf); // find difference between original and filtered/reconstructed frame => film grain estimate + + int blockSize = BLK_8; + uint32_t picSizeInLumaSamples = m_workingBuf->Y().height * m_workingBuf->Y().width; + if (picSizeInLumaSamples <= (1920 * 1080)) + { + blockSize = BLK_8; + } + else if (picSizeInLumaSamples <= (3840 * 2160)) + { + blockSize = BLK_16; + } + else + { + blockSize = BLK_32; + } + + for (int compIdx = 0; compIdx < getNumberValidComponents(m_chromaFormatIDC); compIdx++) + { // loop over components + ComponentID compID = ComponentID(compIdx); + ChannelType channelId = toChannelType(compID); + + if (!m_doAnalysis[compID]) + continue; + + unsigned int width = m_workingBuf->getBuf(compID).width; // Width of current frame + unsigned int height = m_workingBuf->getBuf(compID).height; // Height of current frame + unsigned int windowSize = DATA_BASE_SIZE; // Size for Film Grain block + int bitDepth = m_bitDepths[channelId]; + int detect_edges = 0; + int mean = 0; + int var = 0; + + std::vector<int> vec_mean; + std::vector<int> vec_var; + std::vector<PelMatrix> squared_dct_grain_block_list; + + for (int i = 0; i <= width - windowSize; i += windowSize) + { // loop over windowSize x windowSize blocks + for (int j = 0; j <= height - windowSize; j += windowSize) + { + detect_edges = count_edges(*m_maskBuf, windowSize, compID, i, j); // for flat region without edges + + if (detect_edges) // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance. + { + // find transformed blocks; cut-off frequency estimation is done on 64 x 64 blocks as low-pass filtering on synthesis side is done on 64 x 64 blocks. + block_transform(*tmpBuff, squared_dct_grain_block_list, i, j, bitDepth, compID); + } + + int step = windowSize / blockSize; + for (int k = 0; k < step; k++) + { + for (int m = 0; m < step; m++) + { + detect_edges = count_edges(*m_maskBuf, blockSize, compID, i + k * blockSize, j + m * blockSize); // for flat region without edges + + if (detect_edges) // selection of uniform, flat and low-complexity area; extend to other features, e.g., variance. + { + // collect all data for parameter estimation; mean and variance are caluclated on blockSize x blockSize blocks + mean = meanVar(*m_workingBuf, blockSize, compID, i + k * blockSize, j + m * blockSize, false); + var = meanVar(*tmpBuff, blockSize, compID, i + k * blockSize, j + m * blockSize, true); + + if (var < (MAX_REAL_SCALE << (bitDepth - BIT_DEPTH_8))) // limit data points to meaningful values. higher variance can be result of not perfect mask estimation (non-flat regions fall in estimation process) + { + vec_mean.push_back(mean); // mean of the filtered frame + vec_var.push_back(var); // variance of the film grain estimate + } + } + } + } + } + } + + // calculate film grain parameters + estimate_cutoff_freq(squared_dct_grain_block_list, compID); + estimate_scaling_factors(vec_mean, vec_var, bitDepth, compID); + } + + tmpBuff->destroy(); + delete tmpBuff; + tmpBuff = nullptr; +} + +// find compModelValue[0] - different scaling based on the pixel intensity +void FGAnalyser::estimate_scaling_factors(std::vector<int> &data_x, std::vector<int> &data_y, unsigned int bitDepth, ComponentID compID) +{ + if (!m_compModel[compID].presentFlag || data_x.size() < MIN_POINTS_FOR_INTENSITY_ESTIMATION) // if cutoff frequencies are not estimated previously, do not proceed since presentFlag is set to false in a previous step + return; // also if there is no enough points to estimate film grain intensities, default or previously estimated parameters are used + + // estimate intensity regions + std::vector<double> coeffs; + std::vector<double> scalingVec; + std::vector<int> quantVec; + double distortion = 0.0; + + // Fit the points with the curve. Quantization of the curve using Lloyd Max quantization. + bool valid; + int num_passes = 2; + for (int i = 0; i < num_passes; i++) // if num_passes = 2, filtering of the dataset points is performed in the second step + { + valid = fit_function(data_x, data_y, coeffs, scalingVec, ORDER, bitDepth, i); // n-th order polynomial regression for scaling function estimation + if (!valid) + break; + } + if (valid) + { + avg_scaling_vec(scalingVec, compID, bitDepth); // scale with previously fitted function to smooth the intensity + valid = lloyd_max(scalingVec, quantVec, distortion, QUANT_LEVELS, bitDepth); // train quantizer and quantize curve using Lloyd Max + } + + // Based on quantized intervals, set intensity region and scaling parameter + if (valid) // if not valid, reuse previous parameters (for example, if var is all zero) + setEstimatedParameters(quantVec, bitDepth, compID); +} + +// Horizontal and Vertical cutoff frequencies estimation. Assumption is that for complete sequence there is only one set of the cut-off frequencies (implementation decision) +void FGAnalyser::estimate_cutoff_freq(const std::vector<PelMatrix> &blocks, ComponentID compID) +{ + PelMatrixDouble mean_squared_dct_grain(DATA_BASE_SIZE, std::vector<double>(DATA_BASE_SIZE)); + vector<double> vec_mean_dct_grain_row(DATA_BASE_SIZE, 0.0); + vector<double> vec_mean_dct_grain_col(DATA_BASE_SIZE, 0.0); + static bool isFirstCutoffEst[MAX_NUM_COMPONENT] = {true, true, true }; + + int num_blocks = (int) blocks.size(); + if (num_blocks < MIN_BLOCKS_FOR_CUTOFF_ESTIMATION) // if there is no enough 64 x 64 blocks to estimate cut-off freq, skip cut-off freq estimation and use previous parameters + return; + + // iterate over the block and find avarage block + for (int x = 0; x < DATA_BASE_SIZE; x++) + { + for (int y = 0; y < DATA_BASE_SIZE; y++) + { + for (const auto &dct_grain_block: blocks) + { + mean_squared_dct_grain[x][y] += dct_grain_block[x][y]; + } + mean_squared_dct_grain[x][y] /= num_blocks; + + // Computation of horizontal and vertical mean vector (DC component is skipped) + vec_mean_dct_grain_row[x] += ((x != 0) && (y != 0)) ? mean_squared_dct_grain[x][y] : 0.0; + vec_mean_dct_grain_col[y] += ((x != 0) && (y != 0)) ? mean_squared_dct_grain[x][y] : 0.0; + } + } + + for (int x = 0; x < DATA_BASE_SIZE; x++) + { + vec_mean_dct_grain_row[x] /= (x == 0) ? DATA_BASE_SIZE - 1 : DATA_BASE_SIZE; + vec_mean_dct_grain_col[x] /= (x == 0) ? DATA_BASE_SIZE - 1 : DATA_BASE_SIZE; + } + + int cutoff_vertical = cutoff_frequency(vec_mean_dct_grain_row); + int cutoff_horizontal = cutoff_frequency(vec_mean_dct_grain_col); + + if (cutoff_vertical && cutoff_horizontal) + { + m_compModel[compID].presentFlag = true; + m_compModel[compID].numModelValues = 1; + } + else + { + m_compModel[compID].presentFlag = false; + } + + if (m_compModel[compID].presentFlag) + { + if (isFirstCutoffEst[compID]) // to avoid averaging with default + { + m_compModel[compID].intensityValues[0].compModelValue[1] = cutoff_horizontal; + m_compModel[compID].intensityValues[0].compModelValue[2] = cutoff_vertical; + isFirstCutoffEst[compID] = false; + } + else + { + m_compModel[compID].intensityValues[0].compModelValue[1] = (m_compModel[compID].intensityValues[0].compModelValue[1] + cutoff_horizontal + 1) / 2; + m_compModel[compID].intensityValues[0].compModelValue[2] = (m_compModel[compID].intensityValues[0].compModelValue[2] + cutoff_vertical + 1) / 2; + } + + if (m_compModel[compID].intensityValues[0].compModelValue[1] != 8 || m_compModel[compID].intensityValues[0].compModelValue[2] != 8) // default is 8 + m_compModel[compID].numModelValues++; + + if (m_compModel[compID].intensityValues[0].compModelValue[1] != m_compModel[compID].intensityValues[0].compModelValue[2]) + m_compModel[compID].numModelValues++; + } +} + +int FGAnalyser::cutoff_frequency(std::vector<double> &mean) +{ + std::vector<double> sum(DATA_BASE_SIZE, 0.0); + + // Regularize the curve to suppress peaks + mean.push_back(mean.back()); + mean.insert(mean.begin(), mean.front()); + for (int j = 1; j < DATA_BASE_SIZE + 1; j++) + { + sum[j - 1] = (m_tap_filtar[0] * mean[j - 1] + m_tap_filtar[1] * mean[j] + m_tap_filtar[2] * mean[j + 1]) / m_normTap; + } + + double target = 0; + for (int j = 0; j < DATA_BASE_SIZE; j++) + target += sum[j]; + target /= DATA_BASE_SIZE; + + // find final cut-off frequency + std::vector<int> intersectionPointList; + + for (int x = 0; x < DATA_BASE_SIZE - 1; x++) + { + if ((target < sum[x] && target >= sum[x + 1]) || (target > sum[x] && target <= sum[x + 1])) + { // there is intersection + double first_point = fabs(target - sum[x]); + double second_point = fabs(target - sum[x + 1]); + if (first_point < second_point) + { + intersectionPointList.push_back(x); + } + else + { + intersectionPointList.push_back(x + 1); + } + } + } + + int size = (int) intersectionPointList.size(); + + if (size > 0) + { + return Clip3<int>(2, 14, (intersectionPointList[size - 1] - 1) >> 2); // clip to RDD5 range, (h-3)/4 + 0.5 + } + else + { + return 0; + } +} + +// DCT-2 64x64 as defined in VVC +void FGAnalyser::block_transform(const PelStorage &buff, std::vector<PelMatrix> &squared_dct_grain_block_list, + int offsetX, int offsetY, unsigned int bitDepth, ComponentID compID) +{ + unsigned int windowSize = DATA_BASE_SIZE; // Size for Film Grain block + Intermediate_Int max_dynamic_range = (1 << (bitDepth + 6)) - 1; // Dynamic range after DCT transform for 64x64 block + Intermediate_Int min_dynamic_range = -((1 << (bitDepth + 6)) - 1); + Intermediate_Int sum; + + const TMatrixCoeff *tmp = g_trCoreDCT2P64[TRANSFORM_FORWARD][0]; + const int transform_scale = 9; // upscaling of original transform as specified in VVC (for 64x64 block) + const int add_1st = 1 << (transform_scale - 1); + + TMatrixCoeff tr[DATA_BASE_SIZE][DATA_BASE_SIZE]; // Original + TMatrixCoeff trt[DATA_BASE_SIZE][DATA_BASE_SIZE]; // Transpose + for (int x = 0; x < DATA_BASE_SIZE; x++) + { + for (int y = 0; y < DATA_BASE_SIZE; y++) + { + tr[x][y] = tmp[x * 64 + y]; /* Matrix Original */ + trt[y][x] = tmp[x * 64 + y]; /* Matrix Transpose */ + } + } + + // DCT transform + PelMatrix blockDCT(windowSize, std::vector<Intermediate_Int>(windowSize)); + PelMatrix blockTmp(windowSize, std::vector<Intermediate_Int>(windowSize)); + + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + sum = 0; + for (int k = 0; k < windowSize; k++) + { + sum += tr[x][k] * buff.get(compID).at(offsetX + k, offsetY + y); + } + blockTmp[x][y] = (sum + add_1st) >> transform_scale; + } + } + + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + sum = 0; + for (int k = 0; k < windowSize; k++) + { + sum += blockTmp[x][k] * trt[k][y]; + } + blockDCT[x][y] = Clip3(min_dynamic_range, max_dynamic_range, (sum + add_1st) >> transform_scale); + } + } + + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + blockDCT[x][y] = blockDCT[x][y] * blockDCT[x][y]; + } + } + + // store squared transformed block for further analysis + squared_dct_grain_block_list.push_back(blockDCT); +} + +// check edges +int FGAnalyser::count_edges(PelStorage &buffer, int windowSize, ComponentID compID, int offsetX, int offsetY) +{ + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + if (buffer.get(compID).at(offsetX + x, offsetY + y)) + { + return 0; + } + } + } + + return 1; +} + +// calulate mean and variance for windowSize x windowSize block +int FGAnalyser::meanVar(PelStorage &buffer, int windowSize, ComponentID compID, int offsetX, int offsetY, bool getVar) +{ + int m = 0, v = 0; + int log2WindowsSize = floorLog2(windowSize); + + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + m += buffer.get(compID).at(offsetX + x, offsetY + y); + } + } + m = (m + (1 << (log2WindowsSize + log2WindowsSize - 1))) >> (log2WindowsSize + log2WindowsSize); + + if (getVar) + { + for (int x = 0; x < windowSize; x++) + { + for (int y = 0; y < windowSize; y++) + { + v += + (buffer.get(compID).at(offsetX + x, offsetY + y) - m) * (buffer.get(compID).at(offsetX + x, offsetY + y) - m); + } + } + v = (v + (1 << (log2WindowsSize + log2WindowsSize - 1))) >> (log2WindowsSize + log2WindowsSize); + return v; + } + else + { + return m; + } +} + +// Fit data to a function using n-th order polynomial interpolation +bool FGAnalyser::fit_function(std::vector<int> &data_x, std::vector<int> &data_y, std::vector<double> &coeffs, + std::vector<double> &scalingVec, int order, int bitDepth, bool second_pass) +{ + PelMatrixLongDouble a(MAXPAIRS + 1, std::vector<long double>(MAXPAIRS + 1)); + PelVectorLongDouble B(MAXPAIRS + 1), C(MAXPAIRS + 1), S(MAXPAIRS + 1); + long double A1, A2, Y1, m, S1, x1; + long double xscale, yscale; + long double xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0; + long double polycoefs[MAXORDER + 1]; + + int i, j, k, L, R; + + // several data filtering and data manipulations before fitting the function + // create interval points for function fitting + int INTENSITY_INTERVAL_NUMBER = (1 << bitDepth) / INTERVAL_SIZE; + std::vector<int> vec_mean_intensity(INTENSITY_INTERVAL_NUMBER, 0); + std::vector<int> vec_variance_intensity(INTENSITY_INTERVAL_NUMBER, 0); + std::vector<int> element_number_per_interval(INTENSITY_INTERVAL_NUMBER, 0); + std::vector<int> tmp_data_x; + std::vector<int> tmp_data_y; + double mn = 0.0, sd = 0.0; + + if (second_pass) // in second pass, filter based on the variance of the data_y. remove all high and low points + { + xmin = scalingVec.back(); + scalingVec.pop_back(); + xmax = scalingVec.back(); + scalingVec.pop_back(); + int n = (int) data_y.size(); + if (n != 0) + { + mn = accumulate(data_y.begin(), data_y.end(), 0.0) / n; + for (int cnt = 0; cnt < n; cnt++) + { + sd += (data_y[cnt] - mn) * (data_y[cnt] - mn); + } + sd /= n; + sd = sqrt(sd); + } + } + + for (int cnt = 0; cnt < data_x.size(); cnt++) + { + if (second_pass) + { + if (data_x[cnt] >= xmin && data_x[cnt] <= xmax) + { + if ((data_y[cnt] < scalingVec[data_x[cnt] - (int) xmin] + sd * VAR_SCALE_UP) && (data_y[cnt] > scalingVec[data_x[cnt] - (int) xmin] - sd * VAR_SCALE_DOWN)) + { + int block_index = data_x[cnt] / INTERVAL_SIZE; + vec_mean_intensity[block_index] += data_x[cnt]; + vec_variance_intensity[block_index] += data_y[cnt]; + element_number_per_interval[block_index]++; + } + } + } + else + { + int block_index = data_x[cnt] / INTERVAL_SIZE; + vec_mean_intensity[block_index] += data_x[cnt]; + vec_variance_intensity[block_index] += data_y[cnt]; + element_number_per_interval[block_index]++; + } + } + + // create a points per intensity interval + for (int block_idx = 0; block_idx < INTENSITY_INTERVAL_NUMBER; block_idx++) + { + if (element_number_per_interval[block_idx] >= MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL) + { + tmp_data_x.push_back(vec_mean_intensity[block_idx] / element_number_per_interval[block_idx]); + tmp_data_y.push_back(vec_variance_intensity[block_idx] / element_number_per_interval[block_idx]); + } + } + + // There needs to be at least ORDER+1 points to fit the function + if (tmp_data_x.size() < (order + 1)) + return false; // if there is no enough blocks to estimate film grain parameters, default or previously estimated parameters are used + + if (second_pass) + extend_points(tmp_data_x, tmp_data_y, bitDepth); // find the most left and the most right point, and extend edges + + CHECK(tmp_data_x.size() > MAXPAIRS, "Maximum dataset size exceeded."); + + // fitting the function starts here + xmin = tmp_data_x[0]; + xmax = tmp_data_x[0]; + ymin = tmp_data_y[0]; + ymax = tmp_data_y[0]; + for (i = 0; i < tmp_data_x.size(); i++) + { + if (tmp_data_x[i] < xmin) + xmin = tmp_data_x[i]; + if (tmp_data_x[i] > xmax) + xmax = tmp_data_x[i]; + if (tmp_data_y[i] < ymin) + ymin = tmp_data_y[i]; + if (tmp_data_y[i] > ymax) + ymax = tmp_data_y[i]; + } + + long double xlow = xmax; + long double ylow = ymax; + + int data_pairs = (int) tmp_data_x.size(); + + PelMatrixDouble data_array(2, std::vector<double>(MAXPAIRS + 1)); + + for (i = 0; i < data_pairs; i++) + { + data_array[0][i + 1] = (double) tmp_data_x[i]; + data_array[1][i + 1] = (double) tmp_data_y[i]; + } + + // release memory for data_x and data_y, and clear previous vectors + vector<int>().swap(tmp_data_x); + vector<int>().swap(tmp_data_y); + if (second_pass) + { + vector<int>().swap(data_x); + vector<int>().swap(data_y); + vector<double>().swap(coeffs); + vector<double>().swap(scalingVec); + } + + for (i = 1; i <= data_pairs; i++) + { + if (data_array[0][i] < xlow && data_array[0][i] != 0) + xlow = data_array[0][i]; + if (data_array[1][i] < ylow && data_array[1][i] != 0) + ylow = data_array[1][i]; + } + + if (xlow < .001 && xmax < 1000) + xscale = 1 / xlow; + else if (xmax > 1000 && xlow > .001) + xscale = 1 / xmax; + else + xscale = 1; + + if (ylow < .001 && ymax < 1000) + yscale = 1 / ylow; + else if (ymax > 1000 && ylow > .001) + yscale = 1 / ymax; + else + yscale = 1; + + // initialise array variables + for (i = 0; i <= MAXPAIRS; i++) + { + B[i] = 0; + C[i] = 0; + S[i] = 0; + for (j = 0; j < MAXPAIRS; j++) + a[i][j] = 0; + } + + for (i = 0; i <= MAXORDER; i++) + polycoefs[i] = 0; + + Y1 = 0; + for (j = 1; j <= data_pairs; j++) + { + for (i = 1; i <= order; i++) + { + B[i] = B[i] + data_array[1][j] * yscale * ldpow(data_array[0][j] * xscale, i); + if (B[i] == std::numeric_limits<long double>::max()) + return false; + for (k = 1; k <= order; k++) + { + a[i][k] = a[i][k] + ldpow(data_array[0][j] * xscale, (i + k)); + if (a[i][k] == std::numeric_limits<long double>::max()) + return false; + } + S[i] = S[i] + ldpow(data_array[0][j] * xscale, i); + if (S[i] == std::numeric_limits<long double>::max()) + return false; + } + Y1 = Y1 + data_array[1][j] * yscale; + if (Y1 == std::numeric_limits<long double>::max()) + return false; + } + + for (i = 1; i <= order; i++) + { + for (j = 1; j <= order; j++) + { + a[i][j] = a[i][j] - S[i] * S[j] / (long double) data_pairs; + if (a[i][j] == std::numeric_limits<long double>::max()) + return false; + } + B[i] = B[i] - Y1 * S[i] / (long double) data_pairs; + if (B[i] == std::numeric_limits<long double>::max()) + return false; + } + + for (k = 1; k <= order; k++) + { + R = k; + A1 = 0; + for (L = k; L <= order; L++) + { + A2 = fabsl(a[L][k]); + if (A2 > A1) + { + A1 = A2; + R = L; + } + } + if (A1 == 0) + return false; + if (R == k) + goto polfit1; + for (j = k; j <= order; j++) + { + x1 = a[R][j]; + a[R][j] = a[k][j]; + a[k][j] = x1; + } + x1 = B[R]; + B[R] = B[k]; + B[k] = x1; + polfit1: + for (i = k; i <= order; i++) + { + m = a[i][k]; + for (j = k; j <= order; j++) + { + if (i == k) + a[i][j] = a[i][j] / m; + else + a[i][j] = a[i][j] - m * a[k][j]; + } + if (i == k) + B[i] = B[i] / m; + else + B[i] = B[i] - m * B[k]; + } + } + + polycoefs[order] = B[order]; + for (k = 1; k <= order - 1; k++) + { + i = order - k; + S1 = 0; + for (j = 1; j <= order; j++) + { + S1 = S1 + a[i][j] * polycoefs[j]; + if (S1 == std::numeric_limits<long double>::max()) + return false; + } + polycoefs[i] = B[i] - S1; + } + + S1 = 0; + for (i = 1; i <= order; i++) + { + S1 = S1 + polycoefs[i] * S[i] / (long double) data_pairs; + if (S1 == std::numeric_limits<long double>::max()) + return false; + } + polycoefs[0] = (Y1 / (long double) data_pairs - S1); + + // zero all coeficient values smaller than +/- .00000000001 (avoids -0) + for (i = 0; i <= order; i++) + if (fabsl(polycoefs[i] * 100000000000) < 1) + polycoefs[i] = 0; + + // rescale parameters + for (i = 0; i <= order; i++) + { + polycoefs[i] = (1 / yscale) * polycoefs[i] * ldpow(xscale, i); + coeffs.push_back(polycoefs[i]); + } + + // create fg scaling function. interpolation based on coeffs which returns lookup table from 0 - 2^B-1. n-th order polinomial regression + for (i = (int) xmin; i <= (int) xmax; i++) + { + double val = coeffs[0]; + for (j = 1; j < coeffs.size(); j++) + { + val += (coeffs[j] * ldpow(i, j)); + } + + val = Clip3(0.0, (double) (1 << bitDepth) - 1, val); + scalingVec.push_back(val); + } + + // save in scalingVec min and max value for further use + scalingVec.push_back(xmax); + scalingVec.push_back(xmin); + + return true; +} + +// avg scaling vector with previous result to smooth transition betweeen frames +void FGAnalyser::avg_scaling_vec(std::vector<double> &scalingVec, ComponentID compID, int bitDepth) +{ + int xmin = (int) scalingVec.back(); + scalingVec.pop_back(); + int xmax = (int) scalingVec.back(); + scalingVec.pop_back(); + + static std::vector<std::vector<double>> scalingVecAvg(MAX_NUM_COMPONENT, std::vector<double>((int)(1<<bitDepth))); + static bool isFirstScalingEst[MAX_NUM_COMPONENT] = { true, true, true }; + + if (isFirstScalingEst[compID]) + { + for (int i = xmin; i <= xmax; i++) + scalingVecAvg[compID][i] = scalingVec[i-xmin]; + + isFirstScalingEst[compID] = false; + } + else + { + for (int i = 0; i < scalingVec.size(); i++) + scalingVecAvg[compID][i + xmin] += scalingVec[i]; + for (int i = 0; i < scalingVecAvg[compID].size(); i++) + scalingVecAvg[compID][i] /= 2; + } + + // re-init scaling vec and add new min and max to be used in other functions + int index = 0; + for (; index < scalingVecAvg[compID].size(); index++) + if (scalingVecAvg[compID][index]) + break; + xmin = index; + + index = (int) scalingVecAvg[compID].size() - 1; + for (; index >=0 ; index--) + if (scalingVecAvg[compID][index]) + break; + xmax = index; + + scalingVec.resize(xmax - xmin + 1); + for (int i = xmin; i <= xmax; i++) + scalingVec[i-xmin] = scalingVecAvg[compID][i]; + + scalingVec.push_back(xmax); + scalingVec.push_back(xmin); +} + +// Lloyd Max quantizer +bool FGAnalyser::lloyd_max(std::vector<double> &scalingVec, std::vector<int> &quantizedVec, double &distortion, int numQuantizedLevels, int bitDepth) +{ + CHECK(scalingVec.size() <= 0, "Empty training dataset."); + + int xmin = (int) scalingVec.back(); + scalingVec.pop_back(); + scalingVec.pop_back(); // dummy pop_pack ==> int xmax = (int)scalingVec.back(); + + double ymin = 0.0; + double ymax = 0.0; + double init_training = 0.0; + double tolerance = 0.0000001; + double last_distor = 0.0; + double rel_distor = 0.0; + + std::vector<double> codebook(numQuantizedLevels); + std::vector<double> partition(numQuantizedLevels - 1); + + std::vector<double> tmpVec(scalingVec.size(), 0.0); + distortion = 0.0; + + ymin = scalingVec[0]; + ymax = scalingVec[0]; + for (int i = 0; i < scalingVec.size(); i++) + { + if (scalingVec[i] < ymin) + ymin = scalingVec[i]; + if (scalingVec[i] > ymax) + ymax = scalingVec[i]; + } + + init_training = (ymax - ymin) / numQuantizedLevels; + + if (init_training <= 0) + { + // msg(WARNING, "Invalid training dataset. Film grain parameter estimation is not performed. Default or previously estimated parameters are reused.\n"); + return false; + } + + // initial codebook + double step = init_training / 2; + for (int i = 0; i < numQuantizedLevels; i++) + { + codebook[i] = ymin + i * init_training + step; + } + + // initial partition + for (int i = 0; i < numQuantizedLevels - 1; i++) + { + partition[i] = (codebook[i] + codebook[i + 1]) / 2; + } + + // quantizer initialization + quantize(scalingVec, tmpVec, distortion, partition, codebook); + + double tolerance2 = std::numeric_limits<double>::epsilon() * ymax; + if (distortion > tolerance2) + rel_distor = abs(distortion - last_distor) / distortion; + else + rel_distor = distortion; + + // optimization: find optimal codebook and partition + while ((rel_distor > tolerance) && (rel_distor > tolerance2)) + { + for (int i = 0; i < numQuantizedLevels; i++) + { + int count = 0; + double sum = 0.0; + + for (int j = 0; j < tmpVec.size(); j++) + { + if (codebook[i] == tmpVec[j]) + { + count++; + sum += scalingVec[j]; + } + } + + if (count) + { + codebook[i] = sum / (double) count; + } + else + { + sum = 0.0; + count = 0; + if (i == 0) + { + for (int j = 0; j < tmpVec.size(); j++) + { + if (scalingVec[j] <= partition[i]) + { + count++; + sum += scalingVec[j]; + } + } + if (count) + { + codebook[i] = sum / (double) count; + } + else + { + codebook[i] = (partition[i] + ymin) / 2; + } + } + else if (i == numQuantizedLevels - 1) + { + for (int j = 0; j < tmpVec.size(); j++) + { + if (scalingVec[j] >= partition[i - 1]) + { + count++; + sum += scalingVec[j]; + } + } + if (count) + { + codebook[i] = sum / (double) count; + } + else + { + codebook[i] = (partition[i - 1] + ymax) / 2; + } + } + else + { + for (int j = 0; j < tmpVec.size(); j++) + { + if (scalingVec[j] >= partition[i - 1] && scalingVec[j] <= partition[i]) + { + count++; + sum += scalingVec[j]; + } + } + if (count) + { + codebook[i] = sum / (double) count; + } + else + { + codebook[i] = (partition[i - 1] + partition[i]) / 2; + } + } + } + } + + // compute and sort partition + for (int i = 0; i < numQuantizedLevels - 1; i++) + { + partition[i] = (codebook[i] + codebook[i + 1]) / 2; + } + std::sort(partition.begin(), partition.end()); + + // final quantization - testing condition + last_distor = distortion; + quantize(scalingVec, tmpVec, distortion, partition, codebook); + + if (distortion > tolerance2) + rel_distor = abs(distortion - last_distor) / distortion; + else + rel_distor = distortion; + } + + // fill the final quantized vector + quantizedVec.resize((int) (1 << bitDepth), 0); + for (int i = 0; i < tmpVec.size(); i++) + quantizedVec[i + xmin] = Clip3(0, MAX_STANDARD_DEVIATION << (bitDepth - BIT_DEPTH_8), (int) (tmpVec[i] + .5)); + + return true; +} + +void FGAnalyser::quantize(std::vector<double> &scalingVec, std::vector<double> &quantizedVec, double &distortion, + std::vector<double> partition, std::vector<double> codebook) +{ + CHECK(partition.size() <= 0 || codebook.size() <= 0, "Check partitions and codebook."); + + // reset previous quantizedVec to 0 and distortion to 0 + std::fill(quantizedVec.begin(), quantizedVec.end(), 0.0); + distortion = 0.0; + + // quantize input vector + for (int i = 0; i < scalingVec.size(); i++) + { + for (int j = 0; j < partition.size(); j++) + { + quantizedVec[i] = + quantizedVec[i] + (scalingVec[i] > partition[j]); // partition need to be sorted in acceding order + } + quantizedVec[i] = codebook[(int) quantizedVec[i]]; + } + + // compute distortion - mse + for (int i = 0; i < scalingVec.size(); i++) + { + distortion += ((scalingVec[i] - quantizedVec[i]) * (scalingVec[i] - quantizedVec[i])); + } + distortion /= scalingVec.size(); +} + +// Set correctlly SEI parameters based on the quantized curve +void FGAnalyser::setEstimatedParameters(std::vector<int> &quantizedVec, unsigned int bitDepth, ComponentID compID) +{ + std::vector<std::vector<int>> finalIntervalsandScalingFactors(3); // lower_bound, upper_bound, scaling_factor + + int cutoff_horizontal = m_compModel[compID].intensityValues[0].compModelValue[1]; + int cutoff_vertical = m_compModel[compID].intensityValues[0].compModelValue[2]; + + // calculate intervals and scaling factors + define_intervals_and_scalings(finalIntervalsandScalingFactors, quantizedVec, bitDepth); + + // merge small intervals with left or right interval + for (int i = 0; i < finalIntervalsandScalingFactors[2].size(); i++) + { + int tmp1 = finalIntervalsandScalingFactors[1][i] - finalIntervalsandScalingFactors[0][i]; + + if (tmp1 < (2 << (bitDepth - BIT_DEPTH_8))) + { + int diffRight = + (i == (finalIntervalsandScalingFactors[2].size() - 1)) || (finalIntervalsandScalingFactors[2][i + 1] == 0) + ? std::numeric_limits<int>::max() + : abs(finalIntervalsandScalingFactors[2][i] - finalIntervalsandScalingFactors[2][i + 1]); + int diffLeft = (i == 0) || (finalIntervalsandScalingFactors[2][i - 1] == 0) + ? std::numeric_limits<int>::max() + : abs(finalIntervalsandScalingFactors[2][i] - finalIntervalsandScalingFactors[2][i - 1]); + + if (diffLeft < diffRight) // merge with left + { + int tmp2 = finalIntervalsandScalingFactors[1][i - 1] - finalIntervalsandScalingFactors[0][i - 1]; + int newScale = (tmp2 * finalIntervalsandScalingFactors[2][i - 1] + tmp1 * finalIntervalsandScalingFactors[2][i]) / (tmp2 + tmp1); + + finalIntervalsandScalingFactors[1][i - 1] = finalIntervalsandScalingFactors[1][i]; + finalIntervalsandScalingFactors[2][i - 1] = newScale; + for (int j = 0; j < 3; j++) + finalIntervalsandScalingFactors[j].erase(finalIntervalsandScalingFactors[j].begin() + i); + i--; + } + else // merge with right + { + int tmp2 = finalIntervalsandScalingFactors[1][i + 1] - finalIntervalsandScalingFactors[0][i + 1]; + int newScale = (tmp2 * finalIntervalsandScalingFactors[2][i + 1] + tmp1 * finalIntervalsandScalingFactors[2][i]) / (tmp2 + tmp1); + + finalIntervalsandScalingFactors[1][i] = finalIntervalsandScalingFactors[1][i + 1]; + finalIntervalsandScalingFactors[2][i] = newScale; + for (int j = 0; j < 3; j++) + finalIntervalsandScalingFactors[j].erase(finalIntervalsandScalingFactors[j].begin() + i + 1); + i--; + } + } + } + + // scale to 8-bit range as supported by current sei and rdd5 + scale_down(finalIntervalsandScalingFactors, bitDepth); + + // because of scaling in previous step, some intervals may overlap. Check intervals for errors. + confirm_intervals(finalIntervalsandScalingFactors); + + // set number of intervals; exculde intervals with scaling factor 0. + m_compModel[compID].numIntensityIntervals = + (int) finalIntervalsandScalingFactors[2].size() + - (int) count(finalIntervalsandScalingFactors[2].begin(), finalIntervalsandScalingFactors[2].end(), 0); + + if (m_compModel[compID].numIntensityIntervals == 0) + { // check if all intervals are 0, and if yes set presentFlag to false + m_compModel[compID].presentFlag = false; + return; + } + + // if number of intervals is larger than 10, find smallest interval and merge it with the closest one + while (m_compModel[compID].numIntensityIntervals > 10) + { + int diff = finalIntervalsandScalingFactors[1][0] - finalIntervalsandScalingFactors[0][0]; + int minIntervalIdx = 0; + + for (int i = 1; i < finalIntervalsandScalingFactors[2].size(); i++) + { + if (finalIntervalsandScalingFactors[2][i] != 0) + { + int tmp = finalIntervalsandScalingFactors[1][i] - finalIntervalsandScalingFactors[0][i]; + if (tmp < diff) + { + minIntervalIdx = i; + diff = tmp; + } + } + } + + int diffRight = (minIntervalIdx == (finalIntervalsandScalingFactors[2].size() - 1)) + || (finalIntervalsandScalingFactors[2][minIntervalIdx + 1] == 0) + ? std::numeric_limits<int>::max() + : abs(finalIntervalsandScalingFactors[2][minIntervalIdx] - finalIntervalsandScalingFactors[2][minIntervalIdx + 1]); + int diffLeft = (minIntervalIdx == 0) || (finalIntervalsandScalingFactors[2][minIntervalIdx - 1] == 0) + ? std::numeric_limits<int>::max() + : abs(finalIntervalsandScalingFactors[2][minIntervalIdx] - finalIntervalsandScalingFactors[2][minIntervalIdx - 1]); + + // merge with left or right interval + if (diffLeft < diffRight) + { + int tmp1 = + finalIntervalsandScalingFactors[1][minIntervalIdx - 1] - finalIntervalsandScalingFactors[0][minIntervalIdx - 1]; + int tmp2 = + finalIntervalsandScalingFactors[1][minIntervalIdx] - finalIntervalsandScalingFactors[0][minIntervalIdx]; + + int newScale = (tmp1 * finalIntervalsandScalingFactors[2][minIntervalIdx - 1] + + tmp2 * finalIntervalsandScalingFactors[2][minIntervalIdx]) + / (tmp1 + tmp2); + + finalIntervalsandScalingFactors[1][minIntervalIdx - 1] = finalIntervalsandScalingFactors[1][minIntervalIdx]; + finalIntervalsandScalingFactors[2][minIntervalIdx - 1] = newScale; + for (int i = 0; i < 3; i++) + finalIntervalsandScalingFactors[i].erase(finalIntervalsandScalingFactors[i].begin() + minIntervalIdx); + } + else + { + int tmp1 = + finalIntervalsandScalingFactors[1][minIntervalIdx + 1] - finalIntervalsandScalingFactors[0][minIntervalIdx + 1]; + int tmp2 = + finalIntervalsandScalingFactors[1][minIntervalIdx] - finalIntervalsandScalingFactors[0][minIntervalIdx]; + + int newScale = (tmp1 * finalIntervalsandScalingFactors[2][minIntervalIdx + 1] + + tmp2 * finalIntervalsandScalingFactors[2][minIntervalIdx]) + / (tmp1 + tmp2); + + finalIntervalsandScalingFactors[1][minIntervalIdx] = finalIntervalsandScalingFactors[1][minIntervalIdx + 1]; + finalIntervalsandScalingFactors[2][minIntervalIdx] = newScale; + for (int i = 0; i < 3; i++) + finalIntervalsandScalingFactors[i].erase(finalIntervalsandScalingFactors[i].begin() + minIntervalIdx + 1); + } + + m_compModel[compID].numIntensityIntervals--; + } + + // set final interval boundaries and scaling factors. check if some interval has scaling factor 0, and do not encode + // them within SEI. + for (int i = 0, j = 0; i < finalIntervalsandScalingFactors[2].size(); i++) + { + if (finalIntervalsandScalingFactors[2][i] != 0) + { + m_compModel[compID].intensityValues[j].intensityIntervalLowerBound = finalIntervalsandScalingFactors[0][i]; + m_compModel[compID].intensityValues[j].intensityIntervalUpperBound = finalIntervalsandScalingFactors[1][i]; + m_compModel[compID].intensityValues[j].compModelValue[0] = finalIntervalsandScalingFactors[2][i]; + m_compModel[compID].intensityValues[j].compModelValue[1] = cutoff_horizontal; + m_compModel[compID].intensityValues[j].compModelValue[2] = cutoff_vertical; + j++; + } + } +} + +long double FGAnalyser::ldpow(long double n, unsigned p) +{ + long double x = 1; + unsigned i; + + for (i = 0; i < p; i++) + x = x * n; + + return x; +} + +// find bounds of intensity intervals and scaling factors for each interval +void FGAnalyser::define_intervals_and_scalings(std::vector<std::vector<int>> ¶meters, + std::vector<int> &quantizedVec, int bitDepth) +{ + parameters[0].push_back(0); + parameters[2].push_back(quantizedVec[0]); + for (int i = 0; i < quantizedVec.size() - 1; i++) + { + if (quantizedVec[i] != quantizedVec[i + 1]) + { + parameters[0].push_back(i + 1); + parameters[1].push_back(i); + parameters[2].push_back(quantizedVec[i + 1]); + } + } + parameters[1].push_back((1 << bitDepth) - 1); +} + +// scale everything to 8-bit ranges as supported by SEI message +void FGAnalyser::scale_down(std::vector<std::vector<int>> ¶meters, int bitDepth) +{ + for (int i = 0; i < parameters[2].size(); i++) + { + parameters[0][i] >>= (bitDepth - BIT_DEPTH_8); + parameters[1][i] >>= (bitDepth - BIT_DEPTH_8); + parameters[2][i] <<= m_log2ScaleFactor; + parameters[2][i] >>= (bitDepth - BIT_DEPTH_8); + } +} + +// check if intervals are properly set after scaling to 8-bit representation +void FGAnalyser::confirm_intervals(std::vector<std::vector<int>> ¶meters) +{ + std::vector<int> tmp; + for (int i = 0; i < parameters[2].size(); i++) + { + tmp.push_back(parameters[0][i]); + tmp.push_back(parameters[1][i]); + } + for (int i = 0; i < tmp.size() - 1; i++) + { + if (tmp[i] == tmp[i + 1]) + tmp[i + 1]++; + } + for (int i = 0; i < parameters[2].size(); i++) + { + parameters[0][i] = tmp[2 * i]; + parameters[1][i] = tmp[2 * i + 1]; + } +} + +void FGAnalyser::extend_points(std::vector<int> &data_x, std::vector<int> &data_y, int bitDepth) +{ + int xmin = data_x[0]; + int xmax = data_x[0]; + int ymin = data_y[0]; + int ymax = data_y[0]; + for (int i = 0; i < data_x.size(); i++) + { + if (data_x[i] < xmin) + { + xmin = data_x[i]; + ymin = data_y[i]; // not real ymin + } + if (data_x[i] > xmax) + { + xmax = data_x[i]; + ymax = data_y[i]; // not real ymax + } + } + + // extend points to the left + int step = POINT_STEP; + double scale = POINT_SCALE; + while (xmin >= step && ymin > 1) + { + xmin -= step; + ymin = static_cast<int>(ymin / scale); + data_x.push_back(xmin); + data_y.push_back(ymin); + } + + // extend points to the right + while (xmax + step <= ((1 << bitDepth) - 1) && ymax > 1) + { + xmax += step; + ymax = static_cast<int>(ymax / scale); + data_x.push_back(xmax); + data_y.push_back(ymax); + } +} + +#endif \ No newline at end of file diff --git a/source/Lib/CommonLib/SEIFilmGrainAnalyzer.h b/source/Lib/CommonLib/SEIFilmGrainAnalyzer.h new file mode 100644 index 0000000000000000000000000000000000000000..8c9452a2bf79a51f90f22ae0b98236edfecffa2f --- /dev/null +++ b/source/Lib/CommonLib/SEIFilmGrainAnalyzer.h @@ -0,0 +1,207 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainAnalyzer.h + \brief SMPTE RDD5 based film grain analysis functionality from SEI messages +*/ + +#ifndef __SEIFILMGRAINANALYZER__ +#define __SEIFILMGRAINANALYZER__ + +#pragma once + +#include "CommonLib/Picture.h" +#include "CommonLib/SEI.h" +#include "Utilities/VideoIOYuv.h" +#include "CommonLib/CommonDef.h" + +#include "TrQuant_EMT.h" + +#include <numeric> +#include <cmath> +#include <algorithm> + +#if JVET_X0048_X0103_FILM_GRAIN + +static const int MAX_REAL_SCALE = 32; +static const double PI = 3.14159265358979323846; + +// POLYFIT +static const int MAXPAIRS = 256; +static const int MAXORDER = 8; // maximum order of polinomial fitting +static const int ORDER = 3; // order of polinomial function +static const int QUANT_LEVELS = 4; // number of quantization levels in lloyd max quantization +static const int INTERVAL_SIZE = 16; +static const int MIN_ELEMENT_NUMBER_PER_INTENSITY_INTERVAL = 32; +static const int MIN_POINTS_FOR_INTENSITY_ESTIMATION = 128; // 4*32 = 128; 4 intervals with at least 32 points +static const int MIN_BLOCKS_FOR_CUTOFF_ESTIMATION = 4; // 4 blocks of 64 x 64 size +static const int POINT_STEP = 8; // step size in point extension +static const double POINT_SCALE = 1.5; // scaling in point extension +static const double VAR_SCALE_DOWN = 1.5; // filter out large points +static const double VAR_SCALE_UP = 0.5; // filter out large points + +//! \ingroup SEIFilmGrainAnalyzer +//! \{ + +// ==================================================================================================================== +// Class definition +// ==================================================================================================================== + +struct Picture; + +typedef std::vector<std::vector<Intermediate_Int>> PelMatrix; +typedef std::vector<std::vector<double>> PelMatrixDouble; + +typedef std::vector<std::vector<long double>> PelMatrixLongDouble; +typedef std::vector<long double> PelVectorLongDouble; + +class Canny +{ +public: + Canny(); + ~Canny(); + + unsigned int m_convWidthG = 5, m_convHeightG = 5; // Pixel's row and col positions for Gauss filtering + + void detect_edges(const PelStorage* orig, PelStorage* dest, unsigned int uiBitDepth, ComponentID compID); + +private: + static const int m_gx[3][3]; // Sobel kernel x + static const int m_gy[3][3]; // Sobel kernel y + static const int m_gauss5x5[5][5]; // Gauss 5x5 kernel, integer approximation + + unsigned int m_convWidthS = 3, m_convHeightS = 3; // Pixel's row and col positions for Sobel filtering + double m_sigma = 1.0; // Gaussian filter sigma + + double m_lowThresholdRatio = 0.1; // low threshold rato + int m_highThresholdRatio = 3; // high threshold rato + + void gradient ( PelStorage* buff1, PelStorage* buff2, + unsigned int width, unsigned int height, + unsigned int convWidthS, unsigned int convHeightS, unsigned int bitDepth, ComponentID compID ); + void suppressNonMax ( PelStorage* buff1, PelStorage* buff2, unsigned int width, unsigned int height, ComponentID compID ); + void doubleThreshold( PelStorage *buff, unsigned int width, unsigned int height, /*unsigned int windowSizeRatio,*/ + unsigned int bitDepth, ComponentID compID); + void edgeTracking ( PelStorage* buff1, unsigned int width, unsigned int height, + unsigned int windowWidth, unsigned int windowHeight, unsigned int bitDepth, ComponentID compID ); +}; + + +class Morph +{ +public: + Morph(); + ~Morph(); + + int dilation (PelStorage* buff, unsigned int bitDepth, ComponentID compID, int numIter, int iter = 0); + int erosion (PelStorage* buff, unsigned int bitDepth, ComponentID compID, int numIter, int iter = 0); + +private: + unsigned int m_kernelSize = 3; // Dilation and erosion kernel size +}; + + +class FGAnalyser +{ +public: + FGAnalyser(); + ~FGAnalyser(); + + void init(const int width, + const int height, + const ChromaFormat inputChroma, + const BitDepths& inputBitDepths, + const bool doAnalysis[]); + void destroy (); + void initBufs (Picture* pic); + void estimate_grain (Picture* pic); + + int getLog2scaleFactor() { return m_log2ScaleFactor; }; + SEIFilmGrainCharacteristics::CompModel getCompModel(int idx) { return m_compModel[idx]; }; + +private: + ChromaFormat m_chromaFormatIDC; + BitDepths m_bitDepths; + bool m_doAnalysis[MAX_NUM_COMPONENT] = { false, false, false }; + + Canny m_edgeDetector; + Morph m_morphOperation; + double m_lowIntensityRatio = 0.1; // supress everything below 0.1*maxIntensityOffset + static constexpr double m_tap_filtar[3] = { 1, 2, 1 }; + static constexpr double m_normTap = 4.0; + + // fg model parameters + int m_log2ScaleFactor; + SEIFilmGrainCharacteristics::CompModel m_compModel[MAX_NUM_COMPONENT]; + + PelStorage *m_originalBuf = nullptr; + PelStorage *m_workingBuf = nullptr; + PelStorage *m_maskBuf = nullptr; + + int denoise (Picture* pic); + void findMask (); + + void estimate_grain_parameters (); + void block_transform (const PelStorage& buff1, std::vector<PelMatrix>& squared_dct_grain_block_list, int offsetX, int offsetY, unsigned int bitDepth, ComponentID compID); + void estimate_cutoff_freq (const std::vector<PelMatrix>& blocks, ComponentID compID); + int cutoff_frequency (std::vector<double>& mean); + void estimate_scaling_factors (std::vector<int>& data_x, std::vector<int>& data_y, unsigned int bitDepth, ComponentID compID); + bool fit_function (std::vector<int>& data_x, std::vector<int>& data_y, std::vector<double>& coeffs, std::vector<double>& scalingVec, + int order, int bitDepth, bool second_pass); + void avg_scaling_vec (std::vector<double> &scalingVec, ComponentID compID, int bitDepth); + bool lloyd_max (std::vector<double>& scalingVec, std::vector<int>& quantizedVec, double& distortion, int numQuantizedLevels, int bitDepth); + void quantize (std::vector<double>& scalingVec, std::vector<double>& quantizedVec, double& distortion, std::vector<double> partition, std::vector<double> codebook); + void extend_points (std::vector<int>& data_x, std::vector<int>& data_y, int bitDepth); + + void setEstimatedParameters (std::vector<int>& quantizedVec, unsigned int bitDepth, ComponentID compID); + void define_intervals_and_scalings(std::vector<std::vector<int>>& parameters, std::vector<int>& quantizedVec, int bitDepth); + void scale_down (std::vector<std::vector<int>>& parameters, int bitDepth); + void confirm_intervals (std::vector<std::vector<int>>& parameters); + + long double ldpow (long double n, unsigned p); + int meanVar (PelStorage& buffer, int windowSize, ComponentID compID, int offsetX, int offsetY, bool getVar); + int count_edges (PelStorage& buffer, int windowSize, ComponentID compID, int offsetX, int offsetY); + + void subsample (const PelStorage& input, PelStorage& output, ComponentID compID, const int factor = 2, const int padding = 0) const; + void upsample (const PelStorage& input, PelStorage& output, ComponentID compID, const int factor = 2, const int padding = 0) const; + void combineMasks (PelStorage& buff, PelStorage& buff2, ComponentID compID); + void suppressLowIntensity (const PelStorage& buff1, PelStorage& buff2, unsigned int bitDepth, ComponentID compID); + +}; // END CLASS DEFINITION + +//! \} +#endif + +#endif // __SEIFILMGRAINANALYZER__ + + diff --git a/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..39dd1576650e4fc2b40982bc5ba04840052d7674 --- /dev/null +++ b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.cpp @@ -0,0 +1,1268 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + + /** \file SEIFilmGrainSynthesizer.cpp + \brief SMPTE RDD5 based film grain synthesis functionality from SEI messages + */ + +#include "SEIFilmGrainSynthesizer.h" + +#include <stdio.h> +#include <cmath> + +#if JVET_X0048_X0103_FILM_GRAIN + +/* static look up table definitions */ +static const int8_t gaussianLUT[2048] = +{ +-11, 12, 103, -11, 42, -35, 12, 59, 77, 98, -87, 3, 65, -78, 45, 56, +-51, 21, 13, -11, -20, -19, 33, -127, 17, -6, -105, 18, 19, 71, 48, -10, +-38, 42, -2, 75, -67, 52, -90, 33, -47, 21, -3, -56, 49, 1, -57, -42, +-1, 120, -127, -108, -49, 9, 14, 127, 122, 109, 52, 127, 2, 7, 114, 19, +30, 12, 77, 112, 82, -61, -127, 111, -52, -29, 2, -49, -24, 58, -29, -73, +12, 112, 67, 79, -3, -114, -87, -6, -5, 40, 58, -81, 49, -27, -31, -34, +-105, 50, 16, -24, -35, -14, -15, -127, -55, -22, -55, -127, -112, 5, -26, -72, +127, 127, -2, 41, 87, -65, -16, 55, 19, 91, -81, -65, -64, 35, -7, -54, +99, -7, 88, 125, -26, 91, 0, 63, 60, -14, -23, 113, -33, 116, 14, 26, +51, -16, 107, -8, 53, 38, -34, 17, -7, 4, -91, 6, 63, 63, -15, 39, +-36, 19, 55, 17, -51, 40, 33, -37, 126, -39, -118, 17, -30, 0, 19, 98, +60, 101, -12, -73, -17, -52, 98, 3, 3, 60, 33, -3, -2, 10, -42, -106, +-38, 14, 127, 16, -127, -31, -86, -39, -56, 46, -41, 75, 23, -19, -22, -70, +74, -54, -2, 32, -45, 17, -92, 59, -64, -67, 56, -102, -29, -87, -34, -92, +68, 5, -74, -61, 93, -43, 14, -26, -38, -126, -17, 16, -127, 64, 34, 31, +93, 17, -51, -59, 71, 77, 81, 127, 127, 61, 33, -106, -93, 0, 0, 75, +-69, 71, 127, -19, -111, 30, 23, 15, 2, 39, 92, 5, 42, 2, -6, 38, +15, 114, -30, -37, 50, 44, 106, 27, 119, 7, -80, 25, -68, -21, 92, -11, +-1, 18, 41, -50, 79, -127, -43, 127, 18, 11, -21, 32, -52, 27, -88, -90, +-39, -19, -10, 24, -118, 72, -24, -44, 2, 12, 86, -107, 39, -33, -127, 47, +51, -24, -22, 46, 0, 15, -35, -69, -2, -74, 24, -6, 0, 29, -3, 45, +32, -32, 117, -45, 79, -24, -17, -109, -10, -70, 88, -48, 24, -91, 120, -37, +50, -127, 58, 32, -82, -10, -17, -7, 46, -127, -15, 89, 127, 17, 98, -39, +-33, 37, 42, -40, -32, -21, 105, -19, 19, 19, -59, -9, 30, 0, -127, 34, +127, -84, 75, 24, -40, -49, -127, -107, -14, 45, -75, 1, 30, -20, 41, -68, +-40, 12, 127, -3, 5, 20, -73, -59, -127, -3, -3, -53, -6, -119, 93, 120, +-80, -50, 0, 20, -46, 67, 78, -12, -22, -127, 36, -41, 56, 119, -5, -116, +-22, 68, -14, -90, 24, -82, -44, -127, 107, -25, -37, 40, -7, -7, -82, 5, +-87, 44, -34, 9, -127, 39, 70, 49, -63, 74, -49, 109, -27, -89, -47, -39, +44, 49, -4, 60, -42, 80, 9, -127, -9, -56, -49, 125, -66, 47, 36, 117, +15, -11, -96, 109, 94, -17, -56, 70, 8, -14, -5, 50, 37, -45, 120, -30, +-76, 40, -46, 6, 3, 69, 17, -78, 1, -79, 6, 127, 43, 26, 127, -127, +28, -55, -26, 55, 112, 48, 107, -1, -77, -1, 53, -9, -22, -43, 123, 108, +127, 102, 68, 46, 5, 1, 123, -13, -55, -34, -49, 89, 65, -105, -5, 94, +-53, 62, 45, 30, 46, 18, -35, 15, 41, 47, -98, -24, 94, -75, 127, -114, +127, -68, 1, -17, 51, -95, 47, 12, 34, -45, -75, 89, -107, -9, -58, -29, +-109, -24, 127, -61, -13, 77, -45, 17, 19, 83, -24, 9, 127, -66, 54, 4, +26, 13, 111, 43, -113, -22, 10, -24, 83, 67, -14, 75, -123, 59, 127, -12, +99, -19, 64, -38, 54, 9, 7, 61, -56, 3, -57, 113, -104, -59, 3, -9, +-47, 74, 85, -55, -34, 12, 118, 28, 93, -72, 13, -99, -72, -20, 30, 72, +-94, 19, -54, 64, -12, -63, -25, 65, 72, -10, 127, 0, -127, 103, -20, -73, +-112, -103, -6, 28, -42, -21, -59, -29, -26, 19, -4, -51, 94, -58, -95, -37, +35, 20, -69, 127, -19, -127, -22, -120, -53, 37, 74, -127, -1, -12, -119, -53, +-28, 38, 69, 17, 16, -114, 89, 62, 24, 37, -23, 49, -101, -32, -9, -95, +-53, 5, 93, -23, -49, -8, 51, 3, -75, -90, -10, -39, 127, -86, -22, 20, +20, 113, 75, 52, -31, 92, -63, 7, -12, 46, 36, 101, -43, -17, -53, -7, +-38, -76, -31, -21, 62, 31, 62, 20, -127, 31, 64, 36, 102, -85, -10, 77, +80, 58, -79, -8, 35, 8, 80, -24, -9, 3, -17, 72, 127, 83, -87, 55, +18, -119, -123, 36, 10, 127, 56, -55, 113, 13, 26, 32, -13, -48, 22, -13, +5, 58, 27, 24, 26, -11, -36, 37, -92, 78, 81, 9, 51, 14, 67, -13, +0, 32, 45, -76, 32, -39, -22, -49, -127, -27, 31, -9, 36, 14, 71, 13, +57, 12, -53, -86, 53, -44, -35, 2, 127, 12, -66, -44, 46, -115, 3, 10, +56, -35, 119, -19, -61, 52, -59, -127, -49, -23, 4, -5, 17, -82, -6, 127, +25, 79, 67, 64, -25, 14, -64, -37, -127, -28, 21, -63, 66, -53, -41, 109, +-62, 15, -22, 13, 29, -63, 20, 27, 95, -44, -59, -116, -10, 79, -49, 22, +-43, -16, 46, -47, -120, -36, -29, -52, -44, 29, 127, -13, 49, -9, -127, 75, +-28, -23, 88, 59, 11, -95, 81, -59, 58, 60, -26, 40, -92, -3, -22, -58, +-45, -59, -22, -53, 71, -29, 66, -32, -23, 14, -17, -66, -24, -28, -62, 47, +38, 17, 16, -37, -24, -11, 8, -27, -19, 59, 45, -49, -47, -4, -22, -81, +30, -67, -127, 74, 102, 5, -18, 98, 34, -66, 42, -52, 7, -59, 24, -58, +-19, -24, -118, -73, 91, 15, -16, 79, -32, -79, -127, -36, 41, 77, -83, 2, +56, 22, -75, 127, -16, -21, 12, 31, 56, -113, -127, 90, 55, 61, 12, 55, +-14, -113, -14, 32, 49, -67, -17, 91, -10, 1, 21, 69, -70, 99, -19, -112, +66, -90, -10, -9, -71, 127, 50, -81, -49, 24, 61, -61, -111, 7, -41, 127, +88, -66, 108, -127, -6, 36, -14, 41, -50, 14, 14, 73, -101, -28, 77, 127, +-8, -100, 88, 38, 121, 88, -125, -60, 13, -94, -115, 20, -67, -87, -94, -119, +44, -28, -30, 18, 5, -53, -61, 20, -43, 11, -77, -60, 13, 29, 3, 6, +-72, 38, -60, -11, 108, -53, 41, 66, -12, -127, -127, -49, 24, 29, 46, 36, +91, 34, -33, 116, -51, -34, -52, 91, 7, -83, 73, -26, -103, 24, -10, 76, +84, 5, 68, -80, -13, -17, -32, -48, 20, 50, 26, 10, 63, -104, -14, 37, +127, 114, 97, 35, 1, -33, -55, 127, -124, -33, 61, -7, 119, -32, -127, -53, +-42, 63, 3, -5, -26, 70, -58, -33, -44, -43, 34, -56, -127, 127, 25, -35, +-11, 16, -81, 29, -58, 40, -127, -127, 20, -47, -11, -36, -63, -52, -32, -82, +78, -76, -73, 8, 27, -72, -9, -74, -85, -86, -57, 25, 78, -10, -97, 35, +-65, 8, -59, 14, 1, -42, 32, -88, -44, 17, -3, -9, 59, 40, 12, -108, +-40, 24, 34, 18, -28, 2, 51, -110, -4, 100, 1, 65, 22, 0, 127, 61, +45, 25, -31, 6, 9, -7, -48, 99, 16, 44, -2, -40, 32, -39, -52, 10, +-110, -19, 56, -127, 69, 26, 51, 92, 40, 61, -52, 45, -38, 13, 85, 122, +27, 66, 45, -111, -83, -3, 31, 37, 19, -36, 58, 71, 39, -78, -47, 58, +-78, 8, -62, -36, -14, 61, 42, -127, 71, -4, 24, -54, 52, -127, 67, -4, +-42, 30, -63, 59, -3, -1, -18, -46, -92, -81, -96, -14, -53, -10, -11, -77, +13, 1, 8, -67, -127, 127, -28, 26, -14, 18, -13, -26, 2, 10, -46, -32, +-15, 27, -31, -59, 59, 77, -121, 28, 40, -54, -62, -31, -21, -37, -32, -6, +-127, -25, -60, 70, -127, 112, -127, 127, 88, -7, 116, 110, 53, 87, -127, 3, +16, 23, 74, -106, -51, 3, 74, -82, -112, -74, 65, 81, 25, 53, 127, -45, +-50, -103, -41, -65, -29, 79, -67, 64, -33, -30, -8, 127, 0, -13, -51, 67, +-14, 5, -92, 29, -35, -8, -90, -57, -3, 36, 43, 44, -31, -69, -7, 36, +39, -51, 43, -81, 58, 6, 127, 12, 57, 66, 46, 59, -43, -42, 41, -15, +-120, 24, 3, -11, 19, -13, 51, 28, 3, 55, -48, -12, -1, 2, 97, -19, +29, 42, 13, 43, 78, -44, 56, -108, -43, -19, 127, 15, -11, -18, -81, 83, +-37, 77, -109, 15, 65, -50, 43, 12, 13, 27, 28, 61, 57, 30, 26, 106, +-18, 56, 13, 97, 4, -8, -62, -103, 94, 108, -44, 52, 27, -47, -9, 105, +-53, 46, 89, 103, -33, 38, -34, 55, 51, 70, -94, -35, -87, -107, -19, -31, +9, -19, 79, -14, 77, 5, -19, -107, 85, 21, -45, -39, -42, 9, -29, 74, +47, -75, 60, -127, 120, -112, -57, -32, 41, 7, 79, 76, 66, 57, 41, -25, +31, 37, -47, -36, 43, -73, -37, 63, 127, -69, -52, 90, -33, -61, 60, -55, +44, 15, 4, -67, 13, -92, 64, 29, -39, -3, 83, -2, -38, -85, -86, 58, +35, -69, -61, 29, -37, -95, -78, 4, 30, -4, -32, -80, -22, -9, -77, 46, +7, -93, -71, 65, 9, -50, 127, -70, 26, -12, -39, -114, 63, -127, -100, 4, +-32, 111, 22, -60, 65, -101, 26, -42, 21, -59, -27, -74, 2, -94, 6, 126, +5, 76, -88, -9, -43, -101, 127, 1, 125, 92, -63, 52, 56, 4, 81, -127, +127, 80, 127, -29, 30, 116, -74, -17, -57, 105, 48, 45, 25, -72, 48, -38, +-108, 31, -34, 4, -11, 41, -127, 52, -104, -43, -37, 52, 2, 47, 87, -9, +77, 27, -41, -25, 90, 86, -56, 75, 10, 33, 78, 58, 127, 127, -7, -73, +49, -33, -106, -35, 38, 57, 53, -17, -4, 83, 52, -108, 54, -125, 28, 23, +56, -43, -88, -17, -6, 47, 23, -9, 0, -13, 111, 75, 27, -52, -38, -34, +39, 30, 66, 39, 38, -64, 38, 3, 21, -32, -51, -28, 54, -38, -87, 20, +52, 115, 18, -81, -70, 0, -14, -46, -46, -3, 125, 16, -14, 23, -82, -84, +-69, -20, -65, -127, 9, 81, -49, 61, 7, -36, -45, -42, 57, -26, 47, 20, +-85, 46, -13, 41, -37, -75, -60, 86, -78, -127, 12, 50, 2, -3, 13, 47, +5, 19, -78, -55, -27, 65, -71, 12, -108, 20, -16, 11, -31, 63, -55, 37, +75, -17, 127, -73, -33, -28, -120, 105, 68, 106, -103, -106, 71, 61, 2, 23, +-3, 33, -5, -15, -67, -15, -23, -54, 15, -63, 76, 58, -110, 1, 83, -27, +22, 75, -39, -17, -11, 64, -17, -127, -54, -66, 31, 96, 116, 3, -114, -7, +-108, -63, 97, 9, 50, 8, 75, -28, 72, 112, -36, -112, 95, -50, 23, -13, +-19, 55, 21, 23, 92, 91, 22, -49, 16, -75, 23, 9, -49, -97, -37, 49, +-36, 36, -127, -86, 43, 127, -24, -24, 84, 83, -35, -34, -12, 109, 102, -38, +51, -68, 34, 19, -22, 49, -32, 127, 40, 24, -93, -4, -3, 105, 3, -58, +-18, 8, 127, -18, 125, 68, 69, -62, 30, -36, 54, -57, -24, 17, 43, -36, +-27, -57, -67, -21, -10, -49, 68, 12, 65, 4, 48, 55, 127, -75, 44, 89, +-66, -13, -78, -82, -91, 22, 30, 33, -40, -87, -34, 96, -91, 39, 10, -64, +-3, -12, 127, -50, -37, -56, 23, -35, -36, -54, 90, -91, 2, 50, 77, -6, +-127, 16, 46, -5, -73, 0, -56, -18, -72, 28, 93, 60, 49, 20, 18, 111, +-111, 32, -83, 47, 47, -10, 35, -88, 43, 57, -98, 127, -17, 0, 1, -39, +-127, -2, 0, 63, 93, 0, 36, -66, -61, -19, 39, -127, 58, 50, -17, 127, +88, -43, -108, -51, -16, 7, -36, 68, 46, -14, 107, 40, 57, 7, 19, 8, +3, 88, -90, -92, -18, -21, -24, 13, 7, -4, -78, -91, -4, 8, -35, -5, +19, 2, -111, 4, -66, -81, 122, -20, -34, -37, -84, 127, 68, 46, 17, 47 +}; + +static const uint32_t seedLUT[256] = { +747538460, 1088979410, 1744950180, 1767011913, 1403382928, 521866116, 1060417601, 2110622736, +1557184770, 105289385, 585624216, 1827676546, 1191843873, 1018104344, 1123590530, 663361569, +2023850500, 76561770, 1226763489, 80325252, 1992581442, 502705249, 740409860, 516219202, +557974537, 1883843076, 720112066, 1640137737, 1820967556, 40667586, 155354121, 1820967557, +1115949072, 1631803309, 98284748, 287433856, 2119719977, 988742797, 1827432592, 579378475, +1017745956, 1309377032, 1316535465, 2074315269, 1923385360, 209722667, 1546228260, 168102420, +135274561, 355958469, 248291472, 2127839491, 146920100, 585982612, 1611702337, 696506029, +1386498192, 1258072451, 1212240548, 1043171860, 1217404993, 1090770605, 1386498193, 169093201, +541098240, 1468005469, 456510673, 1578687785, 1838217424, 2010752065, 2089828354, 1362717428, +970073673, 854129835, 714793201, 1266069081, 1047060864, 1991471829, 1098097741, 913883585, +1669598224, 1337918685, 1219264706, 1799741108, 1834116681, 683417731, 1120274457, 1073098457, +1648396544, 176642749, 31171789, 718317889, 1266977808, 1400892508, 549749008, 1808010512, +67112961, 1005669825, 903663673, 1771104465, 1277749632, 1229754427, 950632997, 1979371465, +2074373264, 305357524, 1049387408, 1171033360, 1686114305, 2147468765, 1941195985, 117709841, +809550080, 991480851, 1816248997, 1561503561, 329575568, 780651196, 1659144592, 1910793616, +604016641, 1665084765, 1530186961, 1870928913, 809550081, 2079346113, 71307521, 876663040, +1073807360, 832356664, 1573927377, 204073344, 2026918147, 1702476788, 2043881033, 57949587, +2001393952, 1197426649, 1186508931, 332056865, 950043140, 890043474, 349099312, 148914948, +236204097, 2022643605, 1441981517, 498130129, 1443421481, 924216797, 1817491777, 1913146664, +1411989632, 929068432, 495735097, 1684636033, 1284520017, 432816184, 1344884865, 210843729, +676364544, 234449232, 12112337, 1350619139, 1753272996, 2037118872, 1408560528, 533334916, +1043640385, 357326099, 201376421, 110375493, 541106497, 416159637, 242512193, 777294080, +1614872576, 1535546636, 870600145, 910810409, 1821440209, 1605432464, 1145147393, 951695441, +1758494976, 1506656568, 1557150160, 608221521, 1073840384, 217672017, 684818688, 1750138880, +16777217, 677990609, 953274371, 1770050213, 1359128393, 1797602707, 1984616737, 1865815816, +2120835200, 2051677060, 1772234061, 1579794881, 1652821009, 1742099468, 1887260865, 46468113, +1011925248, 1134107920, 881643832, 1354774993, 472508800, 1892499769, 1752793472, 1962502272, +687898625, 883538000, 1354355153, 1761673473, 944820481, 2020102353, 22020353, 961597696, +1342242816, 964808962, 1355809701, 17016649, 1386540177, 647682692, 1849012289, 751668241, +1557184768, 127374604, 1927564752, 1045744913, 1614921984, 43588881, 1016185088, 1544617984, +1090519041, 136122424, 215038417, 1563027841, 2026918145, 1688778833, 701530369, 1372639488, +1342242817, 2036945104, 953274369, 1750192384, 16842753, 964808960, 1359020032, 1358954497 +}; + +static const uint32_t deblockFactor[13] = +{ 64, 71, 77, 84, 90, 96, 103, 109, 116, 122, 128, 128, 128 }; + + +SEIFilmGrainSynthesizer::SEIFilmGrainSynthesizer() + : m_width (0) + , m_height (0) + , m_chromaFormat (NUM_CHROMA_FORMAT) + , m_bitDepth (0) + , m_idrPicId (0) + , m_grainSynt (NULL) + , m_fgsBlkSize (8) + , m_poc (0) + , m_errorCode (0) + , m_fgcParameters (NULL) +{ + +} + +void SEIFilmGrainSynthesizer::create(uint32_t width, uint32_t height, ChromaFormat fmt, uint8_t bitDepth, uint32_t idrPicId) +{ + m_width = width; + m_height = height; + m_chromaFormat = fmt; + m_bitDepth = bitDepth; + m_idrPicId = idrPicId; + m_fgsBlkSize = 8; + m_errorCode = 0; + + if (!m_grainSynt) + m_grainSynt = new GrainSynthesisStruct; + if (!m_fgcParameters) + m_fgcParameters = new SEIFilmGrainCharacteristics; +} + +SEIFilmGrainSynthesizer::~SEIFilmGrainSynthesizer() +{ + destroy(); +} + +void SEIFilmGrainSynthesizer::fgsInit() +{ + deriveFGSBlkSize(); + dataBaseGen(); +} + +void SEIFilmGrainSynthesizer::destroy() +{ + if (m_fgcParameters) + delete m_fgcParameters; + if (m_grainSynt) + delete m_grainSynt; +} + +void SEIFilmGrainSynthesizer::grainSynthesizeAndBlend(PelStorage* pGrainBuf, bool isIdrPic) +{ + uint8_t numComp = MAX_NUM_COMPONENT, compCtr; /* number of color components */ + uint8_t color_offset[MAX_NUM_COMPONENT]; + uint32_t widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT], strideComp[MAX_NUM_COMPONENT]; + uint32_t * offsetsArr[MAX_NUM_COMPONENT]; + Pel * decComp[MAX_NUM_COMPONENT]; + uint32_t pseudoRandValEc; + uint32_t picOffset; + + /* from SMPTE RDD5 */ + color_offset[0] = COLOUR_OFFSET_LUMA; + color_offset[1] = COLOUR_OFFSET_CR; + color_offset[2] = COLOUR_OFFSET_CB; + + if (0 != m_fgcParameters->m_filmGrainCharacteristicsCancelFlag) + { + return; + } + + widthComp[0] = m_width; + heightComp[0] = m_height; + + if (CHROMA_420 == m_chromaFormat) + { + widthComp[1] = (m_width >> 1); + widthComp[2] = (m_width >> 1); + heightComp[1] = (m_height >> 1); + heightComp[2] = (m_height >> 1); + } + else if (CHROMA_422 == m_chromaFormat) + { + widthComp[1] = (m_width >> 1); + widthComp[2] = (m_width >> 1); + heightComp[1] = m_height; + heightComp[2] = m_height; + } + else if (CHROMA_400 == m_chromaFormat) + { + numComp = 1; + } + + /*Allocate memory for offsets assuming 16x16 block size, + 32x32 will need lesser than this*/ + uint32_t maxNumBlocks = ((m_width >> 4) + 1) * ((m_height >> 4) + 1); + + for (compCtr = 0; compCtr < numComp; compCtr++) + { + offsetsArr[compCtr] = new uint32_t[maxNumBlocks]; + } + + /*decComp[0] = pGrainBuf->getOrigin(COMPONENT_Y); + decComp[1] = pGrainBuf->getOrigin(COMPONENT_Cb); + decComp[2] = pGrainBuf->getOrigin(COMPONENT_Cb);*/ + + decComp[0] = pGrainBuf->bufs[0].buf; + decComp[1] = pGrainBuf->bufs[1].buf; + decComp[2] = pGrainBuf->bufs[2].buf; + + /* component strides */ + strideComp[0] = pGrainBuf->bufs[0].stride; + strideComp[1] = 0; + strideComp[2] = 0; + + if (CHROMA_400 != m_chromaFormat) + { + strideComp[1] = pGrainBuf->bufs[1].stride; + strideComp[2] = pGrainBuf->bufs[2].stride; + } + + int32_t numBlks_x[MAX_NUM_COMPONENT]; + int32_t numBlks_y[MAX_NUM_COMPONENT]; + + picOffset = m_poc; + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (BLK_32 == m_fgsBlkSize) + { + numBlks_x[compCtr] = (widthComp[compCtr] >> 5) + ((widthComp[compCtr] & 0x1F) ? 1 : 0); + numBlks_y[compCtr] = (heightComp[compCtr] >> 5) + ((heightComp[compCtr] & 0x1F) ? 1 : 0); + } + else + { + numBlks_x[compCtr] = (widthComp[compCtr] >> 4) + ((widthComp[compCtr] & 0xF) ? 1 : 0); + numBlks_y[compCtr] = (heightComp[compCtr] >> 4) + ((heightComp[compCtr] & 0xF) ? 1 : 0); + } + } + + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (1 == m_fgcParameters->m_compModel[compCtr].presentFlag) + { + uint32_t *tmp = offsetsArr[compCtr]; + int i, j; + + /* Seed initialization for current picture*/ + pseudoRandValEc = seedLUT[((picOffset + color_offset[compCtr]) & 0xFF)]; + + for (i = 0; i < numBlks_y[compCtr]; i++) + { + for (j = 0; j < numBlks_x[compCtr]; j++) + { + *tmp = pseudoRandValEc; + pseudoRandValEc = prng(pseudoRandValEc); + tmp++; + } + } + } + } + + m_fgsArgs.numComp = numComp; + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (1 == m_fgcParameters->m_compModel[compCtr].presentFlag) + { + m_fgsArgs.decComp[compCtr] = decComp[compCtr]; + m_fgsArgs.widthComp[compCtr] = widthComp[compCtr]; + m_fgsArgs.strideComp[compCtr] = strideComp[compCtr]; + m_fgsArgs.fgsOffsets[compCtr] = offsetsArr[compCtr]; + + if (BLK_32 == m_fgsBlkSize) + { + m_fgsArgs.heightComp[compCtr] = numBlks_y[compCtr] * BLK_32; + } + else + { + m_fgsArgs.heightComp[compCtr] = numBlks_y[compCtr] * BLK_16; + } + } + } + m_fgsArgs.pFgcParameters = m_fgcParameters; + m_fgsArgs.blkSize = m_fgsBlkSize; + m_fgsArgs.bitDepth = m_bitDepth; + m_fgsArgs.pGrainSynt = m_grainSynt; + + fgsProcess(m_fgsArgs); + + for (compCtr = 0; compCtr < numComp; compCtr++) + { + delete offsetsArr[compCtr]; + } + return; +} + +/* Function validates film grain parameters and returns 0 for valid parameters of SMPTE-RDD5 else 1*/ +/* Also down converts the chroma model values for 4:2:0 and 4:2:2 chroma_formats */ +uint8_t SEIFilmGrainSynthesizer::grainValidateParams() +{ + uint8_t numComp = MAX_NUM_COMPONENT; /* number of color components */ + uint8_t compCtr, intensityCtr, multiGrainCheck[MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES] = { 0 }; + uint16_t multiGrainCtr; + uint8_t limitCompModelVal1[10] = { 0 }, limitCompModelVal2[10] = { 0 }; + uint8_t num_comp_model_pairs = 0, limitCompModelCtr, compPairMatch; + + memset(m_grainSynt->intensityInterval, INTENSITY_INTERVAL_MATCH_FAIL, sizeof(m_grainSynt->intensityInterval)); + + if ((m_width < MIN_WIDTH) || (m_width > MAX_WIDTH) || (m_width % 4)) + { + return FGS_INVALID_WIDTH; /* Width not supported */ + } + if ((m_height < MIN_HEIGHT) || (m_height > MAX_HEIGHT) || (m_height % 4)) + { + return FGS_INVALID_HEIGHT; /* Height not supported */ + } + if ((m_chromaFormat < MIN_CHROMA_FORMAT_IDC) || (m_chromaFormat > MAX_CHROMA_FORMAT_IDC)) + { + return FGS_INVALID_CHROMA_FORMAT; /* Chroma format not supported */ + } + if (m_chromaFormat == MIN_CHROMA_FORMAT_IDC) /* Mono Chrome */ + { + numComp = 1; + } + + if ((m_bitDepth < MIN_BIT_DEPTH) || (m_bitDepth > MAX_BIT_DEPTH)) + { + return FGS_INVALID_BIT_DEPTH; /* Bit depth not supported */ + } + + if ((0 != m_fgcParameters->m_filmGrainCharacteristicsCancelFlag) && + (1 != m_fgcParameters->m_filmGrainCharacteristicsCancelFlag)) + { + return FGS_INVALID_FGC_CANCEL_FLAG; /* Film grain synthesis disabled */ + } + + if (FILM_GRAIN_MODEL_ID_VALUE != m_fgcParameters->m_filmGrainModelId) + { + return FGS_INVALID_GRAIN_MODEL_ID; /* Not supported */ + } + + if (0 != m_fgcParameters->m_separateColourDescriptionPresentFlag) + { + return FGS_INVALID_SEP_COL_DES_FLAG; /* Not supported */ + } + + if (BLENDING_MODE_VALUE != m_fgcParameters->m_blendingModeId) + { + return FGS_INVALID_BLEND_MODE; /* Not supported */ + } + + if (m_fgcParameters->m_compModel[0].presentFlag || m_fgcParameters->m_compModel[1].presentFlag || m_fgcParameters->m_compModel[2].presentFlag) { + if ((m_fgcParameters->m_log2ScaleFactor < MIN_LOG2SCALE_VALUE) || + (m_fgcParameters->m_log2ScaleFactor > MAX_LOG2SCALE_VALUE)) + { + return FGS_INVALID_LOG2_SCALE_FACTOR; /* Not supported */ + } + } + + /* validation of component model present flag */ + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if ((m_fgcParameters->m_compModel[compCtr].presentFlag != true) && + (m_fgcParameters->m_compModel[compCtr].presentFlag != false)) + { + return FGS_INVALID_COMP_MODEL_PRESENT_FLAG; /* Not supported */ + } + if (m_fgcParameters->m_compModel[compCtr].presentFlag && + (m_fgcParameters->m_compModel[compCtr].numModelValues > MAX_ALLOWED_MODEL_VALUES)) + { + return FGS_INVALID_NUM_MODEL_VALUES; /* Not supported */ + } + } + + /* validation of intensity intervals and */ + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (m_fgcParameters->m_compModel[compCtr].presentFlag) + { + for (intensityCtr = 0; intensityCtr < m_fgcParameters->m_compModel[compCtr].intensityValues.size(); intensityCtr++) + { + + if (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalLowerBound > + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalUpperBound) + { + return FGS_INVALID_INTENSITY_BOUNDARY_VALUES; /* Not supported */ + } + + for (multiGrainCtr = m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalLowerBound; + multiGrainCtr <= m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].intensityIntervalUpperBound; multiGrainCtr++) + { + m_grainSynt->intensityInterval[compCtr][multiGrainCtr] = intensityCtr; + if (multiGrainCheck[compCtr][multiGrainCtr]) /* Non over lap */ + { + return FGS_INVALID_INTENSITY_BOUNDARY_VALUES; /* Not supported */ + } + else + { + multiGrainCheck[compCtr][multiGrainCtr] = 1; + } + } + + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue.resize(MAX_NUM_MODEL_VALUES); + /* default initialization for cut off frequencies */ + if (1 == m_fgcParameters->m_compModel[compCtr].numModelValues) + { + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] = DEFAULT_HORZ_CUT_OFF_FREQUENCY; + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] = m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1]; + } + else if (2 == m_fgcParameters->m_compModel[compCtr].numModelValues) + { + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] = m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1]; + } + + /* Error check on model component value */ + if (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] > (MAX_STANDARD_DEVIATION << (m_bitDepth - BIT_DEPTH_8))) + { + return FGS_INVALID_STANDARD_DEVIATION; /* Not supported */ + } + else if ((m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] < MIN_CUT_OFF_FREQUENCY) || + (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] > MAX_CUT_OFF_FREQUENCY)) + { + return FGS_INVALID_CUT_OFF_FREQUENCIES; /* Not supported */ + } + else if ((m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] < MIN_CUT_OFF_FREQUENCY) || + (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] > MAX_CUT_OFF_FREQUENCY)) + { + return FGS_INVALID_CUT_OFF_FREQUENCIES; /* Not supported */ + } + + /* conversion of component model values for 4:2:0 and 4:4:4 */ + if (CHROMA_444 != m_chromaFormat && (compCtr > 0)) + { + if (CHROMA_420 == m_chromaFormat) /* 4:2:0 */ + { + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] >>= 1; + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] = + CLIP3(MIN_CUT_OFF_FREQUENCY, MAX_CUT_OFF_FREQUENCY, + (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] << 1)); + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] = + CLIP3(MIN_CUT_OFF_FREQUENCY, MAX_CUT_OFF_FREQUENCY, + (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2] << 1)); + + } + else if (CHROMA_422 == m_chromaFormat)/* 4:2:2 */ + { + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] = + (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[0] * SCALE_DOWN_422) >> Q_FORMAT_SCALING; + + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] = + CLIP3(MIN_CUT_OFF_FREQUENCY, MAX_CUT_OFF_FREQUENCY, + (m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1] << 1)); + } + } + + compPairMatch = 0; + for (limitCompModelCtr = 0; limitCompModelCtr <= num_comp_model_pairs; limitCompModelCtr++) + { + if ((limitCompModelVal1[limitCompModelCtr] == + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1]) && + (limitCompModelVal2[limitCompModelCtr] == + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2])) + { + compPairMatch = 1; + } + } + + if (0 == compPairMatch) + { + num_comp_model_pairs++; + /* max allowed pairs are 10 as per SMPTE -RDD5*/ + if (num_comp_model_pairs > MAX_ALLOWED_COMP_MODEL_PAIRS) + { + return FGS_INVALID_NUM_CUT_OFF_FREQ_PAIRS; /* Not supported */ + } + limitCompModelVal1[num_comp_model_pairs - 1] = + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[1]; + limitCompModelVal2[num_comp_model_pairs - 1] = + m_fgcParameters->m_compModel[compCtr].intensityValues[intensityCtr].compModelValue[2]; + } + } + } + } + return FGS_SUCCESS; /* Success */ +} + +void SEIFilmGrainSynthesizer::deriveFGSBlkSize() +{ + uint32_t picSizeInLumaSamples = m_height * m_width; + if (picSizeInLumaSamples <= (1920 * 1080)) + { + m_fgsBlkSize = BLK_8; + } + else if (picSizeInLumaSamples <= (3840 * 2160)) + { + m_fgsBlkSize = BLK_16; + } + else + { + m_fgsBlkSize = BLK_32; + } +} +void SEIFilmGrainSynthesizer::dataBaseGen() +{ + uint32_t pseudoRandValEhv; + uint8_t h, v; /* Horizaontal and vertical cut off frequencies (+2)*/ + uint32_t ScaleCutOffFh, ScaleCutOffFv, l, r, i, j, k; + int32_t B[DATA_BASE_SIZE][DATA_BASE_SIZE], IDCT[DATA_BASE_SIZE][DATA_BASE_SIZE]; + int32_t Grain[DATA_BASE_SIZE][DATA_BASE_SIZE]; + + const TMatrixCoeff* Tmp = g_trCoreDCT2P64[TRANSFORM_FORWARD][0]; + const int transform_scale = 9; // upscaling of original transform as specified in VVC (for 64x64 block) + const int add_1st = 1 << (transform_scale - 1); + + TMatrixCoeff T[DATA_BASE_SIZE][DATA_BASE_SIZE]; // Original + TMatrixCoeff TT[DATA_BASE_SIZE][DATA_BASE_SIZE]; // Transpose + for (int x = 0; x < DATA_BASE_SIZE; x++) + { + for (int y = 0; y < DATA_BASE_SIZE; y++) + { + T[x][y] = Tmp[x * 64 + y]; /* Matrix Original */ + TT[y][x] = Tmp[x * 64 + y]; /* Matrix Transpose */ + } + } + + for (h = 0; h < NUM_CUT_OFF_FREQ; h++) + { + for (v = 0; v < NUM_CUT_OFF_FREQ; v++) + { + memset(&B, 0, DATA_BASE_SIZE*DATA_BASE_SIZE * sizeof(int32_t)); + memset(&IDCT, 0, DATA_BASE_SIZE*DATA_BASE_SIZE * sizeof(int32_t)); + memset(&Grain, 0, DATA_BASE_SIZE*DATA_BASE_SIZE * sizeof(int32_t)); + ScaleCutOffFh = ((h + 3) << 2) - 1; + ScaleCutOffFv = ((v + 3) << 2) - 1; + + /* ehv : seed to be used for the psudo random generator for a given h and v */ + pseudoRandValEhv = seedLUT[h + v * 13]; + + for (l = 0, r = 0; l <= ScaleCutOffFv; l++) + { + for (k = 0; k <= ScaleCutOffFh; k += 4) + { + B[k][l] = gaussianLUT[pseudoRandValEhv % 2048]; + B[k + 1][l] = gaussianLUT[(pseudoRandValEhv + 1) % 2048]; + B[k + 2][l] = gaussianLUT[(pseudoRandValEhv + 2) % 2048]; + B[k + 3][l] = gaussianLUT[(pseudoRandValEhv + 3) % 2048]; + r++; + pseudoRandValEhv = prng(pseudoRandValEhv); + } + } + B[0][0] = 0; + + for (i = 0; i < DATA_BASE_SIZE; i++) + { + for (j = 0; j < DATA_BASE_SIZE; j++) + { + for (k = 0; k < DATA_BASE_SIZE; k++) + { + IDCT[i][j] += TT[i][k] * B[k][j]; + } + IDCT[i][j] += add_1st; + IDCT[i][j] = IDCT[i][j] >> transform_scale; + } + } + + for (i = 0; i < DATA_BASE_SIZE; i++) + { + for (j = 0; j < DATA_BASE_SIZE; j++) + { + for (k = 0; k < DATA_BASE_SIZE; k++) + { + Grain[i][j] += IDCT[i][k] * T[k][j]; + } + Grain[i][j] += add_1st; + Grain[i][j] = Grain[i][j] >> transform_scale; + m_grainSynt->dataBase[h][v][j][i] = CLIP3(-127, 127, Grain[i][j]); + } + } + + /* De-blocking at horizontal block edges */ + for (l = 0; l < DATA_BASE_SIZE; l += m_fgsBlkSize) + { + for (k = 0; k < DATA_BASE_SIZE; k++) + { + m_grainSynt->dataBase[h][v][l][k] = ((m_grainSynt->dataBase[h][v][l][k]) * deblockFactor[v]) >> 7; + m_grainSynt->dataBase[h][v][l + m_fgsBlkSize - 1][k] = ((m_grainSynt->dataBase[h][v][l + m_fgsBlkSize - 1][k]) * deblockFactor[v]) >> 7; + } + } + } + } + return; +} + +uint32_t SEIFilmGrainSynthesizer::prng(uint32_t x_r) +{ + uint32_t addVal; + addVal = (1 + ((x_r & (POS_2)) > 0) + ((x_r & (POS_30)) > 0)) % 2; + x_r = (x_r << 1) + addVal; + return x_r; +} + +uint32_t SEIFilmGrainSynthesizer::fgsProcess(fgsProcessArgs &inArgs) +{ + uint32_t errorCode; + uint8_t blkSize = inArgs.blkSize; + + if (blkSize == 8) + errorCode = fgsSimulationBlending_8x8(&inArgs); + else if (blkSize == 16) + errorCode = fgsSimulationBlending_16x16(&inArgs); + else if (blkSize == 32) + errorCode = fgsSimulationBlending_32x32(&inArgs); + else + errorCode = FGS_FAIL; + + return errorCode; +} + +void SEIFilmGrainSynthesizer::deblockGrainStripe(Pel *grainStripe, uint32_t widthComp, uint32_t heightComp, + uint32_t strideComp, uint32_t blkSize) +{ + int32_t left1, left0, right0, right1; + uint32_t pos, vertCtr; + + uint32_t widthCropped = (widthComp - blkSize); + + for (vertCtr = 0; vertCtr < heightComp; vertCtr++) + { + for (pos = 0; pos < widthCropped; pos += blkSize) + { + left1 = *(grainStripe + blkSize - 2); + left0 = *(grainStripe + blkSize - 1); + right0 = *(grainStripe + blkSize + 0); + right1 = *(grainStripe + blkSize + 1); + *(grainStripe + blkSize + 0) = (left0 + (right0 << 1) + right1) >> 2; + *(grainStripe + blkSize - 1) = (left1 + (left0 << 1) + right0) >> 2; + grainStripe += blkSize; + } + grainStripe = grainStripe + (strideComp - pos); + } + return; +} + +void SEIFilmGrainSynthesizer::blendStripe(Pel *decSampleHbdOffsetY, Pel *grainStripe, uint32_t widthComp, + uint32_t strideSrc, uint32_t strideGrain, uint32_t blockHeight, uint8_t bitDepth) +{ + uint32_t k, l; + uint16_t maxRange; + maxRange = (1 << bitDepth) - 1; + + int32_t grainSample; + uint16_t decodeSampleHbd; + uint8_t bitDepthShift = (bitDepth - BIT_DEPTH_8); + uint32_t bufInc = (strideSrc - widthComp); + uint32_t grainBufInc = (strideGrain - widthComp); + + for (l = 0; l < blockHeight; l++) /* y direction */ + { + for (k = 0; k < widthComp; k++) /* x direction */ + { + decodeSampleHbd = *decSampleHbdOffsetY; + grainSample = *grainStripe; + grainSample <<= bitDepthShift; + grainSample = CLIP3(0, maxRange, grainSample + decodeSampleHbd); + *decSampleHbdOffsetY = (Pel)grainSample; + decSampleHbdOffsetY++; + grainStripe++; + } + decSampleHbdOffsetY += bufInc; + grainStripe += grainBufInc; + } + return; +} + +void SEIFilmGrainSynthesizer::blendStripe_32x32(Pel *decSampleHbdOffsetY, Pel *grainStripe, uint32_t widthComp, + uint32_t strideSrc, uint32_t strideGrain, uint32_t blockHeight, uint8_t bitDepth) +{ + uint32_t k, l; + uint16_t maxRange; + maxRange = (1 << bitDepth) - 1; + + int32_t grainSample; + uint16_t decodeSampleHbd; + uint8_t bitDepthShift = (bitDepth - BIT_DEPTH_8); + uint32_t bufInc = (strideSrc - widthComp); + uint32_t grainBufInc = (strideGrain - widthComp); + + for (l = 0; l < blockHeight; l++) /* y direction */ + { + for (k = 0; k < widthComp; k++) /* x direction */ + { + decodeSampleHbd = *decSampleHbdOffsetY; + grainSample = *grainStripe; + grainSample <<= bitDepthShift; + grainSample = CLIP3(0, maxRange, grainSample + decodeSampleHbd); + *decSampleHbdOffsetY = (Pel)grainSample; + decSampleHbdOffsetY++; + grainStripe++; + } + decSampleHbdOffsetY += bufInc; + grainStripe += grainBufInc; + } + return; +} + +Pel SEIFilmGrainSynthesizer::blockAverage_8x8(Pel *decSampleBlk8, uint32_t widthComp, uint16_t *pNumSamples, + uint8_t ySize, uint8_t xSize, uint8_t bitDepth) +{ + uint32_t blockAvg = 0; + uint8_t k; + uint8_t l; + for (k = 0; k < ySize; k++) + { + for (l = 0; l < xSize; l++) + { + blockAvg += *decSampleBlk8; + decSampleBlk8++; + } + decSampleBlk8 += widthComp - xSize; + } + + blockAvg = blockAvg >> (BLK_8_shift + (bitDepth - BIT_DEPTH_8)); + *pNumSamples = BLK_AREA_8x8; + + return blockAvg; +} + +uint32_t SEIFilmGrainSynthesizer::blockAverage_16x16(Pel *decSampleBlk8, uint32_t widthComp, uint16_t *pNumSamples, + uint8_t ySize, uint8_t xSize, uint8_t bitDepth) +{ + uint32_t blockAvg = 0; + uint8_t k; + uint8_t l; + for (k = 0; k < ySize; k++) + { + for (l = 0; l < xSize; l++) + { + blockAvg += *decSampleBlk8; + decSampleBlk8++; + } + decSampleBlk8 += widthComp - xSize; + } + + // blockAvg = blockAvg >> (BLK_16_shift + (bitDepth - BIT_DEPTH_8)); + // If BLK_16 is not used or changed BLK_AREA_16x16 has to be changed + *pNumSamples = BLK_AREA_16x16; + return blockAvg; +} + +uint32_t SEIFilmGrainSynthesizer::blockAverage_32x32(Pel *decSampleBlk32, uint32_t strideComp, uint8_t bitDepth) +{ + uint32_t blockAvg = 0; + uint8_t k; + uint8_t l; + uint32_t bufInc = strideComp - BLK_32; + for (k = 0; k < BLK_32; k++) + { + for (l = 0; l < BLK_32; l++) + { + blockAvg += *decSampleBlk32++; + } + decSampleBlk32 += bufInc; + } + blockAvg = blockAvg >> (BLK_32_shift + (bitDepth - BIT_DEPTH_8)); + return blockAvg; +} + +void SEIFilmGrainSynthesizer::simulateGrainBlk8x8(Pel *grainStripe, uint32_t grainStripeOffsetBlk8, + GrainSynthesisStruct *grain_synt, uint32_t width, + uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset, + uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize) +{ + uint32_t l; + int8_t * database_h_v = &grain_synt->dataBase[h][v][lOffset][kOffset]; + grainStripe += grainStripeOffsetBlk8; + uint32_t k; + for (l = 0; l < BLK_8; l++) /* y direction */ + { + for (k = 0; k < xSize; k++) /* x direction */ + { + *grainStripe = ((scaleFactor * (*database_h_v)) >> (log2ScaleFactor + GRAIN_SCALE)); + grainStripe++; + database_h_v++; + } + grainStripe += width - xSize; + database_h_v += DATA_BASE_SIZE - xSize; + } + return; +} + +void SEIFilmGrainSynthesizer::simulateGrainBlk16x16(Pel *grainStripe, uint32_t grainStripeOffsetBlk8, + GrainSynthesisStruct *grain_synt, uint32_t width, + uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset, + uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize) +{ + uint32_t l; + int8_t * database_h_v = &grain_synt->dataBase[h][v][lOffset][kOffset]; + grainStripe += grainStripeOffsetBlk8; + uint32_t k; + for (l = 0; l < BLK_16; l++) /* y direction */ + { + for (k = 0; k < xSize; k++) /* x direction */ + { + *grainStripe = (int16_t)(((int32_t)scaleFactor * (*database_h_v)) >> (log2ScaleFactor + GRAIN_SCALE)); + grainStripe++; + database_h_v++; + } + grainStripe += width - xSize; + database_h_v += DATA_BASE_SIZE - xSize; + } + return; +} + +void SEIFilmGrainSynthesizer::simulateGrainBlk32x32(Pel *grainStripe, uint32_t grainStripeOffsetBlk32, + GrainSynthesisStruct *grain_synt, uint32_t width, + uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset, + uint32_t lOffset, uint8_t h, uint8_t v) +{ + uint32_t l; + int8_t * database_h_v = &grain_synt->dataBase[h][v][lOffset][kOffset]; + grainStripe += grainStripeOffsetBlk32; + uint32_t k; + uint8_t shiftVal = log2ScaleFactor + GRAIN_SCALE; + uint32_t grainbufInc = width - BLK_32; + + for (l = 0; l < BLK_32; l++) /* y direction */ + { + for (k = 0; k < BLK_32; k++) /* x direction */ + { + *grainStripe = ((scaleFactor * (*database_h_v)) >> shiftVal); + grainStripe++; + database_h_v++; + } + grainStripe += grainbufInc; + database_h_v += DATA_BASE_SIZE - BLK_32; + } + return; +} + +uint32_t SEIFilmGrainSynthesizer::fgsSimulationBlending_8x8(fgsProcessArgs *inArgs) +{ + uint8_t numComp, compCtr, blkId; /* number of color components */ + uint8_t log2ScaleFactor, h, v; + uint8_t bitDepth; /*grain bit depth and decoded bit depth are assumed to be same */ + uint32_t widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT], strideComp[MAX_NUM_COMPONENT]; + Pel * decSampleHbdBlk16, *decSampleHbdBlk8, *decSampleHbdOffsetY; + Pel * decHbdComp[MAX_NUM_COMPONENT]; + uint16_t numSamples; + int16_t scaleFactor; + uint32_t kOffset, lOffset, grainStripeOffset, grainStripeOffsetBlk8, offsetBlk8x8; + uint32_t kOffset_const, lOffset_const; + int16_t scaleFactor_const; + Pel * grainStripe; /* worth a row of 16x16 : Max size : 16xw;*/ + int32_t yOffset8x8, xOffset8x8; + uint32_t x, y; + uint32_t blockAvg, intensityInt; /* ec : seed to be used for the psudo random generator for a given color component */ + uint32_t grainStripeWidth; + uint32_t wdPadded; + + bitDepth = inArgs->bitDepth; + numComp = inArgs->numComp; + log2ScaleFactor = inArgs->pFgcParameters->m_log2ScaleFactor; + + for (compCtr = 0; compCtr < numComp; compCtr++) + { + decHbdComp[compCtr] = inArgs->decComp[compCtr]; + strideComp[compCtr] = inArgs->strideComp[compCtr]; + widthComp[compCtr] = inArgs->widthComp[compCtr]; + heightComp[compCtr] = inArgs->heightComp[compCtr]; + } + + wdPadded = ((inArgs->widthComp[0] - 1) | 0xF) + 1; + grainStripe = new Pel[wdPadded * BLK_16]; + + if (0 == inArgs->pFgcParameters->m_filmGrainCharacteristicsCancelFlag) + { + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (1 == inArgs->pFgcParameters->m_compModel[compCtr].presentFlag) + { + decSampleHbdOffsetY = decHbdComp[compCtr]; + uint32_t *offset_tmp = inArgs->fgsOffsets[compCtr]; + grainStripeWidth = ((widthComp[compCtr] - 1) | 0xF) + 1; // Make next muliptle of 16 + + /* Loop of 16x16 blocks */ + for (y = 0; y < heightComp[compCtr]; y += BLK_16) + { + /* Initialization of grain stripe of 16xwidth size */ + memset(grainStripe, 0, (grainStripeWidth * BLK_16 * sizeof(Pel))); + for (x = 0; x < widthComp[compCtr]; x += BLK_16) + { + /* start position offset of decoded sample in x direction */ + grainStripeOffset = x; + + decSampleHbdBlk16 = decSampleHbdOffsetY + x; + + kOffset_const = (MSB16(*offset_tmp) % 52); + kOffset_const &= 0xFFFC; + + lOffset_const = (LSB16(*offset_tmp) % 56); + lOffset_const &= 0xFFF8; + scaleFactor_const = 1 - 2 * BIT0(*offset_tmp); + for (blkId = 0; blkId < NUM_8x8_BLKS_16x16; blkId++) + { + yOffset8x8 = (blkId >> 1) * BLK_8; + xOffset8x8 = (blkId & 0x1) * BLK_8; + offsetBlk8x8 = xOffset8x8 + (yOffset8x8 * strideComp[compCtr]); + + grainStripeOffsetBlk8 = grainStripeOffset + (xOffset8x8 + (yOffset8x8 * grainStripeWidth)); + + decSampleHbdBlk8 = decSampleHbdBlk16 + offsetBlk8x8; + blockAvg = blockAverage_8x8(decSampleHbdBlk8, strideComp[compCtr], &numSamples, BLK_8, BLK_8, bitDepth); + + /* Selection of the component model */ + intensityInt = inArgs->pGrainSynt->intensityInterval[compCtr][blockAvg]; + + if (INTENSITY_INTERVAL_MATCH_FAIL != intensityInt) + { + /* 8x8 grain block offset using co-ordinates of decoded 8x8 block in the frame */ + // kOffset = kOffset_const; + kOffset = kOffset_const + xOffset8x8; + + lOffset = lOffset_const + yOffset8x8; + + scaleFactor = + scaleFactor_const + * inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[0]; + h = inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[1] - 2; + v = inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[2] - 2; + + /* 8x8 block grain simulation */ + simulateGrainBlk8x8(grainStripe, grainStripeOffsetBlk8, inArgs->pGrainSynt, grainStripeWidth, + log2ScaleFactor, scaleFactor, kOffset, lOffset, h, v, BLK_8); + } /* only if average falls in any interval */ + // }/* includes corner case handling */ + } /* 8x8 level block processing */ + + /* uppdate the PRNG once per 16x16 block of samples */ + offset_tmp++; + } /* End of 16xwidth grain simulation */ + + /* deblocking at the vertical edges of 8x8 at 16xwidth*/ + deblockGrainStripe(grainStripe, widthComp[compCtr], BLK_16, grainStripeWidth, BLK_8); + + /* Blending of size 16xwidth*/ + + blendStripe(decSampleHbdOffsetY, grainStripe, widthComp[compCtr], strideComp[compCtr], grainStripeWidth, + BLK_16, bitDepth); + decSampleHbdOffsetY += BLK_16 * strideComp[compCtr]; + + } /* end of component loop */ + } + } + } + + delete grainStripe; + return FGS_SUCCESS; +} + +uint32_t SEIFilmGrainSynthesizer::fgsSimulationBlending_16x16(fgsProcessArgs *inArgs) +{ + uint8_t numComp, compCtr; /* number of color components */ + uint8_t log2ScaleFactor, h, v; + uint8_t bitDepth; /*grain bit depth and decoded bit depth are assumed to be same */ + uint32_t widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT], strideComp[MAX_NUM_COMPONENT]; + Pel * decSampleHbdBlk16, *decSampleHbdOffsetY; + Pel * decHbdComp[MAX_NUM_COMPONENT]; + uint16_t numSamples; + int16_t scaleFactor; + uint32_t kOffset, lOffset, grainStripeOffset; + Pel * grainStripe; /* worth a row of 16x16 : Max size : 16xw;*/ + uint32_t x, y; + uint32_t blockAvg, intensityInt; /* ec : seed to be used for the psudo random generator for a given color component */ + uint32_t grainStripeWidth; + uint32_t wdPadded; + + bitDepth = inArgs->bitDepth; + numComp = inArgs->numComp; + log2ScaleFactor = inArgs->pFgcParameters->m_log2ScaleFactor; + + for (compCtr = 0; compCtr < numComp; compCtr++) + { + decHbdComp[compCtr] = inArgs->decComp[compCtr]; + strideComp[compCtr] = inArgs->strideComp[compCtr]; + widthComp[compCtr] = inArgs->widthComp[compCtr]; + heightComp[compCtr] = inArgs->heightComp[compCtr]; + } + + wdPadded = ((inArgs->widthComp[0] - 1) | 0xF) + 1; + grainStripe = new Pel[wdPadded * BLK_16]; + + if (0 == inArgs->pFgcParameters->m_filmGrainCharacteristicsCancelFlag) + { + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (1 == inArgs->pFgcParameters->m_compModel[compCtr].presentFlag) + { + decSampleHbdOffsetY = decHbdComp[compCtr]; + uint32_t *offset_tmp = inArgs->fgsOffsets[compCtr]; + grainStripeWidth = ((widthComp[compCtr] - 1) | 0xF) + 1; // Make next muliptle of 16 + + /* Loop of 16x16 blocks */ + for (y = 0; y < heightComp[compCtr]; y += BLK_16) + { + /* Initialization of grain stripe of 16xwidth size */ + memset(grainStripe, 0, (grainStripeWidth * BLK_16 * sizeof(Pel))); + for (x = 0; x < widthComp[compCtr]; x += BLK_16) + { + /* start position offset of decoded sample in x direction */ + grainStripeOffset = x; + + decSampleHbdBlk16 = decSampleHbdOffsetY + x; + + blockAvg = + blockAverage_16x16(decSampleHbdBlk16, strideComp[compCtr], &numSamples, BLK_16, BLK_16, bitDepth); + blockAvg = blockAvg >> (BLK_16_shift + (bitDepth - BIT_DEPTH_8)); + /* Selection of the component model */ + intensityInt = inArgs->pGrainSynt->intensityInterval[compCtr][blockAvg]; + + if (INTENSITY_INTERVAL_MATCH_FAIL != intensityInt) + { + kOffset = (MSB16(*offset_tmp) % 52); + kOffset &= 0xFFFC; + + lOffset = (LSB16(*offset_tmp) % 56); + lOffset &= 0xFFF8; + scaleFactor = 1 - 2 * BIT0(*offset_tmp); + + scaleFactor *= + inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[0]; + h = inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[1] - 2; + v = inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[2] - 2; + + /* 16x16 block grain simulation */ + simulateGrainBlk16x16(grainStripe, grainStripeOffset, inArgs->pGrainSynt, grainStripeWidth, + log2ScaleFactor, scaleFactor, kOffset, lOffset, h, v, BLK_16); + + } /* only if average falls in any interval */ + // }/* includes corner case handling */ + /* uppdate the PRNG once per 16x16 block of samples */ + offset_tmp++; + } /* End of 16xwidth grain simulation */ + /* deblocking at the vertical edges of 16x16 at 16xwidth*/ + deblockGrainStripe(grainStripe, widthComp[compCtr], BLK_16, grainStripeWidth, BLK_16); + + /* Blending of size 16xwidth*/ + blendStripe(decSampleHbdOffsetY, grainStripe, widthComp[compCtr], strideComp[compCtr], grainStripeWidth, + BLK_16, bitDepth); + decSampleHbdOffsetY += BLK_16 * strideComp[compCtr]; + + } /* end of component loop */ + } + } + } + + delete grainStripe; + return FGS_SUCCESS; +} + +uint32_t SEIFilmGrainSynthesizer::fgsSimulationBlending_32x32(fgsProcessArgs *inArgs) +{ + uint8_t numComp, compCtr; /* number of color components */ + uint8_t log2ScaleFactor, h, v; + uint8_t bitDepth; /*grain bit depth and decoded bit depth are assumed to be same */ + uint32_t widthComp[MAX_NUM_COMPONENT], heightComp[MAX_NUM_COMPONENT], strideComp[MAX_NUM_COMPONENT]; + Pel * decSampleBlk32, *decSampleOffsetY; + Pel * decComp[MAX_NUM_COMPONENT]; + int16_t scaleFactor; + uint32_t kOffset, lOffset, grainStripeOffset; + Pel * grainStripe; + uint32_t x, y; + uint32_t blockAvg, intensityInt; /* ec : seed to be used for the psudo random generator for a given color component */ + uint32_t grainStripeWidth; + uint32_t wdPadded; + + bitDepth = inArgs->bitDepth; + numComp = inArgs->numComp; + + log2ScaleFactor = inArgs->pFgcParameters->m_log2ScaleFactor; + + for (compCtr = 0; compCtr < numComp; compCtr++) + { + decComp[compCtr] = inArgs->decComp[compCtr]; + strideComp[compCtr] = inArgs->strideComp[compCtr]; + heightComp[compCtr] = inArgs->heightComp[compCtr]; + widthComp[compCtr] = inArgs->widthComp[compCtr]; + } + + wdPadded = ((inArgs->widthComp[0] - 1) | 0x1F) + 1; + grainStripe = new Pel[wdPadded * BLK_32]; + + if (0 == inArgs->pFgcParameters->m_filmGrainCharacteristicsCancelFlag) + { + for (compCtr = 0; compCtr < numComp; compCtr++) + { + if (1 == inArgs->pFgcParameters->m_compModel[compCtr].presentFlag) + { + uint32_t *offset_tmp = inArgs->fgsOffsets[compCtr]; + decSampleOffsetY = decComp[compCtr]; + grainStripeWidth = ((widthComp[compCtr] - 1) | 0x1F) + 1; // Make next muliptle of 32 + + /* Loop of 32x32 blocks */ + for (y = 0; y < heightComp[compCtr]; y += BLK_32) + { + /* Initialization of grain stripe of 32xwidth size */ + memset(grainStripe, 0, (grainStripeWidth * BLK_32 * sizeof(Pel))); + for (x = 0; x < widthComp[compCtr]; x += BLK_32) + { + /* start position offset of decoded sample in x direction */ + grainStripeOffset = x; + decSampleBlk32 = decSampleOffsetY + x; + blockAvg = blockAverage_32x32(decSampleBlk32, strideComp[compCtr], bitDepth); + + /* Selection of the component model */ + intensityInt = inArgs->pGrainSynt->intensityInterval[compCtr][blockAvg]; + + if (INTENSITY_INTERVAL_MATCH_FAIL != intensityInt) + { + kOffset = (MSB16(*offset_tmp) % 36); + kOffset &= 0xFFFC; + + lOffset = (LSB16(*offset_tmp) % 40); + lOffset &= 0xFFF8; + scaleFactor = 1 - 2 * BIT0(*offset_tmp); + + scaleFactor *= inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[0]; + h = inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[1] - 2; + v = inArgs->pFgcParameters->m_compModel[compCtr].intensityValues[intensityInt].compModelValue[2] - 2; + + /* 32x32 block grain simulation */ + simulateGrainBlk32x32(grainStripe, grainStripeOffset, inArgs->pGrainSynt, grainStripeWidth, + log2ScaleFactor, scaleFactor, kOffset, lOffset, h, v); + + } /* only if average falls in any interval */ + + /* uppdate the PRNG once per 16x16 block of samples */ + offset_tmp++; + } /* End of 32xwidth grain simulation */ + + /* deblocking at the vertical edges of 8x8 at 16xwidth*/ + deblockGrainStripe(grainStripe, widthComp[compCtr], BLK_32, grainStripeWidth, BLK_32); + + blendStripe_32x32(decSampleOffsetY, grainStripe, widthComp[compCtr], strideComp[compCtr], grainStripeWidth, BLK_32, bitDepth); + decSampleOffsetY += BLK_32 * strideComp[compCtr]; + } /* end of component loop */ + } + } + } + + delete grainStripe; + return FGS_SUCCESS; +} + +#endif \ No newline at end of file diff --git a/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h new file mode 100644 index 0000000000000000000000000000000000000000..0436a12235c1b301144605e64262f2e7b27804f9 --- /dev/null +++ b/source/Lib/CommonLib/SEIFilmGrainSynthesizer.h @@ -0,0 +1,236 @@ +/* The copyright in this software is being made available under the BSD + * License, included below. This software may be subject to other third party + * and contributor rights, including patent rights, and no such rights are + * granted under this license. + * + * Copyright (c) 2010-2021, ITU/ISO/IEC + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** \file SEIFilmGrainSynthesizer.h + \brief SMPTE RDD5 based film grain synthesis functionality from SEI messages +*/ + +#ifndef __SEIFILMGRAINSYNTHESIZER__ +#define __SEIFILMGRAINSYNTHESIZER__ + +#include "SEI.h" +#include "Unit.h" +#include "Buffer.h" +#include "Unit.h" + +#include "TrQuant_EMT.h" + +#if JVET_X0048_X0103_FILM_GRAIN + +//! \ingroup SEIFilmGrainSynthesizer +//! \{ + +// ==================================================================================================================== +// Class definition +// ==================================================================================================================== + +static const int MIN_LOG2SCALE_VALUE = 2; +static const int MAX_LOG2SCALE_VALUE = 7; +static const int FILM_GRAIN_MODEL_ID_VALUE = 0; +static const int BLENDING_MODE_VALUE = 0; +static const int MIN_CUT_OFF_FREQUENCY = 2; +static const int MAX_CUT_OFF_FREQUENCY = 14; +static const int DEFAULT_HORZ_CUT_OFF_FREQUENCY = 8; +static const int NUM_CUT_OFF_FREQ = 13; + +static const int SCALE_DOWN_422 = 181; /* in Q-format of 8 : 1/sqrt(2) */ +static const int Q_FORMAT_SCALING = 8; +static const int GRAIN_SCALE = 6; +static const int MIN_CHROMA_FORMAT_IDC = 0; +static const int MAX_CHROMA_FORMAT_IDC = 3; +static const int MIN_BIT_DEPTH = 8; +static const int MAX_BIT_DEPTH = 16; +static const int BLK_8_shift = 6; +static const int BLK_16_shift = 8; +static const int BLK_32_shift = 10; +static const int NUM_8x8_BLKS_16x16 = 4; +static const int NUM_16x16_BLKS_32x32 = 4; +static const int BLK_AREA_8x8 = 64; +static const int BLK_AREA_16x16 = 256; +static const int INTENSITY_INTERVAL_MATCH_FAIL = -1; +static const int COLOUR_OFFSET_LUMA = 0; +static const int COLOUR_OFFSET_CR = 85; +static const int COLOUR_OFFSET_CB = 170; + +static const int MIN_WIDTH = 128; +static const int MAX_WIDTH = 7680; +static const int MIN_HEIGHT = 128; +static const int MAX_HEIGHT = 4320; + +#define CLIP3(min, max, x) (((x) > (max)) ? (max) :(((x) < (min))? (min):(x))) +#define MIN(x,y) (((x) > (y)) ? (y) : (x)) +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) +#define MSB16(x) ((x&0xFFFF0000)>>16) +#define LSB16(x) (x&0x0000FFFF) +#define BIT0(x) (x&0x1) +#define POS_30 (1<<30) +#define POS_2 (1<<2) + +/* Error start codes for various classes of errors */ +#define FGS_FILE_IO_ERROR 0x0010 +#define FGS_PARAM_ERROR 0x0020 + +/* Error codes for various errors in SMPTE-RDD5 standalone grain synthesizer */ +typedef enum +{ + /* No error */ + FGS_SUCCESS = 0, + /* Invalid input width */ + FGS_INVALID_WIDTH = FGS_FILE_IO_ERROR + 0x01, + /* Invalid input height */ + FGS_INVALID_HEIGHT = FGS_FILE_IO_ERROR + 0x02, + /* Invalid Chroma format idc */ + FGS_INVALID_CHROMA_FORMAT = FGS_FILE_IO_ERROR + 0x03, + /* Invalid bit depth */ + FGS_INVALID_BIT_DEPTH = FGS_FILE_IO_ERROR + 0x04, + /* Invalid Film grain characteristic cancel flag */ + FGS_INVALID_FGC_CANCEL_FLAG = FGS_PARAM_ERROR + 0x01, + /* Invalid film grain model id */ + FGS_INVALID_GRAIN_MODEL_ID = FGS_PARAM_ERROR + 0x02, + /* Invalid separate color description present flag */ + FGS_INVALID_SEP_COL_DES_FLAG = FGS_PARAM_ERROR + 0x03, + /* Invalid blending mode */ + FGS_INVALID_BLEND_MODE = FGS_PARAM_ERROR + 0x04, + /* Invalid log_2_scale_factor value */ + FGS_INVALID_LOG2_SCALE_FACTOR = FGS_PARAM_ERROR + 0x05, + /* Invalid component model present flag */ + FGS_INVALID_COMP_MODEL_PRESENT_FLAG = FGS_PARAM_ERROR + 0x06, + /* Invalid number of model values */ + FGS_INVALID_NUM_MODEL_VALUES = FGS_PARAM_ERROR + 0x07, + /* Invalid bound values, overlapping boundaries */ + FGS_INVALID_INTENSITY_BOUNDARY_VALUES = FGS_PARAM_ERROR + 0x08, + /* Invalid standard deviation */ + FGS_INVALID_STANDARD_DEVIATION = FGS_PARAM_ERROR + 0x09, + /* Invalid cut off frequencies */ + FGS_INVALID_CUT_OFF_FREQUENCIES = FGS_PARAM_ERROR + 0x0A, + /* Invalid number of cut off frequency pairs */ + FGS_INVALID_NUM_CUT_OFF_FREQ_PAIRS = FGS_PARAM_ERROR + 0x0B, + /* Invalid film grain characteristics repetition period */ + FGS_INVALID_FGC_REPETETION_PERIOD = FGS_PARAM_ERROR + 0x0C, + + /* Failure error code */ + FGS_FAIL = 0xFF +}FGS_ERROR_T; +/* FGC Error Codes END */ + +typedef struct GrainSynthesisStruct_t +{ + int8_t dataBase[NUM_CUT_OFF_FREQ][NUM_CUT_OFF_FREQ][DATA_BASE_SIZE][DATA_BASE_SIZE]; + int16_t intensityInterval[MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; +}GrainSynthesisStruct; + +typedef struct fgsProcessArgs +{ + uint8_t numComp; + uint32_t * fgsOffsets[MAX_NUM_COMPONENT]; + Pel * decComp[MAX_NUM_COMPONENT]; + uint32_t widthComp[MAX_NUM_COMPONENT]; + uint32_t heightComp[MAX_NUM_COMPONENT]; + uint32_t strideComp[MAX_NUM_COMPONENT]; + SEIFilmGrainCharacteristics *pFgcParameters; + GrainSynthesisStruct * pGrainSynt; + uint8_t bitDepth; + uint8_t blkSize; +} fgsProcessArgs; + +class SEIFilmGrainSynthesizer +{ + +private: + uint32_t m_width; + uint32_t m_height; + ChromaFormat m_chromaFormat; + uint8_t m_bitDepth; + uint32_t m_idrPicId; + + fgsProcessArgs m_fgsArgs; + GrainSynthesisStruct *m_grainSynt; + uint8_t m_fgsBlkSize; + +public: + uint32_t m_poc; + int32_t m_errorCode; + SEIFilmGrainCharacteristics *m_fgcParameters; + +public: + SEIFilmGrainSynthesizer(); + virtual ~SEIFilmGrainSynthesizer(); + + void create(uint32_t width, uint32_t height, ChromaFormat fmt, uint8_t bitDepth, uint32_t idrPicId); + void destroy (); + + void fgsInit (); + void grainSynthesizeAndBlend (PelStorage* pGrainBuf, bool isIdrPic); + uint8_t grainValidateParams (); + +private: + void deriveFGSBlkSize (); + void dataBaseGen (); + static uint32_t prng (uint32_t x_r); + static uint32_t fgsProcess (fgsProcessArgs &inArgs); + + static void deblockGrainStripe (Pel *grainStripe, uint32_t widthComp, uint32_t heightComp, uint32_t strideComp, + uint32_t blkSize); + static void blendStripe (Pel *decSampleOffsetY, Pel *grainStripe, uint32_t widthComp, uint32_t strideSrc, + uint32_t strideGrain, uint32_t blockHeight, uint8_t bitDepth); + static void blendStripe_32x32 (Pel *decSampleOffsetY, Pel *grainStripe, uint32_t widthComp, uint32_t strideSrc, + uint32_t strideGrain, uint32_t blockHeight, uint8_t bitDepth); + + static Pel blockAverage_8x8 (Pel *decSampleBlk8, uint32_t widthComp, uint16_t *pNumSamples, uint8_t ySize, + uint8_t xSize, uint8_t bitDepth); + static uint32_t blockAverage_16x16 (Pel *decSampleBlk8, uint32_t widthComp, uint16_t *pNumSamples, uint8_t ySize, + uint8_t xSize, uint8_t bitDepth); + static uint32_t blockAverage_32x32 (Pel *decSampleBlk32, uint32_t strideComp, uint8_t bitDepth); + + static void simulateGrainBlk8x8 (Pel *grainStripe, uint32_t grainStripeOffsetBlk8, GrainSynthesisStruct *pGrainSynt, + uint32_t width, uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset, + uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize); + static void simulateGrainBlk16x16(Pel *grainStripe, uint32_t grainStripeOffsetBlk8, GrainSynthesisStruct *grain_synt, + uint32_t width, uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset, + uint32_t lOffset, uint8_t h, uint8_t v, uint32_t xSize); + static void simulateGrainBlk32x32(Pel *grainStripe, uint32_t grainStripeOffsetBlk32, GrainSynthesisStruct *grain_synt, + uint32_t width, uint8_t log2ScaleFactor, int16_t scaleFactor, uint32_t kOffset, + uint32_t lOffset, uint8_t h, uint8_t v); + + static uint32_t fgsSimulationBlending_8x8 (fgsProcessArgs *inArgs); + static uint32_t fgsSimulationBlending_16x16 (fgsProcessArgs *inArgs); + static uint32_t fgsSimulationBlending_32x32 (fgsProcessArgs *inArgs); + +};// END CLASS DEFINITION SEIFilmGrainSynthesizer + +//! \} +#endif + +#endif // __SEIFILMGRAINSYNTHESIZER__ + + diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h index 422d1fb63316364daf2477af7f820fea8735d09f..62eb7d2e031c26c3ac75dc6db14ebccf50fedea3 100644 --- a/source/Lib/CommonLib/TypeDef.h +++ b/source/Lib/CommonLib/TypeDef.h @@ -70,6 +70,8 @@ #define JVET_X0101_ADD_WRAPAROUND_CONSTRAINT 1 // JVET-X0101 add WrapAround constraint for Constrained RASL Encoding SEI message +#define JVET_X0048_X0103_FILM_GRAIN 1 // JVET-X0048-X0103: SMPTE RDD-5 based film grain analysis and synthesis model for film grain characterstics (FGC) SEI + //########### place macros to be be kept below this line ############### #define GDR_ENABLED 1 @@ -905,6 +907,9 @@ private: struct BitDepths { +#if JVET_X0048_X0103_FILM_GRAIN + const int &operator[](const ChannelType ch) const { return recon[ch]; } +#endif int recon[MAX_NUM_CHANNEL_TYPE]; ///< the bit depth as indicated in the SPS }; diff --git a/source/Lib/DecoderLib/DecLib.cpp b/source/Lib/DecoderLib/DecLib.cpp index 37ecdf6901553efbacce08ce5765907d34c1c8c5..ce76ebf13c49625dc2fe4bda93f04afdef46fea0 100644 --- a/source/Lib/DecoderLib/DecLib.cpp +++ b/source/Lib/DecoderLib/DecLib.cpp @@ -430,6 +430,10 @@ DecLib::DecLib() , m_prevTid0POC(0) , m_bFirstSliceInPicture(true) , m_firstPictureInSequence(true) +#if JVET_X0048_X0103_FILM_GRAIN + , m_grainCharacteristic() + , m_grainBuf() +#endif , m_colourTranfParams() , m_firstSliceInBitstream(true) , m_isFirstAuInCvs( true ) @@ -1752,6 +1756,9 @@ void DecLib::xActivateParameterSets( const InputNALUnit nalu ) m_pcPic->finalInit(vps, *sps, *pps, picHeader, apss, lmcsAPS, scalinglistAPS); #else m_pcPic->finalInit( vps, *sps, *pps, &m_picHeader, apss, lmcsAPS, scalinglistAPS ); +#endif +#if JVET_X0048_X0103_FILM_GRAIN + m_pcPic->createGrainSynthesizer(m_firstPictureInSequence, &m_grainCharacteristic, &m_grainBuf, pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(), sps->getChromaFormatIdc(), sps->getBitDepth(CHANNEL_TYPE_LUMA)); #endif m_pcPic->createColourTransfProcessor(m_firstPictureInSequence, &m_colourTranfParams, &m_invColourTransfBuf, pps->getPicWidthInLumaSamples(), pps->getPicHeightInLumaSamples(), sps->getChromaFormatIdc(), sps->getBitDepth(CHANNEL_TYPE_LUMA)); m_firstPictureInSequence = false; diff --git a/source/Lib/DecoderLib/DecLib.h b/source/Lib/DecoderLib/DecLib.h index 16622aafb52f4320d4c243c2f7604fed62424ebe..0ec2a4c245bd038dc660343f0bec8064c4dd1639 100644 --- a/source/Lib/DecoderLib/DecLib.h +++ b/source/Lib/DecoderLib/DecLib.h @@ -131,6 +131,10 @@ private: int m_prevTid0POC; bool m_bFirstSliceInPicture; bool m_firstPictureInSequence; +#if JVET_X0048_X0103_FILM_GRAIN + SEIFilmGrainSynthesizer m_grainCharacteristic; + PelStorage m_grainBuf; +#endif SEIColourTransformApply m_colourTranfParams; PelStorage m_invColourTransfBuf; bool m_firstSliceInSequence[MAX_VPS_LAYERS]; diff --git a/source/Lib/DecoderLib/SEIread.cpp b/source/Lib/DecoderLib/SEIread.cpp index 9231353a5772670a6c5c512a3461a74ce6b1bfde..4195a7e7c745504708ed0aa69f0505a457ce92b4 100644 --- a/source/Lib/DecoderLib/SEIread.cpp +++ b/source/Lib/DecoderLib/SEIread.cpp @@ -1343,11 +1343,20 @@ void SEIReader::xParseSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics& s SEIFilmGrainCharacteristics::CompModel &cm = sei.m_compModel[c]; if (cm.presentFlag) { +#if JVET_X0048_X0103_FILM_GRAIN + sei_read_code(pDecodedMessageOutputStream, 8, code, "fg_num_intensity_intervals_minus1[c]"); cm.numIntensityIntervals = code + 1; +#else uint32_t numIntensityIntervals; sei_read_code(pDecodedMessageOutputStream, 8, code, "fg_num_intensity_intervals_minus1[c]"); numIntensityIntervals = code + 1; +#endif sei_read_code(pDecodedMessageOutputStream, 3, code, "fg_num_model_values_minus1[c]"); cm.numModelValues = code + 1; +#if JVET_X0048_X0103_FILM_GRAIN + cm.intensityValues.resize(cm.numIntensityIntervals); + for (uint32_t interval = 0; interval < cm.numIntensityIntervals; interval++) +#else cm.intensityValues.resize(numIntensityIntervals); - for (uint32_t interval = 0; interval<numIntensityIntervals; interval++) + for (uint32_t interval = 0; interval < numIntensityIntervals; interval++) +#endif { SEIFilmGrainCharacteristics::CompModelIntensityValues &cmiv = cm.intensityValues[interval]; sei_read_code(pDecodedMessageOutputStream, 8, code, "fg_intensity_interval_lower_bound[c][i]"); cmiv.intensityIntervalLowerBound = code; diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h index 948b3791a3e6d36b4ae26b7457669f3d5ea5d47b..3305cc82d01c818920e91aca6e35e38b3cddfde7 100644 --- a/source/Lib/EncoderLib/EncCfg.h +++ b/source/Lib/EncoderLib/EncCfg.h @@ -658,6 +658,15 @@ protected: uint8_t m_fgcSEIBlendingModeID; uint8_t m_fgcSEILog2ScaleFactor; bool m_fgcSEICompModelPresent[MAX_NUM_COMPONENT]; +#if JVET_X0048_X0103_FILM_GRAIN + bool m_fgcSEIAnalysisEnabled; + bool m_fgcSEIPerPictureSEI; + uint8_t m_fgcSEINumModelValuesMinus1 [MAX_NUM_COMPONENT]; + uint8_t m_fgcSEINumIntensityIntervalMinus1 [MAX_NUM_COMPONENT]; + uint8_t m_fgcSEIIntensityIntervalLowerBound [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; + uint8_t m_fgcSEIIntensityIntervalUpperBound [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES]; + uint32_t m_fgcSEICompModelValue [MAX_NUM_COMPONENT][MAX_NUM_INTENSITIES][MAX_NUM_MODEL_VALUES]; +#endif // cll SEI bool m_cllSEIEnabled; uint16_t m_cllSEIMaxContentLevel; @@ -1884,6 +1893,23 @@ public: uint8_t getFilmGrainCharactersticsSEILog2ScaleFactor() { return m_fgcSEILog2ScaleFactor; } void setFGCSEICompModelPresent(bool b, int index) { m_fgcSEICompModelPresent[index] = b; } bool getFGCSEICompModelPresent(int index) { return m_fgcSEICompModelPresent[index]; } +#if JVET_X0048_X0103_FILM_GRAIN + bool* getFGCSEICompModelPresent () { return m_fgcSEICompModelPresent; } + void setFilmGrainAnalysisEnabled (bool b) { m_fgcSEIAnalysisEnabled = b; } + bool getFilmGrainAnalysisEnabled () { return m_fgcSEIAnalysisEnabled; } + void setFilmGrainCharactersticsSEIPerPictureSEI(bool b) { m_fgcSEIPerPictureSEI = b; } + bool getFilmGrainCharactersticsSEIPerPictureSEI() { return m_fgcSEIPerPictureSEI; } + void setFGCSEINumIntensityIntervalMinus1 (uint8_t v, int index) { m_fgcSEINumIntensityIntervalMinus1[index] = v; } + uint8_t getFGCSEINumIntensityIntervalMinus1 (int index) { return m_fgcSEINumIntensityIntervalMinus1[index]; } + void setFGCSEINumModelValuesMinus1 (uint8_t v, int index) { m_fgcSEINumModelValuesMinus1[index] = v; } + uint8_t getFGCSEINumModelValuesMinus1 (int index) { return m_fgcSEINumModelValuesMinus1[index]; } + void setFGCSEIIntensityIntervalLowerBound(uint8_t v, int index, int ctr) { m_fgcSEIIntensityIntervalLowerBound[index][ctr] = v; } + uint8_t getFGCSEIIntensityIntervalLowerBound(int index, int ctr) { return m_fgcSEIIntensityIntervalLowerBound[index][ctr]; } + void setFGCSEIIntensityIntervalUpperBound(uint8_t v, int index, int ctr) { m_fgcSEIIntensityIntervalUpperBound[index][ctr] = v; } + uint8_t getFGCSEIIntensityIntervalUpperBound(int index, int ctr) { return m_fgcSEIIntensityIntervalUpperBound[index][ctr]; } + void setFGCSEICompModelValue (uint32_t v, int index, int ctr, int modelCtr) { m_fgcSEICompModelValue[index][ctr][modelCtr] = v; } + uint32_t getFGCSEICompModelValue (int index, int ctr, int modelCtr) { return m_fgcSEICompModelValue[index][ctr][modelCtr]; } +#endif // cll SEI void setCLLSEIEnabled(bool b) { m_cllSEIEnabled = b; } bool getCLLSEIEnabled() { return m_cllSEIEnabled; } diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp index 17a827756e91982e4efd025a2cc8cdfcba7772f7..a39bfbf010134c8eaa41fd3712e6c69111f1ce6c 100644 --- a/source/Lib/EncoderLib/EncGOP.cpp +++ b/source/Lib/EncoderLib/EncGOP.cpp @@ -207,6 +207,12 @@ void EncGOP::destroy() delete m_picOrig; m_picOrig = NULL; } +#if JVET_X0048_X0103_FILM_GRAIN + if (m_pcCfg->getFilmGrainAnalysisEnabled()) + { + m_FGAnalyser.destroy(); + } +#endif } void EncGOP::init ( EncLib* pcEncLib ) @@ -227,6 +233,13 @@ void EncGOP::init ( EncLib* pcEncLib ) m_AUWriterIf = pcEncLib->getAUWriterIf(); + #if JVET_X0048_X0103_FILM_GRAIN + if (m_pcCfg->getFilmGrainAnalysisEnabled()) + { + m_FGAnalyser.init(m_pcCfg->getSourceWidth(), m_pcCfg->getSourceHeight(), m_pcCfg->getChromaFormatIdc(), *(BitDepths *) pcEncLib->getBitDepth(), m_pcCfg->getFGCSEICompModelPresent()); + } +#endif + #if WCG_EXT if (m_pcCfg->getLmcs()) { @@ -739,10 +752,27 @@ void EncGOP::xCreateIRAPLeadingSEIMessages (SEIMessages& seiMessages, const SPS seiMessages.push_back(seiSampleAspectRatioInfo); } // film grain +#if JVET_X0048_X0103_FILM_GRAIN + if (m_pcCfg->getFilmGrainCharactersticsSEIEnabled() && !m_pcCfg->getFilmGrainCharactersticsSEIPerPictureSEI()) +#else if (m_pcCfg->getFilmGrainCharactersticsSEIEnabled()) +#endif { SEIFilmGrainCharacteristics *sei = new SEIFilmGrainCharacteristics; m_seiEncoder.initSEIFilmGrainCharacteristics(sei); +#if JVET_X0048_X0103_FILM_GRAIN + if (m_pcCfg->getFilmGrainAnalysisEnabled()) + { + sei->m_log2ScaleFactor = m_FGAnalyser.getLog2scaleFactor(); + for (int compIdx = 0; compIdx < getNumberValidComponents(m_pcCfg->getChromaFormatIdc()); compIdx++) + { + if (sei->m_compModel[compIdx].presentFlag) + { // higher importance of presentFlag is from cfg file + sei->m_compModel[compIdx] = m_FGAnalyser.getCompModel(compIdx); + } + } + } +#endif seiMessages.push_back(sei); } @@ -885,6 +915,26 @@ void EncGOP::xCreatePerPictureSEIMessages (int picInGOP, SEIMessages& seiMessage delete seiAnnotatedRegions; } } + +#if JVET_X0048_X0103_FILM_GRAIN + if (m_pcCfg->getFilmGrainCharactersticsSEIEnabled() && m_pcCfg->getFilmGrainCharactersticsSEIPerPictureSEI()) + { + SEIFilmGrainCharacteristics *fgcSEI = new SEIFilmGrainCharacteristics; + m_seiEncoder.initSEIFilmGrainCharacteristics(fgcSEI); + if (m_pcCfg->getFilmGrainAnalysisEnabled()) + { + fgcSEI->m_log2ScaleFactor = m_FGAnalyser.getLog2scaleFactor(); + for (int compIdx = 0; compIdx < getNumberValidComponents(m_pcCfg->getChromaFormatIdc()); compIdx++) + { + if (fgcSEI->m_compModel[compIdx].presentFlag) + { // higher importance of presentFlag is from cfg file + fgcSEI->m_compModel[compIdx] = m_FGAnalyser.getCompModel(compIdx); + } + } + } + seiMessages.push_back(fgcSEI); + } +#endif } void EncGOP::xCreateScalableNestingSEI(SEIMessages& seiMessages, SEIMessages& nestedSeiMessages, const std::vector<int> &targetOLSs, const std::vector<int> &targetLayers, const std::vector<uint16_t>& subpicIDs, uint16_t maxSubpicIdInPic) @@ -3614,6 +3664,38 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic, } } +#if JVET_X0048_X0103_FILM_GRAIN + if (m_pcCfg->getFilmGrainAnalysisEnabled()) + { + int picPoc = pcPic->getPOC(); + int filteredFrame = 0; + + if (m_pcCfg->getIntraPeriod() < 1) + { + filteredFrame = 2 * m_pcCfg->getFrameRate(); + } + else + { + filteredFrame = m_pcCfg->getIntraPeriod(); + } + + if (picPoc % filteredFrame == 0) + { + pcPic->m_isMctfFiltered = true; + } + else + { + pcPic->m_isMctfFiltered = false; + } + + if (pcPic->m_isMctfFiltered) + { + m_FGAnalyser.initBufs(pcPic); + m_FGAnalyser.estimate_grain(pcPic); + } + } +#endif + if( encPic || decPic ) { pcSlice = pcPic->slices[0]; diff --git a/source/Lib/EncoderLib/EncGOP.h b/source/Lib/EncoderLib/EncGOP.h index 00efc4224e62bfce67202a81d860f948e3c2660b..9db6e9f0575aa61f20723979fa974ca84f5f189b 100644 --- a/source/Lib/EncoderLib/EncGOP.h +++ b/source/Lib/EncoderLib/EncGOP.h @@ -56,6 +56,9 @@ #if EXTENSION_360_VIDEO #include "AppEncHelper360/TExt360EncGop.h" #endif +#if JVET_X0048_X0103_FILM_GRAIN +#include "CommonLib/SEIFilmGrainAnalyzer.h" +#endif #include "Analyze.h" #include "RateCtrl.h" @@ -148,6 +151,10 @@ private: SEIWriter m_seiWriter; +#if JVET_X0048_X0103_FILM_GRAIN + FGAnalyser m_FGAnalyser; +#endif + Picture * m_picBg; Picture * m_picOrig; int m_bgPOC; diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp index 76ab64ae438cc3c809e97277fde869c92abf09d2..7d818c047eb87eef5383cb92a6627403886d24e2 100644 --- a/source/Lib/EncoderLib/EncLib.cpp +++ b/source/Lib/EncoderLib/EncLib.cpp @@ -476,7 +476,11 @@ void EncLib::deletePicBuffer() m_cListPic.clear(); } +#if JVET_X0048_X0103_FILM_GRAIN +bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYuvTrueOrg, PelStorage* pcPicYuvFilteredOrg, PelStorage* pcPicYuvFilteredOrgForFG, const InputColourSpaceConversion snrCSC, std::list<PelUnitBuf*>& rcListPicYuvRecOut, int& iNumEncoded ) +#else bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYuvTrueOrg, PelStorage* pcPicYuvFilteredOrg, const InputColourSpaceConversion snrCSC, std::list<PelUnitBuf*>& rcListPicYuvRecOut, int& iNumEncoded ) +#endif { if( m_compositeRefEnabled && m_cGOPEncoder.getPicBg()->getSpliceFull() && m_iPOCLast >= 10 && m_iNumPicRcvd == 0 && m_cGOPEncoder.getEncodedLTRef() == false ) { @@ -609,6 +613,12 @@ bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYu { pcPicCurr->M_BUFS( 0, PIC_FILTERED_ORIGINAL ).swap( *pcPicYuvFilteredOrg ); } +#if JVET_X0048_X0103_FILM_GRAIN + if (m_fgcSEIAnalysisEnabled) + { + pcPicCurr->M_BUFS( 0, PIC_FILTERED_ORIGINAL_FG ).swap( *pcPicYuvFilteredOrgForFG ); + } +#endif } #if GDR_ENABLED PicHeader *picHeader = new PicHeader(); @@ -890,7 +900,11 @@ void EncLib::xGetNewPicBuffer ( std::list<PelUnitBuf*>& rcListPicYuvRecOut, Pict if (rpcPic==0) { rpcPic = new Picture; - rpcPic->create( sps.getChromaFormatIdc(), Size( pps.getPicWidthInLumaSamples(), pps.getPicHeightInLumaSamples() ), sps.getMaxCUWidth(), sps.getMaxCUWidth() + 16, false, m_layerId, m_gopBasedTemporalFilterEnabled ); + rpcPic->create( sps.getChromaFormatIdc(), Size( pps.getPicWidthInLumaSamples(), pps.getPicHeightInLumaSamples() ), sps.getMaxCUWidth(), sps.getMaxCUWidth() + 16, false, m_layerId, m_gopBasedTemporalFilterEnabled +#if JVET_X0048_X0103_FILM_GRAIN + , m_fgcSEIAnalysisEnabled +#endif + ); if (m_resChangeInClvsEnabled) { const PPS &pps0 = *m_ppsMap.getPS(0); diff --git a/source/Lib/EncoderLib/EncLib.h b/source/Lib/EncoderLib/EncLib.h index a901258a84180530d3a26cae3487b3afd2c6c052..24119cc12d790f9b72da701a78dca7d206f97b28 100644 --- a/source/Lib/EncoderLib/EncLib.h +++ b/source/Lib/EncoderLib/EncLib.h @@ -208,13 +208,24 @@ public: // ------------------------------------------------------------------------------------------------------------------- /// encode several number of pictures until end-of-sequence +#if JVET_X0048_X0103_FILM_GRAIN bool encodePrep( bool bEos, - PelStorage* pcPicYuvOrg, - PelStorage* pcPicYuvTrueOrg, - PelStorage* pcPicYuvFilteredOrg, - const InputColourSpaceConversion snrCSC, // used for SNR calculations. Picture in original colour space. - std::list<PelUnitBuf*>& rcListPicYuvRecOut, - int& iNumEncoded ); + PelStorage* pcPicYuvOrg, + PelStorage* pcPicYuvTrueOrg, + PelStorage* pcPicYuvFilteredOrg, + PelStorage* pcPicYuvFilteredOrgForFG, + const InputColourSpaceConversion snrCSC, // used for SNR calculations. Picture in original colour space. + std::list<PelUnitBuf*>& rcListPicYuvRecOut, + int& iNumEncoded ); +#else + bool encodePrep( bool bEos, + PelStorage* pcPicYuvOrg, + PelStorage* pcPicYuvTrueOrg, + PelStorage* pcPicYuvFilteredOrg, + const InputColourSpaceConversion snrCSC, // used for SNR calculations. Picture in original colour space. + std::list<PelUnitBuf*>& rcListPicYuvRecOut, + int& iNumEncoded ); +#endif bool encode( const InputColourSpaceConversion snrCSC, // used for SNR calculations. Picture in original colour space. std::list<PelUnitBuf*>& rcListPicYuvRecOut, diff --git a/source/Lib/EncoderLib/SEIEncoder.cpp b/source/Lib/EncoderLib/SEIEncoder.cpp index 77f9627d026837f4ed96b11306bdcdf377bb75c4..0a4732be4b7adaec0dffd8aa7bb4019e6fcd1a6f 100644 --- a/source/Lib/EncoderLib/SEIEncoder.cpp +++ b/source/Lib/EncoderLib/SEIEncoder.cpp @@ -742,6 +742,24 @@ void SEIEncoder::initSEIFilmGrainCharacteristics(SEIFilmGrainCharacteristics *se for (int i = 0; i < MAX_NUM_COMPONENT; i++) { seiFilmGrain->m_compModel[i].presentFlag = m_pcCfg->getFGCSEICompModelPresent(i); +#if JVET_X0048_X0103_FILM_GRAIN + if (seiFilmGrain->m_compModel[i].presentFlag) + { + seiFilmGrain->m_compModel[i].numModelValues = 1 + m_pcCfg->getFGCSEINumModelValuesMinus1(i); + seiFilmGrain->m_compModel[i].numIntensityIntervals = 1 + m_pcCfg->getFGCSEINumIntensityIntervalMinus1(i); + seiFilmGrain->m_compModel[i].intensityValues.resize(seiFilmGrain->m_compModel[i].numIntensityIntervals); + for (int j = 0; j < seiFilmGrain->m_compModel[i].numIntensityIntervals; j++) + { + seiFilmGrain->m_compModel[i].intensityValues[j].intensityIntervalLowerBound = m_pcCfg->getFGCSEIIntensityIntervalLowerBound(i, j); + seiFilmGrain->m_compModel[i].intensityValues[j].intensityIntervalUpperBound = m_pcCfg->getFGCSEIIntensityIntervalUpperBound(i, j); + seiFilmGrain->m_compModel[i].intensityValues[j].compModelValue.resize(seiFilmGrain->m_compModel[i].numModelValues); + for (int k = 0; k < seiFilmGrain->m_compModel[i].numModelValues; k++) + { + seiFilmGrain->m_compModel[i].intensityValues[j].compModelValue[k] = m_pcCfg->getFGCSEICompModelValue(i, j, k); + } + } + } +#endif } } diff --git a/source/Lib/EncoderLib/SEIwrite.cpp b/source/Lib/EncoderLib/SEIwrite.cpp index c0466b39744db2b9615537cc09e309a48e5273ad..d73b196064472b63851aa81b3c1dce14137615fb 100644 --- a/source/Lib/EncoderLib/SEIwrite.cpp +++ b/source/Lib/EncoderLib/SEIwrite.cpp @@ -1219,14 +1219,22 @@ void SEIWriter::xWriteSEIFilmGrainCharacteristics(const SEIFilmGrainCharacterist for (int c = 0; c<3; c++) { const SEIFilmGrainCharacteristics::CompModel &cm = sei.m_compModel[c]; - const uint32_t numIntensityIntervals = (uint32_t)cm.intensityValues.size(); +#if JVET_X0048_X0103_FILM_GRAIN + const uint32_t numIntensityIntervals = (uint32_t) cm.numIntensityIntervals; +#else + const uint32_t numIntensityIntervals = (uint32_t) cm.intensityValues.size(); +#endif const uint32_t numModelValues = cm.numModelValues; WRITE_FLAG(sei.m_compModel[c].presentFlag && numIntensityIntervals>0 && numModelValues>0, "fg_comp_model_present_flag[c]"); } for (uint32_t c = 0; c<3; c++) { const SEIFilmGrainCharacteristics::CompModel &cm = sei.m_compModel[c]; - const uint32_t numIntensityIntervals = (uint32_t)cm.intensityValues.size(); +#if JVET_X0048_X0103_FILM_GRAIN + const uint32_t numIntensityIntervals = (uint32_t) cm.numIntensityIntervals; +#else + const uint32_t numIntensityIntervals = (uint32_t) cm.intensityValues.size(); +#endif const uint32_t numModelValues = cm.numModelValues; if (cm.presentFlag && numIntensityIntervals>0 && numModelValues>0) { @@ -1239,7 +1247,9 @@ void SEIWriter::xWriteSEIFilmGrainCharacteristics(const SEIFilmGrainCharacterist const SEIFilmGrainCharacteristics::CompModelIntensityValues &cmiv = cm.intensityValues[interval]; WRITE_CODE(cmiv.intensityIntervalLowerBound, 8, "fg_intensity_interval_lower_bound[c][i]"); WRITE_CODE(cmiv.intensityIntervalUpperBound, 8, "fg_intensity_interval_upper_bound[c][i]"); +#if !JVET_X0048_X0103_FILM_GRAIN assert(cmiv.compModelValue.size() == numModelValues); +#endif for (uint32_t j = 0; j<cm.numModelValues; j++) { WRITE_SVLC(cmiv.compModelValue[j], "fg_comp_model_value[c][i]"); diff --git a/source/Lib/Utilities/program_options_lite.h b/source/Lib/Utilities/program_options_lite.h index c21d69a511249d1799bc42f2ad1430ab5f20077b..2b01e5eeec9afe501d35d29888fc1578b6299aae 100644 --- a/source/Lib/Utilities/program_options_lite.h +++ b/source/Lib/Utilities/program_options_lite.h @@ -38,12 +38,43 @@ #define JVET_O0549_ENCODER_ONLY_FILTER_POL 1 // JVET-O0549: Encoder-only GOP-based temporal filter. Program Options Lite related changes. +#define JVET_X0048_X0103_FILM_GRAIN 1 // JVET-X0048-X0103: SMPTE RDD-5 based film grain analysis and synthesis model for film grain characterstics (FGC) SEI +#if JVET_X0048_X0103_FILM_GRAIN +#include <vector> +#endif + #ifndef __PROGRAM_OPTIONS_LITE__ #define __PROGRAM_OPTIONS_LITE__ //! \ingroup TAppCommon //! \{ +#if JVET_X0048_X0103_FILM_GRAIN +using namespace std; +template <class T> +struct SMultiValueInput +{ + static_assert(!std::is_same<T, uint8_t>::value, "SMultiValueInput<uint8_t> is not supported"); + static_assert(!std::is_same<T, int8_t>::value, "SMultiValueInput<int8_t> is not supported"); + const T minValIncl; + const T maxValIncl; + const std::size_t minNumValuesIncl; + const std::size_t maxNumValuesIncl; // Use 0 for unlimited + std::vector<T> values; + SMultiValueInput() : minValIncl(0), maxValIncl(0), minNumValuesIncl(0), maxNumValuesIncl(0), values() { } + SMultiValueInput(std::vector<T> &defaults) : minValIncl(0), maxValIncl(0), minNumValuesIncl(0), maxNumValuesIncl(0), values(defaults) { } + SMultiValueInput(const T &minValue, const T &maxValue, std::size_t minNumberValues = 0, std::size_t maxNumberValues = 0) + : minValIncl(minValue), maxValIncl(maxValue), minNumValuesIncl(minNumberValues), maxNumValuesIncl(maxNumberValues), values() { } + SMultiValueInput(const T &minValue, const T &maxValue, std::size_t minNumberValues, std::size_t maxNumberValues, const T* defValues, const uint32_t numDefValues) + : minValIncl(minValue), maxValIncl(maxValue), minNumValuesIncl(minNumberValues), maxNumValuesIncl(maxNumberValues), values(defValues, defValues + numDefValues) { } + SMultiValueInput<T> &operator=(const std::vector<T> &userValues) { values = userValues; return *this; } + SMultiValueInput<T> &operator=(const SMultiValueInput<T> &userValues) { values = userValues.values; return *this; } + + T readValue(const char *&pStr, bool &bSuccess); + + istream& readValues(std::istream &in); +}; +#endif namespace df {