From 86848eb227398e6bc54ce926ee7ca132a2f479c2 Mon Sep 17 00:00:00 2001 From: nilfm Date: Mon, 18 Dec 2023 18:56:23 +0000 Subject: [PATCH 01/24] Update aom_ctc.md file to list v4, v5, v6 --- resource/doc/aom_ctc.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/resource/doc/aom_ctc.md b/resource/doc/aom_ctc.md index eb43fe9ca..863ceeae8 100644 --- a/resource/doc/aom_ctc.md +++ b/resource/doc/aom_ctc.md @@ -55,3 +55,15 @@ There are also a few optional command-line settings you may find useful. * Add CAMBI * Release: [libvmaf v2.3.1](https://github.com/Netflix/vmaf/releases/tag/v2.3.1) * Precompiled static binaries [here](https://github.com/Netflix/vmaf/releases/tag/v2.3.1) + +* v4.0: `--aom_ctc v4.0` + * Identical to `v3.0` + +* v5.0: `--aom_ctc v5.0` + * Identical to `v4.0` + +* v6.0: `--aom_ctc v6.0` + * 2023-12-07 + * Support bit depth conversion for Y4M inputs + * Release: [libvmaf v3.0.0](https://github.com/Netflix/vmaf/releases/tag/v3.0.0) + * Precompiled static binaries [here](https://github.com/Netflix/vmaf/releases/tag/v3.0.0) \ No newline at end of file From 68a2367fc69c5b7efe1c5eaf939cc3db442e4669 Mon Sep 17 00:00:00 2001 From: Luc Trudeau Date: Mon, 18 Dec 2023 12:57:28 -0500 Subject: [PATCH 02/24] remove deprecation warning in svm.cpp removes the following warning ``` ../src/svm.cpp:57:2: warning: 'vsprintf' is deprecated: This function is provided for compatibility reasons only. Due to security concerns inherent in the design of sprintf(3), it is highly recommended that you use vsnprintf(3) instead. [-Wdeprecated-declarations] ``` --- libvmaf/src/svm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvmaf/src/svm.cpp b/libvmaf/src/svm.cpp index 24175c9a8..94d3379d8 100644 --- a/libvmaf/src/svm.cpp +++ b/libvmaf/src/svm.cpp @@ -54,7 +54,7 @@ static void info(const char *fmt,...) char buf[BUFSIZ]; va_list ap; va_start(ap,fmt); - vsprintf(buf,fmt,ap); + vsnprintf(buf,BUFSIZ,fmt,ap); va_end(ap); (*svm_print_string)(buf); } From cf67786bd63070bd06e76cc73caa4baca128ce1f Mon Sep 17 00:00:00 2001 From: nilfm Date: Tue, 19 Dec 2023 12:50:52 +0000 Subject: [PATCH 03/24] test_cli_parse: fix warning --- libvmaf/test/test_cli_parse.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libvmaf/test/test_cli_parse.c b/libvmaf/test/test_cli_parse.c index a651eb103..7ba87babe 100644 --- a/libvmaf/test/test_cli_parse.c +++ b/libvmaf/test/test_cli_parse.c @@ -23,8 +23,11 @@ #include "cli_parse.h" static int cli_free_dicts(CLISettings *settings) { - for (int i = 0; i < settings->feature_cnt; i++) - vmaf_feature_dictionary_free(&(settings->feature_cfg[i].opts_dict)); + for (unsigned i = 0; i < settings->feature_cnt; i++) { + int err = vmaf_feature_dictionary_free(&(settings->feature_cfg[i].opts_dict)); + if (err) return err; + } + return 0; } static char *test_aom_ctc_v1_0() From 0854388f857579e7936f0d63a5fedd1172abd777 Mon Sep 17 00:00:00 2001 From: nilfm Date: Tue, 19 Dec 2023 12:45:07 +0000 Subject: [PATCH 04/24] cambi: AVX-2 implementation of spatial mask --- libvmaf/src/feature/cambi.c | 62 +++++++++++++++++++--------- libvmaf/src/feature/x86/cambi_avx2.c | 37 +++++++++++++++++ libvmaf/src/feature/x86/cambi_avx2.h | 2 + libvmaf/test/test_cambi.c | 13 +++--- 4 files changed, 88 insertions(+), 26 deletions(-) diff --git a/libvmaf/src/feature/cambi.c b/libvmaf/src/feature/cambi.c index 4b4b2d6fa..094e24c32 100644 --- a/libvmaf/src/feature/cambi.c +++ b/libvmaf/src/feature/cambi.c @@ -83,11 +83,13 @@ typedef struct CambiBuffers { uint16_t *filter_mode_buffer; uint16_t *diffs_to_consider; uint16_t *tvi_for_diff; + uint16_t *derivative_buffer; int *diff_weights; int *all_diffs; } CambiBuffers; typedef void (*VmafRangeUpdater)(uint16_t *arr, int left, int right); +typedef void (*VmafDerivativeCalculator)(const uint16_t *image_data, uint16_t *derivative_buffer, int width, int height, int row, int stride); typedef struct CambiState { VmafPicture pics[PICS_BUFFER_SIZE]; @@ -107,6 +109,7 @@ typedef struct CambiState { FILE *heatmaps_files[NUM_SCALES]; VmafRangeUpdater inc_range_callback; VmafRangeUpdater dec_range_callback; + VmafDerivativeCalculator derivative_callback; CambiBuffers buffers; } CambiState; @@ -322,6 +325,14 @@ static void decrement_range(uint16_t *arr, int left, int right) { } } +static void get_derivative_data_for_row(const uint16_t *image_data, uint16_t *derivative_buffer, int width, int height, int row, int stride) { + for (int col = 0; col < width; col++) { + bool horizontal_derivative = (col == width - 1 || image_data[row * stride + col] == image_data[row * stride + col + 1]); + bool vertical_derivative = (row == height - 1 || image_data[row * stride + col] == image_data[(row + 1) * stride + col]); + derivative_buffer[col] = horizontal_derivative && vertical_derivative; + } +} + #ifdef _WIN32 #define PATH_SEPARATOR '\\' #else @@ -405,6 +416,8 @@ static int init(VmafFeatureExtractor *fex, enum VmafPixelFormat pix_fmt, if (!s->buffers.mask_dp) return -ENOMEM; s->buffers.filter_mode_buffer = aligned_malloc(ALIGN_CEIL(3 * alloc_w * sizeof(uint16_t)), 32); if (!s->buffers.filter_mode_buffer) return -ENOMEM; + s->buffers.derivative_buffer = aligned_malloc(ALIGN_CEIL(alloc_w * sizeof(uint16_t)), 32); + if (!s->buffers.derivative_buffer) return -ENOMEM; if (s->heatmaps_path) { int err = mkdirp(s->heatmaps_path, 0770); @@ -428,12 +441,14 @@ static int init(VmafFeatureExtractor *fex, enum VmafPixelFormat pix_fmt, s->inc_range_callback = increment_range; s->dec_range_callback = decrement_range; + s->derivative_callback = get_derivative_data_for_row; #if ARCH_X86 unsigned flags = vmaf_get_cpu_flags(); if (flags & VMAF_X86_CPU_FLAG_AVX2) { s->inc_range_callback = cambi_increment_range_avx2; s->dec_range_callback = cambi_decrement_range_avx2; + s->derivative_callback = get_derivative_data_for_row_avx2; } #endif @@ -638,8 +653,8 @@ static inline uint16_t mode3(uint16_t a, uint16_t b, uint16_t c) { static void filter_mode(const VmafPicture *image, int width, int height, uint16_t *buffer) { uint16_t *data = image->data[0]; ptrdiff_t stride = image->stride[0] >> 1; + int curr_line = 0; for (int i = 0; i < height; i++) { - int curr_line = i % 3; buffer[curr_line * width + 0] = data[i * stride + 0]; for (int j = 1; j < width - 1; j++) { buffer[curr_line * width + j] = mode3(data[i * stride + j - 1], data[i * stride + j], data[i * stride + j + 1]); @@ -651,6 +666,7 @@ static void filter_mode(const VmafPicture *image, int width, int height, uint16_ data[(i - 1) * stride + j] = mode3(buffer[0 * width + j], buffer[1 * width + j], buffer[2 * width + j]); } } + curr_line = (curr_line + 1 == 3 ? 0 : curr_line + 1); } } @@ -673,11 +689,6 @@ static FORCE_INLINE inline uint16_t get_mask_index(unsigned input_width, unsigne return (filter_size * filter_size + 3 * (ceil_log2(shifted_wh) - 11) - 1)>>1; } -static FORCE_INLINE inline bool get_derivative_data(const uint16_t *data, int width, int height, int i, int j, ptrdiff_t stride) { - return (i == height - 1 || (data[i * stride + j] == data[(i + 1) * stride + j])) && - (j == width - 1 || (data[i * stride + j] == data[i * stride + j + 1])); -} - /* * This function calculates the horizontal and vertical derivatives of the image using 2x1 and 1x2 kernels. * We say a pixel has zero_derivative=1 if it's equal to its right and bottom neighbours, and =0 otherwise (edges also count as "equal"). @@ -687,8 +698,8 @@ static FORCE_INLINE inline bool get_derivative_data(const uint16_t *data, int wi * To save memory, it uses a DP matrix of only the necessary size, rather than the full matrix, and indexes its rows cyclically. */ static void get_spatial_mask_for_index(const VmafPicture *image, VmafPicture *mask, - uint32_t *dp, uint16_t mask_index, uint16_t filter_size, - int width, int height) { + uint32_t *dp, uint16_t *derivative_buffer, uint16_t mask_index, + uint16_t filter_size, int width, int height, VmafDerivativeCalculator derivative_callback) { uint16_t pad_size = filter_size >> 1; uint16_t *image_data = image->data[0]; uint16_t *mask_data = mask->data[0]; @@ -700,8 +711,11 @@ static void get_spatial_mask_for_index(const VmafPicture *image, VmafPicture *ma // Initial computation: fill dp except for the last row for (int i = 0; i < pad_size; i++) { + if (i < height) { + derivative_callback(image_data, derivative_buffer, width, height, i, stride); + } for (int j = 0; j < width + pad_size; j++) { - int value = (i < height && j < width ? get_derivative_data(image_data, width, height, i, j, stride) : 0); + int value = (i < height && j < width ? derivative_buffer[j] : 0); int curr_row = i + pad_size + 1; int curr_col = j + pad_size + 1; dp[curr_row * dp_width + curr_col] = @@ -713,27 +727,31 @@ static void get_spatial_mask_for_index(const VmafPicture *image, VmafPicture *ma } // Start from the last row in the dp matrix + int prev_row = dp_height - 2; int curr_row = dp_height - 1; int curr_compute = pad_size + 1; + int bottom = (curr_compute + pad_size) % dp_height; + int top = (curr_compute + dp_height - pad_size - 1) % dp_height; for (int i = pad_size; i < height + pad_size; i++) { + if (i < height) { + derivative_callback(image_data, derivative_buffer, width, height, i, stride); + } // First compute the values of dp for curr_row for (int j = 0; j < width + pad_size; j++) { - int value = (i < height && j < width ? get_derivative_data(image_data, width, height, i, j, stride) : 0); + int value = (i < height && j < width ? derivative_buffer[j] : 0); int curr_col = j + pad_size + 1; - int prev_row = (curr_row + dp_height - 1) % dp_height; dp[curr_row * dp_width + curr_col] = value + dp[prev_row * dp_width + curr_col] + dp[curr_row * dp_width + curr_col - 1] - dp[prev_row * dp_width + curr_col - 1]; } - curr_row = (curr_row + 1) % dp_height; + prev_row = curr_row; + curr_row = (curr_row + 1 == dp_height ? 0 : curr_row + 1); // Then use the values to compute the square sum for the curr_compute row. for (int j = 0; j < width; j++) { int curr_col = j + pad_size + 1; - int bottom = (curr_compute + pad_size) % dp_height; - int top = (curr_compute + dp_height - pad_size - 1) % dp_height; int right = curr_col + pad_size; int left = curr_col - pad_size - 1; int result = @@ -743,14 +761,17 @@ static void get_spatial_mask_for_index(const VmafPicture *image, VmafPicture *ma + dp[top * dp_width + left]; mask_data[(i - pad_size) * stride + j] = (result > mask_index); } - curr_compute = (curr_compute + 1) % dp_height; + curr_compute = (curr_compute + 1 == dp_height ? 0 : curr_compute + 1); + bottom = (bottom + 1 == dp_height ? 0 : bottom + 1); + top = (top + 1 == dp_height ? 0 : top + 1); } } static void get_spatial_mask(const VmafPicture *image, VmafPicture *mask, - uint32_t *dp, unsigned width, unsigned height) { + uint32_t *dp, uint16_t *derivative_buffer, unsigned width, unsigned height, + VmafDerivativeCalculator derivative_callback) { uint16_t mask_index = get_mask_index(width, height, MASK_FILTER_SIZE); - get_spatial_mask_for_index(image, mask, dp, mask_index, MASK_FILTER_SIZE, width, height); + get_spatial_mask_for_index(image, mask, dp, derivative_buffer, mask_index, MASK_FILTER_SIZE, width, height, derivative_callback); } static float c_value_pixel(const uint16_t *histograms, uint16_t value, const int *diff_weights, @@ -1017,7 +1038,7 @@ static int dump_c_values(FILE *heatmaps_files[], const float *c_values, int widt static int cambi_score(VmafPicture *pics, uint16_t window_size, double topk, const uint16_t num_diffs, const uint16_t *tvi_for_diff, CambiBuffers buffers, VmafRangeUpdater inc_range_callback, VmafRangeUpdater dec_range_callback, - double *score, bool write_heatmaps, FILE *heatmaps_files[], + VmafDerivativeCalculator derivative_callback, double *score, bool write_heatmaps, FILE *heatmaps_files[], int width, int height, int frame) { double scores_per_scale[NUM_SCALES]; VmafPicture *image = &pics[0]; @@ -1026,7 +1047,7 @@ static int cambi_score(VmafPicture *pics, uint16_t window_size, double topk, int scaled_width = width; int scaled_height = height; - get_spatial_mask(image, mask, buffers.mask_dp, width, height); + get_spatial_mask(image, mask, buffers.mask_dp, buffers.derivative_buffer, width, height, derivative_callback); for (unsigned scale = 0; scale < NUM_SCALES; scale++) { if (scale > 0) { scaled_width = (scaled_width + 1) >> 1; @@ -1067,7 +1088,7 @@ static int preprocess_and_extract_cambi(CambiState *s, VmafPicture *pic, double bool write_heatmaps = s->heatmaps_path && !is_src; err = cambi_score(s->pics, window_size, s->topk, num_diffs, s->buffers.tvi_for_diff, - s->buffers, s->inc_range_callback, s->dec_range_callback, score, write_heatmaps, s->heatmaps_files, width, height, frame); + s->buffers, s->inc_range_callback, s->dec_range_callback, s->derivative_callback, score, write_heatmaps, s->heatmaps_files, width, height, frame); if (err) return err; return 0; @@ -1123,6 +1144,7 @@ static int close_cambi(VmafFeatureExtractor *fex) { aligned_free(s->buffers.diffs_to_consider); aligned_free(s->buffers.diff_weights); aligned_free(s->buffers.all_diffs); + aligned_free(s->buffers.derivative_buffer); if (s->heatmaps_path) { for (int scale = 0; scale < NUM_SCALES; scale++) { diff --git a/libvmaf/src/feature/x86/cambi_avx2.c b/libvmaf/src/feature/x86/cambi_avx2.c index 41cffbf6e..ba3deec6e 100644 --- a/libvmaf/src/feature/x86/cambi_avx2.c +++ b/libvmaf/src/feature/x86/cambi_avx2.c @@ -18,6 +18,7 @@ #include #include +#include void cambi_increment_range_avx2(uint16_t *arr, int left, int right) { __m256i val_vector = _mm256_set1_epi16(1); @@ -44,3 +45,39 @@ void cambi_decrement_range_avx2(uint16_t *arr, int left, int right) { arr[col]--; } } + +void get_derivative_data_for_row_avx2(const uint16_t *image_data, uint16_t *derivative_buffer, int width, int height, int row, int stride) { + // For the last row, we only compute horizontal derivatives + if (row == height - 1) { + __m256i ones = _mm256_set1_epi16(1); + int col = 0; + for (; col + 15 < width - 1; col += 16) { + __m256i vals1 = _mm256_loadu_si256((__m256i*) &image_data[row * stride + col]); + __m256i vals2 = _mm256_loadu_si256((__m256i*) &image_data[row * stride + col + 1]); + __m256i result = _mm256_cmpeq_epi16(vals1, vals2); + _mm256_storeu_si256((__m256i*) &derivative_buffer[col], _mm256_and_si256(ones, result)); + } + for (; col < width - 1; col++) { + derivative_buffer[col] = (image_data[row * stride + col] == image_data[row * stride + col + 1]); + } + derivative_buffer[width - 1] = 1; + } + else { + __m256i ones = _mm256_set1_epi16(1); + int col = 0; + for (; col + 15 < width - 1; col += 16) { + __m256i horiz_vals1 = _mm256_loadu_si256((__m256i*) &image_data[row * stride + col]); + __m256i horiz_vals2 = _mm256_loadu_si256((__m256i*) &image_data[row * stride + col + 1]); + __m256i horiz_result = _mm256_and_si256(ones, _mm256_cmpeq_epi16(horiz_vals1, horiz_vals2)); + __m256i vert_vals1 = _mm256_loadu_si256((__m256i*) &image_data[row * stride + col]); + __m256i vert_vals2 = _mm256_loadu_si256((__m256i*) &image_data[(row + 1) * stride + col]); + __m256i vert_result = _mm256_and_si256(ones, _mm256_cmpeq_epi16(vert_vals1, vert_vals2)); + _mm256_storeu_si256((__m256i*) &derivative_buffer[col], _mm256_and_si256(horiz_result, vert_result)); + } + for (; col < width; col++) { + bool horizontal_derivative = (col == width - 1 || image_data[row * stride + col] == image_data[row * stride + col + 1]); + bool vertical_derivative = image_data[row * stride + col] == image_data[(row + 1) * stride + col]; + derivative_buffer[col] = horizontal_derivative && vertical_derivative; + } + } +} \ No newline at end of file diff --git a/libvmaf/src/feature/x86/cambi_avx2.h b/libvmaf/src/feature/x86/cambi_avx2.h index e1bdbbdc4..9388ca577 100644 --- a/libvmaf/src/feature/x86/cambi_avx2.h +++ b/libvmaf/src/feature/x86/cambi_avx2.h @@ -26,4 +26,6 @@ void cambi_increment_range_avx2(uint16_t *arr, int left, int right); void cambi_decrement_range_avx2(uint16_t *arr, int left, int right); +void get_derivative_data_for_row_avx2(const uint16_t *image_data, uint16_t *derivative_buffer, int width, int height, int row, int stride); + #endif /* X86_AVX2_CAMBI_H_ */ diff --git a/libvmaf/test/test_cambi.c b/libvmaf/test/test_cambi.c index def3caf5a..5318b281c 100644 --- a/libvmaf/test/test_cambi.c +++ b/libvmaf/test/test_cambi.c @@ -339,6 +339,7 @@ static char *test_get_spatial_mask_for_index() // dp_height = 2 * (filter_size >> 2) + 2 uint32_t mask_dp[7*4]; int err = 0; + uint16_t derivative_buffer[4]; err |= get_sample_image(&image, 3); mu_assert("test_get_spatial_mask_for_index alloc #1 error", !err); @@ -346,11 +347,11 @@ static char *test_get_spatial_mask_for_index() err |= get_sample_image(&mask, 3); mu_assert("test_get_spatial_mask_for_index alloc #2 error", !err); - get_spatial_mask_for_index(&image, &mask, mask_dp, 2, filter_size, width, height); + get_spatial_mask_for_index(&image, &mask, mask_dp, derivative_buffer, 2, filter_size, width, height, get_derivative_data_for_row); mu_assert("spatial_mask_for_index wrong mask for index=2, image=3", data_pic_sum(&mask)==14); - get_spatial_mask_for_index(&image, &mask, mask_dp, 1, filter_size, width, height); + get_spatial_mask_for_index(&image, &mask, mask_dp, derivative_buffer, 1, filter_size, width, height, get_derivative_data_for_row); mu_assert("spatial_mask_for_index wrong mask for index=1, image=3", data_pic_sum(&mask)==16); - get_spatial_mask_for_index(&image, &mask, mask_dp, 0, filter_size, width, height); + get_spatial_mask_for_index(&image, &mask, mask_dp, derivative_buffer, 0, filter_size, width, height, get_derivative_data_for_row); mu_assert("spatial_mask_for_index wrong mask for index=0, image=3", data_pic_sum(&mask)==16); vmaf_picture_unref(&image); @@ -358,11 +359,11 @@ static char *test_get_spatial_mask_for_index() err |= get_sample_image(&image, 4); mu_assert("test_get_spatial_mask_for_index alloc #3 error", !err); - get_spatial_mask_for_index(&image, &mask, mask_dp, 3, filter_size, width, height); + get_spatial_mask_for_index(&image, &mask, mask_dp, derivative_buffer, 3, filter_size, width, height, get_derivative_data_for_row); mu_assert("spatial_mask_for_index wrong mask for index=3, image=4", data_pic_sum(&mask)==0); - get_spatial_mask_for_index(&image, &mask, mask_dp, 2, filter_size, width, height); + get_spatial_mask_for_index(&image, &mask, mask_dp, derivative_buffer, 2, filter_size, width, height, get_derivative_data_for_row); mu_assert("spatial_mask_for_index wrong mask for index=2, image=4", data_pic_sum(&mask)==6); - get_spatial_mask_for_index(&image, &mask, mask_dp, 1, filter_size, width, height); + get_spatial_mask_for_index(&image, &mask, mask_dp, derivative_buffer, 1, filter_size, width, height, get_derivative_data_for_row); mu_assert("spatial_mask_for_index wrong mask for index=1, image=4", data_pic_sum(&mask)==9); vmaf_picture_unref(&image); From 92981af2e4a601a789ea9b3410893dcf6f495962 Mon Sep 17 00:00:00 2001 From: nilfm Date: Fri, 25 Aug 2023 12:21:07 +0200 Subject: [PATCH 05/24] [VCQ-164] Support parsing string type features from models --- libvmaf/src/read_json_model.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libvmaf/src/read_json_model.c b/libvmaf/src/read_json_model.c index 4022b8e76..7598a82b6 100644 --- a/libvmaf/src/read_json_model.c +++ b/libvmaf/src/read_json_model.c @@ -62,6 +62,13 @@ static int parse_feature_opts_dicts(json_stream *s, VmafModel *model) } free(key); if (err) return err; + } else if (json_peek(s) == JSON_STRING) { + const char *val = json_get_string(s, NULL); + const uint64_t flags = VMAF_DICT_DO_NOT_OVERWRITE; + int err = vmaf_dictionary_set(&(model->feature[i].opts_dict), + key, val, flags); + free(key); + if (err) return err; } else { return -EINVAL; //TODO } From 32c27d7babd7cc552dfa0729b71d06be74925ed0 Mon Sep 17 00:00:00 2001 From: nilfm Date: Wed, 10 Jan 2024 15:02:39 +0000 Subject: [PATCH 06/24] =?UTF-8?q?picture=5Fcopy:=20fix=20warning=20'ISO=20?= =?UTF-8?q?C=20forbids=20=E2=80=98return=E2=80=99=20with=20expression,=20i?= =?UTF-8?q?n=20function=20returning=20void'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libvmaf/src/feature/picture_copy.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libvmaf/src/feature/picture_copy.c b/libvmaf/src/feature/picture_copy.c index 989df124f..cedf466ed 100644 --- a/libvmaf/src/feature/picture_copy.c +++ b/libvmaf/src/feature/picture_copy.c @@ -39,12 +39,16 @@ void picture_copy_hbd(float *dst, ptrdiff_t dst_stride, void picture_copy(float *dst, ptrdiff_t dst_stride, VmafPicture *src, int offset, unsigned bpc) { - if (bpc == 10) - return picture_copy_hbd(dst, dst_stride, src, offset, 4.0f); - else if (bpc == 12) - return picture_copy_hbd(dst, dst_stride, src, offset, 16.0f); - else if (bpc == 16) - return picture_copy_hbd(dst, dst_stride, src, offset, 256.0f); + if (bpc == 10) { + picture_copy_hbd(dst, dst_stride, src, offset, 4.0f); + return; + } else if (bpc == 12) { + picture_copy_hbd(dst, dst_stride, src, offset, 16.0f); + return; + } else if (bpc == 16) { + picture_copy_hbd(dst, dst_stride, src, offset, 256.0f); + return; + } float *float_data = dst; uint8_t *data = src->data[0]; From d562a89cb16799f538ec1d10af9654182bcdc5e8 Mon Sep 17 00:00:00 2001 From: Cosmin Stejerean Date: Thu, 18 Jan 2024 15:35:05 -0800 Subject: [PATCH 07/24] materialize map object before calling np.vstack --- python/vmaf/core/niqe_train_test_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vmaf/core/niqe_train_test_model.py b/python/vmaf/core/niqe_train_test_model.py index 772949e96..05a0a0587 100644 --- a/python/vmaf/core/niqe_train_test_model.py +++ b/python/vmaf/core/niqe_train_test_model.py @@ -64,9 +64,9 @@ def train(self, xys): xs_2d = [] for i_sample in range(num_samples): - xs_2d_ = np.vstack(map( + xs_2d_ = np.vstack(list(map( lambda feature_name: xys[feature_name][i_sample], feature_names) - ).T + )).T xs_2d.append(xs_2d_) xs_2d = np.vstack(xs_2d) From 3fe34f61e6aa9eaa850d7a1fe7c93b71037a3d16 Mon Sep 17 00:00:00 2001 From: Cosmin Stejerean Date: Thu, 18 Jan 2024 16:11:45 -0800 Subject: [PATCH 08/24] update pip install instructions for macOS to use homebrew llvm --- resource/doc/python.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/resource/doc/python.md b/resource/doc/python.md index 683b05d57..e0a7c6b48 100644 --- a/resource/doc/python.md +++ b/resource/doc/python.md @@ -41,7 +41,7 @@ brew install python3 Install the remaining dependencies: ```bash -brew install nasm doxygen +brew install nasm doxygen llvm ``` Note that `brew` requires no `sudo`. @@ -76,6 +76,13 @@ Install the rest of the required Python packages: pip3 install -r python/requirements.txt ``` +On macOS it's important to use the LLVM from homebrew as the macOS clang does not include support for OpenMP, which is needed for libsvm-official + +``` shell script +LLVM_CONFIG=$HOMEBREW_PREFIX/opt/llvm/bin/llvm-config pip install -r ./python/requirements.txt +``` + + ## Testing Run unittests and make sure they all pass: From fe5273802e070887d3c3906a86d0c019b9d73d09 Mon Sep 17 00:00:00 2001 From: Cosmin Stejerean Date: Thu, 18 Jan 2024 14:44:04 -0800 Subject: [PATCH 09/24] unpin PyWavelets since 1.1.1 will not build with modern Python (3.11+) --- python/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/requirements.txt b/python/requirements.txt index 05f020292..1e453240c 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -8,6 +8,6 @@ h5py>=2.6.0 sureal>=0.4.2 dill>=0.3.1 cython -PyWavelets==1.1.1 +PyWavelets>=1.1.1 python-slugify>=5.0.0 libsvm-official>=3.30 From b5eceb3f8eab68052ea18c0e2e9647de8f555f8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:02:42 +0000 Subject: [PATCH 10/24] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 391b4aa4f..6525f7f6d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -30,7 +30,7 @@ jobs: with: fetch-depth: 0 - name: Cache ccache files - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | .ccache From b641938c65649466e5b180f572d2ba2e0ddcc52c Mon Sep 17 00:00:00 2001 From: Nil Fons Miret Date: Wed, 10 Jan 2024 15:56:06 +0000 Subject: [PATCH 11/24] model.c: change strncpy to strcpy, fix warning -Wstringop-truncation --- libvmaf/src/model.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libvmaf/src/model.c b/libvmaf/src/model.c index 4661ec148..9cf8a670a 100644 --- a/libvmaf/src/model.c +++ b/libvmaf/src/model.c @@ -131,9 +131,9 @@ char *vmaf_model_generate_name(VmafModelConfig *cfg) memset(name, 0, name_sz); if (!cfg->name) - strncpy(name, default_name, name_sz); + strcpy(name, default_name); else - strncpy(name, cfg->name, name_sz); + strcpy(name, cfg->name); return name; } From 9a7c9d3be4e54c2172fdb3ca83b11ec7f25f3379 Mon Sep 17 00:00:00 2001 From: Cosmin Stejerean Date: Thu, 18 Jan 2024 17:31:40 -0800 Subject: [PATCH 12/24] allow for floating point differences on arm64 --- python/test/feature_extractor_test.py | 2 +- python/test/quality_runner_test.py | 12 ++++++------ python/test/routine_test.py | 6 +++--- python/test/vmafexec_feature_extractor_test.py | 8 ++++---- python/test/vmafexec_test.py | 12 ++++++------ 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/python/test/feature_extractor_test.py b/python/test/feature_extractor_test.py index 62b871667..fbdab5c26 100644 --- a/python/test/feature_extractor_test.py +++ b/python/test/feature_extractor_test.py @@ -81,7 +81,7 @@ def test_run_vmaf_fextractor(self): self.assertAlmostEqual(results[0]['VMAF_feature_vif_num_score'], 712650.023478, places=0) self.assertAlmostEqual(results[0]['VMAF_feature_vif_den_score'], 1597314.95249, places=0) - self.assertAlmostEqual(results[0]['VMAF_feature_adm_num_score'], 371.80645372916666, places=4) + self.assertAlmostEqual(results[0]['VMAF_feature_adm_num_score'], 371.80645372916666, places=3) self.assertAlmostEqual(results[0]['VMAF_feature_adm_den_score'], 397.83378972916671, places=4) self.assertAlmostEqual(results[0]['VMAF_feature_anpsnr_score'], 34.164776875, places=4) diff --git a/python/test/quality_runner_test.py b/python/test/quality_runner_test.py index 7771297d9..9c224f3dd 100644 --- a/python/test/quality_runner_test.py +++ b/python/test/quality_runner_test.py @@ -186,7 +186,7 @@ def test_run_vmaf_runner_v1_model(self): self.assertAlmostEqual(results[1]['VMAF_feature_adm_score'], 1.0, places=4) self.assertAlmostEqual(results[1]['VMAF_feature_ansnr_score'], 31.2714392708, places=4) - self.assertAlmostEqual(results[0]['VMAF_score'], 77.17414738991636, places=4) + self.assertAlmostEqual(results[0]['VMAF_score'], 77.17414738991636, places=3) self.assertAlmostEqual(results[1]['VMAF_score'], 100.0, places=4) def test_run_vmaf_runner(self): @@ -1045,7 +1045,7 @@ def test_run_bootstrap_vmaf_runner_residue_bootstrap_model(self): self.assertAlmostEqual(results[0]['BOOTSTRAP_VMAF_bagging_score'], 73.09131553704874, places=4) self.assertAlmostEqual(results[1]['BOOTSTRAP_VMAF_bagging_score'], 99.79000465995409, places=4) self.assertAlmostEqual(results[0]['BOOTSTRAP_VMAF_stddev_score'], 1.1982762081883995, places=4) - self.assertAlmostEqual(results[1]['BOOTSTRAP_VMAF_stddev_score'], 1.3028824838324222, places=4) + self.assertAlmostEqual(results[1]['BOOTSTRAP_VMAF_stddev_score'], 1.3028824838324222, places=3) self.assertAlmostEqual(results[0]['BOOTSTRAP_VMAF_ci95_low_score'], 70.81472328674501, places=4) self.assertAlmostEqual(results[1]['BOOTSTRAP_VMAF_ci95_low_score'], 94.79667446930989, places=4) self.assertAlmostEqual(results[0]['BOOTSTRAP_VMAF_ci95_high_score'], 74.83768715705374, places=4) @@ -1311,7 +1311,7 @@ def test_run_vmaf_runner_input160x90(self): with self.assertRaises(KeyError): self.assertAlmostEqual(results[1]['VMAF_integer_feature_motion_score'], 1.0, places=4) - self.assertAlmostEqual(results[0]['VMAF_score'], 92.52344867729687, places=3) + self.assertAlmostEqual(results[0]['VMAF_score'], 92.52344867729687, places=2) self.assertAlmostEqual(results[1]['VMAF_score'], 99.30930978456455, places=4) def test_run_vmaf_runner_json_model(self): @@ -1444,7 +1444,7 @@ def test_run_vmaf_runner_neg_mode(self): self.runner.run(parallelize=False) results = self.runner.results - self.assertAlmostEqual(results[0]['VMAFNEG_score'], 88.030463, places=4) # 132.7329528948058 + self.assertAlmostEqual(results[0]['VMAFNEG_score'], 88.030463, places=3) # 132.7329528948058 self.assertAlmostEqual(results[0]['VMAF_integer_feature_vif_scale0_score'], 0.9837379749630343, places=4) with self.assertRaises(KeyError): @@ -1536,7 +1536,7 @@ def test_run_vmaf_runner_float_nvd6(self): self.assertAlmostEqual(results[1]['VMAF_feature_motion2_score'], 3.8953518541666665, places=4) self.assertAlmostEqual(results[1]['VMAF_feature_adm2_score'], 1.0, places=4) - self.assertAlmostEqual(results[0]['VMAF_score'], 80.61670115719328, places=4) + self.assertAlmostEqual(results[0]['VMAF_score'], 80.61670115719328, places=3) self.assertAlmostEqual(results[1]['VMAF_score'], 99.946416604585025, places=4) def test_run_vmaf_runner_rdh540(self): @@ -1969,7 +1969,7 @@ def test_quality_runner_with_different_models(self): self.assertAlmostEqual(results1[0]['VMAF_score'], 73.28968543912883, places=4) self.assertAlmostEqual(results1[1]['VMAF_score'], 99.946416604585025, places=4) - self.assertAlmostEqual(results2[0]['VMAF_score'], 80.61670115719328, places=4) + self.assertAlmostEqual(results2[0]['VMAF_score'], 80.61670115719328, places=3) self.assertAlmostEqual(results2[1]['VMAF_score'], 99.946416604585025, places=4) diff --git a/python/test/routine_test.py b/python/test/routine_test.py index 575527096..80bfa3b33 100644 --- a/python/test/routine_test.py +++ b/python/test/routine_test.py @@ -360,7 +360,7 @@ def test_test_on_dataset(self): self.assertAlmostEqual(results[0]['VMAF_score'], 99.142659046424384, places=4) self.assertAlmostEqual(results[1]['VMAF_score'], 35.066157497128764, places=4) self.assertAlmostEqual(results[2]['VMAF_score'], 97.428042675471147, places=4) - self.assertAlmostEqual(results[3]['VMAF_score'], 97.427927701008869, places=4) + self.assertAlmostEqual(results[3]['VMAF_score'], 97.427927701008869, places=3) self.assertAlmostEqual(test_assets[0].groundtruth, 100, places=4) self.assertAlmostEqual(test_assets[1].groundtruth, 50, places=4) self.assertAlmostEqual(test_assets[2].groundtruth, 100, places=4) @@ -466,7 +466,7 @@ def test_test_on_dataset_raw(self): self.assertAlmostEqual(results[0]['VMAF_score'], 99.142659046424384, places=4) self.assertAlmostEqual(results[1]['VMAF_score'], 35.066157497128764, places=4) self.assertAlmostEqual(results[2]['VMAF_score'], 97.428042675471147, places=4) - self.assertAlmostEqual(results[3]['VMAF_score'], 97.427927701008869, places=4) + self.assertAlmostEqual(results[3]['VMAF_score'], 97.427927701008869, places=3) self.assertAlmostEqual(test_assets[0].groundtruth, 100, places=4) self.assertAlmostEqual(test_assets[1].groundtruth, 50, places=4) self.assertAlmostEqual(test_assets[2].groundtruth, 100, places=4) @@ -489,7 +489,7 @@ def test_test_on_dataset_mle(self): self.assertAlmostEqual(results[0]['VMAF_score'], 99.142659046424384, places=4) self.assertAlmostEqual(results[1]['VMAF_score'], 35.066157497128764, places=4) self.assertAlmostEqual(results[2]['VMAF_score'], 97.428042675471147, places=4) - self.assertAlmostEqual(results[3]['VMAF_score'], 97.427927701008869, places=4) + self.assertAlmostEqual(results[3]['VMAF_score'], 97.427927701008869, places=3) self.assertAlmostEqual(test_assets[0].groundtruth, 100, places=4) self.assertAlmostEqual(test_assets[1].groundtruth, 50, places=4) self.assertAlmostEqual(test_assets[2].groundtruth, 90, places=4) diff --git a/python/test/vmafexec_feature_extractor_test.py b/python/test/vmafexec_feature_extractor_test.py index 22c1a9af1..828980034 100644 --- a/python/test/vmafexec_feature_extractor_test.py +++ b/python/test/vmafexec_feature_extractor_test.py @@ -162,7 +162,7 @@ def test_run_float_vif_fextractor_with_debug(self): self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale0_score'], 0.3634208125, places=6) self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale1_score'], 0.7666474166666667, places=6) self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale2_score'], 0.8628533333333334, places=5) - self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale3_score'], 0.9159719583333334, places=6) + self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale3_score'], 0.9159719583333334, places=5) self.assertAlmostEqual(results[1]['float_VIF_feature_vif_scale0_score'], 1.0, places=5) self.assertAlmostEqual(results[1]['float_VIF_feature_vif_scale1_score'], 1.0, places=5) self.assertAlmostEqual(results[1]['float_VIF_feature_vif_scale2_score'], 1.0, places=5) @@ -349,13 +349,13 @@ def test_run_float_adm_fextractor_with_debug(self): self.assertAlmostEqual(results[0]['float_ADM_feature_adm_scale3_score'], 0.9649663541666667, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_score'], 0.9345148541666667, places=4) - self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_score'], 371.80645372916666, places=4) + self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_score'], 371.80645372916666, places=3) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_den_score'], 397.83379106250004, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_scale0_score'], 45.526146958333335, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_den_scale0_score'], 50.14385129166667, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_scale1_score'], 66.574236, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_den_scale1_score'], 74.47438383333333, places=4) - self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_scale2_score'], 105.55483329166668, places=4) + self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_scale2_score'], 105.55483329166668, places=3) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_den_scale2_score'], 113.49725864583333, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_num_scale3_score'], 154.15123754166666, places=4) self.assertAlmostEqual(results[0]['float_ADM_feature_adm_den_scale3_score'], 159.7182974375, places=4) @@ -476,7 +476,7 @@ def test_run_float_vif_fextractor_akiyo_multiply(self): ) self.fextractor.run(parallelize=True) results = self.fextractor.results - self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale0_score'], 1.0522544319369052, places=5) + self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale0_score'], 1.0522544319369052, places=4) self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale1_score'], 1.0705609423182443, places=5) self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale2_score'], 1.0731529493098957, places=4) self.assertAlmostEqual(results[0]['float_VIF_feature_vif_scale3_score'], 1.0728060231246508, places=4) diff --git a/python/test/vmafexec_test.py b/python/test/vmafexec_test.py index fb0c5cf75..7244a15bd 100644 --- a/python/test/vmafexec_test.py +++ b/python/test/vmafexec_test.py @@ -639,7 +639,7 @@ def test_run_vmafexec_runner_akiyo_multiply(self): self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale2_score'], 1.072518, places=4) # 1.0731529493098957 self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale3_score'], 1.072512, places=4) # 1.0728060231246508 - self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 132.732952, places=3) # 132.78849246495625 + self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 132.732952, places=2) # 132.78849246495625 def test_run_vmafexec_runner_akiyo_multiply_with_feature_enhn_gain_limit(self): ref_path = VmafConfig.test_resource_path("yuv", "refp_vmaf_hacking_investigation_0_0_akiyo_cif_notyuv_0to0_identity_vs_akiyo_cif_notyuv_0to0_multiply_q_352x288") @@ -667,7 +667,7 @@ def test_run_vmafexec_runner_akiyo_multiply_with_feature_enhn_gain_limit(self): self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale2_egl_1_score'], 0.9984692380091739, places=4) # 1.0731529493098957 self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale3_egl_1_score'], 0.999146211879154, places=4) # 1.0728060231246508 - self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 88.030463, places=4) # 132.78849246495625 + self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 88.030463, places=3) # 132.78849246495625 def test_run_vmafexec_runner_akiyo_multiply_with_feature_enhn_gain_limit_custom(self): ref_path = VmafConfig.test_resource_path("yuv", "refp_vmaf_hacking_investigation_0_0_akiyo_cif_notyuv_0to0_identity_vs_akiyo_cif_notyuv_0to0_multiply_q_352x288") @@ -695,7 +695,7 @@ def test_run_vmafexec_runner_akiyo_multiply_with_feature_enhn_gain_limit_custom( self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale2_egl_1.1_score'], 1.04852, places=4) # 1.0731529493098957 self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale3_egl_1.1_score'], 1.04892, places=4) # 1.0728060231246508 - self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 129.474226, places=3) # 132.78849246495625 + self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 129.474226, places=2) # 132.78849246495625 def test_run_vmafexec_runner_akiyo_multiply_disable_enhn_gain(self): ref_path = VmafConfig.test_resource_path("yuv", "refp_vmaf_hacking_investigation_0_0_akiyo_cif_notyuv_0to0_identity_vs_akiyo_cif_notyuv_0to0_multiply_q_352x288") @@ -723,7 +723,7 @@ def test_run_vmafexec_runner_akiyo_multiply_disable_enhn_gain(self): self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale2_egl_1_score'], 0.9984692380091739, places=4) # 1.0731529493098957 self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale3_egl_1_score'], 0.999146211879154, places=4) # 1.0728060231246508 - self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 88.030463, places=4) # 132.78849246495625 + self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 88.030463, places=3) # 132.78849246495625 def test_run_vmafexec_runner_akiyo_multiply_no_enhn_gain_model(self): ref_path = VmafConfig.test_resource_path("yuv", "refp_vmaf_hacking_investigation_0_0_akiyo_cif_notyuv_0to0_identity_vs_akiyo_cif_notyuv_0to0_multiply_q_352x288") @@ -751,7 +751,7 @@ def test_run_vmafexec_runner_akiyo_multiply_no_enhn_gain_model(self): self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale2_egl_1_score'], 0.9984692380091739, places=4) # 1.0731529493098957 self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale3_egl_1_score'], 0.999146211879154, places=4) # 1.0728060231246508 - self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 88.030463, places=4) # 132.78849246495625 + self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 88.030463, places=3) # 132.78849246495625 def test_run_vmafexec_runner_akiyo_multiply_no_enhn_gain_model_and_cmd_options(self): ref_path = VmafConfig.test_resource_path("yuv", "refp_vmaf_hacking_investigation_0_0_akiyo_cif_notyuv_0to0_identity_vs_akiyo_cif_notyuv_0to0_multiply_q_352x288") @@ -780,7 +780,7 @@ def test_run_vmafexec_runner_akiyo_multiply_no_enhn_gain_model_and_cmd_options(s self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale2_egl_1_score'], 0.9984692380091739, places=4) # 1.0731529493098957 self.assertAlmostEqual(results[0]['VMAFEXEC_vif_scale3_egl_1_score'], 0.999146211879154, places=4) # 1.0728060231246508 - self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 122.804272, places=3) # 132.78849246495625 + self.assertAlmostEqual(results[0]['VMAFEXEC_score'], 122.804272, places=2) # 132.78849246495625 def test_run_vmafexec_runner_akiyo_multiply_no_enhn_gain_model_and_cmd_options_illegal(self): ref_path = VmafConfig.test_resource_path("yuv", "refp_vmaf_hacking_investigation_0_0_akiyo_cif_notyuv_0to0_identity_vs_akiyo_cif_notyuv_0to0_multiply_q_352x288") From 702619d3f754b4c1ba793bbbc833af2565055d29 Mon Sep 17 00:00:00 2001 From: Cosmin Stejerean Date: Tue, 23 Jan 2024 18:14:42 -0800 Subject: [PATCH 13/24] update AdmDwt2CyTest to use actual expected values rather than particular floating point rounding errors --- python/test/cy_test.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/python/test/cy_test.py b/python/test/cy_test.py index 983f95e8a..63a0c2435 100644 --- a/python/test/cy_test.py +++ b/python/test/cy_test.py @@ -87,15 +87,15 @@ def test_adm_dwt2_cy_xsmallP_dc(self): self.assertEqual(h.shape, (9, 11)) self.assertEqual(d.shape, (9, 11)) - self.assertAlmostEqual(float(np.max(a)), 109.99999999999997, places=16) - self.assertAlmostEqual(float(np.max(v)), 8.526512829121202e-14, places=16) - self.assertAlmostEqual(float(np.max(h)), 8.038873388460928e-14, places=16) - self.assertAlmostEqual(float(np.max(d)), 0.0, places=16) + self.assertAlmostEqual(float(np.max(a)), 110.0, places=12) + self.assertAlmostEqual(float(np.max(v)), 0.0, places=12) + self.assertAlmostEqual(float(np.max(h)), 0.0, places=12) + self.assertAlmostEqual(float(np.max(d)), 0.0, places=12) - self.assertAlmostEqual(float(np.min(a)), 109.99999999999997, places=16) - self.assertAlmostEqual(float(np.min(v)), 8.526512829121202e-14, places=16) - self.assertAlmostEqual(float(np.min(h)), 8.038873388460928e-14, places=16) - self.assertAlmostEqual(float(np.min(d)), 0.0, places=16) + self.assertAlmostEqual(float(np.min(a)), 110.0, places=12) + self.assertAlmostEqual(float(np.min(v)), 0.0, places=12) + self.assertAlmostEqual(float(np.min(h)), 0.0, places=12) + self.assertAlmostEqual(float(np.min(d)), 0.0, places=12) def test_adm_dwt2_cy_dc(self): x = (55 * np.ones([324, 576])).astype(np.float64) @@ -106,15 +106,15 @@ def test_adm_dwt2_cy_dc(self): self.assertEqual(h.shape, (162, 288)) self.assertEqual(d.shape, (162, 288)) - self.assertAlmostEqual(float(np.max(a)), 109.99999999999999, places=8) - self.assertAlmostEqual(float(np.max(v)), 8.526512829121202e-14, places=16) - self.assertAlmostEqual(float(np.max(h)), 8.038873388460928e-14, places=16) - self.assertAlmostEqual(float(np.max(d)), 0.0, places=16) + self.assertAlmostEqual(float(np.max(a)), 110.0, places=12) + self.assertAlmostEqual(float(np.max(v)), 0.0, places=12) + self.assertAlmostEqual(float(np.max(h)), 0.0, places=12) + self.assertAlmostEqual(float(np.max(d)), 0.0, places=12) - self.assertAlmostEqual(float(np.min(a)), 109.99999999999997, places=16) - self.assertAlmostEqual(float(np.min(v)), 8.526512829121202e-14, places=16) - self.assertAlmostEqual(float(np.min(h)), 8.038873388460928e-14, places=16) - self.assertAlmostEqual(float(np.min(d)), 0.0, places=16) + self.assertAlmostEqual(float(np.min(a)), 110.0, places=12) + self.assertAlmostEqual(float(np.min(v)), 0.0, places=12) + self.assertAlmostEqual(float(np.min(h)), 0.0, places=12) + self.assertAlmostEqual(float(np.min(d)), 0.0, places=12) class AdmDwt2CyTestOnAkiyo(unittest.TestCase): From c40e5f561f60f689a19dfe2f4d9a959b320e5922 Mon Sep 17 00:00:00 2001 From: Nil Fons Miret Date: Fri, 12 Jan 2024 21:06:46 +0000 Subject: [PATCH 14/24] model.c: fix -Wlto-type-mismatch --- libvmaf/src/model.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/libvmaf/src/model.c b/libvmaf/src/model.c index 9cf8a670a..ab623a9fd 100644 --- a/libvmaf/src/model.c +++ b/libvmaf/src/model.c @@ -21,24 +21,24 @@ typedef struct VmafBuiltInModel { #if VMAF_BUILT_IN_MODELS #if VMAF_FLOAT_FEATURES -extern const char src_vmaf_float_v0_6_1neg_json; +extern const char src_vmaf_float_v0_6_1neg_json[]; extern const int src_vmaf_float_v0_6_1neg_json_len; -extern const char src_vmaf_float_v0_6_1_json; +extern const char src_vmaf_float_v0_6_1_json[]; extern const int src_vmaf_float_v0_6_1_json_len; -extern const char src_vmaf_float_b_v0_6_3_json; +extern const char src_vmaf_float_b_v0_6_3_json[]; extern const int src_vmaf_float_b_v0_6_3_json_len; -extern const char src_vmaf_float_4k_v0_6_1_json; +extern const char src_vmaf_float_4k_v0_6_1_json[]; extern const int src_vmaf_float_4k_v0_6_1_json_len; #endif -extern const char src_vmaf_v0_6_1_json; +extern const char src_vmaf_v0_6_1_json[]; extern const int src_vmaf_v0_6_1_json_len; -extern const char src_vmaf_b_v0_6_3_json; +extern const char src_vmaf_b_v0_6_3_json[]; extern const int src_vmaf_b_v0_6_3_json_len; -extern const char src_vmaf_v0_6_1neg_json; +extern const char src_vmaf_v0_6_1neg_json[]; extern const int src_vmaf_v0_6_1neg_json_len; -extern const char src_vmaf_4k_v0_6_1_json; +extern const char src_vmaf_4k_v0_6_1_json[]; extern const int src_vmaf_4k_v0_6_1_json_len; -extern const char src_vmaf_4k_v0_6_1neg_json; +extern const char src_vmaf_4k_v0_6_1neg_json[]; extern const int src_vmaf_4k_v0_6_1neg_json_len; #endif @@ -47,48 +47,48 @@ static const VmafBuiltInModel built_in_models[] = { #if VMAF_FLOAT_FEATURES { .version = "vmaf_float_v0.6.1", - .data = &src_vmaf_float_v0_6_1_json, + .data = src_vmaf_float_v0_6_1_json, .data_len = &src_vmaf_float_v0_6_1_json_len, }, { .version = "vmaf_float_b_v0.6.3", - .data = &src_vmaf_float_b_v0_6_3_json, + .data = src_vmaf_float_b_v0_6_3_json, .data_len = &src_vmaf_float_b_v0_6_3_json_len, }, { .version = "vmaf_float_v0.6.1neg", - .data = &src_vmaf_float_v0_6_1neg_json, + .data = src_vmaf_float_v0_6_1neg_json, .data_len = &src_vmaf_float_v0_6_1neg_json_len, }, { .version = "vmaf_float_4k_v0.6.1", - .data = &src_vmaf_float_4k_v0_6_1_json, + .data = src_vmaf_float_4k_v0_6_1_json, .data_len = &src_vmaf_float_4k_v0_6_1_json_len, }, #endif { .version = "vmaf_v0.6.1", - .data = &src_vmaf_v0_6_1_json, + .data = src_vmaf_v0_6_1_json, .data_len = &src_vmaf_v0_6_1_json_len, }, { .version = "vmaf_b_v0.6.3", - .data = &src_vmaf_b_v0_6_3_json, + .data = src_vmaf_b_v0_6_3_json, .data_len = &src_vmaf_b_v0_6_3_json_len, }, { .version = "vmaf_v0.6.1neg", - .data = &src_vmaf_v0_6_1neg_json, + .data = src_vmaf_v0_6_1neg_json, .data_len = &src_vmaf_v0_6_1neg_json_len, }, { .version = "vmaf_4k_v0.6.1", - .data = &src_vmaf_4k_v0_6_1_json, + .data = src_vmaf_4k_v0_6_1_json, .data_len = &src_vmaf_4k_v0_6_1_json_len, }, { .version = "vmaf_4k_v0.6.1neg", - .data = &src_vmaf_4k_v0_6_1neg_json, + .data = src_vmaf_4k_v0_6_1neg_json, .data_len = &src_vmaf_4k_v0_6_1neg_json_len, }, #endif From 7b9e4c58b7b996d193fc5bf4325480dc4151f36c Mon Sep 17 00:00:00 2001 From: Christos Bampis Date: Thu, 7 Apr 2022 10:43:40 -0700 Subject: [PATCH 15/24] Start cleaning up setup.py --- python/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/setup.py b/python/setup.py index cd559c8c2..8c3c13bb9 100755 --- a/python/setup.py +++ b/python/setup.py @@ -53,6 +53,7 @@ def get_version(): "sureal>=0.4.2", "dill>=0.3.1", ], + setup_requires=["cython", "numpy"], entry_points = { 'console_scripts': [ 'run_cleaning_cache=vmaf.script.run_cleaning_cache:main', From fac14df32ca8757e886c355236debd7b51f991e4 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 7 Apr 2022 11:34:17 -0700 Subject: [PATCH 16/24] Avoid importing cython "too early" in setup.py --- python/requirements.txt | 1 - python/setup.py | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/python/requirements.txt b/python/requirements.txt index 1e453240c..e6a249bf9 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -7,7 +7,6 @@ scikit-image>=0.16.2 h5py>=2.6.0 sureal>=0.4.2 dill>=0.3.1 -cython PyWavelets>=1.1.1 python-slugify>=5.0.0 libsvm-official>=3.30 diff --git a/python/setup.py b/python/setup.py index 8c3c13bb9..51839b3c6 100755 --- a/python/setup.py +++ b/python/setup.py @@ -9,9 +9,8 @@ """ import os -from distutils.core import setup -from Cython.Build import cythonize -import numpy + +from setuptools import setup PYTHON_PROJECT = os.path.dirname(os.path.abspath(__file__)) @@ -24,12 +23,38 @@ def get_version(): for line in fh: if line.startswith("__version__"): return line.strip().rpartition(" ")[2].replace('"', "") + except Exception: pass + return "0.0-dev" -ext_module = cythonize(['vmaf/core/adm_dwt2_cy.pyx']) -ext_module[0].include_dirs = [numpy.get_include(), '../libvmaf/src'] + +class LazyExtensions(list): + _extensions = None + + @property + def extensions(self): + if self._extensions is None: + from Cython.Build import cythonize + import numpy + + self._extensions = cythonize([ + 'vmaf/core/adm_dwt2_cy.pyx' + ]) + self._extensions[0].include_dirs = [numpy.get_include(), '../libvmaf/src'] + + return self._extensions + + def __iter__(self): + return iter(self.extensions) + + def __contains__(self, value): + return value in self.extensions + + def __len__(self): + return len(self.extensions) + setup( name="vmaf", @@ -67,5 +92,5 @@ def get_version(): 'run_vmaf_training=vmaf.script.run_vmaf_training:main', ], }, - ext_modules=ext_module, + ext_modules=LazyExtensions(), ) From 03d31171b4ed7ec78ae3e87bdb553a56dfe105b4 Mon Sep 17 00:00:00 2001 From: Zoran Simic Date: Thu, 7 Apr 2022 16:39:26 -0700 Subject: [PATCH 17/24] Use pyproject.toml --- python/pyproject.toml | 2 ++ python/setup.py | 1 - python/tox.ini | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 python/pyproject.toml diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 000000000..83de03ad5 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,2 @@ +[build-system] +requires = ["setuptools", "wheel", "Cython", "numpy"] diff --git a/python/setup.py b/python/setup.py index 51839b3c6..3202abf04 100755 --- a/python/setup.py +++ b/python/setup.py @@ -78,7 +78,6 @@ def __len__(self): "sureal>=0.4.2", "dill>=0.3.1", ], - setup_requires=["cython", "numpy"], entry_points = { 'console_scripts': [ 'run_cleaning_cache=vmaf.script.run_cleaning_cache:main', diff --git a/python/tox.ini b/python/tox.ini index cdd98a8dc..0a064f139 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -1,6 +1,5 @@ [tox] envlist = py38, coverage -skip_missing_interpreters = true [testenv] From b6548d4625e5393aebdb9385a55a56057f22bdf8 Mon Sep 17 00:00:00 2001 From: Zhi Li Date: Sat, 28 Jan 2023 12:22:50 -0800 Subject: [PATCH 18/24] Add language_level to cythonize to fix adm_dwt2_cy build issue. --- python/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/setup.py b/python/setup.py index 3202abf04..bb4bf3406 100755 --- a/python/setup.py +++ b/python/setup.py @@ -41,7 +41,7 @@ def extensions(self): self._extensions = cythonize([ 'vmaf/core/adm_dwt2_cy.pyx' - ]) + ], compiler_directives={'language_level' : "3"}) self._extensions[0].include_dirs = [numpy.get_include(), '../libvmaf/src'] return self._extensions From 6629dbe7d7b75a955ffe89c9dd5957ea3b11cfb8 Mon Sep 17 00:00:00 2001 From: Nil Fons Miret Date: Thu, 1 Feb 2024 22:38:05 +0000 Subject: [PATCH 19/24] Add documentation for VmafConfiguration, including link to n_subsample issue --- libvmaf/include/libvmaf/libvmaf.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/libvmaf/include/libvmaf/libvmaf.h b/libvmaf/include/libvmaf/libvmaf.h index 31c5af727..62660d829 100644 --- a/libvmaf/include/libvmaf/libvmaf.h +++ b/libvmaf/include/libvmaf/libvmaf.h @@ -55,6 +55,31 @@ enum VmafPoolingMethod { VMAF_POOL_METHOD_NB }; +/** + * @struct VmafConfiguration + * @brief Configuration needed to initialize a `VmafContext` + * + * @param log_level How verbose the logger is. + * + * @param n_threads How many threads can be used to run + * feature extractors concurrently. + * + * @param n_subsample Compute scores only every N frames. + * Note that setting an even value for N can lead to + * inaccurate results. For more detail, see + * https://github.com/Netflix/vmaf/issues/1214 + * + * @param cpumask Restrict permitted CPU instruction sets. + * if cpumask & 1: disable SSE2 + * if cpumask & 2: disable SSE3/SSSE3 + * if cpumask & 4: disable SSE4.1 + * if cpumask & 8: disable AVX2 + * if cpumask & 16: disable AVX512 + * if cpumask & 32: disable AVX512ICL + * + * @param gpumask Restrict permitted GPU operations. + * if gpumask: disable CUDA + */ typedef struct VmafConfiguration { enum VmafLogLevel log_level; unsigned n_threads; From 346e176d939174b9bcd8d749189ec4e54a01ef74 Mon Sep 17 00:00:00 2001 From: Nil Fons Miret Date: Fri, 2 Feb 2024 18:12:47 +0000 Subject: [PATCH 20/24] libvmaf.h: Fix cpumask & 1 documentation for NEON --- libvmaf/include/libvmaf/libvmaf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvmaf/include/libvmaf/libvmaf.h b/libvmaf/include/libvmaf/libvmaf.h index 62660d829..71d39e656 100644 --- a/libvmaf/include/libvmaf/libvmaf.h +++ b/libvmaf/include/libvmaf/libvmaf.h @@ -70,7 +70,7 @@ enum VmafPoolingMethod { * https://github.com/Netflix/vmaf/issues/1214 * * @param cpumask Restrict permitted CPU instruction sets. - * if cpumask & 1: disable SSE2 + * if cpumask & 1: disable SSE2 / disable NEON (on arm64) * if cpumask & 2: disable SSE3/SSSE3 * if cpumask & 4: disable SSE4.1 * if cpumask & 8: disable AVX2 From 1bb98bbddaf2a48308a625528510aa198a9e93c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20M=C3=BCller?= Date: Wed, 7 Feb 2024 11:52:14 +0100 Subject: [PATCH 21/24] fix: Dockerfile cuda --- .dockerignore | 4 ++++ Dockerfile.cuda | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3098be75a..ebbccc08e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,7 @@ matlab workspace python/.tox .git +.venv +*.mp4 +*.yuv +*.mov diff --git a/Dockerfile.cuda b/Dockerfile.cuda index c4bc1f708..47f5a0f71 100644 --- a/Dockerfile.cuda +++ b/Dockerfile.cuda @@ -1,18 +1,20 @@ -ARG CUDA_VERSION=12.1.0 +ARG CUDA_VERSION=12.3.1 +ARG VMAF_TAG=master +ARG FFFMPEG_TAG=master # By copying the installation from a devel to a runtime container one could likely save a lot container size FROM nvidia/cuda:$CUDA_VERSION-devel-ubuntu22.04 RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libopenjp2-7-dev \ ninja-build cmake git python3 python3-pip nasm xxd pkg-config curl unzip -RUN git clone https://github.com/Netflix/vmaf.git +RUN git clone https://github.com/Netflix/vmaf.git && cd vmaf && git checkout $VMAF_TAG -RUN git clone https://github.com/FFmpeg/FFmpeg.git +RUN git clone https://github.com/FFmpeg/FFmpeg.git && cd FFmpeg && git checkout $FFFMPEG_TAG RUN git clone https://github.com/FFmpeg/nv-codec-headers.git && cd nv-codec-headers && make && make install # install vmaf -RUN python3 -m pip install meson cpython +RUN python3 -m pip install meson RUN cd vmaf && meson libvmaf/build libvmaf -Denable_cuda=true -Denable_avx512=true --buildtype release && \ ninja -vC libvmaf/build && \ ninja -vC libvmaf/build install From 8b2687a91dfdbb6814cd955cdd0f7ba626d0e7bc Mon Sep 17 00:00:00 2001 From: Kyle Swanson Date: Tue, 12 Dec 2023 19:37:48 -0800 Subject: [PATCH 22/24] libvmaf: introduce framesync api Co-authored-by: Niranjan Kumar Co-authored-by: Mallikarjun Kamble Co-authored-by: Nil Fons Miret --- libvmaf/src/feature/feature_extractor.c | 2 + libvmaf/src/feature/feature_extractor.h | 4 + libvmaf/src/feature/float_adm.c | 1 + libvmaf/src/framesync.c | 227 ++++++++++++++++++++++++ libvmaf/src/framesync.h | 46 +++++ libvmaf/src/libvmaf.c | 19 +- libvmaf/src/meson.build | 1 + libvmaf/test/meson.build | 10 ++ libvmaf/test/test_framesync.c | 150 ++++++++++++++++ 9 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 libvmaf/src/framesync.c create mode 100644 libvmaf/src/framesync.h create mode 100644 libvmaf/test/test_framesync.c diff --git a/libvmaf/src/feature/feature_extractor.c b/libvmaf/src/feature/feature_extractor.c index 94a4e16ca..21f9e64e5 100644 --- a/libvmaf/src/feature/feature_extractor.c +++ b/libvmaf/src/feature/feature_extractor.c @@ -386,6 +386,8 @@ int vmaf_fex_ctx_pool_aquire(VmafFeatureExtractorContextPool *pool, } err = vmaf_feature_extractor_context_create(&f, entry->fex, d); if (err) goto unlock; + if (f->fex->flags & VMAF_FEATURE_FRAME_SYNC) + f->fex->framesync = (fex->framesync); } if (!entry->ctx_list[i].in_use) { entry->ctx_list[i].fex_ctx = *fex_ctx = f; diff --git a/libvmaf/src/feature/feature_extractor.h b/libvmaf/src/feature/feature_extractor.h index e34188c3b..574436e76 100644 --- a/libvmaf/src/feature/feature_extractor.h +++ b/libvmaf/src/feature/feature_extractor.h @@ -24,6 +24,7 @@ #include #include "dict.h" +#include "framesync.h" #include "feature_collector.h" #include "opt.h" @@ -36,6 +37,7 @@ enum VmafFeatureExtractorFlags { VMAF_FEATURE_EXTRACTOR_TEMPORAL = 1 << 0, VMAF_FEATURE_EXTRACTOR_CUDA = 1 << 1, + VMAF_FEATURE_FRAME_SYNC = 1 << 2, }; typedef struct VmafFeatureExtractor { @@ -94,6 +96,8 @@ typedef struct VmafFeatureExtractor { VmafCudaState *cu_state; ///< VmafCudaState, set by framework #endif + VmafFrameSyncContext *framesync; + } VmafFeatureExtractor; VmafFeatureExtractor *vmaf_get_feature_extractor_by_name(const char *name); diff --git a/libvmaf/src/feature/float_adm.c b/libvmaf/src/feature/float_adm.c index 488298514..60d1986ec 100644 --- a/libvmaf/src/feature/float_adm.c +++ b/libvmaf/src/feature/float_adm.c @@ -22,6 +22,7 @@ #include "dict.h" #include "feature_collector.h" +#include "framesync.h" #include "feature_extractor.h" #include "feature_name.h" diff --git a/libvmaf/src/framesync.c b/libvmaf/src/framesync.c new file mode 100644 index 000000000..5c4c57758 --- /dev/null +++ b/libvmaf/src/framesync.c @@ -0,0 +1,227 @@ +/** + * + * Copyright 2016-2023 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 +#include +#include +#include +#include +#include "framesync.h" + +enum { + BUF_FREE = 0, + BUF_ACQUIRED, + BUF_FILLED, + BUF_RETRIEVED, +}; + +typedef struct VmafFrameSyncBuf { + void *frame_data; + int buf_status; + signed long index; + struct VmafFrameSyncBuf *next; +} VmafFrameSyncBuf; + +typedef struct VmafFrameSyncContext { + VmafFrameSyncBuf *buf_que; + pthread_mutex_t acquire_lock; + pthread_mutex_t retrieve_lock; + pthread_cond_t retrieve; + unsigned buf_cnt; +} VmafFrameSyncContext; + +int vmaf_framesync_init(VmafFrameSyncContext **fs_ctx) +{ + VmafFrameSyncContext *const ctx = *fs_ctx = malloc(sizeof(VmafFrameSyncContext)); + if (!ctx) return -ENOMEM; + memset(ctx, 0, sizeof(VmafFrameSyncContext)); + ctx->buf_cnt = 1; + + pthread_mutex_init(&(ctx->acquire_lock), NULL); + pthread_mutex_init(&(ctx->retrieve_lock), NULL); + pthread_cond_init(&(ctx->retrieve), NULL); + + VmafFrameSyncBuf *buf_que = ctx->buf_que = malloc(sizeof(VmafFrameSyncBuf)); + + buf_que->frame_data = NULL; + buf_que->buf_status = BUF_FREE; + buf_que->index = -1; + buf_que->next = NULL; + + return 0; +} + +int vmaf_framesync_acquire_new_buf(VmafFrameSyncContext *fs_ctx, void **data, + unsigned data_sz, unsigned index) +{ + VmafFrameSyncBuf *buf_que = fs_ctx->buf_que; + *data = NULL; + + pthread_mutex_lock(&(fs_ctx->acquire_lock)); + + // traverse until a free buffer is found + for (unsigned i = 0; i < fs_ctx->buf_cnt; i++) { + if (buf_que->buf_status == BUF_FREE) { + buf_que->frame_data = *data = malloc(data_sz); + if (!buf_que->frame_data) + return -ENOMEM; + buf_que->buf_status = BUF_ACQUIRED; + buf_que->index = index; + break; + } + // move to next node + if (buf_que->next != NULL) + buf_que = buf_que->next; + } + + // create a new node if all nodes are occupied in the list and append to the tail + if (*data == NULL) { + VmafFrameSyncBuf *new_buf_node = malloc(sizeof(VmafFrameSyncBuf)); + buf_que->next = new_buf_node; + new_buf_node->buf_status = BUF_FREE; + new_buf_node->index = -1; + new_buf_node->next = NULL; + fs_ctx->buf_cnt++; + + new_buf_node->frame_data = *data = malloc(data_sz); + if (!new_buf_node->frame_data) + return -ENOMEM; + new_buf_node->buf_status = BUF_ACQUIRED; + new_buf_node->index = index; + } + + pthread_mutex_unlock(&(fs_ctx->acquire_lock)); + + return 0; +} + +int vmaf_framesync_submit_filled_data(VmafFrameSyncContext *fs_ctx, void *data, + unsigned index) +{ + VmafFrameSyncBuf *buf_que = fs_ctx->buf_que; + + pthread_mutex_lock(&(fs_ctx->retrieve_lock)); + + // loop until a matchng buffer is found + for (unsigned i = 0; i < fs_ctx->buf_cnt; i++) { + if ((buf_que->index == index) && (buf_que->buf_status == BUF_ACQUIRED)) { + buf_que->buf_status = BUF_FILLED; + if (data != buf_que->frame_data) + return -1; + break; + } + + // move to next node + if (NULL != buf_que->next) + buf_que = buf_que->next; + } + + pthread_cond_broadcast(&(fs_ctx->retrieve)); + pthread_mutex_unlock(&(fs_ctx->retrieve_lock)); + + return 0; +} + +int vmaf_framesync_retrieve_filled_data(VmafFrameSyncContext *fs_ctx, + void **data, unsigned index) +{ + *data = NULL; + + while (*data == NULL) { + VmafFrameSyncBuf *buf_que = fs_ctx->buf_que; + pthread_mutex_lock(&(fs_ctx->retrieve_lock)); + // loop until a free buffer is found + for (unsigned i = 0; i < fs_ctx->buf_cnt; i++) { + if ((buf_que->index == index) && (buf_que->buf_status == BUF_FILLED)) { + buf_que->buf_status = BUF_RETRIEVED; + *data = buf_que->frame_data; + break; + } + + // move to next node + if (NULL != buf_que->next) + buf_que = buf_que->next; + } + + if (*data == NULL) + pthread_cond_wait(&(fs_ctx->retrieve), &(fs_ctx->retrieve_lock)); + + pthread_mutex_unlock(&(fs_ctx->retrieve_lock)); + } + + return 0; +} + +int vmaf_framesync_release_buf(VmafFrameSyncContext *fs_ctx, void *data, + unsigned index) +{ + VmafFrameSyncBuf *buf_que = fs_ctx->buf_que; + + pthread_mutex_lock(&(fs_ctx->acquire_lock)); + // loop until a matching buffer is found + for (unsigned i = 0; i < fs_ctx->buf_cnt; i++) { + if ((buf_que->index == index) && (buf_que->buf_status == BUF_RETRIEVED)) { + if (data != buf_que->frame_data) + return -1; + + free(buf_que->frame_data); + buf_que->frame_data = NULL; + buf_que->buf_status = BUF_FREE; + buf_que->index = -1; + break; + } + + // move to next node + if (NULL != buf_que->next) + buf_que = buf_que->next; + } + + pthread_mutex_unlock(&(fs_ctx->acquire_lock)); + return 0; +} + +int vmaf_framesync_destroy(VmafFrameSyncContext *fs_ctx) +{ + VmafFrameSyncBuf *buf_que = fs_ctx->buf_que; + VmafFrameSyncBuf *buf_que_tmp; + + pthread_mutex_destroy(&(fs_ctx->acquire_lock)); + pthread_mutex_destroy(&(fs_ctx->retrieve_lock)); + pthread_cond_destroy(&(fs_ctx->retrieve)); + + //check for any data buffers which are not freed + for (unsigned i = 0; i < fs_ctx->buf_cnt; i++) { + if (NULL != buf_que->frame_data) { + free(buf_que->frame_data); + buf_que->frame_data = NULL; + } + + // move to next node + if (NULL != buf_que->next) { + buf_que_tmp = buf_que; + buf_que = buf_que->next; + free(buf_que_tmp); + } else { + free(buf_que); + } + } + + free(fs_ctx); + + return 0; +} diff --git a/libvmaf/src/framesync.h b/libvmaf/src/framesync.h new file mode 100644 index 000000000..08e9a543a --- /dev/null +++ b/libvmaf/src/framesync.h @@ -0,0 +1,46 @@ +/** + * + * Copyright 2016-2023 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. + * + */ + +#ifndef __VMAF_FRAME_SYNC_H__ +#define __VMAF_FRAME_SYNC_H__ + +#include +#include +#include +#include +#include "libvmaf/libvmaf.h" + +typedef struct VmafFrameSyncContext VmafFrameSyncContext; + +int vmaf_framesync_init(VmafFrameSyncContext **fs_ctx); + +int vmaf_framesync_acquire_new_buf(VmafFrameSyncContext *fs_ctx, void **data, + unsigned data_sz, unsigned index); + +int vmaf_framesync_submit_filled_data(VmafFrameSyncContext *fs_ctx, void *data, + unsigned index); + +int vmaf_framesync_retrieve_filled_data(VmafFrameSyncContext *fs_ctx, void **data, + unsigned index); + +int vmaf_framesync_release_buf(VmafFrameSyncContext *fs_ctx, void *data, + unsigned index); + +int vmaf_framesync_destroy(VmafFrameSyncContext *fs_ctx); + +#endif /* __VMAF_FRAME_SYNC_H__ */ diff --git a/libvmaf/src/libvmaf.c b/libvmaf/src/libvmaf.c index 3fbb050c5..fa3fe6787 100644 --- a/libvmaf/src/libvmaf.c +++ b/libvmaf/src/libvmaf.c @@ -56,6 +56,7 @@ typedef struct VmafContext { RegisteredFeatureExtractors registered_feature_extractors; VmafFeatureExtractorContextPool *fex_ctx_pool; VmafThreadPool *thread_pool; + VmafFrameSyncContext *framesync; #ifdef HAVE_CUDA struct { struct { @@ -99,8 +100,10 @@ int vmaf_init(VmafContext **vmaf, VmafConfiguration cfg) vmaf_set_log_level(cfg.log_level); - err = vmaf_feature_collector_init(&(v->feature_collector)); + 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; @@ -119,6 +122,8 @@ int vmaf_init(VmafContext **vmaf, VmafConfiguration cfg) 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: @@ -235,11 +240,20 @@ static int set_fex_cuda_state(VmafFeatureExtractorContext *fex_ctx, #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); @@ -292,6 +306,7 @@ int vmaf_use_feature(VmafContext *vmaf, const char *feature_name, #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); @@ -339,6 +354,7 @@ int vmaf_use_features_from_model(VmafContext *vmaf, VmafModel *model) #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) { @@ -405,6 +421,7 @@ static int threaded_read_pictures(VmafContext *vmaf, VmafPicture *ref, continue; } + fex->framesync = vmaf->framesync; VmafFeatureExtractorContext *fex_ctx; err = vmaf_fex_ctx_pool_aquire(vmaf->fex_ctx_pool, fex, opts_dict, &fex_ctx); diff --git a/libvmaf/src/meson.build b/libvmaf/src/meson.build index 623639d4e..065c59e20 100644 --- a/libvmaf/src/meson.build +++ b/libvmaf/src/meson.build @@ -461,6 +461,7 @@ libvmaf_sources = [ src_dir + 'read_json_model.c', src_dir + 'pdjson.c', src_dir + 'log.c', + src_dir + 'framesync.c', ] if is_cuda_enabled diff --git a/libvmaf/test/meson.build b/libvmaf/test/meson.build index 250272035..8ae58e512 100644 --- a/libvmaf/test/meson.build +++ b/libvmaf/test/meson.build @@ -122,6 +122,15 @@ test_psnr = executable('test_psnr', link_with : get_option('default_library') == 'both' ? libvmaf.get_static_lib() : libvmaf, ) +test_framesync = executable('test_framesync', + ['test.c', 'test_framesync.c'], + include_directories : [libvmaf_inc, test_inc, include_directories('../src/')], + link_with : get_option('default_library') == 'both' ? libvmaf.get_static_lib() : libvmaf, + c_args : vmaf_cflags_common, + cpp_args : vmaf_cflags_common, + dependencies : thread_lib, +) + if get_option('enable_cuda') test_ring_buffer = executable('test_ring_buffer', ['test.c', 'test_ring_buffer.c', '../src/cuda/ring_buffer.c', '../src/cuda/picture_cuda.c'], @@ -158,3 +167,4 @@ test('test_cambi', test_cambi) test('test_luminance_tools', test_luminance_tools) test('test_cli_parse', test_cli_parse) test('test_psnr', test_psnr) +test('test_framesync', test_framesync) diff --git a/libvmaf/test/test_framesync.c b/libvmaf/test/test_framesync.c new file mode 100644 index 000000000..f5e9997cc --- /dev/null +++ b/libvmaf/test/test_framesync.c @@ -0,0 +1,150 @@ +/** + * + * 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 +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "framesync.h" +#include "test.h" +#include "thread_pool.h" + +#define NUM_TEST_FRAMES 10 +#define FRAME_BUF_LEN 1024 + +typedef struct ThreadData { + uint8_t *ref; + uint8_t *dist; + unsigned index; + VmafFrameSyncContext *fs_ctx; + int err; +} ThreadData; + +static void my_worker(void *data) +{ + int ctr; + struct ThreadData *thread_data = data; + uint8_t *shared_buf; + uint8_t *dependent_buf; + + //acquire new buffer from frame sync + vmaf_framesync_acquire_new_buf(thread_data->fs_ctx, (void*)&shared_buf, + FRAME_BUF_LEN, thread_data->index); + + //populate shared buffer with values + for (ctr = 0; ctr < FRAME_BUF_LEN; ctr++) + shared_buf[ctr] = thread_data->ref[ctr] + thread_data->dist[ctr] + 2; + + //submit filled buffer back to frame sync + vmaf_framesync_submit_filled_data(thread_data->fs_ctx, shared_buf, + thread_data->index); + + //sleep to simulate work load + const int sleep_seconds = 1; +#ifdef _WIN32 + Sleep(1000 * sleep_seconds); +#else + sleep(sleep_seconds); +#endif + + if (thread_data->index == 0) goto cleanup; + + //retrieve dependent buffer from frame sync + vmaf_framesync_retrieve_filled_data(thread_data->fs_ctx, + (void*)&dependent_buf, + thread_data->index - 1); + + for (ctr = 0; ctr < FRAME_BUF_LEN; ctr++) { + if (dependent_buf[ctr] != (thread_data->ref[ctr] + thread_data->dist[ctr])) { + fprintf(stderr, "verification error in frame index %d\n", + thread_data->index); + } + } + + //release dependent buffer from frame sync + vmaf_framesync_release_buf(thread_data->fs_ctx, dependent_buf, + thread_data->index - 1); + +cleanup: + free(thread_data->ref); + free(thread_data->dist); +} + +static char *test_framesync_create_process_and_destroy() +{ + int err, frame_index; + + VmafThreadPool *pool; + VmafFrameSyncContext *fs_ctx; + unsigned n_threads = 2; + + err = vmaf_thread_pool_create(&pool, n_threads); + mu_assert("problem during vmaf_thread_pool_init", !err); + + err = vmaf_framesync_init(&fs_ctx); + mu_assert("problem during vmaf_framesync_init", !err); + + fprintf(stderr, "\n"); + for (frame_index = 0; frame_index < NUM_TEST_FRAMES; frame_index++) { + uint8_t *pic_a = malloc(FRAME_BUF_LEN); + uint8_t *pic_b = malloc(FRAME_BUF_LEN); + + fprintf(stderr, "processing frame %d\r", frame_index); + + memset(pic_a, frame_index, FRAME_BUF_LEN); + memset(pic_b, frame_index, FRAME_BUF_LEN); + + struct ThreadData data = { + .ref = pic_a, + .dist = pic_b, + .index = frame_index, + .fs_ctx = fs_ctx, + .err = 0, + }; + + err = vmaf_thread_pool_enqueue(pool, my_worker, &data, sizeof(ThreadData)); + + mu_assert("problem during vmaf_thread_pool_enqueue with data", !err); + + //wait once in 2 frames + if ((frame_index >= 1) && (frame_index & 1)) { + err = vmaf_thread_pool_wait(pool); + mu_assert("problem during vmaf_thread_pool_wait", !err); + } + } + fprintf(stderr, "\n"); + + err = vmaf_thread_pool_wait(pool); + mu_assert("problem during vmaf_thread_pool_wait\n", !err); + err = vmaf_thread_pool_destroy(pool); + mu_assert("problem during vmaf_thread_pool_destroy\n", !err); + err = vmaf_framesync_destroy(fs_ctx); + mu_assert("problem during vmaf_framesync_destroy\n", !err); + + return NULL; +} + +char *run_tests() +{ + mu_run_test(test_framesync_create_process_and_destroy); + return NULL; +} From 3b430c2aba269db1ca74b4ff8a2e6e82eff3c99c Mon Sep 17 00:00:00 2001 From: nilfm Date: Thu, 8 Feb 2024 11:32:58 -0500 Subject: [PATCH 23/24] upgrade tox to v4 --- .github/workflows/libvmaf.yml | 2 +- python/tox.ini | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/libvmaf.yml b/.github/workflows/libvmaf.yml index be6488d2d..7ac71a45c 100644 --- a/.github/workflows/libvmaf.yml +++ b/.github/workflows/libvmaf.yml @@ -78,7 +78,7 @@ jobs: - name: Run tox tests run: | mkdir -p ~/.ccache && sudo chown -R $(whoami) ~/.ccache - pip install 'tox<4' + pip install tox tox -c python/ -e py -- -v -p no:warnings -m 'main or lib' --doctest-modules - name: Get binary path & Current Release diff --git a/python/tox.ini b/python/tox.ini index 0a064f139..4f15faba2 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -3,7 +3,11 @@ envlist = py38, coverage [testenv] -passenv = APPVEYOR* CI SSL_CERT_FILE TEST_MARKER TRAVIS* +passenv = APPVEYOR* + CI + SSL_CERT_FILE + TEST_MARKER + TRAVIS* setenv = COVERAGE_FILE={toxworkdir}/.coverage.{envname} usedevelop = True deps = -rrequirements.txt From 8a57e3e8f3c386d96b799d75df1d967f3fd532d5 Mon Sep 17 00:00:00 2001 From: Nil Fons Miret Date: Tue, 30 Jan 2024 21:52:29 +0000 Subject: [PATCH 24/24] github/workflows/libvmaf.yml: upload to a specific name for each matrix.oss and matrix.cc --- .github/workflows/libvmaf.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/libvmaf.yml b/.github/workflows/libvmaf.yml index 7ac71a45c..e99370226 100644 --- a/.github/workflows/libvmaf.yml +++ b/.github/workflows/libvmaf.yml @@ -91,7 +91,7 @@ jobs: - name: Upload vmaf uses: actions/upload-artifact@v3 with: - name: ${{ matrix.os }}-vmaf + name: ${{ matrix.os }}-${{ matrix.CC }}-vmaf path: ${{ steps.get_info.outputs.path }} - name: Upload vmaf if: steps.get_info.outputs.upload_url != 'null'