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