主页 > IT业界  > 

v4l2子系统学习(一)V4L2应用程序编程

v4l2子系统学习(一)V4L2应用程序编程

文章目录 1、声明2、前言3、数据采集流程3.1、buffer的管理3.2、完整的使用流程 4、应用程序编写5、测试

1、声明

本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。

韦老师的《驱动大全》:商品详情

其对应的讲义资料: 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/selectioctl 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

标签:

v4l2子系统学习(一)V4L2应用程序编程由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“v4l2子系统学习(一)V4L2应用程序编程