Discussion:
[pulseaudio-discuss] [PATCH] Absolute volume control for A2DP transport
EHfive
2018-10-03 12:37:11 UTC
Permalink
Require bluez-tools/mpris-proxy running. (No hurt if dosen't)

If you need play/pause/next... controls , add configurations below to
/etc/dbus-1/system.d/

------------------------ mpris.conf

<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration
1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy context="default">
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>
</busconfig>

------------------------

===================

diff --git a/src/modules/bluetooth/bluez5-util.c
b/src/modules/bluetooth/bluez5-util.c
index 2d83373..72cd05a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -348,6 +348,50 @@ void
pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }

+static int bluez5_transport_set_property(pa_bluetooth_transport *t,
const char *prop_name, int prop_type, void *prop_value){
+    DBusMessage *m, *r;
+    DBusError err;
+    DBusMessageIter i;
+    const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
+
+    pa_log_debug("Setting property, Owner: %s; Path: %s; Property:
%s",t->owner, t->path, prop_name);
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path,
"org.freedesktop.DBus.Properties", "Set"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING,
&interface));
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING,
&prop_name));
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    r =
dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection),
m, -1, &err);
+    dbus_message_unref(m);
+    m = NULL;
+    if(r) {
+        dbus_message_unref(r);
+        r = NULL;
+    }
+
+    if(dbus_error_is_set(&err)) {
+        pa_log_debug("Failed to set property \"%s.%s\"", interface,
prop_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int bluez5_transport_set_volume(pa_bluetooth_transport *t,
uint16_t volume){
+    if(t->a2dp_gain == volume)
+        return 0;
+    return bluez5_transport_set_property(t, "Volume", DBUS_TYPE_UINT16,
&volume);
+}
+
 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool
optional, size_t *imtu, size_t *omtu) {
     DBusMessage *m, *r;
     DBusError err;
@@ -441,6 +485,14 @@ bool
pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }

+void pa_transport_set_a2dp_gain(pa_bluetooth_transport *t, uint16_t
a2dp_gain){
+    if(t->a2dp_gain == a2dp_gain)
+        return;
+    t->a2dp_gain = a2dp_gain;
+ pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery,
PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), t);
+}
+
+
 static int transport_state_from_string(const char* value,
pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -483,6 +535,18 @@ static void
parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
                 pa_bluetooth_transport_set_state(t, state);
             }

+            break;
+        }
+        case DBUS_TYPE_UINT16: {
+
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                pa_log_debug("Transport Volume Changed to %u ", value);
+                pa_transport_set_a2dp_gain(t, value);
+            }
+
             break;
         }
     }
