gstreamer/subprojects/gst-plugins-bad/sys/uvcgadget/gstuvcsink.c
Michael Grzeschik 4670e1c809 uvcsink: make gst_v4l2uvc_fourcc_to_bare_struct work with more raw formats
The uvcsink was limited to only transfer YUY2 and MJPEG. For the
uncompressed formats there is no technical reason not to support them.

Since gst_video_format_to_string is already supporting more fourcc than
only YUY2 we use the default path in gst_v4l2uvc_fourcc_to_bare_struct
to create structures for more formats and bail out if the returned
format is not from the uncompressed type.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6037>
2024-09-24 20:07:40 +00:00

1025 lines
29 KiB
C

/*
* 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 GstVideoFormat
gst_v4l2_object_v4l2fourcc_to_video_format (guint32 fourcc)
{
GstVideoFormat format;
switch (fourcc) {
case V4L2_PIX_FMT_GREY: /* 8 Greyscale */
format = GST_VIDEO_FORMAT_GRAY8;
break;
case V4L2_PIX_FMT_Y16:
format = GST_VIDEO_FORMAT_GRAY16_LE;
break;
case V4L2_PIX_FMT_Y16_BE:
format = GST_VIDEO_FORMAT_GRAY16_BE;
break;
case V4L2_PIX_FMT_XRGB555:
case V4L2_PIX_FMT_RGB555:
format = GST_VIDEO_FORMAT_RGB15;
break;
case V4L2_PIX_FMT_XRGB555X:
case V4L2_PIX_FMT_RGB555X:
format = GST_VIDEO_FORMAT_BGR15;
break;
case V4L2_PIX_FMT_RGB565:
format = GST_VIDEO_FORMAT_RGB16;
break;
case V4L2_PIX_FMT_RGB24:
format = GST_VIDEO_FORMAT_RGB;
break;
case V4L2_PIX_FMT_BGR24:
format = GST_VIDEO_FORMAT_BGR;
break;
case V4L2_PIX_FMT_XRGB32:
case V4L2_PIX_FMT_RGB32:
format = GST_VIDEO_FORMAT_xRGB;
break;
case V4L2_PIX_FMT_RGBX32:
format = GST_VIDEO_FORMAT_RGBx;
break;
case V4L2_PIX_FMT_XBGR32:
case V4L2_PIX_FMT_BGR32:
format = GST_VIDEO_FORMAT_BGRx;
break;
case V4L2_PIX_FMT_BGRX32:
format = GST_VIDEO_FORMAT_xBGR;
break;
case V4L2_PIX_FMT_ABGR32:
format = GST_VIDEO_FORMAT_BGRA;
break;
case V4L2_PIX_FMT_BGRA32:
format = GST_VIDEO_FORMAT_ABGR;
break;
case V4L2_PIX_FMT_RGBA32:
format = GST_VIDEO_FORMAT_RGBA;
break;
case V4L2_PIX_FMT_ARGB32:
format = GST_VIDEO_FORMAT_ARGB;
break;
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_NV12M:
format = GST_VIDEO_FORMAT_NV12;
break;
case V4L2_PIX_FMT_NV12MT:
format = GST_VIDEO_FORMAT_NV12_64Z32;
break;
case V4L2_PIX_FMT_MM21:
format = GST_VIDEO_FORMAT_NV12_16L32S;
break;
case V4L2_PIX_FMT_NV12M_8L128:
format = GST_VIDEO_FORMAT_NV12_8L128;
break;
case V4L2_PIX_FMT_NV12M_10BE_8L128:
format = GST_VIDEO_FORMAT_NV12_10BE_8L128;
break;
case V4L2_PIX_FMT_NV21:
case V4L2_PIX_FMT_NV21M:
format = GST_VIDEO_FORMAT_NV21;
break;
case V4L2_PIX_FMT_YVU410:
format = GST_VIDEO_FORMAT_YVU9;
break;
case V4L2_PIX_FMT_YUV410:
format = GST_VIDEO_FORMAT_YUV9;
break;
case V4L2_PIX_FMT_YUV420:
case V4L2_PIX_FMT_YUV420M:
format = GST_VIDEO_FORMAT_I420;
break;
case V4L2_PIX_FMT_YUYV:
format = GST_VIDEO_FORMAT_YUY2;
break;
case V4L2_PIX_FMT_YVU420:
case V4L2_PIX_FMT_YVU420M:
format = GST_VIDEO_FORMAT_YV12;
break;
case V4L2_PIX_FMT_UYVY:
format = GST_VIDEO_FORMAT_UYVY;
break;
case V4L2_PIX_FMT_YUV411P:
format = GST_VIDEO_FORMAT_Y41B;
break;
case V4L2_PIX_FMT_YUV422P:
format = GST_VIDEO_FORMAT_Y42B;
break;
case V4L2_PIX_FMT_YVYU:
format = GST_VIDEO_FORMAT_YVYU;
break;
case V4L2_PIX_FMT_NV16:
case V4L2_PIX_FMT_NV16M:
format = GST_VIDEO_FORMAT_NV16;
break;
case V4L2_PIX_FMT_NV61:
case V4L2_PIX_FMT_NV61M:
format = GST_VIDEO_FORMAT_NV61;
break;
case V4L2_PIX_FMT_NV24:
format = GST_VIDEO_FORMAT_NV24;
break;
default:
format = GST_VIDEO_FORMAT_UNKNOWN;
break;
}
return format;
}
static GstStructure *
gst_v4l2uvc_fourcc_to_bare_struct (guint32 fourcc)
{
GstStructure *structure = NULL;
GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
switch (fourcc) {
case V4L2_PIX_FMT_MJPEG: /* Motion-JPEG */
case V4L2_PIX_FMT_JPEG: /* JFIF JPEG */
structure = gst_structure_new_empty ("image/jpeg");
break;
default:
format = gst_v4l2_object_v4l2fourcc_to_video_format (fourcc);
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
GST_DEBUG ("Unsupported fourcc 0x%08x %" GST_FOURCC_FORMAT,
fourcc, GST_FOURCC_ARGS (fourcc));
break;
}
structure = gst_structure_new ("video/x-raw",
"format", G_TYPE_STRING, gst_video_format_to_string (format), NULL);
break;
}
return structure;
}
/* The UVC EVENT_DATA from the host, which is committing 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 GstCaps *
gst_uvc_sink_get_configured_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 NULL;
}
s = gst_v4l2uvc_fourcc_to_bare_struct (format.pixelformat);
if (!s)
return NULL;
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 NULL;
}
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 NULL;
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 NULL;
}
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);
return gst_caps_new_full (s, NULL);
}
static gboolean gst_uvc_sink_to_fakesink (GstUvcSink * self);
static gboolean gst_uvc_sink_to_v4l2sink (GstUvcSink * self);
static void
gst_uvc_sink_update_streaming (GstUvcSink * self)
{
if (self->streamon && !self->streaming)
GST_ERROR_OBJECT (self, "Unexpected STREAMON");
if (self->streamoff && self->streaming)
GST_ERROR_OBJECT (self, "Unexpected STREAMOFF");
if (self->streamon)
gst_uvc_sink_to_v4l2sink (self);
g_atomic_int_set (&self->streamon, FALSE);
g_atomic_int_set (&self->streamoff, FALSE);
}
static gboolean
gst_uvc_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
{
GstUvcSink *self = GST_UVCSINK (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
GST_DEBUG_OBJECT (self, "Handling %" GST_PTR_FORMAT, event);
/* If the UVC host did not yet commit a format, the cur_caps may contain
* all probed caps. In this case, the element is not able to detect, if
* the caps have changed when the stream is enabled. Take the caps from
* upstream to be able to detect a change.
*/
if (!GST_CAPS_IS_SIMPLE (self->cur_caps)) {
GstCaps *caps;
gst_event_parse_caps (event, &caps);
gst_caps_replace (&self->cur_caps, caps);
GST_DEBUG_OBJECT (self,
"UVC host didn't select a format, yet. Using upstream %"
GST_PTR_FORMAT, self->cur_caps);
}
/* EVENT CAPS signals that the buffers after the event will use new caps.
* If the UVC host requested a new format, we now must start the stream.
*/
if (self->caps_changed) {
if (self->streamon || self->streamoff)
g_atomic_int_set (&self->caps_changed, FALSE);
gst_uvc_sink_update_streaming (self);
}
break;
default:
break;
}
return gst_pad_event_default (pad, parent, event);
}
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);
if (!self->caps_changed)
gst_uvc_sink_update_streaming (self);
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_to_fakesink (GstUvcSink * self)
{
if (gst_pad_is_linked (self->fakesinkpad)) {
GST_DEBUG_OBJECT (self, "v4l2sink already disabled");
return FALSE;
}
GST_DEBUG_OBJECT (self, "switching to fakesink");
gst_ghost_pad_set_target (GST_GHOST_PAD (self->sinkpad), self->fakesinkpad);
gst_element_set_state (GST_ELEMENT (self->fakesink), GST_STATE_PLAYING);
/* going to state READY makes v4l2sink lose its reference to the clock */
self->v4l2_clock = gst_element_get_clock (self->v4l2sink);
gst_pad_query (self->v4l2sinkpad, gst_query_new_drain ());
gst_element_set_state (GST_ELEMENT (self->v4l2sink), GST_STATE_READY);
return TRUE;
}
static gboolean
gst_uvc_sink_to_v4l2sink (GstUvcSink * self)
{
if (gst_pad_is_linked (self->v4l2sinkpad)) {
GST_DEBUG_OBJECT (self, "fakesink already disabled");
return FALSE;
}
if (self->v4l2_clock) {
gst_element_set_clock (self->v4l2sink, self->v4l2_clock);
gst_object_unref (self->v4l2_clock);
}
GST_DEBUG_OBJECT (self, "switching to v4l2sink");
gst_ghost_pad_set_target (GST_GHOST_PAD (self->sinkpad), self->v4l2sinkpad);
gst_element_set_state (GST_ELEMENT (self->v4l2sink), GST_STATE_PLAYING);
gst_pad_query (self->fakesinkpad, gst_query_new_drain ());
gst_element_set_state (GST_ELEMENT (self->fakesink), GST_STATE_READY);
return TRUE;
}
static GstPadProbeReturn
gst_uvc_sink_sinkpad_buffer_peer_probe (GstPad * pad,
GstPadProbeInfo * info, gpointer user_data)
{
GstUvcSink *self = user_data;
if (self->streamon || self->streamoff)
return GST_PAD_PROBE_DROP;
self->buffer_peer_probe_id = 0;
return GST_PAD_PROBE_REMOVE;
}
static GstPadProbeReturn
gst_uvc_sink_sinkpad_idle_probe (GstPad * pad,
GstPadProbeInfo * info, gpointer user_data)
{
GstUvcSink *self = user_data;
if (self->streamon || self->streamoff) {
/* Drop all incoming buffers until the streamoff or streamon is done. */
self->buffer_peer_probe_id =
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
gst_uvc_sink_sinkpad_buffer_peer_probe, self, NULL);
GST_DEBUG_OBJECT (self, "Send reconfigure");
gst_pad_push_event (self->sinkpad, gst_event_new_reconfigure ());
}
if (self->streamoff)
gst_uvc_sink_to_fakesink (self);
return GST_PAD_PROBE_PASS;
}
static void
gst_uvc_sink_remove_buffer_peer_probe (GstUvcSink * self)
{
GstPad *peerpad = gst_pad_get_peer (self->sinkpad);
if (peerpad && self->buffer_peer_probe_id) {
gst_pad_remove_probe (peerpad, self->buffer_peer_probe_id);
gst_object_unref (peerpad);
self->buffer_peer_probe_id = 0;
}
}
static void
gst_uvc_sink_create_idle_probe (GstUvcSink * self)
{
GstPad *peerpad = gst_pad_get_peer (self->sinkpad);
if (peerpad) {
self->idle_probe_id =
gst_pad_add_probe (peerpad, GST_PAD_PROBE_TYPE_IDLE,
gst_uvc_sink_sinkpad_idle_probe, self, NULL);
gst_object_unref (peerpad);
}
}
static void
gst_uvc_sink_remove_idle_probe (GstUvcSink * self)
{
GstPad *peerpad = gst_pad_get_peer (self->sinkpad);
if (peerpad && self->idle_probe_id) {
gst_pad_remove_probe (peerpad, self->idle_probe_id);
gst_object_unref (peerpad);
self->idle_probe_id = 0;
}
}
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);
/* add the fakesink element */
self->fakesink = gst_element_factory_make ("fakesink", "fakesink");
if (!self->fakesink)
return;
g_object_set (G_OBJECT (self->fakesink), "sync", TRUE, NULL);
gst_bin_add (GST_BIN (self), self->fakesink);
self->v4l2sinkpad = gst_element_get_static_pad (self->v4l2sink, "sink");
g_return_if_fail (self->v4l2sinkpad != NULL);
self->fakesinkpad = gst_element_get_static_pad (self->fakesink, "sink");
g_return_if_fail (self->fakesinkpad != NULL);
/* create ghost pad sink */
self->sinkpad = gst_ghost_pad_new ("sink", self->fakesinkpad);
gst_element_add_pad (GST_ELEMENT (self), self->sinkpad);
g_atomic_int_set (&self->streaming, FALSE);
g_atomic_int_set (&self->streamon, FALSE);
g_atomic_int_set (&self->streamoff, FALSE);
gst_pad_set_query_function (self->sinkpad, gst_uvc_sink_query);
gst_pad_set_event_function (self->sinkpad, gst_uvc_sink_event);
self->cur_caps = gst_caps_new_empty ();
}
static void
gst_uvc_sink_dispose (GObject * object)
{
GstUvcSink *self = GST_UVCSINK (object);
if (self->sinkpad) {
gst_uvc_sink_remove_buffer_peer_probe (self);
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;
/* Since the plugin needs to be able to start immidiatly in PLAYING
state we ensure the pipeline is not blocked while we poll for
events.
*/
GST_PAD_STREAM_UNLOCK (self->sinkpad);
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");
GST_STATE_LOCK (GST_ELEMENT (self));
g_atomic_int_set (&self->streaming, TRUE);
g_atomic_int_set (&self->streamon, TRUE);
GST_STATE_UNLOCK (GST_ELEMENT (self));
g_object_notify (G_OBJECT (self), "streaming");
break;
case UVC_EVENT_STREAMOFF:
case UVC_EVENT_DISCONNECT:
GST_DEBUG_OBJECT (self, "UVC_EVENT_STREAMOFF");
GST_STATE_LOCK (GST_ELEMENT (self));
g_atomic_int_set (&self->streaming, FALSE);
g_atomic_int_set (&self->streamoff, TRUE);
GST_STATE_UNLOCK (GST_ELEMENT (self));
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 *configured_caps;
GstCaps *prev_caps;
/* The configured caps are not sufficient for negotiation. Select caps
* from the probed caps that match the configured caps.
*/
configured_caps = gst_uvc_sink_get_configured_caps (self);
if (!configured_caps) {
GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
("gst_uvc_sink_get_configured_caps failed"),
("gst_uvc_sink_get_configured_caps on current format failed"));
gst_uvc_sink_unwatch (self);
gst_element_set_state (GST_ELEMENT (self), GST_STATE_NULL);
return;
}
gst_clear_caps (&self->cur_caps);
self->cur_caps =
gst_caps_intersect_full (self->probed_caps, configured_caps,
GST_CAPS_INTERSECT_FIRST);
GST_INFO_OBJECT (self, "UVC host selected %" GST_PTR_FORMAT,
self->cur_caps);
gst_caps_unref (configured_caps);
prev_caps = gst_pad_get_current_caps (self->sinkpad);
if (prev_caps) {
if (!gst_caps_is_subset (prev_caps, self->cur_caps)) {
self->caps_changed = TRUE;
GST_DEBUG_OBJECT (self,
"caps changed from %" GST_PTR_FORMAT, prev_caps);
}
gst_caps_unref (prev_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:
gst_uvc_sink_create_idle_probe (self);
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_element_sync_state_with_parent (GST_ELEMENT (self->fakesink));
gst_uvc_sink_remove_idle_probe (self);
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)