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