@@ -1468,6 +1532,7 @@ static DBusMessage
*endpoint_set_configuration(DBusConnection *conn, DBusMessage
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    t->set_volume = bluez5_transport_set_volume;
     pa_bluetooth_transport_put(t);

     pa_log_debug("Transport %s available for profile %s", t->path,
pa_bluetooth_profile_to_string(t->profile));
diff --git a/src/modules/bluetooth/bluez5-util.h
b/src/modules/bluetooth/bluez5-util.h
index ad30708..5b8149d 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -47,6 +47,7 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call
data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call
data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call
data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED,        /* Call data:
pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;

@@ -70,6 +71,7 @@ typedef void
(*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void
(*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void
(*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t,
uint16_t gain);
 typedef void
(*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport
*t, uint16_t gain);
+typedef int
(*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t,
uint16_t volume);

 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -84,6 +86,7 @@ struct pa_bluetooth_transport {

     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t a2dp_gain;

     pa_bluetooth_transport_state_t state;

@@ -92,6 +95,7 @@ struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_volume_cb set_volume;
     void *userdata;
 };

diff --git a/src/modules/bluetooth/module-bluez5-device.c
b/src/modules/bluetooth/module-bluez5-device.c
index 351ad12..49a6dba 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -64,6 +64,7 @@ PA_MODULE_USAGE("path=<device object path>"
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define BLUEZ_MAX_GAIN 127

 static const char* const valid_modargs[] = {
     "path",
@@ -113,6 +114,7 @@ struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_gain_changed_slot;

     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1056,6 +1058,37 @@ static void source_set_volume_cb(pa_source *s) {
     u->transport->set_microphone_gain(u->transport, gain);
 }

+static void source_set_a2dp_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) *
BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+
+    u->transport->set_volume(u->transport, gain);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -1109,6 +1142,9 @@ static int add_source(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_source_set_set_volume_callback(u->source,
source_set_volume_cb);
         u->source->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_source_set_set_volume_callback(u->source,
source_set_a2dp_volume_cb);
+        u->source->n_volume_steps = 1;
     }
     return 0;
 }
@@ -1230,6 +1266,40 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->transport->set_speaker_gain(u->transport, gain);
 }

+static void sink_set_a2dp_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) *
BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    if(u->transport->set_volume(u->transport, gain) < 0) {
+        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels,
PA_VOLUME_NORM);
+        pa_sink_set_set_volume_callback(s, NULL);
+        u->transport->a2dp_gain = 0xFFu;
+    }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -1284,6 +1354,9 @@ static int add_sink(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+        pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        u->sink->n_volume_steps = 1;
     }
     return 0;
 }
@@ -2340,6 +2413,53 @@ static pa_hook_result_t
transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }

+static pa_hook_result_t
transport_a2dp_gain_changed_cb(pa_bluetooth_discovery *y,
pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+    uint16_t gain;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    gain = t->a2dp_gain;
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+
+    if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK){
+        pa_assert(u->sink);
+
+        if(!u->sink->set_volume){
+            pa_cvolume_set(&u->sink->soft_volume,
u->sample_spec.channels, PA_VOLUME_NORM);
+            pa_sink_set_set_volume_callback(u->sink,
sink_set_a2dp_volume_cb);
+        }
+
+
+        if (gain == 0)
+            pa_sink_mute_changed(u->sink, true);
+        else if(u->sink->muted)
+            pa_sink_mute_changed(u->sink, false);
+
+        pa_sink_volume_changed(u->sink, &v);
+    } else if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE){
+        pa_assert(u->source);
+
+
+        if (gain == 0)
+            pa_source_mute_changed(u->source, true);
+        else if(u->source->muted)
+            pa_source_mute_changed(u->source, false);
+
+        pa_source_volume_changed(u->source, &v);
+    }
+
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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 *m = BLUETOOTH_MSG(obj);
@@ -2430,6 +2550,9 @@ int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery,
PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL,
(pa_hook_cb_t) transport_microphone_gain_changed_cb, u);

+    u->transport_a2dp_gain_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery,
PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), PA_HOOK_NORMAL,
(pa_hook_cb_t) transport_a2dp_gain_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;

@@ -2491,6 +2614,9 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);

+    if (u->transport_a2dp_gain_changed_slot)
+ pa_hook_slot_free(u->transport_a2dp_gain_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
EHfive
2018-10-03 13:12:54 UTC
Permalink
Post by EHfive
Require bluez-tools/mpris-proxy running. (No hurt if dosen't)
If you need play/pause/next... controls , add configurations below to
/etc/dbus-1/system.d/
------------------------ mpris.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus
Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy context="default">
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>
</busconfig>
------------------------
An alternative

https://gist.github.com/EHfive/e2a28d0279a6247fab4bac93d73b8571

A python script which implement org.mpris.MediaPlayer2.Player and
register mpris player object by calling org.bluez.Media1.RegisterPlayer.
Post by EHfive
===================
diff --git a/src/modules/bluetooth/bluez5-util.c
b/src/modules/bluetooth/bluez5-util.c
index 2d83373..72cd05a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -348,6 +348,50 @@ void
pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }
+static int bluez5_transport_set_property(pa_bluetooth_transport *t,
const char *prop_name, int prop_type, void *prop_value){
+    DBusMessage *m, *r;
+    DBusError err;
+    DBusMessageIter i;
+    const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
+
%s",t->owner, t->path, prop_name);
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path,
"org.freedesktop.DBus.Properties", "Set"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING,
&interface));
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING,
&prop_name));
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    r =
dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection),
m, -1, &err);
+    dbus_message_unref(m);
+    m = NULL;
+    if(r) {
+        dbus_message_unref(r);
+        r = NULL;
+    }
+
+    if(dbus_error_is_set(&err)) {
+        pa_log_debug("Failed to set property \"%s.%s\"", interface,
prop_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int bluez5_transport_set_volume(pa_bluetooth_transport *t,
uint16_t volume){
+    if(t->a2dp_gain == volume)
+        return 0;
+    return bluez5_transport_set_property(t, "Volume",
DBUS_TYPE_UINT16, &volume);
+}
+
 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t,
bool optional, size_t *imtu, size_t *omtu) {
     DBusMessage *m, *r;
     DBusError err;
@@ -441,6 +485,14 @@ bool
pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
+void pa_transport_set_a2dp_gain(pa_bluetooth_transport *t, uint16_t
a2dp_gain){
+    if(t->a2dp_gain == a2dp_gain)
+        return;
+    t->a2dp_gain = a2dp_gain;
+ pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery,
PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), t);
+}
+
+
 static int transport_state_from_string(const char* value,
pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -483,6 +535,18 @@ static void
parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
                 pa_bluetooth_transport_set_state(t, state);
             }
+            break;
+        }
+        case DBUS_TYPE_UINT16: {
+
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                pa_log_debug("Transport Volume Changed to %u ", value);
+                pa_transport_set_a2dp_gain(t, value);
+            }
+
             break;
         }
     }
@@ -1468,6 +1532,7 @@ static DBusMessage
*endpoint_set_configuration(DBusConnection *conn, DBusMessage
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    t->set_volume = bluez5_transport_set_volume;
     pa_bluetooth_transport_put(t);
     pa_log_debug("Transport %s available for profile %s", t->path,
pa_bluetooth_profile_to_string(t->profile));
diff --git a/src/modules/bluetooth/bluez5-util.h
b/src/modules/bluetooth/bluez5-util.h
index ad30708..5b8149d 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -47,6 +47,7 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call
data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call
data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call
data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED,        /* Call
data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
@@ -70,6 +71,7 @@ typedef void
(*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void
(*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void
(*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport
*t, uint16_t gain);
 typedef void
(*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport
*t, uint16_t gain);
+typedef int
(*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t,
uint16_t volume);
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -84,6 +86,7 @@ struct pa_bluetooth_transport {
     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t a2dp_gain;
     pa_bluetooth_transport_state_t state;
@@ -92,6 +95,7 @@ struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_volume_cb set_volume;
     void *userdata;
 };
diff --git a/src/modules/bluetooth/module-bluez5-device.c
b/src/modules/bluetooth/module-bluez5-device.c
index 351ad12..49a6dba 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -64,6 +64,7 @@ PA_MODULE_USAGE("path=<device object path>"
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define BLUEZ_MAX_GAIN 127
 static const char* const valid_modargs[] = {
     "path",
@@ -113,6 +114,7 @@ struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_gain_changed_slot;
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1056,6 +1058,37 @@ static void source_set_volume_cb(pa_source *s) {
     u->transport->set_microphone_gain(u->transport, gain);
 }
+static void source_set_a2dp_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) *
BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+
+    u->transport->set_volume(u->transport, gain);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -1109,6 +1142,9 @@ static int add_source(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_source_set_set_volume_callback(u->source,
source_set_volume_cb);
         u->source->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_source_set_set_volume_callback(u->source,
source_set_a2dp_volume_cb);
+        u->source->n_volume_steps = 1;
     }
     return 0;
 }
@@ -1230,6 +1266,40 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->transport->set_speaker_gain(u->transport, gain);
 }
+static void sink_set_a2dp_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) *
BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    if(u->transport->set_volume(u->transport, gain) < 0) {
+        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels,
PA_VOLUME_NORM);
+        pa_sink_set_set_volume_callback(s, NULL);
+        u->transport->a2dp_gain = 0xFFu;
+    }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -1284,6 +1354,9 @@ static int add_sink(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+        pa_sink_set_set_volume_callback(u->sink,
sink_set_a2dp_volume_cb);
+        u->sink->n_volume_steps = 1;
     }
     return 0;
 }
@@ -2340,6 +2413,53 @@ static pa_hook_result_t
transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
+static pa_hook_result_t
transport_a2dp_gain_changed_cb(pa_bluetooth_discovery *y,
pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+    uint16_t gain;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    gain = t->a2dp_gain;
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+
+    if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK){
+        pa_assert(u->sink);
+
+        if(!u->sink->set_volume){
+            pa_cvolume_set(&u->sink->soft_volume,
u->sample_spec.channels, PA_VOLUME_NORM);
+            pa_sink_set_set_volume_callback(u->sink,
sink_set_a2dp_volume_cb);
+        }
+
+
+        if (gain == 0)
+            pa_sink_mute_changed(u->sink, true);
+        else if(u->sink->muted)
+            pa_sink_mute_changed(u->sink, false);
+
+        pa_sink_volume_changed(u->sink, &v);
+    } else if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE){
+        pa_assert(u->source);
+
+
+        if (gain == 0)
+            pa_source_mute_changed(u->source, true);
+        else if(u->source->muted)
+            pa_source_mute_changed(u->source, false);
+
+        pa_source_volume_changed(u->source, &v);
+    }
+
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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 *m = BLUETOOTH_MSG(obj);
@@ -2430,6 +2550,9 @@ int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery,
PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL,
(pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
+    u->transport_a2dp_gain_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery,
PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), PA_HOOK_NORMAL,
(pa_hook_cb_t) transport_a2dp_gain_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
@@ -2491,6 +2614,9 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
+    if (u->transport_a2dp_gain_changed_slot)
+ pa_hook_slot_free(u->transport_a2dp_gain_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
ValdikSS
2018-10-14 18:54:11 UTC
Permalink
Can you please integrate exceptions from the Android database?
https://android.googlesource.com/platform/system/bt/+/master/device/include/interop_database.h
Ones with INTEROP_DISABLE_ABSOLUTE_VOLUME
Post by EHfive
Post by EHfive
Require bluez-tools/mpris-proxy running. (No hurt if dosen't)
If you need play/pause/next... controls , add configurations below to /etc/dbus-1/system.d/
------------------------ mpris.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy context="default">
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>
</busconfig>
------------------------
An alternative
https://gist.github.com/EHfive/e2a28d0279a6247fab4bac93d73b8571
A python script which implement org.mpris.MediaPlayer2.Player and register mpris player object by calling org.bluez.Media1.RegisterPlayer.
Post by EHfive
===================
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d83373..72cd05a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -348,6 +348,50 @@ void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }
+static int bluez5_transport_set_property(pa_bluetooth_transport *t, const char *prop_name, int prop_type, void *prop_value){
+    DBusMessage *m, *r;
+    DBusError err;
+    DBusMessageIter i;
+    const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
+
+    pa_log_debug("Setting property, Owner: %s; Path: %s; Property: %s",t->owner, t->path, prop_name);
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.freedesktop.DBus.Properties", "Set"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface));
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name));
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+    dbus_message_unref(m);
+    m = NULL;
+    if(r) {
+        dbus_message_unref(r);
+        r = NULL;
+    }
+
+    if(dbus_error_is_set(&err)) {
+        pa_log_debug("Failed to set property \"%s.%s\"", interface, prop_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int bluez5_transport_set_volume(pa_bluetooth_transport *t, uint16_t volume){
+    if(t->a2dp_gain == volume)
+        return 0;
+    return bluez5_transport_set_property(t, "Volume", DBUS_TYPE_UINT16, &volume);
+}
+
 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
     DBusMessage *m, *r;
     DBusError err;
@@ -441,6 +485,14 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
+void pa_transport_set_a2dp_gain(pa_bluetooth_transport *t, uint16_t a2dp_gain){
+    if(t->a2dp_gain == a2dp_gain)
+        return;
+    t->a2dp_gain = a2dp_gain;
+ pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), t);
+}
+
+
 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -483,6 +535,18 @@ static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
                 pa_bluetooth_transport_set_state(t, state);
             }
+            break;
+        }
+        case DBUS_TYPE_UINT16: {
+
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                pa_log_debug("Transport Volume Changed to %u ", value);
+                pa_transport_set_a2dp_gain(t, value);
+            }
+
             break;
         }
     }
@@ -1468,6 +1532,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    t->set_volume = bluez5_transport_set_volume;
     pa_bluetooth_transport_put(t);
     pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ad30708..5b8149d 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -47,6 +47,7 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED,        /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
@@ -70,6 +71,7 @@ typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
 typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
+typedef int (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, uint16_t volume);
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -84,6 +86,7 @@ struct pa_bluetooth_transport {
     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t a2dp_gain;
     pa_bluetooth_transport_state_t state;
@@ -92,6 +95,7 @@ struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_volume_cb set_volume;
     void *userdata;
 };
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 351ad12..49a6dba 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -64,6 +64,7 @@ PA_MODULE_USAGE("path=<device object path>"
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define BLUEZ_MAX_GAIN 127
 static const char* const valid_modargs[] = {
     "path",
@@ -113,6 +114,7 @@ struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_gain_changed_slot;
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1056,6 +1058,37 @@ static void source_set_volume_cb(pa_source *s) {
     u->transport->set_microphone_gain(u->transport, gain);
 }
+static void source_set_a2dp_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+
+    u->transport->set_volume(u->transport, gain);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -1109,6 +1142,9 @@ static int add_source(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_source_set_set_volume_callback(u->source, source_set_a2dp_volume_cb);
+        u->source->n_volume_steps = 1;
     }
     return 0;
 }
@@ -1230,6 +1266,40 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->transport->set_speaker_gain(u->transport, gain);
 }
+static void sink_set_a2dp_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    if(u->transport->set_volume(u->transport, gain) < 0) {
+        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+        pa_sink_set_set_volume_callback(s, NULL);
+        u->transport->a2dp_gain = 0xFFu;
+    }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -1284,6 +1354,9 @@ static int add_sink(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+        pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        u->sink->n_volume_steps = 1;
     }
     return 0;
 }
@@ -2340,6 +2413,53 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
+static pa_hook_result_t transport_a2dp_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+    uint16_t gain;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    gain = t->a2dp_gain;
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+
+    if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK){
+        pa_assert(u->sink);
+
+        if(!u->sink->set_volume){
+            pa_cvolume_set(&u->sink->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+            pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        }
+
+
+        if (gain == 0)
+            pa_sink_mute_changed(u->sink, true);
+        else if(u->sink->muted)
+            pa_sink_mute_changed(u->sink, false);
+
+        pa_sink_volume_changed(u->sink, &v);
+    } else if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE){
+        pa_assert(u->source);
+
+
+        if (gain == 0)
+            pa_source_mute_changed(u->source, true);
+        else if(u->source->muted)
+            pa_source_mute_changed(u->source, false);
+
+        pa_source_volume_changed(u->source, &v);
+    }
+
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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 *m = BLUETOOTH_MSG(obj);
@@ -2430,6 +2550,9 @@ int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
+    u->transport_a2dp_gain_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_a2dp_gain_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
@@ -2491,6 +2614,9 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
+    if (u->transport_a2dp_gain_changed_slot)
+ pa_hook_slot_free(u->transport_a2dp_gain_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
EHfive
2018-10-14 23:44:25 UTC
Permalink
Simply add some lines. Also, volume means host (as sink role) volume
when in profile a2dp_source(device as source role) , don't need to
disable it.


But should we do it in PA? Isn't it Bluez's job?
Post by ValdikSS
Can you please integrate exceptions from the Android database?
https://android.googlesource.com/platform/system/bt/+/master/device/include/interop_database.h
Ones with INTEROP_DISABLE_ABSOLUTE_VOLUME
Post by EHfive
Post by EHfive
Require bluez-tools/mpris-proxy running. (No hurt if dosen't)
If you need play/pause/next... controls , add configurations below to /etc/dbus-1/system.d/
------------------------ mpris.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy context="default">
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>
</busconfig>
------------------------
An alternative
https://gist.github.com/EHfive/e2a28d0279a6247fab4bac93d73b8571
A python script which implement org.mpris.MediaPlayer2.Player and register mpris player object by calling org.bluez.Media1.RegisterPlayer.
Post by EHfive
===================
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d83373..72cd05a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -348,6 +348,50 @@ void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }
+static int bluez5_transport_set_property(pa_bluetooth_transport *t, const char *prop_name, int prop_type, void *prop_value){
+    DBusMessage *m, *r;
+    DBusError err;
+    DBusMessageIter i;
+    const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
+
+    pa_log_debug("Setting property, Owner: %s; Path: %s; Property: %s",t->owner, t->path, prop_name);
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.freedesktop.DBus.Properties", "Set"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface));
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name));
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+    dbus_message_unref(m);
+    m = NULL;
+    if(r) {
+        dbus_message_unref(r);
+        r = NULL;
+    }
+
+    if(dbus_error_is_set(&err)) {
+        pa_log_debug("Failed to set property \"%s.%s\"", interface, prop_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int bluez5_transport_set_volume(pa_bluetooth_transport *t, uint16_t volume){
+    if(t->a2dp_gain == volume)
+        return 0;
+    return bluez5_transport_set_property(t, "Volume", DBUS_TYPE_UINT16, &volume);
+}
+
 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
     DBusMessage *m, *r;
     DBusError err;
@@ -441,6 +485,14 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
+void pa_transport_set_a2dp_gain(pa_bluetooth_transport *t, uint16_t a2dp_gain){
+    if(t->a2dp_gain == a2dp_gain)
+        return;
+    t->a2dp_gain = a2dp_gain;
+ pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), t);
+}
+
+
 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -483,6 +535,18 @@ static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
                 pa_bluetooth_transport_set_state(t, state);
             }
+            break;
+        }
+        case DBUS_TYPE_UINT16: {
+
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                pa_log_debug("Transport Volume Changed to %u ", value);
+                pa_transport_set_a2dp_gain(t, value);
+            }
+
             break;
         }
     }
@@ -1468,6 +1532,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    t->set_volume = bluez5_transport_set_volume;
     pa_bluetooth_transport_put(t);
     pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ad30708..5b8149d 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -47,6 +47,7 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED,        /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
@@ -70,6 +71,7 @@ typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
 typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
+typedef int (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, uint16_t volume);
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -84,6 +86,7 @@ struct pa_bluetooth_transport {
     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t a2dp_gain;
     pa_bluetooth_transport_state_t state;
@@ -92,6 +95,7 @@ struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_volume_cb set_volume;
     void *userdata;
 };
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 351ad12..49a6dba 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -64,6 +64,7 @@ PA_MODULE_USAGE("path=<device object path>"
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define BLUEZ_MAX_GAIN 127
 static const char* const valid_modargs[] = {
     "path",
@@ -113,6 +114,7 @@ struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_gain_changed_slot;
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1056,6 +1058,37 @@ static void source_set_volume_cb(pa_source *s) {
     u->transport->set_microphone_gain(u->transport, gain);
 }
+static void source_set_a2dp_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+
+    u->transport->set_volume(u->transport, gain);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -1109,6 +1142,9 @@ static int add_source(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_source_set_set_volume_callback(u->source, source_set_a2dp_volume_cb);
+        u->source->n_volume_steps = 1;
     }
     return 0;
 }
@@ -1230,6 +1266,40 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->transport->set_speaker_gain(u->transport, gain);
 }
+static void sink_set_a2dp_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    if(u->transport->set_volume(u->transport, gain) < 0) {
+        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+        pa_sink_set_set_volume_callback(s, NULL);
+        u->transport->a2dp_gain = 0xFFu;
+    }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -1284,6 +1354,9 @@ static int add_sink(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
bdaddr_t addr = u->device->address;

if (! match_DISABLE_ABSOLUTE_VOLUME(addr))

{
Post by ValdikSS
Post by EHfive
Post by EHfive
+        pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        u->sink->n_volume_steps = 1;
}
Post by ValdikSS
Post by EHfive
Post by EHfive
     }
     return 0;
 }
@@ -2340,6 +2413,53 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
+static pa_hook_result_t transport_a2dp_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+    uint16_t gain;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
bdaddr_t addr = u->device->address;

if (! match_DISABLE_ABSOLUTE_VOLUME(addr))

    return PA_HOOK_OK;
Post by ValdikSS
Post by EHfive
Post by EHfive
+    gain = t->a2dp_gain;
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+
+    if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK){
+        pa_assert(u->sink);
+
+        if(!u->sink->set_volume){
+            pa_cvolume_set(&u->sink->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+            pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        }
+
+
+        if (gain == 0)
+            pa_sink_mute_changed(u->sink, true);
+        else if(u->sink->muted)
+            pa_sink_mute_changed(u->sink, false);
+
+        pa_sink_volume_changed(u->sink, &v);
+    } else if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE){
+        pa_assert(u->source);
+
+
+        if (gain == 0)
+            pa_source_mute_changed(u->source, true);
+        else if(u->source->muted)
+            pa_source_mute_changed(u->source, false);
+
+        pa_source_volume_changed(u->source, &v);
+    }
+
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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 *m = BLUETOOTH_MSG(obj);
@@ -2430,6 +2550,9 @@ int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
+    u->transport_a2dp_gain_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_a2dp_gain_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
@@ -2491,6 +2614,9 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
+    if (u->transport_a2dp_gain_changed_slot)
+ pa_hook_slot_free(u->transport_a2dp_gain_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
EHfive
2018-10-14 23:54:47 UTC
Permalink
Post by EHfive
Simply add some lines. Also, volume means host (as sink role) volume
when in profile a2dp_source(device as source role) , don't need to
disable it.
But should we do it in PA? Isn't it Bluez's job?
Post by ValdikSS
Can you please integrate exceptions from the Android database?
https://android.googlesource.com/platform/system/bt/+/master/device/include/interop_database.h
Ones with INTEROP_DISABLE_ABSOLUTE_VOLUME
Post by EHfive
Post by EHfive
Require bluez-tools/mpris-proxy running. (No hurt if dosen't)
If you need play/pause/next... controls , add configurations below to /etc/dbus-1/system.d/
------------------------ mpris.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy context="default">
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>
</busconfig>
------------------------
An alternative
https://gist.github.com/EHfive/e2a28d0279a6247fab4bac93d73b8571
A python script which implement org.mpris.MediaPlayer2.Player and register mpris player object by calling org.bluez.Media1.RegisterPlayer.
Post by EHfive
===================
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d83373..72cd05a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -348,6 +348,50 @@ void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }
+static int bluez5_transport_set_property(pa_bluetooth_transport *t, const char *prop_name, int prop_type, void *prop_value){
+    DBusMessage *m, *r;
+    DBusError err;
+    DBusMessageIter i;
+    const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
+
+    pa_log_debug("Setting property, Owner: %s; Path: %s; Property: %s",t->owner, t->path, prop_name);
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.freedesktop.DBus.Properties", "Set"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface));
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name));
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+    dbus_message_unref(m);
+    m = NULL;
+    if(r) {
+        dbus_message_unref(r);
+        r = NULL;
+    }
+
+    if(dbus_error_is_set(&err)) {
+        pa_log_debug("Failed to set property \"%s.%s\"", interface, prop_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int bluez5_transport_set_volume(pa_bluetooth_transport *t, uint16_t volume){
+    if(t->a2dp_gain == volume)
+        return 0;
+    return bluez5_transport_set_property(t, "Volume", DBUS_TYPE_UINT16, &volume);
+}
+
 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
     DBusMessage *m, *r;
     DBusError err;
@@ -441,6 +485,14 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
+void pa_transport_set_a2dp_gain(pa_bluetooth_transport *t, uint16_t a2dp_gain){
+    if(t->a2dp_gain == a2dp_gain)
+        return;
+    t->a2dp_gain = a2dp_gain;
+ pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), t);
+}
+
+
 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -483,6 +535,18 @@ static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
                 pa_bluetooth_transport_set_state(t, state);
             }
+            break;
+        }
+        case DBUS_TYPE_UINT16: {
+
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                pa_log_debug("Transport Volume Changed to %u ", value);
+                pa_transport_set_a2dp_gain(t, value);
+            }
+
             break;
         }
     }
@@ -1468,6 +1532,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    t->set_volume = bluez5_transport_set_volume;
     pa_bluetooth_transport_put(t);
     pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ad30708..5b8149d 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -47,6 +47,7 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED,        /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
@@ -70,6 +71,7 @@ typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
 typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
+typedef int (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, uint16_t volume);
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -84,6 +86,7 @@ struct pa_bluetooth_transport {
     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t a2dp_gain;
     pa_bluetooth_transport_state_t state;
@@ -92,6 +95,7 @@ struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_volume_cb set_volume;
     void *userdata;
 };
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 351ad12..49a6dba 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -64,6 +64,7 @@ PA_MODULE_USAGE("path=<device object path>"
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define BLUEZ_MAX_GAIN 127
 static const char* const valid_modargs[] = {
     "path",
@@ -113,6 +114,7 @@ struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_gain_changed_slot;
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1056,6 +1058,37 @@ static void source_set_volume_cb(pa_source *s) {
     u->transport->set_microphone_gain(u->transport, gain);
 }
+static void source_set_a2dp_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+
+    u->transport->set_volume(u->transport, gain);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -1109,6 +1142,9 @@ static int add_source(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_source_set_set_volume_callback(u->source, source_set_a2dp_volume_cb);
+        u->source->n_volume_steps = 1;
     }
     return 0;
 }
@@ -1230,6 +1266,40 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->transport->set_speaker_gain(u->transport, gain);
 }
+static void sink_set_a2dp_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    if(u->transport->set_volume(u->transport, gain) < 0) {
+        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+        pa_sink_set_set_volume_callback(s, NULL);
+        u->transport->a2dp_gain = 0xFFu;
+    }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -1284,6 +1354,9 @@ static int add_sink(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
bdaddr_t addr = u->device->address;
if (! match_DISABLE_ABSOLUTE_VOLUME(addr))
{
Post by ValdikSS
Post by EHfive
Post by EHfive
+        pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        u->sink->n_volume_steps = 1;
}
Post by ValdikSS
Post by EHfive
Post by EHfive
     }
     return 0;
 }
@@ -2340,6 +2413,53 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
+static pa_hook_result_t transport_a2dp_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+    uint16_t gain;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
bdaddr_t addr = u->device->address;
here should be " if (match_DISABLE_ABSOLUTE_VOLUME(addr)) "
Post by EHfive
if (! match_DISABLE_ABSOLUTE_VOLUME(addr))
    return PA_HOOK_OK;
Post by ValdikSS
Post by EHfive
Post by EHfive
+    gain = t->a2dp_gain;
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+
+    if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK){
+        pa_assert(u->sink);
+
+        if(!u->sink->set_volume){
+            pa_cvolume_set(&u->sink->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+            pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        }
+
+
+        if (gain == 0)
+            pa_sink_mute_changed(u->sink, true);
+        else if(u->sink->muted)
+            pa_sink_mute_changed(u->sink, false);
+
+        pa_sink_volume_changed(u->sink, &v);
+    } else if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE){
+        pa_assert(u->source);
+
+
+        if (gain == 0)
+            pa_source_mute_changed(u->source, true);
+        else if(u->source->muted)
+            pa_source_mute_changed(u->source, false);
+
+        pa_source_volume_changed(u->source, &v);
+    }
+
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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 *m = BLUETOOTH_MSG(obj);
@@ -2430,6 +2550,9 @@ int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
+    u->transport_a2dp_gain_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_a2dp_gain_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
@@ -2491,6 +2614,9 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
+    if (u->transport_a2dp_gain_changed_slot)
+ pa_hook_slot_free(u->transport_a2dp_gain_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
ValdikSS
2018-10-20 12:34:17 UTC
Permalink
There are Bluetooth devices which support absolute volume but does not handle it correctly. For example, some Bluetooth headsets are unacceptable loud on the lowest volume, that's why it's better to completely disable absolute volume control for such devices.
Google created a database for broken devices for Android, which I have linked. Please add these exceptions if possible.
Post by ValdikSS
Can you please integrate exceptions from the Android database?
https://android.googlesource.com/platform/system/bt/+/master/device/include/interop_database.h
Ones with INTEROP_DISABLE_ABSOLUTE_VOLUME
Post by EHfive
Post by EHfive
Require bluez-tools/mpris-proxy running. (No hurt if dosen't)
If you need play/pause/next... controls , add configurations below to /etc/dbus-1/system.d/
------------------------ mpris.conf
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy context="default">
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>
</busconfig>
------------------------
An alternative
https://gist.github.com/EHfive/e2a28d0279a6247fab4bac93d73b8571
A python script which implement org.mpris.MediaPlayer2.Player and register mpris player object by calling org.bluez.Media1.RegisterPlayer.
Post by EHfive
===================
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 2d83373..72cd05a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -348,6 +348,50 @@ void pa_bluetooth_transport_free(pa_bluetooth_transport *t) {
     pa_xfree(t);
 }
+static int bluez5_transport_set_property(pa_bluetooth_transport *t, const char *prop_name, int prop_type, void *prop_value){
+    DBusMessage *m, *r;
+    DBusError err;
+    DBusMessageIter i;
+    const char * interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE;
+
+    pa_log_debug("Setting property, Owner: %s; Path: %s; Property: %s",t->owner, t->path, prop_name);
+
+    pa_assert(t);
+    pa_assert(t->device);
+    pa_assert(t->device->discovery);
+
+    dbus_error_init(&err);
+
+    pa_assert_se(m = dbus_message_new_method_call(t->owner, t->path, "org.freedesktop.DBus.Properties", "Set"));
+
+    dbus_message_iter_init_append(m, &i);
+
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface));
+    pa_assert_se(dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &prop_name));
+    pa_dbus_append_basic_variant(&i, prop_type, prop_value);
+
+    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->device->discovery->connection), m, -1, &err);
+    dbus_message_unref(m);
+    m = NULL;
+    if(r) {
+        dbus_message_unref(r);
+        r = NULL;
+    }
+
+    if(dbus_error_is_set(&err)) {
+        pa_log_debug("Failed to set property \"%s.%s\"", interface, prop_name);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int bluez5_transport_set_volume(pa_bluetooth_transport *t, uint16_t volume){
+    if(t->a2dp_gain == volume)
+        return 0;
+    return bluez5_transport_set_property(t, "Volume", DBUS_TYPE_UINT16, &volume);
+}
+
 static int bluez5_transport_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
     DBusMessage *m, *r;
     DBusError err;
@@ -441,6 +485,14 @@ bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     return false;
 }
+void pa_transport_set_a2dp_gain(pa_bluetooth_transport *t, uint16_t a2dp_gain){
+    if(t->a2dp_gain == a2dp_gain)
+        return;
+    t->a2dp_gain = a2dp_gain;
+ pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), t);
+}
+
+
 static int transport_state_from_string(const char* value, pa_bluetooth_transport_state_t *state) {
     pa_assert(value);
     pa_assert(state);
@@ -483,6 +535,18 @@ static void parse_transport_property(pa_bluetooth_transport *t, DBusMessageIter
                 pa_bluetooth_transport_set_state(t, state);
             }
+            break;
+        }
+        case DBUS_TYPE_UINT16: {
+
+            uint16_t value;
+            dbus_message_iter_get_basic(&variant_i, &value);
+
+            if (pa_streq(key, "Volume")) {
+                pa_log_debug("Transport Volume Changed to %u ", value);
+                pa_transport_set_a2dp_gain(t, value);
+            }
+
             break;
         }
     }
@@ -1468,6 +1532,7 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    t->set_volume = bluez5_transport_set_volume;
     pa_bluetooth_transport_put(t);
     pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index ad30708..5b8149d 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -47,6 +47,7 @@ typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED,            /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED,  /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_TRANSPORT_SPEAKER_GAIN_CHANGED,     /* Call data: pa_bluetooth_transport */
+    PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED,        /* Call data: pa_bluetooth_transport */
     PA_BLUETOOTH_HOOK_MAX
 } pa_bluetooth_hook_t;
