summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--main.c131
-rw-r--r--stream.c250
-rw-r--r--stream.h46
-rw-r--r--uvc-gadget.c707
-rw-r--r--uvc.c340
-rw-r--r--uvc.h50
7 files changed, 820 insertions, 708 deletions
diff --git a/Makefile b/Makefile
index 4c5d498..3785140 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,9 @@ LDFLAGS := -g
OBJS := \
configfs.o \
events.o \
- uvc-gadget.o \
+ main.o \
+ stream.o \
+ uvc.o \
v4l2.o
all: uvc-gadget
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..1bff6ef
--- /dev/null
+++ b/main.c
@@ -0,0 +1,131 @@
+/*
+ * UVC gadget test application
+ *
+ * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "configfs.h"
+#include "events.h"
+#include "stream.h"
+
+static void usage(const char *argv0)
+{
+ fprintf(stderr, "Usage: %s [options] <uvc device>\n", argv0);
+ fprintf(stderr, "Available options are\n");
+ fprintf(stderr, " -c device V4L2 source device\n");
+ fprintf(stderr, " -h Print this help screen and exit\n");
+ fprintf(stderr, " -i image MJPEG image\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, " uvc-gadget uvc.1\n");
+ fprintf(stderr, " uvc-gadget g1/functions/uvc.1\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, " uvc-gadget musb-hdrc.0.auto\n");
+}
+
+/* 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;
+ char *cap_device = "/dev/video1";
+ struct uvc_function_config *fc;
+ struct uvc_stream *stream;
+ struct events events;
+ int ret = 0;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "c:h")) != -1) {
+ switch (opt) {
+ case 'c':
+ cap_device = 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;
+ }
+
+ /*
+ * 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 initialise the stream. */
+ stream = uvc_stream_new(fc->video, cap_device);
+ if (stream == NULL) {
+ ret = 1;
+ goto done;
+ }
+
+ uvc_stream_init_uvc(stream, fc);
+ uvc_stream_set_event_handler(stream, &events);
+
+ /* Main capture loop */
+ events_loop(&events);
+
+done:
+ /* Cleanup */
+ uvc_stream_delete(stream);
+ events_cleanup(&events);
+ configfs_free_uvc_function(fc);
+
+ return ret;
+}
diff --git a/stream.c b/stream.c
new file mode 100644
index 0000000..b6e0efe
--- /dev/null
+++ b/stream.c
@@ -0,0 +1,250 @@
+/*
+ * UVC stream handling
+ *
+ * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "events.h"
+#include "stream.h"
+#include "uvc.h"
+#include "v4l2.h"
+
+/* ---------------------------------------------------------------------------
+ * Video streaming
+ */
+
+static void capture_video_process(void *d)
+{
+ struct uvc_stream *stream = d;
+ struct v4l2_video_buffer buf;
+ int ret;
+
+ ret = v4l2_dequeue_buffer(stream->cap, &buf);
+ if (ret < 0)
+ return;
+
+ v4l2_queue_buffer(stream->uvc->vdev, &buf);
+}
+
+static void uvc_video_process(void *d)
+{
+ struct uvc_stream *stream = d;
+ struct v4l2_video_buffer buf;
+ int ret;
+
+ ret = v4l2_dequeue_buffer(stream->uvc->vdev, &buf);
+ if (ret < 0)
+ return;
+
+ v4l2_queue_buffer(stream->cap, &buf);
+}
+
+static int uvc_video_enable(struct uvc_stream *stream, int enable)
+{
+ struct v4l2_device *cap = stream->cap;
+ struct uvc_device *dev = stream->uvc;
+ int ret;
+
+ if (!enable) {
+ printf("Stopping video stream.\n");
+ events_unwatch_fd(stream->events, dev->vdev->fd, EVENT_WRITE);
+ v4l2_stream_off(dev->vdev);
+ v4l2_free_buffers(dev->vdev);
+ return 0;
+ }
+
+ printf("Starting video stream.\n");
+
+ ret = v4l2_alloc_buffers(dev->vdev, V4L2_MEMORY_DMABUF, 4);
+ if (ret < 0) {
+ printf("Failed to allocate buffers: %s (%d)\n", strerror(-ret), -ret);
+ return ret;
+ }
+
+ ret = v4l2_import_buffers(dev->vdev, dev->vdev->nbufs, cap->buffers);
+ if (ret < 0) {
+ printf("Failed to import buffers: %s (%d)\n", strerror(-ret), -ret);
+ goto error;
+ }
+
+ v4l2_stream_on(dev->vdev);
+ events_watch_fd(stream->events, dev->vdev->fd, EVENT_WRITE,
+ uvc_video_process, stream);
+
+ return 0;
+
+error:
+ v4l2_free_buffers(dev->vdev);
+ return ret;
+}
+
+static int capture_video_stream(struct uvc_stream *stream, int enable)
+{
+ struct v4l2_device *cap = stream->cap;
+ unsigned int i;
+ int ret;
+
+ if (!enable) {
+ printf("Stopping video capture stream.\n");
+ events_unwatch_fd(stream->events, cap->fd, EVENT_READ);
+ v4l2_stream_off(cap);
+ v4l2_free_buffers(cap);
+ return 0;
+ }
+
+ printf("Starting video capture stream.\n");
+
+ ret = v4l2_alloc_buffers(cap, V4L2_MEMORY_MMAP, 4);
+ if (ret < 0) {
+ printf("Failed to allocate capture buffers.\n");
+ return ret;
+ }
+
+ ret = v4l2_export_buffers(cap);
+ if (ret < 0) {
+ printf("Failed to export buffers.\n");
+ goto error;
+ }
+
+ for (i = 0; i < cap->nbufs; ++i) {
+ struct v4l2_video_buffer *buf = &cap->buffers[i];
+
+ ret = v4l2_queue_buffer(cap, buf);
+ if (ret < 0)
+ goto error;
+ }
+
+ v4l2_stream_on(cap);
+ events_watch_fd(stream->events, cap->fd, EVENT_READ,
+ capture_video_process, stream);
+
+ return 0;
+
+error:
+ v4l2_free_buffers(cap);
+ return ret;
+}
+
+void uvc_stream_enable(struct uvc_stream *stream, int enable)
+{
+ if (enable) {
+ capture_video_stream(stream, 1);
+ uvc_video_enable(stream, 1);
+ } else {
+ uvc_video_enable(stream, 0);
+ capture_video_stream(stream, 0);
+ }
+}
+
+int uvc_stream_set_format(struct uvc_stream *stream)
+{
+ struct v4l2_device *cap = stream->cap;
+ struct uvc_device *dev = stream->uvc;
+ struct v4l2_pix_format fmt;
+ int ret;
+
+ printf("Setting format to 0x%08x %ux%u\n",
+ dev->fcc, dev->width, dev->height);
+
+ memset(&fmt, 0, sizeof fmt);
+ fmt.width = dev->width;
+ fmt.height = dev->height;
+ fmt.pixelformat = dev->fcc;
+ fmt.field = V4L2_FIELD_NONE;
+ if (dev->fcc == V4L2_PIX_FMT_MJPEG)
+ fmt.sizeimage = dev->maxsize * 1.5;
+
+ ret = v4l2_set_format(dev->vdev, &fmt);
+ if (ret < 0)
+ return ret;
+
+ return v4l2_set_format(cap, &fmt);
+}
+
+static int uvc_video_init(struct uvc_device *dev __attribute__((__unused__)))
+{
+ return 0;
+}
+
+/* ---------------------------------------------------------------------------
+ * Stream handling
+ */
+
+struct uvc_stream *uvc_stream_new(const char *uvc_device,
+ const char *cap_device)
+{
+ struct uvc_stream *stream;
+
+ stream = malloc(sizeof(*stream));
+ if (stream == NULL)
+ return NULL;
+
+ memset(stream, 0, sizeof(*stream));
+
+ stream->cap = v4l2_open(cap_device);
+ if (stream->cap == NULL)
+ goto error;
+
+ stream->uvc = uvc_open(uvc_device);
+ if (stream->uvc == NULL)
+ goto error;
+
+ return stream;
+
+error:
+ if (stream->cap)
+ v4l2_close(stream->cap);
+
+ free(stream);
+ return NULL;
+}
+
+void uvc_stream_delete(struct uvc_stream *stream)
+{
+ if (stream == NULL)
+ return;
+
+ v4l2_close(stream->cap);
+ uvc_close(stream->uvc);
+
+ free(stream);
+}
+
+void uvc_stream_init_uvc(struct uvc_stream *stream,
+ struct uvc_function_config *fc)
+{
+ /*
+ * FIXME: The maximum size should be specified per format and frame.
+ */
+ stream->uvc->maxsize = 0;
+ stream->uvc->fc = fc;
+
+ uvc_events_init(stream->uvc);
+ uvc_video_init(stream->uvc);
+}
+
+void uvc_stream_set_event_handler(struct uvc_stream *stream,
+ struct events *events)
+{
+ stream->events = events;
+
+ events_watch_fd(stream->events, stream->uvc->vdev->fd, EVENT_EXCEPTION,
+ uvc_events_process, stream);
+}
diff --git a/stream.h b/stream.h
new file mode 100644
index 0000000..f3662d8
--- /dev/null
+++ b/stream.h
@@ -0,0 +1,46 @@
+/*
+ * UVC gadget test application
+ *
+ * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ */
+
+#ifndef __STREAM_H__
+#define __STREAM_H__
+
+struct events;
+struct uvc_device;
+struct uvc_function_config;
+struct v4l2_device;
+
+struct uvc_stream
+{
+ struct v4l2_device *cap;
+ struct uvc_device *uvc;
+
+ struct events *events;
+};
+
+struct uvc_stream *uvc_stream_new(const char *uvc_device,
+ const char *cap_device);
+void uvc_stream_init_uvc(struct uvc_stream *stream,
+ struct uvc_function_config *fc);
+void uvc_stream_set_event_handler(struct uvc_stream *stream,
+ struct events *events);
+void uvc_stream_delete(struct uvc_stream *stream);
+int uvc_stream_set_format(struct uvc_stream *stream);
+void uvc_stream_enable(struct uvc_stream *stream, int enable);
+
+#endif /* __STREAM_H__ */
diff --git a/uvc-gadget.c b/uvc-gadget.c
deleted file mode 100644
index 8ccfa07..0000000
--- a/uvc-gadget.c
+++ /dev/null
@@ -1,707 +0,0 @@
-/*
- * UVC gadget test application
- *
- * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- */
-
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <sys/select.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <sys/types.h>
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <linux/usb/ch9.h>
-#include <linux/usb/g_uvc.h>
-#include <linux/usb/video.h>
-#include <linux/videodev2.h>
-
-#include "configfs.h"
-#include "events.h"
-#include "tools.h"
-#include "v4l2.h"
-
-struct uvc_device
-{
- struct v4l2_device *vdev;
-
- struct uvc_function_config *fc;
-
- struct uvc_streaming_control probe;
- struct uvc_streaming_control commit;
-
- int control;
-
- unsigned int fcc;
- unsigned int width;
- unsigned int height;
- unsigned int maxsize;
-};
-
-struct uvc_stream
-{
- struct v4l2_device *cap;
- struct uvc_device *uvc;
-
- struct events *events;
-};
-
-static struct uvc_device *
-uvc_open(const char *devname)
-{
- struct uvc_device *dev;
-
- dev = malloc(sizeof *dev);
- if (dev == NULL)
- return NULL;
-
- memset(dev, 0, sizeof *dev);
-
- dev->vdev = v4l2_open(devname);
- if (dev->vdev == NULL) {
- free(dev);
- return NULL;
- }
-
- return dev;
-}
-
-static void
-uvc_close(struct uvc_device *dev)
-{
- v4l2_close(dev->vdev);
- dev->vdev = NULL;
-
- free(dev);
-}
-
-/* ---------------------------------------------------------------------------
- * Video streaming
- */
-
-static void capture_video_process(void *d)
-{
- struct uvc_stream *stream = d;
- struct v4l2_video_buffer buf;
- int ret;
-
- ret = v4l2_dequeue_buffer(stream->cap, &buf);
- if (ret < 0)
- return;
-
- v4l2_queue_buffer(stream->uvc->vdev, &buf);
-}
-
-static void
-uvc_video_process(void *d)
-{
- struct uvc_stream *stream = d;
- struct v4l2_video_buffer buf;
- int ret;
-
- ret = v4l2_dequeue_buffer(stream->uvc->vdev, &buf);
- if (ret < 0)
- return;
-
- v4l2_queue_buffer(stream->cap, &buf);
-}
-
-static int
-uvc_video_stream(struct uvc_stream *stream, int enable)
-{
- struct v4l2_device *cap = stream->cap;
- struct uvc_device *dev = stream->uvc;
- int ret;
-
- if (!enable) {
- printf("Stopping video stream.\n");
- events_unwatch_fd(stream->events, dev->vdev->fd, EVENT_WRITE);
- v4l2_stream_off(dev->vdev);
- v4l2_free_buffers(dev->vdev);
- return 0;
- }
-
- printf("Starting video stream.\n");
-
- ret = v4l2_alloc_buffers(dev->vdev, V4L2_MEMORY_DMABUF, 4);
- if (ret < 0) {
- printf("Failed to allocate buffers: %s (%d)\n", strerror(-ret), -ret);
- return ret;
- }
-
- ret = v4l2_import_buffers(dev->vdev, dev->vdev->nbufs, cap->buffers);
- if (ret < 0) {
- printf("Failed to import buffers: %s (%d)\n", strerror(-ret), -ret);
- goto error;
- }
-
- v4l2_stream_on(dev->vdev);
- events_watch_fd(stream->events, dev->vdev->fd, EVENT_WRITE,
- uvc_video_process, stream);
-
- return 0;
-
-error:
- v4l2_free_buffers(dev->vdev);
- return ret;
-}
-
-static int capture_video_stream(struct uvc_stream *stream, int enable)
-{
- struct v4l2_device *cap = stream->cap;
- unsigned int i;
- int ret;
-
- if (!enable) {
- printf("Stopping video capture stream.\n");
- events_unwatch_fd(stream->events, cap->fd, EVENT_READ);
- v4l2_stream_off(cap);
- v4l2_free_buffers(cap);
- return 0;
- }
-
- printf("Starting video capture stream.\n");
-
- ret = v4l2_alloc_buffers(cap, V4L2_MEMORY_MMAP, 4);
- if (ret < 0) {
- printf("Failed to allocate capture buffers.\n");
- return ret;
- }
-
- ret = v4l2_export_buffers(cap);
- if (ret < 0) {
- printf("Failed to export buffers.\n");
- goto error;
- }
-
- for (i = 0; i < cap->nbufs; ++i) {
- struct v4l2_video_buffer *buf = &cap->buffers[i];
-
- ret = v4l2_queue_buffer(cap, buf);
- if (ret < 0)
- goto error;
- }
-
- v4l2_stream_on(cap);
- events_watch_fd(stream->events, cap->fd, EVENT_READ,
- capture_video_process, stream);
-
- return 0;
-
-error:
- v4l2_free_buffers(cap);
- return ret;
-}
-
-static int
-uvc_video_set_format(struct uvc_stream *stream)
-{
- struct v4l2_device *cap = stream->cap;
- struct uvc_device *dev = stream->uvc;
- struct v4l2_pix_format fmt;
- int ret;
-
- printf("Setting format to 0x%08x %ux%u\n",
- dev->fcc, dev->width, dev->height);
-
- memset(&fmt, 0, sizeof fmt);
- fmt.width = dev->width;
- fmt.height = dev->height;
- fmt.pixelformat = dev->fcc;
- fmt.field = V4L2_FIELD_NONE;
- if (dev->fcc == V4L2_PIX_FMT_MJPEG)
- fmt.sizeimage = dev->maxsize * 1.5;
-
- ret = v4l2_set_format(dev->vdev, &fmt);
- if (ret < 0)
- return ret;
-
- return v4l2_set_format(cap, &fmt);
-}
-
-static int
-uvc_video_init(struct uvc_device *dev __attribute__((__unused__)))
-{
- return 0;
-}
-
-/* ---------------------------------------------------------------------------
- * Request processing
- */
-
-static void
-uvc_fill_streaming_control(struct uvc_device *dev,
- struct uvc_streaming_control *ctrl,
- int iformat, int iframe, unsigned int ival)
-{
- const struct uvc_function_config_format *format;
- const struct uvc_function_config_frame *frame;
- unsigned int i;
-
- /*
- * Restrict the iformat, iframe and ival to valid values. Negative
- * values for iformat or iframe will result in the maximum valid value
- * being selected.
- */
- iformat = clamp((unsigned int)iformat, 1U,
- dev->fc->streaming.num_formats);
- format = &dev->fc->streaming.formats[iformat-1];
-
- iframe = clamp((unsigned int)iframe, 1U, format->num_frames);
- frame = &format->frames[iframe-1];
-
- for (i = 0; i < frame->num_intervals; ++i) {
- if (ival <= frame->intervals[i]) {
- ival = frame->intervals[i];
- break;
- }
- }
-
- if (i == frame->num_intervals)
- ival = frame->intervals[frame->num_intervals-1];
-
- memset(ctrl, 0, sizeof *ctrl);
-
- ctrl->bmHint = 1;
- ctrl->bFormatIndex = iformat;
- ctrl->bFrameIndex = iframe ;
- ctrl->dwFrameInterval = ival;
-
- switch (format->fcc) {
- case V4L2_PIX_FMT_YUYV:
- ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2;
- break;
- case V4L2_PIX_FMT_MJPEG:
- ctrl->dwMaxVideoFrameSize = dev->maxsize;
- break;
- }
-
- ctrl->dwMaxPayloadTransferSize = dev->fc->streaming.ep.wMaxPacketSize;
- ctrl->bmFramingInfo = 3;
- ctrl->bPreferedVersion = 1;
- ctrl->bMaxVersion = 1;
-}
-
-static void
-uvc_events_process_standard(struct uvc_device *dev,
- const struct usb_ctrlrequest *ctrl,
- struct uvc_request_data *resp)
-{
- printf("standard request\n");
- (void)dev;
- (void)ctrl;
- (void)resp;
-}
-
-static void
-uvc_events_process_control(struct uvc_device *dev, uint8_t req, uint8_t cs,
- struct uvc_request_data *resp)
-{
- printf("control request (req %02x cs %02x)\n", req, cs);
- (void)dev;
- (void)resp;
-}
-
-static void
-uvc_events_process_streaming(struct uvc_device *dev, uint8_t req, uint8_t cs,
- struct uvc_request_data *resp)
-{
- struct uvc_streaming_control *ctrl;
-
- printf("streaming request (req %02x cs %02x)\n", req, cs);
-
- if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL)
- return;
-
- ctrl = (struct uvc_streaming_control *)&resp->data;
- resp->length = sizeof *ctrl;
-
- switch (req) {
- case UVC_SET_CUR:
- dev->control = cs;
- resp->length = 34;
- break;
-
- case UVC_GET_CUR:
- if (cs == UVC_VS_PROBE_CONTROL)
- memcpy(ctrl, &dev->probe, sizeof *ctrl);
- else
- memcpy(ctrl, &dev->commit, sizeof *ctrl);
- break;
-
- case UVC_GET_MIN:
- case UVC_GET_MAX:
- case UVC_GET_DEF:
- uvc_fill_streaming_control(dev, ctrl, req == UVC_GET_MAX ? -1 : 1,
- req == UVC_GET_MAX ? -1 : 1, 0);
- break;
-
- case UVC_GET_RES:
- memset(ctrl, 0, sizeof *ctrl);
- break;
-
- case UVC_GET_LEN:
- resp->data[0] = 0x00;
- resp->data[1] = 0x22;
- resp->length = 2;
- break;
-
- case UVC_GET_INFO:
- resp->data[0] = 0x03;
- resp->length = 1;
- break;
- }
-}
-
-static void
-uvc_events_process_class(struct uvc_device *dev,
- const struct usb_ctrlrequest *ctrl,
- struct uvc_request_data *resp)
-{
- unsigned int interface = ctrl->wIndex & 0xff;
-
- if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
- return;
-
- if (interface == dev->fc->control.intf.bInterfaceNumber)
- uvc_events_process_control(dev, ctrl->bRequest, ctrl->wValue >> 8, resp);
- else if (interface == dev->fc->streaming.intf.bInterfaceNumber)
- uvc_events_process_streaming(dev, ctrl->bRequest, ctrl->wValue >> 8, resp);
-}
-
-static void
-uvc_events_process_setup(struct uvc_device *dev,
- const struct usb_ctrlrequest *ctrl,
- struct uvc_request_data *resp)
-{
- dev->control = 0;
-
- printf("bRequestType %02x bRequest %02x wValue %04x wIndex %04x "
- "wLength %04x\n", ctrl->bRequestType, ctrl->bRequest,
- ctrl->wValue, ctrl->wIndex, ctrl->wLength);
-
- switch (ctrl->bRequestType & USB_TYPE_MASK) {
- case USB_TYPE_STANDARD:
- uvc_events_process_standard(dev, ctrl, resp);
- break;
-
- case USB_TYPE_CLASS:
- uvc_events_process_class(dev, ctrl, resp);
- break;
-
- default:
- break;
- }
-}
-
-static void
-uvc_events_process_data(struct uvc_stream *stream,
- const struct uvc_request_data *data)
-{
- struct uvc_device *dev = stream->uvc;
- 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;
-
- 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;
-
- uvc_video_set_format(stream);
- }
-}
-
-static void
-uvc_events_process(void *d)
-{
- struct uvc_stream *stream = d;
- struct uvc_device *dev = stream->uvc;
- 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(stream, &uvc_event->data);
- return;
-
- case UVC_EVENT_STREAMON:
- capture_video_stream(stream, 1);
- uvc_video_stream(stream, 1);
- return;
-
- case UVC_EVENT_STREAMOFF:
- uvc_video_stream(stream, 0);
- capture_video_stream(stream, 0);
- return;
- }
-
- ioctl(dev->vdev->fd, UVCIOC_SEND_RESPONSE, &resp);
- if (ret < 0) {
- printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno),
- errno);
- return;
- }
-}
-
-static void
-uvc_events_init(struct uvc_device *dev)
-{
- 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);
-}
-
-/* ---------------------------------------------------------------------------
- * Stream handling
- */
-
-static struct uvc_stream *uvc_stream_new(const char *uvc_device,
- const char *cap_device)
-{
- struct uvc_stream *stream;
-
- stream = malloc(sizeof(*stream));
- if (stream == NULL)
- return NULL;
-
- memset(stream, 0, sizeof(*stream));
-
- stream->cap = v4l2_open(cap_device);
- if (stream->cap == NULL)
- goto error;
-
- stream->uvc = uvc_open(uvc_device);
- if (stream->uvc == NULL)
- goto error;
-
- return stream;
-
-error:
- if (stream->cap)
- v4l2_close(stream->cap);
-
- free(stream);
- return NULL;
-}
-
-static void uvc_stream_delete(struct uvc_stream *stream)
-{
- if (stream == NULL)
- return;
-
- v4l2_close(stream->cap);
- uvc_close(stream->uvc);
-
- free(stream);
-}
-
-static void uvc_stream_init_uvc(struct uvc_stream *stream,
- struct uvc_function_config *fc)
-{
- /*
- * FIXME: The maximum size should be specified per format and frame.
- */
- stream->uvc->maxsize = 0;
- stream->uvc->fc = fc;
-
- uvc_events_init(stream->uvc);
- uvc_video_init(stream->uvc);
-}
-
-static void uvc_stream_set_event_handler(struct uvc_stream *stream,
- struct events *events)
-{
- stream->events = events;
-
- events_watch_fd(stream->events, stream->uvc->vdev->fd, EVENT_EXCEPTION,
- uvc_events_process, stream);
-}
-
-/* ---------------------------------------------------------------------------
- * main
- */
-
-static void usage(const char *argv0)
-{
- fprintf(stderr, "Usage: %s [options] <uvc device>\n", argv0);
- fprintf(stderr, "Available options are\n");
- fprintf(stderr, " -c device V4L2 source device\n");
- fprintf(stderr, " -h Print this help screen and exit\n");
- fprintf(stderr, " -i image MJPEG image\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, " uvc-gadget uvc.1\n");
- fprintf(stderr, " uvc-gadget g1/functions/uvc.1\n");
- fprintf(stderr, "\n");
- fprintf(stderr, " uvc-gadget musb-hdrc.0.auto\n");
-}
-
-/* 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;
- char *cap_device = "/dev/video1";
- struct uvc_function_config *fc;
- struct uvc_stream *stream;
- struct events events;
- int ret = 0;
- int opt;
-
- while ((opt = getopt(argc, argv, "c:h")) != -1) {
- switch (opt) {
- case 'c':
- cap_device = 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;
- }
-
- /*
- * 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 initialise the stream. */
- stream = uvc_stream_new(fc->video, cap_device);
- if (stream == NULL) {
- ret = 1;
- goto done;
- }
-
- uvc_stream_init_uvc(stream, fc);
- uvc_stream_set_event_handler(stream, &events);
-
- /* Main capture loop */
- events_loop(&events);
-
-done:
- /* Cleanup */
- uvc_stream_delete(stream);
- events_cleanup(&events);
- configfs_free_uvc_function(fc);
-
- return ret;
-}
diff --git a/uvc.c b/uvc.c
new file mode 100644
index 0000000..dc8c98e
--- /dev/null
+++ b/uvc.c
@@ -0,0 +1,340 @@
+/*
+ * UVC protocol handling
+ *
+ * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ */
+
+#include <errno.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/g_uvc.h>
+#include <linux/usb/video.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "configfs.h"
+#include "stream.h"
+#include "tools.h"
+#include "uvc.h"
+#include "v4l2.h"
+
+struct uvc_device *uvc_open(const char *devname)
+{
+ struct uvc_device *dev;
+
+ dev = malloc(sizeof *dev);
+ if (dev == NULL)
+ return NULL;
+
+ memset(dev, 0, sizeof *dev);
+
+ dev->vdev = v4l2_open(devname);
+ if (dev->vdev == NULL) {
+ free(dev);
+ return NULL;
+ }
+
+ return dev;
+}
+
+void uvc_close(struct uvc_device *dev)
+{
+ v4l2_close(dev->vdev);
+ dev->vdev = NULL;
+
+ free(dev);
+}
+
+/* ---------------------------------------------------------------------------
+ * Request processing
+ */
+
+static void
+uvc_fill_streaming_control(struct uvc_device *dev,
+ struct uvc_streaming_control *ctrl,
+ int iformat, int iframe, unsigned int ival)
+{
+ const struct uvc_function_config_format *format;
+ const struct uvc_function_config_frame *frame;
+ unsigned int i;
+
+ /*
+ * Restrict the iformat, iframe and ival to valid values. Negative
+ * values for iformat or iframe will result in the maximum valid value
+ * being selected.
+ */
+ iformat = clamp((unsigned int)iformat, 1U,
+ dev->fc->streaming.num_formats);
+ format = &dev->fc->streaming.formats[iformat-1];
+
+ iframe = clamp((unsigned int)iframe, 1U, format->num_frames);
+ frame = &format->frames[iframe-1];
+
+ for (i = 0; i < frame->num_intervals; ++i) {
+ if (ival <= frame->intervals[i]) {
+ ival = frame->intervals[i];
+ break;
+ }
+ }
+
+ if (i == frame->num_intervals)
+ ival = frame->intervals[frame->num_intervals-1];
+
+ memset(ctrl, 0, sizeof *ctrl);
+
+ ctrl->bmHint = 1;
+ ctrl->bFormatIndex = iformat;
+ ctrl->bFrameIndex = iframe ;
+ ctrl->dwFrameInterval = ival;
+
+ switch (format->fcc) {
+ case V4L2_PIX_FMT_YUYV:
+ ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2;
+ break;
+ case V4L2_PIX_FMT_MJPEG:
+ ctrl->dwMaxVideoFrameSize = dev->maxsize;
+ break;
+ }
+
+ ctrl->dwMaxPayloadTransferSize = dev->fc->streaming.ep.wMaxPacketSize;
+ ctrl->bmFramingInfo = 3;
+ ctrl->bPreferedVersion = 1;
+ ctrl->bMaxVersion = 1;
+}
+
+static void
+uvc_events_process_standard(struct uvc_device *dev,
+ const struct usb_ctrlrequest *ctrl,
+ struct uvc_request_data *resp)
+{
+ printf("standard request\n");
+ (void)dev;
+ (void)ctrl;
+ (void)resp;
+}
+
+static void
+uvc_events_process_control(struct uvc_device *dev, uint8_t req, uint8_t cs,
+ struct uvc_request_data *resp)
+{
+ printf("control request (req %02x cs %02x)\n", req, cs);
+ (void)dev;
+ (void)resp;
+}
+
+static void
+uvc_events_process_streaming(struct uvc_device *dev, uint8_t req, uint8_t cs,
+ struct uvc_request_data *resp)
+{
+ struct uvc_streaming_control *ctrl;
+
+ printf("streaming request (req %02x cs %02x)\n", req, cs);
+
+ if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL)
+ return;
+
+ ctrl = (struct uvc_streaming_control *)&resp->data;
+ resp->length = sizeof *ctrl;
+
+ switch (req) {
+ case UVC_SET_CUR:
+ dev->control = cs;
+ resp->length = 34;
+ break;
+
+ case UVC_GET_CUR:
+ if (cs == UVC_VS_PROBE_CONTROL)
+ memcpy(ctrl, &dev->probe, sizeof *ctrl);
+ else
+ memcpy(ctrl, &dev->commit, sizeof *ctrl);
+ break;
+
+ case UVC_GET_MIN:
+ case UVC_GET_MAX:
+ case UVC_GET_DEF:
+ uvc_fill_streaming_control(dev, ctrl, req == UVC_GET_MAX ? -1 : 1,
+ req == UVC_GET_MAX ? -1 : 1, 0);
+ break;
+
+ case UVC_GET_RES:
+ memset(ctrl, 0, sizeof *ctrl);
+ break;
+
+ case UVC_GET_LEN:
+ resp->data[0] = 0x00;
+ resp->data[1] = 0x22;
+ resp->length = 2;
+ break;
+
+ case UVC_GET_INFO:
+ resp->data[0] = 0x03;
+ resp->length = 1;
+ break;
+ }
+}
+
+static void
+uvc_events_process_class(struct uvc_device *dev,
+ const struct usb_ctrlrequest *ctrl,
+ struct uvc_request_data *resp)
+{
+ unsigned int interface = ctrl->wIndex & 0xff;
+
+ if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
+ return;
+
+ if (interface == dev->fc->control.intf.bInterfaceNumber)
+ uvc_events_process_control(dev, ctrl->bRequest, ctrl->wValue >> 8, resp);
+ else if (interface == dev->fc->streaming.intf.bInterfaceNumber)
+ uvc_events_process_streaming(dev, ctrl->bRequest, ctrl->wValue >> 8, resp);
+}
+
+static void
+uvc_events_process_setup(struct uvc_device *dev,
+ const struct usb_ctrlrequest *ctrl,
+ struct uvc_request_data *resp)
+{
+ dev->control = 0;
+
+ printf("bRequestType %02x bRequest %02x wValue %04x wIndex %04x "
+ "wLength %04x\n", ctrl->bRequestType, ctrl->bRequest,
+ ctrl->wValue, ctrl->wIndex, ctrl->wLength);
+
+ switch (ctrl->bRequestType & USB_TYPE_MASK) {
+ case USB_TYPE_STANDARD:
+ uvc_events_process_standard(dev, ctrl, resp);
+ break;
+
+ case USB_TYPE_CLASS:
+ uvc_events_process_class(dev, ctrl, resp);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+uvc_events_process_data(struct uvc_stream *stream,
+ const struct uvc_request_data *data)
+{
+ struct uvc_device *dev = stream->uvc;
+ 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;
+
+ 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;
+
+ uvc_stream_set_format(stream);
+ }
+}
+
+void uvc_events_process(void *d)
+{
+ struct uvc_stream *stream = d;
+ struct uvc_device *dev = stream->uvc;
+ 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(stream, &uvc_event->data);
+ return;
+
+ case UVC_EVENT_STREAMON:
+ uvc_stream_enable(stream, 1);
+ return;
+
+ case UVC_EVENT_STREAMOFF:
+ uvc_stream_enable(stream, 0);
+ return;
+ }
+
+ ioctl(dev->vdev->fd, UVCIOC_SEND_RESPONSE, &resp);
+ if (ret < 0) {
+ printf("UVCIOC_S_EVENT failed: %s (%d)\n", strerror(errno),
+ errno);
+ return;
+ }
+}
+
+void uvc_events_init(struct uvc_device *dev)
+{
+ 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);
+}
diff --git a/uvc.h b/uvc.h
new file mode 100644
index 0000000..bfbf2fe
--- /dev/null
+++ b/uvc.h
@@ -0,0 +1,50 @@
+/*
+ * UVC protocol handling
+ *
+ * Copyright (C) 2010 Ideas on board SPRL <laurent.pinchart@ideasonboard.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ */
+
+#ifndef __UVC_H__
+#define __UVC_H__
+
+#include <linux/usb/video.h>
+
+struct v4l2_device;
+struct uvc_function_config;
+
+struct uvc_device
+{
+ struct v4l2_device *vdev;
+
+ struct uvc_function_config *fc;
+
+ struct uvc_streaming_control probe;
+ struct uvc_streaming_control commit;
+
+ int control;
+
+ unsigned int fcc;
+ unsigned int width;
+ unsigned int height;
+ unsigned int maxsize;
+};
+
+void uvc_events_process(void *d);
+struct uvc_device *uvc_open(const char *devname);
+void uvc_close(struct uvc_device *dev);
+void uvc_events_init(struct uvc_device *dev);
+
+#endif /* __UVC_H__ */