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/api/audio/echo_canceller3_config.h b/api/audio/echo_canceller3_config.h
index 3387526..c8de797 100644
--- a/api/audio/echo_canceller3_config.h
+++ b/api/audio/echo_canceller3_config.h
@@ -79,6 +79,7 @@
     bool conservative_initial_phase = false;
     bool enable_shadow_filter_output_usage = true;
     bool use_linear_filter = true;
+    bool export_linear_aec_output = false;
   } filter;
 
   struct Erle {
@@ -185,8 +186,6 @@
     } high_bands_suppression;
 
     float floor_first_increase = 0.00001f;
-    bool enforce_transparent = false;
-    bool enforce_empty_higher_bands = false;
   } suppressor;
 };
 }  // namespace webrtc
diff --git a/api/audio/echo_canceller3_config_json.cc b/api/audio/echo_canceller3_config_json.cc
index c6ee708..ab051bd 100644
--- a/api/audio/echo_canceller3_config_json.cc
+++ b/api/audio/echo_canceller3_config_json.cc
@@ -190,6 +190,8 @@
     ReadParam(section, "enable_shadow_filter_output_usage",
               &cfg.filter.enable_shadow_filter_output_usage);
     ReadParam(section, "use_linear_filter", &cfg.filter.use_linear_filter);
+    ReadParam(section, "export_linear_aec_output",
+              &cfg.filter.export_linear_aec_output);
   }
 
   if (rtc::GetValueFromJsonObject(aec3_root, "erle", &section)) {
@@ -314,10 +316,6 @@
 
     ReadParam(section, "floor_first_increase",
               &cfg.suppressor.floor_first_increase);
-    ReadParam(section, "enforce_transparent",
-              &cfg.suppressor.enforce_transparent);
-    ReadParam(section, "enforce_empty_higher_bands",
-              &cfg.suppressor.enforce_empty_higher_bands);
   }
 }
 
@@ -408,7 +406,12 @@
   ost << "\"conservative_initial_phase\": "
       << (config.filter.conservative_initial_phase ? "true" : "false") << ",";
   ost << "\"enable_shadow_filter_output_usage\": "
-      << (config.filter.enable_shadow_filter_output_usage ? "true" : "false");
+      << (config.filter.enable_shadow_filter_output_usage ? "true" : "false")
+      << ",";
+  ost << "\"use_linear_filter\": "
+      << (config.filter.use_linear_filter ? "true" : "false") << ",";
+  ost << "\"export_linear_aec_output\": "
+      << (config.filter.export_linear_aec_output ? "true" : "false");
 
   ost << "},";
 
@@ -545,12 +548,7 @@
   ost << "\"max_gain_during_echo\": "
       << config.suppressor.high_bands_suppression.max_gain_during_echo;
   ost << "},";
-  ost << "\"floor_first_increase\": " << config.suppressor.floor_first_increase
-      << ",";
-  ost << "\"enforce_transparent\": "
-      << (config.suppressor.enforce_transparent ? "true" : "false") << ",";
-  ost << "\"enforce_empty_higher_bands\": "
-      << (config.suppressor.enforce_empty_higher_bands ? "true" : "false");
+  ost << "\"floor_first_increase\": " << config.suppressor.floor_first_increase;
   ost << "}";
   ost << "}";
   ost << "}";
diff --git a/api/audio/echo_control.h b/api/audio/echo_control.h
index de80f50..b63f123 100644
--- a/api/audio/echo_control.h
+++ b/api/audio/echo_control.h
@@ -31,6 +31,12 @@
   // Processes the capture signal in order to remove the echo.
   virtual void ProcessCapture(AudioBuffer* capture, bool echo_path_change) = 0;
 
