From 9b67dedf2bb250db21b3343a6d49470ffdc47b52 Mon Sep 17 00:00:00 2001 From: Yigithan Yigit Date: Thu, 12 Sep 2024 02:06:24 +0300 Subject: [PATCH 01/12] Modifiying api for support indiviual metric propagation. --- libvmaf/include/libvmaf/libvmaf.h | 41 ++++++++++++++++++++++++- libvmaf/src/feature/feature_collector.c | 20 ++++++++++++ libvmaf/src/feature/feature_collector.h | 3 ++ libvmaf/src/libvmaf.c | 25 ++++++++++++++- libvmaf/src/metadata_handler.c | 1 + libvmaf/test/test_predict.c | 4 ++- 6 files changed, 91 insertions(+), 3 deletions(-) diff --git a/libvmaf/include/libvmaf/libvmaf.h b/libvmaf/include/libvmaf/libvmaf.h index 11d0d1190..d723c2588 100644 --- a/libvmaf/include/libvmaf/libvmaf.h +++ b/libvmaf/include/libvmaf/libvmaf.h @@ -257,6 +257,7 @@ int vmaf_feature_score_at_index(VmafContext *vmaf, const char *feature_name, * Metadata structure. * * @param feature_name Name of the feature to fetch. + * vmaf going to take ownership of this string. * * @param picture_index Picture index. * @@ -285,6 +286,31 @@ typedef struct VmafMetadataConfiguration { void *data; } VmafMetadataConfiguration; + +/** + * Metadata flags. + * + * Features can provide additional metrics about the score. To propagate those, + * the `VmafMetadataFlags` enum should be used for specifying which information should be + * propagated to the metadata. + * + * @param VMAF_METADATA_FLAG_NONE No flags. + * + * @param VMAF_METADATA_FLAG_FEATURE Include all indivual metrics. + * + * @param VMAF_METADATA_FLAG_MODEL Propagate only model score. + * + * @param VMAF_METADATA_FLAG_FEATURE_NAME Propagate only given feature name.. + * + */ + +enum VmafMetadataFlags { + VMAF_METADATA_FLAG_NONE = 0, + VMAF_METADATA_FLAG_FEATURE = 1 << 0, + VMAF_METADATA_FLAG_MODEL = 1 << 1, + VMAF_METADATA_FLAG_FEATURE_NAME = 1 << 2, +}; + /** * Register a callback to receive VMAF metadata. * @@ -296,7 +322,20 @@ typedef struct VmafMetadataConfiguration { * @return 0 on success, or < 0 (a negative errno code) on error. */ -int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg); +int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg, uint64_t flags); + +/** + * Get the number of registered metadata handlers. + * + * @param vmaf The VMAF context allocated with `vmaf_init()`. + * + * @param count Number of registered metadata handlers. + * + * + * @return 0 on success, or < 0 (a negative errno code) on error. + */ + +int vmaf_get_metadata_handler_count(VmafContext *vmaf, unsigned *count); /** * Pooled VMAF score for a specific interval. diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index 2eb2ec643..888b7814a 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -27,6 +27,7 @@ #include "metadata_handler.h" #include "feature_collector.h" #include "feature_name.h" +#include "feature_extractor.h" #include "libvmaf/libvmaf.h" #include "log.h" #include "predict.h" @@ -290,6 +291,25 @@ int vmaf_feature_collector_register_metadata(VmafFeatureCollector *feature_colle return 0; } +int vmaf_feature_collector_get_metadata_count(VmafFeatureCollector *feature_collector, + unsigned *count) +{ + if (!feature_collector) return -EINVAL; + if (!count) return -EINVAL; + + VmafCallbackList *metadata = feature_collector->metadata; + unsigned cnt = 0; + VmafCallbackItem *iter = metadata ? metadata->head : NULL; + while (iter) { + cnt++; + iter = iter->next; + } + + *count = cnt; + + return 0; +} + static FeatureVector *find_feature_vector(VmafFeatureCollector *fc, const char *feature_name) { diff --git a/libvmaf/src/feature/feature_collector.h b/libvmaf/src/feature/feature_collector.h index 83745bf8c..8d07450bc 100644 --- a/libvmaf/src/feature/feature_collector.h +++ b/libvmaf/src/feature/feature_collector.h @@ -70,6 +70,9 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, int vmaf_feature_collector_register_metadata(VmafFeatureCollector *feature_collector, VmafMetadataConfiguration metadata_cfg); +int vmaf_feature_collector_get_metadata_count(VmafFeatureCollector *feature_collector, + unsigned *count); + int vmaf_feature_collector_append_with_dict(VmafFeatureCollector *fc, VmafDictionary *dict, const char *feature_name, double score, unsigned index); diff --git a/libvmaf/src/libvmaf.c b/libvmaf/src/libvmaf.c index 31340e22c..95338b20e 100644 --- a/libvmaf/src/libvmaf.c +++ b/libvmaf/src/libvmaf.c @@ -744,13 +744,36 @@ int vmaf_read_pictures(VmafContext *vmaf, VmafPicture *ref, VmafPicture *dist, return err; } -int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg) +int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg, uint64_t flags) { if (!vmaf) return -EINVAL; + if (flags & VMAF_METADATA_FLAG_FEATURE) { + VmafFeatureExtractor *fex = vmaf_get_feature_extractor_by_name(cfg.feature_name); + if (!fex) return -EINVAL; + int err = 0; + for (unsigned i = 0; fex->provided_features[i] != NULL; i++) { + VmafMetadataConfiguration new_cfg = { 0 }; + new_cfg.data = cfg.data; + new_cfg.callback = cfg.callback; + new_cfg.feature_name = strdup(fex->provided_features[i]); + err = vmaf_feature_collector_register_metadata(vmaf->feature_collector, new_cfg); + if (err) return err; + } + return 0; + } + return vmaf_feature_collector_register_metadata(vmaf->feature_collector, cfg); } +int vmaf_get_metadata_handler_count(VmafContext *vmaf, unsigned *count) +{ + if (!vmaf) return -EINVAL; + if (!count) return -EINVAL; + + return vmaf_feature_collector_get_metadata_count(vmaf->feature_collector, count); +} + int vmaf_feature_score_at_index(VmafContext *vmaf, const char *feature_name, double *score, unsigned index) { diff --git a/libvmaf/src/metadata_handler.c b/libvmaf/src/metadata_handler.c index 99f69c212..463571c69 100644 --- a/libvmaf/src/metadata_handler.c +++ b/libvmaf/src/metadata_handler.c @@ -69,6 +69,7 @@ int vmaf_metadata_destroy(VmafCallbackList *metadata) VmafCallbackItem *iter = metadata->head; while (iter) { VmafCallbackItem *next = iter->next; + free(iter->metadata_cfg.feature_name); free(iter); iter = next; } diff --git a/libvmaf/test/test_predict.c b/libvmaf/test/test_predict.c index 3fe970047..e3bc65378 100644 --- a/libvmaf/test/test_predict.c +++ b/libvmaf/test/test_predict.c @@ -88,7 +88,7 @@ static char* test_propagate_metadata() }; VmafMetadataConfiguration m = { - .feature_name = "vmaf", + .feature_name = strdup("vmaf"), .callback = set_meta, .data = &meta_data, }; @@ -127,6 +127,7 @@ static char* test_propagate_metadata() vmaf_feature_collector_destroy(feature_collector); m.data = NULL; + m.feature_name = strdup("vmaf"); err = vmaf_feature_collector_init(&feature_collector); mu_assert("problem during vmaf_feature_collector_init", !err); @@ -142,6 +143,7 @@ static char* test_propagate_metadata() vmaf_feature_collector_destroy(feature_collector); m.callback = NULL; + m.feature_name = strdup("vmaf"); err = vmaf_feature_collector_init(&feature_collector); mu_assert("problem during vmaf_feature_collector_init", !err); From be55c025022601422b26ae32804d1a41d677825e Mon Sep 17 00:00:00 2001 From: Yigithan Yigit Date: Thu, 12 Sep 2024 03:00:06 +0300 Subject: [PATCH 02/12] bugfix --- libvmaf/src/metadata_handler.c | 3 ++- libvmaf/test/test_propagate_metadata.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libvmaf/src/metadata_handler.c b/libvmaf/src/metadata_handler.c index 463571c69..8fe3a069a 100644 --- a/libvmaf/src/metadata_handler.c +++ b/libvmaf/src/metadata_handler.c @@ -69,7 +69,8 @@ int vmaf_metadata_destroy(VmafCallbackList *metadata) VmafCallbackItem *iter = metadata->head; while (iter) { VmafCallbackItem *next = iter->next; - free(iter->metadata_cfg.feature_name); + if (iter->metadata_cfg.feature_name) + free(iter->metadata_cfg.feature_name); free(iter); iter = next; } diff --git a/libvmaf/test/test_propagate_metadata.c b/libvmaf/test/test_propagate_metadata.c index ee4faced3..1c857432e 100644 --- a/libvmaf/test/test_propagate_metadata.c +++ b/libvmaf/test/test_propagate_metadata.c @@ -55,7 +55,7 @@ static char *test_propagate_metadata_append() int err = vmaf_metadata_init(&propagate_metadata); mu_assert("problem during vmaf_propagate_metadata_init", !err); - VmafMetadataConfiguration metadata_config; + VmafMetadataConfiguration metadata_config = {0}; metadata_config.callback = set_meta; metadata_config.data = NULL; From 31155366a24c15fff846af28eeb613a07b3cc125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Wed, 29 Jan 2025 04:18:57 +0300 Subject: [PATCH 03/12] libvmaf: simplify metadata propagation by deferring until vmaf scores are ready The metadata propagation system has been significantly refactored to reduce complexity and threading issues: - Changed from per-feature propagation to propagating metadata when VMAF scores are complete - Added frame index tracking in metadata handler to ensure ordered processing - Added test coverage for non-monotonic frame processing scenarios This change eliminates the need for complex reordering logic at the caller level and reduces potential thread synchronization issues by only propagating metadata when all necessary metrics are calculated. --- libvmaf/include/libvmaf/libvmaf.h | 2 +- libvmaf/src/feature/feature_collector.c | 84 ++++++++++++------- libvmaf/src/libvmaf.c | 17 +--- libvmaf/src/metadata_handler.c | 2 + libvmaf/src/metadata_handler.h | 4 +- libvmaf/test/test_predict.c | 107 +++++++++++++++++++++++- 6 files changed, 164 insertions(+), 52 deletions(-) diff --git a/libvmaf/include/libvmaf/libvmaf.h b/libvmaf/include/libvmaf/libvmaf.h index d723c2588..a35e6bdd9 100644 --- a/libvmaf/include/libvmaf/libvmaf.h +++ b/libvmaf/include/libvmaf/libvmaf.h @@ -322,7 +322,7 @@ enum VmafMetadataFlags { * @return 0 on success, or < 0 (a negative errno code) on error. */ -int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg, uint64_t flags); +int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg); /** * Get the number of registered metadata handlers. diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index 888b7814a..ac4892057 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -20,18 +20,23 @@ #include #include #include +#include #include #include #include "dict.h" +#include "libvmaf/model.h" #include "metadata_handler.h" #include "feature_collector.h" #include "feature_name.h" #include "feature_extractor.h" #include "libvmaf/libvmaf.h" +#include "feature/alias.h" #include "log.h" #include "predict.h" +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + static int aggregate_vector_init(AggregateVector *aggregate_vector) { if (!aggregate_vector) return -EINVAL; @@ -367,48 +372,63 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, int res = 0; - VmafCallbackItem *metadata_iter = feature_collector->metadata ? - feature_collector->metadata->head : NULL; - while (metadata_iter) { - // Check current feature name is the same as the metadata feature name - if (!strcmp(metadata_iter->metadata_cfg.feature_name, feature_name)) { - - // Call the callback function with the metadata feature name - VmafMetadata data = { - .feature_name = metadata_iter->metadata_cfg.feature_name, - .picture_index = picture_index, - .score = score, - }; - metadata_iter->metadata_cfg.callback(metadata_iter->metadata_cfg.data, &data); - // Move to the next metadata - goto next_metadata; - } + VmafPredictModel *model_iter = feature_collector->models; - VmafPredictModel *model_iter = feature_collector->models; +while (model_iter) { + VmafModel *model = model_iter->model; + bool needs_computation = false; - // If metadata feature name is not the same as the current feature feature_name - // Check if metadata feature name is the predicted feature - while (model_iter) { - VmafModel *model = model_iter->model; + // Check if current score needs computation + pthread_mutex_unlock(&(feature_collector->lock)); + res = vmaf_feature_collector_get_score(feature_collector, model->name, &score, picture_index); + needs_computation = (res != 0); + pthread_mutex_lock(&(feature_collector->lock)); - pthread_mutex_unlock(&(feature_collector->lock)); - res = vmaf_feature_collector_get_score(feature_collector, - model->name, &score, picture_index); - pthread_mutex_lock(&(feature_collector->lock)); + if (needs_computation) { + // Compute the current frame's score + pthread_mutex_unlock(&(feature_collector->lock)); + res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); + pthread_mutex_lock(&(feature_collector->lock)); - if (res) { + if (!res) { + // Process all pending frames up to current index in order + unsigned process_index = feature_collector->metadata->last_seen_lowest_index; + feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); + + while (process_index <= feature_collector->metadata->last_seen_highest_index) { + bool frame_ready = true; + + // First check if this frame's score is ready pthread_mutex_unlock(&(feature_collector->lock)); - res |= vmaf_predict_score_at_index(model, feature_collector, - picture_index, &score, true, true, 0); + if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { + frame_ready = false; + } pthread_mutex_lock(&(feature_collector->lock)); + + if (!frame_ready) break; // Stop at first unready frame + + // Frame is ready, trigger callbacks for all features + for (unsigned j = 0; j < feature_collector->cnt; j++) { + VmafMetadata data = { + .feature_name = feature_collector->feature_vector[j]->name, + .picture_index = process_index, + .score = feature_collector->feature_vector[j]->score[process_index].value, + }; + + // Call all metadata callbacks + feature_collector->metadata->head->metadata_cfg.callback( + feature_collector->metadata->head->metadata_cfg.data, &data); + } + + process_index++; + feature_collector->metadata->last_seen_lowest_index = process_index; } - model_iter = model_iter->next; } - - next_metadata: - metadata_iter = metadata_iter->next; } + model_iter = model_iter->next; +} + unlock: feature_collector->timer.end = clock(); pthread_mutex_unlock(&(feature_collector->lock)); diff --git a/libvmaf/src/libvmaf.c b/libvmaf/src/libvmaf.c index 95338b20e..bef8f85bb 100644 --- a/libvmaf/src/libvmaf.c +++ b/libvmaf/src/libvmaf.c @@ -744,25 +744,10 @@ int vmaf_read_pictures(VmafContext *vmaf, VmafPicture *ref, VmafPicture *dist, return err; } -int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg, uint64_t flags) +int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg) { if (!vmaf) return -EINVAL; - if (flags & VMAF_METADATA_FLAG_FEATURE) { - VmafFeatureExtractor *fex = vmaf_get_feature_extractor_by_name(cfg.feature_name); - if (!fex) return -EINVAL; - int err = 0; - for (unsigned i = 0; fex->provided_features[i] != NULL; i++) { - VmafMetadataConfiguration new_cfg = { 0 }; - new_cfg.data = cfg.data; - new_cfg.callback = cfg.callback; - new_cfg.feature_name = strdup(fex->provided_features[i]); - err = vmaf_feature_collector_register_metadata(vmaf->feature_collector, new_cfg); - if (err) return err; - } - return 0; - } - return vmaf_feature_collector_register_metadata(vmaf->feature_collector, cfg); } diff --git a/libvmaf/src/metadata_handler.c b/libvmaf/src/metadata_handler.c index 8fe3a069a..ce51dda5c 100644 --- a/libvmaf/src/metadata_handler.c +++ b/libvmaf/src/metadata_handler.c @@ -31,6 +31,8 @@ int vmaf_metadata_init(VmafCallbackList **const metadata) if (!metadata_s) goto fail; metadata_s->head = NULL; + metadata_s->last_seen_highest_index = 0; + metadata_s->last_seen_lowest_index = 0; return 0; diff --git a/libvmaf/src/metadata_handler.h b/libvmaf/src/metadata_handler.h index f5f781fa4..c6439ec71 100644 --- a/libvmaf/src/metadata_handler.h +++ b/libvmaf/src/metadata_handler.h @@ -23,13 +23,13 @@ typedef struct VmafCallbackItem { VmafMetadataConfiguration metadata_cfg; - void (*callback)(void *, VmafMetadata *); - void *data; struct VmafCallbackItem *next; } VmafCallbackItem; typedef struct VmafCallbackList{ VmafCallbackItem *head; + unsigned last_seen_highest_index; + unsigned last_seen_lowest_index; } VmafCallbackList; int vmaf_metadata_init(VmafCallbackList **const metadata); diff --git a/libvmaf/test/test_predict.c b/libvmaf/test/test_predict.c index e3bc65378..04752caf0 100644 --- a/libvmaf/test/test_predict.c +++ b/libvmaf/test/test_predict.c @@ -26,13 +26,13 @@ #include #include +#include typedef struct { VmafDictionary **metadata; int flags; } MetaStruct; - static char *test_predict_score_at_index() { int err; @@ -77,6 +77,25 @@ void set_meta(void *data, VmafMetadata *metadata) vmaf_dictionary_set(meta->metadata, key, value, meta->flags); } +static int g_callback_order[4] = {-1, -1, -1, -1}; +static int g_callback_count = 0; + +static void test_non_monotonic_callback(void *data, VmafMetadata *m) +{ + if (!data) return; + MetaStruct *meta = data; + + if (!strcmp("vmaf", m->feature_name)) + // Track callback order + g_callback_order[m->picture_index] = g_callback_count++; + + // Store in dictionary for verification + char key[32], value[32]; + snprintf(key, sizeof(key), "vmaf_%d", m->picture_index); + snprintf(value, sizeof(value), "%f", m->score); + vmaf_dictionary_set(meta->metadata, key, value, meta->flags); +} + static char* test_propagate_metadata() { int err; @@ -157,6 +176,91 @@ static char* test_propagate_metadata() } +static char *test_propagate_metadata_non_monotonic() +{ + int err; + + // Reset global counters + g_callback_count = 0; + for (int i = 0; i < 4; i++) { + g_callback_order[i] = -1; + } + + // Setup dictionary to store callback results + VmafDictionary *dict = NULL; + MetaStruct meta_data = { + .metadata = &dict, + .flags = 0, + }; + + VmafMetadataConfiguration m = { + .feature_name = strdup("vmaf"), + .callback = test_non_monotonic_callback, + .data = &meta_data, + }; + + // Initialize feature collector + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata", !err); + + // Load VMAF model + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem during vmaf_model_load", !err); + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem during vmaf_mount_model", !err); + + // Simulate non-monotonic VMAF score computations + // Frame order: 3, 0, 2, 1 + for (unsigned i = 0; i < model->n_features; i++) { + // Frame 3 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 3); + mu_assert("problem appending frame 3", !err); + + // Frame 0 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 70., 0); + mu_assert("problem appending frame 0", !err); + + // Frame 2 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 80., 2); + mu_assert("problem appending frame 2", !err); + + // Frame 1 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 90., 1); + mu_assert("problem appending frame 1", !err); + } + + // Verify callback order is monotonic regardless of computation order + mu_assert("Frame 0 callback not first", g_callback_order[0] == 0); + mu_assert("Frame 1 callback not second", g_callback_order[1] == 1); + mu_assert("Frame 2 callback not third", g_callback_order[2] == 2); + mu_assert("Frame 3 callback not fourth", g_callback_order[3] == 3); + + // Verify all frame scores were propagated + for (int i = 0; i < 4; i++) { + char key[32]; + snprintf(key, sizeof(key), "vmaf_%d", i); + VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, key, 0); + mu_assert("Missing frame score in metadata", e != NULL); + } + + vmaf_feature_collector_destroy(feature_collector); + vmaf_model_destroy(model); + return NULL; +} + static char *test_find_linear_function_parameters() { int err; @@ -274,5 +378,6 @@ char *run_tests() mu_run_test(test_find_linear_function_parameters); mu_run_test(test_piecewise_linear_mapping); mu_run_test(test_propagate_metadata); + mu_run_test(test_propagate_metadata_non_monotonic); return NULL; } From 855d7aa8f07883b1585212bc4cfc6b6334fb53b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Wed, 29 Jan 2025 04:37:34 +0300 Subject: [PATCH 04/12] refactor: revert unnecessary changes --- libvmaf/include/libvmaf/libvmaf.h | 39 ------------------------- libvmaf/src/feature/feature_collector.c | 19 ------------ libvmaf/src/libvmaf.c | 8 ----- libvmaf/src/metadata_handler.c | 2 -- libvmaf/test/test_predict.c | 6 ++-- 5 files changed, 3 insertions(+), 71 deletions(-) diff --git a/libvmaf/include/libvmaf/libvmaf.h b/libvmaf/include/libvmaf/libvmaf.h index a35e6bdd9..11d0d1190 100644 --- a/libvmaf/include/libvmaf/libvmaf.h +++ b/libvmaf/include/libvmaf/libvmaf.h @@ -257,7 +257,6 @@ int vmaf_feature_score_at_index(VmafContext *vmaf, const char *feature_name, * Metadata structure. * * @param feature_name Name of the feature to fetch. - * vmaf going to take ownership of this string. * * @param picture_index Picture index. * @@ -286,31 +285,6 @@ typedef struct VmafMetadataConfiguration { void *data; } VmafMetadataConfiguration; - -/** - * Metadata flags. - * - * Features can provide additional metrics about the score. To propagate those, - * the `VmafMetadataFlags` enum should be used for specifying which information should be - * propagated to the metadata. - * - * @param VMAF_METADATA_FLAG_NONE No flags. - * - * @param VMAF_METADATA_FLAG_FEATURE Include all indivual metrics. - * - * @param VMAF_METADATA_FLAG_MODEL Propagate only model score. - * - * @param VMAF_METADATA_FLAG_FEATURE_NAME Propagate only given feature name.. - * - */ - -enum VmafMetadataFlags { - VMAF_METADATA_FLAG_NONE = 0, - VMAF_METADATA_FLAG_FEATURE = 1 << 0, - VMAF_METADATA_FLAG_MODEL = 1 << 1, - VMAF_METADATA_FLAG_FEATURE_NAME = 1 << 2, -}; - /** * Register a callback to receive VMAF metadata. * @@ -324,19 +298,6 @@ enum VmafMetadataFlags { int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg); -/** - * Get the number of registered metadata handlers. - * - * @param vmaf The VMAF context allocated with `vmaf_init()`. - * - * @param count Number of registered metadata handlers. - * - * - * @return 0 on success, or < 0 (a negative errno code) on error. - */ - -int vmaf_get_metadata_handler_count(VmafContext *vmaf, unsigned *count); - /** * Pooled VMAF score for a specific interval. * diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index ac4892057..7b98100cd 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -296,25 +296,6 @@ int vmaf_feature_collector_register_metadata(VmafFeatureCollector *feature_colle return 0; } -int vmaf_feature_collector_get_metadata_count(VmafFeatureCollector *feature_collector, - unsigned *count) -{ - if (!feature_collector) return -EINVAL; - if (!count) return -EINVAL; - - VmafCallbackList *metadata = feature_collector->metadata; - unsigned cnt = 0; - VmafCallbackItem *iter = metadata ? metadata->head : NULL; - while (iter) { - cnt++; - iter = iter->next; - } - - *count = cnt; - - return 0; -} - static FeatureVector *find_feature_vector(VmafFeatureCollector *fc, const char *feature_name) { diff --git a/libvmaf/src/libvmaf.c b/libvmaf/src/libvmaf.c index bef8f85bb..31340e22c 100644 --- a/libvmaf/src/libvmaf.c +++ b/libvmaf/src/libvmaf.c @@ -751,14 +751,6 @@ int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration return vmaf_feature_collector_register_metadata(vmaf->feature_collector, cfg); } -int vmaf_get_metadata_handler_count(VmafContext *vmaf, unsigned *count) -{ - if (!vmaf) return -EINVAL; - if (!count) return -EINVAL; - - return vmaf_feature_collector_get_metadata_count(vmaf->feature_collector, count); -} - int vmaf_feature_score_at_index(VmafContext *vmaf, const char *feature_name, double *score, unsigned index) { diff --git a/libvmaf/src/metadata_handler.c b/libvmaf/src/metadata_handler.c index ce51dda5c..cd792e2c5 100644 --- a/libvmaf/src/metadata_handler.c +++ b/libvmaf/src/metadata_handler.c @@ -71,8 +71,6 @@ int vmaf_metadata_destroy(VmafCallbackList *metadata) VmafCallbackItem *iter = metadata->head; while (iter) { VmafCallbackItem *next = iter->next; - if (iter->metadata_cfg.feature_name) - free(iter->metadata_cfg.feature_name); free(iter); iter = next; } diff --git a/libvmaf/test/test_predict.c b/libvmaf/test/test_predict.c index 04752caf0..43967e464 100644 --- a/libvmaf/test/test_predict.c +++ b/libvmaf/test/test_predict.c @@ -107,7 +107,7 @@ static char* test_propagate_metadata() }; VmafMetadataConfiguration m = { - .feature_name = strdup("vmaf"), + .feature_name = "vmaf", .callback = set_meta, .data = &meta_data, }; @@ -146,7 +146,7 @@ static char* test_propagate_metadata() vmaf_feature_collector_destroy(feature_collector); m.data = NULL; - m.feature_name = strdup("vmaf"); + m.feature_name = "vmaf"; err = vmaf_feature_collector_init(&feature_collector); mu_assert("problem during vmaf_feature_collector_init", !err); @@ -162,7 +162,7 @@ static char* test_propagate_metadata() vmaf_feature_collector_destroy(feature_collector); m.callback = NULL; - m.feature_name = strdup("vmaf"); + m.feature_name = "vmaf"; err = vmaf_feature_collector_init(&feature_collector); mu_assert("problem during vmaf_feature_collector_init", !err); From 4e21c824a369fb75392e07d6af28bdb57b92a8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 14:00:04 +0300 Subject: [PATCH 05/12] refactor: Fix indentation --- libvmaf/src/feature/feature_collector.c | 92 ++++++++++++------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index 7b98100cd..42f4e340f 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -355,60 +355,60 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, VmafPredictModel *model_iter = feature_collector->models; -while (model_iter) { - VmafModel *model = model_iter->model; - bool needs_computation = false; + while (model_iter) { + VmafModel *model = model_iter->model; + bool needs_computation = false; - // Check if current score needs computation - pthread_mutex_unlock(&(feature_collector->lock)); - res = vmaf_feature_collector_get_score(feature_collector, model->name, &score, picture_index); - needs_computation = (res != 0); - pthread_mutex_lock(&(feature_collector->lock)); - - if (needs_computation) { - // Compute the current frame's score + // Check if current score needs computation pthread_mutex_unlock(&(feature_collector->lock)); - res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); + res = vmaf_feature_collector_get_score(feature_collector, model->name, &score, picture_index); + needs_computation = (res != 0); pthread_mutex_lock(&(feature_collector->lock)); - if (!res) { - // Process all pending frames up to current index in order - unsigned process_index = feature_collector->metadata->last_seen_lowest_index; - feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); - - while (process_index <= feature_collector->metadata->last_seen_highest_index) { - bool frame_ready = true; - - // First check if this frame's score is ready - pthread_mutex_unlock(&(feature_collector->lock)); - if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { - frame_ready = false; + if (needs_computation) { + // Compute the current frame's score + pthread_mutex_unlock(&(feature_collector->lock)); + res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); + pthread_mutex_lock(&(feature_collector->lock)); + + if (!res) { + // Process all pending frames up to current index in order + unsigned process_index = feature_collector->metadata->last_seen_lowest_index; + feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); + + while (process_index <= feature_collector->metadata->last_seen_highest_index) { + bool frame_ready = true; + + // First check if this frame's score is ready + pthread_mutex_unlock(&(feature_collector->lock)); + if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { + frame_ready = false; + } + pthread_mutex_lock(&(feature_collector->lock)); + + if (!frame_ready) break; // Stop at first unready frame + + // Frame is ready, trigger callbacks for all features + for (unsigned j = 0; j < feature_collector->cnt; j++) { + VmafMetadata data = { + .feature_name = feature_collector->feature_vector[j]->name, + .picture_index = process_index, + .score = feature_collector->feature_vector[j]->score[process_index].value, + }; + + // Call all metadata callbacks + feature_collector->metadata->head->metadata_cfg.callback( + feature_collector->metadata->head->metadata_cfg.data, &data); + } + + process_index++; + feature_collector->metadata->last_seen_lowest_index = process_index; } - pthread_mutex_lock(&(feature_collector->lock)); - - if (!frame_ready) break; // Stop at first unready frame - - // Frame is ready, trigger callbacks for all features - for (unsigned j = 0; j < feature_collector->cnt; j++) { - VmafMetadata data = { - .feature_name = feature_collector->feature_vector[j]->name, - .picture_index = process_index, - .score = feature_collector->feature_vector[j]->score[process_index].value, - }; - - // Call all metadata callbacks - feature_collector->metadata->head->metadata_cfg.callback( - feature_collector->metadata->head->metadata_cfg.data, &data); - } - - process_index++; - feature_collector->metadata->last_seen_lowest_index = process_index; } } - } - model_iter = model_iter->next; -} + model_iter = model_iter->next; + } unlock: feature_collector->timer.end = clock(); From ffb6067dbd13f38239ec7aab48b5719f3b6a306e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 17:00:18 +0300 Subject: [PATCH 06/12] refactoring: update feature_collector to support multiple configurations --- libvmaf/src/feature/feature_collector.c | 77 ++++++++++++++----------- libvmaf/src/metadata_handler.c | 2 - libvmaf/src/metadata_handler.h | 4 +- 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index 42f4e340f..ba4474a29 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -355,7 +355,7 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, VmafPredictModel *model_iter = feature_collector->models; - while (model_iter) { + while(model_iter) { VmafModel *model = model_iter->model; bool needs_computation = false; @@ -370,44 +370,51 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, pthread_mutex_unlock(&(feature_collector->lock)); res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); pthread_mutex_lock(&(feature_collector->lock)); + } + model_iter = model_iter->next; + } + + VmafCallbackItem *metadata_iter = feature_collector->metadata ? + feature_collector->metadata->head : NULL; - if (!res) { - // Process all pending frames up to current index in order - unsigned process_index = feature_collector->metadata->last_seen_lowest_index; - feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); - - while (process_index <= feature_collector->metadata->last_seen_highest_index) { - bool frame_ready = true; - - // First check if this frame's score is ready - pthread_mutex_unlock(&(feature_collector->lock)); - if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { - frame_ready = false; - } - pthread_mutex_lock(&(feature_collector->lock)); - - if (!frame_ready) break; // Stop at first unready frame - - // Frame is ready, trigger callbacks for all features - for (unsigned j = 0; j < feature_collector->cnt; j++) { - VmafMetadata data = { - .feature_name = feature_collector->feature_vector[j]->name, - .picture_index = process_index, - .score = feature_collector->feature_vector[j]->score[process_index].value, - }; - - // Call all metadata callbacks - feature_collector->metadata->head->metadata_cfg.callback( - feature_collector->metadata->head->metadata_cfg.data, &data); - } - - process_index++; - feature_collector->metadata->last_seen_lowest_index = process_index; + while(metadata_iter) { + // Process all pending frames up to current index in order + unsigned process_index = metadata_iter->last_seen_lowest_index; + metadata_iter->last_seen_highest_index = MAX(picture_index, metadata_iter->last_seen_highest_index); + + while (process_index <= metadata_iter->last_seen_highest_index) { + bool frame_ready = true; + + VmafPredictModel *model_iter = feature_collector->models; + while(model_iter && frame_ready) { + // First check if this frame's score is ready + pthread_mutex_unlock(&(feature_collector->lock)); + if (vmaf_feature_collector_get_score(feature_collector, model_iter->model->name, &score, process_index) != 0) { + frame_ready = false; } + pthread_mutex_lock(&(feature_collector->lock)); + model_iter = model_iter->next; } - } - model_iter = model_iter->next; + if (!frame_ready) break; // Stop at first unready frame + + // Frame is ready, trigger callbacks for all features + for (unsigned j = 0; j < feature_collector->cnt; j++) { + VmafMetadata data = { + .feature_name = feature_collector->feature_vector[j]->name, + .picture_index = process_index, + .score = feature_collector->feature_vector[j]->score[process_index].value, + }; + + // Call all metadata callbacks + metadata_iter->metadata_cfg.callback( + metadata_iter->metadata_cfg.data, &data); + } + + process_index++; + metadata_iter->last_seen_lowest_index = process_index; + } + metadata_iter = metadata_iter->next; } unlock: diff --git a/libvmaf/src/metadata_handler.c b/libvmaf/src/metadata_handler.c index cd792e2c5..99f69c212 100644 --- a/libvmaf/src/metadata_handler.c +++ b/libvmaf/src/metadata_handler.c @@ -31,8 +31,6 @@ int vmaf_metadata_init(VmafCallbackList **const metadata) if (!metadata_s) goto fail; metadata_s->head = NULL; - metadata_s->last_seen_highest_index = 0; - metadata_s->last_seen_lowest_index = 0; return 0; diff --git a/libvmaf/src/metadata_handler.h b/libvmaf/src/metadata_handler.h index c6439ec71..908eef5d7 100644 --- a/libvmaf/src/metadata_handler.h +++ b/libvmaf/src/metadata_handler.h @@ -23,13 +23,13 @@ typedef struct VmafCallbackItem { VmafMetadataConfiguration metadata_cfg; + unsigned last_seen_highest_index; + unsigned last_seen_lowest_index; struct VmafCallbackItem *next; } VmafCallbackItem; typedef struct VmafCallbackList{ VmafCallbackItem *head; - unsigned last_seen_highest_index; - unsigned last_seen_lowest_index; } VmafCallbackList; int vmaf_metadata_init(VmafCallbackList **const metadata); From 95d3181d1cf7d2a5215a9c936b86123568f01340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 17:01:20 +0300 Subject: [PATCH 07/12] refactor: delete metadata propagation tests from 'test_predict' --- libvmaf/test/test_predict.c | 204 ------------------------------------ 1 file changed, 204 deletions(-) diff --git a/libvmaf/test/test_predict.c b/libvmaf/test/test_predict.c index 43967e464..49e5e46f4 100644 --- a/libvmaf/test/test_predict.c +++ b/libvmaf/test/test_predict.c @@ -19,7 +19,6 @@ #include #include "feature/feature_collector.h" -#include "metadata_handler.h" #include "test.h" #include "predict.h" #include "predict.c" @@ -28,11 +27,6 @@ #include #include -typedef struct { - VmafDictionary **metadata; - int flags; -} MetaStruct; - static char *test_predict_score_at_index() { int err; @@ -65,202 +59,6 @@ static char *test_predict_score_at_index() return NULL; } - -void set_meta(void *data, VmafMetadata *metadata) -{ - if (!data) return; - MetaStruct *meta = data; - char key[128], value[128]; - snprintf(key, sizeof(value), "%s_%d", metadata->feature_name, - metadata->picture_index); - snprintf(value, sizeof(value), "%f", metadata->score); - vmaf_dictionary_set(meta->metadata, key, value, meta->flags); -} - -static int g_callback_order[4] = {-1, -1, -1, -1}; -static int g_callback_count = 0; - -static void test_non_monotonic_callback(void *data, VmafMetadata *m) -{ - if (!data) return; - MetaStruct *meta = data; - - if (!strcmp("vmaf", m->feature_name)) - // Track callback order - g_callback_order[m->picture_index] = g_callback_count++; - - // Store in dictionary for verification - char key[32], value[32]; - snprintf(key, sizeof(key), "vmaf_%d", m->picture_index); - snprintf(value, sizeof(value), "%f", m->score); - vmaf_dictionary_set(meta->metadata, key, value, meta->flags); -} - -static char* test_propagate_metadata() -{ - int err; - - VmafDictionary *dict = NULL; - MetaStruct meta_data = { - .metadata = &dict, - .flags = 0, - }; - - VmafMetadataConfiguration m = { - .feature_name = "vmaf", - .callback = set_meta, - .data = &meta_data, - }; - - VmafFeatureCollector *feature_collector; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata_0", !err); - - VmafModel *model; - VmafModelConfig cfg = { - .name = "vmaf", - .flags = VMAF_MODEL_FLAGS_DEFAULT, - }; - err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); - mu_assert("problem during vmaf_model_load", !err); - err = vmaf_feature_collector_mount_model(feature_collector, model); - mu_assert("problem during vmaf_mount_model", !err); - - for (unsigned i = 0; i < model->n_features; i++) { - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 0); - mu_assert("problem during vmaf_feature_collector_append", !err); - } - - VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, "vmaf_0", 0); - mu_assert("error on propagaton metadata: propagated key not found!", - e); - mu_assert("error on propagaton metadata: propagated key wrong!", - !strcmp(e->key, "vmaf_0")); - mu_assert("error on propagaton metadata: propagated data wrong!", - !strcmp(e->val, "100.000000")); - - vmaf_feature_collector_destroy(feature_collector); - - m.data = NULL; - m.feature_name = "vmaf"; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata_1", !err); - - for (unsigned i = 0; i < model->n_features; i++) { - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 0); - mu_assert("problem during vmaf_feature_collector_append", !err); - } - - vmaf_feature_collector_destroy(feature_collector); - - m.callback = NULL; - m.feature_name = "vmaf"; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata_2", err); - - vmaf_feature_collector_destroy(feature_collector); - - vmaf_model_destroy(model); - return NULL; - -} - -static char *test_propagate_metadata_non_monotonic() -{ - int err; - - // Reset global counters - g_callback_count = 0; - for (int i = 0; i < 4; i++) { - g_callback_order[i] = -1; - } - - // Setup dictionary to store callback results - VmafDictionary *dict = NULL; - MetaStruct meta_data = { - .metadata = &dict, - .flags = 0, - }; - - VmafMetadataConfiguration m = { - .feature_name = strdup("vmaf"), - .callback = test_non_monotonic_callback, - .data = &meta_data, - }; - - // Initialize feature collector - VmafFeatureCollector *feature_collector; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata", !err); - - // Load VMAF model - VmafModel *model; - VmafModelConfig cfg = { - .name = "vmaf", - .flags = VMAF_MODEL_FLAGS_DEFAULT, - }; - err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); - mu_assert("problem during vmaf_model_load", !err); - err = vmaf_feature_collector_mount_model(feature_collector, model); - mu_assert("problem during vmaf_mount_model", !err); - - // Simulate non-monotonic VMAF score computations - // Frame order: 3, 0, 2, 1 - for (unsigned i = 0; i < model->n_features; i++) { - // Frame 3 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 3); - mu_assert("problem appending frame 3", !err); - - // Frame 0 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 70., 0); - mu_assert("problem appending frame 0", !err); - - // Frame 2 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 80., 2); - mu_assert("problem appending frame 2", !err); - - // Frame 1 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 90., 1); - mu_assert("problem appending frame 1", !err); - } - - // Verify callback order is monotonic regardless of computation order - mu_assert("Frame 0 callback not first", g_callback_order[0] == 0); - mu_assert("Frame 1 callback not second", g_callback_order[1] == 1); - mu_assert("Frame 2 callback not third", g_callback_order[2] == 2); - mu_assert("Frame 3 callback not fourth", g_callback_order[3] == 3); - - // Verify all frame scores were propagated - for (int i = 0; i < 4; i++) { - char key[32]; - snprintf(key, sizeof(key), "vmaf_%d", i); - VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, key, 0); - mu_assert("Missing frame score in metadata", e != NULL); - } - - vmaf_feature_collector_destroy(feature_collector); - vmaf_model_destroy(model); - return NULL; -} - static char *test_find_linear_function_parameters() { int err; @@ -377,7 +175,5 @@ char *run_tests() mu_run_test(test_predict_score_at_index); mu_run_test(test_find_linear_function_parameters); mu_run_test(test_piecewise_linear_mapping); - mu_run_test(test_propagate_metadata); - mu_run_test(test_propagate_metadata_non_monotonic); return NULL; } From 69d7b5faaeea11e8470c83557d53d92623d44a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 17:09:00 +0300 Subject: [PATCH 08/12] test: Add non-monotonic single/multi VmafMetadataConfiguration tests. --- libvmaf/test/meson.build | 9 +- libvmaf/test/test_propagate_metadata.c | 409 ++++++++++++++++++++++++- 2 files changed, 412 insertions(+), 6 deletions(-) diff --git a/libvmaf/test/meson.build b/libvmaf/test/meson.build index e8d943e98..2ace210d9 100644 --- a/libvmaf/test/meson.build +++ b/libvmaf/test/meson.build @@ -18,8 +18,11 @@ test_picture = executable('test_picture', ) test_propagate_metadata = executable('test_propagate_metadata', - ['test.c', 'test_propagate_metadata.c', '../src/metadata_handler.c'], + ['test.c', 'test_propagate_metadata.c', '../src/metadata_handler.c','../src/dict.c', + '../src/feature/feature_collector.c', '../src/feature/alias.c', '../src/model.c', '../src/svm.cpp', '../src/log.c', + '../src/read_json_model.c', '../src/pdjson.c', json_model_c_sources, '../src/feature/feature_name.c', '../src/feature/feature_extractor.c'], include_directories : [libvmaf_inc, test_inc, include_directories('../src/')], + link_with : get_option('default_library') == 'both' ? libvmaf.get_static_lib() : libvmaf, ) test_feature_collector = executable('test_feature_collector', @@ -50,8 +53,8 @@ test_model = executable('test_model', ) test_predict = executable('test_predict', - ['test.c', 'test_predict.c', '../src/dict.c', '../src/metadata_handler.c', - '../src/feature/feature_collector.c', '../src/feature/alias.c', '../src/model.c', '../src/svm.cpp', '../src/log.c', + ['test.c', 'test_predict.c', '../src/dict.c', '../src/feature/feature_collector.c', + '../src/feature/alias.c', '../src/model.c', '../src/svm.cpp', '../src/log.c', '../src/read_json_model.c', '../src/pdjson.c', json_model_c_sources, '../src/feature/feature_name.c', '../src/feature/feature_extractor.c',], include_directories : [libvmaf_inc, test_inc, include_directories('../src/')], link_with : get_option('default_library') == 'both' ? libvmaf.get_static_lib() : libvmaf, diff --git a/libvmaf/test/test_propagate_metadata.c b/libvmaf/test/test_propagate_metadata.c index 1c857432e..6302750b0 100644 --- a/libvmaf/test/test_propagate_metadata.c +++ b/libvmaf/test/test_propagate_metadata.c @@ -16,10 +16,52 @@ * */ -#include "metadata_handler.h" #include "test.h" +#include "metadata_handler.h" +#include "feature/feature_collector.h" +#include "predict.h" +#include "predict.c" + +#include +#include +#include +#include +#include + +typedef struct { + VmafDictionary **metadata; + int flags; +} MetaStruct; + +static void set_meta(void *data, VmafMetadata *metadata) +{ + if (!data) return; + MetaStruct *meta = data; + char key[128], value[128]; + snprintf(key, sizeof(value), "%s_%d", metadata->feature_name, + metadata->picture_index); + snprintf(value, sizeof(value), "%f", metadata->score); + vmaf_dictionary_set(meta->metadata, key, value, meta->flags); +} + +static int g_callback_order[4] = {-1, -1, -1, -1}; +static int g_callback_count = 0; + +static void test_non_monotonic_callback(void *data, VmafMetadata *m) +{ + if (!data) return; + MetaStruct *meta = data; + + if (!strcmp("vmaf", m->feature_name)) + // Track callback order + g_callback_order[m->picture_index] = g_callback_count++; -void set_meta() {} + // Store in dictionary for verification + char key[32], value[32]; + snprintf(key, sizeof(key), "vmaf_%d", m->picture_index); + snprintf(value, sizeof(value), "%f", m->score); + vmaf_dictionary_set(meta->metadata, key, value, meta->flags); +} static char *test_propagate_metadata_init() { @@ -57,7 +99,6 @@ static char *test_propagate_metadata_append() VmafMetadataConfiguration metadata_config = {0}; metadata_config.callback = set_meta; - metadata_config.data = NULL; err = vmaf_metadata_append(propagate_metadata, metadata_config); mu_assert("problem during vmaf_propagate_metadata_append", !err); @@ -77,10 +118,372 @@ static char *test_propagate_metadata_append() return NULL; } +static char* test_propagate_metadata() +{ + int err; + + VmafDictionary *dict = NULL; + MetaStruct meta_data = { + .metadata = &dict, + .flags = 0, + }; + + VmafMetadataConfiguration m = { + .feature_name = "vmaf", + .callback = set_meta, + .data = &meta_data, + }; + + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata_0", !err); + + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem during vmaf_model_load", !err); + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem during vmaf_mount_model", !err); + + for (unsigned i = 0; i < model->n_features; i++) { + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 0); + mu_assert("problem during vmaf_feature_collector_append", !err); + } + + VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, "vmaf_0", 0); + mu_assert("error on propagaton metadata: propagated key not found!", + e); + mu_assert("error on propagaton metadata: propagated key wrong!", + !strcmp(e->key, "vmaf_0")); + mu_assert("error on propagaton metadata: propagated data wrong!", + !strcmp(e->val, "100.000000")); + + vmaf_feature_collector_destroy(feature_collector); + + m.data = NULL; + m.feature_name = "vmaf"; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata_1", !err); + + for (unsigned i = 0; i < model->n_features; i++) { + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 0); + mu_assert("problem during vmaf_feature_collector_append", !err); + } + + vmaf_feature_collector_destroy(feature_collector); + + m.callback = NULL; + m.feature_name = "vmaf"; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata_2", err); + + vmaf_feature_collector_destroy(feature_collector); + + vmaf_model_destroy(model); + return NULL; + +} + +static char *test_propagate_metadata_non_monotonic() +{ + int err; + + // Reset global counters + g_callback_count = 0; + for (int i = 0; i < 4; i++) { + g_callback_order[i] = -1; + } + + // Setup dictionary to store callback results + VmafDictionary *dict = NULL; + MetaStruct meta_data = { + .metadata = &dict, + .flags = 0, + }; + + VmafMetadataConfiguration m = { + .feature_name = strdup("vmaf"), + .callback = test_non_monotonic_callback, + .data = &meta_data, + }; + + // Initialize feature collector + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata", !err); + + // Load VMAF model + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem during vmaf_model_load", !err); + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem during vmaf_mount_model", !err); + + // Simulate non-monotonic VMAF score computations + // Frame order: 3, 0, 2, 1 + for (unsigned i = 0; i < model->n_features; i++) { + // Frame 3 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 3); + mu_assert("problem appending frame 3", !err); + + // Frame 0 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 70., 0); + mu_assert("problem appending frame 0", !err); + + // Frame 2 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 80., 2); + mu_assert("problem appending frame 2", !err); + + // Frame 1 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 90., 1); + mu_assert("problem appending frame 1", !err); + } + + // Verify callback order is monotonic regardless of computation order + mu_assert("Frame 0 callback not first", g_callback_order[0] == 0); + mu_assert("Frame 1 callback not second", g_callback_order[1] == 1); + mu_assert("Frame 2 callback not third", g_callback_order[2] == 2); + mu_assert("Frame 3 callback not fourth", g_callback_order[3] == 3); + + // Verify all frame scores were propagated + for (int i = 0; i < 4; i++) { + char key[32]; + snprintf(key, sizeof(key), "vmaf_%d", i); + VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, key, 0); + mu_assert("Missing frame score in metadata", e != NULL); + } + + vmaf_feature_collector_destroy(feature_collector); + vmaf_model_destroy(model); + return NULL; +} + +// Structure to track callback invocations for multiple callbacks +typedef struct { + int callback_id; + int call_count; + VmafDictionary **metadata; +} MultiCallbackData; + +static void multi_callback(void *data, VmafMetadata *metadata) +{ + if (!data) return; + MultiCallbackData *cb_data = data; + + char key[128], value[128]; + snprintf(key, sizeof(key), "cb%d_%s_%d", + cb_data->callback_id, + metadata->feature_name, + metadata->picture_index); + snprintf(value, sizeof(value), "%f", metadata->score); + cb_data->call_count++; + vmaf_dictionary_set(cb_data->metadata, key, value, 0); +} + +static char *test_multiple_callbacks() +{ + int err; + VmafDictionary *dict1 = NULL; + VmafDictionary *dict2 = NULL; + + // Setup two different callback data structures + MultiCallbackData cb_data1 = { + .callback_id = 1, + .call_count = 0, + .metadata = &dict1 + }; + + MultiCallbackData cb_data2 = { + .callback_id = 2, + .call_count = 0, + .metadata = &dict2 + }; + + // Register two different callbacks + VmafMetadataConfiguration m1 = { + .feature_name = "vmaf", + .callback = multi_callback, + .data = &cb_data1, + }; + + VmafMetadataConfiguration m2 = { + .feature_name = "vmaf", + .callback = multi_callback, + .data = &cb_data2, + }; + + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m1); + mu_assert("problem registering first callback", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m2); + mu_assert("problem registering second callback", !err); + + // Load and mount VMAF model + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem during vmaf_model_load", !err); + + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem mounting model", !err); + + // Add some feature data + for (unsigned i = 0; i < model->n_features; i++) { + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 0); + mu_assert("problem appending features", !err); + } + + // Verify both callbacks were called + mu_assert("first callback not called", cb_data1.call_count > 0); + mu_assert("second callback not called", cb_data2.call_count > 0); + mu_assert("callbacks called different number of times", + cb_data1.call_count == cb_data2.call_count); + + // Verify data in both dictionaries + VmafDictionaryEntry *e1 = vmaf_dictionary_get(&dict1, "cb1_vmaf_0", 0); + VmafDictionaryEntry *e2 = vmaf_dictionary_get(&dict2, "cb2_vmaf_0", 0); + + mu_assert("first callback data missing", e1 != NULL); + mu_assert("second callback data missing", e2 != NULL); + mu_assert("callback data mismatch", strcmp(e1->val, e2->val) == 0); + + vmaf_feature_collector_destroy(feature_collector); + vmaf_model_destroy(model); + return NULL; +} + +static char *test_multiple_callbacks_non_monotonic() +{ + int err; + VmafDictionary *dict1 = NULL; + VmafDictionary *dict2 = NULL; + + // Setup callback data + MultiCallbackData cb_data1 = { + .callback_id = 1, + .call_count = 0, + .metadata = &dict1 + }; + + MultiCallbackData cb_data2 = { + .callback_id = 2, + .call_count = 0, + .metadata = &dict2 + }; + + VmafMetadataConfiguration m1 = { + .feature_name = "vmaf", + .callback = multi_callback, + .data = &cb_data1, + }; + + VmafMetadataConfiguration m2 = { + .feature_name = "vmaf", + .callback = multi_callback, + .data = &cb_data2, + }; + + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m1); + err |= vmaf_feature_collector_register_metadata(feature_collector, m2); + mu_assert("problem registering callbacks", !err); + + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem loading model", !err); + + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem mounting model", !err); + + + for (unsigned i = 0; i < model->n_features; i++) { + // Frame 2 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 2); + mu_assert("problem appending frame 2", !err); + + // Frame 0 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 0); + mu_assert("problem appending frame 0", !err); + + // Frame 3 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 3); + mu_assert("problem appending frame 3", !err); + + // Frame 1 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 1); + mu_assert("problem appending frame 1", !err); + } + + for (int i = 0; i < 4; i++) { + char key1[32], key2[32]; + snprintf(key1, sizeof(key1), "cb1_vmaf_%d", i); + snprintf(key2, sizeof(key2), "cb2_vmaf_%d", i); + + VmafDictionaryEntry *e1 = vmaf_dictionary_get(&dict1, key1, 0); + VmafDictionaryEntry *e2 = vmaf_dictionary_get(&dict2, key2, 0); + + mu_assert("missing callback 1 data", e1 != NULL); + mu_assert("missing callback 2 data", e2 != NULL); + + mu_assert("callback data mismatch", strcmp(e1->val, e2->val) == 0); + } + vmaf_feature_collector_destroy(feature_collector); + vmaf_model_destroy(model); + return NULL; +} + char *run_tests() { mu_run_test(test_propagate_metadata_init); mu_run_test(test_propagate_metadata_destroy); mu_run_test(test_propagate_metadata_append); + mu_run_test(test_propagate_metadata); + mu_run_test(test_propagate_metadata_non_monotonic); + mu_run_test(test_multiple_callbacks); + mu_run_test(test_multiple_callbacks_non_monotonic); return NULL; } From a72cfeb4061921fd6458881cf52268ff819ebb37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 18:36:09 +0300 Subject: [PATCH 09/12] Revert "test: Add non-monotonic single/multi VmafMetadataConfiguration tests." This reverts commit 69d7b5faaeea11e8470c83557d53d92623d44a41. --- libvmaf/test/meson.build | 9 +- libvmaf/test/test_propagate_metadata.c | 409 +------------------------ 2 files changed, 6 insertions(+), 412 deletions(-) diff --git a/libvmaf/test/meson.build b/libvmaf/test/meson.build index 2ace210d9..e8d943e98 100644 --- a/libvmaf/test/meson.build +++ b/libvmaf/test/meson.build @@ -18,11 +18,8 @@ test_picture = executable('test_picture', ) test_propagate_metadata = executable('test_propagate_metadata', - ['test.c', 'test_propagate_metadata.c', '../src/metadata_handler.c','../src/dict.c', - '../src/feature/feature_collector.c', '../src/feature/alias.c', '../src/model.c', '../src/svm.cpp', '../src/log.c', - '../src/read_json_model.c', '../src/pdjson.c', json_model_c_sources, '../src/feature/feature_name.c', '../src/feature/feature_extractor.c'], + ['test.c', 'test_propagate_metadata.c', '../src/metadata_handler.c'], include_directories : [libvmaf_inc, test_inc, include_directories('../src/')], - link_with : get_option('default_library') == 'both' ? libvmaf.get_static_lib() : libvmaf, ) test_feature_collector = executable('test_feature_collector', @@ -53,8 +50,8 @@ test_model = executable('test_model', ) test_predict = executable('test_predict', - ['test.c', 'test_predict.c', '../src/dict.c', '../src/feature/feature_collector.c', - '../src/feature/alias.c', '../src/model.c', '../src/svm.cpp', '../src/log.c', + ['test.c', 'test_predict.c', '../src/dict.c', '../src/metadata_handler.c', + '../src/feature/feature_collector.c', '../src/feature/alias.c', '../src/model.c', '../src/svm.cpp', '../src/log.c', '../src/read_json_model.c', '../src/pdjson.c', json_model_c_sources, '../src/feature/feature_name.c', '../src/feature/feature_extractor.c',], include_directories : [libvmaf_inc, test_inc, include_directories('../src/')], link_with : get_option('default_library') == 'both' ? libvmaf.get_static_lib() : libvmaf, diff --git a/libvmaf/test/test_propagate_metadata.c b/libvmaf/test/test_propagate_metadata.c index 6302750b0..1c857432e 100644 --- a/libvmaf/test/test_propagate_metadata.c +++ b/libvmaf/test/test_propagate_metadata.c @@ -16,52 +16,10 @@ * */ -#include "test.h" #include "metadata_handler.h" -#include "feature/feature_collector.h" -#include "predict.h" -#include "predict.c" - -#include -#include -#include -#include -#include - -typedef struct { - VmafDictionary **metadata; - int flags; -} MetaStruct; - -static void set_meta(void *data, VmafMetadata *metadata) -{ - if (!data) return; - MetaStruct *meta = data; - char key[128], value[128]; - snprintf(key, sizeof(value), "%s_%d", metadata->feature_name, - metadata->picture_index); - snprintf(value, sizeof(value), "%f", metadata->score); - vmaf_dictionary_set(meta->metadata, key, value, meta->flags); -} - -static int g_callback_order[4] = {-1, -1, -1, -1}; -static int g_callback_count = 0; - -static void test_non_monotonic_callback(void *data, VmafMetadata *m) -{ - if (!data) return; - MetaStruct *meta = data; - - if (!strcmp("vmaf", m->feature_name)) - // Track callback order - g_callback_order[m->picture_index] = g_callback_count++; +#include "test.h" - // Store in dictionary for verification - char key[32], value[32]; - snprintf(key, sizeof(key), "vmaf_%d", m->picture_index); - snprintf(value, sizeof(value), "%f", m->score); - vmaf_dictionary_set(meta->metadata, key, value, meta->flags); -} +void set_meta() {} static char *test_propagate_metadata_init() { @@ -99,6 +57,7 @@ static char *test_propagate_metadata_append() VmafMetadataConfiguration metadata_config = {0}; metadata_config.callback = set_meta; + metadata_config.data = NULL; err = vmaf_metadata_append(propagate_metadata, metadata_config); mu_assert("problem during vmaf_propagate_metadata_append", !err); @@ -118,372 +77,10 @@ static char *test_propagate_metadata_append() return NULL; } -static char* test_propagate_metadata() -{ - int err; - - VmafDictionary *dict = NULL; - MetaStruct meta_data = { - .metadata = &dict, - .flags = 0, - }; - - VmafMetadataConfiguration m = { - .feature_name = "vmaf", - .callback = set_meta, - .data = &meta_data, - }; - - VmafFeatureCollector *feature_collector; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata_0", !err); - - VmafModel *model; - VmafModelConfig cfg = { - .name = "vmaf", - .flags = VMAF_MODEL_FLAGS_DEFAULT, - }; - err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); - mu_assert("problem during vmaf_model_load", !err); - err = vmaf_feature_collector_mount_model(feature_collector, model); - mu_assert("problem during vmaf_mount_model", !err); - - for (unsigned i = 0; i < model->n_features; i++) { - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 0); - mu_assert("problem during vmaf_feature_collector_append", !err); - } - - VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, "vmaf_0", 0); - mu_assert("error on propagaton metadata: propagated key not found!", - e); - mu_assert("error on propagaton metadata: propagated key wrong!", - !strcmp(e->key, "vmaf_0")); - mu_assert("error on propagaton metadata: propagated data wrong!", - !strcmp(e->val, "100.000000")); - - vmaf_feature_collector_destroy(feature_collector); - - m.data = NULL; - m.feature_name = "vmaf"; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata_1", !err); - - for (unsigned i = 0; i < model->n_features; i++) { - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 0); - mu_assert("problem during vmaf_feature_collector_append", !err); - } - - vmaf_feature_collector_destroy(feature_collector); - - m.callback = NULL; - m.feature_name = "vmaf"; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata_2", err); - - vmaf_feature_collector_destroy(feature_collector); - - vmaf_model_destroy(model); - return NULL; - -} - -static char *test_propagate_metadata_non_monotonic() -{ - int err; - - // Reset global counters - g_callback_count = 0; - for (int i = 0; i < 4; i++) { - g_callback_order[i] = -1; - } - - // Setup dictionary to store callback results - VmafDictionary *dict = NULL; - MetaStruct meta_data = { - .metadata = &dict, - .flags = 0, - }; - - VmafMetadataConfiguration m = { - .feature_name = strdup("vmaf"), - .callback = test_non_monotonic_callback, - .data = &meta_data, - }; - - // Initialize feature collector - VmafFeatureCollector *feature_collector; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m); - mu_assert("problem during vmaf_feature_collector_register_metadata", !err); - - // Load VMAF model - VmafModel *model; - VmafModelConfig cfg = { - .name = "vmaf", - .flags = VMAF_MODEL_FLAGS_DEFAULT, - }; - err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); - mu_assert("problem during vmaf_model_load", !err); - err = vmaf_feature_collector_mount_model(feature_collector, model); - mu_assert("problem during vmaf_mount_model", !err); - - // Simulate non-monotonic VMAF score computations - // Frame order: 3, 0, 2, 1 - for (unsigned i = 0; i < model->n_features; i++) { - // Frame 3 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 3); - mu_assert("problem appending frame 3", !err); - - // Frame 0 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 70., 0); - mu_assert("problem appending frame 0", !err); - - // Frame 2 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 80., 2); - mu_assert("problem appending frame 2", !err); - - // Frame 1 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 90., 1); - mu_assert("problem appending frame 1", !err); - } - - // Verify callback order is monotonic regardless of computation order - mu_assert("Frame 0 callback not first", g_callback_order[0] == 0); - mu_assert("Frame 1 callback not second", g_callback_order[1] == 1); - mu_assert("Frame 2 callback not third", g_callback_order[2] == 2); - mu_assert("Frame 3 callback not fourth", g_callback_order[3] == 3); - - // Verify all frame scores were propagated - for (int i = 0; i < 4; i++) { - char key[32]; - snprintf(key, sizeof(key), "vmaf_%d", i); - VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, key, 0); - mu_assert("Missing frame score in metadata", e != NULL); - } - - vmaf_feature_collector_destroy(feature_collector); - vmaf_model_destroy(model); - return NULL; -} - -// Structure to track callback invocations for multiple callbacks -typedef struct { - int callback_id; - int call_count; - VmafDictionary **metadata; -} MultiCallbackData; - -static void multi_callback(void *data, VmafMetadata *metadata) -{ - if (!data) return; - MultiCallbackData *cb_data = data; - - char key[128], value[128]; - snprintf(key, sizeof(key), "cb%d_%s_%d", - cb_data->callback_id, - metadata->feature_name, - metadata->picture_index); - snprintf(value, sizeof(value), "%f", metadata->score); - cb_data->call_count++; - vmaf_dictionary_set(cb_data->metadata, key, value, 0); -} - -static char *test_multiple_callbacks() -{ - int err; - VmafDictionary *dict1 = NULL; - VmafDictionary *dict2 = NULL; - - // Setup two different callback data structures - MultiCallbackData cb_data1 = { - .callback_id = 1, - .call_count = 0, - .metadata = &dict1 - }; - - MultiCallbackData cb_data2 = { - .callback_id = 2, - .call_count = 0, - .metadata = &dict2 - }; - - // Register two different callbacks - VmafMetadataConfiguration m1 = { - .feature_name = "vmaf", - .callback = multi_callback, - .data = &cb_data1, - }; - - VmafMetadataConfiguration m2 = { - .feature_name = "vmaf", - .callback = multi_callback, - .data = &cb_data2, - }; - - VmafFeatureCollector *feature_collector; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during vmaf_feature_collector_init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m1); - mu_assert("problem registering first callback", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m2); - mu_assert("problem registering second callback", !err); - - // Load and mount VMAF model - VmafModel *model; - VmafModelConfig cfg = { - .name = "vmaf", - .flags = VMAF_MODEL_FLAGS_DEFAULT, - }; - err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); - mu_assert("problem during vmaf_model_load", !err); - - err = vmaf_feature_collector_mount_model(feature_collector, model); - mu_assert("problem mounting model", !err); - - // Add some feature data - for (unsigned i = 0; i < model->n_features; i++) { - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 0); - mu_assert("problem appending features", !err); - } - - // Verify both callbacks were called - mu_assert("first callback not called", cb_data1.call_count > 0); - mu_assert("second callback not called", cb_data2.call_count > 0); - mu_assert("callbacks called different number of times", - cb_data1.call_count == cb_data2.call_count); - - // Verify data in both dictionaries - VmafDictionaryEntry *e1 = vmaf_dictionary_get(&dict1, "cb1_vmaf_0", 0); - VmafDictionaryEntry *e2 = vmaf_dictionary_get(&dict2, "cb2_vmaf_0", 0); - - mu_assert("first callback data missing", e1 != NULL); - mu_assert("second callback data missing", e2 != NULL); - mu_assert("callback data mismatch", strcmp(e1->val, e2->val) == 0); - - vmaf_feature_collector_destroy(feature_collector); - vmaf_model_destroy(model); - return NULL; -} - -static char *test_multiple_callbacks_non_monotonic() -{ - int err; - VmafDictionary *dict1 = NULL; - VmafDictionary *dict2 = NULL; - - // Setup callback data - MultiCallbackData cb_data1 = { - .callback_id = 1, - .call_count = 0, - .metadata = &dict1 - }; - - MultiCallbackData cb_data2 = { - .callback_id = 2, - .call_count = 0, - .metadata = &dict2 - }; - - VmafMetadataConfiguration m1 = { - .feature_name = "vmaf", - .callback = multi_callback, - .data = &cb_data1, - }; - - VmafMetadataConfiguration m2 = { - .feature_name = "vmaf", - .callback = multi_callback, - .data = &cb_data2, - }; - - VmafFeatureCollector *feature_collector; - err = vmaf_feature_collector_init(&feature_collector); - mu_assert("problem during init", !err); - - err = vmaf_feature_collector_register_metadata(feature_collector, m1); - err |= vmaf_feature_collector_register_metadata(feature_collector, m2); - mu_assert("problem registering callbacks", !err); - - VmafModel *model; - VmafModelConfig cfg = { - .name = "vmaf", - .flags = VMAF_MODEL_FLAGS_DEFAULT, - }; - err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); - mu_assert("problem loading model", !err); - - err = vmaf_feature_collector_mount_model(feature_collector, model); - mu_assert("problem mounting model", !err); - - - for (unsigned i = 0; i < model->n_features; i++) { - // Frame 2 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 2); - mu_assert("problem appending frame 2", !err); - - // Frame 0 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 0); - mu_assert("problem appending frame 0", !err); - - // Frame 3 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 3); - mu_assert("problem appending frame 3", !err); - - // Frame 1 - err = vmaf_feature_collector_append(feature_collector, - model->feature[i].name, 60., 1); - mu_assert("problem appending frame 1", !err); - } - - for (int i = 0; i < 4; i++) { - char key1[32], key2[32]; - snprintf(key1, sizeof(key1), "cb1_vmaf_%d", i); - snprintf(key2, sizeof(key2), "cb2_vmaf_%d", i); - - VmafDictionaryEntry *e1 = vmaf_dictionary_get(&dict1, key1, 0); - VmafDictionaryEntry *e2 = vmaf_dictionary_get(&dict2, key2, 0); - - mu_assert("missing callback 1 data", e1 != NULL); - mu_assert("missing callback 2 data", e2 != NULL); - - mu_assert("callback data mismatch", strcmp(e1->val, e2->val) == 0); - } - vmaf_feature_collector_destroy(feature_collector); - vmaf_model_destroy(model); - return NULL; -} - char *run_tests() { mu_run_test(test_propagate_metadata_init); mu_run_test(test_propagate_metadata_destroy); mu_run_test(test_propagate_metadata_append); - mu_run_test(test_propagate_metadata); - mu_run_test(test_propagate_metadata_non_monotonic); - mu_run_test(test_multiple_callbacks); - mu_run_test(test_multiple_callbacks_non_monotonic); return NULL; } From fc02bb015d95fa51e047d4e14f939114d03f60b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 18:36:16 +0300 Subject: [PATCH 10/12] Revert "refactor: delete metadata propagation tests from 'test_predict'" This reverts commit 95d3181d1cf7d2a5215a9c936b86123568f01340. --- libvmaf/test/test_predict.c | 204 ++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/libvmaf/test/test_predict.c b/libvmaf/test/test_predict.c index 49e5e46f4..43967e464 100644 --- a/libvmaf/test/test_predict.c +++ b/libvmaf/test/test_predict.c @@ -19,6 +19,7 @@ #include #include "feature/feature_collector.h" +#include "metadata_handler.h" #include "test.h" #include "predict.h" #include "predict.c" @@ -27,6 +28,11 @@ #include #include +typedef struct { + VmafDictionary **metadata; + int flags; +} MetaStruct; + static char *test_predict_score_at_index() { int err; @@ -59,6 +65,202 @@ static char *test_predict_score_at_index() return NULL; } + +void set_meta(void *data, VmafMetadata *metadata) +{ + if (!data) return; + MetaStruct *meta = data; + char key[128], value[128]; + snprintf(key, sizeof(value), "%s_%d", metadata->feature_name, + metadata->picture_index); + snprintf(value, sizeof(value), "%f", metadata->score); + vmaf_dictionary_set(meta->metadata, key, value, meta->flags); +} + +static int g_callback_order[4] = {-1, -1, -1, -1}; +static int g_callback_count = 0; + +static void test_non_monotonic_callback(void *data, VmafMetadata *m) +{ + if (!data) return; + MetaStruct *meta = data; + + if (!strcmp("vmaf", m->feature_name)) + // Track callback order + g_callback_order[m->picture_index] = g_callback_count++; + + // Store in dictionary for verification + char key[32], value[32]; + snprintf(key, sizeof(key), "vmaf_%d", m->picture_index); + snprintf(value, sizeof(value), "%f", m->score); + vmaf_dictionary_set(meta->metadata, key, value, meta->flags); +} + +static char* test_propagate_metadata() +{ + int err; + + VmafDictionary *dict = NULL; + MetaStruct meta_data = { + .metadata = &dict, + .flags = 0, + }; + + VmafMetadataConfiguration m = { + .feature_name = "vmaf", + .callback = set_meta, + .data = &meta_data, + }; + + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata_0", !err); + + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem during vmaf_model_load", !err); + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem during vmaf_mount_model", !err); + + for (unsigned i = 0; i < model->n_features; i++) { + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 0); + mu_assert("problem during vmaf_feature_collector_append", !err); + } + + VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, "vmaf_0", 0); + mu_assert("error on propagaton metadata: propagated key not found!", + e); + mu_assert("error on propagaton metadata: propagated key wrong!", + !strcmp(e->key, "vmaf_0")); + mu_assert("error on propagaton metadata: propagated data wrong!", + !strcmp(e->val, "100.000000")); + + vmaf_feature_collector_destroy(feature_collector); + + m.data = NULL; + m.feature_name = "vmaf"; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata_1", !err); + + for (unsigned i = 0; i < model->n_features; i++) { + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 0); + mu_assert("problem during vmaf_feature_collector_append", !err); + } + + vmaf_feature_collector_destroy(feature_collector); + + m.callback = NULL; + m.feature_name = "vmaf"; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata_2", err); + + vmaf_feature_collector_destroy(feature_collector); + + vmaf_model_destroy(model); + return NULL; + +} + +static char *test_propagate_metadata_non_monotonic() +{ + int err; + + // Reset global counters + g_callback_count = 0; + for (int i = 0; i < 4; i++) { + g_callback_order[i] = -1; + } + + // Setup dictionary to store callback results + VmafDictionary *dict = NULL; + MetaStruct meta_data = { + .metadata = &dict, + .flags = 0, + }; + + VmafMetadataConfiguration m = { + .feature_name = strdup("vmaf"), + .callback = test_non_monotonic_callback, + .data = &meta_data, + }; + + // Initialize feature collector + VmafFeatureCollector *feature_collector; + err = vmaf_feature_collector_init(&feature_collector); + mu_assert("problem during vmaf_feature_collector_init", !err); + + err = vmaf_feature_collector_register_metadata(feature_collector, m); + mu_assert("problem during vmaf_feature_collector_register_metadata", !err); + + // Load VMAF model + VmafModel *model; + VmafModelConfig cfg = { + .name = "vmaf", + .flags = VMAF_MODEL_FLAGS_DEFAULT, + }; + err = vmaf_model_load(&model, &cfg, "vmaf_v0.6.1"); + mu_assert("problem during vmaf_model_load", !err); + err = vmaf_feature_collector_mount_model(feature_collector, model); + mu_assert("problem during vmaf_mount_model", !err); + + // Simulate non-monotonic VMAF score computations + // Frame order: 3, 0, 2, 1 + for (unsigned i = 0; i < model->n_features; i++) { + // Frame 3 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 60., 3); + mu_assert("problem appending frame 3", !err); + + // Frame 0 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 70., 0); + mu_assert("problem appending frame 0", !err); + + // Frame 2 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 80., 2); + mu_assert("problem appending frame 2", !err); + + // Frame 1 + err = vmaf_feature_collector_append(feature_collector, + model->feature[i].name, 90., 1); + mu_assert("problem appending frame 1", !err); + } + + // Verify callback order is monotonic regardless of computation order + mu_assert("Frame 0 callback not first", g_callback_order[0] == 0); + mu_assert("Frame 1 callback not second", g_callback_order[1] == 1); + mu_assert("Frame 2 callback not third", g_callback_order[2] == 2); + mu_assert("Frame 3 callback not fourth", g_callback_order[3] == 3); + + // Verify all frame scores were propagated + for (int i = 0; i < 4; i++) { + char key[32]; + snprintf(key, sizeof(key), "vmaf_%d", i); + VmafDictionaryEntry *e = vmaf_dictionary_get(&dict, key, 0); + mu_assert("Missing frame score in metadata", e != NULL); + } + + vmaf_feature_collector_destroy(feature_collector); + vmaf_model_destroy(model); + return NULL; +} + static char *test_find_linear_function_parameters() { int err; @@ -175,5 +377,7 @@ char *run_tests() mu_run_test(test_predict_score_at_index); mu_run_test(test_find_linear_function_parameters); mu_run_test(test_piecewise_linear_mapping); + mu_run_test(test_propagate_metadata); + mu_run_test(test_propagate_metadata_non_monotonic); return NULL; } From 14a5bbd5b8c2f94cb46e39fa12ab22f0d80ae6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 18:36:19 +0300 Subject: [PATCH 11/12] Revert "refactoring: update feature_collector to support multiple configurations" This reverts commit ffb6067dbd13f38239ec7aab48b5719f3b6a306e. --- libvmaf/src/feature/feature_collector.c | 77 +++++++++++-------------- libvmaf/src/metadata_handler.c | 2 + libvmaf/src/metadata_handler.h | 4 +- 3 files changed, 39 insertions(+), 44 deletions(-) diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index ba4474a29..42f4e340f 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -355,7 +355,7 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, VmafPredictModel *model_iter = feature_collector->models; - while(model_iter) { + while (model_iter) { VmafModel *model = model_iter->model; bool needs_computation = false; @@ -370,51 +370,44 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, pthread_mutex_unlock(&(feature_collector->lock)); res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); pthread_mutex_lock(&(feature_collector->lock)); - } - model_iter = model_iter->next; - } - - VmafCallbackItem *metadata_iter = feature_collector->metadata ? - feature_collector->metadata->head : NULL; - while(metadata_iter) { - // Process all pending frames up to current index in order - unsigned process_index = metadata_iter->last_seen_lowest_index; - metadata_iter->last_seen_highest_index = MAX(picture_index, metadata_iter->last_seen_highest_index); - - while (process_index <= metadata_iter->last_seen_highest_index) { - bool frame_ready = true; - - VmafPredictModel *model_iter = feature_collector->models; - while(model_iter && frame_ready) { - // First check if this frame's score is ready - pthread_mutex_unlock(&(feature_collector->lock)); - if (vmaf_feature_collector_get_score(feature_collector, model_iter->model->name, &score, process_index) != 0) { - frame_ready = false; + if (!res) { + // Process all pending frames up to current index in order + unsigned process_index = feature_collector->metadata->last_seen_lowest_index; + feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); + + while (process_index <= feature_collector->metadata->last_seen_highest_index) { + bool frame_ready = true; + + // First check if this frame's score is ready + pthread_mutex_unlock(&(feature_collector->lock)); + if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { + frame_ready = false; + } + pthread_mutex_lock(&(feature_collector->lock)); + + if (!frame_ready) break; // Stop at first unready frame + + // Frame is ready, trigger callbacks for all features + for (unsigned j = 0; j < feature_collector->cnt; j++) { + VmafMetadata data = { + .feature_name = feature_collector->feature_vector[j]->name, + .picture_index = process_index, + .score = feature_collector->feature_vector[j]->score[process_index].value, + }; + + // Call all metadata callbacks + feature_collector->metadata->head->metadata_cfg.callback( + feature_collector->metadata->head->metadata_cfg.data, &data); + } + + process_index++; + feature_collector->metadata->last_seen_lowest_index = process_index; } - pthread_mutex_lock(&(feature_collector->lock)); - model_iter = model_iter->next; } - - if (!frame_ready) break; // Stop at first unready frame - - // Frame is ready, trigger callbacks for all features - for (unsigned j = 0; j < feature_collector->cnt; j++) { - VmafMetadata data = { - .feature_name = feature_collector->feature_vector[j]->name, - .picture_index = process_index, - .score = feature_collector->feature_vector[j]->score[process_index].value, - }; - - // Call all metadata callbacks - metadata_iter->metadata_cfg.callback( - metadata_iter->metadata_cfg.data, &data); - } - - process_index++; - metadata_iter->last_seen_lowest_index = process_index; } - metadata_iter = metadata_iter->next; + + model_iter = model_iter->next; } unlock: diff --git a/libvmaf/src/metadata_handler.c b/libvmaf/src/metadata_handler.c index 99f69c212..cd792e2c5 100644 --- a/libvmaf/src/metadata_handler.c +++ b/libvmaf/src/metadata_handler.c @@ -31,6 +31,8 @@ int vmaf_metadata_init(VmafCallbackList **const metadata) if (!metadata_s) goto fail; metadata_s->head = NULL; + metadata_s->last_seen_highest_index = 0; + metadata_s->last_seen_lowest_index = 0; return 0; diff --git a/libvmaf/src/metadata_handler.h b/libvmaf/src/metadata_handler.h index 908eef5d7..c6439ec71 100644 --- a/libvmaf/src/metadata_handler.h +++ b/libvmaf/src/metadata_handler.h @@ -23,13 +23,13 @@ typedef struct VmafCallbackItem { VmafMetadataConfiguration metadata_cfg; - unsigned last_seen_highest_index; - unsigned last_seen_lowest_index; struct VmafCallbackItem *next; } VmafCallbackItem; typedef struct VmafCallbackList{ VmafCallbackItem *head; + unsigned last_seen_highest_index; + unsigned last_seen_lowest_index; } VmafCallbackList; int vmaf_metadata_init(VmafCallbackList **const metadata); From 24dd775282e76d9192d8731dcee4b8b0e91c0d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yi=C4=9Fithan=20Yi=C4=9Fit?= Date: Fri, 31 Jan 2025 18:36:20 +0300 Subject: [PATCH 12/12] Revert "refactor: Fix indentation" This reverts commit 4e21c824a369fb75392e07d6af28bdb57b92a8ff. --- libvmaf/src/feature/feature_collector.c | 92 ++++++++++++------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/libvmaf/src/feature/feature_collector.c b/libvmaf/src/feature/feature_collector.c index 42f4e340f..7b98100cd 100644 --- a/libvmaf/src/feature/feature_collector.c +++ b/libvmaf/src/feature/feature_collector.c @@ -355,61 +355,61 @@ int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, VmafPredictModel *model_iter = feature_collector->models; - while (model_iter) { - VmafModel *model = model_iter->model; - bool needs_computation = false; +while (model_iter) { + VmafModel *model = model_iter->model; + bool needs_computation = false; - // Check if current score needs computation + // Check if current score needs computation + pthread_mutex_unlock(&(feature_collector->lock)); + res = vmaf_feature_collector_get_score(feature_collector, model->name, &score, picture_index); + needs_computation = (res != 0); + pthread_mutex_lock(&(feature_collector->lock)); + + if (needs_computation) { + // Compute the current frame's score pthread_mutex_unlock(&(feature_collector->lock)); - res = vmaf_feature_collector_get_score(feature_collector, model->name, &score, picture_index); - needs_computation = (res != 0); + res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); pthread_mutex_lock(&(feature_collector->lock)); - if (needs_computation) { - // Compute the current frame's score - pthread_mutex_unlock(&(feature_collector->lock)); - res = vmaf_predict_score_at_index(model, feature_collector, picture_index, &score, true, true, 0); - pthread_mutex_lock(&(feature_collector->lock)); - - if (!res) { - // Process all pending frames up to current index in order - unsigned process_index = feature_collector->metadata->last_seen_lowest_index; - feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); - - while (process_index <= feature_collector->metadata->last_seen_highest_index) { - bool frame_ready = true; - - // First check if this frame's score is ready - pthread_mutex_unlock(&(feature_collector->lock)); - if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { - frame_ready = false; - } - pthread_mutex_lock(&(feature_collector->lock)); - - if (!frame_ready) break; // Stop at first unready frame - - // Frame is ready, trigger callbacks for all features - for (unsigned j = 0; j < feature_collector->cnt; j++) { - VmafMetadata data = { - .feature_name = feature_collector->feature_vector[j]->name, - .picture_index = process_index, - .score = feature_collector->feature_vector[j]->score[process_index].value, - }; - - // Call all metadata callbacks - feature_collector->metadata->head->metadata_cfg.callback( - feature_collector->metadata->head->metadata_cfg.data, &data); - } - - process_index++; - feature_collector->metadata->last_seen_lowest_index = process_index; + if (!res) { + // Process all pending frames up to current index in order + unsigned process_index = feature_collector->metadata->last_seen_lowest_index; + feature_collector->metadata->last_seen_highest_index = MAX(picture_index, feature_collector->metadata->last_seen_highest_index); + + while (process_index <= feature_collector->metadata->last_seen_highest_index) { + bool frame_ready = true; + + // First check if this frame's score is ready + pthread_mutex_unlock(&(feature_collector->lock)); + if (vmaf_feature_collector_get_score(feature_collector, model->name, &score, process_index) != 0) { + frame_ready = false; } + pthread_mutex_lock(&(feature_collector->lock)); + + if (!frame_ready) break; // Stop at first unready frame + + // Frame is ready, trigger callbacks for all features + for (unsigned j = 0; j < feature_collector->cnt; j++) { + VmafMetadata data = { + .feature_name = feature_collector->feature_vector[j]->name, + .picture_index = process_index, + .score = feature_collector->feature_vector[j]->score[process_index].value, + }; + + // Call all metadata callbacks + feature_collector->metadata->head->metadata_cfg.callback( + feature_collector->metadata->head->metadata_cfg.data, &data); + } + + process_index++; + feature_collector->metadata->last_seen_lowest_index = process_index; } } - - model_iter = model_iter->next; } + model_iter = model_iter->next; +} + unlock: feature_collector->timer.end = clock(); pthread_mutex_unlock(&(feature_collector->lock));