1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/frame_buffer_pool.h"
7 #include "base/logging.h"
9 #include "base/check_op.h"
10 #include "base/containers/cxx20_erase.h"
11 #include "base/functional/bind.h"
12 #include "base/functional/callback_helpers.h"
13 #include "base/location.h"
14 #include "base/process/memory.h"
15 #include "base/ranges/algorithm.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/task/sequenced_task_runner.h"
18 #include "base/time/time.h"
19 #include "base/trace_event/memory_allocator_dump.h"
20 #include "base/trace_event/memory_dump_manager.h"
21 #include "base/trace_event/process_memory_dump.h"
25 struct FrameBufferPool::FrameBuffer {
26 // Not using std::vector<uint8_t> as resize() calls take a really long time
28 std::unique_ptr<uint8_t, base::UncheckedFreeDeleter> data;
29 size_t data_size = 0u;
30 std::unique_ptr<uint8_t, base::UncheckedFreeDeleter> alpha_data;
31 size_t alpha_data_size = 0u;
32 bool held_by_library = false;
33 // Needs to be a counter since a frame buffer might be used multiple times.
34 int held_by_frame = 0;
35 base::TimeTicks last_use_time;
38 FrameBufferPool::FrameBufferPool(bool zero_initialize_memory)
39 : zero_initialize_memory_(zero_initialize_memory),
40 tick_clock_(base::DefaultTickClock::GetInstance()),
41 task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}
43 FrameBufferPool::~FrameBufferPool() {
44 base::AutoLock lock(lock_);
47 // May be destructed on any thread.
50 uint8_t* FrameBufferPool::GetFrameBuffer(size_t min_size, void** fb_priv) {
51 base::AutoLock lock(lock_);
52 DCHECK(!in_shutdown_);
54 if (!registered_dump_provider_) {
55 // We tell the dump provider to call us on `task_runner_`, which will also
56 // be the thread that will call `Shutdown()` to unregister us. This lets
57 // the call to `Shutdown()` unregister us synchronously with respect to
58 // calls into `OnMemoryDump()`, so that there won't be calls into
59 // `OnMemoryDump()` after `Shutdown()` completes. The alternative is to
60 // give the `MemoryDumpManager` ownership of `this`, so that it can delete
61 // it after any outstanding dump requests are complete. Since we're
62 // refcounted, that would require a wrapper object to own the ref. This is
64 base::trace_event::MemoryDumpManager::GetInstance()
65 ->RegisterDumpProviderWithSequencedTaskRunner(
66 this, "FrameBufferPool", task_runner_,
67 MemoryDumpProvider::Options());
68 registered_dump_provider_ = true;
71 // Check if a free frame buffer exists.
72 auto it = base::ranges::find_if_not(frame_buffers_, &IsUsedLocked,
73 &std::unique_ptr<FrameBuffer>::get);
75 // If not, create one.
76 if (it == frame_buffers_.end())
77 it = frame_buffers_.insert(it, std::make_unique<FrameBuffer>());
79 auto& frame_buffer = *it;
81 // Resize the frame buffer if necessary.
82 frame_buffer->held_by_library = true;
83 if (frame_buffer->data_size < min_size) {
84 // Free the existing |data| first so that the memory can be reused,
85 // if possible. Note that the new array is purposely not initialized.
86 frame_buffer->data.reset();
88 uint8_t* data = nullptr;
89 if (!force_allocation_error_) {
91 if (zero_initialize_memory_) {
92 result = base::UncheckedCalloc(1u, min_size,
93 reinterpret_cast<void**>(&data));
96 base::UncheckedMalloc(min_size, reinterpret_cast<void**>(&data));
99 // Unclear why, but the docs indicate both that `data` will be null on
100 // failure, and also that the return value must not be discarded.
107 frame_buffers_.erase(it);
111 frame_buffer->data.reset(data);
112 frame_buffer->data_size = min_size;
115 // Provide the client with a private identifier.
116 *fb_priv = frame_buffer.get();
117 return frame_buffer->data.get();
120 void FrameBufferPool::ReleaseFrameBuffer(void* fb_priv) {
121 base::AutoLock lock(lock_);
124 // Note: The library may invoke this method multiple times for the same frame,
125 // so we can't DCHECK that |held_by_library| is true.
126 auto* frame_buffer = static_cast<FrameBuffer*>(fb_priv);
127 frame_buffer->held_by_library = false;
129 if (!IsUsedLocked(frame_buffer)) {
130 frame_buffer->last_use_time = tick_clock_->NowTicks();
134 uint8_t* FrameBufferPool::AllocateAlphaPlaneForFrameBuffer(size_t min_size,
136 base::AutoLock lock(lock_);
139 auto* frame_buffer = static_cast<FrameBuffer*>(fb_priv);
140 DCHECK(IsUsedLocked(frame_buffer));
141 if (frame_buffer->alpha_data_size < min_size) {
142 // Free the existing |alpha_data| first so that the memory can be reused,
143 // if possible. Note that the new array is purposely not initialized.
144 frame_buffer->alpha_data.reset();
145 uint8_t* data = nullptr;
146 if (force_allocation_error_ ||
147 !base::UncheckedMalloc(min_size, reinterpret_cast<void**>(&data)) ||
151 frame_buffer->alpha_data.reset(data);
152 frame_buffer->alpha_data_size = min_size;
154 return frame_buffer->alpha_data.get();
157 base::OnceClosure FrameBufferPool::CreateFrameCallback(void* fb_priv) {
158 base::AutoLock lock(lock_);
160 auto* frame_buffer = static_cast<FrameBuffer*>(fb_priv);
161 ++frame_buffer->held_by_frame;
163 return base::BindOnce(&FrameBufferPool::OnVideoFrameDestroyed, this,
167 bool FrameBufferPool::OnMemoryDump(
168 const base::trace_event::MemoryDumpArgs& args,
169 base::trace_event::ProcessMemoryDump* pmd) {
170 // While there's nothing thread-unsafe about this method, if we're ever not
171 // called on `task_runner_`, it likely means that `MemoryDumpManager` is using
172 // the wrong ownership semantics. See `GetFrameBuffer()` for details.
173 DCHECK(task_runner_->RunsTasksInCurrentSequence());
174 base::AutoLock lock(lock_);
176 base::trace_event::MemoryAllocatorDump* memory_dump =
177 pmd->CreateAllocatorDump(
178 base::StringPrintf("media/frame_buffers/memory_pool/0x%" PRIXPTR,
179 reinterpret_cast<uintptr_t>(this)));
180 base::trace_event::MemoryAllocatorDump* used_memory_dump =
181 pmd->CreateAllocatorDump(
182 base::StringPrintf("media/frame_buffers/memory_pool/used/0x%" PRIXPTR,
183 reinterpret_cast<uintptr_t>(this)));
185 auto* pool_name = base::trace_event::MemoryDumpManager::GetInstance()
186 ->system_allocator_pool_name();
188 pmd->AddSuballocation(memory_dump->guid(), pool_name);
190 size_t bytes_used = 0;
191 size_t bytes_reserved = 0;
192 for (const auto& frame_buffer : frame_buffers_) {
193 if (IsUsedLocked(frame_buffer.get())) {
194 bytes_used += frame_buffer->data_size + frame_buffer->alpha_data_size;
196 bytes_reserved += frame_buffer->data_size + frame_buffer->alpha_data_size;
199 memory_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
200 base::trace_event::MemoryAllocatorDump::kUnitsBytes,
202 used_memory_dump->AddScalar(
203 base::trace_event::MemoryAllocatorDump::kNameSize,
204 base::trace_event::MemoryAllocatorDump::kUnitsBytes, bytes_used);
209 void FrameBufferPool::Shutdown() {
210 // The memory dump manager requires that we unregister on the same thread that
211 // we're expecting memory dump requests on.
212 DCHECK(task_runner_->RunsTasksInCurrentSequence());
214 base::AutoLock lock(lock_);
217 if (registered_dump_provider_) {
218 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
222 // Clear any refs held by the library which isn't good about cleaning up after
223 // itself. This is safe since the library has already been shutdown by this
225 for (const auto& frame_buffer : frame_buffers_)
226 frame_buffer->held_by_library = false;
228 EraseUnusedResourcesLocked();
232 bool FrameBufferPool::IsUsedLocked(const FrameBuffer* buf) {
233 // Static, so can't check that `lock_` is acquired.
234 return buf->held_by_library || buf->held_by_frame > 0;
237 void FrameBufferPool::EraseUnusedResourcesLocked() {
238 lock_.AssertAcquired();
239 base::EraseIf(frame_buffers_, [](const std::unique_ptr<FrameBuffer>& buf) {
240 return !IsUsedLocked(buf.get());
244 void FrameBufferPool::OnVideoFrameDestroyed(FrameBuffer* frame_buffer) {
245 base::AutoLock lock(lock_);
246 DCHECK_GT(frame_buffer->held_by_frame, 0);
247 --frame_buffer->held_by_frame;
250 // If we're in shutdown we can be sure that the library has been destroyed.
251 EraseUnusedResourcesLocked();
255 const base::TimeTicks now = tick_clock_->NowTicks();
256 if (!IsUsedLocked(frame_buffer)) {
257 frame_buffer->last_use_time = now;
260 base::EraseIf(frame_buffers_, [now](const std::unique_ptr<FrameBuffer>& buf) {
261 return !IsUsedLocked(buf.get()) &&
262 now - buf->last_use_time > base::Seconds(kStaleFrameLimitSecs);