Skip to content

Commit

Permalink
ALSA PCM plugin hw compatibility modes none, busy
Browse files Browse the repository at this point in the history
  • Loading branch information
borine committed Dec 20, 2024
1 parent ec13288 commit b1d3f9a
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 13 deletions.
1 change: 1 addition & 0 deletions .github/spellcheck-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ hfphf
hspag
hsphs
HUP
HWCOMPAT
ioplug
IPHONEACCEV
jas
Expand Down
60 changes: 50 additions & 10 deletions doc/bluealsa-plugins.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ bluealsa-plugins
Bluetooth Audio ALSA Plugins
----------------------------

:Date: August 2024
:Date: December 2024
:Manual section: 7
:Manual group: Miscellaneous
:Version: $VERSION$
Expand Down Expand Up @@ -104,6 +104,14 @@ PCM Parameters
special value **unchanged** which causes the PCM to use its existing
softvol value. The default value is **unchanged**.

HWCOMPAT
Modifies the behavior of the plugin on ``a2dp-sink``, ``hfp-hf`` and
``hsp-hs`` nodes in order to align better with the behavior of the ALSA
``hw`` plugin. This is a string option which takes the values **none** or
**busy**.
See `Transport acquisition`_ in the **NOTES** section below for more
information.

DELAY
An integer number which is added to the reported delay (latency) value in
order to manually adjust the audio synchronization. It is not normally
Expand All @@ -129,6 +137,7 @@ own configuration (e.g. in ~/.asoundrc.conf) for example:
defaults.bluealsa.codec "cvsd"
defaults.bluealsa.volume "50+"
defaults.bluealsa.softvol off
defaults.bluealsa.hwcompat "busy"
defaults.bluealsa.delay 5000
defaults.bluealsa.service "org.bluealsa.source"

Expand All @@ -141,11 +150,11 @@ Positional Parameters
ALSA permits arguments to be given as positional parameters as an alternative
to explicitly naming them. When using positional parameters it is important
that the values are given in the correct sequence - *DEV*, *PROFILE*, *CODEC*,
*VOL*, *SOFTVOL*, *DELAY*, *SRV*. For example:
*VOL*, *SOFTVOL*, *HWCOMPAT*, *DELAY*, *SRV*. For example:

::

bluealsa:01:23:45:67:89:AB,a2dp,unchanged,unchanged,unchanged,0,org.bluealsa
bluealsa:01:23:45:67:89:AB,a2dp,unchanged,unchanged,unchanged,none,0,org.bluealsa

