diff --git a/cfg/sei_vui/greenMetadataSEI.cfg b/cfg/sei_vui/greenMetadataSEI.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..6fac97f3c189bbf025e4661acb5595728cd1dfb6
--- /dev/null
+++ b/cfg/sei_vui/greenMetadataSEI.cfg
@@ -0,0 +1,9 @@
+SEIGreenMetadataType: 0                     # 0: Complexity metrics for decoder power reduction; 1: Quality metrics for quality recovery after low-power encoding
+SEIGreenMetadataPeriodType : 0				# Beta version of the Green-MPEG encoder: Correct functionality of the remaining flags is not tested.
+SEIGreenMetadataPeriodTypePictures: 0
+SEIGreenMetadataPeriodTypeSeconds: 0
+SEIGreenMetadataGranularityType: 0
+SEIGreenMetadataExtendedRepresentation: 1
+
+SEIXSDMetricNumber: 1
+SEIXSDMetricTypePSNR: 1
diff --git a/doc/software-manual.tex b/doc/software-manual.tex
index b2adaae64627e7d28d90e1ab782dcb699577b01d..e8850da1e53d243e2a576a772cfe0c01fd1b574e 100644
--- a/doc/software-manual.tex
+++ b/doc/software-manual.tex
@@ -4288,24 +4288,70 @@ Specifies the rotation and mirroring to be applied to the picture.
 
 
 
-\begin{OptionTableNoShorthand}{Green Metadata SEI message encoder parameters}{tab:sei-green-metadata}
-\Option{SEIGreenMetadataType} &
-\Default{0} &
+\Default{-1} &
 Specifies the type of metadata that is present in the SEI message.
 \par
 \begin{tabular}{cp{0.35\textwidth}}
-  0 & Reserved \\
-  1 & Metadata enabling quality recovery after low-power encoding is present \\
+	-1 & Disabled \\
+	0 & Metadata for decoder complexity metrics \\
+	1 & Metadata enabling quality recovery after low-power encoding\\
 \end{tabular}
 \\
-\Option{SEIXSDMetricType} &
+\Option{SEIGreenMetadataPeriodType} &
 \Default{0} &
-Indicates the type of the objective quality metric.
+Indicates the period type of metadata.
 \par
 \begin{tabular}{cp{0.35\textwidth}}
-  0 & PSNR is used as objective quality metric \\
+	0 & Metadata are applicable to a single picture  \\
+	1 & Metadata are applicable to all pictures in decoding order, up to (but not including) the picture containing the next I slice (not implemented) \\
+	2 & Metadata are applicable to all pictures over a specified time interval in seconds \\
+	3 & Metadata are applicable over a specified number of pictures counted in decoding order \\
 \end{tabular}
 \\
+\Option{SEIGreenMetadataPeriodTypeSeconds} &
+\Default{1} &
+Indicates the number of seconds over which metadata should be valid (if SEIGreenMetadataPeriodType == 2)
+\\
+\Option{SEIGreenMetadataPeriodTypePictures} &
+\Default{1} &
+Indicates the number of pictures, counted in decoding order, over which metadata should be valid (if SEIGreenMetadataPeriodType == 3)
+\\
+\Option{SEIGreenMetadataExtendedRepresentation} &
+\Default{0} &
+Enables or disables the signaling of extended complexity metrics (if SEIGreenMetadataType == 0)
+\\
+\Option{GMFA} &
+\Default{false} &
+Enables or disables the output of a file containing analysis statistics for green metadata generation (if SEIGreenMetadataType == 0)
+\\
+\Option{GMFAFile} &
+\Default{} &
+File name for GMFA output file.
+\\
+\Option{GMFAFramewise} &
+\Default{false} &
+Enables or disables frame-wise output of the statistics. If disabled, statistics are calculated for the complete bit stream.
+\\
+\Option{SEIXSDMetricNumber} &
+\Default{1} &
+Number of quality metrics to be signaled (if SEIGreenMetadataType == 1)
+\\
+\Option{SEIXSDMetricTypePSNR} &
+\Default{false} &
+Enables or disables sending of PSNR metric.
+\\
+\Option{SEIXSDMetricTypeSSIM} &
+\Default{false} &
+Enables or disables sending of SSIM metric.
+\\
+\Option{SEIXSDMetricTypeWPSNR} &
+\Default{false} &
+Enables or disables sending of wPSNR metric.
+\\
+\Option{SEIXSDMetricTypeWSPSNR} &
+\Default{false} &
+Enables or disables sending of WS-PSNR metric.
+\\
 \end{OptionTableNoShorthand}
 
 
@@ -6244,6 +6290,38 @@ DTRACE_BLOCK_SCALAR(g_trace_ctx, D_BLOCK_STATISTICS_ALL,
 \end{description}
 
 
+\section{Coding tool statistics extension for green metadata}
+\label{sec:green-meta-sei}
+
+The encoder and the decoder include an extension that generates coding tool statistic. In the encoder, the extension calculates green metadata for encoding green SEI messages, in particular complexity metrics for decoder power reduction. The decoder extension can be used for cross-checking the correct functionality of the encoding extension.
+
+The output of the analyzer can be enabled with the option 'GMFA' (Green Metadata Feature Analyzer). The output file name is specified with the flag 'GMFAFile'.
+Furthermore, it is possible to generate a framewise analysis with the option 'GMFAFramewise'. The output file is generated in a Matlab-readable way. Here is an example for both the encoder and the decoder:
+
+\begin{minted}{bash}
+bin/EncoderAppStatic -b bitstream.vvc  --GMFA 1 --GMFAFramewise=1 --GMFAFile="bitstream.m" [encoder options]
+
+bin/DecoderAppStatic -b bitstream.vvc  --GMFA 1 --GMFAFramewise=1 --GMFAFile="bitstream.m" [decoder options]
+\end{minted}
+
+The output file contains arrays with statistics on the use of coding tools on block-size level. As an example, the number of intra-coded blocks is returned as:
+
+\begin{minted}{bash}
+n.intraBlocks = [...
+0  0  0  0  0  0  0  0 ;...
+0  0  0  16412  2142  54  0  0 ;...
+0  0  41654  41906  9780  665  27  0 ;...
+0  0  23494  22855  8641  906  26  0 ;...
+0  0  4670  4797  4030  1215  60  0 ;...
+0  0  433  507  881  1104  84  0 ;...
+0  0  38  48  43  122  131  0 ;...
+0  0  0  0  0  0  0  0 ];
+\end{minted}
+
+The horizontal position indicates the logarithm to the basis 2 block width (1, 2, 4, .., 128) and the vertical position the block height, accordingly. In this example, the bit stream contains $16{,}412$ intra-coded blocks of size $8\times 2$.
+
+More information can be found in JVET-P0085 and \url{10.1109/ICIP40778.2020.9190840}.
+
 
 
 
diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp
index 4ee2de1b7c20810deb096e6c83d28f1c4398e9de..9ed20447a0ba672564f75f3930d706ae36ac5c31 100644
--- a/source/App/DecoderApp/DecApp.cpp
+++ b/source/App/DecoderApp/DecApp.cpp
@@ -89,7 +89,18 @@ uint32_t DecApp::decode()
 {
   int      poc;
   PicList *pcListPic = nullptr;
-
+  
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct featureCounter;
+  FeatureCounterStruct featureCounterOld;
+  ifstream bitstreamSize(m_bitstreamFileName.c_str(), ifstream::in | ifstream::binary);
+  std::streampos fsize = 0;
+  fsize = bitstreamSize.tellg();
+  bitstreamSize.seekg( 0, std::ios::end );
+  featureCounter.bytes = (int) bitstreamSize.tellg() - (int) fsize;
+  bitstreamSize.close();
+#endif
+  
   ifstream bitstreamFile(m_bitstreamFileName.c_str(), ifstream::in | ifstream::binary);
   if (!bitstreamFile)
   {
@@ -176,6 +187,11 @@ uint32_t DecApp::decode()
     m_cDecLib.setHTidExternalSetFlag(m_mTidExternalSet);
     m_cDecLib.setTOlsIdxExternalFlag(m_tOlsIdxTidExternalSet);
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+    m_cDecLib.setFeatureAnalysisFramewise( m_GMFAFramewise);
+    m_cDecLib.setGMFAFile(m_GMFAFile);
+#endif
+  
   bool gdrRecoveryPeriod[MAX_NUM_LAYER_IDS] = { false };
   bool prevPicSkipped = true;
   int lastNaluLayerId = -1;
@@ -782,6 +798,14 @@ uint32_t DecApp::decode()
       m_cDecLib.resetAccessUnitApsNals();
       m_cDecLib.resetAccessUnitPicInfo();
     }
+#ifdef GREEN_METADATA_SEI_ENABLED
+    if (m_GMFA && m_GMFAFramewise && bNewPicture)
+    {
+      FeatureCounterStruct featureCounterUpdated = m_cDecLib.getFeatureCounter();
+      writeGMFAOutput(featureCounterUpdated, featureCounterOld, m_GMFAFile,false);
+      featureCounterOld = m_cDecLib.getFeatureCounter();
+    }
+#endif
   }
   if (!m_annotatedRegionsSEIFileName.empty())
   {
@@ -790,7 +814,24 @@ uint32_t DecApp::decode()
   // May need to check again one more time as in case one the bitstream has only one picture, the first check may miss it
   setOutputPicturePresentInStream();
   CHECK(!outputPicturePresentInBitstream, "It is required that there shall be at least one picture with PictureOutputFlag equal to 1 in the bitstream")
-
+  
+#ifdef GREEN_METADATA_SEI_ENABLED
+  if (m_GMFA && m_GMFAFramewise) //Last frame
+  {
+    FeatureCounterStruct featureCounterUpdated = m_cDecLib.getFeatureCounter();
+    writeGMFAOutput(featureCounterUpdated, featureCounterOld, m_GMFAFile, false);
+    featureCounterOld = m_cDecLib.getFeatureCounter();
+  }
+  
+  if (m_GMFA)
+  {
+    // Summary
+    FeatureCounterStruct featureCounterFinal = m_cDecLib.getFeatureCounter();
+    FeatureCounterStruct dummy;
+    writeGMFAOutput(featureCounterFinal, dummy, m_GMFAFile, true);
+  }
+#endif
+  
   xFlushOutput( pcListPic );
 
 #if JVET_Z0120_SII_SEI_PROCESSING
@@ -1568,4 +1609,6 @@ bool DecApp::xIsNaluWithinTargetOutputLayerIdSet( const InputNALUnit* nalu ) con
          != m_targetOutputLayerIdSet.end();
 }
 
+
+
 //! \}
diff --git a/source/App/DecoderApp/DecApp.h b/source/App/DecoderApp/DecApp.h
index bf87a7e1a5c7fbf61aac6b5d2f627a8dea5c158a..d427cd39b2a4bd97ef84985ce7f3678121586e08 100644
--- a/source/App/DecoderApp/DecApp.h
+++ b/source/App/DecoderApp/DecApp.h
@@ -122,7 +122,6 @@ private:
 
   void  writeLineToOutputLog(Picture * pcPic);
   void xOutputAnnotatedRegions(PicList* pcListPic);
-
 };
 
 //! \}
diff --git a/source/App/DecoderApp/DecAppCfg.cpp b/source/App/DecoderApp/DecAppCfg.cpp
index 321826beca7cb0119f0f52e9919ebb3cd0f327d1..e5f1a7ccb7651d332bf1f319f5afc1f1093e37c5 100644
--- a/source/App/DecoderApp/DecAppCfg.cpp
+++ b/source/App/DecoderApp/DecAppCfg.cpp
@@ -123,6 +123,11 @@ bool DecAppCfg::parseCfg( int argc, char* argv[] )
                                                                                    "\t1: enable bit statistic\n"
                                                                                    "\t2: enable tool statistic\n"
                                                                                    "\t3: enable bit and tool statistic\n")
+#endif
+#ifdef GREEN_METADATA_SEI_ENABLED
+  ("GMFA", m_GMFA, false, "Write output file for the Green-Metadata analyzer for decoder complexity metrics (JVET-P0085)\n")
+  ("GMFAFile", m_GMFAFile, string(""), "File for the Green Metadata Bit Stream Feature Analyzer output (JVET-P0085)\n")
+  ("GMFAFramewise", m_GMFAFramewise, false, "Output of frame-wise Green Metadata Bit Stream Feature Analyzer files\n")
 #endif
   ("MCTSCheck",                m_mctsCheck,                           false,       "If enabled, the decoder checks for violations of mc_exact_sample_value_match_flag in Temporal MCTS ")
   ("targetSubPicIdx",          m_targetSubPicIdx,                     0,           "Specify which subpicture shall be written to output, using subpic index, 0: disabled, subpicIdx=m_targetSubPicIdx-1 \n" )
diff --git a/source/App/DecoderApp/DecAppCfg.h b/source/App/DecoderApp/DecAppCfg.h
index db503e96d637d31a1a9464997e02a448537c5aa9..4721ba11bb0d010748aedbafd5ea8e5d943073a2 100644
--- a/source/App/DecoderApp/DecAppCfg.h
+++ b/source/App/DecoderApp/DecAppCfg.h
@@ -90,7 +90,11 @@ protected:
   std::string   m_cacheCfgFile;                       ///< Config file of cache model
   int           m_statMode;                           ///< Config statistic mode (0 - bit stat, 1 - tool stat, 3 - both)
   bool          m_mctsCheck;
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  bool          m_GMFA;
+  std::string   m_GMFAFile;
+  bool          m_GMFAFramewise;
+#endif
   int          m_upscaledOutput;                     ////< Output upscaled (2), decoded but in full resolution buffer (1) or decoded cropped (0, default) picture for RPR.
 #if JVET_AB0081
   int          m_upscaleFilterForDisplay;
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index cf7f5b3c07bba6dadfd1ac65707dac8e353a8433..005392c1c8a44d3e0ee65348426e1504e43f6f2e 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -906,6 +906,22 @@ void EncApp::xInitLibCfg( int layerIdx )
   m_cEncLib.setDoSEITransformType                                ( m_doSEITransformType);
   m_cEncLib.setParameterSetsInclusionIndicationSEIEnabled        (m_parameterSetsInclusionIndicationSEIEnabled);
   m_cEncLib.setSelfContainedClvsFlag                             (m_selfContainedClvsFlag);
+#ifdef GREEN_METADATA_SEI_ENABLED
+  m_cEncLib.setGMFAFile(m_GMFAFile);
+  m_cEncLib.setSEIGreenMetadataInfoSEIEnable                     ( m_greenMetadataType );
+  m_cEncLib.setSEIGreenMetadataExtendedRepresentation            ( m_greenMetadataExtendedRepresentation);
+  m_cEncLib.setSEIGreenMetadataGranularityType                   ( m_greenMetadataGranularityType);
+  m_cEncLib.setSEIGreenMetadataType                              ( m_greenMetadataType );
+  m_cEncLib.setSEIGreenMetadataPeriodType                        ( m_greenMetadataPeriodType );
+  m_cEncLib.setSEIGreenMetadataPeriodNumPictures                 (m_greenMetadataPeriodNumPictures);
+  m_cEncLib.setSEIGreenMetadataPeriodNumSeconds                  (m_greenMetadataPeriodNumSeconds);
+  //Metrics for quality recovery after low-power encoding
+  m_cEncLib.setSEIXSDNumberMetrics                               (m_xsdNumberMetrics);
+  m_cEncLib.setSEIXSDMetricTypePSNR                              (m_xsdMetricTypePSNR);
+  m_cEncLib.setSEIXSDMetricTypeSSIM                              (m_xsdMetricTypeSSIM);
+  m_cEncLib.setSEIXSDMetricTypeWPSNR                             (m_xsdMetricTypeWPSNR);
+  m_cEncLib.setSEIXSDMetricTypeWSPSNR                            (m_xsdMetricTypeWSPSNR);
+#endif
   m_cEncLib.setErpSEIEnabled                                     ( m_erpSEIEnabled );
   m_cEncLib.setErpSEICancelFlag                                  ( m_erpSEICancelFlag );
   m_cEncLib.setErpSEIPersistenceFlag                             ( m_erpSEIPersistenceFlag );
@@ -1875,4 +1891,23 @@ void EncApp::printChromaFormat()
   }
 }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+void EncApp::featureToFile(std::ofstream& featureFile,int feature[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1], std::string featureName)
+{
+  featureFile <<   "\tn." << featureName << " = [...\n\t";
+  for (size_t i = 0; i < MAX_CU_DEPTH+1; i++)
+  {
+    for (size_t j = 0; j < MAX_CU_DEPTH+1; j++)
+    {
+      featureFile << " " << feature[j][i] << " ";
+    }
+    if (i != MAX_CU_DEPTH)
+    {
+      featureFile << ";... \n\t";
+    }
+  }
+  featureFile << "]; \n ";
+}
+#endif
+
 //! \}
diff --git a/source/App/EncoderApp/EncApp.h b/source/App/EncoderApp/EncApp.h
index 60e776665103a731964cc5be5c3a739549e38c43..7c4138c60d8aff97c6eb14a243bc21c62330c2ce 100644
--- a/source/App/EncoderApp/EncApp.h
+++ b/source/App/EncoderApp/EncApp.h
@@ -110,6 +110,9 @@ private:
   PelStorage*            m_filteredOrgPicForFG;
   EncTemporalFilter      m_temporalFilterForFG;
   bool m_flush;
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct      m_featureCounter;
+#endif
 
 public:
   EncApp(std::fstream &bitStream, EncLibCommon *encLibCommon);
@@ -134,6 +137,11 @@ public:
   int   getALFAPSIDShift() { return m_cEncLib.getALFAPSIDShift(); }
   void  forceMaxNumALFAPS(int n) { m_cEncLib.setMaxNumALFAPS(n); }
   void  forceALFAPSIDShift(int n) { m_cEncLib.setALFAPSIDShift(n); }
+#ifdef GREEN_METADATA_SEI_ENABLED
+  uint32_t getTotalNumberOfBytes() {return m_totalBytes;}
+  FeatureCounterStruct getFeatureCounter(){return m_cEncLib.getFeatureCounter();}
+  void      featureToFile(std::ofstream& featureFile,int feature[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1], std::string featureName);
+#endif
 };// END CLASS DEFINITION EncApp
 
 //! \}
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index c9b41b55cb0c576d83dff023f0007c6aaa91232d..da97b78e23310cc71be984e492c37e9dde0a9a1f 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -825,7 +825,21 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   ( "CropOffsetBottom",                               m_cropOffsetBottom,                                   0, "Crop Offset Bottom position")
   ( "CalculateHdrMetrics",                            m_calculateHdrMetrics,                            false, "Enable HDR metric calculation")
 #endif
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  ("SEIGreenMetadataType",                            m_greenMetadataType,                                  -1, "Value for the green_metadata_type specifies the type of metadata that is present in the SEI message. -1: Green metadata disabled (default); 0: Decoder complexity metrics; 1: quality recovery after low-power encoding")
+  ("SEIGreenMetadataGranularityType",                 m_greenMetadataGranularityType,                       -1, "Specifies the type of granularity for which the metadata are applicable. Only implemented for picture granularity. ")
+  ("SEIGreenMetadataPeriodType",                      m_greenMetadataPeriodType,                             0, "Value for the Period Type incidacting over which amount of time the metadata have been calculated")
+  ("SEIGreenMetadataPeriodTypeSeconds",               m_greenMetadataPeriodNumSeconds,                       1, "indicates the number of seconds over which the metadata are applicable when SEIGreenMetadataPeriodType is 2.")
+  ("SEIGreenMetadataPeriodTypePictures",              m_greenMetadataPeriodNumPictures,                      1, "specifies the number of pictures, counted in decoding order, over which the metadata are applicable when SEIGreenMetadataPeriodType is 3.")
+  ("SEIXSDMetricNumber",                              m_xsdNumberMetrics,                                    1, "Number of quality metrics.")
+  ("SEIXSDMetricTypePSNR",                            m_xsdMetricTypePSNR,                               false, "Set to 'true' if PSNR shall be signalled. ")
+  ("SEIXSDMetricTypeSSIM",                            m_xsdMetricTypeSSIM,                               false, "Set to 'true' if SSIM shall be signalled. ")
+  ("SEIXSDMetricTypeWPSNR",                           m_xsdMetricTypeWPSNR,                              false, "Set to 'true' if WPSNR shall be signalled. ")
+  ("SEIXSDMetricTypeWSPSNR",                          m_xsdMetricTypeWSPSNR,                             false, "Set to 'true' if WSSPSNR shall be signalled. ")
+  ("SEIGreenMetadataExtendedRepresentation",          m_greenMetadataExtendedRepresentation,                 0, "Specifies whether reduced or extended set of complexity metrics is signelled. ")
+  ("GMFA",                                            m_GMFA,                                            false, "Write output file for the Green-Metadata analyzer for decoder complexity metrics (JVET-P0085)\n")
+  ("GMFAFile",                                        m_GMFAFile,                                   string(""), "File for the Green Metadata Bit Stream Feature Analyzer output (JVET-P0085)\n")
+#endif
   //Field coding parameters
   ("FieldCoding",                                     m_isField,                                        false, "Signals if it's a field based coding")
   ("TopFieldFirst, Tff",                              m_isTopFieldFirst,                                false, "In case of field based coding, signals whether if it's a top field first or not")
@@ -5159,6 +5173,17 @@ bool EncAppCfg::xHasNonZeroTemporalID ()
   return false;
 }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+bool EncAppCfg::getGMFAUsage() {
+  return m_GMFA;
+}
+
+std::string EncAppCfg::getGMFAFile (){
+  return m_GMFAFile;
+}
+
+#endif
+
 bool EncAppCfg::xHasLeadingPicture ()
 {
   for (unsigned int i = 0; i < m_iGOPSize; i++)
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index cf5ea5dcbdf3ddb32cd756ffc02737f7a752ce41..9a43eba2816cca8a81c42ef760f5b7d9e6d41b4a 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -858,7 +858,27 @@ protected:
   int         m_SII_BlendingRatio;
   void        setBlendingRatioSII(int value) { m_SII_BlendingRatio = value; }
 #endif
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+public:
+  std::string getGMFAFile ();
+  bool getGMFAUsage();
+protected:
+  std::string   m_GMFAFile;
+  bool          m_GMFA;
+  
+  int      m_greenMetadataType;
+  int      m_greenMetadataExtendedRepresentation;
+  int      m_greenMetadataGranularityType;
+  int      m_greenMetadataPeriodType;
+  int      m_greenMetadataPeriodNumSeconds;
+  int      m_greenMetadataPeriodNumPictures;
+
+  int      m_xsdNumberMetrics;
+  bool     m_xsdMetricTypePSNR;
+  bool     m_xsdMetricTypeSSIM;
+  bool     m_xsdMetricTypeWPSNR;
+  bool     m_xsdMetricTypeWSPSNR;
+#endif
   std::string m_summaryOutFilename;                           ///< filename to use for producing summary output file.
   std::string m_summaryPicFilenameBase;                       ///< Base filename to use for producing summary picture output files. The actual filenames used will have I.txt, P.txt and B.txt appended.
   uint32_t        m_summaryVerboseness;                           ///< Specifies the level of the verboseness of the text output.
diff --git a/source/App/EncoderApp/encmain.cpp b/source/App/EncoderApp/encmain.cpp
index 79a8738ff7ee44012a2da81ceff3af376d8d5b61..a73088a543c488999ce802d08f1e3299797f4e87 100644
--- a/source/App/EncoderApp/encmain.cpp
+++ b/source/App/EncoderApp/encmain.cpp
@@ -336,7 +336,15 @@ int main(int argc, char* argv[])
 #else
   auto encTime = std::chrono::duration_cast<std::chrono::milliseconds>( endTime - startTime).count();
 #endif
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  for( auto & encApp : pcEncApp )
+  {
+    FeatureCounterStruct  featureCounterFinal = encApp->getFeatureCounter();
+    featureCounterFinal.bytes = encApp->getTotalNumberOfBytes();
+    FeatureCounterStruct dummy;
+    writeGMFAOutput(featureCounterFinal, dummy, encApp->getGMFAFile(),true);
+  }
+#endif
   for( auto & encApp : pcEncApp )
   {
     encApp->destroyLib();
diff --git a/source/Lib/CommonLib/AdaptiveLoopFilter.cpp b/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
index 0276e88886eb3682ac26b1b710e5b45b76366f7c..d7d5b4534ee790ad2190393e3a705bc1bd5ffa5c 100644
--- a/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
+++ b/source/Lib/CommonLib/AdaptiveLoopFilter.cpp
@@ -376,7 +376,9 @@ void AdaptiveLoopFilter::applyCcAlfFilter(CodingStructure &cs, ComponentID compI
               const Area blkDst(xStart >> chromaScaleX, yStart >> chromaScaleY, w >> chromaScaleX, h >> chromaScaleY);
               m_filterCcAlf(dstBuf, buf, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs, m_alfVBLumaCTUHeight,
                             m_alfVBLumaPos);
-
+#ifdef  GREEN_METADATA_SEI_ENABLED
+              cs.m_featureCounter.ccalf++;
+#endif
               xStart = xEnd;
             }
 
@@ -392,6 +394,9 @@ void AdaptiveLoopFilter::applyCcAlfFilter(CodingStructure &cs, ComponentID compI
 
           m_filterCcAlf(dstBuf, recYuvExt, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs, m_alfVBLumaCTUHeight,
                         m_alfVBLumaPos);
+#ifdef  GREEN_METADATA_SEI_ENABLED
+          cs.m_featureCounter.ccalf++;
+#endif
         }
       }
       ctuIdx++;
@@ -515,6 +520,10 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
                 coeff = m_fixedFilterSetCoeffDec[filterSetIndex];
                 clip = m_clipDefault;
               }
+#ifdef  GREEN_METADATA_SEI_ENABLED
+              cs.m_featureCounter.alfLumaType7+= (width * height / 16) ;
+              cs.m_featureCounter.alfLumaPels += (width * height);
+#endif
               m_filter7x7Blk(m_classifier, recYuv, buf, blkDst, blkSrc, COMPONENT_Y, coeff, clip, m_clpRngs.comp[COMPONENT_Y], cs
                 , m_alfVBLumaCTUHeight
                 , m_alfVBLumaPos
@@ -535,6 +544,10 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
                 m_filter5x5Blk(m_classifier, recYuv, buf, blkDst, blkSrc, compID, m_chromaCoeffFinal[alt_num], m_chromaClippFinal[alt_num], m_clpRngs.comp[compIdx], cs
                   , m_alfVBChmaCTUHeight
                    , m_alfVBChmaPos );
+#ifdef  GREEN_METADATA_SEI_ENABLED
+                cs.m_featureCounter.alfChromaType5+= ((width >> chromaScaleX) * (height >> chromaScaleY) / 16) ;
+                cs.m_featureCounter.alfChromaPels += ((width >> chromaScaleX) * (height >> chromaScaleY)) ;
+#endif
               }
               if (cu->slice->m_ccAlfFilterParam.ccAlfFilterEnabled[compIdx - 1])
               {
@@ -546,7 +559,10 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
                   Area blkDst(xStart >> chromaScaleX, yStart >> chromaScaleY, w >> chromaScaleX, h >> chromaScaleY);
 
                   const int16_t *filterCoeff = m_ccAlfFilterParam.ccAlfCoeff[compIdx - 1][filterIdx - 1];
-
+#ifdef  GREEN_METADATA_SEI_ENABLED
+                  cs.m_featureCounter.alfLumaType7+= (width * height / 16) ;
+                  cs.m_featureCounter.alfLumaPels += (width * height);
+#endif
                   m_filterCcAlf(recYuv.get(compID), buf, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs,
                                 m_alfVBLumaCTUHeight, m_alfVBLumaPos);
                 }
@@ -579,6 +595,10 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
             coeff = m_fixedFilterSetCoeffDec[filterSetIndex];
             clip = m_clipDefault;
           }
+#ifdef  GREEN_METADATA_SEI_ENABLED
+          cs.m_featureCounter.alfLumaType7+= (width * height / 16) ;
+          cs.m_featureCounter.alfLumaPels += (width * height);
+#endif
           m_filter7x7Blk(m_classifier, recYuv, tmpYuv, blk, blk, COMPONENT_Y, coeff, clip, m_clpRngs.comp[COMPONENT_Y],
                          cs, m_alfVBLumaCTUHeight, m_alfVBLumaPos);
         }
@@ -593,6 +613,10 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
           {
             Area    blk(xPos >> chromaScaleX, yPos >> chromaScaleY, width >> chromaScaleX, height >> chromaScaleY);
             uint8_t alt_num = m_ctuAlternative[compIdx][ctuIdx];
+#ifdef  GREEN_METADATA_SEI_ENABLED
+            cs.m_featureCounter.alfChromaType5+= ((width >> chromaScaleX) * (height >> chromaScaleY) / 16) ;
+            cs.m_featureCounter.alfChromaPels += ((width >> chromaScaleX) * (height >> chromaScaleY)) ;
+#endif
             m_filter5x5Blk(m_classifier, recYuv, tmpYuv, blk, blk, compID, m_chromaCoeffFinal[alt_num],
                            m_chromaClippFinal[alt_num], m_clpRngs.comp[compIdx], cs, m_alfVBChmaCTUHeight,
                            m_alfVBChmaPos);
@@ -607,7 +631,9 @@ void AdaptiveLoopFilter::ALFProcess(CodingStructure& cs)
               Area blkSrc(xPos, yPos, width, height);
 
               const int16_t *filterCoeff = m_ccAlfFilterParam.ccAlfCoeff[compIdx - 1][filterIdx - 1];
-
+#ifdef  GREEN_METADATA_SEI_ENABLED
+              cs.m_featureCounter.ccalf++;
+#endif
               m_filterCcAlf(recYuv.get(compID), tmpYuv, blkDst, blkSrc, compID, filterCoeff, m_clpRngs, cs,
                             m_alfVBLumaCTUHeight, m_alfVBLumaPos);
             }
diff --git a/source/Lib/CommonLib/CodingStructure.h b/source/Lib/CommonLib/CodingStructure.h
index ef5c606417054a38760655d800050ffdf3e0920e..d3f7823d9a4c5d028b7b6397ad420bb7028199ec 100644
--- a/source/Lib/CommonLib/CodingStructure.h
+++ b/source/Lib/CommonLib/CodingStructure.h
@@ -208,7 +208,10 @@ public:
   Distortion  interHad;
   TreeType    treeType; //because partitioner can not go deep to tu and cu coding (e.g., addCU()), need another variable for indicating treeType
   ModeType    modeType;
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct m_featureCounter;
+#endif
+  
   void initStructData  (const int &QP = MAX_INT, const bool &skipMotBuf = false);
   void initSubStructure(      CodingStructure& cs, const ChannelType chType, const UnitArea &subArea, const bool &isTuEnc);
 
diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index 5c284b9693b5b183b32fe6d68c67af6be55e04e2..7fa2f78b50ebb0dfa8c085595c74c086b54f4eaa 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -43,6 +43,10 @@
 #include <iomanip>
 #include <limits>
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+#include <fstream>
+#endif
+
 
 #if _MSC_VER > 1000
 // disable "signed and unsigned mismatch"
