V4L2 应用编程

1. 打开设备、关闭设备
在V4L2中,打开、关闭设备同打开其他设备没有什么不同。例如:
fd = open("/dev/video0", O_RDWR);

close(fd);


2. 查询设备属性
在V4L2编程中,会用到很多的ioctl函数,其中这里会跟一个VIDIOC_QUERYCAP命令,最后一个参数是struct v4l2_capability类型,struct v4l2_capability定义如下:

struct v4l2_capability {
	__u8    driver[16];		/* 驱动名 */
	__u8    card[32];		/* 设备名 */
	__u8    bus_info[32];		/* 总线名 */
	__u32   version;		/* 驱动版本号 */
	__u32   capabilities;		
	__u32   device_caps;
	__u32   reserved[3];
};
capabilities字段对于camera设备来说我们只需要关注V4L2_CAP_VIDEO_CAPTURE,camera设备必须具有这个标志位。例如:
int v4l2_querycap(int fd)
{
        int ret;
        struct v4l2_capability cap;

        ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
        if (ret < 0) {
                perror("VIDIOC_QUERYCAP failed");
                return ret;
        }

        return 0;
}

3. 枚举出所支持的像素格式
这里会用到VIDIOC_ENUM_FMT命令,而数据存储在struct v4l2_fmtdesc类型的变量中,struct v4l2_fmtdesc定义如下:
struct v4l2_fmtdesc {
	__u32               index;             /* 像素格式索引值 */
	__u32               type;
	__u32               flags;
	__u8                description[32];   /* 像素格式描述字符串 */
	__u32               pixelformat;       /* 像素格式 */
	__u32               reserved[4];
};
例如:
int v4l2_enum_fmt(int fd, enum v4l2_buf_type type)
{
        struct v4l2_fmtdesc desc;

        desc.index = 0;
        desc.type = type;
        while (ioctl(fd, VIDIOC_ENUM_FMT, &desc) == 0) {
                printf("format %s\n", desc.description);
                desc.index++;
        }

        return 0;
}

4. 获取或设置像素格式
获取和设置像素格式使用VIDIOC_G_FMT和VIDIOC_S_FMT,例如:
int v4l2_g_fmt(int fd, enum v4l2_buf_type type, int *width, int *height, int *bytesperline)
{
        int ret;
        struct v4l2_format fmt;

        fmt.type = type;
        ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
        if (ret < 0) {
                perror("VIDIOC_G_FMT failed");
                return ret;
        }

        *width = fmt.fmt.pix.width;
        *height = fmt.fmt.pix.height;
        *bytesperline = fmt.fmt.pix.bytesperline;

        printf("width %d height %d bytesperline %d\n", *width, *height, *bytesperline);

        return 0;
}

5. 申请缓冲区
申请缓冲区使用VIDIOC_REQBUFS命令,申请缓冲区通常有两种方式mmap和userptr,mmap方式由driver申请,然后通过mmap系统调用映射到用户空间。而userptr方式则有用户空间来申请缓冲区,使用mmap方式的例子如下:
int v4l2_reqbufs(int fd, enum v4l2_buf_type type, int nr_buffer)
{
        int ret;
        struct v4l2_requestbuffers req;

        req.count = nr_buffer;
        req.type = type;
        req.memory = V4L2_MEMORY_MMAP;
        ret = ioctl(fd, VIDIOC_REQBUFS, &req);
        if (ret < 0) {
                perror("VIDIOC_REQBUFS failed");
                return ret;
        }

        return 0;
}
缓冲区申请好了之后,需要使用VIDIOC_QUERYBUF命令将申请好了之后的地址读取出来,并使用mmap系统调用将它映射到用户空间,例如:
int v4l2_querybuf(int fd, enum v4l2_buf_type type, int nr_buffer, struct buffer *video_buffers)
{
        int ret, i;
        struct v4l2_buffer buf;

        for (i = 0; i < nr_buffer; i++) {
                buf.index = i;
                buf.type = type;
                buf.memory = V4L2_MEMORY_MMAP;
                ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
                if (ret < 0) {
                        perror("VIDIOC_QUERYBUF failed");
                        return ret;
                }

                video_buffers[i].length = buf.length;
                video_buffers[i].start = mmap(0, buf.length, PROT_READ | PROT_WRITE,
                                                MAP_SHARED, fd, buf.m.offset);
        }

        return 0;
}
其中用到了变量video_buffers,定义如下:
struct buffer {
	void    *start;
	int     length; 
};

#define NR_BUFFER 4
struct buffer buffers[NR_BUFFER];
变量video_buffers只是用来存储缓冲区映射之后的地址。

6. 将缓冲区加入输入队列
缓冲区申请好了之后,还需要将缓冲区加入输入队列中,这样camera设备才能将图像数据存放在缓冲区中。将缓冲区加入输入队列中使用VIDIOC_QBUF命令,例如:
int v4l2_qbuf(int fd, enum v4l2_buf_type type, int index)
{
        int ret;
        struct v4l2_buffer buf;

        buf.index = index;
        buf.type =  type;
        buf.memory = V4L2_MEMORY_MMAP;
        ret = ioctl(fd, VIDIOC_QBUF, &buf);
        if (ret < 0) {
                perror("VIDIOC_QBUF failed");
                return ret;
        }
        return 0;
}

7. 启动开停止camera设备
一切准备好了之后,可以使用命令VIDIOC_STREAMON来启动camera设备,camera设备开始工作,camera设备就开始往缓冲区传输数据了。例如:
int v4l2_streamon(int fd)
{
        int ret;
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        ret = ioctl(fd, VIDIOC_STREAMON, &type);
        if (ret < 0) {
                perror("VIDIOC_STREAMON failed");
                return ret;
        }

        return 0;
}
camera设备使用完了之后,可以使用VIDIOC_STREAMOFF来停止camera设备,例如:
int v4l2_streamoff(int fd)
{
        int ret;
        enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
        if (ret < 0) {
                perror("VIDIOC_STREAMOFF failed");
                return ret;
        }

        return 0;
}

8. 从输出缓冲区队列中取出一帧数据
从输出缓冲区队列中取出一帧数据使用VIDIOC_DQBUF命令,例如:
int v4l2_dqbuf(int fd, enum v4l2_buf_type type)
{
        int ret;
        struct v4l2_buffer buf;

        buf.type = type;
        buf.memory = V4L2_MEMORY_MMAP;
        ret = ioctl(fd, VIDIOC_DQBUF, &buf);
        if (ret < 0) {
                perror("VIDIOC_DQBUF failed");
                return ret;
        }

        return buf.index;
}

常用的v4l2 API也就这些,完整的代码如下,抓取一张图片并保存为bmp格式。
#include 
#include 		/* open() */
#include 
#include 
#include 		/* close() */
#include 		/* memset()  */
#include 		/* ioctl() */
#include 		/* mmap() */
#include 		/* malloc() */

#include 	/* v4l2 */

struct bitmap_fileheader {
	unsigned short	type;
	unsigned int	size;
	unsigned short	reserved1;
	unsigned short	reserved2;
	unsigned int	off_bits;
} __attribute__ ((packed));

struct bitmap_infoheader {
	unsigned int	size;
	unsigned int	width;
	unsigned int	height;
	unsigned short	planes;
	unsigned short	bit_count;
	unsigned int	compression;
	unsigned int	size_image;
	unsigned int	xpels_per_meter;
	unsigned int	ypels_per_meter;
	unsigned int	clr_used;
	unsigned int	clr_important;
} __attribute__ ((packed));

int saveimage(char *filename, char *p, int width, int height, int bits_per_pixel)
{
	FILE *fp;
	struct bitmap_fileheader fh;
	struct bitmap_infoheader ih;
	int x, y;

	fp = fopen(filename, "wb");
	if (fp == NULL) {
		printf("can't open file %s\n", filename);
		return -1;
	}

	memset(&fh, 0, sizeof(struct bitmap_fileheader));
	fh.type	= 0x4d42;
	fh.off_bits = sizeof(struct bitmap_fileheader) + sizeof(struct bitmap_infoheader);
	fh.size = fh.off_bits + width * height * (bits_per_pixel / 8);
	fwrite(&fh, 1, sizeof(struct bitmap_fileheader), fp);

	memset(&ih, 0, sizeof(struct bitmap_infoheader));
	ih.size = sizeof(struct bitmap_infoheader);
	ih.width = width;
	ih.height = height;
	ih.planes = 1;
	ih.bit_count = bits_per_pixel;
/*	ih.compression = 0;
	ih.size_image = 0;
	ih.xpels_per_meter = 0;
	ih.ypels_per_meter = 0;
	ih.clr_used = 0;
	ih.clr_important = 0;*/
	fwrite(&ih, 1, sizeof(struct bitmap_infoheader), fp);

/*	fwrite(p, 1, width * height * (bits_per_pixel / 8), fp);*/

	p += width * (bits_per_pixel / 8) * (height - 1);
	for (y = 0; y < height; y++, p -= width * (bits_per_pixel / 8)) {
		fwrite(p, 1, width * (bits_per_pixel / 8), fp);
	}

	fclose(fp);

	return 0;
}

void yuv_2_rgb888(unsigned char y, unsigned char u, unsigned char v,
		unsigned char *r, unsigned char *g, unsigned char *b)
{
	/*
	 * From Wikipedia: en.wikipedia.org/wiki/YUV
	 *
	 * r = y + 1.402 * (v - 128)
	 * g = y - 0.344 * (u - 128) - 0.714 * (v - 128)
	 * b = y + 1.772 * (u - 128)
	 */
	int red, green, blue;

	red = y + 1.402 * (v - 128);
	green = y - 0.344 * (u - 128) - 0.714 * (v - 128);
	blue = y + 1.772 * (u - 128);

	*r = ((red > 255)) ? 255 : ((red < 0) ? 0 : red);
	*g = ((green > 255)) ? 255 : ((green < 0) ? 0 : green);
	*b = ((blue > 255)) ? 255 : ((blue < 0) ? 0 : blue);
}

int capture(char *filename, char *buf, int width, int height)
{
	unsigned char *p = NULL;
	int x, y;
	
	unsigned char y0, u0, y1, v0;

	p = (unsigned char *)malloc(width * height * 3);
	if (p == NULL)
		return -1;

	for (y = 0; y < height; y++) {
		for (x = 0; x < width*3; x += 6, buf += 4) {
			y0 = buf[0]; u0 = buf[1]; y1 = buf[2]; v0 = buf[3];

			yuv_2_rgb888(y0, u0, v0, &p[y*width*3+x+0], &p[y*width*3+x+1], &p[y*width*3+x+2]);
			yuv_2_rgb888(y1, u0, v0, &p[y*width*3+x+3], &p[y*width*3+x+4], &p[y*width*3+x+5]);
		}
	}

	saveimage(filename, (char *)p, width, height, 24);
	
	free(p);

	return 0;
}

struct buffer {
	void    *start;
	int     length;
};

#define NR_BUFFER 4
struct buffer buffers[NR_BUFFER];

/* V4L2 API */
int v4l2_querycap(int fd)
{
	int ret;
	struct v4l2_capability cap;

	ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
	if (ret < 0) {
		perror("VIDIOC_QUERYCAP failed");
		return ret;
	}

	return 0;
}

int v4l2_enum_fmt(int fd, enum v4l2_buf_type type)
{
	struct v4l2_fmtdesc desc;

	desc.index = 0;
	desc.type = type;
	while (ioctl(fd, VIDIOC_ENUM_FMT, &desc) == 0) {
		printf("format %s\n", desc.description);
		desc.index++;
	}

	return 0;
}

int v4l2_g_fmt(int fd, enum v4l2_buf_type type, int *width, int *height, int *bytesperline)
{
	int ret;
	struct v4l2_format fmt;

	fmt.type = type;
	ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
	if (ret < 0) {
		perror("VIDIOC_G_FMT failed");
		return ret;
	}

	*width = fmt.fmt.pix.width;
	*height = fmt.fmt.pix.height;
	*bytesperline = fmt.fmt.pix.bytesperline;

	printf("width %d height %d bytesperline %d\n", *width, *height, *bytesperline);

	return 0;
}

int v4l2_s_fmt(int fd)
{
	return 0;
}

int v4l2_reqbufs(int fd, enum v4l2_buf_type type, int nr_buffer)
{
	int ret;
	struct v4l2_requestbuffers req;

	req.count = nr_buffer;
	req.type = type;
	req.memory = V4L2_MEMORY_MMAP;
	ret = ioctl(fd, VIDIOC_REQBUFS, &req);
	if (ret < 0) {
		perror("VIDIOC_REQBUFS failed");
		return ret;
	}

	return 0;
}

