Gstreamer学习笔记(7):plugin注册流程分析(超详细)

原文链接: https://blog.csdn.net/weixin_41944449/article/details/81267842

一、从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()代码有点多,就没有直接贴代码,下面通过流程图介绍一下它的工作与步骤。
  
Gstreamer学习笔记(7):plugin注册流程分析(超详细)_第1张图片

通过阅读代码最后我们知道,之前我们的猜测是错误的,传进的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的信号,这个信息是谁来进行接收呢,我们再继续看代码。

二、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()函数,又将会是被谁调用呢,我们接着往下看。

三、plugin_init()去向

上面一节我们说到gst_plugin_name_register()函数是被谁调用,在GST_PLUGIN_DEFINE的展开我们可以看到,gst_plugin_name_register()函数是被GST_PLUGIN_EXPORT修饰的,但是,通过查看gstconfig.h中GST_PLUGIN_EXPORT的解析我们可以知道,这个宏只是在Windows导出符号给DLL,其他平台更多的是为空操作。那么是不是说我们刚才的上面一节的分析是不是都是错误的呢?嗯,是的,起码在Linux平台我们的分析是有点问题了,上述描述的更多的是静态加载,在使用过程中,更多的是动态加载,但是先别急,接着往下看。

在gstreamer编程中,都会在main()函数首先调用gst_init(NULL, NULL)函数,插件的注册登记是否会和它有关呢,看看它的实现。通过阅读代码,我们可以看到gst_init()函数的与插件注册相关的函数调用关系,我们重点关注plugin注册的部分,函数调用如下:

Gstreamer学习笔记(7):plugin注册流程分析(超详细)_第2张图片

在1: gst_init_get_option_group() 函数中,会设置post_parse_func函数指针指向 init_post(),这个最终对在2:处进行调用。而在 init_post() 函数,会先准备gstreamer-core环境,检查当前环境有没有库更新,如果有,将会执行更新操作,这部分就是在3:处进行的。在4: ensure_current_registry() 函数中,将会检查一系列的环境变量,确认搜索库的路径,然后会在5: gst_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)函数创建子进程,该函数的具体实现在这里就不再述说了,它的主要流程大概如下:

  1. 通过检查环境变量GST_PLUGIN_SCANNER_1_0以及GST_PLUGIN_SCANNER,检测是否有指定的gst-plugin-scanner
  2. 如果没有,将会检查,编译的时候Makefile有没有传进GST_PLUGIN_SCANNER_INSTALLED
  3. 一般的,会通过Makefile获取到scanner为/usr/lib/gstreamer-1.0/gst-plugin-scanner;
  4. 然后,通过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;
}

通过上述的do_plugin_load()函数实现,了解到子进程gst-plugin-scanner将会把plugin的外部依赖、支持的features、以及element desc等信息反馈到父进程,所以我们大概可以了解到gst_plugin_load_file()函数的操作。

gst_plugin_load_file()函数也是通过_priv_gst_plugin_load_file_for_registry()函数完成插件信息的获取,函数主体调用流程如下:

/* Note: The return value is (transfer full) although we work with floating
* references here. If a new plugin instance is created, it is always sinked
* in the registry first and a new reference is returned
*/
GstPlugin *
_priv_gst_plugin_load_file_for_registry (const gchar * filename,
										GstRegistry * registry, GError ** error)
{
	if (registry == NULL)
		registry = gst_registry_get ();
	
	g_mutex_lock (&gst_plugin_loading_mutex);
	
	/* 在registry中检查,该路径的库是否已经注册,以及会进行一系列的文件检查操作 */
	...
	
	flags = G_MODULE_BIND_LOCAL;
	
	/* 通过g_module_open打开库并获取相应的句柄 */
	module = g_module_open (filename, flags);
	
	/* 通过extract_symname()函数获取plugin的gst_plugin_name_get_desc函数名称, */
	/* 然后通过g_module_symbol()函数调用dlsym()从库中获取函数地址 */
	symname = extract_symname (filename);
	ret = g_module_symbol (module, symname, &ptr);
	
	if (ret) {
		/* 调用第二节中说到的GST_PLUGIN_DEFINE展开的gst_plugin_name_get_desc()函数 */
		GstPluginDesc *(*get_desc) (void) = ptr;
		ptr = get_desc ();
	} else {
		GST_DEBUG ("Could not find symbol '%s', falling back to gst_plugin_desc",symname);
		ret = g_module_symbol (module, "gst_plugin_desc", &ptr);
	}
	
	g_free (symname);
	
	desc = (const GstPluginDesc *) ptr;
	
	/* 保存库的更新时间、大小、文件名称等信息到plugin */
	if (new_plugin) {
		plugin = g_object_new (GST_TYPE_PLUGIN, NULL);
		plugin->file_mtime = file_status.st_mtime;
		plugin->file_size = file_status.st_size;
		plugin->filename = g_strdup (filename);
		plugin->basename = g_path_get_basename (filename);
	}
	
	plugin->module = module;
	
	if (new_plugin) {
		/* check plugin description: complain about bad values and fail */
		CHECK_PLUGIN_DESC_FIELD (desc, name, filename);
	    CHECK_PLUGIN_DESC_FIELD (desc, description, filename);
	    CHECK_PLUGIN_DESC_FIELD (desc, version, filename);
	    CHECK_PLUGIN_DESC_FIELD (desc, license, filename);
	    CHECK_PLUGIN_DESC_FIELD (desc, source, filename);
	    CHECK_PLUGIN_DESC_FIELD (desc, package, filename);
	    CHECK_PLUGIN_DESC_FIELD (desc, origin, filename);
    }
	/* this is where we load the actual .so, so let's trap SIGSEGV */
	_gst_plugin_fault_handler_setup ();
	_gst_plugin_fault_handler_filename = plugin->filename;
	
	/* 在这里,调用了gst_plugin_register_func()函数 */
	if (!gst_plugin_register_func (plugin, desc, NULL)) {
	...
	}

	/* remove signal handler */
	_gst_plugin_fault_handler_restore ();
	_gst_plugin_fault_handler_filename = NULL;
	GST_INFO ("plugin \"%s\" loaded", plugin->filename);

	if (new_plugin) {
	gst_object_ref (plugin);
	/* 调用gst_registry_add_plugin()函数 */
	gst_registry_add_plugin (registry, plugin);
	}

	g_mutex_unlock (&gst_plugin_loading_mutex);
	return plugin;
}

不知道童鞋有没有发现,在_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()函数和gst_registry_add_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,可以实现什么功能,在需要的时候就可以进行调用。**

你可能感兴趣的:(GStreamer)