Updated on 2026/09/10
There are two types of Bluetooth:
The name BLE is somewhat of a misnomer. Because BLE's data rate is low, overall, it uses less energy than Classic Bluetooth. However, during the times that it does transmit and receives data, it uses more energy than Classic Bluetooth. BLE's range is considerably more than Classic Bluetooth. BLE did not become part of the Bluetooth Standard until version 4.0.
Bluetooth Classic has two data rates:
Sometimes Bluetooth Classic is referred to as Bluetooth BR/EDR and sometimes the forward slash is suppressed (bredr).
A Bluetooth keyboard is quite different than a Bluetooth speaker. One has a low data rate and is an input and other has a high data rate and is an output.
To connect to a Bluetooth device, its profile has to be defined. There are standard profiles for keyboards, speakers, headsets, etc., but sometimes, the user has to enter some data.
The official Bluetooth stack for Linux is BlueZ. With BlueZ's bluethoothctl command, you can pair, trust, and connect to Bluetooth devices. However, the later (connect) requires that the device's profile be defined.
Bluez expects the device profile to be defined by other packages such as PipeWire, PulseAudio or ALSA. PipeWire and PluseAudio are large packages that expect a GUI Desktop. They can usually generate a device profile without user interaction. ALSA is more suited for small and embedded systems. ALSA requires the user to supply some basic information for the device profile.
It is simple to create an ALSA device profile, but for some reason, this is not well known. I have seen numerous blogs that want to connect a Bluetooth speaker with the simpler (and less dependences) ALSA framework, but they do everything they can not to create an ALSA profile. This includes installing PipeWire and PuleAudio and sometimes deleting PulseAudio afterwards.
BlueZ is the official Linux Bluetooth stack. It provides the necessary infrastructure to manage Bluetooth devices, implement Bluetooth profiles, and establish connections.
To install BlueZ and its utilities:
sudo apt install bluez bluez-utils
Unblock Bluetooth with the command:
rfkill unblock bluetooth
The tool for scanning, pairing, trusting and connecting to devices is bluetoothctl.
To scan for nearby devices:
power on
bluetooth scan on
It displays a list of discovered devices with there names and MAC address.
Once you have found the device you want to pair, note it MAC address, then execute the following commands:
bluetoothctl pair xx:xx:xx:xx:xx:xx
bluetoothctl trust xx:xx:xx:xx:xx:xx
You only pair and trust a device once. You connect and reconnect to a device with the connect command, but first you have to set up a profile for the device with BluezALSA. See the next section.
You can view which Bluetooth devices have been previously pair with the command:
bluetoothctl devices
At one time bluealsa (without a z) was a separate package. Now, it is part of the bluezalsa (with a z) package. To install it:
sudo apt install bluez-alsa-utils
To define a profile for your Bluetooth device, you have to create a configuration file - ether /etc/asound.config for global or .asoundrc for local. I do not know why they did not just use bluealsa.config or .bluealsarc. Nor do I know why you have to create the files. Most Linux packages have default configuration files with most or all the parameters in the file are comment out.
Reference 4 has a link to a simple configuration file. The code is below:
defaults.bluealsa {
interface "hci0"
}
pcm.btheadset {
type plug
slave.pcm {
type bluealsa
device "11:22:33:44:55:66"
profile "a2dp"
}
hint {
show on
description "Headset"
}
}
pcm.btspeakers {
type plug
slave.pcm {
type bluealsa
device "AA:BB:CC:DD:EE:FF"
profile "a2dp"
}
hint {
show on
description "Speakers"
}
}
I had an application that used two Bluetooth Speaker. My ./etc/asound.conf file is:
defaults.bluealsa {
interface "hci0"
}
pcm.btspeakers {
type plug
slave.pcm {
type bluealsa
device "38:69:63:98:83:d6"
device "8C:CA:C6:1D:98:2F"
profile "a2dp"
}
hint {
show on
description "Speakers"
}
}
That's all there is to creating a device bluealsa profile.
You should now be able to connect to your devices:
bluetoothctl connect 38:69:63:98:83:d6 bluetoothctl connect 8C:CA:C6:1D:98:2F
In the following section, we will use the aplay command to send sound to the connected Bluetooth speaker. Aplay is part of the blusez-alsa-utils package.
Alsa is a low-level software framework for audio.
Higher level sound servers such as PipeWire, PluseAudio, Jack and sndio run on top of alsa. Because sound devices can come and go (plugged in and unplugged) sound servers assign a device number or node number to them. The device/node number is dynamic and can change for a device. For this reason, they are not ideal for embedded system.
With alasa you can reference a device via its MAC address
On most Linux distributions, alsa is installed by default, but not its utilities. To install alsa-utils:
sudo apt install alsa-utils
Alsa's playback command is aplay. Aplay can playback wav files but not mp3. Its syntax is:
aplay -d no_of_second_to_play -D device_name wave_file.wav
For a Bluetooth speaker, the device name is bluealsa:xx:xx:xx:xx:xx:xx
Alsamixer is a sound mixture with a graphical (non-desktop) interface. Using the F6 key, you can select sound cards by a drop-down box or by name. It will accept the name, bluealsa:xx:xx:xx:xx:xx:xx.
Amixer is a command line only sound mixture. Thus far, I have not been able to change the sound level of a Bluetooth device with it.
Alsactl provides a means of storing and retrieving sound mixture setting. The following command does this:
alsactl store
Using amixer to set the sound and aplay for playback:
amixer -D bluealsa:38:69:63:98:83:d6 set A2DP 75% # a2dp will not work - must be caps aplay -d 39 -D bluealsa:38:69:63:98:83:D6 HotelCalifornia.wav
An optional parameter that you might see when first pairing, trusting and connecting to a Bluetooth device is its Bluetooth volume level. It is a number between 0 and 127 (max).
One of my favorite Bluetooth speakers is the EWA A106 Pro (Amazon $20). Originally this speaker did not have this parameter. Later, it was added, and its level set to 96 by EWA. When I used the exact same amixer and aplay commands with a newer speaker, the maximum volume level was only half of the original speaker. However, my iPhone and PipeWire did not have this problem so there had to be way around this.
The solution is the Bluetooth (Media) Transport Commands [1]. To list the current media transports:
bluetoothctl transport.list
bluetoothctl transport.list | awk '{print $2}'
Note after the MAC address is the transport file descriptor number (fd#).
If a Bluetooth device disconnects and reconnects, it will get a new fd#.
You can view the volume level with the command:
bluetootctl transport.volume <transport>
To change the volume level specify a value (0-128)
bluetootctl transport.volume <transport> value
According to AI, after you list the transports, you
should acquire then, and then execute the
other commands:
bluetoothctl trasnsport.acquire <transport> [transport_2, transport_3, ...]
References
To install pipewire on a Raspberry Pi Zero 2W:
sudo apt install pipewire
sudo apt install pipewire-audio
To set the default output (sink), you have to get the sink number of the device. Get the sink number with wire plumber:
wpctl status
To set the default device, use the number obtained above in the following command.
wpctl set-default <Device-No.>
To set the volume of the default device:
wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.8
To playback a wave (*.wav) file:
pw-cat --playback <sound-file.wav>
or
pw-cat --playback --volume <No.> <sound-file.wav>
PipeWire's preferred session manager WirePlumber [1].
On Raspberry Pi OS Lite (no desktop), PipeWire is not installed. Audio goes through ALSA directly. Use alsamixer for volume and aplay for playback. Install alsa-utils if missing: sudo apt install alsa-utils [2].
To set the default output (sink), you have to get the node number of the sink. Unfortunately, the node number of a sink is not a constant. It can change as sound devices (e.g. speaker & headsets) are connected and unconnected. That is, if you disconnect a Bluetooth speaker and reconnect the same Bluetooth speaker, its node number will change.
However, the sink names do not change, and we can use the sink name to get its present node number. The procedure is:
Putting this on one line:
speaker_1_node_no=$(pw-cli info <sink_name> | head -n 1 | awk '{print $2}')