| Index: media/filters/ffmpeg_video_decoder.cc
|
| diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
|
| index e77ae665bc34e49eaa20262eaede444c66c7dbfc..4eabba9af6388848473a7b284b28bd83b2698eba 100644
|
| --- a/media/filters/ffmpeg_video_decoder.cc
|
| +++ b/media/filters/ffmpeg_video_decoder.cc
|
| @@ -151,10 +151,11 @@ int FFmpegVideoDecoder::GetVideoBuffer(struct AVCodecContext* codec_context,
|
|
|
| void FFmpegVideoDecoder::Initialize(const VideoDecoderConfig& config,
|
| bool low_delay,
|
| - const PipelineStatusCB& status_cb) {
|
| + const PipelineStatusCB& status_cb,
|
| + const OutputCB& output_cb) {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| - DCHECK(decode_cb_.is_null());
|
| DCHECK(!config.is_encrypted());
|
| + DCHECK(!output_cb.is_null());
|
|
|
| FFmpegGlue::InitializeFFmpeg();
|
|
|
| @@ -166,6 +167,8 @@ void FFmpegVideoDecoder::Initialize(const VideoDecoderConfig& config,
|
| return;
|
| }
|
|
|
| + output_cb_ = BindToCurrentLoop(output_cb);
|
| +
|
| // Success!
|
| state_ = kNormal;
|
| initialize_cb.Run(PIPELINE_OK);
|
| @@ -174,58 +177,24 @@ void FFmpegVideoDecoder::Initialize(const VideoDecoderConfig& config,
|
| void FFmpegVideoDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
|
| const DecodeCB& decode_cb) {
|
| DCHECK(task_runner_->BelongsToCurrentThread());
|
| + DCHECK(buffer);
|
| DCHECK(!decode_cb.is_null());
|
| CHECK_NE(state_, kUninitialized);
|
| - CHECK(decode_cb_.is_null()) << "Overlapping decodes are not supported.";
|
| - decode_cb_ = BindToCurrentLoop(decode_cb);
|
| +
|
| + DecodeCB decode_cb_bound = BindToCurrentLoop(decode_cb);
|
|
|
| if (state_ == kError) {
|
| - base::ResetAndReturn(&decode_cb_).Run(kDecodeError, NULL);
|
| + decode_cb_bound.Run(kDecodeError);
|
| return;
|
| }
|
|
|
| - // Return empty frames if decoding has finished.
|
| if (state_ == kDecodeFinished) {
|
| - base::ResetAndReturn(&decode_cb_).Run(kOk, VideoFrame::CreateEOSFrame());
|
| + output_cb_.Run(VideoFrame::CreateEOSFrame());
|
| + decode_cb_bound.Run(kOk);
|
| return;
|
| }
|
|
|
| - DecodeBuffer(buffer);
|
| -}
|
| -
|
| -void FFmpegVideoDecoder::Reset(const base::Closure& closure) {
|
| - DCHECK(task_runner_->BelongsToCurrentThread());
|
| - DCHECK(decode_cb_.is_null());
|
| -
|
| - avcodec_flush_buffers(codec_context_.get());
|
| - state_ = kNormal;
|
| - task_runner_->PostTask(FROM_HERE, closure);
|
| -}
|
| -
|
| -void FFmpegVideoDecoder::Stop() {
|
| - DCHECK(task_runner_->BelongsToCurrentThread());
|
| -
|
| - if (state_ == kUninitialized)
|
| - return;
|
| -
|
| - ReleaseFFmpegResources();
|
| - state_ = kUninitialized;
|
| -}
|
| -
|
| -FFmpegVideoDecoder::~FFmpegVideoDecoder() {
|
| - DCHECK_EQ(kUninitialized, state_);
|
| - DCHECK(!codec_context_);
|
| - DCHECK(!av_frame_);
|
| -}
|
| -
|
| -void FFmpegVideoDecoder::DecodeBuffer(
|
| - const scoped_refptr<DecoderBuffer>& buffer) {
|
| - DCHECK(task_runner_->BelongsToCurrentThread());
|
| - DCHECK_NE(state_, kUninitialized);
|
| - DCHECK_NE(state_, kDecodeFinished);
|
| - DCHECK_NE(state_, kError);
|
| - DCHECK(!decode_cb_.is_null());
|
| - DCHECK(buffer);
|
| + DCHECK_EQ(state_, kNormal);
|
|
|
| // During decode, because reads are issued asynchronously, it is possible to
|
| // receive multiple end of stream buffers since each decode is acked. When the
|
| @@ -236,57 +205,65 @@ void FFmpegVideoDecoder::DecodeBuffer(
|
| //
|
| // 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.
|
| // kError: Unexpected error happened.
|
| //
|
| // These are the possible state transitions.
|
| //
|
| - // kNormal -> kFlushCodec:
|
| - // When buffer->end_of_stream() is first true.
|
| + // kNormal -> kDecodeFinished:
|
| + // When EOS buffer is received and the codec has been flushed.
|
| // kNormal -> kError:
|
| // A decoding error occurs and decoding needs to stop.
|
| - // kFlushCodec -> kDecodeFinished:
|
| - // When avcodec_decode_video2() returns 0 data.
|
| - // kFlushCodec -> kError:
|
| - // When avcodec_decode_video2() errors out.
|
| // (any state) -> kNormal:
|
| // Any time Reset() is called.
|
|
|
| - // Transition to kFlushCodec on the first end of stream buffer.
|
| - if (state_ == kNormal && buffer->end_of_stream()) {
|
| - state_ = kFlushCodec;
|
| - }
|
| + bool has_produced_frame;
|
| + do {
|
| + has_produced_frame = false;
|
| + if (!FFmpegDecode(buffer, &has_produced_frame)) {
|
| + state_ = kError;
|
| + decode_cb_bound.Run(kDecodeError);
|
| + return;
|
| + }
|
| + // Repeat to flush the decoder after receiving EOS buffer.
|
| + } while (buffer->end_of_stream() && has_produced_frame);
|
|
|
| - scoped_refptr<VideoFrame> video_frame;
|
| - if (!FFmpegDecode(buffer, &video_frame)) {
|
| - state_ = kError;
|
| - base::ResetAndReturn(&decode_cb_).Run(kDecodeError, NULL);
|
| - return;
|
| + if (buffer->end_of_stream()) {
|
| + output_cb_.Run(VideoFrame::CreateEOSFrame());
|
| + state_ = kDecodeFinished;
|
| }
|
|
|
| - if (!video_frame.get()) {
|
| - if (state_ == kFlushCodec) {
|
| - DCHECK(buffer->end_of_stream());
|
| - state_ = kDecodeFinished;
|
| - base::ResetAndReturn(&decode_cb_)
|
| - .Run(kOk, VideoFrame::CreateEOSFrame());
|
| - return;
|
| - }
|
| + decode_cb_bound.Run(kOk);
|
| +}
|
| +
|
| +void FFmpegVideoDecoder::Reset(const base::Closure& closure) {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
|
|
| - base::ResetAndReturn(&decode_cb_).Run(kNotEnoughData, NULL);
|
| + avcodec_flush_buffers(codec_context_.get());
|
| + state_ = kNormal;
|
| + task_runner_->PostTask(FROM_HERE, closure);
|
| +}
|
| +
|
| +void FFmpegVideoDecoder::Stop() {
|
| + DCHECK(task_runner_->BelongsToCurrentThread());
|
| +
|
| + if (state_ == kUninitialized)
|
| return;
|
| - }
|
|
|
| - base::ResetAndReturn(&decode_cb_).Run(kOk, video_frame);
|
| + ReleaseFFmpegResources();
|
| + state_ = kUninitialized;
|
| +}
|
| +
|
| +FFmpegVideoDecoder::~FFmpegVideoDecoder() {
|
| + DCHECK_EQ(kUninitialized, state_);
|
| + DCHECK(!codec_context_);
|
| + DCHECK(!av_frame_);
|
| }
|
|
|
| bool FFmpegVideoDecoder::FFmpegDecode(
|
| const scoped_refptr<DecoderBuffer>& buffer,
|
| - scoped_refptr<VideoFrame>* video_frame) {
|
| - DCHECK(video_frame);
|
| + bool* has_produced_frame) {
|
| + DCHECK(!*has_produced_frame);
|
|
|
| // Create a packet for input data.
|
| // Due to FFmpeg API changes we no longer have const read-only pointers.
|
| @@ -311,7 +288,6 @@ bool FFmpegVideoDecoder::FFmpegDecode(
|
| // Log the problem if we can't decode a video frame and exit early.
|
| if (result < 0) {
|
| LOG(ERROR) << "Error decoding video: " << buffer->AsHumanReadableString();
|
| - *video_frame = NULL;
|
| return false;
|
| }
|
|
|
| @@ -325,7 +301,6 @@ bool FFmpegVideoDecoder::FFmpegDecode(
|
| // 1) Decoder was recently initialized/flushed
|
| // 2) End of stream was reached and all internal frames have been output
|
| if (frame_decoded == 0) {
|
| - *video_frame = NULL;
|
| return true;
|
| }
|
|
|
| @@ -336,16 +311,16 @@ bool FFmpegVideoDecoder::FFmpegDecode(
|
| !av_frame_->data[VideoFrame::kUPlane] ||
|
| !av_frame_->data[VideoFrame::kVPlane]) {
|
| LOG(ERROR) << "Video frame was produced yet has invalid frame data.";
|
| - *video_frame = NULL;
|
| av_frame_unref(av_frame_.get());
|
| return false;
|
| }
|
|
|
| - *video_frame =
|
| + scoped_refptr<VideoFrame> frame =
|
| reinterpret_cast<VideoFrame*>(av_buffer_get_opaque(av_frame_->buf[0]));
|
| -
|
| - (*video_frame)->set_timestamp(
|
| + frame->set_timestamp(
|
| base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque));
|
| + *has_produced_frame = true;
|
| + output_cb_.Run(frame);
|
|
|
| av_frame_unref(av_frame_.get());
|
| return true;
|
|
|