V4L2子系统

1       V4L2架构简述

V4L2Linux社区定义的Linux内核的多媒体框架, 本质上来说它就是一个字符设备, 然后社区定义了一系列标准的ioctl来与内核交互.

1.1    框图

 

首先注意框图的实线部分, 对应的是只需要驱动片上外设的情景, 例如mtkvdec, 或者atmellcd overlay. 这种情况下struct video_device做为字符设备驱动响应用户空间的ioctl. 在注册struct video_device, 必须创建一个struct v4l2_device(虽然这种情况下它没什么卵用).

这种情形在《video_device》一节中细述.

 

其次注意框图的实线+虚线部分, 对应的是需要驱动片上外设+外接硬件的情景, 典型的如Camera, 片上外设是Camera控制器(例如atmel-isc.c), 外接硬件是sensor(例如ov2640.c). 这种情况下还是用struct video_device来抽象片上外设, 但同时会新增struct v4l2_subdev来抽象外接硬件. 用户空间的ioctl先传递至片上外设对应的驱动代码, 然后片上外设代码通过v4l2_subdev_call调用v4l2_subdev提供的相关接口操作外接硬件.

这种情形下,一个片上外设可能有多个外接硬件, 每个外接硬件都对应一个v4l2_subdev, 所有的subdev都链接在同一个v4l2_device. 通过v4l2_device, 我们可以遍历所有的sbudev. v4l2_device此时相当于所以subdev的父设备.

v4l2_subdev》一节主要描述这种情形.

 

还有一种不太常见的情形, 例如收音机, 这种情形下片上外设很简单(就是I2C), 我们主要是想驱动外接硬件radio. 此时就不需要针对片上外设创建字符设备了(也就是不需要注册针对片上外设的video_device), 我们可以直接把subdev以字符设备的形式暴露给用户空间, 用户空间在ioctl时直接发给subdev处理.

v4l2_subdev作为字符设备一节描述这种情形.

1.2    关系图

2       video_device

2.1    核心数据结构

2.1.1      struct video_device

头文件 : include/media/v4l2-dev.h

V4L2子系统注册一个video_device意味着用户空间在/dev/下会多出一个字符设备节点. 在注册前, 需要准备好如下数据结构, 这样一旦注册成功, 底层驱动就可以响应用户空间请求了.

 

需要准备的数据结构包括:

struct v4l2_file_operations *fops

头文件 : include/media/v4l2-dev.h

这个数据结构之于video_device的作用与struct file_operations之于cdev的作用一模一样. 主要目的是响应用户空间的open/read/write/ioctl/close等请求.

struct v4l2_ioctl_ops *ioctl_ops

头文件 : include/media/v4l2-ioctl.h

用户空间的ioctl请求最终会转发到这里来处理.

用户空间能发送哪些ioctl是由V4L2标准规定的, 因此这里的v4l2_ioctl_ops需要实现的接口函数也是与v4l2标准一一对应的. 具体可参考头文件定义.

struct list_headfh_list

设备节点每被打开一次, V4L2核心层就会生成一个struct v4l2_fh(详见后文介绍)与之对应. 因此一个video_device下可能会有多个v4l2_fh. 这里的链表头fh_list用于挂载所有隶属于本video_devicev4l2_fh.

struct v4l2_ctrl_handler *ctrl_handler

头文件 : include/media/v4l2-ctrls.h

详见《v4l2 control related

struct v4l2_prio_state *prio

头文件 : include/media/v4l2-dev.h

作用暂不知.

struct vb2_queue *queue

v4l2_ioctl_ops主要提供控制接口, 而这里的vb2_queue则主要提供数据接口. 它与memory的管理密切相关, memory管理在V4L2中是一个比较庞大的框架, 因此在《1.5 内存管理与访问》中专门介绍.

2.1.2      struct v4l2_fh

头文件 : include/media/v4l2-fh.h

设备节点每被打开一次, V4L2核心层就会生成一个struct v4l2_fh.

 

这个数据结构的意义:

一方面是存储runtime相关的一些信息, 例如它可以存储当前被打开的这个实例有多少event需要被dequeued, 当前这个实例的优先级.

另一方面, 它有点类似C++中的派生类, 可以override struct video_device 中的一些信息, 例如两者都定义了struct v4l2_ctrl_handler, 系统优先使用v4l2_fh -> v4l2_ctrl_handler.

2.1.3      v4l2 control related

