ENOSUCHBLOG

Programming, philosophy, pedaling.


A few small PulseAudio configuration tricks

Jan 9, 2023     Tags: howto, music    

This post is at least a year old.

This is just a brief followup to my November post, in which I used a Raspberry Pi and HiFiBerryOS to rejuvenate an old sound system I inherited.

While trying to get AirPlay working on Linux (more on that in a moment) I realized that I had accumulated a (small) variety of PulseAudio tweaks over the years, and figured I’d bundle them all together in a post to share. Hopefully they save you (dear reader) some time1 and stress.

Everything below goes in your default PulseAudio configuration file which, for me, was at /etc/pulse/default.pa. They should all take effect with systemctl --user restart pulseaudio.service (or your equivalent service management tool).


AirPlay on Linux, with autodiscovery

This didn’t ultimately work for me (see directly below), but it’ll probably work for 95% of people:

1
load-module module-raop-discover

(you’ll need this module installed, which for me was present in the pulseaudio-module-raop package in Ubuntu’s index).

With PulseAudio restarted, each of your AirPlay devices should be automatically discovered (including, if I understand correctly, in real time as they join your network) and available as audio sinks.

AirPlay on Linux, without autodiscovery

The above worked too well for me: not only did it discover my HiFiBerry, but it discovered it twice: once over IPv4, and again over IPv6. This is a known issue.

On top of that, it discovered every other AirPlay-aware device on my network, including my laptop, TV, &c. Each of these also received two unique sinks, which would then be ordered inconsistently in the applet I use (pasystray) to manage my inputs and outputs.

I found this very annoying, especially since I couldn’t figure out how to change each sink’s assigned name: I had to visually scan a list of IP-address-containing names to find the correct sink each time, and would regularly click the wrong one.

As it turns out, like the other *-discover plugins in Pulse, module-raop-discover is only responsible for the discovery logic, and delegates all actual AirPlay sink management to a separate module, one that gets loaded once per discovered sink.

That module is module-raop-sink, and can be configured entirely separately (with module-raop-discover removed completely, to prevent the sink spam):

1
load-module module-raop-sink server=ADDRESS:PORT protocol=PROTO codec=CODEC sink_name=NAME

…where ADDRESS:PORT is the IP address and port for your AirPlay device, PROTO is either TCP or UDP depending on what your device speaks, CODEC is PCM or ALAC depending on what your device supports, and sink_name is any name you choose to give your device2.

Thus, for my particular HiFiBerry, I needed the following (with some IP octets censored):

1
load-module module-raop-sink server=192.168.XX.XX:7000 protocol=UDP codec=ALAC sink_name=hifi

(If you’re using a HiFiBerry, I do not recommend trying codec=PCM — the HiFiBerry advertises support for it over mDNS, but actually trying to stream with it just produced terrible noise for me.)

PulseAudio’s documentation for this is really terrible: following it only produced obscure errors on the command line and marginally more helpful syslog messages, so I ended up having to read through raop-sink.c’s actual source to figure out which parameters the module was actually expecting.

Here are some other parameters that, while not necessary for me, may be helpful to you:

Default sources and sinks

Out of the box, PulseAudio (on Ubuntu) tries to be “clever” about your default source and sink. For me, that cleverness would almost never be correct: I use a USB DAC for my headphones, and PulseAudio would regularly default to one of my monitors’ audio interfaces (which aren’t actually connected), presumably based on them appearing to udev or something similar first.

Similarly, I have both a USB microphone and a microphone built into my webcam, and I almost never want the latter. Despite that, PulseAudio regularly defaults to it.

Fixing these required just two additional directives:

1
2
set-default-sink alsa_output.LONG_SINK_NAME_HERE
set-default-source alsa_input.LONG_SOURCE_NAME_HERE

…where the source and sink names come from pactl list sources and pactl list sinks, respectively. These appear to be very stable for the same hardware, so I’ve never needed to update them.

Disabling switch-on-connect

Related to the default sources-and-sinks problem: on Ubuntu (and maybe Debian), PulseAudio has been “helpfully” configured with module-switch-on-connect:

1
2
3
4
### Use hot-plugged devices like Bluetooth or USB automatically (LP: #1702794)
.ifexists module-switch-on-connect.so
load-module module-switch-on-connect
.endif

What module-switch-on-connect does is simple and, in my experience, almost always incorrect: it immediately switches the source and/or sink to the latest hotplugged device. In practice, this means suddenly losing your sink if your hotplugged microphone happens to have a headphone port on it (like mine does), or having your audio go to a monitor’s unconnected sink because of faulty wakeup handling.

To “fix” it, I just disabled it outright:

1
2
3
4
### Use hot-plugged devices like Bluetooth or USB automatically (LP: #1702794)
# .ifexists module-switch-on-connect.so
# load-module module-switch-on-connect
# .endif

Of note: module-switch-on-connect is completely different from module-switch-on-port-available, which is enabled by default without Ubuntu/Debian’s patches. module-switch-on-port-available has much more reasonable behavior (it only switches the active source/sink on plug/unplug changes from things like 3.5mm jacks). I haven’t had a reason to disable that.


  1. “Linux is only free if your time is worthless,” as the saying goes. 

  2. As far as I can tell, this name is still mostly useless — it doesn’t affect the display name used in pavucontrol or pasystray, for example. 


Discussions: Reddit Mastodon