Move files from gst-plugins-bad into the "subprojects/gst-plugins-bad/" subdir
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / mdns / gstmicrodnsdevice.c
1 /* GStreamer
2  * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <microdns/microdns.h>
21
22 #include "gstmicrodnsdevice.h"
23
24 typedef struct _ListenerContext ListenerContext;
25
26 struct _GstMDNSDeviceProvider
27 {
28   GstDeviceProvider parent;
29   ListenerContext *current_ctx;
30 };
31
32 #define LISTEN_INTERVAL_SECONDS 2
33
34 /* #GstDeviceProvider.stop() is synchronous, but libmicrodns' stop mechanism
35  * isn't, as it polls and queries the application's provided stop function
36  * before each new loop crank. This means there can potentially exist N
37  * contexts at any given time, if the provider is started and stopped in
38  * rapid succession. At most one of them can be active however (stop == false),
39  * with the other N - 1 in the process of stopping (stop == true).
40  *
41  * Additionally, mdns_listen() is a blocking call, thus the need to run it in
42  * its own thread.
43  */
44 struct _ListenerContext
45 {
46   GMutex lock;
47   GstDeviceProvider *provider;
48
49   /* The following fields are protected by @lock */
50   bool stop;
51   GHashTable *devices;
52   GSequence *last_seen_devices;
53 };
54
55 G_DEFINE_TYPE (GstMDNSDeviceProvider, gst_mdns_device_provider,
56     GST_TYPE_DEVICE_PROVIDER);
57
58 struct _GstMDNSDevice
59 {
60   GstDevice parent;
61
62   GstURIType uri_type;
63   gchar *uri;
64   GSequenceIter *iter;
65   gint64 last_seen;
66 };
67
68 G_DEFINE_TYPE (GstMDNSDevice, gst_mdns_device, GST_TYPE_DEVICE);
69
70 static gint
71 cmp_last_seen (GstMDNSDevice * a, GstMDNSDevice * b,
72     gpointer G_GNUC_UNUSED user_data)
73 {
74   if (a->last_seen < b->last_seen)
75     return -1;
76   if (a->last_seen == b->last_seen)
77     return 0;
78   return 1;
79 }
80
81 static gint
82 cmp_last_seen_iter (GSequenceIter * ia, GSequenceIter * ib, gpointer user_data)
83 {
84   return cmp_last_seen (GST_MDNS_DEVICE (g_sequence_get (ia)),
85       GST_MDNS_DEVICE (g_sequence_get (ib)), user_data);
86 }
87
88 static void
89 gst_mdns_device_finalize (GObject * object)
90 {
91   GstMDNSDevice *self = GST_MDNS_DEVICE (object);
92
93   g_free (self->uri);
94
95   G_OBJECT_CLASS (gst_mdns_device_parent_class)->finalize (object);
96 }
97
98 static GstElement *
99 gst_mdns_device_create_element (GstDevice * device, const gchar * name)
100 {
101   GstMDNSDevice *self = GST_MDNS_DEVICE (device);
102   GstElement *ret;
103   GError *err = NULL;
104
105   ret = gst_element_make_from_uri (self->uri_type, self->uri, name, &err);
106
107   if (!ret) {
108     GST_ERROR_OBJECT (self, "Failed to create element for URI %s: %s",
109         self->uri, err->message);
110     g_clear_error (&err);
111   }
112
113   return ret;
114 }
115
116 static void
117 gst_mdns_device_init (GstMDNSDevice * self)
118 {
119 }
120
121 static void
122 gst_mdns_device_class_init (GstMDNSDeviceClass * klass)
123 {
124   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
125   GstDeviceClass *gst_device_class = GST_DEVICE_CLASS (klass);
126
127   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_mdns_device_finalize);
128
129   gst_device_class->create_element =
130       GST_DEBUG_FUNCPTR (gst_mdns_device_create_element);
131 }
132
133 /* Slightly unoptimized, ideally add gst_element_factory_from_uri */
134 static GstElementFactory *
135 get_factory_for_uri (GstURIType type, const gchar * uri)
136 {
137   GError *err = NULL;
138   GstElementFactory *ret = NULL;
139   GstElement *elem = gst_element_make_from_uri (type, uri, NULL, &err);
140
141   if (!elem) {
142     GST_LOG ("Failed to make element from uri: %s", err->message);
143     g_clear_error (&err);
144     goto done;
145   }
146
147   ret = gst_element_get_factory (elem);
148
149   gst_object_unref (elem);
150
151 done:
152   return ret;
153 }
154
155 static GstDevice *
156 gst_mdns_device_new (GstElementFactory * factory, const gchar * name,
157     const gchar * uri)
158 {
159   GstDevice *ret = NULL;
160   const GList *templates;
161   GstCaps *caps;
162
163   templates = gst_element_factory_get_static_pad_templates (factory);
164   caps = gst_static_pad_template_get_caps ((GstStaticPadTemplate *)
165       templates->data);
166
167   ret = GST_DEVICE (g_object_new (GST_TYPE_MDNS_DEVICE,
168           "display-name", name,
169           "device-class", gst_element_factory_get_metadata (factory, "klass"),
170           "caps", caps, NULL));
171
172   GST_MDNS_DEVICE (ret)->uri = g_strdup (uri);
173   GST_MDNS_DEVICE (ret)->uri_type = gst_element_factory_get_uri_type (factory);
174
175   gst_caps_unref (caps);
176
177   return ret;
178 }
179
180 static void
181 remove_old_devices (ListenerContext * ctx)
182 {
183   GstMDNSDeviceProvider *self = GST_MDNS_DEVICE_PROVIDER (ctx->provider);
184   GstClockTime now = g_get_monotonic_time ();
185   GSequenceIter *iter = g_sequence_get_begin_iter (ctx->last_seen_devices);
186
187   while (!g_sequence_iter_is_end (iter)) {
188     GstMDNSDevice *dev = GST_MDNS_DEVICE (g_sequence_get (iter));
189     GstClockTime age = now - dev->last_seen;
190
191     GST_LOG_OBJECT (self,
192         "Device %" GST_PTR_FORMAT " last seen %" GST_TIME_FORMAT " ago", dev,
193         GST_TIME_ARGS (age));
194
195     if (age > 4 * LISTEN_INTERVAL_SECONDS * G_USEC_PER_SEC) {
196       GSequenceIter *next = g_sequence_iter_next (iter);
197
198       GST_INFO_OBJECT (self, "Removing device %" GST_PTR_FORMAT, dev);
199
200       gst_device_provider_device_remove (ctx->provider, GST_DEVICE (dev));
201       g_hash_table_remove (ctx->devices, dev->uri);
202       g_sequence_remove (iter);
203       iter = next;
204     } else {
205       GST_LOG_OBJECT (self, "Keeping device %" GST_PTR_FORMAT, dev);
206       iter = g_sequence_get_end_iter (ctx->last_seen_devices);
207     }
208   }
209 }
210
211 static bool
212 stop (void *p_cookie)
213 {
214   bool ret;
215   ListenerContext *ctx = (ListenerContext *) p_cookie;
216
217   g_mutex_lock (&ctx->lock);
218   ret = ctx->stop;
219
220   if (!ctx->stop) {
221     remove_old_devices (ctx);
222   }
223
224   g_mutex_unlock (&ctx->lock);
225
226   return ret;
227 }
228
229 static void
230 callback (void *p_cookie, gint status, const struct rr_entry *entry)
231 {
232   ListenerContext *ctx = (ListenerContext *) p_cookie;
233   gchar err[128];
234   const struct rr_entry *tmp;
235   GHashTable *srvs = g_hash_table_new (g_str_hash, g_str_equal);
236   GstMDNSDeviceProvider *self = GST_MDNS_DEVICE_PROVIDER (ctx->provider);
237
238   g_mutex_lock (&ctx->lock);
239
240   if (ctx->stop)
241     goto done;
242
243   GST_DEBUG_OBJECT (self, "received new entries");
244
245   if (status < 0) {
246     mdns_strerror (status, err, sizeof (err));
247     GST_ERROR ("MDNS error: %s", err);
248     goto done;
249   }
250
251   for (tmp = entry; tmp; tmp = tmp->next) {
252     if (tmp->type == RR_SRV) {
253       g_hash_table_insert (srvs, (gpointer) tmp->name, (gpointer) tmp);
254     }
255   }
256
257   for (tmp = entry; tmp; tmp = tmp->next) {
258     if (tmp->type == RR_TXT) {
259       const struct rr_entry *srv;
260
261       srv = (const struct rr_entry *) g_hash_table_lookup (srvs, tmp->name);
262
263       if (!srv) {
264         GST_LOG_OBJECT (self, "No SRV associated with TXT entry for %s",
265             tmp->name);
266         continue;
267       }
268
269       if (g_str_has_suffix (tmp->name, "._rtsp._tcp.local")) {
270         gchar *path = NULL;
271         gchar *uri;
272         struct rr_data_txt *tmp_txt;
273         GstMDNSDevice *dev;
274
275         for (tmp_txt = tmp->data.TXT; tmp_txt; tmp_txt = tmp_txt->next) {
276           if (g_str_has_prefix (tmp_txt->txt, "path=")) {
277             path = tmp_txt->txt + 5;
278           }
279         }
280
281         if (path) {
282           uri =
283               g_strdup_printf ("rtsp://%s:%d/%s", srv->data.SRV.target,
284               srv->data.SRV.port, path);
285         } else {
286           uri =
287               g_strdup_printf ("rtsp://%s:%d", srv->data.SRV.target,
288               srv->data.SRV.port);
289         }
290
291         dev = GST_MDNS_DEVICE (g_hash_table_lookup (ctx->devices, uri));
292
293         GST_LOG_OBJECT (self, "Saw device at uri %s", uri);
294
295         if (dev) {
296           dev->last_seen = g_get_monotonic_time ();
297           GST_LOG_OBJECT (self,
298               "updating last_seen for device %" GST_PTR_FORMAT ": %"
299               G_GINT64_FORMAT, dev, dev->last_seen);
300           g_sequence_sort_changed_iter (dev->iter, cmp_last_seen_iter, NULL);
301         } else {
302           GstElementFactory *factory;
303           gchar *display_name;
304
305           if (!(factory = get_factory_for_uri (GST_URI_SRC, uri))) {
306             GST_LOG_OBJECT (self,
307                 "Not registering device %s as no compatible factory was found",
308                 tmp->name);
309             goto done;
310           }
311
312           display_name = g_strndup (tmp->name, strlen (tmp->name) - 17);
313           dev =
314               GST_MDNS_DEVICE (gst_mdns_device_new (factory, display_name,
315                   uri));
316           g_free (display_name);
317           dev->last_seen = g_get_monotonic_time ();
318           GST_INFO_OBJECT (self,
319               "Saw new device %" GST_PTR_FORMAT " at %" G_GINT64_FORMAT
320               " with factory %" GST_PTR_FORMAT, dev, dev->last_seen, factory);
321           dev->iter =
322               g_sequence_insert_sorted (ctx->last_seen_devices, (gpointer) dev,
323               (GCompareDataFunc) cmp_last_seen, NULL);
324           g_hash_table_insert (ctx->devices, g_strdup (uri),
325               gst_object_ref (dev));
326           gst_device_provider_device_add (ctx->provider, GST_DEVICE (dev));
327         }
328
329         g_free (uri);
330       } else {
331         GST_LOG_OBJECT (self, "unknown protocol for %s", tmp->name);
332         continue;
333       }
334     }
335   }
336
337 done:
338   g_hash_table_unref (srvs);
339   g_mutex_unlock (&ctx->lock);
340 }
341
342 static gpointer
343 _listen (ListenerContext * ctx)
344 {
345   gint r = 0;
346   gchar err[128];
347   struct mdns_ctx *mctx;
348   const gchar *ppsz_names[] = { "_rtsp._tcp.local" };
349   gint i_nb_names = 1;
350
351   if ((r = mdns_init (&mctx, MDNS_ADDR_IPV4, MDNS_PORT)) < 0)
352     goto err;
353
354   GST_INFO_OBJECT (ctx->provider, "Start listening");
355
356   if ((r = mdns_listen (mctx, ppsz_names, i_nb_names, RR_PTR,
357               LISTEN_INTERVAL_SECONDS, stop, callback, ctx)) < 0) {
358     mdns_destroy (mctx);
359     goto err;
360   }
361
362 done:
363   GST_INFO_OBJECT (ctx->provider, "Done listening");
364
365   g_sequence_free (ctx->last_seen_devices);
366   g_hash_table_unref (ctx->devices);
367   g_mutex_clear (&ctx->lock);
368   g_free (ctx);
369
370   return NULL;
371
372 err:
373   if (r < 0) {
374     mdns_strerror (r, err, sizeof (err));
375     GST_ERROR ("MDNS error: %s", err);
376   }
377
378   goto done;
379 }
380
381 static gboolean
382 gst_mdns_device_provider_start (GstDeviceProvider * provider)
383 {
384   GstMDNSDeviceProvider *self = GST_MDNS_DEVICE_PROVIDER (provider);
385   ListenerContext *ctx = g_new0 (ListenerContext, 1);
386
387   g_mutex_init (&ctx->lock);
388   ctx->provider = provider;
389   ctx->devices =
390       g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
391   ctx->last_seen_devices = g_sequence_new (NULL);
392   self->current_ctx = ctx;
393
394   g_thread_new (NULL, (GThreadFunc) _listen, ctx);
395
396   return TRUE;
397 }
398
399 static void
400 gst_mdns_device_provider_stop (GstDeviceProvider * provider)
401 {
402   GstMDNSDeviceProvider *self = GST_MDNS_DEVICE_PROVIDER (provider);
403
404   g_assert (self->current_ctx);
405
406   g_mutex_lock (&self->current_ctx->lock);
407   self->current_ctx->stop = true;
408   g_mutex_unlock (&self->current_ctx->lock);
409
410   self->current_ctx = NULL;
411 }
412
413 static void
414 gst_mdns_device_provider_init (GstMDNSDeviceProvider * self)
415 {
416 }
417
418 static void
419 gst_mdns_device_provider_class_init (GstMDNSDeviceProviderClass * klass)
420 {
421   GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
422
423   dm_class->start = GST_DEBUG_FUNCPTR (gst_mdns_device_provider_start);
424   dm_class->stop = GST_DEBUG_FUNCPTR (gst_mdns_device_provider_stop);
425
426   gst_device_provider_class_set_static_metadata (dm_class,
427       "MDNS Device Provider", "Source/Network",
428       "List and provides MDNS-advertised source devices",
429       "Mathieu Duponchelle <mathieu@centricular.com>");
430 }