Commit 362149a9 authored by Chih-Yu Huang's avatar Chih-Yu Huang Committed by Commit Bot

Reland "media/gpu/linux: Create DmabufVideoFramePool."

In the original CL, PlatformVideoFramePool class doesn't initialize the value of
some data members. This CL fixed this issue.

Original change's description:
> DmabufVideoFramePool is used to allocate and recycle DMA-buf backed
> VideoFrames. It is used by hardware accelerated video decoder. Besides
> allocating and recycling VideoFrame, it also provide these functions:
>
> - Limit the number of buffers to prevent client from draining the
> memory.
> - Query the original frame by the wrapped frame.
>
> BUG=941330
> TEST=media_unittests
>
> Change-Id: I368d620fa0c8fb2b1a03c3479c08bad54acd55da
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1527911
> Commit-Queue: Chih-Yu Huang <akahuang@chromium.org>
> Auto-Submit: Chih-Yu Huang <akahuang@chromium.org>
> Reviewed-by: Hirokazu Honda <hiroh@chromium.org>
> Reviewed-by: Alexandre Courbot <acourbot@chromium.org>
> Reviewed-by: David Staessens <dstaessens@chromium.org>
> Reviewed-by: Dan Sanders <sandersd@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#664664}

BUG=941330
TEST=Run "media_unittests --gtest_filter=PlatformVideoFramePool*" with gn args "is_msan = true"