+  // As above, but also returns the linear filter output.
+  // TODO(peah): Make pure virtual.
+  virtual void ProcessCapture(AudioBuffer* capture,
+                              AudioBuffer* linear_output,
+                              bool level_change) {}
+
   struct Metrics {
     double echo_return_loss;
     double echo_return_loss_enhancement;
diff --git a/modules/audio_processing/aec3/aec3_common.h b/modules/audio_processing/aec3/aec3_common.h
index bf554e3..d778e50 100644
--- a/modules/audio_processing/aec3/aec3_common.h
+++ b/modules/audio_processing/aec3/aec3_common.h
@@ -42,7 +42,8 @@
 constexpr int kRenderTransferQueueSizeFrames = 100;
 
 constexpr size_t kMaxNumBands = 3;
-constexpr size_t kSubFrameLength = 80;
+constexpr size_t kFrameSize = 160;
+constexpr size_t kSubFrameLength = kFrameSize / 2;
 
 constexpr size_t kBlockSize = kFftLengthBy2;
 constexpr size_t kBlockSizeLog2 = kFftLengthBy2Log2;
diff --git a/modules/audio_processing/aec3/block_processor.cc b/modules/audio_processing/aec3/block_processor.cc
index 8942570..bda2589 100644
--- a/modules/audio_processing/aec3/block_processor.cc
+++ b/modules/audio_processing/aec3/block_processor.cc
@@ -52,6 +52,7 @@
   void ProcessCapture(
       bool echo_path_gain_change,
       bool capture_signal_saturation,
+      std::vector<std::vector<std::vector<float>>>* linear_output,
       std::vector<std::vector<std::vector<float>>>* capture_block) override;
 
   void BufferRender(
@@ -105,6 +106,7 @@
 void BlockProcessorImpl::ProcessCapture(
     bool echo_path_gain_change,
     bool capture_signal_saturation,
+    std::vector<std::vector<std::vector<float>>>* linear_output,
     std::vector<std::vector<std::vector<float>>>* capture_block) {
   RTC_DCHECK(capture_block);
   RTC_DCHECK_EQ(NumBandsForRate(sample_rate_hz_), capture_block->size());
@@ -191,7 +193,7 @@
   if (has_delay_estimator || render_buffer_->HasReceivedBufferDelay()) {
     echo_remover_->ProcessCapture(
         echo_path_variability, capture_signal_saturation, estimated_delay_,
-        render_buffer_->GetRenderBuffer(), capture_block);
+        render_buffer_->GetRenderBuffer(), linear_output, capture_block);
   }
 
   // Update the metrics.
diff --git a/modules/audio_processing/aec3/block_processor.h b/modules/audio_processing/aec3/block_processor.h
index 755444a..9bb0cf1 100644
--- a/modules/audio_processing/aec3/block_processor.h
+++ b/modules/audio_processing/aec3/block_processor.h
@@ -59,6 +59,7 @@
   virtual void ProcessCapture(
       bool echo_path_gain_change,
       bool capture_signal_saturation,
+      std::vector<std::vector<std::vector<float>>>* linear_output,
       std::vector<std::vector<std::vector<float>>>* capture_block) = 0;
 
   // Buffers a block of render data supplied by a FrameBlocker object.
diff --git a/modules/audio_processing/aec3/block_processor_unittest.cc b/modules/audio_processing/aec3/block_processor_unittest.cc
index 9c315e1..2b928e8 100644
--- a/modules/audio_processing/aec3/block_processor_unittest.cc
+++ b/modules/audio_processing/aec3/block_processor_unittest.cc
@@ -48,7 +48,7 @@
                                       std::vector<float>(kBlockSize, 1000.f)));
   for (int k = 0; k < num_iterations; ++k) {
     block_processor->BufferRender(block);
-    block_processor->ProcessCapture(false, false, &block);
+    block_processor->ProcessCapture(false, false, nullptr, &block);
     block_processor->UpdateEchoLeakageStatus(false);
   }
 }
@@ -81,7 +81,8 @@
       std::vector<std::vector<float>>(kNumRenderChannels,
                                       std::vector<float>(kBlockSize - 1, 0.f)));
 
-  EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), "");
+  EXPECT_DEATH(block_processor->ProcessCapture(false, false, nullptr, &block),
+               "");
 }
 
 void RunRenderNumBandsVerificationTest(int sample_rate_hz) {
@@ -117,7 +118,8 @@
       std::vector<std::vector<float>>(kNumRenderChannels,
                                       std::vector<float>(kBlockSize, 0.f)));
 
-  EXPECT_DEATH(block_processor->ProcessCapture(false, false, &block), "");
+  EXPECT_DEATH(block_processor->ProcessCapture(false, false, nullptr, &block),
+               "");
 }
 #endif
 
@@ -172,7 +174,7 @@
       RandomizeSampleVector(&random_generator, render_block[0][0]);
       signal_delay_buffer.Delay(render_block[0][0], capture_block[0][0]);
       block_processor->BufferRender(render_block);
-      block_processor->ProcessCapture(false, false, &capture_block);
+      block_processor->ProcessCapture(false, false, nullptr, &capture_block);
     }
   }
 }
@@ -207,7 +209,7 @@
         .WillRepeatedly(Return(0));
     EXPECT_CALL(*render_delay_controller_mock, GetDelay(_, _, _))
         .Times(kNumBlocks);
-    EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _, _))
+    EXPECT_CALL(*echo_remover_mock, ProcessCapture(_, _, _, _, _, _))
         .Times(kNumBlocks);
     EXPECT_CALL(*echo_remover_mock, UpdateEchoLeakageStatus(_))
         .Times(kNumBlocks);
@@ -230,7 +232,7 @@
       RandomizeSampleVector(&random_generator, render_block[0][0]);
       signal_delay_buffer.Delay(render_block[0][0], capture_block[0][0]);
       block_processor->BufferRender(render_block);
-      block_processor->ProcessCapture(false, false, &capture_block);
+      block_processor->ProcessCapture(false, false, nullptr, &capture_block);
       block_processor->UpdateEchoLeakageStatus(false);
     }
   }
@@ -284,7 +286,7 @@
 TEST(BlockProcessor, NullProcessCaptureParameter) {
   EXPECT_DEATH(std::unique_ptr<BlockProcessor>(
                    BlockProcessor::Create(EchoCanceller3Config(), 16000, 1, 1))
-                   ->ProcessCapture(false, false, nullptr),
+                   ->ProcessCapture(false, false, nullptr, nullptr),
                "");
 }
 
diff --git a/modules/audio_processing/aec3/echo_canceller3.cc b/modules/audio_processing/aec3/echo_canceller3.cc
index 2b50e61..a68ae01 100644
--- a/modules/audio_processing/aec3/echo_canceller3.cc
+++ b/modules/audio_processing/aec3/echo_canceller3.cc
@@ -16,6 +16,7 @@
 #include "modules/audio_processing/high_pass_filter.h"
 #include "modules/audio_processing/logging/apm_data_dumper.h"
 #include "rtc_base/atomic_ops.h"
