Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(321)

Unified Diff: media/filters/ffmpeg_video_decoder.cc

Issue 2854002: merge video_decoder_impl.cc and ffmpeg_video_decoder.cc (Closed)
Patch Set: remove comments Created 10 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/filters/ffmpeg_video_decoder.h ('k') | media/filters/ffmpeg_video_decoder_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/filters/ffmpeg_video_decoder.cc
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 0ccbd489ba720f81516146b0a58c23cdf32fff75..592f260485cf32d521b79c16fb20fd9857075360 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*>(
« no previous file with comments | « media/filters/ffmpeg_video_decoder.h ('k') | media/filters/ffmpeg_video_decoder_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698