diff --git a/src/cfg.c b/src/cfg.c index 865e650e..36e04cb5 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -118,6 +118,9 @@ int kvz_config_init(kvz_config *cfg) cfg->input_format = KVZ_FORMAT_P420; cfg->input_bitdepth = 8; + cfg->gop_lp_definition.d = 0; + cfg->gop_lp_definition.t = 0; + return 1; } @@ -683,105 +686,32 @@ int kvz_config_parse(kvz_config *cfg, const char *name, const char *value) struct { unsigned g; // length unsigned d; // depth - unsigned r; // references unsigned t; // temporal - } gop = { 0, 0, 0, 0 }; + } gop = { 0, 0, 0 }; - if (sscanf(value, "lp-g%ud%ur%ut%u", &gop.g, &gop.d, &gop.r, &gop.t) != 4) { - fprintf(stderr, "Error in GOP syntax. Example: lp-g8d4r2t2\n"); + // Parse --gop=lp-g#d#t# + if (sscanf(value, "lp-g%ud%ut%u", &gop.g, &gop.d, &gop.t) != 3) { + fprintf(stderr, "Error in GOP syntax. Example: lp-g8d4t2\n"); return 0; } if (gop.g < 1 || gop.g > 32) { fprintf(stderr, "gop.g must be between 1 and 32.\n"); + return 0; } if (gop.d < 1 || gop.d > 8) { fprintf(stderr, "gop.d must be between 1 and 8.\n"); - } - if (gop.r < 1 || gop.r > 15) { - fprintf(stderr, "gop.d must be between 1 and 15.\n"); + return 0; } if (gop.t < 1 || gop.t > 15) { - fprintf(stderr, "gop.t must be between 1 and 32.\n"); + fprintf(stderr, "gop.t must be between 1 and 15.\n"); + return 0; } - - // Initialize modulos for testing depth. - // The picture belong to the lowest depth in which (poc % modulo) == 0. - unsigned depth_modulos[8] = { 0 }; - for (int d = 0; d < gop.d; ++d) { - depth_modulos[gop.d - 1 - d] = 1 << d; - } - depth_modulos[0] = gop.g; - cfg->gop_lowdelay = 1; + cfg->gop_lowdelay = true; cfg->gop_len = gop.g; - for (int g = 1; g <= gop.g; ++g) { - kvz_gop_config *gop_pic = &cfg->gop[g - 1]; - - // Find gop depth for picture. - int gop_layer = 0; - while (gop_layer < gop.d && (g % depth_modulos[gop_layer])) { - ++gop_layer; - } - - gop_pic->poc_offset = g; - gop_pic->layer = gop_layer + 1; - gop_pic->qp_offset = gop_layer + 1; - gop_pic->ref_pos_count = 0; - gop_pic->ref_neg_count = gop.r; - gop_pic->is_ref = 0; - - // Set first ref to point to previous frame, and the rest to previous - // key-frames. - // If gop.t > 1, have (poc % gop.t) == 0 point gop.t frames away, - // instead of the previous frame. Set the frames in between to - // point to the nearest frame with a lower gop-depth. - if (gop.t > 1) { - if (gop_pic->poc_offset % gop.t == 0) { - gop_pic->ref_neg[0] = gop.t; - } else { - int r = gop_pic->poc_offset - 1; - while (r > 0) { - if (cfg->gop[r].layer < gop_pic->layer) break; - --r; - } - // Var r is now 0 or index of the pic with layer < depth. - if (cfg->gop[r].layer < gop_pic->layer) { - gop_pic->ref_neg[0] = gop_pic->poc_offset - cfg->gop[r].poc_offset; - cfg->gop[r].is_ref = 1; - } else { - // No ref was found, just refer to the previous key-frame. - gop_pic->ref_neg[0] = gop_pic->poc_offset % gop.g; - } - } - } else { - gop_pic->ref_neg[0] = 1; - if (gop_pic->poc_offset >= 2) { - cfg->gop[gop_pic->poc_offset - 2].is_ref = 1; - } - } - - int keyframe = gop_pic->poc_offset; - for (int i = 1; i < gop_pic->ref_neg_count; ++i) { - while (keyframe == gop_pic->ref_neg[i - 1]) { - keyframe += gop.g; - } - gop_pic->ref_neg[i] = keyframe; - } - - gop_pic->qp_factor = 0.4624; // from HM - } - - for (int g = 0; g < gop.g; ++g) { - kvz_gop_config *gop_pic = &cfg->gop[g]; - if (!gop_pic->is_ref) { - gop_pic->qp_factor = 0.68 * 1.31; // derived from HM - } - } - - // Key-frame is always a reference. - cfg->gop[gop.g - 1].is_ref = 1; - cfg->gop[gop.g - 1].qp_factor = 0.578; // from HM + cfg->gop_lp_definition.d = gop.d; + cfg->gop_lp_definition.t = gop.t; } else if (atoi(value) == 8) { cfg->gop_lowdelay = 0; // GOP @@ -821,10 +751,6 @@ int kvz_config_parse(kvz_config *cfg, const char *name, const char *value) fprintf(stderr, "Input error: unsupported gop length, must be 0 or 8\n"); return 0; } - if (cfg->gop_len && cfg->tmvp_enable) { - cfg->tmvp_enable = false; - fprintf(stderr, "Disabling TMVP because GOP is used.\n"); - } } else if OPT("bipred") cfg->bipred = atobool(value); @@ -985,6 +911,97 @@ int kvz_config_parse(kvz_config *cfg, const char *name, const char *value) return 1; } +void kvz_config_process_lp_gop(kvz_config *cfg) +{ + struct { + unsigned g; + unsigned d; + unsigned t; + } gop; + + gop.g = cfg->gop_len; + gop.d = cfg->gop_lp_definition.d; + gop.t = cfg->gop_lp_definition.t; + + // Initialize modulos for testing depth. + // The picture belong to the lowest depth in which (poc % modulo) == 0. + unsigned depth_modulos[8] = { 0 }; + for (int d = 0; d < gop.d; ++d) { + depth_modulos[gop.d - 1 - d] = 1 << d; + } + depth_modulos[0] = gop.g; + + cfg->gop_lowdelay = 1; + cfg->gop_len = gop.g; + for (int g = 1; g <= gop.g; ++g) { + kvz_gop_config *gop_pic = &cfg->gop[g - 1]; + + // Find gop depth for picture. + int gop_layer = 0; + while (gop_layer < gop.d && (g % depth_modulos[gop_layer])) { + ++gop_layer; + } + + gop_pic->poc_offset = g; + gop_pic->layer = gop_layer + 1; + gop_pic->qp_offset = gop_layer + 1; + gop_pic->ref_pos_count = 0; + gop_pic->ref_neg_count = cfg->ref_frames; + gop_pic->is_ref = 0; + + // Set first ref to point to previous frame, and the rest to previous + // key-frames. + // If gop.t > 1, have (poc % gop.t) == 0 point gop.t frames away, + // instead of the previous frame. Set the frames in between to + // point to the nearest frame with a lower gop-depth. + if (gop.t > 1) { + if (gop_pic->poc_offset % gop.t == 0) { + gop_pic->ref_neg[0] = gop.t; + } else { + int r = gop_pic->poc_offset - 1; + while (r > 0) { + if (cfg->gop[r].layer < gop_pic->layer) break; + --r; + } + // Var r is now 0 or index of the pic with layer < depth. + if (cfg->gop[r].layer < gop_pic->layer) { + gop_pic->ref_neg[0] = gop_pic->poc_offset - cfg->gop[r].poc_offset; + cfg->gop[r].is_ref = 1; + } else { + // No ref was found, just refer to the previous key-frame. + gop_pic->ref_neg[0] = gop_pic->poc_offset % gop.g; + } + } + } else { + gop_pic->ref_neg[0] = 1; + if (gop_pic->poc_offset >= 2) { + cfg->gop[gop_pic->poc_offset - 2].is_ref = 1; + } + } + + int keyframe = gop_pic->poc_offset; + for (int i = 1; i < gop_pic->ref_neg_count; ++i) { + while (keyframe == gop_pic->ref_neg[i - 1]) { + keyframe += gop.g; + } + gop_pic->ref_neg[i] = keyframe; + } + + gop_pic->qp_factor = 0.4624; // from HM + } + + for (int g = 0; g < gop.g; ++g) { + kvz_gop_config *gop_pic = &cfg->gop[g]; + if (!gop_pic->is_ref) { + gop_pic->qp_factor = 0.68 * 1.31; // derived from HM + } + } + + // Key-frame is always a reference. + cfg->gop[gop.g - 1].is_ref = 1; + cfg->gop[gop.g - 1].qp_factor = 0.578; // from HM +} + /** * \brief Check that configuration is sensible. * diff --git a/src/cfg.h b/src/cfg.h index 6f0cf2d5..c719ea4a 100644 --- a/src/cfg.h +++ b/src/cfg.h @@ -36,6 +36,7 @@ kvz_config *kvz_config_alloc(void); int kvz_config_init(kvz_config *cfg); int kvz_config_destroy(kvz_config *cfg); int kvz_config_parse(kvz_config *cfg, const char *name, const char *value); +void kvz_config_process_lp_gop(kvz_config *cfg); int kvz_config_validate(const kvz_config *cfg); #endif diff --git a/src/encoder.c b/src/encoder.c index 46d7eaa0..45e2a2cc 100644 --- a/src/encoder.c +++ b/src/encoder.c @@ -80,7 +80,7 @@ static int select_owf_auto(const kvz_config *const cfg) * \param cfg encoder configuration * \return initialized encoder control or NULL on failure */ -encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg) { +encoder_control_t* kvz_encoder_control_init(kvz_config *const cfg) { encoder_control_t *encoder = NULL; if (!cfg) { @@ -88,6 +88,16 @@ encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg) { goto init_failed; } + if (cfg->gop_len > 0) { + if (cfg->tmvp_enable) { + cfg->tmvp_enable = false; + fprintf(stderr, "Disabling TMVP because GOP is used.\n"); + } + if (cfg->gop_lowdelay) { + kvz_config_process_lp_gop(cfg); + } + } + // Make sure that the parameters make sense. if (!kvz_config_validate(cfg)) { goto init_failed; diff --git a/src/encoder.h b/src/encoder.h index ee9909bd..f3f41e76 100644 --- a/src/encoder.h +++ b/src/encoder.h @@ -155,7 +155,7 @@ typedef struct encoder_control_t } encoder_control_t; -encoder_control_t* kvz_encoder_control_init(const kvz_config *cfg); +encoder_control_t* kvz_encoder_control_init(kvz_config *cfg); void kvz_encoder_control_free(encoder_control_t *encoder); void kvz_encoder_control_input_init(encoder_control_t *encoder, int32_t width, int32_t height); diff --git a/src/kvazaar.c b/src/kvazaar.c index f0ae08cf..d5d3dcac 100644 --- a/src/kvazaar.c +++ b/src/kvazaar.c @@ -75,7 +75,9 @@ static kvz_encoder * kvazaar_open(const kvz_config *cfg) goto kvazaar_open_failure; } - encoder->control = kvz_encoder_control_init(cfg); + // FIXME: const qualifier disgarded. I don't want to change kvazaar_open + // but I really need to change cfg. + encoder->control = kvz_encoder_control_init((kvz_config*)cfg); if (!encoder->control) { goto kvazaar_open_failure; } diff --git a/src/kvazaar.h b/src/kvazaar.h index 50a26b25..ef7cb101 100644 --- a/src/kvazaar.h +++ b/src/kvazaar.h @@ -312,6 +312,11 @@ typedef struct kvz_config enum kvz_input_format input_format; /*!< \brief Use Temporal Motion Vector Predictors. */ int32_t input_bitdepth; /*!< \brief Use Temporal Motion Vector Predictors. */ + + struct { + unsigned d; // depth + unsigned t; // temporal + } gop_lp_definition; } kvz_config; /**