libvmaf/src/libvmaf.c (794 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 <stdbool.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "libvmaf/libvmaf.h" #include "libvmaf/feature.h" #include "libvmaf/picture.h" #include "cpu.h" #include "feature/feature_extractor.h" #include "feature/feature_collector.h" #include "metadata_handler.h" #include "fex_ctx_vector.h" #include "log.h" #include "model.h" #include "output.h" #include "picture.h" #include "predict.h" #include "thread_pool.h" #include "vcs_version.h" #ifdef HAVE_CUDA #include "libvmaf/libvmaf_cuda.h" #include "cuda/common.h" #include "cuda/cuda_helper.cuh" #include "cuda/picture_cuda.h" #include "cuda/ring_buffer.h" #endif typedef struct VmafContext { VmafConfiguration cfg; VmafFeatureCollector *feature_collector; RegisteredFeatureExtractors registered_feature_extractors; VmafFeatureExtractorContextPool *fex_ctx_pool; VmafThreadPool *thread_pool; VmafFrameSyncContext *framesync; #ifdef HAVE_CUDA struct { struct { struct { unsigned w, h; unsigned bpc; enum VmafPixelFormat pix_fmt; } pic_params; enum VmafCudaPicturePreallocationMethod pic_prealloc_method; int device_id; int stream_priority; } cfg; VmafCudaState state; VmafCudaCookie cookie; VmafRingBuffer* ring_buffer; } cuda; #endif struct { unsigned w, h; enum VmafPixelFormat pix_fmt; unsigned bpc; enum VmafPictureBufferType buf_type; } pic_params; unsigned pic_cnt; bool flushed; } VmafContext; int vmaf_init(VmafContext **vmaf, VmafConfiguration cfg) { if (!vmaf) return -EINVAL; int err = 0; VmafContext *const v = *vmaf = malloc(sizeof(*v)); if (!v) goto fail; memset(v, 0, sizeof(*v)); v->cfg = cfg; vmaf_init_cpu(); vmaf_set_cpu_flags_mask(~cfg.cpumask); vmaf_set_log_level(cfg.log_level); err = vmaf_framesync_init(&(v->framesync)); if (err) goto free_v; err = vmaf_feature_collector_init(&(v->feature_collector)); if (err) goto free_framesync; err = feature_extractor_vector_init(&(v->registered_feature_extractors)); if (err) goto free_feature_collector; if (v->cfg.n_threads > 0) { err = vmaf_thread_pool_create(&v->thread_pool, v->cfg.n_threads); if (err) goto free_feature_extractor_vector; err = vmaf_fex_ctx_pool_create(&v->fex_ctx_pool, v->cfg.n_threads); if (err) goto free_thread_pool; } return 0; free_thread_pool: vmaf_thread_pool_destroy(v->thread_pool); free_feature_extractor_vector: feature_extractor_vector_destroy(&(v->registered_feature_extractors)); free_feature_collector: vmaf_feature_collector_destroy(v->feature_collector); free_framesync: vmaf_framesync_destroy(v->framesync); free_v: free(v); fail: return -ENOMEM; } #ifdef HAVE_CUDA static int prepare_ring_buffer(VmafContext *vmaf, unsigned w, unsigned h, enum VmafPixelFormat pix_fmt, unsigned bpc) { if (!vmaf) return -EINVAL; if (!w) return -EINVAL; if (!h) return -EINVAL; if (!pix_fmt) return -EINVAL; if (!bpc) return -EINVAL; vmaf->cuda.cookie.pix_fmt = vmaf->pic_params.pix_fmt = pix_fmt; vmaf->cuda.cookie.h = vmaf->pic_params.h = h; vmaf->cuda.cookie.w = vmaf->pic_params.w = w; vmaf->cuda.cookie.bpc = vmaf->pic_params.bpc = bpc; vmaf->cuda.cookie.state = &vmaf->cuda.state; VmafRingBufferConfig cfg_buf = { .pic_cnt = 4, .cookie = &vmaf->cuda.cookie, .synchronize_picture_callback = vmaf_cuda_picture_synchronize, .alloc_picture_callback = vmaf_cuda_picture_alloc, .free_picture_callback = vmaf_cuda_picture_free, }; return vmaf_ring_buffer_init(&vmaf->cuda.ring_buffer, cfg_buf); } int vmaf_cuda_import_state(VmafContext *vmaf, VmafCudaState *cu_state) { if (!vmaf) return -EINVAL; if (!cu_state) return -EINVAL; vmaf->cuda.state = *cu_state; return 0; } int vmaf_cuda_preallocate_pictures(VmafContext *vmaf, VmafCudaPictureConfiguration cfg) { if (!vmaf) return -EINVAL; int err = 0; vmaf->cuda.cfg.pic_params.w = cfg.pic_params.w; vmaf->cuda.cfg.pic_params.h = cfg.pic_params.h; vmaf->cuda.cfg.pic_params.bpc = cfg.pic_params.bpc; vmaf->cuda.cfg.pic_params.pix_fmt = cfg.pic_params.pix_fmt; vmaf->cuda.cfg.pic_prealloc_method = cfg.pic_prealloc_method; switch (cfg.pic_prealloc_method) { case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_NONE: break; case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_HOST: case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_HOST_PINNED: case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_DEVICE: err = prepare_ring_buffer(vmaf, cfg.pic_params.w, cfg.pic_params.h, cfg.pic_params.pix_fmt, cfg.pic_params.bpc); if (err) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "problem during cuda picture preallocation\n"); return err; } break; default: vmaf_log(VMAF_LOG_LEVEL_ERROR, "unknown cuda picture preallocation method\n"); return -EINVAL; } return err; } int vmaf_cuda_fetch_preallocated_picture(VmafContext *vmaf, VmafPicture* pic) { if (!vmaf) return -EINVAL; if (!pic) return -EINVAL; if (!vmaf->cuda.ring_buffer) return -EINVAL; //TODO: preallocate host pics switch (vmaf->cuda.cfg.pic_prealloc_method) { case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_DEVICE: return vmaf_ring_buffer_fetch_next_picture(vmaf->cuda.ring_buffer, pic); case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_HOST: return vmaf_picture_alloc(pic, vmaf->cuda.cfg.pic_params.pix_fmt, vmaf->cuda.cfg.pic_params.bpc, vmaf->cuda.cfg.pic_params.w, vmaf->cuda.cfg.pic_params.h); case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_HOST_PINNED: return vmaf_cuda_picture_alloc_pinned(pic, vmaf->cuda.cfg.pic_params.pix_fmt, vmaf->cuda.cfg.pic_params.bpc, vmaf->cuda.cfg.pic_params.w, vmaf->cuda.cfg.pic_params.h, &vmaf->cuda.state); case VMAF_CUDA_PICTURE_PREALLOCATION_METHOD_NONE: default: vmaf_log(VMAF_LOG_LEVEL_ERROR, "undefined cuda picture preallocation method\n"); return -EINVAL; } } static int set_fex_cuda_state(VmafFeatureExtractorContext *fex_ctx, VmafContext *vmaf) { if (fex_ctx->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA) fex_ctx->fex->cu_state = &(vmaf->cuda.state); return 0; } #endif static int set_fex_framesync(VmafFeatureExtractorContext *fex_ctx, VmafContext *vmaf) { if (fex_ctx->fex->flags & VMAF_FEATURE_FRAME_SYNC) fex_ctx->fex->framesync = (vmaf->framesync); return 0; } int vmaf_close(VmafContext *vmaf) { if (!vmaf) return -EINVAL; vmaf_thread_pool_wait(vmaf->thread_pool); vmaf_framesync_destroy(vmaf->framesync); feature_extractor_vector_destroy(&(vmaf->registered_feature_extractors)); vmaf_feature_collector_destroy(vmaf->feature_collector); vmaf_thread_pool_destroy(vmaf->thread_pool); vmaf_fex_ctx_pool_destroy(vmaf->fex_ctx_pool); #ifdef HAVE_CUDA if (vmaf->cuda.ring_buffer) vmaf_ring_buffer_close(vmaf->cuda.ring_buffer); if (vmaf->cuda.state.ctx) vmaf_cuda_release(&vmaf->cuda.state); #endif free(vmaf); return 0; } int vmaf_import_feature_score(VmafContext *vmaf, const char *feature_name, double value, unsigned index) { if (!vmaf) return -EINVAL; if (!feature_name) return -EINVAL; return vmaf_feature_collector_append(vmaf->feature_collector, feature_name, value, index); } int vmaf_use_feature(VmafContext *vmaf, const char *feature_name, VmafFeatureDictionary *opts_dict) { if (!vmaf) return -EINVAL; if (!feature_name) return -EINVAL; VmafDictionary *s = (VmafDictionary*) opts_dict; int err = 0; VmafFeatureExtractor *fex = vmaf_get_feature_extractor_by_name(feature_name); if (!fex) return -EINVAL; VmafDictionary *d = NULL; if (s) { err = vmaf_dictionary_copy(&s, &d); if (err) return err; err = vmaf_dictionary_free(&s); if (err) return err; } VmafFeatureExtractorContext *fex_ctx; err = vmaf_feature_extractor_context_create(&fex_ctx, fex, d); #ifdef HAVE_CUDA err |= set_fex_cuda_state(fex_ctx, vmaf); #endif err |= set_fex_framesync(fex_ctx, vmaf); if (err) return err; RegisteredFeatureExtractors *rfe = &(vmaf->registered_feature_extractors); err = feature_extractor_vector_append(rfe, fex_ctx, 0); if (err) err |= vmaf_feature_extractor_context_destroy(fex_ctx); return err; } int vmaf_use_features_from_model(VmafContext *vmaf, VmafModel *model) { if (!vmaf) return -EINVAL; if (!model) return -EINVAL; int err = 0; unsigned fex_flags = 0; #ifdef HAVE_CUDA if (!vmaf->cfg.gpumask && vmaf->cuda.state.ctx) fex_flags |= VMAF_FEATURE_EXTRACTOR_CUDA; #endif RegisteredFeatureExtractors *rfe = &(vmaf->registered_feature_extractors); for (unsigned i = 0; i < model->n_features; i++) { VmafFeatureExtractor *fex = vmaf_get_feature_extractor_by_feature_name(model->feature[i].name, fex_flags); if (!fex) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "could not initialize feature extractor \"%s\"\n", model->feature[i].name); return -EINVAL; } VmafFeatureExtractorContext *fex_ctx; VmafDictionary *d = NULL; if (model->feature[i].opts_dict) { err = vmaf_dictionary_copy(&model->feature[i].opts_dict, &d); if (err) return err; } err = vmaf_feature_extractor_context_create(&fex_ctx, fex, d); #ifdef HAVE_CUDA err |= set_fex_cuda_state(fex_ctx, vmaf); #endif err |= set_fex_framesync(fex_ctx, vmaf); if (err) return err; err = feature_extractor_vector_append(rfe, fex_ctx, 0); if (err) { err |= vmaf_feature_extractor_context_destroy(fex_ctx); return err; } } err = vmaf_feature_collector_mount_model(vmaf->feature_collector, model); if (err) return err; return 0; } int vmaf_use_features_from_model_collection(VmafContext *vmaf, VmafModelCollection *model_collection) { if (!vmaf) return -EINVAL; if (!model_collection) return -EINVAL; int err = 0; for (unsigned i = 0; i < model_collection->cnt; i++) err |= vmaf_use_features_from_model(vmaf, model_collection->model[i]); return err; } struct ThreadData { VmafFeatureExtractorContext *fex_ctx; VmafPicture ref, dist; unsigned index; VmafFeatureCollector *feature_collector; VmafFeatureExtractorContextPool *fex_ctx_pool; int err; }; static void threaded_extract_func(void *e) { struct ThreadData *f = e; f->err = vmaf_feature_extractor_context_extract(f->fex_ctx, &f->ref, NULL, &f->dist, NULL, f->index, f->feature_collector); f->err = vmaf_fex_ctx_pool_release(f->fex_ctx_pool, f->fex_ctx); vmaf_picture_unref(&f->ref); vmaf_picture_unref(&f->dist); } static int threaded_read_pictures(VmafContext *vmaf, VmafPicture *ref, VmafPicture *dist, unsigned index) { if (!vmaf) return -EINVAL; if (!ref) return -EINVAL; if (!dist) return -EINVAL; int err = 0; for (unsigned i = 0; i < vmaf->registered_feature_extractors.cnt; i++) { VmafFeatureExtractor *fex = vmaf->registered_feature_extractors.fex_ctx[i]->fex; if (fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA) continue; VmafDictionary *opts_dict = vmaf->registered_feature_extractors.fex_ctx[i]->opts_dict; if ((vmaf->cfg.n_subsample > 1) && (index % vmaf->cfg.n_subsample) && !(fex->flags & VMAF_FEATURE_EXTRACTOR_TEMPORAL)) { continue; } fex->framesync = vmaf->framesync; VmafFeatureExtractorContext *fex_ctx; err = vmaf_fex_ctx_pool_aquire(vmaf->fex_ctx_pool, fex, opts_dict, &fex_ctx); if (err) return err; VmafPicture pic_a, pic_b; vmaf_picture_ref(&pic_a, ref); vmaf_picture_ref(&pic_b, dist); struct ThreadData data = { .fex_ctx = fex_ctx, .ref = pic_a, .dist = pic_b, .index = index, .feature_collector = vmaf->feature_collector, .fex_ctx_pool = vmaf->fex_ctx_pool, .err = 0, }; err = vmaf_thread_pool_enqueue(vmaf->thread_pool, threaded_extract_func, &data, sizeof(data)); if (err) { vmaf_picture_unref(&pic_a); vmaf_picture_unref(&pic_b); return err; } } return vmaf_picture_unref(ref) | vmaf_picture_unref(dist); } static int validate_pic_params(VmafContext *vmaf, VmafPicture *ref, VmafPicture *dist) { VmafPicturePrivate *ref_priv = ref->priv; VmafPicturePrivate *dist_priv = dist->priv; if (!vmaf->pic_params.w) { vmaf->pic_params.w = ref->w[0]; vmaf->pic_params.h = ref->h[0]; vmaf->pic_params.pix_fmt = ref->pix_fmt; vmaf->pic_params.bpc = ref->bpc; } vmaf->pic_params.buf_type = ref_priv->buf_type; if ((ref->w[0] != dist->w[0]) || (ref->w[0] != vmaf->pic_params.w)) return -EINVAL; if ((ref->h[0] != dist->h[0]) || (ref->h[0] != vmaf->pic_params.h)) return -EINVAL; if ((ref->pix_fmt != dist->pix_fmt) || (ref->pix_fmt != vmaf->pic_params.pix_fmt)) { return -EINVAL; } if ((ref->bpc != dist->bpc) && (ref->bpc != vmaf->pic_params.bpc)) return -EINVAL; if (ref_priv->buf_type != dist_priv->buf_type) return -EINVAL; return 0; } static int flush_context_threaded(VmafContext *vmaf) { int err = 0; err |= vmaf_thread_pool_wait(vmaf->thread_pool); err |= vmaf_fex_ctx_pool_flush(vmaf->fex_ctx_pool, vmaf->feature_collector); if (!err) vmaf->flushed = true; return err; } static int flush_context(VmafContext *vmaf) { int err = 0; if (vmaf->thread_pool) err = flush_context_threaded(vmaf); else { RegisteredFeatureExtractors rfe = vmaf->registered_feature_extractors; for (unsigned i = 0; i < rfe.cnt; i++) { if (!(rfe.fex_ctx[i]->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA)) err |= vmaf_feature_extractor_context_flush(rfe.fex_ctx[i], vmaf->feature_collector); } } #ifdef HAVE_CUDA if (vmaf->cuda.state.ctx) { RegisteredFeatureExtractors rfe = vmaf->registered_feature_extractors; for (unsigned i = 0; i < rfe.cnt; i++) { if (rfe.fex_ctx[i]->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA) err |= vmaf_feature_extractor_context_flush(rfe.fex_ctx[i], vmaf->feature_collector); } err |= cuCtxPushCurrent(vmaf->cuda.state.ctx); err |= cuStreamSynchronize(vmaf->cuda.state.str); err |= cuCtxSynchronize(); err |= cuCtxPopCurrent(NULL); if (err) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "context could not be synchronized\n"); return -EINVAL; } } #endif if (!err) vmaf->flushed = true; return err; } #ifdef HAVE_CUDA static int check_ring_buffer(VmafContext *vmaf) { if (!vmaf->cuda.state.ctx) return 0; int err = 0; if (!vmaf->cuda.cfg.pic_prealloc_method && !vmaf->cuda.ring_buffer) { err = prepare_ring_buffer(vmaf, vmaf->pic_params.w, vmaf->pic_params.h, vmaf->pic_params.pix_fmt, vmaf->pic_params.bpc); if (err) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "problem during prepare_ring_buffer\n"); return -EINVAL; } } return err; } enum { HW_FLAG_HOST = 1 << 0, HW_FLAG_DEVICE = 1 << 1, }; static int translate_picture_host(VmafContext *vmaf, VmafPicture *pic, VmafPicture *pic_device, unsigned hw_flags) { int err = 0; if (!(hw_flags & HW_FLAG_DEVICE)) return err; //host to device switch(vmaf->pic_params.buf_type) { case VMAF_PICTURE_BUFFER_TYPE_HOST: case VMAF_PICTURE_BUFFER_TYPE_CUDA_HOST_PINNED: if (!vmaf->cuda.state.ctx) return -EINVAL; err |= vmaf_ring_buffer_fetch_next_picture(vmaf->cuda.ring_buffer, pic_device); err |= vmaf_cuda_picture_upload_async(pic_device, pic, 0x1); if (err) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "problem moving host pic into cuda device buffer\n"); return err; } break; default: return -EINVAL; } return err; } static int translate_picture_device(VmafContext *vmaf, VmafPicture *pic, VmafPicture *pic_host, unsigned hw_flags) { int err = 0; if (!(hw_flags & HW_FLAG_HOST)) return err; //device to host err = vmaf_picture_alloc(pic_host, pic->pix_fmt, pic->bpc, pic->w[0], pic->h[0]); if (err) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "problem allocating host pic\n"); return err; } err = vmaf_cuda_picture_download_async(pic, pic_host, 0x1); if (err) { vmaf_log(VMAF_LOG_LEVEL_ERROR, "problem moving cuda pic into host buffer\n"); return err; } return err; } static int translate_picture(VmafContext *vmaf, VmafPicture *pic, VmafPicture *pic_host, VmafPicture *pic_device, unsigned hw_flags) { const VmafPicturePrivate *pic_priv = pic->priv; switch(pic_priv->buf_type) { case VMAF_PICTURE_BUFFER_TYPE_HOST: case VMAF_PICTURE_BUFFER_TYPE_CUDA_HOST_PINNED: *pic_host = *pic; return translate_picture_host(vmaf, pic, pic_device, hw_flags); case VMAF_PICTURE_BUFFER_TYPE_CUDA_DEVICE: *pic_device = *pic; return translate_picture_device(vmaf, pic, pic_host, hw_flags); default: return -EINVAL; } } static unsigned rfe_hw_flags(RegisteredFeatureExtractors *rfe) { if (!rfe) return -EINVAL; unsigned flags = 0; for (unsigned i = 0; i < rfe->cnt; i++) { flags |= rfe->fex_ctx[i]->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA ? HW_FLAG_DEVICE : HW_FLAG_HOST; } return flags; } #endif int vmaf_read_pictures(VmafContext *vmaf, VmafPicture *ref, VmafPicture *dist, unsigned index) { if (!vmaf) return -EINVAL; if (vmaf->flushed) return -EINVAL; if (!ref != !dist) return -EINVAL; if (!ref && !dist) return flush_context(vmaf); int err = 0; vmaf->pic_cnt++; err = validate_pic_params(vmaf, ref, dist); if (err) return err; #ifdef HAVE_CUDA err = check_ring_buffer(vmaf); if (err) return err; const unsigned hw_flags = rfe_hw_flags(&vmaf->registered_feature_extractors); VmafPicture ref_host = { 0 }, ref_device = { 0 }; err = translate_picture(vmaf, ref, &ref_host, &ref_device, hw_flags); if (err) return err; VmafPicture dist_host = { 0 }, dist_device = { 0 }; err = translate_picture(vmaf, dist, &dist_host, &dist_device, hw_flags); #endif for (unsigned i = 0; i < vmaf->registered_feature_extractors.cnt; i++) { VmafFeatureExtractorContext *fex_ctx = vmaf->registered_feature_extractors.fex_ctx[i]; if (!(fex_ctx->fex->flags & VMAF_FEATURE_EXTRACTOR_TEMPORAL)) { if ((vmaf->cfg.n_subsample > 1) && (index % vmaf->cfg.n_subsample)) continue; } if (!(fex_ctx->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA) && vmaf->thread_pool) { continue; } #ifdef HAVE_CUDA ref = fex_ctx->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA ? &ref_device : &ref_host; dist = fex_ctx->fex->flags & VMAF_FEATURE_EXTRACTOR_CUDA ? &dist_device : &dist_host; #endif err = vmaf_feature_extractor_context_extract(fex_ctx, ref, NULL, dist, NULL, index, vmaf->feature_collector); if (err) return err; } #ifdef HAVE_CUDA ref = &ref_host; dist = &dist_host; #endif //multithreading for GPU does not yield performance benefits //disabled for now if (vmaf->thread_pool){ return threaded_read_pictures(vmaf, ref, dist, index); } #ifdef HAVE_CUDA if (ref_host.priv) err |= vmaf_picture_unref(&ref_host); if (dist_host.priv) err |= vmaf_picture_unref(&dist_host); if (ref_device.priv) { CHECK_CUDA(cuEventRecord(vmaf_cuda_picture_get_finished_event(&ref_device), vmaf_cuda_picture_get_stream(&ref_device))); //^FIXME: move to picture callback err |= vmaf_picture_unref(&ref_device); } if (dist_device.priv) { CHECK_CUDA(cuEventRecord(vmaf_cuda_picture_get_finished_event(&dist_device), vmaf_cuda_picture_get_stream(&dist_device))); //^FIXME: move to picture callback err |= vmaf_picture_unref(&dist_device); } #else err |= vmaf_picture_unref(ref); err |= vmaf_picture_unref(dist); #endif return err; } int vmaf_register_metadata_handler(VmafContext *vmaf, VmafMetadataConfiguration cfg) { if (!vmaf) return -EINVAL; return vmaf_feature_collector_register_metadata(vmaf->feature_collector, cfg); } int vmaf_feature_score_at_index(VmafContext *vmaf, const char *feature_name, double *score, unsigned index) { if (!vmaf) return -EINVAL; if (!feature_name) return -EINVAL; if (!score) return -EINVAL; return vmaf_feature_collector_get_score(vmaf->feature_collector, feature_name, score, index); } int vmaf_score_at_index(VmafContext *vmaf, VmafModel *model, double *score, unsigned index) { if (!vmaf) return -EINVAL; if (!model) return -EINVAL; if (!score) return -EINVAL; int err = vmaf_feature_collector_get_score(vmaf->feature_collector, model->name, score, index); if (err) { err = vmaf_predict_score_at_index(model, vmaf->feature_collector, index, score, true, false, 0); } return err; } int vmaf_score_at_index_model_collection(VmafContext *vmaf, VmafModelCollection *model_collection, VmafModelCollectionScore *score, unsigned index) { if (!vmaf) return -EINVAL; if (!model_collection) return -EINVAL; if (!score) return -EINVAL; return vmaf_predict_score_at_index_model_collection(model_collection, vmaf->feature_collector, index, score); } int vmaf_feature_score_pooled(VmafContext *vmaf, const char *feature_name, enum VmafPoolingMethod pool_method, double *score, unsigned index_low, unsigned index_high) { if (!vmaf) return -EINVAL; if (!feature_name) return -EINVAL; if (index_low > index_high) return -EINVAL; if (!pool_method) return -EINVAL; unsigned pic_cnt = 0; double min = 0., max = 0., sum = 0., i_sum = 0.; for (unsigned i = index_low; i <= index_high; i++) { if ((vmaf->cfg.n_subsample > 1) && (i % vmaf->cfg.n_subsample)) continue; pic_cnt++; double s; int err = vmaf_feature_score_at_index(vmaf, feature_name, &s, i); if (err) return err; sum += s; i_sum += 1. / (s + 1.); if ((i == index_low) || (s < min)) min = s; if ((i == index_low) || (s > max)) max = s; } switch (pool_method) { case VMAF_POOL_METHOD_MEAN: *score = sum / pic_cnt; break; case VMAF_POOL_METHOD_MIN: *score = min; break; case VMAF_POOL_METHOD_MAX: *score = max; break; case VMAF_POOL_METHOD_HARMONIC_MEAN: *score = pic_cnt / i_sum - 1.0; break; default: return -EINVAL; } return 0; } int vmaf_score_pooled(VmafContext *vmaf, VmafModel *model, enum VmafPoolingMethod pool_method, double *score, unsigned index_low, unsigned index_high) { if (!vmaf) return -EINVAL; if (!model) return -EINVAL; if (!score) return -EINVAL; if (index_low > index_high) return -EINVAL; if (!pool_method) return -EINVAL; for (unsigned i = index_low; i <= index_high; i++) { if ((vmaf->cfg.n_subsample > 1) && (i % vmaf->cfg.n_subsample)) continue; double vmaf_score; int err = vmaf_score_at_index(vmaf, model, &vmaf_score, i); if (err) return err; } return vmaf_feature_score_pooled(vmaf, model->name, pool_method, score, index_low, index_high); } int vmaf_score_pooled_model_collection(VmafContext *vmaf, VmafModelCollection *model_collection, enum VmafPoolingMethod pool_method, VmafModelCollectionScore *score, unsigned index_low, unsigned index_high) { if (!vmaf) return -EINVAL; if (!model_collection) return -EINVAL; if (!score) return -EINVAL; if (index_low > index_high) return -EINVAL; if (!pool_method) return -EINVAL; int err = 0; for (unsigned i = index_low; i <= index_high; i++) { if ((vmaf->cfg.n_subsample > 1) && (i % vmaf->cfg.n_subsample)) continue; VmafModelCollectionScore s; err = vmaf_score_at_index_model_collection(vmaf, model_collection, &s, i); if (err) return err; } score->type = VMAF_MODEL_COLLECTION_SCORE_BOOTSTRAP; //TODO: dedupe, vmaf_bootstrap_predict_score_at_index() const char *suffix_lo = "_ci_p95_lo"; const char *suffix_hi = "_ci_p95_hi"; const char *suffix_bagging = "_bagging"; const char *suffix_stddev = "_stddev"; const size_t name_sz = strlen(model_collection->name) + strlen(suffix_lo) + 1; char name[name_sz]; memset(name, 0, name_sz); snprintf(name, name_sz, "%s%s", model_collection->name, suffix_bagging); err |= vmaf_feature_score_pooled(vmaf, name, pool_method, &score->bootstrap.bagging_score, index_low, index_high); snprintf(name, name_sz, "%s%s", model_collection->name, suffix_stddev); err |= vmaf_feature_score_pooled(vmaf, name, pool_method, &score->bootstrap.stddev, index_low, index_high); snprintf(name, name_sz, "%s%s", model_collection->name, suffix_lo); err |= vmaf_feature_score_pooled(vmaf, name, pool_method, &score->bootstrap.ci.p95.lo, index_low, index_high); snprintf(name, name_sz, "%s%s", model_collection->name, suffix_hi); err |= vmaf_feature_score_pooled(vmaf, name, pool_method, &score->bootstrap.ci.p95.hi, index_low, index_high); return err; } const char *vmaf_version(void) { return VMAF_VERSION; } int vmaf_write_output(VmafContext *vmaf, const char *output_path, enum VmafOutputFormat fmt) { FILE *outfile = fopen(output_path, "w"); if (!outfile) { fprintf(stderr, "could not open file: %s\n", output_path); return -EINVAL; } const double fps = vmaf->pic_cnt / ((double) (vmaf->feature_collector->timer.end - vmaf->feature_collector->timer.begin) / CLOCKS_PER_SEC); int ret = 0; switch (fmt) { case VMAF_OUTPUT_FORMAT_XML: ret = vmaf_write_output_xml(vmaf, vmaf->feature_collector, outfile, vmaf->cfg.n_subsample, vmaf->pic_params.w, vmaf->pic_params.h, fps, vmaf->pic_cnt); break; case VMAF_OUTPUT_FORMAT_JSON: ret = vmaf_write_output_json(vmaf, vmaf->feature_collector, outfile, vmaf->cfg.n_subsample, fps, vmaf->pic_cnt); break; case VMAF_OUTPUT_FORMAT_CSV: ret = vmaf_write_output_csv(vmaf->feature_collector, outfile, vmaf->cfg.n_subsample); break; case VMAF_OUTPUT_FORMAT_SUB: ret = vmaf_write_output_sub(vmaf->feature_collector, outfile, vmaf->cfg.n_subsample); break; default: ret = -EINVAL; break; } fclose(outfile); return ret; }