Index: docs/gst/gstreamer-sections.txt
===================================================================
RCS file: /cvs/gstreamer/gstreamer/docs/gst/gstreamer-sections.txt,v
retrieving revision 1.301
diff -u -p -u -r1.301 gstreamer-sections.txt
 <SUBSECTION element-pads>
 gst_element_add_pad
@@ -1643,6 +1644,10 @@ gst_query_new_segment
 gst_query_set_segment
 gst_query_parse_segment
 
+gst_query_new_qos
+gst_query_set_qos
+gst_query_parse_qos
+
 <SUBSECTION Standard>
 GstQueryClass
 GST_QUERY
Index: gst/gstquark.c
===================================================================
RCS file: /cvs/gstreamer/gstreamer/gst/gstquark.c,v
retrieving revision 1.1
diff -u -p -u -r1.1 gstquark.c
--- gst/gstquark.c	16 Aug 2006 11:47:54 -0000	1.1
+++ gst/gstquark.c	14 Jun 2007 12:05:55 -0000
@@ -29,7 +29,8 @@ static const gchar *_quark_strings[] = {
   "format", "current", "duration", "rate",
   "seekable", "segment-start", "segment-end",
   "src_format", "src_value", "dest_format", "dest_value",
-  "start_format", "start_value", "stop_format", "stop_value"
+  "start_format", "start_value", "stop_format", "stop_value",
+  "frames_rendered", "frames_dropped"
 };
 
 GQuark _priv_gst_quark_table[GST_QUARK_MAX];
Index: gst/gstquark.h
===================================================================
RCS file: /cvs/gstreamer/gstreamer/gst/gstquark.h,v
retrieving revision 1.1
diff -u -p -u -r1.1 gstquark.h
--- gst/gstquark.h	16 Aug 2006 11:47:54 -0000	1.1
+++ gst/gstquark.h	14 Jun 2007 12:05:55 -0000
@@ -41,8 +41,10 @@ typedef enum _GstQuarkId
   GST_QUARK_START_VALUE = 12,
   GST_QUARK_STOP_FORMAT = 13,
   GST_QUARK_STOP_VALUE = 14,
+  GST_QUARK_FRAMES_RENDERED = 15,
+  GST_QUARK_FRAMES_DROPPED = 16,
 
-  GST_QUARK_MAX = 15
+  GST_QUARK_MAX = 17
 } GstQuarkId;
 
 extern GQuark _priv_gst_quark_table[GST_QUARK_MAX];
Index: gst/gstquery.c
===================================================================
RCS file: /cvs/gstreamer/gstreamer/gst/gstquery.c,v
retrieving revision 1.38
diff -u -p -u -r1.38 gstquery.c
--- gst/gstquery.c	2 Feb 2007 18:08:32 -0000	1.38
+++ gst/gstquery.c	14 Jun 2007 12:05:55 -0000
@@ -90,6 +90,7 @@ static GstQueryTypeDefinition standard_d
   {GST_QUERY_SEGMENT, "segment", "currently configured segment", 0},
   {GST_QUERY_CONVERT, "convert", "Converting between formats", 0},
   {GST_QUERY_FORMATS, "formats", "Supported formats for conversion", 0},
+  {GST_QUERY_QOS, "qos", "QOS statistics", 0},
   {0, NULL, NULL, 0}
 };
 
