• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***
2   This file is part of PulseAudio.
3 
4   Copyright 2004-2006 Lennart Poettering
5   Copyright 2008 Colin Guthrie
6 
7   PulseAudio is free software; you can redistribute it and/or modify
8   it under the terms of the GNU Lesser General Public License as
9   published by the Free Software Foundation; either version 2.1 of the
10   License, or (at your option) any later version.
11 
12   PulseAudio is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   General Public License for more details.
16 
17   You should have received a copy of the GNU Lesser General Public
18   License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
19 ***/
20 
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include <avahi-client/client.h>
31 #include <avahi-client/lookup.h>
32 #include <avahi-common/alternative.h>
33 #include <avahi-common/error.h>
34 #include <avahi-common/domain.h>
35 #include <avahi-common/malloc.h>
36 
37 #include <pulse/xmalloc.h>
38 
39 #include <pulsecore/core-util.h>
40 #include <pulsecore/log.h>
41 #include <pulsecore/hashmap.h>
42 #include <pulsecore/modargs.h>
43 #include <pulsecore/namereg.h>
44 #include <pulsecore/avahi-wrap.h>
45 
46 #include "raop-util.h"
47 
48 PA_MODULE_AUTHOR("Colin Guthrie");
49 PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices");
50 PA_MODULE_VERSION(PACKAGE_VERSION);
51 PA_MODULE_LOAD_ONCE(true);
52 PA_MODULE_USAGE(
53         "latency_msec=<audio latency - applies to all devices> ");
54 
55 #define SERVICE_TYPE_SINK "_raop._tcp"
56 
57 struct userdata {
58     pa_core *core;
59     pa_module *module;
60 
61     AvahiPoll *avahi_poll;
62     AvahiClient *client;
63     AvahiServiceBrowser *sink_browser;
64 
65     pa_hashmap *tunnels;
66 
67     bool latency_set;
68     uint32_t latency;
69 };
70 
71 static const char* const valid_modargs[] = {
72     "latency_msec",
73     NULL
74 };
75 
76 struct tunnel {
77     AvahiIfIndex interface;
78     AvahiProtocol protocol;
79     char *name, *type, *domain;
80     uint32_t module_index;
81 };
82 
tunnel_hash(const void * p)83 static unsigned tunnel_hash(const void *p) {
84     const struct tunnel *t = p;
85 
86     return
87         (unsigned) t->interface +
88         (unsigned) t->protocol +
89         pa_idxset_string_hash_func(t->name) +
90         pa_idxset_string_hash_func(t->type) +
91         pa_idxset_string_hash_func(t->domain);
92 }
93 
tunnel_compare(const void * a,const void * b)94 static int tunnel_compare(const void *a, const void *b) {
95     const struct tunnel *ta = a, *tb = b;
96     int r;
97 
98     if (ta->interface != tb->interface)
99         return 1;
100     if (ta->protocol != tb->protocol)
101         return 1;
102     if ((r = strcmp(ta->name, tb->name)))
103         return r;
104     if ((r = strcmp(ta->type, tb->type)))
105         return r;
106     if ((r = strcmp(ta->domain, tb->domain)))
107         return r;
108 
109     return 0;
110 }
111 
tunnel_new(AvahiIfIndex interface,AvahiProtocol protocol,const char * name,const char * type,const char * domain)112 static struct tunnel* tunnel_new(
113         AvahiIfIndex interface, AvahiProtocol protocol,
114         const char *name, const char *type, const char *domain) {
115     struct tunnel *t;
116 
117     t = pa_xnew(struct tunnel, 1);
118     t->interface = interface;
119     t->protocol = protocol;
120     t->name = pa_xstrdup(name);
121     t->type = pa_xstrdup(type);
122     t->domain = pa_xstrdup(domain);
123     t->module_index = PA_IDXSET_INVALID;
124 
125     return t;
126 }
127 
tunnel_free(struct tunnel * t)128 static void tunnel_free(struct tunnel *t) {
129     pa_assert(t);
130     pa_xfree(t->name);
131     pa_xfree(t->type);
132     pa_xfree(t->domain);
133     pa_xfree(t);
134 }
135 
136 /* This functions returns RAOP audio latency as guessed by the
137  * device model header.
138  * Feel free to complete the possible values after testing with
139  * your hardware.
140  */
guess_latency_from_device(const char * model)141 static uint32_t guess_latency_from_device(const char *model) {
142     uint32_t default_latency = RAOP_DEFAULT_LATENCY;
143 
144     if (pa_streq(model, "PIONEER,1")) {
145         /* Pioneer N-30 */
146         default_latency = 2352;
147     } else if (pa_streq(model, "ShairportSync")) {
148         /* Shairport - software AirPort server */
149         default_latency = 2352;
150     }
151 
152     pa_log_debug("Default latency is %u ms for device model %s.", default_latency, model);
153     return default_latency;
154 }
155 
resolver_cb(AvahiServiceResolver * r,AvahiIfIndex interface,AvahiProtocol protocol,AvahiResolverEvent event,const char * name,const char * type,const char * domain,const char * host_name,const AvahiAddress * a,uint16_t port,AvahiStringList * txt,AvahiLookupResultFlags flags,void * userdata)156 static void resolver_cb(
157         AvahiServiceResolver *r,
158         AvahiIfIndex interface, AvahiProtocol protocol,
159         AvahiResolverEvent event,
160         const char *name, const char *type, const char *domain,
161         const char *host_name, const AvahiAddress *a, uint16_t port,
162         AvahiStringList *txt,
163         AvahiLookupResultFlags flags,
164         void *userdata) {
165     struct userdata *u = userdata;
166     struct tunnel *tnl;
167     char *device = NULL, *nicename, *dname, *vname, *args;
168     char *tp = NULL, *et = NULL, *cn = NULL;
169     char *ch = NULL, *ss = NULL, *sr = NULL;
170     char *dm = NULL;
171     char *t = NULL;
172     char at[AVAHI_ADDRESS_STR_MAX];
173     AvahiStringList *l;
174     pa_module *m;
175     uint32_t latency = RAOP_DEFAULT_LATENCY;
176 
177     pa_assert(u);
178 
179     tnl = tunnel_new(interface, protocol, name, type, domain);
180 
181     if (event != AVAHI_RESOLVER_FOUND) {
182         pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client)));
183         goto finish;
184     }
185 
186     if ((nicename = strstr(name, "@"))) {
187         ++nicename;
188         if (strlen(nicename) > 0) {
189             pa_log_debug("Found RAOP: %s", nicename);
190             nicename = pa_escape(nicename, "\"'");
191         } else
192             nicename = NULL;
193     }
194 
195     for (l = txt; l; l = l->next) {
196         char *key, *value;
197         pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0);
198 
199         pa_log_debug("Found key: '%s' with value: '%s'", key, value);
200         if (pa_streq(key, "device")) {
201             device = value;
202             value = NULL;
203         } else if (pa_streq(key, "tp")) {
204             /* Transport protocol:
205              *  - TCP = only TCP,
206              *  - UDP = only UDP,
207              *  - TCP,UDP = both supported (UDP should be preferred) */
208             pa_xfree(tp);
209             if (pa_str_in_list(value, ",", "UDP"))
210                 tp = pa_xstrdup("UDP");
211             else if (pa_str_in_list(value, ",", "TCP"))
212                 tp = pa_xstrdup("TCP");
213             else
214                 tp = pa_xstrdup(value);
215         } else if (pa_streq(key, "et")) {
216             /* Supported encryption types:
217              *  - 0 = none,
218              *  - 1 = RSA,
219              *  - 2 = FairPlay,
220              *  - 3 = MFiSAP,
221              *  - 4 = FairPlay SAPv2.5. */
222             pa_xfree(et);
223             if (pa_str_in_list(value, ",", "1"))
224                 et = pa_xstrdup("RSA");
225             else
226                 et = pa_xstrdup("none");
227         } else if (pa_streq(key, "cn")) {
228             /* Suported audio codecs:
229              *  - 0 = PCM,
230              *  - 1 = ALAC,
231              *  - 2 = AAC,
232              *  - 3 = AAC ELD. */
233             pa_xfree(cn);
234             if (pa_str_in_list(value, ",", "1"))
235                 cn = pa_xstrdup("ALAC");
236             else
237                 cn = pa_xstrdup("PCM");
238         } else if (pa_streq(key, "md")) {
239             /* Supported metadata types:
240              *  - 0 = text,
241              *  - 1 = artwork,
242              *  - 2 = progress. */
243         } else if (pa_streq(key, "pw")) {
244             /* Requires password ? (true/false) */
245         } else if (pa_streq(key, "ch")) {
246             /* Number of channels */
247             pa_xfree(ch);
248             ch = pa_xstrdup(value);
249         } else if (pa_streq(key, "ss")) {
250             /* Sample size */
251             pa_xfree(ss);
252             ss = pa_xstrdup(value);
253         } else if (pa_streq(key, "sr")) {
254             /* Sample rate */
255             pa_xfree(sr);
256             sr = pa_xstrdup(value);
257         } else if (pa_streq(key, "am")) {
258             /* Device model */
259             pa_xfree(dm);
260             dm = pa_xstrdup(value);
261         }
262 
263         avahi_free(key);
264         avahi_free(value);
265     }
266 
267     if (device)
268         dname = pa_sprintf_malloc("raop_output.%s.%s", host_name, device);
269     else
270         dname = pa_sprintf_malloc("raop_output.%s", host_name);
271 
272     if (!(vname = pa_namereg_make_valid_name(dname))) {
273         pa_log("Cannot construct valid device name from '%s'.", dname);
274         avahi_free(device);
275         pa_xfree(dname);
276         pa_xfree(tp);
277         pa_xfree(et);
278         pa_xfree(cn);
279         pa_xfree(ch);
280         pa_xfree(ss);
281         pa_xfree(sr);
282         pa_xfree(dm);
283         goto finish;
284     }
285 
286     avahi_free(device);
287     pa_xfree(dname);
288 
289     avahi_address_snprint(at, sizeof(at), a);
290 
291     if (nicename == NULL)
292         nicename = pa_xstrdup("RAOP");
293 
294     if (dm == NULL)
295         dm = pa_xstrdup(_("Unknown device model"));
296 
297     latency = guess_latency_from_device(dm);
298 
299     args = pa_sprintf_malloc("server=[%s]:%u "
300                              "sink_name=%s "
301                              "sink_properties='device.description=\"%s\" device.model=\"%s\"'",
302                              at, port,
303                              vname,
304                              nicename,
305                              dm);
306     pa_xfree(nicename);
307     pa_xfree(dm);
308 
309     if (tp != NULL) {
310         t = args;
311         args = pa_sprintf_malloc("%s protocol=%s", args, tp);
312         pa_xfree(tp);
313         pa_xfree(t);
314     }
315     if (et != NULL) {
316         t = args;
317         args = pa_sprintf_malloc("%s encryption=%s", args, et);
318         pa_xfree(et);
319         pa_xfree(t);
320     }
321     if (cn != NULL) {
322         t = args;
323         args = pa_sprintf_malloc("%s codec=%s", args, cn);
324         pa_xfree(cn);
325         pa_xfree(t);
326     }
327     if (ch != NULL) {
328         t = args;
329         args = pa_sprintf_malloc("%s channels=%s", args, ch);
330         pa_xfree(ch);
331         pa_xfree(t);
332     }
333     if (ss != NULL) {
334         t = args;
335         args = pa_sprintf_malloc("%s format=%s", args, ss);
336         pa_xfree(ss);
337         pa_xfree(t);
338     }
339     if (sr != NULL) {
340         t = args;
341         args = pa_sprintf_malloc("%s rate=%s", args, sr);
342         pa_xfree(sr);
343         pa_xfree(t);
344     }
345 
346     if (u->latency_set)
347         latency = u->latency;
348 
349     t = args;
350     args = pa_sprintf_malloc("%s latency_msec=%u", args, latency);
351     pa_xfree(t);
352 
353     pa_log_debug("Loading module-raop-sink with arguments '%s'", args);
354 
355     if (pa_module_load(&m, u->core, "module-raop-sink", args) >= 0) {
356         tnl->module_index = m->index;
357         pa_hashmap_put(u->tunnels, tnl, tnl);
358         tnl = NULL;
359     }
360 
361     pa_xfree(vname);
362     pa_xfree(args);
363 
364 finish:
365     avahi_service_resolver_free(r);
366 
367     if (tnl)
368         tunnel_free(tnl);
369 }
370 
browser_cb(AvahiServiceBrowser * b,AvahiIfIndex interface,AvahiProtocol protocol,AvahiBrowserEvent event,const char * name,const char * type,const char * domain,AvahiLookupResultFlags flags,void * userdata)371 static void browser_cb(
372         AvahiServiceBrowser *b,
373         AvahiIfIndex interface, AvahiProtocol protocol,
374         AvahiBrowserEvent event,
375         const char *name, const char *type, const char *domain,
376         AvahiLookupResultFlags flags,
377         void *userdata) {
378     struct userdata *u = userdata;
379     struct tunnel *t;
380 
381     pa_assert(u);
382 
383     if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
384         return;
385 
386     t = tunnel_new(interface, protocol, name, type, domain);
387 
388     if (event == AVAHI_BROWSER_NEW) {
389 
390         if (!pa_hashmap_get(u->tunnels, t))
391             if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u)))
392                 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
393 
394         /* We ignore the returned resolver object here, since the we don't
395          * need to attach any special data to it, and we can still destroy
396          * it from the callback. */
397 
398     } else if (event == AVAHI_BROWSER_REMOVE) {
399         struct tunnel *t2;
400 
401         if ((t2 = pa_hashmap_get(u->tunnels, t))) {
402             pa_module_unload_request_by_index(u->core, t2->module_index, true);
403             pa_hashmap_remove(u->tunnels, t2);
404             tunnel_free(t2);
405         }
406     }
407 
408     tunnel_free(t);
409 }
410 
client_callback(AvahiClient * c,AvahiClientState state,void * userdata)411 static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
412     struct userdata *u = userdata;
413 
414     pa_assert(c);
415     pa_assert(u);
416 
417     u->client = c;
418 
419     switch (state) {
420         case AVAHI_CLIENT_S_REGISTERING:
421         case AVAHI_CLIENT_S_RUNNING:
422         case AVAHI_CLIENT_S_COLLISION:
423             if (!u->sink_browser) {
424                 if (!(u->sink_browser = avahi_service_browser_new(
425                               c,
426                               AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
427                               SERVICE_TYPE_SINK,
428                               NULL,
429                               0,
430                               browser_cb, u))) {
431 
432                     pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c)));
433                     pa_module_unload_request(u->module, true);
434                 }
435             }
436 
437             break;
438 
439         case AVAHI_CLIENT_FAILURE:
440             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
441                 int error;
442 
443                 pa_log_debug("Avahi daemon disconnected.");
444 
445                 /* Try to reconnect. */
446                 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
447                     pa_log("avahi_client_new() failed: %s", avahi_strerror(error));
448                     pa_module_unload_request(u->module, true);
449                 }
450             }
451 
452             /* Fall through. */
453 
454         case AVAHI_CLIENT_CONNECTING:
455             if (u->sink_browser) {
456                 avahi_service_browser_free(u->sink_browser);
457                 u->sink_browser = NULL;
458             }
459 
460             break;
461 
462         default:
463             break;
464     }
465 }
466 
pa__init(pa_module * m)467 int pa__init(pa_module *m) {
468     struct userdata *u;
469     pa_modargs *ma = NULL;
470     int error;
471 
472     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
473         pa_log("Failed to parse module arguments.");
474         goto fail;
475     }
476 
477     m->userdata = u = pa_xnew0(struct userdata, 1);
478     u->core = m->core;
479     u->module = m;
480 
481     if (pa_modargs_get_value(ma, "latency_msec", NULL) != NULL) {
482         u->latency_set = true;
483         if (pa_modargs_get_value_u32(ma, "latency_msec", &u->latency) < 0) {
484             pa_log("Failed to parse latency_msec argument.");
485             goto fail;
486         }
487     }
488 
489     u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare);
490 
491     u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
492 
493     if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
494         pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
495         goto fail;
496     }
497 
498     pa_modargs_free(ma);
499 
500     return 0;
501 
502 fail:
503     pa__done(m);
504 
505     if (ma)
506         pa_modargs_free(ma);
507 
508     return -1;
509 }
510 
pa__done(pa_module * m)511 void pa__done(pa_module *m) {
512     struct userdata *u;
513 
514     pa_assert(m);
515 
516     if (!(u = m->userdata))
517         return;
518 
519     if (u->client)
520         avahi_client_free(u->client);
521 
522     if (u->avahi_poll)
523         pa_avahi_poll_free(u->avahi_poll);
524 
525     if (u->tunnels) {
526         struct tunnel *t;
527 
528         while ((t = pa_hashmap_steal_first(u->tunnels))) {
529             pa_module_unload_request_by_index(u->core, t->module_index, true);
530             tunnel_free(t);
531         }
532 
533         pa_hashmap_free(u->tunnels);
534     }
535 
536     pa_xfree(u);
537 }
538