Bluetooth audio with ev3dev

Just a quick review on how to get Bluetooth Audio working with ev3dev.

This is based on the method explained here. It was tested with a snapshot image (“snapshot-ev3dev-stretch-ev3-generic-2017-06-27.img”) but it should work with the latest stable image from the ev3dev downloads page.

Even with a snapshot image, that already includes many updates released after the stable version, it’s a good practice to update everything before start:

sudo apt update
sudo apt upgrade
sudo apt dist-upgrade

On my case one of the updates available is for the “libpulse0” package, used by pulseaudio (and Bluetooth Audio uses pulseaudio).

As of today I ended up with a 4.9.34 kernel:

robot@ev3dev:~$ uname -a
Linux ev3dev 4.9.34-ev3dev-1.2.0-ev3 #1 PREEMPT Mon Jun 26 20:45:12 CDT 2017 armv5tejl GNU/Linux

Now we install some packages needed:

sudo apt-get install --no-install-recommends pulseaudio pulseaudio-module-bluetooth

This will in fact install much more than just those 2 packages:

...
0 upgraded, 33 newly installed, 0 to remove and 0 not upgraded.
Need to get 9587 kB of archives.

Now we should enable Bluetooth. The easy way is by using ‘brickman’ – the text based User Interface that runs on ev3dev after boot: on the ‘Wireless’ menu, choose ‘Bluetooth’ then ‘Powered’ and ‘Visible’. After EV3 finds our BT audio device (a speaker or an headset) we can pair with it.

In my case I’m using a BT speaker named “BS-400” and EV3 shows something like this:

      BS-400
C7:B5:42:B4:72:EC
connect    remove

After connecting (sometimes I need to try it a second time) we need to go to the command line:

pactl list sinks

This will show two audio devices – the EV3 speaker and my BT speaker:

Sink #0
 State: SUSPENDED
 Name: alsa_output.platform-sound.analog-mono
 Description: LEGO MINDSTORMS EV3 Speaker Analog Mono
...

Sink #1
 State: SUSPENDED
 Name: bluez_sink.C7_B5_42_B4_72_EC.a2dp_sink
 Description: BS-400
...

As far as I know the name of the second device always includes the BT address of our device, it can be useful if we have several devices of the same type.

Now we can test it using one of the audio samples available at ‘/usr/share/sounds/alsa/’:

paplay -d bluez_sink.C7_B5_42_B4_72_EC.a2dp_sink /usr/share/sounds/alsa/Front_Center.wav

We can control the volume with ‘–volume=x’ where x is an integer up to 65536.

Instead of using a wav file we can also redirect the output of ‘espeak’ to convert text to speech:

espeak "Hello" --stdout | paplay -d bluez_sink.C7_B5_42_B4_72_EC.a2dp_sink

(Note: this is a one-line command)

This is great for shell scripts but for python it poses a problem – how to access PulseAudio?

Will post about that later on but for now I show a simple way to use applications that expect ALSA to seamless work with our BT device by activating the PulseAudio plugin for alsalibs:

sudo nano /etc/asound.conf

The asound.conf file should contain just this 6 lines:

pcm.pulse {
 type pulse
}

ctl.pulse {
 type pulse
}

This redirects ALSA to the default PulseAudio device. So we can now use ‘aplay’ instead of ‘paplay’:

aplay -Dpulse /usr/share/sounds/alsa/Front_Center.wav

and we can control the volume with ‘alsamixer’. But better yet, we can use python with the ev3.Sound methods like play or speak:


#!/usr/bin/env python3
from time import sleep
import ev3dev.ev3 as ev3

ev3.Sound.speak('Hello').wait()
sleep(1)
ev3.Sound.play('/usr/share/sounds/alsa/Front_Center.wav')

There are however two methods that will not work with BT: tone and beep. That’s because instead of using ALSA they are hardwired to the onboard EV3 speaker.

And finally we can also play MIDI files locally on the EV3 through BT:

sudo apt install timidity

Timidity++ is a soft synth that allows us to play MIDI without a MIDI card:

timidity brahms_waltz.mid -Os

It works through BT but takes about 30 seconds to start playing and the sound is very poor, mostly glitches:

Requested buffer size 32768, fragment size 8192
ALSA pcm 'default' set buffer size 32768, period size 8192 bytes
Playing brahms_waltz.mid
MIDI file: brahms_waltz.mid
Format: 1 Tracks: 2 Divisions: 256
Sequence: Waltz
Text: Brahms
Track name: Harp

Playing time: ~57 seconds
Notes cut: 86
Notes lost totally: 141

We can tune timidity to use less CPU resources by adding arguments (see the output of ‘timidity –help’) or by editing the configuration file:

sudo nano /etc/timidity/timidity.cfg

We uncomment all options recommended for a slow CPU except the default sample frequency:

...
## If you have a slow CPU, uncomment these:
opt EFresamp=d #disable resampling
opt EFvlpf=d #disable VLPF
opt EFreverb=d #disable reverb
opt EFchorus=d #disable chorus
opt EFdelay=d #disable delay
opt anti-alias=d #disable sample anti-aliasing
opt EWPVSETOZ #disable all Midi Controls
opt p32a #default to 32 voices with auto reduction
#opt s32kHz #default sample frequency to 32kHz
opt fast-decay #fast decay notes
...

Now the same command takes about 13 seconds to start playing and the music is played correctly (although with some white noise background).

We can reduce start time a bit more by using Timidity in server mode – it takes a few seconds to start completely:

robot@ev3dev:~$ timidity -iA -Os &
[1] 8527
robot@ev3dev:~$ Requested buffer size 32768, fragment size 8192
ALSA pcm 'default' set buffer size 32768, period size 8192 bytes
TiMidity starting in ALSA server mode
Opening sequencer port: 128:0 128:1 128:2 128:3

if we now press ENTER we get back to the shell and Timidity keeps running:

robot@ev3dev:~$ pgrep timidity
8527

so now we can use our own MIDI programs to play MIDI through one of the 4 MIDI ports that Timidity created:

aplaymidi -p 128:0 brahms_waltz.mid

It starts playing after 6 seconds.

Not great but at least we can now use one of the several python libraries that can play MIDI music – perhaps after loading the library in memory this initial delay doesn’t happen when playing individal notes instead of a full music.