GStreamer 的调试工具

目标

有时一些事情没有按照预期的运行,但从总线(bus)获得的错误消息也没有提供足够的信息。幸运地是,GStreamer 带有大量的调试信息,它们通常可以对哪里出了问题给出一些提示。这里将介绍:

  • 如何从 GStreamer 获得更多调试信息。
  • 如何把自己的调试信息打印到 GStreamer 日志里。
  • 如何获得管线图。

打印调试信息

调试日志

GStreamer 和它的插件充满了调试追踪,即,放置在有趣的特定代码片段处,信息片段将被打印到终端,伴随着时间戳,进程,种类,源码文件,函数,和元素信息。

调试输出由 GST_DEBUG 环境变量控制。这里有一个 GST_DEBUG=2 的例子:

0:00:00.868050000  1592   09F62420 WARN                 filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"

如你所见,这是相当多的信息。事实上,GStreamer 调试日志是如此的详细,当完全启用时,它会使应用程序失去响应(由于控制台滚动)或者填满数兆字节的文本文件(当重定向到一个文件时)。因此,日志是分类的,我们很少需要一次启用所有类别。

第一类是调试级别,它是一个指定了想要的输出的数字:

| # | Name    | Description                                                    |
|---|---------|----------------------------------------------------------------|
| 0 | none    | No debug information is output.                                |
| 1 | ERROR   | Logs all fatal errors. These are errors that do not allow the  |
|   |         | core or elements to perform the requested action. The          |
|   |         | application can still recover if programmed to handle the      |
|   |         | conditions that triggered the error.                           |
| 2 | WARNING | Logs all warnings. Typically these are non-fatal, but          |
|   |         | user-visible problems are expected to happen.                  |
| 3 | FIXME   | Logs all "fixme" messages. Those typically that a codepath that|
|   |         | is known to be incomplete has been triggered. It may work in   |
|   |         | most cases, but may cause problems in specific instances.      |
| 4 | INFO    | Logs all informational messages. These are typically used for  |
|   |         | events in the system that only happen once, or are important   |
|   |         | and rare enough to be logged at this level.                    |
| 5 | DEBUG   | Logs all debug messages. These are general debug messages for  |
|   |         | events that happen only a limited number of times during an    |
|   |         | object's lifetime; these include setup, teardown, change of    |
|   |         | parameters, etc.                                               |
| 6 | LOG     | Logs all log messages. These are messages for events that      |
|   |         | happen repeatedly during an object's lifetime; these include   |
|   |         | streaming and steady-state conditions. This is used for log    |
|   |         | messages that happen on every buffer in an element for example.|
| 7 | TRACE   | Logs all trace messages. Those are message that happen very    |
|   |         | very often. This is for example is each time the reference     |
|   |         | count of a GstMiniObject, such as a GstBuffer or GstEvent, is  |
|   |         | modified.                                                      |
| 9 | MEMDUMP | Logs all memory dump messages. This is the heaviest logging and|
|   |         | may include dumping the content of blocks of memory.           |
+------------------------------------------------------------------------------+

为了开启调试输出,可以把 GST_DEBUG 环境变量设置为需要的调试级别。所有设置的级别之下级别的日志也将被展示(比如,如果设置 GST_DEBUG=2,你将同时获得 ERRORWARNING 的消息)。

此外,每个插件或 GStreamer 的部分定义了它们自己的类别,因此,你可以给每一个单独的类别指定一个调试等级。比如,GST_DEBUG=2,audiotestsrc:6,将为 audiotestsrc 元素使用调试等级 6,为其它部分使用调试等级 2。

然后,GST_DEBUG 环境变量,是一个逗号分隔的 category:level 对的列表,在开头有一个可选的 level,表示所有类别的默认调试等级。

使用 '*' 通配符也是可以的。比如,GST_DEBUG=2,audio*:6 将为所有以 audio 开头的类别使用调试等级 5。GST_DEBUG=*:2 等价于 GST_DEBUG=2

使用 gst-launch-1.0 --gst-debug-help 来获取所有已经注册的类别。考虑到每个插件注册它自己的类别,因此,当安装或移除插件时,获得的这个列表可能会变化。

当在 GStreamer 总线上抛出的错误信息无助于帮你缩小问题的范围时使用 GST_DEBUG。将输出日志重定向到一个文件,稍后检查它,并从中搜索特定的消息是非常常见的一种实践。

GStreamer 允许定制调试信息处理程序,但当使用默认的处理程序时,调试输出中每一行的内容看起来像这样:

0:00:00.868050000  1592   09F62420 WARN                 filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"

这里是信息被格式化的方式:

| Example          | Explained                                                 |
|------------------|-----------------------------------------------------------|
|0:00:00.868050000 | Time stamp in HH:MM:SS.sssssssss format since the start of|
|                  | the program.                                              |
|1592              | Process ID from which the message was issued. Useful when |
|                  | your problem involves multiple processes.                 |
|09F62420          | Thread ID from which the message was issued. Useful when  |
|                  | your problem involves multiple threads.                   |
|WARN              | Debug level of the message.                               |
|filesrc           | Debug Category of the message.                            |
|gstfilesrc.c:1044 | Source file and line in the GStreamer source code where   |
|                  | this message was issued.                                  |
|gst_file_src_start| Function that issued the message.                         |
|<filesrc0>        | Name of the object that issued the message. It can be an  |
|                  | element, a pad, or something else. Useful when you have   |
|                  | multiple elements of the same kind and need to distinguish|
|                  | among them. Naming your elements with the name property   |
|                  | makes this debug output more readable but GStreamer       |
|                  | assigns each new element a unique name by default.        |
| error: No such   |                                                           |
| file ....        | The actual message.                                       |
+------------------------------------------------------------------------------+
添加你自己的调试信息

在你自己的与 GStreamer 交互的代码部分,使用 GStreamer 的调试工具也很有趣。用这种方式,你将所有调试输出都保存在同一个文件中,并且保留了不同消息之间的临时关系。

要做到这一点,可以使用 GST_ERROR()GST_WARNING()GST_INFO()GST_LOG()GST_DEBUG() 宏。它们接受与 printf 相同的参数,且它们使用 default 类别(在输出日志中 default 将被显示为调试类别)。

要想修改类别为其它更有意义的东西,则在你的代码的顶部添加如下两行:

GST_DEBUG_CATEGORY_STATIC (my_category);
#define GST_CAT_DEFAULT my_category

在用 gst_init() 初始化 GStreamer 之后的一行添加如下的行:

GST_DEBUG_CATEGORY_INIT (my_category, "my category", 0, "This is my very own");

这注册一个新的类别(也就是说,在应用程序运行期间:它不存储在任何文件中),并把它设置为你的代码的默认类别。参考文档来了解更多关于 GST_DEBUG_CATEGORY_INIT() 的内容。

获取管线的图

对于那种你的管线已经开始变得太大,且你已经丢失了对于哪个节点与另一个什么节点连接的追踪的情况,GStreamer 具有输出图文件的能力。它们是 .dot 文件,它们可以通过自由程序如 GraphViz 来读,它描述了你的管线的拓扑,以及每个连接上协商的能力。

当使用如 playbinuridecodebin 这样的在它们内部初始化多个元素的一体式元素的时候,这也非常方便。使用 .dot 文件学习它们已经在内部创建了什么样的管线(并在此过程中学习一些 GStreamer 相关的东西)。

为了获得 .dot 文件,简单地设置 GST_DEBUG_DUMP_DOT_DIR 环境变量指向你想要文件被放置的目录即可。gst-launch-1.0 将在每次状态改变时创建一个 .dot 文件,因此你可以看到功能协商的进化。重置该变量以禁用这个功能。在你的应用程序内,你可以使用 GST_DEBUG_BIN_TO_DOT_FILE()GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS() 宏来在你方便的时候生成 .dot 文件。

这里有一个 playbin 生成的管线的种类的例子。它非常复杂,因为 playbin 可以处理非常多种情况:你手动搭建的管线通常不需要这么长。如果你手动创建的管线开始变得非常大,则可以考虑使用 playbin

playbin

要下载全尺寸图片,使用本页顶部的附件链接(即回形针图标)。

GST_DEBUG_DUMP_DOT_DIR 环境变量在 gstreamer/tools/gst-launch.c 源文件中有获取,在 gstreamer/tools/gst-launch.c 源文件中可以看到如下这段代码:

#ifdef G_OS_UNIX
static gboolean
hup_handler (gpointer user_data)
{
  GstElement *pipeline = (GstElement *) user_data;

  if (g_getenv ("GST_DEBUG_DUMP_DOT_DIR") != NULL) {
    PRINT ("SIGHUP: dumping dot file snapshot ...\n");
  } else {
    PRINT ("SIGHUP: not dumping dot file snapshot, GST_DEBUG_DUMP_DOT_DIR "
        "environment variable not set.\n");
  }

  /* dump graph on hup */
  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (pipeline),
      GST_DEBUG_GRAPH_SHOW_ALL, "gst-launch.snapshot");

  return G_SOURCE_CONTINUE;
}
#endif

可以看到,这个环境变量接口只有在类 Unix 系统中才工作。但这里也仅仅是获取了一下,并检查了获取的值而已。这里我们没有看到任何有关打开文件或者写文件之类的操作。将媒体流处理的管线写入文件的操作也是通过宏 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS() 完成的。

除了 gstreamer/tools/gst-launch.c 源文件外,在源文件 gstreamer/gst/gst.c 中也获取了这个环境变量:

static gboolean
init_pre (GOptionContext * context, GOptionGroup * group, gpointer data,
    GError ** error)
{
  gchar *libdir;
  if (gst_initialized) {
    GST_DEBUG ("already initialized");
    return TRUE;
  }

  find_executable_path ();

  _priv_gst_start_time = gst_util_get_timestamp ();

#ifndef GST_DISABLE_GST_DEBUG
  _priv_gst_debug_init ();
  priv_gst_dump_dot_dir = g_getenv ("GST_DEBUG_DUMP_DOT_DIR");
#endif

init_pre() 这个函数在 GStreamer 初始化时,将获取的环境变量 GST_DEBUG_DUMP_DOT_DIR 的值,即要写入管线图文件的目录保存在全局变量 priv_gst_dump_dot_dir 中。

在源文件 gstreamer/gst/gstdebugutils.c 的用于将管线的图写入文件的 gst_debug_bin_to_dot_file() 函数中,读取全局变量 priv_gst_dump_dot_dir 的值,来获得要保存文件的目录,并将管线图写入文件:

void
gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details,
    const gchar * file_name)
{
  gchar *full_file_name = NULL;
  FILE *out;

  g_return_if_fail (GST_IS_BIN (bin));

  if (G_LIKELY (priv_gst_dump_dot_dir == NULL))
    return;

  if (!file_name) {
    file_name = g_get_application_name ();
    if (!file_name)
      file_name = "unnamed";
  }

  full_file_name = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.dot",
      priv_gst_dump_dot_dir, file_name);

  if ((out = fopen (full_file_name, "wb"))) {
    gchar *buf;

    buf = gst_debug_bin_to_dot_data (bin, details);
    fputs (buf, out);

    g_free (buf);
    fclose (out);

    GST_INFO ("wrote bin graph to : '%s'", full_file_name);
  } else {
    GST_WARNING ("Failed to open file '%s' for writing: %s", full_file_name,
        g_strerror (errno));
  }
  g_free (full_file_name);
}

由此可见,环境变量 GST_DEBUG_DUMP_DOT_DIR 是 GStreamer 框架的配置接口,而不仅仅是 gst-launch 这个工具的配置接口。

GST_DEBUG_BIN_TO_DOT_FILE()GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS() 宏的原型如下:

#define GST_DEBUG_BIN_TO_DOT_FILE(bin, details, file_name)
#define GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin, details, file_name) 

其中 bin 为应该分析的顶级的管线, details 为图中显示的详细信息,如 GST_DEBUG_GRAPH_SHOW_ALL 或一个或多个 GstDebugGraphDetails 标记,file_name 为输出的基名,也就是输出文件的文件名的模式,而不是精确的文件名。

这两个宏的定义为:

#define GST_DEBUG_BIN_TO_DOT_FILE(bin, details, file_name) gst_debug_bin_to_dot_file (bin, details, file_name)
#define GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(bin, details, file_name) gst_debug_bin_to_dot_file_with_ts (bin, details, file_name)

它们是对两个 C 函数的调用。前面我们已经看到了函数 gst_debug_bin_to_dot_file() 的定义,这里再来看下 gst_debug_bin_to_dot_file_with_ts() 函数的定义:

void
gst_debug_bin_to_dot_file_with_ts (GstBin * bin,
    GstDebugGraphDetails details, const gchar * file_name)
{
  gchar *ts_file_name = NULL;
  GstClockTime elapsed;

  g_return_if_fail (GST_IS_BIN (bin));

  if (!file_name) {
    file_name = g_get_application_name ();
    if (!file_name)
      file_name = "unnamed";
  }

  /* add timestamp */
  elapsed = GST_CLOCK_DIFF (_priv_gst_start_time, gst_util_get_timestamp ());

  /* we don't use GST_TIME_FORMAT as such filenames would fail on some
   * filesystems like fat */
  ts_file_name =
      g_strdup_printf ("%u.%02u.%02u.%09u-%s", GST_TIME_ARGS (elapsed),
      file_name);

  gst_debug_bin_to_dot_file (bin, details, ts_file_name);
  g_free (ts_file_name);
}

gst_debug_bin_to_dot_file_with_ts() 函数是对函数 gst_debug_bin_to_dot_file() 的封装。

由此我们看到,要让 GStreamer 将管线图写入文件要完成的两个步骤:

  1. 设置环境变量 GST_DEBUG_DUMP_DOT_DIR 指向一个用于保存管线图文件的目录;
  2. 通过 GST_DEBUG_BIN_TO_DOT_FILE()GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS() 宏写一个文件。

这两个步骤缺一不可。只是对于 gst-launch 工具来说,第 2 步由工具执行。

如对于 GStreamer 的示例程序 helloworld,我们为它加上将管线图写入文件的代码:

  loop = g_main_loop_new (NULL, FALSE);

  bus = gst_element_get_bus (playbin);
  bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
  gst_object_unref (bus);

  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (playbin),
    GST_DEBUG_GRAPH_SHOW_ALL, "gst-helloworld");

  /* start play back and listed to events */
  gst_element_set_state (playbin, GST_STATE_PLAYING);

  GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (playbin),
    GST_DEBUG_GRAPH_SHOW_ALL, "gst-helloworld");

  g_main_loop_run (loop);

同时设置环境变量:

~/Data/opensource/gstreamer$ export GST_DEBUG_DUMP_DOT_DIR=/home/hanpfei/Data/opensource/gstreamer/

当我们将 helloworld 重新编译并运行起来时,

~/Data/opensource/gstreamer$ build/tests/examples/helloworld/helloworld  ~/music.mp3

将在 /home/hanpfei/Data/opensource/gstreamer/ 目录下生成两个 .dot 文件,类似于下面这样:

0.00.00.205294209-gst-helloworld.dot
0.00.00.206333290-gst-helloworld.dot

其中的 0.00.00.206333290-gst-helloworld.dot 文件的内容如下:

digraph pipeline {
  rankdir=LR;
  fontname="sans";
  fontsize="10";
  labelloc=t;
  nodesep=.1;
  ranksep=.2;
  label="<GstPlayBin>\nplaybin0\n[-] -> [>]\ncurrent-uri=\"file:///home/hanpfei/music.mp3\"\nsource=(GstFileSrc) source";
  node [style="filled,rounded", shape=box, fontsize="9", fontname="sans", margin="0.0,0.0"];
  edge [labelfontsize="6", fontsize="9", fontname="monospace"];
  
  legend [
    pos="0,0!",
    margin="0.05,0.05",
    style="filled",
    label="Legend\lElement-States: [~] void-pending, [0] null, [-] ready, [=] paused, [>] playing\lPad-Activation: [-] none, [>] push, [<] pull\lPad-Flags: [b]locked, [f]lushing, [b]locking, [E]OS; upper-case is set\lPad-Task: [T] has started task, [t] has paused task\l",
  ];
  subgraph cluster_uridecodebin0_0x557be77e20d0 {
    fontname="Bitstream Vera Sans";
    fontsize="8";
    style="filled,rounded";
    color=black;
    label="GstURIDecodeBin\nuridecodebin0\n[-] -> [=]\nuri=\"file:///home/hanpfei/music.mp3\"\nsource=(GstFileSrc) source\ncaps=video/x-raw(ANY); audio/x-raw(ANY); text/x-raw(ANY); subpicture/x-dvd; subpictur…";
    fillcolor="#ffffff";
    subgraph cluster_decodebin0_0x557be77e80f0 {
      fontname="Bitstream Vera Sans";
      fontsize="8";
      style="filled,rounded";
      color=black;
      label="GstDecodeBin\ndecodebin0\n[-] -> [=]\ncaps=video/x-raw(ANY); audio/x-raw(ANY); text/x-raw(ANY); subpicture/x-dvd; subpictur…";
      subgraph cluster_decodebin0_0x557be77e80f0_sink {
        label="";
        style="invis";
        _proxypad0_0x557be77e2360 [color=black, fillcolor="#ddddff", label="proxypad0\n[<][bfb]", height="0.2", style="filled,solid"];
      decodebin0_0x557be77e80f0_sink_0x557be77ee060 -> _proxypad0_0x557be77e2360 [style=dashed, minlen=0]
        decodebin0_0x557be77e80f0_sink_0x557be77ee060 [color=black, fillcolor="#ddddff", label="sink\n[<][bfb]", height="0.2", style="filled,solid"];
      }

      fillcolor="#ffffff";
      subgraph cluster_typefind_0x557be77ea010 {
        fontname="Bitstream Vera Sans";
        fontsize="8";
        style="filled,rounded";
        color=black;
        label="GstTypeFindElement\ntypefind\n[=]\ncaps=application/x-id3";
        subgraph cluster_typefind_0x557be77ea010_sink {
          label="";
          style="invis";
          typefind_0x557be77ea010_sink_0x557be77e6440 [color=black, fillcolor="#aaaaff", label="sink\n[<][bfb][T]", height="0.2", style="filled,solid"];
        }

        subgraph cluster_typefind_0x557be77ea010_src {
          label="";
          style="invis";
          typefind_0x557be77ea010_src_0x557be77e6690 [color=black, fillcolor="#ffaaaa", label="src\n[>][bfb]", height="0.2", style="filled,solid"];
        }

        typefind_0x557be77ea010_sink_0x557be77e6440 -> typefind_0x557be77ea010_src_0x557be77e6690 [style="invis"];
        fillcolor="#aaffaa";
      }

      _proxypad0_0x557be77e2360 -> typefind_0x557be77ea010_sink_0x557be77e6440 [label="ANY"]
    }

    subgraph cluster_source_0x557be77e4220 {
      fontname="Bitstream Vera Sans";
      fontsize="8";
      style="filled,rounded";
      color=black;
      label="GstFileSrc\nsource\n[=]\nlocation=\"/home/hanpfei/music.mp3\"";
      subgraph cluster_source_0x557be77e4220_src {
        label="";
        style="invis";
        source_0x557be77e4220_src_0x557be77e61f0 [color=black, fillcolor="#ffaaaa", label="src\n[<][bfb]", height="0.2", style="filled,solid"];
      }

      fillcolor="#ffaaaa";
    }

    source_0x557be77e4220_src_0x557be77e61f0 -> decodebin0_0x557be77e80f0_sink_0x557be77ee060 [label="ANY"]
  }

  subgraph cluster_playsink_0x557be6b00290 {
    fontname="Bitstream Vera Sans";
    fontsize="8";
    style="filled,rounded";
    color=black;
    label="GstPlaySink\nplaysink\n[-] -> [=]\nflags=video+audio+text+soft-volume+deinterlace+soft-colorbalance\nsend-event-mode=first";
    fillcolor="#ffffff";
    subgraph cluster_streamsynchronizer0_0x557be6b02020 {
      fontname="Bitstream Vera Sans";
      fontsize="8";
      style="filled,rounded";
      color=black;
      label="GstStreamSynchronizer\nstreamsynchronizer0\n[=]";
      fillcolor="#ffffff";
    }

  }

}

.dot 文件是图的一种文本形式的描述。

要看到图,还需要通过 GraphViz.dot 文件转为 png 等格式。

安装 graphviz:

~/Data/opensource/gstreamer$ sudo apt-get install graphviz

通过 graphviz 包中的 dot 工具将 .dot 文件转为 PNG 图:

~/Data/opensource/gstreamer$ dot -Tpng -o test1.png 0.00.00.205294209-gst-helloworld.dot 
~/Data/opensource/gstreamer$ dot -Tpng -o test2.png  0.00.00.206333290-gst-helloworld.dot

生成的图如下:

test1.png

test2.png

GStreamer 的媒体处理管线在不同时刻其状态不太一样,使得获得的图也不太一样。

结论

这里展示了:

  • 如何使用 GST_DEBUG 环境变量从 GStreamer 获取更多调试信息
  • 如何通过 GST_ERROR() 及其相关的宏将你自己的调试信息打印到 GStreamer 日志中。
  • 如何通过 GST_DEBUG_DUMP_DOT_DIR 环境变量获得管线图。

参考文档:
Basic tutorial 11: Debugging tools
Gstreamer 管道可视化

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GStreamer是一个用于构建媒体处理应用程序的开源框架,它可以在不同平台上运行,包括Linux、Windows和Mac OS X等。如果你想要学习如何使用GStreamer构建一个媒体处理应用程序,你可以从以下几个方面入手: 1.了解GStreamer的基本概念和工作原理:GStreamer是一个基于管道的框架,它由多个连接在一起的插件组成,每个插件都有一个特定的功能。你需要了解如何在管道中连接这些插件和配置它们的属性。 2.学习GStreamer的核心插件:GStreamer包含许多核心插件,这些插件提供了处理音频、视频和流媒体数据的常用功能。你需要了解如何使用这些插件来处理不同类型的数据,如如何解码、编码、混音和分割音频、视频和数据流。 3.了解GStreamer的扩展插件:GStreamer还有许多扩展插件,这些插件可以提供更高级的功能,如流媒体服务器和数据库支持。你需要了解如何使用这些插件,以扩展你的应用程序的功能。 4.掌握GStreamer的命令行工具:GStreamer还提供了一些命令行工具,如gst-inspect和gst-launch,在调试和测试你的应用程序时非常有用。你需要了解如何使用这些工具来查看插件的属性和在管道中测试插件。 总之,学习GStreamer需要花费一定的时间和精力,但是一旦掌握了这个框架,你就可以轻松地构建媒体处理应用程序,并实现各种各样的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值