blob: eebdcb4d5b8d7bec5f36af937b8927d1b864eb43 [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/optional.h"
#include "base/task/post_task.h"
#include "media/gpu/chromeos/gpu_buffer_layout.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/macros.h"
namespace media {
namespace {
// The default method to create frames.
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,
base::TimeDelta timestamp) {
return CreateGpuMemoryBufferVideoFrame(
gpu_memory_buffer_factory, format, coded_size, visible_rect, natural_size,
timestamp, gfx::BufferUsage::SCANOUT_VDA_WRITE);
}
} // 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;
// VideoFrame::WrapVideoFrame() will check whether the updated visible_rect
// is sub rect of the original visible_rect. Therefore we set visible_rect
// as large as coded_size to guarantee this condition.
scoped_refptr<VideoFrame> new_frame = create_frame_cb_.Run(
gpu_memory_buffer_factory_, format, coded_size, gfx::Rect(coded_size),
coded_size, base::TimeDelta());
if (!new_frame)
return nullptr;
InsertFreeFrame_Locked(std::move(new_frame));
}
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();
return wrapped_frame;
}
base::Optional<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) {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
visible_rect_ = visible_rect;
natural_size_ = natural_size;
max_num_frames_ = max_num_frames;
// 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 base::nullopt;
}
// If the frame layout changed we need to allocate new frames so we will clear
// the pool here. If only the visible or natural size changed we don't need to
// allocate new frames, but will just update the properties of wrapped frames
// returned by GetFrame().
// NOTE: It is assumed layout is determined by |format| and |coded_size|.
if (!IsSameFormat_Locked(format, coded_size)) {
DVLOGF(4) << "The video frame format is changed. Clearing the pool.";
free_frames_.clear();
// Create a temporary frame in order to know VideoFrameLayout that
// VideoFrame that will be allocated in GetFrame() has.
auto frame =
create_frame_cb_.Run(gpu_memory_buffer_factory_, format, coded_size,
visible_rect_, natural_size_, base::TimeDelta());
if (!frame) {
VLOGF(1) << "Failed to create video frame " << format << " (fourcc "
<< fourcc.ToString() << ")";
return base::nullopt;
}
frame_layout_ = GpuBufferLayout::Create(fourcc, frame->coded_size(),
frame->layout().planes());
}
// 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_;
}
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);
}
// static
void PlatformVideoFramePool::OnFrameReleasedThunk(
base::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()))
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 {
DVLOGF(4);
lock_.AssertAcquired();
return frame_layout_ &&
frame_layout_->fourcc().ToVideoPixelFormat() == format &&
frame_layout_->size() == coded_size;
}
size_t PlatformVideoFramePool::GetPoolSizeForTesting() {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
return free_frames_.size();
}
} // namespace media