#ifdef HAVE_CONFIG_H #include "config.h" #endif /* FIXME 0.11: suppress warnings for deprecated API such as GStaticRecMutex * with newer GTK versions (>= 3.3.0) */ #define GDK_DISABLE_DEPRECATION_WARNINGS #include #include #include #include #include GST_DEBUG_CATEGORY_STATIC (scrubby_debug); #define GST_CAT_DEFAULT (scrubby_debug) static GstElement *pipeline; static gint64 position; static gint64 duration; static GtkAdjustment *adjustment; static GtkWidget *hscale; static GtkAdjustment *sadjustment; static GtkWidget *shscale; static gboolean verbose = FALSE; static guint bus_watch = 0; static guint update_id = 0; static guint changed_id = 0; static guint schanged_id = 0; #define SOURCE "filesrc" #define ASINK "alsasink" //#define ASINK "osssink" #define VSINK "xvimagesink" //#define VSINK "ximagesink" //#define VSINK "aasink" //#define VSINK "cacasink" #define RANGE_PREC 10000 #define SEGMENT_LEN 100 #define UPDATE_INTERVAL 500 static gdouble prev_range = -1.0; static GstClockTime prev_time = GST_CLOCK_TIME_NONE; static gdouble cur_range; static GstClockTime cur_time; static GstClockTimeDiff diff; static gdouble cur_speed = 1.0; typedef struct { const gchar *padname; GstPad *target; GstElement *bin; } dyn_link; static GstElement * gst_element_factory_make_or_warn (const gchar * type, const gchar * name) { GstElement *element = gst_element_factory_make (type, name); if (!element) { g_warning ("Failed to create element %s of type %s", name, type); } return element; } static void dynamic_link (GstPadTemplate * templ, GstPad * newpad, gpointer data) { dyn_link *connect = (dyn_link *) data; if (connect->padname == NULL || !strcmp (gst_pad_get_name (newpad), connect->padname)) { if (connect->bin) gst_bin_add (GST_BIN (pipeline), connect->bin); gst_pad_link (newpad, connect->target); } } static void setup_dynamic_link (GstElement * element, const gchar * padname, GstPad * target, GstElement * bin) { dyn_link *connect; connect = g_new0 (dyn_link, 1); connect->padname = g_strdup (padname); connect->target = target; connect->bin = bin; g_signal_connect (G_OBJECT (element), "pad-added", G_CALLBACK (dynamic_link), connect); } static GstElement * make_wav_pipeline (const gchar * location) { GstElement *pipeline; GstElement *src, *decoder, *audiosink; pipeline = gst_pipeline_new ("app"); src = gst_element_factory_make_or_warn (SOURCE, "src"); decoder = gst_element_factory_make_or_warn ("wavparse", "decoder"); audiosink = gst_element_factory_make_or_warn (ASINK, "sink"); g_object_set (G_OBJECT (src), "location", location, NULL); gst_bin_add (GST_BIN (pipeline), src); gst_bin_add (GST_BIN (pipeline), decoder); gst_bin_add (GST_BIN (pipeline), audiosink); gst_element_link (src, decoder); setup_dynamic_link (decoder, "src", gst_element_get_static_pad (audiosink, "sink"), NULL); return pipeline; } static GstElement * make_playerbin_pipeline (const gchar * location) { GstElement *player; player = gst_element_factory_make ("playbin", "player"); g_assert (player); g_object_set (G_OBJECT (player), "uri", location, NULL); return player; } static gchar * format_value (GtkScale * scale, gdouble value) { gint64 real; gint64 seconds; gint64 subseconds; real = value * duration / RANGE_PREC; seconds = (gint64) real / GST_SECOND; subseconds = (gint64) real / (GST_SECOND / RANGE_PREC); return g_strdup_printf ("%02" G_GINT64_FORMAT ":%02" G_GINT64_FORMAT ":%02" G_GINT64_FORMAT, seconds / 60, seconds % 60, subseconds % 100); } static gboolean update_scale (gpointer data) { position = 0; duration = 0; gst_element_query_position (pipeline, GST_FORMAT_TIME, &position); gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration); if (position >= duration) duration = position; if (duration > 0) { gtk_adjustment_set_value (adjustment, position * (gdouble) RANGE_PREC / duration); gtk_widget_queue_draw (hscale); } return TRUE; } static void speed_cb (GtkWidget * widget) { GstEvent *s_event; gboolean res; GST_DEBUG ("speed change"); cur_speed = gtk_range_get_value (GTK_RANGE (widget)); if (cur_speed == 0.0) return; s_event = gst_event_new_seek (cur_speed, GST_FORMAT_TIME, 0, GST_SEEK_TYPE_NONE, -1, GST_SEEK_TYPE_NONE, -1); res = gst_element_send_event (pipeline, s_event); if (!res) g_print ("speed change failed\n"); } static gboolean do_seek (GtkWidget * widget, gboolean flush, gboolean segment); static void seek_cb (GtkWidget * widget) { if (changed_id) { GST_DEBUG ("seek because of slider move"); if (do_seek (widget, TRUE, TRUE)) { g_source_remove (changed_id); changed_id = 0; } } } static gboolean do_seek (GtkWidget * widget, gboolean flush, gboolean segment) { gint64 start, stop; gboolean res = FALSE; GstEvent *s_event; gdouble rate; GTimeVal tv; gboolean valid; gdouble new_range; if (segment) new_range = gtk_range_get_value (GTK_RANGE (widget)); else { new_range = (gdouble) RANGE_PREC; cur_time = -1; } valid = prev_time != -1; GST_DEBUG ("flush %d, segment %d, valid %d", flush, segment, valid); if (new_range == cur_range) return FALSE; prev_time = cur_time; prev_range = cur_range; cur_range = new_range; g_get_current_time (&tv); cur_time = GST_TIMEVAL_TO_TIME (tv); if (!valid) return FALSE; GST_DEBUG ("cur: %lf, %" GST_TIME_FORMAT, cur_range, GST_TIME_ARGS (cur_time)); GST_DEBUG ("prev: %lf, %" GST_TIME_FORMAT, prev_range, GST_TIME_ARGS (prev_time)); diff = cur_time - prev_time; GST_DEBUG ("diff: %" GST_TIME_FORMAT, GST_TIME_ARGS (diff)); start = prev_range * duration / RANGE_PREC; /* play 50 milliseconds */ stop = segment ? cur_range * duration / RANGE_PREC : duration; if (start == stop) return FALSE; if (segment) rate = (stop - start) / (gdouble) diff; else rate = cur_speed; if (start > stop) { gint64 tmp; tmp = start; start = stop; stop = tmp; } if (rate == 0.0) return TRUE; GST_DEBUG ("seek to %" GST_TIME_FORMAT " -- %" GST_TIME_FORMAT ", rate %lf" " on element %s", GST_TIME_ARGS (start), GST_TIME_ARGS (stop), rate, GST_ELEMENT_NAME (pipeline)); s_event = gst_event_new_seek (rate, GST_FORMAT_TIME, (flush ? GST_SEEK_FLAG_FLUSH : 0) | (segment ? GST_SEEK_FLAG_SEGMENT : 0), GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_SET, stop); res = gst_element_send_event (pipeline, s_event); if (!res) g_print ("seek failed\n"); gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); return TRUE; } static gboolean start_seek (GtkWidget * widget, GdkEventButton * event, gpointer user_data) { if (update_id) { g_source_remove (update_id); update_id = 0; } if (changed_id == 0) { changed_id = g_signal_connect (hscale, "value_changed", G_CALLBACK (seek_cb), pipeline); } GST_DEBUG ("start seek"); return FALSE; } static gboolean stop_seek (GtkWidget * widget, gpointer user_data) { update_id = g_timeout_add (UPDATE_INTERVAL, (GSourceFunc) update_scale, pipeline); GST_DEBUG ("stop seek"); if (changed_id) { g_source_remove (changed_id); changed_id = 0; } do_seek (hscale, FALSE, FALSE); return FALSE; } static void play_cb (GtkButton * button, gpointer data) { GstState state; gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE); if (state != GST_STATE_PLAYING) { g_print ("PLAY pipeline\n"); gst_element_set_state (pipeline, GST_STATE_PAUSED); gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); gst_element_set_state (pipeline, GST_STATE_PLAYING); update_id = g_timeout_add (UPDATE_INTERVAL, (GSourceFunc) update_scale, pipeline); } } static void pause_cb (GtkButton * button, gpointer data) { GstState state; gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE); if (state != GST_STATE_PAUSED) { g_print ("PAUSE pipeline\n"); gst_element_set_state (pipeline, GST_STATE_PAUSED); g_source_remove (update_id); } } static void stop_cb (GtkButton * button, gpointer data) { GstState state; gst_element_get_state (pipeline, &state, NULL, GST_CLOCK_TIME_NONE); if (state != GST_STATE_READY) { g_print ("READY pipeline\n"); gst_element_set_state (pipeline, GST_STATE_READY); /* position and speed return to their default values */ gtk_adjustment_set_value (adjustment, 0.0); gtk_adjustment_set_value (sadjustment, 1.0); g_source_remove (update_id); } } static void print_message (GstMessage * message) { const GstStructure *s; s = gst_message_get_structure (message); g_print ("Got Message from element \"%s\"\n", GST_STR_NULL (GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)))); if (s) { gchar *sstr; sstr = gst_structure_to_string (s); g_print ("%s\n", sstr); g_free (sstr); } } static gboolean bus_message (GstBus * bus, GstMessage * message, gpointer data) { switch (GST_MESSAGE_TYPE (message)) { case GST_MESSAGE_EOS: g_print ("EOS\n"); break; case GST_MESSAGE_ERROR: case GST_MESSAGE_WARNING: print_message (message); break; case GST_MESSAGE_SEGMENT_START: break; case GST_MESSAGE_SEGMENT_DONE: GST_DEBUG ("segment_done, doing next seek"); if (!do_seek (hscale, FALSE, update_id == 0)) { if (changed_id == 0) { changed_id = g_signal_connect (hscale, "value_changed", G_CALLBACK (seek_cb), pipeline); } } break; default: break; } return TRUE; } typedef struct { const gchar *name; GstElement *(*func) (const gchar * location); } Pipeline; static Pipeline pipelines[] = { {"wav", make_wav_pipeline}, {"playerbin", make_playerbin_pipeline}, {NULL, NULL}, }; #define NUM_TYPES ((sizeof (pipelines) / sizeof (Pipeline)) - 1) static void print_usage (int argc, char **argv) { gint i; g_print ("usage: %s \n", argv[0]); g_print (" possible types:\n"); for (i = 0; i < NUM_TYPES; i++) { g_print (" %d = %s\n", i, pipelines[i].name); } } int main (int argc, char **argv) { GtkWidget *window, *hbox, *vbox, *play_button, *pause_button, *stop_button; GstBus *bus; GOptionEntry options[] = { {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose properties", NULL}, {NULL} }; gint type; GOptionContext *ctx; GError *err = NULL; ctx = g_option_context_new ("seek"); g_option_context_add_main_entries (ctx, options, NULL); g_option_context_add_group (ctx, gst_init_get_option_group ()); if (!g_option_context_parse (ctx, &argc, &argv, &err)) { g_print ("Error initializing: %s\n", err->message); exit (1); } GST_DEBUG_CATEGORY_INIT (scrubby_debug, "scrubby", 0, "scrubby example"); gtk_init (&argc, &argv); if (argc != 3) { print_usage (argc, argv); exit (-1); } type = atoi (argv[1]); if (type < 0 || type >= NUM_TYPES) { print_usage (argc, argv); exit (-1); } pipeline = pipelines[type].func (argv[2]); g_assert (pipeline); /* initialize gui elements ... */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); hbox = gtk_hbox_new (FALSE, 0); vbox = gtk_vbox_new (FALSE, 0); play_button = gtk_button_new_with_label ("play"); pause_button = gtk_button_new_with_label ("pause"); stop_button = gtk_button_new_with_label ("stop"); adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, (gdouble) RANGE_PREC, 0.1, 1.0, 1.0)); hscale = gtk_hscale_new (adjustment); gtk_scale_set_digits (GTK_SCALE (hscale), 2); sadjustment = GTK_ADJUSTMENT (gtk_adjustment_new (1.0, 0.0, 5.0, 0.1, 1.0, 0.0)); shscale = gtk_hscale_new (sadjustment); gtk_scale_set_digits (GTK_SCALE (shscale), 2); schanged_id = g_signal_connect (shscale, "value_changed", G_CALLBACK (speed_cb), pipeline); g_signal_connect (hscale, "button_press_event", G_CALLBACK (start_seek), pipeline); g_signal_connect (hscale, "button_release_event", G_CALLBACK (stop_seek), pipeline); g_signal_connect (hscale, "format_value", G_CALLBACK (format_value), pipeline); /* do the packing stuff ... */ gtk_window_set_default_size (GTK_WINDOW (window), 96, 96); gtk_container_add (GTK_CONTAINER (window), vbox); gtk_container_add (GTK_CONTAINER (vbox), hbox); gtk_box_pack_start (GTK_BOX (hbox), play_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (hbox), pause_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (hbox), stop_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (vbox), hscale, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (vbox), shscale, TRUE, TRUE, 2); /* connect things ... */ g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), pipeline); g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), pipeline); g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), pipeline); g_signal_connect (G_OBJECT (window), "delete_event", gtk_main_quit, NULL); /* show the gui. */ gtk_widget_show_all (window); if (verbose) { g_signal_connect (pipeline, "deep_notify", G_CALLBACK (gst_object_default_deep_notify), NULL); } bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); g_assert (bus); bus_watch = gst_bus_add_watch_full (bus, G_PRIORITY_HIGH, bus_message, pipeline, NULL); gtk_main (); g_print ("NULL pipeline\n"); gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (bus); g_print ("free pipeline\n"); gst_object_unref (pipeline); return 0; }