linux音视频采集技术: v4l2

简介

在 Linux 系统中,视频设备的支持和管理离不开 V4L2(Video for Linux 2)。作为 Linux 内核的一部分,V4L2 提供了一套统一的接口,允许开发者与视频设备(如摄像头、视频采集卡等)进行交互。无论是视频采集、处理,还是编码和显示,V4L2 都提供了强大的支持。本文将简单介绍一下 V4L2 的工作流程以及如何使用它进行视频采集。

参数介绍

v4l2并没有提供单独封装的API接口,而是通过 ioctl 系统调用以及v4l2所提供的特定参数来对设备进行控制和采集。

下面是主要的 ioctl 控制参数:

1.VIDIOC_QUERYCAP:查询设备能力。
可用于查询枚举视频设备,获取设备名、总线名、支持的能力等。并非所有设备都支持,有可能会查询失败。
相关结构定义:

struct v4l2_capability {
    __u8 driver[16];      // 驱动名称
    __u8 card[32];        // 设备名称
    __u8 bus_info[32];    // 设备的总线信息
    __u32 version;        // 驱动版本号
    __u32 capabilities;   // 设备支持的功能
    __u32 device_caps;    // 设备的具体能力
    __u32 reserved[3];    // 保留字段
};

2.VIDIOC_S_FMT:设置视频格式。
不同设备所支持的 pixelformat 不尽相同,所以设置的某些格式可能不会生效,比如我使用的海康摄像头只支持mjpg和yuyv。因此最好先使用 VIDIOC_ENUM_FMT 查询设备设备支持的格式,以确保设置生效。
相关结构定义:

struct v4l2_format {
    enum v4l2_buf_type type; // 缓冲区类型(如视频采集)
    union {
        struct v4l2_pix_format pix; // 视频帧格式
        // 其他格式(如 overlay、视频输出等)
    } fmt;
};

struct v4l2_pix_format {
    __u32 width;          // 视频宽度
    __u32 height;         // 视频高度
    __u32 pixelformat;    // 像素格式(如 V4L2_PIX_FMT_YUYV)
    __u32 field;          // 场序(如逐行扫描、隔行扫描)
    __u32 bytesperline;   // 每行的字节数
    __u32 sizeimage;      // 每帧的总字节数
    // 其他字段
};

3.VIDIOC_REQBUFS:请求分配缓冲区。
memory类型使用MMAP,后续用于mmap内核缓冲区到用户态,避免内存拷贝
相关结构定义:

struct v4l2_requestbuffers {
    __u32 count;          // 请求的缓冲区数量
    enum v4l2_buf_type type; // 缓冲区类型
    enum v4l2_memory memory; // 内存类型(如 MMAP、USERPTR)
    // 其他字段
};

4.VIDIOC_QUERYBUF:查询缓冲区信息。
相关结构定义:

struct v4l2_buffer {
    __u32 index;          // 缓冲区索引
    enum v4l2_buf_type type; // 缓冲区类型
    __u32 bytesused;      // 缓冲区中实际使用的字节数
    __u32 flags;          // 缓冲区标志
    __u32 field;          // 场序
    struct timeval timestamp; // 时间戳
    // 其他字段
};

5.VIDIOC_QBUF:将缓冲区加入队列。
将申请的 v4l2_buffer 实例入队

6.VIDIOC_DQBUF:从队列中取出缓冲区。
弹出 v4l2_buffer 实例,并通过mmap映射的地址读取采集数据

7.VIDIOC_STREAMON:开始视频采集。

8.VIDIOC_STREAMOFF:停止视频采集。

9.VIDIOC_ENUM_FMT:枚举设备支持的像素格式。
用于提前枚举支持的图像采集格式,以便选择最合适的采集方式。
相关结构定义:

struct v4l2_fmtdesc {
    __u32 index;             // 格式索引(从 0 开始)
    enum v4l2_buf_type type; // 缓冲区类型(如视频采集)
    __u32 flags;             // 格式标志
    __u8 description[32];    // 格式描述
    __u32 pixelformat;       // 像素格式(如 V4L2_PIX_FMT_YUYV)
    __u32 reserved[4];       // 保留字段
};

流程

通常使用的采集流程如下:

1.查询设备能力:使用 VIDIOC_QUERYCAP 查询枚举设备是否支持采集。

2.打开设备:使用 open 打开设备节点。

3.查询设备图像能力:使用 VIDIOC_ENUM_FMT 查询设备支持的像素格式是否匹配。

4.设置视频格式:使用 VIDIOC_S_FMT 设置分辨率、像素格式等。

5.请求缓冲区:使用 VIDIOC_REQBUFS 请求分配缓冲区。

6.映射缓冲区:使用 mmap 将缓冲区映射到用户空间。

7.开始采集:使用 VIDIOC_STREAMON 开始视频采集。

8.采集数据:使用 VIDIOC_DQBUF 从队列中取出缓冲区,处理数据后使用 VIDIOC_QBUF 将缓冲区重新加入队列。

9.停止采集:使用 VIDIOC_STREAMOFF 停止视频采集。

10.释放资源:使用 munmap 释放缓冲区,并关闭设备。

代码示例

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <fstream>
#include <vector>
#include <cstring>

#define VIDEO_DEVICE "/dev/video0"
#define WIDTH 640
#define HEIGHT 480
#define FPS 30
#define OUTPUT_FILE "output.yuv"
#define BUFFER_COUNT 4 // 缓冲区数量

// 检查 V4L2 调用的返回值
#define CHECK(x) \
    if ((x) < 0) { \
        std::cerr << "ioctl error at " << __FILE__ << ":" << __LINE__ << " - " << strerror(errno) << std::endl; \
        exit(EXIT_FAILURE); \
    }

// 检查设备是否支持指定格式
bool is_format_supported(int fd, unsigned int pixel_format) {
    struct v4l2_fmtdesc fmt_desc = {};
    fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt_desc.index = 0;

    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt_desc) == 0) {
        if (fmt_desc.pixelformat == pixel_format) {
            std::cout << "Device supports format: " << fmt_desc.description << std::endl;
            return true;
        }
        fmt_desc.index++;
    }

    std::cerr << "Device does not support the required format (YUV420)" << std::endl;
    return false;
}

int main() {
    // 打开视频设备
    int fd = open(VIDEO_DEVICE, O_RDWR);
    CHECK(fd);

    // 检查设备是否支持 YUV420P 格式
    if (!is_format_supported(fd, V4L2_PIX_FMT_YUV420)) {
        close(fd);
        return -1;
    }

    // 设置视频格式
    struct v4l2_format fmt = {};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; // YUV420P 格式
    fmt.fmt.pix.field = V4L2_FIELD_NONE;

    CHECK(ioctl(fd, VIDIOC_S_FMT, &fmt));

    // 检查设备是否实际设置了 YUV420P 格式
    if (fmt.fmt.pix.pixelformat != V4L2_PIX_FMT_YUV420) {
        std::cerr << "Device does not support YUV420P format" << std::endl;
        close(fd);
        return -1;
    }

    // 设置帧率
    struct v4l2_streamparm streamparm = {};
    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    streamparm.parm.capture.timeperframe.numerator = 1;
    streamparm.parm.capture.timeperframe.denominator = FPS;
    CHECK(ioctl(fd, VIDIOC_S_PARM, &streamparm));

    // 请求缓冲区
    struct v4l2_requestbuffers req = {};
    req.count = BUFFER_COUNT; // 4 个缓冲区
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    CHECK(ioctl(fd, VIDIOC_REQBUFS, &req));

    // 映射所有缓冲区
    std::vector<unsigned char *> buffers(BUFFER_COUNT);
    std::vector<size_t> buffer_sizes(BUFFER_COUNT);

    for (unsigned int i = 0; i < BUFFER_COUNT; i++) {
        struct v4l2_buffer buf = {};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        CHECK(ioctl(fd, VIDIOC_QUERYBUF, &buf));

        buffers[i] = (unsigned char *)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        if (buffers[i] == MAP_FAILED) {
            std::cerr << "Failed to mmap buffer " << i << std::endl;
            close(fd);
            return -1;
        }

        buffer_sizes[i] = buf.length;

        // 将缓冲区加入队列
        CHECK(ioctl(fd, VIDIOC_QBUF, &buf));
    }

    // 开始采集
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    CHECK(ioctl(fd, VIDIOC_STREAMON, &type));

    // 打开输出文件
    std::ofstream outfile(OUTPUT_FILE, std::ios::binary);
    if (!outfile) {
        std::cerr << "Failed to open output file" << std::endl;
        close(fd);
        return -1;
    }

    // 采集 100 帧数据并保存到文件
    for (int i = 0; i < 100; i++) {
        struct v4l2_buffer buf = {};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;

        // 从队列中取出缓冲区
        CHECK(ioctl(fd, VIDIOC_DQBUF, &buf));

        // 将 YUV420P 数据写入文件
        outfile.write((char *)buffers[buf.index], buf.bytesused);

        // 将缓冲区重新加入队列
        CHECK(ioctl(fd, VIDIOC_QBUF, &buf));
    }

    // 停止采集
    CHECK(ioctl(fd, VIDIOC_STREAMOFF, &type));

    // 释放资源
    for (unsigned int i = 0; i < BUFFER_COUNT; i++) {
        munmap(buffers[i], buffer_sizes[i]);
    }
    close(fd);
    outfile.close();

    std::cout << "YUV420P data saved to " << OUTPUT_FILE << std::endl;

    return 0;
}

