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 |