blob: 750011638d21553597b6440a620bf6714fcbbf6d [file] [log] [blame]
// Copyright (c) 2014 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 <stddef.h>
#include <cmath>
#include <memory>
#include <vector>
#include "media/base/audio_bus.h"
#include "media/base/audio_shifter.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
const int kSampleRate = 48000;
const int kInputPacketSize = 48;
const int kOutputPacketSize = 24;
class AudioShifterTest :
public ::testing::TestWithParam<::testing::tuple<int, int, int, bool> > {
public:
AudioShifterTest()
: shifter_(base::TimeDelta::FromMilliseconds(2000),
base::TimeDelta::FromMilliseconds(3),
base::TimeDelta::FromMilliseconds(100),
kSampleRate,
2),
end2end_latency_(base::TimeDelta::FromMilliseconds(30)),
playback_latency_(base::TimeDelta::FromMilliseconds(10)),
tag_input_(false),
expect_smooth_output_(true),
input_sample_n_(0),
output_sample_(0) {
}
void SetupInput(int size, base::TimeDelta rate) {
input_size_ = size;
input_rate_ = rate;
}
std::unique_ptr<AudioBus> CreateTestInput() {
std::unique_ptr<AudioBus> input(AudioBus::Create(2, input_size_));
for (size_t i = 0; i < input_size_; i++) {
input->channel(0)[i] = input->channel(1)[i] = input_sample_n_;
input_sample_n_++;
}
if (tag_input_) {
input->channel(0)[0] = 10000000.0;
tag_input_ = false;
expect_smooth_output_ = false;
}
return input;
}
void SetupOutput(int size, base::TimeDelta rate) {
test_output_ = AudioBus::Create(2, size);
output_rate_ = rate;
}
void SetUp() override {
SetupInput(
kInputPacketSize + ::testing::get<0>(GetParam()) - 1,
base::TimeDelta::FromMicroseconds(
1000 + ::testing::get<1>(GetParam()) * 5 - 5));
SetupOutput(
kOutputPacketSize,
base::TimeDelta::FromMicroseconds(
500 + ::testing::get<2>(GetParam()) * 3 - 3));
if (::testing::get<3>(GetParam())) {
end2end_latency_ = -end2end_latency_;
}
}
void Run(size_t loops) {
for (size_t i = 0; i < loops;) {
if (now_ >= time_to_push_) {
shifter_.Push(CreateTestInput(), now_ + end2end_latency_);
time_to_push_ += input_rate_;
i++;
}
if (now_ >= time_to_pull_) {
shifter_.Pull(test_output_.get(), now_ + playback_latency_);
bool silence = true;
for (size_t j = 0;
j < static_cast<size_t>(test_output_->frames());
j++) {
if (test_output_->channel(0)[j] != 0.0) {
silence = false;
if (test_output_->channel(0)[j] > 3000000.0) {
marker_outputs_.push_back(
now_ + playback_latency_ +
base::TimeDelta::FromSeconds(j) / kSampleRate);
} else {
// We don't expect smooth output once we insert a tag,
// or in the very beginning.
if (expect_smooth_output_ && output_sample_ > 500.0) {
EXPECT_GT(test_output_->channel(0)[j], output_sample_ - 3)
<< "j = " << j;
if (test_output_->channel(0)[j] >
output_sample_ + kOutputPacketSize / 2) {
skip_outputs_.push_back(now_ + playback_latency_);
}
}
output_sample_ = test_output_->channel(0)[j];
}
}
}
if (silence) {
silent_outputs_.push_back(now_);
}
time_to_pull_ += output_rate_;
}
now_ += std::min(time_to_push_ - now_,
time_to_pull_ - now_);
}
}
void RunAndCheckSync(size_t loops) {
Run(100);
size_t expected_silent_outputs = silent_outputs_.size();
Run(loops);
tag_input_ = true;
CHECK(marker_outputs_.empty());
base::TimeTicks expected_mark_time = time_to_push_ + end2end_latency_;
Run(100);
if (end2end_latency_ > base::TimeDelta()) {
CHECK(!marker_outputs_.empty());
base::TimeDelta actual_offset = marker_outputs_[0] - expected_mark_time;
EXPECT_LT(actual_offset, base::TimeDelta::FromMicroseconds(100));
EXPECT_GT(actual_offset, base::TimeDelta::FromMicroseconds(-100));
} else {
EXPECT_GT(marker_outputs_.size(), 0UL);
}
EXPECT_EQ(expected_silent_outputs, silent_outputs_.size());
}
protected:
AudioShifter shifter_;
base::TimeDelta input_rate_;
base::TimeDelta output_rate_;
base::TimeDelta end2end_latency_;
base::TimeDelta playback_latency_;
base::TimeTicks time_to_push_;
base::TimeTicks time_to_pull_;
base::TimeTicks now_;
std::unique_ptr<AudioBus> test_input_;
std::unique_ptr<AudioBus> test_output_;
std::vector<base::TimeTicks> silent_outputs_;
std::vector<base::TimeTicks> skip_outputs_;
std::vector<base::TimeTicks> marker_outputs_;
size_t input_size_;
bool tag_input_;
bool expect_smooth_output_;
size_t input_sample_n_;
double output_sample_;
};
TEST_P(AudioShifterTest, TestSync) {
RunAndCheckSync(1000);
EXPECT_EQ(0UL, skip_outputs_.size());
}
TEST_P(AudioShifterTest, TestSyncWithPush) {
// Push some extra audio.
shifter_.Push(CreateTestInput(), now_ - base::TimeDelta(input_rate_));
RunAndCheckSync(1000);
EXPECT_LE(skip_outputs_.size(), 2UL);
}
TEST_P(AudioShifterTest, TestSyncWithPull) {
// Output should smooth out eventually, but that is not tested yet.
expect_smooth_output_ = false;
Run(100);
for (int i = 0; i < 100; i++) {
shifter_.Pull(test_output_.get(),
now_ + base::TimeDelta::FromMilliseconds(i));
}
RunAndCheckSync(1000);
EXPECT_LE(skip_outputs_.size(), 1UL);
}
TEST_P(AudioShifterTest, UnderOverFlow) {
expect_smooth_output_ = false;
SetupInput(
kInputPacketSize + ::testing::get<0>(GetParam()) * 10 - 10,
base::TimeDelta::FromMicroseconds(
1000 + ::testing::get<1>(GetParam()) * 100 - 100));
SetupOutput(
kOutputPacketSize,
base::TimeDelta::FromMicroseconds(
500 + ::testing::get<2>(GetParam()) * 50 - 50));
// Sane output is not expected, but let's make sure we don't crash.
Run(1000);
}
// Note: First argument is optional and intentionally left blank.
// (it's a prefix for the generated test cases)
INSTANTIATE_TEST_SUITE_P(,
AudioShifterTest,
::testing::Combine(::testing::Range(0, 3),
::testing::Range(0, 3),
::testing::Range(0, 3),
::testing::Bool()));
} // namespace media