tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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