diff --git a/ChangeLog b/ChangeLog index 96d42cf027..2e2d445953 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2008-04-06 Tim-Philipp Müller + + * tests/icles/.cvsignore: + * tests/icles/Makefile.am: + * tests/icles/gdkpixbufsink-test.c: + Interactive test app for gdkpixbufsink. + 2008-04-06 Sebastian Dröge Patch by: Damien Lespiau diff --git a/tests/icles/.gitignore b/tests/icles/.gitignore index 9af3df0949..c4d3fcdcc5 100644 --- a/tests/icles/.gitignore +++ b/tests/icles/.gitignore @@ -1,3 +1,4 @@ +gdkpixbufsink-test ximagesrc-test v4l2src-test videocrop-test diff --git a/tests/icles/Makefile.am b/tests/icles/Makefile.am index 6478afb7dd..529b8dfa97 100644 --- a/tests/icles/Makefile.am +++ b/tests/icles/Makefile.am @@ -1,3 +1,13 @@ +if HAVE_GTK +GTK_TESTS = gdkpixbufsink-test +gdkpixbufsink_test_SOURCES = gdkpixbufsink-test.c +gdkpixbufsink_test_CFLAGS = $(GTK_CFLAGS) $(GST_CFLAGS) +gdkpixbufsink_test_LDADD = $(GTK_LIBS) $(GST_LIBS) +gdkpixbufsink_test_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +else +GTK_TESTS = +endif + if USE_GST_V4L2 V4L2_TESTS = v4l2src-test @@ -25,5 +35,5 @@ videocrop_test_CFLAGS = $(GST_CFLAGS) videocrop_test_LDADD = $(GST_LIBS) videocrop_test_LDFLAGS = $(GST_PLUGIN_LDFLAGS) -noinst_PROGRAMS = $(V4L2_TESTS) $(X_TESTS) videocrop-test +noinst_PROGRAMS = $(GTK_TESTS) $(V4L2_TESTS) $(X_TESTS) videocrop-test diff --git a/tests/icles/gdkpixbufsink-test.c b/tests/icles/gdkpixbufsink-test.c new file mode 100644 index 0000000000..d23b182809 --- /dev/null +++ b/tests/icles/gdkpixbufsink-test.c @@ -0,0 +1,358 @@ +/* GStreamer interactive test for the gdkpixbufsink element + * Copyright (C) 2008 Tim-Philipp Müller + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +typedef struct +{ + GstElement *pipe; + GstElement *sink; + gboolean got_video; + + GtkWidget *win; + GtkWidget *img; + GtkWidget *slider; + GtkWidget *accurate_cb; + + gboolean accurate; /* whether to try accurate seeks */ + + gint64 cur_pos; + gboolean prerolled; +} AppInfo; + +static void seek_to (AppInfo * info, gdouble percent); + +static GstElement * +create_element (const gchar * factory_name) +{ + GstElement *element; + + element = gst_element_factory_make (factory_name, NULL); + + if (element == NULL) + g_error ("Failed to create '%s' element", factory_name); + + return element; +} + +static void +new_decoded_pad (GstElement * dec, GstPad * new_pad, gboolean last, + AppInfo * info) +{ + const gchar *sname; + GstElement *csp, *scale, *filter; + GstStructure *s; + GstCaps *caps; + GstPad *sinkpad; + + /* already found a video stream? */ + if (info->got_video) + return; + + /* FIXME: is this racy or does decodebin2 make sure caps are always + * negotiated at this point? */ + caps = gst_pad_get_caps (new_pad); + g_return_if_fail (caps != NULL); + + s = gst_caps_get_structure (caps, 0); + sname = gst_structure_get_name (s); + if (!g_str_has_prefix (sname, "video/x-raw-")) + goto not_video; + + csp = create_element ("ffmpegcolorspace"); + scale = create_element ("videoscale"); + filter = create_element ("capsfilter"); + info->sink = create_element ("gdkpixbufsink"); + g_object_set (info->sink, "qos", FALSE, "max-lateness", (gint64) - 1, NULL); + + gst_bin_add_many (GST_BIN (info->pipe), csp, scale, filter, info->sink, NULL); + + sinkpad = gst_element_get_static_pad (csp, "sink"); + if (GST_PAD_LINK_FAILED (gst_pad_link (new_pad, sinkpad))) + g_error ("Can't link new decoded pad to ffmpegcolorspace's sink pad"); + gst_object_unref (sinkpad); + + if (!gst_element_link (csp, scale)) + g_error ("Can't link ffmpegcolorspace to videoscale"); + if (!gst_element_link (scale, filter)) + g_error ("Can't link videoscale to capsfilter"); + if (!gst_element_link (filter, info->sink)) + g_error ("Can't link capsfilter to gdkpixbufsink"); + + gst_element_set_state (info->sink, GST_STATE_PAUSED); + gst_element_set_state (filter, GST_STATE_PAUSED); + gst_element_set_state (scale, GST_STATE_PAUSED); + gst_element_set_state (csp, GST_STATE_PAUSED); + + info->got_video = TRUE; + return; + +not_video: + { + if (last) { + g_error ("This file does not contain a video track, or you do not have " + "the necessary decoder(s) installed"); + } + } +} + +static void +bus_message_cb (GstBus * bus, GstMessage * msg, AppInfo * info) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ASYNC_DONE:{ + GstFormat fmt = GST_FORMAT_TIME; + + /* only interested in async-done messages from the top-level pipeline */ + if (msg->src != GST_OBJECT_CAST (info->pipe)) + break; + + if (!info->prerolled) { + /* make slider visible if it's not visible already */ + gtk_widget_show (info->slider); + + /* initial frame is often black, so seek to beginning plus a bit */ + seek_to (info, 0.001); + info->prerolled = TRUE; + } + + /* update position */ + if (!gst_element_query_position (info->pipe, &fmt, &info->cur_pos)) + info->cur_pos = -1; + break; + } + case GST_MESSAGE_ELEMENT:{ + const GValue *val; + GdkPixbuf *pixbuf = NULL; + + /* only interested in element messages from our gdkpixbufsink */ + if (msg->src != GST_OBJECT_CAST (info->sink)) + break; + + /* only interested in these two messages */ + if (!gst_structure_has_name (msg->structure, "preroll-pixbuf") && + !gst_structure_has_name (msg->structure, "pixbuf")) { + break; + } + + g_print ("pixbuf\n"); + val = gst_structure_get_value (msg->structure, "pixbuf"); + g_return_if_fail (val != NULL); + + pixbuf = g_value_dup_object (val); + gtk_image_set_from_pixbuf (GTK_IMAGE (info->img), pixbuf); + g_object_unref (pixbuf); + break; + } + case GST_MESSAGE_ERROR:{ + GError *err = NULL; + gchar *dbg = NULL; + + gst_message_parse_error (msg, &err, &dbg); + g_error ("Error: %s\n%s\n", err->message, (dbg) ? dbg : ""); + g_error_free (err); + g_free (dbg); + break; + } + default: + break; + } +} + +static gboolean +create_pipeline (AppInfo * info, const gchar * filename) +{ + GstElement *src, *dec; + GstBus *bus; + + info->pipe = gst_pipeline_new ("pipeline"); + src = create_element ("filesrc"); + g_object_set (src, "location", filename, NULL); + + dec = create_element ("decodebin2"); + + gst_bin_add_many (GST_BIN (info->pipe), src, dec, NULL); + if (!gst_element_link (src, dec)) + g_error ("Can't link filesrc to decodebin2"); + + g_signal_connect (dec, "new-decoded-pad", G_CALLBACK (new_decoded_pad), info); + + /* set up bus */ + bus = gst_element_get_bus (info->pipe); + gst_bus_add_signal_watch (bus); + g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), info); + gst_object_unref (bus); + + return TRUE; +} + +static void +seek_to (AppInfo * info, gdouble percent) +{ + GstSeekFlags seek_flags; + GstFormat fmt = GST_FORMAT_TIME; + gint64 seek_pos, dur = -1; + + if (!gst_element_query_duration (info->pipe, &fmt, &dur) || dur <= 0) { + g_printerr ("Could not query duration\n"); + return; + } + + seek_pos = gst_gdouble_to_guint64 (gst_guint64_to_gdouble (dur) * percent); + g_print ("Seeking to %" GST_TIME_FORMAT ", accurate: %d\n", + GST_TIME_ARGS (seek_pos), info->accurate); + + seek_flags = GST_SEEK_FLAG_FLUSH; + + if (info->accurate) + seek_flags |= GST_SEEK_FLAG_ACCURATE; + else + seek_flags |= GST_SEEK_FLAG_KEY_UNIT; + + if (!gst_element_seek_simple (info->pipe, GST_FORMAT_TIME, seek_flags, + seek_pos)) { + g_printerr ("Seek failed.\n"); + return; + } +} + +static void +slider_cb (GtkRange * range, AppInfo * info) +{ + gdouble val; + + val = gtk_range_get_value (range); + seek_to (info, val); +} + +static gchar * +slider_format_value_cb (GtkScale * scale, gdouble value, AppInfo * info) +{ + gchar s[64]; + + if (info->cur_pos < 0) + return g_strdup_printf ("%0.1g%%", value * 100.0); + + g_snprintf (s, 64, "%" GST_TIME_FORMAT, GST_TIME_ARGS (info->cur_pos)); + s[10] = '\0'; + return g_strdup (s); +} + +static void +accurate_toggled_cb (GtkToggleButton * toggle, AppInfo * info) +{ + info->accurate = gtk_toggle_button_get_active (toggle); +} + +static void +run_gui (const gchar * filename) +{ + GtkWidget *vbox, *hbox; + AppInfo *info; + + info = g_new0 (AppInfo, 1); + + /* create pipeline */ + if (!create_pipeline (info, filename)) + goto done; + + /* create window */ + info->win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_signal_connect (info->win, "delete-event", G_CALLBACK (gtk_main_quit), + NULL); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); + gtk_container_add (GTK_CONTAINER (info->win), vbox); + + info->img = gtk_image_new (); + gtk_box_pack_start (GTK_BOX (vbox), info->img, FALSE, FALSE, 6); + + hbox = gtk_hbox_new (FALSE, 6); + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 6); + + info->accurate_cb = gtk_check_button_new_with_label ("accurate seek " + "(might not work reliably with all demuxers)"); + gtk_box_pack_start (GTK_BOX (hbox), info->accurate_cb, FALSE, FALSE, 6); + g_signal_connect (info->accurate_cb, "toggled", + G_CALLBACK (accurate_toggled_cb), info); + + info->slider = gtk_hscale_new_with_range (0.0, 1.0, 0.001); + gtk_box_pack_start (GTK_BOX (vbox), info->slider, FALSE, FALSE, 6); + g_signal_connect (info->slider, "value-changed", + G_CALLBACK (slider_cb), info); + g_signal_connect (info->slider, "format-value", + G_CALLBACK (slider_format_value_cb), info); + + /* and go! */ + gst_element_set_state (info->pipe, GST_STATE_PAUSED); + + gtk_widget_show_all (info->win); + gtk_widget_hide (info->slider); /* hide until we're prerolled */ + gtk_main (); + +done: + + g_free (info); +} + +static gchar **filenames = NULL; + +int +main (int argc, char **argv) +{ + static const GOptionEntry test_goptions[] = { + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL}, + {NULL, '\0', 0, 0, NULL, NULL, NULL} + }; + GOptionContext *ctx; + GError *opt_err = NULL; + + if (!g_thread_supported ()) + g_thread_init (NULL); + + gtk_init (&argc, &argv); + + /* command line option parsing */ + ctx = g_option_context_new (" VIDEOFILE"); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + g_option_context_add_main_entries (ctx, test_goptions, NULL); + + if (!g_option_context_parse (ctx, &argc, &argv, &opt_err)) { + g_error ("Error parsing command line options: %s", opt_err->message); + return -1; + } + + if (filenames == NULL || filenames[0] == NULL || filenames[0][0] == '\0') { + g_printerr ("Please specify a path to a video file\n\n"); + return -1; + } + + run_gui (filenames[0]); + + g_free (filenames); + g_option_context_free (ctx); + + return 0; +}