| // 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/ppapi_cdm_adapter.h" |
| |
| #include <string.h> |
| |
| #include "base/macros.h" |
| #include "build/build_config.h" |
| #include "media/base/limits.h" |
| #include "media/cdm/ppapi/cdm_file_io_impl.h" |
| #include "media/cdm/ppapi/cdm_logging.h" |
| #include "media/cdm/supported_cdm_versions.h" |
| #include "ppapi/c/ppb_console.h" |
| #include "ppapi/cpp/private/uma_private.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 { |
| |
| // Constants for UMA reporting of file size (in KB) via HistogramCustomCounts(). |
| // Note that the histogram is log-scaled (rather than linear). |
| const uint32_t kSizeKBMin = 1; |
| const uint32_t kSizeKBMax = 512 * 1024; // 512MB |
| const uint32_t kSizeKBBuckets = 100; |
| |
| #if !defined(NDEBUG) |
| #define DLOG_TO_CONSOLE(message) LogToConsole(message); |
| #else |
| #define DLOG_TO_CONSOLE(message) (void)(message); |
| #endif |
| |
| 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() >= input_buffer->data_size); |
| |
| 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; |
| case cdm::kSessionError: |
| case cdm::kDeferredInitialization: |
| // kSessionError and kDeferredInitialization are only used by the |
| // Initialize* methods internally and never returned. Deliver* |
| // methods should never use these values. |
| break; |
| } |
| |
| PP_NOTREACHED(); |
| return PP_DECRYPTRESULT_DECRYPT_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; |
| } |
| } |
| |
| PP_DecryptedSampleFormat CdmAudioFormatToPpDecryptedSampleFormat( |
| cdm::AudioFormat format) { |
| switch (format) { |
| case cdm::kAudioFormatU8: |
| return PP_DECRYPTEDSAMPLEFORMAT_U8; |
| case cdm::kAudioFormatS16: |
| return PP_DECRYPTEDSAMPLEFORMAT_S16; |
| case cdm::kAudioFormatS32: |
| return PP_DECRYPTEDSAMPLEFORMAT_S32; |
| case cdm::kAudioFormatF32: |
| return PP_DECRYPTEDSAMPLEFORMAT_F32; |
| case cdm::kAudioFormatPlanarS16: |
| return PP_DECRYPTEDSAMPLEFORMAT_PLANAR_S16; |
| case cdm::kAudioFormatPlanarF32: |
| return PP_DECRYPTEDSAMPLEFORMAT_PLANAR_F32; |
| default: |
| return PP_DECRYPTEDSAMPLEFORMAT_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; |
| case PP_VIDEOCODEC_VP9: |
| return cdm::VideoDecoderConfig::kCodecVp9; |
| default: |
| return cdm::VideoDecoderConfig::kUnknownVideoCodec; |
| } |
| } |
| |
| cdm::VideoDecoderConfig::VideoCodecProfile PpVCProfileToCdmVCProfile( |
| PP_VideoCodecProfile profile) { |
| switch (profile) { |
| case PP_VIDEOCODECPROFILE_NOT_NEEDED: |
| return cdm::VideoDecoderConfig::kProfileNotNeeded; |
| 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; |
| } |
| |
| cdm::SessionType PpSessionTypeToCdmSessionType(PP_SessionType session_type) { |
| switch (session_type) { |
| case PP_SESSIONTYPE_TEMPORARY: |
| return cdm::kTemporary; |
| case PP_SESSIONTYPE_PERSISTENT_LICENSE: |
| return cdm::kPersistentLicense; |
| case PP_SESSIONTYPE_PERSISTENT_RELEASE: |
| return cdm::kPersistentKeyRelease; |
| } |
| |
| PP_NOTREACHED(); |
| return cdm::kTemporary; |
| } |
| |
| cdm::InitDataType PpInitDataTypeToCdmInitDataType( |
| PP_InitDataType init_data_type) { |
| switch (init_data_type) { |
| case PP_INITDATATYPE_CENC: |
| return cdm::kCenc; |
| case PP_INITDATATYPE_KEYIDS: |
| return cdm::kKeyIds; |
| case PP_INITDATATYPE_WEBM: |
| return cdm::kWebM; |
| } |
| |
| PP_NOTREACHED(); |
| return cdm::kKeyIds; |
| } |
| |
| PP_CdmExceptionCode CdmExceptionTypeToPpCdmExceptionType(cdm::Error error) { |
| switch (error) { |
| case cdm::kNotSupportedError: |
| return PP_CDMEXCEPTIONCODE_NOTSUPPORTEDERROR; |
| case cdm::kInvalidStateError: |
| return PP_CDMEXCEPTIONCODE_INVALIDSTATEERROR; |
| case cdm::kInvalidAccessError: |
| return PP_CDMEXCEPTIONCODE_INVALIDACCESSERROR; |
| case cdm::kQuotaExceededError: |
| return PP_CDMEXCEPTIONCODE_QUOTAEXCEEDEDERROR; |
| case cdm::kUnknownError: |
| return PP_CDMEXCEPTIONCODE_UNKNOWNERROR; |
| case cdm::kClientError: |
| return PP_CDMEXCEPTIONCODE_CLIENTERROR; |
| case cdm::kOutputError: |
| return PP_CDMEXCEPTIONCODE_OUTPUTERROR; |
| } |
| |
| PP_NOTREACHED(); |
| return PP_CDMEXCEPTIONCODE_UNKNOWNERROR; |
| } |
| |
| PP_CdmMessageType CdmMessageTypeToPpMessageType(cdm::MessageType message) { |
| switch (message) { |
| case cdm::kLicenseRequest: |
| return PP_CDMMESSAGETYPE_LICENSE_REQUEST; |
| case cdm::kLicenseRenewal: |
| return PP_CDMMESSAGETYPE_LICENSE_RENEWAL; |
| case cdm::kLicenseRelease: |
| return PP_CDMMESSAGETYPE_LICENSE_RELEASE; |
| } |
| |
| PP_NOTREACHED(); |
| return PP_CDMMESSAGETYPE_LICENSE_REQUEST; |
| } |
| |
| PP_CdmKeyStatus CdmKeyStatusToPpKeyStatus(cdm::KeyStatus status) { |
| switch (status) { |
| case cdm::kUsable: |
| return PP_CDMKEYSTATUS_USABLE; |
| case cdm::kInternalError: |
| return PP_CDMKEYSTATUS_INVALID; |
| case cdm::kExpired: |
| return PP_CDMKEYSTATUS_EXPIRED; |
| case cdm::kOutputRestricted: |
| return PP_CDMKEYSTATUS_OUTPUTRESTRICTED; |
| case cdm::kOutputDownscaled: |
| return PP_CDMKEYSTATUS_OUTPUTDOWNSCALED; |
| case cdm::kStatusPending: |
| return PP_CDMKEYSTATUS_STATUSPENDING; |
| case cdm::kReleased: |
| return PP_CDMKEYSTATUS_RELEASED; |
| } |
| |
| PP_NOTREACHED(); |
| return PP_CDMKEYSTATUS_INVALID; |
| } |
| |
| } // namespace |
| |
| namespace media { |
| |
| PpapiCdmAdapter::PpapiCdmAdapter(PP_Instance instance, pp::Module* module) |
| : pp::Instance(instance), |
| pp::ContentDecryptor_Private(this), |
| output_protection_(this), |
| output_link_mask_(0), |
| output_protection_mask_(0), |
| query_output_protection_in_progress_(false), |
| uma_for_output_protection_query_reported_(false), |
| uma_for_output_protection_positive_result_reported_(false), |
| platform_verification_(this), |
| allocator_(this), |
| cdm_(NULL), |
| allow_distinctive_identifier_(false), |
| allow_persistent_state_(false), |
| deferred_initialize_audio_decoder_(false), |
| deferred_audio_decoder_config_id_(0), |
| deferred_initialize_video_decoder_(false), |
| deferred_video_decoder_config_id_(0), |
| last_read_file_size_kb_(0), |
| file_size_uma_reported_(false) { |
| callback_factory_.Initialize(this); |
| } |
| |
| PpapiCdmAdapter::~PpapiCdmAdapter() {} |
| |
| CdmWrapper* PpapiCdmAdapter::CreateCdmInstance(const std::string& key_system) { |
| // The Pepper plugin will be staticly linked to the CDM, so pass the plugin's |
| // CreateCdmInstance() to CdmWrapper. |
| CdmWrapper* cdm = CdmWrapper::Create(::CreateCdmInstance, key_system.data(), |
| key_system.size(), GetCdmHost, this); |
| |
| const std::string message = "CDM instance for " + key_system + |
| (cdm ? "" : " could not be") + " created."; |
| DLOG_TO_CONSOLE(message); |
| CDM_DLOG() << message; |
| |
| if (cdm) { |
| pp::UMAPrivate uma_interface(this); |
| |
| // The interface version is relatively small. So using normal histogram |
| // instead of a sparse histogram is okay. The following DCHECK asserts this. |
| PP_DCHECK(cdm->GetInterfaceVersion() <= 30); |
| uma_interface.HistogramEnumeration( |
| "Media.EME.CdmInterfaceVersion", cdm->GetInterfaceVersion(), |
| // Sample value should always be less than the boundary. Hence use "+1". |
| // See the comment on HistogramEnumeration in ppb_uma_private.idl. |
| cdm::ContentDecryptionModule::kVersion + 1); |
| } |
| |
| return cdm; |
| } |
| |
| void PpapiCdmAdapter::Initialize(uint32_t promise_id, |
| const std::string& key_system, |
| bool allow_distinctive_identifier, |
| bool allow_persistent_state) { |
| PP_DCHECK(!key_system.empty()); |
| PP_DCHECK(!cdm_); |
| |
| #if defined(CHECK_DOCUMENT_URL) |
| PP_URLComponents_Dev url_components = {}; |
| const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get(); |
| if (!url_util) { |
| RejectPromise(promise_id, cdm::kUnknownError, 0, |
| "Unable to determine origin."); |
| return; |
| } |
| |
| pp::Var href = url_util->GetDocumentURL(pp::InstanceHandle(pp_instance()), |
| &url_components); |
| PP_DCHECK(href.is_string()); |
| std::string url = href.AsString(); |
| PP_DCHECK(!url.empty()); |
| std::string url_scheme = |
| url.substr(url_components.scheme.begin, url_components.scheme.len); |
| if (url_scheme != "file") { |
| // Skip this check for file:// URLs as they don't have a host component. |
| PP_DCHECK(url_components.host.begin); |
| PP_DCHECK(0 < url_components.host.len); |
| } |
| #endif // defined(CHECK_DOCUMENT_URL) |
| |
| cdm_ = make_linked_ptr(CreateCdmInstance(key_system)); |
| if (!cdm_) { |
| RejectPromise(promise_id, cdm::kInvalidAccessError, 0, |
| "Unable to create CDM."); |
| return; |
| } |
| |
| key_system_ = key_system; |
| allow_distinctive_identifier_ = allow_distinctive_identifier; |
| allow_persistent_state_ = allow_persistent_state; |
| cdm_->Initialize(allow_distinctive_identifier, allow_persistent_state); |
| OnResolvePromise(promise_id); |
| } |
| |
| void PpapiCdmAdapter::SetServerCertificate( |
| uint32_t promise_id, |
| pp::VarArrayBuffer server_certificate) { |
| const uint8_t* server_certificate_ptr = |
| static_cast<const uint8_t*>(server_certificate.Map()); |
| const uint32_t server_certificate_size = server_certificate.ByteLength(); |
| |
| if (!server_certificate_ptr || |
| server_certificate_size < media::limits::kMinCertificateLength || |
| server_certificate_size > media::limits::kMaxCertificateLength) { |
| RejectPromise(promise_id, cdm::kInvalidAccessError, 0, |
| "Incorrect certificate."); |
| return; |
| } |
| |
| cdm_->SetServerCertificate(promise_id, server_certificate_ptr, |
| server_certificate_size); |
| } |
| |
| void PpapiCdmAdapter::CreateSessionAndGenerateRequest( |
| uint32_t promise_id, |
| PP_SessionType session_type, |
| PP_InitDataType init_data_type, |
| pp::VarArrayBuffer init_data) { |
| cdm_->CreateSessionAndGenerateRequest( |
| promise_id, PpSessionTypeToCdmSessionType(session_type), |
| PpInitDataTypeToCdmInitDataType(init_data_type), |
| static_cast<const uint8_t*>(init_data.Map()), init_data.ByteLength()); |
| } |
| |
| void PpapiCdmAdapter::LoadSession(uint32_t promise_id, |
| PP_SessionType session_type, |
| const std::string& session_id) { |
| cdm_->LoadSession(promise_id, PpSessionTypeToCdmSessionType(session_type), |
| session_id.data(), session_id.size()); |
| } |
| |
| void PpapiCdmAdapter::UpdateSession(uint32_t promise_id, |
| const std::string& session_id, |
| pp::VarArrayBuffer response) { |
| const uint8_t* response_ptr = static_cast<const uint8_t*>(response.Map()); |
| const uint32_t response_size = response.ByteLength(); |
| |
| PP_DCHECK(!session_id.empty()); |
| PP_DCHECK(response_ptr); |
| PP_DCHECK(response_size > 0); |
| |
| cdm_->UpdateSession(promise_id, session_id.data(), session_id.length(), |
| response_ptr, response_size); |
| } |
| |
| void PpapiCdmAdapter::CloseSession(uint32_t promise_id, |
| const std::string& session_id) { |
| cdm_->CloseSession(promise_id, session_id.data(), session_id.length()); |
| } |
| |
| void PpapiCdmAdapter::RemoveSession(uint32_t promise_id, |
| const std::string& session_id) { |
| cdm_->RemoveSession(promise_id, session_id.data(), session_id.length()); |
| } |
| |
| // Note: In the following decryption/decoding related functions, errors are NOT |
| // reported via KeyError, but are reported via corresponding PPB calls. |
| |
| void PpapiCdmAdapter::Decrypt( |
| pp::Buffer_Dev encrypted_buffer, |
| const PP_EncryptedBlockInfo& encrypted_block_info) { |
| 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(&PpapiCdmAdapter::DeliverBlock, |
| status, decrypted_block, |
| encrypted_block_info.tracking_info)); |
| } |
| |
| void PpapiCdmAdapter::InitializeAudioDecoder( |
| const PP_AudioDecoderConfig& decoder_config, |
| pp::Buffer_Dev extra_data_buffer) { |
| PP_DCHECK(!deferred_initialize_audio_decoder_); |
| PP_DCHECK(deferred_audio_decoder_config_id_ == 0); |
| 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 = extra_data_buffer.size(); |
| status = cdm_->InitializeAudioDecoder(cdm_decoder_config); |
| } |
| |
| if (status == cdm::kDeferredInitialization) { |
| deferred_initialize_audio_decoder_ = true; |
| deferred_audio_decoder_config_id_ = decoder_config.request_id; |
| return; |
| } |
| |
| CallOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::DecoderInitializeDone, PP_DECRYPTORSTREAMTYPE_AUDIO, |
| decoder_config.request_id, status == cdm::kSuccess)); |
| } |
| |
| void PpapiCdmAdapter::InitializeVideoDecoder( |
| const PP_VideoDecoderConfig& decoder_config, |
| pp::Buffer_Dev extra_data_buffer) { |
| PP_DCHECK(!deferred_initialize_video_decoder_); |
| PP_DCHECK(deferred_video_decoder_config_id_ == 0); |
| 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 = extra_data_buffer.size(); |
| status = cdm_->InitializeVideoDecoder(cdm_decoder_config); |
| } |
| |
| if (status == cdm::kDeferredInitialization) { |
| deferred_initialize_video_decoder_ = true; |
| deferred_video_decoder_config_id_ = decoder_config.request_id; |
| return; |
| } |
| |
| CallOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::DecoderInitializeDone, PP_DECRYPTORSTREAMTYPE_VIDEO, |
| decoder_config.request_id, status == cdm::kSuccess)); |
| } |
| |
| void PpapiCdmAdapter::DeinitializeDecoder(PP_DecryptorStreamType decoder_type, |
| uint32_t request_id) { |
| PP_DCHECK(cdm_); // InitializeXxxxxDecoder should have succeeded. |
| if (cdm_) { |
| cdm_->DeinitializeDecoder( |
| PpDecryptorStreamTypeToCdmStreamType(decoder_type)); |
| } |
| |
| CallOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::DecoderDeinitializeDone, decoder_type, request_id)); |
| } |
| |
| void PpapiCdmAdapter::ResetDecoder(PP_DecryptorStreamType decoder_type, |
| uint32_t request_id) { |
| PP_DCHECK(cdm_); // InitializeXxxxxDecoder should have succeeded. |
| if (cdm_) |
| cdm_->ResetDecoder(PpDecryptorStreamTypeToCdmStreamType(decoder_type)); |
| |
| CallOnMain(callback_factory_.NewCallback(&PpapiCdmAdapter::DecoderResetDone, |
| decoder_type, request_id)); |
| } |
| |
| void PpapiCdmAdapter::DecryptAndDecode( |
| PP_DecryptorStreamType decoder_type, |
| pp::Buffer_Dev encrypted_buffer, |
| const PP_EncryptedBlockInfo& encrypted_block_info) { |
| PP_DCHECK(cdm_); // InitializeXxxxxDecoder 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( |
| &PpapiCdmAdapter::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( |
| &PpapiCdmAdapter::DeliverSamples, status, audio_frames, |
| encrypted_block_info.tracking_info)); |
| return; |
| } |
| |
| default: |
| PP_NOTREACHED(); |
| return; |
| } |
| } |
| |
| cdm::Buffer* PpapiCdmAdapter::Allocate(uint32_t capacity) { |
| return allocator_.Allocate(capacity); |
| } |
| |
| void PpapiCdmAdapter::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(&PpapiCdmAdapter::TimerExpired, context), |
| PP_OK); |
| } |
| |
| void PpapiCdmAdapter::TimerExpired(int32_t result, void* context) { |
| PP_DCHECK(result == PP_OK); |
| cdm_->TimerExpired(context); |
| } |
| |
| cdm::Time PpapiCdmAdapter::GetCurrentWallTime() { |
| return pp::Module::Get()->core()->GetTime(); |
| } |
| |
| void PpapiCdmAdapter::OnResolveNewSessionPromise(uint32_t promise_id, |
| const char* session_id, |
| uint32_t session_id_size) { |
| PostOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendPromiseResolvedWithSessionInternal, promise_id, |
| std::string(session_id, session_id_size))); |
| } |
| |
| void PpapiCdmAdapter::OnResolvePromise(uint32_t promise_id) { |
| PostOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendPromiseResolvedInternal, promise_id)); |
| } |
| |
| void PpapiCdmAdapter::OnRejectPromise(uint32_t promise_id, |
| cdm::Error error, |
| uint32_t system_code, |
| const char* error_message, |
| uint32_t error_message_size) { |
| // UMA to investigate http://crbug.com/410630 |
| // TODO(xhwang): Remove after bug is fixed. |
| if (system_code == 0x27) { |
| pp::UMAPrivate uma_interface(this); |
| uma_interface.HistogramCustomCounts("Media.EME.CdmFileIO.FileSizeKBOnError", |
| last_read_file_size_kb_, kSizeKBMin, |
| kSizeKBMax, kSizeKBBuckets); |
| } |
| |
| RejectPromise(promise_id, error, system_code, |
| std::string(error_message, error_message_size)); |
| } |
| |
| void PpapiCdmAdapter::RejectPromise(uint32_t promise_id, |
| cdm::Error error, |
| uint32_t system_code, |
| const std::string& error_message) { |
| PostOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendPromiseRejectedInternal, promise_id, |
| SessionError(error, system_code, error_message))); |
| } |
| |
| void PpapiCdmAdapter::OnSessionMessage(const char* session_id, |
| uint32_t session_id_size, |
| cdm::MessageType message_type, |
| const char* message, |
| uint32_t message_size, |
| const char* legacy_destination_url, |
| uint32_t legacy_destination_url_size) { |
| // |legacy_destination_url| is obsolete and will be removed as part of |
| // https://crbug.com/570216. |
| // License requests should not specify |legacy_destination_url|. |
| PP_DCHECK(legacy_destination_url_size == 0 || |
| message_type != cdm::MessageType::kLicenseRequest); |
| |
| PostOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendSessionMessageInternal, |
| SessionMessage(std::string(session_id, session_id_size), message_type, |
| message, message_size))); |
| } |
| |
| void PpapiCdmAdapter::OnSessionKeysChange(const char* session_id, |
| uint32_t session_id_size, |
| bool has_additional_usable_key, |
| const cdm::KeyInformation* keys_info, |
| uint32_t keys_info_count) { |
| std::vector<PP_KeyInformation> key_information; |
| for (uint32_t i = 0; i < keys_info_count; ++i) { |
| const auto& key_info = keys_info[i]; |
| PP_KeyInformation next_key = {}; |
| |
| if (key_info.key_id_size > sizeof(next_key.key_id)) { |
| PP_NOTREACHED(); |
| continue; |
| } |
| |
| // Copy key_id into |next_key|. |
| memcpy(next_key.key_id, key_info.key_id, key_info.key_id_size); |
| |
| // Set remaining fields on |next_key|. |
| next_key.key_id_size = key_info.key_id_size; |
| next_key.key_status = CdmKeyStatusToPpKeyStatus(key_info.status); |
| next_key.system_code = key_info.system_code; |
| key_information.push_back(next_key); |
| } |
| |
| PostOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendSessionKeysChangeInternal, |
| std::string(session_id, session_id_size), has_additional_usable_key, |
| key_information)); |
| } |
| |
| void PpapiCdmAdapter::OnExpirationChange(const char* session_id, |
| uint32_t session_id_size, |
| cdm::Time new_expiry_time) { |
| PostOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendExpirationChangeInternal, |
| std::string(session_id, session_id_size), new_expiry_time)); |
| } |
| |
| void PpapiCdmAdapter::OnSessionClosed(const char* session_id, |
| uint32_t session_id_size) { |
| PostOnMain( |
| callback_factory_.NewCallback(&PpapiCdmAdapter::SendSessionClosedInternal, |
| std::string(session_id, session_id_size))); |
| } |
| |
| void PpapiCdmAdapter::OnLegacySessionError(const char* session_id, |
| uint32_t session_id_size, |
| cdm::Error error, |
| uint32_t system_code, |
| const char* error_message, |
| uint32_t error_message_size) { |
| // Obsolete and will be removed as part of https://crbug.com/570216. |
| } |
| |
| // Helpers to pass the event to Pepper. |
| |
| void PpapiCdmAdapter::SendPromiseResolvedInternal(int32_t result, |
| uint32_t promise_id) { |
| PP_DCHECK(result == PP_OK); |
| pp::ContentDecryptor_Private::PromiseResolved(promise_id); |
| } |
| |
| void PpapiCdmAdapter::SendPromiseResolvedWithSessionInternal( |
| int32_t result, |
| uint32_t promise_id, |
| const std::string& session_id) { |
| PP_DCHECK(result == PP_OK); |
| pp::ContentDecryptor_Private::PromiseResolvedWithSession(promise_id, |
| session_id); |
| } |
| |
| void PpapiCdmAdapter::SendPromiseRejectedInternal(int32_t result, |
| uint32_t promise_id, |
| const SessionError& error) { |
| PP_DCHECK(result == PP_OK); |
| pp::ContentDecryptor_Private::PromiseRejected( |
| promise_id, CdmExceptionTypeToPpCdmExceptionType(error.error), |
| error.system_code, error.error_description); |
| } |
| |
| void PpapiCdmAdapter::SendSessionMessageInternal( |
| int32_t result, |
| const SessionMessage& message) { |
| PP_DCHECK(result == PP_OK); |
| |
| pp::VarArrayBuffer message_array_buffer(message.message.size()); |
| if (message.message.size() > 0) { |
| memcpy(message_array_buffer.Map(), message.message.data(), |
| message.message.size()); |
| } |
| |
| pp::ContentDecryptor_Private::SessionMessage( |
| message.session_id, CdmMessageTypeToPpMessageType(message.message_type), |
| message_array_buffer, std::string()); |
| } |
| |
| void PpapiCdmAdapter::SendSessionClosedInternal(int32_t result, |
| const std::string& session_id) { |
| PP_DCHECK(result == PP_OK); |
| pp::ContentDecryptor_Private::SessionClosed(session_id); |
| } |
| |
| void PpapiCdmAdapter::SendSessionKeysChangeInternal( |
| int32_t result, |
| const std::string& session_id, |
| bool has_additional_usable_key, |
| const std::vector<PP_KeyInformation>& key_info) { |
| PP_DCHECK(result == PP_OK); |
| pp::ContentDecryptor_Private::SessionKeysChange( |
| session_id, has_additional_usable_key, key_info); |
| } |
| |
| void PpapiCdmAdapter::SendExpirationChangeInternal( |
| int32_t result, |
| const std::string& session_id, |
| cdm::Time new_expiry_time) { |
| PP_DCHECK(result == PP_OK); |
| pp::ContentDecryptor_Private::SessionExpirationChange(session_id, |
| new_expiry_time); |
| } |
| |
| void PpapiCdmAdapter::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()); |
| decrypted_block_info.tracking_info.buffer_id = ppb_buffer->buffer_id(); |
| decrypted_block_info.data_size = ppb_buffer->Size(); |
| |
| buffer = ppb_buffer->TakeBuffer(); |
| } |
| } |
| |
| pp::ContentDecryptor_Private::DeliverBlock(buffer, decrypted_block_info); |
| } |
| |
| void PpapiCdmAdapter::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 PpapiCdmAdapter::DecoderDeinitializeDone( |
| int32_t result, |
| PP_DecryptorStreamType decoder_type, |
| uint32_t request_id) { |
| pp::ContentDecryptor_Private::DecoderDeinitializeDone(decoder_type, |
| request_id); |
| } |
| |
| void PpapiCdmAdapter::DecoderResetDone(int32_t result, |
| PP_DecryptorStreamType decoder_type, |
| uint32_t request_id) { |
| pp::ContentDecryptor_Private::DecoderResetDone(decoder_type, request_id); |
| } |
| |
| void PpapiCdmAdapter::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()); |
| |
| 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); |
| |
| buffer = ppb_buffer->TakeBuffer(); |
| } |
| } |
| |
| pp::ContentDecryptor_Private::DeliverFrame(buffer, decrypted_frame_info); |
| } |
| |
| void PpapiCdmAdapter::DeliverSamples( |
| int32_t result, |
| const cdm::Status& status, |
| const LinkedAudioFrames& audio_frames, |
| const PP_DecryptTrackingInfo& tracking_info) { |
| PP_DCHECK(result == PP_OK); |
| |
| PP_DecryptedSampleInfo decrypted_sample_info = {}; |
| decrypted_sample_info.tracking_info = tracking_info; |
| decrypted_sample_info.tracking_info.timestamp = 0; |
| decrypted_sample_info.tracking_info.buffer_id = 0; |
| decrypted_sample_info.data_size = 0; |
| decrypted_sample_info.result = CdmStatusToPpDecryptResult(status); |
| |
| pp::Buffer_Dev buffer; |
| |
| if (decrypted_sample_info.result == PP_DECRYPTRESULT_SUCCESS) { |
| PP_DCHECK(audio_frames.get() && audio_frames->FrameBuffer()); |
| if (!audio_frames.get() || !audio_frames->FrameBuffer()) { |
| PP_NOTREACHED(); |
| decrypted_sample_info.result = PP_DECRYPTRESULT_DECRYPT_ERROR; |
| } else { |
| PpbBuffer* ppb_buffer = |
| static_cast<PpbBuffer*>(audio_frames->FrameBuffer()); |
| |
| decrypted_sample_info.tracking_info.buffer_id = ppb_buffer->buffer_id(); |
| decrypted_sample_info.data_size = ppb_buffer->Size(); |
| decrypted_sample_info.format = |
| CdmAudioFormatToPpDecryptedSampleFormat(audio_frames->Format()); |
| |
| buffer = ppb_buffer->TakeBuffer(); |
| } |
| } |
| |
| pp::ContentDecryptor_Private::DeliverSamples(buffer, decrypted_sample_info); |
| } |
| |
| bool PpapiCdmAdapter::IsValidVideoFrame(const LinkedVideoFrame& video_frame) { |
| if (!video_frame.get() || !video_frame->FrameBuffer() || |
| (video_frame->Format() != cdm::kI420 && |
| video_frame->Format() != cdm::kYv12)) { |
| CDM_DLOG() << "Invalid video frame!"; |
| return false; |
| } |
| |
| PpbBuffer* ppb_buffer = static_cast<PpbBuffer*>(video_frame->FrameBuffer()); |
| |
| for (uint32_t 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 PpapiCdmAdapter::OnFirstFileRead(int32_t file_size_bytes) { |
| PP_DCHECK(IsMainThread()); |
| PP_DCHECK(file_size_bytes >= 0); |
| |
| last_read_file_size_kb_ = file_size_bytes / 1024; |
| |
| if (file_size_uma_reported_) |
| return; |
| |
| pp::UMAPrivate uma_interface(this); |
| uma_interface.HistogramCustomCounts( |
| "Media.EME.CdmFileIO.FileSizeKBOnFirstRead", last_read_file_size_kb_, |
| kSizeKBMin, kSizeKBMax, kSizeKBBuckets); |
| file_size_uma_reported_ = true; |
| } |
| |
| #if !defined(NDEBUG) |
| void PpapiCdmAdapter::LogToConsole(const pp::Var& value) { |
| PP_DCHECK(IsMainThread()); |
| const PPB_Console* console = reinterpret_cast<const PPB_Console*>( |
| pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE)); |
| console->Log(pp_instance(), PP_LOGLEVEL_LOG, value.pp_var()); |
| } |
| #endif // !defined(NDEBUG) |
| |
| void PpapiCdmAdapter::SendPlatformChallenge(const char* service_id, |
| uint32_t service_id_size, |
| const char* challenge, |
| uint32_t challenge_size) { |
| #if defined(OS_CHROMEOS) |
| // If access to a distinctive identifier is not allowed, block platform |
| // verification to prevent access to such an identifier. |
| if (allow_distinctive_identifier_) { |
| pp::VarArrayBuffer challenge_var(challenge_size); |
| uint8_t* var_data = static_cast<uint8_t*>(challenge_var.Map()); |
| memcpy(var_data, challenge, challenge_size); |
| |
| std::string service_id_str(service_id, service_id_size); |
| |
| linked_ptr<PepperPlatformChallengeResponse> response( |
| new PepperPlatformChallengeResponse()); |
| |
| int32_t result = platform_verification_.ChallengePlatform( |
| pp::Var(service_id_str), challenge_var, &response->signed_data, |
| &response->signed_data_signature, &response->platform_key_certificate, |
| callback_factory_.NewCallback( |
| &PpapiCdmAdapter::SendPlatformChallengeDone, response)); |
| challenge_var.Unmap(); |
| if (result == PP_OK_COMPLETIONPENDING) |
| return; |
| |
| // Fall through on error and issue an empty OnPlatformChallengeResponse(). |
| PP_DCHECK(result != PP_OK); |
| } |
| #endif |
| |
| cdm::PlatformChallengeResponse platform_challenge_response = {}; |
| cdm_->OnPlatformChallengeResponse(platform_challenge_response); |
| } |
| |
| void PpapiCdmAdapter::EnableOutputProtection(uint32_t desired_protection_mask) { |
| int32_t result = output_protection_.EnableProtection( |
| desired_protection_mask, |
| callback_factory_.NewCallback(&PpapiCdmAdapter::EnableProtectionDone)); |
| |
| // Errors are ignored since clients must call QueryOutputProtectionStatus() to |
| // inspect the protection status on a regular basis. |
| |
| if (result != PP_OK && result != PP_OK_COMPLETIONPENDING) |
| CDM_DLOG() << __func__ << " failed!"; |
| } |
| |
| void PpapiCdmAdapter::QueryOutputProtectionStatus() { |
| PP_DCHECK(!query_output_protection_in_progress_); |
| |
| output_link_mask_ = output_protection_mask_ = 0; |
| const int32_t result = output_protection_.QueryStatus( |
| &output_link_mask_, &output_protection_mask_, |
| callback_factory_.NewCallback( |
| &PpapiCdmAdapter::QueryOutputProtectionStatusDone)); |
| if (result == PP_OK_COMPLETIONPENDING) { |
| query_output_protection_in_progress_ = true; |
| ReportOutputProtectionQuery(); |
| return; |
| } |
| |
| // Fall through on error and issue an empty OnQueryOutputProtectionStatus(). |
| PP_DCHECK(result != PP_OK); |
| CDM_DLOG() << __func__ << " failed, result = " << result; |
| } |
| |
| void PpapiCdmAdapter::OnDeferredInitializationDone(cdm::StreamType stream_type, |
| cdm::Status decoder_status) { |
| switch (stream_type) { |
| case cdm::kStreamTypeAudio: |
| PP_DCHECK(deferred_initialize_audio_decoder_); |
| CallOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::DecoderInitializeDone, PP_DECRYPTORSTREAMTYPE_AUDIO, |
| deferred_audio_decoder_config_id_, decoder_status == cdm::kSuccess)); |
| deferred_initialize_audio_decoder_ = false; |
| deferred_audio_decoder_config_id_ = 0; |
| break; |
| case cdm::kStreamTypeVideo: |
| PP_DCHECK(deferred_initialize_video_decoder_); |
| CallOnMain(callback_factory_.NewCallback( |
| &PpapiCdmAdapter::DecoderInitializeDone, PP_DECRYPTORSTREAMTYPE_VIDEO, |
| deferred_video_decoder_config_id_, decoder_status == cdm::kSuccess)); |
| deferred_initialize_video_decoder_ = false; |
| deferred_video_decoder_config_id_ = 0; |
| break; |
| } |
| } |
| |
| // The CDM owns the returned object and must call FileIO::Close() to release it. |
| cdm::FileIO* PpapiCdmAdapter::CreateFileIO(cdm::FileIOClient* client) { |
| if (!allow_persistent_state_) { |
| CDM_DLOG() |
| << "Cannot create FileIO because persistent state is not allowed."; |
| return nullptr; |
| } |
| |
| return new CdmFileIOImpl( |
| client, pp_instance(), |
| callback_factory_.NewCallback(&PpapiCdmAdapter::OnFirstFileRead)); |
| } |
| |
| void PpapiCdmAdapter::ReportOutputProtectionUMA(OutputProtectionStatus status) { |
| pp::UMAPrivate uma_interface(this); |
| uma_interface.HistogramEnumeration("Media.EME.OutputProtection", status, |
| OUTPUT_PROTECTION_MAX); |
| } |
| |
| void PpapiCdmAdapter::ReportOutputProtectionQuery() { |
| if (uma_for_output_protection_query_reported_) |
| return; |
| |
| ReportOutputProtectionUMA(OUTPUT_PROTECTION_QUERIED); |
| uma_for_output_protection_query_reported_ = true; |
| } |
| |
| void PpapiCdmAdapter::ReportOutputProtectionQueryResult() { |
| if (uma_for_output_protection_positive_result_reported_) |
| return; |
| |
| // Report UMAs for output protection query result. |
| uint32_t external_links = (output_link_mask_ & ~cdm::kLinkTypeInternal); |
| |
| if (!external_links) { |
| ReportOutputProtectionUMA(OUTPUT_PROTECTION_NO_EXTERNAL_LINK); |
| uma_for_output_protection_positive_result_reported_ = true; |
| return; |
| } |
| |
| const uint32_t kProtectableLinks = |
| cdm::kLinkTypeHDMI | cdm::kLinkTypeDVI | cdm::kLinkTypeDisplayPort; |
| bool is_unprotectable_link_connected = |
| (external_links & ~kProtectableLinks) != 0; |
| bool is_hdcp_enabled_on_all_protectable_links = |
| (output_protection_mask_ & cdm::kProtectionHDCP) != 0; |
| |
| if (!is_unprotectable_link_connected && |
| is_hdcp_enabled_on_all_protectable_links) { |
| ReportOutputProtectionUMA(OUTPUT_PROTECTION_ALL_EXTERNAL_LINKS_PROTECTED); |
| uma_for_output_protection_positive_result_reported_ = true; |
| return; |
| } |
| |
| // Do not report a negative result because it could be a false negative. |
| // Instead, we will calculate number of negatives using the total number of |
| // queries and success results. |
| } |
| |
| #if defined(OS_CHROMEOS) |
| void PpapiCdmAdapter::SendPlatformChallengeDone( |
| int32_t result, |
| const linked_ptr<PepperPlatformChallengeResponse>& response) { |
| if (result != PP_OK) { |
| CDM_DLOG() << __func__ << ": Platform challenge failed!"; |
| cdm::PlatformChallengeResponse platform_challenge_response = {}; |
| cdm_->OnPlatformChallengeResponse(platform_challenge_response); |
| return; |
| } |
| |
| pp::VarArrayBuffer signed_data_var(response->signed_data); |
| pp::VarArrayBuffer signed_data_signature_var(response->signed_data_signature); |
| std::string platform_key_certificate_string = |
| response->platform_key_certificate.AsString(); |
| |
| cdm::PlatformChallengeResponse platform_challenge_response = { |
| static_cast<uint8_t*>(signed_data_var.Map()), |
| signed_data_var.ByteLength(), |
| static_cast<uint8_t*>(signed_data_signature_var.Map()), |
| signed_data_signature_var.ByteLength(), |
| reinterpret_cast<const uint8_t*>(platform_key_certificate_string.data()), |
| static_cast<uint32_t>(platform_key_certificate_string.length())}; |
| cdm_->OnPlatformChallengeResponse(platform_challenge_response); |
| |
| signed_data_var.Unmap(); |
| signed_data_signature_var.Unmap(); |
| } |
| #endif |
| |
| void PpapiCdmAdapter::EnableProtectionDone(int32_t result) { |
| // Does nothing since clients must call QueryOutputProtectionStatus() to |
| // inspect the protection status on a regular basis. |
| CDM_DLOG() << __func__ << " : " << result; |
| } |
| |
| void PpapiCdmAdapter::QueryOutputProtectionStatusDone(int32_t result) { |
| PP_DCHECK(query_output_protection_in_progress_); |
| query_output_protection_in_progress_ = false; |
| |
| // Return a query status of failed on error. |
| cdm::QueryResult query_result; |
| if (result != PP_OK) { |
| CDM_DLOG() << __func__ << " failed, result = " << result; |
| output_link_mask_ = output_protection_mask_ = 0; |
| query_result = cdm::kQueryFailed; |
| } else { |
| query_result = cdm::kQuerySucceeded; |
| ReportOutputProtectionQueryResult(); |
| } |
| |
| cdm_->OnQueryOutputProtectionStatus(query_result, output_link_mask_, |
| output_protection_mask_); |
| } |
| |
| PpapiCdmAdapter::SessionError::SessionError( |
| cdm::Error error, |
| uint32_t system_code, |
| const std::string& error_description) |
| : error(error), |
| system_code(system_code), |
| error_description(error_description) {} |
| |
| PpapiCdmAdapter::SessionMessage::SessionMessage(const std::string& session_id, |
| cdm::MessageType message_type, |
| const char* message, |
| uint32_t message_size) |
| : session_id(session_id), |
| message_type(message_type), |
| message(message, message + message_size) {} |
| |
| void* GetCdmHost(int host_interface_version, void* user_data) { |
| if (!host_interface_version || !user_data) |
| return NULL; |
| |
| static_assert( |
| cdm::ContentDecryptionModule::Host::kVersion == cdm::Host_8::kVersion, |
| "update the code below"); |
| |
| // Ensure IsSupportedCdmHostVersion matches implementation of this function. |
| // Always update this DCHECK when updating this function. |
| // If this check fails, update this function and DCHECK or update |
| // IsSupportedCdmHostVersion. |
| |
| PP_DCHECK( |
| // Future version is not supported. |
| !IsSupportedCdmHostVersion(cdm::Host_8::kVersion + 1) && |
| // Current version is supported. |
| IsSupportedCdmHostVersion(cdm::Host_8::kVersion) && |
| // Include all previous supported versions (if any) here. |
| // One older than the oldest supported version is not supported. |
| !IsSupportedCdmHostVersion(cdm::Host_8::kVersion - 1)); |
| PP_DCHECK(IsSupportedCdmHostVersion(host_interface_version)); |
| |
| PpapiCdmAdapter* cdm_adapter = static_cast<PpapiCdmAdapter*>(user_data); |
| CDM_DLOG() << "Create CDM Host with version " << host_interface_version; |
| switch (host_interface_version) { |
| case cdm::Host_8::kVersion: |
| return static_cast<cdm::Host_8*>(cdm_adapter); |
| default: |
| PP_NOTREACHED(); |
| return NULL; |
| } |
| } |
| |
| // This object is the global object representing this plugin library as long |
| // as it is loaded. |
| class PpapiCdmAdapterModule : public pp::Module { |
| public: |
| PpapiCdmAdapterModule() : 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 ~PpapiCdmAdapterModule() { DeinitializeCdmModule(); } |
| |
| virtual pp::Instance* CreateInstance(PP_Instance instance) { |
| return new PpapiCdmAdapter(instance, this); |
| } |
| |
| private: |
| CdmFileIOImpl::ResourceTracker cdm_file_io_impl_resource_tracker; |
| }; |
| |
| } // namespace media |
| |
| namespace pp { |
| |
| // Factory function for your specialization of the Module object. |
| Module* CreateModule() { |
| return new media::PpapiCdmAdapterModule(); |
| } |
| |
| } // namespace pp |