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