Skip to content

Commit

Permalink
aplay: Simplify ALSA PCM buffering selection
Browse files Browse the repository at this point in the history
  • Loading branch information
borine authored and arkq committed Feb 6, 2025
1 parent fe5cc21 commit 10c36bb
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 36 deletions.
65 changes: 36 additions & 29 deletions doc/bluealsa-aplay.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ bluealsa-aplay
a simple BlueALSA player
------------------------

:Date: August 2024
:Date: February 2025
:Manual section: 1
:Manual group: General Commands Manual
:Version: $VERSION$
Expand Down Expand Up @@ -58,7 +58,8 @@ OPTIONS
(i.e., all messages are logged).

-v, --verbose
Make the output more verbose.
Make the output more verbose. This option may be given multiple times to
increase the verbosity.

-l, --list-devices
List connected Bluetooth audio devices.
Expand Down Expand Up @@ -89,35 +90,40 @@ OPTIONS

--pcm-buffer-time=INT
Set the playback PCM buffer duration time to *INT* microseconds.
The default is 200000. It is recommended to choose a buffer time that is
an exact multiple of the period time to avoid potential issues with some
ALSA plugins (see --pcm-period-time option below).
ALSA may choose the nearest available alternative if the requested value is
not supported.
The default is four times the period time. It is recommended to choose a
buffer time that is an exact multiple of the period time to avoid potential
issues with some ALSA plugins (see --pcm-period-time option below). For
reliable performance the buffer time should be at least 3 times the period
time.

If you experience underruns on the ALSA device then a larger buffer may
help. However, a larger buffer will also increase the latency. For reliable
performance the buffer time should be at least 3 times the period time.
ALSA may choose the nearest available alternative if the requested value is
not supported; and some ALSA devices may ignore the requested value
completely (e.g. **dmix**, see dmix_ in the **NOTES** section below).

--pcm-period-time=INT
Set the playback PCM period duration time to *INT* microseconds.
The default is 50000.
Set the playback PCM period duration time to *INT* microseconds. The
default is 50000 for A2DP and 20000 for SCO profiles.
ALSA may choose the nearest available alternative if the requested value is
not supported.
not supported; and some ALSA devices may ignore the requested value
completely (e.g. **dmix**, see dmix_ in the **NOTES** section below).

If you experience underruns on the ALSA device or overruns on the Bluetooth
stream then a larger period time may help. However, a larger period time
will also increase the latency.

The ALSA **rate** plugin, which may be invoked by **plug**, does not always
produce the exact required effective sample rate because of rounding errors
in the conversion between period time and period size. This can have a
significant impact on synchronization "drift", especially with small period
sizes, and can also result in stream underruns (if the effective rate is
too fast) or dropped A2DP frames in the **bluealsad(8)** server (if the
effective rate is too slow). This effect is avoided if the selected period
time results in an exact integer number of frames for both the source rate
(Bluetooth) and sink rate (hardware card). For example, in the case of
Bluetooth stream sampled at 44100Hz playing to a hardware device that
supports only 48000Hz, choosing a period time that is a multiple of 10000
microseconds will result in zero rounding error. (10000 µs at 44100Hz is
441 frames, and at 48000Hz is 480 frames).
too fast) or dropped frames (if the effective rate is too slow). This
effect is avoided if the selected period time results in an exact integer
number of frames for both the source rate (Bluetooth) and sink rate
(hardware card). For example, in the case of Bluetooth stream sampled at
44100 Hz playing to a hardware device that supports only 48000 Hz, choosing
a period time that is a multiple of 10000 microseconds will result in zero
rounding error. (10000 µs at 44100 Hz is 441 frames, and at 48000 Hz is 480
frames).

See also dmix_ in the **NOTES** section below for more information on
rate calculation rounding errors.
Expand Down Expand Up @@ -226,13 +232,14 @@ control.
dmix
----

