[FFmpeg-devel] [PATCH] avfilter: add ANSNR filter

Ashish Singh ashk43712 at gmail.com
Mon Jul 3 16:09:38 EEST 2017


This is ANSNR filter, one of the component filters of VMAF.
Run it using: ffmpeg -i main -i ref -lavfi ansnr -f null -
Currently it outputs the average ansnr score over all frames.

---
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/ansnr.h      |  32 ++++
 libavfilter/vf_ansnr.c   | 403 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 437 insertions(+)
 create mode 100644 libavfilter/ansnr.h
 create mode 100644 libavfilter/vf_ansnr.c

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index f7dfe8a..705e5a1 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -124,6 +124,7 @@ OBJS-$(CONFIG_ANULLSINK_FILTER)              += asink_anullsink.o
 # video filters
 OBJS-$(CONFIG_ALPHAEXTRACT_FILTER)           += vf_extractplanes.o
 OBJS-$(CONFIG_ALPHAMERGE_FILTER)             += vf_alphamerge.o
+OBJS-$(CONFIG_ANSNR_FILTER)                  += vf_ansnr.o dualinput.o framesync.o
 OBJS-$(CONFIG_ASS_FILTER)                    += vf_subtitles.o
 OBJS-$(CONFIG_ATADENOISE_FILTER)             += vf_atadenoise.o
 OBJS-$(CONFIG_AVGBLUR_FILTER)                += vf_avgblur.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index cd35ae4..c1f67c4 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -136,6 +136,7 @@ static void register_all(void)
 
     REGISTER_FILTER(ALPHAEXTRACT,   alphaextract,   vf);
     REGISTER_FILTER(ALPHAMERGE,     alphamerge,     vf);
+    REGISTER_FILTER(ANSNR,          ansnr,          vf);
     REGISTER_FILTER(ASS,            ass,            vf);
     REGISTER_FILTER(ATADENOISE,     atadenoise,     vf);
     REGISTER_FILTER(AVGBLUR,        avgblur,        vf);
