本文通过分析Zephyr已有的Video驱动,了解Video驱动实现原理。
本文着重分析Video驱动实现的模式,不说明实际硬件如何驱动。
Video buffer管理
内存定义
在Zephyr Video驱动之驱动模型一文中已经提到过,Video驱动提供统一的接口实现完成Video buffer的管理。这部分实现的代码在driver/video/video_common.c中
Video_common内管理的buffer有两部分,一部分是struct video_buffer,一部分是video buffer实际装载数据的Buffer。其中vide_buffer是以结构体数组的形式管理,如下:
1 | static struct video_buffer video_buf[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX]; |
CONFIG_VIDEO_BUFFER_POOL_NUM_MAX在driver/video/Kconfig中默认为2, 实际应用中我们要根据Video driver的caps中规定的最小min_vbuf_count来在prj.conf中对VIDEO_BUFFER_POOL_NUM_MAX进行配置
1 | config VIDEO_BUFFER_POOL_NUM_MAX |
再来看struct video_buffer的内容,其实际存放数据的地方是buffer
1 | struct video_buffer { |
因此在分配一个video_buffer的时候需要分配一片连续的内存给buffer指针,在video buffer管理中使用pool来进行分配,因此需要定义buffer用的pool
1 | K_MEM_POOL_DEFINE(video_buffer_pool, |
其中CONFIG_VIDEO_BUFFER_POOL_ALIGN之地的是从pool的对齐大小,默认是64字节。CONFIG_VIDEO_BUFFER_POOL_SZ_MAX指定的是Pool可分配最大的内存块大小,默认是1M,CONFIG_VIDEO_BUFFER_POOL_NUM_MAX指定的是Pool有多少个最大内存块,默认是2.所有的默认定义都在driver/video/Kconfig中。实际使用需要根据video driver的caps在prj.conf中配置,Pool的相关信息可参考Zephyr内存管理之Pool。
在实际使用中CONFIG_VIDEO_BUFFER_POOL_SZ_MAX可以配置为最大一张画幅占用内存量,CONFIG_VIDEO_BUFFER_POOL_NUM_MAX前面已经提到了和需要的Video buffer数量一致。CONFIG_VIDEO_BUFFER_POOL_ALIGN是由硬件决定,例如RT的CSI就要求64字节对齐。
由于从pool内分配出来的memory是以struct k_mem_block管理,每个struct video_buffer要配备一个k_mem_block
1 | static struct k_mem_block video_block[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX]; |
代码分析
有了前面的分析
1 | struct video_buffer *video_buffer_alloc(size_t size) |
接口说明
视频驱动实现下面接口后注册到Device即可,并不是所有接口都要实现,分为必须实现和可选实现,实际要根据驱动的硬件来决定
1 | struct video_driver_api { |
彩条生成器
彩条生成器是Video驱动下一个软件生成器,通过分析这个驱动的实现,可以比较清晰的理解如何实现一个Video驱动。
从Zephyr-Video驱动之驱动模型一文中我们已经知道要实现一个Video驱动只需要实现下面一组API即可
1 | static const struct video_driver_api video_sw_generator_driver_api = { |
这组API实现后被注册到Device,应用层就可以通过设备名”VIDEO_SW_GENERATOR”使用该驱动了
1 | DEVICE_AND_API_INIT(video_sw_generator, "VIDEO_SW_GENERATOR", |
本节内容含有fifo和work的使用,fifo在Zephyr内核对象-数据传递之FIFO-LIFO一文中可以找到详细说明,work本文中会附带简单说明如何工作。
设备初始化
无论哪种Video驱动都有fifo的初始化动作,再根据不同的驱动特性做一些特殊的初始化
设备初始化时初始化video_buffer queue和定期产生数据delay work
1 | static int video_sw_generator_init(const struct device *dev) |
设置通知信号
驱动是否要实现可选,无论哪种Video驱动基本都是一样的模式
通过video_sw_generator_set_signal将通知信号保存到driver内,当有数据事件发生时通知注册者
1 | static int video_sw_generator_set_signal(const struct device *dev, |
数据产生
驱动必须实现,数据产生的API在每个Video驱动都存在,但每种驱动都不一样,例如摄像头的数据产生是CMOS感光抓取,然后有硬件提供,而这里的彩条生成器是由软件生成。
在初始化的时候注册了一个delayed work, 当定时到后会调用__buffer_work函数。
video_sw_generator_stream_start会启动这个work而video_sw_generator_stream_stop停止这个work
1 | static int video_sw_generator_stream_start(const struct device *dev) |
在__buffer_work执行过程中会触发下一个33ms执行自己这个work,这样执行一次触发一次,每次间隔33ms,也就是30fps
1 | static void __buffer_work(struct k_work *work) |
Video buffer的流转
驱动是否要实现可选,无论哪种Video驱动基本都是一样的模式,根据ep进行处理
使用video_sw_generator_enqueue将空闲的video buffer加入到fifo_in中
1 | static int video_sw_generator_enqueue(const struct device *dev, |
使用video_sw_generator_dequeue从fifo_out取出带有数据的Video buffer
1 | static int video_sw_generator_dequeue(const struct device *dev, |
使用video_sw_generator_flush清空fifo,将所有传入的空闲buffer都转移到fifo_out中,该动作是要取消视频功能的前奏,让用户可以通过video_sw_generator_dequeue拿到所有的buffer。
1 | static int video_sw_generator_flush(const struct device *dev, |
格式和能力
驱动必须实现,不同的驱动会有不同的实现方法。
彩条生成器提供video_sw_generator_set_fmt和video_sw_generator_get_fmt,在彩条生成器中set只是简单的保存格式到驱动内,获取就是将保存的格式送出,这里就不列出代码。实际格式生效的地方是在__fill_buffer_colorbar内填充的时候根据格式内容进行填充:
1 | static void __fill_buffer_colorbar(struct video_sw_generator_data *data, |
彩条生成器能力完全是由软件决定,被预设如下video_sw_generator_get_caps会直接将其送出,具体代码很简单就不再列出了
1 | static const struct video_format_cap fmts[] = { |
控制
驱动是否要实现可选,不同的视频驱动有不同的控制内容,需要不同的实现方法
彩条生成器只实现了设置视频翻转,是通用类别中的一种,代码非常简单,就是将释放翻转保存在驱动内,实际在软件填充彩条的时候
1 | static inline int video_sw_generator_set_ctrl(const struct device *dev, |
摄像头驱动
在一文中已经提到过Zephyr目前实现的是RT CSI + MT9M114 Sensor的模式,大体和彩条生成器的情况一样,这里主要分析不同的地方
Sensor
Sensor被抽象为一个只控制不提供数据的Video设备,MT9M114进行如下实现
1 | static const struct video_driver_api mt9m114_driver_api = { |
mt9m114_set_fmt是对实际的硬件操作,同I2C将格式配置给mt9m114,而mt9m114_get_fmt是将设置格式送出。mt9m114_stream_start和mt9m114_stream_stop也是写响应的寄存器让mt9m114启动或者停止工作。相关的细节代码不在本文介绍范围内。
mt9m114_get_caps是将mt9m114支持的format提供出去,这取决于mt9m114的硬件特性和驱动的实现程度,目前只提供了下面一种格式
1 | static const struct video_format_cap fmts[] = { |
由于mt9m114本身的数据需要通过CSI来读取,所以Sensor驱动无法直接提供数据,因此没有实现数据流转相关的API,而Sensor也作为CSI视频驱动的插件被使用。
mt9m114硬件本身是支持控制的,但由于驱动实现的程度不足,因此代码中并未去实现set_ctrl/get_ctrl
CSI
CSI是实现了比较完整视频设备驱动:
1 | static const struct video_driver_api video_mcux_csi_driver_api = { |
CSI主要是完成对数据的搬移处理,因此在视频设备控制上是直接使用Sensor的,我们可以理解为Sensor+CSI才构成一个完整的CSI视频设备
在CSI初始化时,会初始化ep的fifo,同时获取sensor的binding
1 | static int video_mcux_csi_init(const struct device *dev) |
在video_mcux_csi_set_fmt的时候会根据格式对CSI的硬件配置,再通过视频驱动配置sensor
1 | static int video_mcux_csi_set_fmt(const struct device *dev, |
video_mcux_csi_get_fmt或以sensor的格式为主重新改写CSI的格式,如果没有sensor的格式才返回CSI的格式
1 | static int video_mcux_csi_get_fmt(const struct device *dev, |
video_mcux_csi_stream_start和video_mcux_csi_stream_stop也是同时要对CSI和sensor进行操作,代码如下
1 | static int video_mcux_csi_stream_start(const struct device *dev) |
数据流转上video_mcux_csi_enqueue/video_mcux_csi_dequeue/video_mcux_csi_flush的操作除了和彩条生成器一样的处理fifo外,还会操控CSI硬件这里不再贴出代码,可以直接去代码中看。
video_mcux_csi_set_ctrl/video_mcux_csi_get_ctrl/video_mcux_csi_get_caps 是直接包装对sensor_dev驱动的使用,可以直接去代码中看。
对于CSI的数据处理其它没写出的部分都是和操作CSI硬件,涉及到NXP HAL API的使用以及中断的处理,和驱动实现的模式精密度不高,这里就不做详细说明了,这里附件说明下和数据信号通知相关的内容。
video_mcux_csi_set_signal设置通知信号和彩条生成器的写法一样,在通知的时候有下面几种情况:
- CSI收完一帧数据在终端中回调__frame_done_cb,该函数会发送VIDEO_BUF_DONE信号通知应用取数据。
- 在CSI中断发生时回调__frame_done_cb,如果接收到的数据是错误的会发送VIDEO_BUF_ERROR信号通知应用。
- Flush的时候使用VIDEO_BUF_ABORTED通知数据已经ABORTE,这和彩条生成器一致。