From cfa58778992aef5ab229ab6a2421db6f86c0dd29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cr=C3=AAte?= Date: Fri, 2 Nov 2012 13:33:13 +0100 Subject: [PATCH] v4l2: Implement GstDeviceMonitor subclass https://bugzilla.gnome.org/show_bug.cgi?id=678402 --- configure.ac | 7 + sys/v4l2/Makefile.am | 2 + sys/v4l2/gstv4l2.c | 3 + sys/v4l2/gstv4l2devicemonitor.c | 509 ++++++++++++++++++++++++++++++++ sys/v4l2/gstv4l2devicemonitor.h | 101 +++++++ 5 files changed, 622 insertions(+) create mode 100644 sys/v4l2/gstv4l2devicemonitor.c create mode 100644 sys/v4l2/gstv4l2devicemonitor.h diff --git a/configure.ac b/configure.ac index c9d51b10d3..f05434c56f 100644 --- a/configure.ac +++ b/configure.ac @@ -518,6 +518,13 @@ AC_ARG_WITH([gudev], AS_HELP_STRING([--with-gudev],[device detection with gudev]), [], [with_gudev=check]) +if test x$with_gudev != xno; then + PKG_CHECK_MODULES(GUDEV, [ gudev-1.0 >= 147 ], + [ AC_DEFINE(HAVE_GUDEV, 1, + [Whether gudev is available for device detection]) + ]) +fi + AC_SUBST(GUDEV_CFLAGS) AC_SUBST(GUDEV_LIBS) diff --git a/sys/v4l2/Makefile.am b/sys/v4l2/Makefile.am index 4e62c6eb04..df3f980498 100644 --- a/sys/v4l2/Makefile.am +++ b/sys/v4l2/Makefile.am @@ -4,6 +4,7 @@ include $(top_srcdir)/common/gst-glib-gen.mak libgstvideo4linux2_la_SOURCES = gstv4l2.c \ gstv4l2colorbalance.c \ + gstv4l2devicemonitor.c \ gstv4l2object.c \ gstv4l2bufferpool.c \ gstv4l2sink.c \ @@ -38,6 +39,7 @@ libgstvideo4linux2_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) \ noinst_HEADERS = \ gstv4l2bufferpool.h \ gstv4l2colorbalance.h \ + gstv4l2devicemonitor.h \ gstv4l2object.h \ gstv4l2sink.h \ gstv4l2src.h \ diff --git a/sys/v4l2/gstv4l2.c b/sys/v4l2/gstv4l2.c index dd44069c09..a5361d7483 100644 --- a/sys/v4l2/gstv4l2.c +++ b/sys/v4l2/gstv4l2.c @@ -34,6 +34,7 @@ #include "gstv4l2sink.h" #include "gstv4l2radio.h" #include "gstv4l2videodec.h" +#include "gstv4l2devicemonitor.h" /* #include "gstv4l2jpegsrc.h" */ /* #include "gstv4l2mjpegsrc.h" */ /* #include "gstv4l2mjpegsink.h" */ @@ -53,6 +54,8 @@ plugin_init (GstPlugin * plugin) !gst_element_register (plugin, "v4l2radio", GST_RANK_NONE, GST_TYPE_V4L2RADIO) || !gst_v4l2_video_dec_register (plugin) || + !gst_device_monitor_register (plugin, "v4l2monitor", + GST_RANK_PRIMARY, GST_TYPE_V4L2_DEVICE_MONITOR) || /* !gst_element_register (plugin, "v4l2jpegsrc", */ /* GST_RANK_NONE, GST_TYPE_V4L2JPEGSRC) || */ /* !gst_element_register (plugin, "v4l2mjpegsrc", */ diff --git a/sys/v4l2/gstv4l2devicemonitor.c b/sys/v4l2/gstv4l2devicemonitor.c new file mode 100644 index 0000000000..db2f974f15 --- /dev/null +++ b/sys/v4l2/gstv4l2devicemonitor.c @@ -0,0 +1,509 @@ +/* GStreamer + * Copyright (C) 2012 Olivier Crete + * + * gstv4l2devicemonitor.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 "gstv4l2devicemonitor.h" + +#include +#include + +#include + +#include "gstv4l2object.h" +#include "v4l2_calls.h" + +#ifdef HAVE_GUDEV +#include +#endif + +static GstV4l2Device *gst_v4l2_device_new (const gchar * device_path, + const gchar * device_name, GstCaps * caps, GstV4l2DeviceType type); + + +G_DEFINE_TYPE (GstV4l2DeviceMonitor, gst_v4l2_device_monitor, + GST_TYPE_DEVICE_MONITOR); + +static void gst_v4l2_device_monitor_finalize (GObject * object); +static GList *gst_v4l2_device_monitor_probe (GstDeviceMonitor * monitor); + +#if HAVE_GUDEV +static gboolean gst_v4l2_device_monitor_start (GstDeviceMonitor * monitor); +static void gst_v4l2_device_monitor_stop (GstDeviceMonitor * monitor); +#endif + + +static void +gst_v4l2_device_monitor_class_init (GstV4l2DeviceMonitorClass * klass) +{ + GstDeviceMonitorClass *dm_class = GST_DEVICE_MONITOR_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + dm_class->probe = gst_v4l2_device_monitor_probe; + +#if HAVE_GUDEV + dm_class->start = gst_v4l2_device_monitor_start; + dm_class->stop = gst_v4l2_device_monitor_stop; +#endif + + gobject_class->finalize = gst_v4l2_device_monitor_finalize; + + gst_device_monitor_class_set_static_metadata (dm_class, + "Video (video4linux2) Device Monitor", "Source/Sink/Video", + "List and monitor video4linux2 source and sink devices", + "Olivier Crete "); +} + +static void +gst_v4l2_device_monitor_init (GstV4l2DeviceMonitor * monitor) +{ +#if HAVE_GUDEV + g_cond_init (&monitor->started_cond); +#endif +} + +static void +gst_v4l2_device_monitor_finalize (GObject * object) +{ +#if HAVE_GUDEV + GstV4l2DeviceMonitor *monitor = GST_V4L2_DEVICE_MONITOR (object); + + g_cond_clear (&monitor->started_cond); +#endif + + G_OBJECT_CLASS (gst_v4l2_device_monitor_parent_class)->finalize (object); +} + +static GstV4l2Device * +gst_v4l2_device_monitor_probe_device (GstV4l2DeviceMonitor * monitor, + const gchar * device_path, const gchar * device_name) +{ + GstV4l2Object *v4l2obj; + GstCaps *caps; + GstV4l2Device *device = NULL; + struct stat st; + GstV4l2DeviceType type = GST_V4L2_DEVICE_TYPE_INVALID; + + if (stat (device_path, &st) == -1) + return NULL; + + if (!S_ISCHR (st.st_mode)) + return NULL; + + v4l2obj = gst_v4l2_object_new ((GstElement *) monitor, + V4L2_BUF_TYPE_VIDEO_CAPTURE, device_path, NULL, NULL, NULL); + + if (!gst_v4l2_open (v4l2obj)) + goto destroy; + + + if (v4l2obj->vcap.capabilities & V4L2_CAP_VIDEO_CAPTURE) + type = GST_V4L2_DEVICE_TYPE_SOURCE; + + if (v4l2obj->vcap.capabilities & V4L2_CAP_VIDEO_OUTPUT) { + /* Morph it in case our initial guess was wrong */ + v4l2obj->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + if (type == GST_V4L2_DEVICE_TYPE_INVALID) + type = GST_V4L2_DEVICE_TYPE_SINK; + else + /* We ignore M2M devices that are both capture and output for now + * The monitor is not for them + */ + 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); + gst_caps_unref (caps); + +close: + + gst_v4l2_close (v4l2obj); + +destroy: + + gst_v4l2_object_destroy (v4l2obj); + + return device; +} + + +static GList * +gst_v4l2_device_monitor_probe (GstDeviceMonitor * monitor) +{ + GstV4l2DeviceMonitor *self = GST_V4L2_DEVICE_MONITOR (monitor); + GList *devices = NULL; + const gchar *dev_base[] = { "/dev/video", "/dev/v4l2/video", NULL }; + gint base, n; + + /* + * detect /dev entries + */ + for (n = 0; n < 64; n++) { + for (base = 0; dev_base[base] != NULL; base++) { + gchar *dev = g_strdup_printf ("%s%d", dev_base[base], n); + GstV4l2Device *device; + + device = gst_v4l2_device_monitor_probe_device (self, dev, NULL); + + if (device) { + gst_object_ref_sink (device); + devices = g_list_prepend (devices, device); + } + + g_free (dev); + } + } + + return devices; +} + +#if HAVE_GUDEV + +static GstDevice * +gst_v4l2_device_monitor_device_from_udev (GstV4l2DeviceMonitor * monitor, + GUdevDevice * udev_device) +{ + GstV4l2Device *gstdev; + const gchar *device_path = g_udev_device_get_device_file (udev_device); + const gchar *device_name; + + device_name = g_udev_device_get_property (udev_device, "ID_V4L_PRODUCT"); + if (!device_name) + device_name = g_udev_device_get_property (udev_device, "ID_MODEL_ENC"); + if (!device_name) + device_name = g_udev_device_get_property (udev_device, "ID_MODEL"); + + gstdev = gst_v4l2_device_monitor_probe_device (monitor, device_path, + device_name); + + 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, + GstV4l2DeviceMonitor * self) +{ + GstDeviceMonitor *monitor = GST_DEVICE_MONITOR (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_monitor_device_from_udev (self, device); + + if (gstdev) + gst_device_monitor_device_add (monitor, gstdev); + } else if (!strcmp (action, "remove")) { + GstV4l2Device *gstdev = NULL; + GList *item; + + GST_OBJECT_LOCK (self); + for (item = monitor->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 (monitor); + + if (gstdev) { + gst_device_monitor_device_remove (monitor, GST_DEVICE (gstdev)); + g_object_unref (gstdev); + } + } else { + GST_WARNING ("Unhandled action %s", action); + } +} + +static gpointer +monitor_thread (gpointer data) +{ + GstV4l2DeviceMonitor *monitor = data; + GMainContext *context = NULL; + GMainLoop *loop = NULL; + GUdevClient *client; + GList *devices; + static const gchar *subsystems[] = { "video4linux", NULL }; + + GST_OBJECT_LOCK (monitor); + if (monitor->context) + context = g_main_context_ref (monitor->context); + if (monitor->loop) + loop = g_main_loop_ref (monitor->loop); + + if (context == NULL || loop == NULL) { + monitor->started = TRUE; + g_cond_broadcast (&monitor->started_cond); + GST_OBJECT_UNLOCK (monitor); + return NULL; + } + GST_OBJECT_UNLOCK (monitor); + + g_main_context_push_thread_default (context); + + client = g_udev_client_new (subsystems); + + g_signal_connect (client, "uevent", G_CALLBACK (uevent_cb), monitor); + + 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_monitor_device_from_udev (monitor, udev_device); + if (gstdev) + gst_device_monitor_device_add (GST_DEVICE_MONITOR (monitor), gstdev); + } + + g_object_unref (udev_device); + } + + GST_OBJECT_LOCK (monitor); + monitor->started = TRUE; + g_cond_broadcast (&monitor->started_cond); + GST_OBJECT_UNLOCK (monitor); + + g_main_loop_run (loop); + g_main_loop_unref (loop); + + g_object_unref (client); + g_main_context_unref (context); + + gst_object_unref (monitor); + + return NULL; +} + +static gboolean +gst_v4l2_device_monitor_start (GstDeviceMonitor * monitor) +{ + GstV4l2DeviceMonitor *self = GST_V4L2_DEVICE_MONITOR (monitor); + + 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-monitor", monitor_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_monitor_stop (GstDeviceMonitor * monitor) +{ + GstV4l2DeviceMonitor *self = GST_V4L2_DEVICE_MONITOR (monitor); + 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) + return; + + idle_stop_source = g_idle_source_new (); + g_source_set_callback (idle_stop_source, (GSourceFunc) g_main_loop_quit, loop, + (GDestroyNotify) g_main_loop_unref); + g_source_attach (idle_stop_source, context); + g_source_unref (idle_stop_source); + g_main_context_unref (context); + + g_thread_join (self->thread); + g_thread_unref (self->thread); + self->thread = NULL; + self->started = FALSE; +} + +#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) +{ + GstV4l2Device *gstdev; + const gchar *element; + const gchar *klass; + + 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, "klass", klass, 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; + } +} diff --git a/sys/v4l2/gstv4l2devicemonitor.h b/sys/v4l2/gstv4l2devicemonitor.h new file mode 100644 index 0000000000..4e3050e44d --- /dev/null +++ b/sys/v4l2/gstv4l2devicemonitor.h @@ -0,0 +1,101 @@ +/* GStreamer + * Copyright (C) 2012 Olivier Crete + * + * gstv4l2devicemonitor.h: 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. + */ + + +#ifndef __GST_V4L2_DEVICE_MONITOR_H__ +#define __GST_V4L2_DEVICE_MONITOR_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#ifdef HAVE_GUDEV +#include +#endif + +G_BEGIN_DECLS + +typedef struct _GstV4l2DeviceMonitor GstV4l2DeviceMonitor; +typedef struct _GstV4l2DeviceMonitorPrivate GstV4l2DeviceMonitorPrivate; +typedef struct _GstV4l2DeviceMonitorClass GstV4l2DeviceMonitorClass; + +#define GST_TYPE_V4L2_DEVICE_MONITOR (gst_v4l2_device_monitor_get_type()) +#define GST_IS_V4L2_DEVICE_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_V4L2_DEVICE_MONITOR)) +#define GST_IS_V4L2_DEVICE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_V4L2_DEVICE_MONITOR)) +#define GST_V4L2_DEVICE_MONITOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_V4L2_DEVICE_MONITOR, GstV4l2DeviceMonitorClass)) +#define GST_V4L2_DEVICE_MONITOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_V4L2_DEVICE_MONITOR, GstV4l2DeviceMonitor)) +#define GST_V4L2_DEVICE_MONITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_MONITOR, GstV4l2DeviceMonitorClass)) +#define GST_V4L2_DEVICE_MONITOR_CAST(obj) ((GstV4l2DeviceMonitor *)(obj)) + +struct _GstV4l2DeviceMonitor { + GstDeviceMonitor parent; + +#ifdef HAVE_GUDEV + GMainContext *context; + GMainLoop *loop; + GThread *thread; + gboolean started; + GCond started_cond; +#endif +}; + +typedef enum { + GST_V4L2_DEVICE_TYPE_INVALID = 0, + GST_V4L2_DEVICE_TYPE_SOURCE, + GST_V4L2_DEVICE_TYPE_SINK +} GstV4l2DeviceType; + +struct _GstV4l2DeviceMonitorClass { + GstDeviceMonitorClass parent_class; +}; + +GType gst_v4l2_device_monitor_get_type (void); + + +typedef struct _GstV4l2Device GstV4l2Device; +typedef struct _GstV4l2DevicePrivate GstV4l2DevicePrivate; +typedef struct _GstV4l2DeviceClass GstV4l2DeviceClass; + +#define GST_TYPE_V4L2_DEVICE (gst_v4l2_device_get_type()) +#define GST_IS_V4L2_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_V4L2_DEVICE)) +#define GST_IS_V4L2_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_V4L2_DEVICE)) +#define GST_V4L2_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_V4L2_DEVICE, GstV4l2DeviceClass)) +#define GST_V4L2_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_V4L2_DEVICE, GstV4l2Device)) +#define GST_V4L2_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstV4l2DeviceClass)) +#define GST_V4L2_DEVICE_CAST(obj) ((GstV4l2Device *)(obj)) + +struct _GstV4l2Device { + GstDevice parent; + + gchar *device_path; + gchar *syspath; + const gchar *element; +}; + +struct _GstV4l2DeviceClass { + GstDeviceClass parent_class; +}; + +GType gst_v4l2_device_get_type (void); + +#endif /* __GST_V4L2_DEVICE_MONITOR_H__ */