| // Copyright 2016 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/mojo/services/mojo_cdm_allocator.h" |
| |
| #include <limits> |
| #include <memory> |
| |
| #include "base/compiler_specific.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/read_only_shared_memory_region.h" |
| #include "base/memory/shared_memory_mapping.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/numerics/safe_math.h" |
| #include "media/base/video_frame.h" |
| #include "media/cdm/api/content_decryption_module.h" |
| #include "media/cdm/cdm_helpers.h" |
| #include "media/cdm/cdm_type_conversion.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| using RecycleRegionCB = |
| base::OnceCallback<void(std::unique_ptr<base::MappedReadOnlyRegion>)>; |
| |
| // cdm::Buffer implementation that provides access to mojo shared memory. |
| // It owns the memory until Destroy() is called. |
| class MojoCdmBuffer final : public cdm::Buffer { |
| public: |
| MojoCdmBuffer(std::unique_ptr<base::MappedReadOnlyRegion> mapped_region, |
| RecycleRegionCB recycle_region_cb) |
| : mapped_region_(std::move(mapped_region)), |
| recycle_region_cb_(std::move(recycle_region_cb)) { |
| DCHECK(mapped_region_); |
| DCHECK(mapped_region_->IsValid()); |
| DCHECK(recycle_region_cb_); |
| // cdm::Buffer interface limits capacity to uint32. |
| CHECK_LE(mapped_region_->region.GetSize(), |
| std::numeric_limits<uint32_t>::max()); |
| } |
| |
| MojoCdmBuffer(const MojoCdmBuffer&) = delete; |
| MojoCdmBuffer& operator=(const MojoCdmBuffer&) = delete; |
| |
| // cdm::Buffer implementation. |
| void Destroy() final { |
| // If nobody has claimed the handle, then return it. |
| if (mapped_region_ && mapped_region_->IsValid()) { |
| std::move(recycle_region_cb_).Run(std::move(mapped_region_)); |
| } |
| |
| // No need to exist anymore. |
| delete this; |
| } |
| |
| uint32_t Capacity() const final { return mapped_region_->mapping.size(); } |
| |
| uint8_t* Data() final { |
| return mapped_region_->mapping.GetMemoryAs<uint8_t>(); |
| } |
| |
| void SetSize(uint32_t size) final { |
| DCHECK_LE(size, Capacity()); |
| size_ = size > Capacity() ? 0 : size; |
| } |
| |
| uint32_t Size() const final { return size_; } |
| |
| const base::MappedReadOnlyRegion& Region() const { return *mapped_region_; } |
| std::unique_ptr<base::MappedReadOnlyRegion> TakeRegion() { |
| return std::move(mapped_region_); |
| } |
| |
| private: |
| ~MojoCdmBuffer() final { |
| // Verify that the buffer has been returned so it can be reused. |
| DCHECK(!mapped_region_); |
| } |
| |
| std::unique_ptr<base::MappedReadOnlyRegion> mapped_region_; |
| RecycleRegionCB recycle_region_cb_; |
| uint32_t size_ = 0; |
| }; |
| |
| // VideoFrameImpl that is able to create a STORAGE_SHMEM VideoFrame |
| // out of the data. |
| class MojoCdmVideoFrame final : public VideoFrameImpl { |
| public: |
| explicit MojoCdmVideoFrame(RecycleRegionCB recycle_region_cb) |
| : recycle_region_cb_(std::move(recycle_region_cb)) {} |
| |
| MojoCdmVideoFrame(const MojoCdmVideoFrame&) = delete; |
| MojoCdmVideoFrame& operator=(const MojoCdmVideoFrame&) = delete; |
| |
| ~MojoCdmVideoFrame() final = default; |
| |
| // VideoFrameImpl implementation. |
| scoped_refptr<media::VideoFrame> TransformToVideoFrame( |
| gfx::Size natural_size) final { |
| DCHECK(FrameBuffer()); |
| |
| MojoCdmBuffer* buffer = static_cast<MojoCdmBuffer*>(FrameBuffer()); |
| const gfx::Size frame_size(Size().width, Size().height); |
| |
| std::unique_ptr<base::MappedReadOnlyRegion> mapped_region = |
| buffer->TakeRegion(); |
| DCHECK(mapped_region); |
| DCHECK(mapped_region->region.IsValid()); |
| |
| // Clear FrameBuffer so that MojoCdmVideoFrame no longer has a reference |
| // to it (memory will be transferred to media::VideoFrame). |
| SetFrameBuffer(nullptr); |
| |
| // Destroy the MojoCdmBuffer as it is no longer needed. |
| buffer->Destroy(); |
| |
| uint8_t* data = |
| const_cast<uint8_t*>(mapped_region->mapping.GetMemoryAs<uint8_t>()); |
| if (PlaneOffset(cdm::kYPlane) != 0u) { |
| LOG(ERROR) << "The first buffer offset is not 0"; |
| return nullptr; |
| } |
| auto frame = media::VideoFrame::WrapExternalYuvData( |
| ToMediaVideoFormat(Format()), frame_size, gfx::Rect(frame_size), |
| natural_size, static_cast<int32_t>(Stride(cdm::kYPlane)), |
| static_cast<int32_t>(Stride(cdm::kUPlane)), |
| static_cast<int32_t>(Stride(cdm::kVPlane)), |
| data + PlaneOffset(cdm::kYPlane), data + PlaneOffset(cdm::kUPlane), |
| data + PlaneOffset(cdm::kVPlane), base::Microseconds(Timestamp())); |
| |
| // |frame| could fail to be created if the memory can't be mapped into |
| // this address space. |
| if (frame) { |
| frame->set_color_space(MediaColorSpace().ToGfxColorSpace()); |
| frame->BackWithSharedMemory(&mapped_region->region); |
| frame->AddDestructionObserver(base::BindOnce( |
| std::move(recycle_region_cb_), std::move(mapped_region))); |
| } |
| return frame; |
| } |
| |
| private: |
| RecycleRegionCB recycle_region_cb_; |
| }; |
| |
| } // namespace |
| |
| MojoCdmAllocator::MojoCdmAllocator() {} |
| |
| MojoCdmAllocator::~MojoCdmAllocator() = default; |
| |
| // Creates a cdm::Buffer, reusing an existing buffer if one is available. |
| // If not, a new buffer is created using AllocateNewRegion(). The caller is |
| // responsible for calling Destroy() on the buffer when it is no longer needed. |
| cdm::Buffer* MojoCdmAllocator::CreateCdmBuffer(size_t capacity) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!capacity) |
| return nullptr; |
| |
| // Reuse a shmem region in the free map if there is one that fits |capacity|. |
| // Otherwise, create a new one. |
| std::unique_ptr<base::MappedReadOnlyRegion> mapped_region; |
| auto found = available_regions_.lower_bound(capacity); |
| if (found == available_regions_.end()) { |
| mapped_region = AllocateNewRegion(capacity); |
| if (!mapped_region->IsValid()) { |
| return nullptr; |
| } |
| } else { |
| mapped_region = std::move(found->second); |
| available_regions_.erase(found); |
| } |
| |
| // Ownership of `region` is passed to MojoCdmBuffer. When it is done with the |
| // memory, it must call `AddRegionrToAvailableMap()` to make the memory |
| // available for another MojoCdmBuffer. |
| return new MojoCdmBuffer( |
| std::move(mapped_region), |
| base::BindOnce(&MojoCdmAllocator::AddRegionToAvailableMap, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // Creates a new MojoCdmVideoFrame on every request. |
| std::unique_ptr<VideoFrameImpl> MojoCdmAllocator::CreateCdmVideoFrame() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return std::make_unique<MojoCdmVideoFrame>( |
| base::BindOnce(&MojoCdmAllocator::AddRegionToAvailableMap, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| std::unique_ptr<base::MappedReadOnlyRegion> MojoCdmAllocator::AllocateNewRegion( |
| size_t capacity) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Always pad new allocated buffer so that we don't need to reallocate |
| // buffers frequently if requested sizes fluctuate slightly. |
| static const size_t kBufferPadding = 512; |
| |
| // Maximum number of free buffers we can keep when allocating new buffers. |
| static const size_t kFreeLimit = 3; |
| |
| // Destroy the smallest buffer before allocating a new bigger buffer if the |
| // number of free buffers exceeds a limit. This mechanism helps avoid ending |
| // up with too many small buffers, which could happen if the size to be |
| // allocated keeps increasing. |
| if (available_regions_.size() >= kFreeLimit) |
| available_regions_.erase(available_regions_.begin()); |
| |
| // Creation of shared memory may be expensive if it involves synchronous IPC |
| // calls. That's why we try to avoid AllocateNewRegion() as much as we can. |
| base::CheckedNumeric<size_t> requested_capacity(capacity); |
| requested_capacity += kBufferPadding; |
| return std::make_unique<base::MappedReadOnlyRegion>( |
| base::ReadOnlySharedMemoryRegion::Create( |
| requested_capacity.ValueOrDie())); |
| } |
| |
| void MojoCdmAllocator::AddRegionToAvailableMap( |
| std::unique_ptr<base::MappedReadOnlyRegion> mapped_region) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(mapped_region); |
| size_t capacity = mapped_region->region.GetSize(); |
| available_regions_.insert({capacity, std::move(mapped_region)}); |
| } |
| |
| const base::MappedReadOnlyRegion& MojoCdmAllocator::GetRegionForTesting( |
| cdm::Buffer* buffer) const { |
| MojoCdmBuffer* mojo_buffer = static_cast<MojoCdmBuffer*>(buffer); |
| return mojo_buffer->Region(); |
| } |
| |
| size_t MojoCdmAllocator::GetAvailableRegionCountForTesting() { |
| return available_regions_.size(); |
| } |
| |
| } // namespace media |