initial commit

This commit is contained in:
maguoqun 2024-08-19 10:27:41 +08:00
commit ccf48438f7
44 changed files with 9687 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
build
src/*.o
lib/*.o
*.build
*.bak
*.a
*.o
*.so
uvc-gadget-new

62
Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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 */

View 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

View 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

View 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__ */

View 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
View 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 */

View 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_ ;
};

View 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
View 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__ */

View 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
View 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);

View 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__ */

View 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

File diff suppressed because it is too large Load diff

908
lib/configfs.c Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}