| /* |
| * Copyright (c) 2026, Alliance for Open Media. All rights reserved |
| * |
| * This source code is subject to the terms of the BSD 3-Clause Clear License |
| * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear |
| * License was not distributed with this source code in the LICENSE file, you |
| * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the |
| * Alliance for Open Media Patent License 1.0 was not distributed with this |
| * source code in the PATENTS file, you can obtain it at |
| * aomedia.org/license/patent-license/. |
| */ |
| |
| #include "config/avm_dsp_rtcd.h" |
| #include "av2/common/av2_common_int.h" |
| #include "av2/encoder/banding_detection.h" |
| #include "avm_mem/avm_mem.h" |
| |
| #define SWAP_FLOATS(x, y) \ |
| { \ |
| float temp = x; \ |
| x = y; \ |
| y = temp; \ |
| } |
| |
| /* Window size to compute CAMBI: 65 corresponds to approximately 1 degree at 4k |
| * scale */ |
| #define CAMBI_DEFAULT_WINDOW_SIZE (65) |
| |
| /* Encoder banding detection thresholds */ |
| #define CAMBI_DIFF_THRESHOLD_8b 4 |
| #define CAMBI_SOURCE_THRESHOLD_8b 3 |
| |
| #define CAMBI_DIFF_THRESHOLD_10b 3 |
| #define CAMBI_SOURCE_THRESHOLD_10b 2 |
| |
| /* Visibility threshold for luminance ΔL < tvi_threshold*L_mean for BT.1886 */ |
| #define CAMBI_TVI (0.019) |
| |
| /* Max log contrast luma levels */ |
| #define CAMBI_DEFAULT_MAX_LOG_CONTRAST (2) |
| |
| /* Window size for CAMBI */ |
| #define CAMBI_MIN_WIDTH (192) |
| #define CAMBI_MAX_WIDTH (4096) |
| |
| #define CAMBI_NUM_SCALES (5) |
| |
| /* Ratio of pixels for computation, must be 0 > topk >= 1.0 */ |
| #define CAMBI_DEFAULT_TOPK_POOLING (0.6) |
| |
| /* Spatial mask filter size for CAMBI */ |
| #define CAMBI_MASK_FILTER_SIZE (7) |
| |
| #define CLAMP(x, low, high) \ |
| (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) |
| |
| /* CAMBI preprocessing functions */ |
| static void copy_8b_to_10b_buffer(uint16_t *data, int stride, unsigned in_w, |
| unsigned in_h, uint16_t *out_data, |
| int out_stride, unsigned out_w, |
| unsigned out_h) { |
| if (in_w != out_w || in_h != out_h) assert(0); |
| |
| for (unsigned i = 0; i < out_h; i++) |
| for (unsigned j = 0; j < out_w; j++) |
| out_data[i * out_stride + j] = data[i * stride + j] << 2; |
| } |
| |
| static void copy_10b_buffer(uint16_t *data, int stride, unsigned in_w, |
| unsigned in_h, uint16_t *out_data, int out_stride, |
| unsigned out_w, unsigned out_h) { |
| if (in_w != out_w || in_h != out_h) assert(0); |
| |
| for (unsigned i = 0; i < out_h; i++) { |
| memcpy(out_data, data, in_w * sizeof(uint16_t)); |
| data += stride; |
| out_data += out_stride; |
| } |
| } |
| |
| static void anti_dithering_filter(uint16_t *data, int stride, int width, |
| int height) { |
| for (int i = 0; i < height - 1; i++) { |
| for (int j = 0; j < width - 1; j++) { |
| data[i * stride + j] = |
| (data[i * stride + j] + data[i * stride + j + 1] + |
| data[(i + 1) * stride + j] + data[(i + 1) * stride + j + 1]) >> |
| 2; |
| } |
| |
| // Last column |
| int j = width - 1; |
| data[i * stride + j] = |
| (data[i * stride + j] + data[(i + 1) * stride + j]) >> 1; |
| } |
| |
| // Last row |
| int i = height - 1; |
| for (int j = 0; j < width - 1; j++) { |
| data[i * stride + j] = |
| (data[i * stride + j] + data[i * stride + j + 1]) >> 1; |
| } |
| } |
| |
| void cambi_preprocessing(uint16_t *data, int stride, int in_w, int in_h, |
| int bit_depth, uint16_t *out_data, int out_stride, |
| int out_w, int out_h) { |
| if (bit_depth == 8) { |
| copy_8b_to_10b_buffer(data, stride, in_w, in_h, out_data, out_stride, out_w, |
| out_h); |
| anti_dithering_filter(out_data, out_stride, out_w, out_h); |
| } else { |
| copy_10b_buffer(data, stride, in_w, in_h, out_data, out_stride, out_w, |
| out_h); |
| } |
| } |
| |
| /* CAMBI processing functions */ |
| static inline uint16_t get_pixels_in_window(uint16_t window_length) { |
| uint16_t odd_length = 2 * (window_length >> 1) + 1; |
| return odd_length * odd_length; |
| } |
| |
| static inline uint16_t adjust_cambi_window_size(uint16_t size, unsigned width, |
| unsigned height) { |
| // Adjustment with (input_width + input_height) / (4K_WIDTH + 4K_HEIGHT) |
| return CLAMP(((size * (width + height)) / 375) >> 4, 5, |
| CAMBI_DEFAULT_WINDOW_SIZE); |
| } |
| |
| void set_cambi_window(Av2BandDetectInfo *const dbi) { |
| dbi->window_size = CAMBI_DEFAULT_WINDOW_SIZE; |
| dbi->window_size = |
| adjust_cambi_window_size(dbi->window_size, dbi->stride, dbi->height); |
| dbi->pixels_in_window = get_pixels_in_window(dbi->window_size); |
| } |
| |
| static void cambi_decimate(uint16_t *data, int stride, unsigned width, |
| unsigned height) { |
| for (unsigned i = 0; i < height; i++) { |
| for (unsigned j = 0; j < width; j++) { |
| data[i * stride + j] = data[(i << 1) * stride + (j << 1)]; |
| } |
| } |
| } |
| |
| /* Spatial mask functions */ |
| static inline uint16_t ceil_log2(uint32_t num) { |
| if (num == 0) return 0; |
| |
| uint32_t tmp = num - 1; |
| uint16_t shift = 0; |
| while (tmp > 0) { |
| tmp >>= 1; |
| shift += 1; |
| } |
| return shift; |
| } |
| |
| uint16_t cambi_get_mask_index(int input_width, int input_height, |
| uint16_t filter_size) { |
| uint32_t shifted_wh = (input_width >> 6) * (input_height >> 6); |
| return (filter_size * filter_size + 3 * (ceil_log2(shifted_wh) - 11) - 1) >> |
| 1; |
| } |
| |
| static inline bool get_derivative_data(const uint16_t *data, int i, int j, |
| int str) { |
| return (data[i * str + j] == data[(i + 1) * str + j]) && |
| (data[i * str + j] == data[i * str + j + 1]); |
| } |
| |
| /* |
| * This function calculates the horizontal and vertical derivatives of the image |
| * using 2x1 and 1x2 kernels. |
| */ |
| static void get_spatial_mask_for_index(Av2BandDetectInfo *dbi, |
| uint16_t mask_index, |
| uint16_t filter_size, int width, |
| int height) { |
| const uint16_t pad_size = filter_size >> 1; |
| uint16_t *image_data = dbi->frame; |
| uint16_t *mask_data = dbi->mask; |
| const int stride = dbi->stride; |
| uint32_t *dp = dbi->buffers.mask_dp; |
| |
| const int dp_width = width + 2 * pad_size + 1; |
| const int dp_height = 2 * pad_size + 2; |
| memset(dp, 0, dp_width * dp_height * sizeof(uint32_t)); |
| |
| // Initial computation: fill dp except for the last row |
| for (int i = 0; i < pad_size; i++) { |
| int cur_row_start = (i + pad_size + 1) * dp_width; |
| int prev_row_start = cur_row_start - dp_width; |
| int curr_col = pad_size + 1; |
| for (int j = 0; j < width + pad_size; j++, curr_col++) { |
| int value = (i < height - 1 && j < width - 1 |
| ? get_derivative_data(image_data, i, j, stride) |
| : 0); |
| dp[cur_row_start + curr_col] = value + dp[prev_row_start + curr_col] + |
| dp[cur_row_start + curr_col - 1] - |
| dp[prev_row_start + curr_col - 1]; |
| } |
| } |
| |
| // Start from the last row in the dp matrix |
| int curr_row = dp_height - 1; |
| int prev_row = dp_height - 2; |
| int bottom = 2 * pad_size; |
| for (int i = pad_size; i < height + pad_size; i++) { |
| // First compute the values of dp for curr_row |
| int curr_col = pad_size + 1; |
| for (int j = 0; j < width + pad_size; j++, curr_col++) { |
| int value = (i < height - 1 && j < width - 1 |
| ? get_derivative_data(image_data, i, j, stride) |
| : 0); |
| 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]; |
| } |
| prev_row = curr_row; |
| curr_row = curr_row == (dp_height - 1) ? 0 : curr_row + 1; |
| bottom = bottom == (dp_height - 1) ? 0 : bottom + 1; |
| |
| // Then use the values to compute the square sum for the curr computed row. |
| int right = 2 * pad_size + 1; |
| int top = curr_row; |
| for (int left = 0; left < width; left++, right++) { |
| int result = dp[bottom * dp_width + right] - |
| dp[bottom * dp_width + left] - dp[top * dp_width + right] + |
| dp[top * dp_width + left]; |
| mask_data[(i - pad_size) * stride + left] = (result > mask_index); |
| } |
| } |
| } |
| |
| void cambi_get_spatial_mask(Av2BandDetectInfo *dbi, int width, int height) { |
| const uint16_t filter_size = CAMBI_MASK_FILTER_SIZE; |
| const uint16_t mask_index = cambi_get_mask_index(width, height, filter_size); |
| get_spatial_mask_for_index(dbi, mask_index, filter_size, width, height); |
| } |
| |
| static inline uint16_t min3(uint16_t a, uint16_t b, uint16_t c) { |
| if (a <= b && a <= c) return a; |
| if (b <= c) return b; |
| return c; |
| } |
| |
| static inline uint16_t mode3(uint16_t a, uint16_t b, uint16_t c) { |
| if (a == b || a == c) return a; |
| if (b == c) return b; |
| return min3(a, b, c); |
| } |
| |
| void cambi_filter_mode(Av2BandDetectInfo *dbi, int width, int height) { |
| uint16_t *data = dbi->frame; |
| const ptrdiff_t stride = dbi->stride; |
| uint16_t *buffer = dbi->buffers.filter_mode_buffer; |
| |
| 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]); |
| } |
| buffer[curr_line * width + width - 1] = data[i * stride + width - 1]; |
| |
| if (i > 1) { |
| for (int j = 0; j < width; j++) { |
| data[(i - 1) * stride + j] = |
| mode3(buffer[0 * width + j], buffer[1 * width + j], |
| buffer[2 * width + j]); |
| } |
| } |
| } |
| } |
| |
| static inline void increment_range(uint16_t *arr, int left, int right) { |
| for (int i = left; i < right; i++) { |
| arr[i]++; |
| } |
| } |
| |
| static inline void decrement_range(uint16_t *arr, int left, int right) { |
| for (int i = left; i < right; i++) { |
| arr[i]--; |
| } |
| } |
| |
| static inline void cambi_histogram_sub_edge(uint16_t *histograms, |
| uint16_t *image, uint16_t *mask, |
| int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| const uint16_t num_diffs) { |
| const long int index = (i - pad_size - 1) * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| decrement_range(&histograms[val * width], AVMMAX(j - pad_size, 0), |
| AVMMIN(j + pad_size + 1, width)); |
| } |
| } |
| |
| static inline void cambi_histogram_sub(uint16_t *histograms, |
| const uint16_t *image, |
| const uint16_t *mask, int i, int j, |
| int width, ptrdiff_t stride, |
| uint16_t pad_size, uint16_t num_diffs) { |
| const long int index = (i - pad_size - 1) * stride + j; |
| if (mask[index]) { |
| uint16_t val = image[index] + num_diffs; |
| decrement_range(&histograms[val * width], j - pad_size, j + pad_size + 1); |
| } |
| } |
| |
| static inline void cambi_histogram_add_edge(uint16_t *histograms, |
| uint16_t *image, uint16_t *mask, |
| int i, int j, int width, |
| ptrdiff_t stride, uint16_t pad_size, |
| const uint16_t num_diffs) { |
| const long int index = (i + pad_size) * stride + j; |
| if (mask[index]) { |
| const uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], AVMMAX(j - pad_size, 0), |
| AVMMIN(j + pad_size + 1, width)); |
| } |
| } |
| |
| static inline void cambi_histogram_add(uint16_t *histograms, |
| const uint16_t *image, |
| const uint16_t *mask, int i, int j, |
| int width, ptrdiff_t stride, |
| uint16_t pad_size, uint16_t num_diffs) { |
| const long int index = (i + pad_size) * stride + j; |
| if (mask[index]) { |
| const uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], j - pad_size, j + pad_size + 1); |
| } |
| } |
| |
| static inline void cambi_histogram_add_edge_first_pass( |
| uint16_t *histograms, uint16_t *image, uint16_t *mask, int i, int j, |
| int width, ptrdiff_t stride, uint16_t pad_size, const uint16_t num_diffs) { |
| const long int index = i * stride + j; |
| if (mask[index]) { |
| const uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], AVMMAX(j - pad_size, 0), |
| AVMMIN(j + pad_size + 1, width)); |
| } |
| } |
| |
| static inline void cambi_histogram_add_first_pass( |
| uint16_t *histograms, uint16_t *image, uint16_t *mask, int i, int j, |
| int width, ptrdiff_t stride, uint16_t pad_size, const uint16_t num_diffs) { |
| const long int index = i * stride + j; |
| if (mask[index]) { |
| const uint16_t val = image[index] + num_diffs; |
| increment_range(&histograms[val * width], j - pad_size, j + pad_size + 1); |
| } |
| } |
| |
| static float c_value_pixel(const uint16_t *histograms, uint16_t value, |
| const int *diff_weights, uint16_t num_diffs, |
| const uint16_t *tvi_thresholds, int histogram_col, |
| int histogram_width) { |
| const uint16_t p_0 = histograms[value * histogram_width + histogram_col]; |
| float val; |
| float c_value = (float)0.0; |
| for (uint16_t d = 0; d < num_diffs; d++) { |
| if (value <= tvi_thresholds[d]) { |
| const uint16_t p_1 = |
| histograms[(value + d + 1) * histogram_width + histogram_col]; |
| const uint16_t p_2 = |
| histograms[(value - d - 1) * histogram_width + histogram_col]; |
| if (p_1 > p_2) { |
| val = (float)(diff_weights[d] * p_0 * p_1) / (float)(p_1 + p_0); |
| } else { |
| val = (float)(diff_weights[d] * p_0 * p_2) / (float)(p_2 + p_0); |
| } |
| |
| if (val > c_value) { |
| c_value = val; |
| } |
| } |
| } |
| return c_value; |
| } |
| |
| static inline void calculate_c_values_row(float *c_values, uint16_t *histograms, |
| uint16_t *image, const uint16_t *mask, |
| int row, int width, ptrdiff_t stride, |
| const uint16_t num_diffs, |
| const uint16_t *tvi_for_diff, |
| Av2BandDetectInfo *dbi) { |
| for (int col = 0; col < width; col++) { |
| if (mask[row * stride + col]) { |
| c_values[row * width + col] = c_value_pixel( |
| histograms, image[row * stride + col] + num_diffs, dbi->diffs_weights, |
| num_diffs, tvi_for_diff, col, width); |
| } |
| } |
| } |
| |
| static void calculate_c_values(Av2BandDetectInfo *dbi, int width, int height) { |
| uint16_t *image = dbi->frame; |
| uint16_t *mask = dbi->mask; |
| const ptrdiff_t stride = dbi->stride; |
| float *c_values = dbi->buffers.c_values; |
| uint16_t *histograms = dbi->buffers.c_values_histograms; |
| const uint16_t window_size = dbi->window_size; |
| const uint16_t num_diffs = dbi->num_diffs; |
| uint16_t *tvi_for_diff = dbi->tvi_for_diff; |
| const uint16_t pad_size = window_size >> 1; |
| const uint16_t num_bins = 1024 + 2 * num_diffs; |
| |
| memset(c_values, 0.0, sizeof(float) * width * height); |
| |
| // Use a histogram for each pixel in width |
| // histograms[i * width + j] accesses the j'th histogram, i'th value |
| // This is done for cache optimization reasons |
| memset(histograms, 0, width * num_bins * sizeof(uint16_t)); |
| |
| // First pass: first pad_size rows |
| for (int i = 0; i < pad_size; i++) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_add_edge_first_pass(histograms, image, mask, i, j, width, |
| stride, pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_add_first_pass(histograms, image, mask, i, j, width, |
| stride, pad_size, num_diffs); |
| } |
| for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_add_edge_first_pass(histograms, image, mask, i, j, width, |
| stride, pad_size, num_diffs); |
| } |
| } |
| |
| // Iterate over all rows, unrolled into 3 loops to avoid conditions |
| for (int i = 0; i < pad_size + 1; i++) { |
| if (i + pad_size < height) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_add(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| } |
| calculate_c_values_row(c_values, histograms, image, mask, i, width, stride, |
| num_diffs, tvi_for_diff, dbi); |
| } |
| |
| for (int i = pad_size + 1; i < height - pad_size; i++) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_sub(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| cambi_histogram_add(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| cambi_histogram_add_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| calculate_c_values_row(c_values, histograms, image, mask, i, width, stride, |
| num_diffs, tvi_for_diff, dbi); |
| } |
| |
| for (int i = height - pad_size; i < height; i++) { |
| if (i - pad_size - 1 >= 0) { |
| for (int j = 0; j < pad_size; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = pad_size; j < width - pad_size - 1; j++) { |
| cambi_histogram_sub(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| for (int j = AVMMAX(width - pad_size - 1, pad_size); j < width; j++) { |
| cambi_histogram_sub_edge(histograms, image, mask, i, j, width, stride, |
| pad_size, num_diffs); |
| } |
| } |
| calculate_c_values_row(c_values, histograms, image, mask, i, width, stride, |
| num_diffs, tvi_for_diff, dbi); |
| } |
| } |
| |
| static double average_topk_elements(const float *arr, int topk_elements) { |
| double sum = 0; |
| for (int i = 0; i < topk_elements; i++) sum += arr[i]; |
| |
| return (double)sum / topk_elements; |
| } |
| |
| static void quick_select(float *arr, int n, int k) { |
| if (n == k) return; |
| int left = 0; |
| int right = n - 1; |
| while (left < right) { |
| float pivot = arr[k]; |
| int i = left; |
| int j = right; |
| do { |
| while (arr[i] > pivot) { |
| i++; |
| } |
| while (arr[j] < pivot) { |
| j--; |
| } |
| if (i <= j) { |
| SWAP_FLOATS(arr[i], arr[j]); |
| i++; |
| j--; |
| } |
| } while (i <= j); |
| if (j < k) { |
| left = i; |
| } |
| if (k < i) { |
| right = j; |
| } |
| } |
| } |
| |
| static double spatial_pooling(float *c_values, double topk, int width, |
| int height) { |
| const int num_elements = height * width; |
| const int topk_num_elements = |
| CLAMP((int)(topk * num_elements), 1, num_elements); |
| quick_select(c_values, num_elements, topk_num_elements); |
| return average_topk_elements(c_values, topk_num_elements); |
| } |
| |
| // Inner product weighting scores for each scale |
| static inline double weight_scores_per_scale(const int *scale_weights, |
| const double *scores_per_scale, |
| uint16_t normalization) { |
| double score = 0.0; |
| for (unsigned scale = 0; scale < CAMBI_NUM_SCALES; scale++) { |
| score += scores_per_scale[scale] * scale_weights[scale]; |
| } |
| return score / normalization; |
| } |
| |
| double cambi_score(Av2BandDetectInfo *dbi, int frame_width, int frame_height) { |
| double scores_per_scale[CAMBI_NUM_SCALES]; |
| int scaled_width = frame_width; |
| int scaled_height = frame_height; |
| |
| for (unsigned scale = 0; scale < CAMBI_NUM_SCALES; scale++) { |
| if (scale > 0) { |
| scaled_width = (scaled_width + 1) >> 1; |
| scaled_height = (scaled_height + 1) >> 1; |
| cambi_decimate(dbi->frame, dbi->stride, scaled_width, scaled_height); |
| cambi_decimate(dbi->mask, dbi->stride, scaled_width, scaled_height); |
| } else { |
| cambi_get_spatial_mask(dbi, scaled_width, scaled_height); |
| } |
| cambi_filter_mode(dbi, scaled_width, scaled_height); |
| calculate_c_values(dbi, scaled_width, scaled_height); |
| scores_per_scale[scale] = spatial_pooling(dbi->buffers.c_values, dbi->topk, |
| scaled_width, scaled_height); |
| } |
| |
| return weight_scores_per_scale(dbi->scale_weights, scores_per_scale, |
| dbi->pixels_in_window); |
| } |
| |
| double avm_compute_cambi(const YV12_BUFFER_CONFIG *frame, |
| Av2BandDetectInfo *dbi, MACROBLOCKD *xd) { |
| av2_setup_dst_planes(xd->plane, frame, 0, 0, 0, 1, NULL); |
| struct buf_2d pre_buf = xd->plane[0].dst; |
| const int src_stride = xd->plane[0].dst.stride; |
| const int frame_width = pre_buf.width; |
| const int frame_height = pre_buf.height; |
| const int bit_depth = xd->bd; |
| uint16_t *src16 = pre_buf.buf; |
| |
| set_cambi_window(dbi); |
| cambi_preprocessing(src16, src_stride, frame_width, frame_height, bit_depth, |
| dbi->frame, dbi->stride, frame_width, frame_height); |
| return cambi_score(dbi, frame_width, frame_height); |
| } |
| |
| // TODO(Joel): move CAMBI initialization & buffers |
| static const int cambi_scale_weights[CAMBI_NUM_SCALES] = { 16, 8, 4, 2, 1 }; |
| static const int cambi_contrast_weights[8] = { 1, 2, 3, 4, 4, 5, 5, 6 }; |
| |
| void set_contrast_arrays_cambi(Av2BandDetectInfo *const dbi) { |
| const int num_diffs = dbi->num_diffs; |
| |
| dbi->diffs_weights = avm_malloc(sizeof(dbi->diffs_weights) * num_diffs); |
| for (int d = 0; d < num_diffs; d++) { |
| dbi->diffs_weights[d] = cambi_contrast_weights[d]; |
| } |
| |
| for (int scale = 0; scale < CAMBI_NUM_SCALES; scale++) { |
| dbi->scale_weights[scale] = cambi_scale_weights[scale]; |
| } |
| } |
| |
| void set_tvi_per_contrast(Av2BandDetectInfo *const dbi, int bitdepth) { |
| (void)bitdepth; |
| const int num_diffs = dbi->num_diffs; |
| dbi->tvi_for_diff = avm_malloc(sizeof(dbi->tvi_for_diff) * num_diffs); |
| dbi->tvi_threshold = CAMBI_TVI; |
| |
| // Todo(Joel): Set TVI values from encoder input parameters |
| for (int d = 0; d < num_diffs; d++) { |
| dbi->tvi_for_diff[d] = -1; |
| } |
| } |
| |
| int avm_band_detection_init(Av2BandDetectInfo *const dbi, const int frame_width, |
| const int frame_height, const int bit_depth) { |
| if (frame_width < CAMBI_MIN_WIDTH || frame_height > CAMBI_MAX_WIDTH || |
| !(bit_depth == 8 || bit_depth == 10)) { |
| dbi->band_detected = 0; |
| dbi->do_band_detection = 0; |
| return dbi->do_band_detection; |
| } |
| |
| dbi->do_band_detection = 1; |
| dbi->topk = CAMBI_DEFAULT_TOPK_POOLING; |
| dbi->stride = frame_width; |
| dbi->height = frame_height; |
| dbi->frame = avm_malloc(sizeof(*dbi->frame) * frame_height * dbi->stride); |
| |
| dbi->max_log_contrast = CAMBI_DEFAULT_MAX_LOG_CONTRAST; |
| dbi->num_diffs = 1 << dbi->max_log_contrast; |
| set_contrast_arrays_cambi(dbi); |
| set_tvi_per_contrast(dbi, 10); |
| dbi->num_bins = (1 << 10) + 2 * dbi->num_diffs; |
| |
| dbi->buffers.filter_mode_buffer = |
| avm_malloc(3 * frame_width * sizeof(uint16_t)); |
| |
| int pad_size = CAMBI_MASK_FILTER_SIZE >> 1; |
| int dp_width = frame_width + 2 * pad_size + 1; |
| int dp_height = 2 * pad_size + 2; |
| dbi->buffers.mask_dp = avm_malloc(dp_height * dp_width * sizeof(uint32_t)); |
| |
| dbi->mask = avm_malloc(sizeof(*dbi->mask) * frame_height * dbi->stride); |
| dbi->buffers.c_values = |
| avm_malloc(sizeof(float) * frame_height * dbi->stride); |
| dbi->buffers.c_values_histograms = |
| avm_malloc(frame_width * dbi->num_bins * sizeof(uint16_t)); |
| |
| return dbi->do_band_detection; |
| } |
| |
| void avm_band_detection_close(Av2BandDetectInfo *const dbi) { |
| if (!dbi->do_band_detection) { |
| return; |
| } |
| avm_free(dbi->frame); |
| avm_free(dbi->mask); |
| |
| // set_tvi_per_contrast |
| avm_free(dbi->tvi_for_diff); |
| |
| avm_free(dbi->buffers.filter_mode_buffer); |
| avm_free(dbi->buffers.mask_dp); |
| |
| avm_free(dbi->diffs_weights); |
| avm_free(dbi->buffers.c_values); |
| avm_free(dbi->buffers.c_values_histograms); |
| } |
| |
| /*!\brief Assess banding via CAMBI |
| * |
| * Searches for presence of banding computing CAMBI on refernce and distorted |
| * |
| * \param[in] frame Compressed frame buffer |
| * \param[in] ref Source frame buffer |
| * \param[in,out] cm Pointer to top level common structure |
| * \param[in] dbi Banding detection information structure |
| * \param[out] band_metadata Banding hints metadata structure to be filled |
| |
| * |
| * \return Nothing is returned. Instead, presence of banding is stored |
| */ |
| void avm_band_detection(const YV12_BUFFER_CONFIG *frame, |
| const YV12_BUFFER_CONFIG *ref, |
| Av2BandDetectInfo *const dbi, MACROBLOCKD *xd, |
| avm_banding_hints_metadata_t *band_metadata) { |
| const double cambi_ref = avm_compute_cambi(ref, dbi, xd); |
| const double cambi_enc = avm_compute_cambi(frame, dbi, xd); |
| |
| const int bit_depth = xd->bd; |
| const double diff_threshold = |
| bit_depth == 8 ? CAMBI_DIFF_THRESHOLD_8b : CAMBI_DIFF_THRESHOLD_10b; |
| |
| dbi->band_detected = (cambi_enc - cambi_ref >= diff_threshold); |
| |
| const double src_threshold = |
| bit_depth == 8 ? CAMBI_SOURCE_THRESHOLD_8b : CAMBI_SOURCE_THRESHOLD_10b; |
| |
| // Initialize and populate the band metadata structure |
| if (band_metadata) { |
| memset(band_metadata, 0, sizeof(avm_banding_hints_metadata_t)); |
| band_metadata->coding_banding_present_flag = dbi->band_detected; |
| band_metadata->source_banding_present_flag = (cambi_ref > src_threshold); |
| // For now, set banding_hints_flag to 0 (no detailed hints) |
| // This can be extended in the future to include more detailed information |
| band_metadata->banding_hints_flag = 0; |
| } |
| } |