[Bluetooth-audio] How to connect and use Bluetooth headphones on FreeBSD

Important notes:
1) Tested and confirmed to work on FreeBSD 13.0-RELEASE.
2) Obviously you need to first confirm whether FreeBSD supports the built-in Bluetooth of your laptop/computer.
3) For this guide, we will focus on Intel Bluetooth as that's my case in particular. Hence the use of comms/iwmbt-firmware.
4) Both "hcsecd" and "sdpd" Bluetooth-related services are intentionally not enabled on /etc/rc.conf because they're not really needed in my setup. Nonetheless, adding them shouldn't do any harm.
5) Remember to replace "BD_ADDR" by the real unique address of your Bluetooth device. How to get this address is explained later on.


# First, become superuser and install the programs we need:
su
pkg install iwmbt-firmware virtual_oss # If yours isn't Intel, only install audio/virtual_oss.

# Nowadays, drivers are auto-loaded at system start-up, but installing comms/iwmbt-firmware was still needed, as well as issuing the following command afterwards:
service devd restart # This way /etc/devd/iwmbtfw.conf is executed and the required firmware files are downloaded.

# Next load cuse and make it permanent, for it's needed by virtual_oss:
kldload cuse
sysrc kld_list+="cuse"

# Start the Bluetooth stack:
service hcsecd onestart
service bluetooth start ubt0 # If this fails, do it again.

# Now find your Bluetooth headphones (make sure they're on):
hccontrol -n ubt0hci inquiry # Take note of the unique address (BD_ADDR) of your Bluetooth device. Example: "98:52:3d:48:3a:51".

# If unsure, find out the human readable name assigned to your remote device:
hccontrol -n ubt0hci remote_name_request BD_ADDR # Replace BD_ADDR by the real unique address of your Bluetooth device.

# Create a connection to your remote device:
hccontrol -n ubt0hci create_connection BD_ADDR # Replace BD_ADDR by the real unique address of your Bluetooth device.

# Edit /etc/bluetooth/hosts to assign an alias to your device (replace "98:52:3d:48:3a:51" with your actual address). Example:
Code:
# $Id: hosts,v 1.1 2003/05/21 17:48:40 max Exp $
# $FreeBSD$
#
# Bluetooth Host Database
#
# This file should contain the Bluetooth addresses and aliases for hosts.
#
# BD_ADDR               Name [ alias0 alias1 ... ]

# 00:11:22:33:44:55    phone
98:52:3d:48:3a:51 headphones

# Now create a virtual sound device so you can use your Bluetooth headphones as a sound sink:
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

*Let's explain a little some of the meaning of the above command:
- I wanted this virtual device to be exposed to other apps, so install entry in /dev/sndstat. ( -T /dev/sndstat)
- Enabled automatic resampling to fix bad sound quality when playing audio with a different sample-rate than default. ( -S)
- The default volume was too high so I set a negative output amplification. ( -a o,-4)
- I chose a sample rate of 44100 Hz but you can use 48000 Hz instead if that's better for your hardware. ( -r 44100)
- I wanted to be able to use my laptop's internal mic simultaneously while using my headphones. ( -R /dev/dsp0)
- You can disable the recording device by setting "-R /dev/null" instead.


All this works great for programs that use OSS as sound backend (the vast majority in FreeBSD). However, other backends need certain workarounds or extra steps:

[sndio]
# To fix applications that use sndio as backend (e.g. Chromium), you need to enable the sndiod service:
sysrc sndiod_enable=YES
service sndiod start

[Pulseaudio]
# To fix the ones that use a pulseaudio backend (e.g. Firefox and linux Brave), recording the sound output and immediately redirecting it to /dev/dsp acts as a workaround:
pacat --record -d oss_output.dsp0.monitor > /dev/dsp & # It's the only solution I could figure out and it's not perfect, as a very tiny delay can be noticed.
# Also you'd want to mute your computer speakers to avoid hearing double:
mixer pcm 0 # Remember to set it back to 100 after disconnecting your headphones, or you won't hear anything.
# Depending on your particular case, you may need to replace "oss_output.dsp0.monitor" by a different source-output. To list the ones available use:
pacmd list-sources | grep monitor

[OpenAL]
# Sadly, I haven't found yet a workaround to hear sound from Wine games and programs.


# Finally, I created a little script to automate the audio output device switching between Bluetooth headphones and the built-in speakers:
Code:
#!/bin/sh

if [ "$(cat /dev/sndstat | grep 'Virtual OSS')" = "dsp: <Virtual OSS> (play/rec)" ]; then
 killall pacat ; mixer pcm 100 ; sudo /usr/bin/killall virtual_oss ; sudo /sbin/sysctl hw.snd.basename_clone=1 ; notify-send --app-name=Bluetooth --icon=bluetooth -t 5000 'Bluetooth sound device disconnected'
else
 notify-send --app-name=Bluetooth --icon=bluetooth -t 5000 'Connecting Bluetooth sound device...'
 sudo 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 & sleep 5 && mixer pcm 0 ; pacat --record -d oss_output.dsp0.monitor > /dev/dsp &
fi

*Note: This script is meant to be run as normal user. Remember to make it executable (chmod +x). Also I suggest to add the commands that need "sudo" to /usr/local/etc/sudoers to allow them running without the need of entering your password. At least I did that.
You can then create a keyboard shortcut to run it easily.
You may also want to increase the sleep time to 15 seconds or more if your device takes too long to connect. I say this because it's important to run the pacat command AFTER the device is correctly paired, otherwise the sound will be out of sync. In that case a simple killall pacat ; pacat --record -d oss_output.dsp0.monitor > /dev/dsp & should suffice to set it right anyway.
DISCLAIMER: I'm not a coder, so I assume the script above could be greatly improved. All I can say is it works for me :) Any suggestions and corrections are highly welcome.


Sources:
https://docs.freebsd.org/en/books/handbook/advanced-networking/#network-bluetooth
https://docs.freebsd.org/en/books/handbook/multimedia/#sound-setup
https://wiki.freebsd.org/Sound
https://www.freebsd.org/cgi/man.cgi?query=virtual_oss&sektion=8&format=html
https://forums.ghostbsd.org/viewtopic.php?t=1676
https://gist.github.com/maxrp/fe97f9c84c512536b7876042a14626ba
https://meka.rs/blog/2021/10/12/freebsd-audio/


UPDATE 1 (2021-11-01) :
- Added the -S option to the virtual_oss command to enable automatic resampling using Project Z. This fixes bad sound quality when playing audio files with a sample rate different than default.
- Mentioned some additional tips regarding the script.
 
This is awesome thank you. I used a similar virtual-oss line but didn't know about the -S so I must try that. BTW I have had issues when using bluetooth and wifi on my HP Zbook G3. They seem to interfere with one another and either the wifi or the bluetooth will drop from time to time. The laptop has an Intel(R) Dual Band Wireless AC 8260 in it. Anyway what I wanted to add is that for those that use applications that use Alsa you can add your bluetooth audio to the ~/.asoundrc after installing the correct alsa backends for OSS. I think I installed all packages for ALSA in an attempt to get it working. Below is my .asoundrc the mu6 is my bluetooth headset, the headphones device is the headphone jack on the side of the laptop. YMMV.

Code:
pcm.hdmi0 {
    type oss
    device /dev/dsp0
}
pcm.hdmi1 {
    type oss
    device /dev/dsp1
}
pcm.hdmi2 {
    type oss
    device /dev/dsp2
}
pcm.hdmi3 {
    type oss
    device /dev/dsp3
}
pcm.Internal {
    type oss
    device /dev/dsp4
}
pcm.Headphones {
    type oss
    device /dev/dsp5
}
pcm.Mu6 {
    type oss
    device /dev/dsp6
}
ctl.hdmi0 {
    type oss
    device /dev/mixer0
}
ctl.hdmi1 {
    type oss
    device /dev/mixer1
}
ctl.hdmi2 {
    type oss
    device /dev/mixer2
}
ctl.hdmi3 {
    type oss
    device /dev/mixer3
}
ctl.Internal {
    type oss
    device /dev/mixer4
}
ctl.Headphones {
    type oss
    device /dev/mixer5
}
ctl.Mu6 {
    type oss
    device /dev/mixer6
}
pcm.!default pcm.Headphones
ctl.!default ctl.Headphones

In your virtual_oss command Instead of -d dsp try using -d dsp6 or -d dsp[some number not inuse] then you can tell firefox or Linux Brave to use that dsp device in pulseaudio since it will be listed as a different device in /dev/sndstat so you can make it the default device in pulse if needed. Also you can do a -B in the virtual_oss command and run it in the background. Below is the output after running the virtual_oss with -d dsp6 option notice how it set the virtual_oss pcm/dsp devices as userspace. I couldn't figure out how to force it to be seen as a kernel device it's probably done this way on purpose but I'm not able to do sysctl hw.snd.default_unit=6 to make the userspace device the system default device.

Code:
cat /dev/sndstat
Installed devices:
pcm0:  (play)
pcm1:  (play)
pcm2:  (play)
pcm3:  (play)
pcm4:  (play/rec)
pcm5:  (play)
 default Installed devices from userspace:
pcm6:  (play/rec)
dsp6:  (play/rec)

Side Note: Also HDMI audio didn't work until I installed the Nvidia drivers then they all worked.
 
Thank you very much for the guide. I want to add one thing: OpenAL (Wine programs and games) works with Virtual OSS too, with version at least 1.2.16. Previous versions probably not work. It also requires some changes to the command for starting Virtual OSS. My command looks that:

Bash:
sudo virtual_oss -T /dev/sndstat -S -C 2 -c 2 -r 44100 -b 16 -s 1024 -P /dev/bluetooth/headphones -R /dev/null -w vdsp.ctl -d dsp -l mixer &

The only problem is, after the change, the line in the script in the guide:

Bash:
if [ "$(cat /dev/sndstat | grep 'Virtual OSS')" = "dsp: <Virtual OSS> (play/rec)" ]; then

Not detect properly the connected bluetooth headphones. I need to find the solution for the problem. :)
 
-w vdsp.ctl should probably be -t vdsp.ctl -T /dev/sndstat
Thank you for the suggestion. I just tried it. For me, it works in the same way as -w vdsp.ctl. Both commands properly create a device.

And fix for the shell. I noticed that grep returns two lines instead of one, that is the reason it was not working. The working version:

Bash:
if [ "$(cat /dev/sndstat | grep 'dsp: <Virtual OSS')" = "dsp: <Virtual OSS> (play/rec)" ]; then
 
For me -S produces a degradation is quality. So I undo the changes in my /etc/rc.conf to this:

Code:
virtual_oss_dsp="-C 2 -c 2 -r 44100 -b 16 -s 1024 -R /dev/null -P /dev/bluetooth/04:fe:a1:4d:fa:7c -T /dev/sndstat -d dsp"

whit that line I am perfectly happy.
 
In my setup, sometimes in the middle of songs there are some small hiccups. Is it manifesting to you, too?
 
In my setup, sometimes in the middle of songs there are some small hiccups. Is it manifesting to you, too?
I changed a bit my setup some time ago, now it looks that:

Bash:
sudo virtual_oss -T /dev/sndstat -C 2 -c 2 -r 48000 -b 16 -s 20ms -P /dev/bluetooth/headphones -R /dev/null -w vdsp.ctl -d dsp -l mixer &

Same as you, removed -S but also raised sample-rate (-r) and changed buffer size (-s).
 
First of all, thanks to the detailed info! Thanks to this little howto I actually now got bluetooth audio working.

That being said, regarding the comment by zsolt:

In my setup, sometimes in the middle of songs there are some small hiccups. Is it manifesting to you, too?

I am experiencing this too, listenting to music in chromium right now. Did you find the reason why this might be happening?
 
I know this is old post but sending to monitor does not work on 13.1 or 13.2 on mine
bluetooth to earbuds do though.
just youtube videos still go through speakers when I open youtube in firefox and play them

Code:
mixer pcm 0 
Setting the mixer pcm from 100:100 to 0:0.
userx@FreeBeSD:~$  pacat --record -d oss_output.dsp0.monitor > /dev/dsp & 
[1] 8289
userx@FreeBeSD:~$ mixer
Mixer vol      is currently set to 100:100
Mixer pcm      is currently set to   0:0
Mixer speaker  is currently set to 100:100
Mixer rec      is currently set to  37:37
Mixer ogain    is currently set to 100:100
Mixer monitor  is currently set to  67:67
Recording source: monitor
userx@FreeBeSD:~$ mixer monitor 100
Setting the mixer monitor from 67:67 to 100:100.
userx@FreeBeSD:~$ mixer
Mixer vol      is currently set to 100:100
Mixer pcm      is currently set to   0:0
Mixer speaker  is currently set to 100:100
Mixer rec      is currently set to  37:37
Mixer ogain    is currently set to 100:100
Mixer monitor  is currently set to 100:100
Recording source: monitor
userx@FreeBeSD:~$ mixer pcm 100
Setting the mixer pcm from 0:0 to 100:100.
userx@FreeBeSD:~$ cat  /dev/sndstat
Installed devices:
pcm0: <ATI R6xx (HDMI)> (play)
pcm1: <Realtek ALC236 (Analog 2.0+HP/2.0)> (play/rec) default
pcm2: <Realtek ALC236 (Right Analog Mic)> (rec)
Installed devices from userspace:
dsp: <Virtual OSS> (play/rec)
 
I know this is old post but sending to monitor does not work on 13.1 or 13.2 on mine
bluetooth to earbuds do though.
just youtube videos still go through speakers when I open youtube in firefox and play them

Code:
mixer pcm 0
Setting the mixer pcm from 100:100 to 0:0.
userx@FreeBeSD:~$  pacat --record -d oss_output.dsp0.monitor > /dev/dsp &
[1] 8289
userx@FreeBeSD:~$ mixer
Mixer vol      is currently set to 100:100
Mixer pcm      is currently set to   0:0
Mixer speaker  is currently set to 100:100
Mixer rec      is currently set to  37:37
Mixer ogain    is currently set to 100:100
Mixer monitor  is currently set to  67:67
Recording source: monitor
userx@FreeBeSD:~$ mixer monitor 100
Setting the mixer monitor from 67:67 to 100:100.
userx@FreeBeSD:~$ mixer
Mixer vol      is currently set to 100:100
Mixer pcm      is currently set to   0:0
Mixer speaker  is currently set to 100:100
Mixer rec      is currently set to  37:37
Mixer ogain    is currently set to 100:100
Mixer monitor  is currently set to 100:100
Recording source: monitor
userx@FreeBeSD:~$ mixer pcm 100
Setting the mixer pcm from 0:0 to 100:100.
userx@FreeBeSD:~$ cat  /dev/sndstat
Installed devices:
pcm0: <ATI R6xx (HDMI)> (play)
pcm1: <Realtek ALC236 (Analog 2.0+HP/2.0)> (play/rec) default
pcm2: <Realtek ALC236 (Right Analog Mic)> (rec)
Installed devices from userspace:
dsp: <Virtual OSS> (play/rec)
It is a bit unclear to me what you want to do. Did you check /dev/mixer0 through /dev/mixer2 ? There is one mixer device for each pcmX device!
It might be that HDMI devices have some kind of DRM protection, so that you cannot loopback the played back audio contents. Then you need to use virtual_oss. It has some new options allowing loopback of played back audio to same device. Typically this is used with open microphone setups, to get be able to hear yourself a bit while speaking when using sound tight headphones.
 
