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

相关文章

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的

C#中的Converter的具体应用

《C#中的Converter的具体应用》C#中的Converter提供了一种灵活的类型转换机制,本文详细介绍了Converter的基本概念、使用场景,具有一定的参考价值,感兴趣的可以了解一下... 目录Converter的基本概念1. Converter委托2. 使用场景布尔型转换示例示例1:简单的字符串到

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方