Change-Id: I51bfaf571c39ac53d2a2a9884e94f171373aa5f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1635126Reviewed-by: default avatarAlexandre Courbot <acourbot@chromium.org>
Commit-Queue: Chih-Yu Huang <akahuang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665085}
parent 25f990da
......@@ -558,6 +558,9 @@ source_set("unit_tests") {
"h264_decoder_unittest.cc",
]
if (is_linux) {
deps += [ "//media/gpu/linux:unit_tests" ]
}
if (use_vaapi) {
deps += [ "//media/gpu/vaapi:unit_test" ]
}
......
......@@ -10,8 +10,12 @@ assert(is_linux)
source_set("linux") {
defines = [ "MEDIA_GPU_IMPLEMENTATION" ]
sources = [
"dmabuf_video_frame_pool.cc",
"dmabuf_video_frame_pool.h",
"mailbox_video_frame_converter.cc",
"mailbox_video_frame_converter.h",
"platform_video_frame_pool.cc",
"platform_video_frame_pool.h",
"platform_video_frame_utils.cc",
"platform_video_frame_utils.h",
]
......@@ -46,3 +50,17 @@ source_set("video_frame_mapper") {
"//media/gpu:video_frame_mapper_common",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"platform_video_frame_pool_unittest.cc",
]
deps = [
":linux",
"//base",
"//base/test:test_support",
"//skia",
"//testing/gtest",
]
}
// 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/dmabuf_video_frame_pool.h"
namespace media {
DmabufVideoFramePool::DmabufVideoFramePool() = default;
DmabufVideoFramePool::~DmabufVideoFramePool() = default;
void DmabufVideoFramePool::set_parent_task_runner(
scoped_refptr<base::SequencedTaskRunner> parent_task_runner) {
DCHECK(!parent_task_runner_);
parent_task_runner_ = std::move(parent_task_runner);
}
} // namespace media
// 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.
#ifndef MEDIA_GPU_LINUX_DMABUF_VIDEO_FRAME_POOL_H_
#define MEDIA_GPU_LINUX_DMABUF_VIDEO_FRAME_POOL_H_
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h"
#include "media/gpu/media_gpu_export.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace media {
// Interface for allocating and managing DMA-buf VideoFrame. The client should
// set a task runner first, and guarantee both GetFrame() and the destructor are
// executed on this task runner.
// Note: other public methods might be called at different thread. The
// implementation must be thread-safe.
class MEDIA_GPU_EXPORT DmabufVideoFramePool {
public:
DmabufVideoFramePool();
virtual ~DmabufVideoFramePool();
// Setter method of |parent_task_runner_|. GetFrame() and destructor method
// should be called at |parent_task_runner_|.
// This method must be called only once before any GetFrame() is called.
virtual void set_parent_task_runner(
scoped_refptr<base::SequencedTaskRunner> parent_task_runner);
// Sets the maximum number of frames which can be allocated.
// Used to prevent client from draining all memory.
virtual void SetMaxNumFrames(size_t max_num_frames) = 0;
// Sets the parameters of allocating frames.
// This method must be called before GetFrame() is called.
virtual void SetFrameFormat(VideoFrameLayout layout,
gfx::Rect visible_rect,
gfx::Size natural_size) = 0;
// Returns a frame from the pool with the parameters assigned by
// SetFrameFormat(). Returns nullptr if the pool is exhausted.
virtual scoped_refptr<VideoFrame> GetFrame() = 0;
// Checks whether the pool is exhausted. This happens when the pool reached
// its maximum size and all frames are in use. Calling GetFrame() when the
// pool is exhausted will return a nullptr.
virtual bool IsExhausted() = 0;
// Returns the original frame of a wrapped frame. We need this method to
// determine whether the frame returned by GetFrame() is the same one after
// recycling, and bind destruction callback at original frames.
// TODO(akahuang): Find a way to avoid this method.
virtual VideoFrame* UnwrapFrame(const VideoFrame& wrapped_frame) = 0;
protected:
scoped_refptr<base::SequencedTaskRunner> parent_task_runner_;
};
} // namespace media
#endif // MEDIA_GPU_LINUX_DMABUF_VIDEO_FRAME_POOL_H_
// 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);
}
} // 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),
format_(PIXEL_FORMAT_UNKNOWN),
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 (coded_size_.IsEmpty()) {
VLOGF(1) << "Please call SetFrameFormat() first.";
return nullptr;
}
if (free_frames_.empty()) {
if (GetTotalNumFrames_Locked() >= max_num_frames_)
return nullptr;
scoped_refptr<VideoFrame> new_frame = create_frame_cb_.Run(
format_, coded_size_, visible_rect_, natural_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();
scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame(
*origin_frame, origin_frame->format(), origin_frame->visible_rect(),
origin_frame->natural_size());
frames_in_use_.insert(
std::make_pair(wrapped_frame->unique_id(), origin_frame.get()));
wrapped_frame->AddDestructionObserver(
base::BindOnce(&PlatformVideoFramePool::OnFrameReleasedThunk, weak_this_,
parent_task_runner_, wrapped_frame->unique_id(),
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;
}
void PlatformVideoFramePool::SetFrameFormat(VideoFrameLayout layout,
gfx::Rect visible_rect,
gfx::Size natural_size) {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
if (!IsSameFormat_Locked(layout.format(), layout.coded_size(), visible_rect,
natural_size)) {
DVLOGF(4) << "The video frame format is changed. Clearing the pool.";
free_frames_.clear();
format_ = layout.format();
coded_size_ = layout.coded_size();
visible_rect_ = visible_rect;
natural_size_ = natural_size;
}
}
bool PlatformVideoFramePool::IsExhausted() {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
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(wrapped_frame.unique_id());
return (it == frames_in_use_.end()) ? nullptr : it->second;
}
// static
void PlatformVideoFramePool::OnFrameReleasedThunk(
base::Optional<base::WeakPtr<PlatformVideoFramePool>> pool,
scoped_refptr<base::SequencedTaskRunner> task_runner,
int wrapped_frame_id,
scoped_refptr<VideoFrame> origin_frame) {
DCHECK(pool);
DVLOGF(4);
task_runner->PostTask(
FROM_HERE, base::BindOnce(&PlatformVideoFramePool::OnFrameReleased, *pool,
wrapped_frame_id, std::move(origin_frame)));
}
void PlatformVideoFramePool::OnFrameReleased(
int wrapped_frame_id,
scoped_refptr<VideoFrame> origin_frame) {
DCHECK(parent_task_runner_->RunsTasksInCurrentSequence());
DVLOGF(4);
base::AutoLock auto_lock(lock_);
auto it = frames_in_use_.find(wrapped_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->natural_size())) {
InsertFreeFrame_Locked(std::move(origin_frame));
}
}
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::IsSameFormat_Locked(VideoPixelFormat format,
gfx::Size coded_size,
gfx::Rect visible_rect,
gfx::Size natural_size) const {
DVLOGF(4);
lock_.AssertAcquired();
return format_ == format && coded_size_ == coded_size &&
visible_rect_ == visible_rect && natural_size_ == natural_size;
}
size_t PlatformVideoFramePool::GetPoolSizeForTesting() {
DVLOGF(4);
base::AutoLock auto_lock(lock_);
return free_frames_.size();
}
} // namespace media
// 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.
#ifndef MEDIA_GPU_LINUX_PLATFORM_VIDEO_FRAME_POOL_H_
#define MEDIA_GPU_LINUX_PLATFORM_VIDEO_FRAME_POOL_H_
#include <stddef.h>
#include "base/bind.h"
#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/lock.h"
#include "base/thread_annotations.h"
#include "media/base/video_frame.h"
#include "media/gpu/linux/dmabuf_video_frame_pool.h"
#include "media/gpu/media_gpu_export.h"
namespace base {
class TickClock;
}
namespace media {
// Simple VideoFrame pool used to avoid unnecessarily allocating and destroying
// VideoFrame objects. The pool manages the memory for the VideoFrame
// returned by GetFrame(). When one of these VideoFrames is destroyed,
// the memory is returned to the pool for use by a subsequent GetFrame()
// call. The memory in the pool is retained for the life of the
// PlatformVideoFramePool object. Before calling GetFrame(), the client should
// call SetFrameFormat(). If the parameters passed to SetFrameFormat() are
// changed, then the memory used by frames with the old parameter values will be
// purged from the pool. Frames which are not used for a certain period
// will be purged.
class MEDIA_GPU_EXPORT PlatformVideoFramePool : public DmabufVideoFramePool {
public:
PlatformVideoFramePool();
~PlatformVideoFramePool() override;
// VideoFramePoolBase Implementation.
void set_parent_task_runner(
scoped_refptr<base::SequencedTaskRunner> parent_task_runner) override;
void SetMaxNumFrames(size_t max_num_frames) override;
void SetFrameFormat(VideoFrameLayout layout,
gfx::Rect visible_rect,
gfx::Size natural_size) override;
scoped_refptr<VideoFrame> GetFrame() override;
bool IsExhausted() override;
VideoFrame* UnwrapFrame(const VideoFrame& wrapped_frame) override;
private:
friend class PlatformVideoFramePoolTest;
using CreateFrameCB = base::RepeatingCallback<scoped_refptr<VideoFrame>(
VideoPixelFormat format,
const gfx::Size& coded_size,
const gfx::Rect& visible_rect,
const gfx::Size& natural_size,
base::TimeDelta timestamp)>;
// Used to store free frame and the last used time.
struct FrameEntry;
// Allows injection of create frame callback and base::SimpleTestClock.
// This is used to test the behavior of the video frame pool.
PlatformVideoFramePool(CreateFrameCB cb, const base::TickClock* tick_clock);
// Returns the number of frames in the pool for testing purposes.
size_t GetPoolSizeForTesting();
// Thunk to post OnFrameReleased() to |task_runner|.
// Because this thunk may be called in any thread, We don't want to
// dereference WeakPtr. Therefore we wrap the WeakPtr by base::Optional to
// avoid the task runner defererencing the WeakPtr.
static void OnFrameReleasedThunk(
base::Optional<base::WeakPtr<PlatformVideoFramePool>> pool,
scoped_refptr<base::SequencedTaskRunner> task_runner,
int wrapped_frame_id,
scoped_refptr<VideoFrame> origin_frame);
// Called when a wrapped frame gets destroyed. |wrapped_frame_id| is unique_id
// of the wrapped frame. The actual frame is placed in |free_frames_| by
// this function so it can be reused.
// When returning a frame to the pool, the pool might have already been
// destroyed. In this case, the WeakPtr of the pool will have been invalidated
// at |parent_task_runner_|, and OnFrameReleased() will not get executed.
void OnFrameReleased(int wrapped_frame_id,
scoped_refptr<VideoFrame> origin_frame);
void InsertFreeFrame_Locked(scoped_refptr<VideoFrame> frame)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
size_t GetTotalNumFrames_Locked() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
bool IsSameFormat_Locked(VideoPixelFormat format,
gfx::Size coded_size,
gfx::Rect visible_rect,
gfx::Size natural_size) const
EXCLUSIVE_LOCKS_REQUIRED(lock_);
// Purges the stale frame. This method is executed periodically on
// |parent_task_runner_|.
void PurgeStaleFrames();
// The function used to allocate new frames.
const CreateFrameCB create_frame_cb_;
// |tick_clock_| is always a DefaultTickClock outside of testing.
const base::TickClock* tick_clock_;
// Lock to protect all data members.
// Every public method and OnFrameReleased() should acquire this lock.
base::Lock lock_;
// The arguments of current frame.
VideoPixelFormat format_ GUARDED_BY(lock_);
gfx::Size coded_size_ GUARDED_BY(lock_);
gfx::Rect visible_rect_ GUARDED_BY(lock_);
gfx::Size natural_size_ GUARDED_BY(lock_);
// The pool of free frames. The arguments of all the frames in |free_frames_|
// should be the same as |format_|, |coded_size_|, |visible_rect_|, and
// |natural_size_|.
base::circular_deque<FrameEntry> free_frames_ GUARDED_BY(lock_);
// Mapping from the unique_id of the wrapped frame to the original frame.
std::map<int, VideoFrame*> frames_in_use_ GUARDED_BY(lock_);
// The maximum number of frames created by the pool.
size_t max_num_frames_ GUARDED_BY(lock_);
// The weak pointer of this, bound at |parent_task_runner_|.
// Used at the VideoFrame destruction callback.
base::WeakPtr<PlatformVideoFramePool> weak_this_;
base::WeakPtrFactory<PlatformVideoFramePool> weak_this_factory_;
DISALLOW_COPY_AND_ASSIGN(PlatformVideoFramePool);
};
} // namespace media
#endif // MEDIA_GPU_LINUX_PLATFORM_VIDEO_FRAME_POOL_H_
// 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 <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/gpu/linux/platform_video_frame_pool.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
class PlatformVideoFramePoolTest
: public ::testing::TestWithParam<VideoPixelFormat> {
public:
PlatformVideoFramePoolTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME) {
// Seed test clock with some dummy non-zero value to avoid confusion with
// empty base::TimeTicks values.
test_clock_.Advance(base::TimeDelta::FromSeconds(1234));
pool_.reset(new PlatformVideoFramePool(
base::BindRepeating(&VideoFrame::CreateZeroInitializedFrame),
&test_clock_));
pool_->set_parent_task_runner(base::ThreadTaskRunnerHandle::Get());
}
void SetFrameFormat(VideoPixelFormat format) {
gfx::Size coded_size(320, 240);
visible_rect_.set_size(coded_size);
natural_size_ = coded_size;
layout_ = VideoFrameLayout::Create(format, coded_size);
DCHECK(layout_);
pool_->SetFrameFormat(layout_.value(), visible_rect_, natural_size_);
}
scoped_refptr<VideoFrame> GetFrame(int timestamp_ms) {
scoped_refptr<VideoFrame> frame = pool_->GetFrame();
frame->set_timestamp(base::TimeDelta::FromMilliseconds(timestamp_ms));
EXPECT_EQ(layout_.value().format(), frame->format());
EXPECT_EQ(layout_.value().coded_size(), frame->coded_size());
EXPECT_EQ(visible_rect_, frame->visible_rect());
EXPECT_EQ(natural_size_, frame->natural_size());
return frame;
}
void CheckPoolSize(size_t size) const {
EXPECT_EQ(size, pool_->GetPoolSizeForTesting());
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::SimpleTestTickClock test_clock_;
std::unique_ptr<PlatformVideoFramePool,
std::default_delete<DmabufVideoFramePool>>
pool_;
base::Optional<VideoFrameLayout> layout_;
gfx::Rect visible_rect_;
gfx::Size natural_size_;
};
TEST_P(PlatformVideoFramePoolTest, FrameInitializedAndZeroed) {
SetFrameFormat(GetParam());
scoped_refptr<VideoFrame> frame = GetFrame(10);
// Verify that frame is initialized with zeros.
for (size_t i = 0; i < VideoFrame::NumPlanes(frame->format()); ++i)
EXPECT_EQ(0, frame->data(i)[0]);
}
INSTANTIATE_TEST_SUITE_P(,
PlatformVideoFramePoolTest,
testing::Values(PIXEL_FORMAT_I420,
PIXEL_FORMAT_NV12,
PIXEL_FORMAT_ARGB));
TEST_F(PlatformVideoFramePoolTest, SingleFrameReuse) {
SetFrameFormat(PIXEL_FORMAT_I420);
scoped_refptr<VideoFrame> frame = GetFrame(10);
const uint8_t* old_y_data = frame->data(VideoFrame::kYPlane);
// Clear frame reference to return the frame to the pool.
frame = nullptr;
scoped_task_environment_.RunUntilIdle();
// Verify that the next frame from the pool uses the same memory.
scoped_refptr<VideoFrame> new_frame = GetFrame(20);
EXPECT_EQ(old_y_data, new_frame->data(VideoFrame::kYPlane));
}
TEST_F(PlatformVideoFramePoolTest, MultipleFrameReuse) {
SetFrameFormat(PIXEL_FORMAT_I420);
scoped_refptr<VideoFrame> frame1 = GetFrame(10);
scoped_refptr<VideoFrame> frame2 = GetFrame(20);
const uint8_t* old_y_data1 = frame1->data(VideoFrame::kYPlane);
const uint8_t* old_y_data2 = frame2->data(VideoFrame::kYPlane);
frame1 = nullptr;
scoped_task_environment_.RunUntilIdle();
frame1 = GetFrame(30);
EXPECT_EQ(old_y_data1, frame1->data(VideoFrame::kYPlane));
frame2 = nullptr;
scoped_task_environment_.RunUntilIdle();
frame2 = GetFrame(40);
EXPECT_EQ(old_y_data2, frame2->data(VideoFrame::kYPlane));
frame1 = nullptr;
frame2 = nullptr;
scoped_task_environment_.RunUntilIdle();
CheckPoolSize(2u);
}
TEST_F(PlatformVideoFramePoolTest, FormatChange) {
SetFrameFormat(PIXEL_FORMAT_I420);
scoped_refptr<VideoFrame> frame_a = GetFrame(10);
scoped_refptr<VideoFrame> frame_b = GetFrame(10);
// Clear frame references to return the frames to the pool.
frame_a = nullptr;
frame_b = nullptr;
scoped_task_environment_.RunUntilIdle();
// Verify that both frames are in the pool.
CheckPoolSize(2u);
// Verify that requesting a frame with a different format causes the pool
// to get drained.
SetFrameFormat(PIXEL_FORMAT_I420A);
scoped_refptr<VideoFrame> new_frame = GetFrame(10);
CheckPoolSize(0u);
}
TEST_F(PlatformVideoFramePoolTest, FrameValidAfterPoolDestruction) {
SetFrameFormat(PIXEL_FORMAT_I420);
scoped_refptr<VideoFrame> frame = GetFrame(10);
// Destroy the pool.
pool_.reset();
// Write to the Y plane. The memory tools should detect a
// use-after-free if the storage was actually removed by pool destruction.
memset(frame->data(VideoFrame::kYPlane), 0xff,
frame->rows(VideoFrame::kYPlane) * frame->stride(VideoFrame::kYPlane));
}
TEST_F(PlatformVideoFramePoolTest, StaleFramesAreExpired) {
SetFrameFormat(PIXEL_FORMAT_I420);
scoped_refptr<VideoFrame> frame_1 = GetFrame(10);
scoped_refptr<VideoFrame> frame_2 = GetFrame(10);
EXPECT_NE(frame_1.get(), frame_2.get());
CheckPoolSize(0u);
// Drop frame and verify that resources are still available for reuse.
frame_1 = nullptr;
scoped_task_environment_.RunUntilIdle();
CheckPoolSize(1u);
// Advance clock far enough to hit stale timer; ensure only frame_1 has its
// resources released.
base::TimeDelta time_forward = base::TimeDelta::FromMinutes(1);
test_clock_.Advance(time_forward);
scoped_task_environment_.FastForwardBy(time_forward);
frame_2 = nullptr;
scoped_task_environment_.RunUntilIdle();
CheckPoolSize(1u);
}
} // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment