temporary commit of lennarts new bt changes

This commit is contained in:
Lennart Poettering 2009-01-29 16:27:27 +01:00
parent 47a9b96b64
commit a71fa021a3
3 changed files with 530 additions and 941 deletions

View file

@ -1003,9 +1003,11 @@ if HAVE_BLUEZ
modlibexec_LTLIBRARIES += \ modlibexec_LTLIBRARIES += \
module-bluetooth-proximity.la \ module-bluetooth-proximity.la \
module-bluetooth-discover.la \ module-bluetooth-discover.la \
libbluetooth-util.la \
libbluetooth-ipc.la \ libbluetooth-ipc.la \
libbluetooth-sbc.la \ libbluetooth-sbc.la
module-bluetooth-device.la #\
# module-bluetooth-device.la
pulselibexec_PROGRAMS += \ pulselibexec_PROGRAMS += \
proximity-helper proximity-helper
@ -1445,7 +1447,7 @@ proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS)
# Bluetooth sink / source # Bluetooth sink / source
module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c module_bluetooth_discover_la_SOURCES = modules/bluetooth/module-bluetooth-discover.c
module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS) module_bluetooth_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
module_bluetooth_discover_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_bluetooth_discover_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libbluetooth-util.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) module_bluetooth_discover_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
libbluetooth_sbc_la_SOURCES = modules/bluetooth/sbc.c modules/bluetooth/sbc.h modules/bluetooth/sbc_tables.h modules/bluetooth/sbc_math.h libbluetooth_sbc_la_SOURCES = modules/bluetooth/sbc.c modules/bluetooth/sbc.h modules/bluetooth/sbc_tables.h modules/bluetooth/sbc_math.h
@ -1459,10 +1461,15 @@ libbluetooth_ipc_la_LDFLAGS = -avoid-version
libbluetooth_ipc_la_LIBADD = $(AM_LIBADD)libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libbluetooth_ipc_la_LIBADD = $(AM_LIBADD)libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
libbluetooth_ipc_la_CFLAGS = $(AM_CFLAGS) -w libbluetooth_ipc_la_CFLAGS = $(AM_CFLAGS) -w
module_bluetooth_device_la_SOURCES = modules/bluetooth/module-bluetooth-device.c modules/bluetooth/rtp.h libbluetooth_util_la_SOURCES = modules/bluetooth/bluetooth-util.c modules/bluetooth/bluetooth-util.h
module_bluetooth_device_la_LDFLAGS = $(MODULE_LDFLAGS) libbluetooth_util_la_LDFLAGS = -avoid-version
module_bluetooth_device_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libbluetooth-ipc.la libbluetooth-sbc.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libbluetooth_util_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libdbus-util.la
module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -w libbluetooth_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
#module_bluetooth_device_la_SOURCES = modules/bluetooth/module-bluetooth-device.c modules/bluetooth/rtp.h
#module_bluetooth_device_la_LDFLAGS = $(MODULE_LDFLAGS)
#module_bluetooth_device_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libdbus-util.la libbluetooth-ipc.la libbluetooth-sbc.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la
#module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -w
# Apple Airtunes/RAOP # Apple Airtunes/RAOP
module_raop_sink_la_SOURCES = modules/module-raop-sink.c module_raop_sink_la_SOURCES = modules/module-raop-sink.c

View file

