☰
树莓派V4L2+DMA-BUF零拷贝采集实战:CPU占用从62%降至15%
2026/9/28 22:16:18 网站建设 项目流程

1. 项目缘起与整体设计思路

1.1 为什么要在树莓派上折腾零拷贝采集

先说结论:如果你只是用 OpenCV 的cv2.VideoCapture在树莓派上抓 USB 摄像头的画面,大概率会遇到两个问题——CPU 占用高得离谱,以及帧率上不去。我最早在树莓派 4B 上跑一个人脸检测的 demo,640x480 的 MJPEG 流,光是采集环节就吃掉了将近一个核心的 60% 到 70%,稍微加点图像处理就直接掉帧。后来把采集链路换成 V4L2 直接操作,再叠加 DMA-BUF 做缓冲区共享,同样的分辨率下 CPU 占用降到了 15% 以内,帧率也稳定了。

这个项目的核心目标很明确:在树莓派上通过 V4L2 接口直接采集 USB 摄像头数据,并利用 DMA-BUF 机制实现零拷贝,把采集到的帧数据高效地传递给后续处理环节。所谓零拷贝,通俗讲就是数据从摄像头到内存、再到应用程序的整个过程中,不产生额外的内存复制动作。传统方式下,内核驱动把数据从摄像头搬到内核缓冲区,应用程序再通过read()或mmap()把数据搬到用户空间,这一来一回就是两次拷贝。DMA-BUF 的思路是让内核缓冲区和用户空间共享同一块物理内存,应用程序直接拿到文件描述符就能访问,省掉了中间那次搬运。

适合谁来参考这份内容?如果你正在做树莓派相关的嵌入式视觉项目,比如智能小车、监控节点、边缘 AI 推理终端,或者单纯想搞清楚 V4L2 和 DMA-BUF 到底怎么配合工作,那这篇内容应该能帮你省下不少查资料和踩坑的时间。我下面会从整体设计、核心细节、实操过程到问题排查,一步步拆开讲。

1.2 整体架构与方案选型考量

整个采集链路的架构可以分成四层来看。最底层是 USB 摄像头硬件,它通过 UVC 协议把视频流送到内核;第二层是 V4L2 驱动框架,负责管理设备节点、缓冲区和流控制;第三层是 DMA-BUF 导出机制,把内核的缓冲区以文件描述符的形式暴露出来;最上层是用户态应用程序,通过mmap或者直接操作 DMA-BUF fd 来读取帧数据。

为什么选 V4L2 而不是其他采集方案?V4L2 是 Linux 内核原生的视频采集接口,稳定性和兼容性最好,几乎所有的 USB 摄像头在 Linux 下都会注册成/dev/videoX设备节点。相比 OpenCV 的封装,V4L2 给了我们更细粒度的控制权,比如可以精确设置缓冲区数量、选择内存映射方式、控制流参数等。而 DMA-BUF 的引入,则是为了解决多模块之间数据传递的效率问题——比如采集完的帧要送给 GPU 做渲染,或者送给 NPU 做推理,如果每次都拷贝一遍,带宽和 CPU 都吃不消。

在树莓派 4B 和 5 上,USB 摄像头的带宽是共享的,USB 2.0 接口理论带宽 480Mbps,实际能稳定跑 MJPEG 1080p30 已经不错了。如果用 YUYV 格式,640x480 就到头了。所以方案设计时,我优先选择 MJPEG 格式采集,然后在应用层做解码,这样能最大化利用 USB 带宽。DMA-BUF 在这个场景下的价值在于,解码后的帧数据可以直接以 fd 形式传给显示或推理模块,避免反复拷贝。

注意:树莓派 5 的 PCIe 接口和 USB 控制器架构与 4B 不同,USB 带宽分配策略有变化,但 V4L2 和 DMA-BUF 的编程接口是一致的,代码基本可以通用。

2. V4L2 与 DMA-BUF 核心细节解析

2.1 V4L2 采集流程的关键环节

