You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于V4L2的C++摄像头图像采集流程相关问题咨询

V4L2 USB摄像头采集流程疑问解答

问题背景

我此前尝试编写C++代码实现USB摄像头图像采集,目前已完成功能开发,但对采用的采集流程存在多处疑问,现将代码实现步骤及相关疑问整理如下:


核心实现步骤

步骤1:初始化设备并设置图像格式

const char* dev_name = "/dev/video0";
int width=320;
int height=240;
int fd = v4l2_open(dev_name, O_RDWR | O_NONBLOCK, 0);
struct v4l2_format format = {0};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = width;
format.fmt.pix.height = height;
format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YUYV //V4L2_PIX_FMT_RGB24
format.fmt.pix.field = V4L2_FIELD_NONE; //V4L2_FIELD_NONE
xioctl(fd, VIDIOC_S_FMT, &format);

步骤2:请求缓冲区

struct v4l2_requestbuffers req = {0};
req.count = 2;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
xioctl(fd, VIDIOC_REQBUFS, &req);

相关疑问解答

  • 问题1:VIDIOC_REQBUFS请求缓冲区操作的具体作用是什么?
    该指令用于通知V4L2驱动,申请指定数量、指定类型的帧缓冲区,驱动会在内核空间预留对应大小的内存块,同时完成和摄像头硬件的传输适配,后续所有采集帧的存取都基于这些缓冲区完成。
  • 问题2:此处申请的缓冲区物理位置是摄像头内部还是主机PC端?
    位于主机PC端的内核内存空间,不属于摄像头的存储。USB摄像头仅具备极小的传输缓存,采集到的帧会通过USB总线实时传输到主机,存储在驱动申请的这些缓冲区中。

步骤3:查询缓冲区并执行内存映射

struct v4l2_buffer buf;
buffer* buffers;
unsigned int i;
buffers = (buffer*) calloc(req.count, sizeof(*buffers));
for (i = 0; i < req.count; i++) {
clear_memmory(&(buf));

(buf).type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
(buf).memory      = V4L2_MEMORY_MMAP;
(buf).index       = i;

xioctl(fd, VIDIOC_QUERYBUF, &buf);

buffers[i].length = (buf).length;
printf("A buff has a len of: %i\n",buffers[i].length);
buffers[i].start = v4l2_mmap(NULL, (buf).length, PROT_READ | PROT_WRITE, MAP_SHARED,fd, (buf).m.offset);

if (MAP_FAILED == buffers[i].start) {
    perror("Can not map the buffers.");
    exit(EXIT_FAILURE);
    }
}

相关疑问解答

  • 问题3:我知道此处执行的是内存映射操作,请问该操作的必要性是什么?具体的映射关系是怎样的?是否是将其他位置的实际缓冲区映射到当前进程的地址空间便于读取?实际缓冲区的存储位置在哪里?
    必要性是降低数据拷贝开销:如果不做映射,读取帧需要调用read将数据从内核空间拷贝到用户空间,高分辨率高帧率场景下拷贝开销会非常高。
    映射关系是将驱动在内核空间申请的物理缓冲区,映射到当前应用进程的虚拟地址空间,应用层直接读写映射得到的地址,就等于直接操作内核的缓冲区,无需额外拷贝。实际缓冲区就存储在主机的物理内存中,属于内核态分配的内存块。
  • 问题4:是否可以跳过内存映射步骤?
    如果你选用MMAP内存模式就不能跳过;如果不需要高性能,也可以选择V4L2的read模式,无需申请缓冲区和做映射,直接调用read读取设备节点即可获取帧数据,仅适合低分辨率低帧率的场景。

步骤4:启动流并将缓冲区入队

for (i = 0; i < 1; i++) {
    clear_memmory(&(buf));
    (buf).type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    (buf).memory = V4L2_MEMORY_MMAP;
    (buf).index = i;
    xioctl(fd,VIDIOC_QBUF, &(buf));
    }
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
xioctl(fd,VIDIOC_STREAMON, &type);

