[PR] avcodec/lcevcdec: move implementations to supported decoders (PR #23999)

James Almer via ffmpeg-devel <[email protected]> Mon, 03 Aug 2026 23:19:53 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178579919409.59.15887553250689985580@29965ddac10e>
PR #23999 opened by James Almer (jamrial)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23999
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23999.patch

Remove the code from the generic framework as it's a very hacky way to handle it. Instead, make supported decoders attempt to inject the post-processing.


>From 9827b2201a38ccf97a045fd208381b856e848232 Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Thu, 23 Jul 2026 21:16:57 -0300
Subject: [PATCH 1/2] avcodec/lcevcdec: move implementations to supported
 decoders

Remove the code from the generic framework as it's a very hacky
way to handle it. Instead, make supported decoders attempt to
inject the post-processing.

Signed-off-by: James Almer <[email protected]>
---
 libavcodec/av1dec.c        |  12 ++++
 libavcodec/decode.c        | 110 +------------------------------------
 libavcodec/h264_slice.c    |  17 ++++--
 libavcodec/hevc/refs.c     |  19 ++++---
 libavcodec/internal.h      |   4 ++
 libavcodec/lcevcdec.c      |  49 +++++++++++++++--
 libavcodec/lcevcdec.h      |   8 ++-
 libavcodec/libaomdec.c     |  10 ++++
 libavcodec/libdav1d.c      |  10 ++++
 libavcodec/pthread_frame.c |   3 +
 libavcodec/vvc/refs.c      |  19 ++++---
 11 files changed, 123 insertions(+), 138 deletions(-)

diff --git a/libavcodec/av1dec.c b/libavcodec/av1dec.c
index e08dbddc48..3e0173ed24 100644
--- a/libavcodec/av1dec.c
+++ b/libavcodec/av1dec.c
@@ -38,6 +38,7 @@
 #include "internal.h"
 #include "itut35.h"
 #include "hwconfig.h"
+#include "lcevcdec.h"
 #include "profiles.h"
 #include "progressframe.h"
 #include "libavutil/refstruct.h"
@@ -1139,6 +1140,17 @@ static int set_output_frame(AVCodecContext *avctx, AVFrame *frame)
         return ret;
     }
 
+#if CONFIG_LIBLCEVC_DEC
+    const AVFrameSideData *sd = av_frame_get_side_data(frame, AV_FRAME_DATA_LCEVC);
+    if (avctx->internal->lcevc && sd) {
+        ret = ff_lcevc_alloc_frame(avctx, avctx->internal->lcevc, frame, sd->buf);
+        if (ret < 0) {
+            av_frame_unref(frame);
+            return ret;
+        }
+    }
+#endif
+
     if (avctx->export_side_data & AV_CODEC_EXPORT_DATA_FILM_GRAIN) {
         ret = export_film_grain(avctx, frame);
         if (ret < 0) {
diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index ba6ff8a9fd..39f483a268 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -95,18 +95,6 @@ typedef struct DecodeContext {
      * (global or attached to packets) side data over bytestream.
      */
     uint64_t side_data_pref_mask;
-
-#if CONFIG_LIBLCEVC_DEC
-    struct {
-        FFLCEVCContext *ctx;
-        int frame;
-        enum AVPixelFormat format;
-        int base_width;
-        int base_height;
-        int width;
-        int height;
-    } lcevc;
-#endif
 } DecodeContext;
 
 static DecodeContext *decode_ctx(AVCodecInternal *avci)
@@ -1638,32 +1626,6 @@ int ff_decode_frame_props(AVCodecContext *avctx, AVFrame *frame)
         break;
     }
 
-#if CONFIG_LIBLCEVC_DEC
-    AVCodecInternal    *avci = avctx->internal;
-    DecodeContext        *dc = decode_ctx(avci);
-
-    dc->lcevc.frame = dc->lcevc.ctx &&
-                      av_frame_get_side_data(frame, AV_FRAME_DATA_LCEVC);
-
-    if (dc->lcevc.frame) {
-        ret = ff_lcevc_parse_frame(dc->lcevc.ctx, frame, &dc->lcevc.format,
-                                   &dc->lcevc.width, &dc->lcevc.height);
-        if (ret < 0 && (avctx->err_recognition & AV_EF_EXPLODE))
-            return ret;
-
-        // force get_buffer2() to allocate the base frame using the same dimensions
-        // as the final enhanced frame, in order to prevent reinitializing the buffer
-        // pools unnecessarely
-        if (!ret && dc->lcevc.width && dc->lcevc.height) {
-            dc->lcevc.base_width  = frame->width;
-            dc->lcevc.base_height = frame->height;
-            frame->width  = dc->lcevc.width;
-            frame->height = dc->lcevc.height;
-        } else
-            dc->lcevc.frame = 0;
-    }
-#endif
-
     return 0;
 }
 
@@ -1714,63 +1676,6 @@ int ff_attach_decode_data(AVCodecContext *avctx, AVFrame *frame)
 
     frame->private_ref = fdd;
 
-#if CONFIG_LIBLCEVC_DEC
-    AVCodecInternal    *avci = avctx->internal;
-    DecodeContext        *dc = decode_ctx(avci);
-
-    if (!dc->lcevc.frame) {
-        dc->lcevc.frame = dc->lcevc.ctx &&
-                          av_frame_get_side_data(frame, AV_FRAME_DATA_LCEVC);
-
-        if (dc->lcevc.frame) {
-            int ret = ff_lcevc_parse_frame(dc->lcevc.ctx, frame, &dc->lcevc.format,
-                                           &dc->lcevc.width, &dc->lcevc.height);
-            if (ret < 0 && (avctx->err_recognition & AV_EF_EXPLODE))
-                return ret;
-
-            if (!ret && dc->lcevc.width && dc->lcevc.height) {
-                dc->lcevc.base_width  = frame->width;
-                dc->lcevc.base_height = frame->height;
-            } else
-                dc->lcevc.frame = 0;
-        }
-    }
-    if (dc->lcevc.frame) {
-        FFLCEVCFrame *frame_ctx;
-        int ret;
-
-        if (fdd->post_process || !dc->lcevc.width || !dc->lcevc.height) {
-            dc->lcevc.frame = 0;
-            return 0;
-        }
-
-        frame_ctx = av_refstruct_pool_get(dc->lcevc.ctx->frame_pool);
-        if (!frame_ctx)
-            return AVERROR(ENOMEM);
-
-        frame_ctx->lcevc = av_refstruct_ref(dc->lcevc.ctx);
-        frame_ctx->frame->width  = dc->lcevc.width;
-        frame_ctx->frame->height = dc->lcevc.height;
-        frame_ctx->frame->format = dc->lcevc.format;
-        avctx->bits_per_raw_sample = av_pix_fmt_desc_get(dc->lcevc.format)->comp[0].depth;
-
-        frame->width  = dc->lcevc.base_width;
-        frame->height = dc->lcevc.base_height;
-
-        ret = avctx->get_buffer2(avctx, frame_ctx->frame, 0);
-        if (ret < 0) {
-            av_refstruct_unref(&frame_ctx);
-            return ret;
-        }
-
-        validate_avframe_allocation(avctx, frame_ctx->frame);
-
-        fdd->post_process_opaque = frame_ctx;
-        fdd->post_process = ff_lcevc_process;
-    }
-    dc->lcevc.frame = 0;
-#endif
-
     return 0;
 }
 
