Skip to content

Commit

Permalink
v4l2/stateless: Allocate input buffers
Browse files Browse the repository at this point in the history
The InputQueue owns buffers that pass compressed
data to the V4L2 driver. Allocate them and
configure them for use.

Bug: b:278935312
Change-Id: I4351c0074b955b00120fccc045827e5017874a0b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4854383
Reviewed-by: Nathan Hebert <nhebert@chromium.org>
Commit-Queue: Fritz Koenig <frkoenig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1210575}
  • Loading branch information
Fritz Koenig authored and Chromium LUCI CQ committed Oct 17, 2023
1 parent 427141f commit 458d76f
Show file tree
Hide file tree
Showing 5 changed files with 387 additions and 6 deletions.
203 changes: 200 additions & 3 deletions media/gpu/v4l2/stateless/device.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,68 @@ namespace media {

namespace {

// Helper functions for translating between V4L2 structs that should not
// be included in the header and external structs that are shared.
enum v4l2_buf_type BufferTypeToV4L2(BufferType type) {
if (type == BufferType::kCompressedData) {
return V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
} else if (type == BufferType::kRawFrames) {
return V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
}

NOTREACHED();
return V4L2_BUF_TYPE_PRIVATE;
}

enum v4l2_memory MemoryTypeToV4L2(MemoryType memory) {
switch (memory) {
case MemoryType::kMemoryMapped:
return V4L2_MEMORY_MMAP;
case MemoryType::kDmaBuf:
return V4L2_MEMORY_DMABUF;
case MemoryType::kInvalid:
NOTREACHED();
// V4L2_MEMORY_USERPTR is not used in our code.
return V4L2_MEMORY_USERPTR;
}
}

BufferType V4L2ToBufferType(unsigned int type) {
if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
return BufferType::kCompressedData;
} else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
return BufferType::kRawFrames;
}

NOTREACHED();
return BufferType::kInvalid;
}

MemoryType V4L2ToMemoryType(unsigned int memory) {
switch (memory) {
case V4L2_MEMORY_MMAP:
return MemoryType::kMemoryMapped;
case V4L2_MEMORY_DMABUF:
return MemoryType::kDmaBuf;
}

NOTREACHED();
return MemoryType::kInvalid;
}

Buffer V4L2BufferToBuffer(const struct v4l2_buffer& v4l2_buffer) {
const BufferType buffer_type = V4L2ToBufferType(v4l2_buffer.type);
const MemoryType memory_type = V4L2ToMemoryType(v4l2_buffer.memory);
Buffer buffer(buffer_type, memory_type, v4l2_buffer.index,
v4l2_buffer.length);
for (uint32_t plane = 0; plane < buffer.PlaneCount(); ++plane) {
buffer.SetupPlane(plane, v4l2_buffer.m.planes[plane].m.mem_offset,
v4l2_buffer.m.planes[plane].length);
}

return buffer;
}

