[pulseaudio-commits] 5 commits - configure.ac src/Makefile.am src/modules

Tanu Kaskinen tanuk at kemper.freedesktop.org
Fri Aug 22 02:28:52 PDT 2014


 configure.ac                                    |   16 
 src/Makefile.am                                 |    3 
 src/modules/bluetooth/backend-null.c            |   37 ++
 src/modules/bluetooth/backend-ofono.c           |  143 +++++++
 src/modules/bluetooth/bluez5-util.c             |    9 
 src/modules/bluetooth/bluez5-util.h             |   10 
 src/modules/bluetooth/module-bluetooth-policy.c |   27 -
 src/modules/bluetooth/module-bluez5-device.c    |  433 +++++++++++++++++-------
 8 files changed, 557 insertions(+), 121 deletions(-)

New commits:
commit 6d88a139fc932474af6c1bb27bcc504f0d737534
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Fri Aug 22 11:07:15 2014 +0300

    bluetooth: Create oFono backend

diff --git a/configure.ac b/configure.ac
index 4caccba..094b356 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1031,14 +1031,14 @@ 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=<null>],[Backend for Bluetooth headset profiles (null)]))
+    AS_HELP_STRING([--with-bluetooth-headset-backend=<ofono|null>],[Backend for Bluetooth headset profiles (ofono)]))
 if test -z "$with_bluetooth_headset_backend" ; then
-    BLUETOOTH_HEADSET_BACKEND=null
+    BLUETOOTH_HEADSET_BACKEND=ofono
 else
     BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend
 fi
 
-AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"],
+AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xofono && "test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"],
     [AC_MSG_ERROR([*** Invalid Bluetooth Headset backend])])
 
 AC_SUBST(BLUETOOTH_HEADSET_BACKEND)
diff --git a/src/modules/bluetooth/backend-ofono.c b/src/modules/bluetooth/backend-ofono.c
new file mode 100644
index 0000000..bf0db47
--- /dev/null
+++ b/src/modules/bluetooth/backend-ofono.c
@@ -0,0 +1,143 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/dbus-shared.h>
+
+#include "bluez5-util.h"
+
+#define OFONO_SERVICE "org.ofono"
+#define HF_AUDIO_AGENT_INTERFACE OFONO_SERVICE ".HandsfreeAudioAgent"
+
+#define HF_AUDIO_AGENT_PATH "/HandsfreeAudioAgent"
+
+#define HF_AUDIO_AGENT_XML                                          \
+    DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+    "<node>"                                                        \
+    "  <interface name=\"org.freedesktop.DBus.Introspectable\">"    \
+    "    <method name=\"Introspect\">"                              \
+    "      <arg direction=\"out\" type=\"s\" />"                    \
+    "    </method>"                                                 \
+    "  </interface>"                                                \
+    "  <interface name=\"org.ofono.HandsfreeAudioAgent\">"          \
+    "    <method name=\"Release\">"                                 \
+    "    </method>"                                                 \
+    "    <method name=\"NewConnection\">"                           \
+    "      <arg direction=\"in\"  type=\"o\" name=\"card_path\" />" \
+    "      <arg direction=\"in\"  type=\"h\" name=\"sco_fd\" />"    \
+    "      <arg direction=\"in\"  type=\"y\" name=\"codec\" />"     \
+    "    </method>"                                                 \
+    "  </interface>"                                                \
+    "</node>"
+
+struct pa_bluetooth_backend {
+    pa_core *core;
+    pa_dbus_connection *connection;
+};
+
+static DBusMessage *hf_audio_agent_release(DBusConnection *c, DBusMessage *m, void *data) {
+    DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
+    return r;
+}
+
+static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage *m, void *data) {
+    DBusMessage *r = dbus_message_new_error(m, "org.ofono.Error.NotImplemented", "Operation is not implemented");
+    return r;
+}
+
+static DBusHandlerResult hf_audio_agent_handler(DBusConnection *c, DBusMessage *m, void *data) {
+    pa_bluetooth_backend *backend = data;
+    DBusMessage *r = NULL;
+    const char *path, *interface, *member;
+
+    pa_assert(backend);
+
+    path = dbus_message_get_path(m);
+    interface = dbus_message_get_interface(m);
+    member = dbus_message_get_member(m);
+
+    if (!pa_streq(path, HF_AUDIO_AGENT_PATH))
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
+
+    if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) {
+        const char *xml = HF_AUDIO_AGENT_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, HF_AUDIO_AGENT_INTERFACE, "NewConnection"))
+        r = hf_audio_agent_new_connection(c, m, data);
+    else if (dbus_message_is_method_call(m, HF_AUDIO_AGENT_INTERFACE, "Release"))
+        r = hf_audio_agent_release(c, m, data);
+    else
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (r) {
+        pa_assert_se(dbus_connection_send(pa_dbus_connection_get(backend->connection), r, NULL));
+        dbus_message_unref(r);
+    }
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+pa_bluetooth_backend *pa_bluetooth_backend_new(pa_core *c) {
+    pa_bluetooth_backend *backend;
+    DBusError err;
+    static const DBusObjectPathVTable vtable_hf_audio_agent = {
+        .message_function = hf_audio_agent_handler,
+    };
+
+    pa_assert(c);
+
+    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);
+        return NULL;
+    }
+
+    pa_assert_se(dbus_connection_register_object_path(pa_dbus_connection_get(backend->connection), HF_AUDIO_AGENT_PATH,
+                                                      &vtable_hf_audio_agent, backend));
+
+    return backend;
+}
+
+void pa_bluetooth_backend_free(pa_bluetooth_backend *backend) {
+    pa_assert(backend);
+
+    if (backend->connection) {
+        dbus_connection_unregister_object_path(pa_dbus_connection_get(backend->connection), HF_AUDIO_AGENT_PATH);
+
+        pa_dbus_connection_unref(backend->connection);
+    }
+
+    pa_xfree(backend);
+}

