diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 878c5b9b0..7d9cd3653 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -47,6 +47,8 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_G722, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "g722", NULL }, + { 0, 0, NULL, NULL }, }; diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 66359bcc9..0691e72fa 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -556,6 +556,8 @@ static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, case SPA_BT_MEDIA_SOURCE: return codec->bap ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; case SPA_BT_MEDIA_SINK: + if (codec->asha) + return SPA_BT_PROFILE_ASHA_SINK; return codec->bap ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_A2DP_SINK; case SPA_BT_MEDIA_SOURCE_BROADCAST: return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; @@ -2020,10 +2022,11 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) uint32_t connected_profiles = device->connected_profiles; uint32_t connectable_profiles = device->adapter ? adapter_connectable_profiles(device->adapter) : 0; - uint32_t direction_masks[3] = { + uint32_t direction_masks[4] = { SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, SPA_BT_PROFILE_MEDIA_SOURCE, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY, + SPA_BT_PROFILE_ASHA_SINK, }; bool direction_connected = false; bool set_connected = true; @@ -2034,6 +2037,7 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) connected_profiles |= SPA_BT_PROFILE_HEADSET_HEAD_UNIT; if (connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) connected_profiles |= SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; + connected_profiles |= SPA_BT_PROFILE_ASHA_SINK; for (i = 0; i < SPA_N_ELEMENTS(direction_masks); ++i) { uint32_t mask = direction_masks[i] & device->profiles & connectable_profiles; @@ -2645,6 +2649,8 @@ static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor, return d; } +static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor, const char *transport_path); + static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) @@ -2698,6 +2704,15 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en spa_list_append(&device->remote_endpoint_list, &remote_endpoint->device_link); } } + /* For ASHA */ + else if (spa_streq(key, "Transport")) { + if (setup_asha_transport(remote_endpoint, monitor, value)) { + spa_log_error(monitor->log, "Failed to create transport for remote_endpoint %p: %s=%s", remote_endpoint, key, value); + goto next; + } + + spa_log_info(monitor->log, "Created ASHA transport for %s", value); + } } else if (type == DBUS_TYPE_BOOLEAN) { int value; @@ -2721,6 +2736,16 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en remote_endpoint->codec = value; } } + /* Codecs property is present for ASHA */ + else if (type == DBUS_TYPE_UINT16) { + uint16_t value; + + dbus_message_iter_get_basic(&it[1], &value); + + if (spa_streq(key, "Codecs")) { + spa_log_debug(monitor->log, "remote_endpoint %p: %s=%02x", remote_endpoint, key, value); + } + } else if (spa_streq(key, "Capabilities")) { DBusMessageIter iter; uint8_t *value; @@ -2744,6 +2769,23 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en remote_endpoint->capabilities_len = len; } } + /* HiSyncId property is present for ASHA */ + else if (spa_streq(key, "HiSyncId")) { + /* + * TODO: Required for Stereo support in ASHA, for now just log. + */ + DBusMessageIter iter; + uint8_t *value; + int len; + + if (!check_iter_signature(&it[1], "ay")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + dbus_message_iter_get_fixed_array(&iter, &value, &len); + + spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, len); + } else spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); @@ -2761,6 +2803,10 @@ next: profile = spa_bt_profile_from_uuid(remote_endpoint->uuid); if (profile & SPA_BT_PROFILE_BAP_AUDIO) spa_bt_device_add_profile(remote_endpoint->device, profile); + if (profile & SPA_BT_PROFILE_ASHA_SINK) { + spa_log_debug(monitor->log, "Adding profile for remote_endpoint %p: device -> %p", remote_endpoint, remote_endpoint->device); + spa_bt_device_add_profile(remote_endpoint->device, SPA_BT_PROFILE_ASHA_SINK); + } } return 0; @@ -3168,6 +3214,8 @@ static void spa_bt_transport_volume_changed(struct spa_bt_transport *transport) volume_id = SPA_BT_VOLUME_ID_TX; else if (transport->profile & SPA_BT_PROFILE_A2DP_SOURCE) volume_id = SPA_BT_VOLUME_ID_RX; + else if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) + volume_id = SPA_BT_VOLUME_ID_TX; else return; @@ -3409,6 +3457,8 @@ static int transport_update_props(struct spa_bt_transport *transport, t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; else if (transport->profile & SPA_BT_PROFILE_A2DP_SOURCE) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_RX]; + else if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) + t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; else goto next; @@ -4028,6 +4078,61 @@ static const struct spa_bt_transport_implementation transport_impl = { .set_delay = transport_set_delay, }; +static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor, const char *transport_path) +{ + const struct media_codec * const * const media_codecs = monitor->media_codecs; + const struct media_codec *codec = NULL; + struct spa_bt_transport *transport; + + transport = spa_bt_transport_find(monitor, transport_path); + if (transport == NULL) { + char *tpath = strdup(transport_path); + + transport = spa_bt_transport_create(monitor, tpath, 0); + if (transport == NULL) { + spa_log_error(monitor->log, "Failed to create transport for %s", transport_path); + free(tpath); + return -EINVAL; + } + + spa_bt_transport_set_implementation(transport, &transport_impl, transport); + + spa_log_debug(monitor->log, "Created ASHA transport for %s", transport_path); + } + + for (int i = 0; media_codecs[i]; i++) { + const struct media_codec *mcodec = media_codecs[i]; + if (!spa_streq(mcodec->name, "g722")) + continue; + codec = mcodec; + spa_log_debug(monitor->log, "Setting ASHA codec: %s", mcodec->name); + } + + free(transport->endpoint_path); + transport->endpoint_path = strdup(transport_path); + transport->profile = SPA_BT_PROFILE_ASHA_SINK; + transport->media_codec = codec; + transport->device = remote_endpoint->device; + + spa_list_append(&remote_endpoint->device->transport_list, &transport->device_link); + + spa_bt_device_update_last_bluez_action_time(transport->device); + + transport->volumes[SPA_BT_VOLUME_ID_TX].active = true; + transport->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_TX_VOLUME; + transport->n_channels = 1; + transport->channels[0] = SPA_AUDIO_CHANNEL_MONO; + + spa_bt_device_add_profile(transport->device, transport->profile); + spa_bt_device_connect_profile(transport->device, transport->profile); + + transport_sync_volume(transport); + + spa_log_debug(monitor->log, "ASHA transport setup complete"); + + return 0; +} + static void media_codec_switch_reply(DBusPendingCall *pending, void *userdata); static int media_codec_switch_cmp(const void *a, const void *b); @@ -5534,6 +5639,8 @@ static void interface_added(struct spa_bt_monitor *monitor, object_path); return; } + spa_log_info(monitor->log, "Created Bluetooth device %s", + object_path); } device_update_props(d, props_iter, NULL); @@ -5546,7 +5653,14 @@ static void interface_added(struct spa_bt_monitor *monitor, /* Trigger bluez device creation before bluez profile negotiation started so that * profile connection handlers can receive per-device settings during profile negotiation. */ - spa_bt_device_add_profile(d, SPA_BT_PROFILE_NULL); + if (d->profiles & SPA_BT_PROFILE_ASHA_SINK) { + spa_log_info(monitor->log, "Add profile %s: %m", + object_path); + spa_bt_device_add_profile(d, SPA_BT_PROFILE_ASHA_SINK); + spa_bt_device_connect_profile(d, SPA_BT_PROFILE_ASHA_SINK); + } + else + spa_bt_device_add_profile(d, SPA_BT_PROFILE_NULL); } else if (spa_streq(interface_name, BLUEZ_DEVICE_SET_INTERFACE)) { device_set_update_props(monitor, object_path, props_iter, NULL); @@ -6185,7 +6299,7 @@ static int parse_roles(struct spa_bt_monitor *monitor, const struct spa_dict *in { const char *str; int res = 0; - int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE; + int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE | SPA_BT_PROFILE_ASHA_SINK; /* HSP/HFP backends parse this property separately */ if (info && (str = spa_dict_lookup(info, "bluez5.roles"))) { diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 3625bd7b5..545c3aabc 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -59,6 +59,7 @@ enum { DEVICE_PROFILE_A2DP = 2, DEVICE_PROFILE_HSP_HFP = 3, DEVICE_PROFILE_BAP = 4, + DEVICE_PROFILE_ASHA = 5, DEVICE_PROFILE_LAST, }; @@ -1108,6 +1109,17 @@ static int emit_nodes(struct impl *this) } } break; + case DEVICE_PROFILE_ASHA: + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) { + t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); + if (t) { + this->props.codec = t->media_codec->id; + emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + } else { + spa_log_warn(this->log, "Unable to find transport for ASHA"); + } + } + break; case DEVICE_PROFILE_A2DP: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE); @@ -1284,6 +1296,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a this->save_profile = save; if (this->profile == profile && + (this->profile != DEVICE_PROFILE_ASHA || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec)) @@ -1418,6 +1431,11 @@ static void profiles_changed(void *userdata, uint32_t connected_change) spa_log_debug(this->log, "profiles changed: AG nodes changed: %d", nodes_changed); break; + case DEVICE_PROFILE_ASHA: + nodes_changed = (connected_change & SPA_BT_PROFILE_ASHA_SINK); + spa_log_debug(this->log, "profiles changed: ASHA nodes changed: %d", + nodes_changed); + break; case DEVICE_PROFILE_A2DP: nodes_changed = (connected_change & SPA_BT_PROFILE_A2DP_DUPLEX); spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d", @@ -1586,6 +1604,10 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s if (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) have_output = have_input = true; break; + case DEVICE_PROFILE_ASHA: + if (device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) + have_input = true; + break; default: break; } @@ -1609,6 +1631,10 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32 *codec = 0; *next = (profile + 1) << 16; return profile; + case DEVICE_PROFILE_ASHA: + *codec = SPA_BLUETOOTH_AUDIO_CODEC_G722; + *next = (profile + 1) << 16; + return profile; case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_HSP_HFP: case DEVICE_PROFILE_BAP: @@ -1636,6 +1662,9 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum case DEVICE_PROFILE_AG: return (profile << 16); + case DEVICE_PROFILE_ASHA: + return (profile << 16) | (SPA_BLUETOOTH_AUDIO_CODEC_G722 & 0xffff); + case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_HSP_HFP: @@ -1647,6 +1676,25 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum return SPA_ID_INVALID; } +static bool set_initial_asha_profile(struct impl *this) +{ + struct spa_bt_transport *t; + if (!(this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) + return false; + + t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); + if (t) { + this->profile = DEVICE_PROFILE_ASHA; + this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_G722; + + spa_log_debug(this->log, "initial ASHA profile:%d codec:%d", + this->profile, this->props.codec); + return true; + } + + return false; +} + static bool set_initial_hsp_hfp_profile(struct impl *this) { struct spa_bt_transport *t; @@ -1688,13 +1736,16 @@ static void set_initial_profile(struct impl *this) /* If default profile is set to HSP/HFP, first try those and exit if found. */ if (this->bt_dev->settings != NULL) { const char *str = spa_dict_lookup(this->bt_dev->settings, "bluez5.profile"); + + if (spa_streq(str, "asha-sink") && set_initial_asha_profile(this)) + return; if (spa_streq(str, "off")) goto off; if (spa_streq(str, "headset-head-unit") && set_initial_hsp_hfp_profile(this)) return; } - for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { + for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_ASHA_SINK; i <<= 1) { if (!(this->bt_dev->connected_profiles & i)) continue; @@ -1704,6 +1755,8 @@ static void set_initial_profile(struct impl *this) this->profile = DEVICE_PROFILE_AG; else if (i == SPA_BT_PROFILE_BAP_SINK) this->profile = DEVICE_PROFILE_BAP; + else if (i == SPA_BT_PROFILE_ASHA_SINK) + this->profile = DEVICE_PROFILE_ASHA; else this->profile = DEVICE_PROFILE_A2DP; this->props.codec = t->media_codec->id; @@ -1765,6 +1818,26 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * priority = 256; break; } + case DEVICE_PROFILE_ASHA: + { + uint32_t profile = device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; + + if (codec == 0) + return NULL; + if (profile == 0) + return NULL; + if (!(profile & SPA_BT_PROFILE_ASHA_SINK)) { + return NULL; + } + + name = spa_bt_profile_name(profile); + desc = _("Audio Streaming for Hearing Aids (ASHA Sink)"); + + n_sink++; + priority = 1; + + break; + } case DEVICE_PROFILE_A2DP: { /* make this device profile visible only if there is an A2DP sink */ @@ -2022,6 +2095,12 @@ static bool profile_has_route(uint32_t profile, uint32_t route) return true; } break; + case DEVICE_PROFILE_ASHA: + switch (route) { + case ROUTE_OUTPUT: + return true; + } + break; } return false; } @@ -2328,7 +2407,7 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || this->profile == DEVICE_PROFILE_ASHA) { FOR_EACH_MEDIA_CODEC(j, codec) { spa_pod_builder_int(b, codec->id); spa_pod_builder_string(b, codec->description); @@ -2747,7 +2826,7 @@ static int impl_set_param(void *object, if (codec_id == SPA_ID_INVALID) return 0; - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || this->profile == DEVICE_PROFILE_ASHA) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { if (this->supported_codecs[j]->id == codec_id) { diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 4249488c8..7bee8f908 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -127,6 +127,7 @@ extern "C" { #define SPA_BT_UUID_BAP_SOURCE "00002bcb-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_BROADCAST_SOURCE "00001852-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_BROADCAST_SINK "00001851-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_ASHA_SINK "0000FDF0-0000-1000-8000-00805f9b34fb" #define PROFILE_HSP_AG "/Profile/HSPAG" #define PROFILE_HSP_HS "/Profile/HSPHS" @@ -181,12 +182,13 @@ enum spa_bt_profile { SPA_BT_PROFILE_BAP_SOURCE = (1 << 1), SPA_BT_PROFILE_A2DP_SINK = (1 << 2), SPA_BT_PROFILE_A2DP_SOURCE = (1 << 3), - SPA_BT_PROFILE_HSP_HS = (1 << 4), - SPA_BT_PROFILE_HSP_AG = (1 << 5), - SPA_BT_PROFILE_HFP_HF = (1 << 6), - SPA_BT_PROFILE_HFP_AG = (1 << 7), - SPA_BT_PROFILE_BAP_BROADCAST_SOURCE = (1 << 8), - SPA_BT_PROFILE_BAP_BROADCAST_SINK = (1 << 9), + SPA_BT_PROFILE_ASHA_SINK = (1 << 4), + SPA_BT_PROFILE_HSP_HS = (1 << 5), + SPA_BT_PROFILE_HSP_AG = (1 << 6), + SPA_BT_PROFILE_HFP_HF = (1 << 7), + SPA_BT_PROFILE_HFP_AG = (1 << 8), + SPA_BT_PROFILE_BAP_BROADCAST_SOURCE = (1 << 9), + SPA_BT_PROFILE_BAP_BROADCAST_SINK = (1 << 10), SPA_BT_PROFILE_A2DP_DUPLEX = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE), SPA_BT_PROFILE_BAP_DUPLEX = (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE), @@ -226,6 +228,8 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; else if (strcasecmp(uuid, SPA_BT_UUID_BAP_BROADCAST_SINK) == 0) return SPA_BT_PROFILE_BAP_BROADCAST_SINK; + else if (strcasecmp(uuid, SPA_BT_UUID_ASHA_SINK) == 0) + return SPA_BT_PROFILE_ASHA_SINK; else return 0; } @@ -316,6 +320,8 @@ enum spa_bt_hfp_sdp_hf_features { static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) { switch (profile) { + case SPA_BT_PROFILE_ASHA_SINK: + return "asha-sink"; case SPA_BT_PROFILE_A2DP_SOURCE: return "a2dp-source"; case SPA_BT_PROFILE_A2DP_SINK: