blob: 77efe58e868d93655a23b6e8a2ea0aa3378e9d57 [file] [log] [blame]
// Copyright 2017 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 "chromecast/media/cma/backend/post_processors/post_processor_unittest.h"
#include "chromecast/media/cma/backend/post_processors/post_processor_wrapper.h"
#include <time.h>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <limits>
#include "base/logging.h"
namespace chromecast {
namespace media {
namespace post_processor_test {
namespace {
const float kEpsilon = std::numeric_limits<float>::epsilon();
// Benchmark parameters.
const float kTestDurationSec = 1.0;
} // namespace
using Status = AudioPostProcessor2::Status;
AudioPostProcessor2::Config MakeProcessorConfig(int sample_rate_hz) {
AudioPostProcessor2::Config config;
config.output_sample_rate = sample_rate_hz;
config.system_output_sample_rate = sample_rate_hz;
config.output_frames_per_write = kBufSizeFrames;
return config;
}
AlignedBuffer<float> LinearChirp(int frames,
const std::vector<double>& start_frequencies,
const std::vector<double>& end_frequencies) {
DCHECK_EQ(start_frequencies.size(), end_frequencies.size());
AlignedBuffer<float> chirp(frames * start_frequencies.size());
for (size_t ch = 0; ch < start_frequencies.size(); ++ch) {
double angle = 0.0;
for (int f = 0; f < frames; ++f) {
angle +=
start_frequencies[ch] +
(end_frequencies[ch] - start_frequencies[ch]) * f * M_PI / frames;
chirp[ch + f * start_frequencies.size()] = sin(angle);
}
}
return chirp;
}
AlignedBuffer<float> GetStereoChirp(int frames,
float start_frequency_left,
float end_frequency_left,
float start_frequency_right,
float end_frequency_right) {
std::vector<double> start_frequencies(2);
std::vector<double> end_frequencies(2);
start_frequencies[0] = start_frequency_left;
start_frequencies[1] = start_frequency_right;
end_frequencies[0] = end_frequency_left;
end_frequencies[1] = end_frequency_right;
return LinearChirp(frames, start_frequencies, end_frequencies);
}
void TestDelay(AudioPostProcessor2* pp,
int output_sample_rate,
int num_input_channels) {
ASSERT_TRUE(pp->SetConfig(MakeProcessorConfig(output_sample_rate)));
const Status& status = pp->GetStatus();
ASSERT_GT(status.input_sample_rate, 0);
ASSERT_GT(status.output_channels, 0);
ASSERT_GE(status.rendering_delay_frames, 0);
ASSERT_GE(status.ringing_time_frames, 0);
const int input_size_frames = kBufSizeFrames * 100;
const int resample_factor = output_sample_rate / status.input_sample_rate;
const int output_size_frames = input_size_frames * resample_factor;
AlignedBuffer<float> data_in = LinearChirp(
input_size_frames, std::vector<double>(num_input_channels, 0.0),
std::vector<double>(num_input_channels, 1.0));
AlignedBuffer<float> data_expected = LinearChirp(
output_size_frames, std::vector<double>(num_input_channels, 0.0),
std::vector<double>(num_input_channels, 1.0 / resample_factor));
AlignedBuffer<float> data_out(data_in.size() * resample_factor);
const int output_buf_size = kBufSizeFrames * resample_factor *
status.output_channels * sizeof(data_out[0]);
for (int i = 0; i < input_size_frames; i += kBufSizeFrames) {
pp->ProcessFrames(&data_in[i * num_input_channels], kBufSizeFrames, 1.0,
0.0);
std::memcpy(&data_out[i * status.output_channels * resample_factor],
status.output_buffer, output_buf_size);
}
double max_sum = 0;
int max_idx = -1; // index (offset), corresponding to maximum x-correlation.
// Find the offset of maximum x-correlation of in/out.
// Search range should be larger than post-processor's expected delay.
int search_range =
status.rendering_delay_frames * resample_factor + kBufSizeFrames;
for (int offset = 0; offset < search_range; ++offset) {
double sum = 0.0;
int upper_search_limit_frames = output_size_frames - search_range;
for (int f = 0; f < upper_search_limit_frames; ++f) {
for (int ch = 0; ch < status.output_channels; ++ch) {
sum += data_expected[f * num_input_channels] *
data_out[(f + offset) * status.output_channels + ch];
}
}
// No need to normalize because every dot product is the same length.
if (sum > max_sum) {
max_sum = sum;
max_idx = offset;
}
}
EXPECT_EQ(max_idx / resample_factor, status.rendering_delay_frames);
}
void TestRingingTime(AudioPostProcessor2* pp,
int sample_rate,
int num_input_channels) {
ASSERT_TRUE(pp->SetConfig(MakeProcessorConfig(sample_rate)));
const Status& status = pp->GetStatus();
ASSERT_GT(status.input_sample_rate, 0);
ASSERT_GT(status.output_channels, 0);
ASSERT_GE(status.rendering_delay_frames, 0);
ASSERT_GE(status.ringing_time_frames, 0);
const int kNumFrames = kBufSizeFrames;
const int kSinFreq = 2000;
// Send a second of data to excite the filter.
for (int i = 0; i < sample_rate; i += kNumFrames) {
AlignedBuffer<float> data =
GetSineData(kNumFrames, kSinFreq, sample_rate, num_input_channels);
pp->ProcessFrames(data.data(), kNumFrames, 1.0, 0.0);
}
AlignedBuffer<float> data =
GetSineData(kNumFrames, kSinFreq, sample_rate, num_input_channels);
pp->ProcessFrames(data.data(), kNumFrames, 1.0, 0.0);
// Compute the amplitude of the last buffer
ASSERT_NE(status.output_buffer, nullptr);
float original_amplitude =
SineAmplitude(status.output_buffer, status.output_channels * kNumFrames);
EXPECT_GE(original_amplitude, 0)
<< "Output of nonzero data is 0; cannot test ringing";
// Feed |ringing_time_frames| of silence.
int frames_remaining = status.ringing_time_frames;
int frames_to_process = std::min(frames_remaining, kNumFrames);
while (frames_remaining > 0) {
frames_to_process = std::min(frames_to_process, frames_remaining);
data.assign(frames_to_process * num_input_channels, 0);
pp->ProcessFrames(data.data(), frames_to_process, 1.0, 0.0);
frames_remaining -= frames_to_process;
}
// Send a little more data and ensure the amplitude is < 1% the original.
data.assign(kNumFrames * num_input_channels, 0);
pp->ProcessFrames(data.data(), kNumFrames, 1.0, 0.0);
// Only look at the amplitude of the first few frames.
EXPECT_LE(SineAmplitude(status.output_buffer, 10 * status.output_channels) /
original_amplitude,
0.01)
<< "Output level after " << status.ringing_time_frames
<< " is more than 1%.";
}
void TestPassthrough(AudioPostProcessor2* pp,
int sample_rate,
int num_input_channels) {
ASSERT_TRUE(pp->SetConfig(MakeProcessorConfig(sample_rate)));
const Status& status = pp->GetStatus();
ASSERT_GT(status.input_sample_rate, 0);
ASSERT_GT(status.output_channels, 0);
ASSERT_GE(status.rendering_delay_frames, 0);
ASSERT_EQ(status.output_channels, num_input_channels)
<< "\"Passthrough\" is not well defined for "
<< "num_input_channels != num_output_channels";
ASSERT_EQ(status.input_sample_rate, sample_rate);
const int kNumFrames = kBufSizeFrames;
const int kSinFreq = 2000;
AlignedBuffer<float> data =
GetSineData(kNumFrames, kSinFreq, sample_rate, num_input_channels);
AlignedBuffer<float> expected(data);
pp->ProcessFrames(data.data(), kNumFrames, 1.0, 0.0);
int delayed_frames = 0;
while (status.rendering_delay_frames >= delayed_frames + kNumFrames) {
delayed_frames += kNumFrames;
for (int i = 0; i < kNumFrames * num_input_channels; ++i) {
EXPECT_EQ(0.0f, data[i]) << i;
}
data = expected;
pp->ProcessFrames(data.data(), kNumFrames, 1.0, 0.0);
ASSERT_GE(status.rendering_delay_frames, delayed_frames);
}
int delay_samples =
(status.rendering_delay_frames - delayed_frames) * status.output_channels;
ASSERT_LE(delay_samples, status.output_channels * kNumFrames);
CheckArraysEqual(expected.data(), status.output_buffer + delay_samples,
data.size() - delay_samples);
}
void AudioProcessorBenchmark(AudioPostProcessor2* pp,
int sample_rate,
int num_input_channels) {
ASSERT_TRUE(pp->SetConfig(MakeProcessorConfig(sample_rate)));
int test_size_frames = kTestDurationSec * pp->GetStatus().input_sample_rate;
// Make test_size multiple of kBufSizeFrames and calculate effective
// duration.
test_size_frames -= test_size_frames % kBufSizeFrames;
float effective_duration = static_cast<float>(test_size_frames) / sample_rate;
AlignedBuffer<float> data_in = LinearChirp(
test_size_frames, std::vector<double>(num_input_channels, 0.0),
std::vector<double>(num_input_channels, 1.0));
clock_t start_clock = clock();
for (int i = 0; i < test_size_frames; i += kBufSizeFrames * kNumChannels) {
pp->ProcessFrames(&data_in[i], kBufSizeFrames, 1.0, 0.0);
}
clock_t stop_clock = clock();
LOG(INFO) << "At " << sample_rate
<< " frames per second CPU usage: " << std::defaultfloat
<< 100.0 * (stop_clock - start_clock) /
(CLOCKS_PER_SEC * effective_duration)
<< "%";
}
void AudioProcessorBenchmark(AudioPostProcessor* pp, int sample_rate) {
AudioPostProcessorWrapper wrapper(pp, kNumChannels);
AudioProcessorBenchmark(&wrapper, sample_rate, kNumChannels);
}
template <typename T>
void CheckArraysEqual(const T* expected, const T* actual, size_t size) {
std::vector<int> differing_values = CompareArray(expected, actual, size);
if (differing_values.empty()) {
return;
}
size_t size_to_print =
std::min(static_cast<size_t>(differing_values[0] + 8), size);
EXPECT_EQ(differing_values.size(), 0u)
<< "Arrays differ at indices "
<< ::testing::PrintToString(differing_values)
<< "\n Expected: " << ArrayToString(expected, size_to_print)
<< "\n Actual: " << ArrayToString(actual, size_to_print);
}
template <typename T>
std::vector<int> CompareArray(const T* expected, const T* actual, size_t size) {
std::vector<int> diffs;
for (size_t i = 0; i < size; ++i) {
if (std::abs(expected[i] - actual[i]) > kEpsilon) {
diffs.push_back(i);
}
}
return diffs;
}
template <typename T>
std::string ArrayToString(const T* array, size_t size) {
std::string result;
for (size_t i = 0; i < size; ++i) {
result += ::testing::PrintToString(array[i]) + " ";
}
return result;
}
float SineAmplitude(const float* data, int num_frames) {
double power = 0;
for (int i = 0; i < num_frames; ++i) {
power += std::pow(data[i], 2);
}
return std::sqrt(power / num_frames) * sqrt(2);
}
AlignedBuffer<float> GetSineData(int frames,
float frequency,
int sample_rate,
int num_channels) {
AlignedBuffer<float> sine(frames * num_channels);
for (int f = 0; f < frames; ++f) {
for (int ch = 0; ch < num_channels; ++ch) {
// Offset by a little so that first value is non-zero
sine[f * num_channels + ch] =
sin(static_cast<double>(f + ch) * frequency * 2 * M_PI / sample_rate);
}
}
return sine;
}
void TestDelay(AudioPostProcessor* pp, int sample_rate) {
AudioPostProcessorWrapper ppw(pp, kNumChannels);
TestDelay(&ppw, sample_rate, kNumChannels);
}
void TestRingingTime(AudioPostProcessor* pp, int sample_rate) {
AudioPostProcessorWrapper ppw(pp, kNumChannels);
TestRingingTime(&ppw, sample_rate, kNumChannels);
}
void TestPassthrough(AudioPostProcessor* pp, int sample_rate) {
AudioPostProcessorWrapper ppw(pp, kNumChannels);
TestPassthrough(&ppw, sample_rate, kNumChannels);
}
PostProcessorTest::PostProcessorTest() : sample_rate_(GetParam()) {}
PostProcessorTest::~PostProcessorTest() = default;
INSTANTIATE_TEST_SUITE_P(SampleRates,
PostProcessorTest,
::testing::Values(44100, 48000));
} // namespace post_processor_test
} // namespace media
} // namespace chromecast