|  | /* | 
|  | * Copyright (c) 2022, 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 "av1/ratectrl_qmode.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cassert> | 
|  | #include <climits> | 
|  | #include <numeric> | 
|  | #include <vector> | 
|  |  | 
|  | #include "av1/encoder/pass2_strategy.h" | 
|  | #include "av1/encoder/tpl_model.h" | 
|  |  | 
|  | namespace aom { | 
|  |  | 
|  | GopFrame gop_frame_invalid() { | 
|  | GopFrame gop_frame = {}; | 
|  | gop_frame.is_valid = false; | 
|  | gop_frame.coding_idx = -1; | 
|  | gop_frame.order_idx = -1; | 
|  | return gop_frame; | 
|  | } | 
|  |  | 
|  | GopFrame gop_frame_basic(int coding_idx, int order_idx, bool is_key_frame, | 
|  | bool is_arf_frame, bool is_golden_frame, | 
|  | bool is_show_frame, int depth) { | 
|  | GopFrame gop_frame; | 
|  | gop_frame.is_valid = true; | 
|  | gop_frame.coding_idx = coding_idx; | 
|  | gop_frame.order_idx = order_idx; | 
|  | gop_frame.is_key_frame = is_key_frame; | 
|  | gop_frame.is_arf_frame = is_arf_frame; | 
|  | gop_frame.is_golden_frame = is_golden_frame; | 
|  | gop_frame.is_show_frame = is_show_frame; | 
|  | gop_frame.encode_ref_mode = EncodeRefMode::kRegular; | 
|  | gop_frame.colocated_ref_idx = -1; | 
|  | gop_frame.update_ref_idx = -1; | 
|  | gop_frame.layer_depth = depth + kLayerDepthOffset; | 
|  | return gop_frame; | 
|  | } | 
|  |  | 
|  | // This function create gop frames with indices of display order from | 
|  | // order_start to order_end - 1. The function will recursively introduce | 
|  | // intermediate ARF untill maximum depth is met or the number of regular frames | 
|  | // in between two ARFs are less than 3. Than the regular frames will be added | 
|  | // into the gop_struct. | 
|  | void construct_gop_multi_layer(GopStruct *gop_struct, | 
|  | RefFrameManager *ref_frame_manager, | 
|  | int max_depth, int depth, int order_start, | 
|  | int order_end) { | 
|  | int coding_idx = static_cast<int>(gop_struct->gop_frame_list.size()); | 
|  | GopFrame gop_frame; | 
|  | int num_frames = order_end - order_start; | 
|  | // If there are less than kMinIntervalToAddArf frames, stop introducing ARF | 
|  | if (depth < max_depth && num_frames >= kMinIntervalToAddArf) { | 
|  | int order_mid = (order_start + order_end) / 2; | 
|  | // intermediate ARF | 
|  | gop_frame = gop_frame_basic(coding_idx, order_mid, 0, 1, 0, 0, depth); | 
|  | ref_frame_manager->UpdateFrame(&gop_frame, RefUpdateType::kForward, | 
|  | EncodeRefMode::kRegular); | 
|  | gop_struct->gop_frame_list.push_back(gop_frame); | 
|  | construct_gop_multi_layer(gop_struct, ref_frame_manager, max_depth, | 
|  | depth + 1, order_start, order_mid); | 
|  | // show existing intermediate ARF | 
|  | gop_frame = gop_frame_basic(coding_idx, order_mid, 0, 0, 0, 1, max_depth); | 
|  | ref_frame_manager->UpdateFrame(&gop_frame, RefUpdateType::kNone, | 
|  | EncodeRefMode::kShowExisting); | 
|  | gop_struct->gop_frame_list.push_back(gop_frame); | 
|  | construct_gop_multi_layer(gop_struct, ref_frame_manager, max_depth, | 
|  | depth + 1, order_mid + 1, order_end); | 
|  | } else { | 
|  | // regular frame | 
|  | for (int i = order_start; i < order_end; ++i) { | 
|  | coding_idx = static_cast<int>(gop_struct->gop_frame_list.size()); | 
|  | gop_frame = gop_frame_basic(coding_idx, i, 0, 0, 0, 1, max_depth); | 
|  | ref_frame_manager->UpdateFrame(&gop_frame, RefUpdateType::kLast, | 
|  | EncodeRefMode::kRegular); | 
|  | gop_struct->gop_frame_list.push_back(gop_frame); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | GopStruct construct_gop(RefFrameManager *ref_frame_manager, | 
|  | int show_frame_count, bool has_key_frame) { | 
|  | GopStruct gop_struct; | 
|  | gop_struct.show_frame_count = show_frame_count; | 
|  | int order_start = 0; | 
|  | int order_arf = show_frame_count - 1; | 
|  | int coding_idx; | 
|  | GopFrame gop_frame; | 
|  | if (has_key_frame) { | 
|  | const int key_frame_depth = -1; | 
|  | ref_frame_manager->Reset(); | 
|  | coding_idx = static_cast<int>(gop_struct.gop_frame_list.size()); | 
|  | gop_frame = | 
|  | gop_frame_basic(coding_idx, order_start, 1, 0, 1, 1, key_frame_depth); | 
|  | ref_frame_manager->UpdateFrame(&gop_frame, RefUpdateType::kBackward, | 
|  | EncodeRefMode::kRegular); | 
|  | gop_struct.gop_frame_list.push_back(gop_frame); | 
|  | order_start++; | 
|  | } | 
|  | // ARF | 
|  | const int arf_depth = 0; | 
|  | coding_idx = static_cast<int>(gop_struct.gop_frame_list.size()); | 
|  | gop_frame = gop_frame_basic(coding_idx, order_arf, 0, 1, 1, 0, arf_depth); | 
|  | ref_frame_manager->UpdateFrame(&gop_frame, RefUpdateType::kForward, | 
|  | EncodeRefMode::kRegular); | 
|  | gop_struct.gop_frame_list.push_back(gop_frame); | 
|  | construct_gop_multi_layer(&gop_struct, ref_frame_manager, | 
|  | ref_frame_manager->ForwardMaxSize(), arf_depth + 1, | 
|  | order_start, order_arf); | 
|  | // Overlay | 
|  | coding_idx = static_cast<int>(gop_struct.gop_frame_list.size()); | 
|  | gop_frame = gop_frame_basic(coding_idx, order_arf, 0, 0, 0, 1, | 
|  | ref_frame_manager->ForwardMaxSize()); | 
|  | ref_frame_manager->UpdateFrame(&gop_frame, RefUpdateType::kNone, | 
|  | EncodeRefMode::kOverlay); | 
|  | gop_struct.gop_frame_list.push_back(gop_frame); | 
|  | return gop_struct; | 
|  | } | 
|  |  | 
|  | void AV1RateControlQMode::SetRcParam(const RateControlParam &rc_param) { | 
|  | rc_param_ = rc_param; | 
|  | } | 
|  |  | 
|  | // initialize GF_GROUP_STATS | 
|  | static void init_gf_stats(GF_GROUP_STATS *gf_stats) { | 
|  | gf_stats->gf_group_err = 0.0; | 
|  | gf_stats->gf_group_raw_error = 0.0; | 
|  | gf_stats->gf_group_skip_pct = 0.0; | 
|  | gf_stats->gf_group_inactive_zone_rows = 0.0; | 
|  |  | 
|  | gf_stats->mv_ratio_accumulator = 0.0; | 
|  | gf_stats->decay_accumulator = 1.0; | 
|  | gf_stats->zero_motion_accumulator = 1.0; | 
|  | gf_stats->loop_decay_rate = 1.0; | 
|  | gf_stats->last_loop_decay_rate = 1.0; | 
|  | gf_stats->this_frame_mv_in_out = 0.0; | 
|  | gf_stats->mv_in_out_accumulator = 0.0; | 
|  | gf_stats->abs_mv_in_out_accumulator = 0.0; | 
|  |  | 
|  | gf_stats->avg_sr_coded_error = 0.0; | 
|  | gf_stats->avg_pcnt_second_ref = 0.0; | 
|  | gf_stats->avg_new_mv_count = 0.0; | 
|  | gf_stats->avg_wavelet_energy = 0.0; | 
|  | gf_stats->avg_raw_err_stdev = 0.0; | 
|  | gf_stats->non_zero_stdev_count = 0; | 
|  | } | 
|  |  | 
|  | static int find_regions_index(const std::vector<REGIONS> ®ions, | 
|  | int frame_idx) { | 
|  | for (int k = 0; k < static_cast<int>(regions.size()); k++) { | 
|  | if (regions[k].start <= frame_idx && regions[k].last >= frame_idx) { | 
|  | return k; | 
|  | } | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | #define MIN_SHRINK_LEN 6 | 
|  |  | 
|  | /*!\brief Determine the length of future GF groups. | 
|  | * | 
|  | * \ingroup gf_group_algo | 
|  | * This function decides the gf group length of future frames in batch | 
|  | * | 
|  | * \param[in]    rc_param         Rate control parameters | 
|  | * \param[in]    stats_list       List of first pass stats | 
|  | * \param[in]    regions_list     List of regions from av1_identify_regions | 
|  | * \param[in]    order_index      Index of current frame in stats_list | 
|  | * \param[in]    frames_since_key Number of frames since the last key frame | 
|  | * \param[in]    frames_to_key    Number of frames to the next key frame | 
|  | * | 
|  | * \return Returns a vector of decided GF group lengths. | 
|  | */ | 
|  | static std::vector<int> partition_gop_intervals( | 
|  | const RateControlParam &rc_param, | 
|  | const std::vector<FIRSTPASS_STATS> &stats_list, | 
|  | const std::vector<REGIONS> ®ions_list, int order_index, | 
|  | int frames_since_key, int frames_to_key) { | 
|  | const int min_shrink_int = | 
|  | std::max(MIN_SHRINK_LEN, rc_param.min_gop_show_frame_count); | 
|  | int i = (frames_since_key == 0) ? 1 : 0; | 
|  | // If cpi->gf_state.arf_gf_boost_lst is 0, we are starting with a KF or GF. | 
|  | int cur_start = 0, cur_last; | 
|  | // Each element is the last frame of the previous GOP. If there are n GOPs, | 
|  | // you need n + 1 cuts to find the durations. So cut_pos starts out with -1, | 
|  | // which is the last frame of the previous GOP. | 
|  | std::vector<int> cut_pos(1, -1); | 
|  | int cut_here = 0; | 
|  | GF_GROUP_STATS gf_stats; | 
|  | init_gf_stats(&gf_stats); | 
|  | int num_regions = static_cast<int>(regions_list.size()); | 
|  | int num_stats = static_cast<int>(stats_list.size()); | 
|  | int stats_in_loop_index = order_index; | 
|  | while (i + order_index < num_stats) { | 
|  | // reaches next key frame, break here | 
|  | if (i >= frames_to_key) { | 
|  | cut_here = 2; | 
|  | } else if (i - cur_start >= rc_param.max_gop_show_frame_count) { | 
|  | // reached maximum len, but nothing special yet (almost static) | 
|  | // let's look at the next interval | 
|  | cut_here = 1; | 
|  | } else if (stats_in_loop_index >= num_stats) { | 
|  | // reaches last frame, break | 
|  | cut_here = 2; | 
|  | } | 
|  |  | 
|  | if (!cut_here) { | 
|  | ++i; | 
|  | continue; | 
|  | } | 
|  | cur_last = i - 1;  // the current last frame in the gf group | 
|  | int ori_last = cur_last; | 
|  | int scenecut_idx = -1; | 
|  | // only try shrinking if interval smaller than active_max_gf_interval | 
|  | if (cur_last - cur_start <= rc_param.max_gop_show_frame_count && | 
|  | cur_last > cur_start) { | 
|  | // find the region indices of where the first and last frame belong. | 
|  | int k_start = | 
|  | find_regions_index(regions_list, cur_start + frames_since_key); | 
|  | int k_last = | 
|  | find_regions_index(regions_list, cur_last + frames_since_key); | 
|  | if (cur_start + frames_since_key == 0) k_start = 0; | 
|  |  | 
|  | // See if we have a scenecut in between | 
|  | for (int r = k_start + 1; r <= k_last; r++) { | 
|  | if (regions_list[r].type == SCENECUT_REGION && | 
|  | regions_list[r].last - frames_since_key - cur_start > | 
|  | rc_param.min_gop_show_frame_count) { | 
|  | scenecut_idx = r; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | // if the found scenecut is very close to the end, ignore it. | 
|  | if (regions_list[num_regions - 1].last - regions_list[scenecut_idx].last < | 
|  | 4) { | 
|  | scenecut_idx = -1; | 
|  | } | 
|  |  | 
|  | if (scenecut_idx != -1) { | 
|  | // If we have a scenecut, then stop at it. | 
|  | // TODO(bohanli): add logic here to stop before the scenecut and for | 
|  | // the next gop start from the scenecut with GF | 
|  | int is_minor_sc = | 
|  | (regions_list[scenecut_idx].avg_cor_coeff * | 
|  | (1 - | 
|  | stats_list[order_index + regions_list[scenecut_idx].start - | 
|  | frames_since_key] | 
|  | .noise_var / | 
|  | regions_list[scenecut_idx].avg_intra_err) > | 
|  | 0.6); | 
|  | cur_last = | 
|  | regions_list[scenecut_idx].last - frames_since_key - !is_minor_sc; | 
|  | } else { | 
|  | int is_last_analysed = | 
|  | (k_last == num_regions - 1) && | 
|  | (cur_last + frames_since_key == regions_list[k_last].last); | 
|  | int not_enough_regions = | 
|  | k_last - k_start <= | 
|  | 1 + (regions_list[k_start].type == SCENECUT_REGION); | 
|  | // if we are very close to the end, then do not shrink since it may | 
|  | // introduce intervals that are too short | 
|  | if (!(is_last_analysed && not_enough_regions)) { | 
|  | const double arf_length_factor = 0.1; | 
|  | double best_score = 0; | 
|  | int best_j = -1; | 
|  | const int first_frame = regions_list[0].start - frames_since_key; | 
|  | const int last_frame = | 
|  | regions_list[num_regions - 1].last - frames_since_key; | 
|  | // score of how much the arf helps the whole GOP | 
|  | double base_score = 0.0; | 
|  | // Accumulate base_score in | 
|  | for (int j = cur_start + 1; j < cur_start + min_shrink_int; j++) { | 
|  | if (order_index + j >= num_stats) break; | 
|  | base_score = | 
|  | (base_score + 1.0) * stats_list[order_index + j].cor_coeff; | 
|  | } | 
|  | int met_blending = 0;   // Whether we have met blending areas before | 
|  | int last_blending = 0;  // Whether the previous frame if blending | 
|  | for (int j = cur_start + min_shrink_int; j <= cur_last; j++) { | 
|  | if (order_index + j >= num_stats) break; | 
|  | base_score = | 
|  | (base_score + 1.0) * stats_list[order_index + j].cor_coeff; | 
|  | int this_reg = | 
|  | find_regions_index(regions_list, j + frames_since_key); | 
|  | if (this_reg < 0) continue; | 
|  | // A GOP should include at most 1 blending region. | 
|  | if (regions_list[this_reg].type == BLENDING_REGION) { | 
|  | last_blending = 1; | 
|  | if (met_blending) { | 
|  | break; | 
|  | } else { | 
|  | base_score = 0; | 
|  | continue; | 
|  | } | 
|  | } else { | 
|  | if (last_blending) met_blending = 1; | 
|  | last_blending = 0; | 
|  | } | 
|  |  | 
|  | // Add the factor of how good the neighborhood is for this | 
|  | // candidate arf. | 
|  | double this_score = arf_length_factor * base_score; | 
|  | double temp_accu_coeff = 1.0; | 
|  | // following frames | 
|  | int count_f = 0; | 
|  | for (int n = j + 1; n <= j + 3 && n <= last_frame; n++) { | 
|  | if (order_index + n >= num_stats) break; | 
|  | temp_accu_coeff *= stats_list[order_index + n].cor_coeff; | 
|  | this_score += | 
|  | temp_accu_coeff * | 
|  | (1 - stats_list[order_index + n].noise_var / | 
|  | AOMMAX(regions_list[this_reg].avg_intra_err, 0.001)); | 
|  | count_f++; | 
|  | } | 
|  | // preceding frames | 
|  | temp_accu_coeff = 1.0; | 
|  | for (int n = j; n > j - 3 * 2 + count_f && n > first_frame; n--) { | 
|  | if (order_index + n < num_stats) break; | 
|  | temp_accu_coeff *= stats_list[order_index + n].cor_coeff; | 
|  | this_score += | 
|  | temp_accu_coeff * | 
|  | (1 - stats_list[order_index + n].noise_var / | 
|  | AOMMAX(regions_list[this_reg].avg_intra_err, 0.001)); | 
|  | } | 
|  |  | 
|  | if (this_score > best_score) { | 
|  | best_score = this_score; | 
|  | best_j = j; | 
|  | } | 
|  | } | 
|  |  | 
|  | // For blending areas, move one more frame in case we missed the | 
|  | // first blending frame. | 
|  | int best_reg = | 
|  | find_regions_index(regions_list, best_j + frames_since_key); | 
|  | if (best_reg < num_regions - 1 && best_reg > 0) { | 
|  | if (regions_list[best_reg - 1].type == BLENDING_REGION && | 
|  | regions_list[best_reg + 1].type == BLENDING_REGION) { | 
|  | if (best_j + frames_since_key == regions_list[best_reg].start && | 
|  | best_j + frames_since_key < regions_list[best_reg].last) { | 
|  | best_j += 1; | 
|  | } else if (best_j + frames_since_key == | 
|  | regions_list[best_reg].last && | 
|  | best_j + frames_since_key > | 
|  | regions_list[best_reg].start) { | 
|  | best_j -= 1; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (cur_last - best_j < 2) best_j = cur_last; | 
|  | if (best_j > 0 && best_score > 0.1) cur_last = best_j; | 
|  | // if cannot find anything, just cut at the original place. | 
|  | } | 
|  | } | 
|  | } | 
|  | cut_pos.push_back(cur_last); | 
|  |  | 
|  | // reset pointers to the shrunken location | 
|  | stats_in_loop_index = order_index + cur_last; | 
|  | cur_start = cur_last; | 
|  | int cur_region_idx = | 
|  | find_regions_index(regions_list, cur_start + 1 + frames_since_key); | 
|  | if (cur_region_idx >= 0) | 
|  | if (regions_list[cur_region_idx].type == SCENECUT_REGION) cur_start++; | 
|  |  | 
|  | if (cut_here > 1 && cur_last == ori_last) break; | 
|  | // reset accumulators | 
|  | init_gf_stats(&gf_stats); | 
|  | i = cur_last + 1; | 
|  | } | 
|  | std::vector<int> gf_intervals; | 
|  | // save intervals | 
|  | for (size_t n = 1; n < cut_pos.size(); n++) { | 
|  | gf_intervals.push_back(cut_pos[n] - cut_pos[n - 1]); | 
|  | } | 
|  |  | 
|  | return gf_intervals; | 
|  | } | 
|  |  | 
|  | GopStructList AV1RateControlQMode::DetermineGopInfo( | 
|  | const FirstpassInfo &firstpass_info) { | 
|  | std::vector<REGIONS> regions_list(MAX_FIRSTPASS_ANALYSIS_FRAMES); | 
|  | int total_regions = 0; | 
|  | // TODO(jianj): firstpass_info.size() should eventually be replaced | 
|  | // by the number of frames to the next KF. | 
|  | av1_identify_regions(firstpass_info.data(), | 
|  | std::min(static_cast<int>(firstpass_info.size()), | 
|  | MAX_FIRSTPASS_ANALYSIS_FRAMES), | 
|  | 0, regions_list.data(), &total_regions); | 
|  | regions_list.resize(total_regions); | 
|  | int order_index = 0, frames_since_key = 0, frames_to_key = 0; | 
|  | std::vector<int> gf_intervals = | 
|  | partition_gop_intervals(rc_param_, firstpass_info, regions_list, | 
|  | order_index, frames_since_key, frames_to_key); | 
|  | // A temporary simple implementation | 
|  | const int max_gop_show_frame_count = 16; | 
|  | int remaining_show_frame_count = static_cast<int>(firstpass_info.size()); | 
|  | GopStructList gop_list; | 
|  |  | 
|  | RefFrameManager ref_frame_manager(rc_param_.max_ref_frames); | 
|  |  | 
|  | while (remaining_show_frame_count > 0) { | 
|  | int show_frame_count = | 
|  | std::min(remaining_show_frame_count, max_gop_show_frame_count); | 
|  | // TODO(angiebird): determine gop show frame count based on first pass stats | 
|  | // here. | 
|  | bool has_key_frame = gop_list.size() == 0; | 
|  | GopStruct gop = | 
|  | construct_gop(&ref_frame_manager, show_frame_count, has_key_frame); | 
|  | gop_list.push_back(gop); | 
|  | remaining_show_frame_count -= show_frame_count; | 
|  | } | 
|  | return gop_list; | 
|  | } | 
|  |  | 
|  | TplFrameDepStats create_tpl_frame_dep_stats_empty(int frame_height, | 
|  | int frame_width, | 
|  | int min_block_size) { | 
|  | const int unit_rows = | 
|  | frame_height / min_block_size + !!(frame_height % min_block_size); | 
|  | const int unit_cols = | 
|  | frame_width / min_block_size + !!(frame_width % min_block_size); | 
|  | TplFrameDepStats frame_dep_stats; | 
|  | frame_dep_stats.unit_size = min_block_size; | 
|  | frame_dep_stats.unit_stats = std::vector<std::vector<double>>( | 
|  | unit_rows, std::vector<double>(unit_cols, 0)); | 
|  | return frame_dep_stats; | 
|  | } | 
|  |  | 
|  | TplFrameDepStats create_tpl_frame_dep_stats_wo_propagation( | 
|  | const TplFrameStats &frame_stats) { | 
|  | const int min_block_size = frame_stats.min_block_size; | 
|  | TplFrameDepStats frame_dep_stats = create_tpl_frame_dep_stats_empty( | 
|  | frame_stats.frame_height, frame_stats.frame_width, min_block_size); | 
|  | for (const TplBlockStats &block_stats : frame_stats.block_stats_list) { | 
|  | const int block_unit_rows = block_stats.height / min_block_size; | 
|  | const int block_unit_cols = block_stats.width / min_block_size; | 
|  | const int unit_count = block_unit_rows * block_unit_cols; | 
|  | const int block_unit_row = block_stats.row / min_block_size; | 
|  | const int block_unit_col = block_stats.col / min_block_size; | 
|  | const double cost_diff = | 
|  | (block_stats.inter_cost - block_stats.intra_cost) * 1.0 / unit_count; | 
|  | for (int r = 0; r < block_unit_rows; r++) { | 
|  | for (int c = 0; c < block_unit_cols; c++) { | 
|  | frame_dep_stats.unit_stats[block_unit_row + r][block_unit_col + c] = | 
|  | cost_diff; | 
|  | } | 
|  | } | 
|  | } | 
|  | return frame_dep_stats; | 
|  | } | 
|  |  | 
|  | int get_ref_coding_idx_list(const TplBlockStats &block_stats, | 
|  | const RefFrameTable &ref_frame_table, | 
|  | int *ref_coding_idx_list) { | 
|  | int ref_frame_count = 0; | 
|  | for (int i = 0; i < kBlockRefCount; ++i) { | 
|  | ref_coding_idx_list[i] = -1; | 
|  | int ref_frame_index = block_stats.ref_frame_index[i]; | 
|  | if (ref_frame_index != -1) { | 
|  | ref_coding_idx_list[i] = ref_frame_table[ref_frame_index].coding_idx; | 
|  | ref_frame_count++; | 
|  | } | 
|  | } | 
|  | return ref_frame_count; | 
|  | } | 
|  |  | 
|  | int get_block_overlap_area(int r0, int c0, int r1, int c1, int size) { | 
|  | const int r_low = std::max(r0, r1); | 
|  | const int r_high = std::min(r0 + size, r1 + size); | 
|  | const int c_low = std::max(c0, c1); | 
|  | const int c_high = std::min(c0 + size, c1 + size); | 
|  | if (r_high >= r_low && c_high >= c_low) { | 
|  | return (r_high - r_low) * (c_high - c_low); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | double tpl_frame_stats_accumulate(const TplFrameStats &frame_stats) { | 
|  | double ref_sum_cost_diff = 0; | 
|  | for (auto &block_stats : frame_stats.block_stats_list) { | 
|  | ref_sum_cost_diff += block_stats.inter_cost - block_stats.intra_cost; | 
|  | } | 
|  | return ref_sum_cost_diff; | 
|  | } | 
|  |  | 
|  | double tpl_frame_dep_stats_accumulate(const TplFrameDepStats &frame_dep_stats) { | 
|  | double sum = 0; | 
|  | for (const auto &row : frame_dep_stats.unit_stats) { | 
|  | sum = std::accumulate(row.begin(), row.end(), sum); | 
|  | } | 
|  | return sum; | 
|  | } | 
|  |  | 
|  | // This is a generalization of GET_MV_RAWPEL that allows for an arbitrary number | 
|  | // of fractional bits. | 
|  | // TODO(angiebird): Add unit test to this function | 
|  | int get_fullpel_value(int subpel_value, int subpel_bits) { | 
|  | const int subpel_scale = (1 << subpel_bits); | 
|  | const int sign = subpel_value >= 0 ? 1 : -1; | 
|  | int fullpel_value = (abs(subpel_value) + subpel_scale / 2) >> subpel_bits; | 
|  | fullpel_value *= sign; | 
|  | return fullpel_value; | 
|  | } | 
|  |  | 
|  | void tpl_frame_dep_stats_propagate(const TplFrameStats &frame_stats, | 
|  | const RefFrameTable &ref_frame_table, | 
|  | TplGopDepStats *tpl_gop_dep_stats) { | 
|  | const int min_block_size = frame_stats.min_block_size; | 
|  | const int frame_unit_rows = | 
|  | frame_stats.frame_height / frame_stats.min_block_size; | 
|  | const int frame_unit_cols = | 
|  | frame_stats.frame_width / frame_stats.min_block_size; | 
|  | for (const TplBlockStats &block_stats : frame_stats.block_stats_list) { | 
|  | int ref_coding_idx_list[kBlockRefCount] = { -1, -1 }; | 
|  | int ref_frame_count = get_ref_coding_idx_list(block_stats, ref_frame_table, | 
|  | ref_coding_idx_list); | 
|  | if (ref_frame_count > 0) { | 
|  | double propagation_ratio = 1.0 / ref_frame_count; | 
|  | for (int i = 0; i < kBlockRefCount; ++i) { | 
|  | if (ref_coding_idx_list[i] != -1) { | 
|  | auto &ref_frame_dep_stats = | 
|  | tpl_gop_dep_stats->frame_dep_stats_list[ref_coding_idx_list[i]]; | 
|  | const auto &mv = block_stats.mv[i]; | 
|  | const int mv_row = get_fullpel_value(mv.row, mv.subpel_bits); | 
|  | const int mv_col = get_fullpel_value(mv.col, mv.subpel_bits); | 
|  | const int block_unit_rows = block_stats.height / min_block_size; | 
|  | const int block_unit_cols = block_stats.width / min_block_size; | 
|  | const int unit_count = block_unit_rows * block_unit_cols; | 
|  | const double cost_diff = | 
|  | (block_stats.inter_cost - block_stats.intra_cost) * 1.0 / | 
|  | unit_count; | 
|  | for (int r = 0; r < block_unit_rows; r++) { | 
|  | for (int c = 0; c < block_unit_cols; c++) { | 
|  | const int ref_block_row = | 
|  | block_stats.row + r * min_block_size + mv_row; | 
|  | const int ref_block_col = | 
|  | block_stats.col + c * min_block_size + mv_col; | 
|  | const int ref_unit_row_low = ref_block_row / min_block_size; | 
|  | const int ref_unit_col_low = ref_block_col / min_block_size; | 
|  | for (int j = 0; j < 2; ++j) { | 
|  | for (int k = 0; k < 2; ++k) { | 
|  | const int unit_row = ref_unit_row_low + j; | 
|  | const int unit_col = ref_unit_col_low + k; | 
|  | if (unit_row >= 0 && unit_row < frame_unit_rows && | 
|  | unit_col >= 0 && unit_col < frame_unit_cols) { | 
|  | const int overlap_area = get_block_overlap_area( | 
|  | unit_row * min_block_size, unit_col * min_block_size, | 
|  | ref_block_row, ref_block_col, min_block_size); | 
|  | const double overlap_ratio = | 
|  | overlap_area * 1.0 / (min_block_size * min_block_size); | 
|  | ref_frame_dep_stats.unit_stats[unit_row][unit_col] += | 
|  | cost_diff * overlap_ratio * propagation_ratio; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(angiebird): Add unit test for this function | 
|  | std::vector<RefFrameTable> get_ref_frame_table_list( | 
|  | const GopStruct &gop_struct, RefFrameTable ref_frame_table) { | 
|  | const int frame_count = static_cast<int>(gop_struct.gop_frame_list.size()); | 
|  | std::vector<RefFrameTable> ref_frame_table_list; | 
|  | ref_frame_table_list.push_back(ref_frame_table); | 
|  | for (int coding_idx = 0; coding_idx < frame_count; coding_idx++) { | 
|  | const auto &gop_frame = gop_struct.gop_frame_list[coding_idx]; | 
|  | if (gop_frame.update_ref_idx != -1) { | 
|  | ref_frame_table[gop_frame.update_ref_idx] = gop_frame; | 
|  | } | 
|  | ref_frame_table_list.push_back(ref_frame_table); | 
|  | } | 
|  | return ref_frame_table_list; | 
|  | } | 
|  |  | 
|  | TplGopDepStats compute_tpl_gop_dep_stats( | 
|  | const TplGopStats &tpl_gop_stats, | 
|  | const std::vector<RefFrameTable> &ref_frame_table_list) { | 
|  | const int frame_count = static_cast<int>(ref_frame_table_list.size()); | 
|  |  | 
|  | // Create the struct to store TPL dependency stats | 
|  | TplGopDepStats tpl_gop_dep_stats; | 
|  | for (int coding_idx = 0; coding_idx < frame_count; coding_idx++) { | 
|  | tpl_gop_dep_stats.frame_dep_stats_list.push_back( | 
|  | create_tpl_frame_dep_stats_wo_propagation( | 
|  | tpl_gop_stats.frame_stats_list[coding_idx])); | 
|  | } | 
|  |  | 
|  | // Back propagation | 
|  | for (int coding_idx = frame_count - 1; coding_idx >= 0; coding_idx--) { | 
|  | auto &ref_frame_table = ref_frame_table_list[coding_idx]; | 
|  | // TODO(angiebird): Handle/test the case where reference frame | 
|  | // is in the previous GOP | 
|  | tpl_frame_dep_stats_propagate(tpl_gop_stats.frame_stats_list[coding_idx], | 
|  | ref_frame_table, &tpl_gop_dep_stats); | 
|  | } | 
|  | return tpl_gop_dep_stats; | 
|  | } | 
|  |  | 
|  | GopEncodeInfo AV1RateControlQMode::GetGopEncodeInfo( | 
|  | const GopStruct &gop_struct, const TplGopStats &tpl_gop_stats, | 
|  | const RefFrameTable &ref_frame_table_snapshot_init) { | 
|  | const std::vector<RefFrameTable> ref_frame_table_list = | 
|  | get_ref_frame_table_list(gop_struct, ref_frame_table_snapshot_init); | 
|  |  | 
|  | GopEncodeInfo gop_encode_info; | 
|  | gop_encode_info.final_snapshot = ref_frame_table_list.back(); | 
|  | TplGopDepStats gop_dep_stats = | 
|  | compute_tpl_gop_dep_stats(tpl_gop_stats, ref_frame_table_list); | 
|  | const int frame_count = | 
|  | static_cast<int>(tpl_gop_stats.frame_stats_list.size()); | 
|  | for (int i = 0; i < frame_count; i++) { | 
|  | const TplFrameStats &frame_stats = tpl_gop_stats.frame_stats_list[i]; | 
|  | const TplFrameDepStats &frame_dep_stats = | 
|  | gop_dep_stats.frame_dep_stats_list[i]; | 
|  | const double cost_without_propagation = | 
|  | tpl_frame_stats_accumulate(frame_stats); | 
|  | const double cost_with_propagation = | 
|  | tpl_frame_dep_stats_accumulate(frame_dep_stats); | 
|  | // TODO(angiebird): This part is still a draft. Check whether this makes | 
|  | // sense mathematically. | 
|  | const double frame_importance = | 
|  | cost_with_propagation / cost_without_propagation; | 
|  | // Imitate the behavior of av1_tpl_get_qstep_ratio() | 
|  | const double qstep_ratio = sqrt(1 / frame_importance); | 
|  | FrameEncodeParameters param; | 
|  | param.q_index = av1_get_q_index_from_qstep_ratio(rc_param_.base_q_index, | 
|  | qstep_ratio, AOM_BITS_8); | 
|  | // TODO(angiebird): Determine rdmult based on q_index | 
|  | param.rdmult = 1; | 
|  | gop_encode_info.param_list.push_back(param); | 
|  | } | 
|  | return gop_encode_info; | 
|  | } | 
|  |  | 
|  | }  // namespace aom |