videobox: Add unit test
[platform/upstream/gst-plugins-good.git] / sys / v4l2 / gstv4l2devicemonitor.c
1 /* GStreamer
2  * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
3  *
4  * gstv4l2devicemonitor.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 "gstv4l2devicemonitor.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 (GstV4l2DeviceMonitor, gst_v4l2_device_monitor,
46     GST_TYPE_DEVICE_MONITOR);
47
48 static void gst_v4l2_device_monitor_finalize (GObject * object);
49 static GList *gst_v4l2_device_monitor_probe (GstDeviceMonitor * monitor);
50
51 #if HAVE_GUDEV
52 static gboolean gst_v4l2_device_monitor_start (GstDeviceMonitor * monitor);
53 static void gst_v4l2_device_monitor_stop (GstDeviceMonitor * monitor);
54 #endif
55
56
57 static void
58 gst_v4l2_device_monitor_class_init (GstV4l2DeviceMonitorClass * klass)
59 {
60   GstDeviceMonitorClass *dm_class = GST_DEVICE_MONITOR_CLASS (klass);
61   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
62
63   dm_class->probe = gst_v4l2_device_monitor_probe;
64
65 #if HAVE_GUDEV
66   dm_class->start = gst_v4l2_device_monitor_start;
67   dm_class->stop = gst_v4l2_device_monitor_stop;
68 #endif
69
70   gobject_class->finalize = gst_v4l2_device_monitor_finalize;
71
72   gst_device_monitor_class_set_static_metadata (dm_class,
73       "Video (video4linux2) Device Monitor", "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_monitor_init (GstV4l2DeviceMonitor * monitor)
80 {
81 #if HAVE_GUDEV
82   g_cond_init (&monitor->started_cond);
83 #endif
84 }
85
86 static void
87 gst_v4l2_device_monitor_finalize (GObject * object)
88 {
89 #if HAVE_GUDEV
90   GstV4l2DeviceMonitor *monitor = GST_V4L2_DEVICE_MONITOR (object);
91
92   g_cond_clear (&monitor->started_cond);
93 #endif
94
95   G_OBJECT_CLASS (gst_v4l2_device_monitor_parent_class)->finalize (object);
96 }
97
98 static GstV4l2Device *
99 gst_v4l2_device_monitor_probe_device (GstV4l2DeviceMonitor * monitor,
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 *) monitor,
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 monitor 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_monitor_probe (GstDeviceMonitor * monitor)
164 {
165   GstV4l2DeviceMonitor *self = GST_V4L2_DEVICE_MONITOR (monitor);
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 = gst_v4l2_device_monitor_probe_device (self, it->device_path, NULL);
175
176     if (device) {
177       gst_object_ref_sink (device);
178       devices = g_list_prepend (devices, device);
179     }
180   }
181
182   gst_v4l2_iterator_free (it);
183
184   return devices;
185 }
186
187 #if HAVE_GUDEV
188
189 static GstDevice *
190 gst_v4l2_device_monitor_device_from_udev (GstV4l2DeviceMonitor * monitor,
191     GUdevDevice * udev_device)
192 {
193   GstV4l2Device *gstdev;
194   const gchar *device_path = g_udev_device_get_device_file (udev_device);
195   const gchar *device_name;
196
197   device_name = g_udev_device_get_property (udev_device, "ID_V4L_PRODUCT");
198   if (!device_name)
199     device_name = g_udev_device_get_property (udev_device, "ID_MODEL_ENC");
200   if (!device_name)
201     device_name = g_udev_device_get_property (udev_device, "ID_MODEL");
202
203   gstdev = gst_v4l2_device_monitor_probe_device (monitor, device_path,
204       device_name);
205
206   if (gstdev)
207     gstdev->syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device));
208
209   return GST_DEVICE (gstdev);
210 }
211
212 static void
213 uevent_cb (GUdevClient * client, const gchar * action, GUdevDevice * device,
214     GstV4l2DeviceMonitor * self)
215 {
216   GstDeviceMonitor *monitor = GST_DEVICE_MONITOR (self);
217
218   /* Not V4L2, ignoring */
219   if (g_udev_device_get_property_as_int (device, "ID_V4L_VERSION") != 2)
220     return;
221
222   if (!strcmp (action, "add")) {
223     GstDevice *gstdev = NULL;
224
225     gstdev = gst_v4l2_device_monitor_device_from_udev (self, device);
226
227     if (gstdev)
228       gst_device_monitor_device_add (monitor, gstdev);
229   } else if (!strcmp (action, "remove")) {
230     GstV4l2Device *gstdev = NULL;
231     GList *item;
232
233     GST_OBJECT_LOCK (self);
234     for (item = monitor->devices; item; item = item->next) {
235       gstdev = item->data;
236
237       if (!strcmp (gstdev->syspath, g_udev_device_get_sysfs_path (device))) {
238         gst_object_ref (gstdev);
239         break;
240       }
241
242       gstdev = NULL;
243     }
244     GST_OBJECT_UNLOCK (monitor);
245
246     if (gstdev) {
247       gst_device_monitor_device_remove (monitor, GST_DEVICE (gstdev));
248       g_object_unref (gstdev);
249     }
250   } else {
251     GST_WARNING ("Unhandled action %s", action);
252   }
253 }
254
255 static gpointer
256 monitor_thread (gpointer data)
257 {
258   GstV4l2DeviceMonitor *monitor = data;
259   GMainContext *context = NULL;
260   GMainLoop *loop = NULL;
261   GUdevClient *client;
262   GList *devices;
263   static const gchar *subsystems[] = { "video4linux", NULL };
264
265   GST_OBJECT_LOCK (monitor);
266   if (monitor->context)
267     context = g_main_context_ref (monitor->context);
268   if (monitor->loop)
269     loop = g_main_loop_ref (monitor->loop);
270
271   if (context == NULL || loop == NULL) {
272     monitor->started = TRUE;
273     g_cond_broadcast (&monitor->started_cond);
274     GST_OBJECT_UNLOCK (monitor);
275     return NULL;
276   }
277   GST_OBJECT_UNLOCK (monitor);
278
279   g_main_context_push_thread_default (context);
280
281   client = g_udev_client_new (subsystems);
282
283   g_signal_connect (client, "uevent", G_CALLBACK (uevent_cb), monitor);
284
285   devices = g_udev_client_query_by_subsystem (client, "video4linux");
286
287   while (devices) {
288     GUdevDevice *udev_device = devices->data;
289     GstDevice *gstdev;
290
291     devices = g_list_remove (devices, udev_device);
292
293     if (g_udev_device_get_property_as_int (udev_device, "ID_V4L_VERSION") == 2) {
294       gstdev = gst_v4l2_device_monitor_device_from_udev (monitor, udev_device);
295       if (gstdev)
296         gst_device_monitor_device_add (GST_DEVICE_MONITOR (monitor), gstdev);
297     }
298
299     g_object_unref (udev_device);
300   }
301
302   GST_OBJECT_LOCK (monitor);
303   monitor->started = TRUE;
304   g_cond_broadcast (&monitor->started_cond);
305   GST_OBJECT_UNLOCK (monitor);
306
307   g_main_loop_run (loop);
308   g_main_loop_unref (loop);
309
310   g_object_unref (client);
311   g_main_context_unref (context);
312
313   gst_object_unref (monitor);
314
315   return NULL;
316 }
317
318 static gboolean
319 gst_v4l2_device_monitor_start (GstDeviceMonitor * monitor)
320 {
321   GstV4l2DeviceMonitor *self = GST_V4L2_DEVICE_MONITOR (monitor);
322
323   GST_OBJECT_LOCK (self);
324   g_assert (self->context == NULL);
325
326   self->context = g_main_context_new ();
327   self->loop = g_main_loop_new (self->context, FALSE);
328
329   self->thread = g_thread_new ("v4l2-device-monitor", monitor_thread,
330       g_object_ref (self));
331
332   while (self->started == FALSE)
333     g_cond_wait (&self->started_cond, GST_OBJECT_GET_LOCK (self));
334
335   GST_OBJECT_UNLOCK (self);
336
337   return TRUE;
338 }
339
340 static void
341 gst_v4l2_device_monitor_stop (GstDeviceMonitor * monitor)
342 {
343   GstV4l2DeviceMonitor *self = GST_V4L2_DEVICE_MONITOR (monitor);
344   GMainContext *context;
345   GMainLoop *loop;
346   GSource *idle_stop_source;
347
348   GST_OBJECT_LOCK (self);
349   context = self->context;
350   loop = self->loop;
351   self->context = NULL;
352   self->loop = NULL;
353   GST_OBJECT_UNLOCK (self);
354
355   if (!context || !loop)
356     return;
357
358   idle_stop_source = g_idle_source_new ();
359   g_source_set_callback (idle_stop_source, (GSourceFunc) g_main_loop_quit, loop,
360       (GDestroyNotify) g_main_loop_unref);
361   g_source_attach (idle_stop_source, context);
362   g_source_unref (idle_stop_source);
363   g_main_context_unref (context);
364
365   g_thread_join (self->thread);
366   g_thread_unref (self->thread);
367   self->thread = NULL;
368   self->started = FALSE;
369 }
370
371 #endif
372
373 enum
374 {
375   PROP_DEVICE_PATH = 1,
376 };
377
378 G_DEFINE_TYPE (GstV4l2Device, gst_v4l2_device, GST_TYPE_DEVICE);
379
380 static void gst_v4l2_device_get_property (GObject * object, guint prop_id,
381     GValue * value, GParamSpec * pspec);
382 static void gst_v4l2_device_set_property (GObject * object, guint prop_id,
383     const GValue * value, GParamSpec * pspec);
384 static void gst_v4l2_device_finalize (GObject * object);
385 static GstElement *gst_v4l2_device_create_element (GstDevice * device,
386     const gchar * name);
387
388 static void
389 gst_v4l2_device_class_init (GstV4l2DeviceClass * klass)
390 {
391   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
392   GObjectClass *object_class = G_OBJECT_CLASS (klass);
393
394   dev_class->create_element = gst_v4l2_device_create_element;
395
396   object_class->get_property = gst_v4l2_device_get_property;
397   object_class->set_property = gst_v4l2_device_set_property;
398   object_class->finalize = gst_v4l2_device_finalize;
399
400   g_object_class_install_property (object_class, PROP_DEVICE_PATH,
401       g_param_spec_string ("device-path", "Device Path",
402           "The Path of the device node", "",
403           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
404 }
405
406 static void
407 gst_v4l2_device_init (GstV4l2Device * device)
408 {
409 }
410
411 static void
412 gst_v4l2_device_finalize (GObject * object)
413 {
414   GstV4l2Device *device = GST_V4L2_DEVICE (object);
415
416   g_free (device->device_path);
417   g_free (device->syspath);
418
419   G_OBJECT_CLASS (gst_v4l2_device_parent_class)->finalize (object);
420 }
421
422 static GstElement *
423 gst_v4l2_device_create_element (GstDevice * device, const gchar * name)
424 {
425   GstV4l2Device *v4l2_dev = GST_V4L2_DEVICE (device);
426   GstElement *elem;
427
428   elem = gst_element_factory_make (v4l2_dev->element, name);
429   g_object_set (elem, "device", v4l2_dev->device_path, NULL);
430
431   return elem;
432 }
433
434 static GstV4l2Device *
435 gst_v4l2_device_new (const gchar * device_path, const gchar * device_name,
436     GstCaps * caps, GstV4l2DeviceType type)
437 {
438   GstV4l2Device *gstdev;
439   const gchar *element;
440   const gchar *klass;
441
442   g_return_val_if_fail (device_path, NULL);
443   g_return_val_if_fail (device_name, NULL);
444   g_return_val_if_fail (caps, NULL);
445
446   switch (type) {
447     case GST_V4L2_DEVICE_TYPE_SOURCE:
448       element = "v4l2src";
449       klass = "Video/Source";
450       break;
451     case GST_V4L2_DEVICE_TYPE_SINK:
452       element = "v4l2sink";
453       klass = "Video/Sink";
454       break;
455     default:
456       g_assert_not_reached ();
457       break;
458   }
459
460   gstdev = g_object_new (GST_TYPE_V4L2_DEVICE, "device-path", device_path,
461       "display-name", device_name, "caps", caps, "device-class", klass, NULL);
462
463   gstdev->element = element;
464
465
466   return gstdev;
467 }
468
469
470 static void
471 gst_v4l2_device_get_property (GObject * object, guint prop_id,
472     GValue * value, GParamSpec * pspec)
473 {
474   GstV4l2Device *device;
475
476   device = GST_V4L2_DEVICE_CAST (object);
477
478   switch (prop_id) {
479     case PROP_DEVICE_PATH:
480       g_value_set_string (value, device->device_path);
481       break;
482     default:
483       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
484       break;
485   }
486 }
487
488
489 static void
490 gst_v4l2_device_set_property (GObject * object, guint prop_id,
491     const GValue * value, GParamSpec * pspec)
492 {
493   GstV4l2Device *device;
494
495   device = GST_V4L2_DEVICE_CAST (object);
496
497   switch (prop_id) {
498     case PROP_DEVICE_PATH:
499       device->device_path = g_value_dup_string (value);
500       break;
501     default:
502       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
503       break;
504   }
505 }