diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 599c43e08e50bfd25e08e2d16192a26939b26365..ab22f12e29a6ea1870bb89851025095380eb075e 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -246,6 +246,18 @@ void EncApp::xInitLibCfg()
   m_cEncLib.setUseGBi                                            ( m_GBi );
   m_cEncLib.setUseGBiFast                                        ( m_GBiFast );
 #endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  m_cEncLib.setUseLadf                                           ( m_LadfEnabed );
+  if ( m_LadfEnabed )
+  {
+    m_cEncLib.setLadfNumIntervals                                ( m_LadfNumIntervals);
+    for ( int k = 0; k < m_LadfNumIntervals; k++ )
+    {
+      m_cEncLib.setLadfQpOffset( m_LadfQpOffset[k], k );
+      m_cEncLib.setLadfIntervalLowerBound(m_LadfIntervalLowerBound[k], k);
+    }
+  }
+#endif  
   // ADD_NEW_TOOL : (encoder app) add setting of tool enabling flags and associated parameters here
 
   m_cEncLib.setMaxCUWidth                                        ( m_QTBT ? m_uiCTUSize : m_uiMaxCUWidth );
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index f893b6432374bf077f9aa2d9b113ff6e7bace537..e6723233eeb7f91df4cf1170470b85452e25187e 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -699,6 +699,12 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   SMultiValueInput<bool> cfg_timeCodeSeiHoursFlag            (0,  1, 0, MAX_TIMECODE_SEI_SETS);
   SMultiValueInput<int>  cfg_timeCodeSeiTimeOffsetLength     (0, 31, 0, MAX_TIMECODE_SEI_SETS);
   SMultiValueInput<int>  cfg_timeCodeSeiTimeOffsetValue      (std::numeric_limits<int>::min(), std::numeric_limits<int>::max(), 0, MAX_TIMECODE_SEI_SETS);
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  const int defaultLadfQpOffset[3] = { 1, 0, 1 };
+  const int defaultLadfIntervalLowerBound[2] = { 350, 833 };
+  SMultiValueInput<int>  cfg_LadfQpOffset                    ( -MAX_QP, MAX_QP, 2, MAX_LADF_INTERVALS, defaultLadfQpOffset, 3 );
+  SMultiValueInput<int>  cfg_LadfIntervalLowerBound          ( 0, std::numeric_limits<int>::max(), 1, MAX_LADF_INTERVALS - 1, defaultLadfIntervalLowerBound, 2 );
+#endif
   int warnUnknowParameter = 0;
 
 #if ENABLE_TRACING
@@ -850,6 +856,12 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 #if JVET_L0646_GBI
   ("GBi",                                             m_GBi,                                            false, "Enable Generalized Bi-prediction(GBi)")
   ("GBiFast",                                         m_GBiFast,                                        false, "Fast methods for Generalized Bi-prediction(GBi)\n")
+#endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  ("LADF",                                            m_LadfEnabed,                                     false, "Luma adaptive deblocking filter QP Offset(L0414)")
+  ("LadfNumIntervals",                                m_LadfNumIntervals,                                   3, "LADF number of intervals (2-5, inclusive)")
+  ("LadfQpOffset",                                    cfg_LadfQpOffset,                      cfg_LadfQpOffset, "LADF QP offset")
+  ("LadfIntervalLowerBound",                          cfg_LadfIntervalLowerBound,  cfg_LadfIntervalLowerBound, "LADF lower bound for 2nd lowest interval")
 #endif
   // ADD_NEW_TOOL : (encoder app) add parsing parameters here
 