+#include "rtc_base/logging.h"
 #include "system_wrappers/include/field_trial.h"
 
 namespace webrtc {
@@ -87,28 +88,52 @@
 }
 
 void ProcessCaptureFrameContent(
+    AudioBuffer* linear_output,
     AudioBuffer* capture,
     bool level_change,
     bool saturated_microphone_signal,
     size_t sub_frame_index,
     FrameBlocker* capture_blocker,
+    BlockFramer* linear_output_framer,
     BlockFramer* output_framer,
     BlockProcessor* block_processor,
-    std::vector<std::vector<std::vector<float>>>* block,
-    std::vector<std::vector<rtc::ArrayView<float>>>* sub_frame_view) {
-  FillSubFrameView(capture, sub_frame_index, sub_frame_view);
-  capture_blocker->InsertSubFrameAndExtractBlock(*sub_frame_view, block);
+    std::vector<std::vector<std::vector<float>>>* linear_output_block,
+    std::vector<std::vector<rtc::ArrayView<float>>>*
+        linear_output_sub_frame_view,
+    std::vector<std::vector<std::vector<float>>>* capture_block,
+    std::vector<std::vector<rtc::ArrayView<float>>>* capture_sub_frame_view) {
+  FillSubFrameView(capture, sub_frame_index, capture_sub_frame_view);
+
+  if (linear_output) {
+    RTC_DCHECK(linear_output_framer);
+    RTC_DCHECK(linear_output_block);
+    RTC_DCHECK(linear_output_sub_frame_view);
+    FillSubFrameView(linear_output, sub_frame_index,
+                     linear_output_sub_frame_view);
+  }
+
+  capture_blocker->InsertSubFrameAndExtractBlock(*capture_sub_frame_view,
+                                                 capture_block);
   block_processor->ProcessCapture(level_change, saturated_microphone_signal,
-                                  block);
-  output_framer->InsertBlockAndExtractSubFrame(*block, sub_frame_view);
+                                  linear_output_block, capture_block);
+  output_framer->InsertBlockAndExtractSubFrame(*capture_block,
+                                               capture_sub_frame_view);
+
+  if (linear_output) {
+    RTC_DCHECK(linear_output_framer);
+    linear_output_framer->InsertBlockAndExtractSubFrame(
+        *linear_output_block, linear_output_sub_frame_view);
+  }
 }
 
 void ProcessRemainingCaptureFrameContent(
     bool level_change,
     bool saturated_microphone_signal,
     FrameBlocker* capture_blocker,
+    BlockFramer* linear_output_framer,
     BlockFramer* output_framer,
     BlockProcessor* block_processor,
+    std::vector<std::vector<std::vector<float>>>* linear_output_block,
     std::vector<std::vector<std::vector<float>>>* block) {
   if (!capture_blocker->IsBlockAvailable()) {
     return;
@@ -116,8 +141,13 @@
 
   capture_blocker->ExtractBlock(block);
   block_processor->ProcessCapture(level_change, saturated_microphone_signal,
-                                  block);
+                                  linear_output_block, block);
   output_framer->InsertBlock(*block);
+
+  if (linear_output_framer) {
+    RTC_DCHECK(linear_output_block);
+    linear_output_framer->InsertBlock(*linear_output_block);
+  }
 }
 
 void BufferRenderFrameContent(
@@ -295,12 +325,24 @@
 
   RTC_DCHECK_EQ(num_bands_, std::max(sample_rate_hz_, 16000) / 16000);
   RTC_DCHECK_GE(kMaxNumBands, num_bands_);
+
+  if (config_.filter.export_linear_aec_output) {
+    linear_output_framer_.reset(new BlockFramer(1, num_capture_channels_));
+    linear_output_block_ =
+        std::make_unique<std::vector<std::vector<std::vector<float>>>>(
+            1, std::vector<std::vector<float>>(
+                   num_capture_channels_, std::vector<float>(kBlockSize, 0.f)));
+    linear_output_sub_frame_view_ =
+        std::vector<std::vector<rtc::ArrayView<float>>>(
+            1, std::vector<rtc::ArrayView<float>>(num_capture_channels_));
+  }
 }
 
 EchoCanceller3::~EchoCanceller3() = default;
 
 void EchoCanceller3::AnalyzeRender(const AudioBuffer& render) {
   RTC_DCHECK_RUNS_SERIALIZED(&render_race_checker_);
+
   RTC_DCHECK_EQ(render.num_channels(), num_render_channels_);
   data_dumper_->DumpRaw("aec3_call_order",
                         static_cast<int>(EchoCanceller3ApiCall::kRender));
@@ -312,7 +354,6 @@
   RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_);
   data_dumper_->DumpWav("aec3_capture_analyze_input", capture.num_frames(),
                         capture.channels_const()[0], sample_rate_hz_, 1);
