/* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include // size_t #include #include #include #include "absl/strings/string_view.h" #include "api/audio/echo_canceller3_factory.h" #include "modules/audio_coding/neteq/tools/resample_input_audio_file.h" #include "modules/audio_processing/aec_dump/aec_dump_factory.h" #include "modules/audio_processing/test/audio_processing_builder_for_testing.h" #include "modules/audio_processing/test/debug_dump_replayer.h" #include "modules/audio_processing/test/test_utils.h" #include "rtc_base/task_queue_for_test.h" #include "test/gtest.h" #include "test/testsupport/file_utils.h" namespace webrtc { namespace test { namespace { void MaybeResetBuffer(std::unique_ptr>* buffer, const StreamConfig& config) { auto& buffer_ref = *buffer; if (!buffer_ref.get() || buffer_ref->num_frames() != config.num_frames() || buffer_ref->num_channels() != config.num_channels()) { buffer_ref.reset( new ChannelBuffer(config.num_frames(), config.num_channels())); } } class DebugDumpGenerator { public: DebugDumpGenerator(absl::string_view input_file_name, int input_rate_hz, int input_channels, absl::string_view reverse_file_name, int reverse_rate_hz, int reverse_channels, absl::string_view dump_file_name, bool enable_pre_amplifier); // Constructor that uses default input files. explicit DebugDumpGenerator(const AudioProcessing::Config& apm_config); ~DebugDumpGenerator(); // Changes the sample rate of the input audio to the APM. void SetInputRate(int rate_hz); // Sets if converts stereo input signal to mono by discarding other channels. void ForceInputMono(bool mono); // Changes the sample rate of the reverse audio to the APM. void SetReverseRate(int rate_hz); // Sets if converts stereo reverse signal to mono by discarding other // channels. void ForceReverseMono(bool mono); // Sets the required sample rate of the APM output. void SetOutputRate(int rate_hz); // Sets the required channels of the APM output. void SetOutputChannels(int channels); std::string dump_file_name() const { return dump_file_name_; } void StartRecording(); void Process(size_t num_blocks); void StopRecording(); AudioProcessing* apm() const { return apm_.get(); } private: static void ReadAndDeinterleave(ResampleInputAudioFile* audio, int channels, const StreamConfig& config, float* const* buffer); // APM input/output settings. StreamConfig input_config_; StreamConfig reverse_config_; StreamConfig output_config_; // Input file format. const std::string input_file_name_; ResampleInputAudioFile input_audio_; const int input_file_channels_; // Reverse file format. const std::string reverse_file_name_; ResampleInputAudioFile reverse_audio_; const int reverse_file_channels_; // Buffer for APM input/output. std::unique_ptr> input_; std::unique_ptr> reverse_; std::unique_ptr> output_; bool enable_pre_amplifier_; TaskQueueForTest worker_queue_; rtc::scoped_refptr apm_; const std::string dump_file_name_; }; DebugDumpGenerator::DebugDumpGenerator(absl::string_view input_file_name, int input_rate_hz, int input_channels, absl::string_view reverse_file_name, int reverse_rate_hz, int reverse_channels, absl::string_view dump_file_name, bool enable_pre_amplifier) : input_config_(input_rate_hz, input_channels), reverse_config_(reverse_rate_hz, reverse_channels), output_config_(input_rate_hz, input_channels), input_audio_(input_file_name, input_rate_hz, input_rate_hz), input_file_channels_(input_channels), reverse_audio_(reverse_file_name, reverse_rate_hz, reverse_rate_hz), reverse_file_channels_(reverse_channels), input_(new ChannelBuffer(input_config_.num_frames(), input_config_.num_channels())), reverse_(new ChannelBuffer(reverse_config_.num_frames(), reverse_config_.num_channels())), output_(new ChannelBuffer(output_config_.num_frames(), output_config_.num_channels())), enable_pre_amplifier_(enable_pre_amplifier), worker_queue_("debug_dump_generator_worker_queue"), dump_file_name_(dump_file_name) { AudioProcessingBuilderForTesting apm_builder; apm_ = apm_builder.Create(); } DebugDumpGenerator::DebugDumpGenerator( const AudioProcessing::Config& apm_config) : DebugDumpGenerator(ResourcePath("near32_stereo", "pcm"), 32000, 2, ResourcePath("far32_stereo", "pcm"), 32000, 2, TempFilename(OutputPath(), "debug_aec"), apm_config.pre_amplifier.enabled) { apm_->ApplyConfig(apm_config); } DebugDumpGenerator::~DebugDumpGenerator() { remove(dump_file_name_.c_str()); } void DebugDumpGenerator::SetInputRate(int rate_hz) { input_audio_.set_output_rate_hz(rate_hz); input_config_.set_sample_rate_hz(rate_hz); MaybeResetBuffer(&input_, input_config_); } void DebugDumpGenerator::ForceInputMono(bool mono) { const int channels = mono ? 1 : input_file_channels_; input_config_.set_num_channels(channels); MaybeResetBuffer(&input_, input_config_); } void DebugDumpGenerator::SetReverseRate(int rate_hz) { reverse_audio_.set_output_rate_hz(rate_hz); reverse_config_.set_sample_rate_hz(rate_hz); MaybeResetBuffer(&reverse_, reverse_config_); } void DebugDumpGenerator::ForceReverseMono(bool mono) { const int channels = mono ? 1 : reverse_file_channels_; reverse_config_.set_num_channels(channels); MaybeResetBuffer(&reverse_, reverse_config_); } void DebugDumpGenerator::SetOutputRate(int rate_hz) { output_config_.set_sample_rate_hz(rate_hz); MaybeResetBuffer(&output_, output_config_); } void DebugDumpGenerator::SetOutputChannels(int channels) { output_config_.set_num_channels(channels); MaybeResetBuffer(&output_, output_config_); } void DebugDumpGenerator::StartRecording() { apm_->AttachAecDump( AecDumpFactory::Create(dump_file_name_.c_str(), -1, &worker_queue_)); } void DebugDumpGenerator::Process(size_t num_blocks) { for (size_t i = 0; i < num_blocks; ++i) { ReadAndDeinterleave(&reverse_audio_, reverse_file_channels_, reverse_config_, reverse_->channels()); ReadAndDeinterleave(&input_audio_, input_file_channels_, input_config_, input_->channels()); RTC_CHECK_EQ(AudioProcessing::kNoError, apm_->set_stream_delay_ms(100)); apm_->set_stream_analog_level(100); if (enable_pre_amplifier_) { apm_->SetRuntimeSetting( AudioProcessing::RuntimeSetting::CreateCapturePreGain(1 + i % 10)); } apm_->set_stream_key_pressed(i % 10 == 9); RTC_CHECK_EQ(AudioProcessing::kNoError, apm_->ProcessStream(input_->channels(), input_config_, output_config_, output_->channels())); RTC_CHECK_EQ( AudioProcessing::kNoError, apm_->ProcessReverseStream(reverse_->channels(), reverse_config_, reverse_config_, reverse_->channels())); } } void DebugDumpGenerator::StopRecording() { apm_->DetachAecDump(); } void DebugDumpGenerator::ReadAndDeinterleave(ResampleInputAudioFile* audio, int channels, const StreamConfig& config, float* const* buffer) { const size_t num_frames = config.num_frames(); const int out_channels = config.num_channels(); std::vector signal(channels * num_frames); audio->Read(num_frames * channels, &signal[0]); // We only allow reducing number of channels by discarding some channels. RTC_CHECK_LE(out_channels, channels); for (int channel = 0; channel < out_channels; ++channel) { for (size_t i = 0; i < num_frames; ++i) { buffer[channel][i] = S16ToFloat(signal[i * channels + channel]); } } } } // namespace class DebugDumpTest : public ::testing::Test { public: // VerifyDebugDump replays a debug dump using APM and verifies that the result // is bit-exact-identical to the output channel in the dump. This is only // guaranteed if the debug dump is started on the first frame. void VerifyDebugDump(absl::string_view in_filename); private: DebugDumpReplayer debug_dump_replayer_; }; void DebugDumpTest::VerifyDebugDump(absl::string_view in_filename) { ASSERT_TRUE(debug_dump_replayer_.SetDumpFile(in_filename)); while (const absl::optional event = debug_dump_replayer_.GetNextEvent()) { debug_dump_replayer_.RunNextEvent(); if (event->type() == audioproc::Event::STREAM) { const audioproc::Stream* msg = &event->stream(); const StreamConfig output_config = debug_dump_replayer_.GetOutputConfig(); const ChannelBuffer* output = debug_dump_replayer_.GetOutput(); // Check that output of APM is bit-exact to the output in the dump. ASSERT_EQ(output_config.num_channels(), static_cast(msg->output_channel_size())); ASSERT_EQ(output_config.num_frames() * sizeof(float), msg->output_channel(0).size()); for (int i = 0; i < msg->output_channel_size(); ++i) { ASSERT_EQ(0, memcmp(output->channels()[i], msg->output_channel(i).data(), msg->output_channel(i).size())); } } } } TEST_F(DebugDumpTest, SimpleCase) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, ChangeInputFormat) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); generator.SetInputRate(48000); generator.ForceInputMono(true); // Number of output channel should not be larger than that of input. APM will // fail otherwise. generator.SetOutputChannels(1); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, ChangeReverseFormat) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); generator.SetReverseRate(48000); generator.ForceReverseMono(true); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, ChangeOutputFormat) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); generator.SetOutputRate(48000); generator.SetOutputChannels(1); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, ToggleAec) { AudioProcessing::Config apm_config; apm_config.echo_canceller.enabled = true; DebugDumpGenerator generator(apm_config); generator.StartRecording(); generator.Process(100); apm_config.echo_canceller.enabled = false; generator.apm()->ApplyConfig(apm_config); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, VerifyCombinedExperimentalStringInclusive) { AudioProcessing::Config apm_config; apm_config.echo_canceller.enabled = true; apm_config.gain_controller1.analog_gain_controller.enabled = true; apm_config.gain_controller1.analog_gain_controller.startup_min_volume = 0; DebugDumpGenerator generator(apm_config); generator.StartRecording(); generator.Process(100); generator.StopRecording(); DebugDumpReplayer debug_dump_replayer_; ASSERT_TRUE(debug_dump_replayer_.SetDumpFile(generator.dump_file_name())); while (const absl::optional event = debug_dump_replayer_.GetNextEvent()) { debug_dump_replayer_.RunNextEvent(); if (event->type() == audioproc::Event::CONFIG) { const audioproc::Config* msg = &event->config(); ASSERT_TRUE(msg->has_experiments_description()); EXPECT_PRED_FORMAT2(::testing::IsSubstring, "EchoController", msg->experiments_description().c_str()); } } } TEST_F(DebugDumpTest, VerifyCombinedExperimentalStringExclusive) { AudioProcessing::Config apm_config; apm_config.echo_canceller.enabled = true; DebugDumpGenerator generator(apm_config); generator.StartRecording(); generator.Process(100); generator.StopRecording(); DebugDumpReplayer debug_dump_replayer_; ASSERT_TRUE(debug_dump_replayer_.SetDumpFile(generator.dump_file_name())); while (const absl::optional event = debug_dump_replayer_.GetNextEvent()) { debug_dump_replayer_.RunNextEvent(); if (event->type() == audioproc::Event::CONFIG) { const audioproc::Config* msg = &event->config(); ASSERT_TRUE(msg->has_experiments_description()); EXPECT_PRED_FORMAT2(::testing::IsNotSubstring, "AgcClippingLevelExperiment", msg->experiments_description().c_str()); } } } TEST_F(DebugDumpTest, VerifyAec3ExperimentalString) { AudioProcessing::Config apm_config; apm_config.echo_canceller.enabled = true; DebugDumpGenerator generator(apm_config); generator.StartRecording(); generator.Process(100); generator.StopRecording(); DebugDumpReplayer debug_dump_replayer_; ASSERT_TRUE(debug_dump_replayer_.SetDumpFile(generator.dump_file_name())); while (const absl::optional event = debug_dump_replayer_.GetNextEvent()) { debug_dump_replayer_.RunNextEvent(); if (event->type() == audioproc::Event::CONFIG) { const audioproc::Config* msg = &event->config(); ASSERT_TRUE(msg->has_experiments_description()); EXPECT_PRED_FORMAT2(::testing::IsSubstring, "EchoController", msg->experiments_description().c_str()); } } } TEST_F(DebugDumpTest, VerifyEmptyExperimentalString) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); generator.StopRecording(); DebugDumpReplayer debug_dump_replayer_; ASSERT_TRUE(debug_dump_replayer_.SetDumpFile(generator.dump_file_name())); while (const absl::optional event = debug_dump_replayer_.GetNextEvent()) { debug_dump_replayer_.RunNextEvent(); if (event->type() == audioproc::Event::CONFIG) { const audioproc::Config* msg = &event->config(); ASSERT_TRUE(msg->has_experiments_description()); EXPECT_EQ(0u, msg->experiments_description().size()); } } } // AGC is not supported on Android or iOS. #if defined(WEBRTC_ANDROID) || defined(WEBRTC_IOS) #define MAYBE_ToggleAgc DISABLED_ToggleAgc #else #define MAYBE_ToggleAgc ToggleAgc #endif TEST_F(DebugDumpTest, MAYBE_ToggleAgc) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); AudioProcessing::Config apm_config = generator.apm()->GetConfig(); apm_config.gain_controller1.enabled = !apm_config.gain_controller1.enabled; generator.apm()->ApplyConfig(apm_config); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, ToggleNs) { DebugDumpGenerator generator(/*apm_config=*/{}); generator.StartRecording(); generator.Process(100); AudioProcessing::Config apm_config = generator.apm()->GetConfig(); apm_config.noise_suppression.enabled = !apm_config.noise_suppression.enabled; generator.apm()->ApplyConfig(apm_config); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, TransientSuppressionOn) { DebugDumpGenerator generator(/*apm_config=*/{}); AudioProcessing::Config apm_config = generator.apm()->GetConfig(); apm_config.transient_suppression.enabled = true; generator.apm()->ApplyConfig(apm_config); generator.StartRecording(); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } TEST_F(DebugDumpTest, PreAmplifierIsOn) { AudioProcessing::Config apm_config; apm_config.pre_amplifier.enabled = true; DebugDumpGenerator generator(apm_config); generator.StartRecording(); generator.Process(100); generator.StopRecording(); VerifyDebugDump(generator.dump_file_name()); } } // namespace test } // namespace webrtc