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 48PA_MODULE_AUTHOR("Colin Guthrie"); 49PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of RAOP devices"); 50PA_MODULE_VERSION(PACKAGE_VERSION); 51PA_MODULE_LOAD_ONCE(true); 52PA_MODULE_USAGE( 53 "latency_msec=<audio latency - applies to all devices> "); 54 55#define SERVICE_TYPE_SINK "_raop._tcp" 56 57struct 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 71static const char* const valid_modargs[] = { 72 "latency_msec", 73 NULL 74}; 75 76struct tunnel { 77 AvahiIfIndex interface; 78 AvahiProtocol protocol; 79 char *name, *type, *domain; 80 uint32_t module_index; 81}; 82 83static 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 94static 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 112static 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 128static 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 */ 141static 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 156static 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 364finish: 365 avahi_service_resolver_free(r); 366 367 if (tnl) 368 tunnel_free(tnl); 369} 370 371static 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 411static 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 467int 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 502fail: 503 pa__done(m); 504 505 if (ma) 506 pa_modargs_free(ma); 507 508 return -1; 509} 510 511void 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