[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