/*
* Clutter-GStreamer.
*
* GStreamer integration library for Clutter.
*
* clutter-gst-camera-device.c - GObject representing a camera device using GStreamer.
*
* 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-device
* @short_description: GObject representing a camera device using GStreamer.
*
* #ClutterGstCameraDevice is a #GObject representing a camera device using GStreamer.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <glib.h>
#include <gst/gst.h>
#include "clutter-gst-camera-device.h"
#include "clutter-gst-debug.h"
#include "clutter-gst-enum-types.h"
#include "clutter-gst-marshal.h"
#include "clutter-gst-private.h"
#include "clutter-gst-types.h"
struct _ClutterGstCameraDevicePrivate
{
GstElementFactory *element_factory;
gchar *node;
gchar *name;
GPtrArray *supported_resolutions;
gint capture_width;
gint capture_height;
};
enum {
PROP_0,
PROP_ELEMENT_FACTORY,
PROP_NODE,
PROP_NAME
};
enum
{
CAPTURE_RESOLUTION_CHANGED,
LAST_SIGNAL
};
static int camera_device_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (ClutterGstCameraDevice,
clutter_gst_camera_device,
G_TYPE_OBJECT);
static void
free_resolution (ClutterGstVideoResolution *resolution)
{
g_slice_free (ClutterGstVideoResolution, resolution);
}
static void
add_supported_resolution (ClutterGstCameraDevice *device,
gint width,
gint height)
{
ClutterGstCameraDevicePrivate *priv = device->priv;
ClutterGstVideoResolution *resolution;
guint i;
/* check if we already have this resolution set */
for (i = 0; i < priv->supported_resolutions->len; ++i)
{
ClutterGstVideoResolution *other_res;
other_res = g_ptr_array_index (priv->supported_resolutions, i);
if (other_res->width == width && other_res->height == height)
return;
}
resolution = g_slice_new0 (ClutterGstVideoResolution);
resolution->width = width;
resolution->height = height;
g_ptr_array_add (priv->supported_resolutions, resolution);
}
static void
parse_device_caps (ClutterGstCameraDevice *device,
GstCaps *caps)
{
guint size;
guint i;
size = gst_caps_get_size (caps);
for (i = 0; i < size; ++i)
{
GstStructure *structure;
const GValue *width, *height;
structure = gst_caps_get_structure (caps, i);
width = gst_structure_get_value (structure, "width");
height = gst_structure_get_value (structure, "height");
if (G_VALUE_HOLDS_INT (width) && G_VALUE_HOLDS_INT (height))
add_supported_resolution (device,
g_value_get_int (width),
g_value_get_int (height));
else if (GST_VALUE_HOLDS_INT_RANGE (width) &&
GST_VALUE_HOLDS_INT_RANGE (height))
{
gint min_width;
gint max_width;
gint min_height;
gint max_height;
min_width = gst_value_get_int_range_min (width);
max_width = gst_value_get_int_range_max (width);
min_height = gst_value_get_int_range_min (height);
max_height = gst_value_get_int_range_max (height);
/* TODO: how to improve this? */
add_supported_resolution (device, min_width, min_height);
add_supported_resolution (device, max_width, max_height);
}
}
}
static gint
compare_resolution (ClutterGstVideoResolution **a, ClutterGstVideoResolution **b)
{
return (((*b)->width * (*b)->height) - ((*a)->width * (*a)->height));
}
#if 0
static void
debug_resolutions (ClutterGstCameraDevice *device)
{
ClutterGstCameraDevicePrivate *priv = device->priv;
guint i;
g_print ("Supported resolutions for device %s (node=%s):\n",
priv->name, priv->node);
for (i = 0; i < priv->supported_resolutions->len; ++i)
{
ClutterGstVideoResolution *res;
res = g_ptr_array_index (priv->supported_resolutions, i);
g_print ("\t%dx%d:\n", res->width, res->height);
}
}
#endif
static void
probe_supported_resolutions (ClutterGstCameraDevice *device,
GstElement *element)
{
ClutterGstCameraDevicePrivate *priv = device->priv;
GstPad *pad;
GstCaps *caps;
priv->supported_resolutions = g_ptr_array_new_with_free_func (
(GDestroyNotify) free_resolution);
if (gst_element_set_state (element, GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS)
{
g_warning ("Unable to detect supported resolutions for camera device %s (node=%s)",
priv->name, priv->node);
return;
}
pad = gst_element_get_static_pad (element, "src");
caps = gst_pad_query_caps (pad, NULL);
parse_device_caps (device, caps);
gst_caps_unref (caps);
gst_object_unref (pad);
gst_element_set_state (element, GST_STATE_NULL);
g_ptr_array_sort (priv->supported_resolutions,
(GCompareFunc) compare_resolution);
}
/*
* GObject implementation
*/
static void
clutter_gst_camera_device_constructed (GObject *object)
{
ClutterGstCameraDevice *device = CLUTTER_GST_CAMERA_DEVICE (object);
ClutterGstCameraDevicePrivate *priv = device->priv;
GstElement *element;
if (!priv->element_factory || !priv->node || !priv->name)
{
g_critical ("Unable to setup device without element factory, "
"node and name set %p %p %p", priv->element_factory,
priv->node, priv->name);
return;
}
element = gst_element_factory_create (priv->element_factory, NULL);
if (!element)
{
g_warning ("Unable to create source for camera device %s (node=%s)",
priv->name, priv->node);
return;
}
g_object_set (G_OBJECT (element), "device", priv->node, NULL);
probe_supported_resolutions (device, element);
if (priv->supported_resolutions->len > 0)
{
ClutterGstVideoResolution *resolution;
gint width;
gint height;
resolution = g_ptr_array_index (priv->supported_resolutions, 0);
width = resolution->width;
height = resolution->height;
clutter_gst_camera_device_set_capture_resolution (device, width, height);
}
gst_object_unref (element);
}
static void
clutter_gst_camera_device_dispose (GObject *object)
{
ClutterGstCameraDevice *device = CLUTTER_GST_CAMERA_DEVICE (object);
ClutterGstCameraDevicePrivate *priv = device->priv;
if (priv->element_factory)
{
gst_object_unref (priv->element_factory);
priv->element_factory = NULL;
}
g_free (priv->node);
priv->node = NULL;
g_free (priv->name);
priv->name = NULL;
if (priv->supported_resolutions)
{
g_ptr_array_unref (priv->supported_resolutions);
priv->supported_resolutions = NULL;
}
G_OBJECT_CLASS (clutter_gst_camera_device_parent_class)->dispose (object);
}
static void
clutter_gst_camera_device_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
ClutterGstCameraDevice *actor = CLUTTER_GST_CAMERA_DEVICE (object);
ClutterGstCameraDevicePrivate *priv = actor->priv;
switch (property_id)
{
case PROP_ELEMENT_FACTORY:
g_value_set_object (value, priv->element_factory);
break;
case PROP_NODE:
g_value_set_string (value, priv->node);
break;
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
clutter_gst_camera_device_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
ClutterGstCameraDevice *actor = CLUTTER_GST_CAMERA_DEVICE (object);
ClutterGstCameraDevicePrivate *priv = actor->priv;
switch (property_id)
{
case PROP_ELEMENT_FACTORY:
if (priv->element_factory)
gst_object_unref (priv->element_factory);
priv->element_factory = gst_object_ref (GST_OBJECT (g_value_get_object (value)));
break;
case PROP_NODE:
g_free (priv->node);
priv->node = g_value_dup_string (value);
break;
case PROP_NAME:
g_free (priv->name);
priv->name = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
clutter_gst_camera_device_class_init (ClutterGstCameraDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
g_type_class_add_private (klass, sizeof (ClutterGstCameraDevicePrivate));
object_class->constructed = clutter_gst_camera_device_constructed;
object_class->dispose = clutter_gst_camera_device_dispose;
object_class->set_property = clutter_gst_camera_device_set_property;
object_class->get_property = clutter_gst_camera_device_get_property;
/**
* ClutterGstCameraDevice:element-factory:
*
* The GstElementFactory for this device.
*/
pspec = g_param_spec_object ("element-factory",
"ElementFactory",
"The GstElementFactory for this device",
GST_TYPE_ELEMENT_FACTORY,
CLUTTER_GST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_ELEMENT_FACTORY, pspec);
/**
* ClutterGstCameraDevice:node:
*
* The device node.
*/
pspec = g_param_spec_string ("node",
"Node",
"The device node",
NULL,
CLUTTER_GST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_NODE, pspec);
/**
* ClutterGstCameraDevice:name:
*
* The device name.
*/
pspec = g_param_spec_string ("name",
"Name",
"The device name",
NULL,
CLUTTER_GST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_property (object_class, PROP_NAME, pspec);
/**
* ClutterGstCameraDevice::capture-resolution-changed:
* @device: the device which received the signal
* @width: The new width
* @height: The new height
*
* The ::capture-resolution-changed signal is emitted whenever the value of
* clutter_gst_camera_device_get_capture_resolution changes.
*/
camera_device_signals[CAPTURE_RESOLUTION_CHANGED] =
g_signal_new ("capture-resolution-changed",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterGstCameraDeviceClass, capture_resolution_changed),
NULL, NULL,
_clutter_gst_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
}
static void
clutter_gst_camera_device_init (ClutterGstCameraDevice *device)
{
ClutterGstCameraDevicePrivate *priv;
device->priv = priv =
G_TYPE_INSTANCE_GET_PRIVATE (device,
CLUTTER_GST_TYPE_CAMERA_DEVICE,
ClutterGstCameraDevicePrivate);
}
/*
* Public symbols
*/
/**
* clutter_gst_camera_device_get_node:
* @device: a #ClutterGstCameraDevice
*
* Retrieve the node (location) of the @device.
*
* Return value: (transfer none): the device node.
*/
const gchar *
clutter_gst_camera_device_get_node (ClutterGstCameraDevice *device)
{
g_return_val_if_fail (CLUTTER_GST_IS_CAMERA_DEVICE (device), NULL);
return device->priv->node;
}
/**
* clutter_gst_camera_device_get_name:
* @device: a #ClutterGstCameraDevice
*
* Retrieve the name of the @device.
*
* Return value: (transfer none): the device name.
*/
const gchar *
clutter_gst_camera_device_get_name (ClutterGstCameraDevice *device)
{
g_return_val_if_fail (CLUTTER_GST_IS_CAMERA_DEVICE (device), NULL);
return device->priv->name;
}
/**
* clutter_gst_camera_device_get_supported_resolutions:
* @device: a #ClutterGstCameraDevice
*
* Retrieve the supported resolutions of the @device.
*
* Return value: (transfer none) (element-type ClutterGst.VideoResolution): an array of #ClutterGstVideoResolution with the
* supported resolutions.
*/
const GPtrArray *
clutter_gst_camera_device_get_supported_resolutions (ClutterGstCameraDevice *device)
{
g_return_val_if_fail (CLUTTER_GST_IS_CAMERA_DEVICE (device), NULL);
return device->priv->supported_resolutions;
}
/**
* clutter_gst_camera_device_get_capture_resolution:
* @device: a #ClutterGstCameraDevice
* @width: (out): Pointer to store the current capture resolution width
* @height: (out): Pointer to store the current capture resolution height
*
* Retrieve the current capture resolution being used by @device.
*/
void
clutter_gst_camera_device_get_capture_resolution (ClutterGstCameraDevice *device,
gint *width,
gint *height)
{
ClutterGstCameraDevicePrivate *priv;
g_return_if_fail (CLUTTER_GST_IS_CAMERA_DEVICE (device));
priv = device->priv;
if (width)
*width = priv->capture_width;
if (height)
*height = priv->capture_height;
}
/**
* clutter_gst_camera_device_set_capture_resolution:
* @device: a #ClutterGstCameraDevice
* @width: The new capture resolution width to use
* @height: The new capture resolution height to use
*
* Set the capture resolution to be used by @device.
*/
void
clutter_gst_camera_device_set_capture_resolution (ClutterGstCameraDevice *device,
gint width,
gint height)
{
ClutterGstCameraDevicePrivate *priv;
g_return_if_fail (CLUTTER_GST_IS_CAMERA_DEVICE (device));
priv = device->priv;
priv->capture_width = width;
priv->capture_height = height;
g_signal_emit (device,
camera_device_signals[CAPTURE_RESOLUTION_CHANGED], 0,
width, height);
}