Last active
October 3, 2018 13:20
-
-
Save EHfive/e2a28d0279a6247fab4bac93d73b8571 to your computer and use it in GitHub Desktop.
Mpris Player register to bluez dbus adapter object https://lists.freedesktop.org/archives/pulseaudio-discuss/2018-October/030537.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
## From bluez (GPL2) | |
import dbus | |
SERVICE_NAME = "org.bluez" | |
ADAPTER_INTERFACE = SERVICE_NAME + ".Adapter1" | |
DEVICE_INTERFACE = SERVICE_NAME + ".Device1" | |
def get_managed_objects(): | |
bus = dbus.SystemBus() | |
manager = dbus.Interface(bus.get_object("org.bluez", "/"), | |
"org.freedesktop.DBus.ObjectManager") | |
return manager.GetManagedObjects() | |
def find_adapter(pattern=None): | |
return find_adapter_in_objects(get_managed_objects(), pattern) | |
def find_adapter_in_objects(objects, pattern=None): | |
bus = dbus.SystemBus() | |
for path, ifaces in objects.items(): | |
adapter = ifaces.get(ADAPTER_INTERFACE) | |
if adapter is None: | |
continue | |
if not pattern or pattern == adapter["Address"] or \ | |
path.endswith(pattern): | |
obj = bus.get_object(SERVICE_NAME, path) | |
return dbus.Interface(obj, ADAPTER_INTERFACE) | |
raise Exception("Bluetooth adapter not found") | |
def find_device(device_address, adapter_pattern=None): | |
return find_device_in_objects(get_managed_objects(), device_address, | |
adapter_pattern) | |
def find_device_in_objects(objects, device_address, adapter_pattern=None): | |
bus = dbus.SystemBus() | |
path_prefix = "" | |
if adapter_pattern: | |
adapter = find_adapter_in_objects(objects, adapter_pattern) | |
path_prefix = adapter.object_path | |
for path, ifaces in objects.items(): | |
device = ifaces.get(DEVICE_INTERFACE) | |
if device is None: | |
continue | |
if (device["Address"] == device_address and | |
path.startswith(path_prefix)): | |
obj = bus.get_object(SERVICE_NAME, path) | |
return dbus.Interface(obj, DEVICE_INTERFACE) | |
raise Exception("Bluetooth device not found") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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"> | |
<deny send_destination="org.mpris.MediaPlayer2.a2dp"/> | |
</policy> | |
<policy group="lp"> | |
<allow own="org.mpris.MediaPlayer2.a2dp"/> | |
</policy> | |
<policy user="root"> | |
<allow own="org.mpris.MediaPlayer2.a2dp"/> | |
<allow send_destination="org.mpris.MediaPlayer2.a2dp"/> | |
<allow send_interface="org.mpris.MediaPlayer2.Player"/> | |
<allow send_interface="org.freedesktop.DBus.Properties"/> | |
</policy> | |
<policy at_console="true"> | |
<allow send_destination="org.mpris.MediaPlayer2.a2dp"/> | |
</policy> | |
</busconfig> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
import sys | |
import pkg_resources | |
from pydbus.generic import signal | |
from pydbus import SystemBus | |
from pydbus import SessionBus | |
import logging | |
import bluezutils | |
try: | |
from gi.repository import GObject | |
from gi.repository import GLib | |
except ImportError: | |
import gobject as GObject | |
import glib as Glib | |
class Player(object): | |
def __init__(self): | |
self.dbus = pkg_resources.resource_string(__name__, 'player.xml') | |
self.props = { | |
"PlaybackStatus": GLib.Variant('s', "Playing"), | |
"LoopStatus": GLib.Variant('s', "None"), | |
"Rate": GLib.Variant('d', 1.0), | |
"Shuffle": GLib.Variant('b', False), | |
"Metadata": GLib.Variant('a{sv}', {}), | |
"Volume": GLib.Variant('d', 1.0), | |
"Position": GLib.Variant('x', 0), | |
"MinimumRate": GLib.Variant('d', 1.0), | |
"MaximumRate": GLib.Variant('d', 1.0), | |
"CanGoNext": GLib.Variant('b', True), | |
"CanGoPrevious": GLib.Variant('b', True), | |
"CanPlay": GLib.Variant('b', True), | |
"CanPause": GLib.Variant('b', True), | |
"CanSeek": GLib.Variant('b', False), | |
"CanControl": GLib.Variant('b', True)} | |
self.properties = { | |
"PlaybackStatus": "Stopped", | |
"LoopStatus": "None", | |
"Rate": 1.0, | |
"Shuffle": False, | |
"Metadata": {}, | |
"Volume": 1.0, | |
"Position": 0, | |
"MinimumRate": 1.0, | |
"MaximumRate": 1.0, | |
"CanGoNext": True, | |
"CanGoPrevious": True, | |
"CanPlay": True, | |
"CanPause": True, | |
"CanSeek": False, | |
"CanControl": True} | |
def get_property(self, name): | |
return self.properties[name] | |
def set_property(self, name, val, notify=True): | |
self.properties[name] = val | |
if notify: | |
self.PropertiesChanged("org.mpris.MediaPlayer2.Player", | |
{name: val}, []) | |
@property | |
def playing(self): | |
return self.properties["PlaybackStatus"] == GLib.Variant('s', "Playing") | |
@property | |
def paused(self): | |
return self.properties["PlaybackStatus"] == GLib.Variant('s', "Paused") | |
@property | |
def stopped(self): | |
return self.properties["PlaybackStatus"] == "Stopped" | |
@property | |
def PlaybackStatus(self): | |
return self.get_property("PlaybackStatus") | |
@PlaybackStatus.setter | |
def PlaybackStatus(self, value): | |
self.set_property("PlaybackStatus", value) | |
@property | |
def Mark(self): | |
return self.get_property("Mark") | |
@Mark.setter | |
def Mark(self, value): | |
self.set_property("Mark", value) | |
@property | |
def Rate(self): | |
return self.get_property("Rate") | |
@Rate.setter | |
def Rate(self, value): | |
self.set_property("Rate", value) | |
@property | |
def Shuffle(self): | |
return self.get_property("Shuffle") | |
@Shuffle.setter | |
def Shuffle(self, value): | |
self.set_property("Shuffle", value) | |
@property | |
def LoopStatus(self): | |
return self.get_property("LoopStatus") | |
@LoopStatus.setter | |
def LoopStatus(self, value): | |
self.set_property("LoopStatus", value) | |
@property | |
def Volume(self): | |
return self.get_property("Volume") | |
@Volume.setter | |
def Volume(self, value): | |
self.set_property("Volume", value) | |
def __getattr__(self, attr): | |
return self.get_property(attr) | |
__getitem__ = __getattr__ | |
def Next(self): | |
print("Next() Called") | |
def Previous(self): | |
print("Previous() Called") | |
def Play(self): | |
print("Play() Called") | |
if not self.playing: | |
self.PlaybackStatus = "Playing" | |
def Pause(self): | |
print("Pause() Called") | |
if not self.paused: | |
self.PlaybackStatus = "Paused" | |
def PlayPause(self): | |
print("PlayPause() Called") | |
if self.properties["PlaybackStatus"] is "Playing": | |
self.Pause() | |
else: | |
self.Play() | |
def Stop(self): | |
print("Stop() Called") | |
if not self.stopped: | |
self.properties["PlaybackStatus"] = "Stopped" | |
self.PropertiesChanged("org.mpris.MediaPlayer2.Player", | |
{"PlaybackStatus": "Stopped"}, []) | |
def Seek(self, offset): | |
print("Seek(%d) Called" % offset) | |
def SetPosition(self, track_id, position): | |
print("SetPosition(%s, %d) Called" % (track_id, position)) | |
def OpenUri(self, uri): | |
print("OpenUri(%d) Called" % uri) | |
PropertiesChanged = signal() | |
Seeked = signal() | |
if __name__ == '__main__': | |
logging.basicConfig(level=logging.DEBUG) | |
bus = SystemBus() | |
loop = GObject.MainLoop() | |
ply = Player() | |
bus.publish("org.mpris.MediaPlayer2.a2dp", ( | |
'/org/mpris/MediaPlayer2', ply, ply.dbus | |
)) | |
path = bluezutils.find_adapter().object_path | |
obj = bus.get("org.bluez", path) | |
media = obj["org.bluez.Media1"] | |
media.RegisterPlayer('/org/mpris/MediaPlayer2', ply.props) | |
print("running") | |
loop.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment