【实时Linux实战系列】V4L2 采集零拷贝:DMA-BUF 在低延迟视频中的应用

在实时视频处理系统中,视频帧的高效传输和处理是确保系统低延迟和高吞吐量的关键。传统的视频采集和处理流程中,数据拷贝是一个常见的性能瓶颈,它不仅增加了处理延迟,还可能导致帧间抖动。为了克服这些问题,Linux 提供了 V4L2(Video for Linux 2)和 DMA-BUF(DMA Buffer Sharing)等技术,用于实现零拷贝的数据传输,从而降低帧传输延迟并稳定帧间抖动。

项目背景与重要性

在实时视频处理系统中,如视频监控、实时视频会议、自动驾驶等领域,低延迟和稳定的帧传输是确保系统性能的关键。V4L2 是 Linux 内核中用于视频设备的标准接口,而 DMA-BUF 是一种用于共享内存缓冲区的机制,它们的结合可以实现高效的零拷贝数据传输。

掌握此技能的重要性

  1. 降低延迟:通过零拷贝技术,可以减少数据在内存中的拷贝次数,从而降低处理延迟。

  2. 减少抖动:零拷贝技术可以减少数据传输过程中的不确定性,从而稳定帧间抖动。

  3. 提高性能:减少数据拷贝可以释放 CPU 资源,提高系统的整体性能。

  4. 简化代码:使用 V4L2 和 DMA-BUF 可以简化视频采集和处理的代码,提高代码的可读性和可维护性。

核心概念

在深入实践之前,我们需要了解一些与主题相关的基本概念和术语。

V4L2(Video for Linux 2)

V4L2 是 Linux 内核中用于视频设备的标准接口,它提供了一组 API,用于控制视频设备的采集、处理和输出。V4L2 支持多种视频格式和采集模式,适用于各种视频设备。

DMA-BUF(DMA Buffer Sharing)

DMA-BUF 是一种用于共享内存缓冲区的机制,它允许内核中的不同组件共享内存缓冲区,而无需进行数据拷贝。DMA-BUF 可以显著减少数据在内存中的拷贝次数,提高数据传输的效率。

DMABUF-HEAPS

DMABUF-HEAPS 是一个用户空间库,用于管理 DMA-BUF 缓冲区。它提供了一组 API,用于分配、释放和共享 DMA-BUF 缓冲区。

零拷贝

零拷贝是指在数据传输过程中,数据不需要在内存中进行多次拷贝,从而减少处理延迟和提高性能。在视频处理中,零拷贝技术可以显著减少帧传输延迟和帧间抖动。

环境准备

在开始实践之前,我们需要准备以下软硬件环境。

操作系统

  • Linux:建议使用 Ubuntu 20.04 或更高版本,因为这些版本提供了最新的内核和开发工具。

开发工具

  • GCC:用于编译 C 程序。可以通过以下命令安装:

  • sudo apt-get update
    sudo apt-get install build-essential
  • GDB:用于调试程序。可以通过以下命令安装:

    sudo apt-get install gdb
  • libv4l2:用于操作 V4L2 设备。可以通过以下命令安装:

    sudo apt-get install libv4l2-dev
  • DMABUF-HEAPS:用于管理 DMA-BUF 缓冲区。可以通过以下命令安装:

  • sudo apt-get install libdmabuf-heaps-dev

硬件环境

  • 开发板:建议使用树莓派或 BeagleBone 等开发板,这些开发板提供了丰富的视频接口。

  • 视频设备:准备一个支持 V4L2 的视频设备,如 USB 摄像头或 HDMI 捕获卡。

环境配置

确保你的系统已经安装了上述工具,并且可以通过命令行访问它们。可以通过以下命令检查 GCC 和 GDB 是否安装成功:

gcc --version
gdb --version

实际案例与步骤

接下来,我们将通过一个具体的案例来展示如何使用 V4L2 和 DMA-BUF 实现零拷贝的视频采集。我们将创建一个简单的程序,该程序从 V4L2 设备采集视频帧,并通过 DMA-BUF 将帧共享给其他进程。

步骤 1:初始化 V4L2 设备

首先,我们需要初始化 V4L2 设备,并设置视频采集参数。

示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>#define DEVICE "/dev/video0"int main() {int fd;struct v4l2_format fmt;struct v4l2_requestbuffers req;struct v4l2_buffer buf;void *buffers[4];// 打开 V4L2 设备fd = open(DEVICE, O_RDWR);if (fd < 0) {perror("open");exit(EXIT_FAILURE);}// 设置视频格式memset(&fmt, 0, sizeof(fmt));fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;fmt.fmt.pix.width = 640;fmt.fmt.pix.height = 480;fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {perror("ioctl VIDIOC_S_FMT");close(fd);exit(EXIT_FAILURE);}// 请求缓冲区memset(&req, 0, sizeof(req));req.count = 4;req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;req.memory = V4L2_MEMORY_MMAP;if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {perror("ioctl VIDIOC_REQBUFS");close(fd);exit(EXIT_FAILURE);}// 映射缓冲区for (int i = 0; i < req.count; i++) {memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {perror("ioctl VIDIOC_QUERYBUF");close(fd);exit(EXIT_FAILURE);}buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);if (buffers[i] == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}}// 将缓冲区放入队列for (int i = 0; i < req.count; i++) {memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {perror("ioctl VIDIOC_QBUF");close(fd);exit(EXIT_FAILURE);}}// 开始采集if (ioctl(fd, VIDIOC_STREAMON, &buf.type) < 0) {perror("ioctl VIDIOC_STREAMON");close(fd);exit(EXIT_FAILURE);}printf("V4L2 device initialized\n");// 保持程序运行一段时间sleep(10);// 停止采集if (ioctl(fd, VIDIOC_STREAMOFF, &buf.type) < 0) {perror("ioctl VIDIOC_STREAMOFF");}// 取消映射缓冲区for (int i = 0; i < req.count; i++) {munmap(buffers[i], buf.length);}close(fd);return 0;
}
编译与运行

将上述代码保存为 v4l2_capture.c,然后使用以下命令编译和运行程序:

gcc -o v4l2_capture v4l2_capture.c
./v4l2_capture

步骤 2:使用 DMA-BUF 共享缓冲区

接下来,我们将使用 DMA-BUF 将采集到的视频帧共享给其他进程。

示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <linux/dma-buf.h>#define DEVICE "/dev/video0"
#define DMA_BUF_DEVICE "/

 

// 打开 V4L2 设备
fd = open(DEVICE, O_RDWR);
if (fd < 0) {perror("open");exit(EXIT_FAILURE);
}// 设置视频格式
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.height = 480;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {perror("ioctl VIDIOC_S_FMT");close(fd);exit(EXIT_FAILURE);
}// 请求缓冲区
memset(&req, 0, sizeof(req));
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {perror("ioctl VIDIOC_REQBUFS");close(fd);exit(EXIT_FAILURE);
}// 映射缓冲区
for (int i = 0; i < req.count; i++) {memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {perror("ioctl VIDIOC_QUERYBUF");close(fd);exit(EXIT_FAILURE);}buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);if (buffers[i] == MAP_FAILED) {perror("mmap");close(fd);exit(EXIT_FAILURE);}
}// 将缓冲区放入队列
for (int i = 0; i < req.count; i++) {memset(&buf, 0, sizeof(buf));buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;buf.memory = V4L2_MEMORY_MMAP;buf.index = i;if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {perror("ioctl VIDIOC_QBUF");close(fd);exit(EXIT_FAILURE);}
}// 开始采集
if (ioctl(fd, VIDIOC_STREAMON, &buf.type) < 0) {perror("ioctl VIDIOC_STREAMON");close(fd);exit(EXIT_FAILURE);
}// 导出 DMA-BUF 文件描述符
memset(&exp, 0, sizeof(exp));
exp.flags = O_CLOEXEC;
if (ioctl(fd, DMA_BUF_IOCTL_EXPORT, &exp) < 0) {perror("ioctl DMA_BUF_IOCTL_EXPORT");close(fd);exit(EXIT_FAILURE);
}
dma_buf_fd = exp.fd;printf("DMA-BUF file descriptor: %d\n", dma_buf_fd);// 保持程序运行一段时间
sleep(10);// 停止采集
if (ioctl(fd, VIDIOC_STREAMOFF, &buf.type) < 0) {perror("ioctl VIDIOC_STREAMOFF");
}// 取消映射缓冲区
for (int i = 0; i < req.count; i++) {munmap(buffers[i], buf.length);
}close(fd);
close(dma_buf_fd);
return 0;

}


#### 编译与运行将上述代码保存为 `v4l2_dma_buf.c`,然后使用以下命令编译和运行程序:```bash
gcc -o v4l2_dma_buf v4l2_dma_buf.c
./v4l2_dma_buf

步骤 3:在其他进程访问 DMA-BUF 缓冲区

接下来,我们将在另一个进程中访问 DMA-BUF 缓冲区,以实现零拷贝的视频帧共享。

示例代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/dma-buf.h>#define DMA_BUF_DEVICE "/dev/dma_buf"int main() {int dma_buf_fd;struct dma_buf_importer imp;void *buffer;// 打开 DMA-BUF 设备dma_buf_fd = open(DMA_BUF_DEVICE, O_RDWR);if (dma_buf_fd < 0) {perror("open");exit(EXIT_FAILURE);}// 导入 DMA-BUF 缓冲区memset(&imp, 0, sizeof(imp));imp.fd = dma_buf_fd;if (ioctl(dma_buf_fd, DMA_BUF_IOCTL_IMPORT, &imp) < 0) {perror("ioctl DMA_BUF_IOCTL_IMPORT");close(dma_buf_fd);exit(EXIT_FAILURE);}// 映射缓冲区buffer = mmap(NULL, imp.size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf_fd, 0);if (buffer == MAP_FAILED) {perror("mmap");close(dma_buf_fd);exit(EXIT_FAILURE);}printf("DMA-BUF buffer mapped at: %p\n", buffer);// 保持程序运行一段时间sleep(10);// 取消映射缓冲区munmap(buffer, imp.size);close(dma_buf_fd);return 0;
}
编译与运行

将上述代码保存为 dma_buf_importer.c,然后使用以下命令编译和运行程序:

gcc -o dma_buf_importer dma_buf_importer.c
./dma_buf_importer

代码说明

  • open:打开 V4L2 设备和 DMA-BUF 设备。

  • ioctl:设置视频格式、请求缓冲区、查询缓冲区、导出和导入 DMA-BUF 缓冲区。

  • mmap:映射 DMA-BUF 缓冲区到用户空间。

  • munmap:取消映射 DMA-BUF 缓冲区。

  • close:关闭文件描述符。

常见问题与解答

问题 1:如何确定 V4L2 设备的设备文件?

解答:可以通过查看 /dev 目录下的设备文件来确定 V4L2 设备的设备文件。例如:

ls /dev/video*

问题 2:如何确定 DMA-BUF 设备的设备文件?

解答:DMA-BUF 设备的设备文件通常为 /dev/dma_buf。如果系统中没有该设备文件,可能需要加载相应的内核模块。例如:

sudo modprobe dma_buf

问题 3:如何检查 V4L2 设备支持的格式?

解答:可以通过 v4l2-ctl 工具来检查 V4L2 设备支持的格式。例如:

v4l2-ctl --list-formats --device /dev/video0

问题 4:如何检查 DMA-BUF 缓冲区的大小?

解答:可以通过 ioctl 调用 DMA_BUF_IOCTL_INFO 来检查 DMA-BUF 缓冲区的大小。例如:

struct dma_buf_info info;
memset(&info, 0, sizeof(info));
if (ioctl(dma_buf_fd, DMA_BUF_IOCTL_INFO, &info) < 0) {perror("ioctl DMA_BUF_IOCTL_INFO");
}
printf("DMA-BUF size: %zu\n", info.size);

实践建议与最佳实践

调试技巧

  • 使用 dmesg:通过 dmesg 查看内核日志,以便跟踪硬件接口的初始化和错误信息。

  • 使用 strace:通过 strace 跟踪系统调用,检查程序是否正确与硬件接口通信。

性能优化

  • 减少拷贝:通过使用 DMA-BUF 实现零拷贝的数据传输,减少数据在内存中的拷贝次数。

  • 优化缓冲区大小:根据实际需求调整缓冲区大小,以减少内存占用和提高性能。

常见错误解决方案

  • 设备文件不存在:检查设备文件是否正确安装和配置。

  • 权限不足:确保程序具有访问硬件接口的权限。

  • 缓冲区映射失败:检查缓冲区大小和权限是否正确。

总结与应用场景

通过本文的介绍,我们学习了如何使用 V4L2 和 DMA-BUF 实现零拷贝的视频采集和共享。通过减少数据在内存中的拷贝次数,可以降低帧传输延迟并稳定帧间抖动。在实际应用中,这种技术可以应用于视频监控、实时视频会议、自动驾驶等领域,帮助开发者构建高性能的实时视频处理系统。

希望读者能够将所学知识应用到真实项目中,通过实践不断提升自己的编程能力和技术水平。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/923485.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/923485.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32精准控制水流

如何用STM32精准控制水的流量&#xff1f;一、系统组成框图------------- ------------ ----------- -------------| | | | | | | || 流量传感器 -----> STM32 ----->| 驱动电路 ----->…

吃透 Vue 样式穿透:从 scoped 原理到组件库样式修改实战

在 Vue 项目开发中&#xff0c;我们经常会引入 Element Plus、Vant、Ant Design等成熟组件库来提升开发效率。但即便组件库提供了基础样式配置&#xff0c;实际业务中仍需根据设计需求调整组件内部细节样式——这时候&#xff0c;「样式穿透」就成了必须掌握的技能。而要理解样…

记一次维修网桥经历

1.前言 前俩天突然下大雨了&#xff0c;大雨过后我也迎来断网时刻&#xff0c;经过简单排查发现是网络的网桥这条线路无法连通。 猜测1 可能是网线损坏&#xff0c;2 网桥损坏 2.拆解 经过测试网线设备后发现是网桥的问题&#xff0c;尝试reset发现无反应&#xff08;正常情况重…

OceanBase001-入门--里面有的概念不确定文章作为了解使用

目录资料来源特点支持和不支持的点名词概念租户资源池租户使用资源数据库表分区示例资料来源 B站视频 点击跳转 特点 分两个版本 企业版支持Oracle 和MySql 社区版本支持 MySql 这里视频这么讲解的。后续有没有社区版本什么样子不知道&#xff0c;请不要喷我 单节点部署 兼…

KITTI数据集

KITTI数据集是由德国卡尔斯鲁厄理工学院 Karlsruhe Institute of Technology (KIT) 和美国芝加哥丰田技术研究院 Toyota Technological Institute at Chicago (TTI-C) 于2012年联合创办&#xff0c;是目前国际上最为常用的自动驾驶场景下的计算机视觉算法评测数据集之一。该数据…

rk3568移植WebRTC AudioProcessing

前言&#xff1a; 大家好&#xff0c;我是飞一样的成长&#xff0c;今天这篇文章主要想分享音频3A的内容。在之前有网友找我怎么移植原生的webrtc到rk3568/rk3588上&#xff0c;当时我自己也没有移植过&#xff0c;后面折腾了一个礼拜才搞定&#xff0c;当时遇到的最大问题&…

介绍一下 RetNet

RetNet&#xff08;Retention Network&#xff09;是微软亚洲研究院于 2023 年提出的一种新型序列建模架构&#xff0c;旨在解决 Transformer 架构在长序列处理中存在的计算复杂度高、内存占用大、推理速度慢等核心问题。它通过创新的 “循环注意力机制”&#xff0c;实现了 “…

CANopen - PDO映射

CiA402为什么不放到一个PDO中。而是分成几个PDO? 简短答案&#xff1a;装不下 解耦时序。 PDO负载上限&#xff1a;经典CAN的每个PDO只有8字节。TargetPosition(607A:0032bit) ProfileVelocity(60FF:0032bit) ModesOfOperation(6060:008bit) 共9字节&#xff0c;单个PDO放不…

北理工提出仅依赖机载传感器针对IAP的控制与状态估计框架

近日&#xff0c;度量用户、北京理工大学俞玉树老师团队在IEEE RAL&#xff0c;IEEE TRO和IEEE TASE期刊上分别发表论文&#xff0c;研究着力于解决多飞行器集联平台&#xff08;Integrated Aerial Platforms, IAPs&#xff09;的相对位姿和全局定位问题&#xff0c;提出IAP的控…

13年测试老鸟,性能测试-618与双11大促销压测(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、准备工作 准备…

StreamCap(直播录制) v1.0.2 绿色版

StreamCap 是一个基于FFmpeg和StreamGet的多平台直播流录制客户端&#xff0c;覆盖 40 国内外主流直播平台&#xff0c;支持批量录制、循环监控、定时监控和自动转码等功能。软件特色 多端支持&#xff1a;支持Windows/MacOS/Web运行。循环监控&#xff1a;实时监控直播间状态&…

OpenCV:图像拼接(SIFT 特征匹配 + 透视变换)

目录 一、核心技术原理与对应 API 解析 1.1 SIFT 特征检测与描述&#xff08;尺度不变特征提取&#xff09; 1.1.1 灰度图转换&#xff1a;cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 1.1.2 SIFT 检测器初始化&#xff1a;cv2.SIFT_create() 1.1.3 特征点检测与描述符计算&…

日语学习-日语知识点小记-进阶-JLPT-N1阶段蓝宝书,共120语法(10):91-100语法+考え方13

日语学习-日语知识点小记-进阶-JLPT-N1阶段蓝宝书&#xff0c;共120语法&#xff08;10&#xff09;&#xff1a;91-100语法1、前言&#xff08;1&#xff09;情况说明&#xff08;2&#xff09;工程师的信仰&#xff08;3&#xff09;高级语法N1语法和难点2、知识点-语法&…

继承与组合:C++面向对象的核心

C 继承&#xff1a;从基础到实战&#xff0c;彻底搞懂面向对象的 “代码复用术” 在面向对象编程&#xff08;OOP&#xff09;的世界里&#xff0c;“继承” 是实现代码复用的核心机制 —— 就像现实中孩子会继承父母的特征&#xff0c;C 的子类也能 “继承” 父类的成员&#…

Matplotlib定制:精解颜色、字体、线型与标记

Matplotlib定制&#xff1a;精解颜色、字体、线型与标记导语 Matplotlib 是 Python 数据可视化领域的基石。虽然它的默认样式足以满足快速分析的需求&#xff0c;但要创作出具有专业水准、信息清晰、视觉美观的图表&#xff0c;就必须掌握其强大的定制功能。本文将深入探讨 Mat…

Qt开发经验 --- Qt监听文件/文件夹改变(17)

文章目录[toc]1 概述2 演示效果3 简单使用示例4 带界面的使用示例5 源代码地址更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt开发经验 &#x1f448;1 概述 QT实现实时监控文件的创建、修改、删除操作 跟踪文件夹内容的增删改变化 可用于文件发生变化时自…

数据分析:合并一

&#x1f537; DA37&#xff1a;统计运动会项目报名人数&#xff08;仅输出有人报名的项目&#xff09;✅ 题目描述给定两个 CSV 文件&#xff1a;items.csv&#xff1a;包含项目信息&#xff08;item_id, item_name, location&#xff09;signup.csv&#xff1a;包含员工报名信…

WWW‘25一通读 |图Anomaly/OOD检测相关文章(1)

写在前面&#xff1a;进入新一轮学习阶段&#xff0c;从阅读开始。 本文分享的是WWW2025收录的与作者研究相近的graph-based xx相关paper的阅读笔记&#xff0c;含个人理解&#xff0c;仅供参考&#x1f604; 0x01 HEI&#xff1a;利用不变性原理实现异配图结构分布偏移学习 J…

static_cast:C++类型系统的“正经翻译官”

1. 背景与核心概念 1.1 C的“类型安全”哲学 想象一下&#xff0c;你所在的世界突然失去了所有规则&#xff1a;文字可以随意变成数字&#xff0c;人可以瞬间变成椅子&#xff0c;汽车能飞上天变成飞机… 这听起来像是疯狂的梦境&#xff0c;但对于早期C语言来说&#xff0c;这…

【嵌入式原理系列-第八篇】USART从原理到配置全解析

目录 一.通信领域基础知识介绍 1.1 串行和并行通信 1.2 同步和异步传输 1.3 串口和COM口 1.4 通信协议标准以及物理层定义 1.5 物理层协议之TTL / RS-232 / RS-485 二.USART介绍 2.1 USART特点介绍 2.2 UART和TTL / RS-232 / RS-485 2.3 USART硬线流控介绍 2.4 USAR…