一、采集数据流程
申请buffer用来放置摄像头数据
-
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:停止摄像头
二、代码如下:
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;
/*申请buffer*/
if(0 == ioctl(fd, VIDIOC_REQBUFS,&rb))
{
buf_cnt = rb.count;
for(i = 0; i<rb.count;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_QUERYBUF,&buf))/*查询申请到buf是否成功*/
{
bufs[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, buf.m.offset);/*申请成功后,mmap这些buffer*/
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 ");
}
/*把所有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))
{
perror("Uable to queue buffer");
return -1;
}
}
printf("queue buffers ok\n");
/*启动摄像头*/
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(0 != ioctl(fd,VIDIOC_STREAMON, &type))
{
perror("Uable to start capture");
return -1;
}
printf("start capture ok\n");
while(1)
{
/*poll*/
memset(fds, 0, sizeof(fds));
fds[0].fd = fd;
fds[0].events = POLLIN;
if(1 ==poll(fds,1,-1))
{
/*把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))
{
perror("Unable to dequeue buffer");
return -1;
}
/*把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 creat file :%s \n", filename);
return -1;
}
write(fd_file, bufs[buf.index], buf.bytesused);
close(fd_file);
/*把buffer放入队列*/
if(0 != ioctl(fd, VIDIOC_QBUF,&buf))
{
perror("Uable to queue buffer");
return -1;
}
}
}
if(0 != ioctl(fd,VIDIOC_STREAMOFF, &type));
{
perror("Uable to stop capture");
return -1;
}
printf("stop capture ok\n");
close(fd);
输出结果为:将每一帧数据采集为jpg格式保存在当前目录下。
三、代码知识点补充
1、mmap()
bufs[i] = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, buf.m.offset);
mmap
函数用于将一个文件或设备(如视频设备)的内容映射到进程的地址空间。这样,可以通过指针直接访问文件或设备的内容,而不需要使用系统调用,如 read
或 write
,从而提高了访问效率。
参数解释
- addr: 指定映射的起始地址。通常设为
0
或NULL
,表示由内核决定映射区域的起始地址。 - length: 要映射的文件部分的长度。这里是
buf.length
,表示需要映射的缓冲区的大小。 - prot: 映射区域的保护方式。可以是以下几个值的组合:
PROT_READ
:页内容可以被读取。PROT_WRITE
:页内容可以被写入。PROT_EXEC
:页内容可以被执行。
- flags: 映射对象的类型、映射选项和页是否可以共享等。常用值有:
MAP_SHARED
:映射区内的写入数据会写回到原文件,同时对其他映射到该文件的进程可见。MAP_PRIVATE
:写入数据会产生一个写时拷贝(copy-on-write),对其他映射到该文件的进程不可见。
- fd: 要映射到内存的文件描述符。这里是视频设备文件描述符
fd
。 - offset: 文件映射的起始位置。通常是缓冲区的偏移量,这里是
buf.m.offset
。
返回值
mmap
成功时返回映射区的指针,失败时返回 MAP_FAILED
,并设置 errno
以指示错误。
2、perror()
perror
函数是 C 标准库中的一个函数,用于输出描述最近一次函数调用发生错误的错误信息。它会根据 errno
的值输出对应的错误消息。errno
是一个全局变量,用于记录最近一次系统调用或库函数调用错误的错误码。
perror
函数根据 errno
的值,查找并输出对应的错误消息。错误消息通常来自系统定义的一系列错误描述字符串。errno
由最近一次出错的系统调用或标准库函数设置。
errno
:errno
是由库函数和系统调用设置的全局变量,表示上一次操作的错误码。每个错误码对应一个错误消息。- 错误消息查找:
perror
根据当前errno
的值,在系统预定义的错误消息列表中查找并输出相应的错误消息。
四、遇到的问题
/*启动摄像头*/
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(0 != ioctl(fd,VIDIOC_STREAMON, &type))
{
perror("Uable to start capture");
return -1;
}
printf("start capture ok\n");
在if语句后误加了;分号,导致if语句判断没有执行,而perror会一直执行
我是用Windows上的vscode通过ssh链接Ubuntu开发的,VScode没有报错且交叉编译也通过了,所以执行后一直报错Uable to start capture: Invalid argument