summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorjiesun@google.com <jiesun@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-16 22:16:12 +0000
committerjiesun@google.com <jiesun@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-06-16 22:16:12 +0000
commitf10aba97c7626e0f1a79280c8e5bc860434e3887 (patch)
tree542105e188f982e885a5c9dad94170528ad5da33 /media
parent10c7a9fc09c8bc73cafb36631ad47d1d574724b1 (diff)
downloadchromium_src-f10aba97c7626e0f1a79280c8e5bc860434e3887.zip
chromium_src-f10aba97c7626e0f1a79280c8e5bc860434e3887.tar.gz
chromium_src-f10aba97c7626e0f1a79280c8e5bc860434e3887.tar.bz2
merge video_decoder_impl.cc and ffmpeg_video_decoder.cc
Review URL: http://codereview.chromium.org/2854002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50043 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/filters/ffmpeg_video_decoder.cc235
-rw-r--r--media/filters/ffmpeg_video_decoder.h101
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc (renamed from media/filters/video_decoder_impl_unittest.cc)82
-rw-r--r--media/filters/video_decoder_impl.cc246
-rw-r--r--media/filters/video_decoder_impl.h112
-rw-r--r--media/media.gyp4
6 files changed, 364 insertions, 416 deletions
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 0ccbd48..592f260 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -4,19 +4,248 @@
#include "media/filters/ffmpeg_video_decoder.h"
+#include <deque>
+
+#include "base/task.h"
+#include "media/base/filters.h"
+#include "media/base/limits.h"
#include "media/base/media_format.h"
-#include "media/ffmpeg/ffmpeg_common.h" // For kFFmpegVideo.
+#include "media/base/video_frame.h"
+#include "media/ffmpeg/ffmpeg_common.h"
+#include "media/ffmpeg/ffmpeg_util.h"
+#include "media/filters/ffmpeg_interfaces.h"
#include "media/filters/ffmpeg_video_decode_engine.h"
+#include "media/filters/video_decode_engine.h"
namespace media {
-FFmpegVideoDecoder::FFmpegVideoDecoder(FFmpegVideoDecodeEngine* engine)
- : VideoDecoderImpl(engine) {
+FFmpegVideoDecoder::FFmpegVideoDecoder(VideoDecodeEngine* engine)
+ : width_(0),
+ height_(0),
+ time_base_(new AVRational()),
+ state_(kNormal),
+ decode_engine_(engine) {
}
FFmpegVideoDecoder::~FFmpegVideoDecoder() {
}
+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;
+ }
+ AVStream* av_stream = av_stream_provider->GetAVStream();
+
+ time_base_->den = av_stream->r_frame_rate.num;
+ time_base_->num = av_stream->r_frame_rate.den;
+
+ // 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;
+ if (width_ > Limits::kMaxDimension ||
+ height_ > Limits::kMaxDimension ||
+ (width_ * height_) > Limits::kMaxCanvas) {
+ return;
+ }
+
+ // Only set kMimeType when derived class has not done so.
+ if (!media_format_.Contains(MediaFormat::kMimeType))
+ media_format_.SetAsString(MediaFormat::kMimeType,
+ mime_type::kUncompressedVideo);
+ media_format_.SetAsInteger(MediaFormat::kWidth, width_);
+ media_format_.SetAsInteger(MediaFormat::kHeight, height_);
+
+ decode_engine_->Initialize(
+ message_loop(),
+ av_stream,
+ NewCallback(this, &FFmpegVideoDecoder::OnEmptyBufferDone),
+ NewCallback(this, &FFmpegVideoDecoder::OnDecodeComplete),
+ NewRunnableMethod(this,
+ &FFmpegVideoDecoder::OnInitializeComplete,
+ success,
+ done_runner.release()));
+}
+
+void FFmpegVideoDecoder::OnInitializeComplete(bool* success, Task* done_cb) {
+ AutoTaskRunner done_runner(done_cb);
+
+ *success = decode_engine_->state() == VideoDecodeEngine::kNormal;
+}
+
+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 DoDecode() is called we will have a discontinuous buffer.
+ //
+ // TODO(ajwong): Should we put a guard here to prevent leaving kError.
+ state_ = kNormal;
+
+ decode_engine_->Flush(done_cb);
+}
+
+void FFmpegVideoDecoder::DoDecode(Buffer* buffer) {
+ // 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
+ // up in the decoder so we need to go through the decode loop until it stops
+ // giving sensible data. After that, the decoder should output empty
+ // frames. There are three states the decoder can be in:
+ //
+ // kNormal: This is the starting state. Buffers are decoded. Decode errors
+ // are discarded.
+ // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
+ // until no more data is returned to flush out remaining
+ // frames. The input buffer is ignored at this point.
+ // kDecodeFinished: All calls return empty frames.
+ //
+ // These are the possible state transitions.
+ //
+ // kNormal -> kFlushCodec:
+ // When buffer->IsEndOfStream() is first true.
+ // kNormal -> kDecodeFinished:
+ // A catastrophic failure occurs, and decoding needs to stop.
+ // kFlushCodec -> kDecodeFinished:
+ // When avcodec_decode_video2() returns 0 data or errors out.
+ // (any state) -> kNormal:
+ // Any time buffer->IsDiscontinuous() is true.
+ //
+ // If the decoding is finished, we just always return empty frames.
+ if (state_ == kDecodeFinished) {
+ EnqueueEmptyFrame();
+ OnEmptyBufferDone(NULL);
+ return;
+ }
+
+ // Transition to kFlushCodec on the first end of stream buffer.
+ if (state_ == kNormal && buffer->IsEndOfStream()) {
+ state_ = kFlushCodec;
+ }
+
+ // Push all incoming timestamps into the priority queue as long as we have
+ // not yet received an end of stream buffer. It is important that this line
+ // stay below the state transition into kFlushCodec done above.
+ //
+ // TODO(ajwong): This push logic, along with the pop logic below needs to
+ // be reevaluated to correctly handle decode errors.
+ if (state_ == kNormal &&
+ buffer->GetTimestamp() != StreamSample::kInvalidTimestamp) {
+ pts_heap_.Push(buffer->GetTimestamp());
+ }
+
+ // Otherwise, attempt to decode a single frame.
+ decode_engine_->EmptyThisBuffer(buffer);
+}
+
+void FFmpegVideoDecoder::OnDecodeComplete(
+ scoped_refptr<VideoFrame> video_frame) {
+ if (video_frame.get()) {
+ // If we actually got data back, enqueue a frame.
+ last_pts_ = FindPtsAndDuration(*time_base_, &pts_heap_, last_pts_,
+ video_frame.get());
+
+ video_frame->SetTimestamp(last_pts_.timestamp);
+ video_frame->SetDuration(last_pts_.duration);
+ EnqueueVideoFrame(video_frame);
+ } else {
+ // When in kFlushCodec, any errored decode, or a 0-lengthed frame,
+ // is taken as a signal to stop decoding.
+ if (state_ == kFlushCodec) {
+ state_ = kDecodeFinished;
+ EnqueueEmptyFrame();
+ }
+ }
+
+ OnEmptyBufferDone(NULL);
+}
+
+void FFmpegVideoDecoder::OnEmptyBufferDone(scoped_refptr<Buffer> buffer) {
+ // Currently we just ignore the returned buffer.
+ DecoderBase<VideoDecoder, VideoFrame>::OnDecodeComplete();
+}
+
+void FFmpegVideoDecoder::EnqueueVideoFrame(
+ const scoped_refptr<VideoFrame>& video_frame) {
+ EnqueueResult(video_frame);
+}
+
+void FFmpegVideoDecoder::EnqueueEmptyFrame() {
+ scoped_refptr<VideoFrame> video_frame;
+ VideoFrame::CreateEmptyFrame(&video_frame);
+ EnqueueResult(video_frame);
+}
+
+FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoder::FindPtsAndDuration(
+ const AVRational& time_base,
+ PtsHeap* pts_heap,
+ const TimeTuple& last_pts,
+ const VideoFrame* frame) {
+ TimeTuple pts;
+
+ // First search the VideoFrame for the pts. This is the most authoritative.
+ // Make a special exclusion for the value pts == 0. Though this is
+ // technically a valid value, it seems a number of FFmpeg codecs will
+ // mistakenly always set pts to 0.
+ //
+ // TODO(scherkus): FFmpegVideoDecodeEngine should be able to detect this
+ // situation and set the timestamp to kInvalidTimestamp.
+ DCHECK(frame);
+ base::TimeDelta timestamp = frame->GetTimestamp();
+ if (timestamp != StreamSample::kInvalidTimestamp &&
+ timestamp.ToInternalValue() != 0) {
+ pts.timestamp = timestamp;
+ } else if (!pts_heap->IsEmpty()) {
+ // If the frame did not have pts, try to get the pts from the |pts_heap|.
+ pts.timestamp = pts_heap->Top();
+ pts_heap->Pop();
+ } else if (last_pts.timestamp != StreamSample::kInvalidTimestamp &&
+ last_pts.duration != StreamSample::kInvalidTimestamp) {
+ // Guess assuming this frame was the same as the last frame.
+ pts.timestamp = last_pts.timestamp + last_pts.duration;
+ } else {
+ // Now we really have no clue!!! Mark an invalid timestamp and let the
+ // video renderer handle it (i.e., drop frame).
+ pts.timestamp = StreamSample::kInvalidTimestamp;
+ }
+
+ // Fill in the duration, using the frame itself as the authoratative source.
+ base::TimeDelta duration = frame->GetDuration();
+ if (duration != StreamSample::kInvalidTimestamp &&
+ duration.ToInternalValue() != 0) {
+ pts.duration = duration;
+ } else {
+ // Otherwise assume a normal frame duration.
+ pts.duration = ConvertTimestamp(time_base, 1);
+ }
+
+ return pts;
+}
+
+void FFmpegVideoDecoder::SignalPipelineError() {
+ host()->SetError(PIPELINE_ERROR_DECODE);
+ state_ = kDecodeFinished;
+}
+
+void FFmpegVideoDecoder::SetVideoDecodeEngineForTest(
+ VideoDecodeEngine* engine) {
+ decode_engine_.reset(engine);
+}
+
// static
FilterFactory* FFmpegVideoDecoder::CreateFactory() {
return new FilterFactoryImpl1<FFmpegVideoDecoder, FFmpegVideoDecodeEngine*>(
diff --git a/media/filters/ffmpeg_video_decoder.h b/media/filters/ffmpeg_video_decoder.h
index 8fbb74e..8350492 100644
--- a/media/filters/ffmpeg_video_decoder.h
+++ b/media/filters/ffmpeg_video_decoder.h
@@ -1,25 +1,106 @@
-// 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.
+// Copyright (c) 2010 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_FFMPEG_VIDEO_DECODER_H_
#define MEDIA_FILTERS_FFMPEG_VIDEO_DECODER_H_
-#include "media/filters/video_decoder_impl.h"
+#include "base/time.h"
+#include "media/base/pts_heap.h"
+#include "media/base/video_frame.h"
+#include "media/filters/decoder_base.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+// FFmpeg types.
+struct AVRational;
namespace media {
-class FFmpegVideoDecodeEngine;
-class FilterFactory;
-class MediaFormat;
+class VideoDecodeEngine;
-class FFmpegVideoDecoder : public VideoDecoderImpl {
+class FFmpegVideoDecoder : public DecoderBase<VideoDecoder, VideoFrame> {
public:
+ explicit FFmpegVideoDecoder(VideoDecodeEngine* engine);
+ virtual ~FFmpegVideoDecoder();
+
static FilterFactory* CreateFactory();
static bool IsMediaFormatSupported(const MediaFormat& media_format);
- FFmpegVideoDecoder(FFmpegVideoDecodeEngine* engine);
- virtual ~FFmpegVideoDecoder();
+ protected:
+ virtual void DoInitialize(DemuxerStream* demuxer_stream, bool* success,
+ Task* done_cb);
+ virtual void DoSeek(base::TimeDelta time, Task* done_cb);
+ virtual void DoDecode(Buffer* input);
+
+ protected:
+ virtual void OnEmptyBufferDone(scoped_refptr<Buffer> buffer);
+
+ private:
+ friend class FilterFactoryImpl1<FFmpegVideoDecoder, VideoDecodeEngine*>;
+ friend class DecoderPrivateMock;
+ friend class FFmpegVideoDecoderTest;
+ FRIEND_TEST(FFmpegVideoDecoderTest, FindPtsAndDuration);
+ 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.
+ struct TimeTuple {
+ base::TimeDelta timestamp;
+ base::TimeDelta duration;
+ };
+
+ enum DecoderState {
+ kNormal,
+ kFlushCodec,
+ kDecodeFinished,
+ };
+
+ // Implement DecoderBase template methods.
+ virtual void EnqueueVideoFrame(const scoped_refptr<VideoFrame>& video_frame);
+
+ // Create an empty video frame and queue it.
+ virtual void EnqueueEmptyFrame();
+
+ // Methods that pickup after the decode engine has finished its action.
+ virtual void OnInitializeComplete(bool* success /* Not owned */,
+ Task* done_cb);
+
+ virtual void OnDecodeComplete(scoped_refptr<VideoFrame> video_frame);
+
+ // 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
+ // writen into the AVFrame itself. If no data is available in either, then
+ // attempt to generate a best guess of the pts based on the last known pts.
+ //
+ // Data inside the AVFrame (if provided) is trusted the most, followed
+ // by data from the packet stream. Estimation based on the |last_pts| is
+ // reserved as a last-ditch effort.
+ virtual TimeTuple FindPtsAndDuration(const AVRational& time_base,
+ PtsHeap* pts_heap,
+ const TimeTuple& last_pts,
+ const VideoFrame* frame);
+
+ // Signals the pipeline that a decode error occurs, and moves the decoder
+ // 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_;
+
+ PtsHeap pts_heap_; // Heap of presentation timestamps.
+ TimeTuple last_pts_;
+ scoped_ptr<AVRational> time_base_; // Pointer to avoid needing full type.
+ DecoderState state_;
+ scoped_ptr<VideoDecodeEngine> decode_engine_;
+
+ DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoder);
};
} // namespace media
diff --git a/media/filters/video_decoder_impl_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index d84067c..90c16fa 100644
--- a/media/filters/video_decoder_impl_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -18,7 +18,6 @@
#include "media/filters/ffmpeg_interfaces.h"
#include "media/filters/ffmpeg_video_decoder.h"
#include "media/filters/video_decode_engine.h"
-#include "media/filters/video_decoder_impl.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
@@ -66,10 +65,10 @@ class MockVideoDecodeEngine : public VideoDecodeEngine {
};
// Class that just mocks the private functions.
-class DecoderPrivateMock : public VideoDecoderImpl {
+class DecoderPrivateMock : public FFmpegVideoDecoder {
public:
DecoderPrivateMock(VideoDecodeEngine* engine)
- : VideoDecoderImpl(engine) {
+ : FFmpegVideoDecoder(engine) {
}
MOCK_METHOD1(EnqueueVideoFrame,
@@ -85,29 +84,28 @@ class DecoderPrivateMock : public VideoDecoderImpl {
// change access qualifier for test: used in actions.
void OnDecodeComplete(scoped_refptr<VideoFrame> video_frame) {
- VideoDecoderImpl::OnDecodeComplete(video_frame);
+ FFmpegVideoDecoder::OnDecodeComplete(video_frame);
}
};
// Fixture class to facilitate writing tests. Takes care of setting up the
// FFmpeg, pipeline and filter host mocks.
-class VideoDecoderImplTest : public testing::Test {
+class FFmpegVideoDecoderTest : public testing::Test {
protected:
static const int kWidth;
static const int kHeight;
- static const VideoDecoderImpl::TimeTuple kTestPts1;
- static const VideoDecoderImpl::TimeTuple kTestPts2;
+ static const FFmpegVideoDecoder::TimeTuple kTestPts1;
+ static const FFmpegVideoDecoder::TimeTuple kTestPts2;
- VideoDecoderImplTest() {
+ FFmpegVideoDecoderTest() {
MediaFormat media_format;
media_format.SetAsString(MediaFormat::kMimeType, mime_type::kFFmpegVideo);
- // Create an VideoDecoderImpl, and MockVideoDecodeEngine. We use an
- // FFmpegVideoDecoder to instantiate a full VideoDecoderImpl with an engine.
+ // Create an FFmpegVideoDecoder, and MockVideoDecodeEngine.
//
// TODO(ajwong): Break the test's dependency on FFmpegVideoDecoder.
factory_ = FFmpegVideoDecoder::CreateFactory();
- decoder_ = factory_->Create<VideoDecoderImpl>(media_format);
+ decoder_ = factory_->Create<FFmpegVideoDecoder>(media_format);
engine_ = new StrictMock<MockVideoDecodeEngine>();
DCHECK(decoder_);
@@ -137,7 +135,7 @@ class VideoDecoderImplTest : public testing::Test {
MockFFmpeg::set(&mock_ffmpeg_);
}
- virtual ~VideoDecoderImplTest() {
+ virtual ~FFmpegVideoDecoderTest() {
// Call Stop() to shut down internal threads.
EXPECT_CALL(callback_, OnFilterCallback());
EXPECT_CALL(callback_, OnCallbackDestroyed());
@@ -153,7 +151,7 @@ class VideoDecoderImplTest : public testing::Test {
// Fixture members.
scoped_refptr<FilterFactory> factory_;
MockVideoDecodeEngine* engine_; // Owned by |decoder_|.
- scoped_refptr<VideoDecoderImpl> decoder_;
+ scoped_refptr<FFmpegVideoDecoder> decoder_;
scoped_refptr<StrictMock<MockFFmpegDemuxerStream> > demuxer_;
scoped_refptr<DataBuffer> buffer_;
scoped_refptr<DataBuffer> end_of_stream_buffer_;
@@ -170,19 +168,19 @@ class VideoDecoderImplTest : public testing::Test {
StrictMock<MockFFmpeg> mock_ffmpeg_;
private:
- DISALLOW_COPY_AND_ASSIGN(VideoDecoderImplTest);
+ DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecoderTest);
};
-const int VideoDecoderImplTest::kWidth = 1280;
-const int VideoDecoderImplTest::kHeight = 720;
-const VideoDecoderImpl::TimeTuple VideoDecoderImplTest::kTestPts1 =
+const int FFmpegVideoDecoderTest::kWidth = 1280;
+const int FFmpegVideoDecoderTest::kHeight = 720;
+const FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoderTest::kTestPts1 =
{ base::TimeDelta::FromMicroseconds(123),
base::TimeDelta::FromMicroseconds(50) };
-const VideoDecoderImpl::TimeTuple VideoDecoderImplTest::kTestPts2 =
+const FFmpegVideoDecoder::TimeTuple FFmpegVideoDecoderTest::kTestPts2 =
{ base::TimeDelta::FromMicroseconds(456),
base::TimeDelta::FromMicroseconds(60) };
-TEST(VideoDecoderImplFactoryTest, Create) {
+TEST(FFmpegVideoDecoderFactoryTest, Create) {
// Should only accept video/x-ffmpeg mime type.
scoped_refptr<FilterFactory> factory = FFmpegVideoDecoder::CreateFactory();
MediaFormat media_format;
@@ -199,7 +197,7 @@ TEST(VideoDecoderImplFactoryTest, Create) {
ASSERT_TRUE(decoder);
}
-TEST_F(VideoDecoderImplTest, Initialize_QueryInterfaceFails) {
+TEST_F(FFmpegVideoDecoderTest, Initialize_QueryInterfaceFails) {
// Test QueryInterface returning NULL.
EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id()))
.WillOnce(ReturnNull());
@@ -219,7 +217,7 @@ ACTION_P(SaveEmptyCallback, engine) {
engine->empty_buffer_callback_.reset(arg2);
}
-TEST_F(VideoDecoderImplTest, Initialize_EngineFails) {
+TEST_F(FFmpegVideoDecoderTest, Initialize_EngineFails) {
// Test successful initialization.
AVStreamProvider* av_stream_provider = demuxer_;
EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id()))
@@ -243,7 +241,7 @@ TEST_F(VideoDecoderImplTest, Initialize_EngineFails) {
message_loop_.RunAllPending();
}
-TEST_F(VideoDecoderImplTest, Initialize_Successful) {
+TEST_F(FFmpegVideoDecoderTest, Initialize_Successful) {
// Test successful initialization.
AVStreamProvider* av_stream_provider = demuxer_;
EXPECT_CALL(*demuxer_, QueryInterface(AVStreamProvider::interface_id()))
@@ -278,7 +276,7 @@ TEST_F(VideoDecoderImplTest, Initialize_Successful) {
EXPECT_EQ(kHeight, height);
}
-TEST_F(VideoDecoderImplTest, FindPtsAndDuration) {
+TEST_F(FFmpegVideoDecoderTest, FindPtsAndDuration) {
// Start with an empty timestamp queue.
PtsHeap pts_heap;
@@ -286,7 +284,7 @@ TEST_F(VideoDecoderImplTest, FindPtsAndDuration) {
// 500000 microseconds.
AVRational time_base = {1, 2};
- VideoDecoderImpl::TimeTuple last_pts;
+ FFmpegVideoDecoder::TimeTuple last_pts;
last_pts.timestamp = StreamSample::kInvalidTimestamp;
last_pts.duration = StreamSample::kInvalidTimestamp;
@@ -294,7 +292,7 @@ TEST_F(VideoDecoderImplTest, FindPtsAndDuration) {
// determine a timestamp at all.
video_frame_->SetTimestamp(StreamSample::kInvalidTimestamp);
video_frame_->SetDuration(StreamSample::kInvalidTimestamp);
- VideoDecoderImpl::TimeTuple result_pts =
+ FFmpegVideoDecoder::TimeTuple result_pts =
decoder_->FindPtsAndDuration(time_base, &pts_heap,
last_pts, video_frame_.get());
EXPECT_EQ(StreamSample::kInvalidTimestamp.InMicroseconds(),
@@ -363,7 +361,7 @@ ACTION_P(ConsumePTS, pts_heap) {
pts_heap->Pop();
}
-TEST_F(VideoDecoderImplTest, DoDecode_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 DoDecode.
//
@@ -401,19 +399,19 @@ TEST_F(VideoDecoderImplTest, DoDecode_TestStateTransition) {
.Times(6);
// Setup initial state and check that it is sane.
- ASSERT_EQ(VideoDecoderImpl::kNormal, mock_decoder->state_);
+ 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->DoDecode(buffer_);
- EXPECT_EQ(VideoDecoderImpl::kNormal, mock_decoder->state_);
+ 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->DoDecode(buffer_);
- EXPECT_EQ(VideoDecoderImpl::kNormal, mock_decoder->state_);
+ 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);
EXPECT_FALSE(mock_decoder->pts_heap_.IsEmpty());
@@ -421,7 +419,7 @@ TEST_F(VideoDecoderImplTest, DoDecode_TestStateTransition) {
// Decode a third time, with a regular buffer. The decode will error
// out, but the state should be the same.
mock_decoder->DoDecode(buffer_);
- EXPECT_EQ(VideoDecoderImpl::kNormal, mock_decoder->state_);
+ 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);
EXPECT_FALSE(mock_decoder->pts_heap_.IsEmpty());
@@ -429,7 +427,7 @@ TEST_F(VideoDecoderImplTest, DoDecode_TestStateTransition) {
// Decode a fourth time, with an end of stream buffer. This should
// yield the second frame, and stay in flushing mode.
mock_decoder->DoDecode(end_of_stream_buffer_);
- EXPECT_EQ(VideoDecoderImpl::kFlushCodec, mock_decoder->state_);
+ 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);
EXPECT_FALSE(mock_decoder->pts_heap_.IsEmpty());
@@ -437,7 +435,7 @@ TEST_F(VideoDecoderImplTest, DoDecode_TestStateTransition) {
// Decode a fifth time with an end of stream buffer. this should
// yield the third frame.
mock_decoder->DoDecode(end_of_stream_buffer_);
- EXPECT_EQ(VideoDecoderImpl::kFlushCodec, mock_decoder->state_);
+ 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);
EXPECT_TRUE(mock_decoder->pts_heap_.IsEmpty());
@@ -445,19 +443,19 @@ TEST_F(VideoDecoderImplTest, DoDecode_TestStateTransition) {
// Decode a sixth time with an end of stream buffer. This should
// Move into kDecodeFinished.
mock_decoder->DoDecode(end_of_stream_buffer_);
- EXPECT_EQ(VideoDecoderImpl::kDecodeFinished, mock_decoder->state_);
+ 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(VideoDecoderImplTest, DoDecode_FinishEnqueuesEmptyFrames) {
+TEST_F(FFmpegVideoDecoderTest, DoDecode_FinishEnqueuesEmptyFrames) {
MockVideoDecodeEngine* mock_engine = new StrictMock<MockVideoDecodeEngine>();
scoped_refptr<DecoderPrivateMock> mock_decoder =
new StrictMock<DecoderPrivateMock>(mock_engine);
// Move the decoder into the finished state for this test.
- mock_decoder->state_ = VideoDecoderImpl::kDecodeFinished;
+ mock_decoder->state_ = FFmpegVideoDecoder::kDecodeFinished;
// Expect 2 calls, make two calls. If kDecodeFinished is set, the buffer is
// not even examined.
@@ -467,18 +465,18 @@ TEST_F(VideoDecoderImplTest, DoDecode_FinishEnqueuesEmptyFrames) {
mock_decoder->DoDecode(NULL);
mock_decoder->DoDecode(buffer_);
mock_decoder->DoDecode(end_of_stream_buffer_);
- EXPECT_EQ(VideoDecoderImpl::kDecodeFinished, mock_decoder->state_);
+ EXPECT_EQ(FFmpegVideoDecoder::kDecodeFinished, mock_decoder->state_);
}
-TEST_F(VideoDecoderImplTest, DoSeek) {
+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.
const base::TimeDelta kZero;
- const VideoDecoderImpl::DecoderState kStates[] = {
- VideoDecoderImpl::kNormal,
- VideoDecoderImpl::kFlushCodec,
- VideoDecoderImpl::kDecodeFinished,
+ const FFmpegVideoDecoder::DecoderState kStates[] = {
+ FFmpegVideoDecoder::kNormal,
+ FFmpegVideoDecoder::kFlushCodec,
+ FFmpegVideoDecoder::kDecodeFinished,
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kStates); ++i) {
@@ -505,7 +503,7 @@ TEST_F(VideoDecoderImplTest, DoSeek) {
// Seek and verify the results.
mock_decoder->DoSeek(kZero, done_cb.CreateTask());
EXPECT_TRUE(mock_decoder->pts_heap_.IsEmpty());
- EXPECT_EQ(VideoDecoderImpl::kNormal, mock_decoder->state_);
+ EXPECT_EQ(FFmpegVideoDecoder::kNormal, mock_decoder->state_);
}
}
diff --git a/media/filters/video_decoder_impl.cc b/media/filters/video_decoder_impl.cc
deleted file mode 100644
index 5f3dbf0..0000000
--- a/media/filters/video_decoder_impl.cc
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright (c) 2010 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/video_decoder_impl.h"
-
-#include "base/task.h"
-#include "media/base/filters.h"
-#include "media/base/limits.h"
-#include "media/base/video_frame.h"
-#include "media/ffmpeg/ffmpeg_common.h"
-#include "media/filters/ffmpeg_interfaces.h"
-#include "media/filters/video_decode_engine.h"
-#include "media/ffmpeg/ffmpeg_util.h"
-
-namespace media {
-
-VideoDecoderImpl::VideoDecoderImpl(VideoDecodeEngine* engine)
- : width_(0),
- height_(0),
- time_base_(new AVRational()),
- state_(kNormal),
- decode_engine_(engine) {
-}
-
-VideoDecoderImpl::~VideoDecoderImpl() {
-}
-
-void VideoDecoderImpl::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;
- }
- AVStream* av_stream = av_stream_provider->GetAVStream();
-
- time_base_->den = av_stream->r_frame_rate.num;
- time_base_->num = av_stream->r_frame_rate.den;
-
- // 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;
- if (width_ > Limits::kMaxDimension ||
- height_ > Limits::kMaxDimension ||
- (width_ * height_) > Limits::kMaxCanvas) {
- return;
- }
-
- // Only set kMimeType when derived class has not done so.
- if (!media_format_.Contains(MediaFormat::kMimeType))
- media_format_.SetAsString(MediaFormat::kMimeType,
- mime_type::kUncompressedVideo);
- media_format_.SetAsInteger(MediaFormat::kWidth, width_);
- media_format_.SetAsInteger(MediaFormat::kHeight, height_);
-
- decode_engine_->Initialize(
- message_loop(),
- av_stream,
- NewCallback(this, &VideoDecoderImpl::OnEmptyBufferDone),
- NewCallback(this, &VideoDecoderImpl::OnDecodeComplete),
- NewRunnableMethod(this,
- &VideoDecoderImpl::OnInitializeComplete,
- success,
- done_runner.release()));
-}
-
-void VideoDecoderImpl::OnInitializeComplete(bool* success, Task* done_cb) {
- AutoTaskRunner done_runner(done_cb);
-
- *success = decode_engine_->state() == VideoDecodeEngine::kNormal;
-}
-
-void VideoDecoderImpl::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 DoDecode() is called we will have a discontinuous buffer.
- //
- // TODO(ajwong): Should we put a guard here to prevent leaving kError.
- state_ = kNormal;
-
- decode_engine_->Flush(done_cb);
-}
-
-void VideoDecoderImpl::DoDecode(Buffer* buffer) {
- // 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
- // up in the decoder so we need to go through the decode loop until it stops
- // giving sensible data. After that, the decoder should output empty
- // frames. There are three states the decoder can be in:
- //
- // kNormal: This is the starting state. Buffers are decoded. Decode errors
- // are discarded.
- // kFlushCodec: There isn't any more input data. Call avcodec_decode_video2
- // until no more data is returned to flush out remaining
- // frames. The input buffer is ignored at this point.
- // kDecodeFinished: All calls return empty frames.
- //
- // These are the possible state transitions.
- //
- // kNormal -> kFlushCodec:
- // When buffer->IsEndOfStream() is first true.
- // kNormal -> kDecodeFinished:
- // A catastrophic failure occurs, and decoding needs to stop.
- // kFlushCodec -> kDecodeFinished:
- // When avcodec_decode_video2() returns 0 data or errors out.
- // (any state) -> kNormal:
- // Any time buffer->IsDiscontinuous() is true.
- //
- // If the decoding is finished, we just always return empty frames.
- if (state_ == kDecodeFinished) {
- EnqueueEmptyFrame();
- OnEmptyBufferDone(NULL);
- return;
- }
-
- // Transition to kFlushCodec on the first end of stream buffer.
- if (state_ == kNormal && buffer->IsEndOfStream()) {
- state_ = kFlushCodec;
- }
-
- // Push all incoming timestamps into the priority queue as long as we have
- // not yet received an end of stream buffer. It is important that this line
- // stay below the state transition into kFlushCodec done above.
- //
- // TODO(ajwong): This push logic, along with the pop logic below needs to
- // be reevaluated to correctly handle decode errors.
- if (state_ == kNormal &&
- buffer->GetTimestamp() != StreamSample::kInvalidTimestamp) {
- pts_heap_.Push(buffer->GetTimestamp());
- }
-
- // Otherwise, attempt to decode a single frame.
- decode_engine_->EmptyThisBuffer(buffer);
-}
-
-void VideoDecoderImpl::OnDecodeComplete(scoped_refptr<VideoFrame> video_frame) {
- if (video_frame.get()) {
- // If we actually got data back, enqueue a frame.
- last_pts_ = FindPtsAndDuration(*time_base_, &pts_heap_, last_pts_,
- video_frame.get());
-
- video_frame->SetTimestamp(last_pts_.timestamp);
- video_frame->SetDuration(last_pts_.duration);
- EnqueueVideoFrame(video_frame);
- } else {
- // When in kFlushCodec, any errored decode, or a 0-lengthed frame,
- // is taken as a signal to stop decoding.
- if (state_ == kFlushCodec) {
- state_ = kDecodeFinished;
- EnqueueEmptyFrame();
- }
- }
-
- OnEmptyBufferDone(NULL);
-}
-
-void VideoDecoderImpl::OnEmptyBufferDone(scoped_refptr<Buffer> buffer) {
- // TODO(jiesun): what |DecodeBase::OnDecodeComplete| done is just
- // what should be done in EmptyThisBufferCallback.
- // Currently we just ignore the returned buffer.
- DecoderBase<VideoDecoder, VideoFrame>::OnDecodeComplete();
-}
-
-void VideoDecoderImpl::EnqueueVideoFrame(
- const scoped_refptr<VideoFrame>& video_frame) {
- EnqueueResult(video_frame);
-}
-
-void VideoDecoderImpl::EnqueueEmptyFrame() {
- scoped_refptr<VideoFrame> video_frame;
- VideoFrame::CreateEmptyFrame(&video_frame);
- EnqueueResult(video_frame);
-}
-
-VideoDecoderImpl::TimeTuple VideoDecoderImpl::FindPtsAndDuration(
- const AVRational& time_base,
- PtsHeap* pts_heap,
- const TimeTuple& last_pts,
- const VideoFrame* frame) {
- TimeTuple pts;
-
- // First search the VideoFrame for the pts. This is the most authoritative.
- // Make a special exclusion for the value pts == 0. Though this is
- // technically a valid value, it seems a number of FFmpeg codecs will
- // mistakenly always set pts to 0.
- //
- // TODO(scherkus): FFmpegVideoDecodeEngine should be able to detect this
- // situation and set the timestamp to kInvalidTimestamp.
- DCHECK(frame);
- base::TimeDelta timestamp = frame->GetTimestamp();
- if (timestamp != StreamSample::kInvalidTimestamp &&
- timestamp.ToInternalValue() != 0) {
- pts.timestamp = timestamp;
- } else if (!pts_heap->IsEmpty()) {
- // If the frame did not have pts, try to get the pts from the |pts_heap|.
- pts.timestamp = pts_heap->Top();
- pts_heap->Pop();
- } else if (last_pts.timestamp != StreamSample::kInvalidTimestamp &&
- last_pts.duration != StreamSample::kInvalidTimestamp) {
- // Guess assuming this frame was the same as the last frame.
- pts.timestamp = last_pts.timestamp + last_pts.duration;
- } else {
- // Now we really have no clue!!! Mark an invalid timestamp and let the
- // video renderer handle it (i.e., drop frame).
- pts.timestamp = StreamSample::kInvalidTimestamp;
- }
-
- // Fill in the duration, using the frame itself as the authoratative source.
- base::TimeDelta duration = frame->GetDuration();
- if (duration != StreamSample::kInvalidTimestamp &&
- duration.ToInternalValue() != 0) {
- pts.duration = duration;
- } else {
- // Otherwise assume a normal frame duration.
- pts.duration = ConvertTimestamp(time_base, 1);
- }
-
- return pts;
-}
-
-void VideoDecoderImpl::SignalPipelineError() {
- host()->SetError(PIPELINE_ERROR_DECODE);
- state_ = kDecodeFinished;
-}
-
-void VideoDecoderImpl::SetVideoDecodeEngineForTest(
- VideoDecodeEngine* engine) {
- decode_engine_.reset(engine);
-}
-
-} // namespace media
diff --git a/media/filters/video_decoder_impl.h b/media/filters/video_decoder_impl.h
deleted file mode 100644
index d972a8f..0000000
--- a/media/filters/video_decoder_impl.h
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2010 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_DECODER_IMPL_H_
-#define MEDIA_FILTERS_VIDEO_DECODER_IMPL_H_
-
-#include "base/time.h"
-#include "media/base/pts_heap.h"
-#include "media/base/video_frame.h"
-#include "media/filters/decoder_base.h"
-#include "testing/gtest/include/gtest/gtest_prod.h"
-
-// FFmpeg types.
-struct AVRational;
-
-namespace media {
-
-class VideoDecodeEngine;
-
-class VideoDecoderImpl : public DecoderBase<VideoDecoder, VideoFrame> {
- protected:
- VideoDecoderImpl(VideoDecodeEngine* engine);
-
- // Make this class abstract to document that this class cannot be used
- // directly as a filter type because it does not implement the static methods
- // CreateFactory() and IsMediaFormatSupported().
- //
- // TODO(ajwong): When we clean up the filters to not required a static
- // implementation of CreateFactory() and IsMediaFormatSupported(), this
- // class doesn't probably need to be abstract.
- virtual ~VideoDecoderImpl() = 0;
-
- virtual void DoInitialize(DemuxerStream* demuxer_stream, bool* success,
- Task* done_cb);
- virtual void DoSeek(base::TimeDelta time, Task* done_cb);
- virtual void DoDecode(Buffer* input);
-
- protected:
- virtual void OnEmptyBufferDone(scoped_refptr<Buffer> buffer);
-
- private:
- friend class FilterFactoryImpl1<VideoDecoderImpl, VideoDecodeEngine*>;
- friend class DecoderPrivateMock;
- friend class VideoDecoderImplTest;
- FRIEND_TEST(VideoDecoderImplTest, FindPtsAndDuration);
- FRIEND_TEST(VideoDecoderImplTest, DoDecode_EnqueueVideoFrameError);
- FRIEND_TEST(VideoDecoderImplTest, DoDecode_FinishEnqueuesEmptyFrames);
- FRIEND_TEST(VideoDecoderImplTest, DoDecode_TestStateTransition);
- FRIEND_TEST(VideoDecoderImplTest, DoSeek);
-
- // The TimeTuple struct is used to hold the needed timestamp data needed for
- // enqueuing a video frame.
- struct TimeTuple {
- base::TimeDelta timestamp;
- base::TimeDelta duration;
- };
-
- enum DecoderState {
- kNormal,
- kFlushCodec,
- kDecodeFinished,
- };
-
- // Implement DecoderBase template methods.
- virtual void EnqueueVideoFrame(const scoped_refptr<VideoFrame>& video_frame);
-
- // Create an empty video frame and queue it.
- virtual void EnqueueEmptyFrame();
-
- // Methods that pickup after the decode engine has finished its action.
- virtual void OnInitializeComplete(bool* success /* Not owned */,
- Task* done_cb);
-
- virtual void OnDecodeComplete(scoped_refptr<VideoFrame> video_frame);
-
- // 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
- // writen into the AVFrame itself. If no data is available in either, then
- // attempt to generate a best guess of the pts based on the last known pts.
- //
- // Data inside the AVFrame (if provided) is trusted the most, followed
- // by data from the packet stream. Estimation based on the |last_pts| is
- // reserved as a last-ditch effort.
- virtual TimeTuple FindPtsAndDuration(const AVRational& time_base,
- PtsHeap* pts_heap,
- const TimeTuple& last_pts,
- const VideoFrame* frame);
-
- // Signals the pipeline that a decode error occurs, and moves the decoder
- // 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_;
-
- PtsHeap pts_heap_; // Heap of presentation timestamps.
- TimeTuple last_pts_;
- scoped_ptr<AVRational> time_base_; // Pointer to avoid needing full type.
- DecoderState state_;
- scoped_ptr<VideoDecodeEngine> decode_engine_;
-
- DISALLOW_COPY_AND_ASSIGN(VideoDecoderImpl);
-};
-
-} // namespace media
-
-#endif // MEDIA_FILTERS_VIDEO_DECODER_IMPL_H_
diff --git a/media/media.gyp b/media/media.gyp
index 39aadd3..f3e57ff 100644
--- a/media/media.gyp
+++ b/media/media.gyp
@@ -121,8 +121,6 @@
'filters/omx_video_decode_engine.h',
'filters/omx_video_decoder.cc',
'filters/omx_video_decoder.h',
- 'filters/video_decoder_impl.cc',
- 'filters/video_decoder_impl.h',
'filters/video_decode_engine.h',
'filters/video_renderer_base.cc',
'filters/video_renderer_base.h',
@@ -211,8 +209,8 @@
'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_decoder_impl_unittest.cc',
'filters/video_renderer_base_unittest.cc',
'omx/mock_omx.cc',
'omx/mock_omx.h',