Prepare for delta QPs at CU-level

- Replaces lcu_dqp_enabled with max_qp_delta_depth in encoder_control_t.
- Fixes set_cu_qps so that it can handle quantization groups of
  arbitrary size.
- Fixes computation of QP predictors so that it works for quantization
  groups of arbitrary size.
This commit is contained in:
Arttu Ylä-Outinen 2017-05-08 10:54:06 +03:00
parent a3274de3b4
commit a343f6d587
10 changed files with 153 additions and 65 deletions

View file

@ -435,7 +435,9 @@ static void encode_transform_coeff(encoder_state_t * const state,
const cu_info_t *cur_pu = kvz_cu_array_at_const(frame->cu_array, x, y);
// Round coordinates down to a multiple of 8 to get the location of the
// containing CU.
const cu_info_t *cur_cu = kvz_cu_array_at_const(frame->cu_array, x & ~7, y & ~7);
const int x_cu = 8 * (x / 8);
const int y_cu = 8 * (y / 8);
const cu_info_t *cur_cu = kvz_cu_array_at_const(frame->cu_array, x_cu, y_cu);
// NxN signifies implicit transform split at the first transform level.
// There is a similar implicit split for inter, but it is only used when
@ -508,9 +510,10 @@ static void encode_transform_coeff(encoder_state_t * const state,
if (cb_flag_y | cb_flag_u | cb_flag_v) {
if (state->must_code_qp_delta) {
const int qp_delta = state->qp - state->ref_qp;
const int qp_delta_abs = ABS(qp_delta);
cabac_data_t* cabac = &state->cabac;
const int qp_pred = kvz_get_cu_ref_qp(state, x_cu, y_cu, state->last_qp);
const int qp_delta = cur_cu->qp - qp_pred;
const int qp_delta_abs = ABS(qp_delta);
cabac_data_t* cabac = &state->cabac;
// cu_qp_delta_abs prefix
cabac->cur_ctx = &cabac->ctx.cu_qp_delta_abs[0];
@ -526,7 +529,6 @@ static void encode_transform_coeff(encoder_state_t * const state,
}
state->must_code_qp_delta = false;
state->ref_qp = state->qp;
}
encode_transform_unit(state, x, y, depth);
@ -957,6 +959,9 @@ void kvz_encode_coding_tree(encoder_state_t * const state,
const videoframe_t * const frame = state->tile->frame;
const cu_info_t *cur_cu = kvz_cu_array_at_const(frame->cu_array, x, y);
const int cu_width = LCU_WIDTH >> depth;
const int half_cu = cu_width >> 1;
const cu_info_t *left_cu = NULL;
if (x > 0) {
left_cu = kvz_cu_array_at_const(frame->cu_array, x - 1, y);
@ -973,13 +978,17 @@ void kvz_encode_coding_tree(encoder_state_t * const state,
uint16_t abs_x = x + state->tile->offset_x;
uint16_t abs_y = y + state->tile->offset_y;
// Check for slice border FIXME
bool border_x = ctrl->in.width < abs_x + (LCU_WIDTH >> depth);
bool border_y = ctrl->in.height < abs_y + (LCU_WIDTH >> depth);
bool border_split_x = ctrl->in.width >= abs_x + (LCU_WIDTH >> MAX_DEPTH) + (LCU_WIDTH >> (depth + 1));
bool border_split_y = ctrl->in.height >= abs_y + (LCU_WIDTH >> MAX_DEPTH) + (LCU_WIDTH >> (depth + 1));
// Check for slice border
bool border_x = ctrl->in.width < abs_x + cu_width;
bool border_y = ctrl->in.height < abs_y + cu_width;
bool border_split_x = ctrl->in.width >= abs_x + (LCU_WIDTH >> MAX_DEPTH) + half_cu;
bool border_split_y = ctrl->in.height >= abs_y + (LCU_WIDTH >> MAX_DEPTH) + half_cu;
bool border = border_x || border_y; /*!< are we in any border CU */
if (depth <= ctrl->max_qp_delta_depth) {
state->must_code_qp_delta = true;
}
// When not in MAX_DEPTH, insert split flag and split the blocks if needed
if (depth != MAX_DEPTH) {
// Implisit split flag when on border
@ -999,25 +1008,22 @@ void kvz_encode_coding_tree(encoder_state_t * const state,
if (split_flag || border) {
// Split blocks and remember to change x and y block positions
int offset = LCU_WIDTH >> (depth + 1);
kvz_encode_coding_tree(state, x, y, depth + 1);
// TODO: fix when other half of the block would not be completely over the border
if (!border_x || border_split_x) {
kvz_encode_coding_tree(state, x + offset, y, depth + 1);
kvz_encode_coding_tree(state, x + half_cu, y, depth + 1);
}
if (!border_y || border_split_y) {
kvz_encode_coding_tree(state, x, y + offset, depth + 1);
kvz_encode_coding_tree(state, x, y + half_cu, depth + 1);
}
if (!border || (border_split_x && border_split_y)) {
kvz_encode_coding_tree(state, x + offset, y + offset, depth + 1);
kvz_encode_coding_tree(state, x + half_cu, y + half_cu, depth + 1);
}
return;
}
}
if (state->encoder_control->cfg.lossless) {
if (ctrl->cfg.lossless) {
cabac->cur_ctx = &cabac->ctx.cu_transquant_bypass;
CABAC_BIN(cabac, 1, "cu_transquant_bypass_flag");
}
@ -1053,7 +1059,7 @@ void kvz_encode_coding_tree(encoder_state_t * const state,
}
}
}
return;
goto end;
}
}
@ -1068,7 +1074,6 @@ void kvz_encode_coding_tree(encoder_state_t * const state,
if (cur_cu->type == CU_INTER) {
const int num_pu = kvz_part_mode_num_parts[cur_cu->part_size];
const int cu_width = LCU_WIDTH >> depth;
for (int i = 0; i < num_pu; ++i) {
const int pu_x = PU_GET_X(cur_cu->part_size, cu_width, x, i);
@ -1139,6 +1144,12 @@ void kvz_encode_coding_tree(encoder_state_t * const state,
assert(0);
exit(1);
}
end:
if (is_last_cu_in_qg(state, x, y, depth)) {
state->last_qp = cur_cu->qp;
}
}

View file

@ -347,12 +347,16 @@ encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg)
}
encoder->lcu_dqp_enabled = cfg->target_bitrate > 0 || encoder->cfg.roi.dqps;
// NOTE: When tr_depth_inter is equal to 0, the transform is still split
// for SMP and AMP partition units.
encoder->tr_depth_inter = 0;
if (encoder->cfg.target_bitrate > 0 || encoder->cfg.roi.dqps) {
encoder->max_qp_delta_depth = 0;
} else {
encoder->max_qp_delta_depth = -1;
}
//Tiles
encoder->tiles_enable = encoder->cfg.tiles_width_count > 1 ||
encoder->cfg.tiles_height_count > 1;

View file

@ -118,7 +118,7 @@ typedef struct encoder_control_t
//! Picture weights when GOP is used.
double gop_layer_weights[MAX_GOP_LAYERS];
bool lcu_dqp_enabled;
int8_t max_qp_delta_depth;
int tr_depth_inter;

View file

@ -455,12 +455,12 @@ static void encoder_state_write_bitstream_pic_parameter_set(bitstream_t* stream,
WRITE_U(stream, 0, 1, "constrained_intra_pred_flag");
WRITE_U(stream, encoder->cfg.trskip_enable, 1, "transform_skip_enabled_flag");
if (encoder->lcu_dqp_enabled) {
if (encoder->max_qp_delta_depth >= 0) {
// Use separate QP for each LCU when rate control is enabled.
WRITE_U(stream, 1, 1, "cu_qp_delta_enabled_flag");
WRITE_UE(stream, 0, "diff_cu_qp_delta_depth");
WRITE_UE(stream, encoder->max_qp_delta_depth, "diff_cu_qp_delta_depth");
} else {
WRITE_U(stream, 0, 1, "cu_qp_delta_enabled_flag");
WRITE_U(stream, 0, 1, "cu_qp_delta_enabled_flag");
}
//TODO: add QP offsets

View file

@ -312,6 +312,7 @@ int kvz_encoder_state_init(encoder_state_t * const child_state, encoder_state_t
child_state->children = MALLOC(encoder_state_t, 1);
child_state->children[0].encoder_control = NULL;
child_state->crypto_hdl = NULL;
child_state->must_code_qp_delta = false;
child_state->tqj_bitstream_written = NULL;
child_state->tqj_recon_done = NULL;

View file

@ -526,68 +526,81 @@ static void encode_sao(encoder_state_t * const state,
/**
* \brief Sets the QP for each CU in state->tile->frame->cu_array.
*
* The QPs are used in deblocking.
* The QPs are used in deblocking and QP prediction.
*
* The delta QP for an LCU is coded when the first CU with coded block flag
* set is encountered. Hence, for the purposes of deblocking, all CUs
* before the first one with cbf set use state->ref_qp and all CUs after
* that use state->qp.
* The QP delta for a quantization group is coded when the first CU with
* coded block flag set is encountered. Hence, for the purposes of
* deblocking and QP prediction, all CUs in before the first one that has
* cbf set use the QP predictor and all CUs after that use (QP predictor
* + QP delta).
*
* \param state encoder state
* \param x x-coordinate of the left edge of the root CU
* \param y y-coordinate of the top edge of the root CU
* \param depth depth in the CU quadtree
* \param coeffs_coded Used for tracking whether a CU with a residual
* has been encountered. Should be set to false at
* the top level.
* \return Whether there were any CUs with residual or not.
* \param last_qp QP of the last CU in the last quantization group
* \param prev_qp -1 if QP delta has not been coded in current QG,
* otherwise the QP of the current QG
*/
static bool set_cu_qps(encoder_state_t *state, int x, int y, int depth, bool coeffs_coded)
static void set_cu_qps(encoder_state_t *state, int x, int y, int depth, int *last_qp, int *prev_qp)
{
if (state->qp == state->ref_qp) {
// If the QPs are equal there is no need to care about the residuals.
coeffs_coded = true;
}
// Stop recursion if the CU is completely outside the frame.
if (x >= state->tile->frame->width || y >= state->tile->frame->height) return;
cu_info_t *cu = kvz_cu_array_at(state->tile->frame->cu_array, x, y);
const int cu_width = LCU_WIDTH >> depth;
coeffs_coded = coeffs_coded || cbf_is_set_any(cu->cbf, cu->depth);
if (!coeffs_coded && cu->depth > depth) {
if (depth <= state->encoder_control->max_qp_delta_depth) {
*prev_qp = -1;
}
if (cu->depth > depth) {
// Recursively process sub-CUs.
const int d = cu_width >> 1;
coeffs_coded = set_cu_qps(state, x, y, depth + 1, coeffs_coded);
coeffs_coded = set_cu_qps(state, x + d, y, depth + 1, coeffs_coded);
coeffs_coded = set_cu_qps(state, x, y + d, depth + 1, coeffs_coded);
coeffs_coded = set_cu_qps(state, x + d, y + d, depth + 1, coeffs_coded);
set_cu_qps(state, x, y, depth + 1, last_qp, prev_qp);
set_cu_qps(state, x + d, y, depth + 1, last_qp, prev_qp);
set_cu_qps(state, x, y + d, depth + 1, last_qp, prev_qp);
set_cu_qps(state, x + d, y + d, depth + 1, last_qp, prev_qp);
} else {
if (!coeffs_coded && cu->tr_depth > depth) {
bool cbf_found = *prev_qp >= 0;
if (cu->tr_depth > depth) {
// The CU is split into smaller transform units. Check whether coded
// block flag is set for any of the TUs.
const int tu_width = LCU_WIDTH >> cu->tr_depth;
for (int y_scu = y; y_scu < y + cu_width; y_scu += tu_width) {
for (int x_scu = x; x_scu < x + cu_width; x_scu += tu_width) {
for (int y_scu = y; !cbf_found && y_scu < y + cu_width; y_scu += tu_width) {
for (int x_scu = x; !cbf_found && x_scu < x + cu_width; x_scu += tu_width) {
cu_info_t *tu = kvz_cu_array_at(state->tile->frame->cu_array, x_scu, y_scu);
if (cbf_is_set_any(tu->cbf, cu->depth)) {
coeffs_coded = true;
cbf_found = true;
}
}
}
} else if (cbf_is_set_any(cu->cbf, cu->depth)) {
cbf_found = true;
}
int8_t qp;
if (cbf_found) {
*prev_qp = qp = cu->qp;
} else {
qp = kvz_get_cu_ref_qp(state, x, y, *last_qp);
}
// Set the correct QP for all state->tile->frame->cu_array elements in
// the area covered by the CU.
const int8_t qp = coeffs_coded ? state->qp : state->ref_qp;
for (int y_scu = y; y_scu < y + cu_width; y_scu += SCU_WIDTH) {
for (int x_scu = x; x_scu < x + cu_width; x_scu += SCU_WIDTH) {
kvz_cu_array_at(state->tile->frame->cu_array, x_scu, y_scu)->qp = qp;
}
}
}
return coeffs_coded;
if (is_last_cu_in_qg(state, x, y, depth)) {
*last_qp = cu->qp;
}
}
}
@ -608,11 +621,13 @@ static void encoder_state_worker_encode_lcu(void * opaque)
encoder_state_recdata_to_bufs(state, lcu, state->tile->hor_buf_search, state->tile->ver_buf_search);
if (encoder->cfg.deblock_enable) {
if (encoder->lcu_dqp_enabled) {
set_cu_qps(state, lcu->position_px.x, lcu->position_px.y, 0, false);
}
if (encoder->max_qp_delta_depth >= 0) {
int last_qp = state->last_qp;
int prev_qp = -1;
set_cu_qps(state, lcu->position_px.x, lcu->position_px.y, 0, &last_qp, &prev_qp);
}
if (encoder->cfg.deblock_enable) {
kvz_filter_deblock_lcu(state, lcu->position_px.x, lcu->position_px.y);
}
@ -635,9 +650,6 @@ static void encoder_state_worker_encode_lcu(void * opaque)
encode_sao(state, lcu->position.x, lcu->position.y, &frame->sao_luma[lcu->position.y * frame->width_in_lcu + lcu->position.x], &frame->sao_chroma[lcu->position.y * frame->width_in_lcu + lcu->position.x]);
}
// QP delta is not used when rate control is turned off.
state->must_code_qp_delta = encoder->lcu_dqp_enabled;
//Encode coding tree
kvz_encode_coding_tree(state, lcu->position.x * LCU_WIDTH, lcu->position.y * LCU_WIDTH, 0);
@ -709,7 +721,7 @@ static void encoder_state_encode_leaf(encoder_state_t * const state)
const encoder_control_t *ctrl = state->encoder_control;
const kvz_config *cfg = &ctrl->cfg;
state->ref_qp = state->frame->QP;
state->last_qp = state->frame->QP;
if (cfg->crypto_features) {
state->crypto_hdl = kvz_crypto_create(cfg);
@ -1362,3 +1374,27 @@ lcu_stats_t* kvz_get_lcu_stats(encoder_state_t *state, int lcu_x, int lcu_y)
state->encoder_control->in.width_in_lcu;
return &state->frame->lcu_stats[index];
}
int kvz_get_cu_ref_qp(const encoder_state_t *state, int x, int y, int last_qp)
{
const encoder_control_t *ctrl = state->encoder_control;
const cu_array_t *cua = state->tile->frame->cu_array;
// Quantization group width
const int qg_width = LCU_WIDTH >> MIN(ctrl->max_qp_delta_depth, kvz_cu_array_at_const(cua, x, y)->depth);
// Coordinates of the top-left corner of the quantization group
const int x_qg = x & ~(qg_width - 1);
const int y_qg = y & ~(qg_width - 1);
int qp_pred_a = last_qp;
if (x_qg % LCU_WIDTH > 0) {
qp_pred_a = kvz_cu_array_at_const(cua, x_qg - 1, y_qg)->qp;
}
int qp_pred_b = last_qp;
if (y_qg % LCU_WIDTH > 0) {
qp_pred_b = kvz_cu_array_at_const(cua, x_qg, y_qg - 1)->qp;
}
return ((qp_pred_a + qp_pred_b + 1) >> 1);
}

View file

@ -268,10 +268,17 @@ typedef struct encoder_state_t {
bool must_code_qp_delta;
/**
* \brief Reference for computing QP delta for the next LCU that is coded
* next. Updated whenever a QP delta is coded.
* \brief QP value of the last CU in the last coded quantization group.
*
* A quantization group is a square of width
* (LCU_WIDTH >> encoder_control->max_qp_delta_depth). All CUs of in the
* same quantization group share the QP predictor value, but may have
* different QP values.
*
* Set to the frame QP at the beginning of a wavefront row or a tile and
* updated when the last CU of a quantization group is coded.
*/
int8_t ref_qp;
int8_t last_qp;
/**
* \brief Coeffs for the LCU.
@ -297,6 +304,8 @@ void kvz_encoder_create_ref_lists(const encoder_state_t *const state);
lcu_stats_t* kvz_get_lcu_stats(encoder_state_t *state, int lcu_x, int lcu_y);
int kvz_get_cu_ref_qp(const encoder_state_t *state, int x, int y, int last_qp);
/**
* Whether the parameter sets should be written with the current frame.
*/
@ -309,6 +318,30 @@ static INLINE bool encoder_state_must_write_vps(const encoder_state_t *state)
(vps_period >= 0 && frame == 0);
}
/**
* \brief Returns true if the CU is the last CU in its containing
* quantization group.
*
* \param state encoder state
* \param x x-coordinate of the left edge of the CU
* \param y y-cooradinate of the top edge of the CU
* \param depth depth in the CU tree
* \return true, if it's the last CU in its QG, otherwise false
*/
static INLINE bool is_last_cu_in_qg(const encoder_state_t *state, int x, int y, int depth)
{
if (state->encoder_control->max_qp_delta_depth < 0) return false;
const int cu_width = LCU_WIDTH >> depth;
const int qg_width = LCU_WIDTH >> state->encoder_control->max_qp_delta_depth;
const int right = x + cu_width;
const int bottom = y + cu_width;
return (right % qg_width == 0 || right >= state->tile->frame->width) &&
(bottom % qg_width == 0 || bottom >= state->tile->frame->height);
}
static const uint8_t g_group_idx[32] = {
0, 1, 2, 3, 4, 4, 5, 5, 6, 6,
6, 6, 7, 7, 7, 7, 8, 8, 8, 8,

View file

@ -262,7 +262,7 @@ static bool is_on_8x8_grid(int x, int y, edge_dir dir)
static int8_t get_qp_y_pred(const encoder_state_t* state, int x, int y, edge_dir dir)
{
if (!state->encoder_control->lcu_dqp_enabled) {
if (state->encoder_control->max_qp_delta_depth < 0) {
return state->qp;
}

View file

@ -138,6 +138,7 @@ static void lcu_fill_cu_info(lcu_t *lcu, int x_local, int y_local, int width, in
to->type = cu->type;
to->depth = cu->depth;
to->part_size = cu->part_size;
to->qp = cu->qp;
if (cu->type == CU_INTRA) {
to->intra.mode = cu->intra.mode;
@ -413,6 +414,7 @@ static double search_cu(encoder_state_t * const state, int x, int y, int depth,
cur_cu->tr_depth = depth > 0 ? depth : 1;
cur_cu->type = CU_NOTSET;
cur_cu->part_size = SIZE_2Nx2N;
cur_cu->qp = state->qp;
// If the CU is completely inside the frame at this depth, search for
// prediction modes at this depth.

View file

@ -1746,6 +1746,7 @@ void kvz_search_cu_smp(encoder_state_t * const state,
cur_pu->type = CU_INTER;
cur_pu->part_size = part_mode;
cur_pu->depth = depth;
cur_pu->qp = state->qp;
double cost = MAX_INT;
uint32_t bitcost = MAX_INT;