blob: fc18fa73a44bca73a39099c9c6df98c1945cbdc1 [file] [log] [blame]
// Copyright 2016 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 <stdint.h>
#include <stdlib.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chromecast/base/task_runner_impl.h"
#include "chromecast/media/cma/base/decoder_buffer_adapter.h"
#include "chromecast/media/cma/base/decoder_config_adapter.h"
#include "chromecast/public/cast_media_shlib.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "chromecast/public/media/decoder_config.h"
#include "chromecast/public/media/media_pipeline_backend.h"
#include "chromecast/public/media/media_pipeline_device_params.h"
#include "chromecast/public/volume_control.h"
#include "media/audio/audio_device_description.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromecast {
namespace media {
class MultizoneBackendTest;
namespace {
// Total length of test, in microseconds.
const int64_t kPushTimeUs = 2 * base::Time::kMicrosecondsPerSecond;
const int64_t kStartPts = 0;
const int64_t kMaxRenderingDelayErrorUs = 200;
const int kNumEffectsStreams = 1;
const int64_t kNoTimestamp = std::numeric_limits<int64_t>::min();
void IgnoreEos() {}
class BufferFeeder : public MediaPipelineBackend::Decoder::Delegate {
public:
BufferFeeder(const AudioConfig& config,
bool effects_only,
base::OnceClosure eos_cb,
int playback_rate_change_count);
~BufferFeeder() override {}
void Initialize();
void Start(float playback_rate);
void Stop();
int64_t GetMaxRenderingDelayErrorUs() {
int64_t max = 0;
for (int64_t error : errors_) {
if (error == kNoTimestamp) {
continue;
}
max = std::max(max, std::abs(error));
}
return max;
}
private:
void FeedBuffer();
// MediaPipelineBackend::Decoder::Delegate implementation:
void OnPushBufferComplete(MediaPipelineBackend::BufferStatus status) override;
void OnEndOfStream() override;
void OnDecoderError() override {
DCHECK(thread_checker_.CalledOnValidThread());
if (effects_only_) {
feeding_completed_ = true;
} else {
ASSERT_TRUE(false);
}
}
void OnKeyStatusChanged(const std::string& key_id,
CastKeyStatus key_status,
uint32_t system_code) override {
DCHECK(thread_checker_.CalledOnValidThread());
ASSERT_TRUE(false);
}
void OnVideoResolutionChanged(const Size& size) override {
DCHECK(thread_checker_.CalledOnValidThread());
}
const AudioConfig config_;
const bool effects_only_;
base::OnceClosure eos_cb_;
const int64_t push_limit_us_;
const int playback_rate_change_count_;
const int64_t playback_rate_change_interval_us_;
float original_playback_rate_;
float playback_rate_;
std::vector<int64_t> errors_;
bool feeding_completed_;
TaskRunnerImpl task_runner_;
std::unique_ptr<MediaPipelineBackend> backend_;
MediaPipelineBackend::AudioDecoder* decoder_;
int64_t last_push_length_us_;
int64_t pushed_us_;
int64_t pushed_us_when_rate_changed_;
int64_t next_push_playback_timestamp_;
scoped_refptr<DecoderBufferBase> pending_buffer_;
base::ThreadChecker thread_checker_;
DISALLOW_COPY_AND_ASSIGN(BufferFeeder);
};
} // namespace
using TestParams = std::tuple<int /* sample rate */,
float /* playback rate */,
bool /* change_playback_rate */>;
class MultizoneBackendTest : public testing::TestWithParam<TestParams> {
public:
MultizoneBackendTest();
~MultizoneBackendTest() override;
void SetUp() override {
srand(12345);
CastMediaShlib::Initialize(base::CommandLine::ForCurrentProcess()->argv());
VolumeControl::Initialize(base::CommandLine::ForCurrentProcess()->argv());
}
void TearDown() override {
// Pipeline must be destroyed before finalizing media shlib.
audio_feeder_.reset();
effects_feeders_.clear();
VolumeControl::Finalize();
CastMediaShlib::Finalize();
}
void AddEffectsStreams();
void Initialize(int sample_rate, int playback_rate_change_count);
void Start(float playback_rate);
void OnEndOfStream();
private:
base::MessageLoop message_loop_;
std::vector<std::unique_ptr<BufferFeeder>> effects_feeders_;
std::unique_ptr<BufferFeeder> audio_feeder_;
DISALLOW_COPY_AND_ASSIGN(MultizoneBackendTest);
};
namespace {
BufferFeeder::BufferFeeder(const AudioConfig& config,
bool effects_only,
base::OnceClosure eos_cb,
int playback_rate_change_count)
: config_(config),
effects_only_(effects_only),
eos_cb_(std::move(eos_cb)),
push_limit_us_(effects_only_ ? 0 : kPushTimeUs),
playback_rate_change_count_(playback_rate_change_count),
playback_rate_change_interval_us_(push_limit_us_ /
(playback_rate_change_count + 1)),
original_playback_rate_(1.0f),
playback_rate_(1.0f),
feeding_completed_(false),
decoder_(nullptr),
last_push_length_us_(0),
pushed_us_(0),
pushed_us_when_rate_changed_(0),
next_push_playback_timestamp_(kNoTimestamp) {
CHECK(eos_cb_);
}
void BufferFeeder::Initialize() {
MediaPipelineDeviceParams params(
MediaPipelineDeviceParams::kModeIgnorePts,
effects_only_ ? MediaPipelineDeviceParams::kAudioStreamSoundEffects
: MediaPipelineDeviceParams::kAudioStreamNormal,
&task_runner_, AudioContentType::kMedia,
::media::AudioDeviceDescription::kDefaultDeviceId);
backend_.reset(CastMediaShlib::CreateMediaPipelineBackend(params));
CHECK(backend_);
decoder_ = backend_->CreateAudioDecoder();
CHECK(decoder_);
ASSERT_TRUE(decoder_->SetConfig(config_));
decoder_->SetDelegate(this);
ASSERT_TRUE(backend_->Initialize());
}
void BufferFeeder::Start(float playback_rate) {
// AMP devices only support playback rates between 0.5 and 2.0.
ASSERT_GE(playback_rate, 0.5f);
ASSERT_LE(playback_rate, 2.0f);
original_playback_rate_ = playback_rate_ = playback_rate;
ASSERT_TRUE(backend_->Start(kStartPts));
ASSERT_TRUE(backend_->SetPlaybackRate(playback_rate));
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&BufferFeeder::FeedBuffer, base::Unretained(this)));
}
void BufferFeeder::Stop() {
feeding_completed_ = true;
backend_->Stop();
}
void BufferFeeder::FeedBuffer() {
CHECK(decoder_);
if (feeding_completed_)
return;
if (playback_rate_change_count_ > 1 && !effects_only_ &&
pushed_us_ >
pushed_us_when_rate_changed_ + playback_rate_change_interval_us_) {
pushed_us_when_rate_changed_ = pushed_us_;
if (playback_rate_ != original_playback_rate_) {
playback_rate_ = original_playback_rate_;
} else if (original_playback_rate_ < 1.0) {
playback_rate_ = original_playback_rate_ * 2;
} else {
playback_rate_ = original_playback_rate_ / 2;
}
ASSERT_TRUE(backend_->SetPlaybackRate(playback_rate_));
// Changing the playback rate will change the rendering delay on devices
// where playback rate changes apply to audio that has already been pushed.
// Ignore the next rendering delay.
next_push_playback_timestamp_ = kNoTimestamp;
}
if (!effects_only_ && pushed_us_ >= push_limit_us_) {
pending_buffer_ = new media::DecoderBufferAdapter(
::media::DecoderBuffer::CreateEOSBuffer());
feeding_completed_ = true;
last_push_length_us_ = 0;
} else {
int size_bytes = (rand() % 96 + 32) * 16;
int num_samples =
size_bytes / (config_.bytes_per_channel * config_.channel_number);
last_push_length_us_ = num_samples * base::Time::kMicrosecondsPerSecond /
(config_.samples_per_second * playback_rate_);
scoped_refptr<::media::DecoderBuffer> silence_buffer(
new ::media::DecoderBuffer(size_bytes));
memset(silence_buffer->writable_data(), 0, silence_buffer->data_size());
pending_buffer_ = new media::DecoderBufferAdapter(silence_buffer);
pending_buffer_->set_timestamp(
base::TimeDelta::FromMicroseconds(pushed_us_));
}
BufferStatus status = decoder_->PushBuffer(pending_buffer_.get());
ASSERT_NE(status, MediaPipelineBackend::kBufferFailed);
if (status == MediaPipelineBackend::kBufferPending)
return;
OnPushBufferComplete(MediaPipelineBackend::kBufferSuccess);
}
void BufferFeeder::OnEndOfStream() {
DCHECK(thread_checker_.CalledOnValidThread());
std::move(eos_cb_).Run();
}
void BufferFeeder::OnPushBufferComplete(BufferStatus status) {
DCHECK(thread_checker_.CalledOnValidThread());
pending_buffer_ = nullptr;
if (!effects_only_) {
ASSERT_NE(status, MediaPipelineBackend::kBufferFailed);
MediaPipelineBackend::AudioDecoder::RenderingDelay delay =
decoder_->GetRenderingDelay();
int64_t error = kNoTimestamp;
if (delay.timestamp_microseconds == kNoTimestamp) {
next_push_playback_timestamp_ = kNoTimestamp;
} else {
if (next_push_playback_timestamp_ == kNoTimestamp) {
next_push_playback_timestamp_ =
delay.timestamp_microseconds + delay.delay_microseconds;
} else {
int64_t expected_next_push_playback_timestamp =
next_push_playback_timestamp_ + last_push_length_us_;
next_push_playback_timestamp_ =
delay.timestamp_microseconds + delay.delay_microseconds;
error = next_push_playback_timestamp_ -
expected_next_push_playback_timestamp;
}
}
errors_.push_back(error);
}
pushed_us_ += last_push_length_us_;
if (feeding_completed_)
return;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&BufferFeeder::FeedBuffer, base::Unretained(this)));
}
} // namespace
MultizoneBackendTest::MultizoneBackendTest() {}
MultizoneBackendTest::~MultizoneBackendTest() {}
void MultizoneBackendTest::Initialize(int sample_rate,
int playback_rate_change_count) {
AudioConfig config;
config.codec = kCodecPCM;
config.channel_layout = ChannelLayout::STEREO;
config.sample_format = kSampleFormatPlanarF32;
config.channel_number = 2;
config.bytes_per_channel = 4;
config.samples_per_second = sample_rate;
audio_feeder_ = std::make_unique<BufferFeeder>(
config, false /* effects_only */,
base::BindOnce(&MultizoneBackendTest::OnEndOfStream,
base::Unretained(this)),
playback_rate_change_count);
audio_feeder_->Initialize();
}
void MultizoneBackendTest::AddEffectsStreams() {
AudioConfig effects_config;
effects_config.codec = kCodecPCM;
effects_config.channel_layout = ChannelLayout::STEREO;
effects_config.sample_format = kSampleFormatS16;
effects_config.channel_number = 2;
effects_config.bytes_per_channel = 2;
effects_config.samples_per_second = 48000;
for (int i = 0; i < kNumEffectsStreams; ++i) {
auto feeder = std::make_unique<BufferFeeder>(
effects_config, true /* effects_only */, base::BindOnce(&IgnoreEos), 0);
feeder->Initialize();
effects_feeders_.push_back(std::move(feeder));
}
}
void MultizoneBackendTest::Start(float playback_rate) {
for (auto& feeder : effects_feeders_)
feeder->Start(1.0f);
CHECK(audio_feeder_);
audio_feeder_->Start(playback_rate);
base::RunLoop().Run();
}
void MultizoneBackendTest::OnEndOfStream() {
audio_feeder_->Stop();
for (auto& feeder : effects_feeders_)
feeder->Stop();
base::RunLoop::QuitCurrentWhenIdleDeprecated();
EXPECT_LT(audio_feeder_->GetMaxRenderingDelayErrorUs(),
kMaxRenderingDelayErrorUs);
}
TEST_P(MultizoneBackendTest, RenderingDelay) {
const TestParams& params = GetParam();
int sample_rate = testing::get<0>(params);
float playback_rate = testing::get<1>(params);
bool change_playback_rate = testing::get<2>(params);
int playback_rate_change_count = (change_playback_rate ? 1 : 0);
Initialize(sample_rate, playback_rate_change_count);
AddEffectsStreams();
Start(playback_rate);
}
TEST_F(MultizoneBackendTest, RenderingDelayWithMultipleRateChanges) {
Initialize(48000 /* sample_rate */, 10 /* playback_rate_change_count */);
AddEffectsStreams();
Start(1.0f /* playback_rate */);
}
INSTANTIATE_TEST_SUITE_P(
Required,
MultizoneBackendTest,
testing::Combine(::testing::Values(8000,
11025,
12000,
16000,
22050,
24000,
32000,
44100,
48000),
::testing::Values(0.5f, 0.99f, 1.0f, 1.01f, 2.0f),
::testing::Values(true)));
INSTANTIATE_TEST_SUITE_P(
Optional,
MultizoneBackendTest,
testing::Combine(::testing::Values(64000, 88200, 96000),
::testing::Values(1.0f),
::testing::Values(false)));
} // namespace media
} // namespace chromecast