编译前需要先安装v4l2的开发包:

sudo apt install libv4l-dev

也可以同时安装v4l2的工具包,用于信息查询:

sudo apt install v4l-utils

注:webrtc在linux下提供了两种采集方式,一种是v4l2,另一种是pipewire,感兴趣的可以看一下它们的实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

B树及其Java实现详解

文章目录 B树及其Java实现详解一、引言二、B树的结构与性质1、节点结构2、性质 三、B树的操作1、插入操作1.1、插入过程 2、删除操作2.1、删除过程 3、搜索操作 四、B树的Java实现1、节点类实现2、B树类实现 五、使用示例六、总结 B树及其Java实现详解 一、引言 B树是一种多路…

数据分析思维(八):分析方法——RFM分析方法

数据分析并非只是简单的数据分析工具三板斧——Excel、SQL、Python&#xff0c;更重要的是数据分析思维。没有数据分析思维和业务知识&#xff0c;就算拿到一堆数据&#xff0c;也不知道如何下手。 推荐书本《数据分析思维——分析方法和业务知识》&#xff0c;本文内容就是提取…

微信小程序用的SSL证书有什么要求吗?

微信小程序主要建立在手机端使用&#xff0c;然而手机又涉及到各种系统及版本&#xff0c;所以对SSL证书也有要求&#xff0c;如果要小程序可以安全有效的访问需要满足以下要求&#xff1a; 1、原厂SSL证书&#xff08;原厂封&#xff09;。 2、DV单域名或者DV通配符。 3、兼…

手动安装 Maven 依赖到本地仓库

文章目录 手动安装 Maven 依赖到本地仓库1. 下载所需的 JAR 文件2. 安装 JAR 文件到本地仓库3. 验证安装4. 在项目中使用该依赖 手动安装 Maven 依赖到本地仓库 遇到的问题&#xff1a; idea导入一个新的工程&#xff0c;发现pom文件中的一些依赖死活下载不下来&#xff0c;这…

VSCode Live Server 插件安装和使用

VSCode Live Server是一个由Ritwick Dey开发的Visual Studio Code扩展插件&#xff0c;它提供了一个带有实时重载功能的本地开发服务器。在VSCode中安装和使用Live Server插件进行实时预览和调试Web应用程序。这将大大提高前端开发效率&#xff0c;使网页设计和开发变得更为流畅…

UART串口数据分析

串口基础知识详细介绍&#xff1a; 该链接详细介绍了串并行、单双工、同异步、连接方式 https://blog.csdn.net/weixin_43386810/article/details/127156063 该文章将介绍串口数据的电平变化、波特率计算、脉宽计算以及数据传输量的计算。 捕获工具&#xff1a;逻辑分析仪&…

Internet协议原理

文章目录 考试说明Chapter 0: 本书介绍Chapter 1: Introduction And Overview 【第1章&#xff1a;引言与概述】Chapter 2: Overview Of Underlying Network Technologies 【第2章&#xff1a;底层网络技术的回顾】Chapter 3: Internetworking Concept And Architectural Model…

DeepSeek-V3 通俗详解:从诞生到优势,以及与 GPT-4o 的对比

1. DeepSeek 的前世今生 1.1 什么是 DeepSeek&#xff1f; DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于打造高性能、低成本的 AI 模型。它的目标是让 AI 技术更加普惠&#xff0c;让更多人能够用上强大的 AI 工具。 1.2 DeepSeek-V3 的诞生 DeepSeek-V…

linux之自动挂载

如果想要实现自动挂载&#xff0c;应该挂在客户端&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 客户端&#xff1a; [rootlocalhost ~]# yum install nfs-utils -y &#xff08;下载软件&#xff09; [rootlocalhost ~]# systemctl start nfs-utils.servic…

RHCSA知识点汇总

第0章&#xff1a;Linux基础入门 0.1 什么是计算机 计算机的组成&#xff1a; 控制器&#xff1a;是整个计算机的中枢神经&#xff0c;根据程序要求进行控制&#xff0c;协调计算机各部分工作及内存与外设的访问等。 输入设备&#xff1a;将文字、数据、程序和控制命令等信…

交响曲-24-3-单细胞CNV分析及聚类

CNV概述 小于1kb是常见的插入、移位、缺失等的变异 人体内包含<10% 的正常CNV&#xff0c;我们的染色体数是两倍体&#xff0c;正常情况下&#xff0c;只有一条染色体表达&#xff0c;另一条沉默&#xff0c;当表达的那条染色体发生CNV之后&#xff0c;表达数量就会成倍增加…

【Linux-多线程】POSIX信号量-基于环形队列生产消费模型

POSIX信号量 POSIX信号量和System V信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源的目的。但POSIX可以用于线程间同步 1.快速认识信号量接口 POSIX信号量分为两种类型&#xff1a; 命名信号量&#xff08;Named Semaphores&#xff09;&…

Linux下文件操作相关接口

文章目录 一 文件是什么普通数据文件 二 文件是谁打开的进程用户 三 进程打开文件的相关的接口c语言标准库相关文件接口1. fopen 函数2. fread 函数3. fwrite 函数4. fclose 函数5. fseek 函数 linux系统调用接口1. open 系统调用2. creat 系统调用3. read 系统调用4. write 系…

UE蓝图节点备忘录

获取索引为0的玩家 获取视图缩放 反投影屏幕到世界 获取屏幕上的鼠标位置 对指定的物体类型进行射线检测 判断物体是否有实现某个接口 上面节点的完整应用 通过PlayerControlle获取相机相关数据 从相机处发射射线撞击物体从而获取物体信息 抽屉推拉功能 节点说明 ##门的旋转开关…

玩机搞机基本常识-------列举安卓机型一些不常用的adb联机命令

前面分享过很多 常用的adb命令&#xff0c;今天分享一些不经常使用的adb指令。以作备用 1---查看当前手机所有app包名 adb shell pm list package 2--查看当前机型所有apk包安装位置 adb shell pm list package -f 3--- 清除指定应用程序数据【例如清除浏览器应用的数据】 …

LeetCode【剑指offer】系列(字符串篇)

剑指offer05.替换空格 题目链接 题目&#xff1a;假定一段路径记作字符串path&#xff0c;其中以 “.” 作为分隔符。现需将路径加密&#xff0c;加密方法为将path中的分隔符替换为空格" "&#xff0c;请返回加密后的字符串。 思路&#xff1a;遍历即可。 通过代…

idea java.lang.OutOfMemoryError: GC overhead limit exceeded

Idea build项目直接报错 java: GC overhead limit exceeded java.lang.OutOfMemoryError: GC overhead limit exceeded 设置 编译器 原先heap size 设置的是 700M , 改成 2048M即可

aws(学习笔记第二十二课) 复杂的lambda应用程序(python zip打包)

aws(学习笔记第二十二课) 开发复杂的lambda应用程序(python的zip包) 学习内容&#xff1a; 练习使用CloudShell开发复杂lambda应用程序(python) 1. 练习使用CloudShell CloudShell使用背景 复杂的python的lambda程序会有许多依赖的包&#xff0c;如果不提前准备好这些python的…

conda 批量安装requirements.txt文件

conda 批量安装requirements.txt文件中包含的组件依赖 conda install --yes --file requirements.txt #这种执行方式&#xff0c;一遇到安装不上就整体停止不会继续下面的包安装。 下面这条命令能解决上面出现的不执行后续包的问题&#xff0c;需要在CMD窗口执行&#xff1a; 点…

如何操作github,gitee,gitcode三个git平台建立镜像仓库机制,这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈

如何操作github&#xff0c;gitee&#xff0c;gitcode三个git平台建立镜像仓库机制&#xff0c;这样便于维护项目只需要维护一个平台仓库地址的即可-优雅草央千澈 问题背景 由于我司最早期19年使用的是gitee&#xff0c;因此大部分仓库都在gitee有几百个库的代码&#xff0c;…