Bluetooth Audio Streaming on Yocto-Based Systems
Installation Steps and Implementation Guidelines
Linux provides a solution for audio streaming using Bluetooth technology. This solution is based on BlueZ and PulseAudio, which are both open source software. BlueZ is the Linux implementation of the Bluetooth stack and PulseAudio is a sound server, which among other things, streams audio data to remote Bluetooth devices.
Since the Yocto project allows the creation of a custom Linux-based embedded system, this article provides installation steps and implementation guidelines on adding Bluetooth audio streaming to your customized embedded system.
Before diving into the content, it’s good to understand the high-level design of the Linux Sound Architecture as it relates to Bluetooth audio streaming.
The Sound Card is the actual hardware device attached to the system that enables it to process and deliver sound. It sends sound to any Audio Device connected to the lineout jack (ex. Headphones) attached to the system. As with all hardware, a device driver or API is needed for the operating system to interact with it. ALSA represents the API interface for the Linux OS. It allows the system to use all the features of the sound card with little complexity.
In scenarios where the system needs to do more complex audio processing beyond what ALSA can do, Linux provides sound servers. PulseAudio is one of the sound server applications that sits above ALSA. It can do more complex processing such as sound mixing, routing audio to multiple audio devices concurrently, send sound to Bluetooth headsets, etc. BlueZ is the Bluetooth protocol stack provided in Linux. It uses the A2DP (Advanced Audio Distribution Profile) profile for wireless transmission of audio. PulseAudio supports this profile, and allows it to send data to Bluetooth headsets using BlueZ.
BlueZ software tar file should be downloaded and installed in the root of your file system. The latest tar file can be downloaded from http://www.bluez.org/download/.
Extract the tar file into a directory and cd into this directory. In this example, the subdirectory name is bluez-build:
$ cd /home/root/bluez-build
$ ./configure –-prefix=/usr \
//test the result
$ make check
$ make install &&
ln –svf ../libexec/bluetooth/bluetoothd /usr/sbin
//install main configuration file as the root user
$ install –v –dm755 /etc/bluetooth &&
install –v –m644 src/main.conf /etc/bluetooth/main.conf
After installation, insert a Bluetooth adapter into the USB port. Reboot your system.
Upon reboot, you’re ready to have Bluetooth connectivity:
//list the Bluetooth adapters
$ rfkill list
//unblock the index of the Bluetooth adapter.
//In my case, its 2. Use the correct one on your system
$ rfkill unblock 2
//activate Bluetooth adapter
$ hciconfig hci0 up
Activate the Bluetooth daemon which manages all Bluetooth devices.
$ bluetoothd –d -n
Open a new terminal to pair a device. The bluetoothctl utility will enable pairing and connection to a remote bluetooth device. The exact steps vary depending on the device and its input functionality. However, a general summary of steps is provided below.
// start bluetoothctl
//list the available Bluetooth adapters
//set default adapter
//power on the adapter
[Bluetooth]# power on
//enable the agent and set as default
[bluetooth]# agent on
//set the adapter as discoverable (temporarily for 3 // mins) and pairable
[bluetooth]# discoverable on
[bluetooth]# pairable on
//scan for devices
[bluetooth]# scan on
//put the Bluetooth device in pairing mode.
//discover the device MAC address
//pair with the headset
//trust the device
//connect the device
Once Bluetooth headset connectivity is able to be established on your system, the next step is to stream audio to it using PulseAudio.
The source and documentation for Pulseaudio is https://www.freedesktop.org/wiki/Software/PulseAudio/
For your custom Yocto build, you will need add PulseAudio to your bitbake, as well as the following plugins to have full Bluetooth streaming functionality:
Once PulseAudio is on your system, it usually runs automatically once the system is up. To test this, run the following from the command line.
$ ps agx | grep pulse
The command response should look like the following if it’s already running,
2700 ? S<l 0:00 /usr/bin/pulseaudio --start --log-target=syslog
If it’s not running, enter the following command.
$ pulseaudio --start
*Once it starts, PulseAudio is difficult to kill because it respawns itself when killed. To deactivate this autospawn feature, edit
/etc/pulse/client.conf, and change
autospawn = yes to
autospawn = no.
pacmd and pactl are tools that allow users to query and send commands to the PulseAudio server from the command line. For example, to see the list of output sources configured, enter:
$ pacmd list-sinks | grep –e ‘name:’ –e ‘index:’
* index: 0
Streaming audio from PulseAudio to Bluetooth
While PulseAudio is running, pair a Bluetooth headset to your system with bluetoothctl using previous instructions. Once the headsets are connected to the system, PulseAudio will register the headset’s MAC address as a new audio output. Entering the previous pacmd command will give an output similar to the one below.
* index: 0
The index with the asterisk(*) is the current default output
Once you’ve confirmed that the headset is now a valid PulseAudio output, there are two methods that can be used to move the audio stream output to the Bluetooth headset. First option is to explicitly move the input stream.
$ pacmd list-sink-inputs //tells the index of the input stream
>>> 1 sink input(s) available
$ pacmd move-sink-input 3 1 //moves input stream 3 to sink 1
*A drawback of this method is that the input stream index changes every time a new connection is made to the PulseAudio server. So we always have to find out the current stream index before we can switch using this commmand.
The second option is to change streaming to Bluetooth is to change the default sink to the index of headset. However, the change is not immediate and will only take effect after disconnecting and reconnecting to the PulseAudio server.
$ pacmd set-default-sink 1 //set headset index as default output
In addition to these commandline tools, PulseAudio provides an extensive API that allows querying and sending of commands to the PulseAudio server. It includes a set of functions that allows your application to detect when a Bluetooth headset connects to the system, direct output to Bluetooth headset, control the volume and even combine multiple audio sinks so that sound is sent to them simultaneously to name a few.
A handful of those methods will be discussed in the article, however you can reference the API documentation of an extensive description of all available functions.
Pulseaudio has a simple API and an asynchronous one. The simple API is usually enough for most basic streaming tasks while the asynchronous API is useful for more complex tasks.
To stream audio using the simple API, a playback stream connection needs to be made to the PulseAudio server using pa_simple_new(). Once a connection is successful, audio data is written to the stream with pa_simple_write(). Sound will be streamed to the default audio sink specified in pulseaudio configuration file default.pa. The pacmd commands specified previously can be used to change the default audio sink.
The PulseAudio server is able to detect when a new Bluetooth device that can be streamed to has connected to the system. An asynchronous API method is utilized to capture the detection. As the name suggests, most of the methods in this API are invoked asynchronously. A function call is made to the server, and when the ready response is received, PulseAudio invokes the callback that was passed into the function call.
The asynchronous API works as follows :
- Create a mainloop.
- Create a context object that allows communication with the server within the loop.
- Connect to the pulseaudio server within the context.
- Register a callback to be triggered within the context whenever pulseaudio state changes.
- Start the mainloop
- Within the context callback, implement the behavior for any states you’re interested in. For example, when the context is READY, get the list of sinks that audio data can be streamed to.
PulseAudio allows multiple simultaneous connections to the server, in which case, a threaded mainloop is used. For example, there can be a mainloop context for playback and simultaneously have another context that tracks sinks that are connected/disconnected to the system.
Within the context of Bluetooth audio streaming, the function pa_context_get_sink_info_list will set up a callback function to list sink devices : ex. pa_context_get_sink_info_list(c, sink_cb,userdata) where c is the context, sink_cb is the callback function to be triggered when the server is ready to provide the sink info, and userdata is data passed to the callback. The function pa_context_set_subscribe_callback will set up a callback function that is triggered when a new event is triggered on the server – for example if a new Bluetooth headset sink connects to the server.
In conclusion, with Bluez and PulseAudio installed and properly configured, a wide range of applications with sound playback capabilities can be developed. This article is simply scratching the surface of the many possibilities. Also worth noting is that, in addition to streaming to Bluetooth devices, PulseAudio also allows streaming to lineout jack, USB headsets and hdmi outputs, provided that the soundcards for these devices are installed and properly configured.