Adding a sample binary to model film grain

This allows a user to provide a denoised file (with their own custom
denoiser) and outputs grain parameters that can be consumed by the
encoder. In this way, rather than denoising directly in the encoder,
the denoising doesn't have to happen on multiple passes (or on multiple
representations/scaled versions of the same file)

Change-Id: Ie28b98d122752144d561c685b72614cf6d51bbef
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dad6559..5407fd3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -416,6 +416,12 @@
                    "${AOM_ROOT}/examples/twopass_encoder.c"
                    $<TARGET_OBJECTS:aom_common_app_util>
                    $<TARGET_OBJECTS:aom_encoder_app_util>)
+    if (CONFIG_FILM_GRAIN)
+      add_executable(noise_model
+                     "${AOM_ROOT}/examples/noise_model.c"
+                     $<TARGET_OBJECTS:aom_common_app_util>
+                     $<TARGET_OBJECTS:aom_encoder_app_util>)
+    endif ()
     if (CONFIG_SCALABILITY)
       add_executable(scalable_encoder
                      "${AOM_ROOT}/examples/scalable_encoder.c"
@@ -425,7 +431,7 @@
 
     # Maintain a list of encoder example targets.
     set(AOM_ENCODER_EXAMPLE_TARGETS
-        aomenc lossless_encoder set_maps simple_encoder twopass_encoder)
+        aomenc lossless_encoder noise_model set_maps simple_encoder twopass_encoder)
 
     if (CONFIG_SCALABILITY)
       set(AOM_ENCODER_EXAMPLE_TARGETS ${AOM_ENCODER_EXAMPLE_TARGETS}
diff --git a/aom_dsp/noise_model.c b/aom_dsp/noise_model.c
index de09cec..4cccd68 100644
--- a/aom_dsp/noise_model.c
+++ b/aom_dsp/noise_model.c
@@ -44,6 +44,14 @@
   memset(eqns->b, 0, sizeof(*eqns->b) * n);
 }
 
+static void equation_system_copy(aom_equation_system_t *dst,
+                                 const aom_equation_system_t *src) {
+  const int n = dst->n;
+  memcpy(dst->A, src->A, sizeof(*dst->A) * n * n);
+  memcpy(dst->x, src->x, sizeof(*dst->x) * n);
+  memcpy(dst->b, src->b, sizeof(*dst->b) * n);
+}
+
 static int equation_system_init(aom_equation_system_t *eqns, int n) {
   eqns->A = (double *)aom_malloc(sizeof(*eqns->A) * n * n);
   eqns->b = (double *)aom_malloc(sizeof(*eqns->b) * n);
@@ -105,6 +113,12 @@
   memset(eqns, 0, sizeof(*eqns));
 }
 
+static void noise_strength_solver_clear(aom_noise_strength_solver_t *solver) {
+  equation_system_clear(&solver->eqns);
+  solver->num_equations = 0;
+  solver->total = 0;
+}
+
 static void noise_strength_solver_add(aom_noise_strength_solver_t *dest,
                                       aom_noise_strength_solver_t *src) {
   equation_system_add(&dest->eqns, &src->eqns);
@@ -320,8 +334,9 @@
         min_index = j;
       }
     }
-    double dx = lut->points[min_index + 1][0] - lut->points[min_index - 1][0];
-    double avg_residual = residual[min_index] / dx;
+    const double dx =
+        lut->points[min_index + 1][0] - lut->points[min_index - 1][0];
+    const double avg_residual = residual[min_index] / dx;
     if (lut->num_points <= max_output_points && avg_residual > kTolerance) {
       break;
     }
@@ -750,6 +765,46 @@
   }
 }
 
