相机驱动层–V4L2框架解析

概览
相机驱动层位于HAL Moudle与硬件之间,借助linux内核框架层,以文件节点的方式暴露给用户空间,让HAL Moudle通过标准的文件访问接口,从而下发到内核之中。
按照V4L2的标准,它是将一个数据流设备抽象成一个VideoX节点,从属的子设备对应着各自的V4L2_subdev实现,并通过media controller 进行统一管理。
高通平台整个内核相机驱动建立在V4L2框架上,以节点Video0暴露给用户空间,主要用于管理内核中的Session、Request以及与子设备,同时各个子设备实现了各自的v4l2_subdev设备,并且以v4l2_subdev暴露给用户空间。
Camera Hardware是相机驱动交互的最底层了,驱动层控制着他的上下电逻辑以及寄存器读取时序并按照I2C协议与硬件进行通信。根据MIPI CSI协议传递数据,控制各个硬件设备,并且获取图像数据。

流程简介
1) 打开video设备
在操作视频流之前,通过字符设备操作接口open的方法打开一个video设备,并且将返回的字符句柄保存在本地,之后的一系列操作会通过这个句柄来进行,而在打开设备的过程总,会去给每一个子设备上电,完成初始化操作。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
…
int camera_fd;
camera_fd = open("/dev/video0", O_RDWR); // 阻塞打开
…
camera_fd = open("/dev/video0", O_RDWR | O_NONBLOCK); // 非阻塞打开


非阻塞打开,若没有数据,则会返回错误。
2) 查看并设置设备
2.1 打开设备文件
打开设备获取到文件句柄之后,需要查看设备支持哪些功能,该主要是通过ioctl传入VIDIOC_QUERYCAP参数来完成,最终通过检查struct v4l2_capability中的capabilities获取设备支持的功能。除此之外,还可以通过 传入VIDIOC_ENUM_FMT来枚举支持的数据格式,通过传入VIDIOC_G_FMT/VIDIOC_S_FMT来分别获取和获取当前的数据格式,通过传入VIDIOC_G_PARM/VIDIOC_S_PARM来分别获取和设置参数。

#include <sys/ioctl.h>
#include <linux/videodev2.h>
…
struct v4l2_capability cap = {0};
int ret = ioctl(camera_fd, VIDIOC_QUERYCAP, & capability);
…
// 判断是否支持某些功能
if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
printf(“v4l2 device support video capture\n”);
if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
printf(“v4l2 device support video output\n”);


2.2获取设备支持的功能
在使用video设备之前,需要了解video设备支持哪些功能,是图像捕捉还是图像输出设备、可以对视频进行何种调制。支持VBI、是否具有音频功能。
设备的功能保存在struct v4l2_capability结构体中,capabilities变量具体表示了设备具有的功能,功能由宏定义V4L2_CAP_XXXX表示。

2.3 选择设备输入
Video设备可能有多个输入。如某些芯片上,摄像头控制器可以接多个摄像头,则需要选择哪一个摄像头作为输入源。若只有一个输入,则无需选择。VIDIOC_ENUMINPUT命令可以列出对应编号输入设备的信息,信息存放在struct v4l2_input结构体中。VIDIOC_S_INPUT可以通过编号指定当前的输入设备。#include <sys/ioctl.h>
 

#include <linux/videodev2.h>
…
struct v4l2_input input = {0};
// 枚举Video设备所有输入
while (!ioctl(camera_fd, VIDIOC_ENUMINPUT, &input)) {undefined
printf(“video input name: %s\n”, input.name);
++input.index;
}
…
input.index = XX; // 指定输入的编号为XX
ret = ioctl(camera_fd, VIDIOC_S_INPUT, &input); // 设置编号为XX的输入为当前的输入设备
设备的输入信息保存在struct v4l2_input结构体中。
struct v4l2_input {undefined
__u32 index; /* Which input,用来确定输入设备 /
__u8 name[32]; / Label /
__u32 type; / Type of input /
__u32 audioset; / Associated audios (bitfield) /
__u32 tuner; / enum v4l2_tuner_type */
v4l2_std_id std;
__u32 status;
__u32 capabilities;
__u32 reserved[3];
};


2.4 获取和设置像素格式
有些摄像头支持多个格式,有的摄像头支持一种数据格式,因此在设置像素格式之前,还需要了解摄像头支持的像素格式,然后在进行设置。VIDIOC_ENUM_FMT命令枚举设备支持的像素格式,VIDOC_S_FMT命令设备设备当前采用的格式。

#include <sys/ioctl.h>
#include <linux/videodev2.h>
struct v4l2_fmtdesc fmtdesc = {0};
…
// 获取支持的像素格式
while (!ioctl(camera_fd, VIDIOC_ENUM_FMT, &fmtdesc)) {undefined
printf(“fmt: %s\n”, fmtdesc.description);
fmtdesc.index++;
}
…


// 设置像素格式

struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // 视频捕获模式
v4l2_fmt.fmt.pix.width = 720; // 视频宽度
v4l2_fmt.fmt.pix.height = 576; // 视频高度
v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // YUYV像素格式
v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
ret = ioctl(camera_fd, VIDIOC_S_FMT, &fmt);

下面是用到的结构体和枚举定义。

3) 申请帧缓冲区
设备配置完成之后,便可以向设备申请多个用于盛装图像数据的帧缓冲区,该动作通过调用ioctl并且传入VIDIOC_REQBUFS命令来完成,最后缓冲区通过mmap方式影射到用户空间。
在申请之前,需要设置申请的缓冲区数量nr_bufs、缓冲区数据类型type和缓冲区内存使用方式memory。

4) 将帧缓冲区入队
申请好帧缓冲区之后,通过调用ioctl方法将VIDOC_QBUF命令来将帧缓冲区加入到v4l2框架中的缓冲区队列,等硬件模块将图像数据填充到缓冲区中。

#include <stddef.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>

struct video_buf {
    void* start;
    size_t length;
}
struct v4l2_buffer v4l2_buf;
struct video_buf* buf = calloc(req.count, sizeof(struct video_buf));
......
for (i = 0; i < req.count; i++) {  // 将申请的缓冲区全部映射
    memset(&v4l2_buf, 0, sizeof(v4l2_buf));
    buf.index = i;
    // 获取编号为i的缓冲区信息
    ret = ioctl(camera_fd, VIDIOC_QUERYBUF, &v4l2_buffer);
    ......
    buf[i].length = v4l2_buf.length;  // 记录缓冲区长度
    // 开始映射,映射完成后,应用通过该地址获取视频数据
    buf[i].start = mmap(NULL, v4l2_buf.length, PORT_READ | PORT_WRITE, 
            MAP_SHARED, camera_fd, v4l2_buf.m.offset);
    ......
    // 将缓冲区加入到内核的缓冲队列中
    ret = ioctl(camera_fd, VIDIOC_QBUF, v4l2_buf);
    ......
}


struct v4l2_buffer的详细定义如下。

5) 开启数据流(采集视频)
将所有的缓冲区都加入队列中之后便可以调用ioctl并且传入VIDOC_STREAMON命令,来通知整个框架进行数据传输,其中大概包括了通知各个子设备开始进行工作,最终将数据填充到v4l2框架中的缓冲区队列中。

#include <sys/ioctl.h>
#include <linux/videodev2.h>
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
…
ret = ioctl(camera_fd, VIDIOC_STREAMON, &type);


6) 将帧缓冲区出队(处理视频数据格式)
一旦数据流开始进行流转了,我们可以通过ioctl下发VIDOC_DQBUF命令来获取帧缓冲区,并将缓冲区的图像数据取出,进行预览、拍照或者录像的处理,处理完成之后,需要将此次缓冲区再次放入v4l2框架中的队列中等待下次图像数据填充。

#include <sys/ioctl.h>
#include <linux/videodev2.h>
void* data = NULL;
size_t length = 0;
struct v4l2_buffer v4l2_buf = {0};
for (;😉 {undefined
ret = ioctl(camera_fd, VIDIOC_QBUF, &v4l2_buf); // 从环形队列中获取一个缓冲区
…
data = buf[v4l2_buf.index].start; // 缓冲区地址
length = buf[v4l2_buf.index].length // 缓冲区数据长度
…
ret = ioctl(camera_fd, VIDIOC_QBUF, &v4l2_buf); // 将缓冲区放入环形队列中
…
}


7) 停止采集视频

#include <sys/ioctl.h>
#include <linux/videodev2.h>
…
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(camera_fd, VIDIOC_STREAMOFF, &type);

关键结构体
从图中可以看到,v4l2_device是一个顶层管理者,一方面嵌入到了video_device中,暴漏了一个/videox的设备节点。一方面创建了一个media_entity实体作为在media controller中的抽象体,加入到了struct list_head *entites的链表中,为了对其挂载的子设备进行控制,内部有一个subdevs挂载所有子设备的链表。
每一个子设备都有一个v4l2_subdev的结构体进行描述,一方面通过嵌入video_device中暴漏出一个/v4l2_subdev的设备节点给用户空间进行控制,另一方面,生成了一个media_entity的实体作为在media controller中的抽象体,加入到了ebtites的链表中。
通过加入entites链表的方式,media_service保持了对其所有的设备信息的查询和控制能力,而这个能力会通过media controller框架在用户空间创建media设备的节点,暴漏给用户进行控制。

v4l2_device
struct v4l2_device {undefined
struct device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_device *mdev;
#endif
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、 v4l2_device v4l2设备,subdev挂载所有子设备
2、 框架放入media controller管理,初始化时将创建的media_device赋值内部 mdev,建立media_device联系
3、 调用v4l2_devide_register和v4l2_device_unregister 注册和释放。
注册:
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
当前设备和v4l2的设备进行绑定,并保存v4l2设备对象到当前设备的driverdata中。此函数就是用来将当前设备驱动和v4l2对象进行捆绑,便于挂接子设备。
void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
注销掉v4l2的所有子设备和v4l2_dev其他相关资源。

V4l2_subdev1、 device_subdev 代表子设备,初始化时挂载在v4l2_device上,v4l2设备赋值给内部v4l2_dev
2、 加入v4l2_device子设备链表(struct list_head list)统一管理,提高查找效率
3、 V4l2_subdev_ops 不同硬件操作行为
4、 使用CONFIG_MEDIA_CONTROLLER 生成media_entity代表该子设备
5、 当前entity存入到子设备结构体entity中
6、 创建设备节点,调用video_device的api接口,video_deivce存入video_device * devnode

Video_device
struct video_device
{undefined
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
struct media_intf_devnode *intf_devnode;
struct media_pipeline pipe;
#endif
const struct v4l2_file_operations *fops;

u32 device_caps;

/* sysfs */
struct device dev;
struct cdev *cdev;

struct v4l2_device *v4l2_dev;
struct device *dev_parent;

struct v4l2_ctrl_handler *ctrl_handler;

struct vb2_queue *queue;

struct v4l2_prio_state *prio;

/* device info */
char name[32];
int vfl_type;
int vfl_dir;
int minor;
u16 num;
unsigned long flags;
int index;

/* V4L2 file handles */
spinlock_t      fh_lock;
struct list_head    fh_list;

int dev_debug;

v4l2_std_id tvnorms;

/* callbacks */
void (*release)(struct video_device *vdev);
const struct v4l2_ioctl_ops *ioctl_ops;
DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);

DECLARE_BITMAP(disable_locking, BASE_VIDIOC_PRIVATE);
struct mutex *lock;
};
1、 v4l2_device、v4l2_subdev创建节点,实现结构体video_device
2、 video_register_device进行创建
3、 fops对应video_device操作方法集
4、 device_video嵌入到具有特定主设备的字符设备,方法在操作节点时用到
static inline int __must_check video_register_device(struct video_device *vdev,
int type, int nr)
{undefined
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}


media_device
1、使用CONFIG_MEDIA_CONTROLLER,v4l2_device 创建出media_device
2、media_device是media_controller抽象管理者,v4l2及子设备对应各自的entity,media_device进行统一管理
3、用户访问media节点,找出属于v4l2_device的子设备
4、media_device将各个media_entity按照顺序连接起来,控制数据流向
vb2_queue

1、 v4l2核心图像缓冲区,vb2_queue完成,打开设备被创建
2、 结构体中的vb2_ops驱动自己实现
3、 Vb2_mem_ops内存分配方法集
4、 用户空间和内核空间相互传递方法集buf_ops,定义为v4l2_buf_ops标准方法集
5、 Vb2_queue通过vb2_buffer数组管理申请的数据缓冲区,通过queued_list管理入队状态的buffer,通过done_list管理填充数据等待消耗的buffer。
vb2_buffer

1、 vb2_buffer图像缓冲区
2、 入队状态内部queued_entry链接到vb2_queue中的qudued_list
3、 消费状态内部done_entry链接到vb2_queue中的done_list
4、 Vb_queue缓冲区管理者
总结:
V4l2_device相机内核体系的顶层管理者,内部使用链表控制所属子设备v4l2_subdev,使用vb2_queue申请管理数据缓冲区,通过video_device向用户空间暴露设备节点以及控制接口,接收来自用户空间的控制指令,嵌入放到media controller 实现枚举、连接子设备控制数据流走向
模块初始化
内核驱动运行机制,启动过程中,通过moudle_init进行初始化,一方面是v4l2_device初始化,一个是子设备初始化。
V4l2_device初始化,linux找到moudle_init声明的驱动,调用probe进行探测设备,探测成功,初始化完成。
Probe做了以下操作:
1、 获取dts硬件信息,初始化部分硬件设备。
2、 创建v4l2_device结构体,填充信息,通过v4l2_device_register方法注册video设备节点
3、 创建media_device结构体,填充信息,通过media_device_register方法注册media设备节点,赋值给v4l2_device中的mdev.
4、 创建v4l2_device中的media_entity添加到media controller进行管理。
子设备v4l2_subdev初始化:
1、获取dts硬件信息,初始化子设备硬件模块
2、 创建v4l2_subdev结构体,填充信息,通过v4l2_device_register_subdev向系统注册,并将其挂载到v4l2_device设备中
3、创建对应的media_entity,并通过media_device_register_entity方法其添加到media controller中进行统一管理。
4、调用v4l2_device_register_subdev_nodes方法,为所有的设置了V4L2_SUBDEV_FL_HAS_DEVNODE属性的子设备创建设备节点。
处理用户空间请求

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值