Allow extracting the linear AEC output

This CL enables extracting the linear AEC output,
allowing for more straightforward
testing/development.

Bug: b/140823178
Change-Id: I14f7934008d87066b35500466cb6e6d96f811688
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/153672
Commit-Queue: Per Åhgren <peah@webrtc.org>
Reviewed-by: Gustaf Ullberg <gustaf@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29789}
diff --git a/modules/audio_processing/test/audio_processing_simulator.cc b/modules/audio_processing/test/audio_processing_simulator.cc
index 7f354a9..38b97ca 100644
--- a/modules/audio_processing/test/audio_processing_simulator.cc
+++ b/modules/audio_processing/test/audio_processing_simulator.cc
@@ -227,6 +227,20 @@
     buffer_file_writer_->Write(*out_buf_);
   }
 
+  if (linear_aec_output_file_writer_) {
+    bool output_available = ap_->GetLinearAecOutput(linear_aec_output_buf_);
+    RTC_CHECK(output_available);
+    RTC_CHECK_GT(linear_aec_output_buf_.size(), 0);
+    RTC_CHECK_EQ(linear_aec_output_buf_[0].size(), 160);
+    for (size_t k = 0; k < linear_aec_output_buf_[0].size(); ++k) {
+      for (size_t ch = 0; ch < linear_aec_output_buf_.size(); ++ch) {
+        RTC_CHECK_EQ(linear_aec_output_buf_[ch].size(), 160);
+        linear_aec_output_file_writer_->WriteSamples(
+            &linear_aec_output_buf_[ch][k], 1);
+      }
+    }
+  }
+
   if (residual_echo_likelihood_graph_writer_.is_open()) {
     auto stats = ap_->GetStatistics(true /*has_remote_tracks*/);
     residual_echo_likelihood_graph_writer_
@@ -342,6 +356,21 @@
         settings_.processed_capture_samples);
   }
 
+  if (settings_.linear_aec_output_filename) {
+    std::string filename;
+    if (settings_.store_intermediate_output) {
+      filename = GetIndexedOutputWavFilename(
+          *settings_.linear_aec_output_filename, output_reset_counter_);
+    } else {
+      filename = *settings_.linear_aec_output_filename;
+    }
+
+    linear_aec_output_file_writer_.reset(
+        new WavWriter(filename, 16000, out_config_.num_channels()));
+
+    linear_aec_output_buf_.resize(out_config_.num_channels());
+  }
+
   if (settings_.reverse_output_filename) {
     std::string filename;
     if (settings_.store_intermediate_output) {
@@ -410,6 +439,8 @@
     apm_config.echo_canceller.mobile_mode = use_aecm;
     apm_config.echo_canceller.use_legacy_aec = use_legacy_aec;
   }
+  apm_config.echo_canceller.export_linear_aec_output =
+      !!settings_.linear_aec_output_filename;
 
   RTC_CHECK(!(use_legacy_aec && settings_.aec_settings_filename))
       << "The legacy AEC cannot be configured using settings";
@@ -421,9 +452,14 @@
         std::cout << "Reading AEC Parameters from JSON input." << std::endl;
       }
       cfg = ReadAec3ConfigFromJsonFile(*settings_.aec_settings_filename);
-      echo_control_factory.reset(new EchoCanceller3Factory(cfg));
     }
 
+    if (settings_.linear_aec_output_filename) {
+      cfg.filter.export_linear_aec_output = true;
+    }
+
+    echo_control_factory.reset(new EchoCanceller3Factory(cfg));
+
     if (settings_.print_aec_parameter_values) {
       if (!settings_.use_quiet_output) {
         std::cout << "AEC settings:" << std::endl;
diff --git a/modules/audio_processing/test/audio_processing_simulator.h b/modules/audio_processing/test/audio_processing_simulator.h
index bf718b2..8ee2db8 100644
--- a/modules/audio_processing/test/audio_processing_simulator.h
+++ b/modules/audio_processing/test/audio_processing_simulator.h
@@ -47,6 +47,7 @@
   absl::optional<std::string> input_filename;
   absl::optional<std::string> reverse_input_filename;
   absl::optional<std::string> artificial_nearend_filename;
+  absl::optional<std::string> linear_aec_output_filename;
   absl::optional<bool> use_aec;
   absl::optional<bool> use_aecm;
   absl::optional<bool> use_ed;  // Residual Echo Detector.
@@ -156,6 +157,7 @@
   std::unique_ptr<ChannelBuffer<float>> out_buf_;
   std::unique_ptr<ChannelBuffer<float>> reverse_in_buf_;
   std::unique_ptr<ChannelBuffer<float>> reverse_out_buf_;
+  std::vector<std::array<float, 160>> linear_aec_output_buf_;
   StreamConfig in_config_;
   StreamConfig out_config_;
   StreamConfig reverse_in_config_;
@@ -178,6 +180,7 @@
   std::unique_ptr<ChannelBufferWavWriter> buffer_file_writer_;
   std::unique_ptr<ChannelBufferWavWriter> reverse_buffer_file_writer_;
   std::unique_ptr<ChannelBufferVectorWriter> buffer_memory_writer_;
+  std::unique_ptr<WavWriter> linear_aec_output_file_writer_;
   ApiCallStatistics api_call_statistics_;
   std::ofstream residual_echo_likelihood_graph_writer_;
   int analog_mic_level_;
diff --git a/modules/audio_processing/test/audioproc_float_impl.cc b/modules/audio_processing/test/audioproc_float_impl.cc
index 3e755b5..8301c4e 100644
--- a/modules/audio_processing/test/audioproc_float_impl.cc
+++ b/modules/audio_processing/test/audioproc_float_impl.cc
@@ -40,6 +40,7 @@
           artificial_nearend,
           "",
           "Artificial nearend wav filename");
+ABSL_FLAG(std::string, linear_aec_output, "", "Linear AEC output wav filename");
 ABSL_FLAG(int,
           output_num_channels,
           kParameterNotSpecifiedValue,
@@ -364,6 +365,8 @@
                         &settings.reverse_output_filename);
   SetSettingIfSpecified(absl::GetFlag(FLAGS_artificial_nearend),
                         &settings.artificial_nearend_filename);
+  SetSettingIfSpecified(absl::GetFlag(FLAGS_linear_aec_output),
+                        &settings.linear_aec_output_filename);
   SetSettingIfSpecified(absl::GetFlag(FLAGS_output_num_channels),
                         &settings.output_num_channels);
   SetSettingIfSpecified(absl::GetFlag(FLAGS_reverse_output_num_channels),
@@ -508,6 +511,19 @@
         "aec dump input string!\n");
   }
 
+  ReportConditionalErrorAndExit(settings.use_aec && !(*settings.use_aec) &&
+                                    settings.linear_aec_output_filename,
+                                "Error: The linear AEC ouput filename cannot "
+                                "be specified without the AEC being active");
+
+  ReportConditionalErrorAndExit(
+      ((settings.use_aec && *settings.use_aec && settings.use_legacy_aec &&
+        *settings.use_legacy_aec) ||
+       (settings.use_aecm && *settings.use_aecm)) &&
+          !!settings.linear_aec_output_filename,
+      "Error: The linear AEC ouput filename cannot be specified when the "
+      "legacy AEC or the AECm are used");
+
   ReportConditionalErrorAndExit(
       settings.use_aec && *settings.use_aec && settings.use_aecm &&
           *settings.use_aecm,
@@ -618,6 +634,11 @@
       "Error: --artifical_nearend must be a valid .wav file name.\n");
 
   ReportConditionalErrorAndExit(
+      settings.linear_aec_output_filename &&
+          (!valid_wav_name(*settings.linear_aec_output_filename)),
+      "Error: --linear_aec_output must be a valid .wav file name.\n");
+
+  ReportConditionalErrorAndExit(
       WEBRTC_APM_DEBUG_DUMP == 0 && settings.dump_internal_data,
       "Error: --dump_data cannot be set without proper build support.\n");
 
diff --git a/modules/audio_processing/test/echo_control_mock.h b/modules/audio_processing/test/echo_control_mock.h
index c2082c2..95d3be5 100644
--- a/modules/audio_processing/test/echo_control_mock.h
+++ b/modules/audio_processing/test/echo_control_mock.h
@@ -24,6 +24,10 @@
   MOCK_METHOD1(AnalyzeCapture, void(AudioBuffer* capture));
   MOCK_METHOD2(ProcessCapture,
                void(AudioBuffer* capture, bool echo_path_change));
+  MOCK_METHOD3(ProcessCapture,
+               void(AudioBuffer* capture,
+                    AudioBuffer* linear_output,
+                    bool echo_path_change));
   MOCK_CONST_METHOD0(GetMetrics, EchoControl::Metrics());
   MOCK_METHOD1(SetAudioBufferDelay, void(int delay_ms));
   MOCK_CONST_METHOD0(ActiveProcessing, bool());