V4L2接口编程

官方文档连接:LinuxTV文档中心

参考链接:3-1.V4l2接口编程

基本使用为以下几个步骤:

  1. 打开设备

    1
    2
    3
    4
    5
    6
    int fd = open("/dev/video0", O_RDWR);
    if(fd < 0)
    {
    perror("打开设备失败");
    return -1;
    }
  2. 通过ioctl获取支持格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    struct v4l2_fmtdesc v4fmt;
    v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//指定类型
    v4fmt.index = 0;//指定获取格式的序列号(有的摄像头可能有多个可支持的格式)
    int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);//VIDIOC_ENUM_FMT命令,获得支持格式
    if(ret < 0)
    {
    perror("获取失败");
    }
    printf("index=%d\n", v4fmt.index);
    printf("flags=%d\n", v4fmt.flags);
    printf("description=%s\n", v4fmt.description);
    unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
    printf("pixelformat=%c%c%c%c\n", p[0],p[1],p[2],p[3]);
    printf("reserved=%d\n", v4fmt.reserved[0]);
  3. 通过ioctl配置摄像头采集格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
    vfmt.fmt.pix.width = 640;//设置宽(不能任意,需要硬件支持)
    vfmt.fmt.pix.height = 480;//设置高
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//设置视频采集格式
    int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);//VIDIOC_S_FMT:设置摄像头采集格式
    if(ret < 0)
    {
    perror("设置格式失败");
    }
    memset(&vfmt, 0, sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);//VIDIOC_G_FMT:获取摄像头采集格式
    if(ret < 0)
    {
    perror("获取格式失败");
    }
    //对比
    if(vfmt.fmt.pix.width == 640 && vfmt.fmt.pix.height == 480 &&
    vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
    {
    printf("设置成功\n");
    }else
    {
    printf("设置失败\n");
    }
  4. 通过ioctl申请内核缓冲区队列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = 4; //申请4个缓冲区
    reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);//VIDIOC_REQBUFS:申请内核缓冲区队列
    if(ret < 0)
    {
    perror("申请队列空间失败");
    }
  5. 把内核的缓冲区队列映射到用户空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    unsigned char *mptr[4];//保存映射后用户空间的首地址
    unsigned int size[4];//记录映射的长度,便于后期释放
    struct v4l2_buffer mapbuffer;
    //初始化type, index
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for(int i=0; i<4; i++)
    {
    mapbuffer.index = i;
    ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
    if(ret < 0)
    {
    perror("查询内核空间队列失败");
    }
    mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);//mmap映射
    size[i]=mapbuffer.length;//记录映射长度

    ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);//通知使用完毕--‘放回去’
    if(ret < 0)
    {
    perror("放回失败");
    }
    }
  6. 准备开始采集

    1
    2
    3
    4
    5
    6
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);//开始采集写数据到队列中
    if(ret < 0)
    {
    perror("开启失败");
    }
  7. 采集数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //从队列中提取一帧数据
    struct v4l2_buffer readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);//告诉内核要某一个数据,内核不可以修改
    if(ret < 0)
    {
    perror("提取数据失败");
    }

    FILE *file=fopen("my.jpg", "w+");
    //mptr[readbuffer.index]
    fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
    fclose(file);

    //通知内核已经使用完毕
    ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);//告诉内核已经使用完毕
    if(ret < 0)
    {
    perror("放回队列失败");
    }
  8. 停止采集

    1
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);//停止采集-不在向队列中写数据
  9. 释放内存

    1
    2
    for(int i=0; i<4; i)
    munmap(mptr[i], size[i]);
  10. 关闭设备

    1
    close(fd);

综合例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>

int main(void)
{
//1.打开设备
int fd = open("/dev/video0", O_RDWR);
if(fd < 0)
{
perror("打开设备失败");
return -1;
}

//3.设置采集格式
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//摄像头采集
vfmt.fmt.pix.width = 640;//设置宽(不能任意)
vfmt.fmt.pix.height = 480;//设置高
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//设置视频采集格式
int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
if(ret < 0)
{
perror("设置格式失败");
}

//4.申请内核空间
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //申请4个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP ;//映射方式
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
if(ret < 0)
{
perror("申请队列空间失败");
}

//5.映射
unsigned char *mptr[4];//保存映射后用户空间的首地址
unsigned int size[4];
struct v4l2_buffer mapbuffer;
//初始化type, index
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for(int i=0; i<4; i++)
{
mapbuffer.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);//从内核空间中查询一个空间做映射
if(ret < 0)
{
perror("查询内核空间队列失败");
}
mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, mapbuffer.m.offset);
size[i]=mapbuffer.length;

//通知使用完毕--‘放回去’
ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
if(ret < 0)
{
perror("放回失败");
}
}

//6.开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0)
{
perror("开启失败");
}

//7.采集数据
//从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
if(ret < 0)
{
perror("提取数据失败");
}

FILE *file=fopen("my.jpg", "w+");
//mptr[readbuffer.index]
fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
fclose(file);

//通知内核已经使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if(ret < 0)
{
perror("放回队列失败");
}

//8.停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);

//9.释放内存
for(int i=0; i<4; i)
munmap(mptr[i], size[i]);

//10.关闭设备
close(fd);
return 0;
}