|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 #include <stdlib.h> |
|
18 #include <glib.h> |
|
19 #include <gtk/gtk.h> |
|
20 #include <gst/gst.h> |
|
21 #include <string.h> |
|
22 |
|
23 GST_DEBUG_CATEGORY_STATIC (scrubby_debug); |
|
24 #define GST_CAT_DEFAULT (scrubby_debug) |
|
25 |
|
26 static GstElement *pipeline; |
|
27 static gint64 position; |
|
28 static gint64 duration; |
|
29 static GtkAdjustment *adjustment; |
|
30 static GtkWidget *hscale; |
|
31 static GtkAdjustment *sadjustment; |
|
32 static GtkWidget *shscale; |
|
33 static gboolean verbose = FALSE; |
|
34 |
|
35 static guint bus_watch = 0; |
|
36 static guint update_id = 0; |
|
37 static guint changed_id = 0; |
|
38 static guint schanged_id = 0; |
|
39 |
|
40 //#define SOURCE "filesrc" |
|
41 #define SOURCE "gnomevfssrc" |
|
42 #define ASINK "alsasink" |
|
43 //#define ASINK "osssink" |
|
44 #define VSINK "xvimagesink" |
|
45 //#define VSINK "ximagesink" |
|
46 //#define VSINK "aasink" |
|
47 //#define VSINK "cacasink" |
|
48 |
|
49 #define RANGE_PREC 10000 |
|
50 #define SEGMENT_LEN 100 |
|
51 #define UPDATE_INTERVAL 500 |
|
52 |
|
53 static gdouble prev_range = -1.0; |
|
54 static GstClockTime prev_time = GST_CLOCK_TIME_NONE; |
|
55 static gdouble cur_range; |
|
56 static GstClockTime cur_time; |
|
57 static GstClockTimeDiff diff; |
|
58 static gdouble cur_speed = 1.0; |
|
59 |
|
60 typedef struct |
|
61 { |
|
62 const gchar *padname; |
|
63 GstPad *target; |
|
64 GstElement *bin; |
|
65 } |
|
66 dyn_link; |
|
67 |
|
68 static GstElement * |
|
69 gst_element_factory_make_or_warn (gchar * type, gchar * name) |
|
70 { |
|
71 GstElement *element = gst_element_factory_make (type, name); |
|
72 |
|
73 if (!element) { |
|
74 g_warning ("Failed to create element %s of type %s", name, type); |
|
75 } |
|
76 |
|
77 return element; |
|
78 } |
|
79 |
|
80 static void |
|
81 dynamic_link (GstPadTemplate * templ, GstPad * newpad, gpointer data) |
|
82 { |
|
83 dyn_link *connect = (dyn_link *) data; |
|
84 |
|
85 if (connect->padname == NULL || |
|
86 !strcmp (gst_pad_get_name (newpad), connect->padname)) { |
|
87 if (connect->bin) |
|
88 gst_bin_add (GST_BIN (pipeline), connect->bin); |
|
89 gst_pad_link (newpad, connect->target); |
|
90 } |
|
91 } |
|
92 |
|
93 static void |
|
94 setup_dynamic_link (GstElement * element, const gchar * padname, |
|
95 GstPad * target, GstElement * bin) |
|
96 { |
|
97 dyn_link *connect; |
|
98 |
|
99 connect = g_new0 (dyn_link, 1); |
|
100 connect->padname = g_strdup (padname); |
|
101 connect->target = target; |
|
102 connect->bin = bin; |
|
103 |
|
104 g_signal_connect (G_OBJECT (element), "pad-added", G_CALLBACK (dynamic_link), |
|
105 connect); |
|
106 } |
|
107 |
|
108 static GstElement * |
|
109 make_wav_pipeline (const gchar * location) |
|
110 { |
|
111 GstElement *pipeline; |
|
112 GstElement *src, *decoder, *audiosink; |
|
113 |
|
114 pipeline = gst_pipeline_new ("app"); |
|
115 |
|
116 src = gst_element_factory_make_or_warn (SOURCE, "src"); |
|
117 decoder = gst_element_factory_make_or_warn ("wavparse", "decoder"); |
|
118 audiosink = gst_element_factory_make_or_warn (ASINK, "sink"); |
|
119 |
|
120 g_object_set (G_OBJECT (src), "location", location, NULL); |
|
121 |
|
122 gst_bin_add (GST_BIN (pipeline), src); |
|
123 gst_bin_add (GST_BIN (pipeline), decoder); |
|
124 gst_bin_add (GST_BIN (pipeline), audiosink); |
|
125 |
|
126 gst_element_link (src, decoder); |
|
127 |
|
128 setup_dynamic_link (decoder, "src", gst_element_get_pad (audiosink, "sink"), |
|
129 NULL); |
|
130 |
|
131 return pipeline; |
|
132 } |
|
133 |
|
134 static GstElement * |
|
135 make_playerbin_pipeline (const gchar * location) |
|
136 { |
|
137 GstElement *player; |
|
138 |
|
139 player = gst_element_factory_make ("playbin", "player"); |
|
140 g_assert (player); |
|
141 |
|
142 g_object_set (G_OBJECT (player), "uri", location, NULL); |
|
143 |
|
144 return player; |
|
145 } |
|
146 |
|
147 static gchar * |
|
148 format_value (GtkScale * scale, gdouble value) |
|
149 { |
|
150 gint64 real; |
|
151 gint64 seconds; |
|
152 gint64 subseconds; |
|
153 |
|
154 real = value * duration / RANGE_PREC; |
|
155 seconds = (gint64) real / GST_SECOND; |
|
156 subseconds = (gint64) real / (GST_SECOND / RANGE_PREC); |
|
157 |
|
158 return g_strdup_printf ("%02" G_GINT64_FORMAT ":%02" G_GINT64_FORMAT ":%02" |
|
159 G_GINT64_FORMAT, seconds / 60, seconds % 60, subseconds % 100); |
|
160 } |
|
161 |
|
162 static gboolean |
|
163 update_scale (gpointer data) |
|
164 { |
|
165 GstFormat format; |
|
166 |
|
167 position = 0; |
|
168 duration = 0; |
|
169 |
|
170 format = GST_FORMAT_TIME; |
|
171 |
|
172 gst_element_query_position (pipeline, &format, &position); |
|
173 gst_element_query_duration (pipeline, &format, &duration); |
|
174 |
|
175 if (position >= duration) |
|
176 duration = position; |
|
177 |
|
178 if (duration > 0) { |
|
179 gtk_adjustment_set_value (adjustment, |
|
180 position * (gdouble) RANGE_PREC / duration); |
|
181 gtk_widget_queue_draw (hscale); |
|
182 } |
|
183 |
|
184 return TRUE; |
|
185 } |
|
186 |
|
187 static void |
|
188 speed_cb (GtkWidget * widget) |
|
189 { |
|
190 GstEvent *s_event; |
|
191 gboolean res; |
|
192 |
|
193 GST_DEBUG ("speed change"); |
|
194 cur_speed = gtk_range_get_value (GTK_RANGE (widget)); |
|
195 |
|
196 if (cur_speed == 0.0) |
|
197 return; |
|
198 |
|
199 s_event = gst_event_new_seek (cur_speed, |
|
200 GST_FORMAT_TIME, 0, GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); |
|
201 |
|
202 res = gst_element_send_event (pipeline, s_event); |
|
203 if (!res) |
|
204 g_print ("speed change failed\n"); |
|
205 } |
|
206 |
|
207 static gboolean do_seek (GtkWidget * widget, gboolean flush, gboolean segment); |
|
208 |
|
209 static void |
|
210 seek_cb (GtkWidget * widget) |
|
211 { |
|
212 if (changed_id) { |
|
213 GST_DEBUG ("seek because of slider move"); |
|
214 |
|
215 if (do_seek (widget, TRUE, TRUE)) { |
|
216 g_source_remove (changed_id); |
|
217 changed_id = 0; |
|
218 } |
|
219 } |
|
220 } |
|
221 |
|
222 static gboolean |
|
223 do_seek (GtkWidget * widget, gboolean flush, gboolean segment) |
|
224 { |
|
225 gint64 start, stop; |
|
226 gboolean res = FALSE; |
|
227 GstEvent *s_event; |
|
228 gdouble rate; |
|
229 GTimeVal tv; |
|
230 gboolean valid; |
|
231 gdouble new_range; |
|
232 |
|
233 if (segment) |
|
234 new_range = gtk_range_get_value (GTK_RANGE (widget)); |
|
235 else { |
|
236 new_range = (gdouble) RANGE_PREC; |
|
237 cur_time = -1; |
|
238 } |
|
239 |
|
240 valid = prev_time != -1; |
|
241 |
|
242 GST_DEBUG ("flush %d, segment %d, valid %d", flush, segment, valid); |
|
243 |
|
244 if (new_range == cur_range) |
|
245 return FALSE; |
|
246 |
|
247 prev_time = cur_time; |
|
248 prev_range = cur_range; |
|
249 |
|
250 cur_range = new_range; |
|
251 |
|
252 g_get_current_time (&tv); |
|
253 cur_time = GST_TIMEVAL_TO_TIME (tv); |
|
254 |
|
255 if (!valid) |
|
256 return FALSE; |
|
257 |
|
258 GST_DEBUG ("cur: %lf, %" GST_TIME_FORMAT, cur_range, |
|
259 GST_TIME_ARGS (cur_time)); |
|
260 GST_DEBUG ("prev: %lf, %" GST_TIME_FORMAT, prev_range, |
|
261 GST_TIME_ARGS (prev_time)); |
|
262 |
|
263 diff = cur_time - prev_time; |
|
264 |
|
265 GST_DEBUG ("diff: %" GST_TIME_FORMAT, GST_TIME_ARGS (diff)); |
|
266 |
|
267 start = prev_range * duration / RANGE_PREC; |
|
268 /* play 50 milliseconds */ |
|
269 stop = segment ? cur_range * duration / RANGE_PREC : duration; |
|
270 |
|
271 if (start == stop) |
|
272 return FALSE; |
|
273 |
|
274 if (segment) |
|
275 rate = (stop - start) / (gdouble) diff; |
|
276 else |
|
277 rate = cur_speed; |
|
278 |
|
279 if (start > stop) { |
|
280 gint64 tmp; |
|
281 |
|
282 tmp = start; |
|
283 start = stop; |
|
284 stop = tmp; |
|
285 } |
|
286 |
|
287 if (rate == 0.0) |
|
288 return TRUE; |
|
289 |
|
290 GST_DEBUG ("seek to %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT ", rate %lf" |
|
291 " on element %s", |
|
292 GST_TIME_ARGS (start), GST_TIME_ARGS (stop), rate, |
|
293 GST_ELEMENT_NAME (pipeline)); |
|
294 |
|
295 s_event = gst_event_new_seek (rate, |
|
296 GST_FORMAT_TIME, |
|
297 (flush ? GST_SEEK_FLAG_FLUSH : 0) | |
|
298 (segment ? GST_SEEK_FLAG_SEGMENT : 0), |
|
299 GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, stop); |
|
300 |
|
301 res = gst_element_send_event (pipeline, s_event); |
|
302 if (!res) |
|
303 g_print ("seek failed\n"); |
|
304 |
|
305 gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); |
|
306 |
|
307 return TRUE; |
|
308 } |
|
309 |
|
310 static gboolean |
|
311 start_seek (GtkWidget * widget, GdkEventButton * event, gpointer user_data) |
|
312 { |
|
313 if (update_id) { |
|
314 g_source_remove (update_id); |
|
315 update_id = 0; |
|
316 } |
|
317 |
|
318 if (changed_id == 0) { |
|
319 changed_id = gtk_signal_connect (GTK_OBJECT (hscale), |
|
320 "value_changed", G_CALLBACK (seek_cb), pipeline); |
|
321 } |
|
322 |
|
323 GST_DEBUG ("start seek"); |
|
324 |
|
325 return FALSE; |
|
326 } |
|
327 |
|
328 static gboolean |
|
329 stop_seek (GtkWidget * widget, gpointer user_data) |
|
330 { |
|
331 update_id = |
|
332 g_timeout_add (UPDATE_INTERVAL, (GtkFunction) update_scale, pipeline); |
|
333 |
|
334 GST_DEBUG ("stop seek"); |
|
335 |
|
336 if (changed_id) { |
|
337 g_source_remove (changed_id); |
|
338 changed_id = 0; |
|
339 } |
|
340 |
|
341 do_seek (hscale, FALSE, FALSE); |
|
342 |
|
343 return FALSE; |
|
344 } |
|
345 |
|
346 static void |
|
347 play_cb (GtkButton * button, gpointer data) |
|
348 { |
|
349 GstState state; |
|
350 |
|
351 gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE); |
|
352 if (state != GST_STATE_PLAYING) { |
|
353 g_print ("PLAY pipeline\n"); |
|
354 gst_element_set_state (pipeline, GST_STATE_PAUSED); |
|
355 gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); |
|
356 gst_element_set_state (pipeline, GST_STATE_PLAYING); |
|
357 update_id = |
|
358 g_timeout_add (UPDATE_INTERVAL, (GtkFunction) update_scale, pipeline); |
|
359 } |
|
360 } |
|
361 |
|
362 static void |
|
363 pause_cb (GtkButton * button, gpointer data) |
|
364 { |
|
365 GstState state; |
|
366 |
|
367 gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE); |
|
368 if (state != GST_STATE_PAUSED) { |
|
369 g_print ("PAUSE pipeline\n"); |
|
370 gst_element_set_state (pipeline, GST_STATE_PAUSED); |
|
371 g_source_remove (update_id); |
|
372 } |
|
373 } |
|
374 |
|
375 static void |
|
376 stop_cb (GtkButton * button, gpointer data) |
|
377 { |
|
378 GstState state; |
|
379 |
|
380 gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE); |
|
381 if (state != GST_STATE_READY) { |
|
382 g_print ("READY pipeline\n"); |
|
383 gst_element_set_state (pipeline, GST_STATE_READY); |
|
384 /* position and speed return to their default values */ |
|
385 gtk_adjustment_set_value (adjustment, 0.0); |
|
386 gtk_adjustment_set_value (sadjustment, 1.0); |
|
387 g_source_remove (update_id); |
|
388 } |
|
389 } |
|
390 |
|
391 static void |
|
392 print_message (GstMessage * message) |
|
393 { |
|
394 const GstStructure *s; |
|
395 |
|
396 s = gst_message_get_structure (message); |
|
397 g_print ("Got Message from element \"%s\"\n", |
|
398 GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)))); |
|
399 |
|
400 if (s) { |
|
401 gchar *sstr; |
|
402 |
|
403 sstr = gst_structure_to_string (s); |
|
404 g_print ("%s\n", sstr); |
|
405 g_free (sstr); |
|
406 } |
|
407 } |
|
408 |
|
409 static gboolean |
|
410 bus_message (GstBus * bus, GstMessage * message, gpointer data) |
|
411 { |
|
412 switch (GST_MESSAGE_TYPE (message)) { |
|
413 case GST_MESSAGE_EOS: |
|
414 g_print ("EOS\n"); |
|
415 break; |
|
416 case GST_MESSAGE_ERROR: |
|
417 case GST_MESSAGE_WARNING: |
|
418 print_message (message); |
|
419 break; |
|
420 case GST_MESSAGE_SEGMENT_START: |
|
421 break; |
|
422 case GST_MESSAGE_SEGMENT_DONE: |
|
423 GST_DEBUG ("segment_done, doing next seek"); |
|
424 if (!do_seek (hscale, FALSE, update_id == 0)) { |
|
425 if (changed_id == 0) { |
|
426 changed_id = gtk_signal_connect (GTK_OBJECT (hscale), |
|
427 "value_changed", G_CALLBACK (seek_cb), pipeline); |
|
428 } |
|
429 } |
|
430 break; |
|
431 default: |
|
432 break; |
|
433 } |
|
434 |
|
435 return TRUE; |
|
436 } |
|
437 |
|
438 typedef struct |
|
439 { |
|
440 gchar *name; |
|
441 GstElement *(*func) (const gchar * location); |
|
442 } |
|
443 Pipeline; |
|
444 |
|
445 static Pipeline pipelines[] = { |
|
446 {"wav", make_wav_pipeline}, |
|
447 {"playerbin", make_playerbin_pipeline}, |
|
448 {NULL, NULL}, |
|
449 }; |
|
450 |
|
451 #define NUM_TYPES ((sizeof (pipelines) / sizeof (Pipeline)) - 1) |
|
452 |
|
453 static void |
|
454 print_usage (int argc, char **argv) |
|
455 { |
|
456 gint i; |
|
457 |
|
458 g_print ("usage: %s <type> <filename>\n", argv[0]); |
|
459 g_print (" possible types:\n"); |
|
460 |
|
461 for (i = 0; i < NUM_TYPES; i++) { |
|
462 g_print (" %d = %s\n", i, pipelines[i].name); |
|
463 } |
|
464 } |
|
465 |
|
466 int |
|
467 main (int argc, char **argv) |
|
468 { |
|
469 GtkWidget *window, *hbox, *vbox, *play_button, *pause_button, *stop_button; |
|
470 GstBus *bus; |
|
471 GOptionEntry options[] = { |
|
472 {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, |
|
473 "Verbose properties", NULL}, |
|
474 {NULL} |
|
475 }; |
|
476 gint type; |
|
477 GOptionContext *ctx; |
|
478 GError *err = NULL; |
|
479 |
|
480 if (!g_thread_supported ()) |
|
481 g_thread_init (NULL); |
|
482 |
|
483 ctx = g_option_context_new ("seek"); |
|
484 g_option_context_add_main_entries (ctx, options, NULL); |
|
485 g_option_context_add_group (ctx, gst_init_get_option_group ()); |
|
486 |
|
487 if (!g_option_context_parse (ctx, &argc, &argv, &err)) { |
|
488 g_print ("Error initializing: %s\n", err->message); |
|
489 exit (1); |
|
490 } |
|
491 |
|
492 GST_DEBUG_CATEGORY_INIT (scrubby_debug, "scrubby", 0, "scrubby example"); |
|
493 |
|
494 gtk_init (&argc, &argv); |
|
495 |
|
496 if (argc != 3) { |
|
497 print_usage (argc, argv); |
|
498 exit (-1); |
|
499 } |
|
500 |
|
501 type = atoi (argv[1]); |
|
502 |
|
503 if (type < 0 || type >= NUM_TYPES) { |
|
504 print_usage (argc, argv); |
|
505 exit (-1); |
|
506 } |
|
507 |
|
508 pipeline = pipelines[type].func (argv[2]); |
|
509 g_assert (pipeline); |
|
510 |
|
511 /* initialize gui elements ... */ |
|
512 window = gtk_window_new (GTK_WINDOW_TOPLEVEL); |
|
513 hbox = gtk_hbox_new (FALSE, 0); |
|
514 vbox = gtk_vbox_new (FALSE, 0); |
|
515 play_button = gtk_button_new_with_label ("play"); |
|
516 pause_button = gtk_button_new_with_label ("pause"); |
|
517 stop_button = gtk_button_new_with_label ("stop"); |
|
518 |
|
519 adjustment = |
|
520 GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, (gdouble) RANGE_PREC, 0.1, |
|
521 1.0, 1.0)); |
|
522 hscale = gtk_hscale_new (adjustment); |
|
523 gtk_scale_set_digits (GTK_SCALE (hscale), 2); |
|
524 gtk_range_set_update_policy (GTK_RANGE (hscale), GTK_UPDATE_CONTINUOUS); |
|
525 |
|
526 sadjustment = |
|
527 GTK_ADJUSTMENT (gtk_adjustment_new (1.0, 0.0, 5.0, 0.1, 1.0, 0.0)); |
|
528 shscale = gtk_hscale_new (sadjustment); |
|
529 gtk_scale_set_digits (GTK_SCALE (shscale), 2); |
|
530 gtk_range_set_update_policy (GTK_RANGE (shscale), GTK_UPDATE_CONTINUOUS); |
|
531 |
|
532 schanged_id = gtk_signal_connect (GTK_OBJECT (shscale), |
|
533 "value_changed", G_CALLBACK (speed_cb), pipeline); |
|
534 |
|
535 gtk_signal_connect (GTK_OBJECT (hscale), |
|
536 "button_press_event", G_CALLBACK (start_seek), pipeline); |
|
537 gtk_signal_connect (GTK_OBJECT (hscale), |
|
538 "button_release_event", G_CALLBACK (stop_seek), pipeline); |
|
539 gtk_signal_connect (GTK_OBJECT (hscale), |
|
540 "format_value", G_CALLBACK (format_value), pipeline); |
|
541 |
|
542 /* do the packing stuff ... */ |
|
543 gtk_window_set_default_size (GTK_WINDOW (window), 96, 96); |
|
544 gtk_container_add (GTK_CONTAINER (window), vbox); |
|
545 gtk_container_add (GTK_CONTAINER (vbox), hbox); |
|
546 gtk_box_pack_start (GTK_BOX (hbox), play_button, FALSE, FALSE, 2); |
|
547 gtk_box_pack_start (GTK_BOX (hbox), pause_button, FALSE, FALSE, 2); |
|
548 gtk_box_pack_start (GTK_BOX (hbox), stop_button, FALSE, FALSE, 2); |
|
549 gtk_box_pack_start (GTK_BOX (vbox), hscale, TRUE, TRUE, 2); |
|
550 gtk_box_pack_start (GTK_BOX (vbox), shscale, TRUE, TRUE, 2); |
|
551 |
|
552 /* connect things ... */ |
|
553 g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), |
|
554 pipeline); |
|
555 g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), |
|
556 pipeline); |
|
557 g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), |
|
558 pipeline); |
|
559 g_signal_connect (G_OBJECT (window), "delete_event", gtk_main_quit, NULL); |
|
560 |
|
561 /* show the gui. */ |
|
562 gtk_widget_show_all (window); |
|
563 |
|
564 if (verbose) { |
|
565 g_signal_connect (pipeline, "deep_notify", |
|
566 G_CALLBACK (gst_object_default_deep_notify), NULL); |
|
567 } |
|
568 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); |
|
569 g_assert (bus); |
|
570 |
|
571 bus_watch = gst_bus_add_watch_full (bus, |
|
572 G_PRIORITY_HIGH, bus_message, pipeline, NULL); |
|
573 |
|
574 gtk_main (); |
|
575 |
|
576 g_print ("NULL pipeline\n"); |
|
577 gst_element_set_state (pipeline, GST_STATE_NULL); |
|
578 |
|
579 g_print ("free pipeline\n"); |
|
580 gst_object_unref (pipeline); |
|
581 |
|
582 return 0; |
|
583 } |