blob: 477a7b8681074313125814703de6d50e7e212a9b [file] [log] [blame] [edit]
/*
* Copyright (c) 2021, 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 <stdlib.h>
#include <math.h>
#include "av1/common/common.h"
#include "av1/common/filter.h"
#include "av1/common/spherical_pred.h"
#include "aom_dsp/aom_dsp_common.h"
#if CONFIG_SPHERICAL_PRED
void av1_sphere_to_plane_erp(double phi, double theta, int width, int height,
double *x, double *y) {
double phi_mod = fmod(phi, 2 * PI);
int is_on_other_hemisphere = ((phi_mod > 0.5 * PI && phi_mod < 1.5 * PI) ||
(phi_mod < -0.5 * PI && phi_mod > -1.5 * PI));
// Flip phi to the destination quadrant
phi_mod = is_on_other_hemisphere ? PI - phi_mod : phi_mod;
// Regulate phi back to [-pi/2, pi/2)
if (phi_mod > 1.5 * PI) {
phi_mod = phi_mod - 2 * PI;
} else if (phi_mod < -1.5 * PI) {
phi_mod = phi_mod + 2 * PI;
}
double theta_mod = fmod(is_on_other_hemisphere ? theta + PI : theta, 2 * PI);
if (theta_mod < -PI) {
theta_mod = theta_mod + 2 * PI;
} else if (theta_mod >= PI) {
theta_mod = theta_mod - 2 * PI;
}
*x = theta_mod / PI * width * 0.5 + width * 0.5;
// No minus sign for y since we only use an imaginary upside-down globe
*y = phi_mod / (PI * 0.5) * height * 0.5 + height * 0.5;
*y = AOMMIN(*y, height - 0.00001);
}
void av1_plane_to_sphere_erp(double x, double y, int width, int height,
double *phi, double *theta) {
// Without the 0.0001, the X information of the top row will be discarded. Any
// points on the top row of the frame will be mapped to the north pole, hence
// will be mapped back to the center of the top row in the frame.
y = AOMMAX(y, 0.00001);
y = AOMMIN(y, height - 1);
double x_mod = fmod(x, width);
x_mod = x_mod >= 0 ? x_mod : x_mod + width;
// Since x_mod is in [0, width), theta is in [-PI, PI)
*theta = (x_mod - width * 0.5) / width * 2 * PI;
*phi = (y - height * 0.5) / height * PI;
}
void av1_normalize_carte_vector(CartesianVector *carte) {
double temp =
sqrt(carte->x * carte->x + carte->y * carte->y + carte->z * carte->z);
// k would be (0, 0, 0) if v and v_prime are the same
if (temp > 0.0001) {
carte->x = carte->x / temp;
carte->y = carte->y / temp;
carte->z = carte->z / temp;
}
}
void av1_sphere_polar_to_carte(const PolarVector *polar,
CartesianVector *carte) {
assert(polar != NULL && carte != NULL);
carte->x = polar->r * cos(polar->theta) * sin(polar->phi);
carte->y = polar->r * sin(polar->theta) * sin(polar->phi);
carte->z = polar->r * cos(polar->phi);
}
void av1_sphere_carte_to_polar(const CartesianVector *carte,
PolarVector *polar) {
assert(polar != NULL && carte != NULL);
polar->r =
sqrt(carte->x * carte->x + carte->y * carte->y + carte->z * carte->z);
polar->phi = acos(carte->z / polar->r);
if (carte->x == 0) {
if (carte->y > 0) {
polar->theta = 0.5 * PI;
} else if (carte->y < 0) {
polar->theta = 1.5 * PI;
} else {
polar->theta = 0;
}
} else {
polar->theta = atan(carte->y / carte->x);
// arctan only gives (-PI/2, PI/2), so we need to adjust the result
if (carte->x < 0) {
polar->theta += PI;
}
}
}
double av1_carte_vectors_dot_product(const CartesianVector *left,
const CartesianVector *right) {
assert(left != NULL && right != NULL);
return left->x * right->x + left->y * right->y + left->z * right->z;
}
void av1_carte_vectors_cross_product(const CartesianVector *left,
const CartesianVector *right,
CartesianVector *result) {
assert(left != NULL && right != NULL && result != NULL);
result->x = left->y * right->z - left->z * right->y;
result->y = left->z * right->x - left->x * right->z;
result->z = left->x * right->y - left->y * right->x;
}
/*!\brief Calculate the filter kernel index based on the subpixel coordination
* \param[in] subpel_pos The subpixel coordination
* \param[out] pos_ref The integer coordination to which the filter kernel
* center should align to. If the subpixel coordination
* is with in the range of (0, -0.0625) to the pixel on
* its right, we choose that pixel. Otherwise, we
* choose the pixel on the left of the subpixel.
* \return The kernel index. It is doubled since we only use
* #(0, 2, 4, ... 14) kernels
*/
static int cal_kernel_idx(double subpel_pos, int *pos_ref) {
double diff = subpel_pos - floor(subpel_pos);
int idx = (int)round(diff / 0.125);
if (idx >= SUBPEL_SHIFTS / 2) {
idx = idx % (SUBPEL_SHIFTS / 2);
*pos_ref = (int)ceil(subpel_pos);
} else {
*pos_ref = (int)floor(subpel_pos);
}
return idx * 2;
}
// Get the filtered sums of the rows int the 8x8 block centered at the
// input location
static void get_interp_x_arr(int x, int y, const uint8_t *ref_frame,
int ref_frame_stride, int frame_width,
int frame_height, const InterpKernel *kernel,
int filter_tap, int x_kernel_idx,
double *x_sum_arr) {
int fo = filter_tap / 2 - 1;
int cur_x;
int cur_y;
double coeff = 0;
double sum = 0;
for (int i = 0; i < 8; i++) {
cur_y = y - fo + i;
cur_y = AOMMAX(cur_y, 0);
cur_y = AOMMIN(cur_y, frame_height - 1);
sum = 0;
for (int k = 0; k < filter_tap; k++) {
coeff = kernel[x_kernel_idx][k] / 128.0;
cur_x = x - fo + k;
cur_x = cur_x % frame_width;
cur_x = cur_x >= 0 ? cur_x : frame_width + cur_x;
sum += ref_frame[cur_x + cur_y * ref_frame_stride] * coeff;
}
x_sum_arr[i] = sum;
} // for i
}
static uint8_t interp_pixel_erp(const uint8_t *ref_frame, int ref_frame_stride,
int frame_width, int frame_height,
double subpel_x, double subpel_y,
const InterpKernel *kernel, int filter_tap) {
double x_sum_arr[8];
double sum = 0;
double coeff = 0;
int x_kernel_idx;
int y_kernel_idx;
int x_ref;
int y_ref;
int pixel;
x_kernel_idx = cal_kernel_idx(subpel_x, &x_ref);
y_kernel_idx = cal_kernel_idx(subpel_y, &y_ref);
get_interp_x_arr(x_ref, y_ref, ref_frame, ref_frame_stride, frame_width,
frame_height, kernel, filter_tap, x_kernel_idx, x_sum_arr);
// Perform filtering on the column
sum = 0;
for (int k = 0; k < filter_tap; k++) {
coeff = kernel[y_kernel_idx][k] / 128.0;
sum += x_sum_arr[k] * coeff;
}
pixel = (int)round(sum);
pixel = clamp(pixel, 0, 255);
return (uint8_t)pixel;
}
void av1_get_pred_erp(int block_x, int block_y, int block_width,
int block_height, double delta_phi, double delta_theta,
const uint8_t *ref_frame, int ref_frame_stride,
int frame_width, int frame_height, int pred_block_stride,
uint8_t *pred_block) {
assert(ref_frame != NULL && frame_width > 0 && frame_height > 0 &&
ref_frame_stride >= frame_width);
assert(pred_block != NULL && block_width > 0 && block_height > 0 &&
pred_block_stride >= block_width);
int pos_pred; // Offset in pred_block buffer
double x_current; // X coordiante in current frame
double y_current; // Y coordiante in currrent frame
double subpel_x_ref; // X coordinate in reference frame
double subpel_y_ref; // Y coordiante in reference frame
PolarVector block_polar;
CartesianVector block_v;
CartesianVector block_v_prime;
CartesianVector k; // The axis that the block will rotate along
block_polar.r = 1.0;
av1_plane_to_sphere_erp(block_x, block_y, frame_width, frame_height,
&block_polar.phi, &block_polar.theta);
block_polar.phi += 0.5 * PI;
av1_sphere_polar_to_carte(&block_polar, &block_v);
block_polar.phi += delta_phi;
block_polar.theta += delta_theta;
av1_sphere_polar_to_carte(&block_polar, &block_v_prime);
av1_carte_vectors_cross_product(&block_v, &block_v_prime, &k);
av1_normalize_carte_vector(&k);
double product = av1_carte_vectors_dot_product(&block_v, &block_v_prime);
// Avoid floating point precision issues
product = AOMMAX(product, -1.0);
product = AOMMIN(product, 1.0);
double alpha = acos(product);
CartesianVector v;
CartesianVector v_prime;
PolarVector v_polar;
PolarVector v_prime_polar;
v_polar.r = 1.0;
v_prime_polar.r = 1.0;
double k_dot_v;
const int filter_tap = 8;
for (int idx_y = 0; idx_y < block_height; idx_y++) {
for (int idx_x = 0; idx_x < block_width; idx_x++) {
x_current = idx_x + block_x;
y_current = idx_y + block_y;
av1_plane_to_sphere_erp(x_current, y_current, frame_width, frame_height,
&v_polar.phi, &v_polar.theta);
v_polar.phi += 0.5 * PI;
av1_sphere_polar_to_carte(&v_polar, &v);
k_dot_v = av1_carte_vectors_dot_product(&k, &v);
v_prime.x = k.x * k_dot_v * (1 - cos(alpha)) + v.x * cos(alpha) +
(k.y * v.z - k.z * v.y) * sin(alpha);
v_prime.y = k.y * k_dot_v * (1 - cos(alpha)) + v.y * cos(alpha) +
(k.z * v.x - k.x * v.z) * sin(alpha);
v_prime.z = k.z * k_dot_v * (1 - cos(alpha)) + v.z * cos(alpha) +
(k.x * v.y - k.y * v.x) * sin(alpha);
av1_sphere_carte_to_polar(&v_prime, &v_prime_polar);
v_prime_polar.phi -= 0.5 * PI;
av1_sphere_to_plane_erp(v_prime_polar.phi, v_prime_polar.theta,
frame_width, frame_height, &subpel_x_ref,
&subpel_y_ref);
pos_pred = idx_x + idx_y * pred_block_stride;
pred_block[pos_pred] = interp_pixel_erp(
ref_frame, ref_frame_stride, frame_width, frame_height, subpel_x_ref,
subpel_y_ref, av1_sub_pel_filters_8, filter_tap);
}
} // for idx_y
}
static int get_sad_of_blocks(const uint8_t *cur_block,
const uint8_t *pred_block, int block_width,
int block_height, int cur_block_stride,
int pred_block_stride) {
assert(block_width > 0 && block_height > 0 && cur_block_stride >= 0 &&
pred_block_stride >= 0);
int pos_curr;
int pos_pred;
int ret_sad = 0;
for (int idx_y = 0; idx_y < block_height; idx_y++) {
for (int idx_x = 0; idx_x < block_width; idx_x++) {
pos_curr = idx_x + idx_y * cur_block_stride;
pos_pred = idx_x + idx_y * pred_block_stride;
ret_sad += abs((int)cur_block[pos_curr] - (int)pred_block[pos_pred]);
}
}
return ret_sad;
}
/* Calculate the scale of theta based on the current phi.
* Makes the search/filter pattern on the latitude sparse as the position moves
* toward polars. Will be used for adjusting searching step, searching range,
* and filtering.
*/
static double cal_theta_scale(double phi) {
const double max_scale = 10.0;
if (fabs(cos(phi)) > 0.00001) {
return AOMMIN(max_scale, 1 / fabs(cos(phi)));
} else {
return max_scale;
}
}
static double cal_range_theta(int search_range, int frame_width, double phi) {
return search_range * cal_theta_scale(phi) * 2 * PI / frame_width;
}
int av1_motion_search_brute_force_erp(
int block_x, int block_y, int block_width, int block_height,
const uint8_t *cur_frame, const uint8_t *ref_frame, int frame_stride,
int frame_width, int frame_height, int search_range, SphereMV *best_mv) {
assert(cur_frame != NULL && ref_frame != NULL && best_mv != NULL);
assert(block_width > 0 && block_height > 0 && block_width <= 128 &&
block_height <= 128 && block_x >= 0 && block_y >= 0 &&
frame_width > 0 && frame_height > 0);
assert(search_range > 0);
const double search_step_phi = PI / frame_height;
const double search_step_theta = 2 * PI / frame_width;
double delta_phi;
double delta_theta;
int temp_sad;
int best_sad;
const uint8_t *cur_block = &cur_frame[block_x + block_y * frame_stride];
uint8_t pred_block[128 * 128];
const int pred_block_stride = 128;
av1_get_pred_erp(block_x, block_y, block_width, block_height, 0, 0, ref_frame,
frame_stride, frame_width, frame_height, pred_block_stride,
pred_block);
best_sad = get_sad_of_blocks(cur_block, pred_block, block_width, block_height,
frame_stride, pred_block_stride);
best_mv->phi = 0;
best_mv->theta = 0;
for (int i = -search_range; i <= search_range; i++) {
delta_phi = i * search_step_phi;
for (int j = -search_range; j <= search_range; j++) {
delta_theta = j * search_step_theta;
av1_get_pred_erp(block_x, block_y, block_width, block_height, delta_phi,
delta_theta, ref_frame, frame_stride, frame_width,
frame_height, pred_block_stride, pred_block);
temp_sad =
get_sad_of_blocks(cur_block, pred_block, block_width, block_height,
frame_stride, pred_block_stride);
if (temp_sad < best_sad) {
best_sad = temp_sad;
best_mv->phi = delta_phi;
best_mv->theta = delta_theta;
}
} // for j
} // for i
return best_sad;
}
/* Update Large Diamond Search Pattern shperical motion vectors based on the
* center
* ldsp_mv[1]
* / \
* ldsp_mv[8] ldsp_mv[2]
* / \
* ldsp_mv[7] ldsp_mv[0] ldsp_mv[3]
* \ /
* ldsp_mv[6] ldsp_mv[4]
* \ /
* ldsp_mv[5]
*/
static void update_sphere_mv_ldsp(SphereMV ldsp_mv[9], double search_step_phi,
double search_step_theta, double block_phi) {
double magnified_step_theta;
ldsp_mv[1].phi = ldsp_mv[0].phi - 2 * search_step_phi;
ldsp_mv[1].theta = ldsp_mv[0].theta;
ldsp_mv[2].phi = ldsp_mv[0].phi - search_step_phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + ldsp_mv[2].phi);
ldsp_mv[2].theta = ldsp_mv[0].theta + magnified_step_theta;
ldsp_mv[3].phi = ldsp_mv[0].phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + ldsp_mv[3].phi);
ldsp_mv[3].theta = ldsp_mv[0].theta + 2 * magnified_step_theta;
ldsp_mv[4].phi = ldsp_mv[0].phi + search_step_phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + ldsp_mv[4].phi);
ldsp_mv[4].theta = ldsp_mv[0].theta + magnified_step_theta;
ldsp_mv[5].phi = ldsp_mv[0].phi + 2 * search_step_phi;
ldsp_mv[5].theta = ldsp_mv[0].theta;
ldsp_mv[6].phi = ldsp_mv[0].phi + search_step_phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + ldsp_mv[6].phi);
ldsp_mv[6].theta = ldsp_mv[0].theta - magnified_step_theta;
ldsp_mv[7].phi = ldsp_mv[0].phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + ldsp_mv[7].phi);
ldsp_mv[7].theta = ldsp_mv[0].theta - 2 * magnified_step_theta;
ldsp_mv[8].phi = ldsp_mv[0].phi - search_step_phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + ldsp_mv[8].phi);
ldsp_mv[8].theta = ldsp_mv[0].theta - magnified_step_theta;
}
/* Small Diamond Search Pattern on shpere
* sdsp_mv[1]
* / \
* sdsp_mv[4] sdsp_mv[0] sdsp_mv[2]
* \ /
* sdsp_mv[3]
*/
static void update_sphere_mv_sdsp(SphereMV sdsp_mv[5], double search_step_phi,
double search_step_theta, double block_phi) {
double magnified_step_theta;
sdsp_mv[1].phi = sdsp_mv[0].phi - search_step_phi;
sdsp_mv[1].theta = sdsp_mv[0].theta;
sdsp_mv[2].phi = sdsp_mv[0].phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + sdsp_mv[2].phi);
sdsp_mv[2].theta = sdsp_mv[0].theta + magnified_step_theta;
sdsp_mv[3].phi = sdsp_mv[0].phi + search_step_phi;
sdsp_mv[3].theta = sdsp_mv[0].theta;
sdsp_mv[4].phi = sdsp_mv[0].phi;
magnified_step_theta =
search_step_theta * cal_theta_scale(block_phi + sdsp_mv[4].phi);
sdsp_mv[4].theta = sdsp_mv[0].theta - magnified_step_theta;
}
int av1_motion_search_diamond_erp(int block_x, int block_y, int block_width,
int block_height, const uint8_t *cur_frame,
const uint8_t *ref_frame, int frame_stride,
int frame_width, int frame_height,
int search_range, const SphereMV *start_mv,
SphereMV *best_mv) {
assert(cur_frame != NULL && ref_frame != NULL && best_mv != NULL);
assert(block_width > 0 && block_height > 0 && block_width <= 128 &&
block_height <= 128 && block_x >= 0 && block_y >= 0 &&
frame_width > 0 && frame_height > 0);
assert(search_range > 0);
// Large Diamond Search Pattern on shpere
SphereMV ldsp_mv[9];
// Small Diamond Search Pattern on shpere
SphereMV sdsp_mv[5];
double search_step_phi =
AOMMIN(0.25 * block_height, 0.25 * search_range) * PI / frame_height;
double search_step_theta =
AOMMIN(0.25 * block_width, 0.25 * search_range) * 2 * PI / frame_width;
const uint8_t *cur_block = &cur_frame[block_x + block_y * frame_stride];
uint8_t pred_block[128 * 128];
const int pred_block_stride = 128;
double start_phi;
double start_theta;
av1_plane_to_sphere_erp(block_x, block_y, frame_width, frame_height,
&start_phi, &start_theta);
double max_range_phi =
start_phi + start_mv->phi + search_range * PI / frame_height;
double min_range_phi =
start_phi + start_mv->phi - search_range * PI / frame_height;
int best_mv_idx = 0;
ldsp_mv[0].phi = start_mv->phi;
ldsp_mv[0].theta = start_mv->theta;
int temp_sad;
int best_sad;
av1_get_pred_erp(block_x, block_y, block_width, block_height, ldsp_mv[0].phi,
ldsp_mv[0].theta, ref_frame, frame_stride, frame_width,
frame_height, pred_block_stride, pred_block);
best_sad = get_sad_of_blocks(cur_block, pred_block, block_width, block_height,
frame_stride, pred_block_stride);
update_sphere_mv_ldsp(ldsp_mv, search_step_phi, search_step_theta, start_phi);
const double min_scale = 0.125;
while (start_phi + ldsp_mv[5].phi <= max_range_phi &&
start_phi + ldsp_mv[1].phi >= min_range_phi &&
start_theta + ldsp_mv[3].theta <=
start_theta + start_mv->theta +
cal_range_theta(search_range, frame_width,
start_phi + ldsp_mv[3].phi) &&
start_theta + ldsp_mv[7].theta >=
start_theta + start_mv->theta -
cal_range_theta(search_range, frame_width,
start_phi + ldsp_mv[7].phi)) {
for (int i = 0; i < 9; i++) {
av1_get_pred_erp(block_x, block_y, block_width, block_height,
ldsp_mv[i].phi, ldsp_mv[i].theta, ref_frame,
frame_stride, frame_width, frame_height,
pred_block_stride, pred_block);
temp_sad =
get_sad_of_blocks(cur_block, pred_block, block_width, block_height,
frame_stride, pred_block_stride);
if (temp_sad < best_sad) {
best_sad = temp_sad;
best_mv_idx = i;
}
} // for
if (best_mv_idx == 0) {
if (search_step_phi > min_scale * PI / frame_height &&
search_step_theta > min_scale * 2 * PI / frame_height) {
search_step_phi *= 0.5;
search_step_theta *= 0.5;
} else {
break;
}
} else {
ldsp_mv[0].phi = ldsp_mv[best_mv_idx].phi;
ldsp_mv[0].theta = ldsp_mv[best_mv_idx].theta;
best_mv_idx = 0;
}
update_sphere_mv_ldsp(ldsp_mv, search_step_phi, search_step_theta,
start_phi);
}
sdsp_mv[0].phi = ldsp_mv[0].phi;
sdsp_mv[0].theta = ldsp_mv[0].theta;
best_mv_idx = 0;
search_step_phi = min_scale * PI / frame_height;
search_step_theta = min_scale * 2 * PI / frame_width;
update_sphere_mv_sdsp(sdsp_mv, search_step_phi, search_step_theta, start_phi);
if (start_phi + sdsp_mv[3].phi <= max_range_phi &&
start_phi + sdsp_mv[1].phi >= min_range_phi &&
start_theta + sdsp_mv[2].theta <=
start_theta + start_mv->theta +
cal_range_theta(search_range, frame_width,
start_phi + sdsp_mv[2].phi) &&
start_theta + sdsp_mv[4].theta >=
start_theta + start_mv->theta +
cal_range_theta(search_range, frame_width,
start_phi + sdsp_mv[4].phi)) {
for (int i = 0; i < 5; i++) {
av1_get_pred_erp(block_x, block_y, block_width, block_height,
sdsp_mv[i].phi, sdsp_mv[i].theta, ref_frame,
frame_stride, frame_width, frame_height,
pred_block_stride, pred_block);
temp_sad =
get_sad_of_blocks(cur_block, pred_block, block_width, block_height,
frame_stride, pred_block_stride);
if (temp_sad < best_sad) {
best_sad = temp_sad;
best_mv_idx = i;
}
} // for
}
best_mv->phi = sdsp_mv[best_mv_idx].phi;
best_mv->theta = sdsp_mv[best_mv_idx].theta;
return best_sad;
}
#endif