Fix bug in code
[platform/upstream/gstreamer.git] / gst-sdk / tutorials / basic-tutorial-5.c
1 #include <string.h>
2   
3 #include <gtk/gtk.h>
4 #include <gst/gst.h>
5 #include <gst/interfaces/xoverlay.h>
6   
7 #include <gdk/gdk.h>
8 #if defined (GDK_WINDOWING_X11)
9 #include <gdk/gdkx.h>
10 #elif defined (GDK_WINDOWING_WIN32)
11 #include <gdk/gdkwin32.h>
12 #elif defined (GDK_WINDOWING_QUARTZ)
13 #include <gdk/gdkquartzwindow.h>
14 #endif
15   
16 /* Structure to contain all our information, so we can pass it around */
17 typedef struct _CustomData {
18   GstElement *playbin2;           /* Our one and only pipeline */
19   
20   GtkWidget *slider;              /* Slider widget to keep track of current position */
21   GtkWidget *streams_list;        /* Text widget to display info about the streams */
22   gulong slider_update_signal_id; /* Signal ID for the slider update signal */
23   
24   GstState state;                 /* Current state of the pipeline */
25   gint64 duration;                /* Duration of the clip, in nanoseconds */
26 } CustomData;
27   
28 /* This function is called when the GUI toolkit creates the physical window that will hold the video.
29  * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
30  * and pass it to GStreamer through the XOverlay interface. */
31 static void realize_cb (GtkWidget *widget, CustomData *data) {
32   GdkWindow *window = gtk_widget_get_window (widget);
33   guintptr window_handle;
34   
35   if (!gdk_window_ensure_native (window))
36     g_error ("Couldn't create native window needed for GstXOverlay!");
37   
38   /* Retrieve window handler from GDK */
39 #if defined (GDK_WINDOWING_WIN32)
40   window_handle = (guintptr)GDK_WINDOW_HWND (window);
41 #elif defined (GDK_WINDOWING_QUARTZ)
42   window_handle = gdk_quartz_window_get_nsview (window);
43 #elif defined (GDK_WINDOWING_X11)
44   window_handle = GDK_WINDOW_XID (window);
45 #endif
46   /* Pass it to playbin2, which implements XOverlay and will forward it to the video sink */
47   gst_x_overlay_set_window_handle (GST_X_OVERLAY (data->playbin2), window_handle);
48 }
49   
50 /* This function is called when the PLAY button is clicked */
51 static void play_cb (GtkButton *button, CustomData *data) {
52   gst_element_set_state (data->playbin2, GST_STATE_PLAYING);
53 }
54   
55 /* This function is called when the PAUSE button is clicked */
56 static void pause_cb (GtkButton *button, CustomData *data) {
57   gst_element_set_state (data->playbin2, GST_STATE_PAUSED);
58 }
59   
60 /* This function is called when the STOP button is clicked */
61 static void stop_cb (GtkButton *button, CustomData *data) {
62   gst_element_set_state (data->playbin2, GST_STATE_READY);
63 }
64   
65 /* This function is called when the main window is closed */
66 static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
67   stop_cb (NULL, data);
68   gtk_main_quit ();
69 }
70   
71 /* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
72  * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
73  * we simply draw a black rectangle to avoid garbage showing up. */
74 static gboolean expose_cb (GtkWidget *widget, GdkEventExpose *event, CustomData *data) {
75   if (data->state < GST_STATE_PAUSED) {
76     GtkAllocation allocation;
77     GdkWindow *window = gtk_widget_get_window (widget);
78     cairo_t *cr;
79     
80     /* Cairo is a 2D graphics library which we use here to clean the video window.
81      * It is used by GStreamer for other reasons, so it will always be available to us. */
82     gtk_widget_get_allocation (widget, &allocation);
83     cr = gdk_cairo_create (window);
84     cairo_set_source_rgb (cr, 0, 0, 0);
85     cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
86     cairo_fill (cr);
87     cairo_destroy (cr);
88   }
89   
90   return FALSE;
91 }
92   
93 /* This function is called when the slider changes its position. We perform a seek to the
94  * new position here. */
95 static void slider_cb (GtkRange *range, CustomData *data) {
96   gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
97   gst_element_seek_simple (data->playbin2, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
98       (gint64)(value * GST_SECOND));
99 }
100   
101 /* This creates all the GTK+ widgets that compose our application, and registers the callbacks */
102 static void create_ui (CustomData *data) {
103   GtkWidget *main_window;  /* The uppermost window, containing all other windows */
104   GtkWidget *video_window; /* The drawing area where the video will be shown */
105   GtkWidget *main_box;     /* VBox to hold main_hbox and the controls */
106   GtkWidget *main_hbox;    /* HBox to hold the video_window and the stream info text widget */
107   GtkWidget *controls;     /* HBox to hold the buttons and the slider */
108   GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */
109   
110   main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
111   g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);
112   
113   video_window = gtk_drawing_area_new ();
114   gtk_widget_set_double_buffered (video_window, FALSE);
115   g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data);
116   g_signal_connect (video_window, "expose_event", G_CALLBACK (expose_cb), data);
117   
118   play_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
119   g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);
120   
121   pause_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PAUSE);
122   g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);
123   
124   stop_button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_STOP);
125   g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);
126   
127   data->slider = gtk_hscale_new_with_range (0, 100, 1);
128   gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0);
129   data->slider_update_signal_id = g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data);
130   
131   data->streams_list = gtk_text_view_new ();
132   gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);
133   
134   controls = gtk_hbox_new (FALSE, 0);
135   gtk_box_pack_start (GTK_BOX (controls), play_button, FALSE, FALSE, 2);
136   gtk_box_pack_start (GTK_BOX (controls), pause_button, FALSE, FALSE, 2);
137   gtk_box_pack_start (GTK_BOX (controls), stop_button, FALSE, FALSE, 2);
138   gtk_box_pack_start (GTK_BOX (controls), data->slider, TRUE, TRUE, 2);
139   
140   main_hbox = gtk_hbox_new (FALSE, 0);
141   gtk_box_pack_start (GTK_BOX (main_hbox), video_window, TRUE, TRUE, 0);
142   gtk_box_pack_start (GTK_BOX (main_hbox), data->streams_list, FALSE, FALSE, 2);
143   
144   main_box = gtk_vbox_new (FALSE, 0);
145   gtk_box_pack_start (GTK_BOX (main_box), main_hbox, TRUE, TRUE, 0);
146   gtk_box_pack_start (GTK_BOX (main_box), controls, FALSE, FALSE, 0);
147   gtk_container_add (GTK_CONTAINER (main_window), main_box);
148   gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480);
149   
150   gtk_widget_show_all (main_window);
151 }
152   
153 /* This function is called periodically to refresh the GUI */
154 static gboolean refresh_ui (CustomData *data) {
155   GstFormat fmt = GST_FORMAT_TIME;
156   gint64 current = -1;
157   
158   /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
159   if (data->state < GST_STATE_PAUSED)
160     return TRUE;
161   
162   /* If we didn't know it yet, query the stream duration */
163   if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
164     if (!gst_element_query_duration (data->playbin2, &fmt, &data->duration)) {
165       g_printerr ("Could not query current duration.\n");
166     } else {
167       /* Set the range of the slider to the clip duration, in SECONDS */
168       gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
169     }
170   }
171   
172   if (gst_element_query_position (data->playbin2, &fmt, &current)) {
173     /* Block the "value-changed" signal, so the slider_cb function is not called
174      * (which would trigger a seek the user has not requested) */
175     g_signal_handler_block (data->slider, data->slider_update_signal_id);
176     /* Set the position of the slider to the current pipeline positoin, in SECONDS */
177     gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
178     /* Re-enable the signal */
179     g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
180   }
181   return TRUE;
182 }
183   
184 /* This function is called when new metadata is discovered in the stream */
185 static void tags_cb (GstElement *playbin2, gint stream, CustomData *data) {
186   /* We are possibly in a GStreamer working thread, so we notify the main
187    * thread of this event through a message in the bus */
188   gst_element_post_message (playbin2,
189     gst_message_new_application (GST_OBJECT (playbin2),
190       gst_structure_new ("tags-changed", NULL)));
191 }
192   
193 /* This function is called when an error message is posted on the bus */
194 static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
195   GError *err;
196   gchar *debug_info;
197   
198   /* Print error details on the screen */
199   gst_message_parse_error (msg, &err, &debug_info);
200   g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message);
201   g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none");
202   g_clear_error (&err);
203   g_free (debug_info);
204   
205   /* Set the pipeline to READY (which stops playback) */
206   gst_element_set_state (data->playbin2, GST_STATE_READY);
207 }
208   
209 /* This function is called when an End-Of-Stream message is posted on the bus.
210  * We just set the pipeline to READY (which stops playback) */
211 static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
212   g_print ("End-Of-Stream reached.\n");
213   gst_element_set_state (data->playbin2, GST_STATE_READY);
214 }
215   
216 /* This function is called when the pipeline changes states. We use it to
217  * keep track of the current state. */
218 static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
219   GstState old_state, new_state, pending_state;
220   gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
221   if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin2)) {
222     data->state = new_state;
223     g_print ("State set to %s\n", gst_element_state_get_name (new_state));
224     if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
225       /* For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state */
226       refresh_ui (data);
227     }
228   }
229 }
230   
231 /* Extract metadata from all the streams and write it to the text widget in the GUI */
232 static void analyze_streams (CustomData *data) {
233   gint i;
234   GstTagList *tags;
235   gchar *str, *total_str;
236   guint rate;
237   gint n_video, n_audio, n_text;
238   GtkTextBuffer *text;
239   
240   /* Clean current contents of the widget */
241   text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
242   gtk_text_buffer_set_text (text, "", -1);
243   
244   /* Read some properties */
245   g_object_get (data->playbin2, "n-video", &n_video, NULL);
246   g_object_get (data->playbin2, "n-audio", &n_audio, NULL);
247   g_object_get (data->playbin2, "n-text", &n_text, NULL);
248   
249   for (i = 0; i < n_video; i++) {
250     tags = NULL;
251     /* Retrieve the stream's video tags */
252     g_signal_emit_by_name (data->playbin2, "get-video-tags", i, &tags);
253     if (tags) {
254       total_str = g_strdup_printf ("video stream %d:\n", i);
255       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
256       g_free (total_str);
257       gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
258       total_str = g_strdup_printf ("  codec: %s\n", str ? str : "unknown");
259       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
260       g_free (total_str);
261       g_free (str);
262       gst_tag_list_free (tags);
263     }
264   }
265   
266   for (i = 0; i < n_audio; i++) {
267     tags = NULL;
268     /* Retrieve the stream's audio tags */
269     g_signal_emit_by_name (data->playbin2, "get-audio-tags", i, &tags);
270     if (tags) {
271       total_str = g_strdup_printf ("\naudio stream %d:\n", i);
272       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
273       g_free (total_str);
274       if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
275         total_str = g_strdup_printf ("  codec: %s\n", str);
276         gtk_text_buffer_insert_at_cursor (text, total_str, -1);
277         g_free (total_str);
278         g_free (str);
279       }
280       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
281         total_str = g_strdup_printf ("  language: %s\n", str);
282         gtk_text_buffer_insert_at_cursor (text, total_str, -1);
283         g_free (total_str);
284         g_free (str);
285       }
286       if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
287         total_str = g_strdup_printf ("  bitrate: %d\n", rate);
288         gtk_text_buffer_insert_at_cursor (text, total_str, -1);
289         g_free (total_str);
290       }
291       gst_tag_list_free (tags);
292     }
293   }
294   
295   for (i = 0; i < n_text; i++) {
296     tags = NULL;
297     /* Retrieve the stream's subtitle tags */
298     g_signal_emit_by_name (data->playbin2, "get-text-tags", i, &tags);
299     if (tags) {
300       total_str = g_strdup_printf ("\nsubtitle stream %d:\n", i);
301       gtk_text_buffer_insert_at_cursor (text, total_str, -1);
302       g_free (total_str);
303       if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
304         total_str = g_strdup_printf ("  language: %s\n", str);
305         gtk_text_buffer_insert_at_cursor (text, total_str, -1);
306         g_free (total_str);
307         g_free (str);
308       }
309       gst_tag_list_free (tags);
310     }
311   }
312 }
313   
314 /* This function is called when an "application" message is posted on the bus.
315  * Here we retrieve the message posted by the tags_cb callback */
316 static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
317   if (g_strcmp0 (gst_structure_get_name (msg->structure), "tags-changed") == 0) {
318     /* If the message is the "tags-changed" (only one we are currently issuing), update
319      * the stream info GUI */
320     analyze_streams (data);
321   }
322 }
323   
324 int main(int argc, char *argv[]) {
325   CustomData data;
326   GstStateChangeReturn ret;
327   GstBus *bus;
328   
329   /* Initialize GTK */
330   gtk_init (&argc, &argv);
331   
332   /* Initialize GStreamer */
333   gst_init (&argc, &argv);
334   
335   /* Initialize our data structure */
336   memset (&data, 0, sizeof (data));
337   data.duration = GST_CLOCK_TIME_NONE;
338   
339   /* Create the elements */
340   data.playbin2 = gst_element_factory_make ("playbin2", "playbin2");
341    
342   if (!data.playbin2) {
343     g_printerr ("Not all elements could be created.\n");
344     return -1;
345   }
346   
347   /* Set the URI to play */
348   g_object_set (data.playbin2, "uri", "http://docs.gstreamer.com/media/sintel_trailer-480p.webm", NULL);
349   
350   /* Connect to interesting signals in playbin2 */
351   g_signal_connect (G_OBJECT (data.playbin2), "video-tags-changed", (GCallback) tags_cb, &data);
352   g_signal_connect (G_OBJECT (data.playbin2), "audio-tags-changed", (GCallback) tags_cb, &data);
353   g_signal_connect (G_OBJECT (data.playbin2), "text-tags-changed", (GCallback) tags_cb, &data);
354   
355   /* Create the GUI */
356   create_ui (&data);
357   
358   /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
359   bus = gst_element_get_bus (data.playbin2);
360   gst_bus_add_signal_watch (bus);
361   g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
362   g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
363   g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
364   g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
365   gst_object_unref (bus);
366   
367   /* Start playing */
368   ret = gst_element_set_state (data.playbin2, GST_STATE_PLAYING);
369   if (ret == GST_STATE_CHANGE_FAILURE) {
370     g_printerr ("Unable to set the pipeline to the playing state.\n");
371     gst_object_unref (data.playbin2);
372     return -1;
373   }
374   
375   /* Register a function that GLib will call every second */
376   g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);
377   
378   /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
379   gtk_main ();
380   
381   /* Free resources */
382   gst_element_set_state (data.playbin2, GST_STATE_NULL);
383   gst_object_unref (data.playbin2);
384   return 0;
385 }