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