.. SPDX-License-Identifier: GPL-2.0 OR GFDL-1.1-no-invariants-or-later
.. c:namespace:: MC
.. _media-request-api:
Request API
===========
The Request API has been designed to allow V4L2 to deal with requirements of
modern devices (stateless codecs, complex camera pipelines, ...) and APIs
(Android Codec v2). One such requirement is the ability for devices belonging to
the same pipeline to reconfigure and collaborate closely on a per-frame basis.
Another is support of stateless codecs, which require controls to be applied
to specific frames (aka 'per-frame controls') in order to be used efficiently.
While the initial use-case was V4L2, it can be extended to other subsystems
as well, as long as they use the media controller.
Supporting these features without the Request API is not always possible and if
it is, it is terribly inefficient: user-space would have to flush all activity
on the media pipeline, reconfigure it for the next frame, queue the buffers to
be processed with that configuration, and wait until they are all available for
dequeuing before considering the next frame. This defeats the purpose of having
buffer queues since in practice only one buffer would be queued at a time.
The Request API allows a specific configuration of the pipeline (media
controller topology + configuration for each media entity) to be associated with
specific buffers. This allows user-space to schedule several tasks ("requests")
with different configurations in advance, knowing that the configuration will be
applied when needed to get the expected result. Configuration values at the time
of request completion are also available for reading.
General Usage
-------------
The Request API extends the Media Controller API and cooperates with
subsystem-specific APIs to support request usage. At the Media Controller
level, requests are allocated from the supporting Media Controller device
node. Their life cycle is then managed through the request file descriptors in
an opaque way. Configuration data, buffer handles and processing results
stored in requests are accessed through subsystem-specific APIs extended for
request support, such as V4L2 APIs that take an explicit ``request_fd``
parameter.
Request Allocation
------------------
User-space allocates requests using :ref:`MEDIA_IOC_REQUEST_ALLOC`
for the media device node. This returns a file descriptor representing the
request. Typically, several such requests will be allocated.
Request Preparation
-------------------
Standard V4L2 ioctls can then receive a request file descriptor to express the
fact that the ioctl is part of said request, and is not to be applied
immediately. See :ref:`MEDIA_IOC_REQUEST_ALLOC` for a list of ioctls that
support this. Configurations set with a ``request_fd`` parameter are stored
instead of being immediately applied, and buffers queued to a request do not
enter the regular buffer queue until the request itself is queued.
Request Submission
------------------
Once the configuration and buffers of the request are specified, it can be
queued by calling :ref:`MEDIA_REQUEST_IOC_QUEUE` on the request file descriptor.
A request must contain at least one buffer, otherwise ``ENOENT`` is returned.
A queued request cannot be modified anymore.
.. caution::
For :ref:`memory-to-memory devices <mem2mem>` you can use requests only for
output buffers, not for capture buffers. Attempting to add a capture buffer
to a request will result in an ``EBADR`` error.
If the request contains configurations for multiple entities, individual drivers
may synchronize so the requested pipeline's topology is applied before the
buffers are processed. Media controller drivers do a best effort implementation
since perfect atomicity may not be possible due to hardware limitations.
.. caution::
It is not allowed to mix queuing requests with directly queuing buffers:
whichever method is used first locks this in place until
:ref:`VIDIOC_STREAMOFF <VIDIOC_STREAMON>` is called or the device is
:ref:`closed <func-close>`. Attempts to directly queue a buffer when earlier
a buffer was queued via a request or vice versa will result in an ``EBUSY``
error.
Controls can still be set without a request and are applied immediately,
regardless of whether a request is in use or not.
.. caution::
Setting the same control through a request and also directly can lead to
undefined behavior!
User-space can :c:func:`poll()` a request file descriptor in
order to wait until the request completes. A request is considered complete
once all its associated buffers are available for dequeuing and all the
associated controls have been updated with the values at the time of completion.
Note that user-space does not need to wait for the request to complete to
dequeue its buffers: buffers that are available halfway through a request can
be dequeued independently of the request's state.
A completed request contains the state of the device after the request was
executed. User-space can query that state by calling
:ref:`ioctl VIDIOC_G_EXT_CTRLS <VIDIOC_G_EXT_CTRLS>` with the request file
descriptor. Calling :ref:`ioctl VIDIOC_G_EXT_CTRLS <VIDIOC_G_EXT_CTRLS>` for a
request that has been queued but not yet completed will return ``EBUSY``
since the control values might be changed at any time by the driver while the
request is in flight.
.. _media-request-life-time:
Recycling and Destruction
-------------------------
Finally, a completed request can either be discarded or be reused. Calling
:c:func:`close()` on a request file descriptor will make
that file descriptor unusable and the request will be freed once it is no
longer in use by the kernel. That is, if the request is queued and then the
file descriptor is closed, then it won't be freed until the driver completed
the request.
The :ref:`MEDIA_REQUEST_IOC_REINIT` will clear a request's state and make it
available again. No state is retained by this operation: the request is as
if it had just been allocated.
Example for a Codec Device
--------------------------
For use-cases such as :ref:`codecs <mem2mem>`, the request API can be used
to associate specific controls to
be applied by the driver for the OUTPUT buffer, allowing user-space
to queue many such buffers in advance. It can also take advantage of requests'
ability to capture the state of controls when the request completes to read back
information that may be subject to change.
Put into code, after obtaining a request, user-space can assign controls and one
OUTPUT buffer to it:
.. code-block:: c
struct v4l2_buffer buf;
struct v4l2_ext_controls ctrls;
int req_fd;
...
if (ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd))
return errno;
...
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
if (ioctl(codec_fd, VIDIOC_S_EXT_CTRLS, &ctrls))
return errno;
...
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
buf.request_fd = req_fd;
if (ioctl(codec_fd, VIDIOC_QBUF, &buf))
return errno;
Note that it is not allowed to use the Request API for CAPTURE buffers
since there are no per-frame settings to report there.
Once the request is fully prepared, it can be queued to the driver:
.. code-block:: c
if (ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE))
return errno;
User-space can then either wait for the request to complete by calling poll() on
its file descriptor, or start dequeuing CAPTURE buffers. Most likely, it will
want to get CAPTURE buffers as soon as possible and this can be done using a
regular :ref:`VIDIOC_DQBUF <VIDIOC_QBUF>`:
.. code-block:: c
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(codec_fd, VIDIOC_DQBUF, &buf))
return errno;
Note that this example assumes for simplicity that for every OUTPUT buffer
there will be one CAPTURE buffer, but this does not have to be the case.
We can then, after ensuring that the request is completed via polling the
request file descriptor, query control values at the time of its completion via
a call to :ref:`VIDIOC_G_EXT_CTRLS <VIDIOC_G_EXT_CTRLS>`.
This is particularly useful for volatile controls for which we want to
query values as soon as the capture buffer is produced.
.. code-block:: c
struct pollfd pfd = { .events = POLLPRI, .fd = req_fd };
poll(&pfd, 1, -1);
...
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
if (ioctl(codec_fd, VIDIOC_G_EXT_CTRLS, &ctrls))
return errno;
Once we don't need the request anymore, we can either recycle it for reuse with
:ref:`MEDIA_REQUEST_IOC_REINIT`...
.. code-block:: c
if (ioctl(req_fd, MEDIA_REQUEST_IOC_REINIT))
return errno;
... or close its file descriptor to completely dispose of it.
.. code-block:: c
close(req_fd);
Example for a Simple Capture Device
-----------------------------------
With a simple capture device, requests can be used to specify controls to apply
for a given CAPTURE buffer.
.. code-block:: c
struct v4l2_buffer buf;
struct v4l2_ext_controls ctrls;
int req_fd;
...
if (ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd))
return errno;
...
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
if (ioctl(camera_fd, VIDIOC_S_EXT_CTRLS, &ctrls))
return errno;
...
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
buf.request_fd = req_fd;
if (ioctl(camera_fd, VIDIOC_QBUF, &buf))
return errno;
Once the request is fully prepared, it can be queued to the driver:
.. code-block:: c
if (ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE))
return errno;
User-space can then dequeue buffers, wait for the request completion, query
controls and recycle the request as in the M2M example above.