深入学习Linux内核之v4l2应用编程(二)

news2024/9/23 19:26:25

一,用户空间访问v4l2设备步骤

V4L2(Video for Linux 2)是Linux中关于视频设备的内核驱动,它使得Linux系统能够支持视频设备,如摄像头。对于Camera V4L2的应用编程,一般遵循以下步骤:

1,打开设备:
使用open()函数打开视频设备文件,通常位于/dev/videoX(X为设备编号,如0、1等)。
2,查询设备功能:
使用ioctl()函数和VIDIOC_QUERYCAP命令来查询设备的功能和属性,如是否支持视频捕获、是否支持流I/O等。
3,设置图像格式:
使用ioctl()函数和VIDIOC_ENUM_FMT、VIDIOC_S_FMT等命令来设置视频捕获的格式,如分辨率、颜色空间等。
4, 设置缓存:
使用ioctl()函数和VIDIOC_REQBUFS命令来请求一定数量的帧缓冲区(buffers),并可能需要使用mmap()函数将内核空间的帧缓冲区映射到用户空间。
5,开始捕获:
如果设备支持流I/O,可以使用ioctl()函数和VIDIOC_STREAMON命令开始视频捕获。
6,读取数据:
通过之前映射的帧缓冲区地址,可以直接访问捕获的视频帧数据。也可以使用read()函数从设备文件中读取数据,但这通常不是首选方法,因为效率较低。
7,停止捕获:
使用ioctl()函数和VIDIOC_STREAMOFF命令停止视频捕获。
8,关闭设备:
使用close()函数关闭视频设备文件。
9, 释放资源:
如果之前使用了mmap()映射了帧缓冲区,需要使用munmap()函数来取消映射。

在编程过程中,可能还需要考虑其他因素,如错误处理、多线程/多进程同步、内存管理等。同时,由于V4L2 API的复杂性和设备驱动的不同实现,具体的编程步骤和细节可能会有所不同。因此,在实际编程时,建议参考相关的文档和示例代码。

二、v4l2 API介绍

查询设备的功能

由于V4L2涵盖了各种各样的设备,因此并非API的所有方面都适用于所有类型的设备,在使用v4l2设备时,必须调用此API,获得设备支持的功能(capture、output、overlay…)
在这里插入图片描述
图像格式

图像由多种格式YUV和RGB还有压缩格式等等,其中每种格式又分有多种格式,比如RGB:RGB565、RGB888…

所以在使用设备时,需要对格式进行设置
在这里插入图片描述图像裁剪、插入与缩放
在这里插入图片描述数据的输入和输出

内核中使用缓存队列对图像数据进行管理,用户空间获取图像数据有两种方式,一种是通过read、write方式读取内核空间的缓存,一种是将内核空间的缓存映射到用户空间。在操作v4l2设备时,通过VIDIOC_QUERYCAP获取设备支持哪种方式
在这里插入图片描述ioctl API就先介绍到这里,还有非常多的接口这里就不一一介绍了,具体可以查看V4L2 Function Reference

三、v4l2设备操作流程

V4L2支持多种接口:capture(捕获)、output(输出)、overlay(预览)等等

这里讲解如何使用capture功能,下面讲解操作流程
step1:打开设备

在Linux中,视频设备节点为/dev/videox,使用open函数将其打开

int fd = open(name, flag);
if(fd < 0)
{
    printf("ERR(%s):failed to open %s\n", __func__, name);
    return -1;
}

return fd;

step 2:查询设备功能

if (ioctl(fd, VIDIOC_QUERYCAP, cap) < 0)
{
    printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);
    return -1;
}

看一看v4l2_capability

struct v4l2_capability {
	__u8	driver[16];	/* i.e. "bttv" */
	__u8	card[32];	/* i.e. "Hauppauge WinTV" */
	__u8	bus_info[32];	/* "PCI:" + pci_name(pci_dev) */
	__u32   version;        /* should use KERNEL_VERSION() */
	__u32	capabilities;	/* Device capabilities */
	__u32	reserved[4];
};

其中最重要的是capabilities字段,这个字段标记着v4l2设备的功能,capabilities有以下部分标记位
在这里插入图片描述step 3:设置图像格式

有的摄像头支持多种像素格式,有的摄像头只支持一种像素格式,在设置格式之前,要先枚举出所有的格式,看一看是否支持要设置的格式,然后再进一步设置

1.枚举支持的像素格式

struct v4l2_fmtdesc fmtdesc;

fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmtdesc.index = 0;

while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
{
    printf("fmt:%s\n", fmtdesc.description);

    fmtdesc.index++;
}

2.设置像素格式

