Flawless Bluetooth Headset (MDR-100ABN) on Linux

Diego Fernández Giraldo
3 min readNov 11, 2018

When I first got my headset, I kept having problems where A2DP wouldn’t work right upon connecting to bluetooth. I could get it to work by reconnecting occasionally, but nothing worked consistently. I googled around and found https://askubuntu.com/questions/765233/pulseaudio-fails-to-set-card-profile-to-a2dp-sink-how-can-i-see-the-logs-and, from this thread I found a few different solutions, some which worked. However, I wasn’t happy with manually running some sort of a2dp-fix script each time I plugged my headset in, so I set out to automate it. The main issue I had was that bluetoothctl wouldn’t work when running from a udev rule. After looking at some examples and digging into the source code, I figured out how to use dbus to accomplish the same. I now finally have it working using the following scripts:

/usr/local/bin/a2dp-fix

#!/bin/bash

bt_device_addr=$(pacmd list-cards | grep -i 'name:.*bluez_card' | sed -E 's/.*<?bluez_card\.([A-Z0-9_]+)>?/\1/')
device_mac=$(echo $bt_device_addr | sed 's/_/:/g')

a2dp_available=$(pacmd list-cards | grep -A30 bluez | grep "A2DP Sink" | sed -E 's/.* available: ([a-z]+)\)/\1/g')

if [[ "$a2dp_available" == "no" ]] || [[ "$a2dp_available" == "unknown" ]]
then
dbus-send --system --dest=org.bluez --print-reply /org/bluez/hci0/dev_$bt_device_addr org.bluez.Device1.Connect

pacmd set-card-profile bluez_card.$bt_device_addr off
pacmd set-card-profile bluez_card.$bt_device_addr a2dp_sink
pacmd set-default-sink bluez_sink.$bt_device_addr.a2dp_sink
fi

This is the main script, which does the heavy lifting. Before trying to automate this, see if running this script fixes A2DP for you. If it does, then create the next couple of scripts to automate it.

/etc/udev/rules.d/80-bt-headset.rules

ACTION=="add", SUBSYSTEM=="bluetooth", RUN+="/usr/local/bin/a2dp-fix-wrapper"

That is the rule that triggers the fix upon connecting the headset. This could probably be improved to be a bit more strict, but I don’t see the harm on running every time some bluetooth device is connected.

/usr/local/bin/a2dp-fix-wrapper

#!/bin/bashfor PID in $(pgrep pulseaudio); do
USER=$(grep -z USER= /proc/$PID/environ | sed 's/.*=//' | tr -d '\0')
USER_ID=$(id -u $USER)
HOME=$(echo $(getent passwd $USER )| cut -d : -f 6)
export XDG_RUNTIME_DIR=/run/user/$USER_ID
export XAUTHORITY=$HOME/.Xauthority
export DISPLAY=:0
sleep 5
if [[ ! -z $USER ]]; then
sudo -u $USER -E /usr/local/bin/a2dp-fix
fi
done

This script sets up all the necessary variables for a2dp-fix to work correctly when launched by udev.

With these 3 files, you should have A2DP available every time you connect your headset. However, I also find myself wanting to switch to headset mode for meetings and such. In order to make that easier, I wrote the following script and bound it to a key-shortcut

~/.local/bin/switch_headset

#!/bin/shset_a2dp() {
pacmd set-card-profile bluez_card.$BT_DEVICE_ADDR a2dp_sink
pacmd set-default-sink bluez_sink.$BT_DEVICE_ADDR.a2dp_sink
}
set_headset() {
pacmd set-card-profile bluez_card.$BT_DEVICE_ADDR headset_head_unit
pacmd set-default-sink bluez_sink.$BT_DEVICE_ADDR.headset_head_unit
pacmd set-default-source bluez_source.$BT_DEVICE_ADDR.headset_head_unit
}
BT_SINK=$(pacmd list-sinks | grep 'name:.*bluez_sink' | sed -E 's/.*<(.*)>/\1/')
regex='bluez_sink\.([A-Z0-9_]+)\.([a-z0-9_-]+)'
if [[ $BT_SINK =~ $regex ]]
then
BT_DEVICE_ADDR="${BASH_REMATCH[1]}"
BT_PROFILE="${BASH_REMATCH[2]}"
if [[ $BT_PROFILE == "headset_head_unit" ]]
then set_a2dp
else set_headset
fi
fi

Now my headset works correctly whenever I connect it, and I can easily switch between A2DP and headset modes with a single keybinding :)

Edit 01/27/19: Small fix in `/usr/local/bin/a2dp-fix` thanks to https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/525#note_108750

--

--

Diego Fernández Giraldo

I’m Diego Fernández Giraldo, a Freelance Data Science Engineer looking to help your business succeed by ensuring you are leveraging data to its full potential.