v4l2子系统学习(一)V4L2应用程序编程
文章目录
- 1、声明
- 2、前言
- 3、数据采集流程
- 3.1、buffer的管理
- 3.2、完整的使用流程
- 4、应用程序编写
- 5、测试
1、声明
本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。
韦老师的《驱动大全》:商品详情
其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git
2、前言
本章只从应用的角度去介绍如何使用v4l2的接口来获取图像。在v4l2应用编程中,主要都是通过ioctl命令来完成一系列任务,如查询设备能力使用VIDIOC_QUERYCAP
,申请buffer使用VIDIOC_REQBUFS
等等。
3、数据采集流程
3.1、buffer的管理
使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer。
摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。
驱动程序周而复始地做如下事情:
- 从硬件采集到数据。
- 把"空闲链表"取出buffer,把数据存入buffer。
- 把含有数据的buffer放入"完成链表"。
APP也会周而复始地做如下事情:
- 监测"完成链表",等待它含有buffer。
- 从"完成链表"中取出buffer。
- 处理数据。
- 把buffer放入"空闲链表"
链表操作示意图如下:
3.2、完整的使用流程
下面列出一个获取图像的完成流程:
- open:打开设备节点/dev/videoX。
- ioctl VIDIOC_QUERYCAP:查询能力,比如
- 确认它是否是"捕获设备",因为有些节点是输出设备。
- 确认它是否支持mmap操作,还是仅支持read/write操作。
- ioctl VIDIOC_ENUM_FMT:枚举所支持的图像格式。
- ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式。
- ioctl VIDIOC_REQBUFS:通知驱动程序申请buffer,APP中可以表明需要申请多少个buffer,但驱动程序不一定能申请到。
- 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:停止摄像头。
4、应用程序编写
/* v4l2_test.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <linux/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#include <poll.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
int fd;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_frmsizeenum fsenum;
int fmt_index = 0;
int frame_index = 0;
void *bufs[32];
int buf_cnt;
struct pollfd fds[1];
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
char filename[32];
int file_cnt = 0;
int i;
if(argc != 2)
{
printf("Usage : %s </dev/videoX>\n", argv[0]);
return -1;
}
/* 1、打开vedio设备节点 */
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("can not open %s\n", argv[1]);
return -1;
}
/* 2、查询能力 */
struct v4l2_capability cap;
memset(&cap, 0, sizeof(struct v4l2_capability));
if (0 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
{
if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0)
{
fprintf(stderr, "Error opening device %s: video capture not supported.\n",
argv[1]);
return -1;
}
if((cap.capabilities & V4L2_CAP_STREAMING) == 0)
{
fprintf(stderr, "%s does not support streaming i/o\n", argv[1]);
return -1;
}
}
else
{
printf("can not get capability\n");
return -1;
}
while(1)
{
/* 3、枚举格式 */
fmtdesc.index = fmt_index; // 从0开始枚举
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(0 != ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
break;
frame_index = 0;
printf("format %s,%d\n", fmtdesc.description, fmtdesc.pixelformat);
while(1)
{
/* 4、枚举这种格式所支持的帧大小 */
memset(&fsenum, 0, sizeof(struct v4l2_frmsizeenum));
fsenum.pixel_format = fmtdesc.pixelformat;
fsenum.index = frame_index;
if (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsenum) == 0)
{
printf("\tframesize %d: %d x %d\n", frame_index, fsenum.discrete.width, fsenum.discrete.height);
}
else
{
break;
}
frame_index++;
}
fmt_index++;
}
/* 5、设置格式 */
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(struct v4l2_format));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1280;
fmt.fmt.pix.height = 720;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (0 == ioctl(fd, VIDIOC_S_FMT, &fmt))
{
printf("set format ok: %d x %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height);
}
else
{
printf("can not set format\n");
return -1;
}
/* 6、申请buffer */
struct v4l2_requestbuffers rb;
memset(&rb, 0, sizeof(struct v4l2_requestbuffers));
rb.count = 32;
rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
rb.memory = V4L2_MEMORY_MMAP;
if (0 == ioctl(fd, VIDIOC_REQBUFS, &rb)) // 向驱动程序申请32个buffer,只是申请,不一定成功
{
/* 7、申请完成后,mmap这些buffer */
buf_cnt = rb.count;
for (i = 0; i < rb.count; i++) // 枚举这32个buffer
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (0 == ioctl(fd, VIDIOC_QUERYBUF, &buf)) // 查询第n个buffer的信息
{
bufs[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); // 使用mmap映射到用户空间
if(bufs[i] == MAP_FAILED) // 映射失败
{
perror("Unable to map buffer");
return -1;
}
}
else
{
printf("can not query buffer\n");
return -1;
}
}
printf("map %d buffers ok\n", buf_cnt);
}
else
{
printf("can not request buffers\n");
return -1;
}
/* 8、把所有buffer放入空闲链表 */
for (i = 0; i < buf_cnt; ++i)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (0 != ioctl(fd, VIDIOC_QBUF, &buf)) // 这里只是通知驱动程序,把buffer放入空闲链表。这里的buffer是驱动程序里的。
{
perror("Unable to queue buffer");
return -1;
}
}
printf("queue buffers ok\n");
/* 9、启动摄像头 */
if (0 != ioctl(fd, VIDIOC_STREAMON, &type))
{
perror("Unable to start capture");
return -1;
}
printf("start capture ok\n");
while (1)
{
/* 10、使用poll监听是否有buffer可以取出 */
memset(fds, 0, sizeof(fds));
fds[0].fd = fd;
fds[0].events = POLLIN;
if (poll(fds, 1, -1) > 0)
{
/* 11、把buffer取出队列 */
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(struct v4l2_buffer));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (0 != ioctl(fd, VIDIOC_DQBUF, &buf)) // 这里只是通知驱动程序,把buffer从完成链表中取出。这里的buffer是驱动程序里的。
{
perror("Unable to dequeue buffer");
return -1;
}
/* 12、把buffer的数据存为文件 */
sprintf(filename, "video_raw_data_%04d.jpg", file_cnt++);
int fd_file = open(filename, O_RDWR | O_CREAT, 0666);
if (fd_file < 0)
{
printf("can not create file : %s\n", filename);
}
printf("capture to %s\n", filename);
write(fd_file, bufs[buf.index], buf.bytesused); // 前面已经对驱动程序里申请的buffer进行了mmap,所以这里将用户空间的buffer写入文件中
close(fd_file);
/* 13、重新把buffer放入空闲队列 */
if (0 != ioctl(fd, VIDIOC_QBUF, &buf))
{
perror("Unable to queue buffer");
return -1;
}
}
}
if (0 != ioctl(fd, VIDIOC_STREAMOFF, &type))
{
perror("Unable to stop capture");
return -1;
}
printf("stop capture ok\n");
close(fd);
return 0;
}
5、测试
编译应用程序,插入usb摄像头,执行应用程序:
# 1、编译应用程序
gcc -o v4l2_test v4l2_test.c
# 2、插入摄像头
# 3、执行应用程序
./v4l2_test /dev/video10