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;
}
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;
}
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_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只是用来存储缓冲区映射之后的地址。
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_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;
}
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;
}
#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;
}