blob: dc78fe9f62d3046108a5a2d8fb90b1d24612b13b [file] [log] [blame]
/*
* Copyright 2012 Google Inc. 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 "media/base/shell_buffer_factory.h"
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/stringprintf.h"
#include "media/base/decrypt_config.h"
#include "media/base/shell_media_platform.h"
#include "media/base/shell_media_statistics.h"
namespace media {
// ==== ShellScopedArray =======================================================
ShellScopedArray::ShellScopedArray(uint8* reusable_buffer, size_t size)
: array_(reusable_buffer), size_(size) {
if (array_) {
// Retain a reference to the buffer factory, to ensure that we do not
// outlive it.
buffer_factory_ = ShellBufferFactory::Instance();
}
}
ShellScopedArray::~ShellScopedArray() {
TRACE_EVENT0("media_stack", "ShellScopedArray::~ShellScopedArray()");
if (array_) {
buffer_factory_->Reclaim(array_);
}
}
// ==== ShellBufferFactory =====================================================
scoped_refptr<ShellBufferFactory> ShellBufferFactory::instance_ = NULL;
// static
void ShellBufferFactory::Initialize() {
// safe to call multiple times
if (!instance_) {
instance_ = new ShellBufferFactory();
}
}
bool ShellBufferFactory::AllocateBuffer(size_t size,
bool is_keyframe,
AllocCB cb) {
TRACE_EVENT1("media_stack", "ShellBufferFactory::AllocateBuffer()", "size",
size);
// Zero-size buffers are allocation error, allocate an EOS buffer explicity
// with the provided EOS method.
if (size == 0) {
TRACE_EVENT0("media_stack",
"ShellBufferFactory::AllocateBuffer() failed as size is 0.");
return false;
}
// If we can allocate a buffer right now save a pointer to it so that we don't
// run the callback while holding the memory lock, for safety's sake.
scoped_refptr<DecoderBuffer> instant_buffer = NULL;
{
base::AutoLock lock(lock_);
// We only service requests directly if there's no callbacks pending and
// we can accommodate a buffer of the requested size
if (pending_allocs_.size() == 0) {
uint8* bytes = Allocate_Locked(size);
if (bytes) {
instant_buffer = new DecoderBuffer(bytes, size, is_keyframe);
TRACE_EVENT0(
"media_stack",
"ShellBufferFactory::AllocateBuffer() finished allocation.");
DCHECK(!instant_buffer->IsEndOfStream());
}
}
if (!instant_buffer) {
// Alright, we have to wait, enqueue the buffer and size.
TRACE_EVENT0("media_stack",
"ShellBufferFactory::AllocateBuffer() deferred.");
pending_allocs_.push_back(
std::make_pair(cb, new DecoderBuffer(NULL, size, is_keyframe)));
}
}
// If we managed to create a buffer run the callback after releasing the lock.
if (instant_buffer) {
cb.Run(instant_buffer);
}
return true;
}
scoped_refptr<DecoderBuffer> ShellBufferFactory::AllocateBufferNow(
size_t size,
bool is_keyframe) {
TRACE_EVENT1("media_stack", "ShellBufferFactory::AllocateBufferNow()", "size",
size);
// Zero-size buffers are allocation error, allocate an EOS buffer explicity
// with the provided EOS method.
if (size == 0) {
TRACE_EVENT0(
"media_stack",
"ShellBufferFactory::AllocateBufferNow() failed as size is 0.");
return NULL;
}
base::AutoLock lock(lock_);
uint8* bytes = Allocate_Locked(size);
if (!bytes) {
TRACE_EVENT0(
"media_stack",
"ShellBufferFactory::AllocateBufferNow() failed as size is too large.");
return NULL;
}
scoped_refptr<DecoderBuffer> buffer =
new DecoderBuffer(bytes, size, is_keyframe);
TRACE_EVENT0("media_stack",
"ShellBufferFactory::AllocateBufferNow() finished allocation.");
DCHECK(!buffer->IsEndOfStream());
return buffer;
}
uint8* ShellBufferFactory::AllocateNow(size_t size) {
// we skip to the head of the line for these allocations, if there's
// room we allocate it.
base::AutoLock lock(lock_);
uint8* bytes = Allocate_Locked(size);
if (!bytes) {
DLOG(ERROR) << base::StringPrintf("Failed to allocate %d bytes!",
(int)size);
}
return bytes;
}
scoped_refptr<ShellScopedArray> ShellBufferFactory::AllocateArray(size_t size) {
TRACE_EVENT1("media_stack", "ShellBufferFactory::AllocateArray()", "size",
size);
uint8* allocated_bytes = NULL;
if (size == 0) {
TRACE_EVENT0("media_stack",
"ShellBufferFactory::AllocateArray() failed as size is 0.");
return NULL;
}
if (size <= kShellMaxArraySize) {
base::AutoLock lock(lock_);
// there should not already be somebody waiting on an array
if (array_requested_size_ > 0) {
TRACE_EVENT0(
"media_stack",
"ShellBufferFactory::AllocateArray() failed as another allocation is"
" in progress.");
NOTREACHED() << "Max one thread blocking on array allocation at a time.";
return NULL;
}
// Attempt to allocate.
allocated_bytes = Allocate_Locked(size);
// If we don't have room save state while we still have the lock
if (!allocated_bytes) {
array_requested_size_ = size;
}
} else { // oversized requests always fail instantly.
TRACE_EVENT0(
"media_stack",
"ShellBufferFactory::AllocateArray() failed as size is too large.");
return NULL;
}
// Lock is released. Now safe to block this thread if we need to.
if (!allocated_bytes) {
TRACE_EVENT0("media_stack",
"ShellBufferFactory::AllocateArray() deferred.");
// Wait until enough memory has been released to service this allocation.
array_allocation_event_.Wait();
{
// acquire lock to get address and clear requested size
base::AutoLock lock(lock_);
// make sure this allocation makes sense
DCHECK_EQ(size, array_requested_size_);
DCHECK(array_allocation_);
allocated_bytes = array_allocation_;
array_allocation_ = NULL;
array_requested_size_ = 0;
}
}
// Whether we blocked or not we should now have a pointer
DCHECK(allocated_bytes);
TRACE_EVENT0("media_stack",
"ShellBufferFactory::AllocateArray() finished allocation.");
return scoped_refptr<ShellScopedArray>(
new ShellScopedArray(allocated_bytes, size));
}
void ShellBufferFactory::Reclaim(uint8* p) {
TRACE_EVENT0("media_stack", "ShellBufferFactory::Reclaim()");
typedef std::list<std::pair<AllocCB, scoped_refptr<DecoderBuffer> > >
FinishList;
FinishList finished_allocs;
// Reclaim() on a NULL buffer is a no-op, don't even acquire the lock.
if (p) {
base::AutoLock lock(lock_);
ShellMediaPlatform::Instance()->FreeBuffer(p);
// Try to service a blocking array request if there is one, and it hasn't
// already been serviced. If we can't service it then we won't allocate any
// additional ShellBuffers as arrays get priority treatment.
bool service_buffers = true;
if (array_requested_size_ > 0 && !array_allocation_) {
array_allocation_ = Allocate_Locked(array_requested_size_);
if (array_allocation_) {
// Wake up blocked thread
array_allocation_event_.Signal();
} else {
// Not enough room for the array so don't give away what room we have
// to the buffers.
service_buffers = false;
}
}
// Try to process any enqueued allocs in FIFO order until we run out of room
while (service_buffers && pending_allocs_.size()) {
size_t size = pending_allocs_.front().second->GetAllocatedSize();
uint8* bytes = Allocate_Locked(size);
if (bytes) {
scoped_refptr<DecoderBuffer> alloc_buff =
pending_allocs_.front().second;
alloc_buff->SetBuffer(bytes);
TRACE_EVENT1("media_stack",
"ShellBufferFactory::Reclaim() finished allocation.",
"size", size);
finished_allocs.push_back(
std::make_pair(pending_allocs_.front().first, alloc_buff));
pending_allocs_.pop_front();
} else {
service_buffers = false;
}
}
}
// OK, lock released, do callbacks for finished allocs
for (FinishList::iterator it = finished_allocs.begin();
it != finished_allocs.end(); ++it) {
it->first.Run(it->second);
}
}
uint8* ShellBufferFactory::Allocate_Locked(size_t size) {
// should have acquired the lock already
lock_.AssertAcquired();
UPDATE_MEDIA_STATISTICS(STAT_TYPE_ALLOCATED_SHELL_BUFFER_SIZE, size);
return static_cast<uint8*>(
ShellMediaPlatform::Instance()->AllocateBuffer(size));
}
// static
void ShellBufferFactory::Terminate() {
instance_ = NULL;
}
ShellBufferFactory::ShellBufferFactory()
: array_allocation_event_(false, false),
array_requested_size_(0),
array_allocation_(NULL) {}
// Will be called when all ShellBuffers have been deleted AND instance_ has
// been set to NULL.
ShellBufferFactory::~ShellBufferFactory() {
// and no outstanding array requests
DCHECK_EQ(array_requested_size_, 0);
}
} // namespace media