diff --git a/libavfilter/ansnr.h b/libavfilter/ansnr.h
new file mode 100644
index 0000000..effd722
--- /dev/null
+++ b/libavfilter/ansnr.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2017 Ronald S. Bultje <rsbultje at gmail.com>
+ * Copyright (c) 2017 Ashish Pratap Singh <ashk43712 at gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVFILTER_PSNR_H
+#define AVFILTER_PSNR_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+static int compute_ansnr(const void *ref, const void *dis, int w,
+                         int h, int ref_stride, int dis_stride, double *score,
+                         double *score_psnr, double peak, double psnr_max, void *ctx);
+
+#endif /* AVFILTER_PSNR_H */
diff --git a/libavfilter/vf_ansnr.c b/libavfilter/vf_ansnr.c
new file mode 100644
index 0000000..f8d44bf
--- /dev/null
+++ b/libavfilter/vf_ansnr.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2017 Ronald S. Bultje <rsbultje at gmail.com>
+ * Copyright (c) 2017 Ashish Pratap Singh <ashk43712 at gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Calculate the ANSNR between two input videos.
+ */
+
+#include <inttypes.h>
+#include "libavutil/avstring.h"
+#include "libavutil/opt.h"
+#include "libavutil/pixdesc.h"
+#include "avfilter.h"
+#include "dualinput.h"
+#include "drawutils.h"
+#include "formats.h"
+#include "internal.h"
+#include "ansnr.h"
+#include "video.h"
+
+typedef struct ANSNRContext {
+    const AVClass *class;
+    FFDualInputContext dinput;
+    int width;
+    int height;
+    char *format;
+    float *data_buf;
+    double ansnr_sum;
+    uint64_t nb_frames;
+} ANSNRContext;
+
+#define OFFSET(x) offsetof(ANSNRContext, x)
+#define MAX_ALIGN 32
+#define ALIGN_CEIL(x) ((x) + ((x) % MAX_ALIGN ? MAX_ALIGN - (x) % MAX_ALIGN : 0))
+#define OPT_RANGE_PIXEL_OFFSET (-128)
+
+const int ansnr_filter2d_ref_width = 3;
+const int ansnr_filter2d_dis_width = 5;
+const float ansnr_filter2d_ref[3 * 3] = {
+    1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0,
+    2.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0,
+    1.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0
+};
+const float ansnr_filter2d_dis[5 * 5] = {
+    2.0 / 571.0,  7.0 / 571.0,  12.0 / 571.0,  7.0 / 571.0,  2.0 / 571.0,
+    7.0 / 571.0,  31.0 / 571.0, 52.0 / 571.0,  31.0 / 571.0, 7.0 / 571.0,
+    12.0 / 571.0, 52.0 / 571.0, 127.0 / 571.0, 52.0 / 571.0, 12.0 / 571.0,
+    7.0 / 571.0,  31.0 / 571.0, 52.0 / 571.0,  31.0 / 571.0, 7.0 / 571.0,
+    2.0 / 571.0,  7.0 / 571.0,  12.0 / 571.0,  7.0 / 571.0,  2.0 / 571.0
+};
+
+static const AVOption ansnr_options[] = {
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(ansnr);
+
+static inline double get_ansnr_avg(double ansnr_sum, uint64_t nb_frames)
+{
+    return ansnr_sum / nb_frames;
+}
+
+static inline float pow_2(float base)
+{
+    return base*base;
+}
+
+static void ansnr_mse(float *ref, float *dis, float *signal, float *noise,
+                      int w, int h, int ref_stride, int dis_stride)
+{
+    int i, j;
+
+    int ref_ind;
+    int dis_ind;
+
+    float signal_sum = 0;
+    float noise_sum = 0;
+
+    for (i = 0; i < h; ++i) {
+        for (j = 0; j < w; ++j) {
+            ref_ind = i * ref_stride + j;
+            dis_ind = i * dis_stride + j;
+
+            signal_sum   += pow_2(ref[ref_ind]);
+            noise_sum += pow_2(ref[ref_ind] - dis[dis_ind]);
+        }
+    }
+
+    if (signal) {
+        *signal = signal_sum;
+    }
+    if (noise) {
+        *noise = noise_sum;
+    }
+}
+
+static void ansnr_filter2d(const float *filt, const void *src, float *dst,
+                           int w, int h, int src_stride, int dst_stride,
+                           int filt_width, ANSNRContext *s)
+{
+    uint8_t type;
+    uint8_t sz;
+
+    uint8_t *src_8bit = (uint8_t *) src;
+    uint16_t *src_10bit = (uint16_t *) src;
+
+    int src_px_stride;
+
+    float filt_coeff, img_coeff;
+    int i, j, filt_i, filt_j, src_i, src_j;
+
+    if (!strcmp(s->format, "yuv420p") || !strcmp(s->format, "yuv422p") ||
+        !strcmp(s->format, "yuv444p")) {
+        type = 8;
+        sz = sizeof(uint8_t);
+    }
+    else if (!strcmp(s->format, "yuv420p10le") || !strcmp(s->format,
+             "yuv422p10le") || !strcmp(s->format, "yuv444p10le")) {
+        type = 10;
+        sz = sizeof(uint16_t);
+    }
+
+    src_px_stride = src_stride / sizeof(sz);
+
+    for (i = 0; i < h; ++i) {
+        for (j = 0; j < w; ++j) {
+            float accum = 0;
+            for (filt_i = 0; filt_i < filt_width; ++filt_i) {
+                for (filt_j = 0; filt_j < filt_width; ++filt_j) {
+                    filt_coeff = filt[filt_i * filt_width + filt_j];
+
+                    src_i = i - filt_width / 2 + filt_i;
+                    src_j = j - filt_width / 2 + filt_j;
+
+                    src_i = FFABS(src_i);
+                    if (src_i >= h) {
+                        src_i = 2 * h - src_i - 1;
+                    }
+                    src_j = FFABS(src_j);
+                    if (src_j >= w) {
+                        src_j = 2 * w - src_j - 1;
+                    }
+
+                    if (type == 8) {
+                        img_coeff = src_8bit[src_i * src_px_stride + src_j] +
+                            OPT_RANGE_PIXEL_OFFSET;
+                    } else {
+                        img_coeff = src_10bit[src_i * src_px_stride + src_j] +
+                            OPT_RANGE_PIXEL_OFFSET;
+                    }
+
+                    accum += filt_coeff * img_coeff;
+                }
+            }
+            dst[i * dst_stride + j] = accum;
+        }
+    }
+}
+
+static int compute_ansnr(const void *ref, const void *dis, int w, int h,
+                         int ref_stride, int dis_stride, double *score,
+                         double *score_psnr, double peak, double psnr_max,
+                         void *ctx)
+{
+    ANSNRContext *s = (ANSNRContext *) ctx;
+
+    char *data_top;
+
+    float *ref_filt;
+    float *dis_filt;
+
+    float signal, noise;
+
+    int buf_stride = ALIGN_CEIL(w * sizeof(float));
+    size_t buf_sz = (size_t) (buf_stride * h);
+
+    double eps = 1e-10;
+
+    data_top = (float *) (s->data_buf);
+
+    ref_filt = (float *) data_top;
+    data_top += buf_sz;
+    dis_filt = (float *) data_top;
+    data_top += buf_sz;
+
+    buf_stride = buf_stride / sizeof(float);
+
+    ansnr_filter2d(ansnr_filter2d_ref, ref, ref_filt, w, h, ref_stride,
+                   buf_stride, ansnr_filter2d_ref_width, s);
+    ansnr_filter2d(ansnr_filter2d_dis, dis, dis_filt, w, h, dis_stride,
+                   buf_stride, ansnr_filter2d_dis_width, s);
+
+    ansnr_mse(ref_filt, dis_filt, &signal, &noise, w, h, buf_stride,
+              buf_stride);
+
+    *score = (noise==0) ? (psnr_max) : (10.0 * log10(signal / noise));
+
+    *score_psnr = FFMIN(10 * log10(pow_2(peak) * w * h / FFMAX(noise, eps)),
+                        psnr_max);
+
+    return 0;
+}
+
+static AVFrame *do_ansnr(AVFilterContext *ctx, AVFrame *main, const AVFrame *ref)
+{
+    ANSNRContext *s = ctx->priv;
+
+    char *format = s->format;
+
+    double score = 0.0;
+    double score_psnr = 0;
+
+    int w = s->width;
+    int h = s->height;
+
+    double stride;
+
+    double max_psnr;
+    double peak;
+
+    uint8_t sz;
+
+    if (!strcmp(format, "yuv420p") || !strcmp(format, "yuv422p") ||
+        !strcmp(format, "yuv444p")) {
+        peak = 255.0;
+        max_psnr = 60;
+        sz = sizeof(uint8_t);
+    }
+    else if (!strcmp(format, "yuv420p10le") || !strcmp(format, "yuv422p10le") ||
+             !strcmp(format, "yuv444p10le")) {
+        peak = 255.75;
+        max_psnr = 72;
+        sz = sizeof(uint16_t);
+    }
+
+    stride = ALIGN_CEIL(w * sz);
+
+    compute_ansnr(ref->data[0], main->data[0], w, h, stride, stride, &score,
+                  &score_psnr, peak, max_psnr, s);
+
+    s->nb_frames++;
+
+    s->ansnr_sum += score;
+
+    return main;
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    ANSNRContext *s = ctx->priv;
+
+    s->dinput.process = do_ansnr;
+
+    return 0;
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVPixelFormat pix_fmts[] = {
+        AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUV420P,
+        AV_PIX_FMT_YUV444P10LE, AV_PIX_FMT_YUV422P10LE, AV_PIX_FMT_YUV420P10LE,
+        AV_PIX_FMT_NONE
+    };
+
+    AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
+    if (!fmts_list)
+        return AVERROR(ENOMEM);
+    return ff_set_common_formats(ctx, fmts_list);
+}
+
+static int config_input_ref(AVFilterLink *inlink)
+{
+    AVFilterContext *ctx  = inlink->dst;
+    ANSNRContext *s = ctx->priv;
+    int buf_stride;
+    size_t buf_sz;
+
+    if (ctx->inputs[0]->w != ctx->inputs[1]->w ||
+        ctx->inputs[0]->h != ctx->inputs[1]->h) {
+        av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n");
+        return AVERROR(EINVAL);
+    }
+    if (ctx->inputs[0]->format != ctx->inputs[1]->format) {
+        av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n");
+        return AVERROR(EINVAL);
+    }
+
+    s->width = ctx->inputs[0]->w;
+    s->height = ctx->inputs[0]->h;
+    s->format = av_get_pix_fmt_name(ctx->inputs[0]->format);
+
+    buf_stride = ALIGN_CEIL(s->width * sizeof(float));
+    buf_sz = (size_t)buf_stride * s->height;
+
+    if (SIZE_MAX / buf_sz < 3) {
+        av_log(ctx, AV_LOG_ERROR, "insufficient size.\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (!(s->data_buf = av_malloc(buf_sz * 3))) {
+        av_log(ctx, AV_LOG_ERROR, "data_buf allocation failed.\n");
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    ANSNRContext *s = ctx->priv;
+    AVFilterLink *mainlink = ctx->inputs[0];
+    int ret;
+
+    outlink->w = mainlink->w;
+    outlink->h = mainlink->h;
+    outlink->time_base = mainlink->time_base;
+    outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
+    outlink->frame_rate = mainlink->frame_rate;
+    if ((ret = ff_dualinput_init(ctx, &s->dinput)) < 0)
+        return ret;
+
+    return 0;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *inpicref)
+{
+    ANSNRContext *s = inlink->dst->priv;
+    return ff_dualinput_filter_frame(&s->dinput, inlink, inpicref);
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    ANSNRContext *s = outlink->src->priv;
+    return ff_dualinput_request_frame(&s->dinput, outlink);
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    ANSNRContext *s = ctx->priv;
+
+    ff_dualinput_uninit(&s->dinput);
+
+    av_free(s->data_buf);
+
+    av_log(ctx, AV_LOG_INFO, "ANSNR AVG: %.3f\n", get_ansnr_avg(s->ansnr_sum,
+                                                                s->nb_frames));
+}
+
+static const AVFilterPad ansnr_inputs[] = {
+    {
+        .name         = "main",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+    },{
+        .name         = "reference",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .filter_frame = filter_frame,
+        .config_props = config_input_ref,
+    },
+    { NULL }
+};
+
+static const AVFilterPad ansnr_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+        .request_frame = request_frame,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_ansnr = {
+    .name          = "ansnr",
+    .description   = NULL_IF_CONFIG_SMALL("Calculate the ANSNR between two video streams."),
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .priv_size     = sizeof(ANSNRContext),
+    .priv_class    = &ansnr_class,
+    .inputs        = ansnr_inputs,
+    .outputs       = ansnr_outputs,
+};
-- 
2.7.4



More information about the ffmpeg-devel mailing list