blob: ccc63cfc9f853d80975725a6be0fc74f621b7342 [file] [log] [blame]
// Copyright 2013 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/base/video_frame_pool.h"
#include "base/bind.h"
#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "base/time/default_tick_clock.h"
namespace media {
class VideoFramePool::PoolImpl
: public base::RefCountedThreadSafe<VideoFramePool::PoolImpl> {
public:
PoolImpl();
// See VideoFramePool::CreateFrame() for usage. Attempts to keep |frames_| in
// LRU order by always pulling from the back of |frames_|.
scoped_refptr<VideoFrame> CreateFrame(VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp);
// Shuts down the frame pool and releases all frames in |frames_|.
// Once this is called frames will no longer be inserted back into
// |frames_|.
void Shutdown();
size_t get_pool_size_for_testing() const { return frames_.size(); }
void set_tick_clock_for_testing(base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
private:
friend class base::RefCountedThreadSafe<VideoFramePool::PoolImpl>;
~PoolImpl();
// Called when the frame wrapper gets destroyed. |frame| is the actual frame
// that was wrapped and is placed in |frames_| by this function so it can be
// reused. This will attempt to expire frames that haven't been used in some
// time. It relies on |frames_| being in LRU order with the front being the
// least recently used entry.
void FrameReleased(scoped_refptr<VideoFrame> frame);
base::Lock lock_;
bool is_shutdown_ = false;
struct FrameEntry {
base::TimeTicks last_use_time;
scoped_refptr<VideoFrame> frame;
};
base::circular_deque<FrameEntry> frames_;
// |tick_clock_| is always a DefaultTickClock outside of testing.
base::TickClock* tick_clock_;
DISALLOW_COPY_AND_ASSIGN(PoolImpl);
};
VideoFramePool::PoolImpl::PoolImpl()
: tick_clock_(base::DefaultTickClock::GetInstance()) {}
VideoFramePool::PoolImpl::~PoolImpl() {
DCHECK(is_shutdown_);
}
scoped_refptr<VideoFrame> VideoFramePool::PoolImpl::CreateFrame(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp) {
base::AutoLock auto_lock(lock_);
DCHECK(!is_shutdown_);
scoped_refptr<VideoFrame> frame;
while (!frame && !frames_.empty()) {
scoped_refptr<VideoFrame> pool_frame = std::move(frames_.back().frame);
frames_.pop_back();
if (pool_frame->format() == format &&
pool_frame->coded_size() == coded_size &&
pool_frame->visible_rect() == visible_rect &&
pool_frame->natural_size() == natural_size) {
frame = pool_frame;
frame->set_timestamp(timestamp);
frame->metadata()->Clear();
break;
}
}
if (!frame) {
frame = VideoFrame::CreateZeroInitializedFrame(
format, coded_size, visible_rect, natural_size, timestamp);
// This can happen if the arguments are not valid.
if (!frame) {
LOG(ERROR) << "Failed to create a video frame";
return nullptr;
}
}
scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame(
frame, frame->format(), frame->visible_rect(), frame->natural_size());
wrapped_frame->AddDestructionObserver(base::Bind(
&VideoFramePool::PoolImpl::FrameReleased, this, std::move(frame)));
return wrapped_frame;
}
void VideoFramePool::PoolImpl::Shutdown() {
base::AutoLock auto_lock(lock_);
is_shutdown_ = true;
frames_.clear();
}
void VideoFramePool::PoolImpl::FrameReleased(scoped_refptr<VideoFrame> frame) {
base::AutoLock auto_lock(lock_);
if (is_shutdown_)
return;
const base::TimeTicks now = tick_clock_->NowTicks();
frames_.push_back({now, std::move(frame)});
// After this loop, |stale_index| is the index of the oldest non-stale frame.
// Such an index must exist because |frame| is never stale.
int stale_index = -1;
constexpr base::TimeDelta kStaleFrameLimit = base::TimeDelta::FromSeconds(10);
while (now - frames_[++stale_index].last_use_time > kStaleFrameLimit) {
// Last frame should never be included since we just added it.
DCHECK_LE(static_cast<size_t>(stale_index), frames_.size());
}
if (stale_index)
frames_.erase(frames_.begin(), frames_.begin() + stale_index);
}
VideoFramePool::VideoFramePool() : pool_(new PoolImpl()) {}
VideoFramePool::~VideoFramePool() {
pool_->Shutdown();
}
scoped_refptr<VideoFrame> VideoFramePool::CreateFrame(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp) {
return pool_->CreateFrame(format, coded_size, visible_rect, natural_size,
timestamp);
}
size_t VideoFramePool::GetPoolSizeForTesting() const {
return pool_->get_pool_size_for_testing();
}
void VideoFramePool::SetTickClockForTesting(base::TickClock* tick_clock) {
pool_->set_tick_clock_for_testing(tick_clock);
}
} // namespace media