[pulseaudio-discuss] [PATCH 4/4] backend-null: add HSP profile support

Luiz Augusto von Dentz luiz.dentz at gmail.com
Tue Sep 2 03:14:12 PDT 2014


Hi Wim,

On Mon, Sep 1, 2014 at 4:41 PM, Wim Taymans <wim.taymans at gmail.com> wrote:
> Add simple support for the HSP profile. This allows pulseaudio to output
> audio to a Headset using the HSP profile.
>
> Make the null backend the default, now that it does something.

Nice this came out quite quickly, btw did you think about changing the
backend name to native instead of null?

> ---
>  configure.ac                         |   4 +-
>  src/modules/bluetooth/backend-null.c | 457 ++++++++++++++++++++++++++++++++++-
>  2 files changed, 455 insertions(+), 6 deletions(-)
>
> diff --git a/configure.ac b/configure.ac
> index 7b56210..6eb956e 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1030,9 +1030,9 @@ AM_CONDITIONAL([HAVE_BLUEZ], [test "x$HAVE_BLUEZ" = x1])
>  ## Bluetooth Headset profiles backend ##
>
>  AC_ARG_WITH(bluetooth_headset_backend,
> -    AS_HELP_STRING([--with-bluetooth-headset-backend=<ofono|null>],[Backend for Bluetooth headset profiles (ofono)]))
> +    AS_HELP_STRING([--with-bluetooth-headset-backend=<ofono|null>],[Backend for Bluetooth headset profiles (null)]))
>  if test -z "$with_bluetooth_headset_backend" ; then
> -    BLUETOOTH_HEADSET_BACKEND=ofono
> +    BLUETOOTH_HEADSET_BACKEND=null
>  else
>      BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend
>  fi
> diff --git a/src/modules/bluetooth/backend-null.c b/src/modules/bluetooth/backend-null.c
> index f8a145b..e3fe215 100644
> --- a/src/modules/bluetooth/backend-null.c
> +++ b/src/modules/bluetooth/backend-null.c
> @@ -23,15 +23,464 @@
>  #include <config.h>
>  #endif
>
> +#include <pulsecore/shared.h>
> +#include <pulsecore/core-error.h>
> +#include <pulsecore/core-util.h>
> +#include <pulsecore/dbus-shared.h>
>  #include <pulsecore/log.h>
>
> +#include <errno.h>
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +
> +#include <bluetooth/bluetooth.h>
> +#include <bluetooth/sco.h>
> +
>  #include "bluez5-util.h"
>
> +struct pa_bluetooth_backend {
> +  pa_core *core;
> +  pa_dbus_connection *connection;
> +  pa_bluetooth_discovery *discovery;
> +
> +  PA_LLIST_HEAD(pa_dbus_pending, pending);
> +};
> +
> +struct transport_rfcomm {
> +    int rfcomm_fd;
> +    pa_io_event *rfcomm_io;
> +    pa_bluetooth_backend *backend;
> +};
> +
> +#define BLUEZ_SERVICE "org.bluez"
> +#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
> +
> +#define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported"
> +
> +#define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1"
> +#define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1"
> +
> +#define HSP_AG_PROFILE "/Profile/HSPAGProfile"
> +
> +#define PROFILE_INTROSPECT_XML                                          \
> +    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                           \
> +    "<node>"                                                            \
> +    " <interface name=\"" BLUEZ_PROFILE_INTERFACE "\">"                 \
> +    "  <method name=\"Release\">"                                       \
> +    "  </method>"                                                       \
> +    "  <method name=\"Cancel\">"                                        \
> +    "  </method>"                                                       \
> +    "  <method name=\"RequestDisconnection\">"                          \
> +    "   <arg name=\"device\" direction=\"in\" type=\"o\"/>"             \
> +    "  </method>"                                                       \
> +    "  <method name=\"NewConnection\">"                                 \
> +    "   <arg name=\"device\" direction=\"in\" type=\"o\"/>"             \
> +    "   <arg name=\"fd\" direction=\"in\" type=\"h\"/>"                 \
> +    "   <arg name=\"opts\" direction=\"in\" type=\"a{sv}\"/>"           \
> +    "  </method>"                                                       \
> +    " </interface>"                                                     \
> +    " <interface name=\"org.freedesktop.DBus.Introspectable\">"         \
> +    "  <method name=\"Introspect\">"                                    \
> +    "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"              \
> +    "  </method>"                                                       \
> +    " </interface>"                                                     \
> +    "</node>"
> +
> +static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
> +                                                DBusPendingCallNotifyFunction func, void *call_data) {
> +    pa_dbus_pending *p;
> +    DBusPendingCall *call;
> +
> +    pa_assert(backend);
> +    pa_assert(m);
> +
> +    pa_assert_se(dbus_connection_send_with_reply(pa_dbus_connection_get(backend->connection), m, &call, -1));
> +
> +    p = pa_dbus_pending_new(pa_dbus_connection_get(backend->connection), m, call, backend, call_data);
> +    PA_LLIST_PREPEND(pa_dbus_pending, backend->pending, p);
> +    dbus_pending_call_set_notify(call, func, p, NULL);
> +
> +    return p;
> +}
> +
> +static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
> +    pa_bluetooth_device *d = t->device;
> +    struct sockaddr_sco addr;
> +    int err, i;
> +    int sock;
> +    bdaddr_t src;
> +    bdaddr_t dst;
> +    int voice = 0x60;
> +    const char *src_addr, *dst_addr;
> +
> +    src_addr = d->adapter->address;
> +    dst_addr = d->address;
> +
> +    for (i = 5; i >= 0; i--, src_addr += 3)
> +        src.b[i] = strtol(src_addr, NULL, 16);
> +    for (i = 5; i >= 0; i--, dst_addr += 3)
> +        dst.b[i] = strtol(dst_addr, NULL, 16);
> +
> +    sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
> +    if (sock < 0) {
> +        pa_log_error("socket(SEQPACKET, SCO) %s", pa_cstrerror(errno));
> +        return -1;
> +    }
> +
> +    memset(&addr, 0, sizeof(addr));
> +    addr.sco_family = AF_BLUETOOTH;
> +    bacpy(&addr.sco_bdaddr, &src);
> +
> +    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
> +        pa_log_error("bind(): %s", pa_cstrerror(errno));
> +        goto fail_close;
> +    }
> +
> +    if (voice) {
> +        struct bt_voice opts;
> +
> +        /* SCO voice setting */
> +        memset(&opts, 0, sizeof(opts));
> +        opts.setting = voice;
> +        if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &opts, sizeof(opts)) < 0) {
> +          pa_log_error("setsockopt(): %s", pa_cstrerror(errno));
> +          goto fail_close;
> +        }
> +    }

