diff options
author | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-29 06:52:29 +0000 |
---|---|---|
committer | xhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-29 06:52:29 +0000 |
commit | a38edc462009e1591e4629b1e17c45d65d324ac5 (patch) | |
tree | 1be1c32fed59b74000598bd0f2e1e2d2e6135213 /media | |
parent | 9d70ced22d51dcec3cae44b7a4d4b000dc9ec00a (diff) | |
download | chromium_src-a38edc462009e1591e4629b1e17c45d65d324ac5.zip chromium_src-a38edc462009e1591e4629b1e17c45d65d324ac5.tar.gz chromium_src-a38edc462009e1591e4629b1e17c45d65d324ac5.tar.bz2 |
Add FakeVideoDecoder.
The FakeVideoDecoder simulates a typical video decoder that could have some
decoding delays (to simulate out-of-order decoding). Since it's fake,
it does not care about what the input is. It always generate output frames
successfully but with a frame delay (specified in the ctor).
All callbacks passed to FakeVideoDecoder can be held if desired. This gives the
test class a chance to do some operation during pending callbacks.
BUG=141788
Review URL: https://chromiumcodereview.appspot.com/15085011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@202789 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/callback_holder.h | 88 | ||||
-rw-r--r-- | media/base/callback_holder_unittest.cc | 125 | ||||
-rw-r--r-- | media/filters/fake_demuxer_stream.cc | 28 | ||||
-rw-r--r-- | media/filters/fake_demuxer_stream.h | 15 | ||||
-rw-r--r-- | media/filters/fake_demuxer_stream_unittest.cc | 60 | ||||
-rw-r--r-- | media/filters/fake_video_decoder.cc | 235 | ||||
-rw-r--r-- | media/filters/fake_video_decoder.h | 101 | ||||
-rw-r--r-- | media/filters/fake_video_decoder_unittest.cc | 434 | ||||
-rw-r--r-- | media/media.gyp | 5 |
9 files changed, 1060 insertions, 31 deletions
diff --git a/media/base/callback_holder.h b/media/base/callback_holder.h new file mode 100644 index 0000000..2ea5edb --- /dev/null +++ b/media/base/callback_holder.h @@ -0,0 +1,88 @@ +// Copyright 2013 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. + +#ifndef MEDIA_BASE_CALLBACK_HOLDER_H_ +#define MEDIA_BASE_CALLBACK_HOLDER_H_ + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "media/base/bind_to_loop.h" + +namespace media { + +// A helper class that can hold a callback from being fired. +template <typename CB> class CallbackHolder { + public: + CallbackHolder() : hold_(false) {} + + ~CallbackHolder() { + // Make sure all callbacks are satisfied! + DCHECK(!hold_); + DCHECK(original_cb_.is_null()); + DCHECK(held_cb_.is_null()); + } + + // Sets the callback to be potentially held. + void SetCallback(const CB& cb) { + DCHECK(original_cb_.is_null()); + DCHECK(held_cb_.is_null()); + original_cb_ = cb; + } + + bool IsNull() const { + return original_cb_.is_null() && held_cb_.is_null(); + } + + // Holds the callback when Run() is called. + void HoldCallback() { hold_ = true; } + + // Runs or holds the callback as specified by |hold_|. + // This method has overloaded versions to support different types of CB. + void RunOrHold() { + DCHECK(held_cb_.is_null()); + if (hold_) + held_cb_ = base::ResetAndReturn(&original_cb_); + else + base::ResetAndReturn(&original_cb_).Run(); + } + + template <typename A1> void RunOrHold(A1 a1) { + DCHECK(held_cb_.is_null()); + if (hold_) { + held_cb_ = base::Bind(base::ResetAndReturn(&original_cb_), + internal::TrampolineForward(a1)); + } else { + base::ResetAndReturn(&original_cb_).Run(a1); + } + } + + template <typename A1, typename A2> void RunOrHold(A1 a1, A2 a2) { + DCHECK(held_cb_.is_null()); + if (hold_) { + held_cb_ = base::Bind(base::ResetAndReturn(&original_cb_), + internal::TrampolineForward(a1), + internal::TrampolineForward(a2)); + } else { + base::ResetAndReturn(&original_cb_).Run(a1, a2); + } + } + + // Releases and runs the held callback. + void RunHeldCallback() { + DCHECK(hold_); + DCHECK(!held_cb_.is_null()); + hold_ = false; + base::ResetAndReturn(&held_cb_).Run(); + } + + private: + bool hold_; + CB original_cb_; + base::Closure held_cb_; +}; + +} // namespace media + +#endif // MEDIA_BASE_CALLBACK_HOLDER_H_ diff --git a/media/base/callback_holder_unittest.cc b/media/base/callback_holder_unittest.cc new file mode 100644 index 0000000..e06a930 --- /dev/null +++ b/media/base/callback_holder_unittest.cc @@ -0,0 +1,125 @@ +// Copyright 2013 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 "media/base/callback_holder.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static void SetBool(bool* var) { + DCHECK(!*var); + *var = true; +} + +static void CopyVar(int var1, int* var2) { + DCHECK_NE(var1, *var2); + *var2 = var1; +} + +TEST(CallbackHolderTest, SetAfterHold_Closure) { + CallbackHolder<base::Closure> cb; + EXPECT_TRUE(cb.IsNull()); + + cb.HoldCallback(); + + bool closure_called = false; + cb.SetCallback(base::Bind(&SetBool, &closure_called)); + EXPECT_FALSE(cb.IsNull()); + + cb.RunOrHold(); + EXPECT_FALSE(closure_called); + + EXPECT_FALSE(cb.IsNull()); + cb.RunHeldCallback(); + EXPECT_TRUE(cb.IsNull()); + EXPECT_TRUE(closure_called); +} + +TEST(CallbackHolderTest, HoldAfterSet_Closure) { + CallbackHolder<base::Closure> cb; + EXPECT_TRUE(cb.IsNull()); + + bool closure_called = false; + cb.SetCallback(base::Bind(&SetBool, &closure_called)); + EXPECT_FALSE(cb.IsNull()); + + cb.HoldCallback(); + + cb.RunOrHold(); + EXPECT_FALSE(closure_called); + EXPECT_FALSE(cb.IsNull()); + cb.RunHeldCallback(); + EXPECT_TRUE(cb.IsNull()); + EXPECT_TRUE(closure_called); +} + +TEST(CallbackHolderTest, NotHold_Closure) { + CallbackHolder<base::Closure> cb; + EXPECT_TRUE(cb.IsNull()); + + bool closure_called = false; + cb.SetCallback(base::Bind(&SetBool, &closure_called)); + EXPECT_FALSE(cb.IsNull()); + + cb.RunOrHold(); + EXPECT_TRUE(cb.IsNull()); + EXPECT_TRUE(closure_called); +} + +TEST(CallbackHolderTest, SetAfterHold_Callback) { + CallbackHolder<base::Callback<void(int, int*)> > cb; + EXPECT_TRUE(cb.IsNull()); + + cb.HoldCallback(); + + cb.SetCallback(base::Bind(&CopyVar)); + EXPECT_FALSE(cb.IsNull()); + + int var1 = 100; + int var2 = 0; + cb.RunOrHold(var1, &var2); + EXPECT_FALSE(cb.IsNull()); + EXPECT_NE(var1, var2); + + cb.RunHeldCallback(); + EXPECT_TRUE(cb.IsNull()); + EXPECT_EQ(var1, var2); +} + +TEST(CallbackHolderTest, HoldAfterSet_Callback) { + CallbackHolder<base::Callback<void(int, int*)> > cb; + EXPECT_TRUE(cb.IsNull()); + + cb.SetCallback(base::Bind(&CopyVar)); + EXPECT_FALSE(cb.IsNull()); + + cb.HoldCallback(); + + int var1 = 100; + int var2 = 0; + cb.RunOrHold(var1, &var2); + EXPECT_FALSE(cb.IsNull()); + EXPECT_NE(var1, var2); + + cb.RunHeldCallback(); + EXPECT_TRUE(cb.IsNull()); + EXPECT_EQ(var1, var2); +} + +TEST(CallbackHolderTest, NotHold_Callback) { + CallbackHolder<base::Callback<void(int, int*)> > cb; + EXPECT_TRUE(cb.IsNull()); + + cb.SetCallback(base::Bind(&CopyVar)); + EXPECT_FALSE(cb.IsNull()); + + int var1 = 100; + int var2 = 0; + cb.RunOrHold(var1, &var2); + EXPECT_TRUE(cb.IsNull()); + EXPECT_EQ(var1, var2); +} + +} // namespace media diff --git a/media/filters/fake_demuxer_stream.cc b/media/filters/fake_demuxer_stream.cc index 3cc7fa6..8fdd5d3 100644 --- a/media/filters/fake_demuxer_stream.cc +++ b/media/filters/fake_demuxer_stream.cc @@ -34,10 +34,12 @@ FakeDemuxerStream::FakeDemuxerStream(int num_configs, num_buffers_in_one_config_(num_buffers_in_one_config), is_encrypted_(is_encrypted), num_buffers_left_in_current_config_(num_buffers_in_one_config), + num_buffers_returned_(0), current_timestamp_(base::TimeDelta::FromMilliseconds(kStartTimestampMs)), duration_(base::TimeDelta::FromMilliseconds(kDurationMs)), next_coded_size_(kStartWidth, kStartHeight), - hold_next_read_(false) { + next_read_num_(0), + read_to_hold_(-1) { DCHECK_GT(num_configs_left_, 0); DCHECK_GT(num_buffers_in_one_config_, 0); UpdateVideoDecoderConfig(); @@ -51,8 +53,11 @@ void FakeDemuxerStream::Read(const ReadCB& read_cb) { read_cb_ = BindToCurrentLoop(read_cb); - if (!hold_next_read_) - DoRead(); + if (read_to_hold_ == next_read_num_) + return; + + DCHECK(read_to_hold_ == -1 || read_to_hold_ > next_read_num_); + DoRead(); } const AudioDecoderConfig& FakeDemuxerStream::audio_decoder_config() { @@ -78,19 +83,27 @@ void FakeDemuxerStream::EnableBitstreamConverter() { void FakeDemuxerStream::HoldNextRead() { DCHECK(message_loop_->BelongsToCurrentThread()); - hold_next_read_ = true; + read_to_hold_ = next_read_num_; +} + +void FakeDemuxerStream::HoldNextConfigChangeRead() { + DCHECK(message_loop_->BelongsToCurrentThread()); + // Set |read_to_hold_| to be the next config change read. + read_to_hold_ = next_read_num_ + num_buffers_in_one_config_ - + next_read_num_ % (num_buffers_in_one_config_ + 1); } void FakeDemuxerStream::SatisfyRead() { DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK(hold_next_read_); + DCHECK_EQ(read_to_hold_, next_read_num_); DCHECK(!read_cb_.is_null()); + read_to_hold_ = -1; DoRead(); } void FakeDemuxerStream::Reset() { - hold_next_read_ = false; + read_to_hold_ = -1; if (!read_cb_.is_null()) base::ResetAndReturn(&read_cb_).Run(kAborted, NULL); @@ -109,6 +122,8 @@ void FakeDemuxerStream::DoRead() { DCHECK(message_loop_->BelongsToCurrentThread()); DCHECK(!read_cb_.is_null()); + next_read_num_++; + if (num_buffers_left_in_current_config_ == 0) { // End of stream. if (num_configs_left_ == 0) { @@ -143,6 +158,7 @@ void FakeDemuxerStream::DoRead() { if (num_buffers_left_in_current_config_ == 0) num_configs_left_--; + num_buffers_returned_++; base::ResetAndReturn(&read_cb_).Run(kOk, buffer); } diff --git a/media/filters/fake_demuxer_stream.h b/media/filters/fake_demuxer_stream.h index 1b20ba7..1797599 100644 --- a/media/filters/fake_demuxer_stream.h +++ b/media/filters/fake_demuxer_stream.h @@ -35,10 +35,17 @@ class MEDIA_EXPORT FakeDemuxerStream : public DemuxerStream { virtual Type type() OVERRIDE; virtual void EnableBitstreamConverter() OVERRIDE; + int num_buffers_returned() const { return num_buffers_returned_; } + // Upon the next read, holds the read callback until SatisfyRead() or Reset() // is called. void HoldNextRead(); + // Upon the next config change read, holds the read callback until + // SatisfyRead() or Reset() is called. If there is no config change any more, + // no read will be held. + void HoldNextConfigChangeRead(); + // Satisfies the pending read with the next scheduled status and buffer. void SatisfyRead(); @@ -59,6 +66,8 @@ class MEDIA_EXPORT FakeDemuxerStream : public DemuxerStream { // Number of frames left with the current decoder config. int num_buffers_left_in_current_config_; + int num_buffers_returned_; + base::TimeDelta current_timestamp_; base::TimeDelta duration_; @@ -68,7 +77,11 @@ class MEDIA_EXPORT FakeDemuxerStream : public DemuxerStream { VideoDecoderConfig video_decoder_config_; ReadCB read_cb_; - bool hold_next_read_; + + int next_read_num_; + // Zero-based number indicating which read operation should be held. -1 means + // no read shall be held. + int read_to_hold_; DISALLOW_COPY_AND_ASSIGN(FakeDemuxerStream); }; diff --git a/media/filters/fake_demuxer_stream_unittest.cc b/media/filters/fake_demuxer_stream_unittest.cc index 3a63e96..aa9ee47 100644 --- a/media/filters/fake_demuxer_stream_unittest.cc +++ b/media/filters/fake_demuxer_stream_unittest.cc @@ -13,10 +13,10 @@ namespace media { -static const int kNumFramesInOneConfig = 9; -static const int kNumFramesToReadFirst = 5; +static const int kNumBuffersInOneConfig = 9; +static const int kNumBuffersToReadFirst = 5; static const int kNumConfigs = 3; -COMPILE_ASSERT(kNumFramesToReadFirst < kNumFramesInOneConfig, +COMPILE_ASSERT(kNumBuffersToReadFirst < kNumBuffersInOneConfig, do_not_read_too_many_buffers); COMPILE_ASSERT(kNumConfigs > 0, need_multiple_configs_to_trigger_config_change); @@ -45,21 +45,14 @@ class FakeDemuxerStreamTest : public testing::Test { void EnterNormalReadState() { stream_.reset( - new FakeDemuxerStream(kNumConfigs, kNumFramesInOneConfig, false)); - for (int i = 0; i < kNumFramesToReadFirst; ++i) - ReadAndExpect(OK); - } - - void EnterBeforeConfigChangedState() { - stream_.reset( - new FakeDemuxerStream(kNumConfigs, kNumFramesInOneConfig, false)); - for (int i = 0; i < kNumFramesInOneConfig; ++i) + new FakeDemuxerStream(kNumConfigs, kNumBuffersInOneConfig, false)); + for (int i = 0; i < kNumBuffersToReadFirst; ++i) ReadAndExpect(OK); } void EnterBeforeEOSState() { - stream_.reset(new FakeDemuxerStream(1, kNumFramesInOneConfig, false)); - for (int i = 0; i < kNumFramesInOneConfig; ++i) + stream_.reset(new FakeDemuxerStream(1, kNumBuffersInOneConfig, false)); + for (int i = 0; i < kNumBuffersInOneConfig; ++i) ReadAndExpect(OK); } @@ -106,6 +99,17 @@ class FakeDemuxerStreamTest : public testing::Test { ExpectReadResult(result); } + void ReadUntilPending() { + while (1) { + read_pending_ = true; + stream_->Read(base::Bind(&FakeDemuxerStreamTest::BufferReady, + base::Unretained(this))); + message_loop_.RunUntilIdle(); + if (read_pending_) + break; + } + } + void SatisfyReadAndExpect(ReadResult result) { EXPECT_TRUE(read_pending_); stream_->SatisfyRead(); @@ -124,18 +128,23 @@ class FakeDemuxerStreamTest : public testing::Test { } void TestRead(int num_configs, - int num_frames_in_one_config, + int num_buffers_in_one_config, bool is_encrypted) { stream_.reset(new FakeDemuxerStream( - num_configs, num_frames_in_one_config, is_encrypted)); + num_configs, num_buffers_in_one_config, is_encrypted)); + + int num_buffers_received = 0; const VideoDecoderConfig& config = stream_->video_decoder_config(); EXPECT_TRUE(config.IsValidConfig()); EXPECT_EQ(is_encrypted, config.is_encrypted()); for (int i = 0; i < num_configs; ++i) { - for (int j = 0; j < num_frames_in_one_config; ++j) + for (int j = 0; j < num_buffers_in_one_config; ++j) { ReadAndExpect(OK); + num_buffers_received++; + EXPECT_EQ(num_buffers_received, stream_->num_buffers_returned()); + } if (i == num_configs - 1) ReadAndExpect(EOS); @@ -145,6 +154,8 @@ class FakeDemuxerStreamTest : public testing::Test { // Will always get EOS after we hit EOS. ReadAndExpect(EOS); + + EXPECT_EQ(num_configs * num_buffers_in_one_config, num_buffers_received); } base::MessageLoop message_loop_; @@ -153,6 +164,7 @@ class FakeDemuxerStreamTest : public testing::Test { DemuxerStream::Status status_; scoped_refptr<DecoderBuffer> buffer_; bool read_pending_; + int num_buffers_received_; private: DISALLOW_COPY_AND_ASSIGN(FakeDemuxerStreamTest); @@ -166,7 +178,7 @@ TEST_F(FakeDemuxerStreamTest, Read_MultipleConfigs) { TestRead(3, 5, false); } -TEST_F(FakeDemuxerStreamTest, Read_OneFramePerConfig) { +TEST_F(FakeDemuxerStreamTest, Read_OneBufferPerConfig) { TestRead(3, 1, false); } @@ -182,9 +194,9 @@ TEST_F(FakeDemuxerStreamTest, HoldRead_Normal) { } TEST_F(FakeDemuxerStreamTest, HoldRead_BeforeConfigChanged) { - EnterBeforeConfigChangedState(); - stream_->HoldNextRead(); - ReadAndExpect(PENDING); + EnterNormalReadState(); + stream_->HoldNextConfigChangeRead(); + ReadUntilPending(); SatisfyReadAndExpect(CONFIG_CHANGED); } @@ -217,9 +229,9 @@ TEST_F(FakeDemuxerStreamTest, Reset_DuringPendingRead) { } TEST_F(FakeDemuxerStreamTest, Reset_BeforeConfigChanged) { - EnterBeforeConfigChangedState(); - stream_->HoldNextRead(); - ReadAndExpect(PENDING); + EnterNormalReadState(); + stream_->HoldNextConfigChangeRead(); + ReadUntilPending(); Reset(); ReadAndExpect(CONFIG_CHANGED); } diff --git a/media/filters/fake_video_decoder.cc b/media/filters/fake_video_decoder.cc new file mode 100644 index 0000000..026710a --- /dev/null +++ b/media/filters/fake_video_decoder.cc @@ -0,0 +1,235 @@ +// Copyright 2013 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 "media/filters/fake_video_decoder.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/location.h" +#include "base/message_loop_proxy.h" +#include "media/base/bind_to_loop.h" +#include "media/base/demuxer_stream.h" + +namespace media { + +FakeVideoDecoder::FakeVideoDecoder(int decoding_delay) + : message_loop_(base::MessageLoopProxy::current()), + weak_factory_(this), + decoding_delay_(decoding_delay), + state_(UNINITIALIZED), + demuxer_stream_(NULL) { + DCHECK_GE(decoding_delay, 0); +} + +FakeVideoDecoder::~FakeVideoDecoder() { + DCHECK_EQ(state_, UNINITIALIZED); +} + +void FakeVideoDecoder::Initialize(DemuxerStream* stream, + const PipelineStatusCB& status_cb, + const StatisticsCB& statistics_cb) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(stream); + DCHECK(stream->video_decoder_config().IsValidConfig()); + DCHECK(read_cb_.IsNull()) << "No reinitialization during pending read."; + DCHECK(reset_cb_.IsNull()) << "No reinitialization during pending reset."; + + weak_this_ = weak_factory_.GetWeakPtr(); + + demuxer_stream_ = stream; + statistics_cb_ = statistics_cb; + current_config_ = stream->video_decoder_config(); + init_cb_.SetCallback(BindToCurrentLoop(status_cb)); + + if (!decoded_frames_.empty()) { + DVLOG(1) << "Decoded frames dropped during reinitialization."; + decoded_frames_.clear(); + } + + state_ = NORMAL; + init_cb_.RunOrHold(PIPELINE_OK); +} + +void FakeVideoDecoder::Read(const ReadCB& read_cb) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(read_cb_.IsNull()) << "Overlapping decodes are not supported."; + DCHECK(reset_cb_.IsNull()); + DCHECK_LE(decoded_frames_.size(), static_cast<size_t>(decoding_delay_)); + + read_cb_.SetCallback(BindToCurrentLoop(read_cb)); + ReadFromDemuxerStream(); +} + +void FakeVideoDecoder::Reset(const base::Closure& closure) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(reset_cb_.IsNull()); + reset_cb_.SetCallback(BindToCurrentLoop(closure)); + + // Defer the reset if a read is pending. + if (!read_cb_.IsNull()) + return; + + DoReset(); +} + +void FakeVideoDecoder::Stop(const base::Closure& closure) { + DCHECK(message_loop_->BelongsToCurrentThread()); + stop_cb_.SetCallback(BindToCurrentLoop(closure)); + + // Defer the reset if a read and/or a reset is pending. + if (!read_cb_.IsNull() || !reset_cb_.IsNull()) + return; + + DoStop(); +} + +void FakeVideoDecoder::HoldNextInit() { + DCHECK(message_loop_->BelongsToCurrentThread()); + init_cb_.HoldCallback(); +} + +void FakeVideoDecoder::HoldNextRead() { + DCHECK(message_loop_->BelongsToCurrentThread()); + read_cb_.HoldCallback(); +} + +void FakeVideoDecoder::HoldNextReset() { + DCHECK(message_loop_->BelongsToCurrentThread()); + reset_cb_.HoldCallback(); +} + +void FakeVideoDecoder::HoldNextStop() { + DCHECK(message_loop_->BelongsToCurrentThread()); + stop_cb_.HoldCallback(); +} + +void FakeVideoDecoder::SatisfyInit() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(read_cb_.IsNull()); + DCHECK(reset_cb_.IsNull()); + + init_cb_.RunHeldCallback(); + + if (!stop_cb_.IsNull()) + DoStop(); +} + +void FakeVideoDecoder::SatisfyRead() { + DCHECK(message_loop_->BelongsToCurrentThread()); + read_cb_.RunHeldCallback(); + + if (!reset_cb_.IsNull()) + DoReset(); + + if (reset_cb_.IsNull() && !stop_cb_.IsNull()) + DoStop(); +} + +void FakeVideoDecoder::SatisfyReset() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(read_cb_.IsNull()); + reset_cb_.RunHeldCallback(); + + if (!stop_cb_.IsNull()) + DoStop(); +} + +void FakeVideoDecoder::SatisfyStop() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(read_cb_.IsNull()); + DCHECK(reset_cb_.IsNull()); + stop_cb_.RunHeldCallback(); +} + +void FakeVideoDecoder::ReadFromDemuxerStream() { + DCHECK_EQ(state_, NORMAL); + DCHECK(!read_cb_.IsNull()); + demuxer_stream_->Read(base::Bind(&FakeVideoDecoder::BufferReady, weak_this_)); +} + +void FakeVideoDecoder::BufferReady(DemuxerStream::Status status, + const scoped_refptr<DecoderBuffer>& buffer) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_EQ(state_, NORMAL); + DCHECK(!read_cb_.IsNull()); + DCHECK_EQ(status != DemuxerStream::kOk, !buffer) << status; + + if (!stop_cb_.IsNull()) { + read_cb_.RunOrHold(kOk, scoped_refptr<VideoFrame>()); + if (!reset_cb_.IsNull()) { + DoReset(); + } + DoStop(); + return; + } + + if (status == DemuxerStream::kConfigChanged) { + DCHECK(demuxer_stream_->video_decoder_config().IsValidConfig()); + current_config_ = demuxer_stream_->video_decoder_config(); + + if (reset_cb_.IsNull()) { + ReadFromDemuxerStream(); + return; + } + } + + if (!reset_cb_.IsNull()) { + read_cb_.RunOrHold(kOk, scoped_refptr<VideoFrame>()); + DoReset(); + return; + } + + if (status == DemuxerStream::kAborted) { + read_cb_.RunOrHold(kOk, scoped_refptr<VideoFrame>()); + return; + } + + DCHECK_EQ(status, DemuxerStream::kOk); + + // Make sure the decoder is always configured with the latest config. + DCHECK(current_config_.Matches(demuxer_stream_->video_decoder_config())); + + if (buffer->IsEndOfStream() && decoded_frames_.empty()) { + read_cb_.RunOrHold(kOk, VideoFrame::CreateEmptyFrame()); + return; + } + + if (!buffer->IsEndOfStream()) { + scoped_refptr<VideoFrame> video_frame = VideoFrame::CreateColorFrame( + current_config_.coded_size(), 0, 0, 0, buffer->GetTimestamp()); + decoded_frames_.push_back(video_frame); + + if (decoded_frames_.size() <= static_cast<size_t>(decoding_delay_)) { + ReadFromDemuxerStream(); + return; + } + } + + scoped_refptr<VideoFrame> frame = decoded_frames_.front(); + decoded_frames_.pop_front(); + read_cb_.RunOrHold(kOk, frame); +} + +void FakeVideoDecoder::DoReset() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(read_cb_.IsNull()); + DCHECK(!reset_cb_.IsNull()); + + decoded_frames_.clear(); + reset_cb_.RunOrHold(); +} + +void FakeVideoDecoder::DoStop() { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(read_cb_.IsNull()); + DCHECK(reset_cb_.IsNull()); + DCHECK(!stop_cb_.IsNull()); + + state_ = UNINITIALIZED; + demuxer_stream_ = NULL; + decoded_frames_.clear(); + stop_cb_.RunOrHold(); +} + +} // namespace media diff --git a/media/filters/fake_video_decoder.h b/media/filters/fake_video_decoder.h new file mode 100644 index 0000000..c82679c --- /dev/null +++ b/media/filters/fake_video_decoder.h @@ -0,0 +1,101 @@ +// Copyright 2013 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. + +#ifndef MEDIA_FILTERS_FAKE_VIDEO_DECODER_H_ +#define MEDIA_FILTERS_FAKE_VIDEO_DECODER_H_ + +#include <list> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/memory/weak_ptr.h" +#include "media/base/callback_holder.h" +#include "media/base/decoder_buffer.h" +#include "media/base/demuxer_stream.h" +#include "media/base/media_export.h" +#include "media/base/pipeline_status.h" +#include "media/base/video_decoder.h" +#include "media/base/video_decoder_config.h" +#include "media/base/video_frame.h" +#include "ui/gfx/size.h" + +using base::ResetAndReturn; + +namespace base { +class MessageLoopProxy; +} + +namespace media { + +class MEDIA_EXPORT FakeVideoDecoder : public VideoDecoder { + public: + // Constructs an object with a decoding delay of |decoding_delay| frames. + explicit FakeVideoDecoder(int decoding_delay); + virtual ~FakeVideoDecoder(); + + // VideoDecoder implementation. + virtual void Initialize(DemuxerStream* stream, + const PipelineStatusCB& status_cb, + const StatisticsCB& statistics_cb) OVERRIDE; + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual void Reset(const base::Closure& closure) OVERRIDE; + virtual void Stop(const base::Closure& closure) OVERRIDE; + + // Holds the next init/read/reset/stop callback from firing. + void HoldNextInit(); + void HoldNextRead(); + void HoldNextReset(); + void HoldNextStop(); + + // Satisfies the pending init/read/reset/stop callback, which must be ready + // to fire when these methods are called. + void SatisfyInit(); + void SatisfyRead(); + void SatisfyReset(); + void SatisfyStop(); + + private: + enum State { + UNINITIALIZED, + NORMAL + }; + + void ReadFromDemuxerStream(); + + // Callback for DemuxerStream::Read(). + void BufferReady(DemuxerStream::Status status, + const scoped_refptr<DecoderBuffer>& buffer); + + void DoReset(); + void DoStop(); + + scoped_refptr<base::MessageLoopProxy> message_loop_; + base::WeakPtrFactory<FakeVideoDecoder> weak_factory_; + base::WeakPtr<FakeVideoDecoder> weak_this_; + + const int decoding_delay_; + + State state_; + + StatisticsCB statistics_cb_; + + CallbackHolder<PipelineStatusCB> init_cb_; + CallbackHolder<ReadCB> read_cb_; + CallbackHolder<base::Closure> reset_cb_; + CallbackHolder<base::Closure> stop_cb_; + + // Pointer to the demuxer stream that will feed us compressed buffers. + DemuxerStream* demuxer_stream_; + + VideoDecoderConfig current_config_; + + std::list<scoped_refptr<VideoFrame> > decoded_frames_; + + DISALLOW_COPY_AND_ASSIGN(FakeVideoDecoder); +}; + +} // namespace media + +#endif // MEDIA_FILTERS_FAKE_VIDEO_DECODER_H_ diff --git a/media/filters/fake_video_decoder_unittest.cc b/media/filters/fake_video_decoder_unittest.cc new file mode 100644 index 0000000..24f0e0b --- /dev/null +++ b/media/filters/fake_video_decoder_unittest.cc @@ -0,0 +1,434 @@ +// Copyright 2013 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 "base/basictypes.h" +#include "base/bind.h" +#include "base/message_loop.h" +#include "media/base/decoder_buffer.h" +#include "media/base/mock_filters.h" +#include "media/base/test_helpers.h" +#include "media/base/video_frame.h" +#include "media/filters/fake_demuxer_stream.h" +#include "media/filters/fake_video_decoder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static const int kDecodingDelay = 9; +static const int kNumConfigs = 3; +static const int kNumBuffersInOneConfig = 9; +static const int kNumInputBuffers = kNumConfigs * kNumBuffersInOneConfig; + +class FakeVideoDecoderTest : public testing::Test { + public: + FakeVideoDecoderTest() + : decoder_(new FakeVideoDecoder(kDecodingDelay)), + demuxer_stream_( + new FakeDemuxerStream(kNumConfigs, kNumBuffersInOneConfig, false)), + num_decoded_frames_(0), + is_read_pending_(false), + is_reset_pending_(false), + is_stop_pending_(false) {} + + virtual ~FakeVideoDecoderTest() { + StopAndExpect(OK); + } + + void Initialize() { + decoder_->Initialize(demuxer_stream_.get(), + NewExpectedStatusCB(PIPELINE_OK), + base::Bind(&MockStatisticsCB::OnStatistics, + base::Unretained(&statistics_cb_))); + message_loop_.RunUntilIdle(); + } + + // Callback for VideoDecoder::Read(). + void FrameReady(VideoDecoder::Status status, + const scoped_refptr<VideoFrame>& frame) { + DCHECK(is_read_pending_); + ASSERT_EQ(VideoDecoder::kOk, status); + + is_read_pending_ = false; + frame_read_ = frame; + + if (frame && !frame->IsEndOfStream()) + num_decoded_frames_++; + } + + enum CallbackResult { + PENDING, + OK, + ABROTED, + EOS + }; + + void ExpectReadResult(CallbackResult result) { + switch (result) { + case PENDING: + EXPECT_TRUE(is_read_pending_); + ASSERT_FALSE(frame_read_); + break; + case OK: + EXPECT_FALSE(is_read_pending_); + ASSERT_TRUE(frame_read_); + EXPECT_FALSE(frame_read_->IsEndOfStream()); + break; + case ABROTED: + EXPECT_FALSE(is_read_pending_); + EXPECT_FALSE(frame_read_); + break; + case EOS: + EXPECT_FALSE(is_read_pending_); + ASSERT_TRUE(frame_read_); + EXPECT_TRUE(frame_read_->IsEndOfStream()); + break; + } + } + + void ReadOneFrame() { + frame_read_ = NULL; + is_read_pending_ = true; + decoder_->Read( + base::Bind(&FakeVideoDecoderTest::FrameReady, base::Unretained(this))); + message_loop_.RunUntilIdle(); + } + + void ReadUntilEOS() { + do { + ReadOneFrame(); + } while (frame_read_ && !frame_read_->IsEndOfStream()); + } + + void EnterPendingReadState() { + decoder_->HoldNextRead(); + ReadOneFrame(); + ExpectReadResult(PENDING); + } + + void SatisfyRead() { + decoder_->SatisfyRead(); + message_loop_.RunUntilIdle(); + ExpectReadResult(OK); + } + + // Callback for VideoDecoder::Reset(). + void OnDecoderReset() { + DCHECK(is_reset_pending_); + is_reset_pending_ = false; + } + + void ExpectResetResult(CallbackResult result) { + switch (result) { + case PENDING: + EXPECT_TRUE(is_reset_pending_); + break; + case OK: + EXPECT_FALSE(is_reset_pending_); + break; + default: + NOTREACHED(); + } + } + + void ResetAndExpect(CallbackResult result) { + is_reset_pending_ = true; + decoder_->Reset(base::Bind(&FakeVideoDecoderTest::OnDecoderReset, + base::Unretained(this))); + message_loop_.RunUntilIdle(); + ExpectResetResult(result); + } + + void EnterPendingResetState() { + decoder_->HoldNextReset(); + ResetAndExpect(PENDING); + } + + void SatisfyReset() { + decoder_->SatisfyReset(); + message_loop_.RunUntilIdle(); + ExpectResetResult(OK); + } + + // Callback for VideoDecoder::Stop(). + void OnDecoderStopped() { + DCHECK(is_stop_pending_); + is_stop_pending_ = false; + } + + void ExpectStopResult(CallbackResult result) { + switch (result) { + case PENDING: + EXPECT_TRUE(is_stop_pending_); + break; + case OK: + EXPECT_FALSE(is_stop_pending_); + break; + default: + NOTREACHED(); + } + } + + void StopAndExpect(CallbackResult result) { + is_stop_pending_ = true; + decoder_->Stop(base::Bind(&FakeVideoDecoderTest::OnDecoderStopped, + base::Unretained(this))); + message_loop_.RunUntilIdle(); + ExpectStopResult(result); + } + + void EnterPendingStopState() { + decoder_->HoldNextStop(); + StopAndExpect(PENDING); + } + + void SatisfyStop() { + decoder_->SatisfyStop(); + message_loop_.RunUntilIdle(); + ExpectStopResult(OK); + } + + // Callback for DemuxerStream::Read so that we can skip frames to trigger a + // config change. + void BufferReady(bool* config_changed, + DemuxerStream::Status status, + const scoped_refptr<DecoderBuffer>& buffer) { + if (status == DemuxerStream::kConfigChanged) + *config_changed = true; + } + + void ChangeConfig() { + bool config_changed = false; + while (!config_changed) { + demuxer_stream_->Read(base::Bind(&FakeVideoDecoderTest::BufferReady, + base::Unretained(this), + &config_changed)); + message_loop_.RunUntilIdle(); + } + } + + void EnterPendingDemuxerReadState() { + demuxer_stream_->HoldNextRead(); + ReadOneFrame(); + } + + void SatisfyDemuxerRead() { + demuxer_stream_->SatisfyRead(); + message_loop_.RunUntilIdle(); + } + + void AbortDemuxerRead() { + demuxer_stream_->Reset(); + message_loop_.RunUntilIdle(); + } + + base::MessageLoop message_loop_; + scoped_ptr<FakeVideoDecoder> decoder_; + scoped_ptr<FakeDemuxerStream> demuxer_stream_; + MockStatisticsCB statistics_cb_; + int num_decoded_frames_; + + // Callback result/status. + scoped_refptr<VideoFrame> frame_read_; + bool is_read_pending_; + bool is_reset_pending_; + bool is_stop_pending_; + + private: + DISALLOW_COPY_AND_ASSIGN(FakeVideoDecoderTest); +}; + +TEST_F(FakeVideoDecoderTest, Initialize) { + Initialize(); +} + +TEST_F(FakeVideoDecoderTest, Read_AllFrames) { + Initialize(); + ReadUntilEOS(); + EXPECT_EQ(kNumInputBuffers, num_decoded_frames_); +} + +TEST_F(FakeVideoDecoderTest, Read_AbortedDemuxerRead) { + Initialize(); + demuxer_stream_->HoldNextRead(); + ReadOneFrame(); + AbortDemuxerRead(); + ExpectReadResult(ABROTED); +} + +TEST_F(FakeVideoDecoderTest, Read_DecodingDelay) { + Initialize(); + + while (demuxer_stream_->num_buffers_returned() < kNumInputBuffers) { + ReadOneFrame(); + EXPECT_EQ(demuxer_stream_->num_buffers_returned(), + num_decoded_frames_ + kDecodingDelay); + } +} + +TEST_F(FakeVideoDecoderTest, Read_ZeroDelay) { + decoder_.reset(new FakeVideoDecoder(0)); + Initialize(); + + while (demuxer_stream_->num_buffers_returned() < kNumInputBuffers) { + ReadOneFrame(); + EXPECT_EQ(demuxer_stream_->num_buffers_returned(), num_decoded_frames_); + } +} + +TEST_F(FakeVideoDecoderTest, Read_Pending) { + Initialize(); + EnterPendingReadState(); + SatisfyRead(); +} + +TEST_F(FakeVideoDecoderTest, Reinitialize) { + Initialize(); + VideoDecoderConfig old_config = demuxer_stream_->video_decoder_config(); + ChangeConfig(); + VideoDecoderConfig new_config = demuxer_stream_->video_decoder_config(); + EXPECT_FALSE(new_config.Matches(old_config)); + Initialize(); +} + +// Reinitializing the decoder during the middle of the decoding process can +// cause dropped frames. +TEST_F(FakeVideoDecoderTest, Reinitialize_FrameDropped) { + Initialize(); + ReadOneFrame(); + Initialize(); + ReadUntilEOS(); + EXPECT_LT(num_decoded_frames_, kNumInputBuffers); +} + +TEST_F(FakeVideoDecoderTest, Reset) { + Initialize(); + ReadOneFrame(); + ResetAndExpect(OK); +} + +TEST_F(FakeVideoDecoderTest, Reset_DuringPendingDemuxerRead) { + Initialize(); + EnterPendingDemuxerReadState(); + ResetAndExpect(PENDING); + SatisfyDemuxerRead(); + ExpectReadResult(ABROTED); +} + +TEST_F(FakeVideoDecoderTest, Reset_DuringPendingDemuxerRead_Aborted) { + Initialize(); + EnterPendingDemuxerReadState(); + ResetAndExpect(PENDING); + AbortDemuxerRead(); + ExpectReadResult(ABROTED); +} + +TEST_F(FakeVideoDecoderTest, Reset_DuringPendingRead) { + Initialize(); + EnterPendingReadState(); + ResetAndExpect(PENDING); + SatisfyRead(); +} + +TEST_F(FakeVideoDecoderTest, Reset_Pending) { + Initialize(); + EnterPendingResetState(); + SatisfyReset(); +} + +TEST_F(FakeVideoDecoderTest, Reset_PendingDuringPendingRead) { + Initialize(); + EnterPendingReadState(); + EnterPendingResetState(); + SatisfyRead(); + SatisfyReset(); +} + +TEST_F(FakeVideoDecoderTest, Stop) { + Initialize(); + ReadOneFrame(); + ExpectReadResult(OK); + StopAndExpect(OK); +} + +TEST_F(FakeVideoDecoderTest, Stop_DuringPendingDemuxerRead) { + Initialize(); + EnterPendingDemuxerReadState(); + StopAndExpect(PENDING); + SatisfyDemuxerRead(); + ExpectReadResult(ABROTED); +} + +TEST_F(FakeVideoDecoderTest, Stop_DuringPendingDemuxerRead_Aborted) { + Initialize(); + EnterPendingDemuxerReadState(); + ResetAndExpect(PENDING); + StopAndExpect(PENDING); + SatisfyDemuxerRead(); + ExpectReadResult(ABROTED); + ExpectResetResult(OK); + ExpectStopResult(OK); +} + +TEST_F(FakeVideoDecoderTest, Stop_DuringPendingRead) { + Initialize(); + EnterPendingReadState(); + StopAndExpect(PENDING); + SatisfyRead(); + ExpectStopResult(OK); +} + +TEST_F(FakeVideoDecoderTest, Stop_DuringPendingReset) { + Initialize(); + EnterPendingResetState(); + StopAndExpect(PENDING); + SatisfyReset(); + ExpectStopResult(OK); +} + +TEST_F(FakeVideoDecoderTest, Stop_DuringPendingReadAndPendingReset) { + Initialize(); + EnterPendingReadState(); + EnterPendingResetState(); + StopAndExpect(PENDING); + SatisfyRead(); + SatisfyReset(); + ExpectStopResult(OK); +} + +TEST_F(FakeVideoDecoderTest, Stop_Pending) { + Initialize(); + decoder_->HoldNextStop(); + StopAndExpect(PENDING); + decoder_->SatisfyStop(); + message_loop_.RunUntilIdle(); + ExpectStopResult(OK); +} + +TEST_F(FakeVideoDecoderTest, Stop_PendingDuringPendingRead) { + Initialize(); + EnterPendingReadState(); + EnterPendingStopState(); + SatisfyRead(); + SatisfyStop(); +} + +TEST_F(FakeVideoDecoderTest, Stop_PendingDuringPendingReset) { + Initialize(); + EnterPendingResetState(); + EnterPendingStopState(); + SatisfyReset(); + SatisfyStop(); +} + +TEST_F(FakeVideoDecoderTest, Stop_PendingDuringPendingReadAndPendingReset) { + Initialize(); + EnterPendingReadState(); + EnterPendingResetState(); + EnterPendingStopState(); + SatisfyRead(); + SatisfyReset(); + SatisfyStop(); +} + +} // namespace media diff --git a/media/media.gyp b/media/media.gyp index 0938bb1..3a9cb7b 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -235,6 +235,7 @@ 'base/buffers.h', 'base/byte_queue.cc', 'base/byte_queue.h', + 'base/callback_holder.h', 'base/channel_mixer.cc', 'base/channel_mixer.h', 'base/clock.cc', @@ -334,6 +335,8 @@ 'filters/decrypting_video_decoder.h', 'filters/fake_demuxer_stream.cc', 'filters/fake_demuxer_stream.h', + 'filters/fake_video_decoder.cc', + 'filters/fake_video_decoder.h', 'filters/ffmpeg_audio_decoder.cc', 'filters/ffmpeg_audio_decoder.h', 'filters/ffmpeg_demuxer.cc', @@ -957,6 +960,7 @@ 'base/audio_timestamp_helper_unittest.cc', 'base/bind_to_loop_unittest.cc', 'base/bit_reader_unittest.cc', + 'base/callback_holder_unittest.cc', 'base/channel_mixer_unittest.cc', 'base/clock_unittest.cc', 'base/container_names_unittest.cc', @@ -991,6 +995,7 @@ 'filters/decrypting_demuxer_stream_unittest.cc', 'filters/decrypting_video_decoder_unittest.cc', 'filters/fake_demuxer_stream_unittest.cc', + 'filters/fake_video_decoder_unittest.cc', 'filters/ffmpeg_audio_decoder_unittest.cc', 'filters/ffmpeg_demuxer_unittest.cc', 'filters/ffmpeg_glue_unittest.cc', |