嵌入式设备(linux)摄像头视频采集

要想在Linux下采集摄像头视频,就不可避免的要接触到Viedo4linux,目前为止,它包含两个版本,V4L和V4L2

Viedo4linux2简称V4L2,为linux中关于视频设备的内核驱动。在linux中,视频设备是设备文件,可以像访问普通文件一样对其读写,采用V4L2驱动的摄像头设备文件为/dev/video0

V4L2的主要功能:使得程序有发现设备和操作设备的能力,主要使用一系列的回调函数来实现这些功能。例如:设置摄像头的频率、帧频、视频压缩格式和图像参数等,也可以用于多媒体的开发

V4L2是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头

使用V4L2采集摄像头的流程如下:

嵌入式设备(linux)摄像头视频采集_第1张图片

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

你可能感兴趣的:(ARM开发)