| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/gpu/chromeos/oop_video_decoder.h" |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/no_destructor.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "build/build_config.h" |
| #include "chromeos/components/cdm_factory_daemon/cdm_context_for_oopvd_impl.h" |
| #include "media/base/format_utils.h" |
| #include "media/base/video_util.h" |
| #include "media/gpu/buffer_validation.h" |
| #include "media/gpu/chromeos/native_pixmap_frame_resource.h" |
| #include "media/gpu/chromeos/platform_video_frame_utils.h" |
| #include "media/gpu/chromeos/video_frame_resource.h" |
| #include "media/gpu/macros.h" |
| #include "media/mojo/common/mojo_decoder_buffer_converter.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| |
| #if BUILDFLAG(USE_VAAPI) |
| #include "media/gpu/vaapi/vaapi_wrapper.h" |
| #endif // BUILDFLAG(USE_VAAPI) |
| |
| // Throughout this file, we have sprinkled many CHECK()s to assert invariants |
| // that should hold regardless of the behavior of the remote decoder or |
| // untrusted client. We use CHECK()s instead of DCHECK()s because |
| // OOPVideoDecoder and associated classes are very stateful so: |
| // |
| // a) They're hard to reason about. |
| // b) They're hard to fully exercise with tests. |
| // c) It's hard to reason if the violation of an invariant can have security |
| // implications because once we enter into a bad state, everything is fair |
| // game. |
| // |
| // Hence it's safer to crash and surface those crashes. |
| // |
| // More specifically: |
| // |
| // - It's illegal to call many methods if OOPVideoDecoder enters into an error |
| // state (tracked by |has_error_|). |
| // |
| // - The media::VideoDecoder interface demands that its users don't call certain |
| // methods while in specific states. An OOPVideoDecoder is used by an |
| // in-process class (the VideoDecoderPipeline) to communicate with an |
| // out-of-process video decoder. Therefore, we trust that the in-process user |
| // of this class abides by the requirements of the media::VideoDecoder |
| // interface and thus, we don't handle violations gracefully. In particular: |
| // |
| // - No media::VideoDecoder methods should be called before the |init_cb| |
| // passed to Initialize() is called. We track this interim state with |
| // |init_cb_|. |
| // |
| // - Initialize() should not be called while there are pending decodes (i.e., |
| // while !pending_decodes_.empty()). |
| // |
| // - No media::VideoDecoder methods should be called before the |closure| |
| // passed to Reset() is called. We track this interim state with |
| // |reset_cb_|. |
| |
| // TODO(b/220915557): OOPVideoDecoder cannot trust |remote_decoder_| (but |
| // |remote_decoder_| might trust us). We need to audit this class to make sure: |
| // |
| // - That OOPVideoDecoder validates everything coming from |
| // |remote_video_decoder_|. |
| // |
| // - That OOPVideoDecoder meets the requirements of the media::VideoDecoder and |
| // the media::VideoDecoderMixin interfaces. For example, we need to make sure |
| // we guarantee statements like "all pending Decode() requests will be |
| // finished or aborted before |closure| is called" (for |
| // VideoDecoder::Reset()). |
| // |
| // - That OOPVideoDecoder asserts it's not being misused (which might cause us |
| // to violate the requirements of the VideoDecoder interface). For |
| // example, the VideoDecoder interface says for Decode(): "this must not |
| // be called while there are pending Initialize(), Reset(), or Decode(EOS) |
| // requests." |
| |
| namespace media { |
| |
| namespace { |
| |
| // Size of the timestamp cache. We don't want the cache to grow without bounds. |
| // The maximum size is chosen to be the same as in the VaapiVideoDecoder. |
| constexpr size_t kTimestampCacheSize = 128; |
| |
| scoped_refptr<FrameResource> CreateDecodedFrameResource( |
| const VideoFrameLayout& layout, |
| const gfx::Rect& visible_rect, |
| const gfx::Size& natural_size, |
| std::vector<base::ScopedFD> dmabuf_fds, |
| base::TimeDelta timestamp, |
| const VideoFrameMetadata& metadata, |
| const gfx::ColorSpace& color_space, |
| const std::optional<gfx::HDRMetadata>& hdr_metadata) { |
| // The VideoFrame mojo traits already perform an extensive validation of the |
| // frame. No additional validations need to take place. |
| |
| scoped_refptr<media::NativePixmapFrameResource> native_pixmap_frame = |
| NativePixmapFrameResource::Create(layout, visible_rect, natural_size, |
| std::move(dmabuf_fds), timestamp); |
| if (!native_pixmap_frame) { |
| VLOGF(2) << "Could not create a NativePixmap-backed FrameResource"; |
| return nullptr; |
| } |
| |
| native_pixmap_frame->set_metadata(metadata); |
| native_pixmap_frame->set_color_space(color_space); |
| native_pixmap_frame->set_hdr_metadata(hdr_metadata); |
| |
| return native_pixmap_frame; |
| } |
| |
| // A singleton helper class that makes it easy to manage requests to wait until |
| // the supported video decoder configurations are known and cache those |
| // configurations. |
| // |
| // All public methods are thread- and sequence-safe. |
| class OOPVideoDecoderSupportedConfigsManager { |
| public: |
| static OOPVideoDecoderSupportedConfigsManager& Instance() { |
| static base::NoDestructor<OOPVideoDecoderSupportedConfigsManager> instance; |
| return *instance; |
| } |
| |
| std::optional<SupportedVideoDecoderConfigs> Get() { |
| base::AutoLock lock(lock_); |
| return configs_; |
| } |
| |
| VideoDecoderType GetDecoderType() { |
| base::AutoLock lock(lock_); |
| // This method should only be called in the initialization path of an |
| // OOPVideoDecoder instance. OOPVideoDecoder instances are initialized only |
| // after higher layers check that a VideoDecoderConfig is supported. If |
| // |decoder_type_| is not initialized to non-nullopt, it means that we're in |
| // one of two cases: |
| // |
| // a) We didn't try to get the supported configurations before initializing |
| // OOPVideoDecoder instances. This should be impossible as higher layers |
| // should guarantee that we know the supported configurations before |
| // creating OOPVideoDecoder instances. See the logic in |
| // InterfaceFactoryImpl::CreateVideoDecoder(). |
| // |
| // b) We did try to get the supported configurations but an error occurred. |
| // This case reduces to no supported configurations in which case, a |
| // higher layer should reject any initialization attempt. |
| // |
| // Therefore, GetDecoderType() should only be reached when |decoder_type_| |
| // is known. |
| CHECK(decoder_type_.has_value()); |
| return *decoder_type_; |
| } |
| |
| uint32_t GetInterfaceVersion() { |
| base::AutoLock lock(lock_); |
| // The justification for this CHECK() is similar as the one in |
| // GetDecoderType(). |
| CHECK(interface_version_.has_value()); |
| return *interface_version_; |
| } |
| |
| void NotifySupportKnown( |
| mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder, |
| base::OnceCallback<void(mojo::PendingRemote<mojom::VideoDecoder>)> cb) { |
| base::ReleasableAutoLock lock(&lock_); |
| if ((configs_ && interface_version_) || disconnected_) { |
| // Both the supported configurations and the interface version are already |
| // known (or a disconnection has occurred, in which case |configs_| should |
| // be an empty list). We can call |cb| immediately. |
| // |
| // We release the lock in case the |waiting_callback|.cb wants to re-enter |
| // OOPVideoDecoderSupportedConfigsManager by reaching |
| // OOPVideoDecoderSupportedConfigsManager::Get() in the callback. |
| lock.Release(); |
| std::move(cb).Run(std::move(oop_video_decoder)); |
| return; |
| } else if (!waiting_callbacks_.empty()) { |
| // There is a query in progress. We need to queue |cb| to call it later |
| // when the supported configurations and interface version are known. |
| waiting_callbacks_.emplace( |
| std::move(oop_video_decoder), std::move(cb), |
| base::SequencedTaskRunner::GetCurrentDefault()); |
| return; |
| } |
| |
| // At this point both the |configs_| and the |interface_version_| are |
| // unknown. We need to use |oop_video_decoder| to query them. |
| // |
| // Note: base::Unretained(this) is safe because the |
| // OOPVideoDecoderSupportedConfigsManager never gets destroyed. |
| CHECK(!configs_.has_value() && !interface_version_.has_value()); |
| oop_video_decoder_.Bind(std::move(oop_video_decoder)); |
| oop_video_decoder_.set_disconnect_handler(base::BindOnce( |
| &OOPVideoDecoderSupportedConfigsManager::OnDecoderDisconnected, |
| base::Unretained(this))); |
| oop_video_decoder_.QueryVersion(base::BindOnce( |
| &OOPVideoDecoderSupportedConfigsManager::OnGetInterfaceVersion, |
| base::Unretained(this))); |
| oop_video_decoder_->GetSupportedConfigs(base::BindOnce( |
| &OOPVideoDecoderSupportedConfigsManager::OnGetSupportedConfigs, |
| base::Unretained(this))); |
| |
| // Eventually, we need to call |cb|. We can't store |oop_video_decoder| here |
| // because it's been taken over by the |oop_video_decoder_|. For now, we'll |
| // store a default-constructed PendingRemote. Later, when we have to call |
| // |cb|, we can pass |oop_video_decoder_|.Unbind(). |
| waiting_callbacks_.emplace(mojo::PendingRemote<mojom::VideoDecoder>(), |
| std::move(cb), |
| base::SequencedTaskRunner::GetCurrentDefault()); |
| } |
| |
| void ResetForTesting() { |
| base::AutoLock lock(lock_); |
| oop_video_decoder_.reset(); |
| disconnected_ = false; |
| configs_.reset(); |
| decoder_type_.reset(); |
| interface_version_.reset(); |
| config_retry_count_ = 0u; |
| while (!waiting_callbacks_.empty()) { |
| waiting_callbacks_.pop(); |
| } |
| } |
| |
| private: |
| friend class base::NoDestructor<OOPVideoDecoderSupportedConfigsManager>; |
| |
| OOPVideoDecoderSupportedConfigsManager() = default; |
| ~OOPVideoDecoderSupportedConfigsManager() = default; |
| |
| void OnDecoderDisconnected() { |
| base::AutoLock lock(lock_); |
| configs_.emplace(); |
| decoder_type_ = std::nullopt; |
| interface_version_ = std::nullopt; |
| disconnected_ = true; |
| MaybeNotifyWaitingCallbacks(); |
| } |
| |
| void OnGetInterfaceVersion(uint32_t interface_version) { |
| base::AutoLock lock(lock_); |
| DCHECK(!interface_version_); |
| CHECK(!disconnected_); |
| interface_version_ = interface_version; |
| MaybeNotifyWaitingCallbacks(); |
| } |
| |
| void GetSupportedConfigs() { |
| base::AutoLock lock(lock_); |
| if (!disconnected_) { |
| oop_video_decoder_->GetSupportedConfigs(base::BindOnce( |
| &OOPVideoDecoderSupportedConfigsManager::OnGetSupportedConfigs, |
| base::Unretained(this))); |
| } |
| } |
| |
| void OnGetSupportedConfigs(const SupportedVideoDecoderConfigs& configs, |
| VideoDecoderType decoder_type) { |
| base::AutoLock lock(lock_); |
| DCHECK(!configs_); |
| DCHECK(!decoder_type_); |
| CHECK(!disconnected_); |
| constexpr uint32_t kMaxConfigRetries = 20; |
| if (decoder_type == VideoDecoderType::kVda || |
| decoder_type == VideoDecoderType::kVaapi || |
| decoder_type == VideoDecoderType::kV4L2) { |
| if (configs.empty() && config_retry_count_ < kMaxConfigRetries) { |
| // TODO(b/328092014): Redo this to not use a hacky delay. |
| VLOGF(1) << "OOPVD failed getting configs, retry after delay"; |
| config_retry_count_++; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce( |
| &OOPVideoDecoderSupportedConfigsManager::GetSupportedConfigs, |
| base::Unretained(this)), |
| base::Milliseconds(250)); |
| |
| return; |
| } |
| configs_ = configs; |
| decoder_type_ = decoder_type; |
| } else { |
| // The remote decoder is of an unexpected type, so let's assume it's bad. |
| configs_.emplace(); |
| } |
| |
| MaybeNotifyWaitingCallbacks(); |
| } |
| |
| void MaybeNotifyWaitingCallbacks() EXCLUSIVE_LOCKS_REQUIRED(lock_) { |
| if (!disconnected_ && |
| (!configs_.has_value() || !interface_version_.has_value())) { |
| // We're still connected but still waiting on either the supported |
| // configurations or the interface version. |
| return; |
| } |
| |
| // Here we either a) know both the supported configurations and the |
| // interface version; or b) have disconnected. In the latter case, |
| // |configs_| should be an empty list. |
| CHECK(!disconnected_ || (configs_.has_value() && configs_->empty())); |
| |
| while (!waiting_callbacks_.empty()) { |
| WaitingCallbackContext waiting_callback = |
| std::move(waiting_callbacks_.front()); |
| waiting_callbacks_.pop(); |
| |
| mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder = |
| waiting_callback.oop_video_decoder |
| ? std::move(waiting_callback.oop_video_decoder) |
| : oop_video_decoder_.Unbind(); |
| |
| if (waiting_callback.cb_task_runner->RunsTasksInCurrentSequence()) { |
| // Release the lock in case the |waiting_callback|.cb wants to re-enter |
| // OOPVideoDecoderSupportedConfigsManager by reaching |
| // OOPVideoDecoderSupportedConfigsManager::Get() in the callback. |
| base::AutoUnlock unlock(lock_); |
| std::move(waiting_callback.cb).Run(std::move(oop_video_decoder)); |
| } else { |
| waiting_callback.cb_task_runner->PostTask( |
| FROM_HERE, base::BindOnce(std::move(waiting_callback.cb), |
| std::move(oop_video_decoder))); |
| } |
| } |
| } |
| |
| base::Lock lock_; |
| |
| // The first PendingRemote that NotifySupportKnown() is called with is bound |
| // to |oop_video_decoder_| and we use it to query the supported configurations |
| // and the interface version of the out-of-process video decoder. |
| // |oop_video_decoder_| will get unbound once both of those things are known. |
| mojo::Remote<mojom::VideoDecoder> oop_video_decoder_; |
| |
| bool disconnected_ GUARDED_BY(lock_) = false; |
| |
| // The cached supported video decoder configurations, decoder type, and |
| // interface version. |
| std::optional<SupportedVideoDecoderConfigs> configs_ GUARDED_BY(lock_); |
| std::optional<VideoDecoderType> decoder_type_ GUARDED_BY(lock_); |
| std::optional<uint32_t> interface_version_ GUARDED_BY(lock_); |
| uint32_t config_retry_count_ GUARDED_BY(lock_) = 0; |
| |
| // This tracks everything that's needed to call a callback passed to |
| // NotifySupportKnown() that had to be queued because there was a query in |
| // progress. |
| struct WaitingCallbackContext { |
| WaitingCallbackContext( |
| mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder, |
| base::OnceCallback<void(mojo::PendingRemote<mojom::VideoDecoder>)> cb, |
| scoped_refptr<base::SequencedTaskRunner> cb_task_runner) |
| : oop_video_decoder(std::move(oop_video_decoder)), |
| cb(std::move(cb)), |
| cb_task_runner(std::move(cb_task_runner)) {} |
| mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder; |
| base::OnceCallback<void(mojo::PendingRemote<mojom::VideoDecoder>)> cb; |
| scoped_refptr<base::SequencedTaskRunner> cb_task_runner; |
| }; |
| base::queue<WaitingCallbackContext> waiting_callbacks_ GUARDED_BY(lock_); |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<VideoDecoderMixin> OOPVideoDecoder::Create( |
| mojo::PendingRemote<mojom::VideoDecoder> pending_remote_decoder, |
| std::unique_ptr<media::MediaLog> media_log, |
| scoped_refptr<base::SequencedTaskRunner> decoder_task_runner, |
| base::WeakPtr<VideoDecoderMixin::Client> client) { |
| // TODO(b/171813538): make the destructor of this class (as well as the |
| // destructor of sister class VaapiVideoDecoder) public so the explicit |
| // argument can be removed from this call to base::WrapUnique(). |
| return base::WrapUnique<VideoDecoderMixin>(new OOPVideoDecoder( |
| std::move(media_log), std::move(decoder_task_runner), std::move(client), |
| std::move(pending_remote_decoder))); |
| } |
| |
| // static |
| void OOPVideoDecoder::NotifySupportKnown( |
| mojo::PendingRemote<mojom::VideoDecoder> oop_video_decoder, |
| base::OnceCallback<void(mojo::PendingRemote<mojom::VideoDecoder>)> cb) { |
| OOPVideoDecoderSupportedConfigsManager::Instance().NotifySupportKnown( |
| std::move(oop_video_decoder), std::move(cb)); |
| } |
| |
| // static |
| std::optional<SupportedVideoDecoderConfigs> |
| OOPVideoDecoder::GetSupportedConfigs() { |
| return OOPVideoDecoderSupportedConfigsManager::Instance().Get(); |
| } |
| |
| // static |
| void OOPVideoDecoder::ResetGlobalStateForTesting() { |
| OOPVideoDecoderSupportedConfigsManager::Instance() |
| .ResetForTesting(); // IN-TEST |
| } |
| |
| OOPVideoDecoder::OOPVideoDecoder( |
| std::unique_ptr<media::MediaLog> media_log, |
| scoped_refptr<base::SequencedTaskRunner> decoder_task_runner, |
| base::WeakPtr<VideoDecoderMixin::Client> client, |
| mojo::PendingRemote<mojom::VideoDecoder> pending_remote_decoder) |
| : VideoDecoderMixin(std::move(media_log), |
| std::move(decoder_task_runner), |
| std::move(client)), |
| fake_timestamp_to_real_timestamp_cache_(kTimestampCacheSize), |
| remote_decoder_(std::move(pending_remote_decoder)), |
| weak_this_factory_(this) { |
| VLOGF(2); |
| DCHECK(decoder_task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Set a connection error handler in case the remote decoder gets |
| // disconnected, for instance, if the remote decoder process crashes. |
| // The remote decoder lives in a utility process. |
| // base::Unretained() is safe because `this` owns the `mojo::Remote`. |
| remote_decoder_.set_disconnect_handler( |
| base::BindOnce(&OOPVideoDecoder::Stop, base::Unretained(this))); |
| |
| // TODO(b/195769334): |remote_consumer_handle| corresponds to the data pipe |
| // that allows us to send data to the out-of-process video decoder. This data |
| // pipe is separate from the one set up by renderers to send data to the GPU |
| // process. Therefore, we're introducing the need for copying the encoded data |
| // from one pipe to the other. Ideally, we would just forward the pipe |
| // endpoint directly to the out-of-process video decoder and avoid the extra |
| // copy. This would require us to plumb the mojo::ScopedDataPipeConsumerHandle |
| // from the MojoVideoDecoderService all the way here. |
| mojo::ScopedDataPipeConsumerHandle remote_consumer_handle; |
| mojo_decoder_buffer_writer_ = MojoDecoderBufferWriter::Create( |
| GetDefaultDecoderBufferConverterCapacity(DemuxerStream::VIDEO), |
| &remote_consumer_handle); |
| CHECK(mojo_decoder_buffer_writer_); |
| |
| DCHECK(!video_frame_handle_releaser_remote_.is_bound()); |
| mojo::PendingReceiver<mojom::VideoFrameHandleReleaser> |
| video_frame_handle_releaser_receiver = |
| video_frame_handle_releaser_remote_.BindNewPipeAndPassReceiver(); |
| |
| // base::Unretained() is safe because `this` owns the `mojo::Remote`. |
| video_frame_handle_releaser_remote_.set_disconnect_handler( |
| base::BindOnce(&OOPVideoDecoder::Stop, base::Unretained(this))); |
| |
| DCHECK(!media_log_receiver_.is_bound()); |
| |
| CHECK(!has_error_); |
| // TODO(b/171813538): plumb the remaining parameters. |
| remote_decoder_->Construct(client_receiver_.BindNewEndpointAndPassRemote(), |
| media_log_receiver_.BindNewPipeAndPassRemote(), |
| std::move(video_frame_handle_releaser_receiver), |
| std::move(remote_consumer_handle), |
| media::mojom::CommandBufferIdPtr(), |
| gfx::ColorSpace()); |
| } |
| |
| OOPVideoDecoder::~OOPVideoDecoder() { |
| VLOGF(2); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| for (auto& pending_decode : pending_decodes_) { |
| decoder_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(std::move(pending_decode.second), |
| DecoderStatus::Codes::kAborted)); |
| } |
| } |
| |
| void OOPVideoDecoder::Initialize(const VideoDecoderConfig& config, |
| bool low_delay, |
| CdmContext* cdm_context, |
| InitCB init_cb, |
| const PipelineOutputCB& output_cb, |
| const WaitingCB& waiting_cb) { |
| DVLOGF(2) << config.AsHumanReadableString(); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!init_cb_); |
| CHECK(!HasPendingDecodeCallbacks()); |
| CHECK(!reset_cb_); |
| |
| // According to the VideoDecoder interface, Initialize() shouldn't be called |
| // during pending decodes. Therefore, in addition to CHECK()ing that there are |
| // no pending decode callbacks above, we also clear |
| // |fake_timestamp_to_real_timestamp_cache_| which, together with the |
| // validation in OnVideoFrameDecoded(), should guarantee that all frames |
| // received going forward come from Decode() requests after this point. |
| fake_timestamp_to_real_timestamp_cache_.Clear(); |
| |
| if (has_error_) { |
| // TODO(b/171813538): create specific error code for this decoder. |
| std::move(init_cb).Run(DecoderStatus::Codes::kFailed); |
| return; |
| } |
| |
| mojo::PendingRemote<mojom::CdmContextForOOPVD> |
| pending_remote_cdm_context_for_oopvd; |
| if (config.is_encrypted()) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| // There's logic in MojoVideoDecoderService::Initialize() to ensure that the |
| // CDM doesn't change across Initialize() calls. We rely on this assumption |
| // to ensure that creating a single CdmContextForOOPVDImpl that survives |
| // re-initializations is correct: the remote decoder requires a bound |
| // |pending_remote_stable_cdm_context| only for the first Initialize() call |
| // that sets up encryption. |
| DCHECK(!cdm_context_for_oopvd_ || |
| cdm_context == cdm_context_for_oopvd_->cdm_context()); |
| if (!cdm_context_for_oopvd_) { |
| if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) { |
| std::move(init_cb).Run( |
| DecoderStatus::Codes::kUnsupportedEncryptionMode); |
| return; |
| } |
| cdm_context_for_oopvd_ = |
| std::make_unique<chromeos::CdmContextForOOPVDImpl>(cdm_context); |
| cdm_context_for_oopvd_receiver_ = |
| std::make_unique<mojo::Receiver<mojom::CdmContextForOOPVD>>( |
| cdm_context_for_oopvd_.get(), |
| pending_remote_cdm_context_for_oopvd |
| .InitWithNewPipeAndPassReceiver()); |
| |
| // base::Unretained() is safe because |this| owns the mojo::Receiver. |
| cdm_context_for_oopvd_receiver_->set_disconnect_handler( |
| base::BindOnce(&OOPVideoDecoder::Stop, base::Unretained(this))); |
| } |
| #else |
| std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode); |
| return; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| initialized_for_protected_content_ = config.is_encrypted(); |
| |
| // This will be updated in OnInitializeDone() as needed. |
| needs_transcryption_ = false; |
| |
| init_cb_ = std::move(init_cb); |
| output_cb_ = output_cb; |
| waiting_cb_ = waiting_cb; |
| |
| remote_decoder_->Initialize(config, low_delay, |
| pending_remote_cdm_context_for_oopvd |
| ? mojom::Cdm::NewCdmContext(std::move( |
| pending_remote_cdm_context_for_oopvd)) |
| : nullptr, |
| base::BindOnce(&OOPVideoDecoder::OnInitializeDone, |
| weak_this_factory_.GetWeakPtr())); |
| } |
| |
| void OOPVideoDecoder::OnInitializeDone(const DecoderStatus& status, |
| bool needs_bitstream_conversion, |
| int32_t max_decode_requests, |
| VideoDecoderType decoder_type, |
| bool needs_transcryption) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!has_error_); |
| |
| if (max_decode_requests <= 0) { |
| Stop(); |
| return; |
| } |
| |
| const VideoDecoderType expected_decoder_type = |
| OOPVideoDecoderSupportedConfigsManager::Instance().GetDecoderType(); |
| |
| if (!status.is_ok() || decoder_type != expected_decoder_type || |
| (remote_decoder_type_ != VideoDecoderType::kUnknown && |
| remote_decoder_type_ != decoder_type)) { |
| Stop(); |
| return; |
| } |
| |
| needs_bitstream_conversion_ = needs_bitstream_conversion; |
| max_decode_requests_ = max_decode_requests; |
| remote_decoder_type_ = decoder_type; |
| |
| needs_transcryption_ = |
| initialized_for_protected_content_ && needs_transcryption; |
| |
| std::move(init_cb_).Run(status); |
| } |
| |
| void OOPVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer, |
| DecodeCB decode_cb) { |
| DVLOGF(4); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!init_cb_); |
| CHECK(!reset_cb_); |
| CHECK(!is_flushing_); |
| |
| if (has_error_ || remote_decoder_type_ == VideoDecoderType::kUnknown) { |
| DeferDecodeCallback(std::move(decode_cb), |
| DecoderStatus::Codes::kNotInitialized); |
| return; |
| } |
| |
| if (decode_counter_ == std::numeric_limits<uint64_t>::max()) { |
| // Error out in case of overflow. |
| DeferDecodeCallback(std::move(decode_cb), DecoderStatus::Codes::kFailed); |
| return; |
| } |
| |
| // If we change |buffer| to have a fake timestamp, we'll need to restore the |
| // original timestamp in case higher layers rely on that timestamp. The |
| // |buffer_timestamp_restorer| ensures that happens before Decode() returns. |
| CHECK(buffer); |
| base::ScopedClosureRunner buffer_timestamp_restorer; |
| if (!buffer->end_of_stream()) { |
| const base::TimeDelta next_fake_timestamp = |
| current_fake_timestamp_ + base::Microseconds(1u); |
| if (next_fake_timestamp == current_fake_timestamp_) { |
| // We've reached the maximum base::TimeDelta. |
| DeferDecodeCallback(std::move(decode_cb), DecoderStatus::Codes::kFailed); |
| return; |
| } |
| current_fake_timestamp_ = next_fake_timestamp; |
| DCHECK( |
| fake_timestamp_to_real_timestamp_cache_.Peek(current_fake_timestamp_) == |
| fake_timestamp_to_real_timestamp_cache_.end()); |
| fake_timestamp_to_real_timestamp_cache_.Put(current_fake_timestamp_, |
| buffer->timestamp()); |
| buffer_timestamp_restorer.ReplaceClosure(base::BindOnce( |
| [](scoped_refptr<DecoderBuffer> decoder_buffer, |
| base::TimeDelta original_timestamp) { |
| decoder_buffer->set_timestamp(original_timestamp); |
| }, |
| buffer, buffer->timestamp())); |
| buffer->set_timestamp(current_fake_timestamp_); |
| } |
| |
| const uint64_t decode_id = decode_counter_++; |
| pending_decodes_.insert({decode_id, std::move(decode_cb)}); |
| |
| mojom::DecoderBufferPtr mojo_buffer = |
| mojo_decoder_buffer_writer_->WriteDecoderBuffer(buffer); |
| if (!mojo_buffer) { |
| Stop(); |
| return; |
| } |
| |
| is_flushing_ = buffer->end_of_stream(); |
| remote_decoder_->Decode( |
| std::move(mojo_buffer), |
| base::BindOnce(&OOPVideoDecoder::OnDecodeDone, |
| weak_this_factory_.GetWeakPtr(), decode_id, is_flushing_)); |
| } |
| |
| void OOPVideoDecoder::OnDecodeDone(uint64_t decode_id, |
| bool is_flush_cb, |
| const DecoderStatus& status) { |
| DVLOGF(4); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!has_error_); |
| |
| // Check that decode callbacks are called in the same order as Decode(). |
| CHECK(!pending_decodes_.empty()); |
| if (pending_decodes_.cbegin()->first != decode_id) { |
| VLOGF(2) << "Unexpected decode callback for request " << decode_id; |
| Stop(); |
| return; |
| } |
| |
| if (is_flush_cb) { |
| CHECK(is_flushing_); |
| |
| // Check that the |decode_cb| corresponding to the flush is not called until |
| // the decode callback has been called for each pending decode. |
| CHECK_EQ(num_deferred_decode_cbs_, 0u); |
| if (pending_decodes_.size() != 1) { |
| VLOGF(2) << "Received a flush callback while having pending decodes"; |
| Stop(); |
| return; |
| } |
| |
| // After a flush is completed, we shouldn't receive decoded frames |
| // corresponding to Decode() calls that came in prior to the flush. The |
| // clearing of the cache together with the validation in |
| // OnVideoFrameDecoded() should guarantee this. |
| fake_timestamp_to_real_timestamp_cache_.Clear(); |
| |
| is_flushing_ = false; |
| } |
| |
| auto it = pending_decodes_.begin(); |
| DecodeCB decode_cb = std::move(it->second); |
| pending_decodes_.erase(it); |
| std::move(decode_cb).Run(status); |
| } |
| |
| void OOPVideoDecoder::DeferDecodeCallback(DecodeCB decode_cb, |
| const DecoderStatus& status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // TODO(b/220915557): it's very unlikely that we'll get an integer overflow |
| // here, but should we handle it gracefully if we do? |
| CHECK_LT(num_deferred_decode_cbs_, std::numeric_limits<uint64_t>::max()); |
| num_deferred_decode_cbs_++; |
| decoder_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&OOPVideoDecoder::CallDeferredDecodeCallback, |
| weak_this_factory_.GetWeakPtr(), |
| std::move(decode_cb), status)); |
| } |
| |
| void OOPVideoDecoder::CallDeferredDecodeCallback(DecodeCB decode_cb, |
| const DecoderStatus& status) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::move(decode_cb).Run(status); |
| num_deferred_decode_cbs_--; |
| } |
| |
| bool OOPVideoDecoder::HasPendingDecodeCallbacks() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return !pending_decodes_.empty() || num_deferred_decode_cbs_ > 0; |
| } |
| |
| void OOPVideoDecoder::Reset(base::OnceClosure reset_cb) { |
| DVLOGF(2); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!init_cb_); |
| CHECK(!reset_cb_); |
| |
| reset_cb_ = std::move(reset_cb); |
| |
| if (has_error_ || remote_decoder_type_ == VideoDecoderType::kUnknown) { |
| // Post a task instead of calling |reset_cb| immediately in order to keep |
| // the relative order between decode callbacks (posted as tasks in Decode()) |
| // and the reset callback. |
| // |
| // Note: we don't post std::move(reset_cb_) as the task because we want |
| // |reset_cb_| to be valid until it's actually called so that we can |
| // properly enforce the VideoDecoder API requirement that no VideoDecoder |
| // calls are made before the reset callback is executed. |
| decoder_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&OOPVideoDecoder::CallResetCallback, |
| weak_this_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| remote_decoder_->Reset(base::BindOnce(&OOPVideoDecoder::OnResetDone, |
| weak_this_factory_.GetWeakPtr())); |
| } |
| |
| void OOPVideoDecoder::OnResetDone() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!has_error_); |
| CHECK(reset_cb_); |
| CHECK_EQ(num_deferred_decode_cbs_, 0u); |
| if (!pending_decodes_.empty()) { |
| VLOGF(2) << "Received a reset callback while having pending decodes"; |
| Stop(); |
| return; |
| } |
| |
| // After a reset is completed, we shouldn't receive decoded frames |
| // corresponding to Decode() calls that came in prior to the reset (similar to |
| // a flush). That's because according to the media::VideoDecoder and |
| // media::mojom::VideoDecoder interfaces, all ongoing Decode() |
| // requests must be completed or aborted prior to executing the reset |
| // callback. The clearing of the cache together with the validation in |
| // OnVideoFrameDecoded() should guarantee this. |
| fake_timestamp_to_real_timestamp_cache_.Clear(); |
| |
| CallResetCallback(); |
| } |
| |
| void OOPVideoDecoder::CallResetCallback() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(reset_cb_); |
| std::move(reset_cb_).Run(); |
| } |
| |
| void OOPVideoDecoder::Stop() { |
| VLOGF(2); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (has_error_) |
| return; |
| |
| has_error_ = true; |
| |
| // There may be in-flight decode, initialize or reset callbacks. |
| // Invalidate any outstanding weak pointers so those callbacks are ignored. |
| weak_this_factory_.InvalidateWeakPtrs(); |
| |
| // |init_cb_| is likely to reentrantly destruct |this|, so we check for that |
| // using an on-stack WeakPtr. |
| base::WeakPtr<OOPVideoDecoder> weak_this = weak_this_factory_.GetWeakPtr(); |
| |
| client_receiver_.reset(); |
| media_log_receiver_.reset(); |
| remote_decoder_.reset(); |
| mojo_decoder_buffer_writer_.reset(); |
| video_frame_handle_releaser_remote_.reset(); |
| fake_timestamp_to_real_timestamp_cache_.Clear(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| cdm_context_for_oopvd_receiver_.reset(); |
| cdm_context_for_oopvd_.reset(); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| if (init_cb_) |
| std::move(init_cb_).Run(DecoderStatus::Codes::kFailed); |
| |
| if (!weak_this) |
| return; |
| |
| for (auto& pending_decode : pending_decodes_) { |
| // Note that Stop() may be called from within Decode(), and according to the |
| // media::VideoDecoder interface, the decode callback should not be called |
| // from within Decode(). Therefore, we should not call the decode callbacks |
| // here, and instead, we should post them as tasks. |
| DeferDecodeCallback(std::move(pending_decode.second), |
| DecoderStatus::Codes::kFailed); |
| } |
| pending_decodes_.clear(); |
| is_flushing_ = false; |
| |
| if (reset_cb_) { |
| // We post a task instead of calling |reset_cb_| immediately so that we keep |
| // the order of pending decode callbacks (posted as tasks above) with |
| // respect to the reset callback. |
| decoder_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&OOPVideoDecoder::CallResetCallback, |
| weak_this_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void OOPVideoDecoder::ReleaseVideoFrame( |
| const base::UnguessableToken& release_token) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!has_error_); |
| CHECK(video_frame_handle_releaser_remote_.is_bound()); |
| |
| video_frame_handle_releaser_remote_->ReleaseVideoFrame( |
| release_token, /*release_sync_token=*/{}); |
| } |
| |
| void OOPVideoDecoder::ApplyResolutionChange() { |
| NOTREACHED(); |
| } |
| |
| bool OOPVideoDecoder::NeedsBitstreamConversion() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(!has_error_); |
| CHECK_NE(remote_decoder_type_, VideoDecoderType::kUnknown); |
| return needs_bitstream_conversion_; |
| } |
| |
| bool OOPVideoDecoder::CanReadWithoutStalling() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(!init_cb_); |
| // TODO(b/220915557): according to the VideoDecoder interface, no VideoDecoder |
| // calls should be made before the reset callback is executed. In theory, this |
| // includes CanReadWithoutStalling(). However, asserting this through the |
| // commented CHECK(!reset_cb_) below causes a crash because we need to call |
| // CanReadWithoutStalling() in the frame output callback |
| // (VideoDecoderPipeline::OnFrameDecoded()) which can happen in an in-progress |
| // Reset(). It's likely that the VideoDecoder restriction expressed above does |
| // not include CanReadWithoutStalling() because |
| // MojoVideoDecoderService::OnDecoderOutput() (a frame output callback) |
| // already calls VideoDecoder::CanReadWithoutStalling(). If so, then we should |
| // update the VideoDecoder::Reset() documentation. |
| // CHECK(!reset_cb_); |
| CHECK(!has_error_); |
| return can_read_without_stalling_; |
| } |
| |
| int OOPVideoDecoder::GetMaxDecodeRequests() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(!has_error_); |
| CHECK_NE(remote_decoder_type_, VideoDecoderType::kUnknown); |
| return base::strict_cast<int>(max_decode_requests_); |
| } |
| |
| VideoDecoderType OOPVideoDecoder::GetDecoderType() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(!init_cb_); |
| CHECK(!reset_cb_); |
| return VideoDecoderType::kOutOfProcess; |
| } |
| |
| bool OOPVideoDecoder::IsPlatformDecoder() const { |
| NOTREACHED(); |
| } |
| |
| bool OOPVideoDecoder::NeedsTranscryption() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return needs_transcryption_; |
| } |
| |
| void OOPVideoDecoder::OnVideoFrameDecoded( |
| const scoped_refptr<VideoFrame>& frame, |
| bool can_read_without_stalling, |
| const std::optional<base::UnguessableToken>& release_token) { |
| DVLOGF(4); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!has_error_); |
| |
| if (init_cb_) { |
| VLOGF(2) << "Received a decoded frame while waiting for initialization"; |
| Stop(); |
| return; |
| } |
| |
| // According to the media::VideoDecoder API, |output_cb_| should not be |
| // supplied with EOS frames. |
| if (frame->metadata().end_of_stream) { |
| VLOGF(2) << "Unexpectedly received an EOS frame"; |
| Stop(); |
| return; |
| } |
| if (!frame->metadata().allow_overlay) { |
| // All decoded frames should be eligible for overlay promotion at this stage |
| // in the pipeline. |
| VLOGF(2) << "Unexpectedly received a frame with allow_overlay = false"; |
| Stop(); |
| return; |
| } |
| if (!frame->metadata().read_lock_fences_enabled) { |
| // The remote decoder should expect that frames are returned only when they |
| // are no longer needed by the client. |
| VLOGF(2) << "Unexpectedly received a frame with read_lock_fences_enabled =" |
| " false"; |
| Stop(); |
| return; |
| } |
| if (!frame->metadata().power_efficient) { |
| // All frames coming from a hardware decoder should have been decoded in a |
| // power efficient manner. |
| VLOGF(2) << "Unexpectedly received a frame with power_efficient = false"; |
| Stop(); |
| return; |
| } |
| if (frame->metadata().hw_protected && !frame->metadata().protected_video) { |
| // According to the VideoFrameMetadata documentation, |hw_protected| is only |
| // valid if |protected_video| is set to true. |
| VLOGF(2) << "Unexpectedly received a frame with hw_protected = true but " |
| "protected_video = false"; |
| Stop(); |
| return; |
| } |
| |
| // VideoFrameMetadata has many fields and we don't validate all of them. |
| // Fortunately, we also don't need all the fields. |metadata_to_propagate| |
| // will be explicitly initialized with the fields that: |
| // |
| // 1) We need, |
| // |
| // AND |
| // |
| // 2) We've validated above or know that not validating won't have security |
| // implications. |
| // |
| // The rest of the fields are left as default. |
| VideoFrameMetadata metadata_to_propagate; |
| metadata_to_propagate.allow_overlay = true; |
| metadata_to_propagate.end_of_stream = false; |
| metadata_to_propagate.read_lock_fences_enabled = true; |
| metadata_to_propagate.protected_video = frame->metadata().protected_video; |
| metadata_to_propagate.hw_protected = frame->metadata().hw_protected; |
| metadata_to_propagate.needs_detiling = frame->metadata().needs_detiling; |
| metadata_to_propagate.power_efficient = true; |
| |
| if (!release_token.has_value()) { |
| VLOGF(2) << "Did not receive a valid release token"; |
| Stop(); |
| return; |
| } |
| |
| if (frame->storage_type() != VideoFrame::STORAGE_DMABUFS) { |
| VLOGF(2) << "Received a frame with an unexpected storage type"; |
| Stop(); |
| return; |
| } |
| |
| // The mojo traits guarantee this. |
| CHECK(gfx::Rect(frame->coded_size()).Contains(frame->visible_rect())); |
| |
| const size_t num_fds = frame->NumDmabufFds(); |
| if (0 == num_fds) { |
| VLOGF(2) << "Received a frame with zero DMA buffer FD's"; |
| Stop(); |
| return; |
| } |
| |
| std::vector<base::ScopedFD> duped_fds; |
| duped_fds.reserve(num_fds); |
| for (size_t i = 0; i < num_fds; ++i) { |
| if (frame->GetDmabufFd(i) < 0) { |
| VLOGF(2) << "Received at least one invalid FD"; |
| Stop(); |
| return; |
| } |
| duped_fds.emplace_back(HANDLE_EINTR(dup(frame->GetDmabufFd(i)))); |
| if (!duped_fds.back().is_valid()) { |
| VLOGF(2) << "Failed to dup() an FD"; |
| Stop(); |
| return; |
| } |
| } |
| |
| const base::TimeDelta fake_timestamp = frame->timestamp(); |
| auto it = fake_timestamp_to_real_timestamp_cache_.Get(fake_timestamp); |
| if (it == fake_timestamp_to_real_timestamp_cache_.end()) { |
| // The remote decoder is misbehaving. |
| VLOGF(2) << "Received an unexpected decoded frame"; |
| Stop(); |
| return; |
| } |
| const base::TimeDelta real_timestamp = it->second; |
| |
| if (!frame->metadata().tracking_token.has_value() || |
| frame->metadata().tracking_token->is_empty()) { |
| VLOGF(2) << "Received a frame with a missing or invalid tracking token"; |
| Stop(); |
| return; |
| } |
| |
| // Validate protected content metadata. |
| if (!initialized_for_protected_content_ && |
| (metadata_to_propagate.protected_video || |
| metadata_to_propagate.hw_protected)) { |
| VLOGF(2) << "Received a frame with unexpected metadata from a decoder that " |
| "was not configured for protected content"; |
| Stop(); |
| return; |
| } |
| if (initialized_for_protected_content_ && |
| (!metadata_to_propagate.protected_video || |
| !metadata_to_propagate.hw_protected)) { |
| VLOGF(2) << "Received a frame with unexpected metadata from a decoder that " |
| "was configured for protected content"; |
| Stop(); |
| return; |
| } |
| |
| // What follows is all the logic necessary to recycle buffers safely. |
| // |
| // Note that the way we track buffers is with the frame's tracking token. In |
| // theory, this should uniquely identify allocated buffers. In practice, we |
| // can't trust what comes from the remote decoder. A malicious decoder could |
| // send us two frames that have the same tracking token, but actually refer to |
| // different dma-bufs. The solution to this is that we assume that the remote |
| // decoder is telling the truth in a sense: if we receive an incoming buffer |
| // with a tracking_token that we already know about (by looking it up in |
| // |received_token_to_decoded_frame_map_|), we will re-use the buffer that we |
| // know about and ignore the incoming one. The goal with the rest of the logic |
| // below is that if this assumption is violated, the worst case is a visually |
| // incorrect output but not a security problem. |
| // |
| // When something like a resolution change happens, we assume that the remote |
| // decoder recreated its pool of buffers. Therefore, in those cases we can |
| // forget about all known frames since we shouldn't see those buffers again. |
| // In order to detect those cases, we replicate the logic from |
| // PlatformVideoFramePool::IsSameFormat_Locked(). |
| const VideoPixelFormat format = frame->format(); |
| const gfx::Size coded_size = frame->coded_size(); |
| const gfx::Rect visible_rect = frame->visible_rect(); |
| const gfx::Size natural_size = frame->natural_size(); |
| const gfx::ColorSpace color_space = frame->ColorSpace(); |
| const std::optional<gfx::HDRMetadata> hdr_metadata = frame->hdr_metadata(); |
| const VideoFrameMetadata metadata = metadata_to_propagate; |
| const base::UnguessableToken received_tracking_token = |
| *frame->metadata().tracking_token; |
| if (!received_token_to_decoded_frame_map_.empty()) { |
| // It doesn't matter which frame we pick to calculate the current state. All |
| // of them should yield the same result. |
| const VideoPixelFormat current_format = |
| received_token_to_decoded_frame_map_.cbegin()->second->format(); |
| const gfx::Size& current_coded_size = |
| received_token_to_decoded_frame_map_.cbegin()->second->coded_size(); |
| const gfx::Size& current_visible_rect_size_from_origin = |
| GetRectSizeFromOrigin(received_token_to_decoded_frame_map_.cbegin() |
| ->second->visible_rect()); |
| const bool currently_uses_protected = |
| received_token_to_decoded_frame_map_.cbegin() |
| ->second->metadata() |
| .hw_protected; |
| |
| if (format != current_format || coded_size != current_coded_size || |
| GetRectSizeFromOrigin(visible_rect) != |
| current_visible_rect_size_from_origin || |
| metadata.hw_protected != currently_uses_protected) { |
| received_token_to_decoded_frame_map_.clear(); |
| generated_token_to_decoded_frame_map_.clear(); |
| } |
| } |
| |
| scoped_refptr<FrameResource> frame_to_wrap; |
| auto decoded_frame_it = |
| received_token_to_decoded_frame_map_.find(received_tracking_token); |
| if (decoded_frame_it != received_token_to_decoded_frame_map_.end()) { |
| frame_to_wrap = decoded_frame_it->second; |
| CHECK_EQ(frame_to_wrap->format(), format); |
| CHECK_EQ(frame_to_wrap->coded_size(), coded_size); |
| CHECK_EQ(GetRectSizeFromOrigin(frame_to_wrap->visible_rect()), |
| GetRectSizeFromOrigin(visible_rect)); |
| CHECK_EQ(frame_to_wrap->metadata().hw_protected, metadata.hw_protected); |
| } else { |
| scoped_refptr<FrameResource> native_pixmap_frame = |
| CreateDecodedFrameResource(frame->layout(), visible_rect, natural_size, |
| std::move(duped_fds), fake_timestamp, |
| metadata, color_space, hdr_metadata); |
| if (!native_pixmap_frame) { |
| Stop(); |
| return; |
| } |
| received_token_to_decoded_frame_map_[received_tracking_token] = |
| native_pixmap_frame; |
| generated_token_to_decoded_frame_map_[native_pixmap_frame |
| ->tracking_token()] = |
| native_pixmap_frame.get(); |
| frame_to_wrap = std::move(native_pixmap_frame); |
| } |
| |
| // If |frame_to_wrap| was cached in |received_token_to_decoded_frame_map_|, |
| // then there is a possibility that |visible_rect| and |natural_size|, which |
| // are computed from |frame| are different than in |frame_to_wrap| and |
| // |frame|. Because of this, CreateWrappingFrame() is called with |
| // |visible_rect| and |natural_size|. |
| scoped_refptr<FrameResource> wrapped_frame = |
| frame_to_wrap->CreateWrappingFrame(visible_rect, natural_size); |
| if (!wrapped_frame) { |
| VLOGF(2) << "Could not wrap the frame"; |
| Stop(); |
| return; |
| } |
| |
| wrapped_frame->set_timestamp(real_timestamp); |
| wrapped_frame->set_color_space(color_space); |
| wrapped_frame->set_hdr_metadata(hdr_metadata); |
| wrapped_frame->set_metadata(metadata); |
| |
| // The destruction observer will be called after the client releases the |
| // video frame. base::BindPostTaskToCurrentDefault() is used to make sure that |
| // the WeakPtr is dereferenced on the correct sequence. |
| wrapped_frame->AddDestructionObserver(base::BindPostTaskToCurrentDefault( |
| base::BindOnce(&OOPVideoDecoder::ReleaseVideoFrame, |
| weak_this_factory_.GetWeakPtr(), *release_token))); |
| |
| can_read_without_stalling_ = can_read_without_stalling; |
| |
| if (output_cb_) { |
| output_cb_.Run(std::move(wrapped_frame)); |
| } |
| } |
| |
| void OOPVideoDecoder::OnWaiting(WaitingReason reason) { |
| DVLOGF(4); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!has_error_); |
| |
| // It's not expected that we'll ever use WaitingReason::kNoCdm for anything |
| // legitimate in ChromeOS, so if we receive that for any reason, the remote |
| // decoder is misbehaving. |
| if (reason == WaitingReason::kNoCdm) { |
| VLOGF(2) << "Received an unexpected WaitingReason"; |
| Stop(); |
| return; |
| } |
| |
| if (waiting_cb_) |
| waiting_cb_.Run(reason); |
| } |
| |
| void OOPVideoDecoder::RequestOverlayInfo(bool restart_for_transitions) { |
| DVLOGF(4); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void OOPVideoDecoder::AddLogRecord(const MediaLogRecord& event) { |
| VLOGF(2); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // TODO(b/220915557): we should validate |event| before using it since we |
| // can't trust anything coming from the remote decoder. |
| // if (media_log_) |
| // media_log_->AddLogRecord(std::make_unique<media::MediaLogRecord>(event)); |
| } |
| |
| FrameResource* OOPVideoDecoder::GetOriginalFrame( |
| const base::UnguessableToken& tracking_token) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| CHECK(!tracking_token.is_empty()); |
| auto it = generated_token_to_decoded_frame_map_.find(tracking_token); |
| return (it == generated_token_to_decoded_frame_map_.end()) ? nullptr |
| : it->second; |
| } |
| |
| } // namespace media |