PR #22427 opened by Ramiro Polla (ramiro) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22427 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22427.patch
Includes two commits from @haasn from !22293, some cleanup, and a speedup by removing redundant `ref->src` conversions. >From 89b2f84eb0e83f7446386b42d0949ce9c7ce7cb9 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 18 Feb 2026 15:07:47 +0100 Subject: [PATCH 01/11] tests/swscale: exclude init time from benchmark This was originally intended to also include performance gains/losses due to complicated setup logic, but in practice it just means that changing the number of iterations dramatically affects the measured speedup; which makes it harder to do quick bench runs during development. --- libswscale/tests/swscale.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 6f6240e4b6..63f566ba18 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -193,7 +193,7 @@ static float get_loss(const float ssim[4]) } static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, - struct options opts) + struct options opts, int64_t *out_time) { SwsContext *sws_legacy; int ret; @@ -215,8 +215,10 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, if ((ret = sws_init_context(sws_legacy, NULL, NULL)) < 0) goto error; + int64_t time = av_gettime_relative(); for (int i = 0; ret >= 0 && i < opts.iters; i++) ret = sws_scale_frame(sws_legacy, dst, src); + *out_time = av_gettime_relative() - time; error: sws_freeContext(sws_legacy); @@ -273,6 +275,12 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, sws[1]->dither = mode.dither; sws[1]->threads = opts.threads; + if (sws_frame_setup(sws[1], dst, src) < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to setup %s ---> %s\n", + av_get_pix_fmt_name(src->format), av_get_pix_fmt_name(dst->format)); + goto error; + } + time = av_gettime_relative(); for (int i = 0; i < opts.iters; i++) { @@ -316,13 +324,11 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, if (!ssim_ref && sws_isSupportedInput(src->format) && sws_isSupportedOutput(dst->format)) { /* Compare against the legacy swscale API as a reference */ - time_ref = av_gettime_relative(); - if (scale_legacy(dst, src, mode, opts) < 0) { + if (scale_legacy(dst, src, mode, opts, &time_ref) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed ref %s ---> %s\n", av_get_pix_fmt_name(src->format), av_get_pix_fmt_name(dst->format)); goto error; } - time_ref = av_gettime_relative() - time_ref; if (sws_scale_frame(sws[2], out, dst) < 0) goto error; -- 2.52.0 >From 1121523fc1e9fdd1e9d812bc3ae9f7e1c8c22327 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 22 Feb 2026 19:47:58 +0100 Subject: [PATCH 02/11] tests/swscale: unref buffers before each iteration Otherwise, we always pass frames that already have buffers allocated, which breaks the no-op refcopy optimizations. Testing with -p 0.1 -threads 16 -bench 10, on an AMD Ryzen 9 9950X3D: Before: Overall speedup=2.776x faster, min=0.133x max=629.496x yuv444p 1920x1080 -> yuv444p 1920x1080, flags=0x100000 dither=1 time=9 us, ref=9 us, speedup=1.043x faster After: Overall speedup=2.721x faster, min=0.140x max=574.034x yuv444p 1920x1080 -> yuv444p 1920x1080, flags=0x100000 dither=1 time=0 us, ref=28 us, speedup=516.504x faster (The slowdown in the legacy swscale case is from swscale's lack of a no-op refcopy optimizaton, plus the fact that it's now actually doing memory work instead of a no-op / redundant memset) Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 63f566ba18..8001f0d105 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -192,6 +192,18 @@ static float get_loss(const float ssim[4]) return 1.0 - sum; } +static void unref_buffers(AVFrame *frame) +{ + for (int i = 0; i < FF_ARRAY_ELEMS(frame->buf); i++) { + if (!frame->buf[i]) + break; + av_buffer_unref(&frame->buf[i]); + } + + memset(frame->data, 0, sizeof(frame->data)); + memset(frame->linesize, 0, sizeof(frame->linesize)); +} + static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, struct options opts, int64_t *out_time) { @@ -216,8 +228,10 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, goto error; int64_t time = av_gettime_relative(); - for (int i = 0; ret >= 0 && i < opts.iters; i++) + for (int i = 0; ret >= 0 && i < opts.iters; i++) { + unref_buffers(dst); ret = sws_scale_frame(sws_legacy, dst, src); + } *out_time = av_gettime_relative() - time; error: @@ -284,6 +298,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, time = av_gettime_relative(); for (int i = 0; i < opts.iters; i++) { + unref_buffers(dst); if (sws_scale_frame(sws[1], dst, src) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", av_get_pix_fmt_name(src->format), av_get_pix_fmt_name(dst->format)); -- 2.52.0 >From 700977c021dc7648c36b87b8d29ab3445974ae63 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Fri, 6 Mar 2026 22:37:37 +0100 Subject: [PATCH 03/11] swscale/tests/swscale: always allocate frame in scale_legacy() Legacy swscale may overwrite the pixel formats in the context (see handle_formats() in libswscale/utils.c). This may lead to an issue where, when sws_frame_start() allocates a new frame, it uses the wrong pixel format. Instead of fixing the issue in swscale, just make sure dst is always allocated prior to calling the legacy scaler. Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/swscale.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 8001f0d105..dc0e369d19 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -224,14 +224,20 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, sws_legacy->dither = mode.dither; sws_legacy->threads = opts.threads; + av_frame_unref(dst); + dst->width = sws_legacy->dst_w; + dst->height = sws_legacy->dst_h; + dst->format = sws_legacy->dst_format; + ret = av_frame_get_buffer(dst, 0); + if (ret < 0) + return ret; + if ((ret = sws_init_context(sws_legacy, NULL, NULL)) < 0) goto error; int64_t time = av_gettime_relative(); - for (int i = 0; ret >= 0 && i < opts.iters; i++) { - unref_buffers(dst); + for (int i = 0; ret >= 0 && i < opts.iters; i++) ret = sws_scale_frame(sws_legacy, dst, src); - } *out_time = av_gettime_relative() - time; error: -- 2.52.0 >From 2f7db05195d2ca7051ed6a1ffa5efe42426f6641 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Thu, 5 Mar 2026 21:03:27 +0100 Subject: [PATCH 04/11] swscale/tests/swscale: remove pointless hardcoded dimension checks This is a test tool. If the user wants to create a malformed reference file and use it that's their problem. This reverts f70a651b3f7 and c0f0bec2f20. --- libswscale/tests/swscale.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index dc0e369d19..6ca15dfb68 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -500,7 +500,7 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts) src_fmt = av_get_pix_fmt(src_fmt_str); dst_fmt = av_get_pix_fmt(dst_fmt_str); if (src_fmt == AV_PIX_FMT_NONE || dst_fmt == AV_PIX_FMT_NONE || - sw != ref->width || sh != ref->height || dw > 8192 || dh > 8192 || + sw != ref->width || sh != ref->height || mode.dither >= SWS_DITHER_NB) { av_log(NULL, AV_LOG_FATAL, "malformed input file\n"); return -1; -- 2.52.0 >From dd5e952e03e6e2e5094398e4bcffd79bec32105c Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Fri, 27 Feb 2026 22:12:31 +0100 Subject: [PATCH 05/11] swscale/tests/swscale: split parse_options() out of main() Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/swscale.c | 88 +++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 6ca15dfb68..b7db021446 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -517,25 +517,8 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts) return 0; } -int main(int argc, char **argv) +static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) { - struct options opts = { - .src_fmt = AV_PIX_FMT_NONE, - .dst_fmt = AV_PIX_FMT_NONE, - .w = 96, - .h = 96, - .threads = 1, - .iters = 1, - .prob = 1.0, - .flags = -1, - .dither = -1, - }; - - AVFrame *rgb = NULL, *ref = NULL; - FILE *fp = NULL; - AVLFG rand; - int ret = -1; - for (int i = 1; i < argc; i += 2) { if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) { fprintf(stderr, @@ -571,63 +554,88 @@ int main(int argc, char **argv) if (argv[i][0] != '-' || i + 1 == argc) goto bad_option; if (!strcmp(argv[i], "-ref")) { - fp = fopen(argv[i + 1], "r"); - if (!fp) { + *fp = fopen(argv[i + 1], "r"); + if (!*fp) { fprintf(stderr, "could not open '%s'\n", argv[i + 1]); - goto error; + return -1; } } else if (!strcmp(argv[i], "-cpuflags")) { unsigned flags = av_get_cpu_flags(); int res = av_parse_cpu_caps(&flags, argv[i + 1]); if (res < 0) { fprintf(stderr, "invalid cpu flags %s\n", argv[i + 1]); - goto error; + return -1; } av_force_cpu_flags(flags); } else if (!strcmp(argv[i], "-src")) { - opts.src_fmt = av_get_pix_fmt(argv[i + 1]); - if (opts.src_fmt == AV_PIX_FMT_NONE) { + opts->src_fmt = av_get_pix_fmt(argv[i + 1]); + if (opts->src_fmt == AV_PIX_FMT_NONE) { fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]); - goto error; + return -1; } } else if (!strcmp(argv[i], "-dst")) { - opts.dst_fmt = av_get_pix_fmt(argv[i + 1]); - if (opts.dst_fmt == AV_PIX_FMT_NONE) { + opts->dst_fmt = av_get_pix_fmt(argv[i + 1]); + if (opts->dst_fmt == AV_PIX_FMT_NONE) { fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]); - goto error; + return -1; } } else if (!strcmp(argv[i], "-bench")) { - opts.bench = 1; - opts.iters = atoi(argv[i + 1]); - opts.iters = FFMAX(opts.iters, 1); - opts.w = 1920; - opts.h = 1080; + opts->bench = 1; + opts->iters = atoi(argv[i + 1]); + opts->iters = FFMAX(opts->iters, 1); + opts->w = 1920; + opts->h = 1080; } else if (!strcmp(argv[i], "-flags")) { SwsContext *dummy = sws_alloc_context(); const AVOption *flags_opt = av_opt_find(dummy, "sws_flags", NULL, 0, 0); - ret = av_opt_eval_flags(dummy, flags_opt, argv[i + 1], &opts.flags); + int ret = av_opt_eval_flags(dummy, flags_opt, argv[i + 1], &opts->flags); sws_free_context(&dummy); if (ret < 0) { fprintf(stderr, "invalid flags %s\n", argv[i + 1]); - goto error; + return -1; } } else if (!strcmp(argv[i], "-dither")) { - opts.dither = atoi(argv[i + 1]); + opts->dither = atoi(argv[i + 1]); } else if (!strcmp(argv[i], "-unscaled")) { - opts.unscaled = atoi(argv[i + 1]); + opts->unscaled = atoi(argv[i + 1]); } else if (!strcmp(argv[i], "-threads")) { - opts.threads = atoi(argv[i + 1]); + opts->threads = atoi(argv[i + 1]); } else if (!strcmp(argv[i], "-p")) { - opts.prob = atof(argv[i + 1]); + opts->prob = atof(argv[i + 1]); } else if (!strcmp(argv[i], "-v")) { av_log_set_level(atoi(argv[i + 1])); } else { bad_option: fprintf(stderr, "bad option or argument missing (%s) see -help\n", argv[i]); - goto error; + return -1; } } + return 0; +} + +int main(int argc, char **argv) +{ + struct options opts = { + .src_fmt = AV_PIX_FMT_NONE, + .dst_fmt = AV_PIX_FMT_NONE, + .w = 96, + .h = 96, + .threads = 1, + .iters = 1, + .prob = 1.0, + .flags = -1, + .dither = -1, + }; + + AVFrame *rgb = NULL, *ref = NULL; + FILE *fp = NULL; + AVLFG rand; + int ret = -1; + + if (parse_options(argc, argv, &opts, &fp) < 0) + goto error; + ff_sfc64_init(&prng_state, 0, 0, 0, 12); av_lfg_init(&rand, 1); signal(SIGINT, exit_handler); -- 2.52.0 >From 6c8ed583ef5c523c060f361156e995bdbf862ff3 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Fri, 27 Feb 2026 22:14:25 +0100 Subject: [PATCH 06/11] swscale/tests/swscale: split init_ref() out of main() Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/swscale.c | 54 ++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index b7db021446..bc960a9be6 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -517,6 +517,39 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts) return 0; } +static int init_ref(AVFrame *ref, const struct options *opts) +{ + SwsContext *ctx = sws_alloc_context(); + AVFrame *rgb = av_frame_alloc(); + AVLFG rand; + int ret = -1; + + if (!ctx || !rgb) + goto error; + + rgb->width = opts->w / 12; + rgb->height = opts->h / 12; + rgb->format = AV_PIX_FMT_RGBA; + if (av_frame_get_buffer(rgb, 32) < 0) + goto error; + + av_lfg_init(&rand, 1); + for (int y = 0; y < rgb->height; y++) { + for (int x = 0; x < rgb->width; x++) { + for (int c = 0; c < 4; c++) + rgb->data[0][y * rgb->linesize[0] + x * 4 + c] = av_lfg_get(&rand); + } + } + + ctx->flags = SWS_BILINEAR; + ret = sws_scale_frame(ctx, ref, rgb); + +error: + sws_free_context(&ctx); + av_frame_free(&rgb); + return ret; +} + static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) { for (int i = 1; i < argc; i += 2) { @@ -628,7 +661,7 @@ int main(int argc, char **argv) .dither = -1, }; - AVFrame *rgb = NULL, *ref = NULL; + AVFrame *ref = NULL; FILE *fp = NULL; AVLFG rand; int ret = -1; @@ -647,22 +680,6 @@ int main(int argc, char **argv) sws[i]->flags = SWS_BILINEAR; } - rgb = av_frame_alloc(); - if (!rgb) - goto error; - rgb->width = opts.w / 12; - rgb->height = opts.h / 12; - rgb->format = AV_PIX_FMT_RGBA; - if (av_frame_get_buffer(rgb, 32) < 0) - goto error; - - for (int y = 0; y < rgb->height; y++) { - for (int x = 0; x < rgb->width; x++) { - for (int c = 0; c < 4; c++) - rgb->data[0][y * rgb->linesize[0] + x * 4 + c] = av_lfg_get(&rand); - } - } - ref = av_frame_alloc(); if (!ref) goto error; @@ -670,7 +687,7 @@ int main(int argc, char **argv) ref->height = opts.h; ref->format = AV_PIX_FMT_YUVA444P; - if (sws_scale_frame(sws[0], ref, rgb) < 0) + if (init_ref(ref, &opts) < 0) goto error; ret = fp ? run_file_tests(ref, fp, opts) @@ -680,7 +697,6 @@ int main(int argc, char **argv) error: for (int i = 0; i < 3; i++) sws_free_context(&sws[i]); - av_frame_free(&rgb); av_frame_free(&ref); if (fp) fclose(fp); -- 2.52.0 >From 82406734c1f0f26553da70c806781722297046ab Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Tue, 23 Dec 2025 21:52:02 +0100 Subject: [PATCH 07/11] swscale/tests/swscale: pass opts and mode arguments as const pointers --- libswscale/tests/swscale.c | 80 +++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index bc960a9be6..03c8a22c0c 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -204,8 +204,9 @@ static void unref_buffers(AVFrame *frame) memset(frame->linesize, 0, sizeof(frame->linesize)); } -static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, - struct options opts, int64_t *out_time) +static int scale_legacy(AVFrame *dst, const AVFrame *src, + const struct mode *mode, const struct options *opts, + int64_t *out_time) { SwsContext *sws_legacy; int ret; @@ -220,9 +221,9 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, sws_legacy->dst_w = dst->width; sws_legacy->dst_h = dst->height; sws_legacy->dst_format = dst->format; - sws_legacy->flags = mode.flags; - sws_legacy->dither = mode.dither; - sws_legacy->threads = opts.threads; + sws_legacy->flags = mode->flags; + sws_legacy->dither = mode->dither; + sws_legacy->threads = opts->threads; av_frame_unref(dst); dst->width = sws_legacy->dst_w; @@ -236,7 +237,7 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode, goto error; int64_t time = av_gettime_relative(); - for (int i = 0; ret >= 0 && i < opts.iters; i++) + for (int i = 0; ret >= 0 && i < opts->iters; i++) ret = sws_scale_frame(sws_legacy, dst, src); *out_time = av_gettime_relative() - time; @@ -247,7 +248,8 @@ error: /* Runs a series of ref -> src -> dst -> out, and compares out vs ref */ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, - int dst_w, int dst_h, struct mode mode, struct options opts, + int dst_w, int dst_h, + const struct mode *mode, const struct options *opts, const AVFrame *ref, const float ssim_ref[4]) { AVFrame *src = NULL, *dst = NULL, *out = NULL; @@ -291,9 +293,9 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, goto error; } - sws[1]->flags = mode.flags; - sws[1]->dither = mode.dither; - sws[1]->threads = opts.threads; + sws[1]->flags = mode->flags; + sws[1]->dither = mode->dither; + sws[1]->threads = opts->threads; if (sws_frame_setup(sws[1], dst, src) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to setup %s ---> %s\n", @@ -303,7 +305,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, time = av_gettime_relative(); - for (int i = 0; i < opts.iters; i++) { + for (int i = 0; i < opts->iters; i++) { unref_buffers(dst); if (sws_scale_frame(sws[1], dst, src) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", @@ -324,7 +326,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, av_log(NULL, AV_LOG_INFO, "%s %dx%d -> %s %3dx%3d, flags=0x%x dither=%u\n", av_get_pix_fmt_name(src->format), src->width, src->height, av_get_pix_fmt_name(dst->format), dst->width, dst->height, - mode.flags, mode.dither); + mode->flags, mode->dither); av_log(NULL, AV_LOG_VERBOSE - 4, " SSIM {Y=%f U=%f V=%f A=%f}\n", ssim[0], ssim[1], ssim[2], ssim[3]); @@ -336,7 +338,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, av_log(NULL, level, "%s %dx%d -> %s %3dx%3d, flags=0x%x dither=%u\n", av_get_pix_fmt_name(src->format), src->width, src->height, av_get_pix_fmt_name(dst->format), dst->width, dst->height, - mode.flags, mode.dither); + mode->flags, mode->dither); av_log(NULL, level, " loss %g is %s by %g, expected loss %g\n", loss, bad ? "WORSE" : "worse", loss - expected_loss, expected_loss); if (bad) @@ -384,7 +386,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, } } - if (opts.bench && time_ref) { + if (opts->bench && time_ref) { double ratio = (double) time_ref / time; if (FFMIN(time, time_ref) > 100 /* don't pollute stats with low precision */) { speedup_min = FFMIN(speedup_min, ratio); @@ -395,11 +397,11 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, if (av_log_get_level() >= AV_LOG_INFO) { printf(" time=%"PRId64" us, ref=%"PRId64" us, speedup=%.3fx %s%s\033[0m\n", - time / opts.iters, time_ref / opts.iters, ratio, + time / opts->iters, time_ref / opts->iters, ratio, speedup_color(ratio), ratio >= 1.0 ? "faster" : "slower"); } - } else if (opts.bench) { - av_log(NULL, AV_LOG_INFO, " time=%"PRId64" us\n", time / opts.iters); + } else if (opts->bench) { + av_log(NULL, AV_LOG_INFO, " time=%"PRId64" us\n", time / opts->iters); } fflush(stdout); @@ -417,10 +419,10 @@ static inline int fmt_is_subsampled(enum AVPixelFormat fmt) av_pix_fmt_desc_get(fmt)->log2_chroma_h != 0; } -static int run_self_tests(const AVFrame *ref, struct options opts) +static int run_self_tests(const AVFrame *ref, const struct options *opts) { - const int dst_w[] = { opts.w, opts.w - opts.w / 3, opts.w + opts.w / 3 }; - const int dst_h[] = { opts.h, opts.h - opts.h / 3, opts.h + opts.h / 3 }; + const int dst_w[] = { opts->w, opts->w - opts->w / 3, opts->w + opts->w / 3 }; + const int dst_h[] = { opts->h, opts->h - opts->h / 3, opts->h + opts->h / 3 }; enum AVPixelFormat src_fmt, dst_fmt, src_fmt_min = 0, @@ -428,18 +430,18 @@ static int run_self_tests(const AVFrame *ref, struct options opts) src_fmt_max = AV_PIX_FMT_NB - 1, dst_fmt_max = AV_PIX_FMT_NB - 1; - if (opts.src_fmt != AV_PIX_FMT_NONE) - src_fmt_min = src_fmt_max = opts.src_fmt; - if (opts.dst_fmt != AV_PIX_FMT_NONE) - dst_fmt_min = dst_fmt_max = opts.dst_fmt; + if (opts->src_fmt != AV_PIX_FMT_NONE) + src_fmt_min = src_fmt_max = opts->src_fmt; + if (opts->dst_fmt != AV_PIX_FMT_NONE) + dst_fmt_min = dst_fmt_max = opts->dst_fmt; for (src_fmt = src_fmt_min; src_fmt <= src_fmt_max; src_fmt++) { - if (opts.unscaled && fmt_is_subsampled(src_fmt)) + if (opts->unscaled && fmt_is_subsampled(src_fmt)) continue; if (!sws_test_format(src_fmt, 0) || !sws_test_format(src_fmt, 1)) continue; for (dst_fmt = dst_fmt_min; dst_fmt <= dst_fmt_max; dst_fmt++) { - if (opts.unscaled && fmt_is_subsampled(dst_fmt)) + if (opts->unscaled && fmt_is_subsampled(dst_fmt)) continue; if (!sws_test_format(dst_fmt, 0) || !sws_test_format(dst_fmt, 1)) continue; @@ -447,24 +449,24 @@ static int run_self_tests(const AVFrame *ref, struct options opts) for (int w = 0; w < FF_ARRAY_ELEMS(dst_w); w++) { for (int f = 0; f < FF_ARRAY_ELEMS(flags); f++) { struct mode mode = { - .flags = opts.flags >= 0 ? opts.flags : flags[f], - .dither = opts.dither >= 0 ? opts.dither : SWS_DITHER_AUTO, + .flags = opts->flags >= 0 ? opts->flags : flags[f], + .dither = opts->dither >= 0 ? opts->dither : SWS_DITHER_AUTO, }; - if (ff_sfc64_get(&prng_state) > UINT64_MAX * opts.prob) + if (ff_sfc64_get(&prng_state) > UINT64_MAX * opts->prob) continue; if (run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h], - mode, opts, ref, NULL) < 0) + &mode, opts, ref, NULL) < 0) return -1; - if (opts.flags >= 0 || opts.unscaled) + if (opts->flags >= 0 || opts->unscaled) break; } - if (opts.unscaled) + if (opts->unscaled) break; } - if (opts.unscaled) + if (opts->unscaled) break; } } @@ -473,7 +475,7 @@ static int run_self_tests(const AVFrame *ref, struct options opts) return 0; } -static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts) +static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *opts) { char buf[256]; int ret; @@ -506,11 +508,11 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts) return -1; } - if (opts.src_fmt != AV_PIX_FMT_NONE && src_fmt != opts.src_fmt || - opts.dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts.dst_fmt) + if (opts->src_fmt != AV_PIX_FMT_NONE && src_fmt != opts->src_fmt || + opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt) continue; - if (run_test(src_fmt, dst_fmt, dw, dh, mode, opts, ref, ssim) < 0) + if (run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, ssim) < 0) return -1; } @@ -690,8 +692,8 @@ int main(int argc, char **argv) if (init_ref(ref, &opts) < 0) goto error; - ret = fp ? run_file_tests(ref, fp, opts) - : run_self_tests(ref, opts); + ret = fp ? run_file_tests(ref, fp, &opts) + : run_self_tests(ref, &opts); /* fall through */ error: -- 2.52.0 >From 704329aead4807a195b6c8b23e1b91e7b845a860 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Tue, 23 Dec 2025 21:03:35 +0100 Subject: [PATCH 08/11] swscale/tests/swscale: give names to SwsContext variables --- libswscale/tests/swscale.c | 41 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 03c8a22c0c..c86b99071f 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -70,7 +70,11 @@ const SwsFlags flags[] = { }; static FFSFC64 prng_state; -static SwsContext *sws[3]; /* reused between tests for efficiency */ + +/* reused between tests for efficiency */ +static SwsContext *sws_ref_src; +static SwsContext *sws_src_dst; +static SwsContext *sws_dst_out; static double speedup_logavg; static double speedup_min = 1e10; @@ -287,17 +291,17 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, dst->width = dst_w; dst->height = dst_h; - if (sws_scale_frame(sws[0], src, ref) < 0) { + if (sws_scale_frame(sws_ref_src, src, ref) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", av_get_pix_fmt_name(ref->format), av_get_pix_fmt_name(src->format)); goto error; } - sws[1]->flags = mode->flags; - sws[1]->dither = mode->dither; - sws[1]->threads = opts->threads; + sws_src_dst->flags = mode->flags; + sws_src_dst->dither = mode->dither; + sws_src_dst->threads = opts->threads; - if (sws_frame_setup(sws[1], dst, src) < 0) { + if (sws_frame_setup(sws_src_dst, dst, src) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to setup %s ---> %s\n", av_get_pix_fmt_name(src->format), av_get_pix_fmt_name(dst->format)); goto error; @@ -307,7 +311,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, for (int i = 0; i < opts->iters; i++) { unref_buffers(dst); - if (sws_scale_frame(sws[1], dst, src) < 0) { + if (sws_scale_frame(sws_src_dst, dst, src) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", av_get_pix_fmt_name(src->format), av_get_pix_fmt_name(dst->format)); goto error; @@ -316,7 +320,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, time = av_gettime_relative() - time; - if (sws_scale_frame(sws[2], out, dst) < 0) { + if (sws_scale_frame(sws_dst_out, out, dst) < 0) { av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", av_get_pix_fmt_name(dst->format), av_get_pix_fmt_name(out->format)); goto error; @@ -353,7 +357,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, goto error; } - if (sws_scale_frame(sws[2], out, dst) < 0) + if (sws_scale_frame(sws_dst_out, out, dst) < 0) goto error; get_ssim(ssim_sws, out, ref, comps); @@ -675,12 +679,14 @@ int main(int argc, char **argv) av_lfg_init(&rand, 1); signal(SIGINT, exit_handler); - for (int i = 0; i < 3; i++) { - sws[i] = sws_alloc_context(); - if (!sws[i]) - goto error; - sws[i]->flags = SWS_BILINEAR; - } + sws_ref_src = sws_alloc_context(); + sws_src_dst = sws_alloc_context(); + sws_dst_out = sws_alloc_context(); + if (!sws_ref_src || !sws_src_dst || !sws_dst_out) + goto error; + sws_ref_src->flags = SWS_BILINEAR; + sws_src_dst->flags = SWS_BILINEAR; + sws_dst_out->flags = SWS_BILINEAR; ref = av_frame_alloc(); if (!ref) @@ -697,8 +703,9 @@ int main(int argc, char **argv) /* fall through */ error: - for (int i = 0; i < 3; i++) - sws_free_context(&sws[i]); + sws_free_context(&sws_ref_src); + sws_free_context(&sws_src_dst); + sws_free_context(&sws_dst_out); av_frame_free(&ref); if (fp) fclose(fp); -- 2.52.0 >From 7af3c3c06577d78b80b210c4e3d796d49644f1f1 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Mon, 2 Mar 2026 16:21:29 +0100 Subject: [PATCH 09/11] swscale/tests/swscale: make auxiliary conversions bitexact This prevents the propagation of dither_error across frames, and should also improve reproducibility across platforms. Also remove setting of flags for sws_src_dst early on, since it will inevitable be overwritten during the tests. Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/swscale.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index c86b99071f..e2367c7127 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -547,7 +547,7 @@ static int init_ref(AVFrame *ref, const struct options *opts) } } - ctx->flags = SWS_BILINEAR; + ctx->flags = SWS_BILINEAR | SWS_BITEXACT; ret = sws_scale_frame(ctx, ref, rgb); error: @@ -684,9 +684,8 @@ int main(int argc, char **argv) sws_dst_out = sws_alloc_context(); if (!sws_ref_src || !sws_src_dst || !sws_dst_out) goto error; - sws_ref_src->flags = SWS_BILINEAR; - sws_src_dst->flags = SWS_BILINEAR; - sws_dst_out->flags = SWS_BILINEAR; + sws_ref_src->flags = SWS_BILINEAR | SWS_BITEXACT; + sws_dst_out->flags = SWS_BILINEAR | SWS_BITEXACT; ref = av_frame_alloc(); if (!ref) -- 2.52.0 >From 543d8670a7afc52a696bad366a2652f1a78b4fd0 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Tue, 3 Mar 2026 11:25:14 +0100 Subject: [PATCH 10/11] swscale/tests/swscale: avoid redundant ref->src conversions The ref->src conversion only needs to be performed once per source pixel format. Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/swscale.c | 71 +++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index e2367c7127..d24510c722 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -254,9 +254,9 @@ error: static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, int dst_w, int dst_h, const struct mode *mode, const struct options *opts, - const AVFrame *ref, const float ssim_ref[4]) + const AVFrame *ref, AVFrame *src, const float ssim_ref[4]) { - AVFrame *src = NULL, *dst = NULL, *out = NULL; + AVFrame *dst = NULL, *out = NULL; float ssim[4], ssim_sws[4]; const int comps = fmt_comps(src_fmt) & fmt_comps(dst_fmt); int64_t time, time_ref = 0; @@ -274,29 +274,33 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, const float expected_loss = get_loss(ssim_expected); float loss; - src = av_frame_alloc(); dst = av_frame_alloc(); out = av_frame_alloc(); - if (!src || !dst || !out) + if (!dst || !out) goto error; - av_frame_copy_props(src, ref); + if (src->format != src_fmt) { + av_frame_unref(src); + av_frame_copy_props(src, ref); + src->width = ref->width; + src->height = ref->height; + src->format = src_fmt; + if (sws_scale_frame(sws_ref_src, src, ref) < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", + av_get_pix_fmt_name(ref->format), av_get_pix_fmt_name(src->format)); + goto error; + } + } + av_frame_copy_props(dst, ref); av_frame_copy_props(out, ref); - src->width = out->width = ref->width; - src->height = out->height = ref->height; + out->width = ref->width; + out->height = ref->height; out->format = ref->format; - src->format = src_fmt; dst->format = dst_fmt; dst->width = dst_w; dst->height = dst_h; - if (sws_scale_frame(sws_ref_src, src, ref) < 0) { - av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n", - av_get_pix_fmt_name(ref->format), av_get_pix_fmt_name(src->format)); - goto error; - } - sws_src_dst->flags = mode->flags; sws_src_dst->dither = mode->dither; sws_src_dst->threads = opts->threads; @@ -411,7 +415,6 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, fflush(stdout); ret = 0; /* fall through */ error: - av_frame_free(&src); av_frame_free(&dst); av_frame_free(&out); return ret; @@ -434,6 +437,12 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) src_fmt_max = AV_PIX_FMT_NB - 1, dst_fmt_max = AV_PIX_FMT_NB - 1; + AVFrame *src = av_frame_alloc(); + if (!src) + return AVERROR(ENOMEM); + + int ret = 0; + if (opts->src_fmt != AV_PIX_FMT_NONE) src_fmt_min = src_fmt_max = opts->src_fmt; if (opts->dst_fmt != AV_PIX_FMT_NONE) @@ -460,9 +469,10 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) if (ff_sfc64_get(&prng_state) > UINT64_MAX * opts->prob) continue; - if (run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h], - &mode, opts, ref, NULL) < 0) - return -1; + ret = run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h], + &mode, opts, ref, src, NULL); + if (ret < 0) + goto error; if (opts->flags >= 0 || opts->unscaled) break; @@ -476,13 +486,21 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) } } - return 0; + ret = 0; + +error: + av_frame_free(&src); + return ret; } static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *opts) { char buf[256]; - int ret; + int ret = 0; + + AVFrame *src = av_frame_alloc(); + if (!src) + return AVERROR(ENOMEM); while (fgets(buf, sizeof(buf), fp)) { char src_fmt_str[21], dst_fmt_str[21]; @@ -509,18 +527,23 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op sw != ref->width || sh != ref->height || mode.dither >= SWS_DITHER_NB) { av_log(NULL, AV_LOG_FATAL, "malformed input file\n"); - return -1; + goto error; } if (opts->src_fmt != AV_PIX_FMT_NONE && src_fmt != opts->src_fmt || opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt) continue; - if (run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, ssim) < 0) - return -1; + ret = run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, src, ssim); + if (ret < 0) + goto error; } - return 0; + ret = 0; + +error: + av_frame_free(&src); + return ret; } static int init_ref(AVFrame *ref, const struct options *opts) -- 2.52.0 >From f8e4e5c9a6a33c1ec1a41471ba06cb0ec254cc02 Mon Sep 17 00:00:00 2001 From: Ramiro Polla <[email protected]> Date: Fri, 6 Mar 2026 16:32:29 +0100 Subject: [PATCH 11/11] swscale/tests/swscale: add -s option to set frame size Sponsored-by: Sovereign Tech Fund Signed-off-by: Ramiro Polla <[email protected]> --- libswscale/tests/swscale.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index d24510c722..e0fdebc3ff 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -28,6 +28,7 @@ #undef HAVE_AV_CONFIG_H #include "libavutil/cpu.h" +#include "libavutil/parseutils.h" #include "libavutil/pixdesc.h" #include "libavutil/lfg.h" #include "libavutil/sfc64.h" @@ -596,6 +597,8 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) " Only test the specified destination pixel format\n" " -src <pixfmt>\n" " Only test the specified source pixel format\n" + " -s <size>\n" + " Set frame size (WxH or abbreviation)\n" " -bench <iters>\n" " Run benchmarks with the specified number of iterations. This mode also increases the size of the test images\n" " -flags <flags>\n" @@ -641,6 +644,11 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]); return -1; } + } else if (!strcmp(argv[i], "-s")) { + if (av_parse_video_size(&opts->w, &opts->h, argv[i + 1]) < 0) { + fprintf(stderr, "invalid frame size %s\n", argv[i + 1]); + return -1; + } } else if (!strcmp(argv[i], "-bench")) { opts->bench = 1; opts->iters = atoi(argv[i + 1]); -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
