Index: chromecast/media/cma/backend/alsa/slew_volume_unittests.cc |
diff --git a/chromecast/media/cma/backend/alsa/slew_volume_unittests.cc b/chromecast/media/cma/backend/alsa/slew_volume_unittests.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..08653e1a98e251a69bc3cfeb5bbab8aa0f6fa96c |
--- /dev/null |
+++ b/chromecast/media/cma/backend/alsa/slew_volume_unittests.cc |
@@ -0,0 +1,363 @@ |
+// 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 <cmath> |
+#include <cstdint> |
+#include <limits> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "base/memory/ptr_util.h" |
+#include "chromecast/media/cma/backend/alsa/slew_volume.h" |
+#include "media/base/audio_bus.h" |
+#include "media/base/vector_math.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace chromecast { |
+namespace media { |
+ |
+namespace { |
+ |
+const int kNumChannels = 2; |
+const int kNumFrames = 100; |
+const float kSinFrequency = 1.0f / kNumFrames; |
+const int kBytesPerSample = sizeof(int32_t); |
+ |
+// Frequency is in frames (frequency = frequency_in_hz / sample rate) |
+std::unique_ptr<::media::AudioBus> GetSineData(size_t frames, float frequency) { |
+ auto data = ::media::AudioBus::Create(kNumChannels, frames); |
+ std::vector<int32_t> sine(frames * 2); |
+ for (size_t i = 0; i < frames; ++i) { |
+ // Offset by 1 because sin(0) = 0 and the first value is a special case. |
+ sine[i * 2] = sin(static_cast<float>(i + 1) * frequency * 2 * M_PI) * |
+ std::numeric_limits<int32_t>::max(); |
+ sine[i * 2 + 1] = cos(static_cast<float>(i + 1) * frequency * 2 * M_PI) * |
+ std::numeric_limits<int32_t>::max(); |
+ } |
+ data->FromInterleaved(sine.data(), frames, kBytesPerSample); |
+ return data; |
+} |
+ |
+// Gets pointers to the data in an audiobus. |
+// If |swapped| is true, the channel order will be swapped. |
+std::vector<float*> GetDataChannels(::media::AudioBus* audio, |
+ bool swapped = false) { |
+ std::vector<float*> data(kNumChannels); |
+ for (int i = 0; i < kNumChannels; ++i) { |
+ int source_channel = swapped ? (i + 1) % kNumChannels : i; |
+ data[i] = audio->channel(source_channel); |
+ } |
+ return data; |
+} |
+ |
+void ScaleData(const std::vector<float*>& data, int frames, float scale) { |
+ for (size_t ch = 0; ch < data.size(); ++ch) { |
+ for (int f = 0; f < frames; ++f) { |
+ data[ch][f] *= scale; |
+ } |
+ } |
+} |
+ |
+void CompareDataPartial(const std::vector<float*>& expected, |
+ const std::vector<float*>& actual, |
+ int start, |
+ int end) { |
+ ASSERT_GE(start, 0); |
+ ASSERT_LT(start, end); |
+ ASSERT_EQ(expected.size(), actual.size()); |
+ |
+ for (size_t ch = 0; ch < expected.size(); ++ch) { |
+ for (int f = start; f < end; ++f) { |
+ EXPECT_FLOAT_EQ(expected[ch][f], actual[ch][f]) |
+ << "ch: " << ch << " f: " << f; |
+ } |
+ } |
+} |
+ |
+} // namespace |
+ |
+class SlewVolumeBaseTest : public ::testing::Test { |
+ protected: |
+ SlewVolumeBaseTest() = default; |
+ ~SlewVolumeBaseTest() override = default; |
+ |
+ void SetUp() override { |
+ slew_volume_ = base::MakeUnique<SlewVolume>(); |
+ slew_volume_->Interrupted(); |
+ MakeData(kNumFrames); |
+ } |
+ |
+ void MakeData(int num_frames) { |
+ num_frames_ = num_frames; |
+ data_bus_ = GetSineData(num_frames_, kSinFrequency); |
+ data_bus_2_ = GetSineData(num_frames_, kSinFrequency); |
+ expected_bus_ = GetSineData(num_frames_, kSinFrequency); |
+ data_ = GetDataChannels(data_bus_.get()); |
+ data_2_ = GetDataChannels(data_bus_2_.get(), true /* swapped */); |
+ expected_ = GetDataChannels(expected_bus_.get()); |
+ } |
+ |
+ void CompareBuffers(int start = 0, int end = -1) { |
+ if (end == -1) { |
+ end = num_frames_; |
+ } |
+ ASSERT_LE(end, num_frames_); |
bcf
2017/05/04 03:45:06
ASSERT_GE(end, 0)
bshaya
2017/05/04 18:15:41
Done.
|
+ CompareDataPartial(expected_, data_, start, end); |
+ } |
+ |
+ void ClearInterrupted() { |
+ float throwaway __attribute__((__aligned__(16))) = 0.0f; |
+ slew_volume_->ProcessFMUL(false, &throwaway, 1, &throwaway); |
+ } |
+ |
+ int num_frames_; |
+ |
+ std::unique_ptr<SlewVolume> slew_volume_; |
+ std::unique_ptr<::media::AudioBus> data_bus_; |
+ std::unique_ptr<::media::AudioBus> data_bus_2_; |
+ std::unique_ptr<::media::AudioBus> expected_bus_; |
+ std::vector<float*> data_; |
+ std::vector<float*> data_2_; |
+ std::vector<float*> expected_; |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(SlewVolumeBaseTest); |
+}; |
+ |
+TEST_F(SlewVolumeBaseTest, BadSampleRate) { |
+ ASSERT_DEATH(slew_volume_->SetSampleRate(0), "sample_rate"); |
+} |
+ |
+TEST_F(SlewVolumeBaseTest, BadSlewTime) { |
+ ASSERT_DEATH(slew_volume_->SetMaxSlewTimeMs(-1), ""); |
+} |
+ |
+class SlewVolumeSteadyStateTest : public SlewVolumeBaseTest { |
+ protected: |
+ SlewVolumeSteadyStateTest() = default; |
+ ~SlewVolumeSteadyStateTest() override = default; |
+ |
+ void SetUp() override { |
+ SlewVolumeBaseTest::SetUp(); |
+ slew_volume_->Interrupted(); |
+ } |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(SlewVolumeSteadyStateTest); |
+}; |
+ |
+TEST_F(SlewVolumeSteadyStateTest, FMULNoOp) { |
+ slew_volume_->SetVolume(1.0f); |
+ |
+ slew_volume_->ProcessFMUL(false /* repeat transition */, data_[0], |
+ num_frames_, data_[0]); |
+ slew_volume_->ProcessFMUL(true /* repeat transition */, data_[1], num_frames_, |
+ data_[1]); |
+ CompareBuffers(); |
+} |
+ |
+TEST_F(SlewVolumeSteadyStateTest, FMULCopy) { |
+ slew_volume_->SetVolume(1.0f); |
+ |
+ slew_volume_->ProcessFMUL(false /* repeat transition */, data_2_[0], |
+ num_frames_, data_[0]); |
+ slew_volume_->ProcessFMUL(true /* repeat transition */, data_2_[1], |
+ num_frames_, data_[1]); |
+ CompareDataPartial(data_2_, data_, 0, num_frames_); |
+} |
+ |
+TEST_F(SlewVolumeSteadyStateTest, FMULZero) { |
+ slew_volume_->SetVolume(0.0f); |
+ slew_volume_->ProcessFMUL(false, /* repeat transition */ |
+ data_[0], num_frames_, data_[0]); |
+ slew_volume_->ProcessFMUL(true /* repeat transition */, data_[1], num_frames_, |
+ data_[1]); |
+ |
+ for (size_t ch = 0; ch < data_.size(); ++ch) { |
+ for (int f = 0; f < num_frames_; ++f) { |
+ EXPECT_EQ(0.0f, data_[ch][f]) << "at ch " << ch << "frame " << f; |
+ } |
+ } |
+} |
+ |
+TEST_F(SlewVolumeSteadyStateTest, FMULInterrupted) { |
+ float volume = 0.6f; |
+ slew_volume_->SetVolume(volume); |
+ |
+ slew_volume_->ProcessFMUL(false /* repeat transition */, |
+ data_[0] /* source */, num_frames_, |
+ data_[0] /* dst */); |
+ slew_volume_->ProcessFMUL(true /* repeat transition */, data_[1] /* source */, |
+ num_frames_, data_[1] /* dst */); |
+ ScaleData(expected_, num_frames_, volume); |
+ CompareBuffers(); |
+} |
+ |
+TEST_F(SlewVolumeSteadyStateTest, FMACNoOp) { |
+ slew_volume_->SetVolume(0.0f); |
+ slew_volume_->ProcessFMAC(false /* repeat transition */, |
+ data_2_[0] /* source */, num_frames_, |
+ data_[0] /* dst */); |
+ slew_volume_->ProcessFMAC(false /* repeat transition */, |
+ data_2_[1] /* source */, num_frames_, |
+ data_[1] /*dst */); |
+ CompareBuffers(); |
+} |
+ |
+class SlewVolumeDynamicTest |
+ : public SlewVolumeBaseTest, |
+ public ::testing::WithParamInterface<std::tuple<int, int>> { |
+ protected: |
+ SlewVolumeDynamicTest() = default; |
+ ~SlewVolumeDynamicTest() override = default; |
+ |
+ void SetUp() override { |
+ SlewVolumeBaseTest::SetUp(); |
+ sample_rate_ = std::get<0>(GetParam()); |
+ slew_time_ms_ = std::get<1>(GetParam()); |
+ slew_time_frames_ = sample_rate_ * slew_time_ms_ / 1000; |
+ slew_volume_->SetSampleRate(sample_rate_); |
+ slew_volume_->SetMaxSlewTimeMs(slew_time_ms_); |
+ |
+ int num_frames = slew_time_frames_ + 2; // +2 frames for numeric errors. |
+ ASSERT_GE(num_frames, 1); |
+ MakeData(num_frames); |
+ } |
+ |
+ // Checks data_ = slew_volume_(expected_) |
+ void CheckSlewMUL(double start_vol, double end_vol) { |
+ for (size_t ch = 0; ch < data_.size(); ++ch) { |
+ // First value should have original scaling applied. |
+ EXPECT_FLOAT_EQ(expected_[ch][0] * start_vol, data_[ch][0]) << ch; |
+ |
+ // Steady state have final scaling applied |
+ int f = num_frames_ - 1; |
+ EXPECT_FLOAT_EQ(expected_[ch][f] * end_vol, data_[ch][f]) << ch; |
+ } |
+ } |
+ |
+ // Checks data_ = expected_ + slew_volume_(data_2_) |
+ void CheckSlewMAC(double start_vol, double end_vol) { |
+ for (size_t ch = 0; ch < data_.size(); ++ch) { |
+ // First value should have original scaling applied. |
+ EXPECT_FLOAT_EQ(expected_[ch][0] + data_2_[ch][0] * start_vol, |
+ data_[ch][0]) |
+ << ch; |
+ |
+ // Steady state have final scaling applied |
+ int f = num_frames_ - 1; |
+ EXPECT_FLOAT_EQ(expected_[ch][f] + data_2_[ch][f] * end_vol, data_[ch][f]) |
+ << ch << " " << f; |
+ } |
+ } |
+ |
+ int sample_rate_; |
+ int slew_time_ms_; |
+ int slew_time_frames_; |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(SlewVolumeDynamicTest); |
+}; |
+ |
+TEST_P(SlewVolumeDynamicTest, FMULRampUp) { |
+ double start = 0.0; |
+ double end = 1.0; |
+ slew_volume_->SetVolume(start); |
+ ClearInterrupted(); |
+ |
+ slew_volume_->SetVolume(end); |
+ slew_volume_->ProcessFMUL(false, data_[0], num_frames_, data_[0]); |
+ slew_volume_->ProcessFMUL(true, data_[1], num_frames_, data_[1]); |
+ CheckSlewMUL(start, end); |
+} |
+ |
+TEST_P(SlewVolumeDynamicTest, FMULRampDown) { |
+ double start = 1.0; |
+ double end = 0.0; |
+ slew_volume_->SetVolume(start); |
+ ClearInterrupted(); |
+ |
+ slew_volume_->SetVolume(end); |
+ slew_volume_->ProcessFMUL(false, data_[0], num_frames_, data_[0]); |
+ slew_volume_->ProcessFMUL(true, data_[1], num_frames_, data_[1]); |
+ CheckSlewMUL(start, end); |
+} |
+ |
+// Provide data as small buffers. |
+TEST_P(SlewVolumeDynamicTest, FMULRampDownByParts) { |
+ double start = 1.0; |
+ double end = 0.0; |
+ slew_volume_->SetVolume(start); |
+ ClearInterrupted(); |
+ |
+ slew_volume_->SetVolume(end); |
+ int frame_step = ::media::vector_math::kRequiredAlignment / kBytesPerSample; |
+ int f; |
+ for (f = 0; f < num_frames_; f += frame_step) { |
+ // Process any remaining samples in the last step. |
+ if (num_frames_ - f < frame_step * 2) { |
+ frame_step = num_frames_ - f; |
+ } |
+ slew_volume_->ProcessFMUL(false, expected_[0] + f, frame_step, |
+ data_[0] + f); |
+ slew_volume_->ProcessFMUL(true, expected_[1] + f, frame_step, data_[1] + f); |
+ } |
+ ASSERT_EQ(num_frames_, f); |
+ CheckSlewMUL(start, end); |
+} |
+ |
+TEST_P(SlewVolumeDynamicTest, FMACRampUp) { |
+ double start = 0.0; |
+ double end = 1.0; |
+ slew_volume_->SetVolume(start); |
+ ClearInterrupted(); |
+ |
+ slew_volume_->SetVolume(end); |
+ slew_volume_->ProcessFMAC(false, data_2_[0], num_frames_, data_[0]); |
+ slew_volume_->ProcessFMAC(true, data_2_[1], num_frames_, data_[1]); |
+ CheckSlewMAC(start, end); |
+} |
+ |
+TEST_P(SlewVolumeDynamicTest, FMACRampDown) { |
+ double start = 1.0; |
+ double end = 0.0; |
+ slew_volume_->SetVolume(start); |
+ ClearInterrupted(); |
+ |
+ slew_volume_->SetVolume(end); |
+ slew_volume_->ProcessFMAC(false, data_2_[0], num_frames_, data_[0]); |
+ slew_volume_->ProcessFMAC(true, data_2_[1], num_frames_, data_[1]); |
+ CheckSlewMAC(start, end); |
+} |
+ |
+// Provide data as small buffers. |
+TEST_P(SlewVolumeDynamicTest, FMACRampUpByParts) { |
+ double start = 0.0; |
+ double end = 1.0; |
+ slew_volume_->SetVolume(start); |
+ ClearInterrupted(); |
+ |
+ slew_volume_->SetVolume(end); |
+ int frame_step = ::media::vector_math::kRequiredAlignment / kBytesPerSample; |
+ int f; |
+ for (f = 0; f < num_frames_; f += frame_step) { |
+ // Process any remaining samples in the last step. |
+ if (num_frames_ - f < frame_step * 2) { |
+ frame_step = num_frames_ - f; |
+ } |
+ slew_volume_->ProcessFMAC(false, data_2_[0] + f, frame_step, data_[0] + f); |
+ slew_volume_->ProcessFMAC(true, data_2_[1] + f, frame_step, data_[1] + f); |
+ } |
+ ASSERT_EQ(num_frames_, f); |
+ CheckSlewMAC(start, end); |
+} |
+ |
+INSTANTIATE_TEST_CASE_P(SingleBufferSlew, |
+ SlewVolumeDynamicTest, |
+ ::testing::Combine(::testing::Values(44100, 48000), |
+ ::testing::Values(0, 15, 100))); |
+} // namespace media |
+} // namespace chromecast |