Add debanding postloop filter
diff --git a/Sample.cfg b/Sample.cfg index 6835079..b64f8aa 100644 --- a/Sample.cfg +++ b/Sample.cfg
@@ -35,4 +35,5 @@ enable_smooth_interintra = 1 # enable smooth inter/intra enable_cdef = 1 # enable CDEF filter enable_restoration = 1 # enable Loop Restoration Filter +enable_deband = 1 # enable debanding disable_trellis_quant = 0 # enable trellis quantization \ No newline at end of file
diff --git a/aom/aom_encoder.h b/aom/aom_encoder.h index d17724e..0d8ad02 100644 --- a/aom/aom_encoder.h +++ b/aom/aom_encoder.h
@@ -434,6 +434,12 @@ */ unsigned int enable_pef; #endif // CONFIG_PEF +#if CONFIG_DEBAND + /*!\brief enable debanding algorithm + * + */ + unsigned int enable_deband; +#endif /*!\brief enable OBMC * */
diff --git a/apps/aomenc.c b/apps/aomenc.c index d61921b..792950c 100644 --- a/apps/aomenc.c +++ b/apps/aomenc.c
@@ -481,6 +481,9 @@ #if CONFIG_PEF &g_av1_codec_arg_defs.enable_pef, #endif // CONFIG_PEF +#if CONFIG_DEBAND + &g_av1_codec_arg_defs.enable_deband, +#endif #if CONFIG_IBC_SR_EXT &g_av1_codec_arg_defs.enable_intrabc_ext, #endif // CONFIG_IBC_SR_EXT @@ -700,6 +703,9 @@ #if CONFIG_WIENER_NONSEP config->enable_wiener_nonsep = 1; #endif // CONFIG_WIENER_NONSEP +#if CONFIG_DEBAND + config->enable_deband = 1; +#endif #if CONFIG_CCSO config->enable_ccso = 1; #endif @@ -1658,6 +1664,9 @@ #if CONFIG_CCSO "CCSO (%d), " #endif +#if CONFIG_DEBAND + "Deband (%d), " +#endif #if CONFIG_PC_WIENER && CONFIG_WIENER_NONSEP "LoopRestoration (%d: [%d/%d/%d/%d])\n", #elif CONFIG_PC_WIENER || CONFIG_WIENER_NONSEP @@ -1669,6 +1678,9 @@ #if CONFIG_CCSO encoder_cfg->enable_ccso, #endif +#if CONFIG_DEBAND + encoder_cfg->enable_deband, +#endif encoder_cfg->enable_restoration, encoder_cfg->enable_wiener, encoder_cfg->enable_sgrproj #if CONFIG_PC_WIENER && CONFIG_WIENER_NONSEP
diff --git a/av1/arg_defs.c b/av1/arg_defs.c index c7b357e..1737dde 100644 --- a/av1/arg_defs.c +++ b/av1/arg_defs.c
@@ -360,6 +360,10 @@ "Enable prediction enhancement filter (0: false " "1: true)"), #endif // CONFIG_PEF +#if CONFIG_DEBAND + .enable_deband = ARG_DEF(NULL, "enable-deband", 1, + "Enable debanding (0: false 1: true)"), +#endif .disable_ml_partition_speed_features = ARG_DEF(NULL, "disable-ml-partition-speed-features", 1, "Disable ML partition speed features "
diff --git a/av1/arg_defs.h b/av1/arg_defs.h index 0d6389a..5ef4172 100644 --- a/av1/arg_defs.h +++ b/av1/arg_defs.h
@@ -145,6 +145,9 @@ #if CONFIG_PEF arg_def_t enable_pef; #endif // CONFIG_PEF +#if CONFIG_DEBAND + arg_def_t enable_deband; +#endif arg_def_t disable_ml_partition_speed_features; #if CONFIG_EXT_RECUR_PARTITIONS arg_def_t erp_pruning_level;
diff --git a/av1/av1.cmake b/av1/av1.cmake index 409084b..9346aab 100644 --- a/av1/av1.cmake +++ b/av1/av1.cmake
@@ -44,6 +44,8 @@ "${AOM_ROOT}/av1/common/common_data.h" "${AOM_ROOT}/av1/common/convolve.c" "${AOM_ROOT}/av1/common/convolve.h" + "${AOM_ROOT}/av1/common/debanding.c" + "${AOM_ROOT}/av1/common/debanding.h" "${AOM_ROOT}/av1/common/debugmodes.c" "${AOM_ROOT}/av1/common/entropy.c" "${AOM_ROOT}/av1/common/entropy.h" @@ -238,6 +240,8 @@ "${AOM_ROOT}/av1/encoder/pass2_strategy.c" "${AOM_ROOT}/av1/encoder/pickcdef.c" "${AOM_ROOT}/av1/encoder/pickcdef.h" + "${AOM_ROOT}/av1/encoder/pickdeband.c" + "${AOM_ROOT}/av1/encoder/pickdeband.h" "${AOM_ROOT}/av1/encoder/picklpf.c" "${AOM_ROOT}/av1/encoder/picklpf.h" "${AOM_ROOT}/av1/encoder/pickrst.c"
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c index 987f0ed..69d2249 100644 --- a/av1/av1_cx_iface.c +++ b/av1/av1_cx_iface.c
@@ -82,6 +82,9 @@ #if CONFIG_PEF unsigned int enable_pef; #endif // CONFIG_PEF +#if CONFIG_DEBAND + unsigned int enable_deband; +#endif unsigned int force_video_mode; unsigned int enable_obmc; unsigned int enable_trellis_quant; @@ -425,6 +428,9 @@ #if CONFIG_PEF 1, // enable_pef #endif // CONFIG_PEF +#if CONFIG_DEBAND + 1, // enable_deband +#endif 0, // force_video_mode 1, // enable_obmc 3, // enable_trellis_quant @@ -960,6 +966,9 @@ #if CONFIG_PEF cfg->enable_pef = extra_cfg->enable_pef; #endif // CONFIG_PEF +#if CONFIG_DEBAND + cfg->enable_deband = extra_cfg->enable_deband; +#endif cfg->superblock_size = (extra_cfg->superblock_size == AOM_SUPERBLOCK_SIZE_64X64) ? 64 : (extra_cfg->superblock_size == AOM_SUPERBLOCK_SIZE_128X128) ? 128 @@ -1089,10 +1098,14 @@ #if CONFIG_PEF extra_cfg->enable_pef = cfg->enable_pef; #endif // CONFIG_PEF +#if CONFIG_DEBAND + extra_cfg->enable_deband = cfg->enable_deband; +#endif extra_cfg->superblock_size = (cfg->superblock_size == 64) ? AOM_SUPERBLOCK_SIZE_64X64 : (cfg->superblock_size == 128) ? AOM_SUPERBLOCK_SIZE_128X128 : AOM_SUPERBLOCK_SIZE_DYNAMIC; + extra_cfg->enable_warped_motion = cfg->enable_warped_motion; extra_cfg->enable_diff_wtd_comp = cfg->enable_diff_wtd_comp; #if CONFIG_OPTFLOW_REFINEMENT @@ -1423,6 +1436,9 @@ } } #endif // CONFIG_PEF +#if CONFIG_DEBAND + tool_cfg->enable_deband = extra_cfg->enable_deband; +#endif #if CONFIG_ADAPTIVE_MVD tool_cfg->enable_adaptive_mvd = extra_cfg->enable_adaptive_mvd; #endif // CONFIG_ADAPTIVE_MVD @@ -2142,6 +2158,7 @@ return update_extra_cfg(ctx, &extra_cfg); } + static aom_codec_err_t ctrl_set_enable_restoration(aom_codec_alg_priv_t *ctx, va_list args) { struct av1_extracfg extra_cfg = ctx->extra_cfg; @@ -3785,6 +3802,11 @@ err_string)) { extra_cfg.enable_pef = arg_parse_int_helper(&arg, err_string); #endif // CONFIG_PEF +#if CONFIG_DEBAND + } else if (arg_match_helper(&arg, &g_av1_codec_arg_defs.enable_deband, argv, + err_string)) { + extra_cfg.enable_deband = arg_parse_int_helper(&arg, err_string); +#endif } else if (arg_match_helper(&arg, &g_av1_codec_arg_defs.force_video_mode, argv, err_string)) { extra_cfg.force_video_mode = arg_parse_uint_helper(&arg, err_string); @@ -4477,6 +4499,9 @@ #if CONFIG_PEF 1, #endif // CONFIG_PEF +#if CONFIG_DEBAND + 1, +#endif 1, 1, #if CONFIG_EXTENDED_WARP_PREDICTION 1, 1, 1,
diff --git a/av1/av1_dx_iface.c b/av1/av1_dx_iface.c index 87f1f37..f5c8899 100644 --- a/av1/av1_dx_iface.c +++ b/av1/av1_dx_iface.c
@@ -34,6 +34,10 @@ #include "av1/decoder/decodeframe.h" #include "av1/decoder/obu.h" +#if CONFIG_DEBAND +#include "av1/common/debanding.h" +#endif + #include "av1/av1_iface_common.h" struct aom_codec_alg_priv { @@ -63,6 +67,11 @@ aom_image_t image_with_grain; aom_codec_frame_buffer_t grain_image_frame_buffers[MAX_NUM_SPATIAL_LAYERS]; size_t num_grain_image_frame_buffers; +#if CONFIG_DEBAND + aom_image_t image_debanded; + aom_codec_frame_buffer_t image_debanded_frame_buffers[MAX_NUM_SPATIAL_LAYERS]; + size_t num_image_debanded_frame_buffers; +#endif int need_resync; // wait for key/intra-only frame // BufferPool that holds all reference frames. Shared by all the FrameWorkers. BufferPool *buffer_pool; @@ -99,6 +108,9 @@ ctx->config.dec = &priv->cfg; } priv->num_grain_image_frame_buffers = 0; +#if CONFIG_DEBAND + priv->num_image_debanded_frame_buffers = 0; +#endif // Turn row_mt on by default. priv->row_mt = 1; @@ -139,6 +151,12 @@ } if (ctx->buffer_pool) { +#if CONFIG_DEBAND + for (size_t i = 0; i < ctx->num_image_debanded_frame_buffers; i++) { + ctx->buffer_pool->release_fb_cb(ctx->buffer_pool->cb_priv, + &ctx->image_debanded_frame_buffers[i]); + } +#endif for (size_t i = 0; i < ctx->num_grain_image_frame_buffers; i++) { ctx->buffer_pool->release_fb_cb(ctx->buffer_pool->cb_priv, &ctx->grain_image_frame_buffers[i]); @@ -152,6 +170,9 @@ aom_free(ctx->buffer_pool); aom_img_free(&ctx->img); aom_img_free(&ctx->image_with_grain); +#if CONFIG_DEBAND + aom_img_free(&ctx->image_debanded); +#endif aom_free(ctx); return AOM_CODEC_OK; } @@ -629,6 +650,15 @@ #endif // CONFIG_OUTPUT_FRAME_BASED_ON_ORDER_HINT pbi->num_output_frames = 0; unlock_buffer_pool(pool); +#if CONFIG_DEBAND + for (size_t j = 0; j < ctx->num_image_debanded_frame_buffers; j++) { + pool->release_fb_cb(pool->cb_priv, &ctx->image_debanded_frame_buffers[j]); + ctx->image_debanded_frame_buffers[j].data = NULL; + ctx->image_debanded_frame_buffers[j].size = 0; + ctx->image_debanded_frame_buffers[j].priv = NULL; + } + ctx->num_image_debanded_frame_buffers = 0; +#endif for (size_t j = 0; j < ctx->num_grain_image_frame_buffers; j++) { pool->release_fb_cb(pool->cb_priv, &ctx->grain_image_frame_buffers[j]); ctx->grain_image_frame_buffers[j].data = NULL; @@ -716,6 +746,145 @@ return param->fb->data; } +#if CONFIG_DEBAND +static void copy_rect(uint8_t *src, int src_stride, uint8_t *dst, + int dst_stride, int width, int height, + int use_high_bit_depth) { + int hbd_coeff = use_high_bit_depth ? 2 : 1; + while (height) { + memcpy(dst, src, width * sizeof(uint8_t) * hbd_coeff); + src += src_stride; + dst += dst_stride; + --height; + } + return; +} + +int copy_image_for_deband(const aom_image_t *src, + aom_image_t *dst) { + int height, width; + int use_high_bit_depth = 0; + int chroma_subsamp_x = 0; + int chroma_subsamp_y = 0; + + switch (src->fmt) { + case AOM_IMG_FMT_AOMI420: + case AOM_IMG_FMT_I420: + use_high_bit_depth = 0; + chroma_subsamp_x = 1; + chroma_subsamp_y = 1; + break; + case AOM_IMG_FMT_I42016: + use_high_bit_depth = 1; + chroma_subsamp_x = 1; + chroma_subsamp_y = 1; + break; + // case AOM_IMG_FMT_444A: + case AOM_IMG_FMT_I444: + use_high_bit_depth = 0; + chroma_subsamp_x = 0; + chroma_subsamp_y = 0; + break; + case AOM_IMG_FMT_I44416: + use_high_bit_depth = 1; + chroma_subsamp_x = 0; + chroma_subsamp_y = 0; + break; + case AOM_IMG_FMT_I422: + use_high_bit_depth = 0; + chroma_subsamp_x = 1; + chroma_subsamp_y = 0; + break; + case AOM_IMG_FMT_I42216: + use_high_bit_depth = 1; + chroma_subsamp_x = 1; + chroma_subsamp_y = 0; + break; + default: // unknown input format + fprintf(stderr, "Deband error: input format is not supported!\n"); + return -1; + } + + dst->fmt = src->fmt; + dst->bit_depth = src->bit_depth; + + dst->w = src->w; + dst->h = src->h; + dst->r_w = src->r_w; + dst->r_h = src->r_h; + dst->d_w = src->d_w; + dst->d_h = src->d_h; + + dst->cp = src->cp; + dst->tc = src->tc; + dst->mc = src->mc; + + dst->monochrome = src->monochrome; + dst->csp = src->csp; + dst->range = src->range; + + dst->x_chroma_shift = src->x_chroma_shift; + dst->y_chroma_shift = src->y_chroma_shift; + + dst->temporal_id = src->temporal_id; + dst->spatial_id = src->spatial_id; + + width = src->d_w % 2 ? src->d_w + 1 : src->d_w; + height = src->d_h % 2 ? src->d_h + 1 : src->d_h; + + copy_rect(src->planes[AOM_PLANE_Y], src->stride[AOM_PLANE_Y], + dst->planes[AOM_PLANE_Y], dst->stride[AOM_PLANE_Y], src->d_w, + src->d_h, use_high_bit_depth); + + // Note that dst is already assumed to be aligned to even. + // extend_even(dst->planes[AOM_PLANE_Y], dst->stride[AOM_PLANE_Y], src->d_w, + // src->d_h, use_high_bit_depth); + + if (!src->monochrome) { + copy_rect(src->planes[AOM_PLANE_U], src->stride[AOM_PLANE_U], + dst->planes[AOM_PLANE_U], dst->stride[AOM_PLANE_U], + width >> chroma_subsamp_x, height >> chroma_subsamp_y, + use_high_bit_depth); + + copy_rect(src->planes[AOM_PLANE_V], src->stride[AOM_PLANE_V], + dst->planes[AOM_PLANE_V], dst->stride[AOM_PLANE_V], + width >> chroma_subsamp_x, height >> chroma_subsamp_y, + use_high_bit_depth); + } + + return 0; +} + +static aom_image_t *alloc_image_deband(aom_codec_alg_priv_t *ctx, + aom_image_t *img, + aom_image_t *img_deband) { + BufferPool *const pool = ctx->buffer_pool; + aom_codec_frame_buffer_t *fb = + &ctx->image_debanded_frame_buffers[ctx->num_image_debanded_frame_buffers]; + AllocCbParam param; + param.pool = pool; + param.fb = fb; + if (!aom_img_alloc_with_cb(img_deband, img->fmt, img->w, img->h, 16, + AllocWithGetFrameBufferCb, ¶m)) { + return NULL; + } + + img_deband->user_priv = img->user_priv; + img_deband->fb_priv = fb->priv; + aom_img_remove_metadata(img_deband); + img_deband->metadata = img->metadata; + img->metadata = NULL; + + if(copy_image_for_deband(img, img_deband)) { + pool->release_fb_cb(pool->cb_priv, fb); + return NULL; + } + + ctx->num_image_debanded_frame_buffers++; + return img_deband; +} +#endif + // If grain_params->apply_grain is false, returns img. Otherwise, adds film // grain to img, saves the result in grain_img, and returns grain_img. static aom_image_t *add_grain_if_needed(aom_codec_alg_priv_t *ctx, @@ -871,8 +1040,26 @@ img->temporal_id = cm->temporal_layer_id; img->spatial_id = cm->spatial_layer_id; if (pbi->skip_film_grain) grain_params->apply_grain = 0; - aom_image_t *res = - add_grain_if_needed(ctx, img, &ctx->image_with_grain, grain_params); + aom_image_t *res = NULL; +#if CONFIG_DEBAND + const int do_deband = + !cm->features.coded_lossless && cm->deband_info.deband_enable; + if (do_deband) { + DebandInfo *const dbi = &cm->deband_info; + if(avm_deband_init(dbi, img->w, img->h, img->bit_depth, 0)) { + aom_image_t *img_deb = alloc_image_deband(ctx, img, + &ctx->image_debanded); + if (!img_deb) { + aom_internal_error(&pbi->common.error, AOM_CODEC_CORRUPT_FRAME, + "Debanding failed at allocation\n"); + } + avm_deband_frame(img_deb, dbi); + avm_deband_close(dbi, 0); + img = img_deb; + } + } +#endif + res = add_grain_if_needed(ctx, img, &ctx->image_with_grain, grain_params); if (!res) { aom_internal_error(&pbi->common.error, AOM_CODEC_CORRUPT_FRAME, "Grain systhesis failed\n");
diff --git a/av1/common/av1_common_int.h b/av1/common/av1_common_int.h index b6d2063..aa37ea0 100644 --- a/av1/common/av1_common_int.h +++ b/av1/common/av1_common_int.h
@@ -369,6 +369,63 @@ } CcsoInfo; #endif +#if CONFIG_DEBAND +/** deband buffers */ +typedef struct DebandBuffers { + /** c_values (encoder-only) */ + float *c_values; + /** mask buffer */ + uint32_t *mask_dp; + /** histogram for the c_values */ + uint16_t *c_values_histograms; + /** filtering step buffer */ + uint16_t *filter_mode_buffer; +} DebandBuffers; + +/** deband & CAMBI info */ +typedef struct { + /** deband enable */ + bool deband_enable; + /** frame to deband */ + uint16_t *frame; + /** mask frame buffer */ + uint16_t *mask; + /** stride from frame, mask and c_values buffers */ + int stride; + /** frame height for 'frame', mask and c_values buffers */ + int height; + /** visibility threshold for contrast */ + uint16_t *tvi_for_diff; + /** weight per contrast to determine score */ + int *diffs_weights; + /** weight per scale to determine score (encoder-only) */ + int scale_weights[5]; + /** banding contrasts to evaluate */ + uint16_t *diffs_to_consider; + /** window size over which banding is assessed */ + uint16_t window_size; + /** number of pixels in the window */ + uint16_t pixels_in_window; + /** ratio of worst banding pixels used to determine the score */ + double topk; + /** visibility threshold */ + double tvi_threshold; + /** max log 2 number of contrasts */ + uint16_t max_log_contrast; + /** number of contrasts */ + int num_diffs; + /** number of bins for the histogram in CAMBI */ + int num_bins; + /** Pointer to data to deband */ + uint16_t *dst; + /** stride to dst */ + int dst_stride; + /** dst bitdepth */ + int dst_bd; + /** buffers used in CAMBI and debanding */ + DebandBuffers buffers; +} DebandInfo; +#endif /*!\cond */ typedef struct { @@ -536,6 +593,9 @@ #if CONFIG_IMPROVED_GLOBAL_MOTION bool enable_global_motion; #endif // CONFIG_IMPROVED_GLOBAL_MOTION +#if CONFIG_DEBAND + uint8_t enable_deband; // To turn on/off debanding +#endif BITSTREAM_PROFILE profile; // Color config. @@ -1593,6 +1653,13 @@ CcsoInfo ccso_info; #endif +#if CONFIG_DEBAND + /*! + * Deband parameters. + */ + DebandInfo deband_info; +#endif + /*! * Parameters for film grain synthesis. */
diff --git a/av1/common/debanding.c b/av1/common/debanding.c new file mode 100644 index 0000000..f5e60e5 --- /dev/null +++ b/av1/common/debanding.c
@@ -0,0 +1,544 @@ + +/* + * Copyright (c) 2021, 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 "av1/common/av1_common_int.h" +#include "av1/common/debanding.h" + +/* Visibility threshold functions */ +#define BT1886_GAMMA (2.4) + +enum CambiTVIBisectFlag { + CAMBI_TVI_BISECT_TOO_SMALL, + CAMBI_TVI_BISECT_CORRECT, + CAMBI_TVI_BISECT_TOO_BIG +}; + +static inline int clip(int value, int low, int high) { + return value < low ? low : (value > high ? high : value); +} + +static inline double bt1886_eotf(double V, double gamma, double Lw, double Lb) { + double a = pow(pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma), gamma); + double b = pow(Lb, 1.0 / gamma) / (pow(Lw, 1.0 / gamma) - pow(Lb, 1.0 / gamma)); + double L = a * pow(AOMMAX(V + b, 0), gamma); + return L; +} + +static inline void range_foot_head(int bitdepth, const char *pix_range, + int *foot, int *head) { + int foot_8b = 0; + int head_8b = 255; + if (!strcmp(pix_range, "standard")) { + foot_8b = 16; + head_8b = 235; + } + *foot = foot_8b * (1 << (bitdepth - 8)); + *head = head_8b * (1 << (bitdepth - 8)); +} + +static double normalize_range(int sample, int bitdepth, const char *pix_range) { + int foot, head, clipped_sample; + range_foot_head(bitdepth, pix_range, &foot, &head); + clipped_sample = clip(sample, foot, head); + return (double)(clipped_sample - foot) / (head - foot); +} + +static double luminance_bt1886(int sample, int bitdepth, + double Lw, double Lb, const char *pix_range) { + double normalized = normalize_range(sample, bitdepth, pix_range); + normalized = normalize_range(sample, bitdepth, pix_range); + return bt1886_eotf(normalized, BT1886_GAMMA, Lw, Lb); +} + +static bool tvi_condition(int sample, int diff, double tvi_threshold, + int bitdepth, double Lw, double Lb, const char *pix_range) { + double mean_luminance = luminance_bt1886(sample, bitdepth, Lw, Lb, pix_range); + double diff_luminance = luminance_bt1886(sample + diff, bitdepth, Lw, Lb, pix_range); + double delta_luminance = diff_luminance - mean_luminance; + return (delta_luminance > tvi_threshold * mean_luminance); +} + +static enum CambiTVIBisectFlag tvi_hard_threshold_condition(int sample, int diff, + double tvi_threshold, + int bitdepth, double Lw, double Lb, + const char *pix_range) { + bool condition; + condition = tvi_condition(sample, diff, tvi_threshold, bitdepth, Lw, Lb, pix_range); + if (!condition) return CAMBI_TVI_BISECT_TOO_BIG; + + condition = tvi_condition(sample + 1, diff, tvi_threshold, bitdepth, Lw, Lb, pix_range); + if (condition) return CAMBI_TVI_BISECT_TOO_SMALL; + + return CAMBI_TVI_BISECT_CORRECT; +} + +static int get_tvi_for_diff(int diff, double tvi_threshold, int bitdepth, + double Lw, double Lb, const char *pix_range) { + int foot, head, mid; + enum CambiTVIBisectFlag tvi_bisect; + const int max_val = (1 << bitdepth) - 1; + + range_foot_head(bitdepth, pix_range, &foot, &head); + head = head - diff - 1; + + tvi_bisect = tvi_hard_threshold_condition(foot, diff, tvi_threshold, bitdepth, + Lw, Lb, pix_range); + if (tvi_bisect == CAMBI_TVI_BISECT_TOO_BIG) return 0; + if (tvi_bisect == CAMBI_TVI_BISECT_CORRECT) return foot; + + tvi_bisect = tvi_hard_threshold_condition(head, diff, tvi_threshold, bitdepth, + Lw, Lb, pix_range); + if (tvi_bisect == CAMBI_TVI_BISECT_TOO_SMALL) return max_val; + if (tvi_bisect == CAMBI_TVI_BISECT_CORRECT) return head; + + // bisect + while (1) { + mid = foot + (head - foot) / 2; + tvi_bisect = tvi_hard_threshold_condition(mid, diff, tvi_threshold, bitdepth, + Lw, Lb, pix_range); + if (tvi_bisect == CAMBI_TVI_BISECT_TOO_BIG) + head = mid; + else if (tvi_bisect == CAMBI_TVI_BISECT_TOO_SMALL) + foot = mid; + else //(tvi_bisect == CAMBI_TVI_BISECT_CORRECT) + return mid; + } +} + +void set_contrast_arrays_camda(DebandInfo *const dbi) { + int num_diffs = dbi->num_diffs; + + dbi->diffs_to_consider = + aom_malloc(sizeof(dbi->diffs_to_consider) * num_diffs); + + for (int d = 0; d < num_diffs; d++) { + dbi->diffs_to_consider[d] = d + 1; + } +} + +// TODO(Joel): move CAMBI/encoder-only 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(DebandInfo *const dbi) { + set_contrast_arrays_camda(dbi); + int num_diffs = dbi->num_diffs; + + dbi->diffs_weights = aom_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(DebandInfo *const dbi, int bitdepth) { + (void) bitdepth; + int num_diffs = dbi->num_diffs; + dbi->tvi_for_diff = aom_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; + } +} + +/* CAMDA functions */ +static inline uint16_t adjust_camda_window_size(uint16_t size, + unsigned width, + unsigned height) { + return CLAMP(4 + ((((1<<6) + ((size * (width+height)) / 375)) >> 7) << 3), + CAMDA_MIN_WINDOW_SIZE, CAMDA_MAX_WINDOW_SIZE); + +} + +static inline uint16_t get_pixels_in_window(uint16_t window_length) { + return window_length * window_length; +} + +void set_camda_window(DebandInfo *const dbi) { + dbi->window_size = CAMDA_DEFAULT_WINDOW_SIZE; + dbi->window_size = + adjust_camda_window_size(dbi->window_size, dbi->stride, dbi->height); + dbi->pixels_in_window = get_pixels_in_window(dbi->window_size); +} + +/* CAMBI & CAMDA initialization */ +// bool encoder = 1 for CAMBI +int avm_deband_init(DebandInfo *const dbi, const int frame_width, + const int frame_height, const int bit_depth, bool encoder) { + int use_deband = 0; + if (frame_width < CAMBI_MIN_WIDTH || frame_height > CAMBI_MAX_WIDTH || + !(bit_depth==8 || bit_depth==10)) { + dbi->deband_enable = 0; + return use_deband; + } + use_deband = 1; + + dbi->topk = CAMBI_DEFAULT_TOPK_POOLING; + dbi->stride = frame_width; + dbi->height = frame_height; + dbi->frame = aom_malloc(sizeof(*dbi->frame) * frame_height + * dbi->stride); + + if (encoder) { + 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; + } else { + dbi->max_log_contrast = bit_depth==8 ? CAMDA_DEFAULT_MAX_LOG_CONTRAST_8b + : CAMDA_DEFAULT_MAX_LOG_CONTRAST_10b; + dbi->num_diffs = 1 << dbi->max_log_contrast; + assert(dbi->num_diffs <= CAMDA_MAX_NUM_DIFFS); + set_contrast_arrays_camda(dbi); + set_tvi_per_contrast(dbi, bit_depth); + dbi->num_bins = (1 << bit_depth) + 2 * dbi->num_diffs; + } + + dbi->buffers.filter_mode_buffer = aom_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 = aom_malloc(dp_height * dp_width * sizeof(uint32_t)); + + if (encoder) { + dbi->mask = aom_malloc(sizeof(*dbi->mask) * frame_height + * dbi->stride); + dbi->buffers.c_values = aom_malloc(sizeof(float) * frame_height + * dbi->stride); + dbi->buffers.c_values_histograms = + aom_malloc(frame_width * dbi->num_bins * sizeof(uint16_t)); + } else { + int mask_width = frame_width >> CAMDA_LOG2_BLOCK_SIZE; + int mask_height = frame_height >> CAMDA_LOG2_BLOCK_SIZE; + dbi->mask = aom_malloc(mask_width * mask_height * sizeof(*dbi->mask)); + } + + return use_deband; +} + +void camda_preprocessing(uint16_t *data, int stride, int in_w, int in_h, + uint16_t *out_data, int out_stride, + int out_w, int out_h) { + + if (in_w != out_w || in_h != out_h) { + printf("Error in camda_preprocessing: different buffer sizes\n"); + assert(in_w == out_w && in_h == out_h); + } + + // CAMDA preprocessing: just copying the data buffer + for (int i = 0; i < out_h; i++) { + memcpy(out_data, data, in_w * sizeof(uint16_t)); + data += stride; + out_data += out_stride; + } +} + +/* 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; +} + +void camda_get_spatial_mask(DebandInfo *dbi, int width, int height) { + uint16_t pad_size = CAMDA_MASK_FILTER_SIZE >> 1; + uint16_t *image_data = dbi->frame; + uint16_t *mask_data = dbi->mask; + int mask_stride = width >> CAMDA_LOG2_BLOCK_SIZE; + int mask_height = height >> CAMDA_LOG2_BLOCK_SIZE; + int stride = dbi->stride; + uint16_t mask_index = cambi_get_mask_index(width, height, CAMDA_MASK_FILTER_SIZE); + + uint32_t *dp = dbi->buffers.mask_dp; + int dp_width = width + 2 * pad_size + 1; + int dp_height = 2 * pad_size + 2; + memset(dp, 0, dp_width * dp_height * sizeof(uint32_t)); + memset(mask_data, 0, mask_stride * mask_height * sizeof(uint16_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 - 1; j++, curr_col++) { + int ind = i * stride + j; + dp[cur_row_start + curr_col] = + ((image_data[ind]==image_data[ind + stride]) && (image_data[ind]==image_data[ind + 1])) + + 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; + if (i < height - 1) { + for (int j = 0; j < width - 1; j++, curr_col++) { + int ind = i * stride + j; + dp[curr_row * dp_width + curr_col] = + ((image_data[ind]==image_data[ind + stride]) && (image_data[ind]==image_data[ind + 1])) + + dp[prev_row * dp_width + curr_col] + + dp[curr_row * dp_width + curr_col - 1] + - dp[prev_row * dp_width + curr_col - 1]; + } + } else { + for (int j = 0; j < width - 1; j++, curr_col++) { + dp[curr_row * dp_width + curr_col] = + 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. + if ((i - pad_size - 1) % CAMDA_BLOCK_SIZE == 0) { + int top = curr_row; + int mask_i = (i - pad_size + 1) >> CAMDA_LOG2_BLOCK_SIZE; + if (mask_i<mask_height) { + for (int left = 1; left < width; left+=CAMDA_BLOCK_SIZE) { + int right = left + CAMDA_MASK_FILTER_SIZE; + int result = + dp[bottom * dp_width + right] + - dp[bottom * dp_width + left] + - dp[top * dp_width + right] + + dp[top * dp_width + left]; + + int mask_j = left >> CAMDA_LOG2_BLOCK_SIZE; + mask_data[mask_i * mask_stride + mask_j] = (result > mask_index); + } + } + } + } +} + +static inline void add_block_to_histogram(uint16_t *histogram, int b_row, int b_col, + uint16_t *image, ptrdiff_t stride, + const uint16_t num_diffs) { + uint16_t *hist_diff = histogram + num_diffs; + const int row = b_row << CAMDA_LOG2_BLOCK_SIZE; + const int col = b_col << CAMDA_LOG2_BLOCK_SIZE; + long int index = row * stride + col; + for (int i=0; i<CAMDA_BLOCK_SIZE; i++, index+=stride) { + hist_diff[image[index]]++; + hist_diff[image[index+1]]++; + hist_diff[image[index+2]]++; + hist_diff[image[index+3]]++; + } +} + +static inline void sub_block_to_histogram(uint16_t *histogram, int b_row, int b_col, + uint16_t *image, ptrdiff_t stride, + const uint16_t num_diffs) { + uint16_t *hist_diff = histogram + num_diffs; + const int row = b_row << CAMDA_LOG2_BLOCK_SIZE; + const int col = b_col << CAMDA_LOG2_BLOCK_SIZE; + long int index = row * stride + col; + for (int i=0; i<CAMDA_BLOCK_SIZE; i++, index+=stride) { + hist_diff[image[index]]--; + hist_diff[image[index+1]]--; + hist_diff[image[index+2]]--; + hist_diff[image[index+3]]--; + } +} + +typedef struct DitherPixelInfo { + int16_t value; + uint16_t base[(CAMDA_MAX_NUM_DIFFS + 1)<<1]; + uint16_t *cum; +} DitherPixelInfo; + +static uint16_t rand_vals[256] = {17, 85, 163, 83, 101, 46, 134, 256, 187, 124, 33, 181, 109, 23, 178, 81, 5, 155, 161, 41, 184, 166, 87, 177, 115, 123, 27, 51, 54, 38, 67, 16, 210, 58, 99, 207, 243, 255, 153, 154, 231, 142, 108, 22, 1, 216, 158, 139, 230, 149, 150, 28, 106, 194, 86, 224, 105, 223, 74, 110, 10, 70, 120, 164, 233, 186, 201, 30, 209, 112, 235, 95, 89, 202, 227, 144, 78, 103, 162, 47, 214, 238, 208, 228, 111, 253, 34, 254, 25, 62, 135, 9, 88, 119, 136, 76, 241, 102, 49, 8, 146, 176, 249, 121, 138, 14, 79, 7, 97, 218, 31, 122, 196, 190, 147, 24, 140, 96, 114, 239, 141, 35, 77, 84, 15, 212, 213, 20, 36, 32, 247, 236, 130, 215, 55, 240, 127, 242, 57, 157, 152, 204, 229, 13, 191, 169, 93, 179, 182, 44, 159, 68, 211, 45, 197, 39, 132, 251, 64, 94, 193, 252, 188, 168, 65, 128, 244, 43, 71, 125, 133, 203, 72, 56, 206, 4, 195, 19, 82, 171, 53, 131, 59, 234, 126, 48, 129, 170, 225, 173, 116, 221, 143, 91, 165, 185, 175, 52, 75, 148, 66, 63, 246, 92, 69, 167, 6, 29, 160, 3, 172, 180, 40, 183, 104, 189, 137, 21, 199, 2, 98, 232, 156, 12, 174, 113, 245, 73, 192, 100, 11, 219, 90, 118, 198, 250, 217, 60, 200, 80, 107, 42, 226, 145, 151, 61, 37, 50, 205, 220, 248, 222, 26, 117, 18, 237}; +static int16_t diffs_sorted[9] = {0, 1, -1, 2, -2, 3, -3, 4, -4}; + +static inline int16_t dither_pixel(const uint16_t *histogram, uint16_t value, + uint16_t num_diffs, + const uint16_t *tvi_thresholds, + DitherPixelInfo *dither_info, + int pixels_in_window, int row, int col) { + + uint16_t small_band_thr = pixels_in_window>>5; + uint16_t flat_area_thr = pixels_in_window - (pixels_in_window>>6); + if (histogram[value] <= small_band_thr || histogram[value] >= flat_area_thr) { + return 0; + } + + if (dither_info->value != value) { + dither_info->value = (int16_t) value; + for (int d = 0; d <= 2*num_diffs; d++) { + dither_info->cum[d] = dither_info->cum[d - 1] + histogram[dither_info->value + diffs_sorted[d]]; + } + } + + uint16_t offset = (histogram[value] + (col * row) + (row ^ col)) & 255; + int pr_range = (rand_vals[offset] * dither_info->cum[2 * num_diffs]) >> 8; + int16_t index=0; + while(pr_range > dither_info->cum[index]) + index++; + return diffs_sorted[index]; +} + +static inline void dither_block(uint16_t *histogram, uint16_t *image, + int b_row, int b_col, ptrdiff_t stride, + uint16_t num_diffs, + const uint16_t *tvi_diff, DebandInfo *dbi) { + + int row = b_row << CAMDA_LOG2_BLOCK_SIZE; + int col = b_col << CAMDA_LOG2_BLOCK_SIZE; + int pixels_in_window = dbi->pixels_in_window; + ptrdiff_t dst_pix_r = row * dbi->dst_stride + col; + ptrdiff_t img_pix_r = row * stride + col; + + if(histogram[image[img_pix_r]+num_diffs] >= pixels_in_window) + return; + + DitherPixelInfo dither_info; + dither_info.cum = (uint16_t*) dither_info.base + 1; + dither_info.value = -1; + dither_info.base[0] = 0; + + col &= 255; + row &= 255; + for (int i=0; i < CAMDA_BLOCK_SIZE; i++, dst_pix_r += dbi->dst_stride, + img_pix_r += stride) { + ptrdiff_t dst_pix = dst_pix_r; + ptrdiff_t img_pix = img_pix_r; + int pix_row = row + i; + for (int j=0; j < CAMDA_BLOCK_SIZE; j++, img_pix++, dst_pix++) { + dbi->dst[dst_pix] += dither_pixel(histogram, image[img_pix]+num_diffs, + num_diffs, tvi_diff, &dither_info, + pixels_in_window, pix_row, col+j); + } + } +} + +static void camda_dither_frame(DebandInfo *dbi, int width, int height) { + uint16_t *image = dbi->frame; + ptrdiff_t stride = dbi->stride; + uint16_t *mask = dbi->mask; + ptrdiff_t mask_stride = (width >> CAMDA_LOG2_BLOCK_SIZE); + uint16_t window_size = dbi->window_size; + uint16_t num_diffs = dbi->num_diffs; + uint16_t *tvi_for_diff = dbi->tvi_for_diff; + uint16_t b_pad_size = window_size >> (CAMDA_LOG2_BLOCK_SIZE + 1); + uint16_t *hist = (uint16_t*) malloc(sizeof(uint16_t) * dbi->num_bins); + + int b_width = width >> CAMDA_LOG2_BLOCK_SIZE; + int b_height = height >> CAMDA_LOG2_BLOCK_SIZE; + + for (int b_row=0; b_row<b_height; b_row++) { + memset(hist, 0, sizeof(uint16_t) * dbi->num_bins); + + for (int b_i=-b_pad_size; b_i<=b_pad_size; b_i++) + for (int b_j=0; b_j<=b_pad_size; b_j++) + if (b_row+b_i>=0 && b_row+b_i<b_height) + if (mask[(b_row+b_i) * mask_stride + b_j]) + add_block_to_histogram(hist, b_row + b_i, b_j, image, stride, num_diffs); + + if (mask[b_row * mask_stride]) + dither_block(hist, image, b_row, 0, stride, num_diffs, tvi_for_diff, dbi); + + for (int b_col=1; b_col<b_width; b_col++) { + for (int b_i=-b_pad_size; b_i<=b_pad_size; b_i++) { + int b_row_curr = b_row + b_i; + if (b_row_curr >= 0 && b_row_curr < b_height) { + int b_col_in = b_col + b_pad_size; + int b_col_out = b_col - b_pad_size - 1; + + if (b_col_out >= 0) + if (mask[b_row_curr * mask_stride + b_col_out]) + sub_block_to_histogram(hist, b_row_curr, b_col_out, + image, stride, num_diffs); + + if (b_col_in<b_width) + if (mask[b_row_curr * mask_stride + b_col_in]) + add_block_to_histogram(hist, b_row_curr, b_col_in, + image, stride, num_diffs); + } + } + + if (mask[b_row * mask_stride + b_col]) + dither_block(hist, image, b_row, b_col, stride, num_diffs, + tvi_for_diff, dbi); + } + } + free(hist); +} + +void avm_deband_frame(aom_image_t *img, DebandInfo *const dbi) { + int frame_width = img->d_w; + int frame_height = img->d_h; + dbi->dst_bd = img->bit_depth; + dbi->dst_stride = img->stride[AOM_PLANE_Y] >> 1; + dbi->dst = (uint16_t *) img->planes[AOM_PLANE_Y]; + + set_camda_window(dbi); + camda_preprocessing(dbi->dst, dbi->dst_stride, frame_width, frame_height, + dbi->frame, dbi->stride, + frame_width, frame_height); + + camda_get_spatial_mask(dbi, frame_width, frame_height); + camda_dither_frame(dbi, frame_width, frame_height); +} + +void avm_deband_close(DebandInfo *const dbi, bool encoder) { + if (dbi->deband_enable == 0) { + return; + } + aom_free(dbi->frame); + aom_free(dbi->mask); + + // set_contrast_arrays + aom_free(dbi->diffs_to_consider); + + // set_tvi_per_contrast + aom_free(dbi->tvi_for_diff); + + aom_free(dbi->buffers.filter_mode_buffer); + aom_free(dbi->buffers.mask_dp); + + // CAMBI: encoder-only usage + if (encoder) { + aom_free(dbi->diffs_weights); + aom_free(dbi->buffers.c_values); + aom_free(dbi->buffers.c_values_histograms); + } +}
diff --git a/av1/common/debanding.h b/av1/common/debanding.h new file mode 100644 index 0000000..d320e0f --- /dev/null +++ b/av1/common/debanding.h
@@ -0,0 +1,75 @@ + +/* +* Copyright (c) 2021, 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/. +*/ +#ifndef AOM_AV1_COMMON_DEBANDING_H_ +#define AOM_AV1_COMMON_DEBANDING_H_ + +#include "config/aom_config.h" +#include "aom/aom_integer.h" +#include "aom_ports/mem.h" +#include "av1/common/av1_common_int.h" +#include "av1/common/reconinter.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Window size to compute CAMDA */ +#define CAMDA_MIN_WINDOW_SIZE (12) +#define CAMDA_MAX_WINDOW_SIZE (36) +#define CAMDA_DEFAULT_WINDOW_SIZE (65) + +#define CAMDA_MASK_FILTER_SIZE (7) +#define CAMDA_BLOCK_SIZE (4) +#define CAMDA_LOG2_BLOCK_SIZE (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) +#define CAMDA_MAX_NUM_DIFFS (4) + +// Todo (Joel): MAX_LOG_CONTRAST as a parameter: 0, 1, or 2 +#define CAMDA_DEFAULT_MAX_LOG_CONTRAST_10b (2) +#define CAMDA_DEFAULT_MAX_LOG_CONTRAST_8b (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))) + +int avm_deband_init(DebandInfo *const dbi, const int frame_width, + const int frame_height, const int bit_depth, bool encoder); + + + +void avm_deband_frame(aom_image_t *img, DebandInfo *const dbi); + +void avm_deband_close(DebandInfo *const dbi, bool encoder); + +uint16_t cambi_get_mask_index(int input_width, int input_height, uint16_t filter_size); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // AOM_AV1_COMMON_DEBANDING_H_ \ No newline at end of file
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c index 71a5da3..be2cc4c 100644 --- a/av1/decoder/decodeframe.c +++ b/av1/decoder/decodeframe.c
@@ -3777,6 +3777,13 @@ } #endif +#if CONFIG_DEBAND +static AOM_INLINE void setup_deband(AV1_COMMON *cm, + struct aom_read_bit_buffer *rb) { + cm->deband_info.deband_enable = aom_rb_read_literal(rb, 1); +} +#endif + static INLINE int read_delta_q(struct aom_read_bit_buffer *rb) { return aom_rb_read_bit(rb) ? aom_rb_read_inv_signed_literal(rb, 6) : 0; } @@ -6488,6 +6495,9 @@ #if CONFIG_PEF seq_params->enable_pef = aom_rb_read_bit(rb); #endif // CONFIG_PEF +#if CONFIG_DEBAND + seq_params->enable_deband = aom_rb_read_bit(rb); +#endif #if CONFIG_ORIP seq_params->enable_orip = aom_rb_read_bit(rb); #endif @@ -7787,6 +7797,11 @@ setup_ccso(cm, rb); } #endif +#if CONFIG_DEBAND + if (!features->coded_lossless && seq_params->enable_deband) { + setup_deband(cm, rb); + } +#endif #if CONFIG_PAR_HIDING if (features->coded_lossless || !cm->seq_params.enable_parity_hiding) @@ -8163,7 +8178,6 @@ extend_ccso_border(ext_rec_y, CCSO_PADDING_SIZE, xd); } #endif - const int do_loop_restoration = #if CONFIG_HIGH_PASS_CROSS_WIENER_FILTER cm->rst_info[0].frame_cross_restoration_type != RESTORE_NONE ||
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c index f0d06ab..d8d29e3 100644 --- a/av1/encoder/bitstream.c +++ b/av1/encoder/bitstream.c
@@ -4145,6 +4145,13 @@ } #endif +#if CONFIG_DEBAND +static AOM_INLINE void encode_deband(const AV1_COMMON *cm, + struct aom_write_bit_buffer *wb) { + aom_wb_write_literal(wb, cm->deband_info.deband_enable, 1); +} +#endif + static AOM_INLINE void write_delta_q(struct aom_write_bit_buffer *wb, int delta_q) { if (delta_q != 0) { @@ -4852,6 +4859,9 @@ #if CONFIG_PEF aom_wb_write_bit(wb, seq_params->enable_pef); #endif // CONFIG_PEF +#if CONFIG_DEBAND + aom_wb_write_bit(wb, seq_params->enable_deband); +#endif #if CONFIG_ORIP aom_wb_write_bit(wb, seq_params->enable_orip); #endif @@ -5493,6 +5503,11 @@ encode_ccso(cm, wb); } #endif +#if CONFIG_DEBAND + if (!features->coded_lossless && cm->seq_params.enable_deband) { + encode_deband(cm, wb); + } +#endif } #if CONFIG_PAR_HIDING
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c index b7f30ab..7fe3b33 100644 --- a/av1/encoder/encoder.c +++ b/av1/encoder/encoder.c
@@ -67,6 +67,7 @@ #include "av1/encoder/mv_prec.h" #include "av1/encoder/pass2_strategy.h" #include "av1/encoder/pickcdef.h" +#include "av1/encoder/pickdeband.h" #if CONFIG_CCSO #include "av1/encoder/pickccso.h" #endif @@ -474,6 +475,9 @@ #if CONFIG_D071_IMP_MSK_BLD seq->enable_imp_msk_bld = tool_cfg->enable_imp_msk_bld; #endif // CONFIG_D071_IMP_MSK_BLD +#if CONFIG_DEBAND + seq->enable_deband = tool_cfg->enable_deband; +#endif #if CONFIG_EXTENDED_WARP_PREDICTION seq->seq_enabled_motion_modes = oxcf->motion_mode_cfg.seq_enabled_motion_modes; @@ -2281,6 +2285,28 @@ } #endif // CONFIG_HIGH_PASS_CROSS_WIENER_FILTER +#if CONFIG_DEBAND +/*!\brief Select and apply debanding process + * + * \ingroup high_level_algo + */ +static void avm_debanding(AV1_COMP *cpi, AV1_COMMON *cm, + MACROBLOCKD *xd, int use_debanding) { + if (use_debanding) { + DebandInfo *const dbi = &cm->deband_info; + int bit_depth = xd->bd; + int frame_width = xd->plane[0].dst.width; + int frame_height = xd->plane[0].dst.height; + if(avm_deband_init(dbi, frame_width, frame_height, bit_depth, 1)) { + avm_deband_search(&cm->cur_frame->buf, cpi->source, cm, xd); + avm_deband_close(&cm->deband_info, 1); + } + } else { + cm->deband_info.deband_enable = 0; + } +} +#endif + /*!\brief Select and apply cdef filters and switchable restoration filters * * \ingroup high_level_algo @@ -3201,6 +3227,15 @@ aom_write_one_yuv_frame(cm, &cm->cur_frame->buf); #endif +#if CONFIG_DEBAND + MACROBLOCKD *xd = &cpi->td.mb.e_mbd; + const int use_debanding = cm->seq_params.enable_deband && + !cm->features.all_lossless && + !cm->tiles.large_scale; + + avm_debanding(cpi, cm, xd, use_debanding); +#endif + av1_finalize_encoded_frame(cpi); // Build the bitstream #if CONFIG_COLLECT_COMPONENT_TIMING
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h index e533bdc..96e4cfe 100644 --- a/av1/encoder/encoder.h +++ b/av1/encoder/encoder.h
@@ -894,6 +894,10 @@ // enable implicit masked blending bool enable_imp_msk_bld; #endif // CONFIG_D071_IMP_MSK_BLD +#if CONFIG_DEBAND + // Indicates if deband should be enabled. + bool enable_deband; +#endif // When enabled, video mode should be used even for single frame input. bool force_video_mode; // Indicates if the error resiliency features should be enabled.
diff --git a/av1/encoder/pickdeband.c b/av1/encoder/pickdeband.c new file mode 100644 index 0000000..ba384f6 --- /dev/null +++ b/av1/encoder/pickdeband.c
@@ -0,0 +1,587 @@ +/* + * Copyright (c) 2021, 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/aom_dsp_rtcd.h" +#include "av1/common/av1_common_int.h" +#include "av1/encoder/pickdeband.h" + + +/* 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 the input and output sizes are the same + if (in_w == out_w && in_h == out_h) { + 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; + } else { + printf("Error in copy_8b_to_10b_buffer: different size\n"); + } +} + +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) { + for (unsigned i = 0; i < out_h; i++) { + memcpy(out_data, data, in_w * sizeof(uint16_t)); + data += stride; + out_data += out_stride; + } + } else { + printf("Error in copy_10b_buffer: different size\n"); + } +} + +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(DebandInfo *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 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(DebandInfo *dbi, uint16_t mask_index, + uint16_t filter_size, int width, + int height) { + uint16_t pad_size = filter_size >> 1; + uint16_t *image_data = dbi->frame; + uint16_t *mask_data = dbi->mask; + int stride = dbi->stride; + uint32_t *dp = dbi->buffers.mask_dp; + + int dp_width = width + 2 * pad_size + 1; + 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(DebandInfo *dbi, int width, int height) { + const uint16_t filter_size = CAMBI_MASK_FILTER_SIZE; + 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(DebandInfo *dbi, int width, int height) { + uint16_t *data = dbi->frame; + 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) { + long int index = (i - pad_size - 1) * stride + j; + if (mask[index]) { + uint16_t val = image[index] + num_diffs; + decrement_range(&histograms[val * width], AOMMAX(j - pad_size, 0), + AOMMIN(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) { + 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) { + long int index = (i + pad_size) * stride + j; + if (mask[index]) { + uint16_t val = image[index] + num_diffs; + increment_range(&histograms[val * width], AOMMAX(j - pad_size, 0), + AOMMIN(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) { + long int index = (i + pad_size) * stride + j; + if (mask[index]) { + 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) { + long int index = i * stride + j; + if (mask[index]) { + uint16_t val = image[index] + num_diffs; + increment_range(&histograms[val * width], AOMMAX(j - pad_size, 0), + AOMMIN(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) { + long int index = i * stride + j; + if (mask[index]) { + 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) { + 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]) { + uint16_t p_1 = histograms[(value + d + 1) + * histogram_width + histogram_col]; + 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, + DebandInfo *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(DebandInfo *dbi, int width, int height) { + uint16_t *image = dbi->frame; + uint16_t *mask = dbi->mask; + ptrdiff_t stride = dbi->stride; + float *c_values = dbi->buffers.c_values; + uint16_t *histograms = dbi->buffers.c_values_histograms; + uint16_t window_size = dbi->window_size; + uint16_t num_diffs = dbi->num_diffs; + uint16_t *tvi_for_diff = dbi->tvi_for_diff; + 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 = AOMMAX(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 = AOMMAX(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 = AOMMAX(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 = AOMMAX(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) { + int num_elements = height * width; + 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(DebandInfo *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, DebandInfo *dbi, MACROBLOCKD *xd) { + av1_setup_dst_planes(xd->plane, frame, 0, 0, 0, 1, NULL); + struct buf_2d pre_buf = xd->plane[0].dst; + int src_stride = xd->plane[0].dst.stride; + int frame_width = pre_buf.width; + int frame_height = pre_buf.height; + 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); +} + +/*!\brief Assess banding via CAMBI + * + * \ingroup in_loop_cdef + * + * 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] xd Pointer to common current coding block structure + * + * \return Nothing is returned. Instead, presence of banding is storedoptimal CDEF parameters are stored + * in the \c dbi structure of type \ref DebandInfo inside \c cm: + * \arg \c deband_enable: enabled when banding detected, disabled otherwise + * + */ +void avm_deband_search(const YV12_BUFFER_CONFIG *frame, + const YV12_BUFFER_CONFIG *ref, AV1_COMMON *cm, + MACROBLOCKD *xd) { + DebandInfo *const dbi = &cm->deband_info; + double cambi_ref = avm_compute_cambi(ref, dbi, xd); + double cambi_enc = avm_compute_cambi(frame, dbi, xd); + + int bit_depth = xd->bd; + double diff_threshold = bit_depth==8 ? CAMBI_DIFF_THRESHOLD_8b + : CAMBI_DIFF_THRESHOLD_10b; + + dbi->deband_enable = (cambi_enc - cambi_ref >= diff_threshold); + + // double src_threshold = bit_depth==8 ? CAMBI_SOURCE_THRESHOLD_8b + // : CAMBI_SOURCE_THRESHOLD_10b; + // dbi->deband_enable &= (cambi_ref < src_threshold); + + printf("CAMBI ref: %f CAMBI enc: %f, enable: %d\n", + cambi_ref, cambi_enc, dbi->deband_enable); +}
diff --git a/av1/encoder/pickdeband.h b/av1/encoder/pickdeband.h new file mode 100644 index 0000000..7305d64 --- /dev/null +++ b/av1/encoder/pickdeband.h
@@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021, 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/. + */ +#ifndef AOM_AV1_ENCODER_PICKDEBAND_H_ +#define AOM_AV1_ENCODER_PICKDEBAND_H_ + +#include "av1/common/debanding.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SWAP_FLOATS(x, y) \ + { \ + float temp = x; \ + x = y; \ + y = temp; \ + } + +/*!\brief AVM deband parameter search + * + * \ingroup in_loop_deband + * + * Searches for debanding parameters for frame + * + * \param[in] frame Compressed frame buffer + * \param[in] ref Source frame buffer + * \param[in,out] cm Pointer to top level common structure + * \param[in] xd Pointer to common current coding block structure + * \param[in] rdmult rd multiplier to use in making param choices + * + * \return Nothing is returned. Instead, selected debanding parameters are stored + * + */ + + +/* 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 + + +void avm_deband_search(const YV12_BUFFER_CONFIG *frame, + const YV12_BUFFER_CONFIG *ref, AV1_COMMON *cm, + MACROBLOCKD *xd); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // AOM_AV1_ENCODER_PICKDEBAND_H_
diff --git a/build/cmake/aom_config_defaults.cmake b/build/cmake/aom_config_defaults.cmake index 529ba9f..21a1718 100644 --- a/build/cmake/aom_config_defaults.cmake +++ b/build/cmake/aom_config_defaults.cmake
@@ -288,7 +288,8 @@ set_aom_config_var(CONFIG_C076_INTER_MOD_CTX 1 "AV2 experiment flag to simplify inter mode contexts") - +set_aom_config_var(CONFIG_DEBAND 1 + "AV2 experiment flag to enable debanding.") set_aom_config_var(CONFIG_NEW_CONTEXT_MODELING 1 "Enable to improve the context modeling") set_aom_config_var(CONFIG_CROSS_CHROMA_TX 1
diff --git a/common/args.c b/common/args.c index 82a7e2a..33b3e50 100644 --- a/common/args.c +++ b/common/args.c
@@ -139,6 +139,9 @@ #if CONFIG_PEF GET_PARAMS(enable_pef); #endif // CONFIG_PEF +#if CONFIG_DEBAND + GET_PARAMS(enable_deband); +#endif GET_PARAMS(enable_obmc); GET_PARAMS(enable_warped_motion); GET_PARAMS(enable_global_motion);
diff --git a/common/av1_config.c b/common/av1_config.c index 3d00147..c8cf95e 100644 --- a/common/av1_config.c +++ b/common/av1_config.c
@@ -280,6 +280,9 @@ #if CONFIG_PEF AV1C_READ_BIT_OR_RETURN_ERROR(enable_pef); #endif // CONFIG_PEF +#if CONFIG_DEBAND + AV1C_READ_BIT_OR_RETURN_ERROR(enable_deband); +#endif #if CONFIG_ORIP AV1C_READ_BIT_OR_RETURN_ERROR(enable_orip); #endif