一、定义
V4L2代表Video for Linux Two,它是Linux内核的一部分,提供了一种统一的方式来访问各种视频输入/输出设备,如摄像头、电视卡等。
二、工作流程(重点)
打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法(缓冲 区管理)-> 循环获取数据-> 关闭设备。
1. 首先是打开摄像头设备;
2. 查询设备的属性或功能;
3. 设置设备的参数,譬如像素格式、 帧大小、 帧率;
4. 申请帧缓冲、 内存映射;
5. 帧缓冲入队;
6. 开启视频采集;
7. 帧缓冲出队、对采集的数据进行处理;
8. 处理完后,再次将帧缓冲入队,往复;
9. 结束采集
三、相关接口
1.设备的打开和关闭
#include <fcntl.h>
int open(const char *device_name, int flags);
#include <unistd.h>
int close(int fd);
int fd=open(“/dev/video0”,O_RDWR); // 打开设备
close(fd); // 关闭设备
注意:V4L2 的相关定义包含在头文件<linux/videodev2.h> 中.
2.查询设备属性
VIDIOC_QUERYCAP 这个控制命令用来查询设备的属性
ioctl()函数是一个通用的I/O控制函数,在Linux中用于与设备进行通信。
ioctl()对于设备文件来说是一个非常重要的系统调用, 凡是涉及到配置设备、获取设备配置等操作都会使用 ioctl 来完成;但对于普通文件来说, ioctl()几乎没什么用。
int ioctl(int fd, unsigned long request, ... /* variable argument list */ );
V4L2 指令 | 描述 |
VIDIOC_QUERYCAP | 查询设备的属性/能力/功能 |
VIDIOC_ENUM_FMT | 枚举设备支持的像素格式 |
VIDIOC_G_FMT | 获取设备当前的帧格式信息 |
VIDIOC_S_FMT | 设置帧格式信息 |
VIDIOC_REQBUFS | 申请帧缓冲 |
VIDIOC_QUERYBUF | 查询帧缓冲 |
VIDIOC_QBUF | 帧缓冲入队操作 |
VIDIOC_DQBUF | 帧缓冲出队操作 |
VIDIOC_STREAMON | 开启视频采集 |
VIDIOC_STREAMOFF | 关闭视频采集 |
VIDIOC_G_PARM | 获取设备的一些参数 |
VIDIOC_S_PARM | 设置参数 |
VIDIOC_TRY_FMT | 尝试设置帧格式、用于判断设备是否支持该格式 |
VIDIOC_ENUM_FRAMESIZES | 枚举设备支持的视频采集分辨率 |
VIDIOC_ENUM_FRAMEINTERVALS | 枚举设备支持的视频采集帧 |
例如
int ioctl(int fd, int request, struct v4l2_capability *argp);
fd是设备文件描述符,request是请求号,argp是一个指向结构体的指针,用于传递额外的参数。在这个例子中,request是VIDIOC_QUERYCAP,表示查询设备属性。
argp是一个指向struct v4l2_capability结构体的指针,用于存储设备能力的相关信息。
struct v4l2_capability
{
u8 driver[16]; // 驱动名字
u8 card[32]; // 设备名字
u8 bus_info[32]; // 设备在系统中的位置
u32 version; // 驱动版本号
u32 capabilities; // 设备支持的操作
u32 reserved[4]; // 保留字段
};
显示设备信息
struct v4l2_capability cap;
ioctl(fd,VIDIOC_QUERYCAP,&cap);
printf(“Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\n”,
cap.driver,
cap.card,
cap.bus_info,
(cap.version>>16)&0XFF,
(cap.version>>8)&0XFF,
cap.version&0XFF);
3.设置视频帧格式
int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
struct v4l2_fmtdesc结构体的指针,用于存储格式描述信息
int ioctl(int fd, int request, struct v4l2_format *argp);
4l2_format结构体的指针,用于设置或获取格式信息
v4l2_format 的各个域,如 type(传输流类型),
fmt.pix.width(宽),
fmt.pix.heigth(高),
fmt.pix.field(采样区域,如隔行采样),
fmt.pix.pixelformat(采样类型,如 YUV4:2:2),
然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式
四、用V4L2 查询设备信息以及 用它驱动摄像头拍摄一张JPEG格式的照片。
#if 0
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#define DEVICE "/dev/video0"
// Driver Name: PC Camera
// Driver Version: 132864
// Current Format:
// Width: 320
// Height: 240
// Pixel Format: JPEG
// Supported Formats:
// JPEG
void print_device_info(int fd) { //fd 是表示视频设备的文件描述符
struct v4l2_capability cap; // 用于存储设备的能力信息
struct v4l2_format fmt;//用于存储设备的输出格式
struct v4l2_fmtdesc fmt_desc;//用于枚举视频格式
int i, ret;
memset(&cap, 0, sizeof(cap));// 初始化清零
if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {//使用ioctl函数查询设备能力,如果失败则输出错误信息并返回
perror("VIDIOC_QUERYCAP");
return;
}
printf("Driver Name: %s\n", cap.card);//打印设备名称
printf("Driver Version: %u\n", cap.version);//打印设备版本信息
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;// 表示视频捕获
if (ioctl(fd, VIDIOC_G_FMT, &fmt) == -1) {
perror("VIDIOC_G_FMT");
return;
}
printf("Current Format:\n");
printf(" Width: %d\n", fmt.fmt.pix.width);//当前视频格式 宽和高
printf(" Height: %d\n", fmt.fmt.pix.height);
printf(" Pixel Format: %c%c%c%c\n",//打印当前视频格式的像素格式
fmt.fmt.pix.pixelformat & 0xFF,//fmt.fmt.pix.pixelformat 的从低到高的24位
(fmt.fmt.pix.pixelformat >> 8) & 0xFF,//
(fmt.fmt.pix.pixelformat >> 16) & 0xFF,//
(fmt.fmt.pix.pixelformat >> 24) & 0xFF);//
memset(&fmt_desc, 0, sizeof(fmt_desc));//初始化
fmt_desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//存储的格式类型
printf("Supported Formats:\n");//支持的类型
for (i = 0; ioctl(fd, VIDIOC_ENUM_FMT, &fmt_desc) == 0; i++) {
printf(" %c%c%c%c\n",
fmt_desc.pixelformat & 0xFF,
(fmt_desc.pixelformat >> 8) & 0xFF,
(fmt_desc.pixelformat >> 16) & 0xFF,
(fmt_desc.pixelformat >> 24) & 0xFF);
fmt_desc.index++;
}
}
int main() {
int fd = open(DEVICE, O_RDWR);
if (fd == -1) {
perror("open");
return 1;
}
print_device_info(fd);
close(fd);
return 0;
}
#endif
#if 1
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <jpeglib.h> // 需要安装 libjpeg-dev
#define DEVICE "/dev/video0"
#define NB_BUFFER 4
typedef struct videoDeviceParam {
int iFd; //文件描述符 用于标示打开的文件或设备
int iWidth;
int iHeight;
int iBpp;// 表示位深度 每个像素占用的位数
int pixelformat;// 表示视频帧的像素格式
int iVideoBufCnt;// 标示视频缓冲区的数量
int iVideoBufCurIndex;// 表示当前视频缓冲区的索引
int iVideoBufMaxLen;//标示最大视频缓冲区长度
unsigned char *pucVideBuf[NB_BUFFER];// 指针数组 用于存储视频缓冲区的指针
} T_videoDeviceParam, *PT_videoDeviceParam;//将结构体重命名 同时定义了指向该结构体的指针
//
int V4l2InitDevice(char *videoDevName, PT_videoDeviceParam pt_videoDeviceParam) {
int i;
int iFd;
int iError;
struct v4l2_capability tV4l2Cap;
struct v4l2_format tV4l2Fmt;//声明一个 v4l2_format 结构体 用于设置视频格式
struct v4l2_requestbuffers tV4l2ReqBuffs;// 用于请求缓冲区
struct v4l2_buffer tV4l2Buf;//用于操作缓冲区
iFd = open(videoDevName, O_RDWR);//打开视频设备
if (iFd < 0) {
perror("Video OPEN Failed");
return -1;
}
pt_videoDeviceParam->iFd = iFd;
memset(&tV4l2Cap, 0, sizeof(struct v4l2_capability));//初始化
iError = ioctl(iFd, VIDIOC_QUERYCAP, &tV4l2Cap);
if (iError) {
perror("Unable to query device");
close(iFd);
return -1;
}
memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type成员指定视频设备的操作类型
//V4L2_BUF_TYPE_VIDEO_CAPTURE 表示对视频进行视频捕捉操作
tV4l2Fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; // Example format 设置像素格式为JPEG
tV4l2Fmt.fmt.pix.width = 320;
tV4l2Fmt.fmt.pix.height = 240;
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;//设置场域为任意
iError = ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);// VIDIOC_S_FMT 控制指令 用于设置视频设备的格式
if (iError) {
perror("Unable to set format");
close(iFd);
return -1;
}
pt_videoDeviceParam->iWidth = tV4l2Fmt.fmt.pix.width;
pt_videoDeviceParam->iHeight = tV4l2Fmt.fmt.pix.height;
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
tV4l2ReqBuffs.count = NB_BUFFER;//设置缓冲区数量
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//设置缓冲区类型
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;//设置内存映射类型
iError = ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);//求情缓冲区
if (iError) {
perror("Unable to request buffers");
close(iFd);
return -1;
}
pt_videoDeviceParam->iVideoBufCnt = tV4l2ReqBuffs.count;//将缓冲区数量赋值
if (tV4l2Cap.capabilities & V4L2_CAP_STREAMING) {// 如果设备支持流式传输
for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));//清零
tV4l2Buf.index = i;//设置缓冲区索引
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
iError = ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);//查询缓冲区
if (iError) {
perror("Unable to query buffer");
close(iFd);
return -1;
}
pt_videoDeviceParam->iVideoBufMaxLen = tV4l2Buf.length;//将缓冲区最大长度赋值
pt_videoDeviceParam->pucVideBuf[i] = mmap(0,
tV4l2Buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, iFd,
tV4l2Buf.m.offset);//将缓冲区地址赋值给结构体中的相应元素
if (pt_videoDeviceParam->pucVideBuf[i] == MAP_FAILED) {//如果映射失败 报错
perror("Unable to map buffer");
close(iFd);
return -1;
}
}
for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i;//
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//
tV4l2Buf.memory = V4L2_MEMORY_MMAP;//
iError = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf);//将缓冲区加入队列
if (iError) {
perror("Unable to queue buffer");
close(iFd);
return -1;
}
}
// Start streaming
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(iFd, VIDIOC_STREAMON, &type) < 0) {//开始流式传输
perror("Unable to start streaming");
close(iFd);
return -1;
}
// Capture a single frame
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
if (ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf) < 0) {//从队列中取出一个缓冲区
perror("Unable to dequeue buffer");
ioctl(iFd, VIDIOC_STREAMOFF, &type);
close(iFd);
return -1;
}
unsigned char *frame_data = pt_videoDeviceParam->pucVideBuf[tV4l2Buf.index];//获取缓冲区数据指针
printf("Captured a frame of length %d\n", tV4l2Buf.bytesused);//打印捕获到的帧长度
// Save the frame as JPEG
FILE *file = fopen("capture.jpg", "wb");//打开*.jpg文件进行写入 wb表示以二进制写入的方式打开文件
if (!file) {
perror("Unable to open file");
ioctl(iFd, VIDIOC_STREAMOFF, &type);
close(iFd);
return -1;
}
fwrite(frame_data, 1, tV4l2Buf.bytesused, file);//写入帧数据到文件
fclose(file);
if (ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf) < 0) {//将缓冲区重新加入队列
perror("Unable to queue buffer");
ioctl(iFd, VIDIOC_STREAMOFF, &type);
close(iFd);
return -1;
}
// Stop streaming
if (ioctl(iFd, VIDIOC_STREAMOFF, &type) < 0) {//停止流式传输
perror("Unable to stop streaming");
}
}
// Cleanup
for (i = 0; i < pt_videoDeviceParam->iVideoBufCnt; i++) {//遍历所有缓冲区
munmap(pt_videoDeviceParam->pucVideBuf[i], pt_videoDeviceParam->iVideoBufMaxLen);//释放缓冲区内存
}
close(iFd);
return 0;
}
int main() {
T_videoDeviceParam videoDeviceParam;
if (V4l2InitDevice(DEVICE, &videoDeviceParam) == 0) {// 调用 V4l2InitDevice 函数
//函数接受 一个设备名称字符串 和一个 指向 T_videoDeviceParam 结构体的指针作为参数 成功返回0 失败返回-1
printf("Device initialized successfully.\n");
} else {
printf("Failed to initialize device.\n");
}
return 0;
}
#endif