blob: c43099bb48da16ee2766a2a0c0b47927c4e5932b [file] [log] [blame]
// 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/gpu/chromeos/platform_video_frame_pool.h"
#include <utility>
#include "base/logging.h"
#include "base/task/post_task.h"
#include "media/base/video_util.h"
#include "media/gpu/chromeos/gpu_buffer_layout.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/macros.h"
#include "media/media_buildflags.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace media {
namespace {
// The default method to create frames.
CroStatus::Or<scoped_refptr<VideoFrame>> DefaultCreateFrame(
gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory,
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
bool use_protected,
base::TimeDelta timestamp) {
scoped_refptr<VideoFrame> frame = CreateGpuMemoryBufferVideoFrame(
gpu_memory_buffer_factory, format, coded_size, visible_rect, natural_size,
timestamp,
use_protected ? gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE
: gfx::BufferUsage::SCANOUT_VDA_WRITE);
if (!frame)
return CroStatus::Codes::kFailedToCreateVideoFrame;
if (use_protected) {
media::VideoFrameMetadata frame_metadata;
frame_metadata.protected_video = true;
frame_metadata.hw_protected = true;
frame->set_metadata(frame_metadata);
}
return frame;
}
} // namespace
PlatformVideoFramePool::PlatformVideoFramePool(
gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory)
: create_frame_cb_(base::BindRepeating(&DefaultCreateFrame)),
gpu_memory_buffer_factory_(gpu_memory_buffer_factory) {
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();
}
// static
gfx::GpuMemoryBufferId PlatformVideoFramePool::GetGpuMemoryBufferId(
const VideoFrame& frame) {
DCHECK_EQ(frame.storage_type(),
VideoFrame::StorageType::STORAGE_GPU_MEMORY_BUFFER);
DCHECK(frame.GetGpuMemoryBuffer());
return frame.GetGpuMemoryBuffer()->GetId();
}
scoped_refptr<VideoFrame> 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|.
CroStatus::Or<scoped_refptr<VideoFrame>> new_frame =
create_frame_cb_.Run(gpu_memory_buffer_factory_, format, coded_size,
gfx::Rect(GetRectSizeFromOrigin(visible_rect_)),
coded_size, use_protected_, base::TimeDelta());
if (new_frame.has_error()) {
// TODO(crbug.com/c/1103510) Push the error up instead of dropping it.
return nullptr;
}
InsertFreeFrame_Locked(std::move(new_frame).value());
}
DCHECK(!free_frames_.empty());
scoped_refptr<VideoFrame> 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<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame(
origin_frame, format, visible_rect_, natural_size_);
DCHECK(wrapped_frame);
frames_in_use_.emplace(GetGpuMemoryBufferId(*wrapped_frame),
origin_frame.get());
wrapped_frame->AddDestructionObserver(
base::BindOnce(&PlatformVideoFramePool::OnFrameReleasedThunk, weak_this_,
parent_task_runner_, std::move(origin_frame)));
// Clear all metadata before returning to client, in case origin frame has any
// unrelated metadata.
wrapped_frame->clear_metadata();
// We need to put this metadata in the wrapped frame if we are in protected
// mode.
if (use_protected_) {
media::VideoFrameMetadata frame_metadata;
frame_metadata.protected_video = true;
frame_metadata.hw_protected = true;
wrapped_frame->set_metadata(frame_metadata);
}
return wrapped_frame;
}
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) {
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
// 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.
if (!IsSameFormat_Locked(format, coded_size, visible_rect, use_protected)) {
DVLOGF(4) << "The video frame format is changed. Clearing the pool.";
free_frames_.clear();
auto maybe_frame = create_frame_cb_.Run(
gpu_memory_buffer_factory_, format, coded_size, visible_rect,
natural_size, use_protected, base::TimeDelta());
if (maybe_frame.has_error())
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;
// 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) {
base::AutoLock auto_lock(lock_);
create_frame_cb_ = allocator;
}
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_;
}
VideoFrame* PlatformVideoFramePool::UnwrapFrame(
const VideoFrame& wrapped_frame) {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
auto it = frames_in_use_.find(GetGpuMemoryBufferId(wrapped_frame));
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();
weak_this_factory_.InvalidateWeakPtrs();
weak_this_ = weak_this_factory_.GetWeakPtr();
}
// static
void PlatformVideoFramePool::OnFrameReleasedThunk(
absl::optional<base::WeakPtr<PlatformVideoFramePool>> pool,
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<VideoFrame> origin_frame) {
DCHECK(pool);
DVLOGF(4);
task_runner->PostTask(
FROM_HERE, base::BindOnce(&PlatformVideoFramePool::OnFrameReleased, *pool,
std::move(origin_frame)));
}
void PlatformVideoFramePool::OnFrameReleased(
scoped_refptr<VideoFrame> origin_frame) {
DCHECK(parent_task_runner_->RunsTasksInCurrentSequence());
DVLOGF(4);
base::AutoLock auto_lock(lock_);
gfx::GpuMemoryBufferId frame_id = GetGpuMemoryBufferId(*origin_frame);
auto it = frames_in_use_.find(frame_id);
DCHECK(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));
}
if (frame_available_cb_ && !IsExhausted_Locked())
std::move(frame_available_cb_).Run();
}
void PlatformVideoFramePool::InsertFreeFrame_Locked(
scoped_refptr<VideoFrame> 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