文章目录
- 1 V4L2
- 1.1 V4L2特点
- 1.2 V4L2设备
- 2 V4L2设备访问接口
- 2.1设备访问
- 2.1.1 查询设备属性
- 2.1.2 查询设备输出格式
- 2.1.3 设置帧输出格式
- 2.1.4 申请帧缓存
- 2.1.5 内核内存转换
- 2.1.6 缓冲帧内存入队操作
- 2.1.7 启动视频流采集
- 2.1.8 读取数据帧内存序号
- 2.1.9 关闭视频流采集
- 2.2 视频流读取
- 3 V4L2应用开发流程
- 4 参考文章
1 V4L2
V4L2全称是Video for Linux two(Video4Linux2),是V4L2改进衍生版本。V4L2是linux操作系统下的一个标注化的音频、视频设备驱动框架,向下屏蔽底层设备的差异,向上提供标准统一的访问接口,提高用户在音视频方面的应用开发效率。只要底层音视频设备(如摄像头)兼容V4L2标准,则应用程序可以无缝接入该音视频设备。本文主要描述V4L2视频设备(摄像头)的应用。
1.1 V4L2特点
屏蔽底层差异,向上提供标准的用户访问接口,更换支持V4L2标准的物理音视频设备,原则上应用程序可以完全兼容。V4L2对用户提供的接口包括:
- 视频采集接口
- 视频输出接口
- 视频覆盖/预览接口
- 视频输出覆盖接口
- 编解码接口
1.2 V4L2设备
linux思想是一切皆文件。V4L2设备接入并且驱动被加载成功时在"/dev"
生成设备文件,名称为"videoX"
(/dev/videoX),X为设备设备序号;一般按接入的设备顺序排序,只有一个设备时为video0
。V4L2应用程序通过调用linux标准的设备文件系统接口,访问音视频设备。
2 V4L2设备访问接口
V4L2音视频设备可通过标准文件系统接口open/read/ioctl/close
访问,设备访问分为两大部分,分别是设备访问和视频流获取。关于V4L2的接口声明、宏定义、枚举类型可以在“include/uapi/linux/videodev2.h”头文件中查阅。
2.1设备访问
设备访问包括获取和设置设备信息,如设备驱动信息、设备属性、图像帧格式、控制图像捕获等。这些都通过ioctl
来实现访问,访问格式如下。
ioctl(fd, cmd, param); /* 文件描述符, 命令字, 参数信息 */
常用命令字:
VIDIOC_QUERYCAP /* 查询设备属性 */
VIDIOC_ENUM_FMT /* 查询设备支持的输出格式*/
VIDIOC_G_FMT /* 查询设备输出帧格式 */
VIDIOC_S_FMT /* 设置设备输出帧格式 */
VIDIOC_REQBUFS /* 申请帧缓存 */
VIDIOC_QUERYBUF /* 获取申请的帧缓存 */
VIDIOC_QBUF /* 将帧缓存加入视频流采集队列 */
VIDIOC_DQBUF /* 获取已采集视频流的缓存帧 */
VIDIOC_STREAMON /* 开启视频流采集 */
VIDIOC_STREAMOFF /* 关闭视频流采集 */
2.1.1 查询设备属性
函数原型:
int ioctl(int fd, int cmd, struct v4l2_capability *cap);
cmd
,命令字:VIDIOC_QUERYCAP
cap
,设备属性数据结构:
struct v4l2_capability{
__u8 driver[16]; /* driver驱动名字 */
__u8 card[32]; /* device设备名字 */
__u8 bus_info[32]; /* 设备在系统中的位置 */
__u32 version; /* 驱动版本号 */
__u32 capabilities; /* 设备支持的操作 */
__u32 device_caps; /* 特殊设备信息 */
__u32 reserved[4]; /* 保留字段 */
};
【1】capabilities
,视频设备支持的操作,以每一位表示,常用类型宏位于“/uapi/linux/videodev2.h”
定义。
/* Values for 'capabilities' field */
#define V4L2_CAP_VIDEO_CAPTURE 0x00000001 /* Is a video capture device */
#define V4L2_CAP_VIDEO_OUTPUT 0x00000002 /* Is a video output device */
#define V4L2_CAP_VIDEO_OVERLAY 0x00000004 /* Can do video overlay */
#define V4L2_CAP_VBI_CAPTURE 0x00000010 /* Is a raw VBI capture device */
#define V4L2_CAP_VBI_OUTPUT 0x00000020 /* Is a raw VBI output device */
#define V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 /* Is a sliced VBI capture device */
#define V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 /* Is a sliced VBI output device */
#define V4L2_CAP_RDS_CAPTURE 0x00000100 /* RDS data capture */
#define V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 /* Can do video output overlay */
#define V4L2_CAP_HW_FREQ_SEEK 0x00000400 /* Can do hardware frequency seek */
#define V4L2_CAP_RDS_OUTPUT 0x00000800 /* Is an RDS encoder */
查询设备属性伪代码:
struct v4l2_capability cap = {0};
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
2.1.2 查询设备输出格式
函数原型:
int ioctl(int fd, int cmd, struct v4l2_fmtdesc *fmt);
cmd
,命令字:VIDIOC_ENUM_FMT
fmt
,设备输出格式数据结构:
struct v4l2_fmtdesc {
__u32 index; /* 设置信息,查询格式序号 */
__u32 type; /* 设置信息,设备类型 */
__u32 flags;
__u8 description[32]; /* 返回信息,格式描述 */
__u32 pixelformat; /* 返回信息,格式 */
__u32 reserved[4]; /* 保留字段 */
};
【1】index
,查询序号,从0开始查询
【2】type
,设备类型,实际类型为enum v4l2_buf_type
,位于“/uapi/linux/videodev2.h”
定义;如果是camera设备,则设置为V4L2_BUF_TYPE_VIDEO_CAPTURE
【3】flags
,一般不用
循环获取设备输出格式伪代码:
struct v4l2_fmtdesc fmtdesc = {0};
fmtdesc.index = 0 ;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("the video device support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)
{
printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);
fmtdesc.index++;
}
2.1.3 设置帧输出格式
图像帧输出格式基本参数包括:
- 像素宽度
- 像素高度
- 数据类型
函数原型:
int ioctl(int fd, int cmd, struct v4l2_format *format);
cmd
,命令字:VIDIOC_S_FMT
format
,设置输出格式数据结构:
struct v4l2_format {
__u32 type;/* 设备类型 */
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE 设备(camera)使用 */
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
struct v4l2_sliced_vbi_formatsliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
__u8 raw_data[200]; /* 保留字段 */
} fmt;
};
【1】type
,设备类型,实际类型为enum v4l2_buf_type
,如果是camera设备,则设置为V4L2_BUF_TYPE_VIDEO_CAPTURE
【2】fmt
,一个共用体,不同设备的具体配置参数,以camera为例,其数据结构struct v4l2_pix_format
如下
struct v4l2_pix_format {
__u32 width; /* 像素宽度 */
__u32 height; /* 像素高度 */
__u32 pixelformat;/* 输出格式,根据camera支持的格式选择,JPG或YUV */
__u32 field; /* enum v4l2_field */
__u32 bytesperline; /* for padding, zero if unused */
__u32 sizeimage;
__u32 colorspace; /* enum v4l2_colorspace */
__u32 priv; /* private data, depends on pixelformat */
__u32 flags; /* format flags (V4L2_PIX_FMT_FLAG_*) */
__u32 ycbcr_enc; /* enum v4l2_ycbcr_encoding */
__u32 quantization; /* enum v4l2_quantization */
__u32 xfer_func; /* enum v4l2_xfer_func */
};
2.1.4 申请帧缓存
函数原型:
int ioctl(int fd, int cmd, struct v4l2_requestbuffers *reqbuf);
cmd
,命令字:VIDIOC_REQBUFS
reqbuf
,申请内存数据结构:
struct v4l2_requestbuffers {
__u32 count; /* 内存块数目 */
__u32 type; /* 设备类型 */
__u32 memory; /* 内存用途 */
__u32 reserved[2];/* 保留字段 */
};
【1】count
,申请内存块数目,至少为1
【2】type
,设备类型,实际类型为enum v4l2_buf_type
,如果是camera设备,则设置为V4L2_BUF_TYPE_VIDEO_CAPTURE
【3】memory
,内存用途,实际类型为enum v4l2_memory
,一般用作内存映射V4L2_MEMORY_MMAP
enum v4l2_memory {
V4L2_MEMORY_MMAP = 1, /* 内存映射 */
V4L2_MEMORY_USERPTR = 2, /* 用户指针 */
V4L2_MEMORY_OVERLAY = 3, /* 内存覆盖 */
V4L2_MEMORY_DMABUF = 4, /* DMA映射 */
};
申请帧缓存伪代码:
struct v4l2_requestbuffers req_buf = {0};
req_buf.count = 1;
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);
2.1.5 内核内存转换
通VIDIOC_S_FMT
命令字申请的内核态内存,还需转换为物理内存,用于映射到用户态,这样用户能直接从物理内存中获取视频流数据,提高效率。
函数原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
cmd
,命令字:VIDIOC_QUERYBUF
buf
,返回内存数据结构:
struct v4l2_buffer {
__u32 index; /* 内存块序号 */
__u32 type; /* 类型,与申请的类型一致 */
__u32 bytesused;/* 已使用的内存大小 */
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory; /* 内存用途,一般用内存映射 */
union {
__u32 offset; /* 内存块可用偏移地址,对于内存映射有效 */
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length; /* 内存块大小 */
__u32 config_store;
__u32 reserved;
};
应用伪代码:
struct v4l2_requestbuffers req_buf = {0};
struct v4l2_buffer buf = {0};
req_buf.count = 1;
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req_buf);
for(i=0; i<req_buf.count; i++)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);
pdevice->mmap_buf[i].size = buf.length;
pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE,MAP_PRIVATE, pdevice->fd, buf.m.offset);
if(MAP_FAILED == pdevice->mmap_buf[i].addr)
{
perror("mmap failed");
}
}
2.1.6 缓冲帧内存入队操作
该操作是将缓冲内存加入V4L2驱动的采集队列中,视频设备采集完成,数据存于该内存中。
函数原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
-
cmd
,命令字:VIDIOC_QBUF
-
buf
,帧缓存数据结构
应用代码:
int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index)
{
int ret = 0;
struct v4l2_buffer buf = {0};
/* 将内核缓存放入队列 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = index;
ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_QBUF\' failed");
return -1;
}
}
2.1.7 启动视频流采集
执行该命令,视频设备执行数据采集,采集完成,将视频流数据存于指定的内存空间。
函数原型:
int ioctl(int fd, int cmd, enum v4l2_buf_type *type);
-
cmd
,命令字:VIDIOC_STREAMON
-
type
,设备(缓存帧)类型,camera设备为V4L2_BUF_TYPE_VIDEO_CAPTURE
2.1.8 读取数据帧内存序号
实质上,执行“开启采集”命令成功后,视频流数据会存于预先申请的物理内存空间。然而,如果申请了多个数量的帧缓存,此时需知道视频流存于哪个帧缓存中,用户根据序号访问内存块。通过该命令可以获取存放视频流数据帧内存序号。
函数原型:
int ioctl(int fd, int cmd, struct v4l2_buffer *buf);
-
cmd
,命令字:VIDIOC_DQBUF
-
buf
,帧缓存数据结构
应用代码:
int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index)
{
int ret = 0;
int i;
struct v4l2_buffer buf = {0};
/* 从队列取出数据 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_DQBUF\' failed");
return -1;
}
if (buf.index > pdevice->mmap_buf_cnt)
{
printf("buf overflow[%d]\n", buf.index);
}
*out_buf_index = buf.index;
return 0;
}
注:
即使只申请一个帧缓存空间,也需要执行该命令,以确定数据是否正确采集完成,否则内存中数据是未知状态。
2.1.9 关闭视频流采集
结束视频流采集后,调用VIDIOC_STREAMOFF
关闭视频流采集。
函数原型:
int ioctl(int fd, int cmd, enum v4l2_buf_type *type);
-
cmd
,命令字:VIDIOC_STREAMOFF
-
type
,设备(缓存帧)类型,camera设备为V4L2_BUF_TYPE_VIDEO_CAPTURE
2.2 视频流读取
视频流读取有两种方式。
- 通过标准文件系统接口
read
读取 - 将V4L2设备内核态映射(mmap)到用户态,直接从物理内存获取视频流
- 用户指针模式,内存由用户分配,与内存映射类似,使用较少
通过read
函数读取视频流,需经过物理内存到内核态、内核态到用户态两个内存拷贝过程,效率比较低,一般用于静态图像的采集获取,一般比较少使用。内存映射是常用的方式, 省去两个拷贝过程,效率高。
3 V4L2应用开发流程
应用程序访问一个V4L2设备的的总体流程如下图。
关键步骤分析:
- 查询设备属性,包驱动信息、支持视频流格式,以方便后续设置视频输出属性
- 设置设备属性,根据获取的设备属性设置,主要是设置视频流输出格式,如制式、宽度、高度、编码方式
- 帧缓存申请,用于存放驱动采集的视频流,并映射到用户态
- 帧缓存加入采集队列,如果需循环采集视频流,每次获取视频流后都需执行将帧缓存加入采集队列
- 结束过程,包括关闭视频流采集、释放内存映射、关闭设备描述符
示例:
- 获取摄像头信息
- 获取视频流,保存为图片信息
#include <linux/videodev2.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/types.h>
#include <malloc.h>
#include <math.h>
#include <errno.h>
#include <assert.h>
struct _mmap_buf
{
void *addr;
int size;
};
struct _v4l2_video
{
int fd;
struct _mmap_buf *mmap_buf;
int mmap_buf_cnt;
};
int open_video_device(const char *device_name)
{
int fd = 0;
if (device_name == NULL)
{
return -1;
}
fd = open(device_name, O_RDWR); /* 以读写方式打开;以只读方式打开导致内存映射出错 */
if(fd < 0)
{
perror("open video device failed");
return -1;
};
return fd;
}
int close_video_device(struct _v4l2_video *pdevice)
{
int ret = 0;
int i;
for(i = 0; i<pdevice->mmap_buf_cnt; i++)
{
if (pdevice->mmap_buf[i].addr != 0x00)
{
ret = munmap(pdevice->mmap_buf[i].addr, pdevice->mmap_buf[i].size);
if(ret < 0)
{
perror("munmap failed");
continue;
}
}
}
if (pdevice->mmap_buf != 0x00)
{
free(pdevice->mmap_buf);
}
if (pdevice->fd != 0x00)
{
ret = close(pdevice->fd);
if(ret < 0)
{
perror("close fd failed");
}
}
return ret;
}
int query_video_device_cap(struct _v4l2_video *pdevice)
{
int ret = 0;
struct v4l2_capability cap = {0};
struct v4l2_fmtdesc fmtdesc = {0};
/* 查询摄像头信息 */
ret = ioctl(pdevice->fd, VIDIOC_QUERYCAP, &cap);
if(ret < 0)
{
perror("ioctl call \'VIDEO_QUERYCAP\' failed \n");
return -1;
}
printf("video driver name:%s\n", cap.driver);
printf("video device name:%s\n", cap.card);
printf("video bus information:%s\n", cap.bus_info);
printf("video driver version:%d\n", cap.version);
printf("video capabilities:%x\n", cap.capabilities);
/* 检查设备是否支持视频捕获 */
if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
{
printf("the video device support capture\n");
}
/* 检查设备是否支持数据流 */
if(!(cap.capabilities & V4L2_CAP_STREAMING))
{
printf("the video device support stream\n");
}
/* 查询设备支持的输出格式 */
fmtdesc.index = 0 ;
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("the video device support format:\n");
while(ioctl(pdevice->fd,VIDIOC_ENUM_FMT,&fmtdesc) != -1)
{
printf("%d.%s\n", fmtdesc.index+1, fmtdesc.description);
fmtdesc.index++;
}
return ret;
}
int set_video_device_par(struct _v4l2_video *pdevice)
{
int ret = 0;
struct v4l2_format format;
/* 设置帧输出格式 */
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
format.fmt.pix.width = 640; /* 像素宽度 */
format.fmt.pix.height = 480; /* 像素高度 */
format.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; /* 输出格式,前提是摄像头支持该格式,V4L2_PIX_FMT_YYUV */
format.fmt.pix.field = V4L2_FIELD_NONE;
ret = ioctl(pdevice->fd, VIDIOC_S_FMT, &format);
if (ret < 0)
{
perror("ioctl call \'VIDIOC_S_FMT\' failed");
}
return ret;
}
int set_video_device_mmap(struct _v4l2_video *pdevice)
{
int ret = 0;
int i = 0;
struct v4l2_requestbuffers req_buf = {0};
struct v4l2_buffer buf = {0};
/* 申请内核缓存区 */
pdevice->mmap_buf_cnt = 1;
req_buf.count = 1; /* 缓存数目 */
req_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(pdevice->fd, VIDIOC_REQBUFS, &req_buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_REQBUFS\' failed");
return -1;
}
pdevice->mmap_buf = malloc(req_buf.count * sizeof(struct _mmap_buf));
if(pdevice->mmap_buf == NULL)
{
perror("malloc memory failed");
return -1;
}
/* 将内核态内存映射到用户态 */
for(i=0; i<req_buf.count; i++)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
ret = ioctl(pdevice->fd, VIDIOC_QUERYBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_QUERYBUF\' failed");
return -1;
}
pdevice->mmap_buf[i].size = buf.length;
pdevice->mmap_buf[i].addr = (char *)mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, pdevice->fd, buf.m.offset);
if(MAP_FAILED == pdevice->mmap_buf[i].addr)
{
perror("mmap failed");
return -1;
}
}
return 0;
}
int set_video_device_stream_queue(struct _v4l2_video *pdevice, int index)
{
int ret = 0;
struct v4l2_buffer buf = {0};
/* 将内核缓存放入队列 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = index;
ret = ioctl(pdevice->fd, VIDIOC_QBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_QBUF\' failed");
return -1;
}
}
int set_video_device_stream_on(struct _v4l2_video *pdevice)
{
int ret = 0;
int i;
struct v4l2_buffer buf = {0};
enum v4l2_buf_type type;
/* 将内核缓存放入队列 */
for (i=0; i<pdevice->mmap_buf_cnt; i++)
{
set_video_device_stream_queue(pdevice, i);
if(ret < 0)
{
return -1;
}
}
/* 开启数据流 */
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(pdevice->fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_STREAMON\' failed");
return 0;
}
return 0;
}
int read_video_device_stream_frame(struct _v4l2_video *pdevice, int *out_buf_index)
{
int ret = 0;
int i;
struct v4l2_buffer buf = {0};
/* 从队列取出数据 */
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(pdevice->fd, VIDIOC_DQBUF, &buf);
if(ret < 0)
{
perror("ioctl call \'VIDIOC_DQBUF\' failed");
return -1;
}
if (buf.index > pdevice->mmap_buf_cnt)
{
printf("buf overflow[%d]\n", buf.index);
}
*out_buf_index = buf.index;
return 0;
}
int set_video_device_stream_off(struct _v4l2_video *pdevice)
{
int ret = 0;
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(pdevice->fd, VIDIOC_STREAMOFF, &type); /* 关闭数据流 */
if(ret < 0)
{
perror("ioctl call \'VIDIOC_STREAMOFF\' failed");
return -1;
}
return 0;
}
int main(int argc, int **argv)
{
FILE *fp = NULL;
int index = 0;
int ret = 0;
int i = 0;
char buf[12] = {0};
struct _v4l2_video video;
fd_set fds;
struct timeval tv;
if (argc < 2)
{
printf("parameter invalid\n");
return -1;
}
video.fd = open_video_device((const char*)argv[1]);
if (video.fd < 0)
{
return -1;
}
ret = query_video_device_cap(&video);
if (ret < 0)
{
goto __exit;
}
ret = set_video_device_par(&video);
if (ret < 0)
{
goto __exit;
}
ret = set_video_device_mmap(&video);
if (ret < 0)
{
goto __exit;
}
ret = set_video_device_stream_on(&video);
if (ret < 0)
{
goto __exit;
}
for (i=0; i<5; i++)
{/* 采集5张(帧)图片 */
FD_ZERO(&fds);
FD_SET(video.fd,&fds);
tv.tv_sec = 5; /* wait time */
tv.tv_usec = 0;
ret = select(video.fd + 1, &fds, NULL, NULL, &tv);
if(ret < 0)
{
perror("select error");
goto __exit;
}
else if(ret == 0)
{
printf("select timeout\n");
goto __exit;
}
ret = read_video_device_stream_frame(&video, &index);
if (ret < 0)
{
goto __exit;
}
sprintf(buf, "./image%d.jpg", i);
fp = fopen(buf, "wb"); /* 保存为图片文件 */
if(fp == NULL)
{
perror("open image file failed\n");
goto __exit;
}
printf("save %s \n", buf);
fwrite(video.mmap_buf[index].addr, video.mmap_buf[index].size, 1, fp);
fclose(fp);
set_video_device_stream_queue(&video, index);
usleep(1000);
}
__exit:
set_video_device_stream_off(&video);
close_video_device(&video);
return 0;
}
编译测试
- 系统:Ubuntu16
- 摄像头:笔记本自带摄像头
acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ gcc v4l2_base.c -o v4l2_base
acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ ./v4l2_base /dev/video0
video driver name:uvcvideo
video device name:Integrated Camera: Integrated C
video bus information:usb-0000:03:00.0-2.1
video driver version:266002
video capabilities:84200001
the video device support capture
the video device support format:
1.YUYV 4:2:2
2.Motion-JPEG
save ./image0.jpg
save ./image1.jpg
save ./image2.jpg
save ./image3.jpg
save ./image4.jpg
从执行结果来看,此笔记本自带的摄像头是USB接口的,支持YUV和JPEG格式输出。程序执行后在当前目录生成5张.jpg
格式的图片文件。
acuity@ubuntu:/mnt/hgfs/LSW/STHB/camera$ tree
.
├── image0.jpg
├── image1.jpg
├── image2.jpg
├── image3.jpg
├── image4.jpg
├── v4l2_base
└── v4l2_base.c