diff mbox

[FFmpeg-devel] avfilter: add VMAF filter

Message ID 1498333838-9317-1-git-send-email-ashk43712@gmail.com
State Superseded
Headers show

Commit Message

Ashish Singh June 24, 2017, 7:50 p.m. UTC
This is VMAF filter which requires Netflix's vmaf library installed,
but currently there is no library implementation in the Netflix's vmaf.
I will open a pull request soon to Netflix's vmaf for the library integration
code and provide a link in this thread.
After installing the Netflix's vmaf library, do --enable-libvmaf at the time of configure.
It can be run using "ffmpeg -i main -i ref -lavfi vmaf="model_path=model_path" -f null -"

---
 configure                |   5 +
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/vf_vmaf.c    | 429 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 436 insertions(+)
 create mode 100644 libavfilter/vf_vmaf.c

Comments

Ashish Singh June 24, 2017, 8:31 p.m. UTC | #1
Hi, this is the pull request at Netflix's vmaf for library integration:
https://github.com/Netflix/vmaf/pull/90

On Sun, Jun 25, 2017 at 1:20 AM, Ashish Singh <ashk43712@gmail.com> wrote:

> This is VMAF filter which requires Netflix's vmaf library installed,
> but currently there is no library implementation in the Netflix's vmaf.
> I will open a pull request soon to Netflix's vmaf for the library
> integration
> code and provide a link in this thread.
> After installing the Netflix's vmaf library, do --enable-libvmaf at the
> time of configure.
> It can be run using "ffmpeg -i main -i ref -lavfi
> vmaf="model_path=model_path" -f null -"
>
> ---
>  configure                |   5 +
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_vmaf.c    | 429 ++++++++++++++++++++++++++++++
> +++++++++++++++++
>  4 files changed, 436 insertions(+)
>  create mode 100644 libavfilter/vf_vmaf.c
>
> diff --git a/configure b/configure
> index 5ae5227..faaf236 100755
> --- a/configure
> +++ b/configure
> @@ -259,6 +259,7 @@ External library support:
>    --enable-libtwolame      enable MP2 encoding via libtwolame [no]
>    --enable-libv4l2         enable libv4l2/v4l-utils [no]
>    --enable-libvidstab      enable video stabilization using vid.stab [no]
> +  --enable-libvmaf         enable vmaf filter via libvmaf [no]
>    --enable-libvo-amrwbenc  enable AMR-WB encoding via libvo-amrwbenc [no]
>    --enable-libvorbis       enable Vorbis en/decoding via libvorbis,
>                             native implementation exists [no]
> @@ -1569,6 +1570,7 @@ EXTERNAL_LIBRARY_LIST="
>      libtheora
>      libtwolame
>      libv4l2
> +    libvmaf
>      libvorbis
>      libvpx
>      libwavpack
> @@ -2878,6 +2880,7 @@ libspeex_encoder_deps="libspeex"
>  libspeex_encoder_select="audio_frame_queue"
>  libtheora_encoder_deps="libtheora"
>  libtwolame_encoder_deps="libtwolame"
> +libvmaf_filter_deps="libvmaf"
>  libvo_amrwbenc_encoder_deps="libvo_amrwbenc"
>  libvorbis_decoder_deps="libvorbis"
>  libvorbis_encoder_deps="libvorbis"
> @@ -5845,6 +5848,8 @@ enabled libtwolame        && require libtwolame
> twolame.h twolame_init -ltwolame
>                                 die "ERROR: libtwolame must be installed
> and version must be >= 0.3.10"; }
>  enabled libv4l2           && require_pkg_config libv4l2 libv4l2.h
> v4l2_ioctl
>  enabled libvidstab        && require_pkg_config "vidstab >= 0.98"
> vid.stab/libvidstab.h vsMotionDetectInit
> +enabled libvmaf           && { check_lib libvmaf "libvmaf.h"
> "compute_vmaf" -lvmaf -lstdc++ -lpthread -lm ||
> +                               die "ERROR: libvmaf must be installed"; }
>  enabled libvo_amrwbenc    && require libvo_amrwbenc vo-amrwbenc/enc_if.h
> E_IF_init -lvo-amrwbenc
>  enabled libvorbis         && require libvorbis vorbis/vorbisenc.h
> vorbis_info_init -lvorbisenc -lvorbis -logg
>
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index f7dfe8a..1c4bd56 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -314,6 +314,7 @@ OBJS-$(CONFIG_VFLIP_FILTER)                  +=
> vf_vflip.o
>  OBJS-$(CONFIG_VIDSTABDETECT_FILTER)          += vidstabutils.o
> vf_vidstabdetect.o
>  OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)       += vidstabutils.o
> vf_vidstabtransform.o
>  OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o
> +OBJS-$(CONFIG_VMAF_FILTER)                   += vf_vmaf.o dualinput.o
> framesync.o
>  OBJS-$(CONFIG_VSTACK_FILTER)                 += vf_stack.o framesync.o
>  OBJS-$(CONFIG_W3FDIF_FILTER)                 += vf_w3fdif.o
>  OBJS-$(CONFIG_WAVEFORM_FILTER)               += vf_waveform.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index cd35ae4..6894a6f 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -325,6 +325,7 @@ static void register_all(void)
>      REGISTER_FILTER(VIDSTABDETECT,  vidstabdetect,  vf);
>      REGISTER_FILTER(VIDSTABTRANSFORM, vidstabtransform, vf);
>      REGISTER_FILTER(VIGNETTE,       vignette,       vf);
> +    REGISTER_FILTER(VMAF,           vmaf,           vf);
>      REGISTER_FILTER(VSTACK,         vstack,         vf);
>      REGISTER_FILTER(W3FDIF,         w3fdif,         vf);
>      REGISTER_FILTER(WAVEFORM,       waveform,       vf);
> diff --git a/libavfilter/vf_vmaf.c b/libavfilter/vf_vmaf.c
> new file mode 100644
> index 0000000..12aaede
> --- /dev/null
> +++ b/libavfilter/vf_vmaf.c
> @@ -0,0 +1,429 @@
> +/*
> + * Copyright (c) 2017 Ronald S. Bultje <rsbultje@gmail.com>
> + * Copyright (c) 2017 Ashish Pratap Singh <ashk43712@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
> + * Caculate the VMAF between two input videos.
> + */
> +
> +#include <inttypes.h>
> +#include <pthread.h>
> +#include <string.h>
> +#include <libvmaf.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 "video.h"
> +
> +typedef struct VMAFContext {
> +    const AVClass *class;
> +    FFDualInputContext dinput;
> +    char *format;
> +    int width;
> +    int height;
> +    double curr_vmaf_score;
> +    double vmaf_score;
> +    uint64_t nb_frames;
> +    pthread_t vmaf_thread;
> +    pthread_attr_t attr;
> +    pthread_mutex_t lock;
> +    pthread_cond_t cond;
> +    int eof;
> +    AVFrame *gmain;
> +    AVFrame *gref;
> +    int frame_set;
> +    char *model_path;
> +    char *log_path;
> +    char *log_fmt;
> +    int disable_clip;
> +    int disable_avx;
> +    int enable_transform;
> +    int phone_model;
> +    int psnr;
> +    int ssim;
> +    int ms_ssim;
> +    char *pool;
> +    FILE *stats_file;
> +    char *stats_file_str;
> +    int stats_version;
> +    int stats_header_written;
> +    int stats_add_max;
> +    int nb_components;
> +} VMAFContext;
> +
> +#define OFFSET(x) offsetof(VMAFContext, x)
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
> +
> +static const AVOption vmaf_options[] = {
> +    {"stats_file", "Set file where to store per-frame difference
> information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0,
> 0, FLAGS },
> +    {"f",          "Set file where to store per-frame difference
> information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0,
> 0, FLAGS },
> +    {"stats_version", "Set the format version for the stats file.",
>          OFFSET(stats_version),  AV_OPT_TYPE_INT,    {.i64=1},    1, 2,
> FLAGS },
> +    {"output_max",  "Add raw stats (max values) to the output log.",
>       OFFSET(stats_add_max), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"model_path",  "Set the model to be used for computing vmaf.",
>       OFFSET(model_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
> +    {"log_path",  "Set the file path to be used to store logs.",
>   OFFSET(log_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
> +    {"log_fmt",  "Set the format of the log (xml or json).",
> OFFSET(log_fmt), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
> +    {"disable_clip",  "Disables clip for computing vmaf.",
> OFFSET(disable_clip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"disable avx",  "Disables avx for computing vmaf.",
> OFFSET(disable_avx), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"enable_transform",  "Enables transform for computing vmaf.",
>     OFFSET(enable_transform), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"phone_model",  "Invokes the phone model that will generate higher
> VMAF scores.",            OFFSET(phone_model), AV_OPT_TYPE_BOOL, {.i64=0},
> 0, 1, FLAGS},
> +    {"psnr",  "Enables computing psnr along with vmaf.",
> OFFSET(psnr), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"ssim",  "Enables computing ssim along with vmaf.",
> OFFSET(ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"ms_ssim",  "Enables computing ms-ssim along with vmaf.",
> OFFSET(ms_ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"pool",  "Set the pool method to be used for computing vmaf.",
>       OFFSET(pool), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(vmaf);
> +
> +static int read_frame_8bit(float *ref_data, float *main_data, int stride,
> double *score, void *ctx){
> +
> +    VMAFContext *s = (VMAFContext *)ctx;
> +
> +    if (s->eof == 1) {
> +        s->eof++;
> +    }
> +    else if (s->eof == 2) {
> +        return s->eof;
> +    }
> +
> +    pthread_mutex_lock(&s->lock);
> +
> +    while (s->frame_set == 0) {
> +        pthread_cond_wait(&s->cond, &s->lock);
> +    }
> +
> +    int ref_stride = s->gref->linesize[0];
> +    int main_stride = s->gmain->linesize[0];
> +
> +    uint8_t *ptr = s->gref->data[0];
> +    float *ptr1 = ref_data;
> +
> +    int h = s->height;
> +    int w = s->width;
> +
> +    int i,j;
> +
> +    for (i = 0; i < h; i++) {
> +        for ( j = 0; j < w; j++) {
> +            ptr1[j] = (float)ptr[j];
> +        }
> +        ptr += ref_stride/sizeof(*ptr);
> +        ptr1 += stride/sizeof(*ptr1);
> +    }
> +
> +    ptr = s->gmain->data[0];
> +    ptr1 = main_data;
> +
> +    for (i = 0; i < h; i++) {
> +        for (j = 0; j < w; j++) {
> +            ptr1[j] = (float)ptr[j];
> +        }
> +        ptr += main_stride/sizeof(*ptr);
> +        ptr1 += stride/sizeof(*ptr1);
> +    }
> +
> +    s->frame_set = 0;
> +
> +    pthread_cond_signal(&s->cond);
> +    pthread_mutex_unlock(&s->lock);
> +
> +    return 0;
> +}
> +
> +static int read_frame_10bit(float *ref_data, float *main_data, int
> stride, double *score, void *ctx){
> +
> +    VMAFContext *s = (VMAFContext *)ctx;
> +
> +    if (s->eof == 1) {
> +        s->eof++;
> +    }
> +    else if (s->eof == 2) {
> +        return s->eof;
> +    }
> +
> +    pthread_mutex_lock(&s->lock);
> +
> +    while (s->frame_set == 0) {
> +        pthread_cond_wait(&s->cond, &s->lock);
> +    }
> +
> +    int ref_stride = s->gref->linesize[0];
> +    int main_stride = s->gmain->linesize[0];
> +
> +    uint16_t *ptr = s->gref->data[0];
> +    float *ptr1 = ref_data;
> +
> +    int h = s->height;
> +    int w = s->width;
> +
> +    int i,j;
> +
> +    for (i = 0; i < h; i++) {
> +        for (j = 0; j < w; j++) {
> +            ptr1[j] = (float)ptr[j];
> +        }
> +        ptr += ref_stride / sizeof(*ptr);
> +        ptr1 += stride / sizeof(*ptr1);
> +    }
> +
> +    ptr = s->gmain->data[0];
> +    ptr1 = main_data;
> +
> +    for (i = 0; i < h; i++) {
> +        for (j = 0; j < w; j++) {
> +            ptr1[j] = (float)ptr[j];
> +        }
> +        ptr += main_stride / sizeof(*ptr);
> +        ptr1 += stride / sizeof(*ptr1);
> +    }
> +
> +    s->frame_set = 0;
> +
> +    pthread_cond_signal(&s->cond);
> +    pthread_mutex_unlock(&s->lock);
> +
> +    return 0;
> +}
> +
> +static void compute_vmaf_score(VMAFContext *s)
> +{
> +    void *func;
> +    if (strcmp(s->format, "yuv420p") || strcmp(s->format, "yuv422p") ||
> strcmp(s->format, "yuv444p")) {
> +        func = read_frame_8bit;
> +    }
> +    else {
> +        func = read_frame_10bit;
> +    }
> +
> +    s->vmaf_score = compute_vmaf(s->format, s->width, s->height, func,
> +                                 s->model_path, s->log_path, s->log_fmt,
> +                                 s->disable_clip, s->disable_avx,
> +                                 s->enable_transform, s->phone_model,
> +                                 s->psnr, s->ssim, s->ms_ssim, s->pool,
> s);
> +}
> +
> +static void *call_vmaf(void *ctx)
> +{
> +    VMAFContext *s = (VMAFContext *)ctx;
> +    compute_vmaf_score(s);
> +    pthread_exit(NULL);
> +}
> +
> +static AVFrame *do_vmaf(AVFilterContext *ctx, AVFrame *main, const
> AVFrame *ref)
> +{
> +    VMAFContext *s = ctx->priv;
> +
> +    pthread_mutex_lock(&s->lock);
> +
> +    while (s->frame_set != 0) {
> +        pthread_cond_wait(&s->cond, &s->lock);
> +    }
> +
> +    av_frame_ref(s->gref, ref);
> +    av_frame_ref(s->gmain, main);
> +
> +    s->frame_set = 1;
> +
> +    pthread_cond_signal(&s->cond);
> +    pthread_mutex_unlock(&s->lock);
> +
> +    return main;
> +}
> +
> +static av_cold int init(AVFilterContext *ctx)
> +{
> +    VMAFContext *s = ctx->priv;
> +
> +    if (s->stats_file_str) {
> +        if (s->stats_version < 2 && s->stats_add_max) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                   "stats_add_max was specified but stats_version < 2.\n"
> );
> +            return AVERROR(EINVAL);
> +        }
> +        if (!strcmp(s->stats_file_str, "-")) {
> +            s->stats_file = stdout;
> +        } else {
> +            s->stats_file = fopen(s->stats_file_str, "w");
> +            if (!s->stats_file) {
> +                int err = AVERROR(errno);
> +                char buf[128];
> +                av_strerror(err, buf, sizeof(buf));
> +                av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s:
> %s\n",
> +                       s->stats_file_str, buf);
> +                return err;
> +            }
> +        }
> +    }
> +
> +    s->gref = av_frame_alloc();
> +    s->gmain = av_frame_alloc();
> +
> +    pthread_mutex_init(&s->lock, NULL);
> +    pthread_cond_init (&s->cond, NULL);
> +
> +    pthread_attr_init(&s->attr);
> +
> +    s->dinput.process = do_vmaf;
> +    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)
> +{
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
> +    AVFilterContext *ctx  = inlink->dst;
> +    VMAFContext *s = ctx->priv;
> +    s->nb_components = desc->nb_components;
> +    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);
> +    }
> +    if (!(s->model_path)) {
> +        av_log(ctx, AV_LOG_ERROR, "No model specified.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    s->format = av_get_pix_fmt_name(ctx->inputs[0]->format);
> +    s->width = ctx->inputs[0]->w;
> +    s->height = ctx->inputs[0]->h;
> +
> +    int rc = pthread_create(&s->vmaf_thread, &s->attr, call_vmaf, (void
> *)s);
> +    if (rc) {
> +        av_log(ctx, AV_LOG_ERROR, "Thread creation failed.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    return 0;
> +}
> +
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    VMAFContext *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)
> +{
> +    VMAFContext *s = inlink->dst->priv;
> +    return ff_dualinput_filter_frame(&s->dinput, inlink, inpicref);
> +}
> +
> +static int request_frame(AVFilterLink *outlink)
> +{
> +    VMAFContext *s = outlink->src->priv;
> +    return ff_dualinput_request_frame(&s->dinput, outlink);
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    VMAFContext *s = ctx->priv;
> +
> +    ff_dualinput_uninit(&s->dinput);
> +
> +    if (s->stats_file && s->stats_file != stdout)
> +        fclose(s->stats_file);
> +
> +    pthread_mutex_lock(&s->lock);
> +    s->eof = 1;
> +    pthread_cond_signal(&s->cond);
> +    pthread_mutex_unlock(&s->lock);
> +
> +    pthread_join(s->vmaf_thread, NULL);
> +
> +    av_frame_free(&s->gref);
> +    av_frame_free(&s->gmain);
> +
> +    av_log(ctx, AV_LOG_INFO, "VMAF score: %f\n",s->vmaf_score);
> +
> +}
> +
> +static const AVFilterPad vmaf_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 vmaf_outputs[] = {
> +    {
> +        .name          = "default",
> +        .type          = AVMEDIA_TYPE_VIDEO,
> +        .config_props  = config_output,
> +        .request_frame = request_frame,
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_vf_vmaf = {
> +    .name          = "vmaf",
> +    .description   = NULL_IF_CONFIG_SMALL("Calculate the VMAF between
> two videos."),
> +    .init          = init,
> +    .uninit        = uninit,
> +    .query_formats = query_formats,
> +    .priv_size     = sizeof(VMAFContext),
> +    .priv_class    = &vmaf_class,
> +    .inputs        = vmaf_inputs,
> +    .outputs       = vmaf_outputs,
> +};
> --
> 2.7.4
>
>
Ronald S. Bultje June 24, 2017, 9:14 p.m. UTC | #2
Hi,

On Sat, Jun 24, 2017 at 3:50 PM, Ashish Singh <ashk43712@gmail.com> wrote:

> +typedef struct VMAFContext {
>
[..]

> +    double curr_vmaf_score;
>

Unused.


> +    uint64_t nb_frames;
>

Unused.


> +    pthread_attr_t attr;
>

This is essentially unused, so might just as well pass NULL into
pthread_create().


> +    int psnr;
> +    int ssim;
> +    int ms_ssim;
>

Hm... I see you're trying to replicate the commandline arguments here, but
we already have psnr/ssim filters by themselves. I'm not sure we need those
options here. (If others think we should keep them or you have a strong
opinion on keeping it, we can keep it.)


> +    FILE *stats_file;
> +    char *stats_file_str;
>

Unused.


> +    int stats_version;
>

Unused, and also unneeded. stats_version was added to some other filters so
we could update the stats_file format without breaking backwards
compatibility for applications expecting the old log format. This filter
has no "old" or "new" log format yet, so this isn't needed.


> +    int stats_header_written;
> +    int stats_add_max;
> +    int nb_components;
>

Unused.


> +static const AVOption vmaf_options[] = {
>
[..]

> +    {"stats_file", "Set file where to store per-frame difference
> information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0,
> 0, FLAGS },
> +    {"f",          "Set file where to store per-frame difference
> information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0,
> 0, FLAGS },
> +    {"stats_version", "Set the format version for the stats file.",
>          OFFSET(stats_version),  AV_OPT_TYPE_INT,    {.i64=1},    1, 2,
> FLAGS },
> +    {"output_max",  "Add raw stats (max values) to the output log.",
>       OFFSET(stats_add_max), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
>

Unused.


> +    {"model_path",  "Set the model to be used for computing vmaf.",
>       OFFSET(model_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
>

I would suggest to add a non-NULL default model path here (instead of {
.str = NULL }). Maybe the default path can be detected by configure?


> +    {"psnr",  "Enables computing psnr along with vmaf.",
> OFFSET(psnr), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"ssim",  "Enables computing ssim along with vmaf.",
> OFFSET(ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
> +    {"ms_ssim",  "Enables computing ms-ssim along with vmaf.",
> OFFSET(ms_ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
>

See related comment above.


> +static int read_frame_8bit(float *ref_data, float *main_data, int stride,
> double *score, void *ctx){
> +
>

{ Should go on new line for function declarations.


> +    VMAFContext *s = (VMAFContext *)ctx;
>

.. = (VMAFContext *) ctx; << note space between ")" and "ctx"


> +    if (s->eof == 1) {
> +        s->eof++;
> +    }
> +    else if (s->eof == 2) {
> +        return s->eof;
> +    }
>

Umm, this isn't what I meant with my comment earlier. There's a race
condition here where s->eof is written to by a different thread but you
read it outside the critical section within s->lock protection:


> +    pthread_mutex_lock(&s->lock);
> +
> +    while (s->frame_set == 0) {
> +        pthread_cond_wait(&s->cond, &s->lock);
> +    }
>

So at this point, you have two variables (s->eof and s->frame_set) both
accessible from s->lock. That's fine, but the problem is that you're not
checking whether s->frame_set is 1 before exiting based on s->eof values.
My suggestion is something like this:

pthread_mutex_lock(&s->lock);
while (!s->frame_set && !s->eof) {
    pthread_cond_wait(&s->cond, &s->lock);
}
if (s->frame_set) {
    .. copy data into vmaf buffers
}

int res = !s->frame_set;
s->frame_set = 0;
pthread_cond_signal(&s->cond);
pthread_mutex_unlock(&s->lock);

return ret;

+static int read_frame_10bit(float *ref_data, float *main_data, int stride,
> double *score, void *ctx){
>

I'd also recommend to tempalte the read_frame_8/10bit() functions, that
makes maintenance easier because it reduces source code size. But that can
be done later...

+static void compute_vmaf_score(VMAFContext *s)
> +{
> +    void *func;
>

int (*read_frame)(int arg1, int arg2);


> +    if (strcmp(s->format, "yuv420p") || strcmp(s->format, "yuv422p") ||
> strcmp(s->format, "yuv444p")) {
> +        func = read_frame_8bit;
>

read_frame = read_frame_8bit;


> +    }
> +    else {
>

"} else {" all on same line.

+        func = read_frame_10bit;
>

read_frame = read_frame_10bit;


> +static av_cold int init(AVFilterContext *ctx)
> +{
>
[..]

> +    if (s->stats_file_str) {
> +        if (s->stats_version < 2 && s->stats_add_max) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                   "stats_add_max was specified but stats_version < 2.\n"
> );
> +            return AVERROR(EINVAL);
> +        }
> +        if (!strcmp(s->stats_file_str, "-")) {
> +            s->stats_file = stdout;
> +        } else {
> +            s->stats_file = fopen(s->stats_file_str, "w");
> +            if (!s->stats_file) {
> +                int err = AVERROR(errno);
> +                char buf[128];
> +                av_strerror(err, buf, sizeof(buf));
> +                av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s:
> %s\n",
> +                       s->stats_file_str, buf);
> +                return err;
> +            }
> +        }
> +    }
>

I think ATM all this code can be removed.


> +static av_cold void uninit(AVFilterContext *ctx)
>
[..]

> +    if (s->stats_file && s->stats_file != stdout)
> +        fclose(s->stats_file);
>

Can be removed.


> +    av_log(ctx, AV_LOG_INFO, "VMAF score: %f\n",s->vmaf_score);
> +
> +}
>

Remove empty line at the end.

Don't forget to pthread_mutex/cond_destroy() the cond and lock variables.

Ronald
Moritz Barsnick June 26, 2017, 9:30 a.m. UTC | #3
On Sun, Jun 25, 2017 at 01:20:38 +0530, Ashish Singh wrote:
>  configure                |   5 +
>  libavfilter/Makefile     |   1 +
>  libavfilter/allfilters.c |   1 +
>  libavfilter/vf_vmaf.c    | 429 +++++++++++++++++++++++++++++++++++++++++++++++

Still missing doc/filters.texi.

> + * Caculate the VMAF between two input videos.

Still a typo.

> +        av_log(ctx, AV_LOG_ERROR, "Width and height of input videos must be same.\n");
[...]
> +        av_log(ctx, AV_LOG_ERROR, "Inputs must be of same pixel format.\n");

My previous remarks are still valid (but it probably doesn't matter).

Moritz
diff mbox

Patch

diff --git a/configure b/configure
index 5ae5227..faaf236 100755
--- a/configure
+++ b/configure
@@ -259,6 +259,7 @@  External library support:
   --enable-libtwolame      enable MP2 encoding via libtwolame [no]
   --enable-libv4l2         enable libv4l2/v4l-utils [no]
   --enable-libvidstab      enable video stabilization using vid.stab [no]
+  --enable-libvmaf         enable vmaf filter via libvmaf [no]
   --enable-libvo-amrwbenc  enable AMR-WB encoding via libvo-amrwbenc [no]
   --enable-libvorbis       enable Vorbis en/decoding via libvorbis,
                            native implementation exists [no]
@@ -1569,6 +1570,7 @@  EXTERNAL_LIBRARY_LIST="
     libtheora
     libtwolame
     libv4l2
+    libvmaf
     libvorbis
     libvpx
     libwavpack
@@ -2878,6 +2880,7 @@  libspeex_encoder_deps="libspeex"
 libspeex_encoder_select="audio_frame_queue"
 libtheora_encoder_deps="libtheora"
 libtwolame_encoder_deps="libtwolame"
+libvmaf_filter_deps="libvmaf"
 libvo_amrwbenc_encoder_deps="libvo_amrwbenc"
 libvorbis_decoder_deps="libvorbis"
 libvorbis_encoder_deps="libvorbis"
@@ -5845,6 +5848,8 @@  enabled libtwolame        && require libtwolame twolame.h twolame_init -ltwolame
                                die "ERROR: libtwolame must be installed and version must be >= 0.3.10"; }
 enabled libv4l2           && require_pkg_config libv4l2 libv4l2.h v4l2_ioctl
 enabled libvidstab        && require_pkg_config "vidstab >= 0.98" vid.stab/libvidstab.h vsMotionDetectInit
+enabled libvmaf           && { check_lib libvmaf "libvmaf.h" "compute_vmaf" -lvmaf -lstdc++ -lpthread -lm ||
+                               die "ERROR: libvmaf must be installed"; }
 enabled libvo_amrwbenc    && require libvo_amrwbenc vo-amrwbenc/enc_if.h E_IF_init -lvo-amrwbenc
 enabled libvorbis         && require libvorbis vorbis/vorbisenc.h vorbis_info_init -lvorbisenc -lvorbis -logg
 
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index f7dfe8a..1c4bd56 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -314,6 +314,7 @@  OBJS-$(CONFIG_VFLIP_FILTER)                  += vf_vflip.o
 OBJS-$(CONFIG_VIDSTABDETECT_FILTER)          += vidstabutils.o vf_vidstabdetect.o
 OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)       += vidstabutils.o vf_vidstabtransform.o
 OBJS-$(CONFIG_VIGNETTE_FILTER)               += vf_vignette.o
+OBJS-$(CONFIG_VMAF_FILTER)                   += vf_vmaf.o dualinput.o framesync.o
 OBJS-$(CONFIG_VSTACK_FILTER)                 += vf_stack.o framesync.o
 OBJS-$(CONFIG_W3FDIF_FILTER)                 += vf_w3fdif.o
 OBJS-$(CONFIG_WAVEFORM_FILTER)               += vf_waveform.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index cd35ae4..6894a6f 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -325,6 +325,7 @@  static void register_all(void)
     REGISTER_FILTER(VIDSTABDETECT,  vidstabdetect,  vf);
     REGISTER_FILTER(VIDSTABTRANSFORM, vidstabtransform, vf);
     REGISTER_FILTER(VIGNETTE,       vignette,       vf);
+    REGISTER_FILTER(VMAF,           vmaf,           vf);
     REGISTER_FILTER(VSTACK,         vstack,         vf);
     REGISTER_FILTER(W3FDIF,         w3fdif,         vf);
     REGISTER_FILTER(WAVEFORM,       waveform,       vf);
diff --git a/libavfilter/vf_vmaf.c b/libavfilter/vf_vmaf.c
new file mode 100644
index 0000000..12aaede
--- /dev/null
+++ b/libavfilter/vf_vmaf.c
@@ -0,0 +1,429 @@ 
+/*
+ * Copyright (c) 2017 Ronald S. Bultje <rsbultje@gmail.com>
+ * Copyright (c) 2017 Ashish Pratap Singh <ashk43712@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
+ * Caculate the VMAF between two input videos.
+ */
+
+#include <inttypes.h>
+#include <pthread.h>
+#include <string.h>
+#include <libvmaf.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 "video.h"
+
+typedef struct VMAFContext {
+    const AVClass *class;
+    FFDualInputContext dinput;
+    char *format;
+    int width;
+    int height;
+    double curr_vmaf_score;
+    double vmaf_score;
+    uint64_t nb_frames;
+    pthread_t vmaf_thread;
+    pthread_attr_t attr;
+    pthread_mutex_t lock;
+    pthread_cond_t cond;
+    int eof;
+    AVFrame *gmain;
+    AVFrame *gref;
+    int frame_set;
+    char *model_path;
+    char *log_path;
+    char *log_fmt;
+    int disable_clip;
+    int disable_avx;
+    int enable_transform;
+    int phone_model;
+    int psnr;
+    int ssim;
+    int ms_ssim;
+    char *pool;
+    FILE *stats_file;
+    char *stats_file_str;
+    int stats_version;
+    int stats_header_written;
+    int stats_add_max;
+    int nb_components;
+} VMAFContext;
+
+#define OFFSET(x) offsetof(VMAFContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption vmaf_options[] = {
+    {"stats_file", "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
+    {"f",          "Set file where to store per-frame difference information", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, FLAGS },
+    {"stats_version", "Set the format version for the stats file.",               OFFSET(stats_version),  AV_OPT_TYPE_INT,    {.i64=1},    1, 2, FLAGS },
+    {"output_max",  "Add raw stats (max values) to the output log.",            OFFSET(stats_add_max), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"model_path",  "Set the model to be used for computing vmaf.",            OFFSET(model_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
+    {"log_path",  "Set the file path to be used to store logs.",            OFFSET(log_path), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
+    {"log_fmt",  "Set the format of the log (xml or json).",            OFFSET(log_fmt), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
+    {"disable_clip",  "Disables clip for computing vmaf.",            OFFSET(disable_clip), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"disable avx",  "Disables avx for computing vmaf.",            OFFSET(disable_avx), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"enable_transform",  "Enables transform for computing vmaf.",            OFFSET(enable_transform), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"phone_model",  "Invokes the phone model that will generate higher VMAF scores.",            OFFSET(phone_model), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"psnr",  "Enables computing psnr along with vmaf.",            OFFSET(psnr), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"ssim",  "Enables computing ssim along with vmaf.",            OFFSET(ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"ms_ssim",  "Enables computing ms-ssim along with vmaf.",            OFFSET(ms_ssim), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, FLAGS},
+    {"pool",  "Set the pool method to be used for computing vmaf.",            OFFSET(pool), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(vmaf);
+
+static int read_frame_8bit(float *ref_data, float *main_data, int stride, double *score, void *ctx){
+
+    VMAFContext *s = (VMAFContext *)ctx;
+
+    if (s->eof == 1) {
+        s->eof++;
+    }
+    else if (s->eof == 2) {
+        return s->eof;
+    }
+
+    pthread_mutex_lock(&s->lock);
+
+    while (s->frame_set == 0) {
+        pthread_cond_wait(&s->cond, &s->lock);
+    }
+
+    int ref_stride = s->gref->linesize[0];
+    int main_stride = s->gmain->linesize[0];
+
+    uint8_t *ptr = s->gref->data[0];
+    float *ptr1 = ref_data;
+
+    int h = s->height;
+    int w = s->width;
+
+    int i,j;
+
+    for (i = 0; i < h; i++) {
+        for ( j = 0; j < w; j++) {
+            ptr1[j] = (float)ptr[j];
+        }
+        ptr += ref_stride/sizeof(*ptr);
+        ptr1 += stride/sizeof(*ptr1);
+    }
+
+    ptr = s->gmain->data[0];
+    ptr1 = main_data;
+
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            ptr1[j] = (float)ptr[j];
+        }
+        ptr += main_stride/sizeof(*ptr);
+        ptr1 += stride/sizeof(*ptr1);
+    }
+
+    s->frame_set = 0;
+
+    pthread_cond_signal(&s->cond);
+    pthread_mutex_unlock(&s->lock);
+
+    return 0;
+}
+
+static int read_frame_10bit(float *ref_data, float *main_data, int stride, double *score, void *ctx){
+
+    VMAFContext *s = (VMAFContext *)ctx;
+
+    if (s->eof == 1) {
+        s->eof++;
+    }
+    else if (s->eof == 2) {
+        return s->eof;
+    }
+
+    pthread_mutex_lock(&s->lock);
+
+    while (s->frame_set == 0) {
+        pthread_cond_wait(&s->cond, &s->lock);
+    }
+
+    int ref_stride = s->gref->linesize[0];
+    int main_stride = s->gmain->linesize[0];
+
+    uint16_t *ptr = s->gref->data[0];
+    float *ptr1 = ref_data;
+
+    int h = s->height;
+    int w = s->width;
+
+    int i,j;
+
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            ptr1[j] = (float)ptr[j];
+        }
+        ptr += ref_stride / sizeof(*ptr);
+        ptr1 += stride / sizeof(*ptr1);
+    }
+
+    ptr = s->gmain->data[0];
+    ptr1 = main_data;
+
+    for (i = 0; i < h; i++) {
+        for (j = 0; j < w; j++) {
+            ptr1[j] = (float)ptr[j];
+        }
+        ptr += main_stride / sizeof(*ptr);
+        ptr1 += stride / sizeof(*ptr1);
+    }
+
+    s->frame_set = 0;
+
+    pthread_cond_signal(&s->cond);
+    pthread_mutex_unlock(&s->lock);
+
+    return 0;
+}
+
+static void compute_vmaf_score(VMAFContext *s)
+{
+    void *func;
+    if (strcmp(s->format, "yuv420p") || strcmp(s->format, "yuv422p") || strcmp(s->format, "yuv444p")) {
+        func = read_frame_8bit;
+    }
+    else {
+        func = read_frame_10bit;
+    }
+
+    s->vmaf_score = compute_vmaf(s->format, s->width, s->height, func,
+                                 s->model_path, s->log_path, s->log_fmt,
+                                 s->disable_clip, s->disable_avx,
+                                 s->enable_transform, s->phone_model,
+                                 s->psnr, s->ssim, s->ms_ssim, s->pool, s);
+}
+
+static void *call_vmaf(void *ctx)
+{
+    VMAFContext *s = (VMAFContext *)ctx;
+    compute_vmaf_score(s);
+    pthread_exit(NULL);
+}
+
+static AVFrame *do_vmaf(AVFilterContext *ctx, AVFrame *main, const AVFrame *ref)
+{
+    VMAFContext *s = ctx->priv;
+
+    pthread_mutex_lock(&s->lock);
+
+    while (s->frame_set != 0) {
+        pthread_cond_wait(&s->cond, &s->lock);
+    }
+
+    av_frame_ref(s->gref, ref);
+    av_frame_ref(s->gmain, main);
+
+    s->frame_set = 1;
+
+    pthread_cond_signal(&s->cond);
+    pthread_mutex_unlock(&s->lock);
+
+    return main;
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    VMAFContext *s = ctx->priv;
+
+    if (s->stats_file_str) {
+        if (s->stats_version < 2 && s->stats_add_max) {
+            av_log(ctx, AV_LOG_ERROR,
+                   "stats_add_max was specified but stats_version < 2.\n" );
+            return AVERROR(EINVAL);
+        }
+        if (!strcmp(s->stats_file_str, "-")) {
+            s->stats_file = stdout;
+        } else {
+            s->stats_file = fopen(s->stats_file_str, "w");
+            if (!s->stats_file) {
+                int err = AVERROR(errno);
+                char buf[128];
+                av_strerror(err, buf, sizeof(buf));
+                av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s: %s\n",
+                       s->stats_file_str, buf);
+                return err;
+            }
+        }
+    }
+
+    s->gref = av_frame_alloc();
+    s->gmain = av_frame_alloc();
+
+    pthread_mutex_init(&s->lock, NULL);
+    pthread_cond_init (&s->cond, NULL);
+
+    pthread_attr_init(&s->attr);
+
+    s->dinput.process = do_vmaf;
+    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)
+{
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+    AVFilterContext *ctx  = inlink->dst;
+    VMAFContext *s = ctx->priv;
+    s->nb_components = desc->nb_components;
+    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);
+    }
+    if (!(s->model_path)) {
+        av_log(ctx, AV_LOG_ERROR, "No model specified.\n");
+        return AVERROR(EINVAL);
+    }
+
+    s->format = av_get_pix_fmt_name(ctx->inputs[0]->format);
+    s->width = ctx->inputs[0]->w;
+    s->height = ctx->inputs[0]->h;
+
+    int rc = pthread_create(&s->vmaf_thread, &s->attr, call_vmaf, (void *)s);
+    if (rc) {
+        av_log(ctx, AV_LOG_ERROR, "Thread creation failed.\n");
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    VMAFContext *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)
+{
+    VMAFContext *s = inlink->dst->priv;
+    return ff_dualinput_filter_frame(&s->dinput, inlink, inpicref);
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    VMAFContext *s = outlink->src->priv;
+    return ff_dualinput_request_frame(&s->dinput, outlink);
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    VMAFContext *s = ctx->priv;
+
+    ff_dualinput_uninit(&s->dinput);
+
+    if (s->stats_file && s->stats_file != stdout)
+        fclose(s->stats_file);
+
+    pthread_mutex_lock(&s->lock);
+    s->eof = 1;
+    pthread_cond_signal(&s->cond);
+    pthread_mutex_unlock(&s->lock);
+
+    pthread_join(s->vmaf_thread, NULL);
+
+    av_frame_free(&s->gref);
+    av_frame_free(&s->gmain);
+
+    av_log(ctx, AV_LOG_INFO, "VMAF score: %f\n",s->vmaf_score);
+
+}
+
+static const AVFilterPad vmaf_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 vmaf_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+        .request_frame = request_frame,
+    },
+    { NULL }
+};
+
+AVFilter ff_vf_vmaf = {
+    .name          = "vmaf",
+    .description   = NULL_IF_CONFIG_SMALL("Calculate the VMAF between two videos."),
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .priv_size     = sizeof(VMAFContext),
+    .priv_class    = &vmaf_class,
+    .inputs        = vmaf_inputs,
+    .outputs       = vmaf_outputs,
+};