// Copyright 2017 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/filters/frame_buffer_pool.h" #include "base/bind.h" #include "base/callback_helpers.h" #include "base/check_op.h" #include "base/containers/cxx20_erase.h" #include "base/location.h" #include "base/memory/free_deleter.h" #include "base/process/memory.h" #include "base/strings/stringprintf.h" #include "base/task/sequenced_task_runner.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/time/time.h" #include "base/trace_event/memory_allocator_dump.h" #include "base/trace_event/memory_dump_manager.h" #include "base/trace_event/process_memory_dump.h" namespace media { struct FrameBufferPool::FrameBuffer { // Not using std::vector as resize() calls take a really long time // for large buffers. std::unique_ptr data; size_t data_size = 0u; std::unique_ptr alpha_data; size_t alpha_data_size = 0u; bool held_by_library = false; // Needs to be a counter since a frame buffer might be used multiple times. int held_by_frame = 0; base::TimeTicks last_use_time; }; FrameBufferPool::FrameBufferPool() : tick_clock_(base::DefaultTickClock::GetInstance()) { DETACH_FROM_SEQUENCE(sequence_checker_); } FrameBufferPool::~FrameBufferPool() { DCHECK(in_shutdown_); // May be destructed on any thread. } uint8_t* FrameBufferPool::GetFrameBuffer(size_t min_size, void** fb_priv) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!in_shutdown_); if (!registered_dump_provider_) { base::trace_event::MemoryDumpManager::GetInstance() ->RegisterDumpProviderWithSequencedTaskRunner( this, "FrameBufferPool", base::SequencedTaskRunnerHandle::Get(), MemoryDumpProvider::Options()); registered_dump_provider_ = true; } // Check if a free frame buffer exists. auto it = std::find_if( frame_buffers_.begin(), frame_buffers_.end(), [](const std::unique_ptr& fb) { return !IsUsed(fb.get()); }); // If not, create one. if (it == frame_buffers_.end()) it = frame_buffers_.insert(it, std::make_unique()); auto& frame_buffer = *it; // Resize the frame buffer if necessary. frame_buffer->held_by_library = true; if (frame_buffer->data_size < min_size) { // Free the existing |data| first so that the memory can be reused, // if possible. Note that the new array is purposely not initialized. frame_buffer->data.reset(); uint8_t* data = nullptr; if (force_allocation_error_ || !base::UncheckedMalloc(min_size, reinterpret_cast(&data)) || !data) { frame_buffers_.erase(it); return nullptr; } frame_buffer->data.reset(data); frame_buffer->data_size = min_size; } // Provide the client with a private identifier. *fb_priv = frame_buffer.get(); return frame_buffer->data.get(); } void FrameBufferPool::ReleaseFrameBuffer(void* fb_priv) { DCHECK(fb_priv); // Note: The library may invoke this method multiple times for the same frame, // so we can't DCHECK that |held_by_library| is true. auto* frame_buffer = static_cast(fb_priv); frame_buffer->held_by_library = false; if (!IsUsed(frame_buffer)) frame_buffer->last_use_time = tick_clock_->NowTicks(); } uint8_t* FrameBufferPool::AllocateAlphaPlaneForFrameBuffer(size_t min_size, void* fb_priv) { DCHECK(fb_priv); auto* frame_buffer = static_cast(fb_priv); DCHECK(IsUsed(frame_buffer)); if (frame_buffer->alpha_data_size < min_size) { // Free the existing |alpha_data| first so that the memory can be reused, // if possible. Note that the new array is purposely not initialized. frame_buffer->alpha_data.reset(); uint8_t* data = nullptr; if (force_allocation_error_ || !base::UncheckedMalloc(min_size, reinterpret_cast(&data)) || !data) { return nullptr; } frame_buffer->alpha_data.reset(data); frame_buffer->alpha_data_size = min_size; } return frame_buffer->alpha_data.get(); } base::OnceClosure FrameBufferPool::CreateFrameCallback(void* fb_priv) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto* frame_buffer = static_cast(fb_priv); ++frame_buffer->held_by_frame; return base::BindOnce(&FrameBufferPool::OnVideoFrameDestroyed, this, base::SequencedTaskRunnerHandle::Get(), frame_buffer); } bool FrameBufferPool::OnMemoryDump( const base::trace_event::MemoryDumpArgs& args, base::trace_event::ProcessMemoryDump* pmd) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); base::trace_event::MemoryAllocatorDump* memory_dump = pmd->CreateAllocatorDump( base::StringPrintf("media/frame_buffers/memory_pool/0x%" PRIXPTR, reinterpret_cast(this))); base::trace_event::MemoryAllocatorDump* used_memory_dump = pmd->CreateAllocatorDump( base::StringPrintf("media/frame_buffers/memory_pool/used/0x%" PRIXPTR, reinterpret_cast(this))); pmd->AddSuballocation(memory_dump->guid(), base::trace_event::MemoryDumpManager::GetInstance() ->system_allocator_pool_name()); size_t bytes_used = 0; size_t bytes_reserved = 0; for (const auto& frame_buffer : frame_buffers_) { if (IsUsed(frame_buffer.get())) bytes_used += frame_buffer->data_size + frame_buffer->alpha_data_size; bytes_reserved += frame_buffer->data_size + frame_buffer->alpha_data_size; } memory_dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, base::trace_event::MemoryAllocatorDump::kUnitsBytes, bytes_reserved); used_memory_dump->AddScalar( base::trace_event::MemoryAllocatorDump::kNameSize, base::trace_event::MemoryAllocatorDump::kUnitsBytes, bytes_used); return true; } void FrameBufferPool::Shutdown() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); in_shutdown_ = true; if (registered_dump_provider_) { base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( this); } // Clear any refs held by the library which isn't good about cleaning up after // itself. This is safe since the library has already been shutdown by this // point. for (const auto& frame_buffer : frame_buffers_) frame_buffer->held_by_library = false; EraseUnusedResources(); } // static bool FrameBufferPool::IsUsed(const FrameBuffer* buf) { return buf->held_by_library || buf->held_by_frame > 0; } void FrameBufferPool::EraseUnusedResources() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); base::EraseIf(frame_buffers_, [](const std::unique_ptr& buf) { return !IsUsed(buf.get()); }); } void FrameBufferPool::OnVideoFrameDestroyed( scoped_refptr task_runner, FrameBuffer* frame_buffer) { if (!task_runner->RunsTasksInCurrentSequence()) { task_runner->PostTask( FROM_HERE, base::BindOnce(&FrameBufferPool::OnVideoFrameDestroyed, this, task_runner, frame_buffer)); return; } DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_GT(frame_buffer->held_by_frame, 0); --frame_buffer->held_by_frame; if (in_shutdown_) { // If we're in shutdown we can be sure that the library has been destroyed. EraseUnusedResources(); return; } const base::TimeTicks now = tick_clock_->NowTicks(); if (!IsUsed(frame_buffer)) frame_buffer->last_use_time = now; base::EraseIf(frame_buffers_, [now](const std::unique_ptr& buf) { return !IsUsed(buf.get()) && now - buf->last_use_time > base::Seconds(kStaleFrameLimitSecs); }); } } // namespace media