Index: media/filters/vpx_video_decoder.cc |
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/vpx_video_decoder.cc |
similarity index 34% |
copy from media/filters/ffmpeg_video_decoder.cc |
copy to media/filters/vpx_video_decoder.cc |
index 6f6c49506d54ebe41f26fd474d913bdbd67c202a..662b8cc62bf8d2c0ec67cbfd56fc7825676bee4d 100644 |
--- a/media/filters/ffmpeg_video_decoder.cc |
+++ b/media/filters/vpx_video_decoder.cc |
@@ -2,46 +2,45 @@ |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
-#include "media/filters/ffmpeg_video_decoder.h" |
- |
-#include <algorithm> |
-#include <string> |
+#include "media/filters/vpx_video_decoder.h" |
#include "base/bind.h" |
#include "base/callback_helpers.h" |
#include "base/command_line.h" |
#include "base/location.h" |
+#include "base/logging.h" |
#include "base/message_loop_proxy.h" |
#include "base/string_number_conversions.h" |
#include "media/base/bind_to_loop.h" |
#include "media/base/decoder_buffer.h" |
#include "media/base/demuxer_stream.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_frame.h" |
#include "media/base/video_util.h" |
-#include "media/ffmpeg/ffmpeg_common.h" |
-#include "media/filters/ffmpeg_glue.h" |
+ |
+// Include libvpx header files. |
+// VPX_CODEC_DISABLE_COMPAT excludes parts of the libvpx API that provide |
+// backwards compatibility for legacy applications using the library. |
+#define VPX_CODEC_DISABLE_COMPAT 1 |
+extern "C" { |
+#include "third_party/libvpx/libvpx.h" |
+} |
namespace media { |
// Always try to use three threads for video decoding. There is little reason |
// not to since current day CPUs tend to be multi-core and we measured |
// performance benefits on older machines such as P4s with hyperthreading. |
-// |
-// Handling decoding on separate threads also frees up the pipeline thread to |
-// continue processing. Although it'd be nice to have the option of a single |
-// decoding thread, FFmpeg treats having one thread the same as having zero |
-// threads (i.e., avcodec_decode_video() will execute on the calling thread). |
-// Yet another reason for having two threads :) |
static const int kDecodeThreads = 2; |
static const int kMaxDecodeThreads = 16; |
-// Returns the number of threads given the FFmpeg CodecID. Also inspects the |
-// command line for a valid --video-threads flag. |
-static int GetThreadCount(CodecID codec_id) { |
+// Returns the number of threads. |
+static int GetThreadCount() { |
+ // TODO(scherkus): De-duplicate this function and the one used by |
+ // FFmpegVideoDecoder. |
+ |
// Refer to http://crbug.com/93932 for tsan suppressions on decoding. |
int decode_threads = kDecodeThreads; |
@@ -55,91 +54,27 @@ static int GetThreadCount(CodecID codec_id) { |
return decode_threads; |
} |
-FFmpegVideoDecoder::FFmpegVideoDecoder( |
+VpxVideoDecoder::VpxVideoDecoder( |
const scoped_refptr<base::MessageLoopProxy>& message_loop) |
: message_loop_(message_loop), |
state_(kUninitialized), |
- codec_context_(NULL), |
- av_frame_(NULL) { |
-} |
- |
-int FFmpegVideoDecoder::GetVideoBuffer(AVCodecContext* codec_context, |
- AVFrame* frame) { |
- // Don't use |codec_context_| here! With threaded decoding, |
- // it will contain unsynchronized width/height/pix_fmt values, |
- // whereas |codec_context| contains the current threads's |
- // updated width/height/pix_fmt, which can change for adaptive |
- // content. |
- VideoFrame::Format format = PixelFormatToVideoFormat(codec_context->pix_fmt); |
- if (format == VideoFrame::INVALID) |
- return AVERROR(EINVAL); |
- DCHECK(format == VideoFrame::YV12 || format == VideoFrame::YV16); |
- |
- gfx::Size size(codec_context->width, codec_context->height); |
- int ret; |
- if ((ret = av_image_check_size(size.width(), size.height(), 0, NULL)) < 0) |
- return ret; |
- |
- gfx::Size natural_size; |
- if (codec_context->sample_aspect_ratio.num > 0) { |
- natural_size = GetNaturalSize(size, |
- codec_context->sample_aspect_ratio.num, |
- codec_context->sample_aspect_ratio.den); |
- } else { |
- natural_size = demuxer_stream_->video_decoder_config().natural_size(); |
- } |
- |
- if (!VideoFrame::IsValidConfig(format, size, gfx::Rect(size), natural_size)) |
- return AVERROR(EINVAL); |
- |
- scoped_refptr<VideoFrame> video_frame = |
- VideoFrame::CreateFrame(format, size, gfx::Rect(size), natural_size, |
- kNoTimestamp()); |
- |
- for (int i = 0; i < 3; i++) { |
- frame->base[i] = video_frame->data(i); |
- frame->data[i] = video_frame->data(i); |
- frame->linesize[i] = video_frame->stride(i); |
- } |
- |
- frame->opaque = NULL; |
- video_frame.swap(reinterpret_cast<VideoFrame**>(&frame->opaque)); |
- frame->type = FF_BUFFER_TYPE_USER; |
- frame->pkt_pts = codec_context->pkt ? codec_context->pkt->pts : |
- AV_NOPTS_VALUE; |
- frame->width = codec_context->width; |
- frame->height = codec_context->height; |
- frame->format = codec_context->pix_fmt; |
- |
- return 0; |
-} |
- |
-static int GetVideoBufferImpl(AVCodecContext* s, AVFrame* frame) { |
- FFmpegVideoDecoder* vd = static_cast<FFmpegVideoDecoder*>(s->opaque); |
- return vd->GetVideoBuffer(s, frame); |
+ vpx_codec_(NULL) { |
} |
-static void ReleaseVideoBufferImpl(AVCodecContext* s, AVFrame* frame) { |
- scoped_refptr<VideoFrame> video_frame; |
- video_frame.swap(reinterpret_cast<VideoFrame**>(&frame->opaque)); |
- |
- // The FFmpeg API expects us to zero the data pointers in |
- // this callback |
- memset(frame->data, 0, sizeof(frame->data)); |
- frame->opaque = NULL; |
+VpxVideoDecoder::~VpxVideoDecoder() { |
+ DCHECK_EQ(kUninitialized, state_); |
+ CloseDecoder(); |
} |
-void FFmpegVideoDecoder::Initialize(const scoped_refptr<DemuxerStream>& stream, |
- const PipelineStatusCB& status_cb, |
- const StatisticsCB& statistics_cb) { |
+void VpxVideoDecoder::Initialize( |
+ const scoped_refptr<DemuxerStream>& stream, |
+ const PipelineStatusCB& status_cb, |
+ const StatisticsCB& statistics_cb) { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
- PipelineStatusCB initialize_cb = BindToCurrentLoop(status_cb); |
- |
- FFmpegGlue::InitializeFFmpeg(); |
DCHECK(!demuxer_stream_) << "Already initialized."; |
if (!stream) { |
- initialize_cb.Run(PIPELINE_ERROR_DECODE); |
+ status_cb.Run(PIPELINE_ERROR_DECODE); |
return; |
} |
@@ -147,16 +82,57 @@ void FFmpegVideoDecoder::Initialize(const scoped_refptr<DemuxerStream>& stream, |
statistics_cb_ = statistics_cb; |
if (!ConfigureDecoder()) { |
- initialize_cb.Run(DECODER_ERROR_NOT_SUPPORTED); |
+ status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); |
return; |
} |
// Success! |
state_ = kNormal; |
- initialize_cb.Run(PIPELINE_OK); |
+ status_cb.Run(PIPELINE_OK); |
} |
-void FFmpegVideoDecoder::Read(const ReadCB& read_cb) { |
+bool VpxVideoDecoder::ConfigureDecoder() { |
+ const VideoDecoderConfig& config = demuxer_stream_->video_decoder_config(); |
+ if (!config.IsValidConfig()) { |
+ DLOG(ERROR) << "Invalid video stream config: " |
+ << config.AsHumanReadableString(); |
+ return false; |
+ } |
+ |
+ if (config.codec() != kCodecVP9) |
+ return false; |
+ |
+ CloseDecoder(); |
+ |
+ vpx_codec_ = new vpx_codec_ctx(); |
+ vpx_codec_dec_cfg_t vpx_config = {0}; |
+ vpx_config.w = config.coded_size().width(); |
+ vpx_config.h = config.coded_size().height(); |
+ vpx_config.threads = GetThreadCount(); |
+ |
+ vpx_codec_err_t status = vpx_codec_dec_init(vpx_codec_, |
+ vpx_codec_vp9_dx(), |
+ &vpx_config, |
+ 0); |
+ if (status != VPX_CODEC_OK) { |
+ LOG(ERROR) << "vpx_codec_dec_init failed, status=" << status; |
+ delete vpx_codec_; |
+ vpx_codec_ = NULL; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+void VpxVideoDecoder::CloseDecoder() { |
+ if (vpx_codec_) { |
+ vpx_codec_destroy(vpx_codec_); |
+ delete vpx_codec_; |
+ vpx_codec_ = NULL; |
+ } |
+} |
+ |
+void VpxVideoDecoder::Read(const ReadCB& read_cb) { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
DCHECK(!read_cb.is_null()); |
CHECK_NE(state_, kUninitialized); |
@@ -165,14 +141,14 @@ void FFmpegVideoDecoder::Read(const ReadCB& read_cb) { |
// Return empty frames if decoding has finished. |
if (state_ == kDecodeFinished) { |
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame()); |
+ read_cb.Run(kOk, VideoFrame::CreateEmptyFrame()); |
return; |
} |
ReadFromDemuxerStream(); |
} |
-void FFmpegVideoDecoder::Reset(const base::Closure& closure) { |
+void VpxVideoDecoder::Reset(const base::Closure& closure) { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
DCHECK(reset_cb_.is_null()); |
reset_cb_ = BindToCurrentLoop(closure); |
@@ -184,44 +160,31 @@ void FFmpegVideoDecoder::Reset(const base::Closure& closure) { |
DoReset(); |
} |
-void FFmpegVideoDecoder::DoReset() { |
- DCHECK(read_cb_.is_null()); |
- |
- avcodec_flush_buffers(codec_context_); |
- state_ = kNormal; |
- base::ResetAndReturn(&reset_cb_).Run(); |
-} |
- |
-void FFmpegVideoDecoder::Stop(const base::Closure& closure) { |
+void VpxVideoDecoder::Stop(const base::Closure& closure) { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
- base::ScopedClosureRunner runner(BindToCurrentLoop(closure)); |
- if (state_ == kUninitialized) |
+ if (state_ == kUninitialized) { |
+ closure.Run(); |
return; |
+ } |
if (!read_cb_.is_null()) |
base::ResetAndReturn(&read_cb_).Run(kOk, NULL); |
- ReleaseFFmpegResources(); |
state_ = kUninitialized; |
+ closure.Run(); |
} |
-FFmpegVideoDecoder::~FFmpegVideoDecoder() { |
- DCHECK_EQ(kUninitialized, state_); |
- DCHECK(!codec_context_); |
- DCHECK(!av_frame_); |
-} |
- |
-void FFmpegVideoDecoder::ReadFromDemuxerStream() { |
+void VpxVideoDecoder::ReadFromDemuxerStream() { |
DCHECK_NE(state_, kUninitialized); |
DCHECK_NE(state_, kDecodeFinished); |
DCHECK(!read_cb_.is_null()); |
demuxer_stream_->Read(base::Bind( |
- &FFmpegVideoDecoder::BufferReady, this)); |
+ &VpxVideoDecoder::DoDecryptOrDecodeBuffer, this)); |
} |
-void FFmpegVideoDecoder::BufferReady( |
+void VpxVideoDecoder::DoDecryptOrDecodeBuffer( |
DemuxerStream::Status status, |
const scoped_refptr<DecoderBuffer>& buffer) { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
@@ -258,7 +221,7 @@ void FFmpegVideoDecoder::BufferReady( |
DecodeBuffer(buffer); |
} |
-void FFmpegVideoDecoder::DecodeBuffer( |
+void VpxVideoDecoder::DecodeBuffer( |
const scoped_refptr<DecoderBuffer>& buffer) { |
DCHECK(message_loop_->BelongsToCurrentThread()); |
DCHECK_NE(state_, kUninitialized); |
@@ -267,34 +230,11 @@ void FFmpegVideoDecoder::DecodeBuffer( |
DCHECK(!read_cb_.is_null()); |
DCHECK(buffer); |
- // 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 decoding error occurs and decoding needs to stop. |
- // kFlushCodec -> kDecodeFinished: |
- // When avcodec_decode_video2() returns 0 data or errors out. |
- // (any state) -> kNormal: |
- // Any time Reset() is called. |
- |
- // Transition to kFlushCodec on the first end of stream buffer. |
+ // Transition to kDecodeFinished on the first end of stream buffer. |
if (state_ == kNormal && buffer->IsEndOfStream()) { |
- state_ = kFlushCodec; |
+ state_ = kDecodeFinished; |
+ base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame()); |
+ return; |
} |
scoped_refptr<VideoFrame> video_frame; |
@@ -311,15 +251,8 @@ void FFmpegVideoDecoder::DecodeBuffer( |
statistics_cb_.Run(statistics); |
} |
- // If we didn't get a frame then we've either completely finished decoding or |
- // we need more data. |
+ // If we didn't get a frame we need more data. |
if (!video_frame) { |
- if (state_ == kFlushCodec) { |
- state_ = kDecodeFinished; |
- base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame()); |
- return; |
- } |
- |
ReadFromDemuxerStream(); |
return; |
} |
@@ -327,125 +260,80 @@ void FFmpegVideoDecoder::DecodeBuffer( |
base::ResetAndReturn(&read_cb_).Run(kOk, video_frame); |
} |
-bool FFmpegVideoDecoder::Decode( |
+bool VpxVideoDecoder::Decode( |
const scoped_refptr<DecoderBuffer>& 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. |
- AVPacket packet; |
- av_init_packet(&packet); |
- packet.data = const_cast<uint8*>(buffer->GetData()); |
- packet.size = buffer->GetDataSize(); |
- |
- // Let FFmpeg handle presentation timestamp reordering. |
- codec_context_->reordered_opaque = buffer->GetTimestamp().InMicroseconds(); |
- |
- // Reset frame to default values. |
- avcodec_get_frame_defaults(av_frame_); |
- |
- // This is for codecs not using get_buffer to initialize |
- // |av_frame_->reordered_opaque| |
- av_frame_->reordered_opaque = codec_context_->reordered_opaque; |
- |
- int frame_decoded = 0; |
- int result = avcodec_decode_video2(codec_context_, |
- av_frame_, |
- &frame_decoded, |
- &packet); |
- // Log the problem if we can't decode a video frame and exit early. |
- if (result < 0) { |
- LOG(ERROR) << "Error decoding a video frame with timestamp: " |
- << buffer->GetTimestamp().InMicroseconds() << " us, duration: " |
- << buffer->GetDuration().InMicroseconds() << " us, packet size: " |
- << buffer->GetDataSize() << " bytes"; |
- *video_frame = NULL; |
+ // Pass |buffer| to libvpx. |
+ int64 timestamp = buffer->GetTimestamp().InMicroseconds(); |
+ void* user_priv = reinterpret_cast<void*>(×tamp); |
+ vpx_codec_err_t status = vpx_codec_decode(vpx_codec_, |
+ buffer->GetData(), |
+ buffer->GetDataSize(), |
+ user_priv, |
+ 0); |
+ if (status != VPX_CODEC_OK) { |
+ LOG(ERROR) << "vpx_codec_decode() failed, status=" << status; |
return false; |
} |
- // 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) { |
+ // Gets pointer to decoded data. |
+ vpx_codec_iter_t iter = NULL; |
+ const vpx_image_t* vpx_image = vpx_codec_get_frame(vpx_codec_, &iter); |
+ if (!vpx_image) { |
*video_frame = NULL; |
return true; |
} |
- // 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]) { |
- LOG(ERROR) << "Video frame was produced yet has invalid frame data."; |
- *video_frame = NULL; |
+ if (vpx_image->user_priv != reinterpret_cast<void*>(×tamp)) { |
+ LOG(ERROR) << "Invalid output timestamp."; |
return false; |
} |
- if (!av_frame_->opaque) { |
- LOG(ERROR) << "VideoFrame object associated with frame data not set."; |
- return false; |
- } |
- *video_frame = static_cast<VideoFrame*>(av_frame_->opaque); |
- |
- (*video_frame)->SetTimestamp( |
- base::TimeDelta::FromMicroseconds(av_frame_->reordered_opaque)); |
- |
+ CopyVpxImageTo(vpx_image, video_frame); |
+ (*video_frame)->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp)); |
return true; |
} |
-void FFmpegVideoDecoder::ReleaseFFmpegResources() { |
- if (codec_context_) { |
- av_free(codec_context_->extradata); |
- avcodec_close(codec_context_); |
- av_free(codec_context_); |
- codec_context_ = NULL; |
- } |
- if (av_frame_) { |
- av_free(av_frame_); |
- av_frame_ = NULL; |
- } |
-} |
- |
-bool FFmpegVideoDecoder::ConfigureDecoder() { |
- const VideoDecoderConfig& config = demuxer_stream_->video_decoder_config(); |
- |
- if (!config.IsValidConfig()) { |
- DLOG(ERROR) << "Invalid video stream - " << config.AsHumanReadableString(); |
- return false; |
- } |
- |
- if (config.is_encrypted()) { |
- DLOG(ERROR) << "Encrypted video stream not supported."; |
- return false; |
- } |
+void VpxVideoDecoder::DoReset() { |
+ DCHECK(read_cb_.is_null()); |
- // Release existing decoder resources if necessary. |
- ReleaseFFmpegResources(); |
- |
- // Initialize AVCodecContext structure. |
- codec_context_ = avcodec_alloc_context3(NULL); |
- VideoDecoderConfigToAVCodecContext(config, codec_context_); |
- |
- // Enable motion vector search (potentially slow), strong deblocking filter |
- // for damaged macroblocks, and set our error detection sensitivity. |
- codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK; |
- codec_context_->thread_count = GetThreadCount(codec_context_->codec_id); |
- codec_context_->opaque = this; |
- codec_context_->flags |= CODEC_FLAG_EMU_EDGE; |
- codec_context_->get_buffer = GetVideoBufferImpl; |
- codec_context_->release_buffer = ReleaseVideoBufferImpl; |
- |
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); |
- if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) { |
- ReleaseFFmpegResources(); |
- return false; |
- } |
+ state_ = kNormal; |
+ reset_cb_.Run(); |
+ reset_cb_.Reset(); |
+} |
- av_frame_ = avcodec_alloc_frame(); |
- return true; |
+void VpxVideoDecoder::CopyVpxImageTo( |
+ const vpx_image* vpx_image, |
+ scoped_refptr<VideoFrame>* video_frame) { |
+ CHECK(vpx_image); |
+ CHECK_EQ(vpx_image->d_w % 2, 0U); |
+ CHECK_EQ(vpx_image->d_h % 2, 0U); |
+ CHECK(vpx_image->fmt == VPX_IMG_FMT_I420 || |
+ vpx_image->fmt == VPX_IMG_FMT_YV12); |
+ |
+ gfx::Size size(vpx_image->d_w, vpx_image->d_h); |
+ gfx::Size natural_size = |
+ demuxer_stream_->video_decoder_config().natural_size(); |
+ |
+ *video_frame = VideoFrame::CreateFrame(VideoFrame::YV12, |
+ size, |
+ gfx::Rect(size), |
+ natural_size, |
+ kNoTimestamp()); |
+ CopyYPlane(vpx_image->planes[VPX_PLANE_Y], |
+ vpx_image->stride[VPX_PLANE_Y], |
+ vpx_image->d_h, |
+ *video_frame); |
+ CopyUPlane(vpx_image->planes[VPX_PLANE_U], |
+ vpx_image->stride[VPX_PLANE_U], |
+ vpx_image->d_h / 2, |
+ *video_frame); |
+ CopyVPlane(vpx_image->planes[VPX_PLANE_V], |
+ vpx_image->stride[VPX_PLANE_V], |
+ vpx_image->d_h / 2, |
+ *video_frame); |
} |
} // namespace media |