| // 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/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/threading/sequenced_task_runner_handle.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<uint8_t> as resize() calls take a really long time |
| // for large buffers. |
| std::unique_ptr<uint8_t[]> data; |
| size_t data_size = 0u; |
| std::unique_ptr<uint8_t[]> 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<FrameBuffer>& fb) { return !IsUsed(fb.get()); }); |
| |
| // If not, create one. |
| if (it == frame_buffers_.end()) |
| it = frame_buffers_.insert(it, std::make_unique<FrameBuffer>()); |
| |
| 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(); |
| frame_buffer->data.reset(new uint8_t[min_size]); |
| 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<FrameBuffer*>(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<FrameBuffer*>(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(); |
| frame_buffer->alpha_data.reset(new uint8_t[min_size]); |
| frame_buffer->alpha_data_size = min_size; |
| } |
| return frame_buffer->alpha_data.get(); |
| } |
| |
| base::Closure FrameBufferPool::CreateFrameCallback(void* fb_priv) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto* frame_buffer = static_cast<FrameBuffer*>(fb_priv); |
| ++frame_buffer->held_by_frame; |
| |
| return base::Bind(&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("media/frame_buffers/memory_pool"); |
| base::trace_event::MemoryAllocatorDump* used_memory_dump = |
| pmd->CreateAllocatorDump("media/frame_buffers/memory_pool/used"); |
| |
| 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<FrameBuffer>& buf) { |
| return !IsUsed(buf.get()); |
| }); |
| } |
| |
| void FrameBufferPool::OnVideoFrameDestroyed( |
| scoped_refptr<base::SequencedTaskRunner> task_runner, |
| FrameBuffer* frame_buffer) { |
| if (!task_runner->RunsTasksInCurrentSequence()) { |
| task_runner->PostTask( |
| FROM_HERE, base::Bind(&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<FrameBuffer>& buf) { |
| return !IsUsed(buf.get()) && |
| now - buf->last_use_time > |
| base::TimeDelta::FromSeconds(kStaleFrameLimitSecs); |
| }); |
| } |
| |
| } // namespace media |