Commit b8e6f74a6b for ffmpeg

commit b8e6f74a6b3ff464a093cfc4877c9cc61a9da0a3
Author: romainhedouin <romain.hedouin@epitech.eu>
Date:   Fri Oct 16 22:37:20 2026 +0200

    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 <romain.hedouin@epitech.eu>

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;