Add the q_segmentation experiment

This experiment implements low-cost delta q signalling on a per-block basis
for all non-inter frame types, which would allow for more efficient AQ
which bases its decisions on temporal information.

Based on an Intel proposal from March.

Change-Id: I18e73d8b12f4caa0b165df12c58ab506271bec03
diff --git a/av1/common/alloccommon.c b/av1/common/alloccommon.c
index 4f1ad49..b8e30cf 100644
--- a/av1/common/alloccommon.c
+++ b/av1/common/alloccommon.c
@@ -48,8 +48,14 @@
   cm->MBs = cm->mb_rows * cm->mb_cols;
 }
 
-static int alloc_seg_map(AV1_COMMON *cm, int seg_map_size) {
+static int alloc_seg_map(AV1_COMMON *cm, int rows, int cols) {
   int i;
+  int seg_map_size = rows * cols;
+
+#if CONFIG_Q_SEGMENTATION
+  cm->q_seg_map = (uint8_t *)aom_calloc(seg_map_size, 1);
+  if (!cm->q_seg_map) return 1;
+#endif
 
   for (i = 0; i < NUM_PING_PONG_BUFFERS; ++i) {
     cm->seg_map_array[i] = (uint8_t *)aom_calloc(seg_map_size, 1);
@@ -71,6 +77,11 @@
 static void free_seg_map(AV1_COMMON *cm) {
   int i;
 
+#if CONFIG_Q_SEGMENTATION
+  aom_free(cm->q_seg_map);
+  cm->q_seg_map = NULL;
+#endif
+
   for (i = 0; i < NUM_PING_PONG_BUFFERS; ++i) {
     aom_free(cm->seg_map_array[i]);
     cm->seg_map_array[i] = NULL;
@@ -222,7 +233,7 @@
   if (cm->seg_map_alloc_size < cm->mi_rows * cm->mi_cols) {
     // Create the segmentation map structure and set to 0.
     free_seg_map(cm);
-    if (alloc_seg_map(cm, cm->mi_rows * cm->mi_cols)) goto fail;
+    if (alloc_seg_map(cm, cm->mi_rows, cm->mi_cols)) goto fail;
   }
 
   if (cm->above_context_alloc_cols < cm->mi_cols) {
diff --git a/av1/common/blockd.h b/av1/common/blockd.h
index 878f949..e7b2658 100644
--- a/av1/common/blockd.h
+++ b/av1/common/blockd.h
@@ -310,6 +310,9 @@
   int8_t skip_mode;
 #endif  // CONFIG_EXT_SKIP
   int8_t segment_id;
+#if CONFIG_Q_SEGMENTATION
+  int8_t q_segment_id;
+#endif
   int8_t seg_id_predicted;  // valid only when temporal_update is enabled
 
 #if CONFIG_MRC_TX
diff --git a/av1/common/entropy.c b/av1/common/entropy.c
index 234d829..f89eac5 100644
--- a/av1/common/entropy.c
+++ b/av1/common/entropy.c
@@ -1750,6 +1750,10 @@
   AVERAGE_TILE_CDFS(lpf_delta_cdf);
   AVERAGE_TILE_CDFS(lpf_sign_cdf);
 #endif  // CONFIG_LPF_SB
+#if CONFIG_Q_SEGMENTATION
+  int j;
+  for (j = 0; j < Q_SEGMENT_CDF_COUNT; j++) AVERAGE_TILE_CDFS(seg.q_seg_cdf[j]);
+#endif
 }
 
 void av1_average_tile_inter_cdfs(AV1_COMMON *cm, FRAME_CONTEXT *fc,
@@ -1816,4 +1820,8 @@
   AVERAGE_TILE_CDFS(lpf_delta_cdf);
   AVERAGE_TILE_CDFS(lpf_sign_cdf);
 #endif  // CONFIG_LPF_SB
+#if CONFIG_Q_SEGMENTATION
+  int j;
+  for (j = 0; j < Q_SEGMENT_CDF_COUNT; j++) AVERAGE_TILE_CDFS(seg.q_seg_cdf[j]);
+#endif
 }
diff --git a/av1/common/entropymode.c b/av1/common/entropymode.c
index 34e7189..705d265 100644
--- a/av1/common/entropymode.c
+++ b/av1/common/entropymode.c
@@ -1746,6 +1746,21 @@
   AOM_CDF8(4096, 8192, 12288, 16384, 20480, 24576, 28672)
 };
 
+#if CONFIG_Q_SEGMENTATION
+static const aom_cdf_prob
+    default_q_seg_tree_cdf[Q_SEGMENT_CDF_COUNT][CDF_SIZE(MAX_SEGMENTS)] = {
+      {
+          AOM_CDF8(5622, 7893, 16093, 18233, 27809, 28373, 32533),
+      },
+      {
+          AOM_CDF8(14274, 18230, 22557, 24935, 29980, 30851, 32344),
+      },
+      {
+          AOM_CDF8(27527, 28487, 28723, 28890, 32397, 32647, 32679),
+      },
+    };
+#endif
+
 static const aom_cdf_prob
     default_tx_size_cdf[MAX_TX_DEPTH][TX_SIZE_CONTEXTS][CDF_SIZE(MAX_TX_DEPTH +
                                                                  1)] = {
@@ -3521,6 +3536,10 @@
   av1_copy(fc->skip_probs, default_skip_probs);
 #endif  // CONFIG_NEW_MULTISYMBOL
   av1_copy(fc->seg.tree_cdf, default_seg_tree_cdf);
+#if CONFIG_Q_SEGMENTATION
+  for (int i = 0; i < Q_SEGMENT_CDF_COUNT; i++)
+    av1_copy(fc->seg.q_seg_cdf[i], default_q_seg_tree_cdf[i]);
+#endif
   av1_copy(fc->tx_size_cdf, default_tx_size_cdf);
   av1_copy(fc->delta_q_prob, default_delta_q_probs);
   av1_copy(fc->delta_q_cdf, default_delta_q_cdf);
diff --git a/av1/common/onyxc_int.h b/av1/common/onyxc_int.h
index 7296460..5687de2 100644
--- a/av1/common/onyxc_int.h
+++ b/av1/common/onyxc_int.h
@@ -365,6 +365,9 @@
   uint8_t *seg_map_array[NUM_PING_PONG_BUFFERS];
   uint8_t *last_frame_seg_map;
   uint8_t *current_frame_seg_map;
+#if CONFIG_Q_SEGMENTATION
+  uint8_t *q_seg_map;
+#endif
   int seg_map_alloc_size;
 
   InterpFilter interp_filter;
diff --git a/av1/common/pred_common.h b/av1/common/pred_common.h
index 882c9a2..5002769 100644
--- a/av1/common/pred_common.h
+++ b/av1/common/pred_common.h
@@ -20,6 +20,38 @@
 extern "C" {
 #endif
 
+#if CONFIG_Q_SEGMENTATION
+/* Picks CDFs based on number of matching segment IDs */
+static INLINE int pick_q_seg_cdf(int prev_ul, int prev_u, int prev_l) {
+  if ((prev_ul == prev_u) && (prev_ul == prev_l))
+    return 2;
+  else if ((prev_ul == prev_u) || (prev_ul == prev_l) || (prev_u == prev_l))
+    return 1;
+  else
+    return 0;
+}
+
+static INLINE int pick_q_seg_pred(int prev_ul, int prev_u, int prev_l) {
+  /* If 2 or more are identical returns that as predictor, otherwise prev_l */
+  return (prev_ul == prev_u) ? prev_u : prev_l;
+}
+
+static INLINE void set_q_segment_id(const AV1_COMMON *const cm,
+                                    uint8_t *segment_ids, BLOCK_SIZE bsize,
+                                    int mi_row, int mi_col, int segment_id) {
+  const int mi_offset = mi_row * cm->mi_cols + mi_col;
+  const int bw = mi_size_wide[bsize];
+  const int bh = mi_size_high[bsize];
+  const int xmis = AOMMIN(cm->mi_cols - mi_col, bw);
+  const int ymis = AOMMIN(cm->mi_rows - mi_row, bh);
+  int x, y;
+
+  for (y = 0; y < ymis; ++y)
+    for (x = 0; x < xmis; ++x)
+      segment_ids[mi_offset + y * cm->mi_cols + x] = segment_id;
+}
+#endif
+
 static INLINE int get_segment_id(const AV1_COMMON *const cm,
                                  const uint8_t *segment_ids, BLOCK_SIZE bsize,
                                  int mi_row, int mi_col) {
diff --git a/av1/common/quant_common.c b/av1/common/quant_common.c
index f14c539..3590ba3 100644
--- a/av1/common/quant_common.c
+++ b/av1/common/quant_common.c
@@ -330,14 +330,25 @@
 }
 
 int av1_get_qindex(const struct segmentation *seg, int segment_id,
-                   int base_qindex) {
+#if CONFIG_Q_SEGMENTATION
+                   int q_segment_id, int base_qindex)
+#else
+                   int base_qindex)
+#endif
+{
   if (segfeature_active(seg, segment_id, SEG_LVL_ALT_Q)) {
     const int data = get_segdata(seg, segment_id, SEG_LVL_ALT_Q);
     const int seg_qindex = base_qindex + data;
     return clamp(seg_qindex, 0, MAXQ);
-  } else {
-    return base_qindex;
   }
+#if CONFIG_Q_SEGMENTATION
+  else if (q_segment_id < seg->q_lvls) {
+    const int seg_qindex = base_qindex + seg->q_delta[q_segment_id];
+    return clamp(seg_qindex, 0, MAXQ);
+  }
+#endif
+  else
+    return base_qindex;
 }
 
 #if CONFIG_AOM_QM
diff --git a/av1/common/quant_common.h b/av1/common/quant_common.h
index 516e325..dee105c 100644
--- a/av1/common/quant_common.h
+++ b/av1/common/quant_common.h
@@ -42,7 +42,11 @@
 int16_t av1_qindex_from_ac(int ac, aom_bit_depth_t bit_depth);
 
 int av1_get_qindex(const struct segmentation *seg, int segment_id,
+#if CONFIG_Q_SEGMENTATION
+                   int q_segment_id, int base_qindex);
+#else
                    int base_qindex);
+#endif
 #if CONFIG_AOM_QM
 // Reduce the large number of quantizers to a smaller number of levels for which
 // different matrices may be defined
diff --git a/av1/common/seg_common.h b/av1/common/seg_common.h
index 44011dc..6b0675a 100644
--- a/av1/common/seg_common.h
+++ b/av1/common/seg_common.h
@@ -22,6 +22,9 @@
 #define SEG_TREE_PROBS (MAX_SEGMENTS - 1)
 
 #define PREDICTION_PROBS 3
+#if CONFIG_Q_SEGMENTATION
+#define Q_SEGMENT_CDF_COUNT 3
+#endif
 
 #if CONFIG_LOOPFILTER_LEVEL
 typedef enum {
@@ -57,6 +60,10 @@
 
 struct segmentation {
   uint8_t enabled;
+#if CONFIG_Q_SEGMENTATION
+  uint8_t q_lvls;
+  int16_t q_delta[MAX_SEGMENTS];
+#endif
   uint8_t update_map;
   uint8_t update_data;
   uint8_t temporal_update;
@@ -71,6 +78,13 @@
   aom_prob pred_probs[PREDICTION_PROBS];
 #if CONFIG_NEW_MULTISYMBOL
   aom_cdf_prob pred_cdf[PREDICTION_PROBS][CDF_SIZE(2)];
+#if CONFIG_Q_SEGMENTATION
+  aom_cdf_prob q_seg_cdf[Q_SEGMENT_CDF_COUNT][CDF_SIZE(MAX_SEGMENTS)];
+#endif
+#else
+#if CONFIG_Q_SEGMENTATION
+  aom_prob q_seg_cdf[Q_SEGMENT_CDF_COUNT][CDF_SIZE(MAX_SEGMENTS)];
+#endif
 #endif
 };
 
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c
index 3dfd790..bc11ea2 100644
--- a/av1/decoder/decodeframe.c
+++ b/av1/decoder/decodeframe.c
@@ -488,7 +488,11 @@
     for (i = 0; i < MAX_SEGMENTS; i++) {
 #if CONFIG_EXT_DELTA_Q
       const int current_qindex =
+#if CONFIG_Q_SEGMENTATION
+          av1_get_qindex(&cm->seg, i, i, xd->current_qindex);
+#else
           av1_get_qindex(&cm->seg, i, xd->current_qindex);
+#endif
 #else
       const int current_qindex = xd->current_qindex;
 #endif  // CONFIG_EXT_DELTA_Q
@@ -1107,6 +1111,31 @@
   }
 }
 
+#if CONFIG_Q_SEGMENTATION
+static void setup_q_segmentation(AV1_COMMON *const cm,
+                                 struct aom_read_bit_buffer *rb) {
+  int i;
+  struct segmentation *const seg = &cm->seg;
+
+  for (i = 0; i < MAX_SEGMENTS; i++) {
+    if (segfeature_active(seg, i, SEG_LVL_ALT_Q)) {
+      seg->q_lvls = 0;
+      return;
+    }
+  }
+
+  if (!aom_rb_read_bit(rb)) return;
+
+  seg->q_lvls = decode_unsigned_max(rb, MAX_SEGMENTS);
+
+  for (i = 0; i < seg->q_lvls; i++) {
+    int val = decode_unsigned_max(rb, MAXQ);
+    val *= 1 - 2 * aom_rb_read_bit(rb);
+    seg->q_delta[i] = val;
+  }
+}
+#endif
+
 #if CONFIG_LOOP_RESTORATION
 static void decode_restoration_mode(AV1_COMMON *cm,
                                     struct aom_read_bit_buffer *rb) {
@@ -1367,7 +1396,11 @@
   // remaining are don't cares.
   const int max_segments = cm->seg.enabled ? MAX_SEGMENTS : 1;
   for (int i = 0; i < max_segments; ++i) {
+#if CONFIG_Q_SEGMENTATION
+    const int qindex = av1_get_qindex(&cm->seg, i, i, cm->base_qindex);
+#else
     const int qindex = av1_get_qindex(&cm->seg, i, cm->base_qindex);
+#endif
     cm->y_dequant_QTX[i][0] = dequant_Q3_to_QTX(
         av1_dc_quant(qindex, cm->y_dc_delta_q, cm->bit_depth), cm->bit_depth);
     cm->y_dequant_QTX[i][1] = dequant_Q3_to_QTX(
@@ -3141,6 +3174,9 @@
 #endif  // CONFIG_Q_ADAPT_PROBS
 
   setup_segmentation(cm, rb);
+#if CONFIG_Q_SEGMENTATION
+  setup_q_segmentation(cm, rb);
+#endif
 
   {
     int delta_q_allowed = 1;
@@ -3151,6 +3187,9 @@
       if (segfeature_active(seg, i, SEG_LVL_ALT_Q)) {
         segment_quantizer_active = 1;
       }
+#if CONFIG_Q_SEGMENTATION
+      if (seg->q_lvls) segment_quantizer_active = 1;
+#endif
     }
     delta_q_allowed = !segment_quantizer_active;
 #endif
@@ -3191,7 +3230,11 @@
 
   for (i = 0; i < MAX_SEGMENTS; ++i) {
     const int qindex = cm->seg.enabled
+#if CONFIG_Q_SEGMENTATION
+                           ? av1_get_qindex(&cm->seg, i, i, cm->base_qindex)
+#else
                            ? av1_get_qindex(&cm->seg, i, cm->base_qindex)
+#endif
                            : cm->base_qindex;
     xd->lossless[i] = qindex == 0 && cm->y_dc_delta_q == 0 &&
                       cm->uv_dc_delta_q == 0 && cm->uv_ac_delta_q == 0;
diff --git a/av1/decoder/decodemv.c b/av1/decoder/decodemv.c
index 14fd853..914c1a8 100644
--- a/av1/decoder/decodemv.c
+++ b/av1/decoder/decodemv.c
@@ -350,6 +350,82 @@
   return aom_read_symbol(r, segp->tree_cdf, MAX_SEGMENTS, ACCT_STR);
 }
 
+#if CONFIG_Q_SEGMENTATION
+static int neg_deinterleave(int diff, int ref, int max) {
+  if (!ref) return diff;
+  if (ref >= (max - 1)) return max - diff - 1;
+  if (2 * ref < max) {
+    if (diff <= 2 * ref) {
+      if (diff & 1)
+        return ref + ((diff + 1) >> 1);
+      else
+        return ref - (diff >> 1);
+    }
+    return diff;
+  } else {
+    if (diff <= 2 * (max - ref - 1)) {
+      if (diff & 1)
+        return ref + ((diff + 1) >> 1);
+      else
+        return ref - (diff >> 1);
+    }
+    return max - (diff + 1);
+  }
+}
+
+static int read_q_segment_id(AV1_COMMON *const cm, MACROBLOCKD *const xd,
+                             int mi_row, int mi_col, aom_reader *r) {
+  struct segmentation *const seg = &cm->seg;
+  FRAME_CONTEXT *ec_ctx = xd->tile_ctx;
+  struct segmentation_probs *const segp = &ec_ctx->seg;
+  MB_MODE_INFO *const mbmi = &xd->mi[0]->mbmi;
+  int prev_ul = 0; /* Top left segment_id */
+  int prev_l = 0;  /* Current left segment_id */
+  int prev_u = 0;  /* Current top segment_id */
+
+  if (!seg->q_lvls) return 0;
+
+  MODE_INFO *const mi = cm->mi + mi_row * cm->mi_stride + mi_col;
+  int tinfo = mi->mbmi.boundary_info;
+  int above = (!(tinfo & TILE_ABOVE_BOUNDARY)) && ((mi_row - 1) >= 0);
+  int left = (!(tinfo & TILE_LEFT_BOUNDARY)) && ((mi_col - 1) >= 0);
+
+  if (above && left)
+    prev_ul =
+        get_segment_id(cm, cm->q_seg_map, BLOCK_4X4, mi_row - 1, mi_col - 1);
+
+  if (above)
+    prev_u = get_segment_id(cm, cm->q_seg_map, BLOCK_4X4, mi_row - 1, mi_col);
+
+  if (left)
+    prev_l = get_segment_id(cm, cm->q_seg_map, BLOCK_4X4, mi_row, mi_col - 1);
+
+  int cdf_num = pick_q_seg_cdf(prev_ul, prev_u, prev_l);
+  int pred = pick_q_seg_pred(prev_ul, prev_u, prev_l);
+
+  if (mbmi->skip) {
+    set_q_segment_id(cm, cm->q_seg_map, mbmi->sb_type, mi_row, mi_col, pred);
+    return 0;
+  }
+
+#if CONFIG_NEW_MULTISYMBOL
+  aom_cdf_prob *pred_cdf = segp->q_seg_cdf[cdf_num];
+  int coded_id = aom_read_symbol(r, pred_cdf, 8, ACCT_STR);
+#else
+  const aom_prob pred_cdf = segp->q_seg_cdf[cdf_num];
+  int coded_id = aom_read(r, pred_cdf, ACCT_STR);
+#endif
+
+  int segment_id = neg_deinterleave(coded_id, pred, seg->q_lvls);
+
+  assert(segment_id >= 0 && segment_id < seg->q_lvls);
+  set_q_segment_id(cm, cm->q_seg_map, mbmi->sb_type, mi_row, mi_col,
+                   segment_id);
+
+  return segment_id;
+}
+#endif
+
 static void read_tx_size_vartx(AV1_COMMON *cm, MACROBLOCKD *xd,
                                MB_MODE_INFO *mbmi, FRAME_COUNTS *counts,
                                TX_SIZE tx_size, int depth, int blk_row,
@@ -1086,6 +1162,9 @@
 
   mbmi->segment_id = read_intra_segment_id(cm, xd, mi_offset, x_mis, y_mis, r);
   mbmi->skip = read_skip(cm, xd, mbmi->segment_id, r);
+#if CONFIG_Q_SEGMENTATION
+  mbmi->q_segment_id = read_q_segment_id(cm, xd, mi_row, mi_col, r);
+#endif
 
   if (cm->delta_q_present_flag) {
     xd->current_qindex =
@@ -2524,6 +2603,9 @@
 
   if (mbmi->skip_mode) {
     mbmi->skip = 1;
+#if CONFIG_Q_SEGMENTATION
+    mbmi->q_segment_id = read_q_segment_id(cm, xd, mi_row, mi_col, r);
+#endif
 
     if (cm->delta_q_present_flag) {
       xd->current_qindex = xd->prev_qindex;
@@ -2547,6 +2629,9 @@
   } else {
 #endif  // CONFIG_EXT_SKIP
     mbmi->skip = read_skip(cm, xd, mbmi->segment_id, r);
+#if CONFIG_Q_SEGMENTATION
+    mbmi->q_segment_id = read_q_segment_id(cm, xd, mi_row, mi_col, r);
+#endif
 
     if (cm->delta_q_present_flag) {
       xd->current_qindex =
diff --git a/av1/encoder/aq_cyclicrefresh.c b/av1/encoder/aq_cyclicrefresh.c
index 12bdc80..22247a2 100644
--- a/av1/encoder/aq_cyclicrefresh.c
+++ b/av1/encoder/aq_cyclicrefresh.c
@@ -410,7 +410,11 @@
     int mi_col = sb_col_index * cm->mib_size;
     int qindex_thresh =
         cpi->oxcf.content == AOM_CONTENT_SCREEN
+#if CONFIG_Q_SEGMENTATION
+            ? av1_get_qindex(&cm->seg, CR_SEGMENT_ID_BOOST2, 0, cm->base_qindex)
+#else
             ? av1_get_qindex(&cm->seg, CR_SEGMENT_ID_BOOST2, cm->base_qindex)
+#endif
             : 0;
     assert(mi_row >= 0 && mi_row < cm->mi_rows);
     assert(mi_col >= 0 && mi_col < cm->mi_cols);
diff --git a/av1/encoder/av1_quantize.c b/av1/encoder/av1_quantize.c
index 4b886f2..701c1cb 100644
--- a/av1/encoder/av1_quantize.c
+++ b/av1/encoder/av1_quantize.c
@@ -1619,7 +1619,11 @@
 }
 
 void av1_init_plane_quantizers(const AV1_COMP *cpi, MACROBLOCK *x,
+#if CONFIG_Q_SEGMENTATION
+                               int segment_id, int q_segment_id) {
+#else
                                int segment_id) {
+#endif
   const AV1_COMMON *const cm = &cpi->common;
   MACROBLOCKD *const xd = &x->e_mbd;
   const QUANTS *const quants = &cpi->quants;
@@ -1636,7 +1640,12 @@
                 cm->delta_q_present_flag ? cm->base_qindex + xd->delta_qindex
                                          : cm->base_qindex));
 #endif
+#if CONFIG_Q_SEGMENTATION
+  const int qindex =
+      av1_get_qindex(&cm->seg, segment_id, q_segment_id, current_q_index);
+#else
   const int qindex = av1_get_qindex(&cm->seg, segment_id, current_q_index);
+#endif
   const int rdmult = av1_compute_rd_mult(cpi, qindex + cm->y_dc_delta_q);
   int i;
 #if CONFIG_AOM_QM
@@ -1711,7 +1720,12 @@
 void av1_frame_init_quantizer(AV1_COMP *cpi) {
   MACROBLOCK *const x = &cpi->td.mb;
   MACROBLOCKD *const xd = &x->e_mbd;
+#if CONFIG_Q_SEGMENTATION
+  av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id,
+                            xd->mi[0]->mbmi.q_segment_id);
+#else
   av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id);
+#endif
 }
 
 void av1_set_quantizer(AV1_COMMON *cm, int q) {
diff --git a/av1/encoder/av1_quantize.h b/av1/encoder/av1_quantize.h
index 4635f66..25cbf6d 100644
--- a/av1/encoder/av1_quantize.h
+++ b/av1/encoder/av1_quantize.h
@@ -99,7 +99,11 @@
 void av1_frame_init_quantizer(struct AV1_COMP *cpi);
 
 void av1_init_plane_quantizers(const struct AV1_COMP *cpi, MACROBLOCK *x,
+#if CONFIG_Q_SEGMENTATION
+                               int segment_id, int q_segment_id);
+#else
                                int segment_id);
+#endif
 
 void av1_build_quantizer(aom_bit_depth_t bit_depth, int y_dc_delta_q,
                          int uv_dc_delta_q, int uv_ac_delta_q,
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index f964d3b..1340cef 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -744,6 +744,79 @@
 }
 #endif  // CONFIG_LV_MAP
 
+#if CONFIG_Q_SEGMENTATION
+static int neg_interleave(int x, int ref, int max) {
+  const int diff = x - ref;
+  if (!ref) return x;
+  if (ref >= (max - 1)) return -diff;
+  if (2 * ref < max) {
+    if (abs(diff) <= ref) {
+      if (diff > 0)
+        return (diff << 1) - 1;
+      else
+        return ((-diff) << 1);
+    }
+    return x;
+  } else {
+    if (abs(diff) < (max - ref)) {
+      if (diff > 0)
+        return (diff << 1) - 1;
+      else
+        return ((-diff) << 1);
+    }
+    return (max - x) - 1;
+  }
+}
+
+static void write_q_segment_id(const AV1_COMMON *cm, int skip,
+                               const MB_MODE_INFO *const mbmi, aom_writer *w,
+                               const struct segmentation *seg,
+                               struct segmentation_probs *segp,
+                               BLOCK_SIZE bsize, int mi_row, int mi_col) {
+  int prev_ul = 0; /* Top left segment_id */
+  int prev_l = 0;  /* Current left segment_id */
+  int prev_u = 0;  /* Current top segment_id */
+
+  if (!seg->q_lvls) return;
+
+  MODE_INFO *const mi = cm->mi + mi_row * cm->mi_stride + mi_col;
+  int tinfo = mi->mbmi.boundary_info;
+  int above = (!(tinfo & TILE_ABOVE_BOUNDARY)) && ((mi_row - 1) >= 0);
+  int left = (!(tinfo & TILE_LEFT_BOUNDARY)) && ((mi_col - 1) >= 0);
+
+  if (above && left)
+    prev_ul =
+        get_segment_id(cm, cm->q_seg_map, BLOCK_4X4, mi_row - 1, mi_col - 1);
+
+  if (above)
+    prev_u = get_segment_id(cm, cm->q_seg_map, BLOCK_4X4, mi_row - 1, mi_col);
+
+  if (left)
+    prev_l = get_segment_id(cm, cm->q_seg_map, BLOCK_4X4, mi_row, mi_col - 1);
+
+  int cdf_num = pick_q_seg_cdf(prev_ul, prev_u, prev_l);
+  int pred = pick_q_seg_pred(prev_ul, prev_u, prev_l);
+
+  if (skip) {
+    set_q_segment_id(cm, cm->q_seg_map, mbmi->sb_type, mi_row, mi_col, pred);
+    return;
+  }
+
+  int coded_id = neg_interleave(mbmi->q_segment_id, pred, seg->q_lvls);
+
+#if CONFIG_NEW_MULTISYMBOL
+  aom_cdf_prob *pred_cdf = segp->q_seg_cdf[cdf_num];
+  aom_write_symbol(w, coded_id, pred_cdf, 8);
+#else
+  aom_prob pred_cdf = segp->q_seg_cdf[cdf_num];
+  aom_write(w, coded_id, pred_prob);
+#endif
+
+  set_q_segment_id(cm, cm->q_seg_map, bsize, mi_row, mi_col,
+                   mbmi->q_segment_id);
+}
+#endif
+
 static void write_segment_id(aom_writer *w, const struct segmentation *seg,
                              struct segmentation_probs *segp, int segment_id) {
   if (seg->enabled && seg->update_map) {
@@ -1319,6 +1392,9 @@
   }
 
   skip = write_skip(cm, xd, segment_id, mi, w);
+#if CONFIG_Q_SEGMENTATION
+  write_q_segment_id(cm, skip, mbmi, w, seg, segp, bsize, mi_row, mi_col);
+#endif
   if (cm->delta_q_present_flag) {
     int super_block_upper_left = ((mi_row & (cm->mib_size - 1)) == 0) &&
                                  ((mi_col & (cm->mib_size - 1)) == 0);
@@ -1644,6 +1720,9 @@
   if (seg->update_map) write_segment_id(w, seg, segp, mbmi->segment_id);
 
   const int skip = write_skip(cm, xd, mbmi->segment_id, mi, w);
+#if CONFIG_Q_SEGMENTATION
+  write_q_segment_id(cm, skip, mbmi, w, seg, segp, bsize, mi_row, mi_col);
+#endif
   if (cm->delta_q_present_flag) {
     int super_block_upper_left = ((mi_row & (cm->mib_size - 1)) == 0) &&
                                  ((mi_col & (cm->mib_size - 1)) == 0);
@@ -2747,6 +2826,32 @@
   }
 }
 
+#if CONFIG_Q_SEGMENTATION
+static void encode_q_segmentation(AV1_COMMON *cm,
+                                  struct aom_write_bit_buffer *wb) {
+  int i;
+  struct segmentation *seg = &cm->seg;
+
+  for (i = 0; i < MAX_SEGMENTS; i++) {
+    if (segfeature_active(seg, i, SEG_LVL_ALT_Q)) {
+      seg->q_lvls = 0;
+      return;
+    }
+  }
+
+  aom_wb_write_bit(wb, !!seg->q_lvls);
+  if (!seg->q_lvls) return;
+
+  encode_unsigned_max(wb, seg->q_lvls, MAX_SEGMENTS);
+
+  for (i = 0; i < seg->q_lvls; i++) {
+    const int val = seg->q_delta[i];
+    encode_unsigned_max(wb, abs(val), MAXQ);
+    aom_wb_write_bit(wb, val < 0);
+  }
+}
+#endif
+
 static void write_tx_mode(AV1_COMMON *cm, TX_MODE *mode,
                           struct aom_write_bit_buffer *wb) {
   if (cm->all_lossless) {
@@ -3916,6 +4021,9 @@
   encode_loopfilter(cm, wb);
   encode_quantization(cm, wb);
   encode_segmentation(cm, xd, wb);
+#if CONFIG_Q_SEGMENTATION
+  encode_q_segmentation(cm, wb);
+#endif
   {
     int delta_q_allowed = 1;
 #if !CONFIG_EXT_DELTA_Q
@@ -4270,6 +4378,9 @@
   encode_loopfilter(cm, wb);
   encode_quantization(cm, wb);
   encode_segmentation(cm, xd, wb);
+#if CONFIG_Q_SEGMENTATION
+  encode_q_segmentation(cm, wb);
+#endif
   {
     int delta_q_allowed = 1;
 #if !CONFIG_EXT_DELTA_Q
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 99d0d6d..b646d29 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -288,16 +288,30 @@
   xd->cfl->mi_col = mi_col;
 #endif
 
-  // Setup segment ID.
+  mbmi->segment_id = 0;
+#if CONFIG_Q_SEGMENTATION
+  mbmi->q_segment_id = 0;
+#endif
+
+// Setup segment ID.
+#if CONFIG_Q_SEGMENTATION
+  if (seg->enabled || seg->q_lvls) {
+#else
   if (seg->enabled) {
-    if (!cpi->vaq_refresh) {
+#endif
+    if (seg->enabled && !cpi->vaq_refresh) {
       const uint8_t *const map =
           seg->update_map ? cpi->segmentation_map : cm->last_frame_seg_map;
       mbmi->segment_id = get_segment_id(cm, map, bsize, mi_row, mi_col);
     }
+#if CONFIG_Q_SEGMENTATION
+    if (seg->q_lvls)
+      mbmi->q_segment_id =
+          get_segment_id(cm, cpi->q_seg_encoding_map, bsize, mi_row, mi_col);
+    av1_init_plane_quantizers(cpi, x, mbmi->segment_id, mbmi->q_segment_id);
+#else
     av1_init_plane_quantizers(cpi, x, mbmi->segment_id);
-  } else {
-    mbmi->segment_id = 0;
+#endif
   }
 }
 
@@ -497,11 +511,21 @@
 
 #if !CONFIG_EXT_DELTA_Q
   if (cpi->oxcf.aq_mode > NO_AQ && cpi->oxcf.aq_mode < DELTA_AQ)
+#if CONFIG_Q_SEGMENTATION
+    av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id,
+                              xd->mi[0]->mbmi.q_segment_id);
+#else
     av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id);
+#endif
 #else
   if (cpi->oxcf.aq_mode)
+#if CONFIG_Q_SEGMENTATION
+    av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id,
+                              xd->mi[0]->mbmi.q_segment_id);
+#else
     av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id);
 #endif
+#endif
 
   x->skip = ctx->skip;
 
@@ -728,12 +752,25 @@
 }
 
 static int set_segment_rdmult(const AV1_COMP *const cpi, MACROBLOCK *const x,
+#if CONFIG_Q_SEGMENTATION
+                              int8_t segment_id, int8_t q_segment_id) {
+#else
                               int8_t segment_id) {
+#endif
   int segment_qindex;
   const AV1_COMMON *const cm = &cpi->common;
+#if CONFIG_Q_SEGMENTATION
+  av1_init_plane_quantizers(cpi, x, segment_id, q_segment_id);
+#else
   av1_init_plane_quantizers(cpi, x, segment_id);
+#endif
   aom_clear_system_state();
+#if CONFIG_Q_SEGMENTATION
+  segment_qindex =
+      av1_get_qindex(&cm->seg, segment_id, q_segment_id, cm->base_qindex);
+#else
   segment_qindex = av1_get_qindex(&cm->seg, segment_id, cm->base_qindex);
+#endif
   return av1_compute_rd_mult(cpi, segment_qindex + cm->y_dc_delta_q);
 }
 
@@ -847,12 +884,24 @@
       const int energy =
           bsize <= BLOCK_16X16 ? x->mb_energy : av1_block_energy(cpi, x, bsize);
       mbmi->segment_id = av1_vaq_segment_id(energy);
-      // Re-initialise quantiser
+// Re-initialise quantiser
+#if CONFIG_Q_SEGMENTATION
+      av1_init_plane_quantizers(cpi, x, mbmi->segment_id, mbmi->q_segment_id);
+#else
       av1_init_plane_quantizers(cpi, x, mbmi->segment_id);
+#endif
     }
+#if CONFIG_Q_SEGMENTATION
+    x->rdmult =
+        set_segment_rdmult(cpi, x, mbmi->segment_id, mbmi->q_segment_id);
+  } else if (aq_mode == COMPLEXITY_AQ) {
+    x->rdmult =
+        set_segment_rdmult(cpi, x, mbmi->segment_id, mbmi->q_segment_id);
+#else
     x->rdmult = set_segment_rdmult(cpi, x, mbmi->segment_id);
   } else if (aq_mode == COMPLEXITY_AQ) {
     x->rdmult = set_segment_rdmult(cpi, x, mbmi->segment_id);
+#endif
   } else if (aq_mode == CYCLIC_REFRESH_AQ) {
     // If segment is boosted, use rdmult for that segment.
     if (cyclic_refresh_segment_id_boosted(mbmi->segment_id))
@@ -2161,24 +2210,66 @@
 
 #if CONFIG_FP_MB_STATS
 const int qindex_skip_threshold_lookup[BLOCK_SIZES] = {
-  0, 10, 10, 30, 40, 40, 60, 80, 80, 90, 100, 100, 120,
+  0,
+  10,
+  10,
+  30,
+  40,
+  40,
+  60,
+  80,
+  80,
+  90,
+  100,
+  100,
+  120,
 #if CONFIG_EXT_PARTITION
   // TODO(debargha): What are the correct numbers here?
-  130, 130, 150
+  130,
+  130,
+  150
 #endif  // CONFIG_EXT_PARTITION
 };
 const int qindex_split_threshold_lookup[BLOCK_SIZES] = {
-  0, 3, 3, 7, 15, 15, 30, 40, 40, 60, 80, 80, 120,
+  0,
+  3,
+  3,
+  7,
+  15,
+  15,
+  30,
+  40,
+  40,
+  60,
+  80,
+  80,
+  120,
 #if CONFIG_EXT_PARTITION
   // TODO(debargha): What are the correct numbers here?
-  160, 160, 240
+  160,
+  160,
+  240
 #endif  // CONFIG_EXT_PARTITION
 };
 const int complexity_16x16_blocks_threshold[BLOCK_SIZES] = {
-  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 6,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  1,
+  4,
+  4,
+  6,
 #if CONFIG_EXT_PARTITION
   // TODO(debargha): What are the correct numbers here?
-  8, 8, 10
+  8,
+  8,
+  10
 #endif  // CONFIG_EXT_PARTITION
 };
 
@@ -3301,8 +3392,16 @@
       xd->mi[0]->mbmi.current_q_index = current_qindex;
 #if !CONFIG_EXT_DELTA_Q
       xd->mi[0]->mbmi.segment_id = 0;
+#if CONFIG_Q_SEGMENTATION
+      xd->mi[0]->mbmi.q_segment_id = 0;
+#endif
 #endif  // CONFIG_EXT_DELTA_Q
+#if CONFIG_Q_SEGMENTATION
+      av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id,
+                                xd->mi[0]->mbmi.q_segment_id);
+#else
       av1_init_plane_quantizers(cpi, x, xd->mi[0]->mbmi.segment_id);
+#endif
 #if CONFIG_EXT_DELTA_Q
       if (cpi->oxcf.deltaq_mode == DELTA_Q_LF) {
         int j, k;
@@ -4014,7 +4113,11 @@
 
   for (i = 0; i < MAX_SEGMENTS; ++i) {
     const int qindex = cm->seg.enabled
+#if CONFIG_Q_SEGMENTATION
+                           ? av1_get_qindex(&cm->seg, i, i, cm->base_qindex)
+#else
                            ? av1_get_qindex(&cm->seg, i, cm->base_qindex)
+#endif
                            : cm->base_qindex;
     xd->lossless[i] = qindex == 0 && cm->y_dc_delta_q == 0 &&
                       cm->uv_dc_delta_q == 0 && cm->uv_ac_delta_q == 0;
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 421622c..453be1b 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -486,6 +486,9 @@
 #endif  // CONFIG_FARME_MARKER
 
   uint8_t *segmentation_map;
+#if CONFIG_Q_SEGMENTATION
+  uint8_t *q_seg_encoding_map;  // Must be allocated and set by AQs
+#endif
 
   CYCLIC_REFRESH *cyclic_refresh;
   ActiveMap active_map;
diff --git a/av1/encoder/rd.c b/av1/encoder/rd.c
index 3b687fe..c0c8438 100644
--- a/av1/encoder/rd.c
+++ b/av1/encoder/rd.c
@@ -505,9 +505,14 @@
 
   for (segment_id = 0; segment_id < MAX_SEGMENTS; ++segment_id) {
     const int qindex =
+#if CONFIG_Q_SEGMENTATION
+        clamp(
+            av1_get_qindex(&cm->seg, segment_id, segment_id, cm->base_qindex) +
+#else
         clamp(av1_get_qindex(&cm->seg, segment_id, cm->base_qindex) +
-                  cm->y_dc_delta_q,
-              0, MAXQ);
+#endif
+                cm->y_dc_delta_q,
+            0, MAXQ);
     const int q = compute_rd_thresh_factor(qindex, cm->bit_depth);
 
     for (bsize = 0; bsize < BLOCK_SIZES_ALL; ++bsize) {
diff --git a/build/cmake/aom_config_defaults.cmake b/build/cmake/aom_config_defaults.cmake
index 46c9310..488582d 100644
--- a/build/cmake/aom_config_defaults.cmake
+++ b/build/cmake/aom_config_defaults.cmake
@@ -177,6 +177,7 @@
 set(CONFIG_PALETTE_THROUGHPUT 1 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_PARALLEL_DEBLOCKING 1 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_Q_ADAPT_PROBS 0 CACHE NUMBER "AV1 experiment flag.")
+set(CONFIG_Q_SEGMENTATION 0 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_RAWBITS 0 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_RD_DEBUG 0 CACHE NUMBER "AV1 experiment flag.")
 set(CONFIG_RECT_TX_EXT 0 CACHE NUMBER "AV1 experiment flag.")
diff --git a/configure b/configure
index a19a3e9..561f6fb 100755
--- a/configure
+++ b/configure
@@ -338,6 +338,7 @@
     eob_first
     eighth_pel_mv_only
     mono_video
+    q_segmentation
 "
 CONFIG_LIST="
     dependency_tracking