shithub: libvpx

Download patch

ref: c7da053d4b6e652343cb9e6b7bd5d126ff9b8a6d
parent: c2f6a7df8d500ba540503d042dc1dbc519a2a873
author: Marco <marpan@chromium.org>
date: Sun Nov 1 09:40:05 EST 2015

Move noise level estimate outside denoiser.

Source noise level estimate is also useful for
setting variance encoder parameters (variance thresholds,
qp-delta, mode selection, etc), so allow it to be used also
if denoising is not on.

Change-Id: I4fe23d47607b4e17a35287057f489c29114beed1

--- a/vp9/encoder/vp9_aq_cyclicrefresh.c
+++ b/vp9/encoder/vp9_aq_cyclicrefresh.c
@@ -490,12 +490,9 @@
     cr->rate_ratio_qdelta = 3.0;
   } else {
     cr->rate_ratio_qdelta = 2.0;
-#if CONFIG_VP9_TEMPORAL_DENOISING
-  if (cpi->oxcf.noise_sensitivity > 0 &&
-      cpi->denoiser.denoising_level >= kMedium)
+  if (cpi->noise_estimate.enabled && cpi->noise_estimate.level >= kMedium)
     // Reduce the delta-qp if the estimated source noise is above threshold.
     cr->rate_ratio_qdelta = 1.5;
-#endif
   }
   // Adjust some parameters for low resolutions at low bitrates.
   if (cm->width <= 352 &&
--- a/vp9/encoder/vp9_denoiser.c
+++ b/vp9/encoder/vp9_denoiser.c
@@ -323,7 +323,7 @@
   struct buf_2d src = mb->plane[0].src;
   int is_skin = 0;
 
-  if (bs <= BLOCK_16X16 && denoiser->denoising_level >= kMedium) {
+  if (bs <= BLOCK_16X16 && denoiser->denoising_level >= kDenMedium) {
     // Take center pixel in block to determine is_skin.
     const int y_width_shift = (4 << b_width_log2_lookup[bs]) >> 1;
     const int y_height_shift = (4 << b_height_log2_lookup[bs]) >> 1;
@@ -343,13 +343,13 @@
   mv_col = ctx->best_sse_mv.as_mv.col;
   mv_row = ctx->best_sse_mv.as_mv.row;
   motion_magnitude = mv_row * mv_row + mv_col * mv_col;
-  if (denoiser->denoising_level == kHigh && motion_magnitude < 16) {
+  if (denoiser->denoising_level == kDenHigh && motion_magnitude < 16) {
     denoiser->increase_denoising = 1;
   } else {
     denoiser->increase_denoising = 0;
   }
 
-  if (denoiser->denoising_level >= kMedium)
+  if (denoiser->denoising_level >= kDenMedium)
     decision = perform_motion_compensation(denoiser, mb, bs,
                                            denoiser->increase_denoising,
                                            mi_row, mi_col, ctx,
@@ -524,27 +524,9 @@
 #endif
   denoiser->increase_denoising = 0;
   denoiser->frame_buffer_initialized = 1;
-  vp9_denoiser_init_noise_estimate(denoiser, width, height);
   return 0;
 }
 
-void vp9_denoiser_init_noise_estimate(VP9_DENOISER *denoiser,
-                                      int width,
-                                      int height) {
-  // Denoiser is off by default, i.e., no denoising is performed.
-  // Noise level is measured periodically, and if observed to be above
-  // thresh_noise_estimate, then denoising is performed.
-  denoiser->denoising_level = kLow;
-  denoiser->noise_estimate = 0;
-  denoiser->noise_estimate_count = 0;
-  denoiser->thresh_noise_estimate = 20;
-  if (width * height >= 1920 * 1080) {
-    denoiser->thresh_noise_estimate = 70;
-  } else if (width * height >= 1280 * 720) {
-    denoiser->thresh_noise_estimate = 40;
-  }
-}
-
 void vp9_denoiser_free(VP9_DENOISER *denoiser) {
   int i;
   denoiser->frame_buffer_initialized = 0;
@@ -558,123 +540,9 @@
   vpx_free_frame_buffer(&denoiser->last_source);
 }
 
-void vp9_denoiser_update_noise_estimate(VP9_COMP *const cpi) {
-  const VP9_COMMON *const cm = &cpi->common;
-  CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
-  int frame_period = 10;
-  int thresh_consec_zeromv = 8;
-  unsigned int thresh_sum_diff = 128;
-  int num_frames_estimate = 20;
-  int min_blocks_estimate = cm->mi_rows * cm->mi_cols >> 7;
-  // Estimate of noise level every frame_period frames.
-  // Estimate is between current source and last source.
-  if (cm->current_video_frame % frame_period != 0 ||
-     cpi->denoiser.last_source.y_buffer == NULL ||
-     cpi->resize_pending != 0) {
-    copy_frame(&cpi->denoiser.last_source, cpi->Source);
-    return;
-  } else {
-    int num_samples = 0;
-    uint64_t avg_est = 0;
-    int bsize = BLOCK_16X16;
-    static const unsigned char const_source[16] = {
-         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
-         128, 128};
-    // Loop over sub-sample of 16x16 blocks of frame, and for blocks that have
-    // been encoded as zero/small mv at least x consecutive frames, compute
-    // the variance to update estimate of noise in the source.
-    const uint8_t *src_y = cpi->Source->y_buffer;
-    const int src_ystride = cpi->Source->y_stride;
-    const uint8_t *last_src_y = cpi->denoiser.last_source.y_buffer;
-    const int last_src_ystride = cpi->denoiser.last_source.y_stride;
-    const uint8_t *src_u = cpi->Source->u_buffer;
-    const uint8_t *src_v = cpi->Source->v_buffer;
-    const int src_uvstride = cpi->Source->uv_stride;
-    const int y_width_shift = (4 << b_width_log2_lookup[bsize]) >> 1;
-    const int y_height_shift = (4 << b_height_log2_lookup[bsize]) >> 1;
-    const int uv_width_shift = y_width_shift >> 1;
-    const int uv_height_shift = y_height_shift >> 1;
-    int mi_row, mi_col;
-    for (mi_row = 0; mi_row < cm->mi_rows; mi_row ++) {
-      for (mi_col = 0; mi_col < cm->mi_cols; mi_col ++) {
-        // 16x16 blocks, 1/4 sample of frame.
-        if (mi_row % 4 == 0 && mi_col % 4 == 0) {
-          int bl_index = mi_row * cm->mi_cols + mi_col;
-          int bl_index1 = bl_index + 1;
-          int bl_index2 = bl_index + cm->mi_cols;
-          int bl_index3 = bl_index2 + 1;
-          // Only consider blocks that are likely steady background. i.e, have
-          // been encoded as zero/low motion x (= thresh_consec_zeromv) frames
-          // in a row. consec_zero_mv[] defined for 8x8 blocks, so consider all
-          // 4 sub-blocks for 16x16 block. Also, avoid skin blocks.
-          const uint8_t ysource =
-            src_y[y_height_shift * src_ystride + y_width_shift];
-          const uint8_t usource =
-            src_u[uv_height_shift * src_uvstride + uv_width_shift];
-          const uint8_t vsource =
-            src_v[uv_height_shift * src_uvstride + uv_width_shift];
-          int is_skin = vp9_skin_pixel(ysource, usource, vsource);
-          if (cr->consec_zero_mv[bl_index] > thresh_consec_zeromv &&
-              cr->consec_zero_mv[bl_index1] > thresh_consec_zeromv &&
-              cr->consec_zero_mv[bl_index2] > thresh_consec_zeromv &&
-              cr->consec_zero_mv[bl_index3] > thresh_consec_zeromv &&
-              !is_skin) {
-            // Compute variance.
-            unsigned int sse;
-            unsigned int variance = cpi->fn_ptr[bsize].vf(src_y,
-                                                          src_ystride,
-                                                          last_src_y,
-                                                          last_src_ystride,
-                                                          &sse);
-            // Only consider this block as valid for noise measurement if the
-            // average term (sse - variance = N * avg^{2}, N = 16X16) of the
-            // temporal residual is small (avoid effects from lighting change).
-            if ((sse - variance) < thresh_sum_diff) {
-              unsigned int sse2;
-              const unsigned int spatial_variance =
-                  cpi->fn_ptr[bsize].vf(src_y, src_ystride, const_source,
-                                        0, &sse2);
-              avg_est += variance / (10 + spatial_variance);
-              num_samples++;
-            }
-          }
-        }
-        src_y += 8;
-        last_src_y += 8;
-        src_u += 4;
-        src_v += 4;
-      }
-      src_y += (src_ystride << 3) - (cm->mi_cols << 3);
-      last_src_y += (last_src_ystride << 3) - (cm->mi_cols << 3);
-      src_u += (src_uvstride << 2) - (cm->mi_cols << 2);
-      src_v += (src_uvstride << 2) - (cm->mi_cols << 2);
-    }
-    // Update noise estimate if we have at a minimum number of block samples,
-    // and avg_est > 0 (avg_est == 0 can happen if the application inputs
-    // duplicate frames).
-    if (num_samples > min_blocks_estimate && avg_est > 0) {
-      // Normalize.
-      avg_est = (avg_est << 8) / num_samples;
-      // Update noise estimate.
-      cpi->denoiser.noise_estimate =  (3 * cpi->denoiser.noise_estimate +
-          avg_est) >> 2;
-      cpi->denoiser.noise_estimate_count++;
-      if (cpi->denoiser.noise_estimate_count == num_frames_estimate) {
-        // Reset counter and check noise level condition.
-        cpi->denoiser.noise_estimate_count = 0;
-        if (cpi->denoiser.noise_estimate >
-            (cpi->denoiser.thresh_noise_estimate << 1))
-          cpi->denoiser.denoising_level = kHigh;
-        else
-          if (cpi->denoiser.noise_estimate >
-              cpi->denoiser.thresh_noise_estimate)
-            cpi->denoiser.denoising_level = kMedium;
-          else
-            cpi->denoiser.denoising_level = kLow;
-      }
-    }
-  }
-  copy_frame(&cpi->denoiser.last_source, cpi->Source);
+void vp9_denoiser_set_noise_level(VP9_DENOISER *denoiser,
+                                  int noise_level) {
+  denoiser->denoising_level = noise_level;
 }
 
 #ifdef OUTPUT_YUV_DENOISED
--- a/vp9/encoder/vp9_denoiser.h
+++ b/vp9/encoder/vp9_denoiser.h
@@ -27,9 +27,9 @@
 } VP9_DENOISER_DECISION;
 
 typedef enum vp9_denoiser_level {
-  kLow,
-  kMedium,
-  kHigh
+  kDenLow,
+  kDenMedium,
+  kDenHigh
 } VP9_DENOISER_LEVEL;
 
 typedef struct vp9_denoiser {
@@ -39,9 +39,6 @@
   int increase_denoising;
   int frame_buffer_initialized;
   VP9_DENOISER_LEVEL denoising_level;
-  int noise_estimate;
-  int thresh_noise_estimate;
-  int noise_estimate_count;
 } VP9_DENOISER;
 
 struct VP9_COMP;
@@ -82,11 +79,8 @@
 
 void vp9_denoiser_free(VP9_DENOISER *denoiser);
 
-void vp9_denoiser_init_noise_estimate(VP9_DENOISER *denoiser,
-                                      int width,
-                                      int height);
-
-void vp9_denoiser_update_noise_estimate(struct VP9_COMP *const cpi);
+void vp9_denoiser_set_noise_level(VP9_DENOISER *denoiser,
+                                  int noise_level);
 
 #ifdef __cplusplus
 }  // extern "C"
--- a/vp9/encoder/vp9_encodeframe.c
+++ b/vp9/encoder/vp9_encodeframe.c
@@ -489,16 +489,14 @@
     thresholds[2] = threshold_base >> 2;
     thresholds[3] = threshold_base << 2;
   } else {
-#if CONFIG_VP9_TEMPORAL_DENOISING
-  if (cpi->oxcf.noise_sensitivity > 0) {
-    // Increase base variance threshold is estimated noise level is high.
-    if (cpi->denoiser.denoising_level == kHigh)
-      threshold_base = threshold_base << 2;
-    else
-      if (cpi->denoiser.denoising_level == kMedium)
-        threshold_base = threshold_base << 1;
-  }
-#endif
+    // Increase base variance threshold if estimated noise level is high.
+    if (cpi->noise_estimate.enabled) {
+      if (cpi->noise_estimate.level == kHigh)
+        threshold_base = threshold_base << 2;
+      else
+        if (cpi->noise_estimate.level == kMedium)
+          threshold_base = threshold_base << 1;
+    }
     thresholds[1] = threshold_base;
     if (cm->width <= 352 && cm->height <= 288) {
       thresholds[0] = threshold_base >> 2;
--- a/vp9/encoder/vp9_encoder.c
+++ b/vp9/encoder/vp9_encoder.c
@@ -47,6 +47,7 @@
 #include "vp9/encoder/vp9_ethread.h"
 #include "vp9/encoder/vp9_firstpass.h"
 #include "vp9/encoder/vp9_mbgraph.h"
+#include "vp9/encoder/vp9_noise_estimate.h"
 #include "vp9/encoder/vp9_picklpf.h"
 #include "vp9/encoder/vp9_ratectrl.h"
 #include "vp9/encoder/vp9_rd.h"
@@ -805,6 +806,8 @@
   cpi->ref_frame_flags = 0;
 
   init_buffer_indices(cpi);
+
+  vp9_noise_estimate_init(&cpi->noise_estimate, cm->width, cm->height);
 }
 
 static void set_rc_buffer_sizes(RATE_CONTROL *rc,
@@ -3160,6 +3163,7 @@
     // TODO(agrange) Scale cpi->max_mv_magnitude if frame-size has changed.
     set_mv_search_params(cpi);
 
+    vp9_noise_estimate_init(&cpi->noise_estimate, cm->width, cm->height);
 #if CONFIG_VP9_TEMPORAL_DENOISING
     // Reset the denoiser on the resized frame.
     if (cpi->oxcf.noise_sensitivity > 0) {
@@ -3241,21 +3245,18 @@
 
   // Avoid scaling last_source unless its needed.
   // Last source is currently only used for screen-content mode,
-  // or if partition_search_type == SOURCE_VAR_BASED_PARTITION.
+  // if partition_search_type == SOURCE_VAR_BASED_PARTITION, or if noise
+  // estimation is enabled.
   if (cpi->unscaled_last_source != NULL &&
       (cpi->oxcf.content == VP9E_CONTENT_SCREEN ||
-      cpi->sf.partition_search_type == SOURCE_VAR_BASED_PARTITION))
+      cpi->sf.partition_search_type == SOURCE_VAR_BASED_PARTITION ||
+      cpi->noise_estimate.enabled))
     cpi->Last_Source = vp9_scale_if_required(cm,
                                              cpi->unscaled_last_source,
                                              &cpi->scaled_last_source,
                                              (cpi->oxcf.pass == 0));
 
-#if CONFIG_VP9_TEMPORAL_DENOISING
-  if (cpi->oxcf.noise_sensitivity > 0 &&
-      cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ) {
-    vp9_denoiser_update_noise_estimate(cpi);
-  }
-#endif
+  vp9_update_noise_estimate(cpi);
 
   if (cpi->oxcf.pass == 0 &&
       cpi->oxcf.rc_mode == VPX_CBR &&
--- a/vp9/encoder/vp9_encoder.h
+++ b/vp9/encoder/vp9_encoder.h
@@ -35,6 +35,7 @@
 #include "vp9/encoder/vp9_lookahead.h"
 #include "vp9/encoder/vp9_mbgraph.h"
 #include "vp9/encoder/vp9_mcomp.h"
+#include "vp9/encoder/vp9_noise_estimate.h"
 #include "vp9/encoder/vp9_quantize.h"
 #include "vp9/encoder/vp9_ratectrl.h"
 #include "vp9/encoder/vp9_rd.h"
@@ -489,6 +490,8 @@
   int resize_avg_qp;
   int resize_buffer_underflow;
   int resize_count;
+
+  NOISE_ESTIMATE noise_estimate;
 
   // VAR_BASED_PARTITION thresholds
   // 0 - threshold_64x64; 1 - threshold_32x32;
--- /dev/null
+++ b/vp9/encoder/vp9_noise_estimate.c
@@ -1,0 +1,210 @@
+/*
+ *  Copyright (c) 2015 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 <assert.h>
+#include <limits.h>
+#include <math.h>
+
+#include "./vpx_dsp_rtcd.h"
+#include "vpx_dsp/vpx_dsp_common.h"
+#include "vpx_scale/yv12config.h"
+#include "vpx/vpx_integer.h"
+#include "vp9/common/vp9_reconinter.h"
+#include "vp9/encoder/vp9_context_tree.h"
+#include "vp9/encoder/vp9_noise_estimate.h"
+#include "vp9/encoder/vp9_encoder.h"
+
+void vp9_noise_estimate_init(NOISE_ESTIMATE *const ne,
+                             int width,
+                             int height) {
+  ne->enabled = 0;
+  ne->level = kLow;
+  ne->value = 0;
+  ne->count = 0;
+  ne->thresh = 20;
+  if (width * height >= 1920 * 1080) {
+    ne->thresh = 70;
+  } else if (width * height >= 1280 * 720) {
+    ne->thresh = 40;
+  }
+}
+
+int enable_noise_estimation(VP9_COMP *const cpi) {
+  // Enable noise estimation if denoising is on (and cyclic refresh, since
+  // noise estimate is currently using a struct defined in cyclic refresh).
+#if CONFIG_VP9_TEMPORAL_DENOISING
+  if (cpi->oxcf.noise_sensitivity > 0 &&
+      cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ)
+    return 1;
+#endif
+  // Only allow noise estimate under certain encoding mode.
+  // Enabled for 1 pass CBR, speed >=5, and if resolution is same as original.
+  // Not enabled for SVC mode and screen_content_mode.
+  // Not enabled for low resolutions.
+  if (cpi->oxcf.pass == 0 &&
+      cpi->oxcf.rc_mode == VPX_CBR &&
+      cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ &&
+      cpi->oxcf.speed >= 5 &&
+      cpi->resize_state == ORIG &&
+      !cpi->use_svc &&
+      cpi->oxcf.content != VP9E_CONTENT_SCREEN &&
+      cpi->common.width > 352 &&
+      cpi->common.height > 288)
+    return 1;
+  else
+    return 0;
+}
+
+static void copy_frame(YV12_BUFFER_CONFIG * const dest,
+                       const YV12_BUFFER_CONFIG * const src) {
+  int r;
+  const uint8_t *srcbuf = src->y_buffer;
+  uint8_t *destbuf = dest->y_buffer;
+
+  assert(dest->y_width == src->y_width);
+  assert(dest->y_height == src->y_height);
+
+  for (r = 0; r < dest->y_height; ++r) {
+    memcpy(destbuf, srcbuf, dest->y_width);
+    destbuf += dest->y_stride;
+    srcbuf += src->y_stride;
+  }
+}
+
+void vp9_update_noise_estimate(VP9_COMP *const cpi) {
+  const VP9_COMMON *const cm = &cpi->common;
+  CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
+  NOISE_ESTIMATE *const ne = &cpi->noise_estimate;
+  // Estimate of noise level every frame_period frames.
+  int frame_period = 10;
+  int thresh_consec_zeromv = 8;
+  unsigned int thresh_sum_diff = 128;
+  int num_frames_estimate = 20;
+  int min_blocks_estimate = cm->mi_rows * cm->mi_cols >> 7;
+  // Estimate is between current source and last source.
+  YV12_BUFFER_CONFIG *last_source = cpi->Last_Source;
+#if CONFIG_VP9_TEMPORAL_DENOISING
+  if (cpi->oxcf.noise_sensitivity > 0)
+    last_source = &cpi->denoiser.last_source;
+#endif
+  ne->enabled = enable_noise_estimation(cpi);
+  if (!ne->enabled ||
+      cm->current_video_frame % frame_period != 0 ||
+      last_source == NULL) {
+#if CONFIG_VP9_TEMPORAL_DENOISING
+  if (cpi->oxcf.noise_sensitivity > 0)
+    copy_frame(&cpi->denoiser.last_source, cpi->Source);
+#endif
+    return;
+  } else {
+    int num_samples = 0;
+    uint64_t avg_est = 0;
+    int bsize = BLOCK_16X16;
+    static const unsigned char const_source[16] = {
+         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
+         128, 128};
+    // Loop over sub-sample of 16x16 blocks of frame, and for blocks that have
+    // been encoded as zero/small mv at least x consecutive frames, compute
+    // the variance to update estimate of noise in the source.
+    const uint8_t *src_y = cpi->Source->y_buffer;
+    const int src_ystride = cpi->Source->y_stride;
+    const uint8_t *last_src_y = last_source->y_buffer;
+    const int last_src_ystride = last_source->y_stride;
+    const uint8_t *src_u = cpi->Source->u_buffer;
+    const uint8_t *src_v = cpi->Source->v_buffer;
+    const int src_uvstride = cpi->Source->uv_stride;
+    const int y_width_shift = (4 << b_width_log2_lookup[bsize]) >> 1;
+    const int y_height_shift = (4 << b_height_log2_lookup[bsize]) >> 1;
+    const int uv_width_shift = y_width_shift >> 1;
+    const int uv_height_shift = y_height_shift >> 1;
+    int mi_row, mi_col;
+    for (mi_row = 0; mi_row < cm->mi_rows; mi_row++) {
+      for (mi_col = 0; mi_col < cm->mi_cols; mi_col++) {
+        // 16x16 blocks, 1/4 sample of frame.
+        if (mi_row % 4 == 0 && mi_col % 4 == 0) {
+          int bl_index = mi_row * cm->mi_cols + mi_col;
+          int bl_index1 = bl_index + 1;
+          int bl_index2 = bl_index + cm->mi_cols;
+          int bl_index3 = bl_index2 + 1;
+          // Only consider blocks that are likely steady background. i.e, have
+          // been encoded as zero/low motion x (= thresh_consec_zeromv) frames
+          // in a row. consec_zero_mv[] defined for 8x8 blocks, so consider all
+          // 4 sub-blocks for 16x16 block. Also, avoid skin blocks.
+          const uint8_t ysource =
+            src_y[y_height_shift * src_ystride + y_width_shift];
+          const uint8_t usource =
+            src_u[uv_height_shift * src_uvstride + uv_width_shift];
+          const uint8_t vsource =
+            src_v[uv_height_shift * src_uvstride + uv_width_shift];
+          int is_skin = vp9_skin_pixel(ysource, usource, vsource);
+          if (cr->consec_zero_mv[bl_index] > thresh_consec_zeromv &&
+              cr->consec_zero_mv[bl_index1] > thresh_consec_zeromv &&
+              cr->consec_zero_mv[bl_index2] > thresh_consec_zeromv &&
+              cr->consec_zero_mv[bl_index3] > thresh_consec_zeromv &&
+              !is_skin) {
+            // Compute variance.
+            unsigned int sse;
+            unsigned int variance = cpi->fn_ptr[bsize].vf(src_y,
+                                                          src_ystride,
+                                                          last_src_y,
+                                                          last_src_ystride,
+                                                          &sse);
+            // Only consider this block as valid for noise measurement if the
+            // average term (sse - variance = N * avg^{2}, N = 16X16) of the
+            // temporal residual is small (avoid effects from lighting change).
+            if ((sse - variance) < thresh_sum_diff) {
+              unsigned int sse2;
+              const unsigned int spatial_variance =
+                  cpi->fn_ptr[bsize].vf(src_y, src_ystride, const_source,
+                                        0, &sse2);
+              avg_est += variance / (10 + spatial_variance);
+              num_samples++;
+            }
+          }
+        }
+        src_y += 8;
+        last_src_y += 8;
+        src_u += 4;
+        src_v += 4;
+      }
+      src_y += (src_ystride << 3) - (cm->mi_cols << 3);
+      last_src_y += (last_src_ystride << 3) - (cm->mi_cols << 3);
+      src_u += (src_uvstride << 2) - (cm->mi_cols << 2);
+      src_v += (src_uvstride << 2) - (cm->mi_cols << 2);
+    }
+    // Update noise estimate if we have at a minimum number of block samples,
+    // and avg_est > 0 (avg_est == 0 can happen if the application inputs
+    // duplicate frames).
+    if (num_samples > min_blocks_estimate && avg_est > 0) {
+      // Normalize.
+      avg_est = (avg_est << 8) / num_samples;
+      // Update noise estimate.
+      ne->value = (3 * ne->value + avg_est) >> 2;
+      ne->count++;
+      if (ne->count == num_frames_estimate) {
+        // Reset counter and check noise level condition.
+        ne->count = 0;
+        if (ne->value > (ne->thresh << 1))
+          ne->level = kHigh;
+        else
+          if (ne->value > ne->thresh)
+            ne->level = kMedium;
+          else
+            ne->level = kLow;
+      }
+    }
+  }
+#if CONFIG_VP9_TEMPORAL_DENOISING
+  if (cpi->oxcf.noise_sensitivity > 0) {
+    copy_frame(&cpi->denoiser.last_source, cpi->Source);
+    vp9_denoiser_set_noise_level(&cpi->denoiser, ne->level);
+  }
+#endif
+}
--- /dev/null
+++ b/vp9/encoder/vp9_noise_estimate.h
@@ -1,0 +1,52 @@
+/*
+ *  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.
+ */
+
+#ifndef VP9_ENCODER_NOISE_ESTIMATE_H_
+#define VP9_ENCODER_NOISE_ESTIMATE_H_
+
+#include "vp9/encoder/vp9_block.h"
+#include "vp9/encoder/vp9_skin_detection.h"
+#include "vpx_scale/yv12config.h"
+
+#if CONFIG_VP9_TEMPORAL_DENOISING
+#include "vp9/encoder/vp9_denoiser.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum noise_level {
+  kLow,
+  kMedium,
+  kHigh
+} NOISE_LEVEL;
+
+typedef struct noise_estimate {
+  int enabled;
+  NOISE_LEVEL level;
+  int value;
+  int thresh;
+  int count;
+} NOISE_ESTIMATE;
+
+struct VP9_COMP;
+
+void vp9_noise_estimate_init(NOISE_ESTIMATE *const ne,
+                             int width,
+                             int height);
+
+void vp9_update_noise_estimate(struct VP9_COMP *const cpi);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // VP9_ENCODER_NOISE_ESTIMATE_H_
--- a/vp9/encoder/vp9_pickmode.c
+++ b/vp9/encoder/vp9_pickmode.c
@@ -1073,12 +1073,9 @@
   // Reduce the intra cost penalty for small blocks (<=16x16).
   int reduction_fac =
       (bsize <= BLOCK_16X16) ? ((bsize <= BLOCK_8X8) ? 4 : 2) : 0;
-#if CONFIG_VP9_TEMPORAL_DENOISING
-  if (cpi->oxcf.noise_sensitivity > 0 &&
-      cpi->denoiser.denoising_level == kHigh)
+  if (cpi->noise_estimate.enabled && cpi->noise_estimate.level == kHigh)
      // Don't reduce intra cost penalty if estimated noise level is high.
      reduction_fac = 0;
-#endif
   return vp9_get_intra_cost_penalty(
       cm->base_qindex, cm->y_dc_delta_q, cm->bit_depth) >> reduction_fac;
 }
--- a/vp9/vp9cx.mk
+++ b/vp9/vp9cx.mk
@@ -82,6 +82,8 @@
 VP9_CX_SRCS-yes += encoder/vp9_aq_complexity.h
 VP9_CX_SRCS-yes += encoder/vp9_skin_detection.c
 VP9_CX_SRCS-yes += encoder/vp9_skin_detection.h
+VP9_CX_SRCS-yes += encoder/vp9_noise_estimate.c
+VP9_CX_SRCS-yes += encoder/vp9_noise_estimate.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