-
   saturated_microphone_signal_ = false;
   for (size_t channel = 0; channel < capture.num_channels(); ++channel) {
     saturated_microphone_signal_ |=
@@ -325,6 +366,12 @@
 }
 
 void EchoCanceller3::ProcessCapture(AudioBuffer* capture, bool level_change) {
+  ProcessCapture(capture, nullptr, level_change);
+}
+
+void EchoCanceller3::ProcessCapture(AudioBuffer* capture,
+                                    AudioBuffer* linear_output,
+                                    bool level_change) {
   RTC_DCHECK_RUNS_SERIALIZED(&capture_race_checker_);
   RTC_DCHECK(capture);
   RTC_DCHECK_EQ(num_bands_, capture->num_bands());
@@ -333,6 +380,12 @@
   data_dumper_->DumpRaw("aec3_call_order",
                         static_cast<int>(EchoCanceller3ApiCall::kCapture));
 
+  if (linear_output && !linear_output_framer_) {
+    RTC_LOG(LS_ERROR) << "Trying to retrieve the linear AEC output without "
+                         "properly configuring AEC3.";
+    RTC_NOTREACHED();
+  }
+
   // Report capture call in the metrics and periodically update API call
   // metrics.
   api_call_metrics_.ReportCaptureCall();
@@ -349,19 +402,24 @@
 
   EmptyRenderQueue();
 
-  ProcessCaptureFrameContent(capture, level_change,
+  ProcessCaptureFrameContent(linear_output, capture, level_change,
                              saturated_microphone_signal_, 0, &capture_blocker_,
-                             &output_framer_, block_processor_.get(),
-                             &capture_block_, &capture_sub_frame_view_);
+                             linear_output_framer_.get(), &output_framer_,
+                             block_processor_.get(), linear_output_block_.get(),
+                             &linear_output_sub_frame_view_, &capture_block_,
+                             &capture_sub_frame_view_);
 
-  ProcessCaptureFrameContent(capture, level_change,
+  ProcessCaptureFrameContent(linear_output, capture, level_change,
                              saturated_microphone_signal_, 1, &capture_blocker_,
-                             &output_framer_, block_processor_.get(),
-                             &capture_block_, &capture_sub_frame_view_);
+                             linear_output_framer_.get(), &output_framer_,
+                             block_processor_.get(), linear_output_block_.get(),
+                             &linear_output_sub_frame_view_, &capture_block_,
+                             &capture_sub_frame_view_);
 
   ProcessRemainingCaptureFrameContent(
       level_change, saturated_microphone_signal_, &capture_blocker_,
-      &output_framer_, block_processor_.get(), &capture_block_);
+      linear_output_framer_.get(), &output_framer_, block_processor_.get(),
+      linear_output_block_.get(), &capture_block_);
 
   data_dumper_->DumpWav("aec3_capture_output", AudioBuffer::kSplitBandSize,
                         &capture->split_bands(0)[0][0], 16000, 1);
diff --git a/modules/audio_processing/aec3/echo_canceller3.h b/modules/audio_processing/aec3/echo_canceller3.h
index ce36cc1..a828d57 100644
--- a/modules/audio_processing/aec3/echo_canceller3.h
+++ b/modules/audio_processing/aec3/echo_canceller3.h
@@ -70,8 +70,6 @@
 // Main class for the echo canceller3.
 // It does 4 things:
 // -Receives 10 ms frames of band-split audio.
-// -Optionally applies an anti-hum (high-pass) filter on the
-// received signals.
 // -Provides the lower level echo canceller functionality with
 // blocks of 64 samples of audio data.
 // -Partially handles the jitter in the render and capture API
@@ -106,6 +104,10 @@
   // Processes the split-band domain capture signal in order to remove any echo
   // present in the signal.
   void ProcessCapture(AudioBuffer* capture, bool level_change) override;
+  // As above, but also returns the linear filter output.
+  void ProcessCapture(AudioBuffer* capture,
+                      AudioBuffer* linear_output,
+                      bool level_change) override;
   // Collect current metrics from the echo canceller.
   Metrics GetMetrics() const override;
   // Provides an optional external estimate of the audio buffer delay.
@@ -149,6 +151,8 @@
   const int num_bands_;
   const size_t num_render_channels_;
   const size_t num_capture_channels_;
+  std::unique_ptr<BlockFramer> linear_output_framer_
+      RTC_GUARDED_BY(capture_race_checker_);
   BlockFramer output_framer_ RTC_GUARDED_BY(capture_race_checker_);
   FrameBlocker capture_blocker_ RTC_GUARDED_BY(capture_race_checker_);
   FrameBlocker render_blocker_ RTC_GUARDED_BY(capture_race_checker_);
@@ -163,10 +167,14 @@
       false;
   std::vector<std::vector<std::vector<float>>> render_block_
       RTC_GUARDED_BY(capture_race_checker_);
+  std::unique_ptr<std::vector<std::vector<std::vector<float>>>>
+      linear_output_block_ RTC_GUARDED_BY(capture_race_checker_);
   std::vector<std::vector<std::vector<float>>> capture_block_
       RTC_GUARDED_BY(capture_race_checker_);
   std::vector<std::vector<rtc::ArrayView<float>>> render_sub_frame_view_
       RTC_GUARDED_BY(capture_race_checker_);
+  std::vector<std::vector<rtc::ArrayView<float>>> linear_output_sub_frame_view_
+      RTC_GUARDED_BY(capture_race_checker_);
   std::vector<std::vector<rtc::ArrayView<float>>> capture_sub_frame_view_
       RTC_GUARDED_BY(capture_race_checker_);
   BlockDelayBuffer block_delay_buffer_ RTC_GUARDED_BY(capture_race_checker_);