@ -4,17 +4,17 @@
Copyright 2008 Joao Paulo Rechi Vita Copyright 2008 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published it under the terms of the GNU Lesser General Public License as
by the Free Software Foundation; either version 2 of the License, published by the Free Software Foundation; either version 2 of the
or (at your option) any later version. License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details. General Public License for more details.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public
along with PulseAudio; if not, write to the Free Software License along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA. USA.
***/ ***/
@ -44,13 +44,14 @@
#include <pulsecore/time-smoother.h> #include <pulsecore/time-smoother.h>
#include <pulsecore/rtclock.h> #include <pulsecore/rtclock.h>
#include "../dbus-util.h" #include <modules/dbus-util.h>
#include "module-bluetooth-device-symdef.h" #include "module-bluetooth-device-symdef.h"
#include "ipc.h" #include "ipc.h"
#include "sbc.h" #include "sbc.h"
#include "rtp.h" #include "rtp.h"
#include "bluetooth-util.h"
#define DEFAULT_SINK_NAME "bluetooth_sink"
#define BUFFER_SIZE 2048 #define BUFFER_SIZE 2048
#define MAX_BITPOOL 64 #define MAX_BITPOOL 64
#define MIN_BITPOOL 2U #define MIN_BITPOOL 2U
@ -63,13 +64,29 @@ PA_MODULE_DESCRIPTION("Bluetooth audio sink and source");
PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE( PA_MODULE_USAGE(
"sink_name=<name of the device> " "name=<name for the card/sink/source, to be prefixed> "
"card_name=<name for the card> "
"sink_name=<name for the sink> "
"source_name=<name for the source> "
"address=<address of the device> " "address=<address of the device> "
"profile=<a2dp|hsp> " "profile=<a2dp|hsp> "
"rate=<sample rate> " "rate=<sample rate> "
"channels=<number of channels> " "channels=<number of channels> "
"path=<device object path>"); "path=<device object path>");
static const char* const valid_modargs[] = {
"name",
"card_name",
"sink_name",
"source_name",
"address",
"profile",
"rate",
"channels",
"path",
NULL
};
struct bt_a2dp { struct bt_a2dp {
sbc_capabilities_t sbc_capabilities; sbc_capabilities_t sbc_capabilities;
sbc_t sbc; /* Codec data */ sbc_t sbc; /* Codec data */
@ -84,6 +101,12 @@ struct bt_a2dp {
unsigned frame_count; /* Current frames in buffer*/ unsigned frame_count; /* Current frames in buffer*/
}; };
enum profile {
PROFILE_A2DP,
PROFILE_SCO,
PROFILE_OFF
};
struct userdata { struct userdata {
pa_core *core; pa_core *core;
pa_module *module; pa_module *module;
@ -98,33 +121,24 @@ struct userdata {
uint64_t offset; uint64_t offset;
pa_smoother *smoother; pa_smoother *smoother;
char *name; char *address;
char *addr; pa_sample_spec sample_spec;
char *profile;
pa_sample_spec ss;
int audioservice_fd; int service_fd;
int stream_fd; int stream_fd;
uint8_t transport; uint8_t transport;
char *strtransport;
size_t link_mtu; size_t link_mtu;
size_t block_size; size_t block_size;
pa_usec_t latency; pa_usec_t latency;
struct bt_a2dp a2dp; struct bt_a2dp a2dp;
char *path; char *path;
pa_dbus_connection *conn; pa_dbus_connection *connection;
};
static const char* const valid_modargs[] = { enum profile profile;
"sink_name",
"address", pa_bluetooth_device *device;
"profile",
"rate",
"channels",
"path",
NULL
}; };
static int bt_audioservice_send(int sk, const bt_audio_msg_header_t *msg) { static int bt_audioservice_send(int sk, const bt_audio_msg_header_t *msg) {
@ -234,7 +248,7 @@ static int bt_getcaps(struct userdata *u) {
msg.getcaps_req.h.name = BT_GET_CAPABILITIES; msg.getcaps_req.h.name = BT_GET_CAPABILITIES;
msg.getcaps_req.h.length = sizeof(msg.getcaps_req); msg.getcaps_req.h.length = sizeof(msg.getcaps_req);
strncpy(msg.getcaps_req.device, u->addr, 18); strncpy(msg.getcaps_req.device, u->address, 18);
if (pa_streq(u->profile, "a2dp")) if (pa_streq(u->profile, "a2dp"))
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP; msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
else if (pa_streq(u->profile, "hsp")) else if (pa_streq(u->profile, "hsp"))
@ -489,7 +503,6 @@ static int bt_setconf(struct userdata *u) {
} }
u->transport = msg.setconf_rsp.transport; u->transport = msg.setconf_rsp.transport;
u->strtransport = (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ? pa_xstrdup("A2DP") : pa_xstrdup("SCO"));
u->link_mtu = msg.setconf_rsp.link_mtu; u->link_mtu = msg.setconf_rsp.link_mtu;
/* setup SBC encoder now we agree on parameters */ /* setup SBC encoder now we agree on parameters */
@ -933,18 +946,288 @@ static int source_set_volume_cb(pa_source *s) {
return 0; return 0;
} }
static char *get_name(const char *type, pa_modargs *ma, const char *device_id, pa_bool_t *namereg_fail) {
char *t;
const char *n;
pa_assert(type);
pa_assert(ma);
pa_assert(device_id);
pa_assert(namereg_fail);
t = pa_sprintf_malloc("%s_name", type);
n = pa_modargs_get_value(ma, t, NULL);
pa_xfree(t);
if (n) {
*namereg_fail = TRUE;
return pa_xstrdup(n);
}
if ((n = pa_modargs_get_value(ma, "name", NULL)))
*namereg_fail = TRUE;
else {
n = device_id;
*namereg_fail = FALSE;
}
return pa_sprintf_malloc("bluez_%s.%s", type, n);
}
static int add_sink(struct userdata *u) {
pa_sink_new_data data;
pa_bool_t b;
pa_sink_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
pa_sink_new_data_set_sample_spec(&data, &u->ss);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Bluetooth %s on %s", u->profile == PROFILE_A2DP ? "A2DP" : "HSP/HFP", u->addr);
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco");
data.card = u->card;
data.name = get_name("sink", ma, u->addr, &b);
data.namereg_fail = b;
u->sink = pa_sink_new(u->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
if (!u->sink) {
pa_log_error("Failed to create sink");
return -1;
}
u->sink->userdata = u;
u->sink->parent.process_msg = sink_process_msg;
u->sink->get_volume = sink_get_volume_cb;
u->sink->set_volume = sink_set_volume_cb;
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
return 0;
}
static int add_source(struct userdata *u) {
pa_source_new_data data;
pa_bool_t b;
pa_source_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
pa_source_new_data_set_sample_spec(&data, &u->ss);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Bluetooth %s on %s", u->profile == PROFILE_A2DP ? "A2DP" : "HSP/HFP", u->addr);
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco");
data.card = u->card;
data.name = get_name("source", ma, u->addr, &b);
data.namereg_fail = b;
u->source = pa_source_new(u->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
pa_source_new_data_done(&data);
if (!u->source) {
pa_log_error("Failed to create source");
return -1;
}
u->source->userdata = u;
u->source->parent.process_msg = source_process_msg;
u->source->get_volume = source_get_volume_cb;
u->source->set_volume = source_set_volume_cb;
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
pa_source_set_rtpoll(u->source, u->rtpoll);
return 0;
}
static void init_profile(struct userdata *u) {
enum profile *d;
pa_assert(u);
if (u->profile == PROFILE_A2DP ||
u->profile == PROFILE_SCO)
add_sink(u);
if (u->profile == PROFILE_SCO)
add_source(u);
}
static int init_bt(struct userdata *u) {
pa_assert(u);
/* connect to the bluez audio service */
if ((u->audioservice_fd = bt_audio_service_open()) < 0) {
pa_log_error("Couldn't connect to bluetooth audio service");
return -1;
}
pa_log_debug("Connected to the bluetooth audio service");
/* queries device capabilities */
if (bt_getcaps(u) < 0) {
pa_log_error("Failed to get device capabilities");
return -1;
}
pa_log_debug("Got device capabilities");
return 0;
}
static setup_bt(struct userdata *u) {
pa_assert(u);
/* configures the connection */
if (bt_setconf(u) < 0) {
pa_log_error("Failed to set config");
return -1;
}
pa_log_debug("Connection to the device configured");
/* gets the device socket */
if (bt_getstreamfd(u) < 0) {
pa_log_error("Failed to get stream fd (%d)", e);
return -1;
}
pa_log_debug("Got the device socket");
}
static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
struct userdata *u;
enum profile *d;
pa_queue *inputs = NULL, *outputs = NULL;
pa_assert(c);
pa_assert(new_profile);
pa_assert_se(u = c->userdata);
d = PA_CARD_PROFILE_DATA(new_profile);
if (u->sink) {
inputs = pa_sink_move_all_start(u->sink);
pa_sink_unlink(u->sink);
pa_sink_unref(u->sink);
u->sink = NULL;
}
if (u->source) {
outputs = pa_source_move_all_start(u->source);
pa_source_unlink(u->source);
pa_source_unref(u->source);
u->source = NULL;
}
u->profile = *d;
init_profile(u);
if (inputs) {
if (u->sink)
pa_sink_move_all_finish(u->sink, inputs, FALSE);
else
pa_sink_move_all_fail(inputs);
}
if (outputs) {
if (u->source)
pa_source_move_all_finish(u->source, outputs, FALSE);
else
pa_source_move_all_fail(outputs);
}
return 0;
}
static int add_card(struct userdata *u) {
pa_card_new_data data;
pa_bool_t b;
pa_card_profile *p;
enum profile *d;
pa_card_new_data_init(&data);
data.driver = __FILE__;
data.module = u->module;
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->addr);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_API, "bluez");
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_CONNECTOR, "bluetooth");
/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, "headset"); /\*FIXME*\/ */
/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_VENDOR_PRODUCT_ID, "product_id"); /\*FIXME*\/ */
/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_SERIAL, "serial"); /\*FIXME*\/ */
data.name = get_name("card", ma, u->addr, &b);
data.namereg_fail = b;
data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ||
u->transport == BT_CAPABILITIES_TRANSPORT_ANY) {
p = pa_card_profile_new("a2dp", "A2DP Advanced Audio Distribution Profile", sizeof(enum profile));
p->priority = 10;
p->n_sinks = 1;
p->n_sources = 0;
p->max_sink_channels = 2;
p->max_source_channels = 0;
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_A2DP;
}
if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO ||
u->transport == BT_CAPABILITIES_TRANSPORT_ANY) {
p = pa_card_profile_new("hsp", "HSP/HFP Headset/Hands-Free Profile", sizeof(enum profile));
p->priority = 20;
p->n_sinks = 1;
p->n_sources = 1;
p->max_sink_channels = 1;
p->max_source_channels = 1;
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_SCO;
}
pa_assert(!pa_hashmap_isempty(data.profiles));
p = pa_card_profile_new("off", "Off", sizeof(enum profile));
d = PA_CARD_PROFILE_DATA(p);
*d = PROFILE_OFF;
u->card = pa_card_new(m->core, &data);
pa_card_new_data_done(&data);
if (!u->card) {
pa_log("Failed to allocate card.");
return -1;
}
card->userdata = u;
card->set_profile = card_set_profile;
d = PA_CARD_PROFILE_DATA(u->card->active_profile);
u->profile = *d;
return 0;
}
int pa__init(pa_module* m) { int pa__init(pa_module* m) {
int e;
pa_modargs *ma; pa_modargs *ma;
uint32_t channels; uint32_t channels;
pa_sink_new_data data; pa_sink_new_data sink_data;
pa_source_new_data source_data;
pa_card card_data;
struct pollfd *pollfd; struct pollfd *pollfd;
struct userdata *u; struct userdata *u;
DBusError err; pa_bool_t b;
char *tmp; const char *p;
pa_assert(m); pa_assert(m);
dbus_error_init(&err);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log_error("Failed to parse module arguments");
goto fail;
}
m->userdata = u = pa_xnew0(struct userdata, 1); m->userdata = u = pa_xnew0(struct userdata, 1);
u->module = m; u->module = m;
@ -952,157 +1235,109 @@ int pa__init(pa_module* m) {
u->audioservice_fd = -1; u->audioservice_fd = -1;
u->stream_fd = -1; u->stream_fd = -1;
u->transport = (uint8_t) -1; u->transport = (uint8_t) -1;
u->offset = 0;
u->latency = 0;
u->a2dp.sbc_initialized = FALSE;
u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10);
u->rtpoll = pa_rtpoll_new(); u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll); pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll);
u->rtpoll_item = NULL; u->add_source = m->core->default_sample_spec;
u->ss = m->core->default_sample_spec;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { if (!(u->address = pa_xstrdup(pa_modargs_get_value(ma, "address", NULL)))) {
pa_log_error("Failed to parse module arguments");
goto fail;
}
if (!(u->name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)))) {
pa_log_error("Failed to get device name from module arguments");
goto fail;
}
if (!(u->addr = pa_xstrdup(pa_modargs_get_value(ma, "address", NULL)))) {
pa_log_error("Failed to get device address from module arguments"); pa_log_error("Failed to get device address from module arguments");
goto fail; goto fail;
} }
if (!(u->profile = pa_xstrdup(pa_modargs_get_value(ma, "profile", NULL)))) {
u->path = pa_xstrdup(pa_modargs_get_value(ma, "path", NULL));
p = pa_modargs_get_value(ma, "profile", NULL);
if (p && !pa_streq(p, "hsp") && !pa_streq(p, "a2dp")) {
pa_log_error("Failed to get profile from module arguments"); pa_log_error("Failed to get profile from module arguments");
goto fail; goto fail;
} }
if (pa_modargs_get_value_u32(ma, "rate", &u->ss.rate) < 0) {
if (pa_modargs_get_value_u32(ma, "rate", &u->ss.rate) < 0 ||
u->ss.rate <= 0 || u->ss.rate > PA_RATE_MAX) {
pa_log_error("Failed to get rate from module arguments"); pa_log_error("Failed to get rate from module arguments");
goto fail; goto fail;
} }
u->path = pa_xstrdup(pa_modargs_get_value(ma, "path", NULL));
channels = u->ss.channels; channels = u->ss.channels;
if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0) { if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 ||
channels <= 0 || channels > PA_CHANNELS_MAX) {
pa_log_error("Failed to get channels from module arguments"); pa_log_error("Failed to get channels from module arguments");
goto fail; goto fail;
} }
u->ss.channels = (uint8_t) channels; u->ss.channels = (uint8_t) channels;
/* connect to the bluez audio service */ /* Connect to the BT service and query capabilities */
u->audioservice_fd = bt_audio_service_open(); if (init_bt(u) < 0)
if (u->audioservice_fd <= 0) {
pa_log_error("Couldn't connect to bluetooth audio service");
goto fail; goto fail;
}
pa_log_debug("Connected to the bluetooth audio service");
/* queries device capabilities */ /* Add the card structure. This will also initialize the default profile */
e = bt_getcaps(u); if (add_card(u) < 0)
if (e < 0) {
pa_log_error("Failed to get device capabilities");
goto fail; goto fail;
}
pa_log_debug("Got device capabilities");
/* configures the connection */ /* Now configure the the right profile */
e = bt_setconf(u); if (setup_bt(u) < 0)
if (e < 0) {
pa_log_error("Failed to set config");
goto fail;
}
pa_log_debug("Connection to the device configured");
/* gets the device socket */ if (init_profile(u) < 0)
e = bt_getstreamfd(u);
if (e < 0) {
pa_log_error("Failed to get stream fd (%d)", e);
goto fail; goto fail;
}
pa_log_debug("Got the device socket");
/* create sink */ if (u->path) {
pa_sink_new_data_init(&data); DBusError err;
data.driver = __FILE__; dbus_error_init(&err);
data.module = m; char *t;
pa_sink_new_data_set_name(&data, u->name);
pa_sink_new_data_set_sample_spec(&data, &u->ss); u->conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &err);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->name); if (dbus_error_is_set(&err) || (!u->conn)) {
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Bluetooth %s '%s' (%s)", u->strtransport, u->name, u->addr); pa_log("Failed to get D-Bus connection: %s", err.message);
pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile);
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_API, "bluez");
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
pa_proplist_setf(data.proplist, PA_PROP_DEVICE_CONNECTOR, "bluetooth");
/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, "headset"); /\*FIXME*\/ */
/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_VENDOR_PRODUCT_ID, "product_id"); /\*FIXME*\/ */
/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_SERIAL, "serial"); /\*FIXME*\/ */
u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY);
pa_sink_new_data_done(&data);
if (!u->sink) {
pa_log_error("Failed to create sink");
goto fail; goto fail;
} }
u->sink->userdata = u;
u->sink->parent.process_msg = sink_process_msg; if (!dbus_connection_add_filter(pa_dbus_connection_get(u->conn), filter_cb, u, NULL)) {
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_log_error("Failed to add filter function");
pa_sink_set_rtpoll(u->sink, u->rtpoll); goto fail;
}
if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO ||
u->transport == BT_CAPABILITIES_TRANSPORT_ANY) {
t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path);
dbus_bus_add_match(pa_dbus_connection_get(u->conn), t, &err);
pa_xfree(t);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.Headset signals: %s: %s", err.name, err.message);
goto fail;
}
}
if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ||
u->transport == BT_CAPABILITIES_TRANSPORT_ANY) {
t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path);
dbus_bus_add_match(pa_dbus_connection_get(u->conn), t, &err);
pa_xfree(t);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.AudioSink signals: %s: %s", err.name, err.message);
goto fail;
}
}
}
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
pollfd->fd = u->stream_fd; pollfd->fd = u->stream_fd;
pollfd->events = pollfd->revents = 0; pollfd->events = pollfd->revents = 0;
/* start rt thread */
if (!(u->thread = pa_thread_new(thread_func, u))) { if (!(u->thread = pa_thread_new(thread_func, u))) {
pa_log_error("Failed to create IO thread"); pa_log_error("Failed to create IO thread");
goto fail; goto fail;
} }
if (u->sink)
pa_sink_put(u->sink); pa_sink_put(u->sink);
if (!u->path)
goto end;
/* connect to the bus */ if (u->source)
u->conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &err); pa_sink_put(u->source);
if (dbus_error_is_set(&err) || (u->conn == NULL) ) {
pa_log("Failed to get D-Bus connection: %s", err.message);
goto fail;
}
/* monitor property changes */
if (!dbus_connection_add_filter(pa_dbus_connection_get(u->conn), filter_cb, u, NULL)) {
pa_log_error("Failed to add filter function");
goto fail;
}
if (pa_streq(u->profile, "hsp")) {
tmp = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path);
dbus_bus_add_match(pa_dbus_connection_get(u->conn), tmp, &err);
pa_xfree(tmp);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.Headset signals: %s: %s", err.name, err.message);
goto fail;
}
} else {
tmp = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path);
dbus_bus_add_match(pa_dbus_connection_get(u->conn), tmp, &err);
pa_xfree(tmp);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.AudioSink signals: %s: %s", err.name, err.message);
goto fail;
}
}
u->sink->get_volume = sink_get_volume_cb;
u->sink->set_volume = sink_set_volume_cb;
if (u->source) {
u->source->get_volume = source_get_volume_cb;
u->source->set_volume = source_set_volume_cb;
}
end:
pa_modargs_free(ma); pa_modargs_free(ma);
return 0; return 0;
@ -1114,6 +1349,17 @@ fail:
return -1; return -1;
} }
int pa__get_n_used(pa_module *m) {
struct userdata *u;
pa_assert(m);
pa_assert_se(u = m->userdata);
return
(u->sink ? pa_sink_linked_by(u->sink) : 0) +
(u->source ? pa_source_linked_by(u->source) : 0);
}
void pa__done(pa_module *m) { void pa__done(pa_module *m) {
struct userdata *u; struct userdata *u;
pa_assert(m); pa_assert(m);
@ -1123,23 +1369,29 @@ void pa__done(pa_module *m) {
if (u->conn) { if (u->conn) {
DBusError error; DBusError error;
char *tmp; char *t;
dbus_error_init(&error);
if (pa_streq(u->profile, "hsp")) { if (u->transport == BT_CAPABILITIES_TRANSPORT_SCO ||
tmp = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path); u->transport == BT_CAPABILITIES_TRANSPORT_ANY) {
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), tmp, &error);
pa_xfree(tmp); t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged',path='%s'", u->path);
dbus_error_init(&error);
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), t, &error);
dbus_error_free(&error); dbus_error_free(&error);
} else { pa_xfree(t);
tmp = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path); }
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), tmp, &error);
pa_xfree(tmp); if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ||
u->transport == BT_CAPABILITIES_TRANSPORT_ANY) {
t = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged',path='%s'", u->path);
dbus_error_init(&error);
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), t, &error);
dbus_error_free(&error); dbus_error_free(&error);
pa_xfree(t);
} }
dbus_connection_remove_filter(pa_dbus_connection_get(u->conn), filter_cb, u); dbus_connection_remove_filter(pa_dbus_connection_get(u->conn), filter_cb, u);
pa_dbus_connection_unref(u->conn); pa_dbus_connection_unref(u->conn);
} }
@ -1149,6 +1401,9 @@ void pa__done(pa_module *m) {
if (u->sink) if (u->sink)
pa_sink_unlink(u->sink); pa_sink_unlink(u->sink);
if (u->source)
pa_source_unlink(u->source);
if (u->thread) { if (u->thread) {
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
pa_thread_free(u->thread); pa_thread_free(u->thread);
@ -1157,6 +1412,12 @@ void pa__done(pa_module *m) {
if (u->sink) if (u->sink)
pa_sink_unref(u->sink); pa_sink_unref(u->sink);
if (u->source)
pa_source_unref(u->source);
if (u->card)
pa_card_unref(u->card);
pa_thread_mq_done(&u->thread_mq); pa_thread_mq_done(&u->thread_mq);
if (u->rtpoll_item) if (u->rtpoll_item)
@ -1177,7 +1438,7 @@ void pa__done(pa_module *m) {
pa_close(u->stream_fd); pa_close(u->stream_fd);
if (u->audioservice_fd >= 0) if (u->audioservice_fd >= 0)
pa_close(u->audioservice_fd); pa_close(u->service_fd);
pa_xfree(u); pa_xfree(u);
} }

View file

@ -4,17 +4,17 @@
Copyright 2008 Joao Paulo Rechi Vita Copyright 2008 Joao Paulo Rechi Vita
PulseAudio is free software; you can redistribute it and/or modify PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published it under the terms of the GNU Lesser General Public License as
by the Free Software Foundation; either version 2 of the License, published by the Free Software Foundation; either version 2 of the
or (at your option) any later version. License, or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details. General Public License for more details.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public
along with PulseAudio; if not, write to the Free Software License along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA. USA.
***/ ***/
@ -29,817 +29,138 @@
#include <pulse/xmalloc.h> #include <pulse/xmalloc.h>
#include <pulsecore/module.h> #include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h> #include <pulsecore/modargs.h>
#include <pulsecore/macro.h> #include <pulsecore/macro.h>
#include <pulsecore/llist.h> #include <pulsecore/llist.h>
#include <pulsecore/core-util.h> #include <pulsecore/core-util.h>
#include <modules/dbus-util.h>
#include "../dbus-util.h"
#include "module-bluetooth-discover-symdef.h" #include "module-bluetooth-discover-symdef.h"
#include "bluetooth-util.h"
PA_MODULE_AUTHOR("Joao Paulo Rechi Vita"); PA_MODULE_AUTHOR("Joao Paulo Rechi Vita");
PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers"); PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers");
PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_USAGE(""); PA_MODULE_USAGE("async=<Asynchronous initialization?>");
struct module { static const char* const valid_modargs[] = {
char *profile; "async",
uint32_t index; NULL
PA_LLIST_FIELDS(struct module);
};
struct uuid {
char *uuid;
PA_LLIST_FIELDS(struct uuid);
};
struct dbus_pending {
char *path;
char *profile;
DBusPendingCall *pending;
PA_LLIST_FIELDS(struct dbus_pending);
};
struct device {
char *name;
char *object_path;
int paired;
char *alias;
int connected;
PA_LLIST_HEAD(struct uuid, uuid_list);
char *address;
int class;
int trusted;
PA_LLIST_HEAD(struct module, module_list);
PA_LLIST_FIELDS(struct device);
}; };
struct userdata { struct userdata {
pa_module *module; pa_module *module;
pa_dbus_connection *conn; pa_core *core;
dbus_int32_t dbus_data_slot; pa_dbus_connection *connection;
PA_LLIST_HEAD(struct device, device_list); pa_bluetooth_discovery *discovery;
PA_LLIST_HEAD(struct dbus_pending, dbus_pending_list);
}; };
static struct module *module_new(const char *profile, pa_module *pa_m) { static void load_module_for_device(struct userdata *u, pa_bluetooth_device *d, pa_bool_t good) {
struct module *m;
m = pa_xnew(struct module, 1);
m->profile = pa_xstrdup(profile);
m->index = pa_m->index;
PA_LLIST_INIT(struct module, m);
return m;
}
static void module_free(struct module *m) {
pa_assert(m);
pa_xfree(m->profile);
pa_xfree(m);
}
static struct module* module_find(struct device *d, const char *profile) {
struct module *m;
for (m = d->module_list; m; m = m->next)
if (pa_streq(m->profile, profile))
return m;
return NULL;
}
static struct uuid *uuid_new(const char *uuid) {
struct uuid *node;
node = pa_xnew(struct uuid, 1);
node->uuid = pa_xstrdup(uuid);
PA_LLIST_INIT(struct uuid, node);
return node;
}
static void uuid_free(struct uuid *uuid) {
pa_assert(uuid);
pa_xfree(uuid->uuid);
pa_xfree(uuid);
}
static struct dbus_pending *dbus_pending_new(struct userdata *u, DBusPendingCall *pending, const char *path, const char *profile) {
struct dbus_pending *node;
pa_assert(pending);
node = pa_xnew(struct dbus_pending, 1);
node->pending = pending;
node->path = pa_xstrdup(path);
node->profile = pa_xstrdup(profile);
PA_LLIST_INIT(struct dbus_pending, node);
dbus_pending_call_set_data(pending, u->dbus_data_slot, node, NULL);
return node;
}
static void dbus_pending_free(struct dbus_pending *pending) {
pa_assert(pending);
pa_xfree(pending->path);
pa_xfree(pending->profile);
dbus_pending_call_cancel(pending->pending);
dbus_pending_call_unref(pending->pending);
pa_xfree(pending);
}
static struct device *device_new(const char *object_path) {
struct device *node;
node = pa_xnew(struct device, 1);
node->name = NULL;
node->object_path = pa_xstrdup(object_path);
node->paired = -1;
node->alias = NULL;
node->connected = -1;
PA_LLIST_HEAD_INIT(struct uuid, node->uuid_list);
node->address = NULL;
node->class = -1;
node->trusted = -1;
PA_LLIST_HEAD_INIT(struct module, node->module_list);
PA_LLIST_INIT(struct device, node);
return node;
}
static void device_free(struct device *device) {
struct module *m;
struct uuid *i;
pa_assert(device);
while ((m = device->module_list)) {
PA_LLIST_REMOVE(struct module, device->module_list, m);
module_free(m);
}
while ((i = device->uuid_list)) {
PA_LLIST_REMOVE(struct uuid, device->uuid_list, i);
uuid_free(i);
}
pa_xfree(device->name);
pa_xfree(device->object_path);
pa_xfree(device->alias);
pa_xfree(device->address);
pa_xfree(device);
}
static struct device* device_find(struct userdata *u, const char *path) {
struct device *i;
for (i = u->device_list; i; i = i->next)
if (pa_streq(i->object_path, path))
return i;
return NULL;
}
static int parse_device_property(struct userdata *u, struct device *d, DBusMessageIter *i) {
const char *key;
DBusMessageIter variant_i;
pa_assert(u); pa_assert(u);
pa_assert(d); pa_assert(d);
pa_assert(i);
if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { if (good &&
pa_log("Property name not a string."); d->device_connected > 0 &&
(d->audio_sink_connected > 0 || d->headset_connected > 0)) {
if (PA_PTR_TO_UINT(d->data) == PA_INVALID_INDEX) {
pa_module *m = NULL;
char *args;
/* Oh, awesome, a new device has shown up and been connected! */
args = pa_sprintf_malloc("name=\"%s\" address=\"%s\" path=\"%s\"", d->address, d->address, d->path);
pa_log_debug("Loading module-bluetooth-device %s", args);
/* m = pa_module_load(u->module->core, "module-bluetooth-device", args); */
pa_xfree(args);
if (m)
d->data = PA_UINT_TO_PTR(m->index);
else
pa_log_debug("Failed to load module for device %s", d->path);
}
} else {
if (PA_PTR_TO_UINT(d->data) != PA_INVALID_INDEX) {
/* Hmm, disconnection? Then let's unload our module */
pa_log_debug("Unloading module for %s", d->path);
/* pa_module_unload_request_by_index(u->core, PA_PTR_TO_UINT(d->data), TRUE); */
d->data = NULL;
}
}
}
static int setup_dbus(struct userdata *u) {
DBusError err;
dbus_error_init(&err);
u->connection = pa_dbus_bus_get(u->core, DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err) || !u->connection) {
pa_log("Failed to get D-Bus connection: %s", err.message);
dbus_error_free(&err);
return -1; return -1;
} }
dbus_message_iter_get_basic(i, &key);
if (!dbus_message_iter_next(i)) {
pa_log("Property value missing");
return -1;
}
if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) {
pa_log("Property value not a variant.");
return -1;
}
dbus_message_iter_recurse(i, &variant_i);
pa_log_debug("Parsing device property %s", key);
switch (dbus_message_iter_get_arg_type(&variant_i)) {
case DBUS_TYPE_STRING: {
const char *value;
dbus_message_iter_get_basic(&variant_i, &value);
if (pa_streq(key, "Name")) {
pa_xfree(d->name);
d->name = pa_xstrdup(value);
} else if (pa_streq(key, "Alias")) {
pa_xfree(d->alias);
d->alias = pa_xstrdup(value);
} else if (pa_streq(key, "Address")) {
pa_xfree(d->address);
d->address = pa_xstrdup(value);
}
break;
}
case DBUS_TYPE_BOOLEAN: {
dbus_bool_t value;
dbus_message_iter_get_basic(&variant_i, &value);
if (pa_streq(key, "Paired"))
d->paired = !!value;
else if (pa_streq(key, "Connected"))
d->connected = !!value;
else if (pa_streq(key, "Trusted"))
d->trusted = !!value;
break;
}
case DBUS_TYPE_UINT32: {
uint32_t value;
dbus_message_iter_get_basic(&variant_i, &value);
if (pa_streq(key, "Class"))
d->class = (int) value;
break;
}
case DBUS_TYPE_ARRAY: {
DBusMessageIter ai;
dbus_message_iter_recurse(&variant_i, &ai);
if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING &&
pa_streq(key, "UUIDs")) {
while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
struct uuid *node;
const char *value;
dbus_message_iter_get_basic(&ai, &value);
node = uuid_new(value);
PA_LLIST_PREPEND(struct uuid, d->uuid_list, node);
if (!dbus_message_iter_next(&ai))
break;
}
}
break;
}
}
return 0; return 0;
} }
static int get_device_properties(struct userdata *u, struct device *d) { int pa__init(pa_module* m) {
DBusError e;
DBusMessage *m = NULL, *r = NULL;
DBusMessageIter arg_i, element_i;
int ret = -1;
pa_assert(u);
pa_assert(d);
dbus_error_init(&e);
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->object_path, "org.bluez.Device", "GetProperties"));
r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->conn), m, -1, &e);
if (!r) {
pa_log("org.bluez.Device.GetProperties failed: %s", e.message);
goto finish;
}
if (!dbus_message_iter_init(r, &arg_i)) {
pa_log("org.bluez.Device.GetProperties reply has no arguments");
goto finish;
}
if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
pa_log("org.bluez.Device.GetProperties argument is not an array");
goto finish;
}
dbus_message_iter_recurse(&arg_i, &element_i);
while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter dict_i;
dbus_message_iter_recurse(&element_i, &dict_i);
if (parse_device_property(u, d, &dict_i) < 0)
goto finish;
}
if (!dbus_message_iter_next(&element_i))
break;
}
ret = 0;
finish:
if (m)
dbus_message_unref(m);
if (r)
dbus_message_unref(r);
dbus_error_free(&e);
return ret;
}
static void load_module_for_device(struct userdata *u, struct device *d, const char *profile) {
char *args;
pa_module *pa_m;
struct module *m;
pa_assert(u);
pa_assert(d);
get_device_properties(u, d);
args = pa_sprintf_malloc("sink_name=\"%s\" address=\"%s\" profile=\"%s\" path=\"%s\"", d->name, d->address, profile, d->object_path);
pa_m = pa_module_load(u->module->core, "module-bluetooth-device", args);
pa_xfree(args);
if (!pa_m) {
pa_log_debug("Failed to load module for device %s", d->object_path);
return;
}
m = module_new(profile, pa_m);
PA_LLIST_PREPEND(struct module, d->module_list, m);
}
static void unload_module_for_device(struct userdata *u, struct device *d, const char *profile) {
struct module *m;
pa_assert(u);
pa_assert(d);
if (!(m = module_find(d, profile)))
return;
pa_module_unload_request_by_index(u->module->core, m->index, TRUE);
PA_LLIST_REMOVE(struct module, d->module_list, m);
module_free(m);
}
static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *msg, void *userdata) {
DBusMessageIter arg_i;
DBusError err;
const char *value;
struct userdata *u; struct userdata *u;
pa_modargs *ma;
pa_bool_t async = FALSE;
pa_assert(bus); pa_assert(m);
pa_assert(msg);
pa_assert(userdata);
u = userdata;
dbus_error_init(&err); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments");
pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", goto fail;
dbus_message_get_interface(msg),
dbus_message_get_path(msg),
dbus_message_get_member(msg));
if (dbus_message_is_signal(msg, "org.bluez.Adapter", "DeviceRemoved")) {
if (!dbus_message_iter_init(msg, &arg_i))
pa_log("dbus: message has no parameters");
else if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_OBJECT_PATH)
pa_log("dbus: argument is not object path");
else {
struct device *d;
dbus_message_iter_get_basic(&arg_i, &value);
pa_log_debug("hcid: device %s removed", value);
if ((d = device_find(u, value))) {
PA_LLIST_REMOVE(struct device, u->device_list, d);
device_free(d);
}
} }
} else if (dbus_message_is_signal(msg, "org.bluez.Headset", "PropertyChanged") || if (pa_modargs_get_value_boolean(ma, "async", &async) < 0) {
dbus_message_is_signal(msg, "org.bluez.AudioSink", "PropertyChanged")) { pa_log("Failed to parse tsched argument.");
goto fail;
struct device *d;
const char *profile;
DBusMessageIter variant_i;
dbus_bool_t connected;
if (!dbus_message_iter_init(msg, &arg_i)) {
pa_log("dbus: message has no parameters");
goto done;
} }
if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_STRING) { m->userdata = u = pa_xnew0(struct userdata, 1);
pa_log("Property name not a string."); u->module = m;
goto done; u->core = m->core;
}
dbus_message_iter_get_basic(&arg_i, &value); if (setup_dbus(u) < 0)
goto fail;
if (!pa_streq(value, "Connected")) if (!(u->discovery = pa_bluetooth_discovery_new(pa_dbus_connection_get(u->connection), load_module_for_device, u)))
goto done; goto fail;
if (!dbus_message_iter_next(&arg_i)) { if (!async)
pa_log("Property value missing"); pa_bluetooth_discovery_sync(u->discovery);
goto done;
}
if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_VARIANT) { return 0;
pa_log("Property value not a variant.");
goto done;
}
dbus_message_iter_recurse(&arg_i, &variant_i); fail:
pa__done(m);
if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_BOOLEAN) { return -1;
pa_log("Property value not a boolean.");
goto done;
}
dbus_message_iter_get_basic(&variant_i, &connected);
if (dbus_message_is_signal(msg, "org.bluez.Headset", "PropertyChanged"))
profile = "hsp";
else
profile = "a2dp";
d = device_find(u, dbus_message_get_path(msg));
if (connected) {
if (!d) {
d = device_new(dbus_message_get_path(msg));
PA_LLIST_PREPEND(struct device, u->device_list, d);
}
load_module_for_device(u, d, profile);
} else if (d)
unload_module_for_device(u, d, profile);
}
done:
dbus_error_free(&err);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static void get_properties_reply(DBusPendingCall *pending, void *user_data) {
struct userdata *u;
DBusMessage *r;
dbus_bool_t connected;
DBusMessageIter arg_i, element_i;
DBusMessageIter variant_i;
struct device *d;
struct dbus_pending *p;
pa_assert(u = user_data);
r = dbus_pending_call_steal_reply(pending);
if (!r)
goto end;
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log("Error from GetProperties reply: %s", dbus_message_get_error_name(r));
goto end;
}
if (!dbus_message_iter_init(r, &arg_i)) {
pa_log("%s GetProperties reply has no arguments", p->profile);
goto end;
}
if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) {
pa_log("%s GetProperties argument is not an array", p->profile);
goto end;
}
connected = FALSE;
dbus_message_iter_recurse(&arg_i, &element_i);
while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) {
if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter dict_i;
const char *key;
dbus_message_iter_recurse(&element_i, &dict_i);
if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_STRING) {
pa_log("Property name not a string.");
goto end;
}
dbus_message_iter_get_basic(&dict_i, &key);
if (!dbus_message_iter_next(&dict_i)) {
pa_log("Property value missing");
goto end;
}
if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_VARIANT) {
pa_log("Property value not a variant.");
goto end;
}
dbus_message_iter_recurse(&dict_i, &variant_i);
switch (dbus_message_iter_get_arg_type(&variant_i)) {
case DBUS_TYPE_BOOLEAN: {
dbus_bool_t value;
dbus_message_iter_get_basic(&variant_i, &value);
if (pa_streq(key, "Connected")) {
connected = value;
goto endloop;
}
break;
}
}
}
if (!dbus_message_iter_next(&element_i))
break;
}
endloop:
if (connected) {
p = dbus_pending_call_get_data(pending, u->dbus_data_slot);
pa_log_debug("%s: %s connected", p->path, p->profile);
d = device_find(u, p->path);
if (!d) {
d = device_new(p->path);
PA_LLIST_PREPEND(struct device, u->device_list, d);
}
load_module_for_device(u, d, p->profile);
}
dbus_message_unref(r);
end:
p = dbus_pending_call_get_data(pending, u->dbus_data_slot);
PA_LLIST_REMOVE(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_free(p);
}
static void list_devices_reply(DBusPendingCall *pending, void *user_data) {
DBusMessage *r, *m;
DBusPendingCall *call;
DBusError e;
char **paths = NULL;
int i, num = -1;
struct dbus_pending *p;
struct userdata *u;
pa_assert(u = user_data);
dbus_error_init(&e);
r = dbus_pending_call_steal_reply(pending);
if (!r) {
pa_log("Failed to get ListDevices reply");
goto end;
}
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log("Error from ListDevices reply: %s", dbus_message_get_error_name(r));
goto end;
}
if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
pa_log("org.bluez.Adapter.ListDevices returned an error: '%s'\n", e.message);
dbus_error_free(&e);
} else {
for (i = 0; i < num; ++i) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", paths[i], "org.bluez.Headset", "GetProperties"));
if (dbus_connection_send_with_reply(pa_dbus_connection_get(u->conn), m, &call, -1)) {
p = dbus_pending_new(u, call, paths[i], "hsp");
PA_LLIST_PREPEND(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_call_set_notify(call, get_properties_reply, u, NULL);
} else {
pa_log("Failed to send GetProperties");
}
dbus_message_unref(m);
pa_assert_se(m = dbus_message_new_method_call("org.bluez", paths[i], "org.bluez.AudioSink", "GetProperties"));
if (dbus_connection_send_with_reply(pa_dbus_connection_get(u->conn), m, &call, -1)) {
p = dbus_pending_new(u, call, paths[i], "a2dp");
PA_LLIST_PREPEND(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_call_set_notify(call, get_properties_reply, u, NULL);
} else {
pa_log("Failed to send GetProperties");
}
dbus_message_unref(m);
}
}
if (paths)
dbus_free_string_array (paths);
dbus_message_unref(r);
end:
p = dbus_pending_call_get_data(pending, u->dbus_data_slot);
PA_LLIST_REMOVE(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_free(p);
}
static void list_adapters_reply(DBusPendingCall *pending, void *user_data) {
DBusMessage *r, *m;
DBusPendingCall *call;
DBusError e;
char **paths = NULL;
int i, num = -1;
struct dbus_pending *p;
struct userdata *u;
pa_assert(u = user_data);
dbus_error_init(&e);
r = dbus_pending_call_steal_reply(pending);
if (!r) {
pa_log("Failed to get ListAdapters reply");
goto end;
}
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
pa_log("Error from ListAdapters reply: %s", dbus_message_get_error_name(r));
goto end;
}
if (!dbus_message_get_args(r, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &paths, &num, DBUS_TYPE_INVALID)) {
pa_log("org.bluez.Manager.ListAdapters returned an error: '%s'\n", e.message);
dbus_error_free(&e);
} else {
for (i = 0; i < num; ++i) {
pa_assert_se(m = dbus_message_new_method_call("org.bluez", paths[i], "org.bluez.Adapter", "ListDevices"));
if (dbus_connection_send_with_reply(pa_dbus_connection_get(u->conn), m, &call, -1)) {
p = dbus_pending_new(u, call, NULL, NULL);
PA_LLIST_PREPEND(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_call_set_notify(call, list_devices_reply, u, NULL);
} else {
pa_log("Failed to send ListDevices");
}
dbus_message_unref(m);
}
}
if (paths)
dbus_free_string_array (paths);
dbus_message_unref(r);
end:
p = dbus_pending_call_get_data(pending, u->dbus_data_slot);
PA_LLIST_REMOVE(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_free(p);
}
static void lookup_devices(struct userdata *u) {
DBusMessage *m;
DBusPendingCall *call;
struct dbus_pending *p;
pa_assert(u);
pa_assert_se(m = dbus_message_new_method_call("org.bluez", "/", "org.bluez.Manager", "ListAdapters"));
if (dbus_connection_send_with_reply(pa_dbus_connection_get(u->conn), m, &call, -1)) {
p = dbus_pending_new(u, call, NULL, NULL);
PA_LLIST_PREPEND(struct dbus_pending, u->dbus_pending_list, p);
dbus_pending_call_set_notify(call, list_adapters_reply, u, NULL);
} else {
pa_log("Failed to send ListAdapters");
}
dbus_message_unref(m);
} }
void pa__done(pa_module* m) { void pa__done(pa_module* m) {
struct userdata *u; struct userdata *u;
struct device *i;
struct dbus_pending *p;
pa_assert(m); pa_assert(m);
if (!(u = m->userdata)) if (!(u = m->userdata))
return; return;
while ((p = u->dbus_pending_list)) { if (u->discovery)
PA_LLIST_REMOVE(struct dbus_pending, u->dbus_pending_list, p); pa_bluetooth_discovery_free(u->discovery);
dbus_pending_free(p);
}
while ((i = u->device_list)) { if (u->connection)
PA_LLIST_REMOVE(struct device, u->device_list, i); pa_dbus_connection_unref(u->connection);
device_free(i);
}
if (u->dbus_data_slot != -1) {
dbus_pending_call_free_data_slot(&u->dbus_data_slot);
}
if (u->conn) {
DBusError error;
dbus_error_init(&error);
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", &error);
dbus_error_free(&error);
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", &error);
dbus_error_free(&error);
dbus_bus_remove_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", &error);
dbus_error_free(&error);
dbus_connection_remove_filter(pa_dbus_connection_get(u->conn), filter_cb, u);
pa_dbus_connection_unref(u->conn);
}
pa_xfree(u); pa_xfree(u);
} }
int pa__init(pa_module* m) {
DBusError err;
struct userdata *u;
pa_assert(m);
dbus_error_init(&err);
m->userdata = u = pa_xnew(struct userdata, 1);
u->dbus_data_slot = -1;
u->module = m;
PA_LLIST_HEAD_INIT(struct device, u->device_list);
PA_LLIST_HEAD_INIT(DBusPendingCall, u->dbus_pending_list);
/* connect to the bus */
u->conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &err);
if (dbus_error_is_set(&err) || (u->conn == NULL) ) {
pa_log("Failed to get D-Bus connection: %s", err.message);
goto fail;
}
if (!dbus_pending_call_allocate_data_slot(&u->dbus_data_slot))
goto fail;
/* dynamic detection of bluetooth audio devices */
if (!dbus_connection_add_filter(pa_dbus_connection_get(u->conn), filter_cb, u, NULL)) {
pa_log_error("Failed to add filter function");
goto fail;
}
dbus_bus_add_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", &err);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.Adapter signals: %s: %s", err.name, err.message);
goto fail;
}
dbus_bus_add_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", &err);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.Headset signals: %s: %s", err.name, err.message);
goto fail;
}
dbus_bus_add_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", &err);
if (dbus_error_is_set(&err)) {
pa_log_error("Unable to subscribe to org.bluez.AudioSink signals: %s: %s", err.name, err.message);
goto fail;
}
lookup_devices(u);
return 0;
fail:
dbus_error_free(&err);
pa__done(m);
return -1;
}