2019年12月4日开发手记
OK,经过昨天对V4L2工作流程的学习,现在已经大体了解了V4L2的工作原理,现在开始对V4L2的API的学习,目标:1、打开摄像头 2、储存图像 3、关闭摄像头,API网址:Linux Media Infrastructure userspace API — The Linux Kernel documentation https://linuxtv.org/downloads/v4l-dvb-apis/media_uapi.html
具体流程如下:
1、打开设备:
static void open_device()
{
dev_name = “/dev/video0”;
fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);
}
2、初始化设备:
static void init_device()
介于初始化设备函数过于复杂,这里就直接copy官方例程中的init_device()函数,此处就不多做叙述了,例程:https://blog.csdn.net/snow_rain_1314/article/details/85072669 。在初始化设备时主要完成了对用户空间的获取,图像格式以及I/O模式的确定等,这里我们的I/O模式为内存映射即Memory Mapping(MMAP)模式。
3、捕获图像:
static void start_capturing(void)
{
unsigned int i;
enum v4l2_buf_type type;
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
errno_exit(“VIDIOC_QBUF”);
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
errno_exit(“VIDIOC_STREAMON”);
}
4、主循环
static void mainloop(void)
{
unsigned int count;
count = frame_count;//帧数70
while (count– > 0) {
for (;;) {/*select()机制中提供一fd_set的数据结构,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,
当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件可读*/
fd_set fds;
struct timeval tv;//时间值
int r;
FD_ZERO(&fds); /*将set清零使集合中不含任何fd*/
FD_SET(fd, &fds);/*将fd加入set集合*/
/* Timeout. */
tv.tv_sec = 2; //2s
tv.tv_usec = 0;//0微妙
r = select(fd + 1, &fds, NULL, NULL, &tv);//select操作,用于确定一个或多个套接口的状态,返回满足条件的套接口的数目,最多只等待两秒,
if (-1 == r) { //所有描述符集清零
if (EINTR == errno)//如果错误类型为4(中断系统呼叫)
continue;
errno_exit(“select”);
}
if (0 == r) { //超时
fprintf(stderr, “select timeout\\n”);
exit(EXIT_FAILURE);
}
if (read_frame())//读取帧
break;
/* EAGAIN – continue select loop. */
}
}
}
5、在主循环的过程中读取帧
static int read_frame(void)
{
struct v4l2_buffer buf;//v4l2中临时缓冲器
unsigned int i;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit(“VIDIOC_DQBUF”);
}
}
assert(buf.index < n_buffers);//断言,索引小于缓冲区个数
process_image(buffers[buf.index].start, buf.bytesused);//进程映射
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
errno_exit(“VIDIOC_QBUF”);
}
6、读取帧时开始存储图像
static void process_image(const void *p, int size)
{
FILE *fp = fopen(“a.jpg”,”w”);
if (out_buf)
{
fwrite(p, size, 1, fp);
fclose(fp);
//printf(“1”);
}
fflush(stderr);
fprintf(stderr, “Can not open it!\n”);
fflush(stdout);
}
7、停止捕获
static void stop_capturing(void)
{
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
errno_exit(“VIDIOC_STREAMOFF”);
break;
}
}
8、释放内存
static void uninit_device(void)
{
unsigned int i;
switch (io) {
case IO_METHOD_READ:
free(buffers[0].start);
break;
case IO_METHOD_MMAP:
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap(buffers[i].start, buffers[i].length))
errno_exit(“munmap”);
break;
case IO_METHOD_USERPTR:
for (i = 0; i < n_buffers; ++i)
free(buffers[i].start);
break;
}
free(buffers);
}
9、关闭设备
static void close_device(void)
{
if (-1 == close(fd))
errno_exit(“close”);
fd = -1;
}
10、输出异常
fprintf(stderr, “\\n”);
使用V4L2打开摄像头获取图像的流程就是这样,下一步就是使用python调用这个程序,取代cv2.imwrite。