V4L2视频采集与H264编码2—v4l2采集YUV数据

    在上一篇中因为是在PC机上使用的USB摄像头只能支持GPEG image格式,但是H264编码需要使用YUV数据,所以我找了个ARM开发板来做测试。本以为代码从PC机移植到开发板是很简单的一个事,谁知因为平台或是V4L2底层驱动的不同,最终也是花了九牛二虎之力才把问题给解了。话不多说,直接上代码:

/*=============================================================================
#     FileName: v4l2.c
#         Desc: this program aim to get image from USB camera,
#               used the V4L2 interface.
#       Author: Licaibiao
#      Version: 
#   LastChange: 2016-12-10 
#      History:

=============================================================================*/
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <linux/types.h>
#include <linux/videodev2.h>
#include <malloc.h>
#include <math.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>

#define FILE_VIDEO  "/dev/video0"
#define JPG "./out/image%d"

typedef struct{
    void *start;
	int length;
}BUFTYPE;

BUFTYPE *usr_buf;
static unsigned int n_buffer = 0;


/*set video capture ways(mmap)*/
int init_mmap(int fd)
{
	/*to request frame cache, contain requested counts*/
	struct v4l2_requestbuffers reqbufs;

	memset(&reqbufs, 0, sizeof(reqbufs));
	reqbufs.count = 1; 	 							/*the number of buffer*/
	reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;    
	reqbufs.memory = V4L2_MEMORY_MMAP;				

	if(-1 == ioctl(fd,VIDIOC_REQBUFS,&reqbufs))
	{
		perror("Fail to ioctl 'VIDIOC_REQBUFS'");
		exit(EXIT_FAILURE);
	}
	
	n_buffer = reqbufs.count;
	printf("n_buffer = %d\n", n_buffer);
	//usr_buf = calloc(reqbufs.count, sizeof(usr_buf));
	usr_buf = calloc(reqbufs.count, sizeof(BUFTYPE));
	if(usr_buf == NULL)
	{
		printf("Out of memory\n");
		exit(-1);
	}

	/*map kernel cache to user process*/
	for(n_buffer = 0; n_buffer < reqbufs.count; ++n_buffer)
	{
		//stand for a frame
		struct v4l2_buffer buf;
		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = n_buffer;
		
		/*check the information of the kernel cache requested*/
		if(-1 == ioctl(fd,VIDIOC_QUERYBUF,&buf))
		{
			perror("Fail to ioctl : VIDIOC_QUERYBUF");
			exit(EXIT_FAILURE);
		}

		usr_buf[n_buffer].length = buf.length;
		usr_buf[n_buffer].start = (char *)mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED, fd,buf.m.offset);

		if(MAP_FAILED == usr_buf[n_buffer].start)
		{
			perror("Fail to mmap");
			exit(EXIT_FAILURE);
		}

	}

}

int open_camera(void)
{
	int fd;
	struct v4l2_input inp;

	fd = open(FILE_VIDEO, O_RDWR | O_NONBLOCK,0);
	if(fd < 0)
	{	
		fprintf(stderr, "%s open err \n", FILE_VIDEO);
		exit(EXIT_FAILURE);
	};

	inp.index = 0;
	if (-1 == ioctl (fd, VIDIOC_S_INPUT, &inp))
	{
		fprintf(stderr, "VIDIOC_S_INPUT \n");
	}

	return fd;
}

int init_camera(int fd)
{
	struct v4l2_capability 	cap;	/* decive fuction, such as video input */
	struct v4l2_format 		tv_fmt; /* frame format */  
	struct v4l2_fmtdesc 	fmtdesc;  	/* detail control value */
	struct v4l2_control 	ctrl;
	int ret;
	
			/*show all the support format*/
	memset(&fmtdesc, 0, sizeof(fmtdesc));
	fmtdesc.index = 0 ;                 /* the number to check */
	fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

	/* check video decive driver capability */
	if(ret=ioctl(fd, VIDIOC_QUERYCAP, &cap)<0)
	{
		fprintf(stderr, "fail to ioctl VIDEO_QUERYCAP \n");
		exit(EXIT_FAILURE);
	}
	
	/*judge wherher or not to be a video-get device*/
	if(!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))
	{
		fprintf(stderr, "The Current device is not a video capture device \n");
		exit(EXIT_FAILURE);
	}

	/*judge whether or not to supply the form of video stream*/
	if(!(cap.capabilities & V4L2_CAP_STREAMING))
	{
		printf("The Current device does not support streaming i/o\n");
		exit(EXIT_FAILURE);
	}
	
	printf("\ncamera driver name is : %s\n",cap.driver);
	printf("camera device name is : %s\n",cap.card);
	printf("camera bus information: %s\n",cap.bus_info);

	/*display the format device support*/
	printf("\n");
	while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
	{	
		printf("support device %d.%s\n",fmtdesc.index+1,fmtdesc.description);
		fmtdesc.index++;
	}
	printf("\n");

	/*set the form of camera capture data*/
	tv_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      /*v4l2_buf_typea,camera must use V4L2_BUF_TYPE_VIDEO_CAPTURE*/
	tv_fmt.fmt.pix.width = 680;
	tv_fmt.fmt.pix.height = 480;
	tv_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;	/*V4L2_PIX_FMT_YYUV*/
	tv_fmt.fmt.pix.field = V4L2_FIELD_NONE;   		/*V4L2_FIELD_NONE*/
	if (ioctl(fd, VIDIOC_S_FMT, &tv_fmt)< 0) 
	{
		fprintf(stderr,"VIDIOC_S_FMT set err\n");
		exit(-1);
		close(fd);
	}

	init_mmap(fd);
}

int start_capture(int fd)
{
	unsigned int i;
	enum v4l2_buf_type type;
	
	/*place the kernel cache to a queue*/
	for(i = 0; i < n_buffer; i++)
	{
		struct v4l2_buffer buf;
		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;

		if(-1 == ioctl(fd, VIDIOC_QBUF, &buf))
		{
			perror("Fail to ioctl 'VIDIOC_QBUF'");
			exit(EXIT_FAILURE);
		}
	}

	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if(-1 == ioctl(fd, VIDIOC_STREAMON, &type))
	{
		printf("i=%d.\n", i);
		perror("VIDIOC_STREAMON");
		close(fd);
		exit(EXIT_FAILURE);
	}

	return 0;
}


int process_image(void *addr, int length)
{
	FILE *fp;

	static int num = 0;

	char image_name[20];
	sprintf(image_name, JPG, num++);
	if((fp = fopen(image_name, "w")) == NULL)
	{
		perror("Fail to fopen");
		exit(EXIT_FAILURE);
	}
	fwrite(addr, length, 1, fp);
	usleep(500);
	fclose(fp);
	return 0;
}

int read_frame(int fd)
{
	struct v4l2_buffer buf;
	unsigned int i;
	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	//put cache from queue
	if(-1 == ioctl(fd, VIDIOC_DQBUF,&buf))
	{
		perror("Fail to ioctl 'VIDIOC_DQBUF'");
		exit(EXIT_FAILURE);
	}
	assert(buf.index < n_buffer);

	//read process space's data to a file
	process_image(usr_buf[buf.index].start, usr_buf[buf.index].length);
	if(-1 == ioctl(fd, VIDIOC_QBUF,&buf))
	{
		perror("Fail to ioctl 'VIDIOC_QBUF'");
		exit(EXIT_FAILURE);
	}
	return 1;
}


int mainloop(int fd)
{
	int count = 10;
	while(count-- > 0)
	{
		for(;;)
		{
			fd_set fds;
			struct timeval tv;
			int r;

			FD_ZERO(&fds);
			FD_SET(fd,&fds);

			/*Timeout*/
			tv.tv_sec = 2;
			tv.tv_usec = 0;
			r = select(fd + 1,&fds,NULL,NULL,&tv);
			
			if(-1 == r)
			{
				 if(EINTR == errno)
					continue;
				perror("Fail to select");
				exit(EXIT_FAILURE);
			}
			if(0 == r)
			{
				fprintf(stderr,"select Timeout\n");
				exit(-1);
			}

			if(read_frame(fd))
			break;
		}
	}
	return 0;
}

