V4L2 视频采集
文章目录
本文用于记录学习 V4L2 采集视频,写得比较难粗略。完整代码在文末给出。
一、 V4L2简介
Video for Linux two(Video4Linux2) 简称 V4L2,是 V4L 的改进版。V4L2 是 linux 操作系统下一套用于采集图片、视频和音频数据的通用API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。V4L2 像一个优秀的快递员,将视频采集设备的图像数据安全、高效的传递给不同需求的用户。
在 Linux 中,一切皆文件,所有外设都被看成一种特殊的文件,称为“设备文件”。视频设备也不例外,也可以可以看成是设备文件,可以像访问普通文件一样对其进行读写。V4L2驱动的摄像头的设备文件一般是/dev/videoX(X为任意数字,要与自己的设备相对应)。
V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;用户指针使用较少,如有兴趣可自行研究。由于内存映射方式的应用更广泛,所以本文重点讨论内存映射方式的视频采集。
二、 V4L2 视频采集流程
使用V4L2进行视频采集,一般分为5个步骤:
(1)打开设备,进行初始化参数设置,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;
(2)申请图像帧缓冲,并进行内存映射,将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取、处理图像数据;
(3)将帧缓冲进行入队操作,启动视频采集;
(4)驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;
(5)释放资源,停止采集工作。
三、V4L2 程序实例
3.1 头文件和宏定义
#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/videodev2.h>
#define VIDEO_DEVICE "/dev/video0" // 摄像头设备文件路径
#define OUTPUT_FOLDER "./captured_frames/" // 图像保存文件夹路径
#define NUM_FRAMES 100 // 要捕获的帧数
3.2 打开摄像头设备
查询设备属性需要使用struct v4l2_capability结构体,该结构体描述了视频采集设备的driver信息。
struct v4l2_capability
{
__u8 driver[16]; // 驱动名字
__u8 card[32]; // 设备名字
__u8 bus_info[32]; // 设备在系统中的位置
__u32 version; // 驱动版本号
__u32 capabilities; // 设备支持的操作
__u32 reserved[4]; // 保留字段
};
// 打开摄像头设备
fd = open(VIDEO_DEVICE, O_RDWR);
if (fd == -1)
{
perror("Unable to open device");
return -1;
}
// 查询摄像头能力
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
{
perror("Unable to query device");
close(fd);
return -1;
}
3.3 设置图像帧格式
设置图像格式需要用到struct v4l2_format结构体,该结构体描述每帧图像的具体格式,包括帧类型以及图像的长、宽等信息。
// 设置图像格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640; // 设置图像宽度
fmt.fmt.pix.height = 480; // 设置图像高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置图像格式为MJPEG
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
{
perror("Unable to set format");
close(fd);
return -1;
}
这里我们可以先用如下方式查询摄像头支持的格式,代码运行后会输出所以摄像头支持的格式。
//1.打开设备
int fd = open("/dev/video0", O_RDWR);
if(fd < 0)
{
perror("打开设备失败");
return -1;
}
//2.获取摄像头支持的格式ioctl(文件描述符, 命令, 与命令对应的结构体)
struct v4l2_fmtdesc v4fmt;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int i=0;
while(1)
{
v4fmt.index = i++;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
if(ret < 0)
{
perror("获取失败");
break;
}
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]);
}
//9.关闭设备
close(fd);
return 0;
3.4 申请图像帧缓存,映射到用户空间
// 请求缓冲区
req.count = 4; // 请求4个缓冲区(可以根据需要调整)
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1)
{
perror("Unable to request buffers");
close(fd);
return -1;
}
// 映射缓冲区
for (int i = 0; i < req.count; ++i)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1)
{
perror("Unable to query buffer");
close(fd);
return -1;
}
buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
// 启用缓冲区
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
perror("Unable to queue buffer");
close(fd);
return -1;
}
}
3.5 开始捕获和停止捕获
// 启动捕获
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) == -1)
{
perror("Unable to start capture");
close(fd);
return -1;
}
// 创建输出文件夹
if (mkdir(OUTPUT_FOLDER, 0755) == -1)
{
perror("Unable to create output folder");
close(fd);
return -1;
}
while (frame_count < NUM_FRAMES)
{
// 从缓冲区中获取图像数据
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1)
{
perror("Unable to dequeue buffer");
continue;
}
// 生成文件名
snprintf(filename, sizeof(filename), "%sframe%d.jpg", OUTPUT_FOLDER, frame_count);
// 创建并保存图像文件
output = fopen(filename, "wb");
if (!output)
{
perror("Unable to open output file");
}
else
{
fwrite(buffer, buf.bytesused, 1, output);
fclose(output);
printf("Frame %d captured and saved to %s\n", frame_count, filename);
frame_count++;
}
// 将缓冲区重新排队以继续捕获
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
perror("Unable to queue buffer");
break;
}
}
// 停止捕获
if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1)
{
perror("Unable to stop capture");
}
// 关闭文件和设备
for (int i = 0; i < req.count; ++i)
{
munmap(buffer, buf.length);
}
close(fd);
完整代码如下,代码实现了利用 V4L2 采集图像并创建一个文件夹将图像储存在其中。
#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/videodev2.h>
#define VIDEO_DEVICE "/dev/video0" // 摄像头设备文件路径
#define OUTPUT_FOLDER "./captured_frames/" // 图像保存文件夹路径
#define NUM_FRAMES 100 // 要捕获的帧数
int main()
{
int fd;
struct v4l2_capability cap;
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
void *buffer;
char filename[256];
FILE *output;
int frame_count = 0;
// 打开摄像头设备
fd = open(VIDEO_DEVICE, O_RDWR);
if (fd == -1)
{
perror("Unable to open device");
return -1;
}
// 查询摄像头能力
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
{
perror("Unable to query device");
close(fd);
return -1;
}
// 设置图像格式
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 640; // 设置图像宽度
fmt.fmt.pix.height = 480; // 设置图像高度
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; // 设置图像格式为MJPEG
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
{
perror("Unable to set format");
close(fd);
return -1;
}
// 请求缓冲区
req.count = 4; // 请求4个缓冲区(可以根据需要调整)
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1)
{
perror("Unable to request buffers");
close(fd);
return -1;
}
// 映射缓冲区
for (int i = 0; i < req.count; ++i)
{
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1)
{
perror("Unable to query buffer");
close(fd);
return -1;
}
buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
// 启用缓冲区
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
perror("Unable to queue buffer");
close(fd);
return -1;
}
}
// 启动捕获
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_STREAMON, &type) == -1)
{
perror("Unable to start capture");
close(fd);
return -1;
}
// 创建输出文件夹
if (mkdir(OUTPUT_FOLDER, 0755) == -1)
{
perror("Unable to create output folder");
close(fd);
return -1;
}
while (frame_count < NUM_FRAMES)
{
// 从缓冲区中获取图像数据
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1)
{
perror("Unable to dequeue buffer");
continue;
}
// 生成文件名
snprintf(filename, sizeof(filename), "%sframe%d.jpg", OUTPUT_FOLDER, frame_count);
// 创建并保存图像文件
output = fopen(filename, "wb");
if (!output)
{
perror("Unable to open output file");
}
else
{
fwrite(buffer, buf.bytesused, 1, output);
fclose(output);
printf("Frame %d captured and saved to %s\n", frame_count, filename);
frame_count++;
}
// 将缓冲区重新排队以继续捕获
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1)
{
perror("Unable to queue buffer");
break;
}
}
// 停止捕获
if (ioctl(fd, VIDIOC_STREAMOFF, &type) == -1)
{
perror("Unable to stop capture");
}
// 关闭文件和设备
for (int i = 0; i < req.count; ++i)
{
munmap(buffer, buf.length);
}
close(fd);
return 0;
}