[FFmpeg-devel] [PATCH v2] avfilter/vf_libvmaf: Add metadata propagation support

Yigithan Yigit yigithanyigitdevel at gmail.com
Wed Aug 28 20:29:29 EEST 2024


Ping.

Thanks


> On 26 Aug 2024, at 20:51, Yigithan Yigit <yigithanyigitdevel at gmail.com> wrote:
> 
> ---
> libavfilter/vf_libvmaf.c | 328 ++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 326 insertions(+), 2 deletions(-)
> 
> diff --git a/libavfilter/vf_libvmaf.c b/libavfilter/vf_libvmaf.c
> index f655092b20..e6707aff53 100644
> --- a/libavfilter/vf_libvmaf.c
> +++ b/libavfilter/vf_libvmaf.c
> @@ -27,8 +27,11 @@
> #include "config_components.h"
> 
> #include <libvmaf.h>
> +#include <libvmaf/version.h>
> 
> #include "libavutil/avstring.h"
> +#include "libavutil/dict.h"
> +#include "libavutil/frame.h"
> #include "libavutil/mem.h"
> #include "libavutil/opt.h"
> #include "libavutil/pixdesc.h"
> @@ -46,6 +49,31 @@
> #include "libavutil/hwcontext_cuda_internal.h"
> #endif
> 
> +#define VMAF_VERSION_INT_VER(major, minor, patch) \
> +    ((major) * 10000 + (minor) * 100 + (patch))
> +
> +#if VMAF_VERSION_INT_VER(VMAF_API_VERSION_MAJOR, VMAF_API_VERSION_MINOR, VMAF_API_VERSION_PATCH) > VMAF_VERSION_INT_VER(3, 0, 0)
> +#define CONFIG_LIBVMAF_METADATA_ENABLED 1
> +#else
> +#define CONFIG_LIBVMAF_METADATA_ENABLED 0
> +#endif
> +
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +#include <stdatomic.h>
> +
> +typedef struct FrameList {
> +    AVFrame *frame;
> +    unsigned frame_number;
> +    unsigned propagated_handlers_cnt;
> +    struct FrameList *next;
> +} FrameList;
> +
> +typedef struct CallbackStruct {
> +    struct LIBVMAFContext *s;
> +    FrameList *frame_list;
> +} CallbackStruct;
> +#endif
> +
> typedef struct LIBVMAFContext {
>     const AVClass *class;
>     FFFrameSync fs;
> @@ -56,8 +84,19 @@ typedef struct LIBVMAFContext {
>     int n_subsample;
>     char *model_cfg;
>     char *feature_cfg;
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    char *metadata_feature_cfg;
> +    struct {
> +        VmafMetadataConfiguration *metadata_cfgs;
> +        unsigned metadata_cfg_cnt;
> +    } metadata_cfg_list;
> +    CallbackStruct *cb;
> +    atomic_uint outlink_eof;
> +    atomic_uint eof_frame;
> +#endif
>     VmafContext *vmaf;
>     VmafModel **model;
> +    int flushed;
>     unsigned model_cnt;
>     unsigned frame_cnt;
>     unsigned bpc;
> @@ -77,6 +116,9 @@ static const AVOption libvmaf_options[] = {
>     {"n_subsample", "Set interval for frame subsampling used when computing vmaf.",     OFFSET(n_subsample), AV_OPT_TYPE_INT, {.i64=1}, 1, UINT_MAX, FLAGS},
>     {"model",  "Set the model to be used for computing vmaf.",                          OFFSET(model_cfg), AV_OPT_TYPE_STRING, {.str="version=vmaf_v0.6.1"}, 0, 1, FLAGS},
>     {"feature",  "Set the feature to be used for computing vmaf.",                      OFFSET(feature_cfg), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 1, FLAGS},
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    {"metadata_handler",  "Set the feature to be propagated as metadata.",              OFFSET(metadata_feature_cfg), AV_OPT_TYPE_STRING, {.str="name=vmaf"}, 0, 1, FLAGS},
> +#endif
>     { NULL }
> };
> 
> @@ -105,6 +147,123 @@ static enum VmafPixelFormat pix_fmt_map(enum AVPixelFormat av_pix_fmt)
>     }
> }
> 
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +static int add_to_frame_list(FrameList **head, AVFrame *frame, unsigned frame_number)
> +{
> +    FrameList *new_frame = av_malloc(sizeof(FrameList));
> +    if (!new_frame)
> +        return AVERROR(ENOMEM);
> +
> +    new_frame->frame = frame;
> +    new_frame->frame_number = frame_number;
> +    new_frame->propagated_handlers_cnt = 0;
> +    new_frame->next = NULL;
> +
> +    if (*head == NULL) {
> +        *head = new_frame;
> +    } else {
> +        FrameList *current = *head;
> +        while (current->next != NULL) {
> +            current = current->next;
> +        }
> +        current->next = new_frame;
> +    }
> +
> +    return 0;
> +}
> +
> +static int remove_from_frame_list(FrameList **frame_list, unsigned frame_number)
> +{
> +    FrameList *cur = *frame_list;
> +    FrameList *prev = NULL;
> +
> +    while (cur) {
> +        if (cur->frame_number == frame_number) {
> +            if (prev)
> +                prev->next = cur->next;
> +            else
> +                *frame_list = cur->next;
> +            av_free(cur);
> +            return 0;
> +        }
> +        prev = cur;
> +        cur = cur->next;
> +    }
> +
> +    return AVERROR(EINVAL);
> +}
> +
> +static int free_frame_list(FrameList **frame_list)
> +{
> +    FrameList *cur = *frame_list;
> +    while (cur) {
> +        FrameList *next = cur->next;
> +        av_frame_free(&cur->frame);
> +        av_free(cur);
> +        cur = next;
> +    }
> +    *frame_list = NULL;
> +    return 0;
> +}
> +
> +static FrameList* get_frame_from_frame_list(FrameList *frame_list,
> +                                          unsigned frame_number)
> +{
> +    FrameList *cur = frame_list;
> +    while (cur) {
> +        if (cur->frame_number == frame_number)
> +            return cur;
> +        cur = cur->next;
> +    }
> +    return NULL;
> +}
> +
> +static void set_meta(void *data, VmafMetadata *metadata)
> +{
> +    int err = 0;
> +    FrameList *current_frame = NULL;
> +    CallbackStruct *cb = data;
> +    char value[128], key[128];
> +    snprintf(value, sizeof(value), "%0.2f", metadata->score);
> +    snprintf(key, sizeof(key), "%s.%d", metadata->feature_name, metadata->picture_index);
> +
> +    current_frame = get_frame_from_frame_list(cb->frame_list, metadata->picture_index);
> +    if (!current_frame) {
> +        av_log(NULL, AV_LOG_ERROR, "could not find frame with index: %d\n",
> +               metadata->picture_index);
> +        return;
> +    }
> +
> +    err = av_dict_set(&current_frame->frame->metadata, key, value, 0);
> +    if (err < 0)
> +        av_log(NULL, AV_LOG_ERROR, "could not set metadata: %s\n", key);
> +
> +    current_frame->propagated_handlers_cnt++;
> +
> +    if (current_frame->propagated_handlers_cnt == cb->s->metadata_cfg_list.metadata_cfg_cnt) {
> +        FrameList *cur = cb->frame_list;
> +        // This code block allows to send frames monotonically
> +        while(cur && cur->frame_number <= metadata->picture_index) {
> +            if (cur->propagated_handlers_cnt == cb->s->metadata_cfg_list.metadata_cfg_cnt) {
> +                FrameList *next;
> +                // Check outlink is closed
> +                if (!cb->s->outlink_eof) {
> +                    av_log(cb->s->fs.parent, AV_LOG_DEBUG, "VMAF feature: %d, score: %f\n", cur->frame_number, metadata->score);
> +                    cb->s->eof_frame = cur->frame_number;
> +                    if(ff_filter_frame(cb->s->fs.parent->outputs[0], cur->frame))
> +                        return;
> +                }
> +                next = cur->next;
> +                remove_from_frame_list(&cb->frame_list, cur->frame_number);
> +                cur = next;
> +            }
> +            else
> +                break;
> +        }
> +    }
> +}
> +#endif
> +
> static int copy_picture_data(AVFrame *src, VmafPicture *dst, unsigned bpc)
> {
>     const int bytes_per_value = bpc > 8 ? 2 : 1;
> @@ -160,13 +319,28 @@ static int do_vmaf(FFFrameSync *fs)
>         return AVERROR(ENOMEM);
>     }
> 
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    err = add_to_frame_list(&s->cb->frame_list, dist, s->frame_cnt);
> +    if (err) {
> +        av_log(s, AV_LOG_ERROR, "problem during add_to_frame_list.\n");
> +        return AVERROR(ENOMEM);
> +    }
> +#endif
> +
>     err = vmaf_read_pictures(s->vmaf, &pic_ref, &pic_dist, s->frame_cnt++);
>     if (err) {
>         av_log(s, AV_LOG_ERROR, "problem during vmaf_read_pictures.\n");
>         return AVERROR(EINVAL);
>     }
> 
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    if (s->metadata_cfg_list.metadata_cfg_cnt)
> +        return 0;
> +    else
> +        return ff_filter_frame(ctx->outputs[0], dist);
> +#else
>     return ff_filter_frame(ctx->outputs[0], dist);
> +#endif
> }
> 
> static AVDictionary **delimited_dict_parse(char *str, unsigned *cnt)
> @@ -408,6 +582,83 @@ exit:
>     return err;
> }
> 
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +static int parse_metadata_handlers(AVFilterContext *ctx)
> +{
> +    LIBVMAFContext *s = ctx->priv;
> +    AVDictionary **dict;
> +    unsigned dict_cnt;
> +    int err = 0;
> +
> +    if (!s->metadata_feature_cfg)
> +        return 0;
> +
> +    dict_cnt = 0;
> +    dict = delimited_dict_parse(s->metadata_feature_cfg, &dict_cnt);
> +    if (!dict) {
> +        av_log(ctx, AV_LOG_ERROR,
> +               "could not parse metadata feature config: %s\n",
> +               s->metadata_feature_cfg);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    for (unsigned i = 0; i < dict_cnt; i++) {
> +        VmafMetadataConfiguration *metadata_cfg = av_calloc(1, sizeof(*metadata_cfg));
> +        const AVDictionaryEntry *e = NULL;
> +        char *feature_name = NULL;
> +
> +        while (e = av_dict_iterate(dict[i], e)) {
> +            if (!strcmp(e->key, "name")) {
> +                metadata_cfg->feature_name = av_strdup(e->value);
> +                continue;
> +            }
> +        }
> +
> +        metadata_cfg->data = s->cb;
> +        metadata_cfg->callback = &set_meta;
> +
> +        err = vmaf_register_metadata_handler(s->vmaf, *metadata_cfg);
> +        if (err) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                   "problem during vmaf_register_metadata_handler: %s\n",
> +                   feature_name);
> +            goto exit;
> +        }
> +
> +        s->metadata_cfg_list.metadata_cfgs = av_realloc(s->metadata_cfg_list.metadata_cfgs,
> +                                             (s->metadata_cfg_list.metadata_cfg_cnt + 1) *
> +                                             sizeof(*s->metadata_cfg_list.metadata_cfgs));
> +        if (!s->metadata_cfg_list.metadata_cfgs) {
> +            err = AVERROR(ENOMEM);
> +            goto exit;
> +        }
> +
> +        s->metadata_cfg_list.metadata_cfgs[s->metadata_cfg_list.metadata_cfg_cnt++] = *metadata_cfg;
> +    }
> +
> +exit:
> +    for (unsigned i = 0; i < dict_cnt; i++) {
> +        if (dict[i])
> +            av_dict_free(&dict[i]);
> +    }
> +    av_free(dict);
> +    return err;
> +}
> +
> +static int init_metadata(AVFilterContext *ctx)
> +{
> +    LIBVMAFContext *s = ctx->priv;
> +
> +    s->cb = av_calloc(1, sizeof(CallbackStruct));
> +    if (!s->cb)
> +        return AVERROR(ENOMEM);
> +
> +    s->cb->s = s;
> +
> +    return 0;
> +}
> +#endif
> +
> static enum VmafLogLevel log_level_map(int log_level)
> {
>     switch (log_level) {
> @@ -441,6 +692,16 @@ static av_cold int init(AVFilterContext *ctx)
>     if (err)
>         return AVERROR(EINVAL);
> 
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    err = init_metadata(ctx);
> +    if (err)
> +        return err;
> +
> +    err = parse_metadata_handlers(ctx);
> +    if (err)
> +        return err;
> +#endif
> +
>     err = parse_models(ctx);
>     if (err)
>         return err;
> @@ -518,6 +779,38 @@ static int config_output(AVFilterLink *outlink)
> static int activate(AVFilterContext *ctx)
> {
>     LIBVMAFContext *s = ctx->priv;
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    // There are 2 cases for metadata propagation:
> +    // 1. Where the case that outlink closes
> +    // 2. Where inlink closes
> +    // Case 1:
> +    //   In this case we need check outlink somehow for the status in every iteration.
> +    //   If outlink is not wanting frame anymore, we need to proceed with uninit with setting inlink.
> +    //   But nature of multithreading settting eof inside the activate call can make sync issues and
> +    //   can lead to extra propagated frames. Atomic variables are used to avoid this.
> +    // Case 2:
> +    //   This case relatively easy to handle. Because of calculation of vmaf score takes time
> +    //   So `do_vmaf` buffers many of frames before sending to outlink that causes
> +    //   premature close of outlink.
> +    //   Checking inlink status is enough and if inlink == eof flushing vmaf is enough for this.
> +    int64_t pts;
> +    int status, ret = 0;
> +
> +    if (ff_outlink_get_status(ctx->outputs[0]))
> +        s->outlink_eof = 1;
> +
> +    if (ff_inlink_acknowledge_status(ctx->inputs[0], &status, &pts) &&
> +        ff_inlink_acknowledge_status(ctx->inputs[1], &status, &pts)) {
> +        if (!s->flushed) {
> +            ret = vmaf_read_pictures(s->vmaf, NULL, NULL, 0);
> +            if (ret)
> +                av_log(ctx, AV_LOG_ERROR,
> +                       "problem flushing libvmaf context.\n");
> +            else
> +                s->flushed = 1;
> +        }
> +    }
> +#endif
>     return ff_framesync_activate(&s->fs);
> }
> 
> @@ -556,21 +849,52 @@ static av_cold void uninit(AVFilterContext *ctx)
>     LIBVMAFContext *s = ctx->priv;
>     int err = 0;
> 
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    if (!s->outlink_eof)
> +        s->outlink_eof = 1;
> +#endif
> +
>     ff_framesync_uninit(&s->fs);
> 
>     if (!s->frame_cnt)
>         goto clean_up;
> 
> -    err = vmaf_read_pictures(s->vmaf, NULL, NULL, 0);
> +    if (!s->flushed) {
> +        err = vmaf_read_pictures(s->vmaf, NULL, NULL, 0);
> +        if (err) {
> +            av_log(ctx, AV_LOG_ERROR,
> +                   "problem flushing libvmaf context.\n");
> +        } else
> +            s->flushed = 1;
> +    }
> +
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +    if (s->metadata_cfg_list.metadata_cfgs) {
> +        for (unsigned i = 0; i < s->metadata_cfg_list.metadata_cfg_cnt; i++) {
> +            av_free(s->metadata_cfg_list.metadata_cfgs[i].feature_name);
> +        }
> +        av_free(s->metadata_cfg_list.metadata_cfgs);
> +    }
> +
> +    err = free_frame_list(&s->cb->frame_list);
>     if (err) {
>         av_log(ctx, AV_LOG_ERROR,
> -               "problem flushing libvmaf context.\n");
> +               "problem freeing frame list.\n");
>     }
> +#endif
> 
>     for (unsigned i = 0; i < s->model_cnt; i++) {
>         double vmaf_score;
> +
> +#if CONFIG_LIBVMAF_METADATA_ENABLED
> +        err = vmaf_score_pooled(s->vmaf, s->model[i], pool_method_map(s->pool),
> +                                &vmaf_score, 0, s->eof_frame);
> +        av_log(ctx, AV_LOG_DEBUG, "frame: %d frame_cnt %d\n", s->eof_frame, s->frame_cnt - 1);
> +#else
>         err = vmaf_score_pooled(s->vmaf, s->model[i], pool_method_map(s->pool),
>                                 &vmaf_score, 0, s->frame_cnt - 1);
> +#endif
> +
>         if (err) {
>             av_log(ctx, AV_LOG_ERROR,
>                    "problem getting pooled vmaf score.\n");
> --
> 2.45.2
> 


More information about the ffmpeg-devel mailing list