gstreamer插件scan过程
gstreamer plugin注册(加载)的过程,是从机器端的相应路径搜索gstreamer的plugin库,同时将相应的plugin信息保存下来,方便后续在查找使用的时候,能够加快这一个过程。
gstreamer将plugin的库都放到同一个路径下,比如/usr/lib/gstreamer-1.0
,将无plugin信息的库,放到/usr/lib/
目录下,经过这样的方式,减小搜索的范围。同时,gstreamer还经过建立辅助子进程的方式,让子进程来帮忙获取plugin的信息。
这个就要以plugin_loader_load()
函数为入口,通过分析plugin_loader_load的load过程,以及和scanner子进程的交互,来理解scan的过程。
plugin_loader_load说明
plugin_loader_load负责创建gst-plugin-scanner子进程,并且向scanner进程下发扫描插件的指令,并且通过socket和scanner进程通讯,完成插件的更新。
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_loader_load()
函数中,将会经过gst_plugin_loader_spawn (loader)
函数建立子进程:
static gboolean
plugin_loader_load (GstPluginLoader * loader, const gchar * filename,
off_t file_size, time_t file_mtime)
{
gint len;
PendingPluginEntry *entry;
/* 创建子进程gst-plugin-scanner */
if (!gst_plugin_loader_spawn (loader))
return FALSE;
/* Send a packet to the child requesting that it load the given file */
GST_LOG_OBJECT (loader->registry,
"Sending file %s to child. tag %u", filename, loader->next_tag);
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);
/* 发送一个PACKET_LOAD_PLUGIN消息到子进程 */
len = strlen (filename);
put_packet (loader, PACKET_LOAD_PLUGIN, entry->tag,
(guint8 *) filename, len + 1);
if (!exchange_packets (loader)) {
if (!plugin_loader_replay_pending (loader))
return FALSE;
}
return TRUE;
}
- 检查环境变量
GST_PLUGIN_SCANNER_1_0
以及GST_PLUGIN_SCANNER
,检测是否有指定的gst-plugin-scanner
- 若是没有,将会检查有没有
GST_PLUGIN_SCANNER_INSTALLED
- 获取到
gst-plugin-scanner
后,经过fork()建立子进程运行gst-plugin-scanner
,父进程将从plugin的库路径搜索库,并检查有效性,而子进程则是负责从相应的库中获取plugin信息并返回。
plugin_loader_load的流程
顺便看一下进入plugin_loader_load的流程,最后再看gst-plugin-scanner,plugin_loader_load的是在_priv_gst_plugin_loader_funcs
中指定的:
/* functions used in GstRegistry scanning */
const GstPluginLoaderFuncs _priv_gst_plugin_loader_funcs = {
plugin_loader_new, plugin_loader_free, plugin_loader_load
};
然后在gst_registry_scan_plugin_file中根据helper_state调用load函数:
/* Have a plugin to load - see if the scan-helper needs starting */
if (context->helper_state == REGISTRY_SCAN_HELPER_NOT_STARTED) {
GST_DEBUG ("Starting plugin scanner for file %s", filename);
context->helper = _priv_gst_plugin_loader_funcs.create (context->registry);
if (context->helper != NULL)
context->helper_state = REGISTRY_SCAN_HELPER_RUNNING;
else {
GST_WARNING ("Failed starting plugin scanner. Scanning in-process");
context->helper_state = REGISTRY_SCAN_HELPER_DISABLED;
}
}
if (context->helper_state == REGISTRY_SCAN_HELPER_RUNNING) {
GST_DEBUG ("Using scan-helper to load plugin %s", filename);
/* 调用plugin_loader_load,如果返回false,说明辅助程序没有找到,helper状态就设置为disabled */
if (!_priv_gst_plugin_loader_funcs.load (context->helper,
filename, file_size, file_mtime)) {
g_warning ("External plugin loader failed. This most likely means that "
"the plugin loader helper binary was not found or could not be run. "
"You might need to set the GST_PLUGIN_SCANNER environment variable "
"if your setup is unusual. This should normally not be required "
"though.");
context->helper_state = REGISTRY_SCAN_HELPER_DISABLED;
}
}
如果helper_state是disabled状态,就不会fork子进程,以老式方式加载插件
,即在同一个进程中扫描加载插件。
上面这段代码是在gst_registry_scan_plugin_file中调用的,存在下面的调用栈(从下往上),init_post中调用gst_update_registry更新registry,ensure_current_registry中获取GST_REGISTRY
指定的cache文件,如果没有cache文件,直接进行do_update操作,进入scan_and_update_registry。如果有cache文件,do_update还依赖于GST_DISABLE_REGISTRY
和GST_REGISTRY_UPDATE
的值。
1 gst_registry_scan_plugin_file gstregistry.c 1167
2 gst_registry_scan_path_level gstregistry.c 1357
3 gst_registry_scan_path_internal gstregistry.c 1384
4 scan_and_update_registry gstregistry.c 1679
5 ensure_current_registry gstregistry.c 1813
6 gst_update_registry gstregistry.c 1890
7 init_post gst.c 830
ensure_current_registry
ensure_current_registry中调用scan_and_update_registry启动scan和update:
if (do_update) {
const gchar *reuse_env;
if ((reuse_env = g_getenv ("GST_REGISTRY_REUSE_PLUGIN_SCANNER"))) {
/* do reuse for any value different from "no" */
__registry_reuse_plugin_scanner = (strcmp (reuse_env, "no") != 0);
}
/* now check registry */
GST_DEBUG ("Updating registry cache");
scan_and_update_registry (default_registry, registry_file, TRUE, error);
} else {
GST_DEBUG ("Not updating registry cache (disabled)");
}
然后,在scan_and_update_registry先通过init_scan_context初始化registry,最后调用gst_registry_scan_path_internal扫描GST_PLUGIN_PATH
指定路径中的插件库。
init_scan_context
init_scan_context中获取GST_REGISTRY_FORK
环境变量,确定scanner helper_state的状态:
static void
init_scan_context (GstRegistryScanContext * context, GstRegistry * registry)
{
gboolean do_fork;
context->registry = registry;
/* see if forking is enabled and set up the scan helper state accordingly */
do_fork = _gst_enable_registry_fork;
if (do_fork) {
const gchar *fork_env;
/* forking enabled, see if it is disabled with an env var */
if ((fork_env = g_getenv ("GST_REGISTRY_FORK"))) {
/* fork enabled for any value different from "no" */
do_fork = strcmp (fork_env, "no") != 0;
}
}
if (do_fork)
context->helper_state = REGISTRY_SCAN_HELPER_NOT_STARTED;
else
context->helper_state = REGISTRY_SCAN_HELPER_DISABLED;
context->helper = NULL;
context->changed = FALSE;
}
gst_registry_scan_path_level
gst_registry_scan_path_level中会根据文件的状态去判断是否发生了改变,是个递归调用。
/* If a file with a certain basename is seen on a different path,
* update the plugin to ensure the registry cache will reflect up
* to date information */
if (plugin->file_mtime == file_status.st_mtime &&
plugin->file_size == file_status.st_size && !env_vars_changed &&
!(deps_changed = _priv_plugin_deps_files_changed (plugin)) &&
!strcmp (plugin->filename, filename)) {
GST_LOG_OBJECT (context->registry, "file %s cached", filename);
GST_OBJECT_FLAG_UNSET (plugin, GST_PLUGIN_FLAG_CACHED);
GST_LOG_OBJECT (context->registry,
"marking plugin %p as registered as %s", plugin, filename);
plugin->registered = TRUE;
} else {
GST_INFO_OBJECT (context->registry, "cached info for %s is stale",
filename);
GST_DEBUG_OBJECT (context->registry, "mtime %" G_GINT64_FORMAT " != %"
G_GINT64_FORMAT " or size %" G_GINT64_FORMAT " != %"
G_GINT64_FORMAT " or external dependency env_vars changed: %d or"
" external dependencies changed: %d or old path %s != new path %s",
(gint64) plugin->file_mtime, (gint64) file_status.st_mtime,
(gint64) plugin->file_size, (gint64) file_status.st_size,
env_vars_changed, deps_changed, plugin->filename, filename);
gst_registry_remove_plugin (context->registry, plugin);
changed |= gst_registry_scan_plugin_file (context, filename,
file_status.st_size, file_status.st_mtime);
}
gst_object_unref (plugin);
} else {
GST_DEBUG_OBJECT (context->registry, "file %s not yet in registry",
filename);
changed |= gst_registry_scan_plugin_file (context, filename,
file_status.st_size, file_status.st_mtime);
}
最后在scan_and_update_registry中根据change状态,决定是否需要更新registry文件:
if (!write_changes) {
GST_INFO ("Registry cache changed, but writing is disabled. Not writing.");
return REGISTRY_SCAN_AND_UPDATE_FAILURE;
}
GST_INFO ("Registry cache changed. Writing new registry cache");
if (!priv_gst_registry_binary_write_cache (default_registry,
default_registry->priv->plugins, registry_file)) {
g_set_error (error, GST_CORE_ERROR, GST_CORE_ERROR_FAILED,
_("Error writing registry cache to %s: %s"),
registry_file, g_strerror (errno));
return REGISTRY_SCAN_AND_UPDATE_FAILURE;
}
这部分是在plugin_loader_load之前,和gst-plugin-scanner
相关的还是要回到前面的plugin_loader_load部分。
子进程gst-plugin-scanner
gst-plugin-scanner子进程在前面说过,在plugin_loader_load中fork出来,处理父进程交给它的scan任务,最后返回plugin的信息给父进程。
subprojects/gstreamer/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;
/* 在这个run函数内部循环等待父进程发过来的消息并处理 */
/* Create registry scanner listener and run */
if (!_gst_plugin_loader_client_run ())
return 1;
return 0;
}
_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()函数进行处理。
static gboolean
exchange_packets (GstPluginLoader * l)
{
gint res;
/* Wait for activity on our FDs */
do {
do {
res = gst_poll_wait (l->fdset, GST_SECOND);
} while (res == -1 && (errno == EINTR || errno == EAGAIN));
if (res < 0)
return FALSE;
GST_LOG ("Poll res = %d. %d bytes pending for write", res,
l->tx_buf_write - l->tx_buf_read);
if (!l->rx_done) {
if (gst_poll_fd_has_error (l->fdset, &l->fd_r)) {
GST_LOG ("read fd %d errored", l->fd_r.fd);
goto fail_and_cleanup;
}
if (gst_poll_fd_can_read (l->fdset, &l->fd_r)) {
/* 经过read_one()函数进行 */
if (!read_one (l))
goto fail_and_cleanup;
} else if (gst_poll_fd_has_closed (l->fdset, &l->fd_r)) {
GST_LOG ("read fd %d closed", l->fd_r.fd);
goto fail_and_cleanup;
}
}
if (l->tx_buf_read < l->tx_buf_write) {
if (gst_poll_fd_has_error (l->fdset, &l->fd_w)) {
GST_ERROR ("write fd %d errored", l->fd_w.fd);
goto fail_and_cleanup;
}
if (gst_poll_fd_can_write (l->fdset, &l->fd_w)) {
if (!write_one (l))
goto fail_and_cleanup;
} else if (gst_poll_fd_has_closed (l->fdset, &l->fd_w)) {
GST_LOG ("write fd %d closed", l->fd_w.fd);
goto fail_and_cleanup;
}
}
} while (l->tx_buf_read < l->tx_buf_write);
// ...
}
而在read_one()
函数中,按照协商好的协议,读取解析rx_buf,再经过如下handle_rx_packet函数处理收到的数据rx_buf:
TX and RX are abbreviations for Transmit and Receive
- rx:receive
- tx:transmit
return handle_rx_packet (l, l->rx_buf[0], tag,
l->rx_buf + HEADER_SIZE, packet_len);
这里l是GstPluginLoader对象,l->rx_buf[0]
的值就是pack_type的类型,pack_type是PACKET_LOAD_PLUGIN
,在handle_rx_packet中,根据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;
// ..
}
scan相关的环境变量和option
REGISTRY和PLUGIN相关的环境变量
GST_REGISTRY
GST_REGISTRY_UPDATE
GST_REGISTRY_FORK
GST_PLUGIN_PATH
GST_PLUGIN_SCANNER
GST_PLUGIN_SCANNER_1_0
GST_DISABLE_REGISTRY_DEFINE
option
–gst-disable-registry-fork
可以通过--gst-disable-registry-fork
禁用fork
subprojects/gstreamer/gst/gst.c
parse_goption_arg:
{
"--gst-plugin-spew", ARG_PLUGIN_SPEW}, {
"--gst-plugin-path", ARG_PLUGIN_PATH}, {
"--gst-plugin-load", ARG_PLUGIN_LOAD}, {
"--gst-disable-segtrap", ARG_SEGTRAP_DISABLE}, {
"--gst-disable-registry-update", ARG_REGISTRY_UPDATE_DISABLE}, {
"--gst-disable-registry-fork", ARG_REGISTRY_FORK_DISABLE}, {
NULL}
};
parse_one_option:
case ARG_REGISTRY_FORK_DISABLE:
gst_registry_fork_set_enabled (FALSE);
–gst-plugin-path
等同于GST_PLUGIN_PATH
/* GST_PLUGIN_PATH specifies a list of directories to scan for
* additional plugins. These take precedence over the system plugins */
plugin_path = g_getenv ("GST_PLUGIN_PATH_1_0");
if (plugin_path == NULL)
plugin_path = g_getenv ("GST_PLUGIN_PATH");
if (plugin_path) {
char **list;
int i;