// Copyright 2017 The Chromium Authors
// 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 <memory>
#include <vector>
#include "base/check.h"
#include "chromecast/media/base/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<::media::SignedInt32SampleTypeTraits>(sine.data(),
frames);
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 {
public:
SlewVolumeBaseTest(const SlewVolumeBaseTest&) = delete;
SlewVolumeBaseTest& operator=(const SlewVolumeBaseTest&) = delete;
protected:
SlewVolumeBaseTest() = default;
~SlewVolumeBaseTest() override = default;
void SetUp() override {
slew_volume_ = std::make_unique<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_GE(start, 0);
ASSERT_LT(start, end);
ASSERT_LE(end, num_frames_);
CompareDataPartial(expected_, data_, start, end);
}
void ClearInterrupted() {
float throwaway __attribute__((__aligned__(16))) = 0.0f;
slew_volume_->ProcessFMUL(false, &throwaway, 1, 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_;
};
// ASSERT_DEATH isn't implemented on Fuchsia.
#if defined(ASSERT_DEATH)
TEST_F(SlewVolumeBaseTest, BadSampleRate) {
// String arguments aren't passed to CHECK() in official builds.
#if defined(OFFICIAL_BUILD) && defined(NDEBUG)
ASSERT_DEATH(slew_volume_->SetSampleRate(0), "");
#else
ASSERT_DEATH(slew_volume_->SetSampleRate(0), "sample_rate");
#endif
}
TEST_F(SlewVolumeBaseTest, BadSlewTime) {
ASSERT_DEATH(slew_volume_->SetMaxSlewTimeMs(-1), "");
}
#endif // defined(ASSERT_DEATH)
TEST_F(SlewVolumeBaseTest, InstantVolumeDecreasing) {
slew_volume_->SetMaxSlewTimeMs(10);
slew_volume_->SetSampleRate(10000);
// Max slew per sample = 1000 / (max_time * sample_rate)
// = 0.01
slew_volume_->SetVolume(1.0);
ClearInterrupted();
slew_volume_->SetVolume(0.0);
const int kFramesPerTransaction = 10;
// LastVolume lags, so 101 steps are needed.
for (int i = 0; i < 101; i += kFramesPerTransaction) {
for (size_t ch = 0; ch < data_.size(); ++ch) {
slew_volume_->ProcessFMAC(ch != 0, data_[ch], 10, 1, data_2_[ch]);
}
ASSERT_FLOAT_EQ(1.0 - (0.01 * i), slew_volume_->LastBufferMaxMultiplier());
}
}
TEST_F(SlewVolumeBaseTest, InstantVolumeIncreasing) {
slew_volume_->SetMaxSlewTimeMs(10);
slew_volume_->SetSampleRate(10000);
// Max slew per sample = 1000 / (max_time * sample_rate)
// = 0.01
slew_volume_->SetVolume(0.0);
ClearInterrupted();
slew_volume_->SetVolume(1.0);
const int kFramesPerTransaction = 10;
// LastVolume leads, so 100 steps are needed.
for (int i = 0; i < 100; i += kFramesPerTransaction) {
for (size_t ch = 0; ch < data_.size(); ++ch) {
slew_volume_->ProcessFMAC(ch != 0, data_[ch], 10, 1, data_2_[ch]);
}
ASSERT_FLOAT_EQ(0.01 * (i + kFramesPerTransaction),
slew_volume_->LastBufferMaxMultiplier());
}
}
class SlewVolumeSteadyStateTest : public SlewVolumeBaseTest {
public:
SlewVolumeSteadyStateTest(const SlewVolumeSteadyStateTest&) = delete;
SlewVolumeSteadyStateTest& operator=(const SlewVolumeSteadyStateTest&) =
delete;
protected:
SlewVolumeSteadyStateTest() = default;
~SlewVolumeSteadyStateTest() override = default;
void SetUp() override {
SlewVolumeBaseTest::SetUp();
slew_volume_->Interrupted();
}
};
TEST_F(SlewVolumeSteadyStateTest, FMULNoOp) {
slew_volume_->SetVolume(1.0f);
slew_volume_->ProcessFMUL(false /* repeat transition */, data_[0],
num_frames_, 1, data_[0]);
slew_volume_->ProcessFMUL(true /* repeat transition */, data_[1], num_frames_,
1, data_[1]);
CompareBuffers();
}
TEST_F(SlewVolumeSteadyStateTest, FMULCopy) {
slew_volume_->SetVolume(1.0f);
slew_volume_->ProcessFMUL(false /* repeat transition */, data_2_[0],
num_frames_, 1, data_[0]);
slew_volume_->ProcessFMUL(true /* repeat transition */, data_2_[1],
num_frames_, 1, 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_, 1, data_[0]);
slew_volume_->ProcessFMUL(true, data_[1], num_frames_, 1, 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, data_[0], num_frames_, 1, data_[0]);
slew_volume_->ProcessFMUL(true, data_[1], num_frames_, 1, data_[1]);
ScaleData(expected_, num_frames_, volume);
CompareBuffers();
}
TEST_F(SlewVolumeSteadyStateTest, FMACNoOp) {
slew_volume_->SetVolume(0.0f);
slew_volume_->ProcessFMAC(false, data_2_[0], num_frames_, 1, data_[0]);
slew_volume_->ProcessFMAC(false, data_2_[1], num_frames_, 1, data_[1]);
CompareBuffers();
}
class SlewVolumeDynamicTest
: public SlewVolumeBaseTest,
public ::testing::WithParamInterface<std::tuple<int, int>> {
public:
SlewVolumeDynamicTest(const SlewVolumeDynamicTest&) = delete;
SlewVolumeDynamicTest& operator=(const SlewVolumeDynamicTest&) = delete;
protected:
SlewVolumeDynamicTest() = default;
~SlewVolumeDynamicTest() override = default;
void SetUp() override {
SlewVolumeBaseTest::SetUp();
channels_ = 2;
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_);
// +2 frames for numeric errors.
int num_frames = slew_time_frames_ + 2;
max_frame_ = num_frames - 1;
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;
for (int f = 1; f < slew_time_frames_; ++f) {
// Can't calculate gain if input is 0.
if (Expected(ch, f) == 0.0)
continue;
double actual_gain = Data(ch, f) / Expected(ch, f);
// Interpolate to get expected gain.
double frame_gain_change = (end_vol - start_vol) / (slew_time_frames_);
double expected_gain = frame_gain_change * f + start_vol;
EXPECT_LE(std::abs(actual_gain - expected_gain),
std::abs(frame_gain_change))
<< ch << " " << f;
}
// Steady state should have final scaling applied.
int f = max_frame_;
EXPECT_FLOAT_EQ(Expected(ch, f) * end_vol, Data(ch, f))
<< ch << " " << f
<< " Actual gain = " << Data(ch, f) / Expected(ch, f);
}
}
// Checks data_ = expected_ + slew_volume_(data_2_).
void CheckSlewMAC(double start_vol, double end_vol) {
for (int ch = 0; ch < channels_; ++ch) {
// First value should have original scaling applied.
EXPECT_FLOAT_EQ(Expected(ch, 0) + Data2(ch, 0) * start_vol, Data(ch, 0))
<< ch;
for (int f = 1; f < slew_time_frames_; ++f) {
// Can't calculate gain if input is 0.
if (Data2(ch, f) == 0.0)
continue;
double actual_gain = (Data(ch, f) - Expected(ch, f)) / Data2(ch, f);
// Interpolate to get expected gain.
double frame_gain_change = (end_vol - start_vol) / (slew_time_frames_);
double expected_gain = frame_gain_change * f + start_vol;
EXPECT_LE(std::abs(actual_gain - expected_gain),
std::abs(frame_gain_change))
<< f;
}
// Steady state should have final gain applied.
int f = max_frame_;
EXPECT_FLOAT_EQ(Expected(ch, f) + Data2(ch, f) * end_vol, Data(ch, f))
<< ch << " " << f << " Actual gain = "
<< (Data(ch, f) - Expected(ch, f)) / Data2(ch, f);
}
}
// Data accessors. Override to change access method of CheckSlewM[AC/UL].
virtual float Data(int channel, int frame) { return data_[channel][frame]; }
virtual float Data2(int channel, int frame) {
return data_2_[channel][frame];
}
virtual float Expected(int channel, int frame) {
return expected_[channel][frame];
}
int sample_rate_;
int slew_time_ms_;
int slew_time_frames_;
int channels_;
int max_frame_;
};
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_, 1, data_[0]);
slew_volume_->ProcessFMUL(true, data_[1], num_frames_, 1, 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_, 1, data_[0]);
slew_volume_->ProcessFMUL(true, data_[1], num_frames_, 1, 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, 1,
data_[0] + f);
slew_volume_->ProcessFMUL(true, expected_[1] + f, frame_step, 1,
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_, 1, data_[0]);
slew_volume_->ProcessFMAC(true, data_2_[1], num_frames_, 1, 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_, 1, data_[0]);
slew_volume_->ProcessFMAC(true, data_2_[1], num_frames_, 1, 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, 1,
data_[0] + f);
slew_volume_->ProcessFMAC(true, data_2_[1] + f, frame_step, 1,
data_[1] + f);
}
ASSERT_EQ(num_frames_, f);
CheckSlewMAC(start, end);
}
INSTANTIATE_TEST_SUITE_P(SingleBufferSlew,
SlewVolumeDynamicTest,
::testing::Combine(::testing::Values(44100, 48000),
::testing::Values(0, 15, 100)));
class SlewVolumeInterleavedTest : public SlewVolumeDynamicTest {
public:
SlewVolumeInterleavedTest(const SlewVolumeInterleavedTest&) = delete;
SlewVolumeInterleavedTest& operator=(const SlewVolumeInterleavedTest&) =
delete;
protected:
SlewVolumeInterleavedTest() = default;
~SlewVolumeInterleavedTest() override = default;
void SetUp() override {
slew_volume_ = std::make_unique<SlewVolume>();
slew_volume_->Interrupted();
channels_ = std::get<0>(GetParam());
sample_rate_ = 16000;
slew_time_ms_ = 20;
slew_time_frames_ = sample_rate_ * slew_time_ms_ / 1000;
slew_volume_->SetMaxSlewTimeMs(slew_time_ms_);
slew_volume_->SetSampleRate(sample_rate_);
num_frames_ = (2 + slew_time_frames_) * channels_;
max_frame_ = num_frames_ / channels_ - 1;
MakeData(num_frames_);
}
float Data(int channel, int frame) override {
return data_[0][channels_ * frame + channel];
}
float Data2(int channel, int frame) override {
return data_2_[0][channels_ * frame + channel];
}
float Expected(int channel, int frame) override {
return expected_[0][channels_ * frame + channel];
}
};
TEST_P(SlewVolumeInterleavedTest, 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_ / channels_,
channels_, data_[0]);
CheckSlewMAC(start, end);
}
TEST_P(SlewVolumeInterleavedTest, 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_ / channels_,
channels_, data_[0]);
CheckSlewMAC(start, end);
}
TEST_P(SlewVolumeInterleavedTest, 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_ / channels_, channels_,
data_[0]);
CheckSlewMUL(start, end);
}
TEST_P(SlewVolumeInterleavedTest, 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_ / channels_, channels_,
data_[0]);
CheckSlewMUL(start, end);
}
INSTANTIATE_TEST_SUITE_P(Interleaved,
SlewVolumeInterleavedTest,
::testing::Combine(::testing::Values(2, 4),
::testing::Values(0)));
} // namespace media
} // namespace chromecast