V4L2架构中, 用户空间与内核主要是通过ioctl交互的. V4L2标准定义了交互的协议.

 

针对下表所列的几种CTRL类型的ioctl (这些ioctl主要是与硬件相关的一些设置, 例如亮度、饱和度、对比度和清晰度等), V4L2内核代码实现了一套框架, 称之为v4l2 control.

IOCTL

Func

VIDIOC_QUERYCTRL

v4l_queryctrl

VIDIOC_QUERY_EXT_CTRL

v4l_query_ext_ctrl

VIDIOC_QUERYMENU

v4l_querymenu

VIDIOC_G_CTRL

v4l_g_ctrl

VIDIOC_S_CTRL

v4l_s_ctrl

VIDIOC_G_EXT_CTRLS

v4l_g_ext_ctrls

VIDIOC_S_EXT_CTRLS

v4l_s_ext_ctrls

VIDIOC_TRY_EXT_CTRLS

v4l_try_ext_ctrls

 

当然你也可以选择不使用这套框架, 这样使用的就是原来的v4l2_ioctl_ops框架. 这块的处理代码典型的如下:

         如果使用v4l2 control框架, 则优先调用v4l2_fh ->ctrl_handler; 其次是video_device ->ctrl_handler.

         否则, 使用v4l2_ioctl_ops框架, 调用底层驱动实现的vidioc_xxx函数.

 

v4l2 control框架中, 内核系统为我们处理了很多公共的逻辑, 这样需要我们自己编写的代码就很简单了. 因此推荐优先使用此框架.

 

v4l2 control框架相关的几个核心数据结构如下:

头文件 : include/media/v4l2-ctrls.h

struct v4l2_ctrl

struct v4l2_ctrl_ops

v4l2_ctrl代表一个属性(属性名 / 取值范围、默认值、当前值).

v4l2_ctrl_ops代表此属性的操作方法(获取属性值/尝试设置属性值/真正设置属性值).

 

struct v4l2_ctrl_handler

相当于一个链表, 挂载所有的v4l2_ctrls.

 

struct video_device中有一个指针指向这个链表, 它的意义是指在向系统注册video_device时准备好这样一条链表, 注册完毕后用户空间就可以直接使用v4l2 control相关功能了.

 

struct v4l2_fh中也有一个指针指向这个链表, 当用户空间open设备节点时, 这个指针会默认指向video_device ->v4l2_ctrl_handler. 不过内核驱动可以随后准备一条新的链表, 然后让v4l2_fh ->v4l2_ctrl_handler指向这条链表. 此即所谓override.

 

那到底该如何构建这样一条链表呢? 详见《v4l2 control APIs.

2.2    APIs

2.2.1      video_device APIs

struct video_device *video_device_alloc(void)

分配video_device存储空间.

static inline int __must_check video_register_device(struct video_device *vdev, …)

向核心层注册一个video_device, 此时核心层会创建字符设备驱动. 后面用户空间就可以通过字符设备节点与video_device交互了.

 

核心层在注册字符设备时, 给定的opsv4l2_fops, 它会把来至用户空间的请求转发给video_device->v4l2_file_operations.

2.2.2      v4l2 control APIs

头文件 : include/media/v4l2-ctrls.h

实现文件 : drivers/media/v4l2-core/v4l2-ctrls.c

v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned int nr_of_controls_hint)

初始化v4l2_ctrl_handler链表, @nr_of_controls_hint代表链表的最大容量

void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl)

清除v4l2_ctrl_handler, 释放相关资源

struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s64 min, s64 max, u64 step, s64 def)

向链表中新增一个非菜单式的控制变量. @min是最小值, @max是最大值, @step是步进长度, @def是默认值.

这个函数适用于在某一范围内均匀变化的控制变量, 例如声音.

struct v4l2_ctrl *v4l2_ctrl_new_std_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u64 mask, u8 def)

向链表中新增一个菜单式控制变量. 与上一个API类似, @max是最大值, @def是默认值. 区别在于@min is set to 0 and the @mask value determines which menu items are to be skipped.

这个函数适用于从0开始, 步进为1的控制变量.

struct v4l2_ctrl *v4l2_ctrl_new_int_menu(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u8 def, const s64 *qmenu_int)

与上一个API类似, 不过有显著区别, 首先@qmenu_int是一个数组, 里面存储着这个菜单所有的可选值; 其次@max不是代表可取的最大值, 而是代表数组索引的最大值. @def则是代表默认的索引值.

