aboutsummaryrefslogtreecommitdiff
path: root/webrtc/modules/audio_processing/audio_processing_impl_locking_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webrtc/modules/audio_processing/audio_processing_impl_locking_unittest.cc')
-rw-r--r--webrtc/modules/audio_processing/audio_processing_impl_locking_unittest.cc1133
1 files changed, 1133 insertions, 0 deletions
diff --git a/webrtc/modules/audio_processing/audio_processing_impl_locking_unittest.cc b/webrtc/modules/audio_processing/audio_processing_impl_locking_unittest.cc
new file mode 100644
index 0000000000..e1e6a310a5
--- /dev/null
+++ b/webrtc/modules/audio_processing/audio_processing_impl_locking_unittest.cc
@@ -0,0 +1,1133 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "webrtc/modules/audio_processing/audio_processing_impl.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webrtc/base/array_view.h"
+#include "webrtc/base/criticalsection.h"
+#include "webrtc/base/event.h"
+#include "webrtc/base/platform_thread.h"
+#include "webrtc/base/random.h"
+#include "webrtc/config.h"
+#include "webrtc/modules/audio_processing/test/test_utils.h"
+#include "webrtc/modules/include/module_common_types.h"
+#include "webrtc/system_wrappers/include/sleep.h"
+
+namespace webrtc {
+
+namespace {
+
+class AudioProcessingImplLockTest;
+
+// Type of the render thread APM API call to use in the test.
+enum class RenderApiImpl {
+ ProcessReverseStreamImpl1,
+ ProcessReverseStreamImpl2,
+ AnalyzeReverseStreamImpl1,
+ AnalyzeReverseStreamImpl2
+};
+
+// Type of the capture thread APM API call to use in the test.
+enum class CaptureApiImpl {
+ ProcessStreamImpl1,
+ ProcessStreamImpl2,
+ ProcessStreamImpl3
+};
+
+// The runtime parameter setting scheme to use in the test.
+enum class RuntimeParameterSettingScheme {
+ SparseStreamMetadataChangeScheme,
+ ExtremeStreamMetadataChangeScheme,
+ FixedMonoStreamMetadataScheme,
+ FixedStereoStreamMetadataScheme
+};
+
+// Variant of echo canceller settings to use in the test.
+enum class AecType {
+ BasicWebRtcAecSettings,
+ AecTurnedOff,
+ BasicWebRtcAecSettingsWithExtentedFilter,
+ BasicWebRtcAecSettingsWithDelayAgnosticAec,
+ BasicWebRtcAecSettingsWithAecMobile
+};
+
+// Thread-safe random number generator wrapper.
+class RandomGenerator {
+ public:
+ RandomGenerator() : rand_gen_(42U) {}
+
+ int RandInt(int min, int max) {
+ rtc::CritScope cs(&crit_);
+ return rand_gen_.Rand(min, max);
+ }
+
+ int RandInt(int max) {
+ rtc::CritScope cs(&crit_);
+ return rand_gen_.Rand(max);
+ }
+
+ float RandFloat() {
+ rtc::CritScope cs(&crit_);
+ return rand_gen_.Rand<float>();
+ }
+
+ private:
+ rtc::CriticalSection crit_;
+ Random rand_gen_ GUARDED_BY(crit_);
+};
+
+// Variables related to the audio data and formats.
+struct AudioFrameData {
+ explicit AudioFrameData(int max_frame_size) {
+ // Set up the two-dimensional arrays needed for the APM API calls.
+ input_framechannels.resize(2 * max_frame_size);
+ input_frame.resize(2);
+ input_frame[0] = &input_framechannels[0];
+ input_frame[1] = &input_framechannels[max_frame_size];
+
+ output_frame_channels.resize(2 * max_frame_size);
+ output_frame.resize(2);
+ output_frame[0] = &output_frame_channels[0];
+ output_frame[1] = &output_frame_channels[max_frame_size];
+ }
+
+ AudioFrame frame;
+ std::vector<float*> output_frame;
+ std::vector<float> output_frame_channels;
+ AudioProcessing::ChannelLayout output_channel_layout =
+ AudioProcessing::ChannelLayout::kMono;
+ int input_sample_rate_hz = 16000;
+ int input_number_of_channels = -1;
+ std::vector<float*> input_frame;
+ std::vector<float> input_framechannels;
+ AudioProcessing::ChannelLayout input_channel_layout =
+ AudioProcessing::ChannelLayout::kMono;
+ int output_sample_rate_hz = 16000;
+ int output_number_of_channels = -1;
+ StreamConfig input_stream_config;
+ StreamConfig output_stream_config;
+ int input_samples_per_channel = -1;
+ int output_samples_per_channel = -1;
+};
+
+// The configuration for the test.
+struct TestConfig {
+ // Test case generator for the test configurations to use in the brief tests.
+ static std::vector<TestConfig> GenerateBriefTestConfigs() {
+ std::vector<TestConfig> test_configs;
+ AecType aec_types[] = {AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec,
+ AecType::BasicWebRtcAecSettingsWithAecMobile};
+ for (auto aec_type : aec_types) {
+ TestConfig test_config;
+ test_config.aec_type = aec_type;
+
+ test_config.min_number_of_calls = 300;
+
+ // Perform tests only with the extreme runtime parameter setting scheme.
+ test_config.runtime_parameter_setting_scheme =
+ RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme;
+
+ // Only test 16 kHz for this test suite.
+ test_config.initial_sample_rate_hz = 16000;
+
+ // Create test config for the second processing API function set.
+ test_config.render_api_function =
+ RenderApiImpl::ProcessReverseStreamImpl2;
+ test_config.capture_api_function = CaptureApiImpl::ProcessStreamImpl2;
+
+ // Create test config for the first processing API function set.
+ test_configs.push_back(test_config);
+ test_config.render_api_function =
+ RenderApiImpl::AnalyzeReverseStreamImpl2;
+ test_config.capture_api_function = CaptureApiImpl::ProcessStreamImpl3;
+ test_configs.push_back(test_config);
+ }
+
+ // Return the created test configurations.
+ return test_configs;
+ }
+
+ // Test case generator for the test configurations to use in the extensive
+ // tests.
+ static std::vector<TestConfig> GenerateExtensiveTestConfigs() {
+ // Lambda functions for the test config generation.
+ auto add_processing_apis = [](TestConfig test_config) {
+ struct AllowedApiCallCombinations {
+ RenderApiImpl render_api;
+ CaptureApiImpl capture_api;
+ };
+
+ const AllowedApiCallCombinations api_calls[] = {
+ {RenderApiImpl::ProcessReverseStreamImpl1,
+ CaptureApiImpl::ProcessStreamImpl1},
+ {RenderApiImpl::AnalyzeReverseStreamImpl1,
+ CaptureApiImpl::ProcessStreamImpl1},
+ {RenderApiImpl::ProcessReverseStreamImpl2,
+ CaptureApiImpl::ProcessStreamImpl2},
+ {RenderApiImpl::ProcessReverseStreamImpl2,
+ CaptureApiImpl::ProcessStreamImpl3},
+ {RenderApiImpl::AnalyzeReverseStreamImpl2,
+ CaptureApiImpl::ProcessStreamImpl2},
+ {RenderApiImpl::AnalyzeReverseStreamImpl2,
+ CaptureApiImpl::ProcessStreamImpl3}};
+ std::vector<TestConfig> out;
+ for (auto api_call : api_calls) {
+ test_config.render_api_function = api_call.render_api;
+ test_config.capture_api_function = api_call.capture_api;
+ out.push_back(test_config);
+ }
+ return out;
+ };
+
+ auto add_aec_settings = [](const std::vector<TestConfig>& in) {
+ std::vector<TestConfig> out;
+ AecType aec_types[] = {
+ AecType::BasicWebRtcAecSettings, AecType::AecTurnedOff,
+ AecType::BasicWebRtcAecSettingsWithExtentedFilter,
+ AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec,
+ AecType::BasicWebRtcAecSettingsWithAecMobile};
+ for (auto test_config : in) {
+ for (auto aec_type : aec_types) {
+ test_config.aec_type = aec_type;
+ out.push_back(test_config);
+ }
+ }
+ return out;
+ };
+
+ auto add_settings_scheme = [](const std::vector<TestConfig>& in) {
+ std::vector<TestConfig> out;
+ RuntimeParameterSettingScheme schemes[] = {
+ RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme,
+ RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme,
+ RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme,
+ RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme};
+
+ for (auto test_config : in) {
+ for (auto scheme : schemes) {
+ test_config.runtime_parameter_setting_scheme = scheme;
+ out.push_back(test_config);
+ }
+ }
+ return out;
+ };
+
+ auto add_sample_rates = [](const std::vector<TestConfig>& in) {
+ const int sample_rates[] = {8000, 16000, 32000, 48000};
+
+ std::vector<TestConfig> out;
+ for (auto test_config : in) {
+ auto available_rates =
+ (test_config.aec_type ==
+ AecType::BasicWebRtcAecSettingsWithAecMobile
+ ? rtc::ArrayView<const int>(sample_rates, 2)
+ : rtc::ArrayView<const int>(sample_rates));
+
+ for (auto rate : available_rates) {
+ test_config.initial_sample_rate_hz = rate;
+ out.push_back(test_config);
+ }
+ }
+ return out;
+ };
+
+ // Generate test configurations of the relevant combinations of the
+ // parameters to
+ // test.
+ TestConfig test_config;
+ test_config.min_number_of_calls = 10000;
+ return add_sample_rates(add_settings_scheme(
+ add_aec_settings(add_processing_apis(test_config))));
+ }
+
+ RenderApiImpl render_api_function = RenderApiImpl::ProcessReverseStreamImpl2;
+ CaptureApiImpl capture_api_function = CaptureApiImpl::ProcessStreamImpl2;
+ RuntimeParameterSettingScheme runtime_parameter_setting_scheme =
+ RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme;
+ int initial_sample_rate_hz = 16000;
+ AecType aec_type = AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec;
+ int min_number_of_calls = 300;
+};
+
+// Handler for the frame counters.
+class FrameCounters {
+ public:
+ void IncreaseRenderCounter() {
+ rtc::CritScope cs(&crit_);
+ render_count++;
+ }
+
+ void IncreaseCaptureCounter() {
+ rtc::CritScope cs(&crit_);
+ capture_count++;
+ }
+
+ int GetCaptureCounter() const {
+ rtc::CritScope cs(&crit_);
+ return capture_count;
+ }
+
+ int GetRenderCounter() const {
+ rtc::CritScope cs(&crit_);
+ return render_count;
+ }
+
+ int CaptureMinusRenderCounters() const {
+ rtc::CritScope cs(&crit_);
+ return capture_count - render_count;
+ }
+
+ int RenderMinusCaptureCounters() const {
+ return -CaptureMinusRenderCounters();
+ }
+
+ bool BothCountersExceedeThreshold(int threshold) {
+ rtc::CritScope cs(&crit_);
+ return (render_count > threshold && capture_count > threshold);
+ }
+
+ private:
+ mutable rtc::CriticalSection crit_;
+ int render_count GUARDED_BY(crit_) = 0;
+ int capture_count GUARDED_BY(crit_) = 0;
+};
+
+// Class for handling the capture side processing.
+class CaptureProcessor {
+ public:
+ CaptureProcessor(int max_frame_size,
+ RandomGenerator* rand_gen,
+ rtc::Event* render_call_event,
+ rtc::Event* capture_call_event,
+ FrameCounters* shared_counters_state,
+ AudioProcessingImplLockTest* test_framework,
+ TestConfig* test_config,
+ AudioProcessing* apm);
+ bool Process();
+
+ private:
+ static const int kMaxCallDifference = 10;
+ static const float kCaptureInputFloatLevel;
+ static const int kCaptureInputFixLevel = 1024;
+
+ void PrepareFrame();
+ void CallApmCaptureSide();
+ void ApplyRuntimeSettingScheme();
+
+ RandomGenerator* const rand_gen_ = nullptr;
+ rtc::Event* const render_call_event_ = nullptr;
+ rtc::Event* const capture_call_event_ = nullptr;
+ FrameCounters* const frame_counters_ = nullptr;
+ AudioProcessingImplLockTest* const test_ = nullptr;
+ const TestConfig* const test_config_ = nullptr;
+ AudioProcessing* const apm_ = nullptr;
+ AudioFrameData frame_data_;
+};
+
+// Class for handling the stats processing.
+class StatsProcessor {
+ public:
+ StatsProcessor(RandomGenerator* rand_gen,
+ TestConfig* test_config,
+ AudioProcessing* apm);
+ bool Process();
+
+ private:
+ RandomGenerator* rand_gen_ = nullptr;
+ TestConfig* test_config_ = nullptr;
+ AudioProcessing* apm_ = nullptr;
+};
+
+// Class for handling the render side processing.
+class RenderProcessor {
+ public:
+ RenderProcessor(int max_frame_size,
+ RandomGenerator* rand_gen,
+ rtc::Event* render_call_event,
+ rtc::Event* capture_call_event,
+ FrameCounters* shared_counters_state,
+ AudioProcessingImplLockTest* test_framework,
+ TestConfig* test_config,
+ AudioProcessing* apm);
+ bool Process();
+
+ private:
+ static const int kMaxCallDifference = 10;
+ static const int kRenderInputFixLevel = 16384;
+ static const float kRenderInputFloatLevel;
+
+ void PrepareFrame();
+ void CallApmRenderSide();
+ void ApplyRuntimeSettingScheme();
+
+ RandomGenerator* const rand_gen_ = nullptr;
+ rtc::Event* const render_call_event_ = nullptr;
+ rtc::Event* const capture_call_event_ = nullptr;
+ FrameCounters* const frame_counters_ = nullptr;
+ AudioProcessingImplLockTest* const test_ = nullptr;
+ const TestConfig* const test_config_ = nullptr;
+ AudioProcessing* const apm_ = nullptr;
+ AudioFrameData frame_data_;
+ bool first_render_call_ = true;
+};
+
+class AudioProcessingImplLockTest
+ : public ::testing::TestWithParam<TestConfig> {
+ public:
+ AudioProcessingImplLockTest();
+ bool RunTest();
+ bool MaybeEndTest();
+
+ private:
+ static const int kTestTimeOutLimit = 10 * 60 * 1000;
+ static const int kMaxFrameSize = 480;
+
+ // ::testing::TestWithParam<> implementation
+ void SetUp() override;
+ void TearDown() override;
+
+ // Thread callback for the render thread
+ static bool RenderProcessorThreadFunc(void* context) {
+ return reinterpret_cast<AudioProcessingImplLockTest*>(context)
+ ->render_thread_state_.Process();
+ }
+
+ // Thread callback for the capture thread
+ static bool CaptureProcessorThreadFunc(void* context) {
+ return reinterpret_cast<AudioProcessingImplLockTest*>(context)
+ ->capture_thread_state_.Process();
+ }
+
+ // Thread callback for the stats thread
+ static bool StatsProcessorThreadFunc(void* context) {
+ return reinterpret_cast<AudioProcessingImplLockTest*>(context)
+ ->stats_thread_state_.Process();
+ }
+
+ // Tests whether all the required render and capture side calls have been
+ // done.
+ bool TestDone() {
+ return frame_counters_.BothCountersExceedeThreshold(
+ test_config_.min_number_of_calls);
+ }
+
+ // Start the threads used in the test.
+ void StartThreads() {
+ render_thread_.Start();
+ render_thread_.SetPriority(rtc::kRealtimePriority);
+ capture_thread_.Start();
+ capture_thread_.SetPriority(rtc::kRealtimePriority);
+ stats_thread_.Start();
+ stats_thread_.SetPriority(rtc::kNormalPriority);
+ }
+
+ // Event handlers for the test.
+ rtc::Event test_complete_;
+ rtc::Event render_call_event_;
+ rtc::Event capture_call_event_;
+
+ // Thread related variables.
+ rtc::PlatformThread render_thread_;
+ rtc::PlatformThread capture_thread_;
+ rtc::PlatformThread stats_thread_;
+ mutable RandomGenerator rand_gen_;
+
+ rtc::scoped_ptr<AudioProcessing> apm_;
+ TestConfig test_config_;
+ FrameCounters frame_counters_;
+ RenderProcessor render_thread_state_;
+ CaptureProcessor capture_thread_state_;
+ StatsProcessor stats_thread_state_;
+};
+
+// Sleeps a random time between 0 and max_sleep milliseconds.
+void SleepRandomMs(int max_sleep, RandomGenerator* rand_gen) {
+ int sleeptime = rand_gen->RandInt(0, max_sleep);
+ SleepMs(sleeptime);
+}
+
+// Populates a float audio frame with random data.
+void PopulateAudioFrame(float** frame,
+ float amplitude,
+ size_t num_channels,
+ size_t samples_per_channel,
+ RandomGenerator* rand_gen) {
+ for (size_t ch = 0; ch < num_channels; ch++) {
+ for (size_t k = 0; k < samples_per_channel; k++) {
+ // Store random 16 bit quantized float number between +-amplitude.
+ frame[ch][k] = amplitude * (2 * rand_gen->RandFloat() - 1);
+ }
+ }
+}
+
+// Populates an audioframe frame of AudioFrame type with random data.
+void PopulateAudioFrame(AudioFrame* frame,
+ int16_t amplitude,
+ RandomGenerator* rand_gen) {
+ ASSERT_GT(amplitude, 0);
+ ASSERT_LE(amplitude, 32767);
+ for (size_t ch = 0; ch < frame->num_channels_; ch++) {
+ for (size_t k = 0; k < frame->samples_per_channel_; k++) {
+ // Store random 16 bit number between -(amplitude+1) and
+ // amplitude.
+ frame->data_[k * ch] =
+ rand_gen->RandInt(2 * amplitude + 1) - amplitude - 1;
+ }
+ }
+}
+
+AudioProcessingImplLockTest::AudioProcessingImplLockTest()
+ : test_complete_(false, false),
+ render_call_event_(false, false),
+ capture_call_event_(false, false),
+ render_thread_(RenderProcessorThreadFunc, this, "render"),
+ capture_thread_(CaptureProcessorThreadFunc, this, "capture"),
+ stats_thread_(StatsProcessorThreadFunc, this, "stats"),
+ apm_(AudioProcessingImpl::Create()),
+ render_thread_state_(kMaxFrameSize,
+ &rand_gen_,
+ &render_call_event_,
+ &capture_call_event_,
+ &frame_counters_,
+ this,
+ &test_config_,
+ apm_.get()),
+ capture_thread_state_(kMaxFrameSize,
+ &rand_gen_,
+ &render_call_event_,
+ &capture_call_event_,
+ &frame_counters_,
+ this,
+ &test_config_,
+ apm_.get()),
+ stats_thread_state_(&rand_gen_, &test_config_, apm_.get()) {}
+
+// Run the test with a timeout.
+bool AudioProcessingImplLockTest::RunTest() {
+ StartThreads();
+ return test_complete_.Wait(kTestTimeOutLimit);
+}
+
+bool AudioProcessingImplLockTest::MaybeEndTest() {
+ if (HasFatalFailure() || TestDone()) {
+ test_complete_.Set();
+ return true;
+ }
+ return false;
+}
+
+// Setup of test and APM.
+void AudioProcessingImplLockTest::SetUp() {
+ test_config_ = static_cast<TestConfig>(GetParam());
+
+ ASSERT_EQ(apm_->kNoError, apm_->level_estimator()->Enable(true));
+ ASSERT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
+
+ ASSERT_EQ(apm_->kNoError,
+ apm_->gain_control()->set_mode(GainControl::kAdaptiveDigital));
+ ASSERT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true));
+
+ ASSERT_EQ(apm_->kNoError, apm_->noise_suppression()->Enable(true));
+ ASSERT_EQ(apm_->kNoError, apm_->voice_detection()->Enable(true));
+
+ Config config;
+ if (test_config_.aec_type == AecType::AecTurnedOff) {
+ ASSERT_EQ(apm_->kNoError, apm_->echo_control_mobile()->Enable(false));
+ ASSERT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(false));
+ } else if (test_config_.aec_type ==
+ AecType::BasicWebRtcAecSettingsWithAecMobile) {
+ ASSERT_EQ(apm_->kNoError, apm_->echo_control_mobile()->Enable(true));
+ ASSERT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(false));
+ } else {
+ ASSERT_EQ(apm_->kNoError, apm_->echo_control_mobile()->Enable(false));
+ ASSERT_EQ(apm_->kNoError, apm_->echo_cancellation()->Enable(true));
+ ASSERT_EQ(apm_->kNoError, apm_->echo_cancellation()->enable_metrics(true));
+ ASSERT_EQ(apm_->kNoError,
+ apm_->echo_cancellation()->enable_delay_logging(true));
+
+ config.Set<ExtendedFilter>(
+ new ExtendedFilter(test_config_.aec_type ==
+ AecType::BasicWebRtcAecSettingsWithExtentedFilter));
+
+ config.Set<DelayAgnostic>(
+ new DelayAgnostic(test_config_.aec_type ==
+ AecType::BasicWebRtcAecSettingsWithDelayAgnosticAec));
+
+ apm_->SetExtraOptions(config);
+ }
+}
+
+void AudioProcessingImplLockTest::TearDown() {
+ render_call_event_.Set();
+ capture_call_event_.Set();
+ render_thread_.Stop();
+ capture_thread_.Stop();
+ stats_thread_.Stop();
+}
+
+StatsProcessor::StatsProcessor(RandomGenerator* rand_gen,
+ TestConfig* test_config,
+ AudioProcessing* apm)
+ : rand_gen_(rand_gen), test_config_(test_config), apm_(apm) {}
+
+// Implements the callback functionality for the statistics
+// collection thread.
+bool StatsProcessor::Process() {
+ SleepRandomMs(100, rand_gen_);
+
+ EXPECT_EQ(apm_->echo_cancellation()->is_enabled(),
+ ((test_config_->aec_type != AecType::AecTurnedOff) &&
+ (test_config_->aec_type !=
+ AecType::BasicWebRtcAecSettingsWithAecMobile)));
+ apm_->echo_cancellation()->stream_drift_samples();
+ EXPECT_EQ(apm_->echo_control_mobile()->is_enabled(),
+ (test_config_->aec_type != AecType::AecTurnedOff) &&
+ (test_config_->aec_type ==
+ AecType::BasicWebRtcAecSettingsWithAecMobile));
+ EXPECT_TRUE(apm_->gain_control()->is_enabled());
+ apm_->gain_control()->stream_analog_level();
+ EXPECT_TRUE(apm_->noise_suppression()->is_enabled());
+
+ // The below return values are not testable.
+ apm_->noise_suppression()->speech_probability();
+ apm_->voice_detection()->is_enabled();
+
+ return true;
+}
+
+const float CaptureProcessor::kCaptureInputFloatLevel = 0.03125f;
+
+CaptureProcessor::CaptureProcessor(int max_frame_size,
+ RandomGenerator* rand_gen,
+ rtc::Event* render_call_event,
+ rtc::Event* capture_call_event,
+ FrameCounters* shared_counters_state,
+ AudioProcessingImplLockTest* test_framework,
+ TestConfig* test_config,
+ AudioProcessing* apm)
+ : rand_gen_(rand_gen),
+ render_call_event_(render_call_event),
+ capture_call_event_(capture_call_event),
+ frame_counters_(shared_counters_state),
+ test_(test_framework),
+ test_config_(test_config),
+ apm_(apm),
+ frame_data_(max_frame_size) {}
+
+// Implements the callback functionality for the capture thread.
+bool CaptureProcessor::Process() {
+ // Sleep a random time to simulate thread jitter.
+ SleepRandomMs(3, rand_gen_);
+
+ // Check whether the test is done.
+ if (test_->MaybeEndTest()) {
+ return false;
+ }
+
+ // Ensure that the number of render and capture calls do not
+ // differ too much.
+ if (frame_counters_->CaptureMinusRenderCounters() > kMaxCallDifference) {
+ render_call_event_->Wait(rtc::Event::kForever);
+ }
+
+ // Apply any specified capture side APM non-processing runtime calls.
+ ApplyRuntimeSettingScheme();
+
+ // Apply the capture side processing call.
+ CallApmCaptureSide();
+
+ // Increase the number of capture-side calls.
+ frame_counters_->IncreaseCaptureCounter();
+
+ // Flag to the render thread that another capture API call has occurred
+ // by triggering this threads call event.
+ capture_call_event_->Set();
+
+ return true;
+}
+
+// Prepares a frame with relevant audio data and metadata.
+void CaptureProcessor::PrepareFrame() {
+ // Restrict to a common fixed sample rate if the AudioFrame
+ // interface is used.
+ if (test_config_->capture_api_function ==
+ CaptureApiImpl::ProcessStreamImpl1) {
+ frame_data_.input_sample_rate_hz = test_config_->initial_sample_rate_hz;
+ frame_data_.output_sample_rate_hz = test_config_->initial_sample_rate_hz;
+ }
+
+ // Prepare the audioframe data and metadata.
+ frame_data_.input_samples_per_channel =
+ frame_data_.input_sample_rate_hz * AudioProcessing::kChunkSizeMs / 1000;
+ frame_data_.frame.sample_rate_hz_ = frame_data_.input_sample_rate_hz;
+ frame_data_.frame.num_channels_ = frame_data_.input_number_of_channels;
+ frame_data_.frame.samples_per_channel_ =
+ frame_data_.input_samples_per_channel;
+ PopulateAudioFrame(&frame_data_.frame, kCaptureInputFixLevel, rand_gen_);
+
+ // Prepare the float audio input data and metadata.
+ frame_data_.input_stream_config.set_sample_rate_hz(
+ frame_data_.input_sample_rate_hz);
+ frame_data_.input_stream_config.set_num_channels(
+ frame_data_.input_number_of_channels);
+ frame_data_.input_stream_config.set_has_keyboard(false);
+ PopulateAudioFrame(&frame_data_.input_frame[0], kCaptureInputFloatLevel,
+ frame_data_.input_number_of_channels,
+ frame_data_.input_samples_per_channel, rand_gen_);
+ frame_data_.input_channel_layout =
+ (frame_data_.input_number_of_channels == 1
+ ? AudioProcessing::ChannelLayout::kMono
+ : AudioProcessing::ChannelLayout::kStereo);
+
+ // Prepare the float audio output data and metadata.
+ frame_data_.output_samples_per_channel =
+ frame_data_.output_sample_rate_hz * AudioProcessing::kChunkSizeMs / 1000;
+ frame_data_.output_stream_config.set_sample_rate_hz(
+ frame_data_.output_sample_rate_hz);
+ frame_data_.output_stream_config.set_num_channels(
+ frame_data_.output_number_of_channels);
+ frame_data_.output_stream_config.set_has_keyboard(false);
+ frame_data_.output_channel_layout =
+ (frame_data_.output_number_of_channels == 1
+ ? AudioProcessing::ChannelLayout::kMono
+ : AudioProcessing::ChannelLayout::kStereo);
+}
+
+// Applies the capture side processing API call.
+void CaptureProcessor::CallApmCaptureSide() {
+ // Prepare a proper capture side processing API call input.
+ PrepareFrame();
+
+ // Set the stream delay
+ apm_->set_stream_delay_ms(30);
+
+ // Call the specified capture side API processing method.
+ int result = AudioProcessing::kNoError;
+ switch (test_config_->capture_api_function) {
+ case CaptureApiImpl::ProcessStreamImpl1:
+ result = apm_->ProcessStream(&frame_data_.frame);
+ break;
+ case CaptureApiImpl::ProcessStreamImpl2:
+ result = apm_->ProcessStream(
+ &frame_data_.input_frame[0], frame_data_.input_samples_per_channel,
+ frame_data_.input_sample_rate_hz, frame_data_.input_channel_layout,
+ frame_data_.output_sample_rate_hz, frame_data_.output_channel_layout,
+ &frame_data_.output_frame[0]);
+ break;
+ case CaptureApiImpl::ProcessStreamImpl3:
+ result = apm_->ProcessStream(
+ &frame_data_.input_frame[0], frame_data_.input_stream_config,
+ frame_data_.output_stream_config, &frame_data_.output_frame[0]);
+ break;
+ default:
+ FAIL();
+ }
+
+ // Check the return code for error.
+ ASSERT_EQ(AudioProcessing::kNoError, result);
+}
+
+// Applies any runtime capture APM API calls and audio stream characteristics
+// specified by the scheme for the test.
+void CaptureProcessor::ApplyRuntimeSettingScheme() {
+ const int capture_count_local = frame_counters_->GetCaptureCounter();
+
+ // Update the number of channels and sample rates for the input and output.
+ // Note that the counts frequencies for when to set parameters
+ // are set using prime numbers in order to ensure that the
+ // permutation scheme in the parameter setting changes.
+ switch (test_config_->runtime_parameter_setting_scheme) {
+ case RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme:
+ if (capture_count_local == 0)
+ frame_data_.input_sample_rate_hz = 16000;
+ else if (capture_count_local % 11 == 0)
+ frame_data_.input_sample_rate_hz = 32000;
+ else if (capture_count_local % 73 == 0)
+ frame_data_.input_sample_rate_hz = 48000;
+ else if (capture_count_local % 89 == 0)
+ frame_data_.input_sample_rate_hz = 16000;
+ else if (capture_count_local % 97 == 0)
+ frame_data_.input_sample_rate_hz = 8000;
+
+ if (capture_count_local == 0)
+ frame_data_.input_number_of_channels = 1;
+ else if (capture_count_local % 4 == 0)
+ frame_data_.input_number_of_channels =
+ (frame_data_.input_number_of_channels == 1 ? 2 : 1);
+
+ if (capture_count_local == 0)
+ frame_data_.output_sample_rate_hz = 16000;
+ else if (capture_count_local % 5 == 0)
+ frame_data_.output_sample_rate_hz = 32000;
+ else if (capture_count_local % 47 == 0)
+ frame_data_.output_sample_rate_hz = 48000;
+ else if (capture_count_local % 53 == 0)
+ frame_data_.output_sample_rate_hz = 16000;
+ else if (capture_count_local % 71 == 0)
+ frame_data_.output_sample_rate_hz = 8000;
+
+ if (capture_count_local == 0)
+ frame_data_.output_number_of_channels = 1;
+ else if (capture_count_local % 8 == 0)
+ frame_data_.output_number_of_channels =
+ (frame_data_.output_number_of_channels == 1 ? 2 : 1);
+ break;
+ case RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme:
+ if (capture_count_local % 2 == 0) {
+ frame_data_.input_number_of_channels = 1;
+ frame_data_.input_sample_rate_hz = 16000;
+ frame_data_.output_number_of_channels = 1;
+ frame_data_.output_sample_rate_hz = 16000;
+ } else {
+ frame_data_.input_number_of_channels =
+ (frame_data_.input_number_of_channels == 1 ? 2 : 1);
+ if (frame_data_.input_sample_rate_hz == 8000)
+ frame_data_.input_sample_rate_hz = 16000;
+ else if (frame_data_.input_sample_rate_hz == 16000)
+ frame_data_.input_sample_rate_hz = 32000;
+ else if (frame_data_.input_sample_rate_hz == 32000)
+ frame_data_.input_sample_rate_hz = 48000;
+ else if (frame_data_.input_sample_rate_hz == 48000)
+ frame_data_.input_sample_rate_hz = 8000;
+
+ frame_data_.output_number_of_channels =
+ (frame_data_.output_number_of_channels == 1 ? 2 : 1);
+ if (frame_data_.output_sample_rate_hz == 8000)
+ frame_data_.output_sample_rate_hz = 16000;
+ else if (frame_data_.output_sample_rate_hz == 16000)
+ frame_data_.output_sample_rate_hz = 32000;
+ else if (frame_data_.output_sample_rate_hz == 32000)
+ frame_data_.output_sample_rate_hz = 48000;
+ else if (frame_data_.output_sample_rate_hz == 48000)
+ frame_data_.output_sample_rate_hz = 8000;
+ }
+ break;
+ case RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme:
+ if (capture_count_local == 0) {
+ frame_data_.input_sample_rate_hz = 16000;
+ frame_data_.input_number_of_channels = 1;
+ frame_data_.output_sample_rate_hz = 16000;
+ frame_data_.output_number_of_channels = 1;
+ }
+ break;
+ case RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme:
+ if (capture_count_local == 0) {
+ frame_data_.input_sample_rate_hz = 16000;
+ frame_data_.input_number_of_channels = 2;
+ frame_data_.output_sample_rate_hz = 16000;
+ frame_data_.output_number_of_channels = 2;
+ }
+ break;
+ default:
+ FAIL();
+ }
+
+ // Call any specified runtime APM setter and
+ // getter calls.
+ switch (test_config_->runtime_parameter_setting_scheme) {
+ case RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme:
+ case RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme:
+ break;
+ case RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme:
+ case RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme:
+ if (capture_count_local % 2 == 0) {
+ ASSERT_EQ(AudioProcessing::Error::kNoError,
+ apm_->set_stream_delay_ms(30));
+ apm_->set_stream_key_pressed(true);
+ apm_->set_delay_offset_ms(15);
+ EXPECT_EQ(apm_->delay_offset_ms(), 15);
+ } else {
+ ASSERT_EQ(AudioProcessing::Error::kNoError,
+ apm_->set_stream_delay_ms(50));
+ apm_->set_stream_key_pressed(false);
+ apm_->set_delay_offset_ms(20);
+ EXPECT_EQ(apm_->delay_offset_ms(), 20);
+ apm_->delay_offset_ms();
+ }
+ break;
+ default:
+ FAIL();
+ }
+
+ // Restric the number of output channels not to exceed
+ // the number of input channels.
+ frame_data_.output_number_of_channels =
+ std::min(frame_data_.output_number_of_channels,
+ frame_data_.input_number_of_channels);
+}
+
+const float RenderProcessor::kRenderInputFloatLevel = 0.5f;
+
+RenderProcessor::RenderProcessor(int max_frame_size,
+ RandomGenerator* rand_gen,
+ rtc::Event* render_call_event,
+ rtc::Event* capture_call_event,
+ FrameCounters* shared_counters_state,
+ AudioProcessingImplLockTest* test_framework,
+ TestConfig* test_config,
+ AudioProcessing* apm)
+ : rand_gen_(rand_gen),
+ render_call_event_(render_call_event),
+ capture_call_event_(capture_call_event),
+ frame_counters_(shared_counters_state),
+ test_(test_framework),
+ test_config_(test_config),
+ apm_(apm),
+ frame_data_(max_frame_size) {}
+
+// Implements the callback functionality for the render thread.
+bool RenderProcessor::Process() {
+ // Conditional wait to ensure that a capture call has been done
+ // before the first render call is performed (implicitly
+ // required by the APM API).
+ if (first_render_call_) {
+ capture_call_event_->Wait(rtc::Event::kForever);
+ first_render_call_ = false;
+ }
+
+ // Sleep a random time to simulate thread jitter.
+ SleepRandomMs(3, rand_gen_);
+
+ // Check whether the test is done.
+ if (test_->MaybeEndTest()) {
+ return false;
+ }
+
+ // Ensure that the number of render and capture calls do not
+ // differ too much.
+ if (frame_counters_->RenderMinusCaptureCounters() > kMaxCallDifference) {
+ capture_call_event_->Wait(rtc::Event::kForever);
+ }
+
+ // Apply any specified render side APM non-processing runtime calls.
+ ApplyRuntimeSettingScheme();
+
+ // Apply the render side processing call.
+ CallApmRenderSide();
+
+ // Increase the number of render-side calls.
+ frame_counters_->IncreaseRenderCounter();
+
+ // Flag to the capture thread that another render API call has occurred
+ // by triggering this threads call event.
+ render_call_event_->Set();
+ return true;
+}
+
+// Prepares the render side frame and the accompanying metadata
+// with the appropriate information.
+void RenderProcessor::PrepareFrame() {
+ // Restrict to a common fixed sample rate if the AudioFrame interface is
+ // used.
+ if ((test_config_->render_api_function ==
+ RenderApiImpl::AnalyzeReverseStreamImpl1) ||
+ (test_config_->render_api_function ==
+ RenderApiImpl::ProcessReverseStreamImpl1) ||
+ (test_config_->aec_type !=
+ AecType::BasicWebRtcAecSettingsWithAecMobile)) {
+ frame_data_.input_sample_rate_hz = test_config_->initial_sample_rate_hz;
+ frame_data_.output_sample_rate_hz = test_config_->initial_sample_rate_hz;
+ }
+
+ // Prepare the audioframe data and metadata
+ frame_data_.input_samples_per_channel =
+ frame_data_.input_sample_rate_hz * AudioProcessing::kChunkSizeMs / 1000;
+ frame_data_.frame.sample_rate_hz_ = frame_data_.input_sample_rate_hz;
+ frame_data_.frame.num_channels_ = frame_data_.input_number_of_channels;
+ frame_data_.frame.samples_per_channel_ =
+ frame_data_.input_samples_per_channel;
+ PopulateAudioFrame(&frame_data_.frame, kRenderInputFixLevel, rand_gen_);
+
+ // Prepare the float audio input data and metadata.
+ frame_data_.input_stream_config.set_sample_rate_hz(
+ frame_data_.input_sample_rate_hz);
+ frame_data_.input_stream_config.set_num_channels(
+ frame_data_.input_number_of_channels);
+ frame_data_.input_stream_config.set_has_keyboard(false);
+ PopulateAudioFrame(&frame_data_.input_frame[0], kRenderInputFloatLevel,
+ frame_data_.input_number_of_channels,
+ frame_data_.input_samples_per_channel, rand_gen_);
+ frame_data_.input_channel_layout =
+ (frame_data_.input_number_of_channels == 1
+ ? AudioProcessing::ChannelLayout::kMono
+ : AudioProcessing::ChannelLayout::kStereo);
+
+ // Prepare the float audio output data and metadata.
+ frame_data_.output_samples_per_channel =
+ frame_data_.output_sample_rate_hz * AudioProcessing::kChunkSizeMs / 1000;
+ frame_data_.output_stream_config.set_sample_rate_hz(
+ frame_data_.output_sample_rate_hz);
+ frame_data_.output_stream_config.set_num_channels(
+ frame_data_.output_number_of_channels);
+ frame_data_.output_stream_config.set_has_keyboard(false);
+ frame_data_.output_channel_layout =
+ (frame_data_.output_number_of_channels == 1
+ ? AudioProcessing::ChannelLayout::kMono
+ : AudioProcessing::ChannelLayout::kStereo);
+}
+
+// Makes the render side processing API call.
+void RenderProcessor::CallApmRenderSide() {
+ // Prepare a proper render side processing API call input.
+ PrepareFrame();
+
+ // Call the specified render side API processing method.
+ int result = AudioProcessing::kNoError;
+ switch (test_config_->render_api_function) {
+ case RenderApiImpl::ProcessReverseStreamImpl1:
+ result = apm_->ProcessReverseStream(&frame_data_.frame);
+ break;
+ case RenderApiImpl::ProcessReverseStreamImpl2:
+ result = apm_->ProcessReverseStream(
+ &frame_data_.input_frame[0], frame_data_.input_stream_config,
+ frame_data_.output_stream_config, &frame_data_.output_frame[0]);
+ break;
+ case RenderApiImpl::AnalyzeReverseStreamImpl1:
+ result = apm_->AnalyzeReverseStream(&frame_data_.frame);
+ break;
+ case RenderApiImpl::AnalyzeReverseStreamImpl2:
+ result = apm_->AnalyzeReverseStream(
+ &frame_data_.input_frame[0], frame_data_.input_samples_per_channel,
+ frame_data_.input_sample_rate_hz, frame_data_.input_channel_layout);
+ break;
+ default:
+ FAIL();
+ }
+
+ // Check the return code for error.
+ ASSERT_EQ(AudioProcessing::kNoError, result);
+}
+
+// Applies any render capture side APM API calls and audio stream
+// characteristics
+// specified by the scheme for the test.
+void RenderProcessor::ApplyRuntimeSettingScheme() {
+ const int render_count_local = frame_counters_->GetRenderCounter();
+
+ // Update the number of channels and sample rates for the input and output.
+ // Note that the counts frequencies for when to set parameters
+ // are set using prime numbers in order to ensure that the
+ // permutation scheme in the parameter setting changes.
+ switch (test_config_->runtime_parameter_setting_scheme) {
+ case RuntimeParameterSettingScheme::SparseStreamMetadataChangeScheme:
+ if (render_count_local == 0)
+ frame_data_.input_sample_rate_hz = 16000;
+ else if (render_count_local % 47 == 0)
+ frame_data_.input_sample_rate_hz = 32000;
+ else if (render_count_local % 71 == 0)
+ frame_data_.input_sample_rate_hz = 48000;
+ else if (render_count_local % 79 == 0)
+ frame_data_.input_sample_rate_hz = 16000;
+ else if (render_count_local % 83 == 0)
+ frame_data_.input_sample_rate_hz = 8000;
+
+ if (render_count_local == 0)
+ frame_data_.input_number_of_channels = 1;
+ else if (render_count_local % 4 == 0)
+ frame_data_.input_number_of_channels =
+ (frame_data_.input_number_of_channels == 1 ? 2 : 1);
+
+ if (render_count_local == 0)
+ frame_data_.output_sample_rate_hz = 16000;
+ else if (render_count_local % 17 == 0)
+ frame_data_.output_sample_rate_hz = 32000;
+ else if (render_count_local % 19 == 0)
+ frame_data_.output_sample_rate_hz = 48000;
+ else if (render_count_local % 29 == 0)
+ frame_data_.output_sample_rate_hz = 16000;
+ else if (render_count_local % 61 == 0)
+ frame_data_.output_sample_rate_hz = 8000;
+
+ if (render_count_local == 0)
+ frame_data_.output_number_of_channels = 1;
+ else if (render_count_local % 8 == 0)
+ frame_data_.output_number_of_channels =
+ (frame_data_.output_number_of_channels == 1 ? 2 : 1);
+ break;
+ case RuntimeParameterSettingScheme::ExtremeStreamMetadataChangeScheme:
+ if (render_count_local == 0) {
+ frame_data_.input_number_of_channels = 1;
+ frame_data_.input_sample_rate_hz = 16000;
+ frame_data_.output_number_of_channels = 1;
+ frame_data_.output_sample_rate_hz = 16000;
+ } else {
+ frame_data_.input_number_of_channels =
+ (frame_data_.input_number_of_channels == 1 ? 2 : 1);
+ if (frame_data_.input_sample_rate_hz == 8000)
+ frame_data_.input_sample_rate_hz = 16000;
+ else if (frame_data_.input_sample_rate_hz == 16000)
+ frame_data_.input_sample_rate_hz = 32000;
+ else if (frame_data_.input_sample_rate_hz == 32000)
+ frame_data_.input_sample_rate_hz = 48000;
+ else if (frame_data_.input_sample_rate_hz == 48000)
+ frame_data_.input_sample_rate_hz = 8000;
+
+ frame_data_.output_number_of_channels =
+ (frame_data_.output_number_of_channels == 1 ? 2 : 1);
+ if (frame_data_.output_sample_rate_hz == 8000)
+ frame_data_.output_sample_rate_hz = 16000;
+ else if (frame_data_.output_sample_rate_hz == 16000)
+ frame_data_.output_sample_rate_hz = 32000;
+ else if (frame_data_.output_sample_rate_hz == 32000)
+ frame_data_.output_sample_rate_hz = 48000;
+ else if (frame_data_.output_sample_rate_hz == 48000)
+ frame_data_.output_sample_rate_hz = 8000;
+ }
+ break;
+ case RuntimeParameterSettingScheme::FixedMonoStreamMetadataScheme:
+ if (render_count_local == 0) {
+ frame_data_.input_sample_rate_hz = 16000;
+ frame_data_.input_number_of_channels = 1;
+ frame_data_.output_sample_rate_hz = 16000;
+ frame_data_.output_number_of_channels = 1;
+ }
+ break;
+ case RuntimeParameterSettingScheme::FixedStereoStreamMetadataScheme:
+ if (render_count_local == 0) {
+ frame_data_.input_sample_rate_hz = 16000;
+ frame_data_.input_number_of_channels = 2;
+ frame_data_.output_sample_rate_hz = 16000;
+ frame_data_.output_number_of_channels = 2;
+ }
+ break;
+ default:
+ FAIL();
+ }
+
+ // Restric the number of output channels not to exceed
+ // the number of input channels.
+ frame_data_.output_number_of_channels =
+ std::min(frame_data_.output_number_of_channels,
+ frame_data_.input_number_of_channels);
+}
+
+} // anonymous namespace
+
+TEST_P(AudioProcessingImplLockTest, LockTest) {
+ // Run test and verify that it did not time out.
+ ASSERT_TRUE(RunTest());
+}
+
+// Instantiate tests from the extreme test configuration set.
+INSTANTIATE_TEST_CASE_P(
+ DISABLED_AudioProcessingImplLockExtensive,
+ AudioProcessingImplLockTest,
+ ::testing::ValuesIn(TestConfig::GenerateExtensiveTestConfigs()));
+
+INSTANTIATE_TEST_CASE_P(
+ AudioProcessingImplLockBrief,
+ AudioProcessingImplLockTest,
+ ::testing::ValuesIn(TestConfig::GenerateBriefTestConfigs()));
+
+} // namespace webrtc