V4L2是Video for Linux2的简称,是Linux操作系统中支持实时视频采集的设备驱动和一系列API的集合。V4L2驱动会在文件系统中创建/dev/video*设备节点供视频采集操作使用。
一、V4L2的用法
首先通过open()函数打开设备节点获取文件描述符,使用ioctl()函数查询设备状态、查询/设置参数、获取视频等。
二、常见命令如下
- VIDIOC_REQBUFS:分配内存
- VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
- VIDIOC_QUERYCAP:查询支持的能力
- VIDIOC_ENUM_FMT:获取支持的视频格式
- VIDIOC_S_FMT:设置频捕获格式
- VIDIOC_G_FMT:读取频捕获格式
- VIDIOC_TRY_FMT:验证当前显示格式
- VIDIOC_CROPCAP:查询驱动的裁剪能力
- VIDIOC_S_CROP:设置视频信号的边框
- VIDIOC_G_CROP:读取视频信号的边框
- VIDIOC_QBUF:把数据放回缓存队列
- VIDIOC_DQBUF:把数据从缓存中读取出来
- VIDIOC_STREAMON:开始视频显示
- VIDIOC_STREAMOFF:结束视频显示
- VIDIOC_QUERYSTD:当前视频设备支持的标准,例如PAL或NTSC
三、示例:抓取一帧图像
c
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
int video_camera_capture_test(char *dev_name)
{
int fd = 0;
void *start = NULL;
FILE *mjpeg_fp = NULL;
enum v4l2_buf_type type;
struct v4l2_buffer buf;
struct v4l2_capability cap;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
fd = open(dev_name, O_RDWR|O_NONBLOCK);
if (fd < 0) {
printf("open dev[%s] failed\n", dev_name);
return -1;
}
if (ioctl(fd, VIDIOC_QUERYCAP, &cap)<0) {
printf("Not a v4lv2 driver.\n");
return -1;
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
printf("%s is not a video capture device\n", VIDEO_DEV);
return -1;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
printf("%s does not support streaming i/o\n", VIDEO_DEV);
return -1;
}
printf("Driver is %s, version is %i\n", cap.driver, cap.version);
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
fmt.fmt.pix.width = 1080;
fmt.fmt.pix.height = 720;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if(ioctl(fd, VIDIOC_S_FMT, &fmt) != 0) {
printf("VIDIOC_S_FMT failed: %s. Read-only driver maybe ?\n",
strerror(errno));
} else {
printf("VIDIOC_S_FMT success\n");
}
memset(&req,0,sizeof(req));
req.count = 1;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req)<0) {
printf("Error requesting info on mmap'd buffers: %s\n",
strerror(errno));
return -1;
} else {
printf("VIDIOC_REQBUFS success\n");
}
memset(&buf,0,sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf)<0){
printf("Could not VIDIOC_QUERYBUF : %s\n",strerror(errno));
return -1;
}
start = mmap(NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
printf("buf length:%d\n", buf.length);
if (start==NULL){
printf("Could not mmap: %s\n",strerror(errno));
return -1;
}
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = 0;
if (-1 == ioctl (fd, VIDIOC_QBUF, &buf)){
printf("VIDIOC_QBUF failed: %s\n", strerror(errno));
return -1;
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)){
printf("VIDIOC_STREAMON failed: %s\n",strerror(errno));
return -1;
}
memset(&buf,0,sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
int times = 10;
while (times--) {
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
switch (errno) {
case EAGAIN:
usleep(20000);
break;
default:
printf("VIDIOC_DQBUF failed: %s\n",strerror(errno));
}
} else {
printf("get picture\n");
mjpeg_fp = fopen("./output.jpeg", "w");
fwrite(start, buf.bytesused, 1, mjpeg_fp);
fclose(mjpeg_fp);
break;
}
}
return 0;
}
int main(int argc, char *argv[])
{
video_camera_capture_test("/dev/video0");
return 0;
}
测试结果:
bash
Driver is uvcvideo, version is 267201
VIDIOC_S_FMT success
VIDIOC_REQBUFS success
buf length:1572864
get picture