Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aplay resampler #750

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/apt-install-deps/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ runs:
libopenaptx-dev
libopus-dev
libreadline-dev
libsamplerate0-dev
libsbc-dev
libspandsp-dev
python3-docutils
2 changes: 2 additions & 0 deletions .github/spellcheck-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ HUP
ioplug
IPHONEACCEV
jas
jitter
keyp
larc
LEAdvertisement
Expand Down Expand Up @@ -256,6 +257,7 @@ SIGIO
SIGPIPE
SIGSEGV
SIGTERM
SINC
SL
SNR
spandsp
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
features:
- --enable-debug
- --enable-debug --enable-aac --enable-msbc --enable-lc3-swb
- --enable-debug --enable-mp3lame --enable-mpg123
- --enable-debug --enable-mp3lame --enable-mpg123 --enable-upower
- --enable-faststream --enable-midi --enable-mp3lame
- --enable-ofono --enable-opus --enable-upower
- --enable-aplay --with-libsamplerate --enable-ofono --enable-opus
- --disable-aplay --enable-rfcomm --enable-manpages
- --disable-ctl --enable-aptx --enable-aptx-hd --with-libopenaptx
fail-fast: false
Expand Down Expand Up @@ -126,6 +126,7 @@ jobs:
--enable-opus \
--enable-upower \
--enable-aplay \
--with-libsamplerate \
--enable-ctl \
--enable-test
- name: Build
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/code-scanning.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
--enable-opus \
--enable-upower \
--enable-aplay \
--with-libsamplerate \
--enable-ctl \
--enable-rfcomm \
--enable-a2dpconf \
Expand Down Expand Up @@ -108,6 +109,7 @@ jobs:
--enable-opus \
--enable-upower \
--enable-aplay \
--with-libsamplerate \
--enable-ctl \
--enable-rfcomm \
--enable-a2dpconf \
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codecov-report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
--enable-opus \
--enable-upower \
--enable-aplay \
--with-libsamplerate \
--enable-ctl \
--enable-test \
--with-coverage
Expand Down
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ unreleased
- optional support for A2DP Sink and Source with LHDC v3 codec
- channel map and volume control for surround sound (5.1, 7.1) audio
- native A2DP volume control by default (dropped --a2dp-volume option)
- optional support for adaptive audio resampling in bluealsa-aplay
- fix configuration for Android 13 A2DP Opus codec

bluez-alsa v4.3.1 (2024-08-30)
Expand Down
8 changes: 8 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,14 @@ AC_ARG_ENABLE([aplay],
[AS_HELP_STRING([--disable-aplay], [disable building of bluealsa-aplay tool])])
AM_CONDITIONAL([ENABLE_APLAY], [test "x$enable_aplay" != "xno"])

AC_ARG_WITH([libsamplerate],
[AS_HELP_STRING([--with-libsamplerate], [use libsamplerate in bluealsa-aplay])])
AM_CONDITIONAL([WITH_LIBSAMPLERATE], [test "x$with_libsamplerate" = "xyes"])
AM_COND_IF([WITH_LIBSAMPLERATE], [
PKG_CHECK_MODULES([SAMPLERATE], [samplerate >= 0.2.0])
AC_DEFINE([WITH_LIBSAMPLERATE], [1], [Define to 1 if libsamplerate shall be used.])
])

AC_ARG_ENABLE([rfcomm],
[AS_HELP_STRING([--enable-rfcomm], [enable building of bluealsa-rfcomm tool])])
AM_CONDITIONAL([ENABLE_RFCOMM], [test "x$enable_rfcomm" = "xyes"])
Expand Down
48 changes: 41 additions & 7 deletions doc/bluealsa-aplay.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ OPTIONS
Select ALSA playback PCM device to use for audio output.
The default is ``default``.

Internally, **bluealsa-aplay** does not perform any audio transformations
Internally, **bluealsa-aplay** is able to perform sample rate conversion
if it was built with libsamplerate support (see the option
**--resampler=**), but does not perform any other audio transformations
nor streams mixing. If multiple Bluetooth devices are connected it simply
opens a new connection to the ALSA PCM device for each stream. Selected
hardware parameters like sample rate and number of channels are
hardware parameters like sample format and number of channels are
taken from the audio profile of a particular Bluetooth connection. Note,
that each connection can have a different setup.

