/* * UVC gadget test application * * Copyright (C) 2010 Ideas on board SPRL * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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] \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 instance specifier\n"); fprintf(stderr, "\n"); fprintf(stderr, " For ConfigFS devices the 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; }