// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_GPU_V4L2_TEST_V4L2_IOCTL_SHIM_H_
#define MEDIA_GPU_V4L2_TEST_V4L2_IOCTL_SHIM_H_
#include <linux/videodev2.h>
#include <string.h>
#include <set>
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "ui/gfx/geometry/size.h"
namespace media {
namespace v4l2_test {
// MmappedBuffer maintains |mmapped_planes_| for each buffer as well as
// |buffer_id_|. |buffer_id_| is an index used for VIDIOC_REQBUFS ioctl call.
class MmappedBuffer : public base::RefCounted<MmappedBuffer> {
public:
MmappedBuffer(const base::PlatformFile decode_fd,
const struct v4l2_buffer& v4l2_buffer);
class MmappedPlane {
public:
raw_ptr<void> start_addr;
const size_t length;
size_t bytes_used = 0;
MmappedPlane(void* start, size_t len) : start_addr(start), length(len) {}
// Appends the current slice data to the mmapped buffer. Resets |bytes_used|
// to 0 for the first slice. This function is used for HEVC because multiple
// slices per frame are supported.
void CopyInSlice(const uint8_t* frame_data,
size_t frame_size,
bool is_first_slice) {
if (is_first_slice) {
bytes_used = 0;
}
LOG_ASSERT((bytes_used + frame_size) < length)
<< "Not enough memory allocated to copy into.";
memcpy(static_cast<uint8_t*>(start_addr) + bytes_used, frame_data,
frame_size);
bytes_used += frame_size;
}
// Overwrites the mmapped buffer with the current frame data.
void CopyIn(const uint8_t* frame_data, size_t frame_size) {
CopyInSlice(frame_data, frame_size, true);
}
};
using MmappedPlanes = std::vector<MmappedPlane>;
MmappedPlanes& mmapped_planes() { return mmapped_planes_; }
uint32_t buffer_id() const { return buffer_id_; }
void set_buffer_id(uint32_t buffer_id) { buffer_id_ = buffer_id; }
uint32_t frame_number() const { return frame_number_; }
void set_frame_number(uint32_t frame_number) { frame_number_ = frame_number; }
private:
friend class base::RefCounted<MmappedBuffer>;
~MmappedBuffer();
MmappedBuffer(const MmappedBuffer&) = delete;
MmappedBuffer& operator=(const MmappedBuffer&) = delete;
MmappedPlanes mmapped_planes_;
const uint32_t num_planes_;
uint32_t buffer_id_;
// Indicates which frame in input bitstream corresponds to this MmappedBuffer
// in OUTPUT queue.
uint32_t frame_number_;
};
using MmappedBuffers = std::vector<scoped_refptr<MmappedBuffer>>;
// V4L2Queue class maintains properties of a queue.
class V4L2Queue {
public:
V4L2Queue(enum v4l2_buf_type type,
const gfx::Size& resolution,
enum v4l2_memory memory);
V4L2Queue(const V4L2Queue&) = delete;
V4L2Queue& operator=(const V4L2Queue&) = delete;
~V4L2Queue();
// Retrieves a mmapped buffer for the given |buffer_id|, which is a decoded
// surface, from MmappedBuffers.
scoped_refptr<MmappedBuffer> GetBuffer(const size_t buffer_id) const;
enum v4l2_buf_type type() const { return type_; }
uint32_t fourcc() const { return fourcc_; }
void set_fourcc(uint32_t fourcc) { fourcc_ = fourcc; }
gfx::Size resolution() const { return resolution_; }
void set_resolution(gfx::Size resolution) { resolution_ = resolution; }
enum v4l2_memory memory() const { return memory_; }
void set_buffers(MmappedBuffers& buffers) { buffers_ = buffers; }
uint32_t num_buffers() const { return num_buffers_; }
void set_num_buffers(uint32_t num_buffers) { num_buffers_ = num_buffers; }
uint32_t num_planes() const { return num_planes_; }
void set_num_planes(uint32_t num_planes) { num_planes_ = num_planes; }
uint32_t last_queued_buffer_id() const { return last_queued_buffer_id_; }
void set_last_queued_buffer_id(uint32_t last_queued_buffer_id) {
last_queued_buffer_id_ = last_queued_buffer_id;
}
int media_request_fd() const { return media_request_fd_; }
void set_media_request_fd(int media_request_fd) {
media_request_fd_ = media_request_fd;
}
std::set<uint32_t> queued_buffer_ids() const { return queued_buffer_ids_; }
void QueueBufferId(uint32_t last_queued_buffer_id) {
queued_buffer_ids_.insert(last_queued_buffer_id);
}
void DequeueBufferId(uint32_t buffer_id) {
queued_buffer_ids_.erase(buffer_id);
}
void DequeueAllBufferIds() { queued_buffer_ids_.clear(); }
private:
const enum v4l2_buf_type type_;
uint32_t fourcc_;
MmappedBuffers buffers_;
uint32_t num_buffers_;
// For the OUTPUT queue resolution refers to the coded dimensions of the
// video. For the CAPTURE queue resolution refers to the size of the
// buffer necessary for the driver to decode into and must
// contain the resolution of the OUTPUT queue.
gfx::Size resolution_;
uint32_t num_planes_;
const enum v4l2_memory memory_;
// File descriptor returned by MEDIA_IOC_REQUEST_ALLOC ioctl call
// to submit requests.
int media_request_fd_;
// Tracks which CAPTURE buffer was queued in the previous frame.
uint32_t last_queued_buffer_id_;
std::set<uint32_t> queued_buffer_ids_;
};
// V4L2IoctlShim is a shallow wrapper which wraps V4L2 ioctl requests
// with error checking and maintains the lifetime of a file descriptor
// for decode/media device.
// https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/user-func.html
class V4L2IoctlShim {
public:
// Finds first decoder that can decode |coded_fourcc|
V4L2IoctlShim(uint32_t coded_fourcc);
V4L2IoctlShim(const V4L2IoctlShim&) = delete;
V4L2IoctlShim& operator=(const V4L2IoctlShim&) = delete;
~V4L2IoctlShim();
// Queries whether the given |ctrl_id| is supported on current platform.
[[nodiscard]] bool QueryCtrl(const uint32_t ctrl_id) const;
// Enumerates all frame sizes that the device supports
// via VIDIOC_ENUM_FRAMESIZES.
[[nodiscard]] bool EnumFrameSizes(uint32_t fourcc) const;
// Configures the underlying V4L2 queue via VIDIOC_S_FMT. Returns true
// if the configuration was successful.
void SetFmt(const std::unique_ptr<V4L2Queue>& queue) const;
// Retrieves the format, |fmt|, (via VIDIOC_G_FMT)
void GetFmt(struct v4l2_format* fmt) const;
// Tries to configure |fmt|. This does not modify the underlying driver state.
// https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/vidioc-g-fmt.html?highlight=vidioc_try_fmt#description
void TryFmt(struct v4l2_format* fmt) const;
// Allocates buffers via VIDIOC_REQBUFS for |queue| with a buffer count.
void ReqBufs(std::unique_ptr<V4L2Queue>& queue, uint32_t count) const;
// Enqueues an empty (capturing) or filled (output) buffer
// in the driver's incoming |queue|.
[[nodiscard]] bool QBuf(const std::unique_ptr<V4L2Queue>& queue,
const uint32_t buffer_id) const;
// Dequeues a filled (capturing) or decoded (output) buffer
// from the driver’s outgoing |queue|.
void DQBuf(const std::unique_ptr<V4L2Queue>& queue,
uint32_t* buffer_id) const;
// Starts streaming |queue| (via VIDIOC_STREAMON).
void StreamOn(const enum v4l2_buf_type type) const;
// Stops streaming |queue| (via VIDIOC_STREAMOFF).
void StreamOff(const enum v4l2_buf_type type) const;
// Sets the value of controls which specify decoding parameters for each
// frame. |immediate| forces the call to be processed immediately when
// |MediaIocRequestAlloc| is next called as opposed to being put in the queue.
void SetExtCtrls(const std::unique_ptr<V4L2Queue>& queue,
v4l2_ext_controls* ext_ctrls,
bool immediate = false) const;
// Allocates requests (likely one per OUTPUT buffer) via
// MEDIA_IOC_REQUEST_ALLOC on the media device.
void MediaIocRequestAlloc(int* req_fd) const;
// Submits a request for the given OUTPUT |queue| by queueing
// the request with |queue|'s media_request_fd().
void MediaRequestIocQueue(const std::unique_ptr<V4L2Queue>& queue) const;
// Re-initializes the previously allocated request for reuse.
void MediaRequestIocReinit(const std::unique_ptr<V4L2Queue>& queue) const;
// Completion of the request implies that the OUTPUT and CAPTURE buffers
// are available for dequeueing
void WaitForRequestCompletion(const std::unique_ptr<V4L2Queue>& queue) const;
// Finds available media device for video decoder. This function also checks
// to make sure either |bus_info| or |driver| field from |media_device_info|
// struct (obtained from MEDIA_IOC_DEVICE_INFO call) is matched from the same
// field in |v4l2_capability| struct.
[[nodiscard]] bool FindMediaDevice(struct v4l2_capability* cap);
// Allocates buffers for the given |queue|.
void QueryAndMmapQueueBuffers(std::unique_ptr<V4L2Queue>& queue) const;
enum class DeviceType {
kDecoder,
kMedia,
};
private:
// Queries |v4l_fd| to see if it can use the specified |fourcc| format
// for the given buffer |type|.
[[nodiscard]] bool QueryFormat(enum v4l2_buf_type type,
uint32_t fourcc) const;
// Uses a specialized function template to execute V4L2 ioctl request
// for |request_code| and returns the output of the ioctl() in |arg|
// if this is a pointer, otherwise |arg| is considered a file descriptor
// for said ioctl().
template <typename T>
[[nodiscard]] bool Ioctl(int request_code, T arg) const;
// Decode device file descriptor used for ioctl requests.
base::File decode_fd_;
// Media device file descriptor used for ioctl requests.
base::File media_fd_;
// Whether V4L2_CTRL_WHICH_CUR_VAL is implemented correctly
bool cur_val_is_supported_ = true;
};
} // namespace v4l2_test
} // namespace media
#endif // MEDIA_GPU_V4L2_TEST_V4L2_IOCTL_SHIM_H_