Commit 1cb1c002 authored by Hui Su's avatar Hui Su

Add cdf_update_mode experiment

Allow the CDF update to operate in different modes, e.g. update vs no
update.

The update mode is transmitted in the uncompressed frame header of
every keyframe and intra-only frame.

This patch only adds bitstream signaling and API support. The
implementation of the update modes will be in later patches.

Change-Id: Ic9fcd60e8a75f9c01f414253823d78cf9b3113dd
parent cd93bf42
......@@ -350,6 +350,13 @@ enum aome_enc_control_id {
*/
AV1E_SET_TUNE_CONTENT,
/*!\brief Codec control function to set CDF update mode.
*
* 0: no update 1: update all the time
* 2: update half of the time 3: update quarter of the time
*/
AV1E_SET_CDF_UPDATE_MODE,
/*!\brief Codec control function to set color space info.
* \note Valid ranges: 0..23, default is "Unspecified".
* 0 = For future use
......@@ -1017,6 +1024,9 @@ AOM_CTRL_USE_TYPE(AV1E_ENABLE_MOTION_VECTOR_UNIT_TEST, unsigned int)
AOM_CTRL_USE_TYPE(AV1E_SET_FILM_GRAIN_TEST_VECTOR, unsigned int)
#define AOM_CTRL_AV1E_SET_FILM_GRAIN_TEST_VECTOR
AOM_CTRL_USE_TYPE(AV1E_SET_CDF_UPDATE_MODE, int)
#define AOM_CTRL_AV1E_SET_CDF_UPDATE_MODE
/*!\endcond */
/*! @} - end defgroup aom_encoder */
#ifdef __cplusplus
......
......@@ -645,9 +645,16 @@ static const struct arg_enum_list tune_content_enum[] = {
static const arg_def_t tune_content = ARG_DEF_ENUM(
NULL, "tune-content", 1, "Tune content type", tune_content_enum);
#endif
#if CONFIG_AV1_ENCODER
#if CONFIG_CDF_UPDATE_MODE
static const arg_def_t cdf_update_mode =
ARG_DEF(NULL, "cdf-update-mode", 1,
"CDF update rate for entropy coding "
"(0: off; 1: update all the time(default); "
"2: update half the time; "
"3: update quarter of the time)");
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_EXT_PARTITION
static const struct arg_enum_list superblock_size_enum[] = {
{ "dynamic", AOM_SUPERBLOCK_SIZE_DYNAMIC },
......@@ -705,6 +712,9 @@ static const arg_def_t *av1_args[] = { &cpu_used_av1,
&frame_periodic_boost,
&noise_sens,
&tune_content,
#if CONFIG_CDF_UPDATE_MODE
&cdf_update_mode,
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_CICP
&input_color_primaries,
&input_transfer_characteristics,
......@@ -777,6 +787,9 @@ static const int av1_arg_ctrl_map[] = { AOME_SET_CPUUSED,
AV1E_SET_FRAME_PERIODIC_BOOST,
AV1E_SET_NOISE_SENSITIVITY,
AV1E_SET_TUNE_CONTENT,
#if CONFIG_CDF_UPDATE_MODE
AV1E_SET_CDF_UPDATE_MODE,
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_CICP
AV1E_SET_COLOR_PRIMARIES,
AV1E_SET_TRANSFER_CHARACTERISTICS,
......@@ -801,7 +814,7 @@ static const int av1_arg_ctrl_map[] = { AOME_SET_CPUUSED,
#endif
AV1E_SET_DISABLE_TEMPMV,
0 };
#endif
#endif // CONFIG_AV1_ENCODER
static const arg_def_t *no_args[] = { NULL };
......
......@@ -113,6 +113,9 @@ struct av1_extracfg {
int film_grain_test_vector;
#endif
unsigned int motion_vector_unit_test;
#if CONFIG_CDF_UPDATE_MODE
unsigned int cdf_update_mode;
#endif // CONFIG_CDF_UPDATE_MODE
};
static struct av1_extracfg default_extra_cfg = {
......@@ -195,6 +198,9 @@ static struct av1_extracfg default_extra_cfg = {
0,
#endif
0, // motion_vector_unit_test
#if CONFIG_CDF_UPDATE_MODE
1, // CDF update mode
#endif // CONFIG_CDF_UPDATE_MODE
};
struct aom_codec_alg_priv {
......@@ -306,6 +312,9 @@ static aom_codec_err_t validate_config(aom_codec_alg_priv_t *ctx,
RANGE_CHECK(cfg, rc_superres_qthresh, 1, 63);
RANGE_CHECK(cfg, rc_superres_kf_qthresh, 1, 63);
#endif // CONFIG_HORZONLY_FRAME_SUPERRES
#if CONFIG_CDF_UPDATE_MODE
RANGE_CHECK_HI(extra_cfg, cdf_update_mode, 3);
#endif // CONFIG_CDF_UPDATE_MODE
// AV1 does not support a lower bound on the keyframe interval in
// automatic keyframe placement mode.
......@@ -691,6 +700,10 @@ static aom_codec_err_t set_encoder_config(
oxcf->tuning = extra_cfg->tuning;
oxcf->content = extra_cfg->content;
#if CONFIG_CDF_UPDATE_MODE
oxcf->cdf_update_mode = (uint8_t)extra_cfg->cdf_update_mode;
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_EXT_PARTITION
oxcf->superblock_size = extra_cfg->superblock_size;
#endif // CONFIG_EXT_PARTITION
......@@ -1682,6 +1695,15 @@ static aom_codec_err_t ctrl_set_tune_content(aom_codec_alg_priv_t *ctx,
return update_extra_cfg(ctx, &extra_cfg);
}
#if CONFIG_CDF_UPDATE_MODE
static aom_codec_err_t ctrl_set_cdf_update_mode(aom_codec_alg_priv_t *ctx,
va_list args) {
struct av1_extracfg extra_cfg = ctx->extra_cfg;
extra_cfg.cdf_update_mode = CAST(AV1E_SET_CDF_UPDATE_MODE, args);
return update_extra_cfg(ctx, &extra_cfg);
}
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_CICP
static aom_codec_err_t ctrl_set_color_primaries(aom_codec_alg_priv_t *ctx,
va_list args) {
......@@ -1831,6 +1853,9 @@ static aom_codec_ctrl_fn_map_t encoder_ctrl_maps[] = {
#endif
{ AV1E_SET_FRAME_PERIODIC_BOOST, ctrl_set_frame_periodic_boost },
{ AV1E_SET_TUNE_CONTENT, ctrl_set_tune_content },
#if CONFIG_CDF_UPDATE_MODE
{ AV1E_SET_CDF_UPDATE_MODE, ctrl_set_cdf_update_mode },
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_CICP
{ AV1E_SET_COLOR_PRIMARIES, ctrl_set_color_primaries },
{ AV1E_SET_TRANSFER_CHARACTERISTICS, ctrl_set_transfer_characteristics },
......
......@@ -319,6 +319,9 @@ typedef struct AV1Common {
// Flag signaling that the frame is encoded using only INTRA modes.
uint8_t intra_only;
uint8_t last_intra_only;
#if CONFIG_CDF_UPDATE_MODE
uint8_t cdf_update_mode;
#endif // CONFIG_CDF_UPDATE_MODE
int allow_high_precision_mv;
#if CONFIG_AMVR
......
......@@ -2887,6 +2887,9 @@ static int read_uncompressed_header(AV1Decoder *pbi,
#endif // CONFIG_HORZONLY_FRAME_SUPERRES
cm->allow_intrabc = aom_rb_read_bit(rb);
#endif // CONFIG_INTRABC
#if CONFIG_CDF_UPDATE_MODE
cm->cdf_update_mode = aom_rb_read_literal(rb, 2);
#endif // CONFIG_CDF_UPDATE_MODE
cm->use_prev_frame_mvs = 0;
} else {
if (cm->intra_only || cm->error_resilient_mode) cm->use_prev_frame_mvs = 0;
......@@ -2943,6 +2946,9 @@ static int read_uncompressed_header(AV1Decoder *pbi,
#endif // CONFIG_HORZONLY_FRAME_SUPERRES
cm->allow_intrabc = aom_rb_read_bit(rb);
#endif // CONFIG_INTRABC // CONFIG_INTRABC
#if CONFIG_CDF_UPDATE_MODE
cm->cdf_update_mode = aom_rb_read_literal(rb, 2);
#endif // CONFIG_CDF_UPDATE_MODE
} else if (pbi->need_resync != 1) { /* Skip if need resync */
#if CONFIG_OBU
pbi->refresh_frame_flags = (cm->frame_type == S_FRAME)
......
......@@ -3757,6 +3757,9 @@ static void write_uncompressed_header_frame(AV1_COMP *cpi,
#endif
aom_wb_write_bit(wb, cm->allow_intrabc);
#endif // CONFIG_INTRABC
#if CONFIG_CDF_UPDATE_MODE
aom_wb_write_literal(wb, cm->cdf_update_mode, 2);
#endif // CONFIG_CDF_UPDATE_MODE
} else {
#if !CONFIG_NO_FRAME_CONTEXT_SIGNALING
if (!cm->error_resilient_mode) {
......@@ -3800,6 +3803,9 @@ static void write_uncompressed_header_frame(AV1_COMP *cpi,
#endif
aom_wb_write_bit(wb, cm->allow_intrabc);
#endif // CONFIG_INTRABC
#if CONFIG_CDF_UPDATE_MODE
aom_wb_write_literal(wb, cm->cdf_update_mode, 2);
#endif // CONFIG_CDF_UPDATE_MODE
} else {
aom_wb_write_literal(wb, cpi->refresh_frame_mask, REF_FRAMES);
......@@ -4108,6 +4114,9 @@ static void write_uncompressed_header_obu(AV1_COMP *cpi,
#endif
aom_wb_write_bit(wb, cm->allow_intrabc);
#endif // CONFIG_INTRABC
#if CONFIG_CDF_UPDATE_MODE
aom_wb_write_literal(wb, cm->cdf_update_mode, 2);
#endif // CONFIG_CDF_UPDATE_MODE
} else if (cm->frame_type == INTRA_ONLY_FRAME) {
#if !CONFIG_NO_FRAME_CONTEXT_SIGNALING
if (!cm->error_resilient_mode) {
......@@ -4137,6 +4146,9 @@ static void write_uncompressed_header_obu(AV1_COMP *cpi,
#endif
aom_wb_write_bit(wb, cm->allow_intrabc);
#endif // CONFIG_INTRABC
#if CONFIG_CDF_UPDATE_MODE
aom_wb_write_literal(wb, cm->cdf_update_mode, 2);
#endif // CONFIG_CDF_UPDATE_MODE
}
} else if (cm->frame_type == INTER_FRAME) {
MV_REFERENCE_FRAME ref_frame;
......
......@@ -356,9 +356,10 @@ static void update_filter_type_count(uint8_t allow_update_cdf,
InterpFilter filter =
av1_extract_interp_filter(mbmi->interp_filters, dir);
++counts->switchable_interp[ctx][filter];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(xd->tile_ctx->switchable_interp_cdf[ctx], filter,
SWITCHABLE_FILTERS);
}
}
}
}
......@@ -824,10 +825,11 @@ static void sum_intra_stats(FRAME_COUNTS *counts, MACROBLOCKD *xd,
}
#endif // CONFIG_ENTROPY_STATS
if (allow_update_cdf) {
if (use_filter_intra_mode)
if (use_filter_intra_mode) {
update_cdf(fc->filter_intra_mode_cdf,
mbmi->filter_intra_mode_info.filter_intra_mode,
FILTER_INTRA_MODES);
}
update_cdf(fc->filter_intra_cdfs[mbmi->tx_size], use_filter_intra_mode,
2);
}
......@@ -840,10 +842,11 @@ static void sum_intra_stats(FRAME_COUNTS *counts, MACROBLOCKD *xd,
++counts->angle_delta[mbmi->mode - V_PRED]
[mbmi->angle_delta[PLANE_TYPE_Y] + MAX_ANGLE_DELTA];
#endif
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->angle_delta_cdf[mbmi->mode - V_PRED],
mbmi->angle_delta[PLANE_TYPE_Y] + MAX_ANGLE_DELTA,
2 * MAX_ANGLE_DELTA + 1);
}
}
#endif // CONFIG_EXT_INTRA_MOD
......@@ -858,10 +861,11 @@ static void sum_intra_stats(FRAME_COUNTS *counts, MACROBLOCKD *xd,
++counts->angle_delta[mbmi->uv_mode - V_PRED]
[mbmi->angle_delta[PLANE_TYPE_UV] + MAX_ANGLE_DELTA];
#endif
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->angle_delta_cdf[mbmi->uv_mode - V_PRED],
mbmi->angle_delta[PLANE_TYPE_UV] + MAX_ANGLE_DELTA,
2 * MAX_ANGLE_DELTA + 1);
}
}
#endif // CONFIG_EXT_INTRA_MOD
#if CONFIG_ENTROPY_STATS
......@@ -1005,13 +1009,13 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
frame_is_intra_only(cm), mi_row, mi_col,
tile_data->allow_update_cdf);
if (av1_allow_palette(cm->allow_screen_content_tools, bsize) &&
tile_data->allow_update_cdf)
allow_update_cdf)
update_palette_cdf(xd, mi);
}
#if CONFIG_INTRABC
if (frame_is_intra_only(cm) && av1_allow_intrabc(cm)) {
if (tile_data->allow_update_cdf)
if (allow_update_cdf)
update_cdf(fc->intrabc_cdf, is_intrabc_block(mbmi), 2);
#if CONFIG_ENTROPY_STATS
++td->counts->intrabc[is_intrabc_block(mbmi)];
......@@ -1040,9 +1044,10 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
if (!seg_ref_active) {
counts->intra_inter[av1_get_intra_inter_context(xd)][inter_block]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->intra_inter_cdf[av1_get_intra_inter_context(xd)],
inter_block, 2);
}
// If the segment reference feature is enabled we have only a single
// reference frame allowed for the segment so exclude it from
// the reference frame counts used to work out probabilities.
......@@ -1061,9 +1066,10 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
counts->comp_inter[av1_get_reference_mode_context(cm, xd)]
[has_second_ref(mbmi)]++;
#endif // CONFIG_ENTROPY_STATS
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(av1_get_reference_mode_cdf(cm, xd),
has_second_ref(mbmi), 2);
}
}
}
......@@ -1220,14 +1226,16 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
if (allow_update_cdf)
update_cdf(fc->interintra_cdf[bsize_group], 1, 2);
counts->interintra_mode[bsize_group][mbmi->interintra_mode]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->interintra_mode_cdf[bsize_group],
mbmi->interintra_mode, INTERINTRA_MODES);
}
if (is_interintra_wedge_used(bsize)) {
counts->wedge_interintra[bsize][mbmi->use_wedge_interintra]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->wedge_interintra_cdf[bsize],
mbmi->use_wedge_interintra, 2);
}
}
} else {
counts->interintra[bsize_group][0]++;
......@@ -1242,14 +1250,16 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
if (mbmi->ref_frame[1] != INTRA_FRAME) {
if (motion_allowed == WARPED_CAUSAL) {
counts->motion_mode[bsize][mbmi->motion_mode]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->motion_mode_cdf[bsize], mbmi->motion_mode,
MOTION_MODES);
}
} else if (motion_allowed == OBMC_CAUSAL) {
counts->obmc[bsize][mbmi->motion_mode == OBMC_CAUSAL]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->obmc_cdf[bsize], mbmi->motion_mode == OBMC_CAUSAL,
2);
}
}
}
......@@ -1264,26 +1274,29 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
if (masked_compound_used) {
const int comp_group_idx_ctx = get_comp_group_idx_context(xd);
++counts->comp_group_idx[comp_group_idx_ctx][mbmi->comp_group_idx];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->comp_group_idx_cdf[comp_group_idx_ctx],
mbmi->comp_group_idx, 2);
}
}
if (mbmi->comp_group_idx == 0) {
const int comp_index_ctx = get_comp_index_context(cm, xd);
++counts->compound_index[comp_index_ctx][mbmi->compound_idx];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->compound_index_cdf[comp_index_ctx],
mbmi->compound_idx, 2);
}
} else {
assert(masked_compound_used);
if (is_interinter_compound_used(COMPOUND_WEDGE, bsize)) {
counts->compound_interinter[bsize]
[mbmi->interinter_compound_type - 1]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->compound_type_cdf[bsize],
mbmi->interinter_compound_type - 1,
COMPOUND_TYPES - 1);
}
}
}
}
......@@ -1294,9 +1307,10 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
if (is_interinter_compound_used(COMPOUND_WEDGE, bsize)) {
counts
->compound_interinter[bsize][mbmi->interinter_compound_type]++;
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->compound_type_cdf[bsize],
mbmi->interinter_compound_type, COMPOUND_TYPES);
}
}
}
#endif // CONFIG_JNT_COMP
......@@ -1323,9 +1337,10 @@ static void update_stats(const AV1_COMMON *const cm, TileDataEnc *tile_data,
if (has_second_ref(mbmi)) {
mode_ctx = mbmi_ext->compound_mode_context[mbmi->ref_frame[0]];
++counts->inter_compound_mode[mode_ctx][INTER_COMPOUND_OFFSET(mode)];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->inter_compound_mode_cdf[mode_ctx],
INTER_COMPOUND_OFFSET(mode), INTER_COMPOUND_MODES);
}
} else {
mode_ctx =
av1_mode_context_analyzer(mbmi_ext->mode_context, mbmi->ref_frame);
......@@ -3868,7 +3883,6 @@ void av1_init_tile_data(AV1_COMP *cpi) {
pre_tok = cpi->tile_tok[tile_row][tile_col];
tile_tok = allocated_tokens(
*tile_info, cm->seq_params.mib_size_log2 + MI_SIZE_LOG2, num_planes);
#if CONFIG_EXT_TILE
tile_data->allow_update_cdf = !cm->large_scale_tile;
#else
......@@ -4251,6 +4265,9 @@ static void encode_frame_internal(AV1_COMP *cpi) {
cm->allow_screen_content_tools =
cm->seq_params.force_screen_content_tools;
}
#if CONFIG_CDF_UPDATE_MODE
cm->cdf_update_mode = cpi->oxcf.cdf_update_mode;
#endif // CONFIG_CDF_UPDATE_MODE
}
#if CONFIG_INTRABC
......@@ -4861,10 +4878,11 @@ void av1_update_tx_type_count(const AV1_COMMON *cm, MACROBLOCKD *xd,
const TxSetType tx_set_type = get_ext_tx_set_type(
tx_size, bsize, is_inter, cm->reduced_tx_set_used);
if (is_inter) {
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(fc->inter_ext_tx_cdf[eset][txsize_sqr_map[tx_size]],
av1_ext_tx_ind[tx_set_type][tx_type],
av1_num_ext_tx_set[tx_set_type]);
}
#if CONFIG_ENTROPY_STATS
++counts->inter_ext_tx[eset][txsize_sqr_map[tx_size]][tx_type];
#endif // CONFIG_ENTROPY_STATS
......@@ -4880,21 +4898,23 @@ void av1_update_tx_type_count(const AV1_COMMON *cm, MACROBLOCKD *xd,
++counts
->intra_ext_tx[eset][txsize_sqr_map[tx_size]][intra_dir][tx_type];
#endif // CONFIG_ENTROPY_STATS
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(
fc->intra_ext_tx_cdf[eset][txsize_sqr_map[tx_size]][intra_dir],
av1_ext_tx_ind[tx_set_type][tx_type],
av1_num_ext_tx_set[tx_set_type]);
}
#else
#if CONFIG_ENTROPY_STATS
++counts->intra_ext_tx[eset][txsize_sqr_map[tx_size]][mbmi->mode]
[tx_type];
#endif // CONFIG_ENTROPY_STATS
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(
fc->intra_ext_tx_cdf[eset][txsize_sqr_map[tx_size]][mbmi->mode],
av1_ext_tx_ind[tx_set_type][tx_type],
av1_num_ext_tx_set[tx_set_type]);
}
#endif
}
}
......
......@@ -309,6 +309,9 @@ typedef struct AV1EncoderConfig {
int film_grain_test_vector;
#endif
#if CONFIG_CDF_UPDATE_MODE
uint8_t cdf_update_mode;
#endif // CONFIG_CDF_UPDATE_MODE
#if CONFIG_EXT_PARTITION
aom_superblock_size_t superblock_size;
#endif // CONFIG_EXT_PARTITION
......
......@@ -209,28 +209,32 @@ void av1_update_eob_context(int eob, int seg_eob, TX_SIZE tx_size,
break;
case 3:
++counts->eob_multi128[plane][eob_multi_ctx][eob_pt - 1];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(ec_ctx->eob_flag_cdf128[plane][eob_multi_ctx], eob_pt - 1,
8);
}
break;
case 4:
++counts->eob_multi256[plane][eob_multi_ctx][eob_pt - 1];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(ec_ctx->eob_flag_cdf256[plane][eob_multi_ctx], eob_pt - 1,
9);
}
break;
case 5:
++counts->eob_multi512[plane][eob_multi_ctx][eob_pt - 1];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(ec_ctx->eob_flag_cdf512[plane][eob_multi_ctx], eob_pt - 1,
10);
}
break;
case 6:
default:
++counts->eob_multi1024[plane][eob_multi_ctx][eob_pt - 1];
if (allow_update_cdf)
if (allow_update_cdf) {
update_cdf(ec_ctx->eob_flag_cdf1024[plane][eob_multi_ctx], eob_pt - 1,
11);
}
break;
}
......
......@@ -92,6 +92,7 @@ set(CONFIG_AOM_QM 1 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_AOM_QM_EXT 0 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_BGSPRITE 0 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_CDF_STORAGE_REDUCTION 0 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_CDF_UPDATE_MODE 0 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_CFL 1 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_CICP 0 CACHE NUMBER "AV1 experiment flag.")
set(CONFIG_COLORSPACE_HEADERS 0 CACHE NUMBER "AV1 experiment flag.")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment