/* 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstv4l2codecdevice.h" #include "linux/media.h" #include #include #include #include #if HAVE_MAKEDEV_IN_MKDEV #include #elif HAVE_MAKEDEV_IN_SYSMACROS #include #elif HAVE_MAKEDEV_IN_TYPES #include #endif #include #define GST_CAT_DEFAULT gstv4l2codecs_debug GST_DEBUG_CATEGORY_EXTERN (gstv4l2codecs_debug); 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_TYPE_V4L2_CODEC_DEVICE, 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"); if (!udev_devices) GST_DEBUG ("Found no media devices"); 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; gboolean ret; fd = open (path, 0); if (fd < 0) continue; GST_DEBUG ("Analysing media device '%s'", path); ret = get_topology (fd, &topology); close (fd); if (!ret) { 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); }