|  | // Copyright 2019 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/platform_video_frame_pool.h" | 
|  |  | 
|  | #include <optional> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/logging.h" | 
|  | #include "base/not_fatal_until.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "build/build_config.h" | 
|  | #include "media/base/media_switches.h" | 
|  | #include "media/base/video_util.h" | 
|  | #include "media/gpu/chromeos/gpu_buffer_layout.h" | 
|  | #include "media/gpu/chromeos/native_pixmap_frame_resource.h" | 
|  | #include "media/gpu/chromeos/platform_video_frame_utils.h" | 
|  | #include "media/gpu/macros.h" | 
|  | #include "media/media_buildflags.h" | 
|  |  | 
|  | namespace media { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // This needs to be synchronized with the frame type from DefaultCreateFrame(). | 
|  | // There is a runtime CHECK() to validate this. | 
|  | constexpr VideoFrame::StorageType kDefaultFrameStorageType = | 
|  | VideoFrame::STORAGE_DMABUFS; | 
|  |  | 
|  | // The default method to create frames. | 
|  | CroStatus::Or<scoped_refptr<FrameResource>> DefaultCreateFrame( | 
|  | VideoPixelFormat format, | 
|  | const gfx::Size& coded_size, | 
|  | const gfx::Rect& visible_rect, | 
|  | const gfx::Size& natural_size, | 
|  | bool use_protected, | 
|  | bool use_linear_buffers, | 
|  | bool needs_detiling, | 
|  | base::TimeDelta timestamp) { | 
|  | if (use_protected && use_linear_buffers) { | 
|  | VLOGF(1) << "Linear buffers are unsupported when |use_protected| is true."; | 
|  | return CroStatus::Codes::kFailedToCreateVideoFrame; | 
|  | } | 
|  |  | 
|  | scoped_refptr<FrameResource> frame = NativePixmapFrameResource::Create( | 
|  | format, coded_size, visible_rect, natural_size, timestamp, | 
|  | use_protected | 
|  | ? gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE | 
|  | : (use_linear_buffers ? gfx::BufferUsage::SCANOUT_CPU_READ_WRITE | 
|  | : gfx::BufferUsage::SCANOUT_VDA_WRITE)); | 
|  |  | 
|  | if (!frame) | 
|  | return CroStatus::Codes::kFailedToCreateVideoFrame; | 
|  |  | 
|  | // A SCANOUT usage was requested for the allocated |frame|, so there's a | 
|  | // possibility that it can be promoted to overlay, mark it so. | 
|  | frame->metadata().allow_overlay = true; | 
|  | frame->metadata().protected_video = use_protected; | 
|  | frame->metadata().hw_protected = use_protected; | 
|  |  | 
|  | #if defined(ARCH_CPU_ARM_FAMILY) | 
|  | if (base::FeatureList::IsEnabled(media::kEnableProtectedVulkanDetiling)) { | 
|  | frame->metadata().needs_detiling = needs_detiling; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | return frame; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | PlatformVideoFramePool::PlatformVideoFramePool() | 
|  | : create_frame_cb_(base::BindRepeating(&DefaultCreateFrame)), | 
|  | frame_storage_type_(kDefaultFrameStorageType) { | 
|  | DVLOGF(4); | 
|  | weak_this_ = weak_this_factory_.GetWeakPtr(); | 
|  | } | 
|  |  | 
|  | PlatformVideoFramePool::~PlatformVideoFramePool() { | 
|  | if (parent_task_runner_) | 
|  | DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | frames_in_use_.clear(); | 
|  | free_frames_.clear(); | 
|  | weak_this_factory_.InvalidateWeakPtrs(); | 
|  | } | 
|  |  | 
|  | scoped_refptr<FrameResource> PlatformVideoFramePool::GetFrame() { | 
|  | DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | if (!frame_layout_) { | 
|  | VLOGF(1) << "Please call Initialize() first."; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | const VideoPixelFormat format = frame_layout_->fourcc().ToVideoPixelFormat(); | 
|  | const gfx::Size& coded_size = frame_layout_->size(); | 
|  | if (free_frames_.empty()) { | 
|  | if (GetTotalNumFrames_Locked() >= max_num_frames_) | 
|  | return nullptr; | 
|  |  | 
|  | // We want to be able to re-use |new_frame| if possible even when the pool | 
|  | // is re-initialized with a different |visible_rect_|. DRM framebuffers can | 
|  | // be re-used if the visible-rect-from-the-origin (a.k.a "usable area") is | 
|  | // the same. For example, say we have a |visible_rect_| of (10, 20), 640x360 | 
|  | // (DRM framebuffer of size 650x380); if we re-initialize the pool with | 
|  | // (5, 2), 645x378, we can re-use the resources, but if we re-initialize | 
|  | // with (10, 20), 100x100 we cannot (even though it's contained in the | 
|  | // former). Hence the use of GetRectSizeFromOrigin() to calculate the | 
|  | // visible rect for |new_frame|. | 
|  | // | 
|  | // TODO(b/230370976): after https://crrev.com/c/3597211, | 
|  | // PlatformVideoFramePool doesn't create DRM framebuffers for a dma-buf at | 
|  | // allocation time (instead, they will be created at the moment of creating | 
|  | // a SharedImage). That means that we probably don't need to take the | 
|  | // |visible_rect| into account for IsSameFormat_Locked() any more which | 
|  | // implies that we can create |new_frame| using gfx::Rect(coded_size) as | 
|  | // the visible rectangle. | 
|  | CHECK(use_linear_buffers_.has_value()); | 
|  | CroStatus::Or<scoped_refptr<FrameResource>> new_frame = | 
|  | create_frame_cb_.Run( | 
|  | format, coded_size, gfx::Rect(GetRectSizeFromOrigin(visible_rect_)), | 
|  | coded_size, use_protected_, *use_linear_buffers_, | 
|  | frame_layout_->fourcc() == Fourcc(Fourcc::MM21) || | 
|  | frame_layout_->fourcc() == Fourcc(Fourcc::MT2T), | 
|  | base::TimeDelta()); | 
|  | if (!new_frame.has_value()) { | 
|  | // TODO(crbug.com/c/1103510) Push the error up instead of dropping it. | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | CHECK(*new_frame); | 
|  | // This passes because |frame_storage_type_| is set to match the StorageType | 
|  | // of frames produced by |create_frame_cb_|. When |create_frame_cb_| is set | 
|  | // to DefaultCreateFrame(), then |frame_storage_type_| is set to | 
|  | // |kDefaultFrameStorageType|, which is hardcoded to match the storage type | 
|  | // used by DefaultCreateFrame(). When |create_frame_cb_| has been set by | 
|  | // SetCustomAllocator(), then |frame_storage_type_| is expected to be | 
|  | // correctly // set by the caller. | 
|  | CHECK_EQ((*new_frame)->storage_type(), frame_storage_type_); | 
|  |  | 
|  | // Sets and/or registers the frame's |tracking_token|. | 
|  | frame_tracking_token_helper_.SetUniqueTrackingToken(new_frame->metadata()); | 
|  |  | 
|  | InsertFreeFrame_Locked(std::move(new_frame).value()); | 
|  | } | 
|  |  | 
|  | DCHECK(!free_frames_.empty()); | 
|  | scoped_refptr<FrameResource> origin_frame = std::move(free_frames_.back()); | 
|  | free_frames_.pop_back(); | 
|  | DCHECK_EQ(origin_frame->format(), format); | 
|  | DCHECK_EQ(origin_frame->coded_size(), coded_size); | 
|  |  | 
|  | scoped_refptr<FrameResource> wrapped_frame = | 
|  | origin_frame->CreateWrappingFrame(visible_rect_, natural_size_); | 
|  | DCHECK(wrapped_frame); | 
|  | frames_in_use_.emplace(wrapped_frame->tracking_token(), origin_frame.get()); | 
|  | wrapped_frame->AddDestructionObserver( | 
|  | base::BindOnce(&PlatformVideoFramePool::OnFrameReleasedThunk, weak_this_, | 
|  | parent_task_runner_, std::move(origin_frame))); | 
|  |  | 
|  | DCHECK_EQ(wrapped_frame->metadata().protected_video, use_protected_); | 
|  | DCHECK_EQ(wrapped_frame->metadata().hw_protected, use_protected_); | 
|  |  | 
|  | return wrapped_frame; | 
|  | } | 
|  |  | 
|  | VideoFrame::StorageType PlatformVideoFramePool::GetFrameStorageType() const { | 
|  | base::AutoLock auto_lock(lock_); | 
|  | return frame_storage_type_; | 
|  | } | 
|  |  | 
|  | PlatformVideoFramePool* PlatformVideoFramePool::AsPlatformVideoFramePool() { | 
|  | return this; | 
|  | } | 
|  |  | 
|  | CroStatus::Or<GpuBufferLayout> PlatformVideoFramePool::Initialize( | 
|  | const Fourcc& fourcc, | 
|  | const gfx::Size& coded_size, | 
|  | const gfx::Rect& visible_rect, | 
|  | const gfx::Size& natural_size, | 
|  | size_t max_num_frames, | 
|  | bool use_protected, | 
|  | bool use_linear_buffers) { | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | // Only support the Fourcc that could map to VideoPixelFormat. | 
|  | VideoPixelFormat format = fourcc.ToVideoPixelFormat(); | 
|  | if (format == PIXEL_FORMAT_UNKNOWN) { | 
|  | VLOGF(1) << "Unsupported fourcc: " << fourcc.ToString(); | 
|  | return CroStatus::Codes::kFourccUnknownFormat; | 
|  | } | 
|  |  | 
|  | #if !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA) | 
|  | if (use_protected) { | 
|  | VLOGF(1) << "Protected buffers unsupported"; | 
|  | return CroStatus::Codes::kProtectedContentUnsupported; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | // |use_linear_buffers| does not change unless we are switching in or out of | 
|  | // using protected content. | 
|  | CHECK(use_protected != use_protected_ || !use_linear_buffers_ || | 
|  | *use_linear_buffers_ == use_linear_buffers); | 
|  |  | 
|  | // If the frame layout changed we need to allocate new frames so we will clear | 
|  | // the pool here. If only the visible rect or natural size changed, we don't | 
|  | // need to allocate new frames (unless the change in the visible rect causes a | 
|  | // change in the size of the DRM framebuffer, see note below): we'll just | 
|  | // update the properties of wrapped frames returned by GetFrame(). | 
|  | // | 
|  | // NOTE: It is assumed layout is determined by |format|, |coded_size|, and | 
|  | // possibly |visible_rect|. The reason for the "possibly" is that | 
|  | // |visible_rect| is used to compute the size of the DRM framebuffer for | 
|  | // hardware overlay purposes. The caveat is that different visible rectangles | 
|  | // can map to the same framebuffer size, i.e., all the visible rectangles with | 
|  | // the same bottom-right corner map to the same framebuffer size. | 
|  | // | 
|  | // TODO(b/230370976): after https://crrev.com/c/3597211, | 
|  | // PlatformVideoFramePool doesn't create DRM framebuffers for a dma-buf at | 
|  | // allocation time (instead, they will be created at the moment of creating a | 
|  | // SharedImage). That means that we probably don't need to take the | 
|  | // |visible_rect| into account for IsSameFormat_Locked() any more. | 
|  | if (!IsSameFormat_Locked(format, coded_size, visible_rect, use_protected)) { | 
|  | DVLOGF(4) << "The video frame format is changed. Clearing the pool."; | 
|  | // This loop clears the free frames' tokens from | 
|  | // |frame_tracking_token_helper_|. The in-use frames' tokens will be cleared | 
|  | // as the frames are released. | 
|  | for (const auto& frame : free_frames_) { | 
|  | frame_tracking_token_helper_.ClearToken(frame->tracking_token()); | 
|  | } | 
|  | free_frames_.clear(); | 
|  | auto maybe_frame = create_frame_cb_.Run( | 
|  | format, coded_size, visible_rect, natural_size, use_protected, | 
|  | use_linear_buffers, | 
|  | fourcc == Fourcc(Fourcc::MM21) || fourcc == Fourcc(Fourcc::MT2T), | 
|  | base::TimeDelta()); | 
|  | if (!maybe_frame.has_value()) | 
|  | return std::move(maybe_frame).error(); | 
|  | auto frame = std::move(maybe_frame).value(); | 
|  | frame_layout_ = GpuBufferLayout::Create(fourcc, frame->coded_size(), | 
|  | frame->layout().planes(), | 
|  | frame->layout().modifier()); | 
|  | if (!frame_layout_) | 
|  | return CroStatus::Codes::kFailedToGetFrameLayout; | 
|  | } | 
|  |  | 
|  | DCHECK(frame_layout_); | 
|  |  | 
|  | visible_rect_ = visible_rect; | 
|  | natural_size_ = natural_size; | 
|  | max_num_frames_ = max_num_frames; | 
|  | use_protected_ = use_protected; | 
|  | use_linear_buffers_ = use_linear_buffers; | 
|  |  | 
|  | // The pool might become available because of |max_num_frames_| increased. | 
|  | // Notify the client if so. | 
|  | if (frame_available_cb_ && !IsExhausted_Locked()) | 
|  | std::move(frame_available_cb_).Run(); | 
|  |  | 
|  | return *frame_layout_; | 
|  | } | 
|  |  | 
|  | void PlatformVideoFramePool::SetCustomFrameAllocator( | 
|  | DmabufVideoFramePool::CreateFrameCB allocator, | 
|  | VideoFrame::StorageType frame_storage_type) { | 
|  | base::AutoLock auto_lock(lock_); | 
|  | create_frame_cb_ = allocator; | 
|  | frame_storage_type_ = frame_storage_type; | 
|  | } | 
|  |  | 
|  | bool PlatformVideoFramePool::IsExhausted() { | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | return IsExhausted_Locked(); | 
|  | } | 
|  |  | 
|  | bool PlatformVideoFramePool::IsExhausted_Locked() { | 
|  | DVLOGF(4); | 
|  | lock_.AssertAcquired(); | 
|  |  | 
|  | return free_frames_.empty() && GetTotalNumFrames_Locked() >= max_num_frames_; | 
|  | } | 
|  |  | 
|  | FrameResource* PlatformVideoFramePool::GetOriginalFrame( | 
|  | const base::UnguessableToken& tracking_token) { | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | auto it = frames_in_use_.find(tracking_token); | 
|  | return (it == frames_in_use_.end()) ? nullptr : it->second; | 
|  | } | 
|  |  | 
|  | void PlatformVideoFramePool::NotifyWhenFrameAvailable(base::OnceClosure cb) { | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | if (!IsExhausted_Locked()) { | 
|  | parent_task_runner_->PostTask(FROM_HERE, std::move(cb)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | frame_available_cb_ = std::move(cb); | 
|  | } | 
|  |  | 
|  | void PlatformVideoFramePool::ReleaseAllFrames() { | 
|  | DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  | free_frames_.clear(); | 
|  | frames_in_use_.clear(); | 
|  | frame_tracking_token_helper_.ClearTokens(); | 
|  | weak_this_factory_.InvalidateWeakPtrs(); | 
|  | weak_this_ = weak_this_factory_.GetWeakPtr(); | 
|  | } | 
|  |  | 
|  | std::optional<GpuBufferLayout> PlatformVideoFramePool::GetGpuBufferLayout() { | 
|  | DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); | 
|  | base::AutoLock auto_lock(lock_); | 
|  | return frame_layout_; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void PlatformVideoFramePool::OnFrameReleasedThunk( | 
|  | std::optional<base::WeakPtr<PlatformVideoFramePool>> pool, | 
|  | scoped_refptr<base::SequencedTaskRunner> task_runner, | 
|  | scoped_refptr<FrameResource> origin_frame) { | 
|  | TRACE_EVENT2("media", "PlatformVideoFramePool::OnFrameReleasedThunk", | 
|  | "frame_id", origin_frame->unique_id(), "frame", | 
|  | origin_frame->AsHumanReadableString()); | 
|  | DCHECK(pool); | 
|  | DVLOGF(4); | 
|  |  | 
|  | task_runner->PostTask( | 
|  | FROM_HERE, base::BindOnce(&PlatformVideoFramePool::OnFrameReleased, *pool, | 
|  | std::move(origin_frame))); | 
|  | } | 
|  |  | 
|  | void PlatformVideoFramePool::OnFrameReleased( | 
|  | scoped_refptr<FrameResource> origin_frame) { | 
|  | TRACE_EVENT2("media", "PlatformVideoFramePool::OnFrameReleased", "frame_id", | 
|  | origin_frame->unique_id(), "frame", | 
|  | origin_frame->AsHumanReadableString()); | 
|  | DCHECK(parent_task_runner_->RunsTasksInCurrentSequence()); | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | auto it = frames_in_use_.find(origin_frame->tracking_token()); | 
|  | CHECK(it != frames_in_use_.end()); | 
|  | frames_in_use_.erase(it); | 
|  |  | 
|  | if (IsSameFormat_Locked(origin_frame->format(), origin_frame->coded_size(), | 
|  | origin_frame->visible_rect(), | 
|  | origin_frame->metadata().hw_protected)) { | 
|  | InsertFreeFrame_Locked(std::move(origin_frame)); | 
|  | } else { | 
|  | frame_tracking_token_helper_.ClearToken(origin_frame->tracking_token()); | 
|  | } | 
|  |  | 
|  | if (frame_available_cb_ && !IsExhausted_Locked()) | 
|  | std::move(frame_available_cb_).Run(); | 
|  | } | 
|  |  | 
|  | void PlatformVideoFramePool::InsertFreeFrame_Locked( | 
|  | scoped_refptr<FrameResource> frame) { | 
|  | DCHECK(frame); | 
|  | DVLOGF(4); | 
|  | lock_.AssertAcquired(); | 
|  |  | 
|  | if (GetTotalNumFrames_Locked() < max_num_frames_) | 
|  | free_frames_.push_back(std::move(frame)); | 
|  | } | 
|  |  | 
|  | size_t PlatformVideoFramePool::GetTotalNumFrames_Locked() const { | 
|  | DVLOGF(4); | 
|  | lock_.AssertAcquired(); | 
|  |  | 
|  | return free_frames_.size() + frames_in_use_.size(); | 
|  | } | 
|  |  | 
|  | bool PlatformVideoFramePool::IsSameFormat_Locked(VideoPixelFormat format, | 
|  | const gfx::Size& coded_size, | 
|  | const gfx::Rect& visible_rect, | 
|  | bool use_protected) const { | 
|  | DVLOGF(4); | 
|  | lock_.AssertAcquired(); | 
|  |  | 
|  | return frame_layout_ && | 
|  | frame_layout_->fourcc().ToVideoPixelFormat() == format && | 
|  | frame_layout_->size() == coded_size && | 
|  | GetRectSizeFromOrigin(visible_rect_) == | 
|  | GetRectSizeFromOrigin(visible_rect) && | 
|  | use_protected_ == use_protected; | 
|  | } | 
|  |  | 
|  | size_t PlatformVideoFramePool::GetPoolSizeForTesting() { | 
|  | DVLOGF(4); | 
|  | base::AutoLock auto_lock(lock_); | 
|  |  | 
|  | return free_frames_.size(); | 
|  | } | 
|  |  | 
|  | }  // namespace media |