About me: My name is Solène Rapenne, pronouns she/her. I like learning and sharing knowledge. Hobbies: '(BSD OpenBSD Qubes OS Lisp cmdline gaming security QubesOS internet-stuff). I love percent and lambda characters. OpenBSD developer solene@. No AI is involved in this blog.

Contact me: solene at dataswamp dot org or @solene@bsd.network (mastodon).

You can sponsor my work financially if you want to help me writing this blog and contributing to Free Software as my daily job.

Stream your OpenBSD desktop audio to other devices

Written by Solène, on 05 May 2023.
Tags: #openbsd #streaming #icecast #hacking

Comments on Fediverse/Mastodon

1. Introduction §

Hi, back on OpenBSD desktop, I miss being able to use my bluetooth headphones (especially the Shokz ones that allow me to listen to music without anything on my ears).

Unfortunately, OpenBSD doesn't have a bluetooth stack, but I have a smartphone (and a few other computers), so why not stream my desktop sound to another device with bluetooh? Let's see what we can do!

I'll often refer to the "monitor" input source, which is the name of an input that provides "what you hear" from your computer.

While it would be easy to just allow a remote device to play music files, I want to stream the computer's monitor input, so it could be litteraly anything, and not just music files.

This method can be used on any Linux distribution, and certainly on other BSDs, but I will only cover OpenBSD.

2. The different solutions §

2.1. Icecast §

One simple setup is to use icecast, the program used by most web radios, and ices, a companion program to icecast, in order to stream your monitor input to the network.

The pros:

  • it works with anything that can read OGG from the network (any serious audio client or web browser can do this)
  • it's easy to set up
  • you can have multiple clients at once
  • secure (icecast is in a chroot, and other components are sending data or playing music)

The cons:

  • there is a ~10s delay, which prevents you from watching a video on your computer and listening the audio from another device (you could still set 10s offset, but it's not constant)
  • reencoding happens, which can slightly reduce the sound quality (if you are able to tell the difference)

2.2. Sndiod §

The default sound server in OpenBSD, namely sndiod, supports network streaming!

Too bad, if you want to use Bluetooth as an output, you would have to run sndiod on Linux (which is perfectly fine), but you can't use Bluetooth with sndiod, even on Linux.

So, no sndiod. Between two OpenBSD, or OpenBSD and Linux, it works perfectly well without latency, and it's a super simple setup, but as Bluetooth can't be used, I won't cover this setup.

The pros:

  • easy to setup
  • works fine

The cons:

  • no android support

2.3. Pulseaudio §

This sound server is available as a port on OpenBSD, and has two streaming modes: native-protocol-tcp and RTP, the former is exchanging pulseaudio internal protocol from one server to another which isn't ideal and prone to problems over a bad network, the latter being more efficient and resilient.

However, the RTP sender doesn't work on OpenBSD, and I have no interest in finding out why (the bug doesn't seem to be straightforward), but the native protocol works just fine.

The pros:

  • almost no latency (may depend of the network and remote hardware)
  • easy to setup

2.4. Snapcast §

Snapcast is an amazing piece of software that you can use to broadcast your audio toward multiple other client (using snapcast or a web page) with the twist that the audio will be synchronized on each client, allowing a multi room setup at no cost.

Unfortunately, I've not been able to build it on OpenBSD :(

The pros:

  • multi room setup with synchronized clients
  • compatible with almost any client able to display an HTML5 page

The cons:

  • playback latency
  • not so easy to setup

3. Setup §

Here are the instructions to setup different solutions.

3.1. Pulseaudio §

3.1.1. Client setup (OpenBSD) §

On the local OpenBSD, you need to install pulseaudio and ffmpeg packages.

You also need to set sndiod flags, using rcctl set sndiod flags -s default -m play,mon -s mon, this will allow you to use the monitor input through the device snd/0.mon.

Now, when you want to stream your monitor to a remote pulseaudio, run this command in your terminal:

ffmpeg -f sndio -i snd/0.mon -ar 44100 -f s16le - | pacat -s 10.42.42.199 --raw --process-time-msec=30 --latency-msec=30

The command is composed of two parts:

  • ffmpeg reading the monitor input and sending it to the pipe
  • pacat (pulseaudio cat) relaying the pipe input to the pulseaudio server 10.42.42.199, with some tweaks to reduce the latency

3.2. Server setup (the device with bluetooth) §

The setup is easy, but note that this doesn't involve any authentication or encryption, so please use this on trusted network, or through a VPN.

On a system with pulseaudio, type:

pacmd load-module module-native-protocol-tcp auth-anonymous=1 auth-ip-acl=192.168.1.0/24

This will load the module accepting network connections, the auth-anonymous option is there to simplify connection to the server, otherwise you would have to share the pulseaudio cookie between computers, which I recommend doing but on a smartphone this can be really cumbersome to do, and out of scope here.

The other option is pretty obvious, just give a list of IPs you want to allow to connect to the server.

If you want the changes to be persistent, edit /etc/pulse/default.pa to add the line load-module module-native-protocol-tcp auth-anonymous=1 auth-ip-acl=192.168.1.0/24.

On Android, you can install pulseaudio using Termux (available on f-droid), using the commands:

pkg install pulseaudio
pulseaudio --start --exit-idle-time=3600
pacmd load-module module-native-protocol-tcp auth-anonymous=1 auth-ip-acl=192.168.1.0/24

There is a project named PulseDroid, the original project has been unmaintained for 13 years, but someone took it back quite recently, unfortunately no APK are provided, and I'm still trying to build it to try, it should provide an easier user experience to run pulseaudio on Android.

PulseDroid gitlab repository

3.3. Icecast §

Using icecast, you will have to setup an icecast server, and locally use ices2 client to broadcast your monitor input. Then, any client can play the stream URL.

Install the component using:

pkg_add icecast ices--%ices2

3.3.1. Server part §

As suggested by the file /usr/local/share/doc/pkg-readmes/icecast, run the following commands to populate icecast's chroot:

cp -p /etc/{hosts,localtime,resolv.conf} /var/icecast/etc
cp -p /usr/share/misc/mime.types /var/icecast/etc

Edit /var/icecast/icecast.xml:

  • in the <authentication> node, change all the passwords. The only one you will need is the source password used to send the audio to icecast, but set all other passwords to something random.
  • in the <hostname> node, set the IP or hostname of the computer with icecast.
  • add a <bind-address> node to <listen-socket> using the example for 127.0.0.1, but use the IP of the icecast server, this will allow other to connect.

Keep in mind this is the bare minimum for a working setup, if you want to open it to the wide Internet, I'd strongly recommend reading icecast documentation before. Using a VPN may be wiser if it's only for private use.

We can start icecast and set it to start at boot:

rcctl enable icecast
rcctl start icecast

3.3.2. Broadcast part §

Then, to configure ices2, copy the file /usr/local/share/examples/ices2/ices-sndio.xml somewhere you feel comfortable for storing user configuration files. The example file is an almost working template to send sndio sources to icecast.

Edit the file, under the <instance> node:

  • modify <hostname> with the hostname used in icecast.
  • modify <password> with the source password defined earlier.
  • modify <mount> to something ending in .ogg of your liking, this will be the filename in the URL (can be /stream.ogg if you are out of ideas).
  • set <yp> to 0, otherwise the stream will appear on the icecast status page (you may want to have it displayed though).

Now, search for <channels> and set it to 2 because we want to broadcast stereo sound, and set <downmix> to 0 because we don't need to merge both channels into a mono output. (If those values aren't in sync, you will have funny results =D)

When you want to broadcast, run the command:

env AUDIORECDEVICE=snd/0.mon ices2 ices-sndio.xml

With any device, open the url http://<hostname>:8000/file.ogg with file.ogg being what you've put in <mount> earlier. And voilà, you have a working local audio streaming!

4. Limitations §

Of course, the setup isn't ideal, you can't use your headset microphone or buttons (using MPRIS protocol).

5. Conclusion §

With these two setup, you have a choice for occasionnaly streaming your audio to another device, which may have bluetooth support or something making it interesting enough to go through the setup.

I'm personally happy to be able to use bluetooth headphones through my smartphone to listen to my OpenBSD desktop sound.

6. Going further §

If you want to directly attach bluetooth headphones to your OpenBSD, you can buy an USB dongle that will pair to the headphones and appear as a sound card to OpenBSD.

jcs@ article about Bluetooth audio on OpenBSD