Rewrite SAO recon to handle arbitrary sized blocks

Adds width and height parameters to function kvz_sao_reconstruct and
changes it to take coordinates in units of pixels. This will be useful
for doing SAO for areas smaller than a whole CTU.
This commit is contained in:
Arttu Ylä-Outinen 2017-06-30 16:09:18 +03:00
parent 61fc028eef
commit ec9ff42077
3 changed files with 165 additions and 172 deletions

View file

@ -606,10 +606,36 @@ static void encoder_state_worker_sao_reconstruct_lcu(void *opaque) {
// sao_do_rdo(encoder, lcu.x, lcu.y, sao_luma, sao_chroma); // sao_do_rdo(encoder, lcu.x, lcu.y, sao_luma, sao_chroma);
sao_info_t *sao_luma = &frame->sao_luma[data->y * stride + x]; sao_info_t *sao_luma = &frame->sao_luma[data->y * stride + x];
sao_info_t *sao_chroma = &frame->sao_chroma[data->y * stride + x]; sao_info_t *sao_chroma = &frame->sao_chroma[data->y * stride + x];
kvz_sao_reconstruct(data->encoder_state->encoder_control, frame, new_y_data, x, data->y, sao_luma, COLOR_Y);
kvz_sao_reconstruct(data->encoder_state,
&new_y_data[x * LCU_WIDTH + frame->width * (data->y * LCU_WIDTH)],
frame->width,
x * LCU_WIDTH,
data->y * LCU_WIDTH,
MIN(LCU_WIDTH, frame->width - x * LCU_WIDTH),
MIN(LCU_WIDTH, frame->height - data->y * LCU_WIDTH),
sao_luma,
COLOR_Y);
if (frame->rec->chroma_format != KVZ_CSP_400) { if (frame->rec->chroma_format != KVZ_CSP_400) {
kvz_sao_reconstruct(data->encoder_state->encoder_control, frame, new_u_data, x, data->y, sao_chroma, COLOR_U); kvz_sao_reconstruct(data->encoder_state,
kvz_sao_reconstruct(data->encoder_state->encoder_control, frame, new_v_data, x, data->y, sao_chroma, COLOR_V); &new_u_data[x * LCU_WIDTH_C + frame->width/2 * (data->y * LCU_WIDTH_C)],
frame->width/2,
x * LCU_WIDTH_C,
data->y * LCU_WIDTH_C,
MIN(LCU_WIDTH_C, frame->width/2 - x * LCU_WIDTH_C),
MIN(LCU_WIDTH_C, frame->height/2 - data->y * LCU_WIDTH_C),
sao_chroma,
COLOR_U);
kvz_sao_reconstruct(data->encoder_state,
&new_v_data[x * LCU_WIDTH_C + frame->width/2 * (data->y * LCU_WIDTH_C)],
frame->width/2,
x * LCU_WIDTH_C,
data->y * LCU_WIDTH_C,
MIN(LCU_WIDTH_C, frame->width/2 - x * LCU_WIDTH_C),
MIN(LCU_WIDTH_C, frame->height/2 - data->y * LCU_WIDTH_C),
sao_chroma,
COLOR_V);
} }
} }

270
src/sao.c
View file

@ -261,181 +261,115 @@ static void calc_sao_bands(const encoder_state_t * const state, const kvz_pixel
} }
/** static int sao_calc_eo_cat(kvz_pixel a, kvz_pixel b, kvz_pixel c)
* \brief Calculate dimensions of the buffer used by sao reconstruction.
* \param pic Picture.
* \param sao Sao parameters.
* \param rec Top-left corner of the LCU
*/
static void sao_calc_band_block_dims(const videoframe_t *frame, color_t color_i,
vector2d_t *rec, vector2d_t *block)
{ {
const int is_chroma = (color_i != COLOR_Y ? 1 : 0); // Mapping relationships between a, b and c to eo_idx.
int width = frame->width >> is_chroma; static const int sao_eo_idx_to_eo_category[] = { 1, 2, 0, 3, 4 };
int height = frame->height >> is_chroma;
int block_width = LCU_WIDTH >> is_chroma;
int eo_idx = 2 + SIGN3((int)c - (int)a) + SIGN3((int)c - (int)b);
// Handle right and bottom, taking care of non-LCU sized CUs. return sao_eo_idx_to_eo_category[eo_idx];
if (rec->y + block_width >= height) {
if (rec->y + block_width >= height) {
block->y = height - rec->y;
}
}
if (rec->x + block_width >= width) {
if (rec->x + block_width > width) {
block->x = width - rec->x;
}
} }
rec->x = 0; rec->y = 0;
}
/** /**
* \brief Calculate dimensions of the buffer used by sao reconstruction. * \brief Reconstruct SAO.
* *
* This function calculates 4 vectors that can be used to make the temporary * \param encoder encoder state
* buffers required by sao_reconstruct_color. * \param buffer Buffer containing the deblocked input pixels. The
* * area to filter starts at index 0.
* Vector block is the area affected by sao. Vectors tr and br are top-left * \param stride stride of buffer
* margin and bottom-right margin, which contain pixels that are not modified * \param x x-coordinate of the top-left corner in pixels
* by the reconstruction of this LCU but are needed by the reconstruction. * \param y y-coordinate of the top-left corner in pixels
* Vector rec is the offset from the CU to the required pixel area. * \param width width of the area to filter
* * \param height height of the area to filter
* The margins are always either 0 or 1, depending on the direction of the * \param sao SAO information
* edge offset class. * \param color color plane index
* * \param border_left true, if the left border of the area exists
* This also takes into account borders of the picture and non-LCU sized * \param border_right true, if the right border of the area exists
* CU's at the bottom and right of the picture. * \param border_above true, if the top border of the area exists
* * \param border_below true, if the bottom border of the area exists
* \ CU + rec
* +------+
* |\ tl |
* | +--+ |
* | |\ block
* | | \| |
* | +--+ |
* | \ br
* +------+
*
* \param pic Picture.
* \param sao Sao parameters.
* \param rec Top-left corner of the LCU, modified to be top-left corner of
*/ */
static void sao_calc_edge_block_dims(const videoframe_t * const frame, color_t color_i, void kvz_sao_reconstruct(const encoder_state_t *state,
const sao_info_t *sao, vector2d_t *rec, const kvz_pixel *buffer,
vector2d_t *tl, vector2d_t *br, int stride,
vector2d_t *block) int frame_x,
int frame_y,
int width,
int height,
const sao_info_t *sao,
color_t color)
{ {
vector2d_t a_ofs = g_sao_edge_offsets[sao->eo_class][0]; const encoder_control_t *const ctrl = state->encoder_control;
vector2d_t b_ofs = g_sao_edge_offsets[sao->eo_class][1]; videoframe_t *const frame = state->tile->frame;
const int is_chroma = (color_i != COLOR_Y ? 1 : 0); const int shift = color == COLOR_Y ? 0 : 1;
int width = frame->width >> is_chroma;
int height = frame->height >> is_chroma;
int block_width = LCU_WIDTH >> is_chroma;
// Handle top and left. const int frame_width = frame->width >> shift;
if (rec->y == 0) { const int frame_height = frame->height >> shift;
tl->y = 0; kvz_pixel *output = &frame->rec->data[color][frame_x + frame_y * frame_width];
if (a_ofs.y == -1 || b_ofs.y == -1) {
block->y -= 1; switch (sao->type) {
tl->y += 1;
case SAO_TYPE_NONE:
break;
case SAO_TYPE_BAND: {
int offsets[1 << KVZ_BIT_DEPTH];
kvz_calc_sao_offset_array(ctrl, sao, offsets, color);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
output[x + y * frame_width] = offsets[buffer[x + y * stride]];
} }
} }
if (rec->x == 0) { break;
tl->x = 0;
if (a_ofs.x == -1 || b_ofs.x == -1) {
block->x -= 1;
tl->x += 1;
}
} }
// Handle right and bottom, taking care of non-LCU sized CUs. case SAO_TYPE_EDGE: {
if (rec->y + block_width >= height) { const int offset_v = color == COLOR_V ? 5 : 0;
br->y = 0; const vector2d_t *offset = g_sao_edge_offsets[sao->eo_class];
block->y -= block_width + rec->y - height;
if (a_ofs.y == 1 || b_ofs.y == 1) {
block->y -= 1;
br->y += 1;
}
}
if (rec->x + block_width >= width) {
br->x = 0;
block->x -= block_width + rec->x - width;
if (a_ofs.x == 1 || b_ofs.x == 1) {
block->x -= 1;
br->x += 1;
}
}
rec->y = (rec->y == 0 ? 0 : -1); int x_orig = 0;
rec->x = (rec->x == 0 ? 0 : -1); int y_orig = 0;
}
void kvz_sao_reconstruct(const encoder_control_t * const encoder, videoframe_t * frame, const kvz_pixel *old_rec, if (frame_x + offset[0].x < 0 || frame_x + offset[1].x < 0) {
unsigned x_ctb, unsigned y_ctb, // Nothing to do for the leftmost column.
const sao_info_t *sao, color_t color_i) x_orig += 1;
}
if (frame_x + width + offset[0].x > frame_width ||
frame_x + width + offset[1].x > frame_width)
{ {
const int is_chroma = (color_i != COLOR_Y ? 1 : 0); // Nothing to do for the rightmost column.
const int pic_stride = frame->width >> is_chroma; width -= 1;
const int lcu_stride = LCU_WIDTH >> is_chroma; }
const int buf_stride = lcu_stride + 2; if (frame_y + offset[0].y < 0 || frame_y + offset[1].y < 0) {
// Nothing to do for the topmost row.
kvz_pixel *recdata = frame->rec->data[color_i]; y_orig += 1;
kvz_pixel buf_rec[(LCU_WIDTH + 2) * (LCU_WIDTH + 2)]; }
kvz_pixel new_rec[LCU_WIDTH * LCU_WIDTH]; if (frame_y + height + offset[0].y > frame_height ||
// Calling CU_TO_PIXEL with depth 1 is the same as using block size of 32. frame_y + height + offset[1].y > frame_height)
kvz_pixel *lcu_rec = &recdata[CU_TO_PIXEL(x_ctb, y_ctb, is_chroma, frame->rec->stride>>is_chroma)]; {
const kvz_pixel *old_lcu_rec = &old_rec[CU_TO_PIXEL(x_ctb, y_ctb, is_chroma, pic_stride)]; // Nothing to do for the bottommost row.
height -= 1;
vector2d_t ofs;
vector2d_t tl = { 1, 1 };
vector2d_t br = { 1, 1 };
vector2d_t block;
if (sao->type == SAO_TYPE_NONE) {
return;
} }
ofs.x = x_ctb * lcu_stride; for (int y = y_orig; y < height; y++) {
ofs.y = y_ctb * lcu_stride; for (int x = x_orig; x < width; x++) {
block.x = lcu_stride; const kvz_pixel *data = &buffer[x + y * stride];
block.y = lcu_stride;
if (sao->type == SAO_TYPE_BAND) { kvz_pixel a = data[offset[0].x + offset[0].y * stride];
tl.x = 0; tl.y = 0; kvz_pixel c = data[0];
br.x = 0; br.y = 0; kvz_pixel b = data[offset[1].x + offset[1].y * stride];
sao_calc_band_block_dims(frame, color_i, &ofs, &block);
const int eo_cat = sao_calc_eo_cat(a, b, c);
output[x + y * frame_width] =
CLIP(0, (1 << KVZ_BIT_DEPTH) - 1, c + sao->offsets[eo_cat + offset_v]);
}
}
break;
} }
else {
sao_calc_edge_block_dims(frame, color_i, sao, &ofs, &tl, &br, &block);
} }
assert(ofs.x + tl.x + block.x + br.x <= frame->width);
assert(ofs.y + tl.y + block.y + br.y <= frame->height);
CHECKPOINT("ofs.x=%d ofs.y=%d tl.x=%d tl.y=%d block.x=%d block.y=%d br.x=%d br.y=%d",
ofs.x, ofs.y, tl.x, tl.y, block.x, block.y, br.x, br.y);
// Data to tmp buffer.
kvz_pixels_blit(&old_lcu_rec[ofs.y * pic_stride + ofs.x],
buf_rec,
tl.x + block.x + br.x,
tl.y + block.y + br.y,
pic_stride, buf_stride);
kvz_sao_reconstruct_color(encoder, &buf_rec[tl.y * buf_stride + tl.x],
&new_rec[(ofs.y + tl.y) * lcu_stride + ofs.x + tl.x],
sao,
buf_stride, lcu_stride,
block.x, block.y, color_i);
// Copy reconstructed block from tmp buffer to rec image.
kvz_pixels_blit(&new_rec[(tl.y + ofs.y) * lcu_stride + (tl.x + ofs.x)],
&lcu_rec[(tl.y + ofs.y) * (frame->rec->stride >> is_chroma) + (tl.x + ofs.x)],
block.x, block.y, lcu_stride, frame->rec->stride >> is_chroma);
} }
static void sao_search_edge_sao(const encoder_state_t * const state, static void sao_search_edge_sao(const encoder_state_t * const state,
@ -820,7 +754,16 @@ void kvz_sao_reconstruct_frame(encoder_state_t * const state)
sao_info_t *sao_luma = &frame->sao_luma[lcu.y * stride + lcu.x]; sao_info_t *sao_luma = &frame->sao_luma[lcu.y * stride + lcu.x];
// sao_do_rdo(encoder, lcu.x, lcu.y, sao_luma, sao_chroma); // sao_do_rdo(encoder, lcu.x, lcu.y, sao_luma, sao_chroma);
kvz_sao_reconstruct(state->encoder_control, frame, new_y_data, lcu.x, lcu.y, sao_luma, COLOR_Y); vector2d_t pos = { lcu.x * LCU_WIDTH, lcu.y * LCU_WIDTH };
kvz_sao_reconstruct(state,
&new_y_data[pos.x + pos.y * frame->width],
frame->width,
pos.x,
pos.y,
MIN(LCU_WIDTH, frame->width - pos.x),
MIN(LCU_WIDTH, frame->height - pos.y),
sao_luma,
COLOR_Y);
} }
} }
free(new_y_data); free(new_y_data);
@ -837,8 +780,25 @@ void kvz_sao_reconstruct_frame(encoder_state_t * const state)
unsigned stride = frame->width_in_lcu; unsigned stride = frame->width_in_lcu;
sao_info_t *sao_chroma = &frame->sao_chroma[lcu.y * stride + lcu.x]; sao_info_t *sao_chroma = &frame->sao_chroma[lcu.y * stride + lcu.x];
kvz_sao_reconstruct(state->encoder_control, frame, new_u_data, lcu.x, lcu.y, sao_chroma, COLOR_U); vector2d_t pos = { lcu.x * LCU_WIDTH_C, lcu.y * LCU_WIDTH_C };
kvz_sao_reconstruct(state->encoder_control, frame, new_v_data, lcu.x, lcu.y, sao_chroma, COLOR_V); kvz_sao_reconstruct(state,
&new_u_data[pos.x + pos.y * frame->width/2],
frame->width / 2,
pos.x,
pos.y,
MIN(LCU_WIDTH_C, frame->width/2 - pos.x),
MIN(LCU_WIDTH_C, frame->height/2 - pos.y),
sao_chroma,
COLOR_U);
kvz_sao_reconstruct(state,
&new_v_data[pos.x + pos.y * frame->width/2],
frame->width / 2,
pos.x,
pos.y,
MIN(LCU_WIDTH_C, frame->width/2 - pos.x),
MIN(LCU_WIDTH_C, frame->height/2 - pos.y),
sao_chroma,
COLOR_V);
} }
} }

View file

@ -72,9 +72,16 @@ static const vector2d_t g_sao_edge_offsets[SAO_NUM_EO][2] = {
(sao).offsets[0], (sao).offsets[1], (sao).offsets[2], (sao).offsets[3], (sao).offsets[4]) (sao).offsets[0], (sao).offsets[1], (sao).offsets[2], (sao).offsets[3], (sao).offsets[4])
void kvz_sao_reconstruct(const encoder_control_t * encoder, videoframe_t *frame, const kvz_pixel *old_rec, void kvz_sao_reconstruct(const encoder_state_t *state,
unsigned x_ctb, unsigned y_ctb, const kvz_pixel *buffer,
const sao_info_t *sao, color_t color_i); int stride,
int frame_x,
int frame_y,
int width,
int height,
const sao_info_t *sao,
color_t color);
void kvz_sao_reconstruct_frame(encoder_state_t *state); void kvz_sao_reconstruct_frame(encoder_state_t *state);
void kvz_sao_search_lcu(const encoder_state_t* const state, int lcu_x, int lcu_y); void kvz_sao_search_lcu(const encoder_state_t* const state, int lcu_x, int lcu_y);
void kvz_calc_sao_offset_array(const encoder_control_t * const encoder, const sao_info_t *sao, int *offset, color_t color_i); void kvz_calc_sao_offset_array(const encoder_control_t * const encoder, const sao_info_t *sao, int *offset, color_t color_i);