/*
* V4L2 像素格式与分辨率枚举示例
*
* 本例演示如何通过 V4L2 ioctl 接口:
* 1. 枚举摄像头设备支持的所有像素格式(VIDIOC_ENUM_FMT)
* 2. 对每种像素格式,枚举其支持的分辨率/帧尺寸(VIDIOC_ENUM_FRAMESIZES)
*
* 典型的 V4L2 枚举模式:
* 设置结构体 -> 设置 index -> 调用 ioctl -> index++ 直到 ioctl 返回负值
*/
#include <sys/ioctl.h> /* ioctl() */
#include <fcntl.h> /* open() */
#include <linux/videodev2.h> /* V4L2 相关结构体与 ioctl 宏 */
#include <unistd.h> /* close() */
#include <stdio.h> /* printf() */
#include <stdlib.h> /* EXIT_FAILURE 等 */
#include <string.h> /* memset() */
/* 默认摄像头设备节点,可通过修改此变量更换设备 */
static char *dev = "/dev/video0";
/*
* enum_framesizes - 枚举指定像素格式支持的帧尺寸(分辨率)
* @fd: 打开的 video 设备文件描述符
* @pixelformat: 要查询的像素格式 FourCC 码(如 V4L2_PIX_FMT_YUYV)
*
* 使用 VIDIOC_ENUM_FRAMESIZES ioctl 查询驱动报告的分辨率信息。
* v4l2_frmsizeenum.type 有三种可能,分别对应不同的查询方式:
*
* V4L2_FRMSIZE_TYPE_DISCRETE 离散列表
* 驱动提供一个分辨率列表。需要递增 frmsize.index 反复调用,
* 每次返回一个 (width, height) 对,直到 ioctl 返回负值(-1)表示枚举结束。
* 常见于硬件编码器或老式 sensor。
*
* V4L2_FRMSIZE_TYPE_STEPWISE 步进范围
* 驱动报告一个矩形范围 (min_width~max_width, min_height~max_height)
* 以及水平和垂直的步进值 (step_width, step_height)。
* 实际可用分辨率为范围内按步进递增的所有组合。
* 一次调用即可获得全部信息,无需循环。
*
* V4L2_FRMSIZE_TYPE_CONTINUOUS 连续范围
* 驱动报告 min~max 范围,此范围内任意分辨率都支持。
* 实际上与 STEPWISE 使用相同的 union stepwise 字段,但步进视为 1。
* 一次调用即可获得全部信息,无需循环。
*
* 返回值:无(结果直接 printf 打印)
*/
static void enum_framesizes(int fd, unsigned int pixelformat)
{
struct v4l2_frmsizeenum frmsize;
printf(" 分辨率:\n");
/*
* 离散列表需要多次调用。index 从 0 开始递增,
* 直到 ioctl 返回负值(表示没有更多分辨率了)。
* 对于 STEPWISE 和 CONTINUOUS,第一次调用就已经拿到结果,
* 第二次调用 ioctl 就会返回负值,因此也会直接退出循环。
*/
for (int i = 0; ; i++) {
memset(&frmsize, 0, sizeof(frmsize));
frmsize.pixel_format = pixelformat;
frmsize.index = i;
/* VIDIOC_ENUM_FRAMESIZES 根据 pixel_format + index 查询分辨率 */
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) < 0)
break;
switch (frmsize.type) {
case V4L2_FRMSIZE_TYPE_DISCRETE:
/* 离散分辨率:每次返回一个具体的宽高值 */
printf(" [%d] %dx%d\n", i,
frmsize.discrete.width,
frmsize.discrete.height);
break;
case V4L2_FRMSIZE_TYPE_STEPWISE:
/*
* 步进范围:min~max 范围内按 step 递增。
* 例如 min_width=320, max_width=1920, step_width=160 表示
* 支持 320, 480, 640, ..., 1920 这些宽度值。
* 一次调用即得全部信息,return 退出函数,不再继续循环。
*/
printf(" stepwise: %dx%d ~ %dx%d step %dx%d\n",
frmsize.stepwise.min_width, frmsize.stepwise.min_height,
frmsize.stepwise.max_width, frmsize.stepwise.max_height,
frmsize.stepwise.step_width, frmsize.stepwise.step_height);
return;
case V4L2_FRMSIZE_TYPE_CONTINUOUS:
/*
* 连续范围:min~max 之间任意分辨率都支持。
* 实际上驱动通常会限制一定的粒度,这里只是"名义上连续"。
* 一次调用即得全部信息,return 退出函数。
*/
printf(" continuous: %dx%d ~ %dx%d\n",
frmsize.stepwise.min_width, frmsize.stepwise.min_height,
frmsize.stepwise.max_width, frmsize.stepwise.max_height);
return;
}
}
}
/*
* enum_format_with_sizes - 枚举所有像素格式并打印其分辨率
* @fd: 打开的 video 设备文件描述符
*
* 分两步:
* 1. 使用 VIDIOC_ENUM_FMT 遍历驱动所有支持的像素格式
* 2. 对每种格式调用 enum_framesizes() 获取分辨率
*
* VIDIOC_ENUM_FMT 使用说明:
* - 设置 fmt.type 为 V4L2_BUF_TYPE_VIDEO_CAPTURE(捕获设备)
* - 从 fmt.index = 0 开始递增调用 ioctl
* - 每次返回一种像素格式的 pixelformat(FourCC 码)和 description(可读名称)
* - 当 ioctl 返回负值时表示所有格式已枚举完毕
* - 每次调用前必须使用 memset 清零结构体,避免内核解析残留数据
*
* 为什么需要先清零结构体:
* V4L2 ioctl 采用"结构体传入+传出"模式。内核在解析输入字段时,
* 如果结构体中有未初始化的字段(例如 padding 字节),可能导致
* 内核在不同内核版本间行为不一致。memset 清零确保所有字段初始
* 为 0,保证兼容性。
*/
static void enum_format_with_sizes(int fd)
{
struct v4l2_fmtdesc fmt;
printf("====== 枚举像素格式及支持的分辨率 ======\n");
/* memset 清零:避免结构体中的残留数据影响 ioctl 行为 */
memset(&fmt, 0, sizeof(fmt));
/* 设置缓冲区类型为视频捕获,其他类型还有 V4L2_BUF_TYPE_VIDEO_OUTPUT 等 */
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/*
* index 从 0 开始递增枚举。驱动会按顺序返回每种格式,
* 当枚举完所有格式后,ioctl 返回负值(errno 通常为 EINVAL)。
* 这种"递增 index 直到失败"的模式在 V4L2 枚举接口中非常常见
* (格式、分辨率、帧率、controls 等都采用此模式)。
*/
for (fmt.index = 0; ; fmt.index++) {
if (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) < 0)
break;
/* fmt.pixelformat 是 FourCC 码(如 0x56595559 即 "YUYV") */
printf(" [%d] 0x%08x %s\n",
fmt.index,
fmt.pixelformat,
fmt.description);
/* 对每种格式查询其支持的分辨率 */
enum_framesizes(fd, fmt.pixelformat);
}
printf("\n");
}
int main(void)
{
int fd;
/*
* 打开 V4L2 设备节点。
* O_RDWR:读写模式,既可用于查询(G_FMT/ENUM_FMT)也可用于设置(S_FMT)。
* 部分设备可能只需要 O_RDONLY,但 O_RDWR 兼容性最好。
*/
fd = open(dev, O_RDWR);
if (fd < 0) {
perror("open"); /* perror 会打印 "open: <系统错误信息>" */
return -1;
}
/* 枚举所有像素格式及其支持的分辨率 */
enum_format_with_sizes(fd);
close(fd);
return 0;
}
root@wyl:~/c-std/for-linux/099_other/v4l2# ./002_v4l2_fmt_pix
====== 枚举像素格式及支持的分辨率 ======
[0] 0x56595559 YUYV 4:2:2
分辨率:
[0] 96x200
[1] 200x96
[2] 8x8448
[3] 8448x8
[4] 4x2321
[5] 96x100
[6] 4x2637
[7] 4x14733
[8] 4x5188
[9] 5188x4
[10] 8x8642
[11] 8642x8
[12] 96x96
[13] 240x240