libvmaf/src/feature/feature_collector.c (364 lines of code) (raw):

/** * * Copyright 2016-2020 Netflix, Inc. * * Licensed under the BSD+Patent License (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://opensource.org/licenses/BSDplusPatent * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include <errno.h> #include <pthread.h> #include <stdarg.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> #include "dict.h" #include "metadata_handler.h" #include "feature_collector.h" #include "feature_name.h" #include "libvmaf/libvmaf.h" #include "log.h" #include "predict.h" static int aggregate_vector_init(AggregateVector *aggregate_vector) { if (!aggregate_vector) return -EINVAL; memset(aggregate_vector, 0, sizeof(*aggregate_vector)); const unsigned initial_capacity = 8; const size_t metric_vector_sz = sizeof(aggregate_vector->metric[0]) * initial_capacity; aggregate_vector->metric = malloc(metric_vector_sz); if (!aggregate_vector->metric) return -ENOMEM; memset(aggregate_vector->metric, 0, metric_vector_sz); aggregate_vector->capacity = initial_capacity; return 0; } static int aggregate_vector_append(AggregateVector *aggregate_vector, const char *feature_name, double score) { if (!aggregate_vector) return -EINVAL; for (unsigned i = 0; i < aggregate_vector->cnt; i++) { if (!strcmp(feature_name, aggregate_vector->metric[i].name)) { if (aggregate_vector->metric[i].value == score) { return 0; } else { return -EINVAL; } } } const unsigned cnt = aggregate_vector->cnt; if (cnt >= aggregate_vector->capacity) { size_t initial_size = sizeof(aggregate_vector->metric[0]) * aggregate_vector->capacity; void *metric = realloc(aggregate_vector->metric, initial_size * 2); if (!metric) return -ENOMEM; memset((char*)metric + initial_size, 0, initial_size); aggregate_vector->metric = metric; aggregate_vector->capacity *= 2; } const size_t feature_name_sz = strnlen(feature_name, 2048); char *f = malloc(feature_name_sz + 1); if (!f) return -EINVAL; memset(f, 0, feature_name_sz + 1); strncpy(f, feature_name, feature_name_sz); aggregate_vector->metric[cnt].name = f; aggregate_vector->metric[cnt].value = score; aggregate_vector->cnt++; return 0; } static void aggregate_vector_destroy(AggregateVector *aggregate_vector) { if (!aggregate_vector) return; for (unsigned i = 0; i < aggregate_vector->cnt; i++) { if (aggregate_vector->metric[i].name) free(aggregate_vector->metric[i].name); } free(aggregate_vector->metric); } int vmaf_feature_collector_set_aggregate(VmafFeatureCollector *feature_collector, const char *feature_name, double score) { if (!feature_collector) return -EINVAL; if (!feature_name) return -EINVAL; pthread_mutex_lock(&(feature_collector->lock)); int err = aggregate_vector_append(&feature_collector->aggregate_vector, feature_name, score); pthread_mutex_unlock(&(feature_collector->lock)); return err; } int vmaf_feature_collector_get_aggregate(VmafFeatureCollector *feature_collector, const char *feature_name, double *score) { if (!feature_collector) return -EINVAL; if (!feature_name) return -EINVAL; if (!score) return -EINVAL; pthread_mutex_lock(&(feature_collector->lock)); int err = 0; double *s = NULL; for (unsigned i = 0; i < feature_collector->aggregate_vector.cnt; i++) { const char *f = feature_collector->aggregate_vector.metric[i].name; if (!strcmp(f, feature_name)) { s = &(feature_collector->aggregate_vector.metric[i].value); break; } } if (!s) { err = -EINVAL; goto unlock; }; *score = *s; unlock: pthread_mutex_unlock(&(feature_collector->lock)); return err; } static int feature_vector_init(FeatureVector **const feature_vector, const char *name) { if (!feature_vector) return -EINVAL; if (!name) return -EINVAL; FeatureVector *const fv = *feature_vector = malloc(sizeof(*fv)); if (!fv) goto fail; memset(fv, 0, sizeof(*fv)); fv->name = malloc(strlen(name) + 1); if (!fv->name) goto free_fv; strcpy(fv->name, name); fv->capacity = 8; fv->score = malloc(sizeof(fv->score[0]) * fv->capacity); if (!fv->score) goto free_name; memset(fv->score, 0, sizeof(fv->score[0]) * fv->capacity); return 0; free_name: free(fv->name); free_fv: free(fv); fail: return -ENOMEM; } static void feature_vector_destroy(FeatureVector *feature_vector) { if (!feature_vector) return; free(feature_vector->name); free(feature_vector->score); free(feature_vector); } static int feature_vector_append(FeatureVector *feature_vector, unsigned index, double score) { if (!feature_vector) return -EINVAL; while (index >= feature_vector->capacity) { size_t initial_size = sizeof(feature_vector->score[0]) * feature_vector->capacity; void *score = realloc(feature_vector->score, initial_size * 2); if (!score) return -ENOMEM; memset((char*)score + initial_size, 0, initial_size); feature_vector->score = score; feature_vector->capacity *= 2; } if (feature_vector->score[index].written) { vmaf_log(VMAF_LOG_LEVEL_WARNING, "feature \"%s\" cannot be overwritten at index %d\n", feature_vector->name, index); return -EINVAL; } feature_vector->score[index].written = true; feature_vector->score[index].value = score; return 0; } int vmaf_feature_collector_init(VmafFeatureCollector **const feature_collector) { if (!feature_collector) return -EINVAL; int err = 0; VmafFeatureCollector *const fc = *feature_collector = malloc(sizeof(*fc)); if (!fc) goto fail; memset(fc, 0, sizeof(*fc)); fc->capacity = 8; fc->feature_vector = malloc(sizeof(*(fc->feature_vector)) * fc->capacity); if (!fc->feature_vector) goto free_fc; memset(fc->feature_vector, 0, sizeof(*(fc->feature_vector)) * fc->capacity); err = aggregate_vector_init(&fc->aggregate_vector); if (err) goto free_feature_vector; err = pthread_mutex_init(&(fc->lock), NULL); if (err) goto free_aggregate_vector; err = vmaf_metadata_init(&(fc->metadata)); if (err) goto free_mutex; return 0; free_mutex: pthread_mutex_destroy(&(fc->lock)); free_aggregate_vector: aggregate_vector_destroy(&(fc->aggregate_vector)); free_feature_vector: free(fc->feature_vector); free_fc: free(fc); fail: return -ENOMEM; } int vmaf_feature_collector_mount_model(VmafFeatureCollector *feature_collector, VmafModel *model) { if (!feature_collector) return -EINVAL; if (!model) return -EINVAL; VmafPredictModel *m = malloc(sizeof(VmafPredictModel)); if (!m) return -ENOMEM; m->model = model; m->next = NULL; VmafPredictModel **head = &feature_collector->models; while (*head && (*head)->next != NULL) *head = (*head)->next; if (!(*head)) *head = m; else (*head)->next = m; return 0; } int vmaf_feature_collector_unmount_model(VmafFeatureCollector *feature_collector, VmafModel *model) { if (!feature_collector) return -EINVAL; if (!model) return -EINVAL; VmafPredictModel **head = &feature_collector->models; while (*head && (*head)->model != model) head = &(*head)->next; if (!(*head)) return -EINVAL; VmafPredictModel *m = *head; *head = m->next; free(m); return 0; } int vmaf_feature_collector_register_metadata(VmafFeatureCollector *feature_collector, VmafMetadataConfiguration metadata_cfg) { if (!feature_collector) return -EINVAL; if (!metadata_cfg.feature_name) return -EINVAL; if (!metadata_cfg.callback) return -EINVAL; VmafCallbackList *metadata = feature_collector->metadata; int err = vmaf_metadata_append(metadata, metadata_cfg); if (err) return err; return 0; } static FeatureVector *find_feature_vector(VmafFeatureCollector *fc, const char *feature_name) { FeatureVector *feature_vector = NULL; for (unsigned i = 0; i < fc->cnt; i++) { FeatureVector *fv = fc->feature_vector[i]; if (!strcmp(fv->name, feature_name)) { feature_vector = fv; break; } } return feature_vector; } int vmaf_feature_collector_append(VmafFeatureCollector *feature_collector, const char *feature_name, double score, unsigned picture_index) { if (!feature_collector) return -EINVAL; if (!feature_name) return -EINVAL; pthread_mutex_lock(&(feature_collector->lock)); int err = 0; if (!feature_collector->timer.begin) feature_collector->timer.begin = clock(); FeatureVector *feature_vector = find_feature_vector(feature_collector, feature_name); if (!feature_vector) { err = feature_vector_init(&feature_vector, feature_name); if (err) goto unlock; if (feature_collector->cnt + 1 > feature_collector->capacity) { size_t initial_size = sizeof(feature_collector->feature_vector[0]) * feature_vector->capacity; FeatureVector **fv = realloc(feature_collector->feature_vector, sizeof(*(feature_collector->feature_vector)) * initial_size * 2); if (!fv) { err = -ENOMEM; goto unlock; } memset(fv + feature_collector->capacity, 0, initial_size); feature_collector->feature_vector = fv; feature_collector->capacity *= 2; } feature_collector->feature_vector[feature_collector->cnt++] = feature_vector; } err = feature_vector_append(feature_vector, picture_index, score); if (err) goto unlock; 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; // 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; 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 (res) { 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; } next_metadata: metadata_iter = metadata_iter->next; } unlock: feature_collector->timer.end = clock(); pthread_mutex_unlock(&(feature_collector->lock)); return err; } int vmaf_feature_collector_append_with_dict(VmafFeatureCollector *fc, VmafDictionary *dict, const char *feature_name, double score, unsigned index) { if (!fc) return -EINVAL; if (!dict) return -EINVAL; VmafDictionaryEntry *entry = vmaf_dictionary_get(&dict, feature_name, 0); const char *fn = entry ? entry->val : feature_name; return vmaf_feature_collector_append(fc, fn, score, index); } int vmaf_feature_collector_get_score(VmafFeatureCollector *feature_collector, const char *feature_name, double *score, unsigned index) { if (!feature_collector) return -EINVAL; if (!feature_name) return -EINVAL; if (!score) return -EINVAL; pthread_mutex_lock(&(feature_collector->lock)); int err = 0; FeatureVector *feature_vector = find_feature_vector(feature_collector, feature_name); if (!feature_vector || index >= feature_vector->capacity) { err = -EINVAL; goto unlock; } if (!feature_vector->score[index].written) { err = -EINVAL; goto unlock; } *score = feature_vector->score[index].value; unlock: pthread_mutex_unlock(&(feature_collector->lock)); return err; } void vmaf_feature_collector_destroy(VmafFeatureCollector *feature_collector) { if (!feature_collector) return; pthread_mutex_lock(&(feature_collector->lock)); aggregate_vector_destroy(&(feature_collector->aggregate_vector)); for (unsigned i = 0; i < feature_collector->cnt; i++) feature_vector_destroy(feature_collector->feature_vector[i]); while (feature_collector->models) vmaf_feature_collector_unmount_model(feature_collector, feature_collector->models->model); vmaf_metadata_destroy(feature_collector->metadata); free(feature_collector->feature_vector); pthread_mutex_unlock(&(feature_collector->lock)); pthread_mutex_destroy(&(feature_collector->lock)); free(feature_collector); }