1、从plugin_init()函数
gstreamer插件,直接就是经过plugin_init()函数注册到Gstreamer中的,每一个plugin都是在plugin_init()函数中经过gst_element_register()函数将plugin的相应信息注册到gstreamer中。
函数如下:
static gboolean plugin_init (GstPlugin * plugin)
{
if (!gst_element_register (plugin, "mssdemux",
GST_RANK_PRIMARY, GST_TYPE_MSS_DEMUX))
return FALSE;
return TRUE;
}
从以上函数可知,plugin_init()函数只有一个参数,这个参数就是GSTPlugin指针类型的plugin指针,如今咱们不知道它具体是干吗的,可是咱们能够先猜想一下,这个应该是在gstreamer核心在进行注册plugin的时候,经过传进指针获得plugin的相应信息,然后方便管理和查找,咱们后续验证一下,看看这个函数调用的时候,是否如同咱们猜想的那样。编程
在plugin_init()函数中,经过gst_element_register()函数将plugin的相应信息注册到gstreamer中,gst_element_register()函数声明以下:
/** * gst_element_register: * @plugin: (allow-none): #GstPlugin to register the element with, or %NULL for * a static element. * @name: name of elements of this type * @rank: rank of element (higher rank means more importance when autoplugging) * @type: GType of element to register * * Create a new elementfactory capable of instantiating objects of the * @type and add the factory to @plugin. * * Returns: %TRUE, if the registering succeeded, %FALSE on error */
gboolean
gst_element_register (GstPlugin * plugin, const gchar * name, guint rank, GType type);
从gst_element_register()的函数声明咱们能够了解到,经过该函数,能够建立一个名称为name、优先级为rank的type类型elementfactory,并将elementfactory添加到registry。在gstreamer中,每一个plugin都应对应的名称,也都会有相应的信息说明它具有什么功能,可是颇有可能在gstreamer中具有相同功能的plugin有多个呢,这个时候它又如何知道,该选那个了呢。这时就是rank起做用了,每一个plugin都具有相应的功能,同时在注册plugin的时候也都会经过rank说明plugin的优先级,在遇到相同功能的plugin,gstreamer会优先选择rank数值大的plugin,rank在gstreamer系统中,默认的plugin rank最大值也就是GST_RANK_PRIMARY(256),因此当你本身编写一个插件,但愿gstreamer在使用你插件所具有的功能,在注册plugin的时候,将rank的值设置为比GST_RANK_PRIMARY大便可,这样就将会优先选择你的plugin。
在上一节 gstreamer学习笔记—Gobject类对象,咱们说到过,在系统第一次调用某类型的type_name##_get_type()函数时,将会向gobject系统注册该种新的类型,那么,类型的第一次调用,是在何时呢,就是在plugin_init()函数中,将type_name##_get_type()做为函数参数type传递给gst_element_register()函数,从而完成类型向gobject系统的注册,只不过大多数的时候,type_name##_get_type()函数都被封装为一个宏定义GST_TYPE_xxx。
下面咱们继续来分析一下gst_element_register()函数都干了什么。gst_element_register()代码有点多,就没有直接贴代码,下面经过流程图介绍一下它的工做与步骤。
经过阅读代码可知传进的plugin指针,只是经过它获取到plugin的一些描述信息,并将factory与plugin创建弱链接。而factory与gstreamer的联系,真正的都是在全局指针变量_gst_registry_default中。通常的,padtemplates信息是在plugin的类初始化函数xxx_class_init()经过gst_element_class_add_static_pad_template()函数添加到plugin。
而在gst_element_register()函数最后,调用了gst_registry_add_feature()函数,该函数又是如何操做的,函数声明以下:
/** * gst_registry_add_feature: * @registry: the registry to add the plugin to * @feature: (transfer floating): the feature to add * * Add the feature to the registry. The feature-added signal will be emitted. * * @feature's reference count will be incremented, and any floating * reference will be removed (see gst_object_ref_sink()) * * Returns: %TRUE on success. * * MT safe. */
gboolean
gst_registry_add_feature (GstRegistry * registry, GstPluginFeature * feature)
在gst_registry_add_feature()函数中,很简单,就是将传进来的feature保存到全局变量的_gst_registry_default的priv->features成员中,同时根据feature的name生成相应的hash保存(在查找feature时就是根据name的hash快速查找),这里的feature,就是element支持的caps,将会说明element所支持的功能。最后会经过 g_signal_emit (registry, gst_registry_signals[FEATURE_ADDED], 0, feature) 函数发送一个添加了feature的信号,这个信息是谁来进行接收呢,我们再继续看代码。
2、plugin_init()函数调用
从以上代码,我们知道,gstreamer的plugin注册是经过在plugin_init()函数中调用gst_element_register()函数完成的,那么plugin_init()函数又是何时调用呢?
实际上每一个plugin_init()函数定义都是个静态函数,也就证实该函数只是在本文件起做用,同时,在同一个C文件中有经过GST_PLUGIN_DEFINE这样的一个宏定义对plugin_init()进行修饰,而GST_PLUGIN_DEFINE这个宏是干吗的呢,来看一下它的定义。
GST_PLUGIN_DEFINE详细定义在gstplugin.h,将其实现展开以下:
#define GST_PLUGIN_DEFINE(major,minor,name,description,init,version,license,package,origin)
/* 文本中的name都将使用GST_PLUGIN_DEFINE传进的name替换 */
GST_PLUGIN_EXPORT const GstPluginDesc * gst_plugin_name_get_desc (void);
GST_PLUGIN_EXPORT void gst_plugin_name_register (void);
static const GstPluginDesc gst_plugin_desc = {
major,
minor,
G_STRINGIFY(name),
(gchar *) description,
init,
version,
license,
PACKAGE,
package,
origin,
__GST_PACKAGE_RELEASE_DATETIME,
GST_PADDING_INIT
};
const GstPluginDesc *
gst_plugin_name_get_desc (void)
{
return &gst_plugin_desc;
}
void
gst_plugin_name_register (void)
{
gst_plugin_register_static (major, minor, G_STRINGIFY(name),
description, init, version, license,
PACKAGE, package, origin);
}
从GST_PLUGIN_DEFINE的展开咱们能够看到,其实它就是定义了两个函数,分别是:
GST_PLUGIN_EXPORT const GstPluginDesc * gst_plugin_name_get_desc (void);
GST_PLUGIN_EXPORT void gst_plugin_name_register (void);
gst_plugin_name_get_desc()函数将会返回一个描述plugin的结构体,里面包含了一系列plugin详细信息,主要有plugin编译的gstreamer-core版本号、插件名称、描述信息、传进来做为初始化函数的plugin_init()函数、版本号、许可证等。
gst_plugin_name_register()函数则是将经过gst_plugin_register_static()函数完成plugin的静态注册登记。gst_plugin_register_static()函数主体实现以下:
gboolean
gst_plugin_register_static (gint major_version, gint minor_version,
const gchar * name, const gchar * description, GstPluginInitFunc init_func,
const gchar * version, const gchar * license, const gchar * source,
const gchar * package, const gchar * origin)
{
GstPluginDesc desc = { major_version, minor_version, name, description,
init_func, version, license, source, package, origin, NULL,
};
GstPlugin *plugin;
gboolean res = FALSE;
/* 一系列的输入参数检查以及gstreamer core初始化检查 */
GST_LOG ("attempting to load static plugin \"%s\" now...", name);
plugin = g_object_new (GST_TYPE_PLUGIN, NULL);
if (gst_plugin_register_func (plugin, &desc, NULL) != NULL) {
GST_INFO ("registered static plugin \"%s\"", name);
res = gst_registry_add_plugin (gst_registry_get (), plugin);
GST_INFO ("added static plugin \"%s\", result: %d", name, res);
}
return res;
}
在该函数中,将会先经过gst_plugin_register_func()函数进行plugin的注册,gst_plugin_register_func()将会检查版本之类的信息,而后拷贝关于plugin的描述信息,在经过函数指针desc->plugin_init真正的完成plugin的注册(desc->plugin_init函数指针指向的就是上述说到的plugin_init()函数),这样,就调用到了plugin_init()。接着,在gst_plugin_register_static()函数中还调用了gst_registry_add_plugin()函数,gst_registry_add_plugin()函数与gst_registry_add_feature()函数相似,gst_registry_add_feature()是将feature保存到全局变量的_gst_registry_default的priv->features成员,而gst_registry_add_plugin()函数则是将plugin保存到_gst_registry_default的priv->plugins成员。
经过分析宏定义GST_PLUGIN_DEFINE以后,可以知道plugin_init()函数是被gst_plugin_name_register()函数调用的,那么gst_plugin_name_register()函数,又将会是被谁调用呢,我们接着往下看。
在GST_PLUGIN_DEFINE的展开咱们能够看到,gst_plugin_name_register()函数是被GST_PLUGIN_EXPORT修饰的,可是,经过查看gstconfig.h中GST_PLUGIN_EXPORT的解析咱们能够知道,这个宏只是在Windows导出符号给DLL,其余平台更多的是为空操做。在gstreamer编程中,都会在main()函数首先调用gst_init(NULL, NULL)函数,插件的注册登记是否会和它有关呢,看看它的实现。通过阅读代码,我们可以看到gst_init()函数的与插件注册相关的函数调用关系如下:
在1处,gst_init_get_option_group() 函数中,会设置post_parse_func函数指针指向 init_post(),这个最终对在2处进行调用。而在 init_post() 函数,会先准备gstreamer-core环境,检查当前环境有没有库更新,如果有,将会执行更新操作,这部分就是在3处进行的。在4ensure_current_registry() 函数中,将会检查一系列的环境变量,确认搜索库的路径,然后会在5gst_registry_scan_path_level() 进行循环,搜索相应的库进行加载,最后是在 gst_registry_scan_plugin_file() 中,通过 _priv_gst_plugin_loader_funcs.load 成员函数plugin_loader_load()进行plugin加载。
gstreamer plugin加载方式——协助者
所谓的plugin加载,并不是什么动态库加载,动态库的加载过程,在程序一开始运行的时候就已经加载了,我们所说的plugin注册过程(或者说加载过程),只是从机器端的相应路径搜索gstreamer的plugin库,同时将相应的plugin信息保存下来,方便后续在查找使用的时候,可以加快这一个过程。大家都知道,什么搜索啊,查找啊,都是比较耗时的,所以gstreamer将plugin的库都放到同一个路径下,比如/usr/lib/gstreamer-1.0,而一些并没有plugin信息的库,则是和平常的库没有什么区别,放到/usr/lib/目录下,通过这样的方式,减少搜索的范围。同时,gstreamer还通过创建子进程的方式,让子进程来帮忙获取plugin的信息。详细是怎么做的呢,接下来我们看看 plugin_loader_load() 函数是如何获取plugin相关信息。plugin_loader_load()声明如下:
static gboolean
plugin_loader_load (GstPluginLoader * loader, const gchar * filename,
off_t file_size, time_t file_mtime)
函数也就四个参数,一个是loader,将会通过它保存plugin的信息,filename则是需要检查的库路径,file_size则是这个库的大小,file_mtime则是库的更新时间。通过函数声明的参数来看,通过库的路径获取库,从而获取plugin信息,同时保存库的大小以及最后的修改时间,以确定是否需要更新。在plugin_loader_load()函数中,将会通过gst_plugin_loader_spawn (loader)函数创建子进程,该函数的具体实现在这里就不再述说了,它的主要流程大概如下:
通过检查环境变量GST_PLUGIN_SCANNER_1_0以及GST_PLUGIN_SCANNER,检测是否有指定的gst-plugin-scanner;
如果没有,将会检查,编译的时候Makefile有没有传进GST_PLUGIN_SCANNER_INSTALLED;
一般的,会通过Makefile获取到scanner为/usr/lib/gstreamer-1.0/gst-plugin-scanner;
然后,通过fork()创建子进程运行gst-plugin-scanner,父进程将从plugin的库路径搜索库,并检查有效性,而子进程则是负责从相应的库中获取plugin信息并反馈;
可能有人会问,那么是通过创建一个子进程帮忙获取plugin信息的,那么他们父子进程是怎么通信的呢,这个正是通过linux系统的管道进行通信的,详细的创建过程,有兴趣可以看看代码的实现。
gstreamer plugin加载过程——起端(父进程)
父进程,也就是我们的主体运行程序,在找到相应的库的时候,将会通过下面的那种形式,将库的相关信息通过管道发送到子进程gst-plugin-scanner。
entry = g_slice_new (PendingPluginEntry);
entry->tag = loader->next_tag++;
entry->filename = g_strdup (filename);
entry->file_size = file_size;
entry->file_mtime = file_mtime;
loader->pending_plugins_tail = g_list_append (loader->pending_plugins_tail, entry);
if (loader->pending_plugins == NULL)
loader->pending_plugins = loader->pending_plugins_tail;
else
loader->pending_plugins_tail = g_list_next (loader->pending_plugins_tail);
len = strlen (filename);
put_packet (loader, PACKET_LOAD_PLUGIN, entry->tag, (guint8 *) filename, len + 1);
通过上面的代码,我们可以清晰的看到,在调用put_packet()
函数将消息push到管道之前,会有个tag标记现在是第几个plugin,以及文件的路径、大小、更新时间等信息,同时会通过一个链表保存这些信息,最后发送一个PACKET_LOAD_PLUGIN消息到管道,接下来,就是子进程gst-plugin-scanner表演的时候了。
gstreamer plugin加载过程——解决(子进程)
上述已经说了,在父进程通过管道将PACKET_LOAD_PLUGIN类型的消息到子进程gst-plugin-scanner,那么子进程又将会进行什么操作呢,下面我们来看看。gst-plugin-scanner的源码是gstreamer-1.xx.0/libs/gst/helpers目录下的gst-plugin-scanner.c:
int
main (int argc, char *argv[])
{
gboolean res;
char **my_argv;
int my_argc;
/* We may or may not have an executable path */
if (argc != 2 && argc != 3)
return 1;
if (strcmp (argv[1], "-l"))
return 1;
my_argc = 2;
my_argv = g_malloc (my_argc * sizeof (char *));
my_argv[0] = argv[0];
my_argv[1] = (char *) "--gst-disable-registry-update";
#ifndef GST_DISABLE_REGISTRY
_gst_disable_registry_cache = TRUE;
#endif
if (argc == 3)
_gst_executable_path = g_strdup (argv[2]);
res = gst_init_check (&my_argc, &my_argv, NULL);
g_free (my_argv);
if (!res)
return 1;
/* Create registry scanner listener and run */
if (!_gst_plugin_loader_client_run ())
return 1;
return 0;
}
通过上面可以看到,就是通过检查运行时候的输入参数,参数一般为-l + 库的查找根路径,而后在_gst_plugin_loader_client_run ()
中循环等待父进程发过来的消息并处理。而在_gst_plugin_loader_client_run ()
函数中设置好管道的接收和发送端口之后,就是通过以下循环进行接收处理操作的。
/* Loop, listening for incoming packets on the fd and writing responses */
while (!l->rx_done && exchange_packets (l));
而在exchange_packets()中,将会检查,是否有数据可以进行接收或者发送,如果有,将会进行相应的处理。现在,我们假设,父进程已经发送PACKET_LOAD_PLUGIN
类型的数据过来,接下来,子进程gst-plugin-scanner将会在exchange_packets()函数中,见过一系列的检查之后,通过read_one()
函数进行处理。而在read_one()
函数中,按照协商好的协议,读取packet,再通过以下函数处理:
return handle_rx_packet (l, l->rx_buf[0], tag,
l->rx_buf + HEADER_SIZE, packet_len);
而在handle_rx_packet()函数中,根据pack_type的类型,即l->rx_buf[0]
的值进行相应的操作,比如加载plugin是PACKET_LOAD_PLUGIN
,将会调用do_plugin_load()函数进行处理。
case PACKET_LOAD_PLUGIN:{
if (!l->is_child)
return TRUE;
/* Payload is the filename to load */
res = do_plugin_load (l, (gchar *) payload, tag);
break;
}
在do_plugin_load()函数中,先通过gst_plugin_load_file()
加载plugin并将相应信息反馈父进程。
static gboolean
do_plugin_load (GstPluginLoader * l, const gchar * filename, guint tag)
{
GstPlugin *newplugin;
GList *chunks = NULL;
GST_DEBUG ("Plugin scanner loading file %s. tag %u", filename, tag);
/* 通过库的文件路径,搜索库并得到相应的plugin数据 */
newplugin = gst_plugin_load_file ((gchar *) filename, NULL);
if (newplugin) {
guint hdr_pos;
guint offset;
/* 将plugin信息保存到chunks */
/* Now serialise the plugin details and send */
if (!_priv_gst_registry_chunks_save_plugin (&chunks,gst_registry_get (), newplugin))
goto fail;
/* Store where the header is, write an empty one, then write
* all the payload chunks, then fix up the header size */
hdr_pos = l->tx_buf_write;
offset = HEADER_SIZE;
/* 发送PACKET_PLUGIN_DETAILS类型消息 */
put_packet (l, PACKET_PLUGIN_DETAILS, tag, NULL, 0);
if (chunks) {
GList *walk;
for (walk = chunks; walk; walk = g_list_next (walk)) {
GstRegistryChunk *cur = walk->data;
/* 发送external deps、plugin features、element desc等信息 */
put_chunk (l, cur, &offset);
_priv_gst_registry_chunk_free (cur);
}
g_list_free (chunks);
/* Store the size of the written payload */
GST_WRITE_UINT32_BE (l->tx_buf + hdr_pos + 4, offset - HEADER_SIZE);
}
gst_object_unref (newplugin);
} else {
put_packet (l, PACKET_PLUGIN_DETAILS, tag, NULL, 0);
}
return TRUE;
}
不知道大家有没有发现,在_priv_gst_plugin_load_file_for_registry()函数中,先后调用了gst_plugin_register_func()和gst_registry_add_plugin()函数,这两个函数,不正是第二节说到的宏定义GST_PLUGIN_DEFINE实现展开中gst_plugin_name_register()函数中所调用的gst_plugin_register_static()函数中的具体实现吗?原来,在相应的plugin注册函数中的相应函数,只是使用了gst_plugin_name_get_desc()函数,而gst_plugin_name_register()函数则是没有调用,而是在子进程进行插件注册的时候,直接调用了更底层的函数动态完成plugin注册的这一过程。
通过gst_plugin_register_func()函数进行plugin的注册,gst_plugin_register_func()将会检查版本之类的信息,然后拷贝关于plugin的描述信息,在通过函数指针desc->plugin_init真正的完成plugin的注册(desc->plugin_init函数指针指向的就是上述说到的plugin_init()函数),这样,就调用到了plugin_init()。gst_registry_add_plugin()函数与gst_registry_add_feature()函数类似,gst_registry_add_feature()是将feature保存到全局变量的_gst_registry_default的priv->features成员,而gst_registry_add_plugin()函数则是将plugin保存到_gst_registry_default的priv->plugins成员。
我们再回到do_plugin_load()函数,在通过gst_plugin_load_file()函数获取库中的plugin信息之后,子进程将会把相应的信息通过管道发送给父进程,至此,子进程的协助工作就相当于是完成了一次,下面我们将切换回父进程,看看父进程又是如何处理这些信息的。
gstreamer plugin加载过程——保存完成(父进程)
前面已经说到,当父进程在指定的路径下搜索到相应的库时,将相应的信息发给子进程之后,将会运行到以下程序:
if (!exchange_packets (loader)) {
if (!plugin_loader_replay_pending (loader))
return FALSE;
}
父进程发送完消息之后,也将会通过exchange_packets()
函数处理消息,管道消息的真正发送也是在这里的,前面的put_packet()函数只是封装消息packet。当子进程完成plugin信息的注册时将会发送PACKET_PLUGIN_DETAILS
类型的消息,下面我们来看看,父进程是如何处理该消息的。
case PACKET_PLUGIN_DETAILS:{
gchar *tmp = (gchar *) payload;
PendingPluginEntry *entry = NULL;
GList *cur;
/* 在我们发送PACKET_LOAD_PLUGIN消息给子进程的时候,
* 我们就已经将plugin的一些信息保存到l->pending_plugins,
* 所以现在我们得到反馈,也应该检查一下,是哪个plugin */
cur = l->pending_plugins;
while (cur) {
PendingPluginEntry *e = (PendingPluginEntry *) (cur->data);
if (e->tag > tag)
break;
if (e->tag == tag) {
entry = e;
break;
} else {
cur = g_list_delete_link (cur, cur);
g_free (e->filename);
g_slice_free (PendingPluginEntry, e);
}
}
l->pending_plugins = cur;
if (cur == NULL)
l->pending_plugins_tail = NULL;
if (payload_len > 0) {
GstPlugin *newplugin = NULL;
/* 通过_priv_gst_registry_chunks_load_plugin()函数解析数据包 */
if (!_priv_gst_registry_chunks_load_plugin (l->registry, &tmp,
tmp + payload_len, &newplugin)) {
return FALSE;
}
GST_OBJECT_FLAG_UNSET (newplugin, GST_PLUGIN_FLAG_CACHED);
newplugin->registered = TRUE;
/* We got a set of plugin details - remember it for later */
l->got_plugin_details = TRUE;
} else if (entry != NULL) {
/* Create a blacklist entry for this file to prevent scanning every time */
plugin_loader_create_blacklist_plugin (l, entry);
l->got_plugin_details = TRUE;
}
/* Remove the plugin entry we just loaded */
cur = l->pending_plugins;
if (cur != NULL)
cur = g_list_delete_link (cur, cur);
l->pending_plugins = cur;
if (cur == NULL)
l->pending_plugins_tail = NULL;
break;
}
从上面我们可以看到,具体的数据包解析是在_priv_gst_registry_chunks_load_plugin()函数中,需要注意的,该函数的第一个参数,传进的是l->registry,实质上就是通过gst_registry_get ()函数获取到的静态全局变量指针_gst_registry_default,所以显然,plugin的信息是保存到_gst_registry_default指向的数据区域。
在_priv_gst_registry_chunks_load_plugin()函数中,除了保存plugin的更新时间、文件大小、desc信息以及将plugin添加到_gst_registry_defaultwait,还会将plugin features以及相应的外部依赖保存到_gst_registry_defaultwait。至此,plugin注册完成,相应信息保存到全局变量_gst_registry_default中,可通过gst_registry_get()函数获取该指针。
就是这样,通过两个进程的协作,完成plugin库的相关信息搜索、保存,当plugin都查找完之后,将会给子进程发送退出信号,至此,子进程功成身退。
四、总结
前面说到的,在gst_registry_add_feature()函数中,最后会通过g_signal_emit (registry, gst_registry_signals[FEATURE_ADDED], 0, feature)发出信号,这个信号最后是谁接收的,我们现在还不知道,后续学习过程中,如果发现这个点,再回头进程论述。
在plugin注册的过程,我们还有很多是没有提到的,比如,上面提到的,会保存plugin的外部依赖等信息,是在plugin_init()函数中,通过gst_plugin_add_dependency()函数设置的,通过设置该函数,可以使plugin随硬件的变换而具备不同的属性。同时,通过保存plugin的库更新时间以及大小,是为了下一次进行plugin库扫描的时候,可以将没有修改的库舍弃,减少扫描时间。以及,我们上面所述说的,plugin的信息都是保存到静态全局变量_gst_registry_default中,如果说,我们的程序退出了相应的信息也就没有了,但是大家都会发现,我们只有第一次运行gstreamer的时候,启动速度很慢,接下来的启动都是以比较快的速度启动gstreamer的,原因就在于,在扫描完plugin库的信息之后,会将_gst_registry_default保存的数据信息通过priv_gst_registry_binary_write_cache()函数保存为二进制文件,在下一次启动gstreamer的时候,可以直接读取文件获得plugin的信息而不用扫描。
gst_plugin_name_get_desc(),然后在gstreamer启动过程,通过获取库的gst_plugin_name_get_desc()函数取得plugin信息,这样gstreamer core就知道,它自己具备什么plugin,可以实现什么功能,在需要的时候就可以进行调用。