mirror of
https://gitee.com/bianbu-linux/usb-gadget
synced 2025-04-18 20:34:44 -04:00
initial commit
This commit is contained in:
commit
ccf48438f7
44 changed files with 9687 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
build
|
||||
src/*.o
|
||||
lib/*.o
|
||||
*.build
|
||||
*.bak
|
||||
*.a
|
||||
*.o
|
||||
*.so
|
||||
uvc-gadget-new
|
62
Makefile
Normal file
62
Makefile
Normal file
|
@ -0,0 +1,62 @@
|
|||
INSTALL = install
|
||||
PREFIX = $(HUMBIRD_ROOTFS_DIR)/usr
|
||||
SBINDIR = $(PREFIX)/bin
|
||||
|
||||
TARGET = uvc-gadget-new
|
||||
INCLUDES += -I./lib \
|
||||
-I./include \
|
||||
-I./include/uvcgadget \
|
||||
-L.
|
||||
#LDFLAGS += -DDEBUG
|
||||
|
||||
SRCS := src/main.c
|
||||
LIBSRCS := lib/configfs.c \
|
||||
lib/events.c \
|
||||
lib/jpg-source.c \
|
||||
lib/slideshow-source.c \
|
||||
lib/stream.c \
|
||||
lib/test-source.c \
|
||||
lib/timer.c \
|
||||
lib/uvc.c \
|
||||
lib/v4l2.c \
|
||||
lib/v4l2-source.c \
|
||||
lib/video-buffers.c \
|
||||
lib/video-source.c
|
||||
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
||||
LIBOBJS = $(LIBSRCS:.c=.o)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $(LOCAL_CFLAGS) $(INCLUDES) -c -o $@ $<
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
libuvcgadget.a: $(LIBOBJS)
|
||||
ar r $@ $(LIBOBJS)
|
||||
|
||||
LOCAL_CFLAGS := -O2 -g -Wimplicit-function-declaration -Wno-unused-parameter
|
||||
LOCAL_CFLAGS += -D_GNU_SOURCE
|
||||
|
||||
$(TARGET): $(OBJS) libuvcgadget.a
|
||||
$(CC) $(LDFLAGS) $(INCLUDES) $(OBJS) -luvcgadget -o $@
|
||||
|
||||
install:
|
||||
$(INSTALL) -d $(SBINDIR)
|
||||
$(INSTALL) -m 755 -s --strip-program=$(STRIP) $(TARGET) $(SBINDIR)/
|
||||
$(INSTALL) -m 777 scripts/uvc-gadget-setup.sh $(SBINDIR)/uvc-gadget-setup
|
||||
$(INSTALL) -m 777 scripts/gadget-setup.sh $(SBINDIR)/gadget-setup
|
||||
|
||||
clean:
|
||||
rm -f lib/*.o
|
||||
rm -f src/*.o
|
||||
rm -f $(TARGET)
|
||||
rm -f libuvcgadget.a
|
||||
|
||||
distclean: clean
|
||||
-rm -rf .stamp_*
|
||||
|
||||
uninstall:
|
||||
-rm -rf $(SBINDIR)/$(TARGET)
|
||||
|
||||
.PHONY:all clean install uninstall $(TARGET)
|
28
README.md
Normal file
28
README.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# uvcgadget - UVC gadget C library
|
||||
|
||||
uvcgadget is a pure C library that implements handling of UVC gadget functions.
|
||||
|
||||
## Utilities
|
||||
|
||||
- uvc-gadget - Sample test application
|
||||
|
||||
## Build instructions:
|
||||
|
||||
To compile:
|
||||
|
||||
```
|
||||
$ meson build
|
||||
$ ninja -C build
|
||||
```
|
||||
|
||||
## Cross compiling instructions:
|
||||
|
||||
Cross compilation can be managed by meson. Please read the directions at
|
||||
https://mesonbuild.com/Cross-compilation.html for detailed guidance on using
|
||||
meson.
|
||||
|
||||
In brief summary:
|
||||
```
|
||||
$ meson build --cross <meson cross file>
|
||||
$ ninja -C build
|
||||
```
|
185
include/compat/glob.h
Normal file
185
include/compat/glob.h
Normal file
|
@ -0,0 +1,185 @@
|
|||
/* Copyright (C) 1991-2017 Free Software Foundation, Inc.
|
||||
This file is part of the GNU C Library.
|
||||
|
||||
The GNU C Library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
The GNU C Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the GNU C Library; if not, see
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef _GLOB_H
|
||||
#define _GLOB_H 1
|
||||
|
||||
#ifndef __THROW
|
||||
#define __THROW
|
||||
#endif
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/* We need `size_t' for the following definitions. */
|
||||
#ifndef __size_t
|
||||
typedef __SIZE_TYPE__ __size_t;
|
||||
# if defined __USE_XOPEN || defined __USE_XOPEN2K8
|
||||
typedef __SIZE_TYPE__ size_t;
|
||||
# endif
|
||||
#else
|
||||
/* The GNU CC stddef.h version defines __size_t as empty. We need a real
|
||||
definition. */
|
||||
# undef __size_t
|
||||
# define __size_t size_t
|
||||
#endif
|
||||
|
||||
/* Bits set in the FLAGS argument to `glob'. */
|
||||
#define GLOB_ERR (1 << 0)/* Return on read errors. */
|
||||
#define GLOB_MARK (1 << 1)/* Append a slash to each name. */
|
||||
#define GLOB_NOSORT (1 << 2)/* Don't sort the names. */
|
||||
#define GLOB_DOOFFS (1 << 3)/* Insert PGLOB->gl_offs NULLs. */
|
||||
#define GLOB_NOCHECK (1 << 4)/* If nothing matches, return the pattern. */
|
||||
#define GLOB_APPEND (1 << 5)/* Append to results of a previous call. */
|
||||
#define GLOB_NOESCAPE (1 << 6)/* Backslashes don't quote metacharacters. */
|
||||
#define GLOB_PERIOD (1 << 7)/* Leading `.' can be matched by metachars. */
|
||||
|
||||
#if !defined __USE_POSIX2 || defined __USE_MISC
|
||||
# define GLOB_MAGCHAR (1 << 8)/* Set in gl_flags if any metachars seen. */
|
||||
# define GLOB_ALTDIRFUNC (1 << 9)/* Use gl_opendir et al functions. */
|
||||
# define GLOB_BRACE (1 << 10)/* Expand "{a,b}" to "a" "b". */
|
||||
# define GLOB_NOMAGIC (1 << 11)/* If no magic chars, return the pattern. */
|
||||
# define GLOB_TILDE (1 << 12)/* Expand ~user and ~ to home directories. */
|
||||
# define GLOB_ONLYDIR (1 << 13)/* Match only directories. */
|
||||
# define GLOB_TILDE_CHECK (1 << 14)/* Like GLOB_TILDE but return an error
|
||||
if the user name is not available. */
|
||||
# define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \
|
||||
GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \
|
||||
GLOB_PERIOD|GLOB_ALTDIRFUNC|GLOB_BRACE| \
|
||||
GLOB_NOMAGIC|GLOB_TILDE|GLOB_ONLYDIR|GLOB_TILDE_CHECK)
|
||||
#else
|
||||
# define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \
|
||||
GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \
|
||||
GLOB_PERIOD)
|
||||
#endif
|
||||
|
||||
/* Error returns from `glob'. */
|
||||
#define GLOB_NOSPACE 1 /* Ran out of memory. */
|
||||
#define GLOB_ABORTED 2 /* Read error. */
|
||||
#define GLOB_NOMATCH 3 /* No matches found. */
|
||||
#define GLOB_NOSYS 4 /* Not implemented. */
|
||||
#ifdef __USE_GNU
|
||||
/* Previous versions of this file defined GLOB_ABEND instead of
|
||||
GLOB_ABORTED. Provide a compatibility definition here. */
|
||||
# define GLOB_ABEND GLOB_ABORTED
|
||||
#endif
|
||||
|
||||
/* Structure describing a globbing run. */
|
||||
#ifdef __USE_GNU
|
||||
struct stat;
|
||||
#endif
|
||||
typedef struct
|
||||
{
|
||||
__size_t gl_pathc; /* Count of paths matched by the pattern. */
|
||||
char **gl_pathv; /* List of matched pathnames. */
|
||||
__size_t gl_offs; /* Slots to reserve in `gl_pathv'. */
|
||||
int gl_flags; /* Set to FLAGS, maybe | GLOB_MAGCHAR. */
|
||||
|
||||
/* If the GLOB_ALTDIRFUNC flag is set, the following functions
|
||||
are used instead of the normal file access functions. */
|
||||
void (*gl_closedir) (void *);
|
||||
#ifdef __USE_GNU
|
||||
struct dirent *(*gl_readdir) (void *);
|
||||
#else
|
||||
void *(*gl_readdir) (void *);
|
||||
#endif
|
||||
void *(*gl_opendir) (const char *);
|
||||
#ifdef __USE_GNU
|
||||
int (*gl_lstat) (const char *__restrict, struct stat *__restrict);
|
||||
int (*gl_stat) (const char *__restrict, struct stat *__restrict);
|
||||
#else
|
||||
int (*gl_lstat) (const char *__restrict, void *__restrict);
|
||||
int (*gl_stat) (const char *__restrict, void *__restrict);
|
||||
#endif
|
||||
} glob_t;
|
||||
|
||||
#ifdef __USE_LARGEFILE64
|
||||
# ifdef __USE_GNU
|
||||
struct stat64;
|
||||
# endif
|
||||
typedef struct
|
||||
{
|
||||
__size_t gl_pathc;
|
||||
char **gl_pathv;
|
||||
__size_t gl_offs;
|
||||
int gl_flags;
|
||||
|
||||
/* If the GLOB_ALTDIRFUNC flag is set, the following functions
|
||||
are used instead of the normal file access functions. */
|
||||
void (*gl_closedir) (void *);
|
||||
# ifdef __USE_GNU
|
||||
struct dirent64 *(*gl_readdir) (void *);
|
||||
# else
|
||||
void *(*gl_readdir) (void *);
|
||||
# endif
|
||||
void *(*gl_opendir) (const char *);
|
||||
# ifdef __USE_GNU
|
||||
int (*gl_lstat) (const char *__restrict, struct stat64 *__restrict);
|
||||
int (*gl_stat) (const char *__restrict, struct stat64 *__restrict);
|
||||
# else
|
||||
int (*gl_lstat) (const char *__restrict, void *__restrict);
|
||||
int (*gl_stat) (const char *__restrict, void *__restrict);
|
||||
# endif
|
||||
} glob64_t;
|
||||
#endif
|
||||
|
||||
/* Do glob searching for PATTERN, placing results in PGLOB.
|
||||
The bits defined above may be set in FLAGS.
|
||||
If a directory cannot be opened or read and ERRFUNC is not nil,
|
||||
it is called with the pathname that caused the error, and the
|
||||
`errno' value from the failing call; if it returns non-zero
|
||||
`glob' returns GLOB_ABEND; if it returns zero, the error is ignored.
|
||||
If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
|
||||
Otherwise, `glob' returns zero. */
|
||||
#if !defined __USE_FILE_OFFSET64
|
||||
extern int glob (const char *__restrict __pattern, int __flags,
|
||||
int (*__errfunc) (const char *, int),
|
||||
glob_t *__restrict __pglob) __THROW;
|
||||
|
||||
/* Free storage allocated in PGLOB by a previous `glob' call. */
|
||||
extern void globfree (glob_t *__pglob) __THROW;
|
||||
#else
|
||||
extern int __REDIRECT_NTH (glob, (const char *__restrict __pattern,
|
||||
int __flags,
|
||||
int (*__errfunc) (const char *, int),
|
||||
glob_t *__restrict __pglob), glob64);
|
||||
|
||||
extern void __REDIRECT_NTH (globfree, (glob_t *__pglob), globfree64);
|
||||
#endif
|
||||
|
||||
#ifdef __USE_LARGEFILE64
|
||||
extern int glob64 (const char *__restrict __pattern, int __flags,
|
||||
int (*__errfunc) (const char *, int),
|
||||
glob64_t *__restrict __pglob) __THROW;
|
||||
|
||||
extern void globfree64 (glob64_t *__pglob) __THROW;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __USE_GNU
|
||||
/* Return nonzero if PATTERN contains any metacharacters.
|
||||
Metacharacters can be quoted with backslashes if QUOTE is nonzero.
|
||||
|
||||
This function is not part of the interface specified by POSIX.2
|
||||
but several programs want to use it. */
|
||||
extern int glob_pattern_p (const char *__pattern, int __quote) __THROW;
|
||||
#endif
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* glob.h */
|
11
include/config.h
Normal file
11
include/config.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#ifndef __UVC_GADGET_CONFIG_H__
|
||||
#define __UVC_GADGET_CONFIG_H__
|
||||
|
||||
//#cmakedefine HAVE_DIRENT_H @HAVE_DIRENT_H@
|
||||
//#cmakedefine HAVE_GLOB @HAVE_GLOB@
|
||||
#define HAVE_DIRENT_H 1
|
||||
#define HAVE_GLOB 1
|
||||
|
||||
#endif
|
20
include/glob.h
Normal file
20
include/glob.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* glob() compatibility
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
#ifndef __UVC_GADGET_GLOB_H__
|
||||
#define __UVC_GADGET_GLOB_H__
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_GLOB
|
||||
#include_next <glob.h>
|
||||
#else
|
||||
#include "compat/glob.h"
|
||||
#endif
|
||||
|
||||
#endif /* __UVC_GADGET_GLOB_H__ */
|
40
include/linux/usb/g_uvc.h
Normal file
40
include/linux/usb/g_uvc.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/* This file is imported from the Linux kernel, don't modify it manually. */
|
||||
/*
|
||||
* g_uvc.h -- USB Video Class Gadget driver API
|
||||
*
|
||||
* Copyright (C) 2009-2010 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_USB_G_UVC_H
|
||||
#define __LINUX_USB_G_UVC_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
|
||||
#define UVC_EVENT_FIRST (V4L2_EVENT_PRIVATE_START + 0)
|
||||
#define UVC_EVENT_CONNECT (V4L2_EVENT_PRIVATE_START + 0)
|
||||
#define UVC_EVENT_DISCONNECT (V4L2_EVENT_PRIVATE_START + 1)
|
||||
#define UVC_EVENT_STREAMON (V4L2_EVENT_PRIVATE_START + 2)
|
||||
#define UVC_EVENT_STREAMOFF (V4L2_EVENT_PRIVATE_START + 3)
|
||||
#define UVC_EVENT_SETUP (V4L2_EVENT_PRIVATE_START + 4)
|
||||
#define UVC_EVENT_DATA (V4L2_EVENT_PRIVATE_START + 5)
|
||||
#define UVC_EVENT_LAST (V4L2_EVENT_PRIVATE_START + 5)
|
||||
|
||||
struct uvc_request_data {
|
||||
__s32 length;
|
||||
__u8 data[60];
|
||||
};
|
||||
|
||||
struct uvc_event {
|
||||
union {
|
||||
enum usb_device_speed speed;
|
||||
struct usb_ctrlrequest req;
|
||||
struct uvc_request_data data;
|
||||
};
|
||||
};
|
||||
|
||||
#define UVCIOC_SEND_RESPONSE _IOW('U', 1, struct uvc_request_data)
|
||||
|
||||
#endif /* __LINUX_USB_G_UVC_H */
|
108
include/uvcgadget/configfs.h
Normal file
108
include/uvcgadget/configfs.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* ConfigFS Gadget device handling
|
||||
*
|
||||
* Copyright (C) 2018 Kieran Bingham
|
||||
*
|
||||
* Contact: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#ifndef __CONFIGFS_H__
|
||||
#define __CONFIGFS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* 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
|
49
include/uvcgadget/events.h
Normal file
49
include/uvcgadget/events.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-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>
|
||||
*/
|
||||
|
||||
#ifndef __EVENTS_H__
|
||||
#define __EVENTS_H__
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#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
|
20
include/uvcgadget/jpg-source.h
Normal file
20
include/uvcgadget/jpg-source.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* JPEG still image video source
|
||||
*
|
||||
* Copyright (C) 2018 Paul Elder
|
||||
*
|
||||
* Contact: Paul Elder <paul.elder@ideasonboard.com>
|
||||
*/
|
||||
#ifndef __JPG_VIDEO_SOURCE_H__
|
||||
#define __JPG_VIDEO_SOURCE_H__
|
||||
|
||||
#include "video-source.h"
|
||||
|
||||
struct events;
|
||||
struct video_source;
|
||||
|
||||
struct video_source *jpg_video_source_create(const char *img_path);
|
||||
void jpg_video_source_init(struct video_source *src, struct events *events);
|
||||
|
||||
#endif /* __JPG_VIDEO_SOURCE_H__ */
|
29
include/uvcgadget/libcamera-source.h
Normal file
29
include/uvcgadget/libcamera-source.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* libcamera video source
|
||||
*
|
||||
* Copyright (C) 2022 Ideas on Board Oy.
|
||||
* Copyright (C) 2022 Kieran Bingham
|
||||
*
|
||||
* Contact: Kieran Bingham <kieran.bingham@ideasonboard.com>
|
||||
*/
|
||||
#ifndef __LIBCAMERA_SOURCE_H__
|
||||
#define __LIBCAMERA_SOURCE_H__
|
||||
|
||||
#include "video-source.h"
|
||||
|
||||
struct events;
|
||||
struct video_source;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct video_source *libcamera_source_create(const char *devname);
|
||||
void libcamera_source_init(struct video_source *src, struct events *events);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif /* __LIBCAMERA_SOURCE_H__ */
|
98
include/uvcgadget/list.h
Normal file
98
include/uvcgadget/list.h
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* 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 <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#ifndef __LIST_H
|
||||
#define __LIST_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
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_next_entry(entry, type, member) \
|
||||
list_entry((entry)->next, 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 */
|
89
include/uvcgadget/mjpeg_encoder.hpp
Normal file
89
include/uvcgadget/mjpeg_encoder.hpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* mjpeg_encoder.hpp - mjpeg video encoder.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
|
||||
struct jpeg_compress_struct;
|
||||
typedef std::function<void(void *, size_t, int64_t, unsigned int)> OutputReadyCallback;
|
||||
|
||||
struct StreamInfo
|
||||
{
|
||||
StreamInfo() : width(0), height(0), stride(0) {}
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int stride;
|
||||
libcamera::PixelFormat pixel_format;
|
||||
std::optional<libcamera::ColorSpace> colour_space;
|
||||
};
|
||||
|
||||
class MjpegEncoder
|
||||
{
|
||||
public:
|
||||
MjpegEncoder();
|
||||
~MjpegEncoder();
|
||||
|
||||
void EncodeBuffer(void *mem, void *dest, unsigned int size,
|
||||
StreamInfo const &info, int64_t timestamp_us,
|
||||
unsigned int cookie);
|
||||
StreamInfo getStreamInfo(libcamera::Stream *stream);
|
||||
void SetOutputReadyCallback(OutputReadyCallback callback) { output_ready_callback_ = callback; }
|
||||
|
||||
private:
|
||||
static const int NUM_ENC_THREADS = 4;
|
||||
|
||||
void encodeThread(int num);
|
||||
|
||||
/*
|
||||
* Handle the output buffers in another thread so as not to block the
|
||||
* encoders. The application can take its time, after which we return
|
||||
* this buffer to the encoder for re-use.
|
||||
*/
|
||||
void outputThread();
|
||||
|
||||
bool abortEncode_;
|
||||
bool abortOutput_;
|
||||
uint64_t index_;
|
||||
|
||||
struct EncodeItem
|
||||
{
|
||||
void *mem;
|
||||
void *dest;
|
||||
unsigned int size;
|
||||
StreamInfo info;
|
||||
int64_t timestamp_us;
|
||||
uint64_t index;
|
||||
unsigned int cookie;
|
||||
};
|
||||
|
||||
std::queue<EncodeItem> encode_queue_;
|
||||
std::mutex encode_mutex_;
|
||||
std::condition_variable encode_cond_var_;
|
||||
std::thread encode_thread_[NUM_ENC_THREADS];
|
||||
void encodeJPEG(struct jpeg_compress_struct &cinfo, EncodeItem &item,
|
||||
uint8_t *&encoded_buffer, size_t &buffer_len);
|
||||
|
||||
struct OutputItem
|
||||
{
|
||||
void *mem;
|
||||
size_t bytes_used;
|
||||
int64_t timestamp_us;
|
||||
uint64_t index;
|
||||
unsigned int cookie;
|
||||
};
|
||||
|
||||
std::queue<OutputItem> output_queue_[NUM_ENC_THREADS];
|
||||
std::mutex output_mutex_;
|
||||
std::condition_variable output_cond_var_;
|
||||
std::thread output_thread_;
|
||||
OutputReadyCallback output_ready_callback_ ;
|
||||
};
|
20
include/uvcgadget/slideshow-source.h
Normal file
20
include/uvcgadget/slideshow-source.h
Normal file
|
@ -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__ */
|
116
include/uvcgadget/stream.h
Normal file
116
include/uvcgadget/stream.h
Normal file
|
@ -0,0 +1,116 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* UVC stream handling
|
||||
*
|
||||
* Copyright (C) 2010-2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#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_set_frame_rate - Set the frame rate for the stream
|
||||
* @stream: the UVC stream
|
||||
* @fps: the frame rate in frames per second
|
||||
*
|
||||
* This function is called from the UVC protocol handler to configure the frame
|
||||
* rate for the video source of 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_frame_rate(struct uvc_stream *stream, unsigned int fps);
|
||||
|
||||
/*
|
||||
* 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__ */
|
20
include/uvcgadget/test-source.h
Normal file
20
include/uvcgadget/test-source.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Test video source
|
||||
*
|
||||
* Copyright (C) 2018 Paul Elder
|
||||
*
|
||||
* Contact: Paul Elder <paul.elder@ideasonboard.com>
|
||||
*/
|
||||
#ifndef __TEST_VIDEO_SOURCE_H__
|
||||
#define __TEST_VIDEO_SOURCE_H__
|
||||
|
||||
#include "video-source.h"
|
||||
|
||||
struct events;
|
||||
struct video_source;
|
||||
|
||||
struct video_source *test_video_source_create(void);
|
||||
void test_video_source_init(struct video_source *src, struct events *events);
|
||||
|
||||
#endif /* __TEST_VIDEO_SOURCE_H__ */
|
59
include/uvcgadget/timer.h
Normal file
59
include/uvcgadget/timer.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* libuvcgadget timer utils
|
||||
*
|
||||
* Copyright (C) 2022 Daniel Scally
|
||||
*
|
||||
* Contact: Daniel Scally <dan.scally@ideasonboard.com>
|
||||
*/
|
||||
|
||||
struct timer;
|
||||
|
||||
/*
|
||||
* timer_new - Create a new timer
|
||||
*
|
||||
* Allocates and returns a new struct timer. This must be configured with
|
||||
* timer_set_fps() and then armed with timer_arm(), following which calls to
|
||||
* timer_wait() will block until the expiration of a period as defined by
|
||||
* timer_set_fps().
|
||||
*
|
||||
* Timers allocated with this function should be removed with timer_destroy()
|
||||
*/
|
||||
struct timer *timer_new(void);
|
||||
|
||||
/*
|
||||
* timer_set_fps - Configure the timer's wait period
|
||||
*
|
||||
* Configure the timer to wait for a period of time such that expirations per
|
||||
* second matches @fps
|
||||
*/
|
||||
void timer_set_fps(struct timer *timer, int fps);
|
||||
|
||||
/*
|
||||
* timer_arm
|
||||
*
|
||||
* Arms the timer such that calls to timer_wait() become blocking until the
|
||||
* expiration of a period.
|
||||
*/
|
||||
int timer_arm(struct timer *timer);
|
||||
|
||||
/*
|
||||
* timer_disarm
|
||||
*
|
||||
* Disarms the timer such that calls to timer_wait() return without blocking.
|
||||
*/
|
||||
int timer_disarm(struct timer *timer);
|
||||
|
||||
/*
|
||||
* timer_wait
|
||||
*
|
||||
* If the timer is armed, block until the expiration of a period
|
||||
*/
|
||||
void timer_wait(struct timer *timer);
|
||||
|
||||
/*
|
||||
* timer_destroy
|
||||
*
|
||||
* Close the timer's file descriptor and free the memory
|
||||
*/
|
||||
void timer_destroy(struct timer *timer);
|
20
include/uvcgadget/v4l2-source.h
Normal file
20
include/uvcgadget/v4l2-source.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* V4L2 video source
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
#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__ */
|
81
include/uvcgadget/video-source.h
Normal file
81
include/uvcgadget/video-source.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Abstract video source
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
#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(*set_frame_rate)(struct video_source *src, unsigned int fps);
|
||||
int(*alloc_buffers)(struct video_source *src, unsigned int nbufs);
|
||||
int(*export_buffers)(struct video_source *src,
|
||||
struct video_buffer_set **buffers);
|
||||
int(*import_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);
|
||||
void(*fill_buffer)(struct video_source *src, struct video_buffer *buf);
|
||||
void(*fill_buffer_ext)(struct video_source *src, struct video_buffer *buf);
|
||||
};
|
||||
|
||||
typedef void(*video_source_buffer_handler_t)(void *, struct video_source *,
|
||||
struct video_buffer *);
|
||||
|
||||
/*
|
||||
* video_source_type - Enumeration of the different kinds of video source
|
||||
* @VIDEO_SOURCE_DMABUF A source that can share data with the sink via a
|
||||
* DMA file descriptor.
|
||||
* @VIDEO_SOURCE_STATIC A source that draws data from an unchanging
|
||||
* buffer such as a .jpg file
|
||||
*/
|
||||
enum video_source_type {
|
||||
VIDEO_SOURCE_DMABUF,
|
||||
VIDEO_SOURCE_STATIC,
|
||||
VIDEO_SOURCE_ENCODED,
|
||||
};
|
||||
|
||||
struct video_source {
|
||||
const struct video_source_ops *ops;
|
||||
struct events *events;
|
||||
video_source_buffer_handler_t handler;
|
||||
void *handler_data;
|
||||
enum video_source_type type;
|
||||
};
|
||||
|
||||
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_set_frame_rate(struct video_source *src, unsigned int fps);
|
||||
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_import_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);
|
||||
void video_source_fill_buffer(struct video_source *src,
|
||||
struct video_buffer *buf);
|
||||
void video_source_fill_buffer_ext(struct video_source *src,
|
||||
struct video_buffer *buf);
|
||||
|
||||
#endif /* __VIDEO_SOURCE_H__ */
|
1747
lib/compat/glob.c
Normal file
1747
lib/compat/glob.c
Normal file
File diff suppressed because it is too large
Load diff
908
lib/configfs.c
Normal file
908
lib/configfs.c
Normal file
|
@ -0,0 +1,908 @@
|
|||
/* 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"
|
||||
#include "uvc-formats.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Path handling and support
|
||||
*/
|
||||
|
||||
static char *path_join(const char *dirname, const char *name)
|
||||
{
|
||||
char *path;
|
||||
int ret;
|
||||
|
||||
ret = asprintf(&path, "%s/%s", dirname, name);
|
||||
|
||||
/*
|
||||
* asprintf returns -1 on allocation or other errors, leaving 'path'
|
||||
* undefined. We shouldn't even call free(path) here. We want to return
|
||||
* NULL on error, so we must manually set it.
|
||||
*/
|
||||
if (ret < 0)
|
||||
path = NULL;
|
||||
|
||||
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, %s: %s\n", f, path, file,
|
||||
strerror(errno));
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ret = read(fd, buf, len);
|
||||
close(fd);
|
||||
|
||||
if (ret < 0) {
|
||||
printf("Failed to read attribute %s: %s\n", f,
|
||||
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;
|
||||
int ret;
|
||||
|
||||
ret = asprintf(&vpath,
|
||||
"/sys/class/udc/%s/device/gadget*/video4linux/video*",
|
||||
udc ? udc : "*");
|
||||
if (!ret)
|
||||
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;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 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 void *memdup(const void *src, size_t size)
|
||||
{
|
||||
void *dst;
|
||||
|
||||
dst = malloc(size);
|
||||
if (!dst)
|
||||
return NULL;
|
||||
memcpy(dst, src, size);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static int parse_legacy_g_webcam(const char *udc,
|
||||
struct uvc_function_config *fc)
|
||||
{
|
||||
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_mount_point - Identify the ConfigFS mount location
|
||||
*
|
||||
* Return a pointer to a newly allocated string containing the fully qualified
|
||||
* path to the ConfigFS mount point if located. Returns NULL if the ConfigFS
|
||||
* mount point can not be identified.
|
||||
*/
|
||||
static char *configfs_mount_point(void)
|
||||
{
|
||||
FILE *mounts;
|
||||
char *line = NULL;
|
||||
char *path = NULL;
|
||||
size_t len = 0;
|
||||
|
||||
mounts = fopen("/proc/mounts", "r");
|
||||
if (mounts == NULL)
|
||||
return NULL;
|
||||
|
||||
while (getline(&line, &len, mounts) != -1) {
|
||||
if (strstr(line, "configfs")) {
|
||||
char *saveptr;
|
||||
char *token;
|
||||
|
||||
/* Obtain the second token. */
|
||||
token = strtok_r(line, " ", &saveptr);
|
||||
token = strtok_r(NULL, " ", &saveptr);
|
||||
|
||||
if (token)
|
||||
path = strdup(token);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(line);
|
||||
fclose(mounts);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 *format;
|
||||
char *configfs;
|
||||
char *func_path;
|
||||
char *path;
|
||||
int ret;
|
||||
|
||||
configfs = configfs_mount_point();
|
||||
if (!configfs)
|
||||
printf("Failed to locate configfs mount point, using default\n");
|
||||
|
||||
/*
|
||||
* 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, '/'))
|
||||
format = "%s/usb_gadget/*/functions/%s";
|
||||
else
|
||||
format = "%s/usb_gadget/%s";
|
||||
|
||||
ret = asprintf(&path, format, configfs ? configfs : "/sys/kernel/config",
|
||||
target);
|
||||
free(configfs);
|
||||
if (!ret)
|
||||
return NULL;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static int configfs_parse_streaming_format(const char *path,
|
||||
struct uvc_function_config_format *format)
|
||||
{
|
||||
struct dirent **entries;
|
||||
char link_target[1024];
|
||||
char *segment;
|
||||
unsigned int i;
|
||||
int n_entries;
|
||||
int ret;
|
||||
|
||||
ret = attribute_read_uint(path, "bFormatIndex", &format->index);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = readlink(path, link_target, sizeof(link_target) - 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
link_target[ret] = '\0';
|
||||
|
||||
/*
|
||||
* Extract the second-to-last path component of the link target,
|
||||
* which contains the format descriptor type name as exposed by
|
||||
* the UVC function driver.
|
||||
*/
|
||||
segment = strrchr(link_target, '/');
|
||||
if (!segment)
|
||||
return -EINVAL;
|
||||
*segment = '\0';
|
||||
segment = strrchr(link_target, '/');
|
||||
if (!segment)
|
||||
return -EINVAL;
|
||||
segment++;
|
||||
|
||||
if (!strcmp(segment, "mjpeg")) {
|
||||
static const uint8_t guid[16] = UVC_GUID_FORMAT_MJPEG;
|
||||
memcpy(format->guid, guid, 16);
|
||||
} else if (!strcmp(segment, "uncompressed")) {
|
||||
ret = attribute_read(path, "guidFormat", format->guid,
|
||||
sizeof(format->guid));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
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 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static int configfs_parse_streaming_header(const char *path,
|
||||
struct uvc_function_config_streaming *cfg)
|
||||
{
|
||||
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 link_filter(const struct dirent *ent)
|
||||
{
|
||||
/* Accept all links. */
|
||||
return ent->d_type == DT_LNK;
|
||||
}
|
||||
|
||||
static int configfs_parse_streaming(const char *path,
|
||||
struct uvc_function_config_streaming *cfg)
|
||||
{
|
||||
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;
|
||||
}
|
182
lib/events.c
Normal file
182
lib/events.c
Normal file
|
@ -0,0 +1,182 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-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;
|
||||
|
||||
list_for_each_entry(event, &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);
|
||||
}
|
||||
}
|
206
lib/jpg-source.c
Normal file
206
lib/jpg-source.c
Normal file
|
@ -0,0 +1,206 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* JPEG still image video source
|
||||
*
|
||||
* Copyright (C) 2018 Paul Elder
|
||||
*
|
||||
* Contact: Paul Elder <paul.elder@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "timer.h"
|
||||
#include "tools.h"
|
||||
#include "v4l2.h"
|
||||
#include "jpg-source.h"
|
||||
#include "video-buffers.h"
|
||||
|
||||
struct jpg_source {
|
||||
struct video_source src;
|
||||
|
||||
unsigned int imgsize;
|
||||
void *imgdata;
|
||||
|
||||
struct timer *timer;
|
||||
bool streaming;
|
||||
};
|
||||
|
||||
#define to_jpg_source(s) container_of(s, struct jpg_source, src)
|
||||
|
||||
static void jpg_source_destroy(struct video_source *s)
|
||||
{
|
||||
struct jpg_source *src = to_jpg_source(s);
|
||||
|
||||
if (src->imgdata)
|
||||
free(src->imgdata);
|
||||
|
||||
timer_destroy(src->timer);
|
||||
|
||||
free(src);
|
||||
}
|
||||
|
||||
static int jpg_source_set_format(struct video_source *s __attribute__((unused)),
|
||||
struct v4l2_pix_format *fmt)
|
||||
{
|
||||
if (fmt->pixelformat != v4l2_fourcc('M', 'J', 'P', 'G')) {
|
||||
printf("jpg-source: unsupported fourcc\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jpg_source_set_frame_rate(struct video_source *s, unsigned int fps)
|
||||
{
|
||||
struct jpg_source *src = to_jpg_source(s);
|
||||
|
||||
timer_set_fps(src->timer, fps);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jpg_source_free_buffers(struct video_source *s __attribute__((unused)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jpg_source_stream_on(struct video_source *s)
|
||||
{
|
||||
struct jpg_source *src = to_jpg_source(s);
|
||||
int ret;
|
||||
|
||||
ret = timer_arm(src->timer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
src->streaming = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int jpg_source_stream_off(struct video_source *s)
|
||||
{
|
||||
struct jpg_source *src = to_jpg_source(s);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* No error check here, because we want to flag that streaming is over
|
||||
* even if the timer is still running due to the failure.
|
||||
*/
|
||||
ret = timer_disarm(src->timer);
|
||||
src->streaming = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void jpg_source_fill_buffer(struct video_source *s,
|
||||
struct video_buffer *buf)
|
||||
{
|
||||
struct jpg_source *src = to_jpg_source(s);
|
||||
unsigned int size;
|
||||
|
||||
/*
|
||||
* Nothing currently stops the user from providing a source file which is
|
||||
* larger than the buffer size calculated from the format we have set.
|
||||
* Clamp the size of the buffer we copy to be sure we don't overflow the
|
||||
* buffer we was allocated to receive it.
|
||||
*/
|
||||
size = min(src->imgsize, buf->size);
|
||||
memcpy(buf->mem, src->imgdata, size);
|
||||
buf->bytesused = size;
|
||||
|
||||
/*
|
||||
* 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 jpg_source_ops = {
|
||||
.destroy = jpg_source_destroy,
|
||||
.set_format = jpg_source_set_format,
|
||||
.set_frame_rate = jpg_source_set_frame_rate,
|
||||
.alloc_buffers = NULL,
|
||||
.export_buffers = NULL,
|
||||
.free_buffers = jpg_source_free_buffers,
|
||||
.stream_on = jpg_source_stream_on,
|
||||
.stream_off = jpg_source_stream_off,
|
||||
.queue_buffer = NULL,
|
||||
.fill_buffer = jpg_source_fill_buffer,
|
||||
.fill_buffer_ext = NULL,
|
||||
};
|
||||
|
||||
struct video_source *jpg_video_source_create(const char *img_path)
|
||||
{
|
||||
struct jpg_source *src;
|
||||
int fd = -1;
|
||||
int ret;
|
||||
|
||||
printf("using jpg video source\n");
|
||||
|
||||
if (img_path == NULL)
|
||||
return NULL;
|
||||
|
||||
src = malloc(sizeof *src);
|
||||
if (!src)
|
||||
return NULL;
|
||||
|
||||
memset(src, 0, sizeof *src);
|
||||
src->src.ops = &jpg_source_ops;
|
||||
src->src.type = VIDEO_SOURCE_STATIC;
|
||||
|
||||
fd = open(img_path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
printf("Unable to open MJPEG image '%s'\n", img_path);
|
||||
goto err_free_src;
|
||||
}
|
||||
|
||||
src->imgsize = lseek(fd, 0, SEEK_END);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
src->imgdata = malloc(src->imgsize);
|
||||
if (src->imgdata == NULL) {
|
||||
printf("Unable to allocate memory for MJPEG image\n");
|
||||
goto err_close_fd;
|
||||
}
|
||||
|
||||
ret = read(fd, src->imgdata, src->imgsize);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "error reading data from %s: %d\n", img_path, errno);
|
||||
goto err_free_imgdata;
|
||||
}
|
||||
|
||||
src->timer = timer_new();
|
||||
if (!src->timer)
|
||||
goto err_free_imgdata;
|
||||
|
||||
close(fd);
|
||||
|
||||
return &src->src;
|
||||
|
||||
err_free_imgdata:
|
||||
free(src->imgdata);
|
||||
err_close_fd:
|
||||
close(fd);
|
||||
err_free_src:
|
||||
free(src);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void jpg_video_source_init(struct video_source *s, struct events *events)
|
||||
{
|
||||
struct jpg_source *src = to_jpg_source(s);
|
||||
|
||||
src->src.events = events;
|
||||
}
|
603
lib/libcamera-source.cpp
Normal file
603
lib/libcamera-source.cpp
Normal file
|
@ -0,0 +1,603 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* libcamera source
|
||||
*
|
||||
* Copyright (C) 2022 Ideas on Board Oy
|
||||
*
|
||||
* Contact: Daniel Scally <dan.scally@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <memory.h>
|
||||
#include <queue>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <map>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <libcamera/libcamera.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mjpeg_encoder.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "events.h"
|
||||
#include "libcamera-source.h"
|
||||
#include "tools.h"
|
||||
#include "video-buffers.h"
|
||||
}
|
||||
|
||||
using namespace libcamera;
|
||||
using namespace std::placeholders;
|
||||
|
||||
#define to_libcamera_source(s) container_of(s, struct libcamera_source, src)
|
||||
|
||||
struct libcamera_source {
|
||||
struct video_source src;
|
||||
|
||||
std::unique_ptr<CameraManager> cm;
|
||||
std::unique_ptr<CameraConfiguration> config;
|
||||
std::shared_ptr<Camera> camera;
|
||||
ControlList controls;
|
||||
|
||||
FrameBufferAllocator *allocator;
|
||||
std::vector<std::unique_ptr<Request>> requests;
|
||||
std::queue<Request *> completed_requests;
|
||||
int pfds[2];
|
||||
|
||||
MjpegEncoder *encoder;
|
||||
std::unordered_map<FrameBuffer *, Span<uint8_t>> mapped_buffers_;
|
||||
|
||||
struct video_buffer_set buffers;
|
||||
|
||||
void mapBuffer(const std::unique_ptr<FrameBuffer> &buffer);
|
||||
void requestComplete(Request *request);
|
||||
void outputReady(void *mem, size_t bytesused, int64_t timestamp, unsigned int cookie);
|
||||
};
|
||||
|
||||
void libcamera_source::mapBuffer(const std::unique_ptr<FrameBuffer> &buffer)
|
||||
{
|
||||
size_t buffer_size = 0;
|
||||
|
||||
for (unsigned int i = 0; i < buffer->planes().size(); i++) {
|
||||
const FrameBuffer::Plane &plane = buffer->planes()[i];
|
||||
buffer_size += plane.length;
|
||||
|
||||
if (i == buffer->planes().size() - 1 ||
|
||||
plane.fd.get() != buffer->planes()[i + 1].fd.get()) {
|
||||
void *memory = mmap(NULL, buffer_size, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, plane.fd.get(), 0);
|
||||
mapped_buffers_[buffer.get()] =
|
||||
Span<uint8_t>(static_cast<uint8_t *>(memory), buffer_size);
|
||||
buffer_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void libcamera_source::requestComplete(Request *request)
|
||||
{
|
||||
if (request->status() == Request::RequestCancelled)
|
||||
return;
|
||||
|
||||
completed_requests.push(request);
|
||||
|
||||
/*
|
||||
* We want to hand off to the event loop to do any further processing,
|
||||
* which we can achieve by simply writing to the end of the pipe since
|
||||
* the loop is polling the other end. Once the event loop picks up this
|
||||
* write it will run libcamera_source_video_process().
|
||||
*/
|
||||
write(pfds[1], "x", 1);
|
||||
};
|
||||
|
||||
void libcamera_source::outputReady(void *mem, size_t bytesused, int64_t timestamp, unsigned int cookie)
|
||||
{
|
||||
struct video_buffer buffer;
|
||||
|
||||
buffer.index = cookie;
|
||||
buffer.mem = mem;
|
||||
buffer.bytesused = bytesused;
|
||||
buffer.timestamp.tv_sec = timestamp / 1000000;
|
||||
buffer.timestamp.tv_usec = timestamp % 1000000;
|
||||
|
||||
src.handler(src.handler_data, &src, &buffer);
|
||||
}
|
||||
|
||||
static void libcamera_source_video_process(void *d)
|
||||
{
|
||||
struct libcamera_source *src = (struct libcamera_source *)d;
|
||||
Stream *stream = src->config->at(0).stream();
|
||||
struct video_buffer buffer;
|
||||
Request *request;
|
||||
char buf;
|
||||
|
||||
/*
|
||||
* We need to perform a read here or the fd will stay active each time
|
||||
* the event loop cycles.
|
||||
*/
|
||||
read(src->pfds[0], &buf, 1);
|
||||
|
||||
if (src->completed_requests.empty())
|
||||
return;
|
||||
|
||||
request = src->completed_requests.front();
|
||||
src->completed_requests.pop();
|
||||
|
||||
/* We have only a single buffer per request, so just pick the first */
|
||||
FrameBuffer *framebuf = request->buffers().begin()->second;
|
||||
|
||||
/*
|
||||
* If we have an encoder, then rather than simply detailing the buffer
|
||||
* here and passing it back to the sink we need to queue it to the
|
||||
* encoder. The encoder will queue that buffer to the sink after
|
||||
* compression.
|
||||
*/
|
||||
if (src->src.type == VIDEO_SOURCE_ENCODED) {
|
||||
int64_t timestamp_ns = framebuf->metadata().timestamp;
|
||||
StreamInfo info = src->encoder->getStreamInfo(stream);
|
||||
auto span = src->mapped_buffers_.find(framebuf);
|
||||
void *mem = span->second.data();
|
||||
void *dest = src->buffers.buffers[request->cookie()].mem;
|
||||
unsigned int size = span->second.size();
|
||||
|
||||
src->encoder->EncodeBuffer(mem, dest, size, info, timestamp_ns / 1000, request->cookie());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.index = request->cookie();
|
||||
|
||||
/* TODO: Correct this for formats libcamera treats as multiplanar */
|
||||
buffer.size = framebuf->planes()[0].length;
|
||||
buffer.mem = NULL;
|
||||
buffer.bytesused = framebuf->metadata().planes()[0].bytesused;
|
||||
buffer.timestamp.tv_usec = framebuf->metadata().timestamp;
|
||||
buffer.error = false;
|
||||
|
||||
src->src.handler(src->src.handler_data, &src->src, &buffer);
|
||||
}
|
||||
|
||||
static void libcamera_source_destroy(struct video_source *s)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
|
||||
src->camera->requestCompleted.disconnect(src);
|
||||
|
||||
/* Closing the event notification file descriptors */
|
||||
close(src->pfds[0]);
|
||||
close(src->pfds[1]);
|
||||
|
||||
src->camera->release();
|
||||
src->camera.reset();
|
||||
src->cm->stop();
|
||||
delete src;
|
||||
}
|
||||
|
||||
static int libcamera_source_set_format(struct video_source *s,
|
||||
struct v4l2_pix_format *fmt)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
StreamConfiguration &streamConfig = src->config->at(0);
|
||||
__u32 chosen_pixelformat = fmt->pixelformat;
|
||||
|
||||
streamConfig.size.width = fmt->width;
|
||||
streamConfig.size.height = fmt->height;
|
||||
streamConfig.pixelFormat = PixelFormat(chosen_pixelformat);
|
||||
|
||||
src->config->validate();
|
||||
|
||||
#ifdef CONFIG_CAN_ENCODE
|
||||
/*
|
||||
* If the user requests MJPEG but the camera can't supply it, try again
|
||||
* with YUV420 and initialise an MjpegEncoder to compress the data.
|
||||
*/
|
||||
if (chosen_pixelformat == V4L2_PIX_FMT_MJPEG &&
|
||||
streamConfig.pixelFormat.fourcc() != chosen_pixelformat) {
|
||||
std::cout << "MJPEG format not natively supported; encoding YUV420" << std::endl;
|
||||
|
||||
src->encoder = new MjpegEncoder();
|
||||
src->encoder->SetOutputReadyCallback(std::bind(&libcamera_source::outputReady, src, _1, _2, _3, _4));
|
||||
|
||||
streamConfig.pixelFormat = PixelFormat(V4L2_PIX_FMT_YUV420);
|
||||
src->src.type = VIDEO_SOURCE_ENCODED;
|
||||
|
||||
src->config->validate();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fmt->pixelformat != streamConfig.pixelFormat.fourcc())
|
||||
std::cerr << "Warning: set_format: Requested format unavailable" << std::endl;
|
||||
|
||||
std::cout << "setting format to " << streamConfig.toString() << std::endl;
|
||||
|
||||
/*
|
||||
* No .configure() call at this stage, because we need to pick up the
|
||||
* number of buffers to use later on so we'd need to call it then too.
|
||||
*/
|
||||
|
||||
fmt->width = streamConfig.size.width;
|
||||
fmt->height = streamConfig.size.height;
|
||||
fmt->pixelformat = src->encoder ? V4L2_PIX_FMT_MJPEG : streamConfig.pixelFormat.fourcc();
|
||||
fmt->field = V4L2_FIELD_ANY;
|
||||
|
||||
/* TODO: Can we use libcamera helpers to get image size / stride? */
|
||||
fmt->sizeimage = fmt->width * fmt->height * 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_set_frame_rate(struct video_source *s, unsigned int fps)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
int64_t frame_time = 1000000 / fps;
|
||||
|
||||
src->controls.set(controls::FrameDurationLimits,
|
||||
Span<const int64_t, 2>({ frame_time, frame_time }));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_alloc_buffers(struct video_source *s, unsigned int nbufs)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
StreamConfiguration &streamConfig = src->config->at(0);
|
||||
int ret;
|
||||
|
||||
streamConfig.bufferCount = nbufs;
|
||||
ret = src->camera->configure(src->config.get());
|
||||
if (ret) {
|
||||
std::cerr << "failed to configure the camera" << std::endl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Stream *stream = src->config->at(0).stream();
|
||||
FrameBufferAllocator *allocator;
|
||||
|
||||
allocator = new FrameBufferAllocator(src->camera);
|
||||
|
||||
ret = allocator->allocate(stream);
|
||||
if (ret < 0) {
|
||||
std::cerr << "failed to allocate buffers" << std::endl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
src->allocator = allocator;
|
||||
|
||||
const std::vector<std::unique_ptr<FrameBuffer>> &buffers = allocator->buffers(stream);
|
||||
src->buffers.nbufs = buffers.size();
|
||||
|
||||
if (src->src.type == VIDEO_SOURCE_ENCODED) {
|
||||
for (const std::unique_ptr<FrameBuffer> &buffer : buffers)
|
||||
src->mapBuffer(buffer);
|
||||
}
|
||||
|
||||
src->buffers.buffers = (video_buffer *)calloc(src->buffers.nbufs, sizeof(*src->buffers.buffers));
|
||||
if (!src->buffers.buffers) {
|
||||
std::cerr << "failed to allocate buffers" << std::endl;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < buffers.size(); ++i) {
|
||||
src->buffers.buffers[i].index = i;
|
||||
src->buffers.buffers[i].dmabuf = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int libcamera_source_export_buffers(struct video_source *s,
|
||||
struct video_buffer_set **bufs)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
Stream *stream = src->config->at(0).stream();
|
||||
const std::vector<std::unique_ptr<FrameBuffer>> &buffers = src->allocator->buffers(stream);
|
||||
struct video_buffer_set *vid_buf_set;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < buffers.size(); i++) {
|
||||
const std::unique_ptr<FrameBuffer> &buffer = buffers[i];
|
||||
|
||||
src->buffers.buffers[i].size = buffer->planes()[0].length;
|
||||
src->buffers.buffers[i].dmabuf = buffer->planes()[0].fd.get();
|
||||
}
|
||||
|
||||
vid_buf_set = video_buffer_set_new(buffers.size());
|
||||
if (!vid_buf_set)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < src->buffers.nbufs; ++i) {
|
||||
struct video_buffer *buffer = &src->buffers.buffers[i];
|
||||
|
||||
vid_buf_set->buffers[i].size = buffer->size;
|
||||
vid_buf_set->buffers[i].dmabuf = buffer->dmabuf;
|
||||
}
|
||||
|
||||
*bufs = vid_buf_set;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_import_buffers(struct video_source *s,
|
||||
struct video_buffer_set *buffers)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
|
||||
for (unsigned int i = 0; i < buffers->nbufs; i++)
|
||||
src->buffers.buffers[i].mem = buffers->buffers[i].mem;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_free_buffers(struct video_source *s)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
Stream *stream = src->config->at(0).stream();
|
||||
|
||||
for (auto &[buf, span] : src->mapped_buffers_)
|
||||
munmap(span.data(), span.size());
|
||||
|
||||
src->mapped_buffers_.clear();
|
||||
|
||||
src->allocator->free(stream);
|
||||
delete src->allocator;
|
||||
free(src->buffers.buffers);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_stream_on(struct video_source *s)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
Stream *stream = src->config->at(0).stream();
|
||||
int ret;
|
||||
|
||||
const std::vector<std::unique_ptr<FrameBuffer>> &buffers = src->allocator->buffers(stream);
|
||||
|
||||
for (unsigned int i = 0; i < buffers.size(); ++i) {
|
||||
std::unique_ptr<Request> request = src->camera->createRequest(i);
|
||||
if (!request) {
|
||||
std::cerr << "failed to create request" << std::endl;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
const std::unique_ptr<FrameBuffer> &buffer = buffers[i];
|
||||
ret = request->addBuffer(stream, buffer.get());
|
||||
if (ret < 0) {
|
||||
std::cerr << "failed to set buffer for request" << std::endl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
src->requests.push_back(std::move(request));
|
||||
}
|
||||
|
||||
ret = src->camera->start(&src->controls);
|
||||
if (ret) {
|
||||
std::cerr << "failed to start camera" << std::endl;
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (std::unique_ptr<Request> &request : src->requests) {
|
||||
ret = src->camera->queueRequest(request.get());
|
||||
if (ret) {
|
||||
std::cerr << "failed to queue request" << std::endl;
|
||||
src->camera->stop();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given our event handling code is designed for V4L2 file descriptors
|
||||
* and lacks a way to trigger an event manually, we're using a pipe so
|
||||
* that we can watch the read end and write to the other end when
|
||||
* requestComplete() is ran.
|
||||
*/
|
||||
events_watch_fd(src->src.events, src->pfds[0], EVENT_READ,
|
||||
libcamera_source_video_process, src);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_stream_off(struct video_source *s)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
|
||||
src->camera->stop();
|
||||
events_unwatch_fd(src->src.events, src->pfds[0], EVENT_READ);
|
||||
src->requests.clear();
|
||||
|
||||
while (!src->completed_requests.empty())
|
||||
src->completed_requests.pop();
|
||||
|
||||
if (src->src.type == VIDEO_SOURCE_ENCODED) {
|
||||
delete src->encoder;
|
||||
src->encoder = nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to reinitialise this here, as if the user selected an
|
||||
* unsupported MJPEG format the encoding routine will have overriden
|
||||
* this setting.
|
||||
*/
|
||||
src->src.type = VIDEO_SOURCE_DMABUF;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int libcamera_source_queue_buffer(struct video_source *s,
|
||||
struct video_buffer *buf)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
|
||||
for (std::unique_ptr<Request> &r : src->requests) {
|
||||
if (r->cookie() == buf->index) {
|
||||
r->reuse(Request::ReuseBuffers);
|
||||
src->camera->queueRequest(r.get());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct video_source_ops libcamera_source_ops = {
|
||||
.destroy = libcamera_source_destroy,
|
||||
.set_format = libcamera_source_set_format,
|
||||
.set_frame_rate = libcamera_source_set_frame_rate,
|
||||
.alloc_buffers = libcamera_source_alloc_buffers,
|
||||
.export_buffers = libcamera_source_export_buffers,
|
||||
.import_buffers = libcamera_source_import_buffers,
|
||||
.free_buffers = libcamera_source_free_buffers,
|
||||
.stream_on = libcamera_source_stream_on,
|
||||
.stream_off = libcamera_source_stream_off,
|
||||
.queue_buffer = libcamera_source_queue_buffer,
|
||||
.fill_buffer = NULL,
|
||||
.fill_buffer_ext = NULL,
|
||||
};
|
||||
|
||||
std::string cameraName(Camera *camera)
|
||||
{
|
||||
const ControlList &props = camera->properties();
|
||||
std::string name;
|
||||
|
||||
const auto &location = props.get(properties::Location);
|
||||
if (location) {
|
||||
switch (*location) {
|
||||
case properties::CameraLocationFront:
|
||||
name = "Internal Front Camera";
|
||||
break;
|
||||
case properties::CameraLocationBack:
|
||||
name = "Internal back camera";
|
||||
break;
|
||||
case properties::CameraLocationExternal:
|
||||
name = "External camera";
|
||||
const auto &model = props.get(properties::Model);
|
||||
if (model)
|
||||
name = *model;
|
||||
break;
|
||||
}
|
||||
}
|
||||
name += " (" + camera->id() + ")";
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
struct video_source *libcamera_source_create(const char *devname)
|
||||
{
|
||||
struct libcamera_source *src;
|
||||
int ret;
|
||||
|
||||
if (!devname) {
|
||||
std::cerr << "No camera identifier was passed" << std::endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
src = new libcamera_source;
|
||||
|
||||
/*
|
||||
* Event handling in libuvcgadget currently depends on select(), but
|
||||
* unlike a V4L2 devnode there's no file descriptor for completed
|
||||
* libcamera Requests. We'll spoof the events using a pipe for now,
|
||||
* but...
|
||||
*
|
||||
* TODO: Replace event handling with libevent
|
||||
*/
|
||||
|
||||
ret = pipe2(src->pfds, O_NONBLOCK);
|
||||
if (ret) {
|
||||
std::cerr << "failed to create pipe" << std::endl;
|
||||
goto err_free_src;
|
||||
}
|
||||
|
||||
src->src.ops = &libcamera_source_ops;
|
||||
src->src.type = VIDEO_SOURCE_DMABUF;
|
||||
|
||||
src->cm = std::make_unique<CameraManager>();
|
||||
src->cm->start();
|
||||
|
||||
if (src->cm->cameras().empty()) {
|
||||
std::cout << "No cameras were identified on the system" << std::endl;
|
||||
goto err_close_pipe;
|
||||
}
|
||||
|
||||
/* TODO: make a separate way to list libcamera cameras */
|
||||
for (auto const &camera : src->cm->cameras())
|
||||
printf("- %s\n", cameraName(camera.get()).c_str());
|
||||
|
||||
/*
|
||||
* Camera selection is by ID or index. Camera ID's start with a slash.
|
||||
* If the first character is a digit, assume we're indexing, otherwise
|
||||
* treat it as an ID.
|
||||
*/
|
||||
if (std::isdigit(devname[0])) {
|
||||
unsigned long index = std::atoi(devname);
|
||||
|
||||
if (index >= src->cm->cameras().size()) {
|
||||
std::cerr << "No camera at index " << index << std::endl;
|
||||
goto err_close_pipe;
|
||||
}
|
||||
|
||||
src->camera = src->cm->cameras()[index];
|
||||
} else {
|
||||
src->camera = src->cm->get(std::string(devname));
|
||||
if (!src->camera) {
|
||||
std::cerr << "found no camera matching " << devname << std::endl;
|
||||
goto err_close_pipe;
|
||||
}
|
||||
}
|
||||
|
||||
ret = src->camera->acquire();
|
||||
if (ret) {
|
||||
fprintf(stderr, "failed to acquire camera\n");
|
||||
goto err_close_pipe;
|
||||
}
|
||||
|
||||
std::cout << "Using camera " << cameraName(src->camera.get()) << std::endl;
|
||||
|
||||
src->config =
|
||||
src->camera->generateConfiguration( { StreamRole::VideoRecording });
|
||||
if (!src->config) {
|
||||
std::cerr << "failed to generate camera config" << std::endl;
|
||||
goto err_release_camera;
|
||||
}
|
||||
|
||||
src->camera->requestCompleted.connect(src, &libcamera_source::requestComplete);
|
||||
|
||||
{
|
||||
/*
|
||||
* We enable AutoFocus by default if it's supported by the camera.
|
||||
* Keep the infoMap scoped to calm the compiler worrying about
|
||||
* jumping over the reference with the gotos.
|
||||
*/
|
||||
const ControlInfoMap &infoMap = src->camera->controls();
|
||||
if (infoMap.find(&controls::AfMode) != infoMap.end()) {
|
||||
std::cout << "Enabling continuous auto-focus" << std::endl;
|
||||
src->controls.set(controls::AfMode, controls::AfModeContinuous);
|
||||
}
|
||||
}
|
||||
|
||||
return &src->src;
|
||||
|
||||
err_release_camera:
|
||||
src->camera->release();
|
||||
err_close_pipe:
|
||||
close(src->pfds[0]);
|
||||
close(src->pfds[1]);
|
||||
src->cm->stop();
|
||||
err_free_src:
|
||||
delete src;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void libcamera_source_init(struct video_source *s, struct events *events)
|
||||
{
|
||||
struct libcamera_source *src = to_libcamera_source(s);
|
||||
|
||||
src->src.events = events;
|
||||
}
|
217
lib/mjpeg_encoder.cpp
Normal file
217
lib/mjpeg_encoder.cpp
Normal file
|
@ -0,0 +1,217 @@
|
|||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* Copyright (C) 2020, Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* mjpeg_encoder.cpp - mjpeg video encoder.
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <jpeglib.h>
|
||||
|
||||
#include <libcamera/libcamera.h>
|
||||
|
||||
#include "mjpeg_encoder.hpp"
|
||||
|
||||
#if JPEG_LIB_VERSION_MAJOR > 9 || (JPEG_LIB_VERSION_MAJOR == 9 && JPEG_LIB_VERSION_MINOR >= 4)
|
||||
typedef size_t jpeg_mem_len_t;
|
||||
#else
|
||||
typedef unsigned long jpeg_mem_len_t;
|
||||
#endif
|
||||
|
||||
MjpegEncoder::MjpegEncoder()
|
||||
: abortEncode_(false), abortOutput_(false), index_(0)
|
||||
{
|
||||
output_thread_ = std::thread(&MjpegEncoder::outputThread, this);
|
||||
for (int i = 0; i < NUM_ENC_THREADS; i++)
|
||||
encode_thread_[i] = std::thread(std::bind(&MjpegEncoder::encodeThread, this, i));
|
||||
}
|
||||
|
||||
MjpegEncoder::~MjpegEncoder()
|
||||
{
|
||||
abortEncode_ = true;
|
||||
for (int i = 0; i < NUM_ENC_THREADS; i++)
|
||||
encode_thread_[i].join();
|
||||
abortOutput_ = true;
|
||||
output_thread_.join();
|
||||
}
|
||||
|
||||
void MjpegEncoder::EncodeBuffer(void *mem, void *dest, unsigned int size,
|
||||
StreamInfo const &info, int64_t timestamp_us,
|
||||
unsigned int cookie)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(encode_mutex_);
|
||||
EncodeItem item = { mem, dest, size, info, timestamp_us, index_++, cookie };
|
||||
|
||||
encode_queue_.push(item);
|
||||
encode_cond_var_.notify_all();
|
||||
}
|
||||
|
||||
void MjpegEncoder::encodeJPEG(struct jpeg_compress_struct &cinfo, EncodeItem &item,
|
||||
uint8_t *&encoded_buffer, size_t &buffer_len)
|
||||
{
|
||||
cinfo.image_width = item.info.width;
|
||||
cinfo.image_height = item.info.height;
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_YCbCr;
|
||||
cinfo.restart_interval = 0;
|
||||
|
||||
jpeg_set_defaults(&cinfo);
|
||||
cinfo.raw_data_in = TRUE;
|
||||
jpeg_set_quality(&cinfo, 50, TRUE);
|
||||
|
||||
jpeg_mem_len_t jpeg_mem_len = buffer_len;
|
||||
jpeg_mem_dest(&cinfo, &encoded_buffer, &jpeg_mem_len);
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
|
||||
int stride2 = item.info.stride / 2;
|
||||
uint8_t *Y = (uint8_t *)item.mem;
|
||||
uint8_t *U = (uint8_t *)Y + item.info.stride * item.info.height;
|
||||
uint8_t *V = (uint8_t *)U + stride2 * (item.info.height / 2);
|
||||
uint8_t *Y_max = U - item.info.stride;
|
||||
uint8_t *U_max = V - stride2;
|
||||
uint8_t *V_max = U_max + stride2 * (item.info.height / 2);
|
||||
|
||||
JSAMPROW y_rows[16];
|
||||
JSAMPROW u_rows[8];
|
||||
JSAMPROW v_rows[8];
|
||||
|
||||
for (uint8_t *Y_row = Y, *U_row = U, *V_row = V; cinfo.next_scanline < item.info.height;)
|
||||
{
|
||||
for (int i = 0; i < 16; i++, Y_row += item.info.stride)
|
||||
y_rows[i] = std::min(Y_row, Y_max);
|
||||
for (int i = 0; i < 8; i++, U_row += stride2, V_row += stride2) {
|
||||
u_rows[i] = std::min(U_row, U_max);
|
||||
v_rows[i] = std::min(V_row, V_max);
|
||||
}
|
||||
|
||||
JSAMPARRAY rows[] = { y_rows, u_rows, v_rows };
|
||||
jpeg_write_raw_data(&cinfo, rows, 16);
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&cinfo);
|
||||
|
||||
buffer_len = jpeg_mem_len;
|
||||
}
|
||||
|
||||
void MjpegEncoder::encodeThread(int num)
|
||||
{
|
||||
struct jpeg_compress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
EncodeItem encode_item;
|
||||
uint32_t frames = 0;
|
||||
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jpeg_create_compress(&cinfo);
|
||||
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(encode_mutex_);
|
||||
while (true)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
if (abortEncode_ && encode_queue_.empty())
|
||||
{
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
return;
|
||||
}
|
||||
if (!encode_queue_.empty())
|
||||
{
|
||||
encode_item = encode_queue_.front();
|
||||
encode_queue_.pop();
|
||||
break;
|
||||
}
|
||||
else
|
||||
encode_cond_var_.wait_for(lock, 200ms);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *encoded_buffer = (uint8_t *)encode_item.dest;
|
||||
size_t buffer_len = encode_item.size;
|
||||
|
||||
encodeJPEG(cinfo, encode_item, encoded_buffer, buffer_len);
|
||||
|
||||
frames++;
|
||||
|
||||
/*
|
||||
* Don't return buffers until the output thread as that's where
|
||||
* they're in order again.
|
||||
*
|
||||
* We push this encoded buffer to another thread so that our
|
||||
* application can take its time with the data without blocking
|
||||
* the encode process.
|
||||
*/
|
||||
OutputItem output_item = {
|
||||
encoded_buffer,
|
||||
buffer_len,
|
||||
encode_item.timestamp_us,
|
||||
encode_item.index,
|
||||
encode_item.cookie
|
||||
};
|
||||
std::lock_guard<std::mutex> lock(output_mutex_);
|
||||
output_queue_[num].push(output_item);
|
||||
output_cond_var_.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void MjpegEncoder::outputThread()
|
||||
{
|
||||
OutputItem item;
|
||||
uint64_t index = 0;
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(output_mutex_);
|
||||
while (true)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
/*
|
||||
* We look for the thread that's completed the
|
||||
* frame we want next. If we don't find it, we
|
||||
* wait.
|
||||
*
|
||||
* Must also check for an abort signal and if
|
||||
* set, all queues must be empty. This is done
|
||||
* first to ensure all frame callbacks have a
|
||||
* chance to run.
|
||||
*/
|
||||
bool abort = abortOutput_ ? true : false;
|
||||
for (auto &q : output_queue_)
|
||||
{
|
||||
if (abort && !q.empty())
|
||||
abort = false;
|
||||
|
||||
if (!q.empty() && q.front().index == index)
|
||||
{
|
||||
item = q.front();
|
||||
q.pop();
|
||||
goto got_item;
|
||||
}
|
||||
}
|
||||
if (abort)
|
||||
return;
|
||||
|
||||
output_cond_var_.wait_for(lock, 200ms);
|
||||
}
|
||||
}
|
||||
got_item:
|
||||
output_ready_callback_(item.mem, item.bytes_used, item.timestamp_us, item.cookie);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
StreamInfo MjpegEncoder::getStreamInfo(libcamera::Stream *stream)
|
||||
{
|
||||
libcamera::StreamConfiguration const &cfg = stream->configuration();
|
||||
StreamInfo info;
|
||||
info.width = cfg.size.width;
|
||||
info.height = cfg.size.height;
|
||||
info.stride = cfg.stride;
|
||||
info.pixel_format = cfg.pixelFormat;
|
||||
info.colour_space = cfg.colorSpace;
|
||||
return info;
|
||||
}
|
401
lib/slideshow-source.c
Normal file
401
lib/slideshow-source.c
Normal file
|
@ -0,0 +1,401 @@
|
|||
/* 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);
|
||||
unsigned int size;
|
||||
|
||||
/*
|
||||
* Nothing currently stops the user from providing a source file which is
|
||||
* larger than the buffer size calculated from the format we have set.
|
||||
* Clamp the size of the buffer we copy to be sure we don't overflow the
|
||||
* buffer we was allocated to receive it.
|
||||
*/
|
||||
size = min(src->cur_slide->imgsize, buf->size);
|
||||
memcpy(buf->mem, src->cur_slide->imgdata, size);
|
||||
buf->bytesused = size;
|
||||
|
||||
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,
|
||||
.fill_buffer_ext = NULL,
|
||||
};
|
||||
|
||||
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;
|
||||
src->src.type = VIDEO_SOURCE_STATIC;
|
||||
|
||||
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;
|
||||
}
|
360
lib/stream.c
Normal file
360
lib/stream.c
Normal file
|
@ -0,0 +1,360 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* UVC stream handling
|
||||
*
|
||||
* Copyright (C) 2010-2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#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 __attribute__((unused)),
|
||||
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 void uvc_stream_uvc_process_no_buf(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_fill_buffer(stream->src, &buf);
|
||||
video_source_fill_buffer_ext(stream->src, &buf);
|
||||
|
||||
v4l2_queue_buffer(sink, &buf);
|
||||
}
|
||||
|
||||
|
||||
static int uvc_stream_start_alloc(struct uvc_stream *stream)
|
||||
{
|
||||
struct v4l2_device *sink = uvc_v4l2_device(stream->uvc);
|
||||
struct video_buffer_set *buffers = NULL;
|
||||
int ret;
|
||||
|
||||
/* 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_start_no_alloc(struct uvc_stream *stream)
|
||||
{
|
||||
struct v4l2_device *sink = uvc_v4l2_device(stream->uvc);
|
||||
int ret;
|
||||
unsigned int i;
|
||||
|
||||
/* Allocate buffers on the sink. */
|
||||
ret = v4l2_alloc_buffers(sink, V4L2_MEMORY_MMAP, 8);
|
||||
if (ret < 0) {
|
||||
printf("Failed to allocate sink buffers: %s (%d)\n",
|
||||
strerror(-ret), -ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* mmap buffers. */
|
||||
ret = v4l2_mmap_buffers(sink);
|
||||
if (ret < 0) {
|
||||
printf("Failed to query sink buffers: %s (%d)\n",
|
||||
strerror(-ret), -ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Queue buffers to sink. */
|
||||
for (i = 0; i < sink->buffers.nbufs; ++i) {
|
||||
struct video_buffer buf = {
|
||||
.index = i,
|
||||
.size = sink->buffers.buffers[i].size,
|
||||
.mem = sink->buffers.buffers[i].mem,
|
||||
};
|
||||
|
||||
video_source_fill_buffer(stream->src, &buf);
|
||||
ret = v4l2_queue_buffer(sink, &buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Start the source and sink. */
|
||||
video_source_stream_on(stream->src);
|
||||
ret = v4l2_stream_on(sink);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
events_watch_fd(stream->events, sink->fd, EVENT_WRITE,
|
||||
uvc_stream_uvc_process_no_buf, stream);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uvc_stream_start_encoded(struct uvc_stream *stream)
|
||||
{
|
||||
struct v4l2_device *sink = uvc_v4l2_device(stream->uvc);
|
||||
int ret;
|
||||
|
||||
/* Allocate 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;
|
||||
}
|
||||
|
||||
/* Allocate buffers on the sink. */
|
||||
ret = v4l2_alloc_buffers(sink, V4L2_MEMORY_MMAP, 8);
|
||||
if (ret < 0) {
|
||||
printf("Failed to allocate sink buffers: %s (%d)\n",
|
||||
strerror(-ret), -ret);
|
||||
goto error_free_source;
|
||||
}
|
||||
|
||||
/* mmap buffers. */
|
||||
ret = v4l2_mmap_buffers(sink);
|
||||
if (ret < 0) {
|
||||
printf("Failed to query sink buffers: %s (%d)\n",
|
||||
strerror(-ret), -ret);
|
||||
goto error_free_sink;
|
||||
}
|
||||
|
||||
/* Import the sink's buffers to the source */
|
||||
ret = video_source_import_buffers(stream->src, &sink->buffers);
|
||||
if (ret) {
|
||||
printf("Failed to import sink buffers: %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);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int uvc_stream_start(struct uvc_stream *stream)
|
||||
{
|
||||
printf("Starting video stream.\n");
|
||||
|
||||
switch (stream->src->type) {
|
||||
case VIDEO_SOURCE_DMABUF:
|
||||
video_source_set_buffer_handler(stream->src, uvc_stream_source_process,
|
||||
stream);
|
||||
return uvc_stream_start_alloc(stream);
|
||||
case VIDEO_SOURCE_STATIC:
|
||||
return uvc_stream_start_no_alloc(stream);
|
||||
case VIDEO_SOURCE_ENCODED:
|
||||
video_source_set_buffer_handler(stream->src, uvc_stream_source_process,
|
||||
stream);
|
||||
return uvc_stream_start_encoded(stream);
|
||||
default:
|
||||
fprintf(stderr, "invalid video source type\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int uvc_stream_set_frame_rate(struct uvc_stream *stream, unsigned int fps)
|
||||
{
|
||||
printf("=== Setting frame rate to %u fps\n", fps);
|
||||
return video_source_set_frame_rate(stream->src, fps);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
183
lib/test-source.c
Normal file
183
lib/test-source.c
Normal file
|
@ -0,0 +1,183 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* Test video source
|
||||
*
|
||||
* Copyright (C) 2018 Paul Elder
|
||||
*
|
||||
* Contact: Paul Elder <paul.elder@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include "events.h"
|
||||
#include "test-source.h"
|
||||
#include "tools.h"
|
||||
#include "video-buffers.h"
|
||||
|
||||
|
||||
|
||||
#define GREY 0x80b480b4
|
||||
|
||||
#define WHITE 0x80eb80eb
|
||||
#define YELLOW 0x8adb10db
|
||||
#define CYAN 0x10bc9abc
|
||||
#define GREEN 0x2aad1aad
|
||||
#define MAGENTA 0xe64ed64e
|
||||
#define RED 0xf03f663f
|
||||
#define BLUE 0x7620f020
|
||||
#define BLACK 0x80108010
|
||||
|
||||
|
||||
struct test_source {
|
||||
struct video_source src;
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int pixelformat;
|
||||
};
|
||||
|
||||
#define to_test_source(s) container_of(s, struct test_source, src)
|
||||
|
||||
static void test_source_destroy(struct video_source *s)
|
||||
{
|
||||
struct test_source *src = to_test_source(s);
|
||||
|
||||
free(src);
|
||||
}
|
||||
|
||||
static int test_source_set_format(struct video_source *s,
|
||||
struct v4l2_pix_format *fmt)
|
||||
{
|
||||
struct test_source *src = to_test_source(s);
|
||||
|
||||
src->width = fmt->width;
|
||||
src->height = fmt->height;
|
||||
src->pixelformat = fmt->pixelformat;
|
||||
|
||||
if (src->pixelformat != v4l2_fourcc('Y', 'U', 'Y', 'V'))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_source_set_frame_rate(struct video_source *s __attribute__((unused)),
|
||||
unsigned int fps __attribute__((unused)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_source_free_buffers(struct video_source *s __attribute__((unused)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_source_stream_on(struct video_source *s __attribute__((unused)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_source_stream_off(struct video_source *s __attribute__((unused)))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_source_fill_buffer(struct video_source *s,
|
||||
struct video_buffer *buf)
|
||||
{
|
||||
struct test_source *src = to_test_source(s);
|
||||
unsigned int bpl;
|
||||
unsigned int i, j;
|
||||
void *mem = buf->mem;
|
||||
|
||||
bpl = src->width * 2;
|
||||
for (i = 0; i < src->height; ++i) {
|
||||
for (j = 0; j < bpl; j += 4) {
|
||||
if (j < bpl * 1 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = WHITE;
|
||||
else if (j < bpl * 2 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = YELLOW;
|
||||
else if (j < bpl * 3 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = CYAN;
|
||||
else if (j < bpl * 4 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = GREEN;
|
||||
else if (j < bpl * 5 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = MAGENTA;
|
||||
else if (j < bpl * 6 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = RED;
|
||||
else if (j < bpl * 7 / 8)
|
||||
*(unsigned int *)(mem + i*bpl + j) = BLUE;
|
||||
else
|
||||
*(unsigned int *)(mem + i*bpl + j) = BLACK;
|
||||
}
|
||||
}
|
||||
buf->bytesused = bpl * src->height;
|
||||
}
|
||||
|
||||
static void test_source_fill_buffer_ext(struct video_source *s,
|
||||
struct video_buffer *buf)
|
||||
{
|
||||
struct test_source *src = to_test_source(s);
|
||||
unsigned int bpl;
|
||||
unsigned int i, j;
|
||||
//void *mem = buf->mem;
|
||||
|
||||
bpl = src->width * 2;
|
||||
// for (i = 0; i < src->height; ++i) {
|
||||
// for (j = 0; j < bpl; j += 4) {
|
||||
// if (j < bpl * 1 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = WHITE;
|
||||
// else if (j < bpl * 2 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = YELLOW;
|
||||
// else if (j < bpl * 3 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = CYAN;
|
||||
// else if (j < bpl * 4 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = GREEN;
|
||||
// else if (j < bpl * 5 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = MAGENTA;
|
||||
// else if (j < bpl * 6 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = RED;
|
||||
// else if (j < bpl * 7 / 8)
|
||||
// *(unsigned int *)(mem + i*bpl + j) = BLUE;
|
||||
// else
|
||||
// *(unsigned int *)(mem + i*bpl + j) = BLACK;
|
||||
// }
|
||||
// }
|
||||
buf->bytesused = bpl * src->height;
|
||||
}
|
||||
|
||||
static const struct video_source_ops test_source_ops = {
|
||||
.destroy = test_source_destroy,
|
||||
.set_format = test_source_set_format,
|
||||
.set_frame_rate = test_source_set_frame_rate,
|
||||
.free_buffers = test_source_free_buffers,
|
||||
.stream_on = test_source_stream_on,
|
||||
.stream_off = test_source_stream_off,
|
||||
.queue_buffer = NULL,
|
||||
.fill_buffer = test_source_fill_buffer,
|
||||
.fill_buffer_ext = test_source_fill_buffer_ext,
|
||||
};
|
||||
|
||||
struct video_source *test_video_source_create()
|
||||
{
|
||||
struct test_source *src;
|
||||
|
||||
src = malloc(sizeof *src);
|
||||
if (!src)
|
||||
return NULL;
|
||||
|
||||
memset(src, 0, sizeof *src);
|
||||
src->src.ops = &test_source_ops;
|
||||
src->src.type = VIDEO_SOURCE_STATIC;
|
||||
|
||||
return &src->src;
|
||||
}
|
||||
|
||||
void test_video_source_init(struct video_source *s, struct events *events)
|
||||
{
|
||||
struct test_source *src = to_test_source(s);
|
||||
|
||||
src->src.events = events;
|
||||
}
|
96
lib/timer.c
Normal file
96
lib/timer.c
Normal file
|
@ -0,0 +1,96 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* libuvcgadget timer utils
|
||||
*
|
||||
* Copyright (C) 2022 Daniel Scally
|
||||
*
|
||||
* Contact: Daniel Scally <dan.scally@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "timer.h"
|
||||
|
||||
struct timer {
|
||||
int fd;
|
||||
struct itimerspec settings;
|
||||
};
|
||||
|
||||
struct timer *timer_new(void)
|
||||
{
|
||||
struct timer *timer;
|
||||
|
||||
timer = malloc(sizeof(*timer));
|
||||
if (!timer)
|
||||
return NULL;
|
||||
|
||||
memset(timer, 0, sizeof(*timer));
|
||||
|
||||
timer->fd = timerfd_create(CLOCK_REALTIME, 0);
|
||||
if (timer->fd < 0) {
|
||||
fprintf(stderr, "failed to create timer: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
goto err_free_timer;
|
||||
}
|
||||
|
||||
return timer;
|
||||
|
||||
err_free_timer:
|
||||
free(timer);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void timer_set_fps(struct timer *timer, int fps)
|
||||
{
|
||||
int ns_per_frame = 1000000000 / fps;
|
||||
|
||||
timer->settings.it_value.tv_nsec = ns_per_frame;
|
||||
timer->settings.it_interval.tv_nsec = ns_per_frame;
|
||||
}
|
||||
|
||||
int timer_arm(struct timer *timer)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = timerfd_settime(timer->fd, 0, &timer->settings, NULL);
|
||||
if (ret)
|
||||
fprintf(stderr, "failed to change timer settings: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int timer_disarm(struct timer *timer)
|
||||
{
|
||||
static const struct itimerspec disable_settings = {
|
||||
{ 0, 0 },
|
||||
{ 0, 0 },
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = timerfd_settime(timer->fd, 0, &disable_settings, NULL);
|
||||
if (ret)
|
||||
fprintf(stderr, "failed to disable timer: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void timer_wait(struct timer *timer)
|
||||
{
|
||||
char read_buf[8];
|
||||
|
||||
read(timer->fd, read_buf, 8);
|
||||
}
|
||||
|
||||
void timer_destroy(struct timer *timer)
|
||||
{
|
||||
close(timer->fd);
|
||||
free(timer);
|
||||
}
|
65
lib/tools.h
Normal file
65
lib/tools.h
Normal file
|
@ -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__ */
|
103
lib/uvc-formats.h
Normal file
103
lib/uvc-formats.h
Normal file
|
@ -0,0 +1,103 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* UVC-V4L2 Format Map and GUIDs
|
||||
*
|
||||
* Copyright (C) 2023 Ideas on Board Oy
|
||||
*
|
||||
* Contact: Daniel Scally <dan.scally@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
#include <stdint.h>
|
||||
#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}
|
||||
#define UVC_GUID_FORMAT_NV12 \
|
||||
{ 'N', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_YV12 \
|
||||
{ 'Y', 'V', '1', '2', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_I420 \
|
||||
{ 'I', '4', '2', '0', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_UYVY \
|
||||
{ 'U', 'Y', 'V', 'Y', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_Y8 \
|
||||
{ 'Y', '8', ' ', ' ', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_Y10 \
|
||||
{ 'Y', '1', '0', ' ', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_Y12 \
|
||||
{ 'Y', '1', '2', ' ', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_Y16 \
|
||||
{ 'Y', '1', '6', ' ', 0x00, 0x00, 0x10, 0x00, \
|
||||
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
#define UVC_GUID_FORMAT_RGBP \
|
||||
{ 'R', 'G', 'B', 'P', 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[] = {
|
||||
/* Compressed Formats */
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_MJPEG,
|
||||
.fcc = V4L2_PIX_FMT_MJPEG,
|
||||
},
|
||||
|
||||
/* Greyscale Formats */
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y8,
|
||||
.fcc = V4L2_PIX_FMT_GREY,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y10,
|
||||
.fcc = V4L2_PIX_FMT_Y10,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y12,
|
||||
.fcc = V4L2_PIX_FMT_Y12,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_Y16,
|
||||
.fcc = V4L2_PIX_FMT_Y16,
|
||||
},
|
||||
|
||||
/* YUV Formats */
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_YUY2,
|
||||
.fcc = V4L2_PIX_FMT_YUYV,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_NV12,
|
||||
.fcc = V4L2_PIX_FMT_NV12,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_YV12,
|
||||
.fcc = V4L2_PIX_FMT_YVU420,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_I420,
|
||||
.fcc = V4L2_PIX_FMT_YUV420,
|
||||
},
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_UYVY,
|
||||
.fcc = V4L2_PIX_FMT_UYVY,
|
||||
},
|
||||
|
||||
/* RGB Formats */
|
||||
{
|
||||
.guid = UVC_GUID_FORMAT_RGBP,
|
||||
.fcc = V4L2_PIX_FMT_RGB565,
|
||||
},
|
||||
};
|
449
lib/uvc.c
Normal file
449
lib/uvc.c
Normal file
|
@ -0,0 +1,449 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* UVC protocol handling
|
||||
*
|
||||
* Copyright (C) 2010-2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.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;
|
||||
};
|
||||
|
||||
static const char *uvc_request_names[] = {
|
||||
[UVC_RC_UNDEFINED] = "UNDEFINED",
|
||||
[UVC_SET_CUR] = "SET_CUR",
|
||||
[UVC_GET_CUR] = "GET_CUR",
|
||||
[UVC_GET_MIN] = "GET_MIN",
|
||||
[UVC_GET_MAX] = "GET_MAX",
|
||||
[UVC_GET_RES] = "GET_RES",
|
||||
[UVC_GET_LEN] = "GET_LEN",
|
||||
[UVC_GET_INFO] = "GET_INFO",
|
||||
[UVC_GET_DEF] = "GET_DEF",
|
||||
};
|
||||
|
||||
static const char *uvc_request_name(uint8_t req)
|
||||
{
|
||||
if (req < ARRAY_SIZE(uvc_request_names))
|
||||
return uvc_request_names[req];
|
||||
else
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static const char *uvc_pu_control_names[] = {
|
||||
[UVC_PU_CONTROL_UNDEFINED] = "UNDEFINED",
|
||||
[UVC_PU_BACKLIGHT_COMPENSATION_CONTROL] = "BACKLIGHT_COMPENSATION",
|
||||
[UVC_PU_BRIGHTNESS_CONTROL] = "BRIGHTNESS",
|
||||
[UVC_PU_CONTRAST_CONTROL] = "CONTRAST",
|
||||
[UVC_PU_GAIN_CONTROL] = "GAIN",
|
||||
[UVC_PU_POWER_LINE_FREQUENCY_CONTROL] = "POWER_LINE_FREQUENCY",
|
||||
[UVC_PU_HUE_CONTROL] = "HUE",
|
||||
[UVC_PU_SATURATION_CONTROL] = "SATURATION",
|
||||
[UVC_PU_SHARPNESS_CONTROL] = "SHARPNESS",
|
||||
[UVC_PU_GAMMA_CONTROL] = "GAMMA",
|
||||
[UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL] = "WHITE_BALANCE_TEMPERATURE",
|
||||
[UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL] = "WHITE_BALANCE_TEMPERATURE_AUTO",
|
||||
[UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL] = "WHITE_BALANCE_COMPONENT",
|
||||
[UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL] = "WHITE_BALANCE_COMPONENT_AUTO",
|
||||
[UVC_PU_DIGITAL_MULTIPLIER_CONTROL] = "DIGITAL_MULTIPLIER",
|
||||
[UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL] = "DIGITAL_MULTIPLIER_LIMIT",
|
||||
[UVC_PU_HUE_AUTO_CONTROL] = "HUE_AUTO",
|
||||
[UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL] = "ANALOG_VIDEO_STANDARD",
|
||||
[UVC_PU_ANALOG_LOCK_STATUS_CONTROL] = "ANALOG_LOCK_STATUS",
|
||||
};
|
||||
|
||||
static const char *pu_control_name(uint8_t cs)
|
||||
{
|
||||
if (cs < ARRAY_SIZE(uvc_pu_control_names))
|
||||
return uvc_pu_control_names[cs];
|
||||
else
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* The maximum size in bytes for a single frame depends on the format.
|
||||
* This switch will need extending for any new formats that are added
|
||||
* to ensure the buffer size calculations are done correctly.
|
||||
*/
|
||||
switch (format->fcc) {
|
||||
case V4L2_PIX_FMT_YUYV:
|
||||
case V4L2_PIX_FMT_MJPEG:
|
||||
ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2;
|
||||
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, uint8_t len,
|
||||
struct uvc_request_data *resp)
|
||||
{
|
||||
printf("control request (req %s cs %s)\n", uvc_request_name(req), pu_control_name(cs));
|
||||
(void)dev;
|
||||
|
||||
/*
|
||||
* Responding to controls is not currently implemented. As an interim
|
||||
* measure respond to say that both get and set operations are permitted.
|
||||
*/
|
||||
resp->data[0] = 0x03;
|
||||
resp->length = len;
|
||||
}
|
||||
|
||||
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 %s cs %02x)\n", uvc_request_name(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:
|
||||
if (req == UVC_GET_MAX)
|
||||
uvc_fill_streaming_control(dev, ctrl, -1, -1, UINT_MAX);
|
||||
else
|
||||
uvc_fill_streaming_control(dev, ctrl, 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, ctrl->wLength, 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;
|
||||
unsigned int fps;
|
||||
|
||||
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 = target->dwMaxVideoFrameSize;
|
||||
|
||||
uvc_stream_set_format(dev->stream, &pixfmt);
|
||||
|
||||
/* fps is guaranteed to be non-zero and thus valid. */
|
||||
fps = 1.0 / (target->dwFrameInterval / 10000000.0);
|
||||
uvc_stream_set_frame_rate(dev->stream, fps);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ret = ioctl(dev->vdev->fd, UVCIOC_SEND_RESPONSE, &resp);
|
||||
if (ret < 0) {
|
||||
printf("UVCIOC_SEND_RESPONSE 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)
|
||||
{
|
||||
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;
|
||||
}
|
26
lib/uvc.h
Normal file
26
lib/uvc.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* UVC protocol handling
|
||||
*
|
||||
* Copyright (C) 2010-2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#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__ */
|
202
lib/v4l2-source.c
Normal file
202
lib/v4l2-source.c
Normal file
|
@ -0,0 +1,202 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
/*
|
||||
* V4L2 video source
|
||||
*
|
||||
* Copyright (C) 2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.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_set_frame_rate(struct video_source *s, unsigned int fps)
|
||||
{
|
||||
struct v4l2_source *src = to_v4l2_source(s);
|
||||
|
||||
return v4l2_set_frame_rate(src->vdev, fps);
|
||||
}
|
||||
|
||||
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,
|
||||
.set_frame_rate = v4l2_source_set_frame_rate,
|
||||
.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->src.type = VIDEO_SOURCE_DMABUF;
|
||||
|
||||
src->vdev = v4l2_open(devname);
|
||||
if (!src->vdev)
|
||||
goto err_free_src;
|
||||
|
||||
if (src->vdev->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
|
||||
fprintf(stderr, "v4l2 device does not support video capture\n");
|
||||
goto err_close_v4l2;
|
||||
}
|
||||
|
||||
return &src->src;
|
||||
|
||||
err_close_v4l2:
|
||||
v4l2_close(src->vdev);
|
||||
err_free_src:
|
||||
free(src);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void v4l2_video_source_init(struct video_source *s, struct events *events)
|
||||
{
|
||||
struct v4l2_source *src = to_v4l2_source(s);
|
||||
|
||||
src->src.events = events;
|
||||
}
|
856
lib/v4l2.c
Normal file
856
lib/v4l2.c
Normal file
|
@ -0,0 +1,856 @@
|
|||
/* 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;
|
||||
fmt.fmt.pix.sizeimage = format->sizeimage;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int v4l2_set_frame_rate(struct v4l2_device *dev, unsigned int fps)
|
||||
{
|
||||
struct v4l2_streamparm parm;
|
||||
int ret;
|
||||
|
||||
memset(&parm, 0, sizeof parm);
|
||||
parm.type = dev->type;
|
||||
parm.parm.capture.timeperframe.numerator = 1;
|
||||
parm.parm.capture.timeperframe.denominator = fps;
|
||||
|
||||
ret = ioctl(dev->fd, VIDIOC_S_PARM, &parm);
|
||||
if (ret < 0) {
|
||||
printf("%s: unable to set frame rate (%d).\n", dev->name, errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
dev->fps = fps;
|
||||
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->size = buf.length;
|
||||
buffer->mem = dev->buffers.buffers[buf.index].mem;
|
||||
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;
|
||||
}
|
353
lib/v4l2.h
Normal file
353
lib/v4l2.h
Normal file
|
@ -0,0 +1,353 @@
|
|||
/* 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;
|
||||
unsigned int fps;
|
||||
|
||||
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_set_frame_rate - Set the frame rate
|
||||
* @dev: Device instance
|
||||
* @fps: Frame rate
|
||||
*
|
||||
* Set the frame rate specified by @fps.
|
||||
* The device can modify the requested format and size, in which case @dev->fps
|
||||
* will be updated to reflect the modified setting.
|
||||
*
|
||||
* Return 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int v4l2_set_frame_rate(struct v4l2_device *dev, unsigned int fps);
|
||||
|
||||
/*
|
||||
* 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
|
40
lib/video-buffers.c
Normal file
40
lib/video-buffers.c
Normal file
|
@ -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);
|
||||
}
|
48
lib/video-buffers.h
Normal file
48
lib/video-buffers.h
Normal file
|
@ -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__ */
|
91
lib/video-source.c
Normal file
91
lib/video-source.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
/* 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_set_frame_rate(struct video_source *src, unsigned int fps)
|
||||
{
|
||||
return src->ops->set_frame_rate(src, fps);
|
||||
}
|
||||
|
||||
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_import_buffers(struct video_source *src,
|
||||
struct video_buffer_set *buffers)
|
||||
{
|
||||
return src->ops->import_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);
|
||||
}
|
||||
|
||||
void video_source_fill_buffer(struct video_source *src,
|
||||
struct video_buffer *buf)
|
||||
{
|
||||
src->ops->fill_buffer(src, buf);
|
||||
}
|
||||
|
||||
|
||||
void video_source_fill_buffer_ext(struct video_source *src,
|
||||
struct video_buffer *buf)
|
||||
{
|
||||
if (src->ops->fill_buffer_ext){
|
||||
src->ops->fill_buffer_ext(src, buf);
|
||||
}
|
||||
else {
|
||||
video_source_fill_buffer(src, buf);
|
||||
}
|
||||
}
|
760
scripts/gadget-setup.sh
Normal file
760
scripts/gadget-setup.sh
Normal file
|
@ -0,0 +1,760 @@
|
|||
#!/bin/sh
|
||||
# WARNING: This setup script does NOT support humbird rootfs!!!
|
||||
# Supported: bianbu-linux, bianbu-desktop
|
||||
|
||||
name=`basename $0`
|
||||
SCRIPT_VERSION="v0.4"
|
||||
CONFIG_FILE=$HOME/.usb_config
|
||||
|
||||
# USB Descriptors
|
||||
VENDOR_ID="0x361c"
|
||||
PRODUC_ID="0x0007"
|
||||
MANUAF_STR="Spacemit"
|
||||
PRODUC_STR="K1 Composite Device"
|
||||
SERNUM_STR="20211102"
|
||||
[ "$ADB_BOARD_SN" ] || ADB_BOARD_SN=$( [ -e /proc/device-tree/serial-number ] && tr -d '\000' < /proc/device-tree/serial-number )
|
||||
[ "$ADB_BOARD_SN" ] && SERNUM_STR=$ADB_BOARD_SN
|
||||
|
||||
# Select default_udc as UDC from DTS if exist, use the first in sysfs otherwise
|
||||
USB_UDC_DTS=$( [ -e /proc/device-tree/default_udc ] && tr -d '\000' < /proc/device-tree/default_udc )
|
||||
[ "$USB_UDC" ] || USB_UDC=$USB_UDC_DTS
|
||||
[ "$USB_UDC" ] || USB_UDC=$(ls /sys/class/udc | awk "NR==1{print}")
|
||||
|
||||
[ "$MAXPACKAGESIZE" ] || MAXPACKAGESIZE=1024
|
||||
CONFIGFS=/sys/kernel/config
|
||||
GADGET_PATH=$CONFIGFS/usb_gadget/spacemit
|
||||
GFUNC_PATH=$GADGET_PATH/functions
|
||||
GCONFIG=$GADGET_PATH/configs/c.1
|
||||
|
||||
# Debug Ramdisk for MSC without any argument
|
||||
RAMDISK_PATH=/var/sdcard
|
||||
TMPFS_FOUND=`mount | grep tmpfs | grep -v devtmpfs | awk '{print $3}' | grep '/dev/shm' | wc -l`
|
||||
[ "$TMPFS_FOUND" -eq 1 ] && RAMDISK_PATH=/dev/shm/sdcard
|
||||
TMPFS_FOUND=`mount | grep tmpfs | grep -v devtmpfs | awk '{print $3}' | grep '/tmp' | wc -l`
|
||||
[ "$TMPFS_FOUND" -eq 1 ] && RAMDISK_PATH=/tmp/sdcard
|
||||
|
||||
# SCSI Target
|
||||
NAA="naa.6001405c3214b06a"
|
||||
CORE_DIR=$CONFIGFS/target/core
|
||||
USB_GDIR=$CONFIGFS/target/usb_gadget
|
||||
|
||||
print_info()
|
||||
{
|
||||
echo "SpacemiT gadget-setup tool $SCRIPT_VERSION"
|
||||
echo
|
||||
echo "Board Model: `tr -d '\000' < /proc/device-tree/model`"
|
||||
echo "General Config Info: $VENDOR_ID/$PRODUC_ID/$SERNUM_STR/$MANUAF_STR/$PRODUC_STR."
|
||||
echo "Config File Path: $CONFIG_FILE"
|
||||
echo "MSC Ramdisk Path (selected from tmpfs mounting point): $RAMDISK_PATH"
|
||||
echo "UASP SCSI NAA: $NAA"
|
||||
echo "UASP Target Dir: $USB_GDIR"
|
||||
echo "Available UDCs: `ls -1 /sys/class/udc/ | tr '\n' ' '`"
|
||||
echo "DTS default UDC: $USB_UDC_DTS"
|
||||
echo "DTS Serial Number: $ADB_BOARD_SN"
|
||||
echo
|
||||
}
|
||||
|
||||
# Global variables to record configured functions
|
||||
MSC=disabled
|
||||
UAS=disabled
|
||||
UAS_ARG=""
|
||||
MSC_ARG=""
|
||||
ADB=disabled
|
||||
UVC=disabled
|
||||
UVCH=disabled
|
||||
RNDIS=disabled
|
||||
FUNCTION_CNT=0
|
||||
DEBUG=okay
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "$name usage: "
|
||||
echo ""
|
||||
echo -e "Support Select functions in $CONFIG_FILE:"
|
||||
echo -e "\tWrite <func>:<arg> line in $CONFIG_FILE, then run:"
|
||||
echo -e "\t$name [start|stop|reload|config]"
|
||||
echo -e "Or Select functions manually:"
|
||||
echo -e "\t$name <function1>(,<function2>...)"
|
||||
echo -e "Set USB connection:"
|
||||
echo -e "\t$name [pause|resume]"
|
||||
echo -e "\n$name info: show gadget info"
|
||||
echo -e "\nhint: udc is automatically selected, you can"
|
||||
echo -e "\toverride udc idx with env USB_UDC_IDX="
|
||||
echo ""
|
||||
echo "Functions and arguments supported:"
|
||||
echo -e "\tmsc(:dev/file) Mass Storage(Bulk-Only)."
|
||||
echo -e "\tuas(:dev/file) Mass Storage(UASP)."
|
||||
echo -e "\tadb Android Debug Bridge over USB."
|
||||
echo -e "\tuvc Webcam."
|
||||
# uvc1, uvc2 are for debug usage
|
||||
echo -e "\trndis RNDIS NIC function."
|
||||
echo -e "\nSpacemiT gadget-setup tool $SCRIPT_VERSION"
|
||||
echo ""
|
||||
}
|
||||
|
||||
gadget_info()
|
||||
{
|
||||
echo "$name: $1"
|
||||
}
|
||||
|
||||
gadget_debug()
|
||||
{
|
||||
[ $DEBUG ] && echo "$name: $1"
|
||||
}
|
||||
|
||||
die()
|
||||
{
|
||||
gadget_info "$1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
g_remove()
|
||||
{
|
||||
[ -h $1 ] && rm -f $1
|
||||
[ -d $1 ] && rmdir $1
|
||||
[ -e $1 ] && rm -f $1
|
||||
}
|
||||
|
||||
## MSC
|
||||
msc_ramdisk_()
|
||||
{
|
||||
gadget_info "msc: ramdisk: $RAMDISK_PATH/disk.img"
|
||||
mkdir -p $RAMDISK_PATH/sda
|
||||
dd if=/dev/zero of=$RAMDISK_PATH/disk.img bs=1M count=1038
|
||||
mkdosfs -F 32 $RAMDISK_PATH/disk.img
|
||||
}
|
||||
|
||||
msc_config()
|
||||
{
|
||||
gadget_debug "add a msc function instance"
|
||||
MSC_DIR=$GFUNC_PATH/mass_storage.usb0
|
||||
mkdir -p $MSC_DIR
|
||||
DEVICE=$1
|
||||
[ $DEVICE ] || DEVICE=$MSC_ARG
|
||||
# Create a backstore
|
||||
if [ -z "$DEVICE" ]; then
|
||||
echo "$name: no device specificed, select ramdisk as backstore"
|
||||
msc_ramdisk_
|
||||
echo "tmp files would be created in: $RAMDISK_PATH"
|
||||
echo "$RAMDISK_PATH/disk.img" > $MSC_DIR/lun.0/file
|
||||
elif [ -b $DEVICE ]; then
|
||||
echo "$name: block device"
|
||||
echo "$DEVICE" > $MSC_DIR/lun.0/file
|
||||
else
|
||||
echo "$name: other path, regular file"
|
||||
echo "$DEVICE" > $MSC_DIR/lun.0/file
|
||||
fi
|
||||
|
||||
echo 1 > $MSC_DIR/lun.0/removable
|
||||
echo 0 > $MSC_DIR/lun.0/nofua
|
||||
}
|
||||
|
||||
msc_link()
|
||||
{
|
||||
gadget_debug "add msc to usb config"
|
||||
ln -s $MSC_DIR $GCONFIG/mass_storage.usb0
|
||||
}
|
||||
|
||||
msc_unlink()
|
||||
{
|
||||
gadget_debug "remove msc from usb config"
|
||||
g_remove $GCONFIG/mass_storage.usb0
|
||||
}
|
||||
|
||||
msc_clean()
|
||||
{
|
||||
gadget_debug "clean msc"
|
||||
g_remove $GFUNC_PATH/mass_storage.usb0
|
||||
g_remove $RAMDISK_PATH/disk.img
|
||||
g_remove $RAMDISK_PATH/sda
|
||||
}
|
||||
|
||||
## UAS
|
||||
|
||||
uas_config()
|
||||
{
|
||||
gadget_debug "add a uas function instance"
|
||||
# Load the target modules and mount the add a file function instance system
|
||||
# Uncomment these if modules not built-in:
|
||||
# lsmod | grep -q configfs || modprobe configfs
|
||||
# lsmod | grep -q target_core_mod || modprobe target_core_mod
|
||||
DEVICE=$1
|
||||
[ $DEVICE ] || DEVICE=$UAS_ARG
|
||||
mkdir -p $GADGET_PATH/functions/tcm.0
|
||||
# Create a backstore
|
||||
if [ -z "$DEVICE" ]; then
|
||||
echo "$name: no device specificed, select rd_mcp as backstore"
|
||||
BACKSTORE_DIR=$CORE_DIR/rd_mcp_0/ramdisk
|
||||
mkdir -p $BACKSTORE_DIR
|
||||
# 128MB pure ramdisk
|
||||
if [ $UAS_PERFORMACE ]; then
|
||||
gadgdet_info "Warning: user asked for performance test, uasp storage will broke rw reqs"
|
||||
else
|
||||
echo rd_pages=200000 > $BACKSTORE_DIR/control
|
||||
fi
|
||||
elif [ -b $DEVICE ]; then
|
||||
echo "$name: block device, select iblock as backstore"
|
||||
BACKSTORE_DIR=$CORE_DIR/iblock_0/iblock
|
||||
mkdir -p $BACKSTORE_DIR
|
||||
echo "udev_path=${DEVICE}" > $BACKSTORE_DIR/control
|
||||
else
|
||||
echo "$name: other path, select fileio as backstore"
|
||||
BACKSTORE_DIR=$CORE_DIR/fileio_0/fileio
|
||||
mkdir -p $BACKSTORE_DIR
|
||||
DEVICE_SIZE=$(du -b $DEVICE | cut -f1)
|
||||
echo "fd_dev_name=${DEVICE},fd_dev_size=${DEVICE_SIZE}" > $BACKSTORE_DIR/control
|
||||
# echo 1 > $BACKSTORE_DIR/attrib/emulate_write_cache
|
||||
fi
|
||||
[ -n "$DEVICE" ] && umount $DEVICE
|
||||
echo 1 > $BACKSTORE_DIR/enable
|
||||
echo "$name: NAA of target: $NAA"
|
||||
# Create an NAA target and a target portal group (TPG)
|
||||
mkdir -p $USB_GDIR/$NAA/tpgt_1/
|
||||
echo "$name tpgt_1 has lun_0"
|
||||
# Create a LUN
|
||||
mkdir $USB_GDIR/$NAA/tpgt_1/lun/lun_0
|
||||
# Nexus initiator on target port 1 to $NAA
|
||||
echo $NAA > $USB_GDIR/$NAA/tpgt_1/nexus
|
||||
|
||||
# Allow write access for non authenticated initiators
|
||||
# echo 0 > $USB_GDIR/$NAA/tpgt_1/attrib/demo_mode_write_protect
|
||||
ln -s $BACKSTORE_DIR $USB_GDIR/$NAA/tpgt_1/lun/lun_0/data
|
||||
#ln -s $BACKSTORE_DIR $USB_GDIR/$NAA/tpgt_1/lun/lun_0/virtual_scsi_port
|
||||
# echo 15 > $USB_GDIR/$NAA/tpgt_1/maxburst
|
||||
|
||||
# Enable the target portal group, with 1 lun
|
||||
echo 1 > $USB_GDIR/$NAA/tpgt_1/enable
|
||||
}
|
||||
|
||||
uas_link()
|
||||
{
|
||||
gadget_debug "add uas to usb config"
|
||||
ln -s $GADGET_PATH/functions/tcm.0 $GCONFIG/tcm.0
|
||||
}
|
||||
|
||||
uas_unlink()
|
||||
{
|
||||
gadget_debug "remove uas from usb config"
|
||||
g_remove $GCONFIG/tcm.0
|
||||
}
|
||||
|
||||
uas_clean()
|
||||
{
|
||||
gadget_debug "clean uas"
|
||||
[ -d "$USB_GDIR/$NAA/tpgt_1/enable" ] && echo 0 > $USB_GDIR/$NAA/tpgt_1/enable
|
||||
g_remove $USB_GDIR/$NAA/tpgt_1/lun/lun_0/data
|
||||
g_remove $USB_GDIR/$NAA/tpgt_1/lun/lun_0/virtual_scsi_port
|
||||
g_remove $USB_GDIR/$NAA/tpgt_1/lun/lun_0
|
||||
g_remove $USB_GDIR/$NAA/tpgt_1/
|
||||
g_remove $USB_GDIR/$NAA/
|
||||
g_remove $USB_GDIR
|
||||
BACKSTORE_DIR=$CORE_DIR/iblock_0/iblock
|
||||
g_remove $BACKSTORE_DIR
|
||||
BACKSTORE_DIR=$CORE_DIR/fileio_0/fileio
|
||||
g_remove $BACKSTORE_DIR
|
||||
BACKSTORE_DIR=$CORE_DIR/rd_mcp_0/ramdisk
|
||||
g_remove $BACKSTORE_DIR
|
||||
g_remove $GADGET_PATH/functions/tcm.0
|
||||
}
|
||||
|
||||
## ADB
|
||||
|
||||
adb_config()
|
||||
{
|
||||
gadget_debug "add a adb function instance"
|
||||
mkdir $GFUNC_PATH/ffs.adb
|
||||
}
|
||||
|
||||
adb_link()
|
||||
{
|
||||
gadget_debug "add adb to usb config"
|
||||
ln -s $GFUNC_PATH/ffs.adb/ $GCONFIG/ffs.adb
|
||||
mkdir /dev/usb-ffs
|
||||
mkdir /dev/usb-ffs/adb
|
||||
mount -o uid=2000,gid=2000 -t functionfs adb /dev/usb-ffs/adb/
|
||||
#mkdir /dev/pts
|
||||
#mount -t devpts -o defaults,mode=644,ptmxmode=666 devpts /dev/pts
|
||||
adbd &
|
||||
sleep 1
|
||||
}
|
||||
|
||||
adb_unlink()
|
||||
{
|
||||
gadget_debug "remove adb from usb config"
|
||||
killall adbd
|
||||
g_remove $GCONFIG/ffs.adb
|
||||
[ -e /dev/usb-ffs/adb/ ] && umount /dev/usb-ffs/adb/
|
||||
#[ -e /dev/pts ] && umount /dev/pts
|
||||
#g_remove /dev/pts
|
||||
g_remove /dev/usb-ffs/adb
|
||||
g_remove /dev/usb-ffs
|
||||
}
|
||||
|
||||
adb_clean()
|
||||
{
|
||||
gadget_debug "clean adb"
|
||||
g_remove $GFUNC_PATH/ffs.adb
|
||||
}
|
||||
|
||||
## UVC
|
||||
### UVC Common
|
||||
### Setup uvc frame interval for yuv 360p with 15fps (7MBps).
|
||||
uvc_frame_1_(){
|
||||
UVC_FRAME_WDIR=$1
|
||||
echo 1000000 > $UVC_FRAME_WDIR/dwDefaultFrameInterval
|
||||
echo 49152000 > $UVC_FRAME_WDIR/dwMinBitRate
|
||||
echo 55296000 > $UVC_FRAME_WDIR/dwMaxBitRate
|
||||
}
|
||||
|
||||
### Setup uvc frame interval for at most yuv 360p with 60fps (13MBps)
|
||||
uvc_frame_2_(){
|
||||
UVC_FRAME_WDIR=$1
|
||||
echo 333333 > $UVC_FRAME_WDIR/dwDefaultFrameInterval
|
||||
echo 110592000 > $UVC_FRAME_WDIR/dwMinBitRate
|
||||
echo 221184000 > $UVC_FRAME_WDIR/dwMaxBitRate
|
||||
}
|
||||
|
||||
### Setup uvc frame interval for at most yuv 720p with 60fps (14~52MBps)
|
||||
uvc_frame_3_(){
|
||||
UVC_FRAME_WDIR=$1
|
||||
echo 333333 > $UVC_FRAME_WDIR/dwDefaultFrameInterval
|
||||
echo 110592000 > $UVC_FRAME_WDIR/dwMinBitRate
|
||||
echo 442276000 > $UVC_FRAME_WDIR/dwMaxBitRate
|
||||
}
|
||||
|
||||
### Setup uvc frame interval for at most yuv 1080p with 30fps (14~52MBps)
|
||||
uvc_frame_4_(){
|
||||
UVC_FRAME_WDIR=$1
|
||||
echo 333333 > $UVC_FRAME_WDIR/dwDefaultFrameInterval
|
||||
echo 110592000 > $UVC_FRAME_WDIR/dwMinBitRate
|
||||
echo 442276000 > $UVC_FRAME_WDIR/dwMaxBitRate
|
||||
}
|
||||
|
||||
### Setup streaming/ directory.
|
||||
configure_uvc_format_()
|
||||
{
|
||||
FORMAT=$1 # $1 format "uncompressed/y" / "mjpeg/m"
|
||||
UVC_DISPLAY_W=$2 # $2 Width
|
||||
UVC_DISPLAY_H=$3 # $3 Height
|
||||
HIGH_FRAMERATE=$4 # $4 HIGH_FRAMERATE 0/1
|
||||
#https://docs.kernel.org/usb/gadget_uvc.html
|
||||
UVC_MJPEG_PRE_PATH=$GFUNC_PATH/$UVC_INSTANCE/streaming/$FORMAT
|
||||
UVC_FRAME_WDIR=${UVC_MJPEG_PRE_PATH}/${UVC_DISPLAY_H}p
|
||||
mkdir -p $UVC_FRAME_WDIR
|
||||
echo $UVC_DISPLAY_W > $UVC_FRAME_WDIR/wWidth
|
||||
echo $UVC_DISPLAY_H > $UVC_FRAME_WDIR/wHeight
|
||||
echo $(( $UVC_DISPLAY_W * $UVC_DISPLAY_H * 2 )) > $UVC_FRAME_WDIR/dwMaxVideoFrameBufferSize
|
||||
if [ "$HIGH_FRAMERATE" -eq 1 ]; then
|
||||
uvc_frame_1_ $UVC_FRAME_WDIR
|
||||
else
|
||||
uvc_frame_1_ $UVC_FRAME_WDIR
|
||||
fi
|
||||
cat <<EOF > $UVC_FRAME_WDIR/dwFrameInterval
|
||||
166666
|
||||
333333
|
||||
416667
|
||||
500000
|
||||
666666
|
||||
1000000
|
||||
1333333
|
||||
2000000
|
||||
EOF
|
||||
}
|
||||
|
||||
clean_uvc_format_()
|
||||
{
|
||||
FORMAT=$1
|
||||
UVC_DISPLAY_W=$2
|
||||
UVC_DISPLAY_H=$3
|
||||
UVC_MJPEG_PRE_PATH=$GFUNC_PATH/$UVC_INSTANCE/streaming/$FORMAT
|
||||
UVC_FRAME_WDIR=${UVC_MJPEG_PRE_PATH}/${UVC_DISPLAY_H}p
|
||||
g_remove $UVC_FRAME_WDIR
|
||||
}
|
||||
|
||||
clean_uvc_format_all_()
|
||||
{
|
||||
clean_uvc_format_ uncompressed/y 640 360
|
||||
clean_uvc_format_ uncompressed/y 640 480
|
||||
clean_uvc_format_ uncompressed/y 1280 720
|
||||
clean_uvc_format_ uncompressed/y 1920 1080
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/uncompressed/y
|
||||
clean_uvc_format_ mjpeg/m 640 360
|
||||
clean_uvc_format_ mjpeg/m 640 480
|
||||
clean_uvc_format_ mjpeg/m 1280 720
|
||||
clean_uvc_format_ mjpeg/m 1920 1080
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/mjpeg/m
|
||||
}
|
||||
|
||||
configure_uvc_link_()
|
||||
{
|
||||
mkdir $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/streaming/mjpeg/m/ $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/m
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/streaming/uncompressed/y/ $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/y
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/ $GFUNC_PATH/$UVC_INSTANCE/streaming/class/fs
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/ $GFUNC_PATH/$UVC_INSTANCE/streaming/class/hs
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/ $GFUNC_PATH/$UVC_INSTANCE/streaming/class/ss
|
||||
mkdir $GFUNC_PATH/$UVC_INSTANCE/control/header/h
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/control/header/h/ $GFUNC_PATH/$UVC_INSTANCE/control/class/fs/
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/control/header/h/ $GFUNC_PATH/$UVC_INSTANCE/control/class/ss/
|
||||
}
|
||||
|
||||
clean_uvc_link_()
|
||||
{
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/control/class/fs/h
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/control/class/ss/h
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/control/header/h
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/class/ss/h
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/class/hs/h
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/class/fs/h
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/m
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h/y
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE/streaming/header/h
|
||||
}
|
||||
|
||||
clean_uvc_()
|
||||
{
|
||||
clean_uvc_link_
|
||||
clean_uvc_format_all_
|
||||
g_remove $GFUNC_PATH/$UVC_INSTANCE
|
||||
}
|
||||
|
||||
configure_uvc_maxpacket_()
|
||||
{
|
||||
MAX=$1 ## $1 1024/2048/3072
|
||||
FUNCTION=$GFUNC_PATH/$UVC_INSTANCE
|
||||
gadget_info "Setting streaming_maxpacket to $MAX"
|
||||
echo $MAX > $FUNCTION/streaming_maxpacket
|
||||
echo 15 > $FUNCTION/streaming_maxburst
|
||||
}
|
||||
|
||||
configure_uvc_xu_()
|
||||
{
|
||||
# Include an Extension Unit if the kernel supports that
|
||||
CONTROL_PATH=$GFUNC_PATH/$UVC_INSTANCE/control/
|
||||
if [ -d $CONTROL_PATH/extensions ]; then
|
||||
mkdir $CONTROL_PATH/extensions/xu.0
|
||||
pushd $CONTROL_PATH/extensions/xu.0
|
||||
# Set the bUnitID of the Processing Unit as the XU's source
|
||||
echo 2 > baSourceID
|
||||
# Set this XU as the source for the default output terminal
|
||||
cat bUnitID > ../../terminal/output/default/bSourceID
|
||||
# Flag some arbitrary controls. This sets alternating bits of the
|
||||
# first byte of bmControls active.
|
||||
echo 0x55 > bmControls
|
||||
# Set the GUID
|
||||
echo -e -n "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" > guidExtensionCode
|
||||
popd
|
||||
fi
|
||||
}
|
||||
|
||||
### End UVC Common
|
||||
## UVC
|
||||
|
||||
uvc_config()
|
||||
{
|
||||
gadget_debug "add a uvc function instance"
|
||||
UVC_INSTANCE=uvc.usb0
|
||||
mkdir -p $GFUNC_PATH/$UVC_INSTANCE
|
||||
configure_uvc_format_ uncompressed/y 640 360 0
|
||||
configure_uvc_format_ uncompressed/y 640 480 0
|
||||
configure_uvc_format_ uncompressed/y 1280 720 1
|
||||
configure_uvc_format_ uncompressed/y 1920 1080 1
|
||||
configure_uvc_format_ mjpeg/m 640 360 1
|
||||
configure_uvc_format_ mjpeg/m 1280 720 1
|
||||
configure_uvc_format_ mjpeg/m 1920 1080 1
|
||||
## TODO: H.264 and HEVC(265)
|
||||
## Latest Ongoing: https://patchwork.kernel.org/project/linux-usb/patch/20240711082304.1363-1-quic_akakum@quicinc.com/# configure_uvc_format_ h264/h 640 360 1
|
||||
# configure_uvc_format_ h264/h 1280 720 1
|
||||
# configure_uvc_format_ h264/h 1920 1080 1
|
||||
# configure_uvc_format_ hevc/h 640 360 1
|
||||
# configure_uvc_format_ hevc/h 1280 720 1
|
||||
# configure_uvc_format_ hevc/h 1920 1080 1
|
||||
configure_uvc_maxpacket_ $MAXPACKAGESIZE
|
||||
configure_uvc_link_
|
||||
}
|
||||
|
||||
uvc_link()
|
||||
{
|
||||
gadget_debug "add uvc to usb config, unlike adb, you have to run ur own uvc-gadget app"
|
||||
UVC_INSTANCE=uvc.usb0
|
||||
ln -s $GFUNC_PATH/$UVC_INSTANCE/ $GCONFIG/$UVC_INSTANCE
|
||||
}
|
||||
|
||||
uvc_unlink()
|
||||
{
|
||||
gadget_debug "remove uvc from usb config"
|
||||
UVC_INSTANCE=uvc.usb0
|
||||
g_remove $GCONFIG/$UVC_INSTANCE
|
||||
}
|
||||
|
||||
uvc_clean()
|
||||
{
|
||||
gadget_debug "clean uvc"
|
||||
UVC_INSTANCE=uvc.usb0
|
||||
clean_uvc_
|
||||
}
|
||||
|
||||
## RNDIS
|
||||
|
||||
rndis_config()
|
||||
{
|
||||
OVERRIDE_VENDOR_FOR_WINDOWS=$1
|
||||
# create function instance
|
||||
# functions/<f_function allowed>.<instance name>
|
||||
# f_function allowed: rndis
|
||||
mkdir -p $GFUNC_PATH/rndis.0
|
||||
}
|
||||
|
||||
rndis_link()
|
||||
{
|
||||
ln -s $GFUNC_PATH/rndis.0 $GCONFIG
|
||||
HOST_ADDR=`cat $GFUNC_PATH/rndis.0/host_addr`
|
||||
DEV_ADDR=`cat $GFUNC_PATH/rndis.0/dev_addr`
|
||||
IFNAME=`cat $GFUNC_PATH/rndis.0/ifname`
|
||||
gadget_info "rndis function enabled, mac(h): $HOST_ADDR, mac(g): $DEV_ADDR, ifname: $IFNAME."
|
||||
gadget_info "execute ifconfig $IFNAME up to enable rndis iface."
|
||||
}
|
||||
|
||||
rndis_unlink()
|
||||
{
|
||||
[ -e $GFUNC_PATH/rndis.0/ifname ] && ifconfig `cat $GFUNC_PATH/rndis.0/ifname` down
|
||||
g_remove $GCONFIG/rndis.0
|
||||
}
|
||||
|
||||
rndis_clean()
|
||||
{
|
||||
g_remove $GFUNC_PATH/rndis.0
|
||||
}
|
||||
|
||||
## MTP
|
||||
|
||||
mtp_config()
|
||||
{
|
||||
die "MTP Not Supported yet."
|
||||
}
|
||||
|
||||
mtp_link()
|
||||
{
|
||||
die "MTP Not Supported yet."
|
||||
}
|
||||
|
||||
mtp_unlink()
|
||||
{
|
||||
die "MTP Not Supported yet."
|
||||
}
|
||||
|
||||
mtp_clean()
|
||||
{
|
||||
die "MTP Not Supported yet."
|
||||
}
|
||||
|
||||
## GADGET
|
||||
no_udc()
|
||||
{
|
||||
gadget_info "Echo none to udc"
|
||||
logger "We are now trying to echo None to UDC......"
|
||||
[ -e $GADGET_PATH/UDC ] || die "gadget not configured yet"
|
||||
[ `cat $GADGET_PATH/UDC` ] && echo "" > $GADGET_PATH/UDC
|
||||
gadget_info "echo none to UDC successfully done"
|
||||
logger "echo none to UDC done."
|
||||
}
|
||||
|
||||
echo_udc()
|
||||
{
|
||||
[ $USBDEV_IDX ] || USBDEV_IDX=1
|
||||
[ -e $GADGET_PATH/UDC ] || die "gadget not configured yet"
|
||||
[ `cat $GADGET_PATH/UDC` ] && die "UDC `cat $GADGET_PATH/UDC` already been set"
|
||||
if [ "$USB_UDC_IDX" ]; then
|
||||
selected_udc=$(ls /sys/class/udc | awk "NR==$USBDEV_IDX{print}")
|
||||
else
|
||||
selected_udc=$USB_UDC
|
||||
gadget_info "Selected udc idx $USBDEV_IDX: $selected_udc"
|
||||
gadget_info "We are now trying to echo $selected_udc to UDC......"
|
||||
fi
|
||||
echo $selected_udc > $GADGET_PATH/UDC
|
||||
gadget_info "echo $selected_udc to UDC done"
|
||||
}
|
||||
|
||||
gconfig()
|
||||
{
|
||||
gadget_info "config $VENDOR_ID/$PRODUC_ID/$SERNUM_STR/$MANUAF_STR/$PRODUC_STR."
|
||||
mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config
|
||||
[ -e $GADGET_PATH ] && die "ERROR: gadget already configured, should run stop first"
|
||||
mkdir $GADGET_PATH
|
||||
echo $VENDOR_ID > $GADGET_PATH/idVendor
|
||||
echo $PRODUC_ID > $GADGET_PATH/idProduct
|
||||
mkdir $GADGET_PATH/strings/0x409
|
||||
echo $SERNUM_STR > $GADGET_PATH/strings/0x409/serialnumber
|
||||
echo $MANUAF_STR > $GADGET_PATH/strings/0x409/manufacturer
|
||||
echo $PRODUC_STR > $GADGET_PATH/strings/0x409/product
|
||||
mkdir $GCONFIG
|
||||
echo 0xc0 > $GCONFIG/bmAttributes
|
||||
echo 500 > $GCONFIG/MaxPower
|
||||
mkdir $GCONFIG/strings/0x409
|
||||
[ $MSC = okay ] && msc_config
|
||||
[ $UAS = okay ] && uas_config
|
||||
[ $RNDIS = okay ] && rndis_config
|
||||
[ $ADB = okay ] && adb_config
|
||||
[ $UVC = okay ] && uvc_config
|
||||
}
|
||||
|
||||
gclean()
|
||||
{
|
||||
[ -e $GADGET_PATH/UDC ] || die "gadget not configured, no need to clean"
|
||||
msc_clean
|
||||
uas_clean
|
||||
rndis_clean
|
||||
adb_clean
|
||||
uvc_clean
|
||||
# Remove string in gadget
|
||||
gadget_info "remove strings of $GADGET_PATH."
|
||||
g_remove $GADGET_PATH/strings/0x409
|
||||
# Remove gadget
|
||||
gadget_info "remove $GADGET_PATH."
|
||||
g_remove $GADGET_PATH
|
||||
}
|
||||
|
||||
glink()
|
||||
{
|
||||
[ $MSC = okay ] && msc_link
|
||||
[ $UAS = okay ] && uas_link
|
||||
[ $RNDIS = okay ] && rndis_link
|
||||
[ $ADB = okay ] && adb_link
|
||||
[ $UVC = okay ] && uvc_link
|
||||
[ $UVCH = okay ] && uvch_link
|
||||
}
|
||||
|
||||
gunlink()
|
||||
{
|
||||
[ -e $GADGET_PATH/UDC ] || die "gadget not configured yet"
|
||||
msc_unlink
|
||||
uas_unlink
|
||||
rndis_unlink
|
||||
# adb_unlink
|
||||
uvc_unlink
|
||||
# Remove strings:
|
||||
gadget_info "remove strings of c.1."
|
||||
g_remove $GCONFIG/strings/0x409
|
||||
# Remove config:
|
||||
gadget_info "remove configs c.1."
|
||||
g_remove $GCONFIG
|
||||
}
|
||||
|
||||
select_one()
|
||||
{
|
||||
func=$1
|
||||
|
||||
if [[ "$func" == "#"* ]];then
|
||||
gadget_debug "met hashtag, skip"
|
||||
return
|
||||
fi
|
||||
|
||||
case "$func" in
|
||||
msc*|mass*|storage*)
|
||||
MSC=okay
|
||||
MSC_ARG=$(echo $func | awk -F: '{print $2}')
|
||||
;;
|
||||
"uvc"|"video|webcam")
|
||||
UVC=okay
|
||||
;;
|
||||
"uvch"|"videoh")
|
||||
UVCH=okay
|
||||
;;
|
||||
uas*|uasp*)
|
||||
UAS=okay
|
||||
UAS_ARG=$(echo $func | awk -F: '{print $2}')
|
||||
;;
|
||||
"rndis"|"network"|"net"|"if")
|
||||
RNDIS=okay
|
||||
;;
|
||||
"mtp")
|
||||
MTP=okay
|
||||
;;
|
||||
"adb"|"fastboot"|"adbd")
|
||||
ADB=okay
|
||||
;;
|
||||
*)
|
||||
die "not supported function: $func"
|
||||
;;
|
||||
esac
|
||||
gadget_info "Selected function $func"
|
||||
let FUNCTION_CNT=FUNCTION_CNT+1
|
||||
}
|
||||
|
||||
handle_select() {
|
||||
local input_str=$1
|
||||
local IFS=, # split via comma
|
||||
OLDIFS=$IFS # split functions
|
||||
IFS=,
|
||||
for token in $input_str; do
|
||||
[ $DEBUG ]
|
||||
select_one $token
|
||||
done
|
||||
IFS=$OLDIFS
|
||||
}
|
||||
|
||||
parse_config()
|
||||
{
|
||||
[ -e $CONFIG_FILE ] || die "$CONFIG_FILE not found, abort."
|
||||
while read line
|
||||
do
|
||||
select_one $line
|
||||
done < $CONFIG_FILE
|
||||
}
|
||||
|
||||
gstart()
|
||||
{
|
||||
gconfig
|
||||
glink
|
||||
[ $FUNCTION_CNT -lt 1 ] && die "No function selected, will not pullup."
|
||||
echo_udc $1
|
||||
}
|
||||
|
||||
gstop()
|
||||
{
|
||||
no_udc
|
||||
gunlink
|
||||
gclean
|
||||
}
|
||||
|
||||
## MAIN
|
||||
case "$1" in
|
||||
stop|clean)
|
||||
gstop
|
||||
;;
|
||||
restart|reload)
|
||||
gstop
|
||||
parse_config
|
||||
gstart
|
||||
;;
|
||||
start)
|
||||
parse_config
|
||||
gstart $2
|
||||
;;
|
||||
pause|disconnect)
|
||||
no_udc
|
||||
;;
|
||||
resume|connect)
|
||||
USBDEV_IDX=$2
|
||||
echo_udc
|
||||
;;
|
||||
config)
|
||||
vi $CONFIG_FILE
|
||||
[ -e $CONFIG_FILE ] && gadget_info ".usb_config updated"
|
||||
;;
|
||||
help)
|
||||
usage
|
||||
;;
|
||||
info)
|
||||
print_info
|
||||
;;
|
||||
[a-z]*)
|
||||
handle_select $1
|
||||
gstart $2
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $?
|
50
scripts/gen-version.sh
Normal file
50
scripts/gen-version.sh
Normal file
|
@ -0,0 +1,50 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Generate a version string using git describe
|
||||
|
||||
build_dir="$1"
|
||||
src_dir="$2"
|
||||
|
||||
# If .tarball-version exists, output the version string from the file and exit.
|
||||
# This file is auto-generated on a 'meson dist' command from the run-dist.sh
|
||||
# script.
|
||||
if [ -n "$src_dir" ] && [ -f "$src_dir"/.tarball-version ]
|
||||
then
|
||||
cat "$src_dir"/.tarball-version
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Bail out if the directory isn't under git control
|
||||
git_dir=$(git rev-parse --git-dir 2>&1) || exit 1
|
||||
|
||||
# Derive the source directory from the git directory if not specified.
|
||||
if [ -z "$src_dir" ]
|
||||
then
|
||||
src_dir=$(readlink -f "$git_dir/..")
|
||||
fi
|
||||
|
||||
# Get a short description from the tree.
|
||||
version=$(git describe --abbrev=8 --match "v[0-9]*" 2>/dev/null)
|
||||
|
||||
if [ -z "$version" ]
|
||||
then
|
||||
# Handle an un-tagged repository
|
||||
sha=$(git describe --abbrev=8 --always 2>/dev/null)
|
||||
commits=$(git log --oneline | wc -l 2>/dev/null)
|
||||
version="v0.0.0-$commits-g$sha"
|
||||
fi
|
||||
|
||||
# Append a '-dirty' suffix if the working tree is dirty. Prevent false
|
||||
# positives due to changed timestamps by running git update-index.
|
||||
if [ -z "$build_dir" ] || (echo "$build_dir" | grep -q "$src_dir")
|
||||
then
|
||||
git update-index --refresh > /dev/null 2>&1
|
||||
fi
|
||||
git diff-index --quiet HEAD || version="$version-dirty ($(date --iso-8601=seconds))"
|
||||
|
||||
# Replace first '-' with a '+' to denote build metadata, strip the 'g' in from
|
||||
# of the git SHA1 and remove the initial 'v'.
|
||||
version=$(echo "$version" | sed -e 's/-/+/' | sed -e 's/-g/-/' | cut -c 2-)
|
||||
|
||||
echo "$version"
|
46
scripts/release.sh
Normal file
46
scripts/release.sh
Normal file
|
@ -0,0 +1,46 @@
|
|||
#!/bin/sh
|
||||
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Prepare a project release
|
||||
|
||||
set -e
|
||||
|
||||
# Abort if we are not within the project root or the tree is not clean.
|
||||
if [ ! -e scripts/gen-version.sh ] || [ ! -e .git ]; then
|
||||
echo "This release script must be run from the root of uvc-gadget git tree."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! git diff-index --quiet HEAD; then
|
||||
echo "Tree must be clean to release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Identify current version components
|
||||
version=$(./scripts/gen-version.sh)
|
||||
|
||||
# Decide if we are here to bump major, minor, or patch release.
|
||||
case $1 in
|
||||
major|minor|patch)
|
||||
bump=$1;
|
||||
;;
|
||||
*)
|
||||
echo "You must specify the version bump level: (major, minor, patch)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
new_version=$(./scripts/semver bump "$bump" "$version")
|
||||
|
||||
echo "Bumping $bump"
|
||||
echo " Existing version is: $version"
|
||||
echo " New version is : $new_version"
|
||||
|
||||
# Patch in the version to our meson.build
|
||||
sed -i -E "s/ version : '.*',/ version : '$new_version',/" meson.build
|
||||
|
||||
# Commit the update
|
||||
git commit meson.build -esm "uvc-gadget v$new_version"
|
||||
|
||||
# Create a tag from that commit
|
||||
git show -s --format=%B | git tag "v$new_version" -s -F -
|
446
scripts/semver
Normal file
446
scripts/semver
Normal file
|
@ -0,0 +1,446 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -o errexit -o nounset -o pipefail
|
||||
|
||||
NAT='0|[1-9][0-9]*'
|
||||
ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*'
|
||||
IDENT="$NAT|$ALPHANUM"
|
||||
FIELD='[0-9A-Za-z-]+'
|
||||
|
||||
SEMVER_REGEX="\
|
||||
^[vV]?\
|
||||
($NAT)\\.($NAT)\\.($NAT)\
|
||||
(\\-(${IDENT})(\\.(${IDENT}))*)?\
|
||||
(\\+${FIELD}(\\.${FIELD})*)?$"
|
||||
|
||||
PROG=semver
|
||||
PROG_VERSION="3.4.0"
|
||||
|
||||
USAGE="\
|
||||
Usage:
|
||||
$PROG bump major <version>
|
||||
$PROG bump minor <version>
|
||||
$PROG bump patch <version>
|
||||
$PROG bump prerel|prerelease [<prerel>] <version>
|
||||
$PROG bump build <build> <version>
|
||||
$PROG bump release <version>
|
||||
$PROG get major <version>
|
||||
$PROG get minor <version>
|
||||
$PROG get patch <version>
|
||||
$PROG get prerel|prerelease <version>
|
||||
$PROG get build <version>
|
||||
$PROG get release <version>
|
||||
$PROG compare <version> <other_version>
|
||||
$PROG diff <version> <other_version>
|
||||
$PROG validate <version>
|
||||
$PROG --help
|
||||
$PROG --version
|
||||
|
||||
Arguments:
|
||||
<version> A version must match the following regular expression:
|
||||
\"${SEMVER_REGEX}\"
|
||||
In English:
|
||||
-- The version must match X.Y.Z[-PRERELEASE][+BUILD]
|
||||
where X, Y and Z are non-negative integers.
|
||||
-- PRERELEASE is a dot separated sequence of non-negative integers and/or
|
||||
identifiers composed of alphanumeric characters and hyphens (with
|
||||
at least one non-digit). Numeric identifiers must not have leading
|
||||
zeros. A hyphen (\"-\") introduces this optional part.
|
||||
-- BUILD is a dot separated sequence of identifiers composed of alphanumeric
|
||||
characters and hyphens. A plus (\"+\") introduces this optional part.
|
||||
|
||||
<other_version> See <version> definition.
|
||||
|
||||
<prerel> A string as defined by PRERELEASE above. Or, it can be a PRERELEASE
|
||||
prototype string followed by a dot.
|
||||
|
||||
<build> A string as defined by BUILD above.
|
||||
|
||||
Options:
|
||||
-v, --version Print the version of this tool.
|
||||
-h, --help Print this help message.
|
||||
|
||||
Commands:
|
||||
bump Bump by one of major, minor, patch; zeroing or removing
|
||||
subsequent parts. \"bump prerel\" (or its synonym \"bump prerelease\")
|
||||
sets the PRERELEASE part and removes any BUILD part. A trailing dot
|
||||
in the <prerel> argument introduces an incrementing numeric field
|
||||
which is added or bumped. If no <prerel> argument is provided, an
|
||||
incrementing numeric field is introduced/bumped. \"bump build\" sets
|
||||
the BUILD part. \"bump release\" removes any PRERELEASE or BUILD parts.
|
||||
The bumped version is written to stdout.
|
||||
|
||||
get Extract given part of <version>, where part is one of major, minor,
|
||||
patch, prerel (alternatively: prerelease), build, or release.
|
||||
|
||||
compare Compare <version> with <other_version>, output to stdout the
|
||||
following values: -1 if <other_version> is newer, 0 if equal, 1 if
|
||||
older. The BUILD part is not used in comparisons.
|
||||
|
||||
diff Compare <version> with <other_version>, output to stdout the
|
||||
difference between two versions by the release type (MAJOR, MINOR,
|
||||
PATCH, PRERELEASE, BUILD).
|
||||
|
||||
validate Validate if <version> follows the SEMVER pattern (see <version>
|
||||
definition). Print 'valid' to stdout if the version is valid, otherwise
|
||||
print 'invalid'.
|
||||
|
||||
See also:
|
||||
https://semver.org -- Semantic Versioning 2.0.0"
|
||||
|
||||
function error {
|
||||
echo -e "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
function usage_help {
|
||||
error "$USAGE"
|
||||
}
|
||||
|
||||
function usage_version {
|
||||
echo -e "${PROG}: $PROG_VERSION"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# normalize the "part" keywords to a canonical string. At present,
|
||||
# only "prerelease" is normalized to "prerel".
|
||||
|
||||
function normalize_part {
|
||||
if [ "$1" == "prerelease" ]
|
||||
then
|
||||
echo "prerel"
|
||||
else
|
||||
echo "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
function validate_version {
|
||||
local version=$1
|
||||
if [[ "$version" =~ $SEMVER_REGEX ]]; then
|
||||
# if a second argument is passed, store the result in var named by $2
|
||||
if [ "$#" -eq "2" ]; then
|
||||
local major=${BASH_REMATCH[1]}
|
||||
local minor=${BASH_REMATCH[2]}
|
||||
local patch=${BASH_REMATCH[3]}
|
||||
local prere=${BASH_REMATCH[4]}
|
||||
local build=${BASH_REMATCH[8]}
|
||||
eval "$2=(\"$major\" \"$minor\" \"$patch\" \"$prere\" \"$build\")"
|
||||
else
|
||||
echo "$version"
|
||||
fi
|
||||
else
|
||||
error "version $version does not match the semver scheme 'X.Y.Z(-PRERELEASE)(+BUILD)'. See help for more information."
|
||||
fi
|
||||
}
|
||||
|
||||
function is_nat {
|
||||
[[ "$1" =~ ^($NAT)$ ]]
|
||||
}
|
||||
|
||||
function is_null {
|
||||
[ -z "$1" ]
|
||||
}
|
||||
|
||||
function order_nat {
|
||||
[ "$1" -lt "$2" ] && { echo -1 ; return ; }
|
||||
[ "$1" -gt "$2" ] && { echo 1 ; return ; }
|
||||
echo 0
|
||||
}
|
||||
|
||||
function order_string {
|
||||
[[ $1 < $2 ]] && { echo -1 ; return ; }
|
||||
[[ $1 > $2 ]] && { echo 1 ; return ; }
|
||||
echo 0
|
||||
}
|
||||
|
||||
# given two (named) arrays containing NAT and/or ALPHANUM fields, compare them
|
||||
# one by one according to semver 2.0.0 spec. Return -1, 0, 1 if left array ($1)
|
||||
# is less-than, equal, or greater-than the right array ($2). The longer array
|
||||
# is considered greater-than the shorter if the shorter is a prefix of the longer.
|
||||
#
|
||||
function compare_fields {
|
||||
local l="$1[@]"
|
||||
local r="$2[@]"
|
||||
local leftfield=( "${!l}" )
|
||||
local rightfield=( "${!r}" )
|
||||
local left
|
||||
local right
|
||||
|
||||
local i=$(( -1 ))
|
||||
local order=$(( 0 ))
|
||||
|
||||
while true
|
||||
do
|
||||
[ $order -ne 0 ] && { echo $order ; return ; }
|
||||
|
||||
: $(( i++ ))
|
||||
left="${leftfield[$i]}"
|
||||
right="${rightfield[$i]}"
|
||||
|
||||
is_null "$left" && is_null "$right" && { echo 0 ; return ; }
|
||||
is_null "$left" && { echo -1 ; return ; }
|
||||
is_null "$right" && { echo 1 ; return ; }
|
||||
|
||||
is_nat "$left" && is_nat "$right" && { order=$(order_nat "$left" "$right") ; continue ; }
|
||||
is_nat "$left" && { echo -1 ; return ; }
|
||||
is_nat "$right" && { echo 1 ; return ; }
|
||||
{ order=$(order_string "$left" "$right") ; continue ; }
|
||||
done
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2206 # checked by "validate"; ok to expand prerel id's into array
|
||||
function compare_version {
|
||||
local order
|
||||
validate_version "$1" V
|
||||
validate_version "$2" V_
|
||||
|
||||
# compare major, minor, patch
|
||||
|
||||
local left=( "${V[0]}" "${V[1]}" "${V[2]}" )
|
||||
local right=( "${V_[0]}" "${V_[1]}" "${V_[2]}" )
|
||||
|
||||
order=$(compare_fields left right)
|
||||
[ "$order" -ne 0 ] && { echo "$order" ; return ; }
|
||||
|
||||
# compare pre-release ids when M.m.p are equal
|
||||
|
||||
local prerel="${V[3]:1}"
|
||||
local prerel_="${V_[3]:1}"
|
||||
local left=( ${prerel//./ } )
|
||||
local right=( ${prerel_//./ } )
|
||||
|
||||
# if left and right have no pre-release part, then left equals right
|
||||
# if only one of left/right has pre-release part, that one is less than simple M.m.p
|
||||
|
||||
[ -z "$prerel" ] && [ -z "$prerel_" ] && { echo 0 ; return ; }
|
||||
[ -z "$prerel" ] && { echo 1 ; return ; }
|
||||
[ -z "$prerel_" ] && { echo -1 ; return ; }
|
||||
|
||||
# otherwise, compare the pre-release id's
|
||||
|
||||
compare_fields left right
|
||||
}
|
||||
|
||||
# render_prerel -- return a prerel field with a trailing numeric string
|
||||
# usage: render_prerel numeric [prefix-string]
|
||||
#
|
||||
function render_prerel {
|
||||
if [ -z "$2" ]
|
||||
then
|
||||
echo "${1}"
|
||||
else
|
||||
echo "${2}${1}"
|
||||
fi
|
||||
}
|
||||
|
||||
# extract_prerel -- extract prefix and trailing numeric portions of a pre-release part
|
||||
# usage: extract_prerel prerel prerel_parts
|
||||
# The prefix and trailing numeric parts are returned in "prerel_parts".
|
||||
#
|
||||
PREFIX_ALPHANUM='[.0-9A-Za-z-]*[.A-Za-z-]'
|
||||
DIGITS='[0-9][0-9]*'
|
||||
EXTRACT_REGEX="^(${PREFIX_ALPHANUM})*(${DIGITS})$"
|
||||
|
||||
function extract_prerel {
|
||||
local prefix; local numeric;
|
||||
|
||||
if [[ "$1" =~ $EXTRACT_REGEX ]]
|
||||
then # found prefix and trailing numeric parts
|
||||
prefix="${BASH_REMATCH[1]}"
|
||||
numeric="${BASH_REMATCH[2]}"
|
||||
else # no numeric part
|
||||
prefix="${1}"
|
||||
numeric=
|
||||
fi
|
||||
|
||||
eval "$2=(\"$prefix\" \"$numeric\")"
|
||||
}
|
||||
|
||||
# bump_prerel -- return the new pre-release part based on previous pre-release part
|
||||
# and prototype for bump
|
||||
# usage: bump_prerel proto previous
|
||||
#
|
||||
function bump_prerel {
|
||||
local proto; local prev_prefix; local prev_numeric;
|
||||
|
||||
# case one: no trailing dot in prototype => simply replace previous with proto
|
||||
if [[ ! ( "$1" =~ \.$ ) ]]
|
||||
then
|
||||
echo "$1"
|
||||
return
|
||||
fi
|
||||
|
||||
proto="${1%.}" # discard trailing dot marker from prototype
|
||||
|
||||
extract_prerel "${2#-}" prerel_parts # extract parts of previous pre-release
|
||||
# shellcheck disable=SC2154
|
||||
prev_prefix="${prerel_parts[0]}"
|
||||
prev_numeric="${prerel_parts[1]}"
|
||||
|
||||
# case two: bump or append numeric to previous pre-release part
|
||||
if [ "$proto" == "+" ] # dummy "+" indicates no prototype argument provided
|
||||
then
|
||||
if [ -n "$prev_numeric" ]
|
||||
then
|
||||
: $(( ++prev_numeric )) # previous pre-release is already numbered, bump it
|
||||
render_prerel "$prev_numeric" "$prev_prefix"
|
||||
else
|
||||
render_prerel 1 "$prev_prefix" # append starting number
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
# case three: set, bump, or append using prototype prefix
|
||||
if [ "$prev_prefix" != "$proto" ]
|
||||
then
|
||||
render_prerel 1 "$proto" # proto not same pre-release; set and start at '1'
|
||||
elif [ -n "$prev_numeric" ]
|
||||
then
|
||||
: $(( ++prev_numeric )) # pre-release is numbered; bump it
|
||||
render_prerel "$prev_numeric" "$prev_prefix"
|
||||
else
|
||||
render_prerel 1 "$prev_prefix" # start pre-release at number '1'
|
||||
fi
|
||||
}
|
||||
|
||||
function command_bump {
|
||||
local new; local version; local sub_version; local command;
|
||||
|
||||
command="$(normalize_part "$1")"
|
||||
|
||||
case $# in
|
||||
2) case "$command" in
|
||||
major|minor|patch|prerel|release) sub_version="+."; version=$2;;
|
||||
*) usage_help;;
|
||||
esac ;;
|
||||
3) case "$command" in
|
||||
prerel|build) sub_version=$2 version=$3 ;;
|
||||
*) usage_help;;
|
||||
esac ;;
|
||||
*) usage_help;;
|
||||
esac
|
||||
|
||||
validate_version "$version" parts
|
||||
# shellcheck disable=SC2154
|
||||
local major="${parts[0]}"
|
||||
local minor="${parts[1]}"
|
||||
local patch="${parts[2]}"
|
||||
local prere="${parts[3]}"
|
||||
local build="${parts[4]}"
|
||||
|
||||
case "$command" in
|
||||
major) new="$((major + 1)).0.0";;
|
||||
minor) new="${major}.$((minor + 1)).0";;
|
||||
patch) new="${major}.${minor}.$((patch + 1))";;
|
||||
release) new="${major}.${minor}.${patch}";;
|
||||
prerel) new=$(validate_version "${major}.${minor}.${patch}-$(bump_prerel "$sub_version" "$prere")");;
|
||||
build) new=$(validate_version "${major}.${minor}.${patch}${prere}+${sub_version}");;
|
||||
*) usage_help ;;
|
||||
esac
|
||||
|
||||
echo "$new"
|
||||
exit 0
|
||||
}
|
||||
|
||||
function command_compare {
|
||||
local v; local v_;
|
||||
|
||||
case $# in
|
||||
2) v=$(validate_version "$1"); v_=$(validate_version "$2") ;;
|
||||
*) usage_help ;;
|
||||
esac
|
||||
|
||||
set +u # need unset array element to evaluate to null
|
||||
compare_version "$v" "$v_"
|
||||
exit 0
|
||||
}
|
||||
|
||||
function command_diff {
|
||||
validate_version "$1" v1_parts
|
||||
# shellcheck disable=SC2154
|
||||
local v1_major="${v1_parts[0]}"
|
||||
local v1_minor="${v1_parts[1]}"
|
||||
local v1_patch="${v1_parts[2]}"
|
||||
local v1_prere="${v1_parts[3]}"
|
||||
local v1_build="${v1_parts[4]}"
|
||||
|
||||
validate_version "$2" v2_parts
|
||||
# shellcheck disable=SC2154
|
||||
local v2_major="${v2_parts[0]}"
|
||||
local v2_minor="${v2_parts[1]}"
|
||||
local v2_patch="${v2_parts[2]}"
|
||||
local v2_prere="${v2_parts[3]}"
|
||||
local v2_build="${v2_parts[4]}"
|
||||
|
||||
if [ "${v1_major}" != "${v2_major}" ]; then
|
||||
echo "major"
|
||||
elif [ "${v1_minor}" != "${v2_minor}" ]; then
|
||||
echo "minor"
|
||||
elif [ "${v1_patch}" != "${v2_patch}" ]; then
|
||||
echo "patch"
|
||||
elif [ "${v1_prere}" != "${v2_prere}" ]; then
|
||||
echo "prerelease"
|
||||
elif [ "${v1_build}" != "${v2_build}" ]; then
|
||||
echo "build"
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2034
|
||||
function command_get {
|
||||
local part version
|
||||
|
||||
if [[ "$#" -ne "2" ]] || [[ -z "$1" ]] || [[ -z "$2" ]]; then
|
||||
usage_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
part="$1"
|
||||
version="$2"
|
||||
|
||||
validate_version "$version" parts
|
||||
local major="${parts[0]}"
|
||||
local minor="${parts[1]}"
|
||||
local patch="${parts[2]}"
|
||||
local prerel="${parts[3]:1}"
|
||||
local build="${parts[4]:1}"
|
||||
local release="${major}.${minor}.${patch}"
|
||||
|
||||
part="$(normalize_part "$part")"
|
||||
|
||||
case "$part" in
|
||||
major|minor|patch|release|prerel|build) echo "${!part}" ;;
|
||||
*) usage_help ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
function command_validate {
|
||||
if [[ "$#" -ne "1" ]]; then
|
||||
usage_help
|
||||
fi
|
||||
|
||||
if [[ "$1" =~ $SEMVER_REGEX ]]; then
|
||||
echo "valid"
|
||||
else
|
||||
echo "invalid"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
case $# in
|
||||
0) echo "Unknown command: $*"; usage_help;;
|
||||
esac
|
||||
|
||||
case $1 in
|
||||
--help|-h) echo -e "$USAGE"; exit 0;;
|
||||
--version|-v) usage_version ;;
|
||||
bump) shift; command_bump "$@";;
|
||||
get) shift; command_get "$@";;
|
||||
compare) shift; command_compare "$@";;
|
||||
diff) shift; command_diff "$@";;
|
||||
validate) shift; command_validate "$@";;
|
||||
*) echo "Unknown arguments: $*"; usage_help;;
|
||||
esac
|
185
src/main.c
Normal file
185
src/main.c
Normal file
|
@ -0,0 +1,185 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* UVC gadget test application
|
||||
*
|
||||
* Copyright (C) 2010-2018 Laurent Pinchart
|
||||
*
|
||||
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "configfs.h"
|
||||
#include "events.h"
|
||||
#include "stream.h"
|
||||
#include "libcamera-source.h"
|
||||
#include "v4l2-source.h"
|
||||
#include "test-source.h"
|
||||
#include "jpg-source.h"
|
||||
#include "slideshow-source.h"
|
||||
|
||||
static void usage(const char *argv0)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [options] <uvc device>\n", argv0);
|
||||
fprintf(stderr, "Available options are\n");
|
||||
#ifdef HAVE_LIBCAMERA
|
||||
fprintf(stderr, " -c <index|id> libcamera camera name\n");
|
||||
#endif
|
||||
fprintf(stderr, " -d device V4L2 source device\n");
|
||||
fprintf(stderr, " -i image MJPEG image\n");
|
||||
fprintf(stderr, " -s directory directory of slideshow images\n");
|
||||
fprintf(stderr, " -h Print this help screen and exit\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " <uvc device> UVC device instance specifier\n");
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
fprintf(stderr, " For ConfigFS devices the <uvc device> parameter can take the form of a shortened\n");
|
||||
fprintf(stderr, " function specifier such as: 'uvc.0', or if multiple gadgets are configured, the\n");
|
||||
fprintf(stderr, " gadget name should be included to prevent ambiguity: 'g1/functions/uvc.0'.\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " For legacy g_webcam UVC instances, this parameter will identify the UDC that the\n");
|
||||
fprintf(stderr, " UVC function is bound to.\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " The parameter is optional, and if not provided the first UVC function on the first\n");
|
||||
fprintf(stderr, " gadget identified will be used.\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Example usage:\n");
|
||||
fprintf(stderr, " %s uvc.1\n", argv0);
|
||||
fprintf(stderr, " %s g1/functions/uvc.1\n", argv0);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " %s musb-hdrc.0.auto\n", argv0);
|
||||
}
|
||||
|
||||
/* Necessary for and only used by signal handler. */
|
||||
static struct events *sigint_events;
|
||||
|
||||
static void sigint_handler(int signal __attribute__((unused)))
|
||||
{
|
||||
/* Stop the main loop when the user presses CTRL-C */
|
||||
events_stop(sigint_events);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *function = NULL;
|
||||
#ifdef HAVE_LIBCAMERA
|
||||
char *camera = NULL;
|
||||
#endif
|
||||
char *cap_device = NULL;
|
||||
char *img_path = NULL;
|
||||
char *slideshow_dir = NULL;
|
||||
|
||||
struct uvc_function_config *fc;
|
||||
struct uvc_stream *stream = NULL;
|
||||
struct video_source *src = NULL;
|
||||
struct events events;
|
||||
int ret = 0;
|
||||
int opt;
|
||||
printf("new version: enhance qbuf performance, use fixed buffer\n");
|
||||
while ((opt = getopt(argc, argv, "c:d:i:s:k:h")) != -1) {
|
||||
switch (opt) {
|
||||
#ifdef HAVE_LIBCAMERA
|
||||
case 'c':
|
||||
camera = optarg;
|
||||
break;
|
||||
#endif
|
||||
case 'd':
|
||||
cap_device = optarg;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
img_path = optarg;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
slideshow_dir = optarg;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Invalid option '-%c'\n", opt);
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (argv[optind] != NULL)
|
||||
function = argv[optind];
|
||||
|
||||
fc = configfs_parse_uvc_function(function);
|
||||
if (!fc) {
|
||||
printf("Failed to identify function configuration\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cap_device != NULL && img_path != NULL) {
|
||||
printf("Both capture device and still image specified\n");
|
||||
printf("Please specify only one\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the events handler. Register a signal handler for SIGINT,
|
||||
* received when the user presses CTRL-C. This will allow the main loop
|
||||
* to be interrupted, and resources to be freed cleanly.
|
||||
*/
|
||||
events_init(&events);
|
||||
|
||||
sigint_events = &events;
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
/* Create and initialize a video source. */
|
||||
if (cap_device)
|
||||
src = v4l2_video_source_create(cap_device);
|
||||
#ifdef HAVE_LIBCAMERA
|
||||
else if (camera)
|
||||
src = libcamera_source_create(camera);
|
||||
#endif
|
||||
else if (img_path)
|
||||
src = jpg_video_source_create(img_path);
|
||||
else if (slideshow_dir)
|
||||
src = slideshow_video_source_create(slideshow_dir);
|
||||
else
|
||||
src = test_video_source_create();
|
||||
if (src == NULL) {
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (cap_device)
|
||||
v4l2_video_source_init(src, &events);
|
||||
|
||||
#ifdef HAVE_LIBCAMERA
|
||||
if (camera)
|
||||
libcamera_source_init(src, &events);
|
||||
#endif
|
||||
|
||||
/* Create and initialise the stream. */
|
||||
stream = uvc_stream_new(fc->video);
|
||||
if (stream == NULL) {
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
uvc_stream_set_event_handler(stream, &events);
|
||||
uvc_stream_set_video_source(stream, src);
|
||||
uvc_stream_init_uvc(stream, fc);
|
||||
|
||||
/* Main capture loop */
|
||||
events_loop(&events);
|
||||
|
||||
done:
|
||||
/* Cleanup */
|
||||
uvc_stream_delete(stream);
|
||||
video_source_destroy(src);
|
||||
events_cleanup(&events);
|
||||
configfs_free_uvc_function(fc);
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Add table
Reference in a new issue