@@ -1686,6 +1698,19 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
   }
 #endif
 
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  if ( m_LadfEnabed )
+  {
+    CHECK( m_LadfNumIntervals != cfg_LadfQpOffset.values.size(), "size of LadfQpOffset must be equal to LadfNumIntervals");
+    CHECK( m_LadfNumIntervals - 1 != cfg_LadfIntervalLowerBound.values.size(), "size of LadfIntervalLowerBound must be equal to LadfNumIntervals - 1");
+    m_LadfQpOffset = cfg_LadfQpOffset.values;
+    for (int k = 1; k < m_LadfNumIntervals; k++)
+    {
+      m_LadfIntervalLowerBound[k] = cfg_LadfIntervalLowerBound.values[k - 1];
+    }
+  }
+#endif
+
   // reading external dQP description from file
   if ( !m_dQPFileName.empty() )
   {
@@ -3130,6 +3155,9 @@ void EncAppCfg::xPrintParameter()
 #if JVET_L0646_GBI
     msg( VERBOSE, "GBi:%d ", m_GBi );
     msg( VERBOSE, "GBiFast:%d ", m_GBiFast );
+#endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+    msg( VERBOSE, "LADF:%d ", m_LadfEnabed );
 #endif
   }
   // ADD_NEW_TOOL (add some output indicating the usage of tools)
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 1245c737a65ca9e1d2bc4de4d7b7ba24a651d68f..cf5095f67d48a66364d4b438d0a1447d78fca63d 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -227,6 +227,12 @@ protected:
 #if JVET_L0646_GBI
   bool      m_GBi;
   bool      m_GBiFast;
+#endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  bool      m_LadfEnabed;
+  int       m_LadfNumIntervals;
+  std::vector<int> m_LadfQpOffset;
+  int       m_LadfIntervalLowerBound[MAX_LADF_INTERVALS];
 #endif
   // ADD_NEW_TOOL : (encoder app) add tool enabling flags and associated parameters here
 
diff --git a/source/Lib/CommonLib/CommonDef.h b/source/Lib/CommonLib/CommonDef.h
index 6e853ecfc327faea9aaa70b5afd30c217019b08e..c007a9bba2caa9f1a93991f3cb72e1cd5ff8e573 100644
--- a/source/Lib/CommonLib/CommonDef.h
+++ b/source/Lib/CommonLib/CommonDef.h
@@ -364,6 +364,10 @@ static const unsigned C806_ALF_TEMPPRED_NUM =                      6;
 
 static const int NTAPS_LUMA               =                         8; ///< Number of taps for luma
 static const int NTAPS_CHROMA             =                         4; ///< Number of taps for chroma
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+static const int MAX_LADF_INTERVALS       =                         5; /// max number of luma adaptive deblocking filter qp offset intervals
+#endif
+
 // ====================================================================================================================
 // Macro functions
 // ====================================================================================================================
