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 *****************************************************************************/
35 #include <vlc_common.h>
36 #include <vlc_services_discovery.h>
37 #include <vlc_plugin.h>
39 # include <vlc_modules.h>
43 static int OpenV4L (vlc_object_t *);
45 static int OpenALSA (vlc_object_t *);
47 static int OpenDisc (vlc_object_t *);
48 static void Close (vlc_object_t *);
49 static int vlc_sd_probe_Open (vlc_object_t *);
55 set_shortname (N_("Video capture"))
56 set_description (N_("Video capture (Video4Linux)"))
57 set_category (CAT_PLAYLIST)
58 set_subcategory (SUBCAT_PLAYLIST_SD)
59 set_capability ("services_discovery", 0)
60 set_callbacks (OpenV4L, Close)
61 add_shortcut ("v4l", "video")
64 set_shortname (N_("Audio capture"))
65 set_description (N_("Audio capture (ALSA)"))
66 set_category (CAT_PLAYLIST)
67 set_subcategory (SUBCAT_PLAYLIST_SD)
68 set_capability ("services_discovery", 0)
69 set_callbacks (OpenALSA, Close)
70 add_shortcut ("alsa", "audio")
73 set_shortname (N_("Discs"))
74 set_description (N_("Discs"))
75 set_category (CAT_PLAYLIST)
76 set_subcategory (SUBCAT_PLAYLIST_SD)
77 set_capability ("services_discovery", 0)
78 set_callbacks (OpenDisc, Close)
81 VLC_SD_PROBE_SUBMODULE
85 static int vlc_sd_probe_Open (vlc_object_t *obj)
87 vlc_probe_t *probe = (vlc_probe_t *)obj;
89 struct udev *udev = udev_new ();
91 return VLC_PROBE_CONTINUE;
93 struct udev_monitor *mon = udev_monitor_new_from_netlink (udev, "udev");
96 vlc_sd_probe_Add (probe, "v4l{longname=\"Video capture\"}",
97 N_("Video capture"), SD_CAT_DEVICES);
99 if (!module_exists ("pulselist"))
100 vlc_sd_probe_Add (probe, "alsa{longname=\"Audio capture\"}",
101 N_("Audio capture"), SD_CAT_DEVICES);
103 vlc_sd_probe_Add (probe, "disc{longname=\"Discs\"}", N_("Discs"),
105 udev_monitor_unref (mon);
108 return VLC_PROBE_CONTINUE;
113 dev_t devnum; /* must be first */
115 services_discovery_t *sd;
121 char * (*get_mrl) (struct udev_device *dev);
122 char * (*get_name) (struct udev_device *dev);
126 struct services_discovery_sys_t
128 const struct subsys *subsys;
129 struct udev_monitor *monitor;
135 * Compares two devices (to support binary search).
137 static int cmpdev (const void *a, const void *b)
139 const dev_t *da = a, *db = b;
140 dev_t delta = *da - *db;
142 if (sizeof (delta) > sizeof (int))
143 return delta ? ((delta > 0) ? 1 : -1) : 0;
144 return (signed)delta;
147 static void DestroyDevice (void *data)
149 struct device *d = data;
152 services_discovery_RemoveItem (d->sd, d->item);
153 vlc_gc_decref (d->item);
157 static char *decode_property (struct udev_device *, const char *);
160 * Adds a udev device.
162 static int AddDevice (services_discovery_t *sd, struct udev_device *dev)
164 services_discovery_sys_t *p_sys = sd->p_sys;
166 char *mrl = p_sys->subsys->get_mrl (dev);
168 return 0; /* don't know if it was an error... */
169 char *name = p_sys->subsys->get_name (dev);
170 input_item_t *item = input_item_NewWithType (mrl, name ? name : mrl,
172 p_sys->subsys->item_type);
173 msg_Dbg (sd, "adding %s (%s)", mrl, name);
179 struct device *d = malloc (sizeof (*d));
182 vlc_gc_decref (item);
185 d->devnum = udev_device_get_devnum (dev);
189 struct device **dp = tsearch (d, &p_sys->root, cmpdev);
190 if (dp == NULL) /* Out-of-memory */
195 if (*dp != d) /* Overwrite existing device */
201 services_discovery_AddItem (sd, item, NULL);
207 * Removes a udev device (if present).
209 static void RemoveDevice (services_discovery_t *sd, struct udev_device *dev)
211 services_discovery_sys_t *p_sys = sd->p_sys;
213 dev_t num = udev_device_get_devnum (dev);
214 struct device **dp = tfind (&(dev_t){ num }, &p_sys->root, cmpdev);
218 struct device *d = *dp;
219 tdelete (d, &p_sys->root, cmpdev);
223 static void *Run (void *);
226 * Probes and initializes.
228 static int Open (vlc_object_t *obj, const struct subsys *subsys)
230 services_discovery_t *sd = (services_discovery_t *)obj;
231 services_discovery_sys_t *p_sys = malloc (sizeof (*p_sys));
236 p_sys->subsys = subsys;
239 struct udev_monitor *mon = NULL;
240 struct udev *udev = udev_new ();
244 mon = udev_monitor_new_from_netlink (udev, "udev");
246 || udev_monitor_filter_add_match_subsystem_devtype (mon, subsys->name,
249 p_sys->monitor = mon;
251 /* Enumerate existing devices */
252 struct udev_enumerate *devenum = udev_enumerate_new (udev);
255 if (udev_enumerate_add_match_subsystem (devenum, subsys->name))
257 udev_enumerate_unref (devenum);
261 udev_monitor_enable_receiving (mon);
262 /* Note that we enumerate _after_ monitoring is enabled so that we do not
263 * loose device events occuring while we are enumerating. We could still
264 * loose events if the Netlink socket receive buffer overflows. */
265 udev_enumerate_scan_devices (devenum);
266 struct udev_list_entry *devlist = udev_enumerate_get_list_entry (devenum);
267 struct udev_list_entry *deventry;
268 udev_list_entry_foreach (deventry, devlist)
270 const char *path = udev_list_entry_get_name (deventry);
271 struct udev_device *dev = udev_device_new_from_syspath (udev, path);
273 udev_device_unref (dev);
275 udev_enumerate_unref (devenum);
277 if (vlc_clone (&p_sys->thread, Run, sd, VLC_THREAD_PRIORITY_LOW))
278 { /* Fallback without thread */
279 udev_monitor_unref (mon);
281 p_sys->monitor = NULL;
287 udev_monitor_unref (mon);
297 static void Close (vlc_object_t *obj)
299 services_discovery_t *sd = (services_discovery_t *)obj;
300 services_discovery_sys_t *p_sys = sd->p_sys;
302 if (p_sys->monitor != NULL)
304 struct udev *udev = udev_monitor_get_udev (p_sys->monitor);
306 vlc_cancel (p_sys->thread);
307 vlc_join (p_sys->thread, NULL);
308 udev_monitor_unref (p_sys->monitor);
312 tdestroy (p_sys->root, DestroyDevice);
316 static void *Run (void *data)
318 services_discovery_t *sd = data;
319 services_discovery_sys_t *p_sys = sd->p_sys;
320 struct udev_monitor *mon = p_sys->monitor;
322 int fd = udev_monitor_get_fd (mon);
323 struct pollfd ufd = { .fd = fd, .events = POLLIN, };
327 while (poll (&ufd, 1, -1) == -1)
331 int canc = vlc_savecancel ();
332 struct udev_device *dev = udev_monitor_receive_device (mon);
336 const char *action = udev_device_get_action (dev);
337 if (!strcmp (action, "add"))
339 else if (!strcmp (action, "remove"))
340 RemoveDevice (sd, dev);
341 else if (!strcmp (action, "change"))
343 RemoveDevice (sd, dev);
346 udev_device_unref (dev);
347 vlc_restorecancel (canc);
353 * Converts an hexadecimal digit to an integer.
355 static int hex (char c)
357 if (c >= '0' && c <= '9')
359 if (c >= 'A' && c <= 'F')
361 if (c >= 'a' && c <= 'f')
367 * Decodes a udev hexadecimal-encoded property.
369 static char *decode (const char *enc)
371 char *ret = enc ? strdup (enc) : NULL;
376 for (const char *in = ret; *in; out++)
380 if ((in[0] == '\\') && (in[1] == 'x')
381 && ((h1 = hex (in[2])) != -1)
382 && ((h2 = hex (in[3])) != -1))
384 *out = (h1 << 4) | h2;
397 static char *decode_property (struct udev_device *dev, const char *name)
399 return decode (udev_device_get_property_value (dev, name));
403 /*** Video4Linux support ***/
404 static bool v4l_is_legacy (struct udev_device *dev)
408 version = udev_device_get_property_value (dev, "ID_V4L_VERSION");
409 return (version != NULL) && !strcmp (version, "1");
412 static bool v4l_can_capture (struct udev_device *dev)
416 caps = udev_device_get_property_value (dev, "ID_V4L_CAPABILITIES");
417 return (caps != NULL) && (strstr (caps, ":capture:") != NULL);
420 static char *v4l_get_mrl (struct udev_device *dev)
422 /* Determine media location */
423 if (v4l_is_legacy (dev) || !v4l_can_capture (dev))
426 const char *node = udev_device_get_devnode (dev);
429 if (asprintf (&mrl, "v4l2://%s", node) == -1)
434 static char *v4l_get_name (struct udev_device *dev)
436 const char *prd = udev_device_get_property_value (dev, "ID_V4L_PRODUCT");
437 return prd ? strdup (prd) : NULL;
440 int OpenV4L (vlc_object_t *obj)
442 static const struct subsys subsys = {
443 "video4linux", v4l_get_mrl, v4l_get_name, ITEM_TYPE_CARD,
446 return Open (obj, &subsys);
451 /*** Advanced Linux Sound Architecture support ***/
452 #include <alsa/asoundlib.h>
454 static int alsa_get_device (struct udev_device *dev, unsigned *restrict pcard,
455 unsigned *restrict pdevice)
457 const char *node = udev_device_get_devpath (dev);
460 node = strrchr (node, '/');
463 if (sscanf (node, "/pcmC%uD%u%c", pcard, pdevice, &type) < 3)
471 static char *alsa_get_mrl (struct udev_device *dev)
473 /* Determine media location */
475 unsigned card, device;
477 if (alsa_get_device (dev, &card, &device))
480 if (asprintf (&mrl, "alsa://plughw:%u,%u", card, device) == -1)
485 static char *alsa_get_name (struct udev_device *dev)
488 unsigned card, device;
490 if (alsa_get_device (dev, &card, &device))
493 char card_name[4 + 3 * sizeof (int)];
494 snprintf (card_name, sizeof (card_name), "hw:%u", card);
497 if (snd_ctl_open (&ctl, card_name, 0))
500 snd_pcm_info_t *pcm_info;
501 snd_pcm_info_alloca (&pcm_info);
502 snd_pcm_info_set_device (pcm_info, device);
503 snd_pcm_info_set_subdevice (pcm_info, 0);
504 snd_pcm_info_set_stream (pcm_info, SND_PCM_STREAM_CAPTURE);
505 if (snd_ctl_pcm_info (ctl, pcm_info))
508 name = strdup (snd_pcm_info_get_name (pcm_info));
514 int OpenALSA (vlc_object_t *obj)
516 static const struct subsys subsys = {
517 "sound", alsa_get_mrl, alsa_get_name, ITEM_TYPE_CARD,
520 return Open (obj, &subsys);
522 #endif /* HAVE_ALSA */
525 /*** Discs support ***/
526 static char *disc_get_mrl (struct udev_device *dev)
528 const char *node = udev_device_get_devnode (dev);
531 val = udev_device_get_property_value (dev, "ID_CDROM");
533 return NULL; /* Ignore non-optical block devices */
535 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_STATE");
537 { /* Force probing of the disc in the drive if any. */
538 int fd = open (node, O_RDONLY);
542 if (!strcmp (val, "blank"))
543 return NULL; /* ignore empty drives and virgin recordable discs */
545 const char *scheme = NULL;
546 val = udev_device_get_property_value (dev,
547 "ID_CDROM_MEDIA_TRACK_COUNT_AUDIO");
548 if (val && atoi (val))
549 scheme = "cdda"; /* Audio CD rather than file system */
550 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_DVD");
551 if (val && atoi (val))
554 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_BD");
555 if (val && atoi (val))
558 val = udev_device_get_property_value (dev, "ID_CDROM_MEDIA_HDDVD");
559 if (val && atoi (val))
563 /* We didn't get any property that could tell we have optical disc
568 return vlc_path2uri (node, scheme);
571 static char *disc_get_name (struct udev_device *dev)
574 struct udev_list_entry *list, *entry;
576 list = udev_device_get_properties_list_entry (dev);
577 if (unlikely(list == NULL))
580 const char *cat = NULL;
581 udev_list_entry_foreach (entry, list)
583 const char *name = udev_list_entry_get_name (entry);
585 if (strncmp (name, "ID_CDROM_MEDIA_", 15))
587 if (!atoi (udev_list_entry_get_value (entry)))
591 if (!strncmp (name, "CD", 2))
593 else if (!strncmp (name, "DVD", 3))
595 else if (!strncmp (name, "BD", 2))
597 else if (!strncmp (name, "HDDVD", 5))
605 cat = N_("Unknown type");
607 char *label = decode_property (dev, "ID_FS_LABEL_ENC");
610 if (asprintf(&name, "%s (%s)", label, vlc_gettext(cat)) < 0)
617 int OpenDisc (vlc_object_t *obj)
619 static const struct subsys subsys = {
620 "block", disc_get_mrl, disc_get_name, ITEM_TYPE_DISC,
623 return Open (obj, &subsys);