diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 24d834f763..ea841ccf69 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -233656,6 +233656,49 @@ "tracers": {}, "url": "Unknown package origin" }, + "uvcgadget": { + "description": "gstuvcgadget plugin", + "elements": { + "uvcsink": { + "author": "Michael Grzeschik ", + "description": "Streams Video via UVC Gadget", + "hierarchy": [ + "GstUvcSink", + "GstBin", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstChildProxy" + ], + "klass": "Sink/Video", + "properties": { + "streaming": { + "blurb": "The stream status of the host", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": false + } + }, + "rank": "none" + } + }, + "filename": "gstuvcgadget", + "license": "LGPL", + "other-types": {}, + "package": "GStreamer Bad Plug-ins", + "source": "gst-plugins-bad", + "tracers": {}, + "url": "Unknown package origin" + }, "uvch264": { "description": "UVC compliant H264 encoding cameras plugin", "elements": { diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index 4f96669721..f76372652a 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -195,6 +195,7 @@ option( option('magicleap', type : 'feature', value : 'auto', description : 'Magic Leap platform support') option('v4l2codecs', type : 'feature', value : 'auto', description : 'Video4Linux Stateless CODECs support') +option('uvcgadget', type : 'feature', value : 'auto', description : 'uvc video gadget plugin') option('isac', type : 'feature', value : 'auto', description : 'iSAC plugin') # HLS plugin options diff --git a/subprojects/gst-plugins-bad/sys/meson.build b/subprojects/gst-plugins-bad/sys/meson.build index cfe1778207..2ad1b22878 100644 --- a/subprojects/gst-plugins-bad/sys/meson.build +++ b/subprojects/gst-plugins-bad/sys/meson.build @@ -22,6 +22,7 @@ subdir('shm') subdir('tinyalsa') subdir('uvch264') subdir('v4l2codecs') +subdir('uvcgadget') subdir('va') subdir('wasapi') subdir('wasapi2') diff --git a/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.c b/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.c new file mode 100644 index 0000000000..3e2269540b --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.c @@ -0,0 +1,668 @@ +/* + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * Copyright (C) 2023 Pengutronix e.K. - www.pengutronix.de + * + */ + +/** + * SECTION:plugin-uvcgadget + * @title: uvcgadget + * + * Since: 1.24 + */ + +/** + * SECTION:element-uvcsink + * @title: uvcsink + * + * uvcsink can be used to push frames to the Linux UVC Gadget + * driver. + * + * ## Example launch lines + * |[ + * gst-launch videotestsrc ! uvcsink v4l2sink::device=/dev/video1 + * ]| + * This pipeline streams a test pattern on UVC gadget /dev/video1. + * + * Before starting the pipeline, the linux system needs an uvc gadget + * configured on the udc (usb device controller) + * + * Either by using the legacy g_webcam gadget or by preconfiguring it + * with configfs. One way to configure is to use the example script from + * uvc-gadget: + * + * https://git.ideasonboard.org/uvc-gadget.git/blob/HEAD:/scripts/uvc-gadget.sh + * + * A modern way of configuring the gadget with an scheme file that gets + * loaded by gadget-tool (gt) using libusbgx. + * + * https://github.com/linux-usb-gadgets/libusbgx + * https://github.com/linux-usb-gadgets/gt + * + * Since: 1.24 + */ +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "gstuvcsink.h" + +GST_DEBUG_CATEGORY (uvcsink_debug); +#define GST_CAT_DEFAULT uvcsink_debug + +enum +{ + ARG_0, + PROP_STREAMING, + PROP_LAST +}; + +#define gst_uvc_sink_parent_class parent_class +G_DEFINE_TYPE (GstUvcSink, gst_uvc_sink, GST_TYPE_BIN); +GST_ELEMENT_REGISTER_DEFINE (uvcsink, + "uvcsink", GST_RANK_NONE, GST_TYPE_UVCSINK); + +/* GstElement methods: */ +static GstStateChangeReturn gst_uvc_sink_change_state (GstElement * + element, GstStateChange transition); + +static void gst_uvc_sink_dispose (GObject * object); +static gboolean gst_uvc_sink_prepare_configfs (GstUvcSink * self); + +static void +gst_uvc_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstUvcSink *self = GST_UVCSINK (object); + + switch (prop_id) { + case PROP_STREAMING: + g_value_set_boolean (value, g_atomic_int_get (&self->streaming)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStructure * +gst_v4l2uvc_fourcc_to_bare_struct (guint32 fourcc) +{ + GstStructure *structure = NULL; + + /* Since MJPEG and YUY2 are currently the only one supported + * we limit the function to parse only these fourccs + */ + switch (fourcc) { + case V4L2_PIX_FMT_MJPEG: /* Motion-JPEG */ + case V4L2_PIX_FMT_JPEG: /* JFIF JPEG */ + structure = gst_structure_new_empty ("image/jpeg"); + break; + case V4L2_PIX_FMT_YUYV:{ + GstVideoFormat format = GST_VIDEO_FORMAT_YUY2; + if (format != GST_VIDEO_FORMAT_UNKNOWN) + structure = gst_structure_new ("video/x-raw", + "format", G_TYPE_STRING, gst_video_format_to_string (format), NULL); + break; + } + break; + default: + GST_DEBUG ("Unsupported fourcc 0x%08x %" GST_FOURCC_FORMAT, + fourcc, GST_FOURCC_ARGS (fourcc)); + break; + } + + return structure; +} + +/* The UVC EVENT_DATA from the host, which is commiting the currently + * selected configuration (format+resolution+framerate) is only selected + * by some index values (except the framerate). We have to transform + * those values to an valid caps string that we can return on the caps + * query. + */ +static gboolean +gst_uvc_sink_parse_cur_caps (GstUvcSink * self) +{ + struct v4l2_fmtdesc format; + struct v4l2_frmsizeenum size; + struct v4l2_frmivalenum ival; + guint32 width, height; + gint device_fd; + GstStructure *s; + gint numerator, denominator; + GValue framerate = { 0, }; + + g_object_get (G_OBJECT (self->v4l2sink), "device-fd", &device_fd, NULL); + + format.index = self->cur.bFormatIndex - 1; + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + if (ioctl (device_fd, VIDIOC_ENUM_FMT, &format) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Linux kernel error"), + ("VIDIOC_ENUM_FMT failed: %s (%d)", g_strerror (errno), errno)); + return FALSE; + } + + s = gst_v4l2uvc_fourcc_to_bare_struct (format.pixelformat); + + memset (&size, 0, sizeof (struct v4l2_frmsizeenum)); + size.index = self->cur.bFrameIndex - 1; + size.pixel_format = format.pixelformat; + + if (ioctl (device_fd, VIDIOC_ENUM_FRAMESIZES, &size) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Linux kernel error"), + ("VIDIOC_ENUM_FRAMESIZES failed: %s (%d)", g_strerror (errno), errno)); + return FALSE; + } + + GST_LOG_OBJECT (self, "got discrete frame size %dx%d", + size.discrete.width, size.discrete.height); + + width = MIN (size.discrete.width, G_MAXINT); + height = MIN (size.discrete.height, G_MAXINT); + + if (!width || !height) + return FALSE; + + g_value_init (&framerate, GST_TYPE_FRACTION); + + memset (&ival, 0, sizeof (struct v4l2_frmivalenum)); + + ival.index = 0; + ival.pixel_format = format.pixelformat; + ival.width = width; + ival.height = height; + +#define SIMPLIFY_FRACTION_N_TERMS 8 +#define SIMPLIFY_FRACTION_THRESHOLD 333 + + numerator = self->cur.dwFrameInterval; + denominator = 10000000; + gst_util_simplify_fraction (&numerator, &denominator, + SIMPLIFY_FRACTION_N_TERMS, SIMPLIFY_FRACTION_THRESHOLD); + + if (ioctl (device_fd, VIDIOC_ENUM_FRAMEINTERVALS, &ival) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("Linux kernel error"), + ("VIDIOC_ENUM_FRAMEINTERVALS failed: %s (%d)", + g_strerror (errno), errno)); + return FALSE; + } + + do { + gint inumerator = ival.discrete.numerator; + gint idenominator = ival.discrete.denominator; + + gst_util_simplify_fraction (&inumerator, &idenominator, + SIMPLIFY_FRACTION_N_TERMS, SIMPLIFY_FRACTION_THRESHOLD); + + if (numerator == inumerator && denominator == idenominator) + /* swap to get the framerate */ + gst_value_set_fraction (&framerate, denominator, numerator); + + ival.index++; + } while (ioctl (device_fd, VIDIOC_ENUM_FRAMEINTERVALS, &ival) >= 0); + + gst_structure_set (s, "width", G_TYPE_INT, (gint) width, + "height", G_TYPE_INT, (gint) height, NULL); + + gst_structure_take_value (s, "framerate", &framerate); + + gst_clear_caps (&self->cur_caps); + self->cur_caps = gst_caps_new_full (s, NULL); + + return TRUE; +} + +static gboolean +gst_uvc_sink_query (GstPad * pad, GstObject * parent, GstQuery * query) +{ + GstUvcSink *self = GST_UVCSINK (parent); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CAPS: + { + if (gst_caps_is_empty (self->cur_caps)) + return gst_pad_query_default (pad, parent, query); + + GST_DEBUG_OBJECT (self, "caps %" GST_PTR_FORMAT, self->cur_caps); + gst_query_set_caps_result (query, self->cur_caps); + return TRUE; + } + case GST_QUERY_ALLOCATION: + return TRUE; + default: + return gst_pad_query_default (pad, parent, query); + } + return TRUE; +} + +static void +gst_uvc_sink_class_init (GstUvcSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *element_class; + + element_class = GST_ELEMENT_CLASS (klass); + + element_class->change_state = gst_uvc_sink_change_state; + + gst_element_class_set_metadata (element_class, + "UVC Sink", "Sink/Video", + "Streams Video via UVC Gadget", "Michael Grzeschik "); + + GST_DEBUG_CATEGORY_INIT (uvcsink_debug, "uvcsink", 0, "uvc sink element"); + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->dispose = gst_uvc_sink_dispose; + gobject_class->get_property = gst_uvc_sink_get_property; + + /** + * uvcsink:streaming: + * + * The stream status of the host. + * + * Since: 1.24 + */ + g_object_class_install_property (gobject_class, PROP_STREAMING, + g_param_spec_boolean ("streaming", "streaming", + "The stream status of the host", 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +static gboolean +gst_uvc_sink_open (GstUvcSink * self) +{ + if (!self->v4l2sink) { + gst_element_post_message (GST_ELEMENT_CAST (self), + gst_missing_element_message_new (GST_ELEMENT_CAST (self), "v4l2sink")); + + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("No v4l2sink element found"), ("Check your plugin installation")); + return FALSE; + } + + if (!self->fakesink) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("No fakesink element found"), ("Check your plugin installation")); + return FALSE; + } + + return TRUE; +} + +static void +gst_uvc_sink_init (GstUvcSink * self) +{ + /* add the v4l2sink element */ + self->v4l2sink = gst_element_factory_make ("v4l2sink", "v4l2sink"); + if (!self->v4l2sink) + return; + + g_object_set (G_OBJECT (self->v4l2sink), "async", FALSE, NULL); + gst_bin_add (GST_BIN (self), self->v4l2sink); + + self->v4l2sinkpad = gst_element_get_static_pad (self->v4l2sink, "sink"); + g_return_if_fail (self->v4l2sinkpad != NULL); + + /* create ghost pad sink */ + self->sinkpad = gst_ghost_pad_new ("sink", self->v4l2sinkpad); + gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); + + g_atomic_int_set (&self->streaming, FALSE); + + gst_pad_set_query_function (self->sinkpad, gst_uvc_sink_query); + + self->cur_caps = gst_caps_new_empty (); +} + +static void +gst_uvc_sink_dispose (GObject * object) +{ + GstUvcSink *self = GST_UVCSINK (object); + + if (self->sinkpad) { + gst_pad_set_active (self->sinkpad, FALSE); + gst_element_remove_pad (GST_ELEMENT (self), self->sinkpad); + self->sinkpad = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static gboolean gst_uvc_sink_unwatch (GstUvcSink * self); + +/* the thread where everything happens */ +static void +gst_uvc_sink_task (gpointer data) +{ + GstUvcSink *self = GST_UVCSINK (data); + GstClockTime timeout = GST_CLOCK_TIME_NONE; + struct uvc_request_data resp; + gboolean ret = TRUE; + + ret = gst_poll_wait (self->poll, timeout); + if (G_UNLIKELY (ret < 0)) + return; + + timeout = GST_CLOCK_TIME_NONE; + + if (gst_poll_fd_has_closed (self->poll, &self->pollfd)) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("videofd was closed"), NULL); + gst_uvc_sink_unwatch (self); + gst_element_set_state (GST_ELEMENT (self), GST_STATE_NULL); + return; + } + + if (gst_poll_fd_has_error (self->poll, &self->pollfd)) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, ("videofd has error"), NULL); + gst_uvc_sink_unwatch (self); + gst_element_set_state (GST_ELEMENT (self), GST_STATE_NULL); + return; + } + + /* PRI is used to signal that events are available */ + if (gst_poll_fd_has_pri (self->poll, &self->pollfd)) { + struct v4l2_event event = { 0, }; + struct uvc_event *uvc_event = (void *) &event.u.data; + + if (ioctl (self->pollfd.fd, VIDIOC_DQEVENT, &event) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, READ, + ("could not dequeue event"), NULL); + gst_uvc_sink_unwatch (self); + gst_element_set_state (GST_ELEMENT (self), GST_STATE_NULL); + return; + } + + switch (event.type) { + case UVC_EVENT_STREAMON: + GST_DEBUG_OBJECT (self, "UVC_EVENT_STREAMON"); + g_atomic_int_set (&self->streaming, TRUE); + g_object_notify (G_OBJECT (self), "streaming"); + GST_PAD_STREAM_UNLOCK (GST_PAD (self->sinkpad)); + break; + case UVC_EVENT_STREAMOFF: + case UVC_EVENT_DISCONNECT: + GST_DEBUG_OBJECT (self, "UVC_EVENT_STREAMOFF"); + g_atomic_int_set (&self->streaming, FALSE); + g_object_notify (G_OBJECT (self), "streaming"); + break; + case UVC_EVENT_SETUP: + GST_DEBUG_OBJECT (self, "UVC_EVENT_SETUP"); + + memset (&resp, 0, sizeof (resp)); + resp.length = -EL2HLT; + + uvc_events_process_setup (self, &uvc_event->req, &resp); + + if (ioctl (self->pollfd.fd, UVCIOC_SEND_RESPONSE, &resp) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("UVCIOC_SEND_RESPONSE failed"), + ("UVCIOC_SEND_RESPONSE on UVC_EVENT_SETUP failed")); + gst_uvc_sink_unwatch (self); + gst_element_set_state (GST_ELEMENT (self), GST_STATE_NULL); + return; + } + break; + case UVC_EVENT_DATA: + GST_DEBUG_OBJECT (self, "UVC_EVENT_DATA"); + uvc_events_process_data (self, &uvc_event->data); + if (self->control == UVC_VS_COMMIT_CONTROL) { + GstCaps *caps; + + gst_uvc_sink_parse_cur_caps (self); + caps = gst_caps_copy (self->cur_caps); + gst_clear_caps (&self->cur_caps); + self->cur_caps = + gst_caps_intersect_full (self->probed_caps, caps, + GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + } + break; + default: + break; + } + } +} + +static gboolean +gst_uvc_sink_watch (GstUvcSink * self) +{ + struct v4l2_event_subscription sub = {.type = UVC_EVENT_STREAMON }; + gboolean ret = TRUE; + gint device_fd; + gint fd; + + ret = gst_uvc_sink_prepare_configfs (self); + if (!ret) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("could not parse configfs"), ("Check your configfs setup")); + return FALSE; + } + + g_object_get (G_OBJECT (self->v4l2sink), "device-fd", &device_fd, NULL); + + fd = dup3 (device_fd, device_fd + 1, O_CLOEXEC); + if (fd < 0) + return FALSE; + + self->poll = gst_poll_new (TRUE); + gst_poll_fd_init (&self->pollfd); + self->pollfd.fd = fd; + gst_poll_add_fd (self->poll, &self->pollfd); + gst_poll_fd_ctl_pri (self->poll, &self->pollfd, TRUE); + + if (ioctl (device_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to subscribe event"), + ("UVC_EVENT_STREAMON could not be subscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_STREAMOFF; + if (ioctl (device_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to subscribe event"), + ("UVC_EVENT_STREAMOFF could not be subscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_DISCONNECT; + if (ioctl (device_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to subscribe event"), + ("UVC_EVENT_DISCONNECT could not be subscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_SETUP; + if (ioctl (device_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to subscribe event"), + ("UVC_EVENT_SETUP could not be subscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_DATA; + if (ioctl (device_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to subscribe event"), + ("UVC_EVENT_DATA could not be subscribed")); + return FALSE; + } + + if (!gst_pad_start_task (GST_PAD (self->sinkpad), + (GstTaskFunction) gst_uvc_sink_task, self, NULL)) { + GST_ELEMENT_ERROR (self, CORE, THREAD, ("Could not start pad task"), + ("Could not start pad task")); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_uvc_sink_unwatch (GstUvcSink * self) +{ + struct v4l2_event_subscription sub = {.type = UVC_EVENT_DATA }; + gint device_fd; + + gst_poll_set_flushing (self->poll, TRUE); + gst_pad_stop_task (GST_PAD (self->sinkpad)); + + g_object_get (G_OBJECT (self->v4l2sink), "device-fd", &device_fd, NULL); + + if (ioctl (device_fd, VIDIOC_UNSUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to unsubscribe event"), + ("UVC_EVENT_DATA could not be unsubscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_SETUP; + if (ioctl (device_fd, VIDIOC_UNSUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to unsubscribe event"), + ("UVC_EVENT_SETUP could not be unsubscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_STREAMON; + if (ioctl (device_fd, VIDIOC_UNSUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to unsubscribe event"), + ("UVC_EVENT_STREAMON could not be unsubscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_STREAMOFF; + if (ioctl (device_fd, VIDIOC_UNSUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to unsubscribe event"), + ("UVC_EVENT_STREAMOFF could not be unsubscribed")); + return FALSE; + } + + sub.type = UVC_EVENT_DISCONNECT; + if (ioctl (device_fd, VIDIOC_UNSUBSCRIBE_EVENT, &sub) < 0) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to unsubscribe event"), + ("UVC_EVENT_DISCONNECT could not be unsubscribed")); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_uvc_sink_prepare_configfs (GstUvcSink * self) +{ + gint device_fd; + GValue device = G_VALUE_INIT; + + g_object_get (G_OBJECT (self->v4l2sink), "device-fd", &device_fd, NULL); + g_object_get_property (G_OBJECT (self->v4l2sink), "device", &device); + + self->fc = configfs_parse_uvc_videodev (device_fd, + g_value_get_string (&device)); + if (!self->fc) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Failed to identify function configuration"), + ("Check your configfs setup")); + return FALSE; + } + + uvc_fill_streaming_control (self, &self->probe, self->cur.bFrameIndex, + self->cur.bFormatIndex, self->cur.dwFrameInterval); + uvc_fill_streaming_control (self, &self->commit, self->cur.bFrameIndex, + self->cur.bFormatIndex, self->cur.dwFrameInterval); + + return TRUE; +} + +static gboolean +gst_uvc_sink_query_probed_caps (GstUvcSink * self) +{ + GstQuery *caps_query = gst_query_new_caps (NULL); + GstCaps *query_caps; + + gst_clear_caps (&self->probed_caps); + if (!gst_pad_query (self->v4l2sinkpad, caps_query)) + return FALSE; + + gst_query_parse_caps_result (caps_query, &query_caps); + gst_query_unref (caps_query); + + self->probed_caps = gst_caps_copy (query_caps); + + gst_caps_replace (&self->cur_caps, self->probed_caps); + + return TRUE; +} + +static GstStateChangeReturn +gst_uvc_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstUvcSink *self = GST_UVCSINK (element); + int bret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (self, "%s -> %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_uvc_sink_open (self)) + return GST_STATE_CHANGE_FAILURE; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + if (!gst_uvc_sink_unwatch (self)) + return GST_STATE_CHANGE_FAILURE; + break; + default: + break; + } + + bret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_uvc_sink_watch (self)) + return GST_STATE_CHANGE_FAILURE; + if (!gst_uvc_sink_query_probed_caps (self)) + return GST_STATE_CHANGE_FAILURE; + break; + default: + break; + } + + return bret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return GST_ELEMENT_REGISTER (uvcsink, plugin); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + uvcgadget, + "gstuvcgadget plugin", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.h b/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.h new file mode 100644 index 0000000000..c9e9e4b76a --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.h @@ -0,0 +1,67 @@ +/* + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * Copyright (C) 2023 Pengutronix e.K. - www.pengutronix.de + * + */ + +#pragma once + +#include "linux/usb/g_uvc.h" +#include "linux/usb/video.h" +#include "linux/videodev2.h" + +#include + +#include "configfs.h" + +G_BEGIN_DECLS GST_DEBUG_CATEGORY_EXTERN (uvcsink_debug); + +#define GST_TYPE_UVCSINK (gst_uvc_sink_get_type()) +G_DECLARE_FINAL_TYPE (GstUvcSink, gst_uvc_sink, GST, UVCSINK, GstBin) + +GST_ELEMENT_REGISTER_DECLARE (uvcsink); + +struct _GstUvcSink +{ + GstBin bin; + GstElement *v4l2sink; + GstPad *sinkpad; + GstPad *v4l2sinkpad; + + /* streaming status */ + gboolean streaming; + + GstCaps *probed_caps; + GstCaps *cur_caps; + + /* a poll for video_fd */ + GstPoll *poll; + GstPollFD pollfd; + + struct uvc_function_config *fc; + + struct { + int bFrameIndex; + int bFormatIndex; + unsigned int dwFrameInterval; + } cur; + + struct uvc_streaming_control probe; + struct uvc_streaming_control commit; + + int control; +}; + +#define UVCSINK_MSG_LOCK(v) g_mutex_lock(&(v)->msg_lock) +#define UVCSINK_MSG_UNLOCK(v) g_mutex_unlock(&(v)->msg_lock) + +int uvc_events_process_data(GstUvcSink * self, + const struct uvc_request_data *data); +int uvc_events_process_setup(GstUvcSink * self, + const struct usb_ctrlrequest *ctrl, + struct uvc_request_data *resp); +int uvc_fill_streaming_control(GstUvcSink * self, + struct uvc_streaming_control *ctrl, + int iframe, int iformat, unsigned int dwival); +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/uvcgadget/meson.build b/subprojects/gst-plugins-bad/sys/uvcgadget/meson.build new file mode 100644 index 0000000000..0d3cbf4e81 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/uvcgadget/meson.build @@ -0,0 +1,40 @@ +uvcgadget_sources = [ + 'gstuvcsink.c', + 'configfs.c', + 'uvc.c' +] + +libgudev_dep = dependency('gudev-1.0', required: get_option('uvcgadget')) + +if get_option('uvcgadget').disabled() + have_v4l2 = false + message('UVCSink plugin is disabled') +else + # Should only be built on Linux, check for Linux kernel headers even though + # we have our own copy. + have_v4l2 = cc.has_header('linux/videodev2.h') or cc.has_header('sys/videodev2.h') or cc.has_header('sys/videoio.h') + if get_option('uvcgadget').enabled() and not have_v4l2 + error('UVCGADGET is requested but kernel headers were not found') + endif + + # Find makedev in various header files. Different operating systems put the + # macro in different header files. + foreach name: ['mkdev', 'sysmacros', 'types'] + have_makedev = cc.has_header_symbol('sys/@0@.h'.format(name), 'makedev') + cdata.set10('HAVE_MAKEDEV_IN_' + name.to_upper(), have_makedev) + endforeach +endif + +if have_v4l2 and libgudev_dep.found() + gstuvcgadget = library('gstuvcgadget', + uvcgadget_sources, + c_args : gst_plugins_bad_args, + cpp_args: gst_plugins_bad_args, + include_directories : [configinc, include_directories('../v4l2codecs')], + dependencies : [gstbase_dep, gstvideo_dep, gstallocators_dep, libgudev_dep, gstpbutils_dep,], + install : true, + install_dir : plugins_install_dir, + ) + pkgconfig.generate(gstuvcgadget, install_dir : plugins_pkgconfig_install_dir) + plugins += [gstuvcgadget] +endif diff --git a/subprojects/gst-plugins-bad/sys/uvcgadget/uvc.c b/subprojects/gst-plugins-bad/sys/uvcgadget/uvc.c new file mode 100644 index 0000000000..e00a5a2968 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/uvcgadget/uvc.c @@ -0,0 +1,420 @@ +/* + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * Copyright (C) 2023 Pengutronix e.K. - www.pengutronix.de + * + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include "gstuvcsink.h" +#include "configfs.h" + +#define GST_CAT_DEFAULT uvcsink_debug + +#define UVC_STRING_CONTROL_IDX 0 +#define UVC_STRING_STREAMING_IDX 1 + +/* -------------------------------------------------------------------------- + * Control requests + */ + +static const char * +uvc_request_name (uint8_t req) +{ + switch (req) { + case UVC_SET_CUR: + return "SET_CUR"; + case UVC_GET_CUR: + return "GET_CUR"; + case UVC_GET_MIN: + return "GET_MIN"; + case UVC_GET_MAX: + return "GET_MAX"; + case UVC_GET_RES: + return "GET_RES"; + case UVC_GET_LEN: + return "GET_LEN"; + case UVC_GET_INFO: + return "GET_INFO"; + case UVC_GET_DEF: + return "GET_DEF"; + default: + return ""; + } +} + +static const char * +uvc_video_control_interface_control_selector_name (uint8_t cs) +{ + switch (cs) { + case UVC_VC_CONTROL_UNDEFINED: + return "UVC_VC_CONTROL_UNDEFINED"; + case UVC_VC_VIDEO_POWER_MODE_CONTROL: + return "UVC_VC_VIDEO_POWER_MODE_CONTROL"; + case UVC_VC_REQUEST_ERROR_CODE_CONTROL: + return "UVC_VC_REQUEST_ERROR_CODE_CONTROL"; + default: + return ""; + } +} + +static const char * +uvc_camera_terminal_control_selector_name (uint8_t cs) +{ + switch (cs) { + case UVC_CT_CONTROL_UNDEFINED: + return "UVC_CT_CONTROL_UNDEFINED"; + case UVC_CT_SCANNING_MODE_CONTROL: + return "UVC_CT_SCANNING_MODE_CONTROL"; + case UVC_CT_AE_MODE_CONTROL: + return "UVC_CT_AE_MODE_CONTROL"; + case UVC_CT_AE_PRIORITY_CONTROL: + return "UVC_CT_AE_PRIORITY_CONTROL"; + case UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL: + return "UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL"; + case UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL: + return "UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL"; + case UVC_CT_FOCUS_ABSOLUTE_CONTROL: + return "UVC_CT_FOCUS_ABSOLUTE_CONTROL"; + case UVC_CT_FOCUS_RELATIVE_CONTROL: + return "UVC_CT_FOCUS_RELATIVE_CONTROL"; + case UVC_CT_FOCUS_AUTO_CONTROL: + return "UVC_CT_FOCUS_AUTO_CONTROL"; + case UVC_CT_IRIS_ABSOLUTE_CONTROL: + return "UVC_CT_IRIS_ABSOLUTE_CONTROL"; + case UVC_CT_IRIS_RELATIVE_CONTROL: + return "UVC_CT_IRIS_RELATIVE_CONTROL"; + case UVC_CT_ZOOM_ABSOLUTE_CONTROL: + return "UVC_CT_ZOOM_ABSOLUTE_CONTROL"; + case UVC_CT_ZOOM_RELATIVE_CONTROL: + return "UVC_CT_ZOOM_RELATIVE_CONTROL"; + case UVC_CT_PANTILT_ABSOLUTE_CONTROL: + return "UVC_CT_PANTILT_ABSOLUTE_CONTROL"; + case UVC_CT_PANTILT_RELATIVE_CONTROL: + return "UVC_CT_PANTILT_RELATIVE_CONTROL"; + case UVC_CT_ROLL_ABSOLUTE_CONTROL: + return "UVC_CT_ROLL_ABSOLUTE_CONTROL"; + case UVC_CT_ROLL_RELATIVE_CONTROL: + return "UVC_CT_ROLL_RELATIVE_CONTROL"; + case UVC_CT_PRIVACY_CONTROL: + return "UVC_CT_PRIVACY_CONTROL"; + default: + return ""; + } +} + +static const char * +uvc_processing_unit_control_selector_name (uint8_t cs) +{ + switch (cs) { + case UVC_PU_CONTROL_UNDEFINED: + return "UVC_PU_CONTROL_UNDEFINED"; + case UVC_PU_BACKLIGHT_COMPENSATION_CONTROL: + return "UVC_PU_BACKLIGHT_COMPENSATION_CONTROL"; + case UVC_PU_BRIGHTNESS_CONTROL: + return "UVC_PU_BRIGHTNESS_CONTROL"; + case UVC_PU_CONTRAST_CONTROL: + return "UVC_PU_CONTRAST_CONTROL"; + case UVC_PU_GAIN_CONTROL: + return "UVC_PU_GAIN_CONTROL"; + case UVC_PU_POWER_LINE_FREQUENCY_CONTROL: + return "UVC_PU_POWER_LINE_FREQUENCY_CONTROL"; + case UVC_PU_HUE_CONTROL: + return "UVC_PU_HUE_CONTROL"; + case UVC_PU_SATURATION_CONTROL: + return "UVC_PU_SATURATION_CONTROL"; + case UVC_PU_SHARPNESS_CONTROL: + return "UVC_PU_SHARPNESS_CONTROL"; + case UVC_PU_GAMMA_CONTROL: + return "UVC_PU_GAMMA_CONTROL"; + case UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL: + return "UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL"; + case UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL: + return "UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL"; + case UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL: + return "UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL"; + case UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL: + return "UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL"; + case UVC_PU_DIGITAL_MULTIPLIER_CONTROL: + return "UVC_PU_DIGITAL_MULTIPLIER_CONTROL"; + case UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL: + return "UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL"; + case UVC_PU_HUE_AUTO_CONTROL: + return "UVC_PU_HUE_AUTO_CONTROL"; + case UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL: + return "UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL"; + case UVC_PU_ANALOG_LOCK_STATUS_CONTROL: + return "UVC_PU_ANALOG_LOCK_STATUS_CONTROL"; + default: + return ""; + } +} + +static const char * +uvc_video_streaming_interface_control_selector_name (uint8_t cs) +{ + switch (cs) { + case UVC_VS_CONTROL_UNDEFINED: + return "UVC_VS_CONTROL_UNDEFINED"; + case UVC_VS_PROBE_CONTROL: + return "UVC_VS_PROBE_CONTROL"; + case UVC_VS_COMMIT_CONTROL: + return "UVC_VS_COMMIT_CONTROL"; + case UVC_VS_STILL_PROBE_CONTROL: + return "UVC_VS_STILL_PROBE_CONTROL"; + case UVC_VS_STILL_COMMIT_CONTROL: + return "UVC_VS_STILL_COMMIT_CONTROL"; + case UVC_VS_STILL_IMAGE_TRIGGER_CONTROL: + return "UVC_VS_STILL_IMAGE_TRIGGER_CONTROL"; + case UVC_VS_STREAM_ERROR_CODE_CONTROL: + return "UVC_VS_STREAM_ERROR_CODE_CONTROL"; + case UVC_VS_GENERATE_KEY_FRAME_CONTROL: + return "UVC_VS_GENERATE_KEY_FRAME_CONTROL"; + case UVC_VS_UPDATE_FRAME_SEGMENT_CONTROL: + return "UVC_VS_UPDATE_FRAME_SEGMENT_CONTROL"; + case UVC_VS_SYNC_DELAY_CONTROL: + return "UVC_VS_SYNC_DELAY_CONTROL"; + default: + return ""; + } +} + +int +uvc_fill_streaming_control (GstUvcSink * self, + struct uvc_streaming_control *ctrl, + int iframe, int iformat, unsigned int dwival) +{ + const struct uvc_function_config_format *format; + const struct uvc_function_config_frame *frame; + unsigned int i; + + if (!self->fc) + return -ENOENT; + /* + * 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, self->fc->streaming.num_formats); + format = &self->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 (dwival <= frame->intervals[i]) { + dwival = frame->intervals[i]; + break; + } + } + + if (i == frame->num_intervals) + dwival = frame->intervals[frame->num_intervals - 1]; + + memset (ctrl, 0, sizeof (*ctrl)); + + ctrl->bmHint = 1; + ctrl->bFormatIndex = iformat; + ctrl->bFrameIndex = iframe; + ctrl->dwFrameInterval = dwival; + + switch (format->fcc) { + case V4L2_PIX_FMT_YUYV: + ctrl->dwMaxVideoFrameSize = frame->width * frame->height * 2; + break; + case V4L2_PIX_FMT_MJPEG: + ctrl->dwMaxVideoFrameSize = frame->maxvideofbsize; + break; + } + + ctrl->dwMaxPayloadTransferSize = self->fc->streaming.ep.wMaxPacketSize; + ctrl->bmFramingInfo = 3; + ctrl->bPreferedVersion = 1; + ctrl->bMaxVersion = 1; + + return 0; +} + +int +uvc_events_process_data (GstUvcSink * self, const struct uvc_request_data *data) +{ + const struct uvc_streaming_control *ctrl = + (const struct uvc_streaming_control *) &data->data; + struct uvc_streaming_control *target; + int ret; + + switch (self->control) { + case UVC_VS_PROBE_CONTROL: + GST_DEBUG_OBJECT (self, "setting probe control"); + target = &self->probe; + break; + + case UVC_VS_COMMIT_CONTROL: + GST_DEBUG_OBJECT (self, "setting commit control"); + target = &self->commit; + break; + + default: + GST_ELEMENT_ERROR (self, RESOURCE, READ, + ("setting unknown control, %d", self->control), NULL); + return -EOPNOTSUPP; + } + + ret = uvc_fill_streaming_control (self, target, ctrl->bFrameIndex, + ctrl->bFormatIndex, ctrl->dwFrameInterval); + if (ret) + return ret; + + if (self->control == UVC_VS_COMMIT_CONTROL) { + self->cur.bFrameIndex = ctrl->bFrameIndex; + self->cur.bFormatIndex = ctrl->bFormatIndex; + self->cur.dwFrameInterval = ctrl->dwFrameInterval; + } + + return 0; +} + +static int +uvc_events_process_streaming (GstUvcSink * self, uint8_t req, uint8_t cs, + struct uvc_request_data *resp) +{ + struct uvc_streaming_control *ctrl; + int ret; + + GST_DEBUG_OBJECT (self, + "%s: %s", + uvc_video_streaming_interface_control_selector_name (cs), + uvc_request_name (req)); + + if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL) + return 0; + + ctrl = (struct uvc_streaming_control *) &resp->data; + resp->length = sizeof (*ctrl); + + switch (req) { + case UVC_SET_CUR: + self->control = cs; + resp->length = 34; + break; + + case UVC_GET_CUR: + if (cs == UVC_VS_PROBE_CONTROL) + memcpy (ctrl, &self->probe, sizeof (*ctrl)); + else + memcpy (ctrl, &self->commit, sizeof (*ctrl)); + break; + + case UVC_GET_MIN: + case UVC_GET_MAX: + case UVC_GET_DEF: + if (req == UVC_GET_MAX) + ret = uvc_fill_streaming_control (self, ctrl, -1, -1, UINT_MAX); + else + ret = uvc_fill_streaming_control (self, ctrl, 1, 1, 0); + if (ret) + return ret; + 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; + } + + return 0; +} + +static void +uvc_events_parse_control (GstUvcSink * self, uint8_t req, + uint8_t cs, uint8_t entity_id, uint8_t len, struct uvc_request_data *resp) +{ + switch (entity_id) { + case 0: + GST_DEBUG_OBJECT (self, "%s", + uvc_video_control_interface_control_selector_name (cs)); + break; + + case 1: + GST_DEBUG_OBJECT (self, "%s: %s", + uvc_camera_terminal_control_selector_name (cs), + uvc_request_name (req)); + break; + + case 2: + GST_DEBUG_OBJECT (self, "%s: %s", + uvc_processing_unit_control_selector_name (cs), + uvc_request_name (req)); + break; + + default: + GST_DEBUG_OBJECT (self, + "Unknown entity ID (0x%02x), CS: 0x%02x, Request %s (0x%02x)", + entity_id, cs, uvc_request_name (req), req); + break; + } +} + +static int +uvc_events_process_class (GstUvcSink * self, + const struct usb_ctrlrequest *ctrl, struct uvc_request_data *resp) +{ + unsigned int interface = le16toh (ctrl->wIndex) & 0xff; + + if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE) + return -EINVAL; + + if (interface == UVC_STRING_CONTROL_IDX) { + uvc_events_parse_control (self, ctrl->bRequest, ctrl->wValue >> 8, + ctrl->wIndex >> 8, ctrl->wLength, resp); + return -EOPNOTSUPP; + } else if (interface == UVC_STRING_STREAMING_IDX) { + return uvc_events_process_streaming (self, ctrl->bRequest, + le16toh (ctrl->wValue) >> 8, resp); + } + + return 0; +} + +int +uvc_events_process_setup (GstUvcSink * self, + const struct usb_ctrlrequest *ctrl, struct uvc_request_data *resp) +{ + self->control = 0; + + GST_DEBUG_OBJECT (self, + "bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %04x", + ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, + ctrl->wIndex, ctrl->wLength); + + switch (ctrl->bRequestType & USB_TYPE_MASK) { + case USB_TYPE_STANDARD: + return -EOPNOTSUPP; + + case USB_TYPE_CLASS: + return uvc_events_process_class (self, ctrl, resp); + + default: + break; + } + + return 0; +} diff --git a/subprojects/gst-plugins-bad/sys/v4l2codecs/linux/usb/g_uvc.h b/subprojects/gst-plugins-bad/sys/v4l2codecs/linux/usb/g_uvc.h new file mode 100644 index 0000000000..652f169a01 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/v4l2codecs/linux/usb/g_uvc.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * g_uvc.h -- USB Video Class Gadget driver API + * + * Copyright (C) 2009-2010 Laurent Pinchart + */ + +#ifndef __LINUX_USB_G_UVC_H +#define __LINUX_USB_G_UVC_H + +#include +#include +#include + +#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 */ diff --git a/subprojects/gst-plugins-bad/sys/v4l2codecs/linux/usb/video.h b/subprojects/gst-plugins-bad/sys/v4l2codecs/linux/usb/video.h new file mode 100644 index 0000000000..bfdae12cda --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/v4l2codecs/linux/usb/video.h @@ -0,0 +1,571 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * USB Video Class definitions. + * + * Copyright (C) 2009 Laurent Pinchart + * + * This file holds USB constants and structures defined by the USB Device + * Class Definition for Video Devices. Unless otherwise stated, comments + * below reference relevant sections of the USB Video Class 1.1 specification + * available at + * + * http://www.usb.org/developers/devclass_docs/USB_Video_Class_1_1.zip + */ + +#ifndef __LINUX_USB_VIDEO_H +#define __LINUX_USB_VIDEO_H + +#include + +/* -------------------------------------------------------------------------- + * UVC constants + */ + +/* A.2. Video Interface Subclass Codes */ +#define UVC_SC_UNDEFINED 0x00 +#define UVC_SC_VIDEOCONTROL 0x01 +#define UVC_SC_VIDEOSTREAMING 0x02 +#define UVC_SC_VIDEO_INTERFACE_COLLECTION 0x03 + +/* A.3. Video Interface Protocol Codes */ +#define UVC_PC_PROTOCOL_UNDEFINED 0x00 +#define UVC_PC_PROTOCOL_15 0x01 + +/* A.5. Video Class-Specific VC Interface Descriptor Subtypes */ +#define UVC_VC_DESCRIPTOR_UNDEFINED 0x00 +#define UVC_VC_HEADER 0x01 +#define UVC_VC_INPUT_TERMINAL 0x02 +#define UVC_VC_OUTPUT_TERMINAL 0x03 +#define UVC_VC_SELECTOR_UNIT 0x04 +#define UVC_VC_PROCESSING_UNIT 0x05 +#define UVC_VC_EXTENSION_UNIT 0x06 + +/* A.6. Video Class-Specific VS Interface Descriptor Subtypes */ +#define UVC_VS_UNDEFINED 0x00 +#define UVC_VS_INPUT_HEADER 0x01 +#define UVC_VS_OUTPUT_HEADER 0x02 +#define UVC_VS_STILL_IMAGE_FRAME 0x03 +#define UVC_VS_FORMAT_UNCOMPRESSED 0x04 +#define UVC_VS_FRAME_UNCOMPRESSED 0x05 +#define UVC_VS_FORMAT_MJPEG 0x06 +#define UVC_VS_FRAME_MJPEG 0x07 +#define UVC_VS_FORMAT_MPEG2TS 0x0a +#define UVC_VS_FORMAT_DV 0x0c +#define UVC_VS_COLORFORMAT 0x0d +#define UVC_VS_FORMAT_FRAME_BASED 0x10 +#define UVC_VS_FRAME_FRAME_BASED 0x11 +#define UVC_VS_FORMAT_STREAM_BASED 0x12 + +/* A.7. Video Class-Specific Endpoint Descriptor Subtypes */ +#define UVC_EP_UNDEFINED 0x00 +#define UVC_EP_GENERAL 0x01 +#define UVC_EP_ENDPOINT 0x02 +#define UVC_EP_INTERRUPT 0x03 + +/* A.8. Video Class-Specific Request Codes */ +#define UVC_RC_UNDEFINED 0x00 +#define UVC_SET_CUR 0x01 +#define UVC_GET_CUR 0x81 +#define UVC_GET_MIN 0x82 +#define UVC_GET_MAX 0x83 +#define UVC_GET_RES 0x84 +#define UVC_GET_LEN 0x85 +#define UVC_GET_INFO 0x86 +#define UVC_GET_DEF 0x87 + +/* A.9.1. VideoControl Interface Control Selectors */ +#define UVC_VC_CONTROL_UNDEFINED 0x00 +#define UVC_VC_VIDEO_POWER_MODE_CONTROL 0x01 +#define UVC_VC_REQUEST_ERROR_CODE_CONTROL 0x02 + +/* A.9.2. Terminal Control Selectors */ +#define UVC_TE_CONTROL_UNDEFINED 0x00 + +/* A.9.3. Selector Unit Control Selectors */ +#define UVC_SU_CONTROL_UNDEFINED 0x00 +#define UVC_SU_INPUT_SELECT_CONTROL 0x01 + +/* A.9.4. Camera Terminal Control Selectors */ +#define UVC_CT_CONTROL_UNDEFINED 0x00 +#define UVC_CT_SCANNING_MODE_CONTROL 0x01 +#define UVC_CT_AE_MODE_CONTROL 0x02 +#define UVC_CT_AE_PRIORITY_CONTROL 0x03 +#define UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL 0x04 +#define UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL 0x05 +#define UVC_CT_FOCUS_ABSOLUTE_CONTROL 0x06 +#define UVC_CT_FOCUS_RELATIVE_CONTROL 0x07 +#define UVC_CT_FOCUS_AUTO_CONTROL 0x08 +#define UVC_CT_IRIS_ABSOLUTE_CONTROL 0x09 +#define UVC_CT_IRIS_RELATIVE_CONTROL 0x0a +#define UVC_CT_ZOOM_ABSOLUTE_CONTROL 0x0b +#define UVC_CT_ZOOM_RELATIVE_CONTROL 0x0c +#define UVC_CT_PANTILT_ABSOLUTE_CONTROL 0x0d +#define UVC_CT_PANTILT_RELATIVE_CONTROL 0x0e +#define UVC_CT_ROLL_ABSOLUTE_CONTROL 0x0f +#define UVC_CT_ROLL_RELATIVE_CONTROL 0x10 +#define UVC_CT_PRIVACY_CONTROL 0x11 + +/* A.9.5. Processing Unit Control Selectors */ +#define UVC_PU_CONTROL_UNDEFINED 0x00 +#define UVC_PU_BACKLIGHT_COMPENSATION_CONTROL 0x01 +#define UVC_PU_BRIGHTNESS_CONTROL 0x02 +#define UVC_PU_CONTRAST_CONTROL 0x03 +#define UVC_PU_GAIN_CONTROL 0x04 +#define UVC_PU_POWER_LINE_FREQUENCY_CONTROL 0x05 +#define UVC_PU_HUE_CONTROL 0x06 +#define UVC_PU_SATURATION_CONTROL 0x07 +#define UVC_PU_SHARPNESS_CONTROL 0x08 +#define UVC_PU_GAMMA_CONTROL 0x09 +#define UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL 0x0a +#define UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL 0x0b +#define UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL 0x0c +#define UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL 0x0d +#define UVC_PU_DIGITAL_MULTIPLIER_CONTROL 0x0e +#define UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL 0x0f +#define UVC_PU_HUE_AUTO_CONTROL 0x10 +#define UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL 0x11 +#define UVC_PU_ANALOG_LOCK_STATUS_CONTROL 0x12 + +/* A.9.7. VideoStreaming Interface Control Selectors */ +#define UVC_VS_CONTROL_UNDEFINED 0x00 +#define UVC_VS_PROBE_CONTROL 0x01 +#define UVC_VS_COMMIT_CONTROL 0x02 +#define UVC_VS_STILL_PROBE_CONTROL 0x03 +#define UVC_VS_STILL_COMMIT_CONTROL 0x04 +#define UVC_VS_STILL_IMAGE_TRIGGER_CONTROL 0x05 +#define UVC_VS_STREAM_ERROR_CODE_CONTROL 0x06 +#define UVC_VS_GENERATE_KEY_FRAME_CONTROL 0x07 +#define UVC_VS_UPDATE_FRAME_SEGMENT_CONTROL 0x08 +#define UVC_VS_SYNC_DELAY_CONTROL 0x09 + +/* B.1. USB Terminal Types */ +#define UVC_TT_VENDOR_SPECIFIC 0x0100 +#define UVC_TT_STREAMING 0x0101 + +/* B.2. Input Terminal Types */ +#define UVC_ITT_VENDOR_SPECIFIC 0x0200 +#define UVC_ITT_CAMERA 0x0201 +#define UVC_ITT_MEDIA_TRANSPORT_INPUT 0x0202 + +/* B.3. Output Terminal Types */ +#define UVC_OTT_VENDOR_SPECIFIC 0x0300 +#define UVC_OTT_DISPLAY 0x0301 +#define UVC_OTT_MEDIA_TRANSPORT_OUTPUT 0x0302 + +/* B.4. External Terminal Types */ +#define UVC_EXTERNAL_VENDOR_SPECIFIC 0x0400 +#define UVC_COMPOSITE_CONNECTOR 0x0401 +#define UVC_SVIDEO_CONNECTOR 0x0402 +#define UVC_COMPONENT_CONNECTOR 0x0403 + +/* 2.4.2.2. Status Packet Type */ +#define UVC_STATUS_TYPE_CONTROL 1 +#define UVC_STATUS_TYPE_STREAMING 2 + +/* 2.4.3.3. Payload Header Information */ +#define UVC_STREAM_EOH (1 << 7) +#define UVC_STREAM_ERR (1 << 6) +#define UVC_STREAM_STI (1 << 5) +#define UVC_STREAM_RES (1 << 4) +#define UVC_STREAM_SCR (1 << 3) +#define UVC_STREAM_PTS (1 << 2) +#define UVC_STREAM_EOF (1 << 1) +#define UVC_STREAM_FID (1 << 0) + +/* 4.1.2. Control Capabilities */ +#define UVC_CONTROL_CAP_GET (1 << 0) +#define UVC_CONTROL_CAP_SET (1 << 1) +#define UVC_CONTROL_CAP_DISABLED (1 << 2) +#define UVC_CONTROL_CAP_AUTOUPDATE (1 << 3) +#define UVC_CONTROL_CAP_ASYNCHRONOUS (1 << 4) + +/* ------------------------------------------------------------------------ + * UVC structures + */ + +/* All UVC descriptors have these 3 fields at the beginning */ +struct uvc_descriptor_header { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; +} __attribute__((packed)); + +/* 3.7.2. Video Control Interface Header Descriptor */ +struct uvc_header_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __le16 bcdUVC; + __le16 wTotalLength; + __le32 dwClockFrequency; + __u8 bInCollection; + __u8 baInterfaceNr[]; +} __attribute__((__packed__)); + +#define UVC_DT_HEADER_SIZE(n) (12+(n)) + +#define UVC_HEADER_DESCRIPTOR(n) \ + uvc_header_descriptor_##n + +#define DECLARE_UVC_HEADER_DESCRIPTOR(n) \ +struct UVC_HEADER_DESCRIPTOR(n) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __le16 bcdUVC; \ + __le16 wTotalLength; \ + __le32 dwClockFrequency; \ + __u8 bInCollection; \ + __u8 baInterfaceNr[n]; \ +} __attribute__ ((packed)) + +/* 3.7.2.1. Input Terminal Descriptor */ +struct uvc_input_terminal_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bTerminalID; + __le16 wTerminalType; + __u8 bAssocTerminal; + __u8 iTerminal; +} __attribute__((__packed__)); + +#define UVC_DT_INPUT_TERMINAL_SIZE 8 + +/* 3.7.2.2. Output Terminal Descriptor */ +struct uvc_output_terminal_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bTerminalID; + __le16 wTerminalType; + __u8 bAssocTerminal; + __u8 bSourceID; + __u8 iTerminal; +} __attribute__((__packed__)); + +#define UVC_DT_OUTPUT_TERMINAL_SIZE 9 + +/* 3.7.2.3. Camera Terminal Descriptor */ +struct uvc_camera_terminal_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bTerminalID; + __le16 wTerminalType; + __u8 bAssocTerminal; + __u8 iTerminal; + __le16 wObjectiveFocalLengthMin; + __le16 wObjectiveFocalLengthMax; + __le16 wOcularFocalLength; + __u8 bControlSize; + __u8 bmControls[3]; +} __attribute__((__packed__)); + +#define UVC_DT_CAMERA_TERMINAL_SIZE(n) (15+(n)) + +/* 3.7.2.4. Selector Unit Descriptor */ +struct uvc_selector_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bUnitID; + __u8 bNrInPins; + __u8 baSourceID[0]; + __u8 iSelector; +} __attribute__((__packed__)); + +#define UVC_DT_SELECTOR_UNIT_SIZE(n) (6+(n)) + +#define UVC_SELECTOR_UNIT_DESCRIPTOR(n) \ + uvc_selector_unit_descriptor_##n + +#define DECLARE_UVC_SELECTOR_UNIT_DESCRIPTOR(n) \ +struct UVC_SELECTOR_UNIT_DESCRIPTOR(n) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __u8 bUnitID; \ + __u8 bNrInPins; \ + __u8 baSourceID[n]; \ + __u8 iSelector; \ +} __attribute__ ((packed)) + +/* 3.7.2.5. Processing Unit Descriptor */ +struct uvc_processing_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bUnitID; + __u8 bSourceID; + __le16 wMaxMultiplier; + __u8 bControlSize; + __u8 bmControls[2]; + __u8 iProcessing; + __u8 bmVideoStandards; +} __attribute__((__packed__)); + +#define UVC_DT_PROCESSING_UNIT_SIZE(n) (10+(n)) + +/* 3.7.2.6. Extension Unit Descriptor */ +struct uvc_extension_unit_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bUnitID; + __u8 guidExtensionCode[16]; + __u8 bNumControls; + __u8 bNrInPins; + __u8 baSourceID[0]; + __u8 bControlSize; + __u8 bmControls[0]; + __u8 iExtension; +} __attribute__((__packed__)); + +#define UVC_DT_EXTENSION_UNIT_SIZE(p, n) (24+(p)+(n)) + +#define UVC_EXTENSION_UNIT_DESCRIPTOR(p, n) \ + uvc_extension_unit_descriptor_##p_##n + +#define DECLARE_UVC_EXTENSION_UNIT_DESCRIPTOR(p, n) \ +struct UVC_EXTENSION_UNIT_DESCRIPTOR(p, n) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __u8 bUnitID; \ + __u8 guidExtensionCode[16]; \ + __u8 bNumControls; \ + __u8 bNrInPins; \ + __u8 baSourceID[p]; \ + __u8 bControlSize; \ + __u8 bmControls[n]; \ + __u8 iExtension; \ +} __attribute__ ((packed)) + +/* 3.8.2.2. Video Control Interrupt Endpoint Descriptor */ +struct uvc_control_endpoint_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __le16 wMaxTransferSize; +} __attribute__((__packed__)); + +#define UVC_DT_CONTROL_ENDPOINT_SIZE 5 + +/* 3.9.2.1. Input Header Descriptor */ +struct uvc_input_header_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bNumFormats; + __le16 wTotalLength; + __u8 bEndpointAddress; + __u8 bmInfo; + __u8 bTerminalLink; + __u8 bStillCaptureMethod; + __u8 bTriggerSupport; + __u8 bTriggerUsage; + __u8 bControlSize; + __u8 bmaControls[]; +} __attribute__((__packed__)); + +#define UVC_DT_INPUT_HEADER_SIZE(n, p) (13+(n*p)) + +#define UVC_INPUT_HEADER_DESCRIPTOR(n, p) \ + uvc_input_header_descriptor_##n_##p + +#define DECLARE_UVC_INPUT_HEADER_DESCRIPTOR(n, p) \ +struct UVC_INPUT_HEADER_DESCRIPTOR(n, p) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __u8 bNumFormats; \ + __le16 wTotalLength; \ + __u8 bEndpointAddress; \ + __u8 bmInfo; \ + __u8 bTerminalLink; \ + __u8 bStillCaptureMethod; \ + __u8 bTriggerSupport; \ + __u8 bTriggerUsage; \ + __u8 bControlSize; \ + __u8 bmaControls[p][n]; \ +} __attribute__ ((packed)) + +/* 3.9.2.2. Output Header Descriptor */ +struct uvc_output_header_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bNumFormats; + __le16 wTotalLength; + __u8 bEndpointAddress; + __u8 bTerminalLink; + __u8 bControlSize; + __u8 bmaControls[]; +} __attribute__((__packed__)); + +#define UVC_DT_OUTPUT_HEADER_SIZE(n, p) (9+(n*p)) + +#define UVC_OUTPUT_HEADER_DESCRIPTOR(n, p) \ + uvc_output_header_descriptor_##n_##p + +#define DECLARE_UVC_OUTPUT_HEADER_DESCRIPTOR(n, p) \ +struct UVC_OUTPUT_HEADER_DESCRIPTOR(n, p) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __u8 bNumFormats; \ + __le16 wTotalLength; \ + __u8 bEndpointAddress; \ + __u8 bTerminalLink; \ + __u8 bControlSize; \ + __u8 bmaControls[p][n]; \ +} __attribute__ ((packed)) + +/* 3.9.2.6. Color matching descriptor */ +struct uvc_color_matching_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bColorPrimaries; + __u8 bTransferCharacteristics; + __u8 bMatrixCoefficients; +} __attribute__((__packed__)); + +#define UVC_DT_COLOR_MATCHING_SIZE 6 + +/* 4.3.1.1. Video Probe and Commit Controls */ +struct uvc_streaming_control { + __u16 bmHint; + __u8 bFormatIndex; + __u8 bFrameIndex; + __u32 dwFrameInterval; + __u16 wKeyFrameRate; + __u16 wPFrameRate; + __u16 wCompQuality; + __u16 wCompWindowSize; + __u16 wDelay; + __u32 dwMaxVideoFrameSize; + __u32 dwMaxPayloadTransferSize; + __u32 dwClockFrequency; + __u8 bmFramingInfo; + __u8 bPreferedVersion; + __u8 bMinVersion; + __u8 bMaxVersion; +} __attribute__((__packed__)); + +/* Uncompressed Payload - 3.1.1. Uncompressed Video Format Descriptor */ +struct uvc_format_uncompressed { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bFormatIndex; + __u8 bNumFrameDescriptors; + __u8 guidFormat[16]; + __u8 bBitsPerPixel; + __u8 bDefaultFrameIndex; + __u8 bAspectRatioX; + __u8 bAspectRatioY; + __u8 bmInterfaceFlags; + __u8 bCopyProtect; +} __attribute__((__packed__)); + +#define UVC_DT_FORMAT_UNCOMPRESSED_SIZE 27 + +/* Uncompressed Payload - 3.1.2. Uncompressed Video Frame Descriptor */ +struct uvc_frame_uncompressed { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bFrameIndex; + __u8 bmCapabilities; + __le16 wWidth; + __le16 wHeight; + __le32 dwMinBitRate; + __le32 dwMaxBitRate; + __le32 dwMaxVideoFrameBufferSize; + __le32 dwDefaultFrameInterval; + __u8 bFrameIntervalType; + __le32 dwFrameInterval[]; +} __attribute__((__packed__)); + +#define UVC_DT_FRAME_UNCOMPRESSED_SIZE(n) (26+4*(n)) + +#define UVC_FRAME_UNCOMPRESSED(n) \ + uvc_frame_uncompressed_##n + +#define DECLARE_UVC_FRAME_UNCOMPRESSED(n) \ +struct UVC_FRAME_UNCOMPRESSED(n) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __u8 bFrameIndex; \ + __u8 bmCapabilities; \ + __le16 wWidth; \ + __le16 wHeight; \ + __le32 dwMinBitRate; \ + __le32 dwMaxBitRate; \ + __le32 dwMaxVideoFrameBufferSize; \ + __le32 dwDefaultFrameInterval; \ + __u8 bFrameIntervalType; \ + __le32 dwFrameInterval[n]; \ +} __attribute__ ((packed)) + +/* MJPEG Payload - 3.1.1. MJPEG Video Format Descriptor */ +struct uvc_format_mjpeg { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bFormatIndex; + __u8 bNumFrameDescriptors; + __u8 bmFlags; + __u8 bDefaultFrameIndex; + __u8 bAspectRatioX; + __u8 bAspectRatioY; + __u8 bmInterfaceFlags; + __u8 bCopyProtect; +} __attribute__((__packed__)); + +#define UVC_DT_FORMAT_MJPEG_SIZE 11 + +/* MJPEG Payload - 3.1.2. MJPEG Video Frame Descriptor */ +struct uvc_frame_mjpeg { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubType; + __u8 bFrameIndex; + __u8 bmCapabilities; + __le16 wWidth; + __le16 wHeight; + __le32 dwMinBitRate; + __le32 dwMaxBitRate; + __le32 dwMaxVideoFrameBufferSize; + __le32 dwDefaultFrameInterval; + __u8 bFrameIntervalType; + __le32 dwFrameInterval[]; +} __attribute__((__packed__)); + +#define UVC_DT_FRAME_MJPEG_SIZE(n) (26+4*(n)) + +#define UVC_FRAME_MJPEG(n) \ + uvc_frame_mjpeg_##n + +#define DECLARE_UVC_FRAME_MJPEG(n) \ +struct UVC_FRAME_MJPEG(n) { \ + __u8 bLength; \ + __u8 bDescriptorType; \ + __u8 bDescriptorSubType; \ + __u8 bFrameIndex; \ + __u8 bmCapabilities; \ + __le16 wWidth; \ + __le16 wHeight; \ + __le32 dwMinBitRate; \ + __le32 dwMaxBitRate; \ + __le32 dwMaxVideoFrameBufferSize; \ + __le32 dwDefaultFrameInterval; \ + __u8 bFrameIntervalType; \ + __le32 dwFrameInterval[n]; \ +} __attribute__ ((packed)) + +#endif /* __LINUX_USB_VIDEO_H */ +