audioproc_f: input AEC dump as string, output audio to vector

This CL adds the following options:

pass an input AEC dump as a string (currently, the tool can only accept a path to an AEC dump file)
write the processed capture samples to a given vector

Bug: webrtc:10808
Change-Id: I02863c97ec3cd8c03ade2ea8521836f2e7417050
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/145208
Commit-Queue: Sonia-Florina Horchidan <soniahorchidan@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Reviewed-by: Per Ã…hgren <peah@webrtc.org>
Reviewed-by: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Ivo Creusen <ivoc@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28826}
diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn
index b03b40d..eaa8826 100644
--- a/modules/audio_processing/BUILD.gn
+++ b/modules/audio_processing/BUILD.gn
@@ -755,6 +755,7 @@
         "../../rtc_base:protobuf_utils",
         "../../rtc_base:rtc_base_approved",
         "../../rtc_base/system:arch",
+        "//third_party/abseil-cpp/absl/memory:memory",
       ]
     }
 
diff --git a/modules/audio_processing/test/aec_dump_based_simulator.cc b/modules/audio_processing/test/aec_dump_based_simulator.cc
index 00fd25e..0d6bdd1 100644
--- a/modules/audio_processing/test/aec_dump_based_simulator.cc
+++ b/modules/audio_processing/test/aec_dump_based_simulator.cc
@@ -212,8 +212,6 @@
 
 void AecDumpBasedSimulator::Process() {
   CreateAudioProcessor();
-  dump_input_file_ = OpenFile(settings_.aec_dump_input_filename->c_str(), "rb");
-
   if (settings_.artificial_nearend_filename) {
     std::unique_ptr<WavReader> artificial_nearend_file(
         new WavReader(settings_.artificial_nearend_filename->c_str()));
@@ -231,39 +229,52 @@
 
   webrtc::audioproc::Event event_msg;
   int num_forward_chunks_processed = 0;
-  while (ReadMessageFromFile(dump_input_file_, &event_msg)) {
-    switch (event_msg.type()) {
-      case webrtc::audioproc::Event::INIT:
-        RTC_CHECK(event_msg.has_init());
-        HandleMessage(event_msg.init());
-        break;
-      case webrtc::audioproc::Event::STREAM:
-        RTC_CHECK(event_msg.has_stream());
-        HandleMessage(event_msg.stream());
-        ++num_forward_chunks_processed;
-        break;
-      case webrtc::audioproc::Event::REVERSE_STREAM:
-        RTC_CHECK(event_msg.has_reverse_stream());
-        HandleMessage(event_msg.reverse_stream());
-        break;
-      case webrtc::audioproc::Event::CONFIG:
-        RTC_CHECK(event_msg.has_config());
-        HandleMessage(event_msg.config());
-        break;
-      case webrtc::audioproc::Event::RUNTIME_SETTING:
-        HandleMessage(event_msg.runtime_setting());
-        break;
-      case webrtc::audioproc::Event::UNKNOWN_EVENT:
-        RTC_CHECK(false);
-        break;
-    }
+  if (settings_.aec_dump_input_string.has_value()) {
+    std::stringstream input;
+    input << settings_.aec_dump_input_string.value();
+    while (ReadMessageFromString(&input, &event_msg))
+      HandleEvent(event_msg, &num_forward_chunks_processed);
+  } else {
+    dump_input_file_ =
+        OpenFile(settings_.aec_dump_input_filename->c_str(), "rb");
+    while (ReadMessageFromFile(dump_input_file_, &event_msg))
+      HandleEvent(event_msg, &num_forward_chunks_processed);
+    fclose(dump_input_file_);
   }
 
-  fclose(dump_input_file_);
-
   DestroyAudioProcessor();
 }
 
+void AecDumpBasedSimulator::HandleEvent(
+    const webrtc::audioproc::Event& event_msg,
+    int* num_forward_chunks_processed) {
+  switch (event_msg.type()) {
+    case webrtc::audioproc::Event::INIT:
+      RTC_CHECK(event_msg.has_init());
+      HandleMessage(event_msg.init());
+      break;
+    case webrtc::audioproc::Event::STREAM:
+      RTC_CHECK(event_msg.has_stream());
+      HandleMessage(event_msg.stream());
+      ++num_forward_chunks_processed;
+      break;
+    case webrtc::audioproc::Event::REVERSE_STREAM:
+      RTC_CHECK(event_msg.has_reverse_stream());
+      HandleMessage(event_msg.reverse_stream());
+      break;
+    case webrtc::audioproc::Event::CONFIG:
+      RTC_CHECK(event_msg.has_config());
+      HandleMessage(event_msg.config());
+      break;
+    case webrtc::audioproc::Event::RUNTIME_SETTING:
+      HandleMessage(event_msg.runtime_setting());
+      break;
+    case webrtc::audioproc::Event::UNKNOWN_EVENT:
+      RTC_CHECK(false);
+      break;
+  }
+}
+
 void AecDumpBasedSimulator::HandleMessage(
     const webrtc::audioproc::Config& msg) {
   if (settings_.use_verbose_logging) {
diff --git a/modules/audio_processing/test/aec_dump_based_simulator.h b/modules/audio_processing/test/aec_dump_based_simulator.h
index 1181979..ef032d0 100644
--- a/modules/audio_processing/test/aec_dump_based_simulator.h
+++ b/modules/audio_processing/test/aec_dump_based_simulator.h
@@ -40,6 +40,8 @@
   void Process() override;
 
  private:
+  void HandleEvent(const webrtc::audioproc::Event& event_msg,
+                   int* num_forward_chunks_processed);
   void HandleMessage(const webrtc::audioproc::Init& msg);
   void HandleMessage(const webrtc::audioproc::Stream& msg);
   void HandleMessage(const webrtc::audioproc::ReverseStream& msg);
diff --git a/modules/audio_processing/test/audio_processing_simulator.cc b/modules/audio_processing/test/audio_processing_simulator.cc
index a212125..65a52d5 100644
--- a/modules/audio_processing/test/audio_processing_simulator.cc
+++ b/modules/audio_processing/test/audio_processing_simulator.cc
@@ -222,9 +222,12 @@
   if (settings_.simulate_mic_gain) {
     fake_recording_device_.SetMicLevel(analog_mic_level_);
   }
-
-  if (buffer_writer_) {
-    buffer_writer_->Write(*out_buf_);
+  if (buffer_memory_writer_) {
+    RTC_CHECK(!buffer_file_writer_);
+    buffer_memory_writer_->Write(*out_buf_);
+  } else if (buffer_file_writer_) {
+    RTC_CHECK(!buffer_memory_writer_);
+    buffer_file_writer_->Write(*out_buf_);
   }
 
   if (residual_echo_likelihood_graph_writer_.is_open()) {
@@ -254,8 +257,8 @@
                      reverse_out_config_, reverse_out_buf_->channels()));
   }
 
-  if (reverse_buffer_writer_) {
-    reverse_buffer_writer_->Write(*reverse_out_buf_);
+  if (reverse_buffer_file_writer_) {
+    reverse_buffer_file_writer_->Write(*reverse_out_buf_);
   }
 
   ++num_reverse_process_stream_calls_;
@@ -336,7 +339,10 @@
     std::unique_ptr<WavWriter> out_file(
         new WavWriter(filename, out_config_.sample_rate_hz(),
                       static_cast<size_t>(out_config_.num_channels())));
-    buffer_writer_.reset(new ChannelBufferWavWriter(std::move(out_file)));
+    buffer_file_writer_.reset(new ChannelBufferWavWriter(std::move(out_file)));
+  } else if (settings_.aec_dump_input_string.has_value()) {
+    buffer_memory_writer_ = absl::make_unique<ChannelBufferVectorWriter>(
+        settings_.processed_capture_samples);
   }
 
   if (settings_.reverse_output_filename) {
@@ -351,7 +357,7 @@
     std::unique_ptr<WavWriter> reverse_out_file(
         new WavWriter(filename, reverse_out_config_.sample_rate_hz(),
                       static_cast<size_t>(reverse_out_config_.num_channels())));
-    reverse_buffer_writer_.reset(
+    reverse_buffer_file_writer_.reset(
         new ChannelBufferWavWriter(std::move(reverse_out_file)));
   }
 
diff --git a/modules/audio_processing/test/audio_processing_simulator.h b/modules/audio_processing/test/audio_processing_simulator.h
index 7800afa..6f84813 100644
--- a/modules/audio_processing/test/audio_processing_simulator.h
+++ b/modules/audio_processing/test/audio_processing_simulator.h
@@ -101,6 +101,8 @@
   absl::optional<std::string> call_order_input_filename;
   absl::optional<std::string> call_order_output_filename;
   absl::optional<std::string> aec_settings_filename;
+  absl::optional<absl::string_view> aec_dump_input_string;
+  std::vector<float>* processed_capture_samples = nullptr;
 };
 
 // Copies samples present in a ChannelBuffer into an AudioFrame.
@@ -172,8 +174,9 @@
 
   size_t num_process_stream_calls_ = 0;
   size_t num_reverse_process_stream_calls_ = 0;
-  std::unique_ptr<ChannelBufferWavWriter> buffer_writer_;
-  std::unique_ptr<ChannelBufferWavWriter> reverse_buffer_writer_;
+  std::unique_ptr<ChannelBufferWavWriter> buffer_file_writer_;
+  std::unique_ptr<ChannelBufferWavWriter> reverse_buffer_file_writer_;
+  std::unique_ptr<ChannelBufferVectorWriter> buffer_memory_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 3764282..41d137b 100644
--- a/modules/audio_processing/test/audioproc_float_impl.cc
+++ b/modules/audio_processing/test/audioproc_float_impl.cc
@@ -462,9 +462,15 @@
 
 void PerformBasicParameterSanityChecks(const SimulationSettings& settings) {
   if (settings.input_filename || settings.reverse_input_filename) {
-    ReportConditionalErrorAndExit(!!settings.aec_dump_input_filename,
-                                  "Error: The aec dump cannot be specified "
-                                  "together with input wav files!\n");
+    ReportConditionalErrorAndExit(
+        !!settings.aec_dump_input_filename,
+        "Error: The aec dump file cannot be specified "
+        "together with input wav files!\n");
+
+    ReportConditionalErrorAndExit(
+        !!settings.aec_dump_input_string,
+        "Error: The aec dump input string cannot be specified "
+        "together with input wav files!\n");
 
     ReportConditionalErrorAndExit(!!settings.artificial_nearend_filename,
                                   "Error: The artificial nearend cannot be "
@@ -480,9 +486,14 @@
         "Error: When operating at wav files, the reverse input wav filename "
         "must be specified if the reverse output wav filename is specified!\n");
   } else {
-    ReportConditionalErrorAndExit(!settings.aec_dump_input_filename,
-                                  "Error: Either the aec dump or the wav "
-                                  "input files must be specified!\n");
+    ReportConditionalErrorAndExit(
+        !settings.aec_dump_input_filename && !settings.aec_dump_input_string,
+        "Error: Either the aec dump input file, the wav "
+        "input file or the aec dump input string must be specified!\n");
+    ReportConditionalErrorAndExit(
+        settings.aec_dump_input_filename && settings.aec_dump_input_string,
+        "Error: The aec dump input file cannot be specified together with the "
+        "aec dump input string!\n");
   }
 
   ReportConditionalErrorAndExit(
@@ -624,7 +635,9 @@
 
 int AudioprocFloatImpl(std::unique_ptr<AudioProcessingBuilder> ap_builder,
                        int argc,
-                       char* argv[]) {
+                       char* argv[],
+                       absl::string_view input_aecdump,
+                       std::vector<float>* processed_capture_samples) {
   std::vector<char*> args = absl::ParseCommandLine(argc, argv);
   if (args.size() != 1) {
     printf("%s", kUsageDescription);
@@ -632,10 +645,15 @@
   }
 
   SimulationSettings settings = CreateSettings();
+  if (!input_aecdump.empty()) {
+    settings.aec_dump_input_string = input_aecdump;
+    settings.processed_capture_samples = processed_capture_samples;
+    RTC_CHECK(settings.processed_capture_samples);
+  }
   PerformBasicParameterSanityChecks(settings);
   std::unique_ptr<AudioProcessingSimulator> processor;
 
-  if (settings.aec_dump_input_filename) {
+  if (settings.aec_dump_input_filename || settings.aec_dump_input_string) {
     processor.reset(new AecDumpBasedSimulator(settings, std::move(ap_builder)));
   } else {
     processor.reset(new WavBasedSimulator(settings, std::move(ap_builder)));
diff --git a/modules/audio_processing/test/audioproc_float_impl.h b/modules/audio_processing/test/audioproc_float_impl.h
index 063ecb4..9a9013c 100644
--- a/modules/audio_processing/test/audioproc_float_impl.h
+++ b/modules/audio_processing/test/audioproc_float_impl.h
@@ -18,10 +18,17 @@
 namespace webrtc {
 namespace test {
 
-// This function implements the audio processing simulation utility.
+// This function implements the audio processing simulation utility. Pass
+// |input_aecdump| to provide the content of an AEC dump file as a string; if
+// |input_aecdump| is not passed, a WAV or AEC input dump file must be specified
+// via the |argv| argument. Pass |processed_capture_samples| to write in it the
+// samples processed on the capture side; if |processed_capture_samples| is not
+// passed, the output file can optionally be specified via the |argv| argument.
 int AudioprocFloatImpl(std::unique_ptr<AudioProcessingBuilder> ap_builder,
                        int argc,
-                       char* argv[]);
+                       char* argv[],
+                       absl::string_view input_aecdump,
+                       std::vector<float>* processed_capture_samples);
 
 }  // namespace test
 }  // namespace webrtc
diff --git a/modules/audio_processing/test/protobuf_utils.cc b/modules/audio_processing/test/protobuf_utils.cc
index f3c97ee..3042bce 100644
--- a/modules/audio_processing/test/protobuf_utils.cc
+++ b/modules/audio_processing/test/protobuf_utils.cc
@@ -10,8 +10,31 @@
 
 #include "modules/audio_processing/test/protobuf_utils.h"
 
+#include "absl/memory/memory.h"
 #include "rtc_base/system/arch.h"
 
+namespace {
+// Allocates new memory in the memory owned by the unique_ptr to fit the raw
+// message and returns the number of bytes read when having a string stream as
+// input.
+size_t ReadMessageBytesFromString(std::stringstream* input,
+                                  std::unique_ptr<uint8_t[]>* bytes) {
+  int32_t size = 0;
+  input->read(reinterpret_cast<char*>(&size), sizeof(int32_t));
+  int32_t size_read = input->gcount();
+  if (size_read != sizeof(int32_t))
+    return 0;
+  if (size <= 0)
+    return 0;
+
+  *bytes = absl::make_unique<uint8_t[]>(size);
+  input->read(reinterpret_cast<char*>(bytes->get()),
+              size * sizeof((*bytes)[0]));
+  size_read = input->gcount();
+  return size_read == size ? size : 0;
+}
+}  // namespace
+
 namespace webrtc {
 
 size_t ReadMessageBytesFromFile(FILE* file, std::unique_ptr<uint8_t[]>* bytes) {
@@ -26,7 +49,7 @@
   if (size <= 0)
     return 0;
 
-  bytes->reset(new uint8_t[size]);
+  *bytes = absl::make_unique<uint8_t[]>(size);
   return fread(bytes->get(), sizeof((*bytes)[0]), size, file);
 }
 
@@ -41,4 +64,15 @@
   return msg->ParseFromArray(bytes.get(), size);
 }
 
+// Returns true on success, false on error or end of string stream.
+bool ReadMessageFromString(std::stringstream* input, MessageLite* msg) {
+  std::unique_ptr<uint8_t[]> bytes;
+  size_t size = ReadMessageBytesFromString(input, &bytes);
+  if (!size)
+    return false;
+
+  msg->Clear();
+  return msg->ParseFromArray(bytes.get(), size);
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_processing/test/protobuf_utils.h b/modules/audio_processing/test/protobuf_utils.h
index dded9b4..b9c2e81 100644
--- a/modules/audio_processing/test/protobuf_utils.h
+++ b/modules/audio_processing/test/protobuf_utils.h
@@ -12,6 +12,7 @@
 #define MODULES_AUDIO_PROCESSING_TEST_PROTOBUF_UTILS_H_
 
 #include <memory>
+#include <sstream>  // no-presubmit-check TODO(webrtc:8982)
 
 #include "rtc_base/ignore_wundef.h"
 #include "rtc_base/protobuf_utils.h"
@@ -29,6 +30,11 @@
 // Returns true on success, false on error or end-of-file.
 bool ReadMessageFromFile(FILE* file, MessageLite* msg);
 
+// Returns true on success, false on error or end of string stream.
+bool ReadMessageFromString(
+    std::stringstream* input,  // no-presubmit-check TODO(webrtc:8982)
+    MessageLite* msg);
+
 }  // namespace webrtc
 
 #endif  // MODULES_AUDIO_PROCESSING_TEST_PROTOBUF_UTILS_H_
diff --git a/modules/audio_processing/test/test_utils.cc b/modules/audio_processing/test/test_utils.cc
index c02bc76..d8d51bc 100644
--- a/modules/audio_processing/test/test_utils.cc
+++ b/modules/audio_processing/test/test_utils.cc
@@ -68,6 +68,24 @@
   file_->WriteSamples(&interleaved_[0], interleaved_.size());
 }
 
+ChannelBufferVectorWriter::ChannelBufferVectorWriter(std::vector<float>* output)
+    : output_(output) {
+  RTC_DCHECK(output_);
+}
+
+ChannelBufferVectorWriter::~ChannelBufferVectorWriter() = default;
+
+void ChannelBufferVectorWriter::Write(const ChannelBuffer<float>& buffer) {
+  // Account for sample rate changes throughout a simulation.
+  interleaved_buffer_.resize(buffer.size());
+  Interleave(buffer.channels(), buffer.num_frames(), buffer.num_channels(),
+             interleaved_buffer_.data());
+  size_t old_size = output_->size();
+  output_->resize(old_size + interleaved_buffer_.size());
+  FloatToFloatS16(interleaved_buffer_.data(), interleaved_buffer_.size(),
+                  output_->data() + old_size);
+}
+
 void WriteIntData(const int16_t* data,
                   size_t length,
                   WavWriter* wav_file,
diff --git a/modules/audio_processing/test/test_utils.h b/modules/audio_processing/test/test_utils.h
index 0dd4a40..341f2b2 100644
--- a/modules/audio_processing/test/test_utils.h
+++ b/modules/audio_processing/test/test_utils.h
@@ -77,6 +77,26 @@
   RTC_DISALLOW_COPY_AND_ASSIGN(ChannelBufferWavWriter);
 };
 
+// Takes a pointer to a vector. Allows appending the samples of channel buffers
+// to the given vector, by interleaving the samples and converting them to float
+// S16.
+class ChannelBufferVectorWriter final {
+ public:
+  explicit ChannelBufferVectorWriter(std::vector<float>* output);
+  ChannelBufferVectorWriter(const ChannelBufferVectorWriter&) = delete;
+  ChannelBufferVectorWriter& operator=(const ChannelBufferVectorWriter&) =
+      delete;
+  ~ChannelBufferVectorWriter();
+
+  // Creates an interleaved copy of |buffer|, converts the samples to float S16
+  // and appends the result to output_.
+  void Write(const ChannelBuffer<float>& buffer);
+
+ private:
+  std::vector<float> interleaved_buffer_;
+  std::vector<float>* output_;
+};
+
 void WriteIntData(const int16_t* data,
                   size_t length,
                   WavWriter* wav_file,