这个函数适用于变量值是不连续的无规则的整数的控制变量.

struct v4l2_ctrl *v4l2_ctrl_new_std_menu_items(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, u8 max, u64 mask, u8 def, const char * const *qmenu)

与上一个API很类似, 区别在于@qmenu不是整形数组, 而是字符串数组. 另外它还多了一个@mask, 代表数组中的哪些itemskip.

int v4l2_ctrl_add_handler(struct v4l2_ctrl_handler *hdl, struct v4l2_ctrl_handler *add, v4l2_ctrl_filter filter)

@add中所有的控制变量都添加到@hdl.

int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl)

API的调用时可选的. 每个控制变量都是自己的默认值, API的目的是把所有控制变量的默认值设置到硬件里面.

 

了解了这些API, 在来看如何构造一个v4l2_ctrl_handler链表就很简单了. 这里不赘述了.

2.3    ioctl

用户空间对V4L2设备的操作基本都是ioctl来实现的, ioctl框架是由v4l2_ioctl.c文件实现, 文件中定义结构体数组v4l2_ioctls, 可以看做是ioctl指令和回调函数的关系表. 用户空间调用系统调用ioctl, 传递下来ioctl指令, 然后通过查找此关系表找到对应回调函数.

2.4    Demo

Ref mtkvdec.

3       v4l2_subdev

subdev其实不算V4L2框架的一部分, 可能是因为它是在V4L2出现后才新增的. 它定义了一套自己的接口函数struct v4l2_subdev_ops, 所有的subdev驱动都要实现这套函数.

不过这套函数与V4L2定义的ioctl标准是基本兼容的, 我们可以把用户空间的标准v4l2 ioctl调用映射到v4l2_subdev_ops, 例如subdev_do_ioctl就做了这种映射.

3.1    核心数据结构

3.1.1      struct v4l2_device

头文件 : include/media/v4l2-device.h

 

v4l2_device中有一个元素是struct kref ref , 因此它的第一个功能是可作为引用计数.

另外, 如果系统中存在v4l2_subdev, v4l2_device充当所有v4l2_subdev的父设备, 管理着注册在其下的子设备.

3.1.2      struct v4l2_subdev

头文件 : include/media/v4l2-subdev.h

 

struct v4l2_subdev用于抽象一个子设备(外接硬件). 它会挂载到v4l2_device链表下.

除了这个结构体外, 我们还需要一些ops用于描述如何操作硬件, 它们是:

struct v4l2_subdev_ops

struct v4l2_subdev_core_ops
struct v4l2_subdev_tuner_ops
struct v4l2_subdev_audio_ops
struct v4l2_subdev_video_ops
struct v4l2_subdev_vbi_ops
struct v4l2_subdev_ir_ops
struct v4l2_subdev_sensor_ops
struct v4l2_subdev_pad_ops

v4l2_subdev_ops及相关的几个ops, 主要是用来描述如何操作硬件的, 比如设置寄存器等.

struct v4l2_subdev_internal_ops

v4l2_subdev_internal_ops提供了4个接口: registeredunregisteredopenclose. 4个接口是给v4l2核心层代码回调用的. 当向核心层注册/注销subdev, 核心层会回调这里的registered/unregistered. 当用户空间打开/关闭设备节点时, 核心层会回调这里的open/close.

3.1.3      v4l2 async related

头文件 : include/media/v4l2-async.h

 

async机制有点类似于设备模型中devicedriver的匹配过程.

 

camera为例, 硬件组成为片上camera控制器和外接sensor. 在控制器相关的代码里面可以通过async机制等待外接sensor初始化, sensor初始化代码被调用时, 会通过async通知控制器代码, 然后控制器代码就可以开始创建设备节点(也就是注册video_device设备).

 

从代码细节上看, 控制器代码首先要定义一个或多个v4l2_async_subdev, 每个代表控制器想与哪个sensor进行匹配, 多个就组成了一个匹配列表. 然后控制器代码需要实现v4l2_async_notifier_operations中定义回调函数. 最后控制器代码把这两者一起封装成一个v4l2_async_notifier.

struct v4l2_async_subdev

struct v4l2_async_notifier_operations

struct v4l2_async_notifier

 

数据结构准备好后, 控制器代码就可调用v4l2_async_notifier_register把这个notifier注册到系统.

 

