Intro
This page details setting up Bluetooth device specific audio EQ settings in Linux.
It's split into two sections - application and udev. Application details the setup needed to switch between EQs automatically, and udev details how to listen for device connect/disconnect events, which will be used to invoke the application side of things. There are a few separate parts here but each is relatively simple and they all fit together cleanly.
Application
For this you will need:
- EasyEffects, to apply an EQ
- inotify-tools, to create a file watcher that listens for changes
Both should be in your distribution's repositories.
EasyEffects
EasyEffects is a very well featured program that provides, amongst many other things, an audio EQ.
EasyEffects needs to be set up for this to work, with the following configuration:
- Be running in service mode (i.e it is still active even when you close the GUI)
- Presets for your devices. I have 'BoseQC' and 'Blank' (the default)
Scripts
Later on we control EasyEffects from udev events. udev runs scripts as root, which is a problem because EasyEffects is running as our user. In theory we could use sudo -u username
to interact with EasyEffects, but I found EasyEffects threw an error when doing this and I was unable to resolve it. Instead I settled on a more convoluted structure, which is as follows:
- Use a udev invoked script to write the current audio device to a temporary file
- Run a script as my user which listens for changes to this file and invoke a preset from EasyEffects.
On some (most?) distributions /tmp is set to tmpfs with resides in memory so there's no real disadvantage to using a temporary file here.
This is the script:
# ~/bin/deviceeq.sh
# -----------------
#!/usr/bin/env bash
tmppath=/tmp/deviceeq
set_device() {
echo $1 > "$tmppath"
}
set_eq() {
local preset=`cat "$tmppath"`
echo "Loading preset $preset"
easyeffects -l "$preset"
local code=$?
if [[ $code -ne 0 ]]; then
echo "easyeffects -l $preset failed with status $code"
return 1
fi
}
listen() {
touch "$tmppath"
set_eq
while inotifywait -r "$tmppath" -e create,delete,modify; do {
set_eq
}; done
}
while [[ $# -gt 0 ]]; do
case $1 in
--device)
set_device "$2"
exit 0
;;
--load-preset)
set_eq
exit $?
;;
--listen)
listen
shift # past argument
;;
*)
echo "Unknown option $1"
exit 1
;;
*)
;;
esac
done
This script gets run in a few different ways
- It needs to be running from login, with
--listen
- It invokes itself with
--load-preset
- It is invoked by udev with
--device DEVICE_NAME
To facilitate 1, we also need to set the script to be run at login. There are many ways to do this, the easiest is probably to use your desktop environment's autostart:
# ~/.config/autostart/deviceeq.desktop
# ------------------------------------
#
[Desktop Entry]
Name=Device EQ
Comment=Device EQ service
Exec=/home/mark/bin/deviceeq.sh --listen
Icon=com.github.wwmm.easyeffects
StartupNotify=false
Terminal=false
Type=Application
udev
udev is a Linux subsystem for allowing us to hook into device events (e.g connecting and disconnecting a Bluetooth device).
Finding relevant udev properties
In order to target the device using udev, we need to find some properties that we can use to identify it. This is a bit of a pain, but the following commands will help.
You can monitor for udev events using # udevadm monitor --environment --udev
. With this running, connect your device and see what you get in the console. Here's an example:
# udevadm monitor --environment --udev
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
UDEV [104170.620864] add /devices/pci0000:00/0000:00:08.1/0000:06:00.3/usb1/1-4/1-4:1.0/bluetooth/hci0/hci0:51 (bluetooth)
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:08.1/0000:06:00.3/usb1/1-4/1-4:1.0/bluetooth/hci0/hci0:51
SUBSYSTEM=bluetooth
DEVTYPE=link
SEQNUM=40406
USEC_INITIALIZED=104170603611
.MM_USBIFNUM=00
SYSTEMD_ALIAS=/sys/subsystem/bluetooth/devices/hci0:51
SYSTEMD_WANTS=bluetooth.target
SYSTEMD_USER_WANTS=bluetooth.target
TAGS=:systemd:
CURRENT_TAGS=:systemd:
UDEV [104171.697630] add /devices/virtual/input/input129 (input)
ACTION=add
DEVPATH=/devices/virtual/input/input129
SUBSYSTEM=input
PRODUCT=5/9e/4075/106
NAME="Bose QC Headphones (AVRCP)"
PHYS="14:ac:60:33:1e:ba"
PROP=0
EV=100007
KEY=2fc800 145200000000 0 10300 49e800000c00 e16800000000f f810000010000ffc
REL=0
MODALIAS=input:b0005v009Ep4075e0106-e0,1,2,14,k71,72,73,8A,8B,A3,A5,A6,A7,A8,AB,AE,C8,C9,D0,161,164,166,16A,16C,18B,18E,18F,190,191,192,193,195,ramlsfw
SEQNUM=40407
USEC_INITIALIZED=104171685878
ID_INPUT=1
ID_INPUT_KEY=1
ID_BUS=bluetooth
TAGS=:seat:
CURRENT_TAGS=:seat:
UDEV [104171.744538] add /devices/virtual/input/input129/event16 (input)
ACTION=add
DEVPATH=/devices/virtual/input/input129/event16
SUBSYSTEM=input
DEVNAME=/dev/input/event16
SEQNUM=40408
USEC_INITIALIZED=104171686857
ID_INPUT=1
ID_INPUT_KEY=1
ID_BUS=bluetooth
LIBINPUT_DEVICE_GROUP=5/9e/4075:14:ac:60:33:1e:ba
MAJOR=13
MINOR=80
TAGS=:power-switch:
CURRENT_TAGS=:power-switch:
So, I got an event /devices/virtual/input/input129/event16
.
We can use # udevadm info -ap
on this event to find out more:
# udevadm info -ap /devices/virtual/input/input129/event16
Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.
looking at device '/devices/virtual/input/input129/event16':
KERNEL=="event16"
SUBSYSTEM=="input"
DRIVER==""
ATTR{power/control}=="auto"
ATTR{power/runtime_active_time}=="0"
ATTR{power/runtime_status}=="unsupported"
ATTR{power/runtime_suspended_time}=="0"
looking at parent device '/devices/virtual/input/input129':
KERNELS=="input129"
SUBSYSTEMS=="input"
DRIVERS==""
ATTRS{capabilities/abs}=="0"
ATTRS{capabilities/ev}=="100007"
ATTRS{capabilities/ff}=="0"
ATTRS{capabilities/key}=="2fc800 145200000000 0 10300 49e800000c00 e16800000000f f810000010000ffc"
ATTRS{capabilities/led}=="0"
ATTRS{capabilities/msc}=="0"
ATTRS{capabilities/rel}=="0"
ATTRS{capabilities/snd}=="0"
ATTRS{capabilities/sw}=="0"
ATTRS{id/bustype}=="0005"
ATTRS{id/product}=="4075"
ATTRS{id/vendor}=="009e"
ATTRS{id/version}=="0106"
ATTRS{inhibited}=="0"
ATTRS{name}=="Bose QC Headphones (AVRCP)"
ATTRS{phys}=="14:ac:60:33:1e:ba"
ATTRS{power/control}=="auto"
ATTRS{power/runtime_active_time}=="0"
ATTRS{power/runtime_status}=="unsupported"
ATTRS{power/runtime_suspended_time}=="0"
ATTRS{properties}=="0"
ATTRS{uniq}==""
This is useful. We've got a few properties we can use here:
ATTRS{id/product}=="4075"
ATTRS{id/vendor}=="009e"
ATTRS{name}=="Bose QC Headphones (AVRCP)"
ATTRS{phys}=="14:ac:60:33:1e:ba"
Putting them into a udev rule
A udev rule looks something like the following and can be placed in any file in ~/etc/udev/rules.d/ as long as it ends in '.rules'.
Here is mine:
# /etc/udev/rules.d/50-bluetooth.rules
# ------------------------------------
ACTION=="add" , SUBSYSTEM=="input", ATTR{id/vendor}=="009e", ATTR{id/product}=="4075", RUN+="/home/mark/bin/deviceeq.sh --device BoseQC"
ACTION=="remove" , SUBSYSTEM=="input", ATTRS{name}=="Bose QC Headphones (AVRCP)", RUN+="/home/mark/bin/deviceeq.sh --device Blank"
You will need to edit the attributes to match your device attributes as well as the path to the script.
For reasons I wasn't clear on, the remove action wouldn't fire with the id/vendor and id/product, so I ended up using the name attribute.
After editing udev rules, you can reload the system by running the following:
# udevadm control --reload-rules && udevadm trigger
Conclusion and troubleshooting
After following this, reloading your device rules and ensuring that the listener is running, you should find that connecting and disconnecting your bluetooth headphones will adjust the EQ. You can monitor this by having an EasyEffects window open as you connect and disconnect.
There is a little bit of error debugging built into the script. If you are having problems, disable it from auto-starting and instead try running "deviceeq.sh --listen" in a terminal and watchings its output as you connect and disconnect your headphones. You can also look in /tmp/deviceeq yourself, as it is just a plain text file, and see what preset udev has set.