void stop_capture(int fd)
{
	enum v4l2_buf_type type;
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&type))
	{
		perror("Fail to ioctl 'VIDIOC_STREAMOFF'");
		exit(EXIT_FAILURE);
	}
}

void close_camera_device(int fd)
{
	unsigned int i;
	for(i = 0;i < n_buffer; i++)
	{
		if(-1 == munmap(usr_buf[i].start,usr_buf[i].length))
		{
			exit(-1);
		}
	}

	free(usr_buf);

	if(-1 == close(fd))
	{
		perror("Fail to close fd");
		exit(EXIT_FAILURE);
	}
}


void main(void)
{
	int fd;
	fd = open_camera();
	init_camera(fd);
	start_capture(fd);
	mainloop(fd);
	stop_capture(fd);
	close_camera_device(fd);
}

    该代码由上一章的代码移植过来,主要是针对我的板子移植,主要修改了:

(1)camera 打开由阻塞打开改为了非阻塞方式打开

(2)mmap 由原来的MAP_PRIVATE 模式改为MAP_SHARED方式

(3)imag 格式由原来的V4L2_PIX_FMT_JPEG 改为V4L2_PIX_FMT_YUV420

(4)在我的开发板中,必须设定camera 设备的索引号,也就是上面代码中的ioctl (fd, VIDIOC_S_INPUT, &inp) ,值为inp.index = 0; 如果不设置,select的时候会出现select   timeout 的问题。

    将上面代码交叉编译后放到开发板上去运行,结果如下:

/tmp # ./test 

camera driver name is : sunxi-vfe
camera device name is : sunxi-vfe
camera bus information: sunxi_vfe vfe.2

support device 1.planar YUV 422
support device 2.planar YUV 420
support device 3.planar YVU 420
support device 4.planar YUV 422 UV combined
support device 5.planar YUV 420 UV combined
support device 6.planar YUV 422 VU combined
support device 7.planar YUV 420 VU combined
support device 8.MB YUV420
support device 9.YUV422 YUYV
support device 10.YUV422 YVYU
support device 11.YUV422 UYVY
support device 12.YUV422 VYUY
support device 13.RAW Bayer BGGR 8bit
support device 14.RAW Bayer GBRG 8bit
support device 15.RAW Bayer GRBG 8bit
support device 16.RAW Bayer RGGB 8bit
support device 17.RAW Bayer BGGR 10bit
support device 18.RAW Bayer GBRG 10bit
support device 19.RAW Bayer GRBG 10bit
support device 20.RAW Bayer RGGB 10bit
support device 21.RAW Bayer BGGR 12bit
support device 22.RAW Bayer GBRG 12bit
support device 23.RAW Bayer GRBG 12bit
support device 24.RAW Bayer RGGB 12bit

n_buffer = 3
/tmp # ls
hostapd   messages  out       test      utmp
/tmp # cd out
/tmp/out # ls
image0  image2  image4  image6  image8
image1  image3  image5  image7  image9
/tmp/out # ls -l
total 4520
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image0
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image1
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image2
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image3
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image4
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image5
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image6
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image7
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image8
-rw-r--r--    1 root     root        460800 Jan  1 00:01 image9
/tmp/out # 
    从我们输出的信息可以看出,在我开发板上可以支持24种image格式,在这了我们后面H264的编码,这里选用的是V4L2_PIX_FMT_YUV420 格式。我开发板上装的是GC0308 VGA 摄像头,从生成image的大小可以判断出是正确的(YUV420数据大小 = 长 * 宽 * 1.5 = 640 * 480 * 1.5 = 460800 = 450k)

可以将image文件拷出来,使用pYUV 软件查看YUV图片。这里需要注意,使用pYUV 查看YUV图片的时候,需要正确设置图片格式,按我上面代码采集的数据格式,其设置如下图:


正常打开显示的图片为:


到这里采集YUV数据就结束了,下一章介绍使用X264库将YUV数据编码成H264视频文件。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

li_wen01

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

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

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

打赏作者

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

抵扣说明:

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

余额充值