Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

V4L2 — Video4Linux2

Overview

Video4Linux2 (V4L2) is the Linux kernel framework for video capture and output devices. It provides a unified API for webcams, TV tuners, video capture cards, and other multimedia devices. V4L2 is the second generation of the Video4Linux API (replacing the original Video4Linux), and has been the standard Linux video interface since kernel 2.6.x.

V4L2 handles video capture, video output, codec operations, and advanced media pipeline management through the media controller framework. It is the backbone of applications like video conferencing, streaming, surveillance, and video editing on Linux.

Architecture

┌─────────────────────────────────────────────┐
│              Userspace Applications          │
│  (GStreamer, FFmpeg, OBS, v4l2-ctl, etc.)   │
└──────────────────┬──────────────────────────┘
                   │ /dev/videoN, /dev/v4l-subdevN
┌──────────────────┴──────────────────────────┐
│              V4L2 Core Framework             │
│  ┌─────────────┐  ┌──────────────────────┐  │
│  │  Video Dev  │  │  Media Controller    │  │
│  │  /dev/video │  │  /dev/mediaN         │  │
│  └──────┬──────┘  └──────────┬───────────┘  │
│         │                    │              │
│  ┌──────┴────────────────────┴───────────┐  │
│  │         V4L2 Subsystem                │  │
│  │  Subdev API │ MC API │ Framework API  │  │
│  └──────────────┬───────────────────────┘  │
├─────────────────┴──────────────────────────┤
│              Device Drivers                 │
│  uvcvideo │ vivid │ ivtv │ gspca │ etc.   │
└────────────────────────────────────────────┘

Device Nodes

V4L2 creates several types of device nodes:

NodeDescriptionExample
/dev/videoNVideo device (capture/output)/dev/video0
/dev/v4l-subdevNSub-device (sensor, encoder)/dev/v4l-subdev0
/dev/mediaNMedia controller/dev/media0
/dev/vbiNVertical blanking interval/dev/vbi0
/dev/radioNRadio tuner/dev/radio0

Device Discovery

# List video devices
ls /dev/video*

# Detailed device info
v4l2-ctl --list-devices

# Example output:
# Integrated Camera (usb-0000:00:14.0-1):
#         /dev/video0
#         /dev/video1
#         /dev/media0

V4L2 ioctl Interface

Core Operations

#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

int fd = open("/dev/video0", O_RDWR);

/* Query capabilities */
struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
printf("Driver: %s\n", cap.driver);
printf("Card: %s\n", cap.card);
printf("Bus: %s\n", cap.bus_info);
printf("Capabilities: 0x%x\n", cap.capabilities);
/* V4L2_CAP_VIDEO_CAPTURE, V4L2_CAP_STREAMING, etc. */

Format Negotiation

/* Get current format */
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_G_FMT, &fmt);
printf("Current: %ux%u, fourcc: %c%c%c%c\n",
       fmt.fmt.pix.width, fmt.fmt.pix.height,
       fmt.fmt.pix.pixelformat & 0xFF,
       (fmt.fmt.pix.pixelformat >> 8) & 0xFF,
       (fmt.fmt.pix.pixelformat >> 16) & 0xFF,
       (fmt.fmt.pix.pixelformat >> 24) & 0xFF);

/* Set desired format */
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ioctl(fd, VIDIOC_S_FMT, &fmt);
/* Driver may adjust — check returned values */

/* Enumerate supported formats */
struct v4l2_fmtdesc fmtdesc = {0};
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
    printf("Format: %c%c%c%c - %s\n",
           fmtdesc.pixelformat & 0xFF,
           (fmtdesc.pixelformat >> 8) & 0xFF,
           (fmtdesc.pixelformat >> 16) & 0xFF,
           (fmtdesc.pixelformat >> 24) & 0xFF,
           fmtdesc.description);
    fmtdesc.index++;
}

Frame Rate