@@ -815,6 +816,78 @@ gst_query_parse_segment (GstQuery * quer
 }
 
 /**
+ * gst_query_new_qos:
+ *
+ * Constructs a new qos query object. Use gst_query_unref()
+ * when done with it. A qos query is used to gather quality of service
+ * statistics.
+ *
+ * Returns: a #GstQuery
+ */
+GstQuery *
+gst_query_new_qos (void)
+{
+  GstQuery *query;
+  GstStructure *structure;
+
+  structure = gst_structure_empty_new ("GstQueryQOS");
+  gst_structure_id_set (structure,
+      GST_QUARK (FRAMES_RENDERED), G_TYPE_UINT64, (guint64) 0,
+      GST_QUARK (FRAMES_DROPPED), G_TYPE_UINT64, (guint64) 0, NULL);
+
+  query = gst_query_new (GST_QUERY_QOS, structure);
+
+  return query;
+}
+
+/**
+ * gst_query_set_qos:
+ * @query: a #GstQuery
+ * @frames_rendered: the rendered frames
+ * @frames_dropped: the dropped frames
+ *
+ * Answer a qos query by setting the requested values.
+ */
+void
+gst_query_set_qos (GstQuery *query, guint64 frames_rendered, guint64 frames_dropped)
+{
+  GstStructure *structure;
+
+  g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_QOS);
+
+  structure = gst_query_get_structure (query);
+  gst_structure_id_set (structure,
+      GST_QUARK (FRAMES_RENDERED), G_TYPE_UINT64, frames_rendered,
+      GST_QUARK (FRAMES_DROPPED), G_TYPE_UINT64, frames_dropped, NULL);
+
+}
+
+/**
+ * gst_query_parse_qos:
+ * @query: a #GstQuery
+ * @frames_rendered: the storage for the rendered frames, or NULL
+ * @frames_dropped: the storage for the dropped frames, or NULL
+ *
+ * Parse a qos query answer. Any of @frames_rendered and 
+ * @frames_dropped may be NULL, which will cause this value to be omitted.
+ */
+void
+gst_query_parse_qos (GstQuery *query, guint64 *frames_rendered, guint64 *frames_dropped)
+{
+  GstStructure *structure;
+
+  g_return_if_fail (GST_QUERY_TYPE (query) == GST_QUERY_QOS);
+
+  structure = gst_query_get_structure (query);
+  if (frames_rendered)
+    *frames_rendered = g_value_get_uint64 (gst_structure_id_get_value (structure,
+            GST_QUARK (FRAMES_RENDERED)));
+  if (frames_dropped)
+    *frames_dropped = g_value_get_uint64 (gst_structure_id_get_value (structure,
+            GST_QUARK (FRAMES_DROPPED)));
+}
+
+/**
  * gst_query_new_application:
  * @type: the query type
  * @structure: a structure for the query
Index: gst/gstquery.h
===================================================================
RCS file: /cvs/gstreamer/gstreamer/gst/gstquery.h,v
retrieving revision 1.35
diff -u -p -u -r1.35 gstquery.h
--- gst/gstquery.h	2 Feb 2007 11:48:48 -0000	1.35
+++ gst/gstquery.h	14 Jun 2007 12:05:55 -0000
@@ -46,6 +46,7 @@ G_BEGIN_DECLS
  * @GST_QUERY_SEGMENT: segment start/stop positions
  * @GST_QUERY_CONVERT: convert values between formats
  * @GST_QUERY_FORMATS: query supported formats for convert
+ * @GST_QUERY_QOS: qos statistics
  *
  * Standard predefined Query types
  */
@@ -61,7 +62,8 @@ typedef enum {
   GST_QUERY_SEEKING,
   GST_QUERY_SEGMENT,
   GST_QUERY_CONVERT,
-  GST_QUERY_FORMATS
+  GST_QUERY_FORMATS,
+  GST_QUERY_QOS
 } GstQueryType;
 
 typedef struct _GstQueryTypeDefinition GstQueryTypeDefinition;
@@ -224,6 +226,11 @@ void            gst_query_set_segment   
                                                  gint64 start_value, gint64 stop_value);
 void            gst_query_parse_segment         (GstQuery *query, gdouble *rate, GstFormat *format,
                                                  gint64 *start_value, gint64 *stop_value);
+/* qos query */
+GstQuery*       gst_query_new_qos               (void);
+void            gst_query_set_qos               (GstQuery *query, guint64 frames_rendered, guint64 frames_dropped);
+void            gst_query_parse_qos             (GstQuery *query, guint64 *frames_rendered, guint64 *frames_dropped);
+
 
 /* application specific query */
 GstQuery *	gst_query_new_application 	(GstQueryType type,
Index: libs/gst/base/gstbasesink.c
===================================================================
RCS file: /cvs/gstreamer/gstreamer/libs/gst/base/gstbasesink.c,v
retrieving revision 1.172
diff -u -p -u -r1.172 gstbasesink.c
--- libs/gst/base/gstbasesink.c	14 Jun 2007 12:03:44 -0000	1.172
+++ libs/gst/base/gstbasesink.c	14 Jun 2007 12:05:56 -0000
@@ -2799,6 +2799,14 @@ gst_base_sink_query (GstElement * elemen
           GST_FORMAT_TIME, basesink->segment.start, basesink->segment.stop);
       break;
     }
+    case GST_QUERY_QOS:
+      GST_DEBUG_OBJECT (basesink, "rendered: %" G_GUINT64_FORMAT
+          ", dropped: %" G_GUINT64_FORMAT, basesink->priv->rendered,
+          basesink->priv->dropped);
+      gst_query_set_qos (query, basesink->priv->rendered,
+          basesink->priv->dropped);
+      res = TRUE;
+      break;
     case GST_QUERY_SEEKING:
     case GST_QUERY_CONVERT:
     case GST_QUERY_FORMATS:
Index: tools/gst-launch.c
===================================================================
RCS file: /cvs/gstreamer/gstreamer/tools/gst-launch.c,v
retrieving revision 1.122
diff -u -p -u -r1.122 gst-launch.c
--- tools/gst-launch.c	24 May 2007 15:00:55 -0000	1.122
+++ tools/gst-launch.c	14 Jun 2007 12:05:57 -0000
@@ -61,6 +61,11 @@ static gboolean caught_error = FALSE;
 static gboolean tags = FALSE;
 static gboolean messages = FALSE;
 
+static GSList *qos_data = NULL;
+typedef struct _QOSData {
+  GstClockTime position;
+  gdouble proportion;
+} QOSData;
 
 #ifndef GST_DISABLE_LOADSAVE
 static GstElement *
@@ -555,6 +560,20 @@ exit:
   }
 }
 
+static gboolean
+qos_event_probe_handler (GstPad * sinkpad, GstEvent * event, gpointer *user_data)
+{
+  if (GST_EVENT_TYPE (event) == GST_EVENT_QOS) {
+    GstClockTimeDiff diff;
+    QOSData *qos_data_item = g_new (QOSData, 1);
+
+    gst_event_parse_qos (event, &qos_data_item->proportion, &diff, &qos_data_item->position);
+
+    qos_data = g_slist_prepend ( qos_data, qos_data_item);
+  }
+  return TRUE;
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -564,6 +583,8 @@ main (int argc, char *argv[])
   gboolean verbose = FALSE;
   gboolean no_fault = FALSE;
   gboolean trace = FALSE;
+  gboolean qos_report = FALSE;
+  gchar *qos_report_file = FALSE;
   gchar *savefile = NULL;
   gchar *exclude_args = NULL;
   GOptionEntry options[] = {
@@ -573,6 +594,10 @@ main (int argc, char *argv[])
         N_("Output status information and property notifications"), NULL},
     {"messages", 'm', 0, G_OPTION_ARG_NONE, &messages,
         N_("Output messages"), NULL},
+    {"report", 'r', 0, G_OPTION_ARG_NONE, &qos_report,
+        N_("Track QOS and display sumary after EOS"), NULL},
+    {"report-file", 0 , 0, G_OPTION_ARG_STRING, &qos_report_file,
+        N_("Write QOS event data to a data file"), NULL},
     {"exclude", 'X', 0, G_OPTION_ARG_NONE, &exclude_args,
         N_("Do not output status information of TYPE"), N_("TYPE1,TYPE2,...")},
 #ifndef GST_DISABLE_LOADSAVE
@@ -694,6 +721,33 @@ main (int argc, char *argv[])
     gst_xml_write_file (GST_ELEMENT (pipeline), fopen (savefile, "w"));
   }
 #endif
