V4L2采集视频

V4L2 API:

Part I - Video for Linux API

参考:

https://github.com/Jin992/robo_vision_mavalink/blob/c10d67cafcd65ac9efbf7bd84b2b117e00764fbf/Video/VideoCapture.cpp

采集步骤:

打开设备->获取和设置设备属性->设置输出输入->(申请缓冲及缓冲管理)->获取数据->关闭设备

打开设备

fd = open(v4l_device, O_RDWR, 0)

获取设备信息

1
2
3
4
5
6
7
struct v4l2_capability cam_cap;  
ret = ioctl(cap_fd, VIDIOC_QUERYCAP, &cam_cap);
printf("Driver Name: %s\nCard Name: %s\nBus info: %s\nDriver Version: %u.%u.%u\n",
cam_cap.driver,
cam_cap.card,
cam_cap.bus_info,
(cam_cap.version >> 16) & 0XFF, (cam_cap.version >> 8) & 0XFF, cam_cap.version & 0XFF);
1
2
3
4
5
6
7
8
struct v4l2_capability {
__u8 driver[16]; /* i.e. "bttv" */
__u8 card[32]; /* i.e. "Hauppauge WinTV" */
__u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
__u32 version; /* should use KERNEL_VERSION() */
__u32 capabilities; /* Device capabilities */
__u32 reserved[4];
};

获取图像信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct v4l2_format cam_format;
cam_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(dev_fd[dev_id], VIDIOC_G_FMT, &cam_format)
printf(" width : %d height : %d\n\
pixelformat : %d\n\
field : %d\n\
bytesperline : %d\n\
sizeimage : %d\n\
colorspace : %d\n\
priv : %d\n",\
cam_format.fmt.pix.width,\
cam_format.fmt.pix.height,\
cam_format.fmt.pix.pixelformat,\
cam_format.fmt.pix.field, \
cam_format.fmt.pix.bytesperline, \
cam_format.fmt.pix.sizeimage, \
cam_format.fmt.pix.colorspace, \
cam_format.fmt.pix.priv);
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
struct v4l2_fmtdesc cam_fmtdesc; 
struct v4l2_frmsizeenum fsize;

cam_fmtdesc.index = 0;
cam_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &cam_fmtdesc) != -1) {
printf(" %d. %4s(%s)\n", cam_fmtdesc.index + 1, (char *)&cam_fmtdesc.pixelformat, cam_fmtdesc.description);
cam_fmtdesc.index++;

// enumerate frame sizes
fsize.index = 0;
fsize.pixel_format = cam_fmtdesc.pixelformat;
while ((ret = ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize)) == 0)
{
fsize.index++;
if (fsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
{
printf(" discrete: %ux%u: ", fsize.discrete.width, fsize.discrete.height);
// enumerate frame rates
struct v4l2_frmivalenum fival;
fival.index = 0;
fival.pixel_format = cam_fmtdesc.pixelformat;
fival.width = fsize.discrete.width;
fival.height = fsize.discrete.height;
sizes_buf[fsize.index-1][0] = fsize.discrete.width;
sizes_buf[fsize.index-1][1] = fsize.discrete.height;
while ((ret = ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival)) == 0)
{
fival.index++;
if (fival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
{
printf("%u/%u ", fival.discrete.numerator, fival.discrete.denominator);
}
else
printf("I only handle discrete frame intervals...\n");
}
printf("\n");
}
else if (fsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)
{
printf(" continuous: %ux%u to %ux%u\n",
fsize.stepwise.min_width, fsize.stepwise.min_height,
fsize.stepwise.max_width, fsize.stepwise.max_height);
}
else if (fsize.type == V4L2_FRMSIZE_TYPE_STEPWISE)
{
printf(" stepwise: %ux%u to %ux%u step %ux%u\n",
fsize.stepwise.min_width, fsize.stepwise.min_height,
fsize.stepwise.max_width, fsize.stepwise.max_height,
fsize.stepwise.step_width, fsize.stepwise.step_height);
}
else
{
printf(" fsize.type not supported: %d\n", fsize.type);
}
}
}

查询视频设备支持的功能

