audioproc_f with simulated mic analog gain
The gain suggested by AGC is optionally used in audioproc_f to simulate analog gain applied to the mic. The simulation is done by applying digital gain to the input samples. This functionality is optional and disabled by default. If an AECdump is provided and the mic gain simulation is enabled, an extra "level undo" step is performed to virtually restore the unmodified mic signal. This CL has been ported from https://codereview.webrtc.org/2834643002/. Bug: webrtc:7494 Change-Id: I0df52b5d45a6bfa1efced980d8d6de5c5d9bed48 Reviewed-on: https://webrtc-review.googlesource.com/2685 Commit-Queue: Alessio Bazzica <alessiob@webrtc.org> Reviewed-by: Per Åhgren <peah@webrtc.org> Cr-Commit-Position: refs/heads/master@{#19992}
This commit is contained in:
parent
06319b7830
commit
ca90a552e9
@ -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",
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -14,12 +14,15 @@
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<float>& 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;
|
||||
|
||||
@ -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<int> vad_likelihood;
|
||||
rtc::Optional<int> ns_level;
|
||||
rtc::Optional<bool> use_refined_adaptive_filter;
|
||||
int initial_mic_level;
|
||||
bool simulate_mic_gain = false;
|
||||
rtc::Optional<int> 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<ChannelBufferWavWriter> 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_;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
154
modules/audio_processing/test/fake_recording_device.cc
Normal file
154
modules/audio_processing/test/fake_recording_device.cc
Normal file
@ -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 <algorithm>
|
||||
|
||||
#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<int>(level);
|
||||
}
|
||||
virtual ~FakeRecordingDeviceWorker() = default;
|
||||
virtual void ModifyBufferInt16(AudioFrame* buffer) = 0;
|
||||
virtual void ModifyBufferFloat(ChannelBuffer<float>* buffer) = 0;
|
||||
|
||||
protected:
|
||||
// Mic level to simulate.
|
||||
int mic_level_;
|
||||
// Optional mic level to undo.
|
||||
rtc::Optional<int> 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<float>* 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<int16_t>(static_cast<float>(data[i]) *
|
||||
mic_level_ / divisor)));
|
||||
}
|
||||
}
|
||||
void ModifyBufferFloat(ChannelBuffer<float>* 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<FakeRecordingDeviceIdentity>(initial_mic_level);
|
||||
break;
|
||||
case 1:
|
||||
worker_ = rtc::MakeUnique<FakeRecordingDeviceLinear>(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<float>* buffer) {
|
||||
RTC_DCHECK(worker_);
|
||||
worker_->ModifyBufferFloat(buffer);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
75
modules/audio_processing/test/fake_recording_device.h
Normal file
75
modules/audio_processing/test/fake_recording_device.h
Normal file
@ -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 <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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<float>* buffer);
|
||||
|
||||
private:
|
||||
// Fake recording device worker.
|
||||
std::unique_ptr<FakeRecordingDeviceWorker> worker_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // MODULES_AUDIO_PROCESSING_TEST_FAKE_RECORDING_DEVICE_H_
|
||||
231
modules/audio_processing/test/fake_recording_device_unittest.cc
Normal file
231
modules/audio_processing/test/fake_recording_device_unittest.cc
Normal file
@ -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 <cmath>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<int> kFakeRecDeviceKinds = {0, 1};
|
||||
|
||||
const std::vector<std::vector<float>> kTestMultiChannelSamples{
|
||||
std::vector<float>{-10.f, -1.f, -0.1f, 0.f, 0.1f, 1.f, 10.f}};
|
||||
|
||||
// Writes samples into ChannelBuffer<float>.
|
||||
void WritesDataIntoChannelBuffer(const std::vector<std::vector<float>>& data,
|
||||
ChannelBuffer<float>* 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<ChannelBuffer<float>> CreateChannelBufferWithData(
|
||||
const std::vector<std::vector<float>>& data) {
|
||||
auto buff =
|
||||
rtc::MakeUnique<ChannelBuffer<float>>(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<float>* prev,
|
||||
const ChannelBuffer<float>* 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<float>* src,
|
||||
const ChannelBuffer<float>* 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<const ChannelBuffer<float>> 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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -45,7 +45,6 @@ class WavBasedSimulator final : public AudioProcessingSimulator {
|
||||
const std::string& filename);
|
||||
|
||||
std::vector<SimulationEventType> call_chain_;
|
||||
int last_specified_microphone_level_ = 100;
|
||||
|
||||
RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(WavBasedSimulator);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user