1、摄像头介绍
【Camera基础(一)】Camera摄像头工作原理及整机架构
(1)摄像头的基本工作原理
如图所示,一个景象的反射光被镜头所捕捉(镜头的光圈可以调节进光量,马达用来调节对焦),最终将聚焦好的图像精准对焦到图片传感器上边(色彩滤波会产生三基色),光信号转化为数字信号,通过模数转换最终得到原始码流数据。
我们以 RK3568 芯片为例,其包含 CIF (Camera Interface) 和 lSP (lmage SignalProcessor) 模块。这两个模块是 RK3568 芯片的关键组成部分,用于图像采集和处理。
CIF 是一个标准接口,用于连接 CMOS 或 CCD 图像传感器,并从传感器读取图像数据。而 ISP 是一种专门用于图像处理的硬件模块,它可以对从传感器读取的原始图像数据进行预处理、降噪、白平衡、自动对焦等处理操作,以生成最终的图像数据。
(2)编码
摄像头软件层,一般会提供多种格式和分辨率的参数,供上层选择,常见的格式如:YUYV、MJPEG、H264、NV12 等。其中:
- YUYV:原始码流,每个像素点占 2 个字节。
- MJPEG:可以将数据压缩 7 倍左右,可以是 NV12 也可以是 YUYV 。
- H264编码:主要看配置,其中 I 帧压缩 7 倍左右,P 帧 20 倍左右,B 帧 50 倍左右,理论上 B 帧越多,就可能支持的高分辨率高帧率的码流。
- NV12:原始码流,每个像素点 1.5 个字节。
(3)编码的目的
如果没有编码,我们计算一下 1s 请求 NV12 4K 30HZ 的码流需要多大的带宽?
答案:(3840 * 2160 * 1.5 * 30)字节 = 373248000 字节 = 356M
按照,我们整机常用的 camera 接口 usb 2.0 的理论带宽:480Mbps = 60M/s,无法满足 NV12 原始码流 4K 30HZ 的预览要求的,编解码技术,可以有效的压缩数据的体积而不会或较少的影像画质。
(4)传输
作为相机的数据传输协议,肯定是要统一的,广泛的,厂家和广大开发者支持的协议。其中 USB 协议肯定有一席之地。整机方案,基本采用的都是 USB Camera 方案。
UVC 是 USB Video Class 的简写,也就是 USB 接口的视频设备。一个 UVC 设备,需包含 1 个 VC Interface 和 1 个或多个 VS Interface 。
VC Interface 进行配置参数的传递,如启动和关闭自动对焦,白平衡等。
VS Interface 进行图片数据流的传输。
(5)小结:
在 Linux 系统中,应用层和 USB 相机通过 UVC 协议进行交互。系统为了兼容不同的交互协议。在 kernel 层抽象了 V4L2 驱动,方便上层进程和各个协议对接。
2、V4L2 介绍
V4L2(Video for Linux 2)是 Linux 操作系统中用于支持摄像头和视频设备的框架。它提供了一组 API 和驱动程序接口,用于在 Linux 系统中进行视频采集、视频流处理和视频播放等操作。
V4L2 框架具有以下特点和功能:
设备抽象层:V4L2 框架提供了一个设备抽象层,使得应用程序可以与各种不同类型的视频设备进行通信,包括摄像头、视频采集卡等。
统一的控制接口:V4L2 定义了一套统一的控制接口,可以通过这些接口来配置和调整视频设备的各种参数,比如亮度、对比度、饱和度等。
视频捕获和输出:V4L2 支持视频的捕获和输出功能,可以从视频设备中获取原始图像数据,并将其保存到文件或者进行实时显示。
视频流处理:V4L2 框架提供了丰富的视频流处理功能,包括图像缩放、色彩空间转换、帧率控制、图像增强等,可以对视频数据进行实时的处理和操作。
内存映射和 DMA 支持:V4L2 支持内存映射和 DMA(直接内存访问)技术,可以加快视频数据的传输速度,提高系统性能。
事件和回调机制:V4L2 框架支持事件和回调机制,可以实时通知应用程序有关视频设备和视频流的状态变化,比如帧捕获完成、设备断开连接等。
多线程支持:V4L2 允许应用程序在多个线程中同时进行视频采集、处理和显示等操作,以实现并发处理和更高的效率。
V4L2 架构设计之初是只针对视频设备的,那时的 V4L2 被限制只能在 struct video_device 结构体里面创建,并且用 video_buf 控制视频缓存。但随着硬件的变化也越来越复杂,现在大部分设备里面包含了多个子设备 IC ,比较常见的子设备如编解码器、传感器、摄像头控制器等。通常这些 IC 一般通过 i2c 总线连接到主板,这些设备都统称为 sub-devices(即子设备)。
3、v4l2-应用程序读取图像的流程
代码如下:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <string.h> #include <linux/videodev2.h> //命令码 #include <sys/mman.h> int main(int argc, const char *argv[]) { // 1.打开设备 int fd = open("/dev/video0", O_RDWR); if (fd < 0) { perror("open video0 faild"); return -1; } // 2.获取摄像头支持的格式ioctl struct v4l2_fmtdesc v4fmt; v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // v4fmt.index = 0; int i = 0; while (1) { v4fmt.index = i++; int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt); if (ret < 0) { perror("itctl get fmt faild"); 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[0] = %d\n", v4fmt.reserved[0]); printf("------------------------------------------\n"); } // 3.设置采集格式 struct v4l2_format vfmt; vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集 vfmt.fmt.pix.width = 640; //设置宽 vfmt.fmt.pix.height = 480; //设置高 //vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置视频采集格式 vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt); if (ret < 0) { perror("VIDIOC_S_FMT faild"); } //查看是否设置成功 memset(&vfmt, 0, sizeof(vfmt)); vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); //获取视频采集格式 if (ret < 0) { perror("VIDIOC_G_FMT faild"); } printf("vfmt.fmt.pix.width = %d\n", vfmt.fmt.pix.width); printf("vfmt.fmt.pix.height = %d\n", vfmt.fmt.pix.height); unsigned char *p = (unsigned char *)&v4fmt.pixelformat; printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]); // 4.申请内核空间 struct v4l2_requestbuffers reqbuffer; reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuffer.count = 4; //申请4个缓冲区 reqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式 ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer); if (ret < 0) { perror("VIDIOC_REQBUFS faild"); } // 5.映射 unsigned char *mptr[4]; //保存映射后用户空间首地址 unsigned int size[4]; struct v4l2_buffer mapbuffer; //初始化type,index mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; for (int i = 0; i < 4; i++) { mapbuffer.index = i; ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer); //从内核空间查询一空间做映射 if (ret < 0) { perror("VIDIOC_QUERYBUF faild"); } mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset); size[i] = mapbuffer.length; //使用完毕,放回 ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer); if (ret < 0) { perror("VIDIOC_QBUF faild"); } } // 6.开始采集 int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_STREAMON, &type); if (ret < 0) { perror("VIDIOC_STREAMON faild"); } //7.从队列中提取一帧数据 struct v4l2_buffer readbuffer; readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer); if (ret < 0) { perror("VIDIOC_DQBUF faild"); } FILE *file = fopen("my.jpg", "w+"); fwrite(mptr[readbuffer.index], readbuffer.length, 1, file); fclose(file); //通知内核已经使用完毕 ret = ioctl(fd, VIDIOC_QBUF, &readbuffer); if (ret < 0) { perror("VIDIOC_QBUF faild"); } //8.停止采集 ret = ioctl(fd, VIDIOC_STREAMOFF, &type); if (ret < 0) { perror("VIDIOC_STREAMOFF faild"); } //9.释放映射 for(int i = 0;i<4 ;i++){ munmap(mptr[i],size[i]); } // 10.关闭设备 close(fd); return 0; }
4、V4L2 核心
(1)概述
- 通常一个 camera 的模组如图所示,通常包括 Lens、Sensor、CSI 接口等,其中 CSI 接口用于视频数据的传输;
- SoC 的 Mipi 接口对接 Camera ,并通过 I2C/SPI 控制 camera 模组;
- Camera 模组中也可以包含 ISP 模块,用于对图像进行处理,有的 SoC 中也集成了 ISP 的 IP,接收 camera 的 raw 数据后,进行图像处理;
(2)数据结构
如果以上图的硬件为例,对摄像头的硬件该怎么来抽象呢?没错,就是以 v4l2_device 和 v4l2_subdev 来进行抽象,以 v4l2_device 来代表整个输入设备,以 v4l2_subdev 来代表子模块,比如 CSI、Sensor 等;
驱动开发者实现 Linux 的 video 设备驱动,需要按照 V4L2 的驱动模型进行设计,该驱动模型主要围绕核心数据结构 struct video_device 来展开设计,通过该数据结构来完成视频设备的分配、设置、注册等工作。
1.对于没有子设备的简单视频设备来说,其驱动程序重点需要实现两个操作集: v4l2_file_operations 和 v4l2_ioctl_ops ,V4L2 架构最终会调用这两个操作集中的函数接口,来完成对视频设备硬件的控制。
2.对于有子设备的视频设备,则需要借助 v4l2_subdev 框架来完成复杂视频设备驱动。每一个 subdev 驱动程序都应该创建一个 struct v4l2_subdev 结构实例,你可以在你的驱动程序中单独的为该结构申请内存,同样可以将这个结构嵌入到其他的驱动数据结构中去。
v4l2_device:对视频设备的整体进行抽象,可以看成是一个纽带,将各个子设备联系在一起,通常它会嵌入在其他结构体中以提供 v4l2 框架的功能,比如 strcut isp_device ;
v4l2_subdev:对子设备进行抽象,该结构体中包含的 struct v4l2_subdev_ops 是一个完备的操作函数集,用于对接各种不同的子设备,比如 video、audio、sensor 等,同时还有一个核心的函数集 struct v4l2_subdev_core_ops ,提供更通用的功能。子设备驱动根据设备特点实现该函数集中的某些函数即可;
video_device:用于向系统注册字符设备节点,以便用户空间可以进行交互,包括各类设置以及数据 buffer 的获取等,在该结构体中也能看到 struct v4l2_ioctl_ops 和 struct vb2_queue 结构体字段,这些与上文中的应用层代码编写息息相关;
如果子设备不需要与应用层交互,struct v4l2_subdev 中内嵌的 video_device 也可以不向系统注册字符设备;
video_device 结构体,可以内嵌在其他结构体中,以便提供用户层交互的功能,比如 struct isp_video;
针对图中回调函数集,v4l2-core 提供了一些实现,所以 driver 在实现时,非特殊情况下可以不用重复造轮子;
video_device
// include/media/v4l2-dev.h struct video_device { // ... const struct v4l2_file_operations *fops; // ... /* sysfs */ struct device dev; struct cdev *cdev; struct v4l2_device *v4l2_dev; struct device *dev_parent; // ... /* callbacks */ void (*release)(struct video_device *vdev); const struct v4l2_ioctl_ops *ioctl_ops; // ... };
v4l2_device
// include/media/v4l2-device.h struct v4l2_device { struct device *dev; struct media_device *mdev; struct list_head subdevs; spinlock_t lock; char name[V4L2_DEVICE_NAME_SIZE]; void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg); struct v4l2_ctrl_handler *ctrl_handler; struct v4l2_prio_state prio; struct kref ref; void (*release)(struct v4l2_device *v4l2_dev); };
v4l2_ioctl_ops
// include/media/v4l2-ioctl.h struct v4l2_ioctl_ops { /* ioctl callbacks */ /* VIDIOC_QUERYCAP handler */ int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap); /* VIDIOC_ENUM_FMT handlers */ int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh, struct v4l2_fmtdesc *f); // ... }
v4l2_subdev
// include/media/v4l2-subdev.h struct v4l2_subdev { #if defined(CONFIG_MEDIA_CONTROLLER) struct media_entity entity; #endif struct list_head list; struct module *owner; bool owner_v4l2_dev; u32 flags; struct v4l2_device *v4l2_dev; const struct v4l2_subdev_ops *ops; const struct v4l2_subdev_internal_ops *internal_ops; struct v4l2_ctrl_handler *ctrl_handler; char name[V4L2_SUBDEV_NAME_SIZE]; u32 grp_id; void *dev_priv; void *host_priv; struct video_device *devnode; struct device *dev; struct fwnode_handle *fwnode; struct list_head async_list; struct v4l2_async_subdev *asd; struct v4l2_async_notifier *notifier; struct v4l2_async_notifier *subdev_notifier; struct v4l2_subdev_platform_data *pdata; };
v4l2_subdev_ops
// include/media/v4l2-subdev.h struct v4l2_subdev_ops { const struct v4l2_subdev_core_ops *core; const struct v4l2_subdev_tuner_ops *tuner; const struct v4l2_subdev_audio_ops *audio; const struct v4l2_subdev_video_ops *video; const struct v4l2_subdev_vbi_ops *vbi; const struct v4l2_subdev_ir_ops *ir; const struct v4l2_subdev_sensor_ops *sensor; const struct v4l2_subdev_pad_ops *pad; };
(3)流程分析
在驱动实现中,驱动结构体中内嵌 struct video_device,同时实现 struct v4l2_file_operations 结构体中的函数,最终通过 video_register_device 向提供注册;
v4l2_register_device(video_register_device) 函数通过 cdev_add 向系统注册字符设备,并指定了 file_operations ,用户空间调用 open/read/write/ioctl 等接口,便可回调到驱动实现中;
v4l2_register_device(video_register_device)函数中,通过 device_register 向系统注册设备,会在 /sys 文件系统下创建节点;
完成注册后,用户空间便可通过文件描述符来进行访问,从应用层看,大部分都是通过 ioctl 接口来完成,流程如下:
Linux内核
专栏收录该内容
69 篇文章11 订阅
订阅专栏
文章目录
1、摄像头介绍
(1)摄像头的基本工作原理
(2)编码
(3)编码的目的
(4)传输
(5)小结:
2、V4L2 介绍
3、v4l2-应用程序读取图像的流程
4、V4L2 核心
(1)概述
(2)数据结构
video_device
v4l2_device
v4l2_ioctl_ops
v4l2_subdev
v4l2_subdev_ops
(3)流程分析
To be continued:
5、v4l2 API
6、参考
1、摄像头介绍
【Camera基础(一)】Camera摄像头工作原理及整机架构
(1)摄像头的基本工作原理
如图所示,一个景象的反射光被镜头所捕捉(镜头的光圈可以调节进光量,马达用来调节对焦),最终将聚焦好的图像精准对焦到图片传感器上边(色彩滤波会产生三基色),光信号转化为数字信号,通过模数转换最终得到原始码流数据。
我们以 RK3568 芯片为例,其包含 CIF (Camera Interface) 和 lSP (lmage SignalProcessor) 模块。这两个模块是 RK3568 芯片的关键组成部分,用于图像采集和处理。
CIF 是一个标准接口,用于连接 CMOS 或 CCD 图像传感器,并从传感器读取图像数据。而 ISP 是一种专门用于图像处理的硬件模块,它可以对从传感器读取的原始图像数据进行预处理、降噪、白平衡、自动对焦等处理操作,以生成最终的图像数据。
(2)编码
摄像头软件层,一般会提供多种格式和分辨率的参数,供上层选择,常见的格式如:YUYV、MJPEG、H264、NV12 等。其中:
YUYV:原始码流,每个像素点占 2 个字节。
MJPEG:可以将数据压缩 7 倍左右,可以是 NV12 也可以是 YUYV 。
H264编码:主要看配置,其中 I 帧压缩 7 倍左右,P 帧 20 倍左右,B 帧 50 倍左右,理论上 B 帧越多,就可能支持的高分辨率高帧率的码流。
NV12:原始码流,每个像素点 1.5 个字节。
(3)编码的目的
如果没有编码,我们计算一下 1s 请求 NV12 4K 30HZ 的码流需要多大的带宽?
答案:(3840 * 2160 * 1.5 * 30)字节 = 373248000 字节 = 356M
按照,我们整机常用的 camera 接口 usb 2.0 的理论带宽:480Mbps = 60M/s,无法满足 NV12 原始码流 4K 30HZ 的预览要求的,编解码技术,可以有效的压缩数据的体积而不会或较少的影像画质。
(4)传输
作为相机的数据传输协议,肯定是要统一的,广泛的,厂家和广大开发者支持的协议。其中 USB 协议肯定有一席之地。整机方案,基本采用的都是 USB Camera 方案。
UVC 是 USB Video Class 的简写,也就是 USB 接口的视频设备。一个 UVC 设备,需包含 1 个 VC Interface 和 1 个或多个 VS Interface 。
VC Interface 进行配置参数的传递,如启动和关闭自动对焦,白平衡等。
VS Interface 进行图片数据流的传输。
(5)小结:
在 Linux 系统中,应用层和 USB 相机通过 UVC 协议进行交互。系统为了兼容不同的交互协议。在 kernel 层抽象了 V4L2 驱动,方便上层进程和各个协议对接。
2、V4L2 介绍
Camera基础(Linux之V4L2驱动框架)
V4L2(Video for Linux 2)是 Linux 操作系统中用于支持摄像头和视频设备的框架。它提供了一组 API 和驱动程序接口,用于在 Linux 系统中进行视频采集、视频流处理和视频播放等操作。
V4L2 框架具有以下特点和功能:
设备抽象层:V4L2 框架提供了一个设备抽象层,使得应用程序可以与各种不同类型的视频设备进行通信,包括摄像头、视频采集卡等。
统一的控制接口:V4L2 定义了一套统一的控制接口,可以通过这些接口来配置和调整视频设备的各种参数,比如亮度、对比度、饱和度等。
视频捕获和输出:V4L2 支持视频的捕获和输出功能,可以从视频设备中获取原始图像数据,并将其保存到文件或者进行实时显示。
视频流处理:V4L2 框架提供了丰富的视频流处理功能,包括图像缩放、色彩空间转换、帧率控制、图像增强等,可以对视频数据进行实时的处理和操作。
内存映射和 DMA 支持:V4L2 支持内存映射和 DMA(直接内存访问)技术,可以加快视频数据的传输速度,提高系统性能。
事件和回调机制:V4L2 框架支持事件和回调机制,可以实时通知应用程序有关视频设备和视频流的状态变化,比如帧捕获完成、设备断开连接等。
多线程支持:V4L2 允许应用程序在多个线程中同时进行视频采集、处理和显示等操作,以实现并发处理和更高的效率。
V4L2 架构设计之初是只针对视频设备的,那时的 V4L2 被限制只能在 struct video_device 结构体里面创建,并且用 video_buf 控制视频缓存。但随着硬件的变化也越来越复杂,现在大部分设备里面包含了多个子设备 IC ,比较常见的子设备如编解码器、传感器、摄像头控制器等。通常这些 IC 一般通过 i2c 总线连接到主板,这些设备都统称为 sub-devices(即子设备)。
3、v4l2-应用程序读取图像的流程
v4l2应用程序接口
代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/videodev2.h> //命令码
#include <sys/mman.h>
int main(int argc, const char *argv[])
{
// 1.打开设备
int fd = open("/dev/video0", O_RDWR);
if (fd < 0)
{
perror("open video0 faild");
return -1;
}
// 2.获取摄像头支持的格式ioctl
struct v4l2_fmtdesc v4fmt;
v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// v4fmt.index = 0;
int i = 0;
while (1)
{
v4fmt.index = i++;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
if (ret < 0)
{
perror("itctl get fmt faild");
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[0] = %d\n", v4fmt.reserved[0]);
printf("------------------------------------------\n");
}
// 3.设置采集格式
struct v4l2_format vfmt;
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集
vfmt.fmt.pix.width = 640; //设置宽
vfmt.fmt.pix.height = 480; //设置高
//vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置视频采集格式
vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
if (ret < 0)
{
perror("VIDIOC_S_FMT faild");
}
//查看是否设置成功
memset(&vfmt, 0, sizeof(vfmt));
vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); //获取视频采集格式
if (ret < 0)
{
perror("VIDIOC_G_FMT faild");
}
printf("vfmt.fmt.pix.width = %d\n", vfmt.fmt.pix.width);
printf("vfmt.fmt.pix.height = %d\n", vfmt.fmt.pix.height);
unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);
// 4.申请内核空间
struct v4l2_requestbuffers reqbuffer;
reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuffer.count = 4; //申请4个缓冲区
reqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式
ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
if (ret < 0)
{
perror("VIDIOC_REQBUFS faild");
}
// 5.映射
unsigned char *mptr[4]; //保存映射后用户空间首地址
unsigned int size[4];
struct v4l2_buffer mapbuffer;
//初始化type,index
mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (int i = 0; i < 4; i++)
{
mapbuffer.index = i;
ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer); //从内核空间查询一空间做映射
if (ret < 0)
{
perror("VIDIOC_QUERYBUF faild");
}
mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);
size[i] = mapbuffer.length;
//使用完毕,放回
ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
if (ret < 0)
{
perror("VIDIOC_QBUF faild");
}
}
// 6.开始采集
int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_STREAMON, &type);
if (ret < 0)
{
perror("VIDIOC_STREAMON faild");
}
//7.从队列中提取一帧数据
struct v4l2_buffer readbuffer;
readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
if (ret < 0)
{
perror("VIDIOC_DQBUF faild");
}
FILE *file = fopen("my.jpg", "w+");
fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
fclose(file);
//通知内核已经使用完毕
ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
if (ret < 0)
{
perror("VIDIOC_QBUF faild");
}
//8.停止采集
ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
if (ret < 0)
{
perror("VIDIOC_STREAMOFF faild");
}
//9.释放映射
for(int i = 0;i<4 ;i++){
munmap(mptr[i],size[i]);
}
// 10.关闭设备
close(fd);
return 0;
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
4、V4L2 核心
一文分析Linux v4l2框架
(1)概述
通常一个 camera 的模组如图所示,通常包括 Lens、Sensor、CSI 接口等,其中 CSI 接口用于视频数据的传输;
SoC 的 Mipi 接口对接 Camera ,并通过 I2C/SPI 控制 camera 模组;
Camera 模组中也可以包含 ISP 模块,用于对图像进行处理,有的 SoC 中也集成了 ISP 的 IP,接收 camera 的 raw 数据后,进行图像处理;
(2)数据结构
v4l2驱动架构 v4l2驱动框架为何那么复杂
如果以上图的硬件为例,对摄像头的硬件该怎么来抽象呢?没错,就是以 v4l2_device 和 v4l2_subdev 来进行抽象,以 v4l2_device 来代表整个输入设备,以 v4l2_subdev 来代表子模块,比如 CSI、Sensor 等;
驱动开发者实现 Linux 的 video 设备驱动,需要按照 V4L2 的驱动模型进行设计,该驱动模型主要围绕核心数据结构 struct video_device 来展开设计,通过该数据结构来完成视频设备的分配、设置、注册等工作。
对于没有子设备的简单视频设备来说,其驱动程序重点需要实现两个操作集: v4l2_file_operations 和 v4l2_ioctl_ops ,V4L2 架构最终会调用这两个操作集中的函数接口,来完成对视频设备硬件的控制。
对于有子设备的视频设备,则需要借助 v4l2_subdev 框架来完成复杂视频设备驱动。每一个 subdev 驱动程序都应该创建一个 struct v4l2_subdev 结构实例,你可以在你的驱动程序中单独的为该结构申请内存,同样可以将这个结构嵌入到其他的驱动数据结构中去。
v4l2_device:对视频设备的整体进行抽象,可以看成是一个纽带,将各个子设备联系在一起,通常它会嵌入在其他结构体中以提供 v4l2 框架的功能,比如 strcut isp_device ;
v4l2_subdev:对子设备进行抽象,该结构体中包含的 struct v4l2_subdev_ops 是一个完备的操作函数集,用于对接各种不同的子设备,比如 video、audio、sensor 等,同时还有一个核心的函数集 struct v4l2_subdev_core_ops ,提供更通用的功能。子设备驱动根据设备特点实现该函数集中的某些函数即可;
video_device:用于向系统注册字符设备节点,以便用户空间可以进行交互,包括各类设置以及数据 buffer 的获取等,在该结构体中也能看到 struct v4l2_ioctl_ops 和 struct vb2_queue 结构体字段,这些与上文中的应用层代码编写息息相关;
如果子设备不需要与应用层交互,struct v4l2_subdev 中内嵌的 video_device 也可以不向系统注册字符设备;
video_device 结构体,可以内嵌在其他结构体中,以便提供用户层交互的功能,比如 struct isp_video;
针对图中回调函数集,v4l2-core 提供了一些实现,所以 driver 在实现时,非特殊情况下可以不用重复造轮子;
video_device
// include/media/v4l2-dev.h
struct video_device
{
// ...
const struct v4l2_file_operations *fops;
// ...
/* sysfs */
struct device dev;
struct cdev *cdev;
struct v4l2_device *v4l2_dev;
struct device *dev_parent;
// ...
/* callbacks */
void (*release)(struct video_device *vdev);
const struct v4l2_ioctl_ops *ioctl_ops;
// ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
v4l2_device
// include/media/v4l2-device.h
struct v4l2_device {
struct device *dev;
struct media_device *mdev;
struct list_head subdevs;
spinlock_t lock;
char name[V4L2_DEVICE_NAME_SIZE];
void (*notify)(struct v4l2_subdev *sd,
unsigned int notification, void *arg);
struct v4l2_ctrl_handler *ctrl_handler;
struct v4l2_prio_state prio;
struct kref ref;
void (*release)(struct v4l2_device *v4l2_dev);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
v4l2_ioctl_ops
// include/media/v4l2-ioctl.h
struct v4l2_ioctl_ops {
/* ioctl callbacks */
/* VIDIOC_QUERYCAP handler */
int (*vidioc_querycap)(struct file *file, void *fh,
struct v4l2_capability *cap);
/* VIDIOC_ENUM_FMT handlers */
int (*vidioc_enum_fmt_vid_cap)(struct file *file, void *fh,
struct v4l2_fmtdesc *f);
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
v4l2_subdev
// include/media/v4l2-subdev.h
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
#endif
struct list_head list;
struct module *owner;
bool owner_v4l2_dev;
u32 flags;
struct v4l2_device *v4l2_dev;
const struct v4l2_subdev_ops *ops;
const struct v4l2_subdev_internal_ops *internal_ops;
struct v4l2_ctrl_handler *ctrl_handler;
char name[V4L2_SUBDEV_NAME_SIZE];
u32 grp_id;
void *dev_priv;
void *host_priv;
struct video_device *devnode;
struct device *dev;
struct fwnode_handle *fwnode;
struct list_head async_list;
struct v4l2_async_subdev *asd;
struct v4l2_async_notifier *notifier;
struct v4l2_async_notifier *subdev_notifier;
struct v4l2_subdev_platform_data *pdata;
};
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
v4l2_subdev_ops
// include/media/v4l2-subdev.h
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_vbi_ops *vbi;
const struct v4l2_subdev_ir_ops *ir;
const struct v4l2_subdev_sensor_ops *sensor;
const struct v4l2_subdev_pad_ops *pad;
};
1
2
3
4
5
6
7
8
9
10
11
(3)流程分析
在驱动实现中,驱动结构体中内嵌 struct video_device,同时实现 struct v4l2_file_operations 结构体中的函数,最终通过 video_register_device 向提供注册;
v4l2_register_device(video_register_device) 函数通过 cdev_add 向系统注册字符设备,并指定了 file_operations ,用户空间调用 open/read/write/ioctl 等接口,便可回调到驱动实现中;
v4l2_register_device(video_register_device)函数中,通过 device_register 向系统注册设备,会在 /sys 文件系统下创建节点;
完成注册后,用户空间便可通过文件描述符来进行访问,从应用层看,大部分都是通过 ioctl 接口来完成,流程如下:
用户层的 ioctl 回调到 __video_do_ioctl 中,该函数会对系统提供的 struct v4l2_ioctl_info v4l2_ioctls[] 表进行查询,找到对应的项后进行调用;
驱动做的工作就是填空题,实现对应的回调,在合适的时候被调用;
To be continued:
5、v4l2 API
6、参考
【Camera基础(一)】Camera摄像头工作原理及整机架构
【Camera基础(二)】摄像头驱动原理和开发&&V4L2子系统驱动架构
基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集
基于ZedBoard的Webcam设计(二):USB摄像头图片采集+QT显示
基于ZedBoard的Webcam设计(三):视频的采集和动态显示
基于ZedBoard的Webcam设计(四):MJPG编码和AVI封装
基于ZedBoard的Webcam设计(五):x264编码在zedboard上的实现(软编码)
Linux摄像头驱动学习之:(二)通过虚拟驱动vivi分析摄像头驱动