diff mbox series

[FFmpeg-devel,v2,17/19] tests/swscale: rewrite on top of new API

Message ID 20241014134354.180848-18-ffmpeg@haasn.xyz
State New
Headers show
Series swscale: major refactoring and new API | expand

Checks

Context Check Description
yinshiyou/configure_loongarch64 warning Failed to apply patch

Commit Message

Niklas Haas Oct. 14, 2024, 1:37 p.m. UTC
From: Niklas Haas <git@haasn.dev>

This rewrite cleans up the code to use AVFrames and the new swscale API. The
log format has also been simplified and expanded to account for the new
options. (Not yet implemented)

The self testing code path has also been expanded to test the new swscale
implementation against the old one, to serve as an unchanging reference. This
does not accomplish much yet, but serves as a framework for future work.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
---
 libswscale/tests/swscale.c | 665 ++++++++++++++++---------------------
 1 file changed, 284 insertions(+), 381 deletions(-)
diff mbox series

Patch

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index f9b5f60ef0..c11a46024e 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -1,4 +1,5 @@ 
 /*
+ * Copyright (C) 2024      Nikles Haas
  * Copyright (C) 2003-2011 Michael Niedermayer <michaelni@gmx.at>
  *
  * This file is part of FFmpeg.
@@ -26,424 +27,307 @@ 
 
 #undef HAVE_AV_CONFIG_H
 #include "libavutil/cpu.h"
-#include "libavutil/imgutils.h"
-#include "libavutil/mem.h"
-#include "libavutil/avutil.h"
-#include "libavutil/crc.h"
-#include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/lfg.h"
 #include "libavutil/sfc64.h"
+#include "libavutil/frame.h"
+#include "libavutil/pixfmt.h"
+#include "libavutil/avassert.h"
+#include "libavutil/macros.h"
 
 #include "libswscale/swscale.h"
 
-/* HACK Duplicated from swscale_internal.h.
- * Should be removed when a cleaner pixel format system exists. */
-#define isGray(x)                      \
-    ((x) == AV_PIX_FMT_GRAY8       ||     \
-     (x) == AV_PIX_FMT_YA8         ||     \
-     (x) == AV_PIX_FMT_GRAY16BE    ||     \
-     (x) == AV_PIX_FMT_GRAY16LE    ||     \
-     (x) == AV_PIX_FMT_YA16BE      ||     \
-     (x) == AV_PIX_FMT_YA16LE)
-#define hasChroma(x)                   \
-    (!(isGray(x)                ||     \
-       (x) == AV_PIX_FMT_MONOBLACK ||     \
-       (x) == AV_PIX_FMT_MONOWHITE))
-
-static av_always_inline int isALPHA(enum AVPixelFormat pix_fmt)
-{
-    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
-    return desc->flags & AV_PIX_FMT_FLAG_ALPHA;
-}
+enum {
+    WIDTH  = 96,
+    HEIGHT = 96,
+};
 
-static double prob = 1;
-FFSFC64 prng_state;
+struct options {
+    enum AVPixelFormat src_fmt;
+    enum AVPixelFormat dst_fmt;
+    double prob;
+};
 
-static uint64_t getSSD(const uint8_t *src1, const uint8_t *src2,
-                       int stride1, int stride2, int w, int h)
-{
-    int x, y;
-    uint64_t ssd = 0;
+struct mode {
+    SwsFlags flags;
+    SwsDither dither;
+};
 
-    for (y = 0; y < h; y++) {
-        for (x = 0; x < w; x++) {
-            int d = src1[x + y * stride1] - src2[x + y * stride2];
-            ssd += d * d;
-        }
-    }
-    return ssd;
-}
+const int dst_w[] = { WIDTH,  WIDTH  - WIDTH  / 3, WIDTH  + WIDTH  / 3 };
+const int dst_h[] = { HEIGHT, HEIGHT - HEIGHT / 3, HEIGHT + HEIGHT / 3 };
+
+const struct mode modes[] = {
+    { SWS_FAST_BILINEAR },
+    { SWS_BILINEAR },
+    { SWS_BICUBIC },
+    { SWS_X | SWS_BITEXACT },
+    { SWS_POINT },
+    { SWS_AREA | SWS_ACCURATE_RND },
+    { SWS_BICUBIC | SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP },
+    {0}, // test defaults
+};
 
-static uint64_t getSSD0(int ref, const uint8_t *src1, int stride1,
-                        int w, int h)
-{
-    int x, y;
-    uint64_t ssd = 0;
+static FFSFC64 prng_state;
+static SwsContext *sws[3]; /* reused between tests for efficiency */
 
-    for (y = 0; y < h; y++) {
-        for (x = 0; x < w; x++) {
-            int d = src1[x + y * stride1] - ref;
-            ssd += d * d;
-        }
-    }
-    return ssd;
+static int fmt_comps(enum AVPixelFormat fmt)
+{
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+    int comps = desc->nb_components >= 3 ? 0b111 : 0b1;
+    if (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
+        comps |= 0b1000;
+    return comps;
 }
 
-struct Results {
-    uint64_t ssdY;
-    uint64_t ssdU;
-    uint64_t ssdV;
-    uint64_t ssdA;
-    uint32_t crc;
-};
-
-// test by ref -> src -> dst -> out & compare out against ref
-// ref & out are YV12
-static int doTest(const uint8_t * const ref[4], int refStride[4], int w, int h,
-                  enum AVPixelFormat srcFormat, enum AVPixelFormat dstFormat,
-                  int srcW, int srcH, int dstW, int dstH, int flags,
-                  struct Results *r)
+static void get_mse(int mse[4], const AVFrame *a, const AVFrame *b, int comps)
 {
-    const AVPixFmtDescriptor *desc_yuva420p = av_pix_fmt_desc_get(AV_PIX_FMT_YUVA420P);
-    const AVPixFmtDescriptor *desc_src      = av_pix_fmt_desc_get(srcFormat);
-    const AVPixFmtDescriptor *desc_dst      = av_pix_fmt_desc_get(dstFormat);
-    static enum AVPixelFormat cur_srcFormat;
-    static int cur_srcW, cur_srcH;
-    static const uint8_t *src[4];
-    static int srcStride[4];
-    uint8_t *dst[4] = { 0 };
-    uint8_t *out[4] = { 0 };
-    int dstStride[4] = {0};
-    int i;
-    uint64_t ssdY, ssdU = 0, ssdV = 0, ssdA = 0;
-    SwsInternal *dstContext = NULL, *outContext = NULL;
-    uint32_t crc = 0;
-    int res      = 0;
-
-    if (ff_sfc64_get(&prng_state) > UINT64_MAX * prob)
-        return 0;
-
-    if (cur_srcFormat != srcFormat || cur_srcW != srcW || cur_srcH != srcH) {
-        SwsInternal *srcContext = NULL;
-        int p;
-
-        for (p = 0; p < 4; p++)
-            av_freep(&src[p]);
-
-        res = av_image_fill_linesizes(srcStride, srcFormat, srcW);
-        if (res < 0) {
-            fprintf(stderr, "av_image_fill_linesizes failed\n");
-            goto end;
-        }
-        for (p = 0; p < 4; p++) {
-            srcStride[p] = FFALIGN(srcStride[p], 16);
-            if (srcStride[p])
-                src[p] = av_mallocz(srcStride[p] * srcH + 16);
-            if (srcStride[p] && !src[p]) {
-                perror("Malloc");
-                res = -1;
-                goto end;
+    av_assert1(a->format == AV_PIX_FMT_YUVA420P);
+    av_assert1(b->format == a->format);
+    av_assert1(b->width == a->width && b->height == a->height);
+
+    for (int p = 0; p < 4; p++) {
+        const int is_chroma = p == 1 || p == 2;
+        const int stride_a = a->linesize[p];
+        const int stride_b = b->linesize[p];
+        const int w = (a->width + is_chroma) >> is_chroma;
+        const int h = (a->height + is_chroma) >> is_chroma;
+        uint64_t sum = 0;
+
+        if (comps & (1 << p)) {
+            for (int y = 0; y < h; y++) {
+                for (int x = 0; x < w; x++) {
+                    int d = a->data[p][y * stride_a + x] - b->data[p][y * stride_b + x];
+                    sum += d * d;
+                }
+            }
+        } else {
+            const int ref = is_chroma ? 128 : 0xFF;
+            for (int y = 0; y < h; y++) {
+                for (int x = 0; x < w; x++) {
+                    int d = a->data[p][y * stride_a + x] - ref;
+                    sum += d * d;
+                }
             }
         }
-        srcContext = sws_getContext(w, h, AV_PIX_FMT_YUVA420P, srcW, srcH,
-                                    srcFormat, SWS_BILINEAR, NULL, NULL, NULL);
-        if (!srcContext) {
-            fprintf(stderr, "Failed to get %s ---> %s\n",
-                    desc_yuva420p->name,
-                    desc_src->name);
-            res = -1;
-            goto end;
-        }
-        sws_scale(srcContext, ref, refStride, 0, h,
-                  (uint8_t * const *) src, srcStride);
-        sws_freeContext(srcContext);
 
-        cur_srcFormat = srcFormat;
-        cur_srcW      = srcW;
-        cur_srcH      = srcH;
+        mse[p] = sum / (w * h);
     }
+}
 
-    res = av_image_fill_linesizes(dstStride, dstFormat, dstW);
-    if (res < 0) {
-        fprintf(stderr, "av_image_fill_linesizes failed\n");
-        goto end;
-    }
+static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode)
+{
+    SwsContext *sws_legacy;
+    int ret;
 
-    for (i = 0; i < 4; i++) {
-        /* Image buffers passed into libswscale can be allocated any way you
-         * prefer, as long as they're aligned enough for the architecture, and
-         * they're freed appropriately (such as using av_free for buffers
-         * allocated with av_malloc). */
-        /* An extra 16 bytes is being allocated because some scalers may write
-         * out of bounds. */
-        dstStride[i] = FFALIGN(dstStride[i], 16);
-        if (dstStride[i])
-            dst[i] = av_mallocz(dstStride[i] * dstH + 16);
-        if (dstStride[i] && !dst[i]) {
-            perror("Malloc");
-            res = -1;
-
-            goto end;
-        }
-    }
+    sws_legacy = sws_alloc_context();
+    if (!sws_legacy)
+        return -1;
+
+    sws_legacy->src_w      = src->width;
+    sws_legacy->src_h      = src->height;
+    sws_legacy->src_format = src->format;
+    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;
+
+    ret = sws_init_context(sws_legacy, NULL, NULL);
+    if (!ret)
+        ret = sws_scale_frame(sws_legacy, dst, src);
+
+    sws_freeContext(sws_legacy);
+    return ret;
+}
+
+/* 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, const AVFrame *ref,
+                    const int mse_ref[4])
+{
+    AVFrame *src = NULL, *dst = NULL, *out = NULL;
+    int mse[4], mse_sws[4], ret = -1;
+    const int comps = fmt_comps(src_fmt) & fmt_comps(dst_fmt);
+
+    src = av_frame_alloc();
+    dst = av_frame_alloc();
+    out = av_frame_alloc();
+    if (!src || !dst || !out)
+        goto error;
 
-    dstContext = sws_alloc_context();
-    if (!dstContext) {
-        fprintf(stderr, "Failed to alloc %s ---> %s\n",
-                desc_src->name, desc_dst->name);
-        res = -1;
-        goto end;
+    av_frame_copy_props(src, ref);
+    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->format = ref->format;
+    src->format = src_fmt;
+    dst->format = dst_fmt;
+    dst->width  = dst_w;
+    dst->height = dst_h;
+
+    if (sws_scale_frame(sws[0], src, ref) < 0) {
+        fprintf(stderr, "Failed %s ---> %s\n", av_get_pix_fmt_name(ref->format),
+                av_get_pix_fmt_name(src->format));
+        goto error;
     }
 
-    av_opt_set_int(dstContext, "sws_flags",  flags, 0);
-    av_opt_set_int(dstContext, "srcw",       srcW, 0);
-    av_opt_set_int(dstContext, "srch",       srcH, 0);
-    av_opt_set_int(dstContext, "dstw",       dstW, 0);
-    av_opt_set_int(dstContext, "dsth",       dstH, 0);
-    av_opt_set_int(dstContext, "src_format", srcFormat, 0);
-    av_opt_set_int(dstContext, "dst_format", dstFormat, 0);
-    av_opt_set(dstContext, "alphablend", "none", 0);
-
-    if (sws_init_context(dstContext, NULL, NULL) < 0) {
-        sws_freeContext(dstContext);
-        fprintf(stderr, "Failed to init %s ---> %s\n",
-                desc_src->name, desc_dst->name);
-        res = -1;
-        goto end;
+    sws[1]->flags  = mode.flags;
+    sws[1]->dither = mode.dither;
+    if (sws_scale_frame(sws[1], dst, src) < 0) {
+        fprintf(stderr, "Failed %s ---> %s\n", av_get_pix_fmt_name(src->format),
+                av_get_pix_fmt_name(dst->format));
+        goto error;
     }
 
-    printf(" %s %dx%d -> %s %3dx%3d flags=%2d",
-           desc_src->name, srcW, srcH,
-           desc_dst->name, dstW, dstH,
-           flags);
-    fflush(stdout);
+    if (sws_scale_frame(sws[2], out, dst) < 0) {
+        fprintf(stderr, "Failed %s ---> %s\n", av_get_pix_fmt_name(dst->format),
+                av_get_pix_fmt_name(out->format));
+        goto error;
+    }
 
-    sws_scale(dstContext, (const uint8_t * const*)src, srcStride, 0, srcH, dst, dstStride);
-
-    for (i = 0; i < 4 && dstStride[i]; i++)
-        crc = av_crc(av_crc_get_table(AV_CRC_32_IEEE), crc, dst[i],
-                     dstStride[i] * dstH);
-
-    if (r && crc == r->crc) {
-        ssdY = r->ssdY;
-        ssdU = r->ssdU;
-        ssdV = r->ssdV;
-        ssdA = r->ssdA;
-    } else {
-        for (i = 0; i < 4; i++) {
-            refStride[i] = FFALIGN(refStride[i], 16);
-            if (refStride[i])
-                out[i] = av_mallocz(refStride[i] * h);
-            if (refStride[i] && !out[i]) {
-                perror("Malloc");
-                res = -1;
-                goto end;
-            }
-        }
-        outContext = sws_getContext(dstW, dstH, dstFormat, w, h,
-                                    AV_PIX_FMT_YUVA420P, SWS_BILINEAR,
-                                    NULL, NULL, NULL);
-        if (!outContext) {
-            fprintf(stderr, "Failed to get %s ---> %s\n",
-                    desc_dst->name,
-                    desc_yuva420p->name);
-            res = -1;
-            goto end;
-        }
-        sws_scale(outContext, (const uint8_t * const *) dst, dstStride, 0, dstH,
-                  out, refStride);
-
-        ssdY = getSSD(ref[0], out[0], refStride[0], refStride[0], w, h);
-        if (hasChroma(srcFormat) && hasChroma(dstFormat)) {
-            //FIXME check that output is really gray
-            ssdU = getSSD(ref[1], out[1], refStride[1], refStride[1],
-                          (w + 1) >> 1, (h + 1) >> 1);
-            ssdV = getSSD(ref[2], out[2], refStride[2], refStride[2],
-                          (w + 1) >> 1, (h + 1) >> 1);
-        } else {
-            ssdU = getSSD0(128, out[1], refStride[1],
-                          (w + 1) >> 1, (h + 1) >> 1);
-            ssdV = getSSD0(128, out[2], refStride[2],
-                          (w + 1) >> 1, (h + 1) >> 1);
-        }
-        if (isALPHA(srcFormat) && isALPHA(dstFormat)) {
-            ssdA = getSSD(ref[3], out[3], refStride[3], refStride[3], w, h);
-        } else {
-            ssdA = getSSD0(0xFF, out[3], refStride[3], w, h);
+    get_mse(mse, out, ref, comps);
+    printf("%s %dx%d -> %s %3dx%3d, flags=%u dither=%u, "
+           "MSE={%5d %5d %5d %5d}\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,
+           mse[0], mse[1], mse[2], mse[3]);
+
+    if (!mse_ref) {
+        /* Compare against the legacy swscale API as a reference */
+        if (scale_legacy(dst, src, mode) < 0) {
+            fprintf(stderr, "Failed ref %s ---> %s\n", av_get_pix_fmt_name(src->format),
+                    av_get_pix_fmt_name(dst->format));
+            goto error;
         }
 
-        ssdY /= w * h;
-        ssdU /= w * h / 4;
-        ssdV /= w * h / 4;
-        ssdA /= w * h;
-
-        sws_freeContext(outContext);
+        if (sws_scale_frame(sws[2], out, dst) < 0)
+            goto error;
 
-        for (i = 0; i < 4; i++)
-            if (refStride[i])
-                av_free(out[i]);
+        get_mse(mse_sws, out, ref, comps);
+        mse_ref = mse_sws;
     }
 
-    if(r){
-        if(ssdY>r->ssdY*1.02+1 || ssdU>r->ssdU*1.02+1 || ssdV>r->ssdV*1.02+1|| ssdA>r->ssdA*1.02+1)
-            printf("WORSE SSD=%5"PRId64",%5"PRId64",%5"PRId64",%5"PRId64"",
-            r->ssdY, r->ssdU, r->ssdV, r->ssdA);
-        else if(ssdY>r->ssdY || ssdU>r->ssdU || ssdV>r->ssdV|| ssdA>r->ssdA)
-            printf("worse SSD=%5"PRId64",%5"PRId64",%5"PRId64",%5"PRId64"",
-            r->ssdY, r->ssdU, r->ssdV, r->ssdA);
+    for (int i = 0; i < 4; i++) {
+        if (mse[i] > mse_ref[i]) {
+            int bad = mse[i] > mse_ref[i] * 1.02 + 1;
+            printf("\033[1;31m  %s, ref MSE={%5d %5d %5d %5d}\033[0m\n",
+                   bad ? "WORSE" : "worse",
+                   mse_ref[0], mse_ref[1], mse_ref[2], mse_ref[3]);
+            if (bad)
+                goto error;
+            break;
+        }
     }
 
-    printf(" CRC=%08x SSD=%5"PRId64 ",%5"PRId64 ",%5"PRId64 ",%5"PRId64 "\n",
-           crc, ssdY, ssdU, ssdV, ssdA);
-
-end:
-    sws_freeContext(dstContext);
-
-    for (i = 0; i < 4; i++)
-        if (dstStride[i])
-            av_free(dst[i]);
-
-    return !!res;
+    fflush(stdout);
+    ret = 0; /* fall through */
+ error:
+    av_frame_free(&src);
+    av_frame_free(&dst);
+    av_frame_free(&out);
+    return ret;
 }
 
-static void selfTest(const uint8_t * const ref[4], int refStride[4],
-                     int w, int h,
-                     enum AVPixelFormat srcFormat_in,
-                     enum AVPixelFormat dstFormat_in)
+static int run_self_tests(const AVFrame *ref, struct options opts)
 {
-    const int flags[] = { SWS_FAST_BILINEAR,
-                          SWS_BILINEAR, SWS_BICUBIC,
-                          SWS_X|SWS_BITEXACT       , SWS_POINT  , SWS_AREA|SWS_ACCURATE_RND,
-                          SWS_BICUBIC|SWS_FULL_CHR_H_INT|SWS_FULL_CHR_H_INP, 0};
-    const int srcW   = w;
-    const int srcH   = h;
-    const int dstW[] = { srcW - srcW / 3, srcW, srcW + srcW / 3, 0 };
-    const int dstH[] = { srcH - srcH / 3, srcH, srcH + srcH / 3, 0 };
-    enum AVPixelFormat srcFormat, dstFormat;
-    const AVPixFmtDescriptor *desc_src, *desc_dst;
-
-    for (srcFormat = srcFormat_in != AV_PIX_FMT_NONE ? srcFormat_in : 0;
-         srcFormat < AV_PIX_FMT_NB; srcFormat++) {
-        if (!sws_isSupportedInput(srcFormat) ||
-            !sws_isSupportedOutput(srcFormat))
+    enum AVPixelFormat src_fmt, dst_fmt,
+                       src_fmt_min = 0,
+                       dst_fmt_min = 0,
+                       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;
+
+    for (src_fmt = src_fmt_min; src_fmt <= src_fmt_max; src_fmt++) {
+        if (!sws_test_format(src_fmt, 0) || !sws_test_format(src_fmt, 1))
             continue;
-
-        desc_src = av_pix_fmt_desc_get(srcFormat);
-
-        for (dstFormat = dstFormat_in != AV_PIX_FMT_NONE ? dstFormat_in : 0;
-             dstFormat < AV_PIX_FMT_NB; dstFormat++) {
-            int i, j, k;
-            int res = 0;
-
-            if (!sws_isSupportedInput(dstFormat) ||
-                !sws_isSupportedOutput(dstFormat))
+        for (dst_fmt = dst_fmt_min; dst_fmt <= dst_fmt_max; dst_fmt++) {
+            if (!sws_test_format(dst_fmt, 0) || !sws_test_format(dst_fmt, 1))
                 continue;
-
-            desc_dst = av_pix_fmt_desc_get(dstFormat);
-
-            printf("%s -> %s\n", desc_src->name, desc_dst->name);
-            fflush(stdout);
-
-            for (k = 0; flags[k] && !res; k++)
-                for (i = 0; dstW[i] && !res; i++)
-                    for (j = 0; dstH[j] && !res; j++)
-                        res = doTest(ref, refStride, w, h,
-                                     srcFormat, dstFormat,
-                                     srcW, srcH, dstW[i], dstH[j], flags[k],
-                                     NULL);
-            if (dstFormat_in != AV_PIX_FMT_NONE)
-                break;
+            for (int h = 0; h < FF_ARRAY_ELEMS(dst_h); h++)
+                for (int w = 0; w < FF_ARRAY_ELEMS(dst_w); w++)
+                    for (int m = 0; m < FF_ARRAY_ELEMS(modes); m++) {
+                        if (ff_sfc64_get(&prng_state) > UINT64_MAX * opts.prob)
+                            continue;
+                        if (run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h],
+                                     modes[m], ref, NULL) < 0)
+                            return -1;
+                    }
         }
-        if (srcFormat_in != AV_PIX_FMT_NONE)
-            break;
     }
+
+    return 0;
 }
 
-static int fileTest(const uint8_t * const ref[4], int refStride[4],
-                    int w, int h, FILE *fp,
-                    enum AVPixelFormat srcFormat_in,
-                    enum AVPixelFormat dstFormat_in)
+static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts)
 {
     char buf[256];
+    int ret;
 
     while (fgets(buf, sizeof(buf), fp)) {
-        struct Results r;
-        enum AVPixelFormat srcFormat;
-        char srcStr[21];
-        int srcW = 0, srcH = 0;
-        enum AVPixelFormat dstFormat;
-        char dstStr[21];
-        int dstW = 0, dstH = 0;
-        int flags;
-        int ret;
+        char src_fmt_str[20], dst_fmt_str[20];
+        enum AVPixelFormat src_fmt;
+        enum AVPixelFormat dst_fmt;
+        int sw, sh, dw, dh, mse[4];
+        struct mode mode;
 
         ret = sscanf(buf,
-                     " %20s %dx%d -> %20s %dx%d flags=%d CRC=%x"
-                     " SSD=%"SCNu64 ", %"SCNu64 ", %"SCNu64 ", %"SCNu64 "\n",
-                     srcStr, &srcW, &srcH, dstStr, &dstW, &dstH,
-                     &flags, &r.crc, &r.ssdY, &r.ssdU, &r.ssdV, &r.ssdA);
-        if (ret != 12) {
-            srcStr[0] = dstStr[0] = 0;
-            ret       = sscanf(buf, "%20s -> %20s\n", srcStr, dstStr);
+                     " %20s %dx%d -> %20s %dx%d, flags=%u dither=%u, "
+                     "MSE={%d %d %d %d}\n",
+                     src_fmt_str, &sw, &sh, dst_fmt_str, &dw, &dh,
+                     &mode.flags, &mode.dither,
+                     &mse[0], &mse[1], &mse[2], &mse[3]);
+        if (ret != 13) {
+            printf("%s", buf);
+            continue;
         }
 
-        srcFormat = av_get_pix_fmt(srcStr);
-        dstFormat = av_get_pix_fmt(dstStr);
-
-        if (srcFormat == AV_PIX_FMT_NONE || dstFormat == AV_PIX_FMT_NONE ||
-            srcW > 8192U || srcH > 8192U || dstW > 8192U || dstH > 8192U) {
+        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 ||
+            mode.dither >= SWS_DITHER_NB) {
             fprintf(stderr, "malformed input file\n");
             return -1;
         }
-        if ((srcFormat_in != AV_PIX_FMT_NONE && srcFormat_in != srcFormat) ||
-            (dstFormat_in != AV_PIX_FMT_NONE && dstFormat_in != dstFormat))
-            continue;
-        if (ret != 12) {
-            printf("%s", buf);
+
+        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;
-        }
 
-        doTest(ref, refStride, w, h,
-               srcFormat, dstFormat,
-               srcW, srcH, dstW, dstH, flags,
-               &r);
+        if (run_test(src_fmt, dst_fmt, dw, dh, mode, ref, mse) < 0)
+            return -1;
     }
 
     return 0;
 }
 
-#define W 96
-#define H 96
-
 int main(int argc, char **argv)
 {
-    enum AVPixelFormat srcFormat = AV_PIX_FMT_NONE;
-    enum AVPixelFormat dstFormat = AV_PIX_FMT_NONE;
-    uint8_t *rgb_data   = av_malloc(W * H * 4);
-    const uint8_t * const rgb_src[4] = { rgb_data, NULL, NULL, NULL };
-    int rgb_stride[4]   = { 4 * W, 0, 0, 0 };
-    uint8_t *data       = av_malloc(4 * W * H);
-    const uint8_t * const src[4] = { data, data + W * H, data + W * H * 2, data + W * H * 3 };
-    int stride[4]       = { W, W, W, W };
-    int x, y;
-    SwsInternal *sws;
-    AVLFG rand;
-    int res = -1;
-    int i;
-    FILE *fp = NULL;
+    struct options opts = {
+        .src_fmt = AV_PIX_FMT_NONE,
+        .dst_fmt = AV_PIX_FMT_NONE,
+        .prob = 1.0,
+    };
 
-    if (!rgb_data || !data)
-        return -1;
+    AVFrame *rgb = NULL, *ref = NULL;
+    FILE *fp = NULL;
+    AVLFG rand;
+    int ret = -1;
 
-    for (i = 1; i < argc; i += 2) {
+    for (int i = 1; i < argc; i += 2) {
         if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) {
             fprintf(stderr,
                     "swscale [options...]\n"
                     "   -help\n"
                     "       This text\n"
                     "   -ref <file>\n"
-                    "       Uses file as reference to compae tests againsts. Tests that have become worse will contain the string worse or WORSE\n"
+                    "       Uses file as reference to compare tests againsts. Tests that have become worse will contain the string worse or WORSE\n"
                     "   -p <number between 0.0 and 1.0>\n"
                     "       The percentage of tests or comparisons to perform. Doing all tests will take long and generate over a hundred MB text output\n"
                     "       It is often convenient to perform a random subset\n"
@@ -454,7 +338,7 @@  int main(int argc, char **argv)
                     "   -cpuflags <cpuflags>\n"
                     "       Uses the specified cpuflags in the tests\n"
             );
-            goto error;
+            return 0;
         }
         if (argv[i][0] != '-' || i + 1 == argc)
             goto bad_option;
@@ -466,26 +350,26 @@  int main(int argc, char **argv)
             }
         } else if (!strcmp(argv[i], "-cpuflags")) {
             unsigned flags = av_get_cpu_flags();
-            int ret = av_parse_cpu_caps(&flags, argv[i + 1]);
-            if (ret < 0) {
+            int res = av_parse_cpu_caps(&flags, argv[i + 1]);
+            if (res < 0) {
                 fprintf(stderr, "invalid cpu flags %s\n", argv[i + 1]);
-                return ret;
+                goto error;
             }
             av_force_cpu_flags(flags);
         } else if (!strcmp(argv[i], "-src")) {
-            srcFormat = av_get_pix_fmt(argv[i + 1]);
-            if (srcFormat == 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]);
-                return -1;
+                goto error;
             }
         } else if (!strcmp(argv[i], "-dst")) {
-            dstFormat = av_get_pix_fmt(argv[i + 1]);
-            if (dstFormat == 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]);
-                return -1;
+                goto error;
             }
         } else if (!strcmp(argv[i], "-p")) {
-            prob = atof(argv[i + 1]);
+            opts.prob = atof(argv[i + 1]);
         } else {
 bad_option:
             fprintf(stderr, "bad option or argument missing (%s) see -help\n", argv[i]);
@@ -494,32 +378,51 @@  bad_option:
     }
 
     ff_sfc64_init(&prng_state, 0, 0, 0, 12);
