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

Yigithan Yigit yigithanyigitdevel at gmail.com
Mon Aug 26 20:51:32 EEST 2024


---
 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