3. 枚举并选择视频格式

枚举并选择视频格式 学习卡片

任务目标

编写一个V4L2用户空间程序,枚举摄像头设备支持的像素格式(如YUYV、MJPEG)及每种格式对应的帧尺寸,并从中选择一种常用格式(YUYV)作为后续采集的配置格式。通过本案例理解struct v4l2_format、struct v4l2_fmtdesc和struct v4l2_frmsizeenum三个核心描述符的结构与使用方式。

环境与依赖

  • Linux系统,内核已启用V4L2框架,摄像头设备节点为/dev/video0(如不同请修改)
  • 无需额外库,仅使用Linux系统头文件,编译命令:gcc -o enum_fmt enum_fmt.c

关键完整代码

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

static void print_fourcc(__u32 fourcc) {
    printf("%c%c%c%c", (fourcc >> 0) & 0xFF, (fourcc >> 8) & 0xFF,
           (fourcc >> 16) & 0xFF, (fourcc >> 24) & 0xFF);
}

int main(void) {
    int fd = open("/dev/video0", O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { perror("QUERYCAP"); return 1; }
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        fprintf(stderr, "设备不支持视频采集\n"); return 1;
    }

    printf("设备: %s\n", cap.card);

    /* 1. 枚举像素格式 */
    struct v4l2_fmtdesc fmtdesc;
    memset(&fmtdesc, 0, sizeof(fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    printf("\n支持的像素格式:\n");
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
        printf("  索引%u: ", fmtdesc.index);
        print_fourcc(fmtdesc.pixelformat);
        printf("  (%s)\n", fmtdesc.description);
        fmtdesc.index++;
    }

    /* 2. 枚举YUYV格式的帧尺寸 */
    struct v4l2_frmsizeenum frmsize;
    memset(&frmsize, 0, sizeof(frmsize));
    frmsize.pixel_format = V4L2_PIX_FMT_YUYV;
    printf("\nYUYV支持的帧尺寸:\n");
    while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) {
        if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
            printf("  %ux%u\n", frmsize.discrete.width, frmsize.discrete.height);
        } else if (frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS ||
                   frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
            printf("  范围: %u-%u x %u-%u\n",
                   frmsize.stepwise.min_width, frmsize.stepwise.max_width,
                   frmsize.stepwise.min_height, frmsize.stepwise.max_height);
            break; /* 范围型只需打印一次 */
        }
        frmsize.index++;
    }

    /* 3. 选择YUYV 640x480并设置 */
    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_NONE;

    if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("S_FMT"); return 1;
    }

    /* 4. 回读确认实际生效的格式 */
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_G_FMT, &fmt) == 0) {
        printf("\n已设置格式: ");
        print_fourcc(fmt.fmt.pix.pixelformat);
        printf(" %ux%u\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
    }

    close(fd);
    return 0;
}

运行与验证

  1. 编译:gcc -o enum_fmt enum_fmt.c
  2. 运行:./enum_fmt(确保当前用户有/dev/video0访问权限,必要时sudo)
  3. 通过条件:程序依次输出设备名称、所有支持的像素格式列表、YUYV格式的帧尺寸列表,最后显示已设置格式: YUYV 640x480。若摄像头不支持640x480的YUYV,VIDIOC_S_FMT会失败或回读尺寸与请求不同,此时应改为枚举结果中实际存在的尺寸。

核心结构说明

  • struct v4l2_fmtdesc:用于VIDIOC_ENUM_FMT,其中pixelformat为四字符码(FourCC),description为可读描述。
  • struct v4l2_frmsizeenum:用于VIDIOC_ENUM_FRAMESIZES,需先指定pixel_format。类型为离散时用discrete,连续或步进时用stepwise。
  • struct v4l2_format:用于VIDIOC_S_FMT/VIDIOC_G_FMT,采集场景使用fmt.pix子结构,包含宽、高、像素格式和场设置。

注意:VIDIOC_S_FMT可能因设备限制调整参数(如宽度对齐),因此设置后必须用VIDIOC_G_FMT回读实际值,后续采集需以回读值为准。