diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 707d0771e..d721f8ac6 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -268,10 +268,19 @@ static int rfcomm_send_reply(struct spa_source *source, const char *data) return len; } +static bool rfcomm_volume_enabled(struct rfcomm *rfcomm) +{ + return rfcomm->device != NULL + && (rfcomm->device->hw_volume_profiles & rfcomm->profile); +} + static void rfcomm_emit_volume_changed(struct rfcomm *rfcomm, int id, int hw_volume) { struct spa_bt_transport_volume *t_volume; + if (!rfcomm_volume_enabled(rfcomm)) + return; + if ((id == SPA_BT_VOLUME_ID_RX || id == SPA_BT_VOLUME_ID_TX) && hw_volume >= 0) { rfcomm->volumes[id].active = true; rfcomm->volumes[id].hw_volume = hw_volume; @@ -335,6 +344,9 @@ static bool rfcomm_send_volume_cmd(struct spa_source *source, int id) char *cmd; int hw_volume; + if (!rfcomm_volume_enabled(rfcomm)) + return false; + t_volume = rfcomm->transport ? &rfcomm->transport->volumes[id] : NULL; if (!(t_volume && t_volume->active)) @@ -1185,10 +1197,9 @@ static int sco_set_volume_cb(void *data, int id, float volume) char *msg; int value; - if (!(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) - return -ENOTSUP; - - if (!(rfcomm->has_volume && rfcomm->volumes[id].active)) + if (!rfcomm_volume_enabled(rfcomm) + || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) + || !(rfcomm->has_volume && rfcomm->volumes[id].active)) return -ENOTSUP; value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); @@ -1473,7 +1484,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->transport = t; if (profile == SPA_BT_PROFILE_HSP_AG) { - rfcomm->has_volume = true; + rfcomm->has_volume = rfcomm_volume_enabled(rfcomm); rfcomm->hs_state = hsp_hs_init1; } @@ -1500,8 +1511,10 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->codec_negotiation_supported = false; } - rfcomm->has_volume = true; - hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; + if (rfcomm_volume_enabled(rfcomm)) { + rfcomm->has_volume = true; + hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; + } /* send command to AG with the features supported by Hands-Free */ cmd = spa_aprintf("AT+BRSF=%u", hf_features); diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 0af6de0bf..d3137f5ad 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -136,6 +136,9 @@ struct spa_bt_a2dp_codec_switch { size_t num_paths; }; +#define DEFAULT_RECONNECT_PROFILES SPA_BT_PROFILE_NULL +#define DEFAULT_HW_VOLUME_PROFILES (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | SPA_BT_PROFILE_A2DP_SOURCE) + #define BT_DEVICE_DISCONNECTED 0 #define BT_DEVICE_CONNECTED 1 #define BT_DEVICE_INIT -1 @@ -665,6 +668,9 @@ static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const d->monitor = monitor; d->path = strdup(path); d->battery_path = battery_get_name(d->path); + d->reconnect_profiles = DEFAULT_RECONNECT_PROFILES; + d->hw_volume_profiles = DEFAULT_HW_VOLUME_PROFILES; + spa_list_init(&d->remote_endpoint_list); spa_list_init(&d->transport_list); spa_list_init(&d->codec_switch_list); @@ -1464,8 +1470,17 @@ struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, return t; } +bool spa_bt_transport_volume_enabled(struct spa_bt_transport *transport) +{ + return transport->device != NULL + && (transport->device->hw_volume_profiles & transport->profile); +} + static void transport_sync_volume(struct spa_bt_transport *transport) { + if (!spa_bt_transport_volume_enabled(transport)) + return; + for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) spa_bt_transport_set_volume(transport, i, transport->volumes[i].volume); spa_bt_transport_emit_volume_changed(transport); @@ -1687,7 +1702,8 @@ static void spa_bt_transport_volume_changed(struct spa_bt_transport *transport) t_volume->hw_volume_max); spa_log_debug(monitor->log, "transport %p: volume changed %d(%f) ", transport, t_volume->new_hw_volume, t_volume->volume); - spa_bt_transport_emit_volume_changed(transport); + if (spa_bt_transport_volume_enabled(transport)) + spa_bt_transport_emit_volume_changed(transport); } } @@ -1942,7 +1958,7 @@ static int transport_set_volume(void *data, int id, float volume) struct spa_bt_transport_volume *t_volume = &transport->volumes[id]; uint16_t value; - if (!t_volume->active) + if (!t_volume->active || !spa_bt_transport_volume_enabled(transport)) return -ENOTSUP; value = spa_bt_volume_linear_to_hw(volume, 127); diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index d61617427..814cc3b42 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -288,7 +288,7 @@ static void volume_changed(void *userdata) struct spa_bt_transport_volume *t_volume; float prev_hw_volume; - if (!node->transport) + if (!node->transport || !spa_bt_transport_volume_enabled(node->transport)) return; /* PW is the controller for remote device. */ @@ -445,7 +445,8 @@ static void dynamic_node_volume_changed(void *data) int id = SPA_FLAG_CLEAR(node->id, DYNAMIC_NODE_ID_FLAG), volume_id; /* Remote device is the controller */ - if (!node->transport || impl->profile != DEVICE_PROFILE_AG) + if (!node->transport || impl->profile != DEVICE_PROFILE_AG + || !spa_bt_transport_volume_enabled(node->transport)) return; if (id == 0 || id == 2) @@ -1514,7 +1515,8 @@ static int node_set_volume(struct impl *this, struct node *node, float volumes[] t_volume = node->transport ? &node->transport->volumes[node->id]: NULL; - if (t_volume && t_volume->active) { + if (t_volume && t_volume->active + && spa_bt_transport_volume_enabled(node->transport)) { float hw_volume = node_get_hw_volume(node); spa_log_debug(this->log, "node %p hardware volume %f", node, hw_volume); @@ -1866,11 +1868,16 @@ impl_init(const struct spa_handle_factory *factory, if (info) { int profiles; this->bt_dev->settings = filter_bluez_device_setting(this, info); - if ((str = spa_dict_lookup(info, "bluez5.reconnect-profiles")) != NULL) - profiles = spa_bt_profiles_from_json_array(str); - if (str == NULL || profiles < 0) - profiles = SPA_BT_PROFILE_NULL; - this->bt_dev->reconnect_profiles = profiles; + + if ((str = spa_dict_lookup(info, "bluez5.reconnect-profiles")) != NULL) { + if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0) + this->bt_dev->reconnect_profiles = profiles; + } + + if ((str = spa_dict_lookup(info, "bluez5.hw-volume")) != NULL) { + if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0) + this->bt_dev->hw_volume_profiles = profiles; + } } this->device.iface = SPA_INTERFACE_INIT( diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index 95372b864..131a319ad 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -423,6 +423,8 @@ struct spa_bt_device { uint8_t battery; int has_battery; + uint32_t hw_volume_profiles; + struct spa_hook_list listener_list; bool added; @@ -551,7 +553,8 @@ struct spa_bt_transport *spa_bt_transport_find(struct spa_bt_monitor *monitor, c struct spa_bt_transport *spa_bt_transport_find_full(struct spa_bt_monitor *monitor, bool (*callback) (struct spa_bt_transport *t, const void *data), const void *data); -int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t); +int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *transport); +bool spa_bt_transport_volume_enabled(struct spa_bt_transport *transport); int spa_bt_transport_acquire(struct spa_bt_transport *t, bool optional); int spa_bt_transport_release(struct spa_bt_transport *t); diff --git a/src/daemon/media-session.d/bluez-monitor.conf b/src/daemon/media-session.d/bluez-monitor.conf index b1e31e1e0..50028ec22 100644 --- a/src/daemon/media-session.d/bluez-monitor.conf +++ b/src/daemon/media-session.d/bluez-monitor.conf @@ -55,6 +55,16 @@ rules = [ # Overload mSBC support for native backend and a specific device. #bluez5.msbc-support = false + # Hardware volume control (default: [ hfp_ag hsp_ag a2dp_source ]) + #bluez5.hw-volume = [ + # hfp_hf + # hsp_hs + # a2dp_sink + # hfp_ag + # hsp_ag + # a2dp_source + #] + # LDAC encoding quality # Available values: auto (Adaptive Bitrate, default) # hq (High Quality, 990/909kbps)