blob: b571429464240cb1e2ad3e0a010137b79665e772 [file] [log] [blame]
// Copyright (c) 2013 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 "content/browser/renderer_host/media/video_capture_buffer_pool.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/stl_util.h"
#include "content/browser/gpu/browser_gpu_memory_buffer_manager.h"
#include "content/public/browser/browser_thread.h"
#include "ui/gfx/buffer_format_util.h"
namespace content {
const int VideoCaptureBufferPool::kInvalidId = -1;
// A simple holder of a memory-backed buffer and accessors to it.
class SimpleBufferHandle final : public VideoCaptureBufferPool::BufferHandle {
public:
SimpleBufferHandle(void* data,
size_t mapped_size,
base::SharedMemoryHandle handle)
: data_(data),
mapped_size_(mapped_size)
#if defined(OS_POSIX)
,
handle_(handle)
#endif
{
}
~SimpleBufferHandle() override {}
gfx::Size dimensions() const override {
NOTREACHED();
return gfx::Size();
}
size_t mapped_size() const override { return mapped_size_; }
void* data(int plane) override {
DCHECK_EQ(plane, 0);
return data_;
}
ClientBuffer AsClientBuffer(int plane) override {
NOTREACHED();
return nullptr;
}
#if defined(OS_POSIX)
base::FileDescriptor AsPlatformFile() override {
#if defined(OS_MACOSX)
return handle_.GetFileDescriptor();
#else
return handle_;
#endif // defined(OS_MACOSX)
}
#endif
private:
void* const data_;
const size_t mapped_size_;
#if defined(OS_POSIX)
const base::SharedMemoryHandle handle_;
#endif
};
// A holder of a GpuMemoryBuffer-backed buffer. Holds weak references to
// GpuMemoryBuffer-backed buffers and provides accessors to their data.
class GpuMemoryBufferBufferHandle final
: public VideoCaptureBufferPool::BufferHandle {
public:
GpuMemoryBufferBufferHandle(std::vector<void*>* data,
const gfx::Size& dimensions,
ScopedVector<gfx::GpuMemoryBuffer>* gmbs)
: data_(data), dimensions_(dimensions), gmbs_(gmbs) {
#ifndef NDEBUG
DCHECK_EQ(data->size(), gmbs->size());
for (const auto& gmb : *gmbs)
DCHECK(gmb);
for (const auto& data_ptr : *data)
DCHECK(data_ptr);
#endif
}
~GpuMemoryBufferBufferHandle() override {}
gfx::Size dimensions() const override { return dimensions_; }
size_t mapped_size() const override { return dimensions_.GetArea(); }
void* data(int plane) override {
DCHECK_GE(plane, media::VideoFrame::kYPlane);
DCHECK_LT(plane, static_cast<int>(data_->size()));
return data_->at(plane);
}
ClientBuffer AsClientBuffer(int plane) override {
DCHECK_GE(plane, media::VideoFrame::kYPlane);
DCHECK_LT(plane, static_cast<int>(gmbs_->size()));
return (*gmbs_)[plane]->AsClientBuffer();
}
#if defined(OS_POSIX)
base::FileDescriptor AsPlatformFile() override {
NOTREACHED();
return base::FileDescriptor();
}
#endif
private:
std::vector<void*>* data_;
const gfx::Size dimensions_;
ScopedVector<gfx::GpuMemoryBuffer>* const gmbs_;
};
// Tracker specifics for SharedMemory.
class VideoCaptureBufferPool::SharedMemTracker final : public Tracker {
public:
SharedMemTracker();
bool Init(media::VideoPixelFormat format,
media::VideoPixelStorage storage_type,
const gfx::Size& dimensions) override;
scoped_ptr<BufferHandle> GetBufferHandle() override {
return make_scoped_ptr(new SimpleBufferHandle(
shared_memory_.memory(), mapped_size_, shared_memory_.handle()));
}
bool ShareToProcess(base::ProcessHandle process_handle,
base::SharedMemoryHandle* new_handle) override {
return shared_memory_.ShareToProcess(process_handle, new_handle);
}
bool ShareToProcess2(int plane,
base::ProcessHandle process_handle,
gfx::GpuMemoryBufferHandle* new_handle) override {
NOTREACHED();
return false;
}
private:
// The memory created to be shared with renderer processes.
base::SharedMemory shared_memory_;
size_t mapped_size_;
};
// Tracker specifics for GpuMemoryBuffer. Owns GpuMemoryBuffers and its
// associated pixel dimensions.
class VideoCaptureBufferPool::GpuMemoryBufferTracker final : public Tracker {
public:
GpuMemoryBufferTracker();
bool Init(media::VideoPixelFormat format,
media::VideoPixelStorage storage_type,
const gfx::Size& dimensions) override;
~GpuMemoryBufferTracker() override;
scoped_ptr<BufferHandle> GetBufferHandle() override {
return make_scoped_ptr(new GpuMemoryBufferBufferHandle(
&data_, dimensions_, &gpu_memory_buffers_));
}
bool ShareToProcess(base::ProcessHandle process_handle,
base::SharedMemoryHandle* new_handle) override {
NOTREACHED();
return false;
}
bool ShareToProcess2(int plane,
base::ProcessHandle process_handle,
gfx::GpuMemoryBufferHandle* new_handle) override;
private:
std::vector<void*> data_;
gfx::Size dimensions_;
// Owned references to GpuMemoryBuffers.
ScopedVector<gfx::GpuMemoryBuffer> gpu_memory_buffers_;
};
VideoCaptureBufferPool::SharedMemTracker::SharedMemTracker() : Tracker() {}
bool VideoCaptureBufferPool::SharedMemTracker::Init(
media::VideoPixelFormat format,
media::VideoPixelStorage storage_type,
const gfx::Size& dimensions) {
DVLOG(2) << "allocating ShMem of " << dimensions.ToString();
set_pixel_format(format);
set_storage_type(storage_type);
// |dimensions| can be 0x0 for trackers that do not require memory backing.
set_pixel_count(dimensions.GetArea());
mapped_size_ =
media::VideoCaptureFormat(dimensions, 0.0f, format, storage_type)
.ImageAllocationSize();
if (!mapped_size_)
return true;
return shared_memory_.CreateAndMapAnonymous(mapped_size_);
}
VideoCaptureBufferPool::GpuMemoryBufferTracker::GpuMemoryBufferTracker()
: Tracker() {
}
VideoCaptureBufferPool::GpuMemoryBufferTracker::~GpuMemoryBufferTracker() {
for (const auto& gmb : gpu_memory_buffers_)
gmb->Unmap();
}
bool VideoCaptureBufferPool::GpuMemoryBufferTracker::Init(
media::VideoPixelFormat format,
media::VideoPixelStorage storage_type,
const gfx::Size& dimensions) {
DVLOG(2) << "allocating GMB for " << dimensions.ToString();
// BrowserGpuMemoryBufferManager::current() may not be accessed on IO Thread.
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(BrowserGpuMemoryBufferManager::current());
// This class is only expected to be called with I420 buffer requests at this
// point.
DCHECK_EQ(format, media::PIXEL_FORMAT_I420);
set_pixel_format(format);
set_storage_type(storage_type);
set_pixel_count(dimensions.GetArea());
// |dimensions| can be 0x0 for trackers that do not require memory backing.
if (dimensions.GetArea() == 0u)
return true;
dimensions_ = dimensions;
const media::VideoPixelFormat video_format = media::PIXEL_FORMAT_I420;
const size_t num_planes = media::VideoFrame::NumPlanes(video_format);
for (size_t i = 0; i < num_planes; ++i) {
const gfx::Size& size =
media::VideoFrame::PlaneSize(video_format, i, dimensions);
gpu_memory_buffers_.push_back(
BrowserGpuMemoryBufferManager::current()->AllocateGpuMemoryBuffer(
size,
gfx::BufferFormat::R_8,
gfx::BufferUsage::MAP));
DLOG_IF(ERROR, !gpu_memory_buffers_[i]) << "Allocating GpuMemoryBuffer";
if (!gpu_memory_buffers_[i])
return false;
void* temp_data = nullptr;
gpu_memory_buffers_[i]->Map(&temp_data);
DCHECK(temp_data);
data_.push_back(temp_data);
}
return true;
}
bool VideoCaptureBufferPool::GpuMemoryBufferTracker::ShareToProcess2(
int plane,
base::ProcessHandle process_handle,
gfx::GpuMemoryBufferHandle* new_handle) {
DCHECK_LE(plane, static_cast<int>(gpu_memory_buffers_.size()));
const auto& current_gmb_handle = gpu_memory_buffers_[plane]->GetHandle();
switch (current_gmb_handle.type) {
case gfx::EMPTY_BUFFER:
NOTREACHED();
return false;
case gfx::SHARED_MEMORY_BUFFER: {
DCHECK(base::SharedMemory::IsHandleValid(current_gmb_handle.handle));
base::SharedMemory shared_memory(
base::SharedMemory::DuplicateHandle(current_gmb_handle.handle),
false);
shared_memory.ShareToProcess(process_handle, &new_handle->handle);
DCHECK(base::SharedMemory::IsHandleValid(new_handle->handle));
new_handle->type = gfx::SHARED_MEMORY_BUFFER;
return true;
}
case gfx::IO_SURFACE_BUFFER:
case gfx::SURFACE_TEXTURE_BUFFER:
case gfx::OZONE_NATIVE_PIXMAP:
*new_handle = current_gmb_handle;
return true;
}
NOTREACHED();
return true;
}
// static
scoped_ptr<VideoCaptureBufferPool::Tracker>
VideoCaptureBufferPool::Tracker::CreateTracker(bool use_gmb) {
if (!use_gmb)
return make_scoped_ptr(new SharedMemTracker());
else
return make_scoped_ptr(new GpuMemoryBufferTracker());
}
VideoCaptureBufferPool::Tracker::~Tracker() {}
VideoCaptureBufferPool::VideoCaptureBufferPool(int count)
: count_(count),
next_buffer_id_(0) {
DCHECK_GT(count, 0);
}
VideoCaptureBufferPool::~VideoCaptureBufferPool() {
STLDeleteValues(&trackers_);
}
bool VideoCaptureBufferPool::ShareToProcess(
int buffer_id,
base::ProcessHandle process_handle,
base::SharedMemoryHandle* new_handle) {
base::AutoLock lock(lock_);
Tracker* tracker = GetTracker(buffer_id);
if (!tracker) {
NOTREACHED() << "Invalid buffer_id.";
return false;
}
if (tracker->ShareToProcess(process_handle, new_handle))
return true;
DPLOG(ERROR) << "Error mapping memory";
return false;
}
bool VideoCaptureBufferPool::ShareToProcess2(
int buffer_id,
int plane,
base::ProcessHandle process_handle,
gfx::GpuMemoryBufferHandle* new_handle) {
base::AutoLock lock(lock_);
Tracker* tracker = GetTracker(buffer_id);
if (!tracker) {
NOTREACHED() << "Invalid buffer_id.";
return false;
}
if (tracker->ShareToProcess2(plane, process_handle, new_handle))
return true;
DPLOG(ERROR) << "Error mapping memory";
return false;
}
scoped_ptr<VideoCaptureBufferPool::BufferHandle>
VideoCaptureBufferPool::GetBufferHandle(int buffer_id) {
base::AutoLock lock(lock_);
Tracker* tracker = GetTracker(buffer_id);
if (!tracker) {
NOTREACHED() << "Invalid buffer_id.";
return scoped_ptr<BufferHandle>();
}
DCHECK(tracker->held_by_producer());
return tracker->GetBufferHandle();
}
int VideoCaptureBufferPool::ReserveForProducer(
media::VideoPixelFormat format,
media::VideoPixelStorage storage,
const gfx::Size& dimensions,
int* buffer_id_to_drop) {
base::AutoLock lock(lock_);
return ReserveForProducerInternal(format, storage, dimensions,
buffer_id_to_drop);
}
void VideoCaptureBufferPool::RelinquishProducerReservation(int buffer_id) {
base::AutoLock lock(lock_);
Tracker* tracker = GetTracker(buffer_id);
if (!tracker) {
NOTREACHED() << "Invalid buffer_id.";
return;
}
DCHECK(tracker->held_by_producer());
tracker->set_held_by_producer(false);
}
void VideoCaptureBufferPool::HoldForConsumers(
int buffer_id,
int num_clients) {
base::AutoLock lock(lock_);
Tracker* tracker = GetTracker(buffer_id);
if (!tracker) {
NOTREACHED() << "Invalid buffer_id.";
return;
}
DCHECK(tracker->held_by_producer());
DCHECK(!tracker->consumer_hold_count());
tracker->set_consumer_hold_count(num_clients);
// Note: |held_by_producer()| will stay true until
// RelinquishProducerReservation() (usually called by destructor of the object
// wrapping this tracker, e.g. a media::VideoFrame).
}
void VideoCaptureBufferPool::RelinquishConsumerHold(int buffer_id,
int num_clients) {
base::AutoLock lock(lock_);
Tracker* tracker = GetTracker(buffer_id);
if (!tracker) {
NOTREACHED() << "Invalid buffer_id.";
return;
}
DCHECK_GE(tracker->consumer_hold_count(), num_clients);
tracker->set_consumer_hold_count(tracker->consumer_hold_count() -
num_clients);
}
double VideoCaptureBufferPool::GetBufferPoolUtilization() const {
base::AutoLock lock(lock_);
int num_buffers_held = 0;
for (const auto& entry : trackers_) {
Tracker* const tracker = entry.second;
if (tracker->held_by_producer() || tracker->consumer_hold_count() > 0)
++num_buffers_held;
}
return static_cast<double>(num_buffers_held) / count_;
}
int VideoCaptureBufferPool::ReserveForProducerInternal(
media::VideoPixelFormat pixel_format,
media::VideoPixelStorage storage_type,
const gfx::Size& dimensions,
int* buffer_id_to_drop) {
lock_.AssertAcquired();
*buffer_id_to_drop = kInvalidId;
const size_t size_in_pixels = dimensions.GetArea();
// Look for a tracker that's allocated, big enough, and not in use. Track the
// largest one that's not big enough, in case we have to reallocate a tracker.
*buffer_id_to_drop = kInvalidId;
size_t largest_size_in_pixels = 0;
TrackerMap::iterator tracker_to_drop = trackers_.end();
for (TrackerMap::iterator it = trackers_.begin(); it != trackers_.end();
++it) {
Tracker* const tracker = it->second;
if (!tracker->consumer_hold_count() && !tracker->held_by_producer()) {
if (tracker->pixel_count() >= size_in_pixels &&
(tracker->pixel_format() == pixel_format) &&
(tracker->storage_type() == storage_type)) {
// Existing tracker is big enough and has correct format. Reuse it.
tracker->set_held_by_producer(true);
return it->first;
}
if (tracker->pixel_count() > largest_size_in_pixels) {
largest_size_in_pixels = tracker->pixel_count();
tracker_to_drop = it;
}
}
}
// Preferably grow the pool by creating a new tracker. If we're at maximum
// size, then reallocate by deleting an existing one instead.
if (trackers_.size() == static_cast<size_t>(count_)) {
if (tracker_to_drop == trackers_.end()) {
// We're out of space, and can't find an unused tracker to reallocate.
return kInvalidId;
}
*buffer_id_to_drop = tracker_to_drop->first;
delete tracker_to_drop->second;
trackers_.erase(tracker_to_drop);
}
// Create the new tracker.
const int buffer_id = next_buffer_id_++;
scoped_ptr<Tracker> tracker = Tracker::CreateTracker(
storage_type == media::PIXEL_STORAGE_GPUMEMORYBUFFER);
if (!tracker->Init(pixel_format, storage_type, dimensions)) {
DLOG(ERROR) << "Error initializing Tracker";
return kInvalidId;
}
tracker->set_held_by_producer(true);
trackers_[buffer_id] = tracker.release();
return buffer_id;
}
VideoCaptureBufferPool::Tracker* VideoCaptureBufferPool::GetTracker(
int buffer_id) {
TrackerMap::const_iterator it = trackers_.find(buffer_id);
return (it == trackers_.end()) ? NULL : it->second;
}
} // namespace content