Linux V4L2 应用编程

2024-03-24 13:44
文章标签 linux 应用 编程 v4l2

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

V4L2:Video4Linux2,是 Linux 内核中的一个框架,提供了一套用于视频设备驱动程序开发的 API。它是一个开放的、通用的、模块化的视频设备驱动程序框架,允许 Linux 操作系统和应用程序与各种视频设备(如摄像头、视频采集卡等)进行交互。

V4L2 提供了一个通用的 API,使应用程序能够访问和控制视频设备,包括获取设备信息、设置设备参数、采集视频数据、控制设备状态等。V4L2 还提供了一个统一的视频数据格式,允许应用程序在处理视频数据时无需考虑设备的具体格式。

Video for Linux two(Video4Linux2)简称 V4L2,是 V4L 的改进版。V4L2 支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少。

V4L2 的主要特性

  1. 模块化的架构:V4L2 是一个模块化的架构,允许多个设备驱动程序同时存在并共享同一个 API。每个设备驱动程序都是一个独立的内核模块,可以在运行时加载和卸载。这种架构可以使开发人员更容易地开发新的视频设备驱动程序,并允许多个驱动程序同时使用相同的 API。

  2. 统一的设备节点:V4L2 提供了一种统一的设备节点,使应用程序可以使用相同的方式访问不同类型的视频设备。这种节点通常是 /dev/videoX,其中 X 是一个数字,表示设备的编号。应用程序可以通过打开这个节点来访问设备,并使用 V4L2 API 进行数据采集和控制。

  3. 统一的视频数据格式:V4L2 提供了一个统一的视频数据格式,称为 V4L2_PIX_FMT,允许应用程序在处理视频数据时无需考虑设备的具体格式。V4L2_PIX_FMT 包括了许多常见的视频格式,如 RGB、YUV 等。应用程序可以使用 V4L2 API 来查询设备支持的数据格式,并选择适当的格式进行数据采集和处理。

  4. 支持多种视频设备:V4L2 支持许多不同类型的视频设备,包括摄像头、视频采集卡、TV 卡等。每个设备都有自己的驱动程序,提供了相应的 V4L2 API。这些驱动程序可以根据设备的不同特性,提供不同的采集模式、数据格式、控制参数等。

  5. 支持流式 I/O:V4L2 支持流式 I/O,即通过内存映射的方式将视频数据从设备直接传输到应用程序中。这种方式可以减少数据复制的次数,提高数据传输的效率。

  6. 支持控制参数:V4L2 允许应用程序通过 API 来控制视频设备的参数,包括亮度、对比度、色彩饱和度、曝光时间等。应用程序可以使用 V4L2 API 来查询设备支持的参数,并设置适当的值。

  7. 支持事件通知:V4L2 支持事件通知,当视频设备状态发生变化时,如视频信号丢失、帧率变化等,V4L2 驱动程序可以向应用程序发送通知,以便应用程序做出相应的处理。

从上面的特征可以看出,V4L2 提供了一套通用、灵活、可扩展的视频设备驱动程序框架,使得 Linux 操作系统和应用程序可以方便地与各种视频设备进行交互,并且不需要关心设备的具体实现细节。从而让开发人员能够更加专注于应用程序的开发。

V4L2 视频采集步骤


摄像头使用流程

  • open:打开设备节点 /dev/videoX
  • ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如
    • 确认它是否是"捕获设备",因为有些节点是输出设备
    • 确认它是否支持 mmap 操作,还是仅支持 read/write 操作
  • ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
  • ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式
  • ioctl VIDIOC_REQBUFS:申请 buffer,APP 可以申请很多个 buffer,但是驱动程序不一定能申请到
  • ioctl VIDIOC_QUERYBUF 和 mmap:查询 buffer 信息、映射
    • 如果申请到了 N 个 buffer,这个 ioctl 就应该执行 N 次
    • 执行 mmap 后,APP 就可以直接读写这些 buffer
  • ioctl VIDIOC_QBUF:把 buffer 放入"空闲链表"
    • 如果申请到了 N 个 buffer,这个 ioctl 就应该执行 N 次
  • ioctl VIDIOC_STREAMON:启动摄像头
  • 这里是一个循环:使用 poll/select 监测 buffer,然后从"完成链表"中取出 buffer,处理后再放入"空闲链表"
    • poll/select
    • ioctl VIDIOC_DQBUF:从"完成链表"中取出 buffer
    • 处理:前面使用 mmap 映射了每个 buffer 的地址,处理时就可以直接使用地址来访问 buffer
    • ioclt VIDIOC_QBUF:把 buffer 放入"空闲链表"
  • ioctl VIDIOC_STREAMOFF:停止摄像头

demo

#include <config.h>
#include <video_manager.h>
#include <disp_manager.h>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>static int g_aiSupportedFormats[] = {V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_RGB565};static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf);
static T_VideoOpr g_tV4l2VideoOpr;static int isSupportThisFormat(int iPixelFormat)
{int i;for (i = 0; i < sizeof(g_aiSupportedFormats)/sizeof(g_aiSupportedFormats[0]); i++){if (g_aiSupportedFormats[i] == iPixelFormat)return 1;}return 0;
}/* 参考 luvcview *//* open* VIDIOC_QUERYCAP 确定它是否视频捕捉设备,支持哪种接口(streaming/read,write)* VIDIOC_ENUM_FMT 查询支持哪种格式* VIDIOC_S_FMT    设置摄像头使用哪种格式* VIDIOC_REQBUFS  申请buffer对于 streaming:* VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap* VIDIOC_QBUF     放入队列* VIDIOC_STREAMON 启动设备* poll            等待有数据* VIDIOC_DQBUF    从队列中取出* 处理....* VIDIOC_QBUF     放入队列* ....对于read,write:read处理....read* VIDIOC_STREAMOFF 停止设备**/static int V4l2InitDevice(char *strDevName, PT_VideoDevice ptVideoDevice)
{int i;int iFd;int iError;struct v4l2_capability tV4l2Cap;struct v4l2_fmtdesc tFmtDesc;struct v4l2_format  tV4l2Fmt;struct v4l2_requestbuffers tV4l2ReqBuffs;struct v4l2_buffer tV4l2Buf;int iLcdWidth;int iLcdHeigt;int iLcdBpp;iFd = open(strDevName, O_RDWR);if (iFd < 0){DBG_PRINTF("can not open %s\n", strDevName);return -1;}ptVideoDevice->iFd = iFd;iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);if (iError) {DBG_PRINTF("Error opening device %s: unable to query device.\n", strDevName);goto err_exit;}if (!(tV4l2Cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){DBG_PRINTF("%s is not a video capture device\n", strDevName);goto err_exit;}if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {DBG_PRINTF("%s supports streaming i/o\n", strDevName);}if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE) {DBG_PRINTF("%s supports read i/o\n", strDevName);}memset(&tFmtDesc, 0, sizeof(tFmtDesc));tFmtDesc.index = 0;tFmtDesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;while ((iError = ioctl(iFd, VIDIOC_ENUM_FMT, &tFmtDesc)) == 0) {if (isSupportThisFormat(tFmtDesc.pixelformat)){ptVideoDevice->iPixelFormat = tFmtDesc.pixelformat;break;}tFmtDesc.index++;}if (!ptVideoDevice->iPixelFormat){DBG_PRINTF("can not support the format of this device\n");goto err_exit;        }/* set format in */GetDispResolution(&iLcdWidth, &iLcdHeigt, &iLcdBpp);memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;tV4l2Fmt.fmt.pix.width       = iLcdWidth;tV4l2Fmt.fmt.pix.height      = iLcdHeigt;tV4l2Fmt.fmt.pix.field       = V4L2_FIELD_ANY;/* 如果驱动程序发现无法某些参数(比如分辨率),* 它会调整这些参数, 并且返回给应用程序*/iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt); if (iError) {DBG_PRINTF("Unable to set format\n");goto err_exit;        }ptVideoDevice->iWidth  = tV4l2Fmt.fmt.pix.width;ptVideoDevice->iHeight = tV4l2Fmt.fmt.pix.height;/* request buffers */memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));tV4l2ReqBuffs.count = NB_BUFFER;tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);if (iError) {DBG_PRINTF("Unable to allocate buffers.\n");goto err_exit;        }ptVideoDevice->iVideoBufCnt = tV4l2ReqBuffs.count;if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING){/* map the buffers */for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) {memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.index = i;tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);if (iError) {DBG_PRINTF("Unable to query buffer.\n");goto err_exit;}ptVideoDevice->iVideoBufMaxLen = tV4l2Buf.length;ptVideoDevice->pucVideBuf[i] = mmap(0 /* start anywhere */ ,tV4l2Buf.length, PROT_READ, MAP_SHARED, iFd,tV4l2Buf.m.offset);if (ptVideoDevice->pucVideBuf[i] == MAP_FAILED) {DBG_PRINTF("Unable to map buffer\n");goto err_exit;}}        /* Queue the buffers. */for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++) {memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.index = i;tV4l2Buf.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);if (iError){DBG_PRINTF("Unable to queue buffer.\n");goto err_exit;}}}else if (tV4l2Cap.capabilities & V4L2_CAP_READWRITE){g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;/* read(fd, buf, size) */ptVideoDevice->iVideoBufCnt  = 1;/* 在这个程序所能支持的格式里, 一个象素最多只需要4字节 */ptVideoDevice->iVideoBufMaxLen = ptVideoDevice->iWidth * ptVideoDevice->iHeight * 4;ptVideoDevice->pucVideBuf[0] = malloc(ptVideoDevice->iVideoBufMaxLen);}ptVideoDevice->ptOPr = &g_tV4l2VideoOpr;return 0;err_exit:    close(iFd);return -1;    
}static int V4l2ExitDevice(PT_VideoDevice ptVideoDevice)
{int i;for (i = 0; i < ptVideoDevice->iVideoBufCnt; i++){if (ptVideoDevice->pucVideBuf[i]){munmap(ptVideoDevice->pucVideBuf[i], ptVideoDevice->iVideoBufMaxLen);ptVideoDevice->pucVideBuf[i] = NULL;}}close(ptVideoDevice->iFd);return 0;
}static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{struct pollfd tFds[1];int iRet;struct v4l2_buffer tV4l2Buf;/* poll */tFds[0].fd     = ptVideoDevice->iFd;tFds[0].events = POLLIN;iRet = poll(tFds, 1, -1);if (iRet <= 0){DBG_PRINTF("poll error!\n");return -1;}/* VIDIOC_DQBUF */memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iRet = ioctl(ptVideoDevice->iFd, VIDIOC_DQBUF, &tV4l2Buf);if (iRet < 0) {DBG_PRINTF("Unable to dequeue buffer.\n");return -1;}ptVideoDevice->iVideoBufCurIndex = tV4l2Buf.index;ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \0;ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;ptVideoBuf->tPixelDatas.iTotalBytes   = tV4l2Buf.bytesused;ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[tV4l2Buf.index];    return 0;
}static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{/* VIDIOC_QBUF */struct v4l2_buffer tV4l2Buf;int iError;memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));tV4l2Buf.index  = ptVideoDevice->iVideoBufCurIndex;tV4l2Buf.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;tV4l2Buf.memory = V4L2_MEMORY_MMAP;iError = ioctl(ptVideoDevice->iFd, VIDIOC_QBUF, &tV4l2Buf);if (iError) {DBG_PRINTF("Unable to queue buffer.\n");return -1;}return 0;
}static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{int iRet;iRet = read(ptVideoDevice->iFd, ptVideoDevice->pucVideBuf[0], ptVideoDevice->iVideoBufMaxLen);if (iRet <= 0){return -1;}ptVideoBuf->iPixelFormat        = ptVideoDevice->iPixelFormat;ptVideoBuf->tPixelDatas.iWidth  = ptVideoDevice->iWidth;ptVideoBuf->tPixelDatas.iHeight = ptVideoDevice->iHeight;ptVideoBuf->tPixelDatas.iBpp    = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \(ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565)? 16 : \0;ptVideoBuf->tPixelDatas.iLineBytes    = ptVideoDevice->iWidth * ptVideoBuf->tPixelDatas.iBpp / 8;ptVideoBuf->tPixelDatas.iTotalBytes   = iRet;ptVideoBuf->tPixelDatas.aucPixelDatas = ptVideoDevice->pucVideBuf[0];    return 0;
}static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice, PT_VideoBuf ptVideoBuf)
{return 0;
}static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;int iError;iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMON, &iType);if (iError) {DBG_PRINTF("Unable to start capture.\n");return -1;}return 0;
}static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;int iError;iError = ioctl(ptVideoDevice->iFd, VIDIOC_STREAMOFF, &iType);if (iError) {DBG_PRINTF("Unable to stop capture.\n");return -1;}return 0;
}static int V4l2GetFormat(PT_VideoDevice ptVideoDevice)
{return ptVideoDevice->iPixelFormat;
}/* 构造一个VideoOpr结构体 */
static T_VideoOpr g_tV4l2VideoOpr = {.name        = "v4l2",.InitDevice  = V4l2InitDevice,.ExitDevice  = V4l2ExitDevice,.GetFormat   = V4l2GetFormat,.GetFrame    = V4l2GetFrameForStreaming,.PutFrame    = V4l2PutFrameForStreaming,.StartDevice = V4l2StartDevice,.StopDevice  = V4l2StopDevice,
};/* 注册这个结构体 */
int V4l2Init(void)
{return RegisterVideoOpr(&g_tV4l2VideoOpr);
}

这篇关于Linux V4L2 应用编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Linux创建服务使用systemctl管理详解

《Linux创建服务使用systemctl管理详解》文章指导在Linux中创建systemd服务,设置文件权限为所有者读写、其他只读,重新加载配置,启动服务并检查状态,确保服务正常运行,关键步骤包括权... 目录创建服务 /usr/lib/systemd/system/设置服务文件权限:所有者读写js,其他

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命