mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 04:01:08 +00:00
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.
This commit is contained in:
parent
58a9fd3622
commit
81558d6a94
9 changed files with 469 additions and 7 deletions
27
ChangeLog
27
ChangeLog
|
@ -1,3 +1,30 @@
|
||||||
|
2008-02-19 Wim Taymans <wim.taymans@collabora.co.uk>
|
||||||
|
|
||||||
|
* 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 <slomo@circular-chaos.org>
|
2008-02-19 Sebastian Dröge <slomo@circular-chaos.org>
|
||||||
|
|
||||||
Patch by: Josep Torra Valles <josep at fluendo dot com>
|
Patch by: Josep Torra Valles <josep at fluendo dot com>
|
||||||
|
|
|
@ -17,6 +17,7 @@ libgstplaybin_la_SOURCES = \
|
||||||
gstplaybasebin.c \
|
gstplaybasebin.c \
|
||||||
gstplay-enum.c \
|
gstplay-enum.c \
|
||||||
gstfactorylists.c \
|
gstfactorylists.c \
|
||||||
|
gstscreenshot.c \
|
||||||
gststreaminfo.c \
|
gststreaminfo.c \
|
||||||
gststreamselector.c
|
gststreamselector.c
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ noinst_HEADERS = \
|
||||||
gststreaminfo.h \
|
gststreaminfo.h \
|
||||||
gstfactorylists.h \
|
gstfactorylists.h \
|
||||||
gstplay-enum.h \
|
gstplay-enum.h \
|
||||||
|
gstscreenshot.h \
|
||||||
gststreamselector.h
|
gststreamselector.h
|
||||||
|
|
||||||
noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7
|
noinst_PROGRAMS = test decodetest test2 test3 test4 test5 test6 test7
|
||||||
|
|
|
@ -6,3 +6,4 @@ ENUM:OBJECT,OBJECT,BOXED
|
||||||
ENUM:OBJECT,OBJECT,OBJECT
|
ENUM:OBJECT,OBJECT,OBJECT
|
||||||
BOXED:OBJECT,OBJECT,BOXED
|
BOXED:OBJECT,OBJECT,BOXED
|
||||||
BOXED:INT
|
BOXED:INT
|
||||||
|
OBJECT:BOXED
|
||||||
|
|
|
@ -252,6 +252,7 @@
|
||||||
#include "gstplay-marshal.h"
|
#include "gstplay-marshal.h"
|
||||||
#include "gstplaysink.h"
|
#include "gstplaysink.h"
|
||||||
#include "gstfactorylists.h"
|
#include "gstfactorylists.h"
|
||||||
|
#include "gstscreenshot.h"
|
||||||
#include "gststreaminfo.h"
|
#include "gststreaminfo.h"
|
||||||
#include "gststreamselector.h"
|
#include "gststreamselector.h"
|
||||||
|
|
||||||
|
@ -338,15 +339,22 @@ struct _GstPlayBinClass
|
||||||
{
|
{
|
||||||
GstPipelineClass parent_class;
|
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);
|
void (*about_to_finish) (GstPlayBin * playbin);
|
||||||
|
|
||||||
|
/* notify app that number of audio/video/text streams changed */
|
||||||
void (*video_changed) (GstPlayBin * playbin);
|
void (*video_changed) (GstPlayBin * playbin);
|
||||||
void (*audio_changed) (GstPlayBin * playbin);
|
void (*audio_changed) (GstPlayBin * playbin);
|
||||||
void (*text_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_video_tags) (GstPlayBin * playbin, gint stream);
|
||||||
GstTagList *(*get_audio_tags) (GstPlayBin * playbin, gint stream);
|
GstTagList *(*get_audio_tags) (GstPlayBin * playbin, gint stream);
|
||||||
GstTagList *(*get_text_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 */
|
/* 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,
|
static GstStructure *gst_play_bin_get_text_tags (GstPlayBin * playbin,
|
||||||
gint stream);
|
gint stream);
|
||||||
|
|
||||||
|
static GstBuffer *gst_play_bin_convert_frame (GstPlayBin * playbin,
|
||||||
|
GstCaps * caps);
|
||||||
|
|
||||||
static gboolean setup_next_source (GstPlayBin * playbin);
|
static gboolean setup_next_source (GstPlayBin * playbin);
|
||||||
|
|
||||||
static GstElementClass *parent_class;
|
static GstElementClass *parent_class;
|
||||||
|
@ -441,6 +452,38 @@ GST_ELEMENT_DETAILS ("Player Bin 2",
|
||||||
"Autoplug and play media from an uri",
|
"Autoplug and play media from an uri",
|
||||||
"Wim Taymans <wim.taymans@gmail.com>");
|
"Wim Taymans <wim.taymans@gmail.com>");
|
||||||
|
|
||||||
|
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
|
static GType
|
||||||
gst_play_bin_get_type (void)
|
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",
|
g_param_spec_object ("source", "Source", "Source element",
|
||||||
GST_TYPE_ELEMENT, G_PARAM_READABLE));
|
GST_TYPE_ELEMENT, G_PARAM_READABLE));
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstPlayBin:flags
|
||||||
|
*
|
||||||
|
* Control the behaviour of playbin.
|
||||||
|
*/
|
||||||
g_object_class_install_property (gobject_klass, PROP_FLAGS,
|
g_object_class_install_property (gobject_klass, PROP_FLAGS,
|
||||||
g_param_spec_flags ("flags", "Flags", "Flags to control behaviour",
|
g_param_spec_flags ("flags", "Flags", "Flags to control behaviour",
|
||||||
GST_TYPE_PLAY_FLAGS, DEFAULT_FLAGS, G_PARAM_READWRITE));
|
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,
|
"Mute the audio channel without changing the volume", FALSE,
|
||||||
G_PARAM_READWRITE));
|
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,
|
g_object_class_install_property (gobject_klass, PROP_FRAME,
|
||||||
gst_param_spec_mini_object ("frame", "Frame",
|
gst_param_spec_mini_object ("frame", "Frame",
|
||||||
"The last frame (NULL = no video available)",
|
"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_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
||||||
G_STRUCT_OFFSET (GstPlayBinClass, get_text_tags), NULL, NULL,
|
G_STRUCT_OFFSET (GstPlayBinClass, get_text_tags), NULL, NULL,
|
||||||
gst_play_marshal_BOXED__INT, GST_TYPE_TAG_LIST, 1, G_TYPE_INT);
|
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_video_tags = gst_play_bin_get_video_tags;
|
||||||
klass->get_audio_tags = gst_play_bin_get_audio_tags;
|
klass->get_audio_tags = gst_play_bin_get_audio_tags;
|
||||||
klass->get_text_tags = gst_play_bin_get_text_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);
|
gst_element_class_set_details (gstelement_klass, &gst_play_bin_details);
|
||||||
|
|
||||||
gstelement_klass->change_state =
|
gstelement_klass->change_state =
|
||||||
|
@ -903,6 +981,22 @@ gst_play_bin_get_text_tags (GstPlayBin * playbin, gint stream)
|
||||||
return result;
|
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
|
static gboolean
|
||||||
gst_play_bin_set_current_video_stream (GstPlayBin * playbin, gint stream)
|
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));
|
g_value_set_boolean (value, gst_play_sink_get_mute (playbin->playsink));
|
||||||
break;
|
break;
|
||||||
case PROP_FRAME:
|
case PROP_FRAME:
|
||||||
|
gst_value_take_buffer (value, gst_play_bin_convert_frame (playbin, NULL));
|
||||||
break;
|
break;
|
||||||
case PROP_FONT_DESC:
|
case PROP_FONT_DESC:
|
||||||
break;
|
break;
|
||||||
|
@ -1568,6 +1663,9 @@ activate_group (GstPlayBin * playbin, GstSourceGroup * group)
|
||||||
if (!uridecodebin)
|
if (!uridecodebin)
|
||||||
goto no_decodebin;
|
goto no_decodebin;
|
||||||
|
|
||||||
|
/* configure connection speed */
|
||||||
|
g_object_set (uridecodebin, "connection-speed", playbin->connection_speed,
|
||||||
|
NULL);
|
||||||
/* configure uri */
|
/* configure uri */
|
||||||
g_object_set (uridecodebin, "uri", group->uri, NULL);
|
g_object_set (uridecodebin, "uri", group->uri, NULL);
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,7 @@ gst_play_sink_class_init (GstPlaySinkClass * klass)
|
||||||
0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE));
|
0.0, VOLUME_MAX_DOUBLE, 1.0, G_PARAM_READWRITE));
|
||||||
g_object_class_install_property (gobject_klass, PROP_FRAME,
|
g_object_class_install_property (gobject_klass, PROP_FRAME,
|
||||||
gst_param_spec_mini_object ("frame", "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));
|
GST_TYPE_BUFFER, G_PARAM_READABLE));
|
||||||
g_object_class_install_property (gobject_klass, PROP_FONT_DESC,
|
g_object_class_install_property (gobject_klass, PROP_FONT_DESC,
|
||||||
g_param_spec_string ("subtitle-font-desc",
|
g_param_spec_string ("subtitle-font-desc",
|
||||||
|
@ -1358,6 +1358,32 @@ gst_play_sink_get_flags (GstPlaySink * playsink)
|
||||||
return res;
|
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 *
|
GstPad *
|
||||||
gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
|
gst_play_sink_request_pad (GstPlaySink * playsink, GstPlaySinkType type)
|
||||||
|
|
|
@ -78,6 +78,8 @@ gboolean gst_play_sink_get_mute (GstPlaySink *playsink);
|
||||||
gboolean gst_play_sink_set_flags (GstPlaySink * playsink, GstPlayFlags flags);
|
gboolean gst_play_sink_set_flags (GstPlaySink * playsink, GstPlayFlags flags);
|
||||||
GstPlayFlags gst_play_sink_get_flags (GstPlaySink * playsink);
|
GstPlayFlags gst_play_sink_get_flags (GstPlaySink * playsink);
|
||||||
|
|
||||||
|
GstBuffer * gst_play_sink_get_last_frame (GstPlaySink * playsink);
|
||||||
|
|
||||||
gboolean gst_play_sink_reconfigure (GstPlaySink * playsink);
|
gboolean gst_play_sink_reconfigure (GstPlaySink * playsink);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
202
gst/playback/gstscreenshot.c
Normal file
202
gst/playback/gstscreenshot.c
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
/* Small helper element for format conversion
|
||||||
|
* (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
*
|
||||||
|
* 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 <gst/gst.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
31
gst/playback/gstscreenshot.h
Normal file
31
gst/playback/gstscreenshot.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/* Small helper element for format conversion
|
||||||
|
* (c) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
*
|
||||||
|
* 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 <gst/gst.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
GstBuffer * gst_play_frame_conv_convert (GstBuffer *buf, GstCaps *to);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif /* __GST_PLAY_FRAME_CONV_H__ */
|
|
@ -1625,6 +1625,68 @@ volume_spinbutton_changed_cb (GtkSpinButton * button, GstPipeline * pipeline)
|
||||||
g_object_set (pipeline, "volume", volume, NULL);
|
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
|
static void
|
||||||
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
message_received (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
|
||||||
{
|
{
|
||||||
|
@ -1757,8 +1819,8 @@ print_usage (int argc, char **argv)
|
||||||
int
|
int
|
||||||
main (int argc, char **argv)
|
main (int argc, char **argv)
|
||||||
{
|
{
|
||||||
GtkWidget *window, *hbox, *vbox, *panel, *boxes, *flagtable;
|
GtkWidget *window, *hbox, *vbox, *panel, *boxes, *flagtable, *boxes2;
|
||||||
GtkWidget *play_button, *pause_button, *stop_button;
|
GtkWidget *play_button, *pause_button, *stop_button, *shot_button;
|
||||||
GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox, *flush_checkbox;
|
GtkWidget *accurate_checkbox, *key_checkbox, *loop_checkbox, *flush_checkbox;
|
||||||
GtkWidget *scrub_checkbox, *play_scrub_checkbox, *rate_spinbutton;
|
GtkWidget *scrub_checkbox, *play_scrub_checkbox, *rate_spinbutton;
|
||||||
GtkWidget *rate_label;
|
GtkWidget *rate_label;
|
||||||
|
@ -1874,7 +1936,6 @@ main (int argc, char **argv)
|
||||||
if (pipeline_type == 16) {
|
if (pipeline_type == 16) {
|
||||||
/* the playbin2 panel controls for the video/audio/subtitle tracks */
|
/* the playbin2 panel controls for the video/audio/subtitle tracks */
|
||||||
panel = gtk_hbox_new (FALSE, 0);
|
panel = gtk_hbox_new (FALSE, 0);
|
||||||
boxes = gtk_hbox_new (FALSE, 0);
|
|
||||||
video_combo = gtk_combo_box_new_text ();
|
video_combo = gtk_combo_box_new_text ();
|
||||||
audio_combo = gtk_combo_box_new_text ();
|
audio_combo = gtk_combo_box_new_text ();
|
||||||
text_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_CALLBACK (audio_combo_cb), pipeline);
|
||||||
g_signal_connect (G_OBJECT (text_combo), "changed",
|
g_signal_connect (G_OBJECT (text_combo), "changed",
|
||||||
G_CALLBACK (text_combo_cb), pipeline);
|
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");
|
vis_checkbox = gtk_check_button_new_with_label ("Vis");
|
||||||
video_checkbox = gtk_check_button_new_with_label ("Video");
|
video_checkbox = gtk_check_button_new_with_label ("Video");
|
||||||
audio_checkbox = gtk_check_button_new_with_label ("Audio");
|
audio_checkbox = gtk_check_button_new_with_label ("Audio");
|
||||||
text_checkbox = gtk_check_button_new_with_label ("Text");
|
text_checkbox = gtk_check_button_new_with_label ("Text");
|
||||||
mute_checkbox = gtk_check_button_new_with_label ("Mute");
|
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_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), vis_checkbox, TRUE, TRUE, 2);
|
||||||
gtk_box_pack_start (GTK_BOX (boxes), audio_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_CALLBACK (mute_toggle_cb), pipeline);
|
||||||
g_signal_connect (G_OBJECT (volume_spinbutton), "value_changed",
|
g_signal_connect (G_OBJECT (volume_spinbutton), "value_changed",
|
||||||
G_CALLBACK (volume_spinbutton_changed_cb), pipeline);
|
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 {
|
} else {
|
||||||
panel = boxes = NULL;
|
panel = boxes = boxes2 = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* do the packing stuff ... */
|
/* 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_label, 3, 4, 0, 1);
|
||||||
gtk_table_attach_defaults (GTK_TABLE (flagtable), rate_spinbutton, 3, 4, 1,
|
gtk_table_attach_defaults (GTK_TABLE (flagtable), rate_spinbutton, 3, 4, 1,
|
||||||
2);
|
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), panel, TRUE, TRUE, 2);
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), boxes, 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);
|
gtk_box_pack_start (GTK_BOX (vbox), hscale, TRUE, TRUE, 2);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue