From: wuxiaoliang Date: Wed, 6 Mar 2024 01:46:46 +0000 (+0800) Subject: [M120 Migration][MM] Support W3C EME X-Git-Tag: submit/tizen/20240311.160013~19 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2f2ffca1ace4b27b546416ff4ec581158ba02903;p=platform%2Fframework%2Fweb%2Fchromium-efl.git [M120 Migration][MM] Support W3C EME Add IEMEDrmBridge for encrypted stream. single process -> espp decrypted stream multiple process -> drm decrypted stream -> pass tz_handle to espp Migration from: https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/290566/ https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/291049/ https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/296545/ https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/298081/ https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/297184/ Change-Id: Ibb604997638c6ff330f33159a3ee777122767cbe Signed-off-by: wuxiaoliang --- diff --git a/content/renderer/media/media_factory.cc b/content/renderer/media/media_factory.cc index 500f254..ef9d506 100644 --- a/content/renderer/media/media_factory.cc +++ b/content/renderer/media/media_factory.cc @@ -139,6 +139,10 @@ #include "media/mojo/mojom/speech_recognition_service.mojom.h" #endif // BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_TIZEN_TV) +#include "content/renderer/media/cdm/render_cdm_factory.h" +#endif + namespace { // This limit is much higher than it needs to be right now, because the logic @@ -879,6 +883,8 @@ media::CdmFactory* MediaFactory::GetCdmFactory() { DCHECK(interface_broker_); cdm_factory_ = std::make_unique( std::make_unique(interface_broker_)); +#elif BUILDFLAG(IS_TIZEN_TV) + cdm_factory_.reset(new RenderCdmFactory()); #elif BUILDFLAG(ENABLE_MOJO_CDM) cdm_factory_ = std::make_unique(GetMediaInterfaceFactory()); diff --git a/media/base/decoder_buffer.cc b/media/base/decoder_buffer.cc index 2c2a5a0..282ca15 100644 --- a/media/base/decoder_buffer.cc +++ b/media/base/decoder_buffer.cc @@ -9,6 +9,10 @@ #include "base/debug/alias.h" #include "media/base/subsample_entry.h" +#if BUILDFLAG(IS_TIZEN_TV) +#include "base/logging.h" +#endif + namespace media { DecoderBuffer::TimeInfo::TimeInfo() = default; @@ -216,4 +220,16 @@ void DecoderBuffer::set_timestamp(base::TimeDelta timestamp) { time_info_.timestamp = timestamp; } +#if BUILDFLAG(IS_TIZEN_TV) +scoped_refptr DecoderBuffer::CreateTzHandleBuffer(int handle, + int size) { + auto ptr = scoped_refptr(new DecoderBuffer(NULL, 0)); + ptr->tz_handle_ = handle; + ptr->tz_buffer_size_ = size; + LOG(INFO) << __func__ << "tz_handle : " << handle + << " ; tz_buffer_size_ : " << size; + return ptr; +} +#endif + } // namespace media diff --git a/media/base/decoder_buffer.h b/media/base/decoder_buffer.h index 673e685..7113a5e 100644 --- a/media/base/decoder_buffer.h +++ b/media/base/decoder_buffer.h @@ -27,6 +27,16 @@ #include "media/base/timestamp_constants.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#if BUILDFLAG(IS_TIZEN_TV) +#include "tizen_src/chromium_impl/build/tizen_version.h" + +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) +#include "base/scoped_generic.h" + +#include +#endif +#endif + namespace media { // A specialized buffer for interfacing with audio / video decoders. @@ -141,6 +151,11 @@ class MEDIA_EXPORT DecoderBuffer return time_info_.timestamp; } +#if BUILDFLAG(IS_TIZEN_TV) + static scoped_refptr CreateTzHandleBuffer(int handle, + int size); +#endif + // TODO(dalecurtis): This should be renamed at some point, but to avoid a yak // shave keep as a virtual with hacker_style() for now. virtual void set_timestamp(base::TimeDelta timestamp); @@ -207,8 +222,13 @@ class MEDIA_EXPORT DecoderBuffer // If there's no data in this buffer, it represents end of stream. bool end_of_stream() const { +#if BUILDFLAG(IS_TIZEN_TV) + return !read_only_mapping_.IsValid() && !writable_mapping_.IsValid() && + !external_memory_ && !data_ && !tz_handle_; +#else return !read_only_mapping_.IsValid() && !writable_mapping_.IsValid() && !external_memory_ && !data_; +#endif } bool is_key_frame() const { @@ -246,6 +266,39 @@ class MEDIA_EXPORT DecoderBuffer // Returns a human-readable string describing |*this|. std::string AsHumanReadableString(bool verbose = false) const; +#if BUILDFLAG(IS_TIZEN_TV) + size_t tz_handle() const { + DCHECK(!end_of_stream()); + return tz_handle_; + } + + void set_tz_handle(size_t tz_handle) { + DCHECK(!end_of_stream()); + tz_handle_ = tz_handle; + } + + size_t tz_buffer_size() const { + DCHECK(!end_of_stream()); + return tz_buffer_size_; + } + + void set_tz_buffer_size(size_t tz_buffer_size) { + DCHECK(!end_of_stream()); + tz_buffer_size_ = tz_buffer_size; + } + +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) + void set_decryptor_handle(eme::eme_decryptor_t decryptor_handle) { + DCHECK(!end_of_stream()); + decryptor_handle_ = decryptor_handle; + } + size_t decryptor_handle() const { + DCHECK(!end_of_stream()); + return decryptor_handle_; + } +#endif +#endif + protected: friend class base::RefCountedThreadSafe; @@ -287,6 +340,12 @@ class MEDIA_EXPORT DecoderBuffer // Encryption parameters for the encoded data. std::unique_ptr decrypt_config_; +#if BUILDFLAG(IS_TIZEN_TV) + size_t decryptor_handle_{0}; + size_t tz_handle_{0}; + size_t tz_buffer_size_{0}; +#endif + // Whether the frame was marked as a keyframe in the container. bool is_key_frame_ = false; diff --git a/media/base/decryptor.cc b/media/base/decryptor.cc index fe5fcb3..f4b02e7 100644 --- a/media/base/decryptor.cc +++ b/media/base/decryptor.cc @@ -17,6 +17,10 @@ const char* Decryptor::GetStatusName(Status status) { return "need_more_data"; case kError: return "error"; +#if BUILDFLAG(IS_TIZEN_TV) + case kBufferFull: + return "buffer_full"; +#endif } } diff --git a/media/base/decryptor.h b/media/base/decryptor.h index 5e67e62..03c63e1 100644 --- a/media/base/decryptor.h +++ b/media/base/decryptor.h @@ -12,6 +12,12 @@ #include "media/base/audio_buffer.h" #include "media/base/media_export.h" +#if BUILDFLAG(IS_TIZEN_TV) +#include "media/base/decoder_buffer.h" + +#include +#endif + namespace media { class AudioDecoderConfig; @@ -31,10 +37,20 @@ class MEDIA_EXPORT Decryptor { kSuccess, // Decryption successfully completed. Decrypted buffer ready. kNoKey, // No key is available to decrypt. kNeedMoreData, // Decoder needs more data to produce a frame. - kError, // Key is available but an error occurred during decryption. +#if BUILDFLAG(IS_TIZEN_TV) + kBufferFull, // Decrypt buffer is full. +#endif + kError, // Key is available but an error occurred during decryption. kStatusMax = kError }; +#if BUILDFLAG(IS_TIZEN_TV) + enum class DecryptBufferFullStrategy { + kRetryDecryption, // Retry decryption when buffer is full. This is default. + kAbortDecryption, // Return decryption error when buffer is full. + }; +#endif + static const char* GetStatusName(Status status); enum StreamType { kAudio, kVideo, kStreamTypeMax = kVideo }; @@ -73,6 +89,17 @@ class MEDIA_EXPORT Decryptor { scoped_refptr encrypted, DecryptCB decrypt_cb) = 0; +#if BUILDFLAG(IS_TIZEN_TV) + // If DecryptCB is oncecallback, we only need to cancel the pending decrpt + // But it can't run the oncecallback again when cancel decrypt. + // So change it to repeating callback which is same with M76 + using DecryptCBTizen = + base::RepeatingCallback)>; + virtual void DecryptTizen(StreamType stream_type, + scoped_refptr encrypted, + DecryptCBTizen decrypt_cb) {} +#endif + // Cancels the scheduled decryption operation for |stream_type| and fires the // pending DecryptCB immediately with kSuccess and NULL. // Decrypt() should not be called again before the pending DecryptCB for the @@ -92,6 +119,17 @@ class MEDIA_EXPORT Decryptor { virtual void InitializeVideoDecoder(const VideoDecoderConfig& config, DecoderInitCB init_cb) = 0; +#if BUILDFLAG(IS_TIZEN_TV) + // Returns decryptor handle + virtual eme::eme_decryptor_t GetDecryptorHandle() { + return eme::eme_decryptor_t{0}; + } + + // Set the strategy used when "buffer full" error is returned + // from decryptor. + virtual void SetDecryptStrategy(DecryptBufferFullStrategy) {} +#endif + // Helper structure for managing multiple decoded audio buffers per input. typedef std::list > AudioFrames; diff --git a/media/base/key_systems.cc b/media/base/key_systems.cc index dccc084..185117c 100644 --- a/media/base/key_systems.cc +++ b/media/base/key_systems.cc @@ -236,6 +236,14 @@ static bool IsPotentiallySupportedKeySystem(const std::string& key_system) { return true; } +#if BUILDFLAG(IS_TIZEN_TV) + // Playready key systems are potentially supported + const char kPlayreadyKeySystemSuffix[] = ".playready"; + if (base::EndsWith(key_system, kPlayreadyKeySystemSuffix, + base::CompareCase::SENSITIVE)) + return true; +#endif + // External or MediaFoundation Clear Key is known and supports suffixes for // testing. if (IsExternalClearKey(key_system)) diff --git a/media/base/pipeline_impl.cc b/media/base/pipeline_impl.cc index bf67b12..bfe9c8e 100644 --- a/media/base/pipeline_impl.cc +++ b/media/base/pipeline_impl.cc @@ -39,6 +39,10 @@ #include "media/base/win/mf_feature_checks.h" #endif // BUILDFLAG(IS_WIN) +#if BUILDFLAG(IS_TIZEN_TV) +#include "media/filters/decrypting_media_resource.h" +#endif + static const double kDefaultPlaybackRate = 0.0; static const float kDefaultVolume = 1.0f; @@ -215,6 +219,12 @@ class PipelineImpl::RendererWrapper final : public DemuxerHost, void DestroyRenderer(); void ReportMetadata(StartType start_type); +#if BUILDFLAG(IS_TIZEN_TV) + // Create decrypting media resource + void CreateAndInitializeDecryptingMediaResource(); + void OnDecryptInitDone(bool success); +#endif + // Returns whether there's any encrypted stream in the demuxer. bool HasEncryptedStream(); @@ -273,6 +283,7 @@ class PipelineImpl::RendererWrapper final : public DemuxerHost, #if BUILDFLAG(IS_TIZEN_TV) std::string mime_type_; + std::unique_ptr decrypting_media_resource_{nullptr}; #endif // Whether we've received the audio/video ended events. @@ -694,6 +705,31 @@ void PipelineImpl::RendererWrapper::SetContentMimeType( } #endif +#if BUILDFLAG(IS_TIZEN_TV) +void PipelineImpl::RendererWrapper::OnDecryptInitDone(bool success) { + DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); + LOG(INFO) << "OnDecryptInitDone,this:" << (void*)this; +} + +void PipelineImpl::RendererWrapper:: + CreateAndInitializeDecryptingMediaResource() { + DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); + DCHECK(cdm_context_); + decrypting_media_resource_ = std::make_unique( + demuxer_, cdm_context_, media_log_, media_task_runner_); + + LOG(INFO) << "(" << static_cast(this) << __func__ + << ") decrypting_media_resource_:" + << decrypting_media_resource_.get(); + + decrypting_media_resource_->Initialize( + base::BindOnce(&RendererWrapper::OnDecryptInitDone, + weak_factory_.GetWeakPtr()), + base::BindRepeating(&RendererWrapper::OnWaiting, + weak_factory_.GetWeakPtr())); +} +#endif + void PipelineImpl::RendererWrapper::CreateRendererInternal( PipelineStatusCallback done_cb) { DVLOG(1) << __func__; @@ -1249,7 +1285,7 @@ void PipelineImpl::RendererWrapper::CreateRenderer( DCHECK(state_ == kStarting || state_ == kResuming); if (HasEncryptedStream() && !cdm_context_) { - DVLOG(1) << __func__ << ": Has encrypted stream but CDM is not set."; + LOG(INFO) << __func__ << ": Has encrypted stream but CDM is not set."; create_renderer_done_cb_ = std::move(done_cb); OnWaiting(WaitingReason::kNoCdm); return; @@ -1302,9 +1338,15 @@ void PipelineImpl::RendererWrapper::InitializeRenderer( break; } - if (cdm_context_) + if (cdm_context_) { shared_state_.renderer->SetCdm(cdm_context_, base::DoNothing()); +#if BUILDFLAG(IS_TIZEN_TV) + // Create decrypt media resource for eme + CreateAndInitializeDecryptingMediaResource(); +#endif + } + if (latency_hint_) shared_state_.renderer->SetLatencyHint(latency_hint_); @@ -1331,11 +1373,22 @@ void PipelineImpl::RendererWrapper::InitializeRenderer( // Initialize Renderer and report timeout UMA. std::string uma_name = "Media.InitializeRendererTimeout"; base::UmaHistogramEnumeration(uma_name, CallbackTimeoutStatus::kCreate); - shared_state_.renderer->Initialize( - demuxer_, this, - WrapCallbackWithTimeoutHandler( - std::move(done_cb), /*timeout_delay=*/base::Seconds(10), - base::BindOnce(&OnCallbackTimeout, uma_name))); + +#if BUILDFLAG(IS_TIZEN_TV) + if ((MediaResource::Type::kStream == demuxer_->GetType()) && (cdm_context_)) { + if (!decrypting_media_resource_.get()) { + LOG(ERROR) << "decrypting_media_resource is not created yet"; + return; + } + shared_state_.renderer->Initialize(decrypting_media_resource_.get(), this, + std::move(done_cb)); + } else +#endif + shared_state_.renderer->Initialize( + demuxer_, this, + WrapCallbackWithTimeoutHandler( + std::move(done_cb), /*timeout_delay=*/base::Seconds(10), + base::BindOnce(&OnCallbackTimeout, uma_name))); } void PipelineImpl::RendererWrapper::DestroyRenderer() { diff --git a/media/filters/decrypting_demuxer_stream.cc b/media/filters/decrypting_demuxer_stream.cc index dee07ab..493b17e 100644 --- a/media/filters/decrypting_demuxer_stream.cc +++ b/media/filters/decrypting_demuxer_stream.cc @@ -229,6 +229,19 @@ void DecryptingDemuxerStream::OnBufferReadFromDemuxerStream( return; } +#if BUILDFLAG(IS_TIZEN_TV) + if (status == kNeedBuffer) { + LOG(ERROR) << __func__ << ": need buffer"; + std::move(read_cb_).Run(status, {}); + return; + } + + if (!buffer) { + LOG(ERROR) << "buffer is null."; + return; + } +#endif + DCHECK_EQ(kOk, status); if (buffer->end_of_stream()) { @@ -279,10 +292,19 @@ void DecryptingDemuxerStream::DecryptPendingBuffer() { switched_clear_to_encrypted_ = true; } +#if BUILDFLAG(IS_TIZEN_TV) + decryptor_->DecryptTizen( + GetDecryptorStreamType(), pending_buffer_to_decrypt_, + base::BindPostTaskToCurrentDefault( + base::BindRepeating(&DecryptingDemuxerStream::OnBufferDecrypted, + weak_factory_.GetWeakPtr()))); +#else + decryptor_->Decrypt(GetDecryptorStreamType(), pending_buffer_to_decrypt_, base::BindPostTaskToCurrentDefault(base::BindOnce( &DecryptingDemuxerStream::OnBufferDecrypted, weak_factory_.GetWeakPtr()))); +#endif } void DecryptingDemuxerStream::OnBufferDecrypted( @@ -305,7 +327,9 @@ void DecryptingDemuxerStream::OnBufferDecrypted( return; } +#if !BUILDFLAG(IS_TIZEN_TV) DCHECK_EQ(status == Decryptor::kSuccess, decrypted_buffer.get() != nullptr); +#endif if (status == Decryptor::kError || status == Decryptor::kNeedMoreData) { DVLOG(2) << __func__ << ": Error with status " << status; @@ -344,12 +368,23 @@ void DecryptingDemuxerStream::OnBufferDecrypted( DCHECK_EQ(status, Decryptor::kSuccess); +#if BUILDFLAG(IS_TIZEN_TV) + // Returning status kSuccess and nullptr decryptor buffer is a way to + // inform that Decryptor interface cancelled decrypting. + if (decrypted_buffer.get() == nullptr) { + pending_buffer_to_decrypt_ = nullptr; + state_ = kIdle; + std::move(read_cb_).Run(kAborted, {}); + return; + } +#else // Copy the key frame flag and duration from the encrypted to decrypted // buffer. // TODO(crbug.com/1116263): Ensure all fields are copied by Decryptor. decrypted_buffer->set_is_key_frame( pending_buffer_to_decrypt_->is_key_frame()); decrypted_buffer->set_duration(pending_buffer_to_decrypt_->duration()); +#endif pending_buffer_to_decrypt_ = nullptr; state_ = kIdle; diff --git a/media/mojo/common/media_type_converters.cc b/media/mojo/common/media_type_converters.cc index 7647de0..02122ba 100644 --- a/media/mojo/common/media_type_converters.cc +++ b/media/mojo/common/media_type_converters.cc @@ -97,6 +97,13 @@ TypeConverter::Convert( mojo_buffer->front_discard = input.discard_padding().first; mojo_buffer->back_discard = input.discard_padding().second; +#if BUILDFLAG(IS_TIZEN_TV) + mojo_buffer->tz_handle = base::checked_cast(input.tz_handle()); + mojo_buffer->tz_buffer_size = input.tz_buffer_size(); + mojo_buffer->decryptor_handle = + base::checked_cast(input.decryptor_handle()); +#endif + mojo_buffer->side_data = media::mojom::DecoderBufferSideData::From(input.side_data()); @@ -132,6 +139,12 @@ TypeConverter, buffer->set_duration(input->duration); buffer->set_is_key_frame(input->is_key_frame); +#if BUILDFLAG(IS_TIZEN_TV) + buffer->set_tz_handle(input->tz_handle); + buffer->set_tz_buffer_size(input->tz_buffer_size); + buffer->set_decryptor_handle(input->decryptor_handle); +#endif + if (input->decrypt_config) { buffer->set_decrypt_config( input->decrypt_config.To>()); diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom index 57eccec6..475edc8 100644 --- a/media/mojo/mojom/media_types.mojom +++ b/media/mojo/mojom/media_types.mojom @@ -272,6 +272,13 @@ struct DecoderBuffer { mojo_base.mojom.TimeDelta front_discard; mojo_base.mojom.TimeDelta back_discard; + [EnableIf=is_tizen_tv] + uint32 tz_handle; + [EnableIf=is_tizen_tv] + uint32 tz_buffer_size; + [EnableIf=is_tizen_tv] + uint64 decryptor_handle; + DecoderBufferSideData? side_data; }; diff --git a/packaging/chromium-efl.spec b/packaging/chromium-efl.spec index a1100cc..6cfff52 100644 --- a/packaging/chromium-efl.spec +++ b/packaging/chromium-efl.spec @@ -20,6 +20,12 @@ Source1: content_shell.in %define _nodebug 1 %endif +%ifarch aarch64 +%define __drm_mapi_aarch_64 1 +%else +%define __drm_mapi_aarch_64 0 +%endif + %if 0%{?_nodebug} %global __debug_install_post %{nil} %global debug_package %{nil} @@ -540,6 +546,9 @@ touch ./tizen_src/downloadable/ewk_api_wrapper_generator.py %if %{__enable_network_camera} "enable_network_camera=true" \ %endif +%if %{__drm_mapi_aarch_64} && "%{?tizen_profile_name}" == "tv" && %{tizen_version} >= 80 + "drm_mapi_aarch_64=true" \ +%endif %endif # _skip_gn ninja %{_smp_mflags} -C "%{OUTPUT_FOLDER}" \ diff --git a/tizen_src/build/config/BUILD.gn b/tizen_src/build/config/BUILD.gn index 374d189..b95bf9d 100644 --- a/tizen_src/build/config/BUILD.gn +++ b/tizen_src/build/config/BUILD.gn @@ -112,4 +112,8 @@ config("tizen_feature_flags") { if (build_chrome) { defines += [ "BUILD_CHROME" ] } + + if (drm_mapi_aarch_64) { + defines += [ "DRM_MAPI_AARCH_64" ] + } } diff --git a/tizen_src/build/config/tizen_features.gni b/tizen_src/build/config/tizen_features.gni index 06cbe65..94c5421 100644 --- a/tizen_src/build/config/tizen_features.gni +++ b/tizen_src/build/config/tizen_features.gni @@ -71,6 +71,8 @@ declare_args() { tizen_resource_manager = false enable_network_camera = false tizen_thread_booster_service = false + + drm_mapi_aarch_64 = false } if (is_tizen && !build_chrome) { diff --git a/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc b/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc new file mode 100644 index 0000000..dc2a421 --- /dev/null +++ b/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc @@ -0,0 +1,44 @@ +// Copyright 2023 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 "content/renderer/media/cdm/render_cdm_factory.h" + +#include "media/base/cdm_config.h" +#include "media/base/cdm_promise.h" +#include "media/base/content_decryption_module.h" +#include "media/base/key_systems.h" +#include "media/filters/ieme_drm_bridge.h" +#include "third_party/widevine/cdm/widevine_cdm_common.h" + +namespace content { + +RenderCdmFactory::RenderCdmFactory() {} + +RenderCdmFactory::~RenderCdmFactory() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void RenderCdmFactory::Create( + const media::CdmConfig& cdm_config, + const media::SessionMessageCB& session_message_cb, + const media::SessionClosedCB& session_closed_cb, + const media::SessionKeysChangeCB& session_keys_change_cb, + const media::SessionExpirationUpdateCB& session_expiration_update_cb, + media::CdmCreatedCB cdm_created_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Currently privacy mode will be only used for Widevine. Other platform + // CDMs do not implement it. However, it is still possible to use Widevine + // without privacy mode. + const bool use_privacy_mode = cdm_config.key_system == kWidevineKeySystem; + + // Tizen TV should use IEME for all key systems. + scoped_refptr cdm(media::CreateIEMEDrmBridge( + cdm_config.key_system, session_message_cb, session_closed_cb, + session_keys_change_cb, session_expiration_update_cb, use_privacy_mode)); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, base::BindOnce(std::move(cdm_created_cb), cdm, "")); +} + +} // namespace content \ No newline at end of file diff --git a/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h b/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h new file mode 100644 index 0000000..558ad42 --- /dev/null +++ b/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h @@ -0,0 +1,41 @@ +// Copyright 2023 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 CONTENT_RENDERER_MEDIA_CDM_RENDER_CDM_FACTORY_H_ +#define CONTENT_RENDERER_MEDIA_CDM_RENDER_CDM_FACTORY_H_ + +#include +#include + +#include "base/threading/thread_checker.h" +#include "media/base/cdm_factory.h" + +namespace content { + +// CdmFactory implementation in content/renderer. This class is not thread safe +// and should only be used on one thread. +class RenderCdmFactory : public media::CdmFactory { + public: + RenderCdmFactory(); + RenderCdmFactory(const RenderCdmFactory&) = delete; + RenderCdmFactory& operator=(const RenderCdmFactory&) = delete; + + ~RenderCdmFactory() override; + + // CdmFactory implementation + void Create( + const media::CdmConfig& cdm_config, + const media::SessionMessageCB& session_message_cb, + const media::SessionClosedCB& session_closed_cb, + const media::SessionKeysChangeCB& session_keys_change_cb, + const media::SessionExpirationUpdateCB& session_expiration_update_cb, + media::CdmCreatedCB cdm_created_cb) override; + + private: + base::ThreadChecker thread_checker_; +}; + +} // namespace content + +#endif // CONTENT_RENDERER_MEDIA_CDM_RENDER_CDM_FACTORY_H_ \ No newline at end of file diff --git a/tizen_src/chromium_impl/content/renderer/renderer_efl.gni b/tizen_src/chromium_impl/content/renderer/renderer_efl.gni index 1b19cae..de438fe 100644 --- a/tizen_src/chromium_impl/content/renderer/renderer_efl.gni +++ b/tizen_src/chromium_impl/content/renderer/renderer_efl.gni @@ -14,6 +14,12 @@ external_content_renderer_efl_configs = [ "//tizen_src/build:libtts", ] +if (tizen_multimedia) { + external_content_renderer_efl_configs += [ + "//tizen_src/build:drmdecrypt", + ] +} + ############################################################################## # Dependency ############################################################################## @@ -35,3 +41,10 @@ if (tizen_multimedia) { "//tizen_src/chromium_impl/content/renderer/media/tizen/media_player_renderer_client_factory.h", ] } + +if (tizen_product_tv) { + external_content_renderer_efl_sources += [ + "//tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc", + "//tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h", + ] +} diff --git a/tizen_src/chromium_impl/media/filters/esplusplayer_util.cc b/tizen_src/chromium_impl/media/filters/esplusplayer_util.cc index 13cb234..6d62cd9 100644 --- a/tizen_src/chromium_impl/media/filters/esplusplayer_util.cc +++ b/tizen_src/chromium_impl/media/filters/esplusplayer_util.cc @@ -104,6 +104,11 @@ esplusplayer_audio_mime_type ConvertToESPlusAudioMimeType( case media::AudioCodec::kAC3: audioMimeType = ESPLUSPLAYER_AUDIO_MIME_TYPE_AC3; break; +#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(8, 0, 0) + case media::AudioCodec::kFLAC: + audioMimeType = ESPLUSPLAYER_AUDIO_MIME_TYPE_FLAC; + break; +#endif default: { LOG(WARNING) << "Unknown codec :" << codec << ". Returning MP3."; audioMimeType = ESPLUSPLAYER_AUDIO_MIME_TYPE_MP3; diff --git a/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc b/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc new file mode 100644 index 0000000..25ae845 --- /dev/null +++ b/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc @@ -0,0 +1,794 @@ +// Copyright 2023 Samsung Electronics Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/filters/ieme_drm_bridge.h" + +#include +#include +#include + +#include "base/command_line.h" +#include "base/functional/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_number_conversions.h" +#include "content/public/common/content_switches.h" +#include "media/base/decoder_buffer.h" +#include "url/gurl.h" + +namespace media { + +namespace { + +static const base::TimeDelta kEmeDecryptRetryTime = base::Milliseconds(80); + +// Helper class to invoke free in smart pointers +struct libc_free { + template + void operator()(Raw* ptr) { + ::free(ptr); + } +}; + +// Convert |session_type| to IEME SessionType used in session_create +eme::SessionType ConvertSessionType(CdmSessionType session_type) { + switch (session_type) { + case CdmSessionType::kTemporary: + return eme::kTemporary; + case CdmSessionType::kPersistentLicense: + return eme::kPersistentLicense; + default: + LOG(ERROR) << "Not supported session type " + << static_cast(session_type) << "."; + return eme::kTemporary; + } +} + +// Convert |message_type| from IEME message callback +media::CdmMessageType ConvertMessageType(eme::MessageType message_type) { + switch (message_type) { + case eme::kLicenseRequest: + return CdmMessageType::LICENSE_REQUEST; + case eme::kLicenseRenewal: + return CdmMessageType::LICENSE_RENEWAL; + case eme::kLicenseRelease: + return CdmMessageType::LICENSE_RELEASE; + case eme::kIndividualizationRequest: + return CdmMessageType::LICENSE_REQUEST; + default: + LOG(ERROR) << "Not supported message type " << message_type << "."; + return CdmMessageType::LICENSE_REQUEST; + } +} + +// Convert |init_data_type| to IEME InitDataType used in session_generateRequest +eme::InitDataType ConvertInitDataType(media::EmeInitDataType init_data_type) { + switch (init_data_type) { + case media::EmeInitDataType::WEBM: + return eme::kWebM; + case media::EmeInitDataType::CENC: + return eme::kCenc; + case media::EmeInitDataType::KEYIDS: + return eme::kKeyIds; + default: + LOG(ERROR) + << "Not supported message init date type " + << static_cast::type>( + init_data_type) + << "."; + // TODO(m.debski): Let's hope IEME checks this and returns reasonable + // error, is it ok to assume that? We can reject promise ourselves. + return std::numeric_limits::max(); + } +} + +CdmKeyInformation::KeyStatus ConvertKeyStatus(eme::KeyStatus key_status) { + switch (key_status) { + case eme::kUsable: + return CdmKeyInformation::USABLE; + case eme::kExpired: + return CdmKeyInformation::EXPIRED; + case eme::kOutputRestricted: + return CdmKeyInformation::OUTPUT_RESTRICTED; + case eme::kStatusPending: + // TODO(xhwang): This should probably be renamed to "PENDING". + return CdmKeyInformation::KEY_STATUS_PENDING; + case eme::kInternalError: + return CdmKeyInformation::INTERNAL_ERROR; + case eme::kReleased: + return CdmKeyInformation::RELEASED; + default: + LOG(ERROR) << "Not supported message key status " << key_status << "."; + return CdmKeyInformation::INTERNAL_ERROR; + } +} + +// It should be used only to convert erros. Do not use it +// for kSuccess status because CdmPromise::UNKNOWN_ERROR +// will be returned. +CdmPromise::Exception ConvertEmeError(eme::Status status) { + DCHECK(status != eme::kSuccess); + switch (status) { + case eme::kInvalidAccess: + return CdmPromise::Exception::TYPE_ERROR; + case eme::kNotSupported: + return CdmPromise::Exception::NOT_SUPPORTED_ERROR; + case eme::kInvalidState: + return CdmPromise::Exception::INVALID_STATE_ERROR; + case eme::kQuotaExceeded: + return CdmPromise::Exception::QUOTA_EXCEEDED_ERROR; + default: + return CdmPromise::Exception::TYPE_ERROR; + } +} + +std::string ConvertEmeErrorToString(eme::Status status) { + DCHECK(status != eme::kSuccess); + switch (status) { + case eme::kInvalidAccess: + return "Invalid access error"; + case eme::kNotSupported: + return "Operation not supported"; + case eme::kInvalidState: + return "Invalid state"; + case eme::kQuotaExceeded: + return "Quota was exceed"; + default: + return ""; + } +} + +msd_cipher_algorithm ConvertEncryptionScheme( + EncryptionScheme encryption_scheme) { + switch (encryption_scheme) { + case EncryptionScheme::kCenc: + return MSD_AES128_CTR; + case EncryptionScheme::kCbcs: + return MSD_AES128_CBC; + default: + LOG(WARNING) << "Unknown encryption scheme"; + return MSD_AES128_CTR; + } +} + +void RejectCdmPromise(CdmPromise& promise, + eme::Status error, + const std::string& message) { + DCHECK(error != eme::kSuccess); + std::stringstream ss; + ss << message << ". " << ConvertEmeErrorToString(error) << "."; + promise.reject(ConvertEmeError(error), error, ss.str()); +} + +std::string CreateStringFromBinaryData(const std::vector& data) { + // This way string can contain null characters + return std::string(reinterpret_cast(data.data()), data.size()); +} + +std::vector CreateBinaryDataFromString(const std::string& data) { + const auto data_ptr = reinterpret_cast(data.data()); + const auto data_size = sizeof(*data.data()) * data.size(); + CHECK(data_size >= 0); + return std::vector(data_ptr, data_ptr + data_size); +} + +} // namespace + +// static +bool IEMEDrmBridge::IsKeySystemSupported(const std::string& key_system) { + CHECK(!key_system.empty()); + return eme::IEME::isKeySystemSupported(key_system) == eme::kSupported; +} + +IEMEDrmBridge::IEMEDrmBridge( + const std::string& key_system, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb, + bool privacy_mode) + : key_system_(key_system), + session_message_cb_(session_message_cb), + session_closed_cb_(session_closed_cb), + session_keys_change_cb_(session_keys_change_cb), + session_expiration_update_cb_(session_expiration_update_cb), + cdm_promise_adapter_(new CdmPromiseAdapter()), + single_process_mode_(false), + task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()), + decrypt_buffer_full_strategy_( + DecryptBufferFullStrategy::kRetryDecryption), + weak_factory_(this) { + LOG(INFO) << "Constructing DRM for " << key_system_; + single_process_mode_ = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSingleProcess); + LOG(INFO) << "single process mode:" << std::boolalpha << single_process_mode_; + // TODO(mdebski): what is privacy_mode? + cdm_.reset(eme::IEME::create(this, key_system_, false)); + CHECK(cdm_.get()); +} + +IEMEDrmBridge::~IEMEDrmBridge() { + LOG(INFO) << "Destroying IEMEDrmBridge"; + + // Destroy our CDM so no callbacks are called from this point onwards. + cdm_.reset(); + cdm_promise_adapter_.reset(); +} + +// static +ScopedIEMEDrmBridgePtr IEMEDrmBridge::Create( + const std::string& key_system, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb, + bool privacy_mode) { + LOG(INFO) << "Creating DRM bridge for key system: " << key_system; + ScopedIEMEDrmBridgePtr ieme_drm_bridge; + + if (IsKeySystemSupported(key_system)) { + ieme_drm_bridge.reset(new IEMEDrmBridge( + key_system, session_message_cb, session_closed_cb, + session_keys_change_cb, session_expiration_update_cb, privacy_mode)); + + if (ieme_drm_bridge->cdm_ == nullptr) + ieme_drm_bridge.reset(); + } + + return ieme_drm_bridge; +} + +// static +ScopedIEMEDrmBridgePtr IEMEDrmBridge::CreateWithoutSessionSupport( + const std::string& key_system) { + return IEMEDrmBridge::Create(key_system, SessionMessageCB(), + SessionClosedCB(), SessionKeysChangeCB(), + SessionExpirationUpdateCB(), false); +} + +void IEMEDrmBridge::SetServerCertificate( + const std::vector& certificate, + std::unique_ptr promise) { + LOG(INFO) << "Setting server certificate"; + CHECK(!certificate.empty()); + + const auto result_status = + cdm_->setServerCertificate(CreateStringFromBinaryData(certificate)); + if (eme::kSuccess == result_status) { + promise->resolve(); + } else { + LOG(ERROR) << "Setting server certificate failed with status: " + << result_status; + promise->reject(CdmPromise::Exception::TYPE_ERROR, 0, + "Set server certificate failed."); + } +} + +void IEMEDrmBridge::CreateSessionAndGenerateRequest( + CdmSessionType session_type, + media::EmeInitDataType init_data_type, + const std::vector& init_data, + std::unique_ptr promise) { + LOG(INFO) << "Creating session session_type: " + << static_cast(session_type) << " init_data_type: " + << static_cast::type>( + init_data_type); + + std::string session_id; + const auto create_result_status = + cdm_->session_create(ConvertSessionType(session_type), &session_id); + if (eme::kSuccess != create_result_status) { + LOG(ERROR) << "Creating session failed with status: " + << create_result_status; + RejectCdmPromise(*promise, create_result_status, "Session creation failed"); + return; + } else if (session_id.empty()) { + LOG(ERROR) << "Session empty"; + promise->reject(CdmPromise::Exception::TYPE_ERROR, create_result_status, + "Returned session Id is empty."); + return; + } + + LOG(INFO) << "session_create success:" << session_id; + const auto generate_result_status = cdm_->session_generateRequest( + session_id, ConvertInitDataType(init_data_type), + CreateStringFromBinaryData(init_data)); + if (eme::kSuccess != generate_result_status) { + LOG(ERROR) << "IEME::session_generateRequest failed with status: " + << generate_result_status; + RejectCdmPromise(*promise, generate_result_status, + "Request generation failed"); + return; + } + + LOG(INFO) << "session_generateRequest success:" << session_id; + promise->resolve(session_id); +} + +void IEMEDrmBridge::LoadSession( + CdmSessionType session_type, + const std::string& session_id, + std::unique_ptr promise) { + LOG(INFO) << "Loading session id: " << session_id; + + CHECK(session_type != CdmSessionType::kTemporary); + + const auto load_result_status = cdm_->session_load(session_id); + if (eme::kSuccess != load_result_status) { + LOG(ERROR) << "Loading session failed with status: " << load_result_status; + RejectCdmPromise(*promise, load_result_status, "Loading session failed"); + return; + } + + promise->resolve(session_id); + onKeyStatusesChange(session_id); +} + +void IEMEDrmBridge::UpdateSession( + const std::string& session_id, + const std::vector& response, + std::unique_ptr promise) { + LOG(INFO) << "Updating session id: " << session_id; + + const auto update_result_status = + cdm_->session_update(session_id, CreateStringFromBinaryData(response)); + if (eme::kSuccess != update_result_status) { + LOG(ERROR) << "Updating session failed with status: " + << update_result_status; + RejectCdmPromise(*promise, update_result_status, "Updating session failed"); + return; + } + + promise->resolve(); + onKeyStatusesChange(session_id); +} + +void IEMEDrmBridge::CloseSession( + const std::string& session_id, + std::unique_ptr promise) { + LOG(INFO) << "Closing session id: " << session_id; + + const auto close_result_status = cdm_->session_close(session_id); + if (eme::kSuccess != close_result_status) { + LOG(ERROR) << "Closing failed with status: " << close_result_status; + RejectCdmPromise(*promise, close_result_status, "Closing session failed."); + return; + } + + session_key_statuses_map_.erase(session_id); + last_expiration_times_map_.erase(session_id); + + promise->resolve(); + task_runner_->PostTask(FROM_HERE, + base::BindRepeating(session_closed_cb_, session_id, + CdmSessionClosedReason::kClose)); + onKeyStatusesChange(session_id); +} + +void IEMEDrmBridge::RemoveSession( + const std::string& session_id, + std::unique_ptr promise) { + LOG(INFO) << "Removing session id: " << session_id; + + const auto remove_result_status = cdm_->session_remove(session_id); + if (eme::kSuccess != remove_result_status) { + LOG(ERROR) << "Removing session id: " << session_id + << " failed with status: " << remove_result_status; + RejectCdmPromise(*promise, remove_result_status, + "Removing session failed."); + return; + } + + CHECK(session_promise_map_.find(session_id) == session_promise_map_.end()); + session_promise_map_[session_id] = + cdm_promise_adapter_->SavePromise(std::move(promise)); + onKeyStatusesChange(session_id); +} + +CdmContext* IEMEDrmBridge::GetCdmContext() { + return this; +} + +eme::eme_decryptor_t IEMEDrmBridge::GetDecryptorHandle() { + return cdm_->getDecryptor(""); +} + +const std::string& IEMEDrmBridge::GetKeySystem() const { + return key_system_; +} + +Decryptor* IEMEDrmBridge::GetDecryptor() { + return this; +} + +absl::optional IEMEDrmBridge::GetCdmId() const { + // we want to use Decryptor API + // return CdmContext::kInvalidCdmId; + return absl::nullopt; +} + +std::unique_ptr IEMEDrmBridge::RegisterEventCB( + EventCB event_cb) { + return event_callbacks_.Register(std::move(event_cb)); +} + +#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(6, 0, 0) +bool IEMEDrmBridge::IsKeyReady(const std::string& kid) { + for (auto const& session_key : session_key_statuses_map_) { + KayStatusesMap key_status = session_key.second; + auto iter = key_status.find(kid); + if (iter == key_status.end()) + continue; + if (iter->second == CdmKeyInformation::USABLE) + return true; + } + return false; +} +#endif + +void IEMEDrmBridge::DecryptInChromium( + StreamType stream_type, + const scoped_refptr encrypted, + DecryptCBTizen decrypt_cb) { + auto decrypt_conf = encrypted->decrypt_config(); + scoped_refptr decrypted = encrypted; + Decryptor::Status status = Decryptor::kError; + + MSD_SUBSAMPLE_INFO subsamples_info; + std::vector subsamples; + subsamples.reserve(decrypt_conf->subsamples().size()); + for (const auto& subsample : decrypt_conf->subsamples()) { + subsamples_info.uBytesOfClearData = subsample.clear_bytes; + subsamples_info.uBytesOfEncryptedData = subsample.cypher_bytes; + subsamples.emplace_back(subsamples_info); + } + + MSD_FMP4_DATA subsamples_data; + subsamples_data.uSubSampleCount = subsamples.size(); + subsamples_data.pSubSampleInfo = subsamples.data(); + + msd_cipher_param_s params; + params.algorithm = ConvertEncryptionScheme(decrypt_conf->encryption_scheme()); + params.format = MSD_FORMAT_FMP4; + params.phase = MSD_PHASE_NONE; + params.buseoutbuf = false; + params.pkid = reinterpret_cast( + const_cast(decrypt_conf->key_id().data())); + params.ukidlen = decrypt_conf->key_id().size(); + params.pdata = reinterpret_cast( + const_cast(encrypted->data())); + params.udatalen = encrypted->data_size(); + params.poutbuf = nullptr; + params.uoutbuflen = 0; + params.piv = reinterpret_cast( + const_cast(decrypt_conf->iv().data())); + params.uivlen = decrypt_conf->iv().size(); + params.psubdata = &subsamples_data; + params.psplitoffsets = nullptr; + + /* + CTR encryption : + CENS : pattern->crypt_byte_block() != 0 || pattern->skip_byte_block() != 0 + CENC : pattern->crypt_byte_block() == 0 && pattern->skip_byte_block() == 0 + DRM not support CENS. + */ + if (decrypt_conf->HasPattern()) { + const auto& pattern = decrypt_conf->encryption_pattern(); + params.busepattern = true; + params.uCrypt_byte_block = pattern->crypt_byte_block(); + params.uSkip_byte_block = pattern->skip_byte_block(); + } + + msd_cipher_param_s* params_ptr = ¶ms; + + // This one was allocated with malloc so should be deallocated with free + handle_and_size_s* decrypted_params_ptr = nullptr; + + auto ret = eme_decryptarray(GetDecryptorHandle(), ¶ms_ptr, 1, nullptr, 0, + &decrypted_params_ptr); + std::unique_ptr scoped_decrypted_params_ptr{ + decrypted_params_ptr}; + if (ret == E_SUCCESS) { + decrypted = DecoderBuffer::CreateTzHandleBuffer( + decrypted_params_ptr->handle, decrypted_params_ptr->size); + decrypted->set_timestamp(encrypted->timestamp()); + decrypted->set_duration(encrypted->duration()); + status = Decryptor::kSuccess; + } else if (ret == E_NEED_KEY) { + LOG(WARNING) << "eme_decryptarray() failed no decryption key"; + status = Decryptor::kNoKey; + } +#if BUILDFLAG(IS_TIZEN_TV) + else if (ret == E_DECRYPT_BUFFER_FULL && + decrypt_buffer_full_strategy_ == + DecryptBufferFullStrategy::kAbortDecryption) { + status = Decryptor::kBufferFull; + } +#endif + else if (ret == E_DECRYPT_BUFFER_FULL) { + LOG(WARNING) << "eme_decryptarray() TZ buffer full, stream type: " + << (stream_type == Decryptor::kAudio ? "AUDIO" : "VIDEO"); + decrypt_cb_map_[stream_type].last_decrypt_cb_ = decrypt_cb; + decrypt_cb_map_[stream_type].pending_decrypt_cb_.Reset( + base::BindOnce(&IEMEDrmBridge::DecryptTizen, weak_factory_.GetWeakPtr(), + stream_type, encrypted, decrypt_cb)); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, decrypt_cb_map_[stream_type].pending_decrypt_cb_.callback(), + kEmeDecryptRetryTime); + return; + } else { + LOG(ERROR) << "eme_decryptarray() failed with err code: " << ret; + status = Decryptor::kError; + } + + decrypt_cb.Run(status, decrypted); + decrypt_cb_map_.erase(stream_type); +} + +void IEMEDrmBridge::Decrypt(StreamType stream_type, + const scoped_refptr encrypted, + DecryptCB decrypt_cb) {} + +void IEMEDrmBridge::DecryptTizen(StreamType stream_type, + const scoped_refptr encrypted, + DecryptCBTizen decrypt_cb) { + auto decrypt_conf = encrypted->decrypt_config(); + scoped_refptr decrypted = encrypted; + + // |Decrypt()| is asynchronously called so |cdm_| can be nullptr. + if (!cdm_) { + LOG(ERROR) << "cdm is nullptr"; + decrypt_cb.Run(Decryptor::kNoKey, decrypted); + return; + } + + if (!decrypt_conf) { + LOG(ERROR) << "decrypt_conf should not be nullptr"; + decrypt_cb.Run(Decryptor::kError, decrypted); + decrypt_cb_map_.erase(stream_type); + return; + } + + if (decrypt_conf && decrypt_conf->iv().size() <= 0) { + LOG(ERROR) << "iv size <= 0"; + decrypt_cb.Run(Decryptor::kSuccess, decrypted); + decrypt_cb_map_.erase(stream_type); + return; + } + +#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(6, 0, 0) + if (single_process_mode_) { + if (!IsKeyReady(decrypt_conf->key_id())) { + LOG(ERROR) << base::HexEncode(decrypt_conf->key_id().data(), + decrypt_conf->key_id().size()) + << " is not ready yet"; + decrypt_cb.Run(Decryptor::kNoKey, encrypted); + return; + } + + if (!GetDecryptorHandle()) { + LOG(ERROR) << "decryptor handle is not ready yet"; + decrypt_cb.Run(Decryptor::kNoKey, encrypted); + return; + } + + // esplusplayer will decrypt the frames + encrypted->set_decryptor_handle(GetDecryptorHandle()); + decrypt_cb.Run(Decryptor::kSuccess, encrypted); + decrypt_cb_map_.erase(stream_type); + } else +#endif + DecryptInChromium(stream_type, encrypted, decrypt_cb); +} + +void IEMEDrmBridge::CancelDecrypt(StreamType stream_type) { + // Decrypt() calls the DecryptCB synchronously, but if DRM return + // E_DECRYPT_BUFFER_FULL, IEME will repeat decryption. During + // CancelDecrypt we want to break this loop and finish decryption. + if (decrypt_cb_map_.count(stream_type) == 1) { + decrypt_cb_map_[stream_type].pending_decrypt_cb_.Cancel(); + decrypt_cb_map_[stream_type].last_decrypt_cb_.Run(Decryptor::kSuccess, + nullptr); + decrypt_cb_map_.erase(stream_type); + } +} + +// We problably won't use that +void IEMEDrmBridge::InitializeAudioDecoder(const AudioDecoderConfig& config, + DecoderInitCB init_cb) { + // IEMEDrmBridge does not support audio decoding. + std::move(init_cb).Run(false); +} + +void IEMEDrmBridge::InitializeVideoDecoder(const VideoDecoderConfig& config, + DecoderInitCB init_cb) { + // IEMEDrmBridge does not support video decoding. + std::move(init_cb).Run(false); +} + +void IEMEDrmBridge::DecryptAndDecodeAudio( + const scoped_refptr encrypted, + AudioDecodeCB audio_decode_cb) { + NOTREACHED() << "IEMEDrmBridge does not support audio decoding"; +} + +void IEMEDrmBridge::DecryptAndDecodeVideo( + const scoped_refptr encrypted, + VideoDecodeCB video_decode_cb) { + NOTREACHED() << "IEMEDrmBridge does not support video decoding"; +} + +void IEMEDrmBridge::ResetDecoder(StreamType stream_type) { + NOTREACHED() << "IEMEDrmBridge does not support audio/video decoding"; +} + +void IEMEDrmBridge::DeinitializeDecoder(StreamType stream_type) { + // IEMEDrmBridge does not support audio/video decoding, but since this can be + // called any time after InitializeAudioDecoder/InitializeVideoDecoder, + // nothing to be done here. +} + +void IEMEDrmBridge::IEMEDrmBridge::onMessage(const std::string& session_id, + eme::MessageType message_type, + const std::string& message) { + LOG(INFO) << "onMessage, session: " << session_id << " type: " << message_type + << " message: " << message; + + if (eme::kLicenseAlreadyDone == message_type) + return; + + task_runner_->PostTask(FROM_HERE, + base::BindOnce(session_message_cb_, session_id, + ConvertMessageType(message_type), + CreateBinaryDataFromString(message))); +} + +void IEMEDrmBridge::onKeyStatusesChange(const std::string& session_id) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&IEMEDrmBridge::ChangeKeyStatuses, + weak_factory_.GetWeakPtr(), session_id)); +} + +void IEMEDrmBridge::ChangeKeyStatuses(const std::string& session_id) { + LOG(INFO) << "ChangeKeyStatuses session: " << session_id; + eme::KeyStatusMap key_statuses; + const auto key_status_result_status = + cdm_->session_getKeyStatuses(session_id, &key_statuses); + if (key_status_result_status != eme::kSuccess) { + LOG(ERROR) << "Could not retrieve key statuses for session id: " + << session_id << " status: " << key_status_result_status; + return; + } + UpdateKeyStatuses(session_id, key_statuses); + UpdateExpirationTime(session_id); +} + +void IEMEDrmBridge::UpdateKeyStatuses(const std::string& session_id, + const eme::KeyStatusMap& key_statuses) { + bool has_additional_usable_key = false; + bool key_status_change = false; + + CdmKeysInfo cdm_keys_info; + KayStatusesMap old_key_statuses_map; + auto& current_key_statuses_map = session_key_statuses_map_[session_id]; + std::swap(old_key_statuses_map, current_key_statuses_map); + + for (const auto& key : key_statuses) { + const auto key_id = CreateBinaryDataFromString(key.first); + CHECK(!key_id.empty()); + + CdmKeyInformation::KeyStatus key_status = ConvertKeyStatus(key.second); + + const auto it = old_key_statuses_map.find(key.first); + if ((it == old_key_statuses_map.end() || it->second != key_status) && + // EME spec claims that onKeyStatusChange event should be fired for + // pending key status, but it is suppressed due to problems repoted + // by major content providers. + key_status != CdmKeyInformation::KEY_STATUS_PENDING) { + key_status_change = true; + if (key_status == CdmKeyInformation::USABLE) { + has_additional_usable_key = true; + } + } + if (it != old_key_statuses_map.end()) { + old_key_statuses_map.erase(it); + } + + current_key_statuses_map[key.first] = key_status; + + LOG(INFO) << "Key status: " << base::HexEncode(key_id.data(), key_id.size()) + << ", " << key_status; + cdm_keys_info.push_back(std::make_unique( + key_id, key_status, 0 /* there is no key system code */)); + } + + event_callbacks_.Notify(Event::kHasAdditionalUsableKey); + + // Session keys change callback will be fired when: + // 1. New keys were added + // 2. Keys were removed + // 3. Keys statuses were changed + if (key_status_change || !old_key_statuses_map.empty()) { + session_keys_change_cb_.Run(session_id, has_additional_usable_key, + std::move(cdm_keys_info)); + } +} + +void IEMEDrmBridge::UpdateExpirationTime(const std::string& session_id) { + LOG(INFO) << "Updating expiration time session: " << session_id; + + int64_t expiration_time; + const auto get_expiration_result_status = + cdm_->session_getExpiration(session_id, &expiration_time); + if (eme::kSuccess != get_expiration_result_status) { + LOG(ERROR) << "Updating expiration time for session id: " << session_id + << " failed with status: " << get_expiration_result_status; + return; + } + + LOG(INFO) << "Expiration time: " << expiration_time << " ms"; + + const auto last_expiration_it = last_expiration_times_map_.find(session_id); + + if (last_expiration_times_map_.end() == last_expiration_it || + expiration_time != last_expiration_it->second) { + last_expiration_times_map_[session_id] = expiration_time; + NotifySessionExpirationUpdate(session_id, expiration_time); + } +} + +void IEMEDrmBridge::onRemoveComplete(const std::string& session_id) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&IEMEDrmBridge::RemoveComplete, + weak_factory_.GetWeakPtr(), session_id)); +} + +void IEMEDrmBridge::RemoveComplete(const std::string& session_id) { + LOG(INFO) << "RemoveComplete session: " << session_id; + const auto promise_it = session_promise_map_.find(session_id); + CHECK(promise_it != session_promise_map_.end()); + + if (promise_it != session_promise_map_.end()) { + IEMEDrmBridge::ResolvePromise(promise_it->second); + session_promise_map_.erase(promise_it); + } +} + +void IEMEDrmBridge::ResolvePromise(uint32_t promise_id) { + cdm_promise_adapter_->ResolvePromise(promise_id); +} + +void IEMEDrmBridge::SetDecryptStrategy(DecryptBufferFullStrategy strategy) { + decrypt_buffer_full_strategy_ = strategy; +} + +// From IEME doc: |expiry_time_ms| is the time, in milliseconds since 1970 UTC, +// after which the key(s) in the session will no longer be usable to decrypt +// media data, or -1 if no such time exists. +void IEMEDrmBridge::NotifySessionExpirationUpdate(const std::string& session_id, + int64_t expiry_time_ms) { + if (expiry_time_ms < 0) { + expiry_time_ms = 0; + } + + session_expiration_update_cb_.Run( + session_id, + base::Time::FromSecondsSinceUnixEpoch(expiry_time_ms / 1000.0)); +} + +ContentDecryptionModule* CreateIEMEDrmBridge( + const std::string& key_system, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb, + bool privacy_mode) { + return IEMEDrmBridge::Create(key_system, session_message_cb, + session_closed_cb, session_keys_change_cb, + session_expiration_update_cb, privacy_mode) + .release(); +} + +} // namespace media diff --git a/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h b/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h new file mode 100644 index 0000000..69a3bd9 --- /dev/null +++ b/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h @@ -0,0 +1,221 @@ +// Copyright 2023 Samsung Electronics Inc. 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_BASE_ANDROID_IEME_DRM_BRIDGE_H_ +#define MEDIA_BASE_ANDROID_IEME_DRM_BRIDGE_H_ + +#include +#include +#include +#include + +#include "base/cancelable_callback.h" +#include "base/functional/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/task/single_thread_task_runner.h" +#include "build/tizen_version.h" +#include "media/base/callback_registry.h" +#include "media/base/cdm_context.h" +#include "media/base/cdm_key_information.h" +#include "media/base/cdm_promise_adapter.h" +#include "media/base/content_decryption_module.h" +#include "media/base/decryptor.h" +#include "media/base/media_export.h" + +class GURL; + +namespace media { + +class IEMEDrmBridge; +class MediaPlayerManager; + +using ScopedIEMEDrmBridgePtr = std::unique_ptr; + +// This class provides DRM services for tizen capi EME implementation. +class MEDIA_EXPORT IEMEDrmBridge : public ContentDecryptionModule, + public CdmContext, + public Decryptor, + public eme::IEventListener { + public: + ~IEMEDrmBridge() override; + + IEMEDrmBridge(const IEMEDrmBridge&) = delete; + IEMEDrmBridge& operator=(const IEMEDrmBridge&) = delete; + + // Checks whether |key_system| is supported. + static bool IsKeySystemSupported(const std::string& key_system); + + // Returns a MediaDrmBridge instance if |key_system| is supported, or a NULL + // pointer otherwise. + // TODO(xhwang): Is it okay not to update session expiration info? + static ScopedIEMEDrmBridgePtr Create( + const std::string& key_system, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb, + bool privacy_mode); + + // Returns a MediaDrmBridge instance if |key_system| is supported, or a NULL + // otherwise. No session callbacks are provided. This is used when we need to + // use MediaDrmBridge without creating any sessions. + static ScopedIEMEDrmBridgePtr CreateWithoutSessionSupport( + const std::string& key_system); + + // MediaKeys (via BrowserCdm) implementation. + void SetServerCertificate( + const std::vector& certificate, + std::unique_ptr promise) override; + void CreateSessionAndGenerateRequest( + CdmSessionType session_type, + media::EmeInitDataType init_data_type, + const std::vector& init_data, + std::unique_ptr promise) override; + void LoadSession( + CdmSessionType session_type, + const std::string& js_session_id, + std::unique_ptr promise) override; + void UpdateSession(const std::string& js_session_id, + const std::vector& response, + std::unique_ptr promise) override; + void CloseSession(const std::string& js_session_id, + std::unique_ptr promise) override; + void RemoveSession(const std::string& js_session_id, + std::unique_ptr promise) override; + CdmContext* GetCdmContext() override; + + // Get handle for this IEME instance + eme::eme_decryptor_t GetDecryptorHandle() override; + + // Return key system which it was configured to + const std::string& GetKeySystem() const; + + // CdmContext interface + Decryptor* GetDecryptor() override; + + // TODO(m.debski): We could make it return the same type as + // IEME::getDecryptor() and use that from Browser side MediaPlayer + // to create drm_info. + absl::optional GetCdmId() const override; + + // TODO(m.debski): + // Decryptor interface + std::unique_ptr RegisterEventCB(EventCB event_cb); + + void Decrypt(StreamType stream_type, + scoped_refptr encrypted, + DecryptCB decrypt_cb) override; + void DecryptTizen(StreamType stream_type, + scoped_refptr encrypted, + DecryptCBTizen decrypt_cb) override; + void CancelDecrypt(StreamType stream_type) override; + // We problably won't use that + void InitializeAudioDecoder(const AudioDecoderConfig& config, + DecoderInitCB init_cb) override; + void InitializeVideoDecoder(const VideoDecoderConfig& config, + DecoderInitCB init_cb) override; + void DecryptAndDecodeAudio(const scoped_refptr encrypted, + AudioDecodeCB audio_decode_cb) override; + void DecryptAndDecodeVideo(const scoped_refptr encrypted, + VideoDecodeCB video_decode_cb) override; + void ResetDecoder(StreamType stream_type) override; + void DeinitializeDecoder(StreamType stream_type) override; + void SetDecryptStrategy(DecryptBufferFullStrategy) override; + + // eme::IEventListener implementation. + void onMessage(const std::string& ieme_session_id, + eme::MessageType message_type, + const std::string& message) override; + void onKeyStatusesChange(const std::string& ieme_session_id) override; + void onRemoveComplete(const std::string& ieme_session_id) override; + + private: + IEMEDrmBridge(const std::string& key_system, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb, + bool privacy_mode); + + void ResolvePromise(uint32_t promise_id); + void ChangeKeyStatuses(const std::string& ieme_session_id); + // Updates key statuses map based on given map. Notifies about new key and + // calls session_keys_change_cb + void UpdateKeyStatuses(const std::string& ieme_session_id, + const eme::KeyStatusMap&); + // Checks session expiration time and if changed calls + // NotifySessionExpirationUpdate. + void UpdateExpirationTime(const std::string& ieme_session_id); + void RemoveComplete(const std::string& ieme_session_id); + + void NotifySessionExpirationUpdate(const std::string& ieme_session_id, + int64_t expiry_time_ms); + void GenerateServerCertificateRequest( + std::unique_ptr promise); + void DecryptInChromium(StreamType stream_type, + const scoped_refptr encrypted, + DecryptCBTizen decrypt_cb); +#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(6, 0, 0) + bool IsKeyReady(const std::string& key); +#endif + + // DRM API requires us to call destroy instead of the destructor + struct IEMEDelete { + inline void operator()(eme::IEME* cdm) const { eme::IEME::destroy(cdm); } + }; + + std::unique_ptr cdm_; + + // Key system name which was used to create |cdm_| + const std::string key_system_; + + // Callbacks for firing session events. + SessionMessageCB session_message_cb_; + SessionClosedCB session_closed_cb_; + SessionKeysChangeCB session_keys_change_cb_; + SessionExpirationUpdateCB session_expiration_update_cb_; + + // For resolving remove session promise + // IEME session id is used as a key + typedef std::unordered_map SessionPromiseMap; + SessionPromiseMap session_promise_map_; + std::unique_ptr cdm_promise_adapter_; + bool single_process_mode_; + + // This map is for keeping track of keys and detecting whether new key has + // been added. IEME session id is used as a key + typedef std::unordered_map + KayStatusesMap; + std::unordered_map session_key_statuses_map_; + + // Expiration data after last check + std::unordered_map last_expiration_times_map_; + + // For DRM callbacks because they are not queuing their events on the event + // loop + const scoped_refptr task_runner_; + + CallbackRegistry event_callbacks_; + + DecryptBufferFullStrategy decrypt_buffer_full_strategy_; + + struct DecryptCBPair { + base::CancelableOnceClosure pending_decrypt_cb_; + IEMEDrmBridge::DecryptCBTizen last_decrypt_cb_; + }; + std::map decrypt_cb_map_; + + base::WeakPtrFactory weak_factory_; +}; + +ContentDecryptionModule* CreateIEMEDrmBridge( + const std::string& key_system, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb, + bool privacy_mode = false); +} // namespace media + +#endif // MEDIA_BASE_ANDROID_IEME_DRM_BRIDGE_H_ diff --git a/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc b/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc index 839fcd8..f220826 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc +++ b/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc @@ -4,12 +4,22 @@ #include "tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h" +#include "base/command_line.h" +#include "base/trace_event/trace_event.h" +#include "content/public/common/content_switches.h" #include "media/base/decoder_buffer.h" #include "media/base/renderer_client.h" +#include "tizen/system_info.h" #include "tizen_src/chromium_impl/media/filters/media_player_tizen_client.h" +#include + namespace media { +// Limit of platform player's total (audio and video) buffer size +// for encrypted content in bytes +const media::player_buffer_size_t kDRMPlayerTotalBufferSize = 10 * 1024 * 1024; + MediaPlayerESPlusPlayerTV::MediaPlayerESPlusPlayerTV() { LOG(INFO) << "(" << static_cast(this) << ") " << __func__; } @@ -20,6 +30,8 @@ MediaPlayerESPlusPlayerTV::~MediaPlayerESPlusPlayerTV() { void MediaPlayerESPlusPlayerTV::Initialize(VideoRendererSink* sink) { LOG(INFO) << "(" << static_cast(this) << ") " << __func__; + single_process_mode_ = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSingleProcess); MediaPlayerESPlusPlayer::Initialize(sink); if (GetMediaPlayerClient()) GetMediaPlayerClient()->NotifyPlaybackState(kPlaybackLoad, player_id_); @@ -58,10 +70,36 @@ void MediaPlayerESPlusPlayerTV::PostPrepareComplete() { MediaPlayerESPlusPlayer::PostPrepareComplete(); } +void MediaPlayerESPlusPlayerTV::SetAudioDecoderTypeIfNeeded( + const media::AudioCodec& codec) { + if (codec == media::AudioCodec::kFLAC) { + auto error = esplusplayer_set_audio_codec_type( + esplayer_, ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW); + if (error != ESPLUSPLAYER_ERROR_TYPE_NONE) { + LOG(ERROR) << "esplusplayer_set_audio_codec_type failed, error code " + << error; + } + } +} + +void MediaPlayerESPlusPlayerTV::SetAudioSubmitDataType() { + if (video_codec_ == media::VideoCodec::kUnknown) { + // Emulator does not support DRM playback. + if (is_video_hole_ && !IsEmulatorArch()) + SetSubmitDataType(true, single_process_mode_); + else + SetSubmitDataType(false, single_process_mode_); + } +} + int MediaPlayerESPlusPlayerTV::SetVideoStreamInfo( const media::VideoDecoderConfig& video_config, esplusplayer_video_stream_info& video_stream_info) { MediaPlayerESPlusPlayer::SetVideoStreamInfo(video_config, video_stream_info); + if (!SetVideoSubmitDataType(video_config)) { + LOG(ERROR) << "SetVideoSubmitDataType failed."; + return -1; + } return 0; } @@ -69,12 +107,79 @@ void MediaPlayerESPlusPlayerTV::SetAudioStreamInfo( const media::AudioDecoderConfig& audio_config, esplusplayer_audio_stream_info* audio_stream_info) { MediaPlayerESPlusPlayer::SetAudioStreamInfo(audio_config, audio_stream_info); + + SetAudioDecoderTypeIfNeeded(audio_config.codec()); + SetAudioSubmitDataType(); } bool MediaPlayerESPlusPlayerTV::ReadFromBufferQueue(DemuxerStream::Type type) { return MediaPlayerESPlusPlayer::ReadFromBufferQueue(type); } +bool MediaPlayerESPlusPlayerTV::SetSubmitDataType( + const bool is_drm_eme, + const bool single_process_mode) { + int error = ESPLUSPLAYER_ERROR_TYPE_NONE; + is_drm_eme_ = is_drm_eme; + if (is_drm_eme) { + // only set submit data type for video, + // as this api has no stream type param. +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) + if (single_process_mode) + error = esplusplayer_set_submit_data_type( + esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_ENCRYPTED_DATA); + else +#endif + error = esplusplayer_set_submit_data_type( + esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_TRUSTZONE_DATA); + } else { + error = esplusplayer_set_submit_data_type( + esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_CLEAN_DATA); + } + if (error != ESPLUSPLAYER_ERROR_TYPE_NONE) { + LOG(ERROR) << "esplusplayer_set_submit_data_type failed. error code " + << error; + return false; + } + return true; +} + +bool MediaPlayerESPlusPlayerTV::SetVideoSubmitDataType( + const media::VideoDecoderConfig& video_config) { + if (is_video_hole_ && !IsEmulatorArch() && !video_config.is_rtc()) { + is_video_drm_eme_ = true; + } else { + is_video_drm_eme_ = video_config.is_encrypted(); + } + + return SetSubmitDataType(is_video_drm_eme_, single_process_mode_); +} + +player_buffer_size_t MediaPlayerESPlusPlayerTV::GetMaxVideoBufferSize( + const media::VideoDecoderConfig& video_config) { + const auto total_buffer_size = [this, &video_config]() -> uint64_t { + size_t used{}; // variable not used but required in call below + size_t total{}; + + if (!video_config.is_encrypted()) { + LOG(INFO) << "Clear content, using default"; + return kPlayerTotalBufferSize; + } + + if (eCDMReturnType::E_SUCCESS == ICDI::getDecryptStatus(&used, &total)) { + LOG(INFO) << "Decrypt status, total: " << (total / 1024) << "KB"; + return total; + } else { + LOG(ERROR) << "Decrypt status failed, using default"; + return kDRMPlayerTotalBufferSize; + } + }(); + // There is not much sense setting MMPlayer's video buffer size larger + // that available TrustZone memory. Previously this caused out-of-memory + // being returned often during decryption. + return total_buffer_size - kPlayerAudioBufferSize; +} + bool MediaPlayerESPlusPlayerTV::SetBufferType() { esplusplayer_decoded_video_frame_buffer_type video_frame_buffer_type = is_video_hole_ ? ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_NONE @@ -95,11 +200,79 @@ bool MediaPlayerESPlusPlayerTV::SetBufferType() { esplusplayer_submit_status MediaPlayerESPlusPlayerTV::SubmitEsPacket( DemuxerStream::Type type, scoped_refptr buffer) { - esplusplayer_submit_status status = - MediaPlayerESPlusPlayer::SubmitEsPacket(type, buffer); + TRACE_EVENT2("media", "MediaPlayerESPlusPlayerTV::SubmitEsPacket", + "key_frame", buffer->is_key_frame(), "timestamp", + buffer->timestamp().InMilliseconds()); + esplusplayer_submit_status status = ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS; + if (is_drm_eme_) { + esplusplayer_es_packet packet; + memset(&packet, 0, sizeof(esplusplayer_es_packet)); + packet.type = GetESPlusPlayerStreamType(type); + if (buffer->tz_handle()) + packet.buffer_size = buffer->tz_buffer_size(); + else + packet.buffer_size = buffer->data_size(); + packet.buffer = (char*)(const_cast(buffer->data())); + packet.pts = buffer->timestamp().InMilliseconds(); + packet.duration = buffer->duration().InMilliseconds(); + + LOG(INFO) << __func__ << " " << DemuxerStream::GetTypeName(type) << " : " + << "Pushing esplus timestamp: " << packet.pts + << ", duration: " << packet.duration + << ", size: " << packet.buffer_size + << ", is_key_frame: " << buffer->is_key_frame() + << ", tz_handle: " << buffer->tz_handle(); + + // This filed only set when PushMediaPacket + packet.matroska_color_info = nullptr; + + unsigned int tz_handle = buffer->tz_handle() ? buffer->tz_handle() : 0; +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) + if (single_process_mode_) { + if (buffer->decrypt_config() && buffer->decryptor_handle()) { +#ifdef DRM_MAPI_AARCH_64 + status = esplusplayer_submit_encrypted_packet_64bit( + esplayer_, &packet, + drm_info_.GetPlayerDrmInfo(buffer->decrypt_config(), + buffer->decryptor_handle())); +#else + status = esplusplayer_submit_encrypted_packet( + esplayer_, &packet, + drm_info_.GetPlayerDrmInfo(buffer->decrypt_config(), + buffer->decryptor_handle())); +#endif + } else + status = + esplusplayer_submit_encrypted_packet(esplayer_, &packet, nullptr); + } else +#endif + status = + esplusplayer_submit_trust_zone_packet(esplayer_, &packet, tz_handle); + if (status == ESPLUSPLAYER_SUBMIT_STATUS_FULL || + status == ESPLUSPLAYER_SUBMIT_STATUS_OUT_OF_MEMORY || + status == ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED) { + LOG(WARNING) << DemuxerStream::GetTypeName(type) + << " buffer full, won't feed"; + SetShouldFeed(type, false); + return status; + } else if (status != ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS) { + LOG(WARNING) << "submit " << DemuxerStream::GetTypeName(type) + << " packet : " << GetString(status); + // media need release tz_handle when submit failed. + // espp will release tz_handle when submit success. + ReleaseTzHandle(buffer->tz_handle(), packet.buffer_size); + } + + last_frames_[type].first = base::Milliseconds(packet.pts); + last_frames_[type].second = base::Milliseconds(packet.duration); + } else { + status = MediaPlayerESPlusPlayer::SubmitEsPacket(type, buffer); + } + return status; } + void MediaPlayerESPlusPlayerTV::EnableLowLatencyMode() { NOTIMPLEMENTED(); } @@ -138,4 +311,106 @@ void MediaPlayerESPlusPlayerTV::OnEos() { GetMediaPlayerClient()->NotifyPlaybackState(kPlaybackFinish, player_id_); } +void ReleaseTzHandle(int tz_handle, int size) { + if (!tz_handle || size <= 0) { + return; + } + handle_and_size_s handle{tz_handle, size, TZ_ENABLED_YES}; + + auto ret = release_handle(&handle); + if (ret) { + LOG(ERROR) << "Releasing tz_handle " << tz_handle << " tz_size " << size + << " failed. Error " << ret; + } + return; +} + +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) +esplusplayer_drmb_es_cipher_algorithm EncryptionSchemeToEsppCipherAlgorithm( + EncryptionScheme encryption_scheme) { + switch (encryption_scheme) { + case EncryptionScheme::kCenc: + return ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR; + case EncryptionScheme::kCbcs: + return ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CBC; + default: + LOG(WARNING) << "Unknown encryption scheme"; + return ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR; + } +} + +MediaPlayerESPlusPlayerTV::DrmInfo::DrmInfo() { +#ifdef DRM_MAPI_AARCH_64 + memset(&player_drm_info_, 0, sizeof(esplusplayer_drm_info_64bit)); +#else + memset(&player_drm_info_, 0, sizeof(esplusplayer_drm_info)); +#endif + memset(&subsample_info_, 0, sizeof(esplusplayer_drmb_es_subsample_info)); + memset(&subsamples_data_, 0, sizeof(esplusplayer_drmb_es_fmp4_data)); + + player_drm_info_.algorithm = ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR; + player_drm_info_.format = ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_FMP4; + player_drm_info_.phase = ESPLUSPLAYER_DRMB_ES_CIPHER_PHASE_NONE; + player_drm_info_.split_offsets = nullptr; + player_drm_info_.use_out_buffer = false; + player_drm_info_.use_pattern = false; + player_drm_info_.crypt_byte_block = 0; + player_drm_info_.skip_byte_block = 0; +} + +#ifdef DRM_MAPI_AARCH_64 +esplusplayer_drm_info_64bit* +MediaPlayerESPlusPlayerTV::DrmInfo::GetPlayerDrmInfo( + const DecryptConfig* config, + const size_t& decryptor) { +#else +esplusplayer_drm_info* MediaPlayerESPlusPlayerTV::DrmInfo::GetPlayerDrmInfo( + const DecryptConfig* config, + const size_t& decryptor) { +#endif + if (!config || !decryptor) { + LOG(ERROR) << "config or decryptor is null"; + return nullptr; + } + infos_.clear(); + infos_.reserve(config->subsamples().size()); + for (const auto& sub : config->subsamples()) { + subsample_info_.bytes_of_clear_data = sub.clear_bytes; + subsample_info_.bytes_of_encrypted_data = sub.cypher_bytes; + infos_.emplace_back(subsample_info_); + } + subsamples_data_.subsample_count = infos_.size(); + subsamples_data_.subsample_infos = infos_.data(); + player_drm_info_.algorithm = + EncryptionSchemeToEsppCipherAlgorithm(config->encryption_scheme()); + player_drm_info_.handle = static_cast(decryptor); + player_drm_info_.kid = reinterpret_cast( + const_cast(config->key_id().data())); + player_drm_info_.kid_length = config->key_id().length(); + player_drm_info_.iv = + reinterpret_cast(const_cast(config->iv().data())); + player_drm_info_.iv_length = config->iv().length(); + player_drm_info_.sub_data = &subsamples_data_; + player_drm_info_.use_pattern = config->encryption_pattern().has_value(); + + if (config->encryption_pattern()) { + player_drm_info_.crypt_byte_block = + config->encryption_pattern()->crypt_byte_block(); + player_drm_info_.skip_byte_block = + config->encryption_pattern()->skip_byte_block(); + } + + DLOG(INFO) << "enc algorithm: " + << static_cast(config->encryption_scheme()) + << ", subsamples_data_.subsample_count: " + << subsamples_data_.subsample_count + << ", subsamples_data_.subsample_infos: " + << subsamples_data_.subsample_infos + << ", use pattern: " << player_drm_info_.use_pattern + << ", crypt byte block: " << player_drm_info_.crypt_byte_block + << ", skip byte block: " << player_drm_info_.skip_byte_block; + return &player_drm_info_; +} +#endif + } // namespace media diff --git a/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h b/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h index 37007fb..a063bbf 100644 --- a/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h +++ b/tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h @@ -5,12 +5,23 @@ #ifndef MEDIA_FILTERS_MEDIA_PLAYER_ESPLUSPLAYER_TV_H_ #define MEDIA_FILTERS_MEDIA_PLAYER_ESPLUSPLAYER_TV_H_ +#include "media/base/decrypt_config.h" +#include "tizen_src/chromium_impl/build/tizen_version.h" + #include "tizen_src/chromium_impl/media/filters/media_player_esplusplayer.h" +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) +#include +#include +#endif + namespace media { class DemuxerStream; +// Releases a handle from trusted zone. +void ReleaseTzHandle(int tz_handle, int size); + class MEDIA_EXPORT MediaPlayerESPlusPlayerTV : public MediaPlayerESPlusPlayer { public: MediaPlayerESPlusPlayerTV(); @@ -48,8 +59,45 @@ class MEDIA_EXPORT MediaPlayerESPlusPlayerTV : public MediaPlayerESPlusPlayer { scoped_refptr buffer) override; void InitializeStreamConfig(DemuxerStream::Type type) override; bool SetBufferType() override; + player_buffer_size_t GetMaxVideoBufferSize( + const media::VideoDecoderConfig& video_config) override; private: +#if TIZEN_VERSION_AT_LEAST(6, 0, 0) + class DrmInfo { + public: + DrmInfo(); +#ifdef DRM_MAPI_AARCH_64 + esplusplayer_drm_info_64bit* GetPlayerDrmInfo(const DecryptConfig* config, + const size_t& decryptor); +#else + + esplusplayer_drm_info* GetPlayerDrmInfo(const DecryptConfig* config, + const size_t& decryptor); +#endif + + private: +#ifdef DRM_MAPI_AARCH_64 + esplusplayer_drm_info_64bit player_drm_info_; +#else + esplusplayer_drm_info player_drm_info_; +#endif + esplusplayer_drmb_es_subsample_info subsample_info_; + std::vector infos_; + esplusplayer_drmb_es_fmp4_data subsamples_data_; + }; + + DrmInfo drm_info_; +#endif + bool SetVideoSubmitDataType(const media::VideoDecoderConfig& video_config); + void SetAudioSubmitDataType(); + bool SetSubmitDataType(const bool is_drm_eme, const bool single_process_mode); + void SetAudioDecoderTypeIfNeeded(const media::AudioCodec& codec); + + bool is_drm_eme_{false}; + bool is_video_drm_eme_{false}; + bool single_process_mode_{false}; + media::VideoCodec video_codec_{media::VideoCodec::kUnknown}; base::WeakPtrFactory weak_factory_{this}; }; } // namespace media diff --git a/tizen_src/chromium_impl/media/media_efl.gni b/tizen_src/chromium_impl/media/media_efl.gni index edaf05c..ead5b06 100644 --- a/tizen_src/chromium_impl/media/media_efl.gni +++ b/tizen_src/chromium_impl/media/media_efl.gni @@ -82,11 +82,16 @@ if (tizen_multimedia) { if (tizen_product_tv) { external_media_video_decode_sources += [ + "//tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc", + "//tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h", "//tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.cc", "//tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.h", "//tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc", "//tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h", ] + external_media_video_decode_config += [ + "//tizen_src/build:drmdecrypt", + ] } else { external_media_video_decode_sources += [ "//tizen_src/chromium_impl/media/filters/media_player_esplusplayer_common.cc", diff --git a/tizen_src/ewk/efl_integration/public/ewk_media_playback_info_product.h b/tizen_src/ewk/efl_integration/public/ewk_media_playback_info_product.h index e1ae1a6..49c252e 100644 --- a/tizen_src/ewk/efl_integration/public/ewk_media_playback_info_product.h +++ b/tizen_src/ewk/efl_integration/public/ewk_media_playback_info_product.h @@ -65,7 +65,11 @@ typedef struct _Ewk_DRM_Init_Data_Info { typedef struct _Ewk_DRM_Init_Complete_Info { unsigned int pssh_data_length; unsigned char* pssh_data; +#ifdef DRM_MAPI_AARCH_64 + unsigned long decryptor; +#else int decryptor; +#endif } Ewk_DRM_Init_Complete_Info; typedef struct _Ewk_DRM_Error_Info { diff --git a/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.cc b/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.cc index ccd86d7..e980ee7 100644 --- a/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.cc +++ b/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.cc @@ -61,6 +61,7 @@ #if BUILDFLAG(IS_TIZEN_TV) #include "common/application_type.h" #include "content/public/common/url_utils.h" +#include "renderer/key_systems_tizen.h" #include "renderer/plugins/plugin_placeholder_avplayer.h" #endif @@ -217,6 +218,17 @@ void ContentRendererClientEfl::WebViewCreated( #endif } +void ContentRendererClientEfl::GetSupportedKeySystems( + media::GetSupportedKeySystemsCB cb) { +#if BUILDFLAG(IS_TIZEN_TV) + // key_system changed by + // https://source.chromium.org/chromium/chromium/src/+/ecdc736223cfabe49a67f02b61f72b696a839588 + media::KeySystemInfos key_systems; + efl_integration::TizenAddKeySystems({}, &key_systems); + std::move(cb).Run(std::move(key_systems)); +#endif +} + bool ContentRendererClientEfl::OverrideCreatePlugin( content::RenderFrame* render_frame, const blink::WebPluginParams& params, diff --git a/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.h b/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.h index f3b57c3..0c795ab 100644 --- a/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.h +++ b/tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.h @@ -107,6 +107,8 @@ class ContentRendererClientEfl : public content::ContentRendererClient { bool shutting_down() const { return shutting_down_; } void set_shutting_down(bool shutting_down) { shutting_down_ = shutting_down; } + void GetSupportedKeySystems(media::GetSupportedKeySystemsCB cb) override; + private: static void ApplyCustomMobileSettings(blink::WebView*); @@ -118,6 +120,7 @@ class ContentRendererClientEfl : public content::ContentRendererClient { #if BUILDFLAG(IS_TIZEN_TV) bool floating_video_window_on_ = false; + std::vector key_system_whitelist_; #endif };