The code above would fail in kernels where BT_VOICE is not
implemented, iirc it was introduced in 3.12, so perhaps you should
check first with getsockopt or just ignore this for now since CVSD is
used by default you don't need to set it again.

> +    memset(&addr, 0, sizeof(addr));
> +    addr.sco_family = AF_BLUETOOTH;
> +    bacpy(&addr.sco_bdaddr, &dst);
> +
> +    pa_log_info ("doing connect\n");
> +    err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
> +    if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) {
> +        pa_log_error("connect(): %s", pa_cstrerror(errno));
> +        goto fail_close;
> +    }
> +
> +    if (imtu)
> +        *imtu = 48;
> +
> +    if (omtu)
> +        *omtu = 48;
> +
> +    return sock;
> +
> +fail_close:
> +    close(sock);
> +    return -1;
> +}
> +
> +static void bluez5_sco_release_cb(pa_bluetooth_transport *t) {
> +    pa_log_info("Transport %s released", t->path);
> +}
> +
> +static void register_profile_reply(DBusPendingCall *pending, void *userdata) {
> +    DBusMessage *r;
> +    pa_dbus_pending *p;
> +    pa_bluetooth_backend *b;
> +    char *profile;
> +
> +    pa_assert(pending);
> +    pa_assert_se(p = userdata);
> +    pa_assert_se(b = p->context_data);
> +    pa_assert_se(profile = p->call_data);
> +    pa_assert_se(r = dbus_pending_call_steal_reply(pending));
> +
> +    if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) {
> +        pa_log_info("Couldn't register profile %s because it is disabled in BlueZ", profile);
> +        goto finish;
> +    }
> +
> +    if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) {
> +        pa_log_error(BLUEZ_PROFILE_MANAGER_INTERFACE ".RegisterProfile() failed: %s: %s", dbus_message_get_error_name(r),
> +                     pa_dbus_get_error_message(r));
> +        goto finish;
> +    }
> +
> +finish:
> +    dbus_message_unref(r);
> +
> +    PA_LLIST_REMOVE(pa_dbus_pending, b->pending, p);
> +    pa_dbus_pending_free(p);
> +
> +    pa_xfree(profile);
> +}
> +
> +static void register_profile(pa_bluetooth_backend *b, const char *profile, const char *uuid) {
> +    DBusMessage *m;
> +    DBusMessageIter i, d;
> +
> +    pa_log_debug("Registering Profile %s", profile);
> +
> +    pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile"));
> +
> +    dbus_message_iter_init_append(m, &i);
> +    dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &profile);
> +    dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &uuid);
> +    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);
> +    dbus_message_iter_close_container(&i, &d);
> +
> +    send_and_add_to_pending(b, m, register_profile_reply, pa_xstrdup(profile));
> +}
> +
> +static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) {
> +    pa_bluetooth_transport *t = userdata;
> +
> +    pa_assert(io);
> +    pa_assert(t);
> +
> +    if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
> +        pa_log("Lost RFCOMM connection.");
> +        goto fail;
> +    }
> +
> +    if (events & PA_IO_EVENT_INPUT) {
> +        char buf[512];
> +        ssize_t len;
> +
> +        len = read (fd, buf, 511);
> +        buf[len] = 0;
> +        pa_log("RFCOMM << %s", buf);
> +
> +        pa_log("RFCOMM >> OK");
> +        len = write (fd, "\r\nOK\r\n", 5);
> +        if (len < 0)
> +            pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
> +    }
> +    return;
> +
> +fail:
> +    pa_bluetooth_transport_free(t);
> +    return;
> +}
> +
> +static void transport_dispose(pa_bluetooth_transport *t) {
> +    struct transport_rfcomm *trfc = t->userdata;
> +
> +    trfc->backend->core->mainloop->io_free(trfc->rfcomm_io);
> +    close (trfc->rfcomm_fd);

Since you got the fd from another process it usually a good idea to
call shutdown before close to guarantee the connection is terminated.

> +
> +    pa_xfree(trfc);
> +}
> +
> +
> +static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) {
> +    pa_bluetooth_backend *b = userdata;
> +    pa_bluetooth_device *d;
> +    pa_bluetooth_transport *t;
> +    pa_bluetooth_profile_t p;
> +    DBusMessage *r;
> +    int fd;
> +    const char *sender, *path, *handler;
> +    DBusMessageIter arg_i, element_i;
> +    char *pathfd;
> +    struct transport_rfcomm *trfc;
> +
> +    if (!dbus_message_iter_init(m, &arg_i) || !pa_streq(dbus_message_get_signature(m), "oha{sv}")) {
> +        pa_log_error("Invalid signature found in NewConnection");
> +        goto fail;
> +    }
> +
> +    handler = dbus_message_get_path(m);
> +
> +    pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_OBJECT_PATH);
> +    dbus_message_iter_get_basic(&arg_i, &path);
> +
> +    d = pa_bluetooth_discovery_get_device_by_path(b->discovery, path);
> +    if (d == NULL) {
> +        pa_log_error("Device doesnt exist for %s", path);
> +        goto fail;
> +    }
> +
> +    if (pa_streq(handler, HSP_AG_PROFILE)) {
> +        p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
> +    } else
> +        goto refused;
> +
> +    pa_assert_se(dbus_message_iter_next(&arg_i));
> +
> +    pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_UNIX_FD);
> +    dbus_message_iter_get_basic(&arg_i, &fd);
> +
> +    pa_log_debug("dbus: NewConnection path=%s, fd=%d", path, fd);
> +
> +    pa_assert_se(dbus_message_iter_next(&arg_i));
> +    pa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY);
> +
> +    dbus_message_iter_recurse(&arg_i, &element_i);
> +
> +    while (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 (key == NULL)
> +            break;
> +        if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_STRING)
> +            break;
> +
> +        dbus_message_iter_get_basic(&dict_i, &key);
> +
> +        if (!dbus_message_iter_next(&dict_i))
> +            break;
> +
> +        if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_VARIANT)
> +            break;
> +
> +        pa_log_debug("key=%s", key);
> +
> +        dbus_message_iter_next(&element_i);
> +    }
> +
> +    sender = dbus_message_get_sender(m);
> +
> +    pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd);
> +    d->transports[p] = t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
> +    pa_xfree(pathfd);
> +
> +    t->acquire = bluez5_sco_acquire_cb;
> +    t->release = bluez5_sco_release_cb;
> +    t->dispose = transport_dispose;
> +
> +    trfc =  pa_xnew0(struct transport_rfcomm, 1);
> +    trfc->rfcomm_fd = fd;
> +    trfc->rfcomm_io = b->core->mainloop->io_new(b->core->mainloop, fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP,
> +        rfcomm_io_callback, t);
> +    trfc->backend = b;
> +    t->userdata =  trfc;
> +
> +    pa_bluetooth_transport_put(t);
> +
> +    pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
> +
> +    pa_assert_se(r = dbus_message_new_method_return(m));
> +
> +    return r;
> +
> +fail:
> +    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to handle new connection"));
> +    return r;
> +refused:
> +    pa_log_debug("dbus: NewConnection path=%s rejected", path);
> +    pa_assert_se(r = dbus_message_new_error(m, "org.bluez.Error.Rejected", "New connection rejected"));
> +    return r;
> +}
> +
> +static DBusMessage *profile_request_disconnection(DBusConnection *conn, DBusMessage *m, void *userdata) {
> +    DBusMessage *r;
> +
> +    pa_assert_se(r = dbus_message_new_method_return(m));
> +
> +    return r;
> +}
> +
> +static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void *userdata) {
> +    pa_bluetooth_backend *b = userdata;
> +    DBusMessage *r = NULL;
> +    const char *path, *interface, *member;
> +
> +    pa_assert(b);
> +
> +    path = dbus_message_get_path(m);
> +    interface = dbus_message_get_interface(m);
> +    member = dbus_message_get_member(m);
> +
> +    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
> +
> +    if (!pa_streq(path, HSP_AG_PROFILE))
> +        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
> +
> +    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
> +        const char *xml = PROFILE_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, BLUEZ_PROFILE_INTERFACE, "Release")) {
> +    } else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "Cancel")) {
> +    } else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "RequestDisconnection")) {
> +        r = profile_request_disconnection(c, m, userdata);
> +    } else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "NewConnection"))
> +        r = profile_new_connection(c, m, userdata);
> +    else
> +        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
> +
> +    if (r) {
> +        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(b->connection), r, NULL));
> +        dbus_message_unref(r);
> +    }
> +
> +    return DBUS_HANDLER_RESULT_HANDLED;
> +}
> +
> +static void profile_init(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
> +    static const DBusObjectPathVTable vtable_profile = {
> +        .message_function = profile_handler,
> +    };
> +    const char *object_name;
> +    const char *uuid;
> +
> +    pa_assert(b);
> +
> +    switch(profile) {
> +        case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
> +            object_name = HSP_AG_PROFILE;
> +            uuid = PA_BLUETOOTH_UUID_HSP_AG;
> +            break;
> +        default:
> +            pa_assert_not_reached();
> +            break;
> +    }
> +    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(b->connection),
> +                  object_name, &vtable_profile, b));
> +    register_profile (b, object_name, uuid);
> +}
> +
> +static void profile_done(pa_bluetooth_backend *b, pa_bluetooth_profile_t profile) {
> +    pa_assert(b);
> +
> +    switch(profile) {
> +        case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
> +            dbus_connection_unregister_object_path(pa_dbus_connection_get(b->connection), HSP_AG_PROFILE);
> +            break;
> +        default:
> +            pa_assert_not_reached();
> +            break;
> +    }
> +}
> +
>  pa_bluetooth_backend *pa_bluetooth_backend_new(pa_core *c) {
> -    pa_log_debug("Bluetooth Headset Backend API support disabled");
> -    return NULL;
> +    pa_bluetooth_backend *backend;
> +    DBusError err;
> +
> +    pa_log_debug("Bluetooth Headset Backend API support using the NULL backend");
> +
> +    backend = pa_xnew0(pa_bluetooth_backend, 1);
> +    backend->core = c;
> +
> +    dbus_error_init(&err);
> +    if (!(backend->connection = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &err))) {
> +        pa_log("Failed to get D-Bus connection: %s", err.message);
> +        dbus_error_free(&err);
> +        pa_xfree(backend);
> +        return NULL;
> +    }
> +
> +    backend->discovery = pa_shared_get(c, "bluetooth-discovery");
> +
> +    profile_init(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT);
> +
> +    return backend;
>  }
>
> -void pa_bluetooth_backend_free(pa_bluetooth_backend *b) {
> -    /* Nothing to do here */
> +void pa_bluetooth_backend_free(pa_bluetooth_backend *backend) {
> +    pa_assert(backend);
> +
> +    profile_done(backend, PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT);
> +
> +    pa_xfree(backend);
>  }
> --
> 1.9.3
>
> _______________________________________________
> pulseaudio-discuss mailing list
> pulseaudio-discuss at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss



-- 
Luiz Augusto von Dentz


More information about the pulseaudio-discuss mailing list