PR #25128 opened by romainhedouin
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25128
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25128.patch

The four commits in order:
1. Fix the last line of planes with an odd height, it was never written (for 
example the last chroma line of 720x486 yuv420p) and for very small frames 
nothing was written at all. The output also used changed with the number of 
filter threads. This commit fixes the missing last line & makes the output 
reliable no matter the number of threads.
2. Read the input lines with a loop instead of seven separate calls. Much 
cleaner code.
3. Compute and write one line at a time. Cleaner code, much simpler to read.
4. Use smaller per-thread buffers. Massive RAM saving.

## Testing

With each commit, I compared the output to the commit before that. I ran 620 
whole ffmpeg runs covering all the neural-network model settings, all the 
prescreener and field modes, 18 pixel formats, and 1 and 4 threads ; they were 
all identical.
I added another 1,728 cases of small frames and off heights (varying height 
from 1 to 36, varying widths/thread count/field orders/prescreener) ; they were 
all identical.
I ran a sample of the real-footage cases for every network model, they were 
identical too.
Because the only behavior change is on the first commit with odd heights, my 
tests for that commit consisted of padding frames to get an even height and 
then running the old code and the new code. I also proved that the old code was 
inconsistent depending on the number of threads, while the new code is 
consistent regardless of the thread count.

I only tested on arm64 with clang

## Performance

CPU time is unchanged. 720x480 real footage, 36 frames, default settings, 
median of a few runs:

| Filter threads | Before | After |
|---|---|---|
| 1 | 3.552 s | 3.555 s |
| 2 | 3.548 s | 3.552 s |
| 5 | 4.733 s | 4.774 s |
| 10 | 4.828 s | 4.809 s |

Peak memory, 1080p yuv420p:

| Filter threads | Before | After | Saved |
|---|---|---|---|
| 1 | 76.0 MB | 71.8 MB | 4.2 MB (-95%) |
| 2 | 92.5 MB | 75.9 MB | 16.6 MB (-82%) |
| 5 | 142.0 MB | 88.5 MB | 53.5 MB (-62%) |
| 10 | 224.7 MB | 109.3 MB | 115.4 MB (-49%) |

Found during a code review sweep of ffmpeg using an LLM.

<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->



>From 519bd798208df7da8b0d76c6fa479e56d5193218 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 16:35:47 +0200
Subject: [PATCH 1/7] avfilter/vf_nnedi: fix a heap overflow in the prescreener
 buffer

process_new() writes the prescreener flags four at a time, but the
buffer was only as wide as the plane. If the width is not a multiple
of four, up to three flags were written past the end of it.

Found with ASan using the default settings, for example on gray
321x120.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index 148791f804..8deba18b0c 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -1096,7 +1096,7 @@ static int config_input(AVFilterLink *inlink)
         return AVERROR(ENOMEM);
 
     for (int i = 0; i < s->nb_threads; i++) {
-        s->prescreen_buf[i] = av_calloc(s->planewidth[0], 
sizeof(**s->prescreen_buf));
+        s->prescreen_buf[i] = av_calloc(FFALIGN(s->planewidth[0], 4), 
sizeof(**s->prescreen_buf));
         if (!s->prescreen_buf[i])
             return AVERROR(ENOMEM);
     }
-- 
2.54.0


>From af35f80353900bea7322d5437e65e19e44262f66 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 16:35:47 +0200
Subject: [PATCH 2/7] avfilter/vf_nnedi: fix out of bounds reads on narrow
 planes

read_bytes() and read_words() pad each line with 32 mirrored samples
on both sides. For a plane narrower than 32 samples they read before
the start and past the end of the line. Clamp the indices. The output
for wider planes is unchanged.

