Index: media/filters/ffmpeg_video_decoder.cc |
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc |
index deb0bf375df950b221ca36766ca7ef817fbfd4a3..a54f95177b82d3dec554d1aa1e3002d20ccb0cd5 100644 |
--- a/media/filters/ffmpeg_video_decoder.cc |
+++ b/media/filters/ffmpeg_video_decoder.cc |
@@ -5,12 +5,11 @@ |
#include "media/filters/ffmpeg_video_decoder.h" |
#include "base/bind.h" |
-#include "base/callback.h" |
#include "base/message_loop.h" |
-#include "base/task.h" |
#include "media/base/demuxer_stream.h" |
#include "media/base/filter_host.h" |
#include "media/base/limits.h" |
+#include "media/base/video_decoder_config.h" |
#include "media/base/video_frame.h" |
#include "media/ffmpeg/ffmpeg_common.h" |
#include "media/video/ffmpeg_video_decode_engine.h" |
@@ -19,7 +18,7 @@ namespace media { |
FFmpegVideoDecoder::FFmpegVideoDecoder(MessageLoop* message_loop) |
: message_loop_(message_loop), |
- state_(kUnInitialized), |
+ state_(kUninitialized), |
decode_engine_(new FFmpegVideoDecodeEngine()) { |
} |
@@ -29,17 +28,14 @@ void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, |
const base::Closure& callback, |
const StatisticsCallback& stats_callback) { |
if (MessageLoop::current() != message_loop_) { |
- message_loop_->PostTask( |
- FROM_HERE, |
- base::Bind(&FFmpegVideoDecoder::Initialize, this, |
- make_scoped_refptr(demuxer_stream), |
- callback, stats_callback)); |
+ message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &FFmpegVideoDecoder::Initialize, this, |
+ make_scoped_refptr(demuxer_stream), callback, stats_callback)); |
return; |
} |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
DCHECK(!demuxer_stream_); |
- DCHECK(initialize_callback_.is_null()); |
if (!demuxer_stream) { |
host()->SetError(PIPELINE_ERROR_DECODE); |
@@ -48,7 +44,6 @@ void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, |
} |
demuxer_stream_ = demuxer_stream; |
- initialize_callback_ = callback; |
statistics_callback_ = stats_callback; |
const VideoDecoderConfig& config = demuxer_stream->video_decoder_config(); |
@@ -59,24 +54,19 @@ void FFmpegVideoDecoder::Initialize(DemuxerStream* demuxer_stream, |
if (natural_size_.width() > Limits::kMaxDimension || |
natural_size_.height() > Limits::kMaxDimension || |
natural_size_.GetArea() > Limits::kMaxCanvas) { |
- OnInitializeComplete(false); |
+ host()->SetError(PIPELINE_ERROR_DECODE); |
+ callback.Run(); |
return; |
} |
- state_ = kInitializing; |
- decode_engine_->Initialize(this, config); |
-} |
- |
-void FFmpegVideoDecoder::OnInitializeComplete(bool success) { |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(!initialize_callback_.is_null()); |
- |
- if (success) { |
- state_ = kNormal; |
- } else { |
+ if (!decode_engine_->Initialize(config)) { |
host()->SetError(PIPELINE_ERROR_DECODE); |
+ callback.Run(); |
+ return; |
} |
- ResetAndRunCB(&initialize_callback_); |
+ |
+ state_ = kNormal; |
+ callback.Run(); |
} |
void FFmpegVideoDecoder::Stop(const base::Closure& callback) { |
@@ -87,22 +77,23 @@ void FFmpegVideoDecoder::Stop(const base::Closure& callback) { |
} |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(uninitialize_callback_.is_null()); |
- uninitialize_callback_ = callback; |
- if (state_ != kUnInitialized) |
- decode_engine_->Uninitialize(); |
- else |
- OnUninitializeComplete(); |
+ decode_engine_->Uninitialize(); |
+ state_ = kUninitialized; |
+ callback.Run(); |
} |
-void FFmpegVideoDecoder::OnUninitializeComplete() { |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(!uninitialize_callback_.is_null()); |
+void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) { |
+ if (MessageLoop::current() != message_loop_) { |
+ message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &FFmpegVideoDecoder::Seek, this, time, cb)); |
+ return; |
+ } |
- state_ = kStopped; |
+ DCHECK_EQ(MessageLoop::current(), message_loop_); |
- ResetAndRunCB(&uninitialize_callback_); |
+ pts_stream_.Seek(time); |
+ cb.Run(PIPELINE_OK); |
} |
void FFmpegVideoDecoder::Pause(const base::Closure& callback) { |
@@ -112,7 +103,6 @@ void FFmpegVideoDecoder::Pause(const base::Closure& callback) { |
return; |
} |
- state_ = kPausing; |
callback.Run(); |
} |
@@ -124,66 +114,18 @@ void FFmpegVideoDecoder::Flush(const base::Closure& callback) { |
} |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(flush_callback_.is_null()); |
- |
- state_ = kFlushing; |
- |
- FlushBuffers(); |
- |
- flush_callback_ = callback; |
decode_engine_->Flush(); |
-} |
- |
-void FFmpegVideoDecoder::OnFlushComplete() { |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(!flush_callback_.is_null()); |
- |
- // Everything in the presentation time queue is invalid, clear the queue. |
pts_stream_.Flush(); |
- |
- // Mark flush operation had been done. |
state_ = kNormal; |
- |
- ResetAndRunCB(&flush_callback_); |
-} |
- |
-void FFmpegVideoDecoder::Seek(base::TimeDelta time, const FilterStatusCB& cb) { |
- if (MessageLoop::current() != message_loop_) { |
- message_loop_->PostTask(FROM_HERE, |
- base::Bind(&FFmpegVideoDecoder::Seek, this, |
- time, cb)); |
- return; |
- } |
- |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(seek_cb_.is_null()); |
- |
- pts_stream_.Seek(time); |
- seek_cb_ = cb; |
- decode_engine_->Seek(); |
-} |
- |
-void FFmpegVideoDecoder::OnSeekComplete() { |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK(!seek_cb_.is_null()); |
- |
- ResetAndRunCB(&seek_cb_, PIPELINE_OK); |
-} |
- |
-void FFmpegVideoDecoder::OnError() { |
- VideoFrameReady(NULL); |
-} |
- |
-void FFmpegVideoDecoder::OnReadComplete(Buffer* buffer_in) { |
- scoped_refptr<Buffer> buffer(buffer_in); |
- message_loop_->PostTask(FROM_HERE, base::Bind( |
- &FFmpegVideoDecoder::OnReadCompleteTask, this, buffer)); |
+ callback.Run(); |
} |
-void FFmpegVideoDecoder::OnReadCompleteTask(scoped_refptr<Buffer> buffer) { |
+void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) { |
Ami GONE FROM CHROMIUM
2011/11/01 22:17:40
Why have this be a separate function? It makes De
scherkus (not reviewing)
2011/11/03 04:55:59
Done.
|
DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK_NE(state_, kStopped); // because of Flush() before Stop(). |
+ DCHECK_NE(state_, kUninitialized); |
+ DCHECK_NE(state_, kDecodeFinished); |
+ DCHECK(!frame_ready_cb_.is_null()); |
// During decode, because reads are issued asynchronously, it is possible to |
// receive multiple end of stream buffers since each read is acked. When the |
@@ -208,7 +150,7 @@ void FFmpegVideoDecoder::OnReadCompleteTask(scoped_refptr<Buffer> buffer) { |
// kFlushCodec -> kDecodeFinished: |
// When avcodec_decode_video2() returns 0 data or errors out. |
// (any state) -> kNormal: |
- // Any time buffer->IsDiscontinuous() is true. |
+ // Any time Flush() is called. |
Ami GONE FROM CHROMIUM
2011/11/01 22:17:40
I'm surprised a Flush() can recover from a "catast
scherkus (not reviewing)
2011/11/03 04:55:59
Done.
|
// Transition to kFlushCodec on the first end of stream buffer. |
if (state_ == kNormal && buffer->IsEndOfStream()) { |
@@ -222,99 +164,95 @@ void FFmpegVideoDecoder::OnReadCompleteTask(scoped_refptr<Buffer> buffer) { |
pts_stream_.EnqueuePts(buffer.get()); |
} |
- // Otherwise, attempt to decode a single frame. |
- decode_engine_->ConsumeVideoSample(buffer); |
-} |
- |
-void FFmpegVideoDecoder::ProduceVideoFrame( |
- scoped_refptr<VideoFrame> video_frame) { |
- if (MessageLoop::current() != message_loop_) { |
- if (state_ != kStopped) { |
- message_loop_->PostTask(FROM_HERE, base::Bind( |
- &FFmpegVideoDecoder::ProduceVideoFrame, this, video_frame)); |
- } |
+ scoped_refptr<VideoFrame> video_frame; |
+ if (!decode_engine_->Decode(buffer, &video_frame)) { |
+ state_ = kDecodeFinished; |
+ host()->SetError(PIPELINE_ERROR_DECODE); |
return; |
} |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- |
- // Synchronized flushing before stop should prevent this. |
- DCHECK_NE(state_, kStopped); |
- |
- // If the decoding is finished, we just always return empty frames. |
- if (state_ == kDecodeFinished) { |
- // Signal VideoRenderer the end of the stream event. |
- VideoFrameReady(VideoFrame::CreateEmptyFrame()); |
- |
- // Fall through, because we still need to keep record of this frame. |
+ // Any successful decode counts! |
+ if (buffer->GetDataSize()) { |
+ PipelineStatistics statistics; |
+ statistics.video_bytes_decoded = buffer->GetDataSize(); |
+ statistics_callback_.Run(statistics); |
} |
- // Notify decode engine the available of new frame. |
- decode_engine_->ProduceVideoFrame(video_frame); |
-} |
- |
-void FFmpegVideoDecoder::ConsumeVideoFrame( |
- scoped_refptr<VideoFrame> video_frame, |
- const PipelineStatistics& statistics) { |
- DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK_NE(state_, kStopped); |
- |
- statistics_callback_.Run(statistics); |
- |
- if (video_frame.get()) { |
- if (kPausing == state_ || kFlushing == state_) { |
- frame_queue_flushed_.push_back(video_frame); |
- if (kFlushing == state_) |
- FlushBuffers(); |
+ // If we didn't get a frame then we've either completely finished decoding or |
+ // we need more data. |
+ if (!video_frame) { |
+ if (state_ == kFlushCodec) { |
+ state_ = kDecodeFinished; |
+ DeliverFrame(VideoFrame::CreateEmptyFrame()); |
return; |
} |
- // If we actually got data back, enqueue a frame. |
- pts_stream_.UpdatePtsAndDuration(video_frame.get()); |
+ ReadFromDemuxerStream(); |
+ return; |
+ } |
- video_frame->SetTimestamp(pts_stream_.current_pts()); |
- video_frame->SetDuration(pts_stream_.current_duration()); |
+ // If we got a frame make sure its timestamp is correct before sending it off. |
+ pts_stream_.UpdatePtsAndDuration(video_frame.get()); |
+ video_frame->SetTimestamp(pts_stream_.current_pts()); |
+ video_frame->SetDuration(pts_stream_.current_duration()); |
- VideoFrameReady(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; |
+ DeliverFrame(video_frame); |
+} |
- // Signal VideoRenderer the end of the stream event. |
- VideoFrameReady(VideoFrame::CreateEmptyFrame()); |
- } |
+void FFmpegVideoDecoder::Read(const FrameReadyCB& callback) { |
+ if (MessageLoop::current() != message_loop_) { |
+ message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &FFmpegVideoDecoder::Read, this, callback)); |
+ return; |
} |
-} |
-void FFmpegVideoDecoder::ProduceVideoSample( |
- scoped_refptr<Buffer> buffer) { |
DCHECK_EQ(MessageLoop::current(), message_loop_); |
- DCHECK_NE(state_, kStopped); |
+ CHECK(!callback.is_null()); |
Ami GONE FROM CHROMIUM
2011/11/01 22:17:40
Here and below s/CHECK/DCHECK/?
scherkus (not reviewing)
2011/11/03 04:55:59
maybe... I feel like this is programmer error and
|
+ CHECK(frame_ready_cb_.is_null()) << "Overlapping decodes are not supported."; |
+ |
+ // This can happen during shutdown after Stop() has been called. |
+ if (state_ == kUninitialized) { |
+ // XXXXXXX |
+ // just drop the callback? do we want to honour every callback no matter |
+ // what? |
scherkus (not reviewing)
2011/11/01 04:18:26
FYI
acolwell GONE FROM CHROMIUM
2011/11/01 18:31:03
Will the caller ever block progress if it doesn't
Ami GONE FROM CHROMIUM
2011/11/01 22:17:40
I say drop the callback.
scherkus (not reviewing)
2011/11/03 04:55:59
Done.
|
+ return; |
+ } |
+ |
+ // Return empty frames if decoding has finished. |
+ if (state_ == kDecodeFinished) { |
+ callback.Run(VideoFrame::CreateEmptyFrame()); |
+ return; |
+ } |
- demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::OnReadComplete, |
- this)); |
+ frame_ready_cb_ = callback; |
+ ReadFromDemuxerStream(); |
} |
gfx::Size FFmpegVideoDecoder::natural_size() { |
return natural_size_; |
} |
-void FFmpegVideoDecoder::FlushBuffers() { |
- while (!frame_queue_flushed_.empty()) { |
- scoped_refptr<VideoFrame> video_frame; |
- video_frame = frame_queue_flushed_.front(); |
- frame_queue_flushed_.pop_front(); |
+void FFmpegVideoDecoder::ReadFromDemuxerStream() { |
+ DCHECK_NE(state_, kUninitialized); |
+ DCHECK_NE(state_, kDecodeFinished); |
+ DCHECK(!frame_ready_cb_.is_null()); |
- // Return frames back to the decode engine. |
- decode_engine_->ProduceVideoFrame(video_frame); |
- } |
+ demuxer_stream_->Read(base::Bind(&FFmpegVideoDecoder::DecodeBuffer, this)); |
+} |
+ |
+void FFmpegVideoDecoder::DecodeBuffer(Buffer* buffer) { |
+ // TODO(scherkus): change DemuxerStream::Read() to use scoped_refptr<> for |
acolwell GONE FROM CHROMIUM
2011/11/01 18:31:03
This TODO should be in demuxer_stream.h
scherkus (not reviewing)
2011/11/03 04:55:59
TODO was fixed in r108178
|
+ // callback. |
+ message_loop_->PostTask(FROM_HERE, base::Bind( |
+ &FFmpegVideoDecoder::DoDecodeBuffer, this, make_scoped_refptr(buffer))); |
} |
-void FFmpegVideoDecoder::SetVideoDecodeEngineForTest( |
- VideoDecodeEngine* engine) { |
- decode_engine_.reset(engine); |
+void FFmpegVideoDecoder::DeliverFrame( |
+ const scoped_refptr<VideoFrame>& video_frame) { |
+ // Reset the callback before running to protect against reentrancy. |
+ FrameReadyCB frame_ready_cb = frame_ready_cb_; |
+ frame_ready_cb_.Reset(); |
+ frame_ready_cb.Run(video_frame); |
} |
} // namespace media |