V4L2应用编程
一、定义
V4L2(Video4Linux2)是 Linux 内核中用于视频设备驱动的接口标准,提供了一组统一的接口,允许用户空间应用程序与不同类型的视频设备进行交互,例如摄像头、电视接收器、视频捕获卡等。V4L2 使得应用程序可以使用相同的 API 来操作不同厂商的硬件设备,减少了硬件差异带来的影响。
二、图像的压缩方式
JPEG压缩方式:将颜色空间由RGB转换为YCbCr,其中Y表示亮度,Cb表示蓝色差,Cr表示红色差,通常人眼对颜色偏差不敏感,所以对Cb和Cr进行较强的压缩,而对亮度进行较少的压缩。采用的压缩算法主要是离散余弦变换。这样会将图像分成高频和低频部分,高频部分为边缘和纹理,低频部分为大致结构。而后进行量化,即舍去高频部分。最后再通过熵编码对数据进行进一步压缩。这种方法为有损压缩。
三、YUV色彩空间
YUV 是一种常见的色彩空间,用于表示图像或视频中的亮度(Y)和色度(U 和 V)信息。YUV 中的亮度分量 Y 表示图像的明亮程度,而色度分量 U 和 V 表示色彩信息。常见的 YUV 格式包括 YUV420(每4个Y分量2×2块共用一组U和V)、YUV422 (每两个水平的Y共用一组分量U和V)等,其中 420 是最常用的色度子采样方式。 JPEG中的YCbCr实际上是YUV的一种变体。YUV422相比于RGB,色度数据量减少一半,总体数据量约减少三分之一;YUV422相比于RGB,色度数据量减少至四分之一,总体数据量约减少二分之一。
四、V4L2应用编程步骤
1、打开设备
2、查询摄像头支持的功能
3、设置视频格式
4、在内核空间申请内核缓冲区,并使用内存映射
5、将缓冲区加入到任务队列中
6、开启视频流
7、视频流的捕获
8、关闭视频流
9、关闭文件
五、具体实现步骤
(1)打开设备
int fd = open("/dev/video0",O_RDWR);
if(fd == -1){
perror("open video device failed");
return -1;
}
(2)查询摄像头支持的功能
struct v4l2_capability cap;
int ret = ioctl(fd, VIDIOC_QUERYCAP,&cap);
if(ret < 0){
perror("querycap failed");
close(fd);
return -1;
}
(3)查询摄像头支持的功能
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1280;
fmt.fmt.pix.height = 720;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_NONE;
ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if(ret < 0){
perror("set video format failed");
close(fd);
return -1;
}
(4)申请内核缓冲区并使用内存映射
struct v4l2_requestbuffers req;
memset(&req, 0, sizeof(req));
req.count = MEM_NUM;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS,&req);
if(ret < 0){
perror("request memory failed");
close(fd);
return -1;
}
std::vector<buf_app> buf_a(MEM_NUM);
struct v4l2_buffer buf_v;
for(int i = 0; i < MEM_NUM; i++){
memset(&buf_v, 0, sizeof(buf_v));
buf_v.index = i;
buf_v.memory = V4L2_MEMORY_MMAP;
buf_v.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_QUERYBUF,&buf_v);
if(ret < 0){
perror("memory query failed");
close(fd);
return -1;
}
buf_a[i].length = buf_v.length;
buf_a[i].start = mmap(NULL,buf_v.length,PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf_v.m.offset);
if(buf_a[i].start == MAP_FAILED){
perror("MMAP failed");
close(fd);
return -1;
}
}
(5)将缓冲区加入到任务队列中
for(int i = 0; i < MEM_NUM ; i++){
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.index = i;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if(ret < 0){
perror("queue failed");
for (int j = 0; j < i; j++) {
munmap(buf_a[j].start, buf_a[j].length);
}
close(fd);
return -1;
}
}
(6)开启视频流
v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if(ret < 0){
perror("stream on failed");
for (int j = 0; j < MEM_NUM; j++) {
munmap(buf_a[j].start, buf_a[j].length);
}
close(fd);
return -1;
}
(7)视频流的捕获
while(1){
for(int i = 0; i <MEM_NUM; i++){
struct v4l2_buffer buf;
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_DQBUF, &buf);
if(ret < 0){
perror("DQBUF ERROR");
close(fd);
return -1;
}
printf("get frame\n");
char filename[20];
snprintf(filename,sizeof(filename),"frame-%d.jpg",buf.index);
FILE *file = fopen(filename,"wb");
if(file != NULL){
fwrite(buf_a[buf.index].start,buf.bytesused, 1, file);
fclose(file);
printf("Pic saved as %s\n", filename);
}else{
perror("fopen error");
}
ret = ioctl(fd, VIDIOC_QBUF, &buf);
if(ret < 0){
perror("QBUF error");
close(fd);
return -1;
}
}
}
(8)关闭视频流
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
if(ret < 0){
perror("stream off failed");
}
(9)关闭文件
for(int i = 0; i < MEM_NUM; i++){
munmap(buf_a[i].start,buf_a[i].length);
}
close(fd);
return 0;
六、MMAP内存映射
mmap是Linux和Unix系统中用于内存映射的一个系统调用,他可以将文件或设备的一部分或全部内容映射到进程的虚拟空间中。这样,进程就可以像访问内存一样访问文件或设备,而不用使用read,write来回的在用户空间和内核空间来回切换。从而达到高效的文件操作。
内存映射的原理是通过操作系统的虚拟内存管理来实现的。进程通过 mmap 调用请求将一个文件或设备的内容映射到其地址空间中的一块连续虚拟内存区域。操作系统会管理这个区域的物理内存和虚拟内存之间的映射。
七、mmap函数的具体使用方法
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
八、内存映射的优缺点
优点:
1. 高效: 通过内存映射,进程可以直接访问文件或设备内存,避免了传统 I/O 操作的开销。
2. 简洁: 不需要手动读取或写入数据,操作文件或设备就像操作内存一样。
3. 进程间共享: 通过共享内存区域,多个进程可以快速共享数据。
缺点:
1. 内存限制: 映射的文件或设备大小受到进程虚拟内存空间的限制。
2. 复杂性: 需要处理映射的内存区域的保护、同步等问题。
3. 不适用于小文件: 对于小文件,使用 read 和 write 可能更加简单和高效。