Commit 81558d6a authored by Wim Taymans's avatar Wim Taymans
Browse files

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.
parent 58a9fd36
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>
 
Patch by: Josep Torra Valles <josep at fluendo dot com>
......@@ -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
......
......@@ -6,3 +6,4 @@ ENUM:OBJECT,OBJECT,BOXED
ENUM:OBJECT,OBJECT,OBJECT
BOXED:OBJECT,OBJECT,BOXED
BOXED:INT
OBJECT:BOXED
......@@ -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 <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
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);
......
......@@ -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)
......
......@@ -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
......
/* 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;
}
}
/* 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);
}
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);