Using BlueALSA with the JACK Audio Connection Kit - arkq/bluez-alsa GitHub Wiki

Introduction

A BlueALSA PCM can be used with JACK in the same way as any other ALSA PCM, so long as the constraints imposed by Bluetooth Audio are taken into consideration:

  1. Bluetooth Audio is high-latency. There is nothing that BlueALSA or JACK can do to change that. So you need to resist the instinct of JACK users and general JACK advice to use very small period sizes and buffers. You will not succeed. At best you will waste CPU cycles and at worst experience constant underruns/overruns. Similarly, there is generally no need to run the jackd service with real-time priority unless that is also required by the jack client (for example zita-j2a and zita-a2j require real-time priority), it will not reduce latency.

  2. BlueALSA PCMs are transient. They can connect and disconnect at random intervals. JACK clients and servers always assume that PCMs are permanent; there is no provision in the JACK model for removal of a sound device while in use. jackd, alsa_out and other clients will fail if playing to a BlueALSA PCM that disconnects.

  3. A2DP transports may become "idle". The source may keep the transport open but not send any audio frames. Jack source clients such as zita-a2j and alsa_in will generate errors and possibly fail completely when this happens, so it is important to ensure the audio stream is running before starting the Jack source client, and that the stream continues to run as long as the Jack source is running. This issue applies only to BlueALSA capture devices.

There have been a number of bluez-alsa issues raised in respect of using BlueALSA with JACK. Athough those issues, and the recommendations and workarounds mentioned in them, were valid when written, there have been many improvements to both BlueALSA and JACK recently. So much so that by using the latest bluez-alsa release and recent JACK releases it now quite straightforward to use them together for A2DP playback (within the constraints described above). A2DP capture is also possible, but needs a little more care.

BlueALSA can be used without any need to create ~/.asoundrc entries, using either zita-j2a or alsa_out, and can even be used as the backend device of a jackd service although that is not recommended.

Playback

For audio output on a BlueALSA host running with the a2dp-source profile, start the jack client when the Bluetooth device has connected. Here are some examples:

zita-j2a

Possibly the simplest example is to create a jack sink using the bluealsa default PCM:

zita-j2a -j bluealsa -d bluealsa -p 1024 -n 3 -c 2 -L

The command line arguments are as follows:

-j bluealsa
    Use the name "bluealsa" for the sink. This is the name that jack
    clients use to send audio to the sink.

-d bluealsa
    Use the alsa device "bluealsa". This device name is predefined by
    the bluez-alsa installation, and used in this way will select the most
    recently connected A2DP playback device.

-p 1024
    Use an ALSA period size of 1024 frames.

-n 3
    Use an ALSA buffer of 3 periods. A smaller buffer will almost certainly
    result in constant underruns.

-c 2
    Use 2 channels. `zita-j2a` will create 2 jack ports, called
    `bluealsa:playback_1` and `bluealsa:playback_2`.

-L
    Use S16_LE samples. Without this option `zita-j2a` may convert to
    32-bit samples only for the ALSA `plug` plugin to convert them back
    to 16-bit.

To use a specific bluetooth device rather than the default, specify its MAC address in the device name, and give it a unique jack name:

zita-j2a -j bt_headphones -d bluealsa:XX:XX:XX:XX:XX:XX -p 4096 -n 3 -c 2 -L

We can now play to the headphones from any jack client. For example:

mpg123 -o jack -a bt_headphones:playback_1,bt_headphones:playback_2 MyFavouriteMusic.mp3

or

JACK_PLAY_CONNECT_TO=bt_headphones:playback_%d jack-play test_sounds.wav

alsa_out

The command line arguments for alsa_out are the same as for zita-j2a except that -L is not supported. For example:

alsa_out -j bt_headphones -d bluealsa:XX:XX:XX:XX:XX:XX -c 2 -p 4096 -n 3

There is still a bug in alsa_out that causes a buffer overflow if the device name is too long. It appears the longest device name allowed is 29 characters. Fortunately the form used here is only 26 characters.

jackd

BlueALSA can also be used as a backend for a jackd service

jackd -r -d alsa -P bluealsa -n 3 -S -o 2
-r
    Do not request real-time scheduling (optional - will also work without
    this)

-d alsa
    Use the ALSA backend

-P bluealsa
    Use the most recently connected BlueALSA playback device. You can also
    choose a specific device with `-P bluealsa:XX:XX:XX:XX:XX:XX`

-n 3
    Use an ALSA buffer of 3 periods. A smaller buffer is sure to produce
    underruns.

-o 2
    Create 2 output channels
-S
    Use S16_LE sample format

Clients can then send to the BlueALSA device with

JACK_PLAY_CONNECT_TO=system:playback_%d jack-play test_sounds.wav

Capture

When using BlueALSA with the profile a2dp-sink to input audio, for example from a mobile phone, it is important that the jack client must not be started until after the audio stream has been started. The client should be stopped when the remote device stops the audio stream (it will most likely fail anyway if not explicitly stopped). With the latest BlueALSA sources, the bluealsa plugin can be used with hw compatibility mode "silence" to artificially keep the application stream running even when the remote device is not sending audio so that the jack client can start as soon as the remote device connects; or alternatively a service such as bluealsa-agent from the bluealsa-autoconfig project can be used to automatically start and stop the jack client when the remote device starts or stops the stream.

Some examples using the hwcompat method:

zita-a2j

zita-a2j -j bluealsa -d bluealsa:HWCOMPAT=silence -p 1024 -n 3 -c 2 -L

alsa-in

alsa_in -j bt_headphones -d bluealsa:HWCOMPAT=silence -c 2 -p 4096 -n 3

.asoundrc

It is possible to remove the ALSA plug plugin from the audio processing chain, and instead rely on JACK's own audio processing features. To do this we have to define our PCMs in our ~/.asoundrc file. For example:

pcm.bt-headphones {
	type bluealsa
	device XX:XX:XX:XX:XX:XX
	profile a2dp
}
ctl.bt-headphones {
	type bluealsa
}

Remember here the limit of 29 characters for the PCM name when using alsa_out

With ALSA plug removed, we no longer need to force a sample format, so the -L or -S argument is no longer needed.

Then we can use this with JACK as:

zita-j2a -j bt-headphones -d bt-headphones -p 4096 -n 3 -c 2

or

jackd -r -d alsa -P bt-headphones -n 3 -o 2

Optimizing latency

Note that on some devices the hardware interrupts from the Bluetooth controller can interfere with the timeliness of the handlers for interrupts from the soundcard. This can make it impossible to run the jackd server with very small period sizes. The simplest solution is to increase the jackd period size, for example on a Raspberry Pi Zero W (original model, single core Arm V6 at 1GHz), I can use

zita-a2j -j bluealsa -d bluealsa -c 2 -r 44100 -p 1024 -n 3

with

jackd -d alsa -P hw -o2 -n3 -S -p512

and that give very good results. However, using the same zita-a2j command with

jackd -d alsa -P hw -o2 -n3 -S -p64

gives broken sound with a stream of errors from jackd.

Alternatively, if using a modern multi-core processor, it is possible to direct the interrupts to different cores such that the handlers can run concurrently. For example set the Bluetooth SMP IRQ affinity to one core, and use taskset to limit jackd and zita-a2j to the other cores. Consult your distribution documentation for advice on how to achieve this.