blob: bf244d9f05f4aa7538ce97b7299c335ce54f6a50 [file] [log] [blame]
// 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<SignedInt16SampleTypeTraits>(
data_ptr, data_bus->frames());
// |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.gain_controller1.enabled);
EXPECT_EQ(ap_config.gain_controller1.mode,
ap_config.gain_controller1.kAdaptiveAnalog);
EXPECT_TRUE(ap_config.noise_suppression.enabled);
EXPECT_EQ(ap_config.noise_suppression.level,
ap_config.noise_suppression.kHigh);
EXPECT_TRUE(ap_config.voice_detection.enabled);
}
AudioProcessingSettings GetEnabledAudioProcessingSettings() const {
AudioProcessingSettings settings;
settings.echo_cancellation = EchoCancellationType::kAec3;
settings.noise_suppression = NoiseSuppressionType::kExperimental;
settings.automatic_gain_control = AutomaticGainControlType::kExperimental;
settings.high_pass_filter = true;
settings.typing_detection = true;
return settings;
}
base::test::TaskEnvironment 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