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