diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-23 17:21:03 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-01-23 16:25:15 +0000 |
commit | c551f43206405019121bd2b2c93714319a0a3300 (patch) | |
tree | 1f48c30631c421fd4bbb3c36da20183c8a2ed7d7 /chromium/media/fuchsia | |
parent | 7961cea6d1041e3e454dae6a1da660b453efd238 (diff) | |
download | qtwebengine-chromium-c551f43206405019121bd2b2c93714319a0a3300.tar.gz |
BASELINE: Update Chromium to 79.0.3945.139
Change-Id: I336b7182fab9bca80b709682489c07db112eaca5
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/media/fuchsia')
28 files changed, 2748 insertions, 33 deletions
diff --git a/chromium/media/fuchsia/cdm/BUILD.gn b/chromium/media/fuchsia/cdm/BUILD.gn index 04defe57532..bc242ec7e93 100644 --- a/chromium/media/fuchsia/cdm/BUILD.gn +++ b/chromium/media/fuchsia/cdm/BUILD.gn @@ -8,15 +8,27 @@ source_set("cdm") { sources = [ "fuchsia_cdm.cc", "fuchsia_cdm.h", + "fuchsia_cdm_context.h", "fuchsia_cdm_factory.cc", "fuchsia_cdm_factory.h", + "fuchsia_cdm_provider.h", + "fuchsia_decryptor.cc", + "fuchsia_decryptor.h", + "fuchsia_stream_decryptor.cc", + "fuchsia_stream_decryptor.h", ] + public_deps = [ + "//third_party/fuchsia-sdk/sdk:media_drm", + ] + + configs += [ "//media:subcomponent_config" ] + deps = [ "//fuchsia/base", - "//media", - "//media/fuchsia/mojom", - "//services/service_manager/public/cpp", - "//third_party/fuchsia-sdk/sdk:media_drm", + "//media/base", + "//media/cdm", + "//media/fuchsia/common", + "//url", ] } diff --git a/chromium/media/fuchsia/cdm/DEPS b/chromium/media/fuchsia/cdm/DEPS index 66b87e0e4e1..6e81cc5a759 100644 --- a/chromium/media/fuchsia/cdm/DEPS +++ b/chromium/media/fuchsia/cdm/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+fuchsia/base", + "+mojo/public", "+services/service_manager/public", ] diff --git a/chromium/media/fuchsia/cdm/client/BUILD.gn b/chromium/media/fuchsia/cdm/client/BUILD.gn new file mode 100644 index 00000000000..2b9537cae32 --- /dev/null +++ b/chromium/media/fuchsia/cdm/client/BUILD.gn @@ -0,0 +1,20 @@ +# Copyright 2019 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. + +assert(is_fuchsia) + +source_set("client") { + sources = [ + "fuchsia_cdm_util.cc", + "fuchsia_cdm_util.h", + "mojo_fuchsia_cdm_provider.cc", + "mojo_fuchsia_cdm_provider.h", + ] + + deps = [ + "//media", + "//media/fuchsia/mojom", + "//services/service_manager/public/cpp", + ] +} diff --git a/chromium/media/fuchsia/cdm/client/fuchsia_cdm_util.cc b/chromium/media/fuchsia/cdm/client/fuchsia_cdm_util.cc new file mode 100644 index 00000000000..e002b5be864 --- /dev/null +++ b/chromium/media/fuchsia/cdm/client/fuchsia_cdm_util.cc @@ -0,0 +1,18 @@ +// Copyright 2019 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/fuchsia/cdm/client/fuchsia_cdm_util.h" + +#include "media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.h" +#include "media/fuchsia/cdm/fuchsia_cdm_factory.h" + +namespace media { + +std::unique_ptr<CdmFactory> CreateFuchsiaCdmFactory( + service_manager::InterfaceProvider* interface_provider) { + return std::make_unique<FuchsiaCdmFactory>( + std::make_unique<MojoFuchsiaCdmProvider>(interface_provider)); +} + +} // namespace media diff --git a/chromium/media/fuchsia/cdm/client/fuchsia_cdm_util.h b/chromium/media/fuchsia/cdm/client/fuchsia_cdm_util.h new file mode 100644 index 00000000000..ddc9b60405d --- /dev/null +++ b/chromium/media/fuchsia/cdm/client/fuchsia_cdm_util.h @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CDM_CLIENT_FUCHSIA_CDM_UTIL_H_ +#define MEDIA_FUCHSIA_CDM_CLIENT_FUCHSIA_CDM_UTIL_H_ + +#include <memory> + +namespace service_manager { +class InterfaceProvider; +} + +namespace media { +class CdmFactory; + +std::unique_ptr<CdmFactory> CreateFuchsiaCdmFactory( + service_manager::InterfaceProvider* interface_provider); + +} // namespace media + +#endif // MEDIA_FUCHSIA_CDM_CLIENT_FUCHSIA_CDM_UTIL_H_ diff --git a/chromium/media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.cc b/chromium/media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.cc new file mode 100644 index 00000000000..5366f06cc96 --- /dev/null +++ b/chromium/media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.cc @@ -0,0 +1,30 @@ +// Copyright 2019 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/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.h" +#include "services/service_manager/public/cpp/interface_provider.h" + +namespace media { + +MojoFuchsiaCdmProvider::MojoFuchsiaCdmProvider( + service_manager::InterfaceProvider* interface_provider) + : interface_provider_(interface_provider) { + DCHECK(interface_provider_); +} + +MojoFuchsiaCdmProvider::~MojoFuchsiaCdmProvider() = default; + +void MojoFuchsiaCdmProvider::CreateCdmInterface( + const std::string& key_system, + fidl::InterfaceRequest<fuchsia::media::drm::ContentDecryptionModule> + cdm_request) { + if (!cdm_provider_) { + interface_provider_->GetInterface( + cdm_provider_.BindNewPipeAndPassReceiver()); + } + + cdm_provider_->CreateCdmInterface(key_system, std::move(cdm_request)); +} + +} // namespace media diff --git a/chromium/media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.h b/chromium/media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.h new file mode 100644 index 00000000000..2c34ce45808 --- /dev/null +++ b/chromium/media/fuchsia/cdm/client/mojo_fuchsia_cdm_provider.h @@ -0,0 +1,41 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CDM_CLIENT_MOJO_FUCHSIA_CDM_PROVIDER_H_ +#define MEDIA_FUCHSIA_CDM_CLIENT_MOJO_FUCHSIA_CDM_PROVIDER_H_ + +#include "base/macros.h" +#include "media/fuchsia/cdm/fuchsia_cdm_provider.h" +#include "media/fuchsia/mojom/fuchsia_cdm_provider.mojom.h" +#include "mojo/public/cpp/bindings/remote.h" + +namespace service_manager { +class InterfaceProvider; +} + +namespace media { + +class MojoFuchsiaCdmProvider : public FuchsiaCdmProvider { + public: + // |interface_provider| must outlive this class. + explicit MojoFuchsiaCdmProvider( + service_manager::InterfaceProvider* interface_provider); + ~MojoFuchsiaCdmProvider() override; + + // FuchsiaCdmProvider implementation: + void CreateCdmInterface( + const std::string& key_system, + fidl::InterfaceRequest<fuchsia::media::drm::ContentDecryptionModule> + cdm_request) override; + + private: + service_manager::InterfaceProvider* const interface_provider_; + mojo::Remote<media::mojom::FuchsiaCdmProvider> cdm_provider_; + + DISALLOW_COPY_AND_ASSIGN(MojoFuchsiaCdmProvider); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_CDM_CLIENT_MOJO_FUCHSIA_CDM_PROVIDER_H_ diff --git a/chromium/media/fuchsia/cdm/fuchsia_cdm.cc b/chromium/media/fuchsia/cdm/fuchsia_cdm.cc index cbe083a91d6..2272683b8e8 100644 --- a/chromium/media/fuchsia/cdm/fuchsia_cdm.cc +++ b/chromium/media/fuchsia/cdm/fuchsia_cdm.cc @@ -11,6 +11,13 @@ #include "media/base/callback_registry.h" #include "media/base/cdm_promise.h" +#define REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm) \ + if (!cdm) { \ + promise->reject(CdmPromise::Exception::INVALID_STATE_ERROR, 0, \ + "CDM channel is disconnected."); \ + return; \ + } + namespace media { namespace { @@ -88,6 +95,15 @@ CdmPromise::Exception ToCdmPromiseException(fuchsia::media::drm::Error error) { return CdmPromise::Exception::INVALID_STATE_ERROR; case fuchsia::media::drm::Error::QUOTA_EXCEEDED: return CdmPromise::Exception::QUOTA_EXCEEDED_ERROR; + + case fuchsia::media::drm::Error::NOT_PROVISIONED: + // FuchsiaCdmManager is supposed to provision CDM. + NOTREACHED(); + return CdmPromise::Exception::INVALID_STATE_ERROR; + + case fuchsia::media::drm::Error::INTERNAL: + DLOG(ERROR) << "CDM failed due to an internal error."; + return CdmPromise::Exception::INVALID_STATE_ERROR; } } @@ -98,8 +114,9 @@ class FuchsiaCdm::CdmSession { using ResultCB = base::OnceCallback<void(base::Optional<CdmPromise::Exception>)>; - explicit CdmSession(const FuchsiaCdm::SessionCallbacks* callbacks) - : session_callbacks_(callbacks) { + CdmSession(const FuchsiaCdm::SessionCallbacks* callbacks, + FuchsiaSecureStreamDecryptor::NewKeyCB on_new_key) + : session_callbacks_(callbacks), on_new_key_(on_new_key) { // License session events, e.g. license request message, key status change. // Fuchsia CDM service guarantees callback of functions (e.g. // GenerateLicenseRequest) are called before event callbacks. So it's safe @@ -167,17 +184,30 @@ class FuchsiaCdm::CdmSession { } void OnKeysChanged(std::vector<fuchsia::media::drm::KeyInfo> key_info) { + std::string new_key_id; bool has_additional_usable_key = false; CdmKeysInfo keys_info; for (const auto& info : key_info) { CdmKeyInformation::KeyStatus status = ToCdmKeyStatus(info.status); has_additional_usable_key |= (status == CdmKeyInformation::USABLE); + if (status == CdmKeyInformation::USABLE && new_key_id.empty()) { + // The |key_id| is passed to |on_new_key_| to workaround fxb/38253 in + // FuchsiaSecureStreamDecryptor. It needs just one valid |key_id|, so it + // doesn't matter if |key_info| contains more than one key. + // TODO(crbug.com/1012525): Remove the hack once fxb/38253 is resolved. + new_key_id.assign( + reinterpret_cast<const char*>(info.key_id.data.data()), + info.key_id.data.size()); + } keys_info.emplace_back(new CdmKeyInformation( info.key_id.data.data(), info.key_id.data.size(), status, 0)); } session_callbacks_->keys_change_cb.Run( session_id_, has_additional_usable_key, std::move(keys_info)); + + if (has_additional_usable_key) + on_new_key_.Run(new_key_id); } void OnSessionError(zx_status_t status) { @@ -195,13 +225,16 @@ class FuchsiaCdm::CdmSession { : base::nullopt); } + const SessionCallbacks* const session_callbacks_; + FuchsiaSecureStreamDecryptor::NewKeyCB on_new_key_; + fuchsia::media::drm::LicenseSessionPtr session_; std::string session_id_; // Callback for license operation. ResultCB result_cb_; - const SessionCallbacks* session_callbacks_; + DISALLOW_COPY_AND_ASSIGN(CdmSession); }; FuchsiaCdm::SessionCallbacks::SessionCallbacks() = default; @@ -212,20 +245,50 @@ FuchsiaCdm::SessionCallbacks& FuchsiaCdm::SessionCallbacks::operator=( FuchsiaCdm::FuchsiaCdm(fuchsia::media::drm::ContentDecryptionModulePtr cdm, SessionCallbacks callbacks) - : cdm_(std::move(cdm)), session_callbacks_(std::move(callbacks)) { + : cdm_(std::move(cdm)), + session_callbacks_(std::move(callbacks)), + decryptor_(cdm_.get()) { DCHECK(cdm_); - cdm_.set_error_handler([](zx_status_t status) { - // Error will be handled in CdmSession::OnSessionError. + cdm_.set_error_handler([this](zx_status_t status) { ZX_LOG(ERROR, status) << "The fuchsia.media.drm.ContentDecryptionModule" << " channel was terminated."; + + // Reject all the pending promises. + promises_.Clear(); }); } FuchsiaCdm::~FuchsiaCdm() = default; +std::unique_ptr<FuchsiaSecureStreamDecryptor> FuchsiaCdm::CreateVideoDecryptor( + FuchsiaSecureStreamDecryptor::Client* client) { + fuchsia::media::drm::DecryptorParams params; + + // TODO(crbug.com/997853): Enable secure mode when it's implemented in sysmem. + params.set_require_secure_mode(false); + + params.mutable_input_details()->set_format_details_version_ordinal(0); + fuchsia::media::StreamProcessorPtr stream_processor; + cdm_->CreateDecryptor(std::move(params), stream_processor.NewRequest()); + + auto decryptor = std::make_unique<FuchsiaSecureStreamDecryptor>( + std::move(stream_processor), client); + + // Save callback to use to notify the decryptor about a new key. + auto new_key_cb = decryptor->GetOnNewKeyClosure(); + { + base::AutoLock auto_lock(new_key_cb_for_video_lock_); + new_key_cb_for_video_ = new_key_cb; + } + + return decryptor; +} + void FuchsiaCdm::SetServerCertificate( const std::vector<uint8_t>& certificate, std::unique_ptr<SimpleCdmPromise> promise) { + REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_); + uint32_t promise_id = promises_.SavePromise(std::move(promise)); cdm_->SetServerCertificate( certificate, @@ -261,9 +324,13 @@ void FuchsiaCdm::CreateSessionAndGenerateRequest( return; } + REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_); + uint32_t promise_id = promises_.SavePromise(std::move(promise)); - auto session = std::make_unique<CdmSession>(&session_callbacks_); + auto session = std::make_unique<CdmSession>( + &session_callbacks_, + base::BindRepeating(&FuchsiaCdm::OnNewKey, base::Unretained(this))); CdmSession* session_ptr = session.get(); cdm_->CreateLicenseSession( @@ -332,6 +399,8 @@ void FuchsiaCdm::UpdateSession(const std::string& session_id, return; } + REJECT_PROMISE_AND_RETURN_IF_BAD_CDM(promise, cdm_); + // Caller should NOT pass in an empty response. DCHECK(!response.empty()); @@ -387,12 +456,24 @@ std::unique_ptr<CallbackRegistration> FuchsiaCdm::RegisterEventCB( } Decryptor* FuchsiaCdm::GetDecryptor() { - NOTIMPLEMENTED(); - return nullptr; + return &decryptor_; } int FuchsiaCdm::GetCdmId() const { return kInvalidCdmId; } +FuchsiaCdmContext* FuchsiaCdm::GetFuchsiaCdmContext() { + return this; +} + +void FuchsiaCdm::OnNewKey(const std::string& key_id) { + decryptor_.OnNewKey(); + { + base::AutoLock auto_lock(new_key_cb_for_video_lock_); + if (new_key_cb_for_video_) + new_key_cb_for_video_.Run(key_id); + } +} + } // namespace media diff --git a/chromium/media/fuchsia/cdm/fuchsia_cdm.h b/chromium/media/fuchsia/cdm/fuchsia_cdm.h index 93e2a7efa9b..65fd5be7530 100644 --- a/chromium/media/fuchsia/cdm/fuchsia_cdm.h +++ b/chromium/media/fuchsia/cdm/fuchsia_cdm.h @@ -13,10 +13,14 @@ #include "media/base/cdm_context.h" #include "media/base/cdm_promise_adapter.h" #include "media/base/content_decryption_module.h" +#include "media/fuchsia/cdm/fuchsia_cdm_context.h" +#include "media/fuchsia/cdm/fuchsia_decryptor.h" namespace media { -class FuchsiaCdm : public ContentDecryptionModule, public CdmContext { +class FuchsiaCdm : public ContentDecryptionModule, + public CdmContext, + public FuchsiaCdmContext { public: struct SessionCallbacks { SessionCallbacks(); @@ -61,6 +65,11 @@ class FuchsiaCdm : public ContentDecryptionModule, public CdmContext { EventCB event_cb) override; Decryptor* GetDecryptor() override; int GetCdmId() const override; + FuchsiaCdmContext* GetFuchsiaCdmContext() override; + + // FuchsiaCdmContext implementation: + std::unique_ptr<FuchsiaSecureStreamDecryptor> CreateVideoDecryptor( + FuchsiaSecureStreamDecryptor::Client* client) override; private: class CdmSession; @@ -78,7 +87,8 @@ class FuchsiaCdm : public ContentDecryptionModule, public CdmContext { uint32_t promise_id, base::Optional<CdmPromise::Exception> exception); - void OnCdmError(zx_status_t status); + // TODO(crbug.com/1012525): Remove |key_id| once fxb/38253 is resolved. + void OnNewKey(const std::string& key_id); CdmPromiseAdapter promises_; base::flat_map<std::string, std::unique_ptr<CdmSession>> session_map_; @@ -86,6 +96,12 @@ class FuchsiaCdm : public ContentDecryptionModule, public CdmContext { fuchsia::media::drm::ContentDecryptionModulePtr cdm_; SessionCallbacks session_callbacks_; + FuchsiaDecryptor decryptor_; + + base::Lock new_key_cb_for_video_lock_; + FuchsiaSecureStreamDecryptor::NewKeyCB new_key_cb_for_video_ + GUARDED_BY(new_key_cb_for_video_lock_); + DISALLOW_COPY_AND_ASSIGN(FuchsiaCdm); }; diff --git a/chromium/media/fuchsia/cdm/fuchsia_cdm_context.h b/chromium/media/fuchsia/cdm/fuchsia_cdm_context.h new file mode 100644 index 00000000000..9301cf657fc --- /dev/null +++ b/chromium/media/fuchsia/cdm/fuchsia_cdm_context.h @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_CONTEXT_H_ +#define MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_CONTEXT_H_ + +#include "media/fuchsia/cdm/fuchsia_stream_decryptor.h" + +namespace media { + +// Interface for Fuchsia-specific extensions to the CdmContext interface. +class FuchsiaCdmContext { + public: + FuchsiaCdmContext() = default; + + // Creates FuchsiaSecureStreamDecryptor instance for the CDM context. + virtual std::unique_ptr<FuchsiaSecureStreamDecryptor> CreateVideoDecryptor( + FuchsiaSecureStreamDecryptor::Client* client) = 0; + + protected: + virtual ~FuchsiaCdmContext() = default; +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_CONTEXT_H_ diff --git a/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.cc b/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.cc index 6e8059968ae..df0ee3cf802 100644 --- a/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.cc +++ b/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.cc @@ -4,23 +4,21 @@ #include "media/fuchsia/cdm/fuchsia_cdm_factory.h" -#include <fuchsia/media/drm/cpp/fidl.h> - #include "base/bind.h" #include "media/base/bind_to_current_loop.h" #include "media/base/cdm_config.h" #include "media/base/key_systems.h" #include "media/cdm/aes_decryptor.h" #include "media/fuchsia/cdm/fuchsia_cdm.h" -#include "services/service_manager/public/cpp/interface_provider.h" +#include "media/fuchsia/cdm/fuchsia_cdm_provider.h" #include "url/origin.h" namespace media { FuchsiaCdmFactory::FuchsiaCdmFactory( - service_manager::InterfaceProvider* interface_provider) - : interface_provider_(interface_provider) { - DCHECK(interface_provider_); + std::unique_ptr<FuchsiaCdmProvider> cdm_provider) + : cdm_provider_(std::move(cdm_provider)) { + DCHECK(cdm_provider_); } FuchsiaCdmFactory::~FuchsiaCdmFactory() = default; @@ -49,9 +47,6 @@ void FuchsiaCdmFactory::Create( return; } - if (!cdm_provider_) - interface_provider_->GetInterface(mojo::MakeRequest(&cdm_provider_)); - fuchsia::media::drm::ContentDecryptionModulePtr cdm_ptr; cdm_provider_->CreateCdmInterface(key_system, cdm_ptr.NewRequest()); diff --git a/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.h b/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.h index 6fdba9dac04..af820219722 100644 --- a/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.h +++ b/chromium/media/fuchsia/cdm/fuchsia_cdm_factory.h @@ -5,22 +5,19 @@ #ifndef MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_FACTORY_H_ #define MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_FACTORY_H_ +#include <memory> + #include "base/macros.h" #include "media/base/cdm_factory.h" #include "media/base/media_export.h" -#include "media/fuchsia/mojom/fuchsia_cdm_provider.mojom.h" - -namespace service_manager { -class InterfaceProvider; -} +#include "media/fuchsia/cdm/fuchsia_cdm_provider.h" namespace media { class MEDIA_EXPORT FuchsiaCdmFactory : public CdmFactory { public: // |interface_provider| must outlive this class. - explicit FuchsiaCdmFactory( - service_manager::InterfaceProvider* interface_provider); + explicit FuchsiaCdmFactory(std::unique_ptr<FuchsiaCdmProvider> provider); ~FuchsiaCdmFactory() final; // CdmFactory implementation. @@ -34,8 +31,7 @@ class MEDIA_EXPORT FuchsiaCdmFactory : public CdmFactory { const CdmCreatedCB& cdm_created_cb) final; private: - service_manager::InterfaceProvider* const interface_provider_; - media::mojom::FuchsiaCdmProviderPtr cdm_provider_; + std::unique_ptr<FuchsiaCdmProvider> cdm_provider_; DISALLOW_COPY_AND_ASSIGN(FuchsiaCdmFactory); }; diff --git a/chromium/media/fuchsia/cdm/fuchsia_cdm_provider.h b/chromium/media/fuchsia/cdm/fuchsia_cdm_provider.h new file mode 100644 index 00000000000..0e0a7b5565d --- /dev/null +++ b/chromium/media/fuchsia/cdm/fuchsia_cdm_provider.h @@ -0,0 +1,28 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_PROVIDER_H_ +#define MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_PROVIDER_H_ + +#include <fuchsia/media/drm/cpp/fidl.h> +#include <string> + +#include "media/base/media_export.h" + +namespace media { + +// Interface to connect fuchsia::media::drm::ContentDecryptionModule to the +// remote service. +class MEDIA_EXPORT FuchsiaCdmProvider { + public: + virtual ~FuchsiaCdmProvider() = default; + virtual void CreateCdmInterface( + const std::string& key_system, + fidl::InterfaceRequest<fuchsia::media::drm::ContentDecryptionModule> + cdm_request) = 0; +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_CDM_FUCHSIA_CDM_PROVIDER_H_ diff --git a/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc b/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc new file mode 100644 index 00000000000..9ae54062d9d --- /dev/null +++ b/chromium/media/fuchsia/cdm/fuchsia_decryptor.cc @@ -0,0 +1,96 @@ +// Copyright 2019 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/fuchsia/cdm/fuchsia_decryptor.h" + +#include "base/fuchsia/fuchsia_logging.h" +#include "base/logging.h" +#include "media/base/decoder_buffer.h" +#include "media/base/video_frame.h" +#include "media/fuchsia/cdm/fuchsia_stream_decryptor.h" + +namespace media { + +FuchsiaDecryptor::FuchsiaDecryptor( + fuchsia::media::drm::ContentDecryptionModule* cdm) + : cdm_(cdm) { + DCHECK(cdm_); +} + +FuchsiaDecryptor::~FuchsiaDecryptor() = default; + +void FuchsiaDecryptor::RegisterNewKeyCB(StreamType stream_type, + const NewKeyCB& new_key_cb) { + if (stream_type != kAudio) + return; + + base::AutoLock auto_lock(new_key_cb_lock_); + new_key_cb_ = new_key_cb; +} + +void FuchsiaDecryptor::Decrypt(StreamType stream_type, + scoped_refptr<DecoderBuffer> encrypted, + const DecryptCB& decrypt_cb) { + if (stream_type != StreamType::kAudio) { + decrypt_cb.Run(Status::kError, nullptr); + return; + } + + if (!audio_decryptor_) + audio_decryptor_ = FuchsiaClearStreamDecryptor::Create(cdm_); + + audio_decryptor_->Decrypt(std::move(encrypted), decrypt_cb); +} + +void FuchsiaDecryptor::CancelDecrypt(StreamType stream_type) { + if (stream_type == StreamType::kAudio && audio_decryptor_) { + audio_decryptor_->CancelDecrypt(); + } +} + +void FuchsiaDecryptor::InitializeAudioDecoder(const AudioDecoderConfig& config, + const DecoderInitCB& init_cb) { + // Only decryption is supported. + init_cb.Run(false); +} + +void FuchsiaDecryptor::InitializeVideoDecoder(const VideoDecoderConfig& config, + const DecoderInitCB& init_cb) { + // Only decryption is supported. + init_cb.Run(false); +} + +void FuchsiaDecryptor::DecryptAndDecodeAudio( + scoped_refptr<DecoderBuffer> encrypted, + const AudioDecodeCB& audio_decode_cb) { + NOTREACHED(); + audio_decode_cb.Run(Status::kError, AudioFrames()); +} + +void FuchsiaDecryptor::DecryptAndDecodeVideo( + scoped_refptr<DecoderBuffer> encrypted, + const VideoDecodeCB& video_decode_cb) { + NOTREACHED(); + video_decode_cb.Run(Status::kError, nullptr); +} + +void FuchsiaDecryptor::ResetDecoder(StreamType stream_type) { + NOTREACHED(); +} + +void FuchsiaDecryptor::DeinitializeDecoder(StreamType stream_type) { + NOTREACHED(); +} + +bool FuchsiaDecryptor::CanAlwaysDecrypt() { + return false; +} + +void FuchsiaDecryptor::OnNewKey() { + base::AutoLock auto_lock(new_key_cb_lock_); + if (new_key_cb_) + new_key_cb_.Run(); +} + +} // namespace media diff --git a/chromium/media/fuchsia/cdm/fuchsia_decryptor.h b/chromium/media/fuchsia/cdm/fuchsia_decryptor.h new file mode 100644 index 00000000000..3af9e92f21b --- /dev/null +++ b/chromium/media/fuchsia/cdm/fuchsia_decryptor.h @@ -0,0 +1,68 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CDM_FUCHSIA_DECRYPTOR_H_ +#define MEDIA_FUCHSIA_CDM_FUCHSIA_DECRYPTOR_H_ + +#include <memory> + +#include "base/synchronization/lock.h" +#include "base/thread_annotations.h" +#include "media/base/decryptor.h" +#include "media/fuchsia/cdm/fuchsia_stream_decryptor.h" + +namespace fuchsia { +namespace media { +namespace drm { +class ContentDecryptionModule; +} // namespace drm +} // namespace media +} // namespace fuchsia + +namespace media { + +class FuchsiaClearStreamDecryptor; + +class FuchsiaDecryptor : public Decryptor { + public: + // Caller should make sure |cdm| lives longer than this class. + explicit FuchsiaDecryptor(fuchsia::media::drm::ContentDecryptionModule* cdm); + ~FuchsiaDecryptor() override; + + // media::Decryptor implementation: + void RegisterNewKeyCB(StreamType stream_type, + const NewKeyCB& key_added_cb) override; + void Decrypt(StreamType stream_type, + scoped_refptr<DecoderBuffer> encrypted, + const DecryptCB& decrypt_cb) override; + void CancelDecrypt(StreamType stream_type) override; + void InitializeAudioDecoder(const AudioDecoderConfig& config, + const DecoderInitCB& init_cb) override; + void InitializeVideoDecoder(const VideoDecoderConfig& config, + const DecoderInitCB& init_cb) override; + void DecryptAndDecodeAudio(scoped_refptr<DecoderBuffer> encrypted, + const AudioDecodeCB& audio_decode_cb) override; + void DecryptAndDecodeVideo(scoped_refptr<DecoderBuffer> encrypted, + const VideoDecodeCB& video_decode_cb) override; + void ResetDecoder(StreamType stream_type) override; + void DeinitializeDecoder(StreamType stream_type) override; + bool CanAlwaysDecrypt() override; + + // Called by FuchsiaCdm to notify about the new key. + void OnNewKey(); + + private: + fuchsia::media::drm::ContentDecryptionModule* const cdm_; + + base::Lock new_key_cb_lock_; + NewKeyCB new_key_cb_ GUARDED_BY(new_key_cb_lock_); + + std::unique_ptr<FuchsiaClearStreamDecryptor> audio_decryptor_; + + DISALLOW_COPY_AND_ASSIGN(FuchsiaDecryptor); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_CDM_FUCHSIA_DECRYPTOR_H_ diff --git a/chromium/media/fuchsia/cdm/fuchsia_stream_decryptor.cc b/chromium/media/fuchsia/cdm/fuchsia_stream_decryptor.cc new file mode 100644 index 00000000000..176a0251b0d --- /dev/null +++ b/chromium/media/fuchsia/cdm/fuchsia_stream_decryptor.cc @@ -0,0 +1,555 @@ +// Copyright 2019 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/fuchsia/cdm/fuchsia_stream_decryptor.h" + +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/media/drm/cpp/fidl.h> + +#include "base/bind.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "base/logging.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/decoder_buffer.h" +#include "media/base/decrypt_config.h" +#include "media/base/encryption_pattern.h" +#include "media/base/subsample_entry.h" +#include "media/fuchsia/common/sysmem_buffer_reader.h" +#include "media/fuchsia/common/sysmem_buffer_writer.h" + +namespace media { +namespace { + +// FuchsiaClearStreamDecryptor copies decrypted data immediately once it's +// available, so it doesn't need more than one output buffer. +const size_t kMinClearStreamOutputFrames = 1; + +std::string GetEncryptionMode(EncryptionMode mode) { + switch (mode) { + case EncryptionMode::kCenc: + return fuchsia::media::drm::ENCRYPTION_MODE_CENC; + case EncryptionMode::kCbcs: + return fuchsia::media::drm::ENCRYPTION_MODE_CBCS; + default: + NOTREACHED() << "unknown encryption mode " << static_cast<int>(mode); + return ""; + } +} + +fuchsia::media::KeyId GetKeyId(const std::string& key_id) { + fuchsia::media::KeyId fuchsia_key_id; + // |key_id| may be empty when sending clear frames while we don't have + // Key Id yet. Send a zero |key_id| in that case. + // TODO(crbug.com/1012525): Remove this case once fxb/38253 is resolved. + if (key_id.empty()) { + fuchsia_key_id.data = {0}; + } else { + DCHECK_EQ(key_id.size(), fuchsia::media::KEY_ID_SIZE); + std::copy(key_id.begin(), key_id.end(), fuchsia_key_id.data.begin()); + } + return fuchsia_key_id; +} + +std::vector<fuchsia::media::SubsampleEntry> GetSubsamples( + const std::vector<SubsampleEntry>& subsamples) { + std::vector<fuchsia::media::SubsampleEntry> fuchsia_subsamples( + subsamples.size()); + + for (size_t i = 0; i < subsamples.size(); i++) { + fuchsia_subsamples[i].clear_bytes = subsamples[i].clear_bytes; + fuchsia_subsamples[i].encrypted_bytes = subsamples[i].cypher_bytes; + } + + return fuchsia_subsamples; +} + +fuchsia::media::EncryptionPattern GetEncryptionPattern( + EncryptionPattern pattern) { + fuchsia::media::EncryptionPattern fuchsia_pattern; + fuchsia_pattern.clear_blocks = pattern.skip_byte_block(); + fuchsia_pattern.encrypted_blocks = pattern.crypt_byte_block(); + return fuchsia_pattern; +} + +// We shouldn't need to set Key ID for clear frames, but it's currently +// required by the CDM API, see fxb/38253 . This function takes +// |placeholder_key_id| to workaround that issue. +// TODO(crbug.com/1012525): Remove |placeholder_key_id| once fxb/38253 is +// resolved. +fuchsia::media::FormatDetails GetClearFormatDetails( + size_t packet_size, + const std::string& placeholder_key_id) { + fuchsia::media::EncryptedFormat encrypted_format; + encrypted_format.set_mode(fuchsia::media::drm::ENCRYPTION_MODE_CENC) + .set_key_id(GetKeyId(placeholder_key_id)) + .set_init_vector(std::vector<uint8_t>(DecryptConfig::kDecryptionKeySize)); + + std::vector<fuchsia::media::SubsampleEntry> subsamples(1); + subsamples[0].clear_bytes = packet_size; + subsamples[0].encrypted_bytes = 0; + encrypted_format.set_subsamples(subsamples); + + fuchsia::media::FormatDetails format; + format.set_format_details_version_ordinal(0); + format.mutable_domain()->crypto().set_encrypted(std::move(encrypted_format)); + return format; +} + +fuchsia::media::FormatDetails GetEncryptedFormatDetails( + const DecryptConfig* config) { + DCHECK(config); + + fuchsia::media::EncryptedFormat encrypted_format; + encrypted_format.set_mode(GetEncryptionMode(config->encryption_mode())) + .set_key_id(GetKeyId(config->key_id())) + .set_init_vector( + std::vector<uint8_t>(config->iv().begin(), config->iv().end())) + .set_subsamples(GetSubsamples(config->subsamples())); + if (config->encryption_mode() == EncryptionMode::kCbcs) { + DCHECK(config->encryption_pattern().has_value()); + encrypted_format.set_pattern( + GetEncryptionPattern(config->encryption_pattern().value())); + } + + fuchsia::media::FormatDetails format; + format.set_format_details_version_ordinal(0); + format.mutable_domain()->crypto().set_encrypted(std::move(encrypted_format)); + return format; +} + +} // namespace + +FuchsiaStreamDecryptorBase::FuchsiaStreamDecryptorBase( + fuchsia::media::StreamProcessorPtr processor) + : processor_(std::move(processor), this) {} + +FuchsiaStreamDecryptorBase::~FuchsiaStreamDecryptorBase() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void FuchsiaStreamDecryptorBase::DecryptInternal( + scoped_refptr<DecoderBuffer> encrypted) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + input_writer_queue_.EnqueueBuffer(std::move(encrypted)); +} + +void FuchsiaStreamDecryptorBase::ResetStream() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Close current stream and drop all the cached decoder buffers. + // Keep input and output buffers to avoid buffer re-allocation. + processor_.Reset(); + input_writer_queue_.ResetQueue(); +} + +// StreamProcessorHelper::Client implementation: +void FuchsiaStreamDecryptorBase::AllocateInputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + base::Optional<fuchsia::sysmem::BufferCollectionConstraints> + buffer_constraints = + SysmemBufferWriter::GetRecommendedConstraints(stream_constraints); + + if (!buffer_constraints.has_value()) { + OnError(); + return; + } + + input_pool_creator_ = + allocator_.MakeBufferPoolCreator(1 /* num_shared_token */); + + input_pool_creator_->Create( + std::move(buffer_constraints).value(), + base::BindOnce(&FuchsiaStreamDecryptorBase::OnInputBufferPoolCreated, + base::Unretained(this))); +} + +void FuchsiaStreamDecryptorBase::OnOutputFormat( + fuchsia::media::StreamOutputFormat format) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void FuchsiaStreamDecryptorBase::OnInputBufferPoolCreated( + std::unique_ptr<SysmemBufferPool> pool) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!pool) { + DLOG(ERROR) << "Fail to allocate input buffer."; + OnError(); + return; + } + + input_pool_ = std::move(pool); + + // Provide token before enabling writer. Tokens must be provided to + // StreamProcessor before getting the allocated buffers. + processor_.CompleteInputBuffersAllocation(input_pool_->TakeToken()); + + input_pool_->CreateWriter(base::BindOnce( + &FuchsiaStreamDecryptorBase::OnWriterCreated, base::Unretained(this))); +} + +void FuchsiaStreamDecryptorBase::OnWriterCreated( + std::unique_ptr<SysmemBufferWriter> writer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!writer) { + OnError(); + return; + } + + input_writer_queue_.Start( + std::move(writer), + base::BindRepeating(&FuchsiaStreamDecryptorBase::SendInputPacket, + base::Unretained(this)), + base::BindRepeating(&FuchsiaStreamDecryptorBase::ProcessEndOfStream, + base::Unretained(this))); +} + +void FuchsiaStreamDecryptorBase::SendInputPacket( + const DecoderBuffer* buffer, + StreamProcessorHelper::IoPacket packet) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!packet.unit_end()) { + // The encrypted data size is too big. Decryptor should consider + // splitting the buffer and update the IV and subsample entries. + // TODO(crbug.com/1003651): Handle large encrypted buffer correctly. For + // now, just reject the decryption. + LOG(ERROR) << "DecoderBuffer doesn't fit in one packet."; + OnError(); + return; + } + + fuchsia::media::FormatDetails format = + (buffer->decrypt_config()) + ? GetEncryptedFormatDetails(buffer->decrypt_config()) + : GetClearFormatDetails(packet.size(), last_new_key_id_); + + packet.set_format(std::move(format)); + processor_.Process(std::move(packet)); +} + +void FuchsiaStreamDecryptorBase::ProcessEndOfStream() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + processor_.ProcessEos(); +} + +std::unique_ptr<FuchsiaClearStreamDecryptor> +FuchsiaClearStreamDecryptor::Create( + fuchsia::media::drm::ContentDecryptionModule* cdm) { + DCHECK(cdm); + + fuchsia::media::drm::DecryptorParams params; + params.set_require_secure_mode(false); + params.mutable_input_details()->set_format_details_version_ordinal(0); + fuchsia::media::StreamProcessorPtr stream_processor; + cdm->CreateDecryptor(std::move(params), stream_processor.NewRequest()); + + return std::make_unique<FuchsiaClearStreamDecryptor>( + std::move(stream_processor)); +} + +FuchsiaClearStreamDecryptor::FuchsiaClearStreamDecryptor( + fuchsia::media::StreamProcessorPtr processor) + : FuchsiaStreamDecryptorBase(std::move(processor)) {} + +FuchsiaClearStreamDecryptor::~FuchsiaClearStreamDecryptor() = default; + +void FuchsiaClearStreamDecryptor::Decrypt( + scoped_refptr<DecoderBuffer> encrypted, + Decryptor::DecryptCB decrypt_cb) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!decrypt_cb_); + + decrypt_cb_ = std::move(decrypt_cb); + current_status_ = Decryptor::kSuccess; + DecryptInternal(std::move(encrypted)); +} + +void FuchsiaClearStreamDecryptor::CancelDecrypt() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + ResetStream(); + + // Fire |decrypt_cb_| immediately as required by Decryptor::CancelDecrypt. + if (decrypt_cb_) + std::move(decrypt_cb_).Run(Decryptor::kSuccess, nullptr); +} + +void FuchsiaClearStreamDecryptor::AllocateOutputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!stream_constraints.has_packet_count_for_client_max() || + !stream_constraints.has_packet_count_for_client_min()) { + DLOG(ERROR) << "StreamBufferConstraints doesn't contain required fields."; + OnError(); + return; + } + + size_t num_buffers_for_client = std::max( + kMinClearStreamOutputFrames, + static_cast<size_t>(stream_constraints.packet_count_for_client_min())); + size_t num_buffers_for_server = + stream_constraints.default_settings().packet_count_for_server(); + + output_pool_creator_ = + allocator_.MakeBufferPoolCreator(1 /* num_shared_token */); + output_pool_creator_->Create( + SysmemBufferReader::GetRecommendedConstraints(num_buffers_for_client), + base::BindOnce(&FuchsiaClearStreamDecryptor::OnOutputBufferPoolCreated, + base::Unretained(this), num_buffers_for_client, + num_buffers_for_server)); +} + +void FuchsiaClearStreamDecryptor::OnProcessEos() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Decryptor never pushes EOS frame. + NOTREACHED(); +} + +void FuchsiaClearStreamDecryptor::OnOutputPacket( + StreamProcessorHelper::IoPacket packet) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(decrypt_cb_); + + DCHECK(output_reader_); + if (!output_pool_->is_live()) { + DLOG(ERROR) << "Output buffer pool is dead."; + return; + } + + // If that's not the last packet for the current Decrypt() request then just + // store the output and wait for the next packet. + if (!packet.unit_end()) { + size_t pos = output_data_.size(); + output_data_.resize(pos + packet.size()); + + bool read_success = output_reader_->Read( + packet.index(), packet.offset(), + base::make_span(output_data_.data() + pos, packet.size())); + + if (!read_success) { + // If we've failed to read a partial packet then delay reporting the error + // until we've received the last packet to make sure we consume all output + // packets generated by the last Decrypt() call. + DLOG(ERROR) << "Fail to get decrypted result."; + current_status_ = Decryptor::kError; + output_data_.clear(); + } + + return; + } + + // We've received the last packet. Assemble DecoderBuffer and pass it to the + // DecryptCB. + auto clear_buffer = + base::MakeRefCounted<DecoderBuffer>(output_data_.size() + packet.size()); + clear_buffer->set_timestamp(packet.timestamp()); + + // Copy data received in the previous packets. + memcpy(clear_buffer->writable_data(), output_data_.data(), + output_data_.size()); + output_data_.clear(); + + // Copy data received in the last packet + bool read_success = output_reader_->Read( + packet.index(), packet.offset(), + base::make_span(clear_buffer->writable_data() + output_data_.size(), + packet.size())); + + if (!read_success) { + DLOG(ERROR) << "Fail to get decrypted result."; + current_status_ = Decryptor::kError; + } + + std::move(decrypt_cb_) + .Run(current_status_, current_status_ == Decryptor::kSuccess + ? std::move(clear_buffer) + : nullptr); +} + +void FuchsiaClearStreamDecryptor::OnNoKey() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Reset the queue. The client is expected to call Decrypt() with the same + // buffer again when it gets kNoKey. + input_writer_queue_.ResetQueue(); + + if (decrypt_cb_) + std::move(decrypt_cb_).Run(Decryptor::kNoKey, nullptr); +} + +void FuchsiaClearStreamDecryptor::OnError() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + ResetStream(); + if (decrypt_cb_) + std::move(decrypt_cb_).Run(Decryptor::kError, nullptr); +} + +void FuchsiaClearStreamDecryptor::OnOutputBufferPoolCreated( + size_t num_buffers_for_client, + size_t num_buffers_for_server, + std::unique_ptr<SysmemBufferPool> pool) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!pool) { + LOG(ERROR) << "Fail to allocate output buffer."; + OnError(); + return; + } + + output_pool_ = std::move(pool); + + // Provide token before enabling reader. Tokens must be provided to + // StreamProcessor before getting the allocated buffers. + processor_.CompleteOutputBuffersAllocation(num_buffers_for_client, + num_buffers_for_server, + output_pool_->TakeToken()); + + output_pool_->CreateReader(base::BindOnce( + &FuchsiaClearStreamDecryptor::OnOutputBufferPoolReaderCreated, + base::Unretained(this))); +} + +void FuchsiaClearStreamDecryptor::OnOutputBufferPoolReaderCreated( + std::unique_ptr<SysmemBufferReader> reader) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!reader) { + LOG(ERROR) << "Fail to enable output buffer reader."; + OnError(); + return; + } + + DCHECK(!output_reader_); + output_reader_ = std::move(reader); +} + +FuchsiaSecureStreamDecryptor::FuchsiaSecureStreamDecryptor( + fuchsia::media::StreamProcessorPtr processor, + Client* client) + : FuchsiaStreamDecryptorBase(std::move(processor)), client_(client) {} + +FuchsiaSecureStreamDecryptor::~FuchsiaSecureStreamDecryptor() = default; + +void FuchsiaSecureStreamDecryptor::SetOutputBufferCollectionToken( + fuchsia::sysmem::BufferCollectionTokenPtr token, + size_t num_buffers_for_decryptor, + size_t num_buffers_for_codec) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!complete_buffer_allocation_callback_); + complete_buffer_allocation_callback_ = + base::BindOnce(&StreamProcessorHelper::CompleteOutputBuffersAllocation, + base::Unretained(&processor_), num_buffers_for_decryptor, + num_buffers_for_codec, std::move(token)); + if (waiting_output_buffers_) { + std::move(complete_buffer_allocation_callback_).Run(); + waiting_output_buffers_ = false; + } +} + +void FuchsiaSecureStreamDecryptor::Decrypt( + scoped_refptr<DecoderBuffer> encrypted) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + DecryptInternal(std::move(encrypted)); +} + +void FuchsiaSecureStreamDecryptor::Reset() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + ResetStream(); + waiting_for_key_ = false; +} + +void FuchsiaSecureStreamDecryptor::AllocateOutputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (complete_buffer_allocation_callback_) { + std::move(complete_buffer_allocation_callback_).Run(); + } else { + waiting_output_buffers_ = true; + } +} + +void FuchsiaSecureStreamDecryptor::OnProcessEos() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + client_->OnDecryptorEndOfStreamPacket(); +} + +void FuchsiaSecureStreamDecryptor::OnOutputPacket( + StreamProcessorHelper::IoPacket packet) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + client_->OnDecryptorOutputPacket(std::move(packet)); +} + +FuchsiaSecureStreamDecryptor::NewKeyCB +FuchsiaSecureStreamDecryptor::GetOnNewKeyClosure() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + return BindToCurrentLoop(base::BindRepeating( + &FuchsiaSecureStreamDecryptor::OnNewKey, weak_factory_.GetWeakPtr())); +} + +void FuchsiaSecureStreamDecryptor::OnError() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + ResetStream(); + + // No need to reset other fields since OnError() is called for non-recoverable + // errors. + + client_->OnDecryptorError(); +} + +void FuchsiaSecureStreamDecryptor::OnNoKey() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(!waiting_for_key_); + + // Reset stream position, but keep all pending buffers. They will be + // resubmitted later, when we have a new key. + input_writer_queue_.ResetPositionAndPause(); + + if (retry_on_no_key_) { + retry_on_no_key_ = false; + input_writer_queue_.Unpause(); + return; + } + + waiting_for_key_ = true; + client_->OnDecryptorNoKey(); +} + +void FuchsiaSecureStreamDecryptor::OnNewKey(const std::string& key_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // Currently Widevine CDM requires a valid key_id for frames that are not + // encrypted, but we don't have a key_id in the beginning of the stream. To + // workaround this issue we save the |key_id| here and then use it for clear + // frames in SendInputPacket(). + // TODO(crbug.com/1012525): Remove this hack once fxb/38253 is resolved: CDM + // shouldn't need |key_id| to handle clear frames. + last_new_key_id_ = key_id; + + if (!waiting_for_key_) { + retry_on_no_key_ = true; + return; + } + + DCHECK(!retry_on_no_key_); + waiting_for_key_ = false; + input_writer_queue_.Unpause(); +} + +} // namespace media diff --git a/chromium/media/fuchsia/cdm/fuchsia_stream_decryptor.h b/chromium/media/fuchsia/cdm/fuchsia_stream_decryptor.h new file mode 100644 index 00000000000..d0962b32c02 --- /dev/null +++ b/chromium/media/fuchsia/cdm/fuchsia_stream_decryptor.h @@ -0,0 +1,186 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_CDM_FUCHSIA_STREAM_DECRYPTOR_H_ +#define MEDIA_FUCHSIA_CDM_FUCHSIA_STREAM_DECRYPTOR_H_ + +#include <fuchsia/media/drm/cpp/fidl.h> + +#include <memory> + +#include "base/sequence_checker.h" +#include "media/base/decryptor.h" +#include "media/fuchsia/common/stream_processor_helper.h" +#include "media/fuchsia/common/sysmem_buffer_pool.h" +#include "media/fuchsia/common/sysmem_buffer_writer_queue.h" + +namespace media { +class SysmemBufferReader; + +// Base class for media stream decryptor implementations. +class FuchsiaStreamDecryptorBase : public StreamProcessorHelper::Client { + public: + explicit FuchsiaStreamDecryptorBase( + fuchsia::media::StreamProcessorPtr processor); + ~FuchsiaStreamDecryptorBase() override; + + protected: + // StreamProcessorHelper::Client overrides. + void AllocateInputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) final; + void OnOutputFormat(fuchsia::media::StreamOutputFormat format) final; + + void DecryptInternal(scoped_refptr<DecoderBuffer> encrypted); + void ResetStream(); + + StreamProcessorHelper processor_; + + BufferAllocator allocator_; + + SysmemBufferWriterQueue input_writer_queue_; + + // Key ID for which we received the last OnNewKey() event. + std::string last_new_key_id_; + + SEQUENCE_CHECKER(sequence_checker_); + + private: + void OnInputBufferPoolCreated(std::unique_ptr<SysmemBufferPool> pool); + void OnWriterCreated(std::unique_ptr<SysmemBufferWriter> writer); + void SendInputPacket(const DecoderBuffer* buffer, + StreamProcessorHelper::IoPacket packet); + void ProcessEndOfStream(); + + std::unique_ptr<SysmemBufferPool::Creator> input_pool_creator_; + std::unique_ptr<SysmemBufferPool> input_pool_; + + DISALLOW_COPY_AND_ASSIGN(FuchsiaStreamDecryptorBase); +}; + +// Stream decryptor that copies output to clear DecodeBuffer's. Used for audio +// streams. +class FuchsiaClearStreamDecryptor : public FuchsiaStreamDecryptorBase { + public: + static std::unique_ptr<FuchsiaClearStreamDecryptor> Create( + fuchsia::media::drm::ContentDecryptionModule* cdm); + + FuchsiaClearStreamDecryptor(fuchsia::media::StreamProcessorPtr processor); + ~FuchsiaClearStreamDecryptor() override; + + // Decrypt() behavior should match media::Decryptor interface. + void Decrypt(scoped_refptr<DecoderBuffer> encrypted, + Decryptor::DecryptCB decrypt_cb); + void CancelDecrypt(); + + private: + // StreamProcessorHelper::Client overrides. + void AllocateOutputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) final; + void OnProcessEos() final; + void OnOutputPacket(StreamProcessorHelper::IoPacket packet) final; + void OnNoKey() final; + void OnError() final; + + void OnOutputBufferPoolCreated(size_t num_buffers_for_client, + size_t num_buffers_for_server, + std::unique_ptr<SysmemBufferPool> pool); + void OnOutputBufferPoolReaderCreated( + std::unique_ptr<SysmemBufferReader> reader); + + Decryptor::DecryptCB decrypt_cb_; + + std::unique_ptr<SysmemBufferPool::Creator> output_pool_creator_; + std::unique_ptr<SysmemBufferPool> output_pool_; + std::unique_ptr<SysmemBufferReader> output_reader_; + + // Used to re-assemble decrypted output that was split between multiple sysmem + // buffers. + Decryptor::Status current_status_ = Decryptor::kSuccess; + std::vector<uint8_t> output_data_; + + DISALLOW_COPY_AND_ASSIGN(FuchsiaClearStreamDecryptor); +}; + +// Stream decryptor that decrypts data to protected sysmem buffers. Used for +// video stream. +class FuchsiaSecureStreamDecryptor : public FuchsiaStreamDecryptorBase { + public: + class Client { + public: + virtual void OnDecryptorOutputPacket( + StreamProcessorHelper::IoPacket packet) = 0; + virtual void OnDecryptorEndOfStreamPacket() = 0; + virtual void OnDecryptorError() = 0; + virtual void OnDecryptorNoKey() = 0; + + protected: + virtual ~Client() = default; + }; + + using NewKeyCB = base::RepeatingCallback<void(const std::string& key_id)>; + + FuchsiaSecureStreamDecryptor(fuchsia::media::StreamProcessorPtr processor, + Client* client); + ~FuchsiaSecureStreamDecryptor() override; + + void SetOutputBufferCollectionToken( + fuchsia::sysmem::BufferCollectionTokenPtr token, + size_t num_buffers_for_decryptor, + size_t num_buffers_for_codec); + + // Enqueues the specified buffer to the input queue. Caller is allowed to + // queue as many buffers as it needs without waiting for results from the + // previous Decrypt() calls. + void Decrypt(scoped_refptr<DecoderBuffer> encrypted); + + // Returns closure that should be called when the key changes. This class + // uses this notification to handle NO_KEY errors. Note that this class can + // queue multiple input buffers so it's also responsible for resubmitting + // queued buffers after a new key is received. This is different from + // FuchsiaClearStreamDecryptor and media::Decryptor: they report NO_KEY error + // to the caller and expect the caller to resubmit same buffers again after + // the key is updated. + NewKeyCB GetOnNewKeyClosure(); + + // Drops all pending decryption requests. + void Reset(); + + private: + // StreamProcessorHelper::Client overrides. + void AllocateOutputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) final; + void OnProcessEos() final; + void OnOutputPacket(StreamProcessorHelper::IoPacket packet) final; + void OnNoKey() final; + void OnError() final; + + // Callback returned by GetOnNewKeyClosure(). When waiting for a key this + // method unpauses the stream to decrypt any pending buffers. + void OnNewKey(const std::string& key_id); + + Client* const client_; + + bool waiting_output_buffers_ = false; + base::OnceClosure complete_buffer_allocation_callback_; + + // Set to true if some keys have been updated recently. New key notifications + // are received from a LicenseSession, while DECRYPTOR_NO_KEY error is + // received from StreamProcessor. These are separate FIDL connections that are + // handled on different threads, so they are not synchronized. As result + // OnNewKey() may be called before we get OnNoKey(). To handle this case + // correctly OnNewKey() sets |retry_on_no_key_| and then OnNoKey() tries to + // restart the stream immediately if this flag is set. + bool retry_on_no_key_ = false; + + // Set to true if the stream is paused while we are waiting for new keys. + bool waiting_for_key_ = false; + + base::WeakPtrFactory<FuchsiaSecureStreamDecryptor> weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(FuchsiaSecureStreamDecryptor); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_CDM_FUCHSIA_STREAM_DECRYPTOR_H_ diff --git a/chromium/media/fuchsia/common/BUILD.gn b/chromium/media/fuchsia/common/BUILD.gn new file mode 100644 index 00000000000..578edf6eacd --- /dev/null +++ b/chromium/media/fuchsia/common/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright 2019 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. + +assert(is_fuchsia) + +source_set("common") { + sources = [ + "stream_processor_helper.cc", + "stream_processor_helper.h", + "sysmem_buffer_pool.cc", + "sysmem_buffer_pool.h", + "sysmem_buffer_reader.cc", + "sysmem_buffer_reader.h", + "sysmem_buffer_writer.cc", + "sysmem_buffer_writer.h", + "sysmem_buffer_writer_queue.cc", + "sysmem_buffer_writer_queue.h", + ] + + deps = [ + "//base", + "//media/base", + "//third_party/fuchsia-sdk/sdk:media", + "//third_party/fuchsia-sdk/sdk:sysmem", + ] +} diff --git a/chromium/media/fuchsia/common/stream_processor_helper.cc b/chromium/media/fuchsia/common/stream_processor_helper.cc new file mode 100644 index 00000000000..a7415803541 --- /dev/null +++ b/chromium/media/fuchsia/common/stream_processor_helper.cc @@ -0,0 +1,393 @@ +// Copyright 2019 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/fuchsia/common/stream_processor_helper.h" + +#include "base/bind.h" +#include "base/fuchsia/fuchsia_logging.h" + +namespace media { + +StreamProcessorHelper::IoPacket::IoPacket(size_t index, + size_t offset, + size_t size, + base::TimeDelta timestamp, + bool unit_end, + base::OnceClosure destroy_cb) + : index_(index), + offset_(offset), + size_(size), + timestamp_(timestamp), + unit_end_(unit_end), + destroy_cb_(std::move(destroy_cb)) {} + +StreamProcessorHelper::IoPacket::~IoPacket() = default; + +StreamProcessorHelper::IoPacket::IoPacket(IoPacket&&) = default; +StreamProcessorHelper::IoPacket& StreamProcessorHelper::IoPacket::operator=( + IoPacket&&) = default; + +// static +StreamProcessorHelper::IoPacket StreamProcessorHelper::IoPacket::CreateInput( + size_t index, + size_t size, + base::TimeDelta timestamp, + bool unit_end, + base::OnceClosure destroy_cb) { + return IoPacket(index, 0 /* offset */, size, timestamp, unit_end, + std::move(destroy_cb)); +} + +// static +StreamProcessorHelper::IoPacket StreamProcessorHelper::IoPacket::CreateOutput( + size_t index, + size_t offset, + size_t size, + base::TimeDelta timestamp, + bool unit_end, + base::OnceClosure destroy_cb) { + return IoPacket(index, offset, size, timestamp, unit_end, + std::move(destroy_cb)); +} + +StreamProcessorHelper::StreamProcessorHelper( + fuchsia::media::StreamProcessorPtr processor, + Client* client) + : processor_(std::move(processor)), client_(client), weak_factory_(this) { + DCHECK(processor_); + DCHECK(client_); + weak_this_ = weak_factory_.GetWeakPtr(); + + processor_.set_error_handler( + [this](zx_status_t status) { + ZX_LOG(ERROR, status) + << "The fuchsia.media.StreamProcessor channel was terminated."; + OnError(); + }); + + processor_.events().OnStreamFailed = + fit::bind_member(this, &StreamProcessorHelper::OnStreamFailed); + processor_.events().OnInputConstraints = + fit::bind_member(this, &StreamProcessorHelper::OnInputConstraints); + processor_.events().OnFreeInputPacket = + fit::bind_member(this, &StreamProcessorHelper::OnFreeInputPacket); + processor_.events().OnOutputConstraints = + fit::bind_member(this, &StreamProcessorHelper::OnOutputConstraints); + processor_.events().OnOutputFormat = + fit::bind_member(this, &StreamProcessorHelper::OnOutputFormat); + processor_.events().OnOutputPacket = + fit::bind_member(this, &StreamProcessorHelper::OnOutputPacket); + processor_.events().OnOutputEndOfStream = + fit::bind_member(this, &StreamProcessorHelper::OnOutputEndOfStream); + + processor_->EnableOnStreamFailed(); +} + +StreamProcessorHelper::~StreamProcessorHelper() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); +} + +void StreamProcessorHelper::Process(IoPacket input) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(processor_); + + fuchsia::media::Packet packet; + packet.mutable_header()->set_buffer_lifetime_ordinal( + input_buffer_lifetime_ordinal_); + packet.mutable_header()->set_packet_index(input.index()); + packet.set_buffer_index(packet.header().packet_index()); + packet.set_timestamp_ish(input.timestamp().InNanoseconds()); + packet.set_stream_lifetime_ordinal(stream_lifetime_ordinal_); + packet.set_start_offset(input.offset()); + packet.set_valid_length_bytes(input.size()); + packet.set_known_end_access_unit(input.unit_end()); + + active_stream_ = true; + + if (!input.format().IsEmpty()) { + processor_->QueueInputFormatDetails(stream_lifetime_ordinal_, + fidl::Clone(input.format())); + } + + DCHECK(input_packets_.find(input.index()) == input_packets_.end()); + input_packets_.insert_or_assign(input.index(), std::move(input)); + processor_->QueueInputPacket(std::move(packet)); +} + +void StreamProcessorHelper::ProcessEos() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(processor_); + + active_stream_ = true; + processor_->QueueInputEndOfStream(stream_lifetime_ordinal_); + processor_->FlushEndOfStreamAndCloseStream(stream_lifetime_ordinal_); +} + +void StreamProcessorHelper::Reset() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (!active_stream_) { + // Nothing to do if we don't have an active stream. + return; + } + + if (processor_) { + processor_->CloseCurrentStream(stream_lifetime_ordinal_, + /*release_input_buffers=*/false, + /*release_output_buffers=*/false); + } + + stream_lifetime_ordinal_ += 2; + active_stream_ = false; +} + +void StreamProcessorHelper::OnStreamFailed(uint64_t stream_lifetime_ordinal, + fuchsia::media::StreamError error) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (stream_lifetime_ordinal_ != stream_lifetime_ordinal) { + return; + } + + if (error == fuchsia::media::StreamError::DECRYPTOR_NO_KEY) { + // Always reset the stream since the current one has failed. + Reset(); + + client_->OnNoKey(); + return; + } + + OnError(); +} + +void StreamProcessorHelper::OnInputConstraints( + fuchsia::media::StreamBufferConstraints constraints) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // Buffer lifetime ordinal is an odd number incremented by 2 for each buffer + // generation as required by StreamProcessor. + input_buffer_lifetime_ordinal_ += 2; + + // Default settings are used in CompleteInputBuffersAllocation to finish + // StreamProcessor input buffers setup. + if (!constraints.has_default_settings() || + !constraints.default_settings().has_packet_count_for_server() || + !constraints.default_settings().has_packet_count_for_client()) { + DLOG(ERROR) + << "Received OnInputConstraints() with missing required fields."; + OnError(); + return; + } + + DCHECK(input_packets_.empty()); + input_buffer_constraints_ = std::move(constraints); + + client_->AllocateInputBuffers(input_buffer_constraints_); +} + +void StreamProcessorHelper::OnFreeInputPacket( + fuchsia::media::PacketHeader free_input_packet) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (!free_input_packet.has_buffer_lifetime_ordinal() || + !free_input_packet.has_packet_index()) { + DLOG(ERROR) << "Received OnFreeInputPacket() with missing required fields."; + OnError(); + return; + } + + if (free_input_packet.buffer_lifetime_ordinal() != + input_buffer_lifetime_ordinal_) { + return; + } + + auto it = input_packets_.find(free_input_packet.packet_index()); + if (it == input_packets_.end()) { + DLOG(ERROR) << "Received OnFreeInputPacket() with invalid packet index."; + OnError(); + return; + } + + // The packet should be destroyed only after it's removed from + // |input_packets_|. Otherwise packet destruction observer may call Process() + // for the next packet while the current packet is still in |input_packets_|. + auto packet = std::move(it->second); + input_packets_.erase(it); +} + +void StreamProcessorHelper::OnOutputConstraints( + fuchsia::media::StreamOutputConstraints output_constraints) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (!output_constraints.has_stream_lifetime_ordinal()) { + DLOG(ERROR) + << "Received OnOutputConstraints() with missing required fields."; + OnError(); + return; + } + + if (output_constraints.stream_lifetime_ordinal() != + stream_lifetime_ordinal_) { + return; + } + + if (!output_constraints.has_buffer_constraints_action_required() || + !output_constraints.buffer_constraints_action_required()) { + return; + } + + if (!output_constraints.has_buffer_constraints()) { + DLOG(ERROR) << "Received OnOutputConstraints() which requires buffer " + "constraints action, but without buffer constraints."; + OnError(); + return; + } + + // StreamProcessor API expects odd buffer lifetime ordinal, which is + // incremented by 2 for each buffer generation. + output_buffer_lifetime_ordinal_ += 2; + + output_buffer_constraints_ = + std::move(*output_constraints.mutable_buffer_constraints()); + + client_->AllocateOutputBuffers(output_buffer_constraints_); +} + +void StreamProcessorHelper::OnOutputFormat( + fuchsia::media::StreamOutputFormat output_format) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (!output_format.has_stream_lifetime_ordinal() || + !output_format.has_format_details()) { + DLOG(ERROR) << "Received OnOutputFormat() with missing required fields."; + OnError(); + return; + } + + if (output_format.stream_lifetime_ordinal() != stream_lifetime_ordinal_) { + return; + } + + client_->OnOutputFormat(std::move(output_format)); +} + +void StreamProcessorHelper::OnOutputPacket(fuchsia::media::Packet output_packet, + bool error_detected_before, + bool error_detected_during) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (!output_packet.has_header() || + !output_packet.header().has_buffer_lifetime_ordinal() || + !output_packet.header().has_packet_index() || + !output_packet.has_stream_lifetime_ordinal() || + !output_packet.has_buffer_index()) { + DLOG(ERROR) << "Received OnOutputPacket() with missing required fields."; + OnError(); + return; + } + + if (output_packet.header().buffer_lifetime_ordinal() != + output_buffer_lifetime_ordinal_) { + return; + } + + if (output_packet.stream_lifetime_ordinal() != stream_lifetime_ordinal_) { + // Output packets from old streams still need to be recycled. + OnRecycleOutputBuffer(output_buffer_lifetime_ordinal_, + output_packet.header().packet_index()); + return; + } + + auto buffer_index = output_packet.buffer_index(); + auto packet_index = output_packet.header().packet_index(); + base::TimeDelta timestamp; + if (output_packet.has_timestamp_ish()) { + timestamp = base::TimeDelta::FromNanoseconds(output_packet.timestamp_ish()); + } + + client_->OnOutputPacket(IoPacket::CreateOutput( + buffer_index, output_packet.start_offset(), + output_packet.valid_length_bytes(), timestamp, + output_packet.known_end_access_unit(), + base::BindOnce(&StreamProcessorHelper::OnRecycleOutputBuffer, weak_this_, + output_buffer_lifetime_ordinal_, packet_index))); +} + +void StreamProcessorHelper::OnOutputEndOfStream( + uint64_t stream_lifetime_ordinal, + bool error_detected_before) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (stream_lifetime_ordinal != stream_lifetime_ordinal_) { + return; + } + + stream_lifetime_ordinal_ += 2; + active_stream_ = false; + + client_->OnProcessEos(); +} + +void StreamProcessorHelper::OnError() { + processor_.Unbind(); + client_->OnError(); +} + +void StreamProcessorHelper::CompleteInputBuffersAllocation( + fuchsia::sysmem::BufferCollectionTokenPtr sysmem_token) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!input_buffer_constraints_.IsEmpty()); + fuchsia::media::StreamBufferPartialSettings settings; + settings.set_buffer_lifetime_ordinal(input_buffer_lifetime_ordinal_); + settings.set_buffer_constraints_version_ordinal( + input_buffer_constraints_.buffer_constraints_version_ordinal()); + settings.set_single_buffer_mode(false); + settings.set_packet_count_for_server( + input_buffer_constraints_.default_settings().packet_count_for_server()); + settings.set_packet_count_for_client( + input_buffer_constraints_.default_settings().packet_count_for_client()); + settings.set_sysmem_token(std::move(sysmem_token)); + processor_->SetInputBufferPartialSettings(std::move(settings)); +} + +void StreamProcessorHelper::CompleteOutputBuffersAllocation( + size_t num_buffers_for_client, + size_t num_buffers_for_server, + fuchsia::sysmem::BufferCollectionTokenPtr collection_token) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!output_buffer_constraints_.IsEmpty()); + DCHECK_LE(num_buffers_for_client, + output_buffer_constraints_.packet_count_for_client_max()); + + // Pass new output buffer settings to the stream processor. + fuchsia::media::StreamBufferPartialSettings settings; + settings.set_buffer_lifetime_ordinal(output_buffer_lifetime_ordinal_); + settings.set_buffer_constraints_version_ordinal( + output_buffer_constraints_.buffer_constraints_version_ordinal()); + settings.set_packet_count_for_client(num_buffers_for_client); + settings.set_packet_count_for_server(num_buffers_for_server); + settings.set_sysmem_token(std::move(collection_token)); + processor_->SetOutputBufferPartialSettings(std::move(settings)); + processor_->CompleteOutputBufferPartialSettings( + output_buffer_lifetime_ordinal_); +} + +void StreamProcessorHelper::OnRecycleOutputBuffer( + uint64_t buffer_lifetime_ordinal, + uint32_t packet_index) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (!processor_) + return; + + if (buffer_lifetime_ordinal != output_buffer_lifetime_ordinal_) + return; + + fuchsia::media::PacketHeader header; + header.set_buffer_lifetime_ordinal(buffer_lifetime_ordinal); + header.set_packet_index(packet_index); + processor_->RecycleOutputPacket(std::move(header)); +} + +} // namespace media diff --git a/chromium/media/fuchsia/common/stream_processor_helper.h b/chromium/media/fuchsia/common/stream_processor_helper.h new file mode 100644 index 00000000000..ed084c32645 --- /dev/null +++ b/chromium/media/fuchsia/common/stream_processor_helper.h @@ -0,0 +1,184 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_COMMON_STREAM_PROCESSOR_HELPER_H_ +#define MEDIA_FUCHSIA_COMMON_STREAM_PROCESSOR_HELPER_H_ + +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/sysmem/cpp/fidl.h> + +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/containers/flat_map.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" + +namespace media { + +// Helper class of fuchsia::media::StreamProcessor. It's responsible for: +// 1. Data validation check. +// 2. Stream/Buffer life time management. +// 3. Configure StreamProcessor and input/output buffer settings. +class StreamProcessorHelper { + public: + class IoPacket { + public: + static IoPacket CreateInput(size_t index, + size_t size, + base::TimeDelta timestamp, + bool unit_end, + base::OnceClosure destroy_cb); + + static IoPacket CreateOutput(size_t index, + size_t offset, + size_t size, + base::TimeDelta timestamp, + bool unit_end, + base::OnceClosure destroy_cb); + + IoPacket(size_t index, + size_t offset, + size_t size, + base::TimeDelta timestamp, + bool unit_end, + base::OnceClosure destroy_cb); + ~IoPacket(); + + IoPacket(IoPacket&&); + IoPacket& operator=(IoPacket&&); + + size_t index() const { return index_; } + size_t offset() const { return offset_; } + size_t size() const { return size_; } + base::TimeDelta timestamp() const { return timestamp_; } + bool unit_end() const { return unit_end_; } + void set_format(fuchsia::media::FormatDetails format) { + format_ = std::move(format); + } + const fuchsia::media::FormatDetails& format() const { return format_; } + + private: + size_t index_; + size_t offset_; + size_t size_; + base::TimeDelta timestamp_; + bool unit_end_; + fuchsia::media::FormatDetails format_; + base::ScopedClosureRunner destroy_cb_; + + DISALLOW_COPY_AND_ASSIGN(IoPacket); + }; + + class Client { + public: + // Allocate input/output buffers with the given constraints. Client should + // call ProvideInput/OutputBufferCollectionToken to finish the buffer + // allocation flow. + virtual void AllocateInputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) = 0; + virtual void AllocateOutputBuffers( + const fuchsia::media::StreamBufferConstraints& stream_constraints) = 0; + + // Called when all the pushed packets are processed. + virtual void OnProcessEos() = 0; + + // Called when output format is available. + virtual void OnOutputFormat(fuchsia::media::StreamOutputFormat format) = 0; + + // Called when output packet is available. Deleting |packet| will notify + // StreamProcessor the output buffer is available to be re-used. Client + // should delete |packet| on the same thread as this function. + virtual void OnOutputPacket(IoPacket packet) = 0; + + // Only available for decryption, which indicates currently the + // StreamProcessor doesn't have the content key to process. + virtual void OnNoKey() = 0; + + // Called when any fatal errors happens. + virtual void OnError() = 0; + + protected: + virtual ~Client() = default; + }; + + StreamProcessorHelper(fuchsia::media::StreamProcessorPtr processor, + Client* client); + ~StreamProcessorHelper(); + + // Process one packet. Caller can reuse the underlying buffer when the + // |packet| is destroyed. + void Process(IoPacket packet); + + // Push End-Of-Stream to StreamProcessor. No more data should be sent to + // StreamProcessor without calling Reset. + void ProcessEos(); + + // Provide input/output BufferCollectionToken to finish StreamProcessor buffer + // setup flow. + void CompleteInputBuffersAllocation( + fuchsia::sysmem::BufferCollectionTokenPtr token); + void CompleteOutputBuffersAllocation( + size_t num_buffers_for_client, + size_t num_buffers_for_server, + fuchsia::sysmem::BufferCollectionTokenPtr token); + + void Reset(); + + private: + // Event handlers for |processor_|. + void OnStreamFailed(uint64_t stream_lifetime_ordinal, + fuchsia::media::StreamError error); + void OnInputConstraints( + fuchsia::media::StreamBufferConstraints input_constraints); + void OnFreeInputPacket(fuchsia::media::PacketHeader free_input_packet); + void OnOutputConstraints( + fuchsia::media::StreamOutputConstraints output_constraints); + void OnOutputFormat(fuchsia::media::StreamOutputFormat output_format); + void OnOutputPacket(fuchsia::media::Packet output_packet, + bool error_detected_before, + bool error_detected_during); + void OnOutputEndOfStream(uint64_t stream_lifetime_ordinal, + bool error_detected_before); + + void OnError(); + + void OnRecycleOutputBuffer(uint64_t buffer_lifetime_ordinal, + uint32_t packet_index); + + uint64_t stream_lifetime_ordinal_ = 1; + + // Set to true if we've sent an input packet with the current + // stream_lifetime_ordinal_. + bool active_stream_ = false; + + // Input buffers. + uint64_t input_buffer_lifetime_ordinal_ = 1; + fuchsia::media::StreamBufferConstraints input_buffer_constraints_; + + // Map from packet index to corresponding input IoPacket. IoPacket should be + // owned by this class until StreamProcessor released the buffer. + base::flat_map<size_t, IoPacket> input_packets_; + + // Output buffers. + uint64_t output_buffer_lifetime_ordinal_ = 1; + fuchsia::media::StreamBufferConstraints output_buffer_constraints_; + + fuchsia::media::StreamProcessorPtr processor_; + Client* const client_; + + // FIDL interfaces are thread-affine (see crbug.com/1012875). + THREAD_CHECKER(thread_checker_); + + base::WeakPtr<StreamProcessorHelper> weak_this_; + base::WeakPtrFactory<StreamProcessorHelper> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(StreamProcessorHelper); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_COMMON_STREAM_PROCESSOR_HELPER_H_ diff --git a/chromium/media/fuchsia/common/sysmem_buffer_pool.cc b/chromium/media/fuchsia/common/sysmem_buffer_pool.cc new file mode 100644 index 00000000000..6c30ad8a0df --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_pool.cc @@ -0,0 +1,169 @@ +// Copyright 2019 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/fuchsia/common/sysmem_buffer_pool.h" + +#include <zircon/rights.h> +#include <algorithm> + +#include "base/bind.h" +#include "base/fuchsia/default_context.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "media/fuchsia/common/sysmem_buffer_reader.h" +#include "media/fuchsia/common/sysmem_buffer_writer.h" + +namespace media { + +SysmemBufferPool::Creator::Creator( + fuchsia::sysmem::BufferCollectionPtr collection, + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens) + : collection_(std::move(collection)), + shared_tokens_(std::move(shared_tokens)) { + collection_.set_error_handler( + [this](zx_status_t status) { + ZX_DLOG(ERROR, status) + << "Connection to BufferCollection was disconnected."; + collection_.Unbind(); + + if (create_cb_) + std::move(create_cb_).Run(nullptr); + }); +} + +SysmemBufferPool::Creator::~Creator() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); +} + +void SysmemBufferPool::Creator::Create( + fuchsia::sysmem::BufferCollectionConstraints constraints, + CreateCB create_cb) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!create_cb_); + create_cb_ = std::move(create_cb); + // BufferCollection needs to be synchronized to ensure that all token + // duplicate requests have been processed and sysmem knows about all clients + // that will be using this buffer collection. + collection_->Sync([this, constraints = std::move(constraints)]() mutable { + collection_->SetConstraints(true /* has constraints */, + std::move(constraints)); + + DCHECK(create_cb_); + std::move(create_cb_) + .Run(std::make_unique<SysmemBufferPool>(std::move(collection_), + std::move(shared_tokens_))); + }); +} + +SysmemBufferPool::SysmemBufferPool( + fuchsia::sysmem::BufferCollectionPtr collection, + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens) + : collection_(std::move(collection)), + shared_tokens_(std::move(shared_tokens)) { + collection_.set_error_handler([this](zx_status_t status) { + ZX_LOG(ERROR, status) << "fuchsia.sysmem.BufferCollection disconnected."; + OnError(); + }); +} + +SysmemBufferPool::~SysmemBufferPool() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (collection_) + collection_->Close(); +} + +fuchsia::sysmem::BufferCollectionTokenPtr SysmemBufferPool::TakeToken() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!shared_tokens_.empty()); + auto token = std::move(shared_tokens_.back()); + shared_tokens_.pop_back(); + return token; +} + +void SysmemBufferPool::CreateReader(CreateReaderCB create_cb) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!create_reader_cb_); + create_reader_cb_ = std::move(create_cb); + collection_->WaitForBuffersAllocated( + fit::bind_member(this, &SysmemBufferPool::OnBuffersAllocated)); +} + +void SysmemBufferPool::CreateWriter(CreateWriterCB create_cb) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!create_writer_cb_); + create_writer_cb_ = std::move(create_cb); + collection_->WaitForBuffersAllocated( + fit::bind_member(this, &SysmemBufferPool::OnBuffersAllocated)); +} + +void SysmemBufferPool::OnBuffersAllocated( + zx_status_t status, + fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (status != ZX_OK) { + ZX_LOG(ERROR, status) << "Fail to allocate sysmem buffers."; + OnError(); + return; + } + + if (create_reader_cb_) { + std::move(create_reader_cb_) + .Run(SysmemBufferReader::Create(std::move(buffer_collection_info))); + } else if (create_writer_cb_) { + std::move(create_writer_cb_) + .Run(SysmemBufferWriter::Create(std::move(buffer_collection_info))); + } +} + +void SysmemBufferPool::OnError() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + collection_.Unbind(); + if (create_reader_cb_) + std::move(create_reader_cb_).Run(nullptr); + if (create_writer_cb_) + std::move(create_writer_cb_).Run(nullptr); +} + +BufferAllocator::BufferAllocator() { + allocator_ = base::fuchsia::ComponentContextForCurrentProcess() + ->svc() + ->Connect<fuchsia::sysmem::Allocator>(); + + allocator_.set_error_handler([](zx_status_t status) { + // Just log a warning. We will handle BufferCollection the failure when + // trying to create a new BufferCollection. + ZX_DLOG(WARNING, status) + << "The fuchsia.sysmem.Allocator channel was disconnected."; + }); +} + +BufferAllocator::~BufferAllocator() = default; + +std::unique_ptr<SysmemBufferPool::Creator> +BufferAllocator::MakeBufferPoolCreator(size_t num_of_tokens) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + // Create a new sysmem buffer collection token for the allocated buffers. + fuchsia::sysmem::BufferCollectionTokenPtr collection_token; + allocator_->AllocateSharedCollection(collection_token.NewRequest()); + + // Create collection token for sharing with other components. + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens; + for (size_t i = 0; i < num_of_tokens; i++) { + fuchsia::sysmem::BufferCollectionTokenPtr token; + collection_token->Duplicate(ZX_RIGHT_SAME_RIGHTS, token.NewRequest()); + shared_tokens.push_back(std::move(token)); + } + + fuchsia::sysmem::BufferCollectionPtr buffer_collection; + + // Convert the token to a BufferCollection connection. + allocator_->BindSharedCollection(std::move(collection_token), + buffer_collection.NewRequest()); + + return std::make_unique<SysmemBufferPool::Creator>( + std::move(buffer_collection), std::move(shared_tokens)); +} + +} // namespace media diff --git a/chromium/media/fuchsia/common/sysmem_buffer_pool.h b/chromium/media/fuchsia/common/sysmem_buffer_pool.h new file mode 100644 index 00000000000..c4c6ceabb54 --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_pool.h @@ -0,0 +1,118 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_POOL_H_ +#define MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_POOL_H_ + +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/sysmem/cpp/fidl.h> +#include <lib/sys/cpp/component_context.h> + +#include <list> +#include <vector> + +#include "base/callback.h" +#include "base/containers/span.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" + +namespace media { + +class SysmemBufferReader; +class SysmemBufferWriter; + +// Pool of buffers allocated by sysmem. It owns BufferCollection. It doesn't +// provide any function read/write the buffers. Call should use +// ReadableBufferPool/WritableBufferPool for read/write. +class SysmemBufferPool { + public: + using CreateReaderCB = + base::OnceCallback<void(std::unique_ptr<SysmemBufferReader>)>; + using CreateWriterCB = + base::OnceCallback<void(std::unique_ptr<SysmemBufferWriter>)>; + + // Creates SysmemBufferPool asynchronously. It also owns the channel to + // fuchsia services. + class Creator { + public: + using CreateCB = + base::OnceCallback<void(std::unique_ptr<SysmemBufferPool>)>; + Creator( + fuchsia::sysmem::BufferCollectionPtr collection, + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens); + ~Creator(); + + void Create(fuchsia::sysmem::BufferCollectionConstraints constraints, + CreateCB build_cb); + + private: + fuchsia::sysmem::BufferCollectionPtr collection_; + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens_; + CreateCB create_cb_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(Creator); + }; + + SysmemBufferPool( + fuchsia::sysmem::BufferCollectionPtr collection, + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens); + ~SysmemBufferPool(); + + fuchsia::sysmem::BufferCollectionTokenPtr TakeToken(); + + // Create Reader/Writer to access raw memory. The returned Reader/Writer is + // owned by SysmemBufferPool and lives as long as SysmemBufferPool. + void CreateReader(CreateReaderCB create_cb); + void CreateWriter(CreateWriterCB create_cb); + + // Returns if this object is still usable. Caller must check this before + // calling SysmemBufferReader/Writer APIs. + bool is_live() const { return collection_.is_bound(); } + + private: + friend class BufferAllocator; + + void OnBuffersAllocated( + zx_status_t status, + fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info); + void OnError(); + + fuchsia::sysmem::BufferCollectionPtr collection_; + std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens_; + + CreateReaderCB create_reader_cb_; + CreateWriterCB create_writer_cb_; + + // FIDL interfaces are thread-affine (see crbug.com/1012875). + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(SysmemBufferPool); +}; + +// Wrapper of sysmem Allocator. +class BufferAllocator { + public: + BufferAllocator(); + ~BufferAllocator(); + + std::unique_ptr<SysmemBufferPool::Creator> MakeBufferPoolCreator( + size_t num_shared_token); + + // TODO(sergeyu): Update FuchsiaVideoDecoder to use SysmemBufferPool and + // remove this function. + fuchsia::sysmem::Allocator* raw() { return allocator_.get(); } + + private: + fuchsia::sysmem::AllocatorPtr allocator_; + + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(BufferAllocator); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_POOL_H_ diff --git a/chromium/media/fuchsia/common/sysmem_buffer_reader.cc b/chromium/media/fuchsia/common/sysmem_buffer_reader.cc new file mode 100644 index 00000000000..8667a877398 --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_reader.cc @@ -0,0 +1,56 @@ +// Copyright 2019 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/fuchsia/common/sysmem_buffer_reader.h" + +#include "base/fuchsia/fuchsia_logging.h" + +namespace media { + +SysmemBufferReader::SysmemBufferReader( + fuchsia::sysmem::BufferCollectionInfo_2 info) + : collection_info_(std::move(info)) {} + +SysmemBufferReader::~SysmemBufferReader() = default; + +bool SysmemBufferReader::Read(size_t index, + size_t offset, + base::span<uint8_t> data) { + DCHECK_LT(index, collection_info_.buffer_count); + const fuchsia::sysmem::BufferMemorySettings& settings = + collection_info_.settings.buffer_settings; + fuchsia::sysmem::VmoBuffer& buffer = collection_info_.buffers[index]; + DCHECK_LE(buffer.vmo_usable_start + offset + data.size(), + settings.size_bytes); + + size_t vmo_offset = buffer.vmo_usable_start + offset; + + // Invalidate cache. + if (settings.coherency_domain == fuchsia::sysmem::CoherencyDomain::RAM) { + zx_status_t status = buffer.vmo.op_range( + ZX_VMO_OP_CACHE_CLEAN_INVALIDATE, vmo_offset, data.size(), nullptr, 0); + ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to invalidate cache"; + } + + zx_status_t status = buffer.vmo.read(data.data(), vmo_offset, data.size()); + ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to read"; + return status == ZX_OK; +} + +// static +std::unique_ptr<SysmemBufferReader> SysmemBufferReader::Create( + fuchsia::sysmem::BufferCollectionInfo_2 info) { + return std::make_unique<SysmemBufferReader>(std::move(info)); +} + +// static +fuchsia::sysmem::BufferCollectionConstraints +SysmemBufferReader::GetRecommendedConstraints(size_t max_used_output_frames) { + fuchsia::sysmem::BufferCollectionConstraints buffer_constraints; + buffer_constraints.usage.cpu = fuchsia::sysmem::cpuUsageRead; + buffer_constraints.min_buffer_count_for_camping = max_used_output_frames; + return buffer_constraints; +} + +} // namespace media diff --git a/chromium/media/fuchsia/common/sysmem_buffer_reader.h b/chromium/media/fuchsia/common/sysmem_buffer_reader.h new file mode 100644 index 00000000000..0c314162e68 --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_reader.h @@ -0,0 +1,40 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_READER_H_ +#define MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_READER_H_ + +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/sysmem/cpp/fidl.h> + +#include <memory> + +#include "base/containers/span.h" + +namespace media { + +// Helper class to read content from fuchsia::sysmem::BufferCollection. +class SysmemBufferReader { + public: + static fuchsia::sysmem::BufferCollectionConstraints GetRecommendedConstraints( + size_t max_used_output_frames); + + static std::unique_ptr<SysmemBufferReader> Create( + fuchsia::sysmem::BufferCollectionInfo_2 info); + + explicit SysmemBufferReader(fuchsia::sysmem::BufferCollectionInfo_2 info); + ~SysmemBufferReader(); + + // Read the buffer content at |index| into |data|, starting from |offset|. + bool Read(size_t index, size_t offset, base::span<uint8_t> data); + + private: + fuchsia::sysmem::BufferCollectionInfo_2 collection_info_; + + DISALLOW_COPY_AND_ASSIGN(SysmemBufferReader); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_READER_H_ diff --git a/chromium/media/fuchsia/common/sysmem_buffer_writer.cc b/chromium/media/fuchsia/common/sysmem_buffer_writer.cc new file mode 100644 index 00000000000..addfafb6e94 --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_writer.cc @@ -0,0 +1,190 @@ +// Copyright 2019 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/fuchsia/common/sysmem_buffer_writer.h" + +#include <zircon/rights.h> +#include <algorithm> + +#include "base/bits.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "base/process/process_metrics.h" + +namespace media { + +class SysmemBufferWriter::Buffer { + public: + Buffer() = default; + + ~Buffer() { + if (!base_address_) { + return; + } + + size_t mapped_bytes = + base::bits::Align(offset_ + size_, base::GetPageSize()); + zx_status_t status = zx::vmar::root_self()->unmap( + reinterpret_cast<uintptr_t>(base_address_), mapped_bytes); + ZX_DCHECK(status == ZX_OK, status) << "zx_vmar_unmap"; + } + + Buffer(Buffer&&) = default; + Buffer& operator=(Buffer&&) = default; + + bool Initialize(zx::vmo vmo, + size_t offset, + size_t size, + fuchsia::sysmem::CoherencyDomain coherency_domain) { + DCHECK(!base_address_); + DCHECK(vmo); + + // zx_vmo_write() doesn't work for sysmem-allocated VMOs (see ZX-4854), so + // the VMOs have to be mapped. + size_t bytes_to_map = base::bits::Align(offset + size, base::GetPageSize()); + uintptr_t addr; + zx_status_t status = zx::vmar::root_self()->map( + /*vmar_offset=*/0, vmo, /*vmo_offset=*/0, bytes_to_map, + ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, &addr); + if (status != ZX_OK) { + ZX_DLOG(ERROR, status) << "zx_vmar_map"; + return false; + } + + base_address_ = reinterpret_cast<uint8_t*>(addr); + offset_ = offset; + size_ = size; + coherency_domain_ = coherency_domain; + + return true; + } + + bool is_used() const { return is_used_; } + size_t size() const { return size_; } + + // Copies as much data as possible from |data| to this input buffer. + size_t Write(base::span<const uint8_t> data) { + DCHECK(!is_used_); + is_used_ = true; + + size_t bytes_to_fill = std::min(size_, data.size()); + memcpy(base_address_ + offset_, data.data(), bytes_to_fill); + + // Flush CPU cache if StreamProcessor reads from RAM. + if (coherency_domain_ == fuchsia::sysmem::CoherencyDomain::RAM) { + zx_status_t status = zx_cache_flush(base_address_ + offset_, + bytes_to_fill, ZX_CACHE_FLUSH_DATA); + ZX_DCHECK(status == ZX_OK, status) << "zx_cache_flush"; + } + + return bytes_to_fill; + } + + void Release() { is_used_ = false; } + + private: + uint8_t* base_address_ = nullptr; + + // Buffer settings provided by sysmem. + size_t offset_ = 0; + size_t size_ = 0; + fuchsia::sysmem::CoherencyDomain coherency_domain_; + + // Set to true when this buffer is being used by the codec. + bool is_used_ = false; +}; + +SysmemBufferWriter::SysmemBufferWriter(std::vector<Buffer> buffers) + : buffers_(std::move(buffers)) {} + +SysmemBufferWriter::~SysmemBufferWriter() = default; + +size_t SysmemBufferWriter::Write(size_t index, base::span<const uint8_t> data) { + DCHECK_LT(index, buffers_.size()); + DCHECK(!buffers_[index].is_used()); + + return buffers_[index].Write(data); +} + +base::Optional<size_t> SysmemBufferWriter::Acquire() { + auto it = std::find_if( + buffers_.begin(), buffers_.end(), + [](const SysmemBufferWriter::Buffer& buf) { return !buf.is_used(); }); + + if (it == buffers_.end()) + return base::nullopt; + + return it - buffers_.begin(); +} + +void SysmemBufferWriter::Release(size_t index) { + DCHECK_LT(index, buffers_.size()); + buffers_[index].Release(); +} + +void SysmemBufferWriter::ReleaseAll() { + for (auto& buf : buffers_) { + buf.Release(); + } +} + +size_t SysmemBufferWriter::num_buffers() const { + return buffers_.size(); +} + +// static +std::unique_ptr<SysmemBufferWriter> SysmemBufferWriter::Create( + fuchsia::sysmem::BufferCollectionInfo_2 info) { + std::vector<SysmemBufferWriter::Buffer> buffers; + buffers.resize(info.buffer_count); + + fuchsia::sysmem::BufferMemorySettings& settings = + info.settings.buffer_settings; + for (size_t i = 0; i < info.buffer_count; ++i) { + fuchsia::sysmem::VmoBuffer& buffer = info.buffers[i]; + if (!buffers[i].Initialize(std::move(buffer.vmo), buffer.vmo_usable_start, + settings.size_bytes, + settings.coherency_domain)) { + return nullptr; + } + } + + return std::make_unique<SysmemBufferWriter>(std::move(buffers)); +} + +// static +base::Optional<fuchsia::sysmem::BufferCollectionConstraints> +SysmemBufferWriter::GetRecommendedConstraints( + const fuchsia::media::StreamBufferConstraints& stream_constraints) { + fuchsia::sysmem::BufferCollectionConstraints buffer_constraints; + + if (!stream_constraints.has_default_settings() || + !stream_constraints.default_settings().has_packet_count_for_client()) { + DLOG(ERROR) + << "Received StreamBufferConstaints with missing required fields."; + return base::nullopt; + } + + // Currently we have to map buffers VMOs to write to them (see ZX-4854) and + // memory cannot be mapped as write-only (see ZX-4872), so request RW access + // even though we will never need to read from these buffers. + buffer_constraints.usage.cpu = + fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite; + + buffer_constraints.min_buffer_count_for_camping = + stream_constraints.default_settings().packet_count_for_client(); + buffer_constraints.has_buffer_memory_constraints = true; + + const int kDefaultPacketSize = 512 * 1024; + buffer_constraints.buffer_memory_constraints.min_size_bytes = + stream_constraints.has_per_packet_buffer_bytes_recommended() + ? stream_constraints.per_packet_buffer_bytes_recommended() + : kDefaultPacketSize; + + buffer_constraints.buffer_memory_constraints.ram_domain_supported = true; + buffer_constraints.buffer_memory_constraints.cpu_domain_supported = true; + + return buffer_constraints; +} + +} // namespace media diff --git a/chromium/media/fuchsia/common/sysmem_buffer_writer.h b/chromium/media/fuchsia/common/sysmem_buffer_writer.h new file mode 100644 index 00000000000..9aed936f2b2 --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_writer.h @@ -0,0 +1,59 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_WRITER_H_ +#define MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_WRITER_H_ + +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/sysmem/cpp/fidl.h> + +#include <memory> + +#include "base/containers/span.h" +#include "base/optional.h" + +namespace media { + +// Helper class to write content into fuchsia::sysmem::BufferCollection. +class SysmemBufferWriter { + public: + class Buffer; + + static base::Optional<fuchsia::sysmem::BufferCollectionConstraints> + GetRecommendedConstraints( + const fuchsia::media::StreamBufferConstraints& stream_constraints); + + static std::unique_ptr<SysmemBufferWriter> Create( + fuchsia::sysmem::BufferCollectionInfo_2 info); + + explicit SysmemBufferWriter(std::vector<Buffer> buffers); + ~SysmemBufferWriter(); + + // Write the content of |data| into buffer at |index|. Return num of bytes + // written into the buffer. Write a used buffer will fail. It will mark the + // buffer as "used". + size_t Write(size_t index, base::span<const uint8_t> data); + + // Acquire unused buffer for write. If |min_size| is provided, the returned + // buffer will have available size larger than |min_size|. This will NOT + // mark the buffer as "used". + base::Optional<size_t> Acquire(); + + // Notify the pool buffer at |index| is free to write new data. + void Release(size_t index); + + // Mark all buffers as unused. + void ReleaseAll(); + + size_t num_buffers() const; + + private: + std::vector<Buffer> buffers_; + + DISALLOW_COPY_AND_ASSIGN(SysmemBufferWriter); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_WRITER_H_ diff --git a/chromium/media/fuchsia/common/sysmem_buffer_writer_queue.cc b/chromium/media/fuchsia/common/sysmem_buffer_writer_queue.cc new file mode 100644 index 00000000000..79a75eff644 --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_writer_queue.cc @@ -0,0 +1,179 @@ +// Copyright 2019 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/fuchsia/common/sysmem_buffer_writer_queue.h" + +#include <zircon/rights.h> +#include <algorithm> + +#include "base/bits.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "base/process/process_metrics.h" +#include "media/base/decoder_buffer.h" + +namespace media { + +struct SysmemBufferWriterQueue::PendingBuffer { + PendingBuffer(scoped_refptr<DecoderBuffer> buffer) : buffer(buffer) { + DCHECK(buffer); + } + ~PendingBuffer() = default; + + PendingBuffer(PendingBuffer&& other) = default; + PendingBuffer& operator=(PendingBuffer&& other) = default; + + const uint8_t* data() const { return buffer->data() + buffer_pos; } + size_t bytes_left() const { return buffer->data_size() - buffer_pos; } + void AdvanceCurrentPos(size_t bytes) { + DCHECK_LE(bytes, bytes_left()); + buffer_pos += bytes; + } + + scoped_refptr<DecoderBuffer> buffer; + size_t buffer_pos = 0; + + // Set to true when the consumer has finished processing the buffer and it can + // be released. + bool is_complete = false; + + // Index of the last buffer in the sysmem buffer collection that was used for + // this input buffer. Valid only when |bytes_left()==0|. + size_t tail_sysmem_buffer_index = 0; +}; + +SysmemBufferWriterQueue::SysmemBufferWriterQueue() = default; +SysmemBufferWriterQueue::~SysmemBufferWriterQueue() = default; + +void SysmemBufferWriterQueue::EnqueueBuffer( + scoped_refptr<DecoderBuffer> buffer) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + pending_buffers_.push_back(PendingBuffer(buffer)); + PumpPackets(); +} + +void SysmemBufferWriterQueue::Start(std::unique_ptr<SysmemBufferWriter> writer, + SendPacketCB send_packet_cb, + EndOfStreamCB end_of_stream_cb) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!writer_); + + writer_ = std::move(writer); + send_packet_cb_ = std::move(send_packet_cb); + end_of_stream_cb_ = std::move(end_of_stream_cb); + + PumpPackets(); +} + +void SysmemBufferWriterQueue::PumpPackets() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + auto weak_this = weak_factory_.GetWeakPtr(); + + while (writer_ && !is_paused_ && + input_queue_position_ < pending_buffers_.size()) { + PendingBuffer* current_buffer = &pending_buffers_[input_queue_position_]; + + if (current_buffer->buffer->end_of_stream()) { + pending_buffers_.pop_front(); + end_of_stream_cb_.Run(); + if (!weak_this) + return; + continue; + } + + base::Optional<size_t> index_opt = writer_->Acquire(); + + if (!index_opt.has_value()) { + // No input buffer available. + return; + } + + size_t sysmem_buffer_index = index_opt.value(); + + size_t bytes_filled = writer_->Write( + sysmem_buffer_index, + base::make_span(current_buffer->data(), current_buffer->bytes_left())); + current_buffer->AdvanceCurrentPos(bytes_filled); + + bool buffer_end = current_buffer->bytes_left() == 0; + + auto packet = StreamProcessorHelper::IoPacket::CreateInput( + sysmem_buffer_index, bytes_filled, current_buffer->buffer->timestamp(), + buffer_end, + base::BindOnce(&SysmemBufferWriterQueue::ReleaseBuffer, + weak_factory_.GetWeakPtr(), sysmem_buffer_index)); + + if (buffer_end) { + current_buffer->tail_sysmem_buffer_index = sysmem_buffer_index; + input_queue_position_ += 1; + } + + send_packet_cb_.Run(current_buffer->buffer.get(), std::move(packet)); + if (!weak_this) + return; + } +} + +void SysmemBufferWriterQueue::ResetQueue() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + pending_buffers_.clear(); + input_queue_position_ = 0; + is_paused_ = false; +} + +void SysmemBufferWriterQueue::ResetBuffers() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + writer_.reset(); + send_packet_cb_ = SendPacketCB(); + end_of_stream_cb_ = EndOfStreamCB(); +} + +void SysmemBufferWriterQueue::ResetPositionAndPause() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + for (auto& buffer : pending_buffers_) { + buffer.buffer_pos = 0; + buffer.is_complete = false; + } + input_queue_position_ = 0; + is_paused_ = true; +} + +void SysmemBufferWriterQueue::Unpause() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(is_paused_); + is_paused_ = false; + PumpPackets(); +} + +void SysmemBufferWriterQueue::ReleaseBuffer(size_t buffer_index) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(writer_); + + // Mark the input buffer as complete. + for (size_t i = 0; i < input_queue_position_; ++i) { + if (pending_buffers_[i].tail_sysmem_buffer_index == buffer_index) + pending_buffers_[i].is_complete = true; + } + + // Remove all complete buffers from the head of the queue since we no longer + // need them. Note that currently StreamProcessor doesn't guarantee that input + // buffers are released in the same order they were sent (see + // https://fuchsia.googlesource.com/fuchsia/+/3b12c8c5/sdk/fidl/fuchsia.media/stream_processor.fidl#1646 + // ). This means that some complete buffers will need to stay in the queue + // until all preceding packets are released as well. + while (!pending_buffers_.empty() && pending_buffers_.front().is_complete) { + pending_buffers_.pop_front(); + DCHECK_GT(input_queue_position_, 0U); + input_queue_position_--; + } + + writer_->Release(buffer_index); + PumpPackets(); +} + +size_t SysmemBufferWriterQueue::num_buffers() const { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + return writer_ ? writer_->num_buffers() : 0; +} + +} // namespace media diff --git a/chromium/media/fuchsia/common/sysmem_buffer_writer_queue.h b/chromium/media/fuchsia/common/sysmem_buffer_writer_queue.h new file mode 100644 index 00000000000..b3d7d0e03db --- /dev/null +++ b/chromium/media/fuchsia/common/sysmem_buffer_writer_queue.h @@ -0,0 +1,108 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_WRITER_QUEUE_H_ +#define MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_WRITER_QUEUE_H_ + +#include <fuchsia/media/cpp/fidl.h> +#include <fuchsia/sysmem/cpp/fidl.h> + +#include <memory> + +#include "base/containers/span.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/threading/thread_checker.h" +#include "media/fuchsia/common/stream_processor_helper.h" +#include "media/fuchsia/common/sysmem_buffer_writer.h" + +namespace media { + +class DecoderBuffer; + +// A SysmemBufferWriter wrapper that keeps a queue of pending DecodeBuffers, +// writes them to sysmem buffers and generates StreamProcessor packets. +class SysmemBufferWriterQueue { + public: + // Callback passed to StartSender(). |buffer| corresponds to the original + // buffer from which the |packet| was generated. + using SendPacketCB = + base::RepeatingCallback<void(const DecoderBuffer* buffer, + StreamProcessorHelper::IoPacket packet)>; + + // Called when processing DecoderBuffer that's marked as end-of-stream. + using EndOfStreamCB = base::RepeatingClosure; + + SysmemBufferWriterQueue(); + ~SysmemBufferWriterQueue(); + + // Enqueues buffer to the queue. + void EnqueueBuffer(scoped_refptr<DecoderBuffer> buffer); + + // Sets the buffer writer to use and starts sending outgoing packets using + // |send_packet_cb|. |end_of_stream_cb| will be called when processing each + // end-of-stream buffer. + void Start(std::unique_ptr<SysmemBufferWriter> writer, + SendPacketCB send_packet_cb, + EndOfStreamCB end_of_stream_cb); + + // Resets all pending buffers. Keeps the underlying sysmem buffers. + void ResetQueue(); + + // Resets the buffers. Keeps the current pending buffers, so they will still + // be sent once the new collection is allocated and passed to Start(). + void ResetBuffers(); + + // Resets pending queue position to the start of the queue and pauses the + // writer. All pending buffers will be resent when Unpause() is + // called. + void ResetPositionAndPause(); + + // Normally this should be called after restarting a stream in a + // StreamProcessor. + void Unpause(); + + // Number of buffers in the sysmem collection or 0 if sysmem buffers has not + // been allocated (i.e. before Start()). + size_t num_buffers() const; + + private: + struct PendingBuffer; + class SysmemBuffer; + + // Pumps pending buffers to SendPacketCB. + void PumpPackets(); + + // Callback called when a packet is destroyed. It marks the buffer as unused + // and tries to reuse it for other buffers if any. + void ReleaseBuffer(size_t buffer_index); + + // Buffers that are waiting to be sent. A buffer is removed from the queue + // when it and all previous buffers have finished decoding. + std::deque<PendingBuffer> pending_buffers_; + + // Position of the current buffer in |pending_buffers_|. + size_t input_queue_position_ = 0; + + // Indicates that the stream is paused and no packets should be sent until + // Unpause() is called. + bool is_paused_ = false; + + // Buffers for sysmem buffer collection. Not set until Start() is called. + std::unique_ptr<SysmemBufferWriter> writer_; + + SendPacketCB send_packet_cb_; + EndOfStreamCB end_of_stream_cb_; + + // FIDL interfaces are thread-affine (see crbug.com/1012875). + THREAD_CHECKER(thread_checker_); + + base::WeakPtrFactory<SysmemBufferWriterQueue> weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(SysmemBufferWriterQueue); +}; + +} // namespace media + +#endif // MEDIA_FUCHSIA_COMMON_SYSMEM_BUFFER_WRITER_QUEUE_H_ |