| #include "src/motion_vector.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <memory> |
| |
| #include "src/utils/bit_mask_set.h" |
| #include "src/utils/common.h" |
| #include "src/utils/logging.h" |
| |
| namespace libgav1 { |
| namespace { |
| |
| constexpr int kMvBorder = 128; |
| constexpr int kProjectionMvClamp = 16383; |
| constexpr int kProjectionMvMaxVerticalOffset = 0; |
| constexpr int kProjectionMvMaxHorizontalOffset = 8; |
| constexpr int kInvalidMvValue = -32768; |
| |
| // Applies the sign of |sign_value| to |value| (and does so without a branch). |
| int ApplySign(int value, int sign_value) { |
| static_assert(sizeof(int) == 4, ""); |
| // The next three lines are the branch free equivalent of: |
| // return (sign_value > 0) ? value : -value; |
| const int a = sign_value >> 31; |
| const int b = value ^ a; |
| return b - a; |
| } |
| |
| // 7.10.2.10. |
| void LowerMvPrecision(const Tile::Block& block, int* const mv) { |
| assert(mv != nullptr); |
| if (block.tile.frame_header().allow_high_precision_mv) return; |
| for (int i = 0; i < 2; ++i) { |
| if (block.tile.frame_header().force_integer_mv != 0) { |
| mv[i] = ApplySign(MultiplyBy8(DivideBy8(std::abs(mv[i]) + 3)), mv[i]); |
| } else { |
| if ((mv[i] & 1) != 0) { |
| // The next line is equivalent to: |
| // if (mv[i] > 0) { --mv[i]; } else { ++mv[i]; } |
| mv[i] += ApplySign(-1, mv[i]); |
| } |
| } |
| } |
| } |
| |
| constexpr int16_t kDivisionLookup[32] = { |
| 0, 16384, 8192, 5461, 4096, 3276, 2730, 2340, 2048, 1820, 1638, |
| 1489, 1365, 1260, 1170, 1092, 1024, 963, 910, 862, 819, 780, |
| 744, 712, 682, 655, 630, 606, 585, 564, 546, 528}; |
| |
| // 7.9.3. |
| void GetMvProjection(const MotionVector& mv, int numerator, int denominator, |
| MotionVector* const projection_mv) { |
| denominator = std::min(denominator, static_cast<int>(kMaxFrameDistance)); |
| numerator = Clip3(numerator, -kMaxFrameDistance, kMaxFrameDistance); |
| for (int i = 0; i < 2; ++i) { |
| projection_mv->mv[i] = |
| Clip3(RightShiftWithRoundingSigned( |
| mv.mv[i] * numerator * kDivisionLookup[denominator], 14), |
| -kProjectionMvClamp, kProjectionMvClamp); |
| } |
| } |
| |
| // 7.9.3. (without the Clip3). |
| void GetMvProjectionNoClamp(const MotionVector& mv, int numerator, |
| int denominator, |
| MotionVector* const projection_mv) { |
| assert(std::abs(numerator) <= kMaxFrameDistance); |
| assert(denominator <= kMaxFrameDistance); |
| for (int i = 0; i < 2; ++i) { |
| projection_mv->mv[i] = RightShiftWithRoundingSigned( |
| mv.mv[i] * numerator * kDivisionLookup[denominator], 14); |
| } |
| } |
| |
| // 7.10.2.1. |
| void SetupGlobalMv(const Tile::Block& block, int index, |
| MotionVector* const mv) { |
| const auto& bp = block.parameters(); |
| ReferenceFrameType reference_type = bp.reference_frame[index]; |
| const auto& gm = block.tile.frame_header().global_motion[reference_type]; |
| GlobalMotionTransformationType global_motion_type = |
| (reference_type != kReferenceFrameIntra) |
| ? gm.type |
| : kNumGlobalMotionTransformationTypes; |
| if (reference_type == kReferenceFrameIntra || |
| global_motion_type == kGlobalMotionTransformationTypeIdentity) { |
| mv->mv[MotionVector::kRow] = 0; |
| mv->mv[MotionVector::kColumn] = 0; |
| return; |
| } |
| if (global_motion_type == kGlobalMotionTransformationTypeTranslation) { |
| for (int i = 0; i < 2; ++i) { |
| mv->mv[i] = gm.params[i] >> (kWarpedModelPrecisionBits - 3); |
| } |
| LowerMvPrecision(block, mv->mv); |
| return; |
| } |
| const int x = MultiplyBy4(block.column4x4) + |
| DivideBy2(kBlockWidthPixels[block.size]) - 1; |
| const int y = |
| MultiplyBy4(block.row4x4) + DivideBy2(kBlockHeightPixels[block.size]) - 1; |
| const int xc = (gm.params[2] - (1 << kWarpedModelPrecisionBits)) * x + |
| gm.params[3] * y + gm.params[0]; |
| const int yc = gm.params[4] * x + |
| (gm.params[5] - (1 << kWarpedModelPrecisionBits)) * y + |
| gm.params[1]; |
| if (block.tile.frame_header().allow_high_precision_mv) { |
| mv->mv[MotionVector::kRow] = |
| RightShiftWithRoundingSigned(yc, kWarpedModelPrecisionBits - 3); |
| mv->mv[MotionVector::kColumn] = |
| RightShiftWithRoundingSigned(xc, kWarpedModelPrecisionBits - 3); |
| } else { |
| mv->mv[MotionVector::kRow] = MultiplyBy2( |
| RightShiftWithRoundingSigned(yc, kWarpedModelPrecisionBits - 2)); |
| mv->mv[MotionVector::kColumn] = MultiplyBy2( |
| RightShiftWithRoundingSigned(xc, kWarpedModelPrecisionBits - 2)); |
| LowerMvPrecision(block, mv->mv); |
| } |
| } |
| |
| constexpr BitMaskSet kPredictionModeNewMvMask(kPredictionModeNewMv, |
| kPredictionModeNewNewMv, |
| kPredictionModeNearNewMv, |
| kPredictionModeNewNearMv, |
| kPredictionModeNearestNewMv, |
| kPredictionModeNewNearestMv); |
| |
| // 7.10.2.8. |
| void SearchStackSingle(const Tile::Block& block, int row, int column, int index, |
| int weight, MotionVector global_mv_candidate[2], |
| bool* const found_new_mv, bool* const found_match, |
| int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const auto& bp = block.tile.Parameters(row, column); |
| const PredictionMode candidate_mode = bp.y_mode; |
| const BlockSize candidate_size = bp.size; |
| MotionVector candidate_mv; |
| const auto global_motion_type = |
| block.tile.frame_header() |
| .global_motion[block.parameters().reference_frame[0]] |
| .type; |
| if (IsGlobalMvBlock(candidate_mode, global_motion_type, candidate_size)) { |
| candidate_mv = global_mv_candidate[0]; |
| } else { |
| candidate_mv = bp.mv[index]; |
| } |
| LowerMvPrecision(block, candidate_mv.mv); |
| *found_new_mv |= kPredictionModeNewMvMask.Contains(candidate_mode); |
| *found_match = true; |
| for (int i = 0; i < *num_mv_found; ++i) { |
| if (ref_mv_stack[i].mv[0] == candidate_mv) { |
| ref_mv_stack[i].weight += weight; |
| return; |
| } |
| } |
| if (*num_mv_found >= kMaxRefMvStackSize) return; |
| ref_mv_stack[*num_mv_found] = {{candidate_mv, {}}, weight}; |
| ++*num_mv_found; |
| } |
| |
| // 7.10.2.9. |
| void SearchStackCompound( |
| const Tile::Block& block, int row, int column, int weight, |
| MotionVector global_mv_candidate[2], bool* const found_new_mv, |
| bool* const found_match, int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const auto& bp = block.tile.Parameters(row, column); |
| const PredictionMode candidate_mode = bp.y_mode; |
| const BlockSize candidate_size = bp.size; |
| MotionVector candidate_mv[2]; |
| for (int i = 0; i < 2; ++i) { |
| const auto global_motion_type = |
| block.tile.frame_header() |
| .global_motion[block.parameters().reference_frame[i]] |
| .type; |
| if (IsGlobalMvBlock(candidate_mode, global_motion_type, candidate_size)) { |
| candidate_mv[i] = global_mv_candidate[i]; |
| } else { |
| candidate_mv[i] = bp.mv[i]; |
| } |
| LowerMvPrecision(block, candidate_mv[i].mv); |
| } |
| *found_new_mv |= kPredictionModeNewMvMask.Contains(candidate_mode); |
| *found_match = true; |
| for (int i = 0; i < *num_mv_found; ++i) { |
| if (ref_mv_stack[i].mv[0] == candidate_mv[0] && |
| ref_mv_stack[i].mv[1] == candidate_mv[1]) { |
| ref_mv_stack[i].weight += weight; |
| return; |
| } |
| } |
| if (*num_mv_found >= kMaxRefMvStackSize) return; |
| ref_mv_stack[*num_mv_found] = {{candidate_mv[0], candidate_mv[1]}, weight}; |
| ++*num_mv_found; |
| } |
| |
| // 7.10.2.7. |
| void AddReferenceMvCandidate( |
| const Tile::Block& block, int row, int column, bool is_compound, int weight, |
| MotionVector global_mv[2], bool* const found_new_mv, |
| bool* const found_match, int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const auto& bp = block.tile.Parameters(row, column); |
| if (!bp.is_inter) return; |
| if (is_compound) { |
| if (bp.reference_frame[0] == block.parameters().reference_frame[0] && |
| bp.reference_frame[1] == block.parameters().reference_frame[1]) { |
| SearchStackCompound(block, row, column, weight, global_mv, found_new_mv, |
| found_match, num_mv_found, ref_mv_stack); |
| } |
| return; |
| } |
| for (int i = 0; i < 2; ++i) { |
| if (bp.reference_frame[i] == block.parameters().reference_frame[0]) { |
| SearchStackSingle(block, row, column, i, weight, global_mv, found_new_mv, |
| found_match, num_mv_found, ref_mv_stack); |
| } |
| } |
| } |
| |
| // 7.10.2.2. |
| void ScanRow(const Tile::Block& block, int delta_row, bool is_compound, |
| MotionVector global_mv[2], bool* const found_new_mv, |
| bool* const found_match, int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int block_width4x4 = kNum4x4BlocksWide[block.size]; |
| int delta_column = 0; |
| if (std::abs(delta_row) > 1) { |
| delta_row += block.row4x4 & 1; |
| delta_column = 1 - (block.column4x4 & 1); |
| } |
| const int end_mv_column = |
| block.column4x4 + delta_column + |
| std::min({block_width4x4, |
| block.tile.frame_header().columns4x4 - block.column4x4, 16}); |
| const int mv_row = block.row4x4 + delta_row; |
| for (int step, mv_column = block.column4x4 + delta_column; |
| mv_column < end_mv_column; mv_column += step) { |
| if (!block.tile.IsInside(mv_row, mv_column)) break; |
| step = std::min( |
| block_width4x4, |
| static_cast<int>( |
| kNum4x4BlocksWide[block.tile.Parameters(mv_row, mv_column).size])); |
| if (std::abs(delta_row) > 1) step = std::max(step, 2); |
| if (block_width4x4 >= 16) step = std::max(step, 4); |
| AddReferenceMvCandidate(block, mv_row, mv_column, is_compound, |
| MultiplyBy2(step), global_mv, found_new_mv, |
| found_match, num_mv_found, ref_mv_stack); |
| } |
| } |
| |
| // 7.10.2.3. |
| void ScanColumn(const Tile::Block& block, int delta_column, bool is_compound, |
| MotionVector global_mv[2], bool* const found_new_mv, |
| bool* const found_match, int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int block_height4x4 = kNum4x4BlocksHigh[block.size]; |
| int delta_row = 0; |
| if (std::abs(delta_column) > 1) { |
| delta_row = 1 - (block.row4x4 & 1); |
| delta_column += block.column4x4 & 1; |
| } |
| const int end_mv_row = |
| block.row4x4 + delta_row + |
| std::min({block_height4x4, |
| block.tile.frame_header().rows4x4 - block.row4x4, 16}); |
| const int mv_column = block.column4x4 + delta_column; |
| for (int step, mv_row = block.row4x4 + delta_row; mv_row < end_mv_row; |
| mv_row += step) { |
| if (!block.tile.IsInside(mv_row, mv_column)) break; |
| step = std::min( |
| block_height4x4, |
| static_cast<int>( |
| kNum4x4BlocksHigh[block.tile.Parameters(mv_row, mv_column).size])); |
| if (std::abs(delta_column) > 1) step = std::max(step, 2); |
| if (block_height4x4 >= 16) step = std::max(step, 4); |
| AddReferenceMvCandidate(block, mv_row, mv_column, is_compound, |
| MultiplyBy2(step), global_mv, found_new_mv, |
| found_match, num_mv_found, ref_mv_stack); |
| } |
| } |
| |
| // 7.10.2.4. |
| void ScanPoint(const Tile::Block& block, int delta_row, int delta_column, |
| bool is_compound, MotionVector global_mv[2], |
| bool* const found_new_mv, bool* const found_match, |
| int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int mv_row = block.row4x4 + delta_row; |
| const int mv_column = block.column4x4 + delta_column; |
| if (!block.tile.IsInside(mv_row, mv_column) || |
| !block.tile.HasParameters(mv_row, mv_column) || |
| block.tile.Parameters(mv_row, mv_column).reference_frame[0] == |
| kReferenceFrameNone) { |
| return; |
| } |
| AddReferenceMvCandidate(block, mv_row, mv_column, is_compound, 4, global_mv, |
| found_new_mv, found_match, num_mv_found, |
| ref_mv_stack); |
| } |
| |
| // 7.10.2.6. |
| // |
| // The |zero_mv_context| output parameter may be null. If |zero_mv_context| is |
| // not null, the function may set |*zero_mv_context|. |
| void AddTemporalReferenceMvCandidate( |
| const Tile::Block& block, int delta_row, int delta_column, bool is_compound, |
| MotionVector global_mv[2], |
| const Array2D<TemporalMotionVector>& motion_field_mv, |
| int* const zero_mv_context, int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int mv_row = (block.row4x4 + delta_row) | 1; |
| const int mv_column = (block.column4x4 + delta_column) | 1; |
| if (!block.tile.IsInside(mv_row, mv_column)) return; |
| const int x8 = mv_column >> 1; |
| const int y8 = mv_row >> 1; |
| if (zero_mv_context != nullptr && delta_row == 0 && delta_column == 0) { |
| *zero_mv_context = 1; |
| } |
| const auto& bp = block.parameters(); |
| const TemporalMotionVector& temporal_mv = motion_field_mv[y8][x8]; |
| if (temporal_mv.mv.mv[0] == kInvalidMvValue) return; |
| if (is_compound) { |
| MotionVector candidate_mv[2]; |
| for (int i = 0; i < 2; ++i) { |
| const int reference_offset = GetRelativeDistance( |
| block.tile.frame_header().order_hint, |
| block.tile.current_frame().order_hint(bp.reference_frame[i]), |
| block.tile.sequence_header().enable_order_hint, |
| block.tile.sequence_header().order_hint_bits); |
| GetMvProjection(temporal_mv.mv, reference_offset, |
| temporal_mv.reference_offset, &candidate_mv[i]); |
| LowerMvPrecision(block, candidate_mv[i].mv); |
| } |
| if (zero_mv_context != nullptr && delta_row == 0 && delta_column == 0) { |
| *zero_mv_context = static_cast<int>( |
| std::abs(candidate_mv[0].mv[0] - global_mv[0].mv[0]) >= 16 || |
| std::abs(candidate_mv[0].mv[1] - global_mv[0].mv[1]) >= 16 || |
| std::abs(candidate_mv[1].mv[0] - global_mv[1].mv[0]) >= 16 || |
| std::abs(candidate_mv[1].mv[1] - global_mv[1].mv[1]) >= 16); |
| } |
| for (int i = 0; i < *num_mv_found; ++i) { |
| if (ref_mv_stack[i].mv[0] == candidate_mv[0] && |
| ref_mv_stack[i].mv[1] == candidate_mv[1]) { |
| ref_mv_stack[i].weight += 2; |
| return; |
| } |
| } |
| if (*num_mv_found >= kMaxRefMvStackSize) return; |
| ref_mv_stack[*num_mv_found] = {{candidate_mv[0], candidate_mv[1]}, 2}; |
| ++*num_mv_found; |
| return; |
| } |
| assert(!is_compound); |
| MotionVector candidate_mv; |
| const int reference_offset = GetRelativeDistance( |
| block.tile.frame_header().order_hint, |
| block.tile.current_frame().order_hint(bp.reference_frame[0]), |
| block.tile.sequence_header().enable_order_hint, |
| block.tile.sequence_header().order_hint_bits); |
| GetMvProjection(temporal_mv.mv, reference_offset, |
| temporal_mv.reference_offset, &candidate_mv); |
| LowerMvPrecision(block, candidate_mv.mv); |
| if (zero_mv_context != nullptr && delta_row == 0 && delta_column == 0) { |
| *zero_mv_context = static_cast<int>( |
| std::abs(candidate_mv.mv[0] - global_mv[0].mv[0]) >= 16 || |
| std::abs(candidate_mv.mv[1] - global_mv[0].mv[1]) >= 16); |
| } |
| for (int i = 0; i < *num_mv_found; ++i) { |
| if (ref_mv_stack[i].mv[0] == candidate_mv) { |
| ref_mv_stack[i].weight += 2; |
| return; |
| } |
| } |
| if (*num_mv_found >= kMaxRefMvStackSize) return; |
| ref_mv_stack[*num_mv_found] = {{candidate_mv, {}}, 2}; |
| ++*num_mv_found; |
| } |
| |
| // Part of 7.10.2.5. |
| bool IsWithinTheSame64x64Block(const Tile::Block& block, int delta_row, |
| int delta_column) { |
| const int row = (block.row4x4 & 15) + delta_row; |
| const int column = (block.column4x4 & 15) + delta_column; |
| return row >= 0 && row < 16 && column >= 0 && column < 16; |
| } |
| |
| // 7.10.2.5. |
| // |
| // The |zero_mv_context| output parameter may be null. If |zero_mv_context| is |
| // not null, the function may set |*zero_mv_context|. |
| void TemporalScan(const Tile::Block& block, bool is_compound, |
| MotionVector global_mv[2], |
| const Array2D<TemporalMotionVector>& motion_field_mv, |
| int* const zero_mv_context, int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int block_width4x4 = kNum4x4BlocksWide[block.size]; |
| const int block_height4x4 = kNum4x4BlocksHigh[block.size]; |
| const int step_w = (block_width4x4 >= 16) ? 4 : 2; |
| const int step_h = (block_height4x4 >= 16) ? 4 : 2; |
| for (int row = 0; row < std::min(block_height4x4, 16); row += step_h) { |
| for (int column = 0; column < std::min(block_width4x4, 16); |
| column += step_w) { |
| AddTemporalReferenceMvCandidate( |
| block, row, column, is_compound, global_mv, motion_field_mv, |
| zero_mv_context, num_mv_found, ref_mv_stack); |
| } |
| } |
| if (block_height4x4 >= kNum4x4BlocksHigh[kBlock8x8] && |
| block_height4x4 < kNum4x4BlocksHigh[kBlock64x64] && |
| block_width4x4 >= kNum4x4BlocksWide[kBlock8x8] && |
| block_width4x4 < kNum4x4BlocksWide[kBlock64x64]) { |
| const int temporal_sample_positions[3][2] = { |
| {block_height4x4, -2}, |
| {block_height4x4, block_width4x4}, |
| {block_height4x4 - 2, block_width4x4}}; |
| for (const auto& temporal_sample_position : temporal_sample_positions) { |
| const int row = temporal_sample_position[0]; |
| const int column = temporal_sample_position[1]; |
| if (!IsWithinTheSame64x64Block(block, row, column)) continue; |
| AddTemporalReferenceMvCandidate( |
| block, row, column, is_compound, global_mv, motion_field_mv, |
| zero_mv_context, num_mv_found, ref_mv_stack); |
| } |
| } |
| } |
| |
| // Part of 7.10.2.13. |
| void AddExtraCompoundMvCandidate( |
| const Tile::Block& block, int mv_row, int mv_column, |
| const std::array<bool, kNumReferenceFrameTypes>& reference_frame_sign_bias, |
| int* const ref_id_count, MotionVector ref_id[2][2], |
| int* const ref_diff_count, MotionVector ref_diff[2][2]) { |
| const auto& bp = block.tile.Parameters(mv_row, mv_column); |
| for (int i = 0; i < 2; ++i) { |
| const ReferenceFrameType candidate_reference_frame = bp.reference_frame[i]; |
| if (candidate_reference_frame <= kReferenceFrameIntra) continue; |
| for (int j = 0; j < 2; ++j) { |
| MotionVector candidate_mv = bp.mv[i]; |
| const ReferenceFrameType block_reference_frame = |
| block.parameters().reference_frame[j]; |
| if (candidate_reference_frame == block_reference_frame && |
| ref_id_count[j] < 2) { |
| ref_id[j][ref_id_count[j]] = candidate_mv; |
| ++ref_id_count[j]; |
| } else if (ref_diff_count[j] < 2) { |
| if (reference_frame_sign_bias[candidate_reference_frame] != |
| reference_frame_sign_bias[block_reference_frame]) { |
| candidate_mv.mv[0] *= -1; |
| candidate_mv.mv[1] *= -1; |
| } |
| ref_diff[j][ref_diff_count[j]] = candidate_mv; |
| ++ref_diff_count[j]; |
| } |
| } |
| } |
| } |
| |
| // Part of 7.10.2.13. |
| void AddExtraSingleMvCandidate( |
| const Tile::Block& block, int mv_row, int mv_column, |
| const std::array<bool, kNumReferenceFrameTypes>& reference_frame_sign_bias, |
| int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const auto& bp = block.tile.Parameters(mv_row, mv_column); |
| const ReferenceFrameType block_reference_frame = |
| block.parameters().reference_frame[0]; |
| for (int i = 0; i < 2; ++i) { |
| const ReferenceFrameType candidate_reference_frame = bp.reference_frame[i]; |
| if (candidate_reference_frame <= kReferenceFrameIntra) continue; |
| MotionVector candidate_mv = bp.mv[i]; |
| if (reference_frame_sign_bias[candidate_reference_frame] != |
| reference_frame_sign_bias[block_reference_frame]) { |
| candidate_mv.mv[0] *= -1; |
| candidate_mv.mv[1] *= -1; |
| } |
| int j = 0; |
| for (; j < *num_mv_found; ++j) { |
| if (candidate_mv == ref_mv_stack[j].mv[0]) { |
| break; |
| } |
| } |
| if (j == *num_mv_found) { |
| ref_mv_stack[*num_mv_found] = {{candidate_mv, {}}, 2}; |
| ++*num_mv_found; |
| } |
| } |
| } |
| |
| // 7.10.2.12. |
| void ExtraSearch( |
| const Tile::Block& block, bool is_compound, MotionVector global_mv[2], |
| const std::array<bool, kNumReferenceFrameTypes>& reference_frame_sign_bias, |
| int* const num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int num4x4 = |
| std::min({static_cast<int>(kNum4x4BlocksWide[block.size]), |
| block.tile.frame_header().columns4x4 - block.column4x4, |
| static_cast<int>(kNum4x4BlocksHigh[block.size]), |
| block.tile.frame_header().rows4x4 - block.row4x4, 16}); |
| int ref_id_count[2] = {}; |
| MotionVector ref_id[2][2] = {}; |
| int ref_diff_count[2] = {}; |
| MotionVector ref_diff[2][2] = {}; |
| for (int pass = 0; pass < 2 && *num_mv_found < 2; ++pass) { |
| for (int i = 0; i < num4x4;) { |
| const int mv_row = block.row4x4 + ((pass == 0) ? -1 : i); |
| const int mv_column = block.column4x4 + ((pass == 0) ? i : -1); |
| if (!block.tile.IsInside(mv_row, mv_column)) break; |
| if (is_compound) { |
| AddExtraCompoundMvCandidate(block, mv_row, mv_column, |
| reference_frame_sign_bias, ref_id_count, |
| ref_id, ref_diff_count, ref_diff); |
| } else { |
| AddExtraSingleMvCandidate(block, mv_row, mv_column, |
| reference_frame_sign_bias, num_mv_found, |
| ref_mv_stack); |
| if (*num_mv_found >= 2) break; |
| } |
| const auto& bp = block.tile.Parameters(mv_row, mv_column); |
| i += |
| (pass == 0) ? kNum4x4BlocksWide[bp.size] : kNum4x4BlocksHigh[bp.size]; |
| } |
| } |
| if (is_compound) { |
| // Merge compound mode extra search into mv stack. |
| MotionVector combined_mvs[2][2] = {}; |
| for (int i = 0; i < 2; ++i) { |
| int count = 0; |
| assert(ref_id_count[i] <= 2); |
| for (int j = 0; j < ref_id_count[i]; ++j, ++count) { |
| combined_mvs[count][i] = ref_id[i][j]; |
| } |
| for (int j = 0; j < ref_diff_count[i] && count < 2; ++j, ++count) { |
| combined_mvs[count][i] = ref_diff[i][j]; |
| } |
| for (; count < 2; ++count) { |
| combined_mvs[count][i] = global_mv[i]; |
| } |
| } |
| if (*num_mv_found == 1) { |
| if (combined_mvs[0][0] == ref_mv_stack[0].mv[0] && |
| combined_mvs[0][1] == ref_mv_stack[0].mv[1]) { |
| ref_mv_stack[1] = {{combined_mvs[1][0], combined_mvs[1][1]}, 2}; |
| } else { |
| ref_mv_stack[1] = {{combined_mvs[0][0], combined_mvs[0][1]}, 2}; |
| } |
| ++*num_mv_found; |
| } else { |
| assert(*num_mv_found == 0); |
| *num_mv_found = 2; |
| for (int i = 0; i < 2; ++i) { |
| ref_mv_stack[i] = {{combined_mvs[i][0], combined_mvs[i][1]}, 2}; |
| } |
| } |
| } else { |
| // single prediction mode |
| for (int i = *num_mv_found; i < 2; ++i) { |
| ref_mv_stack[i].mv[0] = global_mv[0]; |
| } |
| } |
| } |
| |
| // 7.10.2.14 (part 1). |
| void ClampMotionVectors( |
| const Tile::Block& block, bool is_compound, int num_mv_found, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize]) { |
| const int block_height4x4 = kNum4x4BlocksHigh[block.size]; |
| const int block_width4x4 = kNum4x4BlocksWide[block.size]; |
| const int row_border = kMvBorder + MultiplyBy32(block_height4x4); |
| const int column_border = kMvBorder + MultiplyBy32(block_width4x4); |
| for (int i = 0; i < num_mv_found; ++i) { |
| for (int mv_index = 0; mv_index < 1 + static_cast<int>(is_compound); |
| ++mv_index) { |
| // Clamp row (5.11.53). |
| const int macroblocks_to_top_edge = -MultiplyBy32(block.row4x4); |
| const int macroblocks_to_bottom_edge = MultiplyBy32( |
| block.tile.frame_header().rows4x4 - block_height4x4 - block.row4x4); |
| ref_mv_stack[i].mv[mv_index].mv[MotionVector::kRow] = |
| Clip3(ref_mv_stack[i].mv[mv_index].mv[MotionVector::kRow], |
| macroblocks_to_top_edge - row_border, |
| macroblocks_to_bottom_edge + row_border); |
| // Clamp column (5.11.54). |
| const int macroblocks_to_left_edge = -MultiplyBy32(block.column4x4); |
| const int macroblocks_to_right_edge = |
| MultiplyBy32(block.tile.frame_header().columns4x4 - block_width4x4 - |
| block.column4x4); |
| ref_mv_stack[i].mv[mv_index].mv[MotionVector::kColumn] = |
| Clip3(ref_mv_stack[i].mv[mv_index].mv[MotionVector::kColumn], |
| macroblocks_to_left_edge - column_border, |
| macroblocks_to_right_edge + column_border); |
| } |
| } |
| } |
| |
| // 7.10.2.14 (part 2). |
| void ComputeContexts(bool found_new_mv, int nearest_matches, int total_matches, |
| int* new_mv_context, int* reference_mv_context) { |
| switch (nearest_matches) { |
| case 0: |
| *new_mv_context = std::min(total_matches, 1); |
| *reference_mv_context = total_matches; |
| break; |
| case 1: |
| *new_mv_context = 3 - static_cast<int>(found_new_mv); |
| *reference_mv_context = 2 + total_matches; |
| break; |
| default: |
| *new_mv_context = 5 - static_cast<int>(found_new_mv); |
| *reference_mv_context = 5; |
| break; |
| } |
| } |
| |
| // 7.10.4.2. |
| void AddSample(const Tile::Block& block, int delta_row, int delta_column, |
| int* const num_warp_samples, int* const num_samples_scanned, |
| int candidates[kMaxLeastSquaresSamples][4]) { |
| if (*num_samples_scanned >= kMaxLeastSquaresSamples) return; |
| const int mv_row = block.row4x4 + delta_row; |
| const int mv_column = block.column4x4 + delta_column; |
| if (!block.tile.IsInside(mv_row, mv_column) || |
| !block.tile.HasParameters(mv_row, mv_column)) { |
| return; |
| } |
| const BlockParameters& bp = block.tile.Parameters(mv_row, mv_column); |
| if (bp.reference_frame[0] != block.parameters().reference_frame[0] || |
| bp.reference_frame[1] != kReferenceFrameNone) { |
| return; |
| } |
| ++*num_samples_scanned; |
| const int candidate_height4x4 = kNum4x4BlocksHigh[bp.size]; |
| const int candidate_row = mv_row & ~(candidate_height4x4 - 1); |
| const int candidate_width4x4 = kNum4x4BlocksWide[bp.size]; |
| const int candidate_column = mv_column & ~(candidate_width4x4 - 1); |
| const BlockParameters& candidate_bp = |
| block.tile.Parameters(candidate_row, candidate_column); |
| const int threshold = Clip3( |
| std::max(kBlockWidthPixels[block.size], kBlockHeightPixels[block.size]), |
| 16, 112); |
| const int mv_diff_row = |
| std::abs(candidate_bp.mv[0].mv[0] - block.parameters().mv[0].mv[0]); |
| const int mv_diff_column = |
| std::abs(candidate_bp.mv[0].mv[1] - block.parameters().mv[0].mv[1]); |
| const bool is_valid = mv_diff_row + mv_diff_column <= threshold; |
| if (!is_valid && *num_samples_scanned > 1) { |
| return; |
| } |
| const int mid_y = |
| MultiplyBy4(candidate_row) + MultiplyBy2(candidate_height4x4) - 1; |
| const int mid_x = |
| MultiplyBy4(candidate_column) + MultiplyBy2(candidate_width4x4) - 1; |
| candidates[*num_warp_samples][0] = MultiplyBy8(mid_y); |
| candidates[*num_warp_samples][1] = MultiplyBy8(mid_x); |
| candidates[*num_warp_samples][2] = |
| MultiplyBy8(mid_y) + candidate_bp.mv[0].mv[0]; |
| candidates[*num_warp_samples][3] = |
| MultiplyBy8(mid_x) + candidate_bp.mv[0].mv[1]; |
| if (is_valid) ++*num_warp_samples; |
| } |
| |
| // Comparator used for sorting candidate motion vectors in descending order of |
| // their weights (as specified in 7.10.2.11). |
| bool CompareCandidateMotionVectors(const CandidateMotionVector& lhs, |
| const CandidateMotionVector& rhs) { |
| return lhs.weight > rhs.weight; |
| } |
| |
| // Part of 7.9.4. |
| bool Project(int value, int delta, int dst_sign, int max_value, int max_offset, |
| int* const projected_value) { |
| const int base_value = value & ~7; |
| const int offset = (delta >= 0) ? DivideBy64(delta) : -DivideBy64(-delta); |
| value += ApplySign(offset, dst_sign); |
| if (value < 0 || value >= max_value || value < base_value - max_offset || |
| value >= base_value + 8 + max_offset) { |
| return false; |
| } |
| *projected_value = value; |
| return true; |
| } |
| |
| // 7.9.4. |
| bool GetBlockPosition(int x8, int y8, int dst_sign, int rows4x4, int columns4x4, |
| const MotionVector& projection_mv, int* const position_y8, |
| int* const position_x8) { |
| return Project(y8, projection_mv.mv[0], dst_sign, DivideBy2(rows4x4), |
| kProjectionMvMaxVerticalOffset, position_y8) && |
| Project(x8, projection_mv.mv[1], dst_sign, DivideBy2(columns4x4), |
| kProjectionMvMaxHorizontalOffset, position_x8); |
| } |
| |
| // 7.9.2. |
| bool MotionFieldProjection( |
| ReferenceFrameType source, int dst_sign, |
| const ObuSequenceHeader& sequence_header, |
| const ObuFrameHeader& frame_header, const RefCountedBuffer& current_frame, |
| const std::array<RefCountedBufferPtr, kNumReferenceFrameTypes>& |
| reference_frames, |
| Array2D<TemporalMotionVector>* const motion_field_mv, int y8_start, |
| int y8_end, int x8_start, int x8_end) { |
| const int source_index = |
| frame_header.reference_frame_index[source - kReferenceFrameLast]; |
| auto* const source_frame = reference_frames[source_index].get(); |
| assert(source_frame != nullptr); |
| if (source_frame->rows4x4() != frame_header.rows4x4 || |
| source_frame->columns4x4() != frame_header.columns4x4 || |
| IsIntraFrame(source_frame->frame_type())) { |
| return false; |
| } |
| const int reference_to_current_with_sign = |
| GetRelativeDistance( |
| current_frame.order_hint(source), frame_header.order_hint, |
| sequence_header.enable_order_hint, sequence_header.order_hint_bits) * |
| dst_sign; |
| if (std::abs(reference_to_current_with_sign) > kMaxFrameDistance) return true; |
| // Index 0 of these two arrays are never used. |
| int reference_offsets[kNumReferenceFrameTypes]; |
| bool skip_reference[kNumReferenceFrameTypes]; |
| for (int source_reference_type = kReferenceFrameLast; |
| source_reference_type <= kNumInterReferenceFrameTypes; |
| ++source_reference_type) { |
| const int reference_offset = GetRelativeDistance( |
| current_frame.order_hint(source), |
| source_frame->order_hint( |
| static_cast<ReferenceFrameType>(source_reference_type)), |
| sequence_header.enable_order_hint, sequence_header.order_hint_bits); |
| skip_reference[source_reference_type] = |
| std::abs(reference_offset) > kMaxFrameDistance || reference_offset <= 0; |
| reference_offsets[source_reference_type] = reference_offset; |
| } |
| // The column range has to be offset by kProjectionMvMaxHorizontalOffset since |
| // coordinates in that range could end up being position_x8 because of |
| // projection. |
| const int adjusted_x8_start = |
| std::max(x8_start - kProjectionMvMaxHorizontalOffset, 0); |
| const int adjusted_x8_end = |
| std::min(x8_end + kProjectionMvMaxHorizontalOffset, |
| DivideBy2(frame_header.columns4x4)); |
| for (int y8 = y8_start; y8 < y8_end; ++y8) { |
| for (int x8 = adjusted_x8_start; x8 < adjusted_x8_end; ++x8) { |
| const ReferenceFrameType source_reference = |
| *source_frame->motion_field_reference_frame(y8, x8); |
| if (source_reference <= kReferenceFrameIntra || |
| skip_reference[source_reference]) { |
| continue; |
| } |
| const int reference_offset = reference_offsets[source_reference]; |
| const MotionVector& mv = *source_frame->motion_field_mv(y8, x8); |
| MotionVector projection_mv; |
| GetMvProjectionNoClamp(mv, reference_to_current_with_sign, |
| reference_offset, &projection_mv); |
| int position_y8; |
| int position_x8; |
| if (!GetBlockPosition(x8, y8, dst_sign, frame_header.rows4x4, |
| frame_header.columns4x4, projection_mv, |
| &position_y8, &position_x8) || |
| position_x8 < x8_start || position_x8 >= x8_end) { |
| // Do not update the motion vector if the block position is not valid or |
| // if position_x8 is outside the current range of x8_start and x8_end. |
| // Note that position_y8 will always be within the range of y8_start and |
| // y8_end. |
| continue; |
| } |
| TemporalMotionVector& temporal_mv = |
| (*motion_field_mv)[position_y8][position_x8]; |
| temporal_mv.mv = mv; |
| temporal_mv.reference_offset = reference_offset; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| void FindMvStack( |
| const Tile::Block& block, bool is_compound, |
| const std::array<bool, kNumReferenceFrameTypes>& reference_frame_sign_bias, |
| const Array2D<TemporalMotionVector>& motion_field_mv, |
| CandidateMotionVector ref_mv_stack[kMaxRefMvStackSize], |
| int* const num_mv_found, MvContexts* const contexts, |
| MotionVector global_mv[2]) { |
| const int block_width4x4 = kNum4x4BlocksWide[block.size]; |
| const int block_height4x4 = kNum4x4BlocksHigh[block.size]; |
| SetupGlobalMv(block, 0, &global_mv[0]); |
| if (is_compound) SetupGlobalMv(block, 1, &global_mv[1]); |
| bool found_new_mv = false; |
| bool found_row_match = false; |
| *num_mv_found = 0; |
| ScanRow(block, -1, is_compound, global_mv, &found_new_mv, &found_row_match, |
| num_mv_found, ref_mv_stack); |
| bool found_column_match = false; |
| ScanColumn(block, -1, is_compound, global_mv, &found_new_mv, |
| &found_column_match, num_mv_found, ref_mv_stack); |
| if (std::max(block_width4x4, block_height4x4) <= 16) { |
| ScanPoint(block, -1, block_width4x4, is_compound, global_mv, &found_new_mv, |
| &found_row_match, num_mv_found, ref_mv_stack); |
| } |
| const int nearest_matches = |
| static_cast<int>(found_row_match) + static_cast<int>(found_column_match); |
| const int nearest_mv_count = *num_mv_found; |
| for (int i = 0; i < nearest_mv_count; ++i) { |
| ref_mv_stack[i].weight += kExtraWeightForNearestMvs; |
| } |
| if (contexts != nullptr) contexts->zero_mv = 0; |
| if (block.tile.frame_header().use_ref_frame_mvs) { |
| TemporalScan(block, is_compound, global_mv, motion_field_mv, |
| (contexts != nullptr) ? &contexts->zero_mv : nullptr, |
| num_mv_found, ref_mv_stack); |
| } |
| bool dummy_bool = false; |
| ScanPoint(block, -1, -1, is_compound, global_mv, &dummy_bool, |
| &found_row_match, num_mv_found, ref_mv_stack); |
| const int deltas[2] = {-3, -5}; |
| for (int i = 0; i < 2; ++i) { |
| if (i == 0 || block_height4x4 > 1) { |
| ScanRow(block, deltas[i], is_compound, global_mv, &dummy_bool, |
| &found_row_match, num_mv_found, ref_mv_stack); |
| } |
| if (i == 0 || block_width4x4 > 1) { |
| ScanColumn(block, deltas[i], is_compound, global_mv, &dummy_bool, |
| &found_column_match, num_mv_found, ref_mv_stack); |
| } |
| } |
| std::stable_sort(&ref_mv_stack[0], &ref_mv_stack[nearest_mv_count], |
| CompareCandidateMotionVectors); |
| std::stable_sort(&ref_mv_stack[nearest_mv_count], |
| &ref_mv_stack[*num_mv_found], CompareCandidateMotionVectors); |
| if (*num_mv_found < 2) { |
| ExtraSearch(block, is_compound, global_mv, reference_frame_sign_bias, |
| num_mv_found, ref_mv_stack); |
| } |
| const int total_matches = |
| static_cast<int>(found_row_match) + static_cast<int>(found_column_match); |
| if (contexts != nullptr) { |
| ComputeContexts(found_new_mv, nearest_matches, total_matches, |
| &contexts->new_mv, &contexts->reference_mv); |
| } |
| ClampMotionVectors(block, is_compound, *num_mv_found, ref_mv_stack); |
| } |
| |
| void FindWarpSamples(const Tile::Block& block, int* const num_warp_samples, |
| int* const num_samples_scanned, |
| int candidates[kMaxLeastSquaresSamples][4]) { |
| bool top_left = true; |
| bool top_right = true; |
| const int block_width4x4 = kNum4x4BlocksWide[block.size]; |
| const int block_height4x4 = kNum4x4BlocksHigh[block.size]; |
| int step = 1; |
| if (block.top_available) { |
| BlockSize source_size = |
| block.tile.Parameters(block.row4x4 - 1, block.column4x4).size; |
| const int source_width4x4 = kNum4x4BlocksWide[source_size]; |
| if (block_width4x4 <= source_width4x4) { |
| // The & here is equivalent to % since source_width4x4 is a power of |
| // two. |
| const int column_offset = -(block.column4x4 & (source_width4x4 - 1)); |
| if (column_offset < 0) top_left = false; |
| if (column_offset + source_width4x4 > block_width4x4) top_right = false; |
| AddSample(block, -1, 0, num_warp_samples, num_samples_scanned, |
| candidates); |
| } else { |
| for (int i = 0; |
| i < std::min(block_width4x4, |
| block.tile.frame_header().columns4x4 - block.column4x4); |
| i += step) { |
| source_size = |
| block.tile.Parameters(block.row4x4 - 1, block.column4x4 + i).size; |
| step = std::min(block_width4x4, |
| static_cast<int>(kNum4x4BlocksWide[source_size])); |
| AddSample(block, -1, i, num_warp_samples, num_samples_scanned, |
| candidates); |
| } |
| } |
| } |
| if (block.left_available) { |
| BlockSize source_size = |
| block.tile.Parameters(block.row4x4, block.column4x4 - 1).size; |
| const int source_height4x4 = kNum4x4BlocksHigh[source_size]; |
| if (block_height4x4 <= source_height4x4) { |
| const int row_offset = -(block.row4x4 & (source_height4x4 - 1)); |
| if (row_offset < 0) top_left = false; |
| AddSample(block, 0, -1, num_warp_samples, num_samples_scanned, |
| candidates); |
| } else { |
| for (int i = 0; |
| i < std::min(block_height4x4, |
| block.tile.frame_header().rows4x4 - block.row4x4); |
| i += step) { |
| source_size = |
| block.tile.Parameters(block.row4x4 + i, block.column4x4 - 1).size; |
| step = std::min(block_height4x4, |
| static_cast<int>(kNum4x4BlocksHigh[source_size])); |
| AddSample(block, i, -1, num_warp_samples, num_samples_scanned, |
| candidates); |
| } |
| } |
| } |
| if (top_left) { |
| AddSample(block, -1, -1, num_warp_samples, num_samples_scanned, candidates); |
| } |
| if (top_right && std::max(block_width4x4, block_height4x4) <= 16) { |
| AddSample(block, -1, block_width4x4, num_warp_samples, num_samples_scanned, |
| candidates); |
| } |
| if (*num_warp_samples == 0 && *num_samples_scanned > 0) *num_warp_samples = 1; |
| } |
| |
| void SetupMotionField( |
| const ObuSequenceHeader& sequence_header, |
| const ObuFrameHeader& frame_header, const RefCountedBuffer& current_frame, |
| const std::array<RefCountedBufferPtr, kNumReferenceFrameTypes>& |
| reference_frames, |
| Array2D<TemporalMotionVector>* const motion_field_mv, int row4x4_start, |
| int row4x4_end, int column4x4_start, int column4x4_end) { |
| assert(frame_header.use_ref_frame_mvs); |
| assert(sequence_header.enable_order_hint); |
| const int y8_start = DivideBy2(row4x4_start); |
| const int y8_end = DivideBy2(std::min(row4x4_end, frame_header.rows4x4)); |
| const int x8_start = DivideBy2(column4x4_start); |
| const int x8_end = |
| std::min(DivideBy2(column4x4_end), DivideBy2(frame_header.columns4x4)); |
| for (int y8 = y8_start; y8 < y8_end; ++y8) { |
| for (int x8 = x8_start; x8 < x8_end; ++x8) { |
| (*motion_field_mv)[y8][x8].mv.mv[0] = kInvalidMvValue; |
| } |
| } |
| const int current_gold_order_hint = |
| current_frame.order_hint(kReferenceFrameGolden); |
| const int last_index = frame_header.reference_frame_index[0]; |
| const int last_alternate_order_hint = |
| reference_frames[last_index]->order_hint(kReferenceFrameAlternate); |
| if (last_alternate_order_hint != current_gold_order_hint) { |
| MotionFieldProjection(kReferenceFrameLast, -1, sequence_header, |
| frame_header, current_frame, reference_frames, |
| motion_field_mv, y8_start, y8_end, x8_start, x8_end); |
| } |
| int ref_stamp = 1; |
| if (GetRelativeDistance(current_frame.order_hint(kReferenceFrameBackward), |
| frame_header.order_hint, |
| sequence_header.enable_order_hint, |
| sequence_header.order_hint_bits) > 0 && |
| MotionFieldProjection(kReferenceFrameBackward, 1, sequence_header, |
| frame_header, current_frame, reference_frames, |
| motion_field_mv, y8_start, y8_end, x8_start, |
| x8_end)) { |
| --ref_stamp; |
| } |
| if (GetRelativeDistance(current_frame.order_hint(kReferenceFrameAlternate2), |
| frame_header.order_hint, |
| sequence_header.enable_order_hint, |
| sequence_header.order_hint_bits) > 0 && |
| MotionFieldProjection(kReferenceFrameAlternate2, 1, sequence_header, |
| frame_header, current_frame, reference_frames, |
| motion_field_mv, y8_start, y8_end, x8_start, |
| x8_end)) { |
| --ref_stamp; |
| } |
| if (ref_stamp >= 0 && |
| GetRelativeDistance(current_frame.order_hint(kReferenceFrameAlternate), |
| frame_header.order_hint, |
| sequence_header.enable_order_hint, |
| sequence_header.order_hint_bits) > 0 && |
| MotionFieldProjection(kReferenceFrameAlternate, 1, sequence_header, |
| frame_header, current_frame, reference_frames, |
| motion_field_mv, y8_start, y8_end, x8_start, |
| x8_end)) { |
| --ref_stamp; |
| } |
| if (ref_stamp >= 0) { |
| MotionFieldProjection(kReferenceFrameLast2, -1, sequence_header, |
| frame_header, current_frame, reference_frames, |
| motion_field_mv, y8_start, y8_end, x8_start, x8_end); |
| } |
| } |
| |
| } // namespace libgav1 |