| /* | |
| * Copyright (c) 2021, Alliance for Open Media. All rights reserved | |
| * | |
| * This source code is subject to the terms of the BSD 3-Clause Clear License | |
| * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear | |
| * License was not distributed with this source code in the LICENSE file, you | |
| * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. 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 | |
| * aomedia.org/license/patent-license/. | |
| */ | |
| #include "aom_dsp/binary_codes_writer.h" | |
| #include "aom_ports/system_state.h" | |
| #include "av1/common/mv.h" | |
| #include "av1/encoder/corner_detect.h" | |
| #include "av1/encoder/encoder.h" | |
| #include "av1/encoder/ethread.h" | |
| #include "av1/encoder/rdopt.h" | |
| // Highest motion model to search. | |
| #define GLOBAL_TRANS_TYPES_ENC 3 | |
| // Computes the cost for the warp parameters. | |
| static int gm_get_params_cost(const WarpedMotionParams *gm, | |
| const WarpedMotionParams *ref_gm, | |
| MvSubpelPrecision precision) { | |
| const int precision_loss = get_gm_precision_loss(precision); | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| (void)precision_loss; | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| int params_cost = 0; | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| const int trans_bits = GM_ABS_TRANS_BITS; | |
| const int trans_prec_diff = GM_TRANS_PREC_DIFF; | |
| const int trans_max = (1 << trans_bits) - 1; | |
| #else | |
| const int trans_bits = (gm->wmtype == TRANSLATION) | |
| ? GM_ABS_TRANS_ONLY_BITS - precision_loss | |
| : GM_ABS_TRANS_BITS; | |
| const int trans_prec_diff = (gm->wmtype == TRANSLATION) | |
| ? GM_TRANS_ONLY_PREC_DIFF + precision_loss | |
| : GM_TRANS_PREC_DIFF; | |
| const int trans_max = (1 << trans_bits); | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| switch (gm->wmtype) { | |
| case AFFINE: | |
| case ROTZOOM: | |
| params_cost += aom_count_signed_primitive_refsubexpfin( | |
| GM_ALPHA_MAX + 1, SUBEXPFIN_K, | |
| (ref_gm->wmmat[2] >> GM_ALPHA_PREC_DIFF) - (1 << GM_ALPHA_PREC_BITS), | |
| (gm->wmmat[2] >> GM_ALPHA_PREC_DIFF) - (1 << GM_ALPHA_PREC_BITS)); | |
| params_cost += aom_count_signed_primitive_refsubexpfin( | |
| GM_ALPHA_MAX + 1, SUBEXPFIN_K, | |
| (ref_gm->wmmat[3] >> GM_ALPHA_PREC_DIFF), | |
| (gm->wmmat[3] >> GM_ALPHA_PREC_DIFF)); | |
| if (gm->wmtype >= AFFINE) { | |
| params_cost += aom_count_signed_primitive_refsubexpfin( | |
| GM_ALPHA_MAX + 1, SUBEXPFIN_K, | |
| (ref_gm->wmmat[4] >> GM_ALPHA_PREC_DIFF), | |
| (gm->wmmat[4] >> GM_ALPHA_PREC_DIFF)); | |
| params_cost += aom_count_signed_primitive_refsubexpfin( | |
| GM_ALPHA_MAX + 1, SUBEXPFIN_K, | |
| (ref_gm->wmmat[5] >> GM_ALPHA_PREC_DIFF) - | |
| (1 << GM_ALPHA_PREC_BITS), | |
| (gm->wmmat[5] >> GM_ALPHA_PREC_DIFF) - (1 << GM_ALPHA_PREC_BITS)); | |
| } | |
| params_cost += aom_count_signed_primitive_refsubexpfin( | |
| trans_max + 1, SUBEXPFIN_K, (ref_gm->wmmat[0] >> trans_prec_diff), | |
| (gm->wmmat[0] >> trans_prec_diff)); | |
| params_cost += aom_count_signed_primitive_refsubexpfin( | |
| trans_max + 1, SUBEXPFIN_K, (ref_gm->wmmat[1] >> trans_prec_diff), | |
| (gm->wmmat[1] >> trans_prec_diff)); | |
| AOM_FALLTHROUGH_INTENDED; | |
| case IDENTITY: break; | |
| default: assert(0); | |
| } | |
| return (params_cost << AV1_PROB_COST_SHIFT); | |
| } | |
| // Calculates the threshold to be used for warp error computation. | |
| static AOM_INLINE int64_t calc_erroradv_threshold(int64_t ref_frame_error) { | |
| return (int64_t)(ref_frame_error * erroradv_tr + 0.5); | |
| } | |
| // For the given reference frame, computes the global motion parameters for | |
| // different motion models and finds the best. | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| static AOM_INLINE void compute_global_motion_for_ref_frame( | |
| AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], int frame, | |
| int num_src_corners, int *src_corners, unsigned char *src_buffer, | |
| MotionModel *params_by_motion, uint8_t *segment_map, | |
| const int segment_map_w, const int segment_map_h) { | |
| #else | |
| static AOM_INLINE void compute_global_motion_for_ref_frame( | |
| AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], int frame, | |
| int num_src_corners, int *src_corners, unsigned char *src_buffer, | |
| MotionModel *params_by_motion, uint8_t *segment_map, | |
| const int segment_map_w, const int segment_map_h, | |
| const WarpedMotionParams *ref_params) { | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| ThreadData *const td = &cpi->td; | |
| MACROBLOCK *const x = &td->mb; | |
| AV1_COMMON *const cm = &cpi->common; | |
| MACROBLOCKD *const xd = &x->e_mbd; | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| GlobalMotionInfo *const gm_info = &cpi->gm_info; | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| int i; | |
| int src_width = cpi->source->y_width; | |
| int src_height = cpi->source->y_height; | |
| int src_stride = cpi->source->y_stride; | |
| // clang-format off | |
| static const double kIdentityParams[MAX_PARAMDIM - 1] = { | |
| 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 | |
| }; | |
| // clang-format on | |
| WarpedMotionParams tmp_wm_params; | |
| const double *params_this_motion; | |
| int inliers_by_motion[RANSAC_NUM_MOTIONS]; | |
| assert(ref_buf[frame] != NULL); | |
| TransformationType model; | |
| aom_clear_system_state(); | |
| // TODO(sarahparker, debargha): Explore do_adaptive_gm_estimation = 1 | |
| const int do_adaptive_gm_estimation = 0; | |
| #if CONFIG_EXPLICIT_TEMPORAL_DIST_CALC | |
| const int ref_frame_dist = get_relative_dist( | |
| &cm->seq_params.order_hint_info, cm->current_frame.display_order_hint, | |
| cm->cur_frame->ref_display_order_hint[frame]); | |
| #else | |
| const int ref_frame_dist = get_relative_dist( | |
| &cm->seq_params.order_hint_info, cm->current_frame.order_hint, | |
| cm->cur_frame->ref_order_hints[frame]); | |
| #endif // CONFIG_EXPLICIT_TEMPORAL_DIST_CALC | |
| const GlobalMotionEstimationType gm_estimation_type = | |
| cm->seq_params.order_hint_info.enable_order_hint && | |
| abs(ref_frame_dist) <= 2 && do_adaptive_gm_estimation | |
| ? GLOBAL_MOTION_DISFLOW_BASED | |
| : GLOBAL_MOTION_FEATURE_BASED; | |
| for (model = ROTZOOM; model < GLOBAL_TRANS_TYPES_ENC; ++model) { | |
| // Initially set all params to identity. | |
| for (i = 0; i < RANSAC_NUM_MOTIONS; ++i) { | |
| memcpy(params_by_motion[i].params, kIdentityParams, | |
| (MAX_PARAMDIM - 1) * sizeof(*(params_by_motion[i].params))); | |
| params_by_motion[i].num_inliers = 0; | |
| } | |
| av1_compute_global_motion(model, src_buffer, src_width, src_height, | |
| src_stride, src_corners, num_src_corners, | |
| ref_buf[frame], cpi->common.seq_params.bit_depth, | |
| gm_estimation_type, inliers_by_motion, | |
| params_by_motion, RANSAC_NUM_MOTIONS); | |
| int64_t best_ref_frame_error = 0; | |
| int64_t best_warp_error = INT64_MAX; | |
| for (i = 0; i < RANSAC_NUM_MOTIONS; ++i) { | |
| if (inliers_by_motion[i] == 0) continue; | |
| params_this_motion = params_by_motion[i].params; | |
| av1_convert_model_to_params(params_this_motion, &tmp_wm_params); | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| // If the found model can be represented as a simple translation, | |
| // then reject it. This is because translational motion is cheaper | |
| // to signal through the standard MV coding tools, rather than through | |
| // global motion | |
| if (tmp_wm_params.wmtype <= TRANSLATION) continue; | |
| #else | |
| // For IDENTITY type models, we don't need to evaluate anything because | |
| // all the following logic is effectively comparing the estimated model | |
| // to an identity model. | |
| if (tmp_wm_params.wmtype == IDENTITY) continue; | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| av1_compute_feature_segmentation_map( | |
| segment_map, segment_map_w, segment_map_h, | |
| params_by_motion[i].inliers, params_by_motion[i].num_inliers); | |
| const int64_t ref_frame_error = av1_segmented_frame_error( | |
| xd->bd, ref_buf[frame]->y_buffer, ref_buf[frame]->y_stride, | |
| cpi->source->y_buffer, src_width, src_height, src_stride, segment_map, | |
| segment_map_w); | |
| if (ref_frame_error == 0) continue; | |
| const int64_t erroradv_threshold = | |
| calc_erroradv_threshold(ref_frame_error); | |
| const int64_t warp_error = av1_refine_integerized_param( | |
| &tmp_wm_params, tmp_wm_params.wmtype, xd->bd, | |
| ref_buf[frame]->y_buffer, ref_buf[frame]->y_width, | |
| ref_buf[frame]->y_height, ref_buf[frame]->y_stride, | |
| cpi->source->y_buffer, src_width, src_height, src_stride, | |
| GM_REFINEMENT_COUNT, best_warp_error, segment_map, segment_map_w, | |
| erroradv_threshold); | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| // av1_refine_integerized_param() can change the wmtype to a simpler | |
| // model type than its input. So we need to check again to see if | |
| // we have a translational model | |
| if (tmp_wm_params.wmtype <= TRANSLATION) continue; | |
| #else | |
| // av1_refine_integerized_param() can return a simpler model type than | |
| // its input, so re-check model type here | |
| if (tmp_wm_params.wmtype == IDENTITY) continue; | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| // Apply initial quality filter, which depends only on the error metrics | |
| // and not the model cost | |
| if (warp_error >= ref_frame_error * erroradv_tr) continue; | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| if (warp_error < best_warp_error) { | |
| best_ref_frame_error = ref_frame_error; | |
| best_warp_error = warp_error; | |
| // Save the wm_params modified by | |
| // av1_refine_integerized_param() rather than motion index to | |
| // avoid rerunning refine() below. | |
| memcpy(&(cm->global_motion[frame]), &tmp_wm_params, | |
| sizeof(WarpedMotionParams)); | |
| } | |
| } | |
| if (cm->global_motion[frame].wmtype <= AFFINE) | |
| if (!av1_get_shear_params(&cm->global_motion[frame])) | |
| cm->global_motion[frame] = default_warp_params; | |
| #if !CONFIG_IMPROVED_GLOBAL_MOTION | |
| if (cm->global_motion[frame].wmtype == TRANSLATION) { | |
| cm->global_motion[frame].wmmat[0] = | |
| convert_to_trans_prec(cm->features.fr_mv_precision, | |
| cm->global_motion[frame].wmmat[0]) * | |
| GM_TRANS_ONLY_DECODE_FACTOR; | |
| cm->global_motion[frame].wmmat[1] = | |
| convert_to_trans_prec(cm->features.fr_mv_precision, | |
| cm->global_motion[frame].wmmat[1]) * | |
| GM_TRANS_ONLY_DECODE_FACTOR; | |
| } | |
| #endif // !CONFIG_IMPROVED_GLOBAL_MOTION | |
| if (cm->global_motion[frame].wmtype == IDENTITY) continue; | |
| // Once we get here, best_ref_frame_error must be > 0. This is because | |
| // of the logic above, which skips over any models which have | |
| // ref_frame_error == 0 | |
| assert(best_ref_frame_error > 0); | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| gm_info->erroradvantage[frame] = | |
| (double)best_warp_error / best_ref_frame_error; | |
| break; | |
| #else | |
| // If the best error advantage found doesn't meet the threshold for | |
| // this motion type, revert to IDENTITY. | |
| if (!av1_is_enough_erroradvantage( | |
| (double)best_warp_error / best_ref_frame_error, | |
| gm_get_params_cost(&cm->global_motion[frame], ref_params, | |
| cm->features.fr_mv_precision))) { | |
| cm->global_motion[frame] = default_warp_params; | |
| } | |
| if (cm->global_motion[frame].wmtype != IDENTITY) break; | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| } | |
| aom_clear_system_state(); | |
| } | |
| // Computes global motion for the given reference frame. | |
| void av1_compute_gm_for_valid_ref_frames( | |
| AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], int frame, | |
| int num_src_corners, int *src_corners, unsigned char *src_buffer, | |
| MotionModel *params_by_motion, uint8_t *segment_map, int segment_map_w, | |
| int segment_map_h) { | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| compute_global_motion_for_ref_frame( | |
| cpi, ref_buf, frame, num_src_corners, src_corners, src_buffer, | |
| params_by_motion, segment_map, segment_map_w, segment_map_h); | |
| #else | |
| AV1_COMMON *const cm = &cpi->common; | |
| const WarpedMotionParams *ref_params = | |
| cm->prev_frame ? &cm->prev_frame->global_motion[frame] | |
| : &default_warp_params; | |
| compute_global_motion_for_ref_frame( | |
| cpi, ref_buf, frame, num_src_corners, src_corners, src_buffer, | |
| params_by_motion, segment_map, segment_map_w, segment_map_h, ref_params); | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| } | |
| // Loops over valid reference frames and computes global motion estimation. | |
| static AOM_INLINE void compute_global_motion_for_references( | |
| AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], | |
| FrameDistPair reference_frame[INTER_REFS_PER_FRAME], int num_ref_frames, | |
| int num_src_corners, int *src_corners, unsigned char *src_buffer, | |
| MotionModel *params_by_motion, uint8_t *segment_map, | |
| const int segment_map_w, const int segment_map_h) { | |
| // Computation of frame corners for the source frame will be done already. | |
| assert(num_src_corners != -1); | |
| AV1_COMMON *const cm = &cpi->common; | |
| // Compute global motion w.r.t. reference frames starting from the nearest ref | |
| // frame in a given direction. | |
| for (int frame = 0; frame < num_ref_frames; frame++) { | |
| int ref_frame = reference_frame[frame].frame; | |
| av1_compute_gm_for_valid_ref_frames( | |
| cpi, ref_buf, ref_frame, num_src_corners, src_corners, src_buffer, | |
| params_by_motion, segment_map, segment_map_w, segment_map_h); | |
| // If global motion w.r.t. current ref frame is | |
| // INVALID/TRANSLATION/IDENTITY, skip the evaluation of global motion w.r.t | |
| // the remaining ref frames in that direction. The below exit is disabled | |
| // when ref frame distance w.r.t. current frame is zero. E.g.: | |
| // source_alt_ref_frame w.r.t. ARF frames. | |
| if (cpi->sf.gm_sf.prune_ref_frame_for_gm_search && | |
| reference_frame[frame].distance != 0 && | |
| cm->global_motion[ref_frame].wmtype <= TRANSLATION) | |
| break; | |
| } | |
| } | |
| // Compares the distance in 'a' and 'b'. Returns 1 if the frame corresponding to | |
| // 'a' is farther, -1 if the frame corresponding to 'b' is farther, 0 otherwise. | |
| static int compare_distance(const void *a, const void *b) { | |
| const int diff = | |
| ((FrameDistPair *)a)->distance - ((FrameDistPair *)b)->distance; | |
| if (diff > 0) | |
| return 1; | |
| else if (diff < 0) | |
| return -1; | |
| return 0; | |
| } | |
| static int disable_gm_search_based_on_stats(const AV1_COMP *const cpi) { | |
| const GF_GROUP *gf_group = &cpi->gf_group; | |
| int is_gm_present = 1; | |
| // Check number of GM models only in GF groups with ARF frames. GM param | |
| // estimation is always done in the case of GF groups with no ARF frames (flat | |
| // gops) | |
| if (gf_group->arf_index > -1) { | |
| // valid_gm_model_found is initialized to INT32_MAX in the beginning of | |
| // every GF group. | |
| // Therefore, GM param estimation is always done for all frames until | |
| // at least 1 frame each of ARF_UPDATE, INTNL_ARF_UPDATE and LF_UPDATE are | |
| // encoded in a GF group For subsequent frames, GM param estimation is | |
| // disabled, if no valid models have been found in all the three update | |
| // types. | |
| is_gm_present = (cpi->valid_gm_model_found[ARF_UPDATE] != 0) || | |
| (cpi->valid_gm_model_found[INTNL_ARF_UPDATE] != 0) || | |
| (cpi->valid_gm_model_found[LF_UPDATE] != 0); | |
| } | |
| return !is_gm_present; | |
| } | |
| // Populates valid reference frames in past/future directions in | |
| // 'reference_frames' and their count in 'num_ref_frames'. | |
| static AOM_INLINE void update_valid_ref_frames_for_gm( | |
| AV1_COMP *cpi, YV12_BUFFER_CONFIG *ref_buf[INTER_REFS_PER_FRAME], | |
| FrameDistPair reference_frames[MAX_DIRECTIONS][INTER_REFS_PER_FRAME], | |
| int *num_ref_frames) { | |
| AV1_COMMON *const cm = &cpi->common; | |
| int *num_past_ref_frames = &num_ref_frames[0]; | |
| int *num_future_ref_frames = &num_ref_frames[1]; | |
| const GF_GROUP *gf_group = &cpi->gf_group; | |
| int ref_pruning_enabled = is_frame_eligible_for_ref_pruning( | |
| gf_group, cpi->sf.inter_sf.selective_ref_frame, 1, gf_group->index); | |
| int cur_frame_gm_disabled = 0; | |
| int pyr_lvl = cm->cur_frame->pyramid_level; | |
| if (cpi->sf.gm_sf.disable_gm_search_based_on_stats) { | |
| cur_frame_gm_disabled = disable_gm_search_based_on_stats(cpi); | |
| } | |
| for (int frame = cm->ref_frames_info.num_total_refs - 1; frame >= 0; | |
| --frame) { | |
| const MV_REFERENCE_FRAME ref_frame[2] = { frame, NONE_FRAME }; | |
| #if CONFIG_ALLOW_SAME_REF_COMPOUND | |
| assert(frame <= INTER_REFS_PER_FRAME); | |
| #endif // CONFIG_ALLOW_SAME_REF_COMPOUND | |
| const int ref_disabled = !(cm->ref_frame_flags & (1 << frame)); | |
| ref_buf[frame] = NULL; | |
| cm->global_motion[frame] = default_warp_params; | |
| RefCntBuffer *buf = get_ref_frame_buf(cm, frame); | |
| // Skip global motion estimation for invalid ref frames | |
| if (buf == NULL || | |
| (ref_disabled && cpi->sf.hl_sf.recode_loop != DISALLOW_RECODE)) { | |
| continue; | |
| } else { | |
| ref_buf[frame] = &buf->buf; | |
| } | |
| int prune_ref_frames = | |
| ref_pruning_enabled && | |
| prune_ref_by_selective_ref_frame(cpi, NULL, ref_frame); | |
| int ref_pyr_lvl = buf->pyramid_level; | |
| if (ref_buf[frame]->y_crop_width == cpi->source->y_crop_width && | |
| ref_buf[frame]->y_crop_height == cpi->source->y_crop_height && | |
| frame < cpi->sf.gm_sf.max_ref_frames && !prune_ref_frames && | |
| ref_pyr_lvl <= pyr_lvl && !cur_frame_gm_disabled) { | |
| assert(ref_buf[frame] != NULL); | |
| const int relative_frame_dist = av1_encoder_get_relative_dist( | |
| buf->display_order_hint, cm->cur_frame->display_order_hint); | |
| // Populate past and future ref frames. | |
| // reference_frames[0][] indicates past direction and | |
| // reference_frames[1][] indicates future direction. | |
| if (relative_frame_dist <= 0) { | |
| reference_frames[0][*num_past_ref_frames].distance = | |
| abs(relative_frame_dist); | |
| reference_frames[0][*num_past_ref_frames].frame = frame; | |
| (*num_past_ref_frames)++; | |
| } else { | |
| reference_frames[1][*num_future_ref_frames].distance = | |
| abs(relative_frame_dist); | |
| reference_frames[1][*num_future_ref_frames].frame = frame; | |
| (*num_future_ref_frames)++; | |
| } | |
| } | |
| } | |
| } | |
| // Allocates and initializes memory for segment_map and MotionModel. | |
| static AOM_INLINE void alloc_global_motion_data(MotionModel *params_by_motion, | |
| uint8_t **segment_map, | |
| const int segment_map_w, | |
| const int segment_map_h) { | |
| for (int m = 0; m < RANSAC_NUM_MOTIONS; m++) { | |
| av1_zero(params_by_motion[m]); | |
| params_by_motion[m].inliers = | |
| aom_malloc(sizeof(*(params_by_motion[m].inliers)) * 2 * MAX_CORNERS); | |
| } | |
| *segment_map = (uint8_t *)aom_malloc(sizeof(*segment_map) * segment_map_w * | |
| segment_map_h); | |
| av1_zero_array(*segment_map, segment_map_w * segment_map_h); | |
| } | |
| // Deallocates segment_map and inliers. | |
| static AOM_INLINE void dealloc_global_motion_data(MotionModel *params_by_motion, | |
| uint8_t *segment_map) { | |
| aom_free(segment_map); | |
| for (int m = 0; m < RANSAC_NUM_MOTIONS; m++) { | |
| aom_free(params_by_motion[m].inliers); | |
| } | |
| } | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| // Select which global motion model to use as a base | |
| static AOM_INLINE void pick_base_gm_params(AV1_COMP *cpi) { | |
| AV1_COMMON *const cm = &cpi->common; | |
| const SequenceHeader *const seq_params = &cm->seq_params; | |
| GlobalMotionInfo *const gm_info = &cpi->gm_info; | |
| int num_total_refs = cm->ref_frames_info.num_total_refs; | |
| int best_our_ref; | |
| int best_their_ref; | |
| const WarpedMotionParams *best_base_model; | |
| int best_temporal_distance; | |
| int best_num_models; | |
| int best_cost; | |
| // Bitmask of which models we will actually use if we accept the current | |
| // best base model | |
| uint8_t best_enable_models; | |
| // First, evaluate the identity model as a base | |
| { | |
| int this_num_models = 0; | |
| int this_cost = | |
| aom_count_primitive_quniform(num_total_refs + 1, num_total_refs) | |
| << AV1_PROB_COST_SHIFT; | |
| uint8_t this_enable_models = 0; | |
| for (int frame = 0; frame < num_total_refs; frame++) { | |
| const WarpedMotionParams *model = &cm->global_motion[frame]; | |
| if (model->wmtype == IDENTITY) continue; | |
| int model_cost = gm_get_params_cost(model, &default_warp_params, | |
| cm->features.fr_mv_precision); | |
| bool use_model = av1_is_enough_erroradvantage( | |
| gm_info->erroradvantage[frame], model_cost); | |
| if (use_model) { | |
| this_num_models += 1; | |
| this_cost += model_cost; | |
| this_enable_models |= (1 << frame); | |
| } | |
| } | |
| // Set initial values | |
| best_our_ref = cm->ref_frames_info.num_total_refs; | |
| best_their_ref = -1; | |
| best_base_model = &default_warp_params; | |
| best_temporal_distance = 1; | |
| best_num_models = this_num_models; | |
| best_cost = this_cost; | |
| best_enable_models = this_enable_models; | |
| } | |
| // Then try each available reference model in turn | |
| for (int our_ref = 0; our_ref < num_total_refs; ++our_ref) { | |
| const int ref_disabled = !(cm->ref_frame_flags & (1 << our_ref)); | |
| RefCntBuffer *buf = get_ref_frame_buf(cm, our_ref); | |
| // Skip looking at invalid ref frames | |
| if (buf == NULL || | |
| (ref_disabled && cpi->sf.hl_sf.recode_loop != DISALLOW_RECODE)) { | |
| continue; | |
| } | |
| int their_num_refs = buf->num_ref_frames; | |
| for (int their_ref = 0; their_ref < their_num_refs; ++their_ref) { | |
| const WarpedMotionParams *base_model = &buf->global_motion[their_ref]; | |
| if (base_model->wmtype == IDENTITY) { | |
| continue; | |
| } | |
| #if CONFIG_EXPLICIT_TEMPORAL_DIST_CALC | |
| const int our_ref_order_hint = buf->display_order_hint; | |
| const int their_ref_order_hint = buf->ref_display_order_hint[their_ref]; | |
| #else | |
| const int our_ref_order_hint = buf->order_hint; | |
| const int their_ref_order_hint = buf->ref_order_hints[their_ref]; | |
| #endif // CONFIG_EXPLICIT_TEMPORAL_DIST_CALC | |
| int base_temporal_distance = | |
| get_relative_dist(&seq_params->order_hint_info, our_ref_order_hint, | |
| their_ref_order_hint); | |
| int this_num_models = 0; | |
| int this_cost = | |
| (aom_count_primitive_quniform(num_total_refs + 1, our_ref) + | |
| aom_count_primitive_quniform(their_num_refs, their_ref)) | |
| << AV1_PROB_COST_SHIFT; | |
| uint8_t this_enable_models = 0; | |
| for (int frame = 0; frame < num_total_refs; frame++) { | |
| const WarpedMotionParams *model = &cm->global_motion[frame]; | |
| if (model->wmtype == IDENTITY) continue; | |
| int temporal_distance; | |
| if (seq_params->order_hint_info.enable_order_hint) { | |
| const RefCntBuffer *const ref_buf = get_ref_frame_buf(cm, frame); | |
| #if CONFIG_EXPLICIT_TEMPORAL_DIST_CALC | |
| const int ref_order_hint = ref_buf->display_order_hint; | |
| const int cur_order_hint = cm->cur_frame->display_order_hint; | |
| #else | |
| const int ref_order_hint = ref_buf->order_hint; | |
| const int cur_order_hint = cm->cur_frame->order_hint; | |
| #endif // CONFIG_EXPLICIT_TEMPORAL_DIST_CALC | |
| temporal_distance = get_relative_dist(&seq_params->order_hint_info, | |
| cur_order_hint, ref_order_hint); | |
| } else { | |
| temporal_distance = 1; | |
| } | |
| if (temporal_distance == 0) { | |
| // Don't code global motion for frames at the same temporal instant | |
| assert(model->wmtype == IDENTITY); | |
| continue; | |
| } | |
| WarpedMotionParams ref_params; | |
| av1_scale_warp_model(base_model, base_temporal_distance, &ref_params, | |
| temporal_distance); | |
| int model_cost = gm_get_params_cost(model, &ref_params, | |
| cm->features.fr_mv_precision); | |
| bool use_model = av1_is_enough_erroradvantage( | |
| gm_info->erroradvantage[frame], model_cost); | |
| if (use_model) { | |
| this_num_models += 1; | |
| this_cost += model_cost; | |
| this_enable_models |= (1 << frame); | |
| } | |
| } | |
| if (this_num_models > best_num_models || | |
| (this_num_models == best_num_models && this_cost < best_cost)) { | |
| best_our_ref = our_ref; | |
| best_their_ref = their_ref; | |
| best_base_model = base_model; | |
| best_temporal_distance = base_temporal_distance; | |
| best_num_models = this_num_models; | |
| best_cost = this_cost; | |
| best_enable_models = this_enable_models; | |
| } | |
| } | |
| } | |
| gm_info->base_model_our_ref = best_our_ref; | |
| gm_info->base_model_their_ref = best_their_ref; | |
| cm->base_global_motion_model = *best_base_model; | |
| cm->base_global_motion_distance = best_temporal_distance; | |
| for (int frame = 0; frame < num_total_refs; frame++) { | |
| if ((best_enable_models & (1 << frame)) == 0) { | |
| // Disable this model | |
| cm->global_motion[frame] = default_warp_params; | |
| } | |
| } | |
| } | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| // Initializes parameters used for computing global motion. | |
| static AOM_INLINE void setup_global_motion_info_params(AV1_COMP *cpi) { | |
| GlobalMotionInfo *const gm_info = &cpi->gm_info; | |
| YV12_BUFFER_CONFIG *source = cpi->source; | |
| // The source buffer is 16-bit, so we need to convert to 8 bits for the | |
| // following code. We cache the result until the source frame is released. | |
| gm_info->src_buffer = | |
| av1_downconvert_frame(source, cpi->common.seq_params.bit_depth); | |
| gm_info->segment_map_w = | |
| (source->y_width + WARP_ERROR_BLOCK) >> WARP_ERROR_BLOCK_LOG; | |
| gm_info->segment_map_h = | |
| (source->y_height + WARP_ERROR_BLOCK) >> WARP_ERROR_BLOCK_LOG; | |
| memset(gm_info->reference_frames, -1, | |
| sizeof(gm_info->reference_frames[0][0]) * MAX_DIRECTIONS * | |
| (INTER_REFS_PER_FRAME)); | |
| av1_zero(gm_info->num_ref_frames); | |
| // Populate ref_buf for valid ref frames in global motion | |
| update_valid_ref_frames_for_gm(cpi, gm_info->ref_buf, | |
| gm_info->reference_frames, | |
| gm_info->num_ref_frames); | |
| // Sort the past and future ref frames in the ascending order of their | |
| // distance from the current frame. reference_frames[0] => past direction | |
| // and reference_frames[1] => future direction. | |
| qsort(gm_info->reference_frames[0], gm_info->num_ref_frames[0], | |
| sizeof(gm_info->reference_frames[0][0]), compare_distance); | |
| qsort(gm_info->reference_frames[1], gm_info->num_ref_frames[1], | |
| sizeof(gm_info->reference_frames[1][0]), compare_distance); | |
| gm_info->num_src_corners = -1; | |
| // If atleast one valid reference frame exists in past/future directions, | |
| // compute interest points of source frame using FAST features. | |
| if (gm_info->num_ref_frames[0] > 0 || gm_info->num_ref_frames[1] > 0) { | |
| gm_info->num_src_corners = av1_fast_corner_detect( | |
| gm_info->src_buffer, source->y_width, source->y_height, | |
| source->y_stride, gm_info->src_corners, MAX_CORNERS); | |
| } | |
| } | |
| // Computes global motion w.r.t. valid reference frames. | |
| static AOM_INLINE void global_motion_estimation(AV1_COMP *cpi) { | |
| GlobalMotionInfo *const gm_info = &cpi->gm_info; | |
| MotionModel params_by_motion[RANSAC_NUM_MOTIONS]; | |
| uint8_t *segment_map = NULL; | |
| alloc_global_motion_data(params_by_motion, &segment_map, | |
| gm_info->segment_map_w, gm_info->segment_map_h); | |
| // Compute global motion w.r.t. past reference frames and future reference | |
| // frames | |
| for (int dir = 0; dir < MAX_DIRECTIONS; dir++) { | |
| if (gm_info->num_ref_frames[dir] > 0) | |
| compute_global_motion_for_references( | |
| cpi, gm_info->ref_buf, gm_info->reference_frames[dir], | |
| gm_info->num_ref_frames[dir], gm_info->num_src_corners, | |
| gm_info->src_corners, gm_info->src_buffer, params_by_motion, | |
| segment_map, gm_info->segment_map_w, gm_info->segment_map_h); | |
| } | |
| dealloc_global_motion_data(params_by_motion, segment_map); | |
| } | |
| static AOM_INLINE void reset_gm_stats(AV1_COMP *cpi) { | |
| for (int i = 0; i < FRAME_UPDATE_TYPES; i++) { | |
| cpi->valid_gm_model_found[i] = INT32_MAX; | |
| } | |
| } | |
| // Updates frame level stats related to global motion | |
| static AOM_INLINE void update_gm_stats(AV1_COMP *cpi) { | |
| const GF_GROUP *gf_group = &cpi->gf_group; | |
| FRAME_UPDATE_TYPE update_type = gf_group->update_type[gf_group->index]; | |
| int is_gm_present = 0; | |
| for (int frame = 0; frame < INTER_REFS_PER_FRAME; frame++) { | |
| if (cpi->common.global_motion[frame].wmtype != IDENTITY) { | |
| is_gm_present = 1; | |
| break; | |
| } | |
| } | |
| if (cpi->valid_gm_model_found[update_type] == INT32_MAX) { | |
| cpi->valid_gm_model_found[update_type] = is_gm_present; | |
| } else { | |
| cpi->valid_gm_model_found[update_type] |= is_gm_present; | |
| } | |
| } | |
| // Global motion estimation for the current frame is computed.This computation | |
| // happens once per frame and the winner motion model parameters are stored in | |
| // cm->cur_frame->global_motion. | |
| void av1_compute_global_motion_facade(AV1_COMP *cpi) { | |
| AV1_COMMON *const cm = &cpi->common; | |
| const GF_GROUP *gf_group = &cpi->gf_group; | |
| GlobalMotionInfo *const gm_info = &cpi->gm_info; | |
| // Reset `valid_gm_model_found` at the start of each GOP | |
| if (cpi->oxcf.tool_cfg.enable_global_motion && | |
| cpi->sf.gm_sf.disable_gm_search_based_on_stats && gf_group->index == 0) { | |
| reset_gm_stats(cpi); | |
| } | |
| if (cpi->common.current_frame.frame_type == INTER_FRAME && cpi->source && | |
| cpi->oxcf.tool_cfg.enable_global_motion && !gm_info->search_done) { | |
| setup_global_motion_info_params(cpi); | |
| if (cpi->mt_info.num_workers > 1) | |
| av1_global_motion_estimation_mt(cpi); | |
| else | |
| global_motion_estimation(cpi); | |
| #if CONFIG_IMPROVED_GLOBAL_MOTION | |
| // Once we have determined the best motion model for each ref frame, | |
| // choose the base parameters to minimize the total encoding cost | |
| pick_base_gm_params(cpi); | |
| #endif // CONFIG_IMPROVED_GLOBAL_MOTION | |
| // Check if the current frame has any valid global motion model across its | |
| // reference frames | |
| if (cpi->sf.gm_sf.disable_gm_search_based_on_stats) { | |
| update_gm_stats(cpi); | |
| } | |
| gm_info->search_done = 1; | |
| } | |
| memcpy(cm->cur_frame->global_motion, cm->global_motion, | |
| sizeof(cm->cur_frame->global_motion)); | |
| } |