要想在Linux下采集摄像头视频,就不可避免的要接触到Viedo4linux,目前为止,它包含两个版本,V4L和V4L2
Viedo4linux2简称V4L2,为linux中关于视频设备的内核驱动。在linux中,视频设备是设备文件,可以像访问普通文件一样对其读写,采用V4L2驱动的摄像头设备文件为/dev/video0
V4L2的主要功能:使得程序有发现设备和操作设备的能力,主要使用一系列的回调函数来实现这些功能。例如:设置摄像头的频率、帧频、视频压缩格式和图像参数等,也可以用于多媒体的开发
V4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头
使用V4L2采集摄像头的流程如下:
V4L2对视频采集的方式主要有两种: 内存映射(mmap)和直接读取(read)
前者主要用于对连续视频数据的采集,后者主要用于静态图片数据的采集
应用程序通过V4L2接口采集视频数据的步骤:
1. 打开设备文件
2. 取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等
3. 选择视频输入,一个视频设备可以有多个视频输入
4. 设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等
5. 向驱动申请帧缓冲,一般不超过5个
6. 将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制
7. 将申请到的帧缓冲全部入队列,以便存放采集到的数据
8. 开始视频的采集
9. 出队列以取得已采集数据的帧缓冲,取得原始采集数据
10. 将缓冲重新入队列尾,这样可以循环采集
11. 停止视频的采集
12. 关闭视频设备
1.int fd_v4l=open("/dev/video0",O_RDWR); //打开设备文件
2.ioctl(fd_v4l,VIDIOC_QUERYCAP,&cap); //查询设备的能力
3.ioctl(fd_v4l,VIDOC_S_STD,&std_id); //设置视频的制式
4.ioctl(fd_v4l,VIDIOC_S_CROP,&crop); //设置视频采集窗口的大小
5.ioctl(fd_v4l,VIDIOC_S_FMT,&fmt); //设置视频帧格式,包括帧的点阵格式,宽、高
6.ioctl(fd_v4l,VIDIOC_S_PARM,&parm); //设置视频的帧率
7.ioctl(fd_v4l,VIDIOC_S_CTRL,&ctrl); //设置视频的旋转方式
8.ioctl(fd_v4l,VIDIOC_S_REQBUFS,&req); //申请若干个帧缓冲区,一般不少于三个
9.ioctl(fd_v4l,VIDIOC_QUERYBUF,&buf); //查询缓冲区的长度和偏移量
10.buffers[i].start=mmap(NULL,buffers[i].length,PROT_READ | PROT_WRITE,
MAP_SHARED,fd_v4l,buffers[i].offset) //内存映射,将帧缓冲区的地址映射到用户空间
11.ioctl(fd_v4l,VIDIOC_QBUF,&buf); //将申请的缓冲区全部放到采集输出队列
12.ioctl(fd_v4l,VIDIOC_STREAMON,&type); //开始视频数据流的采集
13.ioctl(fd_v4l,VIDIOC_DQBUF,&buf); //应用程序将数据取出
14.ioctl(fd_v4l,VIDIOC_QBUF,&buf); //重新将缓冲区放入采集输出队列
15.close(fd_v4l); //关闭视频设备文件
Video4Linux支持的主要数据结构
定义V4L2用户程序结构
struct v4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32capabilities; // 设备支持的操作
__u32reserved[4]; // 保留字段
};
capabilities 常用值:
V4L2_CAP_VIDEO_CAPTURE // 是否支持图像获取
数据帧格式结构
struct v4l2_format
{
enum v4l2_buf_type type; // 数据流类型,必须永远是:V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
__u8 raw_data[200];
} fmt;
};
//像素格式结构
struct v4l2_pix_format
{
__u32 width; // 宽,必须是16的倍数
__u32 height; // 高,必须是16的倍数
__u32 pixelformat; // 视频数据存储类型,例如是//YUV4:2:2还是RGB
enum v4l2_field field;
__u32 bytesperline;
__u32 sizeimage;
enum v4l2_colorspace colorspace;
__u32 priv;
};
请求缓冲
struct v4l2_requestbuffers
{
__u32 count; // 缓冲区内缓冲帧的数目
enumv4l2_buf_type type; // 缓冲帧数据格式
enum v4l2_memorymemory; // 区别是内存映射还是用户指针方式
__u32 reserved[2];
};
利用mmap进行映射的帧的信息
size //帧大小
frames //最多支持的帧数
offsets[VIDEO_MAX_FRAME] //每帧相对基址的偏移
相关函数介绍:
ioctl()函数
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行
控制,例如串口的传输波特率、马达的转速等等。
功能:
控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段。用于向设备发控制和配置命令 ,有些命
令需要控制参数,这些数据是不能用read / write 读写的,称为Out-of-band数据。也就是说,read / write
读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。
程序如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define FB_DEV "/dev/fb0"
#define CONTINUE 0
#define CLEAR(x) memset (&(x), 0, sizeof (x))
struct buffer {
void * start;
size_t length;
};
static char * dev_name = "/dev/video0";//摄像头设备名
static int camera_fd = -1;
struct buffer * buffers = NULL;
static unsigned int n_buffers = 0;
static struct v4l2_format fmt;
static int lcd_fd;
static char * lcd_buf;
struct fb_var_screeninfo vinfo;
static long screensize=0;
/*****************
*LCD 屏幕初始化
******************/
void lcd_init(void)
{
lcd_fd = open (FB_DEV,O_RDWR);
if (lcd_fd < 0){
printf("Error : Can not open framebuffer device\n");
exit(1);
}
if (ioctl(lcd_fd,FBIOGET_VSCREENINFO,&vinfo)){
printf("Error reading variable information\n");
exit(2);
}
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
printf("fb_width = %d, fb_height = %d, fb_depth = %d\n", vinfo.xres,vinfo.yres, vinfo.bits_per_pixel);
lcd_buf =(char *) mmap (0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd,0);
if ((int)lcd_buf == -1){
printf ("Error: failed to map framebuffer device to memory.\n");
exit (3);
}
}
static int read_frame (void)
{
struct v4l2_buffer buf;
unsigned int startx;
unsigned int starty;
int ret = -1;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
/*8.出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF*/
ret = ioctl (camera_fd, VIDIOC_DQBUF, &buf);
if(ret < 0){
printf("error: VIDIOC_DQBUF %s\n", __func__);
goto err;
}
assert(buf.index < n_buffers);
int i,j;
/*9.LCD屏幕显示*/
startx = (vinfo.xres - fmt.fmt.pix.width)/2;
starty = (vinfo.yres - fmt.fmt.pix.height)/2;
for(i = 0; i < fmt.fmt.pix.height; i++){
for(j = 0; j < fmt.fmt.pix.width; j++){
((unsigned short *)lcd_buf)[800 * (i + starty) + j + startx]=((unsigned short *)(buffers[buf.index].start))[fmt.fmt.pix.width * i + j];
}
}
/*10.将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF*/
ret = ioctl (camera_fd, VIDIOC_QBUF, &buf);
if(ret < 0){
printf("error: VIDIOC_QBUF %s\n", __func__);
goto err;
}
err:
return 1;
}
int main (int argc,char ** argv)
{
struct v4l2_capability cap;
enum v4l2_buf_type type;
unsigned int i;
int ret;
sleep(2);
lcd_init();
/*1.打开设备文件。 int fd=open(”/dev/video0″,O_RDWR);*********/
camera_fd = open ("/dev/video0", O_RDWR /* required */ | O_NONBLOCK, 0);//打开设备
/*2.取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability*/
ret = ioctl (camera_fd, VIDIOC_QUERYCAP, &cap);//获取摄像头参数
if(ret < 0){
printf(" VIDIOC_QUERYCAP error!\n");
return -1;
}
/******************视频的输入选择**************
*index = 0 选择摄像头设备
*index = 1 选择屏幕设备,实现画中画功能
**********************************************/
struct v4l2_input input;
memset(&input, 0, sizeof(struct v4l2_input));
input.index = 0;
if(ioctl (camera_fd, VIDIOC_S_INPUT, &input) != 0)
{
printf(" VIDIOC_S_INPUT error!\n");
return -1;
}
/*3.设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。*/
CLEAR (fmt);
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE
//帧的格式,宽度,高度等
if(argc < 2)
{
fmt.fmt.pix.width = 320;
fmt.fmt.pix.height = 240;
}
else
{
fmt.fmt.pix.width = 800;//800;宽,必须是16的倍数
fmt.fmt.pix.height = 480;//480;高,必须是16的倍数
}
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;//视频数据存储类型,LCD屏幕支持显示RGB565 因此改成此格式显示
//fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//add cjf
fmt.fmt.pix.field = V4L2_FIELD_NONE;//V4L2_FIELD_ANY;
//设置当前驱动的频捕获格式
ret = ioctl (camera_fd, VIDIOC_S_FMT, &fmt);
if(ret < 0){
printf(" VIDIOC_S_FMT error!\n");
return -1;
}
/***********************add cjf***************************/
//设置BUF类型
fmt.type = V4L2_BUF_TYPE_PRIVATE;
if (ioctl(camera_fd, VIDIOC_S_FMT, &fmt))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
}
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
//读取当前驱动的频捕获格式
if (ioctl(camera_fd, VIDIOC_G_FMT, &fmt))
{
printf("************** %s, line = %d\n", __FUNCTION__, __LINE__);
}
/**************************end************************/
/*4.向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers*/
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = 4;//缓存数量,也就是说在缓存队列里保持多少张照片
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;//或V4L2_MEMORY_USERPTR
ret = ioctl (camera_fd, VIDIOC_REQBUFS, &req); //申请缓冲,count是申请的数量
if(ret < 0){
printf(" VIDIOC_REQBUFS error!\n");
return -1;
}
if (req.count < 1)
printf(" Insufficient buffer memory\n");
buffers = calloc (req.count, sizeof (*buffers));//内存中建立对应空间
/*5.将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。mmap*/
for (n_buffers = 0; n_buffers < req.count; ++n_buffers){
struct v4l2_buffer buf; //驱动中的一帧
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
//把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
ret = ioctl (camera_fd, VIDIOC_QUERYBUF, &buf);
if(ret < 0){
printf(" VIDIOC_QUERYBUF error!\n");
return -1;
}
//映射用户空间
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap ( NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
camera_fd, buf.m.offset);//通过mmap建立映射关系,返回映射区的起始地址
if (MAP_FAILED == buffers[n_buffers].start){
printf (" mmap failed\n");
return -1;
}
memset(buffers[n_buffers].start, 0, buf.length);
}
/*6.将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer*/
for (i = 0; i < n_buffers; ++i){
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
//把数据从缓存中读取出来
if (-1 == ioctl (camera_fd, VIDIOC_QBUF, &buf))//申请到的缓冲进入列队
printf (" VIDIOC_QBUF failed\n");
}
/*7.开始视频的采集。VIDIOC_STREAMON*/
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl (camera_fd, VIDIOC_STREAMON, &type))//开始捕捉图像数据
{
printf (" VIDIOC_STREAMON failed\n");
return -1;
}
for (;;)//这一段涉及到异步IO
{
fd_set fds;
struct timeval tv;
int r;
FD_ZERO (&fds);//将指定的文件描述符集清空
FD_SET (camera_fd, &fds);//在文件描述符集合中增加新的文件描述符
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select (camera_fd + 1, &fds, NULL, NULL, &tv);//判断是否可读(即摄像头是否准备好),tv是定时
if (-1 == r){
if(EINTR == errno)
continue;
printf ("select err\n");
}
if (0 == r){
fprintf(stderr, "select timeout\n");
exit (EXIT_FAILURE);
}
read_frame();
//usleep(100000);
}
unmap:
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap (buffers[i].start, buffers[i].length))
printf ("munmap error");
if (-1 == munmap (lcd_buf, screensize))
printf ("munmap error");
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/*10.停止视频的采集。VIDIOC_STREAMOFF*/
/**************add*************/
if (-1 == ioctl(camera_fd, VIDIOC_STREAMOFF, &type))
printf("VIDIOC_STREAMOFF");
/***************end************/
/*11.关闭视频设备。close(fd);*/
close(camera_fd);
close(lcd_fd);
exit (EXIT_SUCCESS);
return 0;
}
参考链接如下:http://blog.chinaunix.net/uid-23065002-id-5176233.html
https://blog.csdn.net/sinat_27489187/article/details/49951745