采集并显示摄像头视频流的程序实例

V4L2(Video for Linux 2)是Linux为视频设备所提供的内核驱动和应用程序统一接口体系. 有了它,应用程序就可以通过调用设备接口控制函数ioctl(),来实现对视频设备的各种操控.通过一个摄像头设备的操控程序实例,我们可以学习到应用V4L2系统的步骤和重点.

1. 调用一个摄像头设备的控制函数ioctl(),主要涉及使用下面的一些控制代码(IOCT CODE):
VIDIOC_QUERYCAP  查询设备驱动相关信息及支持的各项功能,返回struct v4l2_queryctrl, 包括驱动名称,硬件名称, bus信息,及是否具备视频捕获/数据流控/音频支持/RDS输入输出等等各项功能.

VIDIOC_QUERYCTRL  查询设备支持可调节的各种控制变量,返回struct v4l2_control, 比如亮度,对比度,饱和度,色度,白平衡(白点位置)等等

VIDIOC_G_CTRL     获取指定控制变量的数值,返回struct v4l2_control, 比如亮度的最小值/最大值/默认值/当前值等.
VIDIOC_S_CTRL    设定指定控制变量的数值  (关联 struct v4l2_control)

VIDIOC_G_FMT    获取数据流格式, 对视频数据流而言, 返回struct v4l2_pix_format, 包含了图像尺寸,图像格式(yuyv/mjpeg...),每行大小bytesperline, 色彩空间colorspace等.
VIDIOC_S_FMT    设定指定数据流格式 (关联 struct v4l2_format )

VIDIOC_G_PARM    获取模式参数, 对视频捕获而言, 返回struct v4l2_captureparm, 主要有帧率FPS等信息.
VIDIOC_S_PARM    设定功能模式参数  (关联struct v4l2_captureparm)

VIDIOC_ENUM_FMT    枚举指定模式下支持的各种数据格式,返回struct v4l2_fmtdesc, 比如摄像头设备在V4L2_BUF_TYPE_VIDEO_CAPTURE模式下. 支持MJPEG和YUV4:2:2图像格式.

VIDIOC_REQBUFS    申请帧缓存,可指定帧缓存的数量和内存映射形式等 (关联 struct v4l2_buffer)
VIDIOC_QUERYBUF    获取帧缓存相关信息,返回struct v4l2_buffer, 包括帧缓存的编号,类型,时间戳,地址,大小等.

VIDIOC_DQBUF    将当前帧缓存从工作队列出取出, 返回struct v4l2_buffer, 包括帧缓存的编号,类型,时间戳,地址,大小等.
VIDIOC_QBUF        将当前帧缓存放入工作队列,以供摄像头写入数据.(关联struct v4l2_buffer)

2. 应用程序操作摄像头设备的基本流程:
2.1 打开摄像头设备文件
2.2 获取设备驱动所支持的各种能力(capability)
      比如: 是否支持视频捕获(video capture); 是否支持数据流控(video streaming I/O ioctls)等.
2.3 查询和设置摄像头的各项控制变量: 比如亮度,对比度,饱和度,色度等等.
2.4 配置视频数据流格式: 模式及相关参数. 比如视频捕捉模式下的画面尺寸,图像格式(MJPEG, YUYV等)
2.5 设置视频捕获模式参数,比如帧率FPS.
2.6 向内核申请视频帧缓存.
2.7 启动设备,开始视频捕获.
2.8 读取帧缓存内数据,对数据进行处理/保存/显示等.

3. 读取摄像头数据并进行显示的程序实例源码: (部分用到EGi库)

/*-------------------------------------------------------------------------------------
操作指定摄像头设备,采集视频数据并将它实时显示在LCD上.支持YUYV和MJPEG格式.

Usage example:
	./test_usbcam -d /dev/video0 -s 320x240 -r 30    (mjpg)
	./test_usbcam -d /dev/video1  -r 20 -f yuyv -v   (yuyv)

Reference:
  超群天晴    https://www.cnblogs.com/surpassal/archive/2012/12/19/zed_webcam_lab1.html
  JWH SMITH   http://jwhsmith.net/2014/12/capturing-a-webcam-stream-using-v4l2

Midas Zhou
-------------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include <linux/videodev2.h> /* include <linux/v4l2-controls.h>, <linux/v4l2-common.h> */

#include <egi_input.h>
#include <egi_fbdev.h>
#include <egi_image.h>
#include <egi_bjp.h>
#include <egi_FTsymbol.h>
#include <egi_timer.h>

/* 申请的帧缓存页数 */
#define REQ_BUFF_NUMBER	5	/* required buffers to be allocated. */

/* 用来记录帧缓存的地址和大小 */
struct buffer {
        void *                  start;
        size_t                  length;
};
struct buffer *buffers;
unsigned long bufindex;

/* 记录保存摄像头的控制变量和对应数值 */
struct v4l2_control vctrl_params[] =
{
  { V4L2_CID_BRIGHTNESS,        0 },
  { V4L2_CID_CONTRAST,          0 },
  { V4L2_CID_SATURATION,        0 },
  { V4L2_CID_SHARPNESS,         0 },
  { V4L2_CID_HUE,               0 },
  { V4L2_CID_GAMMA,             0 },
};


/* 显示标题和时间戳 */
void draw_marks(void);


/*---------------------------
        Print help
-----------------------------*/
void print_help(const char* cmd)
{
        printf("Usage: %s [-hd:s:r:f:v] \n", cmd);
        printf("        -h   help \n");
        printf("        -d:  Video device, default: '/dev/video0' \n");
        printf("        -s:  Image size, default: '320x240' \n");
        printf("        -r:  Frame rate, default: 10fps \n");
        printf("        -f:  Pixel format, default: V4L2_PIX_FMT_MJPEG \n");
        printf("             'yuyv' or 'mjpeg' \n");
	printf("	-v:  Reverse upside down, default: false. for YUYV only.\n");
}


/*-----------------------------
	      MAIN
------------------------------*/
int main (int argc,char ** argv)
{
	char *dev_name = "/dev/video0";
	int fd_dev;	/* Video device */
   	fd_set fds;
	unsigned char *rgb24=NULL;

	struct v4l2_queryctrl		queryctrl={0};
	struct v4l2_control		vctrl;
     	struct v4l2_capability 		cap;
     	struct v4l2_format 		fmt;
     	struct v4l2_fmtdesc 		fmtdesc;
     	struct v4l2_requestbuffers 	req;
     	struct v4l2_buffer 		bufferinfo;
     	struct v4l2_streamparm 		streamparm;
     	enum v4l2_buf_type type;

	unsigned int 	i;
	int	opt;
	char	*pt;

	/* 默认参数 */
	/* Defaut params: pxiformat, image size and frame rate */
	int 	pixelformat=V4L2_PIX_FMT_MJPEG;
	int	width=320;
	int	height=240;
	int	fps=10;
	bool	reverse=false;

	/* 程序选项 */
        /* Parse input option */
        while( (opt=getopt(argc,argv,"hd:s:r:f:v"))!=-1 ) {
                switch(opt) {
                        case 'h':
                                print_help(argv[0]);
                                exit(0);
                                break;
                        case 'd':	/* Video device name */
				dev_name=optarg;
				break;
			case 's':	/* Image size */
				width=atoi(optarg);
				if((pt=strstr(optarg,"x"))!=NULL)
					height=atoi(pt+1);
				else
					height=atoi(strstr(optarg,"X"));
				break;
			case 'r':	/* Frame rate */
				fps=atoi(optarg);
				printf("Input fps=%d\n", fps);
				break;
			case 'f':
				if(strstr(optarg, "yuyv"))
					pixelformat=V4L2_PIX_FMT_YUYV;
				break;
			case 'v':
				reverse=true;
				break;
		}
	}

        /* Init sys FBDEV  初始化FB显示设备 */
        if( init_fbdev(&gv_fb_dev) )
                return -1;

	/* Load FT derived sympg_ascii 加载ASCII字体 */
	if(FTsymbol_load_allpages() !=0) {
		return -1;
	}

	/* 设置显示模式: 是否直接操作FB映像数据, 设置横竖屏 */
        /* Set FB mode */
	fb_set_directFB(&gv_fb_dev, false);
      	fb_position_rotate(&gv_fb_dev, 0);

	/* 打开摄像头设备文件 */
	/* Open video device */
     	fd_dev = open (dev_name, O_RDWR);
	if(fd_dev<0) {
      	  	printf("Fail to open '%s', Err'%s'\n", dev_name, strerror(errno));
		return -1;
	}

	/* 获取设备驱动支持的操作 */
	/* Get video driver capacity */
      	if( ioctl (fd_dev, VIDIOC_QUERYCAP, &cap) !=0) {
      	  	printf("Fail to ioctl VIDIOC_QUERYCAP, Err'%s'\n", strerror(errno));
	  	return -1;
      	}
  	printf("Driver Name:%s\n Card Name:%s\n Bus info:%s Capibilities:0x%04X\n",cap.driver,cap.card,cap.bus_info, cap.capabilities);
      	if( !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) {  /* 是否支持视频捕获 */
	      	printf("The video device does NOT supports capture.\n");
		  	return -1;
      	}
      	if( !(cap.capabilities & V4L2_CAP_STREAMING) ) {      /* 是否支持数据流控 streaming I/O ioctls */
	      	printf("The video device does NOT support streaming!\n");
		return -1;
	}

	/* 在这里查询/设置摄像头的各项控制变量: 亮度,对比度,饱和度,色度等等. */
	/* Set contrast/brightness/saturation/hue/sharpness/gamma/...
	 * Default usually is OK.
	 */
	printf("\n\t\t--- Control params ---\n");
	for(i=0; i<sizeof(vctrl_params)/sizeof(struct v4l2_control); i++) {
		/* Query Contro 获取设备支持的各项控制变量及其数值 */
		vctrl.id=vctrl_params[i].id;
		queryctrl.id=vctrl_params[i].id;
		if( ioctl( fd_dev, VIDIOC_QUERYCTRL, &queryctrl) !=0 ) {
	      	  	printf("Fail to ioctl VIDIOC_QUERYCTRL for control ID=%d, Err'%s'\n", i, strerror(errno));
			continue;
		}
        	if( ioctl( fd_dev, VIDIOC_G_CTRL, &vctrl) !=0 ) {
	      	  	printf("Fail to ioctl VIDIOC_G_CTRL for control ID=%d, Err'%s'\n", i, strerror(errno));
			continue;
		}
		else {
			printf("%s:	min=%d	max=%d	default=%d  value=%d\n",
					queryctrl.name, queryctrl.minimum, queryctrl.maximum, queryctrl.default_value,
					vctrl.value);
		}

		/* 将亮度设置到其值域的80%, 其他也可以类似设置. */
		/* Change brightness to 80% of its range. */
		if(vctrl.id==V4L2_CID_BRIGHTNESS) {
			vctrl.value=queryctrl.minimum+(queryctrl.maximum-queryctrl.minimum)*80/100;
			ioctl( fd_dev, VIDIOC_S_CTRL, &vctrl);
		}
	}

	/* 设置视频数据流格式: 模式及相关参数 */
	/* Set frame format */
     	fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;	/* 视频捕捉模式 */
     	fmt.fmt.pix.width       = width;			/* 视频尺寸 */
     	fmt.fmt.pix.height      = height;
     	fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;	/* 隔行交替 */
     	fmt.fmt.pix.pixelformat = pixelformat;			/*  MJPEG or YUYV */
     	if( ioctl (fd_dev, VIDIOC_S_FMT, &fmt) !=0) {
    		printf("Fail to ioctl VIDIOC_S_FMT, Err'%s'\n", strerror(errno));
		return -2;
	}
     	if( ioctl( fd_dev, VIDIOC_G_FMT, &fmt) !=0 ) {
    		printf("Fail to ioctl VIDIOC_G_FMT, Err'%s'\n", strerror(errno));
		return -2;
	}
	else {
		/* 打印实际设置生效的参数 */
		printf("\n\t--- Effective params ---\n");
	     	printf("fmt.type: 	%d\n", fmt.type);
     		printf("pixelformat:	%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat>>8) & 0xFF,
						(fmt.fmt.pix.pixelformat>>16) & 0xFF, (fmt.fmt.pix.pixelformat>>24) & 0xFF);
	     	printf("Width&Height: 	%d&%d\n",fmt.fmt.pix.width, fmt.fmt.pix.height);
     		printf("Bytesperline: 	%d\n", fmt.fmt.pix.bytesperline);
     		printf("Pixfield: 	%d (1-no field)\n", fmt.fmt.pix.field);
		printf("Colorspace:	%d\n", fmt.fmt.pix.colorspace);
	}

	/* 获取生效的实际画面尺寸 */
	/* Confirm width and height */
	width=fmt.fmt.pix.width;
	height=fmt.fmt.pix.height;

	/* 设置流控参数FPS */
     	/* Set stream params: frame rate */
     	memset(&streamparm, 0, sizeof(streamparm));
     	streamparm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
     	streamparm.parm.capture.timeperframe.denominator=fps;
     	streamparm.parm.capture.timeperframe.numerator=1;
     	if( ioctl( fd_dev, VIDIOC_S_PARM, &streamparm) !=0) {
    		printf("Fail to ioctl VIDIOC_S_PARM, Err'%s'\n", strerror(errno));
		return -3;
	}
	if( ioctl( fd_dev, VIDIOC_G_PARM, &streamparm) !=0) {
    		printf("Fail to ioctl VIDIOC_G_PARM, Err'%s'\n", strerror(errno));
		return -3;
	}
	else {
		printf("Frame rate: %d/%d fps\n", streamparm.parm.capture.timeperframe.denominator,
							streamparm.parm.capture.timeperframe.numerator);
	}

	/* 枚举支持的视频格式 */
     	/* Enumerate formats supported by the vide device */
     	fmtdesc.index=0;
     	fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
     	printf("The device supports following formats for video captrue:\n");
     	while( ioctl( fd_dev, VIDIOC_ENUM_FMT, &fmtdesc)==0 ) {
		printf("\t%d.%s\n", fmtdesc.index+1, fmtdesc.description);
		fmtdesc.index++;
     	}

	/* 申请帧缓存 */
	/* Allocate buffers */
     	req.count               = REQ_BUFF_NUMBER;
     	req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
     	req.memory              = V4L2_MEMORY_MMAP;
     	if( ioctl (fd_dev, VIDIOC_REQBUFS, &req) !=0) {
    		printf("Fail to ioctl VIDIOC_REQBUFS, Err'%s'\n", strerror(errno));
		return -4;
	}
	/* 实际申请到的帧缓存页数 Actual buffer number allocated */
	printf("Req. buffer ret/req:\t%d/%d \n", req.count, REQ_BUFF_NUMBER);


	/* 获取帧缓存的大小和地址,并将其保存到buffers结构中 */
	/* Allocate buffer info */
     	buffers = calloc (req.count, sizeof (*buffers));
	/* Get buffer info */
	memset(&bufferinfo, 0, sizeof(bufferinfo));
     	for (bufindex = 0; bufindex < req.count; ++bufindex)
     	{
           	bufferinfo.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
           	bufferinfo.memory      = V4L2_MEMORY_MMAP;
           	bufferinfo.index       = bufindex;

           	if( ioctl (fd_dev, VIDIOC_QUERYBUF, &bufferinfo) !=0) {
	    		printf("Fail to ioctl VIDIOC_QUERYBUF, Err'%s'\n", strerror(errno));
			return -4;
		}
           	buffers[bufindex].length = bufferinfo.length;

		/* Mmap to user space. the device MUST support V4L2_CAP_STREAMING mode! */
		buffers[bufindex].start = mmap (NULL, bufferinfo.length, PROT_READ | PROT_WRITE,
			                        	                MAP_SHARED, fd_dev, bufferinfo.m.offset);
		if(buffers[bufindex].start == MAP_FAILED) {
	    		printf("Fail to mmap buffer, Err'%s'\n", strerror(errno));
			return -4;
		}

		/* Clear it */
		memset(buffers[bufindex].start, 0, bufferinfo.length);
     	}

	/* 启动捕获视频,注意:有些设备必须将帧缓存先放入队列后才能启动! */
	/* Start to capture video.  some devices may need to queue the buffer at first! */
    	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    	if( ioctl (fd_dev, VIDIOC_STREAMON, &type) !=0) {
    		printf("Fail to ioctl VIDIOC_STREAMON, Err'%s'\n", strerror(errno));
		return -5;
	}

	/* 将帧缓存放入工作队列,以供摄像头写入数据. */
	/* Queue buffer after turn on video capture! */
       	for (i = 0; i < REQ_BUFF_NUMBER; ++i) {
               bufferinfo.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
               bufferinfo.memory      = V4L2_MEMORY_MMAP;
               bufferinfo.index       = i;
               if( ioctl (fd_dev, VIDIOC_QBUF, &bufferinfo) !=0) {
		    	printf("Fail to ioctl VIDIOC_QBUF, Err'%s'\n", strerror(errno));
			return -4;
		}
	}

   	/* Reset index, from which it starts the loop. */
   	bufferinfo.index=0;


		/* 循环例程: 从摄像头读取视频数据,将其输出到FB显示出来. */
        	/* ---------- Loop reading video frames and display --------*/

	/* 1. V4L2_PIX_FMT_YUYV格式读取和显示循环例程序 */
	if( pixelformat==V4L2_PIX_FMT_YUYV )
	{
		printf("Output format: YUYV, reverse=%s\n", reverse?"Yes":"No");

   		/* 为RBG888数据申请内存 */
   		rgb24=calloc(1, width*height*3);
   		if(rgb24==NULL) {
			printf("Fail to calloc rgb24!\n");
			goto END_FUNC;
   		}

   		/* 循环: 帧缓存出列-->读数据转码成RGB888--->帧缓存入列--->显示图像 */
   		for(;;) {

			/* 等待数据 */
   			FD_ZERO (&fds);
   			FD_SET (fd_dev, &fds);
   			select(fd_dev + 1, &fds, NULL, NULL, NULL);

			/* 将当前帧缓存从工作队列出取出 */
    			/* Dequeue the buffer */
        		bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			bufferinfo.memory = V4L2_MEMORY_MMAP;
        		bufferinfo.index++;
			if(bufferinfo.index==REQ_BUFF_NUMBER)
				bufferinfo.index=0;
		        if( ioctl(fd_dev, VIDIOC_DQBUF, &bufferinfo) !=0)
                		printf("Fail to ioctl VIDIOC_DQBUF, Err'%s'\n", strerror(errno));
			else {
				/* 将当前缓存里的数据转化成RGB888格式放入到rbg24中 */
				/* Convert YUYV to RGB888 */
				egi_color_YUYV2RGB888(buffers[bufferinfo.index].start, rgb24, width, height, reverse);

				/* 将当前帧缓存放入工作队列,以供摄像头写入数据.*/
        			/* Queue the buffer */
		        	if( ioctl(fd_dev, VIDIOC_QBUF, &bufferinfo) !=0)
	        		        printf("Fail to ioctl VIDIOC_QBUF, Err'%s'\n", strerror(errno));

				/* 显示图像 */
				/* DirectFB write */
				egi_imgbuf_showRBG888(rgb24, width, height, &gv_fb_dev, 0, 0);
				draw_marks();		/* 加上标题时间戳 */
				fb_render(&gv_fb_dev);  /* 刷新Framebuffer */
			}
   		}

	} /* End V4L2_PIX_FMT_YUYV */

	/* 2. V4L2_PIX_FMT_MJPEG格式读取和显示循环例程序 */
	else
	{
   		printf("Output format: MJPEG\n");

		/* 循环: 帧缓存出列-->解码显示JPG图像--->帧缓存入列 */
   		for(;;) {

			/* 等待数据 */
   			FD_ZERO (&fds);
   			FD_SET (fd_dev, &fds);
   			select(fd_dev + 1, &fds, NULL, NULL, NULL);

			/* 将当前帧缓存从工作队列出取出 */
    			/* Dequeue the buffer */
        		bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			bufferinfo.memory = V4L2_MEMORY_MMAP;
		        bufferinfo.index++;
			if(bufferinfo.index==REQ_BUFF_NUMBER)
				bufferinfo.index=0;
		        if( ioctl(fd_dev, VIDIOC_DQBUF, &bufferinfo) !=0)
                		printf("Fail to ioctl VIDIOC_DQBUF, Err'%s'\n", strerror(errno));
			else {
				/* 直接解码显示缓存中的JPG图像数据 */
				show_jpg(NULL, buffers[bufferinfo.index].start, buffers[bufferinfo.index].length, &gv_fb_dev, 0, 0, 0);
				draw_marks();		/* 加上标题时间戳 */
				fb_render(&gv_fb_dev); 	/* 刷新Framebuffer */
			}

			/* 将当前帧缓存放入工作队列,以供摄像头写入数据.*/
		        /* Queue the buffer */
		        if( ioctl(fd_dev, VIDIOC_QBUF, &bufferinfo) !=0)
                		printf("Fail to ioctl VIDIOC_QBUF, Err'%s'\n", strerror(errno));
   		}

	} /* End  V4L2_PIX_FMT_MJPEG */


END_FUNC:

   /* 关闭视频流 */
   type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
   if( ioctl(fd_dev, VIDIOC_STREAMOFF, &type) !=0)
   	printf("Fail to ioctl VIDIOC_DQBUF, Err'%s'\n", strerror(errno));

   /* 解除内存映射 */
   for (i = 0; i < REQ_BUFF_NUMBER; ++i)
      munmap (buffers[i].start, buffers[i].length);

   /* 释放申请的内存 */
   free(buffers);
   free(rgb24);

   /* 关闭设备文件 */
   close (fd_dev);

   /* 释放字符映像 */
   symbol_release_allpages();

   return 0;
}


/*------------------------------------------
显示标题和时间戳
Put titles and time stamp on the FB image.
------------------------------------------*/
void draw_marks(void)
{
	char strtmp[32];

	/* 标题 */
        symbol_string_writeFB(&gv_fb_dev, &sympg_ascii,
                               WEGI_COLOR_BLUE, -1,     	/* fontcolor, int transpcolor */
                               10, 5,          			/* int x0, int y0 */
                               "EGi:  A Linux Player", 255);    /* string, opaque */
	/* 时间戳 */
	tm_get_strdaytime(strtmp);
        symbol_string_writeFB(&gv_fb_dev, &sympg_ascii,
                                WEGI_COLOR_ORANGE, -1,
                                320/2-20, 240-20,
                                strtmp, 255);
}

 

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Midas-Zhou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值