The ALSA `dmix` plugin will ignore the period and buffer times selected by the
application (because it has to allow connections from multiple applications).
Instead it will choose its own values, which can lead to rounding errors in the
period size calculation when used with the ALSA `rate` plugin. To avoid this,
it is recommended to explicitly define the hardware period size and buffer size
for dmix in your ALSA configuration. For example, suppose we want a period time
of 50000 µs and a buffer holding 4 periods with an Intel 'PCH' card:
The ALSA **dmix** plugin will ignore the period and buffer times selected by
the application (because it has to allow connections from multiple
applications). Instead it will choose its own values, which can lead to
rounding errors in the period size calculation when used with the ALSA **rate**
plugin. To avoid this, it is recommended to explicitly define the hardware
period size and buffer size for **dmix** in your ALSA configuration. For
example, suppose we want a period time of 50000 µs and a buffer holding 4
periods with an Intel 'PCH' card:

::

Expand Down Expand Up @@ -260,7 +267,7 @@ EXAMPLES
========

The simplest usage of **bluealsa-aplay** is to run it with no arguments. It
will play audio from all connected Bluetooth devices to the ``default`` ALSA
will play audio from all connected Bluetooth devices to the **default** ALSA
playback PCM.

::
Expand Down
29 changes: 22 additions & 7 deletions utils/aplay/aplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
#include "dbus.h"
#include "delay-report.h"

/* Many devices cannot synchronize A/V with very high audio latency. To keep
* the overall latency below 400ms we choose default ALSA parameters such that
* the ALSA latency for A2DP is below 200ms. For SCO we choose to prioritize
* much lower latency over audio quality. */
#define DEFAULT_PERIOD_TIME_A2DP 50000
#define DEFAULT_PERIOD_TIME_SCO 20000
#define DEFAULT_PERIODS 4

enum volume_type {
VOL_TYPE_AUTO,
VOL_TYPE_MIXER,
Expand Down Expand Up @@ -85,11 +93,8 @@ static bool ba_profile_a2dp = true;
static bool ba_addr_any = false;
static bdaddr_t *ba_addrs = NULL;
static size_t ba_addrs_count = 0;
/* Many devices cannot synchronize A/V with very high audio latency. To keep
* the overall latency below 400ms we choose ALSA parameters such that the
* ALSA latency is below 200ms. */
static unsigned int pcm_buffer_time = 200000;
static unsigned int pcm_period_time = 50000;
static unsigned int pcm_buffer_time = 0;
static unsigned int pcm_period_time = 0;

/* local PCM muted state for software mute */
static bool pcm_muted = false;
Expand Down Expand Up @@ -1116,6 +1121,16 @@ int main(int argc, char *argv[]) {
if (volume_type == VOL_TYPE_NONE || volume_type == VOL_TYPE_SOFTWARE)
mixer_device = NULL;

if (pcm_buffer_time == 0) {
if (pcm_period_time == 0)
pcm_period_time = ba_profile_a2dp ?
DEFAULT_PERIOD_TIME_A2DP : DEFAULT_PERIOD_TIME_SCO;
pcm_buffer_time = pcm_period_time * DEFAULT_PERIODS;
}
else if (pcm_period_time == 0) {
pcm_period_time = pcm_buffer_time / DEFAULT_PERIODS;
}

if (verbose >= 1) {

char *ba_str = malloc(19 * ba_addrs_count + 1);
Expand All @@ -1137,16 +1152,16 @@ int main(int argc, char *argv[]) {
" ALSA PCM device: %s\n"
" ALSA PCM buffer time: %u us\n"
" ALSA PCM period time: %u us\n"
" Volume control type: %s\n"
" ALSA mixer device: %s\n"
" ALSA mixer element: %s\n"
" Volume control type: %s\n"
" Bluetooth device(s): %s\n"
" Profile: %s",
dbus_ba_service,
pcm_device, pcm_buffer_time, pcm_period_time,
volume_type_str,
mixer_device_str,
mixer_element_str,
volume_type_str,
ba_addr_any ? "ANY" : &ba_str[2],
ba_profile_a2dp ? "A2DP" : "SCO");

Expand Down

0 comments on commit 10c36bb

Please sign in to comment.