Index: media/filters/ffmpeg_video_decode_engine.cc |
diff --git a/media/filters/ffmpeg_video_decode_engine.cc b/media/filters/ffmpeg_video_decode_engine.cc |
index 8685fbfa6a6908916fd21fcbaf39d8f190a42d5c..d13a247b19eb1e6338ece6901890dc93529ee14d 100644 |
--- a/media/filters/ffmpeg_video_decode_engine.cc |
+++ b/media/filters/ffmpeg_video_decode_engine.cc |
@@ -5,8 +5,9 @@ |
#include "media/filters/ffmpeg_video_decode_engine.h" |
#include "base/task.h" |
-#include "media/base/callback.h" |
#include "media/base/buffers.h" |
+#include "media/base/callback.h" |
+#include "media/base/limits.h" |
#include "media/ffmpeg/ffmpeg_common.h" |
#include "media/ffmpeg/ffmpeg_util.h" |
#include "media/filters/ffmpeg_demuxer.h" |
@@ -50,21 +51,54 @@ void FFmpegVideoDecodeEngine::Initialize(AVStream* stream, Task* done_cb) { |
int decode_threads = (codec_context_->codec_id == CODEC_ID_THEORA) |
? 1 : kDecodeThreads; |
+ // 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 (codec && |
avcodec_thread_init(codec_context_, decode_threads) >= 0 && |
- avcodec_open(codec_context_, codec) >= 0) { |
+ avcodec_open(codec_context_, codec) >= 0 && |
+ av_frame_.get()) { |
state_ = kNormal; |
} else { |
state_ = kError; |
} |
} |
+static void CopyPlane(size_t plane, |
+ scoped_refptr<VideoFrame> video_frame, |
+ const AVFrame* frame) { |
+ DCHECK(video_frame->width() % 2 == 0); |
+ const uint8* source = frame->data[plane]; |
+ const size_t source_stride = frame->linesize[plane]; |
+ uint8* dest = video_frame->data(plane); |
+ const size_t dest_stride = video_frame->stride(plane); |
+ size_t bytes_per_line = video_frame->width(); |
+ size_t copy_lines = video_frame->height(); |
+ if (plane != VideoFrame::kYPlane) { |
+ bytes_per_line /= 2; |
+ if (video_frame->format() == VideoFrame::YV12) { |
+ copy_lines = (copy_lines + 1) / 2; |
+ } |
+ } |
+ DCHECK(bytes_per_line <= source_stride && bytes_per_line <= dest_stride); |
+ for (size_t i = 0; i < copy_lines; ++i) { |
+ memcpy(dest, source, bytes_per_line); |
+ source += source_stride; |
+ dest += dest_stride; |
+ } |
+} |
+ |
// Decodes one frame of video with the given buffer. |
-void FFmpegVideoDecodeEngine::DecodeFrame(Buffer* buffer, |
- AVFrame* yuv_frame, |
- bool* got_frame, |
- Task* done_cb) { |
+void FFmpegVideoDecodeEngine::DecodeFrame( |
+ Buffer* buffer, |
+ scoped_refptr<VideoFrame>* video_frame, |
+ bool* got_frame, |
+ Task* done_cb) { |
AutoTaskRunner done_runner(done_cb); |
+ *got_frame = false; |
+ *video_frame = NULL; |
// Create a packet for input data. |
// Due to FFmpeg API changes we no longer have const read-only pointers. |
@@ -73,12 +107,11 @@ void FFmpegVideoDecodeEngine::DecodeFrame(Buffer* buffer, |
packet.data = const_cast<uint8*>(buffer->GetData()); |
packet.size = buffer->GetDataSize(); |
- // 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(). |
int frame_decoded = 0; |
- int result = |
- avcodec_decode_video2(codec_context_, yuv_frame, &frame_decoded, &packet); |
+ int result = avcodec_decode_video2(codec_context_, |
+ av_frame_.get(), |
+ &frame_decoded, |
+ &packet); |
// Log the problem if we can't decode a video frame and exit early. |
if (result < 0) { |
@@ -92,6 +125,43 @@ void FFmpegVideoDecodeEngine::DecodeFrame(Buffer* buffer, |
} else { |
// If frame_decoded == 0, then no frame was produced. |
*got_frame = frame_decoded != 0; |
+ |
+ if (*got_frame) { |
+ // TODO(fbarchard): Work around for FFmpeg http://crbug.com/27675 |
+ // The decoder is in a bad state and not decoding correctly. |
+ // Checking for NULL avoids a crash in CopyPlane(). |
+ if (!av_frame_->data[VideoFrame::kYPlane] || |
+ !av_frame_->data[VideoFrame::kUPlane] || |
+ !av_frame_->data[VideoFrame::kVPlane]) { |
+ // TODO(jiesun): this is also an error case handled as normal. |
+ *got_frame = false; |
+ return; |
+ } |
+ |
+ VideoFrame::CreateFrame(GetSurfaceFormat(), |
+ codec_context_->width, |
+ codec_context_->height, |
+ StreamSample::kInvalidTimestamp, |
+ StreamSample::kInvalidTimestamp, |
+ video_frame); |
+ if (!video_frame->get()) { |
+ // TODO(jiesun): this is also an error case handled as normal. |
+ *got_frame = false; |
+ return; |
+ } |
+ |
+ // 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): figure out pre-allocation/buffer cycling scheme. |
+ // TODO(scherkus): is there a cleaner way to figure out the # of planes? |
+ CopyPlane(VideoFrame::kYPlane, video_frame->get(), av_frame_.get()); |
+ CopyPlane(VideoFrame::kUPlane, video_frame->get(), av_frame_.get()); |
+ CopyPlane(VideoFrame::kVPlane, video_frame->get(), av_frame_.get()); |
+ |
+ DCHECK_LE(av_frame_->repeat_pict, 2); // Sanity check. |
+ (*video_frame)->SetRepeatCount(av_frame_->repeat_pict); |
+ } |
} |
} |