Bug: webrtc:7494 Change-Id: I880ef3991ade4e429ccde843571f069ede149c0e Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213342 Commit-Queue: Alessio Bazzica <alessiob@webrtc.org> Reviewed-by: Jesus de Vicente Pena <devicentepena@webrtc.org> Cr-Commit-Position: refs/heads/master@{#33604}
179 lines
6.7 KiB
C++
179 lines
6.7 KiB
C++
/*
|
|
* Copyright (c) 2018 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/agc2/vad_with_level.h"
|
|
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
#include "modules/audio_processing/agc2/agc2_common.h"
|
|
#include "modules/audio_processing/include/audio_frame_view.h"
|
|
#include "rtc_base/gunit.h"
|
|
#include "rtc_base/numerics/safe_compare.h"
|
|
#include "test/gmock.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
using ::testing::AnyNumber;
|
|
using ::testing::ReturnRoundRobin;
|
|
|
|
constexpr int kNoVadPeriodicReset =
|
|
kFrameDurationMs * (std::numeric_limits<int>::max() / kFrameDurationMs);
|
|
|
|
constexpr float kInstantAttack = 1.0f;
|
|
constexpr float kSlowAttack = 0.1f;
|
|
|
|
constexpr int kSampleRateHz = 8000;
|
|
|
|
class MockVad : public VadLevelAnalyzer::VoiceActivityDetector {
|
|
public:
|
|
MOCK_METHOD(void, Reset, (), (override));
|
|
MOCK_METHOD(float,
|
|
ComputeProbability,
|
|
(AudioFrameView<const float> frame),
|
|
(override));
|
|
};
|
|
|
|
// Creates a `VadLevelAnalyzer` injecting a mock VAD which repeatedly returns
|
|
// the next value from `speech_probabilities` until it reaches the end and will
|
|
// restart from the beginning.
|
|
std::unique_ptr<VadLevelAnalyzer> CreateVadLevelAnalyzerWithMockVad(
|
|
int vad_reset_period_ms,
|
|
float vad_probability_attack,
|
|
const std::vector<float>& speech_probabilities,
|
|
int expected_vad_reset_calls = 0) {
|
|
auto vad = std::make_unique<MockVad>();
|
|
EXPECT_CALL(*vad, ComputeProbability)
|
|
.Times(AnyNumber())
|
|
.WillRepeatedly(ReturnRoundRobin(speech_probabilities));
|
|
if (expected_vad_reset_calls >= 0) {
|
|
EXPECT_CALL(*vad, Reset).Times(expected_vad_reset_calls);
|
|
}
|
|
return std::make_unique<VadLevelAnalyzer>(
|
|
vad_reset_period_ms, vad_probability_attack, std::move(vad));
|
|
}
|
|
|
|
// 10 ms mono frame.
|
|
struct FrameWithView {
|
|
// Ctor. Initializes the frame samples with `value`.
|
|
FrameWithView(float value = 0.0f)
|
|
: channel0(samples.data()),
|
|
view(&channel0, /*num_channels=*/1, samples.size()) {
|
|
samples.fill(value);
|
|
}
|
|
std::array<float, kSampleRateHz / 100> samples;
|
|
const float* const channel0;
|
|
const AudioFrameView<const float> view;
|
|
};
|
|
|
|
TEST(AutomaticGainController2VadLevelAnalyzer, PeakLevelGreaterThanRmsLevel) {
|
|
// Handcrafted frame so that the average is lower than the peak value.
|
|
FrameWithView frame(1000.0f); // Constant frame.
|
|
frame.samples[10] = 2000.0f; // Except for one peak value.
|
|
|
|
// Compute audio frame levels (the VAD result is ignored).
|
|
VadLevelAnalyzer analyzer;
|
|
auto levels_and_vad_prob = analyzer.AnalyzeFrame(frame.view);
|
|
|
|
// Compare peak and RMS levels.
|
|
EXPECT_LT(levels_and_vad_prob.rms_dbfs, levels_and_vad_prob.peak_dbfs);
|
|
}
|
|
|
|
// Checks that the unprocessed and the smoothed speech probabilities match when
|
|
// instant attack is used.
|
|
TEST(AutomaticGainController2VadLevelAnalyzer, NoSpeechProbabilitySmoothing) {
|
|
const std::vector<float> speech_probabilities{0.709f, 0.484f, 0.882f, 0.167f,
|
|
0.44f, 0.525f, 0.858f, 0.314f,
|
|
0.653f, 0.965f, 0.413f, 0.0f};
|
|
auto analyzer = CreateVadLevelAnalyzerWithMockVad(
|
|
kNoVadPeriodicReset, kInstantAttack, speech_probabilities);
|
|
FrameWithView frame;
|
|
for (int i = 0; rtc::SafeLt(i, speech_probabilities.size()); ++i) {
|
|
SCOPED_TRACE(i);
|
|
EXPECT_EQ(speech_probabilities[i],
|
|
analyzer->AnalyzeFrame(frame.view).speech_probability);
|
|
}
|
|
}
|
|
|
|
// Checks that the smoothed speech probability does not instantly converge to
|
|
// the unprocessed one when slow attack is used.
|
|
TEST(AutomaticGainController2VadLevelAnalyzer,
|
|
SlowAttackSpeechProbabilitySmoothing) {
|
|
const std::vector<float> speech_probabilities{0.0f, 0.0f, 1.0f,
|
|
1.0f, 1.0f, 1.0f};
|
|
auto analyzer = CreateVadLevelAnalyzerWithMockVad(
|
|
kNoVadPeriodicReset, kSlowAttack, speech_probabilities);
|
|
FrameWithView frame;
|
|
float prev_probability = 0.0f;
|
|
for (int i = 0; rtc::SafeLt(i, speech_probabilities.size()); ++i) {
|
|
SCOPED_TRACE(i);
|
|
const float smoothed_probability =
|
|
analyzer->AnalyzeFrame(frame.view).speech_probability;
|
|
EXPECT_LT(smoothed_probability, 1.0f); // Not enough time to reach 1.
|
|
EXPECT_LE(prev_probability, smoothed_probability); // Converge towards 1.
|
|
prev_probability = smoothed_probability;
|
|
}
|
|
}
|
|
|
|
// Checks that the smoothed speech probability instantly decays to the
|
|
// unprocessed one when slow attack is used.
|
|
TEST(AutomaticGainController2VadLevelAnalyzer, SpeechProbabilityInstantDecay) {
|
|
const std::vector<float> speech_probabilities{1.0f, 1.0f, 1.0f,
|
|
1.0f, 1.0f, 0.0f};
|
|
auto analyzer = CreateVadLevelAnalyzerWithMockVad(
|
|
kNoVadPeriodicReset, kSlowAttack, speech_probabilities);
|
|
FrameWithView frame;
|
|
for (int i = 0; rtc::SafeLt(i, speech_probabilities.size() - 1); ++i) {
|
|
analyzer->AnalyzeFrame(frame.view);
|
|
}
|
|
EXPECT_EQ(0.0f, analyzer->AnalyzeFrame(frame.view).speech_probability);
|
|
}
|
|
|
|
// Checks that the VAD is not periodically reset.
|
|
TEST(AutomaticGainController2VadLevelAnalyzer, VadNoPeriodicReset) {
|
|
constexpr int kNumFrames = 19;
|
|
auto analyzer = CreateVadLevelAnalyzerWithMockVad(
|
|
kNoVadPeriodicReset, kSlowAttack, /*speech_probabilities=*/{1.0f},
|
|
/*expected_vad_reset_calls=*/0);
|
|
FrameWithView frame;
|
|
for (int i = 0; i < kNumFrames; ++i) {
|
|
analyzer->AnalyzeFrame(frame.view);
|
|
}
|
|
}
|
|
|
|
class VadPeriodResetParametrization
|
|
: public ::testing::TestWithParam<std::tuple<int, int>> {
|
|
protected:
|
|
int num_frames() const { return std::get<0>(GetParam()); }
|
|
int vad_reset_period_frames() const { return std::get<1>(GetParam()); }
|
|
};
|
|
|
|
// Checks that the VAD is periodically reset with the expected period.
|
|
TEST_P(VadPeriodResetParametrization, VadPeriodicReset) {
|
|
auto analyzer = CreateVadLevelAnalyzerWithMockVad(
|
|
/*vad_reset_period_ms=*/vad_reset_period_frames() * kFrameDurationMs,
|
|
kSlowAttack, /*speech_probabilities=*/{1.0f},
|
|
/*expected_vad_reset_calls=*/num_frames() / vad_reset_period_frames());
|
|
FrameWithView frame;
|
|
for (int i = 0; i < num_frames(); ++i) {
|
|
analyzer->AnalyzeFrame(frame.view);
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(AutomaticGainController2VadLevelAnalyzer,
|
|
VadPeriodResetParametrization,
|
|
::testing::Combine(::testing::Values(1, 19, 123),
|
|
::testing::Values(2, 5, 20, 53)));
|
|
|
|
} // namespace
|
|
} // namespace webrtc
|