diff options
-rw-r--r-- | include/uvcgadget/slideshow-source.h | 20 | ||||
-rw-r--r-- | lib/meson.build | 1 | ||||
-rw-r--r-- | lib/slideshow-source.c | 391 |
3 files changed, 412 insertions, 0 deletions
diff --git a/include/uvcgadget/slideshow-source.h b/include/uvcgadget/slideshow-source.h new file mode 100644 index 0000000..9b1d9f9 --- /dev/null +++ b/include/uvcgadget/slideshow-source.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Slideshow video source + * + * Copyright (C) 2018 Paul Elder + * + * Contact: Paul Elder <paul.elder@ideasonboard.com> + */ +#ifndef __SLIDESHOW_VIDEO_SOURCE_H__ +#define __SLIDESHOW_VIDEO_SOURCE_H__ + +#include "video-source.h" + +struct events; +struct video_source; + +struct video_source *slideshow_video_source_create(const char *img_dir); +void slideshow_video_source_init(struct video_source *src, struct events *events); + +#endif /* __SLIDESHOW_VIDEO_SOURCE_H__ */ diff --git a/lib/meson.build b/lib/meson.build index a0d67d5..680be1c 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -4,6 +4,7 @@ libuvcgadget_sources = files([ 'configfs.c', 'events.c', 'jpg-source.c', + 'slideshow-source.c', 'stream.c', 'test-source.c', 'timer.c', diff --git a/lib/slideshow-source.c b/lib/slideshow-source.c new file mode 100644 index 0000000..42a42a9 --- /dev/null +++ b/lib/slideshow-source.c @@ -0,0 +1,391 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Slideshow video source + * + * Copyright (C) 2018 Paul Elder + * + * Contact: Paul Elder <paul.elder@ideasonboard.com> + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <linux/limits.h> +#include <linux/videodev2.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include "events.h" +#include "list.h" +#include "slideshow-source.h" +#include "timer.h" +#include "tools.h" +#include "video-buffers.h" + +struct slide { + struct list_entry list; + unsigned int imgsize; + void *imgdata; +}; + +struct slideshow_source { + struct video_source src; + + char img_dir[NAME_MAX]; + + struct slide *cur_slide; + struct list_entry slides; + + struct timer *timer; + bool streaming; +}; + +#define to_slideshow_source(s) container_of(s, struct slideshow_source, src) + +static void slideshow_source_destroy(struct video_source *s) +{ + struct slideshow_source *src = to_slideshow_source(s); + struct slide *slide, *next; + + list_for_each_entry_safe(slide, next, &src->slides, list) { + list_remove(&slide->list); + free(slide->imgdata); + free(slide); + } + timer_destroy(src->timer); + free(src); +} + +char *v4l2_fourcc2s(__u32 fourcc, char *buf) +{ + buf[0] = fourcc & 0x7f; + buf[1] = (fourcc >> 8) & 0x7f; + buf[2] = (fourcc >> 16) & 0x7f; + buf[3] = (fourcc >> 24) & 0x7f; + if (fourcc & (1 << 31)) { + buf[4] = '-'; + buf[5] = 'B'; + buf[6] = 'E'; + buf[7] = '\0'; + } else { + buf[4] = '\0'; + } + return buf; +} + +/* + * slideshow_source_set_format - set the V4L2 format + * + * For this source, we require images stored in a directory structure with nodes + * for each format and framesize, for example: + * + * slideshow + + * | + * + MJPG + + * | | + * | + 1280x720 + + * | | | + * | | + 01.jpg + * | | | + * | | + 02.jpg + * | | | + * | | + 03.jpg + * | | + * | + 1920x1080 + * | + * + YUYV + + * | + * + 1280x720 + * | + * + 1920x1080 + * + * The root directory will be passed as an argument to slideshow_source_create() + * and so is not fixed, but the second level directories must be named with the + * fourcc of the format the images within represent, and the third level's node + * names must be in the format "<width>x<height>". + */ +static int slideshow_source_set_format(struct video_source *s, + struct v4l2_pix_format *fmt) +{ + struct slideshow_source *src = to_slideshow_source(s); + char dirname[PATH_MAX]; + struct slide *slide, *next; + struct dirent *file; + char fourcc_buf[8]; + int fd = -1; + char *cwd; + DIR *dir; + int ret; + + /* + * If the format is changed, we need to clear the existing list of + * slides before adding new ones. + */ + list_for_each_entry_safe(slide, next, &src->slides, list) { + list_remove(&slide->list); + free(slide->imgdata); + free(slide); + } + + ret = snprintf(dirname, sizeof(dirname), "%s/%s/%ux%u", src->img_dir, + v4l2_fourcc2s(fmt->pixelformat, fourcc_buf), + fmt->width, fmt->height); + if (ret < 0) { + fprintf(stderr, "failed to store directory name: %s (%d)\n", + strerror(ret), ret); + ret = errno; + goto err_dummy_slide; + } + + dir = opendir(dirname); + if (!dir) { + fprintf(stderr, "unable to find directory %s\n", dirname); + ret = -ENOENT; + goto err_dummy_slide; + } + + cwd = getcwd(NULL, 0); + if (!cwd) { + fprintf(stderr, "unable to allocate memory for cwd name\n"); + ret = -ENOMEM; + goto err_dummy_slide; + } + + ret = chdir(dirname); + if (ret) { + fprintf(stderr, "unable to cd to directory '%s': %s (%d)\n", + dirname, strerror(ret), ret); + goto err_close_dir; + } + + while ((file = readdir(dir))) { + if (!strcmp(file->d_name, ".") || + !strcmp(file->d_name, "..")) + continue; + + fd = open(file->d_name, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "Unable to open file '%s/%s'\n", dirname, + file->d_name); + ret = errno; + goto err_unwind; + } + + slide = malloc(sizeof(*slide)); + if (!slide) { + fprintf(stderr, "failed to allocate memory for slide\n"); + ret = -ENOMEM; + goto err_close_fd; + } + + slide->imgsize = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + + slide->imgdata = malloc(slide->imgsize); + if (!slide->imgdata) { + fprintf(stderr, "failed to allocate memory for image\n"); + ret = -ENOMEM; + goto err_free_slide; + } + + ret = read(fd, slide->imgdata, slide->imgsize); + if (ret < 0) { + fprintf(stderr, "failed to read from %s/%s: %u\n", + dirname, file->d_name, errno); + ret = errno; + goto err_free_imgdata; + } + + list_append(&slide->list, &src->slides); + close(fd); + } + + if (list_empty(&src->slides)) { + fprintf(stderr, "failed to find any images in %s\n", dirname); + ret = -ENOENT; + goto err_free_cwd; + } + + ret = chdir(cwd); + if (ret) { + fprintf(stderr, "unable to cd to directory '%s': %s (%d)\n", + cwd, strerror(ret), ret); + goto err_free_cwd; + } + + free(cwd); + closedir(dir); + src->cur_slide = list_first_entry(&src->slides, struct slide, list); + + return 0; + +err_free_imgdata: + free(slide->imgdata); +err_free_slide: + free(slide); +err_close_fd: + close(fd); +err_unwind: + list_for_each_entry_safe(slide, next, &src->slides, list) { + list_remove(&slide->list); + free(slide->imgdata); + free(slide); + } +err_free_cwd: + chdir(cwd); + free(cwd); +err_close_dir: + closedir(dir); +err_dummy_slide: + + /* + * At present, there is no means of stalling a USB SET_CUR control from + * the host; this means that the format passed here _must_ be accepted + * until this issue is resolved. To work around the issue for now simply + * use a single dummy slide... as long as we manage to allocate the + * memory for it at least. + */ + + printf("using dummy slideshow data\n"); + + slide = malloc(sizeof(*slide)); + if (!slide) { + fprintf(stderr, "failed to allocate memory for slide\n"); + return ret; + } + + slide->imgsize = fmt->width * fmt->height * 2; + + slide->imgdata = malloc(slide->imgsize); + if (!slide->imgdata) { + fprintf(stderr, "failed to allocate memory for image\n"); + free(slide); + return -ENOMEM; + } + + memset(slide->imgdata, 0, slide->imgsize); + list_append(&slide->list, &src->slides); + src->cur_slide = slide; + + return ret; +} + +static int slideshow_source_set_frame_rate(struct video_source *s, + unsigned int fps) +{ + struct slideshow_source *src = to_slideshow_source(s); + + timer_set_fps(src->timer, fps); + + return 0; +} + +static int slideshow_source_free_buffers(struct video_source *s __attribute__((unused))) +{ + return 0; +} + +static int slideshow_source_stream_on(struct video_source *s) +{ + struct slideshow_source *src = to_slideshow_source(s); + int ret; + + ret = timer_arm(src->timer); + if (ret) + return ret; + + src->streaming = true; + return 0; +} + +static int slideshow_source_stream_off(struct video_source *s) +{ + struct slideshow_source *src = to_slideshow_source(s); + + /* + * No error check here, because we want to flag that streaming is over + * even if the timer is still running due to the failure. + */ + timer_disarm(src->timer); + src->streaming = false; + + return 0; +} + +static void slideshow_source_fill_buffer(struct video_source *s, + struct video_buffer *buf) +{ + struct slideshow_source *src = to_slideshow_source(s); + + memcpy(buf->mem, src->cur_slide->imgdata, src->cur_slide->imgsize); + buf->bytesused = src->cur_slide->imgsize; + + if (src->cur_slide == list_last_entry(&src->slides, struct slide, list)) + src->cur_slide = list_first_entry(&src->slides, struct slide, list); + else + src->cur_slide = list_next_entry(&src->cur_slide->list, struct slide, list); + + /* + * Wait for the timer to elapse to ensure that our configured frame rate + * is adhered to. + */ + if (src->streaming) + timer_wait(src->timer); +} + +static const struct video_source_ops slideshow_source_ops = { + .destroy = slideshow_source_destroy, + .set_format = slideshow_source_set_format, + .set_frame_rate = slideshow_source_set_frame_rate, + .free_buffers = slideshow_source_free_buffers, + .stream_on = slideshow_source_stream_on, + .stream_off = slideshow_source_stream_off, + .queue_buffer = NULL, + .fill_buffer = slideshow_source_fill_buffer, +}; + +struct video_source *slideshow_video_source_create(const char *img_dir) +{ + struct slideshow_source *src; + + if (img_dir == NULL) + return NULL; + + if (strlen(img_dir) > 31) + return NULL; + + src = malloc(sizeof *src); + if (!src) + return NULL; + + memset(src, 0, sizeof *src); + src->src.ops = &slideshow_source_ops; + + strncpy(src->img_dir, img_dir, sizeof(src->img_dir)); + + src->timer = timer_new(); + if (!src->timer) + goto err_free_src; + + list_init(&src->slides); + + return &src->src; + +err_free_src: + free(src); + return NULL; +} + +void slideshow_video_source_init(struct video_source *s, struct events *events) +{ + struct slideshow_source *src = to_slideshow_source(s); + + src->src.events = events; +} |