uvcsink: add new bin element around v4l2sink

This patch adds an element to stream video data to an uvc video gadget.
The element handles the uvc events STREAMON, STREAMOFF, SETUP and DATA.
to start, stop and configure the video buffer flow by the use of pad
probes. It works with linux kernels of versions higher than v6.1.

The element makes use of the v4l2sink proxy property v4l2sink::device
to locate the corresponding device to parse the configfs for additional
data.

The code in uvc.c is basically derived from /lib/uvc.c in
https://git.ideasonboard.org/uvc-gadget.git.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1304>
This commit is contained in:
Michael Grzeschik 2023-04-17 23:53:58 +02:00 committed by GStreamer Marge Bot
parent 6ef14df09b
commit 5d82deb2c5
9 changed files with 1850 additions and 0 deletions

View file

@ -233656,6 +233656,49 @@
"tracers": {}, "tracers": {},
"url": "Unknown package origin" "url": "Unknown package origin"
}, },
"uvcgadget": {
"description": "gstuvcgadget plugin",
"elements": {
"uvcsink": {
"author": "Michael Grzeschik <mgr@pengutronix.de>",
"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": { "uvch264": {
"description": "UVC compliant H264 encoding cameras plugin", "description": "UVC compliant H264 encoding cameras plugin",
"elements": { "elements": {

View file

@ -195,6 +195,7 @@ option(
option('magicleap', type : 'feature', value : 'auto', description : 'Magic Leap platform support') option('magicleap', type : 'feature', value : 'auto', description : 'Magic Leap platform support')
option('v4l2codecs', type : 'feature', value : 'auto', description : 'Video4Linux Stateless CODECs 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') option('isac', type : 'feature', value : 'auto', description : 'iSAC plugin')
# HLS plugin options # HLS plugin options

View file

@ -22,6 +22,7 @@ subdir('shm')
subdir('tinyalsa') subdir('tinyalsa')
subdir('uvch264') subdir('uvch264')
subdir('v4l2codecs') subdir('v4l2codecs')
subdir('uvcgadget')
subdir('va') subdir('va')
subdir('wasapi') subdir('wasapi')
subdir('wasapi2') subdir('wasapi2')

View file

@ -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 <config.h>
#endif
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#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 <mgr@pengutronix.de>");
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)

View file

@ -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 <gst/gst.h>
#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

View file

@ -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

View file

@ -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 <endian.h>
#include <glob.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#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 "<invalid>";
}
}
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 "<unknown video control interface control selector>";
}
}
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 "<unknown camera terminal control selector>";
}
}
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 "<unknown processing unit control selector>";
}
}
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 "<unknown video streaming interface control selector>";
}
}
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;
}

View file

@ -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 <laurent.pinchart@ideasonboard.com>
*/
#ifndef __LINUX_USB_G_UVC_H
#define __LINUX_USB_G_UVC_H
#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/usb/ch9.h>
#define UVC_EVENT_FIRST (V4L2_EVENT_PRIVATE_START + 0)
#define UVC_EVENT_CONNECT (V4L2_EVENT_PRIVATE_START + 0)
#define UVC_EVENT_DISCONNECT (V4L2_EVENT_PRIVATE_START + 1)
#define UVC_EVENT_STREAMON (V4L2_EVENT_PRIVATE_START + 2)
#define UVC_EVENT_STREAMOFF (V4L2_EVENT_PRIVATE_START + 3)
#define UVC_EVENT_SETUP (V4L2_EVENT_PRIVATE_START + 4)
#define UVC_EVENT_DATA (V4L2_EVENT_PRIVATE_START + 5)
#define UVC_EVENT_LAST (V4L2_EVENT_PRIVATE_START + 5)
struct uvc_request_data {
__s32 length;
__u8 data[60];
};
struct uvc_event {
union {
enum usb_device_speed speed;
struct usb_ctrlrequest req;
struct uvc_request_data data;
};
};
#define UVCIOC_SEND_RESPONSE _IOW('U', 1, struct uvc_request_data)
#endif /* __LINUX_USB_G_UVC_H */

View file

@ -0,0 +1,571 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
/*
* USB Video Class definitions.
*
* Copyright (C) 2009 Laurent Pinchart <laurent.pinchart@skynet.be>
*
* 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 <linux/types.h>
/* --------------------------------------------------------------------------
* 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 */