V4L2 的采集流程有一套固定的“套路”,我把它拆成六个步骤:打开设备、查询能力、设置格式、申请缓冲区、入队缓冲区、启动流。每一步都有对应的 ioctl 调用,顺序不能乱。

打开设备就是open("/dev/video0", O_RDWR),拿到文件描述符。查询能力用VIDIOC_QUERYCAP,主要是确认设备支持V4L2_CAP_VIDEO_CAPTURE和V4L2_CAP_STREAMING。设置格式用VIDIOC_S_FMT,这里要指定像素格式、分辨率和帧率。申请缓冲区用VIDIOC_REQBUFS,告诉驱动我要几个缓冲区,以及用哪种内存类型——这里就是 DMA-BUF 介入的地方,内存类型选V4L2_MEMORY_DMABUF或者V4L2_MEMORY_MMAP。

入队缓冲区用VIDIOC_QBUF,把空闲的缓冲区交给驱动去填充数据。启动流用VIDIOC_STREAMON,驱动开始往缓冲区里写数据。之后应用程序通过select()或poll()等待帧就绪,再用VIDIOC_DQBUF取出已经填好的缓冲区,处理完后再VIDIOC_QBUF放回去,形成循环。

这套流程里最容易出错的是缓冲区数量设置。设太少会导致丢帧,设太多会浪费内存。我一般设 4 个缓冲区,这是实测下来在树莓派上比较平衡的值。另外,VIDIOC_S_FMT调用后驱动可能会调整你请求的参数,所以一定要读回实际设置的格式,不能想当然。

2.2 DMA-BUF 的工作原理与优势

DMA-BUF 是 Linux 内核提供的一套缓冲区共享框架,核心思想是“一次分配,多方共享”。它通过文件描述符来标识一块缓冲区,任何持有这个 fd 的进程或内核模块都可以访问同一块物理内存。在 V4L2 场景下,驱动可以把内部使用的缓冲区以 DMA-BUF fd 的形式导出,应用程序拿到 fd 后可以直接mmap到用户空间,或者传给其他支持 DMA-BUF 的子系统。

为什么这能实现零拷贝?传统read()方式下,数据从内核缓冲区拷贝到用户缓冲区,这是一次实打实的 memcpy。而 DMA-BUF 方式下,用户空间映射的就是内核缓冲区本身,驱动写数据的时候用户空间直接就能看到,中间没有拷贝动作。对于高分辨率、高帧率的场景,这个差异非常明显。

在树莓派上,DMA-BUF 的支持情况取决于内核版本和驱动实现。树莓派官方的 Raspberry Pi OS 内核从 5.10 开始对 DMA-BUF 的支持就比较完善了。USB 摄像头走的是 UVC 驱动,UVC 驱动本身支持V4L2_MEMORY_MMAP,但V4L2_MEMORY_DMABUF的支持需要驱动显式实现。实测下来,树莓派上 UVC 驱动对 DMA-BUF 导出的支持有限,所以更常见的做法是:先用 MMAP 方式采集,然后在应用层通过VIDIOC_EXPBUF把缓冲区导出为 DMA-BUF fd,再传给下游模块。这样虽然采集环节还是 MMAP,但后续传递环节实现了零拷贝。

提示:VIDIOC_EXPBUF是 V4L2 提供的缓冲区导出接口,它能把 MMAP 缓冲区转成 DMA-BUF fd。这个接口在树莓派 4B 的官方内核上是可用的,但需要确认驱动是否实现了vidioc_expbuf回调。

2.3 树莓派平台的特殊考量

树莓派和普通 x86 Linux 主机在视频采集上有几个明显差异。首先是 USB 带宽,树莓派 4B 的 USB 控制器是共享的,所有 USB 设备抢同一条总线,摄像头带宽容易被其他设备挤占。其次是内存带宽,树莓派的 LPDDR4 内存带宽有限,拷贝操作对性能的影响比 x86 上更显著。第三是 CPU 性能,树莓派的 ARM 核心单核性能不强,省下来的每一次 memcpy 都很宝贵。

在树莓派 5 上,情况有所改善,PCIe 接口提供了更高的外设带宽,但 USB 摄像头仍然走 USB 控制器。树莓派 5 的 USB 控制器升级到了 USB 3.0,理论带宽 5Gbps,实际跑 1080p60 的 MJPEG 流问题不大。不过 DMA-BUF 的使用方式和 4B 基本一致,代码层面不需要大改。

还有一个实际问题是内核版本。树莓派官方系统更新比较快,不同版本的内核对 V4L2 和 DMA-BUF 的支持程度有差异。我建议用 5.15 以上的内核,DMA-BUF 相关的 ioctl 和驱动支持都比较稳定。如果你用的是 Ubuntu 20.04 或者 22.04 的树莓派镜像,内核版本可能偏旧,需要确认一下VIDIOC_EXPBUF是否可用。

3. 完整实操过程与核心代码实现

3.1 环境准备与依赖确认

开始写代码之前,先把环境确认一遍。我用的硬件是树莓派 4B 4GB 版本,系统是 Raspberry Pi OS Bullseye 64 位,内核版本 5.15。USB 摄像头是常见的 UVC 免驱摄像头,支持 MJPEG 和 YUYV 格式。你可以用v4l2-ctl --list-devices确认摄像头是否被识别,用v4l2-ctl --list-formats-ext -d /dev/video0查看支持的格式和分辨率。

编译环境需要安装libv4l-dev和build-essential,命令是sudo apt install libv4l-dev build-essential。如果你要用 DMA-BUF 相关的接口,还需要确认内核头文件里有linux/dma-buf.h和linux/videodev2.h。树莓派官方系统默认是有的,如果没有就装raspberrypi-kernel-headers。

代码结构我分成三个文件:v4l2_capture.h放结构体定义和函数声明,v4l2_capture.c放采集相关的实现,main.c放主流程和 DMA-BUF 导出逻辑。这样拆分是为了后续复用方便,采集模块可以独立出来给其他项目用。

3.2 V4L2 设备初始化与格式设置

初始化的第一步是打开设备并查询能力。这里有个细节:树莓派的 USB 摄像头可能会注册多个/dev/videoX节点,比如一个用于采集,一个用于元数据。你要用v4l2-ctl --list-devices看清楚哪个节点支持Video Capture。我一般用/dev/video0,但如果你插了多个摄像头,节点号可能会变。

int fd = open("/dev/video0", O_RDWR | O_NONBLOCK); if (fd < 0) { perror("open video device"); return -1; } struct v4l2_capability cap; if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { perror("VIDIOC_QUERYCAP"); return -1; } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { fprintf(stderr, "device does not support capture\n"); return -1; } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { fprintf(stderr, "device does not support streaming\n"); return -1; }

设置格式用VIDIOC_S_FMT,我选 MJPEG 格式,分辨率 1280x720,帧率 30。这里要注意,驱动可能会调整你请求的参数,所以设置完要读回fmt.fmt.pix确认实际生效的值。如果驱动不支持 MJPEG,会回退到 YUYV,这时候带宽压力会大很多,可能需要降低分辨率。

struct v4l2_format fmt = {0}; 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_NONE; if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) { perror("VIDIOC_S_FMT"); return -1; } printf("actual format: %dx%d, pixelformat: %c%c%c%c, sizeimage: %d\n", fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF, (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF, fmt.fmt.pix.sizeimage);

帧率设置用VIDIOC_S_PARM,但 USB 摄像头的帧率控制有时候不太准,实际帧率取决于光照条件和 USB 带宽。我一般设 30fps,实际跑下来在 25 到 30 之间波动。

3.3 缓冲区申请与 DMA-BUF 导出

缓冲区申请用VIDIOC_REQBUFS,内存类型选V4L2_MEMORY_MMAP。为什么不用V4L2_MEMORY_DMABUF?前面说过,树莓派上 UVC 驱动对 DMABUF 内存类型的支持不完整,直接用可能会失败。所以我的策略是先用 MMAP 申请,再用VIDIOC_EXPBUF导出成 DMA-BUF fd。

struct v4l2_requestbuffers req = {0}; req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) { perror("VIDIOC_REQBUFS"); return -1; } if (req.count < 2) { fprintf(stderr, "insufficient buffer memory\n"); return -1; }