Found with ASan on gray 16x16 and on the chroma of yuv420p 48x32.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index 8deba18b0c..5c4b47e0a2 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -445,13 +445,13 @@ static void read_bytes(const uint8_t *src, float *dst,
 {
     for (int y = 0; y < height; y++) {
         for (int x = 0; x < 32; x++)
-            dst[-x - 1] = src[x];
+            dst[-x - 1] = src[FFMIN(x, width - 1)];
 
         for (int x = 0; x < width; x++)
             dst[x] = src[x];
 
         for (int x = 0; x < 32; x++)
-            dst[width + x] = src[width - x - 1];
+            dst[width + x] = src[FFMAX(width - x - 1, 0)];
 
         dst += dst_stride;
         src += src_stride;
@@ -468,13 +468,13 @@ static void read_words(const uint8_t *srcp, float *dst,
 
     for (int y = 0; y < height; y++) {
         for (int x = 0; x < 32; x++)
-            dst[-x - 1] = src[x] * scale;
+            dst[-x - 1] = src[FFMIN(x, width - 1)] * scale;
 
         for (int x = 0; x < width; x++)
             dst[x] = src[x] * scale;
 
         for (int x = 0; x < 32; x++)
-            dst[width + x] = src[width - x - 1] * scale;
+            dst[width + x] = src[FFMAX(width - x - 1, 0)] * scale;
 
         dst += dst_stride;
         src += src_stride;
-- 
2.54.0


>From e54c06a2f2090dcf733c1011b9cb895743b84213 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 16:35:47 +0200
Subject: [PATCH 3/7] avfilter/vf_nnedi: fix stale prescreener flags after
 pscrn=none

The prescreener flags are only written when a prescreener is used. If
pscrn is changed to none at runtime, the flags of the previous frames
stayed in the buffer, so the predictor skipped those pixels and the
output was wrong. Clear the flags when pscrn is none.

Checked by switching pscrn with sendcmd: the frames after the switch
now match a run that started with pscrn=none.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index 5c4b47e0a2..dff5915e21 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -631,6 +631,9 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
 
+        if (!s->pscrn)
+            memset(prescreen_buf, 0, width);
+
         for (int y = 0; y < slice_end - slice_start; y += 2) {
             if (s->pscrn > 0)
                 s->prescreen[s->pscrn > 1](ctx, srcbuf + (y / 2) * 
srcbuf_stride + 32,
-- 
2.54.0


>From 0fca47b634def188c5915bcbbbab3c1032ed40c5 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 22:37:20 +0200
Subject: [PATCH 4/7] avfilter/vf_nnedi: fix the last line of planes with an
 odd height

filter_slice() split each plane into height / 2 pairs of lines, so the
last line of a plane with an odd height was never written. For 720x486
yuv420p that is the last line of both chroma planes. For very small
frames (gray with a height of 1, 4:2:0 with a height of 2 or less) no
job ran and nothing was written at all.

With odd heights the bottom edge was also clamped to a line of the
wrong field, and the output changed with the number of threads.

Use (height + 1) / 2 pairs, clamp to the first and last line of the
kept field, and always run at least one job. The output for even
heights is unchanged, except in frames where no job ran.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index dff5915e21..c00c995d70 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -551,8 +551,8 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
     for (int p = 0; p < s->nb_planes; p++) {
         const int height = s->planeheight[p];
         const int width = s->planewidth[p];
-        const int slice_start = 2 * (ff_slice_pos(height / 2, jobnr, nb_jobs));
-        const int slice_end = 2 * (ff_slice_pos(height / 2, jobnr + 1, 
nb_jobs));
+        const int slice_start = 2 * (ff_slice_pos((height + 1) / 2, jobnr, 
nb_jobs));
+        const int slice_end = FFMIN(2 * (ff_slice_pos((height + 1) / 2, jobnr 
+ 1, nb_jobs)), height);
         const uint8_t *src_data = in->data[p];
         uint8_t *dst_data = out->data[p];
         uint8_t *dst = out->data[p] + slice_start * out->linesize[p];
@@ -563,8 +563,11 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
         const int srcbuf_stride = width + 64;
         float *dstbuf = s->output_buf[jobnr];
         const int dstbuf_stride = width;
-        const int slice_height = (slice_end - slice_start) / 2;
+        const int slice_height = (slice_end - slice_start + tff) / 2;
         const int last_slice = slice_end == height;
+        // first and last line of the field that is kept
+        const int first = FFMIN(tff, height - 1);
+        const int last = FFMAX(height - 1 - ((height - 1 - tff) & 1), 0);
         const uint8_t *in_line;
         uint8_t *out_line;
         int y_out;
@@ -588,27 +591,30 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
             out_line += dst_linesize * 2;
         }
 
+        if (!slice_height)
+            continue;
+
         y_out = slice_start + ((!tff) ^ (slice_start & 1));
 
-        s->read(src_data + FFMAX(y_out - 5, tff) * src_linesize,
+        s->read(src_data + FFMAX(y_out - 5, first) * src_linesize,
                 srcbuf + 32,
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
         srcbuf += srcbuf_stride;
 
-        s->read(src_data + FFMAX(y_out - 3, tff) * src_linesize,
+        s->read(src_data + FFMAX(y_out - 3, first) * src_linesize,
                 srcbuf + 32,
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
         srcbuf += srcbuf_stride;
 
-        s->read(src_data + FFMAX(y_out - 1, tff) * src_linesize,
+        s->read(src_data + FFMAX(y_out - 1, first) * src_linesize,
                 srcbuf + 32,
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
         srcbuf += srcbuf_stride;
 
-        in_line  = src_data + FFMIN(y_out + 1, height - 1 - !tff) * 
src_linesize;
+        in_line  = src_data + FFMIN(y_out + 1, last) * src_linesize;
         out_line = dst_data + (y_out * dst_linesize);
 
         s->read(in_line, srcbuf + 32, src_linesize * 2, srcbuf_stride,
@@ -616,17 +622,17 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
 
         y_out += (slice_height - last_slice) * 2;
 
-        s->read(src_data + FFMIN(y_out + 1, height - 1 - !tff) * src_linesize,
+        s->read(src_data + FFMIN(y_out + 1, last) * src_linesize,
                 srcbuf + 32 + srcbuf_stride * (slice_height - last_slice),
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
 
-        s->read(src_data + FFMIN(y_out + 3, height - 1 - !tff) * src_linesize,
+        s->read(src_data + FFMIN(y_out + 3, last) * src_linesize,
                 srcbuf + 32 + srcbuf_stride * (slice_height + 1 - last_slice),
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
 
-        s->read(src_data + FFMIN(y_out + 5, height - 1 - !tff) * src_linesize,
+        s->read(src_data + FFMIN(y_out + 5, last) * src_linesize,
                 srcbuf + 32 + srcbuf_stride * (slice_height + 2 - last_slice),
                 src_linesize * 2, srcbuf_stride,
                 width, 1, in_scale);
@@ -634,7 +640,7 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
         if (!s->pscrn)
             memset(prescreen_buf, 0, width);
 
-        for (int y = 0; y < slice_end - slice_start; y += 2) {
+        for (int y = 0; y < 2 * slice_height; y += 2) {
             if (s->pscrn > 0)
                 s->prescreen[s->pscrn > 1](ctx, srcbuf + (y / 2) * 
srcbuf_stride + 32,
                              srcbuf_stride, prescreen_buf, width,
@@ -675,7 +681,7 @@ static int get_frame(AVFilterContext *ctx, int is_second)
     dst->pts = s->pts;
 
     ff_filter_execute(ctx, filter_slice, dst, NULL,
-                      FFMIN(s->planeheight[1] / 2, s->nb_threads));
+                      FFMIN((s->planeheight[1] + 1) / 2, s->nb_threads));
 
     if (s->field == -2 || s->field > 1)
         s->field_n = !s->field_n;
-- 
2.54.0


>From 15b5765e8ef3054c9ff6b9e9e4f807bf4cd79692 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 22:37:20 +0200
Subject: [PATCH 5/7] avfilter/vf_nnedi: read the input lines in a loop

filter_slice() read the input lines with seven separate calls: three
above the slice, one for the slice and three below. Use one loop that
clamps the line number to the kept field.

The output is unchanged.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 45 ++++++------------------------------------
 1 file changed, 6 insertions(+), 39 deletions(-)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index c00c995d70..879318b48f 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -564,7 +564,6 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
         float *dstbuf = s->output_buf[jobnr];
         const int dstbuf_stride = width;
         const int slice_height = (slice_end - slice_start + tff) / 2;
-        const int last_slice = slice_end == height;
         // first and last line of the field that is kept
         const int first = FFMIN(tff, height - 1);
         const int last = FFMAX(height - 1 - ((height - 1 - tff) & 1), 0);
@@ -595,47 +594,15 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
             continue;
 
         y_out = slice_start + ((!tff) ^ (slice_start & 1));
-
-        s->read(src_data + FFMAX(y_out - 5, first) * src_linesize,
-                srcbuf + 32,
-                src_linesize * 2, srcbuf_stride,
-                width, 1, in_scale);
-        srcbuf += srcbuf_stride;
-
-        s->read(src_data + FFMAX(y_out - 3, first) * src_linesize,
-                srcbuf + 32,
-                src_linesize * 2, srcbuf_stride,
-                width, 1, in_scale);
-        srcbuf += srcbuf_stride;
-
-        s->read(src_data + FFMAX(y_out - 1, first) * src_linesize,
-                srcbuf + 32,
-                src_linesize * 2, srcbuf_stride,
-                width, 1, in_scale);
-        srcbuf += srcbuf_stride;
-
-        in_line  = src_data + FFMIN(y_out + 1, last) * src_linesize;
         out_line = dst_data + (y_out * dst_linesize);
 
-        s->read(in_line, srcbuf + 32, src_linesize * 2, srcbuf_stride,
-                width, slice_height - last_slice, in_scale);
+        for (int i = 0; i < slice_height + 5; i++) {
+            const int y = av_clip(y_out - 5 + 2 * i, first, last);
 
-        y_out += (slice_height - last_slice) * 2;
-
-        s->read(src_data + FFMIN(y_out + 1, last) * src_linesize,
-                srcbuf + 32 + srcbuf_stride * (slice_height - last_slice),
-                src_linesize * 2, srcbuf_stride,
-                width, 1, in_scale);
-
-        s->read(src_data + FFMIN(y_out + 3, last) * src_linesize,
-                srcbuf + 32 + srcbuf_stride * (slice_height + 1 - last_slice),
-                src_linesize * 2, srcbuf_stride,
-                width, 1, in_scale);
-
-        s->read(src_data + FFMIN(y_out + 5, last) * src_linesize,
-                srcbuf + 32 + srcbuf_stride * (slice_height + 2 - last_slice),
-                src_linesize * 2, srcbuf_stride,
-                width, 1, in_scale);
+            s->read(src_data + y * src_linesize, srcbuf + 32 + i * 
srcbuf_stride,
+                    src_linesize * 2, srcbuf_stride, width, 1, in_scale);
+        }
+        srcbuf += 3 * srcbuf_stride;
 
         if (!s->pscrn)
             memset(prescreen_buf, 0, width);
-- 
2.54.0


>From 79c35edb66fc9abf71eb03ad20506adc85a8f64f Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 22:37:20 +0200
Subject: [PATCH 6/7] avfilter/vf_nnedi: write each output line as soon as it
 is computed

Compute and write one line at a time instead of filling a float buffer
for the whole slice first. The read and write helpers now work on one
line, so they no longer need strides and a height.

The output is unchanged.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 125 +++++++++++++----------------------------
 1 file changed, 40 insertions(+), 85 deletions(-)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index 879318b48f..bad1b669cd 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -103,11 +103,9 @@ typedef struct NNEDIContext {
     float **output_buf;
 
     void (*read)(const uint8_t *src, float *dst,
-                 int src_stride, int dst_stride,
-                 int width, int height, float scale);
+                 int width, float scale);
     void (*write)(const float *src, uint8_t *dst,
-                  int src_stride, int dst_stride,
-                  int width, int height, int depth, float scale);
+                  int width, int depth, float scale);
     void (*prescreen[2])(AVFilterContext *ctx,
                          const void *src, ptrdiff_t src_stride,
                          uint8_t *prescreen, int N,
@@ -440,77 +438,47 @@ static void predictor(AVFilterContext *ctx,
 }
 
 static void read_bytes(const uint8_t *src, float *dst,
-                       int src_stride, int dst_stride,
-                       int width, int height, float scale)
+                       int width, float scale)
 {
-    for (int y = 0; y < height; y++) {
-        for (int x = 0; x < 32; x++)
-            dst[-x - 1] = src[FFMIN(x, width - 1)];
+    for (int x = 0; x < 32; x++)
+        dst[-x - 1] = src[FFMIN(x, width - 1)];
 
-        for (int x = 0; x < width; x++)
-            dst[x] = src[x];
+    for (int x = 0; x < width; x++)
+        dst[x] = src[x];
 
-        for (int x = 0; x < 32; x++)
-            dst[width + x] = src[FFMAX(width - x - 1, 0)];
-
-        dst += dst_stride;
-        src += src_stride;
-    }
+    for (int x = 0; x < 32; x++)
+        dst[width + x] = src[FFMAX(width - x - 1, 0)];
 }
 
 static void read_words(const uint8_t *srcp, float *dst,
-                       int src_stride, int dst_stride,
-                       int width, int height, float scale)
+                       int width, float scale)
 {
     const uint16_t *src = (const uint16_t *)srcp;
 
-    src_stride /= 2;
+    for (int x = 0; x < 32; x++)
+        dst[-x - 1] = src[FFMIN(x, width - 1)] * scale;
 
-    for (int y = 0; y < height; y++) {
-        for (int x = 0; x < 32; x++)
-            dst[-x - 1] = src[FFMIN(x, width - 1)] * scale;
+    for (int x = 0; x < width; x++)
+        dst[x] = src[x] * scale;
 
-        for (int x = 0; x < width; x++)
-            dst[x] = src[x] * scale;
-
-        for (int x = 0; x < 32; x++)
-            dst[width + x] = src[FFMAX(width - x - 1, 0)] * scale;
-
-        dst += dst_stride;
-        src += src_stride;
-    }
+    for (int x = 0; x < 32; x++)
+        dst[width + x] = src[FFMAX(width - x - 1, 0)] * scale;
 }
 
 static void write_bytes(const float *src, uint8_t *dst,
-                        int src_stride, int dst_stride,
-                        int width, int height, int depth,
-                        float scale)
+                        int width, int depth, float scale)
 {
-    for (int y = 0; y < height; y++) {
-        for (int x = 0; x < width; x++)
-            dst[x] = av_clip_uint8(src[x]);
-
-        dst += dst_stride;
-        src += src_stride;
-    }
+    for (int x = 0; x < width; x++)
+        dst[x] = av_clip_uint8(src[x]);
 }
 
 static void write_words(const float *src, uint8_t *dstp,
-                        int src_stride, int dst_stride,
-                        int width, int height, int depth,
-                        float scale)
+                        int width, int depth, float scale)
 {
     uint16_t *dst = (uint16_t *)dstp;
 
-    dst_stride /= 2;
-
-    for (int y = 0; y < height; y++) {
-        for (int x = 0; x < width; x++)
-            dst[x] = av_clip_uintp2_c(src[x] * scale, depth);
-
-        dst += dst_stride;
-        src += src_stride;
-    }
+    for (int x = 0; x < width; x++)
+        dst[x] = av_clip_uintp2_c(src[x] * scale, depth);
 }
 
 static void interpolation(const void *src, ptrdiff_t src_stride,
@@ -559,11 +527,11 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
         const int src_linesize = in->linesize[p];
         const int dst_linesize = out->linesize[p];
         uint8_t *prescreen_buf = s->prescreen_buf[jobnr];
-        float *srcbuf = s->input_buf[jobnr];
+        float *srcbuf = s->input_buf[jobnr] + 32;
         const int srcbuf_stride = width + 64;
         float *dstbuf = s->output_buf[jobnr];
-        const int dstbuf_stride = width;
-        const int slice_height = (slice_end - slice_start + tff) / 2;
+        const int y0 = slice_start + !tff;
+        const int nb_lines = (slice_end - y0 + 1) / 2;
         // first and last line of the field that is kept
         const int first = FFMIN(tff, height - 1);
         const int last = FFMAX(height - 1 - ((height - 1 - tff) & 1), 0);
@@ -590,45 +558,32 @@ static int filter_slice(AVFilterContext *ctx, void *arg, 
int jobnr, int nb_jobs)
             out_line += dst_linesize * 2;
         }
 
-        if (!slice_height)
-            continue;
+        for (int i = 0; i < nb_lines + 5; i++) {
+            const int y = av_clip(y0 - 5 + 2 * i, first, last);
 
-        y_out = slice_start + ((!tff) ^ (slice_start & 1));
-        out_line = dst_data + (y_out * dst_linesize);
-
-        for (int i = 0; i < slice_height + 5; i++) {
-            const int y = av_clip(y_out - 5 + 2 * i, first, last);
-
-            s->read(src_data + y * src_linesize, srcbuf + 32 + i * 
srcbuf_stride,
-                    src_linesize * 2, srcbuf_stride, width, 1, in_scale);
+            s->read(src_data + y * src_linesize, srcbuf + i * srcbuf_stride,
+                    width, in_scale);
         }
-        srcbuf += 3 * srcbuf_stride;
 
         if (!s->pscrn)
             memset(prescreen_buf, 0, width);
 
-        for (int y = 0; y < 2 * slice_height; y += 2) {
-            if (s->pscrn > 0)
-                s->prescreen[s->pscrn > 1](ctx, srcbuf + (y / 2) * 
srcbuf_stride + 32,
-                             srcbuf_stride, prescreen_buf, width,
-                             &s->prescreener[s->pscrn - 1]);
+        for (int i = 0; i < nb_lines; i++) {
+            const float *src = srcbuf + (i + 3) * srcbuf_stride;
 
-            predictor(ctx,
-                      srcbuf + (y / 2) * srcbuf_stride + 32,
-                      srcbuf_stride,
-                      dstbuf + (y / 2) * dstbuf_stride,
-                      prescreen_buf, width,
+            if (s->pscrn > 0)
+                s->prescreen[s->pscrn > 1](ctx, src, srcbuf_stride, 
prescreen_buf, width,
+                                           &s->prescreener[s->pscrn - 1]);
+
+            predictor(ctx, src, srcbuf_stride, dstbuf, prescreen_buf, width,
                       &s->coeffs[s->etype][s->nnsparam][s->nsize], s->qual == 
2);
 
             if (s->pscrn > 0)
-                interpolation(srcbuf + (y / 2) * srcbuf_stride + 32,
-                              srcbuf_stride,
-                              dstbuf + (y / 2) * dstbuf_stride,
-                              prescreen_buf, width);
-        }
+                interpolation(src, srcbuf_stride, dstbuf, prescreen_buf, 
width);
 
-        s->write(dstbuf, out_line, dstbuf_stride, dst_linesize * 2,
-                 width, slice_height, depth, out_scale);
+            s->write(dstbuf, dst_data + (y0 + 2 * i) * dst_linesize,
+                     width, depth, out_scale);
+        }
     }
 
     return 0;
-- 
2.54.0


>From 8217e3ab487cc06649e14acbd12bf31db9957873 Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Fri, 16 Oct 2026 22:37:20 +0200
Subject: [PATCH 7/7] avfilter/vf_nnedi: use smaller per-thread buffers

The output buffer only needs one line now, and the input buffer half a
frame plus five lines. Both were as big as a whole frame.

For 1080p yuv420p this saves about 115 MB with 10 filter threads and
about 4 MB with one. The output is unchanged.

Found during a code review sweep of ffmpeg using an LLM.

Signed-off-by: romainhedouin <[email protected]>
---
 libavfilter/vf_nnedi.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libavfilter/vf_nnedi.c b/libavfilter/vf_nnedi.c
index bad1b669cd..78a548c81f 100644
--- a/libavfilter/vf_nnedi.c
+++ b/libavfilter/vf_nnedi.c
@@ -1001,7 +1001,7 @@ static int config_input(AVFilterLink *inlink)
         }
     }
 
-    s->input_size = (s->planewidth[0] + 64) * (s->planeheight[0] + 6);
+    s->input_size = (s->planewidth[0] + 64) * ((s->planeheight[0] + 1) / 2 + 
5);
     s->input_buf = av_calloc(s->nb_threads, sizeof(*s->input_buf));
     if (!s->input_buf)
         return AVERROR(ENOMEM);
@@ -1017,7 +1017,7 @@ static int config_input(AVFilterLink *inlink)
         return AVERROR(ENOMEM);
 
     for (int i = 0; i < s->nb_threads; i++) {
-        s->output_buf[i] = av_calloc(s->input_size, sizeof(**s->output_buf));
+        s->output_buf[i] = av_calloc(s->planewidth[0], 
sizeof(**s->output_buf));
         if (!s->output_buf[i])
             return AVERROR(ENOMEM);
     }
-- 
2.54.0

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

Reply via email to