VIDIOC_REQBUFS Buffer Allocation Guide-Free Linux Device Drivers Course

VIDIOC_REQBUFS Buffer Allocation Guide

Free Linux Kernel Development Course — V4L2 User Space API, Part 4

Chapter 9
Lecture 4
Video4Linux2
Kernel 6.x

Topics Covered In This Free Linux Device Drivers Course Lecture

VIDIOC_G_PARM VIDIOC_S_PARM VIDIOC_REQBUFS v4l2_requestbuffers V4L2_MEMORY_USERPTR V4L2_CAP_TIMEPERFRAME Frame Rate Negotiation

Welcome back to EmbeddedPathashala’s free Linux kernel development course. In the previous lecture of this free embedded Linux course we negotiated the pixel format of a video capture device using VIDIOC_G_FMT and VIDIOC_S_FMT. Format negotiation only tells the driver what each frame looks like — it says nothing about how fast frames should arrive, and it says nothing about where those frames will actually be stored in memory. This lecture closes both gaps. We will look at how a V4L2 capture application asks the driver for a specific frame rate using VIDIOC_G_PARM/VIDIOC_S_PARM, and then walk through the first half of buffer management: the VIDIOC_REQBUFS ioctl and the struct v4l2_requestbuffers structure that tells the kernel how many buffers to allocate, of what type, and using which memory model. By the end you will be able to request user-pointer buffers and allocate the matching user-space memory correctly on a modern kernel.

What You Will Learn

  • How V4L2 frame rate negotiation works through struct v4l2_streamparm and struct v4l2_captureparm
  • Why a driver is allowed to silently coerce your requested frame rate, and how to detect it
  • The exact meaning of every field in struct v4l2_requestbuffers
  • How modern kernels expose supported streaming I/O methods without trial-and-error probing
  • How to request and allocate V4L2_MEMORY_USERPTR buffers correctly
  • Common mistakes that cause VIDIOC_REQBUFS to fail with EINVAL or EBUSY

Prerequisites

  • Completion of the earlier lectures in this V4L2 user-space series (device open, capability query, format negotiation)
  • Comfort with the V4L2 ioctl catalog and the xioctl() EINTR-retry wrapper
  • Basic familiarity with a Linux system that exposes a /dev/videoN node (a USB webcam is enough)
  • A working C toolchain — this is part of our free embedded systems course series and assumes no prior kernel driver-writing experience

Why Frame Rate Negotiation Is a Separate Step

A camera sensor is not a single fixed-speed device. The same sensor mode that produces a 1920×1080 frame at 30 frames per second can often also run at 15 fps with lower bandwidth, or the driver may refuse to change the rate at all and simply report back whatever it is capable of. Because of this, V4L2 treats frame timing as a negotiation, not a command: the application proposes a rate, and the driver is free to grant it, coerce it to the nearest supported value, or ignore the request entirely on hardware that cannot vary its frame interval. This negotiation happens through the VIDIOC_G_PARM and VIDIOC_S_PARM ioctls, both of which operate on the same struct v4l2_streamparm used for both capture and output devices.

struct v4l2_streamparm and struct v4l2_captureparm

v4l2_streamparm is a union-style structure, exactly like v4l2_format from the previous lecture — its active member depends on the buffer type you set. For a capture device you fill in parm.capture, of type struct v4l2_captureparm. The two fields that matter for frame rate work are:

  • capability — a bitmask the driver fills in on VIDIOC_G_PARM. If the V4L2_CAP_TIMEPERFRAME bit is set, the driver supports changing the frame interval through VIDIOC_S_PARM. If it is clear, don’t bother calling VIDIOC_S_PARM — the driver will just reject or ignore it
  • timeperframe — a struct v4l2_fract with numerator/denominator fields expressing the interval between frames. A 30 fps stream is expressed as numerator = 1, denominator = 30, not as a plain integer — this lets V4L2 represent non-integer rates like 29.97 fps cleanly

Frame Rate Negotiation Flow

[ Application ] [ V4L2 Driver ] | | |— VIDIOC_G_PARM (query) ————->| | | || | | ||

On the latest stable kernel, this API surface has not changed structurally, but most modern sensor drivers built on the V4L2 subdev framework now also expose frame interval selection through VIDIOC_SUBDEV_S_FRAME_INTERVAL at the subdevice level and VIDIOC_ENUM_FRAMEINTERVALS at the video-node level, letting an application discover exactly which discrete or stepwise rates a given resolution supports before ever calling VIDIOC_S_PARM. We will cover frame interval enumeration in a later lecture of this free linux device drivers course; for now, VIDIOC_S_PARM remains the correct call to actually apply the rate.

Requesting a Frame Rate — Modern Example

Below is an original demo, ep_v4l2_parmbuf, rewritten against the current V4L2 headers. It first queries the current parameters, checks the capability bit, then requests 30 fps and reports whether the driver honoured it.

#include <stdio.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

#define EP_TARGET_FPS 30

int ep_set_frame_rate(int fd)
{
    struct v4l2_streamparm parm = {0};
    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    /* Step 1: find out if the driver supports variable frame rate */
    if (ioctl(fd, VIDIOC_G_PARM, &parm) == -1) {
        perror("VIDIOC_G_PARM");
        return -1;
    }

    if (!(parm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME)) {
        fprintf(stderr, "ep_v4l2_parmbuf: driver has a fixed frame rate\n");
        return 0; /* not fatal, just nothing to negotiate */
    }

    /* Step 2: request the target frame rate */
    memset(&parm, 0, sizeof(parm));
    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    parm.parm.capture.timeperframe.numerator   = 1;
    parm.parm.capture.timeperframe.denominator = EP_TARGET_FPS;

    if (ioctl(fd, VIDIOC_S_PARM, &parm) == -1) {
        perror("VIDIOC_S_PARM");
        return -1;
    }

    /* Step 3: the driver may have coerced our request */
    if (parm.parm.capture.timeperframe.denominator != EP_TARGET_FPS) {
        printf("ep_v4l2_parmbuf: fps coerced from %d to %d\n",
               EP_TARGET_FPS, parm.parm.capture.timeperframe.denominator);
    } else {
        printf("ep_v4l2_parmbuf: frame rate set to %d fps\n", EP_TARGET_FPS);
    }

    return 0;
}

Expected output on a UVC webcam that supports the request:

$ ./ep_v4l2_parmbuf /dev/video0
ep_v4l2_parmbuf: frame rate set to 30 fps

On hardware that can only run at 25 fps for the current resolution, you would instead see:

$ ./ep_v4l2_parmbuf /dev/video0
ep_v4l2_parmbuf: fps coerced from 30 to 25

VIDIOC_REQBUFS — Requesting Video Buffers

Once the format and frame rate are settled, the driver still owns no memory for actual video data. Video capture is a high-bandwidth job, so V4L2 never copies frame data through a single ioctl the way VIDIOC_G_FMT passes structure fields. Instead it uses a streaming I/O model: a pool of buffers is allocated once, then repeatedly handed back and forth between the application and the driver as frames are captured. The first step of setting up that pool is VIDIOC_REQBUFS, which takes a fresh struct v4l2_requestbuffers.

struct v4l2_requestbuffers Fields

  • count — the number of buffers you want. Two or three buffers is the practical minimum to avoid dropped frames under normal scheduling jitter; the driver may grant fewer than you asked for, and it writes the actual granted count back into this same field, so you must always re-read it after the call
  • type — the buffer type, an enum v4l2_buf_type value such as V4L2_BUF_TYPE_VIDEO_CAPTURE for a capture device or V4L2_BUF_TYPE_VIDEO_OUTPUT for an output device
  • memory — the streaming memory model: V4L2_MEMORY_MMAP (driver-allocated, kernel-mapped buffers), V4L2_MEMORY_USERPTR (application-allocated buffers handed to the driver), or V4L2_MEMORY_DMABUF (buffers imported from another DMA-BUF exporter, such as a GPU or another V4L2 device)
  • capabilities — on current kernels the driver fills this field on return with flags such as V4L2_BUF_CAP_SUPPORTS_MMAP, V4L2_BUF_CAP_SUPPORTS_USERPTR, and V4L2_BUF_CAP_SUPPORTS_DMABUF, telling you in one call exactly which memory models this driver accepts

Discovering Supported I/O Methods — Old Way vs Modern Way

Older driver code had no clean way to ask “which streaming methods does this driver support” — the only option was to call VIDIOC_REQBUFS once per memory type and see which calls succeeded or failed with EINVAL, then undo any buffers that were accidentally allocated in the process. Current kernels avoid this entirely: a single VIDIOC_REQBUFS call with count set to 0 allocates nothing but still returns the capabilities bitmask, letting an application query support cleanly before committing to a real allocation.

