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

Ronald S. Bultje rsbultje at gmail.com
Sun Jun 25 00:14:29 EEST 2017


Hi,

On Sat, Jun 24, 2017 at 3:50 PM, Ashish Singh <ashk43712 at 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


More information about the ffmpeg-devel mailing list