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