目标

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

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

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

生成的图如下:

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

结论

这里展示了:

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

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

GStreamer 的调试工具相关推荐

  1. GStreamer基础教程10——GStreamer工具

    目标 GStreamer提供了一系列方便使用的工具.这篇教程里不牵涉任何代码,但还是会讲一些有用的内容: 如何在命令行下建立一个pipeline--完全不使用C 如何找出一个element的Capab ...

  2. Gstreamer基础教程10: Gstreamer 工具

    文章目录 1. Goal 2. 介绍 3. gst-lanuch-1.0 3.1 Elements 3.2 Properties 3.3 Named elements 3.4 Pads 3.5 Cap ...

  3. 多平台Gstreamer Multiplatform

    多平台Gstreamer Multiplatform GStreamer可在所有主要操作系统上运行,例如Linux,Android,Windows,Max OS X,iOS,以及大多数BSD,商业Un ...

  4. GStreamer 1.18.4稳定的错误修复版本

    GStreamer 1.18.4稳定的错误修复版本 GStreamer团队宣布最喜欢的跨平台多媒体框架的稳定的1.18版本系列中的另一个错误修复版本! 此版本仅包含错误修复和重要的安全修复程序,并且从 ...

  5. 什么是GStreamer?

    什么是GStreamer? GStreamer是用于创建流媒体应用程序的框架.基本设计来自俄勒冈大学研究生院的视频管道以及DirectShow的一些想法. GStreamer的开发框架使编写任何类型的 ...

  6. gst-crypto GStreamer插件

    gst-crypto GStreamer插件 内容 • 1. gst-crypto概述 o 1.1gst-crypto GStreamer插件功能 o 1.2用例范例 • 2. GStreamer插件 ...

  7. GStreamer跨平台多媒体框架

    GStreamer跨平台多媒体框架 Gstreamer基本概念 GStreamer是用于构造媒体处理组件图的库.它支持的应用程序范围从简单的Ogg / Vorbis回放,音频/视频流到复杂的音频(混合 ...

  8. GStreamer 1.0 series序列示例

    GStreamer 1.0 series序列示例 OpenEmbedded layer for GStreamer 1.0 这layer层为GStreamer 1.0框架提供了非官方的支持,用于Ope ...

  9. 使用 Strace 调试工具

    strace是Linux环境下的一款程序调试工具,用来监察一个应用程序所使用的系统呼叫及它所接收的系统信息. strace是一个有用的小工具,它可以通过跟踪系统调用来让你知道一个程序在后台所做的事情. ...

最新文章

  1. android datepicker使用方法,android DatePicker
  2. 5GS 协议栈 — Overview
  3. PaddleOCR——C++服务端部署Visual Studio 2019 环境下CMake 编译错误【无法打开输入文件paddle_fluid.lib】解决方案
  4. C#开发和使用中的23个技巧
  5. ASP.NET 2.0:如何让DropDownList同时拥有数据来源项目与自订项目 (转自章立民CnBlogs)...
  6. 服务器操作系统的安装步骤,服务器操作系统的安装步骤
  7. 2017 ACM Jordanian Collegiate Programming Contest
  8. java编程基础码_1.java编程基础
  9. LinkedIn会成为下一个诺基亚吗?
  10. android person类_骚操作:不重启 JVM,如何替换掉已经加载的类?
  11. 软件测试的支付流程图,软件测试流程图案例.doc
  12. VR全景制作方法教程完整版
  13. 【直流潮流】基于直流潮流的电力系统停电分布及自组织临界性分析
  14. css3橙色球形_CSS3橙色的星球绕轨道公转动画_html/css_WEB-ITnose -
  15. 计算机中文速录技能,亚伟中文速录机训教程(6.0版).doc
  16. 深入浅出学算法007-统计求和
  17. 浅析 Google 的云计算平台
  18. MinGW和GCC所有版本下载地址
  19. elasticSearch的安装与使用
  20. 【毕业N年系列】 毕业第一年

热门文章

  1. ios键盘横屏_iOS横竖屏旋转及其基本适配方法 转
  2. 解决MySQL删除外键时报错Error Code: 1091. Can‘t DROP ‘XXX‘; check that column/key exists
  3. JDBC、DriverManage、JNDI、数据源(DataSource)、连接池的区别
  4. int型数据占用的内存空间及ASCII码表
  5. nginx配置文件详细解读
  6. 写一个 JavaScript 框架:比 setTimeout 更棒的定时执行
  7. ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态
  8. 233. Number of Digit One
  9. TransactionScope 分布式事务
  10. 有向图的邻接表表示法