diff --git a/modules/audio_processing/aec3/echo_canceller3_unittest.cc b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
index e7b9ddb..8d9199c 100644
--- a/modules/audio_processing/aec3/echo_canceller3_unittest.cc
+++ b/modules/audio_processing/aec3/echo_canceller3_unittest.cc
@@ -112,6 +112,7 @@
   void ProcessCapture(
       bool level_change,
       bool saturated_microphone_signal,
+      std::vector<std::vector<std::vector<float>>>* linear_output,
       std::vector<std::vector<std::vector<float>>>* capture_block) override {}
 
   void BufferRender(
@@ -137,6 +138,7 @@
   void ProcessCapture(
       bool level_change,
       bool saturated_microphone_signal,
+      std::vector<std::vector<std::vector<float>>>* linear_output,
       std::vector<std::vector<std::vector<float>>>* capture_block) override {
     std::vector<std::vector<std::vector<float>>> render_block =
         received_render_blocks_.front();
@@ -267,17 +269,17 @@
 
     switch (echo_path_change_test_variant) {
       case EchoPathChangeTestVariant::kNone:
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _, _))
             .Times(kExpectedNumBlocksToProcess);
         break;
       case EchoPathChangeTestVariant::kOneSticky:
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _, _))
             .Times(kExpectedNumBlocksToProcess);
         break;
       case EchoPathChangeTestVariant::kOneNonSticky:
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(true, _, _, _))
             .Times(kNumFullBlocksPerFrame);
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(false, _, _, _))
             .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
         break;
     }
@@ -338,7 +340,7 @@
             new StrictMock<webrtc::test::MockBlockProcessor>());
     EXPECT_CALL(*block_processor_mock, BufferRender(_))
         .Times(kExpectedNumBlocksToProcess);
-    EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _))
+    EXPECT_CALL(*block_processor_mock, ProcessCapture(_, _, _, _))
         .Times(kExpectedNumBlocksToProcess);
 
     switch (leakage_report_variant) {
@@ -429,21 +431,21 @@
 
     switch (saturation_variant) {
       case SaturationTestVariant::kNone:
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _))
             .Times(kExpectedNumBlocksToProcess);
         break;
       case SaturationTestVariant::kOneNegative: {
         ::testing::InSequence s;
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _, _))
             .Times(kNumFullBlocksPerFrame);
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _))
             .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
       } break;
       case SaturationTestVariant::kOnePositive: {
         ::testing::InSequence s;
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, true, _, _))
             .Times(kNumFullBlocksPerFrame);
-        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _))
+        EXPECT_CALL(*block_processor_mock, ProcessCapture(_, false, _, _))
             .Times(kExpectedNumBlocksToProcess - kNumFullBlocksPerFrame);
       } break;
     }
diff --git a/modules/audio_processing/aec3/echo_remover.cc b/modules/audio_processing/aec3/echo_remover.cc
index bf68f36..89ba736 100644
--- a/modules/audio_processing/aec3/echo_remover.cc
+++ b/modules/audio_processing/aec3/echo_remover.cc
@@ -123,6 +123,7 @@
       bool capture_signal_saturation,
       const absl::optional<DelayEstimate>& external_delay,
       RenderBuffer* render_buffer,
+      std::vector<std::vector<std::vector<float>>>* linear_output,
       std::vector<std::vector<std::vector<float>>>* capture) override;
 
   // Updates the status on whether echo leakage is detected in the output of the
@@ -235,6 +236,7 @@
     bool capture_signal_saturation,
     const absl::optional<DelayEstimate>& external_delay,
     RenderBuffer* render_buffer,
+    std::vector<std::vector<std::vector<float>>>* linear_output,
     std::vector<std::vector<std::vector<float>>>* capture) {
   ++block_counter_;
   const std::vector<std::vector<std::vector<float>>>& x =
@@ -367,6 +369,16 @@
     E[ch].Spectrum(optimization_, E2[ch]);
   }
 
+  // Optionally return the linear filter output.
+  if (linear_output) {
+    RTC_DCHECK_GE(1, linear_output->size());
+    RTC_DCHECK_EQ(num_capture_channels_, linear_output[0].size());
+    for (size_t ch = 0; ch < num_capture_channels_; ++ch) {
+      RTC_DCHECK_EQ(kBlockSize, (*linear_output)[0][ch].size());
+      std::copy(e[ch].begin(), e[ch].end(), (*linear_output)[0][ch].begin());
+    }
+  }
+
   // Update the AEC state information.
   aec_state_.Update(external_delay, subtractor_.FilterFrequencyResponses(),
                     subtractor_.FilterImpulseResponses(), *render_buffer, E2,
diff --git a/modules/audio_processing/aec3/echo_remover.h b/modules/audio_processing/aec3/echo_remover.h
index 6098a68..ef41646 100644
--- a/modules/audio_processing/aec3/echo_remover.h
+++ b/modules/audio_processing/aec3/echo_remover.h
@@ -42,6 +42,7 @@
       bool capture_signal_saturation,
       const absl::optional<DelayEstimate>& external_delay,
       RenderBuffer* render_buffer,
+      std::vector<std::vector<std::vector<float>>>* linear_output,
       std::vector<std::vector<std::vector<float>>>* capture) = 0;
 
   // Updates the status on whether echo leakage is detected in the output of the
diff --git a/modules/audio_processing/aec3/echo_remover_unittest.cc b/modules/audio_processing/aec3/echo_remover_unittest.cc
index 15d0913..d79993a 100644
--- a/modules/audio_processing/aec3/echo_remover_unittest.cc
+++ b/modules/audio_processing/aec3/echo_remover_unittest.cc
@@ -73,9 +73,9 @@
           render_buffer->Insert(render);
           render_buffer->PrepareCaptureProcessing();
 
