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