3 * @brief List of multimedia devices for VLC media player
5 /*****************************************************************************
6 * Copyright © 2009 Rémi Denis-Courmont
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public License
10 * as published by the Free Software Foundation; either version 2.1
11 * of the License, or (at your option) any later version.
13 * This library 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 General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, 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 (VLC_OBJECT (sd), mrl,
168 p_sys->subsys->item_type);
169 msg_Dbg (sd, "adding %s (%s)", mrl, name);
175 struct device *d = malloc (sizeof (*d));
178 vlc_gc_decref (item);
181 d->devnum = udev_device_get_devnum (dev);
185 struct device **dp = tsearch (d, &p_sys->root, cmpdev);
186 if (dp == NULL) /* Out-of-memory */
191 if (*dp != d) /* Overwrite existing device */
197 name = p_sys->subsys->get_cat (dev);
198 services_discovery_AddItem (sd, item, name ? name : "Generic");
205 * Removes a udev device (if present).
207 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
209 services_discovery_sys_t *p_sys = sd->p_sys;
211 dev_t num = udev_device_get_devnum (dev);
212 struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
216 struct device *d = *dp;
217 tdelete (d, &p_sys->root, cmpdev);
221 static void *Run (void *);
224 * Probes and initializes.
226 static int Open (vlc_object_t *obj, const struct subsys *subsys)
228 services_discovery_t *sd = (services_discovery_t *)obj;
229 services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
234 p_sys->subsys = subsys;
237 struct udev_monitor *mon = NULL;
238 struct udev *udev = udev_new ();
242 mon = udev_monitor_new_from_netlink (udev, "udev");
244 || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
247 p_sys->monitor = mon;
249 /* Enumerate existing devices */
250 struct udev_enumerate *devenum = udev_enumerate_new (udev);
253 if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
255 udev_enumerate_unref (devenum);
259 udev_monitor_enable_receiving (mon);
260 /* Note that we enumerate _after_ monitoring is enabled so that we do not
261 * loose device events occuring while we are enumerating. We could still
262 * loose events if the Netlink socket receive buffer overflows. */
263 udev_enumerate_scan_devices (devenum);
264 struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
265 struct udev_list_entry *deventry;
266 udev_list_entry_foreach (deventry, devlist)
268 const char *path = udev_list_entry_get_name (deventry);
269 struct udev_device *dev = udev_device_new_from_syspath (udev, path);
271 udev_device_unref (dev);
273 udev_enumerate_unref (devenum);
275 if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
276 { /* Fallback without thread */
277 udev_monitor_unref (mon);
279 p_sys->monitor = NULL;
285 udev_monitor_unref (mon);
295 static void Close (vlc_object_t *obj)
297 services_discovery_t *sd = (services_discovery_t *)obj;
298 services_discovery_sys_t *p_sys = sd->p_sys;
300 if (p_sys->monitor != NULL)
302 struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
304 vlc_cancel (p_sys->thread);
305 vlc_join (p_sys->thread, NULL);
306 udev_monitor_unref (p_sys->monitor);
310 tdestroy (p_sys->root, DestroyDevice);
314 static void *Run (void *data)
316 services_discovery_t *sd = data;
317 services_discovery_sys_t *p_sys = sd->p_sys;
318 struct udev_monitor *mon = p_sys->monitor;
320 int fd = udev_monitor_get_fd (mon);
321 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
325 while (poll (&ufd, 1, -1) == -1)
329 int canc = vlc_savecancel ();
330 struct udev_device *dev = udev_monitor_receive_device (mon);
334 const char *action = udev_device_get_action (dev);
335 if (!strcmp (action, "add"))
337 else if (!strcmp (action, "remove"))
338 RemoveDevice (sd, dev);
339 else if (!strcmp (action, "change"))
341 RemoveDevice (sd, dev);
344 udev_device_unref (dev);
345 vlc_restorecancel (canc);
351 * Converts an hexadecimal digit to an integer.
353 static int hex (char c)
355 if (c >= '0' && c <= '9')
357 if (c >= 'A' && c <= 'F')
359 if (c >= 'a' && c <= 'f')
365 * Decodes a udev hexadecimal-encoded property.
367 static char *decode (const char *enc)
369 char *ret = enc ? strdup (enc) : NULL;
374 for (const char *in = ret; *in; out++)
378 if ((in[0] == '\\') && (in[1] == 'x')
379 && ((h1 = hex (in[2])) != -1)
380 && ((h2 = hex (in[3])) != -1))
382 *out = (h1 << 4) | h2;
395 static char *decode_property (struct udev_device *dev, const char *name)
397 return decode (udev_device_get_property_value (dev, name));
401 /*** Video4Linux support ***/
402 static bool is_v4l_legacy (struct udev_device *dev)
406 version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
407 return version && !strcmp (version, "1");
410 static char *v4l_get_mrl (struct udev_device *dev)
412 /* Determine media location */
413 const char *scheme = "v4l2";
414 if (is_v4l_legacy (dev))
416 const char *node = udev_device_get_devnode (dev);
419 if (asprintf (&mrl, "%s://%s", scheme, node) == -1)
424 static char *v4l_get_name (struct udev_device *dev)
426 const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
427 return prd ? strdup (prd) : NULL;
430 static char *v4l_get_cat (struct udev_device *dev)
432 return decode_property (dev, "ID_VENDOR_ENC");
435 int OpenV4L (vlc_object_t *obj)
437 static const struct subsys subsys = {
438 "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat, ITEM_TYPE_CARD,
441 return Open (obj, &subsys);
446 /*** Advanced Linux Sound Architecture support ***/
447 #include <alsa/asoundlib.h>
449 static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard,
450 unsigned *restrict pdevice)
452 const char *node = udev_device_get_devpath (dev);
455 node = strrchr (node, '/');
458 if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3)
466 static char *alsa_get_mrl (struct udev_device *dev)
468 /* Determine media location */
470 unsigned card, device;
472 if (alsa_get_device (dev, &card, &device))
475 if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1)
480 static char *alsa_get_name (struct udev_device *dev)
483 unsigned card, device;
485 if (alsa_get_device (dev, &card, &device))
488 char card_name[4 + 3 * sizeof (int)];
489 snprintf (card_name, sizeof (card_name), "hw:%u", card);
492 if (snd_ctl_open (&ctl, card_name, 0))
495 snd_pcm_info_t *pcm_info;
496 snd_pcm_info_alloca (&pcm_info);
497 snd_pcm_info_set_device (pcm_info, device);
498 snd_pcm_info_set_subdevice (pcm_info, 0);
499 snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE);
500 if (snd_ctl_pcm_info (ctl, pcm_info))
503 name = strdup (snd_pcm_info_get_name (pcm_info));
509 static char *alsa_get_cat (struct udev_device *dev)
513 dev = udev_device_get_parent (dev);
517 vnd = udev_device_get_property_value (dev, "ID_VENDOR_FROM_DATABASE");
519 /* FIXME: USB may take time to settle... the parent device */
520 vnd = udev_device_get_property_value (dev, "ID_BUS");
521 return vnd ? strdup (vnd) : NULL;
524 int OpenALSA (vlc_object_t *obj)
526 static const struct subsys subsys = {
527 "sound", alsa_get_mrl, alsa_get_name, alsa_get_cat, ITEM_TYPE_CARD,
530 return Open (obj, &subsys);
532 #endif /* HAVE_ALSA */
535 /*** Discs support ***/
536 static char *disc_get_mrl (struct udev_device *dev)
540 val = udev_device_get_property_value (dev, "ID_CDROM");
542 return NULL; /* Ignore non-optical block devices */
544 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
545 if (val && !strcmp (val, "blank"))
546 return NULL; /* ignore empty drives and virgin recordable discs */
548 const char *scheme = NULL;
549 val = udev_device_get_property_value (dev,
550 "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
551 if (val && atoi (val))
552 scheme = "cdda"; /* Audio CD rather than file system */
553 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
554 if (val && atoi (val))
557 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
558 if (val && atoi (val))
561 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
562 if (val && atoi (val))
566 /* We didn't get any property that could tell we have optical disc
571 val = udev_device_get_devnode (dev);
572 return make_URI (val, scheme);
575 static char *disc_get_name (struct udev_device *dev)
577 return decode_property (dev, "ID_FS_LABEL_ENC");
580 static char *disc_get_cat (struct udev_device *dev)
582 struct udev_list_entry *list, *entry;
584 list = udev_device_get_properties_list_entry (dev);
585 if (unlikely(list == NULL))
588 const char *cat = NULL;
589 udev_list_entry_foreach (entry, list)
591 const char *name = udev_list_entry_get_name (entry);
593 if (strncmp (name, "ID_CDROM_MEDIA_", 15))
595 if (!atoi (udev_list_entry_get_value (entry)))
599 if (!strncmp (name, "CD", 2))
601 else if (!strncmp (name, "DVD", 3))
603 else if (!strncmp (name, "BD", 2))
605 else if (!strncmp (name, "HDDVD", 5))
613 cat = N_("Unknown type");
614 return strdup (vlc_gettext (cat));
617 int OpenDisc (vlc_object_t *obj)
619 static const struct subsys subsys = {
620 "block", disc_get_mrl, disc_get_name, disc_get_cat, ITEM_TYPE_DISC,
623 return Open (obj, &subsys);