美文网首页
GStreamer 的调试工具

GStreamer 的调试工具

作者: hanpfei | 来源:发表于2021-10-30 16:21 被阅读0次

    目标

    有时一些事情没有按照预期的运行,但从总线(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 管道可视化

    相关文章

      网友评论

          本文标题:GStreamer 的调试工具

          本文链接:https://www.haomeiwen.com/subject/xftsaltx.html