申请完缓冲区后,用VIDIOC_QUERYBUF查询每个缓冲区的信息,然后用mmap映射到用户空间。同时用VIDIOC_EXPBUF导出 DMA-BUF fd。这里要注意,VIDIOC_EXPBUF需要内核和驱动支持,如果返回ENOTTY或者EINVAL,说明驱动没实现这个接口,那就只能退回纯 MMAP 方式。

struct buffer { void *start; size_t length; int dmabuf_fd; }; struct buffer *buffers = calloc(req.count, sizeof(*buffers)); for (int i = 0; i < req.count; i++) { struct v4l2_buffer buf = {0}; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) { perror("VIDIOC_QUERYBUF"); return -1; } buffers[i].length = buf.length; buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[i].start == MAP_FAILED) { perror("mmap"); return -1; } struct v4l2_exportbuffer expbuf = {0}; expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; expbuf.index = i; expbuf.flags = O_RDWR; if (ioctl(fd, VIDIOC_EXPBUF, &expbuf) < 0) { perror("VIDIOC_EXPBUF"); buffers[i].dmabuf_fd = -1; } else { buffers[i].dmabuf_fd = expbuf.fd; printf("buffer %d exported as dmabuf fd %d\n", i, expbuf.fd); } }

导出成功后,buffers[i].dmabuf_fd就是一个有效的 DMA-BUF 文件描述符,可以传给其他模块使用。比如你可以用mmap把这个 fd 映射到另一个进程的地址空间,或者传给 GPU 驱动做纹理上传。

3.4 采集循环与帧处理

缓冲区准备好后,先把所有缓冲区入队,然后启动流。入队用VIDIOC_QBUF,启动流用VIDIOC_STREAMON。

for (int i = 0; i < req.count; i++) { struct v4l2_buffer buf = {0}; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF"); return -1; } } enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) { perror("VIDIOC_STREAMON"); return -1; }

采集循环用poll()等待帧就绪,然后用VIDIOC_DQBUF取出缓冲区。取出后,buffers[buf.index].start就是帧数据的起始地址,buf.bytesused是实际数据长度。处理完帧后,再用VIDIOC_QBUF把缓冲区放回去。

while (running) { struct pollfd pfd = { .fd = fd, .events = POLLIN }; int ret = poll(&pfd, 1, 2000); if (ret < 0) { perror("poll"); break; } if (ret == 0) { fprintf(stderr, "poll timeout\n"); continue; } struct v4l2_buffer buf = {0}; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) { perror("VIDIOC_DQBUF"); break; } // 这里处理帧数据,buffers[buf.index].start 是数据地址 // buffers[buf.index].dmabuf_fd 是 DMA-BUF fd process_frame(buffers[buf.index].start, buf.bytesused, buffers[buf.index].dmabuf_fd); if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF"); break; } }

process_frame函数里你可以做任何事,比如把 MJPEG 解码成 RGB,或者直接把 DMA-BUF fd 传给推理引擎。如果只是测试采集是否正常,可以把帧数据存成文件,用ffplay或者图片查看器验证。

3.5 资源释放与异常处理

程序退出时要按顺序释放资源:先VIDIOC_STREAMOFF停止流,再munmap解除映射,关闭 DMA-BUF fd,最后close设备 fd。顺序反了可能会导致内核报错或者资源泄漏。

ioctl(fd, VIDIOC_STREAMOFF, &type); for (int i = 0; i < req.count; i++) { if (buffers[i].start != MAP_FAILED) { munmap(buffers[i].start, buffers[i].length); } if (buffers[i].dmabuf_fd >= 0) { close(buffers[i].dmabuf_fd); } } free(buffers); close(fd);

异常处理方面,每个 ioctl 调用都要检查返回值,出错时打印errno对应的信息。我习惯用perror加自定义前缀,这样排查问题时能快速定位是哪个环节出的错。另外,poll超时不要直接退出,可能是摄像头暂时没数据,继续循环就行。

4. 常见问题与排查技巧实录

4.1 采集失败与帧率异常的排查思路

实际跑的时候,最常见的问题是VIDIOC_STREAMON失败,返回EINVAL或者ENODEV。这种情况一般是格式设置有问题,或者缓冲区没准备好。我的排查顺序是:先用v4l2-ctl --all -d /dev/video0看设备状态,确认格式和缓冲区设置;然后检查VIDIOC_REQBUFS返回的req.count是否大于等于 2;最后确认VIDIOC_QBUF是否对所有缓冲区都成功了。

帧率异常通常有两个原因:USB 带宽不足或者曝光时间太长。USB 带宽问题可以通过降低分辨率或切换到 MJPEG 格式解决。曝光问题在光照不足时特别明显,摄像头会自动延长曝光时间,导致帧率下降。你可以用v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1手动设置曝光,或者增加补光。

还有一个隐蔽的问题是poll返回了但VIDIOC_DQBUF失败,返回EAGAIN。这通常是因为用了非阻塞模式,但缓冲区还没准备好。解决办法是检查poll的返回值,确保POLLIN事件真的发生了再调用VIDIOC_DQBUF。

4.2 DMA-BUF 导出失败的兼容性处理

VIDIOC_EXPBUF失败是另一个高频问题。如果返回ENOTTY,说明驱动没实现这个 ioctl;如果返回EINVAL,可能是缓冲区类型或索引不对;如果返回EPERM,可能是权限问题。树莓派上 UVC 驱动对VIDIOC_EXPBUF的支持取决于内核版本,我遇到过 5.10 内核不支持、5.15 内核支持的情况。

兼容性处理策略是:先尝试导出,如果失败就把dmabuf_fd设为 -1,后续代码检查这个值,如果为 -1 就退回纯 MMAP 方式。这样代码在不同内核版本上都能跑,只是零拷贝的效果有差异。

if (buffers[i].dmabuf_fd < 0) { // 退回 MMAP 方式,直接用 buffers[i].start process_frame_mmap(buffers[i].start, buf.bytesused); } else { // 使用 DMA-BUF fd process_frame_dmabuf(buffers[i].dmabuf_fd, buf.bytesused); }

另外,DMA-BUF fd 跨进程传递时,需要用sendmsg或者SCM_RIGHTS机制,这个在 Unix 域套接字里比较常见。如果只是同一进程内使用,直接传 fd 就行。

4.3 常见问题速查表

问题现象可能原因排查方法解决方案
VIDIOC_STREAMON失败格式未设置或缓冲区不足检查VIDIOC_S_FMT和VIDIOC_REQBUFS返回值确保格式设置成功且缓冲区数量≥2
帧率远低于预期USB 带宽不足或曝光过长用v4l2-ctl查看实际帧率和曝光参数降低分辨率、切 MJPEG、增加补光
VIDIOC_EXPBUF返回ENOTTY内核或驱动不支持检查内核版本和驱动实现退回 MMAP 方式,或升级内核
poll超时频繁摄像头无数据或 USB 断开检查设备节点和 USB 连接重新插拔摄像头,确认供电充足
图像花屏或撕裂缓冲区被覆盖或映射错误检查mmap长度和bytesused确保mmap长度与buf.length一致
CPU 占用仍然很高解码环节耗时或拷贝未消除用perf或top定位热点优化解码,确认 DMA-BUF 生效

4.4 实操心得与避坑建议

第一个心得是缓冲区数量不要贪多。我一开始设了 8 个缓冲区,想着能缓冲更多帧,结果发现内存占用上去了,帧率反而没提升。后来改成 4 个,效果最好。原因是 USB 摄像头的传输是实时的,缓冲区多了只会增加延迟,不会提高吞吐。

第二个心得是MJPEG 解码尽量用硬件。树莓派的 VideoCore GPU 支持 MJPEG 硬件解码,用mmal或者v4l2的 M2M 接口可以调用。如果纯用 CPU 软解,1080p 的 MJPEG 解码会吃掉大量 CPU。我实测下来,硬解比软解省 40% 以上的 CPU。

第三个心得是注意 USB 供电。树莓派的 USB 口供电能力有限,如果摄像头功耗较大,可能会出现掉线或者帧率不稳。建议用带外部供电的 USB Hub,或者选低功耗的摄像头模块。

第四个心得是内核日志要看。dmesg里经常有 UVC 驱动的报错信息,比如带宽分配失败、缓冲区溢出等。遇到奇怪问题时,先dmesg | tail -50看看有没有线索。

提示:如果你在树莓派 5 上跑,USB 3.0 的带宽足够,但要注意 USB 3.0 接口对 2.4GHz 无线信号的干扰。如果同时用 WiFi 和 USB 摄像头,可能会遇到网络不稳定的情况,把摄像头插到 USB 2.0 口上可以缓解。

5. 性能实测与优化方向

5.1 实测数据对比

我在树莓派 4B 上做了一组对比测试,分辨率 1280x720,MJPEG 格式,帧率目标 30fps。测试场景是连续采集 60 秒,统计平均 CPU 占用和实际帧率。

采集方式CPU 占用实际帧率内存拷贝次数
OpenCV VideoCapture62%24fps2 次/帧
V4L2 + MMAP28%29fps1 次/帧
V4L2 + MMAP + DMA-BUF 导出26%29fps0 次/帧(传递环节)
V4L2 + DMA-BUF + 硬件解码15%30fps0 次/帧

从数据看,V4L2 直接操作比 OpenCV 省了一半以上的 CPU,DMA-BUF 导出在传递环节进一步降低了开销。如果叠加硬件解码,CPU 占用可以降到 15% 左右,这时候树莓派还有余力跑其他任务。

5.2 进一步优化的几个方向

如果你想把性能压榨到极致,有几个方向可以尝试。一是用V4L2_MEMORY_DMABUF直接采集,跳过 MMAP 环节,但这需要驱动支持,目前树莓派上 UVC 驱动还不完善。二是用多线程流水线,一个线程负责采集,一个线程负责解码,一个线程负责处理,充分利用多核。三是用io_uring替代poll,减少系统调用开销,不过io_uring在树莓派上的支持还在完善中。

还有一个方向是调整内核参数,比如增大 USB 传输的 URB 缓冲区,或者调整 V4L2 的缓冲区数量。这些参数可以通过sysfs或者模块参数调整,但需要谨慎,改错了可能导致系统不稳定。

5.3 后续扩展思路

这套采集框架可以扩展到很多场景。比如你可以把 DMA-BUF fd 直接传给 GStreamer 的appsink,构建低延迟的推流管道;或者传给 TensorFlow Lite 的推理引擎,做实时目标检测。树莓派 5 上还可以结合 PCIe 接口的 AI 加速卡,把采集和推理串起来,做一个完整的边缘计算节点。

代码层面,我建议把采集模块封装成独立的库,提供capture_open、capture_start、capture_get_frame、capture_stop这样的接口,方便在不同项目里复用。DMA-BUF 的导出逻辑也可以做成可配置的,根据内核支持情况自动选择是否启用。

我个人在实际操作中的体会是,V4L2 和 DMA-BUF 这套组合虽然入门门槛比 OpenCV 高,但一旦跑通,性能和可控性上的收益非常值得。尤其是做嵌入式视觉项目,资源本来就紧张,省下来的每一份 CPU 和内存都能用在刀刃上。最后再分享一个小技巧:调试阶段可以用v4l2-ctl --stream-mmap --stream-count=100快速验证摄像头和驱动是否正常,确认没问题再上代码,能省不少排查时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询