--- src/modules/bluetooth/module-bluetooth-device.c | 78 ++++++++++++++++++++- src/modules/bluetooth/module-bluetooth-discover.c | 16 ++++ 2 files changed, 93 insertions(+), 1 deletions(-) diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 7992e12..1d84437 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -79,7 +79,8 @@ PA_MODULE_USAGE( "path=<device object path> " "auto_connect=<automatically connect?> " "sco_sink=<SCO over PCM sink name> " - "sco_source=<SCO over PCM source name>"); + "sco_source=<SCO over PCM source name>" + "auto_loopback=<automatically loopback?>"); /* TODO: not close fd when entering suspend mode in a2dp */ @@ -99,6 +100,7 @@ static const char* const valid_modargs[] = { "auto_connect", "sco_sink", "sco_source", + "auto_loopback", NULL }; @@ -146,12 +148,14 @@ struct userdata { pa_bluetooth_discovery *discovery; pa_bool_t auto_connect; + pa_bool_t auto_loopback; pa_dbus_connection *connection; pa_card *card; pa_sink *sink; pa_source *source; + uint32_t source_loop; pa_thread_mq thread_mq; pa_rtpoll *rtpoll; @@ -203,6 +207,63 @@ enum { static int init_bt(struct userdata *u); static int init_profile(struct userdata *u); +static int loopback_source(struct userdata *u) { + pa_source *defsource; + pa_sink *defsink; + const char *s; + pa_module *m = NULL; + char *args; + + pa_assert(u->core); + pa_assert(u->source); + pa_assert(u->source_loop == PA_IDXSET_INVALID); + + /* Don't want to run during startup or shutdown */ + if (u->core->state != PA_CORE_RUNNING) + return -1; + + /* Don't switch to any internal devices */ + if ((s = pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_BUS))) { + if (pa_streq(s, "pci")) + return -1; + else if (pa_streq(s, "isa")) + return -1; + } + + /* Do not loopback default source over default sink */ + defsource = pa_namereg_get_default_source(u->core); + if (defsource == u->source) + return -1; + + defsink = pa_namereg_get_default_sink(u->core); + if (!defsink) { + pa_log_debug("Cannot find suitable sink for loopback from %s", u->source->name); + return -1; + } + + /* Load module-loopback with default source */ + args = pa_sprintf_malloc("source=\"%s\" sink=\"%s\" source_dont_move=\"true\"", u->source->name, defsink->name); + m = pa_module_load(u->core, "module-loopback", args); + + if (m) { + u->source_loop = m->index; + } else { + pa_log_debug("Failed to loopback source %s with args '%s'", u->source->name, args); + pa_xfree(args); + } + + return 0; +} + +static void loopback_stop(struct userdata *u) { + pa_assert(u->core); + + if (u->source_loop != PA_IDXSET_INVALID) { + pa_module_unload_by_index(u->core, u->source_loop, TRUE); + u->source_loop = PA_IDXSET_INVALID; + } +} + static int service_send(struct userdata *u, const bt_audio_msg_header_t *msg) { ssize_t r; @@ -2573,6 +2634,9 @@ static int start_thread(struct userdata *u) { pa_source_set_rtpoll(u->source, u->rtpoll); pa_source_put(u->source); + if (u->auto_loopback) + loopback_source(u); + if (u->source->set_volume) u->source->set_volume(u->source); } @@ -2632,6 +2696,9 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { return -PA_ERR_IO; } + /* We need to stop loopback before trying to move all other sink inputs/source outputs */ + loopback_stop(u); + if (u->sink) { inputs = pa_sink_move_all_start(u->sink, NULL); @@ -2914,6 +2981,7 @@ int pa__init(pa_module* m) { u->stream_fd = -1; u->sample_spec = m->core->default_sample_spec; u->modargs = ma; + u->source_loop = PA_IDXSET_INVALID; if (pa_modargs_get_value(ma, "sco_sink", NULL) && !(u->hsp.sco_sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_sink", NULL), PA_NAMEREG_SINK))) { @@ -2939,6 +3007,12 @@ int pa__init(pa_module* m) { goto fail; } + u->auto_loopback = FALSE; + if (pa_modargs_get_value_boolean(ma, "auto_loopback", &u->auto_loopback)) { + pa_log("Failed to parse auto_loopback= argument"); + goto fail; + } + channels = u->sample_spec.channels; if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels > PA_CHANNELS_MAX) { @@ -3038,6 +3112,8 @@ void pa__done(pa_module *m) { if (!(u = m->userdata)) return; + loopback_stop(u); + if (u->sink && !USE_SCO_OVER_PCM(u)) pa_sink_unlink(u->sink); diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index e96a4f3..0f01bc3 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -49,6 +49,7 @@ static const char* const valid_modargs[] = { "sco_sink", "sco_source", "async", + "auto_loopback", NULL }; @@ -59,6 +60,7 @@ struct userdata { pa_bluetooth_discovery *discovery; pa_hook_slot *slot; pa_hashmap *hashmap; + pa_bool_t auto_loopback; }; struct module_info { @@ -113,6 +115,13 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const if (d->hfgw_state >= PA_BT_AUDIO_STATE_CONNECTED) args = pa_sprintf_malloc("%s profile=\"hfgw\"", args); + if (u->auto_loopback) { + char *tmp; + tmp = pa_sprintf_malloc("%s auto_loopback=%s", args, u->auto_loopback ? "yes" : "no"); + pa_xfree(args); + args = tmp; + } + pa_log_debug("Loading module-bluetooth-device %s", args); m = pa_module_load(u->module->core, "module-bluetooth-device", args); pa_xfree(args); @@ -149,6 +158,7 @@ int pa__init(pa_module* m) { struct userdata *u; pa_modargs *ma = NULL; pa_bool_t async = FALSE; + pa_bool_t auto_loopback = FALSE; pa_assert(m); @@ -162,10 +172,16 @@ int pa__init(pa_module* m) { goto fail; } + if (pa_modargs_get_value_boolean(ma, "auto_loopback", &auto_loopback)) { + pa_log("Failed to parse auto_loopback= argument"); + goto fail; + } + m->userdata = u = pa_xnew0(struct userdata, 1); u->module = m; u->core = m->core; u->modargs = ma; + u->auto_loopback = auto_loopback; ma = NULL; u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); -- 1.7.5.4