summaryrefslogtreecommitdiff
path: root/clutter-gst/clutter-gst-camera.c
diff options
context:
space:
mode:
authorLionel Landwerlin <llandwerlin@gmail.com>2013-02-12 18:07:27 +0000
committerLionel Landwerlin <llandwerlin@gmail.com>2013-03-18 16:24:19 +0000
commit43c904845e0e804b48f1dc5d800ad097c5d938f0 (patch)
tree9f5f324f680a892d1064c46084a8952ac40f8067 /clutter-gst/clutter-gst-camera.c
parent9e4ca9594d72592ce370fef7c4365d7a52f32e1b (diff)
downloadclutter-gst-43c904845e0e804b48f1dc5d800ad097c5d938f0.tar.gz
Break API and concepts around VideoTexture
Diffstat (limited to 'clutter-gst/clutter-gst-camera.c')
-rw-r--r--clutter-gst/clutter-gst-camera.c1846
1 files changed, 1846 insertions, 0 deletions
diff --git a/clutter-gst/clutter-gst-camera.c b/clutter-gst/clutter-gst-camera.c
new file mode 100644
index 0000000..43a95f2
--- /dev/null
+++ b/clutter-gst/clutter-gst-camera.c
@@ -0,0 +1,1846 @@
+/*
+ * Clutter-GStreamer.
+ *
+ * GStreamer integration library for Clutter.
+ *
+ * clutter-gst-camera.c - a GStreamer pipeline to display/manipulate a
+ * camera stream.
+ *
+ * Authored By Andre Moreira Magalhaes <andre.magalhaes@collabora.co.uk>
+ *
+ * Copyright (C) 2012 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ */
+
+/**
+ * SECTION:clutter-gst-camera
+ * @short_description: A player of camera streams.
+ *
+ * #ClutterGstCamera implements the #ClutterGstPlayer interface and
+ * plays camera streams.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+#include <gst/base/gstbasesink.h>
+#include <gst/video/video.h>
+#ifdef HAVE_GUDEV
+#include <gudev/gudev.h>
+#endif
+
+#include "clutter-gst-camera.h"
+#include "clutter-gst-debug.h"
+#include "clutter-gst-enum-types.h"
+#include "clutter-gst-marshal.h"
+#include "clutter-gst-player.h"
+#include "clutter-gst-private.h"
+
+static const gchar *supported_media_types[] = {
+ "video/x-raw",
+ NULL
+};
+
+struct _ClutterGstCameraPrivate
+{
+ GPtrArray *camera_devices;
+ ClutterGstCameraDevice *camera_device;
+
+ GstBus *bus;
+ GstElement *camerabin;
+ GstElement *camera_source;
+
+ /* video filter */
+ GstElement *video_filter_bin;
+ GstElement *identity;
+ GstElement *valve;
+ GstElement *custom_filter;
+ GstElement *gamma;
+ GstElement *pre_colorspace;
+ GstElement *color_balance;
+ GstElement *post_colorspace;
+
+ gboolean is_idle;
+ gboolean is_recording;
+ gchar *photo_filename;
+};
+
+enum
+{
+ CAPTURE_MODE_IMAGE = 1,
+ CAPTURE_MODE_VIDEO
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_IDLE,
+ PROP_PLAYING,
+ PROP_AUDIO_VOLUME
+};
+
+enum
+{
+ READY_FOR_CAPTURE,
+ PHOTO_SAVED,
+ PHOTO_TAKEN,
+ VIDEO_SAVED,
+ LAST_SIGNAL
+};
+
+static int camera_signals[LAST_SIGNAL] = { 0 };
+
+static void player_iface_init (ClutterGstPlayerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (ClutterGstCamera, clutter_gst_camera, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (CLUTTER_GST_TYPE_PLAYER, player_iface_init));
+
+/*
+ * ClutterGstPlayer implementation
+ */
+
+static GstElement *
+clutter_gst_camera_get_pipeline (ClutterGstPlayer *player)
+{
+ ClutterGstCameraPrivate *priv = CLUTTER_GST_CAMERA (player)->priv;
+
+ return priv->camerabin;
+}
+
+static gboolean
+clutter_gst_camera_get_idle (ClutterGstPlayer *player)
+{
+ ClutterGstCameraPrivate *priv = CLUTTER_GST_CAMERA (player)->priv;
+
+ return priv->is_idle;
+}
+
+static gdouble
+clutter_gst_camera_get_audio_volume (ClutterGstPlayer *player)
+{
+ return 0;
+}
+
+
+static void
+clutter_gst_camera_set_audio_volume (ClutterGstPlayer *player,
+ gdouble volume)
+{
+}
+
+static gboolean
+clutter_gst_camera_get_playing (ClutterGstPlayer *player)
+{
+ ClutterGstCameraPrivate *priv = CLUTTER_GST_CAMERA (player)->priv;
+ GstState state, pending;
+ gboolean playing;
+
+ if (!priv->camerabin)
+ return FALSE;
+
+ gst_element_get_state (priv->camerabin, &state, &pending, 0);
+
+ if (pending)
+ playing = (pending == GST_STATE_PLAYING);
+ else
+ playing = (state == GST_STATE_PLAYING);
+
+ return playing;
+}
+
+static void
+clutter_gst_camera_set_playing (ClutterGstPlayer *player,
+ gboolean playing)
+{
+ ClutterGstCameraPrivate *priv = CLUTTER_GST_CAMERA (player)->priv;
+ GstState target_state;
+
+ if (!priv->camerabin)
+ return;
+
+ target_state = playing ? GST_STATE_PLAYING : GST_STATE_NULL;
+
+ gst_element_set_state (priv->camerabin, target_state);
+}
+
+static void
+player_iface_init (ClutterGstPlayerIface *iface)
+{
+ iface->get_pipeline = clutter_gst_camera_get_pipeline;
+ iface->get_idle = clutter_gst_camera_get_idle;
+
+ iface->get_audio_volume = clutter_gst_camera_get_audio_volume;
+ iface->set_audio_volume = clutter_gst_camera_set_audio_volume;
+
+ iface->get_playing = clutter_gst_camera_get_playing;
+ iface->set_playing = clutter_gst_camera_set_playing;
+}
+
+/*
+ * GObject implementation
+ */
+
+static void
+clutter_gst_camera_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_IDLE:
+ g_value_set_boolean (value,
+ clutter_gst_camera_get_idle (CLUTTER_GST_PLAYER (object)));
+ break;
+
+ case PROP_PLAYING:
+ g_value_set_boolean (value,
+ clutter_gst_camera_get_playing (CLUTTER_GST_PLAYER (object)));
+ break;
+
+ case PROP_AUDIO_VOLUME:
+ g_value_set_double (value,
+ clutter_gst_camera_get_audio_volume (CLUTTER_GST_PLAYER (object)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+clutter_gst_camera_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ case PROP_PLAYING:
+ clutter_gst_camera_set_playing (CLUTTER_GST_PLAYER (object),
+ g_value_get_boolean (value));
+ break;
+
+ case PROP_AUDIO_VOLUME:
+ clutter_gst_camera_set_audio_volume (CLUTTER_GST_PLAYER (object),
+ g_value_get_double (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+clutter_gst_camera_dispose (GObject *object)
+{
+ ClutterGstCamera *self = CLUTTER_GST_CAMERA (object);
+ ClutterGstCameraPrivate *priv = self->priv;
+
+ g_free (priv->photo_filename);
+ priv->photo_filename = NULL;
+
+ if (priv->camera_devices)
+ {
+ g_ptr_array_unref (priv->camera_devices);
+ priv->camera_devices = NULL;
+ }
+
+ if (priv->bus)
+ {
+ gst_object_unref (priv->bus);
+ priv->bus = NULL;
+ }
+
+ if (priv->camerabin)
+ {
+ gst_element_set_state (priv->camerabin, GST_STATE_NULL);
+ gst_object_unref (priv->camerabin);
+ priv->camerabin = NULL;
+ }
+
+ G_OBJECT_CLASS (clutter_gst_camera_parent_class)->dispose (object);
+}
+
+static void
+clutter_gst_camera_class_init (ClutterGstCameraClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ ClutterGstActorClass *gst_actor_class = CLUTTER_GST_ACTOR_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (ClutterGstCameraPrivate));
+
+ object_class->get_property = clutter_gst_camera_get_property;
+ object_class->set_property = clutter_gst_camera_set_property;
+ object_class->dispose = clutter_gst_camera_dispose;
+
+ g_object_class_override_property (object_class,
+ PROP_IDLE, "idle");
+ g_object_class_override_property (object_class,
+ PROP_PLAYING, "playing");
+ g_object_class_override_property (object_class,
+ PROP_AUDIO_VOLUME, "audio-volume");
+
+ /* Signals */
+
+ /**
+ * ClutterGstCamera::ready-for-capture:
+ * @self: the actor which received the signal
+ * @ready: whether the @self is ready for a new capture
+ *
+ * The ::ready-for-capture signal is emitted whenever the value of
+ * clutter_gst_camera_is_ready_for_capture changes.
+ */
+ camera_signals[READY_FOR_CAPTURE] =
+ g_signal_new ("ready-for-capture",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (ClutterGstCameraClass, ready_for_capture),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE, 1,
+ G_TYPE_BOOLEAN);
+ /**
+ * ClutterGstCamera::photo-saved:
+ * @self: the actor which received the signal
+ *
+ * The ::photo-saved signal is emitted when a photo was saved to disk.
+ */
+ camera_signals[PHOTO_SAVED] =
+ g_signal_new ("photo-saved",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ClutterGstCameraClass, photo_saved),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ /**
+ * ClutterGstCamera::photo-taken:
+ * @self: the actor which received the signal
+ * @pixbuf: the photo taken as a #GdkPixbuf
+ *
+ * The ::photo-taken signal is emitted when a photo was taken.
+ */
+ camera_signals[PHOTO_TAKEN] =
+ g_signal_new ("photo-taken",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ClutterGstCameraClass, photo_taken),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, GDK_TYPE_PIXBUF);
+ /**
+ * ClutterGstCamera::video-saved:
+ * @self: the actor which received the signal
+ *
+ * The ::video-saved signal is emitted when a video was saved to disk.
+ */
+ camera_signals[VIDEO_SAVED] =
+ g_signal_new ("video-saved",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (ClutterGstCameraClass, video_saved),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+notify_ready_for_capture (GObject *object,
+ GParamSpec *pspec,
+ ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ gboolean ready_for_capture;
+
+ g_object_get (priv->camera_source, "ready-for-capture",
+ &ready_for_capture, NULL);
+ g_signal_emit (self, camera_signals[READY_FOR_CAPTURE],
+ 0, ready_for_capture);
+}
+
+static void
+parse_photo_data (ClutterGstCamera *self,
+ GstSample *sample)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ GstBuffer *buffer;
+ GstCaps *caps;
+ const GstStructure *structure;
+ gint width, height, stride;
+ GdkPixbuf *pixbuf;
+ const gint bits_per_pixel = 8;
+ guchar *data = NULL;
+ GstMapInfo info;
+
+ buffer = gst_sample_get_buffer (sample);
+ caps = gst_sample_get_caps (sample);
+
+ gst_buffer_map (buffer, &info, GST_MAP_READ);
+
+ structure = gst_caps_get_structure (caps, 0);
+ gst_structure_get_int (structure, "width", &width);
+ gst_structure_get_int (structure, "height", &height);
+
+ stride = info.size / height;
+
+ data = g_memdup (info.data, info.size);
+ pixbuf = gdk_pixbuf_new_from_data (data,
+ GDK_COLORSPACE_RGB,
+ FALSE, bits_per_pixel, width, height, stride,
+ data ? (GdkPixbufDestroyNotify) g_free : NULL, NULL);
+
+ g_object_set (G_OBJECT (priv->camerabin), "post-previews", FALSE, NULL);
+ g_signal_emit (self, camera_signals[PHOTO_TAKEN], 0, pixbuf);
+ g_object_unref (pixbuf);
+}
+
+static void
+bus_message_cb (GstBus *bus,
+ GstMessage *message,
+ ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+
+ switch (GST_MESSAGE_TYPE (message))
+ {
+ case GST_MESSAGE_ERROR:
+ {
+ GError *err = NULL;
+ gchar *debug = NULL;
+
+ gst_message_parse_error (message, &err, &debug);
+ if (err && err->message)
+ g_warning ("%s", err->message);
+ else
+ g_warning ("Unparsable GST_MESSAGE_ERROR message.");
+
+ if (err)
+ g_error_free (err);
+ g_free (debug);
+
+ priv->is_idle = TRUE;
+ g_object_notify (G_OBJECT (self), "idle");
+ break;
+ }
+
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ if (strcmp (GST_MESSAGE_SRC_NAME (message), "camerabin") == 0)
+ {
+ GstState new;
+
+ gst_message_parse_state_changed (message, NULL, &new, NULL);
+ if (new == GST_STATE_PLAYING)
+ priv->is_idle = FALSE;
+ else
+ priv->is_idle = TRUE;
+ g_object_notify (G_OBJECT (self), "idle");
+ }
+ break;
+ }
+
+ case GST_MESSAGE_ELEMENT:
+ {
+ const GstStructure *structure;
+ const GValue *image;
+
+ if (strcmp (GST_MESSAGE_SRC_NAME (message), "camera_source") == 0)
+ {
+ structure = gst_message_get_structure (message);
+ if (strcmp (gst_structure_get_name (structure), "preview-image") == 0)
+ {
+ if (gst_structure_has_field_typed (structure, "sample", GST_TYPE_SAMPLE))
+ {
+ image = gst_structure_get_value (structure, "sample");
+ if (image)
+ {
+ GstSample *sample;
+
+ sample = gst_value_get_sample (image);
+ parse_photo_data (self, sample);
+ }
+ else
+ g_warning ("Could not get buffer from bus message");
+ }
+ }
+ }
+ else if (strcmp (GST_MESSAGE_SRC_NAME (message), "camerabin") == 0)
+ {
+ structure = gst_message_get_structure (message);
+ if (strcmp (gst_structure_get_name (structure), "image-done") == 0)
+ {
+ const gchar *filename = gst_structure_get_string (structure, "filename");
+ if (priv->photo_filename != NULL && filename != NULL &&
+ (strcmp (priv->photo_filename, filename) == 0))
+ g_signal_emit (self, camera_signals[PHOTO_SAVED], 0);
+ }
+ else if (strcmp (gst_structure_get_name (structure), "video-done") == 0)
+ {
+ g_signal_emit (self, camera_signals[VIDEO_SAVED], 0);
+ priv->is_recording = FALSE;
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void
+set_video_profile (ClutterGstCamera *self)
+{
+ GstEncodingContainerProfile *prof;
+ GstEncodingAudioProfile *audio_prof;
+ GstEncodingVideoProfile *video_prof;
+ GstCaps *caps;
+
+ caps = gst_caps_from_string ("application/ogg");
+ prof = gst_encoding_container_profile_new ("Ogg audio/video",
+ "Standard Ogg/Theora/Vorbis",
+ caps, NULL);
+ gst_caps_unref (caps);
+
+ caps = gst_caps_from_string ("video/x-theora");
+ video_prof = gst_encoding_video_profile_new (caps, NULL, NULL, 0);
+ gst_encoding_container_profile_add_profile (prof, (GstEncodingProfile*) video_prof);
+ gst_caps_unref (caps);
+
+ caps = gst_caps_from_string ("audio/x-vorbis");
+ audio_prof = gst_encoding_audio_profile_new (caps, NULL, NULL, 0);
+ gst_encoding_container_profile_add_profile (prof, (GstEncodingProfile*) audio_prof);
+ gst_caps_unref (caps);
+
+ clutter_gst_camera_set_video_profile (self,
+ (GstEncodingProfile *) prof);
+
+ gst_encoding_profile_unref (prof);
+}
+
+static GstElement *
+setup_video_filter_bin (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ GstElement *bin;
+ GstPad *pad;
+
+ if ((priv->identity = gst_element_factory_make ("identity", "identity")) == NULL)
+ goto error;
+ if ((priv->valve = gst_element_factory_make ("valve", "valve")) == NULL)
+ goto error;
+ if ((priv->gamma = gst_element_factory_make ("gamma", "gamma")) == NULL)
+ goto error;
+ if ((priv->pre_colorspace = gst_element_factory_make ("videoconvert", "pre_colorspace")) == NULL)
+ goto error;
+ if ((priv->color_balance = gst_element_factory_make ("videobalance", "color_balance")) == NULL)
+ goto error;
+ if ((priv->post_colorspace = gst_element_factory_make ("videoconvert", "post_colorspace")) == NULL)
+ goto error;
+
+ bin = gst_bin_new ("video_filter_bin");
+ gst_bin_add_many (GST_BIN (bin),
+ priv->identity, priv->valve, priv->gamma,
+ priv->pre_colorspace, priv->color_balance, priv->post_colorspace,
+ NULL);
+
+ if (!gst_element_link_many (priv->identity, priv->valve, priv->gamma,
+ priv->pre_colorspace, priv->color_balance, priv->post_colorspace,
+ NULL))
+ goto error_not_linked;
+
+ pad = gst_element_get_static_pad (priv->post_colorspace, "src");
+ gst_element_add_pad (bin, gst_ghost_pad_new ("src", pad));
+ gst_object_unref (pad);
+
+ pad = gst_element_get_static_pad (priv->identity, "sink");
+ gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));
+ gst_object_unref (pad);
+ return bin;
+
+ error:
+ if (priv->identity)
+ gst_object_unref (priv->identity);
+ if (priv->valve)
+ gst_object_unref (priv->valve);
+ if (priv->gamma)
+ gst_object_unref (priv->gamma);
+ if (priv->pre_colorspace)
+ gst_object_unref (priv->pre_colorspace);
+ if (priv->color_balance)
+ gst_object_unref (priv->color_balance);
+ if (priv->post_colorspace)
+ gst_object_unref (priv->post_colorspace);
+ return NULL;
+
+ error_not_linked:
+ gst_object_unref (bin);
+ return NULL;
+}
+
+static GstCaps *
+create_caps_for_formats (gint width,
+ gint height)
+{
+ GstCaps *ret = NULL;
+ guint length;
+ guint i;
+
+ length = g_strv_length ((gchar **) supported_media_types);
+ for (i = 0; i < length; i++)
+ {
+ GstCaps *caps;
+
+ caps = gst_caps_new_simple (supported_media_types[i],
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ NULL);
+ if (!ret)
+ ret = caps;
+ else
+ gst_caps_append (ret, caps);
+ }
+ return ret;
+}
+
+static void
+device_capture_resolution_changed (ClutterGstCameraDevice *camera_device,
+ gint width,
+ gint height,
+ ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ GstCaps *caps;
+
+ if (priv->camera_device != camera_device)
+ return;
+
+ caps = create_caps_for_formats (width, height);
+ g_object_set (G_OBJECT (priv->camerabin), "video-capture-caps", caps, NULL);
+ g_object_set (G_OBJECT (priv->camerabin), "image-capture-caps", caps, NULL);
+ g_object_set (G_OBJECT (priv->camerabin), "viewfinder-caps", caps, NULL);
+ gst_caps_unref (caps);
+}
+
+static void
+set_device_resolutions (ClutterGstCamera *self,
+ ClutterGstCameraDevice *device)
+
+{
+ gint width;
+ gint height;
+
+ clutter_gst_camera_device_get_capture_resolution (device, &width, &height);
+ device_capture_resolution_changed (device, width, height, self);
+}
+
+static void
+add_device (ClutterGstCamera *self,
+ GstElementFactory *element_factory,
+ const gchar *device_node,
+ const gchar *device_name)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ ClutterGstCameraDevice *device;
+
+ if (!priv->camera_devices)
+ priv->camera_devices =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+
+ device = g_object_new (CLUTTER_GST_TYPE_CAMERA_DEVICE,
+ "element-factory", element_factory,
+ "node", device_node,
+ "name", device_name,
+ NULL);
+ g_signal_connect (device, "capture-resolution-changed",
+ G_CALLBACK (device_capture_resolution_changed),
+ self);
+ g_ptr_array_add (priv->camera_devices, device);
+}
+
+static gboolean
+probe_camera_devices (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ GstElement *videosrc;
+ GstElementFactory *element_factory;
+ GParamSpec *pspec;
+ gchar *device_node;
+ gchar *device_name;
+#ifdef HAVE_GUDEV
+ GUdevClient *udev_client;
+ GList *udevices, *l;
+ GUdevDevice *udevice;
+#endif
+
+ videosrc = gst_element_factory_make ("v4l2src", "v4l2src");
+ if (!videosrc)
+ {
+ g_warning ("Unable to get available camera devices, "
+ "v4l2src element missing");
+ return FALSE;
+ }
+
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (G_OBJECT (videosrc)), "device");
+ if (!G_IS_PARAM_SPEC_STRING (pspec))
+ {
+ g_warning ("Unable to get available camera devices, "
+ "v4l2src has no 'device' property");
+ goto out;
+ }
+
+ element_factory = gst_element_get_factory (videosrc);
+
+#ifdef HAVE_GUDEV
+ udev_client = g_udev_client_new (NULL);
+ udevices = g_udev_client_query_by_subsystem (udev_client, "video4linux");
+ for (l = udevices; l != NULL; l = l->next)
+ {
+ gint v4l_version;
+
+ udevice = (GUdevDevice *) l->data;
+ v4l_version = g_udev_device_get_property_as_int (udevice, "ID_V4L_VERSION");
+ if (v4l_version == 2)
+ {
+ const char *caps;
+
+ caps = g_udev_device_get_property (udevice, "ID_V4L_CAPABILITIES");
+ if (caps == NULL || strstr (caps, ":capture:") == NULL)
+ continue;
+
+ device_node = (gchar *) g_udev_device_get_device_file (udevice);
+ device_name = (gchar *) g_udev_device_get_property (udevice, "ID_V4L_PRODUCT");
+
+ add_device (self, element_factory, device_node, device_name);
+ }
+
+ g_object_unref (udevice);
+ }
+ g_list_free (udevices);
+ g_object_unref (udev_client);
+#else
+ /* GStreamer 1.0 does not support property probe, adding default detected
+ * device as only known device */
+ g_object_get (videosrc, "device", &device_node, NULL);
+ g_object_get (videosrc, "device-name", &device_name, NULL);
+ add_device (self, element_factory, device_node, device_name);
+
+ g_free (device_node);
+ g_free (device_name);
+#endif
+
+ out:
+ gst_object_unref (videosrc);
+ return (priv->camera_devices != NULL);
+}
+
+static gboolean
+setup_camera_source (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ GstElement *camera_source;
+ GstElement *old_camera_source = NULL;
+
+ if (priv->camera_source)
+ return TRUE;
+
+ camera_source = gst_element_factory_make ("wrappercamerabinsrc", "camera_source");
+ if (G_UNLIKELY (!camera_source))
+ {
+ g_critical ("Unable to create wrappercamerabinsrc element");
+ return FALSE;
+ }
+
+ priv->camera_source = camera_source;
+ g_object_set (priv->camerabin, "camera-source", camera_source, NULL);
+
+ g_signal_connect (camera_source, "notify::ready-for-capture",
+ G_CALLBACK (notify_ready_for_capture),
+ self);
+
+ if (priv->video_filter_bin)
+ {
+ g_object_set (G_OBJECT (camera_source),
+ "video-source-filter", priv->video_filter_bin,
+ NULL);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+setup_pipeline (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv = self->priv;
+ GstElement *camera_sink;
+
+ if (!probe_camera_devices (self))
+ {
+ g_critical ("Unable to find any suitable capture device");
+ return FALSE;
+ }
+
+ priv->camerabin = gst_element_factory_make ("camerabin", "camerabin");
+ if (G_UNLIKELY (!priv->camerabin))
+ {
+ g_critical ("Unable to create camerabin element");
+ return FALSE;
+ }
+
+ priv->video_filter_bin = setup_video_filter_bin (self);
+ if (!priv->video_filter_bin)
+ g_warning ("Unable to setup video filter, some features will be disabled");
+
+ if (G_UNLIKELY (!setup_camera_source (self)))
+ {
+ g_critical ("Unable to create camera source element");
+ gst_object_unref (priv->camerabin);
+ priv->camerabin = 0;
+ return FALSE;
+ }
+
+ if (!clutter_gst_camera_set_camera_device (self,
+ g_ptr_array_index (priv->camera_devices, 0)))
+ {
+ g_critical ("Unable to select capture device");
+ gst_object_unref (priv->camerabin);
+ priv->camerabin = 0;
+ return FALSE;
+ }
+
+ camera_sink = gst_element_factory_make ("coglsink", NULL);
+ g_object_set (priv->camerabin,
+ "viewfinder-sink", camera_sink,
+ NULL);
+
+ set_video_profile (self);
+
+ priv->bus = gst_element_get_bus (priv->camerabin);
+ gst_bus_add_signal_watch (priv->bus);
+
+ g_signal_connect (G_OBJECT (priv->bus), "message",
+ G_CALLBACK (bus_message_cb), self);
+
+ return TRUE;
+}
+
+static void
+clutter_gst_camera_init (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv;
+
+ self->priv = priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (self,
+ CLUTTER_GST_TYPE_CAMERA,
+ ClutterGstCameraPrivate);
+
+ if (!setup_pipeline (self))
+ {
+ g_warning ("Failed to initiate suitable elements for pipeline.");
+ return;
+ }
+
+ priv->is_idle = TRUE;
+}
+
+/*
+ * Public symbols
+ */
+
+/**
+ * clutter_gst_camera_new:
+ *
+ * Create a camera actor.
+ *
+ * <note>This function has to be called from Clutter's main thread. While
+ * GStreamer will spawn threads to do its work, we want all the GL calls to
+ * happen in the same thread. Clutter-gst knows which thread it is by
+ * assuming this constructor is called from the Clutter thread.</note>
+ *
+ * Return value: the newly created camera actor
+ */
+ClutterGstCamera *
+clutter_gst_camera_new (void)
+{
+ return g_object_new (CLUTTER_GST_TYPE_CAMERA,
+ NULL);
+}
+
+/* /\** */
+/* * clutter_gst_camera_get_pipeline: */
+/* * @self: a #ClutterGstCamera */
+/* * */
+/* * Retrieve the #GstPipeline used by the @self, for direct use with */
+/* * GStreamer API. */
+/* * */
+/* * Return value: (transfer none): the pipeline element used by the camera actor */
+/* *\/ */
+/* GstElement * */
+/* clutter_gst_camera_get_pipeline (ClutterGstCamera *self) */
+/* { */
+/* g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), NULL); */
+
+/* return self->priv->camerabin; */
+/* } */
+
+/* /\** */
+/* * clutter_gst_camera_get_camerabin: */
+/* * @self: a #ClutterGstCamera */
+/* * */
+/* * Retrieve the camerabin element used by the @self, for direct use with */
+/* * GStreamer API. */
+/* * */
+/* * Return value: (transfer none): the pipeline element used by the camera actor */
+/* *\/ */
+/* GstElement * */
+/* clutter_gst_camera_get_camerabin (ClutterGstCamera *self) */
+/* { */
+/* g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), NULL); */
+
+/* return self->priv->camerabin; */
+/* } */
+
+/**
+ * clutter_gst_camera_get_camera_devices:
+ * @self: a #ClutterGstCamera
+ *
+ * Retrieve an array of supported camera devices.
+ *
+ * Return value: (transfer none) (element-type ClutterGst.CameraDevice): An array of #ClutterGstCameraDevice representing
+ * the supported camera devices
+ */
+const GPtrArray *
+clutter_gst_camera_get_camera_devices (ClutterGstCamera *self)
+{
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), NULL);
+
+ return self->priv->camera_devices;
+}
+
+/**
+ * clutter_gst_camera_get_camera_device:
+ * @self: a #ClutterGstCamera
+ *
+ * Retrieve the current selected camera device.
+ *
+ * Return value: (transfer none): The currently selected camera device
+ */
+ClutterGstCameraDevice *
+clutter_gst_camera_get_camera_device (ClutterGstCamera *self)
+{
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), NULL);
+
+ return self->priv->camera_device;
+}
+
+/**
+ * clutter_gst_camera_set_camera_device:
+ * @self: a #ClutterGstCamera
+ * @device: a #ClutterGstCameraDevice
+ *
+ * Set the new active camera device.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_set_camera_device (ClutterGstCamera *self,
+ ClutterGstCameraDevice *device)
+{
+ ClutterGstCameraPrivate *priv;
+ GstElementFactory *element_factory;
+ GstElement *src;
+ gchar *node;
+ gboolean was_playing = FALSE;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+ g_return_val_if_fail (device != NULL, FALSE);
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return FALSE;
+
+ if (priv->is_recording)
+ clutter_gst_camera_stop_video_recording (self);
+
+ if (clutter_gst_camera_is_playing (self))
+ {
+ gst_element_set_state (priv->camerabin, GST_STATE_NULL);
+ was_playing = TRUE;
+ }
+
+ g_object_get (device,
+ "element-factory", &element_factory,
+ "node", &node,
+ NULL);
+ src = gst_element_factory_create (element_factory, NULL);
+ if (!src)
+ {
+ g_warning ("Unable to create device source for "
+ "capture device %s (using factory %s)",
+ node, gst_object_get_name (GST_OBJECT (element_factory)));
+
+ return FALSE;
+ }
+
+#if 0
+ g_print ("Setting active device to %s (using factory %s)\n",
+ node,
+ gst_object_get_name (GST_OBJECT (element_factory)));
+#endif
+
+ gst_object_unref (element_factory);
+
+ priv->camera_device = device;
+
+ g_object_set (G_OBJECT (src), "device", node, NULL);
+ g_free (node);
+ g_object_set (G_OBJECT (priv->camera_source), "video-source", src, NULL);
+
+ set_device_resolutions (self, device);
+
+ if (was_playing)
+ gst_element_set_state (priv->camerabin, GST_STATE_PLAYING);
+
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_supports_gamma_correction:
+ * @self: a #ClutterGstCamera
+ *
+ * Check whether the @self supports gamma correction.
+ *
+ * Return value: %TRUE if @self supports gamma correction, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_supports_gamma_correction (ClutterGstCamera *self)
+{
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ return (self->priv->gamma != NULL);
+}
+
+/**
+ * clutter_gst_camera_get_gamma_range:
+ * @self: a #ClutterGstCamera
+ * @min_value: Pointer to store the minimum gamma value, or %NULL
+ * @max_value: Pointer to store the maximum gamma value, or %NULL
+ * @default_value: Pointer to store the default gamma value, or %NULL
+ *
+ * Retrieve the minimum, maximum and default gamma values.
+ *
+ * This method will return FALSE if gamma correction is not
+ * supported on @self.
+ * See clutter_gst_camera_supports_gamma_correction().
+ *
+ * Return value: %TRUE if successful, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_get_gamma_range (ClutterGstCamera *self,
+ gdouble *min_value,
+ gdouble *max_value,
+ gdouble *default_value)
+{
+ ClutterGstCameraPrivate *priv;
+ GParamSpec *pspec;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->gamma)
+ return FALSE;
+
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (G_OBJECT (priv->gamma)), "gamma");
+ /* shouldn't happen */
+ g_return_val_if_fail (G_IS_PARAM_SPEC_DOUBLE (pspec), FALSE);
+
+ if (min_value)
+ *min_value = G_PARAM_SPEC_DOUBLE (pspec)->minimum;
+ if (max_value)
+ *max_value = G_PARAM_SPEC_DOUBLE (pspec)->maximum;
+ if (default_value)
+ *default_value = G_PARAM_SPEC_DOUBLE (pspec)->default_value;
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_get_gamma:
+ * @self: a #ClutterGstCamera
+ * @cur_value: Pointer to store the current gamma value
+ *
+ * Retrieve the current gamma value.
+ *
+ * This method will return FALSE if gamma correction is not
+ * supported on @self.
+ * See clutter_gst_camera_supports_gamma_correction().
+ *
+ * Return value: %TRUE if successful, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_get_gamma (ClutterGstCamera *self,
+ gdouble *cur_value)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+ g_return_val_if_fail (cur_value != NULL, FALSE);
+
+ priv = self->priv;
+
+ if (!priv->gamma)
+ return FALSE;
+
+ g_object_get (G_OBJECT (priv->gamma), "gamma", cur_value, NULL);
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_set_gamma:
+ * @self: a #ClutterGstCamera
+ * @value: The value to set
+ *
+ * Set the gamma value.
+ * Allowed values can be retrieved with
+ * clutter_gst_camera_get_gamma_range().
+ *
+ * This method will return FALSE if gamma correction is not
+ * supported on @self.
+ * See clutter_gst_camera_supports_gamma_correction().
+ *
+ * Return value: %TRUE if successful, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_set_gamma (ClutterGstCamera *self,
+ gdouble value)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->gamma)
+ return FALSE;
+
+ g_object_set (G_OBJECT (priv->gamma), "gamma", value, NULL);
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_supports_color_balance:
+ * @self: a #ClutterGstCamera
+ *
+ * Check whether the @self supports color balance.
+ *
+ * Return value: %TRUE if @self supports color balance, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_supports_color_balance (ClutterGstCamera *self)
+{
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ return (self->priv->color_balance != NULL);
+}
+
+/**
+ * clutter_gst_camera_get_color_balance_property_range:
+ * @self: a #ClutterGstCamera
+ * @property: Property name
+ * @min_value: Pointer to store the minimum value of @property, or %NULL
+ * @max_value: Pointer to store the maximum value of @property, or %NULL
+ * @default_value: Pointer to store the default value of @property, or %NULL
+ *
+ * Retrieve the minimum, maximum and default values for the color balance property @property,
+ *
+ * This method will return FALSE if @property does not exist or color balance is not
+ * supported on @self.
+ * See clutter_gst_camera_supports_color_balance().
+ *
+ * Return value: %TRUE if successful, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_get_color_balance_property_range (ClutterGstCamera *self,
+ const gchar *property,
+ gdouble *min_value,
+ gdouble *max_value,
+ gdouble *default_value)
+{
+ ClutterGstCameraPrivate *priv;
+ GParamSpec *pspec;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->color_balance)
+ return FALSE;
+
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (G_OBJECT (priv->color_balance)), property);
+ g_return_val_if_fail (G_IS_PARAM_SPEC_DOUBLE (pspec), FALSE);
+
+ if (min_value)
+ *min_value = G_PARAM_SPEC_DOUBLE (pspec)->minimum;
+ if (max_value)
+ *max_value = G_PARAM_SPEC_DOUBLE (pspec)->maximum;
+ if (default_value)
+ *default_value = G_PARAM_SPEC_DOUBLE (pspec)->default_value;
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_get_color_balance_property:
+ * @self: a #ClutterGstCamera
+ * @property: Property name
+ * @cur_value: Pointer to store the current value of @property
+ *
+ * Retrieve the current value for the color balance property @property,
+ *
+ * This method will return FALSE if @property does not exist or color balance is not
+ * supported on @self.
+ * See clutter_gst_camera_supports_color_balance().
+ *
+ * Return value: %TRUE if successful, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_get_color_balance_property (ClutterGstCamera *self,
+ const gchar *property,
+ gdouble *cur_value)
+{
+ ClutterGstCameraPrivate *priv;
+ GParamSpec *pspec;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+ g_return_val_if_fail (cur_value != NULL, FALSE);
+
+ priv = self->priv;
+
+ if (!priv->color_balance)
+ return FALSE;
+
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (G_OBJECT (priv->color_balance)), property);
+ g_return_val_if_fail (G_IS_PARAM_SPEC_DOUBLE (pspec), FALSE);
+
+ g_object_get (G_OBJECT (priv->color_balance), property, cur_value, NULL);
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_set_color_balance_property:
+ * @self: a #ClutterGstCamera
+ * @property: Property name
+ * @value: The value to set
+ *
+ * Set the value for the color balance property @property to @value.
+ * Allowed values can be retrieved with
+ * clutter_gst_camera_get_color_balance_property_range().
+ *
+ * This method will return FALSE if @property does not exist or color balance is not
+ * supported on @self.
+ * See clutter_gst_camera_supports_color_balance().
+ *
+ * Return value: %TRUE if successful, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_set_color_balance_property (ClutterGstCamera *self,
+ const gchar *property,
+ gdouble value)
+{
+ ClutterGstCameraPrivate *priv;
+ GParamSpec *pspec;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->color_balance)
+ return FALSE;
+
+ pspec = g_object_class_find_property (
+ G_OBJECT_GET_CLASS (G_OBJECT (priv->color_balance)), property);
+ g_return_val_if_fail (G_IS_PARAM_SPEC_DOUBLE (pspec), FALSE);
+
+ g_object_set (G_OBJECT (priv->color_balance), property, value, NULL);
+ return TRUE;
+}
+
+gboolean
+clutter_gst_camera_get_brightness_range (ClutterGstCamera *self,
+ gdouble *min_value,
+ gdouble *max_value,
+ gdouble *default_value)
+{
+ return clutter_gst_camera_get_color_balance_property_range (self,
+ "brightness", min_value, max_value, default_value);
+}
+
+gboolean
+clutter_gst_camera_get_brightness (ClutterGstCamera *self,
+ gdouble *cur_value)
+{
+ return clutter_gst_camera_get_color_balance_property (self,
+ "brightness", cur_value);
+}
+
+gboolean
+clutter_gst_camera_set_brightness (ClutterGstCamera *self,
+ gdouble value)
+{
+ return clutter_gst_camera_set_color_balance_property (self,
+ "brightness", value);
+}
+
+gboolean
+clutter_gst_camera_get_contrast_range (ClutterGstCamera *self,
+ gdouble *min_value,
+ gdouble *max_value,
+ gdouble *default_value)
+{
+ return clutter_gst_camera_get_color_balance_property_range (self,
+ "contrast", min_value, max_value, default_value);
+}
+
+gboolean
+clutter_gst_camera_get_contrast (ClutterGstCamera *self,
+ gdouble *cur_value)
+{
+ return clutter_gst_camera_get_color_balance_property (self,
+ "contrast", cur_value);
+}
+
+gboolean
+clutter_gst_camera_set_contrast (ClutterGstCamera *self,
+ gdouble value)
+{
+ return clutter_gst_camera_set_color_balance_property (self,
+ "contrast", value);
+}
+
+gboolean
+clutter_gst_camera_get_saturation_range (ClutterGstCamera *self,
+ gdouble *min_value,
+ gdouble *max_value,
+ gdouble *default_value)
+{
+ return clutter_gst_camera_get_color_balance_property_range (self,
+ "saturation", min_value, max_value, default_value);
+}
+
+gboolean
+clutter_gst_camera_get_saturation (ClutterGstCamera *self,
+ gdouble *cur_value)
+{
+ return clutter_gst_camera_get_color_balance_property (self,
+ "saturation", cur_value);
+}
+
+gboolean
+clutter_gst_camera_set_saturation (ClutterGstCamera *self,
+ gdouble value)
+{
+ return clutter_gst_camera_set_color_balance_property (self,
+ "saturation", value);
+}
+
+gboolean
+clutter_gst_camera_get_hue_range (ClutterGstCamera *self,
+ gdouble *min_value,
+ gdouble *max_value,
+ gdouble *default_value)
+{
+ return clutter_gst_camera_get_color_balance_property_range (self,
+ "hue", min_value, max_value, default_value);
+}
+
+gboolean
+clutter_gst_camera_get_hue (ClutterGstCamera *self,
+ gdouble *cur_value)
+{
+ return clutter_gst_camera_get_color_balance_property (self,
+ "hue", cur_value);
+}
+
+gboolean
+clutter_gst_camera_set_hue (ClutterGstCamera *self,
+ gdouble value)
+{
+ return clutter_gst_camera_set_color_balance_property (self,
+ "hue", value);
+}
+
+/**
+ * clutter_gst_camera_get_filter:
+ * @self: a #ClutterGstCamera
+ *
+ * Retrieve the current filter being used.
+ *
+ * Return value: (transfer none): The current filter or %NULL if none is set
+ */
+GstElement *
+clutter_gst_camera_get_filter (ClutterGstCamera *self)
+{
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ return self->priv->custom_filter;
+}
+
+static GstElement *
+create_filter_bin (GstElement *filter)
+{
+ GstElement *filter_bin = NULL;
+ GstElement *pre_filter_colorspace;
+ GstElement *post_filter_colorspace;
+ GstPad *pad;
+
+ if ((pre_filter_colorspace = gst_element_factory_make ("videoconvert", "pre_filter_colorspace")) == NULL)
+ goto err;
+ if ((post_filter_colorspace = gst_element_factory_make ("videoconvert", "post_filter_colorspace")) == NULL)
+ goto err;
+
+ filter_bin = gst_bin_new ("custom_filter_bin");
+ gst_bin_add_many (GST_BIN (filter_bin), pre_filter_colorspace,
+ filter, post_filter_colorspace, NULL);
+ if (!gst_element_link_many (pre_filter_colorspace,
+ filter, post_filter_colorspace, NULL))
+ goto err_not_linked;
+
+ pad = gst_element_get_static_pad (pre_filter_colorspace, "sink");
+ gst_element_add_pad (filter_bin, gst_ghost_pad_new ("sink", pad));
+ gst_object_unref (GST_OBJECT (pad));
+
+ pad = gst_element_get_static_pad (post_filter_colorspace, "src");
+ gst_element_add_pad (filter_bin, gst_ghost_pad_new ("src", pad));
+ gst_object_unref (GST_OBJECT (pad));
+
+ out:
+ return filter_bin;
+
+ err:
+ if (pre_filter_colorspace)
+ gst_object_unref (pre_filter_colorspace);
+ if (post_filter_colorspace)
+ gst_object_unref (post_filter_colorspace);
+ goto out;
+
+ err_not_linked:
+ gst_object_unref (filter_bin);
+ filter_bin = NULL;
+ goto out;
+}
+
+/**
+ * clutter_gst_camera_set_filter:
+ * @self: a #ClutterGstCamera
+ * @filter: a #GstElement for the filter
+ *
+ * Set the filter element to be used.
+ * Filters can be used for effects, image processing, etc.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_set_filter (ClutterGstCamera *self,
+ GstElement *filter)
+{
+ ClutterGstCameraPrivate *priv;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->custom_filter && !filter)
+ {
+ /* nothing to do here, we don't have a filter and NULL
+ * was passed as new filter */
+ return TRUE;
+ }
+
+ g_object_set (G_OBJECT (priv->valve), "drop", TRUE, NULL);
+
+ if (priv->custom_filter)
+ {
+ /* remove current filter if any */
+ gst_element_unlink_many (priv->valve, priv->custom_filter,
+ priv->gamma, NULL);
+ g_object_ref (priv->custom_filter);
+ gst_bin_remove (GST_BIN (priv->video_filter_bin), priv->custom_filter);
+ gst_element_set_state (priv->custom_filter, GST_STATE_NULL);
+ g_object_unref (priv->custom_filter);
+ priv->custom_filter = NULL;
+ }
+ else
+ {
+ /* we have no current filter,
+ * unlink valve and gamma to set the new filter */
+ gst_element_unlink (priv->valve, priv->gamma);
+ }
+
+ if (filter)
+ {
+ priv->custom_filter = create_filter_bin (filter);
+ if (!priv->custom_filter)
+ goto err_restore;
+
+ gst_bin_add (GST_BIN (priv->video_filter_bin), priv->custom_filter);
+ if (!gst_element_link_many (priv->valve, priv->custom_filter,
+ priv->gamma, NULL))
+ {
+ /* removing will also unref it */
+ gst_bin_remove (GST_BIN (priv->video_filter_bin),
+ priv->custom_filter);
+ priv->custom_filter = NULL;
+ goto err_restore;
+ }
+
+ if (clutter_gst_camera_is_playing (self))
+ gst_element_set_state (priv->custom_filter, GST_STATE_PLAYING);
+ }
+ else
+ gst_element_link (priv->valve, priv->gamma);
+
+ ret = TRUE;
+
+ out:
+ g_object_set (G_OBJECT (priv->valve), "drop", FALSE, NULL);
+ return ret;
+
+ err_restore:
+ ret = FALSE;
+ /* restore default pipeline, should always work */
+ gst_element_link (priv->valve, priv->gamma);
+ goto out;
+}
+
+/**
+ * clutter_gst_camera_remove_filter:
+ * @self: a #ClutterGstCamera
+ *
+ * Remove the current filter, if any.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_remove_filter (ClutterGstCamera *self)
+{
+ return clutter_gst_camera_set_filter (self, NULL);
+}
+
+/**
+ * clutter_gst_camera_is_playing:
+ * @self: a #ClutterGstCamera
+ *
+ * Retrieve whether the @self is playing.
+ *
+ * Return value: %TRUE if playing, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_is_playing (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv;
+ GstState state, pending;
+ gboolean playing;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+ if (!priv->camerabin)
+ return FALSE;
+
+ gst_element_get_state (priv->camerabin, &state, &pending, 0);
+
+ if (pending)
+ playing = (pending == GST_STATE_PLAYING);
+ else
+ playing = (state == GST_STATE_PLAYING);
+
+ return playing;
+}
+
+/**
+ * clutter_gst_camera_is_ready_for_capture:
+ * @self: a #ClutterGstCamera
+ *
+ * Check whether the @self is ready for video/photo capture.
+ *
+ * Return value: %TRUE if @self is ready for capture, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_is_ready_for_capture (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv;
+ gboolean ready_for_capture;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ g_object_get (priv->camera_source, "ready-for-capture", &ready_for_capture, NULL);
+
+ return ready_for_capture;
+}
+
+/**
+ * clutter_gst_camera_is_recording_video:
+ * @self: a #ClutterGstCamera
+ *
+ * Check whether the @self is recording video.
+ *
+ * Return value: %TRUE if @self is recording video, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_is_recording_video (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ return priv->is_recording;
+}
+
+/**
+ * clutter_gst_camera_set_video_profile:
+ * @self: a #ClutterGstCamera
+ * @profile: A #GstEncodingProfile to be used for video recording.
+ *
+ * Set the encoding profile to be used for video recording.
+ * The default profile saves videos as Ogg/Theora videos.
+ */
+void
+clutter_gst_camera_set_video_profile (ClutterGstCamera *self,
+ GstEncodingProfile *profile)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_if_fail (CLUTTER_GST_IS_CAMERA (self));
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return;
+
+ g_object_set (priv->camerabin, "video-profile", profile, NULL);
+}
+
+/**
+ * clutter_gst_camera_start_video_recording:
+ * @self: a #ClutterGstCamera
+ * @filename: (type filename): the name of the video file to where the
+ * recording will be saved
+ *
+ * Start a video recording with the @self and save it to @filename.
+ * This method requires that @self is playing and ready for capture.
+ *
+ * The ::video-saved signal will be emitted when the video is saved.
+ *
+ * Return value: %TRUE if the video recording was successfully started, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_start_video_recording (ClutterGstCamera *self,
+ const gchar *filename)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return FALSE;
+
+ if (priv->is_recording)
+ return TRUE;
+
+ if (!clutter_gst_camera_is_playing (self))
+ return FALSE;
+
+ if (!clutter_gst_camera_is_ready_for_capture (self))
+ return FALSE;
+
+ g_object_set (priv->camerabin, "mode", CAPTURE_MODE_VIDEO, NULL);
+ g_object_set (priv->camerabin, "location", filename, NULL);
+ g_signal_emit_by_name (priv->camerabin, "start-capture");
+ priv->is_recording = TRUE;
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_stop_video_recording:
+ * @self: a #ClutterGstCamera
+ *
+ * Stop recording video on the @self.
+ */
+void
+clutter_gst_camera_stop_video_recording (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv;
+ GstState state;
+
+ g_return_if_fail (CLUTTER_GST_IS_CAMERA (self));
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return;
+
+ if (!priv->is_recording)
+ return;
+
+ if (!clutter_gst_camera_is_playing (self))
+ return;
+
+ gst_element_get_state (priv->camerabin, &state, NULL, 0);
+
+ if (state == GST_STATE_PLAYING)
+ g_signal_emit_by_name (priv->camerabin, "stop-capture");
+ else if (priv->is_recording)
+ {
+ g_warning ("Cannot cleanly shutdown recording pipeline, forcing");
+
+ gst_element_set_state (priv->camerabin, GST_STATE_NULL);
+ gst_element_set_state (priv->camerabin, GST_STATE_PLAYING);
+ priv->is_recording = FALSE;
+ }
+}
+
+/**
+ * clutter_gst_camera_set_photo_profile:
+ * @self: a #ClutterGstCamera
+ * @profile: A #GstEncodingProfile to be used for photo captures.
+ *
+ * Set the encoding profile to be used for photo captures.
+ * The default profile saves photos as JPEG images.
+ */
+void
+clutter_gst_camera_set_photo_profile (ClutterGstCamera *self,
+ GstEncodingProfile *profile)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_if_fail (CLUTTER_GST_IS_CAMERA (self));
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return;
+
+ g_object_set (priv->camerabin, "image-profile", profile, NULL);
+}
+
+/**
+ * clutter_gst_camera_take_photo:
+ * @self: a #ClutterGstCamera
+ * @filename: (type filename): the name of the file to where the
+ * photo will be saved
+ *
+ * Take a photo with the @self and save it to @filename.
+ * This method requires that @self is playing and ready for capture.
+ *
+ * The ::photo-saved signal will be emitted when the video is saved.
+ *
+ * Return value: %TRUE if the photo was successfully captured, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_take_photo (ClutterGstCamera *self,
+ const gchar *filename)
+{
+ ClutterGstCameraPrivate *priv;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return FALSE;
+
+ if (!clutter_gst_camera_is_playing (self))
+ return FALSE;
+
+ if (!clutter_gst_camera_is_ready_for_capture (self))
+ return FALSE;
+
+ g_free (priv->photo_filename);
+ priv->photo_filename = g_strdup (filename);
+
+ /* Take the photo */
+ g_object_set (priv->camerabin, "location", filename, NULL);
+ g_object_set (priv->camerabin, "mode", CAPTURE_MODE_IMAGE, NULL);
+ g_signal_emit_by_name (priv->camerabin, "start-capture");
+ return TRUE;
+}
+
+/**
+ * clutter_gst_camera_take_photo_pixbuf:
+ * @self: a #ClutterGstCamera
+ *
+ * Take a photo with the @self and emit it in the ::photo-taken signal as a
+ * #GdkPixbuf.
+ * This method requires that @self is playing and ready for capture.
+ *
+ * Return value: %TRUE if the photo was successfully captured, %FALSE otherwise
+ */
+gboolean
+clutter_gst_camera_take_photo_pixbuf (ClutterGstCamera *self)
+{
+ ClutterGstCameraPrivate *priv;
+ GstCaps *caps;
+
+ g_return_val_if_fail (CLUTTER_GST_IS_CAMERA (self), FALSE);
+
+ priv = self->priv;
+
+ if (!priv->camerabin)
+ return FALSE;
+
+ if (!clutter_gst_camera_is_playing (self))
+ return FALSE;
+
+ if (!clutter_gst_camera_is_ready_for_capture (self))
+ return FALSE;
+
+ caps = gst_caps_new_simple ("video/x-raw",
+ "bpp", G_TYPE_INT, 24,
+ "depth", G_TYPE_INT, 24,
+ NULL);
+ g_object_set (G_OBJECT (priv->camerabin), "post-previews", TRUE, NULL);
+ g_object_set (G_OBJECT (priv->camerabin), "preview-caps", caps, NULL);
+ gst_caps_unref (caps);
+
+ g_free (priv->photo_filename);
+ priv->photo_filename = NULL;
+
+ /* Take the photo */
+ g_object_set (priv->camerabin, "location", NULL, NULL);
+ g_object_set (priv->camerabin, "mode", CAPTURE_MODE_IMAGE, NULL);
+ g_signal_emit_by_name (priv->camerabin, "start-capture");
+ return TRUE;
+}