File 0007-bluetooth-support-soft-volume-control-for-HFP.patch of Package pulseaudio-modules-bt

From 49c48aa77548054d95b65e5cd0d94e12f28d0c0e Mon Sep 17 00:00:00 2001
From: Faidon Liambotis <paravoid@debian.org>
Date: Mon, 5 Aug 2019 15:07:26 +0300
Subject: [PATCH 7/9] bluetooth: support soft volume control for HFP

Based on a patch from Rodrigo Araujo <araujo.rm@gmail.com>;

According to the HFP 1.6 specification, remote volume control isn't
mandatory. If it is, only the speaker volume control is mandatory.

Add flags on the hfp_config struct and check for this case. By default,
we assume software volume control. When the HF sends us its features
during negotiation, if it supports remote volume control then we disable
software VC for the speaker, but we keep assuming software VC for the
mic unless we receive a unsolicited +VGM at some point.

Signed-off-by: Faidon Liambotis <paravoid@debian.org>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 src/modules/bluetooth/backend-native.c       | 43 +++++++++++++-------
 src/modules/bluetooth/bluez5-util.h          |  7 ++++
 src/modules/bluetooth/module-bluez5-device.c | 30 ++++++++++----
 3 files changed, 57 insertions(+), 23 deletions(-)

diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index 963a9b571a2f..f73c97ceca67 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -55,11 +55,6 @@ struct transport_data {
     pa_mainloop_api *mainloop;
 };
 
-struct hfp_config {
-    uint32_t capabilities;
-    int state;
-};
-
 /*
  * the separate handsfree headset (HF) and Audio Gateway (AG) features
  */
@@ -169,7 +164,7 @@ static void hfp_send_features(int fd)
 {
     char buf[512];
 
-    sprintf(buf, "+BRSF: %d", hfp_features);
+    sprintf(buf, "+BRSF: %u", hfp_features);
     rfcomm_write(fd, buf);
 }
 
@@ -410,6 +405,10 @@ static void register_profile(pa_bluetooth_backend *b, const char *profile, const
         /* HSP version 1.2 */
         version = 0x0102;
         pa_dbus_append_basic_variant_dict_entry(&d, "Version", DBUS_TYPE_UINT16, &version);
+    } else if (pa_streq (uuid, PA_BLUETOOTH_UUID_HFP_AG)) {
+        /* HFP version 1.6 */
+        version = 0x0106;
+        pa_dbus_append_basic_variant_dict_entry(&d, "Version", DBUS_TYPE_UINT16, &version);
     }
     dbus_message_iter_close_container(&i, &d);
 
@@ -432,6 +431,10 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
     if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) {
           c->capabilities = val;
           pa_log_info("HFP capabilities returns 0x%x", val);
+          if (val & HFP_HF_RVOL) {
+              c->speaker_gain_supported = true;
+              c->mic_gain_supported = false;
+          }
           hfp_send_features(fd);
           c->state = 1;
           return true;
@@ -459,7 +462,7 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
     if (c->state != 4) {
         pa_log_error("HFP negotiation failed in state %d with inbound %s\n",
                      c->state, buf);
-        rfcomm_write(fd, "ERROR");
+        rfcomm_write(fd, "\r\nERROR\r\n");
         return false;
     }
 
@@ -487,6 +490,7 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
         ssize_t len;
         int gain, dummy;
         bool  do_reply = false;
+        struct hfp_config *c = t->config;
 
         len = pa_read(fd, buf, 511, NULL);
         if (len < 0) {
@@ -505,18 +509,23 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
          * is changed on the AG side.
          * AT+CKPD=200: Sent by HS when headset button is pressed.
          * RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because
-         * it does not expect a reply. */
-        if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM=%d\r\n", &gain) == 1) {
+         * it does not expect a reply.
+         *
+         * Also in HFP we need to handle negotiation, including codec updating by the HG */
+        if (sscanf(buf, "AT+VGS=%d", &gain) == 1 || sscanf(buf, "\r\n+VGM%*[=:]%d\r\n", &gain) == 1) {
             t->speaker_gain = gain;
             pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED), t);
             do_reply = true;
-
-        } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS=%d\r\n", &gain) == 1) {
+        } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1 || sscanf(buf, "\r\n+VGS%*[=:]%d\r\n", &gain) == 1) {
+            c->mic_gain_supported = true;
             t->microphone_gain = gain;
             pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
             do_reply = true;
         } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) {
             do_reply = true;
+        } else if (pa_startswith(buf, "AT+NREC=0") == 1) {
+            /* TODO: Handle disabling echo cancelation if active */
+            do_reply = true;
         } else if (t->config) { /* t->config is only non-null for hfp profile */
             do_reply = hfp_rfcomm_handle(fd, t, buf);
         } else {
@@ -571,10 +580,12 @@ static void set_speaker_gain(pa_bluetooth_transport *t, uint16_t gain) {
     /* If we are in the AG role, we send a command to the head set to change
      * the speaker gain. In the HS role, source and sink are swapped, so
      * in this case we notify the AG that the microphone gain has changed */
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
-        t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS) {
         len = sprintf(buf, "\r\n+VGS=%d\r\n", gain);
         pa_log_debug("RFCOMM >> +VGS=%d", gain);
+    } else if (t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
+        len = sprintf(buf, "\r\n+VGS:%d\r\n", gain);
+        pa_log_debug("RFCOMM >> +VGS:%d", gain);
     } else {
         len = sprintf(buf, "\r\nAT+VGM=%d\r\n", gain);
         pa_log_debug("RFCOMM >> AT+VGM=%d", gain);
@@ -599,10 +610,12 @@ static void set_microphone_gain(pa_bluetooth_transport *t, uint16_t gain) {
     /* If we are in the AG role, we send a command to the head set to change
      * the microphone gain. In the HS role, source and sink are swapped, so
      * in this case we notify the AG that the speaker gain has changed */
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
-        t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS) {
         len = sprintf(buf, "\r\n+VGM=%d\r\n", gain);
         pa_log_debug("RFCOMM >> +VGM=%d", gain);
+    } else if (t->profile == PA_BLUETOOTH_PROFILE_HFP_HF) {
+        len = sprintf(buf, "\r\n+VGM:%d\r\n", gain);
+        pa_log_debug("RFCOMM >> +VGM:%d", gain);
     } else {
         len = sprintf(buf, "\r\nAT+VGS=%d\r\n", gain);
         pa_log_debug("RFCOMM >> AT+VGS=%d", gain);
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index fc9220d34efb..a0ee1b7fa8a5 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -168,6 +168,13 @@ static inline void pa_bluetooth_ofono_backend_free(pa_bluetooth_backend *b) {}
 pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_hs_role);
 void pa_bluetooth_native_backend_free(pa_bluetooth_backend *b);
 void pa_bluetooth_native_backend_enable_hs_role(pa_bluetooth_backend *b, bool enable_hs_role);
+
+struct hfp_config {
+    uint32_t capabilities;
+    int state;
+    bool speaker_gain_supported;
+    bool mic_gain_supported;
+};
 #else
 static inline pa_bluetooth_backend *pa_bluetooth_native_backend_new(pa_core *c, pa_bluetooth_discovery *y, bool enable_hs_role) {
     return NULL;
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index d7073dd9ec79..a7b18929d803 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -920,11 +920,14 @@ static void source_set_volume_cb(pa_source *s) {
     uint16_t gain;
     pa_volume_t volume;
     struct userdata *u;
+    bool softonly;
+    struct hfp_config *c;
 
     pa_assert(s);
     pa_assert(s->core);
 
     u = s->userdata;
+    c = u->transport->config;
 
     pa_assert(u);
     pa_assert(u->source == s);
@@ -945,15 +948,17 @@ static void source_set_volume_cb(pa_source *s) {
 
     pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
 
-    /* Set soft volume when in headset role */
-    if (u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
+    softonly = (u->profile == PA_BLUETOOTH_PROFILE_HFP_HF && !c->mic_gain_supported);
+    if (softonly ||
+        u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
         u->profile == PA_BLUETOOTH_PROFILE_HFP_AG)
         pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
 
     /* If we are in the AG role, we send a command to the head set to change
      * the microphone gain. In the HS role, source and sink are swapped, so
      * in this case we notify the AG that the speaker gain has changed */
-    u->transport->set_microphone_gain(u->transport, gain);
+    if (!softonly)
+        u->transport->set_microphone_gain(u->transport, gain);
 }
 
 /* Run from main thread */
@@ -1103,11 +1108,14 @@ static void sink_set_volume_cb(pa_sink *s) {
     uint16_t gain;
     pa_volume_t volume;
     struct userdata *u;
+    bool softonly;
+    struct hfp_config *c;
 
     pa_assert(s);
     pa_assert(s->core);
 
     u = s->userdata;
+    c = u->transport->config;
 
     pa_assert(u);
     pa_assert(u->sink == s);
@@ -1128,15 +1136,19 @@ static void sink_set_volume_cb(pa_sink *s) {
 
     pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
 
-    /* Set soft volume when in headset role */
-    if (u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
+    /* Set soft volume when in headset role or when in Audio Gateway mode and
+       the headset/handsfree does not support remote volume control */
+    softonly = (u->profile == PA_BLUETOOTH_PROFILE_HFP_HF && !c->mic_gain_supported);
+    if (softonly ||
+        u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
         u->profile == PA_BLUETOOTH_PROFILE_HFP_AG)
         pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
 
     /* If we are in the AG role, we send a command to the head set to change
      * the speaker gain. In the HS role, source and sink are swapped, so
      * in this case we notify the AG that the microphone gain has changed */
-    u->transport->set_speaker_gain(u->transport, gain);
+    if (!softonly)
+        u->transport->set_speaker_gain(u->transport, gain);
 }
 
 /* Run from main thread */
@@ -2384,7 +2396,8 @@ static pa_hook_result_t transport_speaker_gain_changed_cb(pa_bluetooth_discovery
         volume++;
 
     pa_cvolume_set(&v, u->sample_spec.channels, volume);
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS)
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
+        t->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
         pa_sink_volume_changed(u->sink, &v);
     else
         pa_sink_set_volume(u->sink, &v, true, true);
@@ -2412,7 +2425,8 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
 
     pa_cvolume_set(&v, u->sample_spec.channels, volume);
 
-    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS)
+    if (t->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
+        t->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
         pa_source_volume_changed(u->source, &v);
     else
         pa_source_set_volume(u->source, &v, true, true);
-- 
2.26.2

openSUSE Build Service is sponsored by