Index: chromecast/media/cma/backend/multizone_backend_unittest.cc |
diff --git a/chromecast/media/cma/backend/multizone_backend_unittest.cc b/chromecast/media/cma/backend/multizone_backend_unittest.cc |
index b471fa693d8dc9ef77568dd6299bbf7497f956e2..f3cfb91285f16efca8d9d41dbc8240a693a58897 100644 |
--- a/chromecast/media/cma/backend/multizone_backend_unittest.cc |
+++ b/chromecast/media/cma/backend/multizone_backend_unittest.cc |
@@ -11,8 +11,10 @@ |
#include <vector> |
#include "base/bind.h" |
+#include "base/bind_helpers.h" |
#include "base/command_line.h" |
#include "base/logging.h" |
+#include "base/memory/ptr_util.h" |
#include "base/memory/ref_counted.h" |
#include "base/message_loop/message_loop.h" |
#include "base/run_loop.h" |
@@ -40,9 +42,8 @@ class MultizoneBackendTest; |
namespace { |
-const int64_t kMicrosecondsPerSecond = 1000 * 1000; |
// Total length of test, in microseconds. |
-const int64_t kPushTimeUs = 2 * kMicrosecondsPerSecond; |
+const int64_t kPushTimeUs = 2 * base::Time::kMicrosecondsPerSecond; |
const int64_t kStartPts = 0; |
const int64_t kMaxRenderingDelayErrorUs = 200; |
const int kNumEffectsStreams = 1; |
@@ -54,27 +55,23 @@ class BufferFeeder : public MediaPipelineBackend::Decoder::Delegate { |
public: |
BufferFeeder(const AudioConfig& config, |
bool effects_only, |
- const base::Closure& eos_cb); |
+ base::OnceClosure eos_cb, |
+ int playback_rate_change_count); |
~BufferFeeder() override {} |
- void Initialize(float playback_rate); |
- void Start(); |
+ void Initialize(); |
+ void Start(float playback_rate); |
void Stop(); |
- int64_t max_rendering_delay_error_us() { |
- return max_rendering_delay_error_us_; |
- } |
- |
- int64_t max_positive_rendering_delay_error_us() { |
- return max_positive_rendering_delay_error_us_; |
- } |
- |
- int64_t max_negative_rendering_delay_error_us() { |
- return max_negative_rendering_delay_error_us_; |
- } |
- |
- int64_t average_rendering_delay_error_us() { |
- return total_rendering_delay_error_us_ / sample_count_; |
+ 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: |
@@ -103,21 +100,19 @@ class BufferFeeder : public MediaPipelineBackend::Decoder::Delegate { |
const AudioConfig config_; |
const bool effects_only_; |
- const base::Closure eos_cb_; |
- double original_playback_rate_; |
- double playback_rate_; |
- int64_t max_rendering_delay_error_us_; |
- int64_t max_positive_rendering_delay_error_us_; |
- int64_t max_negative_rendering_delay_error_us_; |
- int64_t total_rendering_delay_error_us_; |
- size_t sample_count_; |
+ base::OnceClosure eos_cb_; |
+ const int64_t push_limit_us_; |
+ const int64_t playback_rate_change_interval_us_; |
+ float original_playback_rate_; |
+ float playback_rate_; |
+ std::vector<int64_t> errors_; |
bool feeding_completed_; |
- std::unique_ptr<TaskRunnerImpl> task_runner_; |
+ TaskRunnerImpl task_runner_; |
std::unique_ptr<MediaPipelineBackend> backend_; |
MediaPipelineBackend::AudioDecoder* decoder_; |
- int64_t push_limit_us_; |
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_; |
@@ -127,8 +122,8 @@ class BufferFeeder : public MediaPipelineBackend::Decoder::Delegate { |
} // namespace |
-using TestParams = std::tr1::tuple<int, // sample rate |
- float>; // playback rate |
+using TestParams = |
+ std::tr1::tuple<int /* sample rate */, float /* playback rate */>; |
class MultizoneBackendTest : public testing::TestWithParam<TestParams> { |
public: |
@@ -155,11 +150,12 @@ class MultizoneBackendTest : public testing::TestWithParam<TestParams> { |
void AddEffectsStreams(); |
- void Initialize(int sample_rate, float playback_rate); |
- void Start(); |
+ 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_; |
@@ -170,34 +166,31 @@ namespace { |
BufferFeeder::BufferFeeder(const AudioConfig& config, |
bool effects_only, |
- const base::Closure& eos_cb) |
+ base::OnceClosure eos_cb, |
+ int playback_rate_change_count) |
: config_(config), |
effects_only_(effects_only), |
- eos_cb_(eos_cb), |
- original_playback_rate_(1.0), |
- playback_rate_(1.0), |
- max_rendering_delay_error_us_(0), |
- max_positive_rendering_delay_error_us_(0), |
- max_negative_rendering_delay_error_us_(0), |
- total_rendering_delay_error_us_(0), |
- sample_count_(0), |
+ eos_cb_(std::move(eos_cb)), |
+ push_limit_us_(effects_only_ ? 0 : kPushTimeUs), |
+ 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), |
- task_runner_(new TaskRunnerImpl()), |
decoder_(nullptr), |
- push_limit_us_(effects_only_ ? 0 : kPushTimeUs), |
last_push_length_us_(0), |
pushed_us_(0), |
+ pushed_us_when_rate_changed_(0), |
next_push_playback_timestamp_(kNoTimestamp) { |
- CHECK(!eos_cb_.is_null()); |
+ CHECK(eos_cb_); |
} |
-void BufferFeeder::Initialize(float playback_rate) { |
- original_playback_rate_ = playback_rate_ = playback_rate; |
+void BufferFeeder::Initialize() { |
MediaPipelineDeviceParams params( |
MediaPipelineDeviceParams::kModeIgnorePts, |
effects_only_ ? MediaPipelineDeviceParams::kAudioStreamSoundEffects |
: MediaPipelineDeviceParams::kAudioStreamNormal, |
- task_runner_.get(), AudioContentType::kMedia, |
+ &task_runner_, AudioContentType::kMedia, |
::media::AudioDeviceDescription::kDefaultDeviceId); |
backend_.reset(CastMediaShlib::CreateMediaPipelineBackend(params)); |
CHECK(backend_); |
@@ -208,13 +201,18 @@ void BufferFeeder::Initialize(float playback_rate) { |
decoder_->SetDelegate(this); |
ASSERT_TRUE(backend_->Initialize()); |
- ASSERT_TRUE(backend_->SetPlaybackRate(playback_rate)); |
} |
-void BufferFeeder::Start() { |
+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::Bind(&BufferFeeder::FeedBuffer, base::Unretained(this))); |
+ FROM_HERE, |
+ base::BindOnce(&BufferFeeder::FeedBuffer, base::Unretained(this))); |
} |
void BufferFeeder::Stop() { |
@@ -227,15 +225,21 @@ void BufferFeeder::FeedBuffer() { |
if (feeding_completed_) |
return; |
- if (!effects_only_ && pushed_us_ >= push_limit_us_ / 2 && |
- playback_rate_ == original_playback_rate_) { |
- if (original_playback_rate_ < 1.0) { |
+ if (!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; |
- ASSERT_TRUE(backend_->SetPlaybackRate(playback_rate_)); |
} else { |
playback_rate_ = original_playback_rate_ / 2; |
- ASSERT_TRUE(backend_->SetPlaybackRate(playback_rate_)); |
} |
+ 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_) { |
@@ -247,7 +251,7 @@ void BufferFeeder::FeedBuffer() { |
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 * kMicrosecondsPerSecond / |
+ 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)); |
@@ -265,7 +269,7 @@ void BufferFeeder::FeedBuffer() { |
void BufferFeeder::OnEndOfStream() { |
DCHECK(thread_checker_.CalledOnValidThread()); |
- eos_cb_.Run(); |
+ std::move(eos_cb_).Run(); |
} |
void BufferFeeder::OnPushBufferComplete(BufferStatus status) { |
@@ -277,6 +281,7 @@ void BufferFeeder::OnPushBufferComplete(BufferStatus status) { |
MediaPipelineBackend::AudioDecoder::RenderingDelay delay = |
decoder_->GetRenderingDelay(); |
+ int64_t error = kNoTimestamp; |
if (delay.timestamp_microseconds == kNoTimestamp) { |
next_push_playback_timestamp_ = kNoTimestamp; |
} else { |
@@ -288,22 +293,11 @@ void BufferFeeder::OnPushBufferComplete(BufferStatus status) { |
next_push_playback_timestamp_ + last_push_length_us_; |
next_push_playback_timestamp_ = |
delay.timestamp_microseconds + delay.delay_microseconds; |
- int64_t error = next_push_playback_timestamp_ - |
- expected_next_push_playback_timestamp; |
- |
- max_rendering_delay_error_us_ = |
- std::max(max_rendering_delay_error_us_, std::abs(error)); |
- total_rendering_delay_error_us_ += std::abs(error); |
- if (error >= 0) { |
- max_positive_rendering_delay_error_us_ = |
- std::max(max_positive_rendering_delay_error_us_, error); |
- } else { |
- max_negative_rendering_delay_error_us_ = |
- std::min(max_negative_rendering_delay_error_us_, error); |
- } |
- sample_count_++; |
+ error = next_push_playback_timestamp_ - |
+ expected_next_push_playback_timestamp; |
} |
} |
+ errors_.push_back(error); |
} |
pushed_us_ += last_push_length_us_; |
@@ -311,7 +305,8 @@ void BufferFeeder::OnPushBufferComplete(BufferStatus status) { |
return; |
base::ThreadTaskRunnerHandle::Get()->PostTask( |
- FROM_HERE, base::Bind(&BufferFeeder::FeedBuffer, base::Unretained(this))); |
+ FROM_HERE, |
+ base::BindOnce(&BufferFeeder::FeedBuffer, base::Unretained(this))); |
} |
} // namespace |
@@ -320,19 +315,21 @@ MultizoneBackendTest::MultizoneBackendTest() {} |
MultizoneBackendTest::~MultizoneBackendTest() {} |
-void MultizoneBackendTest::Initialize(int sample_rate, float playback_rate) { |
+void MultizoneBackendTest::Initialize(int sample_rate, |
+ int playback_rate_change_count) { |
AudioConfig config; |
config.codec = kCodecPCM; |
- config.sample_format = kSampleFormatS32; |
+ config.sample_format = kSampleFormatPlanarF32; |
config.channel_number = 2; |
config.bytes_per_channel = 4; |
config.samples_per_second = sample_rate; |
- audio_feeder_.reset( |
- new BufferFeeder(config, false /* effects_only */, |
- base::Bind(&MultizoneBackendTest::OnEndOfStream, |
- base::Unretained(this)))); |
- audio_feeder_->Initialize(playback_rate); |
+ audio_feeder_ = base::MakeUnique<BufferFeeder>( |
+ config, false /* effects_only */, |
+ base::BindOnce(&MultizoneBackendTest::OnEndOfStream, |
+ base::Unretained(this)), |
+ playback_rate_change_count); |
+ audio_feeder_->Initialize(); |
} |
void MultizoneBackendTest::AddEffectsStreams() { |
@@ -344,18 +341,19 @@ void MultizoneBackendTest::AddEffectsStreams() { |
effects_config.samples_per_second = 48000; |
for (int i = 0; i < kNumEffectsStreams; ++i) { |
- std::unique_ptr<BufferFeeder> feeder(new BufferFeeder( |
- effects_config, true /* effects_only */, base::Bind(&IgnoreEos))); |
- feeder->Initialize(1.0f); |
+ auto feeder = base::MakeUnique<BufferFeeder>( |
+ effects_config, true /* effects_only */, base::BindOnce(&IgnoreEos), 0); |
+ feeder->Initialize(); |
effects_feeders_.push_back(std::move(feeder)); |
} |
} |
-void MultizoneBackendTest::Start() { |
+void MultizoneBackendTest::Start(float playback_rate) { |
for (auto& feeder : effects_feeders_) |
- feeder->Start(); |
+ feeder->Start(1.0f); |
CHECK(audio_feeder_); |
- audio_feeder_->Start(); |
+ audio_feeder_->Start(playback_rate); |
+ base::RunLoop().Run(); |
} |
void MultizoneBackendTest::OnEndOfStream() { |
@@ -365,26 +363,24 @@ void MultizoneBackendTest::OnEndOfStream() { |
base::MessageLoop::current()->QuitWhenIdle(); |
- EXPECT_LT(audio_feeder_->max_rendering_delay_error_us(), |
- kMaxRenderingDelayErrorUs) |
- << "Max positive rendering delay error: " |
- << audio_feeder_->max_positive_rendering_delay_error_us() |
- << "\nMax negative rendering delay error: " |
- << audio_feeder_->max_negative_rendering_delay_error_us() |
- << "\nAverage rendering delay error: " |
- << audio_feeder_->average_rendering_delay_error_us(); |
+ EXPECT_LT(audio_feeder_->GetMaxRenderingDelayErrorUs(), |
+ kMaxRenderingDelayErrorUs); |
} |
TEST_P(MultizoneBackendTest, RenderingDelay) { |
- std::unique_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); |
const TestParams& params = GetParam(); |
int sample_rate = testing::get<0>(params); |
float playback_rate = testing::get<1>(params); |
- Initialize(sample_rate, playback_rate); |
+ Initialize(sample_rate, 1 /* playback_rate_change_count */); |
AddEffectsStreams(); |
- Start(); |
- base::RunLoop().Run(); |
+ Start(playback_rate); |
+} |
+ |
+TEST_F(MultizoneBackendTest, RenderingDelayWithMultipleRateChanges) { |
+ Initialize(48000 /* sample_rate */, 10 /* playback_rate_change_count */); |
+ AddEffectsStreams(); |
+ Start(1.0f /* playback_rate */); |
} |
INSTANTIATE_TEST_CASE_P( |