| /* |
| * 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 "modules/audio_processing/audio_processing_impl.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <vector> |
| |
| #include "api/array_view.h" |
| #include "modules/audio_processing/test/test_utils.h" |
| #include "rtc_base/critical_section.h" |
| #include "rtc_base/event.h" |
| #include "rtc_base/platform_thread.h" |
| #include "rtc_base/random.h" |
| #include "system_wrappers/include/sleep.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| class AudioProcessingImplLockTest; |
| |
| // Type of the render thread APM API call to use in the test. |
| enum class RenderApiImpl { |
| ProcessReverseStreamImpl1, |
| ProcessReverseStreamImpl2, |
| AnalyzeReverseStreamImpl |
| }; |
| |
| // 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_ RTC_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::AnalyzeReverseStreamImpl; |
| 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::ProcessReverseStreamImpl2, |
| CaptureApiImpl::ProcessStreamImpl2}, |
| {RenderApiImpl::ProcessReverseStreamImpl2, |
| CaptureApiImpl::ProcessStreamImpl3}, |
| {RenderApiImpl::AnalyzeReverseStreamImpl, |
| CaptureApiImpl::ProcessStreamImpl2}, |
| {RenderApiImpl::AnalyzeReverseStreamImpl, |
| 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) { |
| // Due to a VisualStudio 2015 compiler issue, the internal loop |
| // variable here cannot override a previously defined name. |
| // In other words "type" cannot be named "aec_type" here. |
| // https://connect.microsoft.com/VisualStudio/feedback/details/2291755 |
| for (auto type : aec_types) { |
| test_config.aec_type = 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: |
| rtc::CriticalSection crit_; |
| int render_count RTC_GUARDED_BY(crit_) = 0; |
| int capture_count RTC_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_; |
| |
| std::unique_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); |
| int16_t* frame_data = frame->mutable_data(); |
| 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() |
| : render_thread_(RenderProcessorThreadFunc, this, "render"), |
| capture_thread_(CaptureProcessorThreadFunc, this, "capture"), |
| stats_thread_(StatsProcessorThreadFunc, this, "stats"), |
| apm_(AudioProcessingBuilder().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_->gain_control()->Enable(true)); |
| |
| ASSERT_EQ(apm_->kNoError, |
| apm_->gain_control()->set_mode(GainControl::kAdaptiveDigital)); |
| ASSERT_EQ(apm_->kNoError, apm_->gain_control()->Enable(true)); |
| |
| AudioProcessing::Config apm_config = apm_->GetConfig(); |
| apm_config.echo_canceller.enabled = |
| (test_config_.aec_type != AecType::AecTurnedOff); |
| apm_config.echo_canceller.mobile_mode = |
| (test_config_.aec_type == AecType::BasicWebRtcAecSettingsWithAecMobile); |
| apm_config.noise_suppression.enabled = true; |
| apm_config.voice_detection.enabled = true; |
| apm_config.level_estimation.enabled = true; |
| apm_->ApplyConfig(apm_config); |
| |
| Config config; |
| 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_); |
| |
| AudioProcessing::Config apm_config = apm_->GetConfig(); |
| if (test_config_->aec_type != AecType::AecTurnedOff) { |
| EXPECT_TRUE(apm_config.echo_canceller.enabled); |
| EXPECT_EQ(apm_config.echo_canceller.mobile_mode, |
| (test_config_->aec_type == |
| AecType::BasicWebRtcAecSettingsWithAecMobile)); |
| } else { |
| EXPECT_FALSE(apm_config.echo_canceller.enabled); |
| } |
| EXPECT_TRUE(apm_->gain_control()->is_enabled()); |
| EXPECT_TRUE(apm_config.noise_suppression.enabled); |
| |
| // The below return values are not testable. |
| apm_->noise_suppression()->speech_probability(); |
| apm_->voice_detection()->is_enabled(); |
| |
| apm_->GetStatistics(/*has_remote_tracks=*/true); |
| |
| 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); |
| |
| // Set the analog level. |
| apm_->gain_control()->set_stream_analog_level(80); |
| |
| // 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(); |
| } |
| |
| // Retrieve the new analog level. |
| apm_->gain_control()->stream_analog_level(); |
| |
| // 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::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::AnalyzeReverseStreamImpl: |
| 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 |