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