文章目录
本文基于linux内核
4.19.4
,抽象媒体设备模型框架的相关源码文件如下:
(1)include/media/media-device.h
(2)include/media/media-entity.h
(3)/drivers/media/media-device.c
(4)/drivers/media/media-devnode.c
(5)/drivers/media/media-entity.c
在linux内核6.2.7
版本中,抽象媒体设备框架描述文件放在了/drivers/media/mc
目录中(也就是单独开了一个目录存放),且文件命名也发生了变化:
-
mc-dev-allocator.c - 媒体控制器设备分配器API
-
mc-device.c:该文件中的代码包含了媒体设备子系统的核心逻辑和数据结构的定义,以及与之相关的函数实现。它提供了用于媒体设备的注册、初始化、配置和控制的接口。
-
mc-devnode.c:文件中的代码包含了媒体设备子系统中设备节点相关的逻辑和函数实现。它负责创建和管理媒体设备子系统中的设备节点,以便应用程序可以通过设备节点与媒体设备进行通信。
-
mc-entry.c:文件中的代码包含了媒体设备子系统中媒体实体相关的逻辑和函数实现。它定义了媒体实体的数据结构以及用于管理和操作媒体实体的接口函数。
-
mc-request.c:该文件为新添文件,该文件中为Media控制器和V4L2接口提供了Request API。目前一些无状态编解码器驱动程序需要使用该文件中实现的函数。
一、抽象媒体设备模型
media控制器设备本文称为媒体控制器框架,该框架的一个设计目标是:发现设备内部拓扑并在运行时对其进行配置,为了实现这一目标,硬件设备被建模为定向图,由pad连接的实体共同组成。即媒体设备被抽象成包含entity
、pad
、link
的对象。
Linux内核的Media Controller是一个通用的多媒体框架,它提供了一种标准化的方法来处理多种媒体类型,包括音频、视频、图像等。Media Controller主要用于描述媒体设备的拓扑结构,即描述多个媒体设备之间的连接关系。
在Media Controller中,每个媒体设备都被描述为一个媒体实体(Media Entity),每个媒体实体都有一个或多个媒体端口(Media Pad),每个媒体端口都可以连接到另一个媒体设备的媒体端口上,从而形成一个媒体设备的拓扑结构。
Media Controller还提供了一组API,使得用户可以方便地访问媒体设备的拓扑结构,并且可以使用相同的API来访问不同类型的媒体设备。
Media Controller的主要组成设备包括:
-
Media Controller设备:Media Controller设备是一个虚拟的设备,它代表了整个媒体设备的拓扑结构,包括所有的媒体实体、媒体端口、连接关系等。在开发中可以通过Media Controller设备来访问整个媒体设备的拓扑结构。
-
Media Entity设备:Media Entity设备是一个实际的媒体设备,它可以是一个摄像头、一个麦克风、一个视频采集卡等。每个Media Entity设备都被描述为一个媒体实体,它包括一个或多个媒体端口和一些属性信息。
-
Media Pad设备:Media Pad设备是一个媒体端口,它描述了一个Media Entity设备的输入或输出端口。每个Media Pad设备包括一些属性信息,如媒体类型、格式、方向等。
-
Media Link设备:Media Link设备描述了两个媒体端口之间的连接关系。每个Media Link设备包括一些属性信息,如连接的源端口、目标端口、媒体类型、格式等。
二、抽象媒体设备
一个媒体设备由struct media_device
实例表示,定义在include/media/media-device.h中。该结构的分配由媒体设备驱动程序完成,通常将media_device实例嵌入到一个更大的特定驱动程序的结构中。
struct media_device {
struct device *dev; //父设备
struct media_devnode *devnode; //媒体设备节点
char model[32]; //设备模型的名称
char driver_name[32]; //设备驱动名称(可选)。如果没有设置,调用MEDIA_IOC_DEVICE_INFO将返回dev->driver->name。
char serial[40]; // 设备序列号 (可选)
char bus_info[32]; //唯一和稳定的设备位置标识符
u32 hw_revision; //硬件设备修订
u64 topology_version; //单调计数器用于存储图形拓扑的版本,
u32 id; //在最后注册的图对象上使用的唯一ID
struct ida entity_internal_idx; //图遍历算法使用的唯一内部实体ID
int entity_internal_idx_max; //分配的内部实体索引
struct list_head entities; //用于存储已注册的entity的链表
struct list_head interfaces; //用于存储已注册的接口的链表
struct list_head pads; //用于存储已注册的pad的链表
struct list_head links; //用于存储已注册的link的链表
struct list_head entity_notify; //用于存储已注册的entity_notify回调的链表
struct mutex graph_mutex; //用于保护对struct media_device数据的访问
struct media_graph pm_count_walk;
void *source_priv; //驱动程序用于启用/禁用源处理程序的私有数据
int (*enable_source)(struct media_entity *entity, struct media_pipeline *pipe);//启用源处理程序函数指针
void (*disable_source)(struct media_entity *entity); //禁用源处理程序函数指针
const struct media_device_ops *ops; //media设备的操作回调集合
struct mutex req_queue_mutex; //互斥锁
atomic_t request_id; //用于生成唯一的请求ID
};
驱动程序通过调用media_device_init()
来初始化媒体设备实例,在初始化媒体设备实例后,通过media_device_register()
宏函数调用__media_device_register()
向linux内核注册媒体设备。
通过调用media_device_unregister()
来取消注册的媒体设备实例,初始化的媒体设备最后必须通过调用media_device_cleanup()
来进行清理操作。
注意,不允许取消未注册的媒体设备实例的注册,也不允许清除未初始化的媒体设备实例。
三、Entity
实体由struct media_entity
实例表示,定义在include/media/media-entity.h中:
struct media_entity {
struct media_gobj graph_obj; //包含媒体对象通用数据的嵌入结构。
const char *name; //实体的名称。
enum media_entity_type obj_type; //实现media_entity的对象类型。
u32 function; //实体主函数,定义在include/uapi/linux/media.h (命名类似于MEDIA_ENT_F_*)。
unsigned long flags;//实体标志,在include/uapi/linux/media.h定义(命名类似于MEDIA_ENT_FL_*)。
u16 num_pads; //sink和source pad的数量。
u16 num_links; //link的总数,转发和返回,启用和禁用。
u16 num_backlinks; //反向的backlink的数量。
int internal_idx; //唯一的内部实体特定编号,如果实体未注册或重新注册,这些号码将被重用。
struct media_pad *pads; //数组的大小由num_pads定义。
struct list_head links; //用于存储数据link的链表。
const struct media_entity_operations *ops; //实体操作。
int use_count; //对实体使用count。
union {
struct {
u32 major;
u32 minor;
} dev; /包含设备major和minor信息。
} info;
};
在驱动程序中可以直接分配entity,但该结构通常会嵌入到更大的结构中,例如v4l2_subdev
或video_device
实例中。
驱动程序通过调用media_entity_pads_init()
初始化entity pad,在初始化pad后,驱动程序通过调用media_device_register_entity()
向媒体设备注册实体。
如果想要注销已注册的media_entity,可调用media_device_unregister_entity()
取消注册。
四、Interfaces
接口由struct media_interface
实例表示,定义在include/media/media-entity.h中。目前只定义了一种类型的接口:设备节点,该接口由struct media_intf_devnode
表示。
struct media_interface {
struct media_gobj graph_obj; //嵌入式的图对象
struct list_head links; //指向图实体的link列表
u32 type; //在include/uapi/linux/media.h中定义的接口类型
u32 flags; //在include/uapi/linux/media.h中定义的接口标志。(以MEDIA_INTF_FL_*进行命名)
};
驱动程序通过调用media_devnode_create()
初始化和创建设备节点接口。
通过调用media_devnode_remove()
来删除设备节点。
五、Pad
Pad由struct media_pad
实例表示,定义在include/media/media-entity.h中。
struct media_pad {
struct media_gobj graph_obj; //包含媒体对象通用数据的嵌入式结构。
struct media_entity *entity; //本pad所属的Entity。
u16 index; //Pad在实体Pad数组中的索引,编号从0到n。
enum media_pad_signal_type sig_type; //media pad的信号类型。
unsigned long flags; //在include/uapi/linux/media.h中定义的Pad标志。
struct media_pipeline *pipe; //该pad属于的Pipeline。可以使用media_entity_pipeline()访问该字段。
};
每个entity将其Pad存储在由entity驱动程序管理的Pad数组中,驱动程序通常将Pad数组嵌入到特定驱动程序的结构中。Pads由它们的entity和它们在Pads数组中基于0的索引来标识,这两个信息都存储在struct media_pad
中,使得struct media_pad
指针成为存储和传递链接引用的规范方法。Pad具有描述pad功能和状态的标志:MEDIA_PAD_FL_SINK
表示pad支持sink数据,MEDIA_PAD_FL_SOURCE
表示pad支持源数据。
每个pad必须设置
MEDIA_PAD_FL_SINK
或MEDIA_PAD_FL_SOURCE
中的一个且只能设置一个标志。
六、Link
Link由struct media_link
实例表示,定义在include/media/media-entity.h中:
struct media_link {
struct media_gobj graph_obj; //包含媒体对象通用数据的嵌入式结构。
struct list_head list; //与拥有该链路的实体或接口相关联的链表。
union {
struct media_gobj *gobj0; //用于获取链接的第一个graph_object的指针。
struct media_pad *source; //仅当第一个对象(gobj0)是pad时使用。在这种情况下,它表示源pad。
struct media_interface *intf; //仅当第一个对象(gobj0)是pad时使用。在这种情况下,它表示源pad。
};
union {
struct media_gobj *gobj1; //用于获取链接的第二个graph_object的指针。
struct media_pad *sink; //仅在第二个对象(gobj1)是pad时使用。在这种情况下,它表示sink pad。
struct media_entity *entity; //仅在第二个对象(gobj1)是实体时使用。
};
struct media_link *reverse; //指向pad到pad link的反向链接的指针。
unsigned long flags; //link标志,在uapi/media.h中定义。
bool is_backlink; //表示link是否是反向链路。
};
Link有两种类型:
-
(1)Pad到Pad的链接
通过它们的pad关联两个实体,每个实体都有一个指向所有链接的列表,这些链接来自或指向其任何一个pad。因此,给定的链接存储两次,一次在源实体中,一次在目标实体中。 -
(2)接口到实体的链接
该种链接用于将一个接口关联到Link。在驱动程序中通过调用media_create_intf_link()
创建接口到实体的链接,并使用media_remove_intf_links()
删除已创建的链接。
驱动程序通过调用media_create_pad_link()
创建pad到pad的链接,并使用media_entity_remove_links()
删除已创建的链接。
Link必须在两端都已创建的情况下才能创建。
链接具有描述其功能和状态的标志:有效值在media_create_pad_link()
和media_create_intf_link()
中描述。
七、Media图遍历
在媒体框架中,提供了API来迭代图中的entity。要遍历属于媒体设备的所有entity,驱动程序可以使用media_device_for_each_entity
宏,该宏定义在include/media/media-device.h中:
#define media_device_for_each_entity(entity, mdev) \
list_for_each_entry(entity, &(mdev)->entities, graph_obj.list)
例如下列使用代码:
struct media_entity *entity;
media_device_for_each_entity(entity, mdev) {
//entity will point to each entity in turn
...
}
在驱动程序中可能还需要遍历图中的所有entity,这些entity只能通过从给定entity开始的链接访问,因此媒体框架为此提供了一个深度优化的图遍历API。
注意:图遍历API不支持带有循环的图(无论是有向的还是无向的)。为了防止出现无限循环,图遍历代码将最大深度限制为
MEDIA_ENTITY_ENUM_MAX_DEPTH
,当前值定义为16。
在驱动程序中,通过调用media_graph_walk_start()
来开始一个图遍历。驱动程序可以通过调用media_graph_walk_next()
来检索下一个entity,当图遍历完成时,函数将返回NULL。
图遍历操作可以在任何时刻中断,不需要调用清理函数,可以正常释放图结构。
以下助手函数可用于查找两个给定pad之间的link,或者通过link查找连接到另一个pad的pad:
media_entity_find_link() //用于查找两个pad之间的连接。
media_pad_remote_pad_first() //在连接的远端查找第一个pad。
media_entity_remote_source_pad_unique() //查找一个连接到实体的远程 source pad。
media_pad_remote_pad_unique() //查找一个连接到pad的远程pad。
八、使用计数和电源处理
由于在驱动程序之间关于电源管理需求存在巨大的差异,所以媒体控制器框架中不实现电源管理。但是,struct media_entity
结构中包括一个use_count
字段,媒体驱动程序可以使用该字段来跟踪每个实体的用户数量,以满足电源管理需求。
media_entity.use_count
字段由媒体驱动程序拥有,entity驱动程序不能使用它,除此之外,对该字段的访问必须受到media_device.graph_mutex
锁的保护。
九、link设置
可以通过调用media_entity_setup_link()
在运行时修改链接(struct media_link
)的属性。该函数原型如下:
int media_entity_setup_link(struct media_link *link, u32 flags)
-
link : 指向struct media_link的指针。
-
flags :标志。
十、Pipeline和Media流
媒体流是源自一个或多个源设备(如传感器)的像素或元数据流,流经媒体实体pad,流向最终接收器。媒体流可以在路由上被设备修改(例如:缩放或像素格式转换),也可以被分割成多个分支,或者进行多分支合并。
媒体管道是一组相互依赖的媒体流,这种相互依赖可能是由硬件引起的(例如,如果第一个流已启用,则无法更改第二个流的配置),也可能是由驱动程序引起的。最常见的是:媒体管道由一个没有分支的流组成。
当开始流时,驱动程序必须通知管道中的所有entity,以防止在流期间调用media_pipeline_start()
进而修改链接状态,在该函数中会将所有作为管道的pad标记为流。
pipe参数所指向的struct media_pipeline
实例将存储在管道中的每个pad中。驱动程序应该在更高级的管道结构中嵌入struct media_pipeline
,然后可以通过struct media_pad pipe
字段访问管道。
media_pipeline_start()
的调用可以嵌套。对于函数的所有嵌套调用,管道指针必须相同。
media_pipeline_start()
可能返回错误,在这种情况下,它将清除自己所做的所有更改。
当停止流时,驱动程序必须用media_pipeline_stop()
通知entity。如果多次调用media_pipeline_start()
,则需要调用相同次数的media_pipeline_stop()
来停止流。media_entity.pipe
字段将在最后一个嵌套调用停止时重置为NULL。
默认情况下,如果link的任何一端是流实体,则使用-EBUSY
配置链路将失败。
可以在流式传输时修改的链接必须使用
MEDIA_LNK_FL_DYNAMIC
标志进行标记。
如果需要禁止流实体上的其他操作(例如:更改实体配置参数),驱动程序可以显式检查media_entity stream_count
字段,以确定entity是否正在进行流操作,该操作必须在持有media_device graph_mutex
的情况下完成。
十一、链接验证
在media_pipeline_start()
函数中对pipeline中有sink pad的实体执行链接验证。media_entity.link_validate()
回调用于实现验证操作,在link_validate()
回调中,实体的驱动程序应该检查被连接实体的source pad和它自己的sink pad的属性是否匹配。匹配的实际含义取决于实体的类型(最终取决于硬件的属性)。
子系统应该通过提供特定子系统的辅助函数来访问通常需要的信息,并最终提供一种使用特定于驱动程序的回调的方法,从而促进链接验证。
十二、媒体控制器设备的分配器API
当媒体设备属于多个驱动程序时,共享媒体设备被分配为共享的struct device
以作为查找的键,共享媒体设备应该保持注册状态,直到最后一个驱动程序注销它。此外,媒体设备应在所有引用都释放时释放,在probe
期间,当分配媒体设备时,每个驱动程序都获得对媒体设备的引用,如果媒体设备已经被分配,那么allocate API会增加refcount并返回现有的媒体设备。
调用media_device_delete()
函数,媒体设备将被取消注册并从kref put处理程序中清除,以确保媒体设备保持在已注册状态,直到最后一个驱动程序取消对媒体设备的注册。
在驱动程序中的使用方法
驱动程序应该使用适当的媒体核心来管理共享媒体设备的生命周期,处理以下两种状态:
-
1、 allocate -> register -> delete
-
2、 获取已注册设备的引用 -> delete
调用media_device_delete()
函数确保能正确处理共享媒体设备的删除操作。
对于驱动.probe
操作:如果media devnode没有注册,需调用media_device_usb_allocate()
来分配或获取一个对媒体设备的引用。
对于驱动disconnect
的操作:调用media_device_delete()
释放media_device,释放由kref put处理程序完成。
【参考链接】
https://docs.kernel.org/driver-api/media/mc-core.html#c.media_device