diff options
-rw-r--r-- | media/filters/ffmpeg_video_decoder.cc | 235 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.h | 101 | ||||
-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.cc | 246 | ||||
-rw-r--r-- | media/filters/video_decoder_impl.h | 112 | ||||
-rw-r--r-- | media/media.gyp | 4 |
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', |