| Index: chromecast/media/cma/backend/alsa/post_processors/post_processor_unittest.cc
|
| diff --git a/chromecast/media/cma/backend/alsa/post_processors/post_processor_unittest.cc b/chromecast/media/cma/backend/alsa/post_processors/post_processor_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..bba39b88a13f1f54b30210e960e4aa370b409f63
|
| --- /dev/null
|
| +++ b/chromecast/media/cma/backend/alsa/post_processors/post_processor_unittest.cc
|
| @@ -0,0 +1,208 @@
|
| +// 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/alsa/post_processors/post_processor_unittest.h"
|
| +
|
| +#include <algorithm>
|
| +#include <cmath>
|
| +#include <limits>
|
| +
|
| +#include "base/logging.h"
|
| +
|
| +namespace chromecast {
|
| +namespace media {
|
| +
|
| +namespace {
|
| +
|
| +const float kEpsilon = std::numeric_limits<float>::epsilon();
|
| +
|
| +} // namespace
|
| +
|
| +namespace post_processor_test {
|
| +
|
| +void TestDelay(AudioPostProcessor* pp, int sample_rate) {
|
| + EXPECT_TRUE(pp->SetSampleRate(sample_rate));
|
| +
|
| + const int kNumFrames = GetMaximumFrames(sample_rate);
|
| + const int kSinFreq = 2000;
|
| + std::vector<float> data = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| + std::vector<float> expected = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| +
|
| + int delay_frames = pp->ProcessFrames(data.data(), kNumFrames, 1.0);
|
| + int delayed_frames = 0;
|
| +
|
| + // PostProcessors that run in dedicated threads may need to delay
|
| + // until they get data processed asyncronously.
|
| + while (delay_frames >= delayed_frames + kNumFrames) {
|
| + delayed_frames += kNumFrames;
|
| + for (int i = 0; i < kNumFrames * kNumChannels; ++i) {
|
| + EXPECT_EQ(0.0f, data[i]) << i;
|
| + }
|
| + std::vector<float> data = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| + delay_frames = pp->ProcessFrames(data.data(), kNumFrames, 1.0);
|
| +
|
| + ASSERT_GE(delay_frames, delayed_frames);
|
| + }
|
| +
|
| + for (int ch = 0; ch < kNumChannels; ++ch) {
|
| + ASSERT_NE(expected[ch], 0.0);
|
| +
|
| + int nonzero_frame = delay_frames - delayed_frames;
|
| + for (int f = 0; f < nonzero_frame; ++f) {
|
| + EXPECT_EQ(0.0f, data[f * kNumChannels + ch]) << ch << " " << f;
|
| + }
|
| + EXPECT_NE(0.0f, data[nonzero_frame * kNumChannels + ch])
|
| + << ch << " " << nonzero_frame;
|
| + }
|
| +}
|
| +
|
| +void TestRingingTime(AudioPostProcessor* pp, int sample_rate) {
|
| + EXPECT_TRUE(pp->SetSampleRate(sample_rate));
|
| +
|
| + const int kNumFrames = GetMaximumFrames(sample_rate);
|
| + const int kSinFreq = 200;
|
| + int ringing_time_frames = pp->GetRingingTimeInFrames();
|
| + std::vector<float> data;
|
| +
|
| + // Send a second of data to excite the filter.
|
| + for (int i = 0; i < sample_rate; i += kNumFrames) {
|
| + data = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| + pp->ProcessFrames(data.data(), kNumFrames, 1.0);
|
| + }
|
| + // Compute the amplitude of the last buffer
|
| + float original_amplitude = SineAmplitude(data, kNumChannels);
|
| +
|
| + EXPECT_GE(original_amplitude, 0)
|
| + << "Output of nonzero data is 0; cannot test ringing";
|
| +
|
| + // Feed |ringing_time_frames| of silence.
|
| + if (ringing_time_frames > 0) {
|
| + int frames_remaining = ringing_time_frames;
|
| + int frames_to_process = std::min(ringing_time_frames, kNumFrames);
|
| + while (frames_remaining > 0) {
|
| + frames_to_process = std::min(frames_to_process, frames_remaining);
|
| + data.assign(frames_to_process * kNumChannels, 0);
|
| + pp->ProcessFrames(data.data(), frames_to_process, 1.0);
|
| + frames_remaining -= frames_to_process;
|
| + }
|
| + }
|
| +
|
| + // Send a little more data and ensure the amplitude is < 1% the original.
|
| + const int probe_frames = 100;
|
| + data.assign(probe_frames * kNumChannels, 0);
|
| + pp->ProcessFrames(data.data(), probe_frames, 1.0);
|
| +
|
| + EXPECT_LE(SineAmplitude(data, probe_frames * kNumChannels),
|
| + original_amplitude * 0.01);
|
| +}
|
| +
|
| +void TestPassthrough(AudioPostProcessor* pp, int sample_rate) {
|
| + EXPECT_TRUE(pp->SetSampleRate(sample_rate));
|
| +
|
| + const int kNumFrames = GetMaximumFrames(sample_rate);
|
| + const int kSinFreq = 2000;
|
| + std::vector<float> data = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| + std::vector<float> expected = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| +
|
| + int delay_frames = pp->ProcessFrames(data.data(), kNumFrames, 1.0);
|
| + int delayed_frames = 0;
|
| +
|
| + // PostProcessors that run in dedicated threads may need to delay
|
| + // until they get data processed asyncronously.
|
| + while (delay_frames >= delayed_frames + kNumFrames) {
|
| + delayed_frames += kNumFrames;
|
| + for (int i = 0; i < kNumFrames * kNumChannels; ++i) {
|
| + EXPECT_EQ(0.0f, data[i]) << i;
|
| + }
|
| + std::vector<float> data = GetSineData(kNumFrames, kSinFreq, sample_rate);
|
| + delay_frames = pp->ProcessFrames(data.data(), kNumFrames, 1.0);
|
| +
|
| + ASSERT_GE(delay_frames, delayed_frames);
|
| + }
|
| +
|
| + int delay_samples = (delay_frames - delayed_frames) * kNumChannels;
|
| + ASSERT_LE(delay_samples, static_cast<int>(data.size()));
|
| +
|
| + CheckArraysEqual(expected.data(), data.data() + delay_samples,
|
| + data.size() - delay_samples);
|
| +}
|
| +
|
| +int GetMaximumFrames(int sample_rate) {
|
| + return kMaxAudioWriteTimeMilliseconds * sample_rate / 1000;
|
| +}
|
| +
|
| +template <typename T>
|
| +void CheckArraysEqual(T* expected, 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(T* expected, 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(T* array, size_t size) {
|
| + std::string result;
|
| + for (size_t i = 0; i < size; ++i) {
|
| + result += ::testing::PrintToString(array[i]) + " ";
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +template <typename T>
|
| +float SineAmplitude(std::vector<T> data, int num_channels) {
|
| + double max_power = 0;
|
| + int frames = data.size() / num_channels;
|
| + for (int ch = 0; ch < kNumChannels; ++ch) {
|
| + double power = 0;
|
| + for (int f = 0; f < frames; ++f) {
|
| + power += std::pow(data[f * num_channels + ch], 2);
|
| + }
|
| + max_power = std::max(max_power, power);
|
| + }
|
| + return std::sqrt(max_power / frames) * sqrt(2);
|
| +}
|
| +
|
| +std::vector<float> GetSineData(size_t frames,
|
| + float frequency,
|
| + int sample_rate) {
|
| + std::vector<float> sine(frames * kNumChannels);
|
| + for (size_t i = 0; i < frames; ++i) {
|
| + // Offset by a little so that first value is non-zero
|
| + sine[i * 2] =
|
| + sin(static_cast<float>(i + 1) * frequency * 2 * M_PI / sample_rate);
|
| + sine[i * 2 + 1] =
|
| + cos(static_cast<float>(i) * frequency * 2 * M_PI / sample_rate);
|
| + }
|
| + return sine;
|
| +}
|
| +
|
| +PostProcessorTest::PostProcessorTest() : sample_rate_(GetParam()) {}
|
| +PostProcessorTest::~PostProcessorTest() = default;
|
| +
|
| +INSTANTIATE_TEST_CASE_P(SampleRates,
|
| + PostProcessorTest,
|
| + ::testing::Values(44100, 48000));
|
| +
|
| +} // namespace post_processor_test
|
| +} // namespace media
|
| +} // namespace chromecast
|
|
|