|
1 /* GStreamer |
|
2 * Copyright (C) 2007 Stefan Kost <ensonic@users.sf.net> |
|
3 * |
|
4 * gstdebugutils.c: debugging and analysis utillities |
|
5 * |
|
6 * This library is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU Library General Public |
|
8 * License as published by the Free Software Foundation; either |
|
9 * version 2 of the License, or (at your option) any later version. |
|
10 * |
|
11 * This library is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * Library General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU Library General Public |
|
17 * License along with this library; if not, write to the |
|
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
19 * Boston, MA 02111-1307, USA. |
|
20 */ |
|
21 /* TODO: |
|
22 * edge [ constraint=false ]; |
|
23 * edge [ minlen=0 ]; |
|
24 * does not create spacial dependency |
|
25 * node [ margin="0.02,0.01" ]; |
|
26 * space surrounding the label |
|
27 */ |
|
28 |
|
29 #include "gst_private.h" |
|
30 #include "gstdebugutils.h" |
|
31 |
|
32 #ifndef GST_DISABLE_GST_DEBUG |
|
33 |
|
34 #include <stdlib.h> |
|
35 #include <stdio.h> |
|
36 #include <string.h> |
|
37 #include <errno.h> |
|
38 |
|
39 #include "gstinfo.h" |
|
40 #include "gstbin.h" |
|
41 #include "gstobject.h" |
|
42 #include "gstghostpad.h" |
|
43 #include "gstpad.h" |
|
44 #include "gstutils.h" |
|
45 |
|
46 /*** PIPELINE GRAPHS **********************************************************/ |
|
47 |
|
48 const gchar *priv_gst_dump_dot_dir; /* NULL *//* set from gst.c */ |
|
49 |
|
50 extern GstClockTime _priv_gst_info_start_time; |
|
51 |
|
52 static gchar * |
|
53 debug_dump_make_object_name (GstObject * element) |
|
54 { |
|
55 return g_strcanon (g_strdup_printf ("%s_%p", GST_OBJECT_NAME (element), |
|
56 element), G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "_", '_'); |
|
57 } |
|
58 |
|
59 static gchar * |
|
60 debug_dump_get_element_state (GstElement * element) |
|
61 { |
|
62 gchar *state_name = NULL; |
|
63 const gchar *state_icons = "~0-=>"; |
|
64 GstState state = 0, pending = 0; |
|
65 |
|
66 gst_element_get_state (element, &state, &pending, 0); |
|
67 if (pending == GST_STATE_VOID_PENDING) { |
|
68 state_name = g_strdup_printf ("\\n[%c]", state_icons[state]); |
|
69 } else { |
|
70 state_name = g_strdup_printf ("\\n[%c]->[%c]", state_icons[state], |
|
71 state_icons[pending]); |
|
72 } |
|
73 return state_name; |
|
74 } |
|
75 |
|
76 static gchar * |
|
77 debug_dump_get_element_params (GstElement * element) |
|
78 { |
|
79 gchar *param_name = NULL; |
|
80 GParamSpec **properties, *property; |
|
81 GValue value = { 0, }; |
|
82 guint i, number_of_properties; |
|
83 gchar *tmp, *value_str; |
|
84 |
|
85 /* get paramspecs and show non-default properties */ |
|
86 properties = |
|
87 g_object_class_list_properties (G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS |
|
88 (element)), &number_of_properties); |
|
89 if (properties) { |
|
90 for (i = 0; i < number_of_properties; i++) { |
|
91 property = properties[i]; |
|
92 |
|
93 /* ski some properties */ |
|
94 if (!(property->flags & G_PARAM_READABLE)) |
|
95 continue; |
|
96 if (!strcmp (property->name, "name")) |
|
97 continue; |
|
98 |
|
99 g_value_init (&value, property->value_type); |
|
100 g_object_get_property (G_OBJECT (element), property->name, &value); |
|
101 if (!(g_param_value_defaults (property, &value))) { |
|
102 tmp = g_strdup_value_contents (&value); |
|
103 value_str = g_strescape (tmp, NULL); |
|
104 g_free (tmp); |
|
105 if (param_name) { |
|
106 tmp = param_name; |
|
107 param_name = g_strdup_printf ("%s\\n%s=%s", |
|
108 tmp, property->name, value_str); |
|
109 g_free (tmp); |
|
110 } else { |
|
111 param_name = g_strdup_printf ("\\n%s=%s", property->name, value_str); |
|
112 } |
|
113 g_free (value_str); |
|
114 } |
|
115 g_value_unset (&value); |
|
116 } |
|
117 g_free (properties); |
|
118 } |
|
119 return param_name; |
|
120 } |
|
121 |
|
122 /* |
|
123 * debug_dump_element: |
|
124 * @bin: the bin that should be analyzed |
|
125 * @out: file to write to |
|
126 * @indent: level of graph indentation |
|
127 * |
|
128 * Helper for _gst_debug_bin_to_dot_file() to recursively dump a pipeline. |
|
129 */ |
|
130 static void |
|
131 debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out, |
|
132 const gint indent) |
|
133 { |
|
134 GstIterator *element_iter, *pad_iter; |
|
135 gboolean elements_done, pads_done; |
|
136 GstElement *element, *peer_element, *target_element; |
|
137 GstPad *pad, *peer_pad, *target_pad; |
|
138 GstPadDirection dir; |
|
139 GstCaps *caps; |
|
140 GstStructure *structure; |
|
141 gboolean free_caps, free_media; |
|
142 guint src_pads, sink_pads; |
|
143 gchar *media = NULL; |
|
144 gchar *pad_name, *element_name; |
|
145 gchar *peer_pad_name, *peer_element_name; |
|
146 gchar *target_pad_name, *target_element_name; |
|
147 gchar *color_name; |
|
148 gchar *state_name = NULL; |
|
149 gchar *param_name = NULL; |
|
150 gchar *spc = NULL; |
|
151 |
|
152 spc = g_malloc (1 + indent * 2); |
|
153 memset (spc, 32, indent * 2); |
|
154 spc[indent * 2] = '\0'; |
|
155 |
|
156 element_iter = gst_bin_iterate_elements (bin); |
|
157 elements_done = FALSE; |
|
158 while (!elements_done) { |
|
159 switch (gst_iterator_next (element_iter, (gpointer) & element)) { |
|
160 case GST_ITERATOR_OK: |
|
161 element_name = debug_dump_make_object_name (GST_OBJECT (element)); |
|
162 |
|
163 if (details & GST_DEBUG_GRAPH_SHOW_STATES) { |
|
164 state_name = debug_dump_get_element_state (GST_ELEMENT (element)); |
|
165 } |
|
166 if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) { |
|
167 param_name = debug_dump_get_element_params (GST_ELEMENT (element)); |
|
168 } |
|
169 /* elements */ |
|
170 fprintf (out, "%ssubgraph cluster_%s {\n", spc, element_name); |
|
171 fprintf (out, "%s fontname=\"Bitstream Vera Sans\";\n", spc); |
|
172 fprintf (out, "%s fontsize=\"8\";\n", spc); |
|
173 fprintf (out, "%s style=filled;\n", spc); |
|
174 fprintf (out, "%s color=black;\n\n", spc); |
|
175 fprintf (out, "%s label=\"<%s>\\n%s%s%s\";\n", spc, |
|
176 G_OBJECT_TYPE_NAME (element), GST_OBJECT_NAME (element), |
|
177 (state_name ? state_name : ""), (param_name ? param_name : "") |
|
178 ); |
|
179 if (state_name) { |
|
180 g_free (state_name); |
|
181 state_name = NULL; |
|
182 } |
|
183 if (param_name) { |
|
184 g_free (param_name); |
|
185 param_name = NULL; |
|
186 } |
|
187 g_free (element_name); |
|
188 |
|
189 src_pads = sink_pads = 0; |
|
190 if ((pad_iter = gst_element_iterate_pads (element))) { |
|
191 pads_done = FALSE; |
|
192 while (!pads_done) { |
|
193 switch (gst_iterator_next (pad_iter, (gpointer) & pad)) { |
|
194 case GST_ITERATOR_OK: |
|
195 dir = gst_pad_get_direction (pad); |
|
196 pad_name = debug_dump_make_object_name (GST_OBJECT (pad)); |
|
197 element_name = |
|
198 debug_dump_make_object_name (GST_OBJECT (element)); |
|
199 if (GST_IS_GHOST_PAD (pad)) { |
|
200 color_name = |
|
201 (dir == GST_PAD_SRC) ? "#ffdddd" : ((dir == |
|
202 GST_PAD_SINK) ? "#ddddff" : "#ffffff"); |
|
203 } else { |
|
204 color_name = |
|
205 (dir == GST_PAD_SRC) ? "#ffaaaa" : ((dir == |
|
206 GST_PAD_SINK) ? "#aaaaff" : "#cccccc"); |
|
207 } |
|
208 /* pads */ |
|
209 fprintf (out, |
|
210 "%s %s_%s [color=black, fillcolor=\"%s\", label=\"%s\"];\n", |
|
211 spc, element_name, pad_name, color_name, |
|
212 GST_OBJECT_NAME (pad)); |
|
213 |
|
214 if (dir == GST_PAD_SRC) |
|
215 src_pads++; |
|
216 else if (dir == GST_PAD_SINK) |
|
217 sink_pads++; |
|
218 g_free (pad_name); |
|
219 g_free (element_name); |
|
220 gst_object_unref (pad); |
|
221 break; |
|
222 case GST_ITERATOR_RESYNC: |
|
223 gst_iterator_resync (pad_iter); |
|
224 break; |
|
225 case GST_ITERATOR_ERROR: |
|
226 case GST_ITERATOR_DONE: |
|
227 pads_done = TRUE; |
|
228 break; |
|
229 } |
|
230 } |
|
231 gst_iterator_free (pad_iter); |
|
232 } |
|
233 if (GST_IS_BIN (element)) { |
|
234 fprintf (out, "%s fillcolor=\"#ffffff\";\n", spc); |
|
235 /* recurse */ |
|
236 debug_dump_element (GST_BIN (element), details, out, indent + 1); |
|
237 } else { |
|
238 if (src_pads && !sink_pads) |
|
239 fprintf (out, "%s fillcolor=\"#ffaaaa\";\n", spc); |
|
240 else if (!src_pads && sink_pads) |
|
241 fprintf (out, "%s fillcolor=\"#aaaaff\";\n", spc); |
|
242 else if (src_pads && sink_pads) |
|
243 fprintf (out, "%s fillcolor=\"#aaffaa\";\n", spc); |
|
244 else |
|
245 fprintf (out, "%s fillcolor=\"#ffffff\";\n", spc); |
|
246 } |
|
247 fprintf (out, "%s}\n\n", spc); |
|
248 if ((pad_iter = gst_element_iterate_pads (element))) { |
|
249 pads_done = FALSE; |
|
250 while (!pads_done) { |
|
251 switch (gst_iterator_next (pad_iter, (gpointer) & pad)) { |
|
252 case GST_ITERATOR_OK: |
|
253 if (gst_pad_is_linked (pad) |
|
254 && gst_pad_get_direction (pad) == GST_PAD_SRC) { |
|
255 if ((peer_pad = gst_pad_get_peer (pad))) { |
|
256 free_media = FALSE; |
|
257 if ((details & GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE) || |
|
258 (details & GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS) |
|
259 ) { |
|
260 if ((caps = gst_pad_get_negotiated_caps (pad))) { |
|
261 free_caps = TRUE; |
|
262 } else { |
|
263 free_caps = FALSE; |
|
264 if (!(caps = (GstCaps *) |
|
265 gst_pad_get_pad_template_caps (pad))) { |
|
266 /* this should not happen */ |
|
267 media = "?"; |
|
268 } |
|
269 } |
|
270 if (caps) { |
|
271 if (details & GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS) { |
|
272 gchar *tmp = |
|
273 g_strdelimit (gst_caps_to_string (caps), ",", |
|
274 '\n'); |
|
275 |
|
276 media = g_strescape (tmp, NULL); |
|
277 free_media = TRUE; |
|
278 g_free (tmp); |
|
279 } else { |
|
280 if (GST_CAPS_IS_SIMPLE (caps)) { |
|
281 structure = gst_caps_get_structure (caps, 0); |
|
282 media = |
|
283 (gchar *) gst_structure_get_name (structure); |
|
284 } else |
|
285 media = "*"; |
|
286 } |
|
287 if (free_caps) { |
|
288 gst_caps_unref (caps); |
|
289 } |
|
290 } |
|
291 } |
|
292 |
|
293 pad_name = debug_dump_make_object_name (GST_OBJECT (pad)); |
|
294 element_name = |
|
295 debug_dump_make_object_name (GST_OBJECT (element)); |
|
296 peer_pad_name = |
|
297 debug_dump_make_object_name (GST_OBJECT (peer_pad)); |
|
298 if ((peer_element = gst_pad_get_parent_element (peer_pad))) { |
|
299 peer_element_name = |
|
300 debug_dump_make_object_name (GST_OBJECT |
|
301 (peer_element)); |
|
302 } else { |
|
303 peer_element_name = ""; |
|
304 } |
|
305 /* pad link */ |
|
306 if (media) { |
|
307 fprintf (out, "%s%s_%s -> %s_%s [label=\"%s\"]\n", spc, |
|
308 element_name, pad_name, peer_element_name, |
|
309 peer_pad_name, media); |
|
310 if (free_media) { |
|
311 g_free (media); |
|
312 } |
|
313 } else { |
|
314 fprintf (out, "%s%s_%s -> %s_%s\n", spc, |
|
315 element_name, pad_name, peer_element_name, |
|
316 peer_pad_name); |
|
317 } |
|
318 |
|
319 if (GST_IS_GHOST_PAD (pad)) { |
|
320 if ((target_pad = |
|
321 gst_ghost_pad_get_target (GST_GHOST_PAD (pad)))) { |
|
322 target_pad_name = |
|
323 debug_dump_make_object_name (GST_OBJECT |
|
324 (target_pad)); |
|
325 if ((target_element = |
|
326 gst_pad_get_parent_element (target_pad))) { |
|
327 target_element_name = |
|
328 debug_dump_make_object_name (GST_OBJECT |
|
329 (target_element)); |
|
330 } else { |
|
331 target_element_name = ""; |
|
332 } |
|
333 /* src ghostpad relationship */ |
|
334 fprintf (out, "%s%s_%s -> %s_%s [style=dashed]\n", spc, |
|
335 target_element_name, target_pad_name, element_name, |
|
336 pad_name); |
|
337 |
|
338 g_free (target_pad_name); |
|
339 if (target_element) { |
|
340 g_free (target_element_name); |
|
341 gst_object_unref (target_element); |
|
342 } |
|
343 gst_object_unref (target_pad); |
|
344 } |
|
345 } |
|
346 if (GST_IS_GHOST_PAD (peer_pad)) { |
|
347 if ((target_pad = |
|
348 gst_ghost_pad_get_target (GST_GHOST_PAD |
|
349 (peer_pad)))) { |
|
350 target_pad_name = |
|
351 debug_dump_make_object_name (GST_OBJECT |
|
352 (target_pad)); |
|
353 if ((target_element = |
|
354 gst_pad_get_parent_element (target_pad))) { |
|
355 target_element_name = |
|
356 debug_dump_make_object_name (GST_OBJECT |
|
357 (target_element)); |
|
358 } else { |
|
359 target_element_name = ""; |
|
360 } |
|
361 /* sink ghostpad relationship */ |
|
362 fprintf (out, "%s%s_%s -> %s_%s [style=dashed]\n", spc, |
|
363 peer_element_name, peer_pad_name, |
|
364 target_element_name, target_pad_name); |
|
365 |
|
366 g_free (target_pad_name); |
|
367 if (target_element) { |
|
368 g_free (target_element_name); |
|
369 gst_object_unref (target_element); |
|
370 } |
|
371 gst_object_unref (target_pad); |
|
372 } |
|
373 } |
|
374 |
|
375 g_free (pad_name); |
|
376 g_free (element_name); |
|
377 g_free (peer_pad_name); |
|
378 if (peer_element) { |
|
379 g_free (peer_element_name); |
|
380 gst_object_unref (peer_element); |
|
381 } |
|
382 gst_object_unref (peer_pad); |
|
383 } |
|
384 } |
|
385 gst_object_unref (pad); |
|
386 break; |
|
387 case GST_ITERATOR_RESYNC: |
|
388 gst_iterator_resync (pad_iter); |
|
389 break; |
|
390 case GST_ITERATOR_ERROR: |
|
391 case GST_ITERATOR_DONE: |
|
392 pads_done = TRUE; |
|
393 break; |
|
394 } |
|
395 } |
|
396 gst_iterator_free (pad_iter); |
|
397 } |
|
398 gst_object_unref (element); |
|
399 break; |
|
400 case GST_ITERATOR_RESYNC: |
|
401 gst_iterator_resync (element_iter); |
|
402 break; |
|
403 case GST_ITERATOR_ERROR: |
|
404 case GST_ITERATOR_DONE: |
|
405 elements_done = TRUE; |
|
406 break; |
|
407 } |
|
408 } |
|
409 gst_iterator_free (element_iter); |
|
410 g_free (spc); |
|
411 } |
|
412 |
|
413 /* |
|
414 * _gst_debug_bin_to_dot_file: |
|
415 * @bin: the top-level pipeline that should be analyzed |
|
416 * @file_name: output base filename (e.g. "myplayer") |
|
417 * |
|
418 * To aid debugging applications one can use this method to write out the whole |
|
419 * network of gstreamer elements that form the pipeline into an dot file. |
|
420 * This file can be processed with graphviz to get an image. |
|
421 * <informalexample><programlisting> |
|
422 * dot -Tpng -oimage.png graph_lowlevel.dot |
|
423 * </programlisting></informalexample> |
|
424 */ |
|
425 #ifdef __SYMBIAN32__ |
|
426 EXPORT_C |
|
427 #endif |
|
428 |
|
429 void |
|
430 _gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details, |
|
431 const gchar * file_name) |
|
432 { |
|
433 gchar *full_file_name = NULL; |
|
434 FILE *out; |
|
435 |
|
436 g_return_if_fail (GST_IS_BIN (bin)); |
|
437 |
|
438 if (G_LIKELY (priv_gst_dump_dot_dir == NULL)) |
|
439 return; |
|
440 |
|
441 if (!file_name) { |
|
442 file_name = g_get_application_name (); |
|
443 if (!file_name) |
|
444 file_name = "unnamed"; |
|
445 } |
|
446 |
|
447 full_file_name = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.dot", |
|
448 priv_gst_dump_dot_dir, file_name); |
|
449 |
|
450 if ((out = fopen (full_file_name, "wb"))) { |
|
451 gchar *state_name = NULL; |
|
452 gchar *param_name = NULL; |
|
453 |
|
454 if (details & GST_DEBUG_GRAPH_SHOW_STATES) { |
|
455 state_name = debug_dump_get_element_state (GST_ELEMENT (bin)); |
|
456 } |
|
457 if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) { |
|
458 param_name = debug_dump_get_element_params (GST_ELEMENT (bin)); |
|
459 } |
|
460 |
|
461 /* write header */ |
|
462 fprintf (out, |
|
463 "digraph pipeline {\n" |
|
464 " rankdir=LR;\n" |
|
465 " fontname=\"Bitstream Vera Sans\";\n" |
|
466 " fontsize=\"8\";\n" |
|
467 " labelloc=t;\n" |
|
468 " nodesep=.1;\n" |
|
469 " ranksep=.2;\n" |
|
470 " label=\"<%s>\\n%s%s%s\";\n" |
|
471 " node [style=filled, shape=box, fontsize=\"7\", fontname=\"Bitstream Vera Sans\"];\n" |
|
472 " edge [labelfontsize=\"7\", fontsize=\"7\", labelfontname=\"Bitstream Vera Sans\", fontname=\"Bitstream Vera Sans\"];\n" |
|
473 "\n", G_OBJECT_TYPE_NAME (bin), GST_OBJECT_NAME (bin), |
|
474 (state_name ? state_name : ""), (param_name ? param_name : "") |
|
475 ); |
|
476 if (state_name) |
|
477 g_free (state_name); |
|
478 if (param_name) |
|
479 g_free (param_name); |
|
480 |
|
481 debug_dump_element (bin, details, out, 1); |
|
482 |
|
483 /* write footer */ |
|
484 fprintf (out, "}\n"); |
|
485 fclose (out); |
|
486 GST_INFO ("wrote bin graph to : '%s'", full_file_name); |
|
487 } else { |
|
488 GST_WARNING ("Failed to open file '%s' for writing: %s", full_file_name, |
|
489 g_strerror (errno)); |
|
490 } |
|
491 g_free (full_file_name); |
|
492 } |
|
493 |
|
494 /* |
|
495 * _gst_debug_bin_to_dot_file_with_ts: |
|
496 * @bin: the top-level pipeline that should be analyzed |
|
497 * @file_name: output base filename (e.g. "myplayer") |
|
498 * |
|
499 * This works like _gst_debug_bin_to_dot_file(), but adds the current timestamp |
|
500 * to the filename, so that it can be used to take multiple snapshots. |
|
501 */ |
|
502 #ifdef __SYMBIAN32__ |
|
503 EXPORT_C |
|
504 #endif |
|
505 |
|
506 void |
|
507 _gst_debug_bin_to_dot_file_with_ts (GstBin * bin, GstDebugGraphDetails details, |
|
508 const gchar * file_name) |
|
509 { |
|
510 gchar *ts_file_name = NULL; |
|
511 GstClockTime elapsed; |
|
512 |
|
513 g_return_if_fail (GST_IS_BIN (bin)); |
|
514 |
|
515 if (!file_name) { |
|
516 file_name = g_get_application_name (); |
|
517 if (!file_name) |
|
518 file_name = "unnamed"; |
|
519 } |
|
520 |
|
521 /* add timestamp */ |
|
522 elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time, |
|
523 gst_util_get_timestamp ()); |
|
524 ts_file_name = |
|
525 g_strdup_printf ("%" GST_TIME_FORMAT "-%s", GST_TIME_ARGS (elapsed), |
|
526 file_name); |
|
527 |
|
528 _gst_debug_bin_to_dot_file (bin, details, ts_file_name); |
|
529 g_free (ts_file_name); |
|
530 } |
|
531 |
|
532 #endif /* GST_DISABLE_GST_DEBUG */ |