#include <linux/sockios.h>
#include <arpa/inet.h>
-#include <pulse/i18n.h>
#include <pulse/rtclock.h>
#include <pulse/sample.h>
#include <pulse/timeval.h>
#include <pulse/xmalloc.h>
+#include <pulsecore/i18n.h>
#include <pulsecore/module.h>
#include <pulsecore/modargs.h>
#include <pulsecore/core-rtclock.h>
#include <pulsecore/time-smoother.h>
#include <pulsecore/namereg.h>
#include <pulsecore/dbus-shared.h>
-#include <pulsecore/llist.h>
#include "module-bluetooth-device-symdef.h"
#include "ipc.h"
void (*sco_source_set_volume)(pa_source *s);
pa_hook_slot *sink_state_changed_slot;
pa_hook_slot *source_state_changed_slot;
+ pa_hook_slot *nrec_changed_slot;
};
+struct bluetooth_msg {
+ pa_msgobject parent;
+ pa_card *card;
+};
+
+typedef struct bluetooth_msg bluetooth_msg;
+PA_DEFINE_PRIVATE_CLASS(bluetooth_msg, pa_msgobject);
+#define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
+
struct userdata {
pa_core *core;
pa_module *module;
pa_rtpoll *rtpoll;
pa_rtpoll_item *rtpoll_item;
pa_thread *thread;
+ bluetooth_msg *msg;
uint64_t read_index, write_index;
pa_usec_t started_at;
pa_bool_t filter_added;
};
+enum {
+ BLUETOOTH_MESSAGE_IO_THREAD_FAILED,
+ BLUETOOTH_MESSAGE_MAX
+};
+
#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
return (r < 0 || !failed) ? r : -1;
}
+/* Called from main thread context */
+static int device_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ struct bluetooth_msg *u = BLUETOOTH_MSG(obj);
+
+ switch (code) {
+ case BLUETOOTH_MESSAGE_IO_THREAD_FAILED: {
+ if (u->card->module->unload_requested)
+ break;
+
+ pa_log_debug("Switching the profile to off due to IO thread failure.");
+
+ if (pa_card_set_profile(u->card, "off", FALSE) < 0)
+ pa_log_debug("Failed to switch profile to off");
+ break;
+ }
+ }
+ return 0;
+}
+
/* Run from IO thread */
static int hsp_process_render(struct userdata *u) {
int ret = 0;
void *d;
ssize_t l;
size_t to_write, to_decode;
- unsigned frame_count;
a2dp_prepare_buffer(u);
d = (uint8_t*) d + written;
to_write -= written;
-
- frame_count++;
}
memchunk.length -= to_write;
pollfd->events = (short) (((u->sink && PA_SINK_IS_LINKED(u->sink->thread_info.state) && !writable) ? POLLOUT : 0) |
(u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state) ? POLLIN : 0));
- if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
+ if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) {
+ pa_log_debug("pa_rtpoll_run failed with: %d", ret);
goto fail;
-
- if (ret == 0)
+ }
+ if (ret == 0) {
+ pa_log_debug("IO thread shutdown requested, stopping cleanly");
+ if (u->transport)
+ bt_transport_release(u);
+ else
+ stop_stream_fd(u);
goto finish;
+ }
pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL;
fail:
/* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */
pa_log_debug("IO thread failed");
- pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u->msg), BLUETOOTH_MESSAGE_IO_THREAD_FAILED, NULL, 0, NULL, NULL);
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
finish:
pa_source_volume_changed(u->source, &v);
}
}
- } else if (dbus_message_is_signal(m, "org.bluez.MediaTransport", "PropertyChanged")) {
- DBusMessageIter arg_i;
- pa_bluetooth_transport *t;
- pa_bool_t nrec;
-
- t = (pa_bluetooth_transport *) pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
- pa_assert(t);
+ } else if (dbus_message_is_signal(m, "org.bluez.HandsfreeGateway", "PropertyChanged")) {
+ const char *key;
+ DBusMessageIter iter;
+ DBusMessageIter variant;
+ pa_bt_audio_state_t state = PA_BT_AUDIO_STATE_INVALID;
- if (!dbus_message_iter_init(m, &arg_i)) {
+ if (!dbus_message_iter_init(m, &iter)) {
pa_log("Failed to parse PropertyChanged: %s", err.message);
goto fail;
}
- nrec = t->nrec;
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ pa_log("Property name not a string.");
+ goto fail;
+ }
+
+ dbus_message_iter_get_basic(&iter, &key);
- if (pa_bluetooth_transport_parse_property(t, &arg_i) < 0)
+ if (!dbus_message_iter_next(&iter)) {
+ pa_log("Property value missing");
goto fail;
+ }
- if (nrec != t->nrec) {
- pa_log_debug("dbus: property 'NREC' changed to value '%s'", t->nrec ? "True" : "False");
- pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+ dbus_message_iter_recurse(&iter, &variant);
+
+ if (dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_STRING) {
+ const char *value;
+ dbus_message_iter_get_basic(&variant, &value);
+
+ if (pa_streq(key, "State")) {
+ pa_log_debug("dbus: HSHFAG property 'State' changed to value '%s'", value);
+ state = pa_bt_audio_state_from_string(value);
+ }
+ }
+
+ switch(state) {
+ case PA_BT_AUDIO_STATE_INVALID:
+ case PA_BT_AUDIO_STATE_DISCONNECTED:
+ case PA_BT_AUDIO_STATE_CONNECTED:
+ case PA_BT_AUDIO_STATE_CONNECTING:
+ goto fail;
+
+ case PA_BT_AUDIO_STATE_PLAYING:
+ if (u->card) {
+ pa_log_debug("Changing profile to hfgw");
+ if (pa_card_set_profile(u->card, "hfgw", FALSE) < 0)
+ pa_log("Failed to change profile to hfgw");
+ }
+ break;
}
}
pa_assert(u->source == s);
pa_assert(u->profile == PROFILE_HSP);
- gain = (pa_cvolume_max(&s->volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM;
+ gain = (pa_cvolume_max(&s->real_volume) * HSP_MAX_GAIN) / PA_VOLUME_NORM;
if (gain > HSP_MAX_GAIN)
gain = HSP_MAX_GAIN;
if (volume < PA_VOLUME_NORM)
volume++;
- pa_cvolume_set(&s->volume, u->sample_spec.channels, volume);
+ pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain"));
pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID));
return pa_sprintf_malloc("bluez_%s.%s", type, n);
}
-static int sco_over_pcm_state_update(struct userdata *u) {
+static int sco_over_pcm_state_update(struct userdata *u, pa_bool_t changed) {
pa_assert(u);
pa_assert(USE_SCO_OVER_PCM(u));
if (u->transport)
return bt_transport_acquire(u, TRUE);
- else
- return start_stream_fd(u);
- } else {
+ return start_stream_fd(u);
+ }
+
+ if (changed) {
if (u->service_fd < 0 && u->stream_fd < 0)
return 0;
pa_close(u->service_fd);
u->service_fd = -1;
}
-
- return 0;
}
+
+ return 0;
}
static pa_hook_result_t sink_state_changed_cb(pa_core *c, pa_sink *s, struct userdata *u) {
if (s != u->hsp.sco_sink)
return PA_HOOK_OK;
- sco_over_pcm_state_update(u);
+ sco_over_pcm_state_update(u, TRUE);
return PA_HOOK_OK;
}
if (s != u->hsp.sco_source)
return PA_HOOK_OK;
- sco_over_pcm_state_update(u);
+ sco_over_pcm_state_update(u, TRUE);
+
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t nrec_changed_cb(pa_bluetooth_transport *t, void *call_data, struct userdata *u) {
+ pa_proplist *p;
+
+ pa_assert(t);
+ pa_assert(u);
+
+ p = pa_proplist_new();
+ pa_proplist_sets(p, "bluetooth.nrec", t->nrec ? "1" : "0");
+ pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p);
+ pa_proplist_free(p);
return PA_HOOK_OK;
}
+static void connect_ports(struct userdata *u, void *sink_or_source_new_data, pa_direction_t direction) {
+ union {
+ pa_sink_new_data *sink_new_data;
+ pa_source_new_data *source_new_data;
+ } data;
+ pa_device_port *port;
+
+ if (direction == PA_DIRECTION_OUTPUT) {
+ data.sink_new_data = sink_or_source_new_data;
+ data.sink_new_data->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ } else {
+ data.source_new_data = sink_or_source_new_data;
+ data.source_new_data->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ }
+
+ switch (u->profile) {
+ case PROFILE_A2DP:
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-output"));
+ pa_assert_se(pa_hashmap_put(data.sink_new_data->ports, port->name, port) >= 0);
+ pa_device_port_ref(port);
+ break;
+
+ case PROFILE_A2DP_SOURCE:
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-input"));
+ pa_assert_se(pa_hashmap_put(data.source_new_data->ports, port->name, port) >= 0);
+ pa_device_port_ref(port);
+ break;
+
+ case PROFILE_HSP:
+ if (direction == PA_DIRECTION_OUTPUT) {
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-output"));
+ pa_assert_se(pa_hashmap_put(data.sink_new_data->ports, port->name, port) >= 0);
+ } else {
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-input"));
+ pa_assert_se(pa_hashmap_put(data.source_new_data->ports, port->name, port) >= 0);
+ }
+ pa_device_port_ref(port);
+ break;
+
+ case PROFILE_HFGW:
+ if (direction == PA_DIRECTION_OUTPUT) {
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-output"));
+ pa_assert_se(pa_hashmap_put(data.sink_new_data->ports, port->name, port) >= 0);
+ } else {
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-input"));
+ pa_assert_se(pa_hashmap_put(data.source_new_data->ports, port->name, port) >= 0);
+ }
+ pa_device_port_ref(port);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+}
+
/* Run from main thread */
static int add_sink(struct userdata *u) {
char *k;
pa_sink_new_data_done(&data);
return -1;
}
+ connect_ports(u, &data, PA_DIRECTION_OUTPUT);
- u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY | (u->profile == PROFILE_HSP ? PA_SINK_HW_VOLUME_CTRL : 0));
+ u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
if (!u->sink) {
}
if (u->profile == PROFILE_HSP) {
- u->sink->set_volume = sink_set_volume_cb;
+ pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
u->sink->n_volume_steps = 16;
k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
return -1;
}
- u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY | (u->profile == PROFILE_HSP ? PA_SOURCE_HW_VOLUME_CTRL : 0));
+ connect_ports(u, &data, PA_DIRECTION_INPUT);
+ u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
pa_source_new_data_done(&data);
if (!u->source) {
if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
if (u->transport) {
- const pa_bluetooth_transport *t;
+ pa_bluetooth_transport *t;
t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport);
pa_assert(t);
pa_proplist_sets(u->source->proplist, "bluetooth.nrec", t->nrec ? "1" : "0");
+
+ if (!u->hsp.nrec_changed_slot)
+ u->hsp.nrec_changed_slot = pa_hook_connect(&t->hooks[PA_BLUETOOTH_TRANSPORT_HOOK_NREC_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) nrec_changed_cb, u);
} else
pa_proplist_sets(u->source->proplist, "bluetooth.nrec", (u->hsp.pcm_capabilities.flags & BT_PCM_FLAG_NREC) ? "1" : "0");
}
if (u->profile == PROFILE_HSP) {
- u->source->set_volume = source_set_volume_cb;
+ pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
u->source->n_volume_steps = 16;
k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->source);
u->hsp.source_state_changed_slot = NULL;
}
+ if (u->hsp.nrec_changed_slot) {
+ pa_hook_slot_free(u->hsp.nrec_changed_slot);
+ u->hsp.nrec_changed_slot = NULL;
+ }
+
if (u->sink) {
if (u->profile == PROFILE_HSP) {
k = pa_sprintf_malloc("bluetooth-device@%p", (void*) u->sink);
pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
if (USE_SCO_OVER_PCM(u)) {
- if (sco_over_pcm_state_update(u) < 0) {
+ if (sco_over_pcm_state_update(u, FALSE) < 0) {
char *k;
if (u->sink) {
pa_assert(u);
pa_assert(USE_SCO_OVER_PCM(u));
- u->hsp.sco_sink->set_volume = u->hsp.sco_sink_set_volume;
- u->hsp.sco_source->set_volume = u->hsp.sco_source_set_volume;
+ pa_sink_set_set_volume_callback(u->hsp.sco_sink, u->hsp.sco_sink_set_volume);
+ pa_source_set_set_volume_callback(u->hsp.sco_source, u->hsp.sco_source_set_volume);
}
/* Run from main thread */
pa_log_warn("A2DP is not connected, refused to switch profile");
return -PA_ERR_IO;
}
- else if (device->hfgw_state <= PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW) {
+ else if (device->hfgw_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW) {
pa_log_warn("HandsfreeGateway is not connected, refused to switch profile");
return -PA_ERR_IO;
}
}
stop_thread(u);
+
+ if (u->profile != PROFILE_OFF && u->transport) {
+ bt_transport_release(u);
+ pa_xfree(u->transport);
+ u->transport = NULL;
+ }
+
shutdown_bt(u);
if (USE_SCO_OVER_PCM(u))
return 0;
}
+static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_new_data, pa_card_profile *profile) {
+ pa_device_port *port;
+ enum profile *d;
+
+ d = PA_CARD_PROFILE_DATA(profile);
+
+ switch (*d) {
+ case PROFILE_A2DP:
+ pa_assert_se(port = pa_device_port_new(u->core, "a2dp-output", _("Bluetooth High Quality (A2DP)"), 0));
+ pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+ port->is_output = 1;
+ port->is_input = 0;
+ port->priority = profile->priority * 100;
+ pa_hashmap_put(port->profiles, profile->name, profile);
+ break;
+
+ case PROFILE_A2DP_SOURCE:
+ pa_assert_se(port = pa_device_port_new(u->core, "a2dp-input", _("Bluetooth High Quality (A2DP)"), 0));
+ pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+ port->is_output = 0;
+ port->is_input = 1;
+ port->priority = profile->priority * 100;
+ pa_hashmap_put(port->profiles, profile->name, profile);
+ break;
+
+ case PROFILE_HSP:
+ pa_assert_se(port = pa_device_port_new(u->core, "hsp-output", _("Bluetooth Telephony (HSP/HFP)"), 0));
+ pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+ port->is_output = 1;
+ port->is_input = 0;
+ port->priority = profile->priority * 100;
+ pa_hashmap_put(port->profiles, profile->name, profile);
+
+ pa_assert_se(port = pa_device_port_new(u->core, "hsp-input", _("Bluetooth Telephony (HSP/HFP)"), 0));
+ pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+ port->is_output = 0;
+ port->is_input = 1;
+ port->priority = profile->priority * 100;
+ pa_hashmap_put(port->profiles, profile->name, profile);
+ break;
+
+ case PROFILE_HFGW:
+ pa_assert_se(port = pa_device_port_new(u->core, "hfgw-output", _("Bluetooth Handsfree Gateway"), 0));
+ pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+ port->is_output = 1;
+ port->is_input = 0;
+ port->priority = profile->priority * 100;
+ pa_hashmap_put(port->profiles, profile->name, profile);
+
+ pa_assert_se(port = pa_device_port_new(u->core, "hfgw-input", _("Bluetooth Handsfree Gateway"), 0));
+ pa_assert_se(pa_hashmap_put(card_new_data->ports, port->name, port) >= 0);
+ port->is_output = 0;
+ port->is_input = 1;
+ port->priority = profile->priority * 100;
+ pa_hashmap_put(port->profiles, profile->name, profile);
+ break;
+
+ default:
+ pa_assert_not_reached();
+ }
+
+}
+
/* Run from main thread */
static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
pa_card_new_data data;
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_A2DP;
+ create_ports_for_profile(u, &data, p);
pa_hashmap_put(data.profiles, p->name, p);
}
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_A2DP_SOURCE;
+ create_ports_for_profile(u, &data, p);
pa_hashmap_put(data.profiles, p->name, p);
}
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_HSP;
+ create_ports_for_profile(u, &data, p);
pa_hashmap_put(data.profiles, p->name, p);
}
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_HFGW;
+ create_ports_for_profile(u, &data, p);
pa_hashmap_put(data.profiles, p->name, p);
}
struct userdata *u;
const char *address, *path;
DBusError err;
- char *mike, *speaker, *transport;
+ char *mike, *speaker;
const pa_bluetooth_device *device;
pa_assert(m);
if (add_card(u, device) < 0)
goto fail;
+ if (!(u->msg = pa_msgobject_new(bluetooth_msg)))
+ goto fail;
+
+ u->msg->parent.process_msg = device_process_msg;
+ u->msg->card = u->card;
+
if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) {
pa_log_error("Failed to add filter function");
goto fail;
speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path);
mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path);
- transport = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'");
if (pa_dbus_add_matches(
pa_dbus_connection_get(u->connection), &err,
speaker,
mike,
- transport,
+ "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
NULL) < 0) {
pa_xfree(speaker);
pa_xfree(mike);
- pa_xfree(transport);
pa_log("Failed to add D-Bus matches: %s", err.message);
goto fail;
pa_xfree(speaker);
pa_xfree(mike);
- pa_xfree(transport);
/* Connect to the BT service */
init_bt(u);
speaker = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='SpeakerGainChanged',path='%s'", u->path);
mike = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='MicrophoneGainChanged',path='%s'", u->path);
- pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), speaker, mike, NULL);
+ pa_dbus_remove_matches(pa_dbus_connection_get(u->connection), speaker, mike,
+ "type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
+ NULL);
pa_xfree(speaker);
pa_xfree(mike);
pa_dbus_connection_unref(u->connection);
}
+ if (u->msg)
+ pa_xfree(u->msg);
+
if (u->card)
pa_card_free(u->card);