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