赞
踩
V4L2,即 Video for linux two ,是 Linux 内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范
使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为 videoX(X为0、1、2…)
视频类设备对应的设备节点为/dev/videoX(X为0、1、2…)
可以使用命令 ls /dev/video*
查看视频类设备对应的设备节点
笔者在Arm板上插上USB摄像头之后可以看到多了一个设备节点为 /dev/video1
//定义一个设备描述符
int fd;
fd = open("/dev/videoX", O_RDWR);
if(fd < 0){
perror("video设备打开失败\n");
return -1;
}
else{
printf("video设备打开成功\n");
}
ioctl(fd, VIDIOC_QUERYCAP, &vcap);
if (!(V4L2_CAP_VIDEO_CAPTURE & vcap.capabilities)) {
perror("Error: No capture video device!\n");
return -1;
}
struct v4l2_fmtdesc {
__u32 index; /* index 就是一个编号 */
__u32 type; /* enum v4l2_buf_type */
__u32 flags;
__u8 description[32]; /* description 字段是一个简单地描述性字符串 */
__u32 pixelformat; /* pixelformat 字段则是对应的像素格式编号 */
__u32 reserved[4];
};
代码实现,使用VIDIOC_ENUM_FMT
struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("摄像头支持所有格式如下:\n");
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc) == 0){
printf("v4l2_format%d:%s\n",fmtdesc.index,fmtdesc.description);
fmtdesc.index++;
}
struct v4l2_frmsizeenum {
__u32 index; /* Frame size number */
__u32 pixel_format; /* 像素格式 */
__u32 type; /* type */
union { /* Frame size */
struct v4l2_frmsize_discrete discrete;
struct v4l2_frmsize_stepwise stepwise;
};
__u32 reserved[2]; /* Reserved space for future use */
};
struct v4l2_frmsize_discrete {
__u32 width; /* Frame width [pixel] */
__u32 height; /* Frame height [pixel] */
};
比如我们要枚举出摄像头 MJPEG 像素格式所支持的所有分辨率:
struct v4l2_frmsizeenum frmsize;
frmsize.index = 0;
frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("MJPEG格式支持所有分辨率如下:\n");
frmsize.pixel_format = V4L2_PIX_FMT_MJPEG;
while(ioctl(fd,VIDIOC_ENUM_FRAMESIZES,&frmsize) == 0){
printf("frame_size<%d*%d>\n",frmsize.discrete.width,frmsize.discrete.height);
frmsize.index++;
}
struct v4l2_frmivalenum { __u32 index; /* Frame format index */ __u32 pixel_format;/* Pixel format */ __u32 width; /* Frame width */ __u32 height; /* Frame height */ __u32 type; /* type */ union { /* Frame interval */ struct v4l2_fract discrete; struct v4l2_frmival_stepwise stepwise; }; __u32 reserved[2]; /* Reserved space for future use */ }; struct v4l2_fract { __u32 numerator; //分子 __u32 denominator; //分母 };
比如我们要枚举出摄像头 MJPEG 格式下640*480分辨率所支持的帧数:
struct v4l2_frmivalenum frmival;
frmival.index = 0;
frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
frmival.pixel_format = V4L2_PIX_FMT_MJPEG;
frmival.width = 640;
frmival.height = 480;
while(ioctl(fd,VIDIOC_ENUM_FRAMEINTERVALS,&frmival) == 0){
printf("frame_interval under frame_size <%d*%d> support %dfps\n",frmival.width,frmival.height,frmival.discrete.denominator / frmival.discrete.numerator);
frmival.index++;
}
首先要定义结构体v4l2_format来保存采集格式信息,使用VIDIOC_S_FMT指令设置格式,最后用VIDIOC_G_FMT指令查看相关参数是否生效
struct v4l2_format vfmt; vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vfmt.fmt.pix.width = 640; vfmt.fmt.pix.height = 480; vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; if(ioctl(fd,VIDIOC_S_FMT,&vfmt) < 0){ perror("设置格式失败\n"); return -1; } // 检查设置参数是否生效 if(ioctl(fd,VIDIOC_G_FMT,&vfmt) < 0){ perror("获取设置格式失败\n"); return -1; } else if(vfmt.fmt.pix.width == 640 && vfmt.fmt.pix.height == 480 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG){ printf("设置格式生效,实际分辨率大小<%d * %d>,图像格式:Motion-JPEG\n",vfmt.fmt.pix.width,vfmt.fmt.pix.height); } else{ printf("设置格式未生效\n"); }
申请帧缓冲顾名思义就是申请用于存储一帧图像数据的缓冲区, 使 VIDIOC_REQBUFS 指令可申请帧缓冲
其中struct v4l2_requestbuffers 结构体描述了申请帧缓冲的信息,代码实现如下:
struct v4l2_requestbuffers reqbuf;
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.count = 3; //3个帧缓冲
reqbuf.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd,VIDIOC_REQBUFS,&reqbuf) < 0){
perror("申请缓冲区失败\n");
return -1;
}
// 将帧缓冲映射到进程地址空间 void *frm_base[3]; //映射后的用户空间的首地址 unsigned int frm_size[3]; struct v4l2_buffer buf; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; // 将每一帧对应的缓冲区的起始地址保存在frm_base数组中,读取采集数据时,只需直接读取映射区即可 for(buf.index=0;buf.index<3;buf.index++){ ioctl(fd, VIDIOC_QUERYBUF, &buf); frm_base[buf.index] = mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,buf.m.offset); frm_size[buf.index] = buf.length; if(frm_base[buf.index] == MAP_FAILED){ perror("mmap failed\n"); return -1; } // 入队操作 if(ioctl(fd,VIDIOC_QBUF,&buf) < 0){ perror("入队失败\n"); return -1; } }
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0){
perror("开始采集失败\n");
return -1;
}
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
readbuffer.memory = V4L2_MEMORY_MMAP;
if(ioctl(fd, VIDIOC_DQBUF, &readbuffer) < 0){
perror("读取帧失败\n");
}
// 保存这一帧,格式为jpg
FILE *file = fopen("v4l2_cap.jpg","w+");
fwrite(frm_base[readbuffer.index],buf.length,1,file);
fclose(file);
读取数据并处理完之后要再次入队
if(ioctl(fd,VIDIOC_QBUF,&readbuffer) < 0){
perror("入队失败\n");
}
// 停止采集
if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0){
perror("停止采集失败\n");
return -1;
}
// 释放映射
for(int i=0;i<3;i++){
munmap(frm_base[i],frm_size[i]);
}
close(fd);
笔者的开发板插上USB摄像头之后生成的采集设备为/dev/video1
在Linux虚拟机也可以运行,用gcc编译即可生成的采集设备为/dev/video0
执行命令 ./v4l2_first_test /dev/video1
输出结果如下
然后可以在同级目录下找到保存的.jpg文件,可以在Linux虚拟机内使用scp命令从开发板下载该图片查看
scp使用方法可以看我的这篇博客: 开发板和虚拟机Linux使用scp命令互传文件
【正点原子】I.MX6U 嵌入式 Linux C 应用编程指南 V1.4
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。