/* GStreamer * * seek.c: seeking sample application * * Copyright (C) 2005 Wim Taymans * 2006 Stefan Kost * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* FIXME: remove #if 0 code * FIXME: is the flush_seek part after sending the seek still needed? * */ #include #include #include #include #include /* configuration */ #define SOURCE "filesrc" //#define SOURCE "gnomevfssrc" #define ASINK "alsasink" //#define ASINK "osssink" #define VSINK "xvimagesink" //#define VSINK "sdlvideosink" //#define VSINK "ximagesink" //#define VSINK "aasink" //#define VSINK "cacasink" //#define UPDATE_INTERVAL 500 //#define UPDATE_INTERVAL 100 #define UPDATE_INTERVAL 500 /* timeout for gst_element_get_state() after a seek */ #define SEEK_TIMEOUT 40 * GST_MSECOND static GList *seekable_elements = NULL; static GstElement *src, *decoder, *sink, *queue, *parse, *pipeline, *adcn, *volume, *demux; static gint pipeline_type; static const gchar *pipeline_spec; static gint64 position = -1; static gint64 duration = -1, s_position=0; static GtkAdjustment *adjustment; static GtkWidget *hscale; static GstState state = GST_STATE_NULL; static guint update_id = 0; static gulong changed_id; GtkWidget *play_button, *pause_button, *stop_button, *rwd_button, *fwd_button; /* pipeline construction */ typedef struct { const gchar *padname; GstPad *target; GstElement *bin; } dyn_link; static GstElement * gst_element_factory_make_or_warn (gchar * type, 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) { gchar *padname; dyn_link *connect = (dyn_link *) data; padname = gst_pad_get_name (newpad); if (connect->padname == NULL || !strcmp (padname, connect->padname)) { if (connect->bin) gst_bin_add (GST_BIN (pipeline), connect->bin); gst_pad_link (newpad, connect->target); } g_free (padname); } 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 void new_pad (GstElement *element, GstPad *pad, gpointer data) { GstPad* sinkpad = NULL; sinkpad = gst_element_get_pad(parse, "sink"); if(sinkpad != NULL) { gst_pad_link (pad, sinkpad); gst_object_unref (GST_OBJECT(sinkpad)); } } static GstElement * make_mp3_pipeline (const gchar * location) { pipeline = gst_pipeline_new ("app"); src = gst_element_factory_make_or_warn (SOURCE, "src"); demux = gst_element_factory_make_or_warn ("id3demux", "demux"); parse = gst_element_factory_make_or_warn ("mp3parse", "parse"); decoder = gst_element_factory_make_or_warn ("mad", "decoder"); adcn = gst_element_factory_make_or_warn ("audioconvert", "adcn"); volume = gst_element_factory_make_or_warn ("volume", "volume_e"); queue = gst_element_factory_make_or_warn ("queue", "queue"); sink = gst_element_factory_make_or_warn (ASINK, "sink"); seekable_elements = g_list_prepend (seekable_elements, sink); seekable_elements = g_list_prepend (seekable_elements, parse); seekable_elements = g_list_prepend (seekable_elements, decoder); g_object_set (G_OBJECT (src), "location", location, NULL); //g_object_set (G_OBJECT (osssink), "fragment", 0x00180008, NULL); gst_bin_add (GST_BIN (pipeline), src); gst_bin_add (GST_BIN (pipeline), demux); gst_bin_add (GST_BIN (pipeline), parse); gst_bin_add (GST_BIN (pipeline), decoder); gst_bin_add (GST_BIN (pipeline), adcn); gst_bin_add (GST_BIN (pipeline), volume); //gst_bin_add (GST_BIN (pipeline), queue); gst_bin_add (GST_BIN (pipeline), sink); g_print("linkinggg\n"); gst_element_link(src, demux); g_signal_connect (demux, "pad-added", G_CALLBACK (new_pad), NULL); gst_element_link (parse, decoder); gst_element_link (decoder, adcn); gst_element_link (adcn, volume); //gst_element_link (volume, queue); //gst_element_link (queue, sink); gst_element_link(volume, sink); return pipeline; } typedef struct { gchar *name; GstElement *(*func) (const gchar * location); } Pipeline; static Pipeline pipelines[] = { {"mp3", make_mp3_pipeline}, {NULL, NULL}, }; #define NUM_TYPES ((sizeof (pipelines) / sizeof (Pipeline)) - 1) /* ui callbacks and helpers */ static gchar * format_value (GtkScale * scale, gdouble value) { gint64 real; gint64 seconds; real = value * duration / 100; seconds = (gint64) real / GST_SECOND; return g_strdup_printf ("%02" G_GINT64_FORMAT ":%02" G_GINT64_FORMAT, seconds / 60, seconds % 60); } typedef struct { const gchar *name; const GstFormat format; } seek_format; static seek_format seek_formats[] = { {"tim", GST_FORMAT_TIME}, {"byt", GST_FORMAT_BYTES}, {"buf", GST_FORMAT_BUFFERS}, {"def", GST_FORMAT_DEFAULT}, {NULL, 0}, }; static void fwd_cb (GtkWidget * widget); static void rwd_cb (GtkWidget * widget); static void set_scale (gdouble value) { g_signal_handlers_block_by_func (fwd_button, fwd_cb, pipeline); g_signal_handlers_block_by_func (rwd_button, rwd_cb, pipeline); gtk_adjustment_set_value (adjustment, value); g_signal_handlers_unblock_by_func (fwd_button, fwd_cb, pipeline); g_signal_handlers_unblock_by_func (rwd_button, rwd_cb, pipeline); gtk_widget_queue_draw (hscale); } static void update_scale (void) { g_print("entryy\n"); GstFormat format = GST_FORMAT_TIME; //GstElement *element = GST_ELEMENT (seekable_elements->data); //gst_element_query_position (element, &format, &position); //gst_element_query_duration (element, &format, &duration); gst_element_query_position (pipeline, &format, &position); gst_element_query_duration (pipeline, &format, &duration); if (position >= duration) duration = position; if (duration > 0) { set_scale (position * 100.0 / duration); } g_print("exitt\n"); } static void set_update_scale (gboolean active) { if (active) { if (update_id == 0) { update_id = g_timeout_add (UPDATE_INTERVAL, (GtkFunction) update_scale, pipeline); } } else { if (update_id) { g_source_remove (update_id); update_id = 0; } } } static gboolean send_event (GstEvent * event) { gboolean res = FALSE; GList *walk = seekable_elements; /* while (walk) { GstElement *seekable = GST_ELEMENT (walk->data); gst_event_ref (event); res = gst_element_send_event (seekable, event); walk = g_list_next (walk); }*/ res = gst_element_send_event (pipeline, event); // gst_event_unref (event); return res; } static void fwd_cb (GtkWidget * widget) { g_print("%s entry\n", __FUNCTION__); gint64 real; gboolean res = FALSE; GstEvent *s_event; GstSeekFlags flags; gdouble value; GstState cur, pen; GstFormat format = GST_FORMAT_TIME; //gst_element_set_state (pipeline, GST_STATE_PAUSED); /*while(1) { gst_element_get_state (GST_ELEMENT (pipeline), &cur, &pen, SEEK_TIMEOUT); if(cur == GST_STATE_PAUSED && pen == GST_STATE_VOID_PENDING) { g_print("pipeline state changed to paused\n"); break; } }*/ set_update_scale (FALSE); update_scale(); flags = 0; flags |= GST_SEEK_FLAG_FLUSH; gst_element_query_position (pipeline, &format, &position); gst_element_query_duration (pipeline, &format, &duration); g_print("before fwd seek %"GST_TIME_FORMAT"\n", GST_TIME_ARGS (position)); s_position = position; real = position + 5 * GST_SECOND; if(real >= duration) real = duration; s_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, real , GST_SEEK_TYPE_SET, -1); res = send_event (s_event); if (res) { //gst_pipeline_set_new_stream_time (GST_PIPELINE (pipeline), 0); /*gst_element_get_state (GST_ELEMENT (pipeline), NULL, NULL, SEEK_TIMEOUT); if(state == GST_STATE_PLAYING) gst_element_set_state (pipeline, GST_STATE_PLAYING); while(1) { gst_element_get_state (GST_ELEMENT (pipeline), &cur, &pen, SEEK_TIMEOUT); if(cur == state && pen == GST_STATE_VOID_PENDING) { g_print("pipeline state changed to playing\n"); break; } }*/ GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); GstMessage *msg; msg=gst_bus_poll(bus, GST_MESSAGE_ASYNC_DONE, -1); gst_message_unref(msg); g_print("async is done\n"); gst_element_query_position (pipeline, &format, &position); gst_element_query_duration (pipeline, &format, &duration); g_print("after fwd seek %"GST_TIME_FORMAT"\n", GST_TIME_ARGS (position)); if(s_position+ (gint64)4 * GST_SECOND >= position ) { g_print("********************************************mistake %lld %lld\n", s_position, position); //exit(1); } s_position = 0; //if(state == GST_STATE_PLAYING) //gst_element_set_state (pipeline, GST_STATE_PLAYING); } else { g_print ("seek failed\n"); set_update_scale (TRUE); } g_print("%s exit\n", __FUNCTION__); } static void rwd_cb (GtkWidget * widget) { g_print("%s entry\n", __FUNCTION__); gint64 real; gboolean res = FALSE; GstEvent *s_event; GstSeekFlags flags; gdouble value; GstFormat format = GST_FORMAT_TIME; GstState cur, pen; //gst_element_set_state (pipeline, GST_STATE_PAUSED); /*while(1) { gst_element_get_state (GST_ELEMENT (pipeline), &cur, &pen, SEEK_TIMEOUT); if(cur == GST_STATE_PAUSED && pen == GST_STATE_VOID_PENDING) { g_print("pipeline state changed to paused\n"); break; } }*/ set_update_scale (FALSE); update_scale(); gst_element_query_position (pipeline, &format, &position); gst_element_query_duration (pipeline, &format, &duration); g_print("before rwd seek %"GST_TIME_FORMAT"\n", GST_TIME_ARGS (position)); s_position = position; flags = 0; flags |= GST_SEEK_FLAG_FLUSH; real = position - 5 * GST_SECOND; if(real <= 0) real = 0; s_event = gst_event_new_seek (1.0, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, real, GST_SEEK_TYPE_SET, -1); res = send_event (s_event); if (res) { //gst_pipeline_set_new_stream_time (GST_PIPELINE (pipeline), 0); /*gst_element_get_state (GST_ELEMENT (pipeline), &cur, &pen, SEEK_TIMEOUT); if(state == GST_STATE_PLAYING) gst_element_set_state (pipeline, GST_STATE_PLAYING); while(1) { gst_element_get_state (GST_ELEMENT (pipeline), &cur, &pen, SEEK_TIMEOUT); if(cur == state && pen == GST_STATE_VOID_PENDING) { g_print("pipeline state changed to playing\n"); break; } }*/ GstBus* bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); GstMessage *msg; msg=gst_bus_poll(bus, GST_MESSAGE_ASYNC_DONE, -1); gst_message_unref(msg); g_print("async is done\n"); gst_element_query_position (pipeline, &format, &position); gst_element_query_duration (pipeline, &format, &duration); g_print("after rwd seek %"GST_TIME_FORMAT"\n", GST_TIME_ARGS (position)); if(position+1000000000 >= s_position ) { g_print("********************************************mistake %lld %lld\n", s_position, position); //exit(1); } s_position = 0; //if(state == GST_STATE_PLAYING) //gst_element_set_state (pipeline, GST_STATE_PLAYING); } else { g_print ("seek failed\n"); set_update_scale (TRUE); } g_print("%s exit\n", __FUNCTION__); } static void play_cb (GtkButton * button, gpointer data) { GstStateChangeReturn ret; if (state != GST_STATE_PLAYING) { g_print ("PLAY pipeline\n"); ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) goto failed; state = GST_STATE_PLAYING; } return; failed: { g_print ("PLAY failed\n"); } } static void pause_cb (GtkButton * button, gpointer data) { if (state != GST_STATE_PAUSED) { GstStateChangeReturn ret; g_print ("PAUSE pipeline\n"); ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); if (ret == GST_STATE_CHANGE_FAILURE) goto failed; state = GST_STATE_PAUSED; } return; failed: { g_print ("PAUSE failed\n"); } } static void stop_cb (GtkButton * button, gpointer data) { if (state != GST_STATE_READY) { GstStateChangeReturn ret; g_print ("READY pipeline\n"); ret = gst_element_set_state (pipeline, GST_STATE_READY); if (ret == GST_STATE_CHANGE_FAILURE) goto failed; set_scale (0.0); state = GST_STATE_READY; /* if one uses parse_launch, play, stop and play again it fails as all the * pads after the demuxer can't be reconnected */ if (!strcmp (pipelines[pipeline_type].name, "parse-launch")) { gst_element_set_state (pipeline, GST_STATE_NULL); gst_object_unref (pipeline); pipeline = pipelines[pipeline_type].func (pipeline_spec); g_assert (pipeline); gst_element_set_state (pipeline, GST_STATE_READY); } } return; failed: { g_print ("STOP failed\n"); } } static void msg_eos (GstBus * bus, GstMessage * message, GstPipeline * pipeline) { g_print("&&&&&&&&&&&&&&&&&&&GOT AN EOS\n"); GstStateChangeReturn ret; g_print ("READY pipeline\n"); ret = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_READY); if (ret == GST_STATE_CHANGE_FAILURE) g_print ("READY failed\n"); set_scale (0.0); state = GST_STATE_READY; } static void msg_state_changed (GstBus * bus, GstMessage * message, GstPipeline * pipeline) { const GstStructure *s; s = gst_message_get_structure (message); /* We only care about state changed on the pipeline */ if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (pipeline)) { GstState old, new, pending; gst_message_parse_state_changed (message, &old, &new, &pending); g_print("state changed by %s , from %s to %s\n", GST_ELEMENT_NAME(GST_MESSAGE_SRC(message)), gst_element_state_get_name(old), gst_element_state_get_name(new)); /* When state of the pipeline changes to playing we start updating scale */ if (new == GST_STATE_PLAYING) { set_update_scale (TRUE); } else { set_update_scale (FALSE); } } } static void msg_async_done (GstBus * bus, GstMessage * message, GstPipeline * pipeline) { g_print("async done by %s\n", GST_ELEMENT_NAME(GST_MESSAGE_SRC (message))); } 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; //*flagtable; GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox, *flush_checkbox; GtkWidget *scrub_checkbox, *play_scrub_checkbox, *rate_spinbutton; GtkWidget *rate_label; GtkTooltips *tips; GError *err = NULL; if (!g_thread_supported ()) g_thread_init (NULL); gtk_init (&argc, &argv); gst_init (&argc, &argv); if (argc != 3) { print_usage (argc, argv); exit (-1); } pipeline_type = atoi (argv[1]); if (pipeline_type < 0 || pipeline_type >= NUM_TYPES) { print_usage (argc, argv); exit (-1); } pipeline_spec = argv[2]; pipeline = pipelines[pipeline_type].func (pipeline_spec); g_assert (pipeline); /* initialize gui elements ... */ tips = gtk_tooltips_new (); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); hbox = gtk_hbox_new (FALSE, 0); vbox = gtk_vbox_new (FALSE, 0); //flagtable = gtk_table_new (4, 2, FALSE); gtk_container_set_border_width (GTK_CONTAINER (vbox), 3); /* media controls */ play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY); pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE); stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP); fwd_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_FORWARD); rwd_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_REWIND); /* seek bar */ adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.00, 100.0, 0.0, 0.0, 0.0)); hscale = gtk_hscale_new (adjustment); gtk_scale_set_digits (GTK_SCALE (hscale), 2); gtk_range_set_update_policy (GTK_RANGE (hscale), GTK_UPDATE_CONTINUOUS); gtk_signal_connect (GTK_OBJECT (hscale), "format_value", G_CALLBACK (format_value), pipeline); /* do the packing stuff ... */ gtk_window_set_default_size (GTK_WINDOW (window), 250, 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 (hbox), fwd_button, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (hbox), rwd_button, FALSE, FALSE, 2); //gtk_box_pack_start (GTK_BOX (hbox), flagtable, FALSE, FALSE, 2); gtk_box_pack_start (GTK_BOX (vbox), hscale, 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 (fwd_button), "clicked", G_CALLBACK (fwd_cb), pipeline); g_signal_connect (G_OBJECT (rwd_button), "clicked", G_CALLBACK (rwd_cb), pipeline); g_signal_connect (G_OBJECT (window), "destroy", gtk_main_quit, NULL); /* show the gui. */ gtk_widget_show_all (window); { GstBus *bus; bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline)); gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); g_signal_connect (bus, "message::eos", (GCallback) msg_eos, pipeline); g_signal_connect (bus, "message::state-changed", (GCallback) msg_state_changed, pipeline); g_signal_connect (bus, "message::async-done", (GCallback) msg_async_done, pipeline); } gtk_main (); g_print ("NULL pipeline\n"); gst_element_set_state (pipeline, GST_STATE_NULL); g_print ("free pipeline\n"); gst_object_unref (pipeline); return 0; }