| // Copyright 2017 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/media/decoder_buffer_allocator.h" |
| |
| #include <vector> |
| |
| #include "cobalt/media/base/starboard_utils.h" |
| #include "cobalt/media/base/video_resolution.h" |
| #include "nb/allocator.h" |
| #include "nb/memory_scope.h" |
| #include "starboard/configuration.h" |
| #include "starboard/media.h" |
| #include "starboard/memory.h" |
| |
| namespace cobalt { |
| namespace media { |
| |
| namespace { |
| |
| const bool kEnableMultiblockAllocate = false; |
| const bool kEnableAllocationLog = false; |
| |
| const std::size_t kAllocationRecordGranularity = 512 * 1024; |
| const std::size_t kSmallAllocationThreshold = 512; |
| |
| bool IsLargeAllocation(std::size_t size) { |
| return size > kSmallAllocationThreshold; |
| } |
| |
| } // namespace |
| |
| DecoderBufferAllocator::DecoderBufferAllocator() { |
| #if SB_API_VERSION >= 10 |
| using_memory_pool_ = SbMediaIsBufferUsingMemoryPool(); |
| bool pool_allocate_on_demand = SbMediaIsBufferPoolAllocateOnDemand(); |
| #elif COBALT_MEDIA_BUFFER_USING_MEMORY_POOL |
| using_memory_pool_ = true; |
| bool pool_allocate_on_demand = COBALT_MEDIA_BUFFER_POOL_ALLOCATE_ON_DEMAND; |
| #endif // SB_API_VERSION >= 10 |
| |
| #if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| if (using_memory_pool_) { |
| if (pool_allocate_on_demand) { |
| DLOG(INFO) << "Allocated media buffer pool on demand."; |
| } else { |
| TRACK_MEMORY_SCOPE("Media"); |
| #if SB_API_VERSION >= 10 |
| int initial_capacity = SbMediaGetInitialBufferCapacity(); |
| // We cannot call SbMediaGetMaxBufferCapacity because |video_codec_| is |
| // not set yet. Use 0 (unbounded) until |video_codec_| is updated in |
| // UpdateVideoConfig. |
| int max_capacity = 0; |
| int allocation_unit = SbMediaGetBufferAllocationUnit(); |
| #else // SB_API_VERSION >= 10 |
| int initial_capacity = COBALT_MEDIA_BUFFER_INITIAL_CAPACITY; |
| int max_capacity = COBALT_MEDIA_BUFFER_MAX_CAPACITY_1080P; |
| int allocation_unit = COBALT_MEDIA_BUFFER_ALLOCATION_UNIT; |
| #endif // SB_API_VERSION >= 10 |
| reuse_allocator_.reset(new ReuseAllocator(&fallback_allocator_, |
| initial_capacity, |
| allocation_unit, max_capacity)); |
| DLOG(INFO) << "Allocated " << initial_capacity |
| << " bytes for media buffer pool as its initial buffer."; |
| } |
| return; |
| } |
| #endif // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| DLOG(INFO) << "Allocated media buffer memory using SbMemory* functions."; |
| } |
| |
| DecoderBufferAllocator::~DecoderBufferAllocator() { |
| #if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| if (using_memory_pool_) { |
| TRACK_MEMORY_SCOPE("Media"); |
| |
| starboard::ScopedLock scoped_lock(mutex_); |
| |
| if (reuse_allocator_) { |
| DCHECK_EQ(reuse_allocator_->GetAllocated(), 0); |
| reuse_allocator_.reset(); |
| } |
| } |
| #endif // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || |
| // SB_API_VERSION >= 10 |
| } |
| |
| DecoderBuffer::Allocator::Allocations DecoderBufferAllocator::Allocate( |
| size_t size, size_t alignment, intptr_t context) { |
| TRACK_MEMORY_SCOPE("Media"); |
| |
| #if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| if (using_memory_pool_) { |
| starboard::ScopedLock scoped_lock(mutex_); |
| |
| if (!reuse_allocator_) { |
| #if SB_API_VERSION >= 10 |
| int initial_capacity = SbMediaGetInitialBufferCapacity(); |
| int max_capacity = SbMediaGetMaxBufferCapacity( |
| video_codec_, resolution_width_, resolution_height_, bits_per_pixel_); |
| int allocation_unit = SbMediaGetBufferAllocationUnit(); |
| #else // SB_API_VERSION >= 10 |
| int initial_capacity = COBALT_MEDIA_BUFFER_INITIAL_CAPACITY; |
| int max_capacity = COBALT_MEDIA_BUFFER_MAX_CAPACITY_1080P; |
| int allocation_unit = COBALT_MEDIA_BUFFER_ALLOCATION_UNIT; |
| #endif // SB_API_VERSION >= 10 |
| reuse_allocator_.reset(new ReuseAllocator(&fallback_allocator_, |
| initial_capacity, |
| allocation_unit, max_capacity)); |
| DLOG(INFO) << "Returned " << initial_capacity |
| << " bytes from media buffer pool to system."; |
| } |
| |
| if (!kEnableMultiblockAllocate || kEnableAllocationLog) { |
| void* p = reuse_allocator_->Allocate(size, alignment); |
| if (!p) { |
| return Allocations(); |
| } |
| LOG_IF(INFO, kEnableAllocationLog) |
| << "Media Allocation Log " << p << " " << size << " " << alignment |
| << " " << context; |
| if (!UpdateAllocationRecord()) { |
| // UpdateAllocationRecord may fail with non-NULL p when capacity is |
| // exceeded. |
| reuse_allocator_->Free(p); |
| return Allocations(); |
| } |
| return Allocations(p, size); |
| } |
| |
| std::size_t allocated_size = size; |
| void* p = reuse_allocator_->AllocateBestBlock(alignment, context, |
| &allocated_size); |
| DCHECK_LE(allocated_size, size); |
| if (!p) { |
| return Allocations(); |
| } |
| if (allocated_size == size) { |
| if (!UpdateAllocationRecord()) { |
| // UpdateAllocationRecord may fail with non-NULL p when capacity is |
| // exceeded. |
| reuse_allocator_->Free(p); |
| return Allocations(); |
| } |
| return Allocations(p, size); |
| } |
| |
| std::vector<void*> buffers = {p}; |
| std::vector<int> buffer_sizes = {static_cast<int>(allocated_size)}; |
| size -= allocated_size; |
| |
| bool update_allocation_record_failed = false; |
| while (size > 0) { |
| allocated_size = size; |
| void* p = reuse_allocator_->AllocateBestBlock(alignment, context, |
| &allocated_size); |
| if (!p) { |
| return Allocations(); |
| } |
| if (!UpdateAllocationRecord()) { |
| update_allocation_record_failed = true; |
| reuse_allocator_->Free(p); |
| break; |
| } |
| DCHECK_LE(allocated_size, size); |
| buffers.push_back(p); |
| buffer_sizes.push_back(allocated_size); |
| |
| size -= allocated_size; |
| } |
| if (update_allocation_record_failed) { |
| for (auto& p : buffers) { |
| reuse_allocator_->Free(p); |
| } |
| return Allocations(); |
| } |
| return Allocations(static_cast<int>(buffers.size()), buffers.data(), |
| buffer_sizes.data()); |
| } |
| #endif // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || |
| // SB_API_VERSION >= 10 |
| return Allocations(SbMemoryAllocateAligned(alignment, size), size); |
| } |
| |
| void DecoderBufferAllocator::Free(Allocations allocations) { |
| TRACK_MEMORY_SCOPE("Media"); |
| #if SB_API_VERSION >= 10 |
| bool pool_allocate_on_demand = SbMediaIsBufferPoolAllocateOnDemand(); |
| #else // SB_API_VERSION >= 10 |
| bool pool_allocate_on_demand = COBALT_MEDIA_BUFFER_POOL_ALLOCATE_ON_DEMAND; |
| #endif // SB_API_VERSION >= 10 |
| |
| #if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| if (using_memory_pool_) { |
| starboard::ScopedLock scoped_lock(mutex_); |
| |
| DCHECK(reuse_allocator_); |
| |
| if (kEnableAllocationLog) { |
| DCHECK_EQ(allocations.number_of_buffers(), 1); |
| LOG(INFO) << "Media Allocation Log " << allocations.buffers()[0]; |
| } |
| for (int i = 0; i < allocations.number_of_buffers(); ++i) { |
| reuse_allocator_->Free(allocations.buffers()[i]); |
| } |
| if (pool_allocate_on_demand) { |
| if (reuse_allocator_->GetAllocated() == 0) { |
| DLOG(INFO) << "Freed " << reuse_allocator_->GetCapacity() |
| << " bytes of media buffer pool `on demand`."; |
| reuse_allocator_.reset(); |
| } |
| } |
| return; |
| } |
| #endif // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || |
| // SB_API_VERSION >= 10 |
| for (int i = 0; i < allocations.number_of_buffers(); ++i) { |
| SbMemoryDeallocateAligned(allocations.buffers()[i]); |
| } |
| } |
| |
| void DecoderBufferAllocator::UpdateVideoConfig( |
| const VideoDecoderConfig& config) { |
| #if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| if (using_memory_pool_) { |
| #if SB_API_VERSION >= 10 |
| video_codec_ = MediaVideoCodecToSbMediaVideoCodec(config.codec()); |
| resolution_width_ = config.visible_rect().size().width(); |
| resolution_height_ = config.visible_rect().size().height(); |
| bits_per_pixel_ = config.webm_color_metadata().BitsPerChannel; |
| #endif // SB_API_VERSION >= 10 |
| if (!reuse_allocator_) { |
| return; |
| } |
| #if SB_API_VERSION >= 10 |
| reuse_allocator_->set_max_capacity(SbMediaGetMaxBufferCapacity( |
| video_codec_, resolution_width_, resolution_height_, bits_per_pixel_)); |
| #else // SB_API_VERSION >= 10 |
| VideoResolution resolution = |
| GetVideoResolution(config.visible_rect().size()); |
| if (reuse_allocator_->max_capacity() && |
| resolution > kVideoResolution1080p) { |
| reuse_allocator_->set_max_capacity(COBALT_MEDIA_BUFFER_MAX_CAPACITY_4K); |
| } |
| #endif // SB_API_VERSION >= 10 |
| } |
| #endif // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || |
| // SB_API_VERSION >= 10 |
| } |
| |
| #if COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| DecoderBufferAllocator::ReuseAllocator::ReuseAllocator( |
| Allocator* fallback_allocator, std::size_t initial_capacity, |
| std::size_t allocation_increment, std::size_t max_capacity) |
| : BidirectionalFitReuseAllocator(fallback_allocator, initial_capacity, |
| kSmallAllocationThreshold, |
| allocation_increment, max_capacity) {} |
| |
| DecoderBufferAllocator::ReuseAllocator::FreeBlockSet::iterator |
| DecoderBufferAllocator::ReuseAllocator::FindBestFreeBlock( |
| std::size_t size, std::size_t alignment, intptr_t context, |
| FreeBlockSet::iterator begin, FreeBlockSet::iterator end, |
| bool* allocate_from_front) { |
| SB_DCHECK(allocate_from_front); |
| |
| auto free_block_iter = |
| FindFreeBlock(size, alignment, begin, end, allocate_from_front); |
| if (free_block_iter != end) { |
| return free_block_iter; |
| } |
| |
| *allocate_from_front = IsLargeAllocation(size); |
| *allocate_from_front = context == 1; |
| if (*allocate_from_front) { |
| for (FreeBlockSet::iterator it = begin; it != end; ++it) { |
| if (it->CanFullfill(1, alignment)) { |
| return it; |
| } |
| } |
| |
| return end; |
| } |
| |
| FreeBlockSet::reverse_iterator rbegin(end); |
| FreeBlockSet::reverse_iterator rend(begin); |
| for (FreeBlockSet::reverse_iterator it = rbegin; it != rend; ++it) { |
| if (it->CanFullfill(1, alignment)) { |
| return --it.base(); |
| } |
| } |
| return end; |
| } |
| |
| bool DecoderBufferAllocator::UpdateAllocationRecord( |
| std::size_t blocks /*= 1*/) const { |
| #if !defined(COBALT_BUILD_TYPE_GOLD) |
| // This code is not quite multi-thread safe but is safe enough for tracking |
| // purposes. |
| #if SB_API_VERSION >= 10 |
| int initial_capacity = SbMediaGetInitialBufferCapacity(); |
| #else // SB_API_VERSION >= 10 |
| int initial_capacity = COBALT_MEDIA_BUFFER_INITIAL_CAPACITY; |
| #endif // SB_API_VERSION >= 10 |
| static std::size_t max_allocated = initial_capacity / 2; |
| static std::size_t max_capacity = initial_capacity; |
| static std::size_t max_blocks = 1; |
| |
| bool new_max_reached = false; |
| if (reuse_allocator_->GetAllocated() > |
| max_allocated + kAllocationRecordGranularity) { |
| max_allocated = reuse_allocator_->GetAllocated(); |
| new_max_reached = true; |
| } |
| if (reuse_allocator_->GetCapacity() > |
| max_capacity + kAllocationRecordGranularity) { |
| max_capacity = reuse_allocator_->GetCapacity(); |
| new_max_reached = true; |
| } |
| if (blocks > max_blocks) { |
| max_blocks = blocks; |
| new_max_reached = true; |
| } |
| if (new_max_reached) { |
| SB_LOG(ERROR) << "New Media Buffer Allocation Record: " |
| << "Max Allocated: " << max_allocated |
| << " Max Capacity: " << max_capacity |
| << " Max Blocks: " << max_blocks; |
| // TODO: Enable the following line once PrintAllocations() accepts max line |
| // as a parameter. |
| // reuse_allocator_->PrintAllocations(); |
| } |
| #endif // !defined(COBALT_BUILD_TYPE_GOLD) |
| #if COBALT_MEDIA_BUFFER_MAX_CAPACITY_1080P > 0 || \ |
| COBALT_MEDIA_BUFFER_MAX_CAPACITY_4K > 0 || SB_API_VERSION >= 10 |
| if (reuse_allocator_->CapacityExceeded()) { |
| SB_LOG(ERROR) << "Cobalt media buffer capacity " |
| << reuse_allocator_->GetCapacity() |
| << " exceeded max capacity " |
| << reuse_allocator_->max_capacity(); |
| return false; |
| } |
| #endif // COBALT_MEDIA_BUFFER_MAX_CAPACITY_1080P > 0 || |
| // COBALT_MEDIA_BUFFER_MAX_CAPACITY_4K > 0 || SB_API_VERSION >= 10 |
| return true; |
| } |
| #endif // COBALT_MEDIA_BUFFER_USING_MEMORY_POOL || SB_API_VERSION >= 10 |
| } // namespace media |
| } // namespace cobalt |