Expand Down Expand Up @@ -187,6 +189,38 @@ OPTIONS
Note: Only one of A2DP or SCO can be used. If both are specified, the
last one given will be selected.

--resampler=METHOD
Use libsamplerate to convert the stream from the Bluetooth sample rate to
the ALSA PCM sample rate. This option is only available if
**bluealsa-aplay** was built with libsamplerate support. The resampler uses
adaptive resampling to compensate for timer drift between the Bluetooth
timer and the ALSA device timer. Resampling can be CPU intensive and
therefore by default this option is not enabled. *METHOD* specifies which
libsamplerate converter to use, and may be one of 6 values:

- **best** - use the SRC_SINC_BEST_QUALITY converter; generates the highest
quality output but also has very high CPU usage.

- **medium** - use the SRC_SINC_MEDIUM_QUALITY converter; generates high
quality output and has moderately high CPU usage.

- **fastest** - use the SRC_SINC_FASTEST converter; generates good quality
output with lower CPU usage than the other SINC based converters. Often
this converter is the best compromise for Bluetooth audio.

- **zero-hold** - use the SRC_ZERO_ORDER_HOLD converter; the audio quality
is relatively poor compared to the SINC converters, but CPU usage is very
low so may be better suited to very low power embedded processors.

- **linear** - use the SRC_LINEAR converter; the lowest quality converter
of libsamplerate. Output quality is similar to the ALSA rate plugin's own
internal linear converter, but this option also performs adaptive
resampling which is not possible with the ALSA rate plugin.

- **none** - do not perform any resampling; the ALSA PCM device is then
responsible for rate conversion, and no timer drift adjustment is made.
This is the default when no resampler is specified.

--single-audio
Allow only one Bluetooth device to play audio at a time.
If multiple devices are connected, only the first to start will play, the
Expand Down Expand Up @@ -236,10 +270,10 @@ 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:
plugin (but not when using the *--resampler=* option). 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 @@ -308,7 +342,7 @@ element will be used as a hardware volume control knob.
COPYRIGHT
=========

Copyright (c) 2016-2024 Arkadiusz Bokowy.
Copyright (c) 2016-2025 Arkadiusz Bokowy.

The bluez-alsa project is licensed under the terms of the MIT license.

Expand Down
2 changes: 1 addition & 1 deletion misc/bash-completion/bluealsad
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ _bluealsa_aplay() {
__ltrim_colon_completions "$cur"
return
;;
--loglevel|--volume)
--loglevel|--resampler|--volume)
readarray -t list < <(_bluealsa_enum_values "$1" "$prev")
readarray -t COMPREPLY < <(compgen -W "${list[*]}" -- "$cur")
return
Expand Down
6 changes: 6 additions & 0 deletions src/shared/rt.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ unsigned int asrsync_get_dms_since_last_sync(const struct asrsync *asrs);
* @return Time in milliseconds. */
#define timespec2ms(ts) ((ts)->tv_sec * 1000 + (ts)->tv_nsec / 1000000)

/**
* Check whether the time defined by the timespec structure is zero. */
static inline bool is_timespec_zero(const struct timespec *ts) {
return ts->tv_sec == 0 && ts->tv_nsec == 0;
}

int difftimespec(
const struct timespec *ts1,
const struct timespec *ts2,
Expand Down
49 changes: 46 additions & 3 deletions test/test-utils-aplay.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ CK_START_TEST(test_play_all) {
ck_assert_int_ne(spawn_bluealsa_aplay(&sp_ba_aplay,
"--profile-a2dp",
"--pcm=null",
"--volume=none",
"-v", "-v",
NULL), -1);
spawn_terminate(&sp_ba_aplay, 500);

char output[8192] = "";
char output[16384] = "";
ck_assert_int_gt(spawn_read(&sp_ba_aplay, NULL, 0, output, sizeof(output)), 0);

/* check if playback was started from both devices */
Expand All @@ -199,11 +200,12 @@ CK_START_TEST(test_play_single_audio) {
"--single-audio",
"--profile-a2dp",
"--pcm=null",
"--volume=none",
"-v", "-v", "-v",
NULL), -1);
spawn_terminate(&sp_ba_aplay, 500);

char output[8192] = "";
char output[16384] = "";
ck_assert_int_gt(spawn_read(&sp_ba_aplay, NULL, 0, output, sizeof(output)), 0);

/* Check if playback was started for only one device. However,
Expand Down Expand Up @@ -272,11 +274,12 @@ CK_START_TEST(test_play_dbus_signals) {
ck_assert_int_ne(spawn_bluealsa_aplay(&sp_ba_aplay,
"--profile-sco",
"--pcm=null",
"--volume=none",
"-v", "-v",
NULL), -1);
spawn_terminate(&sp_ba_aplay, 1500);

char output[8192] = "";
char output[16384] = "";
ck_assert_int_gt(spawn_read(&sp_ba_aplay, NULL, 0, output, sizeof(output)), 0);

#if ENABLE_HFP_CODEC_SELECTION && DEBUG
Expand Down Expand Up @@ -305,6 +308,43 @@ CK_START_TEST(test_play_dbus_signals) {

} CK_END_TEST

#if WITH_LIBSAMPLERATE
CK_START_TEST(test_play_resampler) {

struct spawn_process sp_ba_mock;
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
"--profile=a2dp-sink",
NULL), -1);

struct spawn_process sp_ba_aplay;
ck_assert_int_ne(spawn_bluealsa_aplay(&sp_ba_aplay,
"--profile-a2dp",
"--pcm=null",
"--volume=none",
"--resampler=fastest",
"-v", "-v", "-v", "-v",
NULL), -1);
spawn_terminate(&sp_ba_aplay, 500);

char output[16384] = "";
ck_assert_int_gt(spawn_read(&sp_ba_aplay, NULL, 0, output, sizeof(output)), 0);

ck_assert_ptr_ne(strstr(output,
"Resampler method: fastest"), NULL);

#if DEBUG
/* Check if the resampler is correctly configured. */
ck_assert_ptr_ne(strstr(output,
"PCM sample rate conversion: 44100 Hz -> 44100.00 Hz"), NULL);
#endif

spawn_close(&sp_ba_aplay, NULL);
spawn_terminate(&sp_ba_mock, 0);
spawn_close(&sp_ba_mock, NULL);

} CK_END_TEST
#endif

int main(int argc, char *argv[]) {
preload(argc, argv, ".libs/libaloader.so");

Expand All @@ -330,6 +370,9 @@ int main(int argc, char *argv[]) {
tcase_add_test(tc, test_play_single_audio);
tcase_add_test(tc, test_play_mixer_setup);
tcase_add_test(tc, test_play_dbus_signals);
#if WITH_LIBSAMPLERATE
tcase_add_test(tc, test_play_resampler);
#endif

srunner_run_all(sr, CK_ENV);
int nf = srunner_ntests_failed(sr);
Expand Down
11 changes: 9 additions & 2 deletions utils/aplay/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ bluealsa_aplay_SOURCES = \
delay-report.c \
aplay.c

if WITH_LIBSAMPLERATE
bluealsa_aplay_SOURCES += \
resampler.c
endif

bluealsa_aplay_CFLAGS = \
-I$(top_srcdir)/src \
@ALSA_CFLAGS@ \
@BLUEZ_CFLAGS@ \
@DBUS1_CFLAGS@ \
@LIBUNWIND_CFLAGS@
@LIBUNWIND_CFLAGS@ \
@SAMPLERATE_CFLAGS@

bluealsa_aplay_LDADD = \
@ALSA_LIBS@ \
@BLUEZ_LIBS@ \
@DBUS1_LIBS@ \
@LIBUNWIND_LIBS@
@LIBUNWIND_LIBS@ \
@SAMPLERATE_LIBS@

endif
Loading