-          remover->ProcessCapture(echo_path_variability,
-                                  k % 2 == 0 ? true : false, delay_estimate,
-                                  render_buffer->GetRenderBuffer(), &capture);
+          remover->ProcessCapture(
+              echo_path_variability, k % 2 == 0 ? true : false, delay_estimate,
+              render_buffer->GetRenderBuffer(), nullptr, &capture);
         }
       }
     }
@@ -107,10 +107,10 @@
                                    1, std::vector<float>(kBlockSize - 1, 0.f)));
     EchoPathVariability echo_path_variability(
         false, EchoPathVariability::DelayAdjustment::kNone, false);
-    EXPECT_DEATH(
-        remover->ProcessCapture(echo_path_variability, false, delay_estimate,
-                                render_buffer->GetRenderBuffer(), &capture),
-        "");
+    EXPECT_DEATH(remover->ProcessCapture(
+                     echo_path_variability, false, delay_estimate,
+                     render_buffer->GetRenderBuffer(), nullptr, &capture),
+                 "");
   }
 }
 
@@ -131,10 +131,10 @@
                                         std::vector<float>(kBlockSize, 0.f)));
     EchoPathVariability echo_path_variability(
         false, EchoPathVariability::DelayAdjustment::kNone, false);
-    EXPECT_DEATH(
-        remover->ProcessCapture(echo_path_variability, false, delay_estimate,
-                                render_buffer->GetRenderBuffer(), &capture),
-        "");
+    EXPECT_DEATH(remover->ProcessCapture(
+                     echo_path_variability, false, delay_estimate,
+                     render_buffer->GetRenderBuffer(), nullptr, &capture),
+                 "");
   }
 }
 
@@ -147,10 +147,10 @@
       RenderDelayBuffer::Create(EchoCanceller3Config(), 16000, 1));
   EchoPathVariability echo_path_variability(
       false, EchoPathVariability::DelayAdjustment::kNone, false);
-  EXPECT_DEATH(
-      remover->ProcessCapture(echo_path_variability, false, delay_estimate,
-                              render_buffer->GetRenderBuffer(), nullptr),
-      "");
+  EXPECT_DEATH(remover->ProcessCapture(
+                   echo_path_variability, false, delay_estimate,
+                   render_buffer->GetRenderBuffer(), nullptr, nullptr),
+               "");
 }
 
 #endif
