tor-browser

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

audio_processing_performance_unittest.cc (19625B)


      1 /*
      2 *  Copyright (c) 2015 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 <atomic>
     12 #include <cstddef>
     13 #include <cstdint>
     14 #include <cstdio>
     15 #include <memory>
     16 #include <string>
     17 #include <vector>
     18 
     19 #include "absl/strings/string_view.h"
     20 #include "api/audio/audio_processing.h"
     21 #include "api/audio/builtin_audio_processing_builder.h"
     22 #include "api/environment/environment_factory.h"
     23 #include "api/numerics/samples_stats_counter.h"
     24 #include "api/scoped_refptr.h"
     25 #include "api/test/metrics/global_metrics_logger_and_exporter.h"
     26 #include "api/test/metrics/metric.h"
     27 #include "api/units/time_delta.h"
     28 #include "rtc_base/checks.h"
     29 #include "rtc_base/event.h"
     30 #include "rtc_base/platform_thread.h"
     31 #include "rtc_base/random.h"
     32 #include "system_wrappers/include/clock.h"
     33 #include "test/gtest.h"
     34 
     35 namespace webrtc {
     36 namespace {
     37 
     38 using test::GetGlobalMetricsLogger;
     39 using test::ImprovementDirection;
     40 using test::Metric;
     41 using test::Unit;
     42 
     43 class CallSimulator;
     44 
     45 // Type of the render thread APM API call to use in the test.
     46 enum class ProcessorType { kRender, kCapture };
     47 
     48 // Variant of APM processing settings to use in the test.
     49 enum class SettingsType {
     50  kDefaultApmDesktop,
     51  kDefaultApmMobile,
     52  kAllSubmodulesTurnedOff,
     53  kDefaultApmDesktopWithoutDelayAgnostic,
     54  kDefaultApmDesktopWithoutExtendedFilter
     55 };
     56 
     57 // Variables related to the audio data and formats.
     58 struct AudioFrameData {
     59  explicit AudioFrameData(size_t max_frame_size) {
     60    // Set up the two-dimensional arrays needed for the APM API calls.
     61    input_framechannels.resize(2 * max_frame_size);
     62    input_frame.resize(2);
     63    input_frame[0] = &input_framechannels[0];
     64    input_frame[1] = &input_framechannels[max_frame_size];
     65 
     66    output_frame_channels.resize(2 * max_frame_size);
     67    output_frame.resize(2);
     68    output_frame[0] = &output_frame_channels[0];
     69    output_frame[1] = &output_frame_channels[max_frame_size];
     70  }
     71 
     72  std::vector<float> output_frame_channels;
     73  std::vector<float*> output_frame;
     74  std::vector<float> input_framechannels;
     75  std::vector<float*> input_frame;
     76  StreamConfig input_stream_config;
     77  StreamConfig output_stream_config;
     78 };
     79 
     80 // The configuration for the test.
     81 struct SimulationConfig {
     82  SimulationConfig(int sample_rate_hz, SettingsType simulation_settings)
     83      : sample_rate_hz(sample_rate_hz),
     84        simulation_settings(simulation_settings) {}
     85 
     86  static std::vector<SimulationConfig> GenerateSimulationConfigs() {
     87    std::vector<SimulationConfig> simulation_configs;
     88 #ifndef WEBRTC_ANDROID
     89    const SettingsType desktop_settings[] = {
     90        SettingsType::kDefaultApmDesktop, SettingsType::kAllSubmodulesTurnedOff,
     91        SettingsType::kDefaultApmDesktopWithoutDelayAgnostic,
     92        SettingsType::kDefaultApmDesktopWithoutExtendedFilter};
     93 
     94    const int desktop_sample_rates[] = {8000, 16000, 32000, 48000};
     95 
     96    for (auto sample_rate : desktop_sample_rates) {
     97      for (auto settings : desktop_settings) {
     98        simulation_configs.push_back(SimulationConfig(sample_rate, settings));
     99      }
    100    }
    101 #endif
    102 
    103    const SettingsType mobile_settings[] = {SettingsType::kDefaultApmMobile};
    104 
    105    const int mobile_sample_rates[] = {8000, 16000};
    106 
    107    for (auto sample_rate : mobile_sample_rates) {
    108      for (auto settings : mobile_settings) {
    109        simulation_configs.push_back(SimulationConfig(sample_rate, settings));
    110      }
    111    }
    112 
    113    return simulation_configs;
    114  }
    115 
    116  std::string SettingsDescription() const {
    117    std::string description;
    118    switch (simulation_settings) {
    119      case SettingsType::kDefaultApmMobile:
    120        description = "DefaultApmMobile";
    121        break;
    122      case SettingsType::kDefaultApmDesktop:
    123        description = "DefaultApmDesktop";
    124        break;
    125      case SettingsType::kAllSubmodulesTurnedOff:
    126        description = "AllSubmodulesOff";
    127        break;
    128      case SettingsType::kDefaultApmDesktopWithoutDelayAgnostic:
    129        description = "DefaultApmDesktopWithoutDelayAgnostic";
    130        break;
    131      case SettingsType::kDefaultApmDesktopWithoutExtendedFilter:
    132        description = "DefaultApmDesktopWithoutExtendedFilter";
    133        break;
    134    }
    135    return description;
    136  }
    137 
    138  int sample_rate_hz = 16000;
    139  SettingsType simulation_settings = SettingsType::kDefaultApmDesktop;
    140 };
    141 
    142 // Handler for the frame counters.
    143 class FrameCounters {
    144 public:
    145  void IncreaseRenderCounter() { render_count_.fetch_add(1); }
    146 
    147  void IncreaseCaptureCounter() { capture_count_.fetch_add(1); }
    148 
    149  int CaptureMinusRenderCounters() const {
    150    // The return value will be approximate, but that's good enough since
    151    // by the time we return the value, it's not guaranteed to be correct
    152    // anyway.
    153    return capture_count_.load(std::memory_order_acquire) -
    154           render_count_.load(std::memory_order_acquire);
    155  }
    156 
    157  int RenderMinusCaptureCounters() const {
    158    return -CaptureMinusRenderCounters();
    159  }
    160 
    161  bool BothCountersExceedeThreshold(int threshold) const {
    162    // TODO(tommi): We could use an event to signal this so that we don't need
    163    // to be polling from the main thread and possibly steal cycles.
    164    const int capture_count = capture_count_.load(std::memory_order_acquire);
    165    const int render_count = render_count_.load(std::memory_order_acquire);
    166    return (render_count > threshold && capture_count > threshold);
    167  }
    168 
    169 private:
    170  std::atomic<int> render_count_{0};
    171  std::atomic<int> capture_count_{0};
    172 };
    173 
    174 // Class that represents a flag that can only be raised.
    175 class LockedFlag {
    176 public:
    177  bool get_flag() const { return flag_.load(std::memory_order_acquire); }
    178 
    179  void set_flag() {
    180    if (!get_flag()) {
    181      // read-only operation to avoid affecting the cache-line.
    182      int zero = 0;
    183      flag_.compare_exchange_strong(zero, 1);
    184    }
    185  }
    186 
    187 private:
    188  std::atomic<int> flag_{0};
    189 };
    190 
    191 // Parent class for the thread processors.
    192 class TimedThreadApiProcessor {
    193 public:
    194  TimedThreadApiProcessor(ProcessorType processor_type,
    195                          Random* rand_gen,
    196                          FrameCounters* shared_counters_state,
    197                          LockedFlag* capture_call_checker,
    198                          CallSimulator* test_framework,
    199                          const SimulationConfig* simulation_config,
    200                          AudioProcessing* apm,
    201                          int num_durations_to_store,
    202                          float input_level,
    203                          int num_channels)
    204      : rand_gen_(rand_gen),
    205        frame_counters_(shared_counters_state),
    206        capture_call_checker_(capture_call_checker),
    207        test_(test_framework),
    208        simulation_config_(simulation_config),
    209        apm_(apm),
    210        frame_data_(kMaxFrameSize),
    211        clock_(Clock::GetRealTimeClock()),
    212        num_durations_to_store_(num_durations_to_store),
    213        api_call_durations_(num_durations_to_store_ - kNumInitializationFrames),
    214        samples_count_(0),
    215        input_level_(input_level),
    216        processor_type_(processor_type),
    217        num_channels_(num_channels) {}
    218 
    219  // Implements the callback functionality for the threads.
    220  bool Process();
    221 
    222  // Method for printing out the simulation statistics.
    223  void print_processor_statistics(absl::string_view processor_name) const {
    224    const std::string modifier = "_api_call_duration";
    225 
    226    const std::string sample_rate_name =
    227        "_" + std::to_string(simulation_config_->sample_rate_hz) + "Hz";
    228 
    229    GetGlobalMetricsLogger()->LogMetric(
    230        "apm_timing" + sample_rate_name, processor_name, api_call_durations_,
    231        Unit::kMilliseconds, ImprovementDirection::kNeitherIsBetter);
    232  }
    233 
    234  void AddDuration(int64_t duration) {
    235    if (samples_count_ >= kNumInitializationFrames &&
    236        samples_count_ < num_durations_to_store_) {
    237      api_call_durations_.AddSample({.value = static_cast<double>(duration),
    238                                     .time = clock_->CurrentTime()});
    239    }
    240    samples_count_++;
    241  }
    242 
    243 private:
    244  static const int kMaxCallDifference = 10;
    245  static const int kMaxFrameSize = 480;
    246  static const int kNumInitializationFrames = 5;
    247 
    248  int ProcessCapture() {
    249    // Set the stream delay.
    250    apm_->set_stream_delay_ms(30);
    251 
    252    // Call and time the specified capture side API processing method.
    253    const int64_t start_time = clock_->TimeInMicroseconds();
    254    const int result = apm_->ProcessStream(
    255        &frame_data_.input_frame[0], frame_data_.input_stream_config,
    256        frame_data_.output_stream_config, &frame_data_.output_frame[0]);
    257    const int64_t end_time = clock_->TimeInMicroseconds();
    258 
    259    frame_counters_->IncreaseCaptureCounter();
    260 
    261    AddDuration(end_time - start_time);
    262 
    263    if (first_process_call_) {
    264      // Flag that the capture side has been called at least once
    265      // (needed to ensure that a capture call has been done
    266      // before the first render call is performed (implicitly
    267      // required by the APM API).
    268      capture_call_checker_->set_flag();
    269      first_process_call_ = false;
    270    }
    271    return result;
    272  }
    273 
    274  bool ReadyToProcessCapture() {
    275    return (frame_counters_->CaptureMinusRenderCounters() <=
    276            kMaxCallDifference);
    277  }
    278 
    279  int ProcessRender() {
    280    // Call and time the specified render side API processing method.
    281    const int64_t start_time = clock_->TimeInMicroseconds();
    282    const int result = apm_->ProcessReverseStream(
    283        &frame_data_.input_frame[0], frame_data_.input_stream_config,
    284        frame_data_.output_stream_config, &frame_data_.output_frame[0]);
    285    const int64_t end_time = clock_->TimeInMicroseconds();
    286    frame_counters_->IncreaseRenderCounter();
    287 
    288    AddDuration(end_time - start_time);
    289 
    290    return result;
    291  }
    292 
    293  bool ReadyToProcessRender() {
    294    // Do not process until at least one capture call has been done.
    295    // (implicitly required by the APM API).
    296    if (first_process_call_ && !capture_call_checker_->get_flag()) {
    297      return false;
    298    }
    299 
    300    // Ensure that the number of render and capture calls do not differ too
    301    // much.
    302    if (frame_counters_->RenderMinusCaptureCounters() > kMaxCallDifference) {
    303      return false;
    304    }
    305 
    306    first_process_call_ = false;
    307    return true;
    308  }
    309 
    310  void PrepareFrame() {
    311    // Lambda function for populating a float multichannel audio frame
    312    // with random data.
    313    auto populate_audio_frame = [](float amplitude, size_t num_channels,
    314                                   size_t samples_per_channel, Random* rand_gen,
    315                                   float** frame) {
    316      for (size_t ch = 0; ch < num_channels; ch++) {
    317        for (size_t k = 0; k < samples_per_channel; k++) {
    318          // Store random float number with a value between +-amplitude.
    319          frame[ch][k] = amplitude * (2 * rand_gen->Rand<float>() - 1);
    320        }
    321      }
    322    };
    323 
    324    // Prepare the audio input data and metadata.
    325    frame_data_.input_stream_config.set_sample_rate_hz(
    326        simulation_config_->sample_rate_hz);
    327    frame_data_.input_stream_config.set_num_channels(num_channels_);
    328    populate_audio_frame(input_level_, num_channels_,
    329                         (simulation_config_->sample_rate_hz *
    330                          AudioProcessing::kChunkSizeMs / 1000),
    331                         rand_gen_, &frame_data_.input_frame[0]);
    332 
    333    // Prepare the float audio output data and metadata.
    334    frame_data_.output_stream_config.set_sample_rate_hz(
    335        simulation_config_->sample_rate_hz);
    336    frame_data_.output_stream_config.set_num_channels(1);
    337  }
    338 
    339  bool ReadyToProcess() {
    340    switch (processor_type_) {
    341      case ProcessorType::kRender:
    342        return ReadyToProcessRender();
    343 
    344      case ProcessorType::kCapture:
    345        return ReadyToProcessCapture();
    346    }
    347 
    348    // Should not be reached, but the return statement is needed for the code to
    349    // build successfully on Android.
    350    RTC_DCHECK_NOTREACHED();
    351    return false;
    352  }
    353 
    354  Random* rand_gen_ = nullptr;
    355  FrameCounters* frame_counters_ = nullptr;
    356  LockedFlag* capture_call_checker_ = nullptr;
    357  CallSimulator* test_ = nullptr;
    358  const SimulationConfig* const simulation_config_ = nullptr;
    359  AudioProcessing* apm_ = nullptr;
    360  AudioFrameData frame_data_;
    361  Clock* clock_;
    362  const size_t num_durations_to_store_;
    363  SamplesStatsCounter api_call_durations_;
    364  size_t samples_count_ = 0;
    365  const float input_level_;
    366  bool first_process_call_ = true;
    367  const ProcessorType processor_type_;
    368  const int num_channels_ = 1;
    369 };
    370 
    371 // Class for managing the test simulation.
    372 class CallSimulator : public ::testing::TestWithParam<SimulationConfig> {
    373 public:
    374  CallSimulator()
    375      : rand_gen_(42U),
    376        simulation_config_(static_cast<SimulationConfig>(GetParam())) {}
    377 
    378  // Run the call simulation with a timeout.
    379  bool Run() {
    380    StartThreads();
    381 
    382    bool result = test_complete_.Wait(kTestTimeout);
    383 
    384    StopThreads();
    385 
    386    render_thread_state_->print_processor_statistics(
    387        simulation_config_.SettingsDescription() + "_render");
    388    capture_thread_state_->print_processor_statistics(
    389        simulation_config_.SettingsDescription() + "_capture");
    390 
    391    return result;
    392  }
    393 
    394  // Tests whether all the required render and capture side calls have been
    395  // done.
    396  bool MaybeEndTest() {
    397    if (frame_counters_.BothCountersExceedeThreshold(kMinNumFramesToProcess)) {
    398      test_complete_.Set();
    399      return true;
    400    }
    401    return false;
    402  }
    403 
    404 private:
    405  static const float kCaptureInputFloatLevel;
    406  static const float kRenderInputFloatLevel;
    407  static const int kMinNumFramesToProcess = 150;
    408  static constexpr TimeDelta kTestTimeout =
    409      TimeDelta::Millis(3 * 10 * kMinNumFramesToProcess);
    410 
    411  // Stop all running threads.
    412  void StopThreads() {
    413    render_thread_.Finalize();
    414    capture_thread_.Finalize();
    415  }
    416 
    417  // Simulator and APM setup.
    418  void SetUp() override {
    419    // Lambda function for setting the default APM runtime settings for desktop.
    420    auto set_default_desktop_apm_runtime_settings = [](AudioProcessing* apm) {
    421      AudioProcessing::Config apm_config = apm->GetConfig();
    422      apm_config.echo_canceller.enabled = true;
    423      apm_config.echo_canceller.mobile_mode = false;
    424      apm_config.noise_suppression.enabled = true;
    425      apm_config.gain_controller1.enabled = true;
    426      apm_config.gain_controller1.mode =
    427          AudioProcessing::Config::GainController1::kAdaptiveDigital;
    428      apm->ApplyConfig(apm_config);
    429    };
    430 
    431    // Lambda function for setting the default APM runtime settings for mobile.
    432    auto set_default_mobile_apm_runtime_settings = [](AudioProcessing* apm) {
    433      AudioProcessing::Config apm_config = apm->GetConfig();
    434      apm_config.echo_canceller.enabled = true;
    435      apm_config.echo_canceller.mobile_mode = true;
    436      apm_config.noise_suppression.enabled = true;
    437      apm_config.gain_controller1.mode =
    438          AudioProcessing::Config::GainController1::kAdaptiveDigital;
    439      apm->ApplyConfig(apm_config);
    440    };
    441 
    442    // Lambda function for turning off all of the APM runtime settings
    443    // submodules.
    444    auto turn_off_default_apm_runtime_settings = [](AudioProcessing* apm) {
    445      AudioProcessing::Config apm_config = apm->GetConfig();
    446      apm_config.echo_canceller.enabled = false;
    447      apm_config.gain_controller1.enabled = false;
    448      apm_config.noise_suppression.enabled = false;
    449      apm->ApplyConfig(apm_config);
    450    };
    451 
    452    int num_capture_channels = 1;
    453    switch (simulation_config_.simulation_settings) {
    454      case SettingsType::kDefaultApmMobile: {
    455        apm_ = BuiltinAudioProcessingBuilder().Build(CreateEnvironment());
    456        ASSERT_TRUE(!!apm_);
    457        set_default_mobile_apm_runtime_settings(apm_.get());
    458        break;
    459      }
    460      case SettingsType::kDefaultApmDesktop: {
    461        apm_ = BuiltinAudioProcessingBuilder().Build(CreateEnvironment());
    462        ASSERT_TRUE(!!apm_);
    463        set_default_desktop_apm_runtime_settings(apm_.get());
    464        break;
    465      }
    466      case SettingsType::kAllSubmodulesTurnedOff: {
    467        apm_ = BuiltinAudioProcessingBuilder().Build(CreateEnvironment());
    468        ASSERT_TRUE(!!apm_);
    469        turn_off_default_apm_runtime_settings(apm_.get());
    470        break;
    471      }
    472      case SettingsType::kDefaultApmDesktopWithoutDelayAgnostic: {
    473        apm_ = BuiltinAudioProcessingBuilder().Build(CreateEnvironment());
    474        ASSERT_TRUE(!!apm_);
    475        set_default_desktop_apm_runtime_settings(apm_.get());
    476        break;
    477      }
    478      case SettingsType::kDefaultApmDesktopWithoutExtendedFilter: {
    479        apm_ = BuiltinAudioProcessingBuilder().Build(CreateEnvironment());
    480        ASSERT_TRUE(!!apm_);
    481        set_default_desktop_apm_runtime_settings(apm_.get());
    482        break;
    483      }
    484    }
    485 
    486    render_thread_state_.reset(new TimedThreadApiProcessor(
    487        ProcessorType::kRender, &rand_gen_, &frame_counters_,
    488        &capture_call_checker_, this, &simulation_config_, apm_.get(),
    489        kMinNumFramesToProcess, kRenderInputFloatLevel, 1));
    490    capture_thread_state_.reset(new TimedThreadApiProcessor(
    491        ProcessorType::kCapture, &rand_gen_, &frame_counters_,
    492        &capture_call_checker_, this, &simulation_config_, apm_.get(),
    493        kMinNumFramesToProcess, kCaptureInputFloatLevel, num_capture_channels));
    494  }
    495 
    496  // Start the threads used in the test.
    497  void StartThreads() {
    498    const auto attributes =
    499        ThreadAttributes().SetPriority(ThreadPriority::kRealtime);
    500    render_thread_ = PlatformThread::SpawnJoinable(
    501        [this] {
    502          while (render_thread_state_->Process()) {
    503          }
    504        },
    505        "render", attributes);
    506    capture_thread_ = PlatformThread::SpawnJoinable(
    507        [this] {
    508          while (capture_thread_state_->Process()) {
    509          }
    510        },
    511        "capture", attributes);
    512  }
    513 
    514  // Event handler for the test.
    515  Event test_complete_;
    516 
    517  // Thread related variables.
    518  Random rand_gen_;
    519 
    520  scoped_refptr<AudioProcessing> apm_;
    521  const SimulationConfig simulation_config_;
    522  FrameCounters frame_counters_;
    523  LockedFlag capture_call_checker_;
    524  std::unique_ptr<TimedThreadApiProcessor> render_thread_state_;
    525  std::unique_ptr<TimedThreadApiProcessor> capture_thread_state_;
    526  PlatformThread render_thread_;
    527  PlatformThread capture_thread_;
    528 };
    529 
    530 // Implements the callback functionality for the threads.
    531 bool TimedThreadApiProcessor::Process() {
    532  PrepareFrame();
    533 
    534  // Wait in a spinlock manner until it is ok to start processing.
    535  // Note that SleepMs is not applicable since it only allows sleeping
    536  // on a millisecond basis which is too long.
    537  // TODO(tommi): This loop may affect the performance of the test that it's
    538  // meant to measure.  See if we could use events instead to signal readiness.
    539  while (!ReadyToProcess()) {
    540  }
    541 
    542  int result = AudioProcessing::kNoError;
    543  switch (processor_type_) {
    544    case ProcessorType::kRender:
    545      result = ProcessRender();
    546      break;
    547    case ProcessorType::kCapture:
    548      result = ProcessCapture();
    549      break;
    550  }
    551 
    552  EXPECT_EQ(result, AudioProcessing::kNoError);
    553 
    554  return !test_->MaybeEndTest();
    555 }
    556 
    557 const float CallSimulator::kRenderInputFloatLevel = 0.5f;
    558 const float CallSimulator::kCaptureInputFloatLevel = 0.03125f;
    559 }  // anonymous namespace
    560 
    561 TEST_P(CallSimulator, ApiCallDurationTest) {
    562  // Run test and verify that it did not time out.
    563  EXPECT_TRUE(Run());
    564 }
    565 
    566 INSTANTIATE_TEST_SUITE_P(
    567    AudioProcessingPerformanceTest,
    568    CallSimulator,
    569    ::testing::ValuesIn(SimulationConfig::GenerateSimulationConfigs()));
    570 
    571 }  // namespace webrtc