diff --git a/source/Lib/CommonLib/LoopFilter.cpp b/source/Lib/CommonLib/LoopFilter.cpp
index f4b7522fcca5fa803b7c52569a6fd313297f1966..ceec19637f408d69f6a8b0b5cb1551760e6dac11 100644
--- a/source/Lib/CommonLib/LoopFilter.cpp
+++ b/source/Lib/CommonLib/LoopFilter.cpp
@@ -560,6 +560,36 @@ unsigned LoopFilter::xGetBoundaryStrengthSingle ( const CodingUnit& cu, const De
   return ( ( abs( mvQ0.getHor() - mvP0.getHor() ) >= nThreshold ) || ( abs( mvQ0.getVer() - mvP0.getVer() ) >= nThreshold ) ) ? 1 : 0;
 }
 
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+void LoopFilter::deriveLADFShift( const Pel* piSrc, const int iStride, int& iShift, const DeblockEdgeDir edgeDir, const SPS sps )
+{
+  uint32_t uiLevel = 0;
+  iShift = sps.getSpsNext().getLadfQpOffset(0);
+
+  if (edgeDir == EDGE_VER)
+  {
+    uiLevel = (piSrc[0] + piSrc[3*iStride] + piSrc[-1] + piSrc[3*iStride - 1]) >> 2;
+  }
+  else // (edgeDir == EDGE_HOR)
+  {
+    uiLevel = (piSrc[0] + piSrc[3] + piSrc[-iStride] + piSrc[-iStride + 3]) >> 2;
+  }
+
+  for ( int k = 1; k < sps.getSpsNext().getLadfNumIntervals(); k++ )
+  {
+    const int th = sps.getSpsNext().getLadfIntervalLowerBound( k );
+    if ( uiLevel > th )
+    {
+      iShift = sps.getSpsNext().getLadfQpOffset( k );
+    }
+    else
+    {
+      break;
+    }
+  }
+}
+#endif
+
 void LoopFilter::xEdgeFilterLuma(const CodingUnit& cu, const DeblockEdgeDir edgeDir, const int iEdge)
 {
   const CompArea&  lumaArea = cu.block(COMPONENT_Y);
@@ -645,6 +675,14 @@ void LoopFilter::xEdgeFilterLuma(const CodingUnit& cu, const DeblockEdgeDir edge
 
       iQP = (cuP.qp + cuQ.qp + 1) >> 1;
 
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+      if ( sps.getSpsNext().getLadfEnabled() )
+      {
+        int iShift = 0;
+        deriveLADFShift( piTmpSrc + iSrcStep * (iIdx*pelsInPart), iStride, iShift, edgeDir, sps );
+        iQP += iShift;
+      }
+#endif
       const int iIndexTC  = Clip3(0, MAX_QP + DEFAULT_INTRA_TC_OFFSET, int(iQP + DEFAULT_INTRA_TC_OFFSET*(uiBs - 1) + (tcOffsetDiv2 << 1)));
       const int iIndexB   = Clip3(0, MAX_QP, iQP + (betaOffsetDiv2 << 1));
 
diff --git a/source/Lib/CommonLib/LoopFilter.h b/source/Lib/CommonLib/LoopFilter.h
index efe3a2c154163cfaf08c1d0e72fc8320ea61b24f..2b74f476f92e86527e5f1c9a067c21691fd272f3 100644
--- a/source/Lib/CommonLib/LoopFilter.h
+++ b/source/Lib/CommonLib/LoopFilter.h
@@ -62,7 +62,7 @@ private:
 private:
   /// CU-level deblocking function
   void xDeblockCU                 (       CodingUnit& cu, const DeblockEdgeDir edgeDir );
-
+  
   // set / get functions
   void xSetLoopfilterParam        ( const CodingUnit& cu );
 
@@ -79,6 +79,9 @@ private:
   void xEdgeFilterLuma            ( const CodingUnit& cu, const DeblockEdgeDir edgeDir, const int iEdge );
   void xEdgeFilterChroma          ( const CodingUnit& cu, const DeblockEdgeDir edgeDir, const int iEdge );
 
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  void deriveLADFShift( const Pel* piSrc, const int iStride, int& iShift, const DeblockEdgeDir edgeDir, const SPS sps );
+#endif
   inline void xPelFilterLuma      ( Pel* piSrc, const int iOffset, const int tc, const bool sw, const bool bPartPNoFilter, const bool bPartQNoFilter, const int iThrCut, const bool bFilterSecondP, const bool bFilterSecondQ, const ClpRng& clpRng ) const;
   inline void xPelFilterChroma    ( Pel* piSrc, const int iOffset, const int tc,                const bool bPartPNoFilter, const bool bPartQNoFilter,                                                                          const ClpRng& clpRng ) const;
 
diff --git a/source/Lib/CommonLib/Slice.cpp b/source/Lib/CommonLib/Slice.cpp
index a11b0c1b5f0d28a03be401d9f12a87980c1fd241..3200b16cd2abecc90eae4da446e0de92d316240d 100644
--- a/source/Lib/CommonLib/Slice.cpp
+++ b/source/Lib/CommonLib/Slice.cpp
@@ -1663,6 +1663,12 @@ SPSNext::SPSNext( SPS& sps )
 #if ENABLE_WPP_PARALLELISM
   , m_NextDQP                   ( false )
 #endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  , m_LadfEnabled               ( false )
+  , m_LadfNumIntervals          ( 0 )
+  , m_LadfQpOffset              { 0 }
+  , m_LadfIntervalLowerBound    { 0 }
+#endif
 
   // default values for additional parameters
   , m_CTUSize                   ( 0 )
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index f42961dc0bbcc8a9775c09143ea4a5d77d3540d0..33709b63a31cbd0feed2f8e5acf75b74115cb74f 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -817,6 +817,12 @@ private:
 #if ENABLE_WPP_PARALLELISM
   bool              m_NextDQP;
 #endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  bool              m_LadfEnabled;
+  int               m_LadfNumIntervals;
+  int               m_LadfQpOffset[MAX_LADF_INTERVALS];
+  int               m_LadfIntervalLowerBound[MAX_LADF_INTERVALS];
+#endif
 
 public:
   const static int  NumReservedFlags = 32 - 27; /* current number of tool enabling flags */
@@ -888,6 +894,16 @@ public:
 #if JVET_L0646_GBI
   void      setUseGBi             ( bool b )                                        { m_GBi = b; }
   bool      getUseGBi             ()                                      const     { return m_GBi; }
+#endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  void      setLadfEnabled        ( bool b )                                        { m_LadfEnabled = b; }
+  bool      getLadfEnabled        ()                                      const     { return m_LadfEnabled; }
+  void      setLadfNumIntervals   ( int i )                                         { m_LadfNumIntervals = i; }
+  int       getLadfNumIntervals   ()                                      const     { return m_LadfNumIntervals; }
+  void      setLadfQpOffset       ( int value, int idx )                            { m_LadfQpOffset[ idx ] = value; }
+  int       getLadfQpOffset       ( int idx )                             const     { return m_LadfQpOffset[ idx ]; }
+  void      setLadfIntervalLowerBound( int value, int idx )                         { m_LadfIntervalLowerBound[ idx ] = value; }
+  int       getLadfIntervalLowerBound( int idx )                          const     { return m_LadfIntervalLowerBound[ idx ]; }
 #endif
   //=====  additional parameters  =====
   // qtbt
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 0a2889bb2fb743b1aeb152ae1887d2bf3e815161..b99b27384f6ac4be4cd990d96ff750824fe201a1 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -285,6 +285,7 @@
 
 #define QP_SWITCHING_FOR_PARALLEL                         1 ///< Replace floating point QP with a source-file frame number. After switching POC, increase base QP instead of frame level QP.
 
+#define LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET         1 /// JVET-L0414 (CE11.2.2) with explicit signalling of num interval, threshold and qpOffset
 // ====================================================================================================================
 // Derived macros
 // ====================================================================================================================
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index 33ee5dd2b2e30fe6013792ec7140a81bb0658448..5afb188dc802e0b87573f4b2d8c3f13c421b1b89 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -910,6 +910,21 @@ void HLSyntaxReader::parseSPSNext( SPSNext& spsNext, const bool usePCM )
   }
 
 
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  READ_FLAG( symbol, "sps_ladf_enabled_flag" );                     spsNext.setLadfEnabled( symbol != 0 );
+  if ( spsNext.getLadfEnabled() )
+  {
+    int signedSymbol = 0;
+    READ_CODE( 2, symbol, "sps_num_ladf_intervals_minus2");         spsNext.setLadfNumIntervals( symbol + 2 );
+    READ_SVLC(signedSymbol, "sps_ladf_lowest_interval_qp_offset" );      spsNext.setLadfQpOffset( signedSymbol, 0 );
+    for ( int k = 1; k < spsNext.getLadfNumIntervals(); k++ )
+    {
+      READ_SVLC(signedSymbol, "sps_ladf_qp_offset" );                    spsNext.setLadfQpOffset( signedSymbol, k );
+      READ_UVLC( symbol, "sps_ladf_delta_threshold_minus1");
+      spsNext.setLadfIntervalLowerBound(symbol + spsNext.getLadfIntervalLowerBound(k - 1) + 1, k);
+    }
+  }
+#endif
   // ADD_NEW_TOOL : (sps extension parser) read tool enabling flags and associated parameters here
 }
 
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 9be410e7eed79a790804b43952021f1716958298..6ccf7acdff8a3f0792770fd94edd9196d187a1ad 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -210,6 +210,12 @@ protected:
 #if JVET_L0646_GBI
   bool      m_GBi;
   bool      m_GBiFast;
