mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-01 22:58:47 -04:00
Merge commit 'elmarco/bluetooth-fixes'
This commit is contained in:
commit
1390564227
8 changed files with 275 additions and 131 deletions
|
|
@ -39,6 +39,25 @@ struct pa_bluetooth_discovery {
|
|||
pa_hook hook;
|
||||
};
|
||||
|
||||
static void get_properties_reply(DBusPendingCall *pending, void *userdata);
|
||||
static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessage *m, DBusPendingCallNotifyFunction func);
|
||||
|
||||
static enum pa_bt_audio_state pa_bt_audio_state_from_string(const char* value) {
|
||||
pa_assert(value);
|
||||
|
||||
if (pa_streq(value, "disconnected")) {
|
||||
return PA_BT_AUDIO_STATE_DISCONNECTED;
|
||||
} else if (pa_streq(value, "connecting")) {
|
||||
return PA_BT_AUDIO_STATE_CONNECTING;
|
||||
} else if (pa_streq(value, "connected")) {
|
||||
return PA_BT_AUDIO_STATE_CONNECTED;
|
||||
} else if (pa_streq(value, "playing")) {
|
||||
return PA_BT_AUDIO_STATE_PLAYING;
|
||||
}
|
||||
|
||||
return PA_BT_AUDIO_STATE_INVALID;
|
||||
}
|
||||
|
||||
static pa_bluetooth_uuid *uuid_new(const char *uuid) {
|
||||
pa_bluetooth_uuid *u;
|
||||
|
||||
|
|
@ -63,7 +82,7 @@ static pa_bluetooth_device* device_new(const char *path) {
|
|||
|
||||
d->dead = FALSE;
|
||||
|
||||
d->device_info_valid = d->audio_sink_info_valid = d->headset_info_valid = 0;
|
||||
d->device_info_valid = 0;
|
||||
|
||||
d->name = NULL;
|
||||
d->path = pa_xstrdup(path);
|
||||
|
|
@ -75,9 +94,9 @@ static pa_bluetooth_device* device_new(const char *path) {
|
|||
d->class = -1;
|
||||
d->trusted = -1;
|
||||
|
||||
d->audio_sink_connected = -1;
|
||||
|
||||
d->headset_connected = -1;
|
||||
d->audio_state = PA_BT_AUDIO_STATE_INVALID;
|
||||
d->audio_sink_state = PA_BT_AUDIO_STATE_INVALID;
|
||||
d->headset_state = PA_BT_AUDIO_STATE_INVALID;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
|
@ -99,25 +118,14 @@ static void device_free(pa_bluetooth_device *d) {
|
|||
pa_xfree(d);
|
||||
}
|
||||
|
||||
static pa_bool_t device_is_loaded(pa_bluetooth_device *d) {
|
||||
static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
|
||||
pa_assert(d);
|
||||
|
||||
return
|
||||
d->device_info_valid &&
|
||||
d->audio_sink_info_valid &&
|
||||
d->headset_info_valid;
|
||||
}
|
||||
|
||||
static pa_bool_t device_is_audio(pa_bluetooth_device *d) {
|
||||
pa_assert(d);
|
||||
|
||||
pa_assert(d->device_info_valid);
|
||||
pa_assert(d->audio_sink_info_valid);
|
||||
pa_assert(d->headset_info_valid);
|
||||
|
||||
return
|
||||
d->device_info_valid > 0 &&
|
||||
(d->audio_sink_info_valid > 0 || d->headset_info_valid > 0);
|
||||
(d->audio_state != PA_BT_AUDIO_STATE_INVALID ||
|
||||
d->audio_sink_state != PA_BT_AUDIO_STATE_INVALID ||
|
||||
d->headset_state != PA_BT_AUDIO_STATE_INVALID);
|
||||
}
|
||||
|
||||
static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device *d, DBusMessageIter *i) {
|
||||
|
|
@ -213,11 +221,25 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
|
|||
while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) {
|
||||
pa_bluetooth_uuid *node;
|
||||
const char *value;
|
||||
DBusMessage *m;
|
||||
|
||||
dbus_message_iter_get_basic(&ai, &value);
|
||||
node = uuid_new(value);
|
||||
PA_LLIST_PREPEND(pa_bluetooth_uuid, d->uuids, node);
|
||||
|
||||
/* this might eventually be racy if .Audio is not there yet, but the State change will come anyway later, so this call is for cold-detection mostly */
|
||||
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Audio", "GetProperties"));
|
||||
send_and_add_to_pending(y, d, m, get_properties_reply);
|
||||
|
||||
/* Vudentz said the interfaces are here when the UUIDs are announced */
|
||||
if (strcasecmp(HSP_HS_UUID, value) == 0 || strcasecmp(HFP_HS_UUID, value) == 0) {
|
||||
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.Headset", "GetProperties"));
|
||||
send_and_add_to_pending(y, d, m, get_properties_reply);
|
||||
} else if (strcasecmp(A2DP_SINK_UUID, value) == 0) {
|
||||
pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->path, "org.bluez.AudioSink", "GetProperties"));
|
||||
send_and_add_to_pending(y, d, m, get_properties_reply);
|
||||
}
|
||||
|
||||
if (!dbus_message_iter_next(&ai))
|
||||
break;
|
||||
}
|
||||
|
|
@ -230,12 +252,12 @@ static int parse_device_property(pa_bluetooth_discovery *y, pa_bluetooth_device
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusMessageIter *i) {
|
||||
static int parse_audio_property(pa_bluetooth_discovery *u, int *state, DBusMessageIter *i) {
|
||||
const char *key;
|
||||
DBusMessageIter variant_i;
|
||||
|
||||
pa_assert(u);
|
||||
pa_assert(connected);
|
||||
pa_assert(state);
|
||||
pa_assert(i);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) {
|
||||
|
|
@ -257,17 +279,27 @@ static int parse_audio_property(pa_bluetooth_discovery *u, int *connected, DBusM
|
|||
|
||||
dbus_message_iter_recurse(i, &variant_i);
|
||||
|
||||
/* pa_log_debug("Parsing property org.bluez.{AudioSink|Headset}.%s", key); */
|
||||
/* pa_log_debug("Parsing property org.bluez.{Audio|AudioSink|Headset}.%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, "State"))
|
||||
*state = pa_bt_audio_state_from_string(value);
|
||||
/* pa_log_debug("Value %s", value); */
|
||||
}
|
||||
|
||||
case DBUS_TYPE_BOOLEAN: {
|
||||
|
||||
dbus_bool_t value;
|
||||
dbus_message_iter_get_basic(&variant_i, &value);
|
||||
|
||||
if (pa_streq(key, "Connected"))
|
||||
*connected = !!value;
|
||||
/* if (pa_streq(key, "Connected")) */
|
||||
/* *connected = !!value; */
|
||||
|
||||
/* pa_log_debug("Value %s", pa_yes_no(value)); */
|
||||
|
||||
|
|
@ -282,9 +314,6 @@ static void run_callback(pa_bluetooth_discovery *y, pa_bluetooth_device *d, pa_b
|
|||
pa_assert(y);
|
||||
pa_assert(d);
|
||||
|
||||
if (!device_is_loaded(d))
|
||||
return;
|
||||
|
||||
if (!device_is_audio(d))
|
||||
return;
|
||||
|
||||
|
|
@ -314,10 +343,6 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
|
|||
|
||||
if (dbus_message_is_method_call(p->message, "org.bluez.Device", "GetProperties"))
|
||||
d->device_info_valid = valid;
|
||||
else if (dbus_message_is_method_call(p->message, "org.bluez.Headset", "GetProperties"))
|
||||
d->headset_info_valid = valid;
|
||||
else if (dbus_message_is_method_call(p->message, "org.bluez.AudioSink", "GetProperties"))
|
||||
d->audio_sink_info_valid = valid;
|
||||
|
||||
if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
|
||||
|
||||
|
|
@ -349,12 +374,16 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {
|
|||
if (parse_device_property(y, d, &dict_i) < 0)
|
||||
goto finish;
|
||||
|
||||
} else if (dbus_message_has_interface(p->message, "org.bluez.Audio")) {
|
||||
if (parse_audio_property(y, &d->audio_state, &dict_i) < 0)
|
||||
goto finish;
|
||||
|
||||
} else if (dbus_message_has_interface(p->message, "org.bluez.Headset")) {
|
||||
if (parse_audio_property(y, &d->headset_connected, &dict_i) < 0)
|
||||
if (parse_audio_property(y, &d->headset_state, &dict_i) < 0)
|
||||
goto finish;
|
||||
|
||||
} else if (dbus_message_has_interface(p->message, "org.bluez.AudioSink")) {
|
||||
if (parse_audio_property(y, &d->audio_sink_connected, &dict_i) < 0)
|
||||
if (parse_audio_property(y, &d->audio_sink_state, &dict_i) < 0)
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
|
@ -401,12 +430,6 @@ static void found_device(pa_bluetooth_discovery *y, const char* path) {
|
|||
|
||||
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Device", "GetProperties"));
|
||||
send_and_add_to_pending(y, d, m, get_properties_reply);
|
||||
|
||||
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Headset", "GetProperties"));
|
||||
send_and_add_to_pending(y, d, m, get_properties_reply);
|
||||
|
||||
pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.AudioSink", "GetProperties"));
|
||||
send_and_add_to_pending(y, d, m, get_properties_reply);
|
||||
}
|
||||
|
||||
static void list_devices_reply(DBusPendingCall *pending, void *userdata) {
|
||||
|
|
@ -566,7 +589,8 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
|
|||
found_adapter(y, path);
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
|
||||
} else if (dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
|
||||
} else if (dbus_message_is_signal(m, "org.bluez.Audio", "PropertyChanged") ||
|
||||
dbus_message_is_signal(m, "org.bluez.Headset", "PropertyChanged") ||
|
||||
dbus_message_is_signal(m, "org.bluez.AudioSink", "PropertyChanged") ||
|
||||
dbus_message_is_signal(m, "org.bluez.Device", "PropertyChanged")) {
|
||||
|
||||
|
|
@ -584,12 +608,16 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us
|
|||
if (parse_device_property(y, d, &arg_i) < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (dbus_message_has_interface(m, "org.bluez.Audio")) {
|
||||
if (parse_audio_property(y, &d->audio_state, &arg_i) < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (dbus_message_has_interface(m, "org.bluez.Headset")) {
|
||||
if (parse_audio_property(y, &d->headset_connected, &arg_i) < 0)
|
||||
if (parse_audio_property(y, &d->headset_state, &arg_i) < 0)
|
||||
goto fail;
|
||||
|
||||
} else if (dbus_message_has_interface(m, "org.bluez.AudioSink")) {
|
||||
if (parse_audio_property(y, &d->audio_sink_connected, &arg_i) < 0)
|
||||
if (parse_audio_property(y, &d->audio_sink_state, &arg_i) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -684,6 +712,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
|
|||
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL) < 0) {
|
||||
pa_log("Failed to add D-Bus matches: %s", err.message);
|
||||
|
|
@ -740,6 +769,7 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
|
|||
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceCreated'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Device',member='PropertyChanged'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Audio',member='PropertyChanged'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'",
|
||||
"type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", NULL);
|
||||
|
||||
|
|
@ -827,3 +857,16 @@ char *pa_bluetooth_cleanup_name(const char *name) {
|
|||
|
||||
return t;
|
||||
}
|
||||
|
||||
pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid) {
|
||||
pa_assert(uuid);
|
||||
|
||||
while (uuids) {
|
||||
if (strcasecmp(uuids->uuid, uuid) == 0)
|
||||
return TRUE;
|
||||
|
||||
uuids = uuids->next;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,20 @@
|
|||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
|
||||
/* UUID copied from bluez/audio/device.h */
|
||||
#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
#define HSP_HS_UUID "00001108-0000-1000-8000-00805F9B34FB"
|
||||
#define HSP_AG_UUID "00001112-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
#define HFP_HS_UUID "0000111E-0000-1000-8000-00805F9B34FB"
|
||||
#define HFP_AG_UUID "0000111F-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
#define ADVANCED_AUDIO_UUID "0000110D-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
#define A2DP_SOURCE_UUID "0000110A-0000-1000-8000-00805F9B34FB"
|
||||
#define A2DP_SINK_UUID "0000110B-0000-1000-8000-00805F9B34FB"
|
||||
|
||||
typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;
|
||||
typedef struct pa_bluetooth_device pa_bluetooth_device;
|
||||
typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
|
||||
|
|
@ -39,12 +53,20 @@ struct pa_bluetooth_uuid {
|
|||
PA_LLIST_FIELDS(pa_bluetooth_uuid);
|
||||
};
|
||||
|
||||
/* This enum is shared among Audio, Headset, and AudioSink, although not all values are acceptable in all profiles */
|
||||
enum pa_bt_audio_state {
|
||||
PA_BT_AUDIO_STATE_INVALID = -1,
|
||||
PA_BT_AUDIO_STATE_DISCONNECTED,
|
||||
PA_BT_AUDIO_STATE_CONNECTING,
|
||||
PA_BT_AUDIO_STATE_CONNECTED,
|
||||
PA_BT_AUDIO_STATE_PLAYING,
|
||||
PA_BT_AUDIO_STATE_LAST
|
||||
};
|
||||
|
||||
struct pa_bluetooth_device {
|
||||
pa_bool_t dead;
|
||||
|
||||
int device_info_valid; /* 0: no results yet; 1: good results; -1: bad results ... */
|
||||
int audio_sink_info_valid; /* ... same here ... */
|
||||
int headset_info_valid; /* ... and here */
|
||||
|
||||
/* Device information */
|
||||
char *name;
|
||||
|
|
@ -57,11 +79,14 @@ struct pa_bluetooth_device {
|
|||
int class;
|
||||
int trusted;
|
||||
|
||||
/* AudioSink information */
|
||||
int audio_sink_connected;
|
||||
/* Audio state */
|
||||
int audio_state;
|
||||
|
||||
/* Headset information */
|
||||
int headset_connected;
|
||||
/* AudioSink state */
|
||||
int audio_sink_state;
|
||||
|
||||
/* Headset state */
|
||||
int headset_state;
|
||||
};
|
||||
|
||||
pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core);
|
||||
|
|
@ -79,4 +104,6 @@ const char* pa_bluetooth_get_form_factor(uint32_t class);
|
|||
|
||||
char *pa_bluetooth_cleanup_name(const char *name);
|
||||
|
||||
pa_bool_t pa_bluetooth_uuid_has(pa_bluetooth_uuid *uuids, const char *uuid);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ static const char *strtypes[] = {
|
|||
/* This table contains the string representation for messages names */
|
||||
static const char *strnames[] = {
|
||||
"BT_GET_CAPABILITIES",
|
||||
"BT_OPEN",
|
||||
"BT_SET_CONFIGURATION",
|
||||
"BT_NEW_STREAM",
|
||||
"BT_START_STREAM",
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ extern "C" {
|
|||
#include <sys/un.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define BT_SUGGESTED_BUFFER_SIZE 128
|
||||
#define BT_SUGGESTED_BUFFER_SIZE 512
|
||||
#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio"
|
||||
|
||||
/* Generic message header definition, except for RESPONSE messages */
|
||||
|
|
@ -94,10 +94,12 @@ typedef struct {
|
|||
|
||||
/* Messages names */
|
||||
#define BT_GET_CAPABILITIES 0
|
||||
#define BT_SET_CONFIGURATION 1
|
||||
#define BT_NEW_STREAM 2
|
||||
#define BT_START_STREAM 3
|
||||
#define BT_STOP_STREAM 4
|
||||
#define BT_OPEN 1
|
||||
#define BT_SET_CONFIGURATION 2
|
||||
#define BT_NEW_STREAM 3
|
||||
#define BT_START_STREAM 4
|
||||
#define BT_STOP_STREAM 5
|
||||
#define BT_CLOSE 6
|
||||
#define BT_CONTROL 7
|
||||
|
||||
#define BT_CAPABILITIES_TRANSPORT_A2DP 0
|
||||
|
|
@ -112,19 +114,31 @@ typedef struct {
|
|||
|
||||
struct bt_get_capabilities_req {
|
||||
bt_audio_msg_header_t h;
|
||||
char device[18]; /* Address of the remote Device */
|
||||
char source[18]; /* Address of the local Device */
|
||||
char destination[18];/* Address of the remote Device */
|
||||
char object[128]; /* DBus object path */
|
||||
uint8_t transport; /* Requested transport */
|
||||
uint8_t flags; /* Requested flags */
|
||||
uint8_t seid; /* Requested capability configuration */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/**
|
||||
* SBC Codec parameters as per A2DP profile 1.0 § 4.3
|
||||
* SBC Codec parameters as per A2DP profile 1.0 § 4.3
|
||||
*/
|
||||
|
||||
#define BT_A2DP_CODEC_SBC 0x00
|
||||
#define BT_A2DP_CODEC_MPEG12 0x01
|
||||
#define BT_A2DP_CODEC_MPEG24 0x02
|
||||
#define BT_A2DP_CODEC_ATRAC 0x03
|
||||
/* A2DP seid are 6 bytes long so HSP/HFP are assigned to 7-8 bits */
|
||||
#define BT_A2DP_SEID_RANGE (1 << 6) - 1
|
||||
|
||||
#define BT_A2DP_SBC_SOURCE 0x00
|
||||
#define BT_A2DP_SBC_SINK 0x01
|
||||
#define BT_A2DP_MPEG12_SOURCE 0x02
|
||||
#define BT_A2DP_MPEG12_SINK 0x03
|
||||
#define BT_A2DP_MPEG24_SOURCE 0x04
|
||||
#define BT_A2DP_MPEG24_SINK 0x05
|
||||
#define BT_A2DP_ATRAC_SOURCE 0x06
|
||||
#define BT_A2DP_ATRAC_SINK 0x07
|
||||
#define BT_A2DP_UNKNOWN_SOURCE 0x08
|
||||
#define BT_A2DP_UNKNOWN_SINK 0x09
|
||||
|
||||
#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3)
|
||||
#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2)
|
||||
|
|
@ -163,10 +177,16 @@ struct bt_get_capabilities_req {
|
|||
#define BT_PCM_FLAG_NREC 0x01
|
||||
#define BT_PCM_FLAG_PCM_ROUTING 0x02
|
||||
|
||||
#define BT_WRITE_LOCK (1 << 1)
|
||||
#define BT_READ_LOCK 1
|
||||
|
||||
typedef struct {
|
||||
uint8_t seid;
|
||||
uint8_t transport;
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
uint8_t configured;
|
||||
uint8_t lock;
|
||||
uint8_t data[0];
|
||||
} __attribute__ ((packed)) codec_capabilities_t;
|
||||
|
||||
|
|
@ -199,20 +219,35 @@ typedef struct {
|
|||
|
||||
struct bt_get_capabilities_rsp {
|
||||
bt_audio_msg_header_t h;
|
||||
char source[18]; /* Address of the local Device */
|
||||
char destination[18];/* Address of the remote Device */
|
||||
char object[128]; /* DBus object path */
|
||||
uint8_t data[0]; /* First codec_capabilities_t */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_open_req {
|
||||
bt_audio_msg_header_t h;
|
||||
char source[18]; /* Address of the local Device */
|
||||
char destination[18];/* Address of the remote Device */
|
||||
char object[128]; /* DBus object path */
|
||||
uint8_t seid; /* Requested capability configuration to lock */
|
||||
uint8_t lock; /* Requested lock */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_open_rsp {
|
||||
bt_audio_msg_header_t h;
|
||||
char source[18]; /* Address of the local Device */
|
||||
char destination[18];/* Address of the remote Device */
|
||||
char object[128]; /* DBus object path */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_set_configuration_req {
|
||||
bt_audio_msg_header_t h;
|
||||
char device[18]; /* Address of the remote Device */
|
||||
uint8_t access_mode; /* Requested access mode */
|
||||
codec_capabilities_t codec; /* Requested codec */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_set_configuration_rsp {
|
||||
bt_audio_msg_header_t h;
|
||||
uint8_t transport; /* Granted transport */
|
||||
uint8_t access_mode; /* Granted access mode */
|
||||
uint16_t link_mtu; /* Max length that transport supports */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
|
|
@ -241,6 +276,14 @@ struct bt_stop_stream_rsp {
|
|||
bt_audio_msg_header_t h;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_close_req {
|
||||
bt_audio_msg_header_t h;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_close_rsp {
|
||||
bt_audio_msg_header_t h;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct bt_suspend_stream_ind {
|
||||
bt_audio_msg_header_t h;
|
||||
} __attribute__ ((packed));
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ struct userdata {
|
|||
|
||||
char *address;
|
||||
char *path;
|
||||
const pa_bluetooth_device* device;
|
||||
|
||||
pa_dbus_connection *connection;
|
||||
|
||||
|
|
@ -274,7 +275,7 @@ static ssize_t service_expect(struct userdata*u, bt_audio_msg_header_t *rsp, siz
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *rsp) {
|
||||
static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capabilities_rsp *rsp) {
|
||||
uint16_t bytes_left;
|
||||
const codec_capabilities_t *codec;
|
||||
|
||||
|
|
@ -305,12 +306,15 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *
|
|||
|
||||
pa_assert(codec->type == BT_HFP_CODEC_PCM);
|
||||
|
||||
if (codec->configured && seid == 0)
|
||||
return codec->seid;
|
||||
|
||||
memcpy(&u->hsp.pcm_capabilities, codec, sizeof(u->hsp.pcm_capabilities));
|
||||
|
||||
} else if (u->profile == PROFILE_A2DP) {
|
||||
|
||||
while (bytes_left > 0) {
|
||||
if (codec->type == BT_A2DP_CODEC_SBC)
|
||||
if ((codec->type == BT_A2DP_SBC_SINK) && !codec->lock)
|
||||
break;
|
||||
|
||||
bytes_left -= codec->length;
|
||||
|
|
@ -320,7 +324,10 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *
|
|||
if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities))
|
||||
return -1;
|
||||
|
||||
pa_assert(codec->type == BT_A2DP_CODEC_SBC);
|
||||
pa_assert(codec->type == BT_A2DP_SBC_SINK);
|
||||
|
||||
if (codec->configured && seid == 0)
|
||||
return codec->seid;
|
||||
|
||||
memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));
|
||||
}
|
||||
|
|
@ -328,13 +335,14 @@ static int parse_caps(struct userdata *u, const struct bt_get_capabilities_rsp *
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int get_caps(struct userdata *u) {
|
||||
static int get_caps(struct userdata *u, uint8_t seid) {
|
||||
union {
|
||||
struct bt_get_capabilities_req getcaps_req;
|
||||
struct bt_get_capabilities_rsp getcaps_rsp;
|
||||
bt_audio_error_t error;
|
||||
uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
|
||||
} msg;
|
||||
int ret;
|
||||
|
||||
pa_assert(u);
|
||||
|
||||
|
|
@ -342,8 +350,9 @@ static int get_caps(struct userdata *u) {
|
|||
msg.getcaps_req.h.type = BT_REQUEST;
|
||||
msg.getcaps_req.h.name = BT_GET_CAPABILITIES;
|
||||
msg.getcaps_req.h.length = sizeof(msg.getcaps_req);
|
||||
msg.getcaps_req.seid = seid;
|
||||
|
||||
pa_strlcpy(msg.getcaps_req.device, u->address, sizeof(msg.getcaps_req.device));
|
||||
pa_strlcpy(msg.getcaps_req.object, u->path, sizeof(msg.getcaps_req.object));
|
||||
if (u->profile == PROFILE_A2DP)
|
||||
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
|
||||
else {
|
||||
|
|
@ -358,7 +367,11 @@ static int get_caps(struct userdata *u) {
|
|||
if (service_expect(u, &msg.getcaps_rsp.h, sizeof(msg), BT_GET_CAPABILITIES, 0) < 0)
|
||||
return -1;
|
||||
|
||||
return parse_caps(u, &msg.getcaps_rsp);
|
||||
ret = parse_caps(u, seid, &msg.getcaps_rsp);
|
||||
if (ret <= 0)
|
||||
return ret;
|
||||
|
||||
return get_caps(u, ret);
|
||||
}
|
||||
|
||||
static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) {
|
||||
|
|
@ -434,8 +447,8 @@ static int setup_a2dp(struct userdata *u) {
|
|||
break;
|
||||
}
|
||||
|
||||
if ((unsigned) i >= PA_ELEMENTSOF(freq_table)) {
|
||||
for (; i >= 0; i--) {
|
||||
if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
|
||||
for (--i; i >= 0; i--) {
|
||||
if (cap->frequency & freq_table[i].cap) {
|
||||
u->sample_spec.rate = freq_table[i].rate;
|
||||
cap->frequency = freq_table[i].cap;
|
||||
|
|
@ -449,6 +462,11 @@ static int setup_a2dp(struct userdata *u) {
|
|||
}
|
||||
}
|
||||
|
||||
pa_assert(i < PA_ELEMENTSOF(freq_table));
|
||||
|
||||
if (cap->capability.configured)
|
||||
return 0;
|
||||
|
||||
if (u->sample_spec.channels <= 1) {
|
||||
if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
|
||||
cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
|
||||
|
|
@ -601,12 +619,29 @@ static void setup_sbc(struct a2dp_info *a2dp) {
|
|||
|
||||
static int set_conf(struct userdata *u) {
|
||||
union {
|
||||
struct bt_open_req open_req;
|
||||
struct bt_open_rsp open_rsp;
|
||||
struct bt_set_configuration_req setconf_req;
|
||||
struct bt_set_configuration_rsp setconf_rsp;
|
||||
bt_audio_error_t error;
|
||||
uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
|
||||
} msg;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.open_req.h.type = BT_REQUEST;
|
||||
msg.open_req.h.name = BT_OPEN;
|
||||
msg.open_req.h.length = sizeof(msg.open_req);
|
||||
|
||||
pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object));
|
||||
msg.open_req.seid = u->profile == PROFILE_A2DP ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
|
||||
msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
|
||||
|
||||
if (service_send(u, &msg.open_req.h) < 0)
|
||||
return -1;
|
||||
|
||||
if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0)
|
||||
return -1;
|
||||
|
||||
if (u->profile == PROFILE_A2DP ) {
|
||||
u->sample_spec.format = PA_SAMPLE_S16LE;
|
||||
|
||||
|
|
@ -625,15 +660,14 @@ static int set_conf(struct userdata *u) {
|
|||
msg.setconf_req.h.name = BT_SET_CONFIGURATION;
|
||||
msg.setconf_req.h.length = sizeof(msg.setconf_req);
|
||||
|
||||
pa_strlcpy(msg.setconf_req.device, u->address, sizeof(msg.setconf_req.device));
|
||||
msg.setconf_req.access_mode = u->profile == PROFILE_A2DP ? BT_CAPABILITIES_ACCESS_MODE_WRITE : BT_CAPABILITIES_ACCESS_MODE_READWRITE;
|
||||
|
||||
msg.setconf_req.codec.transport = u->profile == PROFILE_A2DP ? BT_CAPABILITIES_TRANSPORT_A2DP : BT_CAPABILITIES_TRANSPORT_SCO;
|
||||
|
||||
if (u->profile == PROFILE_A2DP) {
|
||||
memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities));
|
||||
msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec);
|
||||
} else {
|
||||
msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
|
||||
msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1;
|
||||
msg.setconf_req.codec.length = sizeof(pcm_capabilities_t);
|
||||
}
|
||||
msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec);
|
||||
|
||||
if (service_send(u, &msg.setconf_req.h) < 0)
|
||||
return -1;
|
||||
|
|
@ -641,18 +675,6 @@ static int set_conf(struct userdata *u) {
|
|||
if (service_expect(u, &msg.setconf_rsp.h, sizeof(msg), BT_SET_CONFIGURATION, sizeof(msg.setconf_rsp)) < 0)
|
||||
return -1;
|
||||
|
||||
if ((u->profile == PROFILE_A2DP && msg.setconf_rsp.transport != BT_CAPABILITIES_TRANSPORT_A2DP) ||
|
||||
(u->profile == PROFILE_HSP && msg.setconf_rsp.transport != BT_CAPABILITIES_TRANSPORT_SCO)) {
|
||||
pa_log("Transport doesn't match what we requested.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((u->profile == PROFILE_A2DP && msg.setconf_rsp.access_mode != BT_CAPABILITIES_ACCESS_MODE_WRITE) ||
|
||||
(u->profile == PROFILE_HSP && msg.setconf_rsp.access_mode != BT_CAPABILITIES_ACCESS_MODE_READWRITE)) {
|
||||
pa_log("Access mode doesn't match what we requested.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
u->link_mtu = msg.setconf_rsp.link_mtu;
|
||||
|
||||
/* setup SBC encoder now we agree on parameters */
|
||||
|
|
@ -1570,7 +1592,7 @@ static int init_bt(struct userdata *u) {
|
|||
static int setup_bt(struct userdata *u) {
|
||||
pa_assert(u);
|
||||
|
||||
if (get_caps(u) < 0)
|
||||
if (get_caps(u, 0) < 0)
|
||||
return -1;
|
||||
|
||||
pa_log_debug("Got device capabilities");
|
||||
|
|
@ -1713,6 +1735,15 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
|
|||
|
||||
d = PA_CARD_PROFILE_DATA(new_profile);
|
||||
|
||||
if (u->device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) {
|
||||
pa_log_warn("HSP is not connected, refused to switch profile");
|
||||
return -1;
|
||||
}
|
||||
else if (u->device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) {
|
||||
pa_log_warn("A2DP is not connected, refused to switch profile");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (u->sink) {
|
||||
inputs = pa_sink_move_all_start(u->sink);
|
||||
#ifdef NOKIA
|
||||
|
|
@ -1789,7 +1820,11 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl
|
|||
|
||||
data.profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
||||
|
||||
if (device->audio_sink_info_valid > 0) {
|
||||
/* we base hsp/a2dp availability on UUIDs.
|
||||
Ideally, it would be based on "Connected" state, but
|
||||
we can't afford to wait for this information when
|
||||
we are loaded with profile="hsp", for instance */
|
||||
if (pa_bluetooth_uuid_has(device->uuids, A2DP_SINK_UUID)) {
|
||||
p = pa_card_profile_new("a2dp", _("High Fidelity Playback (A2DP)"), sizeof(enum profile));
|
||||
p->priority = 10;
|
||||
p->n_sinks = 1;
|
||||
|
|
@ -1803,7 +1838,8 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl
|
|||
pa_hashmap_put(data.profiles, p->name, p);
|
||||
}
|
||||
|
||||
if (device->headset_info_valid > 0) {
|
||||
if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) ||
|
||||
pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) {
|
||||
p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile));
|
||||
p->priority = 20;
|
||||
p->n_sinks = 1;
|
||||
|
|
@ -1906,7 +1942,6 @@ int pa__init(pa_module* m) {
|
|||
uint32_t channels;
|
||||
struct userdata *u;
|
||||
const char *address, *path;
|
||||
const pa_bluetooth_device *d;
|
||||
pa_bluetooth_discovery *y = NULL;
|
||||
DBusError err;
|
||||
char *mike, *speaker;
|
||||
|
|
@ -1967,11 +2002,11 @@ int pa__init(pa_module* m) {
|
|||
if (!(y = pa_bluetooth_discovery_get(m->core)))
|
||||
goto fail;
|
||||
|
||||
if (!(d = find_device(u, y, address, path)))
|
||||
if (!(u->device = find_device(u, y, address, path))) /* should discovery ref be kept? */
|
||||
goto fail;
|
||||
|
||||
/* Add the card structure. This will also initialize the default profile */
|
||||
if (add_card(u, pa_modargs_get_value(ma, "profile", NULL), d) < 0)
|
||||
if (add_card(u, pa_modargs_get_value(ma, "profile", NULL), u->device) < 0)
|
||||
goto fail;
|
||||
|
||||
pa_bluetooth_discovery_unref(y);
|
||||
|
|
|
|||
|
|
@ -84,8 +84,7 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
|
|||
mi = pa_hashmap_get(u->hashmap, d->path);
|
||||
|
||||
if (!d->dead &&
|
||||
d->device_connected > 0 &&
|
||||
(d->audio_sink_connected > 0 || d->headset_connected > 0)) {
|
||||
d->device_connected > 0 && d->audio_state >= PA_BT_AUDIO_STATE_CONNECTED) {
|
||||
|
||||
if (!mi) {
|
||||
pa_module *m = NULL;
|
||||
|
|
@ -93,7 +92,16 @@ static pa_hook_result_t load_module_for_device(pa_bluetooth_discovery *y, const
|
|||
|
||||
/* Oh, awesome, a new device has shown up and been connected! */
|
||||
|
||||
args = pa_sprintf_malloc("address=\"%s\" path=\"%s\" profile=\"%s\"", d->address, d->path, d->headset_connected ? "hsp" : "a2dp");
|
||||
args = pa_sprintf_malloc("address=\"%s\" path=\"%s\"", d->address, d->path);
|
||||
#if 0
|
||||
/* This is in case we have to use hsp immediately, without waiting for .Audio.State = Connected */
|
||||
if (d->headset_state >= PA_BT_AUDIO_STATE_CONNECTED && somecondition) {
|
||||
char *tmp;
|
||||
tmp = pa_sprintf_malloc("%s profile=\"hsp\"", args);
|
||||
pa_xfree(args);
|
||||
args = tmp;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef NOKIA
|
||||
if (pa_modargs_get_value(u->modargs, "sco_sink", NULL) &&
|
||||
|
|
|
|||
|
|
@ -978,10 +978,8 @@ ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len)
|
|||
return sbc_decode(sbc, input, input_len, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
ssize_t sbc_decode(sbc_t *sbc,
|
||||
const void *input, size_t input_len,
|
||||
void *output, size_t output_len,
|
||||
size_t *written)
|
||||
ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
|
||||
void *output, size_t output_len, size_t *written)
|
||||
{
|
||||
struct sbc_priv *priv;
|
||||
char *ptr;
|
||||
|
|
@ -1006,7 +1004,7 @@ ssize_t sbc_decode(sbc_t *sbc,
|
|||
sbc->bitpool = priv->frame.bitpool;
|
||||
|
||||
priv->frame.codesize = sbc_get_codesize(sbc);
|
||||
priv->frame.length = sbc_get_frame_length(sbc);
|
||||
priv->frame.length = framelen;
|
||||
}
|
||||
|
||||
if (!output)
|
||||
|
|
@ -1046,10 +1044,8 @@ ssize_t sbc_decode(sbc_t *sbc,
|
|||
return framelen;
|
||||
}
|
||||
|
||||
ssize_t sbc_encode(sbc_t *sbc,
|
||||
const void *input, size_t input_len,
|
||||
void *output, size_t output_len,
|
||||
size_t *written)
|
||||
ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
|
||||
void *output, size_t output_len, size_t *written)
|
||||
{
|
||||
struct sbc_priv *priv;
|
||||
int framelen, samples;
|
||||
|
|
@ -1140,30 +1136,25 @@ void sbc_finish(sbc_t *sbc)
|
|||
size_t sbc_get_frame_length(sbc_t *sbc)
|
||||
{
|
||||
size_t ret;
|
||||
uint8_t subbands, channels, blocks, joint;
|
||||
uint8_t subbands, channels, blocks, joint, bitpool;
|
||||
struct sbc_priv *priv;
|
||||
|
||||
priv = sbc->priv;
|
||||
if (!priv->init) {
|
||||
subbands = sbc->subbands ? 8 : 4;
|
||||
blocks = 4 + (sbc->blocks * 4);
|
||||
channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
|
||||
joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0;
|
||||
} else {
|
||||
subbands = priv->frame.subbands;
|
||||
blocks = priv->frame.blocks;
|
||||
channels = priv->frame.channels;
|
||||
joint = priv->frame.joint;
|
||||
}
|
||||
if (priv->init)
|
||||
return priv->frame.length;
|
||||
|
||||
subbands = sbc->subbands ? 8 : 4;
|
||||
blocks = 4 + (sbc->blocks * 4);
|
||||
channels = sbc->mode == SBC_MODE_MONO ? 1 : 2;
|
||||
joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0;
|
||||
bitpool = sbc->bitpool;
|
||||
|
||||
ret = 4 + (4 * subbands * channels) / 8;
|
||||
|
||||
/* This term is not always evenly divide so we round it up */
|
||||
if (channels == 1)
|
||||
ret += ((blocks * channels * sbc->bitpool) + 7) / 8;
|
||||
ret += ((blocks * channels * bitpool) + 7) / 8;
|
||||
else
|
||||
ret += (((joint ? subbands : 0) + blocks * sbc->bitpool) + 7)
|
||||
/ 8;
|
||||
ret += (((joint ? subbands : 0) + blocks * bitpool) + 7) / 8;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,16 +85,12 @@ int sbc_reinit(sbc_t *sbc, unsigned long flags);
|
|||
|
||||
ssize_t sbc_parse(sbc_t *sbc, const void *input, size_t input_len);
|
||||
|
||||
ssize_t sbc_decode(sbc_t *sbc,
|
||||
const void *input, size_t input_len,
|
||||
void *output, size_t output_len,
|
||||
size_t *written);
|
||||
ssize_t sbc_decode(sbc_t *sbc, const void *input, size_t input_len,
|
||||
void *output, size_t output_len, size_t *written);
|
||||
|
||||
/* Encodes ONE input block into ONE output block */
|
||||
ssize_t sbc_encode(sbc_t *sbc,
|
||||
const void *input, size_t input_len,
|
||||
void *output, size_t output_len,
|
||||
size_t *written);
|
||||
ssize_t sbc_encode(sbc_t *sbc, const void *input, size_t input_len,
|
||||
void *output, size_t output_len, size_t *written);
|
||||
|
||||
/* Returns the output block size in bytes */
|
||||
size_t sbc_get_frame_length(sbc_t *sbc);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue