From 93d2a95ddc98766bc257a93e66bf065f20d36524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arttu=20Yl=C3=A4-Outinen?= Date: Mon, 4 May 2015 12:20:41 +0300 Subject: [PATCH] Implement rate control in lambda domain. - Rate control adjusts the lambda value. - QP is selected according to lambda. - Bits are allocated for GOPs and individual pictures. --- src/config.c | 8 ++++ src/config.h | 1 + src/encoder_state-ctors_dtors.h | 1 - src/encoderstate.c | 52 ++------------------- src/encoderstate.h | 3 ++ src/rate_control.c | 81 +++++++++++++++++++++++---------- src/rate_control.h | 4 +- 7 files changed, 77 insertions(+), 73 deletions(-) diff --git a/src/config.c b/src/config.c index d1c0236f..0fbdcdb5 100644 --- a/src/config.c +++ b/src/config.c @@ -540,34 +540,42 @@ static int config_parse(config_t *cfg, const char *name, const char *value) cfg->gop[0].poc_offset = 8; cfg->gop[0].qp_offset = 1; cfg->gop[0].layer = 1; cfg->gop[0].qp_factor = 0.442; cfg->gop[0].is_ref = 1; cfg->gop[0].ref_pos_count = 0; cfg->gop[0].ref_neg_count = 3; cfg->gop[0].ref_neg[0] = 8; cfg->gop[0].ref_neg[1] = 12; cfg->gop[0].ref_neg[2] = 16; + cfg->gop[0].weight = 6; cfg->gop[1].poc_offset = 4; cfg->gop[1].qp_offset = 2; cfg->gop[1].layer = 2; cfg->gop[1].qp_factor = 0.3536; cfg->gop[1].is_ref = 1; cfg->gop[1].ref_neg_count = 2; cfg->gop[1].ref_neg[0] = 4; cfg->gop[1].ref_neg[1] = 8; cfg->gop[1].ref_pos_count = 1; cfg->gop[1].ref_pos[0] = 4; + cfg->gop[1].weight = 6; cfg->gop[2].poc_offset = 2; cfg->gop[2].qp_offset = 3; cfg->gop[2].layer = 3; cfg->gop[2].qp_factor = 0.3536; cfg->gop[2].is_ref = 1; cfg->gop[2].ref_neg_count = 2; cfg->gop[2].ref_neg[0] = 2; cfg->gop[2].ref_neg[1] = 6; cfg->gop[2].ref_pos_count = 2; cfg->gop[2].ref_pos[0] = 2; cfg->gop[2].ref_pos[1] = 6; + cfg->gop[2].weight = 3; cfg->gop[3].poc_offset = 1; cfg->gop[3].qp_offset = 4; cfg->gop[3].layer = 4; cfg->gop[3].qp_factor = 0.68; cfg->gop[3].is_ref = 0; cfg->gop[3].ref_neg_count = 1; cfg->gop[3].ref_neg[0] = 1; cfg->gop[3].ref_pos_count = 3; cfg->gop[3].ref_pos[0] = 1; cfg->gop[3].ref_pos[1] = 3; cfg->gop[3].ref_pos[2] = 7; + cfg->gop[3].weight = 2; cfg->gop[4].poc_offset = 3; cfg->gop[4].qp_offset = 4; cfg->gop[4].layer = 4; cfg->gop[4].qp_factor = 0.68; cfg->gop[4].is_ref = 0; cfg->gop[4].ref_neg_count = 2; cfg->gop[4].ref_neg[0] = 1; cfg->gop[4].ref_neg[1] = 3; cfg->gop[4].ref_pos_count = 2; cfg->gop[4].ref_pos[0] = 1; cfg->gop[4].ref_pos[1] = 5; + cfg->gop[4].weight = 2; cfg->gop[5].poc_offset = 6; cfg->gop[5].qp_offset = 3; cfg->gop[5].layer = 3; cfg->gop[5].qp_factor = 0.3536; cfg->gop[5].is_ref = 1; cfg->gop[5].ref_neg_count = 2; cfg->gop[5].ref_neg[0] = 2; cfg->gop[5].ref_neg[1] = 6; cfg->gop[5].ref_pos_count = 1; cfg->gop[5].ref_pos[0] = 2; + cfg->gop[5].weight = 3; cfg->gop[6].poc_offset = 5; cfg->gop[6].qp_offset = 4; cfg->gop[6].layer = 4; cfg->gop[6].qp_factor = 0.68; cfg->gop[6].is_ref = 0; cfg->gop[6].ref_neg_count = 2; cfg->gop[6].ref_neg[0] = 1; cfg->gop[6].ref_neg[1] = 5; cfg->gop[6].ref_pos_count = 2; cfg->gop[6].ref_pos[0] = 1; cfg->gop[6].ref_pos[1] = 3; + cfg->gop[6].weight = 2; cfg->gop[7].poc_offset = 7; cfg->gop[7].qp_offset = 4; cfg->gop[7].layer = 4; cfg->gop[7].qp_factor = 0.68; cfg->gop[7].is_ref = 0; cfg->gop[7].ref_neg_count = 3; cfg->gop[7].ref_neg[0] = 1; cfg->gop[7].ref_neg[1] = 3; cfg->gop[7].ref_neg[2] = 7; cfg->gop[7].ref_pos_count = 1; cfg->gop[7].ref_pos[0] = 1; + cfg->gop[7].weight = 2; } else if(atoi(value)) { fprintf(stderr, "Input error: goplen must be 8\n"); return 0; diff --git a/src/config.h b/src/config.h index 65da7243..d2e3655f 100644 --- a/src/config.h +++ b/src/config.h @@ -31,6 +31,7 @@ typedef struct { double qp_factor; int8_t qp_offset; /*!< \brief QP offset */ + int8_t weight; int8_t poc_offset; /*!< \brief POC offset */ int8_t layer; /*!< \brief Current layer */ int8_t is_ref; /*!< \brief Flag if this picture is used as a reference */ diff --git a/src/encoder_state-ctors_dtors.h b/src/encoder_state-ctors_dtors.h index d21831f2..62144705 100644 --- a/src/encoder_state-ctors_dtors.h +++ b/src/encoder_state-ctors_dtors.h @@ -34,7 +34,6 @@ struct encoder_state_t; int encoder_state_init(struct encoder_state_t * child_state, struct encoder_state_t * parent_state); void encoder_state_finalize(struct encoder_state_t *state); -void encoder_state_init_lambda(struct encoder_state_t *state); #endif // ENCODER_STATE_CTORS_DTORS_H_ diff --git a/src/encoderstate.c b/src/encoderstate.c index 1c8040dc..5d70c30e 100644 --- a/src/encoderstate.c +++ b/src/encoderstate.c @@ -49,37 +49,6 @@ # define LMBD 1.0 #endif -/*! - \brief Initializes lambda-value for current QP - - Implementation closer to HM (Used HM12 as reference) - */ -void encoder_state_init_lambda(encoder_state_t * const state) -{ - double qp = state->global->QP; - double lambda_scale = 1.0 - CLIP(0.0, 0.5, 0.05*(double)state->encoder_control->cfg->gop_len); - double qp_temp = qp - 12; - double lambda; - - // Default QP-factor from HM config - double qp_factor = state->encoder_control->cfg->gop_len ? state->global->QP_factor : 0.4624; - - if (state->global->slicetype == SLICE_I) { - qp_factor=0.57*lambda_scale; - } - - lambda = qp_factor*pow( 2.0, qp_temp/3.0 ); - - if (state->global->slicetype != SLICE_I ) { - lambda *= 0.95; - } - - lambda *= LMBD; - - state->global->cur_lambda_cost = lambda; - state->global->cur_lambda_cost_sqrt = sqrt(lambda); -} - int encoder_state_match_children_of_previous_frame(encoder_state_t * const state) { int i; for (i = 0; state->children[i].encoder_control; ++i) { @@ -802,20 +771,10 @@ static void encoder_state_new_frame(encoder_state_t * const state) { encoder_state_ref_sort(state); } - if (state->encoder_control->cfg->gop_len) { - if (state->global->slicetype == SLICE_I) { - state->global->QP = state->encoder_control->cfg->qp; - state->global->QP_factor = 0.4624; - } - else { - state->global->QP = state->encoder_control->cfg->qp + - state->encoder_control->cfg->gop[state->global->gop_offset].qp_offset; - state->global->QP_factor = state->encoder_control->cfg->gop[state->global->gop_offset].qp_factor; - } - - } else { - state->global->QP = select_picture_QP(state); - } + double lambda = select_picture_lambda(state); + state->global->cur_lambda_cost = lambda; + state->global->cur_lambda_cost_sqrt = sqrt(lambda); + state->global->QP = lambda_to_QP(lambda); } else { //Clear the bitstream if it's not the main encoder @@ -826,9 +785,6 @@ static void encoder_state_new_frame(encoder_state_t * const state) { //Leaf states have cabac and context cabac_start(&state->cabac); init_contexts(state, state->global->QP, state->global->slicetype); - - // Initialize lambda value(s) to use in search - encoder_state_init_lambda(state); } //Clear the jobs diff --git a/src/encoderstate.h b/src/encoderstate.h index 59a0a1d8..f6bd6f24 100644 --- a/src/encoderstate.h +++ b/src/encoderstate.h @@ -89,6 +89,9 @@ typedef struct { //! Number of bits written in the current GOP. uint64_t cur_gop_bits_coded; + //! Number of bits targeted for the current GOP. + double cur_gop_target_bits; + } encoder_state_config_global_t; typedef struct { diff --git a/src/rate_control.c b/src/rate_control.c index d3bfc9f6..df190bf0 100644 --- a/src/rate_control.c +++ b/src/rate_control.c @@ -25,42 +25,77 @@ static const int SMOOTHING_WINDOW = 40; /** - * \brief Select a QP for encoding the next picture + * \brief Allocate bits for the current GOP. * \param state the main encoder state - * \return the QP for the next picture, in range [0, 51] + * + * If GOPs are not used, allocates bits for a single picture. + * + * Sets the cur_gop_target_bits of the encoder state. */ -int8_t select_picture_QP(const encoder_state_t * const state) +static void gop_allocate_bits(encoder_state_t * const state) +{ + const encoder_control_t * const encoder = state->encoder_control; + + const double avg_bits_per_picture = + encoder->cfg->target_bitrate / encoder->cfg->framerate; + + // At this point, total_bits_coded of the current state contains the + // number of bits written encoder->owf frames before the current frame. + int bits_coded = state->global->total_bits_coded; + int pictures_coded = MAX(0, state->global->frame - encoder->owf); + + int gop_offset = (state->global->gop_offset - encoder->owf) % MAX(1, encoder->cfg->gop_len); + // Only take fully coded GOPs into account. + if (encoder->cfg->gop_len > 0 && gop_offset != encoder->cfg->gop_len - 1) { + // Subtract number of bits in the partially coded GOP. + bits_coded -= state->global->cur_gop_bits_coded; + // Subtract number of pictures in the partially coded GOP. + pictures_coded -= gop_offset + 1; + } + + double gop_target_bits = + (avg_bits_per_picture * (pictures_coded + SMOOTHING_WINDOW) - bits_coded) + * MAX(1, encoder->cfg->gop_len) / SMOOTHING_WINDOW; + state->global->cur_gop_target_bits = MAX(200, gop_target_bits); +} + +/** + * \brief Select a lambda value for encoding the next picture + * \param state the main encoder state + * \return lambda for the next picture + */ +double select_picture_lambda(encoder_state_t * const state) { const encoder_control_t * const encoder = state->encoder_control; if (encoder->cfg->target_bitrate <= 0) { // Rate control disabled. - return encoder->cfg->qp; + return exp((encoder->cfg->qp - 13.7223 - 0.5) / 4.2005); } - // At this point, total_bits_coded of the current state contains the - // number of bits written encoder->owf frames before the current frame. - const int bits_coded = state->global->total_bits_coded; - const int pictures_coded = MAX(0, state->global->frame - encoder->owf); + if (encoder->cfg->gop_len == 0 || state->global->gop_offset == 0) { + // a new GOP begins at this frame + gop_allocate_bits(state); + } else { + state->global->cur_gop_target_bits = + state->previous_encoder_state->global->cur_gop_target_bits; + } - const double avg_bits_per_picture = - encoder->cfg->target_bitrate / encoder->cfg->framerate; - - // TODO: use picture weights - const double target_bits_current_picture = - (avg_bits_per_picture * (pictures_coded + SMOOTHING_WINDOW) - bits_coded) - / SMOOTHING_WINDOW; + const double target_bits_current_picture = (encoder->cfg->gop_len > 0) + ? (state->global->cur_gop_target_bits * encoder->cfg->gop[state->global->gop_offset].weight / 22.0) + : state->global->cur_gop_target_bits + ; // TODO: take the picture headers into account const int pixels_per_picture = encoder->in.width * encoder->in.height; const double target_bits_per_pixel = target_bits_current_picture / pixels_per_picture; - // The following magical constants, -5.7420835 and 18.598408755005686 are - // based on the values given in - // - // K. McCann et al., "High Effiency Video Coding (HEVC) Test Model 16 - // (HM 16) Improved Encoder Description", JCTVC-S1002, October 2014, - // (p. 52 - 54) - const int QP = (int)(-5.7420835 * log(MAX(target_bits_per_pixel, 0.001)) + 18.598408755005686); - return CLIP(0, 51, QP); + const double lambda = 3.2003 * pow(target_bits_per_pixel, -1.367); + return CLIP(0.1, 10000, lambda); +} + +int8_t lambda_to_QP(const double lambda) +{ + int8_t qp = 4.2005 * log(lambda) + 13.7223 + 0.5; + return CLIP(0, 51, qp); } diff --git a/src/rate_control.h b/src/rate_control.h index 520dc607..45ab0791 100644 --- a/src/rate_control.h +++ b/src/rate_control.h @@ -27,6 +27,8 @@ #include "encoderstate.h" -int8_t select_picture_QP(const encoder_state_t * const state); +double select_picture_lambda(encoder_state_t * const state); + +int8_t lambda_to_QP(const double lambda); #endif // RATE_CONTROL_H_