diff --git a/src/enc/config.c b/src/enc/config.c index b2351b1c..97620a21 100644 --- a/src/enc/config.c +++ b/src/enc/config.c @@ -33,7 +33,7 @@ int WebPConfigInitInternal(WebPConfig* config, config->target_PSNR = 0.; config->method = 4; config->sns_strength = 50; - config->filter_strength = 60; // rather high filtering, helps w/ gradients. + config->filter_strength = 60; // mid-filtering config->filter_sharpness = 0; config->filter_type = 1; // default: strong (so U/V is filtered too) config->partitions = 0; diff --git a/src/enc/filter.c b/src/enc/filter.c index 576a8671..4893b15a 100644 --- a/src/enc/filter.c +++ b/src/enc/filter.c @@ -11,12 +11,58 @@ // // Author: somnath@google.com (Somnath Banerjee) +#include #include "./vp8enci.h" #if defined(__cplusplus) || defined(c_plusplus) extern "C" { #endif +// this table gives, for a given sharpness, the filtering strength to be +// used (at least) in order to filter a given edge step delta. +#define MAX_DELTA_SIZE 64 +static const uint8_t kLevelsFromDelta[8][MAX_DELTA_SIZE] = { + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, + 20, 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, + 44, 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16, 17, 19, + 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, + 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, + 21, 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, + 45, 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, + 21, 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, + 45, 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 17, 19, 20, + 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, + 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 19, 21, + 22, 24, 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 40, 42, 43, 45, + 46, 48, 49, 51, 52, 54, 55, 57, 58, 60, 61, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }, + { 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 17, 18, 20, 21, + 23, 24, 26, 27, 29, 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, + 47, 48, 50, 51, 53, 54, 56, 57, 59, 60, 62, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 } +}; + +int VP8FilterStrengthFromDelta(int sharpness, int delta) { + const int pos = (delta < MAX_DELTA_SIZE) ? delta : MAX_DELTA_SIZE - 1; + assert(sharpness >= 0 && sharpness <= 7); + return kLevelsFromDelta[sharpness][pos]; +} + +// ----------------------------------------------------------------------------- // NOTE: clip1, tables and InitTables are repeated entries of dsp.c static uint8_t abs0[255 + 255 + 1]; // abs(i) static uint8_t abs1[255 + 255 + 1]; // abs(i)>>1 @@ -353,12 +399,13 @@ void VP8InitFilter(VP8EncIterator* const it) { void VP8StoreFilterStats(VP8EncIterator* const it) { int d; + VP8Encoder* const enc = it->enc_; const int s = it->mb_->segment_; - const int level0 = it->enc_->dqm_[s].fstrength_; // TODO: ref_lf_delta[] + const int level0 = enc->dqm_[s].fstrength_; // TODO: ref_lf_delta[] // explore +/-quant range of values around level0 - const int delta_min = -it->enc_->dqm_[s].quant_; - const int delta_max = it->enc_->dqm_[s].quant_; + const int delta_min = -enc->dqm_[s].quant_; + const int delta_max = enc->dqm_[s].quant_; const int step_size = (delta_max - delta_min >= 4) ? 4 : 1; if (it->lf_stats_ == NULL) return; @@ -385,9 +432,9 @@ void VP8StoreFilterStats(VP8EncIterator* const it) { } void VP8AdjustFilterStrength(VP8EncIterator* const it) { + VP8Encoder* const enc = it->enc_; if (it->lf_stats_ != NULL) { int s; - VP8Encoder* const enc = it->enc_; for (s = 0; s < NUM_MB_SEGMENTS; s++) { int i, best_level = 0; // Improvement over filter level 0 should be at least 1e-5 (relatively) @@ -401,9 +448,28 @@ void VP8AdjustFilterStrength(VP8EncIterator* const it) { } enc->dqm_[s].fstrength_ = best_level; } + } else if (enc->config_->filter_strength > 0) { + int max_level = 0; + int s; + for (s = 0; s < NUM_MB_SEGMENTS; s++) { + VP8SegmentInfo* const dqm = &enc->dqm_[s]; + // this '>> 3' accounts for some inverse WHT scaling + const int delta = (dqm->max_edge_ * dqm->y2_.q_[1]) >> 3; + const int level = + VP8FilterStrengthFromDelta(enc->filter_hdr_.sharpness_, delta); + if (level > dqm->fstrength_) { + dqm->fstrength_ = level; + } + if (max_level < dqm->fstrength_) { + max_level = dqm->fstrength_; + } + } + enc->filter_hdr_.level_ = max_level; } } +// ----------------------------------------------------------------------------- + #if defined(__cplusplus) || defined(c_plusplus) } // extern "C" #endif diff --git a/src/enc/quant.c b/src/enc/quant.c index 281800ec..dded3bef 100644 --- a/src/enc/quant.c +++ b/src/enc/quant.c @@ -13,6 +13,7 @@ #include #include +#include // for abs() #include "./vp8enci.h" #include "./cost.h" @@ -181,17 +182,17 @@ static void SetupMatrices(VP8Encoder* enc) { q16 = ExpandMatrix(&m->y2_, 1); quv = ExpandMatrix(&m->uv_, 2); - // TODO: Switch to kLambda*[] tables? - { - m->lambda_i4_ = (3 * q4 * q4) >> 7; - m->lambda_i16_ = (3 * q16 * q16); - m->lambda_uv_ = (3 * quv * quv) >> 6; - m->lambda_mode_ = (1 * q4 * q4) >> 7; - m->lambda_trellis_i4_ = (7 * q4 * q4) >> 3; - m->lambda_trellis_i16_ = (q16 * q16) >> 2; - m->lambda_trellis_uv_ = (quv *quv) << 1; - m->tlambda_ = (tlambda_scale * q4) >> 5; - } + m->lambda_i4_ = (3 * q4 * q4) >> 7; + m->lambda_i16_ = (3 * q16 * q16); + m->lambda_uv_ = (3 * quv * quv) >> 6; + m->lambda_mode_ = (1 * q4 * q4) >> 7; + m->lambda_trellis_i4_ = (7 * q4 * q4) >> 3; + m->lambda_trellis_i16_ = (q16 * q16) >> 2; + m->lambda_trellis_uv_ = (quv *quv) << 1; + m->tlambda_ = (tlambda_scale * q4) >> 5; + + m->min_disto_ = 10 * m->y1_.q_[0]; // quantization-aware min disto + m->max_edge_ = 0; } } @@ -200,16 +201,21 @@ static void SetupMatrices(VP8Encoder* enc) { // Very small filter-strength values have close to no visual effect. So we can // save a little decoding-CPU by turning filtering off for these. -#define FSTRENGTH_CUTOFF 3 +#define FSTRENGTH_CUTOFF 2 static void SetupFilterStrength(VP8Encoder* const enc) { int i; - const int level0 = enc->config_->filter_strength; + // level0 is in [0..500]. Using '-f 50' as filter_strength is mid-filtering. + const int level0 = 5 * enc->config_->filter_strength; for (i = 0; i < NUM_MB_SEGMENTS; ++i) { - // Segments with lower quantizer will be less filtered. TODO: tune (wrt SNS) - const int level = level0 * 256 * enc->dqm_[i].quant_ / 128; - const int f = level / (256 + enc->dqm_[i].beta_); - enc->dqm_[i].fstrength_ = (f < FSTRENGTH_CUTOFF) ? 0 : (f > 63) ? 63 : f; + VP8SegmentInfo* const m = &enc->dqm_[i]; + // We focus on the quantization of AC coeffs. + const int qstep = kAcTable[clip(m->quant_, 0, 127)] >> 2; + const int base_strength = + VP8FilterStrengthFromDelta(enc->filter_hdr_.sharpness_, qstep); + // Segments with lower complexity ('beta') will be less filtered. + const int f = base_strength * level0 / (256 + m->beta_); + m->fstrength_ = (f < FSTRENGTH_CUTOFF) ? 0 : (f > 63) ? 63 : f; } // We record the initial strength (mainly for the case of 1-segment only). enc->filter_hdr_.level_ = enc->dqm_[0].fstrength_; @@ -648,10 +654,10 @@ static int ReconstructIntra16(VP8EncIterator* const it, VP8ModeScore* const rd, uint8_t* const yuv_out, int mode) { - const VP8Encoder* const enc = it->enc_; + VP8Encoder* const enc = it->enc_; const uint8_t* const ref = it->yuv_p_ + VP8I16ModeOffsets[mode]; const uint8_t* const src = it->yuv_in_ + Y_OFF; - const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; + VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; int nz = 0; int n; int16_t tmp[16][16], dc_tmp[16]; @@ -757,6 +763,17 @@ static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd, // RD-opt decision. Reconstruct each modes, evalue distortion and bit-cost. // Pick the mode is lower RD-cost = Rate + lamba * Distortion. +static void StoreMaxDelta(VP8SegmentInfo* const dqm, const int16_t DCs[16]) { + // We look at the first three AC coefficients to determine what is the average + // delta between each sub-4x4 block. + const int v0 = abs(DCs[1]); + const int v1 = abs(DCs[4]); + const int v2 = abs(DCs[5]); + int max_v = (v0 > v1) ? v1 : v0; + max_v = (v2 > max_v) ? v2 : max_v; + if (max_v > dqm->max_edge_) dqm->max_edge_ = max_v; +} + static void SwapPtr(uint8_t** a, uint8_t** b) { uint8_t* const tmp = *a; *a = *b; @@ -768,8 +785,8 @@ static void SwapOut(VP8EncIterator* const it) { } static void PickBestIntra16(VP8EncIterator* const it, VP8ModeScore* const rd) { - const VP8Encoder* const enc = it->enc_; - const VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; + VP8Encoder* const enc = it->enc_; + VP8SegmentInfo* const dqm = &enc->dqm_[it->mb_->segment_]; const int lambda = dqm->lambda_i16_; const int tlambda = dqm->tlambda_; const uint8_t* const src = it->yuv_in_ + Y_OFF; @@ -804,6 +821,13 @@ static void PickBestIntra16(VP8EncIterator* const it, VP8ModeScore* const rd) { } SetRDScore(dqm->lambda_mode_, rd); // finalize score for mode decision. VP8SetIntra16Mode(it, rd->mode_i16); + + // we have a blocky macroblock (only DCs are non-zero) with fairly high + // distortion, record max delta so we can later adjust the minimal filtering + // strength needed to smooth these blocks out. + if ((rd->nz & 0xffff) == 0 && rd->D > dqm->min_disto_) { + StoreMaxDelta(dqm, rd->y_dc_levels); + } } //------------------------------------------------------------------------------ diff --git a/src/enc/vp8enci.h b/src/enc/vp8enci.h index da333732..7e41dba0 100644 --- a/src/enc/vp8enci.h +++ b/src/enc/vp8enci.h @@ -248,6 +248,8 @@ typedef struct { int beta_; // filter-susceptibility, range [0,255]. int quant_; // final segment quantizer. int fstrength_; // final in-loop filtering strength + int max_edge_; // max edge delta (for filtering strength) + int min_disto_; // minimum distortion required to trigger filtering record // reactivities int lambda_i16_, lambda_i4_, lambda_uv_; int lambda_mode_, lambda_trellis_, tlambda_; @@ -555,6 +557,10 @@ void VP8InitFilter(VP8EncIterator* const it); void VP8StoreFilterStats(VP8EncIterator* const it); void VP8AdjustFilterStrength(VP8EncIterator* const it); +// returns the approximate filtering strength needed to smooth a edge +// step of 'delta', given a sharpness parameter 'sharpness'. +int VP8FilterStrengthFromDelta(int sharpness, int delta); + //------------------------------------------------------------------------------ #if defined(__cplusplus) || defined(c_plusplus)