Memory TypeWho AllocatesTypical Use CaseCopy Overhead
V4L2_MEMORY_MMAPKernel driverMost common, simplest zero-copy pathNone (mapped)
V4L2_MEMORY_USERPTRApplication (malloc/aligned alloc)Custom buffer placement, legacy driversDriver may still copy internally
V4L2_MEMORY_DMABUFExternal exporter (GPU, another V4L2 device)Zero-copy pipelines between subsystemsNone (shared DMA-BUF)

Requesting User Pointer Buffers

Below, our original demo ep_v4l2_reqbufs requests four V4L2_MEMORY_USERPTR buffers and checks the granted count:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>

#define EP_BUF_COUNT 4

int ep_request_userptr_buffers(int fd, unsigned int *granted_count)
{
    struct v4l2_requestbuffers req = {0};

    req.count  = EP_BUF_COUNT;
    req.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_USERPTR;

    if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
        if (errno == EINVAL) {
            fprintf(stderr,
                "ep_v4l2_reqbufs: driver does not support user pointer I/O\n");
        } else {
            perror("VIDIOC_REQBUFS");
        }
        return -1;
    }

    if (req.count < 2) {
        fprintf(stderr,
            "ep_v4l2_reqbufs: insufficient buffer memory (got %u)\n", req.count);
        return -1;
    }

    printf("ep_v4l2_reqbufs: driver granted %u of %u requested buffers\n",
           req.count, EP_BUF_COUNT);

    *granted_count = req.count;
    return 0;
}

With user pointer I/O selected, the kernel does not allocate the buffer memory itself — the application must. Here is the matching allocation step:

struct ep_buffer {
    void   *start;
    size_t  length;
};

struct ep_buffer *ep_allocate_userptr_buffers(unsigned int count, size_t buf_size)
{
    struct ep_buffer *buffers = calloc(count, sizeof(*buffers));
    unsigned int i;

    if (!buffers) {
        fprintf(stderr, "ep_v4l2_reqbufs: out of memory\n");
        return NULL;
    }

    for (i = 0; i < count; ++i) {
        buffers[i].length = buf_size;
        buffers[i].start  = aligned_alloc(4096, buf_size);

        if (!buffers[i].start) {
            fprintf(stderr, "ep_v4l2_reqbufs: allocation failed for buffer %u\n", i);
            free(buffers);
            return NULL;
        }
    }

    return buffers;
}

Expected output:

$ ./ep_v4l2_reqbufs /dev/video0
ep_v4l2_reqbufs: driver granted 4 of 4 requested buffers

Notice the switch from plain malloc() in old reference code to aligned_alloc(4096, buf_size) here. On current kernels, user-pointer buffers handed to a DMA-capable capture driver generally need to be page-aligned so the kernel can pin the correct set of pages for DMA; unaligned buffers are a frequent cause of VIDIOC_QBUF failures we will cover in the next lecture when we enqueue these buffers for capture.

Real-World Use Cases

Frame rate negotiation matters most in bandwidth-constrained pipelines — a USB 2.0 webcam streaming uncompressed YUYV at high resolution simply cannot sustain 60 fps, so applications like video conferencing software query V4L2_CAP_TIMEPERFRAME and fall back gracefully. User-pointer buffer allocation shows up in embedded vision pipelines where frames need to land in a specific pre-registered memory region — for example, a buffer that a downstream image signal processor or a GStreamer element has already reserved — rather than wherever the kernel driver happens to allocate memory internally.

Common Mistakes and Troubleshooting

  • Calling VIDIOC_S_PARM without checking V4L2_CAP_TIMEPERFRAME first — many drivers simply ignore the call on fixed-rate hardware; always check the capability bit so your logs aren’t misleading
  • Assuming the requested frame rate was granted — always re-read timeperframe.denominator after VIDIOC_S_PARM returns
  • Ignoring the granted req.count after VIDIOC_REQBUFS — the driver can silently grant fewer buffers than requested; code that assumes the original count will index out of bounds later
  • Calling VIDIOC_REQBUFS a second time while buffers are still queued — this returns EBUSY; you must stream off and free existing buffers first
  • Using unaligned malloc() for USERPTR buffers — leads to intermittent VIDIOC_QBUF failures on DMA-based capture hardware

Best Practices, Performance and Security Considerations

  • Prefer V4L2_MEMORY_MMAP or V4L2_MEMORY_DMABUF over V4L2_MEMORY_USERPTR when possible — both avoid the extra page-pinning and validation overhead the kernel performs on every user-supplied pointer
  • Request 3-4 buffers as a starting point; more buffers add latency, fewer buffers risk dropped frames under scheduling pressure
  • Always free previously allocated user-pointer memory before requesting a new buffer count, to avoid leaking pinned pages
  • Validate req.count and parm.parm.capture.timeperframe after every call rather than trusting the values you sent in — this is the single most common source of subtle bugs in V4L2 applications on this free linux kernel development course
  • From a security standpoint, user-pointer buffers are pinned and DMA-mapped by the kernel; never pass pointers into memory regions you don’t fully own, since a malicious or buggy capture path could otherwise write video data into unrelated process memory

Interview Questions

Why does V4L2_S_PARM sometimes not change the frame rate at all?

Because not every capture device supports variable frame rate. The driver advertises this through the V4L2_CAP_TIMEPERFRAME bit returned by VIDIOC_G_PARM. If that bit is clear, the hardware runs at a fixed rate for the current mode and VIDIOC_S_PARM has nothing to negotiate.

What is the difference between V4L2_MEMORY_MMAP and V4L2_MEMORY_USERPTR?

With MMAP, the kernel driver allocates the buffer memory and the application maps it into its address space with mmap(). With USERPTR, the application allocates the memory itself (typically page-aligned) and simply hands the pointer and length to the driver, which pins those pages for DMA.

Why must an application re-check req.count after VIDIOC_REQBUFS?

The driver is permitted to grant fewer buffers than requested, based on available memory or hardware limits, and it reports the actual number by overwriting the same count field. Code that keeps using the originally requested count instead of the returned one can index past the allocated buffer array.

Summary and Key Takeaways

Frame rate negotiation and buffer requesting are the two steps that turn a correctly formatted video stream into an actual capture pipeline. VIDIOC_G_PARM and VIDIOC_S_PARM let you query and request a frame interval, always subject to driver coercion that your code must detect. VIDIOC_REQBUFS then asks the kernel to reserve a pool of buffers of a given type and memory model, and on current kernels its returned capabilities field tells you exactly which streaming methods — MMAP, USERPTR, or DMABUF — the driver actually supports, without any trial-and-error probing. For V4L2_MEMORY_USERPTR specifically, the application owns the allocation, and page-aligned memory is the safest choice on modern DMA-capable hardware. In the next lecture of this free linux development course we complete the picture with VIDIOC_QUERYBUF and mmap() for kernel-allocated buffers, followed by the DMA-BUF export path.

Frequently Asked Questions

What does VIDIOC_REQBUFS actually do in the Linux kernel?

It asks the V4L2 driver to reserve a specified number of video buffers of a given memory type (MMAP, USERPTR, or DMABUF) for a given buffer type such as V4L2_BUF_TYPE_VIDEO_CAPTURE, and returns how many buffers were actually granted.

Can I call VIDIOC_REQBUFS with count set to zero?

Yes. On current kernels, requesting a count of zero frees any previously allocated buffers for that memory type and buffer type, and can also be used purely to read back the driver’s supported capabilities flags without allocating anything.

How many V4L2 buffers should I request for smooth video capture?

Three or four buffers is a practical starting point for most USB and embedded camera pipelines. Fewer buffers risk dropped frames under scheduling jitter, while significantly more buffers mainly add latency without a proportional benefit.

Why does the driver change the frame rate I requested with VIDIOC_S_PARM?

Hardware sensors typically only support a discrete or stepwise set of frame intervals for a given resolution. If your requested rate isn’t one of them, the driver coerces it to the nearest supported value and reports the actual rate back in the same structure.

Is V4L2_MEMORY_USERPTR still recommended on modern kernels?

It is still supported and useful when buffers must live in application-controlled memory, but MMAP and DMABUF are generally preferred today because they avoid the extra page-pinning overhead and integrate more cleanly with zero-copy pipelines.

What error does VIDIOC_REQBUFS return if a memory type isn’t supported?

The ioctl fails with errno set to EINVAL. On current kernels you can avoid guessing by first checking the capabilities bitmask returned from a zero-count VIDIOC_REQBUFS call.

Where can I practice this as part of a free Linux kernel development course?

EmbeddedPathashala’s free embedded systems course walks through this entire V4L2 chapter lecture by lecture, from device capability queries through to a full streaming capture loop, using original example code you can build and run on any Linux machine with a USB webcam.

Continue the Free Linux Kernel Development Course

Next up: requesting memory-mappable buffers with VIDIOC_QUERYBUF and mmap(), plus the DMA-BUF export path — part of EmbeddedPathashala’s free linux device drivers course.

Next Lecture Full Course Index

Leave a Reply

Your email address will not be published. Required fields are marked *