| /* |
| * 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 |