一、摄像头设计思路
摄像头在linux系统下的设备名:
/dev/video0
1.学习V4L2编程框架
摄像头采集基本流程
1.参照v4l2手册采集yuyv图片 (v4l2)
2.将yuyv转码rgb (转换成bmp) (rgb图片数据用于lcd屏幕显示)
3.压缩 (压缩成jpg)
二、V4L2框架
1、简介
Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。
在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文件是/dev/video0。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。
而摄像头所用的主要是capature了,视频的捕捉,具体linux的调用可以参考下图。
2、操作流程
- 打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
- 申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
- 将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
- 驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
- 停止视频采集。
通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示。
每一个帧缓冲区都有一个对应的状态标志变量,其中每一个比特代表一个状态
V4L2_BUF_FLAG_UNMAPPED 0B0000
V4L2_BUF_FLAG_MAPPED 0B0001
V4L2_BUF_FLAG_ENQUEUED 0B0010
V4L2_BUF_FLAG_DONE 0B0100
缓冲区的状态转化如图所示。
3、V4L2框架使用
(1)打开摄像头设备
打开视频设备非常简单,在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
#include <unistd.h>
int close(int fd);
1. 用非阻塞模式打开摄像头设备
int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK);
2. 如果用阻塞模式打开摄像头设备,上述代码变为:
cameraFd = open("/dev/video0", O_RDWR);
应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。
(2)摄像头操作
设置视频设备属性通过ioctl来进行设置
ioctl有三个参数,分别是fd, cmd,和parameter
fd: 表示设备描述符
cmd:控制命令
parameter:控制命令参数
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
功能:控制驱动设备工作状态
参数:
fd : 文件描述符
request : 命令码
...... : 传递的数据-->必须传递数据的地址
(可以理解为在用户空间和内核空间的传递只能用地址传递了,不能值传递了)
写成省略号是因为:可以不传递数据
返回值:
成功 返回0
失败 返回 -1 并置位错误码
V4L2 的相关定义包含在头文件<linux/videodev2.h>中
#include <linux/videodev2.h>
相关IOCTL接口命令
VIDIOC_REQBUFS //分配内存
VIDIOC_QUERYBUF //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP //查询驱动功能
VIDIOC_ENUM_FMT //获取当前驱动支持的视频格式
VIDIOC_S_FMT //设置当前驱动的频捕获格式
VIDIOC_G_FMT //读取当前驱动的频捕获格式
VIDIOC_TRY_FMT //验证当前驱动的显示格式
VIDIOC_CROPCAP //查询驱动的修剪能力
VIDIOC_S_CROP //设置视频信号的矩形边框
VIDIOC_G_CROP //读取视频信号的矩形边框
VIDIOC_QBUF //把数据从缓存中读取出来
VIDIOC_DQBUF //把数据放回缓存队列
VIDIOC_STREAMON //开始视频显示函数
VIDIOC_STREAMOFF //结束视频显示函数
VIDIOC_QUERYSTD //检查当前视频设备支持的标准,例如PAL或NTSC。
(3)摄像头属性查询
#include <linux/videodev2.h>
structv4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32capabilities; // 设备支持的操作
__u32reserved[4]; // 保留字段
};
capabilities 常用值:
V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING
表示是一个视频捕捉设备并且具有数据流控制模式
capabilities域是一个位掩码用来描述驱动能做的不同的事情:
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /*是视频捕获设备*/
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /*是视频输出设备*/
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /*可以进行视频覆盖*/
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /*是原始VBI捕获设备*/
#define V4L2_CAP_VBI_OUTPUT 0x00000020 /*是原始VBI输出设备*/
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /*是切片VBI捕获设备*/
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /*是切片VBI输出设备*/
#define V4L2_CAP_RDS_CAPTURE 0x00000100 /*RDS数据捕获*/
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /*可以进行视频输出叠加*/
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /*可以进行硬件频率搜索*/
#define V4L2_CAP_TUNER 0x00010000 /*具有调谐器*/
#define V4L2_CAP_AUDIO 0x00020000 /*支持音频*/
#define V4L2_CAP_RADIO 0x00040000 /*是无线电设备*/
#define V4L2_CAP_READWRITE 0x01000000 /*读/写系统调用*/
#define V4L2_CAP_ASYNCIO 0x02000000 /*异步I/O*/
#define V4L2_CAP_STREAMING 0x04000000 /*流式I/O ioctls*/
使用方法
struct v4l2_capability cap;
//VIDIOC_QUERYCAP 是查询驱动功能
int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (ret < 0) {
printf("VIDIOC_QUERYCAP error\n");
return -1;
}
printf("驱动名 : %s\n",cap.driver);
printf("设备名字 : %s\n",cap.card);
printf("总线信息 : %s\n",cap.bus_info);
printf("驱动版本号 : %d\n",cap.version);
//常用值:V4L2_CAP_VIDEO_CAPTURE 代表是否支持图片获取
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
perror("V4L2_CAP_VIDEO_CAPTURE failed\n");
close(fd);
exit(EXIT_FAILURE);
}
//是否支持视频流
if(!(cap.capabilities & V4L2_CAP_STREAMING)){
perror("V4L2_CAP_STREAMING failed\n");
close(fd);
exit(EXIT_FAILURE);
}
//或者写到一起
//判断是否为视频捕获设备*/
if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE){
printf("视频捕获设备:");
//判断是否支持视频流捕获
if(cap.capabilities & V4L2_CAP_STREAMING){
printf("支持视频流捕获\n");
}else{
printf("不支持视频流捕获\n");
}
}else {
printf("非视频流捕获设备\n");
return -1;
}