diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/DEPS | 1 | ||||
-rw-r--r-- | media/base/media_switches.cc | 3 | ||||
-rw-r--r-- | media/base/media_switches.h | 2 | ||||
-rw-r--r-- | media/base/video_decoder_config.h | 8 | ||||
-rw-r--r-- | media/ffmpeg/ffmpeg_common.cc | 25 | ||||
-rw-r--r-- | media/filters/vpx_video_decoder.cc | 341 | ||||
-rw-r--r-- | media/filters/vpx_video_decoder.h | 86 | ||||
-rw-r--r-- | media/media.gyp | 16 |
8 files changed, 477 insertions, 5 deletions
@@ -1,6 +1,7 @@ include_rules = [ "+jni", "+third_party/ffmpeg", + "+third_party/libvpx", "+third_party/openmax", "+third_party/opus", "+third_party/skia", diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc index f2950e1..acd696e 100644 --- a/media/base/media_switches.cc +++ b/media/base/media_switches.cc @@ -60,4 +60,7 @@ const char kEnableEncryptedMedia[] = "enable-encrypted-media"; // Enables Opus playback in media elements. const char kEnableOpusPlayback[] = "enable-opus-playback"; +// Enables VP9 playback in media elements. +const char kEnableVp9Playback[] = "enable-vp9-playback"; + } // namespace switches diff --git a/media/base/media_switches.h b/media/base/media_switches.h index 21c5879..435ca36 100644 --- a/media/base/media_switches.h +++ b/media/base/media_switches.h @@ -47,6 +47,8 @@ MEDIA_EXPORT extern const char kEnableEncryptedMedia[]; MEDIA_EXPORT extern const char kEnableOpusPlayback[]; +MEDIA_EXPORT extern const char kEnableVp9Playback[]; + } // namespace switches #endif // MEDIA_BASE_MEDIA_SWITCHES_H_ diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h index b5ac792..958192c 100644 --- a/media/base/video_decoder_config.h +++ b/media/base/video_decoder_config.h @@ -27,12 +27,13 @@ enum VideoCodec { kCodecMPEG4, kCodecTheora, kCodecVP8, + kCodecVP9, // DO NOT ADD RANDOM VIDEO CODECS! // // The only acceptable time to add a new codec is if there is production code // that uses said codec in the same CL. - kVideoCodecMax = kCodecVP8 // Must equal the last "real" codec above. + kVideoCodecMax = kCodecVP9 // Must equal the last "real" codec above. }; // Video stream profile. This *must* match PP_VideoDecoder_Profile. @@ -58,7 +59,10 @@ enum VideoCodecProfile { VP8PROFILE_MIN = 11, VP8PROFILE_MAIN = VP8PROFILE_MIN, VP8PROFILE_MAX = VP8PROFILE_MAIN, - VIDEO_CODEC_PROFILE_MAX = VP8PROFILE_MAX, + VP9PROFILE_MIN = 12, + VP9PROFILE_MAIN = VP9PROFILE_MIN, + VP9PROFILE_MAX = VP9PROFILE_MAIN, + VIDEO_CODEC_PROFILE_MAX = VP9PROFILE_MAX, }; class MEDIA_EXPORT VideoDecoderConfig { diff --git a/media/ffmpeg/ffmpeg_common.cc b/media/ffmpeg/ffmpeg_common.cc index 8b81e01..2ae0b1b 100644 --- a/media/ffmpeg/ffmpeg_common.cc +++ b/media/ffmpeg/ffmpeg_common.cc @@ -147,6 +147,8 @@ VideoCodec CodecIDToVideoCodec(CodecID codec_id) { return kCodecMPEG4; case CODEC_ID_VP8: return kCodecVP8; + case AV_CODEC_ID_VP9: + return kCodecVP9; default: DVLOG(1) << "Unknown video CodecID: " << codec_id; } @@ -163,6 +165,8 @@ static CodecID VideoCodecToCodecID(VideoCodec video_codec) { return CODEC_ID_MPEG4; case kCodecVP8: return CODEC_ID_VP8; + case kCodecVP9: + return AV_CODEC_ID_VP9; default: DVLOG(1) << "Unknown VideoCodec: " << video_codec; } @@ -335,13 +339,28 @@ void AVStreamToVideoDecoderConfig( aspect_ratio = stream->codec->sample_aspect_ratio; VideoCodec codec = CodecIDToVideoCodec(stream->codec->codec_id); - VideoCodecProfile profile = (codec == kCodecVP8) ? VP8PROFILE_MAIN : - ProfileIDToVideoCodecProfile(stream->codec->profile); + + VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN; + if (codec == kCodecVP8) + profile = VP8PROFILE_MAIN; + else if (codec == kCodecVP9) + profile = VP9PROFILE_MAIN; + else + profile = ProfileIDToVideoCodecProfile(stream->codec->profile); + gfx::Size natural_size = GetNaturalSize( visible_rect.size(), aspect_ratio.num, aspect_ratio.den); + + VideoFrame::Format format = PixelFormatToVideoFormat(stream->codec->pix_fmt); + if (codec == kCodecVP9) { + // TODO(tomfinegan): libavcodec doesn't know about VP9. + format = VideoFrame::YV12; + coded_size = natural_size; + } + config->Initialize(codec, profile, - PixelFormatToVideoFormat(stream->codec->pix_fmt), + format, coded_size, visible_rect, natural_size, stream->codec->extradata, stream->codec->extradata_size, false, // Not encrypted. diff --git a/media/filters/vpx_video_decoder.cc b/media/filters/vpx_video_decoder.cc new file mode 100644 index 0000000..0b92a7d --- /dev/null +++ b/media/filters/vpx_video_decoder.cc @@ -0,0 +1,341 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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/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 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/source/libvpx/vpx/vpx_decoder.h" +#include "third_party/libvpx/source/libvpx/vpx/vp8dx.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. +static const int kDecodeThreads = 2; +static const int kMaxDecodeThreads = 16; + +// 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; + + const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); + std::string threads(cmd_line->GetSwitchValueASCII(switches::kVideoThreads)); + if (threads.empty() || !base::StringToInt(threads, &decode_threads)) + return decode_threads; + + decode_threads = std::max(decode_threads, 0); + decode_threads = std::min(decode_threads, kMaxDecodeThreads); + return decode_threads; +} + +VpxVideoDecoder::VpxVideoDecoder( + const scoped_refptr<base::MessageLoopProxy>& message_loop) + : message_loop_(message_loop), + state_(kUninitialized), + vpx_codec_(NULL) { +} + +VpxVideoDecoder::~VpxVideoDecoder() { + DCHECK_EQ(kUninitialized, state_); + CloseDecoder(); +} + +void VpxVideoDecoder::Initialize( + const scoped_refptr<DemuxerStream>& stream, + const PipelineStatusCB& status_cb, + const StatisticsCB& statistics_cb) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(!demuxer_stream_) << "Already initialized."; + + if (!stream) { + status_cb.Run(PIPELINE_ERROR_DECODE); + return; + } + + demuxer_stream_ = stream; + statistics_cb_ = statistics_cb; + + if (!ConfigureDecoder()) { + status_cb.Run(DECODER_ERROR_NOT_SUPPORTED); + return; + } + + // Success! + state_ = kNormal; + status_cb.Run(PIPELINE_OK); +} + +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); + CHECK(read_cb_.is_null()) << "Overlapping decodes are not supported."; + read_cb_ = BindToCurrentLoop(read_cb); + + // Return empty frames if decoding has finished. + if (state_ == kDecodeFinished) { + read_cb.Run(kOk, VideoFrame::CreateEmptyFrame()); + return; + } + + ReadFromDemuxerStream(); +} + +void VpxVideoDecoder::Reset(const base::Closure& closure) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(reset_cb_.is_null()); + reset_cb_ = BindToCurrentLoop(closure); + + // Defer the reset if a read is pending. + if (!read_cb_.is_null()) + return; + + DoReset(); +} + +void VpxVideoDecoder::Stop(const base::Closure& closure) { + DCHECK(message_loop_->BelongsToCurrentThread()); + + if (state_ == kUninitialized) { + closure.Run(); + return; + } + + if (!read_cb_.is_null()) + base::ResetAndReturn(&read_cb_).Run(kOk, NULL); + + state_ = kUninitialized; + closure.Run(); +} + +void VpxVideoDecoder::ReadFromDemuxerStream() { + DCHECK_NE(state_, kUninitialized); + DCHECK_NE(state_, kDecodeFinished); + DCHECK(!read_cb_.is_null()); + + demuxer_stream_->Read(base::Bind( + &VpxVideoDecoder::DoDecryptOrDecodeBuffer, this)); +} + +void VpxVideoDecoder::DoDecryptOrDecodeBuffer( + DemuxerStream::Status status, + const scoped_refptr<DecoderBuffer>& buffer) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_NE(state_, kDecodeFinished); + DCHECK_EQ(status != DemuxerStream::kOk, !buffer) << status; + + if (state_ == kUninitialized) + return; + + DCHECK(!read_cb_.is_null()); + + if (!reset_cb_.is_null()) { + base::ResetAndReturn(&read_cb_).Run(kOk, NULL); + DoReset(); + return; + } + + if (status == DemuxerStream::kAborted) { + base::ResetAndReturn(&read_cb_).Run(kOk, NULL); + return; + } + + if (status == DemuxerStream::kConfigChanged) { + if (!ConfigureDecoder()) { + base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); + return; + } + + ReadFromDemuxerStream(); + return; + } + + DCHECK_EQ(status, DemuxerStream::kOk); + DecodeBuffer(buffer); +} + +void VpxVideoDecoder::DecodeBuffer( + const scoped_refptr<DecoderBuffer>& buffer) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK_NE(state_, kUninitialized); + DCHECK_NE(state_, kDecodeFinished); + DCHECK(reset_cb_.is_null()); + DCHECK(!read_cb_.is_null()); + DCHECK(buffer); + + // Transition to kDecodeFinished on the first end of stream buffer. + if (state_ == kNormal && buffer->IsEndOfStream()) { + state_ = kDecodeFinished; + base::ResetAndReturn(&read_cb_).Run(kOk, VideoFrame::CreateEmptyFrame()); + return; + } + + scoped_refptr<VideoFrame> video_frame; + if (!Decode(buffer, &video_frame)) { + state_ = kDecodeFinished; + base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL); + return; + } + + // Any successful decode counts! + if (buffer->GetDataSize()) { + PipelineStatistics statistics; + statistics.video_bytes_decoded = buffer->GetDataSize(); + statistics_cb_.Run(statistics); + } + + // If we didn't get a frame we need more data. + if (!video_frame) { + ReadFromDemuxerStream(); + return; + } + + base::ResetAndReturn(&read_cb_).Run(kOk, video_frame); +} + +bool VpxVideoDecoder::Decode( + const scoped_refptr<DecoderBuffer>& buffer, + scoped_refptr<VideoFrame>* video_frame) { + DCHECK(video_frame); + DCHECK(!buffer->IsEndOfStream()); + + // 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; + } + + // 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; + } + + if (vpx_image->user_priv != reinterpret_cast<void*>(×tamp)) { + LOG(ERROR) << "Invalid output timestamp."; + return false; + } + + CopyVpxImageTo(vpx_image, video_frame); + (*video_frame)->SetTimestamp(base::TimeDelta::FromMicroseconds(timestamp)); + return true; +} + +void VpxVideoDecoder::DoReset() { + DCHECK(read_cb_.is_null()); + + state_ = kNormal; + reset_cb_.Run(); + reset_cb_.Reset(); +} + +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 diff --git a/media/filters/vpx_video_decoder.h b/media/filters/vpx_video_decoder.h new file mode 100644 index 0000000..77578fd --- /dev/null +++ b/media/filters/vpx_video_decoder.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FILTERS_VPX_VIDEO_DECODER_H_ +#define MEDIA_FILTERS_VPX_VIDEO_DECODER_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "media/base/demuxer_stream.h" +#include "media/base/video_decoder.h" + +struct vpx_codec_ctx; +struct vpx_image; + +namespace base { +class MessageLoopProxy; +} + +namespace media { + +class MEDIA_EXPORT VpxVideoDecoder : public VideoDecoder { + public: + explicit VpxVideoDecoder( + const scoped_refptr<base::MessageLoopProxy>& message_loop); + + // VideoDecoder implementation. + virtual void Initialize(const scoped_refptr<DemuxerStream>& stream, + const PipelineStatusCB& status_cb, + const StatisticsCB& statistics_cb) OVERRIDE; + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual void Reset(const base::Closure& closure) OVERRIDE; + virtual void Stop(const base::Closure& closure) OVERRIDE; + + protected: + virtual ~VpxVideoDecoder(); + + private: + enum DecoderState { + kUninitialized, + kNormal, + kFlushCodec, + kDecodeFinished + }; + + // Handles (re-)initializing the decoder with a (new) config. + // Returns true when initialization was successful. + bool ConfigureDecoder(); + + void CloseDecoder(); + void ReadFromDemuxerStream(); + + // Carries out the buffer processing operation scheduled by + // DecryptOrDecodeBuffer(). + void DoDecryptOrDecodeBuffer(DemuxerStream::Status status, + const scoped_refptr<DecoderBuffer>& buffer); + + void DecodeBuffer(const scoped_refptr<DecoderBuffer>& buffer); + bool Decode(const scoped_refptr<DecoderBuffer>& buffer, + scoped_refptr<VideoFrame>* video_frame); + + // Reset decoder and call |reset_cb_|. + void DoReset(); + + void CopyVpxImageTo(const vpx_image* vpx_image, + scoped_refptr<VideoFrame>* video_frame); + + scoped_refptr<base::MessageLoopProxy> message_loop_; + + DecoderState state_; + + StatisticsCB statistics_cb_; + ReadCB read_cb_; + base::Closure reset_cb_; + + // Pointer to the demuxer stream that will feed us compressed buffers. + scoped_refptr<DemuxerStream> demuxer_stream_; + + vpx_codec_ctx* vpx_codec_; + + DISALLOW_COPY_AND_ASSIGN(VpxVideoDecoder); +}; + +} // namespace media + +#endif // MEDIA_FILTERS_VPX_VIDEO_DECODER_H_ diff --git a/media/media.gyp b/media/media.gyp index 78f949c..8b33bd8 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -13,8 +13,11 @@ ['OS == "android" or OS == "ios"', { # Android and iOS don't use ffmpeg. 'use_ffmpeg%': 0, + # Android and iOS don't use libvpx. + 'use_libvpx%': 0, }, { # 'OS != "android" and OS != "ios"' 'use_ffmpeg%': 1, + 'use_libvpx%': 1, }], ], }, @@ -313,6 +316,8 @@ 'filters/video_frame_generator.h', 'filters/video_renderer_base.cc', 'filters/video_renderer_base.h', + 'filters/vpx_video_decoder.cc', + 'filters/vpx_video_decoder.h', 'video/capture/fake_video_capture_device.cc', 'video/capture/fake_video_capture_device.h', 'video/capture/linux/video_capture_device_linux.cc', @@ -413,6 +418,17 @@ 'webm/webm_stream_parser.h', ], }], + ['use_libvpx == 1', { + 'dependencies': [ + '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', + ], + }, { # use_libvpx == 0 + # Exclude the sources that depend on libvpx. + 'sources!': [ + 'filters/vpx_video_decoder.cc', + 'filters/vpx_video_decoder.h', + ], + }], ['OS == "ios"', { 'includes': [ # For shared_memory_support_sources variable. |