+#endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  bool      m_LadfEnabled;
+  int       m_LadfNumIntervals;
+  int       m_LadfQpOffset[MAX_LADF_INTERVALS];
+  int       m_LadfIntervalLowerBound[MAX_LADF_INTERVALS];
 #endif
   // ADD_NEW_TOOL : (encoder lib) add tool enabling flags and associated parameters here
 
@@ -657,6 +663,18 @@ public:
   bool      getUseGBi                       ()         const { return m_GBi; }
   void      setUseGBiFast                   ( uint32_t b )   { m_GBiFast = b; }
   bool      getUseGBiFast                   ()         const { return m_GBiFast; }
+#endif
+
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  void      setUseLadf                      ( bool b )       { m_LadfEnabled = b; }
+  bool      getUseLadf                      ()         const { return m_LadfEnabled; }
+  void      setLadfNumIntervals             ( int i )        { m_LadfNumIntervals = i; }
+  int       getLadfNumIntervals             ()         const { return m_LadfNumIntervals; }
+  void      setLadfQpOffset                 ( int value, int idx ){ m_LadfQpOffset[ idx ] = value; }
+  int       getLadfQpOffset                 ( int idx ) const { return m_LadfQpOffset[ idx ]; }
+  void      setLadfIntervalLowerBound       ( int value, int idx ){ m_LadfIntervalLowerBound[ idx ] = value; }
+  int       getLadfIntervalLowerBound       ( int idx ) const { return m_LadfIntervalLowerBound[ idx ]; }
+
 #endif
   // ADD_NEW_TOOL : (encoder lib) add access functions here
 
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index 4b81b3c8495c195b0dd0342204dc67441512234c..3e4b088bdf4739f18998a6aede40021e8839bddb 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -858,6 +858,20 @@ void EncLib::xInitSPS(SPS &sps)
 #if JVET_L0646_GBI
   sps.getSpsNext().setUseGBi                ( m_GBi );
 #endif
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  sps.getSpsNext().setLadfEnabled           ( m_LadfEnabled );
+  if ( m_LadfEnabled )
+  {
+    sps.getSpsNext().setLadfNumIntervals    ( m_LadfNumIntervals );
+    for ( int k = 0; k < m_LadfNumIntervals; k++ )
+    {
+      sps.getSpsNext().setLadfQpOffset( m_LadfQpOffset[k], k );
+      sps.getSpsNext().setLadfIntervalLowerBound( m_LadfIntervalLowerBound[k], k );
+    }
+    CHECK( m_LadfIntervalLowerBound[0] != 0, "abnormal value set to LadfIntervalLowerBound[0]" );
+  }
+#endif
+
   // ADD_NEW_TOOL : (encoder lib) set tool enabling flags and associated parameters here
 
   int minCUSize = ( /*sps.getSpsNext().getUseQTBT() ? 1 << MIN_CU_LOG2 :*/ sps.getMaxCUWidth() >> sps.getLog2DiffMaxMinCodingBlockSize() );
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index e53e6f2f40f8b6d67197230abd9f92d291f641e6..ef3b0613175878822ff66b6cd0975177703dd661 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -619,6 +619,19 @@ void HLSWriter::codeSPSNext( const SPSNext& spsNext, const bool usePCM )
   {
     WRITE_UVLC( spsNext.getMTTMode() - 1,                                                       "mtt_mode_minus1" );
   }
+#if LUMA_ADAPTIVE_DEBLOCKING_FILTER_QP_OFFSET
+  WRITE_FLAG( spsNext.getLadfEnabled() ? 1 : 0,                                                 "sps_ladf_enabled_flag" );
+  if ( spsNext.getLadfEnabled() )
+  {
+    WRITE_CODE( spsNext.getLadfNumIntervals() - 2, 2,                                           "sps_num_ladf_intervals_minus2" );
+    WRITE_SVLC( spsNext.getLadfQpOffset( 0 ),                                                   "sps_ladf_lowest_interval_qp_offset");
+    for ( int k = 1; k< spsNext.getLadfNumIntervals(); k++ )
+    {
+      WRITE_SVLC( spsNext.getLadfQpOffset( k ),                                                 "sps_ladf_qp_offset" );
+      WRITE_UVLC( spsNext.getLadfIntervalLowerBound( k ) - spsNext.getLadfIntervalLowerBound( k - 1 ) - 1, "sps_ladf_delta_threshold_minus1" );
+    }
+  }
+#endif
   // ADD_NEW_TOOL : (sps extension writer) write tool enabling flags and associated parameters here
 }