我们可以根据支持的功能实现相应的接口

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
static void enumerate_menu(int fd, unsigned int id, struct v4l2_queryctrl *queryctrl)
{
printf(" Menu items:\n");
struct v4l2_querymenu querymenu;
memset(&querymenu, 0, sizeof(querymenu));
querymenu.id = id;

for (querymenu.index = queryctrl->minimum;
querymenu.index <= queryctrl->maximum;
querymenu.index++) {
if (0 == ioctl(fd, VIDIOC_QUERYMENU, &querymenu)) {
printf("\t%s\n", querymenu.name);
}
}
}
void enum_video_ctrls(int fd)
{
struct v4l2_queryctrl qctrl;

printf("=======================\n");
printf("Discovering controls:\n");

memset(&qctrl, 0, sizeof(qctrl));
qctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL; //V4L2_CTRL_CLASS_USER V4L2_CTRL_CLASS_MPEG
while (0 == ioctl(fd, VIDIOC_QUERYCTRL, &qctrl))
{
/*if (V4L2_CTRL_ID2CLASS(qctrl.id) != V4L2_CTRL_CLASS_MPEG)
break;*/

if (!(qctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
printf("---\n[Controls] %s\n", qctrl.name);

printf(" id: %d\n", qctrl.id);
switch (qctrl.type)
{
case V4L2_CTRL_TYPE_INTEGER:
printf(" type: INTEGER\n");
break;
case V4L2_CTRL_TYPE_BOOLEAN:
printf(" type: BOOLEAN\n");
break;
case V4L2_CTRL_TYPE_MENU:
printf(" type: MENU\n");
enumerate_menu(fd, qctrl.id, &qctrl);
break;
case V4L2_CTRL_TYPE_BUTTON:
printf(" type: BUTTON\n");
break;
}
printf(" minimum: %d\n", qctrl.minimum);
printf(" maximum: %d\n", qctrl.maximum);
printf(" step: %d\n", qctrl.step);
printf(" default_value: %d\n", qctrl.default_value);
printf(" flags: %d\n\n", qctrl.flags);
}
qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
}
}

设置配置

1
2
3
4
5
6
7
8
9
10
11
12
13
/*设置帧率fps*/
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = fps;
if(dev_id == LOCAL_CAMERA){
parm.parm.capture.capturemode = mode;
}
if(ioctl(dev_fd[dev_id], VIDIOC_S_PARM, &parm) < 0) {
printf("set frame rate failed\n");
close(dev_fd[dev_id]);
dev_fd[dev_id] = -1;
return -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*设置帧格式,长,宽,采样类型等*/
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = dev_frame_width;
fmt.fmt.pix.height = dev_frame_height;
fmt.fmt.pix.bytesperline = dev_frame_width;
fmt.fmt.pix.priv = 0;
fmt.fmt.pix.sizeimage = 0;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; // V4L2_PIX_FMT_YUV420 V4L2_PIX_FMT_MJPEG V4L2_PIX_FMT_H264
if(ioctl(dev_fd[dev_id], VIDIOC_S_FMT, &fmt) < 0) {
printf("set format failed\n");
close(dev_fd[dev_id]);
dev_fd[dev_id] = -1;
return -1;
}

设置输入输出源 video Inputs and Outputs

1
2
3
4
输入输出视频源切换
VIDIOC_G_INPUT
VIDIOC_S_INPUT
ioctl(fd, VIDIOC_S_INPUT, &g_input/*&input.index*/)

请求BUF

1
2
3
4
5
6
7
8
9
10
struct v4l2_requestbuffers req = {0};
req.count = TEST_BUFFER_NUM;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if(ioctl(dev_fd[dev_id], VIDIOC_REQBUFS, &req) < 0) {
printf("v4l_capture_setup: VIDIOC_REQBUFS failed\n");
close(dev_fd[dev_id]);
dev_fd[dev_id] = -1;
return -1;
}

三种交换数据的方法,

  • 直接 read/write、
  • 内存映射(V4L2_MEMORY_MMAP)
  • 用户指针(V4L2_MEMORY_USERPTR)

内存映射(V4L2_MEMORY_MMAP):

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
struct capture_testbuffer {
unsigned char *start;
size_t offset;
unsigned int length;
unsigned int bytesused;
};

int v4l_start_capturing(int dev_id) {
unsigned int i;
struct v4l2_buffer buf;
enum v4l2_buf_type type;

for(i = 0; i < TEST_BUFFER_NUM; i++) {
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;

if(ioctl(dev_fd[dev_id], VIDIOC_QUERYBUF, &buf) < 0) {
printf("VIDIOC_QUERYBUF error\n");
return -1;
}

cap_buffers[dev_id][i].length = buf.length;
cap_buffers[dev_id][i].offset = (size_t)buf.m.offset;
cap_buffers[dev_id][i].start = mmap(NULL, cap_buffers[dev_id][i].length,
PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd[dev_id], cap_buffers[dev_id][i].offset);

if(cap_buffers[dev_id][i].start == MAP_FAILED) {
printf("v4l_start_capturing mmap error\n");
return -1;
}
}

for(i = 0; i < TEST_BUFFER_NUM; i++) {
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.m.offset = cap_buffers[dev_id][i].offset;

if(ioctl(dev_fd[dev_id], VIDIOC_QBUF, &buf) < 0) {
printf("VIDIOC_QBUF error\n");
return -1;
}
}

启动数据流

VIDIOC_STREAMON

1
2
3
4
5
6
7
8
9
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(dev_fd[dev_id], VIDIOC_STREAMON, &type) < 0) {
printf("VIDIOC_STREAMON error\n");
return -1;
}

printf("v4l_start_capturing v4l2 ok\n");
return 0;
}

获取数据:

VIDIOC_QBUF// 把帧放入队列
VIDIOC_DQBUF// 从队列中取出帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct v4l2_buffer buf;
memset(buf, 0, sizeof(struct v4l2_buffer));
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->memory = V4L2_MEMORY_MMAP;

if(ioctl(cap_fd, VIDIOC_DQBUF, buf) < 0) {
printf("v4l_get_capture_data failed\n");
return -1;
}

*frame = cap_buffers[dev_id][buf.index].start;
*size = buf.bytesused;

if(ioctl(cap_fd, VIDIOC_QBUF, buf) < 0)

停止数据流

VIDIOC_STREAMOFF

1
2
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(dev_fd, VIDIOC_STREAMOFF, &type);

取消映射

1
2
3
4
5
6
7
for(i = 0; i < TEST_BUFFER_NUM; i++) {
if(cap_buffers[dev_id][i].start > 0)
munmap(cap_buffers[dev_id][i].start, cap_buffers[dev_id][i].length);

cap_buffers[dev_id][i].start = NULL;
cap_buffers[dev_id][i].length = 0;
}

关闭设备

close(dev_fd);