From: Marco Paniconi Date: Fri, 14 Mar 2014 21:35:47 +0000 (-0700) Subject: In-frame q adjustment for cyclic background refresh. X-Git-Tag: v1.4.0~2047^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6b83884ba995640bf6d348173fdd299d95e3c1aa;p=libvpx In-frame q adjustment for cyclic background refresh. Activated using aq_mode=3. Change-Id: Ied628b9e7bd0e88b0c75790276bca75b19eb5c07 --- diff --git a/examples/vpx_temporal_scalable_patterns.c b/examples/vpx_temporal_scalable_patterns.c index 6ec1b6208..a360b8ec7 100644 --- a/examples/vpx_temporal_scalable_patterns.c +++ b/examples/vpx_temporal_scalable_patterns.c @@ -563,7 +563,8 @@ int main(int argc, char **argv) { vpx_codec_control(&codec, VP8E_SET_CPUUSED, -6); vpx_codec_control(&codec, VP8E_SET_NOISE_SENSITIVITY, 1); if (strncmp(encoder->name, "vp9", 3) == 0) { - vpx_codec_control(&codec, VP8E_SET_CPUUSED, 3); + vpx_codec_control(&codec, VP8E_SET_CPUUSED, 5); + vpx_codec_control(&codec, VP9E_SET_AQ_MODE, 3); vpx_codec_control(&codec, VP8E_SET_NOISE_SENSITIVITY, 0); if (vpx_codec_control(&codec, VP9E_SET_SVC, 1)) { die_codec(&codec, "Failed to set SVC"); @@ -576,6 +577,8 @@ int main(int argc, char **argv) { // value, like 100 or 200. max_intra_size_pct = (int) (((double)cfg.rc_buf_optimal_sz * 0.5) * ((double) cfg.g_timebase.den / cfg.g_timebase.num) / 10.0); + // For low-quality key frame. + max_intra_size_pct = 200; vpx_codec_control(&codec, VP8E_SET_MAX_INTRA_BITRATE_PCT, max_intra_size_pct); frame_avail = 1; diff --git a/test/aq_segment_test.cc b/test/aq_segment_test.cc new file mode 100644 index 000000000..2f88b539b --- /dev/null +++ b/test/aq_segment_test.cc @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include +#include +#include "third_party/googletest/src/include/gtest/gtest.h" +#include "test/codec_factory.h" +#include "test/encode_test_driver.h" +#include "test/i420_video_source.h" +#include "test/util.h" + +namespace { + +class AqSegmentTest : public ::libvpx_test::EncoderTest, + public ::libvpx_test::CodecTestWith2Params< + libvpx_test::TestMode, int> { + protected: + AqSegmentTest() : EncoderTest(GET_PARAM(0)) {} + + virtual void SetUp() { + InitializeConfig(); + SetMode(GET_PARAM(1)); + set_cpu_used_ = GET_PARAM(2); + aq_mode_ = 0; + } + + virtual void PreEncodeFrameHook(::libvpx_test::VideoSource *video, + ::libvpx_test::Encoder *encoder) { + if (video->frame() == 1) { + encoder->Control(VP8E_SET_CPUUSED, set_cpu_used_); + encoder->Control(VP9E_SET_AQ_MODE, aq_mode_); + encoder->Control(VP8E_SET_MAX_INTRA_BITRATE_PCT, 100); + } + } + + virtual void FramePktHook(const vpx_codec_cx_pkt_t *pkt) { + if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) { + } + } + int set_cpu_used_; + int aq_mode_; +}; + +// Validate that this AQ segmentation mode (AQ=1, variance_ap) +// encodes and decodes without a mismatch. +TEST_P(AqSegmentTest, TestNoMisMatchAQ1) { + cfg_.rc_min_quantizer = 8; + cfg_.rc_max_quantizer = 56; + cfg_.rc_end_usage = VPX_CBR; + cfg_.g_lag_in_frames = 0; + cfg_.rc_buf_initial_sz = 500; + cfg_.rc_buf_optimal_sz = 500; + cfg_.rc_buf_sz = 1000; + cfg_.rc_target_bitrate = 300; + + aq_mode_ = 1; + + ::libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, + 30, 1, 0, 100); + + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); +} + +// Validate that this AQ segmentation mode (AQ=2, complexity_aq) +// encodes and decodes without a mismatch. +TEST_P(AqSegmentTest, TestNoMisMatchAQ2) { + cfg_.rc_min_quantizer = 8; + cfg_.rc_max_quantizer = 56; + cfg_.rc_end_usage = VPX_CBR; + cfg_.g_lag_in_frames = 0; + cfg_.rc_buf_initial_sz = 500; + cfg_.rc_buf_optimal_sz = 500; + cfg_.rc_buf_sz = 1000; + cfg_.rc_target_bitrate = 300; + + aq_mode_ = 2; + + ::libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, + 30, 1, 0, 100); + + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); +} + +// Validate that this AQ segmentation mode (AQ=3, cyclic_refresh_aq) +// encodes and decodes without a mismatch. +TEST_P(AqSegmentTest, TestNoMisMatchAQ3) { + cfg_.rc_min_quantizer = 8; + cfg_.rc_max_quantizer = 56; + cfg_.rc_end_usage = VPX_CBR; + cfg_.g_lag_in_frames = 0; + cfg_.rc_buf_initial_sz = 500; + cfg_.rc_buf_optimal_sz = 500; + cfg_.rc_buf_sz = 1000; + cfg_.rc_target_bitrate = 300; + + aq_mode_ = 3; + + ::libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, + 30, 1, 0, 100); + + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); +} + +using std::tr1::make_tuple; + +#define VP9_FACTORY \ + static_cast (&libvpx_test::kVP9) + +VP9_INSTANTIATE_TEST_CASE(AqSegmentTest, + ::testing::Values(::libvpx_test::kRealTime, + ::libvpx_test::kOnePassGood), + ::testing::Range(3, 9)); +} // namespace diff --git a/test/test.mk b/test/test.mk index 00b35dca8..175bc520f 100644 --- a/test/test.mk +++ b/test/test.mk @@ -18,6 +18,7 @@ LIBVPX_TEST_SRCS-yes += video_source.h LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += ../md5_utils.h ../md5_utils.c LIBVPX_TEST_SRCS-$(CONFIG_DECODERS) += ivf_video_source.h LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS) += ../y4minput.h ../y4minput.c +LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS) += aq_segment_test.cc LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS) += datarate_test.cc LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS) += error_resilience_test.cc LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS) += i420_video_source.h diff --git a/vp9/encoder/vp9_craq.c b/vp9/encoder/vp9_craq.c new file mode 100644 index 000000000..40437c77f --- /dev/null +++ b/vp9/encoder/vp9_craq.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2014 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include + +#include "vp9/encoder/vp9_craq.h" + +#include "vp9/common/vp9_seg_common.h" + +#include "vp9/encoder/vp9_ratectrl.h" +#include "vp9/encoder/vp9_rdopt.h" +#include "vp9/encoder/vp9_segmentation.h" + + +// Check if we should turn off cyclic refresh based on bitrate condition. +static int apply_cyclic_refresh_bitrate(VP9_COMP *const cpi) { + // Turn off cyclic refresh if bits available per frame is not sufficiently + // larger than bit cost of segmentation. Segment map bit cost should scale + // with number of seg blocks, so compare available bits to number of blocks. + // Average bits available per frame = av_per_frame_bandwidth + // Number of (8x8) blocks in frame = mi_rows * mi_cols; + float factor = 0.5; + int number_blocks = cpi->common.mi_rows * cpi->common.mi_cols; + // The condition below corresponds to turning off at target bitrates: + // ~24kbps for CIF, 72kbps for VGA (at 30fps). + if (cpi->rc.av_per_frame_bandwidth < factor * number_blocks) + return 0; + else + return 1; +} + +// Check if this coding block, of size bsize, should be considered for refresh +// (lower-qp coding). Decision can be based on various factors, such as +// size of the coding block (i.e., below min_block size rejected), coding +// mode, and rate/distortion. +static int candidate_refresh_aq(VP9_COMP *const cpi, + MODE_INFO *const mi, + int bsize, + int use_rd) { + CYCLIC_REFRESH *const cr = &cpi->cyclic_refresh; + if (use_rd) { + // If projected rate is below the thresh_rate (well below target, + // so undershoot expected), accept it for lower-qp coding. + if (cr->projected_rate_sb < cr->thresh_rate_sb) + return 1; + // Otherwise, reject the block for lower-qp coding if any of the following: + // 1) prediction block size is below min_block_size + // 2) mode is non-zero mv and projected distortion is above thresh_dist + // 3) mode is an intra-mode (we may want to allow some of this under + // another thresh_dist) + else if ((bsize < cr->min_block_size) || + (mi->mbmi.mv[0].as_int != 0 && + cr->projected_dist_sb > cr->thresh_dist_sb) || + !is_inter_block(&mi->mbmi)) + return 0; + else + return 1; + } else { + // Rate/distortion not used for update. + if ((bsize < cr->min_block_size) || + (mi->mbmi.mv[0].as_int != 0) || + !is_inter_block(&mi->mbmi)) + return 0; + else + return 1; + } +} + +// Prior to coding a given prediction block, of size bsize at (mi_row, mi_col), +// check if we should reset the segment_id, and update the cyclic_refresh map +// and segmentation map. +void vp9_update_segment_aq(VP9_COMP *const cpi, + MODE_INFO *const mi, + int mi_row, + int mi_col, + int bsize, + int use_rd) { + CYCLIC_REFRESH *const cr = &cpi->cyclic_refresh; + VP9_COMMON *const cm = &cpi->common; + const int bw = num_8x8_blocks_wide_lookup[bsize]; + const int bh = num_8x8_blocks_high_lookup[bsize]; + const int xmis = MIN(cm->mi_cols - mi_col, bw); + const int ymis = MIN(cm->mi_rows - mi_row, bh); + const int block_index = mi_row * cm->mi_cols + mi_col; + // Default is to not update the refresh map. + int new_map_value = cr->map[block_index]; + int x = 0; int y = 0; + int current_segment = mi->mbmi.segment_id; + int refresh_this_block = candidate_refresh_aq(cpi, mi, bsize, use_rd); + // Check if we should reset the segment_id for this block. + if (current_segment && !refresh_this_block) + mi->mbmi.segment_id = 0; + + // Update the cyclic refresh map, to be used for setting segmentation map + // for the next frame. If the block will be refreshed this frame, mark it + // as clean. The magnitude of the -ve influences how long before we consider + // it for refresh again. + if (mi->mbmi.segment_id == 1) { + new_map_value = -cr->time_for_refresh; + } else if (refresh_this_block) { + // Else if it is accepted as candidate for refresh, and has not already + // been refreshed (marked as 1) then mark it as a candidate for cleanup + // for future time (marked as 0), otherwise don't update it. + if (cr->map[block_index] == 1) + new_map_value = 0; + } else { + // Leave it marked as block that is not candidate for refresh. + new_map_value = 1; + } + // Update entries in the cyclic refresh map with new_map_value, and + // copy mbmi->segment_id into global segmentation map. + for (y = 0; y < ymis; y++) + for (x = 0; x < xmis; x++) { + cr->map[block_index + y * cm->mi_cols + x] = new_map_value; + cpi->segmentation_map[block_index + y * cm->mi_cols + x] = + mi->mbmi.segment_id; + } + // Keep track of actual number (in units of 8x8) of blocks in segment 1 used + // for encoding this frame. + if (mi->mbmi.segment_id) + cr->num_seg_blocks += xmis * ymis; +} + +// Setup cyclic background refresh: set delta q and segmentation map. +void vp9_setup_cyclic_refresh_aq(VP9_COMP *const cpi) { + VP9_COMMON *const cm = &cpi->common; + CYCLIC_REFRESH *const cr = &cpi->cyclic_refresh; + struct segmentation *const seg = &cm->seg; + unsigned char *seg_map = cpi->segmentation_map; + int apply_cyclic_refresh = apply_cyclic_refresh_bitrate(cpi); + // Don't apply refresh on key frame or enhancement layer frames. + if (!apply_cyclic_refresh || + (cpi->common.frame_type == KEY_FRAME) || + (cpi->svc.temporal_layer_id > 0)) { + // Set segmentation map to 0 and disable. + vpx_memset(seg_map, 0, cm->mi_rows * cm->mi_cols); + vp9_disable_segmentation(&cm->seg); + if (cpi->common.frame_type == KEY_FRAME) + cr->mb_index = 0; + return; + } else { + int qindex_delta = 0; + int mbs_in_frame = cm->mi_rows * cm->mi_cols; + int i, x, y, block_count, bl_index, bl_index2; + int sum_map, new_value, mi_row, mi_col, xmis, ymis, qindex2; + + // Rate target ratio to set q delta. + float rate_ratio_qdelta = 2.0; + vp9_clear_system_state(); + // Some of these parameters may be set via codec-control function later. + cr->max_mbs_perframe = 10; + cr->max_qdelta_perc = 50; + cr->min_block_size = BLOCK_16X16; + cr->time_for_refresh = 1; + // Set rate threshold to some fraction of target (and scaled by 256). + cr->thresh_rate_sb = (cpi->rc.sb64_target_rate * 256) >> 2; + // Distortion threshold, quadratic in Q, scale factor to be adjusted. + cr->thresh_dist_sb = 8 * (int)(vp9_convert_qindex_to_q(cm->base_qindex) * + vp9_convert_qindex_to_q(cm->base_qindex)); + if (cpi->sf.use_nonrd_pick_mode) { + // May want to be more conservative with thresholds in non-rd mode for now + // as rate/distortion are derived from model based on prediction residual. + cr->thresh_rate_sb = (cpi->rc.sb64_target_rate * 256) >> 3; + cr->thresh_dist_sb = 4 * (int)(vp9_convert_qindex_to_q(cm->base_qindex) * + vp9_convert_qindex_to_q(cm->base_qindex)); + } + + cr->num_seg_blocks = 0; + // Set up segmentation. + // Clear down the segment map. + vpx_memset(seg_map, 0, cm->mi_rows * cm->mi_cols); + vp9_enable_segmentation(&cm->seg); + vp9_clearall_segfeatures(seg); + // Select delta coding method. + seg->abs_delta = SEGMENT_DELTADATA; + + // Note: setting temporal_update has no effect, as the seg-map coding method + // (temporal or spatial) is determined in vp9_choose_segmap_coding_method(), + // based on the coding cost of each method. For error_resilient mode on the + // last_frame_seg_map is set to 0, so if temporal coding is used, it is + // relative to 0 previous map. + // seg->temporal_update = 0; + + // Segment 0 "Q" feature is disabled so it defaults to the baseline Q. + vp9_disable_segfeature(seg, 0, SEG_LVL_ALT_Q); + // Use segment 1 for in-frame Q adjustment. + vp9_enable_segfeature(seg, 1, SEG_LVL_ALT_Q); + + // Set the q delta for segment 1. + qindex_delta = vp9_compute_qdelta_by_rate(cpi, + cm->base_qindex, + rate_ratio_qdelta); + // TODO(marpan): Incorporate the actual-vs-target rate over/undershoot from + // previous encoded frame. + if ((-qindex_delta) > cr->max_qdelta_perc * cm->base_qindex / 100) { + qindex_delta = -cr->max_qdelta_perc * cm->base_qindex / 100; + } + + // Compute rd-mult for segment 1. + qindex2 = clamp(cm->base_qindex + cm->y_dc_delta_q + qindex_delta, 0, MAXQ); + cr->rdmult = vp9_compute_rd_mult(cpi, qindex2); + + vp9_set_segdata(seg, 1, SEG_LVL_ALT_Q, qindex_delta); + // Number of target macroblocks to get the q delta (segment 1). + block_count = cr->max_mbs_perframe * mbs_in_frame / 100; + // Set the segmentation map: cycle through the macroblocks, starting at + // cr->mb_index, and stopping when either block_count blocks have been found + // to be refreshed, or we have passed through whole frame. + // Note the setting of seg_map below is done in two steps (one over 8x8) + // and then another over SB, in order to keep the value constant over SB. + // TODO(marpan): Do this in one pass in SB order. + assert(cr->mb_index < mbs_in_frame); + i = cr->mb_index; + do { + // If the macroblock is as a candidate for clean up then mark it + // for possible boost/refresh (segment 1). The segment id may get reset to + // 0 later if the macroblock gets coded anything other than ZEROMV. + if (cr->map[i] == 0) { + seg_map[i] = 1; + block_count--; + } else if (cr->map[i] < 0) { + cr->map[i]++; + } + i++; + if (i == mbs_in_frame) { + i = 0; + } + } while (block_count && i != cr->mb_index); + cr->mb_index = i; + // Enforce constant segment map over superblock. + for (mi_row = 0; mi_row < cm->mi_rows; mi_row += MI_BLOCK_SIZE) + for (mi_col = 0; mi_col < cm->mi_cols; mi_col += MI_BLOCK_SIZE) { + bl_index = mi_row * cm->mi_cols + mi_col; + xmis = num_8x8_blocks_wide_lookup[BLOCK_64X64]; + ymis = num_8x8_blocks_high_lookup[BLOCK_64X64]; + xmis = MIN(cm->mi_cols - mi_col, xmis); + ymis = MIN(cm->mi_rows - mi_row, ymis); + sum_map = 0; + for (y = 0; y < ymis; y++) + for (x = 0; x < xmis; x++) { + bl_index2 = bl_index + y * cm->mi_cols + x; + sum_map += seg_map[bl_index2]; + } + new_value = 0; + // If segment is partial over superblock, reset. + if (sum_map > 0 && sum_map < xmis * ymis) { + if (sum_map < xmis * ymis / 2) + new_value = 0; + else + new_value = 1; + for (y = 0; y < ymis; y++) + for (x = 0; x < xmis; x++) { + bl_index2 = bl_index + y * cm->mi_cols + x; + seg_map[bl_index2] = new_value; + } + } + } + } +} diff --git a/vp9/encoder/vp9_craq.h b/vp9/encoder/vp9_craq.h new file mode 100644 index 000000000..1f81f3e77 --- /dev/null +++ b/vp9/encoder/vp9_craq.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + + +#ifndef VP9_ENCODER_VP9_CRAQ_H_ +#define VP9_ENCODER_VP9_CRAQ_H_ + +#include "vp9/encoder/vp9_onyx_int.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Check if we should turn off cyclic refresh based on bitrate condition. +static int apply_cyclic_refresh_bitrate(VP9_COMP *const cpi); + +// Check if this coding block, of size bsize, should be considered for refresh +// (lower-qp coding). +static int candidate_refresh_aq(VP9_COMP *const cpi, + MODE_INFO *const mi, + int bsize, + int use_rd); + +// Prior to coding a given prediction block, of size bsize at (mi_row, mi_col), +// check if we should reset the segment_id, and update the cyclic_refresh map +// and segmentation map. +void vp9_update_segment_aq(VP9_COMP *const cpi, + MODE_INFO *const mi, + int mi_row, + int mi_col, + int bsize, + int use_rd); + +// Setup cyclic background refresh: set delta q and segmentation map. +void vp9_setup_cyclic_refresh_aq(VP9_COMP *const cpi); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // VP9_ENCODER_VP9_CRAQ_H_ diff --git a/vp9/encoder/vp9_encodeframe.c b/vp9/encoder/vp9_encodeframe.c index f5da91fbc..87ebc3de0 100644 --- a/vp9/encoder/vp9_encodeframe.c +++ b/vp9/encoder/vp9_encodeframe.c @@ -39,6 +39,7 @@ #include "vp9/encoder/vp9_segmentation.h" #include "vp9/encoder/vp9_tokenize.h" #include "vp9/encoder/vp9_vaq.h" +#include "vp9/encoder/vp9_craq.h" #define GF_ZEROMV_ZBIN_BOOST 0 #define LF_ZEROMV_ZBIN_BOOST 0 @@ -875,7 +876,8 @@ static void select_in_frame_q_segment(VP9_COMP *cpi, } static void update_state(VP9_COMP *cpi, PICK_MODE_CONTEXT *ctx, - BLOCK_SIZE bsize, int output_enabled) { + int mi_row, int mi_col, BLOCK_SIZE bsize, + int output_enabled) { int i, x_idx, y; VP9_COMMON *const cm = &cpi->common; MACROBLOCK *const x = &cpi->mb; @@ -885,6 +887,7 @@ static void update_state(VP9_COMP *cpi, PICK_MODE_CONTEXT *ctx, MODE_INFO *mi = &ctx->mic; MB_MODE_INFO *const mbmi = &xd->mi_8x8[0]->mbmi; MODE_INFO *mi_addr = xd->mi_8x8[0]; + const struct segmentation *const seg = &cm->seg; const int mis = cm->mode_info_stride; const int mi_width = num_8x8_blocks_wide_lookup[bsize]; @@ -895,8 +898,16 @@ static void update_state(VP9_COMP *cpi, PICK_MODE_CONTEXT *ctx, // For in frame adaptive Q copy over the chosen segment id into the // mode innfo context for the chosen mode / partition. - if ((cpi->oxcf.aq_mode == COMPLEXITY_AQ) && output_enabled) + if ((cpi->oxcf.aq_mode == COMPLEXITY_AQ || + cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) && + output_enabled) { + // Check for reseting segment_id and update cyclic map. + if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ && seg->enabled) { + vp9_update_segment_aq(cpi, xd->mi_8x8[0], mi_row, mi_col, bsize, 1); + vp9_init_plane_quantizers(cpi, x); + } mi->mbmi.segment_id = xd->mi_8x8[0]->mbmi.segment_id; + } *mi_addr = *mi; @@ -924,10 +935,8 @@ static void update_state(VP9_COMP *cpi, PICK_MODE_CONTEXT *ctx, xd->mi_8x8[x_idx + y * mis] = mi_addr; } - if ((cpi->oxcf.aq_mode == VARIANCE_AQ) || - (cpi->oxcf.aq_mode == COMPLEXITY_AQ)) { + if (cpi->oxcf.aq_mode) vp9_init_plane_quantizers(cpi, x); - } // FIXME(rbultje) I'm pretty sure this should go to the end of this block // (i.e. after the output_enabled) @@ -1095,9 +1104,14 @@ static void rd_pick_sb_modes(VP9_COMP *cpi, const TileInfo *const tile, unsigned char complexity = cpi->complexity_map[mi_offset]; const int is_edge = (mi_row <= 1) || (mi_row >= (cm->mi_rows - 2)) || (mi_col <= 1) || (mi_col >= (cm->mi_cols - 2)); - if (!is_edge && (complexity > 128)) x->rdmult += ((x->rdmult * (complexity - 128)) / 256); + } else if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) { + const uint8_t *const map = cm->seg.update_map ? cpi->segmentation_map + : cm->last_frame_seg_map; + // If segment 1, use rdmult for that segment. + if (vp9_get_segment_id(cm, map, bsize, mi_row, mi_col)) + x->rdmult = cpi->cyclic_refresh.rdmult; } // Find best coding mode & reconstruct the MB so it is available @@ -1120,7 +1134,8 @@ static void rd_pick_sb_modes(VP9_COMP *cpi, const TileInfo *const tile, vp9_clear_system_state(); *totalrate = (int)round(*totalrate * rdmult_ratio); } - } else if (aq_mode == COMPLEXITY_AQ) { + } else if ((cpi->oxcf.aq_mode == COMPLEXITY_AQ) || + (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ)) { x->rdmult = orig_rdmult; } } @@ -1257,7 +1272,8 @@ static void encode_b(VP9_COMP *cpi, const TileInfo *const tile, return; } set_offsets(cpi, tile, mi_row, mi_col, bsize); - update_state(cpi, get_block_context(x, bsize), bsize, output_enabled); + update_state(cpi, get_block_context(x, bsize), mi_row, mi_col, bsize, + output_enabled); encode_superblock(cpi, tp, output_enabled, mi_row, mi_col, bsize); if (output_enabled) { @@ -1444,15 +1460,23 @@ static int sb_has_motion(const VP9_COMMON *cm, MODE_INFO **prev_mi_8x8) { return 0; } -static void update_state_rt(VP9_COMP *cpi, const PICK_MODE_CONTEXT *ctx) { +static void update_state_rt(VP9_COMP *cpi, const PICK_MODE_CONTEXT *ctx, + int mi_row, int mi_col, int bsize) { int i; VP9_COMMON *const cm = &cpi->common; MACROBLOCK *const x = &cpi->mb; MACROBLOCKD *const xd = &x->e_mbd; MB_MODE_INFO *const mbmi = &xd->mi_8x8[0]->mbmi; + const struct segmentation *const seg = &cm->seg; x->skip = ctx->skip; + // Check for reseting segment_id and update cyclic map. + if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ && seg->enabled) { + vp9_update_segment_aq(cpi, xd->mi_8x8[0], mi_row, mi_col, bsize, 1); + vp9_init_plane_quantizers(cpi, x); + } + #if CONFIG_INTERNAL_STATS if (frame_is_intra_only(cm)) { static const int kf_mode_index[] = { @@ -1502,7 +1526,7 @@ static void encode_b_rt(VP9_COMP *cpi, const TileInfo *const tile, return; } set_offsets(cpi, tile, mi_row, mi_col, bsize); - update_state_rt(cpi, get_block_context(x, bsize)); + update_state_rt(cpi, get_block_context(x, bsize), mi_row, mi_col, bsize); encode_superblock(cpi, tp, output_enabled, mi_row, mi_col, bsize); update_stats(cpi); @@ -1703,7 +1727,8 @@ static void rd_use_partition(VP9_COMP *cpi, bsize >= BLOCK_8X8 && mi_row + (mh >> 1) < cm->mi_rows) { int rt = 0; int64_t dt = 0; - update_state(cpi, get_block_context(x, subsize), subsize, 0); + update_state(cpi, get_block_context(x, subsize), mi_row, mi_col, + subsize, 0); encode_superblock(cpi, tp, 0, mi_row, mi_col, subsize); *get_sb_index(x, subsize) = 1; rd_pick_sb_modes(cpi, tile, mi_row + (ms >> 1), mi_col, &rt, &dt, @@ -1727,7 +1752,8 @@ static void rd_use_partition(VP9_COMP *cpi, bsize >= BLOCK_8X8 && mi_col + (ms >> 1) < cm->mi_cols) { int rt = 0; int64_t dt = 0; - update_state(cpi, get_block_context(x, subsize), subsize, 0); + update_state(cpi, get_block_context(x, subsize), mi_row, mi_col, + subsize, 0); encode_superblock(cpi, tp, 0, mi_row, mi_col, subsize); *get_sb_index(x, subsize) = 1; rd_pick_sb_modes(cpi, tile, mi_row, mi_col + (ms >> 1), &rt, &dt, @@ -1875,6 +1901,10 @@ static void rd_use_partition(VP9_COMP *cpi, select_in_frame_q_segment(cpi, mi_row, mi_col, output_enabled, chosen_rate); } + if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) { + cpi->cyclic_refresh.projected_rate_sb = chosen_rate; + cpi->cyclic_refresh.projected_dist_sb = chosen_dist; + } encode_sb(cpi, tile, tp, mi_row, mi_col, output_enabled, bsize); } @@ -2217,7 +2247,8 @@ static void rd_pick_partition(VP9_COMP *cpi, const TileInfo *const tile, sum_rd = RDCOST(x->rdmult, x->rddiv, sum_rate, sum_dist); if (sum_rd < best_rd && mi_row + ms < cm->mi_rows) { - update_state(cpi, get_block_context(x, subsize), subsize, 0); + update_state(cpi, get_block_context(x, subsize), mi_row, mi_col, + subsize, 0); encode_superblock(cpi, tp, 0, mi_row, mi_col, subsize); *get_sb_index(x, subsize) = 1; @@ -2269,7 +2300,8 @@ static void rd_pick_partition(VP9_COMP *cpi, const TileInfo *const tile, get_block_context(x, subsize), best_rd); sum_rd = RDCOST(x->rdmult, x->rddiv, sum_rate, sum_dist); if (sum_rd < best_rd && mi_col + ms < cm->mi_cols) { - update_state(cpi, get_block_context(x, subsize), subsize, 0); + update_state(cpi, get_block_context(x, subsize), mi_row, mi_col, + subsize, 0); encode_superblock(cpi, tp, 0, mi_row, mi_col, subsize); *get_sb_index(x, subsize) = 1; @@ -2323,6 +2355,11 @@ static void rd_pick_partition(VP9_COMP *cpi, const TileInfo *const tile, if ((cpi->oxcf.aq_mode == COMPLEXITY_AQ) && cm->seg.update_map) { select_in_frame_q_segment(cpi, mi_row, mi_col, output_enabled, best_rate); } + if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) { + cpi->cyclic_refresh.projected_rate_sb = best_rate; + cpi->cyclic_refresh.projected_dist_sb = best_dist; + } + encode_sb(cpi, tile, tp, mi_row, mi_col, output_enabled, bsize); } if (bsize == BLOCK_64X64) { @@ -2781,8 +2818,13 @@ static void nonrd_use_partition(VP9_COMP *cpi, assert("Invalid partition type."); } - if (bsize == BLOCK_64X64 && output_enabled) + if (bsize == BLOCK_64X64 && output_enabled) { + if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) { + cpi->cyclic_refresh.projected_rate_sb = *totrate; + cpi->cyclic_refresh.projected_dist_sb = *totdist; + } encode_sb_rt(cpi, tile, tp, mi_row, mi_col, 1, bsize); + } } static void encode_nonrd_sb_row(VP9_COMP *cpi, const TileInfo *const tile, @@ -3168,7 +3210,8 @@ static void encode_superblock(VP9_COMP *cpi, TOKENEXTRA **t, int output_enabled, const int mi_height = num_8x8_blocks_high_lookup[bsize]; x->skip_recode = !x->select_txfm_size && mbmi->sb_type >= BLOCK_8X8 && - (cpi->oxcf.aq_mode != COMPLEXITY_AQ) && + (cpi->oxcf.aq_mode != COMPLEXITY_AQ && + cpi->oxcf.aq_mode != CYCLIC_REFRESH_AQ) && !cpi->sf.use_nonrd_pick_mode; x->skip_optimize = ctx->is_coded; ctx->is_coded = 1; diff --git a/vp9/encoder/vp9_onyx_if.c b/vp9/encoder/vp9_onyx_if.c index c3acd2e56..b0e5e1a5d 100644 --- a/vp9/encoder/vp9_onyx_if.c +++ b/vp9/encoder/vp9_onyx_if.c @@ -28,6 +28,7 @@ #include "vp9/common/vp9_tile_common.h" #include "vp9/encoder/vp9_bitstream.h" +#include "vp9/encoder/vp9_craq.h" #include "vp9/encoder/vp9_encodeframe.h" #include "vp9/encoder/vp9_encodemv.h" #include "vp9/encoder/vp9_firstpass.h" @@ -173,6 +174,8 @@ static void dealloc_compressor_data(VP9_COMP *cpi) { vpx_free(cpi->complexity_map); cpi->complexity_map = 0; + vpx_free(cpi->cyclic_refresh.map); + cpi->cyclic_refresh.map = 0; vpx_free(cpi->active_map); cpi->active_map = 0; @@ -200,8 +203,7 @@ static void dealloc_compressor_data(VP9_COMP *cpi) { } // Computes a q delta (in "q index" terms) to get from a starting q value -// to a target value -// target q value +// to a target q value int vp9_compute_qdelta(const VP9_COMP *cpi, double qstart, double qtarget) { const RATE_CONTROL *const rc = &cpi->rc; int start_index = rc->worst_quality; @@ -226,10 +228,9 @@ int vp9_compute_qdelta(const VP9_COMP *cpi, double qstart, double qtarget) { } // Computes a q delta (in "q index" terms) to get from a starting q value -// to a value that should equate to thegiven rate ratio. - -static int compute_qdelta_by_rate(VP9_COMP *cpi, int base_q_index, - double rate_target_ratio) { +// to a value that should equate to the given rate ratio. +int vp9_compute_qdelta_by_rate(VP9_COMP *cpi, int base_q_index, + double rate_target_ratio) { int i; int target_index = cpi->rc.worst_quality; @@ -282,8 +283,10 @@ static void setup_in_frame_q_adj(VP9_COMP *cpi) { // Use some of the segments for in frame Q adjustment for (segment = 1; segment < 2; segment++) { - const int qindex_delta = compute_qdelta_by_rate(cpi, cm->base_qindex, - in_frame_q_adj_ratio[segment]); + const int qindex_delta = + vp9_compute_qdelta_by_rate(cpi, + cm->base_qindex, + in_frame_q_adj_ratio[segment]); vp9_enable_segfeature(seg, segment, SEG_LVL_ALT_Q); vp9_set_segdata(seg, segment, SEG_LVL_ALT_Q, qindex_delta); } @@ -1648,6 +1651,9 @@ VP9_COMP *vp9_create_compressor(VP9_CONFIG *oxcf) { CHECK_MEM_ERROR(cm, cpi->complexity_map, vpx_calloc(cm->mi_rows * cm->mi_cols, 1)); + // Create a map used for cyclic background refresh. + CHECK_MEM_ERROR(cm, cpi->cyclic_refresh.map, + vpx_calloc(cm->mi_rows * cm->mi_cols, 1)); // And a place holder structure is the coding context // for use if we want to save and restore it @@ -2707,6 +2713,8 @@ static void encode_without_recode_loop(VP9_COMP *cpi, vp9_vaq_frame_setup(cpi); } else if (cpi->oxcf.aq_mode == COMPLEXITY_AQ) { setup_in_frame_q_adj(cpi); + } else if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) { + vp9_setup_cyclic_refresh_aq(cpi); } // transform / motion compensation build reconstruction frame vp9_encode_frame(cpi); diff --git a/vp9/encoder/vp9_onyx_int.h b/vp9/encoder/vp9_onyx_int.h index 271c44632..ad1dd9b2a 100644 --- a/vp9/encoder/vp9_onyx_int.h +++ b/vp9/encoder/vp9_onyx_int.h @@ -435,6 +435,32 @@ typedef enum { USAGE_CONSTANT_QUALITY = 3, } END_USAGE; +typedef struct { + // Target percentage of blocks per frame that are cyclicly refreshed. + int max_mbs_perframe; + // Maximum q-delta as percentage of base q. + int max_qdelta_perc; + // Block size below which we don't apply cyclic refresh. + BLOCK_SIZE min_block_size; + // Macroblock starting index (unit of 8x8) for cycling through the frame. + int mb_index; + // Controls how long a block will need to wait to be refreshed again. + int time_for_refresh; + // Actual number of blocks that were applied delta-q (segment 1). + int num_seg_blocks; + // Actual encoding bits for segment 1. + int actual_seg_bits; + // RD mult. parameters for segment 1. + int rdmult; + // Cyclic refresh map. + signed char *map; + // Projected rate and distortion for the current superblock. + int64_t projected_rate_sb; + int64_t projected_dist_sb; + // Thresholds applied to projected rate/distortion of the superblock. + int64_t thresh_rate_sb; + int64_t thresh_dist_sb; +} CYCLIC_REFRESH; typedef enum { // Good Quality Fast Encoding. The encoder balances quality with the // amount of time it takes to encode the output. (speed setting @@ -478,6 +504,7 @@ typedef enum { NO_AQ = 0, VARIANCE_AQ = 1, COMPLEXITY_AQ = 2, + CYCLIC_REFRESH_AQ = 3, AQ_MODE_COUNT // This should always be the last member of the enum } AQ_MODE; @@ -725,6 +752,8 @@ typedef struct VP9_COMP { unsigned char *active_map; unsigned int active_map_enabled; + CYCLIC_REFRESH cyclic_refresh; + fractional_mv_step_fp *find_fractional_mv_step; fractional_mv_step_comp_fp *find_fractional_mv_step_comp; vp9_full_search_fn_t full_search_sad; @@ -906,6 +935,9 @@ void vp9_alloc_compressor_data(VP9_COMP *cpi); int vp9_compute_qdelta(const VP9_COMP *cpi, double qstart, double qtarget); +int vp9_compute_qdelta_by_rate(VP9_COMP *cpi, int base_q_index, + double rate_target_ratio); + static int get_token_alloc(int mb_rows, int mb_cols) { return mb_rows * mb_cols * (48 * 16 + 4); } diff --git a/vp9/vp9cx.mk b/vp9/vp9cx.mk index ff29d885b..b14e7e5ce 100644 --- a/vp9/vp9cx.mk +++ b/vp9/vp9cx.mk @@ -70,6 +70,8 @@ VP9_CX_SRCS-yes += encoder/vp9_treewriter.c VP9_CX_SRCS-yes += encoder/vp9_variance.c VP9_CX_SRCS-yes += encoder/vp9_vaq.c VP9_CX_SRCS-yes += encoder/vp9_vaq.h +VP9_CX_SRCS-yes += encoder/vp9_craq.c +VP9_CX_SRCS-yes += encoder/vp9_craq.h ifeq ($(CONFIG_VP9_POSTPROC),yes) VP9_CX_SRCS-$(CONFIG_INTERNAL_STATS) += common/vp9_postproc.h VP9_CX_SRCS-$(CONFIG_INTERNAL_STATS) += common/vp9_postproc.c