diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 771e2b597e..69492eee10 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -407,7 +407,7 @@ OBJS-$(CONFIG_H263_V4L2M2M_ENCODER)    += v4l2_m2m_enc.o
 OBJS-$(CONFIG_H264_DECODER)            += h264dec.o h264_cabac.o h264_cavlc.o \
                                           h264_direct.o h264_loopfilter.o  \
                                           h264_mb.o h264_picture.o \
-                                          h264_refs.o \
+                                          h264_refs.o lcevcdec.o \
                                           h264_slice.o h264data.o h274.o
 OBJS-$(CONFIG_H264_AMF_ENCODER)        += amfenc_h264.o
 OBJS-$(CONFIG_H264_CUVID_DECODER)      += cuviddec.o
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 2da63c87ea..ff20d22545 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -418,6 +418,11 @@ typedef struct RcOverride{
  * Do not apply film grain, export it instead.
  */
 #define AV_CODEC_EXPORT_DATA_FILM_GRAIN (1 << 3)
+/**
+ * Decoding only.
+ * Do not apply picture enhancement layers, export them instead.
+ */
+#define AV_CODEC_EXPORT_DATA_ENHANCEMENTS (1 << 3)
 
 /**
  * The decoder will keep a reference to the frame and may reuse it later.
diff --git a/libavcodec/h264_picture.c b/libavcodec/h264_picture.c
index 3234141dbd..1cdb926d15 100644
--- a/libavcodec/h264_picture.c
+++ b/libavcodec/h264_picture.c
@@ -103,6 +103,7 @@ static void h264_copy_picture_params(H264Picture *dst, const H264Picture *src)
     dst->mb_height     = src->mb_height;
     dst->mb_stride     = src->mb_stride;
     dst->needs_fg      = src->needs_fg;
+    dst->needs_lcevc   = src->needs_lcevc;
 }
 
 int ff_h264_ref_picture(H264Picture *dst, const H264Picture *src)
diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c
index a66b75ca80..263172e600 100644
--- a/libavcodec/h264_slice.c
+++ b/libavcodec/h264_slice.c
@@ -191,6 +191,13 @@ static int alloc_picture(H264Context *h, H264Picture *pic)
 
     av_assert0(!pic->f->data[0]);
 
+#if CONFIG_LIBLCEVC_DEC
+    if (pic->needs_lcevc) {
+        pic->f->width  = FFMAX(h->avctx->width,  h->avctx->coded_width) * 2 / FFMAX(h->avctx->sample_aspect_ratio.den, 1);
+        pic->f->height = FFMAX(h->avctx->height, h->avctx->coded_height) * 2 / FFMAX(h->avctx->sample_aspect_ratio.num, 1);
+    }
+#endif
+
     pic->tf.f = pic->f;
     ret = ff_thread_get_ext_buffer(h->avctx, &pic->tf,
                                    pic->reference ? AV_GET_BUFFER_FLAG_REF : 0);
@@ -206,6 +213,13 @@ static int alloc_picture(H264Context *h, H264Picture *pic)
             goto fail;
     }
 
+#if CONFIG_LIBLCEVC_DEC
+    if (pic->needs_lcevc) {
+        pic->f->width  = FFMAX(h->avctx->width,  h->avctx->coded_width);
+        pic->f->height = FFMAX(h->avctx->height, h->avctx->coded_height);
+    }
+#endif
+
     ret = ff_hwaccel_frame_priv_alloc(h->avctx, &pic->hwaccel_picture_private);
     if (ret < 0)
         goto fail;
@@ -459,6 +473,8 @@ int ff_h264_update_thread_context(AVCodecContext *dst,
     h->recovery_frame        = h1->recovery_frame;
     h->non_gray              = h1->non_gray;
 
+    av_buffer_replace(&h->lcevc, h1->lcevc);
+
     return err;
 }
 
@@ -519,6 +535,9 @@ static int h264_frame_start(H264Context *h)
     pic->needs_fg = h->sei.common.film_grain_characteristics.present && !h->avctx->hwaccel &&
         !(h->avctx->export_side_data & AV_CODEC_EXPORT_DATA_FILM_GRAIN);
 
+    pic->needs_lcevc = h->sei.common.lcevc.info && !h->avctx->hwaccel &&
+        !(h->avctx->export_side_data & AV_CODEC_EXPORT_DATA_ENHANCEMENTS);
+
     if ((ret = alloc_picture(h, pic)) < 0)
         return ret;
 
diff --git a/libavcodec/h264dec.c b/libavcodec/h264dec.c
index e186bc18dc..a80d8e16d5 100644
--- a/libavcodec/h264dec.c
+++ b/libavcodec/h264dec.c
@@ -57,6 +57,10 @@
 #include "thread.h"
 #include "threadframe.h"
 
+#if CONFIG_LIBLCEVC_DEC
+#include "lcevcdec.h"
+#endif
+
 const uint16_t ff_h264_mb_sizes[4] = { 256, 384, 512, 768 };
 
 int avpriv_h264_has_num_reorder_frames(AVCodecContext *avctx)
@@ -378,6 +382,8 @@ static av_cold int h264_decode_end(AVCodecContext *avctx)
     h264_free_pic(h, &h->cur_pic);
     h264_free_pic(h, &h->last_pic_for_ec);
 
+    av_buffer_unref(&h->lcevc);
+
     return 0;
 }
 
@@ -405,6 +411,9 @@ FF_ENABLE_DEPRECATION_WARNINGS
 #endif
 
     if (!avctx->internal->is_copy) {
+#if CONFIG_LIBLCEVC_DEC
+        FFLCEVCContext *lcevc;
+#endif
         if (avctx->extradata_size > 0 && avctx->extradata) {
             ret = ff_h264_decode_extradata(avctx->extradata, avctx->extradata_size,
                                            &h->ps, &h->is_avc, &h->nal_length_size,
@@ -419,6 +428,21 @@ FF_ENABLE_DEPRECATION_WARNINGS
                ret = 0;
            }
         }
+#if CONFIG_LIBLCEVC_DEC
+        lcevc = av_mallocz(sizeof(FFLCEVCContext));
+        ret = ff_lcevc_init(lcevc, avctx);
+        if (ret < 0) {
+            int explode = avctx->err_recognition & AV_EF_EXPLODE;
+            av_log(avctx, explode ? AV_LOG_ERROR: AV_LOG_WARNING,
+                   "Error initializing LCEVC\n");
+            if (explode) {
+                av_free(lcevc);
+                return ret;
+            }
+            ret = 0;
+        }
+        h->lcevc = av_buffer_create(lcevc, sizeof(FFLCEVCContext), ff_lcevc_free, lcevc, 0);
+#endif
     }
 
     if (h->ps.sps && h->ps.sps->bitstream_restriction_flag &&
@@ -668,9 +692,23 @@ static int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size)
                     h->setup_finished = 1;
                 }
 
-                if (h->avctx->hwaccel &&
-                    (ret = FF_HW_CALL(h->avctx, start_frame, buf, buf_size)) < 0)
-                    goto end;
+                if (h->avctx->hwaccel) {
+                    ret = FF_HW_CALL(h->avctx, start_frame, buf, buf_size);
+                    if (ret < 0)
+                        goto end;
+                }
+#if CONFIG_LIBLCEVC_DEC
+                else if (h->cur_pic_ptr->needs_lcevc) {
+                    FrameDecodeData *fdd = (FrameDecodeData*)h->cur_pic_ptr->f->private_ref->data;
+                    fdd->post_process_opaque = av_buffer_ref(h->lcevc);
+                    if (!fdd->post_process_opaque) {
+                        ret = -1;
+                        goto end;
+                    }
+                    fdd->post_process_opaque_free = ff_lcevc_unref;
+                    fdd->post_process = ff_lcevc_process;
+                }
+#endif
             }
 
             max_slice_ctx = avctx->hwaccel ? 1 : h->nb_slice_ctx;
@@ -905,6 +943,10 @@ static int output_frame(H264Context *h, AVFrame *dst, H264Picture *srcp)
 
     if (!(h->avctx->export_side_data & AV_CODEC_EXPORT_DATA_FILM_GRAIN))
         av_frame_remove_side_data(dst, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
+#if CONFIG_LIBLCEVC_DEC
+    if (!(h->avctx->export_side_data & AV_CODEC_EXPORT_DATA_ENHANCEMENTS))
+        av_frame_remove_side_data(dst, AV_FRAME_DATA_LCEVC);
+#endif
 
     return 0;
 fail:
diff --git a/libavcodec/h264dec.h b/libavcodec/h264dec.h
index ccd7583bf4..419d699bab 100644
--- a/libavcodec/h264dec.h
+++ b/libavcodec/h264dec.h
@@ -154,6 +154,7 @@ typedef struct H264Picture {
     int invalid_gap;
     int sei_recovery_frame_cnt;
     int needs_fg;           ///< whether picture needs film grain synthesis (see `f_grain`)
+    int needs_lcevc;        ///< whether picture needs LCEVC enhancement
 
     const PPS   *pps;
 
@@ -580,6 +581,8 @@ typedef struct H264Context {
     int non_gray;                       ///< Did we encounter a intra frame after a gray gap frame
     int noref_gray;
     int skip_gray;
+
+    AVBufferRef *lcevc; // FFLCEVCContext
 } H264Context;
 
 extern const uint16_t ff_h264_mb_sizes[4];
