diff --git a/modules/audio_processing/BUILD.gn b/modules/audio_processing/BUILD.gn index 974b59ee0d..dd2ded6945 100644 --- a/modules/audio_processing/BUILD.gn +++ b/modules/audio_processing/BUILD.gn @@ -497,6 +497,7 @@ if (rtc_include_tests) { ":audioproc_f", ":audioproc_unittest_proto", ":unpack_aecdump", + "aec_dump:aec_dump_unittests", "test/conversational_speech", "test/py_quality_assessment", ] @@ -527,6 +528,7 @@ if (rtc_include_tests) { "config_unittest.cc", "echo_cancellation_impl_unittest.cc", "splitting_filter_unittest.cc", + "test/fake_recording_device_unittest.cc", "transient/dyadic_decimator_unittest.cc", "transient/file_utils.cc", "transient/file_utils.h", @@ -549,6 +551,7 @@ if (rtc_include_tests) { ] deps = [ + ":analog_mic_simulation", ":audio_processing", ":audioproc_test_utils", "..:module_api", @@ -711,6 +714,19 @@ if (rtc_include_tests) { } } + rtc_source_set("analog_mic_simulation") { + sources = [ + "test/fake_recording_device.cc", + "test/fake_recording_device.h", + ] + deps = [ + "../../api:array_view", + "../../common_audio:common_audio", + "../../modules:module_api", + "../../rtc_base:rtc_base_approved", + ] + } + if (rtc_enable_protobuf) { rtc_executable("unpack_aecdump") { testonly = true @@ -744,6 +760,7 @@ if (rtc_include_tests) { ] deps = [ + ":analog_mic_simulation", ":audio_processing", ":audioproc_debug_proto", ":audioproc_protobuf_utils", diff --git a/modules/audio_processing/test/aec_dump_based_simulator.cc b/modules/audio_processing/test/aec_dump_based_simulator.cc index 8dda70dfb8..0e32978f73 100644 --- a/modules/audio_processing/test/aec_dump_based_simulator.cc +++ b/modules/audio_processing/test/aec_dump_based_simulator.cc @@ -69,8 +69,7 @@ AecDumpBasedSimulator::AecDumpBasedSimulator(const SimulationSettings& settings) AecDumpBasedSimulator::~AecDumpBasedSimulator() = default; void AecDumpBasedSimulator::PrepareProcessStreamCall( - const webrtc::audioproc::Stream& msg, - bool* set_stream_analog_level_called) { + const webrtc::audioproc::Stream& msg) { if (msg.has_input_data()) { // Fixed interface processing. // Verify interface invariance. @@ -159,15 +158,9 @@ void AecDumpBasedSimulator::PrepareProcessStreamCall( ap_->set_stream_key_pressed(*settings_.use_ts); } - // TODO(peah): Add support for controlling the analog level via the - // command-line. - if (msg.has_level()) { - RTC_CHECK_EQ(AudioProcessing::kNoError, - ap_->gain_control()->set_stream_analog_level(msg.level())); - *set_stream_analog_level_called = true; - } else { - *set_stream_analog_level_called = false; - } + // Level is always logged in AEC dumps. + RTC_CHECK(msg.has_level()); + aec_dump_mic_level_ = msg.level(); } void AecDumpBasedSimulator::VerifyProcessStreamBitExactness( @@ -565,14 +558,8 @@ void AecDumpBasedSimulator::HandleMessage(const webrtc::audioproc::Init& msg) { void AecDumpBasedSimulator::HandleMessage( const webrtc::audioproc::Stream& msg) { - bool set_stream_analog_level_called = false; - PrepareProcessStreamCall(msg, &set_stream_analog_level_called); + PrepareProcessStreamCall(msg); ProcessStream(interface_used_ == InterfaceType::kFixedInterface); - if (set_stream_analog_level_called) { - // Call stream analog level to ensure that any side-effects are triggered. - (void)ap_->gain_control()->stream_analog_level(); - } - VerifyProcessStreamBitExactness(msg); } diff --git a/modules/audio_processing/test/aec_dump_based_simulator.h b/modules/audio_processing/test/aec_dump_based_simulator.h index 3bdd00630d..4c29bf7315 100644 --- a/modules/audio_processing/test/aec_dump_based_simulator.h +++ b/modules/audio_processing/test/aec_dump_based_simulator.h @@ -41,8 +41,7 @@ class AecDumpBasedSimulator final : public AudioProcessingSimulator { void HandleMessage(const webrtc::audioproc::Stream& msg); void HandleMessage(const webrtc::audioproc::ReverseStream& msg); void HandleMessage(const webrtc::audioproc::Config& msg); - void PrepareProcessStreamCall(const webrtc::audioproc::Stream& msg, - bool* set_stream_analog_level_called); + void PrepareProcessStreamCall(const webrtc::audioproc::Stream& msg); void PrepareReverseProcessStreamCall( const webrtc::audioproc::ReverseStream& msg); void VerifyProcessStreamBitExactness(const webrtc::audioproc::Stream& msg); diff --git a/modules/audio_processing/test/audio_processing_simulator.cc b/modules/audio_processing/test/audio_processing_simulator.cc index 55d82c65f0..0926473b10 100644 --- a/modules/audio_processing/test/audio_processing_simulator.cc +++ b/modules/audio_processing/test/audio_processing_simulator.cc @@ -14,12 +14,15 @@ #include #include #include +#include #include #include "common_audio/include/audio_util.h" #include "modules/audio_processing/aec_dump/aec_dump_factory.h" #include "modules/audio_processing/include/audio_processing.h" +#include "modules/audio_processing/test/fake_recording_device.h" #include "rtc_base/checks.h" +#include "rtc_base/logging.h" #include "rtc_base/stringutils.h" namespace webrtc { @@ -80,7 +83,12 @@ void CopyToAudioFrame(const ChannelBuffer& src, AudioFrame* dest) { AudioProcessingSimulator::AudioProcessingSimulator( const SimulationSettings& settings) - : settings_(settings), worker_queue_("file_writer_task_queue") { + : settings_(settings), + analog_mic_level_(settings.initial_mic_level), + fake_recording_device_( + settings.initial_mic_level, + settings_.simulate_mic_gain ? *settings.simulated_mic_kind : 0), + worker_queue_("file_writer_task_queue") { if (settings_.ed_graph_output_filename && !settings_.ed_graph_output_filename->empty()) { residual_echo_likelihood_graph_writer_.open( @@ -88,6 +96,9 @@ AudioProcessingSimulator::AudioProcessingSimulator( RTC_CHECK(residual_echo_likelihood_graph_writer_.is_open()); WriteEchoLikelihoodGraphFileHeader(&residual_echo_likelihood_graph_writer_); } + + if (settings_.simulate_mic_gain) + LOG(LS_VERBOSE) << "Simulating analog mic gain"; } AudioProcessingSimulator::~AudioProcessingSimulator() { @@ -105,6 +116,34 @@ AudioProcessingSimulator::ScopedTimer::~ScopedTimer() { } void AudioProcessingSimulator::ProcessStream(bool fixed_interface) { + // Optionally use the fake recording device to simulate analog gain. + if (settings_.simulate_mic_gain) { + if (settings_.aec_dump_input_filename) { + // When the analog gain is simulated and an AEC dump is used as input, set + // the undo level to |aec_dump_mic_level_| to virtually restore the + // unmodified microphone signal level. + fake_recording_device_.SetUndoMicLevel(aec_dump_mic_level_); + } + + if (fixed_interface) { + fake_recording_device_.SimulateAnalogGain(&fwd_frame_); + } else { + fake_recording_device_.SimulateAnalogGain(in_buf_.get()); + } + + // Notify the current mic level to AGC. + RTC_CHECK_EQ(AudioProcessing::kNoError, + ap_->gain_control()->set_stream_analog_level( + fake_recording_device_.MicLevel())); + } else { + // Notify the current mic level to AGC. + RTC_CHECK_EQ(AudioProcessing::kNoError, + ap_->gain_control()->set_stream_analog_level( + settings_.aec_dump_input_filename ? aec_dump_mic_level_ + : analog_mic_level_)); + } + + // Process the current audio frame. if (fixed_interface) { { const auto st = ScopedTimer(mutable_proc_time()); @@ -118,6 +157,14 @@ void AudioProcessingSimulator::ProcessStream(bool fixed_interface) { out_config_, out_buf_->channels())); } + // Store the mic level suggested by AGC. + // Note that when the analog gain is simulated and an AEC dump is used as + // input, |analog_mic_level_| will not be used with set_stream_analog_level(). + analog_mic_level_ = ap_->gain_control()->stream_analog_level(); + if (settings_.simulate_mic_gain) { + fake_recording_device_.SetMicLevel(analog_mic_level_); + } + if (buffer_writer_) { buffer_writer_->Write(*out_buf_); } @@ -195,6 +242,8 @@ void AudioProcessingSimulator::SetupBuffersConfigsOutputs( rev_frame_.num_channels_ = reverse_input_num_channels; if (settings_.use_verbose_logging) { + rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE); + std::cout << "Sample rates:" << std::endl; std::cout << " Forward input: " << input_sample_rate_hz << std::endl; std::cout << " Forward output: " << output_sample_rate_hz << std::endl; diff --git a/modules/audio_processing/test/audio_processing_simulator.h b/modules/audio_processing/test/audio_processing_simulator.h index 8d780b8cee..9f81e6caa3 100644 --- a/modules/audio_processing/test/audio_processing_simulator.h +++ b/modules/audio_processing/test/audio_processing_simulator.h @@ -20,6 +20,7 @@ #include "api/optional.h" #include "common_audio/channel_buffer.h" #include "modules/audio_processing/include/audio_processing.h" +#include "modules/audio_processing/test/fake_recording_device.h" #include "modules/audio_processing/test/test_utils.h" #include "rtc_base/constructormagic.h" #include "rtc_base/task_queue.h" @@ -76,6 +77,9 @@ struct SimulationSettings { rtc::Optional vad_likelihood; rtc::Optional ns_level; rtc::Optional use_refined_adaptive_filter; + int initial_mic_level; + bool simulate_mic_gain = false; + rtc::Optional simulated_mic_kind; bool report_performance = false; bool report_bitexactness = false; bool use_verbose_logging = false; @@ -166,6 +170,7 @@ class AudioProcessingSimulator { AudioFrame rev_frame_; AudioFrame fwd_frame_; bool bitexact_output_ = true; + int aec_dump_mic_level_ = 0; private: void SetupOutput(); @@ -177,6 +182,8 @@ class AudioProcessingSimulator { std::unique_ptr reverse_buffer_writer_; TickIntervalStats proc_time_; std::ofstream residual_echo_likelihood_graph_writer_; + int analog_mic_level_; + FakeRecordingDevice fake_recording_device_; rtc::TaskQueue worker_queue_; diff --git a/modules/audio_processing/test/audioproc_float.cc b/modules/audio_processing/test/audioproc_float.cc index 41bd5fbf24..df7f43edc9 100644 --- a/modules/audio_processing/test/audioproc_float.cc +++ b/modules/audio_processing/test/audioproc_float.cc @@ -161,6 +161,13 @@ DEFINE_int(stream_delay, DEFINE_int(stream_drift_samples, kParameterNotSpecifiedValue, "Specify the number of stream drift samples to use"); +DEFINE_int(initial_mic_level, 100, "Initial mic level (0-255)"); +DEFINE_int(simulate_mic_gain, + 0, + "Activate (1) or deactivate(0) the analog mic gain simulation"); +DEFINE_int(simulated_mic_kind, + kParameterNotSpecifiedValue, + "Specify which microphone kind to use for microphone simulation"); DEFINE_bool(performance_report, false, "Report the APM performance "); DEFINE_bool(verbose, false, "Produce verbose output"); DEFINE_bool(bitexactness_report, @@ -269,6 +276,9 @@ SimulationSettings CreateSettings() { &settings.stream_drift_samples); SetSettingIfSpecified(FLAG_custom_call_order_file, &settings.custom_call_order_filename); + settings.initial_mic_level = FLAG_initial_mic_level; + settings.simulate_mic_gain = FLAG_simulate_mic_gain; + SetSettingIfSpecified(FLAG_simulated_mic_kind, &settings.simulated_mic_kind); settings.report_performance = FLAG_performance_report; settings.use_verbose_logging = FLAG_verbose; settings.report_bitexactness = FLAG_bitexactness_report; @@ -385,6 +395,20 @@ void PerformBasicParameterSanityChecks(const SimulationSettings& settings) { "Error: --custom_call_order_file cannot be used when operating on an " "aecdump\n"); + ReportConditionalErrorAndExit( + (settings.initial_mic_level < 0 || settings.initial_mic_level > 255), + "Error: --initial_mic_level must be specified between 0 and 255.\n"); + + ReportConditionalErrorAndExit( + settings.simulated_mic_kind && !settings.simulate_mic_gain, + "Error: --simulated_mic_kind cannot be specified when mic simulation is " + "disabled\n"); + + ReportConditionalErrorAndExit( + !settings.simulated_mic_kind && settings.simulate_mic_gain, + "Error: --simulated_mic_kind must be specified when mic simulation is " + "enabled\n"); + auto valid_wav_name = [](const std::string& wav_file_name) { if (wav_file_name.size() < 5) { return false; diff --git a/modules/audio_processing/test/fake_recording_device.cc b/modules/audio_processing/test/fake_recording_device.cc new file mode 100644 index 0000000000..bd6b644e90 --- /dev/null +++ b/modules/audio_processing/test/fake_recording_device.cc @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2017 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 "modules/audio_processing/test/fake_recording_device.h" + +#include + +#include "rtc_base/logging.h" +#include "rtc_base/ptr_util.h" + +namespace webrtc { +namespace test { + +namespace { + +constexpr int16_t kInt16SampleMin = -32768; +constexpr int16_t kInt16SampleMax = 32767; +constexpr float kFloatSampleMin = -32768.f; +constexpr float kFloatSampleMax = 32767.0f; + +} // namespace + +// Abstract class for the different fake recording devices. +class FakeRecordingDeviceWorker { + public: + explicit FakeRecordingDeviceWorker(const int initial_mic_level) + : mic_level_(initial_mic_level) {} + int mic_level() const { return mic_level_; } + void set_mic_level(const int level) { mic_level_ = level; } + void set_undo_mic_level(const int level) { + undo_mic_level_ = rtc::Optional(level); + } + virtual ~FakeRecordingDeviceWorker() = default; + virtual void ModifyBufferInt16(AudioFrame* buffer) = 0; + virtual void ModifyBufferFloat(ChannelBuffer* buffer) = 0; + + protected: + // Mic level to simulate. + int mic_level_; + // Optional mic level to undo. + rtc::Optional undo_mic_level_; +}; + +namespace { + +// Identity fake recording device. The samples are not modified, which is +// equivalent to a constant gain curve at 1.0 - only used for testing. +class FakeRecordingDeviceIdentity final : public FakeRecordingDeviceWorker { + public: + explicit FakeRecordingDeviceIdentity(const int initial_mic_level) + : FakeRecordingDeviceWorker(initial_mic_level) {} + ~FakeRecordingDeviceIdentity() override = default; + void ModifyBufferInt16(AudioFrame* buffer) override {} + void ModifyBufferFloat(ChannelBuffer* buffer) override {} +}; + +// Linear fake recording device. The gain curve is a linear function mapping the +// mic levels range [0, 255] to [0.0, 1.0]. +class FakeRecordingDeviceLinear final : public FakeRecordingDeviceWorker { + public: + explicit FakeRecordingDeviceLinear(const int initial_mic_level) + : FakeRecordingDeviceWorker(initial_mic_level) {} + ~FakeRecordingDeviceLinear() override = default; + void ModifyBufferInt16(AudioFrame* buffer) override { + const size_t number_of_samples = + buffer->samples_per_channel_ * buffer->num_channels_; + int16_t* data = buffer->mutable_data(); + // If an undo level is specified, virtually restore the unmodified + // microphone level; otherwise simulate the mic gain only. + const float divisor = + (undo_mic_level_ && *undo_mic_level_ > 0) ? *undo_mic_level_ : 255.f; + for (size_t i = 0; i < number_of_samples; ++i) { + data[i] = + std::max(kInt16SampleMin, + std::min(kInt16SampleMax, + static_cast(static_cast(data[i]) * + mic_level_ / divisor))); + } + } + void ModifyBufferFloat(ChannelBuffer* buffer) override { + // If an undo level is specified, virtually restore the unmodified + // microphone level; otherwise simulate the mic gain only. + const float divisor = + (undo_mic_level_ && *undo_mic_level_ > 0) ? *undo_mic_level_ : 255.f; + for (size_t c = 0; c < buffer->num_channels(); ++c) { + for (size_t i = 0; i < buffer->num_frames(); ++i) { + buffer->channels()[c][i] = + std::max(kFloatSampleMin, + std::min(kFloatSampleMax, + buffer->channels()[c][i] * mic_level_ / divisor)); + } + } + } +}; + +} // namespace + +FakeRecordingDevice::FakeRecordingDevice(int initial_mic_level, + int device_kind) { + switch (device_kind) { + case 0: + worker_ = rtc::MakeUnique(initial_mic_level); + break; + case 1: + worker_ = rtc::MakeUnique(initial_mic_level); + break; + default: + RTC_NOTREACHED(); + break; + } +} + +FakeRecordingDevice::~FakeRecordingDevice() = default; + +int FakeRecordingDevice::MicLevel() const { + RTC_CHECK(worker_); + return worker_->mic_level(); +} + +void FakeRecordingDevice::SetMicLevel(const int level) { + RTC_CHECK(worker_); + if (level != worker_->mic_level()) + LOG(LS_INFO) << "Simulate mic level update: " << level; + worker_->set_mic_level(level); +} + +void FakeRecordingDevice::SetUndoMicLevel(const int level) { + RTC_DCHECK(worker_); + // TODO(alessiob): The behavior with undo level equal to zero is not clear yet + // and will be defined in future CLs once more FakeRecordingDeviceWorker + // implementations need to be added. + RTC_CHECK(level > 0) << "Zero undo mic level is unsupported"; + worker_->set_undo_mic_level(level); +} + +void FakeRecordingDevice::SimulateAnalogGain(AudioFrame* buffer) { + RTC_DCHECK(worker_); + worker_->ModifyBufferInt16(buffer); +} + +void FakeRecordingDevice::SimulateAnalogGain(ChannelBuffer* buffer) { + RTC_DCHECK(worker_); + worker_->ModifyBufferFloat(buffer); +} + +} // namespace test +} // namespace webrtc diff --git a/modules/audio_processing/test/fake_recording_device.h b/modules/audio_processing/test/fake_recording_device.h new file mode 100644 index 0000000000..b1e37a331d --- /dev/null +++ b/modules/audio_processing/test/fake_recording_device.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2017 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. + */ + +#ifndef MODULES_AUDIO_PROCESSING_TEST_FAKE_RECORDING_DEVICE_H_ +#define MODULES_AUDIO_PROCESSING_TEST_FAKE_RECORDING_DEVICE_H_ + +#include +#include +#include + +#include "api/array_view.h" +#include "common_audio/channel_buffer.h" +#include "modules/include/module_common_types.h" +#include "rtc_base/checks.h" + +namespace webrtc { +namespace test { + +class FakeRecordingDeviceWorker; + +// Class for simulating a microphone with analog gain. +// +// The intended modes of operation are the following: +// +// FakeRecordingDevice fake_mic(255, 1); +// +// fake_mic.SetMicLevel(170); +// fake_mic.SimulateAnalogGain(buffer); +// +// When the mic level to undo is known: +// +// fake_mic.SetMicLevel(170); +// fake_mic.SetUndoMicLevel(30); +// fake_mic.SimulateAnalogGain(buffer); +// +// The second option virtually restores the unmodified microphone level. Calling +// SimulateAnalogGain() will first "undo" the gain applied by the real +// microphone (e.g., 30). +class FakeRecordingDevice final { + public: + FakeRecordingDevice(int initial_mic_level, int device_kind); + ~FakeRecordingDevice(); + + int MicLevel() const; + void SetMicLevel(const int level); + void SetUndoMicLevel(const int level); + + // Simulates the analog gain. + // If |real_device_level| is a valid level, the unmodified mic signal is + // virtually restored. To skip the latter step set |real_device_level| to + // an empty value. + void SimulateAnalogGain(AudioFrame* buffer); + + // Simulates the analog gain. + // If |real_device_level| is a valid level, the unmodified mic signal is + // virtually restored. To skip the latter step set |real_device_level| to + // an empty value. + void SimulateAnalogGain(ChannelBuffer* buffer); + + private: + // Fake recording device worker. + std::unique_ptr worker_; +}; + +} // namespace test +} // namespace webrtc + +#endif // MODULES_AUDIO_PROCESSING_TEST_FAKE_RECORDING_DEVICE_H_ diff --git a/modules/audio_processing/test/fake_recording_device_unittest.cc b/modules/audio_processing/test/fake_recording_device_unittest.cc new file mode 100644 index 0000000000..504459a3c6 --- /dev/null +++ b/modules/audio_processing/test/fake_recording_device_unittest.cc @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2017 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 +#include +#include +#include +#include + +#include "api/array_view.h" +#include "modules/audio_processing/test/fake_recording_device.h" +#include "rtc_base/ptr_util.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { +namespace { + +constexpr int kInitialMicLevel = 100; + +// TODO(alessiob): Add new fake recording device kind values here as they are +// added in FakeRecordingDevice::FakeRecordingDevice. +const std::vector kFakeRecDeviceKinds = {0, 1}; + +const std::vector> kTestMultiChannelSamples{ + std::vector{-10.f, -1.f, -0.1f, 0.f, 0.1f, 1.f, 10.f}}; + +// Writes samples into ChannelBuffer. +void WritesDataIntoChannelBuffer(const std::vector>& data, + ChannelBuffer* buff) { + EXPECT_EQ(data.size(), buff->num_channels()); + EXPECT_EQ(data[0].size(), buff->num_frames()); + for (size_t c = 0; c < buff->num_channels(); ++c) { + for (size_t f = 0; f < buff->num_frames(); ++f) { + buff->channels()[c][f] = data[c][f]; + } + } +} + +std::unique_ptr> CreateChannelBufferWithData( + const std::vector>& data) { + auto buff = + rtc::MakeUnique>(data[0].size(), data.size()); + WritesDataIntoChannelBuffer(data, buff.get()); + return buff; +} + +// Checks that the samples modified using monotonic level values are also +// monotonic. +void CheckIfMonotoneSamplesModules(const ChannelBuffer* prev, + const ChannelBuffer* curr) { + RTC_DCHECK_EQ(prev->num_channels(), curr->num_channels()); + RTC_DCHECK_EQ(prev->num_frames(), curr->num_frames()); + bool valid = true; + for (size_t i = 0; i < prev->num_channels(); ++i) { + for (size_t j = 0; j < prev->num_frames(); ++j) { + valid = std::fabs(prev->channels()[i][j]) <= + std::fabs(curr->channels()[i][j]); + if (!valid) { + break; + } + } + if (!valid) { + break; + } + } + EXPECT_TRUE(valid); +} + +// Checks that the samples in each pair have the same sign unless the sample in +// |dst| is zero (because of zero gain). +void CheckSameSign(const ChannelBuffer* src, + const ChannelBuffer* dst) { + RTC_DCHECK_EQ(src->num_channels(), dst->num_channels()); + RTC_DCHECK_EQ(src->num_frames(), dst->num_frames()); + const auto fsgn = [](float x) { return ((x < 0) ? -1 : (x > 0) ? 1 : 0); }; + bool valid = true; + for (size_t i = 0; i < src->num_channels(); ++i) { + for (size_t j = 0; j < src->num_frames(); ++j) { + valid = dst->channels()[i][j] == 0.0f || + fsgn(src->channels()[i][j]) == fsgn(dst->channels()[i][j]); + if (!valid) { + break; + } + } + if (!valid) { + break; + } + } + EXPECT_TRUE(valid); +} + +std::string FakeRecordingDeviceKindToString(int fake_rec_device_kind) { + std::ostringstream ss; + ss << "fake recording device: " << fake_rec_device_kind; + return ss.str(); +} + +std::string AnalogLevelToString(int level) { + std::ostringstream ss; + ss << "analog level: " << level; + return ss.str(); +} + +} // namespace + +TEST(FakeRecordingDevice, CheckHelperFunctions) { + constexpr size_t kC = 0; // Channel index. + constexpr size_t kS = 1; // Sample index. + + // Check read. + auto buff = CreateChannelBufferWithData(kTestMultiChannelSamples); + for (size_t c = 0; c < kTestMultiChannelSamples.size(); ++c) { + for (size_t s = 0; s < kTestMultiChannelSamples[0].size(); ++s) { + EXPECT_EQ(kTestMultiChannelSamples[c][s], buff->channels()[c][s]); + } + } + + // Check write. + buff->channels()[kC][kS] = -5.0f; + RTC_DCHECK_NE(buff->channels()[kC][kS], kTestMultiChannelSamples[kC][kS]); + + // Check reset. + WritesDataIntoChannelBuffer(kTestMultiChannelSamples, buff.get()); + EXPECT_EQ(buff->channels()[kC][kS], kTestMultiChannelSamples[kC][kS]); +} + +// Implicitly checks that changes to the mic and undo levels are visible to the +// FakeRecordingDeviceWorker implementation are injected in FakeRecordingDevice. +TEST(FakeRecordingDevice, TestWorkerAbstractClass) { + FakeRecordingDevice fake_recording_device(kInitialMicLevel, 1); + + auto buff1 = CreateChannelBufferWithData(kTestMultiChannelSamples); + fake_recording_device.SetMicLevel(100); + fake_recording_device.SimulateAnalogGain(buff1.get()); + + auto buff2 = CreateChannelBufferWithData(kTestMultiChannelSamples); + fake_recording_device.SetMicLevel(200); + fake_recording_device.SimulateAnalogGain(buff2.get()); + + for (size_t c = 0; c < kTestMultiChannelSamples.size(); ++c) { + for (size_t s = 0; s < kTestMultiChannelSamples[0].size(); ++s) { + EXPECT_LE(std::abs(buff1->channels()[c][s]), + std::abs(buff2->channels()[c][s])); + } + } + + auto buff3 = CreateChannelBufferWithData(kTestMultiChannelSamples); + fake_recording_device.SetMicLevel(200); + fake_recording_device.SetUndoMicLevel(100); + fake_recording_device.SimulateAnalogGain(buff3.get()); + + for (size_t c = 0; c < kTestMultiChannelSamples.size(); ++c) { + for (size_t s = 0; s < kTestMultiChannelSamples[0].size(); ++s) { + EXPECT_LE(std::abs(buff1->channels()[c][s]), + std::abs(buff3->channels()[c][s])); + EXPECT_LE(std::abs(buff2->channels()[c][s]), + std::abs(buff3->channels()[c][s])); + } + } +} + +TEST(FakeRecordingDevice, GainCurveShouldBeMonotone) { + // Create input-output buffers. + auto buff_prev = CreateChannelBufferWithData(kTestMultiChannelSamples); + auto buff_curr = CreateChannelBufferWithData(kTestMultiChannelSamples); + + // Test different mappings. + for (auto fake_rec_device_kind : kFakeRecDeviceKinds) { + SCOPED_TRACE(FakeRecordingDeviceKindToString(fake_rec_device_kind)); + FakeRecordingDevice fake_recording_device(kInitialMicLevel, + fake_rec_device_kind); + // TODO(alessiob): The test below is designed for state-less recording + // devices. If, for instance, a device has memory, the test might need + // to be redesigned (e.g., re-initialize fake recording device). + + // Apply lowest analog level. + WritesDataIntoChannelBuffer(kTestMultiChannelSamples, buff_prev.get()); + fake_recording_device.SetMicLevel(0); + fake_recording_device.SimulateAnalogGain(buff_prev.get()); + + // Increment analog level to check monotonicity. + for (int i = 1; i <= 255; ++i) { + SCOPED_TRACE(AnalogLevelToString(i)); + WritesDataIntoChannelBuffer(kTestMultiChannelSamples, buff_curr.get()); + fake_recording_device.SetMicLevel(i); + fake_recording_device.SimulateAnalogGain(buff_curr.get()); + CheckIfMonotoneSamplesModules(buff_prev.get(), buff_curr.get()); + + // Update prev. + buff_prev.swap(buff_curr); + } + } +} + +TEST(FakeRecordingDevice, GainCurveShouldNotChangeSign) { + // Create view on original samples. + std::unique_ptr> buff_orig = + CreateChannelBufferWithData(kTestMultiChannelSamples); + + // Create output buffer. + auto buff = CreateChannelBufferWithData(kTestMultiChannelSamples); + + // Test different mappings. + for (auto fake_rec_device_kind : kFakeRecDeviceKinds) { + SCOPED_TRACE(FakeRecordingDeviceKindToString(fake_rec_device_kind)); + FakeRecordingDevice fake_recording_device(kInitialMicLevel, + fake_rec_device_kind); + + // TODO(alessiob): The test below is designed for state-less recording + // devices. If, for instance, a device has memory, the test might need + // to be redesigned (e.g., re-initialize fake recording device). + for (int i = 0; i <= 255; ++i) { + SCOPED_TRACE(AnalogLevelToString(i)); + WritesDataIntoChannelBuffer(kTestMultiChannelSamples, buff.get()); + fake_recording_device.SetMicLevel(i); + fake_recording_device.SimulateAnalogGain(buff.get()); + CheckSameSign(buff_orig.get(), buff.get()); + } + } +} + +} // namespace test +} // namespace webrtc diff --git a/modules/audio_processing/test/wav_based_simulator.cc b/modules/audio_processing/test/wav_based_simulator.cc index a80ad4d460..599242969a 100644 --- a/modules/audio_processing/test/wav_based_simulator.cc +++ b/modules/audio_processing/test/wav_based_simulator.cc @@ -79,10 +79,6 @@ void WavBasedSimulator::PrepareProcessStreamCall() { ap_->echo_cancellation()->set_stream_drift_samples( settings_.stream_drift_samples ? *settings_.stream_drift_samples : 0); - - RTC_CHECK_EQ(AudioProcessing::kNoError, - ap_->gain_control()->set_stream_analog_level( - last_specified_microphone_level_)); } void WavBasedSimulator::PrepareReverseProcessStreamCall() { @@ -143,10 +139,6 @@ bool WavBasedSimulator::HandleProcessStreamCall() { if (samples_left_to_process) { PrepareProcessStreamCall(); ProcessStream(settings_.fixed_interface); - // Call stream analog level to ensure that any side-effects are triggered. - (void)ap_->gain_control()->stream_analog_level(); - last_specified_microphone_level_ = - ap_->gain_control()->stream_analog_level(); } return samples_left_to_process; } diff --git a/modules/audio_processing/test/wav_based_simulator.h b/modules/audio_processing/test/wav_based_simulator.h index 0ea278cf89..febcffb62c 100644 --- a/modules/audio_processing/test/wav_based_simulator.h +++ b/modules/audio_processing/test/wav_based_simulator.h @@ -45,7 +45,6 @@ class WavBasedSimulator final : public AudioProcessingSimulator { const std::string& filename); std::vector call_chain_; - int last_specified_microphone_level_ = 100; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WavBasedSimulator); };