gstpad: Probes that return HANDLED can reset the data info field
[platform/upstream/gstreamer.git] / gst / gstdebugutils.c
index 4f90de2..a13f548 100644 (file)
@@ -1,7 +1,7 @@
 /* GStreamer
  * Copyright (C) 2007 Stefan Kost <ensonic@users.sf.net>
  *
- * gstdebugutils.c: debugging and analysis utillities
+ * gstdebugutils.c: debugging and analysis utilities
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -21,7 +21,7 @@
 /* TODO:
  * edge [ constraint=false ];
  *   this creates strange graphs ("minlen=0" is better)
- * try puting src/sink ghostpads for each bin into invisible clusters
+ * try putting src/sink ghostpads for each bin into invisible clusters
  *
  * for more compact nodes, try
  * - changing node-shape from box into record
 
 extern const gchar *priv_gst_dump_dot_dir;      /* NULL *//* set from gst.c */
 
-const gchar spaces[] = {
+#define PARAM_MAX_LENGTH 80
+
+static const gchar spaces[] = {
   "                                "    /* 32 */
       "                                "        /* 64 */
       "                                "        /* 96 */
       "                                "        /* 128 */
 };
 
-extern GstClockTime _priv_gst_info_start_time;
+#define MAKE_INDENT(indent) \
+  &spaces[MAX (sizeof (spaces) - (1 + (indent) * 2), 0)]
 
 static gchar *
 debug_dump_make_object_name (GstObject * obj)
@@ -92,42 +95,123 @@ debug_dump_get_element_state (GstElement * element)
 }
 
 static gchar *
-debug_dump_get_element_params (GstElement * element)
+debug_dump_get_object_params (GObject * object,
+    GstDebugGraphDetails details, const char *const *ignored_propnames)
 {
   gchar *param_name = NULL;
   GParamSpec **properties, *property;
   GValue value = { 0, };
   guint i, number_of_properties;
   gchar *tmp, *value_str;
+  const gchar *ellipses;
 
   /* get paramspecs and show non-default properties */
   properties =
-      g_object_class_list_properties (G_OBJECT_CLASS (GST_ELEMENT_GET_CLASS
-          (element)), &number_of_properties);
+      g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
+      &number_of_properties);
   if (properties) {
     for (i = 0; i < number_of_properties; i++) {
+      gint j;
+      gboolean ignore = FALSE;
       property = properties[i];
 
-      /* ski some properties */
+      /* skip some properties */
       if (!(property->flags & G_PARAM_READABLE))
         continue;
       if (!strcmp (property->name, "name"))
         continue;
 
+      if (ignored_propnames)
+        for (j = 0; ignored_propnames[j]; j++)
+          if (!g_strcmp0 (ignored_propnames[j], property->name))
+            ignore = TRUE;
+
+      if (ignore)
+        continue;
+
       g_value_init (&value, property->value_type);
-      g_object_get_property (G_OBJECT (element), property->name, &value);
+      g_object_get_property (G_OBJECT (object), property->name, &value);
       if (!(g_param_value_defaults (property, &value))) {
-        tmp = g_strdup_value_contents (&value);
+        /* we need to serialise enums and flags ourselves to make sure the
+         * enum/flag nick is used and not the enum/flag name, which would be the
+         * C header enum/flag for public enums/flags, but for element-specific
+         * enums/flags we abuse the name field for the property description,
+         * and we don't want to print that in the dot file. The nick will
+         * always work, and it's also shorter. */
+        if (G_VALUE_HOLDS_ENUM (&value)) {
+          GEnumClass *e_class = g_type_class_ref (G_VALUE_TYPE (&value));
+          gint idx, e_val;
+
+          tmp = NULL;
+          e_val = g_value_get_enum (&value);
+          for (idx = 0; idx < e_class->n_values; ++idx) {
+            if (e_class->values[idx].value == e_val) {
+              tmp = g_strdup (e_class->values[idx].value_nick);
+              break;
+            }
+          }
+          if (tmp == NULL) {
+            g_value_unset (&value);
+            continue;
+          }
+        } else if (G_VALUE_HOLDS_FLAGS (&value)) {
+          GFlagsClass *f_class = g_type_class_ref (G_VALUE_TYPE (&value));
+          GFlagsValue *vals = f_class->values;
+          GString *s = NULL;
+          guint idx, flags_left;
+
+          s = g_string_new (NULL);
+
+          /* we assume the values are sorted from lowest to highest value */
+          flags_left = g_value_get_flags (&value);
+          idx = f_class->n_values;
+          while (idx > 0) {
+            --idx;
+            if (vals[idx].value != 0
+                && (flags_left & vals[idx].value) == vals[idx].value) {
+              if (s->len > 0)
+                g_string_prepend_c (s, '+');
+              g_string_prepend (s, vals[idx].value_nick);
+              flags_left -= vals[idx].value;
+              if (flags_left == 0)
+                break;
+            }
+          }
+
+          if (s->len == 0)
+            g_string_assign (s, "(none)");
+
+          tmp = g_string_free (s, FALSE);
+        } else {
+          tmp = g_strdup_value_contents (&value);
+        }
         value_str = g_strescape (tmp, NULL);
         g_free (tmp);
-        if (param_name) {
+
+        /* too long, ellipsize */
+        if (!(details & GST_DEBUG_GRAPH_SHOW_FULL_PARAMS) &&
+            strlen (value_str) > PARAM_MAX_LENGTH)
+          ellipses = "…";
+        else
+          ellipses = "";
+
+        if (param_name)
           tmp = param_name;
-          param_name = g_strdup_printf ("%s\\n%s=%s",
-              tmp, property->name, value_str);
-          g_free (tmp);
+        else
+          tmp = (char *) "";
+
+        if (details & GST_DEBUG_GRAPH_SHOW_FULL_PARAMS) {
+          param_name = g_strdup_printf ("%s\\n%s=%s", tmp, property->name,
+              value_str);
         } else {
-          param_name = g_strdup_printf ("\\n%s=%s", property->name, value_str);
+          param_name = g_strdup_printf ("%s\\n%s=%."
+              G_STRINGIFY (PARAM_MAX_LENGTH) "s%s", tmp, property->name,
+              value_str, ellipses);
         }
+
+        if (tmp[0] != '\0')
+          g_free (tmp);
+
         g_free (value_str);
       }
       g_value_unset (&value);
@@ -139,14 +223,18 @@ debug_dump_get_element_params (GstElement * element)
 
 static void
 debug_dump_pad (GstPad * pad, const gchar * color_name,
-    const gchar * element_name, GstDebugGraphDetails details, FILE * out,
+    const gchar * element_name, GstDebugGraphDetails details, GString * str,
     const gint indent)
 {
   GstPadTemplate *pad_templ;
   GstPadPresence presence;
-  gchar *pad_name;
+  gchar *pad_name, *param_name = NULL;
   const gchar *style_name;
-  const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)];
+  static const char *const ignore_propnames[] =
+      { "parent", "direction", "template",
+    "caps", NULL
+  };
+  const gchar *spc = MAKE_INDENT (indent);
 
   pad_name = debug_dump_make_object_name (GST_OBJECT (pad));
 
@@ -154,15 +242,38 @@ debug_dump_pad (GstPad * pad, const gchar * color_name,
   style_name = "filled,solid";
   if ((pad_templ = gst_pad_get_pad_template (pad))) {
     presence = GST_PAD_TEMPLATE_PRESENCE (pad_templ);
+    gst_object_unref (pad_templ);
     if (presence == GST_PAD_SOMETIMES) {
       style_name = "filled,dotted";
     } else if (presence == GST_PAD_REQUEST) {
       style_name = "filled,dashed";
     }
   }
+
+  param_name =
+      debug_dump_get_object_params (G_OBJECT (pad), details, ignore_propnames);
   if (details & GST_DEBUG_GRAPH_SHOW_STATES) {
-    gchar pad_flags[4];
+    gchar pad_flags[5];
     const gchar *activation_mode = "-><";
+    const gchar *task_mode = "";
+    GstTask *task;
+
+    GST_OBJECT_LOCK (pad);
+    task = GST_PAD_TASK (pad);
+    if (task) {
+      switch (gst_task_get_state (task)) {
+        case GST_TASK_STARTED:
+          task_mode = "[T]";
+          break;
+        case GST_TASK_PAUSED:
+          task_mode = "[t]";
+          break;
+        default:
+          /* Invalid task state, ignoring */
+          break;
+      }
+    }
+    GST_OBJECT_UNLOCK (pad);
 
     /* check if pad flags */
     pad_flags[0] =
@@ -171,25 +282,28 @@ debug_dump_pad (GstPad * pad, const gchar * color_name,
         GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLAG_FLUSHING) ? 'F' : 'f';
     pad_flags[2] =
         GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLAG_BLOCKING) ? 'B' : 'b';
-    pad_flags[3] = '\0';
+    pad_flags[3] = GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLAG_EOS) ? 'E' : '\0';
+    pad_flags[4] = '\0';
 
-    fprintf (out,
-        "%s  %s_%s [color=black, fillcolor=\"%s\", label=\"%s\\n[%c][%s]\", height=\"0.2\", style=\"%s\"];\n",
+    g_string_append_printf (str,
+        "%s  %s_%s [color=black, fillcolor=\"%s\", label=\"%s%s\\n[%c][%s]%s\", height=\"0.2\", style=\"%s\"];\n",
         spc, element_name, pad_name, color_name, GST_OBJECT_NAME (pad),
-        activation_mode[pad->mode], pad_flags, style_name);
+        (param_name ? param_name : ""),
+        activation_mode[pad->mode], pad_flags, task_mode, style_name);
   } else {
-    fprintf (out,
-        "%s  %s_%s [color=black, fillcolor=\"%s\", label=\"%s\", height=\"0.2\", style=\"%s\"];\n",
+    g_string_append_printf (str,
+        "%s  %s_%s [color=black, fillcolor=\"%s\", label=\"%s%s\", height=\"0.2\", style=\"%s\"];\n",
         spc, element_name, pad_name, color_name, GST_OBJECT_NAME (pad),
-        style_name);
+        (param_name ? param_name : ""), style_name);
   }
 
+  g_free (param_name);
   g_free (pad_name);
 }
 
 static void
 debug_dump_element_pad (GstPad * pad, GstElement * element,
-    GstDebugGraphDetails details, FILE * out, const gint indent)
+    GstDebugGraphDetails details, GString * str, const gint indent)
 {
   GstElement *target_element;
   GstPad *target_pad, *tmp_pad;
@@ -208,7 +322,7 @@ debug_dump_element_pad (GstPad * pad, GstElement * element,
     if ((tmp_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad)))) {
       if ((target_pad = gst_pad_get_peer (tmp_pad))) {
         gchar *pad_name, *target_pad_name;
-        const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)];
+        const gchar *spc = MAKE_INDENT (indent);
 
         if ((target_element = gst_pad_get_parent_element (target_pad))) {
           target_element_name =
@@ -217,15 +331,17 @@ debug_dump_element_pad (GstPad * pad, GstElement * element,
           target_element_name = g_strdup ("");
         }
         debug_dump_pad (target_pad, color_name, target_element_name, details,
-            out, indent);
+            str, indent);
         /* src ghostpad relationship */
         pad_name = debug_dump_make_object_name (GST_OBJECT (pad));
         target_pad_name = debug_dump_make_object_name (GST_OBJECT (target_pad));
         if (dir == GST_PAD_SRC) {
-          fprintf (out, "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc,
+          g_string_append_printf (str,
+              "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc,
               target_element_name, target_pad_name, element_name, pad_name);
         } else {
-          fprintf (out, "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc,
+          g_string_append_printf (str,
+              "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc,
               element_name, pad_name, target_element_name, target_pad_name);
         }
         g_free (target_pad_name);
@@ -243,7 +359,7 @@ debug_dump_element_pad (GstPad * pad, GstElement * element,
             GST_PAD_SINK) ? "#aaaaff" : "#cccccc");
   }
   /* pads */
-  debug_dump_pad (pad, color_name, element_name, details, out, indent);
+  debug_dump_pad (pad, color_name, element_name, details, str, indent);
   g_free (element_name);
 }
 
@@ -254,6 +370,11 @@ string_append_field (GQuark field, const GValue * value, gpointer ptr)
   gchar *value_str = gst_value_serialize (value);
   gchar *esc_value_str;
 
+  if (value_str == NULL) {
+    g_string_append_printf (str, "  %18s: NULL\\l", g_quark_to_string (field));
+    return TRUE;
+  }
+
   /* some enums can become really long */
   if (strlen (value_str) > 25) {
     gint pos = 24;
@@ -311,9 +432,18 @@ debug_dump_describe_caps (GstCaps * caps, GstDebugGraphDetails details)
 
       str = g_string_sized_new (slen);
       for (i = 0; i < gst_caps_get_size (caps); i++) {
+        GstCapsFeatures *features = __gst_caps_get_features_unchecked (caps, i);
         GstStructure *structure = gst_caps_get_structure (caps, i);
 
         g_string_append (str, gst_structure_get_name (structure));
+
+        if (features && (gst_caps_features_is_any (features)
+                || !gst_caps_features_is_equal (features,
+                    GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))) {
+          g_string_append_c (str, '(');
+          priv_gst_caps_features_append_to_gstring (features, str);
+          g_string_append_c (str, ')');
+        }
         g_string_append (str, "\\l");
 
         gst_structure_foreach (structure, string_append_field, (gpointer) str);
@@ -334,7 +464,7 @@ debug_dump_describe_caps (GstCaps * caps, GstDebugGraphDetails details)
 
 static void
 debug_dump_element_pad_link (GstPad * pad, GstElement * element,
-    GstDebugGraphDetails details, FILE * out, const gint indent)
+    GstDebugGraphDetails details, GString * str, const gint indent)
 {
   GstElement *peer_element;
   GstPad *peer_pad;
@@ -343,7 +473,7 @@ debug_dump_element_pad_link (GstPad * pad, GstElement * element,
   gchar *media_src = NULL, *media_sink = NULL;
   gchar *pad_name, *element_name;
   gchar *peer_pad_name, *peer_element_name;
-  const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)];
+  const gchar *spc = MAKE_INDENT (indent);
 
   if ((peer_pad = gst_pad_get_peer (pad))) {
     if ((details & GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE) ||
@@ -391,21 +521,22 @@ debug_dump_element_pad_link (GstPad * pad, GstElement * element,
 
     /* pad link */
     if (media) {
-      fprintf (out, "%s%s_%s -> %s_%s [label=\"%s\"]\n", spc,
+      g_string_append_printf (str, "%s%s_%s -> %s_%s [label=\"%s\"]\n", spc,
           element_name, pad_name, peer_element_name, peer_pad_name, media);
       g_free (media);
     } else if (media_src && media_sink) {
       /* dot has some issues with placement of head and taillabels,
        * we need an empty label to make space */
-      fprintf (out, "%s%s_%s -> %s_%s [labeldistance=\"10\", labelangle=\"0\", "
+      g_string_append_printf (str,
+          "%s%s_%s -> %s_%s [labeldistance=\"10\", labelangle=\"0\", "
           "label=\"                                                  \", "
-          "headlabel=\"%s\", taillabel=\"%s\"]\n",
+          "taillabel=\"%s\", headlabel=\"%s\"]\n",
           spc, element_name, pad_name, peer_element_name, peer_pad_name,
           media_src, media_sink);
       g_free (media_src);
       g_free (media_sink);
     } else {
-      fprintf (out, "%s%s_%s -> %s_%s\n", spc,
+      g_string_append_printf (str, "%s%s_%s -> %s_%s\n", spc,
           element_name, pad_name, peer_element_name, peer_pad_name);
     }
 
@@ -421,24 +552,28 @@ debug_dump_element_pad_link (GstPad * pad, GstElement * element,
 
 static void
 debug_dump_element_pads (GstIterator * pad_iter, GstPad * pad,
-    GstElement * element, GstDebugGraphDetails details, FILE * out,
-    const gint indent, guint * src_pads, guint * sink_pads)
+    GstElement * element, GstDebugGraphDetails details, GString * str,
+    const gint indent, guint * num_pads, gchar * cluster_name,
+    gchar ** first_pad_name)
 {
   GValue item = { 0, };
   gboolean pads_done;
-  GstPadDirection dir;
+  const gchar *spc = MAKE_INDENT (indent);
 
   pads_done = FALSE;
   while (!pads_done) {
     switch (gst_iterator_next (pad_iter, &item)) {
       case GST_ITERATOR_OK:
         pad = g_value_get_object (&item);
-        debug_dump_element_pad (pad, element, details, out, indent);
-        dir = gst_pad_get_direction (pad);
-        if (dir == GST_PAD_SRC)
-          (*src_pads)++;
-        else if (dir == GST_PAD_SINK)
-          (*sink_pads)++;
+        if (!*num_pads) {
+          g_string_append_printf (str, "%ssubgraph cluster_%s {\n", spc,
+              cluster_name);
+          g_string_append_printf (str, "%s  label=\"\";\n", spc);
+          g_string_append_printf (str, "%s  style=\"invis\";\n", spc);
+          (*first_pad_name) = debug_dump_make_object_name (GST_OBJECT (pad));
+        }
+        debug_dump_element_pad (pad, element, details, str, indent);
+        (*num_pads)++;
         g_value_reset (&item);
         break;
       case GST_ITERATOR_RESYNC:
@@ -450,6 +585,9 @@ debug_dump_element_pads (GstIterator * pad_iter, GstPad * pad,
         break;
     }
   }
+  if (*num_pads) {
+    g_string_append_printf (str, "%s}\n\n", spc);
+  }
 }
 
 /*
@@ -461,8 +599,8 @@ debug_dump_element_pads (GstIterator * pad_iter, GstPad * pad,
  * Helper for gst_debug_bin_to_dot_file() to recursively dump a pipeline.
  */
 static void
-debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
-    const gint indent)
+debug_dump_element (GstBin * bin, GstDebugGraphDetails details,
+    GString * str, const gint indent)
 {
   GstIterator *element_iter, *pad_iter;
   gboolean elements_done, pads_done;
@@ -471,10 +609,11 @@ debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
   GstElement *element;
   GstPad *pad = NULL;
   guint src_pads, sink_pads;
+  gchar *src_pad_name = NULL, *sink_pad_name = NULL;
   gchar *element_name;
   gchar *state_name = NULL;
   gchar *param_name = NULL;
-  const gchar *spc = &spaces[MAX (sizeof (spaces) - (1 + indent * 2), 0)];
+  const gchar *spc = MAKE_INDENT (indent);
 
   element_iter = gst_bin_iterate_elements (bin);
   elements_done = FALSE;
@@ -488,15 +627,18 @@ debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
           state_name = debug_dump_get_element_state (GST_ELEMENT (element));
         }
         if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) {
-          param_name = debug_dump_get_element_params (GST_ELEMENT (element));
+          param_name = debug_dump_get_object_params (G_OBJECT (element),
+              details, NULL);
         }
         /* elements */
-        fprintf (out, "%ssubgraph cluster_%s {\n", spc, element_name);
-        fprintf (out, "%s  fontname=\"Bitstream Vera Sans\";\n", spc);
-        fprintf (out, "%s  fontsize=\"8\";\n", spc);
-        fprintf (out, "%s  style=filled;\n", spc);
-        fprintf (out, "%s  color=black;\n\n", spc);
-        fprintf (out, "%s  label=\"%s\\n%s%s%s\";\n", spc,
+        g_string_append_printf (str, "%ssubgraph cluster_%s {\n", spc,
+            element_name);
+        g_string_append_printf (str, "%s  fontname=\"Bitstream Vera Sans\";\n",
+            spc);
+        g_string_append_printf (str, "%s  fontsize=\"8\";\n", spc);
+        g_string_append_printf (str, "%s  style=\"filled,rounded\";\n", spc);
+        g_string_append_printf (str, "%s  color=black;\n", spc);
+        g_string_append_printf (str, "%s  label=\"%s\\n%s%s%s\";\n", spc,
             G_OBJECT_TYPE_NAME (element), GST_OBJECT_NAME (element),
             (state_name ? state_name : ""), (param_name ? param_name : "")
             );
@@ -508,34 +650,47 @@ debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
           g_free (param_name);
           param_name = NULL;
         }
-        g_free (element_name);
 
         src_pads = sink_pads = 0;
         if ((pad_iter = gst_element_iterate_sink_pads (element))) {
-          debug_dump_element_pads (pad_iter, pad, element, details, out, indent,
-              &src_pads, &sink_pads);
+          gchar *cluster_name = g_strdup_printf ("%s_sink", element_name);
+          debug_dump_element_pads (pad_iter, pad, element, details, str,
+              indent + 1, &sink_pads, cluster_name, &sink_pad_name);
+          g_free (cluster_name);
           gst_iterator_free (pad_iter);
         }
         if ((pad_iter = gst_element_iterate_src_pads (element))) {
-          debug_dump_element_pads (pad_iter, pad, element, details, out, indent,
-              &src_pads, &sink_pads);
+          gchar *cluster_name = g_strdup_printf ("%s_src", element_name);
+          debug_dump_element_pads (pad_iter, pad, element, details, str,
+              indent + 1, &src_pads, cluster_name, &src_pad_name);
+          g_free (cluster_name);
           gst_iterator_free (pad_iter);
         }
+        if (sink_pads && src_pads) {
+          /* add invisible link from first sink to first src pad */
+          g_string_append_printf (str,
+              "%s  %s_%s -> %s_%s [style=\"invis\"];\n",
+              spc, element_name, sink_pad_name, element_name, src_pad_name);
+        }
+        g_free (sink_pad_name);
+        g_free (src_pad_name);
+        g_free (element_name);
+        sink_pad_name = src_pad_name = NULL;
         if (GST_IS_BIN (element)) {
-          fprintf (out, "%s  fillcolor=\"#ffffff\";\n", spc);
+          g_string_append_printf (str, "%s  fillcolor=\"#ffffff\";\n", spc);
           /* recurse */
-          debug_dump_element (GST_BIN (element), details, out, indent + 1);
+          debug_dump_element (GST_BIN (element), details, str, indent + 1);
         } else {
           if (src_pads && !sink_pads)
-            fprintf (out, "%s  fillcolor=\"#ffaaaa\";\n", spc);
+            g_string_append_printf (str, "%s  fillcolor=\"#ffaaaa\";\n", spc);
           else if (!src_pads && sink_pads)
-            fprintf (out, "%s  fillcolor=\"#aaaaff\";\n", spc);
+            g_string_append_printf (str, "%s  fillcolor=\"#aaaaff\";\n", spc);
           else if (src_pads && sink_pads)
-            fprintf (out, "%s  fillcolor=\"#aaffaa\";\n", spc);
+            g_string_append_printf (str, "%s  fillcolor=\"#aaffaa\";\n", spc);
           else
-            fprintf (out, "%s  fillcolor=\"#ffffff\";\n", spc);
+            g_string_append_printf (str, "%s  fillcolor=\"#ffffff\";\n", spc);
         }
-        fprintf (out, "%s}\n\n", spc);
+        g_string_append_printf (str, "%s}\n\n", spc);
         if ((pad_iter = gst_element_iterate_pads (element))) {
           pads_done = FALSE;
           while (!pads_done) {
@@ -544,7 +699,7 @@ debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
                 pad = g_value_get_object (&item2);
                 if (gst_pad_is_linked (pad)) {
                   if (gst_pad_get_direction (pad) == GST_PAD_SRC) {
-                    debug_dump_element_pad_link (pad, element, details, out,
+                    debug_dump_element_pad_link (pad, element, details, str,
                         indent);
                   } else {
                     GstPad *peer_pad = gst_pad_get_peer (pad);
@@ -553,7 +708,7 @@ debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
                       if (!GST_IS_GHOST_PAD (peer_pad)
                           && GST_IS_PROXY_PAD (peer_pad)) {
                         debug_dump_element_pad_link (peer_pad, NULL, details,
-                            out, indent);
+                            str, indent);
                       }
                       gst_object_unref (peer_pad);
                     }
@@ -589,10 +744,87 @@ debug_dump_element (GstBin * bin, GstDebugGraphDetails details, FILE * out,
   gst_iterator_free (element_iter);
 }
 
-/*
+static void
+debug_dump_header (GstBin * bin, GstDebugGraphDetails details, GString * str)
+{
+  gchar *state_name = NULL;
+  gchar *param_name = NULL;
+
+  if (details & GST_DEBUG_GRAPH_SHOW_STATES) {
+    state_name = debug_dump_get_element_state (GST_ELEMENT (bin));
+  }
+  if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) {
+    param_name = debug_dump_get_object_params (G_OBJECT (bin), details, NULL);
+  }
+
+  /* write header */
+  g_string_append_printf (str,
+      "digraph pipeline {\n"
+      "  rankdir=LR;\n"
+      "  fontname=\"sans\";\n"
+      "  fontsize=\"10\";\n"
+      "  labelloc=t;\n"
+      "  nodesep=.1;\n"
+      "  ranksep=.2;\n"
+      "  label=\"<%s>\\n%s%s%s\";\n"
+      "  node [style=\"filled,rounded\", shape=box, fontsize=\"9\", fontname=\"sans\", margin=\"0.0,0.0\"];\n"
+      "  edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n"
+      "  \n"
+      "  legend [\n"
+      "    pos=\"0,0!\",\n"
+      "    margin=\"0.05,0.05\",\n"
+      "    style=\"filled\",\n"
+      "    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\",\n"
+      "  ];"
+      "\n", G_OBJECT_TYPE_NAME (bin), GST_OBJECT_NAME (bin),
+      (state_name ? state_name : ""), (param_name ? param_name : "")
+      );
+
+  if (state_name)
+    g_free (state_name);
+  if (param_name)
+    g_free (param_name);
+}
+
+static void
+debug_dump_footer (GString * str)
+{
+  g_string_append_printf (str, "}\n");
+}
+
+/**
+ * gst_debug_bin_to_dot_data:
+ * @bin: the top-level pipeline that should be analyzed
+ * @details: type of #GstDebugGraphDetails to use
+ *
+ * To aid debugging applications one can use this method to obtain the whole
+ * network of gstreamer elements that form the pipeline into an dot file.
+ * This data can be processed with graphviz to get an image.
+ *
+ * Returns: (transfer full): a string containing the pipeline in graphviz
+ * dot format.
+ */
+gchar *
+gst_debug_bin_to_dot_data (GstBin * bin, GstDebugGraphDetails details)
+{
+  GString *str;
+
+  g_return_val_if_fail (GST_IS_BIN (bin), NULL);
+
+  str = g_string_new (NULL);
+
+  debug_dump_header (bin, details, str);
+  debug_dump_element (bin, details, str, 1);
+  debug_dump_footer (str);
+
+  return g_string_free (str, FALSE);
+}
+
+/**
  * gst_debug_bin_to_dot_file:
  * @bin: the top-level pipeline that should be analyzed
- * @file_name: output base filename (e.g. "myplayer")
+ * @details: type of #GstDebugGraphDetails to use
+ * @file_name: (type filename): output base filename (e.g. "myplayer")
  *
  * To aid debugging applications one can use this method to write out the whole
  * network of gstreamer elements that form the pipeline into an dot file.
@@ -623,41 +855,14 @@ gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details,
       priv_gst_dump_dot_dir, file_name);
 
   if ((out = fopen (full_file_name, "wb"))) {
-    gchar *state_name = NULL;
-    gchar *param_name = NULL;
+    gchar *buf;
 
-    if (details & GST_DEBUG_GRAPH_SHOW_STATES) {
-      state_name = debug_dump_get_element_state (GST_ELEMENT (bin));
-    }
-    if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) {
-      param_name = debug_dump_get_element_params (GST_ELEMENT (bin));
-    }
+    buf = gst_debug_bin_to_dot_data (bin, details);
+    fputs (buf, out);
 
-    /* write header */
-    fprintf (out,
-        "digraph pipeline {\n"
-        "  rankdir=LR;\n"
-        "  fontname=\"sans\";\n"
-        "  fontsize=\"10\";\n"
-        "  labelloc=t;\n"
-        "  nodesep=.1;\n"
-        "  ranksep=.2;\n"
-        "  label=\"<%s>\\n%s%s%s\";\n"
-        "  node [style=filled, shape=box, fontsize=\"9\", fontname=\"sans\", margin=\"0.0,0.0\"];\n"
-        "  edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n"
-        "\n", G_OBJECT_TYPE_NAME (bin), GST_OBJECT_NAME (bin),
-        (state_name ? state_name : ""), (param_name ? param_name : "")
-        );
-    if (state_name)
-      g_free (state_name);
-    if (param_name)
-      g_free (param_name);
-
-    debug_dump_element (bin, details, out, 1);
-
-    /* write footer */
-    fprintf (out, "}\n");
+    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,
@@ -666,10 +871,11 @@ gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details,
   g_free (full_file_name);
 }
 
-/*
+/**
  * gst_debug_bin_to_dot_file_with_ts:
  * @bin: the top-level pipeline that should be analyzed
- * @file_name: output base filename (e.g. "myplayer")
+ * @details: type of #GstDebugGraphDetails to use
+ * @file_name: (type filename): output base filename (e.g. "myplayer")
  *
  * This works like gst_debug_bin_to_dot_file(), but adds the current timestamp
  * to the filename, so that it can be used to take multiple snapshots.
@@ -690,8 +896,7 @@ gst_debug_bin_to_dot_file_with_ts (GstBin * bin,
   }
 
   /* add timestamp */
-  elapsed = GST_CLOCK_DIFF (_priv_gst_info_start_time,
-      gst_util_get_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 */