xref: /aosp_15_r20/external/webrtc/modules/audio_processing/agc2/noise_level_estimator.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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