| /* |
| * 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 "absl/memory/memory.h" |
| #include "absl/types/optional.h" |
| #include "modules/audio_processing/agc/gain_map_internal.h" |
| #include "rtc_base/logging.h" |
| #include "rtc_base/numerics/safe_conversions.h" |
| #include "rtc_base/numerics/safe_minmax.h" |
| |
| namespace webrtc { |
| namespace test { |
| |
| namespace { |
| |
| 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_ = 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. |
| absl::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] = rtc::saturated_cast<int16_t>(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] = |
| rtc::SafeClamp(buffer->channels()[c][i] * mic_level_ / divisor, |
| kFloatSampleMin, kFloatSampleMax); |
| } |
| } |
| } |
| }; |
| |
| float ComputeAgc1LinearFactor(const absl::optional<int>& undo_mic_level, |
| int mic_level) { |
| // If an undo level is specified, virtually restore the unmodified |
| // microphone level; otherwise simulate the mic gain only. |
| const int undo_level = |
| (undo_mic_level && *undo_mic_level > 0) ? *undo_mic_level : 100; |
| return DbToRatio(kGainMap[mic_level] - kGainMap[undo_level]); |
| } |
| |
| // Roughly dB-scale fake recording device. Valid levels are [0, 255]. The mic |
| // applies a gain from kGainMap in agc/gain_map_internal.h. |
| class FakeRecordingDeviceAgc1 final : public FakeRecordingDeviceWorker { |
| public: |
| explicit FakeRecordingDeviceAgc1(const int initial_mic_level) |
| : FakeRecordingDeviceWorker(initial_mic_level) {} |
| ~FakeRecordingDeviceAgc1() override = default; |
| void ModifyBufferInt16(AudioFrame* buffer) override { |
| const float scaling_factor = |
| ComputeAgc1LinearFactor(undo_mic_level_, mic_level_); |
| const size_t number_of_samples = |
| buffer->samples_per_channel_ * buffer->num_channels_; |
| int16_t* data = buffer->mutable_data(); |
| for (size_t i = 0; i < number_of_samples; ++i) { |
| data[i] = rtc::saturated_cast<int16_t>(data[i] * scaling_factor); |
| } |
| } |
| void ModifyBufferFloat(ChannelBuffer<float>* buffer) override { |
| const float scaling_factor = |
| ComputeAgc1LinearFactor(undo_mic_level_, mic_level_); |
| for (size_t c = 0; c < buffer->num_channels(); ++c) { |
| for (size_t i = 0; i < buffer->num_frames(); ++i) { |
| buffer->channels()[c][i] = |
| rtc::SafeClamp(buffer->channels()[c][i] * scaling_factor, |
| kFloatSampleMin, kFloatSampleMax); |
| } |
| } |
| } |
| }; |
| |
| } // namespace |
| |
| FakeRecordingDevice::FakeRecordingDevice(int initial_mic_level, |
| int device_kind) { |
| switch (device_kind) { |
| case 0: |
| worker_ = |
| absl::make_unique<FakeRecordingDeviceIdentity>(initial_mic_level); |
| break; |
| case 1: |
| worker_ = absl::make_unique<FakeRecordingDeviceLinear>(initial_mic_level); |
| break; |
| case 2: |
| worker_ = absl::make_unique<FakeRecordingDeviceAgc1>(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()) |
| RTC_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 |