-
-    sws = sws_getContext(W / 12, H / 12, AV_PIX_FMT_RGB32, W, H,
-                         AV_PIX_FMT_YUVA420P, SWS_BILINEAR, NULL, NULL, NULL);
-
     av_lfg_init(&rand, 1);
 
-    for (y = 0; y < H; y++)
-        for (x = 0; x < W * 4; x++)
-            rgb_data[ x + y * 4 * W] = av_lfg_get(&rand);
-    res = sws_scale(sws, rgb_src, rgb_stride, 0, H / 12, (uint8_t * const *) src, stride);
-    if (res < 0 || res != H) {
-        res = -1;
-        goto error;
+    for (int i = 0; i < 3; i++) {
+        sws[i] = sws_alloc_context();
+        if (!sws[i])
+            goto error;
+        sws[i]->flags = SWS_BILINEAR;
     }
-    sws_freeContext(sws);
-    av_free(rgb_data);
 
-    if(fp) {
-        res = fileTest(src, stride, W, H, fp, srcFormat, dstFormat);
-        fclose(fp);
-    } else {
-        selfTest(src, stride, W, H, srcFormat, dstFormat);
-        res = 0;
+    rgb = av_frame_alloc();
+    if (!rgb)
+        goto error;
+    rgb->width  = WIDTH  / 12;
+    rgb->height = HEIGHT / 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);
+        }
     }
-error:
-    av_free(data);
 
-    return res;
+    ref = av_frame_alloc();
+    if (!ref)
+        goto error;
+    ref->width  = WIDTH;
+    ref->height = HEIGHT;
+    ref->format = AV_PIX_FMT_YUVA420P;
+
+    if (sws_scale_frame(sws[0], ref, rgb) < 0)
+        goto error;
+
+    ret = fp ? run_file_tests(ref, fp, opts)
+             : run_self_tests(ref, opts);
+
+    /* fall through */
+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);
+    return ret;
 }