diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/modules/bluetooth/module-bluetooth-device.c | 297 | ||||
-rw-r--r-- | src/modules/bluetooth/module-bluetooth-discover.c | 23 | ||||
-rw-r--r-- | src/modules/module-suspend-on-idle.c | 3 | ||||
-rw-r--r-- | src/pulsecore/core.h | 2 | ||||
-rw-r--r-- | src/pulsecore/sink.c | 6 | ||||
-rw-r--r-- | src/pulsecore/source.c | 6 |
6 files changed, 264 insertions, 73 deletions
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index d909d70d..94195957 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -43,6 +43,7 @@ #include <pulsecore/rtpoll.h> #include <pulsecore/time-smoother.h> #include <pulsecore/rtclock.h> +#include <pulsecore/namereg.h> #include <modules/dbus-util.h> @@ -71,7 +72,9 @@ PA_MODULE_USAGE( "profile=<a2dp|hsp> " "rate=<sample rate> " "channels=<number of channels> " - "path=<device object path>"); + "path=<device object path> " + "sco_sink=<SCO over PCM sink name> " + "sco_source=<SCO over PCM source name>"); 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,14 @@ struct a2dp_info { uint16_t seq_num; /* Cumulative packet sequence */ }; +struct hsp_info { + pcm_capabilities_t pcm_capabilities; + pa_sink *sco_sink; + pa_source *sco_source; + pa_hook_slot *sink_state_changed_slot; + pa_hook_slot *source_state_changed_slot; +}; + enum profile { PROFILE_A2DP, PROFILE_HSP, @@ -132,6 +145,7 @@ struct userdata { size_t block_size; struct a2dp_info a2dp; + struct hsp_info hsp; pa_dbus_connection *connection; enum profile profile; @@ -143,6 +157,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; @@ -255,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; } @@ -1218,66 +1245,157 @@ 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 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) { - 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; + 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; + + 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_proplist *p; - 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)) { + 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); - u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); - pa_source_new_data_done(&data); + 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); - 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); + 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; } @@ -1285,6 +1403,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"); @@ -1295,17 +1426,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); @@ -1319,6 +1439,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; @@ -1360,6 +1485,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; @@ -1378,6 +1513,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; @@ -1411,16 +1552,18 @@ 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); - shutdown_bt(u); + init_bt(u); if (u->write_memchunk.memblock) { pa_memblock_unref(u->write_memchunk.memblock); @@ -1428,6 +1571,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) @@ -1605,6 +1756,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"); @@ -1705,10 +1868,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); diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c index f9540b32..3f8a7511 100644 --- a/src/modules/bluetooth/module-bluetooth-discover.c +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -42,15 +42,20 @@ PA_MODULE_AUTHOR("Joao Paulo Rechi Vita"); PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers"); PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_USAGE("async=<Asynchronous initialization?>"); +PA_MODULE_USAGE("sco_sink=<name of sink> " + "sco_source=<name of source>" + "async=<Asynchronous initialization?>"); static const char* const valid_modargs[] = { + "sco_sink", + "sco_source", "async", NULL }; struct userdata { pa_module *module; + pa_modargs *ma; pa_core *core; pa_dbus_connection *connection; pa_bluetooth_discovery *discovery; @@ -71,6 +76,16 @@ static void load_module_for_device(struct userdata *u, pa_bluetooth_device *d, p /* Oh, awesome, a new device has shown up and been connected! */ args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path); + + if (pa_modargs_get_value(u->ma, "sco_sink", NULL) && + pa_modargs_get_value(u->ma, "sco_source", NULL)) { + char *tmp; + + tmp = pa_sprintf_malloc("%s sco_sink=\"%s\" sco_source=\"%s\"", args, pa_modargs_get_value(u->ma, "sco_sink", NULL), pa_modargs_get_value(u->ma, "sco_source", NULL)); + 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); @@ -123,13 +138,14 @@ int pa__init(pa_module* m) { } if (pa_modargs_get_value_boolean(ma, "async", &async) < 0) { - pa_log("Failed to parse tsched argument."); + pa_log("Failed to parse async argument."); goto fail; } m->userdata = u = pa_xnew0(struct userdata, 1); u->module = m; u->core = m->core; + u->ma = ma; if (setup_dbus(u) < 0) goto fail; @@ -162,5 +178,8 @@ void pa__done(pa_module* m) { if (u->connection) pa_dbus_connection_unref(u->connection); + if (u->ma) + pa_modargs_free(u->ma); + pa_xfree(u); } diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 5e5e53e7..22d49f76 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -174,6 +174,9 @@ static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_outpu pa_source_output_assert_ref(s); pa_assert(u); + if (!s->source) + return PA_HOOK_OK; + if (pa_source_check_suspend(s->source) <= 0) { struct device_info *d; if ((d = pa_hashmap_get(u->device_infos, s->source))) diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h index aca96bba..53e2d2c3 100644 --- a/src/pulsecore/core.h +++ b/src/pulsecore/core.h @@ -70,6 +70,7 @@ typedef enum pa_core_hook { PA_CORE_HOOK_SINK_INPUT_UNLINK_POST, PA_CORE_HOOK_SINK_INPUT_MOVE_START, PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH, + PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL, PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED, PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED, PA_CORE_HOOK_SINK_INPUT_SET_VOLUME, @@ -81,6 +82,7 @@ typedef enum pa_core_hook { PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST, PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_START, PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH, + PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL, PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED, PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED, PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT, diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index 57725873..eadef809 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -533,8 +533,10 @@ void pa_sink_move_all_fail(pa_queue *q) { pa_assert(q); while ((i = PA_SINK_INPUT(pa_queue_pop(q)))) { - pa_sink_input_unlink(i); - pa_sink_input_unref(i); + if (pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FAIL], i) == PA_HOOK_OK) { + pa_sink_input_unlink(i); + pa_sink_input_unref(i); + } } pa_queue_free(q, NULL, NULL); diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 04c7f8b7..c31c89c3 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -472,8 +472,10 @@ void pa_source_move_all_fail(pa_queue *q) { pa_assert(q); while ((o = PA_SOURCE_OUTPUT(pa_queue_pop(q)))) { - pa_source_output_unlink(o); - pa_source_output_unref(o); + if (pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FAIL], o) == PA_HOOK_OK) { + pa_source_output_unlink(o); + pa_source_output_unref(o); + } } pa_queue_free(q, NULL, NULL); |