V4L2:Video4Linux2,是 Linux 内核中的一个框架,提供了一套用于视频设备驱动程序开发的 API。它是一个开放的、通用的、模块化的视频设备驱动程序框架,允许 Linux 操作系统和应用程序与各种视频设备(如摄像头、视频采集卡等)进行交互。
V4L2 提供了一个通用的 API,使应用程序能够访问和控制视频设备,包括获取设备信息、设置设备参数、采集视频数据、控制设备状态等。V4L2 还提供了一个统一的视频数据格式,允许应用程序在处理视频数据时无需考虑设备的具体格式。
Video for Linux two(Video4Linux2)简称 V4L2,是 V4L 的改进版。V4L2 支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少。
V4L2 的主要特性
-
模块化的架构:V4L2 是一个模块化的架构,允许多个设备驱动程序同时存在并共享同一个 API。每个设备驱动程序都是一个独立的内核模块,可以在运行时加载和卸载。这种架构可以使开发人员更容易地开发新的视频设备驱动程序,并允许多个驱动程序同时使用相同的 API。
-
统一的设备节点:V4L2 提供了一种统一的设备节点,使应用程序可以使用相同的方式访问不同类型的视频设备。这种节点通常是 /dev/videoX,其中 X 是一个数字,表示设备的编号。应用程序可以通过打开这个节点来访问设备,并使用 V4L2 API 进行数据采集和控制。
-
统一的视频数据格式:V4L2 提供了一个统一的视频数据格式,称为 V4L2_PIX_FMT,允许应用程序在处理视频数据时无需考虑设备的具体格式。V4L2_PIX_FMT 包括了许多常见的视频格式,如 RGB、YUV 等。应用程序可以使用 V4L2 API 来查询设备支持的数据格式,并选择适当的格式进行数据采集和处理。
-
支持多种视频设备:V4L2 支持许多不同类型的视频设备,包括摄像头、视频采集卡、TV 卡等。每个设备都有自己的驱动程序,提供了相应的 V4L2 API。这些驱动程序可以根据设备的不同特性,提供不同的采集模式、数据格式、控制参数等。
-
支持流式 I/O:V4L2 支持流式 I/O,即通过内存映射的方式将视频数据从设备直接传输到应用程序中。这种方式可以减少数据复制的次数,提高数据传输的效率。
-
支持控制参数:V4L2 允许应用程序通过 API 来控制视频设备的参数,包括亮度、对比度、色彩饱和度、曝光时间等。应用程序可以使用 V4L2 API 来查询设备支持的参数,并设置适当的值。
-
支持事件通知: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);
}