ec4b33812929232bcf2d51e701acb0cb99c01ae3
[platform/upstream/gstreamer.git] / tools / gst-play.c
1 /* GStreamer command line playback testing utility
2  *
3  * Copyright (C) 2013 Tim-Philipp Müller <tim centricular net>
4  * Copyright (C) 2013 Collabora Ltd.
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., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <gst/gst.h>
27 #include <gst/gst-i18n-app.h>
28 #include <gst/pbutils/pbutils.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32
33 #include "gst-play-kb.h"
34
35 GST_DEBUG_CATEGORY (play_debug);
36 #define GST_CAT_DEFAULT play_debug
37
38 typedef struct
39 {
40   gchar **uris;
41   guint num_uris;
42   gint cur_idx;
43
44   GstElement *playbin;
45
46   GMainLoop *loop;
47   guint bus_watch;
48   guint timeout;
49
50   /* missing plugin messages */
51   GList *missing;
52
53   gboolean buffering;
54   gboolean is_live;
55
56   GstState desired_state;       /* as per user interaction, PAUSED or PLAYING */
57
58   /* configuration */
59   gboolean gapless;
60 } GstPlay;
61
62 static gboolean play_bus_msg (GstBus * bus, GstMessage * msg, gpointer data);
63 static gboolean play_next (GstPlay * play);
64 static gboolean play_prev (GstPlay * play);
65 static gboolean play_timeout (gpointer user_data);
66 static void play_about_to_finish (GstElement * playbin, gpointer user_data);
67 static void play_reset (GstPlay * play);
68
69 static GstPlay *
70 play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink,
71     gboolean gapless)
72 {
73   GstElement *sink;
74   GstPlay *play;
75
76   play = g_new0 (GstPlay, 1);
77
78   play->uris = uris;
79   play->num_uris = g_strv_length (uris);
80   play->cur_idx = -1;
81
82   play->playbin = gst_element_factory_make ("playbin", "playbin");
83
84   if (audio_sink != NULL) {
85     sink = gst_element_factory_make (audio_sink, NULL);
86     if (sink != NULL)
87       g_object_set (play->playbin, "audio-sink", sink, NULL);
88     else
89       g_warning ("Couldn't create specified audio sink '%s'", audio_sink);
90   }
91   if (video_sink != NULL) {
92     sink = gst_element_factory_make (video_sink, NULL);
93     if (sink != NULL)
94       g_object_set (play->playbin, "video-sink", sink, NULL);
95     else
96       g_warning ("Couldn't create specified video sink '%s'", video_sink);
97   }
98
99   play->loop = g_main_loop_new (NULL, FALSE);
100
101   play->bus_watch = gst_bus_add_watch (GST_ELEMENT_BUS (play->playbin),
102       play_bus_msg, play);
103
104   /* FIXME: make configurable incl. 0 for disable */
105   play->timeout = g_timeout_add (100, play_timeout, play);
106
107   play->missing = NULL;
108   play->buffering = FALSE;
109   play->is_live = FALSE;
110
111   play->desired_state = GST_STATE_PLAYING;
112
113   play->gapless = gapless;
114   if (gapless) {
115     g_signal_connect (play->playbin, "about-to-finish",
116         G_CALLBACK (play_about_to_finish), play);
117   }
118
119   return play;
120 }
121
122 static void
123 play_free (GstPlay * play)
124 {
125   play_reset (play);
126
127   gst_element_set_state (play->playbin, GST_STATE_NULL);
128   gst_object_unref (play->playbin);
129
130   g_source_remove (play->bus_watch);
131   g_source_remove (play->timeout);
132   g_main_loop_unref (play->loop);
133
134   g_strfreev (play->uris);
135   g_free (play);
136 }
137
138 /* reset for new file/stream */
139 static void
140 play_reset (GstPlay * play)
141 {
142   g_list_foreach (play->missing, (GFunc) gst_message_unref, NULL);
143   play->missing = NULL;
144
145   play->buffering = FALSE;
146   play->is_live = FALSE;
147 }
148
149 /* returns TRUE if something was installed and we should restart playback */
150 static gboolean
151 play_install_missing_plugins (GstPlay * play)
152 {
153   /* FIXME: implement: try to install any missing plugins we haven't
154    * tried to install before */
155   return FALSE;
156 }
157
158 static gboolean
159 play_bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data)
160 {
161   GstPlay *play = user_data;
162
163   switch (GST_MESSAGE_TYPE (msg)) {
164     case GST_MESSAGE_ASYNC_DONE:
165       g_print ("Prerolled.\r");
166       if (play->missing != NULL && play_install_missing_plugins (play)) {
167         g_print ("New plugins installed, trying again...\n");
168         --play->cur_idx;
169         play_next (play);
170       }
171       break;
172     case GST_MESSAGE_BUFFERING:{
173       gint percent;
174
175       if (!play->buffering)
176         g_print ("\n");
177
178       gst_message_parse_buffering (msg, &percent);
179       g_print ("%s %d%%  \r", _("Buffering..."), percent);
180
181       /* no state management needed for live pipelines */
182       if (play->is_live)
183         break;
184
185       if (percent == 100) {
186         /* a 100% message means buffering is done */
187         if (play->buffering) {
188           play->buffering = FALSE;
189           gst_element_set_state (play->playbin, play->desired_state);
190         }
191       } else {
192         /* buffering... */
193         if (!play->buffering) {
194           gst_element_set_state (play->playbin, GST_STATE_PAUSED);
195           play->buffering = TRUE;
196         }
197       }
198       break;
199     }
200     case GST_MESSAGE_LATENCY:
201       g_print ("Redistribute latency...\n");
202       gst_bin_recalculate_latency (GST_BIN (play->playbin));
203       break;
204     case GST_MESSAGE_REQUEST_STATE:{
205       GstState state;
206       gchar *name;
207
208       name = gst_object_get_path_string (GST_MESSAGE_SRC (msg));
209
210       gst_message_parse_request_state (msg, &state);
211
212       g_print ("Setting state to %s as requested by %s...\n",
213           gst_element_state_get_name (state), name);
214
215       gst_element_set_state (play->playbin, state);
216       g_free (name);
217       break;
218     }
219     case GST_MESSAGE_EOS:
220       /* print final position at end */
221       play_timeout (play);
222       g_print ("\n");
223       /* and switch to next item in list */
224       if (!play_next (play)) {
225         g_print ("Reached end of play list.\n");
226         g_main_loop_quit (play->loop);
227       }
228       break;
229     case GST_MESSAGE_WARNING:{
230       GError *err;
231       gchar *dbg = NULL;
232
233       gst_message_parse_warning (msg, &err, &dbg);
234       g_printerr ("WARNING %s\n", err->message);
235       if (dbg != NULL)
236         g_printerr ("WARNING debug information: %s\n", dbg);
237       g_error_free (err);
238       g_free (dbg);
239       break;
240     }
241     case GST_MESSAGE_ERROR:{
242       GError *err;
243       gchar *dbg;
244
245       gst_message_parse_error (msg, &err, &dbg);
246       g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]);
247       if (dbg != NULL)
248         g_printerr ("ERROR debug information: %s\n", dbg);
249       g_error_free (err);
250       g_free (dbg);
251
252       /* flush any other error messages from the bus and clean up */
253       gst_element_set_state (play->playbin, GST_STATE_NULL);
254
255       if (play->missing != NULL && play_install_missing_plugins (play)) {
256         g_print ("New plugins installed, trying again...\n");
257         --play->cur_idx;
258         play_next (play);
259         break;
260       }
261       /* try next item in list then */
262       if (!play_next (play)) {
263         g_print ("Reached end of play list.\n");
264         g_main_loop_quit (play->loop);
265       }
266       break;
267     }
268     default:
269       if (gst_is_missing_plugin_message (msg)) {
270         gchar *desc;
271
272         desc = gst_missing_plugin_message_get_description (msg);
273         g_print ("Missing plugin: %s\n", desc);
274         g_free (desc);
275         play->missing = g_list_append (play->missing, gst_message_ref (msg));
276       }
277       break;
278   }
279
280   return TRUE;
281 }
282
283 static gboolean
284 play_timeout (gpointer user_data)
285 {
286   GstPlay *play = user_data;
287   gint64 pos = -1, dur = -1;
288   gchar status[64] = { 0, };
289
290   if (play->buffering)
291     return TRUE;
292
293   gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos);
294   gst_element_query_duration (play->playbin, GST_FORMAT_TIME, &dur);
295
296   if (play->desired_state == GST_STATE_PAUSED)
297     g_snprintf (status, sizeof (status), "Paused");
298   else
299     memset (status, ' ', sizeof (status) - 1);
300
301   if (pos >= 0 && dur > 0) {
302     gchar dstr[32], pstr[32];
303
304     /* FIXME: pretty print in nicer format */
305     g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos));
306     pstr[9] = '\0';
307     g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur));
308     dstr[9] = '\0';
309     g_print ("%s / %s %s\r", pstr, dstr, status);
310   }
311
312   return TRUE;
313 }
314
315 static gchar *
316 play_uri_get_display_name (GstPlay * play, const gchar * uri)
317 {
318   gchar *loc;
319
320   if (gst_uri_has_protocol (uri, "file")) {
321     loc = g_filename_from_uri (uri, NULL, NULL);
322   } else if (gst_uri_has_protocol (uri, "pushfile")) {
323     loc = g_filename_from_uri (uri + 4, NULL, NULL);
324   } else {
325     loc = g_strdup (uri);
326   }
327
328   /* Maybe additionally use glib's filename to display name function */
329   return loc;
330 }
331
332 static void
333 play_uri (GstPlay * play, const gchar * next_uri)
334 {
335   GstStateChangeReturn sret;
336   gchar *loc;
337
338   gst_element_set_state (play->playbin, GST_STATE_READY);
339   play_reset (play);
340
341   loc = play_uri_get_display_name (play, next_uri);
342   g_print ("Now playing %s\n", loc);
343   g_free (loc);
344
345   g_object_set (play->playbin, "uri", next_uri, NULL);
346
347   sret = gst_element_set_state (play->playbin, play->desired_state);
348   switch (sret) {
349     case GST_STATE_CHANGE_FAILURE:
350       /* ignore, we should get an error message posted on the bus */
351       break;
352     case GST_STATE_CHANGE_NO_PREROLL:
353       g_print ("Pipeline is live.\n");
354       play->is_live = TRUE;
355       break;
356     case GST_STATE_CHANGE_ASYNC:
357       g_print ("Prerolling...\r");
358       break;
359     default:
360       break;
361   }
362 }
363
364 /* returns FALSE if we have reached the end of the playlist */
365 static gboolean
366 play_next (GstPlay * play)
367 {
368   if ((play->cur_idx + 1) >= play->num_uris)
369     return FALSE;
370
371   play_uri (play, play->uris[++play->cur_idx]);
372   return TRUE;
373 }
374
375 /* returns FALSE if we have reached the beginning of the playlist */
376 static gboolean
377 play_prev (GstPlay * play)
378 {
379   if (play->cur_idx == 0 || play->num_uris <= 1)
380     return FALSE;
381
382   play_uri (play, play->uris[--play->cur_idx]);
383   return TRUE;
384 }
385
386 static void
387 play_about_to_finish (GstElement * playbin, gpointer user_data)
388 {
389   GstPlay *play = user_data;
390   const gchar *next_uri;
391   gchar *loc;
392   guint next_idx;
393
394   if (!play->gapless)
395     return;
396
397   next_idx = play->cur_idx + 1;
398   if (next_idx >= play->num_uris)
399     return;
400
401   next_uri = play->uris[next_idx];
402   loc = play_uri_get_display_name (play, next_uri);
403   g_print ("About to finish, preparing next title: %s\n", loc);
404   g_free (loc);
405
406   g_object_set (play->playbin, "uri", next_uri, NULL);
407   play->cur_idx = next_idx;
408 }
409
410 static void
411 do_play (GstPlay * play)
412 {
413   gint i;
414
415   /* dump playlist */
416   for (i = 0; i < play->num_uris; ++i)
417     GST_INFO ("%4u : %s", i, play->uris[i]);
418
419   if (!play_next (play))
420     return;
421
422   g_main_loop_run (play->loop);
423 }
424
425 static void
426 add_to_playlist (GPtrArray * playlist, const gchar * filename)
427 {
428   GDir *dir;
429   gchar *uri;
430
431   if (gst_uri_is_valid (filename)) {
432     g_ptr_array_add (playlist, g_strdup (filename));
433     return;
434   }
435
436   if ((dir = g_dir_open (filename, 0, NULL))) {
437     const gchar *entry;
438
439     /* FIXME: sort entries for each directory? */
440     while ((entry = g_dir_read_name (dir))) {
441       gchar *path;
442
443       path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL);
444       add_to_playlist (playlist, path);
445       g_free (path);
446     }
447
448     g_dir_close (dir);
449     return;
450   }
451
452   uri = gst_filename_to_uri (filename, NULL);
453   if (uri != NULL)
454     g_ptr_array_add (playlist, uri);
455   else
456     g_warning ("Could not make URI out of filename '%s'", filename);
457 }
458
459 static void
460 shuffle_uris (gchar ** uris, guint num)
461 {
462   gchar *tmp;
463   guint i, j;
464
465   if (num < 2)
466     return;
467
468   for (i = 0; i < num; i++) {
469     /* gets equally distributed random number in 0..num-1 [0;num[ */
470     j = g_random_int_range (0, num);
471     tmp = uris[j];
472     uris[j] = uris[i];
473     uris[i] = tmp;
474   }
475 }
476
477 static void
478 restore_terminal (void)
479 {
480   gst_play_kb_set_key_handler (NULL, NULL);
481 }
482
483 static void
484 toggle_paused (GstPlay * play)
485 {
486   if (play->desired_state == GST_STATE_PLAYING)
487     play->desired_state = GST_STATE_PAUSED;
488   else
489     play->desired_state = GST_STATE_PLAYING;
490
491   if (!play->buffering) {
492     gst_element_set_state (play->playbin, play->desired_state);
493   } else if (play->desired_state == GST_STATE_PLAYING) {
494     g_print ("\nWill play as soon as buffering finishes)\n");
495   }
496 }
497
498 static void
499 relative_seek (GstPlay * play, gdouble percent)
500 {
501   GstQuery *query;
502   gboolean seekable = FALSE;
503   gint64 dur = -1, pos = -1;
504
505   g_return_if_fail (percent >= -1.0 && percent <= 1.0);
506
507   if (!gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos))
508     goto seek_failed;
509
510   query = gst_query_new_seeking (GST_FORMAT_TIME);
511   if (!gst_element_query (play->playbin, query)) {
512     gst_query_unref (query);
513     goto seek_failed;
514   }
515
516   gst_query_parse_seeking (query, NULL, &seekable, NULL, &dur);
517   gst_query_unref (query);
518
519   if (!seekable || dur <= 0)
520     goto seek_failed;
521
522   pos = pos + dur * percent;
523   if (pos > dur) {
524     if (!play_next (play)) {
525       g_print ("\nReached end of play list.\n");
526       g_main_loop_quit (play->loop);
527     }
528   } else {
529     if (pos < 0)
530       pos = 0;
531     if (!gst_element_seek_simple (play->playbin, GST_FORMAT_TIME,
532             GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, pos))
533       goto seek_failed;
534   }
535
536   return;
537
538 seek_failed:
539   {
540     g_print ("\nCould not seek.\n");
541   }
542 }
543
544 static void
545 keyboard_cb (const gchar * key_input, gpointer user_data)
546 {
547   GstPlay *play = (GstPlay *) user_data;
548
549   switch (g_ascii_tolower (key_input[0])) {
550     case ' ':
551       toggle_paused (play);
552       break;
553     case '>':
554       if (!play_next (play)) {
555         g_print ("\nReached end of play list.\n");
556         g_main_loop_quit (play->loop);
557       }
558       break;
559     case '<':
560       play_prev (play);
561       break;
562     case 27:                   /* ESC */
563     default:
564       if (strcmp (key_input, GST_PLAY_KB_ARROW_RIGHT) == 0) {
565         relative_seek (play, +0.08);
566       } else if (strcmp (key_input, GST_PLAY_KB_ARROW_LEFT) == 0) {
567         relative_seek (play, -0.01);
568       } else {
569         GST_INFO ("keyboard input:");
570         for (; *key_input != '\0'; ++key_input)
571           GST_INFO ("  code %3d", *key_input);
572       }
573       break;
574   }
575 }
576
577 int
578 main (int argc, char **argv)
579 {
580   GstPlay *play;
581   GPtrArray *playlist;
582   gboolean print_version = FALSE;
583   gboolean interactive = FALSE; /* FIXME: maybe enable by default? */
584   gboolean gapless = FALSE;
585   gboolean shuffle = FALSE;
586   gchar **filenames = NULL;
587   gchar *audio_sink = NULL;
588   gchar *video_sink = NULL;
589   gchar **uris;
590   guint num, i;
591   GError *err = NULL;
592   GOptionContext *ctx;
593   GOptionEntry options[] = {
594     {"version", 0, 0, G_OPTION_ARG_NONE, &print_version,
595         N_("Print version information and exit"), NULL},
596     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
597         N_("Video sink to use (default is autovideosink)"), NULL},
598     {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink,
599         N_("Audio sink to use (default is autoaudiosink)"), NULL},
600     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
601         N_("Enable gapless playback"), NULL},
602     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
603         N_("Shuffle playlist"), NULL},
604     {"interactive", 0, 0, G_OPTION_ARG_NONE, &interactive,
605         N_("interactive"), NULL},
606     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
607     {NULL}
608   };
609
610 #ifdef ENABLE_NLS
611   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
612   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
613   textdomain (GETTEXT_PACKAGE);
614 #endif
615
616   g_set_prgname ("gst-play-" GST_API_VERSION);
617
618   ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...");
619   g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
620   g_option_context_add_group (ctx, gst_init_get_option_group ());
621   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
622     g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
623     return 1;
624   }
625   g_option_context_free (ctx);
626
627   GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play");
628
629   if (print_version) {
630     gchar *version_str;
631
632     version_str = gst_version_string ();
633     g_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION);
634     g_print ("%s\n", version_str);
635     g_print ("%s\n", GST_PACKAGE_ORIGIN);
636     g_free (version_str);
637     return 0;
638   }
639
640   if (filenames == NULL || *filenames == NULL) {
641     g_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."),
642         "gst-play-" GST_API_VERSION);
643     g_printerr ("\n\n"),
644         g_printerr ("%s\n\n",
645         _("You must provide at least one filename or URI to play."));
646     return 1;
647   }
648
649   playlist = g_ptr_array_new ();
650
651   /* fill playlist */
652   num = g_strv_length (filenames);
653   for (i = 0; i < num; ++i) {
654     GST_LOG ("command line argument: %s", filenames[i]);
655     add_to_playlist (playlist, filenames[i]);
656   }
657   g_strfreev (filenames);
658
659   num = playlist->len;
660   g_ptr_array_add (playlist, NULL);
661
662   uris = (gchar **) g_ptr_array_free (playlist, FALSE);
663
664   if (shuffle)
665     shuffle_uris (uris, num);
666
667   /* prepare */
668   play = play_new (uris, audio_sink, video_sink, gapless);
669
670   if (interactive) {
671     if (gst_play_kb_set_key_handler (keyboard_cb, play)) {
672       atexit (restore_terminal);
673     } else {
674       g_print ("Interactive keyboard handling in terminal not available.\n");
675     }
676   }
677
678   /* play */
679   do_play (play);
680
681   /* clean up */
682   play_free (play);
683
684   return 0;
685 }