summaryrefslogtreecommitdiffstats
path: root/media/video
diff options
context:
space:
mode:
authorscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-04 02:04:09 +0000
committerscherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-04 02:04:09 +0000
commitf23676abfed3df60d1be4455d422acdb70facf1c (patch)
tree7f4a926b34fe708f1dbf00407eca0f253d1c6ef0 /media/video
parent760afa9837be0fb92b2994360743ccac35fe7384 (diff)
downloadchromium_src-f23676abfed3df60d1be4455d422acdb70facf1c.zip
chromium_src-f23676abfed3df60d1be4455d422acdb70facf1c.tar.gz
chromium_src-f23676abfed3df60d1be4455d422acdb70facf1c.tar.bz2
Simplify VideoDecodeEngine interface by making everything synchronous.
Although I plan to remove VideoDecodeEngine entirely it requires detangling some of the code first. Other noteworthy changes: - It's no longer valid to call VideoFrameReady(NULL), instead FFmpegVideoDecoder will raise an error the moment it finds one - Buffer recycling has been vanquished (for now), with video frames always allocated in the decoder - Produce/ConsumeVideoFrame() has been replaced by Read() - Video decode byte statistics are only updated if more than 0 bytes were decoded - FFmpegVideoDecodeEngine no longer attempts to preroll Review URL: http://codereview.chromium.org/8417019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@108612 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/video')
-rw-r--r--media/video/ffmpeg_video_decode_engine.cc211
-rw-r--r--media/video/ffmpeg_video_decode_engine.h49
-rw-r--r--media/video/ffmpeg_video_decode_engine_unittest.cc286
-rw-r--r--media/video/video_decode_engine.h98
4 files changed, 86 insertions, 558 deletions
diff --git a/media/video/ffmpeg_video_decode_engine.cc b/media/video/ffmpeg_video_decode_engine.cc
index ceaa802..a6bff26 100644
--- a/media/video/ffmpeg_video_decode_engine.cc
+++ b/media/video/ffmpeg_video_decode_engine.cc
@@ -5,12 +5,11 @@
#include "media/video/ffmpeg_video_decode_engine.h"
#include "base/command_line.h"
+#include "base/logging.h"
#include "base/string_number_conversions.h"
-#include "base/task.h"
#include "media/base/buffers.h"
-#include "media/base/limits.h"
#include "media/base/media_switches.h"
-#include "media/base/pipeline.h"
+#include "media/base/video_decoder_config.h"
#include "media/base/video_util.h"
#include "media/ffmpeg/ffmpeg_common.h"
@@ -18,26 +17,16 @@ namespace media {
FFmpegVideoDecodeEngine::FFmpegVideoDecodeEngine()
: codec_context_(NULL),
- event_handler_(NULL),
+ av_frame_(NULL),
frame_rate_numerator_(0),
- frame_rate_denominator_(0),
- pending_input_buffers_(0),
- pending_output_buffers_(0),
- output_eos_reached_(false),
- flush_pending_(false) {
+ frame_rate_denominator_(0) {
}
FFmpegVideoDecodeEngine::~FFmpegVideoDecodeEngine() {
- if (codec_context_) {
- av_free(codec_context_->extradata);
- avcodec_close(codec_context_);
- av_free(codec_context_);
- }
+ Uninitialize();
}
-void FFmpegVideoDecodeEngine::Initialize(
- VideoDecodeEngine::EventHandler* event_handler,
- const VideoDecoderConfig& config) {
+bool FFmpegVideoDecodeEngine::Initialize(const VideoDecoderConfig& config) {
frame_rate_numerator_ = config.frame_rate_numerator();
frame_rate_denominator_ = config.frame_rate_denominator();
@@ -80,72 +69,30 @@ void FFmpegVideoDecodeEngine::Initialize(
codec_context_->thread_count = decode_threads;
- // 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().
- av_frame_.reset(avcodec_alloc_frame());
-
- // If we do not have enough buffers, we will report error too.
- frame_queue_available_.clear();
-
- // Convert the pixel format to video format and ensure we support it.
- VideoFrame::Format format =
- PixelFormatToVideoFormat(codec_context_->pix_fmt);
-
- bool success = false;
- if (format != VideoFrame::INVALID) {
- // Create output buffer pool when direct rendering is not used.
- for (size_t i = 0; i < Limits::kMaxVideoFrames; ++i) {
- scoped_refptr<VideoFrame> video_frame =
- VideoFrame::CreateFrame(format,
- config.visible_rect().width(),
- config.visible_rect().height(),
- kNoTimestamp,
- kNoTimestamp);
- frame_queue_available_.push_back(video_frame);
- }
-
- // Open the codec!
- success = codec && avcodec_open(codec_context_, codec) >= 0;
- }
+ av_frame_ = avcodec_alloc_frame();
- event_handler_ = event_handler;
- event_handler_->OnInitializeComplete(success);
+ // Open the codec!
+ return codec && avcodec_open(codec_context_, codec) >= 0;
}
-void FFmpegVideoDecodeEngine::ConsumeVideoSample(
- scoped_refptr<Buffer> buffer) {
- pending_input_buffers_--;
- if (flush_pending_) {
- TryToFinishPendingFlush();
- } else {
- // Otherwise try to decode this buffer.
- DecodeFrame(buffer);
+void FFmpegVideoDecodeEngine::Uninitialize() {
+ if (codec_context_) {
+ av_free(codec_context_->extradata);
+ avcodec_close(codec_context_);
+ av_free(codec_context_);
+ codec_context_ = NULL;
}
-}
-
-void FFmpegVideoDecodeEngine::ProduceVideoFrame(
- scoped_refptr<VideoFrame> frame) {
- // We should never receive NULL frame or EOS frame.
- DCHECK(frame.get() && !frame->IsEndOfStream());
-
- // Increment pending output buffer count.
- pending_output_buffers_++;
-
- // Return this frame to available pool after display.
- frame_queue_available_.push_back(frame);
-
- if (flush_pending_) {
- TryToFinishPendingFlush();
- } else if (!output_eos_reached_) {
- // If we already deliver EOS to renderer, we stop reading new input.
- ReadInput();
+ if (av_frame_) {
+ av_free(av_frame_);
+ av_frame_ = NULL;
}
+ frame_rate_numerator_ = 0;
+ frame_rate_denominator_ = 0;
}
-// Try to decode frame when both input and output are ready.
-void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
- scoped_refptr<VideoFrame> video_frame;
+bool FFmpegVideoDecodeEngine::Decode(const scoped_refptr<Buffer>& buffer,
+ scoped_refptr<VideoFrame>* video_frame) {
+ DCHECK(video_frame);
// Create a packet for input data.
// Due to FFmpeg API changes we no longer have const read-only pointers.
@@ -154,9 +101,6 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
packet.data = const_cast<uint8*>(buffer->GetData());
packet.size = buffer->GetDataSize();
- PipelineStatistics statistics;
- statistics.video_bytes_decoded = buffer->GetDataSize();
-
// Let FFmpeg handle presentation timestamp reordering.
codec_context_->reordered_opaque = buffer->GetTimestamp().InMicroseconds();
@@ -166,7 +110,7 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
int frame_decoded = 0;
int result = avcodec_decode_video2(codec_context_,
- av_frame_.get(),
+ av_frame_,
&frame_decoded,
&packet);
// Log the problem if we can't decode a video frame and exit early.
@@ -175,23 +119,17 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
<< buffer->GetTimestamp().InMicroseconds() << " us, duration: "
<< buffer->GetDuration().InMicroseconds() << " us, packet size: "
<< buffer->GetDataSize() << " bytes";
- event_handler_->OnError();
- return;
+ *video_frame = NULL;
+ return false;
}
- // If frame_decoded == 0, then no frame was produced.
- // In this case, if we already begin to flush codec with empty
- // input packet at the end of input stream, the first time we
- // encounter frame_decoded == 0 signal output frame had been
- // drained, we mark the flag. Otherwise we read from demuxer again.
+ // If no frame was produced then signal that more data is required to
+ // produce more frames. This can happen under two circumstances:
+ // 1) Decoder was recently initialized/flushed
+ // 2) End of stream was reached and all internal frames have been output
if (frame_decoded == 0) {
- if (buffer->IsEndOfStream()) { // We had started flushing.
- event_handler_->ConsumeVideoFrame(video_frame, statistics);
- output_eos_reached_ = true;
- } else {
- ReadInput();
- }
- return;
+ *video_frame = NULL;
+ return true;
}
// TODO(fbarchard): Work around for FFmpeg http://crbug.com/27675
@@ -200,8 +138,16 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
if (!av_frame_->data[VideoFrame::kYPlane] ||
!av_frame_->data[VideoFrame::kUPlane] ||
!av_frame_->data[VideoFrame::kVPlane]) {
- event_handler_->OnError();
- return;
+ LOG(ERROR) << "Video frame was produced yet has invalid frame data.";
+ *video_frame = NULL;
+ return false;
+ }
+
+ // We've got a frame! Make sure we have a place to store it.
+ *video_frame = AllocateVideoFrame();
+ if (!(*video_frame)) {
+ LOG(ERROR) << "Failed to allocate video frame";
+ return false;
}
// Determine timestamp and calculate the duration based on the repeat picture
@@ -217,83 +163,38 @@ void FFmpegVideoDecodeEngine::DecodeFrame(scoped_refptr<Buffer> buffer) {
doubled_time_base.num = frame_rate_denominator_;
doubled_time_base.den = frame_rate_numerator_ * 2;
- base::TimeDelta timestamp =
- base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque);
- base::TimeDelta duration =
- ConvertFromTimeBase(doubled_time_base, 2 + av_frame_->repeat_pict);
-
- // Available frame is guaranteed, because we issue as much reads as
- // available frame, except the case of |frame_decoded| == 0, which
- // implies decoder order delay, and force us to read more inputs.
- DCHECK(frame_queue_available_.size());
- video_frame = frame_queue_available_.front();
- frame_queue_available_.pop_front();
+ (*video_frame)->SetTimestamp(
+ base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque));
+ (*video_frame)->SetDuration(
+ ConvertFromTimeBase(doubled_time_base, 2 + av_frame_->repeat_pict));
// Copy the frame data since FFmpeg reuses internal buffers for AVFrame
// output, meaning the data is only valid until the next
// avcodec_decode_video() call.
- //
- // TODO(scherkus): use VideoFrame dimensions instead and re-allocate
- // VideoFrame if dimensions changes, but for now adjust size locally.
int y_rows = codec_context_->height;
int uv_rows = codec_context_->height;
if (codec_context_->pix_fmt == PIX_FMT_YUV420P) {
uv_rows /= 2;
}
- CopyYPlane(av_frame_->data[0], av_frame_->linesize[0], y_rows, video_frame);
- CopyUPlane(av_frame_->data[1], av_frame_->linesize[1], uv_rows, video_frame);
- CopyVPlane(av_frame_->data[2], av_frame_->linesize[2], uv_rows, video_frame);
+ CopyYPlane(av_frame_->data[0], av_frame_->linesize[0], y_rows, *video_frame);
+ CopyUPlane(av_frame_->data[1], av_frame_->linesize[1], uv_rows, *video_frame);
+ CopyVPlane(av_frame_->data[2], av_frame_->linesize[2], uv_rows, *video_frame);
- video_frame->SetTimestamp(timestamp);
- video_frame->SetDuration(duration);
-
- pending_output_buffers_--;
- event_handler_->ConsumeVideoFrame(video_frame, statistics);
-}
-
-void FFmpegVideoDecodeEngine::Uninitialize() {
- event_handler_->OnUninitializeComplete();
+ return true;
}
void FFmpegVideoDecodeEngine::Flush() {
avcodec_flush_buffers(codec_context_);
- flush_pending_ = true;
- TryToFinishPendingFlush();
}
-void FFmpegVideoDecodeEngine::TryToFinishPendingFlush() {
- DCHECK(flush_pending_);
+scoped_refptr<VideoFrame> FFmpegVideoDecodeEngine::AllocateVideoFrame() {
+ VideoFrame::Format format = PixelFormatToVideoFormat(codec_context_->pix_fmt);
+ size_t width = codec_context_->width;
+ size_t height = codec_context_->height;
- // We consider ourself flushed when there is no pending input buffers
- // and output buffers, which implies that all buffers had been returned
- // to its owner.
- if (!pending_input_buffers_ && !pending_output_buffers_) {
- // Try to finish flushing and notify pipeline.
- flush_pending_ = false;
- event_handler_->OnFlushComplete();
- }
-}
-
-void FFmpegVideoDecodeEngine::Seek() {
- // After a seek, output stream no longer considered as EOS.
- output_eos_reached_ = false;
-
- // The buffer provider is assumed to perform pre-roll operation.
- for (unsigned int i = 0; i < Limits::kMaxVideoFrames; ++i)
- ReadInput();
-
- event_handler_->OnSeekComplete();
-}
-
-void FFmpegVideoDecodeEngine::ReadInput() {
- DCHECK_EQ(output_eos_reached_, false);
- pending_input_buffers_++;
- event_handler_->ProduceVideoSample(NULL);
+ return VideoFrame::CreateFrame(format, width, height,
+ kNoTimestamp, kNoTimestamp);
}
} // namespace media
-
-// Disable refcounting for this object because this object only lives
-// on the video decoder thread and there's no need to refcount it.
-DISABLE_RUNNABLE_METHOD_REFCOUNT(media::FFmpegVideoDecodeEngine);
diff --git a/media/video/ffmpeg_video_decode_engine.h b/media/video/ffmpeg_video_decode_engine.h
index 3ac7411..072507a 100644
--- a/media/video/ffmpeg_video_decode_engine.h
+++ b/media/video/ffmpeg_video_decode_engine.h
@@ -5,13 +5,9 @@
#ifndef MEDIA_VIDEO_FFMPEG_VIDEO_DECODE_ENGINE_H_
#define MEDIA_VIDEO_FFMPEG_VIDEO_DECODE_ENGINE_H_
-#include <deque>
-
-#include "base/memory/scoped_ptr.h"
-#include "media/ffmpeg/ffmpeg_common.h"
+#include "base/compiler_specific.h"
#include "media/video/video_decode_engine.h"
-// FFmpeg types.
struct AVCodecContext;
struct AVFrame;
@@ -22,47 +18,26 @@ class MEDIA_EXPORT FFmpegVideoDecodeEngine : public VideoDecodeEngine {
FFmpegVideoDecodeEngine();
virtual ~FFmpegVideoDecodeEngine();
- // Implementation of the VideoDecodeEngine Interface.
- virtual void Initialize(VideoDecodeEngine::EventHandler* event_handler,
- const VideoDecoderConfig& config);
- virtual void ConsumeVideoSample(scoped_refptr<Buffer> buffer);
- virtual void ProduceVideoFrame(scoped_refptr<VideoFrame> frame);
- virtual void Uninitialize();
- virtual void Flush();
- virtual void Seek();
+ // VideoDecodeEngine implementation.
+ virtual bool Initialize(const VideoDecoderConfig& config) OVERRIDE;
+ virtual void Uninitialize() OVERRIDE;
+ virtual bool Decode(const scoped_refptr<Buffer>& buffer,
+ scoped_refptr<VideoFrame>* video_frame) OVERRIDE;
+ virtual void Flush() OVERRIDE;
private:
- void DecodeFrame(scoped_refptr<Buffer> buffer);
- void ReadInput();
- void TryToFinishPendingFlush();
+ // Allocates a video frame based on the current format and dimensions based on
+ // the current state of |codec_context_|.
+ scoped_refptr<VideoFrame> AllocateVideoFrame();
+ // FFmpeg structures owned by this object.
AVCodecContext* codec_context_;
- scoped_ptr_malloc<AVFrame, ScopedPtrAVFree> av_frame_;
- VideoDecodeEngine::EventHandler* event_handler_;
+ AVFrame* av_frame_;
// Frame rate of the video.
int frame_rate_numerator_;
int frame_rate_denominator_;
- // Indicate how many buffers are pending on input port of this filter:
- // Increment when engine receive one input packet from demuxer;
- // Decrement when engine send one input packet to demuxer;
- int pending_input_buffers_;
-
- // Indicate how many buffers are pending on output port of this filter:
- // Increment when engine receive one output frame from renderer;
- // Decrement when engine send one output frame to renderer;
- int pending_output_buffers_;
-
- // Whether end of stream had been reached at output side.
- bool output_eos_reached_;
-
- // Used when direct rendering is disabled to hold available output buffers.
- std::deque<scoped_refptr<VideoFrame> > frame_queue_available_;
-
- // Whether flush operation is pending.
- bool flush_pending_;
-
DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecodeEngine);
};
diff --git a/media/video/ffmpeg_video_decode_engine_unittest.cc b/media/video/ffmpeg_video_decode_engine_unittest.cc
deleted file mode 100644
index f2cf348..0000000
--- a/media/video/ffmpeg_video_decode_engine_unittest.cc
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright (c) 2011 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/memory/scoped_ptr.h"
-#include "base/message_loop.h"
-#include "media/base/data_buffer.h"
-#include "media/base/pipeline.h"
-#include "media/base/test_data_util.h"
-#include "media/filters/ffmpeg_glue.h"
-#include "media/video/ffmpeg_video_decode_engine.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::SaveArg;
-using ::testing::SetArgumentPointee;
-using ::testing::StrictMock;
-
-namespace media {
-
-static const VideoFrame::Format kVideoFormat = VideoFrame::YV12;
-static const gfx::Size kCodedSize(320, 240);
-static const gfx::Rect kVisibleRect(320, 240);
-static const gfx::Size kNaturalSize(522, 288);
-static const AVRational kFrameRate = { 100, 1 };
-static const AVRational kAspectRatio = { 1, 1 };
-
-ACTION_P2(DemuxComplete, engine, buffer) {
- engine->ConsumeVideoSample(buffer);
-}
-
-class FFmpegVideoDecodeEngineTest
- : public testing::Test,
- public VideoDecodeEngine::EventHandler {
- public:
- FFmpegVideoDecodeEngineTest()
- : config_(kCodecVP8, kVideoFormat, kCodedSize, kVisibleRect,
- kFrameRate.num, kFrameRate.den,
- kAspectRatio.num, kAspectRatio.den,
- NULL, 0) {
- CHECK(FFmpegGlue::GetInstance());
-
- // Setup FFmpeg structures.
- frame_buffer_.reset(new uint8[kCodedSize.GetArea()]);
-
- test_engine_.reset(new FFmpegVideoDecodeEngine());
-
- ReadTestDataFile("vp8-I-frame-320x240", &i_frame_buffer_);
- ReadTestDataFile("vp8-corrupt-I-frame", &corrupt_i_frame_buffer_);
-
- end_of_stream_buffer_ = new DataBuffer(0);
- }
-
- ~FFmpegVideoDecodeEngineTest() {
- test_engine_.reset();
- }
-
- void Initialize() {
- EXPECT_CALL(*this, OnInitializeComplete(true));
- test_engine_->Initialize(this, config_);
- }
-
- // Decodes the single compressed frame in |buffer| and writes the
- // uncompressed output to |video_frame|. This method works with single
- // and multithreaded decoders. End of stream buffers are used to trigger
- // the frame to be returned in the multithreaded decoder case.
- void DecodeASingleFrame(const scoped_refptr<Buffer>& buffer,
- scoped_refptr<VideoFrame>* video_frame) {
- EXPECT_CALL(*this, ProduceVideoSample(_))
- .WillOnce(DemuxComplete(test_engine_.get(), buffer))
- .WillRepeatedly(DemuxComplete(test_engine_.get(),
- end_of_stream_buffer_));
-
- EXPECT_CALL(*this, ConsumeVideoFrame(_, _))
- .WillOnce(SaveArg<0>(video_frame));
- CallProduceVideoFrame();
- }
-
- // Decodes |i_frame_buffer_| and then decodes the data contained in
- // the file named |test_file_name|. This function expects both buffers
- // to decode to frames that are the same size.
- void DecodeIFrameThenTestFile(const std::string& test_file_name) {
- Initialize();
-
- scoped_refptr<VideoFrame> video_frame_a;
- scoped_refptr<VideoFrame> video_frame_b;
-
- scoped_refptr<Buffer> buffer;
- ReadTestDataFile(test_file_name, &buffer);
-
- EXPECT_CALL(*this, ProduceVideoSample(_))
- .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_))
- .WillOnce(DemuxComplete(test_engine_.get(), buffer))
- .WillRepeatedly(DemuxComplete(test_engine_.get(),
- end_of_stream_buffer_));
-
- EXPECT_CALL(*this, ConsumeVideoFrame(_, _))
- .WillOnce(SaveArg<0>(&video_frame_a))
- .WillOnce(SaveArg<0>(&video_frame_b));
- CallProduceVideoFrame();
- CallProduceVideoFrame();
-
- size_t expected_width = static_cast<size_t>(kVisibleRect.width());
- size_t expected_height = static_cast<size_t>(kVisibleRect.height());
-
- EXPECT_EQ(expected_width, video_frame_a->width());
- EXPECT_EQ(expected_height, video_frame_a->height());
- EXPECT_EQ(expected_width, video_frame_b->width());
- EXPECT_EQ(expected_height, video_frame_b->height());
- }
-
- // VideoDecodeEngine::EventHandler implementation.
- MOCK_METHOD2(ConsumeVideoFrame,
- void(scoped_refptr<VideoFrame>, const PipelineStatistics&));
- MOCK_METHOD1(ProduceVideoSample, void(scoped_refptr<Buffer>));
- MOCK_METHOD1(OnInitializeComplete, void(bool));
- MOCK_METHOD0(OnUninitializeComplete, void());
- MOCK_METHOD0(OnFlushComplete, void());
- MOCK_METHOD0(OnSeekComplete, void());
- MOCK_METHOD0(OnError, void());
-
- void CallProduceVideoFrame() {
- test_engine_->ProduceVideoFrame(VideoFrame::CreateFrame(
- VideoFrame::YV12, kVisibleRect.width(), kVisibleRect.height(),
- kNoTimestamp, kNoTimestamp));
- }
-
- protected:
- VideoDecoderConfig config_;
- scoped_ptr<FFmpegVideoDecodeEngine> test_engine_;
- scoped_array<uint8_t> frame_buffer_;
- scoped_refptr<Buffer> i_frame_buffer_;
- scoped_refptr<Buffer> corrupt_i_frame_buffer_;
- scoped_refptr<Buffer> end_of_stream_buffer_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(FFmpegVideoDecodeEngineTest);
-};
-
-TEST_F(FFmpegVideoDecodeEngineTest, Initialize_Normal) {
- Initialize();
-}
-
-TEST_F(FFmpegVideoDecodeEngineTest, Initialize_FindDecoderFails) {
- VideoDecoderConfig config(kUnknownVideoCodec, kVideoFormat,
- kCodedSize, kVisibleRect,
- kFrameRate.num, kFrameRate.den,
- kAspectRatio.num, kAspectRatio.den,
- NULL, 0);
-
- // Test avcodec_find_decoder() returning NULL.
- EXPECT_CALL(*this, OnInitializeComplete(false));
- test_engine_->Initialize(this, config);
-}
-
-TEST_F(FFmpegVideoDecodeEngineTest, Initialize_OpenDecoderFails) {
- // Specify Theora w/o extra data so that avcodec_open() fails.
- VideoDecoderConfig config(kCodecTheora, kVideoFormat,
- kCodedSize, kVisibleRect,
- kFrameRate.num, kFrameRate.den,
- kAspectRatio.num, kAspectRatio.den,
- NULL, 0);
- EXPECT_CALL(*this, OnInitializeComplete(false));
- test_engine_->Initialize(this, config);
-}
-
-TEST_F(FFmpegVideoDecodeEngineTest, Initialize_UnsupportedPixelFormat) {
- // Ensure decoder handles unsupport pixel formats without crashing.
- VideoDecoderConfig config(kCodecVP8, VideoFrame::INVALID,
- kCodedSize, kVisibleRect,
- kFrameRate.num, kFrameRate.den,
- kAspectRatio.num, kAspectRatio.den,
- NULL, 0);
- EXPECT_CALL(*this, OnInitializeComplete(false));
- test_engine_->Initialize(this, config);
-}
-
-TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_Normal) {
- Initialize();
-
- // Simulate decoding a single frame.
- scoped_refptr<VideoFrame> video_frame;
- DecodeASingleFrame(i_frame_buffer_, &video_frame);
-
- // |video_frame| timestamp is 0 because we set the timestamp based off
- // the buffer timestamp.
- ASSERT_TRUE(video_frame);
- EXPECT_EQ(0, video_frame->GetTimestamp().ToInternalValue());
- EXPECT_EQ(10000, video_frame->GetDuration().ToInternalValue());
-}
-
-
-// Verify current behavior for 0 byte frames. FFmpeg simply ignores
-// the 0 byte frames.
-TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_0ByteFrame) {
- Initialize();
-
- scoped_refptr<DataBuffer> zero_byte_buffer = new DataBuffer(1);
-
- scoped_refptr<VideoFrame> video_frame_a;
- scoped_refptr<VideoFrame> video_frame_b;
- scoped_refptr<VideoFrame> video_frame_c;
-
- EXPECT_CALL(*this, ProduceVideoSample(_))
- .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_))
- .WillOnce(DemuxComplete(test_engine_.get(), zero_byte_buffer))
- .WillOnce(DemuxComplete(test_engine_.get(), i_frame_buffer_))
- .WillRepeatedly(DemuxComplete(test_engine_.get(),
- end_of_stream_buffer_));
-
- EXPECT_CALL(*this, ConsumeVideoFrame(_, _))
- .WillOnce(SaveArg<0>(&video_frame_a))
- .WillOnce(SaveArg<0>(&video_frame_b))
- .WillOnce(SaveArg<0>(&video_frame_c));
- CallProduceVideoFrame();
- CallProduceVideoFrame();
- CallProduceVideoFrame();
-
- EXPECT_TRUE(video_frame_a);
- EXPECT_TRUE(video_frame_b);
- EXPECT_FALSE(video_frame_c);
-}
-
-
-TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeError) {
- Initialize();
-
- EXPECT_CALL(*this, ProduceVideoSample(_))
- .WillOnce(DemuxComplete(test_engine_.get(), corrupt_i_frame_buffer_))
- .WillRepeatedly(DemuxComplete(test_engine_.get(), i_frame_buffer_));
- EXPECT_CALL(*this, OnError());
-
- CallProduceVideoFrame();
-}
-
-// Multi-threaded decoders have different behavior than single-threaded
-// decoders at the end of the stream. Multithreaded decoders hide errors
-// that happen on the last |codec_context_->thread_count| frames to avoid
-// prematurely signalling EOS. This test just exposes that behavior so we can
-// detect if it changes.
-TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_DecodeErrorAtEndOfStream) {
- Initialize();
-
- EXPECT_CALL(*this, ProduceVideoSample(_))
- .WillOnce(DemuxComplete(test_engine_.get(), corrupt_i_frame_buffer_))
- .WillRepeatedly(DemuxComplete(test_engine_.get(), end_of_stream_buffer_));
-
- scoped_refptr<VideoFrame> video_frame;
- EXPECT_CALL(*this, ConsumeVideoFrame(_, _))
- .WillOnce(SaveArg<0>(&video_frame));
- CallProduceVideoFrame();
-
- EXPECT_FALSE(video_frame);
-}
-
-// Decode |i_frame_buffer_| and then a frame with a larger width and verify
-// the output size didn't change.
-// TODO(acolwell): Fix InvalidRead detected by Valgrind
-//TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerWidth) {
-// DecodeIFrameThenTestFile("vp8-I-frame-640x240");
-//}
-
-// Decode |i_frame_buffer_| and then a frame with a smaller width and verify
-// the output size didn't change.
-TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerWidth) {
- DecodeIFrameThenTestFile("vp8-I-frame-160x240");
-}
-
-// Decode |i_frame_buffer_| and then a frame with a larger height and verify
-// the output size didn't change.
-// TODO(acolwell): Fix InvalidRead detected by Valgrind
-//TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_LargerHeight) {
-// DecodeIFrameThenTestFile("vp8-I-frame-320x480");
-//}
-
-// Decode |i_frame_buffer_| and then a frame with a smaller height and verify
-// the output size didn't change.
-TEST_F(FFmpegVideoDecodeEngineTest, DecodeFrame_SmallerHeight) {
- DecodeIFrameThenTestFile("vp8-I-frame-320x120");
-}
-
-} // namespace media
diff --git a/media/video/video_decode_engine.h b/media/video/video_decode_engine.h
index 2d52b24..4599331 100644
--- a/media/video/video_decode_engine.h
+++ b/media/video/video_decode_engine.h
@@ -5,101 +5,39 @@
#ifndef MEDIA_VIDEO_VIDEO_DECODE_ENGINE_H_
#define MEDIA_VIDEO_VIDEO_DECODE_ENGINE_H_
-#include "base/callback.h"
-#include "base/memory/scoped_ptr.h"
+#include "base/memory/ref_counted.h"
#include "media/base/media_export.h"
-#include "media/base/video_decoder_config.h"
-#include "media/base/video_frame.h"
namespace media {
class Buffer;
-struct PipelineStatistics;
+class VideoDecoderConfig;
+class VideoFrame;
class MEDIA_EXPORT VideoDecodeEngine {
public:
- struct MEDIA_EXPORT EventHandler {
- public:
- virtual ~EventHandler() {}
- virtual void OnInitializeComplete(bool success) = 0;
- virtual void OnUninitializeComplete() = 0;
- virtual void OnFlushComplete() = 0;
- virtual void OnSeekComplete() = 0;
- virtual void OnError() = 0;
-
- // TODO(hclam): The following two methods shouldn't belong to this class
- // because they are not video decode events but used to send decoded
- // video frames and request video packets.
- //
- // Signal the user of VideoDecodeEngine to provide a video sample.
- //
- // In the normal running state, this method is called by the video decode
- // engine to request video samples used for decoding.
- //
- // In the case when the video decode engine is flushing, this method is
- // called to return video samples acquired by the video decode engine.
- //
- // |buffer| can be NULL in which case this method call is purely for
- // requesting new video samples. If |buffer| is non-NULL, the buffer is
- // returned to the owner at the same time as a request for video sample
- // is made.
- virtual void ProduceVideoSample(scoped_refptr<Buffer> buffer) = 0;
-
- // Signal the user of VideoDecodeEngine that a video frame is ready to
- // be consumed or a video frame is returned to the owner.
- //
- // In the normal running state, this method is called to signal that
- // |frame| contains a decoded video frame and is ready to be used.
- //
- // In the case of flushing and video frame is provided externally, this
- // method is called to return the video frame object to the owner.
- // The content of the video frame may be invalid.
- virtual void ConsumeVideoFrame(scoped_refptr<VideoFrame> frame,
- const PipelineStatistics& statistics) = 0;
- };
-
virtual ~VideoDecodeEngine() {}
- // Initialize the engine with specified configuration.
- //
- // Engine should call EventHandler::OnInitializeDone() whether the
- // initialization operation finished successfully or not.
- virtual void Initialize(EventHandler* event_handler,
- const VideoDecoderConfig& config) = 0;
+ // Initialize the engine with specified configuration, returning true if
+ // successful.
+ virtual bool Initialize(const VideoDecoderConfig& config) = 0;
- // Uninitialize the engine. Engine should destroy all resources and call
- // EventHandler::OnUninitializeComplete().
+ // Uninitialize the engine, freeing all resources. Calls to Flush() or
+ // Decode() will have no effect afterwards.
virtual void Uninitialize() = 0;
- // Flush the engine. Engine should return all the buffers to owner ( which
- // could be itself. ) then call EventHandler::OnFlushDone().
- virtual void Flush() = 0;
-
- // This method is used as a signal for the decode engine to preroll and
- // issue read requests after Flush() is made.
- virtual void Seek() = 0;
-
- // Provide a video sample to be used by the video decode engine.
+ // Decode the encoded video data and store the result (if any) into
+ // |video_frame|. Note that a frame may not always be produced if the
+ // decode engine has insufficient encoded data. In such circumstances,
+ // additional calls to Decode() may be required.
//
- // This method is called in response to ProvideVideoSample() called to the
- // user.
- virtual void ConsumeVideoSample(scoped_refptr<Buffer> buffer) = 0;
+ // Returns true if decoding was successful (includes zero length input and end
+ // of stream), false if a decoding error occurred.
+ virtual bool Decode(const scoped_refptr<Buffer>& buffer,
+ scoped_refptr<VideoFrame>* video_frame) = 0;
- // Signal the video decode engine to produce a video frame or return the
- // video frame object to the video decode engine.
- //
- // In the normal running state, this method is called by the user of the
- // video decode engine to request a decoded video frame. If |frame| is
- // NULL the video decode engine should allocate a video frame object.
- // Otherwise video decode engine should try to use the video frame object
- // provided as output.
- //
- // In flushing state and video frames are allocated internally this method
- // is called by the user to return the video frame object.
- //
- // In response to this method call, ConsumeVideoFrame() is called with a
- // video frame object containing decoded video content.
- virtual void ProduceVideoFrame(scoped_refptr<VideoFrame> frame) = 0;
+ // Discard all pending data that has yet to be returned via Decode().
+ virtual void Flush() = 0;
};
} // namespace media