• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2020 Collabora Ltd.
3  *   Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "gstv4l2codecdevice.h"
26 #include "linux/media.h"
27 
28 #include <fcntl.h>
29 #include <gudev/gudev.h>
30 #include <sys/ioctl.h>
31 #include <sys/stat.h>
32 
33 #if HAVE_MAKEDEV_IN_MKDEV
34 #include <sys/mkdev.h>
35 #elif HAVE_MAKEDEV_IN_SYSMACROS
36 #include <sys/sysmacros.h>
37 #elif HAVE_MAKEDEV_IN_TYPES
38 #include <sys/types.h>
39 #endif
40 #include <unistd.h>
41 
42 #define GST_CAT_DEFAULT gstv4l2codecs_debug
43 GST_DEBUG_CATEGORY_EXTERN (gstv4l2codecs_debug);
44 
45 GST_DEFINE_MINI_OBJECT_TYPE (GstV4l2CodecDevice, gst_v4l2_codec_device);
46 
47 static void
gst_v4l2_codec_device_free(GstV4l2CodecDevice * device)48 gst_v4l2_codec_device_free (GstV4l2CodecDevice * device)
49 {
50   g_free (device->name);
51   g_free (device->media_device_path);
52   g_free (device->video_device_path);
53   g_free (device);
54 }
55 
56 static GstV4l2CodecDevice *
gst_v4l2_codec_device_new(const gchar * name,guint32 function,const gchar * media_device_path,const gchar * video_device_path)57 gst_v4l2_codec_device_new (const gchar * name, guint32 function,
58     const gchar * media_device_path, const gchar * video_device_path)
59 {
60   GstV4l2CodecDevice *device = g_new0 (GstV4l2CodecDevice, 1);
61 
62   gst_mini_object_init (GST_MINI_OBJECT_CAST (device),
63       0, GST_TYPE_V4L2_CODEC_DEVICE, NULL, NULL,
64       (GstMiniObjectFreeFunction) gst_v4l2_codec_device_free);
65 
66   device->name = g_strdup (name);
67   device->function = function;
68   device->media_device_path = g_strdup (media_device_path);
69   device->video_device_path = g_strdup (video_device_path);
70 
71   return device;
72 }
73 
74 static void
clear_topology(struct media_v2_topology * topology)75 clear_topology (struct media_v2_topology *topology)
76 {
77   g_free ((gpointer) (gsize) topology->ptr_entities);
78   g_free ((gpointer) (gsize) topology->ptr_interfaces);
79   g_free ((gpointer) (gsize) topology->ptr_pads);
80   g_free ((gpointer) (gsize) topology->ptr_links);
81   memset (topology, 0, sizeof (struct media_v2_topology));
82 }
83 
84 static gboolean
get_topology(gint fd,struct media_v2_topology * topology)85 get_topology (gint fd, struct media_v2_topology *topology)
86 {
87   gint ret;
88   guint64 version;
89 
90 again:
91   memset (topology, 0, sizeof (struct media_v2_topology));
92 
93   ret = ioctl (fd, MEDIA_IOC_G_TOPOLOGY, topology);
94   if (ret < 0) {
95     GST_WARNING ("Could not retrieve topology: %s", g_strerror (errno));
96     return FALSE;
97   }
98 
99   version = topology->topology_version;
100   topology->ptr_entities = (guint64) (gsize)
101       g_new0 (struct media_v2_entity, topology->num_entities);
102   topology->ptr_interfaces = (guint64) (gsize)
103       g_new0 (struct media_v2_interface, topology->num_interfaces);
104   topology->ptr_pads = (guint64) (gsize)
105       g_new0 (struct media_v2_pad, topology->num_pads);
106   topology->ptr_links = (guint64) (gsize)
107       g_new0 (struct media_v2_link, topology->num_links);
108 
109   ret = ioctl (fd, MEDIA_IOC_G_TOPOLOGY, topology);
110   if (ret < 0) {
111     GST_WARNING ("Could not retrieve topology: %s", g_strerror (errno));
112     clear_topology (topology);
113     return FALSE;
114   }
115 
116   /* If the topology have changed, just retry */
117   if (version != topology->topology_version) {
118     clear_topology (topology);
119     goto again;
120   }
121 
122   return TRUE;
123 }
124 
125 static struct media_v2_entity *
find_v4l_entity(struct media_v2_topology * topology,guint32 id)126 find_v4l_entity (struct media_v2_topology *topology, guint32 id)
127 {
128   gint i;
129 
130   for (i = 0; i < topology->num_entities; i++) {
131     struct media_v2_entity *entity =
132         ((struct media_v2_entity *) (gsize) topology->ptr_entities) + i;
133     if (entity->function != MEDIA_ENT_F_IO_V4L)
134       continue;
135     if (entity->id == id)
136       return entity;
137   }
138 
139   return NULL;
140 }
141 
142 static struct media_v2_pad *
find_pad(struct media_v2_topology * topology,guint32 id)143 find_pad (struct media_v2_topology *topology, guint32 id)
144 {
145   gint i;
146 
147   for (i = 0; i < topology->num_pads; i++) {
148     struct media_v2_pad *pad =
149         ((struct media_v2_pad *) (gsize) topology->ptr_pads) + i;
150     if (pad->id == id)
151       return pad;
152   }
153 
154   return NULL;
155 }
156 
157 static GList *
find_codec_entity(struct media_v2_topology * topology)158 find_codec_entity (struct media_v2_topology *topology)
159 {
160   GQueue entities = G_QUEUE_INIT;
161   gint i;
162 
163   for (i = 0; i < topology->num_entities; i++) {
164     struct media_v2_entity *entity =
165         ((struct media_v2_entity *) (gsize) topology->ptr_entities) + i;
166 
167     switch (entity->function) {
168       case MEDIA_ENT_F_PROC_VIDEO_ENCODER:
169       case MEDIA_ENT_F_PROC_VIDEO_DECODER:
170         g_queue_push_tail (&entities, entity);
171         break;
172       default:
173         break;
174     }
175   }
176 
177   return entities.head;
178 }
179 
180 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)181 find_codec_entity_pads (struct media_v2_topology *topology,
182     struct media_v2_entity *entity, struct media_v2_pad **sink_pad,
183     struct media_v2_pad **source_pad)
184 {
185   gint i;
186 
187   *sink_pad = NULL;
188   *source_pad = NULL;
189 
190   for (i = 0; i < topology->num_pads; i++) {
191     struct media_v2_pad *pad =
192         ((struct media_v2_pad *) (gsize) topology->ptr_pads) + i;
193 
194     if (pad->entity_id != entity->id)
195       continue;
196 
197     if (pad->flags & MEDIA_PAD_FL_SINK) {
198       if (*sink_pad)
199         return FALSE;
200       *sink_pad = pad;
201     } else if (pad->flags & MEDIA_PAD_FL_SOURCE) {
202       if (*source_pad)
203         return FALSE;
204       *source_pad = pad;
205     } else {
206       /* unknown pad type */
207       return FALSE;
208     }
209   }
210 
211   return (*source_pad && *sink_pad);
212 }
213 
214 static struct media_v2_entity *
find_peer_v4l_entity(struct media_v2_topology * topology,struct media_v2_pad * pad)215 find_peer_v4l_entity (struct media_v2_topology *topology,
216     struct media_v2_pad *pad)
217 {
218   struct media_v2_pad *peer_pad = NULL;
219   gint i;
220 
221   for (i = 0; i < topology->num_links; i++) {
222     struct media_v2_link *link =
223         ((struct media_v2_link *) (gsize) topology->ptr_links) + i;
224 
225     if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK)
226       continue;
227 
228     if ((link->flags & (MEDIA_LNK_FL_IMMUTABLE)) == 0)
229       continue;
230 
231     if ((link->flags & (MEDIA_LNK_FL_ENABLED)) == 0)
232       continue;
233 
234     if (pad->flags & MEDIA_PAD_FL_SINK && link->sink_id == pad->id)
235       peer_pad = find_pad (topology, link->source_id);
236     else if (pad->flags & MEDIA_PAD_FL_SOURCE && link->source_id == pad->id)
237       peer_pad = find_pad (topology, link->sink_id);
238 
239     if (peer_pad)
240       break;
241   }
242 
243   if (peer_pad)
244     return find_v4l_entity (topology, peer_pad->entity_id);
245 
246   return NULL;
247 }
248 
249 
250 static struct media_v2_interface *
find_video_interface(struct media_v2_topology * topology,guint32 id)251 find_video_interface (struct media_v2_topology *topology, guint32 id)
252 {
253   gint i;
254 
255   for (i = 0; i < topology->num_interfaces; i++) {
256     struct media_v2_interface *intf =
257         ((struct media_v2_interface *) (gsize) topology->ptr_interfaces) + i;
258 
259     if (intf->intf_type != MEDIA_INTF_T_V4L_VIDEO)
260       continue;
261 
262     if (intf->id == id)
263       return intf;
264   }
265 
266   return NULL;
267 }
268 
269 static struct media_v2_intf_devnode *
find_video_devnode(struct media_v2_topology * topology,struct media_v2_entity * entity)270 find_video_devnode (struct media_v2_topology *topology,
271     struct media_v2_entity *entity)
272 {
273   gint i;
274 
275   for (i = 0; i < topology->num_links; i++) {
276     struct media_v2_link *link =
277         ((struct media_v2_link *) (gsize) topology->ptr_links) + i;
278     struct media_v2_interface *intf;
279 
280     if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_INTERFACE_LINK)
281       continue;
282 
283     if (link->sink_id != entity->id)
284       continue;
285 
286     intf = find_video_interface (topology, link->source_id);
287     if (intf)
288       return &intf->devnode;
289   }
290 
291   return NULL;
292 }
293 
294 static inline const gchar *
function_to_string(guint32 function)295 function_to_string (guint32 function)
296 {
297   switch (function) {
298     case MEDIA_ENT_F_PROC_VIDEO_ENCODER:
299       return "encoder";
300     case MEDIA_ENT_F_PROC_VIDEO_DECODER:
301       return "decoder";
302     default:
303       break;
304   }
305 
306   return "unknown";
307 }
308 
309 GList *
gst_v4l2_codec_find_devices(void)310 gst_v4l2_codec_find_devices (void)
311 {
312   GUdevClient *client;
313   GList *udev_devices, *d;
314   GQueue devices = G_QUEUE_INIT;
315 
316   client = g_udev_client_new (NULL);
317   udev_devices = g_udev_client_query_by_subsystem (client, "media");
318 
319   for (d = udev_devices; d; d = g_list_next (d)) {
320     GUdevDevice *udev = (GUdevDevice *) d->data;
321     const gchar *path = g_udev_device_get_device_file (udev);
322     gint fd;
323     struct media_v2_topology topology;
324     GList *codec_entities, *c;
325     gboolean ret;
326 
327     fd = open (path, 0);
328     if (fd < 0)
329       continue;
330 
331     GST_DEBUG ("Analysing media device '%s'", path);
332 
333     ret = get_topology (fd, &topology);
334     close (fd);
335 
336     if (!ret) {
337       clear_topology (&topology);
338       continue;
339     }
340 
341     codec_entities = find_codec_entity (&topology);
342     if (!codec_entities) {
343       clear_topology (&topology);
344       continue;
345     }
346 
347     GST_DEBUG ("Found CODEC entities");
348 
349     for (c = codec_entities; c; c = g_list_next (c)) {
350       struct media_v2_entity *entity = (struct media_v2_entity *) c->data;
351       struct media_v2_pad *source_pad;
352       struct media_v2_pad *sink_pad;
353       struct media_v2_entity *source_entity, *sink_entity;
354       struct media_v2_intf_devnode *source_dev, *sink_dev;
355       GUdevDevice *v4l2_udev;
356 
357       GST_DEBUG ("Analysing entity %s", entity->name);
358 
359       if (!find_codec_entity_pads (&topology, entity, &sink_pad, &source_pad))
360         continue;
361 
362       GST_DEBUG ("Found source and sink pads");
363 
364       source_entity = find_peer_v4l_entity (&topology, sink_pad);
365       sink_entity = find_peer_v4l_entity (&topology, source_pad);
366 
367       if (!source_entity || !sink_entity)
368         continue;
369 
370       GST_DEBUG ("Found source and sink V4L IO entities");
371 
372       source_dev = find_video_devnode (&topology, source_entity);
373       sink_dev = find_video_devnode (&topology, source_entity);
374 
375       if (!source_dev || !sink_dev)
376         continue;
377 
378       if (source_dev->major != sink_dev->major
379           || source_dev->minor != sink_dev->minor)
380         continue;
381 
382       v4l2_udev = g_udev_client_query_by_device_number (client,
383           G_UDEV_DEVICE_TYPE_CHAR,
384           makedev (source_dev->major, source_dev->minor));
385       if (!v4l2_udev)
386         continue;
387 
388       GST_INFO ("Found %s device %s", function_to_string (entity->function),
389           entity->name);
390       g_queue_push_tail (&devices,
391           gst_v4l2_codec_device_new (entity->name, entity->function, path,
392               g_udev_device_get_device_file (v4l2_udev)));
393       g_object_unref (v4l2_udev);
394     }
395 
396     clear_topology (&topology);
397     g_list_free (codec_entities);
398   }
399 
400   g_list_free_full (udev_devices, g_object_unref);
401   g_object_unref (client);
402 
403   return devices.head;
404 }
405 
406 void
gst_v4l2_codec_device_list_free(GList * devices)407 gst_v4l2_codec_device_list_free (GList * devices)
408 {
409   g_list_free_full (devices, (GDestroyNotify) gst_mini_object_unref);
410 }
411