Skip to content

Commit

Permalink
Add variance-boost feature by juliobbv
Browse files Browse the repository at this point in the history
  • Loading branch information
BlueSwordM committed Jan 7, 2024
1 parent 279e7b2 commit 649e224
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 8 deletions.
5 changes: 5 additions & 0 deletions Source/API/EbDebugMacros.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ extern "C" {

// Switch frame debugging code
#define DEBUG_SFRAME 0

// Variance boost debugging code
#define DEBUG_VAR_BOOST 0
#define DEBUG_VAR_BOOST_QP 0

// Quantization matrices
#define DEBUG_QM_LEVEL 0
#define DEBUG_STARTUP_MG_SIZE 0
Expand Down
21 changes: 21 additions & 0 deletions Source/API/EbSvtAv1Enc.h
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,27 @@ typedef struct EbSvtAv1EncConfiguration {
uint8_t padding[64 - sizeof(Bool)];

AomFilmGrain *fgs_table;

/* @brief Boost low variance regions according to a fast-growing formula
0: no boost
1: mild boost
2: gentle boost
3: medium boost
4: aggressive boost
5: maximum boost
Default is 3 (medium curve) */
uint8_t variance_boost_strength;

/* @brief Enables the new 8x8-based variance algorithm, and picks an 8x8 variance value per superblock to determine boost
Lower values enable detecting more blocks that need boosting, at the expense of more possible false positives (overall bitrate increase)
0: disabled, uses classic 64x64 based variance algorithm instead
1: enabled, 1st octile
4: enabled, median
8: enabled, maximum
Default is 4
*/
uint8_t new_variance_octile;

} EbSvtAv1EncConfiguration;

/**
Expand Down
35 changes: 35 additions & 0 deletions Source/App/EncApp/EbAppConfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@

#define ROI_MAP_FILE_TOKEN "--roi-map-file"

#define VARIANCE_BOOST_STRENGTH_TOKEN "--variance-boost-strength"
#define NEW_VARIANCE_OCTILE_TOKEN "--new-variance-octile"

static EbErrorType validate_error(EbErrorType err, const char *token, const char *value) {
switch (err) {
case EB_ErrorNone: return EB_ErrorNone;
Expand Down Expand Up @@ -1177,6 +1180,19 @@ ConfigEntry config_entry_color_description[] = {
// Termination
{SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_variance_boost[] = {
// Variance boost
{SINGLE_INPUT,
VARIANCE_BOOST_STRENGTH_TOKEN,
"Variance boost strength, default is 3 [0-5]",
set_cfg_generic_token},
{SINGLE_INPUT,
NEW_VARIANCE_OCTILE_TOKEN,
"Octile for new 8x8 variance algorithm. Set to 0 to use 64x64 variance algorithm, default is 4 (median) [0-8]",
set_cfg_generic_token},
// Termination
{SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry[] = {
// Options
{SINGLE_INPUT, INPUT_FILE_TOKEN, "InputFile", set_cfg_input_file},
Expand Down Expand Up @@ -1351,6 +1367,10 @@ ConfigEntry config_entry[] = {
// ROI
{SINGLE_INPUT, ROI_MAP_FILE_TOKEN, "RoiMapFile", set_cfg_roi_map_file},

// Variance boost
{SINGLE_INPUT, VARIANCE_BOOST_STRENGTH_TOKEN, "VarianceBoostStrength", set_cfg_generic_token},
{SINGLE_INPUT, NEW_VARIANCE_OCTILE_TOKEN, "NewVarianceOctile", set_cfg_generic_token},

// Termination
{SINGLE_INPUT, NULL, NULL, NULL}};

Expand Down Expand Up @@ -2029,6 +2049,21 @@ uint32_t get_help(int32_t argc, char *const argv[]) {
cd_token_index->name);
}
}

printf("\nVariance Boost Options:\n");
for (ConfigEntry *cd_token_index = config_entry_variance_boost; cd_token_index->token; ++cd_token_index) {
switch (check_long(*cd_token_index, cd_token_index[1])) {
case 1:
printf(" %s, %-25s %-25s\n", cd_token_index->token, cd_token_index[1].token, cd_token_index->name);
++cd_token_index;
break;
default:
printf(cd_token_index->token[1] == '-' ? " %-25s %-25s\n" : " -%-25s %-25s\n",
cd_token_index->token,
cd_token_index->name);
}
}

return 1;
}

Expand Down
2 changes: 1 addition & 1 deletion Source/Lib/Encoder/Codec/EbPictureAnalysisProcess.c
Original file line number Diff line number Diff line change
Expand Up @@ -1145,7 +1145,7 @@ static EbErrorType compute_block_mean_compute_variance(
mean_of32x32_squared_values_blocks[3]) >>
2;
// 8x8 variances
if (scs->static_config.enable_adaptive_quantization == 1) {
if (scs->static_config.enable_adaptive_quantization == 1 || scs->static_config.new_variance_octile) {
pcs->variance[sb_index][ME_TIER_ZERO_PU_8x8_0] = (uint16_t)((mean_of_8x8_squared_values_blocks[0] -
(mean_of8x8_blocks[0] * mean_of8x8_blocks[0])) >>
VARIANCE_PRECISION);
Expand Down
2 changes: 1 addition & 1 deletion Source/Lib/Encoder/Codec/EbPictureControlSet.c
Original file line number Diff line number Diff line change
Expand Up @@ -1349,7 +1349,7 @@ static EbErrorType picture_parent_control_set_ctor(PictureParentControlSet *obje

if (init_data_ptr->calculate_variance) {
uint8_t block_count;
if (init_data_ptr->enable_adaptive_quantization == 1)
if (init_data_ptr->enable_adaptive_quantization == 1 || init_data_ptr->new_variance_octile)
block_count = 85;
else
block_count = 1;
Expand Down
2 changes: 2 additions & 0 deletions Source/Lib/Encoder/Codec/EbPictureControlSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,8 @@ typedef struct PictureControlSetInitData {
uint8_t calculate_variance;
Bool is_scale;
bool rtc_tune;
uint8_t variance_boost_strength;
uint8_t new_variance_octile;
} PictureControlSetInitData;

typedef struct Av1Comp {
Expand Down
255 changes: 255 additions & 0 deletions Source/Lib/Encoder/Codec/EbRateControlProcess.c
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,253 @@ static void generate_b64_me_qindex_map(PictureControlSet *pcs) {
}
}

int variance_comp_int(const void *a, const void *b)
{
return (int)* (uint16_t *)a - * (uint16_t *)b;
}

#define VAR_BOOST_MAX_UNSCALED_DELTAQ_RANGE 120
#define VAR_BOOST_MAX_DELTAQ_RANGE 80

static int av1_get_deltaq_sb_variance_boost(
uint8_t base_q_idx,
uint16_t* variances,
uint8_t strength,
EbBitDepth bit_depth,
uint8_t octile) {

// boost q_index based on empirical visual testing
// scale boost depending on base qindex, gentle (strength 2) curve (lower base_q_idx = lower boost)
// sb variance approximate deltaq boost (@ base_q_idx 255)
// 256 0
// 64 25
// 16 50
// 4 75
// 1 100

// copy sb 8x8 variance values to an array for ordering
uint16_t ordered_variances[64];
memcpy(&ordered_variances, variances + ME_TIER_ZERO_PU_8x8_0, sizeof(uint16_t) * 64);
qsort(&ordered_variances, 64, sizeof(uint16_t), variance_comp_int);

// Take the 8x8 variance value in the specified octile
assert(octile >= 1 && octile <= 8);
uint16_t variance = ordered_variances[octile * 8 - 1];

#if DEBUG_VAR_BOOST
SVT_INFO("64x64 variance: %d\n", variances[ME_TIER_ZERO_PU_64x64]);
SVT_INFO("8x8 min %d, 1st oct %d, median %d, max %d\n", ordered_variances[0], ordered_variances[7], ordered_variances[31], ordered_variances[63]);
SVT_INFO("8x8 variances\n");
uint16_t* variances_row = variances + ME_TIER_ZERO_PU_8x8_0;

for (int row = 0; row < 8; row++) {
SVT_INFO("%5d, %5d, %5d, %5d, %5d, %5d, %5d, %5d\n", variances_row[0], variances_row[1], variances_row[2], variances_row[3], variances_row[4], variances_row[5], variances_row[6], variances_row[7]);
variances_row += 8;
}
#endif

// variance = 0 areas are either completely flat patches or very fine gradients
// SVT-AV1 doesn't have enough resolution to tell them apart, so let's assume they're not flat and boost them
if (variance == 0) {
variance = 1;
}

double max_boost = 0;

// compute a boost based on a fast-growing formula
// high and medium variance sbs essentially get no boost, while increasingly lower variance sbs get stronger boosts
switch (strength)
{
case 1: // mild strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 0.75;
break;
case 2: // gentle strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 1.25;
break;
case 3: // medium strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 1.75;
break;
case 4: // aggressive strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 3;
break;
case 5: // extreme strength, crossover at 512 variance
max_boost = (-20 * log2((double)variance) + 180) * 1.25;
break;
}

max_boost = CLIP3(0, VAR_BOOST_MAX_UNSCALED_DELTAQ_RANGE, max_boost);

// current scale boost algorithm, accurate across all CRFs
int32_t base_q = svt_av1_convert_qindex_to_q_fp8(base_q_idx, bit_depth);
int32_t target_q = (int32_t)(base_q / pow(1.018, max_boost));

int32_t scaled_boost = (int32_t)(base_q_idx * -svt_av1_compute_qdelta_fp(base_q, target_q, bit_depth) / 255);
scaled_boost = AOMMIN(VAR_BOOST_MAX_DELTAQ_RANGE, scaled_boost);

#if DEBUG_VAR_BOOST
// previous scale boost algorithm, inaccurate for low CRFs (calculated here for debugging purposes)
int32_t old_scaled_boost = (int32_t)(base_q_idx * max_boost / 255);
old_scaled_boost = AOMMIN(VAR_BOOST_MAX_DELTAQ_RANGE, old_scaled_boost);

SVT_INFO("Variance: %d, Strength: %d, Max boost: %f, Old scaled boost: %d, Scaled boost: %d, Base q: %d, Target q: %d\n", variance, strength, max_boost, old_scaled_boost, scaled_boost, base_q, target_q);
#endif

return scaled_boost;
}

static int av1_get_deltaq_sb_variance_boost_classic(
uint8_t base_q_idx,
uint16_t variance,
uint8_t strength,
EbBitDepth bit_depth) {
// boost q_index based on empirical visual testing
// scale boost depending on base qindex, gentle (strength 2) curve (lower base_q_idx = lower boost)
// sb variance approximate deltaq boost (@ base_q_idx 255)
// 256 0
// 64 25
// 16 50
// 4 75
// 1 100

// variance = 0 areas are either completely flat patches or very fine gradients
// SVT-AV1 doesn't have enough resolution to tell them apart, so let's assume they're not flat and boost them
if (variance == 0) {
variance = 1;
}

double max_boost = 0;

// compute a boost based on a fast-growing formula
// high and medium variance sbs essentially get no boost, while increasingly lower variance sbs get stronger boosts
switch (strength)
{
case 1: // mild strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 0.75;
break;
case 2: // gentle strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 1.25;
break;
case 3: // medium strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 1.75;
break;
case 4: // aggressive strength, crossover at 256 variance
max_boost = (-10 * log2((double)variance) + 80) * 3;
break;
case 5: // extreme strength, crossover at 512 variance
max_boost = (-20 * log2((double)variance) + 180) * 1.25;
break;
}

max_boost = CLIP3(0, 120, max_boost);

// current scale boost algorithm, accurate across all CRFs
int32_t base_q = svt_av1_convert_qindex_to_q_fp8(base_q_idx, bit_depth);
int32_t target_q = (int32_t)(base_q / pow(1.018, max_boost));

int32_t scaled_boost = (int32_t)(base_q_idx * -svt_av1_compute_qdelta_fp(base_q, target_q, bit_depth) / 255);
scaled_boost = AOMMIN(80, scaled_boost);

#if DEBUG_VAR_BOOST
// previous scale boost algorithm, inaccurate for low CRFs (calculated here for debugging purposes)
int32_t old_scaled_boost = (int32_t)(base_q_idx * max_boost / 255);
old_scaled_boost = AOMMIN(80, old_scaled_boost);

SVT_INFO("Variance: %d, Strength: %d, Max boost: %f, Old scaled boost: %d, Scaled boost: %d, Base q: %d, Target q: %d\n", variance, strength, max_boost, old_scaled_boost, scaled_boost, base_q, target_q);
#endif

return scaled_boost;
}

void svt_variance_adjust_qp(PictureControlSet *pcs, uint8_t strength, uint8_t variance_octile) {
PictureParentControlSet *ppcs_ptr = pcs->ppcs;
SequenceControlSet *scs = pcs->ppcs->scs;
SuperBlock *sb_ptr;
uint32_t sb_addr;

pcs->ppcs->frm_hdr.delta_q_params.delta_q_present = 1;

// super res pictures scaled with different sb count, should use sb_total_count for each picture
uint16_t sb_cnt = scs->sb_total_count;
if (ppcs_ptr->frame_superres_enabled || ppcs_ptr->frame_resize_enabled) {
sb_cnt = ppcs_ptr->b64_total_count;
}

uint8_t min_qindex = MAX_Q_INDEX;
uint8_t max_qindex = MIN_Q_INDEX;

for (sb_addr = 0; sb_addr < sb_cnt; ++sb_addr) {
sb_ptr = pcs->sb_ptr_array[sb_addr];
int boost = 0;

if (variance_octile) {
// adjust deltaq based on sb variance (new, 8x8-based), with lower variance resulting in a lower qindex
boost = av1_get_deltaq_sb_variance_boost(ppcs_ptr->frm_hdr.quantization_params.base_q_idx,
ppcs_ptr->variance[sb_addr],
strength,
scs->static_config.encoder_bit_depth,
variance_octile);
} else {
// adjust deltaq based on sb variance (classic, 64x64-based), with lower variance resulting in a lower qindex
boost = av1_get_deltaq_sb_variance_boost_classic(ppcs_ptr->frm_hdr.quantization_params.base_q_idx,
ppcs_ptr->variance[sb_addr][ME_TIER_ZERO_PU_64x64],
strength,
scs->static_config.encoder_bit_depth);
}

// don't clamp qindex on valid deltaq range yet
// we'll do it after adjusting frame qp to maximize deltaq frame range
sb_ptr->qindex = CLIP3(1, // q_index 0 is lossless, and is currently not supported in SVT-AV1
MAX_Q_INDEX,
sb_ptr->qindex - boost);

// record last seen min and max qindexes for frame qp readjusting
min_qindex = AOMMIN(min_qindex, sb_ptr->qindex);
max_qindex = AOMMAX(max_qindex, sb_ptr->qindex);
}

// normalize and clamp frame qindex value to maximize deltaq range
int range = max_qindex - min_qindex;
range = AOMMIN(range, VAR_BOOST_MAX_DELTAQ_RANGE);
int new_base_q_idx = (int)min_qindex + (range >> 1);

#if DEBUG_VAR_BOOST_QP
// previous calculation method that always set frame qindex to half the available deltaq range, even when not needed
int old_new_base_q_idx = (int)min_qindex + (pcs->ppcs->frm_hdr.delta_q_params.delta_q_res * 9 * 4 - 1);
old_new_base_q_idx = AOMMIN(old_new_base_q_idx, MAX_Q_INDEX);

SVT_INFO("old qidx %d, min_qidx %d, max_qidx %d, delta_q_res %d, new qidx %d, new (old algo) %d, range %d\n",
ppcs_ptr->frm_hdr.quantization_params.base_q_idx,
min_qindex,
max_qindex,
pcs->ppcs->frm_hdr.delta_q_params.delta_q_res,
new_base_q_idx,
old_new_base_q_idx,
range);
#endif
ppcs_ptr->frm_hdr.quantization_params.base_q_idx = new_base_q_idx;

pcs->picture_qp = (uint8_t)CLIP3((int32_t)scs->static_config.min_qp_allowed,
(int32_t)scs->static_config.max_qp_allowed,
(ppcs_ptr->frm_hdr.quantization_params.base_q_idx + 2) >> 2);

// normalize sb qindex values
for (sb_addr = 0; sb_addr < sb_cnt; ++sb_addr) {
sb_ptr = pcs->sb_ptr_array[sb_addr];

int offset = (int)sb_ptr->qindex - ppcs_ptr->frm_hdr.quantization_params.base_q_idx;
offset = AOMMIN(offset, VAR_BOOST_MAX_DELTAQ_RANGE >> 1);
offset = AOMMAX(offset, -VAR_BOOST_MAX_DELTAQ_RANGE >> 1);

uint8_t new_qindex = CLIP3(1, // q_index 0 is lossless, and is currently not supported in SVT-AV1
MAX_Q_INDEX,
((int16_t)ppcs_ptr->frm_hdr.quantization_params.base_q_idx + (int16_t)offset));
#if DEBUG_VAR_BOOST_QP
SVT_INFO(" sb %d qindex: old %d, new %d\n", sb_addr, sb_ptr->qindex, new_qindex);
#endif
sb_ptr->qindex = new_qindex;
}
}

/******************************************************
* svt_aom_sb_qp_derivation_tpl_la
* Calculates the QP per SB based on the tpl statistics
Expand Down Expand Up @@ -3286,6 +3533,14 @@ void *svt_aom_rate_control_kernel(void *input_ptr) {
sb_ptr->qindex = frm_hdr->quantization_params.base_q_idx;
}
}

// adjust sb qps based on variance
if (scs->calculate_variance && scs->static_config.variance_boost_strength) {
svt_variance_adjust_qp(pcs,
scs->static_config.variance_boost_strength,
scs->static_config.new_variance_octile);
}

if (pcs->scs->static_config.tune == 2 && !pcs->ppcs->frm_hdr.delta_q_params.delta_q_present) {
// enable sb level qindex when tune 2
pcs->ppcs->frm_hdr.delta_q_params.delta_q_present = 1;
Expand Down

0 comments on commit 649e224

Please sign in to comment.