@@ -2148,7 +2053,7 @@ av_cold int ff_decode_preinit(AVCodecContext *avctx)
     if (!(avctx->export_side_data & AV_CODEC_EXPORT_DATA_ENHANCEMENTS)) {
         if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
 #if CONFIG_LIBLCEVC_DEC
-            ret = ff_lcevc_alloc(&dc->lcevc.ctx, av_log_get_level() + avctx->log_level_offset);
+            ret = ff_lcevc_alloc(&avci->lcevc, av_log_get_level() + avctx->log_level_offset);
             if (ret < 0 && (avctx->err_recognition & AV_EF_EXPLODE))
                 return ret;
 #endif
@@ -2395,22 +2300,11 @@ av_cold void ff_decode_internal_sync(AVCodecContext *dst, const AVCodecContext *
     dst_dc->initial_pict_type = src_dc->initial_pict_type;
     dst_dc->intra_only_flag   = src_dc->intra_only_flag;
     dst_dc->side_data_pref_mask = src_dc->side_data_pref_mask;
-#if CONFIG_LIBLCEVC_DEC
-    av_refstruct_replace(&dst_dc->lcevc.ctx, src_dc->lcevc.ctx);
-    dst_dc->lcevc.width = src_dc->lcevc.width;
-    dst_dc->lcevc.height = src_dc->lcevc.height;
-    dst_dc->lcevc.format = src_dc->lcevc.format;
-#endif
 }
 
 av_cold void ff_decode_internal_uninit(AVCodecContext *avctx)
 {
-#if CONFIG_LIBLCEVC_DEC
-    AVCodecInternal *avci = avctx->internal;
-    DecodeContext *dc = decode_ctx(avci);
-
-    av_refstruct_unref(&dc->lcevc.ctx);
-#endif
+    return;
 }
 
 static int attach_displaymatrix(AVCodecContext *avctx, AVFrame *frame, int orientation)
diff --git a/libavcodec/h264_slice.c b/libavcodec/h264_slice.c
index 9b5ed8f77e..0229da1be1 100644
--- a/libavcodec/h264_slice.c
+++ b/libavcodec/h264_slice.c
@@ -41,7 +41,9 @@
 #include "h264data.h"
 #include "h264chroma.h"
 #include "h264_ps.h"
+#include "internal.h"
 #include "golomb.h"
+#include "lcevcdec.h"
 #include "mathops.h"
 #include "mpegutils.h"
 #include "rectangle.h"
@@ -191,18 +193,21 @@ static int alloc_picture(H264Context *h, H264Picture *pic)
 
     av_assert0(!pic->f->data[0]);
 
-    if (h->sei.common.itut_t35.lcevc) {
-        ret = ff_frame_new_side_data_from_buf(h->avctx, pic->f, AV_FRAME_DATA_LCEVC, &h->sei.common.itut_t35.lcevc);
-        if (ret < 0)
-            return ret;
-    }
-
     pic->tf.f = pic->f;
     ret = ff_thread_get_ext_buffer(h->avctx, &pic->tf,
                                    pic->reference ? AV_GET_BUFFER_FLAG_REF : 0);
     if (ret < 0)
         goto fail;
 
+#if CONFIG_LIBLCEVC_DEC
+    if (h->avctx->internal->lcevc && h->sei.common.itut_t35.lcevc) {
+        ret = ff_lcevc_alloc_frame(h->avctx, h->avctx->internal->lcevc, pic->f,
+                                   h->sei.common.itut_t35.lcevc);
+        if (ret < 0)
+            goto fail;
+    }
+#endif
+
     if (pic->needs_fg) {
         pic->f_grain->format = pic->f->format;
         pic->f_grain->width = pic->f->width;
diff --git a/libavcodec/hevc/refs.c b/libavcodec/hevc/refs.c
index 2df3a2ad56..1d4113577f 100644
--- a/libavcodec/hevc/refs.c
+++ b/libavcodec/hevc/refs.c
@@ -28,6 +28,8 @@
 #include "libavcodec/decode.h"
 #include "hevc.h"
 #include "hevcdec.h"
+#include "libavcodec/internal.h"
+#include "libavcodec/lcevcdec.h"
 #include "libavcodec/progressframe.h"
 #include "libavcodec/thread.h"
 #include "libavutil/refstruct.h"
@@ -121,14 +123,6 @@ static HEVCFrame *alloc_frame(HEVCContext *s, HEVCLayerContext *l)
         if (ret < 0)
             return NULL;
 
-        // Add LCEVC SEI metadata here, as it's needed in get_buffer()
-        if (s->sei.common.itut_t35.lcevc) {
-            ret = ff_frame_new_side_data_from_buf(s->avctx, frame->tf.f,
-                                                  AV_FRAME_DATA_LCEVC, &s->sei.common.itut_t35.lcevc);
-            if (ret < 0)
-                goto fail;
-        }
-
         // add view ID side data if it's nontrivial
         if (!ff_hevc_is_alpha_video(s) && (vps->nb_layers > 1 || view_id)) {
             HEVCSEITDRDI *tdrdi = &s->sei.tdrdi;
@@ -161,6 +155,15 @@ static HEVCFrame *alloc_frame(HEVCContext *s, HEVCLayerContext *l)
         if (ret < 0)
             goto fail;
 
+#if CONFIG_LIBLCEVC_DEC
+        if (s->avctx->internal->lcevc && s->sei.common.itut_t35.lcevc) {
+            ret = ff_lcevc_alloc_frame(s->avctx, s->avctx->internal->lcevc, frame->f,
+                                       s->sei.common.itut_t35.lcevc);
+            if (ret < 0)
+                goto fail;
+        }
+#endif
+
         size_t rpl_bytes;
         if (av_size_mult(s->pkt.nb_nals, sizeof(*frame->rpl), &rpl_bytes) < 0)
             goto fail;
diff --git a/libavcodec/internal.h b/libavcodec/internal.h
index e742b170cf..2750bfc303 100644
--- a/libavcodec/internal.h
+++ b/libavcodec/internal.h
@@ -46,6 +46,8 @@
 #   define STRIDE_ALIGN 8
 #endif
 
+struct FFLCEVCContext;
+
 typedef struct AVCodecInternal {
     /**
      * When using frame-threaded decoding, this field is set for the first
@@ -149,6 +151,8 @@ typedef struct AVCodecInternal {
     FFIccContext icc; /* used to read and write embedded ICC profiles */
 #endif
 
+    struct FFLCEVCContext *lcevc;
+
     /**
      * Set when the user has been warned about a failed allocation from
      * a fixed frame pool.
diff --git a/libavcodec/lcevcdec.c b/libavcodec/lcevcdec.c
index 2e16a5a627..56d55bcdb1 100644
--- a/libavcodec/lcevcdec.c
+++ b/libavcodec/lcevcdec.c
@@ -29,6 +29,7 @@
 #include "lcevc_parse.h"
 #include "lcevcdec.h"
 #include "lcevctab.h"
+#include "thread.h"
 
 static LCEVC_ColorFormat map_format(int format)
 {
@@ -391,7 +392,7 @@ static int lcevc_init(FFLCEVCContext *lcevc)
     return 0;
 }
 
-int ff_lcevc_process(void *logctx, AVFrame *frame)
+static int lcevc_process(void *logctx, AVFrame *frame)
 {
     FrameDecodeData  *fdd = frame->private_ref;
     FFLCEVCFrame *frame_ctx = fdd->post_process_opaque;
@@ -406,7 +407,6 @@ int ff_lcevc_process(void *logctx, AVFrame *frame)
 
     av_assert0(frame_ctx->frame);
 
-
     ret = lcevc_send_frame(frame_ctx, frame);
     if (ret)
         return ret < 0 ? ret : 0;
@@ -420,15 +420,14 @@ int ff_lcevc_process(void *logctx, AVFrame *frame)
     return 0;
 }
 
-int ff_lcevc_parse_frame(FFLCEVCContext *lcevc, const AVFrame *frame,
-                         enum AVPixelFormat *format, int *width, int *height)
+static int lcevc_parse_frame(FFLCEVCContext *lcevc, AVBufferRef *buf,
+                             enum AVPixelFormat *format, int *width, int *height)
 {
     LCEVCRawProcessBlock *block = NULL;
     const LCEVCRawGlobalConfig *gc;
-    const AVFrameSideData *sd = av_frame_get_side_data(frame, AV_FRAME_DATA_LCEVC);
     int ret;
 
-    ret = ff_cbs_read(lcevc->cbc, lcevc->frag, NULL, sd->data, sd->size);
+    ret = ff_cbs_read(lcevc->cbc, lcevc->frag, NULL, buf->data, buf->size);
     if (ret < 0) {
         av_log(lcevc, AV_LOG_ERROR, "Failed to parse Access Unit.\n");
         goto end;
@@ -459,6 +458,44 @@ end:
     return ret;
 }
 
+int ff_lcevc_alloc_frame(AVCodecContext *avctx, FFLCEVCContext *lcevc,
+                         AVFrame *frame, AVBufferRef *buf)
+{
+    FrameDecodeData *fdd = frame->private_ref;
+    int ret;
+
+    if (fdd->post_process)
+        return 0;
+
+    ret = lcevc_parse_frame(lcevc, buf, &lcevc->format, &lcevc->width, &lcevc->height);
+    if (ret < 0)
+        return (avctx->err_recognition & AV_EF_EXPLODE) ? ret : 0;
+
+    if (!lcevc->width || !lcevc->height)
+        return 0;
+
+    FFLCEVCFrame *frame_ctx = av_refstruct_pool_get(lcevc->frame_pool);
+    if (!frame_ctx)
+        return AVERROR(ENOMEM);
+
+    frame_ctx->lcevc = av_refstruct_ref(lcevc);
+    frame_ctx->frame->width  = lcevc->width;
+    frame_ctx->frame->height = lcevc->height;
+    frame_ctx->frame->format = lcevc->format;
+    avctx->bits_per_raw_sample = av_pix_fmt_desc_get(lcevc->format)->comp[0].depth;
+
+    ret = ff_thread_get_buffer(avctx, frame_ctx->frame, 0);
+    if (ret < 0) {
+        av_refstruct_unref(&frame_ctx);
+        return ret;
+    }
+
+    fdd->post_process_opaque = frame_ctx;
+    fdd->post_process = lcevc_process;
+
+    return 0;
+}
+
 static const CodedBitstreamUnitType decompose_unit_types[] = {
     LCEVC_IDR_NUT,
     LCEVC_NON_IDR_NUT,
diff --git a/libavcodec/lcevcdec.h b/libavcodec/lcevcdec.h
index e51d3de989..e3044d4935 100644
--- a/libavcodec/lcevcdec.h
+++ b/libavcodec/lcevcdec.h
@@ -39,10 +39,14 @@ typedef struct FFLCEVCContext {
     struct CodedBitstreamContext *cbc;
     struct CodedBitstreamFragment *frag;
     struct AVRefStructPool *frame_pool; ///< pool of FFLCEVCFrame
+    int width, height;
+    enum AVPixelFormat format;
     int loglevel;
     int initialized;
 } FFLCEVCContext;
 
+struct AVBufferRef;
+struct AVCodecContext;
 struct AVFrame;
 
 typedef struct FFLCEVCFrame {
@@ -51,8 +55,8 @@ typedef struct FFLCEVCFrame {
 } FFLCEVCFrame;
 
 int ff_lcevc_alloc(FFLCEVCContext **plcevc, int loglevel);
+int ff_lcevc_alloc_frame(struct AVCodecContext *avctx, FFLCEVCContext *lcevc,
+                         struct AVFrame *frame, struct AVBufferRef *buf);
 int ff_lcevc_process(void *logctx, struct AVFrame *frame);
-int ff_lcevc_parse_frame(FFLCEVCContext *lcevc, const struct AVFrame *frame,
-                         enum AVPixelFormat *format, int *width, int *height);
 
 #endif /* AVCODEC_LCEVCDEC_H */
diff --git a/libavcodec/libaomdec.c b/libavcodec/libaomdec.c
index 33925d4cb4..70aa829130 100644
--- a/libavcodec/libaomdec.c
+++ b/libavcodec/libaomdec.c
@@ -36,6 +36,7 @@
 #include "codec_internal.h"
 #include "decode.h"
 #include "itut35.h"
+#include "lcevcdec.h"
 #include "libaom.h"
 #include "profiles.h"
 
@@ -229,6 +230,15 @@ static int aom_decode(AVCodecContext *avctx, AVFrame *picture,
         if ((ret = ff_get_buffer(avctx, picture, 0)) < 0)
             return ret;
 
+#if CONFIG_LIBLCEVC_DEC
+        const AVFrameSideData *sd = av_frame_get_side_data(picture, AV_FRAME_DATA_LCEVC);
+        if (avctx->internal->lcevc && sd) {
+            ret = ff_lcevc_alloc_frame(c, avctx->internal->lcevc, frame, sd->buf);
+            if (ret < 0)
+                return ret;
+        }
+#endif
+
 #ifdef AOM_CTRL_AOMD_GET_FRAME_FLAGS
         {
             aom_codec_frame_flags_t flags;
diff --git a/libavcodec/libdav1d.c b/libavcodec/libdav1d.c
index b41b741c0d..5e209dcb7a 100644
--- a/libavcodec/libdav1d.c
+++ b/libavcodec/libdav1d.c
@@ -38,6 +38,7 @@
 #include "dovi_rpu.h"
 #include "internal.h"
 #include "itut35.h"
+#include "lcevcdec.h"
 
 #define FF_DAV1D_VERSION_AT_LEAST(x,y) \
     (DAV1D_API_VERSION_MAJOR > (x) || DAV1D_API_VERSION_MAJOR == (x) && DAV1D_API_VERSION_MINOR >= (y))
@@ -555,6 +556,15 @@ static int libdav1d_receive_frame(AVCodecContext *c, AVFrame *frame)
     if (res < 0)
         return res;
 
+#if CONFIG_LIBLCEVC_DEC
+        const AVFrameSideData *sd = av_frame_get_side_data(frame, AV_FRAME_DATA_LCEVC);
+        if (c->internal->lcevc && sd) {
+            res = ff_lcevc_alloc_frame(c, c->internal->lcevc, frame, sd->buf);
+            if (res < 0)
+                goto fail;
+        }
+#endif
+
     res = 0;
 fail:
     dav1d_picture_unref(p);
diff --git a/libavcodec/pthread_frame.c b/libavcodec/pthread_frame.c
index e6dcc23c15..97e56c264a 100644
--- a/libavcodec/pthread_frame.c
+++ b/libavcodec/pthread_frame.c
@@ -395,6 +395,7 @@ static int update_context_from_thread(AVCodecContext *dst, const AVCodecContext
         dst->hwaccel_flags = src->hwaccel_flags;
 
         av_refstruct_replace(&dst->internal->pool, src->internal->pool);
+        av_refstruct_replace(&dst->internal->lcevc, src->internal->lcevc);
         ff_decode_internal_sync(dst, src);
     }
 
@@ -775,6 +776,7 @@ av_cold void ff_frame_thread_free(AVCodecContext *avctx, int thread_count)
             }
 
             av_refstruct_unref(&ctx->internal->pool);
+            av_refstruct_unref(&ctx->internal->lcevc);
             av_packet_free(&ctx->internal->in_pkt);
             av_packet_free(&ctx->internal->last_pkt_props);
             ff_decode_internal_uninit(ctx);
@@ -837,6 +839,7 @@ static av_cold int init_thread(PerThreadContext *p, int *threads_to_free,
     ff_decode_internal_sync(copy, avctx);
     copy->internal->thread_ctx = p;
     copy->internal->progress_frame_pool = avctx->internal->progress_frame_pool;
+    av_refstruct_replace(&copy->internal->lcevc, avctx->internal->lcevc);
 
     copy->delay = avctx->delay;
 
diff --git a/libavcodec/vvc/refs.c b/libavcodec/vvc/refs.c
index 15024d7b68..e1b9965e7a 100644
--- a/libavcodec/vvc/refs.c
+++ b/libavcodec/vvc/refs.c
@@ -28,6 +28,8 @@
 #include "libavutil/refstruct.h"
 #include "libavcodec/thread.h"
 #include "libavcodec/decode.h"
+#include "libavcodec/internal.h"
+#include "libavcodec/lcevcdec.h"
 
 #include "refs.h"
 
@@ -133,18 +135,19 @@ static VVCFrame *alloc_frame(VVCContext *s, VVCFrameContext *fc)
         frame->sps = av_refstruct_ref_c(fc->ps.sps);
         frame->pps = av_refstruct_ref_c(fc->ps.pps);
 
-        // Add LCEVC SEI metadata here, as it's needed in get_buffer()
-        if (fc->sei.common.itut_t35.lcevc) {
-            ret = ff_frame_new_side_data_from_buf(s->avctx, frame->frame,
-                                                  AV_FRAME_DATA_LCEVC, &fc->sei.common.itut_t35.lcevc);
-            if (ret < 0)
-                goto fail;
-        }
-
         ret = ff_thread_get_buffer(s->avctx, frame->frame, AV_GET_BUFFER_FLAG_REF);
         if (ret < 0)
             return NULL;
 
+#if CONFIG_LIBLCEVC_DEC
+        if (s->avctx->internal->lcevc && fc->sei.common.itut_t35.lcevc) {
+            ret = ff_lcevc_alloc_frame(s->avctx, s->avctx->internal->lcevc, frame->frame,
+                                       fc->sei.common.itut_t35.lcevc);
+            if (ret < 0)
+                goto fail;
+        }
+#endif
+
         frame->rpl = av_refstruct_allocz(s->current_frame.nb_units * sizeof(RefPicListTab));
         if (!frame->rpl)
             goto fail;
-- 
2.52.0


>From 05e4530d26de3ca857f90e16e0a88974eca77057 Mon Sep 17 00:00:00 2001
From: James Almer <[email protected]>
Date: Tue, 28 Jul 2026 23:49:11 -0300
Subject: [PATCH 2/2] avcodec/lcevcdec: add support for passing options

Only a hardcoded subset of options is supported, as liblcevc-dec lacks
a proper function to either query existing options, or parsing option
values passed as strings.

Signed-off-by: James Almer <[email protected]>
---
 libavcodec/decode.c       |  7 ++++-
 libavcodec/h264dec.c      |  2 ++
 libavcodec/h264dec.h      |  2 ++
 libavcodec/hevc/hevcdec.c |  2 ++
 libavcodec/hevc/hevcdec.h |  2 ++
 libavcodec/lcevcdec.c     | 63 +++++++++++++++++++++++++++++++++++----
 libavcodec/lcevcdec.h     | 12 +++++++-
 libavcodec/libaomdec.c    | 15 ++++++++++
 libavcodec/libdav1d.c     |  3 ++
 libavcodec/vvc/dec.c      | 15 ++++++++++
 libavcodec/vvc/dec.h      |  2 ++
 11 files changed, 118 insertions(+), 7 deletions(-)

diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 39f483a268..b33d202b19 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -39,6 +39,7 @@
 #include "libavutil/internal.h"
 #include "libavutil/mastering_display_metadata.h"
 #include "libavutil/mem.h"
+#include "libavutil/opt.h"
 #include "libavutil/stereo3d.h"
 
 #include "avcodec.h"
@@ -2053,7 +2054,11 @@ av_cold int ff_decode_preinit(AVCodecContext *avctx)
     if (!(avctx->export_side_data & AV_CODEC_EXPORT_DATA_ENHANCEMENTS)) {
         if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
 #if CONFIG_LIBLCEVC_DEC
-            ret = ff_lcevc_alloc(&avci->lcevc, av_log_get_level() + avctx->log_level_offset);
+            AVDictionary *options = NULL;
+            if (avctx->priv_data)
+                av_opt_get_dict_val(avctx->priv_data, "lcevc-dec-params", 0, &options);
+            ret = ff_lcevc_alloc(&avci->lcevc, &options, avctx->thread_count,
+                                 av_log_get_level() + avctx->log_level_offset);
             if (ret < 0 && (avctx->err_recognition & AV_EF_EXPLODE))
                 return ret;
 #endif
diff --git a/libavcodec/h264dec.c b/libavcodec/h264dec.c
index b78b7989ea..0d37fd9451 100644
--- a/libavcodec/h264dec.c
+++ b/libavcodec/h264dec.c
@@ -50,6 +50,7 @@
 #include "golomb.h"
 #include "hwaccel_internal.h"
 #include "hwconfig.h"
+#include "lcevcdec.h"
 #include "mpegutils.h"
 #include "profiles.h"
 #include "rectangle.h"
@@ -1096,6 +1097,7 @@ static const AVOption h264_options[] = {
     { "x264_build", "Assume this x264 version if no x264 version found in any SEI", OFFSET(x264_build), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, VD },
     { "skip_gray", "Do not return gray gap frames", OFFSET(skip_gray), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, VD },
     { "noref_gray", "Avoid using gray gap frames as references", OFFSET(noref_gray), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, VD },
+    LCEVCDEC_OPTIONS(H264Context)
     { NULL },
 };
 
diff --git a/libavcodec/h264dec.h b/libavcodec/h264dec.h
index 74fd09dfaa..f8461ce9f1 100644
--- a/libavcodec/h264dec.h
+++ b/libavcodec/h264dec.h
@@ -577,6 +577,8 @@ typedef struct H264Context {
     int non_gray;                       ///< Did we encounter a intra frame after a gray gap frame
     int noref_gray;
     int skip_gray;
+
+    AVDictionary *lcevc_params;
 } H264Context;
 
 extern const uint16_t ff_h264_mb_sizes[4];
diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c
index 475c2738b1..f027153c9d 100644
--- a/libavcodec/hevc/hevcdec.c
+++ b/libavcodec/hevc/hevcdec.c
@@ -50,6 +50,7 @@
 #include "libavcodec/hwaccel_internal.h"
 #include "libavcodec/hwconfig.h"
 #include "libavcodec/internal.h"
+#include "libavcodec/lcevcdec.h"
 #include "libavcodec/profiles.h"
 #include "libavcodec/progressframe.h"
 #include "libavcodec/thread.h"
@@ -4246,6 +4247,7 @@ static const AVOption options[] = {
         { "unspecified", .type = AV_OPT_TYPE_CONST, .default_val = { .i64 = AV_STEREO3D_VIEW_UNSPEC }, .unit = "view_pos" },
         { "left",        .type = AV_OPT_TYPE_CONST, .default_val = { .i64 = AV_STEREO3D_VIEW_LEFT },   .unit = "view_pos" },
         { "right",       .type = AV_OPT_TYPE_CONST, .default_val = { .i64 = AV_STEREO3D_VIEW_RIGHT },  .unit = "view_pos" },
+    LCEVCDEC_OPTIONS(HEVCContext)
 
     { NULL },
 };
diff --git a/libavcodec/hevc/hevcdec.h b/libavcodec/hevc/hevcdec.h
index 8394740c4b..412f931fe5 100644
--- a/libavcodec/hevc/hevcdec.h
+++ b/libavcodec/hevc/hevcdec.h
@@ -581,6 +581,8 @@ typedef struct HEVCContext {
 
     AVBufferRef *rpu_buf;       ///< 0 or 1 Dolby Vision RPUs.
     DOVIContext dovi_ctx;       ///< Dolby Vision decoding context
+
+    AVDictionary *lcevc_params;
 } HEVCContext;
 
 /**
diff --git a/libavcodec/lcevcdec.c b/libavcodec/lcevcdec.c
index 56d55bcdb1..0fa9368405 100644
--- a/libavcodec/lcevcdec.c
+++ b/libavcodec/lcevcdec.c
@@ -368,6 +368,7 @@ static int lcevc_init(FFLCEVCContext *lcevc)
 {
     LCEVC_AccelContextHandle dummy = { 0 };
     const int32_t event = LCEVC_Log;
+    LCEVC_ReturnCode res;
     int level;
 
     if (LCEVC_CreateDecoder(&lcevc->decoder, dummy) != LCEVC_Success) {
@@ -378,6 +379,30 @@ static int lcevc_init(FFLCEVCContext *lcevc)
     level = get_log_level(lcevc->loglevel);
 
     LCEVC_ConfigureDecoderInt(lcevc->decoder, "log_level", level);
+    res = LCEVC_ConfigureDecoderInt(lcevc->decoder, "threads", lcevc->threads);
+    if (res != LCEVC_Success) {
+        av_log(lcevc, AV_LOG_ERROR, "Failed to set threads to %d\n", lcevc->threads);
+        LCEVC_DestroyDecoder(lcevc->decoder);
+        return AVERROR_EXTERNAL;
+    }
+    res = LCEVC_ConfigureDecoderInt(lcevc->decoder, "passthrough_mode", lcevc->passthrough_mode);
+    if (res != LCEVC_Success) {
+        av_log(lcevc, AV_LOG_ERROR, "Failed to set passthrough_mode to %d\n", lcevc->passthrough_mode);
+        LCEVC_DestroyDecoder(lcevc->decoder);
+        return AVERROR_EXTERNAL;
+    }
+    res = LCEVC_ConfigureDecoderBool(lcevc->decoder, "allow_dithering", lcevc->allow_dithering);
+    if (res != LCEVC_Success) {
+        av_log(lcevc, AV_LOG_ERROR, "Failed to set allow_dithering to %d\n", lcevc->allow_dithering);
+        LCEVC_DestroyDecoder(lcevc->decoder);
+        return AVERROR_EXTERNAL;
+    }
+    res = LCEVC_ConfigureDecoderBool(lcevc->decoder, "highlight_residuals", lcevc->highlight_residuals);
+    if (res != LCEVC_Success) {
+        av_log(lcevc, AV_LOG_ERROR, "Failed to set highlight_residuals to %d\n", lcevc->highlight_residuals);
+        LCEVC_DestroyDecoder(lcevc->decoder);
+        return AVERROR_EXTERNAL;
+    }
     LCEVC_ConfigureDecoderIntArray(lcevc->decoder, "events", 1, &event);
     LCEVC_SetDecoderEventCallback(lcevc->decoder, event_callback, lcevc);
 
@@ -501,22 +526,53 @@ static const CodedBitstreamUnitType decompose_unit_types[] = {
     LCEVC_NON_IDR_NUT,
 };
 
+#define OFFSET(x) offsetof(FFLCEVCContext, x)
+static const AVOption lcevc_opts[] = {
+    { "passthrough_mode", "Set passthrough mode", OFFSET(passthrough_mode), AV_OPT_TYPE_INT,
+            { .i64 = 0 }, -1, 2, AV_OPT_FLAG_DECODING_PARAM, .unit = "passthrough_mode" },
+        { "disable", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = -1 }, .unit = "passthrough_mode" },
+        { "allow",   NULL, 0, AV_OPT_TYPE_CONST, { .i64 = 0 },  .unit = "passthrough_mode" },
+        { "force",   NULL, 0, AV_OPT_TYPE_CONST, { .i64 = 1 },  .unit = "passthrough_mode" },
+        { "scale",   NULL, 0, AV_OPT_TYPE_CONST, { .i64 = 2 },  .unit = "passthrough_mode" },
+    { "allow_dithering", "Allow dithering", OFFSET(allow_dithering), AV_OPT_TYPE_BOOL,
+            { .i64 = 1 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
+    { "highlight_residuals", "Gighlight residuals", OFFSET(highlight_residuals), AV_OPT_TYPE_BOOL,
+            { .i64 = 0 }, 0, 1, AV_OPT_FLAG_DECODING_PARAM },
+    { "threads", "Thread count", OFFSET(threads), AV_OPT_TYPE_INT,
+            { .i64 = 0 }, 0, INT_MAX, AV_OPT_FLAG_DECODING_PARAM },
+    { NULL },
+};
 
 static const AVClass lcevcdec_context_class = {
     .class_name     = "liblcevc_dec",
     .item_name      = av_default_item_name,
+    .option         = lcevc_opts,
     .version        = LIBAVUTIL_VERSION_INT,
     .category       = AV_CLASS_CATEGORY_DECODER,
 };
 
-int ff_lcevc_alloc(FFLCEVCContext **plcevc, int loglevel)
+int ff_lcevc_alloc(FFLCEVCContext **plcevc, AVDictionary **options,
+                   int threads, int loglevel)
 {
     FFLCEVCContext *lcevc = NULL;
     int ret;
 
     lcevc = av_refstruct_alloc_ext(sizeof(*lcevc), 0, NULL, lcevc_free);
-    if (!lcevc)
+    if (!lcevc) {
+        av_dict_free(options);
         return AVERROR(ENOMEM);
+    }
+
+    lcevc->class = &lcevcdec_context_class;
+    av_opt_set_defaults(lcevc);
+
+    lcevc->loglevel = loglevel;
+    lcevc->threads  = threads;
+
+    ret = av_opt_set_dict2(lcevc, options, 0);
+    av_dict_free(options);
+    if (ret < 0)
+        return ret;
 
     lcevc->frag = av_mallocz(sizeof(*lcevc->frag));
     if (!lcevc->frag) {
@@ -540,9 +596,6 @@ int ff_lcevc_alloc(FFLCEVCContext **plcevc, int loglevel)
         goto fail;
     }
 
-    lcevc->class = &lcevcdec_context_class;
-    lcevc->loglevel = loglevel;
-
     *plcevc = lcevc;
     return 0;
 fail:
diff --git a/libavcodec/lcevcdec.h b/libavcodec/lcevcdec.h
index e3044d4935..892e6b6a10 100644
--- a/libavcodec/lcevcdec.h
+++ b/libavcodec/lcevcdec.h
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include "libavutil/log.h"
+#include "libavutil/opt.h"
 
 #include <stdint.h>
 #if CONFIG_LIBLCEVC_DEC
@@ -42,6 +43,10 @@ typedef struct FFLCEVCContext {
     int width, height;
     enum AVPixelFormat format;
     int loglevel;
+    int threads;
+    int passthrough_mode;
+    int allow_dithering;
+    int highlight_residuals;
     int initialized;
 } FFLCEVCContext;
 
@@ -54,9 +59,14 @@ typedef struct FFLCEVCFrame {
     struct AVFrame *frame;
 } FFLCEVCFrame;
 
-int ff_lcevc_alloc(FFLCEVCContext **plcevc, int loglevel);
+int ff_lcevc_alloc(FFLCEVCContext **plcevc, AVDictionary **options, int threads, int loglevel);
 int ff_lcevc_alloc_frame(struct AVCodecContext *avctx, FFLCEVCContext *lcevc,
                          struct AVFrame *frame, struct AVBufferRef *buf);
 int ff_lcevc_process(void *logctx, struct AVFrame *frame);
 
+#define LCEVCDEC_OPTIONS(offset) \
+    { "lcevc-dec-params", "Configure liblcevc-dec using a :-separated list of key=value parameters", \
+       offsetof(offset, lcevc_params), AV_OPT_TYPE_DICT, { 0 }, 0, 0, \
+	   AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM },
+
 #endif /* AVCODEC_LCEVCDEC_H */
diff --git a/libavcodec/libaomdec.c b/libavcodec/libaomdec.c
index 70aa829130..9484822c32 100644
--- a/libavcodec/libaomdec.c
+++ b/libavcodec/libaomdec.c
@@ -42,6 +42,7 @@
 
 typedef struct AV1DecodeContext {
     struct aom_codec_ctx decoder;
+    AVDictionary *lcevc_params;
 } AV1DecodeContext;
 
 static av_cold int aom_init(AVCodecContext *avctx,
@@ -296,11 +297,25 @@ static av_cold int av1_init(AVCodecContext *avctx)
     return aom_init(avctx, aom_codec_av1_dx());
 }
 
+static const AVOption options[] = {
+    LCEVCDEC_OPTIONS(AV1DecodeContext)
+
+    { NULL },
+};
+
+static const AVClass aom_class = {
+    .class_name = "libaom-av1 decoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 const FFCodec ff_libaom_av1_decoder = {
     .p.name         = "libaom-av1",
     CODEC_LONG_NAME("libaom AV1"),
     .p.type         = AVMEDIA_TYPE_VIDEO,
     .p.id           = AV_CODEC_ID_AV1,
+    .p.priv_class   = &aom_class,
     .priv_data_size = sizeof(AV1DecodeContext),
     .init           = av1_init,
     .close          = aom_free,
diff --git a/libavcodec/libdav1d.c b/libavcodec/libdav1d.c
index 5e209dcb7a..6a3781faae 100644
--- a/libavcodec/libdav1d.c
+++ b/libavcodec/libdav1d.c
@@ -55,6 +55,8 @@ typedef struct Libdav1dContext {
     int apply_grain;
     int operating_point;
     int all_layers;
+
+    AVDictionary *lcevc_params;
 } Libdav1dContext;
 
 static const enum AVPixelFormat pix_fmt[][3] = {
@@ -602,6 +604,7 @@ static const AVOption libdav1d_options[] = {
     { "filmgrain", "Apply Film Grain", OFFSET(apply_grain), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, VD | AV_OPT_FLAG_DEPRECATED },
     { "oppoint",  "Select an operating point of the scalable bitstream", OFFSET(operating_point), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, 31, VD },
     { "alllayers", "Output all spatial layers", OFFSET(all_layers), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VD },
+    LCEVCDEC_OPTIONS(Libdav1dContext)
     { NULL }
 };
 
diff --git a/libavcodec/vvc/dec.c b/libavcodec/vvc/dec.c
index 926288e07b..765f94fe30 100644
--- a/libavcodec/vvc/dec.c
+++ b/libavcodec/vvc/dec.c
@@ -29,6 +29,7 @@
 #include "libavcodec/profiles.h"
 #include "libavutil/refstruct.h"
 #include "libavcodec/aom_film_grain.h"
+#include "libavcodec/lcevcdec.h"
 #include "libavcodec/thread.h"
 #include "libavutil/cpu.h"
 #include "libavutil/mem.h"
@@ -1305,11 +1306,25 @@ static av_cold int vvc_decode_init(AVCodecContext *avctx)
     return 0;
 }
 
+static const AVOption options[] = {
+    LCEVCDEC_OPTIONS(VVCContext)
+
+    { NULL },
+};
+
+static const AVClass vvc_decoder_class = {
+    .class_name = "VVC decoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 const FFCodec ff_vvc_decoder = {
     .p.name         = "vvc",
     .p.long_name    = NULL_IF_CONFIG_SMALL("VVC (Versatile Video Coding)"),
     .p.type         = AVMEDIA_TYPE_VIDEO,
     .p.id           = AV_CODEC_ID_VVC,
+    .p.priv_class   = &vvc_decoder_class,
     .priv_data_size = sizeof(VVCContext),
     .init           = vvc_decode_init,
     .close          = vvc_decode_free,
diff --git a/libavcodec/vvc/dec.h b/libavcodec/vvc/dec.h
index bfb8a2e20d..21f188d5a5 100644
--- a/libavcodec/vvc/dec.h
+++ b/libavcodec/vvc/dec.h
@@ -250,6 +250,8 @@ typedef struct VVCContext {
     int nb_delayed;         ///< delayed frames
 
     H274HashContext *hash_ctx;
+
+    AVDictionary *lcevc_params;
 }  VVCContext ;
 
 #endif /* AVCODEC_VVC_DEC_H */
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]