+// Return true if the noise estimate appears to be different from the combined
+// (multi-frame) estimate. The difference is measured by checking whether the
+// AR coefficients have diverged (using a threshold on normalized cross
+// correlation), or whether the noise strength has changed.
+static int is_noise_model_different(aom_noise_model_t *const noise_model) {
+  // These thresholds are kind of arbitrary and will likely need further tuning
+  // (or exported as parameters). The threshold on noise strength is a weighted
+  // difference between the noise strength histograms
+  const double kCoeffThreshold = 0.9;
+  const double kStrengthThreshold = 0.1 / 50;
+  for (int c = 0; c < 1; ++c) {
+    const double corr =
+        aom_normalized_cross_correlation(noise_model->latest_state[c].eqns.x,
+                                         noise_model->combined_state[c].eqns.x,
+                                         noise_model->combined_state[c].eqns.n);
+    if (corr < kCoeffThreshold) return 1;
+
+    const double dx =
+        1.0 / noise_model->latest_state[c].strength_solver.num_bins;
+
+    const aom_equation_system_t *latest_eqns =
+        &noise_model->latest_state[c].strength_solver.eqns;
+    const aom_equation_system_t *combined_eqns =
+        &noise_model->combined_state[c].strength_solver.eqns;
+    double diff = 0;
+    double total_weight = 0;
+    for (int j = 0; j < latest_eqns->n; ++j) {
+      double weight = 0;
+      for (int i = 0; i < latest_eqns->n; ++i) {
+        weight += latest_eqns->A[i * latest_eqns->n + j];
+      }
+      weight = sqrt(weight);
+      diff += weight * fabs(latest_eqns->x[j] - combined_eqns->x[j]);
+      total_weight += weight;
+    }
+    if (diff * dx / total_weight > kStrengthThreshold) return 1;
+  }
+  return 0;
+}
+
 aom_noise_status_t aom_noise_model_update(
     aom_noise_model_t *const noise_model, const uint8_t *const data[3],
     const uint8_t *const denoised[3], int w, int h, int stride[3],
@@ -774,6 +829,7 @@
   // Clear the latest equation system
   for (i = 0; i < 3; ++i) {
     equation_system_clear(&noise_model->latest_state[i].eqns);
+    noise_strength_solver_clear(&noise_model->latest_state[i].strength_solver);
   }
 
   // Check that we have enough flat blocks
@@ -828,7 +884,8 @@
     // Check noise characteristics and return if error.
     if (channel == 0 &&
         noise_model->combined_state[channel].strength_solver.num_equations >
-            0) {
+            0 &&
+        is_noise_model_different(noise_model)) {
       y_model_different = 1;
     }
 
@@ -863,6 +920,19 @@
                            : AOM_NOISE_STATUS_OK;
 }
 
+void aom_noise_model_save_latest(aom_noise_model_t *noise_model) {
+  for (int c = 0; c < 3; c++) {
+    equation_system_copy(&noise_model->combined_state[c].eqns,
+                         &noise_model->latest_state[c].eqns);
+    equation_system_copy(&noise_model->combined_state[c].strength_solver.eqns,
+                         &noise_model->latest_state[c].strength_solver.eqns);
+    noise_model->combined_state[c].strength_solver.num_equations =
+        noise_model->latest_state[c].strength_solver.num_equations;
+    noise_model->combined_state[c].strength_solver.total =
+        noise_model->latest_state[c].strength_solver.total;
+  }
+}
+
 int aom_noise_model_get_grain_parameters(aom_noise_model_t *const noise_model,
                                          aom_film_grain_t *film_grain) {
   if (noise_model->params.lag > 3) {
@@ -870,6 +940,10 @@
     return 0;
   }
   memset(film_grain, 0, sizeof(*film_grain));
+
+  film_grain->apply_grain = 1;
+  film_grain->update_parameters = 1;
+
   film_grain->ar_coeff_lag = noise_model->params.lag;
 
   // Convert the scaling functions to 8 bit values
@@ -893,6 +967,7 @@
   const int max_scaling_value_log2 =
       clamp((int)floor(log2(max_scaling_value) + 1), 2, 5);
   film_grain->scaling_shift = 5 + (8 - max_scaling_value_log2);
+
   const double scale_factor = 1 << (8 - max_scaling_value_log2);
   film_grain->num_y_points = scaling_points[0].num_points;
   film_grain->num_cb_points = scaling_points[1].num_points;
diff --git a/aom_dsp/noise_model.h b/aom_dsp/noise_model.h
index b663268..7e32d49 100644
--- a/aom_dsp/noise_model.h
+++ b/aom_dsp/noise_model.h
@@ -226,6 +226,14 @@
     const uint8_t *const denoised[3], int w, int h, int strides[3],
     int chroma_sub_log2[2], const uint8_t *const flat_blocks, int block_size);
 