@@ -644,6 +648,189 @@ T* aligned_malloc(size_t len, size_t alignement) {
 #    define ALWAYS_INLINE
 #endif
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+struct FeatureCounterStruct// Bit Stream Feature Analyzer structure containing all specific features
+{
+  int  width = -1;
+  int  height = -1;
+  int  bytes = -1;
+  int  baseQP[64] = {  0  };
+  int  isYUV400 = -1;
+  int  isYUV420 = -1;
+  int  isYUV422 = -1;
+  int  isYUV444 = -1;
+  int  is8bit = -1;
+  int  is10bit = -1;
+  int  is12bit = -1;
+  int  iSlices = 0;
+  int  bSlices = 0;
+  int  pSlices = 0;
+  
+  int  intraBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaPlaBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaDcBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaHvdBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaHvBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaAngBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaPlaBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaDcBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaHvdBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaHvBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaAngBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaCrossCompBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraPDPCBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaPDPCBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaPDPCBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraMIPBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaMIPBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaMIPBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraSubPartitionsHorizontal[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaSubPartitionsHorizontal[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaSubPartitionsHorizontal[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraSubPartitionsVertical[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraLumaSubPartitionsVertical[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  intraChromaSubPartitionsVertical[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  IBCBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  IBCLumaBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  IBCChromaBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  // Inter-Features
+  int  interBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interLumaBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interChromaBlockSizes[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  interInterBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interLumaInterBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interChromaInterBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  interSkipBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interLumaSkipBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interChromaSkipBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  interMergeBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interLumaMergeBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  interChromaMergeBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  affine[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineLuma[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineChroma[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  affineMerge[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineLumaMerge[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineChromaMerge[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  affineInter[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineLumaInter[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineChromaInter[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  affineSkip[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineLumaSkip[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  affineChromaSkip[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  geo[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  geoLuma[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  geoChroma[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int64_t  biPredPel = 0;
+  int64_t  uniPredPel = 0;
+  int64_t  fracPelHor = 0;
+  int64_t  fracPelVer = 0;
+  int64_t  fracPelBoth = 0;
+  int64_t  copyCUPel = 0;
+  int64_t  affineFracPelHor = 0;
+  int64_t  affineFracPelVer = 0;
+  int64_t  affineFracPelBoth = 0;
+  int64_t  affineCopyCUPel = 0;
+  int  dmvrBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  bdofBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  // Transform
+  int  transformBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  transformLumaBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  transformChromaBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int  transformSkipBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  transformLumaSkipBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  int  transformChromaSkipBlocks[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1] = { { 0 } };
+  
+  int64_t  transformLFNST4 = 0;
+  int64_t  transformLFNST8 = 0;
+  //Coefficent
+  int64_t  nrOfCoeff = 0;
+  int64_t  coeffG1 = 0;
+  double   valueOfCoeff = 0;
+  //In-Loop Filter
+  int64_t  boundaryStrength[3] = { 0 };
+  int64_t  boundaryStrengthPel[3] = { 0 };
+  int64_t  saoLumaBO = 0;
+  int64_t  saoLumaEO = 0;
+  int64_t  saoChromaBO = 0;
+  int64_t  saoChromaEO = 0;
+  int64_t  saoLumaPels = 0;
+  int64_t  saoChromaPels = 0;
+  int64_t  alfLumaType7 = 0;
+  int64_t  alfChromaType5 = 0;
+  int64_t  alfLumaPels = 0;
+  int64_t  alfChromaPels = 0;
+  int64_t  ccalf           = 0;
+  
+  void resetBoundaryStrengths()
+  {
+    for (int i = 0; i < 3; i++)
+    {
+      boundaryStrength[i] = 0;
+      boundaryStrengthPel[i] = 0;
+    }
+  }
+  
+  void addBoundaryStrengths(const FeatureCounterStruct &c)
+  {
+    for (int i = 0; i < 3; i++)
+    {
+      boundaryStrength[i] += c.boundaryStrength[i];
+      boundaryStrengthPel[i] += c.boundaryStrengthPel[i];
+    }
+  }
+  
+  void resetSAO()
+  {
+    saoLumaBO = 0;
+    saoLumaEO = 0;
+    saoChromaBO = 0;
+    saoChromaEO = 0;
+    saoLumaPels = 0;
+    saoChromaPels = 0;
+  }
+  
+  void addSAO(const FeatureCounterStruct &c)
+  {
+    saoLumaBO += c.saoLumaBO;
+    saoLumaEO += c.saoLumaEO;
+    saoChromaBO += c.saoChromaBO;
+    saoChromaEO += c.saoChromaEO;
+    saoLumaPels += c.saoLumaPels;
+    saoChromaPels += c.saoChromaPels;
+  }
+  
+  void resetALF()
+  {
+    alfLumaType7   = 0;
+    alfChromaType5 = 0;
+    alfLumaPels = 0;
+    alfChromaPels = 0;
+    ccalf = 0;
+  }
+  
+  void addALF(const FeatureCounterStruct &c)
+  {
+    alfLumaType7 += c.alfLumaType7;
+    alfChromaType5 += c.alfChromaType5;
+    alfLumaPels += c.alfLumaPels;
+    alfChromaPels += c.alfChromaPels;
+    ccalf += c.ccalf;
+  }
+};
+#endif
+
+
 #if ENABLE_SIMD_OPT
 #ifdef TARGET_SIMD_X86
 typedef enum{
diff --git a/source/Lib/CommonLib/DeblockingFilter.cpp b/source/Lib/CommonLib/DeblockingFilter.cpp
index eb08dc7897d5c8916bbd92b21172595e5d8773ee..d56c273944f95a7c7ed6545acbeafe5298e2341c 100644
--- a/source/Lib/CommonLib/DeblockingFilter.cpp
+++ b/source/Lib/CommonLib/DeblockingFilter.cpp
@@ -155,6 +155,9 @@ void DeblockingFilter::deblockingFilterPic(CodingStructure &cs)
     }
   }
 #endif
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct tempFeatureCounter;
+#endif
 
   for( int y = 0; y < pcv.heightInCtus; y++ )
   {
@@ -172,7 +175,13 @@ void DeblockingFilter::deblockingFilterPic(CodingStructure &cs)
       // CU-based deblocking
       for (auto &currCU: cs.traverseCUs(CS::getArea(cs, ctuArea, CHANNEL_TYPE_LUMA), CHANNEL_TYPE_LUMA))
       {
+#ifdef GREEN_METADATA_SEI_ENABLED
+        currCU.m_featureCounter.resetBoundaryStrengths();
+#endif
         xDeblockCU( currCU, EDGE_VER );
+#ifdef GREEN_METADATA_SEI_ENABLED
+        tempFeatureCounter.addBoundaryStrengths(currCU.m_featureCounter);
+#endif
       }
 
       if( CS::isDualITree( cs ) )
@@ -182,7 +191,13 @@ void DeblockingFilter::deblockingFilterPic(CodingStructure &cs)
 
         for (auto &currCU: cs.traverseCUs(CS::getArea(cs, ctuArea, CHANNEL_TYPE_CHROMA), CHANNEL_TYPE_CHROMA))
         {
+#ifdef GREEN_METADATA_SEI_ENABLED
+          currCU.m_featureCounter.resetBoundaryStrengths();
+#endif
           xDeblockCU( currCU, EDGE_VER );
+#ifdef GREEN_METADATA_SEI_ENABLED
+          tempFeatureCounter.addBoundaryStrengths(currCU.m_featureCounter);
+#endif
         }
       }
     }
@@ -205,7 +220,13 @@ void DeblockingFilter::deblockingFilterPic(CodingStructure &cs)
       // CU-based deblocking
       for (auto &currCU: cs.traverseCUs(CS::getArea(cs, ctuArea, CHANNEL_TYPE_LUMA), CHANNEL_TYPE_LUMA))
       {
+#ifdef GREEN_METADATA_SEI_ENABLED
+        currCU.m_featureCounter.resetBoundaryStrengths();
+#endif
         xDeblockCU( currCU, EDGE_HOR );
+#ifdef GREEN_METADATA_SEI_ENABLED
+        tempFeatureCounter.addBoundaryStrengths(currCU.m_featureCounter);
+#endif
       }
 
       if( CS::isDualITree( cs ) )
@@ -215,12 +236,21 @@ void DeblockingFilter::deblockingFilterPic(CodingStructure &cs)
 
         for (auto &currCU: cs.traverseCUs(CS::getArea(cs, ctuArea, CHANNEL_TYPE_CHROMA), CHANNEL_TYPE_CHROMA))
         {
+#ifdef GREEN_METADATA_SEI_ENABLED
+          currCU.m_featureCounter.resetBoundaryStrengths();
+#endif
           xDeblockCU( currCU, EDGE_HOR );
+#ifdef GREEN_METADATA_SEI_ENABLED
+          tempFeatureCounter.addBoundaryStrengths(currCU.m_featureCounter);
+#endif
         }
       }
     }
   }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+  cs.m_featureCounter.addBoundaryStrengths(tempFeatureCounter);
+#endif
   DTRACE_PIC_COMP(D_REC_CB_LUMA_LF,   cs, cs.getRecoBuf(), COMPONENT_Y);
   DTRACE_PIC_COMP(D_REC_CB_CHROMA_LF, cs, cs.getRecoBuf(), COMPONENT_Cb);
   DTRACE_PIC_COMP(D_REC_CB_CHROMA_LF, cs, cs.getRecoBuf(), COMPONENT_Cr);
@@ -393,10 +423,23 @@ void DeblockingFilter::xDeblockCU( CodingUnit& cu, const DeblockEdgeDir edgeDir
         if(cu.treeType != TREE_C)
         {
           es |= xGetBoundaryStrengthSingle(cu, edgeDir, localPos, CHANNEL_TYPE_LUMA);
+#ifdef  GREEN_METADATA_SEI_ENABLED
+          const int bsY = es.getBoundaryStrength(COMPONENT_Y);
+          cu.m_featureCounter.boundaryStrength[bsY]++;
+          cu.m_featureCounter.boundaryStrengthPel[bsY] += pelsInPart;
+#endif
         }
         if(cu.treeType != TREE_L && cu.chromaFormat != CHROMA_400 && cu.blocks[COMPONENT_Cb].valid())
         {
           es |= xGetBoundaryStrengthSingle(cu, edgeDir, localPos, CHANNEL_TYPE_CHROMA);
+#ifdef GREEN_METADATA_SEI_ENABLED
+          const int bsCb = es.getBoundaryStrength(COMPONENT_Cb);
+          const int bsCr = es.getBoundaryStrength(COMPONENT_Cr);
+          cu.m_featureCounter.boundaryStrength[bsCb]++;
+          cu.m_featureCounter.boundaryStrength[bsCr]++;
+          cu.m_featureCounter.boundaryStrengthPel[bsCb] += pelsInPart;
+          cu.m_featureCounter.boundaryStrengthPel[bsCr] += pelsInPart;
+#endif
         }
         m_edgeStrengths[edgeDir][rasterIdx] = es;
       }
diff --git a/source/Lib/CommonLib/Picture.h b/source/Lib/CommonLib/Picture.h
index 8d5bf6c5b28208c82c557154be477a0cddd06223..3e903c4252d80ba1432811c8b921843b7fa41629 100644
--- a/source/Lib/CommonLib/Picture.h
+++ b/source/Lib/CommonLib/Picture.h
@@ -200,7 +200,9 @@ private:
   Window        m_scalingWindow;
   int           m_decodingOrderNumber;
   NalUnitType   m_pictureType;
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct m_featureCounter;
+#endif
 public:
   bool m_isSubPicBorderSaved;
 
@@ -215,7 +217,11 @@ public:
   void    saveSubPicBorder(int POC, int subPicX0, int subPicY0, int subPicWidth, int subPicHeight);
   void  extendSubPicBorder(int POC, int subPicX0, int subPicY0, int subPicWidth, int subPicHeight);
   void restoreSubPicBorder(int POC, int subPicX0, int subPicY0, int subPicWidth, int subPicHeight);
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  void setFeatureCounter (FeatureCounterStruct b ) { m_featureCounter = b;}
+  FeatureCounterStruct getFeatureCounter (){return m_featureCounter;}
+#endif
+  
   bool getSubPicSaved()          { return m_isSubPicBorderSaved; }
   void setSubPicSaved(bool bVal) { m_isSubPicBorderSaved = bVal; }
   bool     m_extendedBorder;
diff --git a/source/Lib/CommonLib/SEI.h b/source/Lib/CommonLib/SEI.h
index e9ce8cfbfde9081d52cedb7d35f56f4ccc3e656d..6a1632c800a42da8fbb7523c8de5893128fdf71b 100644
--- a/source/Lib/CommonLib/SEI.h
+++ b/source/Lib/CommonLib/SEI.h
@@ -746,7 +746,10 @@ public:
   int m_greenMetadataType =-1;
   // Metrics for quality recovery after low-power encoding
   int m_xsdSubpicNumberMinus1 = -1; //xsd_metric_number_minus1 plus 1 indicates the number of objective quality metrics contained in the SEI message.
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  int m_xsdSubPicIdc;
+#endif
+  
   int     m_xsdMetricValuePSNR;
   int     m_xsdMetricValueSSIM;
   int     m_xsdMetricValueWPSNR;
diff --git a/source/Lib/CommonLib/SampleAdaptiveOffset.cpp b/source/Lib/CommonLib/SampleAdaptiveOffset.cpp
index fa661de22f21753ddb008856a048288e06ee3b59..724e2cbe403dca1009a078488fed546371a89822 100644
--- a/source/Lib/CommonLib/SampleAdaptiveOffset.cpp
+++ b/source/Lib/CommonLib/SampleAdaptiveOffset.cpp
@@ -597,7 +597,34 @@ void SampleAdaptiveOffset::offsetCTU(const UnitArea &area, const CPelUnitBuf &sr
       {
         verVirBndryPosComp[i] = (verVirBndryPos[i] >> ::getComponentScaleX(compID, area.chromaFormat)) - compArea.x;
       }
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+      if (ctbOffset.typeIdc.newType == SAOModeNewTypes::START_BO)
+      {
+        if (compID == COMPONENT_Y)
+        {
+          cs.m_featureCounter.saoLumaBO++;
+          cs.m_featureCounter.saoLumaPels += area.lumaSize().width * area.lumaSize().height;
+        }
+        else
+        {
+          cs.m_featureCounter.saoChromaBO++;
+          cs.m_featureCounter.saoChromaPels += area.chromaSize().width * area.chromaSize().height;
+        }
+      }
+      else if (ctbOffset.typeIdc.newType == SAOModeNewTypes::EO_0 || ctbOffset.typeIdc.newType == SAOModeNewTypes::EO_135 || ctbOffset.typeIdc.newType == SAOModeNewTypes::EO_45 ||ctbOffset.typeIdc.newType == SAOModeNewTypes::EO_90 )
+      {
+        if (compID == COMPONENT_Y)
+        {
+          cs.m_featureCounter.saoLumaEO++;
+          cs.m_featureCounter.saoLumaPels += area.lumaSize().width * area.lumaSize().height;
+        }
+        else
+        {
+          cs.m_featureCounter.saoChromaEO++;
+          cs.m_featureCounter.saoChromaPels += area.chromaSize().width * area.chromaSize().height;
+        }
+      }
+#endif
       offsetBlock(cs.sps->getBitDepth(toChannelType(compID)), cs.slice->clpRng(compID), ctbOffset.typeIdc.newType,
                   ctbOffset.offset, srcBlk, resBlk, srcStride, resStride, compArea.width, compArea.height, isLeftAvail,
                   isRightAvail, isAboveAvail, isBelowAvail, isAboveLeftAvail, isAboveRightAvail, isBelowLeftAvail,
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index 524f6ad993b36fd1d81146e60dde93e4f8f28adc..9c94d193950415517bebe6575558a096e0b351af 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -2810,6 +2810,9 @@ private:
   int                        m_tsrcIndex{ 0 };
   unsigned                   m_riceBit[8];
   int                        m_cntRightBottom;
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct m_featureCounter;
+#endif
 
 public:
                               Slice();
@@ -2963,7 +2966,10 @@ public:
     return (m_eSliceType == B_SLICE && m_eNalUnitType == NAL_UNIT_CODED_SLICE_GDR);
   }
 #endif
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  void setFeatureCounter (FeatureCounterStruct b ) {m_featureCounter = b;}
+  FeatureCounterStruct getFeatureCounter (){return m_featureCounter;}
+#endif
   bool                        getEnableDRAPSEI () const                              { return m_enableDRAPSEI;                                       }
   void                        setEnableDRAPSEI ( bool b )                            { m_enableDRAPSEI = b;                                          }
   bool                        getUseLTforDRAP () const                               { return m_useLTforDRAP;                                        }
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 9294545005a34c8ab954712b68b9a9115c7f8d71..4274ecc618724cb907ff6eff29dbd437f09b6cdc 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -50,6 +50,7 @@
 #include <cstring>
 #include <assert.h>
 #include <cassert>
+#include "CommonDef.h"
 
 // clang-format off
 
@@ -61,6 +62,7 @@
 
 //########### place macros to be removed in next cycle below this line ###############
 #define JVET_AB0047_MOVE_GATED_SYNTAX_OF_NNPFC_URIS_AFTER_NNPFC_MODEIDC 1
+#define JVET_AB0072                                      1 //Green-MPEG SEI Messaging (JVET-AB0072)
 
 //########### place macros to be be kept below this line ###############
 
@@ -136,6 +138,10 @@ typedef std::pair<int, int>  TrCost;
 
 #define KEEP_PRED_AND_RESI_SIGNALS                        0
 
+#if JVET_AB0072
+#define GREEN_METADATA_SEI_ENABLED 0 //JVET-AB0072: Analyser for the Green Metadata SEI
+#endif
+
 // ====================================================================================================================
 // Debugging
 // ====================================================================================================================
@@ -864,6 +870,7 @@ enum NalUnitType
   NAL_UNIT_INVALID
 };
 
+
 #if SHARP_LUMA_DELTA_QP
 enum LumaLevelToDQPMode
 {
@@ -1364,6 +1371,8 @@ struct XUCache
   TUCache tuCache;
 };
 
+
+
 //! \}
 
 #endif
diff --git a/source/Lib/CommonLib/Unit.h b/source/Lib/CommonLib/Unit.h
index 4be76be95be35a151269a9a7694adac18258ec76..b4896fbaa7e56c0ebb995e7db71119ac1d3d4ceb 100644
--- a/source/Lib/CommonLib/Unit.h
+++ b/source/Lib/CommonLib/Unit.h
@@ -334,7 +334,10 @@ struct CodingUnit : public UnitArea
   uint8_t        reusePLTSize[MAX_NUM_CHANNEL_TYPE];
   uint8_t        curPLTSize[MAX_NUM_CHANNEL_TYPE];
   Pel            curPLT[MAX_NUM_COMPONENT][MAXPLTSIZE];
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct m_featureCounter;
+#endif
+  
   CodingUnit() : chType( CH_L ) { }
   CodingUnit(const UnitArea &unit);
   CodingUnit(const ChromaFormat _chromaFormat, const Area &area);
diff --git a/source/Lib/CommonLib/UnitTools.cpp b/source/Lib/CommonLib/UnitTools.cpp
index f7ffd81dbe930cde1be1700683cde2ae67cf3ff2..d5fcad2659081de097189394b2478eb07a563f72 100644
--- a/source/Lib/CommonLib/UnitTools.cpp
+++ b/source/Lib/CommonLib/UnitTools.cpp
@@ -46,6 +46,9 @@
 #include <utility>
 #include <algorithm>
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+#include <fstream>
+#endif
 // CS tools
 
 bool CS::isDualITree( const CodingStructure &cs )
@@ -4841,3 +4844,1087 @@ bool allowLfnstWithMip(const Size& block)
   }
   return false;
 }
+
+
+#ifdef GREEN_METADATA_SEI_ENABLED
+void writeGMFAOutput(FeatureCounterStruct& featureCounterUpdated, FeatureCounterStruct& featureCounterOld, std::string GMFAFile,bool lastFrame )
+{
+  std::string fileName = std::string("");
+  std::string matlabFile = std::string("");
+  if (GMFAFile.length() == 0)
+  {
+    return;
+  }
+  fileName = GMFAFile;
+  size_t strStart = 0;
+  size_t strEnd = 0;
+  
+  int64_t codedFrames = featureCounterUpdated.iSlices + featureCounterUpdated.bSlices + featureCounterUpdated.pSlices;
+  strEnd = fileName.rfind('.');
+  strEnd = strEnd == fileName.length() ? 0 : strEnd;
+
+  if (!lastFrame)
+  {
+    fileName = fileName.substr(0, strEnd) + "_" + std::to_string(codedFrames) + fileName.substr(strEnd, fileName.length());
+  }
+  
+  strStart = fileName.find_last_of("/\\");
+  strStart = strStart == fileName.length() ? 0 : strStart+1;
+
+  matlabFile = fileName.substr(strStart, strEnd-strStart);
+  std::ofstream featureFile(fileName);
+
+  featureFile << "function [n]=";
+  featureFile << matlabFile.c_str();
+  featureFile << "() \n";
+  //General features
+  featureFile << "\tn.EO = 1;\n";
+  featureFile << "\tn.ISlice = " << featureCounterUpdated.iSlices-featureCounterOld.iSlices << ";\n";
+  featureFile << "\tn.PSlice = " << featureCounterUpdated.pSlices-featureCounterOld.pSlices << ";\n";
+  featureFile << "\tn.BSlice = " << featureCounterUpdated.bSlices-featureCounterOld.bSlices << ";\n";
+  
+  featureFile << "\tn.width  = " << featureCounterUpdated.width << " ; \n";
+  featureFile << "\tn.height = " << featureCounterUpdated.height << " ; \n";
+  featureFile << "\tn.baseQP = [...\n\t";
+  for (int iter = 0; iter < 64; iter++)
+  {
+    featureFile << " " << featureCounterUpdated.baseQP[iter]-featureCounterOld.baseQP[iter] << " ";
+    if (iter % 8 == 7 && iter != 63)
+    {
+      featureFile << " ...\n\t";
+    }
+  }
+  featureFile << "]; \n ";
+  
+  featureFile << "\tn.bytes  = " << featureCounterUpdated.bytes << " ; \n";
+  featureFile << "\tn.is8bit = " << featureCounterUpdated.is8bit << " ; \n";
+  featureFile << "\tn.is10bit = " << featureCounterUpdated.is10bit << " ; \n";
+  featureFile << "\tn.is12bit = " << featureCounterUpdated.is12bit << " ; \n";
+  featureFile << "\tn.isYUV400 = " << featureCounterUpdated.isYUV400 << " ; \n";
+  featureFile << "\tn.isYUV420 = " << featureCounterUpdated.isYUV420 << " ; \n";
+  featureFile << "\tn.isYUV422 = " << featureCounterUpdated.isYUV422 << " ; \n";
+  featureFile << "\tn.isYUV444 = " << featureCounterUpdated.isYUV444 << " ; \n";
+  //Intra Feature
+  featureToFile(featureFile, featureCounterUpdated.intraBlockSizes, "intraBlocks",true,featureCounterOld.intraBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaPlaBlockSizes, "intraPlaLuma",true,featureCounterOld.intraLumaPlaBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaDcBlockSizes, "intraDCLuma",true,featureCounterOld.intraLumaDcBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaHvdBlockSizes, "intraHVDLuma",true,featureCounterOld.intraLumaHvdBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaHvBlockSizes, "intraHVLuma",true,featureCounterOld.intraLumaHvBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaAngBlockSizes, "intraAngLuma",true,featureCounterOld.intraLumaAngBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaPlaBlockSizes, "intraPlaChroma",true,featureCounterOld.intraChromaPlaBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaDcBlockSizes, "intraDCChroma",true,featureCounterOld.intraChromaDcBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaHvdBlockSizes, "intraHVDChroma",true,featureCounterOld.intraChromaHvdBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaHvBlockSizes, "intraHVChroma",true,featureCounterOld.intraChromaHvBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaAngBlockSizes, "intraAngChroma",true,featureCounterOld.intraChromaAngBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaCrossCompBlockSizes, "intraCrossComp",true,featureCounterOld.intraChromaCrossCompBlockSizes);
+  
+  featureToFile(featureFile, featureCounterUpdated.intraPDPCBlockSizes, "intraPDPC",true,featureCounterOld.intraPDPCBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaPDPCBlockSizes, "intraLumaPDPC",true,featureCounterOld.intraLumaPDPCBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaPDPCBlockSizes, "intraChromaPDPC",true,featureCounterOld.intraChromaPDPCBlockSizes);
+  
+  featureToFile(featureFile, featureCounterUpdated.intraMIPBlockSizes, "intraMIP",true,featureCounterOld.intraMIPBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaMIPBlockSizes, "intraLumaMIP",true,featureCounterOld.intraLumaMIPBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaMIPBlockSizes, "intraChromaMIP",true,featureCounterOld.intraChromaMIPBlockSizes);
+  
+  featureToFile(featureFile, featureCounterUpdated.IBCBlockSizes, "intraIBC",true,featureCounterOld.IBCBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.IBCLumaBlockSizes, "intraLumaIBC",true,featureCounterOld.IBCLumaBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.IBCChromaBlockSizes, "intraChromaIBC",true,featureCounterOld.IBCChromaBlockSizes);
+  
+  featureToFile(featureFile, featureCounterUpdated.intraSubPartitionsHorizontal, "intraSubPartitionsHorizontal",true,featureCounterOld.intraSubPartitionsHorizontal);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaSubPartitionsHorizontal, "intraLumaSubPartitionsHorizontal",true,featureCounterOld.intraLumaSubPartitionsHorizontal);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaSubPartitionsHorizontal,"intraChromaSubPartitionsHorizontal",true,featureCounterOld.intraChromaSubPartitionsHorizontal);
+  
+  featureToFile(featureFile, featureCounterUpdated.intraSubPartitionsVertical, "intraSubPartitionsVertical",true,featureCounterOld.intraSubPartitionsVertical);
+  featureToFile(featureFile, featureCounterUpdated.intraLumaSubPartitionsVertical, "intraLumaSubPartitionsVertical",true,featureCounterOld.intraLumaSubPartitionsVertical);
+  featureToFile(featureFile, featureCounterUpdated.intraChromaSubPartitionsVertical, "intraChromaSubPartitionsVertical",true,featureCounterOld.intraChromaSubPartitionsVertical);
+  
+  //Inter
+  featureToFile(featureFile, featureCounterUpdated.interBlockSizes, "interBlocks",true,featureCounterOld.interBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.interLumaBlockSizes, "interLumaBlocks",true,featureCounterOld.interLumaBlockSizes);
+  featureToFile(featureFile, featureCounterUpdated.interChromaBlockSizes, "interChromaBlocks",true,featureCounterOld.interChromaBlockSizes);
+  
+  featureToFile(featureFile, featureCounterUpdated.interInterBlocks, "interInter",true,featureCounterOld.interInterBlocks);
+  featureToFile(featureFile, featureCounterUpdated.interLumaInterBlocks, "interLumaInter",true,featureCounterOld.interLumaInterBlocks);
+  featureToFile(featureFile, featureCounterUpdated.interChromaInterBlocks, "interChromaInter",true,featureCounterOld.interChromaInterBlocks);
+  
+  featureToFile(featureFile, featureCounterUpdated.interMergeBlocks, "interMerge",true,featureCounterOld.interMergeBlocks);
+  featureToFile(featureFile, featureCounterUpdated.interLumaMergeBlocks, "interLumaMerge",true,featureCounterOld.interLumaMergeBlocks);
+  featureToFile(featureFile, featureCounterUpdated.interChromaMergeBlocks, "interChromaMerge",true,featureCounterOld.interChromaMergeBlocks);
+  
+  featureToFile(featureFile, featureCounterUpdated.interSkipBlocks, "interSkip",true,featureCounterOld.interSkipBlocks);
+  featureToFile(featureFile, featureCounterUpdated.interLumaSkipBlocks, "interLumaSkip",true,featureCounterOld.interLumaSkipBlocks);
+  featureToFile(featureFile, featureCounterUpdated.interChromaSkipBlocks, "interChromaSkip",true,featureCounterOld.interChromaSkipBlocks);
+  
+  featureToFile(featureFile, featureCounterUpdated.affine, "interAffine",true,featureCounterOld.affine);
+  featureToFile(featureFile, featureCounterUpdated.affineLuma, "interLumaAffine",true,featureCounterOld.affineLuma);
+  featureToFile(featureFile, featureCounterUpdated.affineChroma, "interChromaAffine",true,featureCounterOld.affineChroma);
+  
+  featureToFile(featureFile, featureCounterUpdated.affineInter, "interAffineInter",true,featureCounterOld.affineInter);
+  featureToFile(featureFile, featureCounterUpdated.affineLumaInter, "interLumaAffineInter",true,featureCounterOld.affineLumaInter);
+  featureToFile(featureFile, featureCounterUpdated.affineChromaInter, "interChromaAffineInter",true,featureCounterOld.affineChromaInter);
+  
+  featureToFile(featureFile, featureCounterUpdated.affineMerge, "interAffineMerge",true,featureCounterOld.affineMerge);
+  featureToFile(featureFile, featureCounterUpdated.affineLumaMerge, "interLumaAffineMerge",true,featureCounterOld.affineLumaMerge);
+  featureToFile(featureFile, featureCounterUpdated.affineChromaMerge, "interChromaAffineMerge",true,featureCounterOld.affineChromaMerge);
+  
+  featureToFile(featureFile, featureCounterUpdated.affineSkip, "interAffineSkip",true,featureCounterOld.affineSkip);
+  featureToFile(featureFile, featureCounterUpdated.affineLumaSkip, "interLumaAffineSkip",true,featureCounterOld.affineLumaSkip);
+  featureToFile(featureFile, featureCounterUpdated.affineChromaSkip, "interChromaAffineSkip",true,featureCounterOld.affineChromaSkip);
+  
+  featureToFile(featureFile, featureCounterUpdated.geo, "geo",true,featureCounterOld.geo);
+  featureToFile(featureFile, featureCounterUpdated.geoLuma, "geoLuma",true,featureCounterOld.geoLuma);
+  featureToFile(featureFile, featureCounterUpdated.geoChroma, "geoChroma",true,featureCounterOld.geoChroma);
+  
+  featureToFile(featureFile, featureCounterUpdated.dmvrBlocks, "dmvrBlocks",true,featureCounterOld.dmvrBlocks);
+  featureToFile(featureFile, featureCounterUpdated.bdofBlocks, "bdofBlocks",true,featureCounterOld.bdofBlocks);
+
+  featureFile << "\tn.uniPredPel  = " << featureCounterUpdated.uniPredPel-featureCounterOld.uniPredPel << " ; \n";
+  featureFile << "\tn.biPredPel   = " << featureCounterUpdated.biPredPel-featureCounterOld.biPredPel << " ; \n";
+  featureFile << "\tn.fracPelHor  = " << featureCounterUpdated.fracPelHor-featureCounterOld.fracPelHor << " ; \n";
+  featureFile << "\tn.fracPelVer  = " << featureCounterUpdated.fracPelVer-featureCounterOld.fracPelVer << " ; \n";
+  featureFile << "\tn.fracPelBoth = " << featureCounterUpdated.fracPelBoth-featureCounterOld.fracPelBoth << " ; \n";
+  featureFile << "\tn.copyCUPel   = " << featureCounterUpdated.copyCUPel-featureCounterOld.copyCUPel << " ; \n";
+  
+  featureFile << "\tn.affineFracPelHor  = " << featureCounterUpdated.affineFracPelHor-featureCounterOld.affineFracPelHor << " ; \n";
+  featureFile << "\tn.affineFracPelVer  = " << featureCounterUpdated.affineFracPelVer-featureCounterOld.affineFracPelVer << " ; \n";
+  featureFile << "\tn.affineFracPelBoth = " << featureCounterUpdated.affineFracPelBoth-featureCounterOld.affineFracPelBoth << " ; \n";
+  featureFile << "\tn.affineCopyCUPel   = " << featureCounterUpdated.affineCopyCUPel-featureCounterOld.affineCopyCUPel << " ; \n";
+  
+  //Transform
+  featureToFile(featureFile, featureCounterUpdated.transformBlocks, "transform",true,featureCounterOld.transformBlocks);
+  featureToFile(featureFile, featureCounterUpdated.transformLumaBlocks, "transformLuma",true,featureCounterOld.transformLumaBlocks);
+  featureToFile(featureFile, featureCounterUpdated.transformChromaBlocks, "transformChroma",true,featureCounterOld.transformChromaBlocks);
+  
+  featureToFile(featureFile, featureCounterUpdated.transformSkipBlocks, "transformSkip",true,featureCounterOld.transformSkipBlocks);
+  featureToFile(featureFile, featureCounterUpdated.transformLumaSkipBlocks, "transformLumaSkip",true,featureCounterOld.transformLumaSkipBlocks);
+  featureToFile(featureFile, featureCounterUpdated.transformChromaSkipBlocks, "transformChromaSkip",true,featureCounterOld.transformChromaSkipBlocks);
+  
+  featureFile << "\tn.transformLFNST4  = " << featureCounterUpdated.transformLFNST4-featureCounterOld.transformLFNST4 << " ; \n";
+  featureFile << "\tn.transformLFNST8  = " << featureCounterUpdated.transformLFNST8-featureCounterOld.transformLFNST8 << " ; \n";
+  
+  featureFile << "\tn.nrOfCoeff  = " << featureCounterUpdated.nrOfCoeff-featureCounterOld.nrOfCoeff << " ; \n";
+  featureFile << "\tn.coeffG1    = " << featureCounterUpdated.coeffG1-featureCounterOld.coeffG1 << " ; \n";
+  featureFile << "\tn.valueOfCoeff  = " << featureCounterUpdated.valueOfCoeff-featureCounterOld.valueOfCoeff << " ; \n";
+  //In-Loop
+  featureFile << "\tn.BS0  = " << featureCounterUpdated.boundaryStrength[0]-featureCounterOld.boundaryStrength[0] << " ; \n";
+  featureFile << "\tn.BS1  = " << featureCounterUpdated.boundaryStrength[1]-featureCounterOld.boundaryStrength[1] << " ; \n";
+  featureFile << "\tn.BS2  = " << featureCounterUpdated.boundaryStrength[2]-featureCounterOld.boundaryStrength[2] << " ; \n";
+  featureFile << "\tn.BSPel0  = " << featureCounterUpdated.boundaryStrengthPel[0]-featureCounterOld.boundaryStrengthPel[0] << " ; \n";
+  featureFile << "\tn.BSPel1  = " << featureCounterUpdated.boundaryStrengthPel[1]-featureCounterOld.boundaryStrengthPel[1] << " ; \n";
+  featureFile << "\tn.BSPel2  = " << featureCounterUpdated.boundaryStrengthPel[2]-featureCounterOld.boundaryStrengthPel[2] << " ; \n";
+  featureFile << "\tn.saoLumaBO  = " << featureCounterUpdated.saoLumaBO-featureCounterOld.saoLumaBO << " ; \n";
+  featureFile << "\tn.saoLumaEO  = " << featureCounterUpdated.saoLumaEO-featureCounterOld.saoLumaEO << " ; \n";
+  featureFile << "\tn.saoChromaBO  = " << featureCounterUpdated.saoChromaBO-featureCounterOld.saoChromaBO << " ; \n";
+  featureFile << "\tn.saoChromaEO  = " << featureCounterUpdated.saoChromaEO-featureCounterOld.saoChromaEO << " ; \n";
+  featureFile <<   "\tn.saoLumaPels  = " << featureCounterUpdated.saoLumaPels << " ; \n";
+  featureFile <<   "\tn.saoChromaPels  = " << featureCounterUpdated.saoChromaPels << " ; \n";
+  featureFile << "\tn.alfLumaType7  = " << featureCounterUpdated.alfLumaType7-featureCounterOld.alfLumaType7 << " ; \n";
+  featureFile << "\tn.alfChromaType5 = " << featureCounterUpdated.alfChromaType5-featureCounterOld.alfChromaType5 << " ; \n";
+  featureFile <<   "\tn.alfLumaPels  = " << featureCounterUpdated.alfLumaPels << " ; \n";
+  featureFile <<   "\tn.alfChromaPels  = " << featureCounterUpdated.alfChromaPels << " ; \n";
+  featureFile << "\tn.ccalf = " << featureCounterUpdated.ccalf-featureCounterOld.ccalf  << " ; \n";
+  featureFile << "end\n";
+  featureFile.close();
+}
+
+void featureToFile(std::ofstream& featureFile,int featureCounterReference[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1], std::string featureName,bool calcDifference,int featureCounter[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1])
+{
+  featureFile <<   "\tn." << featureName << " = [...\n\t";
+  for (size_t i = 0; i < MAX_CU_DEPTH+1; i++)
+  {
+    for (size_t j = 0; j < MAX_CU_DEPTH+1; j++)
+    {
+      if (calcDifference)
+      {
+        featureFile << " " << featureCounterReference[j][i]  - featureCounter[j][i] << " ";
+      }
+      else
+      {
+        featureFile << " " << featureCounterReference[j][i]  << " ";
+      }
+    }
+    if (i != MAX_CU_DEPTH)
+    {
+      featureFile << ";... \n\t";
+    }
+  }
+  featureFile << "]; \n ";
+}
+
+void countFeatures(FeatureCounterStruct& featureCounter, CodingStructure& cs, const UnitArea& ctuArea)
+{
+  SizeType             cuWidthIdx     = MAX_UINT;
+  SizeType             cuHeightIdx    = MAX_UINT;
+  
+  for (auto &currCU: cs.traverseCUs(ctuArea, CHANNEL_TYPE_LUMA))
+  {
+    cuWidthIdx  = floorLog2(currCU.lwidth());
+    cuHeightIdx = floorLog2(currCU.lheight());
+    if ((cuWidthIdx <= MAX_CU_DEPTH+1) && (cuHeightIdx <= MAX_CU_DEPTH+1))
+    {
+      if (currCU.predMode == MODE_INTRA)   // Intra-Mode
+      {
+        for (auto &currPU: CU::traversePUs(currCU))
+        {
+          SizeType puWidthIdx  = floorLog2(currPU.Y().width);
+          SizeType puHeightIdx = floorLog2(currPU.Y().height);
+          if ((puWidthIdx <= MAX_CU_DEPTH+1) && (puHeightIdx <= MAX_CU_DEPTH+1))
+          {
+            featureCounter.intraBlockSizes[puWidthIdx][puHeightIdx]++;
+            SizeType lumaPredDir = currPU.intraDir[CHANNEL_TYPE_LUMA];
+            if (currCU.mipFlag)
+            {
+              featureCounter.intraMIPBlockSizes[puWidthIdx][puHeightIdx]++;
+              featureCounter.intraLumaMIPBlockSizes[puWidthIdx][puHeightIdx]++;
+            }
+            else
+            {
+              if (lumaPredDir == PLANAR_IDX)
+              {
+                featureCounter.intraLumaPlaBlockSizes[puWidthIdx][puHeightIdx]++;
+              }
+              else if (lumaPredDir == DC_IDX)
+              {
+                featureCounter.intraLumaDcBlockSizes[puWidthIdx][puHeightIdx]++;
+              }
+              else if (lumaPredDir == HOR_IDX || lumaPredDir == VER_IDX || lumaPredDir == DIA_IDX)
+              {
+                featureCounter.intraLumaHvdBlockSizes[puWidthIdx][puHeightIdx]++;
+              }
+              else
+              {
+                featureCounter.intraLumaAngBlockSizes[puWidthIdx][puHeightIdx]++;
+              }
+            }
+            bool tempPDPC =
+              (currPU.Y().width >= MIN_TB_SIZEY && currPU.Y().height >= MIN_TB_SIZEY) && isLuma(COMPONENT_Y)
+                ? currPU.multiRefIdx
+                : false;
+            if (tempPDPC)
+            {
+              featureCounter.intraPDPCBlockSizes[puWidthIdx][puHeightIdx]++;
+              featureCounter.intraLumaPDPCBlockSizes[puWidthIdx][puHeightIdx]++;
+            }
+          }
+        }
+
+        if (currCU.ispMode > NOT_INTRA_SUBPARTITIONS)
+        {
+          if (currCU.ispMode == VER_INTRA_SUBPARTITIONS)
+          {
+            featureCounter.intraSubPartitionsVertical[cuWidthIdx][cuHeightIdx]++;
+            featureCounter.intraLumaSubPartitionsVertical[cuWidthIdx][cuHeightIdx]++;
+          }
+          if (currCU.ispMode == HOR_INTRA_SUBPARTITIONS)
+          {
+            featureCounter.intraSubPartitionsHorizontal[cuWidthIdx][cuHeightIdx]++;
+            featureCounter.intraLumaSubPartitionsHorizontal[cuWidthIdx][cuHeightIdx]++;
+          }
+        }
+      }
+      else if (currCU.predMode == MODE_IBC)   // IBC-Mode
+      {
+        featureCounter.IBCBlockSizes[cuWidthIdx][cuHeightIdx]++;
+        featureCounter.IBCLumaBlockSizes[cuWidthIdx][cuHeightIdx]++;
+      }
+      else if (currCU.predMode == MODE_INTER)   // Inter-Mode
+      {
+        featureCounter.interBlockSizes[cuWidthIdx][cuHeightIdx]++;
+        featureCounter.interLumaBlockSizes[cuWidthIdx][cuHeightIdx]++;
+        featureCounter.interBlockSizes[cuWidthIdx-1][cuHeightIdx-1] += 2;
+        featureCounter.interChromaBlockSizes[cuWidthIdx-1][cuHeightIdx-1] += 2;
+        if (currCU.skip)
+        {
+          featureCounter.interSkipBlocks[cuWidthIdx][cuHeightIdx]++;
+          featureCounter.interLumaSkipBlocks[cuWidthIdx][cuHeightIdx]++;
+          featureCounter.interSkipBlocks[cuWidthIdx-1][cuHeightIdx-1] += 2;
+          featureCounter.interChromaSkipBlocks[cuWidthIdx-1][cuHeightIdx-1] += 2;
+          if (currCU.affine)
+          {
+            featureCounter.affine[cuWidthIdx][cuHeightIdx]++;
+            featureCounter.affineLuma[cuWidthIdx][cuHeightIdx]++;
+            featureCounter.affineSkip[cuWidthIdx][cuHeightIdx]++;
+            featureCounter.affineLumaSkip[cuWidthIdx][cuHeightIdx]++;
+            featureCounter.affine[cuWidthIdx-1][cuHeightIdx-1] += 2;
+            featureCounter.affineChroma[cuWidthIdx-1][cuHeightIdx-1] += 2;
+            featureCounter.affineSkip[cuWidthIdx-1][cuHeightIdx-1] += 2;
+            featureCounter.affineChromaSkip[cuWidthIdx-1][cuHeightIdx-1] += 2;
+          }
+        }
+        else
+        {
+          for (auto &currPU: CU::traversePUs(currCU))
+          {
+            SizeType puWidthIdx  = floorLog2(currPU.lwidth());
+            SizeType puHeightIdx = floorLog2(currPU.lheight());
+            if ((puWidthIdx <= MAX_CU_DEPTH+1) && (puHeightIdx <= MAX_CU_DEPTH+1))
+            {
+              if (currPU.mergeFlag)
+              {
+                featureCounter.interMergeBlocks[puWidthIdx][puHeightIdx]++;
+                featureCounter.interLumaMergeBlocks[puWidthIdx][puHeightIdx]++;
+                featureCounter.interMergeBlocks[puWidthIdx-1][puHeightIdx-1] += 2;
+                featureCounter.interChromaMergeBlocks[puWidthIdx-1][puHeightIdx-1] += 2;
+                if (currCU.affine)
+                {
+                  featureCounter.affine[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affineLuma[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affineMerge[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affineLumaMerge[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affine[puWidthIdx-1][puHeightIdx-1] += 2;
+                  featureCounter.affineChroma[puWidthIdx-1][puHeightIdx-1] += 2;
+                  featureCounter.affineMerge[puWidthIdx-1][puHeightIdx-1] += 2;
+                  featureCounter.affineChromaMerge[puWidthIdx-1][puHeightIdx-1] += 2;
+                }
+              }
+              else   // InterInter
+              {
+                featureCounter.interInterBlocks[puWidthIdx][puHeightIdx]++;
+                featureCounter.interLumaInterBlocks[puWidthIdx][puHeightIdx]++;
+                featureCounter.interInterBlocks[puWidthIdx-1][puHeightIdx-1] += 2;
+                featureCounter.interChromaInterBlocks[puWidthIdx-1][puHeightIdx-1] += 2;
+                if (currCU.affine)
+                {
+                  featureCounter.affine[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affineLuma[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affineInter[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affineLumaInter[puWidthIdx][puHeightIdx]++;
+                  featureCounter.affine[puWidthIdx-1][puHeightIdx-1] += 2;
+                  featureCounter.affineChroma[puWidthIdx-1][puHeightIdx-1] += 2;
+                  featureCounter.affineInter[puWidthIdx-1][puHeightIdx-1] += 2;
+                  featureCounter.affineChromaInter[puWidthIdx-1][puHeightIdx-1] += 2;
+                }
+              }
+              if (currPU.cu->geoFlag)
+              {
+                featureCounter.geo[puWidthIdx][puHeightIdx]++;
+                featureCounter.geoLuma[puWidthIdx][puHeightIdx]++;
+                featureCounter.geo[puWidthIdx-1][puHeightIdx-1] += 2;
+                featureCounter.geoChroma[puWidthIdx-1][puHeightIdx-1] += 2;
+              }
+            }
+          }
+        }
+
+
+
+        for (auto &currPU: CU::traversePUs(currCU))   // Check whether BDOF or DMVR are used in the current PU
+        {
+          bool m_DMVRapplied = false;
+          bool m_BDOFapplied = false;
+
+          PredictionUnit &pu          = currPU;
+          SizeType        puWidthIdx  = floorLog2(currPU.lwidth());
+          SizeType        puHeightIdx = floorLog2(currPU.lheight());
+          if ((puWidthIdx <= MAX_CU_DEPTH + 1) && (puHeightIdx <= MAX_CU_DEPTH + 1))
+          {
+            const PPS   &pps        = *pu.cs->pps;
+            const Slice &localSlice = *pu.cs->slice;
+            CHECK(!pu.cu->affine && pu.refIdx[0] >= 0 && pu.refIdx[1] >= 0 && (pu.lwidth() + pu.lheight() == 12),
+                  "invalid 4x8/8x4 bi-predicted blocks");
+
+            int refIdx0 = pu.refIdx[REF_PIC_LIST_0];
+            int refIdx1 = pu.refIdx[REF_PIC_LIST_1];
+
+            const WPScalingParam *wp0 = pu.cs->slice->getWpScaling(REF_PIC_LIST_0, refIdx0);
+            const WPScalingParam *wp1 = pu.cs->slice->getWpScaling(REF_PIC_LIST_1, refIdx1);
+
+            bool bioApplied = false;
+            if (pu.cs->sps->getBDOFEnabledFlag() && (!pu.cs->picHeader->getBdofDisabledFlag()))
+            {
+              if (pu.cu->affine)
+              {
+                bioApplied = false;
+              }
+              else
+              {
+                const bool biocheck0 = !((WPScalingParam::isWeighted(wp0) || WPScalingParam::isWeighted(wp1))
+                                         && localSlice.getSliceType() == B_SLICE);
+                const bool biocheck1 = !(pps.getUseWP() && localSlice.getSliceType() == P_SLICE);
+                if (biocheck0 && biocheck1 && PU::isSimpleSymmetricBiPred(pu) && (pu.Y().height >= 8)
+                    && (pu.Y().width >= 8) && ((pu.Y().height * pu.Y().width) >= 128))
+                {
+                  bioApplied = true;
+                }
+              }
+
+              if (bioApplied && pu.ciipFlag)
+              {
+                bioApplied = false;
+              }
+
+              if (bioApplied && pu.cu->smvdMode)
+              {
+                bioApplied = false;
+              }
+
+              if (pu.cu->cs->sps->getUseBcw() && bioApplied && pu.cu->bcwIdx != BCW_DEFAULT)
+              {
+                bioApplied = false;
+              }
+            }
+            if (pu.mmvdEncOptMode == 2 && pu.mmvdMergeFlag)
+            {
+              bioApplied = false;
+            }
+            bool dmvrApplied = false;
+            dmvrApplied      = PU::checkDMVRCondition(pu);
+
+            bool refIsScaled =
+              (refIdx0 < 0 ? false : pu.cu->slice->getRefPic(REF_PIC_LIST_0, refIdx0)->isRefScaled(pu.cs->pps))
+              || (refIdx1 < 0 ? false : pu.cu->slice->getRefPic(REF_PIC_LIST_1, refIdx1)->isRefScaled(pu.cs->pps));
+            dmvrApplied = dmvrApplied && !refIsScaled;
+            bioApplied  = bioApplied && !refIsScaled;
+
+            for (uint32_t refList = 0; refList < NUM_REF_PIC_LIST_01; refList++)
+            {
+              if (pu.refIdx[refList] < 0)
+              {
+                continue;
+              }
+
+              RefPicList eRefPicList = (refList ? REF_PIC_LIST_1 : REF_PIC_LIST_0);
+
+              CHECK(CU::isIBC(*pu.cu) && eRefPicList != REF_PIC_LIST_0, "Invalid interdir for ibc mode");
+              CHECK(CU::isIBC(*pu.cu) && pu.refIdx[refList] != MAX_NUM_REF, "Invalid reference index for ibc mode");
+              CHECK((CU::isInter(*pu.cu) && pu.refIdx[refList] >= localSlice.getNumRefIdx(eRefPicList)),
+                    "Invalid reference index");
+            }
+
+            m_BDOFapplied = bioApplied;
+
+            if (!pu.cu->geoFlag && (!dmvrApplied) && (!bioApplied) && pps.getWPBiPred()
+                && localSlice.getSliceType() == B_SLICE && pu.cu->bcwIdx == BCW_DEFAULT)
+            {
+              m_DMVRapplied = false;
+            }
+            else if (!pu.cu->geoFlag && pps.getUseWP() && localSlice.getSliceType() == P_SLICE)
+            {
+              m_DMVRapplied = false;
+            }
+            else
+            {
+              m_DMVRapplied = dmvrApplied;
+            }
+
+            if (m_DMVRapplied)
+            {
+              featureCounter.dmvrBlocks[puWidthIdx][puHeightIdx]++;
+            }
+
+            if (m_BDOFapplied)
+            {
+              featureCounter.bdofBlocks[puWidthIdx][puHeightIdx]++;
+            }
+          }
+        }
+      }
+
+      if (currCU.predMode == MODE_INTER)
+      {
+        const int nShift  = MV_FRACTIONAL_BITS_DIFF;
+        const int nOffset = 1 << (nShift - 1);
+
+        for (auto &currPU: CU::traversePUs(currCU))
+        {
+          int numberOfPels = currPU.lwidth() * currPU.lheight();
+          if (!currPU.cu->affine && !currPU.cu->geoFlag)
+          {
+            if (currPU.interDir != MODE_TYPE_INTRA /* PRED_L1 */)
+            {
+              Mv mv   = currPU.mv[REF_PIC_LIST_0];
+              Mv mvd  = currPU.mvd[REF_PIC_LIST_0];
+              mv.hor  = mv.hor >= 0 ? (mv.hor + nOffset) >> nShift : -((-mv.hor + nOffset) >> nShift);
+              mv.ver  = mv.ver >= 0 ? (mv.ver + nOffset) >> nShift : -((-mv.ver + nOffset) >> nShift);
+              mvd.hor = mvd.hor >= 0 ? (mvd.hor + nOffset) >> nShift : -((-mvd.hor + nOffset) >> nShift);
+              mvd.ver = mvd.ver >= 0 ? (mvd.ver + nOffset) >> nShift : -((-mvd.ver + nOffset) >> nShift);
+
+              if (currPU.interDir == 3)
+              {
+                featureCounter.biPredPel += numberOfPels;
+              }
+              else
+              {
+                featureCounter.uniPredPel += numberOfPels;
+              }
+
+              if (mv.hor == 0 && mv.ver == 0)
+              {
+                featureCounter.copyCUPel += numberOfPels;
+              }
+              if (mv.hor != 0 && mv.ver == 0)
+              {
+                featureCounter.fracPelHor += numberOfPels;
+              }
+              if (mv.hor == 0 && mv.ver != 0)
+              {
+                featureCounter.fracPelVer += numberOfPels;
+              }
+              if (mv.hor != 0 && mv.ver != 0)
+              {
+                featureCounter.fracPelBoth += numberOfPels;
+              }
+            }
+            if (currPU.interDir != 1 /* PRED_L1 */)
+            {
+              Mv mv   = currPU.mv[REF_PIC_LIST_1];
+              Mv mvd  = currPU.mvd[REF_PIC_LIST_1];
+              mv.hor  = mv.hor >= 0 ? (mv.hor + nOffset) >> nShift : -((-mv.hor + nOffset) >> nShift);
+              mv.ver  = mv.ver >= 0 ? (mv.ver + nOffset) >> nShift : -((-mv.ver + nOffset) >> nShift);
+              mvd.hor = mvd.hor >= 0 ? (mvd.hor + nOffset) >> nShift : -((-mvd.hor + nOffset) >> nShift);
+              mvd.ver = mvd.ver >= 0 ? (mvd.ver + nOffset) >> nShift : -((-mvd.ver + nOffset) >> nShift);
+
+              if (currPU.interDir != 3)
+              {
+                featureCounter.uniPredPel += numberOfPels;
+              }
+
+              if (mv.hor == 0 && mv.ver == 0)
+              {
+                featureCounter.copyCUPel += numberOfPels;
+              }
+              if (mv.hor != 0 && mv.ver == 0)
+              {
+                featureCounter.fracPelHor += numberOfPels;
+              }
+              if (mv.hor == 0 && mv.ver != 0)
+              {
+                featureCounter.fracPelVer += numberOfPels;
+              }
+              if (mv.hor != 0 && mv.ver != 0)
+              {
+                featureCounter.fracPelBoth += numberOfPels;
+              }
+            }
+          }
+          else if (currPU.cu->affine)
+          {
+            if (currPU.interDir != 2 /* PRED_L1 */)
+            {
+              Mv                mv[3];
+              const CMotionBuf &mb = currPU.getMotionBuf();
+              mv[0]                = mb.at(0, 0).mv[REF_PIC_LIST_0];
+              mv[1]                = mb.at(mb.width - 1, 0).mv[REF_PIC_LIST_0];
+              mv[2]                = mb.at(0, mb.height - 1).mv[REF_PIC_LIST_0];
+              mv[0].hor = mv[0].hor >= 0 ? (mv[0].hor + nOffset) >> nShift : -((-mv[0].hor + nOffset) >> nShift);
+              mv[0].ver = mv[0].ver >= 0 ? (mv[0].ver + nOffset) >> nShift : -((-mv[0].ver + nOffset) >> nShift);
+              mv[1].hor = mv[1].hor >= 0 ? (mv[1].hor + nOffset) >> nShift : -((-mv[1].hor + nOffset) >> nShift);
+              mv[1].ver = mv[1].ver >= 0 ? (mv[1].ver + nOffset) >> nShift : -((-mv[1].ver + nOffset) >> nShift);
+              mv[2].hor = mv[2].hor >= 0 ? (mv[2].hor + nOffset) >> nShift : -((-mv[2].hor + nOffset) >> nShift);
+              mv[2].ver = mv[2].ver >= 0 ? (mv[2].ver + nOffset) >> nShift : -((-mv[2].ver + nOffset) >> nShift);
+
+              if (currPU.interDir == 3)
+              {
+                featureCounter.biPredPel += numberOfPels;
+              }
+              else
+              {
+                featureCounter.uniPredPel += numberOfPels;
+              }
+
+              for (int iter = 0; iter < 3; iter++)
+              {
+                if (mv[iter].hor == 0 && mv[iter].ver == 0)
+                {
+                  featureCounter.affineCopyCUPel += numberOfPels;
+                }
+                if (mv[iter].hor != 0 && mv[iter].ver == 0)
+                {
+                  featureCounter.affineFracPelHor += numberOfPels;
+                }
+                if (mv[iter].hor == 0 && mv[iter].ver != 0)
+                {
+                  featureCounter.affineFracPelVer += numberOfPels;
+                }
+                if (mv[iter].hor != 0 && mv[iter].ver != 0)
+                {
+                  featureCounter.affineFracPelBoth += numberOfPels;
+                }
+              }
+            }
+            if (currPU.interDir != 1 /* PRED_L1 */)
+            {
+              Mv                mv[3];
+              const CMotionBuf &mb = currPU.getMotionBuf();
+              mv[0]                = mb.at(0, 0).mv[REF_PIC_LIST_1];
+              mv[1]                = mb.at(mb.width - 1, 0).mv[REF_PIC_LIST_1];
+              mv[2]                = mb.at(0, mb.height - 1).mv[REF_PIC_LIST_1];
+
+              mv[0].hor = mv[0].hor >= 0 ? (mv[0].hor + nOffset) >> nShift : -((-mv[0].hor + nOffset) >> nShift);
+              mv[0].ver = mv[0].ver >= 0 ? (mv[0].ver + nOffset) >> nShift : -((-mv[0].ver + nOffset) >> nShift);
+              mv[1].hor = mv[1].hor >= 0 ? (mv[1].hor + nOffset) >> nShift : -((-mv[1].hor + nOffset) >> nShift);
+              mv[1].ver = mv[1].ver >= 0 ? (mv[1].ver + nOffset) >> nShift : -((-mv[1].ver + nOffset) >> nShift);
+              mv[2].hor = mv[2].hor >= 0 ? (mv[2].hor + nOffset) >> nShift : -((-mv[2].hor + nOffset) >> nShift);
+              mv[2].ver = mv[2].ver >= 0 ? (mv[2].ver + nOffset) >> nShift : -((-mv[2].ver + nOffset) >> nShift);
+
+              if (currPU.interDir != 3)
+              {
+                featureCounter.uniPredPel += numberOfPels;
+              }
+
+              for (int iter = 0; iter < 3; iter++)
+              {
+                if (mv[iter].hor == 0 && mv[iter].ver == 0)
+                {
+                  featureCounter.affineCopyCUPel += numberOfPels;
+                }
+                if (mv[iter].hor != 0 && mv[iter].ver == 0)
+                {
+                  featureCounter.affineFracPelHor += numberOfPels;
+                }
+                if (mv[iter].hor == 0 && mv[iter].ver != 0)
+                {
+                  featureCounter.affineFracPelVer += numberOfPels;
+                }
+                if (mv[iter].hor != 0 && mv[iter].ver != 0)
+                {
+                  featureCounter.affineFracPelBoth += numberOfPels;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      for (auto &currTU: CU::traverseTUs(currCU))
+      {
+        bool     m_notOnCounterGridWidth  = false;
+        bool     m_notOnCounterGridHeight = false;
+        SizeType currTUWIdx               = MAX_UINT;
+        SizeType currTUHIdx               = MAX_UINT;
+        currTUWIdx                        = floorLog2(currTU.Y().width);
+        currTUHIdx                        = floorLog2(currTU.Y().height);
+        if ((currTUWIdx <= MAX_CU_DEPTH + 1) && (currTUHIdx <= MAX_CU_DEPTH + 1))
+        {
+          if (currTU.Y().width != int(pow(2, currTUWIdx)))
+          {
+            m_notOnCounterGridWidth = true;
+          }
+
+          if (currTU.Y().height != int(pow(2, currTUHIdx)))
+          {
+            m_notOnCounterGridHeight = true;
+          }
+
+          if (currTU.cu->skip)
+          {
+            featureCounter.transformSkipBlocks[currTUWIdx][currTUHIdx]++;
+            featureCounter.transformLumaSkipBlocks[currTUWIdx][currTUHIdx]++;
+            if (m_notOnCounterGridHeight || m_notOnCounterGridWidth)
+            {
+              if (m_notOnCounterGridHeight)
+              {
+                featureCounter.transformSkipBlocks[currTUWIdx][currTUHIdx - 1]++;
+                featureCounter.transformLumaSkipBlocks[currTUWIdx][currTUHIdx - 1]++;
+              }
+
+              if (m_notOnCounterGridWidth)
+              {
+                featureCounter.transformSkipBlocks[currTUWIdx - 1][currTUHIdx]++;
+                featureCounter.transformLumaSkipBlocks[currTUWIdx - 1][currTUHIdx]++;
+              }
+            }
+          }
+          else
+          {
+            featureCounter.transformBlocks[currTUWIdx][currTUHIdx]++;
+            featureCounter.transformLumaBlocks[currTUWIdx][currTUHIdx]++;
+            if (m_notOnCounterGridHeight || m_notOnCounterGridWidth)
+            {
+              if (m_notOnCounterGridHeight)
+              {
+                featureCounter.transformBlocks[currTUWIdx][currTUHIdx - 1]++;
+                featureCounter.transformLumaBlocks[currTUWIdx][currTUHIdx - 1]++;
+              }
+
+              if (m_notOnCounterGridWidth)
+              {
+                featureCounter.transformBlocks[currTUWIdx - 1][currTUHIdx]++;
+                featureCounter.transformLumaBlocks[currTUWIdx - 1][currTUHIdx]++;
+              }
+            }
+          }
+
+          if (currTU.cu->lfnstIdx && currTU.mtsIdx[COMPONENT_Y] != MTS_SKIP
+              && (currTU.cu->isSepTree() ? true : isLuma(COMPONENT_Y)))
+          {
+            bool significantCoeff = false;
+            for (int bufferScan = 0; bufferScan < currTU.lwidth() * currTU.lheight() && !significantCoeff; bufferScan++)
+            {
+              if (currTU.getCoeffs(ComponentID(0)).buf[bufferScan] != 0)
+              {
+                significantCoeff = true;
+              }
+            }
+
+            if (significantCoeff)
+            {
+              if (currTU.Y().width >= 8 && currTU.Y().height >= 8)
+              {
+                featureCounter.transformLFNST8++;
+              }
+              else
+              {
+                featureCounter.transformLFNST4++;
+              }
+            }
+          }
+
+          int maxNumberOfCoeffs = 0;
+
+          maxNumberOfCoeffs = currTU.Y().width * currTU.Y().height;
+          if (currTU.cbf[COMPONENT_Y] == 0)
+          {
+            maxNumberOfCoeffs = 0;
+          }
+
+          for (int i = 0; i < maxNumberOfCoeffs; i++)
+          {
+            int counterCoeff = currTU.getCoeffs(COMPONENT_Y).buf[i];
+            if (counterCoeff != 0)
+            {
+              featureCounter.nrOfCoeff++;
+
+              if (counterCoeff < 0)
+              {
+                counterCoeff *= -1;
+              }
+
+              if (counterCoeff > 1)
+              {
+                featureCounter.coeffG1++;
+              }
+
+              counterCoeff--;   // taking account of the fact that n_coeffg1 has already been counted
+              double ldVal = counterCoeff < 2 ? 0.0
+                                              : floorLog2(counterCoeff)
+                                                  + (2 * counterCoeff >= (3 << floorLog2(counterCoeff)) ? 0.585 : 0.0);
+              featureCounter.valueOfCoeff += ldVal;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  for (auto &currCU: cs.traverseCUs(ctuArea, CHANNEL_TYPE_CHROMA))
+  {
+    SizeType cuCbWidthIdx  = floorLog2(currCU.Cb().width);
+    SizeType cuCbHeightIdx = floorLog2(currCU.Cb().height);
+    SizeType cuCrWidthIdx  = floorLog2(currCU.Cr().width);
+    SizeType cuCrHeightIdx = floorLog2(currCU.Cr().height);
+    
+    if ((cuCbWidthIdx <= MAX_CU_DEPTH+1) && (cuCbHeightIdx <= MAX_CU_DEPTH+1) && (cuCrWidthIdx <= MAX_CU_DEPTH+1) && (cuCrHeightIdx <= MAX_CU_DEPTH+1))
+    {
+      if (currCU.predMode == MODE_INTRA)   // Intra-Mode
+      {
+        for (auto &currPU: CU::traversePUs(currCU))
+        {
+          SizeType chromaWidthIdx  = floorLog2(currPU.Cb().width);
+          SizeType chromaHeightIdx = floorLog2(currPU.Cb().height);
+          if ((chromaWidthIdx <= MAX_CU_DEPTH+1) && (chromaHeightIdx <= MAX_CU_DEPTH+1))
+          {
+            featureCounter.intraBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+            SizeType chromaPredDir = currPU.intraDir[CHANNEL_TYPE_CHROMA];
+            // Intra Prediction Direction
+            if (currCU.mipFlag)
+            {
+              featureCounter.intraMIPBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              featureCounter.intraChromaMIPBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+            }
+            else
+            {
+              if (chromaPredDir == PLANAR_IDX)
+              {
+                featureCounter.intraChromaPlaBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+              else if (chromaPredDir == DC_IDX)
+              {
+                featureCounter.intraChromaDcBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+              else if (chromaPredDir == HOR_IDX || chromaPredDir == VER_IDX || chromaPredDir == DIA_IDX)
+              {
+                featureCounter.intraChromaHvdBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+              else if (chromaPredDir < LM_CHROMA_IDX)
+              {
+                featureCounter.intraChromaAngBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+
+              if (PU::isLMCMode(chromaPredDir))
+              {
+                featureCounter.intraChromaCrossCompBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+            }
+
+            bool tempPDPC =
+              (currPU.Cb().width >= MIN_TB_SIZEY && currPU.Cb().height >= MIN_TB_SIZEY) && isLuma(COMPONENT_Cb)
+                ? currPU.multiRefIdx
+                : false;
+            if (tempPDPC)
+            {
+              featureCounter.intraPDPCBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              featureCounter.intraChromaPDPCBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+            }
+          }
+        }
+
+        if (currCU.ispMode > NOT_INTRA_SUBPARTITIONS)
+        {
+          if (currCU.ispMode == VER_INTRA_SUBPARTITIONS)
+          {
+            featureCounter.intraSubPartitionsVertical[cuCbWidthIdx][cuCbHeightIdx]++;
+            featureCounter.intraChromaSubPartitionsVertical[cuCbWidthIdx][cuCbHeightIdx]++;
+          }
+          if (currCU.ispMode == HOR_INTRA_SUBPARTITIONS)
+          {
+            featureCounter.intraSubPartitionsHorizontal[cuCbWidthIdx][cuCbHeightIdx]++;
+            featureCounter.intraChromaSubPartitionsHorizontal[cuCbWidthIdx][cuCbHeightIdx]++;
+          }
+        }
+      }
+
+      if (currCU.predMode == MODE_INTRA)   // Intra-Mode
+      {
+        for (auto &currPU: CU::traversePUs(currCU))
+        {
+          SizeType chromaWidthIdx  = floorLog2(currPU.Cr().width);
+          SizeType chromaHeightIdx = floorLog2(currPU.Cr().height);
+          featureCounter.intraBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+          SizeType chromaPredDir = currPU.intraDir[CHANNEL_TYPE_CHROMA];
+          if ((chromaWidthIdx <= MAX_CU_DEPTH + 1) && (chromaHeightIdx <= MAX_CU_DEPTH + 1))
+          {
+            if (currCU.mipFlag)
+            {
+              featureCounter.intraMIPBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              featureCounter.intraChromaMIPBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+            }
+            else
+            {
+              if (chromaPredDir == PLANAR_IDX)
+              {
+                featureCounter.intraChromaPlaBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+              else if (chromaPredDir == DC_IDX)
+              {
+                featureCounter.intraChromaDcBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+              else if (chromaPredDir == HOR_IDX || chromaPredDir == VER_IDX || chromaPredDir == DIA_IDX)
+              {
+                featureCounter.intraChromaHvdBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+              else if (chromaPredDir < NUM_CHROMA_MODE)
+              {
+                featureCounter.intraChromaAngBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+
+              if (PU::isLMCMode(chromaPredDir))
+              {
+                featureCounter.intraChromaCrossCompBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              }
+            }
+            bool tempPDPC =
+              (currPU.Cr().width >= MIN_TB_SIZEY && currPU.Cr().height >= MIN_TB_SIZEY) && isLuma(COMPONENT_Cr)
+                ? currPU.multiRefIdx
+                : false;
+            if (tempPDPC)
+            {
+              featureCounter.intraPDPCBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+              featureCounter.intraChromaPDPCBlockSizes[chromaWidthIdx][chromaHeightIdx]++;
+            }
+          }
+        }
+
+        if (currCU.ispMode > NOT_INTRA_SUBPARTITIONS)
+        {
+          if (currCU.ispMode == VER_INTRA_SUBPARTITIONS)
+          {
+            featureCounter.intraSubPartitionsVertical[cuCrWidthIdx][cuCrHeightIdx]++;
+            featureCounter.intraChromaSubPartitionsVertical[cuCrWidthIdx][cuCrHeightIdx]++;
+          }
+          if (currCU.ispMode == HOR_INTRA_SUBPARTITIONS)
+          {
+            featureCounter.intraSubPartitionsHorizontal[cuCrWidthIdx][cuCrHeightIdx]++;
+            featureCounter.intraSubPartitionsHorizontal[cuCrWidthIdx][cuCrHeightIdx]++;
+          }
+        }
+      }
+
+
+      for (auto &currTU: CU::traverseTUs(currCU))
+      {
+        for (int m_compID = 1; m_compID < getNumberValidComponents(currTU.chromaFormat); m_compID++)
+        {
+          bool     m_notOnCounterGridWidth  = false;
+          bool     m_notOnCounterGridHeight = false;
+          SizeType currTUWIdx               = MAX_UINT;
+          SizeType currTUHIdx               = MAX_UINT;
+          if ((currTUWIdx <= MAX_CU_DEPTH+1) && (currTUHIdx <= MAX_CU_DEPTH+1))
+          {
+            if (m_compID == COMPONENT_Cb)
+            {
+              currTUWIdx = floorLog2(currTU.Cb().width);
+              currTUHIdx = floorLog2(currTU.Cb().height);
+              if (currTU.Cb().width != int(pow(2, currTUWIdx)))
+              {
+                m_notOnCounterGridWidth = true;
+              }
+
+              if (currTU.Cb().height != int(pow(2, currTUHIdx)))
+              {
+                m_notOnCounterGridHeight = true;
+              }
+            }
+            else
+            {
+              currTUWIdx = floorLog2(currTU.Cr().width);
+              currTUHIdx = floorLog2(currTU.Cr().height);
+              if (currTU.Cr().width != int(pow(2, currTUWIdx)))
+              {
+                m_notOnCounterGridWidth = true;
+              }
+
+              if (currTU.Cr().height != int(pow(2, currTUHIdx)))
+              {
+                m_notOnCounterGridHeight = true;
+              }
+            }
+
+            if (currTU.cu->skip)
+            {
+              featureCounter.transformSkipBlocks[currTUWIdx][currTUHIdx]++;
+              featureCounter.transformChromaSkipBlocks[currTUWIdx][currTUHIdx]++;
+              if (m_notOnCounterGridHeight || m_notOnCounterGridWidth)
+              {
+                if (m_notOnCounterGridHeight)
+                {
+                  featureCounter.transformSkipBlocks[currTUWIdx][currTUHIdx - 1]++;
+                  featureCounter.transformChromaSkipBlocks[currTUWIdx][currTUHIdx - 1]++;
+                }
+
+                if (m_notOnCounterGridWidth)
+                {
+                  featureCounter.transformSkipBlocks[currTUWIdx - 1][currTUHIdx]++;
+                  featureCounter.transformChromaSkipBlocks[currTUWIdx - 1][currTUHIdx]++;
+                }
+              }
+            }
+            else
+            {
+              featureCounter.transformBlocks[currTUWIdx][currTUHIdx]++;
+              featureCounter.transformChromaBlocks[currTUWIdx][currTUHIdx]++;
+              if (m_notOnCounterGridHeight || m_notOnCounterGridWidth)
+              {
+                if (m_notOnCounterGridHeight)
+                {
+                  featureCounter.transformBlocks[currTUWIdx][currTUHIdx - 1]++;
+                  featureCounter.transformChromaBlocks[currTUWIdx][currTUHIdx - 1]++;
+                }
+
+                if (m_notOnCounterGridWidth)
+                {
+                  featureCounter.transformBlocks[currTUWIdx - 1][currTUHIdx]++;
+                  featureCounter.transformChromaBlocks[currTUWIdx - 1][currTUHIdx]++;
+                }
+              }
+            }
+
+            if (currTU.cu->lfnstIdx && currTU.mtsIdx[m_compID] != MTS_SKIP
+                && (currTU.cu->isSepTree() ? true : isLuma(COMPONENT_Cr)))
+            {
+              bool significantCoeff = false;
+              if (m_compID == COMPONENT_Cb)
+              {
+                for (int bufferScan = 0;
+                     bufferScan < currTU.Cb().width * currTU.Cb().height && significantCoeff == false; bufferScan++)
+                {
+                  if (currTU.getCoeffs(ComponentID(m_compID)).buf[bufferScan] != 0)
+                  {
+                    significantCoeff = true;
+                  }
+                }
+              }
+              else
+              {
+                for (int bufferScan = 0;
+                     bufferScan < currTU.Cr().width * currTU.Cr().height && significantCoeff == false; bufferScan++)
+                {
+                  if (currTU.getCoeffs(ComponentID(m_compID)).buf[bufferScan] != 0)
+                  {
+                    significantCoeff = true;
+                  }
+                }
+              }
+
+              if (significantCoeff)
+              {
+                if (currTU.Cb().width >= 8 && currTU.Cb().height >= 8)
+                {
+                  featureCounter.transformLFNST8++;
+                }
+                else
+                {
+                  featureCounter.transformLFNST4++;
+                }
+              }
+            }
+
+            int maxNumberOfCoeffs = 0;
+            if (m_compID == COMPONENT_Y)
+            {
+              maxNumberOfCoeffs = currTU.Y().width * currTU.Y().height;
+              if (currTU.cbf[COMPONENT_Y] == 0)
+              {
+                maxNumberOfCoeffs = 0;
+              }
+            }
+            else if (m_compID == COMPONENT_Cb)
+            {
+              maxNumberOfCoeffs = currTU.Cb().width * currTU.Cb().height;
+              if (currTU.cbf[COMPONENT_Cb] == 0)
+              {
+                maxNumberOfCoeffs = 0;
+              }
+            }
+            else
+            {
+              maxNumberOfCoeffs = currTU.Cr().width * currTU.Cr().height;
+              if (currTU.cbf[COMPONENT_Cr] == 0)
+              {
+                maxNumberOfCoeffs = 0;
+              }
+            }
+
+            for (int i = 0; i < maxNumberOfCoeffs; i++)
+            {
+              int counterCoeff = currTU.getCoeffs((ComponentID) m_compID).buf[i];
+
+              if (counterCoeff != 0)
+              {
+                featureCounter.nrOfCoeff++;
+
+                if (counterCoeff < 0)   // abs val
+                {
+                  counterCoeff *= -1;
+                }
+
+                if (counterCoeff > 1)
+                {
+                  featureCounter.coeffG1++;
+                }
+
+                counterCoeff--;   // taking account of the fact that n_coeffg1 has already been counted
+                double ldVal =
+                  counterCoeff < 2
+                    ? 0.0
+                    : floorLog2(counterCoeff) + (2 * counterCoeff >= (3 << floorLog2(counterCoeff)) ? 0.585 : 0.0);
+                featureCounter.valueOfCoeff += ldVal;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+#endif
diff --git a/source/Lib/CommonLib/UnitTools.h b/source/Lib/CommonLib/UnitTools.h
index 2b34156aa11e39361fb889238967615ae3f0e17f..6bb72a5d4caa8bd34bc0dae20a913a3e7b4348ac 100644
--- a/source/Lib/CommonLib/UnitTools.h
+++ b/source/Lib/CommonLib/UnitTools.h
@@ -231,7 +231,11 @@ uint32_t getCtuAddr        (const Position& pos, const PreCalcValues &pcv);
 int  getNumModesMip   (const Size& block);
 int getMipSizeId      (const Size& block);
 bool allowLfnstWithMip(const Size& block);
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+void writeGMFAOutput(FeatureCounterStruct& featureCounter, FeatureCounterStruct& featureCounterReference, std::string GMFAFile, bool lastFrame);
+void featureToFile(std::ofstream& featureFile,int featureCounterReference[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1], std::string featureName,bool calcDifference=false,int featureCounter[MAX_CU_DEPTH+1][MAX_CU_DEPTH+1]=NULL);
+void countFeatures  (FeatureCounterStruct& featureCounterStruct, CodingStructure& cs, const UnitArea& ctuArea);
+#endif
 template<typename T, size_t N>
 uint32_t updateCandList(T mode, double uiCost, static_vector<T, N> &candModeList,
                         static_vector<double, N> &candCostList, size_t uiFastCandNum = N, int *iserttPos = nullptr)
diff --git a/source/Lib/DecoderLib/DecLib.cpp b/source/Lib/DecoderLib/DecLib.cpp
index 25ccc032f08f4889e1f0f4338a40570d764e0055..8a29bcde60561cba8c083e59a92700cf3e611594 100644
--- a/source/Lib/DecoderLib/DecLib.cpp
+++ b/source/Lib/DecoderLib/DecLib.cpp
@@ -686,6 +686,10 @@ void DecLib::executeLoopFilters()
     m_cReshaper.setRecReshaped(false);
     m_cSAO.setReshaper(&m_cReshaper);
   }
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct initValues;
+  cs.m_featureCounter =  initValues;
+#endif
   // deblocking filter
   m_deblockingFilter.deblockingFilterPic( cs );
   CS::setRefinedMotionField(cs);
@@ -703,6 +707,11 @@ void DecLib::executeLoopFilters()
     m_cALF.ALFProcess(cs);
   }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+  m_featureCounter.addSAO(cs.m_featureCounter);
+  m_featureCounter.addALF(cs.m_featureCounter);
+  m_featureCounter.addBoundaryStrengths(cs.m_featureCounter);
+#endif
   for (int i = 0; i < cs.pps->getNumSubPics() && m_targetSubPicIdx; i++)
   {
     // keep target subpic samples untouched, for other subpics mask their output sample value to 0
@@ -781,7 +790,11 @@ void DecLib::finishPicture(int &poc, PicList *&rpcListPic, MsgLevel msgl, bool a
 
   Slice*  pcSlice = m_pcPic->cs->slice;
   m_prevPicPOC = pcSlice->getPOC();
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  m_featureCounter.height = m_pcPic->Y().height;
+  m_featureCounter.width = m_pcPic->Y().width;
+#endif
+  
   char c = (pcSlice->isIntra() ? 'I' : pcSlice->isInterP() ? 'P' : 'B');
   if (!m_pcPic->referenced)
   {
@@ -3421,9 +3434,15 @@ bool DecLib::xDecodeSlice(InputNALUnit &nalu, int &iSkipFrame, int iPOCLastDispl
     }
   }
 #endif // GDR_LEAK_TEST
+#ifdef GREEN_METADATA_SEI_ENABLED
+  pcSlice->setFeatureCounter(this->m_featureCounter);
+#endif
   //  Decode a picture
   m_cSliceDecoder.decompressSlice( pcSlice, &( nalu.getBitstream() ), ( m_pcPic->poc == getDebugPOC() ? getDebugCTU() : -1 ) );
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  this->m_featureCounter = pcSlice->getFeatureCounter();
+#endif
+  
   m_bFirstSliceInPicture = false;
   m_uiSliceSegmentIdx++;
 
diff --git a/source/Lib/DecoderLib/DecLib.h b/source/Lib/DecoderLib/DecLib.h
index c198ab0bdb33dce84701f291bed4334fd9ba3b17..948b62240a0c512b0d3b1c22fccc3800e161a389 100644
--- a/source/Lib/DecoderLib/DecLib.h
+++ b/source/Lib/DecoderLib/DecLib.h
@@ -321,12 +321,22 @@ public:
   const OPI* getOPI()                     { return m_opi; }
 
   bool      getMixedNaluTypesInPicFlag();
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct m_featureCounter;
+  bool m_GMFAFramewise;
+  std::string   m_GMFAFile;
+  void setFeatureCounter (FeatureCounterStruct b ) {m_featureCounter = b;}
+  FeatureCounterStruct getFeatureCounter (){return m_featureCounter;}
+  void setGMFAFile(std::string b){m_GMFAFile = b;}
+  void setFeatureAnalysisFramewise(bool b){m_GMFAFramewise = b;}
+#endif
 
 #if JVET_Z0120_SII_SEI_PROCESSING
   bool  getShutterFilterFlag()        const { return m_ShutterFilterEnable; }
   void  setShutterFilterFlag(bool value) { m_ShutterFilterEnable = value; }
 #endif
 
+
 protected:
   void  xUpdateRasInit(Slice* slice);
 
diff --git a/source/Lib/DecoderLib/DecSlice.cpp b/source/Lib/DecoderLib/DecSlice.cpp
index 753f6dcdabbcbd8b2d3cf2fd6fe485bf25ebc0aa..498413832c4d2ea23c6dc069567e24ce78c302a0 100644
--- a/source/Lib/DecoderLib/DecSlice.cpp
+++ b/source/Lib/DecoderLib/DecSlice.cpp
@@ -232,7 +232,12 @@ void DecSlice::decompressSlice( Slice* slice, InputBitstream* bitstream, int deb
     cabacReader.coding_tree_unit( cs, ctuArea, pic->m_prevQP, ctuRsAddr );
 
     m_pcCuDecoder->decompressCtu( cs, ctuArea );
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+    FeatureCounterStruct featureCounter = slice->getFeatureCounter();
+    countFeatures( featureCounter, cs,ctuArea);
+    slice->setFeatureCounter(featureCounter);
+#endif
+    
     if( ctuXPosInCtus == tileXPosInCtus && wavefrontsEnabled )
     {
       m_entropyCodingSyncContextState = cabacReader.getCtx();
@@ -285,7 +290,40 @@ void DecSlice::decompressSlice( Slice* slice, InputBitstream* bitstream, int deb
       }
     }
   }
-
+  
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct featureCounter = slice->getFeatureCounter();
+  featureCounter.baseQP[slice->getSliceQpBase()] ++;
+  if (featureCounter.isYUV400 == -1)
+  {
+    featureCounter.isYUV400 = sps->getChromaFormatIdc() == CHROMA_400 ? 1 : 0;
+    featureCounter.isYUV420 = sps->getChromaFormatIdc() == CHROMA_420 ? 1 : 0;
+    featureCounter.isYUV422 = sps->getChromaFormatIdc() == CHROMA_422 ? 1 : 0;
+    featureCounter.isYUV444 = sps->getChromaFormatIdc() == CHROMA_444 ? 1 : 0;
+  }
+  
+  if (featureCounter.is8bit == -1)
+  {
+    featureCounter.is8bit  = (sps->getBitDepth(CHANNEL_TYPE_LUMA) == 8) ? 1 : 0;
+    featureCounter.is10bit = (sps->getBitDepth(CHANNEL_TYPE_LUMA) == 10) ? 1 : 0;
+    featureCounter.is12bit = (sps->getBitDepth(CHANNEL_TYPE_LUMA) == 12) ? 1 : 0;
+  }
+  
+  if (slice->getSliceType() == B_SLICE)
+  {
+    featureCounter.bSlices++;
+  }
+  else if (slice->getSliceType() == P_SLICE)
+  {
+    featureCounter.pSlices++;
+  }
+  else
+  {
+    featureCounter.iSlices++;
+  }
+  slice->setFeatureCounter(featureCounter);
+#endif
+  
   // deallocate all created substreams, including internal buffers.
   for( auto substr: ppcSubstreams )
   {
diff --git a/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp b/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp
index 43289b8b154b1f108fdc25e86c2d5de4d263dffb..8257f3b56faf31e43f4326b77e13751b5f9c7009 100644
--- a/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp
+++ b/source/Lib/EncoderLib/EncAdaptiveLoopFilter.cpp
@@ -3235,6 +3235,10 @@ void EncAdaptiveLoopFilter::alfReconstructor(CodingStructure& cs, const PelUnitB
                 coeff = m_fixedFilterSetCoeffDec[filterSetIndex];
                 clip = m_clipDefault;
               }
+#ifdef GREEN_METADATA_SEI_ENABLED
+              cs.m_featureCounter.alfLumaType7+= (width * height / 16) ;
+              cs.m_featureCounter.alfLumaPels += (width * height);
+#endif
               m_filter7x7Blk(m_classifier, recBuf, buf, blkDst, blkSrc, COMPONENT_Y, coeff, clip,
                              m_clpRngs.comp[COMPONENT_Y], cs, m_alfVBLumaCTUHeight, m_alfVBLumaPos);
             }
@@ -3252,6 +3256,10 @@ void EncAdaptiveLoopFilter::alfReconstructor(CodingStructure& cs, const PelUnitB
                 m_filter5x5Blk(m_classifier, recBuf, buf, blkDst, blkSrc, compID, m_chromaCoeffFinal[alt_num],
                                m_chromaClippFinal[alt_num], m_clpRngs.comp[compIdx], cs, m_alfVBChmaCTUHeight,
                                m_alfVBChmaPos);
+#ifdef GREEN_METADATA_SEI_ENABLED
+                cs.m_featureCounter.alfChromaType5+= ((width >> chromaScaleX) * (height >> chromaScaleY) / 16);
+                cs.m_featureCounter.alfChromaPels += ((width >> chromaScaleX) * (height >> chromaScaleY)) ;
+#endif
               }
             }
 
@@ -3282,6 +3290,10 @@ void EncAdaptiveLoopFilter::alfReconstructor(CodingStructure& cs, const PelUnitB
           }
           m_filter7x7Blk(m_classifier, recBuf, recExtBuf, blk, blk, COMPONENT_Y, coeff, clip,
                          m_clpRngs.comp[COMPONENT_Y], cs, m_alfVBLumaCTUHeight, m_alfVBLumaPos);
+#ifdef GREEN_METADATA_SEI_ENABLED
+          cs.m_featureCounter.alfLumaType7+= (width * height / 16) ;
+          cs.m_featureCounter.alfLumaPels += (width * height);
+#endif
         }
 
         for (int compIdx = 1; compIdx < MAX_NUM_COMPONENT; compIdx++)
@@ -3296,6 +3308,10 @@ void EncAdaptiveLoopFilter::alfReconstructor(CodingStructure& cs, const PelUnitB
             m_filter5x5Blk(m_classifier, recBuf, recExtBuf, blk, blk, compID, m_chromaCoeffFinal[alt_num],
                            m_chromaClippFinal[alt_num], m_clpRngs.comp[compIdx], cs, m_alfVBChmaCTUHeight,
                            m_alfVBChmaPos);
+#ifdef GREEN_METADATA_SEI_ENABLED
+            cs.m_featureCounter.alfChromaType5+= ((width >> chromaScaleX) * (height >> chromaScaleY) / 16) ;
+            cs.m_featureCounter.alfChromaPels += ((width >> chromaScaleX) * (height >> chromaScaleY)) ;
+#endif
           }
         }
       }
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 391b65ec16ac702d920873e11a8623aecd86776a..8c38a9772f70675d8c23870f3911bf2ede64260a 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -564,6 +564,23 @@ protected:
   bool      m_doSEIPersistenceFlag;
   int       m_doSEITransformType;
   bool      m_parameterSetsInclusionIndicationSEIEnabled;
+#ifdef GREEN_METADATA_SEI_ENABLED
+  bool      m_greenMetadataInfoSEIEnabled;
+  int      m_greenMetadataType;
+  int      m_greenMetadataGranularityType;
+  int      m_greenMetadataExtendedRepresentation;
+  int      m_greenMetadataPeriodType;
+  int      m_greenMetadataPeriodNumSeconds;
+  int      m_greenMetadataPeriodNumPictures;
+  //Metrics for quality recovery after low-power encoding
+  int      m_xsdNumberMetrics;
+  bool     m_xsdMetricTypePSNR;
+  bool     m_xsdMetricTypeSSIM;
+  bool     m_xsdMetricTypeVMAF;
+  bool     m_xsdMetricTypeWPSNR;
+  bool     m_xsdMetricTypeWSPSNR;
+  bool     m_xsdMetricTypeEstimatedEnergy;
+#endif
   bool      m_selfContainedClvsFlag;
   bool      m_bpDeltasGOPStructure;
   bool      m_decodingUnitInfoSEIEnabled;
@@ -1925,6 +1942,32 @@ public:
   int   getDOSEITransformType() const                                { return m_doSEITransformType; }
   void  setParameterSetsInclusionIndicationSEIEnabled(bool b)        { m_parameterSetsInclusionIndicationSEIEnabled = b; }
   bool  getParameterSetsInclusionIndicationSEIEnabled() const        { return m_parameterSetsInclusionIndicationSEIEnabled; }
+#ifdef GREEN_METADATA_SEI_ENABLED
+  void setSEIGreenMetadataInfoSEIEnable(int b)                       { (b >= 0) ? m_greenMetadataInfoSEIEnabled = 1 : m_greenMetadataInfoSEIEnabled =0;}
+  bool getSEIGreenMetadataInfoSEIEnable()                            { return m_greenMetadataInfoSEIEnabled;}
+  void setSEIGreenMetadataType(int b)                                { m_greenMetadataType = b;}
+  int getSEIGreenMetadataType()                                      { return m_greenMetadataType;}
+  int getSEIGreenMetadataGranularityType()                           { return m_greenMetadataGranularityType;}
+  void setSEIGreenMetadataGranularityType(int b)                     { m_greenMetadataGranularityType = b;}
+  int getSEIGreenMetadataExtendedRepresentation()                    { return m_greenMetadataExtendedRepresentation;}
+  void setSEIGreenMetadataExtendedRepresentation(int b)              { m_greenMetadataExtendedRepresentation = b;}
+  void setSEIGreenMetadataPeriodType(int b)                          { m_greenMetadataPeriodType = b;}
+  int getSEIGreenMetadataPeriodType()                                { return m_greenMetadataPeriodType;}
+  void setSEIGreenMetadataPeriodNumSeconds(int b)                    {m_greenMetadataPeriodNumSeconds = b;}
+  int getSEIGreenMetadataPeriodNumSeconds()                          {return m_greenMetadataPeriodNumSeconds;}
+  void setSEIGreenMetadataPeriodNumPictures(int b)                   {m_greenMetadataPeriodNumPictures = b;}
+  int getSEIGreenMetadataPeriodNumPictures()                         {return m_greenMetadataPeriodNumPictures;}
+  void setSEIXSDNumberMetrics(int b)                                  { m_xsdNumberMetrics = b;}
+  int  getSEIXSDNumberMetrics()                                      { return m_xsdNumberMetrics;}
+  void setSEIXSDMetricTypePSNR(bool b)                                { m_xsdMetricTypePSNR = b;}
+  bool getSEIXSDMetricTypePSNR()                                     { return m_xsdMetricTypePSNR;}
+  void setSEIXSDMetricTypeSSIM(bool b)                                { m_xsdMetricTypeSSIM = b;}
+  bool getSEIXSDMetricTypeSSIM()                                     { return m_xsdMetricTypeSSIM;}
+  void setSEIXSDMetricTypeWPSNR(bool b)                               { m_xsdMetricTypeWPSNR = b;}
+  bool getSEIXSDMetricTypeWPSNR()                                    { return m_xsdMetricTypeWPSNR;}
+  void setSEIXSDMetricTypeWSPSNR(bool b)                              { m_xsdMetricTypeWSPSNR = b;}
+  bool getSEIXSDMetricTypeWSPSNR()                                   { return m_xsdMetricTypeWSPSNR;}
+#endif
   void  setSelfContainedClvsFlag(bool b)                             { m_selfContainedClvsFlag = b; }
   int   getSelfContainedClvsFlag()                                   { return m_selfContainedClvsFlag; }
   void  setBpDeltasGOPStructure(bool b)                              { m_bpDeltasGOPStructure = b;    }
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index 13012b02678c30122e63e916179ae0998b673fca..546d2bc5568bbea978a445356b27538bbcff2476 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -223,7 +223,6 @@ void EncGOP::init ( EncLib* pcEncLib )
   ::memset(m_lastBPSEI, 0, sizeof(m_lastBPSEI));
   ::memset(m_totalCoded, 0, sizeof(m_totalCoded));
   m_HRD                = pcEncLib->getHRD();
-
   m_AUWriterIf = pcEncLib->getAUWriterIf();
 
   if (m_pcCfg->getFilmGrainAnalysisEnabled())
@@ -3342,7 +3341,18 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l
           computeSignalling(pcPic, pcSlice);
         }
         m_pcSliceEncoder->precompressSlice( pcPic );
-        m_pcSliceEncoder->compressSlice   ( pcPic, false, false );
+#ifdef GREEN_METADATA_SEI_ENABLED
+        pcPic->setFeatureCounter(m_featureCounter);
+        if(m_pcEncLib->getGMFAFramewise())
+        {
+          FeatureCounterStruct m_featureCounterFrameReference;
+          m_featureCounterFrameReference = m_featureCounter;
+        }
+#endif
+        m_pcSliceEncoder->compressSlice   ( pcPic, false, false);
+#ifdef GREEN_METADATA_SEI_ENABLED
+        m_featureCounter = pcPic->getFeatureCounter();
+#endif
 
         if(sliceIdx < pcPic->cs->pps->getNumSlicesInPic() - 1)
         {
@@ -3359,7 +3369,47 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l
           uiNumSliceSegments++;
         }
       }
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+      m_featureCounter.baseQP[pcPic->getLossyQPValue()] ++;
+      if (m_featureCounter.isYUV420 == -1)
+      {
+        m_featureCounter.isYUV400 = pcSlice->getSPS()->getChromaFormatIdc() == CHROMA_400 ? 1 : 0;
+        m_featureCounter.isYUV420 = pcSlice->getSPS()->getChromaFormatIdc() == CHROMA_420 ? 1 : 0;
+        m_featureCounter.isYUV422 = pcSlice->getSPS()->getChromaFormatIdc() == CHROMA_422 ? 1 : 0;
+        m_featureCounter.isYUV444 = pcSlice->getSPS()->getChromaFormatIdc() == CHROMA_444 ? 1 : 0;
+      }
+  
+      if (m_featureCounter.is8bit == -1)
+      {
+        m_featureCounter.is8bit  = (pcSlice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA) == 8) ? 1 : 0;
+        m_featureCounter.is10bit = (pcSlice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA) == 10) ? 1 : 0;
+        m_featureCounter.is12bit = (pcSlice->getSPS()->getBitDepth(CHANNEL_TYPE_LUMA) == 12) ? 1 : 0;
+      }
+  
+  
+      if (pcSlice->getSliceType() == B_SLICE)
+      {
+        m_featureCounter.bSlices++;
+      }
+      else if (pcSlice->getSliceType()== P_SLICE)
+      {
+        m_featureCounter.pSlices++;
+      }
+      else
+      {
+        m_featureCounter.iSlices++;
+      }
+  
+      if (m_featureCounter.width == -1)
+      {
+        m_featureCounter.width = pcPic->getPicWidthInLumaSamples();
+      }
+  
+      if (m_featureCounter.height == -1)
+      {
+        m_featureCounter.height = pcPic->getPicHeightInLumaSamples();
+      }
+#endif
       duData.clear();
 
       CodingStructure& cs = *pcPic->cs;
@@ -3455,12 +3505,21 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l
           }
         }
       }
+#ifdef GREEN_METADATA_SEI_ENABLED
+      cs.m_featureCounter.resetBoundaryStrengths();
+#endif
       m_pcLoopFilter->deblockingFilterPic( cs );
+#ifdef GREEN_METADATA_SEI_ENABLED
+      m_featureCounter.addBoundaryStrengths(cs.m_featureCounter);
+#endif
 
       CS::setRefinedMotionField(cs);
 
       if( pcSlice->getSPS()->getSAOEnabledFlag() )
       {
+#ifdef GREEN_METADATA_SEI_ENABLED
+        cs.m_featureCounter.resetSAO();
+#endif
         bool sliceEnabled[MAX_NUM_COMPONENT];
         m_pcSAO->initCABACEstimator( m_pcEncLib->getCABACEncoder(), m_pcEncLib->getCtxCache(), pcSlice );
         m_pcSAO->SAOProcess( cs, sliceEnabled, pcSlice->getLambdas(),
@@ -3483,6 +3542,9 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l
             pcPic->slices[s]->setSaoEnabledFlag(CHANNEL_TYPE_CHROMA, sliceEnabled[COMPONENT_Cb]);
           }
         }
+#ifdef GREEN_METADATA_SEI_ENABLED
+        m_featureCounter.addSAO(cs.m_featureCounter);
+#endif
       }
 
       if( pcSlice->getSPS()->getALFEnabledFlag() )
@@ -3495,13 +3557,18 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l
           pcPic->slices[s]->setAlfEnabledFlag(COMPONENT_Y, false);
         }
         m_pcALF->initCABACEstimator(m_pcEncLib->getCABACEncoder(), m_pcEncLib->getCtxCache(), pcSlice, m_pcEncLib->getApsMap());
+#ifdef GREEN_METADATA_SEI_ENABLED
+        cs.m_featureCounter.resetALF();
+#endif
         m_pcALF->ALFProcess(cs, pcSlice->getLambdas()
 #if ENABLE_QPA
           , (m_pcCfg->getUsePerceptQPA() && !m_pcCfg->getUseRateCtrl() && pcSlice->getPPS()->getUseDQP() ? m_pcEncLib->getRdCost()->getChromaWeight() : 0.0)
 #endif
           , pcPic, uiNumSliceSegments
         );
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+        m_featureCounter.addALF(cs.m_featureCounter);
+#endif
         //assign ALF slice header
         for (int s = 0; s < uiNumSliceSegments; s++)
         {
@@ -4113,7 +4180,79 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l
       double PSNR_Y;
       xCalculateAddPSNRs(isField, isTff, gopId, pcPic, accessUnit, rcListPic, encTime, snr_conversion, printFrameMSE,
                          printMSSSIM, &PSNR_Y, isEncodeLtRef);
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+      this->setFeatureCounter(m_featureCounter);
+      m_SEIGreenQualityMetrics.psnr = PSNR_Y;
+      if (m_pcCfg->getSEIGreenMetadataInfoSEIEnable())
+      {
+        SEIGreenMetadataInfo* seiGreenMetadataInfo = new SEIGreenMetadataInfo;
+        seiGreenMetadataInfo->m_greenMetadataType = m_pcCfg->getSEIGreenMetadataType();
+        seiGreenMetadataInfo->m_numPictures = m_pcCfg->getSEIGreenMetadataPeriodNumPictures();
+        seiGreenMetadataInfo->m_periodType = m_pcCfg->getSEIGreenMetadataPeriodType();
+        seiGreenMetadataInfo->m_numSeconds = m_pcCfg->getSEIGreenMetadataPeriodNumSeconds();
+        seiGreenMetadataInfo->m_greenMetadataGranularityType = m_pcCfg->getSEIGreenMetadataGranularityType();
+        seiGreenMetadataInfo->m_greenMetadataExtendedRepresentation = m_pcCfg->getSEIGreenMetadataExtendedRepresentation();
+        int64_t codedFrames = m_featureCounter.iSlices + m_featureCounter.bSlices + m_featureCounter.pSlices;
+        int numberFrames = seiGreenMetadataInfo->m_numSeconds * m_pcCfg->getFrameRate();
+    
+        if (seiGreenMetadataInfo->m_greenMetadataType == 0)
+        {
+          switch (m_pcCfg->getSEIGreenMetadataPeriodType()) // Period type
+          {
+          case 0: //0x00 complexity metrics are applicable to a single picture
+            seiGreenMetadataInfo->m_numPictures = m_pcCfg->getSEIGreenMetadataPeriodNumPictures();
+            xCalculateGreenComplexityMetrics(m_featureCounter, m_featureCounterReference, seiGreenMetadataInfo);
+            m_seiEncoder.initSEIGreenMetadataInfo(seiGreenMetadataInfo,  m_featureCounter, m_SEIGreenQualityMetrics,m_SEIGreenComplexityMetrics);
+            leadingSeiMessages.push_back(seiGreenMetadataInfo);
+            m_featureCounterReference = m_featureCounter;
+            break;
+          case 1: //0x01 complexity metrics are applicable to all pictures in decoding order, up to (but not including) the picture containing the next I slice
+            if (codedFrames == m_pcCfg->getFramesToBeEncoded() || codedFrames == 1)
+            {
+              xCalculateGreenComplexityMetrics(m_featureCounter, m_featureCounterReference, seiGreenMetadataInfo);
+              m_seiEncoder.initSEIGreenMetadataInfo(seiGreenMetadataInfo,  m_featureCounter, m_SEIGreenQualityMetrics,m_SEIGreenComplexityMetrics);
+              leadingSeiMessages.push_back(seiGreenMetadataInfo);
+              m_featureCounterReference = m_featureCounter;
+            }
+            break;
+          case 2: //0x02 complexity metrics are applicable over a specified time interval in seconds
+            seiGreenMetadataInfo->m_numSeconds = m_pcCfg->getSEIGreenMetadataPeriodNumSeconds();
+            if( ((codedFrames% numberFrames) == 0) || (codedFrames == m_pcCfg->getFramesToBeEncoded()))
+            {
+              seiGreenMetadataInfo->m_numSeconds = int(floor(double(codedFrames)/double(m_pcCfg->getFrameRate())));
+              xCalculateGreenComplexityMetrics(m_featureCounter, m_featureCounterReference, seiGreenMetadataInfo);
+              m_seiEncoder.initSEIGreenMetadataInfo(seiGreenMetadataInfo,  m_featureCounter, m_SEIGreenQualityMetrics,m_SEIGreenComplexityMetrics);
+              leadingSeiMessages.push_back(seiGreenMetadataInfo);
+              m_featureCounterReference = m_featureCounter;
+            }
+            break;
+          case 3: //0x03 complexity metrics are applicable over a specified number of pictures counted in decoding order
+            seiGreenMetadataInfo->m_numPictures = m_pcCfg->getSEIGreenMetadataPeriodNumPictures();
+            if( ((codedFrames%(seiGreenMetadataInfo->m_numPictures)) == 0) || (codedFrames == m_pcCfg->getFramesToBeEncoded()))
+            {
+              xCalculateGreenComplexityMetrics(m_featureCounter, m_featureCounterReference, seiGreenMetadataInfo);
+              m_seiEncoder.initSEIGreenMetadataInfo(seiGreenMetadataInfo,  m_featureCounter, m_SEIGreenQualityMetrics,m_SEIGreenComplexityMetrics);
+              leadingSeiMessages.push_back(seiGreenMetadataInfo);
+              m_featureCounterReference = m_featureCounter;
+            }
+            break;
+          case 4: //0x04 complexity metrics are applicable to a single picture with slice or tile granularity
+          case 5: //0x05 complexity metrics are applicable to a single picture with subpicture granularity
+          case 6: //0x06 complexity metrics are applicable to all pictures in decoding order, up to (but not including) the picture containing the next I slice with subpicture granularity
+          case 7: //0x07 complexity metrics are applicable over a specified time interval in seconds with subpicture granularity
+          case 8: //0x08 complexity metrics are applicable over a specified number of pictures counted in decoding order with subpicture granularity
+          default: //0x05-0xFF reserved
+            break;
+          }
+        }
+        else if (seiGreenMetadataInfo->m_greenMetadataType == 1) // Quality metric signaling
+        {
+          m_seiEncoder.initSEIGreenMetadataInfo(seiGreenMetadataInfo, m_featureCounter, m_SEIGreenQualityMetrics, m_SEIGreenComplexityMetrics);
+          leadingSeiMessages.push_back(seiGreenMetadataInfo);
+        }
+      }
+#endif
+      
       xWriteTrailingSEIMessages(trailingSeiMessages, accessUnit, pcSlice->getTLayer());
 
 #if GDR_ENABLED
@@ -5161,7 +5300,148 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni
     std::cout << "\r\t" << pcSlice->getPOC();
     std::cout.flush();
   }
+#ifdef GREEN_METADATA_SEI_ENABLED
+  m_SEIGreenQualityMetrics.ssim = msssim[0];
+  m_SEIGreenQualityMetrics.wpsnr = dPSNR[0];
+#endif
+}
+
+#ifdef GREEN_METADATA_SEI_ENABLED
+void EncGOP::xCalculateGreenComplexityMetrics( FeatureCounterStruct featureCounter, FeatureCounterStruct featureCounterReference, SEIGreenMetadataInfo* seiGreenMetadataInfo)
+{
+  double chromaFormatMultiplier = 0;
+  
+  if (featureCounter.isYUV400 == 1)
+  {
+    chromaFormatMultiplier = 1;
+  }
+  else if (featureCounter.isYUV420)
+  {
+    chromaFormatMultiplier = 1.5;
+  }
+  else if (featureCounter.isYUV422)
+  {
+    chromaFormatMultiplier = 2;
+  }
+  else if (featureCounter.isYUV444)
+  {
+    chromaFormatMultiplier = 3;
+  }
+  
+  // Initialize
+  int64_t totalNum4BlocksPic = 0;
+  int64_t totalNum4BlocksInPeriod = 0;
+  int64_t maxNumDeblockingInstances = 0;
+  
+  double numNonZeroBlocks = 0;
+  double numNonZero4_8_16_Blocks = 0;
+  double numNonZero32_64_128_Blocks = 0;
+  double numNonZero256_512_1024_Blocks = 0;
+  double numNonZero2048_4096_Blocks = 0;
+  double numIntraPredictedBlocks = 0;
+  double numBiAndGpmPredictedBlocks = 0;
+  double numBDOFPredictedBlocks = 0;
+  double numDeblockingInstances = 0;
+  double numSaoFilteredBlocks = 0;
+  double numAlfFilteredBlocks = 0;
+  // Calculate difference
+  FeatureCounterStruct featureCounterDifference;
+  
+  featureCounterDifference.iSlices  = featureCounter.iSlices - featureCounterReference.iSlices;
+  featureCounterDifference.bSlices  = featureCounter.bSlices - featureCounterReference.bSlices;
+  featureCounterDifference.pSlices  = featureCounter.pSlices - featureCounterReference.pSlices;
+  featureCounterDifference.nrOfCoeff = featureCounter.nrOfCoeff - featureCounterReference.nrOfCoeff;
+  featureCounterDifference.biPredPel = featureCounter.biPredPel - featureCounterReference.biPredPel;
+  featureCounterDifference.boundaryStrength[0] = featureCounter.boundaryStrength[0] - featureCounterReference.boundaryStrength[0];
+  featureCounterDifference.boundaryStrength[1] = featureCounter.boundaryStrength[1] - featureCounterReference.boundaryStrength[1];
+  featureCounterDifference.boundaryStrength[2] = featureCounter.boundaryStrength[2] - featureCounterReference.boundaryStrength[2];
+  featureCounterDifference.saoLumaEO = featureCounter.saoLumaEO - featureCounterReference.saoLumaEO;
+  featureCounterDifference.saoLumaBO = featureCounter.saoLumaBO - featureCounterReference.saoLumaBO;
+  featureCounterDifference.saoChromaEO = featureCounter.saoChromaEO - featureCounterReference.saoChromaEO;
+  featureCounterDifference.saoChromaBO = featureCounter.saoChromaBO - featureCounterReference.saoChromaBO;
+  featureCounterDifference.saoLumaPels = featureCounter.saoLumaPels - featureCounterReference.saoLumaPels;
+  featureCounterDifference.saoChromaPels = featureCounter.saoChromaPels - featureCounterReference.saoChromaPels;
+  featureCounterDifference.alfLumaType7 = featureCounter.alfLumaType7 - featureCounterReference.alfLumaType7;
+  featureCounterDifference.alfChromaType5 = featureCounter.alfChromaType5 - featureCounterReference.alfChromaType5;
+  featureCounterDifference.alfLumaPels = featureCounter.alfLumaPels - featureCounterReference.alfLumaPels;
+  featureCounterDifference.alfChromaPels = featureCounter.alfChromaPels - featureCounterReference.alfChromaPels;
+  
+  
+  for (int i = 0; i < MAX_CU_DEPTH+1; i++)
+  {
+    for (int j = 0; j < MAX_CU_DEPTH+1; j++)
+    {
+      featureCounterDifference.transformBlocks[i][j] = featureCounter.transformBlocks[i][j] - featureCounterReference.transformBlocks[i][j];
+      featureCounterDifference.intraBlockSizes[i][j] = featureCounter.intraBlockSizes[i][j] - featureCounterReference.intraBlockSizes[i][j];
+      featureCounterDifference.geo[i][j] = featureCounter.geo[i][j] - featureCounterReference.geo[i][j];
+      featureCounterDifference.bdofBlocks[i][j] = featureCounter.bdofBlocks[i][j] - featureCounterReference.bdofBlocks[i][j];
+    }
+  }
+  
+  
+  //Calculate complexity metrics
+  totalNum4BlocksPic = int(chromaFormatMultiplier * featureCounter.width * featureCounter.height / 4);
+  totalNum4BlocksInPeriod = int((featureCounterDifference.iSlices + featureCounterDifference.pSlices + featureCounterDifference.bSlices) * totalNum4BlocksPic);
+  maxNumDeblockingInstances = int(chromaFormatMultiplier * totalNum4BlocksInPeriod - 2 * (featureCounter.width + featureCounter.height) * 2);
+  
+  for (int i = 0; i < MAX_CU_DEPTH+1; i++)
+  {
+    for(int j = 0; j < MAX_CU_DEPTH+1; j++)
+    {
+      double numberOfPels = pow(2,i) * pow(2,j);
+      numNonZeroBlocks += double(featureCounterDifference.transformBlocks[i][j] * numberOfPels / 4);
+      numIntraPredictedBlocks += double(featureCounterDifference.intraBlockSizes[i][j] * numberOfPels / 4);
+      
+      if (numberOfPels == 4 || numberOfPels == 8 || numberOfPels == 16)
+      {
+        numNonZero4_8_16_Blocks += double(featureCounterDifference.transformBlocks[i][j] * numberOfPels / 4);
+      }
+      
+      if (numberOfPels == 32 || numberOfPels == 64 || numberOfPels == 128)
+      {
+        numNonZero32_64_128_Blocks += double(featureCounterDifference.transformBlocks[i][j] * numberOfPels / 4);
+      }
+      
+      if (numberOfPels == 256 || numberOfPels == 512 || numberOfPels == 1024)
+      {
+        numNonZero256_512_1024_Blocks += double(featureCounterDifference.transformBlocks[i][j] * numberOfPels / 4);
+      }
+      
+      if (numberOfPels == 2048 || numberOfPels == 4096 )
+      {
+        numNonZero2048_4096_Blocks += double(featureCounterDifference.transformBlocks[i][j] * numberOfPels / 4);
+      }
+      numBDOFPredictedBlocks += double(featureCounterDifference.bdofBlocks[i][j] * numberOfPels / 4);
+      numBiAndGpmPredictedBlocks += double(featureCounterDifference.geo[i][j] * numberOfPels / 4);
+    }
+  }
+  
+  numBiAndGpmPredictedBlocks += double(featureCounterDifference.biPredPel/4);
+  numDeblockingInstances = double(featureCounterDifference.boundaryStrength[0] + featureCounterDifference.boundaryStrength[1] + featureCounterDifference.boundaryStrength[2]);
+  numSaoFilteredBlocks   = double(featureCounterDifference.saoLumaPels + featureCounterDifference.saoChromaPels)/4;
+  numAlfFilteredBlocks   = double(featureCounterDifference.alfLumaPels + featureCounterDifference.alfChromaPels)/4;
+  
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionNonZeroBlocksArea = int(floor( 255.0 * numNonZeroBlocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionNonZero_4_8_16BlocksArea = int(floor(255.0 * numNonZero4_8_16_Blocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionNonZero_32_64_128BlocksArea = int(floor( 255.0 * numNonZero32_64_128_Blocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionNonZero_256_512_1024BlocksArea = int(floor( 255.0 * numNonZero256_512_1024_Blocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionNonZero_2048_4096BlocksArea = int(floor( 255.0 * numNonZero2048_4096_Blocks / totalNum4BlocksInPeriod));
+  if (numNonZeroBlocks != 0)
+  {
+    seiGreenMetadataInfo->m_greenComplexityMetrics.portionNonZeroTransformCoefficientsArea =
+      int(floor(255.0 * featureCounterDifference.nrOfCoeff / (4 *numNonZeroBlocks)));
+  }
+  
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionIntraPredictedBlocksArea = int(floor( 255.0 * numIntraPredictedBlocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionBiAndGpmPredictedBlocksArea = int(floor( 255.0 * numBiAndGpmPredictedBlocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionBdofBlocksArea  = int(floor( 255.0 * numBDOFPredictedBlocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionDeblockingInstances = int(floor( 255.0 * numDeblockingInstances / maxNumDeblockingInstances));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionSaoInstances = int(floor( 255.0 * numSaoFilteredBlocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_greenComplexityMetrics.portionAlfInstances = int(floor( 255.0 * numAlfFilteredBlocks / totalNum4BlocksInPeriod));
+  seiGreenMetadataInfo->m_numPictures = int(featureCounterDifference.iSlices + featureCounterDifference.bSlices +featureCounterDifference.pSlices);
+  seiGreenMetadataInfo->m_numSeconds = int(floor(double(seiGreenMetadataInfo->m_numPictures) / double(m_pcCfg->getFrameRate())));
 }
+#endif
 
 double EncGOP::xCalculateMSSSIM (const Pel* org, const int orgStride, const Pel* rec, const int recStride, const int width, const int height, const uint32_t bitDepth)
 {
@@ -6697,4 +6977,5 @@ void EncGOP::xCreateExplicitReferencePictureSetFromReference( Slice* slice, PicL
   }
 
 }
+
 //! \}
diff --git a/source/Lib/EncoderLib/EncGOP.h b/source/Lib/EncoderLib/EncGOP.h
index 5d3cff2b6ac0ba5514d138bbb9a5d1ed464262e7..1fe29a27738a07d99c1d5a660cd1d4acf9f1d2ba 100644
--- a/source/Lib/EncoderLib/EncGOP.h
+++ b/source/Lib/EncoderLib/EncGOP.h
@@ -220,6 +220,13 @@ private:
   std::chrono::duration<long long, std::ratio<1, 1000000000>> m_metricTime;
 #endif
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct m_featureCounter;
+  FeatureCounterStruct m_featureCounterReference;
+  SEIQualityMetrics m_SEIGreenQualityMetrics;
+  SEIComplexityMetrics m_SEIGreenComplexityMetrics;
+#endif
+
 public:
   EncGOP();
   virtual ~EncGOP();
@@ -253,6 +260,10 @@ public:
   void      setLastLTRefPoc(int iLastLTRefPoc) { m_lastLTRefPoc = iLastLTRefPoc; }
   int       getLastLTRefPoc() const { return m_lastLTRefPoc; }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct getFeatureCounter(){return m_featureCounter;}
+  void setFeatureCounter(FeatureCounterStruct b){m_featureCounter=b;}
+#endif
 #if GDR_ENABLED
   void      setLastGdrIntervalPoc(int p)  { m_lastGdrIntervalPoc = p; }
   int       getLastGdrIntervalPoc() const { return m_lastGdrIntervalPoc; }
@@ -358,6 +369,9 @@ protected:
   void xCreateExplicitReferencePictureSetFromReference( Slice* slice, PicList& rcListPic, const ReferencePictureList *rpl0, const ReferencePictureList *rpl1 );
   bool xCheckMaxTidILRefPics(int layerIdx, Picture* refPic, bool currentPicIsIRAP);
   void computeSignalling(Picture* pcPic, Slice* pcSlice) const;
+#ifdef GREEN_METADATA_SEI_ENABLED
+  void xCalculateGreenComplexityMetrics(FeatureCounterStruct featureCounter, FeatureCounterStruct featureCounterReference, SEIGreenMetadataInfo* seiGreenMetadataInfo);
+#endif
 };// END CLASS DEFINITION EncGOP
 
 //! \}
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index edcf9cdd344527dcfc29a7d487f3e13a2f9240a7..63fc838688e08017e0b1594781769808c804b8c9 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -676,6 +676,9 @@ bool EncLib::encodePrep(bool flush, PelStorage *pcPicYuvOrg, PelStorage *cPicYuv
 
 #if JVET_O0756_CALCULATE_HDRMETRICS
     m_metricTime = m_cGOPEncoder.getMetricTime();
+#endif
+#ifdef GREEN_METADATA_SEI_ENABLED
+    this->setFeatureCounter(m_cGOPEncoder.getFeatureCounter());
 #endif
     m_cGOPEncoder.setEncodedLTRef( true );
     if( m_RCEnableRateControl )
diff --git a/source/Lib/EncoderLib/EncLib.h b/source/Lib/EncoderLib/EncLib.h
index 415b449e7df0a9d317b28e4320622e1f75a9f87d..b43b4c338e4dd36fd6096025a7090baa8ef5db9a 100644
--- a/source/Lib/EncoderLib/EncLib.h
+++ b/source/Lib/EncoderLib/EncLib.h
@@ -129,7 +129,11 @@ private:
 
   int*                      m_layerDecPicBuffering;
   RPLList                   m_rplLists[2];
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct             m_featureCounter;
+  bool                      m_GMFAFramewise;
+  std::string   m_GMFAFile;
+#endif
 public:
   SPS*                      getSPS( int spsId ) { return m_spsMap.getPS( spsId ); };
   APS**                     getApss() { return m_apss; }
@@ -189,7 +193,13 @@ public:
   CtxCache*               getCtxCache           ()              { return  &m_CtxCache;             }
   RateCtrl*               getRateCtrl           ()              { return  &m_cRateCtrl;            }
 
-
+#ifdef GREEN_METADATA_SEI_ENABLED
+  FeatureCounterStruct getFeatureCounter(){return m_featureCounter;}
+  void setFeatureCounter(FeatureCounterStruct b){m_featureCounter=b;}
+  bool getGMFAFramewise() {return m_GMFAFramewise;}
+  void setGMFAFile(std::string b){m_GMFAFile = b;}
+#endif
+  
   void                    selectReferencePictureList(Slice* slice, int POCCurr, int GOPid, int ltPoc);
 
   void                   setParamSetChanged(int spsId, int ppsId);
diff --git a/source/Lib/EncoderLib/EncSlice.cpp b/source/Lib/EncoderLib/EncSlice.cpp
index 559e35fe9141824c6ff18d368cad554ccade8bdc..051edc93f4f1d0e68bf5757bd13adb391bbffa2c 100644
--- a/source/Lib/EncoderLib/EncSlice.cpp
+++ b/source/Lib/EncoderLib/EncSlice.cpp
@@ -1872,6 +1872,11 @@ void EncSlice::encodeCtus( Picture* pcPic, const bool bCompressEntireSlice, cons
     if (pCfg->getSwitchPOC() != pcPic->poc || ctuRsAddr >= pCfg->getDebugCTU())
     {
       m_pcCuEncoder->compressCtu(cs, ctuArea, ctuRsAddr, prevQP, currQP);
+#ifdef GREEN_METADATA_SEI_ENABLED
+      FeatureCounterStruct m_featureCounter = pcPic->getFeatureCounter();
+      countFeatures(m_featureCounter, cs,ctuArea);
+      pcPic->setFeatureCounter(m_featureCounter);
+#endif
     }
 #if K0149_BLOCK_STATISTICS
     getAndStoreBlockStatistics(cs, ctuArea);
diff --git a/source/Lib/EncoderLib/SEIEncoder.cpp b/source/Lib/EncoderLib/SEIEncoder.cpp
index 70e427c69204164c5d4afdce9056184c1325d7aa..7fa48a729d19e849ddcaeebb41eb410786185dee 100644
--- a/source/Lib/EncoderLib/SEIEncoder.cpp
+++ b/source/Lib/EncoderLib/SEIEncoder.cpp
@@ -232,6 +232,54 @@ void SEIEncoder::initSEIErp(SEIEquirectangularProjection* seiEquirectangularProj
   }
 }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+void SEIEncoder::initSEIGreenMetadataInfo(SEIGreenMetadataInfo* seiGreenMetadataInfo, FeatureCounterStruct featureCounter, SEIQualityMetrics metrics,SEIComplexityMetrics greenMetadata)
+{
+  assert (m_isInitialized);
+  assert (seiGreenMetadataInfo!=NULL);
+  
+  if (m_pcCfg->getSEIGreenMetadataType() == 1) //Metadata for quality recovery after low-power encoding
+  {
+    seiGreenMetadataInfo->m_greenMetadataType = m_pcCfg->getSEIGreenMetadataType();
+    seiGreenMetadataInfo->m_xsdSubpicNumberMinus1 = m_pcCfg->getSEIXSDNumberMetrics()-1;
+    seiGreenMetadataInfo->m_xsdSubPicIdc = 1; //Only 1 Picture is supported
+    // Maximum valid value for 16-bit integer: 65535
+    (m_pcCfg->getSEIXSDMetricTypePSNR()) ? seiGreenMetadataInfo->m_xsdMetricValuePSNR  = min(int(metrics.psnr*100),65535) :  seiGreenMetadataInfo->m_xsdMetricValuePSNR = 0;
+    (m_pcCfg->getSEIXSDMetricTypeSSIM()) ? seiGreenMetadataInfo->m_xsdMetricValueSSIM  = min(int(metrics.ssim*100),65535) : seiGreenMetadataInfo->m_xsdMetricValueSSIM  = 0;
+    (m_pcCfg->getSEIXSDMetricTypeWPSNR()) ? seiGreenMetadataInfo->m_xsdMetricValueWPSNR  = min(int(metrics.wpsnr*100),65535) : seiGreenMetadataInfo->m_xsdMetricValueWPSNR  = 0;
+    (m_pcCfg->getSEIXSDMetricTypeWSPSNR()) ? seiGreenMetadataInfo->m_xsdMetricValueWSPSNR  = min(int(metrics.wspsnr*100),65535) : seiGreenMetadataInfo->m_xsdMetricValueWSPSNR  = 0;
+    
+    seiGreenMetadataInfo->m_xsdMetricTypePSNR = m_pcCfg->getSEIXSDMetricTypePSNR();
+    seiGreenMetadataInfo->m_xsdMetricTypeSSIM = m_pcCfg->getSEIXSDMetricTypeSSIM();
+    seiGreenMetadataInfo->m_xsdMetricTypeWPSNR = m_pcCfg->getSEIXSDMetricTypeWPSNR();
+    seiGreenMetadataInfo->m_xsdMetricTypeWSPSNR = m_pcCfg->getSEIXSDMetricTypeWSPSNR();
+  }
+  else if(m_pcCfg->getSEIGreenMetadataType() == 0) // Metadata for decoder-complexity metrics
+  {
+    seiGreenMetadataInfo->m_greenMetadataType                   = m_pcCfg->getSEIGreenMetadataType();
+    seiGreenMetadataInfo->m_greenMetadataGranularityType        = m_pcCfg->getSEIGreenMetadataGranularityType();
+    seiGreenMetadataInfo->m_greenMetadataExtendedRepresentation = m_pcCfg->getSEIGreenMetadataExtendedRepresentation();
+    switch (m_pcCfg->getSEIGreenMetadataPeriodType())   // Period type
+    {
+    case 0:   // 0x00 complexity metrics are applicable to a single picture
+      seiGreenMetadataInfo->m_numPictures = m_pcCfg->getSEIGreenMetadataPeriodNumPictures();
+      break;
+    case 1:   // 0x01 complexity metrics are applicable to all pictures in decoding order, up to (but not including) the picture containing the next I slice
+      //
+      break;
+    case 2:   // 0x02 complexity metrics are applicable over a specified time interval in seconds
+      seiGreenMetadataInfo->m_numPictures = m_pcCfg->getSEIGreenMetadataPeriodNumPictures();
+      break;
+    case 3:   // 0x03 complexity metrics are applicable over a specified number of pictures counted in decoding order
+      seiGreenMetadataInfo->m_numSeconds = m_pcCfg->getSEIGreenMetadataPeriodNumSeconds();
+      break;
+    default:   // 0x05-0xFF reserved
+      break;   //
+    }
+  }
+}
+#endif
+
 void SEIEncoder::initSEISphereRotation(SEISphereRotation* seiSphereRotation)
 {
   CHECK(!(m_isInitialized), "seiSphereRotation already initialized");
diff --git a/source/Lib/EncoderLib/SEIEncoder.h b/source/Lib/EncoderLib/SEIEncoder.h
index 135badf23388f2992976b09bc77d6aba145b3085..286a14004bdb5b96dde8e9c1d8ff5a070e8c48ee 100644
--- a/source/Lib/EncoderLib/SEIEncoder.h
+++ b/source/Lib/EncoderLib/SEIEncoder.h
@@ -93,6 +93,9 @@ public:
   void initSEINeuralNetworkPostFilterCharacteristics(SEINeuralNetworkPostFilterCharacteristics *sei, int filterIdx);
   void initSEINeuralNetworkPostFilterActivation(SEINeuralNetworkPostFilterActivation *sei);
   void initSEIProcessingOrderInfo(SEIProcessingOrderInfo *sei);
+#ifdef GREEN_METADATA_SEI_ENABLED
+  void initSEIGreenMetadataInfo(SEIGreenMetadataInfo *sei, FeatureCounterStruct featureCounter, SEIQualityMetrics metrics, SEIComplexityMetrics greenMetadata);
+#endif
 private:
   EncCfg* m_pcCfg;
   EncLib* m_pcEncLib;
diff --git a/source/Lib/EncoderLib/SEIwrite.cpp b/source/Lib/EncoderLib/SEIwrite.cpp
index 0c899d49809cf7e38f098c1837907246381151ea..9a9376179508163f22c32aa5e4baa63493aa84c6 100644
--- a/source/Lib/EncoderLib/SEIwrite.cpp
+++ b/source/Lib/EncoderLib/SEIwrite.cpp
@@ -83,6 +83,11 @@ void SEIWriter::xWriteSEIpayloadData(OutputBitstream &bs, const SEI& sei, HRD &h
   case SEI::FRAME_PACKING:
     xWriteSEIFramePacking(*static_cast<const SEIFramePacking*>(&sei));
     break;
+#ifdef  GREEN_METADATA_SEI_ENABLED
+  case SEI::GREEN_METADATA:
+    xWriteSEIGreenMetadataInfo(*static_cast<const SEIGreenMetadataInfo*>(&sei));
+    break;
+#endif
   case SEI::DISPLAY_ORIENTATION:
     xWriteSEIDisplayOrientation(*static_cast<const SEIDisplayOrientation*>(&sei));
     break;
@@ -1420,6 +1425,118 @@ void SEIWriter::xWriteSEIConstrainedRaslIndication(const SEIConstrainedRaslIndic
   // intentionally empty
 }
 
+#ifdef GREEN_METADATA_SEI_ENABLED
+void SEIWriter::xWriteSEIGreenMetadataInfo(const SEIGreenMetadataInfo& sei)
+{
+  WRITE_CODE(sei.m_greenMetadataType, 8, "green_metadata_type");
+  switch (sei.m_greenMetadataType)
+  {
+  case 0:
+    WRITE_CODE(sei.m_periodType,4, "period_type");
+    WRITE_CODE(sei.m_greenMetadataGranularityType,3, "granularity_type");
+    WRITE_CODE(sei.m_greenMetadataExtendedRepresentation,1, "extended_representation_flag");
+    
+    if (sei.m_periodType == 2)
+    {
+      WRITE_CODE(sei.m_numSeconds, 16, "num_seconds");
+    }
+    else if (sei.m_periodType == 3)
+    {
+      WRITE_CODE(sei.m_numPictures, 16, "num_pictures");
+    }
+    
+    if (sei.m_greenMetadataGranularityType == 0)
+    {
+      WRITE_CODE(sei.m_greenComplexityMetrics.portionNonZeroBlocksArea, 8, "portion_non_zero_blocks_area");
+      WRITE_CODE(sei.m_greenComplexityMetrics.portionNonZeroTransformCoefficientsArea, 8, "portion_non_zero_transform_coefficients_area");
+      WRITE_CODE(sei.m_greenComplexityMetrics.portionIntraPredictedBlocksArea, 8, "portion_intra_predicted_blocks_area");
+      WRITE_CODE(sei.m_greenComplexityMetrics.portionDeblockingInstances, 8, "portion_deblocking_instances");
+      WRITE_CODE(sei.m_greenComplexityMetrics.portionAlfInstances, 8, "portion_alf_instances");
+      
+      if(sei.m_greenMetadataExtendedRepresentation == 1)
+      {
+        if(sei.m_greenComplexityMetrics.portionNonZeroBlocksArea != 0)
+        {
+          WRITE_CODE(sei.m_greenComplexityMetrics.portionNonZero_4_8_16BlocksArea, 8, "portion_non_zero_4_8_16_blocks_area");
+          WRITE_CODE(sei.m_greenComplexityMetrics.portionNonZero_32_64_128BlocksArea, 8, "portion_non_zero_32_64_128_blocks_area");
+          WRITE_CODE(sei.m_greenComplexityMetrics.portionNonZero_256_512_1024BlocksArea, 8, "portion_non_zero_256_512_1024_blocks_area");
+          WRITE_CODE(sei.m_greenComplexityMetrics.portionNonZero_2048_4096BlocksArea, 8, "portion_non_zero_2048_4096_blocks_area");
+        }
+        
+        
+        if(sei.m_greenComplexityMetrics.portionIntraPredictedBlocksArea < 255)
+        {
+          WRITE_CODE(sei.m_greenComplexityMetrics.portionBiAndGpmPredictedBlocksArea, 8,"portion_bi_and_gpm_predicted_blocks_area");
+          WRITE_CODE(sei.m_greenComplexityMetrics.portionBdofBlocksArea, 8,"portion_bdof_blocks_area");
+        }
+        
+        WRITE_CODE(sei.m_greenComplexityMetrics.portionSaoInstances, 8, "portion_sao_instances");
+      }
+    }
+    
+    break;
+  case 1:
+    int xsdSubpicNumberMinus1 = 0;
+    WRITE_CODE(xsdSubpicNumberMinus1, 16, "xsd_subpic_number_minus1");
+    for (int i = 0; i <= xsdSubpicNumberMinus1; i++)
+    {
+      int xsdMetricNumberMinus1 = -1;
+      WRITE_CODE(sei.m_xsdSubPicIdc, 16, "xsd_subpic_idc[i]");
+      std::vector <int> xsdMetricArray;
+      if (sei.m_xsdMetricTypePSNR)
+      {
+        xsdMetricNumberMinus1++;
+        xsdMetricArray.push_back(0);
+      }
+      if (sei.m_xsdMetricTypeSSIM)
+      {
+        xsdMetricNumberMinus1++;
+        xsdMetricArray.push_back(1);
+      }
+  
+      if (sei.m_xsdMetricTypeWPSNR)
+      {
+        xsdMetricNumberMinus1++;
+        xsdMetricArray.push_back(2);
+      }
+  
+      if (sei.m_xsdMetricTypeWSPSNR)
+      {
+        xsdMetricNumberMinus1++;
+        xsdMetricArray.push_back(3);
+      }
+      
+      WRITE_CODE(xsdMetricNumberMinus1, 8, "xsd_metric_number_minus1[i]");
+      for (int j = 0; j <= xsdMetricNumberMinus1; j++)
+      {
+        if (xsdMetricArray[j] == 0)
+        {
+          WRITE_CODE(0, 8, "xsd_metric_type");
+          WRITE_CODE(sei.m_xsdMetricValuePSNR, 16, "xsd_metric_type[i][j]");
+        }
+        else if (xsdMetricArray[j] == 1)
+        {
+          WRITE_CODE(1, 8, "xsd_metric_type");
+          WRITE_CODE(sei.m_xsdMetricValueSSIM, 16, "xsd_metric_type[i][j]");
+        }
+        else if (xsdMetricArray[j] == 2)
+        {
+          WRITE_CODE(3, 8, "xsd_metric_type");
+          WRITE_CODE(sei.m_xsdMetricValueWPSNR, 16, "xsd_metric_type[i][j]");
+        }
+        else if (xsdMetricArray[j] == 3)
+        {
+          WRITE_CODE(4, 8, "xsd_metric_type");
+          WRITE_CODE(sei.m_xsdMetricValueWSPSNR, 16, "xsd_metric_type[i][j]");
+        }
+      }
+    }
+    break;
+  }
+}
+#endif
+
+
 void SEIWriter::xWriteSEINeuralNetworkPostFilterCharacteristics(const SEINeuralNetworkPostFilterCharacteristics &sei)
 {
   WRITE_UVLC(sei.m_id, "nnpfc_id");
diff --git a/source/Lib/EncoderLib/SEIwrite.h b/source/Lib/EncoderLib/SEIwrite.h
index 8d3ec33cdf30a0f2af137da2b3043442869104bf..88ec2dfa298815b84f09cbf41d8b032f5170e9c6 100644
--- a/source/Lib/EncoderLib/SEIwrite.h
+++ b/source/Lib/EncoderLib/SEIwrite.h
@@ -97,6 +97,9 @@ protected:
   void xWriteNNPFCComplexityElement(const SEINeuralNetworkPostFilterCharacteristics& sei);
   void xWriteSEINeuralNetworkPostFilterActivation(const SEINeuralNetworkPostFilterActivation &sei);
   void xWriteSEIProcessingOrder(const SEIProcessingOrderInfo &sei);
+#ifdef GREEN_METADATA_SEI_ENABLED
+  void xWriteSEIGreenMetadataInfo                 (const SEIGreenMetadataInfo &sei);
+#endif
 protected:
   HRD m_nestingHrd;
 };