libcamera: Add support for V4L2 requests
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 an fd that is allocated via MEDIA_IOC_REQUEST_ALLOC and then passed to the various V4L2 functions. Implement a V4L2Request class to wrap such an fd and add the corresponding utility functions. Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
@@ -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,
|
||||
* an 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>(UniqueFD(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 An 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,16 @@ 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 -EINVAL One of the controls is not supported or not accessible
|
||||
* \retval -EACCESS The device does not support requests
|
||||
* \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 +392,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;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Copyright (C) 2025, Ideas On Board
|
||||
*
|
||||
* V4L2 Request
|
||||
*/
|
||||
|
||||
#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(UniqueFD &&fd)
|
||||
: fd_(std::move(fd)), fdNotifier_(fd_.get(), EventNotifier::Exception)
|
||||
{
|
||||
if (!fd_.isValid())
|
||||
return;
|
||||
|
||||
fdNotifier_.activated.connect(this, &V4L2Request::requestReady);
|
||||
fdNotifier_.setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* \fn V4L2Request::isValid()
|
||||
* \brief Check if the request is valid
|
||||
*
|
||||
* Checks if the underlying fd is valid.
|
||||
*
|
||||
* \return True if the request is valid, false otherwise
|
||||
*/
|
||||
|
||||
/**
|
||||
* \fn V4L2Request::fd()
|
||||
* \brief Get the file descriptor
|
||||
*
|
||||
* \return The file descriptor wrapped by this V4L2Request
|
||||
*/
|
||||
|
||||
/**
|
||||
* \var V4L2Request::requestDone
|
||||
* \brief Signal that is emitted when the request is done
|
||||
*/
|
||||
|
||||
/**
|
||||
* \brief Reinit the request
|
||||
*
|
||||
* Calls MEDIA_REQUEST_IOC_REINIT on 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 Queue the request
|
||||
*
|
||||
* Calls MEDIA_REQUEST_IOC_QUEUE on 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();
|
||||
|
||||
Reference in New Issue
Block a user