It is a bit unclear to me what you want to do. Did you check /dev/mixer0 through /dev/mixer2 ? There is one mixer device for each pcmX device!
It might be that HDMI devices have some kind of DRM protection, so that you cannot loopback the played back audio contents. Then you need to use virtual_oss. It has some new options allowing loopback of played back audio to same device. Typically this is used with open microphone setups, to get be able to hear yourself a bit while speaking when using sound tight headphones.
now that this is "opened" again, I have it to where I have BT working for earbuds they eventually connect after x amount of tries.

it is the web browsers that are now giving me issues. youtube - Firefox that 'media.cubeb.backend = oss' does not work at all. So I figured I'd just install a different web browser 'dooble' , it worked for YT, netsurf it would not even load YT completely, chromium worked for YT, then I decided to watch a video on Tubi those ones showed the main page to select a video then just gave me a black screen when a movie was selected, bust.

dooble the way it seems to be set up was I have to add every single web sight I want it to load to the allowed list otherwise it does not load the page which is completely asinine.

nevertheless no one web browser works for YT and tubi and I suppose any other web sight that has videos. So where I am right now is wanting to shift everything over to pulseaudio and have yet to find anything to tell me how. Yet my memory tells me when I had fbsd v12.x installed I had it using pulseaudio just not BT. now with 13.x its BT and no piulseaudio.

Pulseaudio is in the system, I have my user added to the group, now I just need to figure out how to complete the setup.