using v4l2_enum_type = decltype(V4L2_PIX_FMT_H264);
// Correspondence from V4L2 codec described as a pixel format to a Control ID.
static const std::map<v4l2_enum_type, v4l2_enum_type>
Expand Down Expand Up @@ -97,6 +159,52 @@ VideoCodec V4L2PixFmtToVideoCodec(uint32_t pix_fmt) {

Device::Device() {}

Buffer::Buffer(BufferType buffer_type,
MemoryType memory_type,
uint32_t index,
uint32_t plane_count)
: buffer_type_(buffer_type), memory_type_(memory_type), index_(index) {
planes_.resize(plane_count);
}

void* Buffer::MappedAddress(uint32_t plane) const {
DCHECK(memory_type_ == MemoryType::kMemoryMapped);
return planes_[plane].mapped_address;
}

void Buffer::SetMappedAddress(uint32_t plane, void* address) {
DVLOGF(4) << plane << " : " << address;
DCHECK(memory_type_ == MemoryType::kMemoryMapped);
planes_[plane].mapped_address = address;
}

void Buffer::SetupPlane(uint32_t plane, size_t offset, size_t size) {
planes_[plane].mem_offset = offset;
planes_[plane].length = size;
}

bool Buffer::CopyDataIn(const void* data, size_t length) {
DVLOGF(4) << MappedAddress(0) << " : " << data << " : " << length;

void* destination_addr = MappedAddress(0);
if (!destination_addr || !data || (planes_.size() != 1) ||
(length > planes_[0].length)) {
DVLOGF(1) << "Requirements to copy buffer failed.";
return false;
}

memcpy(destination_addr, data, length);
planes_[0].bytes_used = length;

return true;
}

Buffer::~Buffer() {}

void Device::Close() {
device_fd_.reset();
}

// VIDIOC_ENUM_FMT
std::set<VideoCodec> Device::EnumerateInputFormats() {
std::set<VideoCodec> pix_fmts;
Expand Down Expand Up @@ -141,6 +249,76 @@ bool Device::SetInputFormat(VideoCodec codec,
return true;
}

// VIDIOC_STREAMON
bool Device::StreamOn(BufferType type) {
enum v4l2_buf_type buf_type = BufferTypeToV4L2(type);

const int ret = IoctlDevice(VIDIOC_STREAMON, &buf_type);
if (ret) {
DVLOGF(1) << "VIDIOC_STREAMON failed: " << ret;
return false;
}
return true;
}

// VIDIOC_STREAMOFF
bool Device::StreamOff(BufferType type) {
enum v4l2_buf_type buf_type = BufferTypeToV4L2(type);

const int ret = IoctlDevice(VIDIOC_STREAMOFF, &buf_type);
if (ret) {
DVLOGF(1) << "VIDIOC_STREAMOFF failed: " << ret;
return false;
}
return true;
}

// VIDIOC_REQBUFS
absl::optional<uint32_t> Device::RequestBuffers(BufferType type,
MemoryType memory,
size_t count) {
struct v4l2_requestbuffers reqbufs;
memset(&reqbufs, 0, sizeof(reqbufs));

reqbufs.count = count;
reqbufs.type = BufferTypeToV4L2(type);
reqbufs.memory = MemoryTypeToV4L2(memory);

const int ret = IoctlDevice(VIDIOC_REQBUFS, &reqbufs);
if (ret) {
DVLOGF(1) << "Failed to allocate " << count
<< " input buffers with VIDIOC_REQBUFS: " << ret;
return absl::nullopt;
}

return reqbufs.count;
}

// VIDIOC_QUERYBUF
absl::optional<Buffer> Device::QueryBuffer(BufferType buffer_type,
MemoryType memory_type,
uint32_t index,
uint32_t num_planes) {
struct v4l2_buffer v4l2_buffer;
struct v4l2_plane v4l2_planes[VIDEO_MAX_PLANES];
memset(&v4l2_buffer, 0, sizeof(v4l2_buffer));
memset(v4l2_planes, 0, sizeof(v4l2_planes));
v4l2_buffer.m.planes = v4l2_planes;
v4l2_buffer.length = num_planes;

v4l2_buffer.type = BufferTypeToV4L2(buffer_type);
v4l2_buffer.memory = MemoryTypeToV4L2(memory_type);
v4l2_buffer.index = index;

const int ret = IoctlDevice(VIDIOC_QUERYBUF, &v4l2_buffer);
if (ret) {
DVLOGF(1) << "VIDIOC_QUERYBUF failed: ";
return absl::nullopt;
}

return V4L2BufferToBuffer(v4l2_buffer);
}

// VIDIOC_ENUM_FRAMESIZES
std::pair<gfx::Size, gfx::Size> Device::GetFrameResolutionRange(
VideoCodec codec) {
Expand Down Expand Up @@ -180,7 +358,6 @@ std::vector<VideoCodecProfile> Device::ProfilesForVideoCodec(VideoCodec codec) {
}

const auto profile_cid = kV4L2CodecPixFmtToProfileCID.at(pix_fmt);

v4l2_queryctrl query_ctrl = {.id = base::strict_cast<__u32>(profile_cid)};
if (IoctlDevice(VIDIOC_QUERYCTRL, &query_ctrl) != kIoctlOk) {
return {};
Expand Down Expand Up @@ -243,8 +420,28 @@ bool Device::OpenDevice() {
return true;
}

void Device::Close() {
device_fd_.reset();
bool Device::MmapBuffer(Buffer& buffer) {
for (uint32_t plane = 0; plane < buffer.PlaneCount(); ++plane) {
void* p = mmap(nullptr, buffer.PlaneLength(plane), PROT_READ | PROT_WRITE,
MAP_SHARED, device_fd_.get(), buffer.PlaneMemOffset(plane));
// dealloc rest if failed
if (p == MAP_FAILED) {
VPLOGF(1) << "mmap() failed: ";
return false;
}
buffer.SetMappedAddress(plane, p);
}

return true;
}

void Device::MunmapBuffer(Buffer& buffer) {
for (uint32_t plane = 0; plane < buffer.PlaneCount(); plane++) {
if (buffer.MappedAddress(plane) != nullptr) {
munmap(buffer.MappedAddress(plane), buffer.PlaneLength(plane));
buffer.SetMappedAddress(plane, nullptr);
}
}
}

Device::~Device() {}
Expand Down
78 changes: 77 additions & 1 deletion media/gpu/v4l2/stateless/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,64 @@
#include "base/memory/ref_counted.h"
#include "media/base/video_codecs.h"
#include "media/gpu/media_gpu_export.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gfx/geometry/size.h"

namespace media {

enum class BufferType { kCompressedData, kRawFrames, kInvalid };
enum class MemoryType { kMemoryMapped, kDmaBuf, kInvalid };

// Abstraction to hold compressed and uncompressed buffers. This class
// provides a structure that does not include and V4L2 references. This is done
// so that users of the driver do not need to care about V4L2 specific
// structures
class Buffer {
public:
Buffer(BufferType buffer_type,
MemoryType memory_type,
uint32_t index,
uint32_t plane_count);
~Buffer();

void SetupPlane(uint32_t plane, size_t offset, size_t size);
uint32_t PlaneCount() const { return planes_.size(); }

void* MappedAddress(uint32_t plane) const;
void SetMappedAddress(uint32_t plane, void* address);

uint32_t PlaneMemOffset(uint32_t plane) const {
return planes_[plane].mem_offset;
}
size_t PlaneLength(uint32_t plane) const { return planes_[plane].length; }
size_t PlaneBytesUsed(uint32_t plane) const {
return planes_[plane].bytes_used;
}

uint32_t GetIndex() const { return index_; }
BufferType GetBufferType() const { return buffer_type_; }
MemoryType GetMemoryType() const { return memory_type_; }

// Method for copying compressed input data into a Buffer's backing store. It
// is limited to destination buffers that have a single plane and are memory
// mapped.
bool CopyDataIn(const void* data, size_t length);

private:
class Plane {
public:
size_t length;
void* mapped_address;
size_t bytes_used;
uint32_t mem_offset;
};

const BufferType buffer_type_;
const MemoryType memory_type_;
const uint32_t index_;
std::vector<Plane> planes_;
};

// Encapsulates the v4l2 subsystem and prevents <linux/videodev2.h> from
// being included elsewhere with the possible exception of the codec specific
// delegates. This keeps all of the v4l2 driver specific structures in one
Expand All @@ -41,6 +92,25 @@ class MEDIA_GPU_EXPORT Device : public base::RefCountedThreadSafe<Device> {
gfx::Size resolution,
size_t encoded_buffer_size);

// Stops streaming on the |type| of buffer using the VIDIOC_STREAMOFF ioctl.
bool StreamOff(BufferType type);

// Starts streaming on the |type| of buffer using the VIDIOC_STREAMON ioctl.
bool StreamOn(BufferType type);

// Request a |count| of buffers via the VIDIOC_REQBUFS ioctl. The driver
// will return a |uint32_t| with the number of buffers allocated. This
// number does not need to be the same as |count|.
absl::optional<uint32_t> RequestBuffers(BufferType type,
MemoryType memory,
size_t count);

// Uses the VIDIOC_QUERYBUF ioctl to fill out and return a |Buffer|.
absl::optional<Buffer> QueryBuffer(BufferType type,
MemoryType memory,
uint32_t index,
uint32_t num_planes);

// Query the driver for the smallest and largest uncompressed frame sizes that
// are supported using the VIDIOC_ENUM_FRAMESIZES ioctl.
std::pair<gfx::Size, gfx::Size> GetFrameResolutionRange(VideoCodec codec);
Expand All @@ -49,7 +119,13 @@ class MEDIA_GPU_EXPORT Device : public base::RefCountedThreadSafe<Device> {
// profiles of the input formats.
std::vector<VideoCodecProfile> ProfilesForVideoCodec(VideoCodec codec);

// Capabilities are queried using VIDIOC_QUERYCAP. Stateless and
// mmap the |buffer| so that it can be read/written.
bool MmapBuffer(Buffer& buffer);

// unmmap the |buffer| when read/write access is no longer needed.
void MunmapBuffer(Buffer& buffer);

// Capabilities are queried using VIDIOC_QUERYCAP. Stateless and
// stateful drivers need different capabilities.
virtual bool CheckCapabilities(VideoCodec codec) = 0;

Expand Down

0 comments on commit 458d76f

Please sign in to comment.