diff --git a/build/VS2010/HEVC_encoder.vcxproj b/build/VS2010/HEVC_encoder.vcxproj
index 4aa42f54..f9cef408 100644
--- a/build/VS2010/HEVC_encoder.vcxproj
+++ b/build/VS2010/HEVC_encoder.vcxproj
@@ -23,9 +23,7 @@
Win32Proj
HEVC_encoder
-
-
StaticLibrary
true
@@ -42,9 +40,7 @@
StaticLibrary
false
-
-
@@ -88,6 +84,7 @@
+
@@ -104,6 +101,7 @@
+
diff --git a/build/VS2010/HEVC_encoder.vcxproj.filters b/build/VS2010/HEVC_encoder.vcxproj.filters
index fafb8952..560b3c4a 100644
--- a/build/VS2010/HEVC_encoder.vcxproj.filters
+++ b/build/VS2010/HEVC_encoder.vcxproj.filters
@@ -69,6 +69,9 @@
Source Files
+
+ Source Files
+
@@ -119,6 +122,9 @@
Header Files
+
+ Header Files
+
diff --git a/src/cabac.c b/src/cabac.c
index 7d7fd57c..81b64e74 100644
--- a/src/cabac.c
+++ b/src/cabac.c
@@ -11,6 +11,7 @@
#include "cabac.h"
+#include
#include
#include
#include
@@ -281,20 +282,52 @@ void cabac_write_unary_max_symbol(cabac_data *data, cabac_ctx *ctx, uint32_t sym
{
int8_t code_last = max_symbol > symbol;
+ assert(symbol <= max_symbol);
+
if (!max_symbol) return;
data->ctx = &ctx[0];
- cabac_encode_bin(data, symbol ? 1 : 0);
+ CABAC_BIN(data, symbol ? 1 : 0, "ums");
if (!symbol) return;
while (--symbol) {
data->ctx = &ctx[offset];
- cabac_encode_bin(data, 1);
+ CABAC_BIN(data, 1, "ums");
}
if (code_last) {
data->ctx = &ctx[offset];
- cabac_encode_bin(data, 0);
+ CABAC_BIN(data, 0, "ums");
+ }
+}
+
+/**
+ * This can be used for Truncated Rice binarization with cRiceParam=0.
+ */
+void cabac_write_unary_max_symbol_ep(cabac_data *data, unsigned symbol, unsigned max_symbol)
+{
+ /*if (symbol == 0) {
+ CABAC_BIN_EP(data, 0, "ums_ep");
+ } else {
+ // Make a bit-string of (symbol) times 1 and a single 0, except when
+ // symbol == max_symbol.
+ unsigned bins = ((1 << symbol) - 1) << (symbol < max_symbol);
+ CABAC_BINS_EP(data, bins, symbol + (symbol < max_symbol), "ums_ep");
+ }*/
+
+ int8_t code_last = max_symbol > symbol;
+
+ assert(symbol <= max_symbol);
+
+ CABAC_BIN_EP(data, symbol ? 1 : 0, "ums_ep");
+
+ if (!symbol) return;
+
+ while (--symbol) {
+ CABAC_BIN_EP(data, 1, "ums_ep");
+ }
+ if (code_last) {
+ CABAC_BIN_EP(data, 0, "ums_ep");
}
}
diff --git a/src/cabac.h b/src/cabac.h
index fb518814..8bd32610 100644
--- a/src/cabac.h
+++ b/src/cabac.h
@@ -55,6 +55,7 @@ void cabac_write_ep_ex_golomb(cabac_data *data, uint32_t symbol,
void cabac_write_unary_max_symbol(cabac_data *data, cabac_ctx *ctx,
uint32_t symbol, int32_t offset,
uint32_t max_symbol);
+void cabac_write_unary_max_symbol_ep(cabac_data *data, unsigned symbol, unsigned max_symbol);
// Macros
diff --git a/src/context.c b/src/context.c
index 2eabeec3..11995cd1 100644
--- a/src/context.c
+++ b/src/context.c
@@ -19,6 +19,8 @@
// CONTEXTS
+cabac_ctx g_sao_merge_flag_model;
+cabac_ctx g_sao_type_idx_model;
cabac_ctx g_split_flag_model[3]; //!< \brief split flag context models
cabac_ctx g_intra_mode_model; //!< \brief intra mode context models
cabac_ctx g_chroma_pred_model[2];
@@ -74,6 +76,9 @@ void init_contexts(encoder_control *encoder, int8_t slice)
uint16_t i;
// Initialize contexts
+ ctx_init(&g_sao_merge_flag_model, encoder->QP, INIT_SAO_MERGE_FLAG[slice]);
+ ctx_init(&g_sao_type_idx_model, encoder->QP, INIT_SAO_TYPE_IDX[slice]);
+
ctx_init(&g_cu_merge_flag_ext_model, encoder->QP, INIT_MERGE_FLAG_EXT[slice][0]);
ctx_init(&g_cu_merge_idx_ext_model, encoder->QP, INIT_MERGE_IDX_EXT[slice][0]);
ctx_init(&g_cu_pred_mode_model, encoder->QP, INIT_PRED_MODE[slice][0]);
diff --git a/src/context.h b/src/context.h
index c2d5cbf3..080da65f 100644
--- a/src/context.h
+++ b/src/context.h
@@ -37,6 +37,8 @@ int32_t context_get_sig_ctx_inc(int32_t pattern_sig_ctx,uint32_t scan_idx,int32_
int32_t pos_y,int32_t block_type,int32_t width, int8_t texture_type);
// CONTEXTS
+extern cabac_ctx g_sao_merge_flag_model;
+extern cabac_ctx g_sao_type_idx_model;
extern cabac_ctx g_split_flag_model[3];
extern cabac_ctx g_intra_mode_model;
extern cabac_ctx g_chroma_pred_model[2];
@@ -65,6 +67,9 @@ extern cabac_ctx g_mvp_idx_model[2];
extern cabac_ctx g_cu_qt_root_cbf_model;
#define CNU 154
+static const uint8_t INIT_SAO_MERGE_FLAG[3] = { 153, 153, 153 };
+static const uint8_t INIT_SAO_TYPE_IDX[3] = { 160, 185, 200 };
+
static const uint8_t
INIT_QT_ROOT_CBF[3][1] =
{
diff --git a/src/encmain.c b/src/encmain.c
index d64f8860..34938997 100644
--- a/src/encmain.c
+++ b/src/encmain.c
@@ -150,7 +150,7 @@ int main(int argc, char *argv[])
encoder->beta_offset_div2 = 0;
encoder->tc_offset_div2 = 0;
// SAO
- encoder->sao_enable = 0;
+ encoder->sao_enable = 1;
init_encoder_input(&encoder->in, input, cfg->width, cfg->height);
diff --git a/src/encoder.c b/src/encoder.c
index a9194a39..45e33f9a 100644
--- a/src/encoder.c
+++ b/src/encoder.c
@@ -27,6 +27,7 @@
#include "inter.h"
#include "filter.h"
#include "search.h"
+#include "sao.h"
int16_t g_lambda_cost[55];
uint32_t* g_sig_last_scan[3][7];
@@ -320,10 +321,6 @@ void encode_one_frame(encoder_control* encoder)
bitstream_clear_buffer(encoder->stream);
}
- // Filtering
- if(encoder->deblock_enable) {
- filter_deblock(encoder);
- }
// Calculate checksum
add_checksum(encoder);
}
@@ -708,6 +705,8 @@ void encode_VUI(encoder_control* encoder)
void encode_slice_header(encoder_control* encoder)
{
+ picture *cur_pic = encoder->in.cur_pic;
+
#ifdef _DEBUG
printf("=========== Slice ===========\n");
#endif
@@ -752,8 +751,8 @@ void encode_slice_header(encoder_control* encoder)
//end if
//end if
if (encoder->sao_enable) {
- WRITE_U(encoder->stream, 1,1, "slice_sao_luma_flag");
- WRITE_U(encoder->stream, 0,1, "slice_sao_chroma_flag");
+ WRITE_U(encoder->stream, cur_pic->slice_sao_luma_flag, 1, "slice_sao_luma_flag");
+ WRITE_U(encoder->stream, cur_pic->slice_sao_chroma_flag, 1, "slice_sao_chroma_flag");
}
if (encoder->in.cur_pic->slicetype != SLICE_I) {
@@ -771,10 +770,141 @@ void encode_slice_header(encoder_control* encoder)
//WRITE_U(encoder->stream, 1, 1, "alignment");
}
+
+void encode_sao_color(encoder_control *encoder, sao_info *sao, color_index color_i)
+{
+ picture *pic = encoder->in.cur_pic;
+
+ // Skip colors with no SAO.
+ if (color_i == COLOR_Y && !pic->slice_sao_luma_flag) {
+ return;
+ } else if (!pic->slice_sao_chroma_flag) {
+ return;
+ }
+
+ if (color_i != COLOR_V) {
+ //CABAC_BIN(&cabac, sao->type, "sao_type_idx");
+ // TR cMax=2
+ // HM codes only the first bin with context.
+ //cabac_write_unary_max_symbol(&cabac, &g_sao_type_idx_model, sao->type, 0, 2);
+ cabac.ctx = &g_sao_type_idx_model;
+ CABAC_BIN(&cabac, sao->type == 0 ? 0 : 1, "sao_type_idx");
+ if (sao->type == SAO_TYPE_BAND) {
+ CABAC_BIN_EP(&cabac, 0, "sao_type_idx_ep");
+ } else if (sao->type == SAO_TYPE_EDGE) {
+ CABAC_BIN_EP(&cabac, 1, "sao_type_idx_ep");
+ }
+ }
+
+ if (sao->type != SAO_TYPE_NONE) {
+ sao_eo_cat i;
+
+ // TR cMax=7 (for 8bit), cRiseParam=0
+ for (i = SAO_EO_CAT1; i <= SAO_EO_CAT2; ++i) {
+ assert(sao->offsets[i] >= 0);
+ cabac_write_unary_max_symbol_ep(&cabac, sao->offsets[i], SAO_ABS_OFFSET_MAX);
+ }
+ for (i = SAO_EO_CAT3; i <= SAO_EO_CAT4; ++i) {
+ assert(sao->offsets[i] <= 0);
+ cabac_write_unary_max_symbol_ep(&cabac, -sao->offsets[i], SAO_ABS_OFFSET_MAX);
+ }
+
+ if (sao->type == SAO_TYPE_BAND) {
+ for (i = SAO_EO_CAT1; i < SAO_EO_CAT4; ++i) {
+ // Parahprasing spec: "If offset_sign is equal to 0, offsetSign is set
+ // equal to 1. Otherwise to -1."
+ // follows: >=0 is coded as 0, <0 is coded as 1
+ // FL cMax=1 (1 bit)
+ CABAC_BIN_EP(&cabac, sao->offsets[i] >= 0 ? 0 : 1, "sao_offset_sign");
+ }
+ // TODO: sao_band_position
+ // FL cMax=31 (6 bits)
+ } else if (color_i != COLOR_V) {
+ // FL cMax=3 (2 bits)
+ CABAC_BINS_EP(&cabac, sao->eo_class, 2, "sao_eo_class");
+ }
+ }
+}
+
+void encode_sao_merge_flags(encoder_control *encoder, sao_info *sao,
+ unsigned x_ctb, unsigned y_ctb)
+{
+ // SAO merge flags are not present if merge candidate is not in the same
+ // slice AND tile, but there isn't any such segmentation right now.
+ assert(!USE_SLICES && !USE_TILES);
+
+ // SAO merge flags are not present for the first row and column.
+ if (x_ctb > 0) {
+ cabac.ctx = &g_sao_merge_flag_model;
+ CABAC_BIN(&cabac, sao->merge_left_flag ? 1 : 0, "sao_merge_left_flag");
+ }
+ if (y_ctb > 0 && !sao->merge_left_flag) {
+ cabac.ctx = &g_sao_merge_flag_model;
+ CABAC_BIN(&cabac, sao->merge_up_flag ? 1 : 0, "sao_merge_up_flag");
+ }
+}
+
+/**
+ * \brief Stub that encodes all LCU's as none type.
+ */
+void encode_sao(encoder_control *encoder, unsigned x_lcu, uint16_t y_lcu,
+ sao_info *sao_luma, sao_info *sao_chroma)
+{
+ unsigned sao_type[3] = {SAO_TYPE_NONE, SAO_TYPE_NONE, SAO_TYPE_NONE};
+ picture *pic = encoder->in.cur_pic;
+
+ // TODO: transmit merge flags outside sao_info
+ encode_sao_merge_flags(encoder, sao_luma, x_lcu, y_lcu);
+
+ // If SAO is merged, nothing else needs to be coded.
+ if (!sao_luma->merge_left_flag && !sao_luma->merge_up_flag) {
+ encode_sao_color(encoder, sao_luma, COLOR_Y);
+ encode_sao_color(encoder, sao_chroma, COLOR_U);
+ encode_sao_color(encoder, sao_chroma, COLOR_V);
+ }
+}
+
void encode_slice_data(encoder_control* encoder)
{
uint16_t x_ctb, y_ctb;
+ picture *pic = encoder->in.cur_pic;
+ // Filtering
+ if(encoder->deblock_enable) {
+ filter_deblock(encoder);
+ }
+
+ if (encoder->sao_enable) {
+ pixel *new_y_data = MALLOC(pixel, pic->width * pic->height);
+ pixel *new_u_data = MALLOC(pixel, (pic->width * pic->height) >> 2);
+ pixel *new_v_data = MALLOC(pixel, (pic->width * pic->height) >> 2);
+ memcpy(new_y_data, pic->y_recdata, sizeof(pixel) * pic->width * pic->height);
+ memcpy(new_u_data, pic->u_recdata, sizeof(pixel) * (pic->width * pic->height) >> 2);
+ memcpy(new_v_data, pic->v_recdata, sizeof(pixel) * (pic->width * pic->height) >> 2);
+
+ for (y_ctb = 0; y_ctb < encoder->in.height_in_lcu; y_ctb++) {
+ for (x_ctb = 0; x_ctb < encoder->in.width_in_lcu; x_ctb++) {
+ unsigned stride = encoder->in.width_in_lcu;
+ sao_info *sao_luma = &pic->sao_luma[y_ctb * stride + x_ctb];
+ sao_info *sao_chroma = &pic->sao_chroma[y_ctb * stride + x_ctb];
+ init_sao_info(sao_luma);
+ init_sao_info(sao_chroma);
+
+ sao_search_luma(encoder->in.cur_pic, x_ctb, y_ctb, sao_luma);
+ sao_search_chroma(encoder->in.cur_pic, x_ctb, y_ctb, sao_chroma);
+ // sao_do_merge(encoder, x_ctb, y_ctb, sao_luma, sao_chroma);
+ // sao_do_rdo(encoder, x_ctb, y_ctb, sao_luma, sao_chroma);
+ sao_reconstruct(pic, new_y_data, x_ctb, y_ctb, sao_luma, COLOR_Y);
+ sao_reconstruct(pic, new_u_data, x_ctb, y_ctb, sao_chroma, COLOR_U);
+ sao_reconstruct(pic, new_v_data, x_ctb, y_ctb, sao_chroma, COLOR_V);
+ }
+ }
+
+ free(new_y_data);
+ free(new_u_data);
+ free(new_v_data);
+ }
+
init_contexts(encoder,encoder->in.cur_pic->slicetype);
// Loop through every LCU in the slice
@@ -785,6 +915,15 @@ void encode_slice_data(encoder_control* encoder)
uint8_t last_cu_x = (x_ctb == (encoder->in.width_in_lcu - 1)) ? 1 : 0;
uint8_t depth = 0;
+ if (encoder->sao_enable) {
+ picture *pic = encoder->in.cur_pic;
+ unsigned stride = encoder->in.width_in_lcu;
+ sao_info sao_luma = pic->sao_luma[y_ctb * stride + x_ctb];
+ sao_info sao_chroma = pic->sao_chroma[y_ctb * stride + x_ctb];
+
+ encode_sao(encoder, x_ctb, y_ctb, &sao_luma, &sao_chroma);
+ }
+
// Recursive function for looping through all the sub-blocks
encode_coding_tree(encoder, x_ctb << MAX_DEPTH, y_ctb << MAX_DEPTH, depth);
diff --git a/src/global.h b/src/global.h
index e04822b1..98d8168e 100644
--- a/src/global.h
+++ b/src/global.h
@@ -65,6 +65,9 @@ typedef int16_t coefficient;
/* END OF CONFIG VARIABLES */
+#define LCU_LUMA_SIZE (LCU_WIDTH * LCU_WIDTH)
+#define LCU_CHROMA_SIZE (LCU_WIDTH * LCU_WIDTH >> 2)
+
#define MAX_REF_PIC_COUNT 5
#define AMVP_MAX_NUM_CANDS 2
@@ -80,11 +83,20 @@ typedef int16_t coefficient;
#define NO_SCU_IN_LCU(no_lcu) ((no_lcu) << MAX_DEPTH)
#define WITHIN(val, min_val, max_val) ((min_val) <= (val) && (val) <= (max_val))
+#define LOG2_LCU_WIDTH 6
+// CU_TO_PIXEL = y * lcu_width * pic_width + x * lcu_width
+#define CU_TO_PIXEL(x, y, depth, width) (((y) << (LOG2_LCU_WIDTH - (depth))) * (width) \
+ + ((x) << (LOG2_LCU_WIDTH - (depth))))
+//#define SIGN3(x) ((x) > 0) ? +1 : ((x) == 0 ? 0 : -1)
+#define SIGN3(x) (((x) > 0) - ((x) < 0))
+
#define VERSION_STRING "0.2 "
#define VERSION 0.2
//#define VERBOSE 1
+#define SAO_ABS_OFFSET_MAX ((1 << (MIN(BIT_DEPTH, 10) - 5)) - 1)
+
#define SIZE_2Nx2N 0
#define SIZE_2NxN 1
@@ -92,6 +104,12 @@ typedef int16_t coefficient;
#define SIZE_NxN 3
#define SIZE_NONE 15
+// These are for marking incomplete implementations that break if slices or
+// tiles are used with asserts. They should be set to 1 if they are ever
+// implemented.
+#define USE_SLICES 0
+#define USE_TILES 0
+
/* Inlining functions */
#ifdef _MSC_VER /* Visual studio */
#define INLINE __forceinline
@@ -110,4 +128,9 @@ typedef int16_t coefficient;
#define FREE_POINTER(pointer) { free(pointer); pointer = NULL; }
#define MOVE_POINTER(dst_pointer,src_pointer) { dst_pointer = src_pointer; src_pointer = NULL; }
+typedef struct {
+ int x;
+ int y;
+} vector2d;
+
#endif
\ No newline at end of file
diff --git a/src/picture.c b/src/picture.c
index d7add68c..d0efd4e1 100644
--- a/src/picture.c
+++ b/src/picture.c
@@ -16,6 +16,8 @@
#include
#include
+#include "sao.h"
+
#define PSNRMAX (255.0 * 255.0)
@@ -66,6 +68,40 @@ void picture_set_block_residual(picture *pic, uint32_t x_scu, uint32_t y_scu,
}
}
+/**
+ * \brief BLock Image Transfer from one buffer to another.
+ *
+ * It's a stupidly simple loop that copies pixels.
+ *
+ * \param orig Start of the originating buffer.
+ * \param dst Start of the destination buffer.
+ * \param width Width of the copied region.
+ * \param height Height of the copied region.
+ * \param orig_stride Width of a row in the originating buffer.
+ * \param dst_stride Width of a row in the destination buffer.
+ *
+ * This should be inlined, but it's defined here for now to see if Visual
+ * Studios LTCG will inline it.
+ */
+void picture_blit_pixels(const pixel *orig, pixel *dst,
+ unsigned width, unsigned height,
+ unsigned orig_stride, unsigned dst_stride)
+{
+ unsigned y, x;
+
+ const pixel *borig = orig;
+ const pixel *bdst = dst;
+
+ for (y = 0; y < height; ++y) {
+ for (x = 0; x < width; ++x) {
+ dst[x] = orig[x];
+ }
+ // Move pointers to the next row.
+ orig += orig_stride;
+ dst += dst_stride;
+ }
+}
+
/**
* \brief Set block coded status
* \param pic picture to use
@@ -270,6 +306,11 @@ picture *picture_init(int32_t width, int32_t height,
pic->coeff_y = NULL; pic->coeff_u = NULL; pic->coeff_v = NULL;
pic->pred_y = NULL; pic->pred_u = NULL; pic->pred_v = NULL;
+ pic->slice_sao_luma_flag = 1;
+ pic->slice_sao_chroma_flag = 1;
+ pic->sao_luma = MALLOC(sao_info, width_in_lcu * height_in_lcu);
+ pic->sao_chroma = MALLOC(sao_info, width_in_lcu * height_in_lcu);
+
return pic;
}
@@ -309,6 +350,9 @@ int picture_destroy(picture *pic)
FREE_POINTER(pic->pred_u);
FREE_POINTER(pic->pred_v);
+ FREE_POINTER(pic->sao_luma);
+ FREE_POINTER(pic->sao_chroma);
+
return 1;
}
diff --git a/src/picture.h b/src/picture.h
index d04ddee3..ea11cae4 100644
--- a/src/picture.h
+++ b/src/picture.h
@@ -14,6 +14,9 @@
#include "global.h"
+//#include "sao.h"
+struct sao_info_struct;
+
//////////////////////////////////////////////////////////////////////////
// CONSTANTS
@@ -103,6 +106,10 @@ typedef struct
cu_info** cu_array; //!< \brief Info for each CU at each depth.
uint8_t type;
uint8_t slicetype;
+ uint8_t slice_sao_luma_flag;
+ uint8_t slice_sao_chroma_flag;
+ struct sao_info_struct *sao_luma; //!< \brief Array of sao parameters for every LCU.
+ struct sao_info_struct *sao_chroma; //!< \brief Array of sao parameters for every LCU.
} picture;
/**
@@ -130,6 +137,10 @@ void picture_set_block_split(picture *pic, uint32_t x_scu, uint32_t y_scu,
uint8_t depth, int8_t split);
void picture_set_block_skipped(picture *pic, uint32_t x_scu, uint32_t y_scu,
uint8_t depth, int8_t skipped);
+void picture_blit_pixels(const pixel* orig, pixel *dst,
+ unsigned width, unsigned height,
+ unsigned orig_stride, unsigned dst_stride);
+
picture_list * picture_list_init(int size);
int picture_list_resize(picture_list *list, int size);
int picture_list_destroy(picture_list *list);
diff --git a/src/sao.c b/src/sao.c
new file mode 100644
index 00000000..ff50e0ba
--- /dev/null
+++ b/src/sao.c
@@ -0,0 +1,362 @@
+/**
+ * \file
+ *
+ * \author Marko Viitanen ( fador@iki.fi ),
+ * Tampere University of Technology,
+ * Department of Pervasive Computing.
+ * \author Ari Koivula ( ari@koivu.la ),
+ * Tampere University of Technology,
+ * Department of Pervasive Computing.
+ */
+
+#include "sao.h"
+
+#include
+
+#include "picture.h"
+
+
+
+void init_sao_info(sao_info *sao) {
+ sao->type = SAO_TYPE_NONE;
+ sao->merge_left_flag = 0;
+ sao->merge_up_flag = 0;
+}
+
+// Mapping of edge_idx values to eo-classes.
+static const unsigned g_sao_eo_idx_to_eo_category[] = { 1, 2, 0, 3, 4 };
+// Mapping relationships between a, b and c to eo_idx.
+#define EO_IDX(a, b, c) (2 + SIGN3((c) - (a)) + SIGN3((c) - (b)))
+
+/**
+ * \param orig_data Original pixel data. 64x64 for luma, 32x32 for chroma.
+ * \param rec_data Reconstructed pixel data. 64x64 for luma, 32x32 for chroma.
+ * \param dir_offsets
+ * \param is_chroma 0 for luma, 1 for chroma. Indicates
+ */
+void calc_sao_edge_dir(const pixel *orig_data, const pixel *rec_data,
+ int eo_class, int block_width, int block_height,
+ int cat_sum_cnt[2][NUM_SAO_EDGE_CATEGORIES])
+{
+ int y, x;
+ vector2d a_ofs = g_sao_edge_offsets[eo_class][0];
+ vector2d b_ofs = g_sao_edge_offsets[eo_class][1];
+ // Arrays orig_data and rec_data are quarter size for chroma.
+
+ // Don't sample the edge pixels because this function doesn't have access to
+ // their neighbours.
+ for (y = 1; y < block_height - 1; ++y) {
+ for (x = 1; x < block_width - 1; ++x) {
+ const pixel *c_data = &rec_data[y * block_width + x];
+ pixel a = c_data[a_ofs.y * block_width + a_ofs.x];
+ pixel c = c_data[0];
+ pixel b = c_data[b_ofs.y * block_width + b_ofs.x];
+
+ int eo_idx = EO_IDX(a, b, c);
+ int eo_cat = g_sao_eo_idx_to_eo_category[eo_idx];
+
+ cat_sum_cnt[0][eo_cat] += orig_data[y * block_width + x] - c;
+ cat_sum_cnt[1][eo_cat] += 1;
+ }
+ }
+}
+
+void sao_reconstruct_color(const pixel *rec_data, pixel *new_rec_data, const sao_info *sao,
+ int stride, int new_stride, int block_width, int block_height)
+{
+ int y, x;
+ vector2d a_ofs = g_sao_edge_offsets[sao->eo_class][0];
+ vector2d b_ofs = g_sao_edge_offsets[sao->eo_class][1];
+ // Arrays orig_data and rec_data are quarter size for chroma.
+
+ // Don't sample the edge pixels because this function doesn't have access to
+ // their neighbours.
+ for (y = 0; y < block_height; ++y) {
+ for (x = 0; x < block_width; ++x) {
+ const pixel *c_data = &rec_data[y * stride + x];
+ pixel *new_data = &new_rec_data[y * new_stride + x];
+ pixel a = c_data[a_ofs.y * stride + a_ofs.x];
+ pixel c = c_data[0];
+ pixel b = c_data[b_ofs.y * stride + b_ofs.x];
+
+ int eo_idx = EO_IDX(a, b, c);
+ int eo_cat = g_sao_eo_idx_to_eo_category[eo_idx];
+
+ new_data[0] = CLIP(0, (1 << BIT_DEPTH) - 1, c_data[0] + sao->offsets[eo_cat]);
+ }
+ }
+}
+
+/**
+ * \brief Calculate dimensions of the buffer used by sao reconstruction.
+ *
+ * This function calculates 4 vectors that can be used to make the temporary
+ * buffers required by sao_reconstruct_color.
+ *
+ * Vector block is the area affected by sao. Vectors tr and br are top-left
+ * margin and bottom-right margin, which contain pixels that are not modified
+ * by the reconstruction of this LCU but are needed by the reconstruction.
+ * Vector rec is the offset from the CU to the required pixel area.
+ *
+ * The margins are always either 0 or 1, depending on the direction of the
+ * edge offset class.
+ *
+ * This also takes into account borders of the picture and non-LCU sized
+ * CU's at the bottom and right of the picture.
+ *
+ * \ 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
+ */
+void sao_calc_block_dims(const picture *pic, color_index color_i,
+ const sao_info *sao, vector2d *rec,
+ vector2d *tl, vector2d *br, vector2d *block)
+{
+ vector2d a_ofs = g_sao_edge_offsets[sao->eo_class][0];
+ vector2d b_ofs = g_sao_edge_offsets[sao->eo_class][1];
+ const int is_chroma = (color_i != COLOR_Y ? 1 : 0);
+ int width = pic->width >> is_chroma;
+ int height = pic->height >> is_chroma;
+ int block_width = LCU_WIDTH >> is_chroma;
+
+ // Handle top and left.
+ if (rec->y == 0) {
+ tl->y = 0;
+ if (a_ofs.y == -1 || b_ofs.y == -1) {
+ block->y -= 1;
+ tl->y += 1;
+ }
+ }
+ if (rec->x == 0) {
+ 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.
+ if (rec->y + block_width >= height) {
+ br->y = 0;
+ if (rec->y + block_width >= height) {
+ block->y = height - rec->y;
+ }
+ if (a_ofs.y == 1 || b_ofs.y == 1) {
+ block->y -= 1;
+ br->y += 1;
+ }
+ }
+ if (rec->x + block_width >= width) {
+ br->x = 0;
+ if (rec->x + block_width > width) {
+ block->x = width - rec->x;
+ }
+ if (a_ofs.x == 1 || b_ofs.x == 1) {
+ block->x -= 1;
+ br->x += 1;
+ }
+ }
+
+ rec->y = (rec->y == 0 ? 0 : -1);
+ rec->x = (rec->x == 0 ? 0 : -1);
+}
+
+void sao_reconstruct(picture *pic, const pixel *old_rec,
+ unsigned x_ctb, unsigned y_ctb,
+ const sao_info *sao, color_index color_i)
+{
+ const int is_chroma = (color_i != COLOR_Y ? 1 : 0);
+ const int pic_stride = pic->width >> is_chroma;
+ const int lcu_stride = LCU_WIDTH >> is_chroma;
+ const int buf_stride = lcu_stride + 2;
+
+ pixel *recdata = (color_i == COLOR_Y ? pic->y_recdata :
+ (color_i == COLOR_U ? pic->u_recdata : pic->v_recdata));
+ pixel buf_rec[(LCU_WIDTH + 2) * (LCU_WIDTH + 2)];
+ pixel new_rec[LCU_WIDTH * LCU_WIDTH];
+ // Calling CU_TO_PIXEL with depth 1 is the same as using block size of 32.
+ pixel *lcu_rec = &recdata[CU_TO_PIXEL(x_ctb, y_ctb, is_chroma, pic_stride)];
+ const pixel *old_lcu_rec = &old_rec[CU_TO_PIXEL(x_ctb, y_ctb, is_chroma, pic_stride)];
+
+ vector2d ofs;
+ vector2d tl = { 1, 1 };
+ vector2d br = { 1, 1 };
+ vector2d block = { LCU_WIDTH, LCU_WIDTH };
+
+ if (sao->type == SAO_TYPE_NONE) {
+ return;
+ }
+
+ ofs.x = x_ctb * lcu_stride;
+ ofs.y = y_ctb * lcu_stride;
+ block.x = lcu_stride;
+ block.y = lcu_stride;
+ sao_calc_block_dims(pic, color_i, sao, &ofs, &tl, &br, &block);
+
+ // Data to tmp buffer.
+ picture_blit_pixels(&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);
+
+ sao_reconstruct_color(&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);
+
+ // Copy reconstructed block from tmp buffer to rec image.
+ picture_blit_pixels(&new_rec[(tl.y + ofs.y) * lcu_stride + (tl.x + ofs.x)],
+ &lcu_rec[(tl.y + ofs.y) * pic_stride + (tl.x + ofs.x)],
+ block.x, block.y, lcu_stride, pic_stride);
+}
+
+
+
+void sao_search_best_mode(const pixel *data[], const pixel *recdata[],
+ int block_width, int block_height,
+ unsigned buf_cnt,
+ sao_info *sao_out)
+{
+ sao_eo_class edge_class;
+ // This array is used to calculate the mean offset used to minimize distortion.
+ int cat_sum_cnt[2][NUM_SAO_EDGE_CATEGORIES];
+ memset(cat_sum_cnt, 0, sizeof(int) * 2 * NUM_SAO_EDGE_CATEGORIES);
+
+ sao_out->ddistortion = INT_MAX;
+
+ for (edge_class = SAO_EO0; edge_class <= SAO_EO3; ++edge_class) {
+ int edge_offset[NUM_SAO_EDGE_CATEGORIES];
+ int sum_ddistortion = 0;
+ sao_eo_cat edge_cat;
+ unsigned i = 0;
+
+ // Call calc_sao_edge_dir once for luma and twice for chroma.
+ for (i = 0; i < buf_cnt; ++i) {
+ calc_sao_edge_dir(data[i], recdata[i], edge_class,
+ block_width, block_height, cat_sum_cnt);
+ }
+
+ for (edge_cat = SAO_EO_CAT1; edge_cat <= SAO_EO_CAT4; ++edge_cat) {
+ int cat_sum = cat_sum_cnt[0][edge_cat];
+ int cat_cnt = cat_sum_cnt[1][edge_cat];
+
+ // The optimum offset can be calculated by getting the minima of the
+ // fast ddistortion estimation formula. The minima is the mean error
+ // and we round that to the nearest integer.
+ int offset = 0;
+ if (cat_cnt != 0) {
+ offset = (cat_sum + (cat_cnt >> 1)) / cat_cnt;
+ offset = CLIP(-SAO_ABS_OFFSET_MAX, SAO_ABS_OFFSET_MAX, offset);
+ }
+
+ // Sharpening edge offsets can't be encoded, so set them to 0 here.
+ if (edge_cat >= SAO_EO_CAT1 && edge_cat <= SAO_EO_CAT2 && offset < 0) {
+ offset = 0;
+ }
+ if (edge_cat >= SAO_EO_CAT3 && edge_cat <= SAO_EO_CAT4 && offset > 0) {
+ offset = 0;
+ }
+
+ edge_offset[edge_cat] = offset;
+ // The ddistortion is amount by which the SSE of data changes. It should
+ // be negative for all categories, if offset was chosen correctly.
+ // ddistortion = N * h^2 - 2 * h * E, where N is the number of samples
+ // and E is the sum of errors.
+ // It basically says that all pixels that are not improved by offset
+ // increase increase SSE by h^2 and all pixels that are improved by
+ // offset decrease SSE by h*E.
+ sum_ddistortion += cat_cnt * offset * offset - 2 * offset * cat_sum;
+ }
+ // SAO is not applied for category 0.
+ edge_offset[SAO_EO_CAT0] = 0;
+
+ // Choose the offset class that offers the least error after offset.
+ if (sum_ddistortion < sao_out->ddistortion) {
+ sao_out->eo_class = edge_class;
+ sao_out->ddistortion = sum_ddistortion;
+ memcpy(sao_out->offsets, edge_offset, sizeof(int) * NUM_SAO_EDGE_CATEGORIES);
+ }
+ }
+}
+
+ void sao_search_chroma(const picture *pic, unsigned x_ctb, unsigned y_ctb, sao_info *sao)
+{
+ pixel orig_u[LCU_CHROMA_SIZE];
+ pixel rec_u[LCU_CHROMA_SIZE];
+ pixel orig_v[LCU_CHROMA_SIZE];
+ pixel rec_v[LCU_CHROMA_SIZE];
+ pixel *orig[2] = { orig_u, orig_v };
+ pixel *rec[2] = { rec_u, rec_v };
+ pixel *u_data = &pic->u_data[CU_TO_PIXEL(x_ctb, y_ctb, 1, pic->width / 2)];
+ pixel *u_recdata = &pic->u_recdata[CU_TO_PIXEL(x_ctb, y_ctb, 1, pic->width / 2)];
+ pixel *v_data = &pic->v_data[CU_TO_PIXEL(x_ctb, y_ctb, 1, pic->width / 2)];
+ pixel *v_recdata = &pic->v_recdata[CU_TO_PIXEL(x_ctb, y_ctb, 1, pic->width / 2)];
+ int block_width = (LCU_WIDTH / 2);
+ int block_height = (LCU_WIDTH / 2);
+
+ if (x_ctb * (LCU_WIDTH / 2) + (LCU_WIDTH / 2) >= (unsigned)pic->width / 2) {
+ block_width = (pic->width - x_ctb * LCU_WIDTH) / 2;
+ }
+ if (y_ctb * (LCU_WIDTH / 2) + (LCU_WIDTH / 2) >= (unsigned)pic->height / 2) {
+ block_height = (pic->height - y_ctb * LCU_WIDTH) / 2;
+ }
+
+ sao->type = SAO_TYPE_EDGE;
+
+ // Fill temporary buffers with picture data.
+ // These buffers are needed only until we switch to a LCU based data
+ // structure for pixels. Then we can give pointers directly to that structure
+ // without making copies.
+ picture_blit_pixels(u_data, orig_u, block_width, block_height,
+ pic->width / 2, LCU_WIDTH / 2);
+ picture_blit_pixels(v_data, orig_v, block_width, block_height,
+ pic->width / 2, LCU_WIDTH / 2);
+ picture_blit_pixels(u_recdata, rec_u, block_width, block_height,
+ pic->width / 2, LCU_WIDTH / 2);
+ picture_blit_pixels(v_recdata, rec_v, block_width, block_height,
+ pic->width / 2, LCU_WIDTH / 2);
+
+ sao_search_best_mode(orig, rec, block_width, block_height, 2, sao);
+}
+
+void sao_search_luma(const picture *pic, unsigned x_ctb, unsigned y_ctb, sao_info *sao)
+{
+ pixel orig_y[LCU_LUMA_SIZE];
+ pixel rec_y[LCU_LUMA_SIZE];
+ pixel *orig[1] = { orig_y };
+ pixel *rec[1] = { rec_y };
+ pixel *y_data = &pic->y_data[CU_TO_PIXEL(x_ctb, y_ctb, 0, pic->width)];
+ pixel *y_recdata = &pic->y_recdata[CU_TO_PIXEL(x_ctb, y_ctb, 0, pic->width)];
+ int block_width = LCU_WIDTH;
+ int block_height = LCU_WIDTH;
+
+ if (x_ctb * LCU_WIDTH + LCU_WIDTH >= (unsigned)pic->width) {
+ block_width = pic->width - x_ctb * LCU_WIDTH;
+ }
+ if (y_ctb * LCU_WIDTH + LCU_WIDTH >= (unsigned)pic->height) {
+ block_height = pic->height - y_ctb * LCU_WIDTH;
+ }
+
+ sao->type = SAO_TYPE_EDGE;
+
+ // Fill temporary buffers with picture data.
+ // These buffers are needed only until we switch to a LCU based data
+ // structure for pixels. Then we can give pointers directly to that structure
+ // without making copies.
+ picture_blit_pixels(y_data, orig_y, block_width, block_height, pic->width, LCU_WIDTH);
+ picture_blit_pixels(y_recdata, rec_y, block_width, block_height, pic->width, LCU_WIDTH);
+
+ sao_search_best_mode(orig, rec, block_width, block_height, 1, sao);
+}
diff --git a/src/sao.h b/src/sao.h
new file mode 100644
index 00000000..0be99eea
--- /dev/null
+++ b/src/sao.h
@@ -0,0 +1,54 @@
+#ifndef SAO_H_
+#define SAO_H_
+/**
+ * \file
+ * \brief Coding Unit (CU) and picture data related functions.
+ *
+ * \author Marko Viitanen ( fador@iki.fi ),
+ * Tampere University of Technology,
+ * Department of Pervasive Computing.
+ * \author Ari Koivula ( ari@koivu.la ),
+ * Tampere University of Technology,
+ * Department of Pervasive Computing.
+ */
+
+#include "global.h"
+#include "picture.h"
+
+
+typedef enum { COLOR_Y = 0, COLOR_U = 1, COLOR_V = 2, NUM_COLORS } color_index;
+typedef enum { SAO_TYPE_NONE = 0, SAO_TYPE_BAND, SAO_TYPE_EDGE } sao_type;
+typedef enum { SAO_EO0 = 0, SAO_EO1, SAO_EO2, SAO_EO3, SAO_NUM_EO } sao_eo_class;
+typedef enum { SAO_EO_CAT0 = 0, SAO_EO_CAT1, SAO_EO_CAT2, SAO_EO_CAT3, SAO_EO_CAT4, NUM_SAO_EDGE_CATEGORIES } sao_eo_cat;
+
+// Offsets of a and b in relation to c.
+// dir_offset[dir][a or b]
+// | | a | a | a |
+// | a c b | c | c | c |
+// | | b | b | b |
+static const vector2d g_sao_edge_offsets[SAO_NUM_EO][2] = {
+ { { -1, 0 }, { 1, 0 } },
+ { { 0, -1 }, { 0, 1 } },
+ { { -1, -1 }, { 1, 1 } },
+ { { 1, -1 }, { -1, 1 } }
+};
+
+
+typedef struct sao_info_struct {
+ sao_type type;
+ sao_eo_class eo_class;
+ int ddistortion;
+ int merge_left_flag;
+ int merge_up_flag;
+ int offsets[NUM_SAO_EDGE_CATEGORIES];
+} sao_info;
+
+
+void init_sao_info(sao_info *sao);
+void sao_search_chroma(const picture *pic, unsigned x_ctb, unsigned y_ctb, sao_info *sao);
+void sao_search_luma(const picture *pic, unsigned x_ctb, unsigned y_ctb, sao_info *sao);
+void sao_reconstruct(picture *pic, const pixel *old_rec,
+ unsigned x_ctb, unsigned y_ctb,
+ const sao_info *sao, color_index color_i);
+
+#endif
\ No newline at end of file
diff --git a/src/search.c b/src/search.c
index eb7ac666..87f3d6b8 100644
--- a/src/search.c
+++ b/src/search.c
@@ -35,11 +35,6 @@
&& (x) + (block_width) <= (width) \
&& (y) + (block_height) <= (height))
-typedef struct {
- int x;
- int y;
-} vector2d;
-
/**
* This is used in the hexagon_search to select 3 points to search.
*