2 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
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.
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.
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.
20 #include <microdns/microdns.h>
22 #include "gstmicrodnsdevice.h"
24 typedef struct _ListenerContext ListenerContext;
26 struct _GstMDNSDeviceProvider
28 GstDeviceProvider parent;
29 ListenerContext *current_ctx;
32 #define LISTEN_INTERVAL_SECONDS 2
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).
41 * Additionally, mdns_listen() is a blocking call, thus the need to run it in
44 struct _ListenerContext
47 GstDeviceProvider *provider;
49 /* The following fields are protected by @lock */
52 GSequence *last_seen_devices;
55 G_DEFINE_TYPE (GstMDNSDeviceProvider, gst_mdns_device_provider,
56 GST_TYPE_DEVICE_PROVIDER);
68 G_DEFINE_TYPE (GstMDNSDevice, gst_mdns_device, GST_TYPE_DEVICE);
71 cmp_last_seen (GstMDNSDevice * a, GstMDNSDevice * b,
72 gpointer G_GNUC_UNUSED user_data)
74 if (a->last_seen < b->last_seen)
76 if (a->last_seen == b->last_seen)
82 cmp_last_seen_iter (GSequenceIter * ia, GSequenceIter * ib, gpointer user_data)
84 return cmp_last_seen (GST_MDNS_DEVICE (g_sequence_get (ia)),
85 GST_MDNS_DEVICE (g_sequence_get (ib)), user_data);
89 gst_mdns_device_finalize (GObject * object)
91 GstMDNSDevice *self = GST_MDNS_DEVICE (object);
95 G_OBJECT_CLASS (gst_mdns_device_parent_class)->finalize (object);
99 gst_mdns_device_create_element (GstDevice * device, const gchar * name)
101 GstMDNSDevice *self = GST_MDNS_DEVICE (device);
105 ret = gst_element_make_from_uri (self->uri_type, self->uri, name, &err);
108 GST_ERROR_OBJECT (self, "Failed to create element for URI %s: %s",
109 self->uri, err->message);
110 g_clear_error (&err);
117 gst_mdns_device_init (GstMDNSDevice * self)
122 gst_mdns_device_class_init (GstMDNSDeviceClass * klass)
124 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
125 GstDeviceClass *gst_device_class = GST_DEVICE_CLASS (klass);
127 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_mdns_device_finalize);
129 gst_device_class->create_element =
130 GST_DEBUG_FUNCPTR (gst_mdns_device_create_element);
133 /* Slightly unoptimized, ideally add gst_element_factory_from_uri */
134 static GstElementFactory *
135 get_factory_for_uri (GstURIType type, const gchar * uri)
138 GstElementFactory *ret = NULL;
139 GstElement *elem = gst_element_make_from_uri (type, uri, NULL, &err);
142 GST_LOG ("Failed to make element from uri: %s", err->message);
143 g_clear_error (&err);
147 ret = gst_element_get_factory (elem);
149 gst_object_unref (elem);
156 gst_mdns_device_new (GstElementFactory * factory, const gchar * name,
159 GstDevice *ret = NULL;
160 const GList *templates;
163 templates = gst_element_factory_get_static_pad_templates (factory);
164 caps = gst_static_pad_template_get_caps ((GstStaticPadTemplate *)
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));
172 GST_MDNS_DEVICE (ret)->uri = g_strdup (uri);
173 GST_MDNS_DEVICE (ret)->uri_type = gst_element_factory_get_uri_type (factory);
175 gst_caps_unref (caps);
181 remove_old_devices (ListenerContext * ctx)
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);
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;
191 GST_LOG_OBJECT (self,
192 "Device %" GST_PTR_FORMAT " last seen %" GST_TIME_FORMAT " ago", dev,
193 GST_TIME_ARGS (age));
195 if (age > 4 * LISTEN_INTERVAL_SECONDS * G_USEC_PER_SEC) {
196 GSequenceIter *next = g_sequence_iter_next (iter);
198 GST_INFO_OBJECT (self, "Removing device %" GST_PTR_FORMAT, dev);
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);
205 GST_LOG_OBJECT (self, "Keeping device %" GST_PTR_FORMAT, dev);
206 iter = g_sequence_get_end_iter (ctx->last_seen_devices);
212 stop (void *p_cookie)
215 ListenerContext *ctx = (ListenerContext *) p_cookie;
217 g_mutex_lock (&ctx->lock);
221 remove_old_devices (ctx);
224 g_mutex_unlock (&ctx->lock);
230 callback (void *p_cookie, gint status, const struct rr_entry *entry)
232 ListenerContext *ctx = (ListenerContext *) p_cookie;
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);
238 g_mutex_lock (&ctx->lock);
243 GST_DEBUG_OBJECT (self, "received new entries");
246 mdns_strerror (status, err, sizeof (err));
247 GST_ERROR ("MDNS error: %s", err);
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);
257 for (tmp = entry; tmp; tmp = tmp->next) {
258 if (tmp->type == RR_TXT) {
259 const struct rr_entry *srv;
261 srv = (const struct rr_entry *) g_hash_table_lookup (srvs, tmp->name);
264 GST_LOG_OBJECT (self, "No SRV associated with TXT entry for %s",
269 if (g_str_has_suffix (tmp->name, "._rtsp._tcp.local")) {
272 struct rr_data_txt *tmp_txt;
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;
283 g_strdup_printf ("rtsp://%s:%d/%s", srv->data.SRV.target,
284 srv->data.SRV.port, path);
287 g_strdup_printf ("rtsp://%s:%d", srv->data.SRV.target,
291 dev = GST_MDNS_DEVICE (g_hash_table_lookup (ctx->devices, uri));
293 GST_LOG_OBJECT (self, "Saw device at uri %s", uri);
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);
302 GstElementFactory *factory;
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",
312 display_name = g_strndup (tmp->name, strlen (tmp->name) - 17);
314 GST_MDNS_DEVICE (gst_mdns_device_new (factory, display_name,
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);
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));
331 GST_LOG_OBJECT (self, "unknown protocol for %s", tmp->name);
338 g_hash_table_unref (srvs);
339 g_mutex_unlock (&ctx->lock);
343 _listen (ListenerContext * ctx)
347 struct mdns_ctx *mctx;
348 const gchar *ppsz_names[] = { "_rtsp._tcp.local" };
351 if ((r = mdns_init (&mctx, MDNS_ADDR_IPV4, MDNS_PORT)) < 0)
354 GST_INFO_OBJECT (ctx->provider, "Start listening");
356 if ((r = mdns_listen (mctx, ppsz_names, i_nb_names, RR_PTR,
357 LISTEN_INTERVAL_SECONDS, stop, callback, ctx)) < 0) {
363 GST_INFO_OBJECT (ctx->provider, "Done listening");
365 g_sequence_free (ctx->last_seen_devices);
366 g_hash_table_unref (ctx->devices);
367 g_mutex_clear (&ctx->lock);
374 mdns_strerror (r, err, sizeof (err));
375 GST_ERROR ("MDNS error: %s", err);
382 gst_mdns_device_provider_start (GstDeviceProvider * provider)
384 GstMDNSDeviceProvider *self = GST_MDNS_DEVICE_PROVIDER (provider);
385 ListenerContext *ctx = g_new0 (ListenerContext, 1);
387 g_mutex_init (&ctx->lock);
388 ctx->provider = provider;
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;
394 g_thread_new (NULL, (GThreadFunc) _listen, ctx);
400 gst_mdns_device_provider_stop (GstDeviceProvider * provider)
402 GstMDNSDeviceProvider *self = GST_MDNS_DEVICE_PROVIDER (provider);
404 g_assert (self->current_ctx);
406 g_mutex_lock (&self->current_ctx->lock);
407 self->current_ctx->stop = true;
408 g_mutex_unlock (&self->current_ctx->lock);
410 self->current_ctx = NULL;
414 gst_mdns_device_provider_init (GstMDNSDeviceProvider * self)
419 gst_mdns_device_provider_class_init (GstMDNSDeviceProviderClass * klass)
421 GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
423 dm_class->start = GST_DEBUG_FUNCPTR (gst_mdns_device_provider_start);
424 dm_class->stop = GST_DEBUG_FUNCPTR (gst_mdns_device_provider_stop);
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>");