V4L2框架-media device
阅读原文
本文对 V4L2 的运行时数据流设备管理做一个详细的介绍,包括什么叫「运行时设备管理」,它是干什么用的,怎么使用等等。本文的目标是掌握 media device 的编码使用方法以及功能运用。
01 - V4L2框架-v4l2 device
00 - V4L2框架概述
media framework
简介
相关的控制 API 在Documentation/DocBook/media/v4l/media-controller.xml
,本文档聚焦于内核测的media框架实现。注意:直接查看是看不出啥的,在内核的根目录下make htmldocs
或者其它格式的都行,易于查看。运行时设备控制
也就是设备启动之后的数据流线路控制,就像一个工厂流水线一样,流水线上面的一个个节点(贴商标、喷丝印、打包)就形同于输入设备中的一个个子设备,运行时设备控制就是要达到能够控制节点的效果,比如贴商标的机器有好几台,应该选择哪一台进行此次流水线处理,要不要把喷丝印加上去,加哪一个机子等等。作用
提供实时的 pipeline 管理,pipeline 就理解为管道,想象一下水管,里面的水就是数据流,输入设备中的 csi->isp->video 就组成了一个 pipeline 线路。media framework 提供 pipeline 的开启、关停、效果控制、节点控制等功能。如何使用
内核当中主要利用四个结构体把众多的节点组织起来:media_device
,media_entity
,media_link
,media_pad
。整个 media framework 都是围绕这四个结构体来进行使用的,下文会对这些进行详细介绍。抽象设备模型
media framework 其中一个目的是:在运行时状态下发现设备拓扑并对其进行配置。为了达到这个目的,media framework将硬件设备抽象为一个个的entity,它们之间通过links连接。
- entity:硬件设备模块抽象(类比电路板上面的各个元器件、芯片)
- pad:硬件设备端口抽象(类比元器件、芯片上面的管脚)
- link:硬件设备的连线抽象,link的两端是pad(类比元器件管脚之间的连线)
#------------# #------------#
| __|__ __|__ |
| | | | link | | | |
| | pad |<-------->| pad | |
| |__|__| |__|__| |
| | | |
| entity | | entity |
#------------# #------------#
可以想象一下,如果各个 entity 之间需要建立连接的话,就需要在 pad 中存储 link 以及 entity 信息,link 中需要存储 pad 与 entity 信息,entity 里面需要存储 link 与 pad 信息,属于你中有我,我中有你的情况。
media 设备
一个 media 设备用一个 media_device
结构体来表示,通常情况下该结构体要嵌入到一个更大的设备自定义的结构体里面,并且大多数时候 media_device
与 v4l2_device
是处于并列的级别,还是以 omap3isp 的代码为例:
struct isp_device {struct v4l2_device v4l2_dev;struct v4l2_async_notifier notifier;struct media_device media_dev;struct device *dev;u32 revision;... ...
}
使用以下函数进行 meida 设备的注册:media_device_register(struct media_device *mdev);
函数的调用者需要在注册之前设置以下结构体成员(提前初始化该结构体是调用者的责任):
- dev:必须指向一个父设备,通常是平台设备的device成员。
- model:模型名字。
以下的成员是可选的: - serial:序列号,必须是唯一的
- bus_info:总线信息,如果是PCI设备的话就可以设置成"PCI:"
- hw_revision:硬件版本。可以的话,应该用KERNEL_VERSION宏定义进行格式化
- driver_version:驱动版本。最终生成的设备节点的名称是media[0-9],节点号由内核自动生成。
使用以下函数进行设备卸载:media_device_unregister(struct media_device *mdev);
需要注意的是,卸载一个并没有注册过的设备是***不安全的***。个人查看代码猜想不安全的原因主要有几个:1. 如果没有被注册,那么 media_device
内部的 entity 成员就有可能没有被初始化,如果其值为一个不确定的值,那么就会引起非法访问;2. 如果没有注册,内部的 devnode
成员就没有初始化,卸载时就会出现问题。
「entities、pads、links」
entities
entities 用一个 media_entity
结构体来表示,该结构体通常被嵌入到一个更大的结构体里面,比如 v4l2_subdev
或者 video_device
结构体(不必自行分配空间,结构体内部已经包含),当然也可以直接分配一个 entities。使用以下函数对 entity 进行初始化:
media_entity_init(struct media_entity *entity, u16 num_pads, structmedia_pad *pads, u16 extra_links);
在执行初始化函数之前需要注意的参数有:
- num_pads:pad的数量,与驱动子设备结构相关。
- pads:media_pad结构体数组,通常pad被嵌入到驱动自定义的结构体里面,数组地址被传递给该参数,pad需提前初始化。
- extra_links:该函数会根据num_pads分配link数目,该参数则指明除了预分配的数量之外还需要多少额外的links。
- entity:
media_entity
的name、type、flags、revision和group_id
需要在初始化之前或者之后进行设置,如果结构体被嵌入到更高级的结构体里面,这些成员也可能被更高级的框架代码所设置,entity的id在注册的时候被填充(如果提前设置了id成员,则注册的时候就保持预设的值)。entity有相关的标志位「flags」来标识它的状态与功能,MEDIA_ENT_FL_DEFAULT
就表示这是一个默认的 entity。可以设置多个 entity 的组 ID 为同一个整数来标识它们是属于同一类别的,对于内核来说,组ID是没有用处的,但是组 ID 会在枚举 entity 的时候被传递到用户空间,可能在用户空间的某种情况下用得上。
pads
pad 使用一个 media_pad
结构体来表示,pads 数据被驱动程序管理(数组形式)。pads 使用 entity 与数组下标来进行唯一标识,entity 内部的 id 不会重复,但是不同 entity 之间的 pad id 可能会重复,所以 pad 的索引要 entity 与 id 联合确认。
由于 pads 的数量是提前获知的(你做的芯片,你肯定知道它有几个管脚),所以 media_pad
结构体不再动态分配,并且驱动应负责对该结构体数组进行管理(避免动态分配)。驱动必须在 media_entity_init
函数被调用之前对 pads 的方向属性进行设置,pads 有 flags
位来标识它的属性,在初始化的时候仅需要设置该成员即可,其余的交由 media_entity_init
函数来完成:
MEDIA_PAD_FL_SINK:目的pad
MEDIA_PAD_FL_SOURCE:源pad
links
links 用一个 media_link
结构体来表示,每一个 entity 的所有 pads 里面都存储了与之相关的所有 links,一个 link 会分别被源 pad 以及目的 pad 存储,以便实现正反两个方向的遍历。使用以下函数创建 links:
media_entity_create_link(struct media_entity *source, u16 source_pad,struct media_entity *sink, u16 sink_pad, u32 flags);
links 有一些 flags
位来标识其属性:
MEDIA_LNK_FL_ENABLED:link被使能,可以用来传输数据,多个link连接到同一个sink pad时,只有一个link可以被使能。
MEDIA_LNK_FL_IMMUTABLE:link的使能状态不能在运行时被改变,一般情况下这两个标志位同时被设置。
MEDIA_LNK_FL_DYNAMIC:link的状态是动态可变的。
和 pads 不一样,links 的数量并不总是提前确定的(电路板上面有时候你也无法完全确认需要管脚连到多少个设备上面,极有可能出现临时变更的情况),所以 media_entity_init
函数根据传入的参数预分配一定数量的 media_link
结构体,如果不够用的话会在 media_entity_create_link
中动态分配(如果 link 数量大于等于 max_link 的话就会扩充 link 数量)。
注册与卸载
驱动需要使用以下函数对 entity 进行注册与卸载(不需要手动执行,在 v4l2_device_un/register_subdev
函数里面完成):
media_device_register_entity(struct media_device *mdev, struct media_entity *entity);
media_device_unregister_entity(struct media_entity *entity);
内核里面使用一个唯一的正整数来表示每一个 entity(同一个 media_device
下唯一),驱动也可以通过填充 media_entity->id
成员来指定 entity 的 ID 值,但是必须保证唯一。如果 ID 由内核自动生成,则不能保证它们是连续的,事实上内核自动生成的 id 是由 entity 的 media_device->entity_id++
来实现赋值的,该值在 media_device_register
函数里面被初始化为1。
在卸载 entity 之后需要调用以下函数来释放申请到的相关资源,主要是释放动态分配的 media_link
结构体内存:
media_entity_cleanup(struct media_entity *entity); //与media_entity_init结对使用
要想遍历 entities 可以在用户空间进行 MEDIA_IOC_ENUM_ENTITIES
系统调用,需要设置 media_entity_desc
的 id 为 (0|MEDIA_ENT_ID_FLAG_NEXT)
,循环的过程中只需要设置 id |= MEDIA_ENT_ID_FLAG_NEXT
即可完成 entity 的遍历过程,如果需要枚举指定的 entitiy,需要设置 id 为指定 entity 的 id 值(内核 entity 的 id 是从1开始),这个在 entity 注册函数里面可以看到。代码实例如下,我尽量精简了贴出来的代码,防止占用过大篇幅:
int enum_media_device_entities(int iFd)
{int iRet;struct media_entity_desc desc;desc.id = 0 | MEDIA_ENT_ID_FLAG_NEXT;while (1) {iRet = ioctl(iFd, MEDIA_IOC_ENUM_ENTITIES, &desc);if (iRet < 0) {MODULE_WRN("enum media entities end\n");break;}MODULE_DBG("entity name[%s]\n", desc.name);desc.id |= MEDIA_ENT_ID_FLAG_NEXT;}return 0;
}int main(int argc, char *argv[])
{int iErr = 0, ivFd;ivFd = open("/dev/media0", O_RDWR);iErr = enum_media_device_entities(ivFd);close(ivFd);
open_err:return iErr;
}
图遍历(深度优先)
图遍历是干嘛的?它为我们提供在运行时访问每一个、指定的 entities 的方法,至于为什么需要访问,是因为我们可能会需要在运行时去管理它们。
可以使用下面的函数对同属于一个media设备的entities进行遍历(线性遍历,非典型图遍历,也就是跟链表一样的遍历方式):
struct media_entity *entity;
media_device_for_each_entity(entity, mdev) {/* entity will point to each entity in turn */...
}
驱动可能需要从一个给定的 entity,通过使能的 links 对所有的可访问到的 entities 进行遍历,meida 框架提供了一个深度优先的 API 来完成这个任务。需要注意的是,要避免对闭环的图进行遍历,否则会陷入死循环,为了避免这种情况,函数限制了最大遍历深度为 MEDIA_ENTITY_ENUM_MAX_DEPTH
,该宏最新的定义是16**「截至 Linux-4.4.138」**。
media_entity_graph_walk_start(struct media_entity_graph *graph,struct media_entity *entity);
media_entity_graph_walk_next(struct media_entity_graph *graph);
使用时先用第一个函数初始化图,然后循环调用第二个函数进行遍历,遍历全部完成之后第二个函数会返回NULL。遍历过程可以在任意一个时刻中断,并且无需调用清理函数。
有相应的帮助函数用来寻找两个给定的 pads 的 link,或者通过一个 pad 来找到与之相连的另一个 pad。
media_entity_find_link(struct media_pad *source, struct media_pad *sink);
media_entity_remote_pad(struct media_pad *pad);
补充 links 的设置
link 的属性可以在运行时被改变,调用以下函数即可完成:media_entity_setup_link(struct media_link *link, u32 flags);
flags
参数用来设置指定的 link 的属性,允许被配置的属性是从 MEDIA_LNK_FL_ENABLED
属性到 MEDIA_LNK_FL_ENABLE
或者 MEDIA_LNK_FL_DISABLE
标志,如果 link 设置了 MEDIA_LNK_FL_IMMUTABLE
标志的话,就不能够使能或者关闭。当一个 link 使能或者关闭时,meida framework 会分两次调用 sink 以及 source 端的 link_setup
来进行相关的设置,如果第二次调用失败的话,第一个调用也会被复原。
media 设备驱动可以设置 media_device->link_notify
指向一个回调函数,此函数会在 link_setup
操作完成之后被调用。如果有任何的 link 是 non-immutable 的话,entity 驱动就需要自己实现 link_setup
操作。一个 link 的配置不应该影响到其他 link,如果一个 link 连接在 sink pad 上,并且该 link 被使能了,那么其他连接到该 pad 的 link 就不能再被使能,此时应该返回 -EBUSY
。
- pipeline与media流
pipeline 的概念前面已经介绍过了,不再重复,这里给一副说明图(其实这个不是非常的清晰易懂,还有更加清晰易懂的由于某些原因不能放出,请读者发挥想象,根据图中以及上面的描述自行抽象出来一个图,只要紧紧围绕一点-pipeline 就是数据流链路的抽象,我相信你):
pipeline 抽象图
当开启 streaming 时,驱动应当通知 pipeline 上所有的 entity 来维护当前状态不被改变,可以调用下面的函数完成通知(该函数只会调用 sink 端的 validate 回调):
media_entity_pipeline_start(struct media_entity *entity, struct media_pipeline *pipe);
该函数将会标记所有的处在使能 link 连线上的 entity 为 streaming 状态,不管是直接还是间接。第二个参数指向的 media_pipeline 结构体会被传递给 pipeline 上面的所有 entity,驱动需要把 media_pipeline 结构体嵌入到一个更高级的结构体里面,并且可以从 media_entity 结构体访问到 pipeline。等到需要停止 streaming 时,需要调用:
media_entity_pipeline_stop(struct media_entity *entity);
由于start函数可以嵌套调用,所以与之对应,stop函数也应该保持相应数量的调用。media_entity_pipeline_start
函数会进行 link 有效性的检验,此时 media_entity
的 link_validate
成员会被调用用来完成检验。下面一幅图是遍历的说明:
pipeline 遍历
上图中的圆圈序号指的是访问的先后顺序。值得一提的是内核关于广度优先图遍历的实现很耐人寻味,很具有参考价值,值得自己去找到内核代码探究一番。其中用到了栈、位图等概念。具体的代码在 media-entity.c/media_entity_pipeline_start
函数里面。
由于上面的遍历是广度优先的,并且是全部遍历,也就是说如果你的 isp 有两个输入源,那这两个输入源可能会同时被打开,更多时候我们并不希望这种情况发生,我们期望的是指定的输入源、指定的单链路的 pipeline 被打开,此时就需要自行去管理 pipeline,可以实现一个自己的 pipeline 结构体进行相关的管理。实现方式就很简单,这个 pipeline 结构体类似下面:
struct spec_pipeline {struct list_head entities; // pipeline 线上的 entity 链表头struct media_entity entities[PIPE_LENGTH]; //与上一个类似,随自己想法去实现int (*open_fun)(struct media_entity entity, int onoff);int (*s_stream)(struct media_entity entity, int onoff);... ...
};
接下来就不用多说了吧,思维赶紧发散一下、扩展一下,很容易实现一个易用的自己特定的 pipeline,可以实现指定同源不同目的的数据流区分管理,不同源不同目的的数据流区分管理等等。
Tips
- 从
media_entity
找到v4l2_subdev
可以使用media_entity_to_v4l2_subdev
函数来完成。 - 可以在 video 模块的 streamon 部分加入对link的使能标记,
media_entity_pipeline_start
函数会对entity.stream_count
成员进行增值操作,并且会将第二个参数中的 pipe 传递给 link 线上的entity.pipe
成员。使能标记完成之后可以对各个 entity 进行图遍历来调用其set_stream
成员开启 stream。事实上在s_param,s_fmt
等 ioctl 调用时就需要进行图遍历以调用整个 pipeline 上面的各个 entity 的回调函数进行相关的设置。 - 在子设备节点都注册完毕之后可以通过
media_entity_create_link
来完成各个 entity 的连接,以待之后整个数据流的开启。 - 为什么要有 media framework?因为仅仅有 subdev 的话,各个子设备类是处于平级状态,并没有数据流的流向之分,如果需要建立数据流 pipeline 的话需要自行去实现,这样会比较麻烦,而有了 media framework 的话这些管理就会方便很多,因为它提供了 pipeline 的一切管理操作,这样就可以把众多的 subdev 串联成为一个完整的数据流,从而进行正确、有向的数据传递。
v4l2_subdev
与video_device
内部都有一个media_entity
结构体(非指针类型),video_device
内部的entity会随着video 设备的注册而注册,名字沿用video_deivce
的名字,这两个是有所区别的,使用的时候需要特别注意下。
结束语:本文到此结束,希望你已经能够完成一个完整功能的 pipeline,能够进行 open/close, s_stream 等操作了,并且可以实现自定义的设备数据流串联,实时管理等功能。下文预告-videobuf2 的实现与使用。
想做的事情就去做吧
V4L2框架-media device相关推荐
- V4L2框架-v4l2 device
阅读原文 本文对 V4L2 中比较容易理解的骨干结构进行介绍,涉及两个核心结构体:v4l2_device, v4l2_subdev.文章围绕这两个结构体以 Linux-4.4 内核的 omap3isp ...
- 深入理解l内核v4l2框架之video for linux 2(一)
在看了很多关于v4l2驱动的例程之后,想深入研究下linux内核的v4l2框架,顺便把这些记录下来,以备查用. Video for Linux 2 随着一些视频或者图像硬件的复杂化,V4L2驱动也越来 ...
- V4L2框架分析学习
Author:CJOK Contact:cjok.liao#gmail.com SinaWeibo:@廖野cjok 1.概述 Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上 ...
- 深入理解l内核v4l2框架之video for linux 2(转载)
在看了很多关于v4l2驱动的例程之后,想深入研究下linux内核的v4l2框架,顺便把这些记录下来,以备查用. Video for Linux 2 随着一些视频或者图像硬件的复杂化,V4L2驱动也越来 ...
- v4l2框架—申请缓存(VIDIOC_REQBUFS)
1.前言 本文对学习V4L2框架缓存管理做一个笔记. 2.v4l2中关于缓存管理的结构体 在v4l2中,使用vb2_queue结构体作为缓存管理的结构体 struct vb2_queue {enum ...
- V4L2框架-videobuf2
阅读原文 本文介绍在 v4l2 框架之下的数据流交互的实现与使用,主要目的是实现一个能够进行用户空间与内核空间进行数据交互.数据流格式设置.数据流 buffer 申请与释放.数据流开启与关闭的 vid ...
- 二十四、V4L2框架主要结构体分析和虚拟摄像头驱动编写
一.V4L2框架主要结构体分析 V4L2(video for linux version 2),是内核中视频设备的驱动框架,为上层访问视频设备提供统一接口. V4L2整体框架如下图: 图中主要包括两层 ...
- Linux v4l2框架分析
背景 说明: Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 V4L2(Video for Linu ...
- V4L2框架分析学习二
转载于:http://www.techbulo.com/1198.html v4l2_device v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设 ...
- 通过V4L2框架获取UVC摄像头的MJPEG格式数据
UVC摄像头一般支持YUV,MJPEG,H264等格式的输出,下面以MJPEG输出为例,介绍如何通过V4L2框架抓取摄像头MJPEG数据. #include <stdio.h> #incl ...
最新文章
- 这大概是一篇最简单最清晰的Java JVM执行流程
- 网关和BFF是如何演进出来的?
- SAP UI5 函数节流和异步完成令牌的应用
- 使用IntelliJ IDEA 2020 高效开发 springboot项目
- IRCTC的完整形式是什么?
- 句句真研—每日长难句打卡Day11
- c语言练习题:求1-1/2+1/3-1/4+... -1/100的值
- 【SQL 提示 之二】index_ss Index Skip Hint
- volley浅析(磨砺营马剑威Android)
- MongoDB 3.2 On CentOS
- foxpro获取html数据类型,FoxPro数据库写入html文件中
- 蓝桥杯_单片机_入门基础知识(七)_DS18b20
- 字节跳动后台开发实习面试回顾
- 处理png图片为透明
- 电机学他励直流发电机matlab,基于Matlab并励直流发电机的自励过程分析
- Jupyter Notebook主题字体设置及自动代码补全
- 汇编基础练习题1:将AX中的数显示输出。
- python在pip安装pytorch时候killed
- 黑苹果活动监视器闪退的解决办法
- 比较图片相似度算法介绍与应用(Java版)
热门文章
- Windows 8激活产品密匙公布
- element实现小问号提示
- Go:http request cancelled 服务端感知
- vue+websocket+nodejs创建聊天室- 创建群聊、加入群聊
- 113.库存明细账案例(包含结存数)
- 《昭君出塞》首映礼纪实——《心周刊》报道
- ac算法 java_Aho-Corasick算法的Java实现与分析
- java乘法口诀编程题_【视频+图文】Java经典基础练习题(二)输出9*9乘法口诀表...
- 2019前端书籍推荐,前端PDF书籍,前端书籍下载
- python-三天打渔、两天晒网 - 实验7 简单的循环程序