+  if (qos_report) {
+    GstIterator *it;
+    GstElement *sink;
+    GstPad *sinkpad;
+    gpointer elem;
+    gboolean done=FALSE;
+
+    /* get base_sink elements */
+    it = gst_bin_iterate_sinks (GST_BIN (pipeline));
+    while (!done) {
+      switch (gst_iterator_next (it, &elem)) {
+        case GST_ITERATOR_OK:
+          sink = GST_ELEMENT (elem);
+          if ((sinkpad = gst_element_get_pad (sink, "sink"))) {
+            gst_pad_add_event_probe (sinkpad, G_CALLBACK (qos_event_probe_handler), NULL);
+            GST_INFO ("probing events for %s",GST_ELEMENT_NAME (sink));
+          }
+          break;
+        case GST_ITERATOR_DONE:
+        case GST_ITERATOR_RESYNC:
+        case GST_ITERATOR_ERROR:
+          done = TRUE;
+          break;
+      }
+    }
+    gst_iterator_free (it);
+  }
 
   if (!savefile) {
     GstState state, pending;
@@ -764,9 +818,44 @@ main (int argc, char *argv[])
     /* iterate mainloop to process pending stuff */
     while (g_main_context_iteration (NULL, FALSE));
 
+    /* print qos query results (they get reset on playin->paused) */
+    if (qos_report) {
+      GstIterator *it;
+      GstQuery *qos_query;
+      gpointer elem;
+      gboolean done=FALSE;
+      guint64 frames_rendered,frames_dropped;
+
+      GST_INFO ("qos report");
+      qos_query = gst_query_new_qos ();
+      it = gst_bin_iterate_elements (GST_BIN (pipeline));
+      g_print ("           name rendered  dropped\n");
+      while (!done) {
+        switch (gst_iterator_next (it, &elem)) {
+          case GST_ITERATOR_OK:
+            GST_INFO ("  qos query for %s",GST_ELEMENT_NAME (GST_ELEMENT (elem)));
+            if (gst_element_query (GST_ELEMENT (elem), qos_query)) {
+              gst_query_parse_qos (qos_query, &frames_rendered, &frames_dropped);
+              g_print ("%14s  %8" G_GUINT64_FORMAT " %8" G_GUINT64_FORMAT "\n",
+                  GST_ELEMENT_NAME (GST_ELEMENT (elem)),
+                  frames_rendered, frames_dropped);
+            }
+            break;
+          case GST_ITERATOR_DONE:
+          case GST_ITERATOR_RESYNC:
+          case GST_ITERATOR_ERROR:
+            done = TRUE;
+            break;
+        }
+      }
+      gst_iterator_free (it);
+      gst_query_unref (qos_query);
+    }
+
     fprintf (stderr, _("Setting pipeline to PAUSED ...\n"));
     gst_element_set_state (pipeline, GST_STATE_PAUSED);
     gst_element_get_state (pipeline, &state, &pending, GST_CLOCK_TIME_NONE);
+
     fprintf (stderr, _("Setting pipeline to READY ...\n"));
     gst_element_set_state (pipeline, GST_STATE_READY);
     gst_element_get_state (pipeline, &state, &pending, GST_CLOCK_TIME_NONE);
@@ -777,6 +866,33 @@ main (int argc, char *argv[])
     gst_element_get_state (pipeline, &state, &pending, GST_CLOCK_TIME_NONE);
   }
 
+  /* print qos data to file */
+  if (qos_report) {
+    GSList *node;
+    FILE *fp = NULL;
+
+    fp = fopen (qos_report_file,"wt");
+
+    for (node = g_slist_reverse(qos_data); node; node = g_slist_next (node)) {
+      QOSData *qos_data_entry = node->data;
+
+      if (fp) {
+        /* gnuplot can't understand milliseconds
+        fprintf (fp, "%" GST_TIME_FORMAT "\t %lf\n",
+            GST_TIME_ARGS (qos_data_entry->position), qos_data_entry->proportion);
+        */
+        fprintf (fp, "%lf\t %lf\n",
+            (gdouble) qos_data_entry->position / GST_SECOND,
+            qos_data_entry->proportion);
+        /* FIXME: if would be useful to get a log from each element when they
+         * drop frames, so that we can show this in the graph as well */
+      }
+      g_free (qos_data_entry);
+    }
+    g_slist_free (qos_data);
+    if (fp) fclose (fp);
+  }
+
   fprintf (stderr, _("FREEING pipeline ...\n"));
   gst_object_unref (pipeline);
 
