一、框架概要:
下面我们大至了解下:
a) 打开video设备
在需要进行视频数据流的操作之前,首先要通过标准的字符设备操作接口open方法来打开一个video设备,并且将返回的字符句柄存在本地,之后的一系列操作都是基于该句柄,而在打开的过程中,会去给每一个子设备的上电,并完成各自的一系列初始化操作。
b) 查看并设置设备
在打开设备获取其文件句柄之后,就需要查询设备的属性,该动作主要通过ioctl传入VIDIOC_QUERYCAP参数来完成,其中该系列属性通过v4l2_capability结构体来表达,除此之外,还可以通过传入VIDIOC_ENUM_FMT来枚举支持的数据格式,通过传入VIDIOC_G_FMT/VIDIOC_S_FMT来分别获取和获取当前的数据格式,通过传入VIDIOC_G_PARM/VIDIOC_S_PARM来分别获取和设置参数。
c) 申请帧缓冲区
完成设备的配置之后,便可以开始向设备申请多个用于盛装图像数据的帧缓冲区,该动作通过调用ioctl并且传入VIDIOC_REQBUFS命令来完成,最后将缓冲区通过mmap方式映射到用户空间。
d) 将帧缓冲区入队
申请好帧缓冲区之后,通过调用ioctl方法传入VIDIOC_QBUF命令来将帧缓冲区加入到v4l2 框架中的缓冲区队列中,静等硬件模块将图像数据填充到缓冲区中。
e) 开启数据流
将所有的缓冲区都加入队列中之后便可以调用ioctl并且传入VIDIOC_STREAMON命令,来通知整个框架开始进行数据传输,其中大致包括了通知各个子设备开始进行工作,最终将数据填充到V4L2框架中的缓冲区队列中。
f) 将帧缓冲区出队
一旦数据流开始进行流转了,我们就可以通过调用ioctl下发VIDIOC_DQBUF命令来获取帧缓冲区,并且将缓冲区的图像数据取出,进行预览、拍照或者录像的处理,处理完成之后,需要将此次缓冲区再次放入V4L2框架中的队列中等待下次的图像数据的填充。
整个采集图像数据的流程现在看来还是比较简单的,接口的控制逻辑很清晰,主要原因是为了提供给用户的接口简单而且抽象,这样方便用户进行集成开发,其中的大部分复杂的业务处理都被V4L2很好的封装了,接下来我们来详细了解下V4L2框架内部是如何表达以及如何运转的。
二、V4L2内部实现:
从上图不难看出,v4l2_device作为顶层管理者,一方面通过嵌入到一个video_device中,暴露video设备节点给用户空间进行控制,另一方面,video_device内部会创建一个media_entity作为在media controller中的抽象体,被加入到media_device中的entitie链表中,此外,为了保持对所从属子设备的控制,内部还维护了一个挂载了所有子设备的subdevs链表。
而对于其中每一个子设备而言,统一采用了v4l2_subdev结构体来进行描述,一方面通过嵌入到video_device,暴露v4l2_subdev子设备节点给用户空间进行控制,另一方面其内部也维护着在media controller中的对应的一个media_entity抽象体,而该抽象体也会链入到media_device中的entities链表中。
通过加入entities链表的方式,media_device保持了对所有的设备信息的查询和控制的能力,而该能力会通过media controller框架在用户空间创建meida设备节点,将这种能力暴露给用户进行控制。
由此可见,V4L2框架都是围绕着以上几个主要结构体来进行的,接下来我们依次简单介绍下:
v4l2_device 源码如下:
-
struct v4l2_device {
-
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);
-
};
该结构体代表了一个整个V4L2设备,作为整个V4L2的顶层管理者,内部通过一个链表管理着整个从属的所有的子设备,并且如果将整个框架放入media conntroller进行管理,便在初始化的时候需要将创建成功的media_device赋值给内部变量 mdev,这样便建立了于与media_device的联系,驱动通过调用v4l2_device_register方法和v4l2_device_unregister方法分别向系统注册和释放一个v4l2_device。
v4l2_subdev源码如下:
-
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 de