commit dca5d0793720f15209c1cf02f3581f0875c118c2
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Fri Aug 22 11:07:14 2014 +0300

    bluetooth: Create NULL backend

diff --git a/configure.ac b/configure.ac
index dc2298d..4caccba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1028,6 +1028,21 @@ AS_IF([test "x$HAVE_BLUEZ_4" = "x1" || test "x$HAVE_BLUEZ_5" = "x1"], HAVE_BLUEZ
 AC_SUBST(HAVE_BLUEZ)
 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=<null>],[Backend for Bluetooth headset profiles (null)]))
+if test -z "$with_bluetooth_headset_backend" ; then
+    BLUETOOTH_HEADSET_BACKEND=null
+else
+    BLUETOOTH_HEADSET_BACKEND=$with_bluetooth_headset_backend
+fi
+
+AS_IF([test "x$BLUETOOTH_HEADSET_BACKEND" != "xnull"],
+    [AC_MSG_ERROR([*** Invalid Bluetooth Headset backend])])
+
+AC_SUBST(BLUETOOTH_HEADSET_BACKEND)
+
 #### UDEV support (optional) ####
 
 AC_ARG_ENABLE([udev],
@@ -1497,6 +1512,7 @@ echo "
     Enable D-Bus:                  ${ENABLE_DBUS}
       Enable BlueZ 4:              ${ENABLE_BLUEZ_4}
       Enable BlueZ 5:              ${ENABLE_BLUEZ_5}
+        headset backed:            ${BLUETOOTH_HEADSET_BACKEND}
     Enable udev:                   ${ENABLE_UDEV}
       Enable HAL->udev compat:     ${ENABLE_HAL_COMPAT}
     Enable systemd login:          ${ENABLE_SYSTEMD}
diff --git a/src/Makefile.am b/src/Makefile.am
index 21eb365..3ceaddc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2099,7 +2099,8 @@ module_bluez4_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) $(SBC_CFLAGS)
 libbluez5_util_la_SOURCES = \
 		modules/bluetooth/bluez5-util.c \
 		modules/bluetooth/bluez5-util.h \
-		modules/bluetooth/a2dp-codecs.h
+		modules/bluetooth/a2dp-codecs.h \
+		modules/bluetooth/backend- at BLUETOOTH_HEADSET_BACKEND@.c
 libbluez5_util_la_LDFLAGS = -avoid-version
 libbluez5_util_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS)
 libbluez5_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS)
diff --git a/src/modules/bluetooth/backend-null.c b/src/modules/bluetooth/backend-null.c
new file mode 100644
index 0000000..f8a145b
--- /dev/null
+++ b/src/modules/bluetooth/backend-null.c
@@ -0,0 +1,37 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 João Paulo Rechi Vita
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/log.h>
+
+#include "bluez5-util.h"
+
+pa_bluetooth_backend *pa_bluetooth_backend_new(pa_core *c) {
+    pa_log_debug("Bluetooth Headset Backend API support disabled");
+    return NULL;
+}
+
+void pa_bluetooth_backend_free(pa_bluetooth_backend *b) {
+    /* Nothing to do here */
+}
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index adb8351..93677b4 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -87,6 +87,7 @@ struct pa_bluetooth_discovery {
     pa_hashmap *devices;
     pa_hashmap *transports;
 
+    pa_bluetooth_backend *backend;
     PA_LLIST_HEAD(pa_dbus_pending, pending);
 };
 
@@ -1590,6 +1591,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c) {
 
     endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SINK);
     endpoint_init(y, PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
+    y->backend = pa_bluetooth_backend_new(c);
 
     get_managed_objects(y);
 
@@ -1631,6 +1633,9 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
         pa_hashmap_free(y->transports);
     }
 
