blob: 8ccfa07e53157404242ac766f6de791a0e5a0d10 [file]
/*
* 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;
}