4. 设置采集分辨率与像素格式

任务说明

本案例目标是通过V4L2的VIDIOC_S_FMT ioctl,将摄像头采集分辨率设置为640x480、像素格式设置为YUYV,然后使用VIDIOC_G_FMT读取驱动实际生效的格式,验证驱动是否正确接受或调整了参数。该操作是任何V4L2采集应用的第一步——只有格式协商成功,后续的缓冲区申请和帧采集才有意义。

核心实现

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

static int xioctl(int fd, unsigned long req, void *arg)
{
    int ret;
    do {
        ret = ioctl(fd, req, arg);
    } while (ret == -1 && errno == EINTR);
    return ret;
}

int main(int argc, char *argv[])
{
    const char *dev = argc > 1 ? argv[1] : "/dev/video0";
    int fd = open(dev, O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = 640;
    fmt.fmt.pix.height = 480;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;

    if (xioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
        perror("VIDIOC_S_FMT");
        close(fd);
        return 1;
    }

    printf("S_FMT 返回: width=%u height=%u format=%c%c%c%c\n",
           fmt.fmt.pix.width, fmt.fmt.pix.height,
           (fmt.fmt.pix.pixelformat >> 0) & 0xff,
           (fmt.fmt.pix.pixelformat >> 8) & 0xff,
           (fmt.fmt.pix.pixelformat >> 16) & 0xff,
           (fmt.fmt.pix.pixelformat >> 24) & 0xff);

    /* 重新读取当前实际生效的格式 */
    struct v4l2_format cur;
    memset(&cur, 0, sizeof(cur));
    cur.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (xioctl(fd, VIDIOC_G_FMT, &cur) == -1) {
        perror("VIDIOC_G_FMT");
        close(fd);
        return 1;
    }

    printf("G_FMT 读取: width=%u height=%u format=%c%c%c%c\n",
           cur.fmt.pix.width, cur.fmt.pix.height,
           (cur.fmt.pix.pixelformat >> 0) & 0xff,
           (cur.fmt.pix.pixelformat >> 8) & 0xff,
           (cur.fmt.pix.pixelformat >> 16) & 0xff,
           (cur.fmt.pix.pixelformat >> 24) & 0xff);

    if (cur.fmt.pix.width == 640 && cur.fmt.pix.height == 480 &&
        cur.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) {
        printf("验证通过: 驱动接受640x480 YUYV\n");
    } else {
        printf("验证结果: 驱动调整了参数(实际为%ux%u)\n",
               cur.fmt.pix.width, cur.fmt.pix.height);
    }

    close(fd);
    return 0;
}

编译与运行

# 编译(需要linux/videodev2.h头文件,通常由libv4l-dev或内核头文件提供)
gcc -o set_fmt set_fmt.c

# 运行(默认打开/dev/video0,也可指定其他设备节点)
./set_fmt /dev/video0

验证方法

程序执行后观察输出:

  • S_FMT返回:显示驱动对请求参数的初步响应,通常与请求一致,但部分驱动会静默调整(如对齐到step、改变格式)。
  • G_FMT读取:这是驱动最终生效的格式,是验证的唯一依据。
  • 判定标准:若G_FMT读回640x480且格式为YUYV,说明驱动完全接受请求;若读回其他值,说明驱动不支持请求参数并做了调整,此时应检查驱动支持的格式列表(可通过VIDIOC_ENUM_FMT和VIDIOC_ENUM_FRAMESIZES查询)。

常见问题

  • 打开设备失败:确认设备节点存在且有读写权限(sudo或加入video组)。
  • ioctl返回EINVAL:设备不支持该类型缓冲区(如某些设备只支持V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE),需改用multi-planar API。
  • 驱动调整分辨率:部分USB摄像头仅支持固定分辨率列表,S_FMT会就近选择。此时应使用VIDIOC_ENUM_FRAMESIZES查询可用尺寸,选择驱动支持的值。