@@ -222,7 +222,8 @@
           render_buffer->PrepareCaptureProcessing();
 
           remover->ProcessCapture(echo_path_variability, false, delay_estimate,
-                                  render_buffer->GetRenderBuffer(), &y);
+                                  render_buffer->GetRenderBuffer(), nullptr,
+                                  &y);
 
           if (k > kNumBlocksToProcess / 2) {
             output_energy = std::inner_product(y[0][0].begin(), y[0][0].end(),
diff --git a/modules/audio_processing/aec3/mock/mock_block_processor.h b/modules/audio_processing/aec3/mock/mock_block_processor.h
index 634d26e..e9a95c8 100644
--- a/modules/audio_processing/aec3/mock/mock_block_processor.h
+++ b/modules/audio_processing/aec3/mock/mock_block_processor.h
@@ -24,10 +24,11 @@
   MockBlockProcessor();
   virtual ~MockBlockProcessor();
 
-  MOCK_METHOD3(
+  MOCK_METHOD4(
       ProcessCapture,
       void(bool level_change,
            bool saturated_microphone_signal,
+           std::vector<std::vector<std::vector<float>>>* linear_output,
            std::vector<std::vector<std::vector<float>>>* capture_block));
   MOCK_METHOD1(BufferRender,
                void(const std::vector<std::vector<std::vector<float>>>& block));
diff --git a/modules/audio_processing/aec3/mock/mock_echo_remover.h b/modules/audio_processing/aec3/mock/mock_echo_remover.h
index f8dd348..6c580f3 100644
--- a/modules/audio_processing/aec3/mock/mock_echo_remover.h
+++ b/modules/audio_processing/aec3/mock/mock_echo_remover.h
@@ -27,11 +27,12 @@
   MockEchoRemover();
   virtual ~MockEchoRemover();
 
-  MOCK_METHOD5(ProcessCapture,
+  MOCK_METHOD6(ProcessCapture,
                void(EchoPathVariability echo_path_variability,
                     bool capture_signal_saturation,
                     const absl::optional<DelayEstimate>& delay_estimate,
                     RenderBuffer* render_buffer,
+                    std::vector<std::vector<std::vector<float>>>* linear_output,
                     std::vector<std::vector<std::vector<float>>>* capture));
   MOCK_CONST_METHOD0(Delay, absl::optional<int>());
   MOCK_METHOD1(UpdateEchoLeakageStatus, void(bool leakage_detected));
diff --git a/modules/audio_processing/aec3/suppression_gain.cc b/modules/audio_processing/aec3/suppression_gain.cc
index d1ef326..bd7a3d6 100644
--- a/modules/audio_processing/aec3/suppression_gain.cc
+++ b/modules/audio_processing/aec3/suppression_gain.cc
@@ -343,13 +343,6 @@
     std::array<float, kFftLengthBy2Plus1>* low_band_gain) {
   RTC_DCHECK(high_bands_gain);
   RTC_DCHECK(low_band_gain);
-  const auto& cfg = config_.suppressor;
-
-  if (cfg.enforce_transparent) {
-    low_band_gain->fill(1.f);
-    *high_bands_gain = cfg.enforce_empty_higher_bands ? 0.f : 1.f;
-    return;
-  }
 
   // Update the nearend state selection.
   dominant_nearend_detector_.Update(nearend_spectrum, residual_echo_spectrum,
@@ -360,11 +353,6 @@
   LowerBandGain(low_noise_render, aec_state, nearend_spectrum,
                 residual_echo_spectrum, comfort_noise_spectrum, low_band_gain);
 
-  if (cfg.enforce_empty_higher_bands) {
-    *high_bands_gain = 0.f;
-    return;
-  }
-
   // Compute the gain for the upper bands.
   const absl::optional<int> narrow_peak_band =
       render_signal_analyzer.NarrowPeakBand();
diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc
index 59d0c32..fad02a0 100644
--- a/modules/audio_processing/audio_processing_impl.cc
+++ b/modules/audio_processing/audio_processing_impl.cc
@@ -1342,8 +1342,9 @@
         submodules_.echo_controller->SetAudioBufferDelay(stream_delay_ms());
       }
 
+      AudioBuffer* linear_aec_buffer = capture_.linear_aec_output.get();
       submodules_.echo_controller->ProcessCapture(
-          capture_buffer, capture_.echo_path_gain_change);
+          capture_buffer, linear_aec_buffer, capture_.echo_path_gain_change);
     } else if (submodules_.echo_cancellation) {
       // Ensure that the stream delay was set before the call to the
       // AEC ProcessCaptureAudio function.
@@ -1625,6 +1626,31 @@
   return retval;
 }
 
+bool AudioProcessingImpl::GetLinearAecOutput(
+    rtc::ArrayView<std::array<float, 160>> linear_output) const {
+  rtc::CritScope cs(&crit_capture_);
+  AudioBuffer* linear_aec_buffer = capture_.linear_aec_output.get();
+
+  RTC_DCHECK(linear_aec_buffer);
+  if (linear_aec_buffer) {
+    RTC_DCHECK_EQ(1, linear_aec_buffer->num_bands());
+    RTC_DCHECK_EQ(linear_output.size(), linear_aec_buffer->num_channels());
+
+    for (size_t ch = 0; ch < linear_aec_buffer->num_channels(); ++ch) {
+      RTC_DCHECK_EQ(linear_output[ch].size(), linear_aec_buffer->num_frames());
+      rtc::ArrayView<const float> channel_view =
+          rtc::ArrayView<const float>(linear_aec_buffer->channels_const()[ch],
+                                      linear_aec_buffer->num_frames());
+      std::copy(channel_view.begin(), channel_view.end(),
+                linear_output[ch].begin());
+    }
+    return true;
+  }
+  RTC_LOG(LS_ERROR) << "No linear AEC output available";
+  RTC_NOTREACHED();
+  return false;
+}
+
 int AudioProcessingImpl::stream_delay_ms() const {
   // Used as callback from submodules, hence locking is not allowed.
   return capture_nonlocked_.stream_delay_ms;
@@ -1790,6 +1816,16 @@
           num_proc_channels());
     }
 
+    // Setup the storage for returning the linear AEC output.
+    if (config_.echo_canceller.export_linear_aec_output) {
+      constexpr int kLinearOutputRateHz = 16000;
+      capture_.linear_aec_output = std::make_unique<AudioBuffer>(
+          kLinearOutputRateHz, num_proc_channels(), kLinearOutputRateHz,
+          num_proc_channels(), kLinearOutputRateHz, num_proc_channels());
+    } else {
+      capture_.linear_aec_output.reset();
+    }
+
     capture_nonlocked_.echo_controller_enabled = true;
 
     submodules_.echo_cancellation.reset();
@@ -1801,6 +1837,7 @@
 
   submodules_.echo_controller.reset();
   capture_nonlocked_.echo_controller_enabled = false;
+  capture_.linear_aec_output.reset();
 
   if (!config_.echo_canceller.enabled) {
     submodules_.echo_cancellation.reset();
diff --git a/modules/audio_processing/audio_processing_impl.h b/modules/audio_processing/audio_processing_impl.h
index e13034f..f7320ac 100644
--- a/modules/audio_processing/audio_processing_impl.h
+++ b/modules/audio_processing/audio_processing_impl.h
@@ -87,6 +87,8 @@
                     const StreamConfig& input_config,
                     const StreamConfig& output_config,
                     float* const* dest) override;
+  bool GetLinearAecOutput(
+      rtc::ArrayView<std::array<float, 160>> linear_output) const override;
   void set_output_will_be_muted(bool muted) override;
   int set_stream_delay_ms(int delay) override;
   void set_delay_offset_ms(int offset) override;
@@ -412,6 +414,7 @@
     bool transient_suppressor_enabled;
     std::unique_ptr<AudioBuffer> capture_audio;
     std::unique_ptr<AudioBuffer> capture_fullband_audio;
+    std::unique_ptr<AudioBuffer> linear_aec_output;
     // Only the rate and samples fields of capture_processing_format_ are used
     // because the capture processing number of channels is mutable and is
     // tracked by the capture_audio_.
diff --git a/modules/audio_processing/audio_processing_impl_unittest.cc b/modules/audio_processing/audio_processing_impl_unittest.cc
index 5707f47..c7e25a9 100644
--- a/modules/audio_processing/audio_processing_impl_unittest.cc
+++ b/modules/audio_processing/audio_processing_impl_unittest.cc
@@ -242,13 +242,13 @@
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
   EXPECT_CALL(*echo_control_mock,
-              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+              ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false))
       .Times(1);
   apm->ProcessStream(&frame);
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
   EXPECT_CALL(*echo_control_mock,
-              ProcessCapture(NotNull(), /*echo_path_change=*/true))
+              ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true))
       .Times(1);
   apm->SetRuntimeSetting(
       AudioProcessing::RuntimeSetting::CreateCapturePreGain(2.f));
