1 /*
2 * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "modules/audio_processing/agc2/noise_level_estimator.h"
12
13 #include <stddef.h>
14
15 #include <algorithm>
16 #include <cmath>
17 #include <numeric>
18
19 #include "api/array_view.h"
20 #include "common_audio/include/audio_util.h"
21 #include "modules/audio_processing/logging/apm_data_dumper.h"
22 #include "rtc_base/checks.h"
23
24 namespace webrtc {
25
26 namespace {
27 constexpr int kFramesPerSecond = 100;
28
FrameEnergy(const AudioFrameView<const float> & audio)29 float FrameEnergy(const AudioFrameView<const float>& audio) {
30 float energy = 0.f;
31 for (size_t k = 0; k < audio.num_channels(); ++k) {
32 float channel_energy =
33 std::accumulate(audio.channel(k).begin(), audio.channel(k).end(), 0.f,
34 [](float a, float b) -> float { return a + b * b; });
35 energy = std::max(channel_energy, energy);
36 }
37 return energy;
38 }
39
EnergyToDbfs(float signal_energy,size_t num_samples)40 float EnergyToDbfs(float signal_energy, size_t num_samples) {
41 const float rms = std::sqrt(signal_energy / num_samples);
42 return FloatS16ToDbfs(rms);
43 }
44 } // namespace
45
NoiseLevelEstimator(ApmDataDumper * data_dumper)46 NoiseLevelEstimator::NoiseLevelEstimator(ApmDataDumper* data_dumper)
47 : signal_classifier_(data_dumper) {
48 Initialize(48000);
49 }
50
~NoiseLevelEstimator()51 NoiseLevelEstimator::~NoiseLevelEstimator() {}
52
Initialize(int sample_rate_hz)53 void NoiseLevelEstimator::Initialize(int sample_rate_hz) {
54 sample_rate_hz_ = sample_rate_hz;
55 noise_energy_ = 1.f;
56 first_update_ = true;
57 min_noise_energy_ = sample_rate_hz * 2.f * 2.f / kFramesPerSecond;
58 noise_energy_hold_counter_ = 0;
59 signal_classifier_.Initialize(sample_rate_hz);
60 }
61
Analyze(const AudioFrameView<const float> & frame)62 float NoiseLevelEstimator::Analyze(const AudioFrameView<const float>& frame) {
63 const int rate =
64 static_cast<int>(frame.samples_per_channel() * kFramesPerSecond);
65 if (rate != sample_rate_hz_) {
66 Initialize(rate);
67 }
68 const float frame_energy = FrameEnergy(frame);
69 if (frame_energy <= 0.f) {
70 RTC_DCHECK_GE(frame_energy, 0.f);
71 return EnergyToDbfs(noise_energy_, frame.samples_per_channel());
72 }
73
74 if (first_update_) {
75 // Initialize the noise energy to the frame energy.
76 first_update_ = false;
77 return EnergyToDbfs(
78 noise_energy_ = std::max(frame_energy, min_noise_energy_),
79 frame.samples_per_channel());
80 }
81
82 const SignalClassifier::SignalType signal_type =
83 signal_classifier_.Analyze(frame.channel(0));
84
85 // Update the noise estimate in a minimum statistics-type manner.
86 if (signal_type == SignalClassifier::SignalType::kStationary) {
87 if (frame_energy > noise_energy_) {
88 // Leak the estimate upwards towards the frame energy if no recent
89 // downward update.
90 noise_energy_hold_counter_ = std::max(noise_energy_hold_counter_ - 1, 0);
91
92 if (noise_energy_hold_counter_ == 0) {
93 noise_energy_ = std::min(noise_energy_ * 1.01f, frame_energy);
94 }
95 } else {
96 // Update smoothly downwards with a limited maximum update magnitude.
97 noise_energy_ =
98 std::max(noise_energy_ * 0.9f,
99 noise_energy_ + 0.05f * (frame_energy - noise_energy_));
100 noise_energy_hold_counter_ = 1000;
101 }
102 } else {
103 // For a non-stationary signal, leak the estimate downwards in order to
104 // avoid estimate locking due to incorrect signal classification.
105 noise_energy_ = noise_energy_ * 0.99f;
106 }
107
108 // Ensure a minimum of the estimate.
109 return EnergyToDbfs(
110 noise_energy_ = std::max(noise_energy_, min_noise_energy_),
111 frame.samples_per_channel());
112 }
113
114 } // namespace webrtc
115