From 2bb0cfbf8137e02cc32aae3b36f85ef7300e8936 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Sat, 9 Jun 2018 14:29:41 +0300 Subject: 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 --- .gitignore | 5 + CMakeLists.txt | 10 + Makefile | 29 -- configfs.c | 846 --------------------------------------- configfs.h | 108 ----- events.c | 183 --------- events.h | 49 --- include/uvcgadget/configfs.h | 108 +++++ include/uvcgadget/events.h | 49 +++ include/uvcgadget/list.h | 95 +++++ include/uvcgadget/stream.h | 113 ++++++ include/uvcgadget/v4l2-source.h | 20 + include/uvcgadget/video-source.h | 55 +++ lib/CMakeLists.txt | 17 + lib/configfs.c | 846 +++++++++++++++++++++++++++++++++++++++ lib/events.c | 183 +++++++++ lib/stream.c | 223 +++++++++++ lib/tools.h | 65 +++ lib/uvc.c | 395 ++++++++++++++++++ lib/uvc.h | 36 ++ lib/v4l2-source.c | 180 +++++++++ lib/v4l2.c | 833 ++++++++++++++++++++++++++++++++++++++ lib/v4l2.h | 339 ++++++++++++++++ lib/video-buffers.c | 40 ++ lib/video-buffers.h | 48 +++ lib/video-source.c | 62 +++ list.h | 95 ----- stream.c | 223 ----------- stream.h | 113 ------ tools.h | 65 --- uvc.c | 395 ------------------ uvc.h | 36 -- v4l2-source.c | 180 --------- v4l2-source.h | 20 - v4l2.c | 833 -------------------------------------- v4l2.h | 339 ---------------- video-buffers.c | 40 -- video-buffers.h | 48 --- video-source.c | 62 --- video-source.h | 55 --- 40 files changed, 3722 insertions(+), 3719 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile delete mode 100644 configfs.c delete mode 100644 configfs.h delete mode 100644 events.c delete mode 100644 events.h create mode 100644 include/uvcgadget/configfs.h create mode 100644 include/uvcgadget/events.h create mode 100644 include/uvcgadget/list.h create mode 100644 include/uvcgadget/stream.h create mode 100644 include/uvcgadget/v4l2-source.h create mode 100644 include/uvcgadget/video-source.h create mode 100644 lib/CMakeLists.txt create mode 100644 lib/configfs.c create mode 100644 lib/events.c create mode 100644 lib/stream.c create mode 100644 lib/tools.h create mode 100644 lib/uvc.c create mode 100644 lib/uvc.h create mode 100644 lib/v4l2-source.c create mode 100644 lib/v4l2.c create mode 100644 lib/v4l2.h create mode 100644 lib/video-buffers.c create mode 100644 lib/video-buffers.h create mode 100644 lib/video-source.c delete mode 100644 list.h delete mode 100644 stream.c delete mode 100644 stream.h delete mode 100644 tools.h delete mode 100644 uvc.c delete mode 100644 uvc.h delete mode 100644 v4l2-source.c delete mode 100644 v4l2-source.h delete mode 100644 v4l2.c delete mode 100644 v4l2.h delete mode 100644 video-buffers.c delete mode 100644 video-buffers.h delete mode 100644 video-source.c delete mode 100644 video-source.h diff --git a/.gitignore b/.gitignore index 8f9d710..2a36808 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ +*.a *.o +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake uvc-gadget diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..79b78c4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.0) +project(uvc-gadget) +set(CMAKE_BUILD_TYPE Release) + +#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter -Werror") + +add_subdirectory(lib) +add_executable(uvc-gadget main.c) +target_link_libraries(uvc-gadget uvcgadget) +install(TARGETS uvc-gadget DESTINATION bin) diff --git a/Makefile b/Makefile deleted file mode 100644 index c3db9d3..0000000 --- a/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -CROSS_COMPILE ?= -ARCH ?= x86 -KERNEL_DIR ?= /usr/src/linux - -CC := $(CROSS_COMPILE)gcc -KERNEL_INCLUDE := -I$(KERNEL_DIR)/include -I$(KERNEL_DIR)/arch/$(ARCH)/include -CFLAGS := -W -Wall -Wno-unused-parameter -g $(KERNEL_INCLUDE) -LDFLAGS := -g - -OBJS := \ - configfs.o \ - events.o \ - main.o \ - stream.o \ - uvc.o \ - v4l2.o \ - v4l2-source.o \ - video-buffers.o \ - video-source.o - -all: uvc-gadget - -uvc-gadget: $(OBJS) - $(CC) $(LDFLAGS) -o $@ $^ - -clean: - rm -f *.o - rm -f uvc-gadget - diff --git a/configfs.c b/configfs.c deleted file mode 100644 index 1dfde95..0000000 --- a/configfs.c +++ /dev/null @@ -1,846 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * ConfigFS Gadget device handling - * - * Copyright (C) 2018 Kieran Bingham - * - * Contact: Kieran Bingham - */ - -/* To provide basename and asprintf from the GNU library. */ - #define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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/configfs.h b/configfs.h deleted file mode 100644 index e5a7efa..0000000 --- a/configfs.h +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * ConfigFS Gadget device handling - * - * Copyright (C) 2018 Kieran Bingham - * - * Contact: Kieran Bingham - */ - -#ifndef __CONFIGFS_H__ -#define __CONFIGFS_H__ - -#include - -/* - * struct uvc_function_config_endpoint - Endpoint parameters - * @bInterval: Transfer interval (interrupt and isochronous only) - * @bMaxBurst: Transfer burst size (super-speed only) - * @wMaxPacketSize: Maximum packet size (including the multiplier) - */ -struct uvc_function_config_endpoint { - unsigned int bInterval; - unsigned int bMaxBurst; - unsigned int wMaxPacketSize; -}; - -/* - * struct uvc_function_config_interface - Interface parameters - * @bInterfaceNumber: Interface number - */ -struct uvc_function_config_interface { - unsigned int bInterfaceNumber; -}; - -/* - * struct uvc_function_config_control - Control interface parameters - * @intf: Generic interface parameters - */ -struct uvc_function_config_control { - struct uvc_function_config_interface intf; -}; - -/* - * struct uvc_function_config_frame - Streaming frame parameters - * @index: Frame index in the UVC descriptors - * @width: Frame width in pixels - * @height: Frame height in pixels - * @num_intervals: Number of entries in the intervals array - * @intervals: Array of frame intervals - */ -struct uvc_function_config_frame { - unsigned int index; - unsigned int width; - unsigned int height; - unsigned int num_intervals; - unsigned int *intervals; -}; - -/* - * struct uvc_function_config_format - Streaming format parameters - * @index: Format index in the UVC descriptors - * @guid: Format GUID - * @fcc: V4L2 pixel format - * @num_frames: Number of entries in the frames array - * @frames: Array of frame descriptors - */ -struct uvc_function_config_format { - unsigned int index; - uint8_t guid[16]; - unsigned int fcc; - unsigned int num_frames; - struct uvc_function_config_frame *frames; -}; - -/* - * struct uvc_function_config_streaming - Streaming interface parameters - * @intf: Generic interface parameters - * @ep: Endpoint parameters - * @num_formats: Number of entries in the formats array - * @formats: Array of format descriptors - */ -struct uvc_function_config_streaming { - struct uvc_function_config_interface intf; - struct uvc_function_config_endpoint ep; - - unsigned int num_formats; - struct uvc_function_config_format *formats; -}; - -/* - * struct uvc_function_config - UVC function configuration parameters - * @video: Full path to the video device node - * @udc: UDC name - * @control: Control interface configuration - * @streaming: Streaming interface configuration - */ -struct uvc_function_config { - char *video; - char *udc; - - struct uvc_function_config_control control; - struct uvc_function_config_streaming streaming; -}; - -struct uvc_function_config *configfs_parse_uvc_function(const char *function); -void configfs_free_uvc_function(struct uvc_function_config *fc); - -#endif diff --git a/events.c b/events.c deleted file mode 100644 index 6840c19..0000000 --- a/events.c +++ /dev/null @@ -1,183 +0,0 @@ -/* 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 - */ - -#define _DEFAULT_SOURCE -#include -#include -#include -#include - -#include - -#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/events.h b/events.h deleted file mode 100644 index b0b8fa8..0000000 --- a/events.h +++ /dev/null @@ -1,49 +0,0 @@ -/* 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 - */ - -#ifndef __EVENTS_H__ -#define __EVENTS_H__ - -#include -#include - -#include "list.h" - -struct events { - struct list_entry events; - bool done; - - int maxfd; - fd_set rfds; - fd_set wfds; - fd_set efds; -}; - -enum event_type { - EVENT_READ = 1, - EVENT_WRITE = 2, - EVENT_EXCEPTION = 4, -}; - -void events_watch_fd(struct events *events, int fd, enum event_type type, - void(*callback)(void *), void *priv); -void events_unwatch_fd(struct events *events, int fd, enum event_type type); - -bool events_loop(struct events *events); -void events_stop(struct events *events); - -void events_init(struct events *events); -void events_cleanup(struct events *events); - -#endif diff --git a/include/uvcgadget/configfs.h b/include/uvcgadget/configfs.h new file mode 100644 index 0000000..e5a7efa --- /dev/null +++ b/include/uvcgadget/configfs.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * ConfigFS Gadget device handling + * + * Copyright (C) 2018 Kieran Bingham + * + * Contact: Kieran Bingham + */ + +#ifndef __CONFIGFS_H__ +#define __CONFIGFS_H__ + +#include + +/* + * struct uvc_function_config_endpoint - Endpoint parameters + * @bInterval: Transfer interval (interrupt and isochronous only) + * @bMaxBurst: Transfer burst size (super-speed only) + * @wMaxPacketSize: Maximum packet size (including the multiplier) + */ +struct uvc_function_config_endpoint { + unsigned int bInterval; + unsigned int bMaxBurst; + unsigned int wMaxPacketSize; +}; + +/* + * struct uvc_function_config_interface - Interface parameters + * @bInterfaceNumber: Interface number + */ +struct uvc_function_config_interface { + unsigned int bInterfaceNumber; +}; + +/* + * struct uvc_function_config_control - Control interface parameters + * @intf: Generic interface parameters + */ +struct uvc_function_config_control { + struct uvc_function_config_interface intf; +}; + +/* + * struct uvc_function_config_frame - Streaming frame parameters + * @index: Frame index in the UVC descriptors + * @width: Frame width in pixels + * @height: Frame height in pixels + * @num_intervals: Number of entries in the intervals array + * @intervals: Array of frame intervals + */ +struct uvc_function_config_frame { + unsigned int index; + unsigned int width; + unsigned int height; + unsigned int num_intervals; + unsigned int *intervals; +}; + +/* + * struct uvc_function_config_format - Streaming format parameters + * @index: Format index in the UVC descriptors + * @guid: Format GUID + * @fcc: V4L2 pixel format + * @num_frames: Number of entries in the frames array + * @frames: Array of frame descriptors + */ +struct uvc_function_config_format { + unsigned int index; + uint8_t guid[16]; + unsigned int fcc; + unsigned int num_frames; + struct uvc_function_config_frame *frames; +}; + +/* + * struct uvc_function_config_streaming - Streaming interface parameters + * @intf: Generic interface parameters + * @ep: Endpoint parameters + * @num_formats: Number of entries in the formats array + * @formats: Array of format descriptors + */ +struct uvc_function_config_streaming { + struct uvc_function_config_interface intf; + struct uvc_function_config_endpoint ep; + + unsigned int num_formats; + struct uvc_function_config_format *formats; +}; + +/* + * struct uvc_function_config - UVC function configuration parameters + * @video: Full path to the video device node + * @udc: UDC name + * @control: Control interface configuration + * @streaming: Streaming interface configuration + */ +struct uvc_function_config { + char *video; + char *udc; + + struct uvc_function_config_control control; + struct uvc_function_config_streaming streaming; +}; + +struct uvc_function_config *configfs_parse_uvc_function(const char *function); +void configfs_free_uvc_function(struct uvc_function_config *fc); + +#endif diff --git a/include/uvcgadget/events.h b/include/uvcgadget/events.h new file mode 100644 index 0000000..b0b8fa8 --- /dev/null +++ b/include/uvcgadget/events.h @@ -0,0 +1,49 @@ +/* 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 + */ + +#ifndef __EVENTS_H__ +#define __EVENTS_H__ + +#include +#include + +#include "list.h" + +struct events { + struct list_entry events; + bool done; + + int maxfd; + fd_set rfds; + fd_set wfds; + fd_set efds; +}; + +enum event_type { + EVENT_READ = 1, + EVENT_WRITE = 2, + EVENT_EXCEPTION = 4, +}; + +void events_watch_fd(struct events *events, int fd, enum event_type type, + void(*callback)(void *), void *priv); +void events_unwatch_fd(struct events *events, int fd, enum event_type type); + +bool events_loop(struct events *events); +void events_stop(struct events *events); + +void events_init(struct events *events); +void events_cleanup(struct events *events); + +#endif diff --git a/include/uvcgadget/list.h b/include/uvcgadget/list.h new file mode 100644 index 0000000..8854c6e --- /dev/null +++ b/include/uvcgadget/list.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Double Linked Lists + * + * 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 + */ + +#ifndef __LIST_H +#define __LIST_H + +#include + +struct list_entry { + struct list_entry *prev; + struct list_entry *next; +}; + +static inline void list_init(struct list_entry *list) +{ + list->next = list; + list->prev = list; +} + +static inline int list_empty(struct list_entry *list) +{ + return list->next == list; +} + +static inline void list_append(struct list_entry *entry, struct list_entry *list) +{ + entry->next = list; + entry->prev = list->prev; + list->prev->next = entry; + list->prev = entry; +} + +static inline void list_prepend(struct list_entry *entry, struct list_entry *list) +{ + entry->next = list->next; + entry->prev = list; + list->next->prev = entry; + list->next = entry; +} + +static inline void list_insert_after(struct list_entry *entry, struct list_entry *after) +{ + list_prepend(entry, after); +} + +static inline void list_insert_before(struct list_entry *entry, struct list_entry *before) +{ + list_append(entry, before); +} + +static inline void list_remove(struct list_entry *entry) +{ + entry->prev->next = entry->next; + entry->next->prev = entry->prev; +} + +#define list_entry(entry, type, member) \ + (type *)((char *)(entry) - offsetof(type, member)) + +#define list_first_entry(list, type, member) \ + list_entry((list)->next, type, member) + +#define list_last_entry(list, type, member) \ + list_entry((list)->prev, type, member) + +#define list_for_each(entry, list) \ + for (entry = (list)->next; entry != (list); entry = entry->next) + +#define list_for_each_entry(entry, list, member) \ + for (entry = list_entry((list)->next, typeof(*entry), member); \ + &entry->member != (list); \ + entry = list_entry(entry->member.next, typeof(*entry), member)) + +#define list_for_each_safe(entry, __next, list) \ + for (entry = (list)->next, __next = entry->next; entry != (list); \ + entry = __next, __next = entry->next) + +#define list_for_each_entry_safe(entry, __next, list, member) \ + for (entry = list_entry((list)->next, typeof(*entry), member), \ + __next = list_entry(entry->member.next, typeof(*entry), member); \ + &entry->member != (list); \ + entry = __next, __next = list_entry(entry->member.next, typeof(*entry), member)) + +#endif /* __LIST_H */ diff --git a/include/uvcgadget/stream.h b/include/uvcgadget/stream.h new file mode 100644 index 0000000..13779d3 --- /dev/null +++ b/include/uvcgadget/stream.h @@ -0,0 +1,113 @@ +/* + * UVC stream handling + * + * Copyright (C) 2010 Ideas on board SPRL + * + * 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 __STREAM_H__ +#define __STREAM_H__ + +struct events; +struct uvc_function_config; +struct uvc_stream; +struct v4l2_pix_format; +struct video_source; + +/* + * uvc_stream_new - Create a new UVC stream + * @uvc_device: Filename of UVC device node + * + * Create a new UVC stream to handle the UVC function corresponding to the video + * device node @uvc_device. + * + * Streams allocated with this function can be deleted with uvc_stream_delete(). + * + * On success, returns a pointer to newly allocated and populated struct uvc_stream. + * On failure, returns NULL. + */ +struct uvc_stream *uvc_stream_new(const char *uvc_device); + +/* + * uvc_stream_init_uvc - Initialize a UVC stream + * @stream: the UVC stream + * @fc: UVC function configuration + * + * Before it can be used, a UVC stream has to be initialized by calling this + * function with a UVC function configuration @fc. The function configuration + * contains all the parameters of the UVC function that will be handled by the + * UVC stream. It can be parsed from the UVC function ConfigFS directory using + * configfs_parse_uvc_function(). + * + * uvc_stream_init_uvc() also registers UVC event notifiers for the stream. The + * caller must have called the uvc_stream_set_event_handler() function first, + * and ensure that the event handler is immediately usable. If the event loop is + * already running, all initialization steps required to handle events must be + * fully performed before calling this function. + */ +void uvc_stream_init_uvc(struct uvc_stream *stream, + struct uvc_function_config *fc); + +/* + * uvc_stream_set_event_handler - Set an event handler for a stream + * @stream: the UVC stream + * @events: the event handler + * + * This function sets the event handler that the stream can use to be notified + * of file descriptor events. + */ +void uvc_stream_set_event_handler(struct uvc_stream *stream, + struct events *events); + +void uvc_stream_set_video_source(struct uvc_stream *stream, + struct video_source *src); + +/* + * uvc_stream_delete - Delete a UVC stream + * @stream: the UVC stream + * + * This functions deletes the @stream created with uvc_stream_new(). Upon return + * the stream object may be freed, the @stream pointer thus becomes invalid and + * the stream must not be touched anymore. + * + * Every stream allocated with uvc_stream_new() must be deleted when not needed + * anymore. + */ +void uvc_stream_delete(struct uvc_stream *stream); + +/* + * uvc_stream_set_format - Set the active video format for the stream + * @stream: the UVC stream + * @format: the video stream format + * + * This function is called from the UVC protocol handler to configure the video + * format for the @stream. It must not be called directly by applications. + * + * Returns 0 on success, or a negative error code on failure. + */ +int uvc_stream_set_format(struct uvc_stream *stream, + const struct v4l2_pix_format *format); + +/* + * uvc_stream_enable - Turn on/off video streaming for the UVC stream + * @stream: the UVC stream + * @enable: 0 to stop the stream, 1 to start it + * + * This function is called from the UVC protocol handler to start video transfer + * for the @stream. It must not be called directly by applications. + */ +void uvc_stream_enable(struct uvc_stream *stream, int enable); + +#endif /* __STREAM_H__ */ diff --git a/include/uvcgadget/v4l2-source.h b/include/uvcgadget/v4l2-source.h new file mode 100644 index 0000000..53e28d6 --- /dev/null +++ b/include/uvcgadget/v4l2-source.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * V4L2 video source + * + * Copyright (C) 2018 Laurent Pinchart + * + * Contact: Laurent Pinchart + */ +#ifndef __V4L2_VIDEO_SOURCE_H__ +#define __V4L2_VIDEO_SOURCE_H__ + +#include "video-source.h" + +struct events; +struct video_source; + +struct video_source *v4l2_video_source_create(const char *devname); +void v4l2_video_source_init(struct video_source *src, struct events *events); + +#endif /* __VIDEO_SOURCE_H__ */ diff --git a/include/uvcgadget/video-source.h b/include/uvcgadget/video-source.h new file mode 100644 index 0000000..fffcba3 --- /dev/null +++ b/include/uvcgadget/video-source.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Abstract video source + * + * Copyright (C) 2018 Laurent Pinchart + * + * Contact: Laurent Pinchart + */ +#ifndef __VIDEO_SOURCE_H__ +#define __VIDEO_SOURCE_H__ + +struct v4l2_buffer; +struct v4l2_pix_format; +struct video_buffer; +struct video_buffer_set; +struct video_source; + +struct video_source_ops { + void(*destroy)(struct video_source *src); + int(*set_format)(struct video_source *src, struct v4l2_pix_format *fmt); + int(*alloc_buffers)(struct video_source *src, unsigned int nbufs); + int(*export_buffers)(struct video_source *src, + struct video_buffer_set **buffers); + int(*free_buffers)(struct video_source *src); + int(*stream_on)(struct video_source *src); + int(*stream_off)(struct video_source *src); + int(*queue_buffer)(struct video_source *src, struct video_buffer *buf); +}; + +typedef void(*video_source_buffer_handler_t)(void *, struct video_source *, + struct video_buffer *); + +struct video_source { + const struct video_source_ops *ops; + struct events *events; + video_source_buffer_handler_t handler; + void *handler_data; +}; + +void video_source_set_buffer_handler(struct video_source *src, + video_source_buffer_handler_t handler, + void *data); +void video_source_destroy(struct video_source *src); +int video_source_set_format(struct video_source *src, + struct v4l2_pix_format *fmt); +int video_source_alloc_buffers(struct video_source *src, unsigned int nbufs); +int video_source_export_buffers(struct video_source *src, + struct video_buffer_set **buffers); +int video_source_free_buffers(struct video_source *src); +int video_source_stream_on(struct video_source *src); +int video_source_stream_off(struct video_source *src); +int video_source_queue_buffer(struct video_source *src, + struct video_buffer *buf); + +#endif /* __VIDEO_SOURCE_H__ */ 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 + $ + $ + 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 + */ + +/* To provide basename and asprintf from the GNU library. */ + #define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + */ + +#define _DEFAULT_SOURCE +#include +#include +#include +#include + +#include + +#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 + * + * 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 +#include +#include + +#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 + */ + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 + */ + +#include +#include +#include + +#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 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#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 + */ +#ifndef __V4L2_H +#define __V4L2_H + +#include +#include + +#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 + */ + +#include "video-buffers.h" + +#include +#include + +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 + */ +#ifndef __VIDEO_BUFFERS_H__ +#define __VIDEO_BUFFERS_H__ + +#include +#include +#include + +/* + * + * 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 + */ + +#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); +} diff --git a/list.h b/list.h deleted file mode 100644 index 8854c6e..0000000 --- a/list.h +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Double Linked Lists - * - * 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 - */ - -#ifndef __LIST_H -#define __LIST_H - -#include - -struct list_entry { - struct list_entry *prev; - struct list_entry *next; -}; - -static inline void list_init(struct list_entry *list) -{ - list->next = list; - list->prev = list; -} - -static inline int list_empty(struct list_entry *list) -{ - return list->next == list; -} - -static inline void list_append(struct list_entry *entry, struct list_entry *list) -{ - entry->next = list; - entry->prev = list->prev; - list->prev->next = entry; - list->prev = entry; -} - -static inline void list_prepend(struct list_entry *entry, struct list_entry *list) -{ - entry->next = list->next; - entry->prev = list; - list->next->prev = entry; - list->next = entry; -} - -static inline void list_insert_after(struct list_entry *entry, struct list_entry *after) -{ - list_prepend(entry, after); -} - -static inline void list_insert_before(struct list_entry *entry, struct list_entry *before) -{ - list_append(entry, before); -} - -static inline void list_remove(struct list_entry *entry) -{ - entry->prev->next = entry->next; - entry->next->prev = entry->prev; -} - -#define list_entry(entry, type, member) \ - (type *)((char *)(entry) - offsetof(type, member)) - -#define list_first_entry(list, type, member) \ - list_entry((list)->next, type, member) - -#define list_last_entry(list, type, member) \ - list_entry((list)->prev, type, member) - -#define list_for_each(entry, list) \ - for (entry = (list)->next; entry != (list); entry = entry->next) - -#define list_for_each_entry(entry, list, member) \ - for (entry = list_entry((list)->next, typeof(*entry), member); \ - &entry->member != (list); \ - entry = list_entry(entry->member.next, typeof(*entry), member)) - -#define list_for_each_safe(entry, __next, list) \ - for (entry = (list)->next, __next = entry->next; entry != (list); \ - entry = __next, __next = entry->next) - -#define list_for_each_entry_safe(entry, __next, list, member) \ - for (entry = list_entry((list)->next, typeof(*entry), member), \ - __next = list_entry(entry->member.next, typeof(*entry), member); \ - &entry->member != (list); \ - entry = __next, __next = list_entry(entry->member.next, typeof(*entry), member)) - -#endif /* __LIST_H */ diff --git a/stream.c b/stream.c deleted file mode 100644 index 57745aa..0000000 --- a/stream.c +++ /dev/null @@ -1,223 +0,0 @@ -/* - * UVC stream handling - * - * Copyright (C) 2010 Ideas on board SPRL - * - * 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 -#include -#include - -#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/stream.h b/stream.h deleted file mode 100644 index 13779d3..0000000 --- a/stream.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * UVC stream handling - * - * Copyright (C) 2010 Ideas on board SPRL - * - * 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 __STREAM_H__ -#define __STREAM_H__ - -struct events; -struct uvc_function_config; -struct uvc_stream; -struct v4l2_pix_format; -struct video_source; - -/* - * uvc_stream_new - Create a new UVC stream - * @uvc_device: Filename of UVC device node - * - * Create a new UVC stream to handle the UVC function corresponding to the video - * device node @uvc_device. - * - * Streams allocated with this function can be deleted with uvc_stream_delete(). - * - * On success, returns a pointer to newly allocated and populated struct uvc_stream. - * On failure, returns NULL. - */ -struct uvc_stream *uvc_stream_new(const char *uvc_device); - -/* - * uvc_stream_init_uvc - Initialize a UVC stream - * @stream: the UVC stream - * @fc: UVC function configuration - * - * Before it can be used, a UVC stream has to be initialized by calling this - * function with a UVC function configuration @fc. The function configuration - * contains all the parameters of the UVC function that will be handled by the - * UVC stream. It can be parsed from the UVC function ConfigFS directory using - * configfs_parse_uvc_function(). - * - * uvc_stream_init_uvc() also registers UVC event notifiers for the stream. The - * caller must have called the uvc_stream_set_event_handler() function first, - * and ensure that the event handler is immediately usable. If the event loop is - * already running, all initialization steps required to handle events must be - * fully performed before calling this function. - */ -void uvc_stream_init_uvc(struct uvc_stream *stream, - struct uvc_function_config *fc); - -/* - * uvc_stream_set_event_handler - Set an event handler for a stream - * @stream: the UVC stream - * @events: the event handler - * - * This function sets the event handler that the stream can use to be notified - * of file descriptor events. - */ -void uvc_stream_set_event_handler(struct uvc_stream *stream, - struct events *events); - -void uvc_stream_set_video_source(struct uvc_stream *stream, - struct video_source *src); - -/* - * uvc_stream_delete - Delete a UVC stream - * @stream: the UVC stream - * - * This functions deletes the @stream created with uvc_stream_new(). Upon return - * the stream object may be freed, the @stream pointer thus becomes invalid and - * the stream must not be touched anymore. - * - * Every stream allocated with uvc_stream_new() must be deleted when not needed - * anymore. - */ -void uvc_stream_delete(struct uvc_stream *stream); - -/* - * uvc_stream_set_format - Set the active video format for the stream - * @stream: the UVC stream - * @format: the video stream format - * - * This function is called from the UVC protocol handler to configure the video - * format for the @stream. It must not be called directly by applications. - * - * Returns 0 on success, or a negative error code on failure. - */ -int uvc_stream_set_format(struct uvc_stream *stream, - const struct v4l2_pix_format *format); - -/* - * uvc_stream_enable - Turn on/off video streaming for the UVC stream - * @stream: the UVC stream - * @enable: 0 to stop the stream, 1 to start it - * - * This function is called from the UVC protocol handler to start video transfer - * for the @stream. It must not be called directly by applications. - */ -void uvc_stream_enable(struct uvc_stream *stream, int enable); - -#endif /* __STREAM_H__ */ diff --git a/tools.h b/tools.h deleted file mode 100644 index ff2f908..0000000 --- a/tools.h +++ /dev/null @@ -1,65 +0,0 @@ -/* 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 - */ - -#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/uvc.c b/uvc.c deleted file mode 100644 index 81d1760..0000000 --- a/uvc.c +++ /dev/null @@ -1,395 +0,0 @@ -/* - * UVC protocol handling - * - * Copyright (C) 2010 Ideas on board SPRL - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -#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/uvc.h b/uvc.h deleted file mode 100644 index f73dbff..0000000 --- a/uvc.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * UVC protocol handling - * - * Copyright (C) 2010 Ideas on board SPRL - * - * 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/v4l2-source.c b/v4l2-source.c deleted file mode 100644 index 7eced6a..0000000 --- a/v4l2-source.c +++ /dev/null @@ -1,180 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * V4L2 video source - * - * Copyright (C) 2018 Laurent Pinchart - * - * Contact: Laurent Pinchart - */ - -#include -#include -#include - -#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/v4l2-source.h b/v4l2-source.h deleted file mode 100644 index 53e28d6..0000000 --- a/v4l2-source.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * V4L2 video source - * - * Copyright (C) 2018 Laurent Pinchart - * - * Contact: Laurent Pinchart - */ -#ifndef __V4L2_VIDEO_SOURCE_H__ -#define __V4L2_VIDEO_SOURCE_H__ - -#include "video-source.h" - -struct events; -struct video_source; - -struct video_source *v4l2_video_source_create(const char *devname); -void v4l2_video_source_init(struct video_source *src, struct events *events); - -#endif /* __VIDEO_SOURCE_H__ */ diff --git a/v4l2.c b/v4l2.c deleted file mode 100644 index 9030270..0000000 --- a/v4l2.c +++ /dev/null @@ -1,833 +0,0 @@ -/* 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 - */ - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#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/v4l2.h b/v4l2.h deleted file mode 100644 index 8d7b3c5..0000000 --- a/v4l2.h +++ /dev/null @@ -1,339 +0,0 @@ -/* 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 - */ -#ifndef __V4L2_H -#define __V4L2_H - -#include -#include - -#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/video-buffers.c b/video-buffers.c deleted file mode 100644 index f5dc6ec..0000000 --- a/video-buffers.c +++ /dev/null @@ -1,40 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Video buffers - * - * Copyright (C) 2018 Laurent Pinchart - * - * Contact: Laurent Pinchart - */ - -#include "video-buffers.h" - -#include -#include - -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/video-buffers.h b/video-buffers.h deleted file mode 100644 index 001f75e..0000000 --- a/video-buffers.h +++ /dev/null @@ -1,48 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Video buffers - * - * Copyright (C) 2018 Laurent Pinchart - * - * Contact: Laurent Pinchart - */ -#ifndef __VIDEO_BUFFERS_H__ -#define __VIDEO_BUFFERS_H__ - -#include -#include -#include - -/* - * - * 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/video-source.c b/video-source.c deleted file mode 100644 index 06092f5..0000000 --- a/video-source.c +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Abstract video source - * - * Copyright (C) 2018 Laurent Pinchart - * - * Contact: Laurent Pinchart - */ - -#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); -} diff --git a/video-source.h b/video-source.h deleted file mode 100644 index fffcba3..0000000 --- a/video-source.h +++ /dev/null @@ -1,55 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* - * Abstract video source - * - * Copyright (C) 2018 Laurent Pinchart - * - * Contact: Laurent Pinchart - */ -#ifndef __VIDEO_SOURCE_H__ -#define __VIDEO_SOURCE_H__ - -struct v4l2_buffer; -struct v4l2_pix_format; -struct video_buffer; -struct video_buffer_set; -struct video_source; - -struct video_source_ops { - void(*destroy)(struct video_source *src); - int(*set_format)(struct video_source *src, struct v4l2_pix_format *fmt); - int(*alloc_buffers)(struct video_source *src, unsigned int nbufs); - int(*export_buffers)(struct video_source *src, - struct video_buffer_set **buffers); - int(*free_buffers)(struct video_source *src); - int(*stream_on)(struct video_source *src); - int(*stream_off)(struct video_source *src); - int(*queue_buffer)(struct video_source *src, struct video_buffer *buf); -}; - -typedef void(*video_source_buffer_handler_t)(void *, struct video_source *, - struct video_buffer *); - -struct video_source { - const struct video_source_ops *ops; - struct events *events; - video_source_buffer_handler_t handler; - void *handler_data; -}; - -void video_source_set_buffer_handler(struct video_source *src, - video_source_buffer_handler_t handler, - void *data); -void video_source_destroy(struct video_source *src); -int video_source_set_format(struct video_source *src, - struct v4l2_pix_format *fmt); -int video_source_alloc_buffers(struct video_source *src, unsigned int nbufs); -int video_source_export_buffers(struct video_source *src, - struct video_buffer_set **buffers); -int video_source_free_buffers(struct video_source *src); -int video_source_stream_on(struct video_source *src); -int video_source_stream_off(struct video_source *src); -int video_source_queue_buffer(struct video_source *src, - struct video_buffer *buf); - -#endif /* __VIDEO_SOURCE_H__ */ -- cgit v1.2.3