@@ -70,6 +71,7 @@ typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_set_speaker_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
 typedef void (*pa_bluetooth_transport_set_microphone_gain_cb)(pa_bluetooth_transport *t, uint16_t gain);
+typedef int (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, uint16_t volume);
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -84,6 +86,7 @@ struct pa_bluetooth_transport {
     uint16_t microphone_gain;
     uint16_t speaker_gain;
+    uint16_t a2dp_gain;
     pa_bluetooth_transport_state_t state;
@@ -92,6 +95,7 @@ struct pa_bluetooth_transport {
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_speaker_gain_cb set_speaker_gain;
     pa_bluetooth_transport_set_microphone_gain_cb set_microphone_gain;
+    pa_bluetooth_transport_set_volume_cb set_volume;
     void *userdata;
 };
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 351ad12..49a6dba 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -64,6 +64,7 @@ PA_MODULE_USAGE("path=<device object path>"
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
 #define HSP_MAX_GAIN 15
+#define BLUEZ_MAX_GAIN 127
 static const char* const valid_modargs[] = {
     "path",
@@ -113,6 +114,7 @@ struct userdata {
     pa_hook_slot *transport_state_changed_slot;
     pa_hook_slot *transport_speaker_gain_changed_slot;
     pa_hook_slot *transport_microphone_gain_changed_slot;
+    pa_hook_slot *transport_a2dp_gain_changed_slot;
     pa_bluetooth_discovery *discovery;
     pa_bluetooth_device *device;
@@ -1056,6 +1058,37 @@ static void source_set_volume_cb(pa_source *s) {
     u->transport->set_microphone_gain(u->transport, gain);
 }
+static void source_set_a2dp_volume_cb(pa_source *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->source == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+    pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, volume);
+
+    u->transport->set_volume(u->transport, gain);
+}
+
 /* Run from main thread */
 static int add_source(struct userdata *u) {
     pa_source_new_data data;
@@ -1109,6 +1142,9 @@ static int add_source(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_source_set_set_volume_callback(u->source, source_set_volume_cb);
         u->source->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        pa_source_set_set_volume_callback(u->source, source_set_a2dp_volume_cb);
+        u->source->n_volume_steps = 1;
     }
     return 0;
 }
@@ -1230,6 +1266,40 @@ static void sink_set_volume_cb(pa_sink *s) {
     u->transport->set_speaker_gain(u->transport, gain);
 }
+static void sink_set_a2dp_volume_cb(pa_sink *s) {
+    uint16_t gain;
+    pa_volume_t volume;
+    struct userdata *u;
+
+    pa_assert(s);
+    pa_assert(s->core);
+
+    u = s->userdata;
+
+    pa_assert(u);
+    pa_assert(u->sink == s);
+
+    if (u->transport->set_volume == NULL)
+        return;
+
+    gain = (uint16_t) ((pa_cvolume_max(&s->real_volume) * BLUEZ_MAX_GAIN) / PA_VOLUME_NORM);
+
+    pa_log_debug("Real Volume Gain:%u", gain);
+
+    if (gain > BLUEZ_MAX_GAIN)
+        gain = BLUEZ_MAX_GAIN;
+
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&s->real_volume, u->sample_spec.channels, volume);
+
+    if(u->transport->set_volume(u->transport, gain) < 0) {
+        pa_cvolume_set(&s->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+        pa_sink_set_set_volume_callback(s, NULL);
+        u->transport->a2dp_gain = 0xFFu;
+    }
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     pa_sink_new_data data;
@@ -1284,6 +1354,9 @@ static int add_sink(struct userdata *u) {
     if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
         pa_sink_set_set_volume_callback(u->sink, sink_set_volume_cb);
         u->sink->n_volume_steps = 16;
+    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+        pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        u->sink->n_volume_steps = 1;
     }
     return 0;
 }
@@ -2340,6 +2413,53 @@ static pa_hook_result_t transport_microphone_gain_changed_cb(pa_bluetooth_discov
     return PA_HOOK_OK;
 }
+static pa_hook_result_t transport_a2dp_gain_changed_cb(pa_bluetooth_discovery *y, pa_bluetooth_transport *t, struct userdata *u) {
+    pa_volume_t volume;
+    pa_cvolume v;
+    uint16_t gain;
+
+    pa_assert(t);
+    pa_assert(u);
+
+    if (t != u->transport)
+        return PA_HOOK_OK;
+
+    gain = t->a2dp_gain;
+    volume = (pa_volume_t) (gain * PA_VOLUME_NORM / BLUEZ_MAX_GAIN);
+
+    pa_cvolume_set(&v, u->sample_spec.channels, volume);
+
+    if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK){
+        pa_assert(u->sink);
+
+        if(!u->sink->set_volume){
+            pa_cvolume_set(&u->sink->soft_volume, u->sample_spec.channels, PA_VOLUME_NORM);
+            pa_sink_set_set_volume_callback(u->sink, sink_set_a2dp_volume_cb);
+        }
+
+
+        if (gain == 0)
+            pa_sink_mute_changed(u->sink, true);
+        else if(u->sink->muted)
+            pa_sink_mute_changed(u->sink, false);
+
+        pa_sink_volume_changed(u->sink, &v);
+    } else if(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE){
+        pa_assert(u->source);
+
+
+        if (gain == 0)
+            pa_source_mute_changed(u->source, true);
+        else if(u->source->muted)
+            pa_source_mute_changed(u->source, false);
+
+        pa_source_volume_changed(u->source, &v);
+    }
+
+
+    return PA_HOOK_OK;
+}
+
 /* Run 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 *m = BLUETOOTH_MSG(obj);
@@ -2430,6 +2550,9 @@ int pa__init(pa_module* m) {
     u->transport_microphone_gain_changed_slot =
pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_microphone_gain_changed_cb, u);
+    u->transport_a2dp_gain_changed_slot =
+ pa_hook_connect(pa_bluetooth_discovery_hook(u->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_A2DP_GAIN_CHANGED), PA_HOOK_NORMAL, (pa_hook_cb_t) transport_a2dp_gain_changed_cb, u);
+
     if (add_card(u) < 0)
         goto fail;
@@ -2491,6 +2614,9 @@ void pa__done(pa_module *m) {
     if (u->transport_microphone_gain_changed_slot)
pa_hook_slot_free(u->transport_microphone_gain_changed_slot);
+    if (u->transport_a2dp_gain_changed_slot)
+ pa_hook_slot_free(u->transport_a2dp_gain_changed_slot);
+
     if (u->sbc_info.buffer)
         pa_xfree(u->sbc_info.buffer);
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
_______________________________________________
pulseaudio-discuss mailing list
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss
Loading...