Skip to content

Instantly share code, notes, and snippets.

@EHfive
Last active October 3, 2018 13:20
Show Gist options
  • Save EHfive/e2a28d0279a6247fab4bac93d73b8571 to your computer and use it in GitHub Desktop.
Save EHfive/e2a28d0279a6247fab4bac93d73b8571 to your computer and use it in GitHub Desktop.
#!/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")
<!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>
#!/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()
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