subdev初始化时, 它会调用v4l2_async_notifier_register, 例如ov2640.c, 该函数会扫描notifier链表, 并进行匹配(匹配规则详见v4l2_async_find_match). 当匹配成功后, 会调用v4l2_device_register_subdev(v4l2_dev, sd)subdevv4l2_dev关联起来. 最后会调用notifier中定义的complete回调函数, 主控器代码在该函数中会注册video_device设备. 最终, 用户空间就会出现设备节点了.

 

之后, 当用户空间通过ioctl操作设备节点时, 主控器代码会调用v4l2_subdev_call(sd, o, f, args...)来调用subdev中定义的ops函数, 从而实现对subdev的控制.

3.2    APIs

3.2.1      v4l2_device APIs

头文件 : include/media/ v4l2-device.h

实现文件 : drivers/media/v4l2-core/v4l2-device.c

 

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)

调用者自行分配一个v4l2_device空间, 然后调用此API对新分配的空间进行初始化.

void v4l2_device_unregister(struct v4l2_device *v4l2_dev)

注销v4l2_device以及它下面挂载的所有v4l2_subdev.

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd)

v4l2_device注册一个subdev.

void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)

注销一个subdev: 将其从v4l2_device下移除, 如果此subdev已经创建了设备节点, 则调用video_unregister_device注销节点.

static inline void v4l2_device_get(struct v4l2_device *v4l2_dev)

增加引用计数.

int v4l2_device_put(struct v4l2_device *v4l2_dev)

减少引用计数.

static inline void v4l2_subdev_notify(struct v4l2_subdev *sd, unsigned int notification, void *arg)

subdev可通过此API通知v4l2_device有事件产生了.

3.2.2      v4l2_subdev APIs

subdev的注册/注销APIv4l2_device_register_subdev/ v4l2_device_unregister_subdev. 详见《v4l2_devices APIs》一节的描述.

 

另外, v4l2_subdev自身还定义了如下API:

头文件 : include/media/v4l2-subdev.h

实现文件 : drivers/media/v4l2-core/v4l2-subdev.c

void v4l2_subdev_init(struct v4l2_subdev *sd, const struct v4l2_subdev_ops *ops)

初始化v4l2_subdev空间.

#define v4l2_subdev_call(sd, o, f, args...)

调用v4l2_subdev_xxx_ops中定义的函数.

#define v4l2_subdev_has_op(sd, o, f)

检查v4l2_subdev_xxx_ops是否定义了某个函数.

void v4l2_subdev_notify_event(struct v4l2_subdev *sd, const struct v4l2_event *ev)

用户空间可以通过ioctl VIDIOC_DQEVENT来等待某个v4l2_event, 此时v4l2核心层代码会调用v4l2_event_dequeue, block并等待事件产生.

subdev检测到event产生时(此时一般会有中断发生), subdev中断处理函数会调用v4l2_subdev_notify_eventv4l2_subdev_notify_event里面会做两件事:

          一是调用v4l2_event_queue, 此时会唤醒用户空间的block, 并把事件传送给用户空间.

          二是调用v4l2_subdev_notify(详见《v4l2 device APIs》), 通知v4l2_device有事件产生了.

3.2.3      v4l2 async APIs

头文件 : include/media/v4l2-async.h

实现文件 : drivers/media/v4l2-core/v4l2-async.c

int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev, struct v4l2_async_notifier *notifier)

片上控制器相关代码调用此API注册一个notifier.

void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)

片上控制器相关代码调用此API注销一个notifier.

int v4l2_async_register_subdev(struct v4l2_subdev *sd)

外接硬件相关代码调用此API向系统注册一个v4l2_subdev, 同时也会触发一次与notifier的匹配过程, 如果匹配上了则调用v4l2_async_notifier_operations中定义的回调函数.

void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)

外接硬件相关代码调用此API注销一个v4l2_subdev.

3.3    Demo

Ref Atmel Camera controller code : atmel-isc.c.

4       v4l2_subdev 作为字符设备

int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)

前文说过, 用户空间是通过设备节点与内核交互的.

 

每个subdev都代表一个外接硬件, 这外接硬件可能是接在某个片上控制器上, 比如camera, 这种情况下我们需要针对这个控制器创建一个设备节点, 然后在控制器的代码中通过v4l2_subdev_call操作外接硬件.

 

