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>
36 static int OpenV4L (vlc_object_t *);
37 static int OpenALSA (vlc_object_t *);
38 static int OpenDisc (vlc_object_t *);
39 static void Close (vlc_object_t *);
40 static int vlc_sd_probe_Open (vlc_object_t *);
46 set_shortname (N_("Video capture"))
47 set_description (N_("Video capture (Video4Linux)"))
48 set_category (CAT_PLAYLIST)
49 set_subcategory (SUBCAT_PLAYLIST_SD)
50 set_capability ("services_discovery", 0)
51 set_callbacks (OpenV4L, Close)
55 set_shortname (N_("Audio capture"))
56 set_description (N_("Audio capture (ALSA)"))
57 set_category (CAT_PLAYLIST)
58 set_subcategory (SUBCAT_PLAYLIST_SD)
59 set_capability ("services_discovery", 0)
60 set_callbacks (OpenALSA, Close)
64 set_shortname (N_("Discs"))
65 set_description (N_("Discs"))
66 set_category (CAT_PLAYLIST)
67 set_subcategory (SUBCAT_PLAYLIST_SD)
68 set_capability ("services_discovery", 0)
69 set_callbacks (OpenDisc, Close)
72 VLC_SD_PROBE_SUBMODULE
76 static int vlc_sd_probe_Open (vlc_object_t *obj)
78 vlc_probe_t *probe = (vlc_probe_t *)obj;
80 struct udev *udev = udev_new ();
82 return VLC_PROBE_CONTINUE;
84 struct udev_monitor *mon = udev_monitor_new_from_netlink (udev, "udev");
87 vlc_sd_probe_Add (probe, "v4l{longname=\"Video capture\"}",
88 N_("Video capture"), SD_CAT_DEVICES);
89 vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}",
90 N_("Audio capture"), SD_CAT_DEVICES);
91 vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"),
93 udev_monitor_unref (mon);
96 return VLC_PROBE_CONTINUE;
101 dev_t devnum; /* must be first */
103 services_discovery_t *sd;
109 char * (*get_mrl) (struct udev_device *dev);
110 char * (*get_name) (struct udev_device *dev);
111 char * (*get_cat) (struct udev_device *dev);
115 struct services_discovery_sys_t
117 const struct subsys *subsys;
118 struct udev_monitor *monitor;
124 * Compares two devices (to support binary search).
126 static int cmpdev (const void *a, const void *b)
128 const dev_t *da = a, *db = b;
129 dev_t delta = *da - *db;
131 if (sizeof (delta) > sizeof (int))
132 return delta ? ((delta > 0) ? 1 : -1) : 0;
133 return (signed)delta;
136 static void DestroyDevice (void *data)
138 struct device *d = data;
141 services_discovery_RemoveItem (d->sd, d->item);
142 vlc_gc_decref (d->item);
146 static char *decode_property (struct udev_device *, const char *);
149 * Adds a udev device.
151 static int AddDevice (services_discovery_t *sd, struct udev_device *dev)
153 services_discovery_sys_t *p_sys = sd->p_sys;
155 char *mrl = p_sys->subsys->get_mrl (dev);
157 return 0; /* don't know if it was an error... */
158 char *name = p_sys->subsys->get_name (dev);
159 input_item_t *item = input_item_NewWithType (VLC_OBJECT (sd), mrl,
162 p_sys->subsys->item_type);
163 msg_Dbg (sd, "adding %s (%s)", mrl, name);
169 struct device *d = malloc (sizeof (*d));
172 vlc_gc_decref (item);
175 d->devnum = udev_device_get_devnum (dev);
179 struct device **dp = tsearch (d, &p_sys->root, cmpdev);
180 if (dp == NULL) /* Out-of-memory */
185 if (*dp != d) /* Overwrite existing device */
191 name = p_sys->subsys->get_cat (dev);
192 services_discovery_AddItem (sd, item, name ? name : "Generic");
199 * Removes a udev device (if present).
201 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
203 services_discovery_sys_t *p_sys = sd->p_sys;
205 dev_t num = udev_device_get_devnum (dev);
206 struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
210 struct device *d = *dp;
211 tdelete (d, &p_sys->root, cmpdev);
215 static void *Run (void *);
218 * Probes and initializes.
220 static int Open (vlc_object_t *obj, const struct subsys *subsys)
222 services_discovery_t *sd = (services_discovery_t *)obj;
223 services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
228 p_sys->subsys = subsys;
231 struct udev_monitor *mon = NULL;
232 struct udev *udev = udev_new ();
236 mon = udev_monitor_new_from_netlink (udev, "udev");
238 || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
241 p_sys->monitor = mon;
243 /* Enumerate existing devices */
244 struct udev_enumerate *devenum = udev_enumerate_new (udev);
247 if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
249 udev_enumerate_unref (devenum);
253 udev_monitor_enable_receiving (mon);
254 /* Note that we enumerate _after_ monitoring is enabled so that we do not
255 * loose device events occuring while we are enumerating. We could still
256 * loose events if the Netlink socket receive buffer overflows. */
257 udev_enumerate_scan_devices (devenum);
258 struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
259 struct udev_list_entry *deventry;
260 udev_list_entry_foreach (deventry, devlist)
262 const char *path = udev_list_entry_get_name (deventry);
263 struct udev_device *dev = udev_device_new_from_syspath (udev, path);
265 udev_device_unref (dev);
267 udev_enumerate_unref (devenum);
269 if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
270 { /* Fallback without thread */
271 udev_monitor_unref (mon);
273 p_sys->monitor = NULL;
279 udev_monitor_unref (mon);
289 static void Close (vlc_object_t *obj)
291 services_discovery_t *sd = (services_discovery_t *)obj;
292 services_discovery_sys_t *p_sys = sd->p_sys;
294 if (p_sys->monitor != NULL)
296 struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
298 vlc_cancel (p_sys->thread);
299 vlc_join (p_sys->thread, NULL);
300 udev_monitor_unref (p_sys->monitor);
304 tdestroy (p_sys->root, DestroyDevice);
308 static void *Run (void *data)
310 services_discovery_t *sd = data;
311 services_discovery_sys_t *p_sys = sd->p_sys;
312 struct udev_monitor *mon = p_sys->monitor;
314 int fd = udev_monitor_get_fd (mon);
315 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
319 while (poll (&ufd, 1, -1) == -1)
323 int canc = vlc_savecancel ();
324 struct udev_device *dev = udev_monitor_receive_device (mon);
328 const char *action = udev_device_get_action (dev);
329 if (!strcmp (action, "add"))
331 else if (!strcmp (action, "remove"))
332 RemoveDevice (sd, dev);
333 else if (!strcmp (action, "change"))
335 RemoveDevice (sd, dev);
338 udev_device_unref (dev);
339 vlc_restorecancel (canc);
345 * Converts an hexadecimal digit to an integer.
347 static int hex (char c)
349 if (c >= '0' && c <= '9')
351 if (c >= 'A' && c <= 'F')
353 if (c >= 'a' && c <= 'f')
359 * Decodes a udev hexadecimal-encoded property.
361 static char *decode (const char *enc)
363 char *ret = enc ? strdup (enc) : NULL;
368 for (const char *in = ret; *in; out++)
372 if ((in[0] == '\\') && (in[1] == 'x')
373 && ((h1 = hex (in[2])) != -1)
374 && ((h2 = hex (in[3])) != -1))
376 *out = (h1 << 4) | h2;
389 static char *decode_property (struct udev_device *dev, const char *name)
391 return decode (udev_device_get_property_value (dev, name));
395 /*** Video4Linux support ***/
396 static bool is_v4l_legacy (struct udev_device *dev)
400 version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
401 return version && !strcmp (version, "1");
404 static char *v4l_get_mrl (struct udev_device *dev)
406 /* Determine media location */
407 const char *scheme = "v4l2";
408 if (is_v4l_legacy (dev))
410 const char *node = udev_device_get_devnode (dev);
413 if (asprintf (&mrl, "%s://%s", scheme, node) == -1)
418 static char *v4l_get_name (struct udev_device *dev)
420 const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
421 return prd ? strdup (prd) : NULL;
424 static char *v4l_get_cat (struct udev_device *dev)
426 return decode_property (dev, "ID_VENDOR_ENC");
429 int OpenV4L (vlc_object_t *obj)
431 static const struct subsys subsys = {
432 "video4linux", v4l_get_mrl, v4l_get_name, v4l_get_cat, ITEM_TYPE_CARD,
435 return Open (obj, &subsys);
440 /*** Advanced Linux Sound Architecture support ***/
441 #include <alsa/asoundlib.h>
443 static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard,
444 unsigned *restrict pdevice)
446 const char *node = udev_device_get_devpath (dev);
449 node = strrchr (node, '/');
452 if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3)
460 static char *alsa_get_mrl (struct udev_device *dev)
462 /* Determine media location */
464 unsigned card, device;
466 if (alsa_get_device (dev, &card, &device))
469 if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1)
474 static char *alsa_get_name (struct udev_device *dev)
477 unsigned card, device;
479 if (alsa_get_device (dev, &card, &device))
482 char card_name[4 + 3 * sizeof (int)];
483 snprintf (card_name, sizeof (card_name), "hw:%u", card);
486 if (snd_ctl_open (&ctl, card_name, 0))
489 snd_pcm_info_t *pcm_info;
490 snd_pcm_info_alloca (&pcm_info);
491 snd_pcm_info_set_device (pcm_info, device);
492 snd_pcm_info_set_subdevice (pcm_info, 0);
493 snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE);
494 if (snd_ctl_pcm_info (ctl, pcm_info))
497 name = strdup (snd_pcm_info_get_name (pcm_info));
503 static char *alsa_get_cat (struct udev_device *dev)
507 dev = udev_device_get_parent (dev);
511 vnd = udev_device_get_property_value (dev, "ID_VENDOR_FROM_DATABASE");
513 /* FIXME: USB may take time to settle... the parent device */
514 vnd = udev_device_get_property_value (dev, "ID_BUS");
515 return vnd ? strdup (vnd) : NULL;
518 int OpenALSA (vlc_object_t *obj)
520 static const struct subsys subsys = {
521 "sound", alsa_get_mrl, alsa_get_name, alsa_get_cat, ITEM_TYPE_CARD,
524 return Open (obj, &subsys);
526 #endif /* HAVE_ALSA */
529 /*** Discs support ***/
530 static char *disc_get_mrl (struct udev_device *dev)
534 val = udev_device_get_property_value (dev, "ID_CDROM");
536 return NULL; /* Ignore non-optical block devices */
538 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
539 if (val && !strcmp (val, "blank"))
540 return NULL; /* ignore empty drives and virgin recordable discs */
542 const char *scheme = NULL;
543 val = udev_device_get_property_value (dev,
544 "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
545 if (val && atoi (val))
546 scheme = "cdda"; /* Audio CD rather than file system */
547 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
548 if (val && atoi (val))
551 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
552 if (val && atoi (val))
555 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
556 if (val && atoi (val))
560 /* We didn't get any property that could tell we have optical disc
565 val = udev_device_get_devnode (dev);
566 return make_URI (val, scheme);
569 static char *disc_get_name (struct udev_device *dev)
571 return decode_property (dev, "ID_FS_LABEL_ENC");
574 static char *disc_get_cat (struct udev_device *dev)
576 struct udev_list_entry *list, *entry;
578 list = udev_device_get_properties_list_entry (dev);
579 if (unlikely(list == NULL))
582 const char *cat = NULL;
583 udev_list_entry_foreach (entry, list)
585 const char *name = udev_list_entry_get_name (entry);
587 if (strncmp (name, "ID_CDROM_MEDIA_", 15))
589 if (!atoi (udev_list_entry_get_value (entry)))
593 if (!strncmp (name, "CD", 2))
595 else if (!strncmp (name, "DVD", 3))
597 else if (!strncmp (name, "BD", 2))
599 else if (!strncmp (name, "HDDVD", 5))
607 cat = N_("Unknown type");
608 return strdup (vlc_gettext (cat));
611 int OpenDisc (vlc_object_t *obj)
613 static const struct subsys subsys = {
614 "block", disc_get_mrl, disc_get_name, disc_get_cat, ITEM_TYPE_DISC,
617 return Open (obj, &subsys);