摄像头系统架构分为四层:
摄像头、支持V4L2的摄像头驱动、V4L2核心、应用程序。
V4L2核心是Linux系统自带的组件,它可以屏蔽摄像头驱动层的差异,不管底层的摄像头有什么差异,上层应用统一调用V4L2来实现对摄像头的操作。因此驱动程序和应用程序都需要遵循V4L2规范
#make menuconfig ARCH=arm
找到Device Drivers->Multimedia support,把Video for Linux和Video capture adaptes选上
进入Video capture adaptes,选中并进入V4L USB devices,选并中进入GSPCA based webcams,选中对应的摄像头驱动,如果是免驱的USB摄像头则勾选UVC即可
编译内核
#make uImage ARCH=arm CROSS_COMPILE=arm-linux-
拷贝uImage到/tftpboot/目录下
下载并启动linux内核:
往开发板上插入摄像头后,会弹出相应提示,同时使用ls /dev/可以查看是否有vedio的驱动文件,如果有则说明驱动开发成功。
这部分转自http://blog.csdn.net/eastmoon502136/article/details/8190262
应用程序通过V4L2进行视频采集的原理
V4L2支持内存映射方式(mmap)和直接读取方式(read)来采集数据,前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集,本文重点讨论内存映射方式的视频采集。
应用程序通过V4L2接口采集视频数据分为五个步骤:
首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;
第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;
第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
第五,停止视频采集。
具体的程序实现流程可以参考下面的流程图:
其实其他的都比较简单,就是通过ioctl这个接口去设置一些参数。最主要的就是buf管理。他有一个或者多个输入队列和输出队列。
启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如图所示。
每一个帧缓冲区都有一个对应的状态标志变量,其中每一个比特代表一个状态
V4L2_BUF_FLAG_UNMAPPED 0B0000
V4L2_BUF_FLAG_MAPPED 0B0001
V4L2_BUF_FLAG_ENQUEUED 0B0010
V4L2_BUF_FLAG_DONE 0B0100
缓冲区的状态转化如图所示。
下面的程序注释的很好,就拿来参考下:
1. 定义
V4L2(Video ForLinux Two) 是内核提供给应用程序访问音、视频驱动的统一接口。
2. 工作流程:
打开设备-> 检查和设置设备属性->设置帧格式-> 设置一种输入输出方法(缓冲区管理)-> 循环获取数据-> 关闭设备。
3. 设备的打开和关闭:
#include
int open(constchar *device_name, int flags);
#include
int close(intfd);
例:
[html] view plain copy
注意:V4L2 的相关定义包含在头文件
4. 查询设备属性: VIDIOC_QUERYCAP
相关函数:
[html] view plain copy
相关结构体:
[html] view plain copy
例:显示设备信息
[html] view plain copy
5. 帧格式:
[html] view plain copy
例:显示所有支持的格式
[html] view plain copy
// 查看或设置当前格式
VIDIOC_G_FMT,VIDIOC_S_FMT
// 检查是否支持某种格式
[html] view plain copy
[html] view plain copy
例:显示当前帧的相关信息
[html] view plain copy
例:检查是否支持某种帧格式
[html] view plain copy
6. 图像的缩放
[html] view plain copy
// 设置缩放
[html] view plain copy
7. 申请和管理缓冲区,应用程序和设备有三种交换数据的方法,直接read/write ,内存映射(memorymapping) ,用户指针。这里只讨论 memorymapping.
// 向设备申请缓冲区
[html] view plain copy
例:申请一个拥有四个缓冲帧的缓冲区
[html] view plain copy
获取缓冲帧的地址,长度:
VIDIOC_QUERYBUF
int ioctl(intfd, int request, struct v4l2_buffer *argp);
[html] view plain copy
MMAP ,定义一个结构体来映射每个缓冲帧。
[html] view plain copy
#include
void *mmap(void*addr, size_t length, int prot, int flags, int fd, off_t offset);
//addr 映射起始地址,一般为NULL ,让内核自动选择
//length 被映射内存块的长度
//prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
//flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 确定被映射的内存地址
返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1);
int munmap(void*addr, size_t length);// 断开映射
//addr 为映射后的地址,length 为映射后的内存长度
例:将四个已申请到的缓冲帧映射到应用程序,用buffers 指针记录。
[html] view plain copy
// 映射
[html] view plain copy
8. 缓冲区处理好之后,就可以开始获取数据了
[html] view plain copy
例:把四个缓冲帧放入队列,并启动数据流
[html] view plain copy
例:获取一帧并处理
[html] view plain copy
至于驱动的实现,可以参考内核中,我是用usb摄像头的,所以,其实现都是好的。主要就是应用程序的实现了。驱动都哦在uvc目录下面,这个待理解。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct buffer {
void * start;
size_t length;
};
struct buffer *buffers;
unsigned long n_buffers;
unsigned long file_length;
int file_fd;
char *dev_name = "/dev/video0";
int fd;
static int read_frame (void)
{
struct v4l2_buffer buf;
/*帧出列*/
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl (fd, VIDIOC_DQBUF, &buf);
write(file_fd,buffers[buf.index].start,buffers[buf.index].length);
/*buf入列*/
ioctl(fd, VIDIOC_QBUF, &buf);
return 1;
}
int main (int argc,char ** argv)
{
struct v4l2_capability cap;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
unsigned int i;
enum v4l2_buf_type type;
file_fd = open("test.jpg", O_RDWR | O_CREAT, 0777);
fd = open (dev_name, O_RDWR | O_NONBLOCK, 0);
/*获取驱动信息*/
ioctl (fd, VIDIOC_QUERYCAP, &cap);
printf("Driver Name:%s\n Card Name:%s\n Bus info:%s\n\n",cap.driver,cap.card,cap.bus_info);
/*设置图像格式*/
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 320;
fmt.fmt.pix.height = 240;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
ioctl (fd, VIDIOC_S_FMT, &fmt) ;
/*申请图像缓冲区*/
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ioctl (fd, VIDIOC_REQBUFS, &req);
buffers = calloc (req.count, sizeof (*buffers));
for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
{
/*获取图像缓冲区的信息*/
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
ioctl (fd, VIDIOC_QUERYBUF, &buf);
buffers[n_buffers].length = buf.length;
// 把内核空间中的图像缓冲区映射到用户空间
buffers[n_buffers].start = mmap (NULL , //通过mmap建立映射关系
buf.length,
PROT_READ | PROT_WRITE ,
MAP_SHARED ,
fd,
buf.m.offset);
}
/*图像缓冲入队*/
for (i = 0; i < n_buffers; ++i)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ioctl (fd, VIDIOC_QBUF, &buf);
}
//开始捕捉图像数据
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl (fd, VIDIOC_STREAMON, &type);
fd_set fds;
FD_ZERO (&fds);
FD_SET (fd, &fds);
select(fd + 1, &fds, NULL, NULL, NULL);
/*读取一幅图像*/
read_frame();
for (i = 0; i < n_buffers; ++i)
munmap (buffers[i].start, buffers[i].length);
close (fd);
close (file_fd);
printf("Camera Done.\n");
return 0;
}