mirror of
https://github.com/ultravideo/uvg266.git
synced 2024-11-23 18:14:06 +00:00
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.
This commit is contained in:
parent
79cb3a2fd3
commit
ee3d4d0e78
|
@ -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 [<beta:tc>] : Deblocking
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
100
src/encoder.c
100
src/encoder.c
|
@ -20,6 +20,9 @@
|
|||
|
||||
#include "encoder.h"
|
||||
|
||||
// This define is required for M_PI on Windows.
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
@ -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 ||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue