gstreamer/subprojects/gst-plugins-good/sys/v4l2/gstv4l2deviceprovider.c
Nicolas Dufresne 3bd43672ec v4l2: device provider: Fix GMainLoop leak
On very quick start/stop, the mainloop may never be run. As a side
effect, our idle stop function is not really being ran, so we can't rely
on that to free the main loop. Simply unref the mainloop when the
thread have completely stop.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4521>
2023-05-03 10:04:58 +00:00

611 lines
17 KiB
C

/* GStreamer
* Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com>
*
* gstv4l2deviceprovider.c: V4l2 device probing and monitoring
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstv4l2deviceprovider.h"
#include <string.h>
#include <sys/stat.h>
#include <gst/gst.h>
#include "gstv4l2object.h"
#include "v4l2-utils.h"
#include "gstv4l2elements.h"
#ifdef HAVE_GUDEV
#include <gudev/gudev.h>
#endif
static GstV4l2Device *gst_v4l2_device_new (const gchar * device_path,
const gchar * device_name, GstCaps * caps, GstV4l2DeviceType type,
GstStructure * props);
G_DEFINE_TYPE (GstV4l2DeviceProvider, gst_v4l2_device_provider,
GST_TYPE_DEVICE_PROVIDER);
GST_DEVICE_PROVIDER_REGISTER_DEFINE (v4l2deviceprovider, "v4l2deviceprovider",
GST_RANK_PRIMARY, GST_TYPE_V4L2_DEVICE_PROVIDER);
static void gst_v4l2_device_provider_finalize (GObject * object);
static GList *gst_v4l2_device_provider_probe (GstDeviceProvider * provider);
#ifdef HAVE_GUDEV
static gboolean gst_v4l2_device_provider_start (GstDeviceProvider * provider);
static void gst_v4l2_device_provider_stop (GstDeviceProvider * provider);
#endif
static void
gst_v4l2_device_provider_class_init (GstV4l2DeviceProviderClass * klass)
{
GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
dm_class->probe = gst_v4l2_device_provider_probe;
#ifdef HAVE_GUDEV
dm_class->start = gst_v4l2_device_provider_start;
dm_class->stop = gst_v4l2_device_provider_stop;
#endif
gobject_class->finalize = gst_v4l2_device_provider_finalize;
gst_device_provider_class_set_static_metadata (dm_class,
"Video (video4linux2) Device Provider", "Source/Sink/Video",
"List and monitor video4linux2 source and sink devices",
"Olivier Crete <olivier.crete@collabora.com>");
}
static void
gst_v4l2_device_provider_init (GstV4l2DeviceProvider * provider)
{
#ifdef HAVE_GUDEV
g_cond_init (&provider->started_cond);
#endif
}
static void
gst_v4l2_device_provider_finalize (GObject * object)
{
#ifdef HAVE_GUDEV
GstV4l2DeviceProvider *provider = GST_V4L2_DEVICE_PROVIDER (object);
g_cond_clear (&provider->started_cond);
#endif
G_OBJECT_CLASS (gst_v4l2_device_provider_parent_class)->finalize (object);
}
static GstV4l2Device *
gst_v4l2_device_provider_probe_device (GstV4l2DeviceProvider * provider,
const gchar * device_path, const gchar * device_name, GstStructure * props)
{
GstV4l2Object *v4l2obj = NULL;
GstCaps *caps;
GstV4l2Device *device = NULL;
struct stat st;
GstV4l2DeviceType type = GST_V4L2_DEVICE_TYPE_INVALID;
g_return_val_if_fail (props != NULL, NULL);
if (stat (device_path, &st) == -1)
goto destroy;
if (!S_ISCHR (st.st_mode))
goto destroy;
v4l2obj = gst_v4l2_object_new (NULL, GST_OBJECT (provider),
V4L2_BUF_TYPE_VIDEO_CAPTURE, device_path, NULL, NULL, NULL);
if (!gst_v4l2_open (v4l2obj, NULL))
goto destroy;
gst_structure_set (props, "device.api", G_TYPE_STRING, "v4l2", NULL);
gst_structure_set (props, "device.path", G_TYPE_STRING, device_path, NULL);
gst_structure_set (props, "v4l2.device.driver", G_TYPE_STRING,
v4l2obj->vcap.driver, NULL);
gst_structure_set (props, "v4l2.device.card", G_TYPE_STRING,
v4l2obj->vcap.card, NULL);
gst_structure_set (props, "v4l2.device.bus_info", G_TYPE_STRING,
v4l2obj->vcap.bus_info, NULL);
gst_structure_set (props, "v4l2.device.version", G_TYPE_UINT,
v4l2obj->vcap.version, NULL);
gst_structure_set (props, "v4l2.device.capabilities", G_TYPE_UINT,
v4l2obj->vcap.capabilities, NULL);
gst_structure_set (props, "v4l2.device.device_caps", G_TYPE_UINT,
v4l2obj->vcap.device_caps, NULL);
if (v4l2obj->device_caps &
(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
/* We ignore touch sensing devices; those are't really video */
if (v4l2obj->device_caps & V4L2_CAP_TOUCH)
goto close;
type = GST_V4L2_DEVICE_TYPE_SOURCE;
v4l2obj->skip_try_fmt_probes = TRUE;
}
if (v4l2obj->device_caps &
(V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE)) {
/* We ignore M2M devices that are both capture and output for now
* The provider is not for them */
if (type != GST_V4L2_DEVICE_TYPE_INVALID)
goto close;
type = GST_V4L2_DEVICE_TYPE_SINK;
/* We have opened as a capture as we didn't know, now that know,
* let's fixed it */
if (v4l2obj->device_caps & V4L2_CAP_VIDEO_OUTPUT_MPLANE)
v4l2obj->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
else
v4l2obj->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
}
if (type == GST_V4L2_DEVICE_TYPE_INVALID)
goto close;
caps = gst_v4l2_object_get_caps (v4l2obj, NULL);
if (caps == NULL)
goto close;
if (gst_caps_is_empty (caps)) {
gst_caps_unref (caps);
goto close;
}
device = gst_v4l2_device_new (device_path,
device_name ? device_name : (gchar *) v4l2obj->vcap.card, caps, type,
props);
gst_caps_unref (caps);
close:
gst_v4l2_close (v4l2obj);
destroy:
if (v4l2obj)
gst_v4l2_object_destroy (v4l2obj);
if (props)
gst_structure_free (props);
return device;
}
static GList *
gst_v4l2_device_provider_probe (GstDeviceProvider * provider)
{
GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
GstV4l2Iterator *it;
GList *devices = NULL;
it = gst_v4l2_iterator_new ();
while (gst_v4l2_iterator_next (it)) {
GstStructure *props;
GstV4l2Device *device;
props = gst_structure_new ("v4l2-proplist", "device.path", G_TYPE_STRING,
it->device_path, "udev-probed", G_TYPE_BOOLEAN, FALSE, NULL);
device = gst_v4l2_device_provider_probe_device (self, it->device_path, NULL,
props);
if (device) {
gst_object_ref_sink (device);
devices = g_list_prepend (devices, device);
}
}
gst_v4l2_iterator_free (it);
return devices;
}
#ifdef HAVE_GUDEV
static GstDevice *
gst_v4l2_device_provider_device_from_udev (GstV4l2DeviceProvider * provider,
GUdevDevice * udev_device)
{
GstV4l2Device *gstdev;
const gchar *device_path = g_udev_device_get_device_file (udev_device);
const gchar *device_name, *str;
GstStructure *props;
props = gst_structure_new ("v4l2deviceprovider", "udev-probed",
G_TYPE_BOOLEAN, TRUE, NULL);
str = g_udev_device_get_property (udev_device, "ID_PATH");
if (!(str && *str)) {
str = g_udev_device_get_sysfs_path (udev_device);
}
if (str && *str)
gst_structure_set (props, "device.bus_path", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_sysfs_path (udev_device)) && *str)
gst_structure_set (props, "sysfs.path", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_property (udev_device, "ID_ID")) && *str)
gst_structure_set (props, "udev.id", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_property (udev_device, "ID_BUS")) && *str)
gst_structure_set (props, "device.bus", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_property (udev_device, "SUBSYSTEM")) && *str)
gst_structure_set (props, "device.subsystem", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_property (udev_device, "ID_VENDOR_ID")) && *str)
gst_structure_set (props, "device.vendor.id", G_TYPE_STRING, str, NULL);
str = g_udev_device_get_property (udev_device, "ID_VENDOR_FROM_DATABASE");
if (!(str && *str)) {
str = g_udev_device_get_property (udev_device, "ID_VENDOR_ENC");
if (!(str && *str)) {
str = g_udev_device_get_property (udev_device, "ID_VENDOR");
}
}
if (str && *str)
gst_structure_set (props, "device.vendor.name", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_property (udev_device, "ID_MODEL_ID")) && *str)
gst_structure_set (props, "device.product.id", G_TYPE_STRING, str, NULL);
device_name = g_udev_device_get_property (udev_device, "ID_V4L_PRODUCT");
if (!(device_name && *device_name)) {
device_name =
g_udev_device_get_property (udev_device, "ID_MODEL_FROM_DATABASE");
if (!(device_name && *device_name)) {
device_name = g_udev_device_get_property (udev_device, "ID_MODEL_ENC");
if (!(device_name && *device_name)) {
device_name = g_udev_device_get_property (udev_device, "ID_MODEL");
}
}
}
if (device_name && *device_name)
gst_structure_set (props, "device.product.name", G_TYPE_STRING, device_name,
NULL);
if ((str = g_udev_device_get_property (udev_device, "ID_SERIAL")) && *str)
gst_structure_set (props, "device.serial", G_TYPE_STRING, str, NULL);
if ((str = g_udev_device_get_property (udev_device, "ID_V4L_CAPABILITIES"))
&& *str)
gst_structure_set (props, "device.capabilities", G_TYPE_STRING, str, NULL);
gstdev = gst_v4l2_device_provider_probe_device (provider, device_path,
device_name, props);
if (gstdev)
gstdev->syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device));
return GST_DEVICE (gstdev);
}
static void
uevent_cb (GUdevClient * client, const gchar * action, GUdevDevice * device,
GstV4l2DeviceProvider * self)
{
GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self);
/* Not V4L2, ignoring */
if (g_udev_device_get_property_as_int (device, "ID_V4L_VERSION") != 2)
return;
if (!strcmp (action, "add")) {
GstDevice *gstdev = NULL;
gstdev = gst_v4l2_device_provider_device_from_udev (self, device);
if (gstdev)
gst_device_provider_device_add (provider, gstdev);
} else if (!strcmp (action, "remove")) {
GstV4l2Device *gstdev = NULL;
GList *item;
GST_OBJECT_LOCK (self);
for (item = provider->devices; item; item = item->next) {
gstdev = item->data;
if (!strcmp (gstdev->syspath, g_udev_device_get_sysfs_path (device))) {
gst_object_ref (gstdev);
break;
}
gstdev = NULL;
}
GST_OBJECT_UNLOCK (provider);
if (gstdev) {
gst_device_provider_device_remove (provider, GST_DEVICE (gstdev));
g_object_unref (gstdev);
}
} else {
GST_WARNING ("Unhandled action %s", action);
}
}
static gpointer
provider_thread (gpointer data)
{
GstV4l2DeviceProvider *provider = data;
GMainContext *context = NULL;
GMainLoop *loop = NULL;
GUdevClient *client;
GList *devices;
static const gchar *subsystems[] = { "video4linux", NULL };
GST_OBJECT_LOCK (provider);
if (provider->context)
context = g_main_context_ref (provider->context);
if (provider->loop)
loop = g_main_loop_ref (provider->loop);
if (context == NULL || loop == NULL) {
provider->started = TRUE;
g_cond_broadcast (&provider->started_cond);
g_clear_pointer (&loop, g_main_loop_unref);
g_clear_pointer (&context, g_main_context_unref);
GST_OBJECT_UNLOCK (provider);
return NULL;
}
GST_OBJECT_UNLOCK (provider);
g_main_context_push_thread_default (context);
client = g_udev_client_new (subsystems);
g_signal_connect (client, "uevent", G_CALLBACK (uevent_cb), provider);
devices = g_udev_client_query_by_subsystem (client, "video4linux");
while (devices) {
GUdevDevice *udev_device = devices->data;
GstDevice *gstdev;
devices = g_list_remove (devices, udev_device);
if (g_udev_device_get_property_as_int (udev_device, "ID_V4L_VERSION") == 2) {
gstdev =
gst_v4l2_device_provider_device_from_udev (provider, udev_device);
if (gstdev)
gst_device_provider_device_add (GST_DEVICE_PROVIDER (provider), gstdev);
}
g_object_unref (udev_device);
}
GST_OBJECT_LOCK (provider);
provider->started = TRUE;
g_cond_broadcast (&provider->started_cond);
GST_OBJECT_UNLOCK (provider);
g_main_loop_run (loop);
g_main_loop_unref (loop);
g_object_unref (client);
g_main_context_unref (context);
gst_object_unref (provider);
return NULL;
}
static gboolean
gst_v4l2_device_provider_start (GstDeviceProvider * provider)
{
GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
GST_OBJECT_LOCK (self);
g_assert (self->context == NULL);
self->context = g_main_context_new ();
self->loop = g_main_loop_new (self->context, FALSE);
self->thread = g_thread_new ("v4l2-device-provider", provider_thread,
g_object_ref (self));
while (self->started == FALSE)
g_cond_wait (&self->started_cond, GST_OBJECT_GET_LOCK (self));
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static void
gst_v4l2_device_provider_stop (GstDeviceProvider * provider)
{
GstV4l2DeviceProvider *self = GST_V4L2_DEVICE_PROVIDER (provider);
GMainContext *context;
GMainLoop *loop;
GSource *idle_stop_source;
GST_OBJECT_LOCK (self);
context = self->context;
loop = self->loop;
self->context = NULL;
self->loop = NULL;
GST_OBJECT_UNLOCK (self);
if (!context || !loop) {
g_clear_pointer (&self->loop, g_main_loop_unref);
g_clear_pointer (&self->context, g_main_context_unref);
return;
}
idle_stop_source = g_idle_source_new ();
g_source_set_callback (idle_stop_source, (GSourceFunc) g_main_loop_quit, loop,
NULL);
g_source_attach (idle_stop_source, context);
g_source_unref (idle_stop_source);
g_thread_join (self->thread);
self->thread = NULL;
self->started = FALSE;
g_main_loop_unref (loop);
g_main_context_unref (context);
}
#endif
enum
{
PROP_DEVICE_PATH = 1,
};
G_DEFINE_TYPE (GstV4l2Device, gst_v4l2_device, GST_TYPE_DEVICE);
static void gst_v4l2_device_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_v4l2_device_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_v4l2_device_finalize (GObject * object);
static GstElement *gst_v4l2_device_create_element (GstDevice * device,
const gchar * name);
static void
gst_v4l2_device_class_init (GstV4l2DeviceClass * klass)
{
GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
dev_class->create_element = gst_v4l2_device_create_element;
object_class->get_property = gst_v4l2_device_get_property;
object_class->set_property = gst_v4l2_device_set_property;
object_class->finalize = gst_v4l2_device_finalize;
g_object_class_install_property (object_class, PROP_DEVICE_PATH,
g_param_spec_string ("device-path", "Device Path",
"The Path of the device node", "",
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
static void
gst_v4l2_device_init (GstV4l2Device * device)
{
}
static void
gst_v4l2_device_finalize (GObject * object)
{
GstV4l2Device *device = GST_V4L2_DEVICE (object);
g_free (device->device_path);
g_free (device->syspath);
G_OBJECT_CLASS (gst_v4l2_device_parent_class)->finalize (object);
}
static GstElement *
gst_v4l2_device_create_element (GstDevice * device, const gchar * name)
{
GstV4l2Device *v4l2_dev = GST_V4L2_DEVICE (device);
GstElement *elem;
elem = gst_element_factory_make (v4l2_dev->element, name);
g_object_set (elem, "device", v4l2_dev->device_path, NULL);
return elem;
}
static GstV4l2Device *
gst_v4l2_device_new (const gchar * device_path, const gchar * device_name,
GstCaps * caps, GstV4l2DeviceType type, GstStructure * props)
{
GstV4l2Device *gstdev;
const gchar *element = NULL;
const gchar *klass = NULL;
g_return_val_if_fail (device_path, NULL);
g_return_val_if_fail (device_name, NULL);
g_return_val_if_fail (caps, NULL);
switch (type) {
case GST_V4L2_DEVICE_TYPE_SOURCE:
element = "v4l2src";
klass = "Video/Source";
break;
case GST_V4L2_DEVICE_TYPE_SINK:
element = "v4l2sink";
klass = "Video/Sink";
break;
default:
g_assert_not_reached ();
break;
}
gstdev = g_object_new (GST_TYPE_V4L2_DEVICE, "device-path", device_path,
"display-name", device_name, "caps", caps, "device-class", klass,
"properties", props, NULL);
gstdev->element = element;
return gstdev;
}
static void
gst_v4l2_device_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstV4l2Device *device;
device = GST_V4L2_DEVICE_CAST (object);
switch (prop_id) {
case PROP_DEVICE_PATH:
g_value_set_string (value, device->device_path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_v4l2_device_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstV4l2Device *device;
device = GST_V4L2_DEVICE_CAST (object);
switch (prop_id) {
case PROP_DEVICE_PATH:
device->device_path = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}