一:V4L2简介
1、概念
V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动,为驱动和应用程序提供了一套统一的接口规范。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写。
可以支持多种设备,它可以有以下几种接口:
(1) 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的.
(2)视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备.
(3)直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.
(4)视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号.
(5)收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流.
V4L2在linux系统中的结构图:
2、编程模式
v4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等,编程模式如下:
(1)打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
(2)申请图像帧缓冲,并进行内存映射,将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取、处理图像数据;
(3)将帧缓冲进行入队操作,启动视频采集;
(4)驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
(5)释放资源,停止采集工作。
二:V4L2指令及工作流程
从结构图中可以看出V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。
1、ioctl常用指令分类:
- Query Capability:查询设备支持的功能,只有VIDIOC_QUERYCAP一个。
- 优先级相关:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,设置优先级。
- capture相关:视频捕获相关Ioctl.
ID 描述 VIDIOC_ENUM_FMT 枚举设备所支持的所有数据格式 VIDIOC_S_FMT 设置数据格式 VIDIOC_G_FMT 获取数据格式 VIDIOC_TRY_FMT 与VIDIOC_S_FMT一样,但不会改变设备的状态 VIDIOC_REQBUFS 向设备请求视频缓冲区,即初始化视频缓冲区 VIDIOC_QUERYBUF 查询缓冲区的状态 VIDIOC_QBUF 从设备获取一帧视频数据 VIDIOC_DQBUF 将视频缓冲区归回给设备, VIDIOC_OVERLAY 开始或者停止overlay VIDIOC_G_FBUF 获取video overlay设备或OSD设备的framebuffer参数 VIDIOC_S_FBUF 设置framebuffer参数 VIDIOC_STREAMON 开始流I/O操作,capture or output device VIDIOC_STREAMOFF 关闭流I/O操作 - TV视频标准:
ID 描述 VIDIOC_ENUMSTD 枚举设备支持的所有标准 VIDIOC_G_STD 获取当前正在使用的标准 VIDIOC_S_STD 设置视频标准 VIDIOC_QUERYSTD 有的设备支持自动侦测输入源的视频标准,此时使用此ioctl查询侦测到的视频标准
2、具体流程图
三:软件框架
1、打开设备查看属性
使用VIDIOC_QUERYCAP命令查看设备属性用到的结构体tV4l2Cap原型为v4l2_capability用来描述视频采集设备的driver信息,其中根据capabilities可以判断当前设备是否为capture设备,并检查使用内存映射还是直接读的方式获取图像数据。
struct v4l2_capability {
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8 bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities; // 设备支持的操作
__u32 device_caps;
__u32 reserved[3]; // 保留字段
};
struct v4l2_capability tV4l2Cap;
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);
}
2、显示所有支持的格式
v4l2_fmtdesc 结构体描述当前camera支持的格式信息,使用命令VIDIOC_ENUM_FMT查询, v4l2_fmtdesc结构体中index要设置,从0开始;v4l2_buf_type type也要设置,如果使用的是camera设备,则v4l2_buf_type type要设置为V4L2_BUF_TYPE_VIDEO_CAPTURE,因为camera是CAPTURE设备。结构体中的其他内容driver会填充。
struct v4l2_fmtdesc {
__u32 index; // 要查询的格式序号,应用程序设置
__u32 type; // 帧类型,应用程序设置
__u32 flags; // 是否为压缩格式
__u8 description[32]; // 格式名称
__u32 pixelformat; //所支持的格式
__u32 reserved[4];
};
struct v4l2_fmtdesc tFmtDesc;
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++;
}
3、设置图像帧格式
struct v4l2_format结构体需要设置enum v4l2_buf_type type和union fmt中的struct v4l2_pix_format pix。enum v4l2_buf_type type因为使用的是camera设备,camera是CAPTURE设备,所以设置成V4L2_BUF_TYPE_VIDEO_CAPTURE。struct v4l2_pix_format pix设置一帧图像的长、宽和格式等,由于要适配LCD输出,所以长、宽设置为LCD支持的长、宽。
struct v4l2_format {
__u32 type;
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
__u8 raw_data[200]; /* user-defined */
} fmt;
};
struct v4l2_format tV4l2Fmt;
/* 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);
4、申请缓冲区
申请一个拥有NB_BUFFER个缓冲帧的缓冲区,__u32 count为缓冲帧的数目;enum v4l2_buf_type type和前文一样,同样设置成V4L2_BUF_TYPE_VIDEO_CAPTURE;enum v4l2_memorymemory用来区分是内存映射还是用户指针,我们使用内存映射的方式,所以设置成V4L2_MEMORY_MMAP。
struct v4l2_requestbuffers {
__u32 count;
__u32 type; /* enum v4l2_buf_type */
__u32 memory; /* enum v4l2_memory */
__u32 reserved[2];
};
struct v4l2_requestbuffers tV4l2ReqBuffs;
/* 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;
}
5、将申请的缓冲帧从内核空间映射到用户空间并将缓冲帧放如队列
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;
__u32 reserved2;
__u32 reserved;
};
struct v4l2_buffer tV4l2Buf;
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;
}
}
}