PR #24558 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24558
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24558.patch

I was clearly drunk when I wrote this code. I have no idea how I arrived at the 
conclusion that this was a reasonable solution to the issue.


>From 96b99edba6eb736fe8f0266611b477f275f554db Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 18 Sep 2026 14:00:30 +0200
Subject: [PATCH 1/3] avfilter/vf_limiter: resolve per-plane limits during
 config_input()

Instead of in the slice thread. These values do not differ by slice.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/vf_limiter.c | 49 ++++++++++++++++++++++++----------------
 1 file changed, 30 insertions(+), 19 deletions(-)

diff --git a/libavfilter/vf_limiter.c b/libavfilter/vf_limiter.c
index 197688225e..16bcab05cc 100644
--- a/libavfilter/vf_limiter.c
+++ b/libavfilter/vf_limiter.c
@@ -16,6 +16,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "libavutil/avassert.h"
 #include "libavutil/attributes.h"
 #include "libavutil/common.h"
 #include "libavutil/imgutils.h"
@@ -35,10 +36,11 @@ typedef struct ThreadData {
 
 typedef struct LimiterContext {
     const AVClass *class;
-    int min;
-    int max;
+    int min, max; /* user-facing option */
     int planes;
     int nb_planes;
+    int plane_min[4]; /* resolved per-plane limits */
+    int plane_max[4];
     int linesize[4];
     int width[4];
     int height[4];
@@ -135,8 +137,29 @@ static int config_input(AVFilterLink *inlink)
     s->width[1]  = s->width[2]  = AV_CEIL_RSHIFT(inlink->w, hsub);
     s->width[0]  = s->width[3]  = inlink->w;
 
-    s->max = FFMIN(s->max, (1 << depth) - 1);
-    s->min = FFMIN(s->min, (1 << depth) - 1);
+    const int is_mpeg = inlink->color_range == AVCOL_RANGE_MPEG &&
+                        !(desc->flags & AV_PIX_FMT_FLAG_RGB);
+
+    const int full_range = (1 << depth) - 1;
+    const int max = FFMIN(s->max, full_range);
+    const int min = FFMIN(s->min, full_range);
+
+    for (int p = 0; p < s->nb_planes; p++) {
+        const int mpeg_min = 16 << (depth - 8);
+        const int mpeg_max = (p ? 240 : 235) << (depth - 8);
+        s->plane_min[p] = min;
+        s->plane_max[p] = max;
+        if (min < 0)
+            s->plane_min[p] = (is_mpeg && p != 3) ? mpeg_min : 0;
+        if (max < 0)
+            s->plane_max[p] = (is_mpeg && p != 3) ? mpeg_max : full_range;
+
+        if (((1 << p) & s->planes) && s->plane_max[p] < s->plane_min[p]) {
+            av_log(ctx, AV_LOG_ERROR, "Invalid min/max values for plane %d: "
+                   "min=%d > max=%d\n", p, s->plane_min[p], s->plane_max[p]);
+            return AVERROR(EINVAL);
+        }
+    }
 
     if (depth == 8) {
         s->dsp.limiter = limiter8;
@@ -162,22 +185,13 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(in->format);
     const int depth = desc->comp[0].depth;
     const int full_range = (1 << depth) - 1;
-    const int is_mpeg = in->color_range == AVCOL_RANGE_MPEG &&
-                        !(desc->flags & AV_PIX_FMT_FLAG_RGB);
 
     for (p = 0; p < s->nb_planes; p++) {
         const int h = s->height[p];
         const int slice_start = ff_slice_pos(h, jobnr, nb_jobs);
         const int slice_end = ff_slice_pos(h, jobnr + 1, nb_jobs);
-        const int mpeg_min = 16 << (depth - 8);
-        const int mpeg_max = (p ? 240 : 235) << (depth - 8);
-
-        int min = s->min, max = s->max;
-        if (min < 0)
-            min = (is_mpeg && p != 3) ? mpeg_min : 0;
-        if (max < 0)
-            max = (is_mpeg && p != 3) ? mpeg_max : full_range;
-
+        const int min = s->plane_min[p];
+        const int max = s->plane_max[p];
         if (!((1 << p) & s->planes) || (min == 0 && max == full_range)) {
             if (out != in)
                 av_image_copy_plane(out->data[p] + slice_start * 
out->linesize[p],
@@ -188,10 +202,7 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
             continue;
         }
 
-        /* check only after resolving no-op planes */
-        if (min > max)
-            return AVERROR(EINVAL);
-
+        av_assert1(max >= min);
         s->dsp.limiter(in->data[p] + slice_start * in->linesize[p],
                        out->data[p] + slice_start * out->linesize[p],
                        in->linesize[p], out->linesize[p],
-- 
2.52.0


>From c29e5c86f4f3fad237a04416e0ac30d0256a2b8d Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 18 Sep 2026 14:01:21 +0200
Subject: [PATCH 2/3] avfilter/vf_limiter: drop unneeded init()

This is redundant with the check in config_input(), and in particular,
rejects an arguably valid configuration that applies an invalid limit to
zero selected planes.

Signed-off-by: Niklas Haas <[email protected]>
---
 libavfilter/vf_limiter.c | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/libavfilter/vf_limiter.c b/libavfilter/vf_limiter.c
index 16bcab05cc..4c7b04fe27 100644
--- a/libavfilter/vf_limiter.c
+++ b/libavfilter/vf_limiter.c
@@ -62,15 +62,6 @@ static const AVOption limiter_options[] = {
 
 AVFILTER_DEFINE_CLASS(limiter);
 
-static av_cold int init(AVFilterContext *ctx)
-{
-    LimiterContext *s = ctx->priv;
-
-    if (s->min >= 0 && s->max >= 0 && s->min > s->max)
-        return AVERROR(EINVAL);
-    return 0;
-}
-
 static const enum AVPixelFormat pix_fmts[] = {
     AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
     AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
@@ -280,7 +271,6 @@ const FFFilter ff_vf_limiter = {
     .p.flags       = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
                      AVFILTER_FLAG_SLICE_THREADS,
     .priv_size     = sizeof(LimiterContext),
-    .init          = init,
     FILTER_INPUTS(inputs),
     FILTER_OUTPUTS(ff_video_default_filterpad),
     FILTER_PIXFMTS_ARRAY(pix_fmts),
-- 
2.52.0


>From 256cd9171243e716f8e0a8c2296fe717bf2a2bcc Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 18 Sep 2026 14:02:20 +0200
Subject: [PATCH 3/3] Revert "avfilter/vf_limiter: gather per-slice return
 values"

This reverts commit 2886aa54c566de980a37a3121b17deb4ac7b91eb.
---
 libavfilter/vf_limiter.c | 17 ++---------------
 1 file changed, 2 insertions(+), 15 deletions(-)

diff --git a/libavfilter/vf_limiter.c b/libavfilter/vf_limiter.c
index 4c7b04fe27..92312b1d2a 100644
--- a/libavfilter/vf_limiter.c
+++ b/libavfilter/vf_limiter.c
@@ -27,8 +27,6 @@
 #include "limiter.h"
 #include "video.h"
 
-#define MAX_THREADS 64
-
 typedef struct ThreadData {
     AVFrame *in;
     AVFrame *out;
@@ -44,7 +42,6 @@ typedef struct LimiterContext {
     int linesize[4];
     int width[4];
     int height[4];
-    int rets[MAX_THREADS];
 
     LimiterDSPContext dsp;
 } LimiterContext;
@@ -212,9 +209,6 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     ThreadData td;
     AVFrame *out;
 
-    const int nb_jobs = FFMIN3(ff_filter_get_nb_threads(ctx),
-                               MAX_THREADS, s->height[2]);
-
     if (av_frame_is_writable(in)) {
         out = in;
     } else {
@@ -228,18 +222,11 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 
     td.out = out;
     td.in = in;
-    memset(s->rets, 0, sizeof(s->rets));
-    ff_filter_execute(ctx, filter_slice, &td, s->rets, nb_jobs);
+    ff_filter_execute(ctx, filter_slice, &td, NULL,
+                      FFMIN(s->height[2], ff_filter_get_nb_threads(ctx)));
     if (out != in)
         av_frame_free(&in);
 
-    for (int i = 0; i < nb_jobs; i++) {
-        if (s->rets[i] < 0) {
-            av_frame_free(&out);
-            return s->rets[i];
-        }
-    }
-
     return ff_filter_frame(outlink, out);
 }
 
-- 
2.52.0

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

Reply via email to