Commit 40a836bbb8 for ffmpeg

commit 40a836bbb86532990e3ec7cec143a48b0dc0b3f7
Author: Niklas Haas <git@haasn.dev>
Date:   Fri Sep 18 14:00:30 2026 +0200

    avfilter/vf_limiter: resolve per-plane limits once per frame

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

    We do it per-frame, rather than in config_input(), to respect dynamically
    changing filter settings.

    Signed-off-by: Niklas Haas <git@haasn.dev>

diff --git a/libavfilter/vf_limiter.c b/libavfilter/vf_limiter.c
index 197688225e..19dbcfd30e 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,9 +137,6 @@ 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);
-
     if (depth == 8) {
         s->dsp.limiter = limiter8;
     } else {
@@ -162,22 +161,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 +178,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],
@@ -210,6 +197,33 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     ThreadData td;
     AVFrame *out;

+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(in->format);
+    const int is_mpeg = in->color_range == AVCOL_RANGE_MPEG &&
+                        !(desc->flags & AV_PIX_FMT_FLAG_RGB);
+
+    const int depth = desc->comp[0].depth;
+    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]);
+            av_frame_free(&in);
+            return AVERROR(EINVAL);
+        }
+    }
+
     const int nb_jobs = FFMIN3(ff_filter_get_nb_threads(ctx),
                                MAX_THREADS, s->height[2]);