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 "modules/audio_processing/logging/apm_data_dumper.h"
21 #include "rtc_base/checks.h"
22
23 namespace webrtc {
24 namespace {
25
26 constexpr int kFramesPerSecond = 100;
27
FrameEnergy(const AudioFrameView<const float> & audio)28 float FrameEnergy(const AudioFrameView<const float>& audio) {
29 float energy = 0.0f;
30 for (int k = 0; k < audio.num_channels(); ++k) {
31 float channel_energy =
32 std::accumulate(audio.channel(k).begin(), audio.channel(k).end(), 0.0f,
33 [](float a, float b) -> float { return a + b * b; });
34 energy = std::max(channel_energy, energy);
35 }
36 return energy;
37 }
38
EnergyToDbfs(float signal_energy,int num_samples)39 float EnergyToDbfs(float signal_energy, int num_samples) {
40 RTC_DCHECK_GE(signal_energy, 0.0f);
41 const float rms_square = signal_energy / num_samples;
42 constexpr float kMinDbfs = -90.30899869919436f;
43 if (rms_square <= 1.0f) {
44 return kMinDbfs;
45 }
46 return 10.0f * std::log10(rms_square) + kMinDbfs;
47 }
48
49 // Updates the noise floor with instant decay and slow attack. This tuning is
50 // specific for AGC2, so that (i) it can promptly increase the gain if the noise
51 // floor drops (instant decay) and (ii) in case of music or fast speech, due to
52 // which the noise floor can be overestimated, the gain reduction is slowed
53 // down.
SmoothNoiseFloorEstimate(float current_estimate,float new_estimate)54 float SmoothNoiseFloorEstimate(float current_estimate, float new_estimate) {
55 constexpr float kAttack = 0.5f;
56 if (current_estimate < new_estimate) {
57 // Attack phase.
58 return kAttack * new_estimate + (1.0f - kAttack) * current_estimate;
59 }
60 // Instant attack.
61 return new_estimate;
62 }
63
64 class NoiseFloorEstimator : public NoiseLevelEstimator {
65 public:
66 // Update the noise floor every 5 seconds.
67 static constexpr int kUpdatePeriodNumFrames = 500;
68 static_assert(kUpdatePeriodNumFrames >= 200,
69 "A too small value may cause noise level overestimation.");
70 static_assert(kUpdatePeriodNumFrames <= 1500,
71 "A too large value may make AGC2 slow at reacting to increased "
72 "noise levels.");
73
NoiseFloorEstimator(ApmDataDumper * data_dumper)74 NoiseFloorEstimator(ApmDataDumper* data_dumper) : data_dumper_(data_dumper) {
75 // Initially assume that 48 kHz will be used. `Analyze()` will detect the
76 // used sample rate and call `Initialize()` again if needed.
77 Initialize(/*sample_rate_hz=*/48000);
78 }
79 NoiseFloorEstimator(const NoiseFloorEstimator&) = delete;
80 NoiseFloorEstimator& operator=(const NoiseFloorEstimator&) = delete;
81 ~NoiseFloorEstimator() = default;
82
Analyze(const AudioFrameView<const float> & frame)83 float Analyze(const AudioFrameView<const float>& frame) override {
84 // Detect sample rate changes.
85 const int sample_rate_hz =
86 static_cast<int>(frame.samples_per_channel() * kFramesPerSecond);
87 if (sample_rate_hz != sample_rate_hz_) {
88 Initialize(sample_rate_hz);
89 }
90
91 const float frame_energy = FrameEnergy(frame);
92 if (frame_energy <= min_noise_energy_) {
93 // Ignore frames when muted or below the minimum measurable energy.
94 data_dumper_->DumpRaw("agc2_noise_floor_estimator_preliminary_level",
95 noise_energy_);
96 return EnergyToDbfs(noise_energy_,
97 static_cast<int>(frame.samples_per_channel()));
98 }
99
100 if (preliminary_noise_energy_set_) {
101 preliminary_noise_energy_ =
102 std::min(preliminary_noise_energy_, frame_energy);
103 } else {
104 preliminary_noise_energy_ = frame_energy;
105 preliminary_noise_energy_set_ = true;
106 }
107 data_dumper_->DumpRaw("agc2_noise_floor_estimator_preliminary_level",
108 preliminary_noise_energy_);
109
110 if (counter_ == 0) {
111 // Full period observed.
112 first_period_ = false;
113 // Update the estimated noise floor energy with the preliminary
114 // estimation.
115 noise_energy_ = SmoothNoiseFloorEstimate(
116 /*current_estimate=*/noise_energy_,
117 /*new_estimate=*/preliminary_noise_energy_);
118 // Reset for a new observation period.
119 counter_ = kUpdatePeriodNumFrames;
120 preliminary_noise_energy_set_ = false;
121 } else if (first_period_) {
122 // While analyzing the signal during the initial period, continuously
123 // update the estimated noise energy, which is monotonic.
124 noise_energy_ = preliminary_noise_energy_;
125 counter_--;
126 } else {
127 // During the observation period it's only allowed to lower the energy.
128 noise_energy_ = std::min(noise_energy_, preliminary_noise_energy_);
129 counter_--;
130 }
131 return EnergyToDbfs(noise_energy_,
132 static_cast<int>(frame.samples_per_channel()));
133 }
134
135 private:
Initialize(int sample_rate_hz)136 void Initialize(int sample_rate_hz) {
137 sample_rate_hz_ = sample_rate_hz;
138 first_period_ = true;
139 preliminary_noise_energy_set_ = false;
140 // Initialize the minimum noise energy to -84 dBFS.
141 min_noise_energy_ = sample_rate_hz * 2.0f * 2.0f / kFramesPerSecond;
142 preliminary_noise_energy_ = min_noise_energy_;
143 noise_energy_ = min_noise_energy_;
144 counter_ = kUpdatePeriodNumFrames;
145 }
146
147 ApmDataDumper* const data_dumper_;
148 int sample_rate_hz_;
149 float min_noise_energy_;
150 bool first_period_;
151 bool preliminary_noise_energy_set_;
152 float preliminary_noise_energy_;
153 float noise_energy_;
154 int counter_;
155 };
156
157 } // namespace
158
CreateNoiseFloorEstimator(ApmDataDumper * data_dumper)159 std::unique_ptr<NoiseLevelEstimator> CreateNoiseFloorEstimator(
160 ApmDataDumper* data_dumper) {
161 return std::make_unique<NoiseFloorEstimator>(data_dumper);
162 }
163
164 } // namespace webrtc
165