struct v4l2_format v4l2_fmt;

memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
v4l2_fmt.fmt.pix.width = width; //宽度
v4l2_fmt.fmt.pix.height = height; //高度
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //像素格式
v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;

if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
{
    printf("ERR(%s):VIDIOC_S_FMT failed\n", __func__);
    return -1;
}

step 4:设置缓存

v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式,具体需要看step 2的返回结果是支持V4L2_CAP_READWRITE还是V4L2_CAP_STREAMING

read方式很容易理解,就是通过read函数读取,那么streaming是什么意思呢?

streaming就是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断地出队列和入队列的过程,如下图所示
在这里插入图片描述
下面讲解如何去申请和映射缓存

1.申请缓存

struct v4l2_requestbuffers req;

req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;

if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
    printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
    return -1;
}

2.映射缓存

为什么要映射缓存?

因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率

映射缓存需要先查询缓存信息,然后再使用缓存信息进行映射,下面是一个例子

struct v4l2_buffer v4l2_buffer;
void* addr;

memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //想要查询的缓存
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;

/* 查询缓存信息 */
ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
if(ret < 0)
{
    printf("Unable to query buffer.\n");
    return -1;
}

/* 映射 */
addr = mmap(NULL /* start anywhere */ ,
            v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
            fd, v4l2_buffer.m.offset);

注:需要将所有申请的缓存使用上述方法进行映射

3.将所有的缓存放入队列

struct v4l2_buffer v4l2_buffer;

for(i = 0; i < nr_bufs; i++)
{
	memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
	v4l2_buffer.index = i; //想要放入队列的缓存
	v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	v4l2_buffer.memory = V4L2_MEMORY_MMAP;	

    ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
    if(ret < 0)
    {
        printf("Unable to queue buffer.\n");
        return -1;
    }
}

step 5:打开设备

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
{
    printf("ERR(%s):VIDIOC_STREAMON failed\n", __func__);
    return -1;
}

step 7:读取数据

获取图像数据其实就是一个不断地入队列和出队列地过程
出队列

struct v4l2_buffer buffer;

buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;

if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0)
{
    printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame\n", __func__);
    return -1;
}

出队列后得到了缓存的下标buffer.index,然后找到对饮的缓存,通过映射过后的地址进行数据的读取

入队列

再数据读取完成后,要将buf重新放入队列中

struct v4l2_buffer v4l2_buf;

v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buf.memory = V4L2_MEMORY_MMAP;
v4l2_buf.index = i; //指定buf

if (ioctl(fd, VIDIOC_QBUF, &v4l2_buf) < 0)
{
    printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);
    return -1;
}

读取数据就是在上面一直不断地循环

step 7:关闭设备

1.关闭设备

enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
{
    printf("ERR(%s):VIDIOC_STREAMOFF failed\n", __func__);
    return -1;
}

2.取消映射

for(i = 0; i < nr_bufs; ++i)
    munmap(buf[i].addr, buf[i]->length);

3.关闭文件描述符

close(fd);

四,uvc camera v4l2应用编程参考代码
当使用V4L2 (Video for Linux 2) API来编程与UVC (USB Video Class) 摄像头交互时,你可以参考以下的基本代码框架。请注意,这只是一个简化的示例,用于说明基本的编程步骤,并且可能需要根据你的具体需求进行调整。

首先,你需要包含必要的头文件,并定义一些常用的错误处理宏:

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <sys/ioctl.h>  
#include <sys/mman.h>  
#include <linux/videodev2.h>  
  
#define CLEAR(x) memset(&(x), 0, sizeof(x))  
#define ERROR_HANDLER(ret, fmt, ...) \  
    do { \  
        if (ret < 0) { \  
            fprintf(stderr, "Error at %s:%d, %s\n  -> ", __FILE__, __LINE__, fmt); \  
            perror(NULL); \  
            exit(EXIT_FAILURE); \  
        } \  
    } while (0)

接下来是主程序框架,它打开设备、设置参数、捕获数据,并最后关闭设备:

int main(int argc, char **argv) {  
    struct v4l2_capability cap;  
    struct v4l2_format fmt;  
    struct v4l2_requestbuffers req;  
    enum v4l2_buf_type type;  
    struct v4l2_buffer buf;  
    unsigned int i;  
    int fd = -1;  
    void *buffers[4];  
  
    // 打开设备  
    fd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);  
    if (fd == -1) {  
        perror("open");  
        exit(EXIT_FAILURE);  
    }  
  
    // 查询设备能力  
    CLEAR(cap);  
    ERROR_HANDLER(ioctl(fd, VIDIOC_QUERYCAP, &cap), "VIDIOC_QUERYCAP");  
  
    // 确保设备支持视频捕获  
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {  
        fprintf(stderr, "%s is no video capture device\n", argv[0]);  
        exit(EXIT_FAILURE);  
    }  
  
    // 设置视频格式(这里只是一个示例,需要根据你的摄像头支持的格式来设置)  
    CLEAR(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;  
    ERROR_HANDLER(ioctl(fd, VIDIOC_S_FMT, &fmt), "VIDIOC_S_FMT");  
  
    // 请求缓冲区  
    CLEAR(req);  
    req.count = 4;  
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
    req.memory = V4L2_MEMORY_MMAP;  
    ERROR_HANDLER(ioctl(fd, VIDIOC_REQBUFS, &req), "VIDIOC_REQBUFS");  
  
    // 映射缓冲区  
    for (i = 0; i < req.count; ++i) {  
        CLEAR(buf);  
  
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
        buf.memory = V4L2_MEMORY_MMAP;  
        buf.index = i;  
  
        ERROR_HANDLER(ioctl(fd, VIDIOC_QUERYBUF, &buf), "VIDIOC_QUERYBUF");  
  
        buffers[i] = mmap(NULL /* start anywhere */,  
                          buf.length,  
                          PROT_READ | PROT_WRITE, /* required */  
                          MAP_SHARED /* recommended */,  
                          fd, buf.m.offset);  
  
        if (buffers[i] == MAP_FAILED) {  
            perror("mmap");  
            exit(EXIT_FAILURE);  
        }  
    }  
  
    // 开始捕获  
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
    ERROR_HANDLER(ioctl(fd, VIDIOC_STREAMON, &type), "VIDIOC_STREAMON");  
  
    // 在这里添加循环来捕获和处理数据

暂时分析到这里,后续在更新!

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

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

相关文章

PADS:生成自交叉平面区域

根据板外形铺铜方法&#xff1a; pads根据板外形铺铜_铺铜如何根据板子形状改变-CSDN博客 根据板外形创建平面区域出现问题&#xff1a; 解决方法&#xff1a;去找结构&#xff0c;让他把出图之前把线合并了

上海市虹桥祥源希尔顿酒店屋顶气膜网球馆

上海市虹桥祥源希尔顿酒店屋顶气膜网球馆为高端酒店设施增添了现代化、环保的运动场所。这座网球馆不仅为酒店住客提供了一个全天候、舒适的运动空间&#xff0c;也为虹桥地区的居民带来了全新的健身体验。作为轻空间&#xff08;江苏&#xff09;膜科技有限公司&#xff08;以…

潮玩与游戏的结合点——潮玩宇宙App与链游

本文主要介绍了潮玩宇宙App的开发过程及其链游的开发&#xff0c;探讨了潮玩与游戏的结合点&#xff0c;分析了其市场前景和潜在风险&#xff0c;并提出了相应的建议。 一、潮玩宇宙App的开发背景 随着互联网的普及和人们对娱乐方式的多样化需求&#xff0c;潮玩市场逐渐崛起…

2024 年 4 月公链研报:比特币减半、市场回调以及关键进展

作者&#xff1a;stellafootprint.network 数据来源&#xff1a;Footprint Analytics 公链研究页面 四月&#xff0c;加密市场在经济环境变化中取得了重要进展。4 月 20 日的比特币完成减半&#xff0c;但市场整体低迷导致比特币及前 25 大公链加密货币价格下跌。与此同时&am…

Git系列:git log 掌握版本控制的精髓

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

Unity WebGL全屏显示

一、删除footer节点 二、删除最下面点击事件绑定 修改Canvas宽高 canvas.style.width "960px"; canvas.style.height "600px"; 改成 canvas.style.width document.documentElement.clientWidth"px"; canvas.style.height document.document…

WebRTC 的核心:RTCPeerConnection

WebRTC 的核心&#xff1a;RTCPeerConnection WebRTC 的核心&#xff1a;RTCPeerConnection创建 RTCPeerConnection 对象RTCPeerConnection 与本地音视频数据绑定媒体协商ICE什么是 Candidate&#xff1f;收集 Candidate交换 Candidate尝试连接 SDP 与 Candidate 消息的互换远端…

python中cv2,等等如何修改为中文字体显示,这里以人脸表情识别中文标签为例

中文字体显示 首先下载字体包部署字体包代码实现部分 想必大家在使用python过程中都会遇到&#xff0c;想要显示中文的时候&#xff0c;但是py基本上都是英文字体&#xff0c;下面我将给大家提供一个比较好的解决方案&#xff1a; 首先下载字体包 方法&#xff1a; 我使用的是…

NGINX SPRING HTTPS证书

服务器&#xff1a;xxx.xxx.xxx.56 客户端器&#xff1a;xxx.xxx.xxx.94##生成服务器证书和密钥容器 keytool -genkey -alias tas-server -keypass 250250 -keyalg RSA -keysize 2048 -validity 3650 -keystore D:\https证书\tas-server.jks -storepass 250250 -dname "C…

喜茶·茶坊黑金首店入驻北京三里屯,率先引入珍稀娟姗奶制茶

发布 | 大力财经 近日&#xff0c;喜茶茶坊 BLACK 在北京三里屯开业&#xff0c;这是喜茶新业态的首家黑金店型。该店在延续喜茶茶坊“鲜、茶、纯”的精品茗茶特色和宋代茶文化审美意趣的基础上&#xff0c;首次升级呈现了铜锅手煮烹茶工艺、娟姗牛乳制茶等创新尝试&#xff0…

Vue.js 详细介绍

文章目录 一、Vue.js 简介1.1 什么是 Vue.js&#xff1f;1.2 Vue.js 的特点 二、快速上手 Vue.js2.1 安装 Vue.js使用 CDN使用 npm 或 yarn 2.2 创建一个 Vue 实例2.3 Vue.js 项目结构 三、Vue.js 核心概念3.1 数据绑定3.2 指令&#xff08;Directives&#xff09;3.3 组件&…

echarts map地图添加背景图

给map地图添加了一个阴影3d的效果&#xff0c;添加一张背景图&#xff0c;给人感觉有3d的效果 具体配置如下&#xff1a; html代码模块&#xff1a; <div class"echart_img" style"position: fixed; visibility: hidden;"></div><div id&q…

ARM机密计算架构

安全之安全(security)博客目录导读 目录 机密计算:Arm架构下一个新的信任模型 Arm CCA组件 领域管理扩展的访问控制 安全地运行所有应用程序 领域如何在受保护的内存空间中执行 重新评估信任关系 我们对Arm机密计算架构&#xff08;CCA&#xff09;的愿景是保护计算发生…

【Web后端】MVC模式

1、简介 MVC模式&#xff0c;全称Model-View-Controller&#xff08;模型-视图-控制器&#xff09;模式&#xff0c;是一种软件设计典范&#xff0c;它将应用程序的用户界面&#xff08;视图&#xff09;和业务逻辑&#xff08;模型&#xff09;分离&#xff0c;同时提供了一个…

Springboot+Vue项目-基于Java+MySQL的民族婚纱预定系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

Stable Diffusion入门使用技巧及个人实例分享--大模型及lora篇

大家好&#xff0c;近期使用Stable Diffusion比较多&#xff0c;积累整理了一些内容&#xff0c;得空分享给大家。如果你近期正好在关注AI绘画领域&#xff0c;可以看看哦。 本文比较适合已经解决了安装问题&#xff0c;&#xff08;没有安装的在文末领取&#xff09; 在寻找合…

线性/非线性最小二乘 与 牛顿/高斯牛顿/LM 原理及算法

最小二乘分为线性最小二乘和非线性最小二乘 最小二乘目标函数都是min ||f(x)||2 若f(x) ax b&#xff0c;就是线性最小二乘&#xff1b;若f(x) ax2 b / ax2 bx 之类的&#xff0c;就是非线性最小二乘&#xff1b; 1. 求解线性最小二乘 【参考】 2. 求解非线性最小二乘…

nn.BatchNorm中affine参数的作用

在PyTorch的nn.BatchNorm2d中&#xff0c;affine参数决定是否在批归一化&#xff08;Batch Normalization&#xff09;过程中引入可学习的缩放和平移参数。 BN层的公式如下&#xff0c; affine参数决定是否在批归一化之后应用一个可学习的线性变换&#xff0c;即缩放和平移。具…

阿里云域名备案流程

阿里云域名备案流程大致可以分为以下几个步骤&#xff0c;这些信息综合了不同来源的最新流程说明&#xff0c;确保了流程的时效性和准确性&#xff1a; UP贴心的附带了链接&#xff1a; 首次备案流程&#xff1a;ICP首次备案_备案(ICP Filing)-阿里云帮助中心 (aliyun.com) …

现实投资者怎么摆脱伦敦银波动影响?

有伦敦银投资经验的朋友会发现&#xff0c;即便自己找到了伦敦银市场的趋势&#xff0c;但是总是没办法坚守自己的仓位&#xff0c;因为没办法摆脱伦敦银波动的影响&#xff0c;比方说在上升趋势中买入后&#xff0c;投资者总是是觉得伦敦银价格会反转下跌&#xff0c;所以他就…