summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-29 06:52:29 +0000
committerxhwang@chromium.org <xhwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-29 06:52:29 +0000
commita38edc462009e1591e4629b1e17c45d65d324ac5 (patch)
tree1be1c32fed59b74000598bd0f2e1e2d2e6135213 /media
parent9d70ced22d51dcec3cae44b7a4d4b000dc9ec00a (diff)
downloadchromium_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.h88
-rw-r--r--media/base/callback_holder_unittest.cc125
-rw-r--r--media/filters/fake_demuxer_stream.cc28
-rw-r--r--media/filters/fake_demuxer_stream.h15
-rw-r--r--media/filters/fake_demuxer_stream_unittest.cc60
-rw-r--r--media/filters/fake_video_decoder.cc235
-rw-r--r--media/filters/fake_video_decoder.h101
-rw-r--r--media/filters/fake_video_decoder_unittest.cc434
-rw-r--r--media/media.gyp5
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',