From ee3d4d0e78c66ace37b9f61687151901fbc0a9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arttu=20Yl=C3=A4-Outinen?= Date: Wed, 19 Apr 2017 15:47:47 +0300 Subject: [PATCH] Add adaptive QP for 360 degree video Adds option --erp-aqp for enabling adaptive QP for 360 degree video with equirectangular projection. When projected into a spherical surface, the middle part of the video covers relatively larger area than the top and bottom parts. Enabling --erp-aqp sets up a ROI delta QP array which uses higher QPs for the top and bottom of the video and lower QPs for the middle part. --- README.md | 2 + doc/kvazaar.1 | 6 ++- src/cfg.c | 4 ++ src/cli.c | 4 ++ src/encoder.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/kvazaar.h | 5 +++ 6 files changed, 117 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b2ae5ba..2ba03bf9 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ Video structure: delta QP values in raster order. The delta QP map can be any size or aspect ratio, and will be mapped to LCU's. + --(no-)erp-aqp : Use adaptive QP for 360 video with + equirectangular projection Compression tools: --deblock [] : Deblocking diff --git a/doc/kvazaar.1 b/doc/kvazaar.1 index 7dc1f23f..4b30c7ed 100644 --- a/doc/kvazaar.1 +++ b/doc/kvazaar.1 @@ -1,4 +1,4 @@ -.TH KVAZAAR "1" "February 2017" "kvazaar v1.1.0" "User Commands" +.TH KVAZAAR "1" "April 2017" "kvazaar v1.1.0" "User Commands" .SH NAME kvazaar \- open source HEVC encoder .SH SYNOPSIS @@ -131,6 +131,10 @@ Use a delta QP map for region of interest delta QP values in raster order. The delta QP map can be any size or aspect ratio, and will be mapped to LCU's. +.TP +\fB\-\-(no\-)erp\-aqp +Use adaptive QP for 360 video with +equirectangular projection .SS "Compression tools:" .TP diff --git a/src/cfg.c b/src/cfg.c index f0135ec0..03c0f10c 100644 --- a/src/cfg.c +++ b/src/cfg.c @@ -119,6 +119,8 @@ int kvz_config_init(kvz_config *cfg) cfg->roi.height = 0; cfg->roi.dqps = NULL; + cfg->erp_aqp = false; + cfg->slices = KVZ_SLICES_NONE; return 1; @@ -1045,6 +1047,8 @@ int kvz_config_parse(kvz_config *cfg, const char *name, const char *value) fclose(f); } + else if OPT("erp-aqp") + cfg->erp_aqp = (bool)atobool(value); else return 0; #undef OPT diff --git a/src/cli.c b/src/cli.c index 5c39a9f0..c821b7bb 100644 --- a/src/cli.c +++ b/src/cli.c @@ -119,6 +119,8 @@ static const struct option long_options[] = { { "implicit-rdpcm", no_argument, NULL, 0 }, { "no-implicit-rdpcm", no_argument, NULL, 0 }, { "roi", required_argument, NULL, 0 }, + { "erp-aqp", no_argument, NULL, 0 }, + { "no-erp-aqp", no_argument, NULL, 0 }, {0, 0, 0, 0} }; @@ -388,6 +390,8 @@ void print_help(void) " delta QP values in raster order.\n" " The delta QP map can be any size or aspect\n" " ratio, and will be mapped to LCU's.\n" + " --(no-)erp-aqp : Use adaptive QP for 360 video with\n" + " equirectangular projection\n" "\n" /* Word wrap to this width to stay under 80 characters (including ") ************/ "Compression tools:\n" diff --git a/src/encoder.c b/src/encoder.c index af9647e3..1acc3c23 100644 --- a/src/encoder.c +++ b/src/encoder.c @@ -20,6 +20,9 @@ #include "encoder.h" +// This define is required for M_PI on Windows. +#define _USE_MATH_DEFINES +#include #include #include @@ -27,6 +30,14 @@ #include "strategyselector.h" +/** + * \brief Strength of QP adjustments when using adaptive QP for 360 video. + * + * Determined empirically. + */ +static const double ERP_AQP_STRENGTH = 3.0; + + static int encoder_control_init_gop_layer_weights(encoder_control_t * const); static int size_of_wpp_ends(int threads) @@ -114,6 +125,82 @@ static unsigned cfg_num_threads(void) } +/** + * \brief Return weight for 360 degree ERP video + * + * Returns the scaling factor of area from equirectangular projection to + * spherical surface. + * + * \param y y-coordinate of the pixel + * \param h height of the picture + */ +static double ws_weight(int y, int h) +{ + return cos((y - 0.5 * h + 0.5) * (M_PI / h)); +} + + + +/** + * \brief Update ROI QPs for 360 video with equirectangular projection. + * + * Writes updated ROI parameters to encoder->cfg.roi. + * + * \param encoder encoder control + * \param orig_roi original delta QPs or NULL + * \param orig_width width of orig_roi + * \param orig_height height of orig_roi + */ +static void init_erp_aqp_roi(encoder_control_t* encoder, + int8_t *orig_roi, + int32_t orig_width, + int32_t orig_height) +{ + // Update ROI with WS-PSNR delta QPs. + int height = encoder->in.height_in_lcu; + int width = orig_roi ? orig_width : 1; + + int frame_height = encoder->in.real_height; + + encoder->cfg.roi.width = width; + encoder->cfg.roi.height = height; + encoder->cfg.roi.dqps = calloc(width * height, sizeof(orig_roi[0])); + + double total_weight = 0.0; + for (int y = 0; y < frame_height; y++) { + total_weight += ws_weight(y, frame_height); + } + + for (int y_lcu = 0; y_lcu < height; y_lcu++) { + int y_orig = LCU_WIDTH * y_lcu; + int lcu_height = MIN(LCU_WIDTH, frame_height - y_orig); + + double lcu_weight = 0.0; + for (int y = y_orig; y < y_orig + lcu_height; y++) { + lcu_weight += ws_weight(y, frame_height); + } + // Normalize. + lcu_weight = (lcu_weight * frame_height) / (total_weight * lcu_height); + + int8_t qp_delta = round(-ERP_AQP_STRENGTH * log2(lcu_weight)); + + if (orig_roi) { + // If a ROI array already exists, we copy the existing values to the + // new array while adding qp_delta to each. + int y_roi = y_lcu * orig_height / height; + for (int x = 0; x < width; x++) { + encoder->cfg.roi.dqps[x + y_lcu * width] = + CLIP(-51, 51, orig_roi[x + y_roi * width] + qp_delta); + } + + } else { + // Otherwise, simply write qp_delta to the ROI array. + encoder->cfg.roi.dqps[y_lcu] = qp_delta; + } + } +} + + /** * \brief Allocate and initialize an encoder control structure. * @@ -219,16 +306,23 @@ encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg) goto init_failed; } - // Copy delta QP array for ROI coding. - if (cfg->roi.dqps) { + if (cfg->erp_aqp) { + init_erp_aqp_roi(encoder, + cfg->roi.dqps, + cfg->roi.width, + cfg->roi.height); + + } else if (cfg->roi.dqps) { + // Copy delta QP array for ROI coding. const size_t roi_size = encoder->cfg.roi.width * encoder->cfg.roi.height; encoder->cfg.roi.dqps = calloc(roi_size, sizeof(cfg->roi.dqps[0])); memcpy(encoder->cfg.roi.dqps, cfg->roi.dqps, roi_size * sizeof(*cfg->roi.dqps)); + } - encoder->lcu_dqp_enabled = cfg->target_bitrate > 0 || cfg->roi.dqps; + encoder->lcu_dqp_enabled = cfg->target_bitrate > 0 || encoder->cfg.roi.dqps; //Tiles encoder->tiles_enable = encoder->cfg.tiles_width_count > 1 || diff --git a/src/kvazaar.h b/src/kvazaar.h index 0b597e2e..76c3b0ea 100644 --- a/src/kvazaar.h +++ b/src/kvazaar.h @@ -339,6 +339,11 @@ typedef struct kvz_config } roi; /*!< \since 3.14.0 \brief Map of delta QPs for region of interest coding. */ unsigned slices; /*!< \since 3.15.0 \brief How to map slices to frame. */ + + /** + * \brief Use adaptive QP for 360 video with equirectangular projection. + */ + int32_t erp_aqp; } kvz_config; /**