When using positional parameters defaults can only be implied at the end of the
id string, so
Expand Down Expand Up @@ -178,6 +187,7 @@ configuration node has the following fields:
[codec STR] # Preferred codec
[volume STR] # Initial volume for this PCM
[softvol BOOLEAN] # Enable/disable BlueALSA's software volume
[hwcompat STR] # HW compatibility mode (none or busy)
[delay INT] # Extra delay (frames) to be reported (default 0)
[service STR] # DBus name of service (default org.bluealsa)
}
Expand Down Expand Up @@ -568,13 +578,43 @@ then **bluealsad(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 HF/HS node then **bluealsad(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
to/from the PCM. For applications playing audio from a file or recording audio
to a file this is not normally an issue; but when streaming between some other
device and a BlueALSA device this may lead to very large latency (delay) or
trigger underruns or overruns in the other device.
for the remote device to acquire the transport. The ALSA PCM plugin state model
does not define any state that can be directly mapped to this situation, so
the BlueALSA PCM plugin offers a choice of behaviors to suit various
application requirements. The choice is selected using the parameter
**hwcompat** (**HWCOMPAT** argument to the pre-defined `bluealsa` PCM) which
takes the following values:

- none

The streams are presented exactly as handled by Bluetooth. No adjustments
are made to align the PCM more to expected ALSA behavior. While waiting for
the transport to be acquired the PCM plugin behaves as if the device
timer is stopped; it does not generate any poll() events, and the
application will be blocked when writing or reading to/from the PCM. For
applications playing audio from a file or recording audio to a file this is
not normally an issue and has the advantage that the played or captured
stream does not contain any frames of silence artificially inserted by the
plugin. However when streaming between some other device and a
BlueALSA device this may lead to very large latency (delay) or trigger
underruns or overruns in the other device. Capture streams may also have
brief interruptions caused by Bluetooth radio link interference. Some
applications, particularly ones which attempt to manage latency such as
``alsaloop(1)``, may become unstable in this situation.

- busy

Causes snd_pcm_open() to return immediately with error code **-EBUSY**
("Device or resource busy") on A2DP sink, HFP-HF and HSP-HS nodes if the
transport is not yet acquired. This is analogous to a ``hw`` device PCM
that is temporarily unavailable (for example because it is in use by some
other application). With this option the plugin also stops the
PCM stream and enters the **SND_PCM_STATE_DISCONNECTED** state if the
remote device releases the transport while in use, which is analogous to a
removable ``hw`` device being unplugged. If a capture stream is interrupted
by temporary Bluetooth link instability then the plugin simply blocks
temporarily, which may cause issues for some applications as noted for the
**none** value above.

PCM drain and non-blocking operation
------------------------------------
Expand Down
12 changes: 11 additions & 1 deletion src/asound/20-bluealsa.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ defaults.bluealsa.volume "unchanged"
# By default do not modify the software volume state
# when a PCM is opened.
defaults.bluealsa.softvol "unchanged"
# By default on a2dp-sink and AG nodes ignore transport acquisition state.
defaults.bluealsa.hwcompat "none"
# If Bluetooth sink device (e.g. headphones) supports
# A2DP v1.3 or later it will report delay by itself,
# so there is no need to set the delay manually.
Expand Down Expand Up @@ -82,7 +84,7 @@ ctl.bluealsa {
}

pcm.bluealsa {
@args [ DEV PROFILE CODEC VOL SOFTVOL DELAY SRV ]
@args [ DEV PROFILE CODEC VOL SOFTVOL HWCOMPAT DELAY SRV ]
@args.DEV {
type string
default {
Expand Down Expand Up @@ -118,6 +120,13 @@ pcm.bluealsa {
name defaults.bluealsa.softvol
}
}
@args.HWCOMPAT {
type string
default {
@func refer
name defaults.bluealsa.hwcompat
}
}
@args.DELAY {
type integer
default {
Expand All @@ -140,6 +149,7 @@ pcm.bluealsa {
codec $CODEC
volume $VOL
softvol $SOFTVOL
hwcompat $HWCOMPAT
delay $DELAY
service $SRV
}
Expand Down
42 changes: 40 additions & 2 deletions src/asound/bluealsa-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
# define BLUEALSA_HW_PARAMS_FIX 1
#endif

enum ba_hwcompat_t {
BA_HWCOMPAT_NONE,
BA_HWCOMPAT_BUSY,
};

struct bluealsa_pcm {
snd_pcm_ioplug_t io;

Expand Down Expand Up @@ -122,6 +127,8 @@ struct bluealsa_pcm {

/* /dev/null is used to clear stale data from the PCM FIFO. */
int null_fd;

enum ba_hwcompat_t hwcompat;
};

/**
Expand Down Expand Up @@ -383,6 +390,10 @@ static void *io_thread(snd_pcm_ioplug_t *io) {
return NULL;
}

static bool bluealsa_transport_available(struct bluealsa_pcm *pcm) {
return (pcm->ba_pcm.running || pcm->hwcompat != BA_HWCOMPAT_BUSY);
}

static int bluealsa_start(snd_pcm_ioplug_t *io) {
struct bluealsa_pcm *pcm = io->private_data;
debug2("Starting");
Expand Down Expand Up @@ -1355,8 +1366,11 @@ static DBusHandlerResult bluealsa_dbus_msg_filter(DBusConnection *conn,
dbus_message_iter_get_basic(&iter, &updated_interface);
dbus_message_iter_next(&iter);

if (strcmp(updated_interface, BLUEALSA_INTERFACE_PCM) == 0)
if (strcmp(updated_interface, BLUEALSA_INTERFACE_PCM) == 0) {
dbus_message_iter_get_ba_pcm_props(&iter, NULL, &pcm->ba_pcm);
if (!bluealsa_transport_available(pcm))
pcm->connected = false;
}

return DBUS_HANDLER_RESULT_HANDLED;
}
Expand Down Expand Up @@ -1469,6 +1483,7 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluealsa) {
const char *codec = NULL;
const char *volume = NULL;
const char *softvol = NULL;
const char *hwcompat = NULL;
long delay = 0;
struct bluealsa_pcm *pcm;
int ret;
Expand Down Expand Up @@ -1541,7 +1556,13 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluealsa) {
}
continue;
}

if (strcmp(id, "hwcompat") == 0) {
if (snd_config_get_string(n, &hwcompat) < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
Expand Down Expand Up @@ -1602,6 +1623,15 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluealsa) {
pcm->pause_state = BA_PAUSE_STATE_RUNNING;
pcm->null_fd = -1;

if (hwcompat == NULL || strcmp(hwcompat, "none") == 0)
pcm->hwcompat = BA_HWCOMPAT_NONE;
else if (strcmp(hwcompat, "busy") == 0)
pcm->hwcompat = BA_HWCOMPAT_BUSY;
else {
SNDERR("Invalid hwcompat mode: %s", hwcompat);
return -EINVAL;
}

dbus_threads_init_default();

DBusError err = DBUS_ERROR_INIT;
Expand Down Expand Up @@ -1671,6 +1701,14 @@ SND_PCM_PLUGIN_DEFINE_FUNC(bluealsa) {
goto fail;
}

if (pcm->ba_pcm.transport & (BA_PCM_TRANSPORT_A2DP_SOURCE | BA_PCM_TRANSPORT_MASK_AG))
pcm->hwcompat = BA_HWCOMPAT_NONE;

if (!bluealsa_transport_available(pcm)) {
ret = -EBUSY;
goto fail;
}

if (stream == SND_PCM_STREAM_CAPTURE)
if ((pcm->null_fd = open("/dev/null", O_WRONLY | O_NONBLOCK)) == -1) {
SNDERR("Couldn't open /dev/null: %s", strerror(errno));
Expand Down

0 comments on commit b1d3f9a

Please sign in to comment.