diff options
| author | Luiz Augusto von Dentz <luiz.dentz-von@nokia.com> | 2010-10-07 17:22:41 +0300 | 
|---|---|---|
| committer | Luiz Augusto von Dentz <luiz.dentz-von@nokia.com> | 2010-10-07 17:22:41 +0300 | 
| commit | 8f3ef04b4310bfbbe0aa8042585340e1832cacf6 (patch) | |
| tree | eca67c7b2b395b6a3d063db89efe1c8c818f07b2 /src/modules | |
| parent | 3de129f3ac8dd6cf51178b266837db4d5e4a1215 (diff) | |
bluetooth: Add support for Media API
Make use of D-Bus to transfer file descriptors.
Diffstat (limited to 'src/modules')
| -rw-r--r-- | src/modules/bluetooth/bluetooth-util.c | 666 | ||||
| -rw-r--r-- | src/modules/bluetooth/bluetooth-util.h | 26 | ||||
| -rw-r--r-- | src/modules/bluetooth/ipc.h | 28 | ||||
| -rw-r--r-- | src/modules/bluetooth/module-bluetooth-device.c | 461 | 
4 files changed, 1109 insertions, 72 deletions
diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index c3b08ea3..bce27663 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -28,6 +28,38 @@  #include <pulsecore/dbus-shared.h>  #include "bluetooth-util.h" +#include "ipc.h" + +#define HFP_AG_ENDPOINT "/MediaEndpoint/HFPAG" +#define A2DP_SOURCE_ENDPOINT "/MediaEndpoint/A2DPSource" +#define A2DP_SINK_ENDPOINT "/MediaEndpoint/A2DPSink" + +#define ENDPOINT_INTROSPECT_XML                                         \ +    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \ +    "<node>"                                                            \ +    " <interface name=\"org.bluez.MediaEndpoint\">"                     \ +    "  <method name=\"SetConfiguration\">"                              \ +    "   <arg name=\"transport\" direction=\"in\" type=\"o\"/>"          \ +    "   <arg name=\"configuration\" direction=\"in\" type=\"ay\"/>"     \ +    "  </method>"                                                       \ +    "  <method name=\"SelectConfiguration\">"                           \ +    "   <arg name=\"capabilities\" direction=\"in\" type=\"ay\"/>"      \ +    "   <arg name=\"configuration\" direction=\"out\" type=\"ay\"/>"    \ +    "  </method>"                                                       \ +    "  <method name=\"ClearConfiguration\">"                            \ +    "  </method>"                                                       \ +    "  <method name=\"Release\">"                                       \ +    "  </method>"                                                       \ +    " </interface>"                                                     \ +    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \ +    "  <method name=\"Introspect\">"                                    \ +    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \ +    "  </method>"                                                       \ +    " </interface>"                                                     \ +    "</node>" + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2U  struct pa_bluetooth_discovery {      PA_REFCNT_DECLARE; @@ -86,6 +118,7 @@ static pa_bluetooth_device* device_new(const char *path) {      d->name = NULL;      d->path = pa_xstrdup(path); +    d->transports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);      d->paired = -1;      d->alias = NULL;      d->device_connected = -1; @@ -103,11 +136,25 @@ static pa_bluetooth_device* device_new(const char *path) {      return d;  } +static void transport_free(pa_bluetooth_transport *t) { +    pa_assert(t); + +    pa_xfree(t->path); +    pa_xfree(t->config); +    pa_xfree(t); +} +  static void device_free(pa_bluetooth_device *d) {      pa_bluetooth_uuid *u; +    pa_bluetooth_transport *t;      pa_assert(d); +    while ((t = pa_hashmap_steal_first(d->transports))) +        transport_free(t); + +    pa_hashmap_free(d->transports, NULL, NULL); +      while ((u = d->uuids)) {          PA_LLIST_REMOVE(pa_bluetooth_uuid, d->uuids, u);          uuid_free(u); @@ -331,6 +378,29 @@ static void remove_all_devices(pa_bluetooth_discovery *y) {      }  } +static pa_bluetooth_device *found_device(pa_bluetooth_discovery *y, const char* path) { +    DBusMessage *m; +    pa_bluetooth_device *d; + +    pa_assert(y); +    pa_assert(path); + +    d = pa_hashmap_get(y->devices, path); +    if (d) +        return d; + +    d = device_new(path); + +    pa_hashmap_put(y->devices, d->path, d); + +    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); + +    /* Before we read the other properties (Audio, AudioSink, AudioSource, +     * Headset) we wait that the UUID is read */ +    return d; +} +  static void get_properties_reply(DBusPendingCall *pending, void *userdata) {      DBusMessage *r;      DBusMessageIter arg_i, element_i; @@ -415,6 +485,7 @@ static void get_properties_reply(DBusPendingCall *pending, void *userdata) {              }  else if (dbus_message_has_interface(p->message, "org.bluez.HandsfreeGateway")) {                  if (parse_audio_property(y, &d->hfgw_state, &arg_i) < 0)                      goto finish; +              }          } @@ -448,25 +519,36 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, pa_bl      return p;  } -static void found_device(pa_bluetooth_discovery *y, const char* path) { -    DBusMessage *m; -    pa_bluetooth_device *d; +static void register_endpoint_reply(DBusPendingCall *pending, void *userdata) { +    DBusError e; +    DBusMessage *r; +    pa_dbus_pending *p; +    pa_bluetooth_discovery *y; -    pa_assert(y); -    pa_assert(path); +    pa_assert(pending); -    if (pa_hashmap_get(y->devices, path)) -        return; +    dbus_error_init(&e); -    d = device_new(path); +    pa_assert_se(p = userdata); +    pa_assert_se(y = p->context_data); +    pa_assert_se(r = dbus_pending_call_steal_reply(pending)); -    pa_hashmap_put(y->devices, d->path, d); +    if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { +        pa_log_debug("Bluetooth daemon is apparently not available."); +        remove_all_devices(y); +        goto finish; +    } -    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); +    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { +        pa_log("Error from RegisterEndpoint reply: %s", dbus_message_get_error_name(r)); +        goto finish; +    } -    /* Before we read the other properties (Audio, AudioSink, AudioSource, -     * Headset) we wait that the UUID is read */ +finish: +    dbus_message_unref(r); + +    PA_LLIST_REMOVE(pa_dbus_pending, y->pending, p); +    pa_dbus_pending_free(p);  }  static void list_devices_reply(DBusPendingCall *pending, void *userdata) { @@ -516,11 +598,126 @@ finish:      pa_dbus_pending_free(p);  } +static void append_variant(DBusMessageIter *iter, int type, const void *val) +{ +    DBusMessageIter value; +    char sig[2] = { type, '\0' }; + +    dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); + +    dbus_message_iter_append_basic(&value, type, val); + +    dbus_message_iter_close_container(iter, &value); +} + +static void append_array_variant(DBusMessageIter *iter, int type, const void *val, int n_elements) +{ +    DBusMessageIter variant, array; +    char type_sig[2] = { type, '\0' }; +    char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' }; + +    dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, array_sig, &variant); + +    dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, type_sig, &array); + +    dbus_message_iter_append_fixed_array(&array, type, val, n_elements); + +    dbus_message_iter_close_container(&variant, &array); + +    dbus_message_iter_close_container(iter, &variant); +} + +static void dict_append_entry(DBusMessageIter *dict, const char *key, int type, void *val) +{ +    DBusMessageIter entry; + +    if (type == DBUS_TYPE_STRING) { +        const char *str = *((const char **) val); +        if (str == NULL) +            return; +    } + +    dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + +    dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + +    append_variant(&entry, type, val); + +    dbus_message_iter_close_container(dict, &entry); +} + +static void dict_append_array(DBusMessageIter *dict, const char *key, int type, void *val, int n_elements) +{ +    DBusMessageIter entry; + +    dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + +    dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + +    append_array_variant(&entry, type, val, n_elements); + +    dbus_message_iter_close_container(dict, &entry); +} + +static void register_endpoint(pa_bluetooth_discovery *y, const char *path, const char *endpoint, const char *uuid) +{ +    DBusMessage *m; +    DBusMessageIter i, d; +    uint8_t codec = 0; + +    pa_log_debug("Registering %s on adapter %s.", endpoint, path); + +    pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Media", "RegisterEndpoint")); + +    dbus_message_iter_init_append(m, &i); + +    dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &endpoint); + +    dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING +                                    DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, +                                    &d); + +    dict_append_entry(&d, "UUID", DBUS_TYPE_STRING, &uuid); + +    dict_append_entry(&d, "Codec", DBUS_TYPE_BYTE, &codec); + +    if (pa_streq(uuid, HFP_AG_UUID)) { +        uint8_t *caps = NULL; +        dict_append_array(&d, "Capabilities", DBUS_TYPE_BYTE, &caps, 0); +    } else { +        sbc_capabilities_raw_t capabilities; +        uint8_t *caps = (uint8_t *) &capabilities; + +        capabilities.channel_mode = BT_A2DP_CHANNEL_MODE_MONO | BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL | +                                    BT_A2DP_CHANNEL_MODE_STEREO | BT_A2DP_CHANNEL_MODE_JOINT_STEREO; +        capabilities.frequency = BT_SBC_SAMPLING_FREQ_16000 | BT_SBC_SAMPLING_FREQ_32000 | +                                 BT_SBC_SAMPLING_FREQ_44100 | BT_SBC_SAMPLING_FREQ_48000; +        capabilities.allocation_method = BT_A2DP_ALLOCATION_SNR | BT_A2DP_ALLOCATION_LOUDNESS; +        capabilities.subbands = BT_A2DP_SUBBANDS_4 | BT_A2DP_SUBBANDS_8; +        capabilities.block_length = BT_A2DP_BLOCK_LENGTH_4 | BT_A2DP_BLOCK_LENGTH_8 | +                                    BT_A2DP_BLOCK_LENGTH_12 | BT_A2DP_BLOCK_LENGTH_16; +        capabilities.min_bitpool = MIN_BITPOOL; +        capabilities.max_bitpool = MAX_BITPOOL; + +        dict_append_array(&d, "Capabilities", DBUS_TYPE_BYTE, &caps, sizeof(capabilities)); +    } + +    dbus_message_iter_close_container(&i, &d); + +    send_and_add_to_pending(y, NULL, m, register_endpoint_reply); +} +  static void found_adapter(pa_bluetooth_discovery *y, const char *path) {      DBusMessage *m;      pa_assert_se(m = dbus_message_new_method_call("org.bluez", path, "org.bluez.Adapter", "ListDevices"));      send_and_add_to_pending(y, NULL, m, list_devices_reply); + +#ifdef DBUS_TYPE_UNIX_FD +    register_endpoint(y, path, HFP_AG_ENDPOINT, HFP_AG_UUID); +    register_endpoint(y, path, A2DP_SOURCE_ENDPOINT, A2DP_SOURCE_UUID); +    register_endpoint(y, path, A2DP_SINK_ENDPOINT, A2DP_SINK_UUID); +#endif  }  static void list_adapters_reply(DBusPendingCall *pending, void *userdata) { @@ -769,6 +966,87 @@ const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_disco      return NULL;  } +const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path) { +    pa_bluetooth_device *d; +    pa_bluetooth_transport *t; +    void *state = NULL; + +    pa_assert(y); +    pa_assert(PA_REFCNT_VALUE(y) > 0); +    pa_assert(path); + +    while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) +        if ((t = pa_hashmap_get(d->transports, path))) +            return t; + +    return NULL; +} + +const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile) { +    pa_bluetooth_transport *t; +    void *state = NULL; + +    pa_assert(d); + +    while ((t = pa_hashmap_iterate(d->transports, &state, NULL))) +        if (t->profile == profile) +            return t; + +    return NULL; +} + +int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype) { +    DBusMessage *m, *r; +    DBusError err; +    int ret; + +    pa_assert(t); +    pa_assert(t->y); + +    dbus_error_init(&err); + +    pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Acquire")); +    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); +    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err); + +    if (dbus_error_is_set(&err) || !r) { +        pa_log("Failed to acquire transport fd: %s", err.message); +        dbus_error_free(&err); +        return -1; +    } + +    if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_INVALID)) { +        pa_log("Failed to parse org.bluez.MediaTransport.Acquire(): %s", err.message); +        ret = -1; +        dbus_error_free(&err); +        goto fail; +    } + +fail: +    dbus_message_unref(r); +    return ret; +} + +void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype) { +    DBusMessage *m; +    DBusError err; + +    pa_assert(t); +    pa_assert(t->y); + +    dbus_error_init(&err); + +    pa_assert_se(m = dbus_message_new_method_call("org.bluez", t->path, "org.bluez.MediaTransport", "Release")); +    pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &accesstype, DBUS_TYPE_INVALID)); +    dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(t->y->connection), m, -1, &err); + +    if (dbus_error_is_set(&err)) { +        pa_log("Failed to release transport %s: %s", t->path, err.message); +        dbus_error_free(&err); +    } else +        pa_log_info("Transport %s released", t->path); +} +  static int setup_dbus(pa_bluetooth_discovery *y) {      DBusError err; @@ -785,9 +1063,358 @@ static int setup_dbus(pa_bluetooth_discovery *y) {      return 0;  } +static pa_bluetooth_transport *transport_new(pa_bluetooth_discovery *y, const char *path, enum profile p, const uint8_t *config, int size) { +    pa_bluetooth_transport *t; + +    t = pa_xnew0(pa_bluetooth_transport, 1); +    t->y = y; +    t->path = pa_xstrdup(path); +    t->profile = p; +    t->config_size = size; + +    if (size > 0) { +        t->config = pa_xnew(uint8_t, size); +        memcpy(t->config, config, size); +    } + +    return t; +} + +static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { +    pa_bluetooth_discovery *y = userdata; +    pa_bluetooth_device *d; +    pa_bluetooth_transport *t; +    const char *path, *dev_path = NULL, *uuid = NULL; +    uint8_t *config = NULL; +    int size = 0; +    enum profile p; +    DBusMessageIter args, props; +    DBusMessage *r; + +    dbus_message_iter_init(m, &args); + +    dbus_message_iter_get_basic(&args, &path); +    if (!dbus_message_iter_next(&args)) +        goto fail; + +    dbus_message_iter_recurse(&args, &props); +    if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) +        goto fail; + +    /* Read transport properties */ +    while (dbus_message_iter_get_arg_type(&props) == DBUS_TYPE_DICT_ENTRY) { +        const char *key; +        DBusMessageIter value, entry; +        int var; + +        dbus_message_iter_recurse(&props, &entry); +        dbus_message_iter_get_basic(&entry, &key); + +        dbus_message_iter_next(&entry); +        dbus_message_iter_recurse(&entry, &value); + +        var = dbus_message_iter_get_arg_type(&value); +        if (strcasecmp(key, "UUID") == 0) { +            if (var != DBUS_TYPE_STRING) +                goto fail; +            dbus_message_iter_get_basic(&value, &uuid); +        } else if (strcasecmp(key, "Device") == 0) { +            if (var != DBUS_TYPE_OBJECT_PATH) +                goto fail; +            dbus_message_iter_get_basic(&value, &dev_path); +        } else if (strcasecmp(key, "Configuration") == 0) { +            DBusMessageIter array; +            if (var != DBUS_TYPE_ARRAY) +                goto fail; +            dbus_message_iter_recurse(&value, &array); +            dbus_message_iter_get_fixed_array(&array, &config, &size); +        } + +        dbus_message_iter_next(&props); +    } + +    d = found_device(y, dev_path); +    if (!d) +        goto fail; + +    if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) +        p = PROFILE_HSP; +    else if (dbus_message_has_path(m, A2DP_SOURCE_ENDPOINT)) +        p = PROFILE_A2DP; +    else +        p = PROFILE_A2DP_SOURCE; + +    t = transport_new(y, path, p, config, size); +    pa_hashmap_put(d->transports, t->path, t); + +    pa_log_debug("Transport %s profile %d available", t->path, t->profile); + +    pa_assert_se(r = dbus_message_new_method_return(m)); + +    return r; + +fail: +    pa_log("org.bluez.MediaEndpoint.SetConfiguration: invalid arguments"); +    pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", +                                                        "Unable to set configuration"))); +    return r; +} + +static DBusMessage *endpoint_clear_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { +    pa_bluetooth_discovery *y = userdata; +    pa_bluetooth_device *d; +    pa_bluetooth_transport *t; +    void *state = NULL; +    DBusMessage *r; +    DBusError e; +    const char *path; + +    dbus_error_init(&e); + +    if (!dbus_message_get_args(m, &e, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) { +        pa_log("org.bluez.MediaEndpoint.ClearConfiguration: %s", e.message); +        dbus_error_free(&e); +        goto fail; +    } + +    while ((d = pa_hashmap_iterate(y->devices, &state, NULL))) { +        if ((t = pa_hashmap_get(d->transports, path))) { +            pa_log_debug("Clearing transport %s profile %d", t->path, t->profile); +            pa_hashmap_remove(d->transports, t->path); +            transport_free(t); +            break; +        } +    } + +    pa_assert_se(r = dbus_message_new_method_return(m)); + +    return r; + +fail: +    pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", +                                                        "Unable to clear configuration"))); +    return r; +} + +static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { + +    switch (freq) { +        case BT_SBC_SAMPLING_FREQ_16000: +        case BT_SBC_SAMPLING_FREQ_32000: +            return 53; + +        case BT_SBC_SAMPLING_FREQ_44100: + +            switch (mode) { +                case BT_A2DP_CHANNEL_MODE_MONO: +                case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: +                    return 31; + +                case BT_A2DP_CHANNEL_MODE_STEREO: +                case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: +                    return 53; + +                default: +                    pa_log_warn("Invalid channel mode %u", mode); +                    return 53; +            } + +        case BT_SBC_SAMPLING_FREQ_48000: + +            switch (mode) { +                case BT_A2DP_CHANNEL_MODE_MONO: +                case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: +                    return 29; + +                case BT_A2DP_CHANNEL_MODE_STEREO: +                case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: +                    return 51; + +                default: +                    pa_log_warn("Invalid channel mode %u", mode); +                    return 51; +            } + +        default: +            pa_log_warn("Invalid sampling freq %u", freq); +            return 53; +    } +} + +static DBusMessage *endpoint_select_configuration(DBusConnection *c, DBusMessage *m, void *userdata) { +    pa_bluetooth_discovery *y = userdata; +    sbc_capabilities_raw_t *cap, config; +    uint8_t *pconf = (uint8_t *) &config; +    int i, size; +    DBusMessage *r; +    DBusError e; + +    static const struct { +        uint32_t rate; +        uint8_t cap; +    } freq_table[] = { +        { 16000U, BT_SBC_SAMPLING_FREQ_16000 }, +        { 32000U, BT_SBC_SAMPLING_FREQ_32000 }, +        { 44100U, BT_SBC_SAMPLING_FREQ_44100 }, +        { 48000U, BT_SBC_SAMPLING_FREQ_48000 } +    }; + +    dbus_error_init(&e); + +    if (!dbus_message_get_args(m, &e, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { +        pa_log("org.bluez.MediaEndpoint.SelectConfiguration: %s", e.message); +        dbus_error_free(&e); +        goto fail; +    } + +    if (dbus_message_has_path(m, HFP_AG_ENDPOINT)) +        goto done; + +    pa_assert(size == sizeof(config)); + +    memset(&config, 0, sizeof(config)); + +    /* Find the lowest freq that is at least as high as the requested +     * sampling rate */ +    for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) +        if (freq_table[i].rate >= y->core->default_sample_spec.rate && (cap->frequency & freq_table[i].cap)) { +            config.frequency = freq_table[i].cap; +            break; +        } + +    if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { +        for (--i; i >= 0; i--) { +            if (cap->frequency & freq_table[i].cap) { +                config.frequency = freq_table[i].cap; +                break; +            } +        } + +        if (i < 0) { +            pa_log("Not suitable sample rate"); +            goto fail; +        } +    } + +    pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); + +    if (y->core->default_sample_spec.channels <= 1) { +        if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) +            config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO; +    } + +    if (y->core->default_sample_spec.channels >= 2) { +        if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) +            config.channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; +        else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) +            config.channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; +        else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) +            config.channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; +        else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) { +            config.channel_mode = BT_A2DP_CHANNEL_MODE_MONO; +        } else { +            pa_log("No supported channel modes"); +            goto fail; +        } +    } + +    if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) +        config.block_length = BT_A2DP_BLOCK_LENGTH_16; +    else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) +        config.block_length = BT_A2DP_BLOCK_LENGTH_12; +    else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) +        config.block_length = BT_A2DP_BLOCK_LENGTH_8; +    else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) +        config.block_length = BT_A2DP_BLOCK_LENGTH_4; +    else { +        pa_log_error("No supported block lengths"); +        goto fail; +    } + +    if (cap->subbands & BT_A2DP_SUBBANDS_8) +        config.subbands = BT_A2DP_SUBBANDS_8; +    else if (cap->subbands & BT_A2DP_SUBBANDS_4) +        config.subbands = BT_A2DP_SUBBANDS_4; +    else { +        pa_log_error("No supported subbands"); +        goto fail; +    } + +    if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) +        config.allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; +    else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) +        config.allocation_method = BT_A2DP_ALLOCATION_SNR; + +    config.min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); +    config.max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(config.frequency, config.channel_mode), cap->max_bitpool); + +done: +    pa_assert_se(r = dbus_message_new_method_return(m)); + +    pa_assert_se(dbus_message_append_args( +                                     r, +                                     DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, +                                     DBUS_TYPE_INVALID)); + +    return r; + +fail: +    pa_assert_se(r = (dbus_message_new_error(m, "org.bluez.MediaEndpoint.Error.InvalidArguments", +                                                        "Unable to select configuration"))); +    return r; +} + +static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { +    struct pa_bluetooth_discovery *y = userdata; +    DBusMessage *r = NULL; +    DBusError e; +    const char *path; + +    pa_assert(y); + +    pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", +            dbus_message_get_interface(m), +            dbus_message_get_path(m), +            dbus_message_get_member(m)); + +    path = dbus_message_get_path(m); +    dbus_error_init(&e); + +    if (!pa_streq(path, A2DP_SOURCE_ENDPOINT) && !pa_streq(path, A2DP_SINK_ENDPOINT) && !pa_streq(path, HFP_AG_ENDPOINT)) +        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { +        const char *xml = ENDPOINT_INTROSPECT_XML; + +        pa_assert_se(r = dbus_message_new_method_return(m)); +        pa_assert_se(dbus_message_append_args( +                                 r, +                                 DBUS_TYPE_STRING, &xml, +                                 DBUS_TYPE_INVALID)); + +    } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SetConfiguration")) { +        r = endpoint_set_configuration(c, m, userdata); +    } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "SelectConfiguration")) { +        r = endpoint_select_configuration(c, m, userdata); +    } else if (dbus_message_is_method_call(m, "org.bluez.MediaEndpoint", "ClearConfiguration")) +        r = endpoint_clear_configuration(c, m, userdata); +    else +        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + +    if (r) { +        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(y->connection), r, NULL)); +        dbus_message_unref(r); +    } + +    return DBUS_HANDLER_RESULT_HANDLED; +} +  pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {      DBusError err;      pa_bluetooth_discovery *y; +    static const DBusObjectPathVTable vtable_endpoint = { +        .message_function = endpoint_handler, +    };      pa_assert(c); @@ -832,6 +1459,12 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {          goto fail;      } +#ifdef DBUS_TYPE_UNIX_FD +    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT, &vtable_endpoint, y)); +    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT, &vtable_endpoint, y)); +    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT, &vtable_endpoint, y)); +#endif +      list_adapters(y);      return y; @@ -870,6 +1503,11 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {      }      if (y->connection) { +#ifdef DBUS_TYPE_UNIX_FD +        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), HFP_AG_ENDPOINT); +        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SOURCE_ENDPOINT); +        dbus_connection_unregister_object_path(pa_dbus_connection_get(y->connection), A2DP_SINK_ENDPOINT); +#endif          pa_dbus_remove_matches(pa_dbus_connection_get(y->connection),                                 "type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0='org.bluez'",                                 "type='signal',sender='org.bluez',interface='org.bluez.Manager',member='AdapterAdded'", @@ -888,6 +1526,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {          if (y->filter_added)              dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); +        dbus_connection_remove_filter(pa_dbus_connection_get(y->connection), filter_cb, y); +          pa_dbus_connection_unref(y->connection);      } diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h index 9cee3de3..ce551967 100644 --- a/src/modules/bluetooth/bluetooth-util.h +++ b/src/modules/bluetooth/bluetooth-util.h @@ -25,6 +25,7 @@  #include <dbus/dbus.h>  #include <pulsecore/llist.h> +#include <pulsecore/strlist.h>  #include <pulsecore/macro.h>  #include <pulsecore/core-util.h> @@ -45,6 +46,7 @@  typedef struct pa_bluetooth_uuid pa_bluetooth_uuid;  typedef struct pa_bluetooth_device pa_bluetooth_device;  typedef struct pa_bluetooth_discovery pa_bluetooth_discovery; +typedef struct pa_bluetooth_transport pa_bluetooth_transport;  struct userdata; @@ -53,6 +55,23 @@ struct pa_bluetooth_uuid {      PA_LLIST_FIELDS(pa_bluetooth_uuid);  }; +enum profile { +    PROFILE_A2DP, +    PROFILE_A2DP_SOURCE, +    PROFILE_HSP, +    PROFILE_HFGW, +    PROFILE_OFF +}; + +struct pa_bluetooth_transport { +    pa_bluetooth_discovery *y; +    char *path; +    enum profile profile; +    uint8_t codec; +    uint8_t *config; +    int config_size; +}; +  /* This enum is shared among Audio, Headset, AudioSink, and AudioSource, although not all values are acceptable in all profiles */  typedef enum pa_bt_audio_state {      PA_BT_AUDIO_STATE_INVALID = -1, @@ -70,6 +89,7 @@ struct pa_bluetooth_device {      /* Device information */      char *name;      char *path; +    pa_hashmap *transports;      int paired;      char *alias;      int device_connected; @@ -103,6 +123,12 @@ void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *d);  const pa_bluetooth_device* pa_bluetooth_discovery_get_by_path(pa_bluetooth_discovery *d, const char* path);  const pa_bluetooth_device* pa_bluetooth_discovery_get_by_address(pa_bluetooth_discovery *d, const char* address); +const pa_bluetooth_transport* pa_bluetooth_discovery_get_transport(pa_bluetooth_discovery *y, const char *path); +const pa_bluetooth_transport* pa_bluetooth_device_get_transport(const pa_bluetooth_device *d, enum profile profile); + +int pa_bluetooth_transport_acquire(const pa_bluetooth_transport *t, const char *accesstype); +void pa_bluetooth_transport_release(const pa_bluetooth_transport *t, const char *accesstype); +  pa_hook* pa_bluetooth_discovery_hook(pa_bluetooth_discovery *d);  const char* pa_bluetooth_get_form_factor(uint32_t class); diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h index 2e170f50..9537886b 100644 --- a/src/modules/bluetooth/ipc.h +++ b/src/modules/bluetooth/ipc.h @@ -201,6 +201,34 @@ typedef struct {  	uint8_t max_bitpool;  } __attribute__ ((packed)) sbc_capabilities_t; +#if __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { +	uint8_t channel_mode:4; +	uint8_t frequency:4; +	uint8_t allocation_method:2; +	uint8_t subbands:2; +	uint8_t block_length:4; +	uint8_t min_bitpool; +	uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_raw_t; + +#elif __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { +	uint8_t frequency:4; +	uint8_t channel_mode:4; +	uint8_t block_length:4; +	uint8_t subbands:2; +	uint8_t allocation_method:2; +	uint8_t min_bitpool; +	uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_raw_t; + +#else +#error "Unknown byte order" +#endif +  typedef struct {  	codec_capabilities_t capability;  	uint8_t channel_mode; diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 61fe3697..c7ac7fa6 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -48,6 +48,7 @@  #include <pulsecore/time-smoother.h>  #include <pulsecore/namereg.h>  #include <pulsecore/dbus-shared.h> +#include <pulsecore/llist.h>  #include "module-bluetooth-device-symdef.h"  #include "ipc.h" @@ -129,20 +130,15 @@ struct hsp_info {      pa_hook_slot *source_state_changed_slot;  }; -enum profile { -    PROFILE_A2DP, -    PROFILE_A2DP_SOURCE, -    PROFILE_HSP, -    PROFILE_HFGW, -    PROFILE_OFF -}; -  struct userdata {      pa_core *core;      pa_module *module;      char *address;      char *path; +    char *transport; +    char *accesstype; +      pa_bluetooth_discovery *discovery;      pa_bool_t auto_connect; @@ -202,10 +198,14 @@ static int service_send(struct userdata *u, const bt_audio_msg_header_t *msg) {      ssize_t r;      pa_assert(u); -    pa_assert(u->service_fd >= 0);      pa_assert(msg);      pa_assert(msg->length > 0); +    if (u->service_fd < 0) { +        pa_log_warn("Service not connected"); +        return -1; +    } +      pa_log_debug("Sending %s -> %s",                   pa_strnull(bt_audio_strtype(msg->type)),                   pa_strnull(bt_audio_strname(msg->name))); @@ -745,6 +745,41 @@ static int set_conf(struct userdata *u) {  }  /* from IO thread, except in SCO over PCM */ + +static int setup_stream(struct userdata *u) { +    struct pollfd *pollfd; +    int one; + +    pa_make_fd_nonblock(u->stream_fd); +    pa_make_socket_low_delay(u->stream_fd); + +    one = 1; +    if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) +        pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno)); + +    pa_log_debug("Stream properly set up, we're ready to roll!"); + +    u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); +    pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); +    pollfd->fd = u->stream_fd; +    pollfd->events = pollfd->revents = 0; + +    u->read_index = u->write_index = 0; +    u->started_at = 0; + +    if (u->source) +        u->read_smoother = pa_smoother_new( +                PA_USEC_PER_SEC, +                PA_USEC_PER_SEC*2, +                TRUE, +                TRUE, +                10, +                pa_rtclock_now(), +                TRUE); + +    return 0; +} +  static int start_stream_fd(struct userdata *u) {      union {          bt_audio_msg_header_t rsp; @@ -754,8 +789,6 @@ static int start_stream_fd(struct userdata *u) {          bt_audio_error_t error;          uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];      } msg; -    struct pollfd *pollfd; -    int one;      pa_assert(u);      pa_assert(u->rtpoll); @@ -781,34 +814,7 @@ static int start_stream_fd(struct userdata *u) {          return -1;      } -    pa_make_fd_nonblock(u->stream_fd); -    pa_make_socket_low_delay(u->stream_fd); - -    one = 1; -    if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) -        pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno)); - -    pa_log_debug("Stream properly set up, we're ready to roll!"); - -    u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); -    pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); -    pollfd->fd = u->stream_fd; -    pollfd->events = pollfd->revents = 0; - -    u->read_index = u->write_index = 0; -    u->started_at = 0; - -    if (u->source) -        u->read_smoother = pa_smoother_new( -                PA_USEC_PER_SEC, -                PA_USEC_PER_SEC*2, -                TRUE, -                TRUE, -                10, -                pa_rtclock_now(), -                TRUE); - -    return 0; +    return setup_stream(u);  }  /* from IO thread */ @@ -852,6 +858,76 @@ static int stop_stream_fd(struct userdata *u) {      return r;  } +static void bt_transport_release(struct userdata *u) +{ +    const char *accesstype = "rw"; +    const pa_bluetooth_transport *t; + +    /* Ignore if already released */ +    if (!u->accesstype) +        return; + +    pa_log_debug("Releasing transport %s", u->transport); + +    t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); +    if (t) +        pa_bluetooth_transport_release(t, accesstype); + +    pa_xfree(u->accesstype); +    u->accesstype = NULL; + +    if (u->rtpoll_item) { +        pa_rtpoll_item_free(u->rtpoll_item); +        u->rtpoll_item = NULL; +    } + +    if (u->stream_fd >= 0) { +        pa_close(u->stream_fd); +        u->stream_fd = -1; +    } + +    if (u->read_smoother) { +        pa_smoother_free(u->read_smoother); +        u->read_smoother = NULL; +    } +} + +static int bt_transport_acquire(struct userdata *u, pa_bool_t start) +{ +    const char *accesstype = "rw"; +    const pa_bluetooth_transport *t; + +    if (u->accesstype) { +        if (start) +            goto done; +        return 0; +    } + +    pa_log_debug("Acquiring transport %s", u->transport); + +    t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); +    if (!t) { +        pa_log("Transport %s no longer available", u->transport); +        pa_xfree(u->transport); +        u->transport = NULL; +        return -1; +    } + +    u->stream_fd = pa_bluetooth_transport_acquire(t, accesstype); +    if (u->stream_fd < 0) +        return -1; + +    u->accesstype = pa_xstrdup(accesstype); +    pa_log_info("Transport %s acquired: fd %d", u->transport, u->stream_fd); + +    if (!start) +        return 0; + +done: +    pa_log_info("Transport %s resuming", u->transport); +    return setup_stream(u); +} +  /* Run from IO thread */  static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {      struct userdata *u = PA_SINK(o)->userdata; @@ -870,11 +946,15 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse                      pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));                      /* Stop the device if the source is suspended as well */ -                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) +                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) {                          /* We deliberately ignore whether stopping                           * actually worked. Since the stream_fd is                           * closed it doesn't really matter */ -                        stop_stream_fd(u); +                        if (u->transport) +                            bt_transport_release(u); +                        else +                            stop_stream_fd(u); +                    }                      break; @@ -884,9 +964,13 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse                          break;                      /* Resume the device if the source was suspended as well */ -                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) -                        if (start_stream_fd(u) < 0) +                    if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) { +                        if (u->transport) { +                            if (bt_transport_acquire(u, TRUE) < 0) +                                failed = TRUE; +                        } else if (start_stream_fd(u) < 0)                              failed = TRUE; +                    }                      break;                  case PA_SINK_UNLINKED: @@ -942,8 +1026,12 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off                      pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state));                      /* Stop the device if the sink is suspended as well */ -                    if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) -                        stop_stream_fd(u); +                    if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) { +                        if (u->transport) +                            bt_transport_release(u); +                        else +                            stop_stream_fd(u); +                    }                      if (u->read_smoother)                          pa_smoother_pause(u->read_smoother, pa_rtclock_now()); @@ -955,10 +1043,13 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off                          break;                      /* Resume the device if the sink was suspended as well */ -                    if (!u->sink || u->sink->thread_info.state == PA_SINK_SUSPENDED) -                        if (start_stream_fd(u) < 0) +                    if (!u->sink || u->sink->thread_info.state == PA_SINK_SUSPENDED) { +                        if (u->transport) { +                            if (bt_transport_acquire(u, TRUE) < 0)                              failed = TRUE; - +                        } else if (start_stream_fd(u) < 0) +                            failed = TRUE; +                    }                      /* We don't resume the smoother here. Instead we                       * wait until the first packet arrives */                      break; @@ -1414,7 +1505,10 @@ static void thread_func(void *userdata) {      pa_thread_mq_install(&u->thread_mq); -    if (start_stream_fd(u) < 0) +    if (u->transport) { +        if (bt_transport_acquire(u, TRUE) < 0) +            goto fail; +    } else if (start_stream_fd(u) < 0)          goto fail;      for (;;) { @@ -1709,17 +1803,25 @@ static void sco_over_pcm_state_update(struct userdata *u) {          if (u->service_fd >= 0)              return; +        init_bt(u); +          pa_log_debug("Resuming SCO over PCM"); -        if ((init_bt(u) < 0) || (init_profile(u) < 0)) +        if (init_profile(u) < 0)              pa_log("Can't resume SCO over PCM"); -        start_stream_fd(u); +        if (u->transport) +            bt_transport_acquire(u, TRUE); +        else +            start_stream_fd(u);      } else {          if (u->service_fd < 0)              return; -        stop_stream_fd(u); +        if (u->transport) +            bt_transport_release(u); +        else +            stop_stream_fd(u);          pa_log_debug("Closing SCO over PCM");          pa_close(u->service_fd); @@ -1906,6 +2008,218 @@ static void shutdown_bt(struct userdata *u) {      }  } +static int bt_transport_config_a2dp(struct userdata *u) +{ +    const pa_bluetooth_transport *t; +    struct a2dp_info *a2dp = &u->a2dp; +    sbc_capabilities_raw_t *config; + +    t = pa_bluetooth_discovery_get_transport(u->discovery, u->transport); +    pa_assert(t); + +    config = (sbc_capabilities_raw_t *) t->config; + +    if (a2dp->sbc_initialized) +        sbc_reinit(&a2dp->sbc, 0); +    else +        sbc_init(&a2dp->sbc, 0); +    a2dp->sbc_initialized = TRUE; + +    switch (config->frequency) { +        case BT_SBC_SAMPLING_FREQ_16000: +            a2dp->sbc.frequency = SBC_FREQ_16000; +            break; +        case BT_SBC_SAMPLING_FREQ_32000: +            a2dp->sbc.frequency = SBC_FREQ_32000; +            break; +        case BT_SBC_SAMPLING_FREQ_44100: +            a2dp->sbc.frequency = SBC_FREQ_44100; +            break; +        case BT_SBC_SAMPLING_FREQ_48000: +            a2dp->sbc.frequency = SBC_FREQ_48000; +            break; +        default: +            pa_assert_not_reached(); +    } + +    switch (config->channel_mode) { +        case BT_A2DP_CHANNEL_MODE_MONO: +            a2dp->sbc.mode = SBC_MODE_MONO; +            break; +        case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: +            a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; +            break; +        case BT_A2DP_CHANNEL_MODE_STEREO: +            a2dp->sbc.mode = SBC_MODE_STEREO; +            break; +        case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: +            a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; +            break; +        default: +            pa_assert_not_reached(); +    } + +    switch (config->allocation_method) { +        case BT_A2DP_ALLOCATION_SNR: +            a2dp->sbc.allocation = SBC_AM_SNR; +            break; +        case BT_A2DP_ALLOCATION_LOUDNESS: +            a2dp->sbc.allocation = SBC_AM_LOUDNESS; +            break; +        default: +            pa_assert_not_reached(); +    } + +    switch (config->subbands) { +        case BT_A2DP_SUBBANDS_4: +            a2dp->sbc.subbands = SBC_SB_4; +            break; +        case BT_A2DP_SUBBANDS_8: +            a2dp->sbc.subbands = SBC_SB_8; +            break; +        default: +            pa_assert_not_reached(); +    } + +    switch (config->block_length) { +        case BT_A2DP_BLOCK_LENGTH_4: +            a2dp->sbc.blocks = SBC_BLK_4; +            break; +        case BT_A2DP_BLOCK_LENGTH_8: +            a2dp->sbc.blocks = SBC_BLK_8; +            break; +        case BT_A2DP_BLOCK_LENGTH_12: +            a2dp->sbc.blocks = SBC_BLK_12; +            break; +        case BT_A2DP_BLOCK_LENGTH_16: +            a2dp->sbc.blocks = SBC_BLK_16; +            break; +        default: +            pa_assert_not_reached(); +    } + +    a2dp->sbc.bitpool = config->max_bitpool; +    a2dp->codesize = sbc_get_codesize(&a2dp->sbc); +    a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); + +    u->block_size = +        ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload)) +        / a2dp->frame_length +        * a2dp->codesize); + +    pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", +                a2dp->sbc.allocation, a2dp->sbc.subbands, a2dp->sbc.blocks, a2dp->sbc.bitpool); + +    return 0; +} + +static int bt_transport_config(struct userdata *u) +{ +    if (u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW) { +        u->block_size = u->link_mtu; +        return 0; +    } + +    return bt_transport_config_a2dp(u); +} + +static int parse_transport_property(struct userdata *u, DBusMessageIter *i) +{ +    const char *key; +    DBusMessageIter variant_i; + +    pa_assert(u); +    pa_assert(i); + +    if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { +        pa_log("Property name not a string."); +        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); + +    switch (dbus_message_iter_get_arg_type(&variant_i)) { + +        case DBUS_TYPE_UINT16: { + +            uint16_t value; +            dbus_message_iter_get_basic(&variant_i, &value); + +            if (pa_streq(key, "OMTU")) +                u->link_mtu = value; + +            break; +        } + +    } + +    return 0; +} + +/* Run from main thread */ +static int bt_transport_open(struct userdata *u) +{ +    DBusMessage *m, *r; +    DBusMessageIter arg_i, element_i; +    DBusError err; + +    if (bt_transport_acquire(u, FALSE) < 0) +        return -1; + +    dbus_error_init(&err); + +    pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->transport, "org.bluez.MediaTransport", "GetProperties")); +    r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &err); + +    if (dbus_error_is_set(&err) || !r) { +        pa_log("Failed to get transport properties: %s", err.message); +        goto fail; +    } + +    if (!dbus_message_iter_init(r, &arg_i)) { +        pa_log("GetProperties reply has no arguments."); +        goto fail; +    } + +    if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) { +        pa_log("GetProperties argument is not an array."); +        goto fail; +    } + +    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); + +            parse_transport_property(u, &dict_i); +        } + +        if (!dbus_message_iter_next(&element_i)) +            break; +    } + +    return bt_transport_config(u); + +fail: +    dbus_message_unref(r); +    return -1; +} +  /* Run from main thread */  static int init_bt(struct userdata *u) {      pa_assert(u); @@ -1917,7 +2231,7 @@ static int init_bt(struct userdata *u) {      u->service_read_type = 0;      if ((u->service_fd = bt_audio_service_open()) < 0) { -        pa_log_error("Couldn't connect to bluetooth audio service"); +        pa_log_warn("Bluetooth audio service not available");          return -1;      } @@ -1928,8 +2242,30 @@ static int init_bt(struct userdata *u) {  /* Run from main thread */  static int setup_bt(struct userdata *u) { +    const pa_bluetooth_device *d; +    const pa_bluetooth_transport *t; +      pa_assert(u); +    if (!(d = pa_bluetooth_discovery_get_by_path(u->discovery, u->path))) { +        pa_log_error("Failed to get device object."); +        return -1; +    } + +    /* release transport if exist */ +    if (u->transport) { +        bt_transport_release(u); +        pa_xfree(u->transport); +        u->transport = NULL; +    } + +    /* check if profile has a transport */ +    t = pa_bluetooth_device_get_transport(d, u->profile); +    if (t) { +        u->transport = pa_xstrdup(t->path); +        return bt_transport_open(u); +    } +      if (get_caps(u, 0) < 0)          return -1; @@ -2036,7 +2372,10 @@ static int start_thread(struct userdata *u) {  #ifdef NOKIA      if (USE_SCO_OVER_PCM(u)) { -        if (start_stream_fd(u) < 0) +        if (u->transport) { +            if (bt_transport_acquire(u, TRUE) < 0) +                return -1; +        } else if (start_stream_fd(u) < 0)              return -1;          pa_sink_ref(u->sink); @@ -2316,7 +2655,7 @@ static const pa_bluetooth_device* find_device(struct userdata *u, const char *ad          }          if (address && !(pa_streq(d->address, address))) { -            pa_log_error("Passed path %s and address %s don't match.", path, address); +            pa_log_error("Passed path %s address %s != %s don't match.", path, d->address, address);              return NULL;          } @@ -2429,10 +2768,6 @@ int pa__init(pa_module* m) {      if (add_card(u, device) < 0)          goto fail; -    /* Connect to the BT service and query capabilities */ -    if (init_bt(u) < 0) -        goto fail; -      if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) {          pa_log_error("Failed to add filter function");          goto fail; @@ -2458,6 +2793,9 @@ int pa__init(pa_module* m) {      pa_xfree(speaker);      pa_xfree(mike); +    /* Connect to the BT service */ +    init_bt(u); +      if (u->profile != PROFILE_OFF)          if (init_profile(u) < 0)              goto fail; @@ -2552,6 +2890,11 @@ void pa__done(pa_module *m) {      pa_xfree(u->address);      pa_xfree(u->path); +    if (u->transport) { +        bt_transport_release(u); +        pa_xfree(u->transport); +    } +      if (u->discovery)          pa_bluetooth_discovery_unref(u->discovery);  | 
