}
/* from IO thread, except in SCO over PCM */
+static void bt_transport_config_mtu(struct userdata *u) {
+ /* Calculate block sizes */
+ if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
+ u->read_block_size = u->read_link_mtu;
+ u->write_block_size = u->write_link_mtu;
+ } else {
+ u->read_block_size =
+ (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / u->a2dp.frame_length * u->a2dp.codesize;
+
+ u->write_block_size =
+ (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+ / u->a2dp.frame_length * u->a2dp.codesize;
+ }
+
+ if (USE_SCO_OVER_PCM(u))
+ return;
+
+ if (u->sink) {
+ pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
+ pa_sink_set_fixed_latency_within_thread(u->sink,
+ (u->profile == PROFILE_A2DP ?
+ FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
+ pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+ }
+
+ if (u->source)
+ pa_source_set_fixed_latency_within_thread(u->source,
+ (u->profile == PROFILE_A2DP_SOURCE ?
+ FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
+ pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+}
+
+/* from IO thread, except in SCO over PCM */
-static int setup_stream(struct userdata *u) {
+static void setup_stream(struct userdata *u) {
struct pollfd *pollfd;
int one;
+ bt_transport_config_mtu(u);
+
pa_make_fd_nonblock(u->stream_fd);
pa_make_socket_low_delay(u->stream_fd);
10,
pa_rtclock_now(),
TRUE);
+}
- return 0;
+static bool bt_transport_is_acquired(struct userdata *u) {
+ if (u->accesstype == NULL) {
+ pa_assert(u->stream_fd < 0);
+ return FALSE;
+ } else {
+ pa_assert(u->stream_fd >= 0);
+ return TRUE;
+ }
}
static void bt_transport_release(struct userdata *u) {
const pa_bluetooth_transport *t;
/* Ignore if already released */
- if (!u->accesstype)
+ if (!bt_transport_is_acquired(u))
return;
pa_log_debug("Releasing transport %s", u->transport);
const char *accesstype = "rw";
const pa_bluetooth_transport *t;
- if (u->accesstype) {
+ if (u->transport == NULL) {
+ pa_log("Transport no longer available.");
+ return -1;
+ }
+
+ if (bt_transport_is_acquired(u)) {
if (start)
goto done;
return 0;
done:
pa_log_info("Transport %s resuming", u->transport);
- return setup_stream(u);
+ setup_stream(u);
+
+ return 0;
}
/* Run from IO thread */
switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
case PA_SINK_SUSPENDED:
- pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+ /* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */
+ if (!PA_SINK_IS_OPENED(u->sink->thread_info.state))
+ break;
/* Stop the device if the source is suspended as well */
if (!u->source || u->source->state == PA_SOURCE_SUSPENDED)
switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) {
case PA_SOURCE_SUSPENDED:
- pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));
+ /* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
+ if (!PA_SOURCE_IS_OPENED(u->source->thread_info.state))
+ break;
/* Stop the device if the sink is suspended as well */
if (!u->sink || u->sink->state == PA_SINK_SUSPENDED)
pa_log_debug("IO thread shutting down");
}
+static pa_bt_audio_state_t parse_state_property_change(DBusMessage *m) {
+ DBusMessageIter iter;
+ DBusMessageIter variant;
+ const char *key;
+ const char *value;
+ pa_bt_audio_state_t state;
+
+ if (!dbus_message_iter_init(m, &iter)) {
+ pa_log("Failed to parse PropertyChanged");
+ return PA_BT_AUDIO_STATE_INVALID;
+ }
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) {
+ pa_log("Property name not a string");
+ return PA_BT_AUDIO_STATE_INVALID;
+ }
+
+ dbus_message_iter_get_basic(&iter, &key);
+
+ if (!pa_streq(key, "State"))
+ return PA_BT_AUDIO_STATE_INVALID;
+
+ if (!dbus_message_iter_next(&iter)) {
+ pa_log("Property value missing");
+ return PA_BT_AUDIO_STATE_INVALID;
+ }
+
+ dbus_message_iter_recurse(&iter, &variant);
+
+ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) {
+ pa_log("Property value not a string");
+ return PA_BT_AUDIO_STATE_INVALID;
+ }
+
+ dbus_message_iter_get_basic(&variant, &value);
+
+ pa_log_debug("dbus: %s property 'State' changed to value '%s'", dbus_message_get_interface(m), value);
+
+ state = pa_bt_audio_state_from_string(value);
+
+ if (state == PA_BT_AUDIO_STATE_INVALID)
+ pa_log("Unexpected value for property 'State': '%s'", value);
+
+ return state;
+}
+
+static pa_port_available_t audio_state_to_availability(pa_bt_audio_state_t state) {
+ if (state < PA_BT_AUDIO_STATE_CONNECTED)
+ return PA_PORT_AVAILABLE_NO;
+ else if (state >= PA_BT_AUDIO_STATE_PLAYING)
+ return PA_PORT_AVAILABLE_YES;
+ else
+ return PA_PORT_AVAILABLE_UNKNOWN;
+}
+
/* Run from main thread */
static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) {
DBusError err;
struct userdata *u;
+ bool acquire = FALSE;
+ bool release = FALSE;
pa_assert(bus);
pa_assert(m);
}
}
} 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, &iter)) {
- pa_log("Failed to parse PropertyChanged: %s", err.message);
- goto fail;
- }
-
- 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 (!dbus_message_iter_next(&iter)) {
- pa_log("Property value missing");
- goto fail;
- }
-
- 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);
- }
- }
+ pa_bt_audio_state_t state = parse_state_property_change(m);
switch(state) {
case PA_BT_AUDIO_STATE_INVALID:
}
break;
}
+
+ if (state != PA_BT_AUDIO_STATE_INVALID) {
+ pa_device_port *port;
+ pa_port_available_t available = audio_state_to_availability(state);
+
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-output"));
+ pa_device_port_set_available(port, available);
+
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hfgw-input"));
+ pa_device_port_set_available(port, available);
+
+ acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HFGW);
+ release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HFGW);
+ }
+ } else if (dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged")) {
+ pa_bt_audio_state_t state = parse_state_property_change(m);
+
+ if (state != PA_BT_AUDIO_STATE_INVALID) {
+ pa_device_port *port;
+ pa_port_available_t available = audio_state_to_availability(state);
+
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-output"));
+ pa_device_port_set_available(port, available);
+
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "hsp-input"));
+ pa_device_port_set_available(port, available);
+
+ acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HSP);
+ release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_HSP);
+ }
+ } else if (dbus_message_is_signal(m, "org.bluez.AudioSource", "PropertyChanged")) {
+ pa_bt_audio_state_t state = parse_state_property_change(m);
+
+ if (state != PA_BT_AUDIO_STATE_INVALID) {
+ pa_device_port *port;
+ pa_port_available_t available = audio_state_to_availability(state);
+
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-input"));
+ pa_device_port_set_available(port, available);
+
+ acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP_SOURCE);
+ release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP_SOURCE);
+ }
+ } else if (dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged")) {
+ pa_bt_audio_state_t state = parse_state_property_change(m);
+
+ if (state != PA_BT_AUDIO_STATE_INVALID) {
+ pa_device_port *port;
+ pa_port_available_t available = audio_state_to_availability(state);
+
+ pa_assert_se(port = pa_hashmap_get(u->card->ports, "a2dp-output"));
+ pa_device_port_set_available(port, available);
+
+ acquire = (available == PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP);
+ release = (available != PA_PORT_AVAILABLE_YES && u->profile == PROFILE_A2DP);
+ }
+ }
+
+ if (acquire)
+ if (bt_transport_acquire(u, FALSE) >= 0) {
+ if (u->source)
+ pa_source_suspend(u->source, FALSE, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+
+ if (u->sink)
+ pa_sink_suspend(u->sink, FALSE, PA_SUSPEND_IDLE|PA_SUSPEND_USER);
+ }
+
+ if (release && bt_transport_is_acquired(u)) {
+ /* FIXME: this release is racy, since the audio stream might have
+ been set up again in the meantime (but not processed yet by PA).
+ BlueZ should probably release the transport automatically, and
+ in that case we would just mark the transport as released */
+
+ /* Remote side closed the stream so we consider it PA_SUSPEND_USER */
+ if (u->source)
+ pa_source_suspend(u->source, TRUE, PA_SUSPEND_USER);
+
+ if (u->sink)
+ pa_sink_suspend(u->sink, TRUE, PA_SUSPEND_USER);
}
fail:
}
}
+static const char *profile_to_string(enum profile profile) {
+ switch(profile) {
+ case PROFILE_A2DP:
+ return "a2dp";
+ case PROFILE_A2DP_SOURCE:
+ return "a2dp_source";
+ case PROFILE_HSP:
+ return "hsp";
+ case PROFILE_HFGW:
+ return "hfgw";
+ default:
+ pa_assert_not_reached();
+ }
+}
+
+static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
+ return 0;
+}
+
+static int source_set_port_cb(pa_source *s, pa_device_port *p) {
+ return 0;
+}
+
/* Run from main thread */
static int add_sink(struct userdata *u) {
char *k;
u->sink = u->hsp.sco_sink;
p = pa_proplist_new();
- pa_proplist_sets(p, "bluetooth.protocol", "sco");
+ pa_proplist_sets(p, "bluetooth.protocol", profile_to_string(u->profile));
pa_proplist_update(u->sink->proplist, PA_UPDATE_MERGE, p);
pa_proplist_free(p);
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");
+ pa_proplist_sets(data.proplist, "bluetooth.protocol", profile_to_string(u->profile));
if (u->profile == PROFILE_HSP)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
u->sink->userdata = u;
u->sink->parent.process_msg = sink_process_msg;
-
- pa_sink_set_max_request(u->sink, u->write_block_size);
- pa_sink_set_fixed_latency(u->sink,
- (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
- pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
+ u->sink->set_port = sink_set_port_cb;
}
if (u->profile == PROFILE_HSP) {
if (USE_SCO_OVER_PCM(u)) {
u->source = u->hsp.sco_source;
- pa_proplist_sets(u->source->proplist, "bluetooth.protocol", "hsp");
+ pa_proplist_sets(u->source->proplist, "bluetooth.protocol", profile_to_string(u->profile));
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);
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_SOURCE ? "a2dp_source" : "hsp");
- if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW))
+ pa_proplist_sets(data.proplist, "bluetooth.protocol", profile_to_string(u->profile));
+ if (u->profile == PROFILE_HSP)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
u->source->userdata = u;
u->source->parent.process_msg = source_process_msg;
-
- pa_source_set_fixed_latency(u->source,
- (u->profile == PROFILE_A2DP_SOURCE ? FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_HSP) +
- pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
+ u->source->set_port = source_set_port_cb;
}
if ((u->profile == PROFILE_HSP) || (u->profile == PROFILE_HFGW)) {
return 0;
}
-static int bt_transport_config_a2dp(struct userdata *u) {
+static void bt_transport_config_a2dp(struct userdata *u) {
const pa_bluetooth_transport *t;
struct a2dp_info *a2dp = &u->a2dp;
a2dp_sbc_t *config;
a2dp->codesize = sbc_get_codesize(&a2dp->sbc);
a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc);
- u->read_block_size =
- (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / a2dp->frame_length * a2dp->codesize;
-
- u->write_block_size =
- (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / a2dp->frame_length * a2dp->codesize;
-
pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool);
-
- return 0;
}
-static int bt_transport_config(struct userdata *u) {
+static void bt_transport_config(struct userdata *u) {
if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) {
- u->read_block_size = u->read_link_mtu;
- u->write_block_size = u->write_link_mtu;
u->sample_spec.format = PA_SAMPLE_S16LE;
u->sample_spec.channels = 1;
u->sample_spec.rate = 8000;
- return 0;
- }
-
- return bt_transport_config_a2dp(u);
+ } else
+ bt_transport_config_a2dp(u);
}
/* Run from main thread */
if (bt_transport_acquire(u, FALSE) < 0)
return -1;
- return bt_transport_config(u);
+ bt_transport_config(u);
+
+ return 0;
}
/* Run from main thread */
if (device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) {
pa_log_warn("HSP is not connected, refused to switch profile");
return -PA_ERR_IO;
- }
- else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) {
- pa_log_warn("A2DP is not connected, refused to switch profile");
+ } else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) {
+ pa_log_warn("A2DP Sink 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->audio_source_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP_SOURCE) {
+ pa_log_warn("A2DP Source is not connected, refused to switch profile");
+ return -PA_ERR_IO;
+ } 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;
}
return 0;
}
-static void create_ports_for_profile(struct userdata *u, pa_card_new_data *card_new_data, pa_card_profile *profile) {
+static void create_ports_for_profile(struct userdata *u, const pa_bluetooth_device *device, pa_card_new_data *card_new_data, pa_card_profile *profile) {
pa_device_port *port;
enum profile *d;
port->is_output = 1;
port->is_input = 0;
port->priority = profile->priority * 100;
+ port->available = audio_state_to_availability(device->audio_sink_state);
pa_hashmap_put(port->profiles, profile->name, profile);
break;
port->is_output = 0;
port->is_input = 1;
port->priority = profile->priority * 100;
+ port->available = audio_state_to_availability(device->audio_source_state);
pa_hashmap_put(port->profiles, profile->name, profile);
break;
port->is_output = 1;
port->is_input = 0;
port->priority = profile->priority * 100;
+ port->available = audio_state_to_availability(device->headset_state);
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));
port->is_output = 0;
port->is_input = 1;
port->priority = profile->priority * 100;
+ port->available = audio_state_to_availability(device->headset_state);
pa_hashmap_put(port->profiles, profile->name, profile);
break;
port->is_output = 1;
port->is_input = 0;
port->priority = profile->priority * 100;
+ port->available = audio_state_to_availability(device->hfgw_state);
pa_hashmap_put(port->profiles, profile->name, profile);
pa_assert_se(port = pa_device_port_new(u->core, "hfgw-input", _("Bluetooth Handsfree Gateway"), 0));
port->is_output = 0;
port->is_input = 1;
port->priority = profile->priority * 100;
+ port->available = audio_state_to_availability(device->hfgw_state);
pa_hashmap_put(port->profiles, profile->name, profile);
break;
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_A2DP;
- create_ports_for_profile(u, &data, p);
+ create_ports_for_profile(u, device, &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);
+ create_ports_for_profile(u, device, &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);
+ create_ports_for_profile(u, device, &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);
+ create_ports_for_profile(u, device, &data, p);
pa_hashmap_put(data.profiles, p->name, p);
}
if ((device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) ||
(device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) ||
+ (device->audio_source_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP_SOURCE) ||
(device->hfgw_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW)) {
pa_log_warn("Default profile not connected, selecting off profile");
u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
mike,
"type='signal',sender='org.bluez',interface='org.bluez.MediaTransport',member='PropertyChanged'",
"type='signal',sender='org.bluez',interface='org.bluez.HandsfreeGateway',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.AudioSource',member='PropertyChanged'",
+ "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'",
NULL) < 0) {
pa_xfree(speaker);