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