gstreamer学习笔记---plugin注册流程分析(超详细)

  通过了解Gstreamer框架,我们可以知道,每个处理单元都是一个plugin,那么,plugin在gstreamer是怎么注册到系统中的呢,gstreamer又是如何知道,现在有哪些plugin,同时具备某一功能的plugin又有哪些,下面,我们来通过阅读源码,一起分析一下。

一、从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添加到plugin。在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()代码有点多,就没有直接贴代码,下面通过流程图介绍一下它的工作与步骤。

Created with Raphaël 2.1.2 传进参数plugin、name、rank和type 通过gst_registry_get()获取静态全局 GstRegistry型指针变量_gst_registry_default 通过name判断已注册的 plugin中是否存在相同的名称 如果已经存在相同的name,那么将替换factory->type return TRUE 创建一个新的GstElementFactory, 设置name、type和metadata等信息 添加type所属的klass padtemplates到facrory->staticpadtemplates 判断type是否是GST_TYPE_URI_HANDLER类型, 如果是,将会进行uri的一些操作 添加type支持的interfaces信息到factory->interfaces 添加plugin信息到factory 通过gst_plugin_feature_set_rank()函数设置factory的rank 通过gst_registry_add_feature()函数 将factory保存到_gst_registry_default指针 yes no

  通过阅读代码最后我们知道,之前我们的猜测是错误的,传进的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,就是gst_element_register()函数传进来的name命名的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注册的部分,函数调用如下:

Created with Raphaël 2.1.2 main函数调用gst_init() gst_init_check() 1:gst_init_get_option_group()设置post_parse_func为init_post() g_option_context_parse() 2:(*group->post_parse_func)() 3:gst_update_registryp() 4:ensure_current_registry() scan_and_update_registry() gst_registry_scan_path_internal() 5:gst_registry_scan_path_level() gst_registry_scan_plugin_file() _priv_gst_plugin_loader_funcs.load() 通过plugin_loader_load()注册plugin

  在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加载。
  接下来,我们将会详细的解析plugin_loader_load()函数的操作,看看gstreamer究竟是如何进行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 */

  /* 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()函数获取该指针。需要注意的,在gst_registry_chunks_load_feature()中,还根据不同的Factory类型进行不同的数据保存的,这个需要区分,但是它们都是通过同一个plugin保存到_gst_registry_default。
  就是这样,通过两个进程的协作,完成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的信息而不用扫描。
  gstreamer plugin加载过程,简单的说,就是将plugin的库放在固定路径,库中都会通过宏GST_PLUGIN_DEFINE定义了gst_plugin_name_get_desc(),然后在gstreamer启动过程,通过获取库的gst_plugin_name_get_desc()函数取得plugin信息,这样gstreamer core就知道,它自己具备什么plugin,可以实现什么功能,在需要的时候就可以进行调用。

你可能感兴趣的:(gstreamer)