• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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