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