int v4l2_querybuf(int fd, enum v4l2_buf_type type, int nr_buffer, struct buffer *video_buffers)
{
	int ret, i;
	struct v4l2_buffer buf;

	for (i = 0; i < nr_buffer; i++) {
		buf.index = i;
		buf.type = type;
		buf.memory = V4L2_MEMORY_MMAP;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &buf);
		if (ret < 0) {
			perror("VIDIOC_QUERYBUF failed");
			return ret;
		}

		video_buffers[i].length = buf.length;
		video_buffers[i].start = mmap(0, buf.length, PROT_READ | PROT_WRITE,
						MAP_SHARED, fd, buf.m.offset);
	}

	return 0;
}

int v4l2_qbuf(int fd, enum v4l2_buf_type type, int index)
{
	int ret;
	struct v4l2_buffer buf;

	buf.index = index;
	buf.type =  type;
	buf.memory = V4L2_MEMORY_MMAP;
	ret = ioctl(fd, VIDIOC_QBUF, &buf);
	if (ret < 0) {
		perror("VIDIOC_QBUF failed");
		return ret;
	}
	return 0;
}

int v4l2_dqbuf(int fd, enum v4l2_buf_type type)
{
	int ret;
	struct v4l2_buffer buf;

	buf.type = type;
	buf.memory = V4L2_MEMORY_MMAP;
	ret = ioctl(fd, VIDIOC_DQBUF, &buf);
	if (ret < 0) {
		perror("VIDIOC_DQBUF failed");
		return ret;
	}

	return buf.index;
}

int v4l2_streamon(int fd)
{
	int ret;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(fd, VIDIOC_STREAMON, &type);
	if (ret < 0) {
		perror("VIDIOC_STREAMON failed");
		return ret;
	}

	return 0;
}

int v4l2_streamoff(int fd)
{
	int ret;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
	if (ret < 0) {
		perror("VIDIOC_STREAMOFF failed");
		return ret;
	}

	return 0;
}
/* V4L2 API end */

int dev_open(char *devname)
{
	int fd;

	fd = open(devname, O_RDWR);
	if (fd < 0) {
		printf("no such video device!\n");
		return -1;
	}

	return fd;
}

void dev_close(int fd)
{
	close(fd);
}

int dev_init(int fd, int *width, int *height, int *bytesperline)
{
	int ret, i;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = v4l2_querycap(fd);
	if (ret < 0)
		return ret;
	
	v4l2_enum_fmt(fd, type);

	ret = v4l2_g_fmt(fd, type, width, height, bytesperline);
	if (ret < 0)
		return ret;

	ret = v4l2_reqbufs(fd, type, NR_BUFFER);
	if (ret < 0)
		return ret;

	ret = v4l2_querybuf(fd, type, NR_BUFFER, buffers);
	if (ret < 0)
		return ret;

	for (i = 0; i < NR_BUFFER; i++) {
		v4l2_qbuf(fd, type, i);
	}

	return 0;
}

int start_preview(int fd)
{
	return v4l2_streamon(fd);
}

int stop_preview(int fd)
{
	return v4l2_streamoff(fd);
}

int start_capture(char *filename, int fd, int width, int height, int byterperline)
{
	int index;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	index = v4l2_dqbuf(fd, type);
	if (index < 0)
		return -1;

	return capture(filename, buffers[index].start, width, height);
}

int main(int argc, char *argv[])
{
	int fd, ret;
	int width, height, bytesperline;

	if (argc < 2) {
		printf("please input a file name!\n");
		return -1;
	}

	fd = dev_open("/dev/video0");
	if (fd < 0)
		return fd;

	ret = dev_init(fd, &width, &height, &bytesperline);
	if (ret < 0)
		return ret;

	ret = start_preview(fd);
	if (ret < 0)
		return ret;

	start_capture(argv[1], fd, width, height, bytesperline);

	ret = stop_preview(fd);
	if (ret < 0)
		return ret;

	dev_close(fd);

	return 0;
}

你可能感兴趣的:(V4L2 应用编程)