简介
Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口。
工作流程
打开设备-> 初始化(检查和设置设备属性-> 设置帧格式-> 设置入输出方法)-> 处理帧数据(循环获取处理数据)-> 关闭设备。
应用程序与驱动进行信息交互通过ioctl函数实现。应用程序和设备有三种交换数据的方法,直接 read/write、内存映射(memory mapping)和用户指针。这里只讨论内存映射(memory mapping)。
设备信息初始化
初始化设备输入方式(内存映射)
设置摄像头输入格式YUV422
设置帧大小320*240
设置摄像头捕获方式预览(OPS_PREVIEW)
1. 打开设备
在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备。用读写和非阻塞模式打开摄像头设备。
1
2
3
4
5#include
int open(const char *device_name, int flags);
#include
int close(int fd);
例:
1
2static int fd= -1;
fd = open("/dev/video1", O_RDWR | O_NONBLOCK, 0);
2. 初始化设备
V4L2常用的命令标志符:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16VIDIOC_QUERYCAP /* 获取设备支持的操作 */
VIDIOC_G_FMT /* 读取当前视频捕获格式 */
VIDIOC_S_FMT /* 设置视频捕获格式 */
VIDIOC_REQBUFS /* 向驱动提出申请内存请求 */
VIDIOC_QUERYBUF /* 获取驱动申请到的内存信息 */
VIDIOC_QBUF /* 将空闲的内存加入可捕获视频的队列 */
VIDIOC_DQBUF /* 将已经捕获好视频的内存拉出已捕获视频的队列 */
VIDIOC_STREAMON /* 打开视频流 */
VIDIOC_STREAMOFF /* 关闭视频流 */
VIDIOC_QUERYCTRL /* 查询驱动是否支持该命令 */
VIDIOC_G_CTRL /* 获取当前命令值 */
VIDIOC_S_CTRL /* 设置新的命令值 */
VIDIOC_G_TUNER /* 获取调谐器信息 */
VIDIOC_S_TUNER /* 设置调谐器信息 */
VIDIOC_G_FREQUENCY /* 获取调谐器频率 */
VIDIOC_S_FREQUENCY /* 设置调谐器频率 */
V4L2常用数据结构:
1
2
3
4
5
6
7
8struct v4l2_requestbuffers //申请帧缓冲, 对应命令VIDIOC_REQBUFS
struct v4l2_capability //视频设备的功能, 对应命令VIDIOC_QUERYCAP
struct v4l2_input //视频输入信息, 对应命令VIDIOC_ENUMINPUT
struct v4l2_standard //视频的制式, 比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format //帧的格式, 对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
struct v4l2_buffer //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_crop //视频信号矩形边框
v4l2_std_id //视频制式
2.1 获取设备支持的操作和相关信息(VIDIOC_QUERYCAP)
可以使用该命令来获取当前设备支持的操作相关信息,进一步判断该设备是否支持V4L2规范。
1
2
3
4
5
6
7
8
9struct v4l2_capability
{
u8 driver[16]; // 驱动名字
u8 card[32]; // 设备名字
u8 bus_info[32]; // 设备在系统中的位置
u32 version; // 驱动版本号
u32 capabilities; // 设备支持的操作
u32 reserved[4]; // 保留字段
};
1
2struct v4l2_capability cap;
ioctl(fd, VIDIOC_QUERYCAP, &cap);
2.2 获取缓冲帧的地址和长度(VIDIOC_CROPCAP)
根据获取到信息(帧地址和长度)映射到内存地址。
1
2
3
4
5
6
7struct v4l2_cropcap
{
enum v4l2_buf_type type; // 应用程序设置
struct v4l2_rect bounds; // 最大边界
struct v4l2_rect defrect; // 默认值
struct v4l2_fract pixelaspect;
};
1
2
3struct v4l2_cropcap cropcap
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_CROPCAP, &cropcap);
2.3 图像的缩放(VIDIOC_S_CROP)
1
2
3
4
5struct v4l2_crop
{
enum v4l2_buf_type type; // 应用程序设置
struct v4l2_rect c;
}
1
2
3
4struct v4l2_crop crop;
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect;/* reset to default */
ioctl(fd, VIDIOC_S_CROP, &crop);
3. 设置图像信息
3.1 选则当前输入一个设备节点对应多个视频源中的一个(VIDIOC_S_INPUT)
1
2int idx = 0;
ioctl(fd, VIDIOC_S_INPUT, &idx);
3.2 设置帧格式(VIDIOC_S_FMT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24struct v4l2_pix_format
{
u32 width; // 帧宽,单位像素
u32 height; // 帧高,单位像素
u32 pixelformat; // 帧格式
enum v4l2_field field;
u32 bytesperline;
u32 sizeimage;
enum v4l2_colorspace colorspace;
u32 priv;
};
struct v4l2_format
{
enum v4l2_buf_type type; // 帧类型,应用程序设置
union fmt
{
struct v4l2_pix_format pix; // 视频设备使用
struct v4l2_window win;
struct v4l2_vbi_format vbi;
struct v4l2_sliced_vbi_format sliced;
u8 raw_data[200];
};
};
1
2
3
4
5
6
7
8
9struct v4l2_format fmt;
fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width= camera_inf->param.width;
fmt.fmt.pix.height= camera_inf->param.height;
fmt.fmt.pix.pixelformat= camera_inf->fmt.fourcc;
fmt.fmt.pix.field= V4L2_FIELD_ANY;/*V4L2_FIELD_NONE;*/
ioctl(fd, VIDIOC_S_FMT, &fmt);
3.3 获取帧信息(VIDIOC_G_FMT)
1ioctl(fd, VIDIOC_G_FMT, &fmt);
4. 建立内存映射
4.1 向设备申请缓冲区(VIDIOC_REQBUFS)
1
2
3
4
5
6
7
8
9
10
11
12
13struct v4l2_requestbuffers req;
int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
struct v4l2_requestbuffers
{
__u32 count; // 缓冲区内缓冲帧的数目
enum v4l2_buf_type type; // 缓冲帧数据格式
enum v4l2_memory memory; // 区别是内存映射还是用户指针方式
__u32 reserved[2];
};
enum v4l2_memoy {V4L2_MEMORY_MMAP, V4L2_MEMORY_USERPTR};
例: 申请一个拥有四2个缓冲帧的缓冲区
1
2
3
4
5
6
7struct v4l2_requestbuffers req;
req.count= 2;
req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory= V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_REQBUFS, &req);
4.2 获取缓冲帧的地址和长度,并将申请到的缓冲帧映射到应用程序,buffers指针(VIDIOC_QUERYBUF)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24struct buffer *buffers;
struct v4l2_buffer
{
__u32 index; //buffer 序号
enum v4l2_buf_type type; //buffer 类型
__u32 byteused; //buffer 中已使用的字节数
__u32 flags; // 区分是MMAP 还是USERPTR
enum v4l2_field field;
struct timeval timestamp; // 获取第一个字节时的系统时间
struct v4l2_timecode timecode;
__u32 sequence; // 队列中的序号
enum v4l2_memory memory; //IO 方式,被应用程序设置
union m
{
__u32 offset; // 缓冲帧地址,只对MMAP 有效
__u32 userptr;
};
__u32 length; // 缓冲帧长度
__u32 input;
__u32 reserved;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct buffer
{
void *start;
size_t length;
};
struct buffer *buffers= NULL;
buffers = malloc(req.count * sizeof(*buffers));
for (n_buffers=0; n_buffers
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory= V4L2_MEMORY_MMAP;
buf.index= n_buffers;
ioctl(fd, VIDIOC_QUERYBUF, &buf);
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
}
5. 获帧图像数据
当我们把缓冲区设置好之后,就可以开始获取数据了。可以通过VIDIOC_STREAMON / VIDIOC_STREAMOFF启动或停止数据流。
5.1 把帧放入队列(VIDIOC_QBUF)
1
2
3
4
5
6
7
8
9
10
11
12
13enum v4l2_buf_type type;
for (i=0; 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;
ioctl(fd, VIDIOC_QBUF, &buf);
}
5.2 启动获取帧数据流(VIDIOC_STREAMON)
1
2type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMON, &type);
5.3 读取帧数据并处理(VIDIOC_DQBUF)
首先读取帧队列并处理,处理后再次把帧缓存加入队列,待下一次读取帧队列数据。
1
2
3
4
5
6
7
8struct v4l2_buffer buf;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ioctl(fd, VIDIOC_DQBUF, &buf);
camera_inf->buf_vaddr = (void *)buffers[buf.index].start;
camera_ctl->ops.priview_picture(camera_inf);//调用回调函数处理帧数据
6. 关闭设备
6.1 关闭视频流(VIDIOC_STREAMOFF)
关闭视频流,munmap解除参数start所指向的内存起始地存起始地址。
1
2
3
4
5
6
7
8
9
10
11enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(fd, VIDIOC_STREAMOFF, &type);
for (i=0; i
{
munmap(buffers[i].start, buffers[i].length);
}
close(fd);