1 /* GStreamer interactive test for the gdkpixbufsink element
2  * Copyright (C) 2008 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <gst/gst.h>
25 #include <gtk/gtk.h>
26 
27 typedef struct
28 {
29   GstElement *pipe;
30   GstElement *sink;
31   gboolean got_video;
32 
33   GtkWidget *win;
34   GtkWidget *img;
35   GtkWidget *slider;
36   GtkWidget *accurate_cb;
37 
38   gboolean accurate;            /* whether to try accurate seeks */
39 
40   gint64 cur_pos;
41   gboolean prerolled;
42 } AppInfo;
43 
44 static void seek_to (AppInfo * info, gdouble percent);
45 
46 static GstElement *
create_element(const gchar * factory_name)47 create_element (const gchar * factory_name)
48 {
49   GstElement *element;
50 
51   element = gst_element_factory_make (factory_name, NULL);
52 
53   if (element == NULL)
54     g_error ("Failed to create '%s' element", factory_name);
55 
56   return element;
57 }
58 
59 static void
new_decoded_pad(GstElement * dec,GstPad * new_pad,AppInfo * info)60 new_decoded_pad (GstElement * dec, GstPad * new_pad, AppInfo * info)
61 {
62   const gchar *sname;
63   GstElement *csp, *scale, *filter;
64   GstStructure *s;
65   GstCaps *caps;
66   GstPad *sinkpad;
67 
68   /* already found a video stream? */
69   if (info->got_video)
70     return;
71 
72   /* FIXME: is this racy or does decodebin make sure caps are always
73    * negotiated at this point? */
74   caps = gst_pad_query_caps (new_pad, NULL);
75   g_return_if_fail (caps != NULL);
76 
77   s = gst_caps_get_structure (caps, 0);
78   sname = gst_structure_get_name (s);
79   if (!g_str_has_prefix (sname, "video/x-raw"))
80     goto not_video;
81 
82   csp = create_element ("videoconvert");
83   scale = create_element ("videoscale");
84   filter = create_element ("capsfilter");
85   info->sink = create_element ("gdkpixbufsink");
86   g_object_set (info->sink, "qos", FALSE, "max-lateness", (gint64) - 1, NULL);
87 
88   gst_bin_add_many (GST_BIN (info->pipe), csp, scale, filter, info->sink, NULL);
89 
90   sinkpad = gst_element_get_static_pad (csp, "sink");
91   if (GST_PAD_LINK_FAILED (gst_pad_link (new_pad, sinkpad)))
92     g_error ("Can't link new decoded pad to videoconvert's sink pad");
93   gst_object_unref (sinkpad);
94 
95   if (!gst_element_link (csp, scale))
96     g_error ("Can't link videoconvert to videoscale");
97   if (!gst_element_link (scale, filter))
98     g_error ("Can't link videoscale to capsfilter");
99   if (!gst_element_link (filter, info->sink))
100     g_error ("Can't link capsfilter to gdkpixbufsink");
101 
102   gst_element_set_state (info->sink, GST_STATE_PAUSED);
103   gst_element_set_state (filter, GST_STATE_PAUSED);
104   gst_element_set_state (scale, GST_STATE_PAUSED);
105   gst_element_set_state (csp, GST_STATE_PAUSED);
106 
107   info->got_video = TRUE;
108   return;
109 
110 not_video:
111   return;
112 }
113 
114 static void
no_more_pads(GstElement * decodebin,AppInfo * info)115 no_more_pads (GstElement * decodebin, AppInfo * info)
116 {
117   if (!info->got_video) {
118     g_error ("This file does not contain a video track, or you do not have "
119         "the necessary decoder(s) installed");
120   }
121 }
122 
123 static void
bus_message_cb(GstBus * bus,GstMessage * msg,AppInfo * info)124 bus_message_cb (GstBus * bus, GstMessage * msg, AppInfo * info)
125 {
126   switch (GST_MESSAGE_TYPE (msg)) {
127     case GST_MESSAGE_ASYNC_DONE:{
128       /* only interested in async-done messages from the top-level pipeline */
129       if (msg->src != GST_OBJECT_CAST (info->pipe))
130         break;
131 
132       if (!info->prerolled) {
133         /* make slider visible if it's not visible already */
134         gtk_widget_show (info->slider);
135 
136         /* initial frame is often black, so seek to beginning plus a bit */
137         seek_to (info, 0.001);
138         info->prerolled = TRUE;
139       }
140 
141       /* update position */
142       if (!gst_element_query_position (info->pipe, GST_FORMAT_TIME,
143               &info->cur_pos))
144         info->cur_pos = -1;
145       break;
146     }
147     case GST_MESSAGE_ELEMENT:{
148       const GValue *val;
149       GdkPixbuf *pixbuf = NULL;
150       const GstStructure *structure;
151 
152       /* only interested in element messages from our gdkpixbufsink */
153       if (msg->src != GST_OBJECT_CAST (info->sink))
154         break;
155 
156       /* only interested in these two messages */
157       if (!gst_message_has_name (msg, "preroll-pixbuf") &&
158           !gst_message_has_name (msg, "pixbuf")) {
159         break;
160       }
161 
162       g_print ("pixbuf\n");
163       structure = gst_message_get_structure (msg);
164       val = gst_structure_get_value (structure, "pixbuf");
165       g_return_if_fail (val != NULL);
166 
167       pixbuf = GDK_PIXBUF (g_value_dup_object (val));
168       gtk_image_set_from_pixbuf (GTK_IMAGE (info->img), pixbuf);
169       g_object_unref (pixbuf);
170       break;
171     }
172     case GST_MESSAGE_ERROR:{
173       GError *err = NULL;
174       gchar *dbg = NULL;
175 
176       gst_message_parse_error (msg, &err, &dbg);
177       g_error ("Error: %s\n%s\n", err->message, (dbg) ? dbg : "");
178       g_clear_error (&err);
179       g_free (dbg);
180       break;
181     }
182     default:
183       break;
184   }
185 }
186 
187 static gboolean
create_pipeline(AppInfo * info,const gchar * filename)188 create_pipeline (AppInfo * info, const gchar * filename)
189 {
190   GstElement *src, *dec;
191   GstBus *bus;
192 
193   info->pipe = gst_pipeline_new ("pipeline");
194   src = create_element ("filesrc");
195   g_object_set (src, "location", filename, NULL);
196 
197   dec = create_element ("decodebin");
198 
199   gst_bin_add_many (GST_BIN (info->pipe), src, dec, NULL);
200   if (!gst_element_link (src, dec))
201     g_error ("Can't link filesrc to decodebin");
202 
203   g_signal_connect (dec, "pad-added", G_CALLBACK (new_decoded_pad), info);
204 
205   g_signal_connect (dec, "no-more-pads", G_CALLBACK (no_more_pads), info);
206 
207   /* set up bus */
208   bus = gst_element_get_bus (info->pipe);
209   gst_bus_add_signal_watch (bus);
210   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), info);
211   gst_object_unref (bus);
212 
213   return TRUE;
214 }
215 
216 static void
seek_to(AppInfo * info,gdouble percent)217 seek_to (AppInfo * info, gdouble percent)
218 {
219   GstSeekFlags seek_flags;
220   gint64 seek_pos, dur = -1;
221 
222   if (!gst_element_query_duration (info->pipe, GST_FORMAT_TIME, &dur)
223       || dur <= 0) {
224     g_printerr ("Could not query duration\n");
225     return;
226   }
227 
228   seek_pos = gst_gdouble_to_guint64 (gst_guint64_to_gdouble (dur) * percent);
229   g_print ("Seeking to %" GST_TIME_FORMAT ", accurate: %d\n",
230       GST_TIME_ARGS (seek_pos), info->accurate);
231 
232   seek_flags = GST_SEEK_FLAG_FLUSH;
233 
234   if (info->accurate)
235     seek_flags |= GST_SEEK_FLAG_ACCURATE;
236   else
237     seek_flags |= GST_SEEK_FLAG_KEY_UNIT;
238 
239   if (!gst_element_seek_simple (info->pipe, GST_FORMAT_TIME, seek_flags,
240           seek_pos)) {
241     g_printerr ("Seek failed.\n");
242     return;
243   }
244 }
245 
246 static void
slider_cb(GtkRange * range,AppInfo * info)247 slider_cb (GtkRange * range, AppInfo * info)
248 {
249   gdouble val;
250 
251   val = gtk_range_get_value (range);
252   seek_to (info, val);
253 }
254 
255 static gchar *
slider_format_value_cb(GtkScale * scale,gdouble value,AppInfo * info)256 slider_format_value_cb (GtkScale * scale, gdouble value, AppInfo * info)
257 {
258   gchar s[64];
259 
260   if (info->cur_pos < 0)
261     return g_strdup_printf ("%0.1g%%", value * 100.0);
262 
263   g_snprintf (s, 64, "%" GST_TIME_FORMAT, GST_TIME_ARGS (info->cur_pos));
264   s[10] = '\0';
265   return g_strdup (s);
266 }
267 
268 static void
accurate_toggled_cb(GtkToggleButton * toggle,AppInfo * info)269 accurate_toggled_cb (GtkToggleButton * toggle, AppInfo * info)
270 {
271   info->accurate = gtk_toggle_button_get_active (toggle);
272 }
273 
274 static void
run_gui(const gchar * filename)275 run_gui (const gchar * filename)
276 {
277   GtkWidget *vbox, *hbox;
278   AppInfo *info;
279 
280   info = g_new0 (AppInfo, 1);
281 
282   /* create pipeline */
283   if (!create_pipeline (info, filename))
284     goto done;
285 
286   /* create window */
287   info->win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
288   g_signal_connect (info->win, "delete-event", G_CALLBACK (gtk_main_quit),
289       NULL);
290 
291   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
292   gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
293   gtk_container_add (GTK_CONTAINER (info->win), vbox);
294 
295   info->img = gtk_image_new ();
296   gtk_box_pack_start (GTK_BOX (vbox), info->img, FALSE, FALSE, 6);
297 
298   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
299   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 6);
300 
301   info->accurate_cb = gtk_check_button_new_with_label ("accurate seek "
302       "(might not work reliably with all demuxers)");
303   gtk_box_pack_start (GTK_BOX (hbox), info->accurate_cb, FALSE, FALSE, 6);
304   g_signal_connect (info->accurate_cb, "toggled",
305       G_CALLBACK (accurate_toggled_cb), info);
306 
307   info->slider = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL,
308       0.0, 1.0, 0.001);
309   gtk_box_pack_start (GTK_BOX (vbox), info->slider, FALSE, FALSE, 6);
310   g_signal_connect (info->slider, "value-changed",
311       G_CALLBACK (slider_cb), info);
312   g_signal_connect (info->slider, "format-value",
313       G_CALLBACK (slider_format_value_cb), info);
314 
315   /* and go! */
316   gst_element_set_state (info->pipe, GST_STATE_PAUSED);
317 
318   gtk_widget_show_all (info->win);
319   gtk_widget_hide (info->slider);       /* hide until we're prerolled */
320   gtk_main ();
321 
322 done:
323 
324   g_free (info);
325 }
326 
327 static gchar **filenames = NULL;
328 
329 int
main(int argc,char ** argv)330 main (int argc, char **argv)
331 {
332   static const GOptionEntry test_goptions[] = {
333     {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL},
334     {NULL, '\0', 0, 0, NULL, NULL, NULL}
335   };
336   GOptionContext *ctx;
337   GError *opt_err = NULL;
338 
339   gtk_init (&argc, &argv);
340 
341   /* command line option parsing */
342   ctx = g_option_context_new (" VIDEOFILE");
343   g_option_context_add_group (ctx, gst_init_get_option_group ());
344   g_option_context_add_main_entries (ctx, test_goptions, NULL);
345 
346   if (!g_option_context_parse (ctx, &argc, &argv, &opt_err)) {
347     g_error ("Error parsing command line options: %s", opt_err->message);
348     g_option_context_free (ctx);
349     g_clear_error (&opt_err);
350     return -1;
351   }
352 
353   if (filenames == NULL || filenames[0] == NULL || filenames[0][0] == '\0') {
354     g_printerr ("Please specify a path to a video file\n\n");
355     return -1;
356   }
357 
358   run_gui (filenames[0]);
359 
360   g_free (filenames);
361   g_option_context_free (ctx);
362 
363   return 0;
364 }
365