/* Get current frame rate */
struct v4l2_streamparm parm = {0};
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_G_PARM, &parm);
printf("Frame rate: %u/%u fps\n",
       parm.parm.capture.timeperframe.denominator,
       parm.parm.capture.timeperframe.numerator);

/* Set 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 = 30;  /* 30 fps */
ioctl(fd, VIDIOC_S_PARM, &parm);

Buffer Management

Memory-Mapped Buffers (MMAP)

/* Request buffers */
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);

/* Map buffers */
struct buffer {
    void *start;
    size_t length;
};
struct buffer buffers[4];

for (int i = 0; i < req.count; i++) {
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    ioctl(fd, VIDIOC_QUERYBUF, &buf);

    buffers[i].length = buf.length;
    buffers[i].start = mmap(NULL, buf.length,
                            PROT_READ | PROT_WRITE,
                            MAP_SHARED, fd, buf.m.offset);
}

/* Queue all buffers */
for (int i = 0; i < req.count; i++) {
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    ioctl(fd, VIDIOC_QBUF, &buf);
}

/* Start streaming */
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);

/* Capture loop */
while (1) {
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    ioctl(fd, VIDIOC_DQBUF, &buf);  /* Dequeue */

    /* Process buf.index frame */
    process_frame(buffers[buf.index].start, buf.bytesused);

    ioctl(fd, VIDIOC_QBUF, &buf);  /* Re-queue */
}

/* Stop streaming */
ioctl(fd, VIDIOC_STREAMOFF, &type);

User-Pointer Buffers

/* Userspace allocates buffers */
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
ioctl(fd, VIDIOC_REQBUFS, &req);

/* Allocate and register user buffers */
unsigned char *user_buf = malloc(width * height * 2);
struct v4l2_buffer buf = {0};
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.m.userptr = (unsigned long)user_buf;
buf.length = width * height * 2;
ioctl(fd, VIDIOC_QBUF, &buf);

DMA-BUF Buffers (Zero-Copy)

/* Request DMA-BUF file descriptors */
struct v4l2_requestbuffers req = {0};
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_DMABUF;
ioctl(fd, VIDIOC_REQBUFS, &req);

/* Export DMA-BUF FD */
struct v4l2_exportbuffer exp = {0};
exp.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
exp.index = 0;
ioctl(fd, VIDIOC_EXPBUF, &exp);
int dma_fd = exp.fd;
/* Can be passed to GPU, encoder, etc. */

Controls

V4L2 controls provide runtime adjustment of device parameters:

Standard Controls

/* List all controls */
struct v4l2_queryctrl qctrl = {0};
qctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL;
while (ioctl(fd, VIDIOC_QUERYCTRL, &qctrl) == 0) {
    printf("Control: %s (id=%u, min=%d, max=%d, default=%d)\n",
           qctrl.name, qctrl.id, qctrl.minimum,
           qctrl.maximum, qctrl.default_value);
    qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
}

/* Get control value */
struct v4l2_control ctrl = {0};
ctrl.id = V4L2_CID_BRIGHTNESS;
ioctl(fd, VIDIOC_G_CTRL, &ctrl);
printf("Brightness: %d\n", ctrl.value);

/* Set control value */
ctrl.value = 128;
ioctl(fd, VIDIOC_S_CTRL, &ctrl);

Extended Controls

/* Use extended controls for more features */
struct v4l2_ext_controls ext = {0};
struct v4l2_ext_control ctrls[2];

ctrls[0].id = V4L2_CID_BRIGHTNESS;
ctrls[0].value = 128;

ctrls[1].id = V4L2_CID_CONTRAST;
ctrls[1].value = 64;

ext.controls = ctrls;
ext.count = 2;
ioctl(fd, VIDIOC_S_EXT_CTRLS, &ext);

Common Controls