@@ -286,7 +286,8 @@
 
   const int initial_analog_gain = apm->recommended_stream_analog_level();
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
-  EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), false)).Times(1);
+  EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, false))
+      .Times(1);
   apm->ProcessStream(&frame);
 
   // Force an analog gain change if it did not happen.
@@ -295,7 +296,8 @@
   }
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
-  EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), true)).Times(1);
+  EXPECT_CALL(*echo_control_mock, ProcessCapture(NotNull(), testing::_, true))
+      .Times(1);
   apm->ProcessStream(&frame);
 }
 
@@ -326,13 +328,13 @@
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
   EXPECT_CALL(*echo_control_mock,
-              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+              ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false))
       .Times(1);
   apm->ProcessStream(&frame);
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
   EXPECT_CALL(*echo_control_mock,
-              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+              ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false))
       .Times(1);
   apm->SetRuntimeSetting(
       AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50));
@@ -340,7 +342,7 @@
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
   EXPECT_CALL(*echo_control_mock,
-              ProcessCapture(NotNull(), /*echo_path_change=*/false))
+              ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/false))
       .Times(1);
   apm->SetRuntimeSetting(
       AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(50));
@@ -348,7 +350,7 @@
 
   EXPECT_CALL(*echo_control_mock, AnalyzeCapture(testing::_)).Times(1);
   EXPECT_CALL(*echo_control_mock,
-              ProcessCapture(NotNull(), /*echo_path_change=*/true))
+              ProcessCapture(NotNull(), testing::_, /*echo_path_change=*/true))
       .Times(1);
   apm->SetRuntimeSetting(
       AudioProcessing::RuntimeSetting::CreatePlayoutVolumeChange(100));
diff --git a/modules/audio_processing/audio_processing_unittest.cc b/modules/audio_processing/audio_processing_unittest.cc
index 06dbba7..0e17db7 100644
--- a/modules/audio_processing/audio_processing_unittest.cc
+++ b/modules/audio_processing/audio_processing_unittest.cc
@@ -2425,7 +2425,8 @@
     auto ec = new test::MockEchoControl();
     EXPECT_CALL(*ec, AnalyzeRender(::testing::_)).Times(1);
     EXPECT_CALL(*ec, AnalyzeCapture(::testing::_)).Times(2);
-    EXPECT_CALL(*ec, ProcessCapture(::testing::_, ::testing::_)).Times(2);
+    EXPECT_CALL(*ec, ProcessCapture(::testing::_, ::testing::_, ::testing::_))
+        .Times(2);
     return std::unique_ptr<EchoControl>(ec);
   }
 
diff --git a/modules/audio_processing/include/audio_processing.h b/modules/audio_processing/include/audio_processing.h
index f1242a7..113bd2a 100644
--- a/modules/audio_processing/include/audio_processing.h
+++ b/modules/audio_processing/include/audio_processing.h
@@ -280,6 +280,7 @@
       bool legacy_moderate_suppression_level = false;
       // Recommended not to use. Will be removed in the future.
       bool use_legacy_aec = false;
+      bool export_linear_aec_output = false;
     } echo_canceller;
 
     // Enables background noise suppression.
@@ -611,6 +612,13 @@
   virtual int AnalyzeReverseStream(const float* const* data,
                                    const StreamConfig& reverse_config) = 0;
 
+  // Returns the most recently produced 10 ms of the linear AEC output at a rate
+  // of 16 kHz. If there is more than one capture channel, a mono representation
+  // of the input is returned. Returns true/false to indicate whether an output
+  // returned.
+  virtual bool GetLinearAecOutput(
+      rtc::ArrayView<std::array<float, 160>> linear_output) const = 0;
+
   // This must be called prior to ProcessStream() if and only if adaptive analog
   // gain control is enabled, to pass the current analog level from the audio
   // HAL. Must be within the range provided in Config::GainController1.
diff --git a/modules/audio_processing/include/mock_audio_processing.h b/modules/audio_processing/include/mock_audio_processing.h
index 6b12392..0932696 100644
--- a/modules/audio_processing/include/mock_audio_processing.h
+++ b/modules/audio_processing/include/mock_audio_processing.h
@@ -47,6 +47,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, Metrics());
   MOCK_METHOD1(SetAudioBufferDelay, void(int delay_ms));
   MOCK_CONST_METHOD0(ActiveProcessing, bool());
@@ -105,6 +109,9 @@
                    const StreamConfig& input_config,
                    const StreamConfig& output_config,
                    float* const* dest));
+  MOCK_CONST_METHOD1(
+      GetLinearAecOutput,
+      bool(rtc::ArrayView<std::array<float, 160>> linear_output));
   MOCK_METHOD1(set_stream_delay_ms, int(int delay));
   MOCK_CONST_METHOD0(stream_delay_ms, int());
   MOCK_CONST_METHOD0(was_stream_delay_set, bool());
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());