V4L2应用程序开发(1)
参考资料:韦东山第三期
v4l2应用程序开发分为两个部分,数据采集流程和控制流程两个部分
数据采集流程:
分为空闲链表和完成链表
驱动程序周而复始地做如下事情:
- 从硬件采集到数据
- 把"空闲链表"取出buffer,把数据存入buffer
- 把含有数据的buffer放入"完成链表"
APP也会周而复始地做如下事情:
- 监测"完成链表",等待它含有buffer
- 从"完成链表"中取出buffer
- 处理数据
- 把buffer放入"空闲链表"
链表操作示意图:

摄像头的应用操作流程,如下:
- open:打开设备节点/dev/videoX
- ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如
- 确认它是否是"捕获设备",因为有些节点是输出设备
- 确认它是否支持mmap操作,还是仅支持read/write操作
- ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
- ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式
- ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到,requery申请
- ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息、映射
- 如果申请到了N个buffer,这个ioctl就应该执行N次
- 执行mmap后,APP就可以直接读写这些buffer
- ioctl VIDIOC_QBUF:把buffer放入"空闲链表"
- 如果申请到了N个buffer,这个ioctl就应该执行N次
- ioctl VIDIOC_STREAMON:启动摄像头
- 这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
- poll/select
- ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer
- 处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer
- ioclt VIDIOC_QBUF:把buffer放入"空闲链表"
- ioctl VIDIOC_STREAMOFF:停止摄像头
控制流程:
应用程序接口
数据格式的枚举、获得和设置:
1、数据格式
枚举格式的时候,只是返回支持的pixelformat和描述
struct v4l2_fmtdesc fmtdesc; fmtdesc.index = 0; // 比如从0开始 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 指定type为"捕获" ioctl(vd->fd, VIDIOC_ENUM_FMT, &fmtdesc); // uapi/linux/videodev2.h struct v4l2_fmtdesc { __u32 index; /* Format number */ __u32 type; /* enum v4l2_buf_type */ __u32 flags; __u8 description[32]; /* Description string */ __u32 pixelformat; /* Format fourcc */ __u32 reserved[4]; }; enum v4l2_buf_type { V4L2_BUF_TYPE_VIDEO_CAPTURE = 1, // 摄像头一般就为捕获设备 V4L2_BUF_TYPE_VIDEO_OUTPUT = 2, V4L2_BUF_TYPE_VIDEO_OVERLAY = 3, V4L2_BUF_TYPE_VBI_CAPTURE = 4, V4L2_BUF_TYPE_VBI_OUTPUT = 5, V4L2_BUF_TYPE_SLICED_VBI_CAPTURE = 6, V4L2_BUF_TYPE_SLICED_VBI_OUTPUT = 7, #if 1 /* Experimental */ V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, #endif V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE = 10, V4L2_BUF_TYPE_SDR_CAPTURE = 11, /* Deprecated, do not use */ V4L2_BUF_TYPE_PRIVATE = 0x80, };
2、获取当前摄像头使用的格式
获取当前格式更加详细的信息,就需要使用VIDIOC_G_FRT
struct v4l2_format currentFormat; memset(¤tFormat, 0, sizeof(struct v4l2_format)); currentFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(vd->fd, VIDIOC_G_FMT, ¤tFormat); #if 0 struct v4l2_format { __u32 type; // 表示捕获设备 union { struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */ struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */ struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */ __u8 raw_data[200]; /* user-defined */ } fmt; }; /* * V I D E O I M A G E F O R M A T */ struct v4l2_pix_format { __u32 width; __u32 height; __u32 pixelformat; __u32 field; /* enum v4l2_field */ __u32 bytesperline; /* for padding, zero if unused */ __u32 sizeimage; __u32 colorspace; /* enum v4l2_colorspace */ __u32 priv; /* private data, depends on pixelformat */ __u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */ __u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */ __u32 quantization; /* enum v4l2_quantization */ __u32 xfer_func; /* enum v4l2_xfer_func */ }; #endif
3、设置当前格式
使用VIDIOC_S_FMT
struct v4l2_format fmt; memset(&fmt, 0, sizeof(struct v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = 1024; fmt.fmt.pix.height = 768; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; fmt.fmt.pix.field = V4L2_FIELD_ANY; int ret = ioctl(vd->fd, VIDIOC_S_FMT, &fmt);
选择输入源:
int value; ioctl(h->fd,VIDIOC_G_INPUT,&value); // 读到的value从0开始, 0表示第1个input源 int value = 0; // 0表示第1个input源 ioctl(h->fd,VIDIOC_S_INPUT,&value)
其他参数:
如果每一参数都提供一系列的ioctl cmd,那使用起来很不方便。
对于这些参数,APP使用对应ID来选中它,然后使用VIDIOC_QUERYCTRL、VIDIOC_G_CTRL、VIDIOC_S_CTRL来操作它。
不同参数的ID值不同。以亮度Brightness为例,有如下调用方法:
1、查询
struct v4l2_queryctrl qctrl; memset(&qctrl, 0, sizeof(qctrl)); qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; ioctl(fd, VIDIOC_QUERYCTRL, &qctrl); /* Used in the VIDIOC_QUERYCTRL ioctl for querying controls */ struct v4l2_queryctrl { __u32 id; __u32 type; /* enum v4l2_ctrl_type */ __u8 name[32]; /* Whatever */ __s32 minimum; /* Note signedness */ __s32 maximum; __s32 step; __s32 default_value; __u32 flags; __u32 reserved[2]; };
2、获得当前值
struct v4l2_control c; c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; ioctl(h->fd, VIDIOC_G_CTRL, &c); /* * C O N T R O L S */ struct v4l2_control { __u32 id; __s32 value; };
3、设置
struct v4l2_control c; c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; c.value = 99; ioctl(h->fd, VIDIOC_S_CTRL, &c);
理解接口:

SU:select unit,选择输入源
PU:processiong unit,用于调整亮度、对比度、色度等
EU:encoding unit,对采集到的数据进行个性化处理的功能

ID会传递给PU
app->驱动->(id, val)->硬件
操作方法:
使用ioctl操作设备节点/dev/video0时,不同的ioctl操作可能是video control接口,或者video streaming接口
跟视频流相关的操作,比如VIDIOC_ENUM_FMT,VIDIOC_G_FMT,VIDIOC_S_FMT,VIDIOC_STREAMON,VIDIOC_STREAMOFF,是操作video streaming的接口
其他的接口,大多是video control接口
从驱动和硬件角度来看,要操作video control接口,需要指明:
1、entity:要操作的是那个terminal或者unit,比如PU
2、control selector:要操作entity里面的那个控制项,比如亮度
3、控制项里面的哪些位:比如camera terminal里的CT_PANTILT_RELATIVE_CONTROL控制项对应的32位数据,其中前16位对应PAN控制(左右转动),后16位对应TILE控制(上下转动)
但是应用程序不关心这些,使用一个ID来指定entity、control selector、哪些位:
struct v4l2_control c; c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0; c.value = 99; ioctl(h->fd, VIDIOC_S_CTRL, &c); /* * C O N T R O L S */ struct v4l2_control { __u32 id; __s32 value; };
支持V4L2_CAP_STREAMING就可以用mmap函数,不支持的话就只能用V4L2_CAP_READWRITE了
分类:
Camera / V4L2
, Camera
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人