diff options
Diffstat (limited to '3rdparty/media-enum')
| -rw-r--r-- | 3rdparty/media-enum/.gitignore | 2 | ||||
| -rw-r--r-- | 3rdparty/media-enum/Makefile | 22 | ||||
| -rw-r--r-- | 3rdparty/media-enum/include/media-enumerate.h | 114 | ||||
| -rw-r--r-- | 3rdparty/media-enum/media-enumerate.c | 646 | 
4 files changed, 784 insertions, 0 deletions
| diff --git a/3rdparty/media-enum/.gitignore b/3rdparty/media-enum/.gitignore new file mode 100644 index 0000000..9eca6c8 --- /dev/null +++ b/3rdparty/media-enum/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/3rdparty/media-enum/Makefile b/3rdparty/media-enum/Makefile new file mode 100644 index 0000000..a79744b --- /dev/null +++ b/3rdparty/media-enum/Makefile @@ -0,0 +1,22 @@ +AR	:= $(CROSS_COMPILE)ar +CC	:= $(CROSS_COMPILE)gcc +CFLAGS	:= -O2 -W -Wall -Wno-unused-parameter -I$(KDIR)/usr/include -I$(topdir)/3rdparty/media-ctl/include -Iinclude/ -I. -fPIC +LDFLAGS	:= + +OBJECTS := media-enumerate.o + +TARGET := libmediaenum.a + +all: $(TARGET) + +$(TARGET): $(OBJECTS) +	$(AR) -r $@ $^ + +%.o : %.c +	$(CC) $(CFLAGS) -c -o $@ $< + +clean: +	-$(RM) *.o +	-$(RM) $(TARGET) + +install: diff --git a/3rdparty/media-enum/include/media-enumerate.h b/3rdparty/media-enum/include/media-enumerate.h new file mode 100644 index 0000000..ecd6b3d --- /dev/null +++ b/3rdparty/media-enum/include/media-enumerate.h @@ -0,0 +1,114 @@ +/* + * Media device discovery library + * + * Copyright (C) 2012 Ideas on board SPRL + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __MEDIA_ENUMERATE_H__ +#define __MEDIA_ENUMERATE_H__ + +struct media_enumerate; + +/** + * @brief Create a new media device enumeration context. + * + * Media device enumeration contexts are used to enumerate media devices. They + * store all context information required by enumeration and cache the + * enumeration results for later access. Enumeration itself is performed by the + * separate media_enumerate_scan() function. + * + * Enumeration contexts are reference-counted, see media_enumerate_ref() and + * media_enumerate_unref() for more information. + * + * @return A pointer to the new media device enumeration context or NULL if + * memory cannot be allocated. + */ +struct media_enumerate *media_enumerate_new(void); + +/** + * @brief Take a reference to the enumeration context. + * @param media_enum - enumeration context instance. + * + * Media device enumeration contexts are reference-counted. Taking a reference + * to an enumeration context prevents it and all the information it stores from + * being freed until all references are released. The reference count is + * initialized to 1 when the context is created. + * + * @return A pointer to @a media. + */ +struct media_enumerate *media_enumerate_ref(struct media_enumerate *media_enum); + +/** + * @brief Release a reference to the enumeration context. + * @param media_enum - enumeration context instance. + * + * Release a reference to the enumeration context. When the reference count + * reaches 0 this function frees the context, including all information it + * stores. + */ +void media_enumerate_unref(struct media_enumerate *media_enum); + +/** + * @brief Enumerate media devices in the system + * @param media_enum - enumeration context instance. + * + * Scan the system to locate all available media devices. This function must be + * called once and only once before accessing the list of enumerated devices. + * + * Devices are enumerated using libudev if available or through sysfs otherwise. + * sysfs-based enumeration requires device nodes to use the standard Linux + * device names and be numbered by the device sysfs device number. For instance + * the device node corresponding to /sys/class/video4linux/video0 must be named + * /dev/video0. libudev-based enumeration doesn't have this restriction. + * + * Media devices are enumerated by scanning media controller, V4L and ALSA + * kernel devices, in that order. Emulated media devices are created as needed + * for V4L devices that are not part of a media controller based device. ALSA + * devices without a corresponding media controller or V4L devices are skipped. + * + * @return Zero on success or a negative error code on failure. + */ +int media_enumerate_scan(struct media_enumerate *media_enum); + +/** + * @brief Get the number of media devices found during enumeration + * @param media_enum - enumeration context instance. + * + * This function returns the total number of media devices found in the system + * during enumeration. If media devices haven't been enumerated yet it will + * return 0. + * + * @return The number of media devices found in the system + */ +unsigned int media_enumerate_get_devices_count(struct media_enumerate *media_enum); + +/** + * @brief Get the media devices + * @param media_enum - enumeration context instance. + * + * This function returns a pointer to the array of enumerated media devices. If + * If media devices haven't been enumerated yet it will return NULL. + * + * The array of media devices is owned by the enumeration context and will be + * freed when the enumeration context is destroyed. + * + * @return A pointer to an array of media devices + */ +struct media_device **media_enumerate_get_devices(struct media_enumerate *media_enum); + +#endif	/* __MEDIA_ENUMERATE_H__ */ diff --git a/3rdparty/media-enum/media-enumerate.c b/3rdparty/media-enum/media-enumerate.c new file mode 100644 index 0000000..0db4cdd --- /dev/null +++ b/3rdparty/media-enum/media-enumerate.c @@ -0,0 +1,646 @@ +/* + * Media device discovery library + * + * Copyright (C) 2012 Ideas on board SPRL + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#define HAVE_LIBUDEV + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <linux/videodev2.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#ifdef HAVE_LIBUDEV +#include <libudev.h> +#endif + +#include "mediactl.h" + +#include "media-enumerate.h" + +struct media_enumerate { +	unsigned int refcount; + +	char **syspaths; +	struct media_device **devices; +	unsigned int devices_count; +	unsigned int devices_max; + +#ifdef HAVE_LIBUDEV +	struct udev *udev; +#endif +}; + +static int media_enumerate_add_device(struct media_enumerate *media_enum, +				      const char *syspath, +				      struct media_device *media) +{ +	if (media_enum->devices_count == media_enum->devices_max) { +		struct media_device **devices; +		char **syspaths; +		unsigned int count; + +		count = media_enum->devices_max * 2; +		count = count < 16 ? 16 : count; + +		devices = realloc(media_enum->devices, count * sizeof *devices); +		if (devices == NULL) +			return -ENOMEM; +		media_enum->devices = devices; + +		syspaths = realloc(media_enum->syspaths, count * sizeof *syspaths); +		if (syspaths == NULL) +			return -ENOMEM; +		media_enum->syspaths = syspaths; + +		media_enum->devices_max = count; +	} + +	media_enum->devices[media_enum->devices_count] = media; +	media_enum->syspaths[media_enum->devices_count] = strdup(syspath); +	media_enum->devices_count++; + +	return 0; +} + +typedef int(*media_enumerate_callback)(struct media_enumerate *, const char *, +				       const char *, dev_t); + +enum media_enumerate_bus_type { +	BUS_TYPE_UNKNOWN, +	BUS_TYPE_PCI, +	BUS_TYPE_USB, +}; + +static enum media_enumerate_bus_type +media_enumerate_get_physical_bus(const char *syspath) +{ +	char *modalias; +	char type[4]; +	int ret; +	int fd; + +	modalias = malloc(strlen(syspath) + 10); +	if (modalias == NULL) +		return BUS_TYPE_UNKNOWN; + +	sprintf(modalias, "%s/modalias", syspath); +	fd = open(modalias, O_RDONLY); +	free(modalias); + +	if (fd == -1) +		return BUS_TYPE_UNKNOWN; + +	ret = read(fd, type, 3); +	close(fd); + +	if (ret != 3) +		return BUS_TYPE_UNKNOWN; + +	type[3] = '\0'; + +	if (!strcmp(type, "pci")) +		return BUS_TYPE_PCI; +	else if (!strcmp(type, "usb")) +		return BUS_TYPE_USB; +	else +		return BUS_TYPE_UNKNOWN; +} + +static char *media_enumerate_get_physical_parent(const char *syspath) +{ +	enum media_enumerate_bus_type bus_type; +	char *phys; +	char *p; + +	phys = realpath(syspath, NULL); +	if (phys == NULL) +		return NULL; + +	p = strstr(phys, "media"); +	if (p) +		*(p - 1) = '\0'; +	p = strstr(phys, "sound"); +	if (p) +		*(p - 1) = '\0'; +	p = strstr(phys, "video4linux"); +	if (p) +		*(p - 1) = '\0'; + +	bus_type = media_enumerate_get_physical_bus(phys); + +	switch (bus_type) { +	case BUS_TYPE_PCI: +		/* Remove the PCI function from the path if present. */ +		p = strrchr(phys, '.'); +		if (p) +			*p = '\0'; +		break; +	case BUS_TYPE_USB: +		/* Remove the USB interface from the path if present. */ +		p = strrchr(phys, '/'); +		if (p && strchr(p, ':')) +			*p = '\0'; +		break; +	default: +		break; +	} + +	return phys; +} + +#ifdef HAVE_LIBUDEV + +/* ----------------------------------------------------------------------------- + * udev helpers + */ + +static int media_enumerate_udev_scan_one(struct media_enumerate *media_enum, +					 const char *syspath, +					 media_enumerate_callback callback) +{ +	struct udev_device *dev; +	const char *devnode; +	char *devpath = NULL; +	dev_t devnum; +	int ret = -ENODEV; + +	dev = udev_device_new_from_syspath(media_enum->udev, syspath); +	if (dev == NULL) +		return -ENODEV; + +	devnode = udev_device_get_devnode(dev); +	if (devnode == NULL) +		goto done; + +	devpath = media_enumerate_get_physical_parent(syspath); +	if (devpath == NULL) +		goto done; + +	devnum = udev_device_get_devnum(dev); +	ret = callback(media_enum, devpath, devnode, devnum); + +done: +	udev_device_unref(dev); +	free(devpath); +	return ret; +} + +static int media_enumerate_udev_scan(struct media_enumerate *media_enum, +				     const char *subsystem, const char *type, +				     media_enumerate_callback callback) +{ +	struct udev_enumerate *udev_enum = NULL; +	struct udev_list_entry *devs; +	struct udev_list_entry *dev; +	unsigned int len; +	int ret; + +	udev_enum = udev_enumerate_new(media_enum->udev); +	if (udev_enum == NULL) { +		ret = -ENOMEM; +		goto done; +	} + +	ret = udev_enumerate_add_match_subsystem(udev_enum, subsystem); +	if (ret < 0) +		goto done; + +	ret = udev_enumerate_scan_devices(udev_enum); +	if (ret < 0) +		goto done; + +	devs = udev_enumerate_get_list_entry(udev_enum); +	if (devs == NULL) +		goto done; + +	len = strlen(type); + +	udev_list_entry_foreach(dev, devs) { +		const char *syspath; +		const char *p; + +		syspath = udev_list_entry_get_name(dev); + +		p = strrchr(syspath, '/'); +		if (p == NULL || strncmp(p + 1, type, len)) +			continue; + +		media_enumerate_udev_scan_one(media_enum, syspath, callback); +	} + +	ret = 0; + +done: +	udev_enumerate_unref(udev_enum); +	return ret; +} + +#else + +/* ----------------------------------------------------------------------------- + * sysfs helpers + */ + +static int +media_enumerate_sysfs_scan_one(struct media_enumerate *media_enum, +			       const char *sysbase, const char *prefix, int idx, +			       media_enumerate_callback callback) +{ +	unsigned int major; +	unsigned int minor; +	struct stat devstat; +	unsigned int len; +	char *devname; +	char *devpath; +	char dev[20]; +	char *end; +	int ret; +	int fd; + +	/* Read the device major and minor from sysfs. */ +	len = strlen(sysbase) + strlen(prefix) + 17; +	devname = malloc(len); +	if (devname == NULL) +		return -ENOMEM; + +	sprintf(devname, "%s/%s%u", sysbase, prefix, idx); +	devpath = media_enumerate_get_physical_parent(devname); +	if (devpath == NULL) +		goto done; + +	sprintf(devname, "%s/%s%u/dev", sysbase, prefix, idx); +	fd = open(devname, O_RDONLY); +	if (fd == -1) { +		ret = -ENODEV; +		goto done; +	} + +	ret = read(fd, dev, sizeof dev - 1); +	close(fd); +	if (ret < 0) { +		ret = -ENODEV; +		goto done; +	} + +	dev[ret] = '\0'; +	major = strtoul(dev, &end, 10); +	if (*end != ':') { +		ret = -ENODEV; +		goto done; +	} + +	minor = strtoul(end + 1, &end, 10); +	if (!isspace(*end) && end != '\0') { +		ret = -ENODEV; +		goto done; +	} + +	/* Verify that the device node exists. udev might have reordered the +	 * device nodes, make sure the major/minor match as a sanity check. +	 */ +	if (!strcmp(prefix, "controlC")) +		sprintf(devname, "/dev/snd/%s%u", prefix, idx); +	else +		sprintf(devname, "/dev/%s%u", prefix, idx); + +	ret = stat(devname, &devstat); +	if (ret < 0) { +		ret = -ENODEV; +		goto done; +	} + +	if (major(devstat.st_rdev) != major || minor(devstat.st_rdev) != minor) { +		ret = -ENODEV; +		goto done; +	} + +	ret = callback(media_enum, devpath, devname, makedev(major, minor)); + +done: +	free(devname); +	free(devpath); +	return ret; +} + +static int +media_enumerate_sysfs_scan(struct media_enumerate *media_enum, +			   const char *sysbase, const char *prefix, +			   media_enumerate_callback callback) +{ +	struct dirent *ent; +	unsigned int len; +	char *end; +	DIR *dir; + +	len = strlen(prefix); + +	dir = opendir(sysbase); +	if (dir == NULL) +		return -ENODEV; + +	while ((ent = readdir(dir)) != NULL) { +		unsigned int idx; + +		if (strncmp(ent->d_name, prefix, len)) +			continue; + +		idx = strtoul(ent->d_name + len, &end, 10); +		if (*end != '\0') +			continue; + +		media_enumerate_sysfs_scan_one(media_enum, sysbase, prefix, idx, +					       callback); +	} + +	closedir(dir); + +	return 0; +} + +#endif + +/* ----------------------------------------------------------------------------- + * Media devices enumeration + */ + +static int media_enumerate_match_one_media(struct media_enumerate *media_enum, +					   const char *syspath, const char *devname, +					   dev_t devnum __attribute__((__unused__))) +{ +	struct media_device *media; +	int ret; + +	media = media_device_new(devname); +	if (media == NULL) +		return -ENODEV; + +	ret = media_device_enumerate(media); +	if (ret < 0) { +		media_device_unref(media); +		return -ENODEV; +	} + +	return media_enumerate_add_device(media_enum, syspath, media); +} + +static int media_enumerate_scan_media(struct media_enumerate *media_enum) +{ +#ifdef HAVE_LIBUDEV +	return media_enumerate_udev_scan(media_enum, "media", "media", +					 media_enumerate_match_one_media); +#else +	return media_enumerate_sysfs_scan(media_enum, "/sys/bus/media/devices", +					  "media", media_enumerate_match_one_media); +#endif	/* HAVE_LIBUDEV */ +} + +/* ----------------------------------------------------------------------------- + * Video devices enumeration + */ + +static int media_enumerate_match_one_video(struct media_enumerate *media_enum, +					   const char *syspath, +					   const char *devname, dev_t devnum) +{ +	struct media_entity_desc entity; +	struct media_device_info info; +	struct v4l2_capability cap; +	struct media_device *media; +	unsigned int i, j; +	int ret; +	int fd; + +	/* Verify whether the video device node is part of an already enumerated +	 * media device. +	 */ +	for (i = 0; i < media_enum->devices_count; ++i) { +		unsigned int nents; + +		media = media_enum->devices[i]; +		nents = media_get_entities_count(media); + +		for (j = 0; j < nents; ++j) { +			struct media_entity *entity = media_get_entity(media, j); +			const struct media_entity_desc *info = media_entity_get_info(entity); +			dev_t dev; + +			if (info->type != MEDIA_ENT_T_DEVNODE_V4L) +				continue; + +			dev = makedev(info->v4l.major, info->v4l.minor); +			if (dev == devnum) +				return 0; +		} +	} + +	fd = open(devname, O_RDWR); +	if (fd < 0) +		return -ENODEV; + +	memset(&cap, 0, sizeof cap); +	ret = ioctl(fd, VIDIOC_QUERYCAP, &cap); +	close(fd); + +	if (ret < 0) +		return -ENODEV; + +	memset(&info, 0, sizeof info); +	memcpy(info.driver, cap.driver, sizeof info.driver); +	memcpy(info.model, cap.card, sizeof info.model); +	memcpy(info.bus_info, cap.bus_info, sizeof info.bus_info); +	info.driver_version = cap.version; + +	media = media_device_new_emulated(&info); +	if (media == NULL) +		return -ENOMEM; + +	memset(&entity, 0, sizeof entity); +	memcpy(entity.name, cap.card, sizeof entity.name); +	entity.type = MEDIA_ENT_T_DEVNODE_V4L; +	entity.flags = MEDIA_ENT_FL_DEFAULT; +	entity.v4l.major = major(devnum); +	entity.v4l.minor = minor(devnum); + +	ret = media_device_add_entity(media, &entity, devname); +	if (ret < 0) { +		media_device_unref(media); +		return ret; +	} + +	return media_enumerate_add_device(media_enum, syspath, media); +} + +static int media_enumerate_scan_video(struct media_enumerate *media_enum) +{ +#ifdef HAVE_LIBUDEV +	return media_enumerate_udev_scan(media_enum, "video4linux", "video", +					 media_enumerate_match_one_video); +#else +	return media_enumerate_sysfs_scan(media_enum, "/sys/class/video4linux", +					  "video", media_enumerate_match_one_video); +#endif	/* HAVE_LIBUDEV */ +} + +/* ----------------------------------------------------------------------------- + * Audio devices enumeration + */ + +static int media_enumerate_match_one_audio(struct media_enumerate *media_enum, +					   const char *syspath, const char *devname, +					   dev_t devnum __attribute__((__unused__))) +{ +	struct media_entity_desc entity; +	struct media_device *parent = NULL; +	struct media_device *media; +	unsigned int card; +	unsigned int i, j; +	char *endp; + +	/* Verify whether the sound card is part of an already enumerated media +	 * device. +	 */ +	card = strtoul(strrchr(devname, '/') + strlen("/controlC"), &endp, 10); +	if (*endp != '\0') +		return -EINVAL; + +	for (i = 0; i < media_enum->devices_count; ++i) { +		unsigned int nents; + +		media = media_enum->devices[i]; +		nents = media_get_entities_count(media); + +		if (!strcmp(media_enum->syspaths[i], syspath)) +			parent = media; + +		for (j = 0; j < nents; ++j) { +			struct media_entity *entity = media_get_entity(media, j); +			const struct media_entity_desc *info = media_entity_get_info(entity); + +			if (info->type != MEDIA_ENT_T_DEVNODE_ALSA) +				continue; + +			if (info->alsa.card == card) +				return 0; +		} +	} + +	if (!parent) +		return 0; + +	memset(&entity, 0, sizeof entity); +	entity.type = MEDIA_ENT_T_DEVNODE_ALSA; +	entity.flags = MEDIA_ENT_FL_DEFAULT; +	snprintf(entity.name, sizeof entity.name, "ALSA %u/%u/%u", card, 0, 0); +	entity.alsa.card = card; +	entity.alsa.device = 0; +	entity.alsa.subdevice = 0; + +	return media_device_add_entity(parent, &entity, devname); +} + +static int media_enumerate_scan_audio(struct media_enumerate *media_enum) +{ +#ifdef HAVE_LIBUDEV +	return media_enumerate_udev_scan(media_enum, "sound", "control", +					 media_enumerate_match_one_audio); +#else +	return media_enumerate_sysfs_scan(media_enum, "/sys/class/sound", +					  "controlC", media_enumerate_match_one_audio); +#endif	/* HAVE_LIBUDEV */ +} + +/* -------------------------------------------------------------------------- */ + +struct media_enumerate *media_enumerate_new(void) +{ +	struct media_enumerate *media_enum; + +	media_enum = calloc(1, sizeof *media_enum); +	if (media_enum == NULL) +		return NULL; + +	media_enum->refcount = 1; + +#ifdef HAVE_LIBUDEV +	media_enum->udev = udev_new(); +	if (media_enum->udev == NULL) { +		printf("Unable to get udev context\n"); +		media_enumerate_unref(media_enum); +		return NULL; +	} +#endif + +	return media_enum; +} + +struct media_enumerate *media_enumerate_ref(struct media_enumerate *media_enum) +{ +	media_enum->refcount++; +	return media_enum; +} + +void media_enumerate_unref(struct media_enumerate *media_enum) +{ +	unsigned int i; + +	media_enum->refcount--; +	if (media_enum->refcount > 0) +		return; + +	for (i = 0; i < media_enum->devices_count; ++i) { +		media_device_unref(media_enum->devices[i]); +		free(media_enum->syspaths[i]); +	} + +#ifdef HAVE_LIBUDEV +	udev_unref(media_enum->udev); +#endif + +	free(media_enum->devices); +	free(media_enum->syspaths); +	free(media_enum); +} + +int media_enumerate_scan(struct media_enumerate *media_enum) +{ +	media_enumerate_scan_media(media_enum); +	media_enumerate_scan_video(media_enum); +	media_enumerate_scan_audio(media_enum); + +	return 0; +} + +struct media_device **media_enumerate_get_devices(struct media_enumerate *media_enum) +{ +	return media_enum->devices; +} + +unsigned int media_enumerate_get_devices_count(struct media_enumerate *media_enum) +{ +	return media_enum->devices_count; +} | 
