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

2024-05-14 22:12

本文主要是介绍深入学习Linux内核之v4l2应用编程(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一,用户空间访问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; //指定bufif (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");  // 在这里添加循环来捕获和处理数据

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

这篇关于深入学习Linux内核之v4l2应用编程(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/989996

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个