diff --git a/api/test/audioproc_float.cc b/api/test/audioproc_float.cc index 9d3ad7e9d8..bba9c622a1 100644 --- a/api/test/audioproc_float.cc +++ b/api/test/audioproc_float.cc @@ -20,7 +20,18 @@ namespace test { int AudioprocFloat(std::unique_ptr ap_builder, int argc, char* argv[]) { - return AudioprocFloatImpl(std::move(ap_builder), argc, argv); + return AudioprocFloatImpl(std::move(ap_builder), argc, argv, + /*input_aecdump=*/"", + /*processed_capture_samples=*/nullptr); +} + +int AudioprocFloat(std::unique_ptr ap_builder, + int argc, + char* argv[], + absl::string_view input_aecdump, + std::vector* processed_capture_samples) { + return AudioprocFloatImpl(std::move(ap_builder), argc, argv, input_aecdump, + processed_capture_samples); } } // namespace test diff --git a/api/test/audioproc_float.h b/api/test/audioproc_float.h index 25e4dd5b1c..2625e6ad9a 100644 --- a/api/test/audioproc_float.h +++ b/api/test/audioproc_float.h @@ -12,6 +12,7 @@ #define API_TEST_AUDIOPROC_FLOAT_H_ #include +#include #include "modules/audio_processing/include/audio_processing.h" @@ -36,6 +37,18 @@ int AudioprocFloat(std::unique_ptr ap_builder, int argc, char* argv[]); +// Interface for the audio processing simulation utility, which is similar to +// the one above, but which adds the option of receiving the input as a string +// and returning the output as an array. The first three arguments fulfill the +// same purpose as above. Pass the |input_aecdump| to provide the content of an +// AEC dump file as a string. After the simulation is completed, +// |processed_capture_samples| will contain the the samples processed on the +// capture side. +int AudioprocFloat(std::unique_ptr ap_builder, + int argc, + char* argv[], + absl::string_view input_aecdump, + std::vector* processed_capture_samples); } // namespace test } // namespace webrtc diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn index b03b40d255..eaa88261f3 100644 --- a/modules/audio_processing/BUILD.gn +++ b/modules/audio_processing/BUILD.gn @@ -755,6 +755,7 @@ if (rtc_include_tests) { "../../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 00fd25e759..0d6bdd1167 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::PrepareReverseProcessStreamCall( void AecDumpBasedSimulator::Process() { CreateAudioProcessor(); - dump_input_file_ = OpenFile(settings_.aec_dump_input_filename->c_str(), "rb"); - if (settings_.artificial_nearend_filename) { std::unique_ptr artificial_nearend_file( new WavReader(settings_.artificial_nearend_filename->c_str())); @@ -231,39 +229,52 @@ void AecDumpBasedSimulator::Process() { 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 1181979422..ef032d0316 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 @@ class AecDumpBasedSimulator final : public AudioProcessingSimulator { 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 a212125ba8..65a52d59f0 100644 --- a/modules/audio_processing/test/audio_processing_simulator.cc +++ b/modules/audio_processing/test/audio_processing_simulator.cc @@ -222,9 +222,12 @@ void AudioProcessingSimulator::ProcessStream(bool fixed_interface) { 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 @@ void AudioProcessingSimulator::ProcessReverseStream(bool fixed_interface) { 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 @@ void AudioProcessingSimulator::SetupOutput() { std::unique_ptr out_file( new WavWriter(filename, out_config_.sample_rate_hz(), static_cast(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( + settings_.processed_capture_samples); } if (settings_.reverse_output_filename) { @@ -351,7 +357,7 @@ void AudioProcessingSimulator::SetupOutput() { std::unique_ptr reverse_out_file( new WavWriter(filename, reverse_out_config_.sample_rate_hz(), static_cast(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 7800afa2a1..6f84813b35 100644 --- a/modules/audio_processing/test/audio_processing_simulator.h +++ b/modules/audio_processing/test/audio_processing_simulator.h @@ -101,6 +101,8 @@ struct SimulationSettings { absl::optional call_order_input_filename; absl::optional call_order_output_filename; absl::optional aec_settings_filename; + absl::optional aec_dump_input_string; + std::vector* processed_capture_samples = nullptr; }; // Copies samples present in a ChannelBuffer into an AudioFrame. @@ -172,8 +174,9 @@ class AudioProcessingSimulator { size_t num_process_stream_calls_ = 0; size_t num_reverse_process_stream_calls_ = 0; - std::unique_ptr buffer_writer_; - std::unique_ptr reverse_buffer_writer_; + std::unique_ptr buffer_file_writer_; + std::unique_ptr reverse_buffer_file_writer_; + std::unique_ptr 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 3764282d99..41d137b701 100644 --- a/modules/audio_processing/test/audioproc_float_impl.cc +++ b/modules/audio_processing/test/audioproc_float_impl.cc @@ -462,9 +462,15 @@ void ReportConditionalErrorAndExit(bool condition, const std::string& message) { 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 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) { "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 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) { int AudioprocFloatImpl(std::unique_ptr ap_builder, int argc, - char* argv[]) { + char* argv[], + absl::string_view input_aecdump, + std::vector* processed_capture_samples) { std::vector args = absl::ParseCommandLine(argc, argv); if (args.size() != 1) { printf("%s", kUsageDescription); @@ -632,10 +645,15 @@ int AudioprocFloatImpl(std::unique_ptr ap_builder, } 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 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 063ecb45b8..9a9013c644 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 ap_builder, int argc, - char* argv[]); + char* argv[], + absl::string_view input_aecdump, + std::vector* 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 f3c97eef7f..3042bce29f 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* bytes) { + int32_t size = 0; + input->read(reinterpret_cast(&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(size); + input->read(reinterpret_cast(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* bytes) { @@ -26,7 +49,7 @@ size_t ReadMessageBytesFromFile(FILE* file, std::unique_ptr* bytes) { if (size <= 0) return 0; - bytes->reset(new uint8_t[size]); + *bytes = absl::make_unique(size); return fread(bytes->get(), sizeof((*bytes)[0]), size, file); } @@ -41,4 +64,15 @@ bool ReadMessageFromFile(FILE* file, MessageLite* msg) { 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 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 dded9b4ca7..b9c2e819f9 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 +#include // no-presubmit-check TODO(webrtc:8982) #include "rtc_base/ignore_wundef.h" #include "rtc_base/protobuf_utils.h" @@ -29,6 +30,11 @@ size_t ReadMessageBytesFromFile(FILE* file, std::unique_ptr* bytes); // 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 c02bc7607a..d8d51bc819 100644 --- a/modules/audio_processing/test/test_utils.cc +++ b/modules/audio_processing/test/test_utils.cc @@ -68,6 +68,24 @@ void ChannelBufferWavWriter::Write(const ChannelBuffer& buffer) { file_->WriteSamples(&interleaved_[0], interleaved_.size()); } +ChannelBufferVectorWriter::ChannelBufferVectorWriter(std::vector* output) + : output_(output) { + RTC_DCHECK(output_); +} + +ChannelBufferVectorWriter::~ChannelBufferVectorWriter() = default; + +void ChannelBufferVectorWriter::Write(const ChannelBuffer& 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 0dd4a40bc9..341f2b2374 100644 --- a/modules/audio_processing/test/test_utils.h +++ b/modules/audio_processing/test/test_utils.h @@ -77,6 +77,26 @@ class ChannelBufferWavWriter final { 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* 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& buffer); + + private: + std::vector interleaved_buffer_; + std::vector* output_; +}; + void WriteIntData(const int16_t* data, size_t length, WavWriter* wav_file,