+/*\brief Save the "latest" estimate into the "combined" estimate.
+ *
+ * This is meant to be called when the noise modeling detected a change
+ * in parameters (or for example, if a user wanted to reset estimation at
+ * a shot boundary).
+ */
+void aom_noise_model_save_latest(aom_noise_model_t *noise_model);
+
 /*!\brief Converts the noise_model parameters to the corresponding
  *    grain_parameters.
  *
diff --git a/examples/noise_model.c b/examples/noise_model.c
new file mode 100644
index 0000000..36e6bc4
--- /dev/null
+++ b/examples/noise_model.c
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2018, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+/*!\file
+ * \brief This is an sample binary to create noise params from input video.
+ *
+ * To allow for external denoising applications, this sample binary illustrates
+ * how to create a film grain table (film grain params as a function of time)
+ * from an input video and its corresponding denoised source.
+ *
+ * The --output-grain-table file can be passed as input to the encoder (in
+ * aomenc this is done through the "--film-grain-table" parameter).
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../args.h"
+#include "../tools_common.h"
+#include "../video_writer.h"
+#include "aom/aom_encoder.h"
+#include "aom_dsp/aom_dsp_common.h"
+#include "aom_dsp/noise_model.h"
+#include "aom_dsp/noise_util.h"
+#include "aom_dsp/grain_table.h"
+#include "aom_mem/aom_mem.h"
+
+static const char *exec_name;
+
+void usage_exit(void) {
+  fprintf(stderr,
+          "Usage: %s --input=<input> --input-denoised=<denoised> "
+          "--output-grain-table=<outfile> "
+          "See comments in noise_model.c for more information.\n",
+          exec_name);
+  exit(EXIT_FAILURE);
+}
+
+static const arg_def_t help =
+    ARG_DEF(NULL, "help", 0, "Show usage options and exit");
+static const arg_def_t width_arg =
+    ARG_DEF("w", "width", 1, "Input width (if rawvideo)");
+static const arg_def_t height_arg =
+    ARG_DEF("h", "height", 1, "Input height (if rawvideo)");
+static const arg_def_t skip_frames_arg =
+    ARG_DEF("s", "skip-frames", 1, "Number of frames to skip (default = 1)");
+static const arg_def_t fps_arg = ARG_DEF(NULL, "fps", 1, "Frame rate");
+static const arg_def_t input_arg = ARG_DEF("-i", "input", 1, "Input filename");
+static const arg_def_t output_grain_table_arg =
+    ARG_DEF("n", "output-grain-table", 1, "Output noise file");
+static const arg_def_t input_denoised_arg =
+    ARG_DEF("d", "input-denoised", 1, "Input denoised filename (YUV) only");
+static const arg_def_t block_size_arg =
+    ARG_DEF("b", "block_size", 1, "Block size");
+static const arg_def_t use_i420 =
+    ARG_DEF(NULL, "i420", 0, "Input file (and denoised) is I420 (default)");
+static const arg_def_t use_i422 =
+    ARG_DEF(NULL, "i422", 0, "Input file (and denoised) is I422");
+static const arg_def_t use_i444 =
+    ARG_DEF(NULL, "i444", 0, "Input file (and denoised) is I444");
+
+typedef struct {
+  int width;
+  int height;
+  struct aom_rational fps;
+  const char *input;
+  const char *input_denoised;
+  const char *output_grain_table;
+  int img_fmt;
+  int block_size;
+  int run_flat_block_finder;
+  int force_flat_psd;
+  int skip_frames;
+} noise_model_args_t;
+
+void parse_args(noise_model_args_t *noise_args, int *argc, char **argv) {
+  struct arg arg;
+  static const arg_def_t *main_args[] = { &help,
+                                          &input_arg,
+                                          &fps_arg,
+                                          &width_arg,
+                                          &height_arg,
+                                          &block_size_arg,
+                                          &output_grain_table_arg,
+                                          &input_denoised_arg,
+                                          &use_i420,
+                                          &use_i422,
+                                          &use_i444,
+                                          NULL };
+  for (int argi = *argc + 1; *argv; argi++, argv++) {
+    if (arg_match(&arg, &help, argv)) {
+      fprintf(stdout, "\nOptions:\n");
+      arg_show_usage(stdout, main_args);
+      exit(0);
+    } else if (arg_match(&arg, &width_arg, argv)) {
+      noise_args->width = atoi(arg.val);
+    } else if (arg_match(&arg, &height_arg, argv)) {
+      noise_args->height = atoi(arg.val);
+    } else if (arg_match(&arg, &input_arg, argv)) {
+      noise_args->input = arg.val;
+    } else if (arg_match(&arg, &input_denoised_arg, argv)) {
+      noise_args->input_denoised = arg.val;
+    } else if (arg_match(&arg, &output_grain_table_arg, argv)) {
+      noise_args->output_grain_table = arg.val;
+    } else if (arg_match(&arg, &block_size_arg, argv)) {
+      noise_args->block_size = atoi(arg.val);
+    } else if (arg_match(&arg, &fps_arg, argv)) {
+      noise_args->fps = arg_parse_rational(&arg);
+    } else if (arg_match(&arg, &use_i420, argv)) {
+      noise_args->img_fmt = AOM_IMG_FMT_I420;
+    } else if (arg_match(&arg, &use_i422, argv)) {
+      noise_args->img_fmt = AOM_IMG_FMT_I422;
+    } else if (arg_match(&arg, &use_i444, argv)) {
+      noise_args->img_fmt = AOM_IMG_FMT_I444;
+    } else if (arg_match(&arg, &skip_frames_arg, argv)) {
+      noise_args->skip_frames = atoi(arg.val);
+    } else {
+      fprintf(stdout, "Unknown arg: %s\n\nUsage:\n", *argv);
+      arg_show_usage(stdout, main_args);
+      exit(0);
+    }
+  }
+}
+
+int main(int argc, char *argv[]) {
+  noise_model_args_t args = { 0,  0, { 1, 25 }, 0, 0, 0, AOM_IMG_FMT_I420,
+                              32, 0, 0,         1 };
+  aom_image_t raw, denoised;
+  FILE *infile = NULL;
+  AvxVideoInfo info;
+
+  memset(&info, 0, sizeof(info));
+
+  exec_name = argv[0];
+  parse_args(&args, &argc, argv + 1);
+
+  info.frame_width = args.width;
+  info.frame_height = args.height;
+  info.time_base.numerator = args.fps.den;
+  info.time_base.denominator = args.fps.num;
+
+  if (info.frame_width <= 0 || info.frame_height <= 0 ||
+      (info.frame_width % 2) != 0 || (info.frame_height % 2) != 0) {
+    die("Invalid frame size: %dx%d", info.frame_width, info.frame_height);
+  }
+  if (!aom_img_alloc(&raw, args.img_fmt, info.frame_width, info.frame_height,
+                     1)) {
+    die("Failed to allocate image.");
+  }
+  if (!aom_img_alloc(&denoised, args.img_fmt, info.frame_width,
+                     info.frame_height, 1)) {
+    die("Failed to allocate image.");
+  }
+  infile = fopen(args.input, "r");
+  if (!infile) {
+    die("Failed to open input file:", args.input);
+  }
+  const int block_size = args.block_size;
+  aom_flat_block_finder_t block_finder;
+  aom_flat_block_finder_init(&block_finder, block_size);
+
+  const int num_blocks_w = (info.frame_width + block_size - 1) / block_size;
+  const int num_blocks_h = (info.frame_height + block_size - 1) / block_size;
+  uint8_t *flat_blocks = (uint8_t *)aom_malloc(num_blocks_w * num_blocks_h);
+  aom_noise_model_t noise_model;
+  aom_noise_model_params_t params = { AOM_NOISE_SHAPE_SQUARE, 3 };
+  aom_noise_model_init(&noise_model, params);
+
+  FILE *denoised_file = 0;
+  if (args.input_denoised) {
+    denoised_file = fopen(args.input_denoised, "rb");
+    if (!denoised_file)
+      die("Unable to open input_denoised: %s", args.input_denoised);
+  } else {
+    die("--input-denoised file must be specified");
+  }
+  aom_film_grain_table_t grain_table = { 0 };
+
+  int64_t prev_timestamp = 0;
+  int frame_count = 0;
+  while (aom_img_read(&raw, infile)) {
+    if (args.input_denoised) {
+      if (!aom_img_read(&denoised, denoised_file)) {
+        die("Unable to read input denoised file");
+      }
+    }
+    if (frame_count % args.skip_frames == 0) {
+      int num_flat_blocks = num_blocks_w * num_blocks_h;
+      memset(flat_blocks, 1, num_flat_blocks);
+      if (args.run_flat_block_finder) {
+        memset(flat_blocks, 0, num_flat_blocks);
+        num_flat_blocks = aom_flat_block_finder_run(
+            &block_finder, raw.planes[0], info.frame_width, info.frame_height,
+            info.frame_width, flat_blocks);
+        fprintf(stdout, "Num flat blocks %d\n", num_flat_blocks);
+      }
+
+      const uint8_t *planes[3] = { raw.planes[0], raw.planes[1],
+                                   raw.planes[2] };
+      uint8_t *denoised_planes[3] = { denoised.planes[0], denoised.planes[1],
+                                      denoised.planes[2] };
+      int strides[3] = { raw.stride[0], raw.stride[1], raw.stride[2] };
+      int chroma_sub[3] = { raw.x_chroma_shift, raw.y_chroma_shift, 0 };
+
+      fprintf(stdout, "Updating noise model...\n");
+      aom_noise_status_t status = aom_noise_model_update(
+          &noise_model, (const uint8_t *const *)planes,
+          (const uint8_t *const *)denoised_planes, info.frame_width,
+          info.frame_height, strides, chroma_sub, flat_blocks, block_size);
+
+      int64_t cur_timestamp =
+          frame_count * 10000000ULL * args.fps.den / args.fps.num;
+      if (status == AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE) {
+        fprintf(stdout,
+                "Noise type is different, updating parameters for time "
+                "[ %" PRId64 ", %" PRId64 ")\n",
+                prev_timestamp, cur_timestamp);
+        aom_film_grain_t grain;
+        aom_noise_model_get_grain_parameters(&noise_model, &grain);
+        aom_film_grain_table_append(&grain_table, prev_timestamp, cur_timestamp,
+                                    &grain);
+        aom_noise_model_save_latest(&noise_model);
+        prev_timestamp = cur_timestamp;
+      }
+
+      fprintf(stdout, "Done noise model update, status = %d\n", status);
+    }
+    frame_count++;
+  }
+
+  aom_film_grain_t grain;
+  aom_noise_model_get_grain_parameters(&noise_model, &grain);
+  aom_film_grain_table_append(&grain_table, prev_timestamp, INT64_MAX, &grain);
+  if (args.output_grain_table) {
+    struct aom_internal_error_info error_info;
+    if (AOM_CODEC_OK != aom_film_grain_table_write(&grain_table,
+                                                   args.output_grain_table,
+                                                   &error_info)) {
+      die("Unable to write output film grain table");
+    }
+  }
+  aom_film_grain_table_free(&grain_table);
+
+  if (infile) fclose(infile);
+  if (denoised_file) fclose(denoised_file);
+  aom_img_free(&raw);
+  aom_img_free(&denoised);
+
+  return EXIT_SUCCESS;
+}
diff --git a/test/noise_model_test.cc b/test/noise_model_test.cc
index 992884a..4405d2e 100644
--- a/test/noise_model_test.cc
+++ b/test/noise_model_test.cc
@@ -602,6 +602,108 @@
   }
 }
 
+TEST_F(NoiseModelUpdateTest, NoiseStrengthChangeSignalsDifferentNoiseType) {
+  // Create a gradient image with std = 1 uncorrelated noise
+  const double kStd = 1;
+  for (int i = 0; i < kWidth * kHeight; ++i) {
+    data_ptr_[0][i] = (uint8_t)(aom_randn(kStd) + (i % kWidth) + 64);
+    data_ptr_[1][i] = (uint8_t)(aom_randn(kStd) + (i % kWidth) + 64);
+    data_ptr_[2][i] = (uint8_t)(aom_randn(kStd) + (i % kWidth) + 64);
+  }
+  flat_blocks_.assign(flat_blocks_.size(), 1);
+  EXPECT_EQ(AOM_NOISE_STATUS_OK,
+            aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth,
+                                   kHeight, strides_, chroma_sub_,
+                                   &flat_blocks_[0], kBlockSize));
+  const int kNumBlocks = kWidth * kHeight / kBlockSize / kBlockSize;
+  EXPECT_EQ(kNumBlocks, model_.latest_state[0].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[1].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[2].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.combined_state[0].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.combined_state[1].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.combined_state[2].strength_solver.num_equations);
+
+  // Bump up noise by an insignificant amount
+  for (int i = 0; i < kWidth * kHeight; ++i) {
+    data_ptr_[1][i] = (uint8_t)(data_ptr_[1][i] + aom_randn(0.2));
+    data_ptr_[2][i] = (uint8_t)(data_ptr_[2][i] + aom_randn(0.2));
+  }
+  EXPECT_EQ(AOM_NOISE_STATUS_OK,
+            aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth,
+                                   kHeight, strides_, chroma_sub_,
+                                   &flat_blocks_[0], kBlockSize));
+  EXPECT_EQ(kNumBlocks, model_.latest_state[0].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[1].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[2].strength_solver.num_equations);
+  EXPECT_EQ(2 * kNumBlocks,
+            model_.combined_state[0].strength_solver.num_equations);
+  EXPECT_EQ(2 * kNumBlocks,
+            model_.combined_state[1].strength_solver.num_equations);
+  EXPECT_EQ(2 * kNumBlocks,
+            model_.combined_state[2].strength_solver.num_equations);
+
+  // Bump up the noise strength on half the image for one channel by a
+  // significant amount.
+  for (int i = 0; i < kWidth * kHeight; ++i) {
+    if (i % kWidth < kWidth / 2)
+      data_ptr_[0][i] = (uint8_t)(data_ptr_[0][i] + aom_randn(0.5));
+  }
+  EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE,
+            aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth,
+                                   kHeight, strides_, chroma_sub_,
+                                   &flat_blocks_[0], kBlockSize));
+  // Since we didn't update the combined state, it should still be at 2 *
+  // num_blocks
+  EXPECT_EQ(kNumBlocks, model_.latest_state[0].strength_solver.num_equations);
+  EXPECT_EQ(2 * kNumBlocks,
+            model_.combined_state[0].strength_solver.num_equations);
+
+  // In normal operation, the "latest" estimate can be saved to the "combined"
+  // state for continued updates.
+  aom_noise_model_save_latest(&model_);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[0].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[1].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.latest_state[2].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.combined_state[0].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.combined_state[1].strength_solver.num_equations);
+  EXPECT_EQ(kNumBlocks, model_.combined_state[2].strength_solver.num_equations);
+}
+
+TEST_F(NoiseModelUpdateTest, NoiseCoeffsSignalsDifferentNoiseType) {
+  const double kCoeffs[2][24] = {
+    { 0.02884, -0.03356, 0.00633,  0.01757,  0.02849,  -0.04620,
+      0.02833, -0.07178, 0.07076,  -0.11603, -0.10413, -0.16571,
+      0.05158, -0.07969, 0.02640,  -0.07191, 0.02530,  0.41968,
+      0.21450, -0.00702, -0.01401, -0.03676, -0.08713, 0.44196 },
+    { 0.00269, -0.01291, -0.01513, 0.07234,  0.03208,   0.00477,
+      0.00226, -0.00254, 0.03533,  0.12841,  -0.25970,  -0.06336,
+      0.05238, -0.00845, -0.03118, 0.09043,  -0.36558,  0.48903,
+      0.00595, -0.11938, 0.02106,  0.095956, -0.350139, 0.59305 }
+  };
+
+  aom_noise_synth(model_.params.lag, model_.n, model_.coords, kCoeffs[0],
+                  noise_ptr_[0], kWidth, kHeight);
+  for (int i = 0; i < kWidth * kHeight; ++i) {
+    data_ptr_[0][i] = (uint8_t)(128 + noise_ptr_[0][i]);
+  }
+  flat_blocks_.assign(flat_blocks_.size(), 1);
+  EXPECT_EQ(AOM_NOISE_STATUS_OK,
+            aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth,
+                                   kHeight, strides_, chroma_sub_,
+                                   &flat_blocks_[0], kBlockSize));
+
+  // Now try with the second set of AR coefficients
+  aom_noise_synth(model_.params.lag, model_.n, model_.coords, kCoeffs[1],
+                  noise_ptr_[0], kWidth, kHeight);
+  for (int i = 0; i < kWidth * kHeight; ++i) {
+    data_ptr_[0][i] = (uint8_t)(128 + noise_ptr_[0][i]);
+  }
+  EXPECT_EQ(AOM_NOISE_STATUS_DIFFERENT_NOISE_TYPE,
+            aom_noise_model_update(&model_, data_ptr_, denoised_ptr_, kWidth,
+                                   kHeight, strides_, chroma_sub_,
+                                   &flat_blocks_[0], kBlockSize));
+}
+
 TEST(NoiseModelGetGrainParameters, TestLagSize) {
   aom_film_grain_t film_grain;
   for (int lag = 1; lag <= 3; ++lag) {
@@ -757,6 +859,8 @@
   EXPECT_EQ(7, film_grain.ar_coeff_shift);
   EXPECT_EQ(10, film_grain.scaling_shift);
   EXPECT_EQ(kNumScalingPointsY, film_grain.num_y_points);
+  EXPECT_EQ(1, film_grain.update_parameters);
+  EXPECT_EQ(1, film_grain.apply_grain);
 
   const int kNumARCoeffs = 24;
   for (int i = 0; i < kNumARCoeffs; ++i) {