From b35ae7f53116949d0cf5d991883e3b54f2c48aa5 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Mon, 9 Feb 2009 22:11:46 +0200 Subject: bluetooth: reconnect to audio service when switching profile Currently, Bluez audio service crash when reusing the same control socket to switch to different profiles. This typically happen when first switching from HSP to A2DP on dual headsets. --- src/modules/bluetooth/module-bluetooth-device.c | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'src/modules/bluetooth/module-bluetooth-device.c') diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index de6bded2..0067d91d 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1282,6 +1282,19 @@ static int add_source(struct userdata *u) { static int init_bt(struct userdata *u) { pa_assert(u); + /* shutdown bt */ + if (u->stream_fd >= 0) { + pa_close(u->stream_fd); + u->stream_fd = -1; + } + + if (u->service_fd >= 0) { + pa_close(u->service_fd); + u->service_fd = -1; + } + + u->write_type = u->read_type = 0; + /* connect to the bluez audio service */ if ((u->service_fd = bt_audio_service_open()) < 0) { pa_log_error("Couldn't connect to bluetooth audio service"); @@ -1292,17 +1305,6 @@ static int init_bt(struct userdata *u) { return 0; } -static void shutdown_bt(struct userdata *u) { - pa_assert(u); - - if (u->stream_fd <= 0) { - pa_close(u->stream_fd); - u->stream_fd = -1; - } - - u->write_type = u->read_type = 0; -} - static int setup_bt(struct userdata *u) { pa_assert(u); @@ -1417,7 +1419,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { } stop_thread(u); - shutdown_bt(u); + init_bt(u); if (u->write_memchunk.memblock) { pa_memblock_unref(u->write_memchunk.memblock); -- cgit From c8a240cddd23eba83c4d3fc901fe2e42a871823f Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Feb 2009 17:15:41 +0200 Subject: bluetooth: SCO over PCM --- src/modules/bluetooth/module-bluetooth-device.c | 156 +++++++++++++++++------- 1 file changed, 111 insertions(+), 45 deletions(-) (limited to 'src/modules/bluetooth/module-bluetooth-device.c') diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 0067d91d..2302b1d5 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -43,6 +43,7 @@ #include #include #include +#include #include @@ -71,7 +72,9 @@ PA_MODULE_USAGE( "profile= " "rate= " "channels= " - "path="); + "path= " + "sco_sink= " + "sco_source="); static const char* const valid_modargs[] = { "name", @@ -83,6 +86,8 @@ static const char* const valid_modargs[] = { "rate", "channels", "path", + "sco_sink", + "sco_source", NULL }; @@ -98,6 +103,11 @@ struct a2dp_info { uint16_t seq_num; /* Cumulative packet sequence */ }; +struct hsp_info { + pa_sink *sco_sink; + pa_source *sco_source; +}; + enum profile { PROFILE_A2DP, PROFILE_HSP, @@ -132,6 +142,7 @@ struct userdata { size_t block_size; struct a2dp_info a2dp; + struct hsp_info hsp; pa_dbus_connection *connection; enum profile profile; @@ -1215,67 +1226,97 @@ static char *get_name(const char *type, pa_modargs *ma, const char *device_id, p return pa_sprintf_malloc("bluez_%s.%s", type, n); } +#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) + static int add_sink(struct userdata *u) { - pa_sink_new_data data; - pa_bool_t b; - pa_sink_new_data_init(&data); - data.driver = __FILE__; - data.module = u->module; - pa_sink_new_data_set_sample_spec(&data, &u->sample_spec); - pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); - data.card = u->card; - data.name = get_name("sink", u->modargs, u->device->address, &b); - data.namereg_fail = b; + if (USE_SCO_OVER_PCM(u)) { + pa_proplist *p; - u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); - pa_sink_new_data_done(&data); + u->sink = u->hsp.sco_sink; + u->sink->card = u->card; /* FIXME! */ + p = pa_proplist_new(); + pa_proplist_sets(p, "bluetooth.protocol", "sco"); + pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p); + pa_proplist_free(p); - if (!u->sink) { - pa_log_error("Failed to create sink"); - return -1; + } else { + pa_sink_new_data data; + pa_bool_t b; + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = u->module; + pa_sink_new_data_set_sample_spec(&data, &u->sample_spec); + pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); + data.card = u->card; + data.name = get_name("sink", u->modargs, u->device->address, &b); + data.namereg_fail = b; + + u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log_error("Failed to create sink"); + return -1; + } + + u->sink->userdata = u; + u->sink->parent.process_msg = sink_process_msg; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); } - u->sink->userdata = u; - u->sink->parent.process_msg = sink_process_msg; /* u->sink->get_volume = sink_get_volume_cb; */ /* u->sink->set_volume = sink_set_volume_cb; */ - pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - pa_sink_set_rtpoll(u->sink, u->rtpoll); - return 0; } static int add_source(struct userdata *u) { - pa_source_new_data data; - pa_bool_t b; - pa_source_new_data_init(&data); - data.driver = __FILE__; - data.module = u->module; - pa_source_new_data_set_sample_spec(&data, &u->sample_spec); - pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); - data.card = u->card; - data.name = get_name("source", u->modargs, u->device->address, &b); - data.namereg_fail = b; + if (USE_SCO_OVER_PCM(u)) { + pa_proplist *p; - u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); - pa_source_new_data_done(&data); + u->source = u->hsp.sco_source; + u->source->card = u->card; /* FIXME! */ + p = pa_proplist_new(); + pa_proplist_sets(p, "bluetooth.protocol", "sco"); + pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p); + pa_proplist_free(p); - if (!u->source) { - pa_log_error("Failed to create source"); - return -1; + } else { + pa_source_new_data data; + pa_bool_t b; + + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = u->module; + pa_source_new_data_set_sample_spec(&data, &u->sample_spec); + pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); + data.card = u->card; + data.name = get_name("source", u->modargs, u->device->address, &b); + data.namereg_fail = b; + + u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log_error("Failed to create source"); + return -1; + } + + u->source->userdata = u; + u->source->parent.process_msg = source_process_msg; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); } - u->source->userdata = u; - u->source->parent.process_msg = source_process_msg; /* u->source->get_volume = source_get_volume_cb; */ /* u->source->set_volume = source_set_volume_cb; */ - pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); - pa_source_set_rtpoll(u->source, u->rtpoll); - return 0; } @@ -1318,6 +1359,11 @@ static int setup_bt(struct userdata *u) { pa_log_debug("Connection to the device configured"); + if (USE_SCO_OVER_PCM(u)) { + pa_log_debug("Configured to use SCO over PCM"); + return 0; + } + if (setup_stream_fd(u) < 0) return -1; @@ -1377,6 +1423,12 @@ static int start_thread(struct userdata *u) { pa_assert(!u->thread); pa_assert(!u->rtpoll_item); + if (USE_SCO_OVER_PCM(u)) { + pa_sink_ref(u->sink); + pa_source_ref(u->source); + return 0; + } + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd->fd = u->stream_fd; @@ -1410,12 +1462,14 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { if (u->sink) { inputs = pa_sink_move_all_start(u->sink); - pa_sink_unlink(u->sink); + if (!USE_SCO_OVER_PCM(u)) + pa_sink_unlink(u->sink); } if (u->source) { outputs = pa_source_move_all_start(u->source); - pa_source_unlink(u->source); + if (!USE_SCO_OVER_PCM(u)) + pa_source_unlink(u->source); } stop_thread(u); @@ -1604,6 +1658,18 @@ int pa__init(pa_module* m) { u->sample_spec = m->core->default_sample_spec; u->modargs = ma; + 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))) { + pa_log("SCO sink not found"); + goto fail; + } + + if (pa_modargs_get_value(ma, "sco_source", NULL) && + !(u->hsp.sco_source = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sco_source", NULL), PA_NAMEREG_SOURCE))) { + pa_log("SCO source not found"); + goto fail; + } + if (pa_modargs_get_value_u32(ma, "rate", &u->sample_spec.rate) < 0 || u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) { pa_log_error("Failed to get rate from module arguments"); @@ -1704,10 +1770,10 @@ void pa__done(pa_module *m) { if (!(u = m->userdata)) return; - if (u->sink) + if (u->sink && !USE_SCO_OVER_PCM(u)) pa_sink_unlink(u->sink); - if (u->source) + if (u->source && !USE_SCO_OVER_PCM(u)) pa_source_unlink(u->source); stop_thread(u); -- cgit From 452e2b9fa073299e2f047dd4dda51a3d7032a7ff Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Feb 2009 18:17:20 +0200 Subject: bluetooth: suspend SCO state when over PCM --- src/modules/bluetooth/module-bluetooth-device.c | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'src/modules/bluetooth/module-bluetooth-device.c') diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 2302b1d5..c70a79d2 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -106,6 +106,8 @@ struct a2dp_info { struct hsp_info { pa_sink *sco_sink; pa_source *sco_source; + pa_hook_slot *sink_state_changed_slot; + pa_hook_slot *source_state_changed_slot; }; enum profile { @@ -154,6 +156,9 @@ struct userdata { int write_type, read_type; }; +static int init_bt(struct userdata *u); +static int init_profile(struct userdata *u); + static int service_send(int fd, const bt_audio_msg_header_t *msg) { size_t length; ssize_t r; @@ -1228,6 +1233,57 @@ static char *get_name(const char *type, pa_modargs *ma, const char *device_id, p #define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) +static void sco_over_pcm_state_update(struct userdata *u) { + pa_assert(u); + + if (PA_SINK_IS_OPENED(pa_sink_get_state(u->hsp.sco_sink)) || + PA_SOURCE_IS_OPENED(pa_source_get_state(u->hsp.sco_source))) { + + if (u->service_fd > 0) + return; + + pa_log_debug("Resuming SCO over PCM"); + if ((init_bt(u) < 0) || (init_profile(u) < 0)) + pa_log("Can't resume SCO over PCM"); + + } else { + + if (u->service_fd <= 0) + return; + + pa_log_debug("Closing SCO over PCM"); + pa_close(u->service_fd); + u->service_fd = 0; + + } +} + +static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) { + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (s != u->hsp.sco_sink) + return PA_HOOK_OK; + + sco_over_pcm_state_update(u); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct userdata *u) { + pa_assert(c); + pa_source_assert_ref(s); + pa_assert(u); + + if (s != u->hsp.sco_source) + return PA_HOOK_OK; + + sco_over_pcm_state_update(u); + + return PA_HOOK_OK; +} + static int add_sink(struct userdata *u) { if (USE_SCO_OVER_PCM(u)) { @@ -1240,6 +1296,9 @@ static int add_sink(struct userdata *u) { pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p); pa_proplist_free(p); + if (!u->hsp.sink_state_changed_slot) + u->hsp.sink_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_cb, u); + } else { pa_sink_new_data data; pa_bool_t b; @@ -1286,6 +1345,9 @@ static int add_source(struct userdata *u) { pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p); pa_proplist_free(p); + if (!u->hsp.source_state_changed_slot) + u->hsp.source_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u); + } else { pa_source_new_data data; pa_bool_t b; @@ -1405,6 +1467,16 @@ static void stop_thread(struct userdata *u) { u->rtpoll_item = NULL; } + if (u->hsp.sink_state_changed_slot) { + pa_hook_slot_free(u->hsp.sink_state_changed_slot); + u->hsp.sink_state_changed_slot = NULL; + } + + if (u->hsp.source_state_changed_slot) { + pa_hook_slot_free(u->hsp.source_state_changed_slot); + u->hsp.source_state_changed_slot = NULL; + } + if (u->sink) { pa_sink_unref(u->sink); u->sink = NULL; -- cgit From cac0f9ef2b91462778600821f456248efffb2517 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 3 Feb 2009 18:43:54 +0200 Subject: bluetooth: export nrec --- src/modules/bluetooth/module-bluetooth-device.c | 41 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) (limited to 'src/modules/bluetooth/module-bluetooth-device.c') diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index c70a79d2..fa9d6bf3 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -104,6 +104,7 @@ struct a2dp_info { }; struct hsp_info { + pcm_capabilities_t pcm_capabilities; pa_sink *sco_sink; pa_source *sco_source; pa_hook_slot *sink_state_changed_slot; @@ -271,23 +272,33 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp * return -1; } - if (u->profile != PROFILE_A2DP) - return 0; + if (u->profile == PROFILE_HSP) { + if (bytes_left <= 0 || codec->length != sizeof(u->hsp.pcm_capabilities)) + return -1; - while (bytes_left > 0) { - if (codec->type == BT_A2DP_CODEC_SBC) - break; + pa_assert(codec->type == BT_HFP_CODEC_PCM); - bytes_left -= codec->length; - codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length); + memcpy(&u->hsp.pcm_capabilities, codec, sizeof(u->hsp.pcm_capabilities)); } - if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities)) - return -1; + if (u->profile == PROFILE_A2DP) { + + while (bytes_left > 0) { + if (codec->type == BT_A2DP_CODEC_SBC) + break; + + bytes_left -= codec->length; + codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length); + } + + if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities)) + return -1; + + pa_assert(codec->type == BT_A2DP_CODEC_SBC); - pa_assert(codec->type == BT_A2DP_CODEC_SBC); + memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities)); + } - memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities)); return 0; } @@ -1334,10 +1345,9 @@ static int add_sink(struct userdata *u) { } static int add_source(struct userdata *u) { + pa_proplist *p; if (USE_SCO_OVER_PCM(u)) { - pa_proplist *p; - u->source = u->hsp.sco_source; u->source->card = u->card; /* FIXME! */ p = pa_proplist_new(); @@ -1379,6 +1389,11 @@ static int add_source(struct userdata *u) { /* u->source->get_volume = source_get_volume_cb; */ /* u->source->set_volume = source_set_volume_cb; */ + p = pa_proplist_new(); + pa_proplist_sets(p, "bluetooth.nrec", pa_yes_no(u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC)); + pa_proplist_update(u->source->proplist, PA_UPDATE_MERGE, p); + pa_proplist_free(p); + return 0; } -- cgit From cce4359831b5ff02f63e9748671fa744d500d27f Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Tue, 10 Feb 2009 23:48:37 +0200 Subject: bluetooth: reinitialize the sample spec when switching profile When doing init_profile(), the value u->sample_spec is modified to the one which has been last configured. In case of HSP, it will be 8kHz. Later on, when setting the profile to A2DP, it picks up the lower rate available which match with current u->sample_spec. In my case, it would be 16kHz. To circunvent the issue, I decided to reinitialize the u->sample_spec to default value with user module argument requested rate. --- src/modules/bluetooth/module-bluetooth-device.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/modules/bluetooth/module-bluetooth-device.c') diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index fa9d6bf3..666dc6a7 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -1568,6 +1568,14 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { } u->profile = *d; + + /* Reinitialize the sample spec to default with module argument rate */ + u->sample_spec = u->module->core->default_sample_spec; + if (pa_modargs_get_value_u32(u->modargs, "rate", &u->sample_spec.rate) < 0 || + u->sample_spec.rate <= 0 || u->sample_spec.rate > PA_RATE_MAX) { + u->sample_spec = u->module->core->default_sample_spec; + } + init_profile(u); if (u->sink || u->source) -- cgit