| /* | 
 |  * Copyright (c) 2016, 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 "config/aom_config.h" | 
 | #include "config/aom_dsp_rtcd.h" | 
 | #include "config/aom_scale_rtcd.h" | 
 | #include "config/av1_rtcd.h" | 
 |  | 
 | #include "aom_ports/system_state.h" | 
 |  | 
 | #include "av1/encoder/encodemv.h" | 
 | #if !CONFIG_REALTIME_ONLY | 
 | #include "av1/encoder/misc_model_weights.h" | 
 | #endif  // !CONFIG_REALTIME_ONLY | 
 | #include "av1/encoder/mv_prec.h" | 
 |  | 
 | #define MV_PREC_DET_SOBEL_EDGE_THRESH 256 | 
 | #define MV_PREC_DET_SSE_STATIC_THRESH 1024 | 
 | #define MV_PREC_DET_BLK_SIZE_BITS 4 | 
 | #define MV_PREC_DET_BLK_SIZE (1 << MV_PREC_DET_BLK_SIZE_BITS) | 
 | #define MV_PREC_DET_THRESH 0.20 | 
 | #define MV_PREC_DET_THRESH2 0.10 | 
 | #define MV_PREC_DET_QTHRESH 64  // Q thresh for 1/8-pel in edge based method | 
 |  | 
 | #define MV_HIPREC_QTHRESH 128   // Q thresh for 1/8-pel in q-based method | 
 | #define MV_HIPREC_QTHRESH2 192  // Q thresh for 1/4-pel in q-based method | 
 |  | 
 | // Q thresh for 1/8-pel in edge based method | 
 | #if !CONFIG_REALTIME_ONLY | 
 | static AOM_INLINE int_mv get_ref_mv_for_mv_stats( | 
 |     const MB_MODE_INFO *mbmi, const MB_MODE_INFO_EXT *mbmi_ext, int ref_idx) { | 
 |   int ref_mv_idx = mbmi->ref_mv_idx; | 
 |   if (mbmi->mode == NEAR_NEWMV || mbmi->mode == NEW_NEARMV) { | 
 |     assert(has_second_ref(mbmi)); | 
 |     ref_mv_idx += 1; | 
 |   } | 
 |  | 
 |   const MV_REFERENCE_FRAME *ref_frames = mbmi->ref_frame; | 
 |   const int8_t ref_frame_type = av1_ref_frame_type(ref_frames); | 
 |   const CANDIDATE_MV *curr_ref_mv_stack = | 
 |       mbmi_ext->ref_mv_info.ref_mv_stack[ref_frame_type]; | 
 |  | 
 |   if (ref_frames[1] > INTRA_FRAME) { | 
 |     assert(ref_idx == 0 || ref_idx == 1); | 
 |     return ref_idx ? curr_ref_mv_stack[ref_mv_idx].comp_mv | 
 |                    : curr_ref_mv_stack[ref_mv_idx].this_mv; | 
 |   } | 
 |  | 
 |   assert(ref_idx == 0); | 
 |   return ref_mv_idx < mbmi_ext->ref_mv_info.ref_mv_count[ref_frame_type] | 
 |              ? curr_ref_mv_stack[ref_mv_idx].this_mv | 
 |              : mbmi_ext->global_mvs[ref_frame_type]; | 
 | } | 
 |  | 
 | static AOM_INLINE int get_symbol_cost(const aom_cdf_prob *cdf, int symbol) { | 
 |   const aom_cdf_prob cur_cdf = AOM_ICDF(cdf[symbol]); | 
 |   const aom_cdf_prob prev_cdf = symbol ? AOM_ICDF(cdf[symbol - 1]) : 0; | 
 |   const aom_cdf_prob p15 = AOMMAX(cur_cdf - prev_cdf, EC_MIN_PROB); | 
 |  | 
 |   return av1_cost_symbol(p15); | 
 | } | 
 |  | 
 | static AOM_INLINE int keep_one_comp_stat(MV_STATS *mv_stats, int comp_val, | 
 |                                          int comp_idx, const AV1_COMP *cpi, | 
 |                                          int *rates) { | 
 |   assert(comp_val != 0 && "mv component should not have zero value!"); | 
 |   const int sign = comp_val < 0; | 
 |   const int mag = sign ? -comp_val : comp_val; | 
 |   const int mag_minus_1 = mag - 1; | 
 |   int offset; | 
 |   const int mv_class = av1_get_mv_class(mag_minus_1, &offset); | 
 |   const int int_part = offset >> 3;         // int mv data | 
 |   const int frac_part = (offset >> 1) & 3;  // fractional mv data | 
 |   const int high_part = offset & 1;         // high precision mv data | 
 |   const int use_hp = cpi->common.fr_mv_precision > MV_SUBPEL_QTR_PRECISION; | 
 |   int r_idx = 0; | 
 |  | 
 |   const MACROBLOCK *const x = &cpi->td.mb; | 
 |   const MACROBLOCKD *const xd = &x->e_mbd; | 
 |   FRAME_CONTEXT *ec_ctx = xd->tile_ctx; | 
 |   nmv_context *nmvc = &ec_ctx->nmvc; | 
 |   nmv_component *mvcomp_ctx = nmvc->comps; | 
 |   nmv_component *cur_mvcomp_ctx = &mvcomp_ctx[comp_idx]; | 
 |   aom_cdf_prob *sign_cdf = cur_mvcomp_ctx->sign_cdf; | 
 |   aom_cdf_prob *class_cdf = cur_mvcomp_ctx->classes_cdf; | 
 |   aom_cdf_prob *class0_cdf = cur_mvcomp_ctx->class0_cdf; | 
 |   aom_cdf_prob(*bits_cdf)[3] = cur_mvcomp_ctx->bits_cdf; | 
 |   aom_cdf_prob *high_part_cdf = | 
 |       mv_class ? (cur_mvcomp_ctx->hp_cdf) : (cur_mvcomp_ctx->class0_hp_cdf); | 
 |  | 
 |   const int sign_rate = get_symbol_cost(sign_cdf, sign); | 
 |   rates[r_idx++] = sign_rate; | 
 |   update_cdf(sign_cdf, sign, 2); | 
 |  | 
 |   const int class_rate = get_symbol_cost(class_cdf, mv_class); | 
 |   rates[r_idx++] = class_rate; | 
 |   update_cdf(class_cdf, mv_class, MV_CLASSES); | 
 |  | 
 |   int int_bit_rate = 0; | 
 |   if (mv_class == MV_CLASS_0) { | 
 |     int_bit_rate = get_symbol_cost(class0_cdf, int_part); | 
 |     update_cdf(class0_cdf, int_part, CLASS0_SIZE); | 
 |   } else { | 
 |     const int n = mv_class + CLASS0_BITS - 1;  // number of bits | 
 |     for (int i = 0; i < n; ++i) { | 
 |       int_bit_rate += get_symbol_cost(bits_cdf[i], (int_part >> i) & 1); | 
 |       update_cdf(bits_cdf[i], (int_part >> i) & 1, 2); | 
 |     } | 
 |   } | 
 |   rates[r_idx++] = int_bit_rate; | 
 | #if CONFIG_FLEX_MVRES | 
 |   aom_cdf_prob *frac_part_cdf = | 
 |       mv_class ? (cur_mvcomp_ctx->fp_cdf[0]) | 
 |                : (cur_mvcomp_ctx->class0_fp_cdf[int_part][0]); | 
 |   int frac_part_rate_hpel = 0, frac_part_rate_qpel = 0; | 
 |   frac_part_rate_hpel = get_symbol_cost(frac_part_cdf, frac_part); | 
 |   rates[r_idx++] = frac_part_rate_hpel; | 
 |   update_cdf(frac_part_cdf, frac_part >> 1, 2); | 
 |  | 
 |   if (cpi->common.fr_mv_precision > MV_SUBPEL_HALF_PRECISION) { | 
 |     frac_part_cdf = | 
 |         mv_class | 
 |             ? (cur_mvcomp_ctx->fp_cdf[1 + (frac_part >> 1)]) | 
 |             : (cur_mvcomp_ctx->class0_fp_cdf[int_part][1 + (frac_part >> 1)]); | 
 |     frac_part_rate_qpel = get_symbol_cost(frac_part_cdf, frac_part); | 
 |     rates[r_idx++] = frac_part_rate_qpel; | 
 |     update_cdf(frac_part_cdf, frac_part & 1, 2); | 
 |   } | 
 |  | 
 | #else | 
 |   aom_cdf_prob *frac_part_cdf = mv_class | 
 |                                     ? (cur_mvcomp_ctx->fp_cdf) | 
 |                                     : (cur_mvcomp_ctx->class0_fp_cdf[int_part]); | 
 |   const int frac_part_rate = get_symbol_cost(frac_part_cdf, frac_part); | 
 |   rates[r_idx++] = frac_part_rate; | 
 |   update_cdf(frac_part_cdf, frac_part, MV_FP_SIZE); | 
 | #endif | 
 |  | 
 |   const int high_part_rate = | 
 |       use_hp ? get_symbol_cost(high_part_cdf, high_part) : 0; | 
 |   if (use_hp) { | 
 |     update_cdf(high_part_cdf, high_part, 2); | 
 |   } | 
 |   rates[r_idx++] = high_part_rate; | 
 |  | 
 |   mv_stats->last_bit_zero += !high_part; | 
 |   mv_stats->last_bit_nonzero += high_part; | 
 |  | 
 | #if CONFIG_FLEX_MVRES | 
 |   const int total_rate = | 
 |       (sign_rate + class_rate + int_bit_rate + frac_part_rate_hpel + | 
 |        frac_part_rate_qpel + high_part_rate); | 
 | #else | 
 |   const int total_rate = | 
 |       (sign_rate + class_rate + int_bit_rate + frac_part_rate + high_part_rate); | 
 | #endif | 
 |   return total_rate; | 
 | } | 
 |  | 
 | static AOM_INLINE void keep_one_mv_stat(MV_STATS *mv_stats, const MV *ref_mv, | 
 |                                         const MV *cur_mv, const AV1_COMP *cpi) { | 
 |   const MACROBLOCK *const x = &cpi->td.mb; | 
 |   const MACROBLOCKD *const xd = &x->e_mbd; | 
 |   FRAME_CONTEXT *ec_ctx = xd->tile_ctx; | 
 |   nmv_context *nmvc = &ec_ctx->nmvc; | 
 |   aom_cdf_prob *joint_cdf = nmvc->joints_cdf; | 
 |   const int use_hp = cpi->common.fr_mv_precision > MV_SUBPEL_QTR_PRECISION; | 
 |  | 
 |   const MV diff = { cur_mv->row - ref_mv->row, cur_mv->col - ref_mv->col }; | 
 |   const int mv_joint = av1_get_mv_joint(&diff); | 
 |   // TODO(chiyotsai@google.com): Estimate hp_diff when we are using lp | 
 |   const MV hp_diff = diff; | 
 |   const int hp_mv_joint = av1_get_mv_joint(&hp_diff); | 
 |   const MV truncated_diff = { (diff.row / 2) * 2, (diff.col / 2) * 2 }; | 
 |   const MV lp_diff = use_hp ? truncated_diff : diff; | 
 |   const int lp_mv_joint = av1_get_mv_joint(&lp_diff); | 
 |  | 
 |   aom_clear_system_state(); | 
 |   const int mv_joint_rate = get_symbol_cost(joint_cdf, mv_joint); | 
 |   const int hp_mv_joint_rate = get_symbol_cost(joint_cdf, hp_mv_joint); | 
 |   const int lp_mv_joint_rate = get_symbol_cost(joint_cdf, lp_mv_joint); | 
 |  | 
 |   update_cdf(joint_cdf, mv_joint, MV_JOINTS); | 
 |  | 
 |   mv_stats->total_mv_rate += mv_joint_rate; | 
 |   mv_stats->hp_total_mv_rate += hp_mv_joint_rate; | 
 |   mv_stats->lp_total_mv_rate += lp_mv_joint_rate; | 
 |   mv_stats->mv_joint_count[mv_joint]++; | 
 |  | 
 |   for (int comp_idx = 0; comp_idx < 2; comp_idx++) { | 
 |     const int comp_val = comp_idx ? diff.col : diff.row; | 
 |     const int hp_comp_val = comp_idx ? hp_diff.col : hp_diff.row; | 
 |     const int lp_comp_val = comp_idx ? lp_diff.col : lp_diff.row; | 
 |     int rates[6]; | 
 |     av1_zero_array(rates, 6); | 
 |  | 
 |     const int comp_rate = | 
 |         comp_val ? keep_one_comp_stat(mv_stats, comp_val, comp_idx, cpi, rates) | 
 |                  : 0; | 
 |     // TODO(chiyotsai@google.com): Properly get hp rate when use_hp is false | 
 | #if CONFIG_FLEX_MVRES | 
 |     const int hp_rate = hp_comp_val ? rates[0] + rates[1] + rates[2] + | 
 |                                           rates[3] + rates[4] + rates[5] | 
 |                                     : 0; | 
 |     const int lp_rate = | 
 |         lp_comp_val ? rates[0] + rates[1] + rates[2] + rates[3] + rates[4] : 0; | 
 | #else | 
 |     const int hp_rate = | 
 |         hp_comp_val ? rates[0] + rates[1] + rates[2] + rates[3] + rates[4] : 0; | 
 |     const int lp_rate = | 
 |         lp_comp_val ? rates[0] + rates[1] + rates[2] + rates[3] : 0; | 
 | #endif | 
 |     mv_stats->total_mv_rate += comp_rate; | 
 |     mv_stats->hp_total_mv_rate += hp_rate; | 
 |     mv_stats->lp_total_mv_rate += lp_rate; | 
 |   } | 
 | } | 
 |  | 
 | static AOM_INLINE void collect_mv_stats_b(MV_STATS *mv_stats, | 
 |                                           const AV1_COMP *cpi, int mi_row, | 
 |                                           int mi_col) { | 
 |   const AV1_COMMON *cm = &cpi->common; | 
 |  | 
 |   if (mi_row >= cm->mi_rows || mi_col >= cm->mi_cols) { | 
 |     return; | 
 |   } | 
 |  | 
 |   const MB_MODE_INFO *mbmi = cm->mi_grid_base[mi_row * cm->mi_stride + mi_col]; | 
 |   const MB_MODE_INFO_EXT *mbmi_ext = | 
 |       cpi->mbmi_ext_base + (mi_row * cm->mi_cols + mi_col); | 
 |  | 
 |   if (!is_inter_block(mbmi)) { | 
 |     mv_stats->intra_count++; | 
 |     return; | 
 |   } | 
 |   mv_stats->inter_count++; | 
 |  | 
 |   const PREDICTION_MODE mode = mbmi->mode; | 
 |   const int is_compound = has_second_ref(mbmi); | 
 |  | 
 |   if (mode == NEWMV || mode == NEW_NEWMV) { | 
 |     // All mvs are new | 
 |     for (int ref_idx = 0; ref_idx < 1 + is_compound; ++ref_idx) { | 
 |       const MV ref_mv = get_ref_mv_for_mv_stats(mbmi, mbmi_ext, ref_idx).as_mv; | 
 |       const MV cur_mv = mbmi->mv[ref_idx].as_mv; | 
 |       keep_one_mv_stat(mv_stats, &ref_mv, &cur_mv, cpi); | 
 |     } | 
 | #if CONFIG_NEW_INTER_MODES | 
 |   } else if (mode == NEAR_NEWMV || mode == NEW_NEARMV) { | 
 | #else | 
 |   } else if (mode == NEAREST_NEWMV || mode == NEAR_NEWMV || | 
 |              mode == NEW_NEARESTMV || mode == NEW_NEARMV) { | 
 | #endif | 
 |     // has exactly one new_mv | 
 |     mv_stats->default_mvs += 1; | 
 | #if CONFIG_NEW_INTER_MODES | 
 |     const int ref_idx = (mode == NEAR_NEWMV); | 
 | #else | 
 |     const int ref_idx = (mode == NEAREST_NEWMV || mode == NEAR_NEWMV); | 
 | #endif | 
 |     const MV ref_mv = get_ref_mv_for_mv_stats(mbmi, mbmi_ext, ref_idx).as_mv; | 
 |     const MV cur_mv = mbmi->mv[ref_idx].as_mv; | 
 |  | 
 |     keep_one_mv_stat(mv_stats, &ref_mv, &cur_mv, cpi); | 
 |   } else { | 
 |     // No new_mv | 
 |     mv_stats->default_mvs += 1 + is_compound; | 
 |   } | 
 |  | 
 |   // Add texture information | 
 |   const BLOCK_SIZE bsize = mbmi->sb_type; | 
 |   const int num_rows = block_size_high[bsize]; | 
 |   const int num_cols = block_size_wide[bsize]; | 
 |   const int y_stride = cpi->source->y_stride; | 
 |   const int px_row = 4 * mi_row, px_col = 4 * mi_col; | 
 |   const int buf_is_hbd = cpi->source->flags & YV12_FLAG_HIGHBITDEPTH; | 
 |   const int bd = cm->seq_params.bit_depth; | 
 |   if (buf_is_hbd) { | 
 |     uint16_t *source_buf = | 
 |         CONVERT_TO_SHORTPTR(cpi->source->y_buffer) + px_row * y_stride + px_col; | 
 |     for (int row = 0; row < num_rows - 1; row++) { | 
 |       for (int col = 0; col < num_cols - 1; col++) { | 
 |         const int offset = row * y_stride + col; | 
 |         const int horz_diff = | 
 |             abs(source_buf[offset + 1] - source_buf[offset]) >> (bd - 8); | 
 |         const int vert_diff = | 
 |             abs(source_buf[offset + y_stride] - source_buf[offset]) >> (bd - 8); | 
 |         mv_stats->horz_text += horz_diff; | 
 |         mv_stats->vert_text += vert_diff; | 
 |         mv_stats->diag_text += horz_diff * vert_diff; | 
 |       } | 
 |     } | 
 |   } else { | 
 |     uint8_t *source_buf = cpi->source->y_buffer + px_row * y_stride + px_col; | 
 |     for (int row = 0; row < num_rows - 1; row++) { | 
 |       for (int col = 0; col < num_cols - 1; col++) { | 
 |         const int offset = row * y_stride + col; | 
 |         const int horz_diff = abs(source_buf[offset + 1] - source_buf[offset]); | 
 |         const int vert_diff = | 
 |             abs(source_buf[offset + y_stride] - source_buf[offset]); | 
 |         mv_stats->horz_text += horz_diff; | 
 |         mv_stats->vert_text += vert_diff; | 
 |         mv_stats->diag_text += horz_diff * vert_diff; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // Split block | 
 | static AOM_INLINE void collect_mv_stats_sb(MV_STATS *mv_stats, | 
 |                                            const AV1_COMP *cpi, int mi_row, | 
 |                                            int mi_col, BLOCK_SIZE bsize) { | 
 |   assert(bsize < BLOCK_SIZES_ALL); | 
 |   const AV1_COMMON *cm = &cpi->common; | 
 |  | 
 |   if (mi_row >= cm->mi_rows || mi_col >= cm->mi_cols) return; | 
 |  | 
 |   const PARTITION_TYPE partition = get_partition(cm, mi_row, mi_col, bsize); | 
 |   const BLOCK_SIZE subsize = get_partition_subsize(bsize, partition); | 
 |  | 
 |   const int hbs = mi_size_wide[bsize] / 2; | 
 |   const int qbs = mi_size_wide[bsize] / 4; | 
 |  | 
 |   switch (partition) { | 
 |     case PARTITION_NONE: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       break; | 
 |     case PARTITION_HORZ: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + hbs, mi_col); | 
 |       break; | 
 |     case PARTITION_VERT: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col + hbs); | 
 |       break; | 
 |     case PARTITION_SPLIT: | 
 |       collect_mv_stats_sb(mv_stats, cpi, mi_row, mi_col, subsize); | 
 |       collect_mv_stats_sb(mv_stats, cpi, mi_row, mi_col + hbs, subsize); | 
 |       collect_mv_stats_sb(mv_stats, cpi, mi_row + hbs, mi_col, subsize); | 
 |       collect_mv_stats_sb(mv_stats, cpi, mi_row + hbs, mi_col + hbs, subsize); | 
 |       break; | 
 | #if !CONFIG_EXT_RECUR_PARTITIONS | 
 |     case PARTITION_HORZ_A: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col + hbs); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + hbs, mi_col); | 
 |       break; | 
 |     case PARTITION_HORZ_B: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + hbs, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + hbs, mi_col + hbs); | 
 |       break; | 
 |     case PARTITION_VERT_A: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + hbs, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col + hbs); | 
 |       break; | 
 |     case PARTITION_VERT_B: | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col + hbs); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + hbs, mi_col + hbs); | 
 |       break; | 
 | #endif  // !CONFIG_EXT_RECUR_PARTITIONS | 
 | #if CONFIG_EXT_RECUR_PARTITIONS | 
 |     case PARTITION_HORZ_3: { | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + qbs, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row + 3 * qbs, mi_col); | 
 |       break; | 
 |     } | 
 |     case PARTITION_VERT_3: { | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col + qbs); | 
 |       collect_mv_stats_b(mv_stats, cpi, mi_row, mi_col + 3 * qbs); | 
 |       break; | 
 |     } | 
 | #else | 
 |     case PARTITION_HORZ_4: | 
 |       for (int i = 0; i < 4; ++i) { | 
 |         const int this_mi_row = mi_row + i * qbs; | 
 |         collect_mv_stats_b(mv_stats, cpi, this_mi_row, mi_col); | 
 |       } | 
 |       break; | 
 |     case PARTITION_VERT_4: | 
 |       for (int i = 0; i < 4; ++i) { | 
 |         const int this_mi_col = mi_col + i * qbs; | 
 |         collect_mv_stats_b(mv_stats, cpi, mi_row, this_mi_col); | 
 |       } | 
 |       break; | 
 | #endif  // CONFIG_EXT_RECUR_PARTITIONS | 
 |     default: assert(0); | 
 |   } | 
 | } | 
 |  | 
 | static AOM_INLINE void collect_mv_stats_tile(MV_STATS *mv_stats, | 
 |                                              const AV1_COMP *cpi, | 
 |                                              const TileInfo *tile_info) { | 
 |   const AV1_COMMON *cm = &cpi->common; | 
 |   const int mi_row_start = tile_info->mi_row_start; | 
 |   const int mi_row_end = tile_info->mi_row_end; | 
 |   const int mi_col_start = tile_info->mi_col_start; | 
 |   const int mi_col_end = tile_info->mi_col_end; | 
 |   const int sb_size_mi = cm->seq_params.mib_size; | 
 |   BLOCK_SIZE sb_size = cm->seq_params.sb_size; | 
 |   for (int mi_row = mi_row_start; mi_row < mi_row_end; mi_row += sb_size_mi) { | 
 |     for (int mi_col = mi_col_start; mi_col < mi_col_end; mi_col += sb_size_mi) { | 
 |       collect_mv_stats_sb(mv_stats, cpi, mi_row, mi_col, sb_size); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void av1_collect_mv_stats(AV1_COMP *cpi, int current_q) { | 
 |   MV_STATS *mv_stats = &cpi->mv_stats; | 
 |   const AV1_COMMON *cm = &cpi->common; | 
 |   const int tile_cols = cm->tile_cols; | 
 |   const int tile_rows = cm->tile_rows; | 
 |  | 
 |   for (int tile_row = 0; tile_row < tile_rows; tile_row++) { | 
 |     TileInfo tile_info; | 
 |     av1_tile_set_row(&tile_info, cm, tile_row); | 
 |     for (int tile_col = 0; tile_col < tile_cols; tile_col++) { | 
 |       const int tile_idx = tile_row * tile_cols + tile_col; | 
 |       av1_tile_set_col(&tile_info, cm, tile_col); | 
 |       cpi->tile_data[tile_idx].tctx = *cm->fc; | 
 |       cpi->td.mb.e_mbd.tile_ctx = &cpi->tile_data[tile_idx].tctx; | 
 |       collect_mv_stats_tile(mv_stats, cpi, &tile_info); | 
 |     } | 
 |   } | 
 |  | 
 |   mv_stats->q = current_q; | 
 |   mv_stats->order = cpi->common.current_frame.order_hint; | 
 |   mv_stats->valid = 1; | 
 | } | 
 |  | 
 | static AOM_INLINE int get_smart_mv_prec(AV1_COMP *cpi, const MV_STATS *mv_stats, | 
 |                                         int current_q) { | 
 |   const AV1_COMMON *cm = &cpi->common; | 
 |   const int order_hint = cpi->common.current_frame.order_hint; | 
 |   const int order_diff = order_hint - mv_stats->order; | 
 |   aom_clear_system_state(); | 
 |   const float area = cm->width * cm->height; | 
 |   float features[MV_PREC_FEATURE_SIZE] = { | 
 |     current_q, | 
 |     mv_stats->q, | 
 |     order_diff, | 
 |     mv_stats->inter_count / area, | 
 |     mv_stats->intra_count / area, | 
 |     mv_stats->default_mvs / area, | 
 |     mv_stats->mv_joint_count[0] / area, | 
 |     mv_stats->mv_joint_count[1] / area, | 
 |     mv_stats->mv_joint_count[2] / area, | 
 |     mv_stats->mv_joint_count[3] / area, | 
 |     mv_stats->last_bit_zero / area, | 
 |     mv_stats->last_bit_nonzero / area, | 
 |     mv_stats->total_mv_rate / area, | 
 |     mv_stats->hp_total_mv_rate / area, | 
 |     mv_stats->lp_total_mv_rate / area, | 
 |     mv_stats->horz_text / area, | 
 |     mv_stats->vert_text / area, | 
 |     mv_stats->diag_text / area, | 
 |   }; | 
 |  | 
 |   for (int f_idx = 0; f_idx < MV_PREC_FEATURE_SIZE; f_idx++) { | 
 |     features[f_idx] = | 
 |         (features[f_idx] - av1_mv_prec_mean[f_idx]) / av1_mv_prec_std[f_idx]; | 
 |   } | 
 |   float score = 0.0f; | 
 |  | 
 |   av1_nn_predict(features, &av1_mv_prec_dnn_config, 1, &score); | 
 |  | 
 |   const int use_high_hp = score >= 0.0f; | 
 |   return use_high_hp; | 
 | } | 
 | #endif  // !CONFIG_REALTIME_ONLY | 
 | // Compute edge energy in the frame | 
 | static MvSubpelPrecision determine_frame_mv_precision(const AV1_COMP *cpi, | 
 |                                                       int q, int use_edges) { | 
 |   (void)cpi; | 
 |   if (!use_edges) { | 
 |     return q < MV_HIPREC_QTHRESH ? MV_SUBPEL_EIGHTH_PRECISION | 
 |                                  : MV_SUBPEL_QTR_PRECISION; | 
 |   } | 
 |   if (q < MV_PREC_DET_QTHRESH) return MV_SUBPEL_EIGHTH_PRECISION; | 
 |   const YV12_BUFFER_CONFIG *srcbuf = cpi->source; | 
 |   const YV12_BUFFER_CONFIG *refbuf = | 
 |       get_ref_frame_yv12_buf(&cpi->common, GOLDEN_FRAME); | 
 |   const int bd = cpi->td.mb.e_mbd.bd; | 
 |   const int width = srcbuf->y_crop_width; | 
 |   const int height = srcbuf->y_crop_height; | 
 |   const int stride = srcbuf->y_stride; | 
 |   const int ref_stride = refbuf->y_stride; | 
 |   int num_blks[2] = { 0, 0 }; | 
 |   if (srcbuf->flags & YV12_FLAG_HIGHBITDEPTH) { | 
 |     const uint16_t *src16 = | 
 |         (const uint16_t *)CONVERT_TO_SHORTPTR(srcbuf->y_buffer); | 
 |     const uint16_t *ref16 = | 
 |         (const uint16_t *)CONVERT_TO_SHORTPTR(refbuf->y_buffer); | 
 |     for (int i = 0; i < height - MV_PREC_DET_BLK_SIZE; | 
 |          i += MV_PREC_DET_BLK_SIZE) { | 
 |       for (int j = 0; j < width - MV_PREC_DET_BLK_SIZE; | 
 |            j += MV_PREC_DET_BLK_SIZE) { | 
 |         const uint16_t *src = src16 + i * stride + j; | 
 |         const uint16_t *ref = ref16 + i * stride + j; | 
 |         int64_t sse = aom_highbd_sse( | 
 |             CONVERT_TO_BYTEPTR(src), stride, CONVERT_TO_BYTEPTR(ref), | 
 |             ref_stride, MV_PREC_DET_BLK_SIZE, MV_PREC_DET_BLK_SIZE); | 
 |         int64_t sse_norm = ROUND_POWER_OF_TWO( | 
 |             sse, 2 * MV_PREC_DET_BLK_SIZE_BITS + 2 * (bd - 8)); | 
 |         if (sse_norm < MV_PREC_DET_SSE_STATIC_THRESH) continue; | 
 |         int64_t gx = 0, gy = 0, g; | 
 |         for (int y = 0; y < MV_PREC_DET_BLK_SIZE; ++y) { | 
 |           for (int x = 0; x < MV_PREC_DET_BLK_SIZE; ++x) { | 
 |             gx += | 
 |                 abs(src[-stride + 1] - src[-stride - 1] + | 
 |                     (src[1] - src[-1]) * 2 + src[stride + 1] - src[stride - 1]); | 
 |             gy += abs(src[stride - 1] - src[-stride - 1] + | 
 |                       (src[stride] - src[-stride]) * 2 + src[stride + 1] - | 
 |                       src[-stride + 1]); | 
 |             src++; | 
 |           } | 
 |           src += stride - MV_PREC_DET_BLK_SIZE; | 
 |         } | 
 |         g = gx * gx + gy * gy; | 
 |         // Normalize to per pixel and bit-depth of 8 | 
 |         g = ROUND_POWER_OF_TWO( | 
 |             g, 4 * MV_PREC_DET_BLK_SIZE_BITS + 4 + 2 * (bd - 8)); | 
 |         ++num_blks[g > MV_PREC_DET_SOBEL_EDGE_THRESH]; | 
 |       } | 
 |     } | 
 |   } else { | 
 |     const uint8_t *src8 = srcbuf->y_buffer; | 
 |     const uint8_t *ref8 = refbuf->y_buffer; | 
 |     for (int i = 0; i < height - MV_PREC_DET_BLK_SIZE; | 
 |          i += MV_PREC_DET_BLK_SIZE) { | 
 |       for (int j = 0; j < width - MV_PREC_DET_BLK_SIZE; | 
 |            j += MV_PREC_DET_BLK_SIZE) { | 
 |         const uint8_t *src = src8 + i * stride + j; | 
 |         const uint8_t *ref = ref8 + i * stride + j; | 
 |         int64_t sse = aom_sse(src, stride, ref, ref_stride, | 
 |                               MV_PREC_DET_BLK_SIZE, MV_PREC_DET_BLK_SIZE); | 
 |         int64_t sse_norm = | 
 |             ROUND_POWER_OF_TWO(sse, 2 * MV_PREC_DET_BLK_SIZE_BITS); | 
 |         if (sse_norm < MV_PREC_DET_SSE_STATIC_THRESH) continue; | 
 |         int64_t gx = 0, gy = 0, g; | 
 |         for (int y = 0; y < MV_PREC_DET_BLK_SIZE; ++y) { | 
 |           for (int x = 0; x < MV_PREC_DET_BLK_SIZE; ++x) { | 
 |             gx += | 
 |                 abs(src[-stride + 1] - src[-stride - 1] + | 
 |                     (src[1] - src[-1]) * 2 + src[stride + 1] - src[stride - 1]); | 
 |             gy += abs(src[stride - 1] - src[-stride - 1] + | 
 |                       (src[stride] - src[-stride]) * 2 + src[stride + 1] - | 
 |                       src[-stride + 1]); | 
 |             src++; | 
 |           } | 
 |           src += stride - MV_PREC_DET_BLK_SIZE; | 
 |         } | 
 |         g = gx * gx + gy * gy; | 
 |         // Normalize to per pixel and bit-depth of 8 | 
 |         g = ROUND_POWER_OF_TWO( | 
 |             g, 4 * MV_PREC_DET_BLK_SIZE_BITS + 4 + 2 * (bd - 8)); | 
 |         ++num_blks[g > MV_PREC_DET_SOBEL_EDGE_THRESH]; | 
 |       } | 
 |     } | 
 |   } | 
 |   if (num_blks[0] + num_blks[1] == 0) return MV_SUBPEL_QTR_PRECISION; | 
 |   const double pct_edge_blks = | 
 |       100.0 * (double)num_blks[1] / (num_blks[0] + num_blks[1]); | 
 |   const double pct_edge_blks_by_q = pct_edge_blks / q; | 
 |   // printf("pct_edge_blks = %f [%d + %d], q = %d, ratio = %f\n", pct_edge_blks, | 
 |   //        num_blks[0], num_blks[1], q, pct_edge_blks_by_q); | 
 |   if (pct_edge_blks_by_q >= MV_PREC_DET_THRESH) | 
 |     return MV_SUBPEL_EIGHTH_PRECISION; | 
 |   return MV_SUBPEL_QTR_PRECISION; | 
 | } | 
 |  | 
 | #if CONFIG_FLEX_MVRES | 
 | #define FLEX_MV_PRECISION_QTHRESH 256  // Reduce to turn off at low quality | 
 | static int determine_pb_flex_mv_precision(const AV1_COMP *cpi, int q) { | 
 |   return (cpi->common.fr_mv_precision >= MV_SUBPEL_QTR_PRECISION && | 
 |           !is_stat_generation_stage(cpi) && q <= FLEX_MV_PRECISION_QTHRESH); | 
 | } | 
 | #endif  // CONFIG_FLEX_MVRES | 
 |  | 
 | void av1_pick_and_set_high_precision_mv(AV1_COMP *cpi, int q) { | 
 |   MvSubpelPrecision precision = cpi->common.cur_frame_force_integer_mv | 
 |                                     ? MV_SUBPEL_NONE | 
 |                                     : determine_frame_mv_precision(cpi, q, 0); | 
 |  | 
 |   assert(IMPLIES(!cpi->common.cur_frame_force_integer_mv, | 
 |                  precision >= MV_SUBPEL_QTR_PRECISION)); | 
 |  | 
 |   if (cpi->sf.high_precision_mv_usage == QTR_ONLY) | 
 |     precision = MV_SUBPEL_QTR_PRECISION; | 
 |  | 
 | #if !CONFIG_REALTIME_ONLY | 
 |   else if (cpi->sf.high_precision_mv_usage == LAST_MV_DATA && | 
 |            av1_frame_allows_smart_mv(cpi) && cpi->mv_stats.valid) { | 
 |     if (get_smart_mv_prec(cpi, &cpi->mv_stats, q)) | 
 |       precision = MV_SUBPEL_EIGHTH_PRECISION; | 
 |   } | 
 | #endif  // !CONFIG_REALTIME_ONLY | 
 |  | 
 |   av1_set_mv_precision(cpi, precision, cpi->common.cur_frame_force_integer_mv); | 
 |  | 
 | #if CONFIG_FLEX_MVRES | 
 |   cpi->common.use_sb_mv_precision = ENABLE_SB_RES; | 
 |   cpi->common.use_pb_mv_precision = | 
 |       ENABLE_PB_RES ? determine_pb_flex_mv_precision(cpi, q) : 0; | 
 | #endif  // CONFIG_FLEX_MVRES | 
 | } |