diff options
Diffstat (limited to 'sys/v4l2/gstv4l2deviceprovider.c')
-rw-r--r-- | sys/v4l2/gstv4l2deviceprovider.c | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/sys/v4l2/gstv4l2deviceprovider.c b/sys/v4l2/gstv4l2deviceprovider.c new file mode 100644 index 00000000..e49e7e59 --- /dev/null +++ b/sys/v4l2/gstv4l2deviceprovider.c @@ -0,0 +1,507 @@ +/* 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_calls.h" +#include "v4l2-utils.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); + + +G_DEFINE_TYPE (GstV4l2DeviceProvider, gst_v4l2_device_provider, + GST_TYPE_DEVICE_PROVIDER); + +static void gst_v4l2_device_provider_finalize (GObject * object); +static GList *gst_v4l2_device_provider_probe (GstDeviceProvider * provider); + +#if 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; + +#if 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) +{ +#if HAVE_GUDEV + g_cond_init (&provider->started_cond); +#endif +} + +static void +gst_v4l2_device_provider_finalize (GObject * object) +{ +#if 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) +{ + 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 *) provider, + 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 provider 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_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)) { + GstV4l2Device *device; + + device = + gst_v4l2_device_provider_probe_device (self, it->device_path, NULL); + + if (device) { + gst_object_ref_sink (device); + devices = g_list_prepend (devices, device); + } + } + + gst_v4l2_iterator_free (it); + + return devices; +} + +#if 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; + + 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_provider_probe_device (provider, 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, + 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); + 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) + 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, "device-class", 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; + } +} |