3 * @brief List of multimedia devices for VLC media player
5 /*****************************************************************************
6 * Copyright © 2009 Rémi Denis-Courmont
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
28 #include <vlc_common.h>
29 #include <vlc_services_discovery.h>
30 #include <vlc_plugin.h>
38 static int OpenV4L (vlc_object_t *);
40 static int OpenALSA (vlc_object_t *);
42 static int OpenDisc (vlc_object_t *);
43 static void Close (vlc_object_t *);
44 static int vlc_sd_probe_Open (vlc_object_t *);
50 set_shortname (N_("Video capture"))
51 set_description (N_("Video capture (Video4Linux)"))
52 set_category (CAT_PLAYLIST)
53 set_subcategory (SUBCAT_PLAYLIST_SD)
54 set_capability ("services_discovery", 0)
55 set_callbacks (OpenV4L, Close)
59 set_shortname (N_("Audio capture"))
60 set_description (N_("Audio capture (ALSA)"))
61 set_category (CAT_PLAYLIST)
62 set_subcategory (SUBCAT_PLAYLIST_SD)
63 set_capability ("services_discovery", 0)
64 set_callbacks (OpenALSA, Close)
68 set_shortname (N_("Discs"))
69 set_description (N_("Discs"))
70 set_category (CAT_PLAYLIST)
71 set_subcategory (SUBCAT_PLAYLIST_SD)
72 set_capability ("services_discovery", 0)
73 set_callbacks (OpenDisc, Close)
76 VLC_SD_PROBE_SUBMODULE
80 static int vlc_sd_probe_Open (vlc_object_t *obj)
82 vlc_probe_t *probe = (vlc_probe_t *)obj;
84 struct udev *udev = udev_new ();
86 return VLC_PROBE_CONTINUE;
88 struct udev_monitor *mon = udev_monitor_new_from_netlink (udev, "udev");
91 vlc_sd_probe_Add (probe, "v4l{longname=\"Video capture\"}",
92 N_("Video capture"), SD_CAT_DEVICES);
94 vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}",
95 N_("Audio capture"), SD_CAT_DEVICES);
97 vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"),
99 udev_monitor_unref (mon);
102 return VLC_PROBE_CONTINUE;
107 dev_t devnum; /* must be first */
109 services_discovery_t *sd;
115 char * (*get_mrl) (struct udev_device *dev);
116 char * (*get_name) (struct udev_device *dev);
117 char * (*get_cat) (struct udev_device *dev);
121 struct services_discovery_sys_t
123 const struct subsys *subsys;
124 struct udev_monitor *monitor;
130 * Compares two devices (to support binary search).
132 static int cmpdev (const void *a, const void *b)
134 const dev_t *da = a, *db = b;
135 dev_t delta = *da - *db;
137 if (sizeof (delta) > sizeof (int))
138 return delta ? ((delta > 0) ? 1 : -1) : 0;
139 return (signed)delta;
142 static void DestroyDevice (void *data)
144 struct device *d = data;
147 services_discovery_RemoveItem (d->sd, d->item);
148 vlc_gc_decref (d->item);
152 static char *decode_property (struct udev_device *, const char *);
155 * Adds a udev device.
157 static int AddDevice (services_discovery_t *sd, struct udev_device *dev)
159 services_discovery_sys_t *p_sys = sd->p_sys;
161 char *mrl = p_sys->subsys->get_mrl (dev);
163 return 0; /* don't know if it was an error... */
164 char *name = p_sys->subsys->get_name (dev);
165 input_item_t *item = input_item_NewWithType (mrl, name ? name : mrl,
167 p_sys->subsys->item_type);
168 msg_Dbg (sd, "adding %s (%s)", mrl, name);
174 struct device *d = malloc (sizeof (*d));
177 vlc_gc_decref (item);
180 d->devnum = udev_device_get_devnum (dev);
184 struct device **dp = tsearch (d, &p_sys->root, cmpdev);
185 if (dp == NULL) /* Out-of-memory */
190 if (*dp != d) /* Overwrite existing device */
196 name = p_sys->subsys->get_cat (dev);
197 services_discovery_AddItem (sd, item, name ? name : "Generic");
204 * Removes a udev device (if present).
206 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
208 services_discovery_sys_t *p_sys = sd->p_sys;
210 dev_t num = udev_device_get_devnum (dev);
211 struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
215 struct device *d = *dp;
216 tdelete (d, &p_sys->root, cmpdev);
220 static void *Run (void *);
223 * Probes and initializes.
225 static int Open (vlc_object_t *obj, const struct subsys *subsys)
227 services_discovery_t *sd = (services_discovery_t *)obj;
228 services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
233 p_sys->subsys = subsys;
236 struct udev_monitor *mon = NULL;
237 struct udev *udev = udev_new ();
241 mon = udev_monitor_new_from_netlink (udev, "udev");
243 || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
246 p_sys->monitor = mon;
248 /* Enumerate existing devices */
249 struct udev_enumerate *devenum = udev_enumerate_new (udev);
252 if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
254 udev_enumerate_unref (devenum);
258 udev_monitor_enable_receiving (mon);
259 /* Note that we enumerate _after_ monitoring is enabled so that we do not
260 * loose device events occuring while we are enumerating. We could still
261 * loose events if the Netlink socket receive buffer overflows. */
262 udev_enumerate_scan_devices (devenum);
263 struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
264 struct udev_list_entry *deventry;
265 udev_list_entry_foreach (deventry, devlist)
267 const char *path = udev_list_entry_get_name (deventry);
268 struct udev_device *dev = udev_device_new_from_syspath (udev, path);
270 udev_device_unref (dev);
272 udev_enumerate_unref (devenum);
274 if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
275 { /* Fallback without thread */
276 udev_monitor_unref (mon);
278 p_sys->monitor = NULL;
284 udev_monitor_unref (mon);
294 static void Close (vlc_object_t *obj)
296 services_discovery_t *sd = (services_discovery_t *)obj;
297 services_discovery_sys_t *p_sys = sd->p_sys;
299 if (p_sys->monitor != NULL)
301 struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
303 vlc_cancel (p_sys->thread);
304 vlc_join (p_sys->thread, NULL);
305 udev_monitor_unref (p_sys->monitor);
309 tdestroy (p_sys->root, DestroyDevice);
313 static void *Run (void *data)
315 services_discovery_t *sd = data;
316 services_discovery_sys_t *p_sys = sd->p_sys;
317 struct udev_monitor *mon = p_sys->monitor;
319 int fd = udev_monitor_get_fd (mon);
320 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
324 while (poll (&ufd, 1, -1) == -1)
328 int canc = vlc_savecancel ();
329 struct udev_device *dev = udev_monitor_receive_device (mon);
333 const char *action = udev_device_get_action (dev);
334 if (!strcmp (action, "add"))
336 else if (!strcmp (action, "remove"))
337 RemoveDevice (sd, dev);
338 else if (!strcmp (action, "change"))
340 RemoveDevice (sd, dev);
343 udev_device_unref (dev);
344 vlc_restorecancel (canc);
350 * Converts an hexadecimal digit to an integer.
352 static int hex (char c)
354 if (c >= '0' && c <= '9')
356 if (c >= 'A' && c <= 'F')
358 if (c >= 'a' && c <= 'f')
364 * Decodes a udev hexadecimal-encoded property.
366 static char *decode (const char *enc)
368 char *ret = enc ? strdup (enc) : NULL;
373 for (const char *in = ret; *in; out++)
377 if ((in[0] == '\\') && (in[1] == 'x')
378 && ((h1 = hex (in[2])) != -1)
379 && ((h2 = hex (in[3])) != -1))
381 *out = (h1 << 4) | h2;
394 static char *decode_property (struct udev_device *dev, const char *name)
396 return decode (udev_device_get_property_value (dev, name));
400 /*** Video4Linux support ***/
401 static bool v4l_is_legacy (struct udev_device *dev)
405 version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
406 return (version != NULL) && !strcmp (version, "1");
409 static bool v4l_can_capture (struct udev_device *dev)
413 caps = udev_device_get_property_value (dev, "ID_V4L_CAPABILITIES");
414 return (caps != NULL) && (strstr (caps, ":capture:") != NULL);
417 static char *v4l_get_mrl (struct udev_device *dev)
419 /* Determine media location */
420 if (v4l_is_legacy (dev) || !v4l_can_capture (dev))
423 const char *node = udev_device_get_devnode (dev);
426 if (asprintf (&mrl, "v4l2://%s", node) == -1)
431 static char *v4l_get_name (struct udev_device *dev)
433 const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
434 return prd ? strdup (prd) : NULL;
437 static char *v4l_get_cat (struct udev_device *dev)
439 return decode_property (dev, "ID_VENDOR_ENC");
442 int OpenV4L (vlc_object_t *obj)
444 static const struct subsys subsys = {
445 "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat, ITEM_TYPE_CARD,
448 return Open (obj, &subsys);
453 /*** Advanced Linux Sound Architecture support ***/
454 #include <alsa/asoundlib.h>
456 static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard,
457 unsigned *restrict pdevice)
459 const char *node = udev_device_get_devpath (dev);
462 node = strrchr (node, '/');
465 if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3)
473 static char *alsa_get_mrl (struct udev_device *dev)
475 /* Determine media location */
477 unsigned card, device;
479 if (alsa_get_device (dev, &card, &device))
482 if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1)
487 static char *alsa_get_name (struct udev_device *dev)
490 unsigned card, device;
492 if (alsa_get_device (dev, &card, &device))
495 char card_name[4 + 3 * sizeof (int)];
496 snprintf (card_name, sizeof (card_name), "hw:%u", card);
499 if (snd_ctl_open (&ctl, card_name, 0))
502 snd_pcm_info_t *pcm_info;
503 snd_pcm_info_alloca (&pcm_info);
504 snd_pcm_info_set_device (pcm_info, device);
505 snd_pcm_info_set_subdevice (pcm_info, 0);
506 snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE);
507 if (snd_ctl_pcm_info (ctl, pcm_info))
510 name = strdup (snd_pcm_info_get_name (pcm_info));
516 static char *alsa_get_cat (struct udev_device *dev)
520 dev = udev_device_get_parent (dev);
524 vnd = udev_device_get_property_value (dev, "ID_VENDOR_FROM_DATABASE");
526 /* FIXME: USB may take time to settle... the parent device */
527 vnd = udev_device_get_property_value (dev, "ID_BUS");
528 return vnd ? strdup (vnd) : NULL;
531 int OpenALSA (vlc_object_t *obj)
533 static const struct subsys subsys = {
534 "sound", alsa_get_mrl, alsa_get_name, alsa_get_cat, ITEM_TYPE_CARD,
537 return Open (obj, &subsys);
539 #endif /* HAVE_ALSA */
542 /*** Discs support ***/
543 static char *disc_get_mrl (struct udev_device *dev)
547 val = udev_device_get_property_value (dev, "ID_CDROM");
549 return NULL; /* Ignore non-optical block devices */
551 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
552 if (val && !strcmp (val, "blank"))
553 return NULL; /* ignore empty drives and virgin recordable discs */
555 const char *scheme = NULL;
556 val = udev_device_get_property_value (dev,
557 "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
558 if (val && atoi (val))
559 scheme = "cdda"; /* Audio CD rather than file system */
560 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
561 if (val && atoi (val))
564 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
565 if (val && atoi (val))
568 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
569 if (val && atoi (val))
573 /* We didn't get any property that could tell we have optical disc
578 val = udev_device_get_devnode (dev);
579 return make_URI (val, scheme);
582 static char *disc_get_name (struct udev_device *dev)
584 return decode_property (dev, "ID_FS_LABEL_ENC");
587 static char *disc_get_cat (struct udev_device *dev)
589 struct udev_list_entry *list, *entry;
591 list = udev_device_get_properties_list_entry (dev);
592 if (unlikely(list == NULL))
595 const char *cat = NULL;
596 udev_list_entry_foreach (entry, list)
598 const char *name = udev_list_entry_get_name (entry);
600 if (strncmp (name, "ID_CDROM_MEDIA_", 15))
602 if (!atoi (udev_list_entry_get_value (entry)))
606 if (!strncmp (name, "CD", 2))
608 else if (!strncmp (name, "DVD", 3))
610 else if (!strncmp (name, "BD", 2))
612 else if (!strncmp (name, "HDDVD", 5))
620 cat = N_("Unknown type");
621 return strdup (vlc_gettext (cat));
624 int OpenDisc (vlc_object_t *obj)
626 static const struct subsys subsys = {
627 "block", disc_get_mrl, disc_get_name, disc_get_cat, ITEM_TYPE_DISC,
630 return Open (obj, &subsys);