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