Linux v4l2架构学习总链接
V4L2支持多种接口:capture(捕获)、output(输出)、overlay(预览)等等
这里主要分析capture
step 1 : 打开设备节点
int fd = open("/dev/video0", flag);
step 2 : 查询设备功能
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;
}
其中capabilities 字段标记着v4l2设备的功能
V4L2_CAP_VIDEO_CAPTURE 设备支持捕获功能
V4L2_CAP_VIDEO_OUTPUT 设备支持输出功能
V4L2_CAP_VIDEO_OVERLAY 设备支持预览功能
V4L2_CAP_STREAMING 设备支持流读写
V4L2_CAP_READWRITE 设备支持read、write方式读写
step 3 : 设置输入设备
一个设备可能有多个输入,比如摄像头控制器和摄像头接口是分离的,需要选择。
1,枚举输入设备
struct v4l2_input input;
input.index = 0;
while (!ioctl(fd, VIDIOC_ENUMINPUT, &input))
{
printf("input:%s\n", input.name);
++input.index;
}
2,设置输入设备
struct v4l2_input input;
input.index = index; //指定输入设备
if (ioctl(fd, VIDIOC_S_INPUT, &input) < 0)
{
printf("ERR(%s):VIDIOC_S_INPUT failed\n", __func__);
return -1;
}
step 4 : 设置图像格式
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++;
}
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; //像素格式
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;
}
step 5 : 设置缓存
v4l2设备读取数据的方式有2种,一种是read方式,一种是streaming方式。
read方式很容易理解,就是通过read函数读取
对于streaming就是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断的出队列和入队列的过程
如何去申请和映射缓存?
1,申请缓存
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;
}
2,映射缓存
为什么要映射缓存?
如果使用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);
}
3,将所有的缓存放入队列
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;
}
}
step 6 : 运行设备
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;
}
step 7 : 读取数据
获取图像数据其实就是一个不断地入队列出队列的过程,在出队列前要调用poll等到数据完成
1,poll或者select
2,出队列
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;
}
出队列后得到了缓存下标buffer.index,然后找到对应的缓存,通过映射过会的地址进行数据的读取
3,入队列
在数据读取完成后,要将buf重新放入队列
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;
}
读取数据就是在上面的这3步中一直不断的循环
step 8 : 关闭设备
1,停止设备
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;
}
2,取消映射
for(i = 0; i < nr_bufs; ++i)
munmap(buf[i].addr, buf[i]->length);
3,关闭设备
close(fd);
这里把应用编写的一个主要流程摘抄了出来,后面的文章会逐个分析ioctl,深入到驱动去学习v4l2。