How I Configure Bluetooth Headphones on FreeBSD 13.1

Post at — Aug 31, 2022

I develop software around other humans so headphones are important

Introduction

Bluetooth on FreeBSD is a topic that’s fraught with some difficulty to say the least. Many modern system bluetooth chipsets are not supported ‘out of the box’ by FreeBSD drivers and the pairing / connectivity software leaves a bit to be desired.

With all that said, it is possible to get Bluetooth working with an audio headset. I have not tried to connect any other devices since my primary use case for bluetooth on FreeBSD is listening to background music while I work at my laptop.

For awhile, I used my smartphone as a primary music source and connected headphones via bluetooth to it. Eventually, I found the phone way too distracting. I like working in a focused development environment where I’m able to put my head down and work on a one thing at a time without looking up too often. Jumping from vim to musikcube and back via tmux is a tiny brain interrupt compared to looking at my phone, unlocking it, attempting to navigate the (terrible) music app and then going back to the laptop.

First off, I found this forum post immensely helpful to me when connecting my headphones: Bluetooth audio and how to connect and use bluetooth headphones on freebsd

My Lenovo T480 laptop has an Intel bluetooth chipset that’s supported by FreeBSD. However, if you do not have a supported chipset, I have personally tested an inexpensive bluetooth dongle from Amazon (Broadcom BCM20702A0) that works very well.

Installation

If you’re using the bluetooth dongle such as the one I linked to above, you can skip the firmware and devd bits and move on to install Virtual OSS.

Because I have a supported Intel bluetooth chipset, I have to install the intel firmware binary blob, iwmbt-firmware:

1
pkg install iwmbt-firmware

After installing the firmware, restart devd to enable it:

1
service devd restart

Now install Virtual OSS which is required for bluetooth audio to work on FreeBSD:

1
pkg install virtual_oss

Virtual OSS requires the user space character device driver (CUSE) to be loaded. Load it manually and enable loading for every boot:

1
2
kldload cuse
sysrc kld_list+="cuse"

Start Bluetooth Stack

The next step is to start the bluetooth stack your system.

This is an odd service start because usually I have to start the service two to three times before it will actually start.

Start the hcsecd before bluetooth (I’ll explain this service a little later):

1
service hcsecd onestart

Now start the bluetooth ubt0 device with the bluetooth service. This will likely need to be run several times.

1
service bluetooth start ubt0
1
/etc/rc.d/bluetooth: ERROR: Unable to setup Bluetooth stack for device ubt0

grumble… grumble… didn’t work…

Second time works, however. Like I said, it rarely works on the first try. I am unsure why.

1
service bluetooth start ubt0

Pair Headphones

If all has gone well, you should have a functional bluetooth stack. A little more work is involved in getting an audio device connected.

First, put your headphones in pairing mode. Then run an inquiry on the bluetooth stack

1
hccontrol -n ubt0hci inquiry
1
2
3
4
5
6
7
8
9
Inquiry result, num_responses=1
Inquiry result #0
        BD_ADDR: f8:9e:94:ee:99:c3
        Page Scan Rep. Mode: 0x1
        Page Scan Period Mode: 00
        Page Scan Mode: 00
        Class: 2a:01:0c
        Clock offset: 0x333b
Inquiry complete. Status: No error [00]

If your headphones are the only bluetooth enabled devices close to your system and they’re in pairing mode, there is a good chance the BD_ADDR from the inquiry are your headphones.

You can ask the BD_ADDR what it’s name is:

1
hccontrol -n ubt0hci remote_name_request f8:9e:94:ee:99:c3
1
2
BD_ADDR: f8:9e:94:ee:99:c3
Name: DESKTOP-VHBH89K

Oops, that’s a computer, not headphones! You can request remote name down the list of devices returned by the inquiry to find your headphones.

Once you’ve found your headphones, you’ll want to edit a couple of files to make your life a bit easier. Remember the hcsecd service? It runs a daemon that listens for key requests from bluetooth devices. My headphones don’t require a key or pin so I add nokey and 0000 as the pin. Like this: /etc/bluetooth/hcsecd.conf

1
2
3
4
5
6
device {
    bdaddr  2c:41:a1:07:c9:c9;
    name    "John's Headphones";
    key     nokey;
    pin     "0000";
}

Like the /etc/hosts file, /etc/bluetooth/hosts allows you to alias BD_ADDR addresses to names. MUCH easier to remember.

/etc/bluetooth/hosts

1
2c:41:a1:07:c9:c9 headphones

Now restart hcsecd. (I’m not sure if this is required)

1
service hcsecd restart

Because we have an alias in /etc/bluetooth/hosts, we can substitute headphones for the BD_ADDR in hccontrol commands. Now it’s time to connect your headphones to the computer via bluetooth! Make sure the headphones are in pairing mode and execute:

1
hccontrol -n ubt0hci create_connection headphones
1
2
3
BD_ADDR: headphones
Connection handle: 256
Encryption mode: Disabled [0]

For my headphones to work, I have to enable write_authentication on the HCI node. I’m not sure why this is required but my headphones do not work without it.

1
hccontrol -n ubt0hci write_authentication_enable 1

Now the part where the headphones actually pair. Running the following virtual_oss command creates the audio channel to my headphones and allows audio packets to flow from my laptop, into the headphone speakers and finally into my head. :)

1
virtual_oss -T /dev/sndstat -S -a o,-4 -C 2 -c 2 -r 44100 -b 16 -s 1024 -R /dev/dsp0 -P /dev/bluetooth/headphones -d dsp -t vdsp.ctl

That’s it! You should now have another DSP device listed in /dev/sndstat

1
cat /dev/sndstat
1
2
3
4
5
6
Installed devices:
pcm0: <Realtek ALC257 (Analog 2.0+HP/2.0)> (play/rec) default
pcm1: <Realtek ALC257 (Right Analog Mic)> (rec)
pcm2: <Intel Kaby Lake (HDMI/DP 8ch)> (play)
Installed devices from userspace:
dsp: <Virtual OSS> (play/rec)

Firefox

If you have pulseaudio installed and running, firefox will use it by default. Your bluetooth headphones are using OSS, not pulseaudio. There are a couple of ways to fix that.

First, configure firefox to use OSS instead of pulseaudio (FreeBSD devs compile in OSS support with the default package.) Open up firefox and put about:config in the URL line. Then add the following entry:

1
media.cubeb.backend:  oss

Second, if you MUST have pulseaudio output, you can follow redirect all OSS output into pulseaudio. It adds latency to the audio but works. I’m sure there’s a better way to do this!

1
pacat --record -d oss_output.dsp0.monitor

Embarrassing Script

I setup a script in my $HOME/bin directory called do_bluetooth.sh and I often have to run it several times to get it to work. I know this is a hacky as heck but I don’t care. It’s easy to run and debug.

1
2
3
4
5
6
7
8
9
#!/bin/sh

set -x
set -e

doas service bluetooth start ubt0
doas hccontrol -n ubt0hci create_connection headphones
doas hccontrol -n ubt0hci write_authentication_enable 1
doas virtual_oss -T /dev/sndstat -S -a o,-4 -C 2 -c 2 -r 44100 -b 16 -s 1024 -R /dev/dsp0 -P /dev/bluetooth/headphones -d dsp -t vdsp.ctl &

Blued

FreeBSD has a new bluetooth daemon called blued that’s supposed to make much of this easier (maybe). I’ll report on it once I get a chance to play with it.

UPDATE 9-6-22 I documented my blued install here

Resources