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;