blob: ddaacfb0a05bedcf7717e92d3abb5483a8068d43 [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/linux/platform_video_frame_pool.h"
#include <utility>
#include "base/logging.h"
#include "base/optional.h"
#include "base/task/post_task.h"
#include "base/time/default_tick_clock.h"
#include "media/gpu/linux/platform_video_frame_utils.h"
#include "media/gpu/macros.h"
namespace media {
namespace {
// The lifespan for stale frames. If a frame is not used for 10 seconds, then
// drop the frame to reduce memory usage.
constexpr base::TimeDelta kStaleFrameLimit = base::TimeDelta::FromSeconds(10);
// The default maximum number of frames.
constexpr size_t kDefaultMaxNumFrames = 64;
// The default method to create frames.
scoped_refptr<VideoFrame> DefaultCreateFrame(VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp) {
return CreatePlatformVideoFrame(format, coded_size, visible_rect,
natural_size, timestamp,
gfx::BufferUsage::SCANOUT_VDA_WRITE);
}
// Get the identifier of |frame|. Calling this method with the frames backed
// by the same Dmabuf should return the same result.
PlatformVideoFramePool::DmabufId GetDmabufId(const VideoFrame& frame) {
return &(frame.DmabufFds());
}
} // namespace
struct PlatformVideoFramePool::FrameEntry {
base::TimeTicks last_use_time;
scoped_refptr<VideoFrame> frame;
FrameEntry(base::TimeTicks time, scoped_refptr<VideoFrame> frame)
: last_use_time(time), frame(std::move(frame)) {}
FrameEntry(const FrameEntry& other) {
last_use_time = other.last_use_time;
frame = other.frame;
}
~FrameEntry() = default;
};
PlatformVideoFramePool::PlatformVideoFramePool()
: PlatformVideoFramePool(base::BindRepeating(&DefaultCreateFrame),
base::DefaultTickClock::GetInstance()) {}
PlatformVideoFramePool::PlatformVideoFramePool(
CreateFrameCB cb,
const base::TickClock* tick_clock)
: create_frame_cb_(std::move(cb)),
tick_clock_(tick_clock),
max_num_frames_(kDefaultMaxNumFrames),
weak_this_factory_(this) {
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<VideoFrame> PlatformVideoFramePool::GetFrame() {
DCHECK(parent_task_runner_->RunsTasksInCurrentSequence());
DVLOGF(4);
base::AutoLock auto_lock(lock_);
if (!frame_layout_) {
VLOGF(1) << "Please call NegotiateFrameFormat() first.";
return nullptr;
}
VideoPixelFormat format = frame_layout_->format();
const gfx::Size& coded_size = frame_layout_->coded_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(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().frame);
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(GetDmabufId(*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->metadata()->Clear();
return wrapped_frame;
}
void PlatformVideoFramePool::set_parent_task_runner(
scoped_refptr<base::SequencedTaskRunner> parent_task_runner) {
DCHECK(!parent_task_runner_);
parent_task_runner_ = std::move(parent_task_runner);
parent_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PlatformVideoFramePool::PurgeStaleFrames, weak_this_),
kStaleFrameLimit);
}
void PlatformVideoFramePool::PurgeStaleFrames() {
DCHECK(parent_task_runner_->RunsTasksInCurrentSequence());
DVLOGF(4);
base::AutoLock auto_lock(lock_);
const base::TimeTicks now = tick_clock_->NowTicks();
while (!free_frames_.empty() &&
now - free_frames_.front().last_use_time > kStaleFrameLimit) {
free_frames_.pop_front();
}
parent_task_runner_->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PlatformVideoFramePool::PurgeStaleFrames, weak_this_),
kStaleFrameLimit);
}
void PlatformVideoFramePool::SetMaxNumFrames(size_t max_num_frames) {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
max_num_frames_ = max_num_frames;
if (frame_available_cb_ && !IsExhausted_Locked())
std::move(frame_available_cb_).Run();
}
base::Optional<VideoFrameLayout> PlatformVideoFramePool::NegotiateFrameFormat(
const VideoFrameLayout& layout,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size) {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
// 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 (!IsSameLayout_Locked(layout)) {
DVLOGF(4) << "The video frame format is changed. Clearing the pool.";
free_frames_.clear();
}
visible_rect_ = visible_rect;
natural_size_ = natural_size;
// Create a temporary frame in order to know VideoFrameLayout that VideoFrame
// that will be allocated in GetFrame() has.
auto frame =
create_frame_cb_.Run(layout.format(), layout.coded_size(), visible_rect,
natural_size_, base::TimeDelta());
if (!frame) {
VLOGF(1) << "Failed to create video frame";
return base::nullopt;
}
frame_layout_ = base::make_optional<VideoFrameLayout>(frame->layout());
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(GetDmabufId(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_);
DmabufId frame_id = GetDmabufId(*origin_frame);
auto it = frames_in_use_.find(frame_id);
DCHECK(it != frames_in_use_.end());
frames_in_use_.erase(it);
if (IsSameLayout_Locked(origin_frame->layout())) {
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({tick_clock_->NowTicks(), std::move(frame)});
}
size_t PlatformVideoFramePool::GetTotalNumFrames_Locked() const {
DVLOGF(4);
lock_.AssertAcquired();
return free_frames_.size() + frames_in_use_.size();
}
bool PlatformVideoFramePool::IsSameLayout_Locked(
const VideoFrameLayout& layout) const {
DVLOGF(4);
lock_.AssertAcquired();
return frame_layout_ && frame_layout_->format() == layout.format() &&
frame_layout_->coded_size() == layout.coded_size();
}
size_t PlatformVideoFramePool::GetPoolSizeForTesting() {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
return free_frames_.size();
}
} // namespace media