I recently got active noise-cancelling headphones (Sony WH-1000MX4). They work just fine with my Android phone but on my Linux notebook I had a bit of a fight to get them working. I am running Arch Linux on most of my machines, so this post is written with Arch specific commands, but most of them (and the general idea) should also work on Ubuntu or Debian based distros. Keep in mind that older versions of PulseAudio and/or PipeWire may not have the features described here! So lets get started with the things I tried (if you are not interested in these just jump to PipeWire).

Failed experiments

At first we have to install the bluez and bluez-utils packages. bluez-utils will provide bluetoothctl to interact via CLI with bluez. As I want to avoid unnecessary sources of error I’m not going for a graphical helper for now.

All of this is (as always) documented in the arch wiki. The following is more or less exactly what can be found there.

  1. Start the Bluetooth service with systemctl start bluetooth.service
  2. Open bluetoothctl
  3. Power on the Bluetooth module with power on
  4. Scan for devices with scan on
  5. Wait for the headphones to show up. In my case they did not, so I got the Bluetooth MAC from my phone.
  6. Pair the headphones with pair <MAC>
  7. Connect the headphones with connect <MAC>

Until now everything was working as expected, but now the struggling started. As I played some music the sound was just bad. Therefore I checked the configuration in pavucontrol and saw that the select profile was HSP/HDF. This is also documented in the Arch wiki. After a bit of research I now know that I want to use the A2DP profile for crispy audio output, but the internal microphone will only work with HSP/HDF. As my notebook got a decent internal microphone and I also got an external one this is no problem for my use case. But keep this in mind if you need the internal mic of the Sony WH-1000MX4 or any other Bluetooth headphones.

I tried a few more things mentioned in the troubleshooting guide like:

# /etc/pulse/default.pa
# Let me use l2dp via bluetooth
load-module module-bluetooth-policy auto_switch=false
# /etc/bluetooth/main.conf
[General]
Disable=Headset
MultiProfile=multiple

But none of them worked and the sound was still just bad. So I did some research about how all of these Bluetooth stuff actually works and discovered that the Sony WH-1000MX4 are using the LDAC codec which is not supported by vanilla pulseaudio. An alternative is APTx which may be supported by most? other headphones, as LDAC is a proprietary Sony codec. Also you want to use a A2DP sink instead of HSP/HDF (mind the earlier described disadvantage of not working microphones!). Therefore it’s time to revisit the Arch wiki and search for this specific codec.

No A2DP sink

Migration to PipeWire

At this point I discovered a pulseaudio module that adds support for APTx and LDAC codecs. But it was deprecated in January 2021 with a recommendation to use vanilla PulseAudio or PipeWire. I already tried PulseAudio earlier (and was annoyed) so I decided to give PipeWire a chance. And PipeWire supports LDAC since release 0.3.19 🎉!

There are 3 packages needed to get started: libldac, pipewire and pipewire-pulse. Install them with pacman -S libldac pipewire pipewire-pulse.

After that reboot your machine and connect the headphones with bluetoothctl as described earlier. I had to remove the headphones with remove <MAC> and pair them again. Now there should be the A2DP option in pavucontrol and the LDAC codec.

LDAC is now supported!

If you want to learn more about PipeWire (and its history) there is a recently published article on LWN.net.

Getting the touch gestures to work

Thanks to Marcelo for pointing this out!

As always this was already documented in the arch wiki. Simply create a new systemd user unit:

~/.config/systemd/user/mpris-proxy.service

[Unit]
Description=Forward bluetooth media controls to MPRIS

[Service]
Type=simple
ExecStart=/usr/bin/mpris-proxy

[Install]
WantedBy=default.target

Reload all units with systemd --user daemon-reload and enable the unit with systemctl --user enable --now mpris-proxy. Now all touch gestures and the wearing detection should work!

Conclusion

As I thought about getting Bluetooth headphones I have not given a thought that Bluetooth (with modern codecs) could be problematic on Linux. Especially as Sony decided to use their own LDAC codec instead of something open (but I’m not even sure if there is an open Bluetooth codec).

So far I had no problems with PipeWire, but I am only running it for a few weeks now, so we will see how it holds up. Keep in mind that PipeWire is not stable yet! There might be updates that require configuration changes (like this one).