diff options
| author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2018-06-09 14:29:41 +0300 | 
|---|---|---|
| committer | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2018-06-12 21:19:58 +0300 | 
| commit | 2bb0cfbf8137e02cc32aae3b36f85ef7300e8936 (patch) | |
| tree | 42899dbeb32459722d27606bd79848a25a8c5e16 /lib | |
| parent | df21a9349a256fd2bea1f8701af198b312682d39 (diff) | |
Split UVC gadget into a library and test application
Split the project into a UVC gadget library and a test application. To
avoid rolling out a custom build system, switch to CMake.
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/CMakeLists.txt | 17 | ||||
| -rw-r--r-- | lib/configfs.c | 846 | ||||
| -rw-r--r-- | lib/events.c | 183 | ||||
| -rw-r--r-- | lib/stream.c | 223 | ||||
| -rw-r--r-- | lib/tools.h | 65 | ||||
| -rw-r--r-- | lib/uvc.c | 395 | ||||
| -rw-r--r-- | lib/uvc.h | 36 | ||||
| -rw-r--r-- | lib/v4l2-source.c | 180 | ||||
| -rw-r--r-- | lib/v4l2.c | 833 | ||||
| -rw-r--r-- | lib/v4l2.h | 339 | ||||
| -rw-r--r-- | lib/video-buffers.c | 40 | ||||
| -rw-r--r-- | lib/video-buffers.h | 48 | ||||
| -rw-r--r-- | lib/video-source.c | 62 | 
13 files changed, 3267 insertions, 0 deletions
| diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..c798d3b --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,17 @@ +file(GLOB SOURCES "*.c" "*.h") +file(GLOB HEADERS "../include/uvcgadget/*.h") + +add_library(uvcgadget ${SOURCES}) + +set_target_properties(uvcgadget PROPERTIES PUBLIC_HEADER "${HEADERS}") +target_include_directories(uvcgadget +	PUBLIC +	$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include/uvcgadget> +	$<INSTALL_INTERFACE:include> +	PRIVATE +	${KERNEL_INCLUDE_DIR}) + +install(TARGETS uvcgadget +	LIBRARY DESTINATION lib +	ARCHIVE DESTINATION lib +	PUBLIC_HEADER DESTINATION include/uvcgadget) diff --git a/lib/configfs.c b/lib/configfs.c new file mode 100644 index 0000000..1dfde95 --- /dev/null +++ b/lib/configfs.c @@ -0,0 +1,846 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * ConfigFS Gadget device handling + * + * Copyright (C) 2018 Kieran Bingham + * + * Contact: Kieran Bingham <kieran.bingham@ideasonboard.com> + */ + +/* To provide basename and asprintf from the GNU library. */ + #define _GNU_SOURCE + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <linux/videodev2.h> + +#include "configfs.h" +#include "tools.h" + +/* ----------------------------------------------------------------------------- + * Path handling and support + */ + +static char *path_join(const char *dirname, const char *name) +{ +	char *path; + +	asprintf(&path, "%s/%s", dirname, name); + +	return path; +} + +static char *path_glob_first_match(const char *g) +{ +	glob_t globbuf; +	char *match = NULL; + +	glob(g, 0, NULL, &globbuf); + +	if (globbuf.gl_pathc) +		match = strdup(globbuf.gl_pathv[0]); + +	globfree(&globbuf); + +	return match; +} + +/* + * Find and return the full path of the first directory entry that satisfies + * the match function. + */ +static char *dir_first_match(const char *dir, int(*match)(const struct dirent *)) +{ +	struct dirent **entries; +	unsigned int i; +	int n_entries; +	char *path; + +	n_entries = scandir(dir, &entries, match, alphasort); +	if (n_entries < 0) +		return NULL; + +	if (n_entries == 0) { +		free(entries); +		return NULL; +	} + +	path = path_join(dir, entries[0]->d_name); + +	for (i = 0; i < (unsigned int)n_entries; ++i) +		free(entries[i]); + +	free(entries); + +	return path; +} + +/* ----------------------------------------------------------------------------- + * Attribute handling + */ + +static int attribute_read(const char *path, const char *file, void *buf, +			  unsigned int len) +{ +	char *f; +	int ret; +	int fd; + +	f = path_join(path, file); +	if (!f) +		return -ENOMEM; + +	fd = open(f, O_RDONLY); +	free(f); +	if (fd == -1) { +		printf("Failed to open attribute %s: %s\n", file, +		       strerror(errno)); +		return -ENOENT; +	} + +	ret = read(fd, buf, len); +	close(fd); + +	if (ret < 0) { +		printf("Failed to read attribute %s: %s\n", file, +		       strerror(errno)); +		return -ENODATA; +	} + +	return len; +} + +static int attribute_read_uint(const char *path, const char *file, +			       unsigned int *val) +{ +	/* 4,294,967,295 */ +	char buf[11]; +	char *endptr; +	int ret; + +	ret = attribute_read(path, file, buf, sizeof(buf) - 1); +	if (ret < 0) +		return ret; + +	buf[ret] = '\0'; + +	errno = 0; + +	/* base 0: Autodetect hex, octal, decimal. */ +	*val = strtoul(buf, &endptr, 0); +	if (errno) +		return -errno; + +	if (endptr == buf) +		return -ENODATA; + +	return 0; +} + +static char *attribute_read_str(const char *path, const char *file) +{ +	char buf[1024]; +	char *p; +	int ret; + +	ret = attribute_read(path, file, buf, sizeof(buf) - 1); +	if (ret < 0) +		return NULL; + +	buf[ret] = '\0'; + +	p = strrchr(buf, '\n'); +	if (p != buf) +		*p = '\0'; + +	return strdup(buf); +} + +/* ----------------------------------------------------------------------------- + * UDC parsing + */ + +/* + * udc_find_video_device - Find the video device node for a UVC function + * @udc: The UDC name + * @function: The UVC function name + * + * This function finds the video device node corresponding to a UVC function as + * specified by a @function name and @udc name. + * + * The @function parameter specifies the name of the USB function, usually in + * the form "uvc.%u". If NULL the first function found will be used. + * + * The @udc parameter specifies the name of the UDC. If NULL any UDC that + * contains a function matching the @function name will be used. + * + * Return a pointer to a newly allocated string containing the video device node + * full path if the function is found. Otherwise return NULL. The returned + * pointer must be freed by the caller with a call to free(). + */ +static char *udc_find_video_device(const char *udc, const char *function) +{ +	char *vpath; +	char *video = NULL; +	glob_t globbuf; +	unsigned int i; + +	asprintf(&vpath, "/sys/class/udc/%s/device/gadget/video4linux/video*", +		 udc ? udc : "*"); +	if (!vpath) +		return NULL; + +	glob(vpath, 0, NULL, &globbuf); +	free(vpath); + +	for (i = 0; i < globbuf.gl_pathc; ++i) { +		char *config; +		bool match; + +		/* Match on the first if no search string. */ +		if (!function) +			break; + +		config = attribute_read_str(globbuf.gl_pathv[i], +					    "function_name"); +		match = strcmp(function, config) == 0; + +		free(config); + +		if (match) +			break; +	} + +	if (i < globbuf.gl_pathc) { +		const char *v = basename(globbuf.gl_pathv[i]); + +		video = path_join("/dev", v); +	} + +	globfree(&globbuf); + +	return video; +} + +/* ------------------------------------------------------------------------ + * GUIDs and formats + */ + +#define UVC_GUID_FORMAT_MJPEG \ +	{ 'M',  'J',  'P',  'G', 0x00, 0x00, 0x10, 0x00, \ +	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} +#define UVC_GUID_FORMAT_YUY2 \ +	{ 'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00, \ +	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71} + +struct uvc_function_format_info { +	uint8_t guid[16]; +	uint32_t fcc; +}; + +static struct uvc_function_format_info uvc_formats[] = { +	{ +		.guid		= UVC_GUID_FORMAT_YUY2, +		.fcc		= V4L2_PIX_FMT_YUYV, +	}, +	{ +		.guid		= UVC_GUID_FORMAT_MJPEG, +		.fcc		= V4L2_PIX_FMT_MJPEG, +	}, +}; + +/* ----------------------------------------------------------------------------- + * Legacy g_webcam support + */ + +static const struct uvc_function_config g_webcam_config = { +	.control = { +		.intf = { +			.bInterfaceNumber = 0, +		}, +	}, +	.streaming = { +		.intf = { +			.bInterfaceNumber = 1, +		}, +		.ep = { +			.bInterval = 1, +			.bMaxBurst = 0, +			.wMaxPacketSize = 1024, +		}, +		.num_formats = 2, +		.formats = (struct uvc_function_config_format[]) { +			{ +				.index = 1, +				.guid = UVC_GUID_FORMAT_YUY2, +				.fcc = V4L2_PIX_FMT_YUYV, +				.num_frames = 2, +				.frames = (struct uvc_function_config_frame[]) { +					{ +						.index = 1, +						.width = 640, +						.height = 360, +						.num_intervals = 3, +						.intervals = (unsigned int[]) { +							666666, +							10000000, +							50000000, +						}, +					}, { +						.index = 2, +						.width = 1280, +						.height = 720, +						.num_intervals = 1, +						.intervals = (unsigned int[]) { +							50000000, +						}, +					}, +				}, +			}, { +				.index = 2, +				.guid = UVC_GUID_FORMAT_MJPEG, +				.fcc = V4L2_PIX_FMT_MJPEG, +				.num_frames = 2, +				.frames = (struct uvc_function_config_frame[]) { +					{ +						.index = 1, +						.width = 640, +						.height = 360, +						.num_intervals = 3, +						.intervals = (unsigned int[]) { +							666666, +							10000000, +							50000000, +						}, +					}, { +						.index = 2, +						.width = 1280, +						.height = 720, +						.num_intervals = 1, +						.intervals = (unsigned int[]) { +							50000000, +						}, +					}, +				}, +			}, +		}, +	}, +}; + +static int parse_legacy_g_webcam(const char *udc, +				 struct uvc_function_config *fc) +{ +	void *memdup(const void *src, size_t size) +	{ +		void *dst; + +		dst = malloc(size); +		if (!dst) +			return NULL; +		memcpy(dst, src, size); +		return dst; +	} + +	unsigned int i, j; +	size_t size; + +	*fc = g_webcam_config; + +	/* +	 * We need to duplicate all sub-structures as the +	 * configfs_free_uvc_function() function expects them to be dynamically +	 * allocated. +	 */ +	size = sizeof *fc->streaming.formats * fc->streaming.num_formats; +	fc->streaming.formats = memdup(fc->streaming.formats, size); + +	for (i = 0; i < fc->streaming.num_formats; ++i) { +		struct uvc_function_config_format *format = +			&fc->streaming.formats[i]; + +		size = sizeof *format->frames * format->num_frames; +		format->frames = memdup(format->frames, size); + +		for (j = 0; j < format->num_frames; ++j) { +			struct uvc_function_config_frame *frame = +				&format->frames[j]; + +			size = sizeof *frame->intervals * frame->num_intervals; +			frame->intervals = memdup(frame->intervals, size); +		} +	} + +	fc->video = udc_find_video_device(udc, NULL); + +	return fc->video ? 0 : -ENODEV; +} + +/* ----------------------------------------------------------------------------- + * ConfigFS support + */ + +/* + * configfs_find_uvc_function - Find the ConfigFS full path for a UVC function + * @function: The UVC function name + * + * Return a pointer to a newly allocated string containing the full ConfigFS + * path to the function if the function is found. Otherwise return NULL. The + * returned pointer must be freed by the caller with a call to free(). + */ +static char *configfs_find_uvc_function(const char *function) +{ +	const char *target = function ? function : "*"; +	const char *root; +	char *func_path; +	char *path; + +	/* +	 * The function description can be provided as a path from the +	 * usb_gadget root "g1/functions/uvc.0", or if there is no ambiguity +	 * over the gadget name, a shortcut "uvc.0" can be provided. +	 */ +	if (!strchr(target, '/')) +		root = "/sys/kernel/config/usb_gadget/*/functions"; +	else +		root = "/sys/kernel/config/usb_gadget"; + +	path = path_join(root, target); + +	func_path = path_glob_first_match(path); +	free(path); + +	return func_path; +} + +/* + * configfs_free_uvc_function - Free a uvc_function_config object + * @fc: The uvc_function_config to be freed + * + * Free the given @fc function previously allocated by a call to + * configfs_parse_uvc_function(). + */ +void configfs_free_uvc_function(struct uvc_function_config *fc) +{ +	unsigned int i, j; + +	free(fc->udc); +	free(fc->video); + +	for (i = 0; i < fc->streaming.num_formats; ++i) { +		struct uvc_function_config_format *format = +			&fc->streaming.formats[i]; + +		for (j = 0; j < format->num_frames; ++j) { +			struct uvc_function_config_frame *frame = +				&format->frames[j]; + +			free(frame->intervals); +		} + +		free(format->frames); +	} + +	free(fc->streaming.formats); +	free(fc); +} + +#define configfs_parse_child(parent, child, cfg, parse)			\ +({									\ +	char *__path;							\ +	int __ret;							\ +									\ +	__path = path_join((parent), (child));				\ +	if (__path) {							\ +		__ret = parse(__path, (cfg));				\ +		free(__path);						\ +	} else {							\ +		__ret = -ENOMEM;					\ +	}								\ +									\ +	__ret;								\ +}) + +static int configfs_parse_interface(const char *path, +				    struct uvc_function_config_interface *cfg) +{ +	int ret; + +	ret = attribute_read_uint(path, "bInterfaceNumber", +				  &cfg->bInterfaceNumber); + +	return ret; +} + +static int configfs_parse_control(const char *path, +				  struct uvc_function_config_control *cfg) +{ +	int ret; + +	ret = configfs_parse_interface(path, &cfg->intf); + +	return ret; +} + +static int configfs_parse_streaming_frame(const char *path, +			struct uvc_function_config_frame *frame) +{ +	char *intervals; +	char *p; +	int ret = 0; + +	ret = ret ? : attribute_read_uint(path, "bFrameIndex", &frame->index); +	ret = ret ? : attribute_read_uint(path, "wWidth", &frame->width); +	ret = ret ? : attribute_read_uint(path, "wHeight", &frame->height); +	if (ret) +		return ret; + +	intervals = attribute_read_str(path, "dwFrameInterval"); +	if (!intervals) +		return -EINVAL; + +	for (p = intervals; *p; ) { +		unsigned int interval; +		unsigned int *mem; +		char *endp; +		size_t size; + +		interval = strtoul(p, &endp, 10); +		if (*endp != '\0' && *endp != '\n') { +			ret = -EINVAL; +			break; +		} + +		p = *endp ? endp + 1 : endp; + +		size = sizeof *frame->intervals * (frame->num_intervals + 1); +		mem = realloc(frame->intervals, size); +		if (!mem) { +			ret = -ENOMEM; +			break; +		} + +		frame->intervals = mem; +		frame->intervals[frame->num_intervals++] = interval; +	} + +	free(intervals); + +	return ret; +} + +static int configfs_parse_streaming_format(const char *path, +			struct uvc_function_config_format *format) +{ +	int frame_filter(const struct dirent *ent) +	{ +		/* Accept all directories but "." and "..". */ +		if (ent->d_type != DT_DIR) +			return 0; +		if (!strcmp(ent->d_name, ".")) +			return 0; +		if (!strcmp(ent->d_name, "..")) +			return 0; +		return 1; +	} + +	int frame_compare(const void *a, const void *b) +	{ +		const struct uvc_function_config_frame *fa = a; +		const struct uvc_function_config_frame *fb = b; + +		if (fa->index < fb->index) +			return -1; +		else if (fa->index == fb->index) +			return 0; +		else +			return 1; +	} + +	struct dirent **entries; +	unsigned int i; +	int n_entries; +	int ret; + +	ret = attribute_read_uint(path, "bFormatIndex", &format->index); +	if (ret < 0) +		return ret; + +	ret = attribute_read(path, "guidFormat", format->guid, +			     sizeof(format->guid)); +	if (ret < 0) +		return ret; +	if (ret != 16) +		return -EINVAL; + +	for (i = 0; i < ARRAY_SIZE(uvc_formats); ++i) { +		if (!memcmp(uvc_formats[i].guid, format->guid, 16)) { +			format->fcc = uvc_formats[i].fcc; +			break; +		} +	} + +	/* Find all entries corresponding to a frame and parse them. */ +	n_entries = scandir(path, &entries, frame_filter, alphasort); +	if (n_entries < 0) +		return -errno; + +	if (n_entries == 0) { +		free(entries); +		return -EINVAL; +	} + +	format->num_frames = n_entries; +	format->frames = calloc(sizeof *format->frames, format->num_frames); +	if (!format->frames) +		return -ENOMEM; + +	for (i = 0; i < (unsigned int)n_entries; ++i) { +		char *frame; + +		frame = path_join(path, entries[i]->d_name); +		if (!frame) { +			ret = -ENOMEM; +			goto done; +		} + +		ret = configfs_parse_streaming_frame(frame, &format->frames[i]); +		free(frame); +		if (ret < 0) +			goto done; +	} + +	/* Sort the frames by index. */ +	qsort(format->frames, format->num_frames, sizeof *format->frames, +	      frame_compare); + +done: +	for (i = 0; i < (unsigned int)n_entries; ++i) +		free(entries[i]); +	free(entries); + +	return ret; +} + +static int configfs_parse_streaming_header(const char *path, +			struct uvc_function_config_streaming *cfg) +{ +	int format_filter(const struct dirent *ent) +	{ +		char *path; +		bool valid; + +		/* +		 * Accept all links that point to a directory containing a +		 * "bFormatIndex" file. +		 */ +		if (ent->d_type != DT_LNK) +			return 0; + +		path = path_join(ent->d_name, "bFormatIndex"); +		if (!path) +			return 0; + +		valid = access(path, R_OK); +		free(path); +		return valid; +	} + +	int format_compare(const void *a, const void *b) +	{ +		const struct uvc_function_config_format *fa = a; +		const struct uvc_function_config_format *fb = b; + +		if (fa->index < fb->index) +			return -1; +		else if (fa->index == fb->index) +			return 0; +		else +			return 1; +	} + +	struct dirent **entries; +	unsigned int i; +	int n_entries; +	int ret; + +	/* Find all entries corresponding to a format and parse them. */ +	n_entries = scandir(path, &entries, format_filter, alphasort); +	if (n_entries < 0) +		return -errno; + +	if (n_entries == 0) { +		free(entries); +		return -EINVAL; +	} + +	cfg->num_formats = n_entries; +	cfg->formats = calloc(sizeof *cfg->formats, cfg->num_formats); +	if (!cfg->formats) +		return -ENOMEM; + +	for (i = 0; i < (unsigned int)n_entries; ++i) { +		char *format; + +		format = path_join(path, entries[i]->d_name); +		if (!format) { +			ret = -ENOMEM; +			goto done; +		} + +		ret = configfs_parse_streaming_format(format, &cfg->formats[i]); +		free(format); +		if (ret < 0) +			goto done; +	} + +	/* Sort the formats by index. */ +	qsort(cfg->formats, cfg->num_formats, sizeof *cfg->formats, +	      format_compare); + +done: +	for (i = 0; i < (unsigned int)n_entries; ++i) +		free(entries[i]); +	free(entries); + +	return ret; +} + +static int configfs_parse_streaming(const char *path, +				    struct uvc_function_config_streaming *cfg) +{ +	int link_filter(const struct dirent *ent) +	{ +		/* Accept all links. */ +		return ent->d_type == DT_LNK; +	} + +	char *header; +	char *class; +	int ret; + +	ret = configfs_parse_interface(path, &cfg->intf); +	if (ret < 0) +		return ret; + +	/* +	 * Handle the high-speed class descriptors only for now. Find the first +	 * link to the class descriptors. +	 */ +	class = path_join(path, "class/hs"); +	if (!class) +		return -ENOMEM; + +	header = dir_first_match(class, link_filter); +	free(class); +	if (!header) +		return -EINVAL; + +	ret = configfs_parse_streaming_header(header, cfg); +	free(header); +	return ret; +} + +static int configfs_parse_uvc(const char *fpath, +			      struct uvc_function_config *fc) +{ +	int ret = 0; + +	ret = ret ? : configfs_parse_child(fpath, "control", &fc->control, +					   configfs_parse_control); +	ret = ret ? : configfs_parse_child(fpath, "streaming", &fc->streaming, +					   configfs_parse_streaming); + +	/* +	 * These parameters should be part of the streaming interface in +	 * ConfigFS, but for legacy reasons they are located directly in the +	 * function directory. +	 */ +	ret = ret ? : attribute_read_uint(fpath, "streaming_interval", +					  &fc->streaming.ep.bInterval); +	ret = ret ? : attribute_read_uint(fpath, "streaming_maxburst", +					  &fc->streaming.ep.bMaxBurst); +	ret = ret ? : attribute_read_uint(fpath, "streaming_maxpacket", +					  &fc->streaming.ep.wMaxPacketSize); + +	return ret; +} + +/* + * configfs_parse_uvc_function - Parse a UVC function configuration in ConfigFS + * @function: The function name + * + * This function locates and parse the configuration of a UVC function in + * ConfigFS as specified by the @function name argument. The function name can + * be fully qualified with a gadget name (e.g. "g%u/functions/uvc.%u"), or as a + * shortcut can be an unqualified function name (e.g. "uvc.%u"). When the + * function name is unqualified, the first function matching the name in any + * UDC will be returned. + * + * Return a pointer to a newly allocated UVC function configuration structure + * that contains configuration parameters for the function, if the function is + * found. Otherwise return NULL. The returned pointer must be freed by the + * caller with a call to free(). + */ +struct uvc_function_config *configfs_parse_uvc_function(const char *function) +{ +	struct uvc_function_config *fc; +	char *fpath; +	int ret = 0; + +	fc = malloc(sizeof *fc); +	if (fc == NULL) +		return NULL; + +	memset(fc, 0, sizeof *fc); + +	/* Find the function in ConfigFS. */ +	fpath = configfs_find_uvc_function(function); +	if (!fpath) { +		/* +		 * If the function can't be found attempt legacy parsing to +		 * support the g_webcam gadget. The function parameter contains +		 * a UDC name in that case. +		 */ +		ret = parse_legacy_g_webcam(function, fc); +		if (ret) { +			configfs_free_uvc_function(fc); +			fc = NULL; +		} + +		return fc; +	} + +	/* +	 * Parse the function configuration. Remove the gadget name qualifier +	 * from the function name, if any. +	 */ +	if (function) +		function = basename(function); + +	fc->udc = attribute_read_str(fpath, "../../UDC"); +	fc->video = udc_find_video_device(fc->udc, function); +	if (!fc->video) { +		ret = -ENODEV; +		goto done; +	} + +	ret = configfs_parse_uvc(fpath, fc); + +done: +	if (ret) { +		configfs_free_uvc_function(fc); +		fc = NULL; +	} + +	free(fpath); + +	return fc; +} diff --git a/lib/events.c b/lib/events.c new file mode 100644 index 0000000..6840c19 --- /dev/null +++ b/lib/events.c @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Generic Event Handling + * + * Copyright (C) 2018 Laurent Pinchart + * + * This file comes from the omap3-isp-live project + * (git://git.ideasonboard.org/omap3-isp-live.git) + * + * Copyright (C) 2010-2011 Ideas on board SPRL + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#define _DEFAULT_SOURCE +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/select.h> + +#include "events.h" +#include "list.h" +#include "tools.h" + +#define SELECT_TIMEOUT		2000		/* in milliseconds */ + +struct event_fd { +	struct list_entry list; + +	int fd; +	enum event_type type; +	void (*callback)(void *priv); +	void *priv; +}; + +void events_watch_fd(struct events *events, int fd, enum event_type type, +		     void(*callback)(void *), void *priv) +{ +	struct event_fd *event; + +	event = malloc(sizeof *event); +	if (event == NULL) +		return; + +	event->fd = fd; +	event->type = type; +	event->callback = callback; +	event->priv = priv; + +	switch (event->type) { +	case EVENT_READ: +		FD_SET(fd, &events->rfds); +		break; +	case EVENT_WRITE: +		FD_SET(fd, &events->wfds); +		break; +	case EVENT_EXCEPTION: +		FD_SET(fd, &events->efds); +		break; +	} + +	events->maxfd = max(events->maxfd, fd); + +	list_append(&event->list, &events->events); +} + +void events_unwatch_fd(struct events *events, int fd, enum event_type type) +{ +	struct event_fd *event = NULL; +	struct event_fd *entry; +	int maxfd = 0; + +	list_for_each_entry(entry, &events->events, list) { +		if (entry->fd == fd && entry->type == type) +			event = entry; +		else +			maxfd = max(maxfd, entry->fd); +	} + +	if (event == NULL) +		return; + +	switch (event->type) { +	case EVENT_READ: +		FD_CLR(fd, &events->rfds); +		break; +	case EVENT_WRITE: +		FD_CLR(fd, &events->wfds); +		break; +	case EVENT_EXCEPTION: +		FD_CLR(fd, &events->efds); +		break; +	} + +	events->maxfd = maxfd; + +	list_remove(&event->list); +	free(event); +} + +static void events_dispatch(struct events *events, const fd_set *rfds, +			    const fd_set *wfds, const fd_set *efds) +{ +	struct event_fd *event; +	struct event_fd *next; + +	list_for_each_entry_safe(event, next, &events->events, list) { +		if (event->type == EVENT_READ && +		    FD_ISSET(event->fd, rfds)) +			event->callback(event->priv); +		else if (event->type == EVENT_WRITE && +			 FD_ISSET(event->fd, wfds)) +			event->callback(event->priv); +		else if (event->type == EVENT_EXCEPTION && +			 FD_ISSET(event->fd, efds)) +			event->callback(event->priv); + +		/* If the callback stopped events processing, we're done. */ +		if (events->done) +			break; +	} +} + +bool events_loop(struct events *events) +{ +	events->done = false; + +	while (!events->done) { +		fd_set rfds; +		fd_set wfds; +		fd_set efds; +		int ret; + +		rfds = events->rfds; +		wfds = events->wfds; +		efds = events->efds; + +		ret = select(events->maxfd + 1, &rfds, &wfds, &efds, NULL); +		if (ret < 0) { +			/* EINTR means that a signal has been received, continue +			 * to the next iteration in that case. +			 */ +			if (errno == EINTR) +				continue; + +			printf("error: select failed with %d\n", errno); +			break; +		} + +		events_dispatch(events, &rfds, &wfds, &efds); +	} + +	return !events->done; +} + +void events_stop(struct events *events) +{ +	events->done = true; +} + +void events_init(struct events *events) +{ +	memset(events, 0, sizeof *events); + +	FD_ZERO(&events->rfds); +	FD_ZERO(&events->wfds); +	FD_ZERO(&events->efds); +	events->maxfd = 0; +	list_init(&events->events); +} + +void events_cleanup(struct events *events) +{ +	while (!list_empty(&events->events)) { +		struct event_fd *event; + +		event = list_first_entry(&events->events, typeof(*event), list); +		list_remove(&event->list); +		free(event); +	} +} diff --git a/lib/stream.c b/lib/stream.c new file mode 100644 index 0000000..57745aa --- /dev/null +++ b/lib/stream.c @@ -0,0 +1,223 @@ +/* + * UVC stream handling + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "events.h" +#include "stream.h" +#include "uvc.h" +#include "v4l2.h" +#include "video-buffers.h" +#include "video-source.h" + +/* + * struct uvc_stream - Representation of a UVC stream + * @src: video source + * @uvc: UVC V4L2 output device + * @events: struct events containing event information + */ +struct uvc_stream +{ +	struct video_source *src; +	struct uvc_device *uvc; + +	struct events *events; +}; + +/* --------------------------------------------------------------------------- + * Video streaming + */ + +static void uvc_stream_source_process(void *d, struct video_source *src, +				      struct video_buffer *buffer) +{ +	struct uvc_stream *stream = d; +	struct v4l2_device *sink = uvc_v4l2_device(stream->uvc); + +	v4l2_queue_buffer(sink, buffer); +} + +static void uvc_stream_uvc_process(void *d) +{ +	struct uvc_stream *stream = d; +	struct v4l2_device *sink = uvc_v4l2_device(stream->uvc); +	struct video_buffer buf; +	int ret; + +	ret = v4l2_dequeue_buffer(sink, &buf); +	if (ret < 0) +		return; + +	video_source_queue_buffer(stream->src, &buf); +} + +static int uvc_stream_start(struct uvc_stream *stream) +{ +	struct v4l2_device *sink = uvc_v4l2_device(stream->uvc); +	struct video_buffer_set *buffers = NULL; +	int ret; + +	printf("Starting video stream.\n"); + +	/* Allocate and export the buffers on the source. */ +	ret = video_source_alloc_buffers(stream->src, 4); +	if (ret < 0) { +		printf("Failed to allocate source buffers: %s (%d)\n", +		       strerror(-ret), -ret); +		return ret; +	} + +	ret = video_source_export_buffers(stream->src, &buffers); +	if (ret < 0) { +		printf("Failed to export buffers on source: %s (%d)\n", +		       strerror(-ret), -ret); +		goto error_free_source; +	} + +	/* Allocate and import the buffers on the sink. */ +	ret = v4l2_alloc_buffers(sink, V4L2_MEMORY_DMABUF, buffers->nbufs); +	if (ret < 0) { +		printf("Failed to allocate sink buffers: %s (%d)\n", +		       strerror(-ret), -ret); +		goto error_free_source; +	} + +	ret = v4l2_import_buffers(sink, buffers); +	if (ret < 0) { +		printf("Failed to import buffers on sink: %s (%d)\n", +		       strerror(-ret), -ret); +		goto error_free_sink; +	} + +	/* Start the source and sink. */ +	video_source_stream_on(stream->src); +	v4l2_stream_on(sink); + +	events_watch_fd(stream->events, sink->fd, EVENT_WRITE, +			uvc_stream_uvc_process, stream); + +	return 0; + +error_free_sink: +	v4l2_free_buffers(sink); +error_free_source: +	video_source_free_buffers(stream->src); +	if (buffers) +		video_buffer_set_delete(buffers); +	return ret; +} + +static int uvc_stream_stop(struct uvc_stream *stream) +{ +	struct v4l2_device *sink = uvc_v4l2_device(stream->uvc); + +	printf("Stopping video stream.\n"); + +	events_unwatch_fd(stream->events, sink->fd, EVENT_WRITE); + +	v4l2_stream_off(sink); +	video_source_stream_off(stream->src); + +	v4l2_free_buffers(sink); +	video_source_free_buffers(stream->src); + +	return 0; +} + +void uvc_stream_enable(struct uvc_stream *stream, int enable) +{ +	if (enable) +		uvc_stream_start(stream); +	else +		uvc_stream_stop(stream); +} + +int uvc_stream_set_format(struct uvc_stream *stream, +			  const struct v4l2_pix_format *format) +{ +	struct v4l2_pix_format fmt = *format; +	int ret; + +	printf("Setting format to 0x%08x %ux%u\n", +		format->pixelformat, format->width, format->height); + +	ret = uvc_set_format(stream->uvc, &fmt); +	if (ret < 0) +		return ret; + +	return video_source_set_format(stream->src, &fmt); +} + +/* --------------------------------------------------------------------------- + * Stream handling + */ + +struct uvc_stream *uvc_stream_new(const char *uvc_device) +{ +	struct uvc_stream *stream; + +	stream = malloc(sizeof(*stream)); +	if (stream == NULL) +		return NULL; + +	memset(stream, 0, sizeof(*stream)); + +	stream->uvc = uvc_open(uvc_device, stream); +	if (stream->uvc == NULL) +		goto error; + +	return stream; + +error: +	free(stream); +	return NULL; +} + +void uvc_stream_delete(struct uvc_stream *stream) +{ +	if (stream == NULL) +		return; + +	uvc_close(stream->uvc); + +	free(stream); +} + +void uvc_stream_init_uvc(struct uvc_stream *stream, +			 struct uvc_function_config *fc) +{ +	uvc_set_config(stream->uvc, fc); +	uvc_events_init(stream->uvc, stream->events); +} + +void uvc_stream_set_event_handler(struct uvc_stream *stream, +				  struct events *events) +{ +	stream->events = events; +} + +void uvc_stream_set_video_source(struct uvc_stream *stream, +				 struct video_source *src) +{ +	stream->src = src; + +	video_source_set_buffer_handler(src, uvc_stream_source_process, stream); +} diff --git a/lib/tools.h b/lib/tools.h new file mode 100644 index 0000000..ff2f908 --- /dev/null +++ b/lib/tools.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Miscellaneous Tools + * + * Copyright (C) 2018 Laurent Pinchart + * + * This file comes from the omap3-isp-live project + * (git://git.ideasonboard.org/omap3-isp-live.git) + * + * Copyright (C) 2010-2011 Ideas on board SPRL + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#ifndef __TOOLS_H__ +#define __TOOLS_H__ + +#define ARRAY_SIZE(array)	(sizeof(array) / sizeof((array)[0])) + +#define min(a, b) ({				\ +	typeof(a) __a = (a);			\ +	typeof(b) __b = (b);			\ +	__a < __b ? __a : __b;			\ +}) + +#define min_t(type, a, b) ({			\ +	type __a = (a);				\ +	type __b = (b);				\ +	__a < __b ? __a : __b;			\ +}) + +#define max(a, b) ({				\ +	typeof(a) __a = (a);			\ +	typeof(b) __b = (b);			\ +	__a > __b ? __a : __b;			\ +}) + +#define max_t(type, a, b) ({			\ +	type __a = (a);				\ +	type __b = (b);				\ +	__a > __b ? __a : __b;			\ +}) + +#define clamp(val, min, max) ({			\ +	typeof(val) __val = (val);		\ +	typeof(min) __min = (min);		\ +	typeof(max) __max = (max);		\ +	__val = __val < __min ? __min : __val;	\ +	__val > __max ? __max : __val;		\ +}) + +#define clamp_t(type, val, min, max) ({		\ +	type __val = (val);			\ +	type __min = (min);			\ +	type __max = (max);			\ +	__val = __val < __min ? __min : __val;	\ +	__val > __max ? __max : __val;		\ +}) + +#define div_round_up(num, denom)	(((num) + (denom) - 1) / (denom)) + +#define container_of(ptr, type, member) \ +	(type *)((char *)(ptr) - offsetof(type, member)) + +#endif /* __TOOLS_H__ */ diff --git a/lib/uvc.c b/lib/uvc.c new file mode 100644 index 0000000..81d1760 --- /dev/null +++ b/lib/uvc.c @@ -0,0 +1,395 @@ +/* + * UVC protocol handling + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#include <errno.h> +#include <linux/usb/ch9.h> +#include <linux/usb/g_uvc.h> +#include <linux/usb/video.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> + +#include "configfs.h" +#include "events.h" +#include "stream.h" +#include "tools.h" +#include "uvc.h" +#include "v4l2.h" + +struct uvc_device +{ +	struct v4l2_device *vdev; + +	struct uvc_stream *stream; +	struct uvc_function_config *fc; + +	struct uvc_streaming_control probe; +	struct uvc_streaming_control commit; + +	int control; + +	unsigned int fcc; +	unsigned int width; +	unsigned int height; +	unsigned int maxsize; +}; + +struct uvc_device *uvc_open(const char *devname, struct uvc_stream *stream) +{ +	struct uvc_device *dev; + +	dev = malloc(sizeof *dev); +	if (dev == NULL) +		return NULL; + +	memset(dev, 0, sizeof *dev); +	dev->stream = stream; + +	dev->vdev = v4l2_open(devname); +	if (dev->vdev == NULL) { +		free(dev); +		return NULL; +	} + +	return dev; +} + +void uvc_close(struct uvc_device *dev) +{ +	v4l2_close(dev->vdev); +	dev->vdev = NULL; + +	free(dev); +} + +/* --------------------------------------------------------------------------- + * Request processing + */ + +static void +uvc_fill_streaming_control(struct uvc_device *dev, +			   struct uvc_streaming_control *ctrl, +			   int iformat, int iframe, unsigned int ival) +{ +	const struct uvc_function_config_format *format; +	const struct uvc_function_config_frame *frame; +	unsigned int i; + +	/* +	 * Restrict the iformat, iframe and ival to valid values. Negative +	 * values for iformat or iframe will result in the maximum valid value +	 * being selected. +	 */ +        iformat = clamp((unsigned int)iformat, 1U, +                        dev->fc->streaming.num_formats); +	format = &dev->fc->streaming.formats[iformat-1]; + +	iframe = clamp((unsigned int)iframe, 1U, format->num_frames); +	frame = &format->frames[iframe-1]; + +	for (i = 0; i < frame->num_intervals; ++i) { +		if (ival <= frame->intervals[i]) { +			ival = frame->intervals[i]; +			break; +		} +	} + +	if (i == frame->num_intervals) +		ival = frame->intervals[frame->num_intervals-1]; + +	memset(ctrl, 0, sizeof *ctrl); + +	ctrl->bmHint = 1; +	ctrl->bFormatIndex = iformat; +	ctrl->bFrameIndex = iframe ; +	ctrl->dwFrameInterval = ival; + +	switch (format->fcc) { +	case V4L2_PIX_FMT_YUYV: +		ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2; +		break; +	case V4L2_PIX_FMT_MJPEG: +		ctrl->dwMaxVideoFrameSize = dev->maxsize; +		break; +	} + +	ctrl->dwMaxPayloadTransferSize = dev->fc->streaming.ep.wMaxPacketSize; +	ctrl->bmFramingInfo = 3; +	ctrl->bPreferedVersion = 1; +	ctrl->bMaxVersion = 1; +} + +static void +uvc_events_process_standard(struct uvc_device *dev, +			    const struct usb_ctrlrequest *ctrl, +			    struct uvc_request_data *resp) +{ +	printf("standard request\n"); +	(void)dev; +	(void)ctrl; +	(void)resp; +} + +static void +uvc_events_process_control(struct uvc_device *dev, uint8_t req, uint8_t cs, +			   struct uvc_request_data *resp) +{ +	printf("control request (req %02x cs %02x)\n", req, cs); +	(void)dev; +	(void)resp; +} + +static void +uvc_events_process_streaming(struct uvc_device *dev, uint8_t req, uint8_t cs, +			     struct uvc_request_data *resp) +{ +	struct uvc_streaming_control *ctrl; + +	printf("streaming request (req %02x cs %02x)\n", req, cs); + +	if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL) +		return; + +	ctrl = (struct uvc_streaming_control *)&resp->data; +	resp->length = sizeof *ctrl; + +	switch (req) { +	case UVC_SET_CUR: +		dev->control = cs; +		resp->length = 34; +		break; + +	case UVC_GET_CUR: +		if (cs == UVC_VS_PROBE_CONTROL) +			memcpy(ctrl, &dev->probe, sizeof *ctrl); +		else +			memcpy(ctrl, &dev->commit, sizeof *ctrl); +		break; + +	case UVC_GET_MIN: +	case UVC_GET_MAX: +	case UVC_GET_DEF: +		uvc_fill_streaming_control(dev, ctrl, req == UVC_GET_MAX ? -1 : 1, +					   req == UVC_GET_MAX ? -1 : 1, 0); +		break; + +	case UVC_GET_RES: +		memset(ctrl, 0, sizeof *ctrl); +		break; + +	case UVC_GET_LEN: +		resp->data[0] = 0x00; +		resp->data[1] = 0x22; +		resp->length = 2; +		break; + +	case UVC_GET_INFO: +		resp->data[0] = 0x03; +		resp->length = 1; +		break; +	} +} + +static void +uvc_events_process_class(struct uvc_device *dev, +			 const struct usb_ctrlrequest *ctrl, +			 struct uvc_request_data *resp) +{ +	unsigned int interface = ctrl->wIndex & 0xff; + +	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) +		return; + +	if (interface == dev->fc->control.intf.bInterfaceNumber) +		uvc_events_process_control(dev, ctrl->bRequest, ctrl->wValue >> 8, resp); +	else if (interface == dev->fc->streaming.intf.bInterfaceNumber) +		uvc_events_process_streaming(dev, ctrl->bRequest, ctrl->wValue >> 8, resp); +} + +static void +uvc_events_process_setup(struct uvc_device *dev, +			 const struct usb_ctrlrequest *ctrl, +			 struct uvc_request_data *resp) +{ +	dev->control = 0; + +	printf("bRequestType %02x bRequest %02x wValue %04x wIndex %04x " +		"wLength %04x\n", ctrl->bRequestType, ctrl->bRequest, +		ctrl->wValue, ctrl->wIndex, ctrl->wLength); + +	switch (ctrl->bRequestType & USB_TYPE_MASK) { +	case USB_TYPE_STANDARD: +		uvc_events_process_standard(dev, ctrl, resp); +		break; + +	case USB_TYPE_CLASS: +		uvc_events_process_class(dev, ctrl, resp); +		break; + +	default: +		break; +	} +} + +static void +uvc_events_process_data(struct uvc_device *dev, +			const struct uvc_request_data *data) +{ +	const struct uvc_streaming_control *ctrl = +		(const struct uvc_streaming_control *)&data->data; +	struct uvc_streaming_control *target; + +	switch (dev->control) { +	case UVC_VS_PROBE_CONTROL: +		printf("setting probe control, length = %d\n", data->length); +		target = &dev->probe; +		break; + +	case UVC_VS_COMMIT_CONTROL: +		printf("setting commit control, length = %d\n", data->length); +		target = &dev->commit; +		break; + +	default: +		printf("setting unknown control, length = %d\n", data->length); +		return; +	} + +	uvc_fill_streaming_control(dev, target, ctrl->bFormatIndex, +				   ctrl->bFrameIndex, ctrl->dwFrameInterval); + +	if (dev->control == UVC_VS_COMMIT_CONTROL) { +		const struct uvc_function_config_format *format; +		const struct uvc_function_config_frame *frame; +		struct v4l2_pix_format pixfmt; + +		format = &dev->fc->streaming.formats[target->bFormatIndex-1]; +		frame = &format->frames[target->bFrameIndex-1]; + +		dev->fcc = format->fcc; +		dev->width = frame->width; +		dev->height = frame->height; + +		memset(&pixfmt, 0, sizeof pixfmt); +		pixfmt.width = frame->width; +		pixfmt.height = frame->height; +		pixfmt.pixelformat = format->fcc; +		pixfmt.field = V4L2_FIELD_NONE; +		if (format->fcc == V4L2_PIX_FMT_MJPEG) +			pixfmt.sizeimage = dev->maxsize * 1.5; + +		uvc_stream_set_format(dev->stream, &pixfmt); +	} +} + +static void uvc_events_process(void *d) +{ +	struct uvc_device *dev = d; +	struct v4l2_event v4l2_event; +	const struct uvc_event *uvc_event = (void *)&v4l2_event.u.data; +	struct uvc_request_data resp; +	int ret; + +	ret = ioctl(dev->vdev->fd, VIDIOC_DQEVENT, &v4l2_event); +	if (ret < 0) { +		printf("VIDIOC_DQEVENT failed: %s (%d)\n", strerror(errno), +			errno); +		return; +	} + +	memset(&resp, 0, sizeof resp); +	resp.length = -EL2HLT; + +	switch (v4l2_event.type) { +	case UVC_EVENT_CONNECT: +	case UVC_EVENT_DISCONNECT: +		return; + +	case UVC_EVENT_SETUP: +		uvc_events_process_setup(dev, &uvc_event->req, &resp); +		break; + +	case UVC_EVENT_DATA: +		uvc_events_process_data(dev, &uvc_event->data); +		return; + +	case UVC_EVENT_STREAMON: +		uvc_stream_enable(dev->stream, 1); +		return; + +	case UVC_EVENT_STREAMOFF: +		uvc_stream_enable(dev->stream, 0); +		return; +	} + +	ioctl(dev->vdev->fd, UVCIOC_SEND_RESPONSE, &resp); +	if (ret < 0) { +		printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno), +			errno); +		return; +	} +} + +/* --------------------------------------------------------------------------- + * Initialization and setup + */ + +void uvc_events_init(struct uvc_device *dev, struct events *events) +{ +	struct v4l2_event_subscription sub; + +	/* Default to the minimum values. */ +	uvc_fill_streaming_control(dev, &dev->probe, 1, 1, 0); +	uvc_fill_streaming_control(dev, &dev->commit, 1, 1, 0); + +	memset(&sub, 0, sizeof sub); +	sub.type = UVC_EVENT_SETUP; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +	sub.type = UVC_EVENT_DATA; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +	sub.type = UVC_EVENT_STREAMON; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); +	sub.type = UVC_EVENT_STREAMOFF; +	ioctl(dev->vdev->fd, VIDIOC_SUBSCRIBE_EVENT, &sub); + +	events_watch_fd(events, dev->vdev->fd, EVENT_EXCEPTION, +			uvc_events_process, dev); +} + +void uvc_set_config(struct uvc_device *dev, struct uvc_function_config *fc) +{ +	/* FIXME: The maximum size should be specified per format and frame. */ +	dev->maxsize = 0; +	dev->fc = fc; +} + +int uvc_set_format(struct uvc_device *dev, struct v4l2_pix_format *format) +{ +	return v4l2_set_format(dev->vdev, format); +} + +struct v4l2_device *uvc_v4l2_device(struct uvc_device *dev) +{ +	/* +	 * TODO: The V4L2 device shouldn't be exposed. We should replace this +	 * with an abstract video sink class when one will be avaiilable. +	 */ +	return dev->vdev; +} diff --git a/lib/uvc.h b/lib/uvc.h new file mode 100644 index 0000000..f73dbff --- /dev/null +++ b/lib/uvc.h @@ -0,0 +1,36 @@ +/* + * UVC protocol handling + * + * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + */ + +#ifndef __UVC_H__ +#define __UVC_H__ + +struct events; +struct v4l2_device; +struct uvc_device; +struct uvc_function_config; +struct uvc_stream; + +struct uvc_device *uvc_open(const char *devname, struct uvc_stream *stream); +void uvc_close(struct uvc_device *dev); +void uvc_events_init(struct uvc_device *dev, struct events *events); +void uvc_set_config(struct uvc_device *dev, struct uvc_function_config *fc); +int uvc_set_format(struct uvc_device *dev, struct v4l2_pix_format *format); +struct v4l2_device *uvc_v4l2_device(struct uvc_device *dev); + +#endif /* __UVC_H__ */ diff --git a/lib/v4l2-source.c b/lib/v4l2-source.c new file mode 100644 index 0000000..7eced6a --- /dev/null +++ b/lib/v4l2-source.c @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * V4L2 video source + * + * Copyright (C) 2018 Laurent Pinchart + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "events.h" +#include "tools.h" +#include "v4l2.h" +#include "v4l2-source.h" +#include "video-buffers.h" + +struct v4l2_source { +	struct video_source src; + +	struct v4l2_device *vdev; +}; + +#define to_v4l2_source(s) container_of(s, struct v4l2_source, src) + +static void v4l2_source_video_process(void *d) +{ +	struct v4l2_source *src = d; +	struct video_buffer buf; +	int ret; + +	ret = v4l2_dequeue_buffer(src->vdev, &buf); +	if (ret < 0) +		return; + +	src->src.handler(src->src.handler_data, &src->src, &buf); +} + +static void v4l2_source_destroy(struct video_source *s) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	v4l2_close(src->vdev); +	free(src); +} + +static int v4l2_source_set_format(struct video_source *s, +				  struct v4l2_pix_format *fmt) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	return v4l2_set_format(src->vdev, fmt); +} + +static int v4l2_source_alloc_buffers(struct video_source *s, unsigned int nbufs) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	return v4l2_alloc_buffers(src->vdev, V4L2_MEMORY_MMAP, nbufs); +} + +static int v4l2_source_export_buffers(struct video_source *s, +				      struct video_buffer_set **bufs) +{ +	struct v4l2_source *src = to_v4l2_source(s); +	struct video_buffer_set *buffers; +	unsigned int i; +	int ret; + +	ret = v4l2_export_buffers(src->vdev); +	if (ret < 0) +		return ret; + +	buffers = video_buffer_set_new(src->vdev->buffers.nbufs); +	if (!buffers) +		return -ENOMEM; + +	for (i = 0; i < src->vdev->buffers.nbufs; ++i) { +		struct video_buffer *buffer = &src->vdev->buffers.buffers[i]; + +		buffers->buffers[i].size = buffer->size; +		buffers->buffers[i].dmabuf = buffer->dmabuf; +	} + +	*bufs = buffers; +	return 0; +} + +static int v4l2_source_free_buffers(struct video_source *s) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	return v4l2_free_buffers(src->vdev); +} + +static int v4l2_source_stream_on(struct video_source *s) +{ +	struct v4l2_source *src = to_v4l2_source(s); +	unsigned int i; +	int ret; + +	/* Queue all buffers. */ +	for (i = 0; i < src->vdev->buffers.nbufs; ++i) { +		struct video_buffer buf = { +			.index = i, +			.size = src->vdev->buffers.buffers[i].size, +			.dmabuf = src->vdev->buffers.buffers[i].dmabuf, +		}; + +		ret = v4l2_queue_buffer(src->vdev, &buf); +		if (ret < 0) +			return ret; +	} + +	ret = v4l2_stream_on(src->vdev); +	if (ret < 0) +		return ret; + +	events_watch_fd(src->src.events, src->vdev->fd, EVENT_READ, +			v4l2_source_video_process, src); + +	return 0; +} + +static int v4l2_source_stream_off(struct video_source *s) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	events_unwatch_fd(src->src.events, src->vdev->fd, EVENT_READ); + +	return v4l2_stream_off(src->vdev); +} + +static int v4l2_source_queue_buffer(struct video_source *s, +				    struct video_buffer *buf) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	return v4l2_queue_buffer(src->vdev, buf); +} + +static const struct video_source_ops v4l2_source_ops = { +	.destroy = v4l2_source_destroy, +	.set_format = v4l2_source_set_format, +	.alloc_buffers = v4l2_source_alloc_buffers, +	.export_buffers = v4l2_source_export_buffers, +	.free_buffers = v4l2_source_free_buffers, +	.stream_on = v4l2_source_stream_on, +	.stream_off = v4l2_source_stream_off, +	.queue_buffer = v4l2_source_queue_buffer, +}; + +struct video_source *v4l2_video_source_create(const char *devname) +{ +	struct v4l2_source *src; + +	src = malloc(sizeof *src); +	if (!src) +		return NULL; + +	memset(src, 0, sizeof *src); +	src->src.ops = &v4l2_source_ops; + +	src->vdev = v4l2_open(devname); +	if (!src->vdev) { +		free(src); +		return NULL; +	} + +	return &src->src; +} + +void v4l2_video_source_init(struct video_source *s, struct events *events) +{ +	struct v4l2_source *src = to_v4l2_source(s); + +	src->src.events = events; +} diff --git a/lib/v4l2.c b/lib/v4l2.c new file mode 100644 index 0000000..9030270 --- /dev/null +++ b/lib/v4l2.c @@ -0,0 +1,833 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * V4L2 Devices + * + * Copyright (C) 2018 Laurent Pinchart + * + * This file originally comes from the omap3-isp-live project + * (git://git.ideasonboard.org/omap3-isp-live.git) + * + * Copyright (C) 2010-2011 Ideas on board SPRL + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <linux/videodev2.h> + +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/select.h> +#include <sys/time.h> + +#include "list.h" +#include "tools.h" +#include "v4l2.h" +#include "video-buffers.h" + +#ifndef V4L2_BUF_FLAG_ERROR +#define V4L2_BUF_FLAG_ERROR	0x0040 +#endif + +struct v4l2_ival_desc { +	struct v4l2_fract min; +	struct v4l2_fract max; +	struct v4l2_fract step; + +	struct list_entry list; +}; + +struct v4l2_frame_desc { +	unsigned int min_width; +	unsigned int min_height; +	unsigned int max_width; +	unsigned int max_height; +	unsigned int step_width; +	unsigned int step_height; + +	struct list_entry list; +	struct list_entry ivals; +}; + +struct v4l2_format_desc { +	unsigned int pixelformat; + +	struct list_entry list; +	struct list_entry frames; +}; + +/* ----------------------------------------------------------------------------- + * Formats enumeration + */ + +static int +v4l2_enum_frame_intervals(struct v4l2_device *dev, struct v4l2_format_desc *format, +	struct v4l2_frame_desc *frame) +{ +	struct v4l2_ival_desc *ival; +	unsigned int i; +	int ret; + +	for (i = 0; ; ++i) { +		struct v4l2_frmivalenum ivalenum; + +		memset(&ivalenum, 0, sizeof ivalenum); +		ivalenum.index = i; +		ivalenum.pixel_format = format->pixelformat; +		ivalenum.width = frame->min_width; +		ivalenum.height = frame->min_height; +		ret = ioctl(dev->fd, VIDIOC_ENUM_FRAMEINTERVALS, &ivalenum); +		if (ret < 0) +			break; + +		if (i != ivalenum.index) +			printf("Warning: driver returned wrong ival index " +				"%u.\n", ivalenum.index); +		if (format->pixelformat != ivalenum.pixel_format) +			printf("Warning: driver returned wrong ival pixel " +				"format %08x.\n", ivalenum.pixel_format); +		if (frame->min_width != ivalenum.width) +			printf("Warning: driver returned wrong ival width " +				"%u.\n", ivalenum.width); +		if (frame->min_height != ivalenum.height) +			printf("Warning: driver returned wrong ival height " +				"%u.\n", ivalenum.height); + +		ival = malloc(sizeof *ival); +		if (ival == NULL) +			return -ENOMEM; + +		memset(ival, 0, sizeof *ival); + +		switch (ivalenum.type) { +		case V4L2_FRMIVAL_TYPE_DISCRETE: +			ival->min = ivalenum.discrete; +			ival->max = ivalenum.discrete; +			ival->step.numerator = 1; +			ival->step.denominator = 1; +			break; + +		case V4L2_FRMIVAL_TYPE_STEPWISE: +			ival->min = ivalenum.stepwise.min; +			ival->max = ivalenum.stepwise.max; +			ival->step = ivalenum.stepwise.step; +			break; + +		default: +			printf("Error: driver returned invalid frame ival " +				"type %u\n", ivalenum.type); +			return -EINVAL; +		} + +		list_append(&ival->list, &frame->ivals); +	} + +	return 0; +} + +static int +v4l2_enum_frame_sizes(struct v4l2_device *dev, struct v4l2_format_desc *format) +{ +	struct v4l2_frame_desc *frame; +	unsigned int i; +	int ret; + +	for (i = 0; ; ++i) { +		struct v4l2_frmsizeenum frmenum; + +		memset(&frmenum, 0, sizeof frmenum); +		frmenum.index = i; +		frmenum.pixel_format = format->pixelformat; + +		ret = ioctl(dev->fd, VIDIOC_ENUM_FRAMESIZES, &frmenum); +		if (ret < 0) +			break; + +		if (i != frmenum.index) +			printf("Warning: driver returned wrong frame index " +				"%u.\n", frmenum.index); +		if (format->pixelformat != frmenum.pixel_format) +			printf("Warning: driver returned wrong frame pixel " +				"format %08x.\n", frmenum.pixel_format); + +		frame = malloc(sizeof *frame); +		if (frame == NULL) +			return -ENOMEM; + +		memset(frame, 0, sizeof *frame); + +		list_init(&frame->ivals); +		frame->step_width = 1; +		frame->step_height = 1; + +		switch (frmenum.type) { +		case V4L2_FRMSIZE_TYPE_DISCRETE: +			frame->min_width = frmenum.discrete.width; +			frame->min_height = frmenum.discrete.height; +			frame->max_width = frmenum.discrete.width; +			frame->max_height = frmenum.discrete.height; +			break; + +		case V4L2_FRMSIZE_TYPE_STEPWISE: +			frame->step_width = frmenum.stepwise.step_width; +			frame->step_height = frmenum.stepwise.step_height; +			/* fallthrough */ +		case V4L2_FRMSIZE_TYPE_CONTINUOUS: +			frame->min_width = frmenum.stepwise.min_width; +			frame->min_height = frmenum.stepwise.min_height; +			frame->max_width = frmenum.stepwise.max_width; +			frame->max_height = frmenum.stepwise.max_height; +			break; + +		default: +			printf("Error: driver returned invalid frame size " +				"type %u\n", frmenum.type); +			return -EINVAL; +		} + +		list_append(&frame->list, &format->frames); + +		ret = v4l2_enum_frame_intervals(dev, format, frame); +		if (ret < 0) +			return ret; +	} + +	return 0; +} +static int v4l2_enum_formats(struct v4l2_device *dev) +{ +	struct v4l2_format_desc *format; +	unsigned int i; +	int ret; + +	for (i = 0; ; ++i) { +		struct v4l2_fmtdesc fmtenum; + +		memset(&fmtenum, 0, sizeof fmtenum); +		fmtenum.index = i; +		fmtenum.type = dev->type; + +		ret = ioctl(dev->fd, VIDIOC_ENUM_FMT, &fmtenum); +		if (ret < 0) +			break; + +		if (i != fmtenum.index) +			printf("Warning: driver returned wrong format index " +				"%u.\n", fmtenum.index); +		if (dev->type != fmtenum.type) +			printf("Warning: driver returned wrong format type " +				"%u.\n", fmtenum.type); + +		format = malloc(sizeof *format); +		if (format == NULL) +			return -ENOMEM; + +		memset(format, 0, sizeof *format); + +		list_init(&format->frames); +		format->pixelformat = fmtenum.pixelformat; + +		list_append(&format->list, &dev->formats); + +		ret = v4l2_enum_frame_sizes(dev, format); +		if (ret < 0) +			return ret; +	} + +	return 0; +} + +/* ----------------------------------------------------------------------------- + * Open/close + */ + +struct v4l2_device *v4l2_open(const char *devname) +{ +	struct v4l2_device *dev; +	struct v4l2_capability cap; +	__u32 capabilities; +	int ret; + +	dev = malloc(sizeof *dev); +	if (dev == NULL) +		return NULL; + +	memset(dev, 0, sizeof *dev); +	dev->fd = -1; +	dev->name = strdup(devname); +	list_init(&dev->formats); + +	dev->fd = open(devname, O_RDWR | O_NONBLOCK); +	if (dev->fd < 0) { +		printf("Error opening device %s: %d.\n", devname, errno); +		v4l2_close(dev); +		return NULL; +	} + +	memset(&cap, 0, sizeof cap); +	ret = ioctl(dev->fd, VIDIOC_QUERYCAP, &cap); +	if (ret < 0) { +		printf("Error opening device %s: unable to query " +			"device.\n", devname); +		v4l2_close(dev); +		return NULL; +	} + +	/*  +	 * If the device_caps field is set use it, otherwise use the older +	 * capabilities field. +	 */ +	capabilities = cap.device_caps ? : cap.capabilities; + +	if (capabilities & V4L2_CAP_VIDEO_CAPTURE) +		dev->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; +	else if (capabilities & V4L2_CAP_VIDEO_OUTPUT) +		dev->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; +	else { +		printf("Error opening device %s: neither video capture " +			"nor video output supported.\n", devname); +		v4l2_close(dev); +		return NULL; +	} + +	ret = v4l2_enum_formats(dev); +	if (ret < 0) { +		printf("Error opening device %s: unable to enumerate " +			"formats.\n", devname); +		v4l2_close(dev); +		return NULL; +	} + +	printf("Device %s opened: %s (%s).\n", devname, cap.card, cap.bus_info); + +	return dev; +} + +void v4l2_close(struct v4l2_device *dev) +{ +	struct v4l2_format_desc *format, *next_fmt; +	struct v4l2_frame_desc *frame, *next_frm; +	struct v4l2_ival_desc *ival, *next_ival; + +	if (dev == NULL) +		return; + +	list_for_each_entry_safe(format, next_fmt, &dev->formats, list) { +		list_for_each_entry_safe(frame, next_frm, &format->frames, list) { +			list_for_each_entry_safe(ival, next_ival, &frame->ivals, list) { +				free(ival); +			} +			free(frame); +		} +		free(format); +	} + +	free(dev->name); +	close(dev->fd); +	free(dev); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +int v4l2_get_control(struct v4l2_device *dev, unsigned int id, int32_t *value) +{ +	struct v4l2_control ctrl; +	int ret; + +	ctrl.id = id; + +	ret = ioctl(dev->fd, VIDIOC_G_CTRL, &ctrl); +	if (ret < 0) { +		printf("%s: unable to get control (%d).\n", dev->name, errno); +		return -errno; +	} + +	*value = ctrl.value; +	return 0; +} + +int v4l2_set_control(struct v4l2_device *dev, unsigned int id, int32_t *value) +{ +	struct v4l2_control ctrl; +	int ret; + +	ctrl.id = id; +	ctrl.value = *value; + +	ret = ioctl(dev->fd, VIDIOC_S_CTRL, &ctrl); +	if (ret < 0) { +		printf("%s: unable to set control (%d).\n", dev->name, errno); +		return -errno; +	} + +	*value = ctrl.value; +	return 0; +} + +int v4l2_get_controls(struct v4l2_device *dev, unsigned int count, +		      struct v4l2_ext_control *ctrls) +{ +	struct v4l2_ext_controls controls; +	int ret; + +	memset(&controls, 0, sizeof controls); +	controls.count = count; +	controls.controls = ctrls; + +	ret = ioctl(dev->fd, VIDIOC_G_EXT_CTRLS, &controls); +	if (ret < 0) +		printf("%s: unable to get multiple controls (%d).\n", dev->name, +		       errno); + +	return ret; +} + +int v4l2_set_controls(struct v4l2_device *dev, unsigned int count, +		      struct v4l2_ext_control *ctrls) +{ +	struct v4l2_ext_controls controls; +	int ret; + +	memset(&controls, 0, sizeof controls); +	controls.count = count; +	controls.controls = ctrls; + +	ret = ioctl(dev->fd, VIDIOC_S_EXT_CTRLS, &controls); +	if (ret < 0) +		printf("%s: unable to set multiple controls (%d).\n", dev->name, +		       errno); + +	return ret; +} + +/* ----------------------------------------------------------------------------- + * Formats and frame rates + */ + +int v4l2_get_crop(struct v4l2_device *dev, struct v4l2_rect *rect) +{ +	struct v4l2_crop crop; +	int ret; + +	memset(&crop, 0, sizeof crop); +	crop.type = dev->type; + +	ret = ioctl(dev->fd, VIDIOC_G_CROP, &crop); +	if (ret < 0) { +		printf("%s: unable to get crop rectangle (%d).\n", dev->name, +		       errno); +		return -errno; +	} + +	dev->crop = crop.c; +	*rect = crop.c; + +	return 0; +} + +int v4l2_set_crop(struct v4l2_device *dev, struct v4l2_rect *rect) +{ +	struct v4l2_crop crop; +	int ret; + +	memset(&crop, 0, sizeof crop); +	crop.type = dev->type; +	crop.c = *rect; + +	ret = ioctl(dev->fd, VIDIOC_S_CROP, &crop); +	if (ret < 0) { +		printf("%s: unable to set crop rectangle (%d).\n", dev->name, +		       errno); +		return -errno; +	} + +	dev->crop = crop.c; +	*rect = crop.c; + +	return 0; +} + +int v4l2_get_format(struct v4l2_device *dev, struct v4l2_pix_format *format) +{ +	struct v4l2_format fmt; +	int ret; + +	memset(&fmt, 0, sizeof fmt); +	fmt.type = dev->type; + +	ret = ioctl(dev->fd, VIDIOC_G_FMT, &fmt); +	if (ret < 0) { +		printf("%s: unable to get format (%d).\n", dev->name, errno); +		return -errno; +	} + +	dev->format = fmt.fmt.pix; +	*format = fmt.fmt.pix; + +	return 0; +} + +int v4l2_set_format(struct v4l2_device *dev, struct v4l2_pix_format *format) +{ +	struct v4l2_format fmt; +	int ret; + +	memset(&fmt, 0, sizeof fmt); +	fmt.type = dev->type; +	fmt.fmt.pix.width = format->width; +	fmt.fmt.pix.height = format->height; +	fmt.fmt.pix.pixelformat = format->pixelformat; +	fmt.fmt.pix.field = V4L2_FIELD_ANY; + +	ret = ioctl(dev->fd, VIDIOC_S_FMT, &fmt); +	if (ret < 0) { +		printf("%s: unable to set format (%d).\n", dev->name, errno); +		return -errno; +	} + +	dev->format = fmt.fmt.pix; +	*format = fmt.fmt.pix; + +	return 0; +} + +/* ----------------------------------------------------------------------------- + * Buffers management + */ + +int v4l2_alloc_buffers(struct v4l2_device *dev, enum v4l2_memory memtype, +		       unsigned int nbufs) +{ +	struct v4l2_requestbuffers rb; +	unsigned int i; +	int ret; + +	if (dev->buffers.nbufs != 0) +		return -EBUSY; + +	if (memtype != V4L2_MEMORY_MMAP && memtype != V4L2_MEMORY_DMABUF) +		return -EINVAL; + +	/* Request the buffers from the driver. */ +	memset(&rb, 0, sizeof rb); +	rb.count = nbufs; +	rb.type = dev->type; +	rb.memory = memtype; + +	ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb); +	if (ret < 0) { +		printf("%s: unable to request buffers (%d).\n", dev->name, +		       errno); +		ret = -errno; +		goto done; +	} + +	if (rb.count > nbufs) { +		printf("%s: driver needs more buffers (%u) than available (%u).\n", +		       dev->name, rb.count, nbufs); +		ret = -E2BIG; +		goto done; +	} + +	printf("%s: %u buffers requested.\n", dev->name, rb.count); + +	/* Allocate the buffer objects. */ +	dev->memtype = memtype; +	dev->buffers.nbufs = rb.count; + +	dev->buffers.buffers = calloc(nbufs, sizeof *dev->buffers.buffers); +	if (dev->buffers.buffers == NULL) { +		ret = -ENOMEM; +		goto done; +	} + +	for (i = 0; i < dev->buffers.nbufs; ++i) { +		dev->buffers.buffers[i].index = i; +		dev->buffers.buffers[i].dmabuf = -1; +	} + +	ret = 0; + +done: +	if (ret < 0) +		v4l2_free_buffers(dev); + +	return ret; +} + +int v4l2_free_buffers(struct v4l2_device *dev) +{ +	struct v4l2_requestbuffers rb; +	unsigned int i; +	int ret; + +	if (dev->buffers.nbufs == 0) +		return 0; + +	for (i = 0; i < dev->buffers.nbufs; ++i) { +		struct video_buffer *buffer = &dev->buffers.buffers[i]; + +		if (buffer->mem) { +			ret = munmap(buffer->mem, buffer->size); +			if (ret < 0) { +				printf("%s: unable to unmap buffer %u (%d)\n", +				       dev->name, i, errno); +				return -errno; +			} + +			buffer->mem = NULL; +		} + +		if (buffer->dmabuf != -1) { +			close(buffer->dmabuf); +			buffer->dmabuf = -1; +		} + +		buffer->size = 0; +	} + +	memset(&rb, 0, sizeof rb); +	rb.count = 0; +	rb.type = dev->type; +	rb.memory = dev->memtype; + +	ret = ioctl(dev->fd, VIDIOC_REQBUFS, &rb); +	if (ret < 0) { +		printf("%s: unable to release buffers (%d)\n", dev->name, +		       errno); +		return -errno; +	} + +	free(dev->buffers.buffers); +	dev->buffers.buffers = NULL; +	dev->buffers.nbufs = 0; + +	return 0; +} + +int v4l2_export_buffers(struct v4l2_device *dev) +{ +	unsigned int i; +	int ret; + +	if (dev->buffers.nbufs == 0) +		return -EINVAL; + +	if (dev->memtype != V4L2_MEMORY_MMAP) +		return -EINVAL; + +	for (i = 0; i < dev->buffers.nbufs; ++i) { +		struct v4l2_exportbuffer expbuf = { +			.type = dev->type, +			.index = i, +		}; +		struct v4l2_buffer buf = { +			.index = i, +			.type = dev->type, +			.memory = dev->memtype, +		}; + +		ret = ioctl(dev->fd, VIDIOC_QUERYBUF, &buf); +		if (ret < 0) { +			printf("%s: unable to query buffer %u (%d).\n", +			       dev->name, i, errno); +			return -errno; +		} + +		ret = ioctl(dev->fd, VIDIOC_EXPBUF, &expbuf); +		if (ret < 0) { +			printf("Failed to export buffer %u.\n", i); +			return -errno; +		} + +		dev->buffers.buffers[i].size = buf.length; +		dev->buffers.buffers[i].dmabuf = expbuf.fd; + +		printf("%s: buffer %u exported with fd %u.\n", +		       dev->name, i, dev->buffers.buffers[i].dmabuf); +	} + +	return 0; +} + +int v4l2_import_buffers(struct v4l2_device *dev, +			const struct video_buffer_set *buffers) +{ +	unsigned int i; +	int ret; + +	if (dev->buffers.nbufs == 0 || dev->buffers.nbufs > buffers->nbufs) +		return -EINVAL; + +	if (dev->memtype != V4L2_MEMORY_DMABUF) +		return -EINVAL; + +	for (i = 0; i < dev->buffers.nbufs; ++i) { +		const struct video_buffer *buffer = &buffers->buffers[i]; +		struct v4l2_buffer buf = { +			.index = i, +			.type = dev->type, +			.memory = dev->memtype, +		}; +		int fd; + +		ret = ioctl(dev->fd, VIDIOC_QUERYBUF, &buf); +		if (ret < 0) { +			printf("%s: unable to query buffer %u (%d).\n", +			       dev->name, i, errno); +			return -errno; +		} + +		if (buffer->size < buf.length) { +			printf("%s: buffer %u too small (%u bytes required, %u bytes available).\n", +			       dev->name, i, buf.length, buffer->size); +			return -EINVAL; +		} + +		fd = dup(buffer->dmabuf); +		if (fd < 0) { +			printf("%s: failed to duplicate dmabuf fd %d.\n", +			       dev->name, buffer->dmabuf); +			return ret; +		} + +		printf("%s: buffer %u valid.\n", dev->name, i); + +		dev->buffers.buffers[i].dmabuf = fd; +		dev->buffers.buffers[i].size = buffer->size; +	} + +	return 0; +} + +int v4l2_mmap_buffers(struct v4l2_device *dev) +{ +	unsigned int i; +	int ret; + +	if (dev->memtype != V4L2_MEMORY_MMAP) +		return -EINVAL; + +	for (i = 0; i < dev->buffers.nbufs; ++i) { +		struct video_buffer *buffer = &dev->buffers.buffers[i]; +		struct v4l2_buffer buf = { +			.index = i, +			.type = dev->type, +			.memory = dev->memtype, +		}; +		void *mem; + +		ret = ioctl(dev->fd, VIDIOC_QUERYBUF, &buf); +		if (ret < 0) { +			printf("%s: unable to query buffer %u (%d).\n", +			       dev->name, i, errno); +			return -errno; +		} + +		mem = mmap(0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, +			   dev->fd, buf.m.offset); +		if (mem == MAP_FAILED) { +			printf("%s: unable to map buffer %u (%d)\n", +			       dev->name, i, errno); +			return -errno; +		} + +		buffer->mem = mem; +		buffer->size = buf.length; + +		printf("%s: buffer %u mapped at address %p.\n", dev->name, i, +		       mem); +	} + +	return 0; +} + +int v4l2_dequeue_buffer(struct v4l2_device *dev, struct video_buffer *buffer) +{ +	struct v4l2_buffer buf; +	int ret; + +	memset(&buf, 0, sizeof buf); +	buf.type = dev->type; +	buf.memory = dev->memtype; + +	ret = ioctl(dev->fd, VIDIOC_DQBUF, &buf); +	if (ret < 0) { +		printf("%s: unable to dequeue buffer index %u/%u (%d)\n", +		       dev->name, buf.index, dev->buffers.nbufs, errno); +		return -errno; +	} + +	buffer->index = buf.index; +	buffer->bytesused = buf.bytesused; +	buffer->timestamp = buf.timestamp; +	buffer->error = !!(buf.flags & V4L2_BUF_FLAG_ERROR); + +	return 0; +} + +int v4l2_queue_buffer(struct v4l2_device *dev, struct video_buffer *buffer) +{ +	struct v4l2_buffer buf; +	int ret; + +	if (buffer->index >= dev->buffers.nbufs) +		return -EINVAL; + +	memset(&buf, 0, sizeof buf); +	buf.index = buffer->index; +	buf.type = dev->type; +	buf.memory = dev->memtype; + +	if (dev->memtype == V4L2_MEMORY_DMABUF) +		buf.m.fd = (unsigned long)dev->buffers.buffers[buffer->index].dmabuf; + +	if (dev->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) +		buf.bytesused = buffer->bytesused; + +	ret = ioctl(dev->fd, VIDIOC_QBUF, &buf); +	if (ret < 0) { +		printf("%s: unable to queue buffer index %u/%u (%d)\n", +		       dev->name, buf.index, dev->buffers.nbufs, errno); +		return -errno; +	} + +	return 0; +} + +/* ----------------------------------------------------------------------------- + * Stream management + */ + +int v4l2_stream_on(struct v4l2_device *dev) +{ +	int type = dev->type; +	int ret; + +	ret = ioctl(dev->fd, VIDIOC_STREAMON, &type); +	if (ret < 0) +		return -errno; + +	return 0; +} + +int v4l2_stream_off(struct v4l2_device *dev) +{ +	int type = dev->type; +	int ret; + +	ret = ioctl(dev->fd, VIDIOC_STREAMOFF, &type); +	if (ret < 0) +		return -errno; + +	return 0; +} diff --git a/lib/v4l2.h b/lib/v4l2.h new file mode 100644 index 0000000..8d7b3c5 --- /dev/null +++ b/lib/v4l2.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * V4L2 Devices + * + * Copyright (C) 2018 Laurent Pinchart + * + * This file originally comes from the omap3-isp-live project + * (git://git.ideasonboard.org/omap3-isp-live.git) + * + * Copyright (C) 2010-2011 Ideas on board SPRL + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ +#ifndef __V4L2_H +#define __V4L2_H + +#include <linux/videodev2.h> +#include <stdint.h> + +#include "list.h" +#include "video-buffers.h" + +struct v4l2_device +{ +	int fd; +	char *name; + +	enum v4l2_buf_type type; +	enum v4l2_memory memtype; + +	struct list_entry formats; +	struct v4l2_pix_format format; +	struct v4l2_rect crop; + +	struct video_buffer_set buffers; +}; + +/* + * v4l2_open - Open a V4L2 device + * @devname: Name (including path) of the device node + * + * Open the V4L2 device referenced by @devname for video capture or display in + * non-blocking mode. + * + * If the device can be opened, query its capabilities and enumerates frame + * formats, sizes and intervals. + * + * Return a pointer to a newly allocated v4l2_device structure instance on + * success and NULL on failure. The returned pointer must be freed with + * v4l2_close when the device isn't needed anymore. + */ +struct v4l2_device *v4l2_open(const char *devname); + +/* + * v4l2_close - Close a V4L2 device + * @dev: Device instance + * + * Close the device instance given as argument and free allocated resources. + * Access to the device instance is forbidden after this function returns. + */ +void v4l2_close(struct v4l2_device *dev); + +/* + * v4l2_get_format - Retrieve the current pixel format + * @dev: Device instance + * @format: Pixel format structure to be filled + * + * Query the device to retrieve the current pixel format and frame size and fill + * the @format structure. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_get_format(struct v4l2_device *dev, struct v4l2_pix_format *format); + +/* + * v4l2_set_format - Set the pixel format + * @dev: Device instance + * @format: Pixel format structure to be set + * + * Set the pixel format and frame size stored in @format. The device can modify + * the requested format and size, in which case the @format structure will be + * updated to reflect the modified settings. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_set_format(struct v4l2_device *dev, struct v4l2_pix_format *format); + +/* + * v4l2_get_crop - Retrieve the current crop rectangle + * @dev: Device instance + * @rect: Crop rectangle structure to be filled + * + * Query the device to retrieve the current crop rectangle and fill the @rect + * structure. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_get_crop(struct v4l2_device *dev, struct v4l2_rect *rect); + +/* + * v4l2_set_crop - Set the crop rectangle + * @dev: Device instance + * @rect: Crop rectangle structure to be set + * + * Set the crop rectangle stored in @rect. The device can modify the requested + * rectangle, in which case the @rect structure will be updated to reflect the + * modified settings. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_set_crop(struct v4l2_device *dev, struct v4l2_rect *rect); + +/* + * v4l2_alloc_buffers - Allocate buffers for video frames + * @dev: Device instance + * @memtype: Type of buffers + * @nbufs: Number of buffers to allocate + * + * Request the driver to allocate @nbufs buffers. The driver can modify the + * number of buffers depending on its needs. The number of allocated buffers + * will be stored in the @dev->buffers.nbufs field. + * + * When @memtype is set to V4L2_MEMORY_MMAP the buffers are allocated by the + * driver. They can then be mapped to userspace by calling v4l2_mmap_buffers(). + * When @memtype is set to V4L2_MEMORY_DMABUF the driver only allocates buffer + * objects and relies on the application to provide memory storage for video + * frames. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_alloc_buffers(struct v4l2_device *dev, enum v4l2_memory memtype, +		       unsigned int nbufs); + +/* + * v4l2_free_buffers - Free buffers + * @dev: Device instance + * + * Free buffers previously allocated by v4l2_alloc_buffers(). If the buffers + * have been allocated with the V4L2_MEMORY_DMABUF memory type only the buffer + * objects are freed, and the caller is responsible for freeing the video frames + * memory if required. + * + * When successful this function sets the @dev->buffers.nbufs field to zero. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_free_buffers(struct v4l2_device *dev); + +/* + * v4l2_export_buffers - Export buffers as dmabuf objects + * @dev: Device instance + * + * Export all the buffers previously allocated by v4l2_alloc_buffers() as dmabuf + * objects. The dmabuf objects handles can be accessed through the dmabuf field + * of each entry in the @dev::buffers array. + * + * The dmabuf objects handles will be automatically closed when the buffers are + * freed with v4l2_free_buffers(). + * + * This function can only be called when buffers have been allocated with the + * memory type set to V4L2_MEMORY_MMAP. If the memory type is different, or if + * no buffers have been allocated, it will return -EINVAL. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_export_buffers(struct v4l2_device *dev); + +/* + * v4l2_import_buffers - Import buffer backing store as dmabuf objects + * @dev: Device instance + * @buffers: Buffers to be imported + * + * Import the dmabuf objects from @buffers as backing store for the device + * buffers previously allocated by v4l2_alloc_buffers(). + * + * The dmabuf file handles are duplicated and stored in the dmabuf field of the + * @dev::buffers array. The handles from the @buffers set can thus be closed + * independently without impacting usage of the imported dmabuf objects. The + * duplicated file handles will be automatically closed when the buffers are + * freed with v4l2_free_buffers(). + * + * This function can only be called when buffers have been allocated with the + * memory type set to V4L2_MEMORY_DMABUF. If the memory type is different, if no + * buffers have been allocated, or if the number of allocated buffers is larger + * than @buffers->nbufs, it will return -EINVAL. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_import_buffers(struct v4l2_device *dev, +			const struct video_buffer_set *buffers); + +/* + * v4l2_mmap_buffers - Map buffers to application memory space + * @dev: Device instance + * + * Map all the buffers previously allocated by v4l2_alloc_buffers() to the + * application memory space. The buffer memory can be accessed through the mem + * field of each entry in the @dev::buffers array. + * + * Buffers will be automatically unmapped when freed with v4l2_free_buffers(). + * + * This function can only be called when buffers have been allocated with the + * memory type set to V4L2_MEMORY_MMAP. If the memory type is different, or if + * no buffers have been allocated, it will return -EINVAL. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_mmap_buffers(struct v4l2_device *dev); + +/* + * v4l2_queue_buffer - Queue a buffer for video capture/output + * @dev: Device instance + * @buffer: Buffer to be queued + * + * Queue the buffer identified by @buffer for video capture or output, depending + * on the device type. + * + * The caller must initialize the @buffer::index field with the index of the + * buffer to be queued. The index is zero-based and must be lower than the + * number of allocated buffers. + * + * For V4L2_MEMORY_DMABUF buffers, the caller must initialize the @buffer::dmabuf + * field with the address of the video frame memory, and the @buffer:length + * field with its size in bytes. For optimal performances the address and length + * should be identical between v4l2_queue_buffer() calls for a given buffer + * index. + * + * For video output, the caller must initialize the @buffer::bytesused field + * with the size of video data. The value should differ from the buffer length + * for variable-size video formats only. + * + * Upon successful return the buffer ownership is transferred to the driver. The + * caller must not touch video memory for that buffer before calling + * v4l2_dequeue_buffer(). Attempting to queue an already queued buffer will + * fail. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_queue_buffer(struct v4l2_device *dev, struct video_buffer *buffer); + +/* + * v4l2_dequeue_buffer - Dequeue the next buffer + * @dev: Device instance + * @buffer: Dequeued buffer data to be filled + * + * Dequeue the next buffer processed by the driver and fill all fields in + * @buffer.  + * + * This function does not block. If no buffer is ready it will return + * immediately with -EAGAIN. + * + * If an error occured during video capture or display, the @buffer::error field + * is set to true. Depending on the device the video data can be partly + * corrupted or complete garbage. + * + * Once dequeued the buffer ownership is transferred to the caller. Video memory + * for that buffer can be safely read from and written to. + * + * Return 0 on success or a negative error code on failure. An error that + * results in @buffer:error being set is not considered as a failure condition + * for the purpose of the return value. + */ +int v4l2_dequeue_buffer(struct v4l2_device *dev, struct video_buffer *buffer); + +/* + * v4l2_stream_on - Start video streaming + * @dev: Device instance + * + * Start video capture or output on the device. For video output devices at + * least one buffer must be queued before starting the stream. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_stream_on(struct v4l2_device *dev); + +/* + * v4l2_stream_off - Stop video streaming + * @dev: Device instance + * + * Stop video capture or output on the device. Upon successful return ownership + * of all buffers is returned to the caller. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_stream_off(struct v4l2_device *dev); + +/* + * v4l2_get_control - Read the value of a control + * @dev: Device instance + * @id: Control ID + * @value: Control value to be filled + * + * Retrieve the current value of control @id and store it in @value. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_get_control(struct v4l2_device *dev, unsigned int id, int32_t *value); + +/* + * v4l2_set_control - Write the value of a control + * @dev: Device instance + * @id: Control ID + * @value: Control value + * + * Set control @id to @value. The device is allowed to modify the requested + * value, in which case @value is updated to the modified value. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_set_control(struct v4l2_device *dev, unsigned int id, int32_t *value); + +/* + * v4l2_get_controls - Read the value of multiple controls + * @dev: Device instance + * @count: Number of controls + * @ctrls: Controls to be read + * + * Retrieve the current value of controls identified by @ctrls. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_get_controls(struct v4l2_device *dev, unsigned int count, +		      struct v4l2_ext_control *ctrls); + +/* + * v4l2_set_controls - Write the value of multiple controls + * @dev: Device instance + * @count: Number of controls + * @ctrls: Controls to be written + * + * Set controls identified by @ctrls. The device is allowed to modify the + * requested values, in which case @ctrls is updated to the modified value. + * + * Return 0 on success or a negative error code on failure. + */ +int v4l2_set_controls(struct v4l2_device *dev, unsigned int count, +		      struct v4l2_ext_control *ctrls); + +#endif diff --git a/lib/video-buffers.c b/lib/video-buffers.c new file mode 100644 index 0000000..f5dc6ec --- /dev/null +++ b/lib/video-buffers.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Video buffers + * + * Copyright (C) 2018 Laurent Pinchart + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include "video-buffers.h" + +#include <stdlib.h> +#include <string.h> + +struct video_buffer_set *video_buffer_set_new(unsigned int nbufs) +{ +	struct video_buffer_set *buffers; + +	buffers = malloc(sizeof *buffers); +	if (!buffers) +		return NULL; + +	buffers->nbufs = nbufs; +	buffers->buffers = calloc(nbufs, sizeof *buffers->buffers); +	if (!buffers->buffers) { +		free(buffers); +		return NULL; +	} + +	return buffers; +} + +void video_buffer_set_delete(struct video_buffer_set *buffers) +{ +	if (!buffers) +		return; + +	free(buffers->buffers); +	free(buffers); +} diff --git a/lib/video-buffers.h b/lib/video-buffers.h new file mode 100644 index 0000000..001f75e --- /dev/null +++ b/lib/video-buffers.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Video buffers + * + * Copyright (C) 2018 Laurent Pinchart + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ +#ifndef __VIDEO_BUFFERS_H__ +#define __VIDEO_BUFFERS_H__ + +#include <stdbool.h> +#include <stddef.h> +#include <sys/time.h> + +/* + * + * struct video_buffer - Video buffer information + * @index: Zero-based buffer index, limited to the number of buffers minus one + * @size: Size of the video memory, in bytes + * @bytesused: Number of bytes used by video data, smaller or equal to @size + * @timestamp: Time stamp at which the buffer has been captured + * @error: True if an error occured while capturing video data for the buffer + * @allocated: True if memory for the buffer has been allocated + * @mem: Video data memory + * @dmabuf: Video data dmabuf handle + */ +struct video_buffer +{ +	unsigned int index; +	unsigned int size; +	unsigned int bytesused; +	struct timeval timestamp; +	bool error; +	void *mem; +	int dmabuf; +}; + +struct video_buffer_set +{ +	struct video_buffer *buffers; +	unsigned int nbufs; +}; + +struct video_buffer_set *video_buffer_set_new(unsigned int nbufs); +void video_buffer_set_delete(struct video_buffer_set *buffers); + +#endif /* __VIDEO_BUFFERS_H__ */ diff --git a/lib/video-source.c b/lib/video-source.c new file mode 100644 index 0000000..06092f5 --- /dev/null +++ b/lib/video-source.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Abstract video source + * + * Copyright (C) 2018 Laurent Pinchart + * + * Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + */ + +#include "video-source.h" + +void video_source_set_buffer_handler(struct video_source *src, +				     video_source_buffer_handler_t handler, +				     void *data) +{ +	src->handler = handler; +	src->handler_data = data; +} + +void video_source_destroy(struct video_source *src) +{ +	if (src) +		src->ops->destroy(src); +} + +int video_source_set_format(struct video_source *src, +			    struct v4l2_pix_format *fmt) +{ +	return src->ops->set_format(src, fmt); +} + +int video_source_alloc_buffers(struct video_source *src, unsigned int nbufs) +{ +	return src->ops->alloc_buffers(src, nbufs); +} + +int video_source_export_buffers(struct video_source *src, +				struct video_buffer_set **buffers) +{ +	return src->ops->export_buffers(src, buffers); +} + +int video_source_free_buffers(struct video_source *src) +{ +	return src->ops->free_buffers(src); +} + +int video_source_stream_on(struct video_source *src) +{ +	return src->ops->stream_on(src); +} + +int video_source_stream_off(struct video_source *src) +{ +	return src->ops->stream_off(src); +} + +int video_source_queue_buffer(struct video_source *src, +			      struct video_buffer *buf) +{ +	return src->ops->queue_buffer(src, buf); +} | 
