add support for qp adjustment for HDR video
Change-Id: I99757226aea84d415f3fcf405ef3f4d90bdbf7dd
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 6d2b9a2..b679dba 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -1110,6 +1110,10 @@
q_cfg->deltaq_mode = extra_cfg->deltaq_mode;
q_cfg->use_fixed_qp_offsets =
cfg->use_fixed_qp_offsets && (rc_cfg->mode == AOM_Q);
+ q_cfg->enable_hdr_deltaq =
+ (q_cfg->deltaq_mode == DELTA_Q_HDR) &&
+ (cfg->g_bit_depth == AOM_BITS_10) &&
+ (extra_cfg->color_primaries == AOM_CICP_CP_BT_2020);
for (int i = 0; i < FIXED_QP_OFFSET_COUNT; ++i) {
if (q_cfg->use_fixed_qp_offsets) {
if (cfg->fixed_qp_offsets[i] >= 0) { // user-provided qp offset
diff --git a/av1/encoder/aq_variance.c b/av1/encoder/aq_variance.c
index 60d5a77..05dd33a 100644
--- a/av1/encoder/aq_variance.c
+++ b/av1/encoder/aq_variance.c
@@ -141,6 +141,34 @@
return (int)(var);
}
+int av1_log_block_avg(const AV1_COMP *cpi, MACROBLOCK *x, BLOCK_SIZE bs,
+ int mi_row, int mi_col) {
+ // This functions returns the block average of luma block
+ unsigned int sum, avg, num_pix;
+ int r, c;
+ const int pic_w = cpi->common.width;
+ const int pic_h = cpi->common.height;
+ const int bw = MI_SIZE * mi_size_wide[bs];
+ const int bh = MI_SIZE * mi_size_high[bs];
+ const uint16_t *x16 = CONVERT_TO_SHORTPTR(x->plane[0].src.buf);
+
+ sum = 0;
+ num_pix = 0;
+ avg = 0;
+ int row = mi_row << MI_SIZE_LOG2;
+ int col = mi_col << MI_SIZE_LOG2;
+ for (r = row; (r < (row + bh)) && (r < pic_h); r++) {
+ for (c = col; (c < (col + bw)) && (c < pic_w); c++) {
+ sum += *(x16 + r * x->plane[0].src.stride + c);
+ num_pix++;
+ }
+ }
+ if (num_pix != 0) {
+ avg = sum / num_pix;
+ }
+ return avg;
+}
+
#define DEFAULT_E_MIDPOINT 10.0
static unsigned int haar_ac_energy(MACROBLOCK *x, BLOCK_SIZE bs) {
diff --git a/av1/encoder/aq_variance.h b/av1/encoder/aq_variance.h
index 543eb0b..aa0535a 100644
--- a/av1/encoder/aq_variance.h
+++ b/av1/encoder/aq_variance.h
@@ -21,6 +21,8 @@
void av1_vaq_frame_setup(AV1_COMP *cpi);
int av1_log_block_var(const AV1_COMP *cpi, MACROBLOCK *x, BLOCK_SIZE bs);
+int av1_log_block_avg(const AV1_COMP *cpi, MACROBLOCK *x, BLOCK_SIZE bs,
+ int mi_row, int mi_col);
int av1_compute_q_from_energy_level_deltaq_mode(const AV1_COMP *const cpi,
int block_var_level);
int av1_block_wavelet_energy_level(const AV1_COMP *cpi, MACROBLOCK *x,
diff --git a/av1/encoder/av1_quantize.c b/av1/encoder/av1_quantize.c
index 66be3b6..105897e 100644
--- a/av1/encoder/av1_quantize.c
+++ b/av1/encoder/av1_quantize.c
@@ -764,14 +764,34 @@
av1_init_plane_quantizers(cpi, x, xd->mi[0]->segment_id);
}
+static int adjust_hdr_cb_deltaq(int base_qindex) {
+ double baseQp = base_qindex / QP_SCALE_FACTOR;
+ const double chromaQp = CHROMA_QP_SCALE * baseQp + CHROMA_QP_OFFSET;
+ const double dcbQP = CHROMA_CB_QP_SCALE * chromaQp * QP_SCALE_FACTOR;
+ int dqpCb = (int)(dcbQP + (dcbQP < 0 ? -0.5 : 0.5));
+ dqpCb = AOMMIN(0, dqpCb);
+ dqpCb = (int)CLIP(dqpCb, -12 * QP_SCALE_FACTOR, 12 * QP_SCALE_FACTOR);
+ return dqpCb;
+}
+
+static int adjust_hdr_cr_deltaq(int base_qindex) {
+ double baseQp = base_qindex / QP_SCALE_FACTOR;
+ const double chromaQp = CHROMA_QP_SCALE * baseQp + CHROMA_QP_OFFSET;
+ const double dcrQP = CHROMA_CR_QP_SCALE * chromaQp * QP_SCALE_FACTOR;
+ int dqpCr = (int)(dcrQP + (dcrQP < 0 ? -0.5 : 0.5));
+ dqpCr = AOMMIN(0, dqpCr);
+ dqpCr = (int)CLIP(dqpCr, -12 * QP_SCALE_FACTOR, 12 * QP_SCALE_FACTOR);
+ return dqpCr;
+}
+
void av1_set_quantizer(AV1_COMMON *const cm, int min_qmlevel, int max_qmlevel,
- int q, int enable_chroma_deltaq) {
+ int q, int enable_chroma_deltaq, int enable_hdr_deltaq) {
// quantizer has to be reinitialized with av1_init_quantizer() if any
// delta_q changes.
CommonQuantParams *quant_params = &cm->quant_params;
quant_params->base_qindex = AOMMAX(cm->delta_q_info.delta_q_present_flag, q);
-
quant_params->y_dc_delta_q = 0;
+
if (enable_chroma_deltaq) {
// TODO(aomedia:2717): need to design better delta
quant_params->u_dc_delta_q = 2;
@@ -785,6 +805,18 @@
quant_params->v_ac_delta_q = 0;
}
+ // following section 8.3.2 in T-REC-H.Sup15 document
+ // to apply to AV1 qindex in the range of [0, 255]
+ if (enable_hdr_deltaq) {
+ int dqpCb = adjust_hdr_cb_deltaq(quant_params->base_qindex);
+ int dqpCr = adjust_hdr_cr_deltaq(quant_params->base_qindex);
+ quant_params->u_dc_delta_q = quant_params->u_ac_delta_q = dqpCb;
+ quant_params->v_dc_delta_q = quant_params->v_ac_delta_q = dqpCr;
+ if (dqpCb != dqpCr) {
+ cm->seq_params->separate_uv_delta_q = 1;
+ }
+ }
+
quant_params->qmatrix_level_y =
aom_get_qmlevel(quant_params->base_qindex, min_qmlevel, max_qmlevel);
quant_params->qmatrix_level_u =
diff --git a/av1/encoder/av1_quantize.h b/av1/encoder/av1_quantize.h
index 62b53b8..085ab17 100644
--- a/av1/encoder/av1_quantize.h
+++ b/av1/encoder/av1_quantize.h
@@ -106,7 +106,8 @@
aom_bit_depth_t bit_depth);
void av1_set_quantizer(struct AV1Common *const cm, int min_qmlevel,
- int max_qmlevel, int q, int enable_chroma_deltaq);
+ int max_qmlevel, int q, int enable_chroma_deltaq,
+ int enable_hdr_deltaq);
int av1_quantizer_to_qindex(int quantizer);
diff --git a/av1/encoder/encodeframe.c b/av1/encoder/encodeframe.c
index 296ff31..ff9b8e0 100644
--- a/av1/encoder/encodeframe.c
+++ b/av1/encoder/encodeframe.c
@@ -241,6 +241,8 @@
current_qindex = av1_get_sbq_perceptual_ai(cpi, sb_size, mi_row, mi_col);
} else if (cpi->oxcf.q_cfg.deltaq_mode == DELTA_Q_USER_RATING_BASED) {
current_qindex = av1_get_sbq_user_rating_based(cpi, mi_row, mi_col);
+ } else if (cpi->oxcf.q_cfg.enable_hdr_deltaq) {
+ current_qindex = av1_get_q_for_hdr(cpi, x, sb_size, mi_row, mi_col);
}
MACROBLOCKD *const xd = &x->e_mbd;
@@ -1361,7 +1363,8 @@
cm->delta_q_info.delta_q_res = DEFAULT_DELTA_Q_RES_PERCEPTUAL;
else if (deltaq_mode == DELTA_Q_USER_RATING_BASED)
cm->delta_q_info.delta_q_res = DEFAULT_DELTA_Q_RES_PERCEPTUAL;
-
+ else if (deltaq_mode == DELTA_Q_HDR)
+ cm->delta_q_info.delta_q_res = DEFAULT_DELTA_Q_RES_PERCEPTUAL;
// Set delta_q_present_flag before it is used for the first time
cm->delta_q_info.delta_lf_res = DEFAULT_DELTA_LF_RES;
cm->delta_q_info.delta_q_present_flag = deltaq_mode != NO_DELTA_Q;
diff --git a/av1/encoder/encodeframe_utils.c b/av1/encoder/encodeframe_utils.c
index 89c2322..9fbd68c 100644
--- a/av1/encoder/encodeframe_utils.c
+++ b/av1/encoder/encodeframe_utils.c
@@ -15,6 +15,7 @@
#include "av1/encoder/encodeframe_utils.h"
#include "av1/encoder/partition_strategy.h"
#include "av1/encoder/rdopt.h"
+#include "av1/encoder/aq_variance.h"
void av1_set_ssim_rdmult(const AV1_COMP *const cpi, int *errorperbit,
const BLOCK_SIZE bsize, const int mi_row,
@@ -957,6 +958,49 @@
return qindex;
}
+
+#if !DISABLE_HDR_LUMA_DELTAQ
+// offset table defined in Table3 of T-REC-H.Sup15 document.
+static const int hdr_thres[HDR_QP_LEVELS + 1] = { 0, 301, 367, 434, 501, 567,
+ 634, 701, 767, 834, 1024 };
+
+static const int hdr10_qp_offset[HDR_QP_LEVELS] = { 3, 2, 1, 0, -1,
+ -2, -3, -4, -5, -6 };
+#endif
+
+int av1_get_q_for_hdr(AV1_COMP *const cpi, MACROBLOCK *const x,
+ BLOCK_SIZE bsize, int mi_row, int mi_col) {
+ AV1_COMMON *const cm = &cpi->common;
+ assert(cm->seq_params->bit_depth == AOM_BITS_10);
+
+#if DISABLE_HDR_LUMA_DELTAQ
+ (void)x;
+ (void)bsize;
+ (void)mi_row;
+ (void)mi_col;
+ return cm->quant_params.base_qindex;
+#else
+ // calculate pixel average
+ const int block_luma_avg = av1_log_block_avg(cpi, x, bsize, mi_row, mi_col);
+ // adjust offset based on average of the pixel block
+ int offset = 0;
+ for (int i = 0; i < HDR_QP_LEVELS; i++) {
+ if (block_luma_avg >= hdr_thres[i] && block_luma_avg < hdr_thres[i + 1]) {
+ offset = (int)(hdr10_qp_offset[i] * QP_SCALE_FACTOR);
+ break;
+ }
+ }
+
+ const DeltaQInfo *const delta_q_info = &cm->delta_q_info;
+ offset = AOMMIN(offset, delta_q_info->delta_q_res * 9 - 1);
+ offset = AOMMAX(offset, -delta_q_info->delta_q_res * 9 + 1);
+ int qindex = cm->quant_params.base_qindex + offset;
+ qindex = AOMMIN(qindex, MAXQ);
+ qindex = AOMMAX(qindex, MINQ);
+
+ return qindex;
+#endif
+}
#endif // !CONFIG_REALTIME_ONLY
void av1_reset_simple_motion_tree_partition(SIMPLE_MOTION_DATA_TREE *sms_tree,
diff --git a/av1/encoder/encodeframe_utils.h b/av1/encoder/encodeframe_utils.h
index 94abc42..3604616 100644
--- a/av1/encoder/encodeframe_utils.h
+++ b/av1/encoder/encodeframe_utils.h
@@ -337,6 +337,9 @@
int av1_get_q_for_deltaq_objective(AV1_COMP *const cpi, BLOCK_SIZE bsize,
int mi_row, int mi_col);
+int av1_get_q_for_hdr(AV1_COMP *const cpi, MACROBLOCK *const x,
+ BLOCK_SIZE bsize, int mi_row, int mi_col);
+
int av1_get_hier_tpl_rdmult(const AV1_COMP *const cpi, MACROBLOCK *const x,
const BLOCK_SIZE bsize, const int mi_row,
const int mi_col, int orig_rdmult);
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 947b41e..15648d9 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -2374,7 +2374,7 @@
}
av1_set_quantizer(cm, q_cfg->qm_minlevel, q_cfg->qm_maxlevel, q,
- q_cfg->enable_chroma_deltaq);
+ q_cfg->enable_chroma_deltaq, q_cfg->enable_hdr_deltaq);
av1_set_speed_features_qindex_dependent(cpi, cpi->oxcf.speed);
if ((q_cfg->deltaq_mode != NO_DELTA_Q) || q_cfg->enable_chroma_deltaq)
av1_init_quantizer(&cpi->enc_quant_dequant_params, &cm->quant_params,
@@ -2389,7 +2389,7 @@
cpi->rc.high_source_sad) {
if (av1_encodedframe_overshoot_cbr(cpi, &q)) {
av1_set_quantizer(cm, q_cfg->qm_minlevel, q_cfg->qm_maxlevel, q,
- q_cfg->enable_chroma_deltaq);
+ q_cfg->enable_chroma_deltaq, q_cfg->enable_hdr_deltaq);
av1_set_speed_features_qindex_dependent(cpi, cpi->oxcf.speed);
if (q_cfg->deltaq_mode != NO_DELTA_Q || q_cfg->enable_chroma_deltaq)
av1_init_quantizer(&cpi->enc_quant_dequant_params, &cm->quant_params,
@@ -2615,7 +2615,7 @@
}
#endif
av1_set_quantizer(cm, q_cfg->qm_minlevel, q_cfg->qm_maxlevel, q,
- q_cfg->enable_chroma_deltaq);
+ q_cfg->enable_chroma_deltaq, q_cfg->enable_hdr_deltaq);
av1_set_speed_features_qindex_dependent(cpi, oxcf->speed);
if (q_cfg->deltaq_mode != NO_DELTA_Q || q_cfg->enable_chroma_deltaq)
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 453fb52..83c8bd1 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -87,6 +87,14 @@
// Lookahead index threshold to enable temporal filtering for second arf.
#define TF_LOOKAHEAD_IDX_THR 7
+#define HDR_QP_LEVELS 10
+#define CHROMA_CB_QP_SCALE 1.04
+#define CHROMA_CR_QP_SCALE 1.04
+#define CHROMA_QP_SCALE -0.46
+#define CHROMA_QP_OFFSET 9.26
+#define QP_SCALE_FACTOR 2.0
+#define DISABLE_HDR_LUMA_DELTAQ 1
+
// Rational number with an int64 numerator
// This structure holds a fractional value
typedef struct aom_rational64 {
@@ -159,6 +167,7 @@
DELTA_Q_PERCEPTUAL = 2, // Modulation to improve video perceptual quality
DELTA_Q_PERCEPTUAL_AI = 3, // Perceptual quality opt for all intra mode
DELTA_Q_USER_RATING_BASED = 4, // User rating based delta q mode
+ DELTA_Q_HDR = 5, // QP adjustment based on HDR block pixel average
DELTA_Q_MODE_COUNT // This should always be the last member of the enum
} UENUM1BYTE(DELTAQ_MODE);
@@ -752,6 +761,8 @@
DELTAQ_MODE deltaq_mode;
// Indicates if delta quantization should be enabled in chroma planes.
bool enable_chroma_deltaq;
+ // Indicates if delta quantization should be enabled for hdr video
+ bool enable_hdr_deltaq;
// Indicates if encoding with quantization matrices should be enabled.
bool using_qm;
} QuantizationCfg;
diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c
index 633125d..66cd272 100644
--- a/av1/encoder/encoder_utils.c
+++ b/av1/encoder/encoder_utils.c
@@ -1009,7 +1009,7 @@
set_encoding_params_for_screen_content(cpi, pass);
av1_set_quantizer(cm, q_cfg->qm_minlevel, q_cfg->qm_maxlevel,
q_for_screen_content_quick_run,
- q_cfg->enable_chroma_deltaq);
+ q_cfg->enable_chroma_deltaq, q_cfg->enable_hdr_deltaq);
av1_set_speed_features_qindex_dependent(cpi, oxcf->speed);
if (q_cfg->deltaq_mode != NO_DELTA_Q || q_cfg->enable_chroma_deltaq)
av1_init_quantizer(&cpi->enc_quant_dequant_params, &cm->quant_params,
diff --git a/av1/encoder/firstpass.c b/av1/encoder/firstpass.c
index 28d527a..af1e8c1 100644
--- a/av1/encoder/firstpass.c
+++ b/av1/encoder/firstpass.c
@@ -1271,9 +1271,9 @@
// Do not use periodic key frames.
cpi->rc.frames_to_key = INT_MAX;
- av1_set_quantizer(cm, cpi->oxcf.q_cfg.qm_minlevel,
- cpi->oxcf.q_cfg.qm_maxlevel, qindex,
- cpi->oxcf.q_cfg.enable_chroma_deltaq);
+ av1_set_quantizer(
+ cm, cpi->oxcf.q_cfg.qm_minlevel, cpi->oxcf.q_cfg.qm_maxlevel, qindex,
+ cpi->oxcf.q_cfg.enable_chroma_deltaq, cpi->oxcf.q_cfg.enable_hdr_deltaq);
av1_setup_block_planes(xd, seq_params->subsampling_x,
seq_params->subsampling_y, num_planes);