相关疑问解答

  • 问题5:VIDIOC_QBUF指令的具体功能是什么?请用通俗易懂的表述解释,该操作逻辑我一直存在混淆。
    你可以把驱动的缓冲区管理理解为两个队列:待填充队列、已填充队列。VIDIOC_QBUF就是把空缓冲区放到待填充队列,相当于告诉驱动:这个缓冲区现在是空的,你采集到新帧就往这里面写。
  • 问题6:VIDIOC_QBUF操作是否需要在VIDIOC_STREAMON之后执行?执行顺序是否会影响功能正常运行?
    必须在VIDIOC_STREAMON之前执行,且至少要先入队1个空缓冲区再启动流。如果启动流时驱动的待填充队列为空,采集到的帧没有地方存储,会直接丢帧,甚至返回错误导致流启动失败。

步骤5:将缓冲区出队并保存帧数据

do {
    FD_ZERO(&fds);
    FD_SET(fd, &fds);

    // Timeout.
    tv.tv_sec = 2;
    tv.tv_usec = 0;

    r = select(fd + 1, &fds, NULL, NULL, &tv);
    } while ((r == -1 && (errno = EINTR)));
if (r == -1) {
    perror("select");
    exit(EXIT_FAILURE);
    }

clear_memmory(&(buf));
(buf).type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
(buf).memory = V4L2_MEMORY_MMAP;
xioctl(fd,VIDIOC_DQBUF, &(buf));

printf("Buff index: %i\n",(buf).index);
sprintf(out_name, "image%03d.ppm",pic_count);
fout = fopen(out_name, "w");
if (!fout) {
    perror("Cannot open image");
    exit(EXIT_FAILURE);
    }
fwrite(buffers[(buf).index].start, (buf).bytesused, 1, fout);
fclose(fout);
pic_count++;

相关疑问解答

  • 问题7:VIDIOC_DQBUF指令的作用是什么?为什么需要执行该操作?
    VIDIOC_DQBUF的作用是从驱动的已填充队列中,取出已经写入完整帧数据的缓冲区,调用后拿到的缓冲区索引对应的数据就是最新采集到的一帧,你可以直接读取处理。没有这个操作你无法知道哪个缓冲区里有有效数据,也无法通知驱动该缓冲区已经被使用可以再次入队。
  • 问题8:若仅分配1个可容纳单张图像的缓冲区,需要采集3帧时,是否需要循环执行3次入队、出队操作?
    是的。你每次DQBUF取出缓冲区处理完成后,必须再调用QBUF把它放回待填充队列,驱动才能用它存储下一帧。只有1个缓冲区的情况下,流程就是:入队→开流→出队处理→入队→出队处理→入队→出队处理,刚好对应3次入队出队操作。
  • 问题9:若需要采集1000000张图像,是否可以配置足够大的缓冲区存储全部数据?缓冲区大小的限制因素是什么?缓冲区是否位于摄像头端,所有图像都会存储在摄像头内部?
    不现实。首先缓冲区是在主机的内核空间,受限于系统给内核预留的内存上限,一般最多只能申请几十个缓冲区,不可能存储上百万张图像。其次USB摄像头本身只有几MB的传输缓存,根本存储不了多帧,所有采集到的帧都会实时传输到主机。大量图像需要存储的话,应该采集一帧就写入磁盘或者用户空间的内存,不要依赖V4L2的驱动缓冲区存储。
  • 问题10:若分配可存储10张图像的缓冲区,需要在指定时刻触发采集时,应调用哪些指令?仅调用VIDIOC_QBUF?还是需要同时调用VIDIOC_QBUF和VIDIOC_DQBUF?还是仅调用VIDIOC_DQBUF?
    启动流之前先把10个缓冲区全部入队,流开启后驱动会自动往空缓冲区写入采集到的帧。指定时刻需要取帧时,直接调用VIDIOC_DQBUF就能拿到最近采集到的一帧,处理完成后再调用VIDIOC_QBUF把缓冲区归还到待填充队列即可。如果流已经正常运行、所有缓冲区都已入队,触发采集时仅需调用DQBUF取帧,用完调用QBUF归还即可。

内容的提问来源于stack exchange,提问作者mcf1lmnfs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 18:36:03