+    if (y->backend)
+        pa_bluetooth_backend_free(y->backend);
+
     if (y->connection) {
 
         if (y->matches_added)
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 0121733..67377e9 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -35,6 +35,7 @@ typedef struct pa_bluetooth_transport pa_bluetooth_transport;
 typedef struct pa_bluetooth_device pa_bluetooth_device;
 typedef struct pa_bluetooth_adapter pa_bluetooth_adapter;
 typedef struct pa_bluetooth_discovery pa_bluetooth_discovery;
+typedef struct pa_bluetooth_backend pa_bluetooth_backend;
 
 typedef enum pa_bluetooth_hook {
     PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED,          /* Call data: pa_bluetooth_device */
@@ -105,6 +106,9 @@ struct pa_bluetooth_adapter {
     bool valid;
 };
 
+pa_bluetooth_backend *pa_bluetooth_backend_new(pa_core *c);
+void pa_bluetooth_backend_free(pa_bluetooth_backend *b);
+
 pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
                                                    pa_bluetooth_profile_t p, const uint8_t *config, size_t size);
 

commit 2198048e5d0917c5bd57316858bbbb7064906884
Author: João Paulo Rechi Vita <jprvita at gmail.com>
Date:   Fri Aug 22 11:07:13 2014 +0300

    bluetooth: Add BlueZ 5 headset profile names in policy module

diff --git a/src/modules/bluetooth/module-bluetooth-policy.c b/src/modules/bluetooth/module-bluetooth-policy.c
index 6837f0d..8bbda3d 100644
--- a/src/modules/bluetooth/module-bluetooth-policy.c
+++ b/src/modules/bluetooth/module-bluetooth-policy.c
@@ -41,17 +41,19 @@ PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(true);
 PA_MODULE_USAGE(
         "a2dp_source=<Handle a2dp_source card profile (sink role)?> "
-        "hfgw=<Handle hfgw card profile (headset role)?>");
+        "ag=<Handle headset_audio_gateway card profile (headset role)?> "
+        "hfgw=<Handle hfgw card profile (headset role)?> DEPRECATED");
 
 static const char* const valid_modargs[] = {
     "a2dp_source",
+    "ag",
     "hfgw",
     NULL
 };
 
 struct userdata {
     bool enable_a2dp_source;
-    bool enable_hfgw;
+    bool enable_ag;
     pa_hook_slot *source_put_slot;
     pa_hook_slot *sink_put_slot;
     pa_hook_slot *profile_available_changed_slot;
@@ -79,9 +81,10 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, pa_source *source,
     if (!s)
         return PA_HOOK_OK;
 
-    if (u->enable_a2dp_source && pa_streq(s, "a2dp_source")) /* A2DP profile (we're doing sink role) */
+    if (u->enable_a2dp_source && pa_streq(s, "a2dp_source"))
         role = "music";
-    else if (u->enable_hfgw && pa_streq(s, "hfgw")) /* HFP profile (we're doing headset role) */
+    /* TODO: remove hfgw when we remove BlueZ 4 support */
+    else if (u->enable_ag && (pa_streq(s, "hfgw") || pa_streq(s, "headset_audio_gateway")))
         role = "phone";
     else {
         pa_log_debug("Profile %s cannot be selected for loopback", s);
@@ -119,7 +122,8 @@ static pa_hook_result_t sink_put_hook_callback(pa_core *c, pa_sink *sink, void *
     if (!s)
         return PA_HOOK_OK;
 
-    if (u->enable_hfgw && pa_streq(s, "hfgw")) /* HFP profile (we're doing headset role) */
+    /* TODO: remove hfgw when we remove BlueZ 4 support */
+    if (u->enable_ag && (pa_streq(s, "hfgw") || pa_streq(s, "headset_audio_gateway")))
         role = "phone";
     else {
         pa_log_debug("Profile %s cannot be selected for loopback", s);
@@ -172,8 +176,9 @@ static pa_hook_result_t profile_available_hook_callback(pa_core *c, pa_card_prof
         return PA_HOOK_OK;
 
     /* Do not automatically switch profiles for headsets, just in case */
-    /* TODO: remove a2dp when we decide to remove support for BlueZ 4 */
-    if (pa_streq(profile->name, "hsp") || pa_streq(profile->name, "a2dp") || pa_streq(profile->name, "a2dp_sink"))
+    /* TODO: remove a2dp and hsp when we remove BlueZ 4 support */
+    if (pa_streq(profile->name, "hsp") || pa_streq(profile->name, "a2dp") || pa_streq(profile->name, "a2dp_sink") ||
+        pa_streq(profile->name, "headset_head_unit"))
         return PA_HOOK_OK;
 
     is_active_profile = card->active_profile == profile;
@@ -236,11 +241,15 @@ int pa__init(pa_module *m) {
         goto fail;
     }
 
-    u->enable_hfgw = true;
-    if (pa_modargs_get_value_boolean(ma, "hfgw", &u->enable_hfgw) < 0) {
+    u->enable_ag = true;
+    if (pa_modargs_get_value_boolean(ma, "hfgw", &u->enable_ag) < 0) {
         pa_log("Failed to parse hfgw argument.");
         goto fail;
     }
+    if (pa_modargs_get_value_boolean(ma, "ag", &u->enable_ag) < 0) {
+        pa_log("Failed to parse ag argument.");
+        goto fail;
+    }
 
     u->source_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL,
                                          (pa_hook_cb_t) source_put_hook_callback, u);

commit bdef2dbd0a08f6e44c15ea8febb66b6d9088875b
Author: Luiz Augusto von Dentz <luiz.von.dentz at intel.com>
Date:   Fri Aug 22 11:07:12 2014 +0300

    bluetooth: Assert transport has a matching profile
    
    It is a bug if a transport has no matching profile.

diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 77964c1..5398a1b 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -1890,10 +1890,8 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot
 
     pa_assert(u);
     pa_assert(t);
+    pa_assert_se(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile)));
 
-    /* Update profile availability */
-    if (!(cp = pa_hashmap_get(u->card->profiles, pa_bluetooth_profile_to_string(t->profile))))
-        return;
     pa_card_profile_set_available(cp, transport_state_to_availability(t->state));
 
     /* Update port availability */

commit 1f0de01bfc85f92785fcd2f0e863e471af7e6ace
Author: João Paulo Rechi Vita <jprvita at openbossa.org>
Date:   Fri Aug 22 11:07:11 2014 +0300

    bluetooth: Add basic support for HEADSET profiles
    
    This commit adds basic support for devices implementing HSP Headset
    Unit, HSP Audio Gateway, HFP Handsfree Unit, HFP Audio Gateway to the
    BlueZ 5 bluetooth audio devices driver module (module-bluez5-device).

diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index 5b6b372..adb8351 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -1109,6 +1109,10 @@ const char *pa_bluetooth_profile_to_string(pa_bluetooth_profile_t profile) {
             return "a2dp_sink";
         case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
             return "a2dp_source";
+        case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
+            return "headset_head_unit";
+        case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+            return "headset_audio_gateway";
         case PA_BLUETOOTH_PROFILE_OFF:
             return "off";
     }
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 63bae35..0121733 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -26,6 +26,10 @@
 
 #define PA_BLUETOOTH_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb"
 #define PA_BLUETOOTH_UUID_A2DP_SINK   "0000110b-0000-1000-8000-00805f9b34fb"
+#define PA_BLUETOOTH_UUID_HSP_HS      "00001108-0000-1000-8000-00805f9b34fb"
+#define PA_BLUETOOTH_UUID_HSP_AG      "00001112-0000-1000-8000-00805f9b34fb"
+#define PA_BLUETOOTH_UUID_HFP_HF      "0000111e-0000-1000-8000-00805f9b34fb"
+#define PA_BLUETOOTH_UUID_HFP_AG      "0000111f-0000-1000-8000-00805f9b34fb"
 
 typedef struct pa_bluetooth_transport pa_bluetooth_transport;
 typedef struct pa_bluetooth_device pa_bluetooth_device;
@@ -41,6 +45,8 @@ typedef enum pa_bluetooth_hook {
 typedef enum profile {
     PA_BLUETOOTH_PROFILE_A2DP_SINK,
     PA_BLUETOOTH_PROFILE_A2DP_SOURCE,
+    PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT,
+    PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY,
     PA_BLUETOOTH_PROFILE_OFF
 } pa_bluetooth_profile_t;
 #define PA_BLUETOOTH_PROFILE_COUNT PA_BLUETOOTH_PROFILE_OFF
diff --git a/src/modules/bluetooth/module-bluez5-device.c b/src/modules/bluetooth/module-bluez5-device.c
index 57b2791..77964c1 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -33,6 +33,7 @@
 #include <pulse/timeval.h>
 
 #include <pulsecore/core-error.h>
+#include <pulsecore/core-rtclock.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/i18n.h>
 #include <pulsecore/module.h>
@@ -59,7 +60,9 @@ PA_MODULE_USAGE("path=<device object path>");
 
 #define MAX_PLAYBACK_CATCH_UP_USEC (100 * PA_USEC_PER_MSEC)
 #define FIXED_LATENCY_PLAYBACK_A2DP (25 * PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_PLAYBACK_SCO (125 * PA_USEC_PER_MSEC)
 #define FIXED_LATENCY_RECORD_A2DP   (25 * PA_USEC_PER_MSEC)
+#define FIXED_LATENCY_RECORD_SCO    (25 * PA_USEC_PER_MSEC)
 
 #define BITPOOL_DEC_LIMIT 32
 #define BITPOOL_DEC_STEP 5
@@ -236,6 +239,157 @@ static void connect_ports(struct userdata *u, void *new_data, pa_direction_t dir
 }
 
 /* Run from IO thread */
+static int sco_process_render(struct userdata *u) {
+    ssize_t l;
+    pa_memchunk memchunk;
+
+    pa_assert(u);
+    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
+                u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
+    pa_assert(u->sink);
+
+    pa_sink_render_full(u->sink, u->write_block_size, &memchunk);
+
+    pa_assert(memchunk.length == u->write_block_size);
+
+    for (;;) {
+        const void *p;
+
+        /* Now write that data to the socket. The socket is of type
+         * SEQPACKET, and we generated the data of the MTU size, so this
+         * should just work. */
+
+        p = (const uint8_t *) pa_memblock_acquire_chunk(&memchunk);
+        l = pa_write(u->stream_fd, p, memchunk.length, &u->stream_write_type);
+        pa_memblock_release(memchunk.memblock);
+
+        pa_assert(l != 0);
+
+        if (l > 0)
+            break;
+
+        if (errno == EINTR)
+            /* Retry right away if we got interrupted */
+            continue;
+        else if (errno == EAGAIN)
+            /* Hmm, apparently the socket was not writable, give up for now */
+            return 0;
+
+        pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(errno));
+        return -1;
+    }
+
+    pa_assert((size_t) l <= memchunk.length);
+
+    if ((size_t) l != memchunk.length) {
+        pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+                    (unsigned long long) l,
+                    (unsigned long long) memchunk.length);
+        return -1;
+    }
+
+    u->write_index += (uint64_t) memchunk.length;
+    pa_memblock_unref(memchunk.memblock);
+
+    return 1;
+}
+
+/* Run from IO thread */
+static int sco_process_push(struct userdata *u) {
+    ssize_t l;
+    pa_memchunk memchunk;
+    struct cmsghdr *cm;
+    struct msghdr m;
+    bool found_tstamp = false;
+    pa_usec_t tstamp = 0;
+
+    pa_assert(u);
+    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
+                u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY);
+    pa_assert(u->source);
+    pa_assert(u->read_smoother);
+
+    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
+    memchunk.index = memchunk.length = 0;
+
+    for (;;) {
+        void *p;
+        uint8_t aux[1024];
+        struct iovec iov;
+
+        pa_zero(m);
+        pa_zero(aux);
+        pa_zero(iov);
+
+        m.msg_iov = &iov;
+        m.msg_iovlen = 1;
+        m.msg_control = aux;
+        m.msg_controllen = sizeof(aux);
+
+        p = pa_memblock_acquire(memchunk.memblock);
+        iov.iov_base = p;
+        iov.iov_len = pa_memblock_get_length(memchunk.memblock);
+        l = recvmsg(u->stream_fd, &m, 0);
+        pa_memblock_release(memchunk.memblock);
+
+        if (l > 0)
+            break;
+
+        if (l < 0 && errno == EINTR)
+            /* Retry right away if we got interrupted */
+            continue;
+
+        pa_memblock_unref(memchunk.memblock);
+
+        if (l < 0 && errno == EAGAIN)
+            /* Hmm, apparently the socket was not readable, give up for now. */
+            return 0;
+
+        pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
+        return -1;
+    }
+
+    pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
+
+    /* In some rare occasions, we might receive packets of a very strange
+     * size. This could potentially be possible if the SCO packet was
+     * received partially over-the-air, or more probably due to hardware
+     * issues in our Bluetooth adapter. In these cases, in order to avoid
+     * an assertion failure due to unaligned data, just discard the whole
+     * packet */
+    if (!pa_frame_aligned(l, &u->sample_spec)) {
+        pa_log_warn("SCO packet received of unaligned size: %zu", l);
+        pa_memblock_unref(memchunk.memblock);
+        return -1;
+    }
+
+    memchunk.length = (size_t) l;
+    u->read_index += (uint64_t) l;
+
+    for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
+        if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
+            struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
+            pa_rtclock_from_wallclock(tv);
+            tstamp = pa_timeval_load(tv);
+            found_tstamp = true;
+            break;
+        }
+
+    if (!found_tstamp) {
+        pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
+        tstamp = pa_rtclock_now();
+    }
+
+    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+    pa_smoother_resume(u->read_smoother, tstamp, true);
+
+    pa_source_post(u->source, &memchunk);
+    pa_memblock_unref(memchunk.memblock);
+
+    return l;
+}
+
+/* Run from IO thread */
 static void a2dp_prepare_buffer(struct userdata *u) {
     size_t min_buffer_size = PA_MAX(u->read_link_mtu, u->write_link_mtu);
 
@@ -611,24 +765,31 @@ static void transport_release(struct userdata *u) {
 
 /* Run from I/O thread */
 static void transport_config_mtu(struct userdata *u) {
-    u->read_block_size =
-        (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / u->sbc_info.frame_length * u->sbc_info.codesize;
+    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
+        u->read_block_size = u->read_link_mtu;
+        u->write_block_size = u->write_link_mtu;
+    } else {
+        u->read_block_size =
+            (u->read_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+            / u->sbc_info.frame_length * u->sbc_info.codesize;
 
-    u->write_block_size =
-        (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
-        / u->sbc_info.frame_length * u->sbc_info.codesize;
+        u->write_block_size =
+            (u->write_link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
+            / u->sbc_info.frame_length * u->sbc_info.codesize;
+    }
 
     if (u->sink) {
         pa_sink_set_max_request_within_thread(u->sink, u->write_block_size);
         pa_sink_set_fixed_latency_within_thread(u->sink,
-                                                FIXED_LATENCY_PLAYBACK_A2DP +
+                                                (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ?
+                                                 FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO) +
                                                 pa_bytes_to_usec(u->write_block_size, &u->sample_spec));
     }
 
     if (u->source)
         pa_source_set_fixed_latency_within_thread(u->source,
-                                                  FIXED_LATENCY_RECORD_A2DP +
+                                                  (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
+                                                   FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO) +
                                                   pa_bytes_to_usec(u->read_block_size, &u->sample_spec));
 }
 
@@ -755,15 +916,19 @@ static int add_source(struct userdata *u) {
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
     pa_source_new_data_set_sample_spec(&data, &u->sample_spec);
+    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
+        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
     connect_ports(u, &data, PA_DIRECTION_INPUT);
 
     if (!u->transport_acquired)
         switch (u->profile) {
             case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
                 data.suspend_cause = PA_SUSPEND_USER;
                 break;
             case PA_BLUETOOTH_PROFILE_A2DP_SINK:
+            case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
             case PA_BLUETOOTH_PROFILE_OFF:
                 pa_assert_not_reached();
                 break;
@@ -870,14 +1035,20 @@ static int add_sink(struct userdata *u) {
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
     pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
+    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT)
+        pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
 
     connect_ports(u, &data, PA_DIRECTION_OUTPUT);
 
     if (!u->transport_acquired)
         switch (u->profile) {
+            case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY:
+                data.suspend_cause = PA_SUSPEND_USER;
+                break;
             case PA_BLUETOOTH_PROFILE_A2DP_SINK:
                 /* Profile switch should have failed */
             case PA_BLUETOOTH_PROFILE_A2DP_SOURCE:
+            case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT:
             case PA_BLUETOOTH_PROFILE_OFF:
                 pa_assert_not_reached();
                 break;
@@ -898,111 +1069,117 @@ static int add_sink(struct userdata *u) {
 
 /* Run from main thread */
 static void transport_config(struct userdata *u) {
-    sbc_info_t *sbc_info = &u->sbc_info;
-    a2dp_sbc_t *config;
+    if (u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY) {
+        u->sample_spec.format = PA_SAMPLE_S16LE;
+        u->sample_spec.channels = 1;
+        u->sample_spec.rate = 8000;
+    } else {
+        sbc_info_t *sbc_info = &u->sbc_info;
+        a2dp_sbc_t *config;
 
-    pa_assert(u->transport);
+        pa_assert(u->transport);
 
-    u->sample_spec.format = PA_SAMPLE_S16LE;
-    config = (a2dp_sbc_t *) u->transport->config;
+        u->sample_spec.format = PA_SAMPLE_S16LE;
+        config = (a2dp_sbc_t *) u->transport->config;
 
-    if (sbc_info->sbc_initialized)
-        sbc_reinit(&sbc_info->sbc, 0);
-    else
-        sbc_init(&sbc_info->sbc, 0);
-    sbc_info->sbc_initialized = true;
+        if (sbc_info->sbc_initialized)
+            sbc_reinit(&sbc_info->sbc, 0);
+        else
+            sbc_init(&sbc_info->sbc, 0);
+        sbc_info->sbc_initialized = true;
 
-    switch (config->frequency) {
-        case SBC_SAMPLING_FREQ_16000:
-            sbc_info->sbc.frequency = SBC_FREQ_16000;
-            u->sample_spec.rate = 16000U;
-            break;
-        case SBC_SAMPLING_FREQ_32000:
-            sbc_info->sbc.frequency = SBC_FREQ_32000;
-            u->sample_spec.rate = 32000U;
-            break;
-        case SBC_SAMPLING_FREQ_44100:
-            sbc_info->sbc.frequency = SBC_FREQ_44100;
-            u->sample_spec.rate = 44100U;
-            break;
-        case SBC_SAMPLING_FREQ_48000:
-            sbc_info->sbc.frequency = SBC_FREQ_48000;
-            u->sample_spec.rate = 48000U;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
+        switch (config->frequency) {
+            case SBC_SAMPLING_FREQ_16000:
+                sbc_info->sbc.frequency = SBC_FREQ_16000;
+                u->sample_spec.rate = 16000U;
+                break;
+            case SBC_SAMPLING_FREQ_32000:
+                sbc_info->sbc.frequency = SBC_FREQ_32000;
+                u->sample_spec.rate = 32000U;
+                break;
+            case SBC_SAMPLING_FREQ_44100:
+                sbc_info->sbc.frequency = SBC_FREQ_44100;
+                u->sample_spec.rate = 44100U;
+                break;
+            case SBC_SAMPLING_FREQ_48000:
+                sbc_info->sbc.frequency = SBC_FREQ_48000;
+                u->sample_spec.rate = 48000U;
+                break;
+            default:
+                pa_assert_not_reached();
+        }
 
-    switch (config->channel_mode) {
-        case SBC_CHANNEL_MODE_MONO:
-            sbc_info->sbc.mode = SBC_MODE_MONO;
-            u->sample_spec.channels = 1;
-            break;
-        case SBC_CHANNEL_MODE_DUAL_CHANNEL:
-            sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
-            u->sample_spec.channels = 2;
-            break;
-        case SBC_CHANNEL_MODE_STEREO:
-            sbc_info->sbc.mode = SBC_MODE_STEREO;
-            u->sample_spec.channels = 2;
-            break;
-        case SBC_CHANNEL_MODE_JOINT_STEREO:
-            sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
-            u->sample_spec.channels = 2;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
+        switch (config->channel_mode) {
+            case SBC_CHANNEL_MODE_MONO:
+                sbc_info->sbc.mode = SBC_MODE_MONO;
+                u->sample_spec.channels = 1;
+                break;
+            case SBC_CHANNEL_MODE_DUAL_CHANNEL:
+                sbc_info->sbc.mode = SBC_MODE_DUAL_CHANNEL;
+                u->sample_spec.channels = 2;
+                break;
+            case SBC_CHANNEL_MODE_STEREO:
+                sbc_info->sbc.mode = SBC_MODE_STEREO;
+                u->sample_spec.channels = 2;
+                break;
+            case SBC_CHANNEL_MODE_JOINT_STEREO:
+                sbc_info->sbc.mode = SBC_MODE_JOINT_STEREO;
+                u->sample_spec.channels = 2;
+                break;
+            default:
+                pa_assert_not_reached();
+        }
 
-    switch (config->allocation_method) {
-        case SBC_ALLOCATION_SNR:
-            sbc_info->sbc.allocation = SBC_AM_SNR;
-            break;
-        case SBC_ALLOCATION_LOUDNESS:
-            sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
+        switch (config->allocation_method) {
+            case SBC_ALLOCATION_SNR:
+                sbc_info->sbc.allocation = SBC_AM_SNR;
+                break;
+            case SBC_ALLOCATION_LOUDNESS:
+                sbc_info->sbc.allocation = SBC_AM_LOUDNESS;
+                break;
+            default:
+                pa_assert_not_reached();
+        }
 
-    switch (config->subbands) {
-        case SBC_SUBBANDS_4:
-            sbc_info->sbc.subbands = SBC_SB_4;
-            break;
-        case SBC_SUBBANDS_8:
-            sbc_info->sbc.subbands = SBC_SB_8;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
+        switch (config->subbands) {
+            case SBC_SUBBANDS_4:
+                sbc_info->sbc.subbands = SBC_SB_4;
+                break;
+            case SBC_SUBBANDS_8:
+                sbc_info->sbc.subbands = SBC_SB_8;
+                break;
+            default:
+                pa_assert_not_reached();
+        }
 
-    switch (config->block_length) {
-        case SBC_BLOCK_LENGTH_4:
-            sbc_info->sbc.blocks = SBC_BLK_4;
-            break;
-        case SBC_BLOCK_LENGTH_8:
-            sbc_info->sbc.blocks = SBC_BLK_8;
-            break;
-        case SBC_BLOCK_LENGTH_12:
-            sbc_info->sbc.blocks = SBC_BLK_12;
-            break;
-        case SBC_BLOCK_LENGTH_16:
-            sbc_info->sbc.blocks = SBC_BLK_16;
-            break;
-        default:
-            pa_assert_not_reached();
-    }
+        switch (config->block_length) {
+            case SBC_BLOCK_LENGTH_4:
+                sbc_info->sbc.blocks = SBC_BLK_4;
+                break;
+            case SBC_BLOCK_LENGTH_8:
+                sbc_info->sbc.blocks = SBC_BLK_8;
+                break;
+            case SBC_BLOCK_LENGTH_12:
+                sbc_info->sbc.blocks = SBC_BLK_12;
+                break;
+            case SBC_BLOCK_LENGTH_16:
+                sbc_info->sbc.blocks = SBC_BLK_16;
+                break;
+            default:
+                pa_assert_not_reached();
+        }
 
-    sbc_info->min_bitpool = config->min_bitpool;
-    sbc_info->max_bitpool = config->max_bitpool;
+        sbc_info->min_bitpool = config->min_bitpool;
+        sbc_info->max_bitpool = config->max_bitpool;
 
-    /* Set minimum bitpool for source to get the maximum possible block_size */
-    sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
-    sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
-    sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
+        /* Set minimum bitpool for source to get the maximum possible block_size */
+        sbc_info->sbc.bitpool = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info->max_bitpool : sbc_info->min_bitpool;
+        sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc);
+        sbc_info->frame_length = sbc_get_frame_length(&sbc_info->sbc);
 
-    pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
-                sbc_info->sbc.allocation, sbc_info->sbc.subbands, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
+        pa_log_info("SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u",
+                    sbc_info->sbc.allocation, sbc_info->sbc.subbands, sbc_info->sbc.blocks, sbc_info->sbc.bitpool);
+    }
 }
 
 /* Run from main thread */
@@ -1022,7 +1199,7 @@ static int setup_transport(struct userdata *u) {
 
     u->transport = t;
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
         transport_acquire(u, true); /* In case of error, the sink/sources will be created suspended */
     else if (transport_acquire(u, false) < 0)
         return -1; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
@@ -1043,11 +1220,13 @@ static int init_profile(struct userdata *u) {
 
     pa_assert(u->transport);
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK)
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
+        u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
         if (add_sink(u) < 0)
             r = -1;
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE || u->profile == PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ||
+        u->profile == PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY)
         if (add_source(u) < 0)
             r = -1;
 
@@ -1111,7 +1290,10 @@ static void thread_func(void *userdata) {
             if (pollfd && (pollfd->revents & POLLIN)) {
                 int n_read;
 
-                n_read = a2dp_process_push(u);
+                if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
+                    n_read = a2dp_process_push(u);
+                else
+                    n_read = sco_process_push(u);
 
                 if (n_read < 0)
                     goto fail;
@@ -1182,8 +1364,13 @@ static void thread_func(void *userdata) {
                     if (u->write_index <= 0)
                         u->started_at = pa_rtclock_now();
 
-                    if ((n_written = a2dp_process_render(u)) < 0)
-                        goto fail;
+                    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
+                        if ((n_written = a2dp_process_render(u)) < 0)
+                            goto fail;
+                    } else {
+                        if ((n_written = sco_process_render(u)) < 0)
+                            goto fail;
+                    }
 
                     if (n_written == 0)
                         pa_log("Broken kernel: we got EAGAIN on write() after POLLOUT!");
@@ -1363,6 +1550,8 @@ static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
     static const pa_direction_t profile_direction[] = {
         [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
         [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
+        [PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
         [PA_BLUETOOTH_PROFILE_OFF] = 0
     };
 
@@ -1540,6 +1729,30 @@ static pa_card_profile *create_card_profile(struct userdata *u, const char *uuid
 
         p = PA_CARD_PROFILE_DATA(cp);
         *p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE;
+    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_HS) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_HF)) {
+        cp = pa_card_profile_new("headset_head_unit", _("Headset Head Unit (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
+        cp->priority = 20;
+        cp->n_sinks = 1;
+        cp->n_sources = 1;
+        cp->max_sink_channels = 1;
+        cp->max_source_channels = 1;
+        pa_hashmap_put(input_port->profiles, cp->name, cp);
+        pa_hashmap_put(output_port->profiles, cp->name, cp);
+
+        p = PA_CARD_PROFILE_DATA(cp);
+        *p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT;
+    } else if (pa_streq(uuid, PA_BLUETOOTH_UUID_HSP_AG) || pa_streq(uuid, PA_BLUETOOTH_UUID_HFP_AG)) {
+        cp = pa_card_profile_new("headset_audio_gateway", _("Headset Audio Gateway (HSP/HFP)"), sizeof(pa_bluetooth_profile_t));
+        cp->priority = 20;
+        cp->n_sinks = 1;
+        cp->n_sources = 1;
+        cp->max_sink_channels = 1;
+        cp->max_source_channels = 1;
+        pa_hashmap_put(input_port->profiles, cp->name, cp);
+        pa_hashmap_put(output_port->profiles, cp->name, cp);
+
+        p = PA_CARD_PROFILE_DATA(cp);
+        *p = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY;
     }
 
     if (cp && u->device->transports[*p])



More information about the pulseaudio-commits mailing list