From 6d9c98ae5ae7f80dba03885f2959ae5060834407 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Tue, 4 Feb 2020 15:52:45 -0500 Subject: [PATCH] v4l2codecs: Add device enumeration This introduces a GstV4L2CodecDevice structure and helper to retrieve a list of CODEC device drivers. In order to find the device driver we enumerate all media devices with UDEV. We then get the media controller topology and locate a entity with function encoder or decoder and make sure it is linked to two V4L2 IO entity pointing to the same device node. --- meson_options.txt | 1 + sys/v4l2codecs/gstv4l2codecdevice.c | 396 ++++++++++++++++++++++++++++ sys/v4l2codecs/gstv4l2codecdevice.h | 43 +++ sys/v4l2codecs/meson.build | 27 +- sys/v4l2codecs/plugin.c | 5 + 5 files changed, 461 insertions(+), 11 deletions(-) create mode 100644 sys/v4l2codecs/gstv4l2codecdevice.c create mode 100644 sys/v4l2codecs/gstv4l2codecdevice.h diff --git a/meson_options.txt b/meson_options.txt index 5e48b8c4a5..50de262ebb 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -165,6 +165,7 @@ option('zbar', type : 'feature', value : 'auto', description : 'Barcode image sc option('zxing', type : 'feature', value : 'auto', description : 'Barcode image scanner plugin using zxing-cpp library') option('wpe', type : 'feature', value : 'auto', description : 'WPE Web browser plugin') option('magicleap', type : 'feature', value : 'auto', description : 'Magic Leap platform support') +option('v4l2codecs', type : 'feature', value : 'auto', description : 'Video4Linux Stateless CODECs support') # HLS plugin options option('hls', type : 'feature', value : 'auto', description : 'HTTP Live Streaming plugin') diff --git a/sys/v4l2codecs/gstv4l2codecdevice.c b/sys/v4l2codecs/gstv4l2codecdevice.c new file mode 100644 index 0000000000..95bf71713a --- /dev/null +++ b/sys/v4l2codecs/gstv4l2codecdevice.c @@ -0,0 +1,396 @@ +/* GStreamer + * Copyright (C) 2020 Collabora Ltd. + * Author: Nicolas Dufresne + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "gstv4l2codecdevice.h" +#include "linux/media.h" + +#include +#include +#include +#include +#include + +#define GST_CAT_DEFAULT gstv4l2codecs_debug +GST_DEBUG_CATEGORY_EXTERN (gstv4l2codecs_debug); + +GType _gst_v4l2_codec_device_type = 0; + +GST_DEFINE_MINI_OBJECT_TYPE (GstV4l2CodecDevice, gst_v4l2_codec_device); + +static void +gst_v4l2_codec_device_free (GstV4l2CodecDevice * device) +{ + g_free (device->name); + g_free (device->media_device_path); + g_free (device->video_device_path); + g_free (device); +} + +static GstV4l2CodecDevice * +gst_v4l2_codec_device_new (const gchar * name, guint32 function, + const gchar * media_device_path, const gchar * video_device_path) +{ + GstV4l2CodecDevice *device = g_new0 (GstV4l2CodecDevice, 1); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (device), + 0, _gst_v4l2_codec_device_type, NULL, NULL, + (GstMiniObjectFreeFunction) gst_v4l2_codec_device_free); + + device->name = g_strdup (name); + device->function = function; + device->media_device_path = g_strdup (media_device_path); + device->video_device_path = g_strdup (video_device_path); + + return device; +} + +static void +clear_topology (struct media_v2_topology *topology) +{ + g_free ((gpointer) (gsize) topology->ptr_entities); + g_free ((gpointer) (gsize) topology->ptr_interfaces); + g_free ((gpointer) (gsize) topology->ptr_pads); + g_free ((gpointer) (gsize) topology->ptr_links); + memset (topology, 0, sizeof (struct media_v2_topology)); +} + +static gboolean +get_topology (gint fd, struct media_v2_topology *topology) +{ + gint ret; + guint64 version; + +again: + memset (topology, 0, sizeof (struct media_v2_topology)); + + ret = ioctl (fd, MEDIA_IOC_G_TOPOLOGY, topology); + if (ret < 0) { + GST_WARNING ("Could not retrieve topology: %s", g_strerror (errno)); + return FALSE; + } + + version = topology->topology_version; + topology->ptr_entities = (guint64) (gsize) + g_new0 (struct media_v2_entity, topology->num_entities); + topology->ptr_interfaces = (guint64) (gsize) + g_new0 (struct media_v2_interface, topology->num_interfaces); + topology->ptr_pads = (guint64) (gsize) + g_new0 (struct media_v2_pad, topology->num_pads); + topology->ptr_links = (guint64) (gsize) + g_new0 (struct media_v2_link, topology->num_links); + + ret = ioctl (fd, MEDIA_IOC_G_TOPOLOGY, topology); + if (ret < 0) { + GST_WARNING ("Could not retrieve topology: %s", g_strerror (errno)); + clear_topology (topology); + return FALSE; + } + + /* If the topology have changed, just retry */ + if (version != topology->topology_version) { + clear_topology (topology); + goto again; + } + + return TRUE; +} + +static struct media_v2_entity * +find_v4l_entity (struct media_v2_topology *topology, guint32 id) +{ + gint i; + + for (i = 0; i < topology->num_entities; i++) { + struct media_v2_entity *entity = + ((struct media_v2_entity *) (gsize) topology->ptr_entities) + i; + if (entity->function != MEDIA_ENT_F_IO_V4L) + continue; + if (entity->id == id) + return entity; + } + + return NULL; +} + +static struct media_v2_pad * +find_pad (struct media_v2_topology *topology, guint32 id) +{ + gint i; + + for (i = 0; i < topology->num_pads; i++) { + struct media_v2_pad *pad = + ((struct media_v2_pad *) (gsize) topology->ptr_pads) + i; + if (pad->id == id) + return pad; + } + + return NULL; +} + +static GList * +find_codec_entity (struct media_v2_topology *topology) +{ + GQueue entities = G_QUEUE_INIT; + gint i; + + for (i = 0; i < topology->num_entities; i++) { + struct media_v2_entity *entity = + ((struct media_v2_entity *) (gsize) topology->ptr_entities) + i; + + switch (entity->function) { + case MEDIA_ENT_F_PROC_VIDEO_ENCODER: + case MEDIA_ENT_F_PROC_VIDEO_DECODER: + g_queue_push_tail (&entities, entity); + break; + default: + break; + } + } + + return entities.head; +} + +static gboolean +find_codec_entity_pads (struct media_v2_topology *topology, + struct media_v2_entity *entity, struct media_v2_pad **sink_pad, + struct media_v2_pad **source_pad) +{ + gint i; + + *sink_pad = NULL; + *source_pad = NULL; + + for (i = 0; i < topology->num_pads; i++) { + struct media_v2_pad *pad = + ((struct media_v2_pad *) (gsize) topology->ptr_pads) + i; + + if (pad->entity_id != entity->id) + continue; + + if (pad->flags & MEDIA_PAD_FL_SINK) { + if (*sink_pad) + return FALSE; + *sink_pad = pad; + } else if (pad->flags & MEDIA_PAD_FL_SOURCE) { + if (*source_pad) + return FALSE; + *source_pad = pad; + } else { + /* unknown pad type */ + return FALSE; + } + } + + return (*source_pad && *sink_pad); +} + +static struct media_v2_entity * +find_peer_v4l_entity (struct media_v2_topology *topology, + struct media_v2_pad *pad) +{ + struct media_v2_pad *peer_pad = NULL; + gint i; + + for (i = 0; i < topology->num_links; i++) { + struct media_v2_link *link = + ((struct media_v2_link *) (gsize) topology->ptr_links) + i; + + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK) + continue; + + if ((link->flags & (MEDIA_LNK_FL_IMMUTABLE)) == 0) + continue; + + if ((link->flags & (MEDIA_LNK_FL_ENABLED)) == 0) + continue; + + if (pad->flags & MEDIA_PAD_FL_SINK && link->sink_id == pad->id) + peer_pad = find_pad (topology, link->source_id); + else if (pad->flags & MEDIA_PAD_FL_SOURCE && link->source_id == pad->id) + peer_pad = find_pad (topology, link->sink_id); + + if (peer_pad) + break; + } + + if (peer_pad) + return find_v4l_entity (topology, peer_pad->entity_id); + + return NULL; +} + + +static struct media_v2_interface * +find_video_interface (struct media_v2_topology *topology, guint32 id) +{ + gint i; + + for (i = 0; i < topology->num_interfaces; i++) { + struct media_v2_interface *intf = + ((struct media_v2_interface *) (gsize) topology->ptr_interfaces) + i; + + if (intf->intf_type != MEDIA_INTF_T_V4L_VIDEO) + continue; + + if (intf->id == id) + return intf; + } + + return NULL; +} + +static struct media_v2_intf_devnode * +find_video_devnode (struct media_v2_topology *topology, + struct media_v2_entity *entity) +{ + gint i; + + for (i = 0; i < topology->num_links; i++) { + struct media_v2_link *link = + ((struct media_v2_link *) (gsize) topology->ptr_links) + i; + struct media_v2_interface *intf; + + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_INTERFACE_LINK) + continue; + + if (link->sink_id != entity->id) + continue; + + intf = find_video_interface (topology, link->source_id); + if (intf) + return &intf->devnode; + } + + return NULL; +} + +static inline const gchar * +function_to_string (guint32 function) +{ + switch (function) { + case MEDIA_ENT_F_PROC_VIDEO_ENCODER: + return "encoder"; + case MEDIA_ENT_F_PROC_VIDEO_DECODER: + return "decoder"; + default: + break; + } + + return "unknown"; +} + +GList * +gst_v4l2_codec_find_devices (void) +{ + GUdevClient *client; + GList *udev_devices, *d; + GQueue devices = G_QUEUE_INIT; + + client = g_udev_client_new (NULL); + udev_devices = g_udev_client_query_by_subsystem (client, "media"); + + for (d = udev_devices; d; d = g_list_next (d)) { + GUdevDevice *udev = (GUdevDevice *) d->data; + const gchar *path = g_udev_device_get_device_file (udev); + gint fd; + struct media_v2_topology topology; + GList *codec_entities, *c; + + fd = open (path, 0); + if (fd < 0) + continue; + + GST_DEBUG ("Analysing media device '%s'", path); + + if (!get_topology (fd, &topology)) { + clear_topology (&topology); + continue; + } + + codec_entities = find_codec_entity (&topology); + if (!codec_entities) { + clear_topology (&topology); + continue; + } + + GST_DEBUG ("Found CODEC entities"); + + for (c = codec_entities; c; c = g_list_next (c)) { + struct media_v2_entity *entity = (struct media_v2_entity *) c->data; + struct media_v2_pad *source_pad; + struct media_v2_pad *sink_pad; + struct media_v2_entity *source_entity, *sink_entity; + struct media_v2_intf_devnode *source_dev, *sink_dev; + GUdevDevice *v4l2_udev; + + GST_DEBUG ("Analysing entity %s", entity->name); + + if (!find_codec_entity_pads (&topology, entity, &sink_pad, &source_pad)) + continue; + + GST_DEBUG ("Found source and sink pads"); + + source_entity = find_peer_v4l_entity (&topology, sink_pad); + sink_entity = find_peer_v4l_entity (&topology, source_pad); + + if (!source_entity || !sink_entity) + continue; + + GST_DEBUG ("Found source and sink V4L IO entities"); + + source_dev = find_video_devnode (&topology, source_entity); + sink_dev = find_video_devnode (&topology, source_entity); + + if (!source_dev || !sink_dev) + continue; + + if (source_dev->major != sink_dev->major + || source_dev->minor != sink_dev->minor) + continue; + + v4l2_udev = g_udev_client_query_by_device_number (client, + G_UDEV_DEVICE_TYPE_CHAR, + makedev (source_dev->major, source_dev->minor)); + if (!v4l2_udev) + continue; + + GST_INFO ("Found %s device %s", function_to_string (entity->function), + entity->name); + g_queue_push_tail (&devices, + gst_v4l2_codec_device_new (entity->name, entity->function, path, + g_udev_device_get_device_file (v4l2_udev))); + g_object_unref (v4l2_udev); + } + + clear_topology (&topology); + g_list_free (codec_entities); + } + + g_list_free_full (udev_devices, g_object_unref); + g_object_unref (client); + + return devices.head; +} + +void +gst_v4l2_codec_device_list_free (GList * devices) +{ + g_list_free_full (devices, (GDestroyNotify) gst_mini_object_unref); +} diff --git a/sys/v4l2codecs/gstv4l2codecdevice.h b/sys/v4l2codecs/gstv4l2codecdevice.h new file mode 100644 index 0000000000..65f4a8f023 --- /dev/null +++ b/sys/v4l2codecs/gstv4l2codecdevice.h @@ -0,0 +1,43 @@ +/* GStreamer + * Copyright (C) 2020 Collabora Ltd. + * Author: Nicolas Dufresne + * + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _GST_V4L2_CODEC_DEVICE_H_ +#define _GST_V4L2_CODEC_DEVICE_H_ + +#include + +#define GST_TYPE_V4L2_CODEC_DEVICE (_gst_v4l2_codec_device_type) +#define GST_IS_V4L2_CODEC_DEVICE(obj) (GST_IS_MINI_OBJECT_TYPE(obj, GST_TYPE_V4L2_CODEC_DEVICE)) +#define GST_V4L2_CODEC_DEVICE(obj) ((GstV4l2CodecDevice *)(obj)) + +typedef struct { + GstMiniObject mini_object; + + gchar *name; + guint32 function; + gchar *media_device_path; + gchar *video_device_path; +} GstV4l2CodecDevice; + +GType gst_v4l2_codec_device_get_type (void); +GList *gst_v4l2_codec_find_devices (void); +void gst_v4l2_codec_device_list_free (GList *devices); + +#endif /* _GST_V4L2_CODECS_DEVICE_H_ */ diff --git a/sys/v4l2codecs/meson.build b/sys/v4l2codecs/meson.build index 7c5afcd48a..dcc9f38bf6 100644 --- a/sys/v4l2codecs/meson.build +++ b/sys/v4l2codecs/meson.build @@ -1,15 +1,20 @@ v4l2codecs_sources = [ 'plugin.c', + 'gstv4l2codecdevice.c', ] -gstv4l2codecs = library('gstv4l2codecs', - v4l2codecs_sources, - c_args : gst_plugins_bad_args, - cpp_args: gst_plugins_bad_args, - include_directories : [configinc], - dependencies : [gstbase_dep, gstcodecs_dep], - install : true, - install_dir : plugins_install_dir, -) -pkgconfig.generate(gstv4l2codecs, install_dir : plugins_pkgconfig_install_dir) -plugins += [gstv4l2codecs] +libgudev_dep = dependency('gudev-1.0', required: get_option('v4l2codecs')) + +if libgudev_dep.found() + gstv4l2codecs = library('gstv4l2codecs', + v4l2codecs_sources, + c_args : gst_plugins_bad_args, + cpp_args: gst_plugins_bad_args, + include_directories : [configinc], + dependencies : [gstbase_dep, gstcodecs_dep, libgudev_dep], + install : true, + install_dir : plugins_install_dir, + ) + pkgconfig.generate(gstv4l2codecs, install_dir : plugins_pkgconfig_install_dir) + plugins += [gstv4l2codecs] +endif diff --git a/sys/v4l2codecs/plugin.c b/sys/v4l2codecs/plugin.c index 8db87ef440..623b117e8b 100644 --- a/sys/v4l2codecs/plugin.c +++ b/sys/v4l2codecs/plugin.c @@ -24,9 +24,14 @@ #include +#define GST_CAT_DEFAULT gstv4l2codecs_debug +GST_DEBUG_CATEGORY (gstv4l2codecs_debug); + static gboolean plugin_init (GstPlugin * plugin) { + GST_DEBUG_CATEGORY_INIT (gstv4l2codecs_debug, "v4l2codecs", 0, + "V4L2 CODECs general debug"); return TRUE; }