f42ba39a4377d7b35bfa1dc6342948f86fc27d44
[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 keyboard_cb (const gchar * key_input, gpointer user_data)
500 {
501   GstPlay *play = (GstPlay *) user_data;
502
503   switch (g_ascii_tolower (key_input[0])) {
504     case ' ':
505       toggle_paused (play);
506       break;
507     case '>':
508       if (!play_next (play)) {
509         g_print ("\nReached end of play list.\n");
510         g_main_loop_quit (play->loop);
511       }
512       break;
513     case '<':
514       play_prev (play);
515       break;
516     case 27:                   /* ESC */
517     default:
518       GST_INFO ("keyboard input:");
519       for (; *key_input != '\0'; ++key_input)
520         GST_INFO ("  code %3d", *key_input);
521       break;
522   }
523 }
524
525 int
526 main (int argc, char **argv)
527 {
528   GstPlay *play;
529   GPtrArray *playlist;
530   gboolean print_version = FALSE;
531   gboolean interactive = FALSE; /* FIXME: maybe enable by default? */
532   gboolean gapless = FALSE;
533   gboolean shuffle = FALSE;
534   gchar **filenames = NULL;
535   gchar *audio_sink = NULL;
536   gchar *video_sink = NULL;
537   gchar **uris;
538   guint num, i;
539   GError *err = NULL;
540   GOptionContext *ctx;
541   GOptionEntry options[] = {
542     {"version", 0, 0, G_OPTION_ARG_NONE, &print_version,
543         N_("Print version information and exit"), NULL},
544     {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink,
545         N_("Video sink to use (default is autovideosink)"), NULL},
546     {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink,
547         N_("Audio sink to use (default is autoaudiosink)"), NULL},
548     {"gapless", 0, 0, G_OPTION_ARG_NONE, &gapless,
549         N_("Enable gapless playback"), NULL},
550     {"shuffle", 0, 0, G_OPTION_ARG_NONE, &shuffle,
551         N_("Shuffle playlist"), NULL},
552     {"interactive", 0, 0, G_OPTION_ARG_NONE, &interactive,
553         N_("interactive"), NULL},
554     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
555     {NULL}
556   };
557
558 #ifdef ENABLE_NLS
559   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
560   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
561   textdomain (GETTEXT_PACKAGE);
562 #endif
563
564   g_set_prgname ("gst-play-" GST_API_VERSION);
565
566   ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ...");
567   g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE);
568   g_option_context_add_group (ctx, gst_init_get_option_group ());
569   if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
570     g_print ("Error initializing: %s\n", GST_STR_NULL (err->message));
571     return 1;
572   }
573   g_option_context_free (ctx);
574
575   GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play");
576
577   if (print_version) {
578     gchar *version_str;
579
580     version_str = gst_version_string ();
581     g_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION);
582     g_print ("%s\n", version_str);
583     g_print ("%s\n", GST_PACKAGE_ORIGIN);
584     g_free (version_str);
585     return 0;
586   }
587
588   if (filenames == NULL || *filenames == NULL) {
589     g_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."),
590         "gst-play-" GST_API_VERSION);
591     g_printerr ("\n\n"),
592         g_printerr ("%s\n\n",
593         _("You must provide at least one filename or URI to play."));
594     return 1;
595   }
596
597   playlist = g_ptr_array_new ();
598
599   /* fill playlist */
600   num = g_strv_length (filenames);
601   for (i = 0; i < num; ++i) {
602     GST_LOG ("command line argument: %s", filenames[i]);
603     add_to_playlist (playlist, filenames[i]);
604   }
605   g_strfreev (filenames);
606
607   num = playlist->len;
608   g_ptr_array_add (playlist, NULL);
609
610   uris = (gchar **) g_ptr_array_free (playlist, FALSE);
611
612   if (shuffle)
613     shuffle_uris (uris, num);
614
615   /* prepare */
616   play = play_new (uris, audio_sink, video_sink, gapless);
617
618   if (interactive) {
619     if (gst_play_kb_set_key_handler (keyboard_cb, play)) {
620       atexit (restore_terminal);
621     } else {
622       g_print ("Interactive keyboard handling in terminal not available.\n");
623     }
624   }
625
626   /* play */
627   do_play (play);
628
629   /* clean up */
630   play_free (play);
631
632   return 0;
633 }