README.md: maintenance guide describing how the adac PipeWire audio hub is wired together — the four audio paths, the five config drop-ins, services, firmware, and common maintenance tasks. Written as a mental model for upkeep rather than install/troubleshooting commands. root-README.md: short redirect telling anyone on the root account to log in as the `user` account (PipeWire is a per-user service), pointing to the maintenance guide. These mirror the files deployed at /home/user/README.md and /root/README.md on the Pi. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| README.md | ||
| root-README.md | ||
| RUNBOOK.md | ||
adac — PipeWire Audio Hub: how it's set up
This is the maintenance guide: it describes how this machine is currently
wired together so you can reason about it, not a list of install commands. If you
ever need to rebuild from a blank SD card, that's a separate install runbook
(RUNBOOK.md, kept on the workstation). For day-to-day "is it working / something
changed / how do I add a phone", this file is the map.
0. The one rule: you are user, not root
The whole audio stack runs as your account, user (uid 1000) — PipeWire is a
per-user service. Always do audio work logged in as user:
ssh user@adac.local
Logged in as user, the normal tools just work: pactl, wpctl,
bluetoothctl, systemctl --user. As root they connect to the wrong (empty)
session and everything looks dead even when it's healthy — that's the single most
common false alarm. Root is only used for system-level pieces (§6).
1. What this box does (signal flow)
It's a small mixer hub. Several sources reach a physical Universal Audio Volt 4 USB interface, which is patched into an analog mixer; the mixer's main output comes back into the Volt and is re-published on the network for the OBS machines.
Phone (Bluetooth) ───────────────▶ Volt OUT 1/2 ─┐
Desktop "animal" (default sink) ──▶ Volt OUT 1/2 ─┤
├─▶ MIXER ─▶ main out
Desktop "animal" ("Desktop 3/4") ─▶ Volt OUT 3/4 ─┘ │
│
OBS machines ◀── network ◀── Volt IN 3/4 ◀─────────────┘
- Wireless: only the phone (Bluetooth A2DP). Everything else is wired Ethernet — Wi-Fi is disabled in firmware (§6).
- The mixer is the heart. The Pi never mixes in software; it just feeds the Volt's outputs and captures the Volt's inputs. Levels/EQ/mute live on the physical mixer.
2. The hardware
- Raspberry Pi 4, hostname
adac, wired IP 10.12.10.110 (eth0). - Universal Audio Volt 4 over USB, in Pro Audio profile so all 4 outputs
and 4 inputs appear as plain channels (no surround remap). Its PipeWire card is
alsa_card.usb-Universal_Audio_Volt_476P_22432056005060-00. - The Pi's HDMI and onboard 3.5 mm audio are disabled (firmware, §6) so the Volt is the only sound card. That's why the Volt is always the default.
Physical patching you must keep consistent (the software assumes it):
| Volt jack | Carries | Patched to |
|---|---|---|
| Outputs 1/2 | Phone + desktop default audio | Mixer channel A |
| Outputs 3/4 | Desktop "Desktop (Volt 3/4)" feed | Mixer channel B |
| Inputs 3/4 | Mixer main output (the return) | Mixer main out |
Inputs/outputs 3/4 are used (not 1/2) for the mixer return and the second desktop feed on purpose: the Volt's inputs 1/2 are the front mic preamps and are often monitored straight to outputs 1/2, which would create a feedback loop.
3. The four audio paths (what plays where)
Everything the Pi exposes is one of these. Knowing which path you're debugging is half the battle.
A. Phone → Volt outputs 1/2 (Bluetooth)
Phone connects over Bluetooth A2DP. WirePlumber routes the phone's audio to the
default sink, which is the Volt → outputs 1/2 → mixer channel A. No manual
routing; the key setting is in 50-bluez.conf (§4).
B. Desktop → Volt outputs 1/2 (network, default)
animal selects the published sink "Volt 476P Pro". Lands on the same outputs
1/2 as the phone (shared mixer channel A).
C. Desktop → Volt outputs 3/4 (network, separate channel)
animal selects the published sink "Desktop (Volt 3/4)" instead. This is a
virtual sink (desktop_34) that forwards into Volt outputs 3/4 → mixer
channel B, so the desktop gets its own fader independent of the phone. This is the
path the desktop normally uses.
D. Mixer main out → OBS machines (network, the return)
The mixer's main output is patched into Volt inputs 3/4. A virtual source
mixer_return ("Mixer Return (Volt in 3/4)") picks up just those two channels and
is published on the network; OBS machines capture it as a PulseAudio Capture
device.
"Connected but silent" (the June 2026 fault): if the desktop is connected to a sink but you hear nothing, the routing is usually fine — check whether the incoming stream is muted. See §7, task "Desktop is connected but silent".
4. The audio config — what each file does
All audio behaviour beyond defaults comes from five small drop-in files under
~/.config. These are the configuration; there are no scripts. To change
behaviour you edit one of these and restart the relevant service (§5).
~/.config/pipewire/pipewire.conf.d/
-
10-clock-rate.conf— locks the whole stack to 48000 Hz (fixed rate end to end avoids resampling crackle over Bluetooth and the network tunnels). -
30-mixer-return.conf— builds path D. A loopback that captures Volt input channels 3/4 (AUX2/AUX3) and presents them as a clean stereo source namedmixer_return. The Volt's full device name is hardcoded here because it contains the unit's serial number. -
40-desktop-34.conf— builds path C. A loopback exposing a virtual sinkdesktop_34("Desktop (Volt 3/4)") that forwards stereo into Volt outputs 3/4 (AUX2/AUX3).
Both loopbacks use
stream.dont-remix = trueso channels 3/4 are hit literally instead of being down-mixed. In30-mixer-return.confthe published side must bemedia.class = Audio/Source(a source the OBS boxes read); in40-desktop-34.confthe side the desktop plays into ismedia.class = Audio/Sink.
~/.config/pipewire/pipewire-pulse.conf.d/
20-network.conf— turns on network audio. Loadsmodule-native-protocol-tcp(lets LAN machines connect, restricted byauth-ip-aclto localhost +10.12.10.0/24) andmodule-zeroconf-publish(advertises the sinks/sources over mDNS so they auto-appear on other machines).
~/.config/wireplumber/wireplumber.conf.d/
50-bluez.conf— Bluetooth behaviour for path A. Enables SBC-XQ and hardware volume, and the critical linebluez5.media-source-role = playback, which makes a connected phone feed the default sink (the Volt) instead of showing up as a recording source. Without this, the phone connects but is silent.
A
pipewire -c filter-chain.confprocess also runs (filter-chain.service). It uses the stock config in/usr/share/pipewire/and does no custom processing — it's a default systemd user unit. Ignore it; there's no EQ/filter set up here.
5. Services that keep it running
Your (user) services — the audio stack, started at boot via linger (so they
come up with nobody logged in):
systemctl --user status pipewire pipewire-pulse wireplumber
After editing a config file, restart the matching service:
| You edited | Restart |
|---|---|
10-clock-rate.conf, 30-mixer-return.conf, 40-desktop-34.conf |
systemctl --user restart pipewire |
20-network.conf |
systemctl --user restart pipewire-pulse |
50-bluez.conf |
systemctl --user restart wireplumber |
When in doubt, restart all three: systemctl --user restart pipewire pipewire-pulse wireplumber.
System services (need sudo, managed from root or with sudo):
bluetooth— the BlueZ stack.bt-agent— auto-accepts pairing on this headless box (no screen to confirm a PIN).avahi-daemon— mDNS; this is what makesadac.localresolve and what carries the network-audio advertisements.
6. System-level setup (firmware & root pieces)
These rarely change but are part of "how it's set up":
/boot/firmware/config.txtholds three deliberate lines:dtparam=audio=offanddtoverlay=vc4-kms-v3d,noaudio— kill onboard + HDMI audio so the Volt is the only card.dtoverlay=disable-wifi— this box is wired-only. Do not adddisable-bt(that kills the phone link).
- Linger is enabled for
user(loginctl show-user user -p Linger→Linger=yes) — that's what starts the audio stack at boot without a login. - Bluetooth tuning lives in
/etc/bluetooth/main.conf(stays discoverable/pairable, auto-reconnect).
7. Common maintenance tasks
All run as user unless they say sudo.
Quick "is it healthy?" check
systemctl --user is-active pipewire pipewire-pulse wireplumber # all "active"
pactl get-default-sink # the Volt pro-output-0
pactl list sinks short # desktop_34 + the Volt
pactl list sources short | grep mixer_return # the OBS return exists
Confirm the network feeds are advertised (what OBS / the desktop discover)
avahi-browse -at | grep -iE 'Volt|Desktop|Mixer Return'
You should see "Volt 476P Pro" (sink), "Desktop (Volt 3/4)" (sink), and "Mixer Return (Volt in 3/4)" (source).
Desktop is connected but silent (the June 2026 fault — path B/C) The stream from the desktop can arrive muted even though routing is fine.
pactl list sink-inputs | grep -E 'Sink Input #|Mute|media.name'
Find the Tunnel for user@animal entry and, if Mute: yes, unmute it:
pactl set-sink-input-mute <id> 0
If it keeps coming back muted, the mute is being set on animal — unmute that
output device on the desktop itself.
Pair a new phone
bluetoothctl
power on
scan on # find the phone, note its MAC, then:
scan off
trust <MAC> # IMPORTANT: trust = it auto-reconnects forever
connect <MAC>
quit
Then play music on the phone — it should come out mixer channel A (Volt out 1/2).
See which phones are known / connected
bluetoothctl devices
bluetoothctl info <MAC> | grep -E 'Paired|Trusted|Connected'
8. Current state & known notes (June 2026)
- Paired phones:
Pixel 8 Pro(B8:DB:38:79:90:CC) andatomic(A4:42:3B:9A:5F:82). - Both phones are trusted (set 2026-06-15), so they auto-reconnect when they
return to range with no SSH needed. If you pair a new phone, remember to
bluetoothctl trust <MAC>it too — trust is what makes reconnect automatic. - Remote desktop:
animal— appears on this Pi asTunnel for user@animalstreams. - Wi-Fi is disabled; if the wired link ever dies the box is unreachable until the cable/switch is fixed.