blob: 4d70d807d05c8a0f399705c53392cdc7aed4f147 [file] [log] [blame]
/*
* Copyright (c) 2021, Alliance for Open Media. All rights reserved
*
* This source code is subject to the terms of the BSD 2 Clause License and
* the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
* was not distributed with this source code in the LICENSE file, you can
* obtain it at www.aomedia.org/license/software. If the Alliance for Open
* Media Patent License 1.0 was not distributed with this source code in the
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
#include <cstdlib>
#include <vector>
#include "av1/encoder/cost.h"
#include "av1/encoder/tpl_model.h"
#include "av1/encoder/encoder.h"
#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
namespace {
double laplace_prob(double q_step, double b, double zero_bin_ratio,
int qcoeff) {
int abs_qcoeff = abs(qcoeff);
double z0 = fmax(exp(-zero_bin_ratio / 2 * q_step / b), TPL_EPSILON);
if (abs_qcoeff == 0) {
double p0 = 1 - z0;
return p0;
} else {
assert(abs_qcoeff > 0);
double z = fmax(exp(-q_step / b), TPL_EPSILON);
double p = z0 / 2 * (1 - z) * pow(z, abs_qcoeff - 1);
return p;
}
}
TEST(TplModelTest, ExponentialEntropyBoundaryTest1) {
double b = 0;
double q_step = 1;
double entropy = av1_exponential_entropy(q_step, b);
EXPECT_NEAR(entropy, 0, 0.00001);
}
TEST(TplModelTest, TransformCoeffEntropyTest1) {
// Check the consistency between av1_estimate_coeff_entropy() and
// laplace_prob()
double b = 1;
double q_step = 1;
double zero_bin_ratio = 2;
for (int qcoeff = -256; qcoeff < 256; ++qcoeff) {
double rate = av1_estimate_coeff_entropy(q_step, b, zero_bin_ratio, qcoeff);
double prob = laplace_prob(q_step, b, zero_bin_ratio, qcoeff);
double ref_rate = -log2(prob);
EXPECT_DOUBLE_EQ(rate, ref_rate);
}
}
TEST(TplModelTest, TransformCoeffEntropyTest2) {
// Check the consistency between av1_estimate_coeff_entropy(), laplace_prob()
// and av1_laplace_entropy()
double b = 1;
double q_step = 1;
double zero_bin_ratio = 2;
double est_expected_rate = 0;
for (int qcoeff = -20; qcoeff < 20; ++qcoeff) {
double rate = av1_estimate_coeff_entropy(q_step, b, zero_bin_ratio, qcoeff);
double prob = laplace_prob(q_step, b, zero_bin_ratio, qcoeff);
est_expected_rate += prob * rate;
}
double expected_rate = av1_laplace_entropy(q_step, b, zero_bin_ratio);
EXPECT_NEAR(expected_rate, est_expected_rate, 0.001);
}
TEST(TplModelTest, DeltaRateCostZeroFlow) {
// When srcrf_dist equal to recrf_dist, av1_delta_rate_cost should return 0
int64_t srcrf_dist = 256;
int64_t recrf_dist = 256;
int64_t delta_rate = 512;
int pixel_num = 256;
int64_t rate_cost =
av1_delta_rate_cost(delta_rate, recrf_dist, srcrf_dist, pixel_num);
EXPECT_EQ(rate_cost, 0);
}
// a reference function of av1_delta_rate_cost() with delta_rate using bit as
// basic unit
double ref_delta_rate_cost(int64_t delta_rate, double src_rec_ratio,
int pixel_count) {
assert(src_rec_ratio <= 1 && src_rec_ratio >= 0);
double bits_per_pixel = (double)delta_rate / pixel_count;
double p = pow(2, bits_per_pixel);
double flow_rate_per_pixel =
sqrt(p * p / (src_rec_ratio * p * p + (1 - src_rec_ratio)));
double rate_cost = pixel_count * log2(flow_rate_per_pixel);
return rate_cost;
}
TEST(TplModelTest, DeltaRateCostReference) {
const int64_t scale = TPL_DEP_COST_SCALE_LOG2 + AV1_PROB_COST_SHIFT;
std::vector<int64_t> srcrf_dist_arr = { 256, 257, 312 };
std::vector<int64_t> recrf_dist_arr = { 512, 288, 620 };
std::vector<int64_t> delta_rate_arr = { 10, 278, 100 };
for (size_t t = 0; t < srcrf_dist_arr.size(); ++t) {
int64_t srcrf_dist = srcrf_dist_arr[t];
int64_t recrf_dist = recrf_dist_arr[t];
int64_t delta_rate = delta_rate_arr[t];
int64_t scaled_delta_rate = delta_rate << scale;
int pixel_count = 256;
int64_t rate_cost = av1_delta_rate_cost(scaled_delta_rate, recrf_dist,
srcrf_dist, pixel_count);
rate_cost >>= scale;
double src_rec_ratio = (double)srcrf_dist / recrf_dist;
double ref_rate_cost =
ref_delta_rate_cost(delta_rate, src_rec_ratio, pixel_count);
EXPECT_NEAR((double)rate_cost, ref_rate_cost, 1);
}
}
TEST(TplModelTest, GetOverlapAreaHasOverlap) {
// The block a's area is [10, 17) x [18, 24).
// The block b's area is [8, 15) x [17, 23).
// The overlapping area between block a and block b is [10, 15) x [18, 23).
// Therefore, the size of the area is (15 - 10) * (23 - 18) = 25.
int row_a = 10;
int col_a = 18;
int row_b = 8;
int col_b = 17;
int height = 7;
int width = 6;
int overlap_area =
av1_get_overlap_area(row_a, col_a, row_b, col_b, width, height);
EXPECT_EQ(overlap_area, 25);
}
TEST(TplModelTest, GetOverlapAreaNoOverlap) {
// The block a's area is [10, 14) x [18, 22).
// The block b's area is [5, 9) x [5, 9).
// Threre is no overlapping area between block a and block b.
// Therefore, the return value should be zero.
int row_a = 10;
int col_a = 18;
int row_b = 5;
int col_b = 5;
int height = 4;
int width = 4;
int overlap_area =
av1_get_overlap_area(row_a, col_a, row_b, col_b, width, height);
EXPECT_EQ(overlap_area, 0);
}
TEST(TPLModelTest, EstimateFrameRateTest) {
/*
* Transform size: 16x16
* Frame count: 16
* Transform block count: 20
*/
const int txfm_size = 256; // 16x16
const int frame_count = 16;
int q_index_list[16];
TplTxfmStats stats_list[16];
for (int i = 0; i < frame_count; i++) {
q_index_list[i] = 1;
stats_list[i].txfm_block_count = 8;
for (int j = 0; j < txfm_size; j++) {
stats_list[i].abs_coeff_sum[j] = 0;
}
}
double result =
av1_estimate_gop_bitrate(q_index_list, frame_count, stats_list);
EXPECT_NEAR(result, 0, 0.1);
}
TEST(TPLModelTest, TxfmStatsInitTest) {
TplTxfmStats tpl_txfm_stats;
av1_init_tpl_txfm_stats(&tpl_txfm_stats);
EXPECT_EQ(tpl_txfm_stats.coeff_num, 256);
EXPECT_EQ(tpl_txfm_stats.txfm_block_count, 0);
for (int i = 0; i < tpl_txfm_stats.coeff_num; ++i) {
EXPECT_DOUBLE_EQ(tpl_txfm_stats.abs_coeff_sum[i], 0);
}
}
TEST(TPLModelTest, TxfmStatsAccumulateTest) {
TplTxfmStats sub_stats;
av1_init_tpl_txfm_stats(&sub_stats);
sub_stats.txfm_block_count = 17;
for (int i = 0; i < sub_stats.coeff_num; ++i) {
sub_stats.abs_coeff_sum[i] = i;
}
TplTxfmStats accumulated_stats;
av1_init_tpl_txfm_stats(&accumulated_stats);
accumulated_stats.txfm_block_count = 13;
for (int i = 0; i < accumulated_stats.coeff_num; ++i) {
accumulated_stats.abs_coeff_sum[i] = 5 * i;
}
av1_accumulate_tpl_txfm_stats(&sub_stats, &accumulated_stats);
EXPECT_DOUBLE_EQ(accumulated_stats.txfm_block_count, 30);
for (int i = 0; i < accumulated_stats.coeff_num; ++i) {
EXPECT_DOUBLE_EQ(accumulated_stats.abs_coeff_sum[i], 6 * i);
}
}
TEST(TPLModelTest, TxfmStatsRecordTest) {
TplTxfmStats stats1;
TplTxfmStats stats2;
av1_init_tpl_txfm_stats(&stats1);
av1_init_tpl_txfm_stats(&stats2);
tran_low_t coeff[256];
for (int i = 0; i < 256; ++i) {
coeff[i] = i;
}
av1_record_tpl_txfm_block(&stats1, coeff);
EXPECT_EQ(stats1.txfm_block_count, 1);
// we record the same transform block twice for testing purpose
av1_record_tpl_txfm_block(&stats2, coeff);
av1_record_tpl_txfm_block(&stats2, coeff);
EXPECT_EQ(stats2.txfm_block_count, 2);
EXPECT_EQ(stats1.coeff_num, 256);
EXPECT_EQ(stats2.coeff_num, 256);
for (int i = 0; i < 256; ++i) {
EXPECT_DOUBLE_EQ(stats2.abs_coeff_sum[i], 2 * stats1.abs_coeff_sum[i]);
}
}
/*
* Helper method to brute-force search for the closest q_index
* that achieves the specified bit budget.
*/
int find_gop_q_iterative(double bit_budget, double arf_qstep_ratio,
GF_GROUP gf_group, TplTxfmStats *stats_list,
int gf_frame_index, aom_bit_depth_t bit_depth) {
// Brute force iterative method to find the optimal q.
// Use the result to test against the binary search result.
// Initial estimate when q = 255
av1_q_mode_compute_gop_q_indices(gf_frame_index, 255, arf_qstep_ratio,
bit_depth, &gf_group, gf_group.q_val);
double curr_estimate =
av1_estimate_gop_bitrate(gf_group.q_val, gf_group.size, stats_list);
double best_estimate_budget_distance = fabs(curr_estimate - bit_budget);
int best_q = 255;
// Start at q = 254 because we already have an estimate for q = 255.
for (int q = 254; q >= 0; q--) {
av1_q_mode_compute_gop_q_indices(gf_frame_index, q, arf_qstep_ratio,
bit_depth, &gf_group, gf_group.q_val);
curr_estimate =
av1_estimate_gop_bitrate(gf_group.q_val, gf_group.size, stats_list);
double curr_estimate_budget_distance = fabs(curr_estimate - bit_budget);
if (curr_estimate_budget_distance <= best_estimate_budget_distance) {
best_estimate_budget_distance = curr_estimate_budget_distance;
best_q = q;
}
}
return best_q;
}
TEST(TplModelTest, QModeEstimateBaseQTest) {
GF_GROUP gf_group = {};
gf_group.size = 25;
TplTxfmStats stats_list[25];
const int gf_group_update_types[25] = { 0, 3, 6, 6, 6, 1, 5, 1, 5, 6, 1, 5, 1,
5, 6, 6, 1, 5, 1, 5, 6, 1, 5, 1, 4 };
const int gf_frame_index = 0;
const double arf_qstep_ratio = 2;
const aom_bit_depth_t bit_depth = AOM_BITS_8;
const double scale_factor = 1.0;
for (int i = 0; i < gf_group.size; i++) {
gf_group.update_type[i] = gf_group_update_types[i];
stats_list[i].txfm_block_count = 8;
for (int j = 0; j < 256; j++) {
stats_list[i].abs_coeff_sum[j] = 1000 + j;
}
}
// Test multiple bit budgets.
const std::vector<double> bit_budgets = { 0, 100, 1000, 10000,
100000, 300000, 500000, 750000,
800000, DBL_MAX };
for (double bit_budget : bit_budgets) {
// Binary search method to find the optimal q.
const int result = av1_q_mode_estimate_base_q(
&gf_group, stats_list, bit_budget, gf_frame_index, arf_qstep_ratio,
bit_depth, scale_factor, gf_group.q_val);
const int test_result =
find_gop_q_iterative(bit_budget, arf_qstep_ratio, gf_group, stats_list,
gf_frame_index, bit_depth);
if (bit_budget == 0) {
EXPECT_EQ(result, 255);
} else if (bit_budget == DBL_MAX) {
EXPECT_EQ(result, 0);
}
EXPECT_EQ(result, test_result);
}
}
} // namespace