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-utils.h"
35 
36 #ifdef HAVE_GUDEV
37 #include <gudev/gudev.h>
38 #endif
39 
40 static GstV4l2Device *gst_v4l2_device_new (const gchar * device_path,
41     const gchar * device_name, GstCaps * caps, GstV4l2DeviceType type,
42     GstStructure * props);
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 #ifdef 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
gst_v4l2_device_provider_class_init(GstV4l2DeviceProviderClass * klass)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 #ifdef 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
gst_v4l2_device_provider_init(GstV4l2DeviceProvider * provider)79 gst_v4l2_device_provider_init (GstV4l2DeviceProvider * provider)
80 {
81 #ifdef HAVE_GUDEV
82   g_cond_init (&provider->started_cond);
83 #endif
84 }
85 
86 static void
gst_v4l2_device_provider_finalize(GObject * object)87 gst_v4l2_device_provider_finalize (GObject * object)
88 {
89 #ifdef 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 *
gst_v4l2_device_provider_probe_device(GstV4l2DeviceProvider * provider,const gchar * device_path,const gchar * device_name,GstStructure * props)99 gst_v4l2_device_provider_probe_device (GstV4l2DeviceProvider * provider,
100     const gchar * device_path, const gchar * device_name, GstStructure * props)
101 {
102   GstV4l2Object *v4l2obj = NULL;
103   GstCaps *caps;
104   GstV4l2Device *device = NULL;
105   struct stat st;
106   GstV4l2DeviceType type = GST_V4L2_DEVICE_TYPE_INVALID;
107 
108   g_return_val_if_fail (props != NULL, NULL);
109 
110   if (stat (device_path, &st) == -1)
111     goto destroy;
112 
113   if (!S_ISCHR (st.st_mode))
114     goto destroy;
115 
116   v4l2obj = gst_v4l2_object_new (NULL, GST_OBJECT (provider),
117       V4L2_BUF_TYPE_VIDEO_CAPTURE, device_path, NULL, NULL, NULL);
118 
119   if (!gst_v4l2_open (v4l2obj))
120     goto destroy;
121 
122   gst_structure_set (props, "device.api", G_TYPE_STRING, "v4l2", NULL);
123   gst_structure_set (props, "device.path", G_TYPE_STRING, device_path, NULL);
124 
125   gst_structure_set (props, "v4l2.device.driver", G_TYPE_STRING,
126       v4l2obj->vcap.driver, NULL);
127   gst_structure_set (props, "v4l2.device.card", G_TYPE_STRING,
128       v4l2obj->vcap.card, NULL);
129   gst_structure_set (props, "v4l2.device.bus_info", G_TYPE_STRING,
130       v4l2obj->vcap.bus_info, NULL);
131   gst_structure_set (props, "v4l2.device.version", G_TYPE_UINT,
132       v4l2obj->vcap.version, NULL);
133   gst_structure_set (props, "v4l2.device.capabilities", G_TYPE_UINT,
134       v4l2obj->vcap.capabilities, NULL);
135   gst_structure_set (props, "v4l2.device.device_caps", G_TYPE_UINT,
136       v4l2obj->vcap.device_caps, NULL);
137 
138   if (v4l2obj->device_caps &
139       (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
140     /* We ignore touch sensing devices; those are't really video */
141     if (v4l2obj->device_caps & V4L2_CAP_TOUCH)
142       goto close;
143 
144     type = GST_V4L2_DEVICE_TYPE_SOURCE;
145     v4l2obj->skip_try_fmt_probes = TRUE;
146   }
147 
148   if (v4l2obj->device_caps &
149       (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE)) {
150     /* We ignore M2M devices that are both capture and output for now
151      * The provider is not for them */
152     if (type != GST_V4L2_DEVICE_TYPE_INVALID)
153       goto close;
154 
155     type = GST_V4L2_DEVICE_TYPE_SINK;
156 
157     /* We have opened as a capture as we didn't know, now that know,
158      * let's fixed it */
159     if (v4l2obj->device_caps & V4L2_CAP_VIDEO_OUTPUT_MPLANE)
160       v4l2obj->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
161     else
162       v4l2obj->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
163   }
164 
165   if (type == GST_V4L2_DEVICE_TYPE_INVALID)
166     goto close;
167 
168   caps = gst_v4l2_object_get_caps (v4l2obj, NULL);
169 
170   if (caps == NULL)
171     goto close;
172   if (gst_caps_is_empty (caps)) {
173     gst_caps_unref (caps);
174     goto close;
175   }
176 
177   device = gst_v4l2_device_new (device_path,
178       device_name ? device_name : (gchar *) v4l2obj->vcap.card, caps, type,
179       props);
180   gst_caps_unref (caps);
181 
182 close:
183 
184   gst_v4l2_close (v4l2obj);
185 
186 destroy:
187 
188   if (v4l2obj)
189     gst_v4l2_object_destroy (v4l2obj);
190 
191   if (props)
192     gst_structure_free (props);
193 
194   return device;
195 }
196 
197 
198 static GList *
gst_v4l2_device_provider_probe(GstDeviceProvider * provider)199 gst_v4l2_device_provider_probe (GstDeviceProvider * provider)
200 {
201   GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
202   GstV4l2Iterator *it;
203   GList *devices = NULL;
204 
205   it = gst_v4l2_iterator_new ();
206 
207   while (gst_v4l2_iterator_next (it)) {
208     GstStructure *props;
209     GstV4l2Device *device;
210 
211     props = gst_structure_new ("v4l2-proplist", "device.path", G_TYPE_STRING,
212         it->device_path, "udev-probed", G_TYPE_BOOLEAN, FALSE, NULL);
213     device = gst_v4l2_device_provider_probe_device (self, it->device_path, NULL,
214         props);
215 
216     if (device) {
217       gst_object_ref_sink (device);
218       devices = g_list_prepend (devices, device);
219     }
220   }
221 
222   gst_v4l2_iterator_free (it);
223 
224   return devices;
225 }
226 
227 #ifdef HAVE_GUDEV
228 
229 static GstDevice *
gst_v4l2_device_provider_device_from_udev(GstV4l2DeviceProvider * provider,GUdevDevice * udev_device)230 gst_v4l2_device_provider_device_from_udev (GstV4l2DeviceProvider * provider,
231     GUdevDevice * udev_device)
232 {
233   GstV4l2Device *gstdev;
234   const gchar *device_path = g_udev_device_get_device_file (udev_device);
235   const gchar *device_name, *str;
236   GstStructure *props;
237 
238   props = gst_structure_new ("v4l2deviceprovider", "udev-probed",
239       G_TYPE_BOOLEAN, TRUE, NULL);
240 
241   str = g_udev_device_get_property (udev_device, "ID_PATH");
242   if (!(str && *str)) {
243     str = g_udev_device_get_sysfs_path (udev_device);
244   }
245   if (str && *str)
246     gst_structure_set (props, "device.bus_path", G_TYPE_STRING, str, NULL);
247 
248   if ((str = g_udev_device_get_sysfs_path (udev_device)) && *str)
249     gst_structure_set (props, "sysfs.path", G_TYPE_STRING, str, NULL);
250 
251   if ((str = g_udev_device_get_property (udev_device, "ID_ID")) && *str)
252     gst_structure_set (props, "udev.id", G_TYPE_STRING, str, NULL);
253 
254   if ((str = g_udev_device_get_property (udev_device, "ID_BUS")) && *str)
255     gst_structure_set (props, "device.bus", G_TYPE_STRING, str, NULL);
256 
257   if ((str = g_udev_device_get_property (udev_device, "SUBSYSTEM")) && *str)
258     gst_structure_set (props, "device.subsystem", G_TYPE_STRING, str, NULL);
259 
260   if ((str = g_udev_device_get_property (udev_device, "ID_VENDOR_ID")) && *str)
261     gst_structure_set (props, "device.vendor.id", G_TYPE_STRING, str, NULL);
262 
263   str = g_udev_device_get_property (udev_device, "ID_VENDOR_FROM_DATABASE");
264   if (!(str && *str)) {
265     str = g_udev_device_get_property (udev_device, "ID_VENDOR_ENC");
266     if (!(str && *str)) {
267       str = g_udev_device_get_property (udev_device, "ID_VENDOR");
268     }
269   }
270   if (str && *str)
271     gst_structure_set (props, "device.vendor.name", G_TYPE_STRING, str, NULL);
272 
273   if ((str = g_udev_device_get_property (udev_device, "ID_MODEL_ID")) && *str)
274     gst_structure_set (props, "device.product.id", G_TYPE_STRING, str, NULL);
275 
276   device_name = g_udev_device_get_property (udev_device, "ID_V4L_PRODUCT");
277   if (!(device_name && *device_name)) {
278     device_name =
279         g_udev_device_get_property (udev_device, "ID_MODEL_FROM_DATABASE");
280     if (!(device_name && *device_name)) {
281       device_name = g_udev_device_get_property (udev_device, "ID_MODEL_ENC");
282       if (!(device_name && *device_name)) {
283         device_name = g_udev_device_get_property (udev_device, "ID_MODEL");
284       }
285     }
286   }
287   if (device_name && *device_name)
288     gst_structure_set (props, "device.product.name", G_TYPE_STRING, device_name,
289         NULL);
290 
291   if ((str = g_udev_device_get_property (udev_device, "ID_SERIAL")) && *str)
292     gst_structure_set (props, "device.serial", G_TYPE_STRING, str, NULL);
293 
294   if ((str = g_udev_device_get_property (udev_device, "ID_V4L_CAPABILITIES"))
295       && *str)
296     gst_structure_set (props, "device.capabilities", G_TYPE_STRING, str, NULL);
297 
298   gstdev = gst_v4l2_device_provider_probe_device (provider, device_path,
299       device_name, props);
300 
301   if (gstdev)
302     gstdev->syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device));
303 
304   return GST_DEVICE (gstdev);
305 }
306 
307 static void
uevent_cb(GUdevClient * client,const gchar * action,GUdevDevice * device,GstV4l2DeviceProvider * self)308 uevent_cb (GUdevClient * client, const gchar * action, GUdevDevice * device,
309     GstV4l2DeviceProvider * self)
310 {
311   GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self);
312 
313   /* Not V4L2, ignoring */
314   if (g_udev_device_get_property_as_int (device, "ID_V4L_VERSION") != 2)
315     return;
316 
317   if (!strcmp (action, "add")) {
318     GstDevice *gstdev = NULL;
319 
320     gstdev = gst_v4l2_device_provider_device_from_udev (self, device);
321 
322     if (gstdev)
323       gst_device_provider_device_add (provider, gstdev);
324   } else if (!strcmp (action, "remove")) {
325     GstV4l2Device *gstdev = NULL;
326     GList *item;
327 
328     GST_OBJECT_LOCK (self);
329     for (item = provider->devices; item; item = item->next) {
330       gstdev = item->data;
331 
332       if (!strcmp (gstdev->syspath, g_udev_device_get_sysfs_path (device))) {
333         gst_object_ref (gstdev);
334         break;
335       }
336 
337       gstdev = NULL;
338     }
339     GST_OBJECT_UNLOCK (provider);
340 
341     if (gstdev) {
342       gst_device_provider_device_remove (provider, GST_DEVICE (gstdev));
343       g_object_unref (gstdev);
344     }
345   } else {
346     GST_WARNING ("Unhandled action %s", action);
347   }
348 }
349 
350 static gpointer
provider_thread(gpointer data)351 provider_thread (gpointer data)
352 {
353   GstV4l2DeviceProvider *provider = data;
354   GMainContext *context = NULL;
355   GMainLoop *loop = NULL;
356   GUdevClient *client;
357   GList *devices;
358   static const gchar *subsystems[] = { "video4linux", NULL };
359 
360   GST_OBJECT_LOCK (provider);
361   if (provider->context)
362     context = g_main_context_ref (provider->context);
363   if (provider->loop)
364     loop = g_main_loop_ref (provider->loop);
365 
366   if (context == NULL || loop == NULL) {
367     provider->started = TRUE;
368     g_cond_broadcast (&provider->started_cond);
369     GST_OBJECT_UNLOCK (provider);
370     return NULL;
371   }
372   GST_OBJECT_UNLOCK (provider);
373 
374   g_main_context_push_thread_default (context);
375 
376   client = g_udev_client_new (subsystems);
377 
378   g_signal_connect (client, "uevent", G_CALLBACK (uevent_cb), provider);
379 
380   devices = g_udev_client_query_by_subsystem (client, "video4linux");
381 
382   while (devices) {
383     GUdevDevice *udev_device = devices->data;
384     GstDevice *gstdev;
385 
386     devices = g_list_remove (devices, udev_device);
387 
388     if (g_udev_device_get_property_as_int (udev_device, "ID_V4L_VERSION") == 2) {
389       gstdev =
390           gst_v4l2_device_provider_device_from_udev (provider, udev_device);
391       if (gstdev)
392         gst_device_provider_device_add (GST_DEVICE_PROVIDER (provider), gstdev);
393     }
394 
395     g_object_unref (udev_device);
396   }
397 
398   GST_OBJECT_LOCK (provider);
399   provider->started = TRUE;
400   g_cond_broadcast (&provider->started_cond);
401   GST_OBJECT_UNLOCK (provider);
402 
403   g_main_loop_run (loop);
404   g_main_loop_unref (loop);
405 
406   g_object_unref (client);
407   g_main_context_unref (context);
408 
409   gst_object_unref (provider);
410 
411   return NULL;
412 }
413 
414 static gboolean
gst_v4l2_device_provider_start(GstDeviceProvider * provider)415 gst_v4l2_device_provider_start (GstDeviceProvider * provider)
416 {
417   GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
418 
419   GST_OBJECT_LOCK (self);
420   g_assert (self->context == NULL);
421 
422   self->context = g_main_context_new ();
423   self->loop = g_main_loop_new (self->context, FALSE);
424 
425   self->thread = g_thread_new ("v4l2-device-provider", provider_thread,
426       g_object_ref (self));
427 
428   while (self->started == FALSE)
429     g_cond_wait (&self->started_cond, GST_OBJECT_GET_LOCK (self));
430 
431   GST_OBJECT_UNLOCK (self);
432 
433   return TRUE;
434 }
435 
436 static void
gst_v4l2_device_provider_stop(GstDeviceProvider * provider)437 gst_v4l2_device_provider_stop (GstDeviceProvider * provider)
438 {
439   GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
440   GMainContext *context;
441   GMainLoop *loop;
442   GSource *idle_stop_source;
443 
444   GST_OBJECT_LOCK (self);
445   context = self->context;
446   loop = self->loop;
447   self->context = NULL;
448   self->loop = NULL;
449   GST_OBJECT_UNLOCK (self);
450 
451   if (!context || !loop)
452     return;
453 
454   idle_stop_source = g_idle_source_new ();
455   g_source_set_callback (idle_stop_source, (GSourceFunc) g_main_loop_quit, loop,
456       (GDestroyNotify) g_main_loop_unref);
457   g_source_attach (idle_stop_source, context);
458   g_source_unref (idle_stop_source);
459   g_main_context_unref (context);
460 
461   g_thread_join (self->thread);
462   self->thread = NULL;
463   self->started = FALSE;
464 }
465 
466 #endif
467 
468 enum
469 {
470   PROP_DEVICE_PATH = 1,
471 };
472 
473 G_DEFINE_TYPE (GstV4l2Device, gst_v4l2_device, GST_TYPE_DEVICE);
474 
475 static void gst_v4l2_device_get_property (GObject * object, guint prop_id,
476     GValue * value, GParamSpec * pspec);
477 static void gst_v4l2_device_set_property (GObject * object, guint prop_id,
478     const GValue * value, GParamSpec * pspec);
479 static void gst_v4l2_device_finalize (GObject * object);
480 static GstElement *gst_v4l2_device_create_element (GstDevice * device,
481     const gchar * name);
482 
483 static void
gst_v4l2_device_class_init(GstV4l2DeviceClass * klass)484 gst_v4l2_device_class_init (GstV4l2DeviceClass * klass)
485 {
486   GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
487   GObjectClass *object_class = G_OBJECT_CLASS (klass);
488 
489   dev_class->create_element = gst_v4l2_device_create_element;
490 
491   object_class->get_property = gst_v4l2_device_get_property;
492   object_class->set_property = gst_v4l2_device_set_property;
493   object_class->finalize = gst_v4l2_device_finalize;
494 
495   g_object_class_install_property (object_class, PROP_DEVICE_PATH,
496       g_param_spec_string ("device-path", "Device Path",
497           "The Path of the device node", "",
498           G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
499 }
500 
501 static void
gst_v4l2_device_init(GstV4l2Device * device)502 gst_v4l2_device_init (GstV4l2Device * device)
503 {
504 }
505 
506 static void
gst_v4l2_device_finalize(GObject * object)507 gst_v4l2_device_finalize (GObject * object)
508 {
509   GstV4l2Device *device = GST_V4L2_DEVICE (object);
510 
511   g_free (device->device_path);
512   g_free (device->syspath);
513 
514   G_OBJECT_CLASS (gst_v4l2_device_parent_class)->finalize (object);
515 }
516 
517 static GstElement *
gst_v4l2_device_create_element(GstDevice * device,const gchar * name)518 gst_v4l2_device_create_element (GstDevice * device, const gchar * name)
519 {
520   GstV4l2Device *v4l2_dev = GST_V4L2_DEVICE (device);
521   GstElement *elem;
522 
523   elem = gst_element_factory_make (v4l2_dev->element, name);
524   g_object_set (elem, "device", v4l2_dev->device_path, NULL);
525 
526   return elem;
527 }
528 
529 static GstV4l2Device *
gst_v4l2_device_new(const gchar * device_path,const gchar * device_name,GstCaps * caps,GstV4l2DeviceType type,GstStructure * props)530 gst_v4l2_device_new (const gchar * device_path, const gchar * device_name,
531     GstCaps * caps, GstV4l2DeviceType type, GstStructure * props)
532 {
533   GstV4l2Device *gstdev;
534   const gchar *element = NULL;
535   const gchar *klass = NULL;
536 
537   g_return_val_if_fail (device_path, NULL);
538   g_return_val_if_fail (device_name, NULL);
539   g_return_val_if_fail (caps, NULL);
540 
541   switch (type) {
542     case GST_V4L2_DEVICE_TYPE_SOURCE:
543       element = "v4l2src";
544       klass = "Video/Source";
545       break;
546     case GST_V4L2_DEVICE_TYPE_SINK:
547       element = "v4l2sink";
548       klass = "Video/Sink";
549       break;
550     default:
551       g_assert_not_reached ();
552       break;
553   }
554 
555   gstdev = g_object_new (GST_TYPE_V4L2_DEVICE, "device-path", device_path,
556       "display-name", device_name, "caps", caps, "device-class", klass,
557       "properties", props, NULL);
558 
559   gstdev->element = element;
560 
561 
562   return gstdev;
563 }
564 
565 
566 static void
gst_v4l2_device_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)567 gst_v4l2_device_get_property (GObject * object, guint prop_id,
568     GValue * value, GParamSpec * pspec)
569 {
570   GstV4l2Device *device;
571 
572   device = GST_V4L2_DEVICE_CAST (object);
573 
574   switch (prop_id) {
575     case PROP_DEVICE_PATH:
576       g_value_set_string (value, device->device_path);
577       break;
578     default:
579       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
580       break;
581   }
582 }
583 
584 
585 static void
gst_v4l2_device_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)586 gst_v4l2_device_set_property (GObject * object, guint prop_id,
587     const GValue * value, GParamSpec * pspec)
588 {
589   GstV4l2Device *device;
590 
591   device = GST_V4L2_DEVICE_CAST (object);
592 
593   switch (prop_id) {
594     case PROP_DEVICE_PATH:
595       device->device_path = g_value_dup_string (value);
596       break;
597     default:
598       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
599       break;
600   }
601 }
602