• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer Progress Report Element
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  * Copyright (C) <2004> Jan Schmidt <thaytan@mad.scientist.com>
5  * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 /**
24  * SECTION:element-progressreport
25  * @title: progressreport
26  *
27  * The progressreport element can be put into a pipeline to report progress,
28  * which is done by doing upstream duration and position queries in regular
29  * (real-time) intervals. Both the interval and the preferred query format
30  * can be specified via the #GstProgressReport:update-freq and the
31  * #GstProgressReport:format property.
32  *
33  * Element messages containing a "progress" structure are posted on the bus
34  * whenever progress has been queried (since gst-plugins-good 0.10.6 only).
35  *
36  * Since the element was originally designed for debugging purposes, it will
37  * by default also print information about the current progress to the
38  * terminal. This can be prevented by setting the #GstProgressReport:silent
39  * property to %TRUE.
40  *
41  * This element is most useful in transcoding pipelines or other situations
42  * where just querying the pipeline might not lead to the wanted result. For
43  * progress in TIME format, the element is best placed in a 'raw stream'
44  * section of the pipeline (or after any demuxers/decoders/parsers).
45  *
46  * Three more things should be pointed out: firstly, the element will only
47  * query progress when data flow happens. If data flow is stalled for some
48  * reason, no progress messages will be posted. Secondly, there are other
49  * elements (like qtdemux, for example) that may also post "progress" element
50  * messages on the bus. Applications should check the source of any element
51  * messages they receive, if needed. Finally, applications should not take
52  * action on receiving notification of progress being 100%, they should only
53  * take action when they receive an EOS message (since the progress reported
54  * is in reference to an internal point of a pipeline and not the pipeline as
55  * a whole).
56  *
57  * ## Example launch line
58  * |[
59  * gst-launch-1.0 -m filesrc location=foo.ogg ! decodebin ! progressreport update-freq=1 ! audioconvert ! audioresample ! autoaudiosink
60  * ]| This shows a progress query where a duration is available.
61  * |[
62  * gst-launch-1.0 -m audiotestsrc ! progressreport update-freq=1 ! audioconvert ! autoaudiosink
63  * ]| This shows a progress query where no duration is available.
64  *
65  */
66 
67 #ifdef HAVE_CONFIG_H
68 #include "config.h"
69 #endif
70 
71 #include <gst/gst.h>
72 #include <string.h>
73 #include <math.h>
74 #include <time.h>
75 
76 #include "gstdebugutilselements.h"
77 #include "progressreport.h"
78 
79 
80 enum
81 {
82   PROP_0,
83   PROP_UPDATE_FREQ,
84   PROP_SILENT,
85   PROP_DO_QUERY,
86   PROP_FORMAT
87 };
88 
89 GstStaticPadTemplate progress_report_src_template =
90 GST_STATIC_PAD_TEMPLATE ("src",
91     GST_PAD_SRC,
92     GST_PAD_ALWAYS,
93     GST_STATIC_CAPS_ANY);
94 
95 GstStaticPadTemplate progress_report_sink_template =
96 GST_STATIC_PAD_TEMPLATE ("sink",
97     GST_PAD_SINK,
98     GST_PAD_ALWAYS,
99     GST_STATIC_CAPS_ANY);
100 
101 #define DEFAULT_UPDATE_FREQ  5
102 #define DEFAULT_SILENT       FALSE
103 #define DEFAULT_DO_QUERY     TRUE
104 #define DEFAULT_FORMAT       "auto"
105 
106 static void gst_progress_report_set_property (GObject * object, guint prop_id,
107     const GValue * value, GParamSpec * pspec);
108 static void gst_progress_report_get_property (GObject * object, guint prop_id,
109     GValue * value, GParamSpec * pspec);
110 
111 static gboolean gst_progress_report_sink_event (GstBaseTransform * trans,
112     GstEvent * event);
113 static GstFlowReturn gst_progress_report_transform_ip (GstBaseTransform * trans,
114     GstBuffer * buf);
115 
116 static gboolean gst_progress_report_start (GstBaseTransform * trans);
117 static gboolean gst_progress_report_stop (GstBaseTransform * trans);
118 
119 #define gst_progress_report_parent_class parent_class
120 G_DEFINE_TYPE (GstProgressReport, gst_progress_report, GST_TYPE_BASE_TRANSFORM);
121 GST_ELEMENT_REGISTER_DEFINE (progressreport, "progressreport",
122     GST_RANK_NONE, gst_progress_report_get_type ());
123 
124 static void
gst_progress_report_finalize(GObject * obj)125 gst_progress_report_finalize (GObject * obj)
126 {
127   GstProgressReport *filter = GST_PROGRESS_REPORT (obj);
128 
129   g_free (filter->format);
130   filter->format = NULL;
131 
132   G_OBJECT_CLASS (parent_class)->finalize (obj);
133 }
134 
135 static void
gst_progress_report_class_init(GstProgressReportClass * g_class)136 gst_progress_report_class_init (GstProgressReportClass * g_class)
137 {
138   GstBaseTransformClass *gstbasetrans_class;
139   GstElementClass *element_class;
140   GObjectClass *gobject_class;
141 
142   gobject_class = G_OBJECT_CLASS (g_class);
143   element_class = GST_ELEMENT_CLASS (g_class);
144   gstbasetrans_class = GST_BASE_TRANSFORM_CLASS (g_class);
145 
146   gobject_class->finalize = gst_progress_report_finalize;
147   gobject_class->set_property = gst_progress_report_set_property;
148   gobject_class->get_property = gst_progress_report_get_property;
149 
150   g_object_class_install_property (gobject_class,
151       PROP_UPDATE_FREQ, g_param_spec_int ("update-freq", "Update Frequency",
152           "Number of seconds between reports when data is flowing", 1, G_MAXINT,
153           DEFAULT_UPDATE_FREQ, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154 
155   g_object_class_install_property (gobject_class,
156       PROP_SILENT, g_param_spec_boolean ("silent",
157           "Do not print output to stdout", "Do not print output to stdout",
158           DEFAULT_SILENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
159 
160   g_object_class_install_property (gobject_class,
161       PROP_DO_QUERY, g_param_spec_boolean ("do-query",
162           "Use a query instead of buffer metadata to determine stream position",
163           "Use a query instead of buffer metadata to determine stream position",
164           DEFAULT_DO_QUERY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
165 
166   g_object_class_install_property (gobject_class,
167       PROP_FORMAT, g_param_spec_string ("format", "format",
168           "Format to use for the querying", DEFAULT_FORMAT,
169           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
170 
171   gst_element_class_add_static_pad_template (element_class,
172       &progress_report_sink_template);
173   gst_element_class_add_static_pad_template (element_class,
174       &progress_report_src_template);
175 
176   gst_element_class_set_static_metadata (element_class, "Progress report",
177       "Testing",
178       "Periodically query and report on processing progress",
179       "Jan Schmidt <thaytan@mad.scientist.com>");
180 
181   gstbasetrans_class->sink_event =
182       GST_DEBUG_FUNCPTR (gst_progress_report_sink_event);
183   gstbasetrans_class->transform_ip =
184       GST_DEBUG_FUNCPTR (gst_progress_report_transform_ip);
185   gstbasetrans_class->start = GST_DEBUG_FUNCPTR (gst_progress_report_start);
186   gstbasetrans_class->stop = GST_DEBUG_FUNCPTR (gst_progress_report_stop);
187 }
188 
189 static void
gst_progress_report_init(GstProgressReport * report)190 gst_progress_report_init (GstProgressReport * report)
191 {
192   gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (report), TRUE);
193 
194   report->update_freq = DEFAULT_UPDATE_FREQ;
195   report->silent = DEFAULT_SILENT;
196   report->do_query = DEFAULT_DO_QUERY;
197   report->format = g_strdup (DEFAULT_FORMAT);
198 }
199 
200 static void
gst_progress_report_post_progress(GstProgressReport * filter,GstFormat format,gint64 current,gint64 total)201 gst_progress_report_post_progress (GstProgressReport * filter,
202     GstFormat format, gint64 current, gint64 total)
203 {
204   GstStructure *s = NULL;
205 
206   if (current >= 0 && total > 0) {
207     gdouble perc;
208 
209     perc = gst_util_guint64_to_gdouble (current) * 100.0 /
210         gst_util_guint64_to_gdouble (total);
211     perc = CLAMP (perc, 0.0, 100.0);
212 
213     /* we provide a "percent" field of integer type to stay compatible
214      * with qtdemux, but add a second "percent-double" field for those who
215      * want more precision and are too lazy to calculate it themselves */
216     s = gst_structure_new ("progress", "percent", G_TYPE_INT, (gint) perc,
217         "percent-double", G_TYPE_DOUBLE, perc, "current", G_TYPE_INT64, current,
218         "total", G_TYPE_INT64, total, NULL);
219   } else if (current >= 0) {
220     s = gst_structure_new ("progress", "current", G_TYPE_INT64, current, NULL);
221   }
222 
223   if (s) {
224     GST_LOG_OBJECT (filter, "posting progress message: %" GST_PTR_FORMAT, s);
225     gst_structure_set (s, "format", GST_TYPE_FORMAT, format, NULL);
226     /* can't post it right here because we're holding the object lock */
227     filter->pending_msg = gst_message_new_element (GST_OBJECT_CAST (filter), s);
228   }
229 }
230 
231 static gboolean
gst_progress_report_do_query(GstProgressReport * filter,GstFormat format,gint hh,gint mm,gint ss,GstBuffer * buf)232 gst_progress_report_do_query (GstProgressReport * filter, GstFormat format,
233     gint hh, gint mm, gint ss, GstBuffer * buf)
234 {
235   const gchar *format_name = NULL;
236   GstPad *sink_pad;
237   gint64 cur, total;
238 
239   sink_pad = GST_BASE_TRANSFORM (filter)->sinkpad;
240 
241   GST_LOG_OBJECT (filter, "querying using format %d (%s)", format,
242       gst_format_get_name (format));
243 
244   if (filter->do_query || !buf) {
245     GST_LOG_OBJECT (filter, "using upstream query");
246     if (!gst_pad_peer_query_position (sink_pad, format, &cur) ||
247         !gst_pad_peer_query_duration (sink_pad, format, &total)) {
248       return FALSE;
249     }
250   } else {
251     GstBaseTransform *base = GST_BASE_TRANSFORM (filter);
252 
253     GST_LOG_OBJECT (filter, "using buffer metadata");
254     if (format == GST_FORMAT_TIME && base->segment.format == GST_FORMAT_TIME) {
255       cur = gst_segment_to_stream_time (&base->segment, format,
256           GST_BUFFER_TIMESTAMP (buf));
257       total = base->segment.duration;
258     } else if (format == GST_FORMAT_BUFFERS) {
259       cur = filter->buffer_count;
260       total = -1;
261     } else {
262       return FALSE;
263     }
264   }
265 
266   switch (format) {
267     case GST_FORMAT_BYTES:
268       format_name = "bytes";
269       break;
270     case GST_FORMAT_BUFFERS:
271       format_name = "buffers";
272       break;
273     case GST_FORMAT_PERCENT:
274       format_name = "percent";
275       break;
276     case GST_FORMAT_TIME:
277       format_name = "seconds";
278       cur /= GST_SECOND;
279       total /= GST_SECOND;
280       break;
281     case GST_FORMAT_DEFAULT:{
282       GstCaps *caps;
283 
284       format_name = "bogounits";
285       caps = gst_pad_get_current_caps (GST_BASE_TRANSFORM (filter)->sinkpad);
286       if (caps) {
287         if (gst_caps_is_fixed (caps) && !gst_caps_is_any (caps)) {
288           GstStructure *s = gst_caps_get_structure (caps, 0);
289           const gchar *mime_type = gst_structure_get_name (s);
290 
291           if (g_str_has_prefix (mime_type, "video/") ||
292               g_str_has_prefix (mime_type, "image/")) {
293             format_name = "frames";
294           } else if (g_str_has_prefix (mime_type, "audio/")) {
295             format_name = "samples";
296           }
297         }
298         gst_caps_unref (caps);
299       }
300       break;
301     }
302     default:{
303       const GstFormatDefinition *details;
304 
305       details = gst_format_get_details (format);
306       if (details) {
307         format_name = details->nick;
308       } else {
309         format_name = "unknown";
310       }
311       break;
312     }
313   }
314 
315   if (!filter->silent) {
316     if (total > 0) {
317       g_print ("%s (%02d:%02d:%02d): %" G_GINT64_FORMAT " / %"
318           G_GINT64_FORMAT " %s (%4.1f %%)\n", GST_OBJECT_NAME (filter), hh,
319           mm, ss, cur, total, format_name, (gdouble) cur / total * 100.0);
320     } else {
321       g_print ("%s (%02d:%02d:%02d): %" G_GINT64_FORMAT " %s\n",
322           GST_OBJECT_NAME (filter), hh, mm, ss, cur, format_name);
323     }
324   }
325 
326   gst_progress_report_post_progress (filter, format, cur, total);
327   return TRUE;
328 }
329 
330 static void
gst_progress_report_report(GstProgressReport * filter,gint64 cur_time_s,GstBuffer * buf)331 gst_progress_report_report (GstProgressReport * filter, gint64 cur_time_s,
332     GstBuffer * buf)
333 {
334   GstFormat try_formats[] = { GST_FORMAT_TIME, GST_FORMAT_BYTES,
335     GST_FORMAT_PERCENT, GST_FORMAT_BUFFERS,
336     GST_FORMAT_DEFAULT
337   };
338   GstMessage *msg;
339   GstFormat format = GST_FORMAT_UNDEFINED;
340   gboolean done = FALSE;
341   glong run_time;
342   gint hh, mm, ss;
343 
344   run_time = cur_time_s - filter->start_time_s;
345 
346   hh = (run_time / 3600) % 100;
347   mm = (run_time / 60) % 60;
348   ss = (run_time % 60);
349 
350   GST_OBJECT_LOCK (filter);
351 
352   if (filter->format != NULL && strcmp (filter->format, "auto") != 0) {
353     format = gst_format_get_by_nick (filter->format);
354   }
355 
356   if (format != GST_FORMAT_UNDEFINED) {
357     done = gst_progress_report_do_query (filter, format, hh, mm, ss, buf);
358   } else {
359     gint i;
360 
361     for (i = 0; i < G_N_ELEMENTS (try_formats); ++i) {
362       done = gst_progress_report_do_query (filter, try_formats[i], hh, mm, ss,
363           buf);
364       if (done)
365         break;
366     }
367   }
368 
369   if (!done && !filter->silent) {
370     g_print ("%s (%2d:%2d:%2d): Could not query position and/or duration\n",
371         GST_OBJECT_NAME (filter), hh, mm, ss);
372   }
373 
374   msg = filter->pending_msg;
375   filter->pending_msg = NULL;
376   GST_OBJECT_UNLOCK (filter);
377 
378   if (msg) {
379     gst_element_post_message (GST_ELEMENT_CAST (filter), msg);
380   }
381 }
382 
383 static gboolean
gst_progress_report_sink_event(GstBaseTransform * trans,GstEvent * event)384 gst_progress_report_sink_event (GstBaseTransform * trans, GstEvent * event)
385 {
386   GstProgressReport *filter;
387 
388   filter = GST_PROGRESS_REPORT (trans);
389 
390   switch (GST_EVENT_TYPE (event)) {
391     case GST_EVENT_EOS:
392     {
393       gint64 cur_time_s = g_get_real_time () / G_USEC_PER_SEC;
394 
395       gst_progress_report_report (filter, cur_time_s, NULL);
396       break;
397     }
398     default:
399       break;
400   }
401   return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
402 }
403 
404 static GstFlowReturn
gst_progress_report_transform_ip(GstBaseTransform * trans,GstBuffer * buf)405 gst_progress_report_transform_ip (GstBaseTransform * trans, GstBuffer * buf)
406 {
407   GstProgressReport *filter;
408   gboolean need_update;
409   gint64 cur_time;
410 
411   cur_time = g_get_real_time () / G_USEC_PER_SEC;
412 
413   filter = GST_PROGRESS_REPORT (trans);
414 
415   /* Check if update_freq seconds have passed since the last update */
416   GST_OBJECT_LOCK (filter);
417   need_update = (cur_time - filter->last_report_s) >= filter->update_freq;
418   filter->buffer_count++;
419   GST_OBJECT_UNLOCK (filter);
420 
421   if (need_update) {
422     gst_progress_report_report (filter, cur_time, buf);
423     GST_OBJECT_LOCK (filter);
424     filter->last_report_s = cur_time;
425     GST_OBJECT_UNLOCK (filter);
426   }
427 
428   return GST_FLOW_OK;
429 }
430 
431 static gboolean
gst_progress_report_start(GstBaseTransform * trans)432 gst_progress_report_start (GstBaseTransform * trans)
433 {
434   GstProgressReport *filter;
435 
436   filter = GST_PROGRESS_REPORT (trans);
437 
438   filter->start_time_s = filter->last_report_s =
439       g_get_real_time () / G_USEC_PER_SEC;
440   filter->buffer_count = 0;
441 
442   return TRUE;
443 }
444 
445 static gboolean
gst_progress_report_stop(GstBaseTransform * trans)446 gst_progress_report_stop (GstBaseTransform * trans)
447 {
448   /* anything we should be doing here? */
449   return TRUE;
450 }
451 
452 static void
gst_progress_report_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)453 gst_progress_report_set_property (GObject * object, guint prop_id,
454     const GValue * value, GParamSpec * pspec)
455 {
456   GstProgressReport *filter;
457 
458   filter = GST_PROGRESS_REPORT (object);
459 
460   switch (prop_id) {
461     case PROP_UPDATE_FREQ:
462       GST_OBJECT_LOCK (filter);
463       filter->update_freq = g_value_get_int (value);
464       GST_OBJECT_UNLOCK (filter);
465       break;
466     case PROP_SILENT:
467       GST_OBJECT_LOCK (filter);
468       filter->silent = g_value_get_boolean (value);
469       GST_OBJECT_UNLOCK (filter);
470       break;
471     case PROP_DO_QUERY:
472       GST_OBJECT_LOCK (filter);
473       filter->do_query = g_value_get_boolean (value);
474       GST_OBJECT_UNLOCK (filter);
475       break;
476     case PROP_FORMAT:
477       GST_OBJECT_LOCK (filter);
478       g_free (filter->format);
479       filter->format = g_value_dup_string (value);
480       if (filter->format == NULL)
481         filter->format = g_strdup ("auto");
482       GST_OBJECT_UNLOCK (filter);
483       break;
484     default:
485       break;
486   }
487 }
488 
489 static void
gst_progress_report_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)490 gst_progress_report_get_property (GObject * object, guint prop_id,
491     GValue * value, GParamSpec * pspec)
492 {
493   GstProgressReport *filter;
494 
495   filter = GST_PROGRESS_REPORT (object);
496 
497   switch (prop_id) {
498     case PROP_UPDATE_FREQ:
499       GST_OBJECT_LOCK (filter);
500       g_value_set_int (value, filter->update_freq);
501       GST_OBJECT_UNLOCK (filter);
502       break;
503     case PROP_SILENT:
504       GST_OBJECT_LOCK (filter);
505       g_value_set_boolean (value, filter->silent);
506       GST_OBJECT_UNLOCK (filter);
507       break;
508     case PROP_DO_QUERY:
509       GST_OBJECT_LOCK (filter);
510       g_value_set_boolean (value, filter->do_query);
511       GST_OBJECT_UNLOCK (filter);
512       break;
513     case PROP_FORMAT:
514       GST_OBJECT_LOCK (filter);
515       g_value_set_string (value, filter->format);
516       GST_OBJECT_UNLOCK (filter);
517       break;
518     default:
519       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
520       break;
521   }
522 }
523