// Copyright 2019 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 <tuple>
#include "base/check_op.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "chromecast/media/api/audio_clock_simulator.h"
#include "media/base/sinc_resampler.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;
namespace chromecast {
namespace media {
namespace {
constexpr int kSampleRate = 48000;
constexpr size_t kDefaultChannels = 1;
constexpr int kBufferSize = 4096;
int64_t FramesToTime(int64_t frames, int sample_rate) {
return std::round(frames * 1000000.0 / sample_rate);
}
class FakeAudioProvider : public AudioProvider {
public:
explicit FakeAudioProvider(size_t num_channels)
: num_channels_(num_channels) {
DCHECK_GT(num_channels_, 0u);
ON_CALL(*this, FillFrames)
.WillByDefault(Invoke(this, &FakeAudioProvider::FillFramesImpl));
}
void SetFillCallback(base::RepeatingCallback<void()> callback) {
fill_callback_ = std::move(callback);
}
// AudioProvider implementation:
MOCK_METHOD(int, FillFrames, (int, int64_t, float* const*));
size_t num_channels() const override { return num_channels_; }
int sample_rate() const override { return kSampleRate; }
int FillFramesImpl(int num_frames,
int64_t playout_timestamp,
float* const* channel_data) {
for (int f = 0; f < num_frames; ++f) {
for (size_t c = 0; c < num_channels_; ++c) {
channel_data[c][f] = static_cast<float>(next_ + f);
}
}
next_ += num_frames;
if (fill_callback_) {
fill_callback_.Run();
}
return num_frames;
}
int consumed() { return next_; }
private:
const size_t num_channels_;
int next_ = 0;
base::RepeatingCallback<void()> fill_callback_;
};
using TestParams =
std::tuple<int /* input request_size */, double /* clock_rate */>;
} // namespace
class AudioClockSimulatorTest : public testing::TestWithParam<TestParams> {
public:
AudioClockSimulatorTest() = default;
~AudioClockSimulatorTest() override = default;
};
TEST_P(AudioClockSimulatorTest, Fill) {
const TestParams& params = GetParam();
const int request_size = testing::get<0>(params);
const double clock_rate = testing::get<1>(params);
LOG(INFO) << "Request size = " << request_size
<< ", clock rate = " << clock_rate;
NiceMock<FakeAudioProvider> provider(kDefaultChannels);
auto clock = AudioClockSimulator::Create(&provider);
if (request_size > kBufferSize) {
return;
}
EXPECT_EQ(clock->SetRate(clock_rate), clock_rate);
float output[kBufferSize];
std::fill_n(output, kBufferSize, 0);
float* test_data[1] = {output};
int i;
for (i = 0; i + request_size <= kBufferSize; i += request_size) {
test_data[0] = output + i;
int64_t timestamp = FramesToTime(i, kSampleRate);
EXPECT_CALL(provider, FillFrames(_, _, _)).Times(testing::AnyNumber());
// Timestamp for requests to provider should not be before current fill
// timestamp.
EXPECT_CALL(provider, FillFrames(_, testing::Lt(timestamp), _)).Times(0);
int provided = clock->FillFrames(request_size, timestamp, test_data);
EXPECT_EQ(provided, request_size);
double delay = clock->DelayFrames();
EXPECT_GE(delay, 0);
testing::Mock::VerifyAndClearExpectations(&provider);
}
}
TEST(AudioClockSimulatorTest, ChangeRateDuringFill) {
NiceMock<FakeAudioProvider> provider(2);
auto clock = AudioClockSimulator::Create(&provider);
double rates[] = {0.9999, 1.0001, 0.9998, 1.0002, 1.0};
int rate_index = 0;
provider.SetFillCallback(base::BindRepeating(
[](AudioClockSimulator* clock, double* rates, int* rate_index) {
if (*rate_index >= 5) {
return;
}
clock->SetRate(rates[*rate_index]);
*rate_index += 1;
},
clock.get(), rates, &rate_index));
float output1[kBufferSize];
float output2[kBufferSize];
std::fill_n(output1, kBufferSize, 0);
std::fill_n(output2, kBufferSize, 0);
float* test_data[2] = {output1, output2};
int requested_frames = 0;
while (true) {
int64_t timestamp = FramesToTime(requested_frames, kSampleRate);
EXPECT_CALL(provider, FillFrames(_, _, _)).Times(testing::AnyNumber());
int provided = clock->FillFrames(kBufferSize, timestamp, test_data);
EXPECT_EQ(provided, kBufferSize);
testing::Mock::VerifyAndClearExpectations(&provider);
if (rate_index >= 5) {
return;
}
}
}
INSTANTIATE_TEST_SUITE_P(
RequestSizes,
AudioClockSimulatorTest,
testing::Combine(::testing::Values(1, 2, 31, 63, 64, 65, 1000),
::testing::Values(1.0, 0.999, 1.001, 0.9995, 1.0005)));
class AudioClockSimulatorLongRunningTest
: public testing::TestWithParam<double> {
public:
AudioClockSimulatorLongRunningTest() = default;
~AudioClockSimulatorLongRunningTest() override = default;
};
TEST_P(AudioClockSimulatorLongRunningTest, Run) {
double rate = GetParam();
LOG(INFO) << "Rate = " << rate;
NiceMock<FakeAudioProvider> provider(kDefaultChannels);
auto clock = AudioClockSimulator::Create(&provider);
clock->SetRate(rate);
const int kRequestSize = 1000;
const int kIterations = 1000;
float output[kRequestSize];
float* test_data[1] = {output};
for (int i = 0; i < kIterations; ++i) {
int provided = clock->FillFrames(kRequestSize, 0, test_data);
EXPECT_EQ(provided, kRequestSize);
}
int input_frames = provider.consumed();
int output_frames = kRequestSize * kIterations;
EXPECT_GE(input_frames, std::floor(rate * output_frames));
EXPECT_LE(input_frames, std::ceil(rate * output_frames) +
::media::SincResampler::kSmallRequestSize);
}
INSTANTIATE_TEST_SUITE_P(Rates,
AudioClockSimulatorLongRunningTest,
::testing::Values(1.0, 0.999, 1.001, 0.9995, 1.0005));
} // namespace media
} // namespace chromecast