diff options
author | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-04 02:04:09 +0000 |
---|---|---|
committer | scherkus@chromium.org <scherkus@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-04 02:04:09 +0000 |
commit | f23676abfed3df60d1be4455d422acdb70facf1c (patch) | |
tree | 7f4a926b34fe708f1dbf00407eca0f253d1c6ef0 /media/video | |
parent | 760afa9837be0fb92b2994360743ccac35fe7384 (diff) | |
download | chromium_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.cc | 211 | ||||
-rw-r--r-- | media/video/ffmpeg_video_decode_engine.h | 49 | ||||
-rw-r--r-- | media/video/ffmpeg_video_decode_engine_unittest.cc | 286 | ||||
-rw-r--r-- | media/video/video_decode_engine.h | 98 |
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 |