Commit 660e3c77 authored by Alexandre Courbot's avatar Alexandre Courbot Committed by Commit Bot

media/gpu/v4l2: add queue management classes

Add the V4L2Queue class, that manages buffers allocations and queuing.
Users of this class don't have to manage the set of free buffers
themselves, which will help factorizing a considerable amount of code
and reduce the potential for bugs.

Users of V4L2Queue can obtain a unique reference to a free buffer that
they can prepare and queue. Dequeued buffers come in the form of a
refcounted buffer instance, which puts the V4L2 buffer back into the
list of free buffer as it is destroyed. This greatly simplifies V4L2
buffers lifecycle, which is now managed by short functions which
invariants can be precisely enforced.

BUG=792790
TEST=Make sure VDA test was compiling and running

Change-Id: I7b4724fbc444c4cd8ed077617a1aaadcbe4c8011
Reviewed-on: https://chromium-review.googlesource.com/1170706
Commit-Queue: Alexandre Courbot <acourbot@chromium.org>
Reviewed-by: default avatarPawel Osciak <posciak@chromium.org>
Cr-Commit-Position: refs/heads/master@{#595014}
parent 084a6653
...@@ -7,11 +7,14 @@ ...@@ -7,11 +7,14 @@
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include <string.h> #include <string.h>
#include <sys/mman.h>
#include <sstream> #include <sstream>
#include "base/bind.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_types.h" #include "media/base/video_types.h"
#include "media/gpu/v4l2/generic_v4l2_device.h" #include "media/gpu/v4l2/generic_v4l2_device.h"
#if defined(ARCH_CPU_ARMEL) #if defined(ARCH_CPU_ARMEL)
...@@ -20,13 +23,774 @@ ...@@ -20,13 +23,774 @@
#define DVLOGF(level) DVLOG(level) << __func__ << "(): " #define DVLOGF(level) DVLOG(level) << __func__ << "(): "
#define VLOGF(level) VLOG(level) << __func__ << "(): " #define VLOGF(level) VLOG(level) << __func__ << "(): "
#define VPLOGF(level) VPLOG(level) << __func__ << "(): "
namespace media { namespace media {
// Class used to store the state of a buffer that should persist between
// reference creations. This includes:
// * Result of initial VIDIOC_QUERYBUF ioctl,
// * Plane mappings.
//
// Also provides helper functions.
class V4L2Buffer {
public:
static std::unique_ptr<V4L2Buffer> Create(scoped_refptr<V4L2Device> device,
enum v4l2_buf_type type,
enum v4l2_memory memory,
size_t buffer_id);
~V4L2Buffer();
void* GetPlaneMapping(const size_t plane);
const struct v4l2_buffer* v4l2_buffer() const { return &v4l2_buffer_; }
private:
V4L2Buffer(scoped_refptr<V4L2Device> device,
enum v4l2_buf_type type,
enum v4l2_memory memory,
size_t buffer_id);
bool Query();
scoped_refptr<V4L2Device> device_;
std::vector<void*> plane_mappings_;
// V4L2 data as queried by QUERYBUF.
struct v4l2_buffer v4l2_buffer_ = {};
struct v4l2_plane v4l2_planes_[VIDEO_MAX_PLANES] = {{}};
DISALLOW_COPY_AND_ASSIGN(V4L2Buffer);
};
std::unique_ptr<V4L2Buffer> V4L2Buffer::Create(scoped_refptr<V4L2Device> device,
enum v4l2_buf_type type,
enum v4l2_memory memory,
size_t buffer_id) {
// Not using std::make_unique because constructor is private.
std::unique_ptr<V4L2Buffer> buffer(
new V4L2Buffer(device, type, memory, buffer_id));
if (!buffer->Query())
return nullptr;
return buffer;
}
V4L2Buffer::V4L2Buffer(scoped_refptr<V4L2Device> device,
enum v4l2_buf_type type,
enum v4l2_memory memory,
size_t buffer_id)
: device_(device), plane_mappings_(VIDEO_MAX_PLANES) {
DCHECK(V4L2_TYPE_IS_MULTIPLANAR(type));
v4l2_buffer_.m.planes = v4l2_planes_;
v4l2_buffer_.length = VIDEO_MAX_PLANES;
v4l2_buffer_.index = buffer_id;
v4l2_buffer_.type = type;
v4l2_buffer_.memory = memory;
}
V4L2Buffer::~V4L2Buffer() {
if (v4l2_buffer_.memory == V4L2_MEMORY_MMAP) {
for (size_t i = 0; i < plane_mappings_.size(); i++)
if (plane_mappings_[i] != nullptr)
device_->Munmap(plane_mappings_[i], v4l2_buffer_.m.planes[i].length);
}
}
bool V4L2Buffer::Query() {
int ret = device_->Ioctl(VIDIOC_QUERYBUF, &v4l2_buffer_);
if (ret) {
VPLOGF(1) << "VIDIOC_QUERYBUF failed: ";
return false;
}
plane_mappings_.resize(v4l2_buffer_.length);
return true;
}
void* V4L2Buffer::GetPlaneMapping(const size_t plane) {
if (plane >= plane_mappings_.size()) {
VLOGF(1) << "Invalid plane " << plane << " requested.";
return nullptr;
}
void* p = plane_mappings_[plane];
if (p)
return p;
// Do this check here to avoid repeating it after a buffer has been
// successfully mapped (we know we are of MMAP type by then).
if (v4l2_buffer_.memory != V4L2_MEMORY_MMAP) {
VLOGF(1) << "Cannot create mapping on non-MMAP buffer";
return nullptr;
}
p = device_->Mmap(NULL, v4l2_buffer_.m.planes[plane].length,
PROT_READ | PROT_WRITE, MAP_SHARED,
v4l2_buffer_.m.planes[plane].m.mem_offset);
if (p == MAP_FAILED) {
VPLOGF(1) << "mmap() failed: ";
return nullptr;
}
plane_mappings_[plane] = p;
return p;
}
// Module-private class that let users query/write V4L2 buffer information.
// It also makes some private V4L2Queue methods available to this module only.
class V4L2BufferQueueProxy {
public:
V4L2BufferQueueProxy(const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue);
void ReturnBuffer() { queue_->ReturnBuffer(BufferId()); }
bool QueueBuffer();
void* GetPlaneMapping(const size_t plane) {
return queue_->buffers_[BufferId()]->GetPlaneMapping(plane);
}
// Data from the buffer, that users can query and/or write.
struct v4l2_buffer v4l2_buffer_;
struct v4l2_plane v4l2_planes_[VIDEO_MAX_PLANES];
private:
size_t BufferId() const { return v4l2_buffer_.index; }
// The queue must be kept alive as long as the reference to the buffer exists.
scoped_refptr<V4L2Queue> queue_;
DISALLOW_COPY_AND_ASSIGN(V4L2BufferQueueProxy);
};
V4L2BufferQueueProxy::V4L2BufferQueueProxy(
const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue)
: queue_(std::move(queue)) {
DCHECK(V4L2_TYPE_IS_MULTIPLANAR(v4l2_buffer->type));
DCHECK_LE(v4l2_buffer->length, static_cast<uint32_t>(VIDEO_MAX_PLANES));
memcpy(&v4l2_buffer_, v4l2_buffer, sizeof(v4l2_buffer_));
memcpy(v4l2_planes_, v4l2_buffer->m.planes,
sizeof(struct v4l2_plane) * v4l2_buffer->length);
v4l2_buffer_.m.planes = v4l2_planes_;
}
bool V4L2BufferQueueProxy::QueueBuffer() {
bool queued = queue_->QueueBuffer(&v4l2_buffer_);
// If an error occurred during queueing, then the buffer must be made
// available again.
if (!queued)
ReturnBuffer();
return queued;
}
V4L2WritableBufferRef::V4L2WritableBufferRef() {
// Invalid buffers can be created from any thread.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
V4L2WritableBufferRef::V4L2WritableBufferRef(
const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue)
: buffer_data_(std::make_unique<V4L2BufferQueueProxy>(v4l2_buffer,
std::move(queue))) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
V4L2WritableBufferRef::V4L2WritableBufferRef(V4L2WritableBufferRef&& other)
: buffer_data_(std::move(other.buffer_data_)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(other.sequence_checker_);
}
V4L2WritableBufferRef::~V4L2WritableBufferRef() {
// Only valid references should be sequence-checked
if (buffer_data_) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
buffer_data_->ReturnBuffer();
}
}
V4L2WritableBufferRef& V4L2WritableBufferRef::operator=(
V4L2WritableBufferRef&& other) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_CALLED_ON_VALID_SEQUENCE(other.sequence_checker_);
if (this == &other)
return *this;
if (IsValid())
buffer_data_->ReturnBuffer();
buffer_data_ = std::move(other.buffer_data_);
return *this;
}
bool V4L2WritableBufferRef::IsValid() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return buffer_data_ != nullptr;
}
enum v4l2_memory V4L2WritableBufferRef::Memory() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
return static_cast<enum v4l2_memory>(buffer_data_->v4l2_buffer_.memory);
}
bool V4L2WritableBufferRef::DoQueue() && {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
bool queued = buffer_data_->QueueBuffer();
// Clear our own reference.
buffer_data_.reset();
return queued;
}
bool V4L2WritableBufferRef::QueueMMap() && {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
// Move ourselves so our data gets freed no matter when we return
V4L2WritableBufferRef self(std::move(*this));
if (self.Memory() != V4L2_MEMORY_MMAP) {
VLOGF(1) << "Called on invalid buffer type!";
return false;
}
return std::move(self).DoQueue();
}
bool V4L2WritableBufferRef::QueueUserPtr(const std::vector<void*>& ptrs) && {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
// Move ourselves so our data gets freed no matter when we return
V4L2WritableBufferRef self(std::move(*this));
if (self.Memory() != V4L2_MEMORY_USERPTR) {
VLOGF(1) << "Called on invalid buffer type!";
return false;
}
if (ptrs.size() != self.PlanesCount()) {
VLOGF(1) << "Provided " << ptrs.size() << " pointers while we require "
<< self.buffer_data_->v4l2_buffer_.length << ".";
return false;
}
for (size_t i = 0; i < ptrs.size(); i++)
self.buffer_data_->v4l2_buffer_.m.planes[i].m.userptr =
reinterpret_cast<unsigned long>(ptrs[i]);
return std::move(self).DoQueue();
}
bool V4L2WritableBufferRef::QueueDMABuf(
const std::vector<base::ScopedFD>& fds) && {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
// Move ourselves so our data gets freed no matter when we return
V4L2WritableBufferRef self(std::move(*this));
if (self.Memory() != V4L2_MEMORY_DMABUF) {
VLOGF(1) << "Called on invalid buffer type!";
return false;
}
if (fds.size() != self.PlanesCount()) {
VLOGF(1) << "Provided " << fds.size() << " FDs while we require "
<< self.buffer_data_->v4l2_buffer_.length << ".";
return false;
}
for (size_t i = 0; i < fds.size(); i++)
self.buffer_data_->v4l2_buffer_.m.planes[i].m.fd = fds[i].get();
return std::move(self).DoQueue();
}
size_t V4L2WritableBufferRef::PlanesCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
return buffer_data_->v4l2_buffer_.length;
}
size_t V4L2WritableBufferRef::GetPlaneSize(const size_t plane) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
if (plane >= PlanesCount()) {
VLOGF(1) << "Invalid plane " << plane << " requested.";
return 0;
}
return buffer_data_->v4l2_buffer_.m.planes[plane].length;
}
void* V4L2WritableBufferRef::GetPlaneMapping(const size_t plane) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
return buffer_data_->GetPlaneMapping(plane);
}
void V4L2WritableBufferRef::SetTimeStamp(const struct timeval& timestamp) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
buffer_data_->v4l2_buffer_.timestamp = timestamp;
}
const struct timeval& V4L2WritableBufferRef::GetTimeStamp() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
return buffer_data_->v4l2_buffer_.timestamp;
}
void V4L2WritableBufferRef::SetPlaneBytesUsed(const size_t plane,
const size_t bytes_used) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
if (plane >= PlanesCount()) {
VLOGF(1) << "Invalid plane " << plane << " requested.";
return;
}
if (bytes_used >= GetPlaneSize(plane)) {
VLOGF(1) << "Set bytes used " << bytes_used << " larger than plane size "
<< GetPlaneSize(plane) << ".";
return;
}
buffer_data_->v4l2_buffer_.m.planes[plane].bytesused = bytes_used;
}
size_t V4L2WritableBufferRef::GetPlaneBytesUsed(const size_t plane) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
if (plane >= PlanesCount()) {
VLOGF(1) << "Invalid plane " << plane << " requested.";
return 0;
}
return buffer_data_->v4l2_buffer_.m.planes[plane].bytesused;
}
size_t V4L2WritableBufferRef::BufferId() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsValid());
return buffer_data_->v4l2_buffer_.index;
}
V4L2ReadableBuffer::V4L2ReadableBuffer(const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue)
: buffer_data_(std::make_unique<V4L2BufferQueueProxy>(v4l2_buffer,
std::move(queue))) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
V4L2ReadableBuffer::~V4L2ReadableBuffer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer_data_);
buffer_data_->ReturnBuffer();
}
bool V4L2ReadableBuffer::IsLast() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer_data_);
return buffer_data_->v4l2_buffer_.flags & V4L2_BUF_FLAG_LAST;
}
struct timeval V4L2ReadableBuffer::GetTimeStamp() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer_data_);
return buffer_data_->v4l2_buffer_.timestamp;
}
size_t V4L2ReadableBuffer::PlanesCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer_data_);
return buffer_data_->v4l2_buffer_.length;
}
size_t V4L2ReadableBuffer::GetPlaneBytesUsed(const size_t plane) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer_data_);
if (plane >= PlanesCount()) {
VLOGF(1) << "Invalid plane " << plane << " requested.";
return 0;
}
return buffer_data_->v4l2_planes_[plane].bytesused;
}
size_t V4L2ReadableBuffer::BufferId() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer_data_);
return buffer_data_->v4l2_buffer_.index;
}
// This class is used to expose buffer reference classes constructors to
// this module. This is to ensure that nobody else can create buffer references.
class V4L2BufferRefFactory {
public:
static V4L2WritableBufferRef CreateWritableRef(
const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue) {
return V4L2WritableBufferRef(v4l2_buffer, std::move(queue));
}
static V4L2ReadableBufferRef CreateReadableRef(
const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue) {
return new V4L2ReadableBuffer(v4l2_buffer, std::move(queue));
}
};
V4L2Queue::V4L2Queue(scoped_refptr<V4L2Device> dev,
enum v4l2_buf_type type,
base::OnceClosure destroy_cb)
: type_(type), device_(dev), destroy_cb_(std::move(destroy_cb)) {
// TODO(acourbot): fix clients - the constructor should be called on the same
// sequence as the rest.
DETACH_FROM_SEQUENCE(sequence_checker_);
}
V4L2Queue::~V4L2Queue() {
// TODO(acourbot): we do this prior to checking the sequence because we
// tolerate queues to be destroyed in the wrong thread if they are properly
// cleaned up. But ultimately clients should be fixed.
if (!is_streaming_ && buffers_.empty()) {
std::move(destroy_cb_).Run();
return;
}
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_streaming_) {
VLOGF(1) << "Queue is still streaming, trying to stop it...";
Streamoff();
}
DCHECK(queued_buffers_.empty());
DCHECK(free_buffers_.empty());
if (!buffers_.empty()) {
VLOGF(1) << "Buffers are still allocated, trying to deallocate them...";
DeallocateBuffers();
}
std::move(destroy_cb_).Run();
}
size_t V4L2Queue::AllocateBuffers(size_t count, enum v4l2_memory memory) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(free_buffers_.size(), 0u);
DCHECK_EQ(queued_buffers_.size(), 0u);
if (IsStreaming()) {
VLOGF(1) << "Cannot allocate buffers while streaming.";
return 0;
}
if (buffers_.size() != 0) {
VLOGF(1) << "Cannot allocate new buffers while others are still allocated.";
return 0;
}
if (count == 0) {
VLOGF(1) << "Attempting to allocate 0 buffers.";
return 0;
}
struct v4l2_requestbuffers reqbufs = {};
reqbufs.count = count;
reqbufs.type = type_;
reqbufs.memory = memory;
DVLOGF(3) << "queue " << type_ << ": requesting " << count << " buffers.";
int ret = device_->Ioctl(VIDIOC_REQBUFS, &reqbufs);
if (ret) {
VPLOGF(1) << "VIDIOC_REQBUFS failed: ";
return 0;
}
DVLOGF(3) << "queue " << type_ << ": got " << reqbufs.count << " buffers.";
memory_ = memory;
// Now query all buffer information.
for (size_t i = 0; i < reqbufs.count; i++) {
auto buffer = V4L2Buffer::Create(device_, type_, memory_, i);
if (!buffer) {
DeallocateBuffers();
return 0;
}
buffers_.emplace_back(std::move(buffer));
ReturnBuffer(i);
}
DCHECK_EQ(free_buffers_.size(), buffers_.size());
DCHECK_EQ(queued_buffers_.size(), 0u);
return buffers_.size();
}
bool V4L2Queue::DeallocateBuffers() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsStreaming()) {
VLOGF(1) << "Cannot deallocate buffers while streaming.";
return false;
}
if (buffers_.size() != free_buffers_.size()) {
VPLOGF(1) << "Trying to deallocate buffers while some are still in use!";
return false;
}
if (buffers_.size() == 0)
return true;
// Free all buffers.
struct v4l2_requestbuffers reqbufs = {};
reqbufs.count = 0;
reqbufs.type = type_;
reqbufs.memory = memory_;
int ret = device_->Ioctl(VIDIOC_REQBUFS, &reqbufs);
if (ret) {
VPLOGF(1) << "VIDIOC_REQBUFS failed: ";
return false;
}
buffers_.clear();
free_buffers_.clear();
DCHECK_EQ(free_buffers_.size(), 0u);
DCHECK_EQ(queued_buffers_.size(), 0u);
return true;
}
V4L2WritableBufferRef V4L2Queue::GetFreeBuffer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = free_buffers_.begin();
if (iter == free_buffers_.end()) {
VLOGF(3) << "No free buffer available!";
return V4L2WritableBufferRef();
}
size_t buffer_id = *iter;
free_buffers_.erase(buffer_id);
return V4L2BufferRefFactory::CreateWritableRef(
buffers_[buffer_id]->v4l2_buffer(), this);
}
void V4L2Queue::ReturnBuffer(size_t buffer_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto inserted = free_buffers_.emplace(buffer_id);
DCHECK_EQ(inserted.second, true);
}
bool V4L2Queue::QueueBuffer(struct v4l2_buffer* v4l2_buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int ret = device_->Ioctl(VIDIOC_QBUF, v4l2_buffer);
if (ret) {
VPLOGF(1) << "VIDIOC_QBUF failed: ";
return false;
}
auto inserted = queued_buffers_.emplace(v4l2_buffer->index);
DCHECK_EQ(inserted.second, true);
return true;
}
std::pair<bool, V4L2ReadableBufferRef> V4L2Queue::DequeueBuffer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// No need to dequeue if no buffers queued.
if (QueuedBuffersCount() == 0)
return std::make_pair(true, nullptr);
if (!IsStreaming()) {
VLOGF(1) << "Attempting to dequeue a buffer while not streaming.";
return std::make_pair(true, nullptr);
}
struct v4l2_buffer v4l2_buffer = {};
struct v4l2_plane planes[VIDEO_MAX_PLANES] = {{}};
v4l2_buffer.type = type_;
v4l2_buffer.memory = memory_;
v4l2_buffer.m.planes = planes;
// TODO(acourbot): use actual number of planes.
v4l2_buffer.length = VIDEO_MAX_PLANES;
int ret = device_->Ioctl(VIDIOC_DQBUF, &v4l2_buffer);
if (ret) {
// TODO(acourbot): we should not have to check for EPIPE as codec clients
// should not call this method after the last buffer is dequeued.
switch (errno) {
case EAGAIN:
case EPIPE:
// This is not an error but won't provide a buffer either.
return std::make_pair(true, nullptr);
default:
VPLOGF(1) << "VIDIOC_DQBUF failed: ";
return std::make_pair(false, nullptr);
}
}
auto it = queued_buffers_.find(v4l2_buffer.index);
DCHECK(it != queued_buffers_.end());
queued_buffers_.erase(*it);
return std::make_pair(
true, V4L2BufferRefFactory::CreateReadableRef(&v4l2_buffer, this));
}
bool V4L2Queue::IsStreaming() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_streaming_;
}
bool V4L2Queue::Streamon() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (is_streaming_)
return true;
int arg = static_cast<int>(type_);
int ret = device_->Ioctl(VIDIOC_STREAMON, &arg);
if (ret) {
VPLOGF(1) << "VIDIOC_STREAMON failed: ";
return false;
}
is_streaming_ = true;
return true;
}
bool V4L2Queue::Streamoff() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// We do not check the value of IsStreaming(), because we may have queued
// buffers to the queue and wish to get them back - in such as case, we may
// need to do a VIDIOC_STREAMOFF on a stopped queue.
int arg = static_cast<int>(type_);
int ret = device_->Ioctl(VIDIOC_STREAMOFF, &arg);
if (ret) {
VPLOGF(1) << "VIDIOC_STREAMOFF failed: ";
return false;
}
for (const auto& buffer_id : queued_buffers_)
ReturnBuffer(buffer_id);
queued_buffers_.clear();
is_streaming_ = false;
return true;
}
size_t V4L2Queue::AllocatedBuffersCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return buffers_.size();
}
size_t V4L2Queue::FreeBuffersCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return free_buffers_.size();
}
size_t V4L2Queue::QueuedBuffersCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return queued_buffers_.size();
}
// This class is used to expose V4L2Queue's constructor to this module. This is
// to ensure that nobody else can create instances of it.
class V4L2QueueFactory {
public:
static scoped_refptr<V4L2Queue> CreateQueue(scoped_refptr<V4L2Device> dev,
enum v4l2_buf_type type,
base::OnceClosure destroy_cb) {
return new V4L2Queue(std::move(dev), type, std::move(destroy_cb));
}
};
V4L2Device::V4L2Device() {} V4L2Device::V4L2Device() {}
V4L2Device::~V4L2Device() {} V4L2Device::~V4L2Device() {}
scoped_refptr<V4L2Queue> V4L2Device::GetQueue(enum v4l2_buf_type type) {
switch (type) {
// Supported queue types.
case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
break;
default:
VLOGF(1) << "Unsupported V4L2 queue type: " << type;
return nullptr;
}
// TODO(acourbot): we should instead query the device for available queues,
// and allocate them accordingly. This will do for now though.
auto it = queues_.find(type);
if (it != queues_.end())
return scoped_refptr<V4L2Queue>(it->second);
scoped_refptr<V4L2Queue> queue = V4L2QueueFactory::CreateQueue(
this, type,
media::BindToCurrentLoop(
base::Bind(&V4L2Device::OnQueueDestroyed, this, type)));
queues_[type] = queue.get();
return queue;
}
void V4L2Device::OnQueueDestroyed(v4l2_buf_type buf_type) {
auto it = queues_.find(buf_type);
DCHECK(it != queues_.end());
queues_.erase(it);
}
// static // static
scoped_refptr<V4L2Device> V4L2Device::Create() { scoped_refptr<V4L2Device> V4L2Device::Create() {
VLOGF(2); VLOGF(2);
......
...@@ -12,8 +12,11 @@ ...@@ -12,8 +12,11 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <vector>
#include <linux/videodev2.h> #include <linux/videodev2.h>
#include "base/containers/flat_map.h"
#include "base/files/scoped_file.h" #include "base/files/scoped_file.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "media/base/video_decoder_config.h" #include "media/base/video_decoder_config.h"
...@@ -34,6 +37,244 @@ ...@@ -34,6 +37,244 @@
namespace media { namespace media {
class V4L2Queue;
class V4L2BufferQueueProxy;
// A unique reference to a buffer for clients to prepare and submit.
//
// Clients can prepare a buffer for queuing using the methods of this class, and
// then either queue it using the Queue() method corresponding to the memory
// type of the buffer, or drop the reference to make the buffer available again.
class MEDIA_GPU_EXPORT V4L2WritableBufferRef {
public:
// Default constructor, creates invalid buffer reference.
V4L2WritableBufferRef();
V4L2WritableBufferRef(V4L2WritableBufferRef&& other);
V4L2WritableBufferRef& operator=(V4L2WritableBufferRef&& other);
// Returns true if the reference points to a valid buffer.
bool IsValid() const;
// Return the memory type of the buffer. Useful to e.g. decide which Queue()
// method to use.
enum v4l2_memory Memory() const;
// Queue a MMAP buffer.
// If successful, true is returned and the reference to the buffer is dropped
// so this reference becomes invalid.
// In case of error, false is returned and the buffer is returned to the free
// list.
bool QueueMMap() &&;
// Queue a USERPTR buffer, assigning |ptrs| as pointer for each plane.
// The size of |ptrs| must be equal to the number of planes of this buffer.
// If successful, true is returned and the reference to the buffer is dropped
// so this reference becomes invalid.
// In case of error, false is returned and the buffer is returned to the free
// list.
bool QueueUserPtr(const std::vector<void*>& ptrs) &&;
// Queue a DMABUF buffer, assigning |fds| as file descriptors for each plane.
// The size of |fds| must be equal to the number of planes of this buffer.
// If successful, true is returned and the reference to the buffer is dropped
// so this reference becomes invalid.
// In case of error, false is returned and the buffer is returned to the free
// list.
bool QueueDMABuf(const std::vector<base::ScopedFD>& fds) &&;
// Returns the number of planes in this buffer.
size_t PlanesCount() const;
// Returns the size of the requested |plane|, in bytes.
size_t GetPlaneSize(const size_t plane) const;
// This method can only be used with MMAP buffers.
// It will return a pointer to the data of the |plane|th plane.
// In case of error (invalid plane index or mapping failed), a nullptr is
// returned.
void* GetPlaneMapping(const size_t plane);
// Set the timestamp field for this buffer.
void SetTimeStamp(const struct timeval& timestamp);
// Return the previously-set timestamp field for this buffer.
const struct timeval& GetTimeStamp() const;
// Set the number of bytes used for |plane|.
void SetPlaneBytesUsed(const size_t plane, const size_t bytes_used);
// Returns the previously-set number of bytes used for |plane|.
size_t GetPlaneBytesUsed(const size_t plane) const;
// Return the V4L2 buffer ID of the underlying buffer.
// TODO(acourbot) This is used for legacy clients but should be ultimately
// removed. See crbug/879971
size_t BufferId() const;
~V4L2WritableBufferRef();
private:
// Do the actual queue operation once the v4l2_buffer structure is properly
// filled.
bool DoQueue() &&;
V4L2WritableBufferRef(const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue);
friend class V4L2BufferRefFactory;
std::unique_ptr<V4L2BufferQueueProxy> buffer_data_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(V4L2WritableBufferRef);
};
// A reference to a read-only, dequeued buffer.
//
// Clients use this class to query the buffer state and content, and are
// guaranteed that the buffer will not be reused until all references are
// destroyed.
class MEDIA_GPU_EXPORT V4L2ReadableBuffer
: public base::RefCounted<V4L2ReadableBuffer> {
public:
// Returns whether the V4L2_BUF_FLAG_LAST flag is set for this buffer.
bool IsLast() const;
// Return the timestamp set by the driver on this buffer.
struct timeval GetTimeStamp() const;
// Returns the number of planes in this buffer.
size_t PlanesCount() const;
// Returns the number of bytes used for |plane|.
size_t GetPlaneBytesUsed(size_t plane) const;
// Return the V4L2 buffer ID of the underlying buffer.
// TODO(acourbot) This is used for legacy clients but should be ultimately
// removed. See crbug/879971
size_t BufferId() const;
private:
~V4L2ReadableBuffer();
V4L2ReadableBuffer(const struct v4l2_buffer* v4l2_buffer,
scoped_refptr<V4L2Queue> queue);
friend class V4L2BufferRefFactory;
std::unique_ptr<V4L2BufferQueueProxy> buffer_data_;
SEQUENCE_CHECKER(sequence_checker_);
friend class base::RefCounted<V4L2ReadableBuffer>;
DISALLOW_COPY_AND_ASSIGN(V4L2ReadableBuffer);
};
// Shortcut for naming consistency.
using V4L2ReadableBufferRef = scoped_refptr<V4L2ReadableBuffer>;
class V4L2Device;
class V4L2Buffer;
// Interface representing a specific queue of a |V4L2Device|. It provides free
// and queued buffer management that is commonly required by clients.
//
// Buffers managed by this class undergo the following cycle:
// 1) Allocated buffers are put into a free buffers pool, indicating that they
// are used neither by the client nor the hardware.
// 2) The client obtains a unique, writable reference to one of the free
// buffers in order to set its content and other parameters.
// 3) The client then queues the buffer obtained in 2), which invalidates its
// reference. The buffer is now prepared to be processed by the hardware.
// 4) Once the hardware is done with the buffer, it is ready to be dequeued by
// the client. The client obtains a read-only, counted reference to the
// buffer and can read its content and metadata, as well as making other
// references to it. The buffer will not be reused until all the references
// are dropped. Once this happens, the buffer goes back to the free list
// described in 1).
class MEDIA_GPU_EXPORT V4L2Queue
: public base::RefCountedThreadSafe<V4L2Queue> {
public:
// Allocate |count| buffers for the current format of this queue, with a
// specific |memory| allocation, and returns the number of buffers allocated
// or zero if an error occurred, or if references to any previously allocated
// buffers are still held by any clients.
//
// The number of allocated buffers may be larger than the number requested, so
// callers must always check the return value.
//
// Calling this method while buffers are still allocated results in an error.
size_t AllocateBuffers(size_t count,
enum v4l2_memory memory) WARN_UNUSED_RESULT;
// Deallocate all buffers previously allocated by |AllocateBuffers|. Any
// references to buffers previously allocated held by the client must be
// released, or this call will fail.
bool DeallocateBuffers();
// Return a unique pointer to a free buffer for the caller to prepare and
// submit, or an empty pointer if no buffer is currently free.
//
// If the caller discards the returned reference, the underlying buffer is
// made available to clients again.
V4L2WritableBufferRef GetFreeBuffer();
// Attempt to dequeue a buffer, and return a reference to it if one was
// available.
//
// The first element of the returned pair will be false if an error occurred,
// in which case the second element will be nullptr. If no error occurred,
// then the first element will be true and the second element will contain a
// reference to the dequeued buffer if one was available, or nullptr
// otherwise.
// Dequeued buffers will not be reused by the driver until all references to
// them are dropped.
std::pair<bool, V4L2ReadableBufferRef> DequeueBuffer();
// Returns true if this queue is currently streaming.
bool IsStreaming() const;
// If not currently streaming, starts streaming. Returns true if we started
// streaming, or were already streaming, or false if we were not streaming
// and an error occurred when attempting to start the stream. On failure, any
// previously-queued buffers will be dequeued without processing and made
// available to the client, while any buffers held by the client will remain
// unchanged and their ownership will remain with the client.
bool Streamon();
// If currently streaming, stops streaming. Also make all queued buffers
// available to the client again regardless of the streaming state.
// If an error occurred while attempting to stop streaming, then false is
// returned and queued buffers are left untouched since the V4L2 queue may
// still be using them.
bool Streamoff();
// Returns the number of buffers currently allocated for this queue.
size_t AllocatedBuffersCount() const;
// Returns the number of currently free buffers on this queue.
size_t FreeBuffersCount() const;
// Returns the number of buffers currently queued on this queue.
size_t QueuedBuffersCount() const;
private:
~V4L2Queue();
// Called when clients lose their reference to a buffer.
void ReturnBuffer(size_t buffer_id);
// Called when clients request a buffer to be queued.
bool QueueBuffer(struct v4l2_buffer* v4l2_buffer);
const enum v4l2_buf_type type_;
enum v4l2_memory memory_ = V4L2_MEMORY_MMAP;
bool is_streaming_ = false;
std::vector<std::unique_ptr<V4L2Buffer>> buffers_;
// Buffers that are available for client to get and submit.
// Buffers in this list are not referenced by anyone else than ourselves.
std::set<size_t> free_buffers_;
// Buffers that have been queued by the client, and not dequeued yet.
std::set<size_t> queued_buffers_;
scoped_refptr<V4L2Device> device_;
// Callback to call in this queue's destructor.
base::OnceClosure destroy_cb_;
V4L2Queue(scoped_refptr<V4L2Device> dev,
enum v4l2_buf_type type,
base::OnceClosure destroy_cb);
friend class V4L2QueueFactory;
friend class V4L2BufferQueueProxy;
friend class base::RefCountedThreadSafe<V4L2Queue>;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(V4L2Queue);
};
class MEDIA_GPU_EXPORT V4L2Device class MEDIA_GPU_EXPORT V4L2Device
: public base::RefCountedThreadSafe<V4L2Device> { : public base::RefCountedThreadSafe<V4L2Device> {
public: public:
...@@ -79,6 +320,10 @@ class MEDIA_GPU_EXPORT V4L2Device ...@@ -79,6 +320,10 @@ class MEDIA_GPU_EXPORT V4L2Device
// The device will be closed in the destructor. // The device will be closed in the destructor.
virtual bool Open(Type type, uint32_t v4l2_pixfmt) = 0; virtual bool Open(Type type, uint32_t v4l2_pixfmt) = 0;
// Returns the V4L2Queue corresponding to the requested |type|, or nullptr
// if the requested queue type is not supported.
scoped_refptr<V4L2Queue> GetQueue(enum v4l2_buf_type type);
// Parameters and return value are the same as for the standard ioctl() system // Parameters and return value are the same as for the standard ioctl() system
// call. // call.
virtual int Ioctl(int request, void* arg) = 0; virtual int Ioctl(int request, void* arg) = 0;
...@@ -208,6 +453,13 @@ class MEDIA_GPU_EXPORT V4L2Device ...@@ -208,6 +453,13 @@ class MEDIA_GPU_EXPORT V4L2Device
// Return true on success, false on error or if the particular implementation // Return true on success, false on error or if the particular implementation
// is not available. // is not available.
virtual bool Initialize() = 0; virtual bool Initialize() = 0;
// Associates a v4l2_buf_type to its queue.
base::flat_map<enum v4l2_buf_type, V4L2Queue*> queues_;
// Callback that is called upon a queue's destruction, to cleanup its pointer
// in queues_.
void OnQueueDestroyed(v4l2_buf_type buf_type);
}; };
} // namespace media } // namespace media
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment