@@ -18,6 +18,7 @@
#include <libcamera/base/unique_fd.h>
#include "libcamera/internal/media_object.h"
+#include "libcamera/internal/v4l2_request.h"
namespace libcamera {
@@ -57,6 +58,11 @@ public:
std::vector<MediaEntity *> locateEntities(unsigned int function);
+ int allocateRequests(unsigned int count,
+ std::vector<std::unique_ptr<V4L2Request>> *requests);
+
+ bool supportsRequests();
+
protected:
std::string logPrefix() const override;
@@ -87,6 +93,7 @@ private:
UniqueFD fd_;
bool valid_;
bool acquired_;
+ std::optional<bool> supportsRequests_;
std::map<unsigned int, MediaObject *> objects_;
std::vector<MediaEntity *> entities_;
@@ -44,6 +44,7 @@ libcamera_internal_headers = files([
'sysfs.h',
'v4l2_device.h',
'v4l2_pixelformat.h',
+ 'v4l2_request.h',
'v4l2_subdevice.h',
'v4l2_videodevice.h',
'vector.h',
@@ -24,6 +24,7 @@
#include <libcamera/controls.h>
#include "libcamera/internal/formats.h"
+#include "libcamera/internal/v4l2_request.h"
namespace libcamera {
@@ -37,8 +38,8 @@ public:
const ControlInfoMap &controls() const { return controls_; }
- ControlList getControls(Span<const uint32_t> ids);
- int setControls(ControlList *ctrls);
+ ControlList getControls(Span<const uint32_t> ids, const V4L2Request *request = nullptr);
+ int setControls(ControlList *ctrls, const V4L2Request *request = nullptr);
const struct v4l2_query_ext_ctrl *controlInfo(uint32_t id) const;
new file mode 100644
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, Ideas On Board
+ *
+ * V4L2 requests
+ */
+
+#pragma once
+
+#include <string>
+
+#include <linux/videodev2.h>
+
+#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/log.h>
+#include <libcamera/base/signal.h>
+#include <libcamera/base/span.h>
+#include <libcamera/base/unique_fd.h>
+
+#include <libcamera/color_space.h>
+#include <libcamera/controls.h>
+
+namespace libcamera {
+
+class V4L2Request : protected Loggable
+{
+public:
+ bool isValid() const { return fd_.isValid(); }
+ int fd() const { return fd_.get(); }
+
+ int reinit();
+ int queue();
+
+ V4L2Request(int fd = -1);
+ ~V4L2Request() = default;
+
+ Signal<V4L2Request *> requestDone;
+
+private:
+ LIBCAMERA_DISABLE_COPY_AND_MOVE(V4L2Request)
+
+ void requestReady();
+ std::string logPrefix() const override;
+
+ UniqueFD fd_;
+ EventNotifier fdNotifier_;
+};
+
+} /* namespace libcamera */
@@ -33,6 +33,7 @@
#include "libcamera/internal/formats.h"
#include "libcamera/internal/v4l2_device.h"
#include "libcamera/internal/v4l2_pixelformat.h"
+#include "libcamera/internal/v4l2_request.h"
namespace libcamera {
@@ -217,7 +218,7 @@ public:
int importBuffers(unsigned int count);
int releaseBuffers();
- int queueBuffer(FrameBuffer *buffer);
+ int queueBuffer(FrameBuffer *buffer, const V4L2Request *request = nullptr);
Signal<FrameBuffer *> bufferReady;
int streamOn();
@@ -20,6 +20,7 @@
#include <linux/media.h>
#include <libcamera/base/log.h>
+#include "libcamera/internal/v4l2_request.h"
/**
* \file media_device.h
@@ -851,4 +852,50 @@ std::vector<MediaEntity *> MediaDevice::locateEntities(unsigned int function)
return found;
}
+/**
+ * \brief Allocate requests
+ * \param[in] count Number of requests to allocate
+ * \param[out] requests Vector to store allocated requests
+ *
+ * Allocates and stores \a count requests in \a requests. If allocation fails,
+ * and error is returned and \a requests is cleared.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int MediaDevice::allocateRequests(unsigned int count,
+ std::vector<std::unique_ptr<V4L2Request>> *requests)
+{
+ requests->resize(count);
+ for (unsigned int i = 0; i < count; i++) {
+ int requestFd;
+ int ret = ::ioctl(fd_.get(), MEDIA_IOC_REQUEST_ALLOC, &requestFd);
+ if (ret < 0) {
+ requests->clear();
+ return -errno;
+ }
+ (*requests)[i] = std::make_unique<V4L2Request>(requestFd);
+ }
+
+ return 0;
+}
+
+/**
+ * \brief Check if requests are supported
+ *
+ * Checks if the device supports V4L2 requests by trying to allocate a single
+ * request. The result is cached, so the allocation is only tried once.
+ *
+ * \return True if the device supports requests, false otherwise
+ */
+bool MediaDevice::supportsRequests()
+{
+ if (supportsRequests_.has_value())
+ return supportsRequests_.value();
+
+ std::vector<std::unique_ptr<V4L2Request>> requests;
+ supportsRequests_ = (allocateRequests(1, &requests) == 0);
+
+ return supportsRequests_.value();
+}
+
} /* namespace libcamera */
@@ -54,6 +54,7 @@ libcamera_internal_sources = files([
'sysfs.cpp',
'v4l2_device.cpp',
'v4l2_pixelformat.cpp',
+ 'v4l2_request.cpp',
'v4l2_subdevice.cpp',
'v4l2_videodevice.cpp',
'vector.cpp',
@@ -162,6 +162,7 @@ void V4L2Device::close()
/**
* \brief Read controls from the device
* \param[in] ids The list of controls to read, specified by their ID
+ * \param[in] request An optional request
*
* This function reads the value of all controls contained in \a ids, and
* returns their values as a ControlList.
@@ -171,10 +172,12 @@ void V4L2Device::close()
* during validation of the requested controls, no control is read and this
* function returns an empty control list.
*
+ * If \a request is specified the controls tied to that request are read.
+ *
* \return The control values in a ControlList on success, or an empty list on
* error
*/
-ControlList V4L2Device::getControls(Span<const uint32_t> ids)
+ControlList V4L2Device::getControls(Span<const uint32_t> ids, const V4L2Request *request)
{
if (ids.empty())
return {};
@@ -242,10 +245,16 @@ ControlList V4L2Device::getControls(Span<const uint32_t> ids)
}
struct v4l2_ext_controls v4l2ExtCtrls = {};
- v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL;
v4l2ExtCtrls.controls = v4l2Ctrls.data();
v4l2ExtCtrls.count = v4l2Ctrls.size();
+ if (request) {
+ v4l2ExtCtrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
+ v4l2ExtCtrls.request_fd = request->fd();
+ } else {
+ v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL;
+ }
+
int ret = ioctl(VIDIOC_G_EXT_CTRLS, &v4l2ExtCtrls);
if (ret) {
unsigned int errorIdx = v4l2ExtCtrls.error_idx;
@@ -273,6 +282,7 @@ ControlList V4L2Device::getControls(Span<const uint32_t> ids)
/**
* \brief Write controls to the device
* \param[in] ctrls The list of controls to write
+ * \param[in] request And optional request
*
* This function writes the value of all controls contained in \a ctrls, and
* stores the values actually applied to the device in the corresponding
@@ -288,11 +298,15 @@ ControlList V4L2Device::getControls(Span<const uint32_t> ids)
* are written and their values are updated in \a ctrls, while all other
* controls are not written and their values are not changed.
*
+ * If \a request is set, the controls will be applied to that request. If the
+ * device doesn't support requests, -EACCESS will be returned. If \a request is
+ * invalid, -EINVAL will be returned.
+ *
* \return 0 on success or an error code otherwise
* \retval -EINVAL One of the control is not supported or not accessible
* \retval i The index of the control that failed
*/
-int V4L2Device::setControls(ControlList *ctrls)
+int V4L2Device::setControls(ControlList *ctrls, const V4L2Request *request)
{
if (ctrls->empty())
return 0;
@@ -377,10 +391,16 @@ int V4L2Device::setControls(ControlList *ctrls)
}
struct v4l2_ext_controls v4l2ExtCtrls = {};
- v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL;
v4l2ExtCtrls.controls = v4l2Ctrls.data();
v4l2ExtCtrls.count = v4l2Ctrls.size();
+ if (request) {
+ v4l2ExtCtrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
+ v4l2ExtCtrls.request_fd = request->fd();
+ } else {
+ v4l2ExtCtrls.which = V4L2_CTRL_WHICH_CUR_VAL;
+ }
+
int ret = ioctl(VIDIOC_S_EXT_CTRLS, &v4l2ExtCtrls);
if (ret) {
unsigned int errorIdx = v4l2ExtCtrls.error_idx;
new file mode 100644
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, Ideas On Board
+ *
+ * V4L2 Request API
+ */
+
+#include "libcamera/internal/v4l2_request.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <linux/media.h>
+
+#include <libcamera/base/event_notifier.h>
+#include <libcamera/base/log.h>
+
+/**
+ * \file v4l2_request.h
+ * \brief V4L2 Request
+ */
+
+namespace libcamera {
+
+LOG_DECLARE_CATEGORY(V4L2)
+
+/**
+ * \class V4L2Request
+ * \brief V4L2Request object and API
+ *
+ * The V4L2Request class wraps a V4L2 request fd and provides some convenience
+ * functions to handle request.
+ *
+ * It is usually constructed by calling \a MediaDevice::allocateRequests().
+ *
+ * A request can then be passed to the V4L2Device::setControls(),
+ * V4L2Device::getControls() and V4L2VideoDevice::queueBuffer().
+ */
+
+/**
+ * \brief Construct a V4L2Request
+ * \param[in] fd The request fd
+ */
+V4L2Request::V4L2Request(int fd)
+ : fd_(fd), fdNotifier_(fd, EventNotifier::Exception)
+{
+ if (!fd_.isValid())
+ return;
+
+ fdNotifier_.activated.connect(this, &V4L2Request::requestReady);
+ fdNotifier_.setEnabled(false);
+}
+
+/**
+ * \brief Reinit the request
+ *
+ * Calls MEDIA_REQUEST_IOC_REINIT om the request fd.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Request::reinit()
+{
+ fdNotifier_.setEnabled(false);
+
+ if (::ioctl(fd_.get(), MEDIA_REQUEST_IOC_REINIT) < 0)
+ return -errno;
+
+ return 0;
+}
+
+/**
+ * \brief Reinit the request
+ *
+ * Calls MEDIA_REQUEST_IOC_QUEUE om the request fd.
+ *
+ * \return 0 on success or a negative error code otherwise
+ */
+int V4L2Request::queue()
+{
+ if (::ioctl(fd_.get(), MEDIA_REQUEST_IOC_QUEUE) < 0)
+ return -errno;
+
+ fdNotifier_.setEnabled(true);
+
+ return 0;
+}
+
+std::string V4L2Request::logPrefix() const
+{
+ return "Request [" + std::to_string(fd()) + "]";
+}
+
+/**
+ * \brief Slot to handle request done events
+ *
+ * When this slot is called, the request is done and the requestDone will be
+ * emitted.
+ */
+void V4L2Request::requestReady()
+{
+ requestDone.emit(this);
+}
+
+} /* namespace libcamera */
@@ -30,6 +30,7 @@
#include "libcamera/internal/framebuffer.h"
#include "libcamera/internal/media_device.h"
#include "libcamera/internal/media_object.h"
+#include "libcamera/internal/v4l2_request.h"
/**
* \file v4l2_videodevice.h
@@ -1629,6 +1630,7 @@ int V4L2VideoDevice::releaseBuffers()
/**
* \brief Queue a buffer to the video device
* \param[in] buffer The buffer to be queued
+ * \param[in] request An optional request
*
* For capture video devices the \a buffer will be filled with data by the
* device. For output video devices the \a buffer shall contain valid data and
@@ -1641,9 +1643,11 @@ int V4L2VideoDevice::releaseBuffers()
* Note that queueBuffer() will fail if the device is in the process of being
* stopped from a streaming state through streamOff().
*
+ * If \a request is specified, the buffer will be tied to that request.
+ *
* \return 0 on success or a negative error code otherwise
*/
-int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer)
+int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer, const V4L2Request *request)
{
struct v4l2_plane v4l2Planes[VIDEO_MAX_PLANES] = {};
struct v4l2_buffer buf = {};
@@ -1674,6 +1678,10 @@ int V4L2VideoDevice::queueBuffer(FrameBuffer *buffer)
buf.type = bufferType_;
buf.memory = memoryType_;
buf.field = V4L2_FIELD_NONE;
+ if (request) {
+ buf.flags = V4L2_BUF_FLAG_REQUEST_FD;
+ buf.request_fd = request->fd();
+ }
bool multiPlanar = V4L2_TYPE_IS_MULTIPLANAR(buf.type);
Span<const FrameBuffer::Plane> planes = buffer->planes();
The V4L2 requests API provides support to atomically tie controls to a set of buffers. This is especially common for m2m devices. Such a request is represented by a fd that is allocated vi MEDIA_IOC_REQUEST_ALLOC and then passed to various V4L2 function. Implement a V4L2Request class to wrap such an fd and add the corresponding utility functions. Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> --- include/libcamera/internal/media_device.h | 7 ++ include/libcamera/internal/meson.build | 1 + include/libcamera/internal/v4l2_device.h | 5 +- include/libcamera/internal/v4l2_request.h | 49 ++++++++ include/libcamera/internal/v4l2_videodevice.h | 3 +- src/libcamera/media_device.cpp | 47 ++++++++ src/libcamera/meson.build | 1 + src/libcamera/v4l2_device.cpp | 28 ++++- src/libcamera/v4l2_request.cpp | 107 ++++++++++++++++++ src/libcamera/v4l2_videodevice.cpp | 10 +- 10 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 include/libcamera/internal/v4l2_request.h create mode 100644 src/libcamera/v4l2_request.cpp