From 81558d6a941e1070fb9ffb8e509c1292c7a15b74 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 19 Feb 2008 15:02:33 +0000 Subject: [PATCH] gst/playback/: Add screenshot conversion code from totem. Original commit message from CVS: * gst/playback/Makefile.am: * gst/playback/gstscreenshot.c: (feed_fakesrc), (save_result), (create_element), (gst_play_frame_conv_convert): * gst/playback/gstscreenshot.h: Add screenshot conversion code from totem. * gst/playback/gstplay-marshal.list: * gst/playback/gstplaybin2.c: (gst_play_marshal_BUFFER__BOXED), (gst_play_bin_class_init), (gst_play_bin_convert_frame), (gst_play_bin_get_property), (no_more_pads_cb), (activate_group): Implement frame property to get a color-unconverted snapshot. Implement convert-frame action signal to get a converted snapshot image. Configure connection speed in uridecodebin. Document some more properties. * gst/playback/gstplaysink.c: (gst_play_sink_class_init), (gen_video_chain), (gen_audio_chain), (gst_play_sink_reconfigure), (gst_play_sink_get_last_frame): * gst/playback/gstplaysink.h: Use last-buffer property of the video sink to get a video snapshot. * tests/examples/seek/seek.c: (shot_cb), (main): Add snapshot button for playbin2 and use the frame property to save the frame as a png in the current directory. --- ChangeLog | 27 ++++ gst/playback/Makefile.am | 2 + gst/playback/gstplay-marshal.list | 1 + gst/playback/gstplaybin2.c | 98 +++++++++++++++ gst/playback/gstplaysink.c | 28 ++++- gst/playback/gstplaysink.h | 2 + gst/playback/gstscreenshot.c | 202 ++++++++++++++++++++++++++++++ gst/playback/gstscreenshot.h | 31 +++++ tests/examples/seek/seek.c | 85 ++++++++++++- 9 files changed, 469 insertions(+), 7 deletions(-) create mode 100644 gst/playback/gstscreenshot.c create mode 100644 gst/playback/gstscreenshot.h diff --git a/ChangeLog b/ChangeLog index 4207101845..6957158bc7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +2008-02-19 Wim Taymans + + * gst/playback/Makefile.am: + * gst/playback/gstscreenshot.c: (feed_fakesrc), (save_result), + (create_element), (gst_play_frame_conv_convert): + * gst/playback/gstscreenshot.h: + Add screenshot conversion code from totem. + + * gst/playback/gstplay-marshal.list: + * gst/playback/gstplaybin2.c: (gst_play_marshal_BUFFER__BOXED), + (gst_play_bin_class_init), (gst_play_bin_convert_frame), + (gst_play_bin_get_property), (no_more_pads_cb), (activate_group): + Implement frame property to get a color-unconverted snapshot. + Implement convert-frame action signal to get a converted snapshot image. + Configure connection speed in uridecodebin. + Document some more properties. + + * gst/playback/gstplaysink.c: (gst_play_sink_class_init), + (gen_video_chain), (gen_audio_chain), (gst_play_sink_reconfigure), + (gst_play_sink_get_last_frame): + * gst/playback/gstplaysink.h: + Use last-buffer property of the video sink to get a video snapshot. + + * tests/examples/seek/seek.c: (shot_cb), (main): + Add snapshot button for playbin2 and use the frame property to save the + frame as a png in the current directory. + 2008-02-19 Sebastian Dröge Patch by: Josep Torra Valles diff --git a/gst/playback/Makefile.am b/gst/playback/Makefile.am index b694885f0c..2b8525c23c 100644 --- a/gst/playback/Makefile.am +++ b/gst/playback/Makefile.am @@ -17,6 +17,7 @@ libgstplaybin_la_SOURCES = \ gstplaybasebin.c \ gstplay-enum.c \ gstfactorylists.c \ + gstscreenshot.c \ gststreaminfo.c \ gststreamselector.c @@ -55,6 +56,7 @@ noinst_HEADERS = \ gststreaminfo.h \ gstfactorylists.h \ gstplay-enum.h \ + gstscreenshot.h \ gststreamselector.h noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7 diff --git a/gst/playback/gstplay-marshal.list b/gst/playback/gstplay-marshal.list index c26a9183e4..135e4ab9dc 100644 --- a/gst/playback/gstplay-marshal.list +++ b/gst/playback/gstplay-marshal.list @@ -6,3 +6,4 @@ ENUM:OBJECT,OBJECT,BOXED ENUM:OBJECT,OBJECT,OBJECT BOXED:OBJECT,OBJECT,BOXED BOXED:INT +OBJECT:BOXED diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index ff632094b1..b208d31744 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -252,6 +252,7 @@ #include "gstplay-marshal.h" #include "gstplaysink.h" #include "gstfactorylists.h" +#include "gstscreenshot.h" #include "gststreaminfo.h" #include "gststreamselector.h" @@ -338,15 +339,22 @@ struct _GstPlayBinClass { GstPipelineClass parent_class; + /* notify app that the current uri finished decoding and it is possible to + * queue a new one for gapless playback */ void (*about_to_finish) (GstPlayBin * playbin); + /* notify app that number of audio/video/text streams changed */ void (*video_changed) (GstPlayBin * playbin); void (*audio_changed) (GstPlayBin * playbin); void (*text_changed) (GstPlayBin * playbin); + /* get audio/video/text tags for a stream */ GstTagList *(*get_video_tags) (GstPlayBin * playbin, gint stream); GstTagList *(*get_audio_tags) (GstPlayBin * playbin, gint stream); GstTagList *(*get_text_tags) (GstPlayBin * playbin, gint stream); + + /* get the last video frame and convert it to the given caps */ + GstBuffer *(*convert_frame) (GstPlayBin * playbin, GstCaps * caps); }; /* props */ @@ -429,6 +437,9 @@ static GstStructure *gst_play_bin_get_audio_tags (GstPlayBin * playbin, static GstStructure *gst_play_bin_get_text_tags (GstPlayBin * playbin, gint stream); +static GstBuffer *gst_play_bin_convert_frame (GstPlayBin * playbin, + GstCaps * caps); + static gboolean setup_next_source (GstPlayBin * playbin); static GstElementClass *parent_class; @@ -441,6 +452,38 @@ GST_ELEMENT_DETAILS ("Player Bin 2", "Autoplug and play media from an uri", "Wim Taymans "); +static void +gst_play_marshal_BUFFER__BOXED (GClosure * closure, + GValue * return_value G_GNUC_UNUSED, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) +{ + typedef GstBuffer *(*GMarshalFunc_OBJECT__BOXED) (gpointer data1, + gpointer arg_1, gpointer data2); + register GMarshalFunc_OBJECT__BOXED callback; + register GCClosure *cc = (GCClosure *) closure; + register gpointer data1, data2; + GstBuffer *v_return; + + g_return_if_fail (return_value != NULL); + g_return_if_fail (n_param_values == 2); + + if (G_CCLOSURE_SWAP_DATA (closure)) { + data1 = closure->data; + data2 = g_value_peek_pointer (param_values + 0); + } else { + data1 = g_value_peek_pointer (param_values + 0); + data2 = closure->data; + } + callback = + (GMarshalFunc_OBJECT__BOXED) (marshal_data ? marshal_data : cc->callback); + + v_return = callback (data1, g_value_get_boxed (param_values + 1), data2); + + gst_value_take_buffer (return_value, v_return); +} + static GType gst_play_bin_get_type (void) { @@ -509,6 +552,12 @@ gst_play_bin_class_init (GstPlayBinClass * klass) g_param_spec_object ("source", "Source", "Source element", GST_TYPE_ELEMENT, G_PARAM_READABLE)); + + /** + * GstPlayBin:flags + * + * Control the behaviour of playbin. + */ g_object_class_install_property (gobject_klass, PROP_FLAGS, g_param_spec_flags ("flags", "Flags", "Flags to control behaviour", GST_TYPE_PLAY_FLAGS, DEFAULT_FLAGS, G_PARAM_READWRITE)); @@ -596,6 +645,13 @@ gst_play_bin_class_init (GstPlayBinClass * klass) "Mute the audio channel without changing the volume", FALSE, G_PARAM_READWRITE)); + /** + * GstPlayBin::frame + * @playbin: a #GstPlayBin + * + * Get the currently rendered or prerolled frame in the sink. + * The #GstCaps on the buffer will describe the format of the buffer. + */ g_object_class_install_property (gobject_klass, PROP_FRAME, gst_param_spec_mini_object ("frame", "Frame", "The last frame (NULL = no video available)", @@ -711,11 +767,33 @@ gst_play_bin_class_init (GstPlayBinClass * klass) G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GstPlayBinClass, get_text_tags), NULL, NULL, gst_play_marshal_BOXED__INT, GST_TYPE_TAG_LIST, 1, G_TYPE_INT); + /** + * GstPlayBin::convert-frame + * @playbin: a #GstPlayBin + * @caps: the target format of the frame + * + * Action signal to retrieve the currently playing video frame in the format + * specified by @caps. + * If @caps is %NULL, no conversion will be performed and this function is + * equivalent to the #GstPlayBin::frame property. + * + * Returns: a #GstBuffer of the current video frame converted to #caps. + * The caps on the buffer will describe the final layout of the buffer data. + * %NULL is returned when no current buffer can be retrieved or when the + * conversion failed. + */ + gst_play_bin_signals[SIGNAL_GET_TEXT_TAGS] = + g_signal_new ("convert-frame", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstPlayBinClass, convert_frame), NULL, NULL, + gst_play_marshal_BUFFER__BOXED, GST_TYPE_BUFFER, 1, GST_TYPE_CAPS); klass->get_video_tags = gst_play_bin_get_video_tags; klass->get_audio_tags = gst_play_bin_get_audio_tags; klass->get_text_tags = gst_play_bin_get_text_tags; + klass->convert_frame = gst_play_bin_convert_frame; + gst_element_class_set_details (gstelement_klass, &gst_play_bin_details); gstelement_klass->change_state = @@ -903,6 +981,22 @@ gst_play_bin_get_text_tags (GstPlayBin * playbin, gint stream) return result; } +static GstBuffer * +gst_play_bin_convert_frame (GstPlayBin * playbin, GstCaps * caps) +{ + GstBuffer *result; + + result = gst_play_sink_get_last_frame (playbin->playsink); + if (result != NULL && caps != NULL) { + GstBuffer *temp; + + temp = gst_play_frame_conv_convert (result, caps); + gst_buffer_unref (result); + result = temp; + } + return result; +} + static gboolean gst_play_bin_set_current_video_stream (GstPlayBin * playbin, gint stream) { @@ -1183,6 +1277,7 @@ gst_play_bin_get_property (GObject * object, guint prop_id, GValue * value, g_value_set_boolean (value, gst_play_sink_get_mute (playbin->playsink)); break; case PROP_FRAME: + gst_value_take_buffer (value, gst_play_bin_convert_frame (playbin, NULL)); break; case PROP_FONT_DESC: break; @@ -1568,6 +1663,9 @@ activate_group (GstPlayBin * playbin, GstSourceGroup * group) if (!uridecodebin) goto no_decodebin; + /* configure connection speed */ + g_object_set (uridecodebin, "connection-speed", playbin->connection_speed, + NULL); /* configure uri */ g_object_set (uridecodebin, "uri", group->uri, NULL); diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index 42e8f83ee9..09c2d65f1d 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -232,7 +232,7 @@ gst_play_sink_class_init (GstPlaySinkClass * klass) 0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE)); g_object_class_install_property (gobject_klass, PROP_FRAME, gst_param_spec_mini_object ("frame", "Frame", - "The last frame (NULL = no video available)", + "The last video frame (NULL = no video available)", GST_TYPE_BUFFER, G_PARAM_READABLE)); g_object_class_install_property (gobject_klass, PROP_FONT_DESC, g_param_spec_string ("subtitle-font-desc", @@ -1358,6 +1358,32 @@ gst_play_sink_get_flags (GstPlaySink * playsink) return res; } +GstBuffer * +gst_play_sink_get_last_frame (GstPlaySink * playsink) +{ + GstBuffer *result = NULL; + GstPlayVideoChain *chain; + + GST_PLAY_SINK_LOCK (playsink); + GST_DEBUG_OBJECT (playsink, "taking last frame"); + /* get the video chain if we can */ + if ((chain = (GstPlayVideoChain *) playsink->videochain)) { + GST_DEBUG_OBJECT (playsink, "found video chain"); + /* see if the chain is active */ + if (chain->chain.activated && chain->sink) { + GST_DEBUG_OBJECT (playsink, "video chain active and has a sink"); + /* get the frame property if we can */ + if (g_object_class_find_property (G_OBJECT_GET_CLASS (chain->sink), + "last-buffer")) { + GST_DEBUG_OBJECT (playsink, "getting last-buffer property"); + g_object_get (chain->sink, "last-buffer", &result, NULL); + } + } + } + GST_PLAY_SINK_UNLOCK (playsink); + + return result; +} GstPad * gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type) diff --git a/gst/playback/gstplaysink.h b/gst/playback/gstplaysink.h index b585de554d..2ef22a53bb 100644 --- a/gst/playback/gstplaysink.h +++ b/gst/playback/gstplaysink.h @@ -78,6 +78,8 @@ gboolean gst_play_sink_get_mute (GstPlaySink *playsink); gboolean gst_play_sink_set_flags (GstPlaySink * playsink, GstPlayFlags flags); GstPlayFlags gst_play_sink_get_flags (GstPlaySink * playsink); +GstBuffer * gst_play_sink_get_last_frame (GstPlaySink * playsink); + gboolean gst_play_sink_reconfigure (GstPlaySink * playsink); G_END_DECLS diff --git a/gst/playback/gstscreenshot.c b/gst/playback/gstscreenshot.c new file mode 100644 index 0000000000..a9d9c1f3c0 --- /dev/null +++ b/gst/playback/gstscreenshot.c @@ -0,0 +1,202 @@ +/* Small helper element for format conversion + * (c) 2004 Ronald Bultje + * + * 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 + +#include "gstscreenshot.h" + +static void +feed_fakesrc (GstElement * src, GstBuffer * buf, GstPad * pad, gpointer data) +{ + GstBuffer *in_buf = GST_BUFFER (data); + + gst_buffer_set_caps (buf, GST_BUFFER_CAPS (in_buf)); + + memcpy (GST_BUFFER_DATA (buf), GST_BUFFER_DATA (in_buf), + GST_BUFFER_SIZE (in_buf)); + + GST_BUFFER_SIZE (buf) = GST_BUFFER_SIZE (in_buf); + + GST_DEBUG ("feeding buffer %p, size %u, caps %" GST_PTR_FORMAT, + buf, GST_BUFFER_SIZE (buf), GST_BUFFER_CAPS (buf)); +} + +static void +save_result (GstElement * sink, GstBuffer * buf, GstPad * pad, gpointer data) +{ + GstBuffer **p_buf = (GstBuffer **) data; + + *p_buf = gst_buffer_ref (buf); + + GST_DEBUG ("received converted buffer %p with caps %" GST_PTR_FORMAT, + *p_buf, GST_BUFFER_CAPS (*p_buf)); +} + +static gboolean +create_element (const gchar * factory_name, GstElement ** element, + GError ** err) +{ + *element = gst_element_factory_make (factory_name, NULL); + if (*element) + return TRUE; + + if (err && *err == NULL) { + *err = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_MISSING_PLUGIN, + "cannot create element '%s' - please check your GStreamer installation", + factory_name); + } + + return FALSE; +} + +/* takes ownership of the input buffer */ +GstBuffer * +gst_play_frame_conv_convert (GstBuffer * buf, GstCaps * to_caps) +{ + GstElement *src, *csp, *filter1, *vscale, *filter2, *sink, *pipeline; + GstMessage *msg; + GstBuffer *result = NULL; + GError *error = NULL; + GstBus *bus; + GstCaps *to_caps_no_par; + + g_return_val_if_fail (GST_BUFFER_CAPS (buf) != NULL, NULL); + + /* videoscale is here to correct for the pixel-aspect-ratio for us */ + GST_DEBUG ("creating elements"); + if (!create_element ("fakesrc", &src, &error) || + !create_element ("ffmpegcolorspace", &csp, &error) || + !create_element ("videoscale", &vscale, &error) || + !create_element ("capsfilter", &filter1, &error) || + !create_element ("capsfilter", &filter2, &error) || + !create_element ("fakesink", &sink, &error)) + goto no_elements; + + pipeline = gst_pipeline_new ("screenshot-pipeline"); + if (pipeline == NULL) + goto no_pipeline; + + GST_DEBUG ("adding elements"); + gst_bin_add_many (GST_BIN (pipeline), src, csp, filter1, vscale, filter2, + sink, NULL); + + g_signal_connect (src, "handoff", G_CALLBACK (feed_fakesrc), buf); + + /* set to 'fixed' sizetype */ + g_object_set (src, "sizemax", GST_BUFFER_SIZE (buf), "sizetype", 2, + "num-buffers", 1, "signal-handoffs", TRUE, NULL); + + /* adding this superfluous capsfilter makes linking cheaper */ + to_caps_no_par = gst_caps_copy (to_caps); + gst_structure_remove_field (gst_caps_get_structure (to_caps_no_par, 0), + "pixel-aspect-ratio"); + g_object_set (filter1, "caps", to_caps_no_par, NULL); + gst_caps_unref (to_caps_no_par); + + g_object_set (filter2, "caps", to_caps, NULL); + + g_signal_connect (sink, "handoff", G_CALLBACK (save_result), &result); + + g_object_set (sink, "preroll-queue-len", 1, "signal-handoffs", TRUE, NULL); + + /* FIXME: linking is still way too expensive, profile this properly */ + GST_DEBUG ("linking src->csp"); + if (!gst_element_link_pads (src, "src", csp, "sink")) + return NULL; + + GST_DEBUG ("linking csp->filter1"); + if (!gst_element_link_pads (csp, "src", filter1, "sink")) + return NULL; + + GST_DEBUG ("linking filter1->vscale"); + if (!gst_element_link_pads (filter1, "src", vscale, "sink")) + return NULL; + + GST_DEBUG ("linking vscale->capsfilter"); + if (!gst_element_link_pads (vscale, "src", filter2, "sink")) + return NULL; + + GST_DEBUG ("linking capsfilter->sink"); + if (!gst_element_link_pads (filter2, "src", sink, "sink")) + return NULL; + + GST_DEBUG ("running conversion pipeline"); + gst_element_set_state (pipeline, GST_STATE_PLAYING); + + bus = gst_element_get_bus (pipeline); + msg = + gst_bus_poll (bus, GST_MESSAGE_ERROR | GST_MESSAGE_EOS, 25 * GST_SECOND); + + if (msg) { + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_EOS:{ + if (result) { + GST_DEBUG ("conversion successful: result = %p", result); + } else { + GST_WARNING ("EOS but no result frame?!"); + } + break; + } + case GST_MESSAGE_ERROR:{ + gchar *dbg = NULL; + + gst_message_parse_error (msg, &error, &dbg); + if (error) { + g_warning ("Could not take screenshot: %s", error->message); + GST_DEBUG ("%s [debug: %s]", error->message, GST_STR_NULL (dbg)); + g_error_free (error); + } else { + g_warning ("Could not take screenshot (and NULL error!)"); + } + g_free (dbg); + result = NULL; + break; + } + default:{ + g_return_val_if_reached (NULL); + } + } + } else { + g_warning ("Could not take screenshot: %s", "timeout during conversion"); + result = NULL; + } + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_object_unref (pipeline); + + return result; + + /* ERRORS */ +no_elements: + { + g_warning ("Could not take screenshot: %s", error->message); + g_error_free (error); + return NULL; + } +no_pipeline: + { + g_warning ("Could not take screenshot: %s", "no pipeline (unknown error)"); + return NULL; + } +} diff --git a/gst/playback/gstscreenshot.h b/gst/playback/gstscreenshot.h new file mode 100644 index 0000000000..a7f5f09103 --- /dev/null +++ b/gst/playback/gstscreenshot.h @@ -0,0 +1,31 @@ +/* Small helper element for format conversion + * (c) 2004 Ronald Bultje + * + * 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. + */ + +#ifndef __GST_PLAY_FRAME_CONV_H__ +#define __GST_PLAY_FRAME_CONV_H__ + +#include + +G_BEGIN_DECLS + +GstBuffer * gst_play_frame_conv_convert (GstBuffer *buf, GstCaps *to); + +G_END_DECLS + +#endif /* __GST_PLAY_FRAME_CONV_H__ */ diff --git a/tests/examples/seek/seek.c b/tests/examples/seek/seek.c index 11ed30226a..6cd4b9ded4 100644 --- a/tests/examples/seek/seek.c +++ b/tests/examples/seek/seek.c @@ -1625,6 +1625,68 @@ volume_spinbutton_changed_cb (GtkSpinButton * button, GstPipeline * pipeline) g_object_set (pipeline, "volume", volume, NULL); } +static void +shot_cb (GtkButton * button, gpointer data) +{ + GstBuffer *buffer; + GstCaps *caps; + + /* convert to our desired format (RGB24) */ + caps = gst_caps_new_simple ("video/x-raw-rgb", + "bpp", G_TYPE_INT, 24, "depth", G_TYPE_INT, 24, + /* Note: we don't ask for a specific width/height here, so that + * videoscale can adjust dimensions from a non-1/1 pixel aspect + * ratio to a 1/1 pixel-aspect-ratio */ + "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + "endianness", G_TYPE_INT, G_BIG_ENDIAN, + "red_mask", G_TYPE_INT, 0xff0000, + "green_mask", G_TYPE_INT, 0x00ff00, + "blue_mask", G_TYPE_INT, 0x0000ff, NULL); + + g_signal_emit_by_name (pipeline, "convert-frame", caps, &buffer); + gst_caps_unref (caps); + + if (buffer) { + GstCaps *caps; + GstStructure *s; + gboolean res; + gint width, height; + GdkPixbuf *pixbuf; + GError *error = NULL; + + /* get the snapshot buffer format now. We set the caps on the appsink so + * that it can only be an rgb buffer. The only thing we have not specified + * on the caps is the height, which is dependant on the pixel-aspect-ratio + * of the source material */ + caps = GST_BUFFER_CAPS (buffer); + if (!caps) { + g_warning ("could not get snapshot format\n"); + goto done; + } + s = gst_caps_get_structure (caps, 0); + + /* we need to get the final caps on the buffer to get the size */ + res = gst_structure_get_int (s, "width", &width); + res |= gst_structure_get_int (s, "height", &height); + if (!res) { + g_warning ("could not get snapshot dimension\n"); + goto done; + } + + /* create pixmap from buffer and save, gstreamer video buffers have a stride + * that is rounded up to the nearest multiple of 4 */ + pixbuf = gdk_pixbuf_new_from_data (GST_BUFFER_DATA (buffer), + GDK_COLORSPACE_RGB, FALSE, 8, width, height, + GST_ROUND_UP_4 (width * 3), NULL, NULL); + + /* save the pixbuf */ + gdk_pixbuf_save (pixbuf, "snapshot.png", "png", &error, NULL); + + done: + gst_buffer_unref (buffer); + } +} + static void message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline) { @@ -1757,8 +1819,8 @@ print_usage (int argc, char **argv) int main (int argc, char **argv) { - GtkWidget *window, *hbox, *vbox, *panel, *boxes, *flagtable; - GtkWidget *play_button, *pause_button, *stop_button; + GtkWidget *window, *hbox, *vbox, *panel, *boxes, *flagtable, *boxes2; + GtkWidget *play_button, *pause_button, *stop_button, *shot_button; GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox, *flush_checkbox; GtkWidget *scrub_checkbox, *play_scrub_checkbox, *rate_spinbutton; GtkWidget *rate_label; @@ -1874,7 +1936,6 @@ main (int argc, char **argv) if (pipeline_type == 16) { /* the playbin2 panel controls for the video/audio/subtitle tracks */ panel = gtk_hbox_new (FALSE, 0); - boxes = gtk_hbox_new (FALSE, 0); video_combo = gtk_combo_box_new_text (); audio_combo = gtk_combo_box_new_text (); text_combo = gtk_combo_box_new_text (); @@ -1890,12 +1951,14 @@ main (int argc, char **argv) G_CALLBACK (audio_combo_cb), pipeline); g_signal_connect (G_OBJECT (text_combo), "changed", G_CALLBACK (text_combo_cb), pipeline); + /* playbin2 panel for flag checkboxes and volume/mute */ + boxes = gtk_hbox_new (FALSE, 0); vis_checkbox = gtk_check_button_new_with_label ("Vis"); video_checkbox = gtk_check_button_new_with_label ("Video"); audio_checkbox = gtk_check_button_new_with_label ("Audio"); text_checkbox = gtk_check_button_new_with_label ("Text"); mute_checkbox = gtk_check_button_new_with_label ("Mute"); - volume_spinbutton = gtk_spin_button_new_with_range (0, 5.0, 0.1); + volume_spinbutton = gtk_spin_button_new_with_range (0, 10.0, 0.1); gtk_spin_button_set_value (GTK_SPIN_BUTTON (volume_spinbutton), 1.0); gtk_box_pack_start (GTK_BOX (boxes), vis_checkbox, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (boxes), audio_checkbox, TRUE, TRUE, 2); @@ -1920,8 +1983,17 @@ main (int argc, char **argv) G_CALLBACK (mute_toggle_cb), pipeline); g_signal_connect (G_OBJECT (volume_spinbutton), "value_changed", G_CALLBACK (volume_spinbutton_changed_cb), pipeline); + /* playbin2 panel for snapshot */ + boxes2 = gtk_hbox_new (FALSE, 0); + shot_button = gtk_button_new_from_stock (GTK_STOCK_SAVE); + gtk_tooltips_set_tip (tips, shot_button, + "save a screenshot .png in the current directory", NULL); + gtk_box_pack_start (GTK_BOX (boxes2), shot_button, TRUE, TRUE, 2); + g_signal_connect (G_OBJECT (shot_button), "clicked", G_CALLBACK (shot_cb), + pipeline); + } else { - panel = boxes = NULL; + panel = boxes = boxes2 = NULL; } /* do the packing stuff ... */ @@ -1943,9 +2015,10 @@ main (int argc, char **argv) gtk_table_attach_defaults (GTK_TABLE (flagtable), rate_label, 3, 4, 0, 1); gtk_table_attach_defaults (GTK_TABLE (flagtable), rate_spinbutton, 3, 4, 1, 2); - if (panel && boxes) { + if (panel && boxes && boxes2) { gtk_box_pack_start (GTK_BOX (vbox), panel, TRUE, TRUE, 2); gtk_box_pack_start (GTK_BOX (vbox), boxes, TRUE, TRUE, 2); + gtk_box_pack_start (GTK_BOX (vbox), boxes2, TRUE, TRUE, 2); } gtk_box_pack_start (GTK_BOX (vbox), hscale, TRUE, TRUE, 2);