• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
34 PA_MODULE_AUTHOR("Frédéric Dalleau, Pali Rohár");
35 PA_MODULE_DESCRIPTION("Policy module to make using bluetooth devices out-of-the-box easier");
36 PA_MODULE_VERSION(PACKAGE_VERSION);
37 PA_MODULE_LOAD_ONCE(true);
38 PA_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 card profile (headset role)?> ");
42 
43 static const char* const valid_modargs[] = {
44     "auto_switch",
45     "a2dp_source",
46     "ag",
47     NULL
48 };
49 
50 struct 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 */
source_put_hook_callback(pa_core * c,pa_source * source,void * userdata)65 static 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"))
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 */
sink_put_hook_callback(pa_core * c,pa_sink * sink,void * userdata)106 static 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"))
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 
card_set_profile(struct userdata * u,pa_card * card,bool revert_to_a2dp)144 static 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"))
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 */
switch_profile(pa_card * card,bool revert_to_a2dp,void * userdata)179 static 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 hsp profile */
194         if (!pa_streq(card->active_profile->name, "headset_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 hsp profile */
206         if (pa_streq(card->active_profile->name, "headset_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 */
ignore_output(pa_source_output * source_output,void * userdata)214 static 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 
source_output_count(pa_core * c,void * userdata)243 static 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 */
switch_profile_all(pa_idxset * cards,bool revert_to_a2dp,void * userdata)256 static 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 */
source_output_put_hook_callback(pa_core * c,pa_source_output * source_output,void * userdata)265 static 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 */
source_output_unlink_hook_callback(pa_core * c,pa_source_output * source_output,void * userdata)277 static 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 
card_init_profile_hook_callback(pa_core * c,pa_card * card,void * userdata)292 static 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 
card_unlink_hook_callback(pa_core * c,pa_card * card,void * userdata)320 static 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 
find_best_profile(pa_card * card)327 static 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 
profile_available_hook_callback(pa_core * c,pa_card_profile * profile,void * userdata)345 static 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") || pa_streq(profile->name, "headset_head_unit"))
362         return PA_HOOK_OK;
363 
364     is_active_profile = card->active_profile == profile;
365 
366     if (profile->available == PA_AVAILABLE_YES) {
367         if (is_active_profile)
368             return PA_HOOK_OK;
369 
370         if (card->active_profile->available == PA_AVAILABLE_YES && card->active_profile->priority >= profile->priority)
371             return PA_HOOK_OK;
372 
373         selected_profile = profile;
374     } else {
375         if (!is_active_profile)
376             return PA_HOOK_OK;
377 
378         pa_assert_se((selected_profile = find_best_profile(card)));
379 
380         if (selected_profile == card->active_profile)
381             return PA_HOOK_OK;
382     }
383 
384     pa_log_debug("Setting card '%s' to profile '%s'", card->name, selected_profile->name);
385 
386     if (pa_card_set_profile(card, selected_profile, false) != 0)
387         pa_log_warn("Could not set profile '%s'", selected_profile->name);
388 
389     return PA_HOOK_OK;
390 }
391 
handle_all_profiles(pa_core * core)392 static void handle_all_profiles(pa_core *core) {
393     pa_card *card;
394     uint32_t state;
395 
396     PA_IDXSET_FOREACH(card, core->cards, state) {
397         pa_card_profile *profile;
398         void *state2;
399 
400         PA_HASHMAP_FOREACH(profile, card->profiles, state2)
401             profile_available_hook_callback(core, profile, NULL);
402     }
403 }
404 
pa__init(pa_module * m)405 int pa__init(pa_module *m) {
406     pa_modargs *ma;
407     struct userdata *u;
408 
409     pa_assert(m);
410 
411     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
412         pa_log_error("Failed to parse module arguments");
413         goto fail;
414     }
415 
416     m->userdata = u = pa_xnew0(struct userdata, 1);
417 
418     u->auto_switch = 1;
419 
420     if (pa_modargs_get_value(ma, "auto_switch", NULL)) {
421         bool auto_switch_bool;
422 
423         /* auto_switch originally took a boolean value, let's keep
424          * compatibility with configuration files that still pass a boolean. */
425         if (pa_modargs_get_value_boolean(ma, "auto_switch", &auto_switch_bool) >= 0) {
426             if (auto_switch_bool)
427                 u->auto_switch = 1;
428             else
429                 u->auto_switch = 0;
430 
431         } else if (pa_modargs_get_value_u32(ma, "auto_switch", &u->auto_switch) < 0) {
432             pa_log("Failed to parse auto_switch argument.");
433             goto fail;
434         }
435     }
436 
437     u->enable_a2dp_source = true;
438     if (pa_modargs_get_value_boolean(ma, "a2dp_source", &u->enable_a2dp_source) < 0) {
439         pa_log("Failed to parse a2dp_source argument.");
440         goto fail;
441     }
442 
443     u->enable_ag = true;
444     if (pa_modargs_get_value_boolean(ma, "ag", &u->enable_ag) < 0) {
445         pa_log("Failed to parse ag argument.");
446         goto fail;
447     }
448 
449     u->will_need_revert_card_map = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
450 
451     u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL,
452                                          (pa_hook_cb_t) source_put_hook_callback, u);
453 
454     u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL,
455                                        (pa_hook_cb_t) sink_put_hook_callback, u);
456 
457     if (u->auto_switch) {
458         u->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL,
459                                                     (pa_hook_cb_t) source_output_put_hook_callback, u);
460 
461         u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL,
462                                                        (pa_hook_cb_t) source_output_unlink_hook_callback, u);
463 
464         u->card_init_profile_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_CHOOSE_INITIAL_PROFILE], PA_HOOK_NORMAL,
465                                            (pa_hook_cb_t) card_init_profile_hook_callback, u);
466 
467         u->card_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_UNLINK], PA_HOOK_NORMAL,
468                                            (pa_hook_cb_t) card_unlink_hook_callback, u);
469     }
470 
471     u->profile_available_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_AVAILABLE_CHANGED],
472                                                         PA_HOOK_NORMAL, (pa_hook_cb_t) profile_available_hook_callback, u);
473 
474     handle_all_profiles(m->core);
475 
476     pa_modargs_free(ma);
477     return 0;
478 
479 fail:
480     if (ma)
481         pa_modargs_free(ma);
482     return -1;
483 }
484 
pa__done(pa_module * m)485 void pa__done(pa_module *m) {
486     struct userdata *u;
487 
488     pa_assert(m);
489 
490     if (!(u = m->userdata))
491         return;
492 
493     if (u->source_put_slot)
494         pa_hook_slot_free(u->source_put_slot);
495 
496     if (u->sink_put_slot)
497         pa_hook_slot_free(u->sink_put_slot);
498 
499     if (u->source_output_put_slot)
500         pa_hook_slot_free(u->source_output_put_slot);
501 
502     if (u->source_output_unlink_slot)
503         pa_hook_slot_free(u->source_output_unlink_slot);
504 
505     if (u->card_init_profile_slot)
506         pa_hook_slot_free(u->card_init_profile_slot);
507 
508     if (u->card_unlink_slot)
509         pa_hook_slot_free(u->card_unlink_slot);
510 
511     if (u->profile_available_changed_slot)
512         pa_hook_slot_free(u->profile_available_changed_slot);
513 
514     pa_hashmap_free(u->will_need_revert_card_map);
515 
516     pa_xfree(u);
517 }
518