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>> &parameters,
+                                               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>> &parameters, 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>> &parameters)
+{
+  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
 {