1 /*
2  * Clutter-GStreamer.
3  *
4  * GStreamer integration library for Clutter.
5  *
6  * clutter-gst-camera-manager.c - a component to list available cameras
7  *
8  * Authored By Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com>
9  *
10  * Copyright (C) 2013 Intel Corporation
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27 
28 /**
29  * SECTION:clutter-gst-camera-manager
30  * @short_description: A component to list available cameras.
31  *
32  * #ClutterGstCameraManager lists available cameras.
33  */
34 
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
38 
39 #include <string.h>
40 
41 #include <gst/gst.h>
42 #ifdef HAVE_GUDEV
43 # include <gudev/gudev.h>
44 #endif
45 
46 #include "clutter-gst-camera-manager.h"
47 #include "clutter-gst-camera-device.h"
48 
49 G_DEFINE_TYPE (ClutterGstCameraManager, clutter_gst_camera_manager, G_TYPE_OBJECT)
50 
51 #define GST_CAMERA_MANAGER_PRIVATE(o) \
52   (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLUTTER_GST_TYPE_CAMERA_MANAGER, ClutterGstCameraManagerPrivate))
53 
54 struct _ClutterGstCameraManagerPrivate
55 {
56   GPtrArray *camera_devices;
57 #ifdef HAVE_GUDEV
58     GUdevClient *udev_client;
59 #endif
60 };
61 
62 enum
63 {
64   CAMERA_ADDED,
65   CAMERA_REMOVED,
66   LAST_SIGNAL
67 };
68 
69 static int signals[LAST_SIGNAL] = { 0 };
70 
71 static void
clutter_gst_camera_manager_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)72 clutter_gst_camera_manager_get_property (GObject    *object,
73                                          guint       property_id,
74                                          GValue     *value,
75                                          GParamSpec *pspec)
76 {
77   switch (property_id)
78     {
79     default:
80       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
81     }
82 }
83 
84 static void
clutter_gst_camera_manager_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)85 clutter_gst_camera_manager_set_property (GObject      *object,
86                                          guint         property_id,
87                                          const GValue *value,
88                                          GParamSpec   *pspec)
89 {
90   switch (property_id)
91     {
92     default:
93       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
94     }
95 }
96 
97 static void
clutter_gst_camera_manager_dispose(GObject * object)98 clutter_gst_camera_manager_dispose (GObject *object)
99 {
100   ClutterGstCameraManagerPrivate *priv = CLUTTER_GST_CAMERA_MANAGER (object)->priv;
101 
102   if (priv->camera_devices)
103     {
104       g_ptr_array_unref (priv->camera_devices);
105       priv->camera_devices = NULL;
106     }
107 
108 #if HAVE_GUDEV
109   g_clear_object (&priv->udev_client);
110 #endif
111 
112   G_OBJECT_CLASS (clutter_gst_camera_manager_parent_class)->dispose (object);
113 }
114 
115 static void
clutter_gst_camera_manager_finalize(GObject * object)116 clutter_gst_camera_manager_finalize (GObject *object)
117 {
118   G_OBJECT_CLASS (clutter_gst_camera_manager_parent_class)->finalize (object);
119 }
120 
121 static void
clutter_gst_camera_manager_class_init(ClutterGstCameraManagerClass * klass)122 clutter_gst_camera_manager_class_init (ClutterGstCameraManagerClass *klass)
123 {
124   GObjectClass *object_class = G_OBJECT_CLASS (klass);
125 
126   g_type_class_add_private (klass, sizeof (ClutterGstCameraManagerPrivate));
127 
128   object_class->get_property = clutter_gst_camera_manager_get_property;
129   object_class->set_property = clutter_gst_camera_manager_set_property;
130   object_class->dispose = clutter_gst_camera_manager_dispose;
131   object_class->finalize = clutter_gst_camera_manager_finalize;
132 
133   /**
134    * ClutterGstCameraManager::camera-added:
135    * @self: the actor camera manager
136    * @camera_device: a camera device added
137    *
138    * The ::camera-added signal is emitted whenever a new camera device
139    * is available.
140    */
141   signals[CAMERA_ADDED] =
142     g_signal_new ("camera-added",
143                   G_TYPE_FROM_CLASS (object_class),
144                   G_SIGNAL_RUN_LAST,
145                   0,
146                   NULL, NULL,
147                   g_cclosure_marshal_VOID__OBJECT,
148                   G_TYPE_NONE, 1,
149                   CLUTTER_GST_TYPE_CAMERA_DEVICE);
150 
151   /**
152    * ClutterGstCameraManager::camera-removed:
153    * @self: the actor camera manager
154    * @camera_device: a camera device
155    *
156    * The ::camera-removed signal is emitted whenever a camera device
157    * is unplugged/removed from the system.
158    */
159   signals[CAMERA_REMOVED] =
160     g_signal_new ("camera-removed",
161                   G_TYPE_FROM_CLASS (object_class),
162                   G_SIGNAL_RUN_LAST,
163                   0,
164                   NULL, NULL,
165                   g_cclosure_marshal_VOID__OBJECT,
166                   G_TYPE_NONE, 1,
167                   CLUTTER_GST_TYPE_CAMERA_DEVICE);
168 
169 }
170 
171 static void
add_device(ClutterGstCameraManager * self,const gchar * device_node,const gchar * device_name)172 add_device (ClutterGstCameraManager *self,
173             const gchar             *device_node,
174             const gchar             *device_name)
175 {
176   ClutterGstCameraManagerPrivate *priv = self->priv;
177   ClutterGstCameraDevice *device;
178   GstElement *videosrc;
179   GstElementFactory *factory;
180   GParamSpec *pspec;
181 
182   videosrc = gst_element_factory_make ("v4l2src", "v4l2src");
183   if (!videosrc)
184     {
185       g_warning ("Unable to get available camera devices, "
186                  "v4l2src element missing");
187       return;
188     }
189 
190   pspec = g_object_class_find_property (
191                                         G_OBJECT_GET_CLASS (G_OBJECT (videosrc)), "device");
192   if (!G_IS_PARAM_SPEC_STRING (pspec))
193     {
194       g_warning ("Unable to get available camera devices, "
195                  "v4l2src has no 'device' property");
196       goto out;
197     }
198 
199   factory = gst_element_get_factory (videosrc);
200 
201   device = g_object_new (CLUTTER_GST_TYPE_CAMERA_DEVICE,
202                          "element-factory", factory,
203                          "node", device_node,
204                          "name", device_name,
205                          NULL);
206   g_ptr_array_add (priv->camera_devices, device);
207 
208   g_signal_emit (self, signals[CAMERA_ADDED], 0, device);
209 
210  out:
211   gst_object_unref (videosrc);
212 }
213 
214 #ifdef HAVE_GUDEV
215 static void
remove_device(ClutterGstCameraManager * self,const gchar * device_node,const gchar * device_name)216 remove_device (ClutterGstCameraManager *self,
217                const gchar             *device_node,
218                const gchar             *device_name)
219 {
220   ClutterGstCameraManagerPrivate *priv = self->priv;
221   guint i;
222 
223   for (i = 0; i < priv->camera_devices->len; i++)
224     {
225       ClutterGstCameraDevice *device =
226         g_ptr_array_index (priv->camera_devices, i);
227 
228       if (!g_strcmp0 (clutter_gst_camera_device_get_node (device), device_node) &&
229           !g_strcmp0 (clutter_gst_camera_device_get_name (device), device_name))
230         {
231           g_signal_emit (self, signals[CAMERA_REMOVED], 0, device);
232           g_ptr_array_remove_index (priv->camera_devices, i);
233           break;
234         }
235     }
236 }
237 
238 static gboolean
is_supported_device(GUdevDevice * udevice)239 is_supported_device (GUdevDevice *udevice)
240 {
241   const char *caps;
242 
243   if (g_strcmp0 (g_udev_device_get_subsystem (udevice), "video4linux") != 0)
244     return FALSE;
245 
246   if (g_udev_device_get_property_as_int (udevice, "ID_V4L_VERSION") != 2)
247     return FALSE;
248 
249   caps = g_udev_device_get_property (udevice, "ID_V4L_CAPABILITIES");
250   if (caps == NULL || strstr (caps, ":capture:") == NULL)
251     return FALSE;
252 
253   return TRUE;
254 }
255 
256 static void
udev_event(GUdevClient * client,gchar * action,GUdevDevice * udevice,ClutterGstCameraManager * self)257 udev_event (GUdevClient             *client,
258             gchar                   *action,
259             GUdevDevice             *udevice,
260             ClutterGstCameraManager *self)
261 {
262   if (!is_supported_device (udevice))
263     return;
264 
265   if (!g_strcmp0 (action, "add"))
266     add_device (self,
267                 g_udev_device_get_device_file (udevice),
268                 g_udev_device_get_property (udevice, "ID_V4L_PRODUCT"));
269   else if (!g_strcmp0 (action, "remove"))
270     remove_device (self,
271                    g_udev_device_get_device_file (udevice),
272                    g_udev_device_get_property (udevice, "ID_V4L_PRODUCT"));
273 }
274 #endif
275 
276 static gboolean
probe_camera_devices(ClutterGstCameraManager * self)277 probe_camera_devices (ClutterGstCameraManager *self)
278 {
279   static const gchar *subsystems[] = { "video4linux", NULL };
280   ClutterGstCameraManagerPrivate *priv = self->priv;
281 #ifdef HAVE_GUDEV
282   GList *udevices, *l;
283 #else
284   gchar *device_node;
285   gchar *device_name;
286   GstElement *videosrc;
287 #endif
288 
289 #ifdef HAVE_GUDEV
290   priv->udev_client = g_udev_client_new (subsystems);
291   g_signal_connect (priv->udev_client, "uevent",
292                     G_CALLBACK (udev_event), self);
293 
294   udevices = g_udev_client_query_by_subsystem (priv->udev_client, "video4linux");
295   for (l = udevices; l != NULL; l = l->next)
296     {
297       GUdevDevice *udevice = (GUdevDevice *) l->data;
298 
299       udev_event (priv->udev_client, (gchar *) "add", udevice, self);
300 
301       g_object_unref (udevice);
302     }
303   g_list_free (udevices);
304 #else
305   videosrc = gst_element_factory_make ("v4l2src", "v4l2src");
306   if (!videosrc)
307     {
308       g_warning ("Unable to get available camera devices, "
309                  "v4l2src element missing");
310       return FALSE;
311     }
312 
313   /* GStreamer 1.0 does not support property probe, adding default detected
314    * device as only known device */
315   g_object_get (videosrc, "device", &device_node, NULL);
316   g_object_get (videosrc, "device-name", &device_name, NULL);
317   add_device (self, device_node, device_name);
318 
319   g_free (device_node);
320   g_free (device_name);
321 
322  out:
323   gst_object_unref (videosrc);
324 #endif
325 
326   return (priv->camera_devices != NULL);
327 }
328 
329 static void
clutter_gst_camera_manager_init(ClutterGstCameraManager * self)330 clutter_gst_camera_manager_init (ClutterGstCameraManager *self)
331 {
332   self->priv = GST_CAMERA_MANAGER_PRIVATE (self);
333 
334   self->priv->camera_devices =
335     g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
336 
337   probe_camera_devices (self);
338 }
339 
340 /**
341  * clutter_gst_camera_manager_get_default:
342  *
343  * Get the camera manager.
344  *
345  * <note>This function has to be called from Clutter's main
346  * thread.</note>
347  *
348  * Return value: (transfer none): the default camera manager.
349  */
350 ClutterGstCameraManager *
clutter_gst_camera_manager_get_default(void)351 clutter_gst_camera_manager_get_default (void)
352 {
353   static ClutterGstCameraManager *manager = NULL;
354 
355   if (G_UNLIKELY (manager == NULL))
356     manager = g_object_new (CLUTTER_GST_TYPE_CAMERA_MANAGER, NULL);
357 
358   return manager;
359 }
360 
361 /**
362  * clutter_gst_camera_manager_get_camera_devices:
363  * @self: a #ClutterGstCameraManager
364  *
365  * Retrieve an array of supported camera devices.
366  *
367  * Return value: (transfer none) (element-type ClutterGst.CameraDevice): An array of #ClutterGstCameraDevice representing
368  *                                the supported camera devices
369  */
370 const GPtrArray *
clutter_gst_camera_manager_get_camera_devices(ClutterGstCameraManager * self)371 clutter_gst_camera_manager_get_camera_devices (ClutterGstCameraManager *self)
372 {
373   g_return_val_if_fail (CLUTTER_GST_IS_CAMERA_MANAGER (self), NULL);
374 
375   return self->priv->camera_devices;
376 }
377