ControlIDDescription
BrightnessV4L2_CID_BRIGHTNESSImage brightness
ContrastV4L2_CID_CONTRASTImage contrast
SaturationV4L2_CID_SATURATIONColor saturation
HueV4L2_CID_HUEColor hue
White BalanceV4L2_CID_WHITE_BALANCE_TEMPERATUREColor temperature
ExposureV4L2_CID_EXPOSURE_ABSOLUTEExposure time
FocusV4L2_CID_FOCUS_ABSOLUTEFocus position
GainV4L2_CID_GAINAnalog/digital gain
Pan/TiltV4L2_CID_PAN_ABSOLUTECamera orientation
/* Enumerate menu items for a control */
struct v4l2_querymenu qmenu = {0};
qmenu.id = V4L2_CID_EXPOSURE_AUTO;

while (ioctl(fd, VIDIOC_QUERYMENU, &qmenu) == 0) {
    printf("Menu item %d: %s\n", qmenu.index, qmenu.name);
    qmenu.index++;
}

Media Controller

The media controller manages complex device topologies with multiple sub-devices:

Media Controller Graph

#include <linux/media.h>

int mfd = open("/dev/media0", O_RDWR);

/* Query topology */
struct media_entity_desc desc = {0};
desc.id = MEDIA_ENT_ID_FLAG_NEXT;

while (ioctl(mfd, MEDIA_IOC_ENUM_ENTITIES, &desc) == 0) {
    printf("Entity: %s (id=%u, type=0x%x, pads=%u)\n",
           desc.name, desc.id, desc.type, desc.pads);
    desc.id |= MEDIA_ENT_ID_FLAG_NEXT;
}
/* Enable a link between two entities */
struct media_link_desc link = {0};
link.source.entity = entity1_id;
link.source.index = 0;
link.sink.entity = entity2_id;
link.sink.index = 0;
link.flags = MEDIA_LNK_FL_ENABLED;
ioctl(mfd, MEDIA_IOC_SETUP_LINK, &link);

Sub-device Operations

/* Open sub-device */
int subdev_fd = open("/dev/v4l-subdev0", O_RDWR);

/* Get/set sub-device format */
struct v4l2_subdev_format sd_fmt = {0};
sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
sd_fmt.format.width = 1920;
sd_fmt.format.height = 1080;
sd_fmt.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
ioctl(subdev_fd, VIDIOC_SUBDEV_S_FMT, &sd_fmt);

Common Pixel Formats

FormatFourCCDescriptionBPP
YUYVYUYVYUV 4:2:2 packed16
NV12NV12YUV 4:2:2 semi-planar12
MJPEGMJPGMotion JPEGvaries
RGB24RGB324-bit RGB24
H.264H264H.264 encoded streamvaries
GREYGREY8-bit grayscale8
SRGGB10RG10Bayer 10-bit10

v4l2-ctl Utility

v4l2-ctl is the primary command-line tool for V4L2:

Device Information

# List all devices
v4l2-ctl --list-devices

# Query device capabilities
v4l2-ctl -d /dev/video0 --all

# List supported formats
v4l2-ctl -d /dev/video0 --list-formats-ext

Capture

# Capture a single frame
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=YUYV \
    --stream-mmap --stream-count=1 --stream-to=frame.raw

# Capture MJPEG video
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=MJPG \
    --stream-mmap --stream-count=30 --stream-to=video.mjpg

# Continuous capture (streaming)
v4l2-ctl -d /dev/video0 --stream-mmap --stream-to=- | ffplay -f rawvideo \
    -pixel_format yuyv422 -video_size 1920x1080 -

Controls

# List all controls
v4l2-ctl -d /dev/video0 -L

# Get a control
v4l2-ctl -d /dev/video0 -C brightness

# Set a control
v4l2-ctl -d /dev/video0 -c brightness=128

# Set multiple controls
v4l2-ctl -d /dev/video0 -c brightness=128,contrast=64,saturation=100

Format Information

# Current format
v4l2-ctl -d /dev/video0 --get-fmt-video

# Set format
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=MJPG

# List frame sizes for a format
v4l2-ctl -d /dev/video0 --list-framesizes=YUYV

# List frame intervals
v4l2-ctl -d /dev/video0 --list-frameintervals=width=1920,height=1080

Media Controller Tools

# Show media topology
media-ctl -d /dev/media0 --print-dot | dot -Tpng > topology.png

# List entities and pads
media-ctl -d /dev/media0 -p

# Set format on a pad
media-ctl -d /dev/media0 -V '"imx219 0-0010":0 [fmt:SRGGB10_1X10/3280x2464]'

# Enable links
media-ctl -d /dev/media0 -l '"imx219 0-0010":0 -> "csi2":0 [1]'

V4L2 Codec Interface

V4L2 supports hardware codec operations:

Memory-to-Memory (M2M) Codec

/* Open M2M device */
int fd = open("/dev/video10", O_RDWR);

/* Set output format (raw input) */
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
fmt.fmt.pix_mp.width = 1920;
fmt.fmt.pix_mp.height = 1080;
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
ioctl(fd, VIDIOC_S_FMT, &fmt);

/* Set capture format (encoded output) */
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = 1920;
fmt.fmt.pix_mp.height = 1080;
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264;
ioctl(fd, VIDIOC_S_FMT, &fmt);

Common V4L2 Drivers

DriverDevicesDescription
uvcvideoUSB webcamsUSB Video Class driver
vividVirtual test driverVirtual video test device
gspcaUSB webcamsLegacy USB webcam framework
ivtvTV capture cardsHauppauge PVR series
ivtvfbFramebufferIVTV framebuffer output
tw5864Video encoderTechwell TW5864 H.264 encoder
vim2mVirtual M2MVirtual memory-to-memory codec
imx-csii.MX SoCNXP i.MX camera interface
sun6i-csiAllwinnerAllwinner camera interface

Userspace Libraries

libv4l2

Provides transparent format conversion and device abstraction:

#include <libv4l2.h>

/* Use v4l2_* functions instead of open/ioctl/close */
int fd = v4l2_open("/dev/video0", O_RDWR);
struct v4l2_capability cap;
v4l2_ioctl(fd, VIDIOC_QUERYCAP, &cap);
v4l2_close(fd);

GStreamer

# Capture from webcam
gst-launch-1.0 v4l2src device=/dev/video0 ! videoconvert ! autovideosink

# Capture and encode
gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw,width=1920,height=1080 \
    ! x264enc ! mp4mux ! filesink location=output.mp4

FFmpeg

# Capture from V4L2
ffmpeg -f v4l2 -video_size 1920x1080 -i /dev/video0 -frames 1 output.jpg

# Continuous capture
ffmpeg -f v4l2 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
    -c:v libx264 output.mp4

Troubleshooting

# Check if device is recognized
dmesg | grep -i video
dmesg | grep -i camera

# Check permissions
ls -la /dev/video*
# Add user to video group
sudo usermod -aG video $USER

# Check for exclusive access
fuser /dev/video0

# Debug with v4l2-ctl
v4l2-ctl -d /dev/video0 --all
v4l2-ctl -d /dev/video0 --list-formats-ext

Further Reading

  • V4L2 API specification: Documentation/userspace-api/media/v4l/v4l2.rst
  • Media controller: Documentation/driver-api/media/v4l2-subdev.rst
  • V4L2 wiki: https://linuxtv.org/wiki/
  • v4l-utils: https://git.linuxtv.org/v4l-utils.git/
  • Source: drivers/media/v4l2-core/ — V4L2 core framework
  • Source: include/uapi/linux/videodev2.h — V4L2 API header
  • Related: Media Controller API — media pipeline management
  • Related: USB Video Class — UVC specification
  • Related: DMA-BUF — zero-copy buffer sharing