camera驱动和设备注册完毕后,会在/dev目录下生成相应的设备节点,用户层可以通过这些设备节点对设备进行配置、查询、数据获取等一系列的操作。
v4l2图像采集的基本流程:
1、打开视频设备文件
//videoX根据摄像头设备选择实际设备节点文件
int fd = open("/dev/videoX", flag);
可以使用阻塞或非阻塞的模式打开设备节点,在非阻塞的情况下,即使未采集到视频数据,驱动任然会将缓存返回给应用程序。
2、查询视频设备属性、功能
v4l2可以通过ioctl接口查询、配置设备属性和功能,常用的ioctl命令标志符:
VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_S_FMT:设置当前驱动的频捕获格式
VIDIOC_G_FMT:读取当前驱动的频捕获格式
VIDIOC_TRY_FMT:验证当前驱动的显示格式
VIDIOC_CROPCAP:查询驱动的修剪能力
VIDIOC_S_CROP:设置视频信号的边框
VIDIOC_G_CROP:读取视频信号的边框
VIDIOC_QBUF:把数据从缓存中读取出来
VIDIOC_DQBUF:把数据放回缓存队列
VIDIOC_STREAMON:开始视频显示函数
VIDIOC_STREAMOFF:结束视频显示函数
VIDIOC_QUERYSTD:检查当前视频设备支持的标准,例如PAL或NTSC。
对于 VIDIOC_QUERYCAP,使用如下:
/*
capabilities中的比特位表示是否支持设备的相应功能,
V4L2_CAP_VIDEO_CAPTURE 设备支持捕获功能
V4L2_CAP_VIDEO_OUTPUT 设备支持输出功能
V4L2_CAP_VIDEO_OVERLAY 设备支持预览功能
V4L2_CAP_STREAMING 设备支持流读写
V4L2_CAP_READWRITE 设备支持read、write方式读写
*/
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};
struct v4l2_capability cap;
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0)
{
printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);
return -1;
}
3、设置视频设备参数,如图像格式
3.1 枚举支持的像素格式:
struct v4l2_fmtdesc fmtdesc;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;
while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
{
printf("fmt:%s\n", fmtdesc.description);
fmtdesc.index++;
}
3.2 设置图像格式
struct v4l2_format v4l2_fmt;
memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //数据流类型
v4l2_fmt.fmt.pix.width = width; //宽度
v4l2_fmt.fmt.pix.height = height; //高度
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //像素格式为yuyv格式
v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
{
printf("ERR(%s):VIDIOC_S_FMT failed\n", __func__);
return -1;
}
4、申请帧缓冲
通过流的方式获取视频数据,需要申请缓冲,这些缓冲会被视频采集输入队列和视频采集输出队列所维护,在采集视频流时,视频采集输入队列取出缓冲存储视频流数据,然后将缓冲放入视频采集输出队列中等待应用程序的获取。应用程序从视频采集输出队列中获取缓冲并处理完数据后,会将缓冲放回视频采集输入队列,等待下一次的视频流获取。
缓冲申请如下
struct v4l2_requestbuffers req;
req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
return -1;
}
5、内存地址映射,将第4步骤中申请的缓冲地址映射到用户空间
通过read方式读取数据,需要将数据从内核空间拷贝到用户空间,效率较低,而是用内存映射的方式则能够将内核空间的内存映射到用户空间,不需要再进行拷贝。
以下是缓冲映射,映射前需要检查一下待映射的缓冲是否正常。
struct v4l2_buffer v4l2_buffer;
void* addr;
for(i = 0; i < nr_bufs; i++)
{
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //想要查询的缓存
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
/* 查询缓存信息 */
ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
if(ret < 0)
{
printf("Unable to query buffer.\n");
return -1;
}
/* 映射 */
addr = mmap(NULL /* start anywhere */ ,
v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, v4l2_buffer.m.offset);
}
6、将申请的缓冲放入视频采集输入队列
struct v4l2_buffer v4l2_buffer;
for(i = 0; i < nr_bufs; i++)
{
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
/* 想要放入队列的缓存 */
v4l2_buffer.index = i;
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
if(ret < 0)
{
printf("Unable to queue buffer.\n");
return -1;
}
}
7、开启视频流采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
printf("ERR(%s):VIDIOC_STREAMON failed\n", __func__);
return -1;
}
8、从视频采集输出队列中取出已采集好帧数据的缓冲
在采集视频流时,v4l2会从视频采集输入队列取出缓冲存储视频流数据,然后将缓冲放入视频采集输出队列中等待应用程序的获取。
以下是通过poll等待缓冲放入输出队列并从输出队列获取缓冲的过程:
struct v4l2_buffer buffer;
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0)
{
printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame\n", __func__);
return -1;
}
9、数据处理完上一步骤取出的缓存后,将缓冲重新放入视频采集输入队列
将缓冲重新放入视频采集输入队列,等待用于新到来的视频数据的存储。应用代码过程如下:
struct v4l2_buffer v4l2_buf;
v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
v4l2_buf.index = i; //指定buf
if (ioctl(fd, VIDIOC_QBUF, &v4l2_buf) < 0)
{
printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);
return -1;
}
10、停止视频流采集
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
{
printf("ERR(%s):VIDIOC_STREAMOFF failed\n", __func__);
return -1;
}
11、关闭设备
关闭设备前要释放资源,比如取消映射,如下:
for(i = 0; i < nr_bufs; ++i)
munmap(buf[i].addr, buf[i]->length);
关闭设备
close(fd);
参考:
从应用调用vivi驱动分析v4l2 -- 应用代码编写_dianlong_lee的博客-CSDN博客