| Index: third_party/libwebp/enc/frame.c
|
| diff --git a/third_party/libwebp/enc/frame.c b/third_party/libwebp/enc/frame.c
|
| index c56abed75e660f6f2aba1516e5a0764ac86503a8..2582244c6c4fdf555622e952cfa4d95a021a06e1 100644
|
| --- a/third_party/libwebp/enc/frame.c
|
| +++ b/third_party/libwebp/enc/frame.c
|
| @@ -18,10 +18,7 @@
|
|
|
| #include "./vp8enci.h"
|
| #include "./cost.h"
|
| -
|
| -#if defined(__cplusplus) || defined(c_plusplus)
|
| -extern "C" {
|
| -#endif
|
| +#include "../webp/format_constants.h" // RIFF constants
|
|
|
| #define SEGMENT_VISU 0
|
| #define DEBUG_SEARCH 0 // useful to track search convergence
|
| @@ -40,6 +37,63 @@ typedef struct {
|
| } VP8Residual;
|
|
|
| //------------------------------------------------------------------------------
|
| +// multi-pass convergence
|
| +
|
| +#define HEADER_SIZE_ESTIMATE (RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE + \
|
| + VP8_FRAME_HEADER_SIZE)
|
| +#define DQ_LIMIT 0.4 // convergence is considered reached if dq < DQ_LIMIT
|
| +// we allow 2k of extra head-room in PARTITION0 limit.
|
| +#define PARTITION0_SIZE_LIMIT ((VP8_MAX_PARTITION0_SIZE - 2048ULL) << 11)
|
| +
|
| +typedef struct { // struct for organizing convergence in either size or PSNR
|
| + int is_first;
|
| + float dq;
|
| + float q, last_q;
|
| + double value, last_value; // PSNR or size
|
| + double target;
|
| + int do_size_search;
|
| +} PassStats;
|
| +
|
| +static int InitPassStats(const VP8Encoder* const enc, PassStats* const s) {
|
| + const uint64_t target_size = (uint64_t)enc->config_->target_size;
|
| + const int do_size_search = (target_size != 0);
|
| + const float target_PSNR = enc->config_->target_PSNR;
|
| +
|
| + s->is_first = 1;
|
| + s->dq = 10.f;
|
| + s->q = s->last_q = enc->config_->quality;
|
| + s->target = do_size_search ? (double)target_size
|
| + : (target_PSNR > 0.) ? target_PSNR
|
| + : 40.; // default, just in case
|
| + s->value = s->last_value = 0.;
|
| + s->do_size_search = do_size_search;
|
| + return do_size_search;
|
| +}
|
| +
|
| +static float Clamp(float v, float min, float max) {
|
| + return (v < min) ? min : (v > max) ? max : v;
|
| +}
|
| +
|
| +static float ComputeNextQ(PassStats* const s) {
|
| + float dq;
|
| + if (s->is_first) {
|
| + dq = (s->value > s->target) ? -s->dq : s->dq;
|
| + s->is_first = 0;
|
| + } else if (s->value != s->last_value) {
|
| + const double slope = (s->target - s->value) / (s->last_value - s->value);
|
| + dq = (float)(slope * (s->last_q - s->q));
|
| + } else {
|
| + dq = 0.; // we're done?!
|
| + }
|
| + // Limit variable to avoid large swings.
|
| + s->dq = Clamp(dq, -30.f, 30.f);
|
| + s->last_q = s->q;
|
| + s->last_value = s->value;
|
| + s->q = Clamp(s->q + s->dq, 0.f, 100.f);
|
| + return s->q;
|
| +}
|
| +
|
| +//------------------------------------------------------------------------------
|
| // Tables for level coding
|
|
|
| const uint8_t VP8EncBands[16 + 1] = {
|
| @@ -292,31 +346,20 @@ static int GetResidualCost(int ctx0, const VP8Residual* const res) {
|
| if (res->last < 0) {
|
| return VP8BitCost(0, p0);
|
| }
|
| - cost = 0;
|
| - while (n < res->last) {
|
| - int v = res->coeffs[n];
|
| + cost = VP8BitCost(1, p0);
|
| + for (; n < res->last; ++n) {
|
| + const int v = abs(res->coeffs[n]);
|
| const int b = VP8EncBands[n + 1];
|
| - ++n;
|
| - if (v == 0) {
|
| - // short-case for VP8LevelCost(t, 0) (note: VP8LevelFixedCosts[0] == 0):
|
| - cost += t[0];
|
| - t = res->cost[b][0];
|
| - continue;
|
| - }
|
| - v = abs(v);
|
| - cost += VP8BitCost(1, p0);
|
| + const int ctx = (v >= 2) ? 2 : v;
|
| cost += VP8LevelCost(t, v);
|
| - {
|
| - const int ctx = (v == 1) ? 1 : 2;
|
| - p0 = res->prob[b][ctx][0];
|
| - t = res->cost[b][ctx];
|
| - }
|
| + t = res->cost[b][ctx];
|
| + // the masking trick is faster than "if (v) cost += ..." with clang
|
| + cost += (v ? ~0U : 0) & VP8BitCost(1, res->prob[b][ctx][0]);
|
| }
|
| // Last coefficient is always non-zero
|
| {
|
| const int v = abs(res->coeffs[n]);
|
| assert(v != 0);
|
| - cost += VP8BitCost(1, p0);
|
| cost += VP8LevelCost(t, v);
|
| if (n < 15) {
|
| const int b = VP8EncBands[n + 1];
|
| @@ -685,81 +728,83 @@ static void StoreSideInfo(const VP8EncIterator* const it) {
|
| #endif
|
| }
|
|
|
| +static double GetPSNR(uint64_t mse, uint64_t size) {
|
| + return (mse > 0 && size > 0) ? 10. * log10(255. * 255. * size / mse) : 99;
|
| +}
|
| +
|
| //------------------------------------------------------------------------------
|
| // StatLoop(): only collect statistics (number of skips, token usage, ...).
|
| // This is used for deciding optimal probabilities. It also modifies the
|
| -// quantizer value if some target (size, PNSR) was specified.
|
| -
|
| -#define kHeaderSizeEstimate (15 + 20 + 10) // TODO: fix better
|
| +// quantizer value if some target (size, PSNR) was specified.
|
|
|
| static void SetLoopParams(VP8Encoder* const enc, float q) {
|
| // Make sure the quality parameter is inside valid bounds
|
| - if (q < 0.) {
|
| - q = 0;
|
| - } else if (q > 100.) {
|
| - q = 100;
|
| - }
|
| + q = Clamp(q, 0.f, 100.f);
|
|
|
| VP8SetSegmentParams(enc, q); // setup segment quantizations and filters
|
| SetSegmentProbas(enc); // compute segment probabilities
|
|
|
| ResetStats(enc);
|
| - ResetTokenStats(enc);
|
| -
|
| ResetSSE(enc);
|
| }
|
|
|
| -static int OneStatPass(VP8Encoder* const enc, float q, VP8RDLevel rd_opt,
|
| - int nb_mbs, float* const PSNR, int percent_delta) {
|
| +static uint64_t OneStatPass(VP8Encoder* const enc, VP8RDLevel rd_opt,
|
| + int nb_mbs, int percent_delta,
|
| + PassStats* const s) {
|
| VP8EncIterator it;
|
| uint64_t size = 0;
|
| + uint64_t size_p0 = 0;
|
| uint64_t distortion = 0;
|
| const uint64_t pixel_count = nb_mbs * 384;
|
|
|
| - SetLoopParams(enc, q);
|
| -
|
| VP8IteratorInit(enc, &it);
|
| + SetLoopParams(enc, s->q);
|
| do {
|
| VP8ModeScore info;
|
| - VP8IteratorImport(&it);
|
| + VP8IteratorImport(&it, NULL);
|
| if (VP8Decimate(&it, &info, rd_opt)) {
|
| // Just record the number of skips and act like skip_proba is not used.
|
| enc->proba_.nb_skip_++;
|
| }
|
| RecordResiduals(&it, &info);
|
| - size += info.R;
|
| + size += info.R + info.H;
|
| + size_p0 += info.H;
|
| distortion += info.D;
|
| if (percent_delta && !VP8IteratorProgress(&it, percent_delta))
|
| return 0;
|
| - } while (VP8IteratorNext(&it, it.yuv_out_) && --nb_mbs > 0);
|
| - size += FinalizeSkipProba(enc);
|
| - size += FinalizeTokenProbas(&enc->proba_);
|
| - size += enc->segment_hdr_.size_;
|
| - size = ((size + 1024) >> 11) + kHeaderSizeEstimate;
|
| -
|
| - if (PSNR) {
|
| - *PSNR = (float)(10.* log10(255. * 255. * pixel_count / distortion));
|
| + VP8IteratorSaveBoundary(&it);
|
| + } while (VP8IteratorNext(&it) && --nb_mbs > 0);
|
| +
|
| + size_p0 += enc->segment_hdr_.size_;
|
| + if (s->do_size_search) {
|
| + size += FinalizeSkipProba(enc);
|
| + size += FinalizeTokenProbas(&enc->proba_);
|
| + size = ((size + size_p0 + 1024) >> 11) + HEADER_SIZE_ESTIMATE;
|
| + s->value = (double)size;
|
| + } else {
|
| + s->value = GetPSNR(distortion, pixel_count);
|
| }
|
| - return (int)size;
|
| + return size_p0;
|
| }
|
|
|
| -// successive refinement increments.
|
| -static const int dqs[] = { 20, 15, 10, 8, 6, 4, 2, 1, 0 };
|
| -
|
| static int StatLoop(VP8Encoder* const enc) {
|
| const int method = enc->method_;
|
| const int do_search = enc->do_search_;
|
| const int fast_probe = ((method == 0 || method == 3) && !do_search);
|
| - float q = enc->config_->quality;
|
| - const int max_passes = enc->config_->pass;
|
| + int num_pass_left = enc->config_->pass;
|
| const int task_percent = 20;
|
| - const int percent_per_pass = (task_percent + max_passes / 2) / max_passes;
|
| + const int percent_per_pass =
|
| + (task_percent + num_pass_left / 2) / num_pass_left;
|
| const int final_percent = enc->percent_ + task_percent;
|
| - int pass;
|
| - int nb_mbs;
|
| + const VP8RDLevel rd_opt =
|
| + (method >= 3 || do_search) ? RD_OPT_BASIC : RD_OPT_NONE;
|
| + int nb_mbs = enc->mb_w_ * enc->mb_h_;
|
| + PassStats stats;
|
| +
|
| + InitPassStats(enc, &stats);
|
| + ResetTokenStats(enc);
|
|
|
| // Fast mode: quick analysis pass over few mbs. Better than nothing.
|
| - nb_mbs = enc->mb_w_ * enc->mb_h_;
|
| if (fast_probe) {
|
| if (method == 3) { // we need more stats for method 3 to be reliable.
|
| nb_mbs = (nb_mbs > 200) ? nb_mbs >> 1 : 100;
|
| @@ -768,37 +813,35 @@ static int StatLoop(VP8Encoder* const enc) {
|
| }
|
| }
|
|
|
| - // No target size: just do several pass without changing 'q'
|
| - if (!do_search) {
|
| - for (pass = 0; pass < max_passes; ++pass) {
|
| - const VP8RDLevel rd_opt = (method >= 3) ? RD_OPT_BASIC : RD_OPT_NONE;
|
| - if (!OneStatPass(enc, q, rd_opt, nb_mbs, NULL, percent_per_pass)) {
|
| - return 0;
|
| - }
|
| - }
|
| - } else {
|
| - // binary search for a size close to target
|
| - for (pass = 0; pass < max_passes && (dqs[pass] > 0); ++pass) {
|
| - float PSNR;
|
| - int criterion;
|
| - const int size = OneStatPass(enc, q, RD_OPT_BASIC, nb_mbs, &PSNR,
|
| - percent_per_pass);
|
| -#if DEBUG_SEARCH
|
| - printf("#%d size=%d PSNR=%.2f q=%.2f\n", pass, size, PSNR, q);
|
| + while (num_pass_left-- > 0) {
|
| + const int is_last_pass = (fabs(stats.dq) <= DQ_LIMIT) ||
|
| + (num_pass_left == 0) ||
|
| + (enc->max_i4_header_bits_ == 0);
|
| + const uint64_t size_p0 =
|
| + OneStatPass(enc, rd_opt, nb_mbs, percent_per_pass, &stats);
|
| + if (size_p0 == 0) return 0;
|
| +#if (DEBUG_SEARCH > 0)
|
| + printf("#%d value:%.1lf -> %.1lf q:%.2f -> %.2f\n",
|
| + num_pass_left, stats.last_value, stats.value, stats.last_q, stats.q);
|
| #endif
|
| - if (size == 0) return 0;
|
| - if (enc->config_->target_PSNR > 0) {
|
| - criterion = (PSNR < enc->config_->target_PSNR);
|
| - } else {
|
| - criterion = (size < enc->config_->target_size);
|
| - }
|
| - // dichotomize
|
| - if (criterion) {
|
| - q += dqs[pass];
|
| - } else {
|
| - q -= dqs[pass];
|
| - }
|
| + if (enc->max_i4_header_bits_ > 0 && size_p0 > PARTITION0_SIZE_LIMIT) {
|
| + ++num_pass_left;
|
| + enc->max_i4_header_bits_ >>= 1; // strengthen header bit limitation...
|
| + continue; // ...and start over
|
| }
|
| + if (is_last_pass) {
|
| + break;
|
| + }
|
| + // If no target size: just do several pass without changing 'q'
|
| + if (do_search) {
|
| + ComputeNextQ(&stats);
|
| + if (fabs(stats.dq) <= DQ_LIMIT) break;
|
| + }
|
| + }
|
| + if (!do_search || !stats.do_size_search) {
|
| + // Need to finalize probas now, since it wasn't done during the search.
|
| + FinalizeSkipProba(enc);
|
| + FinalizeTokenProbas(&enc->proba_);
|
| }
|
| VP8CalculateLevelCosts(&enc->proba_); // finalize costs
|
| return WebPReportProgress(enc->pic_, final_percent, &enc->percent_);
|
| @@ -835,7 +878,7 @@ static int PostLoopFinalize(VP8EncIterator* const it, int ok) {
|
| }
|
|
|
| if (ok) { // All good. Finish up.
|
| - if (enc->pic_->stats) { // finalize byte counters...
|
| + if (enc->pic_->stats != NULL) { // finalize byte counters...
|
| int i, s;
|
| for (i = 0; i <= 2; ++i) {
|
| for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
|
| @@ -877,7 +920,7 @@ int VP8EncLoop(VP8Encoder* const enc) {
|
| const int dont_use_skip = !enc->proba_.use_skip_proba_;
|
| const VP8RDLevel rd_opt = enc->rd_opt_level_;
|
|
|
| - VP8IteratorImport(&it);
|
| + VP8IteratorImport(&it, NULL);
|
| // Warning! order is important: first call VP8Decimate() and
|
| // *then* decide how to code the skip decision if there's one.
|
| if (!VP8Decimate(&it, &info, rd_opt) || dont_use_skip) {
|
| @@ -894,7 +937,8 @@ int VP8EncLoop(VP8Encoder* const enc) {
|
| VP8StoreFilterStats(&it);
|
| VP8IteratorExport(&it);
|
| ok = VP8IteratorProgress(&it, 20);
|
| - } while (ok && VP8IteratorNext(&it, it.yuv_out_));
|
| + VP8IteratorSaveBoundary(&it);
|
| + } while (ok && VP8IteratorNext(&it));
|
|
|
| return PostLoopFinalize(&it, ok);
|
| }
|
| @@ -904,62 +948,110 @@ int VP8EncLoop(VP8Encoder* const enc) {
|
|
|
| #if !defined(DISABLE_TOKEN_BUFFER)
|
|
|
| -#define MIN_COUNT 96 // minimum number of macroblocks before updating stats
|
| +#define MIN_COUNT 96 // minimum number of macroblocks before updating stats
|
|
|
| int VP8EncTokenLoop(VP8Encoder* const enc) {
|
| - int ok;
|
| - // Roughly refresh the proba height times per pass
|
| + // Roughly refresh the proba eight times per pass
|
| int max_count = (enc->mb_w_ * enc->mb_h_) >> 3;
|
| - int cnt;
|
| + int num_pass_left = enc->config_->pass;
|
| + const int do_search = enc->do_search_;
|
| VP8EncIterator it;
|
| VP8Proba* const proba = &enc->proba_;
|
| const VP8RDLevel rd_opt = enc->rd_opt_level_;
|
| + const uint64_t pixel_count = enc->mb_w_ * enc->mb_h_ * 384;
|
| + PassStats stats;
|
| + int ok;
|
| +
|
| + InitPassStats(enc, &stats);
|
| + ok = PreLoopInitialize(enc);
|
| + if (!ok) return 0;
|
|
|
| if (max_count < MIN_COUNT) max_count = MIN_COUNT;
|
| - cnt = max_count;
|
|
|
| assert(enc->num_parts_ == 1);
|
| assert(enc->use_tokens_);
|
| assert(proba->use_skip_proba_ == 0);
|
| assert(rd_opt >= RD_OPT_BASIC); // otherwise, token-buffer won't be useful
|
| - assert(!enc->do_search_); // TODO(skal): handle pass and dichotomy
|
| -
|
| - SetLoopParams(enc, enc->config_->quality);
|
| -
|
| - ok = PreLoopInitialize(enc);
|
| - if (!ok) return 0;
|
| -
|
| - VP8IteratorInit(enc, &it);
|
| - VP8InitFilter(&it);
|
| - do {
|
| - VP8ModeScore info;
|
| - VP8IteratorImport(&it);
|
| - if (--cnt < 0) {
|
| - FinalizeTokenProbas(proba);
|
| - VP8CalculateLevelCosts(proba); // refresh cost tables for rd-opt
|
| - cnt = max_count;
|
| - }
|
| - VP8Decimate(&it, &info, rd_opt);
|
| - RecordTokens(&it, &info, &enc->tokens_);
|
| -#ifdef WEBP_EXPERIMENTAL_FEATURES
|
| - if (enc->use_layer_) {
|
| - VP8EncCodeLayerBlock(&it);
|
| + assert(num_pass_left > 0);
|
| +
|
| + while (ok && num_pass_left-- > 0) {
|
| + const int is_last_pass = (fabs(stats.dq) <= DQ_LIMIT) ||
|
| + (num_pass_left == 0) ||
|
| + (enc->max_i4_header_bits_ == 0);
|
| + uint64_t size_p0 = 0;
|
| + uint64_t distortion = 0;
|
| + int cnt = max_count;
|
| + VP8IteratorInit(enc, &it);
|
| + SetLoopParams(enc, stats.q);
|
| + if (is_last_pass) {
|
| + ResetTokenStats(enc);
|
| + VP8InitFilter(&it); // don't collect stats until last pass (too costly)
|
| }
|
| + VP8TBufferClear(&enc->tokens_);
|
| + do {
|
| + VP8ModeScore info;
|
| + VP8IteratorImport(&it, NULL);
|
| + if (--cnt < 0) {
|
| + FinalizeTokenProbas(proba);
|
| + VP8CalculateLevelCosts(proba); // refresh cost tables for rd-opt
|
| + cnt = max_count;
|
| + }
|
| + VP8Decimate(&it, &info, rd_opt);
|
| + RecordTokens(&it, &info, &enc->tokens_);
|
| + size_p0 += info.H;
|
| + distortion += info.D;
|
| +#ifdef WEBP_EXPERIMENTAL_FEATURES
|
| + if (enc->use_layer_) {
|
| + VP8EncCodeLayerBlock(&it);
|
| + }
|
| #endif
|
| - StoreSideInfo(&it);
|
| - VP8StoreFilterStats(&it);
|
| - VP8IteratorExport(&it);
|
| - ok = VP8IteratorProgress(&it, 20);
|
| - } while (ok && VP8IteratorNext(&it, it.yuv_out_));
|
| -
|
| - ok = ok && WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_);
|
| + if (is_last_pass) {
|
| + StoreSideInfo(&it);
|
| + VP8StoreFilterStats(&it);
|
| + VP8IteratorExport(&it);
|
| + ok = VP8IteratorProgress(&it, 20);
|
| + }
|
| + VP8IteratorSaveBoundary(&it);
|
| + } while (ok && VP8IteratorNext(&it));
|
| + if (!ok) break;
|
| +
|
| + size_p0 += enc->segment_hdr_.size_;
|
| + if (stats.do_size_search) {
|
| + uint64_t size = FinalizeTokenProbas(&enc->proba_);
|
| + size += VP8EstimateTokenSize(&enc->tokens_,
|
| + (const uint8_t*)proba->coeffs_);
|
| + size = (size + size_p0 + 1024) >> 11; // -> size in bytes
|
| + size += HEADER_SIZE_ESTIMATE;
|
| + stats.value = (double)size;
|
| + } else { // compute and store PSNR
|
| + stats.value = GetPSNR(distortion, pixel_count);
|
| + }
|
|
|
| +#if (DEBUG_SEARCH > 0)
|
| + printf("#%2d metric:%.1lf -> %.1lf last_q=%.2lf q=%.2lf dq=%.2lf\n",
|
| + num_pass_left, stats.last_value, stats.value,
|
| + stats.last_q, stats.q, stats.dq);
|
| +#endif
|
| + if (size_p0 > PARTITION0_SIZE_LIMIT) {
|
| + ++num_pass_left;
|
| + enc->max_i4_header_bits_ >>= 1; // strengthen header bit limitation...
|
| + continue; // ...and start over
|
| + }
|
| + if (is_last_pass) {
|
| + break; // done
|
| + }
|
| + if (do_search) {
|
| + ComputeNextQ(&stats); // Adjust q
|
| + }
|
| + }
|
| if (ok) {
|
| - FinalizeTokenProbas(proba);
|
| + if (!stats.do_size_search) {
|
| + FinalizeTokenProbas(&enc->proba_);
|
| + }
|
| ok = VP8EmitTokens(&enc->tokens_, enc->parts_ + 0,
|
| (const uint8_t*)proba->coeffs_, 1);
|
| }
|
| -
|
| + ok = ok && WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_);
|
| return PostLoopFinalize(&it, ok);
|
| }
|
|
|
| @@ -974,6 +1066,3 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
|
|
|
| //------------------------------------------------------------------------------
|
|
|
| -#if defined(__cplusplus) || defined(c_plusplus)
|
| -} // extern "C"
|
| -#endif
|
|
|