| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/aligned_memory.h" |
| #include "base/path_service.h" |
| #include "base/stl_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_parameters.h" |
| #include "media/webrtc/audio_processor.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::AtLeast; |
| using ::testing::Return; |
| |
| namespace media { |
| |
| namespace { |
| |
| const int kAudioProcessingSampleRate = 48000; |
| const int kAudioProcessingNumberOfChannels = 1; |
| |
| // The number of packers used for testing. |
| const int kNumberOfPacketsForTest = 100; |
| const int kMaxNumberOfPlayoutDataChannels = 2; |
| |
| void ReadDataFromSpeechFile(char* data, int length) { |
| base::FilePath file; |
| CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &file)); |
| file = file.Append(FILE_PATH_LITERAL("media")) |
| .Append(FILE_PATH_LITERAL("test")) |
| .Append(FILE_PATH_LITERAL("data")) |
| .Append(FILE_PATH_LITERAL("speech_16b_stereo_48kHz.raw")); |
| DCHECK(base::PathExists(file)); |
| int64_t data_file_size64 = 0; |
| DCHECK(base::GetFileSize(file, &data_file_size64)); |
| EXPECT_EQ(length, base::ReadFile(file, data, length)); |
| DCHECK(data_file_size64 > length); |
| } |
| |
| } // namespace |
| |
| class WebRtcAudioProcessorTest : public ::testing::Test { |
| public: |
| WebRtcAudioProcessorTest() |
| : params_(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::CHANNEL_LAYOUT_STEREO, |
| 48000, |
| 480) {} |
| |
| protected: |
| // Helper method to save duplicated code. |
| void ProcessDataAndVerifyFormat(AudioProcessor* audio_processor, |
| int expected_output_sample_rate, |
| int expected_output_channels, |
| int expected_output_buffer_size) { |
| // Read the audio data from a file. |
| const media::AudioParameters& params = audio_processor->audio_parameters_; |
| const int packet_size = params.frames_per_buffer() * 2 * params.channels(); |
| const size_t length = packet_size * kNumberOfPacketsForTest; |
| std::unique_ptr<char[]> capture_data(new char[length]); |
| ReadDataFromSpeechFile(capture_data.get(), length); |
| const int16_t* data_ptr = |
| reinterpret_cast<const int16_t*>(capture_data.get()); |
| std::unique_ptr<media::AudioBus> data_bus = |
| media::AudioBus::Create(params.channels(), params.frames_per_buffer()); |
| |
| // |data_bus_playout| is used if the number of capture channels is larger |
| // than max allowed playout channels. |data_bus_playout_to_use| points to |
| // the AudioBus to use, either |data_bus| or |data_bus_playout|. |
| std::unique_ptr<media::AudioBus> data_bus_playout; |
| media::AudioBus* data_bus_playout_to_use = data_bus.get(); |
| media::AudioParameters playout_params = params; |
| if (params.channels() > kMaxNumberOfPlayoutDataChannels) { |
| data_bus_playout = |
| media::AudioBus::CreateWrapper(kMaxNumberOfPlayoutDataChannels); |
| data_bus_playout->set_frames(params.frames_per_buffer()); |
| data_bus_playout_to_use = data_bus_playout.get(); |
| playout_params.Reset(params.format(), CHANNEL_LAYOUT_STEREO, |
| params.sample_rate(), params.frames_per_buffer()); |
| } |
| |
| const base::TimeDelta input_capture_delay = |
| base::TimeDelta::FromMilliseconds(20); |
| for (int i = 0; i < kNumberOfPacketsForTest; ++i) { |
| data_bus->FromInterleaved(data_ptr, data_bus->frames(), 2); |
| // |audio_processor| does nothing when the audio processing is off in |
| // the processor. |
| webrtc::AudioProcessing* ap = audio_processor->audio_processing_.get(); |
| const bool is_aec_enabled = ap && ap->GetConfig().echo_canceller.enabled; |
| if (is_aec_enabled) { |
| if (params.channels() > kMaxNumberOfPlayoutDataChannels) { |
| for (int i = 0; i < kMaxNumberOfPlayoutDataChannels; ++i) { |
| data_bus_playout->SetChannelData( |
| i, const_cast<float*>(data_bus->channel(i))); |
| } |
| } |
| base::TimeTicks in_the_future = |
| base::TimeTicks::Now() + base::TimeDelta::FromMilliseconds(10); |
| audio_processor->AnalyzePlayout(*data_bus_playout_to_use, |
| playout_params, in_the_future); |
| } |
| |
| auto result = audio_processor->ProcessCapture( |
| *data_bus, base::TimeTicks::Now() - input_capture_delay, 1.0, false); |
| data_ptr += params.frames_per_buffer() * params.channels(); |
| } |
| } |
| |
| void VerifyEnabledComponents(AudioProcessor* audio_processor) { |
| webrtc::AudioProcessing* audio_processing = |
| audio_processor->audio_processing_.get(); |
| webrtc::AudioProcessing::Config ap_config = audio_processing->GetConfig(); |
| EXPECT_TRUE(ap_config.echo_canceller.enabled); |
| EXPECT_FALSE(ap_config.echo_canceller.mobile_mode); |
| EXPECT_TRUE(ap_config.high_pass_filter.enabled); |
| EXPECT_TRUE(ap_config.voice_detection.enabled); |
| |
| EXPECT_TRUE(audio_processing->noise_suppression()->is_enabled()); |
| EXPECT_TRUE(audio_processing->noise_suppression()->level() == |
| webrtc::NoiseSuppression::kHigh); |
| EXPECT_TRUE(audio_processing->gain_control()->is_enabled()); |
| EXPECT_TRUE(audio_processing->gain_control()->mode() == |
| webrtc::GainControl::kAdaptiveAnalog); |
| } |
| |
| AudioProcessingSettings GetEnabledAudioProcessingSettings() const { |
| AudioProcessingSettings settings; |
| settings.echo_cancellation = EchoCancellationType::kAec2; |
| settings.noise_suppression = NoiseSuppressionType::kExperimental; |
| settings.automatic_gain_control = AutomaticGainControlType::kExperimental; |
| settings.high_pass_filter = true; |
| settings.typing_detection = true; |
| return settings; |
| } |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| media::AudioParameters params_; |
| }; |
| |
| TEST_F(WebRtcAudioProcessorTest, WithAudioProcessing) { |
| AudioProcessor audio_processor(params_, GetEnabledAudioProcessingSettings()); |
| VerifyEnabledComponents(&audio_processor); |
| ProcessDataAndVerifyFormat(&audio_processor, kAudioProcessingSampleRate, |
| kAudioProcessingNumberOfChannels, |
| kAudioProcessingSampleRate / 100); |
| } |
| |
| TEST_F(WebRtcAudioProcessorTest, WithoutAnyProcessing) { |
| // All processing settings are disabled by default |
| AudioProcessingSettings settings; |
| const media::AudioParameters source_params( |
| media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::CHANNEL_LAYOUT_STEREO, kAudioProcessingSampleRate, |
| kAudioProcessingSampleRate / 100); |
| AudioProcessor audio_processor(source_params, settings); |
| |
| ProcessDataAndVerifyFormat(&audio_processor, params_.sample_rate(), |
| params_.channels(), params_.sample_rate() / 100); |
| } |
| |
| TEST_F(WebRtcAudioProcessorTest, TestAllSampleRates) { |
| for (int sample_rate : {8000, 16000, 32000, 44100, 48000}) { |
| int buffer_size = sample_rate / 100; |
| media::AudioParameters params(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::CHANNEL_LAYOUT_STEREO, sample_rate, |
| buffer_size); |
| AudioProcessor audio_processor(params, GetEnabledAudioProcessingSettings()); |
| |
| VerifyEnabledComponents(&audio_processor); |
| |
| ProcessDataAndVerifyFormat(&audio_processor, kAudioProcessingSampleRate, |
| kAudioProcessingNumberOfChannels, |
| kAudioProcessingSampleRate / 100); |
| } |
| } |
| |
| TEST_F(WebRtcAudioProcessorTest, TestStereoAudio) { |
| // All processing settings are disabled by default |
| AudioProcessingSettings settings; |
| settings.stereo_mirroring = true; |
| const media::AudioParameters source_params( |
| media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::CHANNEL_LAYOUT_STEREO, kAudioProcessingSampleRate, |
| kAudioProcessingSampleRate / 100); |
| AudioProcessor audio_processor(source_params, settings); |
| |
| // Construct left and right channels, and assign different values to the |
| // first data of the left channel and right channel. |
| const int size = media::AudioBus::CalculateMemorySize(source_params); |
| std::unique_ptr<float, base::AlignedFreeDeleter> left_channel( |
| static_cast<float*>(base::AlignedAlloc(size, 32))); |
| std::unique_ptr<float, base::AlignedFreeDeleter> right_channel( |
| static_cast<float*>(base::AlignedAlloc(size, 32))); |
| std::unique_ptr<media::AudioBus> wrapper = |
| media::AudioBus::CreateWrapper(source_params.channels()); |
| wrapper->set_frames(source_params.frames_per_buffer()); |
| wrapper->SetChannelData(0, left_channel.get()); |
| wrapper->SetChannelData(1, right_channel.get()); |
| wrapper->Zero(); |
| float* left_channel_ptr = left_channel.get(); |
| left_channel_ptr[0] = 1.0f; |
| |
| // Run the test consecutively to make sure the stereo channels are not |
| // flipped back and forth. |
| static const int kNumberOfPacketsForTest = 100; |
| const base::TimeDelta pushed_capture_delay = |
| base::TimeDelta::FromMilliseconds(42); |
| for (int i = 0; i < kNumberOfPacketsForTest; ++i) { |
| auto result = audio_processor.ProcessCapture( |
| *wrapper, base::TimeTicks::Now() + pushed_capture_delay, 1.0, false); |
| |
| EXPECT_EQ(result.audio.channel(0)[0], 0); |
| EXPECT_NE(result.audio.channel(1)[0], 0); |
| } |
| } |
| |
| TEST_F(WebRtcAudioProcessorTest, TestWithKeyboardMicChannel) { |
| AudioProcessingSettings settings = GetEnabledAudioProcessingSettings(); |
| media::AudioParameters params(media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| media::CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC, |
| kAudioProcessingSampleRate, |
| kAudioProcessingSampleRate / 100); |
| AudioProcessor audio_processor(params, settings); |
| ProcessDataAndVerifyFormat(&audio_processor, kAudioProcessingSampleRate, |
| kAudioProcessingNumberOfChannels, |
| kAudioProcessingSampleRate / 100); |
| } |
| |
| TEST_F(WebRtcAudioProcessorTest, StartStopAecDump) { |
| base::ScopedTempDir temp_directory; |
| ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); |
| base::FilePath temp_file_path; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(), |
| &temp_file_path)); |
| { |
| AudioProcessor audio_processor(params_, |
| GetEnabledAudioProcessingSettings()); |
| |
| // Start and stop recording. |
| audio_processor.StartEchoCancellationDump(base::File( |
| temp_file_path, base::File::FLAG_WRITE | base::File::FLAG_OPEN)); |
| audio_processor.StopEchoCancellationDump(); |
| |
| // Start and wait for d-tor. |
| audio_processor.StartEchoCancellationDump(base::File( |
| temp_file_path, base::File::FLAG_WRITE | base::File::FLAG_OPEN)); |
| } |
| |
| // Check that dump file is non-empty after audio processor has been |
| // destroyed. Note that this test fails when compiling WebRTC |
| // without protobuf support, rtc_enable_protobuf=false. |
| std::string output; |
| ASSERT_TRUE(base::ReadFileToString(temp_file_path, &output)); |
| ASSERT_FALSE(output.empty()); |
| // The temporary file is deleted when temp_directory exists scope. |
| } |
| |
| } // namespace media |