summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-10 00:05:39 +0000
committerajwong@chromium.org <ajwong@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-10 00:05:39 +0000
commite7a557c64a933835da445cda865a8f81bd92b8b0 (patch)
treee5fdf02f91d06f17aa34e32c6a94c82703e4c374 /media
parente3149dbeafdcb286e24aa676b125eb9b0134ab11 (diff)
downloadchromium_src-e7a557c64a933835da445cda865a8f81bd92b8b0.zip
chromium_src-e7a557c64a933835da445cda865a8f81bd92b8b0.tar.gz
chromium_src-e7a557c64a933835da445cda865a8f81bd92b8b0.tar.bz2
Refactor FFmpegVideoDecoder to try and generalize code common to all video decoders.
This changes the DecoderBase API to be fully asynchronous when invoking its subclass's actions. Review URL: http://codereview.chromium.org/465044 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34208 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/base/callback.h62
-rw-r--r--media/base/mock_task.h110
-rw-r--r--media/ffmpeg/ffmpeg_util.cc18
-rw-r--r--media/ffmpeg/ffmpeg_util.h18
-rw-r--r--media/filters/decoder_base.h80
-rw-r--r--media/filters/ffmpeg_audio_decoder.cc31
-rw-r--r--media/filters/ffmpeg_audio_decoder.h9
-rw-r--r--media/filters/ffmpeg_demuxer.cc18
-rw-r--r--media/filters/ffmpeg_demuxer.h4
-rw-r--r--media/filters/ffmpeg_video_decode_engine_unittest.cc184
-rw-r--r--media/filters/ffmpeg_video_decoder.cc301
-rw-r--r--media/filters/ffmpeg_video_decoder.h78
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc297
-rw-r--r--media/filters/video_decode_engine.h57
-rw-r--r--media/media.gyp6
15 files changed, 913 insertions, 360 deletions
diff --git a/media/base/callback.h b/media/base/callback.h
new file mode 100644
index 0000000..553c842
--- /dev/null
+++ b/media/base/callback.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2009 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.
+
+// Some basic utilities for aiding in the management of Tasks and Callbacks.
+// AutoTaskRunner, and its brother AutoCallbackRunner are the scoped_ptr
+// equivalents for callbacks. They are useful for ensuring a callback is
+// executed and delete in the face of multiple return points in a function.
+
+#ifndef MEDIA_BASE_CALLBACK_
+#define MEDIA_BASE_CALLBACK_
+
+#include "base/scoped_ptr.h"
+#include "base/task.h"
+
+namespace media {
+
+class AutoTaskRunner {
+ public:
+ // Takes ownership of the task.
+ explicit AutoTaskRunner(Task* task)
+ : task_(task) {
+ }
+
+ ~AutoTaskRunner() {
+ if (task_.get()) {
+ task_->Run();
+ }
+ }
+
+ Task* release() { return task_.release(); }
+
+ private:
+ scoped_ptr<Task> task_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoTaskRunner);
+};
+
+class AutoCallbackRunner {
+ public:
+ // Takes ownership of the callback.
+ explicit AutoCallbackRunner(Callback0::Type* callback)
+ : callback_(callback) {
+ }
+
+ ~AutoCallbackRunner() {
+ if (callback_.get()) {
+ callback_->Run();
+ }
+ }
+
+ Callback0::Type* release() { return callback_.release(); }
+
+ private:
+ scoped_ptr<Callback0::Type> callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutoCallbackRunner);
+};
+
+} // namespace media
+
+#endif // MEDIA_BASE_CALLBACK_
diff --git a/media/base/mock_task.h b/media/base/mock_task.h
new file mode 100644
index 0000000..cb6e0de
--- /dev/null
+++ b/media/base/mock_task.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2009 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.
+
+// This file provides some utility classes that help with testing APIs which use
+// callbacks.
+//
+// -- InvokeRunnable --
+// The InvokeRunnable is an action that can be used a gMock mock object to
+// invoke the Run() method on mock argument. Example:
+//
+// class MockFoo : public Foo {
+// public:
+// MOCK_METHOD0(DoSomething, void(Task* done_cb));
+// };
+//
+// EXPECT_CALL(foo, DoSomething(_)).WillOnce(WithArg<0>(InvokeRunnable()));
+//
+// Then you pass "foo" to something that will eventually call DoSomething().
+// The mock action will ensure that passed in done_cb is invoked.
+//
+//
+// -- TaskMocker --
+// The TaskMocker class lets you create mock callbacks. Callbacks are
+// difficult to mock because ownership of the callback object is often passed
+// to the funciton being invoked. TaskMocker solves this by providing a
+// GetTask() function that creates a new, single-use task that delegates to
+// the originating TaskMocker object. Expectations are placed on the
+// originating TaskMocker object. Each callback retrieved by GetTask() is
+// tracked to ensure that it is properly deleted. The TaskMocker expects to
+// outlive all the callbacks retrieved by GetTask().
+//
+// Example:
+//
+// TaskMocker done_cb;
+// EXPECT_CALL(done_cb, Run()).Times(3);
+//
+// func1(done_cb.GetTask());
+// func2(done_cb.GetTask());
+// func3(done_cb.GetTask());
+//
+// // All 3 callbacks from GetTask() should be deleted before done_cb goes out
+// // of scope.
+//
+// This class is not threadsafe.
+//
+// TODO(ajwong): Is it even worth bothering with gmock here?
+// TODO(ajwong): Move MockFilterCallback here and merge the implementation
+// differences.
+
+#ifndef MEDIA_BASE_MOCK_TASK_H_
+#define MEDIA_BASE_MOCK_TASK_H_
+
+#include "base/task.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace media {
+
+ACTION(InvokeRunnable) {
+ arg0->Run();
+ delete arg0;
+}
+
+class TaskMocker {
+ public:
+ TaskMocker()
+ : outstanding_tasks_(0) {
+ }
+ ~TaskMocker() {
+ CHECK(outstanding_tasks_ == 0)
+ << "If outstanding_tasks_ is not zero, tasks have been leaked.";
+ }
+
+ Task* CreateTask() {
+ return new CountingTask(this);
+ }
+
+ MOCK_METHOD0(Run, void());
+
+ private:
+ friend class CountingTask;
+ class CountingTask : public Task {
+ public:
+ CountingTask(TaskMocker* origin)
+ : origin_(origin) {
+ origin_->outstanding_tasks_++;
+ }
+
+ virtual void Run() {
+ origin_->Run();
+ }
+
+ virtual ~CountingTask() {
+ origin_->outstanding_tasks_--;
+ }
+
+ private:
+ TaskMocker* origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(CountingTask);
+ };
+
+ int outstanding_tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskMocker);
+};
+
+} // namespace media
+
+#endif //MEDIA_BASE_MOCK_TASK_H_
diff --git a/media/ffmpeg/ffmpeg_util.cc b/media/ffmpeg/ffmpeg_util.cc
new file mode 100644
index 0000000..1be3b12
--- /dev/null
+++ b/media/ffmpeg/ffmpeg_util.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2009 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/ffmpeg/ffmpeg_util.h"
+
+#include "media/filters/ffmpeg_common.h"
+
+namespace media {
+
+static const AVRational kMicrosBase = { 1, base::Time::kMicrosecondsPerSecond };
+
+base::TimeDelta ConvertTimestamp(const AVRational& time_base, int64 timestamp) {
+ int64 microseconds = av_rescale_q(timestamp, time_base, kMicrosBase);
+ return base::TimeDelta::FromMicroseconds(microseconds);
+}
+
+} // namespace media
diff --git a/media/ffmpeg/ffmpeg_util.h b/media/ffmpeg/ffmpeg_util.h
new file mode 100644
index 0000000..6799cc4
--- /dev/null
+++ b/media/ffmpeg/ffmpeg_util.h
@@ -0,0 +1,18 @@
+// Copyright (c) 2009 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_FFMPEG_FFMPEG_UTIL_H_
+#define MEDIA_FFMPEG_FFMPEG_UTIL_H_
+
+#include "base/time.h"
+
+struct AVRational;
+
+namespace media {
+
+base::TimeDelta ConvertTimestamp(const AVRational& time_base, int64 timestamp);
+
+} // namespace media
+
+#endif // MEDIA_FFMPEG_FFMPEG_UTIL_H_
diff --git a/media/filters/decoder_base.h b/media/filters/decoder_base.h
index a228958..cc403bc 100644
--- a/media/filters/decoder_base.h
+++ b/media/filters/decoder_base.h
@@ -14,6 +14,7 @@
#include "base/task.h"
#include "base/thread.h"
#include "media/base/buffers.h"
+#include "media/base/callback.h"
#include "media/base/filters.h"
#include "media/base/filter_host.h"
@@ -55,17 +56,6 @@ class DecoderBase : public Decoder {
NewRunnableMethod(this, &DecoderBase::ReadTask, read_callback));
}
- void OnReadComplete(Buffer* buffer) {
- // Little bit of magic here to get NewRunnableMethod() to generate a Task
- // that holds onto a reference via scoped_refptr<>.
- //
- // TODO(scherkus): change the callback format to pass a scoped_refptr<> or
- // better yet see if we can get away with not using reference counting.
- scoped_refptr<Buffer> buffer_ref = buffer;
- this->message_loop()->PostTask(FROM_HERE,
- NewRunnableMethod(this, &DecoderBase::ReadCompleteTask, buffer_ref));
- }
-
protected:
DecoderBase()
: pending_reads_(0),
@@ -89,6 +79,12 @@ class DecoderBase : public Decoder {
}
}
+ // TODO(ajwong): All these "Task*" used as completion callbacks should be
+ // FilterCallbacks. However, since NewCallback() cannot prebind parameters,
+ // we use NewRunnableMethod() instead which causes an unfortunate refcount.
+ // We should move stoyan's Mutant code into base/task.h and turn these
+ // back into FilterCallbacks.
+
// Method that must be implemented by the derived class. Called from within
// the DecoderBase::Initialize() method before any reads are submitted to
// the demuxer stream. Returns true if successful, otherwise false indicates
@@ -96,32 +92,46 @@ class DecoderBase : public Decoder {
// InitializationComplete() method. If this method returns true, then the
// base class will call the host to complete initialization. During this
// call, the derived class must fill in the media_format_ member.
- virtual bool OnInitialize(DemuxerStream* demuxer_stream) = 0;
+ virtual void DoInitialize(DemuxerStream* demuxer_stream, bool* success,
+ Task* done_cb) = 0;
// Method that may be implemented by the derived class if desired. It will
// be called from within the MediaFilter::Stop() method prior to stopping the
// base class.
- virtual void OnStop() {}
+ //
+ // TODO(ajwong): Make this asynchronous.
+ virtual void DoStop() {}
// Derived class can implement this method and perform seeking logic prior
// to the base class.
- virtual void OnSeek(base::TimeDelta time) {}
+ virtual void DoSeek(base::TimeDelta time, Task* done_cb) = 0;
// Method that must be implemented by the derived class. If the decode
// operation produces one or more outputs, the derived class should call
// the EnequeueResult() method from within this method.
- virtual void OnDecode(Buffer* input) = 0;
+ virtual void DoDecode(Buffer* input, Task* done_cb) = 0;
MediaFormat media_format_;
private:
bool IsStopped() { return state_ == kStopped; }
+ void OnReadComplete(Buffer* buffer) {
+ // Little bit of magic here to get NewRunnableMethod() to generate a Task
+ // that holds onto a reference via scoped_refptr<>.
+ //
+ // TODO(scherkus): change the callback format to pass a scoped_refptr<> or
+ // better yet see if we can get away with not using reference counting.
+ scoped_refptr<Buffer> buffer_ref = buffer;
+ this->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &DecoderBase::ReadCompleteTask, buffer_ref));
+ }
+
void StopTask() {
DCHECK_EQ(MessageLoop::current(), this->message_loop());
// Delegate to the subclass first.
- OnStop();
+ DoStop();
// Throw away all buffers in all queues.
result_queue_.clear();
@@ -133,7 +143,6 @@ class DecoderBase : public Decoder {
DCHECK_EQ(MessageLoop::current(), this->message_loop());
DCHECK_EQ(0u, pending_reads_) << "Pending reads should have completed";
DCHECK(read_queue_.empty()) << "Read requests should be empty";
- scoped_ptr<FilterCallback> c(callback);
// Delegate to the subclass first.
//
@@ -142,8 +151,11 @@ class DecoderBase : public Decoder {
// either flush their buffers here or wait for IsDiscontinuous(). I'm
// inclined to say that they should still wait for IsDiscontinuous() so they
// don't have duplicated logic for Seek() and actual discontinuous frames.
- OnSeek(time);
+ DoSeek(time,
+ NewRunnableMethod(this, &DecoderBase::OnSeekComplete, callback));
+ }
+ void OnSeekComplete(FilterCallback* callback) {
// Flush our decoded results. We'll set a boolean that we can DCHECK to
// verify our assertion that the first buffer received after a Seek() should
// always be discontinuous.
@@ -158,20 +170,30 @@ class DecoderBase : public Decoder {
DCHECK_EQ(MessageLoop::current(), this->message_loop());
CHECK(kUninitialized == state_);
CHECK(!demuxer_stream_);
- scoped_ptr<FilterCallback> c(callback);
demuxer_stream_ = demuxer_stream;
+ bool* success = new bool;
+ DoInitialize(demuxer_stream,
+ success,
+ NewRunnableMethod(this, &DecoderBase::OnInitializeComplete,
+ success, callback));
+ }
+
+ void OnInitializeComplete(bool* success, FilterCallback* done_cb) {
+ // Note: The done_runner must be declared *last* to ensure proper
+ // destruction order.
+ scoped_ptr<bool> success_deleter(success);
+ AutoCallbackRunner done_runner(done_cb);
+
+ DCHECK_EQ(MessageLoop::current(), this->message_loop());
// Delegate to subclass first.
- if (!OnInitialize(demuxer_stream_)) {
+ if (!*success) {
this->host()->SetError(PIPELINE_ERROR_DECODE);
- callback->Run();
- return;
+ } else {
+ // TODO(scherkus): subclass shouldn't mutate superclass media format.
+ DCHECK(!media_format_.empty()) << "Subclass did not set media_format_";
+ state_ = kInitialized;
}
-
- // TODO(scherkus): subclass shouldn't mutate superclass media format.
- DCHECK(!media_format_.empty()) << "Subclass did not set media_format_";
- state_ = kInitialized;
- callback->Run();
}
void ReadTask(ReadCallback* read_callback) {
@@ -210,8 +232,10 @@ class DecoderBase : public Decoder {
}
// Decode the frame right away.
- OnDecode(buffer);
+ DoDecode(buffer, NewRunnableMethod(this, &DecoderBase::OnDecodeComplete));
+ }
+ void OnDecodeComplete() {
// Attempt to fulfill a pending read callback and schedule additional reads
// if necessary.
FulfillPendingRead();
diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc
index 8a7991a..884cde9 100644
--- a/media/filters/ffmpeg_audio_decoder.cc
+++ b/media/filters/ffmpeg_audio_decoder.cc
@@ -4,6 +4,7 @@
#include "media/filters/ffmpeg_audio_decoder.h"
+#include "media/base/callback.h"
#include "media/base/data_buffer.h"
#include "media/base/limits.h"
#include "media/filters/ffmpeg_common.h"
@@ -29,11 +30,16 @@ bool FFmpegAudioDecoder::IsMediaFormatSupported(const MediaFormat& format) {
mime_type::kFFmpegAudio == mime_type;
}
-bool FFmpegAudioDecoder::OnInitialize(DemuxerStream* demuxer_stream) {
+void FFmpegAudioDecoder::DoInitialize(DemuxerStream* demuxer_stream,
+ bool* success,
+ Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+ *success = false;
+
// Get the AVStream by querying for the provider interface.
AVStreamProvider* av_stream_provider;
if (!demuxer_stream->QueryInterface(&av_stream_provider)) {
- return false;
+ return;
}
AVStream* av_stream = av_stream_provider->GetAVStream();
@@ -48,15 +54,17 @@ bool FFmpegAudioDecoder::OnInitialize(DemuxerStream* demuxer_stream) {
bps == 0 ||
static_cast<size_t>(bps) > Limits::kMaxBPS ||
codec_context_->sample_rate == 0 ||
- static_cast<size_t>(codec_context_->sample_rate) > Limits::kMaxSampleRate)
- return false;
+ (static_cast<size_t>(codec_context_->sample_rate) >
+ Limits::kMaxSampleRate)) {
+ return;
+ }
// Serialize calls to avcodec_open().
AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
{
AutoLock auto_lock(FFmpegLock::get()->lock());
if (!codec || avcodec_open(codec_context_, codec) < 0) {
- return false;
+ return;
}
}
@@ -78,20 +86,21 @@ bool FFmpegAudioDecoder::OnInitialize(DemuxerStream* demuxer_stream) {
output_buffer_.reset(static_cast<uint8*>(av_malloc(kOutputBufferSize)));
if (!output_buffer_.get()) {
host()->SetError(PIPELINE_ERROR_OUT_OF_MEMORY);
- return false;
+ return;
}
- return true;
+ *success = true;
}
-void FFmpegAudioDecoder::OnSeek(base::TimeDelta time) {
+void FFmpegAudioDecoder::DoSeek(base::TimeDelta time, Task* done_cb) {
avcodec_flush_buffers(codec_context_);
estimated_next_timestamp_ = StreamSample::kInvalidTimestamp;
+ done_cb->Run();
+ delete done_cb;
}
-void FFmpegAudioDecoder::OnStop() {
-}
+void FFmpegAudioDecoder::DoDecode(Buffer* input, Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
-void FFmpegAudioDecoder::OnDecode(Buffer* input) {
// Due to FFmpeg API changes we no longer have const read-only pointers.
AVPacket packet;
av_init_packet(&packet);
diff --git a/media/filters/ffmpeg_audio_decoder.h b/media/filters/ffmpeg_audio_decoder.h
index f7adc33..f5ff8ac 100644
--- a/media/filters/ffmpeg_audio_decoder.h
+++ b/media/filters/ffmpeg_audio_decoder.h
@@ -24,13 +24,12 @@ class FFmpegAudioDecoder : public DecoderBase<AudioDecoder, Buffer> {
static bool IsMediaFormatSupported(const MediaFormat& media_format);
protected:
- virtual bool OnInitialize(DemuxerStream* demuxer_stream);
+ virtual void DoInitialize(DemuxerStream* demuxer_stream, bool* success,
+ Task* done_cb);
- virtual void OnSeek(base::TimeDelta time);
+ virtual void DoSeek(base::TimeDelta time, Task* done_cb);
- virtual void OnStop();
-
- virtual void OnDecode(Buffer* input);
+ virtual void DoDecode(Buffer* input, Task* done_cb);
private:
friend class FilterFactoryImpl0<FFmpegAudioDecoder>;
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index dd08e40..3eab9f6 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -7,6 +7,7 @@
#include "base/string_util.h"
#include "base/time.h"
#include "media/base/filter_host.h"
+#include "media/ffmpeg/ffmpeg_util.h"
#include "media/filters/ffmpeg_common.h"
#include "media/filters/ffmpeg_demuxer.h"
#include "media/filters/ffmpeg_glue.h"
@@ -76,7 +77,7 @@ FFmpegDemuxerStream::FFmpegDemuxerStream(FFmpegDemuxer* demuxer,
}
// Calculate the duration.
- duration_ = ConvertTimestamp(stream->duration);
+ duration_ = ConvertStreamTimestamp(stream->time_base, stream->duration);
}
FFmpegDemuxerStream::~FFmpegDemuxerStream() {
@@ -103,8 +104,10 @@ bool FFmpegDemuxerStream::HasPendingReads() {
base::TimeDelta FFmpegDemuxerStream::EnqueuePacket(AVPacket* packet) {
DCHECK_EQ(MessageLoop::current(), demuxer_->message_loop());
- base::TimeDelta timestamp = ConvertTimestamp(packet->pts);
- base::TimeDelta duration = ConvertTimestamp(packet->duration);
+ base::TimeDelta timestamp =
+ ConvertStreamTimestamp(stream_->time_base, packet->pts);
+ base::TimeDelta duration =
+ ConvertStreamTimestamp(stream_->time_base, packet->duration);
if (stopped_) {
NOTREACHED() << "Attempted to enqueue packet on a stopped stream";
return timestamp;
@@ -191,12 +194,13 @@ void FFmpegDemuxerStream::FulfillPendingRead() {
read_callback->Run(buffer);
}
-base::TimeDelta FFmpegDemuxerStream::ConvertTimestamp(int64 timestamp) {
+// static
+base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp(
+ const AVRational& time_base, int64 timestamp) {
if (timestamp == static_cast<int64>(AV_NOPTS_VALUE))
return StreamSample::kInvalidTimestamp;
- AVRational time_base = { 1, base::Time::kMicrosecondsPerSecond };
- int64 microseconds = av_rescale_q(timestamp, stream_->time_base, time_base);
- return base::TimeDelta::FromMicroseconds(microseconds);
+
+ return ConvertTimestamp(time_base, timestamp);
}
//
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index f6af38c..f1b13a0 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -39,6 +39,7 @@ struct AVCodecContext;
struct AVBitStreamFilterContext;
struct AVFormatContext;
struct AVPacket;
+struct AVRational;
struct AVStream;
namespace media {
@@ -93,7 +94,8 @@ class FFmpegDemuxerStream : public DemuxerStream, public AVStreamProvider {
void FulfillPendingRead();
// Converts an FFmpeg stream timestamp into a base::TimeDelta.
- base::TimeDelta ConvertTimestamp(int64 timestamp);
+ static base::TimeDelta ConvertStreamTimestamp(const AVRational& time_base,
+ int64 timestamp);
FFmpegDemuxer* demuxer_;
AVStream* stream_;
diff --git a/media/filters/ffmpeg_video_decode_engine_unittest.cc b/media/filters/ffmpeg_video_decode_engine_unittest.cc
new file mode 100644
index 0000000..76c43fd
--- /dev/null
+++ b/media/filters/ffmpeg_video_decode_engine_unittest.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2009 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/scoped_ptr.h"
+#include "media/base/data_buffer.h"
+#include "media/base/mock_ffmpeg.h"
+#include "media/base/mock_task.h"
+#include "media/filters/ffmpeg_video_decoder.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::ReturnNull;
+using ::testing::SetArgumentPointee;
+using ::testing::StrictMock;
+
+namespace media {
+
+class FFmpegVideoDecodeEngineTest : public testing::Test {
+ protected:
+ FFmpegVideoDecodeEngineTest() {
+ // Setup FFmpeg structures.
+ memset(&yuv_frame_, 0, sizeof(yuv_frame_));
+ memset(&codec_context_, 0, sizeof(codec_context_));
+ memset(&codec_, 0, sizeof(codec_));
+ memset(&stream_, 0, sizeof(stream_));
+ stream_.codec = &codec_context_;
+
+ buffer_ = new DataBuffer(1);
+
+ // Initialize MockFFmpeg.
+ MockFFmpeg::set(&mock_ffmpeg_);
+
+ test_engine_.reset(new FFmpegVideoDecodeEngine());
+ test_engine_->SetCodecContextForTest(&codec_context_);
+ }
+
+ scoped_ptr<FFmpegVideoDecodeEngine> test_engine_;
+
+ StrictMock<MockFFmpeg> mock_ffmpeg_;
+
+ AVFrame yuv_frame_;
+ AVCodecContext codec_context_;
+ AVStream stream_;
+ AVCodec codec_;
+ scoped_refptr<DataBuffer> buffer_;
+};
+
+TEST_F(FFmpegVideoDecodeEngineTest, Construction) {
+ FFmpegVideoDecodeEngine engine;
+ EXPECT_FALSE(engine.codec_context());
+ EXPECT_EQ(FFmpegVideoDecodeEngine::kCreated, engine.state());
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, Initialize_Normal) {
+ // Test avcodec_open() failing.
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
+ .WillOnce(Return(&codec_));
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecOpen(&codec_context_, &codec_))
+ .WillOnce(Return(0));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ test_engine_->Initialize(&stream_, done_cb.CreateTask());
+ EXPECT_EQ(VideoDecodeEngine::kNormal, test_engine_->state());
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, Initialize_FindDecoderFails) {
+ // Test avcodec_find_decoder() returning NULL.
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
+ .WillOnce(ReturnNull());
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ test_engine_->Initialize(&stream_, done_cb.CreateTask());
+ EXPECT_EQ(VideoDecodeEngine::kError, test_engine_->state());
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, Initialize_InitThreadFails) {
+ // Test avcodec_thread_init() failing.
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
+ .WillOnce(Return(&codec_));
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2))
+ .WillOnce(Return(-1));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ test_engine_->Initialize(&stream_, done_cb.CreateTask());
+ EXPECT_EQ(VideoDecodeEngine::kError, test_engine_->state());
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, Initialize_OpenDecoderFails) {
+ // Test avcodec_open() failing.
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
+ .WillOnce(Return(&codec_));
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2))
+ .WillOnce(Return(0));
+ EXPECT_CALL(*MockFFmpeg::get(), AVCodecOpen(&codec_context_, &codec_))
+ .WillOnce(Return(-1));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ test_engine_->Initialize(&stream_, done_cb.CreateTask());
+ EXPECT_EQ(VideoDecodeEngine::kError, test_engine_->state());
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_Normal) {
+ // Expect a bunch of avcodec calls.
+ EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_));
+ EXPECT_CALL(mock_ffmpeg_,
+ AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<2>(1), // Simulate 1 byte frame.
+ Return(0)));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ bool got_result;
+ test_engine_->DecodeFrame(*buffer_, &yuv_frame_, &got_result,
+ done_cb.CreateTask());
+ EXPECT_TRUE(got_result);
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_0ByteFrame) {
+ // Expect a bunch of avcodec calls.
+ EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_));
+ EXPECT_CALL(mock_ffmpeg_,
+ AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<2>(0), // Simulate 0 byte frame.
+ Return(0)));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ bool got_result;
+ test_engine_->DecodeFrame(*buffer_, &yuv_frame_, &got_result,
+ done_cb.CreateTask());
+ EXPECT_FALSE(got_result);
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeError) {
+ // Expect a bunch of avcodec calls.
+ EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_));
+ EXPECT_CALL(mock_ffmpeg_,
+ AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _))
+ .WillOnce(Return(-1));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run());
+
+ bool got_result;
+ test_engine_->DecodeFrame(*buffer_, &yuv_frame_, &got_result,
+ done_cb.CreateTask());
+ EXPECT_FALSE(got_result);
+}
+
+TEST_F(FFmpegVideoDecodeEngineTest, GetSurfaceFormat) {
+ // YV12 formats.
+ codec_context_.pix_fmt = PIX_FMT_YUV420P;
+ EXPECT_EQ(VideoSurface::YV12, test_engine_->GetSurfaceFormat());
+ codec_context_.pix_fmt = PIX_FMT_YUVJ420P;
+ EXPECT_EQ(VideoSurface::YV12, test_engine_->GetSurfaceFormat());
+
+ // YV16 formats.
+ codec_context_.pix_fmt = PIX_FMT_YUV422P;
+ EXPECT_EQ(VideoSurface::YV16, test_engine_->GetSurfaceFormat());
+ codec_context_.pix_fmt = PIX_FMT_YUVJ422P;
+ EXPECT_EQ(VideoSurface::YV16, test_engine_->GetSurfaceFormat());
+
+ // Invalid value.
+ codec_context_.pix_fmt = PIX_FMT_NONE;
+ EXPECT_EQ(VideoSurface::INVALID, test_engine_->GetSurfaceFormat());
+}
+
+} // namespace media
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 474d30d..6390216 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -2,47 +2,143 @@
// source code is governed by a BSD-style license that can be found in the
// LICENSE file.
+#include "media/filters/ffmpeg_video_decoder.h"
+
+#include "base/task.h"
+#include "base/waitable_event.h"
+#include "media/base/callback.h"
#include "media/base/limits.h"
#include "media/base/video_frame_impl.h"
+#include "media/ffmpeg/ffmpeg_util.h"
#include "media/filters/ffmpeg_common.h"
#include "media/filters/ffmpeg_demuxer.h"
-#include "media/filters/ffmpeg_video_decoder.h"
-namespace {
+namespace media {
-const AVRational kMicrosBase = { 1, base::Time::kMicrosecondsPerSecond };
+FFmpegVideoDecodeEngine::FFmpegVideoDecodeEngine()
+ : codec_context_(NULL),
+ state_(kCreated) {
+}
-// TODO(ajwong): Move this into a utility function file and dedup with
-// FFmpegDemuxer ConvertTimestamp.
-base::TimeDelta ConvertTimestamp(const AVRational& time_base, int64 timestamp) {
- int64 microseconds = av_rescale_q(timestamp, time_base, kMicrosBase);
- return base::TimeDelta::FromMicroseconds(microseconds);
+FFmpegVideoDecodeEngine::~FFmpegVideoDecodeEngine() {
}
-} // namespace
+void FFmpegVideoDecodeEngine::Initialize(AVStream* stream, Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
-namespace media {
+ // Always try to use two threads for video decoding. There is little reason
+ // not to since current day CPUs tend to be multi-core and we measured
+ // performance benefits on older machines such as P4s with hyperthreading.
+ //
+ // Handling decoding on separate threads also frees up the pipeline thread to
+ // continue processing. Although it'd be nice to have the option of a single
+ // decoding thread, FFmpeg treats having one thread the same as having zero
+ // threads (i.e., avcodec_decode_video() will execute on the calling thread).
+ // Yet another reason for having two threads :)
+ //
+ // TODO(scherkus): some video codecs might not like avcodec_thread_init()
+ // being called on them... should attempt to find out which ones those are!
+ static const int kDecodeThreads = 2;
+
+ CHECK(state_ == kCreated);
+
+ codec_context_ = stream->codec;
+ codec_context_->flags2 |= CODEC_FLAG2_FAST; // Enable faster H264 decode.
+ // Enable motion vector search (potentially slow), strong deblocking filter
+ // for damaged macroblocks, and set our error detection sensitivity.
+ codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
+ codec_context_->error_recognition = FF_ER_CAREFUL;
+
+ // Serialize calls to avcodec_open().
+ AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
+ {
+ AutoLock auto_lock(FFmpegLock::get()->lock());
+ if (codec &&
+ avcodec_thread_init(codec_context_, kDecodeThreads) >= 0 &&
+ avcodec_open(codec_context_, codec) >= 0) {
+ state_ = kNormal;
+ } else {
+ state_ = kError;
+ }
+ }
+}
+
+// Decodes one frame of video with the given buffer.
+void FFmpegVideoDecodeEngine::DecodeFrame(const Buffer& buffer,
+ AVFrame* yuv_frame,
+ bool* got_frame,
+ Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+
+ // Create a packet for input data.
+ // Due to FFmpeg API changes we no longer have const read-only pointers.
+ //
+ // TODO(ajwong): This is dangerous since AVPacket may change size with
+ // different ffmpeg versions. Use the alloca verison.
+ AVPacket packet;
+ av_init_packet(&packet);
+ packet.data = const_cast<uint8*>(buffer.GetData());
+ packet.size = buffer.GetDataSize();
+
+ // We don't allocate AVFrame on the stack since different versions of FFmpeg
+ // may change the size of AVFrame, causing stack corruption. The solution is
+ // to let FFmpeg allocate the structure via avcodec_alloc_frame().
+ int frame_decoded = 0;
+ int result =
+ avcodec_decode_video2(codec_context_, yuv_frame, &frame_decoded, &packet);
+
+ // Log the problem if we can't decode a video frame and exit early.
+ if (result < 0) {
+ LOG(INFO) << "Error decoding a video frame with timestamp: "
+ << buffer.GetTimestamp().InMicroseconds() << " us"
+ << " , duration: "
+ << buffer.GetDuration().InMicroseconds() << " us"
+ << " , packet size: "
+ << buffer.GetDataSize() << " bytes";
+ *got_frame = false;
+ } else {
+ // If frame_decoded == 0, then no frame was produced.
+ *got_frame = frame_decoded != 0;
+ }
+}
+
+void FFmpegVideoDecodeEngine::Flush(Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+
+ avcodec_flush_buffers(codec_context_);
+}
+
+VideoSurface::Format FFmpegVideoDecodeEngine::GetSurfaceFormat() const {
+ // J (Motion JPEG) versions of YUV are full range 0..255.
+ // Regular (MPEG) YUV is 16..240.
+ // For now we will ignore the distinction and treat them the same.
+ switch (codec_context_->pix_fmt) {
+ case PIX_FMT_YUV420P:
+ case PIX_FMT_YUVJ420P:
+ return VideoSurface::YV12;
+ break;
+ case PIX_FMT_YUV422P:
+ case PIX_FMT_YUVJ422P:
+ return VideoSurface::YV16;
+ break;
+ default:
+ // TODO(scherkus): More formats here?
+ return VideoSurface::INVALID;
+ }
+}
+
+// static
+FilterFactory* FFmpegVideoDecoder::CreateFactory() {
+ return new FilterFactoryImpl1<FFmpegVideoDecoder, VideoDecodeEngine*>(
+ new FFmpegVideoDecodeEngine());
+}
-// Always try to use two threads for video decoding. There is little reason
-// not to since current day CPUs tend to be multi-core and we measured
-// performance benefits on older machines such as P4s with hyperthreading.
-//
-// Handling decoding on separate threads also frees up the pipeline thread to
-// continue processing. Although it'd be nice to have the option of a single
-// decoding thread, FFmpeg treats having one thread the same as having zero
-// threads (i.e., avcodec_decode_video() will execute on the calling thread).
-// Yet another reason for having two threads :)
-//
-// TODO(scherkus): some video codecs might not like avcodec_thread_init() being
-// called on them... should attempt to find out which ones those are!
-static const int kDecodeThreads = 2;
-
-FFmpegVideoDecoder::FFmpegVideoDecoder()
+FFmpegVideoDecoder::FFmpegVideoDecoder(VideoDecodeEngine* engine)
: width_(0),
height_(0),
time_base_(new AVRational()),
state_(kNormal),
- codec_context_(NULL) {
+ decode_engine_(engine) {
}
FFmpegVideoDecoder::~FFmpegVideoDecoder() {
@@ -55,59 +151,70 @@ bool FFmpegVideoDecoder::IsMediaFormatSupported(const MediaFormat& format) {
mime_type::kFFmpegVideo == mime_type;
}
-bool FFmpegVideoDecoder::OnInitialize(DemuxerStream* demuxer_stream) {
+void FFmpegVideoDecoder::DoInitialize(DemuxerStream* demuxer_stream,
+ bool* success,
+ Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+ *success = false;
+
// Get the AVStream by querying for the provider interface.
AVStreamProvider* av_stream_provider;
if (!demuxer_stream->QueryInterface(&av_stream_provider)) {
- return false;
+ return;
}
AVStream* av_stream = av_stream_provider->GetAVStream();
+ *time_base_ = av_stream->time_base;
+
+ // TODO(ajwong): We don't need these extra variables if |media_format_| has
+ // them. Remove.
width_ = av_stream->codec->width;
height_ = av_stream->codec->height;
- *time_base_ = av_stream->time_base;
if (width_ > Limits::kMaxDimension || height_ > Limits::kMaxDimension ||
width_ * height_ > Limits::kMaxCanvas)
- return false;
+ return;
media_format_.SetAsString(MediaFormat::kMimeType,
mime_type::kUncompressedVideo);
media_format_.SetAsInteger(MediaFormat::kWidth, width_);
media_format_.SetAsInteger(MediaFormat::kHeight, height_);
- codec_context_ = av_stream->codec;
- codec_context_->flags2 |= CODEC_FLAG2_FAST; // Enable faster H264 decode.
- // Enable motion vector search (potentially slow), strong deblocking filter
- // for damaged macroblocks, and set our error detection sensitivity.
- codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
- codec_context_->error_recognition = FF_ER_CAREFUL;
+ decode_engine_->Initialize(
+ av_stream,
+ NewRunnableMethod(this,
+ &FFmpegVideoDecoder::OnInitializeComplete,
+ success,
+ done_runner.release()));
+}
- // Serialize calls to avcodec_open().
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- {
- AutoLock auto_lock(FFmpegLock::get()->lock());
- if (!codec ||
- avcodec_thread_init(codec_context_, kDecodeThreads) < 0 ||
- avcodec_open(codec_context_, codec) < 0) {
- return false;
- }
- }
- return true;
+void FFmpegVideoDecoder::OnInitializeComplete(bool* success, Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+
+ *success = decode_engine_->state() == FFmpegVideoDecodeEngine::kNormal;
}
-void FFmpegVideoDecoder::OnSeek(base::TimeDelta time) {
+void FFmpegVideoDecoder::DoSeek(base::TimeDelta time, Task* done_cb) {
// Everything in the presentation time queue is invalid, clear the queue.
while (!pts_heap_.IsEmpty())
pts_heap_.Pop();
// We're back where we started. It should be completely safe to flush here
// since DecoderBase uses |expecting_discontinuous_| to verify that the next
- // time OnDecode() is called we will have a discontinuous buffer.
+ // time DoDecode() is called we will have a discontinuous buffer.
+ //
+ // TODO(ajwong): Should we put a guard here to prevent leaving kError.
state_ = kNormal;
- avcodec_flush_buffers(codec_context_);
+
+ decode_engine_->Flush(done_cb);
}
-void FFmpegVideoDecoder::OnDecode(Buffer* buffer) {
+void FFmpegVideoDecoder::DoDecode(Buffer* buffer, Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+
+ // TODO(ajwong): This DoDecode and OnDecodeComplete set of functions is too
+ // complicated to easily unittest. The test becomes fragile. Try to find a
+ // way to reorganize into smaller units for testing.
+
// During decode, because reads are issued asynchronously, it is possible to
// receive multiple end of stream buffers since each read is acked. When the
// first end of stream buffer is read, FFmpeg may still have frames queued
@@ -155,12 +262,31 @@ void FFmpegVideoDecoder::OnDecode(Buffer* buffer) {
}
// Otherwise, attempt to decode a single frame.
- scoped_ptr_malloc<AVFrame, ScopedPtrAVFree> yuv_frame(avcodec_alloc_frame());
- if (DecodeFrame(*buffer, codec_context_, yuv_frame.get())) {
- last_pts_ = FindPtsAndDuration(*time_base_,
- pts_heap_,
- last_pts_,
- yuv_frame.get());
+ AVFrame* yuv_frame = avcodec_alloc_frame();
+ bool* got_frame = new bool;
+ decode_engine_->DecodeFrame(
+ *buffer,
+ yuv_frame,
+ got_frame,
+ NewRunnableMethod(this,
+ &FFmpegVideoDecoder::OnDecodeComplete,
+ yuv_frame,
+ got_frame,
+ done_runner.release()));
+}
+
+void FFmpegVideoDecoder::OnDecodeComplete(AVFrame* yuv_frame, bool* got_frame,
+ Task* done_cb) {
+ // Note: The |done_runner| must be declared *last* to ensure proper
+ // destruction order.
+ scoped_ptr_malloc<AVFrame, ScopedPtrAVFree> yuv_frame_deleter(yuv_frame);
+ scoped_ptr<bool> got_frame_deleter(got_frame);
+ AutoTaskRunner done_runner(done_cb);
+
+ // If we actually got data back, enqueue a frame.
+ if (*got_frame) {
+ last_pts_ = FindPtsAndDuration(*time_base_, pts_heap_, last_pts_,
+ yuv_frame);
// Pop off a pts on a successful decode since we are "using up" one
// timestamp.
@@ -177,7 +303,7 @@ void FFmpegVideoDecoder::OnDecode(Buffer* buffer) {
}
if (!EnqueueVideoFrame(
- GetSurfaceFormat(*codec_context_), last_pts_, yuv_frame.get())) {
+ decode_engine_->GetSurfaceFormat(), last_pts_, yuv_frame)) {
// On an EnqueueEmptyFrame error, error out the whole pipeline and
// set the state to kDecodeFinished.
SignalPipelineError();
@@ -258,38 +384,6 @@ void FFmpegVideoDecoder::EnqueueEmptyFrame() {
EnqueueResult(video_frame);
}
-bool FFmpegVideoDecoder::DecodeFrame(const Buffer& buffer,
- AVCodecContext* codec_context,
- AVFrame* yuv_frame) {
- // Create a packet for input data.
- // Due to FFmpeg API changes we no longer have const read-only pointers.
- AVPacket packet;
- av_init_packet(&packet);
- packet.data = const_cast<uint8*>(buffer.GetData());
- packet.size = buffer.GetDataSize();
-
- // We don't allocate AVFrame on the stack since different versions of FFmpeg
- // may change the size of AVFrame, causing stack corruption. The solution is
- // to let FFmpeg allocate the structure via avcodec_alloc_frame().
- int frame_decoded = 0;
- int result =
- avcodec_decode_video2(codec_context, yuv_frame, &frame_decoded, &packet);
-
- // Log the problem if we can't decode a video frame and exit early.
- if (result < 0) {
- LOG(INFO) << "Error decoding a video frame with timestamp: "
- << buffer.GetTimestamp().InMicroseconds() << " us"
- << " , duration: "
- << buffer.GetDuration().InMicroseconds() << " us"
- << " , packet size: "
- << buffer.GetDataSize() << " bytes";
- return false;
- }
-
- // If frame_decoded == 0, then no frame was produced.
- return frame_decoded != 0;
-}
-
FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration(
const AVRational& time_base,
const PtsHeap& pts_heap,
@@ -333,29 +427,14 @@ FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration(
return pts;
}
-VideoSurface::Format FFmpegVideoDecoder::GetSurfaceFormat(
- const AVCodecContext& codec_context) {
- // J (Motion JPEG) versions of YUV are full range 0..255.
- // Regular (MPEG) YUV is 16..240.
- // For now we will ignore the distinction and treat them the same.
- switch (codec_context.pix_fmt) {
- case PIX_FMT_YUV420P:
- case PIX_FMT_YUVJ420P:
- return VideoSurface::YV12;
- break;
- case PIX_FMT_YUV422P:
- case PIX_FMT_YUVJ422P:
- return VideoSurface::YV16;
- break;
- default:
- // TODO(scherkus): More formats here?
- return VideoSurface::INVALID;
- }
-}
-
void FFmpegVideoDecoder::SignalPipelineError() {
host()->SetError(PIPELINE_ERROR_DECODE);
state_ = kDecodeFinished;
}
-} // namespace
+void FFmpegVideoDecoder::SetVideoDecodeEngineForTest(
+ VideoDecodeEngine* engine) {
+ decode_engine_.reset(engine);
+}
+
+} // namespace media
diff --git a/media/filters/ffmpeg_video_decoder.h b/media/filters/ffmpeg_video_decoder.h
index fba1ae7..b35e173 100644
--- a/media/filters/ffmpeg_video_decoder.h
+++ b/media/filters/ffmpeg_video_decoder.h
@@ -10,43 +10,58 @@
#include "media/base/factory.h"
#include "media/base/pts_heap.h"
#include "media/filters/decoder_base.h"
+#include "media/filters/video_decode_engine.h"
#include "testing/gtest/include/gtest/gtest_prod.h"
// FFmpeg types.
struct AVCodecContext;
struct AVFrame;
struct AVRational;
+struct AVStream;
namespace media {
-class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
+class FFmpegVideoDecodeEngine : public VideoDecodeEngine {
public:
- static FilterFactory* CreateFactory() {
- return new FilterFactoryImpl0<FFmpegVideoDecoder>();
- }
+ FFmpegVideoDecodeEngine();
+ virtual ~FFmpegVideoDecodeEngine();
- static bool IsMediaFormatSupported(const MediaFormat& media_format);
+ // Implementation of the VideoDecodeEngine Interface.
+ virtual void Initialize(AVStream* stream, Task* done_cb);
+ virtual void DecodeFrame(const Buffer& buffer, AVFrame* yuv_frame,
+ bool* got_result, Task* done_cb);
+ virtual void Flush(Task* done_cb);
+ virtual VideoSurface::Format GetSurfaceFormat() const;
- protected:
- virtual bool OnInitialize(DemuxerStream* demuxer_stream);
+ virtual State state() const { return state_; }
- virtual void OnSeek(base::TimeDelta time);
+ virtual AVCodecContext* codec_context() const { return codec_context_; }
- virtual void OnDecode(Buffer* buffer);
+ virtual void SetCodecContextForTest(AVCodecContext* context) {
+ codec_context_ = context;
+ }
private:
- friend class FilterFactoryImpl0<FFmpegVideoDecoder>;
+ AVCodecContext* codec_context_;
+ State state_;
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecodeEngine);
+};
+
+class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
+ public:
+ static FilterFactory* CreateFactory();
+ static bool IsMediaFormatSupported(const MediaFormat& media_format);
+
+ private:
+ friend class FilterFactoryImpl1<FFmpegVideoDecoder, VideoDecodeEngine*>;
friend class DecoderPrivateMock;
friend class FFmpegVideoDecoderTest;
- FRIEND_TEST(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame);
- FRIEND_TEST(FFmpegVideoDecoderTest, DecodeFrame_DecodeError);
- FRIEND_TEST(FFmpegVideoDecoderTest, DecodeFrame_Normal);
FRIEND_TEST(FFmpegVideoDecoderTest, FindPtsAndDuration);
- FRIEND_TEST(FFmpegVideoDecoderTest, GetSurfaceFormat);
- FRIEND_TEST(FFmpegVideoDecoderTest, OnDecode_EnqueueVideoFrameError);
- FRIEND_TEST(FFmpegVideoDecoderTest, OnDecode_FinishEnqueuesEmptyFrames);
- FRIEND_TEST(FFmpegVideoDecoderTest, OnDecode_TestStateTransition);
- FRIEND_TEST(FFmpegVideoDecoderTest, OnSeek);
+ FRIEND_TEST(FFmpegVideoDecoderTest, DoDecode_EnqueueVideoFrameError);
+ FRIEND_TEST(FFmpegVideoDecoderTest, DoDecode_FinishEnqueuesEmptyFrames);
+ FRIEND_TEST(FFmpegVideoDecoderTest, DoDecode_TestStateTransition);
+ FRIEND_TEST(FFmpegVideoDecoderTest, DoSeek);
// The TimeTuple struct is used to hold the needed timestamp data needed for
// enqueuing a video frame.
@@ -55,9 +70,15 @@ class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
base::TimeDelta duration;
};
- FFmpegVideoDecoder();
+ FFmpegVideoDecoder(VideoDecodeEngine* engine);
virtual ~FFmpegVideoDecoder();
+ // Implement DecoderBase template methods.
+ virtual void DoInitialize(DemuxerStream* demuxer_stream, bool* success,
+ Task* done_cb);
+ virtual void DoSeek(base::TimeDelta time, Task* done_cb);
+ virtual void DoDecode(Buffer* buffer, Task* done_cb);
+
virtual bool EnqueueVideoFrame(VideoSurface::Format surface_format,
const TimeTuple& time,
const AVFrame* frame);
@@ -68,14 +89,11 @@ class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
virtual void CopyPlane(size_t plane, const VideoSurface& surface,
const AVFrame* frame);
- // Converts a AVCodecContext |pix_fmt| to a VideoSurface::Format.
- virtual VideoSurface::Format GetSurfaceFormat(
- const AVCodecContext& codec_context);
-
- // Decodes one frame of video with the given buffer. Returns false if there
- // was a decode error, or a zero-byte frame was produced.
- virtual bool DecodeFrame(const Buffer& buffer, AVCodecContext* codec_context,
- AVFrame* yuv_frame);
+ // Methods that pickup after the decode engine has finished its action.
+ virtual void OnInitializeComplete(bool* success /* Not owned */,
+ Task* done_cb);
+ virtual void OnDecodeComplete(AVFrame* yuv_frame, bool* got_frame,
+ Task* done_cb);
// Attempt to get the PTS and Duration for this frame by examining the time
// info provided via packet stream (stored in |pts_heap|), or the info
@@ -94,6 +112,10 @@ class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
// into the kDecodeFinished state.
virtual void SignalPipelineError();
+ // Injection point for unittest to provide a mock engine. Takes ownership of
+ // the provided engine.
+ virtual void SetVideoDecodeEngineForTest(VideoDecodeEngine* engine);
+
size_t width_;
size_t height_;
@@ -109,7 +131,7 @@ class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
DecoderState state_;
- AVCodecContext* codec_context_;
+ scoped_ptr<VideoDecodeEngine> decode_engine_;
DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoder);
};
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index 9c0963b..7bb05a9 100644
--- a/media/filters/ffmpeg_video_decoder_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -5,11 +5,13 @@
#include <deque>
#include "base/singleton.h"
+#include "base/string_util.h"
#include "media/base/data_buffer.h"
#include "media/base/filters.h"
#include "media/base/mock_ffmpeg.h"
#include "media/base/mock_filter_host.h"
#include "media/base/mock_filters.h"
+#include "media/base/mock_task.h"
#include "media/filters/ffmpeg_common.h"
#include "media/filters/ffmpeg_interfaces.h"
#include "media/filters/ffmpeg_video_decoder.h"
@@ -17,10 +19,12 @@
using ::testing::_;
using ::testing::DoAll;
+using ::testing::Message;
using ::testing::Return;
using ::testing::ReturnNull;
using ::testing::SetArgumentPointee;
using ::testing::StrictMock;
+using ::testing::WithArg;
namespace media {
@@ -39,20 +43,29 @@ class MockFFmpegDemuxerStream : public MockDemuxerStream,
DISALLOW_COPY_AND_ASSIGN(MockFFmpegDemuxerStream);
};
+class MockVideoDecodeEngine : public VideoDecodeEngine {
+ public:
+ MOCK_METHOD2(Initialize, void(AVStream* stream, Task* done_cb));
+ MOCK_METHOD4(DecodeFrame, void(const Buffer& buffer, AVFrame* yuv_frame,
+ bool* got_result, Task* done_cb));
+ MOCK_METHOD1(Flush, void(Task* done_cb));
+ MOCK_CONST_METHOD0(state, State());
+ MOCK_CONST_METHOD0(GetSurfaceFormat, VideoSurface::Format());
+};
+
// Class that just mocks the private functions.
class DecoderPrivateMock : public FFmpegVideoDecoder {
public:
+ DecoderPrivateMock(VideoDecodeEngine* engine)
+ : FFmpegVideoDecoder(engine) {
+ }
+
MOCK_METHOD3(EnqueueVideoFrame, bool(VideoSurface::Format surface_format,
const TimeTuple& time,
const AVFrame* frame));
MOCK_METHOD0(EnqueueEmptyFrame, void());
MOCK_METHOD3(CopyPlane, void(size_t plane, const VideoSurface& surface,
const AVFrame* frame));
- MOCK_METHOD1(GetSurfaceFormat,
- VideoSurface::Format(const AVCodecContext& codec_context));
- MOCK_METHOD3(DecodeFrame, bool(const Buffer& buffer,
- AVCodecContext* codec_context,
- AVFrame* yuv_frame));
MOCK_METHOD4(FindPtsAndDuration, TimeTuple(const AVRational& time_base,
const PtsHeap& pts_heap,
const TimeTuple& last_pts,
@@ -73,14 +86,17 @@ class FFmpegVideoDecoderTest : public testing::Test {
MediaFormat media_format;
media_format.SetAsString(MediaFormat::kMimeType, mime_type::kFFmpegVideo);
- // Create an FFmpegVideoDecoder.
+ // Create an FFmpegVideoDecoder, and MockVideoDecodeEngine.
factory_ = FFmpegVideoDecoder::CreateFactory();
decoder_ = factory_->Create<FFmpegVideoDecoder>(media_format);
+ engine_ = new StrictMock<MockVideoDecodeEngine>();
+
DCHECK(decoder_);
- // Inject a filter host and message loop and prepare a demuxer stream.
+ // Inject mocks and prepare a demuxer stream.
decoder_->set_host(&host_);
decoder_->set_message_loop(&message_loop_);
+ decoder_->SetVideoDecodeEngineForTest(engine_);
demuxer_ = new StrictMock<MockFFmpegDemuxerStream>();
// Initialize FFmpeg fixtures.
@@ -112,6 +128,7 @@ class FFmpegVideoDecoderTest : public testing::Test {
// Fixture members.
scoped_refptr<FilterFactory> factory_;
+ MockVideoDecodeEngine* engine_; // Owned by |decoder_|.
scoped_refptr<FFmpegVideoDecoder> decoder_;
scoped_refptr<StrictMock<MockFFmpegDemuxerStream> > demuxer_;
scoped_refptr<DataBuffer> buffer_;
@@ -169,56 +186,21 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_QueryInterfaceFails) {
message_loop_.RunAllPending();
}
-TEST_F(FFmpegVideoDecoderTest, Initialize_FindDecoderFails) {
- // Test avcodec_find_decoder() returning NULL.
+TEST_F(FFmpegVideoDecoderTest, Initialize_EngineFails) {
+ // Test successful initialization.
AVStreamProvider* av_stream_provider = demuxer_;
EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id()))
.WillOnce(Return(av_stream_provider));
EXPECT_CALL(*demuxer_, GetAVStream())
.WillOnce(Return(&stream_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
- .WillOnce(ReturnNull());
- EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
- EXPECT_CALL(callback_, OnFilterCallback());
- EXPECT_CALL(callback_, OnCallbackDestroyed());
- decoder_->Initialize(demuxer_, callback_.NewCallback());
- message_loop_.RunAllPending();
-}
+ EXPECT_CALL(*engine_, Initialize(_, _))
+ .WillOnce(WithArg<1>(InvokeRunnable()));
+ EXPECT_CALL(*engine_, state())
+ .WillOnce(Return(VideoDecodeEngine::kError));
-TEST_F(FFmpegVideoDecoderTest, Initialize_InitThreadFails) {
- // Test avcodec_thread_init() failing.
- AVStreamProvider* av_stream_provider = demuxer_;
- EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id()))
- .WillOnce(Return(av_stream_provider));
- EXPECT_CALL(*demuxer_, GetAVStream())
- .WillOnce(Return(&stream_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
- .WillOnce(Return(&codec_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2))
- .WillOnce(Return(-1));
EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
- EXPECT_CALL(callback_, OnFilterCallback());
- EXPECT_CALL(callback_, OnCallbackDestroyed());
-
- decoder_->Initialize(demuxer_, callback_.NewCallback());
- message_loop_.RunAllPending();
-}
-TEST_F(FFmpegVideoDecoderTest, Initialize_OpenDecoderFails) {
- // Test avcodec_open() failing.
- AVStreamProvider* av_stream_provider = demuxer_;
- EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id()))
- .WillOnce(Return(av_stream_provider));
- EXPECT_CALL(*demuxer_, GetAVStream())
- .WillOnce(Return(&stream_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
- .WillOnce(Return(&codec_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2))
- .WillOnce(Return(0));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecOpen(&codec_context_, &codec_))
- .WillOnce(Return(-1));
- EXPECT_CALL(host_, SetError(PIPELINE_ERROR_DECODE));
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
@@ -233,12 +215,12 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_Successful) {
.WillOnce(Return(av_stream_provider));
EXPECT_CALL(*demuxer_, GetAVStream())
.WillOnce(Return(&stream_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecFindDecoder(CODEC_ID_NONE))
- .WillOnce(Return(&codec_));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecThreadInit(&codec_context_, 2))
- .WillOnce(Return(0));
- EXPECT_CALL(*MockFFmpeg::get(), AVCodecOpen(&codec_context_, &codec_))
- .WillOnce(Return(0));
+
+ EXPECT_CALL(*engine_, Initialize(_, _))
+ .WillOnce(WithArg<1>(InvokeRunnable()));
+ EXPECT_CALL(*engine_, state())
+ .WillOnce(Return(VideoDecodeEngine::kNormal));
+
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
@@ -259,66 +241,9 @@ TEST_F(FFmpegVideoDecoderTest, Initialize_Successful) {
EXPECT_EQ(kHeight, height);
}
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_Normal) {
- // Expect a bunch of avcodec calls.
- EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_));
- EXPECT_CALL(mock_ffmpeg_,
- AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<2>(1), // Simulate 1 byte frame.
- Return(0)));
-
- scoped_refptr<FFmpegVideoDecoder> decoder = new FFmpegVideoDecoder();
- EXPECT_TRUE(decoder->DecodeFrame(*buffer_, &codec_context_, &yuv_frame_));
-}
-
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_0ByteFrame) {
- // Expect a bunch of avcodec calls.
- EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_));
- EXPECT_CALL(mock_ffmpeg_,
- AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<2>(0), // Simulate 0 byte frame.
- Return(0)));
-
- scoped_refptr<FFmpegVideoDecoder> decoder = new FFmpegVideoDecoder();
- EXPECT_FALSE(decoder->DecodeFrame(*buffer_, &codec_context_, &yuv_frame_));
-}
-
-TEST_F(FFmpegVideoDecoderTest, DecodeFrame_DecodeError) {
- // Expect a bunch of avcodec calls.
- EXPECT_CALL(mock_ffmpeg_, AVInitPacket(_));
- EXPECT_CALL(mock_ffmpeg_,
- AVCodecDecodeVideo2(&codec_context_, &yuv_frame_, _, _))
- .WillOnce(Return(-1));
-
- scoped_refptr<FFmpegVideoDecoder> decoder = new FFmpegVideoDecoder();
- EXPECT_FALSE(decoder->DecodeFrame(*buffer_, &codec_context_, &yuv_frame_));
-}
-
-TEST_F(FFmpegVideoDecoderTest, GetSurfaceFormat) {
- AVCodecContext context;
- scoped_refptr<FFmpegVideoDecoder> decoder = new FFmpegVideoDecoder();
-
- // YV12 formats.
- context.pix_fmt = PIX_FMT_YUV420P;
- EXPECT_EQ(VideoSurface::YV12, decoder->GetSurfaceFormat(context));
- context.pix_fmt = PIX_FMT_YUVJ420P;
- EXPECT_EQ(VideoSurface::YV12, decoder->GetSurfaceFormat(context));
-
- // YV16 formats.
- context.pix_fmt = PIX_FMT_YUV422P;
- EXPECT_EQ(VideoSurface::YV16, decoder->GetSurfaceFormat(context));
- context.pix_fmt = PIX_FMT_YUVJ422P;
- EXPECT_EQ(VideoSurface::YV16, decoder->GetSurfaceFormat(context));
-
- // Invalid value.
- context.pix_fmt = PIX_FMT_NONE;
- EXPECT_EQ(VideoSurface::INVALID, decoder->GetSurfaceFormat(context));
-}
-
TEST_F(FFmpegVideoDecoderTest, FindPtsAndDuration) {
// Start with an empty timestamp queue.
PtsHeap pts_heap;
- scoped_refptr<FFmpegVideoDecoder> decoder = new FFmpegVideoDecoder();
// Use 1/2 second for simple results. Thus, calculated Durations should be
// 500000 microseconds.
@@ -333,16 +258,16 @@ TEST_F(FFmpegVideoDecoderTest, FindPtsAndDuration) {
// Simulate an uninitialized yuv_frame.
yuv_frame_.pts = AV_NOPTS_VALUE;
FFmpegVideoDecoder::TimeTuple result_pts =
- decoder->FindPtsAndDuration(time_base, pts_heap, last_pts, &yuv_frame_);
+ decoder_->FindPtsAndDuration(time_base, pts_heap, last_pts, &yuv_frame_);
EXPECT_EQ(116, result_pts.timestamp.InMicroseconds());
EXPECT_EQ(500000, result_pts.duration.InMicroseconds());
// Test that providing no frame has the same result as an uninitialized
// frame.
- result_pts = decoder->FindPtsAndDuration(time_base,
- pts_heap,
- last_pts,
- NULL);
+ result_pts = decoder_->FindPtsAndDuration(time_base,
+ pts_heap,
+ last_pts,
+ NULL);
EXPECT_EQ(116, result_pts.timestamp.InMicroseconds());
EXPECT_EQ(500000, result_pts.duration.InMicroseconds());
@@ -351,33 +276,33 @@ TEST_F(FFmpegVideoDecoderTest, FindPtsAndDuration) {
// data for the frame, which means that value is useless to us.
yuv_frame_.pts = 0;
result_pts =
- decoder->FindPtsAndDuration(time_base, pts_heap, last_pts, &yuv_frame_);
+ decoder_->FindPtsAndDuration(time_base, pts_heap, last_pts, &yuv_frame_);
EXPECT_EQ(116, result_pts.timestamp.InMicroseconds());
EXPECT_EQ(500000, result_pts.duration.InMicroseconds());
// Add a pts to the timequeue and make sure it overrides estimation.
pts_heap.Push(base::TimeDelta::FromMicroseconds(123));
- result_pts = decoder->FindPtsAndDuration(time_base,
- pts_heap,
- last_pts,
- &yuv_frame_);
+ result_pts = decoder_->FindPtsAndDuration(time_base,
+ pts_heap,
+ last_pts,
+ &yuv_frame_);
EXPECT_EQ(123, result_pts.timestamp.InMicroseconds());
EXPECT_EQ(500000, result_pts.duration.InMicroseconds());
// Add a pts into the frame and make sure it overrides the timequeue.
yuv_frame_.pts = 333;
yuv_frame_.repeat_pict = 2;
- result_pts = decoder->FindPtsAndDuration(time_base,
- pts_heap,
- last_pts,
- &yuv_frame_);
+ result_pts = decoder_->FindPtsAndDuration(time_base,
+ pts_heap,
+ last_pts,
+ &yuv_frame_);
EXPECT_EQ(166500000, result_pts.timestamp.InMicroseconds());
EXPECT_EQ(1500000, result_pts.duration.InMicroseconds());
}
-TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
+TEST_F(FFmpegVideoDecoderTest, DoDecode_TestStateTransition) {
// Simulates a input sequence of three buffers, and six decode requests to
- // exercise the state transitions, and bookkeeping logic of OnDecode.
+ // exercise the state transitions, and bookkeeping logic of DoDecode.
//
// We try to verify the following:
// 1) Non-EoS buffer timestamps are pushed into the pts_heap.
@@ -386,28 +311,38 @@ TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
// 4) kDecodeFinished is never left regardless of what kind of buffer is
// given.
// 5) All state transitions happen as expected.
+ MockVideoDecodeEngine* mock_engine = new StrictMock<MockVideoDecodeEngine>();
scoped_refptr<DecoderPrivateMock> mock_decoder =
- new StrictMock<DecoderPrivateMock>();
+ new StrictMock<DecoderPrivateMock>(mock_engine);
// Setup decoder to buffer one frame, decode one frame, fail one frame,
// decode one more, and then fail the last one to end decoding.
- EXPECT_CALL(*mock_decoder, DecodeFrame(_, _, _))
- .WillOnce(Return(false))
- .WillOnce(DoAll(SetArgumentPointee<2>(yuv_frame_), Return(true)))
- .WillOnce(Return(false))
- .WillOnce(DoAll(SetArgumentPointee<2>(yuv_frame_), Return(true)))
- .WillOnce(DoAll(SetArgumentPointee<2>(yuv_frame_), Return(true)))
- .WillOnce(Return(false));
- EXPECT_CALL(*mock_decoder, GetSurfaceFormat(_))
+ EXPECT_CALL(*mock_engine, DecodeFrame(_, _, _,_))
+ .WillOnce(DoAll(SetArgumentPointee<2>(false),
+ WithArg<3>(InvokeRunnable())))
+ .WillOnce(DoAll(SetArgumentPointee<1>(yuv_frame_),
+ SetArgumentPointee<2>(true),
+ WithArg<3>(InvokeRunnable())))
+ .WillOnce(DoAll(SetArgumentPointee<2>(false),
+ WithArg<3>(InvokeRunnable())))
+ .WillOnce(DoAll(SetArgumentPointee<1>(yuv_frame_),
+ SetArgumentPointee<2>(true),
+ WithArg<3>(InvokeRunnable())))
+ .WillOnce(DoAll(SetArgumentPointee<1>(yuv_frame_),
+ SetArgumentPointee<2>(true),
+ WithArg<3>(InvokeRunnable())))
+ .WillOnce(DoAll(SetArgumentPointee<2>(false),
+ WithArg<3>(InvokeRunnable())));
+ EXPECT_CALL(*mock_engine, GetSurfaceFormat())
.Times(3)
.WillRepeatedly(Return(VideoSurface::YV16));
- EXPECT_CALL(*mock_decoder, EnqueueVideoFrame(_, _, _))
- .Times(3)
- .WillRepeatedly(Return(true));
EXPECT_CALL(*mock_decoder, FindPtsAndDuration(_, _, _, _))
.WillOnce(Return(kTestPts1))
.WillOnce(Return(kTestPts2))
.WillOnce(Return(kTestPts1));
+ EXPECT_CALL(*mock_decoder, EnqueueVideoFrame(_, _, _))
+ .Times(3)
+ .WillRepeatedly(Return(true));
EXPECT_CALL(*mock_decoder, EnqueueEmptyFrame())
.Times(1);
@@ -419,21 +354,24 @@ TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
EXPECT_CALL(mock_ffmpeg_, AVFree(&yuv_frame_))
.Times(6);
+ // Setup callbacks to be executed 6 times.
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run()).Times(6);
+
// Setup initial state and check that it is sane.
- mock_decoder->codec_context_ = &codec_context_;
ASSERT_EQ(FFmpegVideoDecoder::kNormal, mock_decoder->state_);
ASSERT_TRUE(base::TimeDelta() == mock_decoder->last_pts_.timestamp);
ASSERT_TRUE(base::TimeDelta() == mock_decoder->last_pts_.duration);
// Decode once, which should simulate a buffering call.
- mock_decoder->OnDecode(buffer_);
+ mock_decoder->DoDecode(buffer_, done_cb.CreateTask());
EXPECT_EQ(FFmpegVideoDecoder::kNormal, mock_decoder->state_);
ASSERT_TRUE(base::TimeDelta() == mock_decoder->last_pts_.timestamp);
ASSERT_TRUE(base::TimeDelta() == mock_decoder->last_pts_.duration);
EXPECT_FALSE(mock_decoder->pts_heap_.IsEmpty());
// Decode a second time, which should yield the first frame.
- mock_decoder->OnDecode(buffer_);
+ mock_decoder->DoDecode(buffer_, done_cb.CreateTask());
EXPECT_EQ(FFmpegVideoDecoder::kNormal, mock_decoder->state_);
EXPECT_TRUE(kTestPts1.timestamp == mock_decoder->last_pts_.timestamp);
EXPECT_TRUE(kTestPts1.duration == mock_decoder->last_pts_.duration);
@@ -441,7 +379,7 @@ TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
// Decode a third time, with a regular buffer. The decode will error
// out, but the state should be the same.
- mock_decoder->OnDecode(buffer_);
+ mock_decoder->DoDecode(buffer_, done_cb.CreateTask());
EXPECT_EQ(FFmpegVideoDecoder::kNormal, mock_decoder->state_);
EXPECT_TRUE(kTestPts1.timestamp == mock_decoder->last_pts_.timestamp);
EXPECT_TRUE(kTestPts1.duration == mock_decoder->last_pts_.duration);
@@ -449,7 +387,7 @@ TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
// Decode a fourth time, with an end of stream buffer. This should
// yield the second frame, and stay in flushing mode.
- mock_decoder->OnDecode(end_of_stream_buffer_);
+ mock_decoder->DoDecode(end_of_stream_buffer_, done_cb.CreateTask());
EXPECT_EQ(FFmpegVideoDecoder::kFlushCodec, mock_decoder->state_);
EXPECT_TRUE(kTestPts2.timestamp == mock_decoder->last_pts_.timestamp);
EXPECT_TRUE(kTestPts2.duration == mock_decoder->last_pts_.duration);
@@ -457,7 +395,7 @@ TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
// Decode a fifth time with an end of stream buffer. this should
// yield the third frame.
- mock_decoder->OnDecode(end_of_stream_buffer_);
+ mock_decoder->DoDecode(end_of_stream_buffer_, done_cb.CreateTask());
EXPECT_EQ(FFmpegVideoDecoder::kFlushCodec, mock_decoder->state_);
EXPECT_TRUE(kTestPts1.timestamp == mock_decoder->last_pts_.timestamp);
EXPECT_TRUE(kTestPts1.duration == mock_decoder->last_pts_.duration);
@@ -465,61 +403,71 @@ TEST_F(FFmpegVideoDecoderTest, OnDecode_TestStateTransition) {
// Decode a sixth time with an end of stream buffer. This should
// Move into kDecodeFinished.
- mock_decoder->OnDecode(end_of_stream_buffer_);
+ mock_decoder->DoDecode(end_of_stream_buffer_, done_cb.CreateTask());
EXPECT_EQ(FFmpegVideoDecoder::kDecodeFinished, mock_decoder->state_);
EXPECT_TRUE(kTestPts1.timestamp == mock_decoder->last_pts_.timestamp);
EXPECT_TRUE(kTestPts1.duration == mock_decoder->last_pts_.duration);
EXPECT_TRUE(mock_decoder->pts_heap_.IsEmpty());
}
-TEST_F(FFmpegVideoDecoderTest, OnDecode_EnqueueVideoFrameError) {
+TEST_F(FFmpegVideoDecoderTest, DoDecode_EnqueueVideoFrameError) {
+ MockVideoDecodeEngine* mock_engine = new StrictMock<MockVideoDecodeEngine>();
scoped_refptr<DecoderPrivateMock> mock_decoder =
- new StrictMock<DecoderPrivateMock>();
+ new StrictMock<DecoderPrivateMock>(mock_engine);
// Setup decoder to decode one frame, but then fail on enqueue.
- EXPECT_CALL(*mock_decoder, DecodeFrame(_, _, _))
- .WillOnce(DoAll(SetArgumentPointee<2>(yuv_frame_), Return(true)));
- EXPECT_CALL(*mock_decoder, GetSurfaceFormat(_))
+ EXPECT_CALL(*mock_engine, DecodeFrame(_, _, _,_))
+ .WillOnce(DoAll(SetArgumentPointee<1>(yuv_frame_),
+ SetArgumentPointee<2>(true),
+ WithArg<3>(InvokeRunnable())));
+ EXPECT_CALL(*mock_engine, GetSurfaceFormat())
.WillOnce(Return(VideoSurface::YV16));
- EXPECT_CALL(*mock_decoder, EnqueueVideoFrame(_, _, _))
- .WillOnce(Return(false));
EXPECT_CALL(*mock_decoder, FindPtsAndDuration(_, _, _, _))
.WillOnce(Return(kTestPts1));
+ EXPECT_CALL(*mock_decoder, EnqueueVideoFrame(_, _, _))
+ .WillOnce(Return(false));
EXPECT_CALL(*mock_decoder, SignalPipelineError());
+ // Count the callback invoked.
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run()).Times(1);
+
// Setup FFmpeg expectations for frame allocations.
EXPECT_CALL(mock_ffmpeg_, AVCodecAllocFrame())
.WillOnce(Return(&yuv_frame_));
EXPECT_CALL(mock_ffmpeg_, AVFree(&yuv_frame_));
// Attempt the decode.
- mock_decoder->codec_context_ = &codec_context_;
- mock_decoder->OnDecode(buffer_);
+ mock_decoder->DoDecode(buffer_, done_cb.CreateTask());
}
-TEST_F(FFmpegVideoDecoderTest, OnDecode_FinishEnqueuesEmptyFrames) {
+TEST_F(FFmpegVideoDecoderTest, DoDecode_FinishEnqueuesEmptyFrames) {
+ MockVideoDecodeEngine* mock_engine = new StrictMock<MockVideoDecodeEngine>();
scoped_refptr<DecoderPrivateMock> mock_decoder =
- new StrictMock<DecoderPrivateMock>();
+ new StrictMock<DecoderPrivateMock>(mock_engine);
// Move the decoder into the finished state for this test.
mock_decoder->state_ = FFmpegVideoDecoder::kDecodeFinished;
- // Expect 2 calls, make two calls.
+ // Expect 2 calls, make two calls. If kDecodeFinished is set, the buffer is
+ // not even examined.
EXPECT_CALL(*mock_decoder, EnqueueEmptyFrame()).Times(3);
- mock_decoder->OnDecode(NULL);
- mock_decoder->OnDecode(buffer_);
- mock_decoder->OnDecode(end_of_stream_buffer_);
+
+ // Setup callbacks to be executed 3 times.
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run()).Times(3);
+
+ mock_decoder->DoDecode(NULL, done_cb.CreateTask());
+ mock_decoder->DoDecode(buffer_, done_cb.CreateTask());
+ mock_decoder->DoDecode(end_of_stream_buffer_, done_cb.CreateTask());
+
EXPECT_EQ(FFmpegVideoDecoder::kDecodeFinished, mock_decoder->state_);
}
-TEST_F(FFmpegVideoDecoderTest, OnSeek) {
- // Simulates receiving a call to OnSeek() while in every possible state. In
+TEST_F(FFmpegVideoDecoderTest, DoSeek) {
+ // Simulates receiving a call to DoSeek() while in every possible state. In
// every case, it should clear the timestamp queue, flush the decoder and
// reset the state to kNormal.
- scoped_refptr<DecoderPrivateMock> mock_decoder =
- new StrictMock<DecoderPrivateMock>();
- mock_decoder->codec_context_ = &codec_context_;
-
const base::TimeDelta kZero;
const FFmpegVideoDecoder::DecoderState kStates[] = {
FFmpegVideoDecoder::kNormal,
@@ -528,6 +476,13 @@ TEST_F(FFmpegVideoDecoderTest, OnSeek) {
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kStates); ++i) {
+ SCOPED_TRACE(Message() << "Iteration " << i);
+
+ MockVideoDecodeEngine* mock_engine =
+ new StrictMock<MockVideoDecodeEngine>();
+ scoped_refptr<DecoderPrivateMock> mock_decoder =
+ new StrictMock<DecoderPrivateMock>(mock_engine);
+
// Push in some timestamps.
mock_decoder->pts_heap_.Push(kTestPts1.timestamp);
mock_decoder->pts_heap_.Push(kTestPts2.timestamp);
@@ -535,10 +490,14 @@ TEST_F(FFmpegVideoDecoderTest, OnSeek) {
// Expect a flush.
mock_decoder->state_ = kStates[i];
- EXPECT_CALL(mock_ffmpeg_, AVCodecFlushBuffers(&codec_context_));
+ EXPECT_CALL(*mock_engine, Flush(_))
+ .WillOnce(WithArg<0>(InvokeRunnable()));
+
+ TaskMocker done_cb;
+ EXPECT_CALL(done_cb, Run()).Times(1);
// Seek and verify the results.
- mock_decoder->OnSeek(kZero);
+ mock_decoder->DoSeek(kZero, done_cb.CreateTask());
EXPECT_TRUE(mock_decoder->pts_heap_.IsEmpty());
EXPECT_EQ(FFmpegVideoDecoder::kNormal, mock_decoder->state_);
}
diff --git a/media/filters/video_decode_engine.h b/media/filters/video_decode_engine.h
new file mode 100644
index 0000000..f7db194
--- /dev/null
+++ b/media/filters/video_decode_engine.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2009 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_VIDEO_DECODE_ENGINE_H_
+#define MEDIA_FILTERS_VIDEO_DECODE_ENGINE_H_
+
+// FFmpeg types.
+//
+// TODO(ajwong): Try to cut the dependency on the FFmpeg types.
+struct AVFrame;
+struct AVStream;
+
+class Task;
+
+namespace media {
+
+class Buffer;
+
+class VideoDecodeEngine {
+ public:
+ enum State {
+ kCreated,
+ kNormal,
+ kError,
+ };
+
+ VideoDecodeEngine() {}
+ virtual ~VideoDecodeEngine() {}
+
+ // Initialized the engine. On successful Initialization, state() should
+ // return kNormal.
+ virtual void Initialize(AVStream* stream, Task* done_cb) = 0;
+
+ // Decodes one frame of video with the given buffer. Returns false if there
+ // was a decode error, or a zero-byte frame was produced.
+ //
+ // TODO(ajwong): Should this function just allocate a new yuv_frame and return
+ // it via a "GetNextFrame()" method or similar?
+ virtual void DecodeFrame(const Buffer& buffer, AVFrame* yuv_frame,
+ bool* got_result, Task* done_cb) = 0;
+
+ // Flushes the decode engine of any buffered input packets.
+ virtual void Flush(Task* done_cb) = 0;
+
+ // Returns the VideoSurface::Format of the resulting |yuv_frame| from
+ // DecodeFrame().
+ virtual VideoSurface::Format GetSurfaceFormat() const = 0;
+
+ // Returns the current state of the decode engine.
+ virtual State state() const = 0;
+
+};
+
+} // namespace media
+
+#endif // MEDIA_FILTERS_VIDEO_DECODE_ENGINE_H_
diff --git a/media/media.gyp b/media/media.gyp
index ef7d524..bc071f4 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -52,6 +52,7 @@
'base/buffer_queue.h',
'base/buffers.cc',
'base/buffers.h',
+ 'base/callback.h',
'base/clock.h',
'base/clock_impl.cc',
'base/clock_impl.h',
@@ -85,6 +86,8 @@
'base/yuv_row_mac.cc',
'base/yuv_row_linux.cc',
'base/yuv_row.h',
+ 'ffmpeg/ffmpeg_util.cc',
+ 'ffmpeg/ffmpeg_util.h',
'filters/audio_renderer_algorithm_base.cc',
'filters/audio_renderer_algorithm_base.h',
'filters/audio_renderer_algorithm_default.cc',
@@ -112,6 +115,7 @@
'filters/file_data_source.h',
'filters/null_audio_renderer.cc',
'filters/null_audio_renderer.h',
+ 'filters/video_decode_engine.h',
'filters/video_renderer_base.cc',
'filters/video_renderer_base.h',
],
@@ -182,6 +186,7 @@
'base/mock_filters.cc',
'base/mock_filters.h',
'base/mock_reader.h',
+ 'base/mock_task.h',
'base/pipeline_impl_unittest.cc',
'base/pts_heap_unittest.cc',
'base/run_all_unittests.cc',
@@ -192,6 +197,7 @@
'filters/audio_renderer_base_unittest.cc',
'filters/ffmpeg_demuxer_unittest.cc',
'filters/ffmpeg_glue_unittest.cc',
+ 'filters/ffmpeg_video_decode_engine_unittest.cc',
'filters/ffmpeg_video_decoder_unittest.cc',
'filters/file_data_source_unittest.cc',
'filters/video_renderer_base_unittest.cc',