videobox: Add unit test
[platform/upstream/gst-plugins-good.git] / sys / v4l2 / gstv4l2deviceprovider.c
1 /* GStreamer
2  * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
3  *
4  * gstv4l2deviceprovider.c: V4l2 device probing and monitoring
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "gstv4l2deviceprovider.h"
27
28 #include <string.h>
29 #include <sys/stat.h>
30
31 #include <gst/gst.h>
32
33 #include "gstv4l2object.h"
34 #include "v4l2_calls.h"
35 #include "v4l2-utils.h"
36
37 #ifdef HAVE_GUDEV
38 #include <gudev/gudev.h>
39 #endif
40
41 static GstV4l2Device *gst_v4l2_device_new (const gchar * device_path,
42     const gchar * device_name, GstCaps * caps, GstV4l2DeviceType type);
43
44
45 G_DEFINE_TYPE (GstV4l2DeviceProvider, gst_v4l2_device_provider,
46     GST_TYPE_DEVICE_PROVIDER);
47
48 static void gst_v4l2_device_provider_finalize (GObject * object);
49 static GList *gst_v4l2_device_provider_probe (GstDeviceProvider * provider);
50
51 #if HAVE_GUDEV
52 static gboolean gst_v4l2_device_provider_start (GstDeviceProvider * provider);
53 static void gst_v4l2_device_provider_stop (GstDeviceProvider * provider);
54 #endif
55
56
57 static void
58 gst_v4l2_device_provider_class_init (GstV4l2DeviceProviderClass * klass)
59 {
60   GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
61   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
62
63   dm_class->probe = gst_v4l2_device_provider_probe;
64
65 #if HAVE_GUDEV
66   dm_class->start = gst_v4l2_device_provider_start;
67   dm_class->stop = gst_v4l2_device_provider_stop;
68 #endif
69
70   gobject_class->finalize = gst_v4l2_device_provider_finalize;
71
72   gst_device_provider_class_set_static_metadata (dm_class,
73       "Video (video4linux2) Device Provider", "Source/Sink/Video",
74       "List and monitor video4linux2 source and sink devices",
75       "Olivier Crete <olivier.crete@collabora.com>");
76 }
77
78 static void
79 gst_v4l2_device_provider_init (GstV4l2DeviceProvider * provider)
80 {
81 #if HAVE_GUDEV
82   g_cond_init (&provider->started_cond);
83 #endif
84 }
85
86 static void
87 gst_v4l2_device_provider_finalize (GObject * object)
88 {
89 #if HAVE_GUDEV
90   GstV4l2DeviceProvider *provider = GST_V4L2_DEVICE_PROVIDER (object);
91
92   g_cond_clear (&provider->started_cond);
93 #endif
94
95   G_OBJECT_CLASS (gst_v4l2_device_provider_parent_class)->finalize (object);
96 }
97
98 static GstV4l2Device *
99 gst_v4l2_device_provider_probe_device (GstV4l2DeviceProvider * provider,
100     const gchar * device_path, const gchar * device_name)
101 {
102   GstV4l2Object *v4l2obj;
103   GstCaps *caps;
104   GstV4l2Device *device = NULL;
105   struct stat st;
106   GstV4l2DeviceType type = GST_V4L2_DEVICE_TYPE_INVALID;
107
108   if (stat (device_path, &st) == -1)
109     return NULL;
110
111   if (!S_ISCHR (st.st_mode))
112     return NULL;
113
114   v4l2obj = gst_v4l2_object_new ((GstElement *) provider,
115       V4L2_BUF_TYPE_VIDEO_CAPTURE, device_path, NULL, NULL, NULL);
116
117   if (!gst_v4l2_open (v4l2obj))
118     goto destroy;
119
120
121   if (v4l2obj->vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
122     type = GST_V4L2_DEVICE_TYPE_SOURCE;
123
124   if (v4l2obj->vcap.capabilities & V4L2_CAP_VIDEO_OUTPUT) {
125     /* Morph it in case our initial guess was wrong */
126     v4l2obj->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
127
128     if (type == GST_V4L2_DEVICE_TYPE_INVALID)
129       type = GST_V4L2_DEVICE_TYPE_SINK;
130     else
131       /* We ignore M2M devices that are both capture and output for now
132        * The provider is not for them
133        */
134       goto close;
135   }
136
137   caps = gst_v4l2_object_get_caps (v4l2obj, NULL);
138
139   if (caps == NULL)
140     goto close;
141   if (gst_caps_is_empty (caps)) {
142     gst_caps_unref (caps);
143     goto close;
144   }
145
146   device = gst_v4l2_device_new (device_path,
147       device_name ? device_name : (gchar *) v4l2obj->vcap.card, caps, type);
148   gst_caps_unref (caps);
149
150 close:
151
152   gst_v4l2_close (v4l2obj);
153
154 destroy:
155
156   gst_v4l2_object_destroy (v4l2obj);
157
158   return device;
159 }
160
161
162 static GList *
163 gst_v4l2_device_provider_probe (GstDeviceProvider * provider)
164 {
165   GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
166   GstV4l2Iterator *it;
167   GList *devices = NULL;
168
169   it = gst_v4l2_iterator_new ();
170
171   while (gst_v4l2_iterator_next (it)) {
172     GstV4l2Device *device;
173
174     device =
175         gst_v4l2_device_provider_probe_device (self, it->device_path, NULL);
176
177     if (device) {
178       gst_object_ref_sink (device);
179       devices = g_list_prepend (devices, device);
180     }
181   }
182
183   gst_v4l2_iterator_free (it);
184
185   return devices;
186 }
187
188 #if HAVE_GUDEV
189
190 static GstDevice *
191 gst_v4l2_device_provider_device_from_udev (GstV4l2DeviceProvider * provider,
192     GUdevDevice * udev_device)
193 {
194   GstV4l2Device *gstdev;
195   const gchar *device_path = g_udev_device_get_device_file (udev_device);
196   const gchar *device_name;
197
198   device_name = g_udev_device_get_property (udev_device, "ID_V4L_PRODUCT");
199   if (!device_name)
200     device_name = g_udev_device_get_property (udev_device, "ID_MODEL_ENC");
201   if (!device_name)
202     device_name = g_udev_device_get_property (udev_device, "ID_MODEL");
203
204   gstdev = gst_v4l2_device_provider_probe_device (provider, device_path,
205       device_name);
206
207   if (gstdev)
208     gstdev->syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device));
209
210   return GST_DEVICE (gstdev);
211 }
212
213 static void
214 uevent_cb (GUdevClient * client, const gchar * action, GUdevDevice * device,
215     GstV4l2DeviceProvider * self)
216 {
217   GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self);
218
219   /* Not V4L2, ignoring */
220   if (g_udev_device_get_property_as_int (device, "ID_V4L_VERSION") != 2)
221     return;
222
223   if (!strcmp (action, "add")) {
224     GstDevice *gstdev = NULL;
225
226     gstdev = gst_v4l2_device_provider_device_from_udev (self, device);
227
228     if (gstdev)
229       gst_device_provider_device_add (provider, gstdev);
230   } else if (!strcmp (action, "remove")) {
231     GstV4l2Device *gstdev = NULL;
232     GList *item;
233
234     GST_OBJECT_LOCK (self);
235     for (item = provider->devices; item; item = item->next) {
236       gstdev = item->data;
237
238       if (!strcmp (gstdev->syspath, g_udev_device_get_sysfs_path (device))) {
239         gst_object_ref (gstdev);
240         break;
241       }
242
243       gstdev = NULL;
244     }
245     GST_OBJECT_UNLOCK (provider);
246
247     if (gstdev) {
248       gst_device_provider_device_remove (provider, GST_DEVICE (gstdev));
249       g_object_unref (gstdev);
250     }
251   } else {
252     GST_WARNING ("Unhandled action %s", action);
253   }
254 }
255
256 static gpointer
257 provider_thread (gpointer data)
258 {
259   GstV4l2DeviceProvider *provider = data;
260   GMainContext *context = NULL;
261   GMainLoop *loop = NULL;
262   GUdevClient *client;
263   GList *devices;
264   static const gchar *subsystems[] = { "video4linux", NULL };
265
266   GST_OBJECT_LOCK (provider);
267   if (provider->context)
268     context = g_main_context_ref (provider->context);
269   if (provider->loop)
270     loop = g_main_loop_ref (provider->loop);
271
272   if (context == NULL || loop == NULL) {
273     provider->started = TRUE;
274     g_cond_broadcast (&provider->started_cond);
275     GST_OBJECT_UNLOCK (provider);
276     return NULL;
277   }
278   GST_OBJECT_UNLOCK (provider);
279
280   g_main_context_push_thread_default (context);
281
282   client = g_udev_client_new (subsystems);
283
284   g_signal_connect (client, "uevent", G_CALLBACK (uevent_cb), provider);
285
286   devices = g_udev_client_query_by_subsystem (client, "video4linux");
287
288   while (devices) {
289     GUdevDevice *udev_device = devices->data;
290     GstDevice *gstdev;
291
292     devices = g_list_remove (devices, udev_device);
293
294     if (g_udev_device_get_property_as_int (udev_device, "ID_V4L_VERSION") == 2) {
295       gstdev =
296           gst_v4l2_device_provider_device_from_udev (provider, udev_device);
297       if (gstdev)
298         gst_device_provider_device_add (GST_DEVICE_PROVIDER (provider), gstdev);
299     }
300
301     g_object_unref (udev_device);
302   }
303
304   GST_OBJECT_LOCK (provider);
305   provider->started = TRUE;
306   g_cond_broadcast (&provider->started_cond);
307   GST_OBJECT_UNLOCK (provider);
308
309   g_main_loop_run (loop);
310   g_main_loop_unref (loop);
311
312   g_object_unref (client);
313   g_main_context_unref (context);
314
315   gst_object_unref (provider);
316
317   return NULL;
318 }
319
320 static gboolean
321 gst_v4l2_device_provider_start (GstDeviceProvider * provider)
322 {
323   GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
324
325   GST_OBJECT_LOCK (self);
326   g_assert (self->context == NULL);
327
328   self->context = g_main_context_new ();
329   self->loop = g_main_loop_new (self->context, FALSE);
330
331   self->thread = g_thread_new ("v4l2-device-provider", provider_thread,
332       g_object_ref (self));
333
334   while (self->started == FALSE)
335     g_cond_wait (&self->started_cond, GST_OBJECT_GET_LOCK (self));
336
337   GST_OBJECT_UNLOCK (self);
338
339   return TRUE;
340 }
341
342 static void
343 gst_v4l2_device_provider_stop (GstDeviceProvider * provider)
344 {
345   GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
346   GMainContext *context;
347   GMainLoop *loop;
348   GSource *idle_stop_source;
349
350   GST_OBJECT_LOCK (self);
351   context = self->context;
352   loop = self->loop;
353   self->context = NULL;
354   self->loop = NULL;
355   GST_OBJECT_UNLOCK (self);
356
357   if (!context || !loop)
358     return;
359
360   idle_stop_source = g_idle_source_new ();
361   g_source_set_callback (idle_stop_source, (GSourceFunc) g_main_loop_quit, loop,
362       (GDestroyNotify) g_main_loop_unref);
363   g_source_attach (idle_stop_source, context);
364   g_source_unref (idle_stop_source);
365   g_main_context_unref (context);
366
367   g_thread_join (self->thread);
368   g_thread_unref (self->thread);
369   self->thread = NULL;
370   self->started = FALSE;
371 }
372
373 #endif
374
375 enum
376 {
377   PROP_DEVICE_PATH = 1,
378 };
379
380 G_DEFINE_TYPE (GstV4l2Device, gst_v4l2_device, GST_TYPE_DEVICE);
381
382 static void gst_v4l2_device_get_property (GObject * object, guint prop_id,
383     GValue * value, GParamSpec * pspec);
384 static void gst_v4l2_device_set_property (GObject * object, guint prop_id,
385     const GValue * value, GParamSpec * pspec);
386 static void gst_v4l2_device_finalize (GObject * object);
387 static GstElement *gst_v4l2_device_create_element (GstDevice * device,
388     const gchar * name);
389
390 static void
391 gst_v4l2_device_class_init (GstV4l2DeviceClass * klass)
392 {
393   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
394   GObjectClass *object_class = G_OBJECT_CLASS (klass);
395
396   dev_class->create_element = gst_v4l2_device_create_element;
397
398   object_class->get_property = gst_v4l2_device_get_property;
399   object_class->set_property = gst_v4l2_device_set_property;
400   object_class->finalize = gst_v4l2_device_finalize;
401
402   g_object_class_install_property (object_class, PROP_DEVICE_PATH,
403       g_param_spec_string ("device-path", "Device Path",
404           "The Path of the device node", "",
405           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
406 }
407
408 static void
409 gst_v4l2_device_init (GstV4l2Device * device)
410 {
411 }
412
413 static void
414 gst_v4l2_device_finalize (GObject * object)
415 {
416   GstV4l2Device *device = GST_V4L2_DEVICE (object);
417
418   g_free (device->device_path);
419   g_free (device->syspath);
420
421   G_OBJECT_CLASS (gst_v4l2_device_parent_class)->finalize (object);
422 }
423
424 static GstElement *
425 gst_v4l2_device_create_element (GstDevice * device, const gchar * name)
426 {
427   GstV4l2Device *v4l2_dev = GST_V4L2_DEVICE (device);
428   GstElement *elem;
429
430   elem = gst_element_factory_make (v4l2_dev->element, name);
431   g_object_set (elem, "device", v4l2_dev->device_path, NULL);
432
433   return elem;
434 }
435
436 static GstV4l2Device *
437 gst_v4l2_device_new (const gchar * device_path, const gchar * device_name,
438     GstCaps * caps, GstV4l2DeviceType type)
439 {
440   GstV4l2Device *gstdev;
441   const gchar *element;
442   const gchar *klass;
443
444   g_return_val_if_fail (device_path, NULL);
445   g_return_val_if_fail (device_name, NULL);
446   g_return_val_if_fail (caps, NULL);
447
448   switch (type) {
449     case GST_V4L2_DEVICE_TYPE_SOURCE:
450       element = "v4l2src";
451       klass = "Video/Source";
452       break;
453     case GST_V4L2_DEVICE_TYPE_SINK:
454       element = "v4l2sink";
455       klass = "Video/Sink";
456       break;
457     default:
458       g_assert_not_reached ();
459       break;
460   }
461
462   gstdev = g_object_new (GST_TYPE_V4L2_DEVICE, "device-path", device_path,
463       "display-name", device_name, "caps", caps, "device-class", klass, NULL);
464
465   gstdev->element = element;
466
467
468   return gstdev;
469 }
470
471
472 static void
473 gst_v4l2_device_get_property (GObject * object, guint prop_id,
474     GValue * value, GParamSpec * pspec)
475 {
476   GstV4l2Device *device;
477
478   device = GST_V4L2_DEVICE_CAST (object);
479
480   switch (prop_id) {
481     case PROP_DEVICE_PATH:
482       g_value_set_string (value, device->device_path);
483       break;
484     default:
485       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
486       break;
487   }
488 }
489
490
491 static void
492 gst_v4l2_device_set_property (GObject * object, guint prop_id,
493     const GValue * value, GParamSpec * pspec)
494 {
495   GstV4l2Device *device;
496
497   device = GST_V4L2_DEVICE_CAST (object);
498
499   switch (prop_id) {
500     case PROP_DEVICE_PATH:
501       device->device_path = g_value_dup_string (value);
502       break;
503     default:
504       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
505       break;
506   }
507 }