Add FLEX_STEPS experiment to research-quant

*Replace strtok to custom fn
*followed recommendation of geritt to use snprintf
* Revert new 25 q-step based AC/DC Q derivation for FLEX_STEPS code path

Change-Id: I4d31ffdbd48345033895cc06aac74ed45e26d8ef
diff --git a/aom/aom_encoder.h b/aom/aom_encoder.h
index 33326ff..a21753b 100644
--- a/aom/aom_encoder.h
+++ b/aom/aom_encoder.h
@@ -354,6 +354,17 @@
    *
    */
   unsigned int reduced_tx_type_set;
+
+#if CONFIG_FLEX_STEPS
+  /*!\brief qstep mode
+   *
+   */
+  unsigned int qstep_mode;
+  /*!\brief qstep config path
+   *
+   */
+  const char *qstep_config_path;
+#endif
 } cfg_options_t;
 
 /*!\brief Encoded Frame Flags
diff --git a/aom/aomcx.h b/aom/aomcx.h
index db2a744..cf9e914 100644
--- a/aom/aomcx.h
+++ b/aom/aomcx.h
@@ -1307,6 +1307,14 @@
   /*!\brief Control to get frame info
    */
   AV1E_GET_FRAME_INFO = 165,
+
+#if CONFIG_FLEX_STEPS
+
+  /*!\brief Control to get frame info
+   */
+  AV1E_SET_QSTEP_CONFIG_PATH = 165,
+
+#endif
 };
 
 /*!\brief aom 1-D scaling mode
@@ -1848,6 +1856,11 @@
 AOM_CTRL_USE_TYPE(AV1E_SET_SUBGOP_CONFIG_PATH, const char *)
 #define AOM_CTRL_AV1E_SET_SUBGOP_CONFIG_PATH
 
+#if CONFIG_FLEX_STEPS
+AOM_CTRL_USE_TYPE(AV1E_SET_QSTEP_CONFIG_PATH, const char *)
+#define AOM_CTRL_AV1E_SET_QSTEP_CONFIG_PATH
+#endif
+
 /*!\endcond */
 /*! @} - end defgroup aom_encoder */
 #ifdef __cplusplus
diff --git a/apps/aomenc.c b/apps/aomenc.c
index 005b4cb..b0d79e2 100644
--- a/apps/aomenc.c
+++ b/apps/aomenc.c
@@ -51,6 +51,10 @@
 #include "third_party/libyuv/include/libyuv/scale.h"
 #endif
 
+#if CONFIG_FLEX_STEPS
+#include "aom_mem/aom_mem.h"
+#endif
+
 #if UINTPTR_MAX == 0xffffffff
 #define ENV_BITS "32 bit "
 #elif UINTPTR_MAX == 0xffffffffffffffff
@@ -853,6 +857,11 @@
             "If this option is not specified (default), the configurations "
             "are chosen by the encoder using a default algorithm.");
 
+#if CONFIG_FLEX_STEPS
+static const arg_def_t qstep_config_path =
+    ARG_DEF(NULL, "qstep-config-path", 1, "Path to the qStep cofig file");
+#endif
+
 static const arg_def_t *av1_args[] = { &cpu_used_av1,
                                        &auto_altref,
                                        &sharpness,
@@ -959,6 +968,9 @@
 #endif
                                        &subgop_config_str,
                                        &subgop_config_path,
+#if CONFIG_FLEX_STEPS
+                                       &qstep_config_path,
+#endif
                                        NULL };
 static const int av1_arg_ctrl_map[] = { AOME_SET_CPUUSED,
                                         AOME_SET_ENABLEAUTOALTREF,
@@ -1066,6 +1078,9 @@
 #endif
                                         AV1E_SET_SUBGOP_CONFIG_STR,
                                         AV1E_SET_SUBGOP_CONFIG_PATH,
+#if CONFIG_FLEX_STEPS
+                                        AV1E_SET_QSTEP_CONFIG_PATH,
+#endif
                                         0 };
 #endif  // CONFIG_AV1_ENCODER
 
@@ -1143,6 +1158,9 @@
 #endif
   const char *subgop_config_str;
   const char *subgop_config_path;
+#if CONFIG_FLEX_STEPS
+  const char *qstep_config_path;
+#endif
 };
 
 struct stream_state {
@@ -1427,6 +1445,13 @@
     return;
   }
 
+#if CONFIG_FLEX_STEPS
+  if (key == AV1E_SET_QSTEP_CONFIG_PATH) {
+    config->qstep_config_path = arg->val;
+    return;
+  }
+#endif
+
   // For target level, the settings should accumulate rather than overwrite,
   // so we simply append it.
   if (key == AV1E_SET_TARGET_SEQ_LEVEL_IDX) {
@@ -1935,7 +1960,10 @@
           "LoopRestortion (%d)\n",
           encoder_cfg->enable_deblocking, encoder_cfg->enable_cdef,
           encoder_cfg->enable_restoration);
-
+#if CONFIG_FLEX_STEPS
+  fprintf(stdout, "Tool setting (Quantization)    : Mode (%d)\n",
+          encoder_cfg->qstep_mode);
+#endif
   fprintf(stdout,
           "Tool setting (Others)          : Palette (%d), IntraBC (%d)\n",
           encoder_cfg->enable_palette, encoder_cfg->enable_intrabc);
@@ -2025,6 +2053,20 @@
   flags |= stream->config.use_16bit_internal ? AOM_CODEC_USE_HIGHBITDEPTH : 0;
   flags |= global->quiet ? 0 : AOM_CODEC_USE_PER_FRAME_STATS;
 
+#if CONFIG_FLEX_STEPS
+  struct stream_config *sc_cfg = &stream->config;
+  if (sc_cfg->qstep_config_path != NULL) {
+    sc_cfg->cfg.encoder_cfg.qstep_config_path =
+        (char *)aom_malloc((strlen(sc_cfg->qstep_config_path) + 1) *
+                           sizeof(*sc_cfg->qstep_config_path));
+    // strcpy((char *)sc_cfg->cfg.encoder_cfg.qstep_config_path,
+    // sc_cfg->qstep_config_path);
+    snprintf((char *)sc_cfg->cfg.encoder_cfg.qstep_config_path,
+             (strlen(sc_cfg->qstep_config_path) + 1) *
+                 sizeof(*sc_cfg->qstep_config_path),
+             "%s", sc_cfg->qstep_config_path);
+  }
+#endif
   /* Construct Encoder Context */
   aom_codec_enc_init(&stream->encoder, global->codec, &stream->config.cfg,
                      flags);
@@ -2059,6 +2101,13 @@
     AOM_CODEC_CONTROL_TYPECHECKED(&stream->encoder, AV1E_SET_SUBGOP_CONFIG_PATH,
                                   stream->config.subgop_config_path);
   }
+#if CONFIG_FLEX_STEPS
+  if (stream->config.qstep_config_path) {
+    AOM_CODEC_CONTROL_TYPECHECKED(&stream->encoder, AV1E_SET_QSTEP_CONFIG_PATH,
+                                  stream->config.qstep_config_path);
+  }
+#endif
+
 #if CONFIG_AV1_DECODER
   if (global->test_decode != TEST_DECODE_OFF) {
     aom_codec_iface_t *decoder = get_aom_decoder_by_short_name(
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 8b0d50e..27feb82 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -27,6 +27,10 @@
 #include "av1/encoder/ethread.h"
 #include "av1/encoder/firstpass.h"
 
+#if CONFIG_FLEX_STEPS
+#include "av1/encoder/encoder_utils.h"
+#endif
+
 #include "aom_dsp/psnr.h"
 #include "aom_ports/aom_timer.h"
 
@@ -54,6 +58,9 @@
   const char *vmaf_model_path;
   const char *subgop_config_str;
   const char *subgop_config_path;
+#if CONFIG_FLEX_STEPS
+  const char *qstep_config_path;
+#endif
   unsigned int qp;  // constant/constrained quality level
   unsigned int rc_max_intra_bitrate_pct;
   unsigned int rc_max_inter_bitrate_pct;
@@ -298,25 +305,28 @@
   "/usr/local/share/model/vmaf_v0.6.1.pkl",  // VMAF model path
   NULL,                                      // subgop_config_str
   NULL,                                      // subgop_config_path
-  40,                                        // qp
-  0,                                         // rc_max_intra_bitrate_pct
-  0,                                         // rc_max_inter_bitrate_pct
-  0,                                         // gf_cbr_boost_pct
-  0,                                         // lossless
-  1,                                         // enable_deblocking
-  1,                                         // enable_cdef
-  1,                                         // enable_restoration
-  0,                                         // force_video_mode
-  1,                                         // enable_obmc
-  3,                                         // enable_trellis_quant
-  0,                                         // enable_qm
-  DEFAULT_QM_Y,                              // qm_y
-  DEFAULT_QM_U,                              // qm_u
-  DEFAULT_QM_V,                              // qm_v
-  DEFAULT_QM_FIRST,                          // qm_min
-  DEFAULT_QM_LAST,                           // qm_max
-  1,                                         // max number of tile groups
-  0,                                         // mtu_size
+#if CONFIG_FLEX_STEPS
+  NULL,  // qstep_config_path
+#endif
+  40,                      // qp
+  0,                       // rc_max_intra_bitrate_pct
+  0,                       // rc_max_inter_bitrate_pct
+  0,                       // gf_cbr_boost_pct
+  0,                       // lossless
+  1,                       // enable_deblocking
+  1,                       // enable_cdef
+  1,                       // enable_restoration
+  0,                       // force_video_mode
+  1,                       // enable_obmc
+  3,                       // enable_trellis_quant
+  0,                       // enable_qm
+  DEFAULT_QM_Y,            // qm_y
+  DEFAULT_QM_U,            // qm_u
+  DEFAULT_QM_V,            // qm_v
+  DEFAULT_QM_FIRST,        // qm_min
+  DEFAULT_QM_LAST,         // qm_max
+  1,                       // max number of tile groups
+  0,                       // mtu_size
   AOM_TIMING_UNSPECIFIED,  // No picture timing signaling in bitstream
   0,                       // frame_parallel_decoding_mode
 #if !CONFIG_REMOVE_DUAL_FILTER
@@ -1142,6 +1152,9 @@
     }
   }
 
+#if CONFIG_FLEX_STEPS
+  oxcf->qstep_config_path = extra_cfg->qstep_config_path;
+#endif
   // Set tune related configuration.
   tune_cfg->tuning = extra_cfg->tuning;
   tune_cfg->vmaf_model_path = extra_cfg->vmaf_model_path;
@@ -2087,6 +2100,15 @@
   return update_extra_cfg(ctx, &extra_cfg);
 }
 
+#if CONFIG_FLEX_STEPS
+static aom_codec_err_t ctrl_set_qstep_config_path(aom_codec_alg_priv_t *ctx,
+                                                  va_list args) {
+  struct av1_extracfg extra_cfg = ctx->extra_cfg;
+  extra_cfg.qstep_config_path = CAST(AV1E_SET_QSTEP_CONFIG_PATH, args);
+  return update_extra_cfg(ctx, &extra_cfg);
+}
+#endif
+
 static aom_codec_err_t ctrl_set_film_grain_test_vector(
     aom_codec_alg_priv_t *ctx, va_list args) {
   struct av1_extracfg extra_cfg = ctx->extra_cfg;
@@ -2303,6 +2325,33 @@
       priv->cfg = *ctx->config.enc;
       ctx->config.enc = &priv->cfg;
     }
+#if CONFIG_FLEX_STEPS
+    QuantizationCfg *q_cfg = &priv->oxcf.q_cfg;
+    // initialize defualt mode
+    q_cfg->qStep_mode = 0;
+    q_cfg->num_qStep_intervals = 9;
+    int defaultQSteps[] = { 8, 8, 16, 32, 32, 32, 32, 32, 32, 32 };
+    for (int idx = 0; idx <= q_cfg->num_qStep_intervals; idx++) {
+      q_cfg->num_qsteps_in_interval[idx] = defaultQSteps[idx];
+    }
+
+    if (ctx->config.enc->encoder_cfg.qstep_config_path != NULL) {
+      priv->oxcf.qstep_config_path = (char *)aom_malloc(
+          (strlen(ctx->config.enc->encoder_cfg.qstep_config_path) + 1) *
+          sizeof(*ctx->config.enc->encoder_cfg.qstep_config_path));
+      // strcpy((char *)priv->oxcf.qstep_config_path,
+      // ctx->config.enc->encoder_cfg.qstep_config_path);
+      snprintf((char *)priv->oxcf.qstep_config_path,
+               (strlen(ctx->config.enc->encoder_cfg.qstep_config_path) + 1) *
+                   sizeof(*ctx->config.enc->encoder_cfg.qstep_config_path),
+               "%s", ctx->config.enc->encoder_cfg.qstep_config_path);
+      initialize_qstep_param(priv->oxcf.qstep_config_path, &priv->oxcf);
+    }
+
+    set_enc_qstep_table(&priv->oxcf);
+    priv->cfg.encoder_cfg.qstep_mode = q_cfg->qStep_mode;
+    // dump_qStep_table(q_cfg->qStep_mode, 0);
+#endif
 
     priv->extra_cfg = default_extra_cfg;
     aom_once(av1_initialize_enc);
@@ -2344,6 +2393,20 @@
           priv->frame_stats_buffer, ENCODE_STAGE, *num_lap_buffers, -1,
           &priv->stats_buf_context);
 
+#if CONFIG_FLEX_STEPS
+      if (res == AOM_CODEC_OK) {
+        if (priv->oxcf.qstep_config_path != NULL) {
+          priv->cpi->qstep_config_path =
+              (char *)aom_malloc((strlen(priv->oxcf.qstep_config_path) + 1) *
+                                 sizeof(*priv->oxcf.qstep_config_path));
+          // strcpy(priv->cpi->qstep_config_path, priv->oxcf.qstep_config_path);
+          snprintf(priv->cpi->qstep_config_path,
+                   (strlen(priv->oxcf.qstep_config_path) + 1) *
+                       sizeof(*priv->oxcf.qstep_config_path),
+                   "%s", priv->oxcf.qstep_config_path);
+        }
+      }
+#endif
       // Create another compressor if look ahead is enabled
       if (res == AOM_CODEC_OK && *num_lap_buffers) {
         res = create_context_and_bufferpool(
@@ -3298,6 +3361,9 @@
   { AV1E_SET_VBR_CORPUS_COMPLEXITY_LAP, ctrl_set_vbr_corpus_complexity_lap },
   { AV1E_ENABLE_SB_MULTIPASS_UNIT_TEST, ctrl_enable_sb_multipass_unit_test },
   { AV1E_ENABLE_SUBGOP_STATS, ctrl_enable_subgop_stats },
+#if CONFIG_FLEX_STEPS
+  { AV1E_SET_QSTEP_CONFIG_PATH, ctrl_set_qstep_config_path },
+#endif
 
   // Getters
   { AOME_GET_LAST_QUANTIZER, ctrl_get_quantizer },
@@ -3382,15 +3448,19 @@
       { 0 },                   // tile_heights
       0,                       // use_fixed_qp_offsets
       { -1, -1, -1, -1, -1 },  // fixed_qp_offsets
-      { 0, 128, 128, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      { 0, 128, 128, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1,   1,
 #if !CONFIG_REMOVE_DIST_WTD_COMP
         1,
 #endif  // !CONFIG_REMOVE_DIST_WTD_COMP
         1, 1,   1,   0, 0, 1, 1, 1, 1,
 #if !CONFIG_REMOVE_DUAL_FILTER
         1,
-#endif                                          // !CONFIG_REMOVE_DUAL_FILTER
+#endif  // !CONFIG_REMOVE_DUAL_FILTER
+#if CONFIG_FLEX_STEPS
+        1, 1,   1,   1, 1, 1, 1, 3, 1, 1, 0, 0, NULL },  // cfg
+#else
         1, 1,   1,   1, 1, 1, 1, 3, 1, 1, 0 },  // cfg
+#endif
   },
   {
       // NOLINT
@@ -3457,15 +3527,19 @@
       { 0 },                   // tile_heights
       0,                       // use_fixed_qp_offsets
       { -1, -1, -1, -1, -1 },  // fixed_qp_offsets
-      { 0, 128, 128, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+      { 0, 128, 128, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1,   1,
 #if !CONFIG_REMOVE_DIST_WTD_COMP
         1,
 #endif  // !CONFIG_REMOVE_DIST_WTD_COMP
         1, 1,   1,   0, 0, 1, 1, 1, 1,
 #if !CONFIG_REMOVE_DUAL_FILTER
         1,
-#endif                                          // !CONFIG_REMOVE_DUAL_FILTER
+#endif  // !CONFIG_REMOVE_DUAL_FILTER
+#if CONFIG_FLEX_STEPS
+        1, 1,   1,   1, 1, 1, 1, 3, 1, 1, 0, 0, NULL },  // cfg
+#else
         1, 1,   1,   1, 1, 1, 1, 3, 1, 1, 0 },  // cfg
+#endif
   },
 };
 
diff --git a/av1/common/av1_common_int.h b/av1/common/av1_common_int.h
index 2f4c22e..4017e77 100644
--- a/av1/common/av1_common_int.h
+++ b/av1/common/av1_common_int.h
@@ -309,6 +309,26 @@
   int8_t base_y_dc_delta_q;
   int8_t base_uv_dc_delta_q;
 #endif  // CONFIG_EXTQUANT
+#if CONFIG_FLEX_STEPS
+  int qStep_mode;
+  // mode 0,1,2
+  int num_qStep_intervals;
+  // mode 0, 1
+  int num_qsteps_in_interval[MAX_NUM_Q_STEP_INTERVALS];
+  // mode 2
+  int num_qStep_levels;
+  int qSteps_level[MAX_NUM_Q_STEP_VAL];
+  // For mode 3
+  // template tables
+  int num_table_templates_minus1;
+  int num_entries_in_table_minus1[MAX_NUM_Q_STEP_VAL];
+  int qSteps_level_in_table[MAX_NUM_TABLES][MAX_NUM_Q_STEP_VAL];
+  // derivation
+  int template_table_idx[MAX_NUM_Q_STEP_INTERVALS];
+  int table_start_region_idx[MAX_NUM_Q_STEP_VAL];
+  int num_qsteps_in_table[MAX_NUM_Q_STEP_INTERVALS];
+#endif  // CONFIG_FLEX_STEPS
+
   uint8_t film_grain_params_present;
 
   // Operating point info.
diff --git a/av1/common/quant_common.c b/av1/common/quant_common.c
index 44bb833..d11838e 100644
--- a/av1/common/quant_common.c
+++ b/av1/common/quant_common.c
@@ -122,6 +122,9 @@
   57926, 59624, 61371
 };
 #else
+#if CONFIG_FLEX_STEPS
+static uint16_t ac_qlookup_QTX[QINDEX_RANGE_8_BITS];
+#else
 //      32,                                              q_index = 0
 // Q =  40 * 2^((q_index - 1)/24)                        q_index in [1, 24]
 //      Q[(q_index - 1) % 24) + 1] * 2^((q_index-1)/24)  q_index in [25, 255]
@@ -130,6 +133,7 @@
   53,    55,    57,    58,    60,    62,    63,    65,    67,    69,    71,
   73,    76,    78
 };
+#endif
 #ifndef NDEBUG
 static const uint16_t ac_qlookup_QTX_full[QINDEX_RANGE_8_BITS] = {
   32,    40,    41,    42,    44,    45,    46,    48,    49,    50,    52,
@@ -266,6 +270,110 @@
 // addition, the minimum allowable quantizer is 4; smaller values will
 // underflow to 0 in the actual quantization routines.
 
+#if CONFIG_FLEX_STEPS
+int getShiftVal(int val) {
+  int logVal = 0;
+  while (val > 0) {
+    val >>= 1;
+    logVal++;
+  }
+  return logVal - 1;
+}
+void set_qStep_table_mode_0_1(int qStep_mode, int num_qStep_intervals,
+                              int *num_qsteps_in_interval) {
+  int mask = (1 << 16) - 1;
+  assert(qStep_mode == 0 || qStep_mode == 1);
+  assert(num_qStep_intervals >= 1);
+
+  (void)qStep_mode;
+
+  ac_qlookup_QTX[0] = 32;
+  int qIdx = 1;
+  int Initial_qIdx_factor = 32;
+
+  for (int i = 0; i <= num_qStep_intervals && (qIdx < QINDEX_RANGE_8_BITS);
+       i++) {
+    int idx = 1;
+    int log_val = getShiftVal(num_qsteps_in_interval[i]);
+    while (idx <= num_qsteps_in_interval[i]) {
+      ac_qlookup_QTX[qIdx] = CLIP(
+          ((Initial_qIdx_factor *
+            ((num_qsteps_in_interval[i] + (idx % num_qsteps_in_interval[i]))
+             << (idx >> log_val))) >>
+           log_val),
+          0, mask);
+      qIdx++;
+      idx++;
+    }
+    Initial_qIdx_factor = ac_qlookup_QTX[qIdx - 1];
+  }
+}
+
+void set_qStep_table_mode_2(int qStep_mode, int num_qStep_levels,
+                            int *qSteps_level) {
+  int mask = (1 << 16) - 1;
+  assert(qStep_mode == 2);
+  (void)qStep_mode;
+
+  int qIdx = 0;
+  num_qStep_levels = num_qStep_levels + 1;
+  int num_periods = (int)(QINDEX_RANGE_8_BITS / num_qStep_levels);
+  if ((QINDEX_RANGE_8_BITS - (num_periods * num_qStep_levels)) > 0)
+    num_periods = num_periods + 1;
+
+  for (int i = 0; i < num_periods; i++) {
+    int idx = 0;
+    while ((idx < num_qStep_levels) && (qIdx < QINDEX_RANGE_8_BITS)) {
+      ac_qlookup_QTX[qIdx] = CLIP((qSteps_level[idx] << i), 0, mask);
+      qIdx++;
+      idx++;
+    }
+  }
+}
+
+void set_qStep_table_mode_3(int qStep_mode, int num_qStep_intervals,
+                            int *template_table_idx,
+                            int *table_start_region_idx,
+                            int *num_qsteps_in_table,
+                            int *qSteps_level_in_table) {
+  int mask = (1 << 16) - 1;
+  assert(qStep_mode == 3);
+  (void)qStep_mode;
+
+  int qIdx = 0;
+  // int Initial_qIdx_factor = 0;
+
+  for (int k = 0; k <= num_qStep_intervals && (qIdx < QINDEX_RANGE_8_BITS);
+       k++) {
+    for (int idx = table_start_region_idx[k];
+         idx <= num_qsteps_in_table[k] + table_start_region_idx[k]; idx++) {
+      ac_qlookup_QTX[qIdx] =
+          CLIP(*(qSteps_level_in_table +
+                 template_table_idx[k] * (MAX_NUM_Q_STEP_VAL) + idx),
+               0, mask);
+      qIdx++;
+    }
+    // Initial_qIdx_factor =  ac_qlookup_QTX[qIdx-1];
+  }
+}
+
+#if 0
+void dump_qStep_table(int mode, int expt) {
+  FILE *fp;
+  char fname[500];
+  snprintf(fname, sizeof(fname), "log_QMatrix_mode%d_expt_%d", mode, expt);
+  fp = fopen(fname, "wb");
+  for (int ii = 0; ii < 256; ii++) {
+    fprintf(fp, "%d,", ac_qlookup_QTX[ii]);
+    if (ii % 8 == 0) {
+      fprintf(fp, "\n");
+    }
+  }
+  fclose(fp);
+}
+#endif
+#endif
+
 #if CONFIG_EXTQUANT
 int32_t av1_dc_quant_QTX(int qindex, int delta, int base_dc_delta_q,
                          aom_bit_depth_t bit_depth) {
@@ -290,6 +398,12 @@
   if (q_clamped > MAXQ_8_BITS) {
     switch (bit_depth) {
       case AOM_BITS_8: assert(q_clamped <= MAXQ_8_BITS);
+#if CONFIG_FLEX_STEPS
+      case AOM_BITS_10:
+        return 4 * (int32_t)ac_qlookup_QTX[q_clamped - qindex_offset];
+      case AOM_BITS_12:
+        return 16 * (int32_t)ac_qlookup_QTX[q_clamped - qindex_offset];
+#else
       case AOM_BITS_10: {
         int32_t Q;
         if ((q_clamped - qindex_offset) < 25) {
@@ -312,12 +426,16 @@
         }
         return 16 * Q;
       }
+#endif
       default:
         assert(0 &&
                "bit_depth should be AOM_BITS_8, AOM_BITS_10 or AOM_BITS_12");
         return -1;
     }
   } else {
+#if CONFIG_FLEX_STEPS
+    return (int32_t)ac_qlookup_QTX[q_clamped];
+#else
     int32_t Q;
     if (q_clamped < 25) {
       Q = ac_qlookup_QTX[q_clamped];
@@ -326,6 +444,7 @@
       assert(Q == ac_qlookup_QTX_full[q_clamped]);
     }
     return Q;
+#endif
   }
 }
 #else
@@ -365,6 +484,12 @@
   if (q_clamped > MAXQ_8_BITS) {
     switch (bit_depth) {
       case AOM_BITS_8: assert(q_clamped <= MAXQ_8_BITS);
+#if CONFIG_FLEX_STEPS
+      case AOM_BITS_10:
+        return 4 * (int32_t)ac_qlookup_QTX[q_clamped - qindex_offset];
+      case AOM_BITS_12:
+        return 16 * (int32_t)ac_qlookup_QTX[q_clamped - qindex_offset];
+#else
       case AOM_BITS_10: {
         int32_t Q;
         if ((q_clamped - qindex_offset) < 25) {
@@ -387,12 +512,16 @@
         }
         return 16 * Q;
       }
+#endif
       default:
         assert(0 &&
                "bit_depth should be AOM_BITS_8, AOM_BITS_10 or AOM_BITS_12");
         return -1;
     }
   } else {
+#if CONFIG_FLEX_STEPS
+    return (int32_t)ac_qlookup_QTX[q_clamped];
+#else
     int32_t Q;
     if (q_clamped < 25) {
       Q = ac_qlookup_QTX[q_clamped];
@@ -401,6 +530,7 @@
       assert(Q == ac_qlookup_QTX_full[q_clamped]);
     }
     return Q;
+#endif
   }
 }
 #else
diff --git a/av1/common/quant_common.h b/av1/common/quant_common.h
index 5cde276..2651167 100644
--- a/av1/common/quant_common.h
+++ b/av1/common/quant_common.h
@@ -54,6 +54,23 @@
 struct CommonQuantParams;
 struct macroblockd;
 
+#if CONFIG_FLEX_STEPS
+#define MAX_NUM_Q_STEP_INTERVALS 16
+#define MAX_NUM_TABLES 8
+#define MAX_NUM_Q_STEP_VAL 256
+
+void set_qStep_table_mode_0_1(int qStep_mode, int num_qStep_intervals,
+                              int *num_qsteps_in_interval);
+void set_qStep_table_mode_2(int qStep_mode, int num_qStep_levels,
+                            int *qSteps_level);
+void set_qStep_table_mode_3(int qStep_mode, int num_qStep_intervals,
+                            int *template_table_idx,
+                            int *table_start_region_idx,
+                            int *num_qsteps_in_table,
+                            int *qSteps_level_in_table);
+// void dump_qStep_table(int mode, int expt);  // kk delete
+#endif
+
 #if CONFIG_EXTQUANT
 int32_t av1_dc_quant_QTX(int qindex, int delta, int base_dc_delta_q,
                          aom_bit_depth_t bit_depth);
diff --git a/av1/decoder/decodeframe.c b/av1/decoder/decodeframe.c
index 01aed0d..ef97d70 100644
--- a/av1/decoder/decodeframe.c
+++ b/av1/decoder/decodeframe.c
@@ -4274,6 +4274,55 @@
   seq_params->enable_restoration = aom_rb_read_bit(rb);
 }
 
+#if CONFIG_FLEX_STEPS
+void av1_read_qStep_config(AV1_COMMON *cm, struct aom_read_bit_buffer *rb,
+                           SequenceHeader *seq_params) {
+  (void)cm;
+  seq_params->qStep_mode = aom_rb_read_literal(rb, 2);
+  if (seq_params->qStep_mode == 0 || seq_params->qStep_mode == 1) {
+    seq_params->num_qStep_intervals = aom_rb_read_literal(rb, 4);
+    seq_params->num_qsteps_in_interval[0] = aom_rb_read_uvlc(rb);
+    for (int idx = 1; idx <= seq_params->num_qStep_intervals; idx++) {
+      // int delta_qsteps_sign = aom_rb_read_bit(rb);
+      int delta_val = aom_rb_read_uvlc(rb);
+      assert(delta_val >= 0);
+      seq_params->num_qsteps_in_interval[idx] =
+          delta_val + seq_params->num_qsteps_in_interval[idx - 1];
+    }
+  } else if (seq_params->qStep_mode == 2) {
+    seq_params->num_qStep_levels = aom_rb_read_literal(rb, 8);
+    seq_params->qSteps_level[0] = aom_rb_read_uvlc(rb);
+    for (int idx = 1; idx <= seq_params->num_qStep_levels; idx++) {
+      int delta_qsteps_sign = aom_rb_read_bit(rb);
+      int delta_val = aom_rb_read_uvlc(rb);
+      seq_params->qSteps_level[idx] =
+          (delta_val * (delta_qsteps_sign ? -1 : 1)) +
+          seq_params->qSteps_level[idx - 1];
+    }
+  } else if (seq_params->qStep_mode == 3) {
+    seq_params->num_table_templates_minus1 = aom_rb_read_literal(rb, 2);
+    for (int i = 0; i <= seq_params->num_table_templates_minus1; i++) {
+      seq_params->num_entries_in_table_minus1[i] = aom_rb_read_literal(rb, 8);
+      seq_params->qSteps_level_in_table[i][0] = aom_rb_read_uvlc(rb);
+      for (int idx = 1; idx <= seq_params->num_entries_in_table_minus1[i];
+           idx++) {
+        int delta_qsteps_sign = aom_rb_read_bit(rb);
+        int delta_val = aom_rb_read_uvlc(rb);
+        seq_params->qSteps_level_in_table[i][idx] =
+            (delta_val * (delta_qsteps_sign ? -1 : 1)) +
+            seq_params->qSteps_level_in_table[i][idx - 1];
+      }
+    }
+    seq_params->num_qStep_intervals = aom_rb_read_literal(rb, 4);
+    for (int idx = 0; idx <= seq_params->num_qStep_intervals; idx++) {
+      seq_params->template_table_idx[idx] = aom_rb_read_literal(rb, 2);
+      seq_params->num_qsteps_in_table[idx] = aom_rb_read_literal(rb, 8);
+      seq_params->table_start_region_idx[idx] = aom_rb_read_literal(rb, 8);
+    }
+  }
+}
+#endif
+
 static int read_global_motion_params(WarpedMotionParams *params,
                                      const WarpedMotionParams *ref_params,
                                      struct aom_read_bit_buffer *rb,
@@ -5175,6 +5224,26 @@
   }
   xd->global_motion = cm->global_motion;
 
+#if CONFIG_FLEX_STEPS
+  SequenceHeader *const seq_params = &cm->seq_params;
+  if ((seq_params->qStep_mode == 0) || (seq_params->qStep_mode == 1)) {
+    set_qStep_table_mode_0_1(seq_params->qStep_mode,
+                             seq_params->num_qStep_intervals,
+                             &seq_params->num_qsteps_in_interval[0]);
+  } else if (seq_params->qStep_mode == 2) {
+    set_qStep_table_mode_2(seq_params->qStep_mode, seq_params->num_qStep_levels,
+                           &seq_params->qSteps_level[0]);
+  } else if (seq_params->qStep_mode == 3) {
+    set_qStep_table_mode_3(seq_params->qStep_mode,
+                           seq_params->num_qStep_intervals,
+                           &seq_params->template_table_idx[0],
+                           &seq_params->table_start_region_idx[0],
+                           &seq_params->num_qsteps_in_table[0],
+                           (int *)seq_params->qSteps_level_in_table);
+  }
+  // dump_qStep_table(seq_params->qStep_mode, 1);
+#endif
+
   read_uncompressed_header(pbi, rb);
 
   if (trailing_bits_present) av1_check_trailing_bits(pbi, rb);
diff --git a/av1/decoder/decodeframe.h b/av1/decoder/decodeframe.h
index 95b3c9f..d7898dc 100644
--- a/av1/decoder/decodeframe.h
+++ b/av1/decoder/decodeframe.h
@@ -26,6 +26,13 @@
 void av1_read_sequence_header(AV1_COMMON *cm, struct aom_read_bit_buffer *rb,
                               SequenceHeader *seq_params);
 
+#if CONFIG_FLEX_STEPS
+// Implements the qStep_config() function in the spec. Reports errors by
+// calling rb->error_handler() or aom_internal_error().
+void av1_read_qStep_config(AV1_COMMON *cm, struct aom_read_bit_buffer *rb,
+                           SequenceHeader *seq_params);
+#endif
+
 void av1_read_frame_size(struct aom_read_bit_buffer *rb, int num_bits_width,
                          int num_bits_height, int *width, int *height);
 BITSTREAM_PROFILE av1_read_profile(struct aom_read_bit_buffer *rb);
diff --git a/av1/decoder/obu.c b/av1/decoder/obu.c
index 0c9cce4..0e0889a 100644
--- a/av1/decoder/obu.c
+++ b/av1/decoder/obu.c
@@ -240,6 +240,10 @@
 
   av1_read_sequence_header(cm, rb, seq_params);
 
+#if CONFIG_FLEX_STEPS
+  av1_read_qStep_config(cm, rb, seq_params);
+#endif
+
   av1_read_color_config(rb, pbi->allow_lowbitdepth, seq_params, &cm->error);
   if (!(seq_params->subsampling_x == 0 && seq_params->subsampling_y == 0) &&
       !(seq_params->subsampling_x == 1 && seq_params->subsampling_y == 1) &&
diff --git a/av1/encoder/bitstream.c b/av1/encoder/bitstream.c
index c5e50e2..d3a301d 100644
--- a/av1/encoder/bitstream.c
+++ b/av1/encoder/bitstream.c
@@ -2705,6 +2705,56 @@
   aom_wb_write_bit(wb, seq_params->enable_restoration);
 }
 
+#if CONFIG_FLEX_STEPS
+static AOM_INLINE void write_qStepinfo(const SequenceHeader *const seq_params,
+                                       struct aom_write_bit_buffer *wb) {
+  aom_wb_write_literal(wb, seq_params->qStep_mode, 2);
+  if (seq_params->qStep_mode == 0 || seq_params->qStep_mode == 1) {
+    aom_wb_write_literal(wb, seq_params->num_qStep_intervals, 4);
+    aom_wb_write_uvlc(wb, seq_params->num_qsteps_in_interval[0]);
+    for (int idx = 1; idx <= seq_params->num_qStep_intervals; idx++) {
+      int delta_qsteps = seq_params->num_qsteps_in_interval[idx] -
+                         seq_params->num_qsteps_in_interval[idx - 1];
+      assert(delta_qsteps >= 0);
+      // int delta_qsteps_sign = (delta_qsteps <  0) ? 1 : 0;
+      // aom_wb_write_bit(wb, delta_qsteps_sign);
+      aom_wb_write_uvlc(wb, abs(delta_qsteps));
+    }
+  } else if (seq_params->qStep_mode == 2) {
+    aom_wb_write_literal(wb, seq_params->num_qStep_levels, 8);
+    aom_wb_write_uvlc(wb, seq_params->qSteps_level[0]);
+    for (int idx = 1; idx <= seq_params->num_qStep_levels; idx++) {
+      int delta_qsteps =
+          seq_params->qSteps_level[idx] - seq_params->qSteps_level[idx - 1];
+      int delta_qsteps_sign = (delta_qsteps < 0) ? 1 : 0;
+      aom_wb_write_bit(wb, delta_qsteps_sign);
+      aom_wb_write_uvlc(wb, abs(delta_qsteps));
+    }
+  } else if (seq_params->qStep_mode == 3) {
+    aom_wb_write_literal(wb, seq_params->num_table_templates_minus1, 2);
+    for (int i = 0; i <= seq_params->num_table_templates_minus1; i++) {
+      aom_wb_write_literal(wb, seq_params->num_entries_in_table_minus1[i], 8);
+      aom_wb_write_uvlc(wb, seq_params->qSteps_level_in_table[i][0]);
+      for (int idx = 1; idx <= seq_params->num_entries_in_table_minus1[i];
+           idx++) {
+        int delta_qsteps = seq_params->qSteps_level_in_table[i][idx] -
+                           seq_params->qSteps_level_in_table[i][idx - 1];
+        int delta_qsteps_sign = (delta_qsteps < 0) ? 1 : 0;
+        aom_wb_write_bit(wb, delta_qsteps_sign);
+        aom_wb_write_uvlc(wb, abs(delta_qsteps));
+      }
+    }
+    aom_wb_write_literal(wb, seq_params->num_qStep_intervals, 4);
+    for (int idx = 0; idx <= seq_params->num_qStep_intervals; idx++) {
+      aom_wb_write_literal(wb, seq_params->template_table_idx[idx], 2);
+      aom_wb_write_literal(wb, seq_params->num_qsteps_in_table[idx], 8);
+      aom_wb_write_literal(wb, seq_params->table_start_region_idx[idx], 8);
+    }
+  }
+}
+
+#endif
+
 static AOM_INLINE void write_global_motion_params(
     const WarpedMotionParams *params, const WarpedMotionParams *ref_params,
     struct aom_write_bit_buffer *wb, int allow_hp) {
@@ -3439,6 +3489,9 @@
   }
   write_sequence_header(seq_params, &wb);
 
+#if CONFIG_FLEX_STEPS
+  write_qStepinfo(seq_params, &wb);
+#endif
   write_color_config(seq_params, &wb);
 
   aom_wb_write_bit(&wb, seq_params->film_grain_params_present);
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index d5b5552..3cb0af5 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -569,6 +569,10 @@
 
   av1_update_film_grain_parameters(cpi, oxcf);
 
+#if CONFIG_FLEX_STEPS
+  update_qstep_parameters(cpi, oxcf);
+#endif
+
   // Single thread case: use counts in common.
   cpi->td.counts = &cpi->counts;
 
@@ -604,6 +608,31 @@
   return strcmp(a, b);
 }
 
+#if CONFIG_FLEX_STEPS
+void set_enc_qstep_table(const AV1EncoderConfig *oxcf) {
+  QuantizationCfg *q_cfg = (QuantizationCfg *)&oxcf->q_cfg;
+
+  if ((q_cfg->qStep_mode == 0) || (q_cfg->qStep_mode == 1)) {
+    set_qStep_table_mode_0_1(q_cfg->qStep_mode, q_cfg->num_qStep_intervals,
+                             &q_cfg->num_qsteps_in_interval[0]);
+  } else if (q_cfg->qStep_mode == 2) {
+    set_qStep_table_mode_2(q_cfg->qStep_mode, q_cfg->num_qStep_levels,
+                           &q_cfg->qSteps_level[0]);
+  } else if (q_cfg->qStep_mode == 3) {
+    set_qStep_table_mode_3(
+        q_cfg->qStep_mode, q_cfg->num_qStep_intervals,
+        &q_cfg->template_table_idx[0], &q_cfg->table_start_region_idx[0],
+        &q_cfg->num_qsteps_in_table[0], (int *)q_cfg->qSteps_level_in_table);
+  }
+}
+
+void initialize_qstep_param(const char *qStep_fname, AV1EncoderConfig *oxcf) {
+  // Wrapper added to extend the custom initializations
+  // not in the qstep config file.
+  process_qStep_config_from_file(qStep_fname, oxcf);
+}
+#endif
+
 void av1_change_config(struct AV1_COMP *cpi, const AV1EncoderConfig *oxcf) {
   AV1_COMMON *const cm = &cpi->common;
   SequenceHeader *const seq_params = &cm->seq_params;
@@ -666,6 +695,32 @@
         10;  // Default value (not signaled)
   }
 
+#if CONFIG_FLEX_STEPS
+  bool qstep_config_changed = false;
+  if (aom_strcmp(cpi->qstep_config_path, oxcf->qstep_config_path)) {
+    assert(1);  // kk hack
+    aom_free(cpi->qstep_config_path);
+    cpi->qstep_config_path = NULL;
+    if (oxcf->qstep_config_path != NULL) {
+      cpi->qstep_config_path =
+          (char *)aom_malloc((strlen(oxcf->qstep_config_path) + 1) *
+                             sizeof(*oxcf->qstep_config_path));
+      // strcpy(cpi->qstep_config_path, oxcf->qstep_config_path);
+      snprintf(cpi->qstep_config_path,
+               (strlen(oxcf->qstep_config_path) + 1) *
+                   sizeof(*oxcf->qstep_config_path),
+               "%s", oxcf->qstep_config_path);
+    }
+    qstep_config_changed = true;
+  }
+  if (qstep_config_changed) {
+    initialize_qstep_param(oxcf->qstep_config_path, (AV1EncoderConfig *)oxcf);
+    set_enc_qstep_table(oxcf);
+  }
+  update_qstep_parameters(cpi, oxcf);
+
+#endif
+
   av1_update_film_grain_parameters(cpi, oxcf);
 
   cpi->oxcf = *oxcf;
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 5b726bb..abc7a87 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -48,6 +48,10 @@
 #include "av1/encoder/tpl_model.h"
 #include "av1/encoder/av1_noise_estimate.h"
 
+#if CONFIG_FLEX_STEPS
+#include "av1/common/quant_common.h"
+#endif
+
 #if CONFIG_INTERNAL_STATS
 #include "aom_dsp/ssim.h"
 #endif
@@ -691,6 +695,25 @@
   bool enable_chroma_deltaq;
   // Indicates if encoding with quantization matrices should be enabled.
   bool using_qm;
+#if CONFIG_FLEX_STEPS
+  int qStep_mode;
+  // mode 0 and mode 1
+  int num_qStep_intervals;
+  int num_qsteps_in_interval[MAX_NUM_Q_STEP_INTERVALS];
+  // mode 2
+  int num_qStep_levels;
+  int qSteps_level[MAX_NUM_Q_STEP_VAL];
+  // For mode 3
+  // template tables
+  int num_table_templates_minus1;
+  int num_entries_in_table_minus1[MAX_NUM_Q_STEP_VAL];
+  int qSteps_level_in_table[MAX_NUM_TABLES][MAX_NUM_Q_STEP_VAL];
+  // derivation
+  int template_table_idx[MAX_NUM_Q_STEP_INTERVALS];
+  int table_start_region_idx[MAX_NUM_Q_STEP_VAL];
+  int num_qsteps_in_table[MAX_NUM_Q_STEP_INTERVALS];
+
+#endif  // CONFIG_FLEX_STEPS
 } QuantizationCfg;
 
 /*!\endcond */
@@ -933,6 +956,11 @@
   // SubGOP config.
   const char *subgop_config_path;
 
+#if CONFIG_FLEX_STEPS
+  // qStep config.
+  const char *qstep_config_path;
+#endif
+
   // Configuration related to encoder toolsets.
   ToolCfg tool_cfg;
 
@@ -2331,6 +2359,13 @@
    */
   char *subgop_config_path;
 
+#if CONFIG_FLEX_STEPS
+  /*!
+   * qStep configuration file path
+   */
+  char *qstep_config_path;
+#endif
+
   /*!
    * Information related to subGOP configuration if specified.
    */
@@ -2749,6 +2784,11 @@
 void av1_init_seq_coding_tools(SequenceHeader *seq, AV1_COMMON *cm,
                                const AV1EncoderConfig *oxcf, int use_svc);
 
+#if CONFIG_FLEX_STEPS
+void set_enc_qstep_table(const AV1EncoderConfig *oxcf);
+void initialize_qstep_param(const char *qStep_fname, AV1EncoderConfig *oxcf);
+#endif
+
 /*!\endcond */
 
 /*!\brief Obtain the raw frame data
diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c
index f4e4630..3615eed 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -29,6 +29,12 @@
 #include "av1/encoder/tune_vmaf.h"
 #endif
 
+#if CONFIG_FLEX_STEPS
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#endif
+
 #define MIN_BOOST_COMBINE_FACTOR 4.0
 #define MAX_BOOST_COMBINE_FACTOR 12.0
 
@@ -452,6 +458,264 @@
   memset(pars->ar_coeffs_cb, 0, sizeof(pars->ar_coeffs_cb));
 }
 
+#if CONFIG_FLEX_STEPS
+/* Note the read_token_after and readline helper functions are same
+   to what is defined in subgop.c as static functins. This needs to be unified
+   <TBD> */
+static char *qStep_strtok_r(char *str, const char *delim, char **saveptr) {
+  if (str == NULL) return NULL;
+  if (strlen(str) == 0) return NULL;
+  char *ptr = str;
+  char *x = strstr(str, delim);
+  if (x) {
+    *x = 0;
+    if (saveptr) *saveptr = x + strlen(delim);
+  } else {
+    if (saveptr) *saveptr = NULL;
+    return ptr;
+  }
+  return ptr;
+}
+
+static char *qStep_read_token_after(char *str, const char *delim,
+                                    char **saveptr) {
+  if (str == NULL) return NULL;
+  if (strlen(str) == 0) return NULL;
+  char *ptr = str;
+  char *x = strstr(str, delim);
+  if (x) {
+    ptr = x + strlen(delim);
+    while (*x != 0 && !isspace(*x)) x++;
+    *x = 0;
+    if (saveptr) *saveptr = x + 1;
+    return ptr;
+  } else {
+    if (saveptr) *saveptr = str;
+    return NULL;
+  }
+}
+
+static bool qStep_readline(char *buf, int size, FILE *fp) {
+  buf[0] = '\0';
+  buf[size - 1] = '\0';
+  char *tmp;
+  while (1) {
+    if (fgets(buf, size, fp) == NULL) {
+      *buf = '\0';
+      return false;
+    } else {
+      if ((tmp = strrchr(buf, '\n')) != NULL) *tmp = '\0';
+      if ((tmp = strchr(buf, '#')) != NULL) *tmp = '\0';
+      for (int i = 0; i < (int)strlen(buf); ++i) {
+        if (!isspace(buf[i])) return true;
+      }
+    }
+  }
+  return true;
+}
+
+int process_qStep_config_from_file(const char *paramfile,
+                                   AV1EncoderConfig *oxcf) {
+  int qStepMode;
+  char *token;
+  char *str;
+  char line[8192];
+  int linesize = 8192;
+
+  if (!paramfile) {
+    return 1;
+  }
+
+  if (!strlen(paramfile)) {
+    return 1;
+  }
+
+  FILE *fp = fopen(paramfile, "r");
+  if (!fp) {
+    return 0;
+  }
+
+  oxcf->q_cfg.qStep_mode = 0;
+  while (qStep_readline(line, linesize, fp)) {
+    if (qStep_read_token_after(line, "qStepMode:", NULL)) {
+      str = line;
+      qStepMode = atoi(qStep_read_token_after(str, "qStepMode:", &str));
+      assert(qStepMode == 0 || qStepMode == 1 || qStepMode == 2 ||
+             qStepMode == 3);
+      oxcf->q_cfg.qStep_mode = qStepMode;
+    }
+
+    if (oxcf->q_cfg.qStep_mode == 1) {
+      int num_transition_interval_minus1 = 0;
+      if (qStep_read_token_after(line,
+                                 "num_transition_interval_minus1:", NULL)) {
+        str = line;
+        num_transition_interval_minus1 = atoi(qStep_read_token_after(
+            str, "num_transition_interval_minus1:", &str));
+        assert(num_transition_interval_minus1 >= 1);
+        oxcf->q_cfg.num_qStep_intervals = num_transition_interval_minus1;
+      }
+      if (qStep_read_token_after(line, "num_Qsteps_in_interval:", NULL)) {
+        int idx = 0;
+        str = line;
+        token = qStep_read_token_after(str, "num_Qsteps_in_interval:", &str);
+        char *token1 =
+            qStep_strtok_r(token, ",", &str);  // strtok(token, ","); //
+        while (token1) {
+          oxcf->q_cfg.num_qsteps_in_interval[idx++] = atoi(token1);
+          token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+        }
+        assert((idx - 1) >= oxcf->q_cfg.num_qStep_intervals);
+      }
+    } else if (oxcf->q_cfg.qStep_mode == 2) {
+      int num_qStep_levels_minus1 = 0;
+      if (qStep_read_token_after(line, "num_q_step_periods_minus1:", NULL)) {
+        str = line;
+        num_qStep_levels_minus1 = atoi(
+            qStep_read_token_after(str, "num_q_step_periods_minus1:", &str));
+        assert(num_qStep_levels_minus1 >= 1);
+        oxcf->q_cfg.num_qStep_levels = num_qStep_levels_minus1;
+      }
+      if (qStep_read_token_after(line, "num_Qsteps_in_period:", NULL)) {
+        int idx = 0;
+        str = line;
+        token = qStep_read_token_after(str, "num_Qsteps_in_period:", &str);
+        char *token1 = qStep_strtok_r(token, ",", &str);  // strtok(token, ",");
+        while (token1) {
+          oxcf->q_cfg.qSteps_level[idx++] = atoi(token1);
+          token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+        }
+        assert((idx - 1) >= oxcf->q_cfg.num_qStep_intervals);
+      }
+    } else if (oxcf->q_cfg.qStep_mode == 3) {
+      int num_table_templates_minus1 = 0;
+      if (qStep_read_token_after(line, "num_table_templates_minus1:", NULL)) {
+        str = line;
+        num_table_templates_minus1 = atoi(
+            qStep_read_token_after(str, "num_table_templates_minus1:", &str));
+        assert(num_table_templates_minus1 >= 1);
+        oxcf->q_cfg.num_table_templates_minus1 = num_table_templates_minus1;
+      }
+      if (qStep_read_token_after(line, "num_entries_in_table_minus1:", NULL)) {
+        int idx = 0;
+        str = line;
+        token =
+            qStep_read_token_after(str, "num_entries_in_table_minus1:", &str);
+        char *token1 = qStep_strtok_r(token, ",", &str);  // strtok(token, ",");
+        while (token1) {
+          oxcf->q_cfg.num_entries_in_table_minus1[idx++] = atoi(token1);
+          token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+        }
+        assert((idx - 1) >= oxcf->q_cfg.num_table_templates_minus1);
+      }
+      for (int kk = 0; kk <= oxcf->q_cfg.num_table_templates_minus1; kk++) {
+        char buffer[100];
+        snprintf(buffer, sizeof(buffer), "qsteps_level_period_%d:", kk);
+        if (qStep_read_token_after(line, buffer, NULL)) {
+          int idx = 0;
+          str = line;
+          token = qStep_read_token_after(str, buffer, &str);
+          char *token1 =
+              qStep_strtok_r(token, ",", &str);  // strtok(token, ",");
+          while (token1) {
+            oxcf->q_cfg.qSteps_level_in_table[kk][idx++] = atoi(token1);
+            token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+          }
+          assert((idx - 1) >= oxcf->q_cfg.num_entries_in_table_minus1[kk]);
+        }
+      }
+
+      int num_transition_interval_minus1 = 0;
+      if (qStep_read_token_after(line,
+                                 "num_transition_interval_minus1:", NULL)) {
+        str = line;
+        num_transition_interval_minus1 = atoi(qStep_read_token_after(
+            str, "num_transition_interval_minus1:", &str));
+        assert(num_transition_interval_minus1 >= 1);
+        oxcf->q_cfg.num_qStep_intervals = num_transition_interval_minus1;
+      }
+      if (qStep_read_token_after(line, "template_table_idx:", NULL)) {
+        int idx = 0;
+        str = line;
+        token = qStep_read_token_after(str, "template_table_idx:", &str);
+        char *token1 = qStep_strtok_r(token, ",", &str);  // strtok(token, ",");
+        while (token1) {
+          oxcf->q_cfg.template_table_idx[idx++] = atoi(token1);
+          token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+        }
+        assert((idx - 1) >= oxcf->q_cfg.num_qStep_intervals);
+      }
+
+      if (qStep_read_token_after(line, "num_qsteps_in_interval:", NULL)) {
+        int idx = 0;
+        str = line;
+        token = qStep_read_token_after(str, "num_qsteps_in_interval:", &str);
+        char *token1 = qStep_strtok_r(token, ",", &str);  // strtok(token, ",");
+        while (token1) {
+          oxcf->q_cfg.num_qsteps_in_table[idx++] = atoi(token1);
+          token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+        }
+        assert((idx - 1) >= oxcf->q_cfg.num_qStep_intervals);
+      }
+      if (qStep_read_token_after(line, "table_start_region_idx:", NULL)) {
+        int idx = 0;
+        str = line;
+        token = qStep_read_token_after(str, "table_start_region_idx:", &str);
+        char *token1 = qStep_strtok_r(token, ",", &str);  // strtok(token, ",");
+        while (token1) {
+          oxcf->q_cfg.table_start_region_idx[idx++] = atoi(token1);
+          token1 = qStep_strtok_r(str, ",", &str);  // strtok(NULL,",");
+        }
+        assert((idx - 1) >= oxcf->q_cfg.num_qStep_intervals);
+      }
+    }
+  }
+  fclose(fp);
+  return 1;
+}
+
+void update_qstep_parameters(struct AV1_COMP *cpi,
+                             const AV1EncoderConfig *oxcf) {
+  AV1_COMMON *const cm = &cpi->common;
+  SequenceHeader *seq_params = &cm->seq_params;
+  cpi->oxcf = *oxcf;
+
+  seq_params->qStep_mode = oxcf->q_cfg.qStep_mode;
+
+  if ((seq_params->qStep_mode == 0) || (seq_params->qStep_mode == 1)) {
+    seq_params->num_qStep_intervals = oxcf->q_cfg.num_qStep_intervals;
+    for (int idx = 0; idx <= seq_params->num_qStep_intervals; idx++) {
+      seq_params->num_qsteps_in_interval[idx] =
+          oxcf->q_cfg.num_qsteps_in_interval[idx];
+    }
+  } else if (seq_params->qStep_mode == 2) {
+    seq_params->num_qStep_levels = oxcf->q_cfg.num_qStep_levels;
+    for (int idx = 0; idx <= seq_params->num_qStep_levels; idx++) {
+      seq_params->qSteps_level[idx] = oxcf->q_cfg.qSteps_level[idx];
+    }
+  } else if (seq_params->qStep_mode == 3) {
+    seq_params->num_table_templates_minus1 =
+        oxcf->q_cfg.num_table_templates_minus1;
+    for (int idx = 0; idx <= seq_params->num_table_templates_minus1; idx++) {
+      seq_params->num_entries_in_table_minus1[idx] =
+          oxcf->q_cfg.num_entries_in_table_minus1[idx];
+      for (int i = 0; i <= seq_params->num_entries_in_table_minus1[idx]; i++) {
+        seq_params->qSteps_level_in_table[idx][i] =
+            oxcf->q_cfg.qSteps_level_in_table[idx][i];
+      }
+    }
+    seq_params->num_qStep_intervals = oxcf->q_cfg.num_qStep_intervals;
+    for (int idx = 0; idx <= seq_params->num_qStep_intervals; idx++) {
+      seq_params->template_table_idx[idx] = oxcf->q_cfg.template_table_idx[idx];
+      seq_params->table_start_region_idx[idx] =
+          oxcf->q_cfg.table_start_region_idx[idx];
+      seq_params->num_qsteps_in_table[idx] =
+          oxcf->q_cfg.num_qsteps_in_table[idx];
+    }
+  }
+}
+#endif
+
 void av1_update_film_grain_parameters(struct AV1_COMP *cpi,
                                       const AV1EncoderConfig *oxcf) {
   AV1_COMMON *const cm = &cpi->common;
diff --git a/av1/encoder/encoder_utils.h b/av1/encoder/encoder_utils.h
index 138606d..099bcc2 100644
--- a/av1/encoder/encoder_utils.h
+++ b/av1/encoder/encoder_utils.h
@@ -951,6 +951,13 @@
 void av1_update_film_grain_parameters(struct AV1_COMP *cpi,
                                       const AV1EncoderConfig *oxcf);
 
+#if CONFIG_FLEX_STEPS
+int process_qStep_config_from_file(const char *paramfile,
+                                   AV1EncoderConfig *oxcf);
+void update_qstep_parameters(struct AV1_COMP *cpi,
+                             const AV1EncoderConfig *oxcf);
+#endif
+
 void av1_scale_references(AV1_COMP *cpi, const InterpFilter filter,
                           const int phase, const int use_optimized_scaler);
 
diff --git a/build/cmake/aom_config_defaults.cmake b/build/cmake/aom_config_defaults.cmake
index 6f33143..678141b 100644
--- a/build/cmake/aom_config_defaults.cmake
+++ b/build/cmake/aom_config_defaults.cmake
@@ -139,7 +139,9 @@
                    "AV2 experiment flag to remove dual filter.")
 set_aom_config_var(CONFIG_EXTQUANT 0
                    "AV2 extended quantization experiment flag")
-
+set_aom_config_var(CONFIG_FLEX_STEPS 0
+                   "AV2 flexible quantization experiment flag (enable with
+ CONFIG_EXTQUANT")
 #
 # Variables in this section control optional features of the build system.
 #
diff --git a/build/cmake/aom_experiment_deps.cmake b/build/cmake/aom_experiment_deps.cmake
index 2e36157..00ea141 100644
--- a/build/cmake/aom_experiment_deps.cmake
+++ b/build/cmake/aom_experiment_deps.cmake
@@ -25,4 +25,8 @@
     change_config_and_warn(CONFIG_DIST_8X8 0 CONFIG_MULTITHREAD)
   endif()
 
+  if(CONFIG_FLEX_STEPS)
+    change_config_and_warn(CONFIG_EXTQUANT 1 CONFIG_FLEX_STEPS)
+  endif()
+
 endmacro()
diff --git a/examples/qstepcfg/qstepcfg_mode1.cfg b/examples/qstepcfg/qstepcfg_mode1.cfg
new file mode 100644
index 0000000..5075012
--- /dev/null
+++ b/examples/qstepcfg/qstepcfg_mode1.cfg
@@ -0,0 +1,27 @@
+#default is qStepMode = 0
+
+
+#qStepMode 1 example
+qStepMode:1
+num_transition_interval_minus1:9
+num_Qsteps_in_interval:8,8,16,32,32,32,32,32,32,32
+
+
+#qStepMode 2 example
+#qStepMode:2
+#num_q_step_periods_minus1:31
+#num_Qsteps_in_period:32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63
+
+
+#qStepMode 3 example
+#qStepMode:3
+#num_table_templates_minus1:3
+#num_entries_in_table_minus1:63,63,63,63
+#qsteps_level_period_0:4,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70
+#qsteps_level_period_1:71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,155,158,161,164,167,170,173
+#qsteps_level_period_2:176,179,182,185,188,191,194,197,200,203,207,211,215,219,223,227,231,235,239,243,247,251,255,260,265,270,275,280,285,290,295,300,305,311,317,323,329,335,341,347,353,359,366,373,380,387,394,401,408,416,424,432,440,448,456,465,474,483,492,501,510,520,530,540
+#qsteps_level_period_3:550,560,571,582,593,604,615,627,639,651,663,676,689,702,715,729,743,757,771,786,801,816,832,848,864,881,898,915,933,951,969,988,1007,1026,1046,1066,1087,1108,1129,1151,1173,1196,1219,1243,1267,1292,1317,1343,1369,1396,1423,1451,1479,1508,1537,1567,1597,1628,1660,1692,1725,1759,1793,1828
+#num_transition_interval_minus1:3
+#template_table_idx:0,1,2,3
+#num_qsteps_in_interval:63,63,63,63
+#table_start_region_idx:0,0,0,0
\ No newline at end of file
diff --git a/examples/qstepcfg/qstepcfg_mode2.cfg b/examples/qstepcfg/qstepcfg_mode2.cfg
new file mode 100644
index 0000000..811d3a4
--- /dev/null
+++ b/examples/qstepcfg/qstepcfg_mode2.cfg
@@ -0,0 +1,27 @@
+#default is qStepMode = 0
+
+
+#qStepMode 1 example
+#qStepMode:1
+#num_transition_interval_minus1:9
+#num_Qsteps_in_interval:8,8,16,32,32,32,32,32,32,32
+
+
+#qStepMode 2 example
+qStepMode:2
+num_q_step_periods_minus1:31
+num_Qsteps_in_period:32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63
+
+
+#qStepMode 3 example
+#qStepMode:3
+#num_table_templates_minus1:3
+#num_entries_in_table_minus1:63,63,63,63
+#qsteps_level_period_0:4,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70
+#qsteps_level_period_1:71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,155,158,161,164,167,170,173
+#qsteps_level_period_2:176,179,182,185,188,191,194,197,200,203,207,211,215,219,223,227,231,235,239,243,247,251,255,260,265,270,275,280,285,290,295,300,305,311,317,323,329,335,341,347,353,359,366,373,380,387,394,401,408,416,424,432,440,448,456,465,474,483,492,501,510,520,530,540
+#qsteps_level_period_3:550,560,571,582,593,604,615,627,639,651,663,676,689,702,715,729,743,757,771,786,801,816,832,848,864,881,898,915,933,951,969,988,1007,1026,1046,1066,1087,1108,1129,1151,1173,1196,1219,1243,1267,1292,1317,1343,1369,1396,1423,1451,1479,1508,1537,1567,1597,1628,1660,1692,1725,1759,1793,1828
+#num_transition_interval_minus1:3
+#template_table_idx:0,1,2,3
+#num_qsteps_in_interval:63,63,63,63
+#table_start_region_idx:0,0,0,0
\ No newline at end of file
diff --git a/examples/qstepcfg/qstepcfg_mode3.cfg b/examples/qstepcfg/qstepcfg_mode3.cfg
new file mode 100644
index 0000000..dde3b05
--- /dev/null
+++ b/examples/qstepcfg/qstepcfg_mode3.cfg
@@ -0,0 +1,27 @@
+#default is qStepMode = 0
+
+
+#qStepMode 1 example
+#qStepMode:1
+#num_transition_interval_minus1:9
+#num_Qsteps_in_interval:8,8,16,32,32,32,32,32,32,32
+
+
+#qStepMode 2 example
+#qStepMode:2
+#num_q_step_periods_minus1:31
+#num_Qsteps_in_period:32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63
+
+
+#qStepMode 3 example
+qStepMode:3
+num_table_templates_minus1:3
+num_entries_in_table_minus1:63,63,63,63
+qsteps_level_period_0:4,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70
+qsteps_level_period_1:71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,104,106,108,110,112,114,116,118,120,122,124,126,128,130,132,134,136,138,140,142,144,146,148,150,152,155,158,161,164,167,170,173
+qsteps_level_period_2:176,179,182,185,188,191,194,197,200,203,207,211,215,219,223,227,231,235,239,243,247,251,255,260,265,270,275,280,285,290,295,300,305,311,317,323,329,335,341,347,353,359,366,373,380,387,394,401,408,416,424,432,440,448,456,465,474,483,492,501,510,520,530,540
+qsteps_level_period_3:550,560,571,582,593,604,615,627,639,651,663,676,689,702,715,729,743,757,771,786,801,816,832,848,864,881,898,915,933,951,969,988,1007,1026,1046,1066,1087,1108,1129,1151,1173,1196,1219,1243,1267,1292,1317,1343,1369,1396,1423,1451,1479,1508,1537,1567,1597,1628,1660,1692,1725,1759,1793,1828
+num_transition_interval_minus1:3
+template_table_idx:0,1,2,3
+num_qsteps_in_interval:63,63,63,63
+table_start_region_idx:0,0,0,0
\ No newline at end of file