1/*** 2 This file is part of PulseAudio. 3 4 Copyright 2006 Lennart Poettering 5 Copyright 2009 Canonical Ltd 6 Copyright (C) 2012 Intel Corporation 7 8 PulseAudio is free software; you can redistribute it and/or modify 9 it under the terms of the GNU Lesser General Public License as published 10 by the Free Software Foundation; either version 2.1 of the License, 11 or (at your option) any later version. 12 13 PulseAudio is distributed in the hope that it will be useful, but 14 WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public License 19 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. 20***/ 21 22#ifdef HAVE_CONFIG_H 23#include <config.h> 24#endif 25 26#include <pulse/xmalloc.h> 27 28#include <pulsecore/core.h> 29#include <pulsecore/modargs.h> 30#include <pulsecore/source-output.h> 31#include <pulsecore/source.h> 32#include <pulsecore/core-util.h> 33 34PA_MODULE_AUTHOR("Frédéric Dalleau, Pali Rohár"); 35PA_MODULE_DESCRIPTION("Policy module to make using bluetooth devices out-of-the-box easier"); 36PA_MODULE_VERSION(PACKAGE_VERSION); 37PA_MODULE_LOAD_ONCE(true); 38PA_MODULE_USAGE( 39 "auto_switch=<Switch between hsp and a2dp profile? (0 - never, 1 - media.role=phone, 2 - heuristic> " 40 "a2dp_source=<Handle a2dp_source card profile (sink role)?> " 41 "ag=<Handle headset_audio_gateway or handsfree_audio_gateway card profile (headset role)?> "); 42 43static const char* const valid_modargs[] = { 44 "auto_switch", 45 "a2dp_source", 46 "ag", 47 NULL 48}; 49 50struct userdata { 51 uint32_t auto_switch; 52 bool enable_a2dp_source; 53 bool enable_ag; 54 pa_hook_slot *source_put_slot; 55 pa_hook_slot *sink_put_slot; 56 pa_hook_slot *source_output_put_slot; 57 pa_hook_slot *source_output_unlink_slot; 58 pa_hook_slot *card_init_profile_slot; 59 pa_hook_slot *card_unlink_slot; 60 pa_hook_slot *profile_available_changed_slot; 61 pa_hashmap *will_need_revert_card_map; 62}; 63 64/* When a source is created, loopback it to default sink */ 65static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source, void *userdata) { 66 struct userdata *u = userdata; 67 const char *s; 68 const char *role; 69 char *args; 70 pa_module *m = NULL; 71 72 pa_assert(c); 73 pa_assert(source); 74 75 /* Only consider bluetooth sinks and sources */ 76 s = pa_proplist_gets(source->proplist, PA_PROP_DEVICE_BUS); 77 if (!s) 78 return PA_HOOK_OK; 79 80 if (!pa_streq(s, "bluetooth")) 81 return PA_HOOK_OK; 82 83 s = pa_proplist_gets(source->proplist, "bluetooth.protocol"); 84 if (!s) 85 return PA_HOOK_OK; 86 87 if (u->enable_a2dp_source && pa_streq(s, "a2dp_source")) 88 role = "music"; 89 else if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway"))) 90 role = "phone"; 91 else { 92 pa_log_debug("Profile %s cannot be selected for loopback", s); 93 return PA_HOOK_OK; 94 } 95 96 /* Load module-loopback */ 97 args = pa_sprintf_malloc("source=\"%s\" source_dont_move=\"true\" sink_input_properties=\"media.role=%s\"", source->name, 98 role); 99 (void) pa_module_load(&m, c, "module-loopback", args); 100 pa_xfree(args); 101 102 return PA_HOOK_OK; 103} 104 105/* When a sink is created, loopback it to default source */ 106static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *userdata) { 107 struct userdata *u = userdata; 108 const char *s; 109 const char *role; 110 char *args; 111 pa_module *m = NULL; 112 113 pa_assert(c); 114 pa_assert(sink); 115 116 /* Only consider bluetooth sinks and sources */ 117 s = pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_BUS); 118 if (!s) 119 return PA_HOOK_OK; 120 121 if (!pa_streq(s, "bluetooth")) 122 return PA_HOOK_OK; 123 124 s = pa_proplist_gets(sink->proplist, "bluetooth.protocol"); 125 if (!s) 126 return PA_HOOK_OK; 127 128 if (u->enable_ag && (pa_streq(s, "headset_audio_gateway") || pa_streq(s, "handsfree_audio_gateway"))) 129 role = "phone"; 130 else { 131 pa_log_debug("Profile %s cannot be selected for loopback", s); 132 return PA_HOOK_OK; 133 } 134 135 /* Load module-loopback */ 136 args = pa_sprintf_malloc("sink=\"%s\" sink_dont_move=\"true\" source_output_properties=\"media.role=%s\"", sink->name, 137 role); 138 (void) pa_module_load(&m, c, "module-loopback", args); 139 pa_xfree(args); 140 141 return PA_HOOK_OK; 142} 143 144static void card_set_profile(struct userdata *u, pa_card *card, bool revert_to_a2dp) 145{ 146 pa_card_profile *profile; 147 void *state; 148 149 /* Find available profile and activate it */ 150 PA_HASHMAP_FOREACH(profile, card->profiles, state) { 151 if (profile->available == PA_AVAILABLE_NO) 152 continue; 153 154 /* Check for correct profile based on revert_to_a2dp */ 155 if (revert_to_a2dp) { 156 if (!pa_streq(profile->name, "a2dp_sink")) 157 continue; 158 } else { 159 if (!pa_streq(profile->name, "headset_head_unit") && !pa_streq(profile->name, "handsfree_head_unit")) 160 continue; 161 } 162 163 pa_log_debug("Setting card '%s' to profile '%s'", card->name, profile->name); 164 165 if (pa_card_set_profile(card, profile, false) != 0) { 166 pa_log_warn("Could not set profile '%s'", profile->name); 167 continue; 168 } 169 170 /* When we are not in revert_to_a2dp phase flag this card for will_need_revert */ 171 if (!revert_to_a2dp) 172 pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); 173 174 break; 175 } 176} 177 178/* Switch profile for one card */ 179static void switch_profile(pa_card *card, bool revert_to_a2dp, void *userdata) { 180 struct userdata *u = userdata; 181 const char *s; 182 183 /* Only consider bluetooth cards */ 184 s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS); 185 if (!s || !pa_streq(s, "bluetooth")) 186 return; 187 188 if (revert_to_a2dp) { 189 /* In revert_to_a2dp phase only consider cards with will_need_revert flag and remove it */ 190 if (!pa_hashmap_remove(u->will_need_revert_card_map, card)) 191 return; 192 193 /* Skip card if does not have active headset profile */ 194 if (!pa_streq(card->active_profile->name, "headset_head_unit") && !pa_streq(card->active_profile->name, "handsfree_head_unit")) 195 return; 196 197 /* Skip card if already has active a2dp profile */ 198 if (pa_streq(card->active_profile->name, "a2dp_sink")) 199 return; 200 } else { 201 /* Skip card if does not have active a2dp profile */ 202 if (!pa_streq(card->active_profile->name, "a2dp_sink")) 203 return; 204 205 /* Skip card if already has active headset profile */ 206 if (pa_streq(card->active_profile->name, "headset_head_unit") || pa_streq(card->active_profile->name, "handsfree_head_unit")) 207 return; 208 } 209 210 card_set_profile(u, card, revert_to_a2dp); 211} 212 213/* Return true if we should ignore this source output */ 214static bool ignore_output(pa_source_output *source_output, void *userdata) { 215 struct userdata *u = userdata; 216 const char *s; 217 218 /* New applications could set media.role for identifying streams */ 219 /* We are interested only in media.role=phone */ 220 s = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE); 221 if (s) 222 return !pa_streq(s, "phone"); 223 224 /* If media.role is not set use some heuristic (if enabled) */ 225 if (u->auto_switch != 2) 226 return true; 227 228 /* Ignore if resample method is peaks (used by desktop volume programs) */ 229 if (pa_source_output_get_resample_method(source_output) == PA_RESAMPLER_PEAKS) 230 return true; 231 232 /* Ignore if there is no client/application assigned (used by virtual stream) */ 233 if (!source_output->client) 234 return true; 235 236 /* Ignore if recording from monitor of sink */ 237 if (source_output->direct_on_input) 238 return true; 239 240 return false; 241} 242 243static unsigned source_output_count(pa_core *c, void *userdata) { 244 pa_source_output *source_output; 245 uint32_t idx; 246 unsigned count = 0; 247 248 PA_IDXSET_FOREACH(source_output, c->source_outputs, idx) 249 if (!ignore_output(source_output, userdata)) 250 ++count; 251 252 return count; 253} 254 255/* Switch profile for all cards */ 256static void switch_profile_all(pa_idxset *cards, bool revert_to_a2dp, void *userdata) { 257 pa_card *card; 258 uint32_t idx; 259 260 PA_IDXSET_FOREACH(card, cards, idx) 261 switch_profile(card, revert_to_a2dp, userdata); 262} 263 264/* When a source output is created, switch profile a2dp to profile hsp */ 265static pa_hook_result_t source_output_put_hook_callback(pa_core *c, pa_source_output *source_output, void *userdata) { 266 pa_assert(c); 267 pa_assert(source_output); 268 269 if (ignore_output(source_output, userdata)) 270 return PA_HOOK_OK; 271 272 switch_profile_all(c->cards, false, userdata); 273 return PA_HOOK_OK; 274} 275 276/* When all source outputs are unlinked, switch profile hsp back back to profile a2dp */ 277static pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source_output *source_output, void *userdata) { 278 pa_assert(c); 279 pa_assert(source_output); 280 281 if (ignore_output(source_output, userdata)) 282 return PA_HOOK_OK; 283 284 /* If there are still some source outputs do nothing. */ 285 if (source_output_count(c, userdata) > 0) 286 return PA_HOOK_OK; 287 288 switch_profile_all(c->cards, true, userdata); 289 return PA_HOOK_OK; 290} 291 292static pa_hook_result_t card_init_profile_hook_callback(pa_core *c, pa_card *card, void *userdata) { 293 struct userdata *u = userdata; 294 const char *s; 295 296 pa_assert(c); 297 pa_assert(card); 298 299 if (source_output_count(c, userdata) == 0) 300 return PA_HOOK_OK; 301 302 /* Only consider bluetooth cards */ 303 s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS); 304 if (!s || !pa_streq(s, "bluetooth")) 305 return PA_HOOK_OK; 306 307 /* Ignore card if has already set other initial profile than a2dp */ 308 if (card->active_profile && 309 !pa_streq(card->active_profile->name, "a2dp_sink")) 310 return PA_HOOK_OK; 311 312 /* Set initial profile to hsp */ 313 card_set_profile(u, card, false); 314 315 /* Flag this card for will_need_revert */ 316 pa_hashmap_put(u->will_need_revert_card_map, card, PA_INT_TO_PTR(1)); 317 return PA_HOOK_OK; 318} 319 320static pa_hook_result_t card_unlink_hook_callback(pa_core *c, pa_card *card, void *userdata) { 321 pa_assert(c); 322 pa_assert(card); 323 switch_profile(card, true, userdata); 324 return PA_HOOK_OK; 325} 326 327static pa_card_profile *find_best_profile(pa_card *card) { 328 void *state; 329 pa_card_profile *profile; 330 pa_card_profile *result = card->active_profile; 331 332 PA_HASHMAP_FOREACH(profile, card->profiles, state) { 333 if (profile->available == PA_AVAILABLE_NO) 334 continue; 335 336 if (result == NULL || 337 (profile->available == PA_AVAILABLE_YES && result->available == PA_AVAILABLE_UNKNOWN) || 338 (profile->available == result->available && profile->priority > result->priority)) 339 result = profile; 340 } 341 342 return result; 343} 344 345static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_profile *profile, void *userdata) { 346 pa_card *card; 347 const char *s; 348 bool is_active_profile; 349 pa_card_profile *selected_profile; 350 351 pa_assert(c); 352 pa_assert(profile); 353 pa_assert_se((card = profile->card)); 354 355 /* Only consider bluetooth cards */ 356 s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS); 357 if (!s || !pa_streq(s, "bluetooth")) 358 return PA_HOOK_OK; 359 360 /* Do not automatically switch profiles for headsets, just in case */ 361 if (pa_streq(profile->name, "a2dp_sink") || 362 pa_streq(profile->name, "headset_head_unit") || 363 pa_streq(profile->name, "handsfree_head_unit")) 364 return PA_HOOK_OK; 365 366 is_active_profile = card->active_profile == profile; 367 368 if (profile->available == PA_AVAILABLE_YES) { 369 if (is_active_profile) 370 return PA_HOOK_OK; 371 372 if (card->active_profile->available == PA_AVAILABLE_YES && card->active_profile->priority >= profile->priority) 373 return PA_HOOK_OK; 374 375 selected_profile = profile; 376 } else { 377 if (!is_active_profile) 378 return PA_HOOK_OK; 379 380 pa_assert_se((selected_profile = find_best_profile(card))); 381 382 if (selected_profile == card->active_profile) 383 return PA_HOOK_OK; 384 } 385 386 pa_log_debug("Setting card '%s' to profile '%s'", card->name, selected_profile->name); 387 388 if (pa_card_set_profile(card, selected_profile, false) != 0) 389 pa_log_warn("Could not set profile '%s'", selected_profile->name); 390 391 return PA_HOOK_OK; 392} 393 394static void handle_all_profiles(pa_core *core) { 395 pa_card *card; 396 uint32_t state; 397 398 PA_IDXSET_FOREACH(card, core->cards, state) { 399 pa_card_profile *profile; 400 void *state2; 401 402 PA_HASHMAP_FOREACH(profile, card->profiles, state2) 403 profile_available_hook_callback(core, profile, NULL); 404 } 405} 406 407int pa__init(pa_module *m) { 408 pa_modargs *ma; 409 struct userdata *u; 410 411 pa_assert(m); 412 413 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { 414 pa_log_error("Failed to parse module arguments"); 415 goto fail; 416 } 417 418 m->userdata = u = pa_xnew0(struct userdata, 1); 419 420 u->auto_switch = 1; 421 422 if (pa_modargs_get_value(ma, "auto_switch", NULL)) { 423 bool auto_switch_bool; 424 425 /* auto_switch originally took a boolean value, let's keep 426 * compatibility with configuration files that still pass a boolean. */ 427 if (pa_modargs_get_value_boolean(ma, "auto_switch", &auto_switch_bool) >= 0) { 428 if (auto_switch_bool) 429 u->auto_switch = 1; 430 else 431 u->auto_switch = 0; 432 433 } else if (pa_modargs_get_value_u32(ma, "auto_switch", &u->auto_switch) < 0) { 434 pa_log("Failed to parse auto_switch argument."); 435 goto fail; 436 } 437 } 438 439 u->enable_a2dp_source = true; 440 if (pa_modargs_get_value_boolean(ma, "a2dp_source", &u->enable_a2dp_source) < 0) { 441 pa_log("Failed to parse a2dp_source argument."); 442 goto fail; 443 } 444 445 u->enable_ag = true; 446 if (pa_modargs_get_value_boolean(ma, "ag", &u->enable_ag) < 0) { 447 pa_log("Failed to parse ag argument."); 448 goto fail; 449 } 450 451 u->will_need_revert_card_map = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); 452 453 u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, 454 (pa_hook_cb_t) source_put_hook_callback, u); 455 456 u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, 457 (pa_hook_cb_t) sink_put_hook_callback, u); 458 459 if (u->auto_switch) { 460 u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, 461 (pa_hook_cb_t) source_output_put_hook_callback, u); 462 463 u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL, 464 (pa_hook_cb_t) source_output_unlink_hook_callback, u); 465 466 u->card_init_profile_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL, 467 (pa_hook_cb_t) card_init_profile_hook_callback, u); 468 469 u->card_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL, 470 (pa_hook_cb_t) card_unlink_hook_callback, u); 471 } 472 473 u->profile_available_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED], 474 PA_HOOK_NORMAL, (pa_hook_cb_t) profile_available_hook_callback, u); 475 476 handle_all_profiles(m->core); 477 478 pa_modargs_free(ma); 479 return 0; 480 481fail: 482 if (ma) 483 pa_modargs_free(ma); 484 return -1; 485} 486 487void pa__done(pa_module *m) { 488 struct userdata *u; 489 490 pa_assert(m); 491 492 if (!(u = m->userdata)) 493 return; 494 495 if (u->source_put_slot) 496 pa_hook_slot_free(u->source_put_slot); 497 498 if (u->sink_put_slot) 499 pa_hook_slot_free(u->sink_put_slot); 500 501 if (u->source_output_put_slot) 502 pa_hook_slot_free(u->source_output_put_slot); 503 504 if (u->source_output_unlink_slot) 505 pa_hook_slot_free(u->source_output_unlink_slot); 506 507 if (u->card_init_profile_slot) 508 pa_hook_slot_free(u->card_init_profile_slot); 509 510 if (u->card_unlink_slot) 511 pa_hook_slot_free(u->card_unlink_slot); 512 513 if (u->profile_available_changed_slot) 514 pa_hook_slot_free(u->profile_available_changed_slot); 515 516 pa_hashmap_free(u->will_need_revert_card_map); 517 518 pa_xfree(u); 519} 520