( it is strage that the people who so this system add pulseaudo, have firefox compiled to use pulaseaudio for its default sound and it still does not work, and they have oss for the defualt sound on the system.

like they are still trying to mix water and oil together and expecting it easily to work.

so I'll probally be looking at some archlinux wiki to see if that can help me
 
now that this is "opened" again, I have it to where I have BT working for earbuds they eventually connect after x amount of tries.

it is the web browsers that are now giving me issues. youtube - Firefox that 'media.cubeb.backend = oss' does not work at all. So I figured I'd just install a different web browser 'dooble' , it worked for YT, netsurf it would not even load YT completely, chromium worked for YT, then I decided to watch a video on Tubi those ones showed the main page to select a video then just gave me a black screen when a movie was selected, bust.

dooble the way it seems to be set up was I have to add every single web sight I want it to load to the allowed list otherwise it does not load the page which is completely asinine.

nevertheless no one web browser works for YT and tubi and I suppose any other web sight that has videos. So where I am right now is wanting to shift everything over to pulseaudio and have yet to find anything to tell me how. Yet my memory tells me when I had fbsd v12.x installed I had it using pulseaudio just not BT. now with 13.x its BT and no piulseaudio.

Pulseaudio is in the system, I have my user added to the group, now I just need to figure out how to complete the setup.

( it is strage that the people who so this system add pulseaudo, have firefox compiled to use pulaseaudio for its default sound and it still does not work, and they have oss for the defualt sound on the system.

like they are still trying to mix water and oil together and expecting it easily to work.

so I'll probally be looking at some archlinux wiki to see if that can help me
Hi,

The "media.cubeb.backend = oss" was broken recently, but I'm not sure if it was fixed again.

For chrome, I recommend to rebuild it to use ALSA. Then install the ALSA OSS plugin.

--HPS
 
Hi,

The "media.cubeb.backend = oss" was broken recently, but I'm not sure if it was fixed again.

For chrome, I recommend to rebuild it to use ALSA. Then install the ALSA OSS plugin.

--HPS
well firefox is not fixed and use alsa that does not have the capibiles of pulseaudo and use that instead ? how does that allow pushing the volume about this 100% threshold when there 100% threshold is not enough? (from what I remember alsa really is as bad a oss is in that arena. as well as that don't mix ports with pkg thing)
 
Hi,

The clue is to separate audio transport from features controlling the audio itself, like volume. In my setups I have my virtual_oss in the bottom, and use the QT based virtual_oss_ctl, if I need to change volume or anything. You can also do volume above 100% and add a compressor and such. ALSA and OSS pass through audio AS-IS without any modifications. This is important to know, because many audio devices are 24-bit nowadays, but 16-bit audio is quite common for playback. Converting 16-bit audio into 24-bit audio by bit-shifting 8 times is not the best way to do that, and leads to noticable quantization problems. virtual_oss knows all about this. I did not check pulseaudio in details yet. Some audio DAWs like JACK use floating point all the way, but it doesn't really help when applications do silly things. It would be better to pass the actual bit-depth right through, than to lie about things. I believe still many people convert from 16-bit samples to float, by dividing by 32768.0f, and there are some *issues* there if you read through the internet, about precision loss, that when you convert back to 16-bit the samples are rounded up/down. pulseaudio is the default for some desktop environments, but yeah, doesn't support bluetooth like virtual_oss does.

Also virtual_oss can combine two different playback/recording devices into a single /dev/dsp device. When everything is built on top of /dev/dsp devices in FreeBSD, pulseaudio is out of the question. There are too many like these, unfortunately, that don't talk to eachother. Could have had just one super-audio library for FreeBSD . Maybe in the future.

Audio is not just audio :)

--HPS
 
pulseaudio is the default for some desktop environments, but yeah, doesn't support bluetooth like virtual_oss does. .... Could have had just one super-audio library for FreeBSD

.
and this is where it fails. I do believe it is still being treated as a it's only a server but sure you can have a desktop on it too but ... ideology, perhaps.

thanks for the heads up on the FBSD and pulseaudio virtual oss don't play together.
 
Hi,

...

Also virtual_oss can combine two different playback/recording devices into a single /dev/dsp device. When everything is built on top of

/dev/dsp devices in FreeBSD, pulseaudio is out of the question. There are too many like these, unfortunately, that don't talk to eachother. Could have had just one super-audio library for FreeBSD . Maybe in the future.

Audio is not just audio :)

--HPS
Hey, I am happy to report I did get pulseaudio bluetooth (earbuds) to work with firefox, (YT and tubie).
installed
sndio
pulseaudio-module-sndio

added the sink in the message to the pa file
added sndiod_enable="YES" to rc.conf
added user to pulse group
and I think that was it besides the BT setup, reboot and it works now through firefox.
 
hi, I am definitely missing something here: /dev/bluetooth/headphones -- it look like there is nothing at this path :(
I'm in the same situation, no /dev/bluetooth devices even with an active connection to headphones:
Code:
hccontrol -n ubt0hci read_connection_list
Remote BD_ADDR    Handle Type Mode Role Encrypt Pending Queue State
Q38                  256  ACL    0 MAST    NONE       0     0 OPEN
 
hi, I am definitely missing something here: /dev/bluetooth/headphones -- it look like there is nothing at this path :(
To have available that option, you have to set it in /etc/bluetooth/hosts first, as mentioned in the first post. ;) It isn't a necessary step, but can be handy.
 
Hello

I can change virtual_oss output to inexistent device, using virtual_oss_cmd, but no sound is played throught Bluetooth headphones.

My virtual_oss configuration:
grep virtual /etc/rc.conf
virtual_oss_enable="YES"
virtual_oss_dsp="-T /dev/sndstat -C 2 -c 2 -S -i 0 -r 48000 -b 24 -s 8.0ms -f /dev/dsp1 -d dsp -t dsp.ctl"

The command to change audio output to bluetooth headphones, none of them works:
virtual_oss_cmd /dev/dsp.ctl -f /dev/bluetooth/Q38
virtual_oss_cmd /dev/dsp.ctl -f /dev/bluetooth/37:a2:cf:87:75:74

And my /etc/bluetooth/hosts file as mentioned in the first post:
cat /etc/bluetooth/hosts
# $Id: hosts,v 1.1 2003/05/21 17:48:40 max Exp $
# $FreeBSD$
#
# Bluetooth Host Database
#
# This file should contain the Bluetooth addresses and aliases for hosts.
#
# BD_ADDR Name [ alias0 alias1 ... ]

# 00:11:22:33:44:55 phone
37:a2:cf:87:75:74 Q38

Best regards.
 
Last edited:
Ok nice, but would it also be possible to use the FreeBSD machine as "headphones" for the phone (and then probably send the phone's audio to a sndio server?)
 
Back
Top