diff options
author | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-10 08:21:16 +0000 |
---|---|---|
committer | dalecurtis@chromium.org <dalecurtis@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-10 08:21:16 +0000 |
commit | 177bcf8faa90b3544ac7879f81138d8802227a85 (patch) | |
tree | c7be51e4a949a1ca2aab4abded2940527b8fdda1 /media | |
parent | e2a322b92337f3dce9012b2fd1ede6459dd808b4 (diff) | |
download | chromium_src-177bcf8faa90b3544ac7879f81138d8802227a85.zip chromium_src-177bcf8faa90b3544ac7879f81138d8802227a85.tar.gz chromium_src-177bcf8faa90b3544ac7879f81138d8802227a85.tar.bz2 |
Relocate last remnants of webkit/renderer/media code.
- Moves webkit/renderer/media/crypto/ppapi/ to media/cdm/ppapi/
- Moves webkit/.../ppapi/cdm/ to media/cdm/ppapi/api/
- Moves everything in webkit_media to media namespace
- Fixes broken builds with use_fake_video_decoder=1 or use_libvpx=1
- Deletes webkit/renderer/media folder (\o/)
BUG=251306
TEST=Chrome builds with mentioned flags, plus branding=Chrome.
TBR=rsleevi
Review URL: https://chromiumcodereview.appspot.com/22362007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@216826 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
22 files changed, 3656 insertions, 9 deletions
diff --git a/media/crypto/DEPS b/media/cdm/DEPS index 4ef4138..4ef4138 100644 --- a/media/crypto/DEPS +++ b/media/cdm/DEPS diff --git a/media/crypto/aes_decryptor.cc b/media/cdm/aes_decryptor.cc index fd7c628..da11442 100644 --- a/media/crypto/aes_decryptor.cc +++ b/media/cdm/aes_decryptor.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2013 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/crypto/aes_decryptor.h" +#include "media/cdm/aes_decryptor.h" #include <vector> diff --git a/media/crypto/aes_decryptor.h b/media/cdm/aes_decryptor.h index aec55d7..fda5a0f 100644 --- a/media/crypto/aes_decryptor.h +++ b/media/cdm/aes_decryptor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2013 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. diff --git a/media/crypto/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc index 8feaec4..1edb8e8 100644 --- a/media/crypto/aes_decryptor_unittest.cc +++ b/media/cdm/aes_decryptor_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2013 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. @@ -10,7 +10,7 @@ #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/base/mock_filters.h" -#include "media/crypto/aes_decryptor.h" +#include "media/cdm/aes_decryptor.h" #include "media/webm/webm_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/media/cdm/ppapi/DEPS b/media/cdm/ppapi/DEPS new file mode 100644 index 0000000..473873d --- /dev/null +++ b/media/cdm/ppapi/DEPS @@ -0,0 +1,7 @@ +# Since media/ does not depend on anything in media/cdm/ppapi/, these extra +# dependencies are okay. +include_rules = [ + "+ppapi/c", + "+ppapi/cpp", + "+ppapi/utility", +] diff --git a/media/cdm/ppapi/cdm_video_decoder.cc b/media/cdm/ppapi/cdm_video_decoder.cc new file mode 100644 index 0000000..95523d4 --- /dev/null +++ b/media/cdm/ppapi/cdm_video_decoder.cc @@ -0,0 +1,56 @@ +// Copyright 2013 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 "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/cdm/ppapi/cdm_video_decoder.h" + +#if defined(CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER) +#include "media/cdm/ppapi/fake_cdm_video_decoder.h" +#endif + +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) +#include "media/cdm/ppapi/ffmpeg_cdm_video_decoder.h" +#endif + +#if defined(CLEAR_KEY_CDM_USE_LIBVPX_DECODER) +#include "media/cdm/ppapi/libvpx_cdm_video_decoder.h" +#endif + +namespace media { + +scoped_ptr<CdmVideoDecoder> CreateVideoDecoder( + cdm::Host* host, const cdm::VideoDecoderConfig& config) { + scoped_ptr<CdmVideoDecoder> video_decoder; +#if defined(CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER) + video_decoder.reset(new FakeCdmVideoDecoder(host)); + + if (!video_decoder->Initialize(config)) + video_decoder.reset(); +#else + +#if defined(CLEAR_KEY_CDM_USE_LIBVPX_DECODER) + if (config.codec == cdm::VideoDecoderConfig::kCodecVp8) { + video_decoder.reset(new LibvpxCdmVideoDecoder(host)); + + if (!video_decoder->Initialize(config)) + video_decoder.reset(); + + return video_decoder.Pass(); + } +#endif + +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + video_decoder.reset(new FFmpegCdmVideoDecoder(host)); + + if (!video_decoder->Initialize(config)) + video_decoder.reset(); +#endif + +#endif // CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER + + return video_decoder.Pass(); +} + +} // namespace media diff --git a/media/cdm/ppapi/cdm_video_decoder.h b/media/cdm/ppapi/cdm_video_decoder.h new file mode 100644 index 0000000..25d48f4 --- /dev/null +++ b/media/cdm/ppapi/cdm_video_decoder.h @@ -0,0 +1,43 @@ +// Copyright 2013 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_CDM_PPAPI_CDM_VIDEO_DECODER_H_ +#define MEDIA_CDM_PPAPI_CDM_VIDEO_DECODER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" + +namespace media { + +class CdmVideoDecoder { + public: + virtual ~CdmVideoDecoder() {} + virtual bool Initialize(const cdm::VideoDecoderConfig& config) = 0; + virtual void Deinitialize() = 0; + virtual void Reset() = 0; + virtual bool is_initialized() const = 0; + + // Decodes |compressed_frame|. Stores output frame in |decoded_frame| and + // returns |cdm::kSuccess| when an output frame is available. Returns + // |cdm::kNeedMoreData| when |compressed_frame| does not produce an output + // frame. Returns |cdm::kDecodeError| when decoding fails. + // + // A null |compressed_frame| will attempt to flush the decoder of any + // remaining frames. |compressed_frame_size| and |timestamp| are ignored. + virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) = 0; +}; + +// Initializes appropriate video decoder based on GYP flags and the value of +// |config.codec|. Returns a scoped_ptr containing a non-null initialized +// CdmVideoDecoder* upon success. +scoped_ptr<CdmVideoDecoder> CreateVideoDecoder( + cdm::Host* host, const cdm::VideoDecoderConfig& config); + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_CDM_VIDEO_DECODER_H_ diff --git a/media/cdm/ppapi/cdm_wrapper.cc b/media/cdm/ppapi/cdm_wrapper.cc new file mode 100644 index 0000000..6348f19 --- /dev/null +++ b/media/cdm/ppapi/cdm_wrapper.cc @@ -0,0 +1,1198 @@ +// Copyright 2013 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 <cstring> +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" +#include "media/cdm/ppapi/linked_ptr.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_stdint.h" +#include "ppapi/c/private/pp_content_decryptor.h" +#include "ppapi/cpp/completion_callback.h" +#include "ppapi/cpp/core.h" +#include "ppapi/cpp/dev/buffer_dev.h" +#include "ppapi/cpp/instance.h" +#include "ppapi/cpp/logging.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/pass_ref.h" +#include "ppapi/cpp/private/content_decryptor_private.h" +#include "ppapi/cpp/resource.h" +#include "ppapi/cpp/var.h" +#include "ppapi/cpp/var_array_buffer.h" +#include "ppapi/utility/completion_callback_factory.h" + +#if defined(CHECK_DOCUMENT_URL) +#include "ppapi/cpp/dev/url_util_dev.h" +#include "ppapi/cpp/instance_handle.h" +#endif // defined(CHECK_DOCUMENT_URL) + +namespace { + +bool IsMainThread() { + return pp::Module::Get()->core()->IsMainThread(); +} + +// Posts a task to run |cb| on the main thread. The task is posted even if the +// current thread is the main thread. +void PostOnMain(pp::CompletionCallback cb) { + pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK); +} + +// Ensures |cb| is called on the main thread, either because the current thread +// is the main thread or by posting it to the main thread. +void CallOnMain(pp::CompletionCallback cb) { + // TODO(tomfinegan): This is only necessary because PPAPI doesn't allow calls + // off the main thread yet. Remove this once the change lands. + if (IsMainThread()) + cb.Run(PP_OK); + else + PostOnMain(cb); +} + +// Configures a cdm::InputBuffer. |subsamples| must exist as long as +// |input_buffer| is in use. +void ConfigureInputBuffer( + const pp::Buffer_Dev& encrypted_buffer, + const PP_EncryptedBlockInfo& encrypted_block_info, + std::vector<cdm::SubsampleEntry>* subsamples, + cdm::InputBuffer* input_buffer) { + PP_DCHECK(subsamples); + PP_DCHECK(!encrypted_buffer.is_null()); + + input_buffer->data = static_cast<uint8_t*>(encrypted_buffer.data()); + input_buffer->data_size = encrypted_block_info.data_size; + PP_DCHECK(encrypted_buffer.size() >= + static_cast<uint32_t>(input_buffer->data_size)); + input_buffer->data_offset = encrypted_block_info.data_offset; + + PP_DCHECK(encrypted_block_info.key_id_size <= + arraysize(encrypted_block_info.key_id)); + input_buffer->key_id_size = encrypted_block_info.key_id_size; + input_buffer->key_id = input_buffer->key_id_size > 0 ? + encrypted_block_info.key_id : NULL; + + PP_DCHECK(encrypted_block_info.iv_size <= arraysize(encrypted_block_info.iv)); + input_buffer->iv_size = encrypted_block_info.iv_size; + input_buffer->iv = encrypted_block_info.iv_size > 0 ? + encrypted_block_info.iv : NULL; + + input_buffer->num_subsamples = encrypted_block_info.num_subsamples; + if (encrypted_block_info.num_subsamples > 0) { + subsamples->reserve(encrypted_block_info.num_subsamples); + + for (uint32_t i = 0; i < encrypted_block_info.num_subsamples; ++i) { + subsamples->push_back(cdm::SubsampleEntry( + encrypted_block_info.subsamples[i].clear_bytes, + encrypted_block_info.subsamples[i].cipher_bytes)); + } + + input_buffer->subsamples = &(*subsamples)[0]; + } + + input_buffer->timestamp = encrypted_block_info.tracking_info.timestamp; +} + +PP_DecryptResult CdmStatusToPpDecryptResult(cdm::Status status) { + switch (status) { + case cdm::kSuccess: + return PP_DECRYPTRESULT_SUCCESS; + case cdm::kNoKey: + return PP_DECRYPTRESULT_DECRYPT_NOKEY; + case cdm::kNeedMoreData: + return PP_DECRYPTRESULT_NEEDMOREDATA; + case cdm::kDecryptError: + return PP_DECRYPTRESULT_DECRYPT_ERROR; + case cdm::kDecodeError: + return PP_DECRYPTRESULT_DECODE_ERROR; + default: + PP_NOTREACHED(); + return PP_DECRYPTRESULT_DECODE_ERROR; + } +} + +PP_DecryptedFrameFormat CdmVideoFormatToPpDecryptedFrameFormat( + cdm::VideoFormat format) { + switch (format) { + case cdm::kYv12: + return PP_DECRYPTEDFRAMEFORMAT_YV12; + case cdm::kI420: + return PP_DECRYPTEDFRAMEFORMAT_I420; + default: + return PP_DECRYPTEDFRAMEFORMAT_UNKNOWN; + } +} + +cdm::AudioDecoderConfig::AudioCodec PpAudioCodecToCdmAudioCodec( + PP_AudioCodec codec) { + switch (codec) { + case PP_AUDIOCODEC_VORBIS: + return cdm::AudioDecoderConfig::kCodecVorbis; + case PP_AUDIOCODEC_AAC: + return cdm::AudioDecoderConfig::kCodecAac; + default: + return cdm::AudioDecoderConfig::kUnknownAudioCodec; + } +} + +cdm::VideoDecoderConfig::VideoCodec PpVideoCodecToCdmVideoCodec( + PP_VideoCodec codec) { + switch (codec) { + case PP_VIDEOCODEC_VP8: + return cdm::VideoDecoderConfig::kCodecVp8; + case PP_VIDEOCODEC_H264: + return cdm::VideoDecoderConfig::kCodecH264; + default: + return cdm::VideoDecoderConfig::kUnknownVideoCodec; + } +} + +cdm::VideoDecoderConfig::VideoCodecProfile PpVCProfileToCdmVCProfile( + PP_VideoCodecProfile profile) { + switch (profile) { + case PP_VIDEOCODECPROFILE_VP8_MAIN: + return cdm::VideoDecoderConfig::kVp8ProfileMain; + case PP_VIDEOCODECPROFILE_H264_BASELINE: + return cdm::VideoDecoderConfig::kH264ProfileBaseline; + case PP_VIDEOCODECPROFILE_H264_MAIN: + return cdm::VideoDecoderConfig::kH264ProfileMain; + case PP_VIDEOCODECPROFILE_H264_EXTENDED: + return cdm::VideoDecoderConfig::kH264ProfileExtended; + case PP_VIDEOCODECPROFILE_H264_HIGH: + return cdm::VideoDecoderConfig::kH264ProfileHigh; + case PP_VIDEOCODECPROFILE_H264_HIGH_10: + return cdm::VideoDecoderConfig::kH264ProfileHigh10; + case PP_VIDEOCODECPROFILE_H264_HIGH_422: + return cdm::VideoDecoderConfig::kH264ProfileHigh422; + case PP_VIDEOCODECPROFILE_H264_HIGH_444_PREDICTIVE: + return cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive; + default: + return cdm::VideoDecoderConfig::kUnknownVideoCodecProfile; + } +} + +cdm::VideoFormat PpDecryptedFrameFormatToCdmVideoFormat( + PP_DecryptedFrameFormat format) { + switch (format) { + case PP_DECRYPTEDFRAMEFORMAT_YV12: + return cdm::kYv12; + case PP_DECRYPTEDFRAMEFORMAT_I420: + return cdm::kI420; + default: + return cdm::kUnknownVideoFormat; + } +} + +cdm::StreamType PpDecryptorStreamTypeToCdmStreamType( + PP_DecryptorStreamType stream_type) { + switch (stream_type) { + case PP_DECRYPTORSTREAMTYPE_AUDIO: + return cdm::kStreamTypeAudio; + case PP_DECRYPTORSTREAMTYPE_VIDEO: + return cdm::kStreamTypeVideo; + } + + PP_NOTREACHED(); + return cdm::kStreamTypeVideo; +} + +} // namespace + +namespace media { + +// cdm::Buffer implementation that provides access to memory owned by a +// pp::Buffer_Dev. +// This class holds a reference to the Buffer_Dev throughout its lifetime. +// TODO(xhwang): Find a better name. It's confusing to have PpbBuffer, +// pp::Buffer_Dev and PPB_Buffer_Dev. +class PpbBuffer : public cdm::Buffer { + public: + static PpbBuffer* Create(const pp::Buffer_Dev& buffer, uint32_t buffer_id) { + PP_DCHECK(buffer.data()); + PP_DCHECK(buffer.size()); + PP_DCHECK(buffer_id); + return new PpbBuffer(buffer, buffer_id); + } + + // cdm::Buffer implementation. + virtual void Destroy() OVERRIDE { delete this; } + + virtual int32_t Capacity() const OVERRIDE { return buffer_.size(); } + + virtual uint8_t* Data() OVERRIDE { + return static_cast<uint8_t*>(buffer_.data()); + } + + virtual void SetSize(int32_t size) OVERRIDE { + PP_DCHECK(size >= 0); + PP_DCHECK(size < Capacity()); + if (size < 0 || size > Capacity()) { + size_ = 0; + return; + } + + size_ = size; + } + + virtual int32_t Size() const OVERRIDE { return size_; } + + pp::Buffer_Dev buffer_dev() const { return buffer_; } + + uint32_t buffer_id() const { return buffer_id_; } + + private: + PpbBuffer(pp::Buffer_Dev buffer, uint32_t buffer_id) + : buffer_(buffer), + buffer_id_(buffer_id), + size_(0) {} + virtual ~PpbBuffer() {} + + pp::Buffer_Dev buffer_; + uint32_t buffer_id_; + int32_t size_; + + DISALLOW_COPY_AND_ASSIGN(PpbBuffer); +}; + +class PpbBufferAllocator { + public: + explicit PpbBufferAllocator(pp::Instance* instance) + : instance_(instance), + next_buffer_id_(1) {} + ~PpbBufferAllocator() {} + + cdm::Buffer* Allocate(int32_t capacity); + + // Releases the buffer with |buffer_id|. A buffer can be recycled after + // it is released. + void Release(uint32_t buffer_id); + + private: + typedef std::map<uint32_t, pp::Buffer_Dev> AllocatedBufferMap; + typedef std::multimap<int, std::pair<uint32_t, pp::Buffer_Dev> > + FreeBufferMap; + + // Always pad new allocated buffer so that we don't need to reallocate + // buffers frequently if requested sizes fluctuate slightly. + static const int kBufferPadding = 512; + + // Maximum number of free buffers we can keep when allocating new buffers. + static const int kFreeLimit = 3; + + pp::Buffer_Dev AllocateNewBuffer(int capacity); + + pp::Instance* const instance_; + uint32_t next_buffer_id_; + AllocatedBufferMap allocated_buffers_; + FreeBufferMap free_buffers_; + + DISALLOW_COPY_AND_ASSIGN(PpbBufferAllocator); +}; + +cdm::Buffer* PpbBufferAllocator::Allocate(int32_t capacity) { + PP_DCHECK(IsMainThread()); + + if (capacity <= 0) + return NULL; + + pp::Buffer_Dev buffer; + uint32_t buffer_id = 0; + + // Reuse a buffer in the free list if there is one that fits |capacity|. + // Otherwise, create a new one. + FreeBufferMap::iterator found = free_buffers_.lower_bound(capacity); + if (found == free_buffers_.end()) { + // TODO(xhwang): Report statistics about how many new buffers are allocated. + buffer = AllocateNewBuffer(capacity); + if (buffer.is_null()) + return NULL; + buffer_id = next_buffer_id_++; + } else { + buffer = found->second.second; + buffer_id = found->second.first; + free_buffers_.erase(found); + } + + allocated_buffers_.insert(std::make_pair(buffer_id, buffer)); + + return PpbBuffer::Create(buffer, buffer_id); +} + +void PpbBufferAllocator::Release(uint32_t buffer_id) { + if (!buffer_id) + return; + + AllocatedBufferMap::iterator found = allocated_buffers_.find(buffer_id); + if (found == allocated_buffers_.end()) + return; + + pp::Buffer_Dev& buffer = found->second; + free_buffers_.insert( + std::make_pair(buffer.size(), std::make_pair(buffer_id, buffer))); + + allocated_buffers_.erase(found); +} + +pp::Buffer_Dev PpbBufferAllocator::AllocateNewBuffer(int32_t capacity) { + // Destroy the smallest buffer before allocating a new bigger buffer if the + // number of free buffers exceeds a limit. This mechanism helps avoid ending + // up with too many small buffers, which could happen if the size to be + // allocated keeps increasing. + if (free_buffers_.size() >= static_cast<uint32_t>(kFreeLimit)) + free_buffers_.erase(free_buffers_.begin()); + + // Creation of pp::Buffer_Dev is expensive! It involves synchronous IPC calls. + // That's why we try to avoid AllocateNewBuffer() as much as we can. + return pp::Buffer_Dev(instance_, capacity + kBufferPadding); +} + +class DecryptedBlockImpl : public cdm::DecryptedBlock { + public: + DecryptedBlockImpl() : buffer_(NULL), timestamp_(0) {} + virtual ~DecryptedBlockImpl() { if (buffer_) buffer_->Destroy(); } + + virtual void SetDecryptedBuffer(cdm::Buffer* buffer) OVERRIDE { + buffer_ = static_cast<PpbBuffer*>(buffer); + } + virtual cdm::Buffer* DecryptedBuffer() OVERRIDE { return buffer_; } + + virtual void SetTimestamp(int64_t timestamp) OVERRIDE { + timestamp_ = timestamp; + } + virtual int64_t Timestamp() const OVERRIDE { return timestamp_; } + + private: + PpbBuffer* buffer_; + int64_t timestamp_; + + DISALLOW_COPY_AND_ASSIGN(DecryptedBlockImpl); +}; + +class VideoFrameImpl : public cdm::VideoFrame { + public: + VideoFrameImpl(); + virtual ~VideoFrameImpl(); + + virtual void SetFormat(cdm::VideoFormat format) OVERRIDE { + format_ = format; + } + virtual cdm::VideoFormat Format() const OVERRIDE { return format_; } + + virtual void SetSize(cdm::Size size) OVERRIDE { size_ = size; } + virtual cdm::Size Size() const OVERRIDE { return size_; } + + virtual void SetFrameBuffer(cdm::Buffer* frame_buffer) OVERRIDE { + frame_buffer_ = static_cast<PpbBuffer*>(frame_buffer); + } + virtual cdm::Buffer* FrameBuffer() OVERRIDE { return frame_buffer_; } + + virtual void SetPlaneOffset(cdm::VideoFrame::VideoPlane plane, + int32_t offset) OVERRIDE { + PP_DCHECK(0 <= plane && plane < kMaxPlanes); + PP_DCHECK(offset >= 0); + plane_offsets_[plane] = offset; + } + virtual int32_t PlaneOffset(VideoPlane plane) OVERRIDE { + PP_DCHECK(0 <= plane && plane < kMaxPlanes); + return plane_offsets_[plane]; + } + + virtual void SetStride(VideoPlane plane, int32_t stride) OVERRIDE { + PP_DCHECK(0 <= plane && plane < kMaxPlanes); + strides_[plane] = stride; + } + virtual int32_t Stride(VideoPlane plane) OVERRIDE { + PP_DCHECK(0 <= plane && plane < kMaxPlanes); + return strides_[plane]; + } + + virtual void SetTimestamp(int64_t timestamp) OVERRIDE { + timestamp_ = timestamp; + } + virtual int64_t Timestamp() const OVERRIDE { return timestamp_; } + + private: + // The video buffer format. + cdm::VideoFormat format_; + + // Width and height of the video frame. + cdm::Size size_; + + // The video frame buffer. + PpbBuffer* frame_buffer_; + + // Array of data pointers to each plane in the video frame buffer. + int32_t plane_offsets_[kMaxPlanes]; + + // Array of strides for each plane, typically greater or equal to the width + // of the surface divided by the horizontal sampling period. Note that + // strides can be negative. + int32_t strides_[kMaxPlanes]; + + // Presentation timestamp in microseconds. + int64_t timestamp_; + + DISALLOW_COPY_AND_ASSIGN(VideoFrameImpl); +}; + +VideoFrameImpl::VideoFrameImpl() + : format_(cdm::kUnknownVideoFormat), + frame_buffer_(NULL), + timestamp_(0) { + for (int32_t i = 0; i < kMaxPlanes; ++i) { + plane_offsets_[i] = 0; + strides_[i] = 0; + } +} + +VideoFrameImpl::~VideoFrameImpl() { + if (frame_buffer_) + frame_buffer_->Destroy(); +} + +class AudioFramesImpl : public cdm::AudioFrames { + public: + AudioFramesImpl() : buffer_(NULL) {} + virtual ~AudioFramesImpl() { + if (buffer_) + buffer_->Destroy(); + } + + // AudioFrames implementation. + virtual void SetFrameBuffer(cdm::Buffer* buffer) OVERRIDE { + buffer_ = static_cast<PpbBuffer*>(buffer); + } + virtual cdm::Buffer* FrameBuffer() OVERRIDE { + return buffer_; + } + + private: + PpbBuffer* buffer_; + + DISALLOW_COPY_AND_ASSIGN(AudioFramesImpl); +}; + +// GetCdmHostFunc implementation. +void* GetCdmHost(int host_interface_version, void* user_data); + +// A wrapper class for abstracting away PPAPI interaction and threading for a +// Content Decryption Module (CDM). +class CdmWrapper : public pp::Instance, + public pp::ContentDecryptor_Private, + public cdm::Host { + public: + CdmWrapper(PP_Instance instance, pp::Module* module); + virtual ~CdmWrapper(); + + // pp::Instance implementation. + virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { + return true; + } + + // PPP_ContentDecryptor_Private implementation. + // Note: Results of calls to these methods must be reported through the + // PPB_ContentDecryptor_Private interface. + virtual void GenerateKeyRequest(const std::string& key_system, + const std::string& type, + pp::VarArrayBuffer init_data) OVERRIDE; + virtual void AddKey(const std::string& session_id, + pp::VarArrayBuffer key, + pp::VarArrayBuffer init_data) OVERRIDE; + virtual void CancelKeyRequest(const std::string& session_id) OVERRIDE; + virtual void Decrypt( + pp::Buffer_Dev encrypted_buffer, + const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE; + virtual void InitializeAudioDecoder( + const PP_AudioDecoderConfig& decoder_config, + pp::Buffer_Dev extra_data_buffer) OVERRIDE; + virtual void InitializeVideoDecoder( + const PP_VideoDecoderConfig& decoder_config, + pp::Buffer_Dev extra_data_buffer) OVERRIDE; + virtual void DeinitializeDecoder(PP_DecryptorStreamType decoder_type, + uint32_t request_id) OVERRIDE; + virtual void ResetDecoder(PP_DecryptorStreamType decoder_type, + uint32_t request_id) OVERRIDE; + virtual void DecryptAndDecode( + PP_DecryptorStreamType decoder_type, + pp::Buffer_Dev encrypted_buffer, + const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE; + + // cdm::Host implementation. + virtual cdm::Buffer* Allocate(int32_t capacity) OVERRIDE; + virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE; + virtual double GetCurrentWallTimeInSeconds() OVERRIDE; + virtual void SendKeyMessage( + const char* session_id, int32_t session_id_length, + const char* message, int32_t message_length, + const char* default_url, int32_t default_url_length) OVERRIDE; + virtual void SendKeyError(const char* session_id, + int32_t session_id_length, + cdm::MediaKeyError error_code, + uint32_t system_code) OVERRIDE; + virtual void GetPrivateData(int32_t* instance, + GetPrivateInterface* get_interface) OVERRIDE; + + private: + struct SessionInfo { + SessionInfo(const std::string& key_system_in, + const std::string& session_id_in) + : key_system(key_system_in), + session_id(session_id_in) {} + const std::string key_system; + const std::string session_id; + }; + + typedef linked_ptr<DecryptedBlockImpl> LinkedDecryptedBlock; + typedef linked_ptr<VideoFrameImpl> LinkedVideoFrame; + typedef linked_ptr<AudioFramesImpl> LinkedAudioFrames; + + bool CreateCdmInstance(const std::string& key_system); + + void SendUnknownKeyError(const std::string& key_system, + const std::string& session_id); + + void SendKeyAdded(const std::string& key_system, + const std::string& session_id); + + void SendKeyErrorInternal(const std::string& key_system, + const std::string& session_id, + cdm::MediaKeyError error_code, + uint32_t system_code); + + // <code>PPB_ContentDecryptor_Private</code> dispatchers. These are passed to + // <code>callback_factory_</code> to ensure that calls into + // <code>PPP_ContentDecryptor_Private</code> are asynchronous. + void KeyAdded(int32_t result, const SessionInfo& session_info); + void KeyMessage(int32_t result, + const SessionInfo& session_info, + const std::vector<uint8>& message, + const std::string& default_url); + void KeyError(int32_t result, + const SessionInfo& session_info, + cdm::MediaKeyError error_code, + uint32_t system_code); + void DeliverBlock(int32_t result, + const cdm::Status& status, + const LinkedDecryptedBlock& decrypted_block, + const PP_DecryptTrackingInfo& tracking_info); + void DecoderInitializeDone(int32_t result, + PP_DecryptorStreamType decoder_type, + uint32_t request_id, + bool success); + void DecoderDeinitializeDone(int32_t result, + PP_DecryptorStreamType decoder_type, + uint32_t request_id); + void DecoderResetDone(int32_t result, + PP_DecryptorStreamType decoder_type, + uint32_t request_id); + void DeliverFrame(int32_t result, + const cdm::Status& status, + const LinkedVideoFrame& video_frame, + const PP_DecryptTrackingInfo& tracking_info); + void DeliverSamples(int32_t result, + const cdm::Status& status, + const LinkedAudioFrames& audio_frames, + const PP_DecryptTrackingInfo& tracking_info); + + // Helper for SetTimer(). + void TimerExpired(int32_t result, void* context); + + bool IsValidVideoFrame(const LinkedVideoFrame& video_frame); + + PpbBufferAllocator allocator_; + pp::CompletionCallbackFactory<CdmWrapper> callback_factory_; + cdm::ContentDecryptionModule* cdm_; + std::string key_system_; + + DISALLOW_COPY_AND_ASSIGN(CdmWrapper); +}; + +CdmWrapper::CdmWrapper(PP_Instance instance, pp::Module* module) + : pp::Instance(instance), + pp::ContentDecryptor_Private(this), + allocator_(this), + cdm_(NULL) { + callback_factory_.Initialize(this); +} + +CdmWrapper::~CdmWrapper() { + if (cdm_) + cdm_->Destroy(); +} + +bool CdmWrapper::CreateCdmInstance(const std::string& key_system) { + PP_DCHECK(!cdm_); + cdm_ = static_cast<cdm::ContentDecryptionModule*>( + ::CreateCdmInstance(cdm::kCdmInterfaceVersion, + key_system.data(), key_system.size(), + GetCdmHost, this)); + + return (cdm_ != NULL); +} + +void CdmWrapper::GenerateKeyRequest(const std::string& key_system, + const std::string& type, + pp::VarArrayBuffer init_data) { + PP_DCHECK(!key_system.empty()); + PP_DCHECK(key_system_.empty() || key_system_ == key_system); + +#if defined(CHECK_DOCUMENT_URL) + PP_URLComponents_Dev url_components = {}; + pp::Var href = pp::URLUtil_Dev::Get()->GetDocumentURL( + pp::InstanceHandle(pp_instance()), &url_components); + PP_DCHECK(href.is_string()); + PP_DCHECK(!href.AsString().empty()); + PP_DCHECK(url_components.host.begin); + PP_DCHECK(0 < url_components.host.len); +#endif // defined(CHECK_DOCUMENT_URL) + + if (!cdm_) { + if (!CreateCdmInstance(key_system)) { + SendUnknownKeyError(key_system, std::string()); + return; + } + } + PP_DCHECK(cdm_); + + // Must be set here in case the CDM synchronously calls a cdm::Host method. + // Clear below on error. + // TODO(ddorwin): Set/clear key_system_ & cdm_ at same time; clear both on + // error below. + key_system_ = key_system; + cdm::Status status = cdm_->GenerateKeyRequest( + type.data(), type.size(), + static_cast<const uint8_t*>(init_data.Map()), + init_data.ByteLength()); + PP_DCHECK(status == cdm::kSuccess || status == cdm::kSessionError); + if (status != cdm::kSuccess) { + key_system_.clear(); // See comment above. + return; + } + + key_system_ = key_system; +} + +void CdmWrapper::AddKey(const std::string& session_id, + pp::VarArrayBuffer key, + pp::VarArrayBuffer init_data) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + if (!cdm_) { + SendUnknownKeyError(key_system_, session_id); + return; + } + + const uint8_t* key_ptr = static_cast<const uint8_t*>(key.Map()); + int key_size = key.ByteLength(); + const uint8_t* init_data_ptr = static_cast<const uint8_t*>(init_data.Map()); + int init_data_size = init_data.ByteLength(); + PP_DCHECK(!init_data_ptr == !init_data_size); + + if (!key_ptr || key_size <= 0) { + SendUnknownKeyError(key_system_, session_id); + return; + } + + cdm::Status status = cdm_->AddKey(session_id.data(), session_id.size(), + key_ptr, key_size, + init_data_ptr, init_data_size); + PP_DCHECK(status == cdm::kSuccess || status == cdm::kSessionError); + if (status != cdm::kSuccess) { + SendUnknownKeyError(key_system_, session_id); + return; + } + + SendKeyAdded(key_system_, session_id); +} + +void CdmWrapper::CancelKeyRequest(const std::string& session_id) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + if (!cdm_) { + SendUnknownKeyError(key_system_, session_id); + return; + } + + cdm::Status status = cdm_->CancelKeyRequest(session_id.data(), + session_id.size()); + PP_DCHECK(status == cdm::kSuccess || status == cdm::kSessionError); + if (status != cdm::kSuccess) + SendUnknownKeyError(key_system_, session_id); +} + +// Note: In the following decryption/decoding related functions, errors are NOT +// reported via KeyError, but are reported via corresponding PPB calls. + +void CdmWrapper::Decrypt(pp::Buffer_Dev encrypted_buffer, + const PP_EncryptedBlockInfo& encrypted_block_info) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + PP_DCHECK(!encrypted_buffer.is_null()); + + // Release a buffer that the caller indicated it is finished with. + allocator_.Release(encrypted_block_info.tracking_info.buffer_id); + + cdm::Status status = cdm::kDecryptError; + LinkedDecryptedBlock decrypted_block(new DecryptedBlockImpl()); + + if (cdm_) { + cdm::InputBuffer input_buffer; + std::vector<cdm::SubsampleEntry> subsamples; + ConfigureInputBuffer(encrypted_buffer, encrypted_block_info, &subsamples, + &input_buffer); + status = cdm_->Decrypt(input_buffer, decrypted_block.get()); + PP_DCHECK(status != cdm::kSuccess || + (decrypted_block->DecryptedBuffer() && + decrypted_block->DecryptedBuffer()->Size())); + } + + CallOnMain(callback_factory_.NewCallback( + &CdmWrapper::DeliverBlock, + status, + decrypted_block, + encrypted_block_info.tracking_info)); +} + +void CdmWrapper::InitializeAudioDecoder( + const PP_AudioDecoderConfig& decoder_config, + pp::Buffer_Dev extra_data_buffer) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + + cdm::Status status = cdm::kSessionError; + if (cdm_) { + cdm::AudioDecoderConfig cdm_decoder_config; + cdm_decoder_config.codec = + PpAudioCodecToCdmAudioCodec(decoder_config.codec); + cdm_decoder_config.channel_count = decoder_config.channel_count; + cdm_decoder_config.bits_per_channel = decoder_config.bits_per_channel; + cdm_decoder_config.samples_per_second = decoder_config.samples_per_second; + cdm_decoder_config.extra_data = + static_cast<uint8_t*>(extra_data_buffer.data()); + cdm_decoder_config.extra_data_size = + static_cast<int32_t>(extra_data_buffer.size()); + status = cdm_->InitializeAudioDecoder(cdm_decoder_config); + } + + CallOnMain(callback_factory_.NewCallback( + &CdmWrapper::DecoderInitializeDone, + PP_DECRYPTORSTREAMTYPE_AUDIO, + decoder_config.request_id, + status == cdm::kSuccess)); +} + +void CdmWrapper::InitializeVideoDecoder( + const PP_VideoDecoderConfig& decoder_config, + pp::Buffer_Dev extra_data_buffer) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + + cdm::Status status = cdm::kSessionError; + if (cdm_) { + cdm::VideoDecoderConfig cdm_decoder_config; + cdm_decoder_config.codec = + PpVideoCodecToCdmVideoCodec(decoder_config.codec); + cdm_decoder_config.profile = + PpVCProfileToCdmVCProfile(decoder_config.profile); + cdm_decoder_config.format = + PpDecryptedFrameFormatToCdmVideoFormat(decoder_config.format); + cdm_decoder_config.coded_size.width = decoder_config.width; + cdm_decoder_config.coded_size.height = decoder_config.height; + cdm_decoder_config.extra_data = + static_cast<uint8_t*>(extra_data_buffer.data()); + cdm_decoder_config.extra_data_size = + static_cast<int32_t>(extra_data_buffer.size()); + status = cdm_->InitializeVideoDecoder(cdm_decoder_config); + } + + CallOnMain(callback_factory_.NewCallback( + &CdmWrapper::DecoderInitializeDone, + PP_DECRYPTORSTREAMTYPE_VIDEO, + decoder_config.request_id, + status == cdm::kSuccess)); +} + +void CdmWrapper::DeinitializeDecoder(PP_DecryptorStreamType decoder_type, + uint32_t request_id) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + if (cdm_) { + cdm_->DeinitializeDecoder( + PpDecryptorStreamTypeToCdmStreamType(decoder_type)); + } + + CallOnMain(callback_factory_.NewCallback( + &CdmWrapper::DecoderDeinitializeDone, + decoder_type, + request_id)); +} + +void CdmWrapper::ResetDecoder(PP_DecryptorStreamType decoder_type, + uint32_t request_id) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + if (cdm_) + cdm_->ResetDecoder(PpDecryptorStreamTypeToCdmStreamType(decoder_type)); + + CallOnMain(callback_factory_.NewCallback(&CdmWrapper::DecoderResetDone, + decoder_type, + request_id)); +} + +void CdmWrapper::DecryptAndDecode( + PP_DecryptorStreamType decoder_type, + pp::Buffer_Dev encrypted_buffer, + const PP_EncryptedBlockInfo& encrypted_block_info) { + PP_DCHECK(cdm_); // GenerateKeyRequest() should have succeeded. + + // Release a buffer that the caller indicated it is finished with. + allocator_.Release(encrypted_block_info.tracking_info.buffer_id); + + cdm::InputBuffer input_buffer; + std::vector<cdm::SubsampleEntry> subsamples; + if (cdm_ && !encrypted_buffer.is_null()) { + ConfigureInputBuffer(encrypted_buffer, + encrypted_block_info, + &subsamples, + &input_buffer); + } + + cdm::Status status = cdm::kDecodeError; + + switch (decoder_type) { + case PP_DECRYPTORSTREAMTYPE_VIDEO: { + LinkedVideoFrame video_frame(new VideoFrameImpl()); + if (cdm_) + status = cdm_->DecryptAndDecodeFrame(input_buffer, video_frame.get()); + CallOnMain(callback_factory_.NewCallback( + &CdmWrapper::DeliverFrame, + status, + video_frame, + encrypted_block_info.tracking_info)); + return; + } + + case PP_DECRYPTORSTREAMTYPE_AUDIO: { + LinkedAudioFrames audio_frames(new AudioFramesImpl()); + if (cdm_) { + status = cdm_->DecryptAndDecodeSamples(input_buffer, + audio_frames.get()); + } + CallOnMain(callback_factory_.NewCallback( + &CdmWrapper::DeliverSamples, + status, + audio_frames, + encrypted_block_info.tracking_info)); + return; + } + + default: + PP_NOTREACHED(); + return; + } +} + +cdm::Buffer* CdmWrapper::Allocate(int32_t capacity) { + return allocator_.Allocate(capacity); +} + +void CdmWrapper::SetTimer(int64_t delay_ms, void* context) { + // NOTE: doesn't really need to run on the main thread; could just as well run + // on a helper thread if |cdm_| were thread-friendly and care was taken. We + // only use CallOnMainThread() here to get delayed-execution behavior. + pp::Module::Get()->core()->CallOnMainThread( + delay_ms, + callback_factory_.NewCallback(&CdmWrapper::TimerExpired, context), + PP_OK); +} + +void CdmWrapper::TimerExpired(int32_t result, void* context) { + PP_DCHECK(result == PP_OK); + cdm_->TimerExpired(context); +} + +double CdmWrapper::GetCurrentWallTimeInSeconds() { + return pp::Module::Get()->core()->GetTime(); +} + +void CdmWrapper::SendKeyMessage( + const char* session_id, int32_t session_id_length, + const char* message, int32_t message_length, + const char* default_url, int32_t default_url_length) { + PP_DCHECK(!key_system_.empty()); + PostOnMain(callback_factory_.NewCallback( + &CdmWrapper::KeyMessage, + SessionInfo(key_system_, + std::string(session_id, session_id_length)), + std::vector<uint8>(message, message + message_length), + std::string(default_url, default_url_length))); +} + +void CdmWrapper::SendKeyError(const char* session_id, + int32_t session_id_length, + cdm::MediaKeyError error_code, + uint32_t system_code) { + SendKeyErrorInternal(key_system_, + std::string(session_id, session_id_length), + error_code, + system_code); +} + +void CdmWrapper::GetPrivateData(int32_t* instance, + cdm::Host::GetPrivateInterface* get_interface) { + *instance = pp_instance(); + *get_interface = pp::Module::Get()->get_browser_interface(); +} + +void CdmWrapper::SendUnknownKeyError(const std::string& key_system, + const std::string& session_id) { + SendKeyErrorInternal(key_system, session_id, cdm::kUnknownError, 0); +} + +void CdmWrapper::SendKeyAdded(const std::string& key_system, + const std::string& session_id) { + PostOnMain(callback_factory_.NewCallback( + &CdmWrapper::KeyAdded, + SessionInfo(key_system_, session_id))); +} + +void CdmWrapper::SendKeyErrorInternal(const std::string& key_system, + const std::string& session_id, + cdm::MediaKeyError error_code, + uint32_t system_code) { + PP_DCHECK(!key_system.empty()); + PostOnMain(callback_factory_.NewCallback(&CdmWrapper::KeyError, + SessionInfo(key_system_, session_id), + error_code, + system_code)); +} + +void CdmWrapper::KeyAdded(int32_t result, const SessionInfo& session_info) { + PP_DCHECK(result == PP_OK); + PP_DCHECK(!session_info.key_system.empty()); + pp::ContentDecryptor_Private::KeyAdded(session_info.key_system, + session_info.session_id); +} + +void CdmWrapper::KeyMessage(int32_t result, + const SessionInfo& session_info, + const std::vector<uint8>& message, + const std::string& default_url) { + PP_DCHECK(result == PP_OK); + PP_DCHECK(!session_info.key_system.empty()); + + pp::VarArrayBuffer message_array_buffer(message.size()); + if (message.size() > 0) { + memcpy(message_array_buffer.Map(), message.data(), message.size()); + } + + pp::ContentDecryptor_Private::KeyMessage( + session_info.key_system, session_info.session_id, + message_array_buffer, default_url); +} + +void CdmWrapper::KeyError(int32_t result, + const SessionInfo& session_info, + cdm::MediaKeyError error_code, + uint32_t system_code) { + PP_DCHECK(result == PP_OK); + PP_DCHECK(!session_info.key_system.empty()); + pp::ContentDecryptor_Private::KeyError( + session_info.key_system, session_info.session_id, + error_code, system_code); +} + +void CdmWrapper::DeliverBlock(int32_t result, + const cdm::Status& status, + const LinkedDecryptedBlock& decrypted_block, + const PP_DecryptTrackingInfo& tracking_info) { + PP_DCHECK(result == PP_OK); + PP_DecryptedBlockInfo decrypted_block_info; + decrypted_block_info.tracking_info = tracking_info; + decrypted_block_info.tracking_info.timestamp = decrypted_block->Timestamp(); + decrypted_block_info.tracking_info.buffer_id = 0; + decrypted_block_info.data_size = 0; + decrypted_block_info.result = CdmStatusToPpDecryptResult(status); + + pp::Buffer_Dev buffer; + + if (decrypted_block_info.result == PP_DECRYPTRESULT_SUCCESS) { + PP_DCHECK(decrypted_block.get() && decrypted_block->DecryptedBuffer()); + if (!decrypted_block.get() || !decrypted_block->DecryptedBuffer()) { + PP_NOTREACHED(); + decrypted_block_info.result = PP_DECRYPTRESULT_DECRYPT_ERROR; + } else { + PpbBuffer* ppb_buffer = + static_cast<PpbBuffer*>(decrypted_block->DecryptedBuffer()); + buffer = ppb_buffer->buffer_dev(); + decrypted_block_info.tracking_info.buffer_id = ppb_buffer->buffer_id(); + decrypted_block_info.data_size = ppb_buffer->Size(); + } + } + + pp::ContentDecryptor_Private::DeliverBlock(buffer, decrypted_block_info); +} + +void CdmWrapper::DecoderInitializeDone(int32_t result, + PP_DecryptorStreamType decoder_type, + uint32_t request_id, + bool success) { + PP_DCHECK(result == PP_OK); + pp::ContentDecryptor_Private::DecoderInitializeDone(decoder_type, + request_id, + success); +} + +void CdmWrapper::DecoderDeinitializeDone(int32_t result, + PP_DecryptorStreamType decoder_type, + uint32_t request_id) { + pp::ContentDecryptor_Private::DecoderDeinitializeDone(decoder_type, + request_id); +} + +void CdmWrapper::DecoderResetDone(int32_t result, + PP_DecryptorStreamType decoder_type, + uint32_t request_id) { + pp::ContentDecryptor_Private::DecoderResetDone(decoder_type, request_id); +} + +void CdmWrapper::DeliverFrame( + int32_t result, + const cdm::Status& status, + const LinkedVideoFrame& video_frame, + const PP_DecryptTrackingInfo& tracking_info) { + PP_DCHECK(result == PP_OK); + PP_DecryptedFrameInfo decrypted_frame_info; + decrypted_frame_info.tracking_info.request_id = tracking_info.request_id; + decrypted_frame_info.tracking_info.buffer_id = 0; + decrypted_frame_info.result = CdmStatusToPpDecryptResult(status); + + pp::Buffer_Dev buffer; + + if (decrypted_frame_info.result == PP_DECRYPTRESULT_SUCCESS) { + if (!IsValidVideoFrame(video_frame)) { + PP_NOTREACHED(); + decrypted_frame_info.result = PP_DECRYPTRESULT_DECODE_ERROR; + } else { + PpbBuffer* ppb_buffer = + static_cast<PpbBuffer*>(video_frame->FrameBuffer()); + + buffer = ppb_buffer->buffer_dev(); + + decrypted_frame_info.tracking_info.timestamp = video_frame->Timestamp(); + decrypted_frame_info.tracking_info.buffer_id = ppb_buffer->buffer_id(); + decrypted_frame_info.format = + CdmVideoFormatToPpDecryptedFrameFormat(video_frame->Format()); + decrypted_frame_info.width = video_frame->Size().width; + decrypted_frame_info.height = video_frame->Size().height; + decrypted_frame_info.plane_offsets[PP_DECRYPTEDFRAMEPLANES_Y] = + video_frame->PlaneOffset(cdm::VideoFrame::kYPlane); + decrypted_frame_info.plane_offsets[PP_DECRYPTEDFRAMEPLANES_U] = + video_frame->PlaneOffset(cdm::VideoFrame::kUPlane); + decrypted_frame_info.plane_offsets[PP_DECRYPTEDFRAMEPLANES_V] = + video_frame->PlaneOffset(cdm::VideoFrame::kVPlane); + decrypted_frame_info.strides[PP_DECRYPTEDFRAMEPLANES_Y] = + video_frame->Stride(cdm::VideoFrame::kYPlane); + decrypted_frame_info.strides[PP_DECRYPTEDFRAMEPLANES_U] = + video_frame->Stride(cdm::VideoFrame::kUPlane); + decrypted_frame_info.strides[PP_DECRYPTEDFRAMEPLANES_V] = + video_frame->Stride(cdm::VideoFrame::kVPlane); + } + } + pp::ContentDecryptor_Private::DeliverFrame(buffer, decrypted_frame_info); +} + +void CdmWrapper::DeliverSamples(int32_t result, + const cdm::Status& status, + const LinkedAudioFrames& audio_frames, + const PP_DecryptTrackingInfo& tracking_info) { + PP_DCHECK(result == PP_OK); + + PP_DecryptedBlockInfo decrypted_block_info; + decrypted_block_info.tracking_info = tracking_info; + decrypted_block_info.tracking_info.timestamp = 0; + decrypted_block_info.tracking_info.buffer_id = 0; + decrypted_block_info.data_size = 0; + decrypted_block_info.result = CdmStatusToPpDecryptResult(status); + + pp::Buffer_Dev buffer; + + if (decrypted_block_info.result == PP_DECRYPTRESULT_SUCCESS) { + PP_DCHECK(audio_frames.get() && audio_frames->FrameBuffer()); + if (!audio_frames.get() || !audio_frames->FrameBuffer()) { + PP_NOTREACHED(); + decrypted_block_info.result = PP_DECRYPTRESULT_DECRYPT_ERROR; + } else { + PpbBuffer* ppb_buffer = + static_cast<PpbBuffer*>(audio_frames->FrameBuffer()); + buffer = ppb_buffer->buffer_dev(); + decrypted_block_info.tracking_info.buffer_id = ppb_buffer->buffer_id(); + decrypted_block_info.data_size = ppb_buffer->Size(); + } + } + + pp::ContentDecryptor_Private::DeliverSamples(buffer, decrypted_block_info); +} + +bool CdmWrapper::IsValidVideoFrame(const LinkedVideoFrame& video_frame) { + if (!video_frame.get() || + !video_frame->FrameBuffer() || + (video_frame->Format() != cdm::kI420 && + video_frame->Format() != cdm::kYv12)) { + return false; + } + + PpbBuffer* ppb_buffer = static_cast<PpbBuffer*>(video_frame->FrameBuffer()); + + for (int i = 0; i < cdm::VideoFrame::kMaxPlanes; ++i) { + int plane_height = (i == cdm::VideoFrame::kYPlane) ? + video_frame->Size().height : (video_frame->Size().height + 1) / 2; + cdm::VideoFrame::VideoPlane plane = + static_cast<cdm::VideoFrame::VideoPlane>(i); + if (ppb_buffer->Size() < video_frame->PlaneOffset(plane) + + plane_height * video_frame->Stride(plane)) { + return false; + } + } + + return true; +} + +void* GetCdmHost(int host_interface_version, void* user_data) { + if (!host_interface_version || !user_data) + return NULL; + + if (host_interface_version != cdm::kHostInterfaceVersion) + return NULL; + + CdmWrapper* cdm_wrapper = static_cast<CdmWrapper*>(user_data); + return static_cast<cdm::Host*>(cdm_wrapper); +} + +// This object is the global object representing this plugin library as long +// as it is loaded. +class CdmWrapperModule : public pp::Module { + public: + CdmWrapperModule() : pp::Module() { + // This function blocks the renderer thread (PluginInstance::Initialize()). + // Move this call to other places if this may be a concern in the future. + INITIALIZE_CDM_MODULE(); + } + virtual ~CdmWrapperModule() { + DeinitializeCdmModule(); + } + + virtual pp::Instance* CreateInstance(PP_Instance instance) { + return new CdmWrapper(instance, this); + } +}; + +} // namespace media + +namespace pp { + +// Factory function for your specialization of the Module object. +Module* CreateModule() { + return new media::CdmWrapperModule(); +} + +} // namespace pp diff --git a/media/cdm/ppapi/clear_key_cdm.cc b/media/cdm/ppapi/clear_key_cdm.cc new file mode 100644 index 0000000..cd6a61f --- /dev/null +++ b/media/cdm/ppapi/clear_key_cdm.cc @@ -0,0 +1,558 @@ +// Copyright 2013 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/cdm/ppapi/clear_key_cdm.h" + +#include <algorithm> +#include <sstream> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "media/base/decoder_buffer.h" +#include "media/base/decrypt_config.h" +#include "media/cdm/ppapi/cdm_video_decoder.h" + +#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) +#include "base/basictypes.h" +static const int64 kNoTimestamp = kint64min; +#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER + +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) +#include "base/at_exit.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "media/base/media.h" +#include "media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h" +#include "media/cdm/ppapi/ffmpeg_cdm_video_decoder.h" + +// Include FFmpeg avformat.h for av_register_all(). +extern "C" { +// Temporarily disable possible loss of data warning. +MSVC_PUSH_DISABLE_WARNING(4244); +#include <libavformat/avformat.h> +MSVC_POP_WARNING(); +} // extern "C" + +// TODO(tomfinegan): When COMPONENT_BUILD is not defined an AtExitManager must +// exist before the call to InitializeFFmpegLibraries(). This should no longer +// be required after http://crbug.com/91970 because we'll be able to get rid of +// InitializeFFmpegLibraries(). +#if !defined COMPONENT_BUILD +static base::AtExitManager g_at_exit_manager; +#endif + +// TODO(tomfinegan): InitializeFFmpegLibraries() and |g_cdm_module_initialized| +// are required for running in the sandbox, and should no longer be required +// after http://crbug.com/91970 is fixed. +static bool InitializeFFmpegLibraries() { + base::FilePath file_path; + CHECK(PathService::Get(base::DIR_MODULE, &file_path)); + CHECK(media::InitializeMediaLibrary(file_path)); + return true; +} + +static bool g_ffmpeg_lib_initialized = InitializeFFmpegLibraries(); +#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER + +static const char kClearKeyCdmVersion[] = "0.1.0.1"; +static const char kExternalClearKey[] = "org.chromium.externalclearkey"; +static const int64 kSecondsPerMinute = 60; +static const int64 kMsPerSecond = 1000; +static const int64 kInitialTimerDelayMs = 200; +static const int64 kMaxTimerDelayMs = 1 * kSecondsPerMinute * kMsPerSecond; +// Heart beat message header. If a key message starts with |kHeartBeatHeader|, +// it's a heart beat message. Otherwise, it's a key request. +static const char kHeartBeatHeader[] = "HEARTBEAT"; + +// Copies |input_buffer| into a media::DecoderBuffer. If the |input_buffer| is +// empty, an empty (end-of-stream) media::DecoderBuffer is returned. +static scoped_refptr<media::DecoderBuffer> CopyDecoderBufferFrom( + const cdm::InputBuffer& input_buffer) { + if (!input_buffer.data) { + DCHECK_EQ(input_buffer.data_size, 0); + return media::DecoderBuffer::CreateEOSBuffer(); + } + + // TODO(tomfinegan): Get rid of this copy. + scoped_refptr<media::DecoderBuffer> output_buffer = + media::DecoderBuffer::CopyFrom(input_buffer.data, input_buffer.data_size); + + std::vector<media::SubsampleEntry> subsamples; + for (int32_t i = 0; i < input_buffer.num_subsamples; ++i) { + media::SubsampleEntry subsample; + subsample.clear_bytes = input_buffer.subsamples[i].clear_bytes; + subsample.cypher_bytes = input_buffer.subsamples[i].cipher_bytes; + subsamples.push_back(subsample); + } + + scoped_ptr<media::DecryptConfig> decrypt_config(new media::DecryptConfig( + std::string(reinterpret_cast<const char*>(input_buffer.key_id), + input_buffer.key_id_size), + std::string(reinterpret_cast<const char*>(input_buffer.iv), + input_buffer.iv_size), + input_buffer.data_offset, + subsamples)); + + output_buffer->set_decrypt_config(decrypt_config.Pass()); + output_buffer->set_timestamp( + base::TimeDelta::FromMicroseconds(input_buffer.timestamp)); + + return output_buffer; +} + +template<typename Type> +class ScopedResetter { + public: + explicit ScopedResetter(Type* object) : object_(object) {} + ~ScopedResetter() { object_->Reset(); } + + private: + Type* const object_; +}; + +void INITIALIZE_CDM_MODULE() { +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + DVLOG(2) << "FFmpeg libraries initialized: " << g_ffmpeg_lib_initialized; + av_register_all(); +#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER +} + +void DeinitializeCdmModule() { +} + +void* CreateCdmInstance( + int cdm_interface_version, + const char* key_system, int key_system_size, + GetCdmHostFunc get_cdm_host_func, void* user_data) { + DVLOG(1) << "CreateCdmInstance()"; + + if (cdm_interface_version != cdm::kCdmInterfaceVersion) + return NULL; + + cdm::Host* host = static_cast<cdm::Host*>( + get_cdm_host_func(cdm::kHostInterfaceVersion, user_data)); + if (!host) + return NULL; + + return static_cast<cdm::ContentDecryptionModule*>( + new media::ClearKeyCdm(host)); +} + +const char* GetCdmVersion() { + return kClearKeyCdmVersion; +} + +namespace media { + +ClearKeyCdm::Client::Client() : status_(kKeyError) {} + +ClearKeyCdm::Client::~Client() {} + +void ClearKeyCdm::Client::Reset() { + status_ = kKeyError; + session_id_.clear(); + key_message_.clear(); + default_url_.clear(); +} + +void ClearKeyCdm::Client::KeyAdded(const std::string& session_id) { + status_ = kKeyAdded; + session_id_ = session_id; +} + +void ClearKeyCdm::Client::KeyError(const std::string& session_id, + media::MediaKeys::KeyError error_code, + int system_code) { + status_ = kKeyError; + session_id_ = session_id; +} + +void ClearKeyCdm::Client::KeyMessage(const std::string& session_id, + const std::vector<uint8>& message, + const std::string& default_url) { + status_ = kKeyMessage; + session_id_ = session_id; + key_message_ = message; + default_url_ = default_url; +} + +ClearKeyCdm::ClearKeyCdm(cdm::Host* host) + : decryptor_(base::Bind(&Client::KeyAdded, base::Unretained(&client_)), + base::Bind(&Client::KeyError, base::Unretained(&client_)), + base::Bind(&Client::KeyMessage, base::Unretained(&client_))), + host_(host), + timer_delay_ms_(kInitialTimerDelayMs), + timer_set_(false) { +#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + channel_count_ = 0; + bits_per_channel_ = 0; + samples_per_second_ = 0; + output_timestamp_base_in_microseconds_ = kNoTimestamp; + total_samples_generated_ = 0; +#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER +} + +ClearKeyCdm::~ClearKeyCdm() {} + +cdm::Status ClearKeyCdm::GenerateKeyRequest(const char* type, int type_size, + const uint8_t* init_data, + int init_data_size) { + DVLOG(1) << "GenerateKeyRequest()"; + base::AutoLock auto_lock(client_lock_); + ScopedResetter<Client> auto_resetter(&client_); + decryptor_.GenerateKeyRequest(std::string(type, type_size), + init_data, init_data_size); + + if (client_.status() != Client::kKeyMessage) { + host_->SendKeyError(NULL, 0, cdm::kUnknownError, 0); + return cdm::kSessionError; + } + + host_->SendKeyMessage( + client_.session_id().data(), client_.session_id().size(), + reinterpret_cast<const char*>(&client_.key_message()[0]), + client_.key_message().size(), + client_.default_url().data(), client_.default_url().size()); + + // Only save the latest session ID for heartbeat messages. + heartbeat_session_id_ = client_.session_id(); + + return cdm::kSuccess; +} + +cdm::Status ClearKeyCdm::AddKey(const char* session_id, + int session_id_size, + const uint8_t* key, + int key_size, + const uint8_t* key_id, + int key_id_size) { + DVLOG(1) << "AddKey()"; + base::AutoLock auto_lock(client_lock_); + ScopedResetter<Client> auto_resetter(&client_); + decryptor_.AddKey(key, key_size, key_id, key_id_size, + std::string(session_id, session_id_size)); + + if (client_.status() != Client::kKeyAdded) + return cdm::kSessionError; + + if (!timer_set_) { + ScheduleNextHeartBeat(); + timer_set_ = true; + } + + return cdm::kSuccess; +} + +cdm::Status ClearKeyCdm::CancelKeyRequest(const char* session_id, + int session_id_size) { + DVLOG(1) << "CancelKeyRequest()"; + base::AutoLock auto_lock(client_lock_); + ScopedResetter<Client> auto_resetter(&client_); + decryptor_.CancelKeyRequest(std::string(session_id, session_id_size)); + return cdm::kSuccess; +} + +void ClearKeyCdm::TimerExpired(void* context) { + std::string heartbeat_message; + if (!next_heartbeat_message_.empty() && + context == &next_heartbeat_message_[0]) { + heartbeat_message = next_heartbeat_message_; + } else { + heartbeat_message = "ERROR: Invalid timer context found!"; + } + + // This URL is only used for testing the code path for defaultURL. + // There is no service at this URL, so applications should ignore it. + const char url[] = "http://test.externalclearkey.chromium.org"; + + host_->SendKeyMessage( + heartbeat_session_id_.data(), heartbeat_session_id_.size(), + heartbeat_message.data(), heartbeat_message.size(), + url, arraysize(url) - 1); + + ScheduleNextHeartBeat(); +} + +static void CopyDecryptResults( + media::Decryptor::Status* status_copy, + scoped_refptr<media::DecoderBuffer>* buffer_copy, + media::Decryptor::Status status, + const scoped_refptr<media::DecoderBuffer>& buffer) { + *status_copy = status; + *buffer_copy = buffer; +} + +cdm::Status ClearKeyCdm::Decrypt( + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + DVLOG(1) << "Decrypt()"; + DCHECK(encrypted_buffer.data); + + scoped_refptr<media::DecoderBuffer> buffer; + cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer); + + if (status != cdm::kSuccess) + return status; + + DCHECK(buffer->data()); + decrypted_block->SetDecryptedBuffer( + host_->Allocate(buffer->data_size())); + memcpy(reinterpret_cast<void*>(decrypted_block->DecryptedBuffer()->Data()), + buffer->data(), + buffer->data_size()); + decrypted_block->DecryptedBuffer()->SetSize(buffer->data_size()); + decrypted_block->SetTimestamp(buffer->timestamp().InMicroseconds()); + + return cdm::kSuccess; +} + +cdm::Status ClearKeyCdm::InitializeAudioDecoder( + const cdm::AudioDecoderConfig& audio_decoder_config) { +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + if (!audio_decoder_) + audio_decoder_.reset(new media::FFmpegCdmAudioDecoder(host_)); + + if (!audio_decoder_->Initialize(audio_decoder_config)) + return cdm::kSessionError; + + return cdm::kSuccess; +#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + channel_count_ = audio_decoder_config.channel_count; + bits_per_channel_ = audio_decoder_config.bits_per_channel; + samples_per_second_ = audio_decoder_config.samples_per_second; + return cdm::kSuccess; +#else + NOTIMPLEMENTED(); + return cdm::kSessionError; +#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER +} + +cdm::Status ClearKeyCdm::InitializeVideoDecoder( + const cdm::VideoDecoderConfig& video_decoder_config) { + if (video_decoder_ && video_decoder_->is_initialized()) { + DCHECK(!video_decoder_->is_initialized()); + return cdm::kSessionError; + } + + // Any uninitialized decoder will be replaced. + video_decoder_ = CreateVideoDecoder(host_, video_decoder_config); + if (!video_decoder_) + return cdm::kSessionError; + + return cdm::kSuccess; +} + +void ClearKeyCdm::ResetDecoder(cdm::StreamType decoder_type) { + DVLOG(1) << "ResetDecoder()"; +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + switch (decoder_type) { + case cdm::kStreamTypeVideo: + video_decoder_->Reset(); + break; + case cdm::kStreamTypeAudio: + audio_decoder_->Reset(); + break; + default: + NOTREACHED() << "ResetDecoder(): invalid cdm::StreamType"; + } +#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + if (decoder_type == cdm::kStreamTypeAudio) { + output_timestamp_base_in_microseconds_ = kNoTimestamp; + total_samples_generated_ = 0; + } +#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER +} + +void ClearKeyCdm::DeinitializeDecoder(cdm::StreamType decoder_type) { + DVLOG(1) << "DeinitializeDecoder()"; + switch (decoder_type) { + case cdm::kStreamTypeVideo: + video_decoder_->Deinitialize(); + break; + case cdm::kStreamTypeAudio: +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + audio_decoder_->Deinitialize(); +#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + output_timestamp_base_in_microseconds_ = kNoTimestamp; + total_samples_generated_ = 0; +#endif + break; + default: + NOTREACHED() << "DeinitializeDecoder(): invalid cdm::StreamType"; + } +} + +cdm::Status ClearKeyCdm::DecryptAndDecodeFrame( + const cdm::InputBuffer& encrypted_buffer, + cdm::VideoFrame* decoded_frame) { + DVLOG(1) << "DecryptAndDecodeFrame()"; + TRACE_EVENT0("eme", "ClearKeyCdm::DecryptAndDecodeFrame"); + + scoped_refptr<media::DecoderBuffer> buffer; + cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer); + + if (status != cdm::kSuccess) + return status; + + const uint8_t* data = NULL; + int32_t size = 0; + int64_t timestamp = 0; + if (!buffer->end_of_stream()) { + data = buffer->data(); + size = buffer->data_size(); + timestamp = encrypted_buffer.timestamp; + } + + return video_decoder_->DecodeFrame(data, size, timestamp, decoded_frame); +} + +cdm::Status ClearKeyCdm::DecryptAndDecodeSamples( + const cdm::InputBuffer& encrypted_buffer, + cdm::AudioFrames* audio_frames) { + DVLOG(1) << "DecryptAndDecodeSamples()"; + + scoped_refptr<media::DecoderBuffer> buffer; + cdm::Status status = DecryptToMediaDecoderBuffer(encrypted_buffer, &buffer); + + if (status != cdm::kSuccess) + return status; + +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + const uint8_t* data = NULL; + int32_t size = 0; + int64_t timestamp = 0; + if (!buffer->end_of_stream()) { + data = buffer->data(); + size = buffer->data_size(); + timestamp = encrypted_buffer.timestamp; + } + + return audio_decoder_->DecodeBuffer(data, size, timestamp, audio_frames); +#elif defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + int64 timestamp_in_microseconds = kNoTimestamp; + if (!buffer->end_of_stream()) { + timestamp_in_microseconds = buffer->GetTimestamp().InMicroseconds(); + DCHECK(timestamp_in_microseconds != kNoTimestamp); + } + return GenerateFakeAudioFrames(timestamp_in_microseconds, audio_frames); +#else + return cdm::kSuccess; +#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER +} + +void ClearKeyCdm::Destroy() { + DVLOG(1) << "Destroy()"; + delete this; +} + +void ClearKeyCdm::ScheduleNextHeartBeat() { + // Prepare the next heartbeat message and set timer. + std::ostringstream msg_stream; + msg_stream << kHeartBeatHeader << " from ClearKey CDM set at time " + << host_->GetCurrentWallTimeInSeconds() << "."; + next_heartbeat_message_ = msg_stream.str(); + + host_->SetTimer(timer_delay_ms_, &next_heartbeat_message_[0]); + + // Use a smaller timer delay at start-up to facilitate testing. Increase the + // timer delay up to a limit to avoid message spam. + if (timer_delay_ms_ < kMaxTimerDelayMs) + timer_delay_ms_ = std::min(2 * timer_delay_ms_, kMaxTimerDelayMs); +} + +cdm::Status ClearKeyCdm::DecryptToMediaDecoderBuffer( + const cdm::InputBuffer& encrypted_buffer, + scoped_refptr<media::DecoderBuffer>* decrypted_buffer) { + DCHECK(decrypted_buffer); + scoped_refptr<media::DecoderBuffer> buffer = + CopyDecoderBufferFrom(encrypted_buffer); + + if (buffer->end_of_stream()) { + *decrypted_buffer = buffer; + return cdm::kSuccess; + } + + // Callback is called synchronously, so we can use variables on the stack. + media::Decryptor::Status status = media::Decryptor::kError; + // The AesDecryptor does not care what the stream type is. Pass kVideo + // for both audio and video decryption. + decryptor_.Decrypt( + media::Decryptor::kVideo, + buffer, + base::Bind(&CopyDecryptResults, &status, decrypted_buffer)); + + if (status == media::Decryptor::kError) + return cdm::kDecryptError; + + if (status == media::Decryptor::kNoKey) + return cdm::kNoKey; + + DCHECK_EQ(status, media::Decryptor::kSuccess); + return cdm::kSuccess; +} + +#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) +int64 ClearKeyCdm::CurrentTimeStampInMicroseconds() const { + return output_timestamp_base_in_microseconds_ + + base::Time::kMicrosecondsPerSecond * + total_samples_generated_ / samples_per_second_; +} + +int ClearKeyCdm::GenerateFakeAudioFramesFromDuration( + int64 duration_in_microseconds, + cdm::AudioFrames* audio_frames) const { + int64 samples_to_generate = static_cast<double>(samples_per_second_) * + duration_in_microseconds / base::Time::kMicrosecondsPerSecond + 0.5; + if (samples_to_generate <= 0) + return 0; + + int64 bytes_per_sample = channel_count_ * bits_per_channel_ / 8; + // |frame_size| must be a multiple of |bytes_per_sample|. + int64 frame_size = bytes_per_sample * samples_to_generate; + + int64 timestamp = CurrentTimeStampInMicroseconds(); + + const int kHeaderSize = sizeof(timestamp) + sizeof(frame_size); + audio_frames->SetFrameBuffer(host_->Allocate(kHeaderSize + frame_size)); + uint8_t* data = audio_frames->FrameBuffer()->Data(); + + memcpy(data, ×tamp, sizeof(timestamp)); + data += sizeof(timestamp); + memcpy(data, &frame_size, sizeof(frame_size)); + data += sizeof(frame_size); + // You won't hear anything because we have all zeros here. But the video + // should play just fine! + memset(data, 0, frame_size); + + audio_frames->FrameBuffer()->SetSize(kHeaderSize + frame_size); + + return samples_to_generate; +} + +cdm::Status ClearKeyCdm::GenerateFakeAudioFrames( + int64 timestamp_in_microseconds, + cdm::AudioFrames* audio_frames) { + if (timestamp_in_microseconds == kNoTimestamp) + return cdm::kNeedMoreData; + + // Return kNeedMoreData for the first frame because duration is unknown. + if (output_timestamp_base_in_microseconds_ == kNoTimestamp) { + output_timestamp_base_in_microseconds_ = timestamp_in_microseconds; + return cdm::kNeedMoreData; + } + + int samples_generated = GenerateFakeAudioFramesFromDuration( + timestamp_in_microseconds - CurrentTimeStampInMicroseconds(), + audio_frames); + total_samples_generated_ += samples_generated; + + return samples_generated == 0 ? cdm::kNeedMoreData : cdm::kSuccess; +} +#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER + +} // namespace media diff --git a/media/cdm/ppapi/clear_key_cdm.h b/media/cdm/ppapi/clear_key_cdm.h new file mode 100644 index 0000000..67637eb --- /dev/null +++ b/media/cdm/ppapi/clear_key_cdm.h @@ -0,0 +1,166 @@ +// Copyright 2013 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_CDM_PPAPI_CLEAR_KEY_CDM_H_ +#define MEDIA_CDM_PPAPI_CLEAR_KEY_CDM_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" +#include "media/cdm/aes_decryptor.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" + +// Enable this to use the fake decoder for testing. +// TODO(tomfinegan): Move fake audio decoder into a separate class. +#if 0 +#define CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER +#endif + +namespace media { +class CdmVideoDecoder; +class DecoderBuffer; +class FFmpegCdmAudioDecoder; + +// Clear key implementation of the cdm::ContentDecryptionModule interface. +class ClearKeyCdm : public cdm::ContentDecryptionModule { + public: + explicit ClearKeyCdm(cdm::Host* host); + virtual ~ClearKeyCdm(); + + // ContentDecryptionModule implementation. + virtual cdm::Status GenerateKeyRequest( + const char* type, int type_size, + const uint8_t* init_data, int init_data_size) OVERRIDE; + virtual cdm::Status AddKey(const char* session_id, int session_id_size, + const uint8_t* key, int key_size, + const uint8_t* key_id, int key_id_size) OVERRIDE; + virtual cdm::Status CancelKeyRequest(const char* session_id, + int session_id_size) OVERRIDE; + virtual void TimerExpired(void* context) OVERRIDE; + virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) OVERRIDE; + virtual cdm::Status InitializeAudioDecoder( + const cdm::AudioDecoderConfig& audio_decoder_config) OVERRIDE; + virtual cdm::Status InitializeVideoDecoder( + const cdm::VideoDecoderConfig& video_decoder_config) OVERRIDE; + virtual void DeinitializeDecoder(cdm::StreamType decoder_type) OVERRIDE; + virtual void ResetDecoder(cdm::StreamType decoder_type) OVERRIDE; + virtual cdm::Status DecryptAndDecodeFrame( + const cdm::InputBuffer& encrypted_buffer, + cdm::VideoFrame* video_frame) OVERRIDE; + virtual cdm::Status DecryptAndDecodeSamples( + const cdm::InputBuffer& encrypted_buffer, + cdm::AudioFrames* audio_frames) OVERRIDE; + virtual void Destroy() OVERRIDE; + + private: + // TODO(xhwang): After we removed DecryptorClient. We probably can also remove + // this Client class as well. Investigate this possibility. + class Client { + public: + enum Status { + kKeyAdded, + kKeyError, + kKeyMessage + }; + + Client(); + virtual ~Client(); + + Status status() { return status_; } + const std::string& session_id() { return session_id_; } + const std::vector<uint8>& key_message() { return key_message_; } + const std::string& default_url() { return default_url_; } + + // Resets the Client to a clean state. + void Reset(); + + void KeyAdded(const std::string& session_id); + void KeyError(const std::string& session_id, + MediaKeys::KeyError error_code, + int system_code); + void KeyMessage(const std::string& session_id, + const std::vector<uint8>& message, + const std::string& default_url); + + private: + Status status_; + std::string session_id_; + std::vector<uint8> key_message_; + std::string default_url_; + }; + + // Prepares next heartbeat message and sets a timer for it. + void ScheduleNextHeartBeat(); + + // Decrypts the |encrypted_buffer| and puts the result in |decrypted_buffer|. + // Returns cdm::kSuccess if decryption succeeded. The decrypted result is + // put in |decrypted_buffer|. If |encrypted_buffer| is empty, the + // |decrypted_buffer| is set to an empty (EOS) buffer. + // Returns cdm::kNoKey if no decryption key was available. In this case + // |decrypted_buffer| should be ignored by the caller. + // Returns cdm::kDecryptError if any decryption error occurred. In this case + // |decrypted_buffer| should be ignored by the caller. + cdm::Status DecryptToMediaDecoderBuffer( + const cdm::InputBuffer& encrypted_buffer, + scoped_refptr<DecoderBuffer>* decrypted_buffer); + +#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + int64 CurrentTimeStampInMicroseconds() const; + + // Generates fake video frames with |duration_in_microseconds|. + // Returns the number of samples generated in the |audio_frames|. + int GenerateFakeAudioFramesFromDuration(int64 duration_in_microseconds, + cdm::AudioFrames* audio_frames) const; + + // Generates fake video frames given |input_timestamp|. + // Returns cdm::kSuccess if any audio frame is successfully generated. + cdm::Status GenerateFakeAudioFrames(int64 timestamp_in_microseconds, + cdm::AudioFrames* audio_frames); +#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER + + Client client_; + AesDecryptor decryptor_; + + // Protects the |client_| from being accessed by the |decryptor_| + // simultaneously. + base::Lock client_lock_; + + cdm::Host* host_; + + std::string heartbeat_session_id_; + std::string next_heartbeat_message_; + + // Timer delay in milliseconds for the next host_->SetTimer() call. + int64 timer_delay_ms_; + + // Indicates whether a timer has been set to prevent multiple timers from + // running. + bool timer_set_; + +#if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) + int channel_count_; + int bits_per_channel_; + int samples_per_second_; + int64 output_timestamp_base_in_microseconds_; + int total_samples_generated_; +#endif // CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER + +#if defined(CLEAR_KEY_CDM_USE_FFMPEG_DECODER) + scoped_ptr<FFmpegCdmAudioDecoder> audio_decoder_; +#endif // CLEAR_KEY_CDM_USE_FFMPEG_DECODER + + scoped_ptr<CdmVideoDecoder> video_decoder_; + + DISALLOW_COPY_AND_ASSIGN(ClearKeyCdm); +}; + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_CLEAR_KEY_CDM_H_ diff --git a/media/cdm/ppapi/fake_cdm_video_decoder.cc b/media/cdm/ppapi/fake_cdm_video_decoder.cc new file mode 100644 index 0000000..b23e5a7 --- /dev/null +++ b/media/cdm/ppapi/fake_cdm_video_decoder.cc @@ -0,0 +1,91 @@ +// Copyright 2013 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/cdm/ppapi/fake_cdm_video_decoder.h" + +#include "base/logging.h" + +namespace media { + +FakeCdmVideoDecoder::FakeCdmVideoDecoder(cdm::Host* host) + : is_initialized_(false), + host_(host) { +} + +FakeCdmVideoDecoder::~FakeCdmVideoDecoder() { + Deinitialize(); +} + +bool FakeCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) { + DVLOG(1) << "Initialize()"; + + video_size_ = config.coded_size; + is_initialized_ = true; + return true; +} + +void FakeCdmVideoDecoder::Deinitialize() { + DVLOG(1) << "Deinitialize()"; + is_initialized_ = false; +} + +void FakeCdmVideoDecoder::Reset() { + DVLOG(1) << "Reset()"; +} + +// Creates a YV12 video frame. +cdm::Status FakeCdmVideoDecoder::DecodeFrame(const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) { + DVLOG(1) << "DecodeFrame()"; + + // The fake decoder does not buffer any frames internally. So if the input is + // empty (EOS), just return kNeedMoreData. + if (!decoded_frame) + return cdm::kNeedMoreData; + + // Choose non-zero alignment and padding on purpose for testing. + const int kAlignment = 8; + const int kPadding = 16; + const int kPlanePadding = 128; + + int width = video_size_.width; + int height = video_size_.height; + DCHECK_EQ(width % 2, 0); + DCHECK_EQ(height % 2, 0); + + int y_stride = (width + kAlignment - 1) / kAlignment * kAlignment + kPadding; + int uv_stride = + (width / 2 + kAlignment - 1) / kAlignment * kAlignment + kPadding; + int y_rows = height; + int uv_rows = height / 2; + int y_offset = 0; + int v_offset = y_stride * y_rows + kPlanePadding; + int u_offset = v_offset + uv_stride * uv_rows + kPlanePadding; + int frame_size = u_offset + uv_stride * uv_rows + kPlanePadding; + + decoded_frame->SetFrameBuffer(host_->Allocate(frame_size)); + decoded_frame->FrameBuffer()->SetSize(frame_size); + + decoded_frame->SetFormat(cdm::kYv12); + decoded_frame->SetSize(video_size_); + decoded_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, y_offset); + decoded_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane, v_offset); + decoded_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, u_offset); + decoded_frame->SetStride(cdm::VideoFrame::kYPlane, y_stride); + decoded_frame->SetStride(cdm::VideoFrame::kVPlane, uv_stride); + decoded_frame->SetStride(cdm::VideoFrame::kUPlane, uv_stride); + decoded_frame->SetTimestamp(timestamp); + + static unsigned char color = 0; + color += 10; + + memset(reinterpret_cast<void*>(decoded_frame->FrameBuffer()->Data()), + color, frame_size); + + return cdm::kSuccess; +} + +} // namespace media diff --git a/media/cdm/ppapi/fake_cdm_video_decoder.h b/media/cdm/ppapi/fake_cdm_video_decoder.h new file mode 100644 index 0000000..05b16ad --- /dev/null +++ b/media/cdm/ppapi/fake_cdm_video_decoder.h @@ -0,0 +1,41 @@ +// Copyright 2013 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_CDM_PPAPI_FAKE_CDM_VIDEO_DECODER_H_ +#define MEDIA_CDM_PPAPI_FAKE_CDM_VIDEO_DECODER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" +#include "media/cdm/ppapi/cdm_video_decoder.h" + +namespace media { + +class FakeCdmVideoDecoder : public CdmVideoDecoder { + public: + explicit FakeCdmVideoDecoder(cdm::Host* host); + virtual ~FakeCdmVideoDecoder(); + + // CdmVideoDecoder implementation. + virtual bool Initialize(const cdm::VideoDecoderConfig& config) OVERRIDE; + virtual void Deinitialize() OVERRIDE; + virtual void Reset() OVERRIDE; + virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) OVERRIDE; + virtual bool is_initialized() const OVERRIDE { return is_initialized_; } + + private: + bool is_initialized_; + cdm::Size video_size_; + + cdm::Host* const host_; + + DISALLOW_COPY_AND_ASSIGN(FakeCdmVideoDecoder); +}; + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_FAKE_CDM_VIDEO_DECODER_H_ diff --git a/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc new file mode 100644 index 0000000..ed619b2 --- /dev/null +++ b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.cc @@ -0,0 +1,412 @@ +// Copyright 2013 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/cdm/ppapi/ffmpeg_cdm_audio_decoder.h" + +#include <algorithm> + +#include "base/logging.h" +#include "media/base/audio_bus.h" +#include "media/base/audio_timestamp_helper.h" +#include "media/base/buffers.h" +#include "media/base/data_buffer.h" +#include "media/base/limits.h" + +// Include FFmpeg header files. +extern "C" { +// Temporarily disable possible loss of data warning. +MSVC_PUSH_DISABLE_WARNING(4244); +#include <libavcodec/avcodec.h> +MSVC_POP_WARNING(); +} // extern "C" + +namespace media { + +// Maximum number of channels with defined layout in src/media. +static const int kMaxChannels = 8; + +static AVCodecID CdmAudioCodecToCodecID( + cdm::AudioDecoderConfig::AudioCodec audio_codec) { + switch (audio_codec) { + case cdm::AudioDecoderConfig::kCodecVorbis: + return AV_CODEC_ID_VORBIS; + case cdm::AudioDecoderConfig::kCodecAac: + return AV_CODEC_ID_AAC; + case cdm::AudioDecoderConfig::kUnknownAudioCodec: + default: + NOTREACHED() << "Unsupported cdm::AudioCodec: " << audio_codec; + return AV_CODEC_ID_NONE; + } +} + +static void CdmAudioDecoderConfigToAVCodecContext( + const cdm::AudioDecoderConfig& config, + AVCodecContext* codec_context) { + codec_context->codec_type = AVMEDIA_TYPE_AUDIO; + codec_context->codec_id = CdmAudioCodecToCodecID(config.codec); + + switch (config.bits_per_channel) { + case 8: + codec_context->sample_fmt = AV_SAMPLE_FMT_U8; + break; + case 16: + codec_context->sample_fmt = AV_SAMPLE_FMT_S16; + break; + case 32: + codec_context->sample_fmt = AV_SAMPLE_FMT_S32; + break; + default: + DVLOG(1) << "CdmAudioDecoderConfigToAVCodecContext() Unsupported bits " + "per channel: " << config.bits_per_channel; + codec_context->sample_fmt = AV_SAMPLE_FMT_NONE; + } + + codec_context->channels = config.channel_count; + codec_context->sample_rate = config.samples_per_second; + + if (config.extra_data) { + codec_context->extradata_size = config.extra_data_size; + codec_context->extradata = reinterpret_cast<uint8_t*>( + av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE)); + memcpy(codec_context->extradata, config.extra_data, + config.extra_data_size); + memset(codec_context->extradata + config.extra_data_size, '\0', + FF_INPUT_BUFFER_PADDING_SIZE); + } else { + codec_context->extradata = NULL; + codec_context->extradata_size = 0; + } +} + +FFmpegCdmAudioDecoder::FFmpegCdmAudioDecoder(cdm::Host* host) + : is_initialized_(false), + host_(host), + codec_context_(NULL), + av_frame_(NULL), + bits_per_channel_(0), + samples_per_second_(0), + channels_(0), + av_sample_format_(0), + bytes_per_frame_(0), + last_input_timestamp_(kNoTimestamp()), + output_bytes_to_drop_(0) { +} + +FFmpegCdmAudioDecoder::~FFmpegCdmAudioDecoder() { + ReleaseFFmpegResources(); +} + +bool FFmpegCdmAudioDecoder::Initialize(const cdm::AudioDecoderConfig& config) { + DVLOG(1) << "Initialize()"; + + if (!IsValidConfig(config)) { + LOG(ERROR) << "Initialize(): invalid audio decoder configuration."; + return false; + } + + if (is_initialized_) { + LOG(ERROR) << "Initialize(): Already initialized."; + return false; + } + + // Initialize AVCodecContext structure. + codec_context_ = avcodec_alloc_context3(NULL); + CdmAudioDecoderConfigToAVCodecContext(config, codec_context_); + + // MP3 decodes to S16P which we don't support, tell it to use S16 instead. + if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P) + codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16; + + AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); + if (!codec || avcodec_open2(codec_context_, codec, NULL) < 0) { + DLOG(ERROR) << "Could not initialize audio decoder: " + << codec_context_->codec_id; + return false; + } + + // Ensure avcodec_open2() respected our format request. + if (codec_context_->sample_fmt == AV_SAMPLE_FMT_S16P) { + DLOG(ERROR) << "Unable to configure a supported sample format: " + << codec_context_->sample_fmt; + return false; + } + + // Some codecs will only output float data, so we need to convert to integer + // before returning the decoded buffer. + if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLTP || + codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT) { + // Preallocate the AudioBus for float conversions. We can treat interleaved + // float data as a single planar channel since our output is expected in an + // interleaved format anyways. + int channels = codec_context_->channels; + if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT) + channels = 1; + converter_bus_ = AudioBus::CreateWrapper(channels); + } + + // Success! + av_frame_ = avcodec_alloc_frame(); + bits_per_channel_ = config.bits_per_channel; + samples_per_second_ = config.samples_per_second; + bytes_per_frame_ = codec_context_->channels * bits_per_channel_ / 8; + output_timestamp_helper_.reset( + new AudioTimestampHelper(config.samples_per_second)); + serialized_audio_frames_.reserve(bytes_per_frame_ * samples_per_second_); + is_initialized_ = true; + + // Store initial values to guard against midstream configuration changes. + channels_ = codec_context_->channels; + av_sample_format_ = codec_context_->sample_fmt; + + return true; +} + +void FFmpegCdmAudioDecoder::Deinitialize() { + DVLOG(1) << "Deinitialize()"; + ReleaseFFmpegResources(); + is_initialized_ = false; + ResetTimestampState(); +} + +void FFmpegCdmAudioDecoder::Reset() { + DVLOG(1) << "Reset()"; + avcodec_flush_buffers(codec_context_); + ResetTimestampState(); +} + +// static +bool FFmpegCdmAudioDecoder::IsValidConfig( + const cdm::AudioDecoderConfig& config) { + return config.codec != cdm::AudioDecoderConfig::kUnknownAudioCodec && + config.channel_count > 0 && + config.channel_count <= kMaxChannels && + config.bits_per_channel > 0 && + config.bits_per_channel <= limits::kMaxBitsPerSample && + config.samples_per_second > 0 && + config.samples_per_second <= limits::kMaxSampleRate; +} + +cdm::Status FFmpegCdmAudioDecoder::DecodeBuffer( + const uint8_t* compressed_buffer, + int32_t compressed_buffer_size, + int64_t input_timestamp, + cdm::AudioFrames* decoded_frames) { + DVLOG(1) << "DecodeBuffer()"; + const bool is_end_of_stream = !compressed_buffer; + base::TimeDelta timestamp = + base::TimeDelta::FromMicroseconds(input_timestamp); + + bool is_vorbis = codec_context_->codec_id == AV_CODEC_ID_VORBIS; + if (!is_end_of_stream) { + if (last_input_timestamp_ == kNoTimestamp()) { + if (is_vorbis && timestamp < base::TimeDelta()) { + // Dropping frames for negative timestamps as outlined in section A.2 + // in the Vorbis spec. http://xiph.org/vorbis/doc/Vorbis_I_spec.html + int frames_to_drop = floor( + 0.5 + -timestamp.InSecondsF() * samples_per_second_); + output_bytes_to_drop_ = bytes_per_frame_ * frames_to_drop; + } else { + last_input_timestamp_ = timestamp; + } + } else if (timestamp != kNoTimestamp()) { + if (timestamp < last_input_timestamp_) { + base::TimeDelta diff = timestamp - last_input_timestamp_; + DVLOG(1) << "Input timestamps are not monotonically increasing! " + << " ts " << timestamp.InMicroseconds() << " us" + << " diff " << diff.InMicroseconds() << " us"; + return cdm::kDecodeError; + } + + last_input_timestamp_ = timestamp; + } + } + + AVPacket packet; + av_init_packet(&packet); + packet.data = const_cast<uint8_t*>(compressed_buffer); + packet.size = compressed_buffer_size; + + // Each audio packet may contain several frames, so we must call the decoder + // until we've exhausted the packet. Regardless of the packet size we always + // want to hand it to the decoder at least once, otherwise we would end up + // skipping end of stream packets since they have a size of zero. + do { + // Reset frame to default values. + avcodec_get_frame_defaults(av_frame_); + + int frame_decoded = 0; + int result = avcodec_decode_audio4( + codec_context_, av_frame_, &frame_decoded, &packet); + + if (result < 0) { + DCHECK(!is_end_of_stream) + << "End of stream buffer produced an error! " + << "This is quite possibly a bug in the audio decoder not handling " + << "end of stream AVPackets correctly."; + + DLOG(ERROR) + << "Error decoding an audio frame with timestamp: " + << timestamp.InMicroseconds() << " us, duration: " + << timestamp.InMicroseconds() << " us, packet size: " + << compressed_buffer_size << " bytes"; + + return cdm::kDecodeError; + } + + // Update packet size and data pointer in case we need to call the decoder + // with the remaining bytes from this packet. + packet.size -= result; + packet.data += result; + + if (output_timestamp_helper_->base_timestamp() == kNoTimestamp() && + !is_end_of_stream) { + DCHECK(timestamp != kNoTimestamp()); + if (output_bytes_to_drop_ > 0) { + // Currently Vorbis is the only codec that causes us to drop samples. + // If we have to drop samples it always means the timeline starts at 0. + DCHECK_EQ(codec_context_->codec_id, AV_CODEC_ID_VORBIS); + output_timestamp_helper_->SetBaseTimestamp(base::TimeDelta()); + } else { + output_timestamp_helper_->SetBaseTimestamp(timestamp); + } + } + + int decoded_audio_size = 0; + if (frame_decoded) { + if (av_frame_->sample_rate != samples_per_second_ || + av_frame_->channels != channels_ || + av_frame_->format != av_sample_format_) { + DLOG(ERROR) << "Unsupported midstream configuration change!" + << " Sample Rate: " << av_frame_->sample_rate << " vs " + << samples_per_second_ + << ", Channels: " << av_frame_->channels << " vs " + << channels_ + << ", Sample Format: " << av_frame_->format << " vs " + << av_sample_format_; + return cdm::kDecodeError; + } + + decoded_audio_size = av_samples_get_buffer_size( + NULL, codec_context_->channels, av_frame_->nb_samples, + codec_context_->sample_fmt, 1); + // If we're decoding into float, adjust audio size. + if (converter_bus_ && bits_per_channel_ / 8 != sizeof(float)) { + DCHECK(codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT || + codec_context_->sample_fmt == AV_SAMPLE_FMT_FLTP); + decoded_audio_size *= + static_cast<float>(bits_per_channel_ / 8) / sizeof(float); + } + } + + int start_sample = 0; + if (decoded_audio_size > 0 && output_bytes_to_drop_ > 0) { + DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0) + << "Decoder didn't output full frames"; + + int dropped_size = std::min(decoded_audio_size, output_bytes_to_drop_); + start_sample = dropped_size / bytes_per_frame_; + decoded_audio_size -= dropped_size; + output_bytes_to_drop_ -= dropped_size; + } + + scoped_refptr<DataBuffer> output; + if (decoded_audio_size > 0) { + DCHECK_EQ(decoded_audio_size % bytes_per_frame_, 0) + << "Decoder didn't output full frames"; + + // Convert float data using an AudioBus. + if (converter_bus_) { + // Setup the AudioBus as a wrapper of the AVFrame data and then use + // AudioBus::ToInterleaved() to convert the data as necessary. + int skip_frames = start_sample; + int total_frames = av_frame_->nb_samples; + int frames_to_interleave = decoded_audio_size / bytes_per_frame_; + if (codec_context_->sample_fmt == AV_SAMPLE_FMT_FLT) { + DCHECK_EQ(converter_bus_->channels(), 1); + total_frames *= codec_context_->channels; + skip_frames *= codec_context_->channels; + frames_to_interleave *= codec_context_->channels; + } + + converter_bus_->set_frames(total_frames); + for (int i = 0; i < converter_bus_->channels(); ++i) { + converter_bus_->SetChannelData(i, reinterpret_cast<float*>( + av_frame_->extended_data[i])); + } + + output = new DataBuffer(decoded_audio_size); + output->set_data_size(decoded_audio_size); + + DCHECK_EQ(frames_to_interleave, converter_bus_->frames() - skip_frames); + converter_bus_->ToInterleavedPartial( + skip_frames, frames_to_interleave, bits_per_channel_ / 8, + output->writable_data()); + } else { + output = DataBuffer::CopyFrom( + av_frame_->extended_data[0] + start_sample * bytes_per_frame_, + decoded_audio_size); + } + + base::TimeDelta output_timestamp = + output_timestamp_helper_->GetTimestamp(); + output_timestamp_helper_->AddFrames(decoded_audio_size / + bytes_per_frame_); + + // Serialize the audio samples into |serialized_audio_frames_|. + SerializeInt64(output_timestamp.InMicroseconds()); + SerializeInt64(output->data_size()); + serialized_audio_frames_.insert( + serialized_audio_frames_.end(), + output->data(), + output->data() + output->data_size()); + } + } while (packet.size > 0); + + if (!serialized_audio_frames_.empty()) { + decoded_frames->SetFrameBuffer( + host_->Allocate(serialized_audio_frames_.size())); + if (!decoded_frames->FrameBuffer()) { + LOG(ERROR) << "DecodeBuffer() cdm::Host::Allocate failed."; + return cdm::kDecodeError; + } + memcpy(decoded_frames->FrameBuffer()->Data(), + &serialized_audio_frames_[0], + serialized_audio_frames_.size()); + decoded_frames->FrameBuffer()->SetSize(serialized_audio_frames_.size()); + serialized_audio_frames_.clear(); + + return cdm::kSuccess; + } + + return cdm::kNeedMoreData; +} + +void FFmpegCdmAudioDecoder::ResetTimestampState() { + output_timestamp_helper_->SetBaseTimestamp(kNoTimestamp()); + last_input_timestamp_ = kNoTimestamp(); + output_bytes_to_drop_ = 0; +} + +void FFmpegCdmAudioDecoder::ReleaseFFmpegResources() { + DVLOG(1) << "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; + } +} + +void FFmpegCdmAudioDecoder::SerializeInt64(int64 value) { + int previous_size = serialized_audio_frames_.size(); + serialized_audio_frames_.resize(previous_size + sizeof(value)); + memcpy(&serialized_audio_frames_[0] + previous_size, &value, sizeof(value)); +} + +} // namespace media diff --git a/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h new file mode 100644 index 0000000..1b4fb8f --- /dev/null +++ b/media/cdm/ppapi/ffmpeg_cdm_audio_decoder.h @@ -0,0 +1,99 @@ +// Copyright 2013 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_CDM_PPAPI_FFMPEG_CDM_AUDIO_DECODER_H_ +#define MEDIA_CDM_PPAPI_FFMPEG_CDM_AUDIO_DECODER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" + +struct AVCodecContext; +struct AVFrame; + +namespace media { +class AudioBus; +class AudioTimestampHelper; +} + +namespace media { + +// TODO(xhwang): This class is partially cloned from FFmpegAudioDecoder. When +// FFmpegAudioDecoder is updated, it's a pain to keep this class in sync with +// FFmpegAudioDecoder. We need a long term sustainable solution for this. See +// http://crbug.com/169203 +class FFmpegCdmAudioDecoder { + public: + explicit FFmpegCdmAudioDecoder(cdm::Host* host); + ~FFmpegCdmAudioDecoder(); + bool Initialize(const cdm::AudioDecoderConfig& config); + void Deinitialize(); + void Reset(); + + // Returns true when |config| is a valid audio decoder configuration. + static bool IsValidConfig(const cdm::AudioDecoderConfig& config); + + // Decodes |compressed_buffer|. Returns |cdm::kSuccess| after storing + // output in |decoded_frames| when output is available. Returns + // |cdm::kNeedMoreData| when |compressed_frame| does not produce output. + // Returns |cdm::kDecodeError| when decoding fails. + // + // A null |compressed_buffer| will attempt to flush the decoder of any + // remaining frames. |compressed_buffer_size| and |timestamp| are ignored. + cdm::Status DecodeBuffer(const uint8_t* compressed_buffer, + int32_t compressed_buffer_size, + int64_t timestamp, + cdm::AudioFrames* decoded_frames); + + private: + void ResetTimestampState(); + void ReleaseFFmpegResources(); + + base::TimeDelta GetNextOutputTimestamp() const; + + void SerializeInt64(int64_t value); + + bool is_initialized_; + + cdm::Host* const host_; + + // FFmpeg structures owned by this object. + AVCodecContext* codec_context_; + AVFrame* av_frame_; + + // Audio format. + int bits_per_channel_; + int samples_per_second_; + int channels_; + + // AVSampleFormat initially requested; not Chrome's SampleFormat. + int av_sample_format_; + + // Used for computing output timestamps. + scoped_ptr<AudioTimestampHelper> output_timestamp_helper_; + int bytes_per_frame_; + base::TimeDelta last_input_timestamp_; + + // We may need to convert the audio data coming out of FFmpeg from planar + // float to integer. + scoped_ptr<AudioBus> converter_bus_; + + // Number of output sample bytes to drop before generating output buffers. + // This is required for handling negative timestamps when decoding Vorbis + // audio, for example. + int output_bytes_to_drop_; + + typedef std::vector<uint8_t> SerializedAudioFrames; + SerializedAudioFrames serialized_audio_frames_; + + DISALLOW_COPY_AND_ASSIGN(FFmpegCdmAudioDecoder); +}; + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_FFMPEG_CDM_AUDIO_DECODER_H_ diff --git a/media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc new file mode 100644 index 0000000..9a2439d --- /dev/null +++ b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.cc @@ -0,0 +1,344 @@ +// Copyright 2013 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/cdm/ppapi/ffmpeg_cdm_video_decoder.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/buffers.h" +#include "media/base/limits.h" + +// Include FFmpeg header files. +extern "C" { +// Temporarily disable possible loss of data warning. +MSVC_PUSH_DISABLE_WARNING(4244); +#include <libavcodec/avcodec.h> +MSVC_POP_WARNING(); +} // extern "C" + +namespace media { + +static const int kDecodeThreads = 1; + +static cdm::VideoFormat PixelFormatToCdmVideoFormat(PixelFormat pixel_format) { + switch (pixel_format) { + case PIX_FMT_YUV420P: + return cdm::kYv12; + default: + DVLOG(1) << "Unsupported PixelFormat: " << pixel_format; + } + return cdm::kUnknownVideoFormat; +} + +static PixelFormat CdmVideoFormatToPixelFormat(cdm::VideoFormat video_format) { + switch (video_format) { + case cdm::kYv12: + case cdm::kI420: + return PIX_FMT_YUV420P; + case cdm::kUnknownVideoFormat: + default: + DVLOG(1) << "Unsupported cdm::VideoFormat: " << video_format; + } + return PIX_FMT_NONE; +} + +static AVCodecID CdmVideoCodecToCodecID( + cdm::VideoDecoderConfig::VideoCodec video_codec) { + switch (video_codec) { + case cdm::VideoDecoderConfig::kCodecVp8: + return AV_CODEC_ID_VP8; + case cdm::VideoDecoderConfig::kCodecH264: + return AV_CODEC_ID_H264; + case cdm::VideoDecoderConfig::kUnknownVideoCodec: + default: + NOTREACHED() << "Unsupported cdm::VideoCodec: " << video_codec; + return AV_CODEC_ID_NONE; + } +} + +static int CdmVideoCodecProfileToProfileID( + cdm::VideoDecoderConfig::VideoCodecProfile profile) { + switch (profile) { + case cdm::VideoDecoderConfig::kVp8ProfileMain: + return FF_PROFILE_UNKNOWN; // VP8 does not define an FFmpeg profile. + case cdm::VideoDecoderConfig::kH264ProfileBaseline: + return FF_PROFILE_H264_BASELINE; + case cdm::VideoDecoderConfig::kH264ProfileMain: + return FF_PROFILE_H264_MAIN; + case cdm::VideoDecoderConfig::kH264ProfileExtended: + return FF_PROFILE_H264_EXTENDED; + case cdm::VideoDecoderConfig::kH264ProfileHigh: + return FF_PROFILE_H264_HIGH; + case cdm::VideoDecoderConfig::kH264ProfileHigh10: + return FF_PROFILE_H264_HIGH_10; + case cdm::VideoDecoderConfig::kH264ProfileHigh422: + return FF_PROFILE_H264_HIGH_422; + case cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive: + return FF_PROFILE_H264_HIGH_444_PREDICTIVE; + case cdm::VideoDecoderConfig::kUnknownVideoCodecProfile: + default: + NOTREACHED() << "Unknown cdm::VideoCodecProfile: " << profile; + return FF_PROFILE_UNKNOWN; + } +} + +static void CdmVideoDecoderConfigToAVCodecContext( + const cdm::VideoDecoderConfig& config, + AVCodecContext* codec_context) { + codec_context->codec_type = AVMEDIA_TYPE_VIDEO; + codec_context->codec_id = CdmVideoCodecToCodecID(config.codec); + codec_context->profile = CdmVideoCodecProfileToProfileID(config.profile); + codec_context->coded_width = config.coded_size.width; + codec_context->coded_height = config.coded_size.height; + codec_context->pix_fmt = CdmVideoFormatToPixelFormat(config.format); + + if (config.extra_data) { + codec_context->extradata_size = config.extra_data_size; + codec_context->extradata = reinterpret_cast<uint8_t*>( + av_malloc(config.extra_data_size + FF_INPUT_BUFFER_PADDING_SIZE)); + memcpy(codec_context->extradata, config.extra_data, + config.extra_data_size); + memset(codec_context->extradata + config.extra_data_size, 0, + FF_INPUT_BUFFER_PADDING_SIZE); + } else { + codec_context->extradata = NULL; + codec_context->extradata_size = 0; + } +} + +static void CopyPlane(const uint8_t* source, + int32_t source_stride, + int32_t target_stride, + int32_t rows, + int32_t copy_bytes_per_row, + uint8_t* target) { + DCHECK(source); + DCHECK(target); + DCHECK_LE(copy_bytes_per_row, source_stride); + DCHECK_LE(copy_bytes_per_row, target_stride); + + for (int i = 0; i < rows; ++i) { + const int source_offset = i * source_stride; + const int target_offset = i * target_stride; + memcpy(target + target_offset, + source + source_offset, + copy_bytes_per_row); + } +} + +FFmpegCdmVideoDecoder::FFmpegCdmVideoDecoder(cdm::Host* host) + : codec_context_(NULL), + av_frame_(NULL), + is_initialized_(false), + host_(host) { +} + +FFmpegCdmVideoDecoder::~FFmpegCdmVideoDecoder() { + ReleaseFFmpegResources(); +} + +bool FFmpegCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) { + DVLOG(1) << "Initialize()"; + + if (!IsValidOutputConfig(config.format, config.coded_size)) { + LOG(ERROR) << "Initialize(): invalid video decoder configuration."; + return false; + } + + if (is_initialized_) { + LOG(ERROR) << "Initialize(): Already initialized."; + return false; + } + + // Initialize AVCodecContext structure. + codec_context_ = avcodec_alloc_context3(NULL); + CdmVideoDecoderConfigToAVCodecContext(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_->err_recognition = AV_EF_CAREFUL; + codec_context_->thread_count = kDecodeThreads; + codec_context_->opaque = this; + codec_context_->flags |= CODEC_FLAG_EMU_EDGE; + + AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id); + if (!codec) { + LOG(ERROR) << "Initialize(): avcodec_find_decoder failed."; + return false; + } + + int status; + if ((status = avcodec_open2(codec_context_, codec, NULL)) < 0) { + LOG(ERROR) << "Initialize(): avcodec_open2 failed: " << status; + return false; + } + + av_frame_ = avcodec_alloc_frame(); + is_initialized_ = true; + + return true; +} + +void FFmpegCdmVideoDecoder::Deinitialize() { + DVLOG(1) << "Deinitialize()"; + ReleaseFFmpegResources(); + is_initialized_ = false; +} + +void FFmpegCdmVideoDecoder::Reset() { + DVLOG(1) << "Reset()"; + avcodec_flush_buffers(codec_context_); +} + +// static +bool FFmpegCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format, + const cdm::Size& data_size) { + return ((format == cdm::kYv12 || format == cdm::kI420) && + (data_size.width % 2) == 0 && (data_size.height % 2) == 0 && + data_size.width > 0 && data_size.height > 0 && + data_size.width <= limits::kMaxDimension && + data_size.height <= limits::kMaxDimension && + data_size.width * data_size.height <= limits::kMaxCanvas); +} + +cdm::Status FFmpegCdmVideoDecoder::DecodeFrame( + const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) { + DVLOG(1) << "DecodeFrame()"; + DCHECK(decoded_frame); + + // Create a packet for input data. + AVPacket packet; + av_init_packet(&packet); + + // The FFmpeg API does not allow us to have const read-only pointers. + packet.data = const_cast<uint8_t*>(compressed_frame); + packet.size = compressed_frame_size; + + // Let FFmpeg handle presentation timestamp reordering. + codec_context_->reordered_opaque = timestamp; + + // 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 when we can't decode a video frame and exit early. + if (result < 0) { + LOG(ERROR) << "DecodeFrame(): Error decoding video frame with timestamp: " + << timestamp << " us, packet size: " << packet.size << " bytes"; + return cdm::kDecodeError; + } + + // If no frame was produced then signal that more data is required to produce + // more frames. + if (frame_decoded == 0) + return cdm::kNeedMoreData; + + // The decoder is in a bad state and not decoding correctly. + // Checking for NULL avoids a crash. + if (!av_frame_->data[cdm::VideoFrame::kYPlane] || + !av_frame_->data[cdm::VideoFrame::kUPlane] || + !av_frame_->data[cdm::VideoFrame::kVPlane]) { + LOG(ERROR) << "DecodeFrame(): Video frame has invalid frame data."; + return cdm::kDecodeError; + } + + if (!CopyAvFrameTo(decoded_frame)) { + LOG(ERROR) << "DecodeFrame() could not copy video frame to output buffer."; + return cdm::kDecodeError; + } + + return cdm::kSuccess; +} + +bool FFmpegCdmVideoDecoder::CopyAvFrameTo(cdm::VideoFrame* cdm_video_frame) { + DCHECK(cdm_video_frame); + DCHECK_EQ(av_frame_->format, PIX_FMT_YUV420P); + DCHECK_EQ(av_frame_->width % 2, 0); + DCHECK_EQ(av_frame_->height % 2, 0); + + const int y_size = av_frame_->width * av_frame_->height; + const int uv_size = y_size / 2; + const int space_required = y_size + (uv_size * 2); + + DCHECK(!cdm_video_frame->FrameBuffer()); + cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required)); + if (!cdm_video_frame->FrameBuffer()) { + LOG(ERROR) << "CopyAvFrameTo() cdm::Host::Allocate failed."; + return false; + } + cdm_video_frame->FrameBuffer()->SetSize(space_required); + + CopyPlane(av_frame_->data[cdm::VideoFrame::kYPlane], + av_frame_->linesize[cdm::VideoFrame::kYPlane], + av_frame_->width, + av_frame_->height, + av_frame_->width, + cdm_video_frame->FrameBuffer()->Data()); + + const int uv_stride = av_frame_->width / 2; + const int uv_rows = av_frame_->height / 2; + CopyPlane(av_frame_->data[cdm::VideoFrame::kUPlane], + av_frame_->linesize[cdm::VideoFrame::kUPlane], + uv_stride, + uv_rows, + uv_stride, + cdm_video_frame->FrameBuffer()->Data() + y_size); + + CopyPlane(av_frame_->data[cdm::VideoFrame::kVPlane], + av_frame_->linesize[cdm::VideoFrame::kVPlane], + uv_stride, + uv_rows, + uv_stride, + cdm_video_frame->FrameBuffer()->Data() + y_size + uv_size); + + PixelFormat format = static_cast<PixelFormat>(av_frame_->format); + cdm_video_frame->SetFormat(PixelFormatToCdmVideoFormat(format)); + + cdm::Size video_frame_size; + video_frame_size.width = av_frame_->width; + video_frame_size.height = av_frame_->height; + cdm_video_frame->SetSize(video_frame_size); + + cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0); + cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size); + cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane, + y_size + uv_size); + + cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane, av_frame_->width); + cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane, uv_stride); + cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane, uv_stride); + + cdm_video_frame->SetTimestamp(av_frame_->reordered_opaque); + + return true; +} + +void FFmpegCdmVideoDecoder::ReleaseFFmpegResources() { + DVLOG(1) << "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; + } +} + +} // namespace media diff --git a/media/cdm/ppapi/ffmpeg_cdm_video_decoder.h b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.h new file mode 100644 index 0000000..17e2b57 --- /dev/null +++ b/media/cdm/ppapi/ffmpeg_cdm_video_decoder.h @@ -0,0 +1,58 @@ +// Copyright 2013 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_CDM_PPAPI_FFMPEG_CDM_VIDEO_DECODER_H_ +#define MEDIA_CDM_PPAPI_FFMPEG_CDM_VIDEO_DECODER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" +#include "media/cdm/ppapi/cdm_video_decoder.h" + +struct AVCodecContext; +struct AVFrame; + +namespace media { + +class FFmpegCdmVideoDecoder : public CdmVideoDecoder { + public: + explicit FFmpegCdmVideoDecoder(cdm::Host* host); + virtual ~FFmpegCdmVideoDecoder(); + + // CdmVideoDecoder implementation. + virtual bool Initialize(const cdm::VideoDecoderConfig& config) OVERRIDE; + virtual void Deinitialize() OVERRIDE; + virtual void Reset() OVERRIDE; + virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) OVERRIDE; + virtual bool is_initialized() const OVERRIDE { return is_initialized_; } + + // Returns true when |format| and |data_size| specify a supported video + // output configuration. + static bool IsValidOutputConfig(cdm::VideoFormat format, + const cdm::Size& data_size); + + private: + // Allocates storage, then copies video frame stored in |av_frame_| to + // |cdm_video_frame|. Returns true when allocation and copy succeed. + bool CopyAvFrameTo(cdm::VideoFrame* cdm_video_frame); + + void ReleaseFFmpegResources(); + + // FFmpeg structures owned by this object. + AVCodecContext* codec_context_; + AVFrame* av_frame_; + + bool is_initialized_; + + cdm::Host* const host_; + + DISALLOW_COPY_AND_ASSIGN(FFmpegCdmVideoDecoder); +}; + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_FFMPEG_CDM_VIDEO_DECODER_H_ diff --git a/media/cdm/ppapi/libvpx_cdm_video_decoder.cc b/media/cdm/ppapi/libvpx_cdm_video_decoder.cc new file mode 100644 index 0000000..a81c485 --- /dev/null +++ b/media/cdm/ppapi/libvpx_cdm_video_decoder.cc @@ -0,0 +1,195 @@ +// Copyright 2013 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/cdm/ppapi/libvpx_cdm_video_decoder.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/buffers.h" +#include "media/base/limits.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" { +// Note: vpx_decoder.h must be first or compile will fail. +#include "third_party/libvpx/source/libvpx/vpx/vpx_decoder.h" // NOLINT +#include "third_party/libvpx/source/libvpx/vpx/vp8dx.h" +} + +// Enable USE_COPYPLANE_WITH_LIBVPX to use |CopyPlane()| instead of memcpy to +// copy video frame data. +// #define USE_COPYPLANE_WITH_LIBVPX 1 + +namespace media { + +static const int kDecodeThreads = 2; + +LibvpxCdmVideoDecoder::LibvpxCdmVideoDecoder(cdm::Host* host) + : is_initialized_(false), + host_(host), + vpx_codec_(NULL), + vpx_image_(NULL) { +} + +LibvpxCdmVideoDecoder::~LibvpxCdmVideoDecoder() { + Deinitialize(); +} + +bool LibvpxCdmVideoDecoder::Initialize(const cdm::VideoDecoderConfig& config) { + DVLOG(1) << "Initialize()"; + + if (!IsValidOutputConfig(config.format, config.coded_size)) { + LOG(ERROR) << "Initialize(): invalid video decoder configuration."; + return false; + } + + if (is_initialized_) { + LOG(ERROR) << "Initialize(): Already initialized."; + return false; + } + + 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 = kDecodeThreads; + + vpx_codec_err_t status = vpx_codec_dec_init(vpx_codec_, + vpx_codec_vp8_dx(), + &vpx_config, + 0); + if (status != VPX_CODEC_OK) { + LOG(ERROR) << "InitializeLibvpx(): vpx_codec_dec_init failed, ret=" + << status; + delete vpx_codec_; + vpx_codec_ = NULL; + } + + is_initialized_ = true; + return true; +} + +void LibvpxCdmVideoDecoder::Deinitialize() { + DVLOG(1) << "Deinitialize()"; + + if (vpx_codec_) { + vpx_codec_destroy(vpx_codec_); + vpx_codec_ = NULL; + } + + is_initialized_ = false; +} + +void LibvpxCdmVideoDecoder::Reset() { + DVLOG(1) << "Reset()"; +} + +// static +bool LibvpxCdmVideoDecoder::IsValidOutputConfig(cdm::VideoFormat format, + const cdm::Size& data_size) { + return ((format == cdm::kYv12 || format == cdm::kI420) && + (data_size.width % 2) == 0 && (data_size.height % 2) == 0 && + data_size.width > 0 && data_size.height > 0 && + data_size.width <= limits::kMaxDimension && + data_size.height <= limits::kMaxDimension && + data_size.width * data_size.height <= limits::kMaxCanvas); +} + +cdm::Status LibvpxCdmVideoDecoder::DecodeFrame( + const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) { + DVLOG(1) << "DecodeFrame()"; + DCHECK(decoded_frame); + + // Pass |compressed_frame| to libvpx. + void* user_priv = reinterpret_cast<void*>(×tamp); + vpx_codec_err_t status = vpx_codec_decode(vpx_codec_, + compressed_frame, + compressed_frame_size, + user_priv, + 0); + if (status != VPX_CODEC_OK) { + LOG(ERROR) << "DecodeFrameLibvpx(): vpx_codec_decode failed, status=" + << status; + return cdm::kDecodeError; + } + + // Gets pointer to decoded data. + vpx_codec_iter_t iter = NULL; + vpx_image_ = vpx_codec_get_frame(vpx_codec_, &iter); + if (!vpx_image_) + return cdm::kNeedMoreData; + + if (vpx_image_->user_priv != reinterpret_cast<void*>(×tamp)) { + LOG(ERROR) << "DecodeFrameLibvpx() invalid output timestamp."; + return cdm::kDecodeError; + } + decoded_frame->SetTimestamp(timestamp); + + if (!CopyVpxImageTo(decoded_frame)) { + LOG(ERROR) << "DecodeFrameLibvpx() could not copy vpx image to output " + << "buffer."; + return cdm::kDecodeError; + } + + return cdm::kSuccess; +} + +bool LibvpxCdmVideoDecoder::CopyVpxImageTo(cdm::VideoFrame* cdm_video_frame) { + DCHECK(cdm_video_frame); + DCHECK_EQ(vpx_image_->fmt, VPX_IMG_FMT_I420); + DCHECK_EQ(vpx_image_->d_w % 2, 0U); + DCHECK_EQ(vpx_image_->d_h % 2, 0U); + + const int y_size = vpx_image_->stride[VPX_PLANE_Y] * vpx_image_->d_h; + const int uv_rows = vpx_image_->d_h / 2; + const int u_size = vpx_image_->stride[VPX_PLANE_U] * uv_rows; + const int v_size = vpx_image_->stride[VPX_PLANE_V] * uv_rows; + const int space_required = y_size + u_size + v_size; + + DCHECK(!cdm_video_frame->FrameBuffer()); + cdm_video_frame->SetFrameBuffer(host_->Allocate(space_required)); + if (!cdm_video_frame->FrameBuffer()) { + LOG(ERROR) << "CopyVpxImageTo() cdm::Host::Allocate failed."; + return false; + } + cdm_video_frame->FrameBuffer()->SetSize(space_required); + + memcpy(cdm_video_frame->FrameBuffer()->Data(), + vpx_image_->planes[VPX_PLANE_Y], + y_size); + memcpy(cdm_video_frame->FrameBuffer()->Data() + y_size, + vpx_image_->planes[VPX_PLANE_U], + u_size); + memcpy(cdm_video_frame->FrameBuffer()->Data() + y_size + u_size, + vpx_image_->planes[VPX_PLANE_V], + v_size); + + cdm_video_frame->SetFormat(cdm::kYv12); + + cdm::Size video_frame_size; + video_frame_size.width = vpx_image_->d_w; + video_frame_size.height = vpx_image_->d_h; + cdm_video_frame->SetSize(video_frame_size); + + cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kYPlane, 0); + cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kUPlane, y_size); + cdm_video_frame->SetPlaneOffset(cdm::VideoFrame::kVPlane, + y_size + u_size); + + cdm_video_frame->SetStride(cdm::VideoFrame::kYPlane, + vpx_image_->stride[VPX_PLANE_Y]); + cdm_video_frame->SetStride(cdm::VideoFrame::kUPlane, + vpx_image_->stride[VPX_PLANE_U]); + cdm_video_frame->SetStride(cdm::VideoFrame::kVPlane, + vpx_image_->stride[VPX_PLANE_V]); + + return true; +} + +} // namespace media diff --git a/media/cdm/ppapi/libvpx_cdm_video_decoder.h b/media/cdm/ppapi/libvpx_cdm_video_decoder.h new file mode 100644 index 0000000..3edaa07 --- /dev/null +++ b/media/cdm/ppapi/libvpx_cdm_video_decoder.h @@ -0,0 +1,55 @@ +// Copyright 2013 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_CDM_PPAPI_LIBVPX_CDM_VIDEO_DECODER_H_ +#define MEDIA_CDM_PPAPI_LIBVPX_CDM_VIDEO_DECODER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "media/cdm/ppapi/api/content_decryption_module.h" +#include "media/cdm/ppapi/cdm_video_decoder.h" + +struct vpx_codec_ctx; +struct vpx_image; + +namespace media { + +class LibvpxCdmVideoDecoder : public CdmVideoDecoder { + public: + explicit LibvpxCdmVideoDecoder(cdm::Host* host); + virtual ~LibvpxCdmVideoDecoder(); + + // CdmVideoDecoder implementation. + virtual bool Initialize(const cdm::VideoDecoderConfig& config) OVERRIDE; + virtual void Deinitialize() OVERRIDE; + virtual void Reset() OVERRIDE; + virtual cdm::Status DecodeFrame(const uint8_t* compressed_frame, + int32_t compressed_frame_size, + int64_t timestamp, + cdm::VideoFrame* decoded_frame) OVERRIDE; + virtual bool is_initialized() const OVERRIDE { return is_initialized_; } + + // Returns true when |format| and |data_size| specify a supported video + // output configuration. + static bool IsValidOutputConfig(cdm::VideoFormat format, + const cdm::Size& data_size); + + private: + // Allocates storage, then copies video frame stored in |vpx_image_| to + // |cdm_video_frame|. Returns true when allocation and copy succeed. + bool CopyVpxImageTo(cdm::VideoFrame* cdm_video_frame); + + bool is_initialized_; + + cdm::Host* const host_; + + vpx_codec_ctx* vpx_codec_; + vpx_image* vpx_image_; + + DISALLOW_COPY_AND_ASSIGN(LibvpxCdmVideoDecoder); +}; + +} // namespace media + +#endif // MEDIA_CDM_PPAPI_LIBVPX_CDM_VIDEO_DECODER_H_ diff --git a/media/cdm/ppapi/linked_ptr.h b/media/cdm/ppapi/linked_ptr.h new file mode 100644 index 0000000..f3eccbb --- /dev/null +++ b/media/cdm/ppapi/linked_ptr.h @@ -0,0 +1,185 @@ +// Copyright 2013 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. +// +// This is a copy of base/linked_ptr.h with CHECKS/DCHECKS replaced with +// PP_DCHECKs. +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is released, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Thread Safety: +// A linked_ptr is NOT thread safe. Copying a linked_ptr object is +// effectively a read-write operation. +// +// Alternative: to linked_ptr is shared_ptr, which +// - is also two pointers in size (8 bytes for 32 bit addresses) +// - is thread safe for copying and deletion +// - supports weak_ptrs + +#ifndef MEDIA_CDM_PPAPI_LINKED_PTR_H_ +#define MEDIA_CDM_PPAPI_LINKED_PTR_H_ + +#include "ppapi/cpp/logging.h" + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr<T>. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Join an existing circle. + void join(linked_ptr_internal const* ptr) { + next_ = ptr->next_; + ptr->next_ = this; + } + + // Leave whatever circle we're part of. Returns true iff we were the + // last member of the circle. Once this is done, you can join() another. + bool depart() { + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template <typename T> +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template <typename U> linked_ptr(linked_ptr<U> const& ptr) { copy(&ptr); } + + linked_ptr(linked_ptr const& ptr) { + PP_DCHECK(&ptr != this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template <typename U> linked_ptr& operator=(linked_ptr<U> const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + // Release ownership of the pointed object and returns it. + // Sole ownership by this linked_ptr object is required. + T* release() { + bool last = link_.depart(); + PP_DCHECK(last); + (void)last; + T* v = value_; + value_ = NULL; + return v; + } + + bool operator==(const T* p) const { return value_ == p; } + bool operator!=(const T* p) const { return value_ != p; } + template <typename U> + bool operator==(linked_ptr<U> const& ptr) const { + return value_ == ptr.get(); + } + template <typename U> + bool operator!=(linked_ptr<U> const& ptr) const { + return value_ != ptr.get(); + } + + private: + template <typename U> + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template <typename U> void copy(linked_ptr<U> const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template<typename T> inline +bool operator==(T* ptr, const linked_ptr<T>& x) { + return ptr == x.get(); +} + +template<typename T> inline +bool operator!=(T* ptr, const linked_ptr<T>& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr<T> +// Doing e.g. make_linked_ptr(new FooBarBaz<type>(arg)) is a shorter notation +// for linked_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg)) +template <typename T> +linked_ptr<T> make_linked_ptr(T* ptr) { + return linked_ptr<T>(ptr); +} + +#endif // MEDIA_CDM_PPAPI_LINKED_PTR_H_ diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc index f260708..26f65b9 100644 --- a/media/filters/pipeline_integration_test.cc +++ b/media/filters/pipeline_integration_test.cc @@ -11,7 +11,7 @@ #include "media/base/decoder_buffer.h" #include "media/base/media_keys.h" #include "media/base/test_data_util.h" -#include "media/crypto/aes_decryptor.h" +#include "media/cdm/aes_decryptor.h" #include "media/filters/chunk_demuxer.h" using testing::AnyNumber; diff --git a/media/media.gyp b/media/media.gyp index c0a08e4..c685090 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -314,8 +314,8 @@ 'base/video_util.h', 'base/yuv_convert.cc', 'base/yuv_convert.h', - 'crypto/aes_decryptor.cc', - 'crypto/aes_decryptor.h', + 'cdm/aes_decryptor.cc', + 'cdm/aes_decryptor.h', 'ffmpeg/ffmpeg_common.cc', 'ffmpeg/ffmpeg_common.h', 'filters/audio_decoder_selector.cc', @@ -945,7 +945,7 @@ 'base/video_frame_unittest.cc', 'base/video_util_unittest.cc', 'base/yuv_convert_unittest.cc', - 'crypto/aes_decryptor_unittest.cc', + 'cdm/aes_decryptor_unittest.cc', 'ffmpeg/ffmpeg_common_unittest.cc', 'filters/audio_decoder_selector_unittest.cc', 'filters/audio_file_reader_unittest.cc', @@ -1294,6 +1294,9 @@ ], # targets }], ['OS!="ios"', { + 'includes': [ + 'media_cdm.gypi', + ], 'targets': [ { # Minimal target for NaCl and other renderer side media clients which diff --git a/media/media_cdm.gypi b/media/media_cdm.gypi new file mode 100644 index 0000000..a4a94f7 --- /dev/null +++ b/media/media_cdm.gypi @@ -0,0 +1,136 @@ +# Copyright 2013 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. + +{ + 'variables': { + 'conditions': [ + ['OS == "android" or OS == "ios"', { + # Android and iOS don't use ffmpeg. + 'use_ffmpeg%': 0, + }, { # 'OS != "android" and OS != "ios"' + 'use_ffmpeg%': 1, + }], + ], + # Set |use_fake_video_decoder| to 1 to ignore input frames in |clearkeycdm|, + # and produce video frames filled with a solid color instead. + 'use_fake_video_decoder%': 0, + # Set |use_libvpx| to 1 to use libvpx for VP8 decoding in |clearkeycdm|. + 'use_libvpx%': 0, + }, + 'targets': [ + { + 'target_name': 'clearkeycdm', + 'type': 'none', + # TODO(tomfinegan): Simplify this by unconditionally including all the + # decoders, and changing clearkeycdm to select which decoder to use + # based on environment variables. + 'conditions': [ + ['use_fake_video_decoder == 1' , { + 'defines': ['CLEAR_KEY_CDM_USE_FAKE_VIDEO_DECODER'], + 'sources': [ + 'cdm/ppapi/fake_cdm_video_decoder.cc', + 'cdm/ppapi/fake_cdm_video_decoder.h', + ], + }], + ['use_ffmpeg == 1' , { + 'defines': ['CLEAR_KEY_CDM_USE_FFMPEG_DECODER'], + 'dependencies': [ + '<(DEPTH)/third_party/ffmpeg/ffmpeg.gyp:ffmpeg', + ], + 'sources': [ + 'cdm/ppapi/ffmpeg_cdm_audio_decoder.cc', + 'cdm/ppapi/ffmpeg_cdm_audio_decoder.h', + ], + }], + ['use_ffmpeg == 1 and use_fake_video_decoder == 0' , { + 'sources': [ + 'cdm/ppapi/ffmpeg_cdm_video_decoder.cc', + 'cdm/ppapi/ffmpeg_cdm_video_decoder.h', + ], + }], + ['use_libvpx == 1 and use_fake_video_decoder == 0' , { + 'defines': ['CLEAR_KEY_CDM_USE_LIBVPX_DECODER'], + 'dependencies': [ + '<(DEPTH)/third_party/libvpx/libvpx.gyp:libvpx', + ], + 'sources': [ + 'cdm/ppapi/libvpx_cdm_video_decoder.cc', + 'cdm/ppapi/libvpx_cdm_video_decoder.h', + ], + }], + ['os_posix == 1 and OS != "mac" and enable_pepper_cdms==1', { + 'type': 'loadable_module', # Must be in PRODUCT_DIR for ASAN bots. + }], + ['(OS == "mac" or OS == "win") and enable_pepper_cdms==1', { + 'type': 'shared_library', + }], + ['OS == "mac"', { + 'xcode_settings': { + 'DYLIB_INSTALL_NAME_BASE': '@loader_path', + }, + }] + ], + 'defines': ['CDM_IMPLEMENTATION'], + 'dependencies': [ + 'media', + # Include the following for media::AudioBus. + 'shared_memory_support', + '<(DEPTH)/base/base.gyp:base', + ], + 'sources': [ + 'cdm/ppapi/cdm_video_decoder.cc', + 'cdm/ppapi/cdm_video_decoder.h', + 'cdm/ppapi/clear_key_cdm.cc', + 'cdm/ppapi/clear_key_cdm.h', + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + }, + { + 'target_name': 'clearkeycdmadapter', + 'type': 'none', + # Check whether the plugin's origin URL is valid. + 'defines': ['CHECK_DOCUMENT_URL'], + 'dependencies': [ + '<(DEPTH)/ppapi/ppapi.gyp:ppapi_cpp', + 'clearkeycdm', + ], + 'sources': [ + 'cdm/ppapi/api/content_decryption_module.h', + 'cdm/ppapi/cdm_wrapper.cc', + 'cdm/ppapi/linked_ptr.h', + ], + 'conditions': [ + ['os_posix == 1 and OS != "mac" and enable_pepper_cdms==1', { + 'cflags': ['-fvisibility=hidden'], + 'type': 'loadable_module', + # Allow the plugin wrapper to find the CDM in the same directory. + 'ldflags': ['-Wl,-rpath=\$$ORIGIN'], + 'libraries': [ + # Built by clearkeycdm. + '<(PRODUCT_DIR)/libclearkeycdm.so', + ], + }], + ['OS == "win" and enable_pepper_cdms==1', { + 'type': 'shared_library', + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + }], + ['OS == "mac" and enable_pepper_cdms==1', { + 'type': 'loadable_module', + 'product_extension': 'plugin', + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + # Not to strip important symbols by -Wl,-dead_strip. + '-Wl,-exported_symbol,_PPP_GetInterface', + '-Wl,-exported_symbol,_PPP_InitializeModule', + '-Wl,-exported_symbol,_PPP_ShutdownModule' + ], + 'DYLIB_INSTALL_NAME_BASE': '@loader_path', + }, + }], + ], + } + ], +} |