除此之外, 这个外接硬件也可能直接接在I2CSPI这些简单的片上外设上, 比如radio, 这种情况下就没有必要针对这些简单的外设创建设备节点, 而是直接针对外接硬件创建设备节点.

当然, 在片上外设+外接硬件的设计中, 也可以针对外接硬件创建设备节点. 这样既可以通过控制器代码间接操作外接硬件, 也可以通过subdev设备节点之间操作外接硬件.

 

API的作用就是针对v4l2_dev下挂载的所有v4l2_subdev, 如果subdev.flags设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志, 则为该subdev创建一个设备节点(设备节点的创建过程其实就是注册video_device.

 

subdev_do_ioctlv4l2规定的ioctl转换成v4l2_subdev定义了v4l2_subdev_ops (subdev定义了几组(core/tuner/audio/video/…, 每组都有各自的function ).

5       内存管理与访问

5.1    概述

前面几小节介绍了很多关于控制方面的结构体和API, 本节主要介绍数据方面的内容.

 

所谓数据, 其实就是与内存相关的问题. 最终的目的是使得内核与用户空间可方便的交换数据. V4L2支持三种不同的数据交换方式:

         readwrite 借助字符设备的read/write接口, 这种方式数据需要在内核和用户之间拷贝, 访问速度会比较慢. 涉及到大量的数据交换时, 不要用这种方式, 会影响性能.

         内存映射缓冲区(V4L2_MEMORY_MMAP) : 是在内核空间开辟缓冲区,应用通过mmap()系统调用映射到用户地址空间。这些缓冲区可以是大而连续DMA缓冲区、通过vmalloc()创建的虚拟缓冲区,或者直接在设备的IO内存中开辟的缓冲区(如果硬件支持).

         用户空间缓冲区(V4L2_MEMORY_USERPTR) : 用户空间中开辟缓冲区,用户与内核空间之间交换缓冲区指针。很明显,在这种情况下是不需要mmap()调用的,但驱动需要额外的设计, 以便使之可访问用户空间内存.

 

Readwrite方式属于帧IO访问方式, 每一帧都需要用户和内核之间数据拷贝; 而后两种是流IO访问方式, 不需要内存拷贝, 访问速度比较快.

内存映射缓冲区访问方式是比较常用的方式.

 

Videobuf管理: videobuf2-core.cvideobuf2-dma-contig.cvideobuf2-dma-sg.cvideobuf2-memops.cvideobuf2-vmalloc.cv4l2-mem2mem.c等文件实现, 完成videobuffer的分配、管理和注销.

5.2    核心数据结构

struct vb2_queue

为了使设备支持流IO这种方式, 驱动需要实现struct vb2_queue, 结构体细节如下:

 

头文件 : include/media/videobuf2-core.h

Struct vb2_queue

Comment

unsigned int type

private buffer type whose content is defined by the vb2-core caller. For example, for V4L2, it should match the types defined on @enum v4l2_buf_type

unsigned int io_modes

访问IO的方式:mmapuserptretc. 详见enum vb2_io_modes

 

const struct vb2_ops*ops

针对队列的操作函数集合, 例如入队列、出队列等.

const struct vb2_mem_ops*mem_ops

针对mem的操作函数集合, 例如内存分配与释放等.

const struct vb2_buf_ops*buf_ops

callbacks to deliver buffer information between user-space and kernel-space.

 

struct vb2_buffer *bufs[VB2_MAX_FRAME]

每个vb2_buffer代表一块内存, 这是存储池

unsigned int  num_buffers

池子里面实际有多少个buf

 

vb2_queue代表一个videobuffer队列, vb2_buffer是这个队列中的基本单位, vb2_mem_ops内存的操作函数集, vb2_ops用来管理队列.

struct vb2_buffer

一个vb2_buffer代表内核空间的一块videobuffer. 一个vb2_queue中会有多个这样的buffer.

 

头文件 : include/media/videobuf2-core.h

Struct vb2_buffer

Comment

struct vb2_queue *vb2_queue

指向该buf所隶属的vb2_queue

unsigned int memory

the method, in which the actual data is passed

 

enum vb2_buffer_state state

一个buffer的多种状态

一块buffer可能有多种状态, 常见的情形如下:

          在驱动的传入队列中VB2_BUF_STATE_QUEUED: 驱动程序将会对此队列中的缓冲区进行处理, 用户空间通过IOCTL:VIDIOC_QBUF把缓冲区放入到队列. 对于一个视频捕获设备, 传入队列中的缓冲区是空的, 驱动会往其中填充数据.

          在驱动的传出队列中VB2_BUF_STATE_DONE: 这些缓冲区已由驱动处理过,对于一个视频捕获设备,缓存区已经填充了视频数据,正等用户空间来认领

          用户空间状态的队列VB2_BUF_STATE_DEQUEUED : 已经通过IOCTL:VIDIOC_DQBUF传出到用户空间的缓冲区, 此时缓冲区由用户空间拥有, 驱动无法访问.

 

这三种状态的切换如下图所示:

struct v4l2_buffer

一个v4l2_buffer在用户空间代表一块videobuf, 它与vb2_buffer一一对应. 不过需要注意的是, vb2_buffer里面包含了实际的存储空间; v4l2_buffer只是包含一些buffer info, 通过这些info, 用户空间可以用mmap或其它方式拿到实际的存储地址, 从而避免在用户空间与内核之间做数据拷贝.

 

头文件 : include/uapi/linux/videodev2.h

Struct v4l2_buffer

Comment

__u32index

buffer序号

__u32type

buffer类型, @enum v4l2_buf_type

__u32bytesused

缓冲区已使用byte

__u32    flags

 

__u32    field

 

struct timeval timestamp

时间戳,代表帧捕获的时间

struct v4l2_timecode  timecode

 

__u32    sequence

 

/*memory location */

__u32  memory

表示缓冲区是内存映射缓冲区还是用户空间缓冲区

union {

    __u32         offset;

    unsigned long   userptr;

    struct v4l2_plane *planes;

    __s32       fd;

} m;

 

//offset代表内核缓冲区的位置

//userptr代表缓冲区的用户空间地址

__u32   length

缓冲区大小, 单位byte

当用户空间拿到v4l2_buffer, 可以获取到缓冲区的相关信息. Byteused是图像数据所占的字节数, 如果是V4L2_MEMORY_MMAP方式, m.offset是内核空间图像数据存放的开始地址, 会传递给mmap函数作为一个偏移, 通过mmap映射返回一个缓冲区指针p, p+byteused是图像数据在进程的虚拟地址空间所占区域; 如果是用户指针缓冲区的方式, 可以获取的图像数据开始地址的指针m.userptr, userptr是一个用户空间的指针, userptr+byteused便是所占的虚拟地址空间, 应用可以直接访问.

struct vb2_mem_ops

vb2_mem_ops包含了内存映射缓冲区、用户空间缓冲区的内存操作方法:

 

头文件 : include/media/videobuf2-core.h

Struct vb2_mem_ops

Comment

void      *(*alloc)(……)

分配视频缓存

void      (*put)(void *buf_priv)

释放视频缓存

void      *(*get_userptr)(……)

获取用户空间视频缓冲区指针

void       (*put_userptr)(void *buf_priv)

释放用户空间视频缓冲区指针

void       (*prepare)(void *buf_priv)

void      (*finish)(void *buf_priv)

void       *(*vaddr)(void *buf_priv)

void       *(*cookie)(void *buf_priv)

用于缓存同步

unsigned int (*num_users)(void *buf_priv)

返回当期在用户空间的buffer

int (*mmap)(void *buf_priv, struct vm_area_struct *vma)

把缓冲区映射到用户空间

这是一个相当庞大的结构体, 这么多的结构体需要实现还不得累死, 幸运的是内核都已经帮我们实现了. 提供了三种类型的视频缓存区操作方法: 连续的DMA缓冲区、集散的DMA缓冲区以及vmalloc创建的缓冲区, 分别由videobuf2-dma-contig.cvideobuf2-dma-sg.cvideobuf-vmalloc.c文件实现, 可以根据实际情况来使用.

struct vb2_ops

vb2_ops是用来管理buffer队列的函数集合, 包括队列和缓冲区初始化

 

头文件 : include/media/videobuf2-core.h

Struct vb2_ops

Comment

int(*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt, unsigned int *num_buffers, unsigned int*num_planes, unsigned int sizes[], void *alloc_ctxs[]);

队列初始化

void (*wait_prepare)(struct vb2_queue *q)

void (*wait_finish)(struct vb2_queue *q)

释放和获取设备操作锁

int (*buf_init)(struct vb2_buffer *vb)

int (*buf_prepare)(struct vb2_buffer *vb)

void (*buf_finish)(struct vb2_buffer *vb)

void (*buf_cleanup)(struct vb2_buffer *vb)

buffer的操作

int (*start_streaming)(struct vb2_queue *q, unsigned int count)

开始视频流

int  (*stop_streaming)(struct vb2_queue *q)

停止视频流

void(*buf_queue)(struct vb2_buffer *vb)

VBv4l2核心层传递给驱动

6       Demo : 用户空间访问设备

下面通过内核映射缓冲区方式访问视频设备(CaptureDevice)的流程.

1>      打开设备文件

fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);

dev_name[/dev/videoX]

2>      查询设备支持的能力

struct v4l2_capability  cap;

ioctl(fd, VIDIOC_QUERYCAP, &cap)

3>      设置视频捕获格式

fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.width       = 640;

fmt.fmt.pix.height      = 480;

fmt.fmt.pix.pixelformat= V4L2_PIX_FMT_YUYV;  //像素格式

fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

 

ioctl(fd,VIDIOC_S_FMT, & fmt)

4>      向驱动申请缓冲区

struct  v4l2_requestbuffers req;

req.count= 4;  //缓冲个数

req.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

req.memory= V4L2_MEMORY_MMAP;

 

if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req))

5>      获取每个缓冲区的信息,映射到用户空间

struct buffer {

    void  *start;

    size_t length;

} *buffers;

 

buffers = calloc(req.count, sizeof(*buffers));

for (n_buffers= 0; n_buffers < req.count; ++n_buffers) {

    struct  v4l2_buffer buf;

    buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory      = V4L2_MEMORY_MMAP;

    buf.index       = n_buffers;

    if (-1 ==ioctl(fd, VIDIOC_QUERYBUF, & buf))

        errno_exit("VIDIOC_QUERYBUF");

 

    buffers[n_buffers].length= buf.length;

    buffers[n_buffers].start= mmap(NULL /* start anywhere */,

        buf.length,

        PROT_READ | PROT_WRITE /* required */,

        MAP_SHARED /* recommended */,

        fd, buf.m.offset);

}

6>      把缓冲区放入到传入队列上, 打开流IO, 开始视频采集

for (i =0; i < n_buffers; ++i) {

    struct v4l2_buffer buf;

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    buf.index = i;

 

    if (-1 == ioctl(fd, VIDIOC_QBUF, &buf))

        errno_exit("VIDIOC_QBUF");

}

 

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

if (-1 == ioctl(fd, VIDIOC_STREAMON, & type))

7>      调用select监测文件描述符, 缓冲区的数据是否填充好, 然后对视频数据

for (;;) {

    fd_set fds;

    struct timeval tv;

    int r;

    FD_ZERO(&fds);

    FD_SET(fd,&fds);

    /* Timeout. */

    tv.tv_sec = 2;

    tv.tv_usec = 0;

 

    //监测文件描述是否变化

    r = select(fd + 1,& fds, NULL, NULL, & tv);

    if (-1 == r) {

        if (EINTR == errno)

            continue;

        errno_exit("select");

    }

 

    if (0 == r) {

        fprintf(stderr,"select timeout\n");

        exit(EXIT_FAILURE);

    }

 

    //对视频数据进行处理

    if (read_frame())

        break;

    /* EAGAIN - continueselect loop. */

}

8>      出已经填充好的缓冲, 获取到视频数据的大小, 然后对数据进行处理. 这里取出的缓冲只包含缓冲区的信息, 并没有进行视频数据拷贝

buf.type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory= V4L2_MEMORY_MMAP;

 

if (-1 == ioctl(fd, VIDIOC_DQBUF, & buf))    //取出缓冲

    errno_exit("VIDIOC_QBUF");

 

process_image(buffers[buf.index].start, buf.bytesused);   //视频数据处理

 

if (-1 == ioctl(fd, VIDIOC_QBUF, & buf))  //然后又放入到传入队列

    errno_exit("VIDIOC_QBUF");

9>      停止视频采集

type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

ioctl(fd, VIDIOC_STREAMOff, & type);

10>   关闭设备

close(fd);

7       Soc Camera

当前的代码中, soc_camera貌似没什么代码在使用了, 好像kernel正在逐步移除它. Camera相关的场景现在都是之间使用的v4l2框架.

后续项目中如果遇到使用soc_camera的情况在来细究.

posted @ 2020-12-13 17:38  johnliuxin  阅读(2558)  评论(3编辑  收藏  举报