commit 142307dbb3ab6a81eb4da52589582da4cc402fe4 Author: borine <32966433+borine@users.noreply.github.com> Date: Thu Jun 1 08:55:29 2023 +0100 Fix mSBC MTU adjustment on Realtek USB adapters diff --git a/.github/spellcheck-wordlist.txt b/.github/spellcheck-wordlist.txt index 49b1f55..097db2c 100644 --- a/.github/spellcheck-wordlist.txt +++ b/.github/spellcheck-wordlist.txt @@ -66,6 +66,7 @@ MPD oFono PipeWire PulseAudio +Realtek UPower # Technical Words diff --git a/doc/bluealsa.8.rst b/doc/bluealsa.8.rst index 57fc89d..ea6d877 100644 --- a/doc/bluealsa.8.rst +++ b/doc/bluealsa.8.rst @@ -100,6 +100,11 @@ OPTIONS It will reduce the gap between playbacks caused by Bluetooth audio transport acquisition. +--disable-realtek-usb-fix + Since Linux kernel 5.14 Realtek USB adapters have required **bluealsa** to + apply a fix for mSBC. This option disables that fix and may be necessary + when using an earlier kernel. + --a2dp-force-mono Force monophonic sound for A2DP profile. diff --git a/src/ba-transport.c b/src/ba-transport.c index 85679e9..b983291 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -887,7 +887,7 @@ static int transport_acquire_bt_sco(struct ba_transport *t) { debug("New SCO link: %s: %d", batostr_(&d->addr), fd); - t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, d->a->hci.type); + t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, d->a); t->bt_fd = fd; return fd; diff --git a/src/bluealsa-config.c b/src/bluealsa-config.c index 6c9cb4d..ef8afb6 100644 --- a/src/bluealsa-config.c +++ b/src/bluealsa-config.c @@ -34,6 +34,8 @@ struct ba_config config = { .volume_init_level = 0, + .disable_realtek_usb_fix = false, + /* CVSD is a mandatory codec */ .hfp.codecs.cvsd = true, #if ENABLE_MSBC diff --git a/src/bluealsa-config.h b/src/bluealsa-config.h index daefc4e..8cfa866 100644 --- a/src/bluealsa-config.h +++ b/src/bluealsa-config.h @@ -68,6 +68,9 @@ struct ba_config { /* the initial volume level */ int volume_init_level; + /* disable alt-3 MTU for mSBC with Realtek USB adapters */ + bool disable_realtek_usb_fix; + struct { /* available HFP codecs */ diff --git a/src/hci.c b/src/hci.c index d30c50f..b78ae38 100644 --- a/src/hci.c +++ b/src/hci.c @@ -23,6 +23,9 @@ #include #include +#include "ba-adapter.h" +#include "bluealsa-config.h" +#include "shared/bluetooth.h" #include "shared/log.h" /** @@ -110,10 +113,10 @@ int hci_sco_connect(int sco_fd, const bdaddr_t *ba, uint16_t voice) { * Get read/write MTU for given SCO socket. * * @param sco_fd File descriptor of opened SCO socket. - * @param hci_type The type of the HCI returned by hci_devinfo(). + * @param a The adapter associated with sco_fd. * @return On success this function returns MTU value. Otherwise, 0 is returned and * errno is set to indicate the error. */ -unsigned int hci_sco_get_mtu(int sco_fd, int hci_type) { +unsigned int hci_sco_get_mtu(int sco_fd, struct ba_adapter *a) { struct sco_options options = { 0 }; struct bt_voice voice = { 0 }; @@ -135,10 +138,16 @@ unsigned int hci_sco_get_mtu(int sco_fd, int hci_type) { /* XXX: It seems, that the MTU value returned by kernel btusb driver * is incorrect. */ - if ((hci_type & 0x0F) == HCI_USB) { + if ((a->hci.type & 0x0F) == HCI_USB) { options.mtu = 48; - if (voice.setting == BT_VOICE_TRANSPARENT) - options.mtu = 24; + if (voice.setting == BT_VOICE_TRANSPARENT) { + if (a->chip.manufacturer == 0) + hci_get_version(a->hci.dev_id, &a->chip); + if (!config.disable_realtek_usb_fix && a->chip.manufacturer == BT_COMPID_REALTEK) + options.mtu = 72; + else + options.mtu = 24; + } debug("USB adjusted SCO MTU: %d: %u", sco_fd, options.mtu); } diff --git a/src/hci.h b/src/hci.h index 17b6409..ec2391c 100644 --- a/src/hci.h +++ b/src/hci.h @@ -22,6 +22,8 @@ #include /* IWYU pragma: keep */ #include +#include "ba-adapter.h" + int hci_get_version(int dev_id, struct hci_version *ver); /** @@ -36,7 +38,8 @@ int hci_get_version(int dev_id, struct hci_version *ver); int hci_sco_open(int dev_id); int hci_sco_connect(int sco_fd, const bdaddr_t *ba, uint16_t voice); -unsigned int hci_sco_get_mtu(int sco_fd, int hci_type); + +unsigned int hci_sco_get_mtu(int sco_fd, struct ba_adapter *a); #define BT_BCM_PARAM_ROUTING_PCM 0x0 #define BT_BCM_PARAM_ROUTING_TRANSPORT 0x1 diff --git a/src/main.c b/src/main.c index f0b276a..e93cb33 100644 --- a/src/main.c +++ b/src/main.c @@ -156,6 +156,7 @@ int main(int argc, char **argv) { { "codec", required_argument, NULL, 'c' }, { "initial-volume", required_argument, NULL, 17 }, { "keep-alive", required_argument, NULL, 8 }, + { "disable-realtek-usb-fix", no_argument, NULL, 21 }, { "a2dp-force-mono", no_argument, NULL, 6 }, { "a2dp-force-audio-cd", no_argument, NULL, 7 }, { "a2dp-volume", no_argument, NULL, 9 }, @@ -204,6 +205,7 @@ int main(int argc, char **argv) { " -c, --codec=NAME\t\tset enabled BT audio codecs\n" " --initial-volume=NUM\t\tinitial volume level [0-100]\n" " --keep-alive=SEC\t\tkeep Bluetooth transport alive\n" + " --disable-realtek-usb-fix\tdisable fix for mSBC on Realtek USB\n" " --a2dp-force-mono\t\ttry to force monophonic sound\n" " --a2dp-force-audio-cd\t\ttry to force 44.1 kHz sampling\n" " --a2dp-volume\t\t\tnative volume control by default\n" @@ -379,6 +381,10 @@ int main(int argc, char **argv) { config.keep_alive_time = atof(optarg) * 1000; break; + case 21 /* --disable-realtek-usb-fix */ : + config.disable_realtek_usb_fix = true; + break; + case 6 /* --a2dp-force-mono */ : config.a2dp.force_mono = true; break; diff --git a/src/ofono.c b/src/ofono.c index 4612469..6fff42a 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -32,7 +32,6 @@ #include #include -#include #include #include @@ -129,7 +128,7 @@ static int ofono_acquire_bt_sco(struct ba_transport *t) { #endif t->bt_fd = fd; - t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a->hci.type); + t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a); ba_transport_set_codec(t, codec); debug("New oFono SCO link (codec: %#x): %d", codec, fd); @@ -820,7 +819,7 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat debug("New oFono SCO link (codec: %#x): %d", codec, fd); t->bt_fd = fd; - t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a->hci.type); + t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a); ba_transport_set_codec(t, codec); pthread_mutex_unlock(&t->bt_fd_mtx); diff --git a/src/sco.c b/src/sco.c index f0cfa7a..857a28e 100644 --- a/src/sco.c +++ b/src/sco.c @@ -144,7 +144,7 @@ static void *sco_dispatcher_thread(struct ba_adapter *a) { pthread_mutex_lock(&t->bt_fd_mtx); t->bt_fd = fd; - t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, a->hci.type); + t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, a); fd = -1; pthread_mutex_unlock(&t->bt_fd_mtx); diff --git a/src/shared/bluetooth.h b/src/shared/bluetooth.h index 4c9636b..d74d129 100644 --- a/src/shared/bluetooth.h +++ b/src/shared/bluetooth.h @@ -21,6 +21,7 @@ #define BT_COMPID_BROADCOM 0x000F #define BT_COMPID_APPLE 0x004C #define BT_COMPID_APT 0x004F +#define BT_COMPID_REALTEK 0x005D #define BT_COMPID_SAMSUNG_ELEC 0x0075 #define BT_COMPID_QUALCOMM_TECH 0x00D7 #define BT_COMPID_SONY 0x012D diff --git a/test/Makefile.am b/test/Makefile.am index 83eef53..d309560 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -156,6 +156,7 @@ test_utils_SOURCES = \ ../src/shared/log.c \ ../src/shared/nv.c \ ../src/shared/rt.c \ + ../src/bluealsa-config.c \ ../src/hci.c \ ../src/utils.c \ test-utils.c commit 90cfbfae06742dfdd0da87d6dbcad5bae2578e8d Author: Arkadiusz Bokowy Date: Thu Jun 1 23:33:49 2023 +0200 Move common Bluetooth definitions to shared dir diff --git a/src/a2dp.c b/src/a2dp.c index abb22fa..65e1b6f 100644 --- a/src/a2dp.c +++ b/src/a2dp.c @@ -43,8 +43,8 @@ #include "a2dp-sbc.h" #include "bluealsa-config.h" #include "codec-sbc.h" -#include "hci.h" #include "shared/a2dp-codecs.h" +#include "shared/bluetooth.h" #include "shared/log.h" struct a2dp_codec * const a2dp_codecs[] = { diff --git a/src/bluez.c b/src/bluez.c index 66e85bf..3900d9d 100644 --- a/src/bluez.c +++ b/src/bluez.c @@ -43,6 +43,7 @@ #include "sco.h" #include "utils.h" #include "shared/a2dp-codecs.h" +#include "shared/bluetooth.h" #include "shared/defs.h" #include "shared/log.h" @@ -653,11 +654,11 @@ static void bluez_register_a2dp_all(struct ba_adapter *adapter) { switch (c->dir) { case A2DP_SOURCE: if (config.profile.a2dp_source && c->enabled) - bluez_register_a2dp(adapter, c, BLUETOOTH_UUID_A2DP_SOURCE); + bluez_register_a2dp(adapter, c, BT_UUID_A2DP_SOURCE); break; case A2DP_SINK: if (config.profile.a2dp_sink && c->enabled) - bluez_register_a2dp(adapter, c, BLUETOOTH_UUID_A2DP_SINK); + bluez_register_a2dp(adapter, c, BT_UUID_A2DP_SINK); break; } } @@ -1076,16 +1077,16 @@ fail: * this function will do nothing. */ static void bluez_register_hfp_all(void) { if (config.profile.hsp_hs) - bluez_register_hfp(BLUETOOTH_UUID_HSP_HS, BA_TRANSPORT_PROFILE_HSP_HS, + bluez_register_hfp(BT_UUID_HSP_HS, BA_TRANSPORT_PROFILE_HSP_HS, 0x0102 /* HSP 1.2 */, 0x1 /* remote audio volume control */); if (config.profile.hsp_ag) - bluez_register_hfp(BLUETOOTH_UUID_HSP_AG, BA_TRANSPORT_PROFILE_HSP_AG, + bluez_register_hfp(BT_UUID_HSP_AG, BA_TRANSPORT_PROFILE_HSP_AG, 0x0102 /* HSP 1.2 */, 0x0); if (config.profile.hfp_hf) - bluez_register_hfp(BLUETOOTH_UUID_HFP_HF, BA_TRANSPORT_PROFILE_HFP_HF, + bluez_register_hfp(BT_UUID_HFP_HF, BA_TRANSPORT_PROFILE_HFP_HF, 0x0107 /* HFP 1.7 */, config.hfp.features_sdp_hf); if (config.profile.hfp_ag) - bluez_register_hfp(BLUETOOTH_UUID_HFP_AG, BA_TRANSPORT_PROFILE_HFP_AG, + bluez_register_hfp(BT_UUID_HFP_AG, BA_TRANSPORT_PROFILE_HFP_AG, 0x0107 /* HFP 1.7 */, config.hfp.features_sdp_ag); } @@ -1099,17 +1100,17 @@ static void bluez_register(void) { bool enabled; bool global; } uuids[] = { - { BLUETOOTH_UUID_A2DP_SOURCE, BA_TRANSPORT_PROFILE_A2DP_SOURCE, + { BT_UUID_A2DP_SOURCE, BA_TRANSPORT_PROFILE_A2DP_SOURCE, config.profile.a2dp_source, false }, - { BLUETOOTH_UUID_A2DP_SINK, BA_TRANSPORT_PROFILE_A2DP_SINK, + { BT_UUID_A2DP_SINK, BA_TRANSPORT_PROFILE_A2DP_SINK, config.profile.a2dp_sink, false }, - { BLUETOOTH_UUID_HSP_HS, BA_TRANSPORT_PROFILE_HSP_HS, + { BT_UUID_HSP_HS, BA_TRANSPORT_PROFILE_HSP_HS, config.profile.hsp_hs, true }, - { BLUETOOTH_UUID_HSP_AG, BA_TRANSPORT_PROFILE_HSP_AG, + { BT_UUID_HSP_AG, BA_TRANSPORT_PROFILE_HSP_AG, config.profile.hsp_ag, true }, - { BLUETOOTH_UUID_HFP_HF, BA_TRANSPORT_PROFILE_HFP_HF, + { BT_UUID_HFP_HF, BA_TRANSPORT_PROFILE_HFP_HF, config.profile.hfp_hf, true }, - { BLUETOOTH_UUID_HFP_AG, BA_TRANSPORT_PROFILE_HFP_AG, + { BT_UUID_HFP_AG, BA_TRANSPORT_PROFILE_HFP_AG, config.profile.hfp_ag, true }, }; @@ -1238,7 +1239,7 @@ static void bluez_signal_interfaces_added(GDBusConnection *conn, const char *sen while (g_variant_iter_next(properties, "{&sv}", &property, &value)) { if (strcmp(property, "UUID") == 0) { const char *uuid = g_variant_get_string(value, NULL); - if (strcasecmp(uuid, BLUETOOTH_UUID_A2DP_SINK) == 0) + if (strcasecmp(uuid, BT_UUID_A2DP_SINK) == 0) sep.dir = A2DP_SINK; } else if (strcmp(property, "Codec") == 0) diff --git a/src/bluez.h b/src/bluez.h index f860655..a6e4eb7 100644 --- a/src/bluez.h +++ b/src/bluez.h @@ -1,6 +1,6 @@ /* * BlueALSA - bluez.h - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -19,14 +19,6 @@ #include "a2dp.h" #include "ba-device.h" -/* List of Bluetooth audio profiles. */ -#define BLUETOOTH_UUID_A2DP_SOURCE "0000110A-0000-1000-8000-00805F9B34FB" -#define BLUETOOTH_UUID_A2DP_SINK "0000110B-0000-1000-8000-00805F9B34FB" -#define BLUETOOTH_UUID_HSP_HS "00001108-0000-1000-8000-00805F9B34FB" -#define BLUETOOTH_UUID_HSP_AG "00001112-0000-1000-8000-00805F9B34FB" -#define BLUETOOTH_UUID_HFP_HF "0000111E-0000-1000-8000-00805F9B34FB" -#define BLUETOOTH_UUID_HFP_AG "0000111F-0000-1000-8000-00805F9B34FB" - #define BLUEZ_A2DP_VOLUME_MIN 0 #define BLUEZ_A2DP_VOLUME_MAX 127 diff --git a/src/hci.h b/src/hci.h index 739bb1d..17b6409 100644 --- a/src/hci.h +++ b/src/hci.h @@ -22,22 +22,6 @@ #include /* IWYU pragma: keep */ #include -/** - * List of all Bluetooth member companies: - * https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers */ - -#define BT_COMPID_INTEL 0x0002 -#define BT_COMPID_QUALCOMM_TECH_INTL 0x000A -#define BT_COMPID_BROADCOM 0x000F -#define BT_COMPID_APPLE 0x004C -#define BT_COMPID_APT 0x004F -#define BT_COMPID_SAMSUNG_ELEC 0x0075 -#define BT_COMPID_QUALCOMM_TECH 0x00D7 -#define BT_COMPID_SONY 0x012D -#define BT_COMPID_CYPRESS 0x0131 -#define BT_COMPID_SAVITECH 0x053A -#define BT_COMPID_FRAUNHOFER_IIS 0x08A9 - int hci_get_version(int dev_id, struct hci_version *ver); /** diff --git a/src/sco.c b/src/sco.c index 4b3132c..f0cfa7a 100644 --- a/src/sco.c +++ b/src/sco.c @@ -37,6 +37,7 @@ #include "hci.h" #include "hfp.h" #include "io.h" +#include "shared/bluetooth.h" #include "shared/defs.h" #include "shared/ffb.h" #include "shared/log.h" diff --git a/src/shared/a2dp-codecs.h b/src/shared/a2dp-codecs.h index ae2d3be..0c8bacc 100644 --- a/src/shared/a2dp-codecs.h +++ b/src/shared/a2dp-codecs.h @@ -34,7 +34,7 @@ #include #include -#include "hci.h" +#include "bluetooth.h" #define A2DP_CODEC_SBC 0x00 #define A2DP_CODEC_MPEG12 0x01 diff --git a/src/shared/bluetooth.h b/src/shared/bluetooth.h new file mode 100644 index 0000000..4c9636b --- /dev/null +++ b/src/shared/bluetooth.h @@ -0,0 +1,41 @@ +/* + * BlueALSA - bluetooth.h + * Copyright (c) 2016-2023 Arkadiusz Bokowy + * + * This file is a part of bluez-alsa. + * + * This project is licensed under the terms of the MIT license. + * + */ + +#pragma once +#ifndef BLUEALSA_SHARED_BLUETOOTH_H_ +#define BLUEALSA_SHARED_BLUETOOTH_H_ + +/** + * List of all Bluetooth member companies: + * https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers */ + +#define BT_COMPID_INTEL 0x0002 +#define BT_COMPID_QUALCOMM_TECH_INTL 0x000A +#define BT_COMPID_BROADCOM 0x000F +#define BT_COMPID_APPLE 0x004C +#define BT_COMPID_APT 0x004F +#define BT_COMPID_SAMSUNG_ELEC 0x0075 +#define BT_COMPID_QUALCOMM_TECH 0x00D7 +#define BT_COMPID_SONY 0x012D +#define BT_COMPID_CYPRESS 0x0131 +#define BT_COMPID_SAVITECH 0x053A +#define BT_COMPID_FRAUNHOFER_IIS 0x08A9 + +/** + * Bluetooth UUIDs associated with audio profiles. */ + +#define BT_UUID_A2DP_SOURCE "0000110A-0000-1000-8000-00805F9B34FB" +#define BT_UUID_A2DP_SINK "0000110B-0000-1000-8000-00805F9B34FB" +#define BT_UUID_HSP_HS "00001108-0000-1000-8000-00805F9B34FB" +#define BT_UUID_HSP_AG "00001112-0000-1000-8000-00805F9B34FB" +#define BT_UUID_HFP_HF "0000111E-0000-1000-8000-00805F9B34FB" +#define BT_UUID_HFP_AG "0000111F-0000-1000-8000-00805F9B34FB" + +#endif diff --git a/src/shared/log.h b/src/shared/log.h index a8fe550..4ac0ab0 100644 --- a/src/shared/log.h +++ b/src/shared/log.h @@ -20,7 +20,7 @@ #include #include -#include "shared/defs.h" +#include "defs.h" void log_open(const char *ident, bool syslog); void log_message(int priority, const char *format, ...) __attribute__ ((format(printf, 2, 3))); commit 2709a34ec1de2c842e350e2f7064b8d4885882bd Author: Arkadiusz Bokowy Date: Mon May 29 22:45:25 2023 +0200 Generate D-Bus interfaces with gdbus-codegen tool diff --git a/.github/workflows/code-scanning.yaml b/.github/workflows/code-scanning.yaml index 0962007..8fbc371 100644 --- a/.github/workflows/code-scanning.yaml +++ b/.github/workflows/code-scanning.yaml @@ -87,6 +87,7 @@ jobs: bear check iwyu libclang-13-dev + jq libasound2-dev libbluetooth-dev libbsd-dev @@ -128,6 +129,9 @@ jobs: run: bear -- make check TESTS= - name: Run IWYU Check run: | + jq 'del(.[] | select(.file | endswith("-iface.c")))' \ + ${{ github.workspace }}/build/compile_commands.json > tmp.json \ + && mv tmp.json ${{ github.workspace }}/build/compile_commands.json iwyu_tool -p ${{ github.workspace }}/build -- \ -Xiwyu --mapping_file=${{ github.workspace }}/.github/iwyu.imp \ -Xiwyu --keep=*/config.h \ diff --git a/configure.ac b/configure.ac index 1a9e1df..deeb17b 100644 --- a/configure.ac +++ b/configure.ac @@ -92,6 +92,9 @@ PKG_CHECK_MODULES([GIO2], [gio-unix-2.0]) PKG_CHECK_MODULES([GLIB2], [glib-2.0 >= 2.32]) PKG_CHECK_MODULES([SBC], [sbc >= 1.2]) +AC_PATH_PROGS([GDBUS_CODEGEN], [gdbus-codegen]) +AS_IF([test "x$GDBUS_CODEGEN" = "x"], [AC_MSG_ERROR([[gdbus-codegen not found]])]) + PKG_CHECK_MODULES([LIBBSD], [libbsd >= 0.8], AC_DEFINE([HAVE_LIBBSD], [1], [Define to 1 if you have the libbsd library.]), [:]) diff --git a/src/Makefile.am b/src/Makefile.am index df9b890..63bd676 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ # BlueALSA - Makefile.am -# Copyright (c) 2016-2022 Arkadiusz Bokowy +# Copyright (c) 2016-2023 Arkadiusz Bokowy bin_PROGRAMS = bluealsa SUBDIRS = asound @@ -8,6 +8,15 @@ dbusconfdir = @DBUS_CONF_DIR@ dbusbluealsauser = @BLUEALSA_USER@ dist_dbusconf_DATA = bluealsa.conf +BUILT_SOURCES = \ + bluealsa-iface.c \ + bluez-iface.c \ + ofono-iface.c + +MOSTLYCLEANFILES = \ + $(dist_dbusconf_DATA) \ + $(BUILT_SOURCES) + bluealsa_SOURCES = \ shared/a2dp-codecs.c \ shared/ffb.c \ @@ -24,10 +33,10 @@ bluealsa_SOURCES = \ ba-transport.c \ bluealsa-config.c \ bluealsa-dbus.c \ - bluealsa-iface.c \ + bluealsa-iface.xml \ bluealsa-skeleton.c \ bluez.c \ - bluez-iface.c \ + bluez-iface.xml \ bluez-skeleton.c \ codec-sbc.c \ dbus.c \ @@ -88,7 +97,7 @@ endif if ENABLE_OFONO bluealsa_SOURCES += \ ofono.c \ - ofono-iface.c \ + ofono-iface.xml \ ofono-skeleton.c endif @@ -131,10 +140,12 @@ LDADD = \ @SPANDSP_LIBS@ SUFFIXES = .conf.in .conf -MOSTLYCLEANFILES = $(dist_dbusconf_DATA) DBUSCONF_SUBS = \ s,[@]bluealsauser[@],$(dbusbluealsauser),g; .conf.in.conf: $(SED) -e '$(DBUSCONF_SUBS)' < $< > $@ + +.xml.c: + $(GDBUS_CODEGEN) --interface-info-body --output $@ $< diff --git a/src/bluealsa-iface.c b/src/bluealsa-iface.c deleted file mode 100644 index 7106184..0000000 --- a/src/bluealsa-iface.c +++ /dev/null @@ -1,233 +0,0 @@ -/* - * BlueALSA - bluealsa-iface.c - * Copyright (c) 2016-2022 Arkadiusz Bokowy - * - * This file is a part of bluez-alsa. - * - * This project is licensed under the terms of the MIT license. - * - */ - -#include "bluealsa-iface.h" - -#include - -static const GDBusArgInfo arg_codec = { - -1, "codec", "s", NULL -}; - -static const GDBusArgInfo arg_codecs = { - -1, "codecs", "a{sa{sv}}", NULL -}; - -static const GDBusArgInfo arg_fd = { - -1, "fd", "h", NULL -}; - -static const GDBusArgInfo arg_props = { - -1, "props", "a{sv}", NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_manager_Version = { - -1, "Version", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_manager_Adapters = { - -1, "Adapters", "as", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_manager_Profiles = { - -1, "Profiles", "as", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_manager_Codecs = { - -1, "Codecs", "as", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo *bluealsa_iface_manager_properties[] = { - &bluealsa_iface_manager_Version, - &bluealsa_iface_manager_Adapters, - &bluealsa_iface_manager_Profiles, - &bluealsa_iface_manager_Codecs, - NULL, -}; - -static const GDBusArgInfo *pcm_Open_out[] = { - &arg_fd, - &arg_fd, - NULL, -}; - -static const GDBusArgInfo *pcm_GetCodecs_out[] = { - &arg_codecs, - NULL, -}; - -static const GDBusArgInfo *pcm_SelectCodec_in[] = { - &arg_codec, - &arg_props, - NULL, -}; - -static const GDBusMethodInfo bluealsa_iface_pcm_Open = { - -1, "Open", - NULL, - (GDBusArgInfo **)pcm_Open_out, - NULL, -}; - -static const GDBusMethodInfo bluealsa_iface_pcm_GetCodecs = { - -1, "GetCodecs", - NULL, - (GDBusArgInfo **)pcm_GetCodecs_out, - NULL, -}; - -static const GDBusMethodInfo bluealsa_iface_pcm_SelectCodec = { - -1, "SelectCodec", - (GDBusArgInfo **)pcm_SelectCodec_in, - NULL, - NULL, -}; - -static const GDBusMethodInfo *bluealsa_iface_pcm_methods[] = { - &bluealsa_iface_pcm_Open, - &bluealsa_iface_pcm_GetCodecs, - &bluealsa_iface_pcm_SelectCodec, - NULL, -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Device = { - -1, "Device", "o", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Sequence = { - -1, "Sequence", "u", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Transport = { - -1, "Transport", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Mode = { - -1, "Mode", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Running = { - -1, "Running", "b", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Format = { - -1, "Format", "q", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Channels = { - -1, "Channels", "y", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Sampling = { - -1, "Sampling", "u", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Codec = { - -1, "Codec", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_CodecConfiguration = { - -1, "CodecConfiguration", "ay", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Delay = { - -1, "Delay", "q", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_SoftVolume = { - -1, "SoftVolume", "b", - G_DBUS_PROPERTY_INFO_FLAGS_READABLE | - G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, - NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_pcm_Volume = { - -1, "Volume", "q", - G_DBUS_PROPERTY_INFO_FLAGS_READABLE | - G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, - NULL -}; - -static const GDBusPropertyInfo *bluealsa_iface_pcm_properties[] = { - &bluealsa_iface_pcm_Device, - &bluealsa_iface_pcm_Sequence, - &bluealsa_iface_pcm_Transport, - &bluealsa_iface_pcm_Mode, - &bluealsa_iface_pcm_Running, - &bluealsa_iface_pcm_Format, - &bluealsa_iface_pcm_Channels, - &bluealsa_iface_pcm_Sampling, - &bluealsa_iface_pcm_Codec, - &bluealsa_iface_pcm_CodecConfiguration, - &bluealsa_iface_pcm_Delay, - &bluealsa_iface_pcm_SoftVolume, - &bluealsa_iface_pcm_Volume, - NULL, -}; - -static const GDBusArgInfo *rfcomm_Open_out[] = { - &arg_fd, - NULL, -}; - -static const GDBusMethodInfo bluealsa_iface_rfcomm_Open = { - -1, "Open", - NULL, - (GDBusArgInfo **)rfcomm_Open_out, - NULL, -}; - -static const GDBusMethodInfo *bluealsa_iface_rfcomm_methods[] = { - &bluealsa_iface_rfcomm_Open, - NULL, -}; - -static const GDBusPropertyInfo bluealsa_iface_rfcomm_Transport = { - -1, "Transport", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_rfcomm_Features = { - -1, "Features", "as", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluealsa_iface_rfcomm_Battery = { - -1, "Battery", "y", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo *bluealsa_iface_rfcomm_properties[] = { - &bluealsa_iface_rfcomm_Transport, - &bluealsa_iface_rfcomm_Features, - &bluealsa_iface_rfcomm_Battery, - NULL, -}; - -const GDBusInterfaceInfo bluealsa_iface_manager = { - -1, BLUEALSA_IFACE_MANAGER, - NULL, - NULL, - (GDBusPropertyInfo **)bluealsa_iface_manager_properties, - NULL, -}; - -const GDBusInterfaceInfo bluealsa_iface_pcm = { - -1, BLUEALSA_IFACE_PCM, - (GDBusMethodInfo **)bluealsa_iface_pcm_methods, - NULL, - (GDBusPropertyInfo **)bluealsa_iface_pcm_properties, - NULL, -}; - -const GDBusInterfaceInfo bluealsa_iface_rfcomm = { - -1, BLUEALSA_IFACE_RFCOMM, - (GDBusMethodInfo **)bluealsa_iface_rfcomm_methods, - NULL, - (GDBusPropertyInfo **)bluealsa_iface_rfcomm_properties, - NULL, -}; diff --git a/src/bluealsa-iface.h b/src/bluealsa-iface.h index bd11cad..d76474b 100644 --- a/src/bluealsa-iface.h +++ b/src/bluealsa-iface.h @@ -1,6 +1,6 @@ /* * BlueALSA - bluealsa-iface.h - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -39,8 +39,8 @@ #define BLUEALSA_PCM_MODE_SINK "sink" #define BLUEALSA_PCM_MODE_SOURCE "source" -extern const GDBusInterfaceInfo bluealsa_iface_manager; -extern const GDBusInterfaceInfo bluealsa_iface_pcm; -extern const GDBusInterfaceInfo bluealsa_iface_rfcomm; +extern const GDBusInterfaceInfo org_bluealsa_manager1_interface; +extern const GDBusInterfaceInfo org_bluealsa_pcm1_interface; +extern const GDBusInterfaceInfo org_bluealsa_rfcomm1_interface; #endif diff --git a/src/bluealsa-iface.xml b/src/bluealsa-iface.xml new file mode 100644 index 0000000..ef14291 --- /dev/null +++ b/src/bluealsa-iface.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bluealsa-skeleton.c b/src/bluealsa-skeleton.c index 40aa43e..d0f7bd3 100644 --- a/src/bluealsa-skeleton.c +++ b/src/bluealsa-skeleton.c @@ -1,6 +1,6 @@ /* * BlueALSA - bluealsa-skeleton.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -43,7 +43,7 @@ bluealsa_ManagerIfaceSkeleton *bluealsa_manager_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = bluealsa_manager_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&bluealsa_iface_manager, + (GDBusInterfaceInfo *)&org_bluealsa_manager1_interface, vtable, userdata, userdata_free_func); } @@ -75,7 +75,7 @@ bluealsa_PCMIfaceSkeleton *bluealsa_pcm_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = bluealsa_pcm_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&bluealsa_iface_pcm, + (GDBusInterfaceInfo *)&org_bluealsa_pcm1_interface, vtable, userdata, userdata_free_func); } @@ -106,6 +106,6 @@ bluealsa_RFCOMMIfaceSkeleton *bluealsa_rfcomm_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = bluealsa_rfcomm_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&bluealsa_iface_rfcomm, + (GDBusInterfaceInfo *)&org_bluealsa_rfcomm1_interface, vtable, userdata, userdata_free_func); } diff --git a/src/bluez-iface.c b/src/bluez-iface.c deleted file mode 100644 index 9036bdd..0000000 --- a/src/bluez-iface.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * BlueALSA - bluez-iface.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy - * - * This file is a part of bluez-alsa. - * - * This project is licensed under the terms of the MIT license. - * - */ - -#include "bluez-iface.h" - -#include - -static const GDBusArgInfo arg_device = { - -1, "device", "o", NULL -}; - -static const GDBusArgInfo arg_transport = { - -1, "transport", "o", NULL -}; - -static const GDBusArgInfo arg_fd = { - -1, "fd", "h", NULL -}; - -static const GDBusArgInfo arg_capabilities = { - -1, "capabilities", "ay", NULL -}; - -static const GDBusArgInfo arg_properties = { - -1, "properties", "a{sv}", NULL -}; - -static const GDBusArgInfo arg_fd_properties = { - -1, "fd_properties", "a{sv}", NULL -}; - -static const GDBusArgInfo *in_SelectConfiguration[] = { - &arg_capabilities, - NULL, -}; - -static const GDBusArgInfo *out_SelectConfiguration[] = { - &arg_capabilities, - NULL, -}; - -static const GDBusArgInfo *in_SetConfiguration[] = { - &arg_transport, - &arg_properties, - NULL, -}; - -static const GDBusArgInfo *in_ClearConfiguration[] = { - &arg_transport, - NULL, -}; - -static const GDBusArgInfo *in_NewConnection[] = { - &arg_device, - &arg_fd, - &arg_fd_properties, - NULL, -}; - -static const GDBusArgInfo *in_RequestDisconnection[] = { - &arg_device, - NULL, -}; - -static const GDBusPropertyInfo bluez_iface_battery_provider_Device = { - -1, "Device", "o", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluez_iface_battery_provider_Percentage = { - -1, "Percentage", "y", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusPropertyInfo bluez_iface_battery_provider_Source = { - -1, "Source", "s", G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL -}; - -static const GDBusMethodInfo bluez_iface_endpoint_SelectConfiguration = { - -1, "SelectConfiguration", - (GDBusArgInfo **)in_SelectConfiguration, - (GDBusArgInfo **)out_SelectConfiguration, - NULL, -}; - -static const GDBusMethodInfo bluez_iface_endpoint_SetConfiguration = { - -1, "SetConfiguration", - (GDBusArgInfo **)in_SetConfiguration, - NULL, - NULL, -}; - -static const GDBusMethodInfo bluez_iface_endpoint_ClearConfiguration = { - -1, "ClearConfiguration", - (GDBusArgInfo **)in_ClearConfiguration, - NULL, - NULL, -}; - -static const GDBusMethodInfo bluez_iface_endpoint_Release = { - -1, "Release", - NULL, - NULL, - NULL, -}; - -static const GDBusMethodInfo bluez_iface_profile_NewConnection = { - -1, "NewConnection", - (GDBusArgInfo **)in_NewConnection, - NULL, - NULL, -}; - -static const GDBusMethodInfo bluez_iface_profile_RequestDisconnection = { - -1, "RequestDisconnection", - (GDBusArgInfo **)in_RequestDisconnection, - NULL, - NULL, -}; - -static const GDBusMethodInfo bluez_iface_profile_Release = { - -1, "Release", - NULL, - NULL, - NULL, -}; - -static const GDBusPropertyInfo *bluez_iface_battery_provider_properties[] = { - &bluez_iface_battery_provider_Device, - &bluez_iface_battery_provider_Percentage, - &bluez_iface_battery_provider_Source, - NULL, -}; - -static const GDBusMethodInfo *bluez_iface_endpoint_methods[] = { - &bluez_iface_endpoint_SelectConfiguration, - &bluez_iface_endpoint_SetConfiguration, - &bluez_iface_endpoint_ClearConfiguration, - &bluez_iface_endpoint_Release, - NULL, -}; - -static const GDBusMethodInfo *bluez_iface_profile_methods[] = { - &bluez_iface_profile_NewConnection, - &bluez_iface_profile_RequestDisconnection, - &bluez_iface_profile_Release, - NULL, -}; - -const GDBusInterfaceInfo bluez_iface_battery_provider = { - -1, BLUEZ_IFACE_BATTERY_PROVIDER, - NULL, - NULL, - (GDBusPropertyInfo **)bluez_iface_battery_provider_properties, - NULL, -}; - -const GDBusInterfaceInfo bluez_iface_media_endpoint = { - -1, BLUEZ_IFACE_MEDIA_ENDPOINT, - (GDBusMethodInfo **)bluez_iface_endpoint_methods, - NULL, - NULL, - NULL, -}; - -const GDBusInterfaceInfo bluez_iface_profile = { - -1, BLUEZ_IFACE_PROFILE, - (GDBusMethodInfo **)bluez_iface_profile_methods, - NULL, - NULL, - NULL, -}; diff --git a/src/bluez-iface.h b/src/bluez-iface.h index 7cf57e5..6db0d56 100644 --- a/src/bluez-iface.h +++ b/src/bluez-iface.h @@ -1,6 +1,6 @@ /* * BlueALSA - bluez-iface.h - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -30,8 +30,8 @@ #define BLUEZ_TRANSPORT_STATE_PENDING "pending" #define BLUEZ_TRANSPORT_STATE_ACTIVE "active" -extern const GDBusInterfaceInfo bluez_iface_battery_provider; -extern const GDBusInterfaceInfo bluez_iface_media_endpoint; -extern const GDBusInterfaceInfo bluez_iface_profile; +extern const GDBusInterfaceInfo org_bluez_battery_provider1_interface; +extern const GDBusInterfaceInfo org_bluez_media_endpoint1_interface; +extern const GDBusInterfaceInfo org_bluez_profile1_interface; #endif diff --git a/src/bluez-iface.xml b/src/bluez-iface.xml new file mode 100644 index 0000000..83568ce --- /dev/null +++ b/src/bluez-iface.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bluez-skeleton.c b/src/bluez-skeleton.c index 2951e47..e45f6a9 100644 --- a/src/bluez-skeleton.c +++ b/src/bluez-skeleton.c @@ -1,6 +1,6 @@ /* * BlueALSA - bluez-skeleton.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -43,7 +43,7 @@ bluez_BatteryProviderIfaceSkeleton *bluez_battery_provider_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = bluez_battery_provider_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&bluez_iface_battery_provider, + (GDBusInterfaceInfo *)&org_bluez_battery_provider1_interface, vtable, userdata, userdata_free_func); } @@ -74,7 +74,7 @@ bluez_MediaEndpointIfaceSkeleton *bluez_media_endpoint_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = bluez_media_endpoint_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&bluez_iface_media_endpoint, + (GDBusInterfaceInfo *)&org_bluez_media_endpoint1_interface, vtable, userdata, userdata_free_func); } @@ -105,6 +105,6 @@ bluez_ProfileIfaceSkeleton *bluez_profile_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = bluez_profile_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&bluez_iface_profile, + (GDBusInterfaceInfo *)&org_bluez_profile1_interface, vtable, userdata, userdata_free_func); } diff --git a/src/ofono-iface.c b/src/ofono-iface.c deleted file mode 100644 index 0927a1e..0000000 --- a/src/ofono-iface.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * BlueALSA - ofono-iface.c - * Copyright (c) 2018 Thierry Bultel (thierry.bultel@iot.bzh) - * - * This file is a part of bluez-alsa. - * - * This project is licensed under the terms of the MIT license. - * - */ - -#include "ofono-iface.h" - -#include - -static const GDBusArgInfo arg_card = { - -1, "card", "o", NULL -}; - -static const GDBusArgInfo arg_fd = { - -1, "fd", "h", NULL -}; - -static const GDBusArgInfo arg_codec = { - -1, "codec", "y", NULL -}; - -static const GDBusArgInfo *in_NewConnection[] = { - &arg_card, - &arg_fd, - &arg_codec, - NULL, -}; - -static const GDBusMethodInfo ofono_iface_hf_audio_agent_NewConnection = { - -1, "NewConnection", - (GDBusArgInfo **)in_NewConnection, - NULL, - NULL, -}; - -static const GDBusMethodInfo ofono_iface_hf_audio_agent_Release = { - -1, "Release", - NULL, - NULL, - NULL, -}; - -static const GDBusMethodInfo *ofono_iface_hf_audio_agent_methods[] = { - &ofono_iface_hf_audio_agent_NewConnection, - &ofono_iface_hf_audio_agent_Release, - NULL, -}; - - -const GDBusInterfaceInfo ofono_iface_hf_audio_agent = { - -1, OFONO_IFACE_HF_AUDIO_AGENT, - (GDBusMethodInfo **)ofono_iface_hf_audio_agent_methods, - NULL, - NULL, - NULL, -}; diff --git a/src/ofono-iface.h b/src/ofono-iface.h index 9be6811..7f8dd2a 100644 --- a/src/ofono-iface.h +++ b/src/ofono-iface.h @@ -32,6 +32,6 @@ #define OFONO_MODEM_TYPE_HFP "hfp" #define OFONO_MODEM_TYPE_SAP "sap" -extern const GDBusInterfaceInfo ofono_iface_hf_audio_agent; +extern const GDBusInterfaceInfo org_ofono_handsfree_audio_agent_interface; #endif diff --git a/src/ofono-iface.xml b/src/ofono-iface.xml new file mode 100644 index 0000000..5aab268 --- /dev/null +++ b/src/ofono-iface.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/src/ofono-skeleton.c b/src/ofono-skeleton.c index 587cf11..138d127 100644 --- a/src/ofono-skeleton.c +++ b/src/ofono-skeleton.c @@ -43,6 +43,6 @@ ofono_HFAudioAgentIfaceSkeleton *ofono_hf_audio_agent_iface_skeleton_new( GDestroyNotify userdata_free_func) { const GType type = ofono_hf_audio_agent_iface_skeleton_get_type(); return g_dbus_interface_skeleton_ex_new(type, - (GDBusInterfaceInfo *)&ofono_iface_hf_audio_agent, + (GDBusInterfaceInfo *)&org_ofono_handsfree_audio_agent_interface, vtable, userdata, userdata_free_func); } commit ae53d4e5236f55a87edc40ccf5745c3f7f38253b Author: Arkadiusz Bokowy Date: Sun May 28 07:26:43 2023 +0200 Release resources in hash table destroy function diff --git a/src/bluez.c b/src/bluez.c index d8e17ea..66e85bf 100644 --- a/src/bluez.c +++ b/src/bluez.c @@ -119,6 +119,10 @@ static void bluez_dbus_object_data_unref( struct bluez_dbus_object_data *obj) { if (atomic_fetch_sub_explicit(&obj->ref_count, 1, memory_order_relaxed) > 1) return; + if (obj->ifs != NULL) { + g_dbus_interface_skeleton_unexport(obj->ifs); + g_object_unref(obj->ifs); + } free(obj); } @@ -1330,12 +1334,11 @@ static void bluez_signal_interfaces_removed(GDBusConnection *conn, const char *s GHashTableIter iter; struct bluez_dbus_object_data *dbus_obj; g_hash_table_iter_init(&iter, dbus_object_data_map); - while (g_hash_table_iter_next(&iter, NULL, (gpointer)&dbus_obj)) - if (dbus_obj->hci_dev_id == hci_dev_id) { - g_dbus_interface_skeleton_unexport(dbus_obj->ifs); - g_object_unref(dbus_obj->ifs); - g_hash_table_iter_remove(&iter); - } + while (g_hash_table_iter_next(&iter, NULL, (gpointer)&dbus_obj)) { + if (dbus_obj->hci_dev_id != hci_dev_id) + continue; + g_hash_table_iter_remove(&iter); + } bluez_adapter_free(&bluez_adapters[hci_dev_id]); @@ -1458,17 +1461,9 @@ static void bluez_disappeared(GDBusConnection *conn, const char *name, pthread_mutex_lock(&bluez_mutex); - GHashTableIter iter; - struct bluez_dbus_object_data *dbus_obj; - g_hash_table_iter_init(&iter, dbus_object_data_map); - while (g_hash_table_iter_next(&iter, NULL, (gpointer)&dbus_obj)) { - g_dbus_interface_skeleton_unexport(dbus_obj->ifs); - g_object_unref(dbus_obj->ifs); - g_hash_table_iter_remove(&iter); - } + g_hash_table_remove_all(dbus_object_data_map); - size_t i; - for (i = 0; i < ARRAYSIZE(bluez_adapters); i++) + for (size_t i = 0; i < ARRAYSIZE(bluez_adapters); i++) bluez_adapter_free(&bluez_adapters[i]); pthread_mutex_unlock(&bluez_mutex); commit 165e15bce4f9c8dd6238ee8d105ab9fa857c63a1 Author: Arkadiusz Bokowy Date: Tue May 23 22:53:05 2023 +0200 Minor version bump (release 4.1.0) diff --git a/.github/spellcheck-wordlist.txt b/.github/spellcheck-wordlist.txt index 4db8f6d..49b1f55 100644 --- a/.github/spellcheck-wordlist.txt +++ b/.github/spellcheck-wordlist.txt @@ -45,6 +45,7 @@ SRC SRV TLV TODO +TSAN UI UUID UUIDs diff --git a/AUTHORS b/AUTHORS index 1e748ce..dbf665e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Lars Wendler mcz Michał Kępień Ming Liu +Nicolas Cavallari Parthiban Nallathambi René Rebe Sebastian Würl diff --git a/LICENSE b/LICENSE index 23b4a7b..95a5c55 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2016-2022 Arkadiusz Bokowy +Copyright (c) 2016-2023 Arkadiusz Bokowy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NEWS b/NEWS index f230fe2..c0493e7 100644 --- a/NEWS +++ b/NEWS @@ -1,14 +1,23 @@ unreleased ========== +bluez-alsa v4.1.0 (2023-05-23) +============================== + +- removed deprecated org.bluealsa.Manger1 D-Bus interface - persistent storage for PCM volume and mute state - PCM volume control with oFono HFP-AG and HFP-HF profiles -- export transport running state via PCM D-Bus interface +- transport running state exported in PCM D-Bus interface +- A2DP codec configuration blob exported in PCM D-Bus interface - optional non-dynamic operation mode for ALSA control plug-in - optional extended controls for ALSA control plug-in -- change RFCOMM D-Bus API features property to array of strings +- changed RFCOMM D-Bus API features property to array of strings - fix for SCO link establishment for oFono HFP-AG profile - fix for volume control for HSP-HS and HFP-HF profiles +- stability fixes for ALSA PCM I/O and control plug-ins +- bluealsa-aplay: fix for volume synchronization +- lots of fixes for race conditions (TSAN) +- lots of updates to the manual pages bluez-alsa v4.0.0 (2022-06-03) ============================== diff --git a/configure.ac b/configure.ac index 4231609..1a9e1df 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) AC_INIT([BlueALSA], - [m4_normalize(esyscmd([test -d .git && git describe --always --dirty || echo v4.0.0]))], + [m4_normalize(esyscmd([test -d .git && git describe --always --dirty || echo v4.1.0]))], [arkadiusz.bokowy@gmail.com], [bluez-alsa], [https://github.com/arkq/bluez-alsa]) AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror]) diff --git a/doc/bluealsa-aplay.1.rst b/doc/bluealsa-aplay.1.rst index 24266f1..e4c21a5 100644 --- a/doc/bluealsa-aplay.1.rst +++ b/doc/bluealsa-aplay.1.rst @@ -263,4 +263,4 @@ SEE ALSO ``amixer(1)``, ``aplay(1)``, ``bluealsa-rfcomm(1)``, ``bluealsa(8)`` Project web site - https://github.com/Arkq/bluez-alsa + https://github.com/arkq/bluez-alsa diff --git a/doc/bluealsa-cli.1.rst b/doc/bluealsa-cli.1.rst index fa8b42b..e2abaff 100644 --- a/doc/bluealsa-cli.1.rst +++ b/doc/bluealsa-cli.1.rst @@ -218,4 +218,4 @@ SEE ALSO ``bluealsa(8)``, ``bluealsa-aplay(1)``, ``bluealsa-rfcomm(1)`` Project web site - https://github.com/Arkq/bluez-alsa + https://github.com/arkq/bluez-alsa diff --git a/doc/bluealsa-plugins.7.rst b/doc/bluealsa-plugins.7.rst index c0327b1..7d8e9d1 100644 --- a/doc/bluealsa-plugins.7.rst +++ b/doc/bluealsa-plugins.7.rst @@ -561,7 +561,7 @@ SEE ALSO ``bluealsa(8)``, ``bluetoothd(8)`` Project web site - https://github.com/Arkq/bluez-alsa + https://github.com/arkq/bluez-alsa ALSA configuration file syntax https://www.alsa-project.org/alsa-doc/alsa-lib/conf.html diff --git a/doc/bluealsa-rfcomm.1.rst b/doc/bluealsa-rfcomm.1.rst index b2b9523..1ffdbc3 100644 --- a/doc/bluealsa-rfcomm.1.rst +++ b/doc/bluealsa-rfcomm.1.rst @@ -64,4 +64,4 @@ SEE ALSO ``bluealsa-aplay(1)`` ``bluealsa(8)`` Project web site - https://github.com/Arkq/bluez-alsa + https://github.com/arkq/bluez-alsa diff --git a/doc/bluealsa.8.rst b/doc/bluealsa.8.rst index cdbfed7..57fc89d 100644 --- a/doc/bluealsa.8.rst +++ b/doc/bluealsa.8.rst @@ -442,4 +442,4 @@ SEE ALSO ``bluetoothctl(1)``, ``bluealsa-plugins(7)``, ``bluetoothd(8)`` Project web site - https://github.com/Arkq/bluez-alsa + https://github.com/arkq/bluez-alsa diff --git a/doc/hcitop.1.rst b/doc/hcitop.1.rst index 716d82a..272b640 100644 --- a/doc/hcitop.1.rst +++ b/doc/hcitop.1.rst @@ -104,4 +104,4 @@ SEE ALSO ``btmon(1)``, ``hciconfig(1)``, ``hcitool(1)`` Project web site - https://github.com/Arkq/bluez-alsa + https://github.com/arkq/bluez-alsa commit e3fce503127c4e152780684b6ddd3a76fbc77197 Author: Arkadiusz Bokowy Date: Mon May 22 22:09:01 2023 +0200 Fix stack overflow introduced in 76f2dae diff --git a/utils/aplay/aplay.c b/utils/aplay/aplay.c index 8162a48..e329c9b 100644 --- a/utils/aplay/aplay.c +++ b/utils/aplay/aplay.c @@ -12,7 +12,6 @@ # include #endif -#include #include #include #include @@ -531,15 +530,16 @@ static void *io_worker_routine(struct io_worker *w) { single_playback_mutex_locked = false; } - struct pollfd *fds; + struct pollfd fds[16] = {{ w->ba_pcm_fd, POLLIN, 0 }}; nfds_t nfds = 1; if (w->snd_mixer != NULL) nfds += snd_mixer_poll_descriptors_count(w->snd_mixer); - fds = alloca(sizeof(*fds) * nfds); - fds[0].fd = w->ba_pcm_fd; - fds[0].events = POLLIN; + if (nfds > ARRAYSIZE(fds)) { + error("Poll FD array size exceeded: %zu > %zu", nfds, ARRAYSIZE(fds)); + goto fail; + } if (w->snd_mixer != NULL) snd_mixer_poll_descriptors(w->snd_mixer, fds + 1, nfds - 1); @@ -1171,8 +1171,7 @@ int main(int argc, char *argv[]) { if (!bluealsa_dbus_get_pcms(&dbus_ctx, &ba_pcms, &ba_pcms_count, &err)) warn("Couldn't get BlueALSA PCM list: %s", err.message); - size_t i; - for (i = 0; i < ba_pcms_count; i++) + for (size_t i = 0; i < ba_pcms_count; i++) supervise_io_worker(&ba_pcms[i]); struct sigaction sigact = { .sa_handler = main_loop_stop }; @@ -1200,5 +1199,10 @@ int main(int argc, char *argv[]) { } + for (size_t i = 0; i < workers_count; i++) + io_worker_stop(i); + free(workers); + + bluealsa_dbus_connection_ctx_free(&dbus_ctx); return EXIT_SUCCESS; } commit 76f2dae1870570a226e47bbda141661257d58758 Author: Arkadiusz Bokowy Date: Sat May 20 09:17:33 2023 +0200 Synchronize ALSA mixer volume with BlueALSA PCM diff --git a/src/asound/bluealsa-pcm.c b/src/asound/bluealsa-pcm.c index 3367416..5556cb3 100644 --- a/src/asound/bluealsa-pcm.c +++ b/src/asound/bluealsa-pcm.c @@ -912,6 +912,9 @@ static int bluealsa_poll_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfd, unsigned int nfds) { struct bluealsa_pcm *pcm = io->private_data; + if (nfds < 1) + return -EINVAL; + nfds_t dbus_nfds = nfds - 1; if (!bluealsa_dbus_connection_poll_fds(&pcm->dbus_ctx, &pfd[1], &dbus_nfds)) return -EINVAL; @@ -930,6 +933,9 @@ static int bluealsa_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, *revents = 0; int ret = 0; + if (nfds < 1) + return -EINVAL; + bluealsa_dbus_connection_poll_dispatch(&pcm->dbus_ctx, &pfd[1], nfds - 1); while (dbus_connection_dispatch(pcm->dbus_ctx.conn) == DBUS_DISPATCH_DATA_REMAINS) continue; diff --git a/test/test-utils-aplay.c b/test/test-utils-aplay.c index 378db31..365f7ad 100644 --- a/test/test-utils-aplay.c +++ b/test/test-utils-aplay.c @@ -230,6 +230,41 @@ CK_START_TEST(test_play_single_audio) { } CK_END_TEST +CK_START_TEST(test_play_mixer_setup) { + + struct spawn_process sp_ba_mock; + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, + "--device-name=23:45:67:89:AB:CD:Headset", + "--profile=hsp-ag", + NULL), -1); + + struct spawn_process sp_ba_aplay; + ck_assert_int_ne(spawn_bluealsa_aplay(&sp_ba_aplay, + "--profile-sco", + "--pcm=bluealsa:PROFILE=sco", + "--mixer-device=bluealsa:DEV=23:45:67:89:AB:CD", + "--mixer-name=SCO", + "-v", + NULL), -1); + spawn_terminate(&sp_ba_aplay, 500); + + char output[4096] = ""; + ck_assert_int_gt(fread(output, 1, sizeof(output) - 1, sp_ba_aplay.f_stderr), 0); + fprintf(stderr, "%s", output); + +#if DEBUG + ck_assert_ptr_ne(strstr(output, + "Opening ALSA mixer: name=bluealsa:DEV=23:45:67:89:AB:CD elem=SCO index=0"), NULL); + ck_assert_ptr_ne(strstr(output, + "Setting up ALSA mixer volume synchronization"), NULL); +#endif + + spawn_close(&sp_ba_aplay, NULL); + spawn_terminate(&sp_ba_mock, 0); + spawn_close(&sp_ba_mock, NULL); + +} CK_END_TEST + int main(int argc, char *argv[], char *envp[]) { preload(argc, argv, envp, ".libs/aloader.so"); @@ -253,6 +288,7 @@ int main(int argc, char *argv[], char *envp[]) { tcase_add_test(tc, test_list_pcms); tcase_add_test(tc, test_play_all); tcase_add_test(tc, test_play_single_audio); + tcase_add_test(tc, test_play_mixer_setup); srunner_run_all(sr, CK_ENV); int nf = srunner_ntests_failed(sr); diff --git a/utils/aplay/aplay.c b/utils/aplay/aplay.c index fd5e7fd..8162a48 100644 --- a/utils/aplay/aplay.c +++ b/utils/aplay/aplay.c @@ -12,6 +12,7 @@ # include #endif +#include #include #include #include @@ -307,21 +308,12 @@ final: } /** - * Synchronize BlueALSA PCM volume with ALSA mixer element. */ -static int io_worker_mixer_volume_sync( + * Update BlueALSA PCM volume according to ALSA mixer element. */ +static int io_worker_mixer_volume_sync_ba_pcm( struct io_worker *worker, struct ba_pcm *ba_pcm) { - /* skip sync in case of software volume */ - if (ba_pcm->soft_volume) - return 0; - snd_mixer_elem_t *elem = worker->snd_mixer_elem; - if (elem == NULL) - return 0; - - debug("Synchronizing volume: %s", ba_pcm->device_path); - const int vmax = BA_PCM_VOLUME_MAX(ba_pcm); long long volume_db_sum = 0; bool muted = true; @@ -382,7 +374,7 @@ static int io_worker_mixer_volume_sync( /** * Update ALSA mixer element according to BlueALSA PCM volume. */ -static int io_worker_mixer_volume_update( +static int io_worker_mixer_volume_sync_snd_mixer_elem( struct io_worker *worker, struct ba_pcm *ba_pcm) { @@ -432,6 +424,37 @@ static int io_worker_mixer_volume_update( return 0; } +int io_worker_mixer_elem_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct io_worker *worker = snd_mixer_elem_get_callback_private(elem); + if (mask & SND_CTL_EVENT_MASK_VALUE) + io_worker_mixer_volume_sync_ba_pcm(worker, &worker->ba_pcm); + return 0; +} + +/** + * Setup volume synchronization between ALSA mixer and BlueALSA PCM. */ +static int io_worker_mixer_volume_sync_setup( + struct io_worker *worker) { + + /* skip setup in case of software volume */ + if (worker->ba_pcm.soft_volume) + return 0; + + snd_mixer_elem_t *elem = worker->snd_mixer_elem; + if (elem == NULL) + return 0; + + debug("Setting up ALSA mixer volume synchronization"); + + snd_mixer_elem_set_callback(elem, io_worker_mixer_elem_callback); + snd_mixer_elem_set_callback_private(elem, worker); + + /* initial synchronization */ + io_worker_mixer_volume_sync_ba_pcm(worker, &worker->ba_pcm); + + return 0; +} + static void io_worker_routine_exit(struct io_worker *worker) { if (worker->ba_pcm_fd != -1) { close(worker->ba_pcm_fd); @@ -498,7 +521,6 @@ static void *io_worker_routine(struct io_worker *w) { size_t pause_retry_pcm_samples = pcm_1s_samples; size_t pause_retries = 0; - struct pollfd pfds[] = {{ w->ba_pcm_fd, POLLIN, 0 }}; int timeout = -1; debug("Starting IO loop"); @@ -509,13 +531,26 @@ static void *io_worker_routine(struct io_worker *w) { single_playback_mutex_locked = false; } + struct pollfd *fds; + nfds_t nfds = 1; + + if (w->snd_mixer != NULL) + nfds += snd_mixer_poll_descriptors_count(w->snd_mixer); + + fds = alloca(sizeof(*fds) * nfds); + fds[0].fd = w->ba_pcm_fd; + fds[0].events = POLLIN; + + if (w->snd_mixer != NULL) + snd_mixer_poll_descriptors(w->snd_mixer, fds + 1, nfds - 1); + /* Reading from the FIFO won't block unless there is an open connection * on the writing side. However, the server does not open PCM FIFO until * a transport is created. With the A2DP, the transport is created when * some clients (BT device) requests audio transfer. */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); - int poll_rv = poll(pfds, ARRAYSIZE(pfds), timeout); + int poll_rv = poll(fds, nfds, timeout); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); switch (poll_rv) { @@ -533,23 +568,36 @@ static void *io_worker_routine(struct io_worker *w) { goto close_alsa; } - /* FIFO has been terminated on the writing side */ - if (pfds[0].revents & POLLHUP) - break; + if (w->snd_mixer != NULL) + snd_mixer_handle_events(w->snd_mixer); + + size_t read_samples = 0; + if (fds[0].revents & POLLIN) { + + ssize_t ret; + size_t _in = MIN(pcm_max_read_len, ffb_blen_in(&buffer)); + if ((ret = read(w->ba_pcm_fd, buffer.tail, _in)) == -1) { + if (errno == EINTR) + continue; + error("BlueALSA source PCM read error: %s", strerror(errno)); + goto fail; + } + + read_samples = ret / pcm_format_size; + if (ret % pcm_format_size != 0) + warn("Invalid read from BlueALSA source PCM: %zd %% %zd != 0", ret, pcm_format_size); - ssize_t ret; - size_t _in = MIN(pcm_max_read_len, ffb_blen_in(&buffer)); - if ((ret = read(w->ba_pcm_fd, buffer.tail, _in)) == -1) { - if (errno == EINTR) - continue; - error("BlueALSA source PCM read error: %s", strerror(errno)); - goto fail; } + else if (fds[0].revents & POLLHUP) { + /* source PCM FIFO has been terminated on the writing side */ + debug("BlueALSA source PCM disconnected: %s", w->ba_pcm.pcm_path); + break; + } + else if (fds[0].revents) + error("Unexpected BlueALSA source PCM poll event: %#x", fds[0].revents); - /* Calculate the number of read samples. */ - size_t read_samples = ret / pcm_format_size; - if (ret % pcm_format_size != 0) - warn("Invalid read from BlueALSA source PCM: %zd %% %zd != 0", ret, pcm_format_size); + if (read_samples == 0) + continue; /* If current worker is not active and the single playback mode was * enabled, we have to check if there is any other active worker. */ @@ -579,8 +627,7 @@ static void *io_worker_routine(struct io_worker *w) { } - if (w->snd_pcm == NULL && - read_samples > 0) { + if (w->snd_pcm == NULL) { unsigned int buffer_time = pcm_buffer_time; unsigned int period_time = pcm_period_time; @@ -622,8 +669,7 @@ static void *io_worker_routine(struct io_worker *w) { free(tmp); } - /* initial volume synchronization */ - io_worker_mixer_volume_sync(w, &w->ba_pcm); + io_worker_mixer_volume_sync_setup(w); /* reset retry counters */ pcm_open_retry_pcm_samples = 0; @@ -906,7 +952,7 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage * if (!bluealsa_dbus_message_iter_get_pcm_props(&iter, NULL, pcm)) goto fail; if ((worker = supervise_io_worker(pcm)) != NULL) - io_worker_mixer_volume_update(worker, pcm); + io_worker_mixer_volume_sync_snd_mixer_elem(worker, pcm); return DBUS_HANDLER_RESULT_HANDLED; } commit 5885a88df667e015d33d9020535246962fabbe08 Author: Arkadiusz Bokowy Date: Sat May 20 19:01:58 2023 +0200 Override ALSA CTL/PCM open calls in test harness Overriding snd_*_open() calls allows to simplify our test cases. Also, because we are loading 20-bluealsa.conf file, we are going to validate it with our CI checks. diff --git a/configure.ac b/configure.ac index 13ef9b5..4231609 100644 --- a/configure.ac +++ b/configure.ac @@ -278,6 +278,8 @@ AC_ARG_ENABLE([test], [AS_HELP_STRING([--enable-test], [enable unit test])]) AM_CONDITIONAL([ENABLE_TEST], [test "x$enable_test" = "xyes"]) AM_COND_IF([ENABLE_TEST], [ + AC_SEARCH_LIBS([dlsym], [dl], + [], [AC_MSG_ERROR([unable to find dlsym() function])]) PKG_CHECK_MODULES([CHECK], [check >= 0.9.10]) PKG_CHECK_MODULES([SNDFILE], [sndfile >= 1.0], AC_DEFINE([HAVE_SNDFILE], [1], [Define to 1 if you have the sndfile library.]), [:]) diff --git a/src/asound/20-bluealsa.conf b/src/asound/20-bluealsa.conf.in similarity index 100% rename from src/asound/20-bluealsa.conf rename to src/asound/20-bluealsa.conf.in diff --git a/src/asound/Makefile.am b/src/asound/Makefile.am index ab1275a..4b641f1 100644 --- a/src/asound/Makefile.am +++ b/src/asound/Makefile.am @@ -1,7 +1,5 @@ # BlueALSA - Makefile.am -# Copyright (c) 2016-2021 Arkadiusz Bokowy - -EXTRA_DIST = 20-bluealsa.conf +# Copyright (c) 2016-2023 Arkadiusz Bokowy asound_module_ctl_LTLIBRARIES = libasound_module_ctl_bluealsa.la asound_module_pcm_LTLIBRARIES = libasound_module_pcm_bluealsa.la @@ -40,3 +38,9 @@ libasound_module_pcm_bluealsa_la_LIBADD = \ @ALSA_LIBS@ \ @DBUS1_LIBS@ \ @LIBUNWIND_LIBS@ + +SUFFIXES = .conf.in .conf +MOSTLYCLEANFILES = $(asound_module_conf_DATA) + +.conf.in.conf: + $(SED) -e '' < $< > $@ diff --git a/test/aloader.c b/test/aloader.c index c671abc..1b9f1fd 100644 --- a/test/aloader.c +++ b/test/aloader.c @@ -1,6 +1,6 @@ /* * aloader.c - * Copyright (c) 2016-2020 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -15,33 +15,115 @@ #include #include #include +#include #include #include +#include -typedef void *(*dlopen_t)(const char *filename, int flags); +#include +typedef void *(*dlopen_t)(const char *, int); static dlopen_t dlopen_orig = NULL; -static char program_invocation_path[1024]; -void *dlopen(const char *filename, int flags) { +typedef int (*snd_ctl_open_t)(snd_ctl_t **, const char *, int); +static snd_ctl_open_t snd_ctl_open_orig = NULL; + +typedef int (*snd_pcm_open_t)(snd_pcm_t **, const char *, snd_pcm_stream_t, int); +static snd_pcm_open_t snd_pcm_open_orig = NULL; + +__attribute__ ((constructor)) +static void init(void) { + *(void **)(&dlopen_orig) = dlsym(RTLD_NEXT, "dlopen"); + *(void **)(&snd_ctl_open_orig) = dlsym(RTLD_NEXT, "snd_ctl_open"); + *(void **)(&snd_pcm_open_orig) = dlsym(RTLD_NEXT, "snd_pcm_open"); +} + +/** + * Get build root directory. */ +static const char *buildrootdir() { - if (dlopen_orig == NULL) { + static char buffer[1024] = ""; - *(void **)(&dlopen_orig) = dlsym(RTLD_NEXT, __func__); + if (buffer[0] == '\0') { char *tmp = strdup(program_invocation_name); - strcpy(program_invocation_path, dirname(tmp)); + snprintf(buffer, sizeof(buffer), "%s/..", dirname(tmp)); free(tmp); + if (strstr(buffer, "../utils/aplay") != NULL || + strstr(buffer, "../utils/cli") != NULL) + strcat(buffer, "/.."); + } - char buffer[1024 + 128]; - strcpy(buffer, program_invocation_path); + return buffer; +} + +void *dlopen(const char *filename, int flags) { + + char tmp[PATH_MAX]; + snprintf(tmp, sizeof(tmp), "%s", buildrootdir()); if (strstr(filename, "libasound_module_ctl_bluealsa.so") != NULL) - filename = strcat(buffer, "/../src/asound/.libs/libasound_module_ctl_bluealsa.so"); + filename = strcat(tmp, "/src/asound/.libs/libasound_module_ctl_bluealsa.so"); if (strstr(filename, "libasound_module_pcm_bluealsa.so") != NULL) - filename = strcat(buffer, "/../src/asound/.libs/libasound_module_pcm_bluealsa.so"); + filename = strcat(tmp, "/src/asound/.libs/libasound_module_pcm_bluealsa.so"); return dlopen_orig(filename, flags); } + +int snd_ctl_open(snd_ctl_t **ctl, const char *name, int mode) { + + if (strstr(name, "bluealsa") == NULL) + return snd_ctl_open_orig(ctl, name, mode); + + char tmp[PATH_MAX]; + snprintf(tmp, sizeof(tmp), "%s/src/asound/20-bluealsa.conf", buildrootdir()); + + snd_config_t *top = NULL; + snd_input_t *input = NULL; + int err; + + if ((err = snd_config_update_ref(&top)) < 0) + goto fail; + if ((err = snd_input_stdio_open(&input, tmp, "r")) != 0) + goto fail; + if ((err = snd_config_load(top, input)) != 0) + goto fail; + err = snd_ctl_open_lconf(ctl, name, mode, top); + +fail: + if (top != NULL) + snd_config_unref(top); + if (input != NULL) + snd_input_close(input); + return err; +} + +int snd_pcm_open(snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode) { + + if (strstr(name, "bluealsa") == NULL) + return snd_pcm_open_orig(pcm, name, stream, mode); + + char tmp[PATH_MAX]; + snprintf(tmp, sizeof(tmp), "%s/src/asound/20-bluealsa.conf", buildrootdir()); + + snd_config_t *top = NULL; + snd_input_t *input = NULL; + int err; + + if ((err = snd_config_update_ref(&top)) < 0) + goto fail; + if ((err = snd_input_stdio_open(&input, tmp, "r")) != 0) + goto fail; + if ((err = snd_config_load(top, input)) != 0) + goto fail; + err = snd_pcm_open_lconf(pcm, name, stream, mode, top); + +fail: + if (top != NULL) + snd_config_unref(top); + if (input != NULL) + snd_input_close(input); + return err; +} diff --git a/test/test-alsa-ctl.c b/test/test-alsa-ctl.c index aaf5dd6..8e42692 100644 --- a/test/test-alsa-ctl.c +++ b/test/test-alsa-ctl.c @@ -27,50 +27,15 @@ #include "inc/preload.inc" #include "inc/spawn.inc" -static int snd_ctl_open_bluealsa( - snd_ctl_t **ctlp, - const char *service, - const char *extra_config, - int mode) { - - char buffer[256]; - snd_config_t *conf = NULL; - snd_input_t *input = NULL; - int err; - - sprintf(buffer, - "ctl.bluealsa {\n" - " type bluealsa\n" - " service \"org.bluealsa.%s\"\n" - " %s\n" - "}\n", service, extra_config); - - if ((err = snd_config_top(&conf)) < 0) - goto fail; - if ((err = snd_input_buffer_open(&input, buffer, strlen(buffer))) != 0) - goto fail; - if ((err = snd_config_load(conf, input)) != 0) - goto fail; - err = snd_ctl_open_lconf(ctlp, "bluealsa", mode, conf); - -fail: - if (conf != NULL) - snd_config_delete(conf); - if (input != NULL) - snd_input_close(input); - return err; -} - static int test_ctl_open(struct spawn_process *sp_ba_mock, snd_ctl_t **ctl, int mode) { - const char *service = "test"; - if (spawn_bluealsa_mock(sp_ba_mock, service, true, + if (spawn_bluealsa_mock(sp_ba_mock, NULL, true, "--timeout=1000", "--profile=a2dp-source", "--profile=a2dp-sink", "--profile=hfp-ag", NULL) == -1) return -1; - return snd_ctl_open_bluealsa(ctl, service, "", mode); + return snd_ctl_open(ctl, "bluealsa", mode); } static int test_pcm_close(struct spawn_process *sp_ba_mock, snd_ctl_t *ctl) { @@ -126,14 +91,12 @@ CK_START_TEST(test_controls_battery) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=hsp-ag", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "battery \"yes\"\n", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:BAT=yes", 0), 0); snd_ctl_elem_list_t *elems; snd_ctl_elem_list_alloca(&elems); @@ -167,15 +130,13 @@ CK_START_TEST(test_controls_extended) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=a2dp-source", "--profile=hfp-ag", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "extended \"yes\"\n", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:EXT=yes", 0), 0); snd_ctl_elem_list_t *elems; snd_ctl_elem_list_alloca(&elems); @@ -246,16 +207,14 @@ CK_START_TEST(test_bidirectional_a2dp) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=a2dp-source", "--profile=a2dp-sink", "--codec=FastStream", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "bttransport \"yes\"\n", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:BTT=yes", 0), 0); snd_ctl_elem_list_t *elems; snd_ctl_elem_list_alloca(&elems); @@ -283,15 +242,14 @@ CK_START_TEST(test_device_name_duplicates) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=a2dp-source", "--device-name=12:34:56:78:9A:BC:Long Bluetooth Device Name", "--device-name=23:45:67:89:AB:CD:Long Bluetooth Device Name", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, "", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa", 0), 0); snd_ctl_elem_list_t *elems; snd_ctl_elem_list_alloca(&elems); @@ -378,15 +336,14 @@ CK_START_TEST(test_single_device) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, "test", true, "--timeout=1000", "--profile=a2dp-source", "--profile=a2dp-sink", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "device \"00:00:00:00:00:00\"", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, + "bluealsa:DEV=00:00:00:00:00:00,SRV=org.bluealsa.test", 0), 0); snd_ctl_card_info_t *info; snd_ctl_card_info_alloca(&info); @@ -417,13 +374,12 @@ CK_START_TEST(test_single_device_not_connected) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "device \"00:00:00:00:00:00\"", 0), -ENODEV); + ck_assert_int_eq(snd_ctl_open(&ctl, + "bluealsa:DEV=00:00:00:00:00:00", 0), -ENODEV); ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0); @@ -434,14 +390,13 @@ CK_START_TEST(test_single_device_no_such_device) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=a2dp-source", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "device \"DE:AD:12:34:56:78\"", 0), -ENODEV); + ck_assert_int_eq(snd_ctl_open(&ctl, + "bluealsa:DEV=DE:AD:12:34:56:78", 0), -ENODEV); ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0); @@ -452,17 +407,15 @@ CK_START_TEST(test_single_device_non_dynamic) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=0", "--profile=a2dp-sink", "--profile=hsp-ag", "--fuzzing=500", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "device \"23:45:67:89:AB:CD\"\n" - "dynamic \"no\"\n", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, + "bluealsa:DEV=23:45:67:89:AB:CD,DYN=no", 0), 0); ck_assert_int_eq(snd_ctl_subscribe_events(ctl, 1), 0); snd_ctl_elem_list_t *elems; @@ -519,16 +472,14 @@ CK_START_TEST(test_notifications) { struct spawn_process sp_ba_mock; snd_ctl_t *ctl = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, false, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, false, "--timeout=10000", "--profile=a2dp-source", "--profile=hfp-ag", "--fuzzing=250", NULL), -1); - ck_assert_int_eq(snd_ctl_open_bluealsa(&ctl, service, - "battery \"yes\"\n", 0), 0); + ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:BAT=yes", 0), 0); snd_ctl_event_t *event; snd_ctl_event_malloc(&event); diff --git a/test/test-alsa-pcm.c b/test/test-alsa-pcm.c index 4a8256e..775c849 100644 --- a/test/test-alsa-pcm.c +++ b/test/test-alsa-pcm.c @@ -47,50 +47,6 @@ static snd_pcm_format_t pcm_format = SND_PCM_FORMAT_S16_LE; /* big enough buffer to keep one period of data */ static int16_t pcm_buffer[1024 * 8]; -static int snd_pcm_open_bluealsa( - snd_pcm_t **pcmp, - const char *service, - const char *device, - const char *profile, - const char *extra_config, - snd_pcm_stream_t stream, - int mode) { - - char buffer[256]; - snd_config_t *conf = NULL; - snd_input_t *input = NULL; - int err; - - if (device == NULL) - device = "12:34:56:78:9A:BC"; - if (profile == NULL) - profile = "a2dp"; - - sprintf(buffer, - "pcm.bluealsa {\n" - " type bluealsa\n" - " service \"org.bluealsa.%s\"\n" - " device \"%s\"\n" - " profile \"%s\"\n" - " %s\n" - "}\n", service, device, profile, extra_config); - - if ((err = snd_config_top(&conf)) < 0) - goto fail; - if ((err = snd_input_buffer_open(&input, buffer, strlen(buffer))) != 0) - goto fail; - if ((err = snd_config_load(conf, input)) != 0) - goto fail; - err = snd_pcm_open_lconf(pcmp, "bluealsa", stream, mode, conf); - -fail: - if (conf != NULL) - snd_config_delete(conf); - if (input != NULL) - snd_input_close(input); - return err; -} - static int set_hw_params(snd_pcm_t *pcm, snd_pcm_format_t format, int channels, int rate, unsigned int *buffer_time, unsigned int *period_time) { @@ -177,13 +133,13 @@ static int test_pcm_open(struct spawn_process *sp_ba_mock, snd_pcm_t **pcm, if (stream == SND_PCM_STREAM_CAPTURE) profile = "--profile=a2dp-sink"; - const char *service = "test"; - if (spawn_bluealsa_mock(sp_ba_mock, service, true, + if (spawn_bluealsa_mock(sp_ba_mock, NULL, true, "--timeout=1000", profile, NULL) == -1) return -1; - return snd_pcm_open_bluealsa(pcm, service, NULL, NULL, "", stream, 0); + + return snd_pcm_open(pcm, "bluealsa:DEV=12:34:56:78:9A:BC", stream, 0); } static int test_pcm_close(struct spawn_process *sp_ba_mock, snd_pcm_t *pcm) { @@ -472,11 +428,34 @@ CK_START_TEST(ba_test_playback_hw_constraints) { struct spawn_process sp_ba_mock; snd_pcm_t *pcm = NULL; + + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, + "--timeout=1000", + "--profile=a2dp-source", + NULL), -1); + + snd_config_t *top; + ck_assert_int_ge(snd_config_top(&top), 0); + + const char *config = + "pcm.ba-direct {\n" + " type bluealsa\n" + " device \"12:34:56:78:9A:BC\"\n" + " profile \"a2dp\"\n" + "}\n"; + snd_input_t *input; + ck_assert_int_eq(snd_input_buffer_open(&input, config, strlen(config)), 0); + ck_assert_int_eq(snd_config_load(top, input), 0); + + ck_assert_int_eq(snd_pcm_open_lconf(&pcm, + "ba-direct", SND_PCM_STREAM_PLAYBACK, 0, top), 0); + + snd_config_delete(top); + snd_input_close(input); + snd_pcm_hw_params_t *params; int d; - ck_assert_int_eq(test_pcm_open(&sp_ba_mock, &pcm, SND_PCM_STREAM_PLAYBACK), 0); - snd_pcm_hw_params_alloca(¶ms); snd_pcm_hw_params_any(pcm, params); @@ -542,8 +521,7 @@ CK_START_TEST(ba_test_playback_no_codec_selected) { struct spawn_process sp_ba_mock; snd_pcm_t *pcm = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=hfp-ag", NULL), -1); @@ -553,8 +531,9 @@ CK_START_TEST(ba_test_playback_no_codec_selected) { rv = -EAGAIN; #endif - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, "sco", - "", SND_PCM_STREAM_PLAYBACK, 0), rv); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,PROFILE=sco", + SND_PCM_STREAM_PLAYBACK, 0), rv); ck_assert_int_eq(test_pcm_close(&sp_ba_mock, pcm), 0); @@ -568,13 +547,13 @@ CK_START_TEST(ba_test_playback_no_such_device) { struct spawn_process sp_ba_mock; snd_pcm_t *pcm = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, "test", true, "--timeout=1000", NULL), -1); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, "DE:AD:DE:AD:DE:AD", NULL, - "", SND_PCM_STREAM_PLAYBACK, 0), -ENODEV); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=DE:AD:DE:AD:DE:AD,SRV=org.bluealsa.test", + SND_PCM_STREAM_PLAYBACK, 0), -ENODEV); ck_assert_int_eq(test_pcm_close(&sp_ba_mock, pcm), 0); @@ -588,35 +567,40 @@ CK_START_TEST(ba_test_playback_extra_setup) { struct spawn_process sp_ba_mock; snd_pcm_t *pcm = NULL; - const char *service = "test"; - ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, service, true, + ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true, "--timeout=1000", "--profile=a2dp-source", "--profile=hfp-ag", NULL), -1); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, NULL, - "codec \"SBC\"", SND_PCM_STREAM_PLAYBACK, 0), 0); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,CODEC=SBC", + SND_PCM_STREAM_PLAYBACK, 0), 0); ck_assert_int_eq(test_pcm_close(NULL, pcm), 0); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, NULL, - "codec \"SBC:ffff0822\"", SND_PCM_STREAM_PLAYBACK, 0), 0); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,CODEC=SBC:ffff0822", + SND_PCM_STREAM_PLAYBACK, 0), 0); ck_assert_int_eq(test_pcm_close(NULL, pcm), 0); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, "sco", - "codec \"CVSD\"", SND_PCM_STREAM_PLAYBACK, 0), 0); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,PROFILE=sco,CODEC=CVSD", + SND_PCM_STREAM_PLAYBACK, 0), 0); ck_assert_int_eq(test_pcm_close(NULL, pcm), 0); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, NULL, - "delay 10", SND_PCM_STREAM_PLAYBACK, 0), 0); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,DELAY=10", + SND_PCM_STREAM_PLAYBACK, 0), 0); ck_assert_int_eq(test_pcm_close(NULL, pcm), 0); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, NULL, - "volume \"50+\"", SND_PCM_STREAM_PLAYBACK, 0), 0); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,VOL=50+", + SND_PCM_STREAM_PLAYBACK, 0), 0); ck_assert_int_eq(test_pcm_close(NULL, pcm), 0); - ck_assert_int_eq(snd_pcm_open_bluealsa(&pcm, service, NULL, NULL, - "softvol true", SND_PCM_STREAM_PLAYBACK, 0), 0); + ck_assert_int_eq(snd_pcm_open(&pcm, + "bluealsa:DEV=12:34:56:78:9A:BC,SOFTVOL=true", + SND_PCM_STREAM_PLAYBACK, 0), 0); ck_assert_int_eq(test_pcm_close(NULL, pcm), 0); spawn_terminate(&sp_ba_mock, 0); commit 95c159c1dec156ed9f2f03cf196507bbed3a9698 Author: Arkadiusz Bokowy Date: Sun May 14 22:59:56 2023 +0200 Access codec ID via dedicated transport getter diff --git a/src/bluealsa-dbus.c b/src/bluealsa-dbus.c index c1e6c45..1dae4e9 100644 --- a/src/bluealsa-dbus.c +++ b/src/bluealsa-dbus.c @@ -468,13 +468,9 @@ static void bluealsa_pcm_open(GDBusMethodInvocation *inv, void *userdata) { * open the same PCM at the same time. */ pthread_mutex_lock(&pcm->client_mtx); - pthread_mutex_lock(&t->codec_id_mtx); - const uint16_t codec_id = t->codec_id; - pthread_mutex_unlock(&t->codec_id_mtx); - /* preliminary check whether HFP codes is selected */ if (t->profile & BA_TRANSPORT_PROFILE_MASK_SCO && - codec_id == HFP_CODEC_UNDEFINED) { + ba_transport_get_codec(t) == HFP_CODEC_UNDEFINED) { g_dbus_method_invocation_return_error(inv, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, "HFP audio codec not selected"); goto fail; diff --git a/test/inc/btd.inc b/test/inc/btd.inc index a193f20..141fa96 100644 --- a/test/inc/btd.inc +++ b/test/inc/btd.inc @@ -90,7 +90,7 @@ void bt_dump_close(struct bt_dump *btd) { * Create BT dump file. */ struct bt_dump *bt_dump_create( const char *path, - const struct ba_transport *t) { + struct ba_transport *t) { struct bt_dump *btd; if ((btd = calloc(1, sizeof(*btd))) == NULL) @@ -127,8 +127,8 @@ struct bt_dump *bt_dump_create( unsigned int mode_ = mode; fprintf(btd->file, "BA.dump-%1x.%1x.", BT_DUMP_CURRENT_VERSION, mode_); - btd->transport_codec_id = t->codec_id; - uint16_t id = htobe16(t->codec_id); + btd->transport_codec_id = ba_transport_get_codec(t); + uint16_t id = htobe16(btd->transport_codec_id); if (bt_dump_write(btd, &id, sizeof(id)) == -1) goto fail; @@ -196,7 +196,7 @@ fail: return NULL; } -static const char *transport_to_fname(const struct ba_transport *t) { +static const char *transport_to_fname(struct ba_transport *t) { const char *profile = NULL; const char *codec = NULL; @@ -205,19 +205,19 @@ static const char *transport_to_fname(const struct ba_transport *t) { return "none"; case BA_TRANSPORT_PROFILE_A2DP_SOURCE: profile = "a2dp-source"; - codec = a2dp_codecs_codec_id_to_string(t->codec_id); + codec = a2dp_codecs_codec_id_to_string(ba_transport_get_codec(t)); break; case BA_TRANSPORT_PROFILE_A2DP_SINK: profile = "a2dp-sink"; - codec = a2dp_codecs_codec_id_to_string(t->codec_id); + codec = a2dp_codecs_codec_id_to_string(ba_transport_get_codec(t)); break; case BA_TRANSPORT_PROFILE_HFP_AG: profile = "hfp-ag"; - codec = hfp_codec_id_to_string(t->codec_id); + codec = hfp_codec_id_to_string(ba_transport_get_codec(t)); break; case BA_TRANSPORT_PROFILE_HFP_HF: profile = "hfp-hf"; - codec = hfp_codec_id_to_string(t->codec_id); + codec = hfp_codec_id_to_string(ba_transport_get_codec(t)); break; case BA_TRANSPORT_PROFILE_HSP_AG: return "hsp-ag-cvsd"; diff --git a/test/mock/mock-bluealsa.c b/test/mock/mock-bluealsa.c index 75e1ff5..7cc90e4 100644 --- a/test/mock/mock-bluealsa.c +++ b/test/mock/mock-bluealsa.c @@ -264,8 +264,8 @@ static struct ba_transport *mock_transport_new_a2dp(const char *device_btmac, codec, configuration); t->acquire = mock_transport_acquire_bt; - fprintf(stderr, "BLUEALSA_PCM_READY=A2DP:%s:%s\n", - device_btmac, a2dp_codecs_codec_id_to_string(t->codec_id)); + fprintf(stderr, "BLUEALSA_PCM_READY=A2DP:%s:%s\n", device_btmac, + a2dp_codecs_codec_id_to_string(ba_transport_get_codec(t))); ba_transport_set_a2dp_state(t, BLUEZ_A2DP_TRANSPORT_STATE_PENDING); @@ -330,8 +330,8 @@ static struct ba_transport *mock_transport_new_sco(const char *device_btmac, #endif t->acquire = mock_transport_acquire_bt; - fprintf(stderr, "BLUEALSA_PCM_READY=SCO:%s:%s\n", - device_btmac, hfp_codec_id_to_string(t->codec_id)); + fprintf(stderr, "BLUEALSA_PCM_READY=SCO:%s:%s\n", device_btmac, + hfp_codec_id_to_string(ba_transport_get_codec(t))); ba_device_unref(d); return t; commit 44a5dd9a17eaf3b2a92ea2d1859b64f9837f2b4a Author: Arkadiusz Bokowy Date: Tue Apr 18 19:14:05 2023 +0200 Flush internal buffers when PCM drop is requested diff --git a/src/a2dp-aac.c b/src/a2dp-aac.c index 2b90609..ad8f22b 100644 --- a/src/a2dp-aac.c +++ b/src/a2dp-aac.c @@ -339,11 +339,20 @@ void *a2dp_aac_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + in_args.numInSamples = -1; + /* flush encoder internal buffers */ + while (aacEncEncode(handle, NULL, &out_buf, &in_args, &out_args) == AACENC_OK) + continue; + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/a2dp-aptx-hd.c b/src/a2dp-aptx-hd.c index 9f9016e..e54426f 100644 --- a/src/a2dp-aptx-hd.c +++ b/src/a2dp-aptx-hd.c @@ -145,11 +145,16 @@ void *a2dp_aptx_hd_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/a2dp-aptx.c b/src/a2dp-aptx.c index ce2c9cf..b51c0d3 100644 --- a/src/a2dp-aptx.c +++ b/src/a2dp-aptx.c @@ -135,11 +135,16 @@ void *a2dp_aptx_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/a2dp-faststream.c b/src/a2dp-faststream.c index ce85159..410a588 100644 --- a/src/a2dp-faststream.c +++ b/src/a2dp-faststream.c @@ -117,8 +117,9 @@ void *a2dp_faststream_enc_thread(struct ba_transport_thread *th) { const bool is_voice = t->profile & BA_TRANSPORT_PROFILE_A2DP_SINK; sbc_t sbc; - if ((errno = -sbc_init_a2dp_faststream(&sbc, 0, &t->a2dp.configuration.faststream, - sizeof(t->a2dp.configuration.faststream), is_voice)) != 0) { + const a2dp_faststream_t *configuration = &t->a2dp.configuration.faststream; + if ((errno = -sbc_init_a2dp_faststream(&sbc, 0, configuration, + sizeof(*configuration), is_voice)) != 0) { error("Couldn't initialize FastStream SBC codec: %s", strerror(errno)); goto fail_init; } @@ -142,11 +143,18 @@ void *a2dp_faststream_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples = ffb_len_in(&pcm); - if ((samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + sbc_reinit_a2dp_faststream(&sbc, 0, configuration, + sizeof(*configuration), is_voice); + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/a2dp-lc3plus.c b/src/a2dp-lc3plus.c index c53cc84..6f54498 100644 --- a/src/a2dp-lc3plus.c +++ b/src/a2dp-lc3plus.c @@ -235,6 +235,7 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_thread *th) { int32_t *pcm_ch1 = malloc(lc3plus_ch_samples * sizeof(int32_t)); int32_t *pcm_ch2 = malloc(lc3plus_ch_samples * sizeof(int32_t)); + int32_t *pcm_ch_buffers[2] = { pcm_ch1, pcm_ch2 }; pthread_cleanup_push(PTHREAD_CLEANUP(free), pcm_ch1); pthread_cleanup_push(PTHREAD_CLEANUP(free), pcm_ch2); @@ -258,11 +259,21 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + int encoded = 0; + memset(pcm_ch1, 0, lc3plus_ch_samples * sizeof(*pcm_ch1)); + memset(pcm_ch2, 0, lc3plus_ch_samples * sizeof(*pcm_ch2)); + /* flush encoder internal buffers by feeding it with silence */ + lc3plus_enc24(handle, pcm_ch_buffers, rtp_payload, &encoded); + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } @@ -288,9 +299,8 @@ void *a2dp_lc3plus_enc_thread(struct ba_transport_thread *th) { lc3plus_frames < ((1 << 4) - 1)) { int encoded = 0; - int32_t *in_buffers[2] = { pcm_ch1, pcm_ch2 }; audio_deinterleave_s24_4le(input, lc3plus_ch_samples, channels, pcm_ch1, pcm_ch2); - if ((err = lc3plus_enc24(handle, in_buffers, bt.tail, &encoded)) != LC3PLUS_OK) { + if ((err = lc3plus_enc24(handle, pcm_ch_buffers, bt.tail, &encoded)) != LC3PLUS_OK) { error("LC3plus encoding error: %s", lc3plus_strerror(err)); break; } @@ -439,6 +449,7 @@ void *a2dp_lc3plus_dec_thread(struct ba_transport_thread *th) { int32_t *pcm_ch1 = malloc(lc3plus_ch_samples * sizeof(int32_t)); int32_t *pcm_ch2 = malloc(lc3plus_ch_samples * sizeof(int32_t)); + int32_t *pcm_ch_buffers[2] = { pcm_ch1, pcm_ch2 }; pthread_cleanup_push(PTHREAD_CLEANUP(free), pcm_ch1); pthread_cleanup_push(PTHREAD_CLEANUP(free), pcm_ch2); @@ -501,8 +512,7 @@ void *a2dp_lc3plus_dec_thread(struct ba_transport_thread *th) { while (missing_pcm_frames > 0) { - int32_t *out_buffers[2] = { pcm_ch1, pcm_ch2 }; - lc3plus_dec24(handle, bt_payload.data, 0, out_buffers, 1); + lc3plus_dec24(handle, bt_payload.data, 0, pcm_ch_buffers, 1); audio_interleave_s24_4le(pcm_ch1, pcm_ch2, lc3plus_ch_samples, channels, pcm.data); warn("Missing LC3plus data, loss concealment applied"); @@ -556,8 +566,7 @@ void *a2dp_lc3plus_dec_thread(struct ba_transport_thread *th) { /* Decode retrieved LC3plus frames. */ while (lc3plus_frames--) { - int32_t *out_buffers[2] = { pcm_ch1, pcm_ch2 }; - err = lc3plus_dec24(handle, lc3plus_payload, lc3plus_frame_len, out_buffers, 0); + err = lc3plus_dec24(handle, lc3plus_payload, lc3plus_frame_len, pcm_ch_buffers, 0); audio_interleave_s24_4le(pcm_ch1, pcm_ch2, lc3plus_ch_samples, channels, pcm.data); if (err == LC3PLUS_DECODE_ERROR) diff --git a/src/a2dp-ldac.c b/src/a2dp-ldac.c index 081802a..74bcc50 100644 --- a/src/a2dp-ldac.c +++ b/src/a2dp-ldac.c @@ -185,11 +185,19 @@ void *a2dp_ldac_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + int tmp; + /* flush encoder internal buffers */ + ldacBT_encode(handle, NULL, &tmp, rtp_payload, &tmp, &tmp); + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/a2dp-mpeg.c b/src/a2dp-mpeg.c index fbe4d76..04bd74e 100644 --- a/src/a2dp-mpeg.c +++ b/src/a2dp-mpeg.c @@ -283,9 +283,10 @@ void *a2dp_mp3_enc_thread(struct ba_transport_thread *th) { const size_t mpeg_pcm_samples = lame_get_framesize(handle); const size_t rtp_headers_len = RTP_HEADER_LEN + sizeof(rtp_mpeg_audio_header_t); - /* It is hard to tell the size of the buffer required, but - * empirical test shows that 2KB should be sufficient. */ - const size_t mpeg_frame_len = 2048; + /* It is hard to tell the size of the buffer required, but empirical test + * shows that 2KB should be sufficient for encoding. However, encoder flush + * function requires a little bit more space. */ + const size_t mpeg_frame_len = 4 * 1024; if (ffb_init_int16_t(&pcm, mpeg_pcm_samples) == -1 || ffb_init_uint8_t(&bt, rtp_headers_len + mpeg_frame_len) == -1) { @@ -306,11 +307,17 @@ void *a2dp_mp3_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + lame_encode_flush(handle, rtp_payload, mpeg_frame_len); + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/a2dp-sbc.c b/src/a2dp-sbc.c index 6681c4f..1580db5 100644 --- a/src/a2dp-sbc.c +++ b/src/a2dp-sbc.c @@ -163,8 +163,8 @@ void *a2dp_sbc_enc_thread(struct ba_transport_thread *th) { struct io_poll io = { .timeout = -1 }; sbc_t sbc; - if ((errno = -sbc_init_a2dp(&sbc, 0, &t->a2dp.configuration.sbc, - sizeof(t->a2dp.configuration.sbc))) != 0) { + const a2dp_sbc_t *configuration = &t->a2dp.configuration.sbc; + if ((errno = -sbc_init_a2dp(&sbc, 0, configuration, sizeof(*configuration))) != 0) { error("Couldn't initialize SBC codec: %s", strerror(errno)); goto fail_init; } @@ -175,7 +175,6 @@ void *a2dp_sbc_enc_thread(struct ba_transport_thread *th) { pthread_cleanup_push(PTHREAD_CLEANUP(ffb_free), &pcm); pthread_cleanup_push(PTHREAD_CLEANUP(sbc_finish), &sbc); - const a2dp_sbc_t *configuration = &t->a2dp.configuration.sbc; const size_t sbc_frame_samples = sbc_get_codesize(&sbc) / sizeof(int16_t); const unsigned int channels = t_pcm->channels; const unsigned int samplerate = t_pcm->sampling; @@ -223,11 +222,18 @@ void *a2dp_sbc_enc_thread(struct ba_transport_thread *th) { debug_transport_thread_loop(th, "START"); for (ba_transport_thread_state_set_running(th);;) { - ssize_t samples; - if ((samples = io_poll_and_read_pcm(&io, t_pcm, - pcm.tail, ffb_len_in(&pcm))) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); + ssize_t samples = ffb_len_in(&pcm); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + sbc_reinit_a2dp(&sbc, 0, configuration, sizeof(*configuration)); + sbc.bitpool = sbc_a2dp_get_bitpool(configuration, config.sbc_quality); + ffb_rewind(&pcm); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: ba_transport_stop_if_no_clients(t); continue; } diff --git a/src/ba-transport.c b/src/ba-transport.c index c100285..85679e9 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -63,6 +63,7 @@ #include "dbus.h" #include "hci.h" #include "hfp.h" +#include "io.h" #if ENABLE_OFONO # include "ofono.h" #endif @@ -1527,7 +1528,7 @@ int ba_transport_start(struct ba_transport *t) { debug("Starting transport: %s", ba_transport_debug_name(t)); if (t->profile & BA_TRANSPORT_PROFILE_MASK_A2DP) - switch (t->codec_id) { + switch (ba_transport_get_codec(t)) { case A2DP_CODEC_SBC: return a2dp_sbc_transport_start(t); #if ENABLE_MPEG @@ -1827,11 +1828,13 @@ int ba_transport_pcm_resume(struct ba_transport_pcm *pcm) { int ba_transport_pcm_drain(struct ba_transport_pcm *pcm) { - if (pcm->th->state != BA_TRANSPORT_THREAD_STATE_RUNNING) - return errno = ESRCH, -1; - pthread_mutex_lock(&pcm->mutex); + if (!ba_transport_thread_state_check_running(pcm->th)) { + pthread_mutex_unlock(&pcm->mutex); + return errno = ESRCH, -1; + } + debug("PCM drain: %d", pcm->fd); pcm->synced = false; @@ -1857,12 +1860,21 @@ int ba_transport_pcm_drain(struct ba_transport_pcm *pcm) { } int ba_transport_pcm_drop(struct ba_transport_pcm *pcm) { + #if DEBUG pthread_mutex_lock(&pcm->mutex); debug("PCM drop: %d", pcm->fd); pthread_mutex_unlock(&pcm->mutex); #endif - return ba_transport_thread_signal_send(&pcm->t->thread_enc, BA_TRANSPORT_THREAD_SIGNAL_PCM_DROP); + + if (io_pcm_flush(pcm) == -1) + return -1; + + int rv = ba_transport_thread_signal_send(pcm->th, BA_TRANSPORT_THREAD_SIGNAL_PCM_DROP); + if (rv == -1 && errno == ESRCH) + rv = 0; + + return rv; } int ba_transport_pcm_release(struct ba_transport_pcm *pcm) { diff --git a/src/ba-transport.h b/src/ba-transport.h index 406c4f9..3a56fab 100644 --- a/src/ba-transport.h +++ b/src/ba-transport.h @@ -183,6 +183,8 @@ bool ba_transport_thread_state_check( struct ba_transport_thread *th, enum ba_transport_thread_state state); +#define ba_transport_thread_state_check_running(th) \ + ba_transport_thread_state_check(th, BA_TRANSPORT_THREAD_STATE_RUNNING) #define ba_transport_thread_state_check_terminated(th) \ ba_transport_thread_state_check(th, BA_TRANSPORT_THREAD_STATE_TERMINATED) diff --git a/src/codec-msbc.c b/src/codec-msbc.c index bc45326..ec13ef4 100644 --- a/src/codec-msbc.c +++ b/src/codec-msbc.c @@ -115,7 +115,7 @@ int msbc_init(struct esco_msbc *msbc) { msbc->seq_number = 0; msbc->frames = 0; - if (msbc->plc) + if (msbc->plc != NULL) plc_init(msbc->plc); else if (!(msbc->plc = plc_init(NULL))) goto fail; diff --git a/src/codec-sbc.c b/src/codec-sbc.c index f682436..a6ea352 100644 --- a/src/codec-sbc.c +++ b/src/codec-sbc.c @@ -1,6 +1,6 @@ /* * BlueALSA - codec-sbc.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -110,27 +110,14 @@ uint8_t sbc_a2dp_get_bitpool(const a2dp_sbc_t *conf, unsigned int quality) { } #if ENABLE_FASTSTREAM -/** - * Initialize SBC audio codec for A2DP FastStream connection. - * - * @param sbc SBC structure which shall be initialized. - * @param flags SBC initialization flags. - * @param conf A2DP FastStream configuration blob. - * @param size Size of the configuration blob. - * @param voice It true, SBC will be initialized for voice direction. - * @return This function returns 0 on success or a negative error value - * in case of SBC audio codec initialization failure. */ -int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, + +static int sbc_set_a2dp_faststream(sbc_t *sbc, const void *conf, size_t size, bool voice) { const a2dp_faststream_t *a2dp = conf; if (size != sizeof(*a2dp)) return -EINVAL; - int rv; - if ((rv = sbc_init(sbc, flags)) != 0) - return rv; - sbc->blocks = SBC_BLK_16; sbc->subbands = SBC_SB_8; sbc->allocation = SBC_AM_LOUDNESS; @@ -138,14 +125,14 @@ int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, if (voice) { if (!(a2dp->direction & FASTSTREAM_DIRECTION_VOICE)) - goto fail; + return -EINVAL; switch (a2dp->frequency_voice) { case FASTSTREAM_SAMPLING_FREQ_VOICE_16000: sbc->frequency = SBC_FREQ_16000; break; default: - goto fail; + return -EINVAL; } sbc->mode = SBC_MODE_MONO; @@ -155,7 +142,7 @@ int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, else { if (!(a2dp->direction & FASTSTREAM_DIRECTION_MUSIC)) - goto fail; + return -EINVAL; switch (a2dp->frequency_music) { case FASTSTREAM_SAMPLING_FREQ_MUSIC_44100: @@ -165,7 +152,7 @@ int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, sbc->frequency = SBC_FREQ_48000; break; default: - goto fail; + return -EINVAL; } sbc->mode = SBC_MODE_JOINT_STEREO; @@ -174,11 +161,51 @@ int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, } return 0; +} -fail: - sbc_finish(sbc); - return -EINVAL; +/** + * Initialize SBC audio codec for A2DP FastStream connection. + * + * @param sbc SBC structure which shall be initialized. + * @param flags SBC initialization flags. + * @param conf A2DP FastStream configuration blob. + * @param size Size of the configuration blob. + * @param voice It true, SBC will be initialized for voice direction. + * @return This function returns 0 on success or a negative error value + * in case of SBC audio codec initialization failure. */ +int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, + const void *conf, size_t size, bool voice) { + + int rv; + if ((rv = sbc_init(sbc, flags)) != 0) + return rv; + + if ((rv = sbc_set_a2dp_faststream(sbc, conf, size, voice)) != 0) { + sbc_finish(sbc); + return rv; + } + + return 0; } + +/** + * Reinitialize SBC audio codec for A2DP FastStream connection. + * + * @param sbc SBC structure which shall be reinitialized. + * @param flags SBC initialization flags. + * @param conf A2DP FastStream configuration blob. + * @param size Size of the configuration blob. + * @param voice It true, SBC will be initialized for voice direction. + * @return This function returns 0 on success or a negative error value + * in case of SBC audio codec initialization failure. */ +int sbc_reinit_a2dp_faststream(sbc_t *sbc, unsigned long flags, + const void *conf, size_t size, bool voice) { + int rv; + if ((rv = sbc_reinit(sbc, flags)) != 0) + return rv; + return sbc_set_a2dp_faststream(sbc, conf, size, voice); +} + #endif #if ENABLE_MSBC diff --git a/src/codec-sbc.h b/src/codec-sbc.h index 3a25970..2e226bf 100644 --- a/src/codec-sbc.h +++ b/src/codec-sbc.h @@ -1,6 +1,6 @@ /* * BlueALSA - codec-sbc.h - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -35,6 +35,8 @@ uint8_t sbc_a2dp_get_bitpool(const a2dp_sbc_t *conf, unsigned int quality); #if ENABLE_FASTSTREAM int sbc_init_a2dp_faststream(sbc_t *sbc, unsigned long flags, const void *conf, size_t size, bool voice); +int sbc_reinit_a2dp_faststream(sbc_t *sbc, unsigned long flags, + const void *conf, size_t size, bool voice); #endif #if ENABLE_MSBC diff --git a/src/io.c b/src/io.c index ca4aed8..6e5c3a6 100644 --- a/src/io.c +++ b/src/io.c @@ -159,17 +159,25 @@ void io_pcm_scale( * Flush read buffer of the transport PCM FIFO. */ ssize_t io_pcm_flush(struct ba_transport_pcm *pcm) { + ssize_t samples = 0; ssize_t rv; pthread_mutex_lock(&pcm->mutex); - rv = splice(pcm->fd, NULL, config.null_fd, NULL, 1024 * 32, SPLICE_F_NONBLOCK); + + const int fd = pcm->fd; + const size_t sample_size = BA_TRANSPORT_PCM_FORMAT_BYTES(pcm->format); + + while ((rv = splice(fd, NULL, config.null_fd, NULL, 32 * 1024, SPLICE_F_NONBLOCK)) > 0) { + debug("Flushed PCM samples [%d]: %zd", fd, rv / sample_size); + samples += rv / sample_size; + } + pthread_mutex_unlock(&pcm->mutex); - if (rv > 0) - rv /= BA_TRANSPORT_PCM_FORMAT_BYTES(pcm->format); - else if (rv == -1 && errno == EAGAIN) - rv = 0; - return rv; + if (rv == -1 && errno != EAGAIN) + return rv; + + return samples; } /** @@ -366,8 +374,10 @@ repoll: io->timeout = 100; goto repoll; case BA_TRANSPORT_THREAD_SIGNAL_PCM_DROP: - io_pcm_flush(pcm); - goto repoll; + /* Notify caller that the PCM FIFO has been dropped. This will give + * the caller a chance to reinitialize its internal state. */ + errno = ESTALE; + return -1; default: goto repoll; } diff --git a/src/sco.c b/src/sco.c index caed0da..4b3132c 100644 --- a/src/sco.c +++ b/src/sco.c @@ -127,7 +127,7 @@ static void *sco_dispatcher_thread(struct ba_adapter *a) { #if ENABLE_MSBC struct bt_voice voice = { .setting = BT_VOICE_TRANSPARENT }; - if (t->codec_id == HFP_CODEC_MSBC && + if (ba_transport_get_codec(t) == HFP_CODEC_MSBC && setsockopt(fd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) == -1) { error("Couldn't setup transparent voice: %s", strerror(errno)); goto cleanup; @@ -245,11 +245,16 @@ static void *sco_cvsd_enc_thread(struct ba_transport_thread *th) { for (ba_transport_thread_state_set_running(th);;) { ssize_t samples = ffb_len_in(&buffer); - if ((samples = io_poll_and_read_pcm(&io, t_pcm, buffer.tail, samples)) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); - else if (samples == 0) - ba_transport_stop_if_no_clients(t); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, buffer.tail, samples)) { + case -1: + if (errno == ESTALE) { + ffb_rewind(&buffer); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: + ba_transport_stop_if_no_clients(t); continue; } @@ -377,11 +382,17 @@ static void *sco_msbc_enc_thread(struct ba_transport_thread *th) { for (ba_transport_thread_state_set_running(th);;) { ssize_t samples = ffb_len_in(&msbc.pcm); - if ((samples = io_poll_and_read_pcm(&io, t_pcm, msbc.pcm.tail, samples)) <= 0) { - if (samples == -1) - error("PCM poll and read error: %s", strerror(errno)); - else if (samples == 0) - ba_transport_stop_if_no_clients(t); + switch (samples = io_poll_and_read_pcm(&io, t_pcm, msbc.pcm.tail, samples)) { + case -1: + if (errno == ESTALE) { + /* reinitialize mSBC encoder */ + msbc_init(&msbc); + continue; + } + error("PCM poll and read error: %s", strerror(errno)); + /* fall-through */ + case 0: + ba_transport_stop_if_no_clients(t); continue; } @@ -498,7 +509,7 @@ fail_msbc: #endif void *sco_enc_thread(struct ba_transport_thread *th) { - switch (th->t->codec_id) { + switch (ba_transport_get_codec(th->t)) { case HFP_CODEC_CVSD: default: return sco_cvsd_enc_thread(th); @@ -511,7 +522,7 @@ void *sco_enc_thread(struct ba_transport_thread *th) { __attribute__ ((weak)) void *sco_dec_thread(struct ba_transport_thread *th) { - switch (th->t->codec_id) { + switch (ba_transport_get_codec(th->t)) { case HFP_CODEC_CVSD: default: return sco_cvsd_dec_thread(th); diff --git a/test/Makefile.am b/test/Makefile.am index f242217..83eef53 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -141,6 +141,7 @@ test_rfcomm_SOURCES = \ ../src/dbus.c \ ../src/hci.c \ ../src/hfp.c \ + ../src/io.c \ ../src/utils.c \ test-rfcomm.c diff --git a/test/mock/Makefile.am b/test/mock/Makefile.am index 4bc3665..3c76fcf 100644 --- a/test/mock/Makefile.am +++ b/test/mock/Makefile.am @@ -81,7 +81,6 @@ endif if ENABLE_LC3PLUS bluealsa_mock_SOURCES += ../../src/a2dp-lc3plus.c -bluealsa_mock_CFLAGS += @LC3PLUS_CFLAGS@ bluealsa_mock_LDADD += @LC3PLUS_LIBS@ endif diff --git a/test/test-ba.c b/test/test-ba.c index 6b5e274..0ba8480 100644 --- a/test/test-ba.c +++ b/test/test-ba.c @@ -189,13 +189,6 @@ CK_START_TEST(test_ba_transport_sco_one_only) { } CK_END_TEST -static uint16_t get_codec_id(struct ba_transport *t) { - pthread_mutex_lock(&t->codec_id_mtx); - uint16_t codec_id = t->codec_id; - pthread_mutex_unlock(&t->codec_id_mtx); - return codec_id; -} - CK_START_TEST(test_ba_transport_sco_default_codec) { struct ba_adapter *a; @@ -207,7 +200,7 @@ CK_START_TEST(test_ba_transport_sco_default_codec) { ck_assert_ptr_ne(d = ba_device_new(a, &addr), NULL); t_sco = ba_transport_new_sco(d, BA_TRANSPORT_PROFILE_HSP_AG, "/owner", "/path/sco", -1); - ck_assert_int_eq(get_codec_id(t_sco), HFP_CODEC_CVSD); + ck_assert_int_eq(ba_transport_get_codec(t_sco), HFP_CODEC_CVSD); ba_transport_unref(t_sco); #if ENABLE_MSBC @@ -217,17 +210,17 @@ CK_START_TEST(test_ba_transport_sco_default_codec) { config.hfp.codecs.msbc = true; t_sco = ba_transport_new_sco(d, BA_TRANSPORT_PROFILE_HFP_AG, "/owner", "/path/sco", -1); - ck_assert_int_eq(get_codec_id(t_sco), HFP_CODEC_UNDEFINED); + ck_assert_int_eq(ba_transport_get_codec(t_sco), HFP_CODEC_UNDEFINED); ba_transport_unref(t_sco); config.hfp.codecs.msbc = false; t_sco = ba_transport_new_sco(d, BA_TRANSPORT_PROFILE_HFP_AG, "/owner", "/path/sco", -1); - ck_assert_int_eq(get_codec_id(t_sco), HFP_CODEC_CVSD); + ck_assert_int_eq(ba_transport_get_codec(t_sco), HFP_CODEC_CVSD); ba_transport_unref(t_sco); #else t_sco = ba_transport_new_sco(d, BA_TRANSPORT_PROFILE_HFP_AG, "/owner", "/path/sco", -1); - ck_assert_int_eq(get_codec_id(t_sco), HFP_CODEC_CVSD); + ck_assert_int_eq(ba_transport_get_codec(t_sco), HFP_CODEC_CVSD); ba_transport_unref(t_sco); #endif diff --git a/test/test-io.c b/test/test-io.c index f1b4c09..aab5856 100644 --- a/test/test-io.c +++ b/test/test-io.c @@ -13,6 +13,7 @@ #endif #include +#include #include #include #include @@ -708,6 +709,118 @@ CK_START_TEST(test_a2dp_sbc_invalid_config) { } CK_END_TEST +CK_START_TEST(test_a2dp_sbc_pcm_drop) { + + int16_t pcm_zero[90] = { 0 }; + int16_t pcm_rand[ARRAYSIZE(pcm_zero)]; + for (size_t i = 0; i < ARRAYSIZE(pcm_rand); i++) + pcm_rand[i] = rand(); + + struct ba_transport *t1 = test_transport_new_a2dp(device1, + BA_TRANSPORT_PROFILE_A2DP_SOURCE, "/path/sbc", &a2dp_sbc_source, + &config_sbc_44100_stereo); + + struct ba_transport *t2 = test_transport_new_a2dp(device2, + BA_TRANSPORT_PROFILE_A2DP_SINK, "/path/sbc", &a2dp_sbc_sink, + &config_sbc_44100_stereo); + + struct ba_transport_thread *th1 = &t1->thread_enc; + struct ba_transport_thread *th2 = &t2->thread_dec; + + int bt_fds[2]; + ck_assert_int_eq(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, bt_fds), 0); + debug("Created BT socket pair: %d, %d", bt_fds[0], bt_fds[1]); + t1->mtu_read = t1->mtu_write = 256; + t2->mtu_read = t2->mtu_write = 256; + t1->bt_fd = bt_fds[1]; + t2->bt_fd = bt_fds[0]; + + int pcm_snk_fds[2]; + ck_assert_int_eq(pipe2(pcm_snk_fds, O_NONBLOCK), 0); + debug("Created PCM pipe pair: %d, %d", pcm_snk_fds[0], pcm_snk_fds[1]); + th1->pcm->fd = pcm_snk_fds[0]; + th1->pcm->active = true; + + int pcm_src_fds[2]; + ck_assert_int_eq(pipe2(pcm_src_fds, O_NONBLOCK), 0); + debug("Created PCM pipe pair: %d, %d", pcm_src_fds[0], pcm_src_fds[1]); + th2->pcm->fd = pcm_src_fds[1]; + th2->pcm->active = true; + + /* sink PCM */ + struct ba_transport_pcm *pcm = th1->pcm; + + /* write zero samples to PCM FIFO until it is full */ + while (write(pcm_snk_fds[1], pcm_zero, sizeof(pcm_zero)) > 0) + continue; + + /* drop PCM samples before IO thread was started */ + ck_assert_int_eq(ba_transport_pcm_drop(pcm), 0); + + /* start IO thread and make sure it is running */ + ck_assert_int_eq(ba_transport_thread_create(th1, a2dp_sbc_enc_thread, "sbc", true), 0); + ck_assert_int_eq(ba_transport_thread_state_wait_running(th1), 0); + + /* wait for 50 ms - let the thread to run for a while */ + usleep(50000); + + uint8_t bt_buffer[1024]; + /* try to read data from BT socket - it should be empty because + * the PCM has been dropped before the thread was started */ + ck_assert_int_eq(read(bt_fds[0], bt_buffer, sizeof(bt_buffer)), -1); + ck_assert_int_eq(errno, EAGAIN); + + /* write non-zero samples to PCM FIFO and process some of it */ + while (write(pcm_snk_fds[1], pcm_rand, sizeof(pcm_rand)) > 0) + continue; + usleep(50000); + + /* drop PCM samples */ + ck_assert_int_eq(ba_transport_pcm_drop(pcm), 0); + + /* write a little bit of non-zero samples and wait for processing; the + * number of samples shall be small enough to not produce a single codec + * frame, but enough to fill-in internal buffers */ + ck_assert_int_gt(write(pcm_snk_fds[1], pcm_rand, sizeof(pcm_rand)), 0); + usleep(10000); + + /* again drop PCM samples */ + ck_assert_int_eq(ba_transport_pcm_drop(pcm), 0); + + /* flush already processed data */ + while (read(bt_fds[0], bt_buffer, sizeof(bt_buffer)) > 0) + continue; + + /* After PCM has been dropped, IO thread should not process any more + * non-zero samples. We will check this by writing zero samples and + * checking if decoded data is all silence. */ + + ck_assert_int_eq(ba_transport_thread_create(th2, a2dp_sbc_dec_thread, "sbc", true), 0); + ck_assert_int_eq(ba_transport_thread_state_wait_running(th2), 0); + + /* write some zero samples to PCM FIFO and process them */ + for (size_t i = 0; i < 100; i++) + if (write(pcm_snk_fds[1], pcm_zero, sizeof(pcm_zero)) <= 0) + break; + usleep(250000); + + ssize_t rv; + int16_t pcm_buffer[1024]; + /* read decoded data and check if it is all silence */ + while ((rv = read(pcm_src_fds[0], pcm_buffer, sizeof(pcm_buffer))) > 0) { + const size_t samples = rv / sizeof(pcm_buffer[0]); + for (size_t i = 0; i < samples; i++) + ck_assert_int_eq(pcm_buffer[i], 0); + } + + ba_transport_destroy(t1); + ba_transport_destroy(t2); + close(pcm_snk_fds[0]); + close(pcm_src_fds[1]); + close(bt_fds[0]); + +} CK_END_TEST + #if ENABLE_MP3LAME CK_START_TEST(test_a2dp_mp3) { @@ -992,6 +1105,7 @@ int main(int argc, char *argv[]) { } codecs[] = { { a2dp_codecs_codec_id_to_string(A2DP_CODEC_SBC), test_a2dp_sbc }, { a2dp_codecs_codec_id_to_string(A2DP_CODEC_SBC), test_a2dp_sbc_invalid_config }, + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_SBC), test_a2dp_sbc_pcm_drop }, #if ENABLE_MP3LAME { a2dp_codecs_codec_id_to_string(A2DP_CODEC_MPEG12), test_a2dp_mp3 }, #endif @@ -1106,10 +1220,12 @@ int main(int argc, char *argv[]) { enabled_codecs = 0; for (size_t i = 0; i < ARRAYSIZE(codecs); i++) if (strcmp(codec, codecs[i].name) == 0) - enabled_codecs = 1 << i; + enabled_codecs |= 1 << i; } + bluealsa_config_init(); + bdaddr_t addr1 = {{ 1, 2, 3, 4, 5, 6 }}; bdaddr_t addr2 = {{ 1, 2, 3, 7, 8, 9 }}; adapter = ba_adapter_new(0); commit 582ce40e2c0583df1f3c9e1594ee2c2f0521bd5e Author: borine <32966433+borine@users.noreply.github.com> Date: Mon Apr 17 09:13:24 2023 +0100 Introduce troubleshooting guide as a separate file diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md index 2735976..91ead5b 100644 --- a/.github/ISSUE_TEMPLATE/1-bug-report.md +++ b/.github/ISSUE_TEMPLATE/1-bug-report.md @@ -4,6 +4,9 @@ about: Create a report to help improve bluez-alsa. title: --- +> Please read the [troubleshooting guide](../blob/master/TROUBLESHOOTING.md) +> before raising a new issue. + ### Problem > A clear and concise description of what the bug is. @@ -20,9 +23,10 @@ title: > - the OS distribution and version > - the version of BlueALSA (`bluealsa --version`) -> - if self-built from source, please state the branch, commit and used configure options > - the version of BlueZ (`bluetoothd --version`) > - the version of ALSA (`aplay --version`) +> - if self-built from source, please state the branch and commit +> (`git log -1 --oneline`), and the used configure options. ### Additional context diff --git a/INSTALL.md b/INSTALL.md index 9210929..43caac4 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -64,6 +64,22 @@ configuration): - [libbsd](https://libbsd.freedesktop.org/) - [ncurses](https://www.gnu.org/software/ncurses/) +If it is intended to use BlueALSA on a system that uses `systemd`, then it is +recommended to include the option `--enable-systemd` as this will create +service unit files. +See the [systemd integration][] wiki page for more information. + +[systemd integration]: https://github.com/arkq/bluez-alsa/wiki/Systemd-integration + +If intending to run the `bluealsa` daemon as a non-root user then it is +recommended to use the `--with-bluealsauser=USER` option as this will configure +the BlueALSA D-Bus policy file with correct permissions for that user account, +and also include that user in the systemd service unit file when used in +combination with `--enable-systemd`. + +If not using systemd, then some manual setup of the host will be required, see +[Runtime Environment](#runtime-environment) below. + Once the desired options have been chosen, run: ```sh @@ -71,13 +87,12 @@ mkdir build && cd build ../configure [ OPTION ... ] ``` -## Installation +## Build -When the project is configured, compile and install it by running: +When the project is configured, compile it by running in the build directory: -```sh +```shell make -sudo make install ``` When building from the git sources, if `git pull` is used to update the source @@ -85,8 +100,85 @@ tree, then it is recommended to refresh the build in order to update the version identifier embedded in the configure files. In the top-level directory run: -```sh +```shell autoreconf --install --force ``` then in the build directory run `make clean` before running `make`. + +## Installation + +The built components can be installed on the local system with + +```shell +sudo make install +``` + +To install into a directory that can be packaged and copied to other hosts (for +example a directory called BLUEALSA): + +```shell +sudo make DESTDIR=BLUEALSA install +``` + +## Runtime Environment + +### Storage directory + +When using `systemd`, all the necessary files and directories are created by +the `bluealsa.service` unit at runtime. If not using `systemd`, or if the + `--enable-systemd` option was not used during configuration, then it is +necessary to manually create the directory used by BlueALSA for persistent +state storage. This directory should be called `bluealsa` and be located under +the system local state directory, which is normally `/var/lib`. The directory +owner must be the user account that the `bluealsa` daemon is run under, and +to prevent accidental corruption of the state files the permissions should be +`rwx------`. For example, on a standard file hierarchy, with the `bluealsa` +daemon running as user `bluealsa`: + +```sh +sudo mkdir /var/lib/bluealsa +sudo chown bluealsa /var/lib/bluealsa +sudo chmod 0700 /var/lib/bluealsa +``` + +### User accounts + +The BlueALSA installation does not create any user accounts. + +### D-Bus policy + +A D-Bus policy file is required to enable the `bluealsa` daemon to register +with D-Bus as a service. The default policy file created by the BlueALSA +installation enables `root` to register the service `org.bluealsa` and enables +members of the group `audio` to use BlueALSA PCMs and the BlueALSA mixer. If +the option `--with-bluealsauser=USER` was used when configuring then the +policy file enables user USER instead of `root` to register the `org.bluealsa` +service. If that option was not used, then it is necessary to edit the policy +file to grant permission to a non-root user. The policy file is located at + +`/etc/dbus-1/system.d/bluealsa.conf`. + +For example: + +```xml + + + + + + + + + + + + + + + + + +``` diff --git a/README.md b/README.md index bfbbc08..8802be3 100644 --- a/README.md +++ b/README.md @@ -217,51 +217,6 @@ follows: bluealsa-aplay -L ``` -## Troubleshooting - -1. Using BlueALSA alongside PulseAudio or PipeWire. - - Due to BlueZ limitations, only one program can register as provider of - Bluetooth audio profile implementation. So it is not possible to use - BlueALSA if either PulseAudio or PipeWire are also running with their own - Bluetooth modules enabled; it is first necessary to disable Bluetooth in - those applications. - -2. ALSA thread-safe API (alsa-lib >= 1.1.2, <= 1.1.3). - - ALSA library versions 1.1.2 and 1.1.3 had a bug in their thread-safe API - functions. This bug does not affect hardware audio devices, but it affects - many software plug-ins. Random deadlocks are inevitable. The best advice is - to use a more recent alsa-lib release, or if that is not possible then - disable the thread locking code via an environment variable, as follows: - `export LIBASOUND_THREAD_SAFE=0`. - -3. Couldn't acquire D-Bus name: org.bluealsa - - It is not possible to run more than one instance of the BlueALSA server per - D-Bus interface. If one tries to run second instance, it will fail with the - `"Couldn't acquire D-Bus name: org.bluealsa"` error message. This message - might also appear when D-Bus policy does not allow acquiring "org.bluealsa" - name for a particular user - by default only root is allowed to start - BlueALSA server. - -4. Couldn't get BlueALSA PCM: PCM not found - - In contrast to standard ALSA sound cards, BlueALSA does not expose all PCMs - right away. In the first place it is required to connect remote Bluetooth - device with desired Bluetooth profile - run `bluealsa --help` for the list - of available profiles. For querying currently connected audio profiles (and - connected devices), run `bluealsa-aplay --list-devices`. The common - misconception is an attempt to use A2DP playback device as a capture one in - case where A2DP is not listed in the "List of CAPTURE Bluetooth Devices" - section. - - Additionally, the cause of the "PCM not found" error might be an incorrect - ALSA PCM name. Run `bluealsa-aplay --list-pcms` for the list of currently - available ALSA PCM names - it might give you a hint what is wrong with your - `.asoundrc` entry. Also, take a look at the [bluealsa-plugins manual - page](doc/bluealsa-plugins.7.rst). - ## Contributing This project welcomes contributions of code, documentation and testing. @@ -270,23 +225,25 @@ Please see the [CONTRIBUTING](CONTRIBUTING.md) guide for details. ## Bug reports, feature requests, and requests for help -Before raising a new issue, please search previous issues (both open and -closed), to see if your question has already been answered or problem resolved. -If reporting a problem, please clearly state: - -* the OS distribution and version you are using, -* the version of BlueALSA that you are using (`bluealsa --version`), -* if self-built from source, please state the branch and commit - (`git log -1 --oneline`) and the configure options used, -* the version of BlueZ (`bluetoothd --version`), -* the version of ALSA (`aplay --version`), -* sufficient additional information for readers to be able to reproduce the -issue. - -Please also look at the [wiki](https://github.com/arkq/bluez-alsa/wiki) if you -require help as there is a great deal of useful information. Unfortunately the -wiki is not indexed by web search engines, so searching on-line for your issue -will not discover the information in there. +The most commonly encountered errors are discussed in the +[TROUBLESHOOTING] guide. Please check that file to see if there is already a +solution for your issue. + +If you are unable to find a solution in that document or by reading the +[manual pages][], then please search [previous issues][] (both open and +closed), and consult the [wiki][] before raising a new issue. Unfortunately +the wiki is not indexed by web search engines, so searching on-line for your +issue will not discover the information in there. + +If reporting a problem as a new issue, please use the appropriate +[bluez-alsa GitHub issue reporting template][] and complete each section of +the template as fully as possible. + +[TROUBLESHOOTING]: ./TROUBLESHOOTING.md +[manual pages]: doc/ +[previous issues]: https://github.com/arkq/bluez-alsa/issues +[wiki]: https://github.com/arkq/bluez-alsa/wiki +[bluez-alsa GitHub issue reporting template]: https://github.com/arkq/bluez-alsa/issues/new/choose ## License diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md new file mode 100644 index 0000000..412d6c1 --- /dev/null +++ b/TROUBLESHOOTING.md @@ -0,0 +1,148 @@ +# Troubleshooting BlueALSA + +This document presents solutions to some of the most commonly encountered +errors when using BlueALSA. + +## 1. Couldn't acquire D-Bus name: org.bluealsa + +The BlueALSA server registers a unique "well-known service name" with D-Bus, +which is used by client applications to identify the correct service instance. +By default it uses the name "org.bluealsa". There are three reasons why +starting the service may fail with the +`"Couldn't acquire D-Bus name: org.bluealsa"` error message: + +- The BlueALSA D-Bus policy file is not installed, or is in the wrong +location.\ +In a default install, the file should be +`/etc/dbus-1/system.d/bluealsa.conf`. Check with your distribution +documentation in case D-Bus uses a different location on your system. +Re-install BlueALSA if the file is missing. + +- The user account that the BlueALSA service is started under is not +permitted to do so by the D-Bus policy.\ +The BlueALSA D-Bus policy file must contain a rule that permits the BlueALSA +service account to register names with the prefix `org.bluealsa`. The default +BlueALSA D-Bus policy file permits only `root` to register the prefix +`org.bluealsa`. To permit some other user account the D-Bus policy must be +updated. For example to permit the BlueALSA service to run under the account +name `bluealsa` in addition to being able to run as `root`: + + ```xml + + + + + + + + + + + + + + + + + + ``` + +- Another instance of the BlueALSA service is already running.\ +To run a second instance of the BlueALSA service, it must use a different +well-known service name. This will also require updating the BlueALSA D-Bus +policy file. See the manual page [bluealsa(8)][] for more information and an +example of running multiple `bluealsa` instances. + +If the D-Bus policy file is edited, then it is necessary to refresh the D-Bus +service for the change to take effect. On most systems this can be achieved +with (as `root`) : + +```sh +systemctl reload dbus.service +``` + +[bluealsa(8)]: doc/bluealsa.8.rst + +## 2. Couldn't get BlueALSA PCM: PCM not found + +In contrast to standard ALSA sound cards, BlueALSA does not expose all PCMs +right away. In the first place it is required to connect remote Bluetooth +device with desired Bluetooth profile - run `bluealsa --help` for the list +of available profiles. For querying currently connected audio profiles (and +connected devices), run `bluealsa-aplay --list-devices`. The common +misconception is an attempt to use A2DP playback device as a capture one in +case where A2DP is not listed in the "List of CAPTURE Bluetooth Devices" +section. + +Additionally, the cause of the "PCM not found" error might be an incorrect +ALSA PCM name. Run `bluealsa-aplay --list-pcms` for the list of currently +available ALSA PCM names - it might give you a hint what is wrong with your +`.asoundrc` entry. Also, take a look at the [bluealsa-plugins manual +page](doc/bluealsa-plugins.7.rst). + +## 3. Couldn't get BlueALSA PCM: Rejected send message + +This error message indicates that the user does not have permission to use +the BlueALSA service. BlueALSA client applications require permission from +D-Bus to communicate with the BlueALSA service. This permission is granted +by a D-Bus policy configuration file. A default BlueALSA installation will +grant permission only to members of the `audio` group and `root` (this is in +line with normal practice on ALSA systems whereby membership of the `audio` +group is required to use sound card devices). + +There are several reasons why this permission is not granted: + +- The D-Bus service has not been refreshed after installing BlueALSA.\ +Try `sudo systemctl reload dbus.service`. If that does not work, try +rebooting. + +- The user is not a member of the `audio` group. + +- The user session was created before the user was added to the `audio` + group.\ +Log out, then log in again. + +- The BlueALSA D-Bus policy file is not installed, or is in the wrong +location.\ +In a default install, the file should be +`/etc/dbus-1/system.d/bluealsa.conf`. Check with your distribution +documentation in case D-Bus uses a different location on your system. +Re-install BlueALSA if the file is missing. + +## 4. Using BlueALSA alongside PulseAudio or PipeWire + +It is not advisable to run BlueALSA if either PulseAudio or PipeWire are also +running with their own Bluetooth modules enabled. If one would like to have a +deterministic setup, it is first necessary to disable Bluetooth in those +applications. + +On startup, the BlueALSA service will issue warnings if some other application +has already registered the Bluetooth Audio profiles: + +```text +bluealsa: W: UUID already registered in BlueZ [hci0]: 0000110A-0000-1000-8000-00805F9B34FB +bluealsa: W: UUID already registered in BlueZ [hci0]: 0000110B-0000-1000-8000-00805F9B34FB +bluealsa: W: UUID already registered in BlueZ: 0000111F-0000-1000-8000-00805F9B34FB +``` + +However, as it is normal practice to start BlueALSA at boot and to start +PulseAudio only when the user logs in, these warnings may not appear in the +logs. + +In the unlikely event that one should need to run BlueALSA at the same time as +PulseAudio, there are some hints on how to disable the PulseAudio Bluetooth +modules in the wiki: [PulseAudio integration][] + +[PulseAudio integration]: https://github.com/arkq/bluez-alsa/wiki/PulseAudio-integration + +## 5. ALSA thread-safe API (alsa-lib >= 1.1.2, <= 1.1.3) + +ALSA library versions 1.1.2 and 1.1.3 had a bug in their thread-safe API +functions. This bug does not affect hardware audio devices, but it affects +many software plug-ins. Random deadlocks are inevitable. The best advice is +to use a more recent alsa-lib release, or if that is not possible then +disable the thread locking code via an environment variable, as follows: + +```shell +export LIBASOUND_THREAD_SAFE=0 +``` commit 5819d2a4c93f7a087fee3898083666855895a01e Author: Arkadiusz Bokowy Date: Sun May 7 23:13:39 2023 +0200 Set properties of CallVolume during volume change diff --git a/NEWS b/NEWS index 5ea748f..f230fe2 100644 --- a/NEWS +++ b/NEWS @@ -2,10 +2,12 @@ unreleased ========== - persistent storage for PCM volume and mute state +- PCM volume control with oFono HFP-AG and HFP-HF profiles - export transport running state via PCM D-Bus interface - optional non-dynamic operation mode for ALSA control plug-in - optional extended controls for ALSA control plug-in - change RFCOMM D-Bus API features property to array of strings +- fix for SCO link establishment for oFono HFP-AG profile - fix for volume control for HSP-HS and HFP-HF profiles bluez-alsa v4.0.0 (2022-06-03) diff --git a/src/ba-transport.c b/src/ba-transport.c index 892e28b..c100285 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -63,6 +63,9 @@ #include "dbus.h" #include "hci.h" #include "hfp.h" +#if ENABLE_OFONO +# include "ofono.h" +#endif #include "sco.h" #include "storage.h" #include "shared/a2dp-codecs.h" @@ -1205,6 +1208,10 @@ void ba_transport_unref(struct ba_transport *t) { ba_rfcomm_destroy(t->sco.rfcomm); transport_pcm_free(&t->sco.spk_pcm); transport_pcm_free(&t->sco.mic_pcm); +#if ENABLE_OFONO + free(t->sco.ofono_dbus_path_card); + free(t->sco.ofono_dbus_path_modem); +#endif } #if DEBUG @@ -1780,10 +1787,16 @@ int ba_transport_pcm_volume_update(struct ba_transport_pcm *pcm) { } } - else if (t->profile & BA_TRANSPORT_PROFILE_MASK_SCO && - t->sco.rfcomm != NULL) { - /* notify associated RFCOMM transport */ - ba_rfcomm_send_signal(t->sco.rfcomm, BA_RFCOMM_SIGNAL_UPDATE_VOLUME); + else if (t->profile & BA_TRANSPORT_PROFILE_MASK_SCO) { + + if (t->sco.rfcomm != NULL) + /* notify associated RFCOMM transport */ + ba_rfcomm_send_signal(t->sco.rfcomm, BA_RFCOMM_SIGNAL_UPDATE_VOLUME); +#if ENABLE_OFONO + else + ofono_call_volume_update(t); +#endif + } final: diff --git a/src/ba-transport.h b/src/ba-transport.h index ae7b1c8..406c4f9 100644 --- a/src/ba-transport.h +++ b/src/ba-transport.h @@ -323,9 +323,17 @@ struct ba_transport { struct { - /* associated RFCOMM thread */ + /* Associated RFCOMM thread for SCO transport handled by local + * HSP/HFP implementation. Otherwise, this field is set to NULL. */ struct ba_rfcomm *rfcomm; +#if ENABLE_OFONO + /* Associated oFono card and modem paths. In case when SCO transport + * is not oFono-based, these fields are set to NULL. */ + char *ofono_dbus_path_card; + char *ofono_dbus_path_modem; +#endif + /* Speaker and microphone signals should to be exposed as * a separate PCM devices. Hence, there is a requirement * for separate configurations. diff --git a/src/ofono.c b/src/ofono.c index 236e87c..4612469 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -51,6 +51,7 @@ #include "ofono-iface.h" #include "ofono-skeleton.h" #include "utils.h" +#include "shared/defs.h" #include "shared/log.h" #include "shared/rt.h" @@ -80,10 +81,9 @@ static int ofono_acquire_bt_sco(struct ba_transport *t) { int fd = -1; int ret = 0; - const char *ofono_dbus_path = t->bluez_dbus_path; - debug("Requesting new oFono SCO link: %s", ofono_dbus_path); - msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, ofono_dbus_path, - OFONO_IFACE_HF_AUDIO_CARD, "Acquire"); + debug("Requesting new oFono SCO link: %s", t->sco.ofono_dbus_path_card); + msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, + t->sco.ofono_dbus_path_card, OFONO_IFACE_HF_AUDIO_CARD, "Acquire"); struct timespec now; struct timespec delay = { @@ -187,17 +187,30 @@ static struct ba_transport *ofono_transport_new( struct ba_device *device, enum ba_transport_profile profile, const char *dbus_owner, - const char *dbus_path) { + const char *dbus_path_card, + const char *dbus_path_modem) { struct ba_transport *t; + int err; - if ((t = ba_transport_new_sco(device, profile, dbus_owner, dbus_path, -1)) == NULL) + if ((t = ba_transport_new_sco(device, profile, dbus_owner, dbus_path_card, -1)) == NULL) return NULL; + if ((t->sco.ofono_dbus_path_card = strdup(dbus_path_card)) == NULL) + goto fail; + if ((t->sco.ofono_dbus_path_modem = strdup(dbus_path_modem)) == NULL) + goto fail; + t->acquire = ofono_acquire_bt_sco; t->release = ofono_release_bt_sco; return t; + +fail: + err = errno; + ba_transport_unref(t); + errno = err; + return NULL; } /** @@ -290,10 +303,9 @@ static void ofono_new_connection_request(struct ba_transport *t) { GDBusMessage *msg; - const char *ofono_dbus_path = t->bluez_dbus_path; - debug("Requesting new oFono SCO link: %s", ofono_dbus_path); - msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, ofono_dbus_path, - OFONO_IFACE_HF_AUDIO_CARD, "Connect"); + debug("Requesting new oFono SCO link: %s", t->sco.ofono_dbus_path_card); + msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, + t->sco.ofono_dbus_path_card, OFONO_IFACE_HF_AUDIO_CARD, "Connect"); g_dbus_connection_send_message_with_reply(config.dbus, msg, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, @@ -441,8 +453,8 @@ static unsigned int ofono_call_volume_property_sync(struct ba_transport *t, goto final; } - uint8_t volume = g_variant_get_byte(value); - int level = ba_transport_pcm_volume_range_to_level(volume, 100); + uint8_t volume = g_variant_get_byte(value) * HFP_VOLUME_GAIN_MAX / 100; + int level = ba_transport_pcm_volume_range_to_level(volume, HFP_VOLUME_GAIN_MAX); debug("Updating SCO speaker volume: %u [%.2f dB]", volume, 0.01 * level); mask |= OFONO_CALL_VOLUME_SPEAKER; @@ -461,8 +473,8 @@ static unsigned int ofono_call_volume_property_sync(struct ba_transport *t, goto final; } - uint8_t volume = g_variant_get_byte(value); - int level = ba_transport_pcm_volume_range_to_level(volume, 100); + uint8_t volume = g_variant_get_byte(value) * HFP_VOLUME_GAIN_MAX / 100; + int level = ba_transport_pcm_volume_range_to_level(volume, HFP_VOLUME_GAIN_MAX); debug("Updating SCO microphone volume: %u [%.2f dB]", volume, 0.01 * level); mask |= OFONO_CALL_VOLUME_MICROPHONE; @@ -478,15 +490,14 @@ final: /** * Get all oFono call volume properties and update transport volumes. */ -static int ofono_call_volume_get_properties(struct ba_transport *t, - const char *modem_path) { +static int ofono_call_volume_get_properties(struct ba_transport *t) { GDBusMessage *msg = NULL, *rep = NULL; GError *err = NULL; int ret = 0; - msg = g_dbus_message_new_method_call(OFONO_SERVICE, modem_path, - OFONO_IFACE_CALL_VOLUME, "GetProperties"); + msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, + t->sco.ofono_dbus_path_modem, OFONO_IFACE_CALL_VOLUME, "GetProperties"); if ((rep = g_dbus_connection_send_message_with_reply_sync(config.dbus, msg, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL) @@ -533,6 +544,41 @@ final: return ret; } +/** + * Set oFono call volume property. */ +static int ofono_call_volume_set_property(struct ba_transport *t, + const char *property, GVariant *value, GError **error) { + + GDBusMessage *msg = NULL, *rep = NULL; + int ret = 0; + + msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, + t->sco.ofono_dbus_path_modem, OFONO_IFACE_CALL_VOLUME, "SetProperty"); + + g_dbus_message_set_body(msg, g_variant_new("(sv)", property, value)); + + if ((rep = g_dbus_connection_send_message_with_reply_sync(config.dbus, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, error)) == NULL) + goto fail; + + if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) { + g_dbus_message_to_gerror(rep, error); + goto fail; + } + + goto final; + +fail: + ret = -1; + +final: + if (msg != NULL) + g_object_unref(msg); + if (rep != NULL) + g_object_unref(rep); + return ret; +} + /** * Add new oFono card (phone). */ static void ofono_card_add(const char *dbus_sender, const char *card, @@ -601,7 +647,8 @@ static void ofono_card_add(const char *dbus_sender, const char *card, goto fail; } - if ((t = ofono_transport_new(d, profile, dbus_sender, card)) == NULL) { + if ((t = ofono_transport_new(d, profile, dbus_sender, + card, ocd->modem_path)) == NULL) { error("Couldn't create new transport: %s", strerror(errno)); goto fail; } @@ -614,7 +661,7 @@ static void ofono_card_add(const char *dbus_sender, const char *card, #endif /* initialize speaker and microphone volumes */ - ofono_call_volume_get_properties(t, ocd->modem_path); + ofono_call_volume_get_properties(t); g_hash_table_insert(ofono_card_data_map, ocd->card, ocd); ocd = NULL; @@ -1051,3 +1098,37 @@ bool ofono_detect_service(void) { return status; } + +/** + * Update oFono call volume properties. */ +int ofono_call_volume_update(struct ba_transport *t) { + + struct ba_transport_pcm *spk = &t->sco.spk_pcm; + struct ba_transport_pcm *mic = &t->sco.mic_pcm; + int ret = 0; + + struct { + const char *name; + GVariant *value; + } props[] = { + { "Muted", + g_variant_new_boolean(mic->volume[0].scale == 0) }, + { "SpeakerVolume", + g_variant_new_byte(MIN(100, + ba_transport_pcm_volume_level_to_range(spk->volume[0].level, 106))) }, + { "MicrophoneVolume", + g_variant_new_byte(MIN(100, + ba_transport_pcm_volume_level_to_range(mic->volume[0].level, 106))) }, + }; + + for (size_t i = 0; i < ARRAYSIZE(props); i++) { + GError *err = NULL; + if (ofono_call_volume_set_property(t, props[i].name, props[i].value, &err) == -1) { + error("Couldn't set oFono call volume: %s: %s", props[i].name, err->message); + g_error_free(err); + ret = -1; + } + } + + return ret; +} diff --git a/src/ofono.h b/src/ofono.h index 766693e..1c8e6d7 100644 --- a/src/ofono.h +++ b/src/ofono.h @@ -19,7 +19,11 @@ #include +#include "ba-transport.h" + int ofono_init(void); bool ofono_detect_service(void); +int ofono_call_volume_update(struct ba_transport *t); + #endif diff --git a/test/mock/mock-bluealsa.c b/test/mock/mock-bluealsa.c index caf8954..75e1ff5 100644 --- a/test/mock/mock-bluealsa.c +++ b/test/mock/mock-bluealsa.c @@ -105,6 +105,12 @@ void bluez_battery_provider_update(struct ba_device *device) { (void)device; } +int ofono_call_volume_update(struct ba_transport *transport) { + debug("%s: %p", __func__, transport); + (void)transport; + return 0; +} + static void *mock_dec(struct ba_transport_thread *th) { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); diff --git a/test/test-ba.c b/test/test-ba.c index 8945246..6b5e274 100644 --- a/test/test-ba.c +++ b/test/test-ba.c @@ -44,6 +44,9 @@ #include "bluealsa-dbus.h" #include "bluez.h" #include "hfp.h" +#if ENABLE_OFONO +# include "ofono.h" +#endif #include "storage.h" #include "shared/a2dp-codecs.h" #include "shared/log.h" @@ -88,6 +91,8 @@ bool bluez_a2dp_set_configuration(const char *current_dbus_sep_path, const struct a2dp_sep *sep, GError **error) { debug("%s: %s: %p", __func__, current_dbus_sep_path, sep); (void)current_dbus_sep_path; (void)sep; (void)error; return false; } +int ofono_call_volume_update(struct ba_transport *t) { + debug("%s: %p", __func__, t); (void)t; return 0; } CK_START_TEST(test_ba_adapter) { diff --git a/test/test-io.c b/test/test-io.c index de8fd0f..f1b4c09 100644 --- a/test/test-io.c +++ b/test/test-io.c @@ -68,6 +68,9 @@ #include "bluez.h" #include "hfp.h" #include "io.h" +#if ENABLE_OFONO +# include "ofono.h" +#endif #if ENABLE_LC3PLUS || ENABLE_LDAC # include "rtp.h" #endif @@ -122,6 +125,8 @@ bool bluez_a2dp_set_configuration(const char *current_dbus_sep_path, const struct a2dp_sep *sep, GError **error) { debug("%s: %s: %p", __func__, current_dbus_sep_path, sep); (void)current_dbus_sep_path; (void)sep; (void)error; return false; } +int ofono_call_volume_update(struct ba_transport *t) { + debug("%s: %p", __func__, t); (void)t; return 0; } int storage_device_load(const struct ba_device *d) { (void)d; return 0; } int storage_device_save(const struct ba_device *d) { (void)d; return 0; } int storage_pcm_data_sync(struct ba_transport_pcm *pcm) { (void)pcm; return 0; } diff --git a/test/test-rfcomm.c b/test/test-rfcomm.c index 5e78150..f467477 100644 --- a/test/test-rfcomm.c +++ b/test/test-rfcomm.c @@ -108,6 +108,8 @@ bool bluez_a2dp_set_configuration(const char *current_dbus_sep_path, (void)current_dbus_sep_path; (void)sep; (void)error; return false; } void bluez_battery_provider_update(struct ba_device *device) { debug("%s: %p", __func__, device); (void)device; } +int ofono_call_volume_update(struct ba_transport *t) { + debug("%s: %p", __func__, t); (void)t; return 0; } #define ck_assert_rfcomm_recv(fd, command) { \ char buffer[sizeof(command)] = { 0 }; \ commit 5139b5149813fef932a7fb53d6efc1b0629e27d9 Author: Arkadiusz Bokowy Date: Thu May 4 18:48:45 2023 +0200 Receive updates from oFono CallVolume interface diff --git a/src/bluez.c b/src/bluez.c index 8eef197..d8e17ea 100644 --- a/src/bluez.c +++ b/src/bluez.c @@ -1375,26 +1375,26 @@ static void bluez_signal_transport_changed(GDBusConnection *conn, const char *se int hci_dev_id = g_dbus_bluez_object_path_to_hci_dev_id(transport_path); if ((a = ba_adapter_lookup(hci_dev_id)) == NULL) { error("Adapter not available: %s", transport_path); - return; + goto fail; } - GVariantIter *properties = NULL; - const char *interface; - const char *property; - GVariant *value; - bdaddr_t addr; g_dbus_bluez_object_path_to_bdaddr(transport_path, &addr); if ((d = ba_device_lookup(a, &addr)) == NULL) { error("Device not available: %s", transport_path); - goto final; + goto fail; } if ((t = ba_transport_lookup(d, transport_path)) == NULL) { error("Transport not available: %s", transport_path); - goto final; + goto fail; } + GVariantIter *properties; + const char *interface; + const char *property; + GVariant *value; + g_variant_get(params, "(&sa{sv}as)", &interface, &properties, NULL); while (g_variant_iter_next(properties, "{&sv}", &property, &value)) { debug("Signal: %s.%s(): %s: %s", interface_, signal, interface, property); @@ -1435,7 +1435,7 @@ static void bluez_signal_transport_changed(GDBusConnection *conn, const char *se } g_variant_iter_free(properties); -final: +fail: if (a != NULL) ba_adapter_unref(a); if (d != NULL) diff --git a/src/ofono-iface.h b/src/ofono-iface.h index 42552c1..9be6811 100644 --- a/src/ofono-iface.h +++ b/src/ofono-iface.h @@ -1,6 +1,6 @@ /* * BlueALSA - ofono-iface.h - * Copyright (c) 2016-2020 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * Copyright (c) 2018 Thierry Bultel * * This file is a part of bluez-alsa. @@ -20,6 +20,7 @@ #define OFONO_IFACE_HF_AUDIO_AGENT OFONO_SERVICE ".HandsfreeAudioAgent" #define OFONO_IFACE_HF_AUDIO_CARD OFONO_SERVICE ".HandsfreeAudioCard" #define OFONO_IFACE_HF_AUDIO_MANAGER OFONO_SERVICE ".HandsfreeAudioManager" +#define OFONO_IFACE_CALL_VOLUME OFONO_SERVICE ".CallVolume" #define OFONO_AUDIO_CARD_TYPE_AG "gateway" #define OFONO_AUDIO_CARD_TYPE_HF "handsfree" diff --git a/src/ofono.c b/src/ofono.c index da71881..236e87c 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -1,6 +1,6 @@ /* * BlueALSA - ofono.c - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * Copyright (c) 2018 Thierry Bultel * * This file is a part of bluez-alsa. @@ -44,11 +44,13 @@ #include "ba-device.h" #include "ba-transport.h" #include "bluealsa-config.h" +#include "bluealsa-dbus.h" #include "dbus.h" #include "hci.h" #include "hfp.h" #include "ofono-iface.h" #include "ofono-skeleton.h" +#include "utils.h" #include "shared/log.h" #include "shared/rt.h" @@ -236,6 +238,26 @@ static struct ba_transport *ofono_transport_lookup_card(const char *card) { return NULL; } +/** + * Lookup a transport associated with oFono modem. + * + * @param modem A path associated with oFono modem. + * @return On success this function returns a transport associated with + * a given oFono modem path. Otherwise, NULL is returned. */ +static struct ba_transport *ofono_transport_lookup_modem(const char *modem) { + + GHashTableIter iter; + struct ofono_card_data *ocd; + + g_hash_table_iter_init(&iter, ofono_card_data_map); + while (g_hash_table_iter_next(&iter, NULL, (void *)&ocd)) { + if (strcmp(ocd->modem_path, modem) == 0) + return ofono_transport_lookup(ocd); + } + + return NULL; +} + #if ENABLE_MSBC static void ofono_new_connection_finish(GObject *source, GAsyncResult *result, void *userdata) { @@ -380,6 +402,137 @@ fail: return ret; } +#define OFONO_CALL_VOLUME_NONE (0) +#define OFONO_CALL_VOLUME_SPEAKER (1 << 0) +#define OFONO_CALL_VOLUME_MICROPHONE (1 << 1) + +static unsigned int ofono_call_volume_property_sync(struct ba_transport *t, + const char *property, GVariant *value) { + + struct ba_transport_pcm *spk = &t->sco.spk_pcm; + struct ba_transport_pcm *mic = &t->sco.mic_pcm; + unsigned int mask = OFONO_CALL_VOLUME_NONE; + + if (strcmp(property, "Muted") == 0 && + g_variant_validate_value(value, G_VARIANT_TYPE_BOOLEAN, property)) { + + if (t->profile & BA_TRANSPORT_PROFILE_MASK_AG && + mic->soft_volume) { + debug("Skipping SCO microphone mute update: %s", "Software volume enabled"); + goto final; + } + + bool muted = g_variant_get_boolean(value); + debug("Updating SCO microphone mute: %s", muted ? "true" : "false"); + mask |= OFONO_CALL_VOLUME_MICROPHONE; + + pthread_mutex_lock(&mic->mutex); + ba_transport_pcm_volume_set(&mic->volume[0], NULL, &muted, NULL); + pthread_mutex_unlock(&mic->mutex); + + } + else if (strcmp(property, "SpeakerVolume") == 0 && + g_variant_validate_value(value, G_VARIANT_TYPE_BYTE, property)) { + /* received volume is in range [0, 100] */ + + if (t->profile & BA_TRANSPORT_PROFILE_MASK_AG && + spk->soft_volume) { + debug("Skipping SCO speaker volume update: %s", "Software volume enabled"); + goto final; + } + + uint8_t volume = g_variant_get_byte(value); + int level = ba_transport_pcm_volume_range_to_level(volume, 100); + debug("Updating SCO speaker volume: %u [%.2f dB]", volume, 0.01 * level); + mask |= OFONO_CALL_VOLUME_SPEAKER; + + pthread_mutex_lock(&spk->mutex); + ba_transport_pcm_volume_set(&spk->volume[0], &level, NULL, NULL); + pthread_mutex_unlock(&spk->mutex); + + } + else if (strcmp(property, "MicrophoneVolume") == 0 && + g_variant_validate_value(value, G_VARIANT_TYPE_BYTE, property)) { + /* received volume is in range [0, 100] */ + + if (t->profile & BA_TRANSPORT_PROFILE_MASK_AG && + mic->soft_volume) { + debug("Skipping SCO microphone volume update: %s", "Software volume enabled"); + goto final; + } + + uint8_t volume = g_variant_get_byte(value); + int level = ba_transport_pcm_volume_range_to_level(volume, 100); + debug("Updating SCO microphone volume: %u [%.2f dB]", volume, 0.01 * level); + mask |= OFONO_CALL_VOLUME_MICROPHONE; + + pthread_mutex_lock(&mic->mutex); + ba_transport_pcm_volume_set(&mic->volume[0], &level, NULL, NULL); + pthread_mutex_unlock(&mic->mutex); + + } + +final: + return mask; +} + +/** + * Get all oFono call volume properties and update transport volumes. */ +static int ofono_call_volume_get_properties(struct ba_transport *t, + const char *modem_path) { + + GDBusMessage *msg = NULL, *rep = NULL; + GError *err = NULL; + int ret = 0; + + msg = g_dbus_message_new_method_call(OFONO_SERVICE, modem_path, + OFONO_IFACE_CALL_VOLUME, "GetProperties"); + + if ((rep = g_dbus_connection_send_message_with_reply_sync(config.dbus, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL) + goto fail; + + if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) { + g_dbus_message_to_gerror(rep, &err); + goto fail; + } + + GVariantIter *properties; + g_variant_get(g_dbus_message_get_body(rep), "(a{sv})", &properties); + + const char *property; + GVariant *value; + + unsigned int mask = OFONO_CALL_VOLUME_NONE; + while (g_variant_iter_next(properties, "{&sv}", &property, &value)) { + mask |= ofono_call_volume_property_sync(t, property, value); + g_variant_unref(value); + } + + if (mask & OFONO_CALL_VOLUME_SPEAKER) + bluealsa_dbus_pcm_update(&t->sco.spk_pcm, BA_DBUS_PCM_UPDATE_VOLUME); + if (mask & OFONO_CALL_VOLUME_MICROPHONE) + bluealsa_dbus_pcm_update(&t->sco.mic_pcm, BA_DBUS_PCM_UPDATE_VOLUME); + + g_variant_iter_free(properties); + goto final; + +fail: + ret = -1; + +final: + if (msg != NULL) + g_object_unref(msg); + if (rep != NULL) + g_object_unref(rep); + if (err != NULL) { + error("Couldn't get oFono call volume: %s", err->message); + g_error_free(err); + } + + return ret; +} + /** * Add new oFono card (phone). */ static void ofono_card_add(const char *dbus_sender, const char *card, @@ -460,6 +613,9 @@ static void ofono_card_add(const char *dbus_sender, const char *card, ofono_new_connection_request(t); #endif + /* initialize speaker and microphone volumes */ + ofono_call_volume_get_properties(t, ocd->modem_path); + g_hash_table_insert(ofono_card_data_map, ocd->card, ocd); ocd = NULL; @@ -741,7 +897,6 @@ final: static void ofono_signal_card_added(GDBusConnection *conn, const char *sender, const char *path, const char *interface, const char *signal, GVariant *params, void *userdata) { - debug("Signal: %s.%s()", interface, signal); (void)conn; (void)path; (void)interface; @@ -752,9 +907,12 @@ static void ofono_signal_card_added(GDBusConnection *conn, const char *sender, GVariantIter *properties = NULL; g_variant_get(params, "(&oa{sv})", &card, &properties); + debug("Signal: %s.%s(%s, ...)", interface, signal, card); + ofono_card_add(sender, card, properties); g_variant_iter_free(properties); + } /** @@ -762,7 +920,6 @@ static void ofono_signal_card_added(GDBusConnection *conn, const char *sender, static void ofono_signal_card_removed(GDBusConnection *conn, const char *sender, const char *path, const char *interface, const char *signal, GVariant *params, void *userdata) { - debug("Signal: %s.%s()", interface, signal); (void)conn; (void)sender; (void)path; @@ -772,8 +929,7 @@ static void ofono_signal_card_removed(GDBusConnection *conn, const char *sender, const char *card = NULL; g_variant_get(params, "(&o)", &card); - - debug("Removing oFono card: %s", card); + debug("Signal: %s.%s(%s)", interface, signal, card); struct ba_transport *t; if ((t = ofono_transport_lookup_card(card)) != NULL) @@ -783,6 +939,40 @@ static void ofono_signal_card_removed(GDBusConnection *conn, const char *sender, } +/** + * Callback for the PropertyChanged signal on the CallVolume interface. */ +static void ofono_signal_volume_changed(GDBusConnection *conn, const char *sender, + const char *modem_path, const char *interface, const char *signal, GVariant *params, + void *userdata) { + (void)conn; + (void)sender; + (void)interface; + (void)signal; + (void)userdata; + + struct ba_transport *t; + if ((t = ofono_transport_lookup_modem(modem_path)) == NULL) { + error("Couldn't lookup transport: %s: %s", modem_path, strerror(errno)); + return; + } + + const char *property; + GVariant *value; + + g_variant_get(params, "(&sv)", &property, &value); + debug("Signal: %s.%s(%s, ...)", interface, signal, property); + + unsigned int mask = ofono_call_volume_property_sync(t, property, value); + if (mask & OFONO_CALL_VOLUME_SPEAKER) + bluealsa_dbus_pcm_update(&t->sco.spk_pcm, BA_DBUS_PCM_UPDATE_VOLUME); + if (mask & OFONO_CALL_VOLUME_MICROPHONE) + bluealsa_dbus_pcm_update(&t->sco.mic_pcm, BA_DBUS_PCM_UPDATE_VOLUME); + + g_variant_unref(value); + ba_transport_unref(t); + +} + /** * Monitor oFono service appearance. */ static void ofono_appeared(GDBusConnection *conn, const char *name, @@ -827,6 +1017,10 @@ int ofono_init(void) { OFONO_IFACE_HF_AUDIO_MANAGER, "CardRemoved", NULL, NULL, G_DBUS_SIGNAL_FLAGS_NONE, ofono_signal_card_removed, NULL, NULL); + g_dbus_connection_signal_subscribe(config.dbus, OFONO_SERVICE, + OFONO_IFACE_CALL_VOLUME, "PropertyChanged", NULL, NULL, + G_DBUS_SIGNAL_FLAGS_NONE, ofono_signal_volume_changed, NULL, NULL); + g_bus_watch_name_on_connection(config.dbus, OFONO_SERVICE, G_BUS_NAME_WATCHER_FLAGS_NONE, ofono_appeared, ofono_disappeared, NULL, NULL); commit 711c66626ab6a4363ece60b018b0b09fab797fb8 Author: Arkadiusz Bokowy Date: Sat May 6 17:33:21 2023 +0200 Improve oFono card data lookup when removing cards diff --git a/src/main.c b/src/main.c index 7c1e9e9..f0b276a 100644 --- a/src/main.c +++ b/src/main.c @@ -602,6 +602,7 @@ int main(int argc, char **argv) { for (size_t i = 0; i < ARRAYSIZE(config.adapters); i++) ba_adapter_destroy(config.adapters[i]); + storage_destroy(); g_dbus_connection_close_sync(config.dbus, NULL, NULL); g_main_loop_unref(loop); g_free(address); diff --git a/src/ofono.c b/src/ofono.c index 40471a7..da71881 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -199,29 +199,18 @@ static struct ba_transport *ofono_transport_new( } /** - * Lookup a transport associated with oFono card. - * - * @param card A path associated with oFono card. - * @return On success this function returns a transport associated with - * a given oFono card path. Otherwise, NULL is returned. */ -static struct ba_transport *ofono_transport_lookup(const char *card) { + * Lookup a transport associated with oFono card data. */ +static struct ba_transport *ofono_transport_lookup(struct ofono_card_data *ocd) { struct ba_adapter *a = NULL; struct ba_device *d = NULL; struct ba_transport *t = NULL; - struct ofono_card_data *ocd; - if ((ocd = g_hash_table_lookup(ofono_card_data_map, card)) == NULL) { - error("Couldn't lookup oFono card data: %s", card); - goto fail; - } - if ((a = ba_adapter_lookup(ocd->hci_dev_id)) == NULL) goto fail; if ((d = ba_device_lookup(a, &ocd->bt_addr)) == NULL) goto fail; - if ((t = ba_transport_lookup(d, card)) == NULL) - goto fail; + t = ba_transport_lookup(d, ocd->card); fail: if (a != NULL) @@ -231,6 +220,22 @@ fail: return t; } +/** + * Lookup a transport associated with oFono card. + * + * @param card A path associated with oFono card. + * @return On success this function returns a transport associated with + * a given oFono card path. Otherwise, NULL is returned. */ +static struct ba_transport *ofono_transport_lookup_card(const char *card) { + + struct ofono_card_data *ocd; + if ((ocd = g_hash_table_lookup(ofono_card_data_map, card)) != NULL) + return ofono_transport_lookup(ocd); + + error("Couldn't lookup oFono card data: %s", card); + return NULL; +} + #if ENABLE_MSBC static void ofono_new_connection_finish(GObject *source, GAsyncResult *result, void *userdata) { @@ -527,15 +532,15 @@ final: static void ofono_remove_all_cards(void) { GHashTableIter iter; - const char *card = NULL; + struct ofono_card_data *ocd; g_hash_table_iter_init(&iter, ofono_card_data_map); - while (g_hash_table_iter_next(&iter, (gpointer)&card, NULL)) { + while (g_hash_table_iter_next(&iter, NULL, (void *)&ocd)) { - debug("Removing oFono card: %s", card); + debug("Removing oFono card: %s", ocd->card); struct ba_transport *t; - if ((t = ofono_transport_lookup(card)) != NULL) + if ((t = ofono_transport_lookup(ocd)) != NULL) ba_transport_destroy(t); } @@ -564,7 +569,7 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat goto fail; } - if ((t = ofono_transport_lookup(card)) == NULL) { + if ((t = ofono_transport_lookup_card(card)) == NULL) { error("Couldn't lookup transport: %s: %s", card, strerror(errno)); goto fail; } @@ -771,7 +776,7 @@ static void ofono_signal_card_removed(GDBusConnection *conn, const char *sender, debug("Removing oFono card: %s", card); struct ba_transport *t; - if ((t = ofono_transport_lookup(card)) != NULL) + if ((t = ofono_transport_lookup_card(card)) != NULL) ba_transport_destroy(t); g_hash_table_remove(ofono_card_data_map, card); diff --git a/src/storage.c b/src/storage.c index d121d7f..b604e31 100644 --- a/src/storage.c +++ b/src/storage.c @@ -93,6 +93,15 @@ int storage_init(const char *root) { return 0; } +/** + * Cleanup resources allocated by the persistent storage. */ +void storage_destroy(void) { + if (storage_map == NULL) + return; + g_hash_table_destroy(storage_map); + storage_map = NULL; +} + /** * Load persistent storage file for the given BT device. */ int storage_device_load(const struct ba_device *d) { diff --git a/src/storage.h b/src/storage.h index 993166b..d4e4ef5 100644 --- a/src/storage.h +++ b/src/storage.h @@ -1,6 +1,6 @@ /* * BlueALSA - storage.h - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -20,6 +20,7 @@ #include "ba-transport.h" int storage_init(const char *root); +void storage_destroy(void); int storage_device_load(const struct ba_device *d); int storage_device_save(const struct ba_device *d); commit d21ed50570ee14f13d1771a5023fd377735d9d04 Author: borine <32966433+borine@users.noreply.github.com> Date: Mon Jan 9 17:19:06 2023 +0000 Enable oFono AG support with mSBC codec BlueALSA refuses to acquire transport on client Open() call if the codec is not set; and oFono will not create the codec connection until the first acquisition. We avoid this stand-off by performing a temporary acquisition request when the device connects. The codec reported by the response to this request from oFono is then used as the codec for subsequent client requests. diff --git a/doc/bluealsa-plugins.7.rst b/doc/bluealsa-plugins.7.rst index 5983e66..c0327b1 100644 --- a/doc/bluealsa-plugins.7.rst +++ b/doc/bluealsa-plugins.7.rst @@ -530,7 +530,7 @@ When the BlueALSA PCM plugin is used on a source A2DP or gateway HFP/HSP node, then **bluealsa(8)** will automatically acquire the transport and begin audio transfer when the plugin starts the PCM. -When used on an A2DP sink or HFP/HSP target node then **bluealsa(8)** must wait +When used on an A2DP sink or HFP/HSP HF/HS node then **bluealsa(8)** must wait for the remote device to acquire the transport. During this waiting time the PCM plugin behaves as if the device "clock" is stopped, it does not generate any poll() events, and the application will be blocked when writing or reading diff --git a/doc/bluealsa.8.rst b/doc/bluealsa.8.rst index 1b35415..cdbfed7 100644 --- a/doc/bluealsa.8.rst +++ b/doc/bluealsa.8.rst @@ -238,7 +238,7 @@ NOTES Profiles -------- -BlueALSA provides support for Bluetooth Advanced Audio Distribution Profile +**bluealsa** provides support for Bluetooth Advanced Audio Distribution Profile (A2DP), Hands-Free Profile (HFP) and Headset Profile (HSP). A2DP profile is dedicated for streaming music (i.e., stereo, 48 kHz or more sampling frequency), while HFP and HSP for two-way voice transmission (mono, 8 @@ -255,20 +255,32 @@ roles, although it is most common to use it either as a source/gateway: bluealsa -p a2dp-source -p hfp-ag -p hsp-ag -or as a sink/target, either with oFono: +or as a sink/target: :: - bluealsa -p a2dp-sink -p hfp-ofono + bluealsa -p a2dp-sink -p hfp-hf -p hsp-hs + +or with oFono for HFP support, -or without oFono: +source/gateway: :: - bluealsa -p a2dp-sink -p hfp-hf -p hsp-hs + bluealsa -p a2dp-source -p hfp-ofono -p hsp-ag + +sink/target: + +:: + + bluealsa -p a2dp-sink -p hfp-ofono -p hsp-hs + +With A2DP, **bluealsa** always includes the mandatory SBC codec and may also +include various optional codecs like AAC, aptX, and other. + +With HFP, **bluealsa** always includes the mandatory CVSD codec and may also +include the optional mSBC codec. -With A2DP, BlueALSA includes mandatory SBC codec and various optional codecs -like AAC, aptX, and other. The full list of available optional codecs, which depends on selected compilation options, will be shown with **bluealsa** command-line help message. diff --git a/src/ofono.c b/src/ofono.c index 160bedd..40471a7 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -20,7 +20,6 @@ /* IWYU pragma: no_include "config.h" */ #include -#include #include #include #include @@ -47,6 +46,7 @@ #include "bluealsa-config.h" #include "dbus.h" #include "hci.h" +#include "hfp.h" #include "ofono-iface.h" #include "ofono-skeleton.h" #include "shared/log.h" @@ -68,28 +68,6 @@ static GHashTable *ofono_card_data_map = NULL; static const char *dbus_agent_object_path = "/org/bluez/HFP/oFono"; static ofono_HFAudioAgentIfaceSkeleton *dbus_hf_agent = NULL; -/** - * Authorize oFono SCO connection. - * - * @param fd SCO socket file descriptor. - * @return On success this function returns 0. Otherwise, -1 is returned and - * errno is set to indicate the error. */ -static int ofono_sco_socket_authorize(int fd) { - - struct pollfd pfd = { fd, POLLOUT, 0 }; - char c; - - if (poll(&pfd, 1, 0) == -1) - return -1; - - /* If socket is not writable, it means that it is in the defer setup - * state, so it needs to be read to authorize the connection. */ - if (!(pfd.revents & POLLOUT) && read(fd, &c, 1) == -1) - return -1; - - return 0; -} - /** * Ask oFono to connect to a card. */ static int ofono_acquire_bt_sco(struct ba_transport *t) { @@ -133,6 +111,21 @@ static int ofono_acquire_bt_sco(struct ba_transport *t) { if ((fd = g_unix_fd_list_get(fd_list, 0, &err)) == -1) goto fail; +#if ENABLE_MSBC + if (codec != ba_transport_get_codec(t)) { + /* Although this connection has succeeded, it is not the codec expected + * by the client. So we have to return an error ... */ + error("Rejecting oFono SCO link: %s", "Codec mismatch"); + /* ... but still update the codec ready for next client request. */ + ba_transport_set_codec(t, codec); + if (fd != -1) { + shutdown(fd, SHUT_RDWR); + close(fd); + } + goto fail; + } +#endif + t->bt_fd = fd; t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a->hci.type); ba_transport_set_codec(t, codec); @@ -238,6 +231,52 @@ fail: return t; } +#if ENABLE_MSBC +static void ofono_new_connection_finish(GObject *source, GAsyncResult *result, + void *userdata) { + (void)userdata; + + GError *err = NULL; + GDBusMessage *rep = g_dbus_connection_send_message_with_reply_finish( + G_DBUS_CONNECTION(source), result, &err); + if (rep != NULL && + g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) + g_dbus_message_to_gerror(rep, &err); + + if (rep != NULL) + g_object_unref(rep); + if (err != NULL) { + error("Couldn't establish oFono SCO link: %s", err->message); + g_error_free(err); + } + +} + +/** + * Ask oFono to create an HFP codec connection. + * + * Codec selection can take a long time with oFono (up to 20 seconds with + * some devices) so we make the request asynchronously. oFono will invoke + * the HandsFreeAudioAgent NewConnection method when the codec selection is + * complete. */ +static void ofono_new_connection_request(struct ba_transport *t) { + + GDBusMessage *msg; + + const char *ofono_dbus_path = t->bluez_dbus_path; + debug("Requesting new oFono SCO link: %s", ofono_dbus_path); + msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, ofono_dbus_path, + OFONO_IFACE_HF_AUDIO_CARD, "Connect"); + + g_dbus_connection_send_message_with_reply(config.dbus, msg, + G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, + ofono_new_connection_finish, NULL); + + g_object_unref(msg); + +} +#endif + /** * Link oFono card with a modem. */ static int ofono_card_link_modem(struct ofono_card_data *ocd) { @@ -409,6 +448,13 @@ static void ofono_card_add(const char *dbus_sender, const char *card, goto fail; } +#if ENABLE_MSBC + if (config.hfp.codecs.msbc && + profile == BA_TRANSPORT_PROFILE_HFP_AG && + ba_transport_get_codec(t) == HFP_CODEC_UNDEFINED) + ofono_new_connection_request(t); +#endif + g_hash_table_insert(ofono_card_data_map, ocd->card, ocd); ocd = NULL; @@ -523,11 +569,42 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat goto fail; } - if (ofono_sco_socket_authorize(fd) == -1) { - error("Couldn't authorize oFono SCO link: %s", strerror(errno)); - goto fail; +#if ENABLE_MSBC + /* In AG mode, we obtain the codec when the device connects by + * performing a temporary acquisition. The response to that initial + * acquisition request is the only situation in which this function + * is called with the transport codec not yet set. */ + if (config.hfp.codecs.msbc && + t->profile == BA_TRANSPORT_PROFILE_HFP_AG && + ba_transport_get_codec(t) == HFP_CODEC_UNDEFINED) { + + /* Immediately release the SCO connection to save battery: we are only + * interested in the selected codec here. */ + if (fd != -1) { + shutdown(fd, SHUT_RDWR); + close(fd); + } + + debug("Initialized oFono SCO link codec: %#x", codec); + ba_transport_set_codec(t, codec); + ba_transport_unref(t); + + g_dbus_method_invocation_return_value(inv, NULL); + return; } + /* For HF, oFono does not authorize after setting the voice option, so we + * have to do it ourselves here. */ + if (t->profile == BA_TRANSPORT_PROFILE_HFP_HF && + codec == HFP_CODEC_MSBC) { + uint8_t auth; + if (read(fd, &auth, sizeof(auth)) == -1) { + error("Couldn't authorize oFono SCO link: %s", strerror(errno)); + goto fail; + } + } +#endif + ba_transport_stop(t); pthread_mutex_lock(&t->bt_fd_mtx); @@ -550,8 +627,10 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat fail: g_dbus_method_invocation_return_error(inv, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, "Unable to get connection"); - if (fd != -1) + if (fd != -1) { + shutdown(fd, SHUT_RDWR); close(fd); + } final: if (t != NULL) diff --git a/src/ofono.h b/src/ofono.h index 7da6673..766693e 100644 --- a/src/ofono.h +++ b/src/ofono.h @@ -13,6 +13,10 @@ #ifndef BLUEALSA_OFONO_H_ #define BLUEALSA_OFONO_H_ +#if HAVE_CONFIG_H +# include +#endif + #include int ofono_init(void); commit 62dfa347cc611435a5f8ed79220ccce931c353f6 Author: borine <32966433+borine@users.noreply.github.com> Date: Fri Apr 21 08:36:56 2023 +0100 Fix SCO link establishment with oFono AG profile diff --git a/src/ba-transport.c b/src/ba-transport.c index 2d7f65d..892e28b 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -696,7 +696,6 @@ fail: static int transport_acquire_bt_a2dp(struct ba_transport *t) { GDBusMessage *msg, *rep; - GUnixFDList *fd_list; GError *err = NULL; int fd = -1; @@ -717,12 +716,13 @@ static int transport_acquire_bt_a2dp(struct ba_transport *t) { g_variant_get(g_dbus_message_get_body(rep), "(hqq)", NULL, &mtu_read, &mtu_write); - t->mtu_read = mtu_read; - t->mtu_write = mtu_write; + GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list(rep); + if ((fd = g_unix_fd_list_get(fd_list, 0, &err)) == -1) + goto fail; - fd_list = g_dbus_message_get_unix_fd_list(rep); - fd = g_unix_fd_list_get(fd_list, 0, &err); t->bt_fd = fd; + t->mtu_read = mtu_read; + t->mtu_write = mtu_write; /* Minimize audio delay and increase responsiveness (seeking, stopping) by * decreasing the BT socket output buffer. We will use a tripled write MTU @@ -902,7 +902,7 @@ static int transport_release_bt_sco(struct ba_transport *t) { close(t->bt_fd); t->bt_fd = -1; - /* Keep the time-stamp when the SCO link has been close. It will be used + /* Keep the time-stamp when the SCO link has been closed. It will be used * for calculating close-connect quirk delay in the acquire function. */ gettimestamp(&t->sco.closed_at); diff --git a/src/bluez.c b/src/bluez.c index a862424..8eef197 100644 --- a/src/bluez.c +++ b/src/bluez.c @@ -824,13 +824,12 @@ static void bluez_profile_new_connection(GDBusMethodInvocation *inv, void *userd const char *device_path; GVariantIter *properties; - GUnixFDList *fd_list; GError *err = NULL; int fd = -1; - g_variant_get(params, "(&oha{sv})", &device_path, &fd, &properties); + g_variant_get(params, "(&oha{sv})", &device_path, NULL, &properties); - fd_list = g_dbus_message_get_unix_fd_list(msg); + GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list(msg); if ((fd = g_unix_fd_list_get(fd_list, 0, &err)) == -1) { error("Couldn't obtain RFCOMM socket: %s", err->message); goto fail; diff --git a/src/ofono.c b/src/ofono.c index 71f2cec..160bedd 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include @@ -48,6 +50,7 @@ #include "ofono-iface.h" #include "ofono-skeleton.h" #include "shared/log.h" +#include "shared/rt.h" /** * Lookup data associated with oFono card. */ @@ -88,17 +91,31 @@ static int ofono_sco_socket_authorize(int fd) { } /** - * Ask oFono to connect to a card (in return it will call NewConnection). */ + * Ask oFono to connect to a card. */ static int ofono_acquire_bt_sco(struct ba_transport *t) { GDBusMessage *msg = NULL, *rep = NULL; GError *err = NULL; + uint8_t codec; + int fd = -1; int ret = 0; const char *ofono_dbus_path = t->bluez_dbus_path; - debug("Requesting new oFono SCO connection: %s", ofono_dbus_path); + debug("Requesting new oFono SCO link: %s", ofono_dbus_path); msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, ofono_dbus_path, - OFONO_IFACE_HF_AUDIO_CARD, "Connect"); + OFONO_IFACE_HF_AUDIO_CARD, "Acquire"); + + struct timespec now; + struct timespec delay = { + .tv_nsec = HCI_SCO_CLOSE_CONNECT_QUIRK_DELAY * 1000000 }; + + gettimestamp(&now); + timespecadd(&t->sco.closed_at, &delay, &delay); + if (difftimespec(&now, &delay, &delay) > 0) { + info("SCO link close-connect quirk delay: %d ms", + (int)(delay.tv_nsec / 1000000)); + nanosleep(&delay, NULL); + } if ((rep = g_dbus_connection_send_message_with_reply_sync(config.dbus, msg, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL) @@ -109,6 +126,19 @@ static int ofono_acquire_bt_sco(struct ba_transport *t) { goto fail; } + GVariant *body = g_dbus_message_get_body(rep); + g_variant_get(body, "(hy)", NULL, &codec); + + GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list(rep); + if ((fd = g_unix_fd_list_get(fd_list, 0, &err)) == -1) + goto fail; + + t->bt_fd = fd; + t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a->hci.type); + ba_transport_set_codec(t, codec); + + debug("New oFono SCO link (codec: %#x): %d", codec, fd); + goto final; fail: @@ -120,7 +150,7 @@ final: if (rep != NULL) g_object_unref(rep); if (err != NULL) { - warn("Couldn't connect to card: %s", err->message); + error("Couldn't establish oFono SCO link: %s", err->message); g_error_free(err); } @@ -142,6 +172,10 @@ static int ofono_release_bt_sco(struct ba_transport *t) { close(t->bt_fd); t->bt_fd = -1; + /* Keep the time-stamp when the SCO link has been closed. It will be used + * for calculating close-connect quirk delay in the acquire function. */ + gettimestamp(&t->sco.closed_at); + return 0; } @@ -472,16 +506,15 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat struct ba_transport *t = NULL; GError *err = NULL; - GUnixFDList *fd_list; const char *card; uint8_t codec; int fd; - g_variant_get(params, "(&ohy)", &card, &fd, &codec); + g_variant_get(params, "(&ohy)", &card, NULL, &codec); - fd_list = g_dbus_message_get_unix_fd_list(msg); + GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list(msg); if ((fd = g_unix_fd_list_get(fd_list, 0, &err)) == -1) { - error("Couldn't obtain SCO socket: %s", err->message); + error("Couldn't obtain oFono SCO link socket: %s", err->message); goto fail; } @@ -491,7 +524,7 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat } if (ofono_sco_socket_authorize(fd) == -1) { - error("Couldn't authorize SCO connection: %s", strerror(errno)); + error("Couldn't authorize oFono SCO link: %s", strerror(errno)); goto fail; } @@ -499,7 +532,7 @@ static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdat pthread_mutex_lock(&t->bt_fd_mtx); - debug("New oFono SCO connection (codec: %#x): %d", codec, fd); + debug("New oFono SCO link (codec: %#x): %d", codec, fd); t->bt_fd = fd; t->mtu_read = t->mtu_write = hci_sco_get_mtu(fd, t->d->a->hci.type); commit 786d4f97b696db4576c568f0bf379631000d93ec Author: Arkadiusz Bokowy Date: Fri May 5 21:36:17 2023 +0200 Fix oFono AG mode after changes in dd27c82 diff --git a/src/ofono-iface.h b/src/ofono-iface.h index 0e00f02..42552c1 100644 --- a/src/ofono-iface.h +++ b/src/ofono-iface.h @@ -27,6 +27,10 @@ #define OFONO_AUDIO_CODEC_CVSD 0x01 #define OFONO_AUDIO_CODEC_MSBC 0x02 +#define OFONO_MODEM_TYPE_HARDWARE "hardware" +#define OFONO_MODEM_TYPE_HFP "hfp" +#define OFONO_MODEM_TYPE_SAP "sap" + extern const GDBusInterfaceInfo ofono_iface_hf_audio_agent; #endif diff --git a/src/ofono.c b/src/ofono.c index 9f4b773..71f2cec 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -52,8 +52,11 @@ /** * Lookup data associated with oFono card. */ struct ofono_card_data { + char card[64]; int hci_dev_id; bdaddr_t bt_addr; + /* if true, card is an HFP AG */ + bool is_gateway; /* object path of a modem associated with this card */ char modem_path[64]; }; @@ -92,9 +95,8 @@ static int ofono_acquire_bt_sco(struct ba_transport *t) { GError *err = NULL; int ret = 0; - debug("Requesting new oFono SCO connection: %s", t->bluez_dbus_path); - - const char *ofono_dbus_path = &t->bluez_dbus_path[6]; + const char *ofono_dbus_path = t->bluez_dbus_path; + debug("Requesting new oFono SCO connection: %s", ofono_dbus_path); msg = g_dbus_message_new_method_call(t->bluez_dbus_owner, ofono_dbus_path, OFONO_IFACE_HF_AUDIO_CARD, "Connect"); @@ -191,7 +193,7 @@ static struct ba_transport *ofono_transport_lookup(const char *card) { goto fail; if ((d = ba_device_lookup(a, &ocd->bt_addr)) == NULL) goto fail; - if ((t = ba_transport_lookup(d, ocd->modem_path)) == NULL) + if ((t = ba_transport_lookup(d, card)) == NULL) goto fail; fail: @@ -203,8 +205,8 @@ fail: } /** - * Link oFono card with HFP modem. */ -static int ofono_card_link_modem_hfp(struct ofono_card_data *ocd) { + * Link oFono card with a modem. */ +static int ofono_card_link_modem(struct ofono_card_data *ocd) { GDBusMessage *msg = NULL, *rep = NULL; GError *err = NULL; @@ -232,27 +234,56 @@ static int ofono_card_link_modem_hfp(struct ofono_card_data *ocd) { while (g_variant_iter_next(modems, "(&oa{sv})", &modem, &properties)) { bdaddr_t bt_addr = { 0 }; - bool is_hfp = false; + bool is_powered = false; + bool is_bt_device = false; + const char *serial = ""; const char *key; GVariant *value; while (g_variant_iter_next(properties, "{&sv}", &key, &value)) { - if (strcmp(key, "Serial") == 0) - str2ba(g_variant_get_string(value, NULL), &bt_addr); - else if (strcmp(key, "Type") == 0) - is_hfp = strcmp(g_variant_get_string(value, NULL), "hfp") == 0; + if (strcmp(key, "Powered") == 0) + is_powered = g_variant_get_boolean(value); + else if (strcmp(key, "Type") == 0) { + const char *type = g_variant_get_string(value, NULL); + if (strcmp(type, OFONO_MODEM_TYPE_HFP) == 0 || + strcmp(type, OFONO_MODEM_TYPE_SAP) == 0) + is_bt_device = true; + } + else if (strcmp(key, "Serial") == 0) + serial = g_variant_get_string(value, NULL); g_variant_unref(value); } + if (is_bt_device) + str2ba(serial, &bt_addr); + g_variant_iter_free(properties); - if (is_hfp && bacmp(&bt_addr, &ocd->bt_addr) == 0) { - debug("Linking oFono card with modem: %s", modem); - strncpy(ocd->modem_path, modem, sizeof(ocd->modem_path) - 1); - ocd->modem_path[sizeof(ocd->modem_path) - 1] = '\0'; - ret = 0; - break; - } + if (!is_powered) + continue; + + /* In case of HFP AG, we are looking for a modem which is not a BT device. + * Unfortunately, oFono does not link card (Bluetooth HF device) with a + * particular modem. In case where more than one card is connected oFono + * uses all of them for call notification... However, in our setup we need + * a 1:1 mapping between card and modem. So, we will link the first modem + * which is not a BT device. + * + * TODO: Find a better way to link oFono card with a modem. */ + if (ocd->is_gateway && is_bt_device) + continue; + + /* In case of HFP HF, we are looking for a modem which is a BT device and + * its serial number matches with the card BT address. */ + if (!ocd->is_gateway && + !(is_bt_device && bacmp(&bt_addr, &ocd->bt_addr) == 0)) + continue; + + debug("Linking oFono card with modem: %s", modem); + strncpy(ocd->modem_path, modem, sizeof(ocd->modem_path) - 1); + ocd->modem_path[sizeof(ocd->modem_path) - 1] = '\0'; + ret = 0; + break; } @@ -280,7 +311,7 @@ static void ofono_card_add(const char *dbus_sender, const char *card, struct ba_device *d = NULL; struct ba_transport *t = NULL; - enum ba_transport_profile profile = BA_TRANSPORT_PROFILE_HFP_HF; + enum ba_transport_profile profile = BA_TRANSPORT_PROFILE_NONE; struct ofono_card_data *ocd = NULL; const char *key = NULL; GVariant *value = NULL; @@ -330,18 +361,21 @@ static void ofono_card_add(const char *dbus_sender, const char *card, ocd->hci_dev_id = hci_dev_id; ocd->bt_addr = addr_dev; + ocd->is_gateway = profile & BA_TRANSPORT_PROFILE_MASK_AG; + strncpy(ocd->card, card, sizeof(ocd->card) - 1); + ocd->card[sizeof(ocd->card) - 1] = '\0'; - if (ofono_card_link_modem_hfp(ocd) == -1) { + if (ofono_card_link_modem(ocd) == -1) { error("Couldn't link oFono card with modem: %s", card); goto fail; } - if ((t = ofono_transport_new(d, profile, dbus_sender, ocd->modem_path)) == NULL) { + if ((t = ofono_transport_new(d, profile, dbus_sender, card)) == NULL) { error("Couldn't create new transport: %s", strerror(errno)); goto fail; } - g_hash_table_insert(ofono_card_data_map, g_strdup(card), ocd); + g_hash_table_insert(ofono_card_data_map, ocd->card, ocd); ocd = NULL; fail: @@ -667,7 +701,7 @@ int ofono_init(void) { return 0; if (ofono_card_data_map == NULL) - ofono_card_data_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free); + ofono_card_data_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free); g_dbus_connection_signal_subscribe(config.dbus, OFONO_SERVICE, OFONO_IFACE_HF_AUDIO_MANAGER, "CardAdded", NULL, NULL, commit 287a2dcdcba488a56e6b873e72379b650a011c2b Author: Arkadiusz Bokowy Date: Wed May 3 23:42:30 2023 +0200 Remove obsolete support for D-Bus async methods New code, if required, shall use glib interface skeleton with the G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD flag. diff --git a/src/dbus.c b/src/dbus.c index 1982f69..7cad2bd 100644 --- a/src/dbus.c +++ b/src/dbus.c @@ -1,6 +1,6 @@ /* * BlueALSA - dbus.c - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -10,32 +10,13 @@ #include "dbus.h" -#include #include #include #include -#include "shared/defs.h" #include "shared/log.h" -/* Compatibility patch for glib < 2.68. */ -#if !GLIB_CHECK_VERSION(2, 68, 0) -# define g_memdup2 g_memdup -#endif - -struct dispatch_method_caller_data { - void (*handler)(GDBusMethodInvocation *, void *); - GDBusMethodInvocation *invocation; - void *userdata; -}; - -static void *dispatch_method_caller(struct dispatch_method_caller_data *data) { - data->handler(data->invocation, data->userdata); - g_free(data); - return NULL; -} - /** * Dispatch incoming D-Bus method call. * @@ -64,33 +45,7 @@ bool g_dbus_dispatch_method_call(const GDBusMethodCallDispatcher *dispatchers, continue; debug("Called: %s.%s() on %s", interface, method, path); - - if (!dispatcher->asynchronous_call) - dispatcher->handler(invocation, userdata); - else { - - struct dispatch_method_caller_data data = { - .handler = dispatcher->handler, - .invocation = invocation, - .userdata = userdata, - }; - - pthread_t thread; - int ret; - - void *ptr = g_memdup2(&data, sizeof(data)); - if ((ret = pthread_create(&thread, NULL, - PTHREAD_FUNC(dispatch_method_caller), ptr)) != 0) { - error("Couldn't create D-Bus call dispatcher: %s", strerror(ret)); - return false; - } - - if ((ret = pthread_detach(thread)) != 0) { - error("Couldn't detach D-Bus call dispatcher: %s", strerror(ret)); - return false; - } - - } + dispatcher->handler(invocation, userdata); return true; } diff --git a/src/dbus.h b/src/dbus.h index adc911f..2e09385 100644 --- a/src/dbus.h +++ b/src/dbus.h @@ -1,6 +1,6 @@ /* * BlueALSA - dbus.h - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -37,8 +37,6 @@ typedef struct _GDBusMethodCallDispatcher { const char *interface; const char *method; void (*handler)(GDBusMethodInvocation *, void *); - /* if true, handler will be called in a separate thread */ - bool asynchronous_call; } GDBusMethodCallDispatcher; typedef struct _GDBusInterfaceSkeletonVTable { diff --git a/src/main.c b/src/main.c index 9bb24d8..7c1e9e9 100644 --- a/src/main.c +++ b/src/main.c @@ -602,6 +602,7 @@ int main(int argc, char **argv) { for (size_t i = 0; i < ARRAYSIZE(config.adapters); i++) ba_adapter_destroy(config.adapters[i]); + g_dbus_connection_close_sync(config.dbus, NULL, NULL); g_main_loop_unref(loop); g_free(address); commit e767f30c8da247da02232c842ec39743ed233a9c Author: Arkadiusz Bokowy Date: Wed May 3 15:07:03 2023 +0200 Convert PCM volume level to arbitrary value range diff --git a/src/ba-rfcomm.c b/src/ba-rfcomm.c index 2f1ae2d..483156c 100644 --- a/src/ba-rfcomm.c +++ b/src/ba-rfcomm.c @@ -436,7 +436,7 @@ static int rfcomm_handler_vgm_set_cb(struct ba_rfcomm *r, const struct bt_at *at return rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK"); r->gain_mic = atoi(at->value); - int level = ba_transport_pcm_volume_bt_to_level(pcm, r->gain_mic); + int level = ba_transport_pcm_volume_range_to_level(r->gain_mic, HFP_VOLUME_GAIN_MAX); pthread_mutex_lock(&pcm->mutex); ba_transport_pcm_volume_set(&pcm->volume[0], &level, NULL, NULL); @@ -457,7 +457,7 @@ static int rfcomm_handler_vgm_resp_cb(struct ba_rfcomm *r, const struct bt_at *a struct ba_transport_pcm *pcm = &t_sco->sco.mic_pcm; r->gain_mic = atoi(at->value); - int level = ba_transport_pcm_volume_bt_to_level(pcm, r->gain_mic); + int level = ba_transport_pcm_volume_range_to_level(r->gain_mic, HFP_VOLUME_GAIN_MAX); pthread_mutex_lock(&pcm->mutex); ba_transport_pcm_volume_set(&pcm->volume[0], &level, NULL, NULL); @@ -481,7 +481,7 @@ static int rfcomm_handler_vgs_set_cb(struct ba_rfcomm *r, const struct bt_at *at return rfcomm_write_at(fd, AT_TYPE_RESP, NULL, "OK"); r->gain_spk = atoi(at->value); - int level = ba_transport_pcm_volume_bt_to_level(pcm, r->gain_spk); + int level = ba_transport_pcm_volume_range_to_level(r->gain_spk, HFP_VOLUME_GAIN_MAX); pthread_mutex_lock(&pcm->mutex); ba_transport_pcm_volume_set(&pcm->volume[0], &level, NULL, NULL); @@ -502,7 +502,7 @@ static int rfcomm_handler_vgs_resp_cb(struct ba_rfcomm *r, const struct bt_at *a struct ba_transport_pcm *pcm = &t_sco->sco.spk_pcm; r->gain_spk = atoi(at->value); - int level = ba_transport_pcm_volume_bt_to_level(pcm, r->gain_spk); + int level = ba_transport_pcm_volume_range_to_level(r->gain_spk, HFP_VOLUME_GAIN_MAX); pthread_mutex_lock(&pcm->mutex); ba_transport_pcm_volume_set(&pcm->volume[0], &level, NULL, NULL); @@ -1079,7 +1079,8 @@ static int rfcomm_notify_volume_change_mic(struct ba_rfcomm *r, bool force) { const int fd = r->fd; char tmp[24]; - int gain = ba_transport_pcm_volume_level_to_bt(pcm, pcm->volume[0].level); + int gain = ba_transport_pcm_volume_level_to_range( + pcm->volume[0].level, HFP_VOLUME_GAIN_MAX); if (!force && r->gain_mic == gain) return 0; @@ -1110,7 +1111,8 @@ static int rfcomm_notify_volume_change_spk(struct ba_rfcomm *r, bool force) { const int fd = r->fd; char tmp[24]; - int gain = ba_transport_pcm_volume_level_to_bt(pcm, pcm->volume[0].level); + int gain = ba_transport_pcm_volume_level_to_range( + pcm->volume[0].level, HFP_VOLUME_GAIN_MAX); if (!force && r->gain_spk == gain) return 0; @@ -1626,10 +1628,10 @@ struct ba_rfcomm *ba_rfcomm_new(struct ba_transport *sco, int fd) { memset(&r->hfp_ind_state, 1, sizeof(r->hfp_ind_state)); /* Initialize data used for volume gain synchronization. */ - r->gain_mic = ba_transport_pcm_volume_level_to_bt( - &sco->sco.mic_pcm, sco->sco.mic_pcm.volume[0].level); - r->gain_spk = ba_transport_pcm_volume_level_to_bt( - &sco->sco.spk_pcm, sco->sco.spk_pcm.volume[0].level); + r->gain_mic = ba_transport_pcm_volume_level_to_range( + sco->sco.mic_pcm.volume[0].level, HFP_VOLUME_GAIN_MAX); + r->gain_spk = ba_transport_pcm_volume_level_to_range( + sco->sco.spk_pcm.volume[0].level, HFP_VOLUME_GAIN_MAX); if (pipe(r->sig_fd) == -1) goto fail; diff --git a/src/ba-transport.c b/src/ba-transport.c index 13465e8..2d7f65d 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -138,6 +138,20 @@ static void transport_pcm_free( } +/** + * Convert PCM volume level to [0, max] range. */ +int ba_transport_pcm_volume_level_to_range(int value, int max) { + int volume = audio_decibel_to_loudness(value / 100.0) * max; + return MIN(MAX(volume, 0), max); +} + +/** + * Convert [0, max] range to PCM volume level. */ +int ba_transport_pcm_volume_range_to_level(int value, int max) { + double level = audio_loudness_to_decibel(1.0 * value / max); + return MIN(MAX(level, -96.0), 96.0) * 100; +} + /** * Set PCM volume level/mute. * @@ -817,13 +831,11 @@ struct ba_transport *ba_transport_new_a2dp( is_sink ? &t->thread_dec : &t->thread_enc, is_sink ? BA_TRANSPORT_PCM_MODE_SOURCE : BA_TRANSPORT_PCM_MODE_SINK); t->a2dp.pcm.soft_volume = !config.a2dp.volume; - t->a2dp.pcm.max_bt_volume = 127; transport_pcm_init(&t->a2dp.pcm_bc, is_sink ? &t->thread_enc : &t->thread_dec, is_sink ? BA_TRANSPORT_PCM_MODE_SINK : BA_TRANSPORT_PCM_MODE_SOURCE); t->a2dp.pcm_bc.soft_volume = !config.a2dp.volume; - t->a2dp.pcm_bc.max_bt_volume = 127; t->acquire = transport_acquire_bt_a2dp; t->release = transport_release_bt_a2dp; @@ -932,12 +944,10 @@ struct ba_transport *ba_transport_new_sco( transport_pcm_init(&t->sco.spk_pcm, is_ag ? &t->thread_enc : &t->thread_dec, is_ag ? BA_TRANSPORT_PCM_MODE_SINK : BA_TRANSPORT_PCM_MODE_SOURCE); - t->sco.spk_pcm.max_bt_volume = 15; transport_pcm_init(&t->sco.mic_pcm, is_ag ? &t->thread_dec : &t->thread_enc, is_ag ? BA_TRANSPORT_PCM_MODE_SOURCE : BA_TRANSPORT_PCM_MODE_SINK); - t->sco.mic_pcm.max_bt_volume = 15; t->acquire = transport_acquire_bt_sco; t->release = transport_release_bt_sco; @@ -1722,20 +1732,6 @@ int ba_transport_pcm_get_delay(const struct ba_transport_pcm *pcm) { return pcm->delay; } -unsigned int ba_transport_pcm_volume_level_to_bt( - const struct ba_transport_pcm *pcm, - int value) { - int volume = audio_decibel_to_loudness(value / 100.0) * pcm->max_bt_volume; - return MIN((unsigned int)MAX(volume, 0), pcm->max_bt_volume); -} - -int ba_transport_pcm_volume_bt_to_level( - const struct ba_transport_pcm *pcm, - unsigned int value) { - double level = audio_loudness_to_decibel(1.0 * value / pcm->max_bt_volume); - return MIN(MAX(level, -96.0), 96.0) * 100; -} - int ba_transport_pcm_volume_update(struct ba_transport_pcm *pcm) { struct ba_transport *t = pcm->t; @@ -1756,11 +1752,13 @@ int ba_transport_pcm_volume_update(struct ba_transport_pcm *pcm) { unsigned int volume; switch (pcm->channels) { case 1: - volume = ba_transport_pcm_volume_level_to_bt(pcm, pcm->volume[0].level); + volume = ba_transport_pcm_volume_level_to_range( + pcm->volume[0].level, BLUEZ_A2DP_VOLUME_MAX); break; case 2: - volume = ba_transport_pcm_volume_level_to_bt(pcm, - (pcm->volume[0].level + pcm->volume[1].level) / 2); + volume = ba_transport_pcm_volume_level_to_range( + (pcm->volume[0].level + pcm->volume[1].level) / 2, + BLUEZ_A2DP_VOLUME_MAX); break; default: g_assert_not_reached(); diff --git a/src/ba-transport.h b/src/ba-transport.h index 325a3f0..ae7b1c8 100644 --- a/src/ba-transport.h +++ b/src/ba-transport.h @@ -88,9 +88,6 @@ struct ba_transport_pcm { /* internal software volume control */ bool soft_volume; - /* maximal possible Bluetooth volume */ - unsigned int max_bt_volume; - /* Volume configuration for channel left [0] and right [1]. In case of * a monophonic sound, only the left [0] channel shall be used. */ struct ba_transport_pcm_volume { @@ -113,6 +110,9 @@ struct ba_transport_pcm { }; +int ba_transport_pcm_volume_level_to_range(int value, int max); +int ba_transport_pcm_volume_range_to_level(int value, int max); + void ba_transport_pcm_volume_set( struct ba_transport_pcm_volume *volume, const int *level, @@ -413,13 +413,6 @@ bool ba_transport_pcm_is_active( int ba_transport_pcm_get_delay( const struct ba_transport_pcm *pcm); -unsigned int ba_transport_pcm_volume_level_to_bt( - const struct ba_transport_pcm *pcm, - int value); -int ba_transport_pcm_volume_bt_to_level( - const struct ba_transport_pcm *pcm, - unsigned int value); - int ba_transport_pcm_volume_update( struct ba_transport_pcm *pcm); diff --git a/src/bluealsa-dbus.c b/src/bluealsa-dbus.c index d114431..c1e6c45 100644 --- a/src/bluealsa-dbus.c +++ b/src/bluealsa-dbus.c @@ -36,6 +36,7 @@ #include "bluealsa-config.h" #include "bluealsa-iface.h" #include "bluealsa-skeleton.h" +#include "bluez.h" #include "dbus.h" #include "hfp.h" #include "utils.h" @@ -268,10 +269,12 @@ static uint8_t ba_volume_pack_dbus_volume(bool muted, int value) { } static GVariant *ba_variant_new_pcm_volume(const struct ba_transport_pcm *pcm) { + const bool is_sco = pcm->t->profile & BA_TRANSPORT_PROFILE_MASK_SCO; + const int max = is_sco ? HFP_VOLUME_GAIN_MAX : BLUEZ_A2DP_VOLUME_MAX; uint8_t ch1 = ba_volume_pack_dbus_volume(pcm->volume[0].scale == 0, - ba_transport_pcm_volume_level_to_bt(pcm, pcm->volume[0].level)); + ba_transport_pcm_volume_level_to_range(pcm->volume[0].level, max)); uint8_t ch2 = ba_volume_pack_dbus_volume(pcm->volume[1].scale == 0, - ba_transport_pcm_volume_level_to_bt(pcm, pcm->volume[1].level)); + ba_transport_pcm_volume_level_to_range(pcm->volume[1].level, max)); return g_variant_new_uint16((ch1 << 8) | (pcm->channels == 1 ? 0 : ch2)); } @@ -867,13 +870,16 @@ static bool bluealsa_pcm_set_property(const char *property, GVariant *value, if (strcmp(property, "Volume") == 0) { + const bool is_sco = pcm->t->profile & BA_TRANSPORT_PROFILE_MASK_SCO; + const int max = is_sco ? HFP_VOLUME_GAIN_MAX : BLUEZ_A2DP_VOLUME_MAX; + uint16_t packed = g_variant_get_uint16(value); uint8_t ch1 = packed >> 8; uint8_t ch2 = packed & 0xFF; - int ch1_level = ba_transport_pcm_volume_bt_to_level(pcm, ch1 & 0x7F); + int ch1_level = ba_transport_pcm_volume_range_to_level(ch1 & 0x7F, max); bool ch1_muted = !!(ch1 & 0x80); - int ch2_level = ba_transport_pcm_volume_bt_to_level(pcm, ch2 & 0x7F); + int ch2_level = ba_transport_pcm_volume_range_to_level(ch2 & 0x7F, max); bool ch2_muted = !!(ch2 & 0x80); pthread_mutex_lock(&pcm->mutex); diff --git a/src/bluez.c b/src/bluez.c index 02c1cd2..a862424 100644 --- a/src/bluez.c +++ b/src/bluez.c @@ -396,7 +396,7 @@ static void bluez_endpoint_set_configuration(GDBusMethodInvocation *inv, void *u if (!(t->profile & BA_TRANSPORT_PROFILE_A2DP_SOURCE && t->a2dp.pcm.soft_volume)) { - int level = ba_transport_pcm_volume_bt_to_level(&t->a2dp.pcm, volume); + int level = ba_transport_pcm_volume_range_to_level(volume, BLUEZ_A2DP_VOLUME_MAX); pthread_mutex_lock(&t->a2dp.pcm.mutex); ba_transport_pcm_volume_set(&t->a2dp.pcm.volume[0], &level, NULL, NULL); @@ -1419,7 +1419,7 @@ static void bluez_signal_transport_changed(GDBusConnection *conn, const char *se debug("Skipping A2DP volume update: %u", volume); else { - int level = ba_transport_pcm_volume_bt_to_level(&t->a2dp.pcm, volume); + int level = ba_transport_pcm_volume_range_to_level(volume, BLUEZ_A2DP_VOLUME_MAX); debug("Updating A2DP volume: %u [%.2f dB]", volume, 0.01 * level); pthread_mutex_lock(&t->a2dp.pcm.mutex); diff --git a/src/bluez.h b/src/bluez.h index 71125bd..f860655 100644 --- a/src/bluez.h +++ b/src/bluez.h @@ -27,6 +27,9 @@ #define BLUETOOTH_UUID_HFP_HF "0000111E-0000-1000-8000-00805F9B34FB" #define BLUETOOTH_UUID_HFP_AG "0000111F-0000-1000-8000-00805F9B34FB" +#define BLUEZ_A2DP_VOLUME_MIN 0 +#define BLUEZ_A2DP_VOLUME_MAX 127 + enum bluez_a2dp_transport_state { BLUEZ_A2DP_TRANSPORT_STATE_IDLE, BLUEZ_A2DP_TRANSPORT_STATE_PENDING, diff --git a/src/hfp.h b/src/hfp.h index 0127dcc..9a58217 100644 --- a/src/hfp.h +++ b/src/hfp.h @@ -21,6 +21,11 @@ #define HFP_CODEC_CVSD 0x01 #define HFP_CODEC_MSBC 0x02 +/** + * HSP/HFP volume gain range */ +#define HFP_VOLUME_GAIN_MIN 0 +#define HFP_VOLUME_GAIN_MAX 15 + /** * SDP AG feature flags */ #define SDP_HFP_AG_FEAT_TWC (1 << 0) /* three-way calling */ diff --git a/test/test-ba.c b/test/test-ba.c index e2c33f9..8945246 100644 --- a/test/test-ba.c +++ b/test/test-ba.c @@ -311,23 +311,17 @@ CK_START_TEST(test_ba_transport_pcm_volume) { ba_adapter_unref(a); ba_device_unref(d); - ck_assert_int_eq(t_a2dp->a2dp.pcm.max_bt_volume, 127); - ck_assert_int_eq(t_a2dp->a2dp.pcm_bc.max_bt_volume, 127); + ck_assert_int_eq(ba_transport_pcm_volume_range_to_level(0, BLUEZ_A2DP_VOLUME_MAX), -9600); + ck_assert_int_eq(ba_transport_pcm_volume_level_to_range(-9600, BLUEZ_A2DP_VOLUME_MAX), 0); - ck_assert_int_eq(t_sco->sco.spk_pcm.max_bt_volume, 15); - ck_assert_int_eq(t_sco->sco.mic_pcm.max_bt_volume, 15); + ck_assert_int_eq(ba_transport_pcm_volume_range_to_level(127, BLUEZ_A2DP_VOLUME_MAX), 0); + ck_assert_int_eq(ba_transport_pcm_volume_level_to_range(0, BLUEZ_A2DP_VOLUME_MAX), 127); - ck_assert_int_eq(ba_transport_pcm_volume_bt_to_level(&t_a2dp->a2dp.pcm, 0), -9600); - ck_assert_int_eq(ba_transport_pcm_volume_level_to_bt(&t_a2dp->a2dp.pcm, -9600), 0); + ck_assert_int_eq(ba_transport_pcm_volume_range_to_level(0, HFP_VOLUME_GAIN_MAX), -9600); + ck_assert_int_eq(ba_transport_pcm_volume_level_to_range(-9600, HFP_VOLUME_GAIN_MAX), 0); - ck_assert_int_eq(ba_transport_pcm_volume_bt_to_level(&t_a2dp->a2dp.pcm, 127), 0); - ck_assert_int_eq(ba_transport_pcm_volume_level_to_bt(&t_a2dp->a2dp.pcm, 0), 127); - - ck_assert_int_eq(ba_transport_pcm_volume_bt_to_level(&t_sco->sco.spk_pcm, 0), -9600); - ck_assert_int_eq(ba_transport_pcm_volume_level_to_bt(&t_sco->sco.spk_pcm, -9600), 0); - - ck_assert_int_eq(ba_transport_pcm_volume_bt_to_level(&t_sco->sco.spk_pcm, 15), 0); - ck_assert_int_eq(ba_transport_pcm_volume_level_to_bt(&t_sco->sco.spk_pcm, 0), 15); + ck_assert_int_eq(ba_transport_pcm_volume_range_to_level(15, HFP_VOLUME_GAIN_MAX), 0); + ck_assert_int_eq(ba_transport_pcm_volume_level_to_range(0, HFP_VOLUME_GAIN_MAX), 15); ba_transport_unref(t_a2dp); ba_transport_unref(t_sco); @@ -393,7 +387,7 @@ CK_START_TEST(test_storage) { ck_assert_int_eq(t->a2dp.pcm.volume[1].soft_mute, true); bool muted = true; - int level = ba_transport_pcm_volume_bt_to_level(&t->a2dp.pcm, 100); + int level = ba_transport_pcm_volume_range_to_level(100, BLUEZ_A2DP_VOLUME_MAX); ba_transport_pcm_volume_set(&t->a2dp.pcm.volume[0], &level, &muted, NULL); ba_transport_pcm_volume_set(&t->a2dp.pcm.volume[1], &level, &muted, NULL); commit 5c5b446c07e98672b18ab92c525fbf24d7c31822 Author: borine <32966433+borine@users.noreply.github.com> Date: Mon Jan 9 17:19:06 2023 +0000 Properly release hash table items in oFono module diff --git a/.github/spellcheck-wordlist.txt b/.github/spellcheck-wordlist.txt index 774d027..4db8f6d 100644 --- a/.github/spellcheck-wordlist.txt +++ b/.github/spellcheck-wordlist.txt @@ -95,6 +95,7 @@ unmute unmutes unref unreferencing +unregister utils # Others diff --git a/src/ba-transport.c b/src/ba-transport.c index 37e35c7..13465e8 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -1114,6 +1114,8 @@ struct ba_transport *ba_transport_ref( return t; } +/** + * Unregister D-Bus interfaces, stop IO threads and release transport. */ void ba_transport_destroy(struct ba_transport *t) { /* Remove D-Bus interfaces, so no one will access diff --git a/src/ofono.c b/src/ofono.c index edd3044..9f4b773 100644 --- a/src/ofono.c +++ b/src/ofono.c @@ -413,31 +413,21 @@ final: static void ofono_remove_all_cards(void) { GHashTableIter iter; - struct ofono_card_data *ocd; + const char *card = NULL; g_hash_table_iter_init(&iter, ofono_card_data_map); - while (g_hash_table_iter_next(&iter, NULL, (gpointer)&ocd)) { + while (g_hash_table_iter_next(&iter, (gpointer)&card, NULL)) { - struct ba_adapter *a = NULL; - struct ba_device *d = NULL; - struct ba_transport *t; - - if ((a = ba_adapter_lookup(ocd->hci_dev_id)) == NULL) - goto fail; - if ((d = ba_device_lookup(a, &ocd->bt_addr)) == NULL) - goto fail; - if ((t = ba_transport_lookup(d, ocd->modem_path)) == NULL) - goto fail; + debug("Removing oFono card: %s", card); - ba_transport_destroy(t); + struct ba_transport *t; + if ((t = ofono_transport_lookup(card)) != NULL) + ba_transport_destroy(t); -fail: - if (a != NULL) - ba_adapter_unref(a); - if (d != NULL) - ba_device_unref(d); } + g_hash_table_remove_all(ofono_card_data_map); + } static void ofono_agent_new_connection(GDBusMethodInvocation *inv, void *userdata) { @@ -632,14 +622,13 @@ static void ofono_signal_card_removed(GDBusConnection *conn, const char *sender, const char *card = NULL; g_variant_get(params, "(&o)", &card); - struct ba_transport *t = NULL; - if ((t = ofono_transport_lookup(card)) == NULL) { - error("Couldn't lookup transport: %s: %s", card, strerror(errno)); - return; - } - debug("Removing oFono card: %s", card); - ba_transport_destroy(t); + + struct ba_transport *t; + if ((t = ofono_transport_lookup(card)) != NULL) + ba_transport_destroy(t); + + g_hash_table_remove(ofono_card_data_map, card); } @@ -678,7 +667,7 @@ int ofono_init(void) { return 0; if (ofono_card_data_map == NULL) - ofono_card_data_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + ofono_card_data_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free); g_dbus_connection_signal_subscribe(config.dbus, OFONO_SERVICE, OFONO_IFACE_HF_AUDIO_MANAGER, "CardAdded", NULL, NULL, commit ef353d0f3fef5eedacb9881f78c8f921b50a6d33 Author: Arkadiusz Bokowy Date: Mon May 1 13:08:39 2023 +0200 Fix shellcheck warnings in bash completion script diff --git a/misc/bash-completion/bluealsa b/misc/bash-completion/bluealsa index 8e7d04c..8e49ee6 100644 --- a/misc/bash-completion/bluealsa +++ b/misc/bash-completion/bluealsa @@ -8,8 +8,8 @@ _bluealsa_profiles() { [[ "$line" = "Available BT profiles:" ]] && start=yes && continue [[ "$start" ]] || continue [[ "$line" ]] || break - words=($line) - printf "%s" "${words[1]} " + read -ra words <<< "$line" + echo "${words[1]}" done } @@ -21,8 +21,8 @@ _bluealsa_codecs() { [[ "$start" ]] || continue [[ "$line" ]] || break line=${line,,} - words=(${line//,/}) - echo ${words[@]:1} + IFS=", " read -ra words <<< "${line,,}" + echo "${words[@]:1}" done } @@ -31,7 +31,8 @@ _bluealsa_codecs() { # @param $2 the bluealsa option to inspect _bluealsa_enum_values() { "$1" "${2}=" 2>&1 | while read -r line; do - [[ $line =~ \{([^}]*)\} ]] && printf "${BASH_REMATCH[1]//,/}" + [[ $line =~ \{([^}]*)\} ]] || continue + echo "${BASH_REMATCH[1]//,/}" done } @@ -43,39 +44,32 @@ _bluealsa_list_dbus_suffices() { org.freedesktop.DBus.ListNames 2>/dev/null | \ while read -r line; do [[ $line =~ org\.bluealsa\.([^'"']+) ]] || continue - printf "%s" "${BASH_REMATCH[1]} " + echo "${BASH_REMATCH[1]}" done } # helper function gets codecs for given pcm -# @param $1 the executable name -# @param $2 dbus option ( --dbus=aaa ) -# @param $3 pcm path +# before calling this function, make sure that the bautil_args was properly +# initialized with the _bluealsa_util_init function +# @param $1 pcm path _bluealsa_pcm_codecs() { - "$1" $2 codec "$3" | while read -r line; do - [[ $line =~ ^Available\ codecs:\ ([^[]+$) ]] && printf "${BASH_REMATCH[1]}" + "${bautil_args[@]}" codec "$1" | while read -r line; do + [[ $line =~ ^Available\ codecs:\ ([^[]+$) ]] || continue + echo "${BASH_REMATCH[1]}" done } # helper function completes supported bluealsa-cli monitor properties _bluealsa_cli_properties() { - local properties=( - Codec - Running - SoftVolume - Volume - ) - + local properties=( codec running softvolume volume ) if [[ "$cur" == *,* ]]; then local realcur prefix chosen remaining realcur="${cur##*,}" prefix="${cur%,*}" - chosen=() - IFS=$',\n' read -ra chosen <<< "$prefix" - remaining=() - readarray -t remaining <<< "$(printf '%s\n' "${properties[@]}" "${chosen[@]}" | sort | uniq -u)" + IFS="," read -ra chosen <<< "${prefix,,}" + readarray -t remaining < <(printf '%s\n' "${properties[@]}" "${chosen[@]}" | sort | uniq -u) if [[ ${#remaining[@]} -gt 0 ]]; then - COMPREPLY=( $(compgen -W "${remaining[*]}" -- "$realcur") ) + readarray -t COMPREPLY < <(compgen -W "${remaining[*]}" -- "$realcur") if [[ ${#COMPREPLY[@]} -eq 1 ]] ; then COMPREPLY[0]="$prefix,${COMPREPLY[0]}" fi @@ -87,7 +81,7 @@ _bluealsa_cli_properties() { fi fi else - COMPREPLY=( $(compgen -W "${properties[*]}" -- "$cur") ) + readarray -t COMPREPLY < <(compgen -W "${properties[*]}" -- "$cur") if [[ ${#COMPREPLY[@]} -eq 1 && "$cur" == "${COMPREPLY[0]}" ]]; then COMPREPLY=("${COMPREPLY[0]},") fi @@ -103,25 +97,28 @@ _bluealsa_aplay_pcms() { while read -r; do [[ "$REPLY" == " "* ]] && continue [[ "$REPLY" == "$cur"* ]] || continue - printf "%s\n" "${REPLY// /\\ }" - done <<< $(aplay -L 2>/dev/null) + echo "${REPLY// /\\ }" + done < <(aplay -L 2>/dev/null) } # helper function gets rfcomm dbus paths # @param $1 the full bluealsa service name ( org.bluealsa* ) _bluealsa_rfcomm_paths() { busctl --list tree "$1" 2>/dev/null | while read -r line; do - [[ "$line" = /org/bluealsa/hci[0-9]/dev*/rfcomm ]] && printf "%s" "${line/\/rfcomm/}" + [[ "$line" = /org/bluealsa/hci[0-9]/dev*/rfcomm ]] || continue + echo "${line/\/rfcomm/}" done } -# helper function - Loop through words of command line. -# puts dbus arg ( --dbus=aaa ) into variable dbus_opt +# helper function gets options from command line +# @param $1 the executable name +# puts exec and dbus arg ( --dbus=aaa ) into array variable bautil_args # puts service name ( org.bluealsa.aaa ) into variable service # puts offset of first non-option argument into variable nonopt_offset # @return 0 if no errors found, 1 if dbus check failed _bluealsa_util_init() { local valopts="-B --dbus" + bautil_args=( "$1" ) service="org.bluealsa" nonopt_offset=0 local i @@ -132,14 +129,14 @@ _bluealsa_util_init() { break elif (( i == COMP_CWORD - 1 )) ; then [[ "${COMP_WORDS[i+1]}" = = ]] && break - dbus_opt="--dbus=${COMP_WORDS[i+1]}" + bautil_args+=( "--dbus=${COMP_WORDS[i+1]}" ) service="org.bluealsa.${COMP_WORDS[i+1]}" break else [[ "${COMP_WORDS[i+1]}" = = ]] && (( i++ )) if [[ "${COMP_WORDS[i+1]}" ]] ; then (( i++ )) - dbus_opt="--dbus=${COMP_WORDS[i]}" + bautil_args+=( "--dbus=${COMP_WORDS[i]}" ) service="org.bluealsa.${COMP_WORDS[i]}" continue fi @@ -149,13 +146,13 @@ _bluealsa_util_init() { if (( i == COMP_CWORD )) ; then break elif (( i == COMP_CWORD - 1 )) ; then - dbus_opt="--dbus=${COMP_WORDS[i+1]}" + bautil_args+=( "--dbus=${COMP_WORDS[i+1]}" ) service="org.bluealsa.${COMP_WORDS[i+1]}" break else if [[ "${COMP_WORDS[i+1]}" ]] ; then (( i++ )) - dbus_opt="--dbus=${COMP_WORDS[i]}" + bautil_args+=( "--dbus=${COMP_WORDS[i]}" ) service="org.bluealsa.${COMP_WORDS[i]}" continue fi @@ -179,43 +176,44 @@ _bluealsa_util_init() { # helper function completes options _bluealsa_complete_options() { - COMPREPLY=( $(compgen -W "$(_parse_help $1)" -- $cur) ) - [[ $COMPREPLY == *= ]] && compopt -o nospace + readarray -t COMPREPLY < <(compgen -W "$(_parse_help "$1")" -- "$cur") + [[ ${COMPREPLY[0]} == *= ]] && compopt -o nospace } # completion function for bluealsa # complete available devices and profiles in addition to options _bluealsa() { - local cur prev words cword split list - local prefix + local cur prev words cword split _init_completion -s || return + local prefix list + case "$prev" in --device|-i) - COMPREPLY=( $(compgen -W "$(ls -I *:* /sys/class/bluetooth)" -- $cur) ) + readarray -t list < <(ls -I '*:*' /sys/class/bluetooth) + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return ;; --profile|-p) [[ $cur =~ ^[+-].* ]] && prefix=${cur:0:1} && cur=${cur:1} - COMPREPLY=( $(compgen -P "$prefix" -W "$(_bluealsa_profiles $1)" -- $cur) ) + readarray -t list < <(_bluealsa_profiles "$1") + readarray -t COMPREPLY < <(compgen -P "$prefix" -W "${list[*]}" -- "$cur") return ;; --codec|-c) [[ $cur =~ ^[+-].* ]] && prefix=${cur:0:1} && cur=${cur:1} - COMPREPLY=( $(compgen -P "$prefix" -W "$(_bluealsa_codecs $1)" -- $cur) ) + readarray -t list < <(_bluealsa_codecs "$1") + readarray -t COMPREPLY < <(compgen -P "$prefix" -W "${list[*]}" -- "$cur") return ;; - --sbc-quality|--mp3-algorithm|--mp3-vbr-quality|--ldac-quality) - COMPREPLY=( $(compgen -W "$(_bluealsa_enum_values $1 $prev)" -- $cur) ) - return - ;; - --aac-latm-version) - COMPREPLY=( $(compgen -W "0 1" -- $cur) ) + --sbc-quality|--aac-latm-version|--mp3-algorithm|--mp3-vbr-quality|--ldac-quality) + readarray -t list < <(_bluealsa_enum_values "$1" "$prev") + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return ;; --xapl-resp-name) - COMPREPLY=( $(compgen -W "BlueALSA iPhone" -- $cur) ) + readarray -t COMPREPLY < <(compgen -W "BlueALSA iPhone" -- "$cur") return ;; --*) @@ -231,15 +229,17 @@ _bluealsa() { # - does not complete MAC addresses # requires aplay to list ALSA pcms _bluealsa_aplay() { - local cur prev words cword split + local cur prev words cword split _init_completion -s -n : || return + local list + case "$prev" in --dbus|-B) _have dbus-send || return - list=$(_bluealsa_list_dbus_suffices) - COMPREPLY=( $(compgen -W "$list" -- $cur) ) + readarray -t list < <(_bluealsa_list_dbus_suffices) + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return ;; --pcm|-D) @@ -248,8 +248,7 @@ _bluealsa_aplay() { # do not attempt completion on words containing ' or " [[ "$cur" =~ [\'\"] ]] && return - local IFS=$'\n' - COMPREPLY=( $(_bluealsa_aplay_pcms "$cur" ) ) + readarray -t COMPREPLY < <(_bluealsa_aplay_pcms "$cur") # ALSA pcm names can contain '=' and ':', both of which cause # problems for bash completion if it considers them to be word @@ -260,7 +259,7 @@ _bluealsa_aplay() { local equal_prefix=${cur%"${cur##*=}"} local i=${#COMPREPLY[*]} while [[ $((--i)) -ge 0 ]]; do - COMPREPLY[$i]=${COMPREPLY[$i]#"$equal_prefix"} + COMPREPLY[i]=${COMPREPLY[$i]#"$equal_prefix"} done fi __ltrim_colon_completions "$cur" @@ -277,13 +276,13 @@ _bluealsa_aplay() { # completion function for bluealsa-cli # complete available dbus suffices, command names and pcm paths in addition to options _bluealsa_cli() { - local cur prev words cword split - local dbus_opt service - local -i nonopt_offset + local cur prev words cword split _init_completion -s || return - _bluealsa_util_init $1 || return + local bautil_args service + local -i nonopt_offset + _bluealsa_util_init "$1" || return # the command names supported by this version of bluealsa-cli local simple_commands="list-pcms list-services monitor status" @@ -297,8 +296,7 @@ _bluealsa_cli() { local base_shortopts="-B -V -q -v" local base_longopts="--dbus= --version --quiet --verbose" - local command= - local path= + local command path list # process pre-command options case "$nonopt_offset" in @@ -306,32 +304,32 @@ _bluealsa_cli() { case "$prev" in --dbus|-B) _have dbus-send || return - list=$(_bluealsa_list_dbus_suffices) - COMPREPLY=( $(compgen -W "$list" -- $cur) ) + readarray -t list < <(_bluealsa_list_dbus_suffices) + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return ;; esac case "$cur" in -|--*) - COMPREPLY=( $(compgen -W "$global_longopts $base_longopts" -- "$cur") ) + readarray -t COMPREPLY < <(compgen -W "$global_longopts $base_longopts" -- "$cur") [[ "${COMPREPLY[0]}" == *= ]] && compopt -o nospace ;; -?) - COMPREPLY=( $(compgen -W "$global_shortopts $base_shortopts" -- "$cur") ) + readarray -t COMPREPLY < <(compgen -W "$global_shortopts $base_shortopts" -- "$cur") ;; esac return ;; "$COMP_CWORD") # list available commands - COMPREPLY=( $(compgen -W "$simple_commands $path_commands" -- "$cur") ) + readarray -t COMPREPLY < <(compgen -W "$simple_commands $path_commands" -- "$cur") return ;; esac # check for valid command command="${COMP_WORDS[nonopt_offset]}" - [[ "$simple_commands $path_commands" =~ "$command" ]] || return + [[ "$simple_commands $path_commands" =~ $command ]] || return # process command-specific options case "$command" in @@ -370,7 +368,7 @@ _bluealsa_cli() { esac # find path argument - for (( i=((nonopt_offset + 1)); i < COMP_CWORD; i++ )); do + for (( i=(nonopt_offset + 1); i < COMP_CWORD; i++ )); do [[ "${COMP_WORDS[i]}" == -* ]] && continue path="${COMP_WORDS[i]}" path_offset=i @@ -381,17 +379,18 @@ _bluealsa_cli() { if [[ -z "$path" ]] ; then case "$cur" in -|--*) - COMPREPLY=( $(compgen -W "$global_longopts" -- $cur) ) + readarray -t COMPREPLY < <(compgen -W "$global_longopts" -- "$cur") [[ "${COMPREPLY[0]}" == --properties* ]] && compopt -o nospace return ;; -?) - COMPREPLY=( $(compgen -W "$global_shortopts" -- $cur) ) + readarray -t COMPREPLY < <(compgen -W "$global_shortopts" -- "$cur") return ;; esac - if [[ "$path_commands" =~ "$command" ]]; then - COMPREPLY=( $(compgen -W "$("$1" $dbus_opt list-pcms 2>/dev/null)" -- $cur) ) + if [[ "$path_commands" =~ $command ]]; then + readarray -t list < <("${bautil_args[@]}" list-pcms 2>/dev/null) + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return fi fi @@ -399,14 +398,17 @@ _bluealsa_cli() { # process command positional arguments case "$command" in codec) - (( COMP_CWORD == path_offset + 1 )) && COMPREPLY=( $(compgen -W "$(_bluealsa_pcm_codecs "$1" "$dbus_opt" "$path")" -- $cur) ) + if (( COMP_CWORD == path_offset + 1 )) ; then + readarray -t list < <(_bluealsa_pcm_codecs "$path") + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") + fi ;; mute) if (( COMP_CWORD < path_offset + 3 )) ; then if [[ "$cur" == "" ]] ; then COMPREPLY=( true false ) else - COMPREPLY=( $(compgen -W "on yes true 1 off no false 0" -- ${cur,,}) ) + readarray -t COMPREPLY < <(compgen -W "on yes true 1 off no false 0" -- "${cur,,}") fi fi ;; @@ -415,7 +417,7 @@ _bluealsa_cli() { if [[ "$cur" == "" ]] ; then COMPREPLY=( true false ) else - COMPREPLY=( $(compgen -W "on yes true 1 off no false 0" -- ${cur,,}) ) + readarray -t COMPREPLY < <(compgen -W "on yes true 1 off no false 0" -- "${cur,,}") fi fi ;; @@ -436,20 +438,22 @@ _bluealsa_cli() { # complete available dbus suffices and device paths in addition to options # requires busctl (part of elogind or systemd) to list device paths _bluealsa_rfcomm() { - local cur prev words cword split - local dbus_opt service - local -i nonopt_offset + local cur prev words cword split _init_completion -s || return - _bluealsa_util_init $1 || return + local bautil_args service + local -i nonopt_offset + _bluealsa_util_init "$1" || return + + local list # check for dbus service suffix first case "$prev" in --dbus|-B) _have dbus-send || return - list=$(_bluealsa_list_dbus_suffices) - COMPREPLY=( $(compgen -W "$list" -- $cur) ) + readarray -t list < <(_bluealsa_list_dbus_suffices) + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return ;; --*) @@ -459,17 +463,21 @@ _bluealsa_rfcomm() { if (( nonopt_offset == COMP_CWORD )) ; then _have busctl || return - COMPREPLY=( $(compgen -W "$(_bluealsa_rfcomm_paths $service)" -- $cur) ) + readarray -t list < <(_bluealsa_rfcomm_paths "$service") + readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur") return fi # do not list options if path was found - (( nonopt_offset > 0 )) || _bluealsa_complete_options "$1" + (( nonopt_offset > 0 )) && return + + _bluealsa_complete_options "$1" } # completion function for a2dpconf and hcitop # complete only the options _bluealsa_others() { + # shellcheck disable=SC2034 local cur prev words cword split _init_completion -s || return case "$prev" in diff --git a/src/main.c b/src/main.c index 12baee7..9bb24d8 100644 --- a/src/main.c +++ b/src/main.c @@ -418,13 +418,15 @@ int main(int argc, char **argv) { case 5 /* --aac-bitrate=BPS */ : config.aac_bitrate = atoi(optarg); break; - case 15 /* --aac-latm-version=NUM */ : - config.aac_latm_version = atoi(optarg); - if (config.aac_latm_version > 2) { - error("Invalid LATM version [0, 2]: %s", optarg); + case 15 /* --aac-latm-version=NUM */ : { + char *tmp; + config.aac_latm_version = strtoul(optarg, &tmp, 10); + if (config.aac_latm_version > 2 || optarg == tmp || *tmp != '\0') { + error("Invalid LATM version {0, 1, 2}: %s", optarg); return EXIT_FAILURE; } break; + } case 18 /* --aac-true-bps */ : config.aac_true_bps = true; break; diff --git a/utils/cli/cmd-monitor.c b/utils/cli/cmd-monitor.c index b0c6b27..1590c0b 100644 --- a/utils/cli/cmd-monitor.c +++ b/utils/cli/cmd-monitor.c @@ -1,6 +1,6 @@ /* * BlueALSA - cmd-monitor.c - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -69,7 +69,7 @@ static dbus_bool_t monitor_dbus_message_iter_get_pcm_props_cb(const char *key, char type_expected; if (monitor_properties_set[PROPERTY_CODEC].enabled && - strcmp(key, "Codec") == 0) { + strcmp(key, monitor_properties_set[PROPERTY_CODEC].name) == 0) { if (type != (type_expected = DBUS_TYPE_STRING)) goto fail; const char *codec; @@ -77,7 +77,7 @@ static dbus_bool_t monitor_dbus_message_iter_get_pcm_props_cb(const char *key, printf("PropertyChanged %s Codec %s\n", path, codec); } else if (monitor_properties_set[PROPERTY_RUNNING].enabled && - strcmp(key, "Running") == 0) { + strcmp(key, monitor_properties_set[PROPERTY_RUNNING].name) == 0) { if (type != (type_expected = DBUS_TYPE_BOOLEAN)) goto fail; dbus_bool_t running; @@ -85,7 +85,7 @@ static dbus_bool_t monitor_dbus_message_iter_get_pcm_props_cb(const char *key, printf("PropertyChanged %s Running %s\n", path, running ? "true" : "false"); } else if (monitor_properties_set[PROPERTY_SOFTVOL].enabled && - strcmp(key, "SoftVolume") == 0) { + strcmp(key, monitor_properties_set[PROPERTY_SOFTVOL].name) == 0) { if (type != (type_expected = DBUS_TYPE_BOOLEAN)) goto fail; dbus_bool_t softvol; @@ -93,7 +93,7 @@ static dbus_bool_t monitor_dbus_message_iter_get_pcm_props_cb(const char *key, printf("PropertyChanged %s SoftVolume %s\n", path, softvol ? "true" : "false"); } else if (monitor_properties_set[PROPERTY_VOLUME].enabled && - strcmp(key, "Volume") == 0) { + strcmp(key, monitor_properties_set[PROPERTY_VOLUME].name) == 0) { if (type != (type_expected = DBUS_TYPE_UINT16)) goto fail; dbus_uint16_t volume; commit 1219b1deb67147db4930217a5ff15647cfa1bf56 Author: borine <32966433+borine@users.noreply.github.com> Date: Sun Apr 23 15:32:27 2023 +0100 Fix race condition during A2DP source acquisition diff --git a/src/ba-transport.c b/src/ba-transport.c index 32f76ee..37e35c7 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -325,6 +325,7 @@ int ba_transport_thread_state_set( pthread_mutex_lock(&th->mutex); enum ba_transport_thread_state old_state = th->state; + /* Moving to the next state is always allowed. */ bool valid = state == th->state + 1; @@ -1482,6 +1483,15 @@ void ba_transport_set_codec( * errno is set to indicate the error. */ int ba_transport_start(struct ba_transport *t) { + /* For A2DP Source profile only, it is possible that BlueZ will + * activate the transport following a D-Bus "Acquire" request before the + * client thread has completed the acquisition procedure by initializing + * the I/O threads state. So in that case we must ensure that the + * acquisition procedure is not still in progress before we check the + * threads' state. */ + if (t->profile == BA_TRANSPORT_PROFILE_A2DP_SOURCE) + pthread_mutex_lock(&t->acquisition_mtx); + pthread_mutex_lock(&t->thread_enc.mutex); bool is_enc_idle = t->thread_enc.state == BA_TRANSPORT_THREAD_STATE_IDLE; pthread_mutex_unlock(&t->thread_enc.mutex); @@ -1489,6 +1499,9 @@ int ba_transport_start(struct ba_transport *t) { bool is_dec_idle = t->thread_dec.state == BA_TRANSPORT_THREAD_STATE_IDLE; pthread_mutex_unlock(&t->thread_dec.mutex); + if (t->profile == BA_TRANSPORT_PROFILE_A2DP_SOURCE) + pthread_mutex_unlock(&t->acquisition_mtx); + if (!is_enc_idle || !is_dec_idle) return errno = EINVAL, -1; commit 9c43684cf0dded1e5bb60fb48127b6ce1744ca9b Author: Arkadiusz Bokowy Date: Sun Apr 23 21:42:50 2023 +0200 Use STATE_DIRECTORY for persistent storage root diff --git a/configure.ac b/configure.ac index e8d9d3d..13ef9b5 100644 --- a/configure.ac +++ b/configure.ac @@ -220,6 +220,7 @@ AC_ARG_ENABLE([systemd], AM_CONDITIONAL([ENABLE_SYSTEMD], [test "x$enable_systemd" = "xyes"]) AM_COND_IF([ENABLE_SYSTEMD], [ PKG_CHECK_MODULES([SYSTEMD], [systemd >= 200]) + AC_DEFINE([ENABLE_SYSTEMD], [1], [Define to 1 if systemd is enabled.]) ]) AC_ARG_ENABLE([upower], diff --git a/src/main.c b/src/main.c index 4d3c9e5..12baee7 100644 --- a/src/main.c +++ b/src/main.c @@ -569,7 +569,13 @@ int main(int argc, char **argv) { a2dp_codecs_init(); - storage_init(BLUEALSA_STORAGE_DIR); + const char *storage_base_dir = BLUEALSA_STORAGE_DIR; +#if ENABLE_SYSTEMD + const char *systemd_state_dir; + if ((systemd_state_dir = getenv("STATE_DIRECTORY")) != NULL) + storage_base_dir = systemd_state_dir; +#endif + storage_init(storage_base_dir); /* In order to receive EPIPE while writing to the pipe whose reading end * is closed, the SIGPIPE signal has to be handled. For more information diff --git a/src/storage.c b/src/storage.c index 4f815b3..d121d7f 100644 --- a/src/storage.c +++ b/src/storage.c @@ -81,6 +81,7 @@ static void storage_free(struct storage *st) { * @return On success this function returns 0. Otherwise -1 is returned. */ int storage_init(const char *root) { + debug("Initializing persistent storage: %s", root); strncpy(storage_root_dir, root, sizeof(storage_root_dir) - 1); if (mkdir(storage_root_dir, S_IRWXU) == -1 && errno != EEXIST) warn("Couldn't create storage directory: %s", strerror(errno)); commit 67b2adb6ed26d964570ab538f00d15dc00d3fd06 Author: borine <32966433+borine@users.noreply.github.com> Date: Sat Apr 22 15:58:04 2023 +0100 Make transport acquisition an atomic operation When both source and sink clients connect in quick succession, it is possible that two client event handler threads will invoke ba_transport_acquire() at the same time. Only one will call the profile acquire() function, which is protected by the bt_fd_mtx mutex. However that mutex is unlocked before the thread states are set to BA_TRANSPORT_THREAD_STATE_IDLE; so there is no guarantee that the thread which set the "acquired" flag will be the first to reach the point where that flag is tested. It is therefore possible that one thread will leave the acquisition operation with the I/O thread states still set to BA_TRANSPORT_THREAD_STATE_TERMINATED. This is fixed by adding a dedicated mutex to ensure that the entire ba_transport_acquire() function is treated as a critical section. diff --git a/src/ba-transport.c b/src/ba-transport.c index 80297e9..32f76ee 100644 --- a/src/ba-transport.c +++ b/src/ba-transport.c @@ -637,6 +637,7 @@ static struct ba_transport *transport_new( pthread_mutex_init(&t->codec_id_mtx, NULL); pthread_mutex_init(&t->codec_select_client_mtx, NULL); pthread_mutex_init(&t->bt_fd_mtx, NULL); + pthread_mutex_init(&t->acquisition_mtx, NULL); pthread_cond_init(&t->stopped, NULL); t->bt_fd = -1; @@ -1224,6 +1225,7 @@ void ba_transport_unref(struct ba_transport *t) { pthread_cond_destroy(&t->stopped); pthread_mutex_destroy(&t->bt_fd_mtx); + pthread_mutex_destroy(&t->acquisition_mtx); pthread_mutex_destroy(&t->codec_select_client_mtx); pthread_mutex_destroy(&t->codec_id_mtx); free(t->bluez_dbus_owner); @@ -1607,6 +1609,8 @@ int ba_transport_acquire(struct ba_transport *t) { bool acquired = false; int fd = -1; + pthread_mutex_lock(&t->acquisition_mtx); + pthread_mutex_lock(&t->bt_fd_mtx); /* If we are in the middle of IO threads stopping, wait until all resources @@ -1644,6 +1648,8 @@ final: } + pthread_mutex_unlock(&t->acquisition_mtx); + return fd; } diff --git a/src/ba-transport.h b/src/ba-transport.h index f0ff8f8..325a3f0 100644 --- a/src/ba-transport.h +++ b/src/ba-transport.h @@ -263,6 +263,10 @@ struct ba_transport { * and the IO threads stopping flag */ pthread_mutex_t bt_fd_mtx; + /* Ensure BT file descriptor acquisition procedure + * is completed atomically. */ + pthread_mutex_t acquisition_mtx; + /* This field stores a file descriptor (socket) associated with the BlueZ * side of the transport. The role of this socket depends on the transport * type - it can be either A2DP or SCO link. */ commit d5246a8be2321f4380f2f074b8b4b0bc240e939a Author: Arkadiusz Bokowy Date: Sat Apr 15 21:48:18 2023 +0200 Keep worker active in case of PCM write error diff --git a/test/mock/mock-bluealsa.c b/test/mock/mock-bluealsa.c index 713ba60..caf8954 100644 --- a/test/mock/mock-bluealsa.c +++ b/test/mock/mock-bluealsa.c @@ -151,7 +151,7 @@ static void *mock_dec(struct ba_transport_thread *th) { const size_t samples = ARRAYSIZE(buffer); const size_t frames = samples / channels; - x = snd_pcm_sine_s16_2le(buffer, frames, channels, x, 1.0 / 128); + x = snd_pcm_sine_s16_2le(buffer, frames, channels, x, 146.83 / samplerate); io_pcm_scale(t_pcm, buffer, samples); if (io_pcm_write(t_pcm, buffer, samples) == -1) diff --git a/utils/aplay/aplay.c b/utils/aplay/aplay.c index e16a449..fd5e7fd 100644 --- a/utils/aplay/aplay.c +++ b/utils/aplay/aplay.c @@ -526,22 +526,11 @@ static void *io_worker_routine(struct io_worker *w) { goto fail; case 0: debug("BT device marked as inactive: %s", w->addr); - pcm_max_read_len = pcm_max_read_len_init; pause_retry_pcm_samples = pcm_1s_samples; pause_retries = 0; - ffb_rewind(&buffer); - if (w->snd_pcm != NULL) { - snd_pcm_close(w->snd_pcm); - w->snd_pcm = NULL; - } - if (w->snd_mixer != NULL) { - snd_mixer_close(w->snd_mixer); - w->snd_mixer_elem = NULL; - w->snd_mixer = NULL; - } w->active = false; timeout = -1; - continue; + goto close_alsa; } /* FIFO has been terminated on the writing side */ @@ -553,7 +542,7 @@ static void *io_worker_routine(struct io_worker *w) { if ((ret = read(w->ba_pcm_fd, buffer.tail, _in)) == -1) { if (errno == EINTR) continue; - error("Couldn't read from BlueALSA source PCM: %s", strerror(errno)); + error("BlueALSA source PCM read error: %s", strerror(errno)); goto fail; } @@ -636,6 +625,8 @@ static void *io_worker_routine(struct io_worker *w) { /* initial volume synchronization */ io_worker_mixer_volume_sync(w, &w->ba_pcm); + /* reset retry counters */ + pcm_open_retry_pcm_samples = 0; pcm_open_retries = 0; if (verbose >= 2) { @@ -689,12 +680,26 @@ retry_alsa_write: goto retry_alsa_write; default: error("ALSA playback PCM write error: %s", snd_strerror(frames)); - goto fail; + goto close_alsa; } /* move leftovers to the beginning and reposition tail */ ffb_shift(&buffer, frames * w->ba_pcm.channels); + continue; + +close_alsa: + ffb_rewind(&buffer); + pcm_max_read_len = pcm_max_read_len_init; + if (w->snd_pcm != NULL) { + snd_pcm_close(w->snd_pcm); + w->snd_pcm = NULL; + } + if (w->snd_mixer != NULL) { + snd_mixer_close(w->snd_mixer); + w->snd_mixer_elem = NULL; + w->snd_mixer = NULL; + } } fail: commit 541523ae1a3c4c2fb7264543777a48a93d66cd47 Author: borine <32966433+borine@users.noreply.github.com> Date: Mon Apr 10 20:03:09 2023 +0100 Contributing guide and extra detail in the diagram diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c51750e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing To BlueALSA + +## Code and manual pages + +This project welcomes contributions of code, documentation and testing. + +To submit code or manual page contributions please use GitHub Pull Requests. +The GitHub source code repository is at [https://github.com/arkq/bluez-alsa](https://github.com/arkq/bluez-alsa) + +For help with creating a Pull Request (PR), please consult the GitHub +documentation. In particular: + +* [creating forks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) + +* [creating pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request) + +The commit message for each commit that you make to your branch should include +a clear description of the change introduced by that commit. That will make the +change history log easier to follow when the PR is merged. + +If the PR is for a new feature, extensive change or non-trivial bug fix please +if possible add a simple unit test. If that is not possible then please include +in the PR description instructions on how to test the code manually. + +Before submitting a pull request, if possible please configure your build with +`--enable-test`; and to catch as many coding errors as possible please compile +with: + +```sh +make CFLAGS="-Wall -Wextra -Wshadow -Werror" +``` + +and then run the unit test suite: + +```sh +make check +``` + +When submitting the PR, please provide a description of the problem and its +fix, or the new feature and its rationale. Help the reviewer understand what to +expect. + +If you have an issue number, please reference it with a syntax `Fixes #123`. + +If you wish to help by testing PRs or by making review comments please do so by +adding comments to the PR. + +## Wiki + +The project [wiki](https://github.com/arkq/bluez-alsa/wiki) is "public" and +contributions there are also welcome. diff --git a/INSTALL.md b/INSTALL.md index cb7fa05..9210929 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -7,7 +7,7 @@ be covered fully here. For a comprehensive installation guide, please look at the [Installation from source][] project wiki page. If you've found something missing or incorrect, feel free to make a wiki contribution. -[Installation from source]: https://github.com/Arkq/bluez-alsa/wiki/Installation-from-source +[Installation from source]: https://github.com/arkq/bluez-alsa/wiki/Installation-from-source ## Configuration @@ -44,7 +44,7 @@ Dependencies: `--enable-mp3lame`) - [mpg123](https://www.mpg123.org/) (when MPEG decoding support is enabled with `--enable-mpg123`) -- [openaptx](https://github.com/Arkq/openaptx) (when apt-X support is enabled +- [openaptx](https://github.com/arkq/openaptx) (when apt-X support is enabled with `--enable-aptx` and/or `--enable-aptx-hd`) - [spandsp](https://www.soft-switch.org) (when mSBC support is enabled with `--enable-msbc`) diff --git a/README.md b/README.md index bec53f7..bfbbc08 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bluetooth Audio ALSA Backend -[![Build Status](https://github.com/Arkq/bluez-alsa/actions/workflows/build-and-test.yaml/badge.svg)](https://github.com/Arkq/bluez-alsa/actions/workflows/build-and-test.yaml) -[![Code Coverage](https://codecov.io/gh/Arkq/bluez-alsa/branch/master/graph/badge.svg)](https://codecov.io/gh/Arkq/bluez-alsa) +[![Build Status](https://github.com/arkq/bluez-alsa/actions/workflows/build-and-test.yaml/badge.svg)](https://github.com/arkq/bluez-alsa/actions/workflows/build-and-test.yaml) +[![Code Coverage](https://codecov.io/gh/arkq/bluez-alsa/branch/master/graph/badge.svg)](https://app.codecov.io/gh/arkq/bluez-alsa) ## About BlueALSA @@ -30,8 +30,11 @@ BlueALSA is designed specifically for use on small, low-powered, dedicated audio or audio/visual systems where the high-level audio management features of PulseAudio or PipeWire are not required. The target system must be able to function correctly with all its audio applications interfacing directly with -ALSA. In such systems BlueALSA adds Bluetooth audio support to the existing -ALSA sound card support. +ALSA, with only one application at a time using each Bluetooth audio stream. +In such systems BlueALSA adds Bluetooth audio support to the existing +ALSA sound card support. Note this means that the applications are constrained +by the capabilities of the ALSA API, and the higher-level audio processing +features of audio servers such as PulseAudio and Pipewire are not available. BlueALSA consists of the daemon `bluealsa`, ALSA plug-ins, and a number of utilities. The basic context is shown in this diagram: @@ -45,11 +48,16 @@ A[Bluetooth Adapter] <--> B((bluetoothd\ndaemon)) A <--> C((bluealsa daemon)) B <--> C C <--> D((bluealsa-aplay)) -C <--> E(("ALSA clients\n(via plugin)")) -C <--> F((other\nD-Bus clients)) - -class A,B external; -class C,D,E,F bluealsa; +D --> E([ALSA libasound]) +E --> K[Speakers] +C <--> F((bluealsa\nALSA plug-ins)) +C <--> G((bluealsa-cli)) +F <--> H([ALSA libasound]) +H <--> I((ALSA\napplications)) +C <--> J((other\nD-Bus clients)) + +class A,B,E,H,I,J,K external; +class C,D,F,G bluealsa; ``` The heart of BlueALSA is the daemon `bluealsa` which interfaces with the BlueZ @@ -80,7 +88,7 @@ are: Build and install instructions are included in the file [INSTALL.md](INSTALL.md) and more detailed guidance is available in the -[wiki](https://github.com/Arkq/bluez-alsa/wiki/Installation-from-source). +[wiki](https://github.com/arkq/bluez-alsa/wiki/Installation-from-source). ## Usage @@ -92,14 +100,19 @@ program shall be run as a root during system startup. It will register configured audio devices. In general, BlueALSA acts as a proxy between BlueZ and ALSA. +The `bluealsa` daemon must be running in order to pair, connect, and use +remote Bluetooth audio devices. In order to stream audio to e.g. a Bluetooth +headset, firstly one has to connect the device. If you are not familiar with +the Bluetooth pairing and connecting procedures on Linux, there is a basic +guide in the wiki: +[Bluetooth pairing and connecting](https://github.com/arkq/bluez-alsa/wiki/Bluetooth-Pairing-And-Connecting). + For details of command-line options to `bluealsa`, consult the [bluealsa manual page](doc/bluealsa.8.rst). ### ALSA plug-ins -In order to stream audio to e.g. a Bluetooth headset, firstly one has to -connect the device. The most straightforward method is to use BlueZ CLI utility -called `bluetoothctl`. When the device is connected one can use the `bluealsa` +When a Bluetooth audio device is connected one can use the `bluealsa` virtual PCM device with ALSA applications just like any other PCM device: ```sh @@ -114,7 +127,8 @@ aplay -D bluealsa:XX:XX:XX:XX:XX:XX, Bourree_in_E_minor.wav ``` Please note that this PCM device is based on the [ALSA software PCM I/O -plug-in][] - it will not be available in the [ALSA Kernel proc interface][]. +plug-in][] - it has no associated sound card, and it will not be available in +the [ALSA Kernel proc interface][]. [ALSA software PCM I/O plug-in]: https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_external_plugins.html [ALSA Kernel proc interface]: https://www.kernel.org/doc/html/latest/sound/designs/procfile.html @@ -170,7 +184,7 @@ For full details of the BlueALSA ALSA PCM device and mixer device consult the There are also a number of articles on the [bluez-alsa project wiki][] giving more examples of using these plug-ins. -[bluez-alsa project wiki]: https://github.com/Arkq/bluez-alsa/wiki +[bluez-alsa project wiki]: https://github.com/arkq/bluez-alsa/wiki For more advanced ALSA configuration, consult the [asoundrc on-line documentation][] provided by the AlsaProject wiki page. @@ -250,37 +264,26 @@ bluealsa-aplay -L ## Contributing -This project welcomes contributions of code, documentation and testing. For -code and manual page contributions, please use GitHub Pull Requests. There is -no strict policy for PRs, each contribution will be evaluated individually. If -you wish to help by testing PRs or by making review comments please do so by -adding comments to the PR. - -When preparing a pull request, if possible please configure with -`--enable-test`, and to catch as many coding errors as possible please compile -with: - -```sh -make CFLAGS="-Wall -Wextra -Werror" -``` +This project welcomes contributions of code, documentation and testing. -and then run the unit test suite: - -```sh -make check -``` - -The project wiki is "public" and contributions there are also welcome. +Please see the [CONTRIBUTING](CONTRIBUTING.md) guide for details. ## Bug reports, feature requests, and requests for help Before raising a new issue, please search previous issues (both open and closed), to see if your question has already been answered or problem resolved. -If reporting a problem, please clearly state the version of BlueALSA that you -are using, and give sufficient information for readers to be able to reproduce -the issue. - -Please also look at the [wiki](https://github.com/Arkq/bluez-alsa/wiki) if you +If reporting a problem, please clearly state: + +* the OS distribution and version you are using, +* the version of BlueALSA that you are using (`bluealsa --version`), +* if self-built from source, please state the branch and commit + (`git log -1 --oneline`) and the configure options used, +* the version of BlueZ (`bluetoothd --version`), +* the version of ALSA (`aplay --version`), +* sufficient additional information for readers to be able to reproduce the +issue. + +Please also look at the [wiki](https://github.com/arkq/bluez-alsa/wiki) if you require help as there is a great deal of useful information. Unfortunately the wiki is not indexed by web search engines, so searching on-line for your issue will not discover the information in there. diff --git a/configure.ac b/configure.ac index ffb8197..e8d9d3d 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ AC_PREREQ([2.60]) AC_INIT([BlueALSA], [m4_normalize(esyscmd([test -d .git && git describe --always --dirty || echo v4.0.0]))], - [arkadiusz.bokowy@gmail.com], [bluez-alsa], [https://github.com/Arkq/bluez-alsa]) + [arkadiusz.bokowy@gmail.com], [bluez-alsa], [https://github.com/arkq/bluez-alsa]) AM_INIT_AUTOMAKE([foreign subdir-objects -Wall -Werror]) AC_CONFIG_SRCDIR([src/bluealsa-config.c]) commit 516bc298ae65472763edcd45b0429fd16d237ae8 Author: Arkadiusz Bokowy Date: Fri Apr 14 07:42:43 2023 +0200 Templates for issues and pull requests diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md new file mode 100644 index 0000000..2735976 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug-report.md @@ -0,0 +1,32 @@ +--- +name: "\U0001F41B Bug report" +about: Create a report to help improve bluez-alsa. +title: +--- + +### Problem + +> A clear and concise description of what the bug is. +> If possible, please check if the bug still exists in the master branch (or +> the latest release if you are not using it already). + +### Reproduction steps + +> Provide a minimal example of how to reproduce the problem. State `bluealsa` +> command line arguments and the content of .asoundrc file (if PCM alias with +> "type bluealsa" was added to that file). + +### Setup + +> - the OS distribution and version +> - the version of BlueALSA (`bluealsa --version`) +> - if self-built from source, please state the branch, commit and used configure options +> - the version of BlueZ (`bluetoothd --version`) +> - the version of ALSA (`aplay --version`) + +### Additional context + +> Add any other context about the problem here, e.g. log messages printed by +> `bluealsa` and/or client application. +> +> Please delete instructions prefixed with '>' to prove you have read them. diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md new file mode 100644 index 0000000..5650185 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature-request.md @@ -0,0 +1,17 @@ +--- +name: "\U0001F4A1 Feature request" +about: Suggest an idea for bluez-alsa. +labels: enhancement +title: +--- + +### Feature description + +> A clear and concise description of what the problem is. Provide a simple use +> case for requested functionality or enhancement. + +## Additional context + +> Add any other context about the feature request. +> +> Please delete instructions prefixed with '>' to prove you have read them. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2b07c53 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +> Please have a good description of the problem and the fix. Help the reviewer +> understand what to expect. +> +> For new feature, extensive change or non-trivial bug fix add a simple unit +> test or at least describe how to test the code manually. +> +> If you have an issue number, please reference it with a syntax `Fixes #123`. +> +> Please delete instructions prefixed with '>' to prove you have read them. commit 4c5b6954d78563027eba3beb70d5d10bcb6c4629 Author: Arkadiusz Bokowy Date: Tue Apr 11 23:30:01 2023 +0200 Show PCM type in bluealsa-aplay log messages diff --git a/.github/workflows/code-scanning.yaml b/.github/workflows/code-scanning.yaml index 4b1a349..0962007 100644 --- a/.github/workflows/code-scanning.yaml +++ b/.github/workflows/code-scanning.yaml @@ -85,6 +85,7 @@ jobs: # XXX: iwyu package depends on clang-14, but is built with clang-13 packages: > bear + check iwyu libclang-13-dev libasound2-dev libbluetooth-dev @@ -120,7 +121,8 @@ jobs: --enable-cli \ --enable-rfcomm \ --enable-a2dpconf \ - --enable-hcitop + --enable-hcitop \ + --enable-test - name: Build working-directory: ${{ github.workspace }}/build run: bear -- make check TESTS= diff --git a/test/mock/mock-bluealsa.c b/test/mock/mock-bluealsa.c index 8035500..713ba60 100644 --- a/test/mock/mock-bluealsa.c +++ b/test/mock/mock-bluealsa.c @@ -29,16 +29,21 @@ #include #include "a2dp.h" -#include "a2dp-aptx.h" -#include "a2dp-aptx-hd.h" -#include "a2dp-faststream.h" +#if ENABLE_APTX +# include "a2dp-aptx.h" +#endif +#if ENABLE_APTX_HD +# include "a2dp-aptx-hd.h" +#endif +#if ENABLE_FASTSTREAM +# include "a2dp-faststream.h" +#endif #include "a2dp-sbc.h" #include "ba-adapter.h" #include "ba-device.h" #include "ba-rfcomm.h" #include "ba-transport.h" #include "bluealsa-config.h" -#include "bluealsa-dbus.h" #include "bluez.h" #include "codec-sbc.h" #include "hfp.h" @@ -213,6 +218,9 @@ static int mock_transport_acquire_bt(struct ba_transport *t) { t->mtu_read = 256; t->mtu_write = 256; + if (t->profile & BA_TRANSPORT_PROFILE_MASK_SCO) + t->mtu_read = t->mtu_write = 48; + debug("New transport: %d (MTU: R:%zu W:%zu)", t->bt_fd, t->mtu_read, t->mtu_write); g_thread_unref(g_thread_new(NULL, mock_bt_dump_thread, GINT_TO_POINTER(bt_fds[1]))); @@ -398,13 +406,8 @@ static void *mock_bluealsa_service_thread(void *userdata) { g_ptr_array_add(tt, t = mock_transport_new_sco(MOCK_DEVICE_1, BA_TRANSPORT_PROFILE_HFP_AG, MOCK_BLUEZ_SCO_PATH_1)); - if (mock_fuzzing_ms) { - t->codec_id = HFP_CODEC_CVSD; - bluealsa_dbus_pcm_update(&t->sco.spk_pcm, - BA_DBUS_PCM_UPDATE_SAMPLING | BA_DBUS_PCM_UPDATE_CODEC); - bluealsa_dbus_pcm_update(&t->sco.mic_pcm, - BA_DBUS_PCM_UPDATE_SAMPLING | BA_DBUS_PCM_UPDATE_CODEC); - } + if (mock_fuzzing_ms) + ba_transport_set_codec(t, HFP_CODEC_CVSD); } diff --git a/test/test-alsa-ctl.c b/test/test-alsa-ctl.c index 4dea1d4..aaf5dd6 100644 --- a/test/test-alsa-ctl.c +++ b/test/test-alsa-ctl.c @@ -544,12 +544,17 @@ CK_START_TEST(test_notifications) { ck_assert_int_eq(snd_ctl_subscribe_events(ctl, 0), 0); + size_t events_update_codec = 0; +#if ENABLE_MSBC + events_update_codec += 4; +#endif + /* Processed events: * - 0 removes; 2 new elems (12:34:... A2DP) * - 2 removes; 4 new elems (12:34:... A2DP, 23:45:... A2DP) * - 4 removes; 7 new elems (2x A2DP, SCO playback, battery) * - 7 removes; 9 new elems (2x A2DP, SCO playback/capture, battery) - * - 4 updates (SCO codec update) + * - 4 updates (SCO codec update if mSBC is supported) * * XXX: It is possible that the battery element (RFCOMM D-Bus path) will not * be exported in time. In such case, the number of events will be less @@ -557,9 +562,10 @@ CK_START_TEST(test_notifications) { * addition and less by another 1 when the path is not available during * the capture SCO addition. We shall account for this in the test, as * it is not an error. */ - if (events == (39 - 2) || events == (39 - 2 - 1)) - events = 39; - ck_assert_int_eq(events, (0 + 2) + (2 + 4) + (4 + 7) + (7 + 9) + 4); + if (events == (35 + events_update_codec - 2) || + events == (35 + events_update_codec - 2 - 1)) + events = 35 + events_update_codec; + ck_assert_int_eq(events, (0 + 2) + (2 + 4) + (4 + 7) + (7 + 9) + events_update_codec); snd_ctl_event_free(event); ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0); diff --git a/test/test-ba.c b/test/test-ba.c index f3c4596..e2c33f9 100644 --- a/test/test-ba.c +++ b/test/test-ba.c @@ -40,8 +40,10 @@ #include "ba-device.h" #include "ba-rfcomm.h" #include "ba-transport.h" +#include "bluealsa-config.h" #include "bluealsa-dbus.h" #include "bluez.h" +#include "hfp.h" #include "storage.h" #include "shared/a2dp-codecs.h" #include "shared/log.h" diff --git a/test/test-io.c b/test/test-io.c index f656ddb..de8fd0f 100644 --- a/test/test-io.c +++ b/test/test-io.c @@ -37,13 +37,27 @@ #endif #include "a2dp.h" -#include "a2dp-aac.h" -#include "a2dp-aptx.h" -#include "a2dp-aptx-hd.h" -#include "a2dp-faststream.h" -#include "a2dp-lc3plus.h" -#include "a2dp-ldac.h" -#include "a2dp-mpeg.h" +#if ENABLE_AAC +# include "a2dp-aac.h" +#endif +#if ENABLE_APTX +# include "a2dp-aptx.h" +#endif +#if ENABLE_APTX_HD +# include "a2dp-aptx-hd.h" +#endif +#if ENABLE_FASTSTREAM +# include "a2dp-faststream.h" +#endif +#if ENABLE_LC3PLUS +# include "a2dp-lc3plus.h" +#endif +#if ENABLE_LDAC +# include "a2dp-ldac.h" +#endif +#if ENABLE_MPEG +# include "a2dp-mpeg.h" +#endif #include "a2dp-sbc.h" #include "ba-adapter.h" #include "ba-device.h" @@ -54,7 +68,9 @@ #include "bluez.h" #include "hfp.h" #include "io.h" -#include "rtp.h" +#if ENABLE_LC3PLUS || ENABLE_LDAC +# include "rtp.h" +#endif #include "storage.h" #include "shared/a2dp-codecs.h" #include "shared/defs.h" diff --git a/test/test-utils-aplay.c b/test/test-utils-aplay.c index b16bf23..378db31 100644 --- a/test/test-utils-aplay.c +++ b/test/test-utils-aplay.c @@ -90,9 +90,9 @@ CK_START_TEST(test_configuration) { /* check selected configuration */ ck_assert_ptr_ne(strstr(output, " BlueALSA service: org.bluealsa.foo"), NULL); - ck_assert_ptr_ne(strstr(output, " PCM device: TestPCM"), NULL); - ck_assert_ptr_ne(strstr(output, " PCM buffer time: 10000 us"), NULL); - ck_assert_ptr_ne(strstr(output, " PCM period time: 500 us"), NULL); + ck_assert_ptr_ne(strstr(output, " ALSA PCM device: TestPCM"), NULL); + ck_assert_ptr_ne(strstr(output, " ALSA PCM buffer time: 10000 us"), NULL); + ck_assert_ptr_ne(strstr(output, " ALSA PCM period time: 500 us"), NULL); ck_assert_ptr_ne(strstr(output, " ALSA mixer device: TestMixer"), NULL); ck_assert_ptr_ne(strstr(output, " ALSA mixer element: 'TestMixerName',1"), NULL); ck_assert_ptr_ne(strstr(output, " Bluetooth device(s): 12:34:56:78:90:AB"), NULL); @@ -202,7 +202,7 @@ CK_START_TEST(test_play_single_audio) { "--single-audio", "--profile-a2dp", "--pcm=null", - "-vv", + "-vvv", NULL), -1); spawn_terminate(&sp_ba_aplay, 500); @@ -215,9 +215,9 @@ CK_START_TEST(test_play_single_audio) { #if DEBUG ck_assert_ptr_ne(strstr(output, - "Creating PCM worker 12:34:56:78:9A:BC"), NULL); + "Creating IO worker 12:34:56:78:9A:BC"), NULL); ck_assert_ptr_ne(strstr(output, - "Creating PCM worker 23:45:67:89:AB:CD"), NULL); + "Creating IO worker 23:45:67:89:AB:CD"), NULL); #endif bool d1_ok = strstr(output, "Used configuration for 12:34:56:78:9A:BC") != NULL; diff --git a/test/test-utils-cli.c b/test/test-utils-cli.c index 001a735..18a6a88 100644 --- a/test/test-utils-cli.c +++ b/test/test-utils-cli.c @@ -13,11 +13,13 @@ #endif #include +#include #include #include #include #include #include +#include #include @@ -353,11 +355,13 @@ CK_START_TEST(test_monitor) { ck_assert_ptr_ne(strstr(output, "Device: /org/bluez/hci0/dev_23_45_67_89_AB_CD"), NULL); +#if ENABLE_MSBC /* notifications for property changed */ ck_assert_ptr_ne(strstr(output, "PropertyChanged /org/bluealsa/hci0/dev_12_34_56_78_9A_BC/hfpag/sink Codec CVSD"), NULL); ck_assert_ptr_ne(strstr(output, "PropertyChanged /org/bluealsa/hci0/dev_12_34_56_78_9A_BC/hfpag/source Codec CVSD"), NULL); +#endif spawn_terminate(&sp_ba_mock, 0); spawn_close(&sp_ba_mock, NULL); diff --git a/utils/aplay/alsa-pcm.c b/utils/aplay/alsa-pcm.c index 346474b..ebe2ee6 100644 --- a/utils/aplay/alsa-pcm.c +++ b/utils/aplay/alsa-pcm.c @@ -1,6 +1,6 @@ /* * BlueALSA - alsa-pcm.c - * Copyright (c) 2016-2020 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -163,3 +163,10 @@ fail: free(tmp); return err; } + +void alsa_pcm_dump(snd_pcm_t *pcm, FILE *fp) { + snd_output_t *out; + snd_output_stdio_attach(&out, fp, 0); + snd_pcm_dump(pcm, out); + snd_output_close(out); +} diff --git a/utils/aplay/alsa-pcm.h b/utils/aplay/alsa-pcm.h index be332ea..73da92d 100644 --- a/utils/aplay/alsa-pcm.h +++ b/utils/aplay/alsa-pcm.h @@ -1,6 +1,6 @@ /* * BlueALSA - alsa-pcm.h - * Copyright (c) 2016-2020 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -8,9 +8,12 @@ * */ +#pragma once #ifndef BLUEALSA_APLAY_ALSAPCM_H_ #define BLUEALSA_APLAY_ALSAPCM_H_ +#include + #include int alsa_pcm_open(snd_pcm_t **pcm, const char *name, @@ -18,4 +21,6 @@ int alsa_pcm_open(snd_pcm_t **pcm, const char *name, unsigned int *buffer_time, unsigned int *period_time, char **msg); +void alsa_pcm_dump(snd_pcm_t *pcm, FILE *fp); + #endif diff --git a/utils/aplay/aplay.c b/utils/aplay/aplay.c index 7b03d8d..e16a449 100644 --- a/utils/aplay/aplay.c +++ b/utils/aplay/aplay.c @@ -1,6 +1,6 @@ /* * BlueALSA - aplay.c - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2023 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -38,7 +39,7 @@ #include "alsa-pcm.h" #include "dbus.h" -struct pcm_worker { +struct io_worker { pthread_t thread; /* used BlueALSA PCM device */ struct ba_pcm ba_pcm; @@ -47,10 +48,10 @@ struct pcm_worker { /* file descriptor of PCM control */ int ba_pcm_ctrl_fd; /* opened playback PCM device */ - snd_pcm_t *pcm; + snd_pcm_t *snd_pcm; /* mixer for volume control */ - snd_mixer_t *mixer; - snd_mixer_elem_t *mixer_elem; + snd_mixer_t *snd_mixer; + snd_mixer_elem_t *snd_mixer_elem; bool mixer_has_mute_switch; /* if true, playback is active */ atomic_bool active; @@ -85,7 +86,7 @@ static pthread_mutex_t single_playback_mutex = PTHREAD_MUTEX_INITIALIZER; static bool force_single_playback = false; static pthread_rwlock_t workers_lock = PTHREAD_RWLOCK_INITIALIZER; -static struct pcm_worker *workers = NULL; +static struct io_worker *workers = NULL; static size_t workers_count = 0; static size_t workers_size = 0; @@ -256,9 +257,9 @@ static struct ba_pcm *get_ba_pcm(const char *path) { return NULL; } -static struct pcm_worker *get_active_worker(void) { +static struct io_worker *get_active_io_worker(void) { - struct pcm_worker *w = NULL; + struct io_worker *w = NULL; size_t i; pthread_rwlock_rdlock(&workers_lock); @@ -307,18 +308,20 @@ final: /** * Synchronize BlueALSA PCM volume with ALSA mixer element. */ -static int pcm_worker_mixer_volume_sync( - struct pcm_worker *worker, +static int io_worker_mixer_volume_sync( + struct io_worker *worker, struct ba_pcm *ba_pcm) { /* skip sync in case of software volume */ if (ba_pcm->soft_volume) return 0; - snd_mixer_elem_t *elem = worker->mixer_elem; + snd_mixer_elem_t *elem = worker->snd_mixer_elem; if (elem == NULL) return 0; + debug("Synchronizing volume: %s", ba_pcm->device_path); + const int vmax = BA_PCM_VOLUME_MAX(ba_pcm); long long volume_db_sum = 0; bool muted = true; @@ -331,14 +334,14 @@ static int pcm_worker_mixer_volume_sync( int err; if ((err = snd_mixer_selem_get_playback_dB(elem, 0, &ch_volume_db)) != 0) { - error("Couldn't get playback dB level: %s", snd_strerror(err)); + error("Couldn't get ALSA mixer playback dB level: %s", snd_strerror(err)); return -1; } /* mute switch is an optional feature for a mixer element */ if ((worker->mixer_has_mute_switch = snd_mixer_selem_has_playback_switch(elem))) { if ((err = snd_mixer_selem_get_playback_switch(elem, 0, &ch_switch)) != 0) { - error("Couldn't get playback switch: %s", snd_strerror(err)); + error("Couldn't get ALSA mixer playback switch: %s", snd_strerror(err)); return -1; } } @@ -369,7 +372,7 @@ static int pcm_worker_mixer_volume_sync( DBusError err = DBUS_ERROR_INIT; if (!bluealsa_dbus_pcm_update(&dbus_ctx, ba_pcm, BLUEALSA_PCM_VOLUME, &err)) { - error("Couldn't update PCM: %s", err.message); + error("Couldn't update BlueALSA source PCM: %s", err.message); dbus_error_free(&err); return -1; } @@ -379,15 +382,15 @@ static int pcm_worker_mixer_volume_sync( /** * Update ALSA mixer element according to BlueALSA PCM volume. */ -static int pcm_worker_mixer_volume_update( - struct pcm_worker *worker, +static int io_worker_mixer_volume_update( + struct io_worker *worker, struct ba_pcm *ba_pcm) { /* skip update in case of software volume */ if (ba_pcm->soft_volume) return 0; - snd_mixer_elem_t *elem = worker->mixer_elem; + snd_mixer_elem_t *elem = worker->snd_mixer_elem; if (elem == NULL) return 0; @@ -415,21 +418,21 @@ static int pcm_worker_mixer_volume_update( int err; if ((err = snd_mixer_selem_set_playback_dB_all(elem, db, 0)) != 0) { - error("Couldn't set playback dB level: %s", snd_strerror(err)); + error("Couldn't set ALSA mixer playback dB level: %s", snd_strerror(err)); return -1; } /* mute switch is an optional feature for a mixer element */ if (worker->mixer_has_mute_switch && (err = snd_mixer_selem_set_playback_switch_all(elem, !muted)) != 0) { - error("Couldn't set playback mute switch: %s", snd_strerror(err)); + error("Couldn't set ALSA mixer playback mute switch: %s", snd_strerror(err)); return -1; } return 0; } -static void pcm_worker_routine_exit(struct pcm_worker *worker) { +static void io_worker_routine_exit(struct io_worker *worker) { if (worker->ba_pcm_fd != -1) { close(worker->ba_pcm_fd); worker->ba_pcm_fd = -1; @@ -438,19 +441,19 @@ static void pcm_worker_routine_exit(struct pcm_worker *worker) { close(worker->ba_pcm_ctrl_fd); worker->ba_pcm_ctrl_fd = -1; } - if (worker->pcm != NULL) { - snd_pcm_close(worker->pcm); - worker->pcm = NULL; + if (worker->snd_pcm != NULL) { + snd_pcm_close(worker->snd_pcm); + worker->snd_pcm = NULL; } - if (worker->mixer != NULL) { - snd_mixer_close(worker->mixer); - worker->mixer_elem = NULL; - worker->mixer = NULL; + if (worker->snd_mixer != NULL) { + snd_mixer_close(worker->snd_mixer); + worker->snd_mixer_elem = NULL; + worker->snd_mixer = NULL; } - debug("Exiting PCM worker %s", worker->addr); + debug("Exiting IO worker %s", worker->addr); } -static void *pcm_worker_routine(struct pcm_worker *w) { +static void *io_worker_routine(struct io_worker *w) { snd_pcm_format_t pcm_format = bluealsa_get_snd_pcm_format(&w->ba_pcm); ssize_t pcm_format_size = snd_pcm_format_size(pcm_format, 1); @@ -461,7 +464,7 @@ static void *pcm_worker_routine(struct pcm_worker *w) { * in order to prevent memory leaks and resources not being released. */ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); - pthread_cleanup_push(PTHREAD_CLEANUP(pcm_worker_routine_exit), w); + pthread_cleanup_push(PTHREAD_CLEANUP(io_worker_routine_exit), w); pthread_cleanup_push(PTHREAD_CLEANUP(ffb_free), &buffer); /* create buffer big enough to hold 100 ms of PCM data */ @@ -471,9 +474,10 @@ static void *pcm_worker_routine(struct pcm_worker *w) { } DBusError err = DBUS_ERROR_INIT; + debug("Opening BlueALSA source PCM: %s", w->ba_pcm.pcm_path); if (!bluealsa_dbus_pcm_open(&dbus_ctx, w->ba_pcm.pcm_path, &w->ba_pcm_fd, &w->ba_pcm_ctrl_fd, &err)) { - error("Couldn't open PCM: %s", err.message); + error("Couldn't open BlueALSA source PCM: %s", err.message); dbus_error_free(&err); goto fail; } @@ -497,7 +501,7 @@ static void *pcm_worker_routine(struct pcm_worker *w) { struct pollfd pfds[] = {{ w->ba_pcm_fd, POLLIN, 0 }}; int timeout = -1; - debug("Starting PCM loop"); + debug("Starting IO loop"); while (main_loop_on) { if (single_playback_mutex_locked) { @@ -518,22 +522,22 @@ static void *pcm_worker_routine(struct pcm_worker *w) { case -1: if (errno == EINTR) continue; - error("PCM FIFO poll error: %s", strerror(errno)); + error("IO loop poll error: %s", strerror(errno)); goto fail; case 0: - debug("Device marked as inactive: %s", w->addr); + debug("BT device marked as inactive: %s", w->addr); pcm_max_read_len = pcm_max_read_len_init; pause_retry_pcm_samples = pcm_1s_samples; pause_retries = 0; ffb_rewind(&buffer); - if (w->pcm != NULL) { - snd_pcm_close(w->pcm); - w->pcm = NULL; + if (w->snd_pcm != NULL) { + snd_pcm_close(w->snd_pcm); + w->snd_pcm = NULL; } - if (w->mixer != NULL) { - snd_mixer_close(w->mixer); - w->mixer_elem = NULL; - w->mixer = NULL; + if (w->snd_mixer != NULL) { + snd_mixer_close(w->snd_mixer); + w->snd_mixer_elem = NULL; + w->snd_mixer = NULL; } w->active = false; timeout = -1; @@ -544,21 +548,19 @@ static void *pcm_worker_routine(struct pcm_worker *w) { if (pfds[0].revents & POLLHUP) break; - #define MIN(a, b) a < b ? a : b - size_t _in = MIN(pcm_max_read_len, ffb_blen_in(&buffer)); - ssize_t ret; + size_t _in = MIN(pcm_max_read_len, ffb_blen_in(&buffer)); if ((ret = read(w->ba_pcm_fd, buffer.tail, _in)) == -1) { if (errno == EINTR) continue; - error("PCM FIFO read error: %s", strerror(errno)); + error("Couldn't read from BlueALSA source PCM: %s", strerror(errno)); goto fail; } /* Calculate the number of read samples. */ size_t read_samples = ret / pcm_format_size; if (ret % pcm_format_size != 0) - warn("Invalid read from PCM FIFO: %zd %% %zd != 0", ret, pcm_format_size); + warn("Invalid read from BlueALSA source PCM: %zd %% %zd != 0", ret, pcm_format_size); /* If current worker is not active and the single playback mode was * enabled, we have to check if there is any other active worker. */ @@ -571,7 +573,7 @@ static void *pcm_worker_routine(struct pcm_worker *w) { pthread_mutex_lock(&single_playback_mutex); single_playback_mutex_locked = true; - if (get_active_worker() != NULL) { + if (get_active_io_worker() != NULL) { /* In order not to flood BT connection with AVRCP packets, * we are going to send pause command every 0.5 second. */ if (pause_retries < 5 && @@ -588,12 +590,13 @@ static void *pcm_worker_routine(struct pcm_worker *w) { } - if (w->pcm == NULL) { + if (w->snd_pcm == NULL && + read_samples > 0) { unsigned int buffer_time = pcm_buffer_time; unsigned int period_time = pcm_period_time; - snd_pcm_uframes_t buffer_size; - snd_pcm_uframes_t period_size; + snd_pcm_uframes_t buffer_frames; + snd_pcm_uframes_t period_frames; char *tmp; if (pcm_open_retries > 0) { @@ -607,9 +610,11 @@ static void *pcm_worker_routine(struct pcm_worker *w) { continue; } - if (alsa_pcm_open(&w->pcm, pcm_device, pcm_format, w->ba_pcm.channels, + debug("Opening ALSA playback PCM: name=%s channels=%u rate=%u", + pcm_device, w->ba_pcm.channels, w->ba_pcm.sampling); + if (alsa_pcm_open(&w->snd_pcm, pcm_device, pcm_format, w->ba_pcm.channels, w->ba_pcm.sampling, &buffer_time, &period_time, &tmp) != 0) { - warn("Couldn't open PCM: %s", tmp); + warn("Couldn't open ALSA playback PCM: %s", tmp); pcm_max_read_len = pcm_max_read_len_init; pcm_open_retry_pcm_samples = 0; pcm_open_retries++; @@ -617,34 +622,40 @@ static void *pcm_worker_routine(struct pcm_worker *w) { continue; } - if (alsa_mixer_open(&w->mixer, &w->mixer_elem, + snd_pcm_get_params(w->snd_pcm, &buffer_frames, &period_frames); + pcm_max_read_len = period_frames * w->ba_pcm.channels * pcm_format_size; + + debug("Opening ALSA mixer: name=%s elem=%s index=%u", + mixer_device, mixer_elem_name, mixer_elem_index); + if (alsa_mixer_open(&w->snd_mixer, &w->snd_mixer_elem, mixer_device, mixer_elem_name, mixer_elem_index, &tmp) != 0) { - warn("Couldn't open mixer: %s", tmp); + warn("Couldn't open ALSA mixer: %s", tmp); free(tmp); } /* initial volume synchronization */ - pcm_worker_mixer_volume_sync(w, &w->ba_pcm); + io_worker_mixer_volume_sync(w, &w->ba_pcm); - snd_pcm_get_params(w->pcm, &buffer_size, &period_size); - pcm_max_read_len = period_size * w->ba_pcm.channels * pcm_format_size; pcm_open_retries = 0; if (verbose >= 2) { info("Used configuration for %s:\n" - " PCM buffer time: %u us (%zu bytes)\n" - " PCM period time: %u us (%zu bytes)\n" + " ALSA PCM buffer time: %u us (%zu bytes)\n" + " ALSA PCM period time: %u us (%zu bytes)\n" " PCM format: %s\n" " Sampling rate: %u Hz\n" " Channels: %u", w->addr, - buffer_time, snd_pcm_frames_to_bytes(w->pcm, buffer_size), - period_time, snd_pcm_frames_to_bytes(w->pcm, period_size), + buffer_time, snd_pcm_frames_to_bytes(w->snd_pcm, buffer_frames), + period_time, snd_pcm_frames_to_bytes(w->snd_pcm, period_frames), snd_pcm_format_name(pcm_format), w->ba_pcm.sampling, w->ba_pcm.channels); } + if (verbose >= 3) + alsa_pcm_dump(w->snd_pcm, stderr); + } /* mark device as active and set timeout to 500ms */ @@ -659,24 +670,25 @@ static void *pcm_worker_routine(struct pcm_worker *w) { } ffb_seek(&buffer, read_samples); - - /* calculate the overall number of frames in the buffer */ size_t samples = ffb_len_out(&buffer); - snd_pcm_sframes_t frames = samples / w->ba_pcm.channels; if (!w->mixer_has_mute_switch && pcm_muted) snd_pcm_format_set_silence(pcm_format, buffer.data, samples); - if ((frames = snd_pcm_writei(w->pcm, buffer.data, frames)) < 0) + snd_pcm_sframes_t frames; + +retry_alsa_write: + frames = samples / w->ba_pcm.channels; + if ((frames = snd_pcm_writei(w->snd_pcm, buffer.data, frames)) < 0) switch (-frames) { + case EINTR: + goto retry_alsa_write; case EPIPE: - debug("An underrun has occurred"); - snd_pcm_prepare(w->pcm); - usleep(50000); - frames = 0; - break; + debug("ALSA playback PCM underrun"); + snd_pcm_prepare(w->snd_pcm); + goto retry_alsa_write; default: - error("Couldn't write to PCM: %s", snd_strerror(frames)); + error("ALSA playback PCM write error: %s", snd_strerror(frames)); goto fail; } @@ -705,7 +717,7 @@ static bool pcm_hw_params_equal( /** * Stop the worker thread at workers[index]. */ -static void pcm_worker_stop(size_t index) { +static void io_worker_stop(size_t index) { /* Safety check for out-of-bounds read. */ assert(index < workers_count); @@ -724,7 +736,7 @@ static void pcm_worker_stop(size_t index) { } -static struct pcm_worker *supervise_pcm_worker_start(const struct ba_pcm *ba_pcm) { +static struct io_worker *supervise_io_worker_start(const struct ba_pcm *ba_pcm) { size_t i; for (i = 0; i < workers_count; i++) @@ -733,7 +745,7 @@ static struct pcm_worker *supervise_pcm_worker_start(const struct ba_pcm *ba_pcm * audio format may have changed. If it has, the worker thread * needs to be restarted. */ if (!pcm_hw_params_equal(&workers[i].ba_pcm, ba_pcm)) - pcm_worker_stop(i); + io_worker_stop(i); else return &workers[i]; } @@ -742,34 +754,34 @@ static struct pcm_worker *supervise_pcm_worker_start(const struct ba_pcm *ba_pcm workers_count++; if (workers_size < workers_count) { - struct pcm_worker *tmp = workers; + struct io_worker *tmp = workers; workers_size += 4; /* coarse-grained realloc */ if ((workers = realloc(workers, sizeof(*workers) * workers_size)) == NULL) { - error("Couldn't (re)allocate memory for PCM workers: %s", strerror(ENOMEM)); + error("Couldn't (re)allocate memory for IO workers: %s", strerror(ENOMEM)); workers = tmp; pthread_rwlock_unlock(&workers_lock); return NULL; } } - struct pcm_worker *worker = &workers[workers_count - 1]; + struct io_worker *worker = &workers[workers_count - 1]; memcpy(&worker->ba_pcm, ba_pcm, sizeof(worker->ba_pcm)); ba2str(&worker->ba_pcm.addr, worker->addr); worker->ba_pcm_fd = -1; worker->ba_pcm_ctrl_fd = -1; - worker->pcm = NULL; - worker->mixer = NULL; - worker->mixer_elem = NULL; + worker->snd_pcm = NULL; + worker->snd_mixer = NULL; + worker->snd_mixer_elem = NULL; worker->mixer_has_mute_switch = false; worker->active = false; pthread_rwlock_unlock(&workers_lock); - debug("Creating PCM worker %s", worker->addr); + debug("Creating IO worker %s", worker->addr); if ((errno = pthread_create(&worker->thread, NULL, - PTHREAD_FUNC(pcm_worker_routine), worker)) != 0) { - error("Couldn't create PCM worker %s: %s", worker->addr, strerror(errno)); + PTHREAD_FUNC(io_worker_routine), worker)) != 0) { + error("Couldn't create IO worker %s: %s", worker->addr, strerror(errno)); workers_count--; return NULL; } @@ -777,17 +789,17 @@ static struct pcm_worker *supervise_pcm_worker_start(const struct ba_pcm *ba_pcm return worker; } -static struct pcm_worker *supervise_pcm_worker_stop(const struct ba_pcm *ba_pcm) { +static struct io_worker *supervise_io_worker_stop(const struct ba_pcm *ba_pcm) { size_t i; for (i = 0; i < workers_count; i++) if (strcmp(workers[i].ba_pcm.pcm_path, ba_pcm->pcm_path) == 0) - pcm_worker_stop(i); + io_worker_stop(i); return NULL; } -static struct pcm_worker *supervise_pcm_worker(const struct ba_pcm *ba_pcm) { +static struct io_worker *supervise_io_worker(const struct ba_pcm *ba_pcm) { if (ba_pcm == NULL) return NULL; @@ -815,9 +827,9 @@ static struct pcm_worker *supervise_pcm_worker(const struct ba_pcm *ba_pcm) { goto start; stop: - return supervise_pcm_worker_stop(ba_pcm); + return supervise_io_worker_stop(ba_pcm); start: - return supervise_pcm_worker_start(ba_pcm); + return supervise_io_worker_start(ba_pcm); } static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage *message, void *data) { @@ -832,7 +844,7 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage * const char *signal = dbus_message_get_member(message); DBusMessageIter iter; - struct pcm_worker *worker; + struct io_worker *worker; if (strcmp(interface, DBUS_INTERFACE_OBJECT_MANAGER) == 0) { @@ -842,7 +854,7 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage * struct ba_pcm pcm; DBusError err = DBUS_ERROR_INIT; if (!bluealsa_dbus_message_iter_get_pcm(&iter, &err, &pcm)) { - error("Couldn't add new PCM: %s", err.message); + error("Couldn't add new BlueALSA PCM: %s", err.message); dbus_error_free(&err); goto fail; } @@ -850,26 +862,26 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage * goto fail; struct ba_pcm *tmp = ba_pcms; if ((ba_pcms = realloc(ba_pcms, (ba_pcms_count + 1) * sizeof(*ba_pcms))) == NULL) { - error("Couldn't add new PCM: %s", strerror(ENOMEM)); + error("Couldn't add new BlueALSA PCM: %s", strerror(ENOMEM)); ba_pcms = tmp; goto fail; } memcpy(&ba_pcms[ba_pcms_count++], &pcm, sizeof(*ba_pcms)); - supervise_pcm_worker(&pcm); + supervise_io_worker(&pcm); return DBUS_HANDLER_RESULT_HANDLED; } if (strcmp(signal, "InterfacesRemoved") == 0) { if (!dbus_message_iter_init(message, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) { - error("Couldn't remove PCM: %s", "Invalid signal signature"); + error("Couldn't remove BlueALSA PCM: %s", "Invalid signal signature"); goto fail; } dbus_message_iter_get_basic(&iter, &path); struct ba_pcm *pcm; if ((pcm = get_ba_pcm(path)) == NULL) goto fail; - supervise_pcm_worker_stop(pcm); + supervise_io_worker_stop(pcm); return DBUS_HANDLER_RESULT_HANDLED; } @@ -881,15 +893,15 @@ static DBusHandlerResult dbus_signal_handler(DBusConnection *conn, DBusMessage * goto fail; if (!dbus_message_iter_init(message, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) { - error("Couldn't update PCM: %s", "Invalid signal signature"); + error("Couldn't update BlueALSA PCM: %s", "Invalid signal signature"); goto fail; } dbus_message_iter_get_basic(&iter, &interface); dbus_message_iter_next(&iter); if (!bluealsa_dbus_message_iter_get_pcm_props(&iter, NULL, pcm)) goto fail; - if ((worker = supervise_pcm_worker(pcm)) != NULL) - pcm_worker_mixer_volume_update(worker, pcm); + if ((worker = supervise_io_worker(pcm)) != NULL) + io_worker_mixer_volume_update(worker, pcm); return DBUS_HANDLER_RESULT_HANDLED; } @@ -1074,9 +1086,9 @@ int main(int argc, char *argv[]) { info("Selected configuration:\n" " BlueALSA service: %s\n" - " PCM device: %s\n" - " PCM buffer time: %u us\n" - " PCM period time: %u us\n" + " ALSA PCM device: %s\n" + " ALSA PCM buffer time: %u us\n" + " ALSA PCM period time: %u us\n" " ALSA mixer device: %s\n" " ALSA mixer element: '%s',%u\n" " Bluetooth device(s): %s\n" @@ -1110,7 +1122,7 @@ int main(int argc, char *argv[]) { size_t i; for (i = 0; i < ba_pcms_count; i++) - supervise_pcm_worker(&ba_pcms[i]); + supervise_io_worker(&ba_pcms[i]); struct sigaction sigact = { .sa_handler = main_loop_stop }; sigaction(SIGTERM, &sigact, NULL);