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, &param)) {
+    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