Audio mit Linux
Optional oder wirklich zu gebrauchen?
Szenario¶
Um mit Linux und OBS Studio auf dem lokalen Rechner vernünftiges Audio für Livestreams bereitzustellen, möchte ich mich virtuellen Audiogeräten - sogenannten Sinks - bedienen.
Hier werden Apps entsprechend ihres Zwecks auf virtuelle Audiogeräte verteilt. Soll heissen der Browser und alle Sounds daraus, YouTube Videos, Internetradio, etc. an ein Browser Audiogerät. Äquivalent dazu Spotify oder andere Musikstreams aus anderen Programmen an ein Spotify Audiogerät.
Das ist nützlich, um jeder Quelle in OBS Studio eigene Filter, Ducking und eben einen Lautstärkepegel zuweisen zu können.
Schematische Darstellung des Setups¶
flowchart LR;
A[YouTube] --> D[Browser Audiogerät];
B[Internetradio] --> D[Browser Audigerät];
C[Sonst. Audioquellen] --> D[Browser Audiogerät];
E[Spotify] --> S[Spotify Audiogerät];
F[Sonst. Audioapps] --> S[Spotify Audiogerät];
G[Spiel] --> X[Game Audiogerät];
G[Spiel] --> Y[Game Raw Audiogerät];
M[Mikrofon] --> MA[Mikrofon Audiogerät];
MA[Mikrofon Audiogerät] ---> Z[Live Mix];
MA[Mikrofon Audiogerät] ---> R[Aufnahme];
D[Browser Audiogerät] ---> Z[Live Mix];
S[Spotify Audiogerät] ---> Z[Live Mix];
X[Game Audiogerät] ---> Z[Live Mix];
Y[Game Raw Audiogerät] ---> R[Aufnahme];
Hinweise¶
- Game Raw Audio wird ggf. nicht benötigt, da ich hier einfach das gleiche Audiogerät ein zweites mal in OBS einfügen könnte und dieses ungefiltert nur auf meine Game Audio Spur aufnehmen lassen könnte
- Das gleiche gilt für das Mikrofon, jedoch werde ich ja bereits höchste Qualitätsmaßstäbe an Klang hier anlegen, daher noch fraglich wie sinnvoll eine unbearbeitete/ungefilterte zusätzliche Spur wäre
Audiospuren in OBS Studio¶
| Spur | Bezeichnung | Zweck |
|---|---|---|
| 1 | Live | Die Live-Ausgabe (Mix) Stream / Aufnahme |
| 2 | Mic | Mikrofon (gefiltert) |
| 3 | Game | Spiel (ungefiltert) |
| 4 | Alerts | Sämtliche Alerts, Spielereien, Soundeffekte |
| 5 | Aux | Hilfsspur für Browser, Musikstreaming etc. |
| 6 | VOD | VOD Spur, mögl. ohne Copyright kritisches Material |
Audiogeräte Matrix OBS Studio¶
| Gerät | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| Browser | x | - | - | - | x | x |
| Spotify | x | - | - | - | x | - |
| Alerts | x | - | - | x | - | x |
| Game | x | - | - | - | - | x |
| Game Raw | - | - | x | - | - | - |
| Mikrofon | x | x | - | - | - | x |
Hinweise¶
- Alle Quellen (außer Game Raw) sind mit einem Kompressorfilter inkl. Sidechain Ducking des gefilterten Mikrofons versehen
- Die Spuren 2-5 sind nur für die Aufnahme relevant
- Spur 1 ist der Live-Mix und Spur 6 eine Kopie dessen; für VODs (Stream-Aufzeichnungen) maßgeblich ohne Copyright-kritisches Material
Virtuelle Sinks¶
Für die schematisch dargestellten Audiogeräte habe ich ein Skript entwickelt, um diese bereitzustellen, das ganze wird über einen User systemd Service gestartet und gestoppt.
Startskript setup-audio.sh:
#!/usr/bin/env bash
set -e
# Namen
SINK_RAW="RawGame"
SINK_GAME="Game"
SINK_BROWSER="Browser"
SINK_SPOTIFY="Spotify"
#SINK_MIC="Virtual_Mic" # optional, Mic Routing
# Erstelle Null sinks falls noch nicht vorhanden
create_sink() {
local name="$1"
# exakte Prüfung der Sink-Namen (2. Spalte), kein Teilstring-Match
if ! pactl list short sinks | awk '{print $2}' | grep -xq "$name"; then
pactl load-module module-null-sink \
sink_name="$name" \
sink_properties=device.description="$name" \
object.linger=1
fi
}
create_sink "${SINK_RAW}"
create_sink "${SINK_GAME}"
create_sink "${SINK_BROWSER}"
create_sink "${SINK_SPOTIFY}"
#create_sink "${SINK_MIC}"
# Kurze Pause, damit PipeWire die Objekte anlegt
sleep 0.5
# Optional: setze sinnvolle Namen für Monitor‑Quellen (werden automatisch erzeugt)
# Monitor‑Quellen heißen in der Regel <sink_name>.monitor
#echo "Virtuelle Sinks erstellt: ${SINK_RAW}, ${SINK_GAME}, ${SINK_BROWSER}, ${SINK_SPOTIFY}, ${SINK_MIC}"
echo "Virtuelle Sinks erstellt: ${SINK_RAW}, ${SINK_GAME}, ${SINK_BROWSER}, ${SINK_SPOTIFY}"
Stopskript unload-sinks.sh:
#!/bin/bash
echo "Unloading custom sinks & modules"
echo -e "\nSinks:"
pactl list short sinks
#echo -e "\nModules:"
#pactl list short modules
echo -e "\n---\n\nUnloading modules..."
pactl list short modules | awk '/spotify_sink|game_sink/ {print $1}' | while read -r id; do
[ -n "$id" ] && pactl unload-module "$id" || true
done
echo -e "\nRestarting pipewire & pipewire-pulse services to unload sinks..."
systemctl --user restart --no-block pipewire pipewire-pulse || true
sleep 3
echo -e "\nResult:"
pactl list short sinks
#pactl list short modules
SystemD User-Service ~/.config/systemd/user/virtual-audio.service:
[Unit]
Description=Virtual audio sinks for OBS (Game / Spotify)
After=pipewire.service pipewire-pulse.service
Wants=pipewire.service pipewire-pulse.service
[Service]
Type=oneshot
ExecStart=%h/bin/setup-audio.sh
ExecStop=%h/bin/unload-sinks.sh
RemainAfterExit=yes
[Install]
WantedBy=default.target
Festlegen der Audiogeräte nach Anwendung¶
Um meinem Chrome-Browser, Spotify und dem Spiel entsprechende virtuelle Sinks zuzuweisen gibt es mehrere Möglichkeiten.
Mir wäre eine automatisierte Festlegung im Rahmen o.g. Service Skripts am liebsten.
Da dies aber momentan für mich sehr komplex ist und noch nicht zu bewerkstelligen bediene ich mich der manuellen Methode mit pavucontrol:
| pavucontrol Screenshot |
|---|
![]() |
| Google wird als Ausgabegerät der virtuelle Sink "Browser" zugewiesen |
Audio-Latenz (dirty?)¶
Man kann zur Laufzeit Latenzen verändern. Bisher geht 256 absolut sauber (vorher 1024).
Das ist eine Rate von 1024/48000 vorher -> 256/48000 nachher, das entspricht 21 ms vs. 5 ms.
Mit pw-top kann man sich die Raten anzeigen lassen, hier sind die drei ALSA-Geräte schon auf 256 gesetzt:

Befehl zum Latenz verringern:
pw-metadata -n settings 0 clock.force-quantum 256
Zurücksetzen zu den Standardwerten:
pw-metadata -n settings 0 clock.force-quantum 0
Audio asynchron nach längerer Streamzeit¶
Mir ist aufgefallen, dass nach ca. 2-3 Stunden und länger das Spielaudio und alles immer mehr Delay bekommen. Spielaudio greife ich ja auch monitorend über einen virtuellen Audiosink ab.
Sehe gerade in der Streamaufnahme, dass das offenbar im Stream nicht der Fall ist, das heisst, dass "nur" mein Audio im Monitoring (also Headset, OBS Monitoring) eine Latenz bekommt, warum auch immer.
Es bleibt zu testen, ob ein simpler Reset in OBS, Monitor aus/an das Problem beheben könnte. Oder es muss Pipewire als Audiogerät verwendet werden in OBS, was evtl. eher zu empfehlen ist?
Pipewire Audiogeräte für OBS¶
Installieren per paru -S obs-pipewire-audio-capture - ich wähle hier das erste Paket aus dem AUR, nicht -bin und nicht -git.
Ich denke ich sollte das mal probieren und alles auf Pipewire umstellen dann.
Realtime Scheduling für Pipewire (Bruteforce Methode?)¶
Da ich mit RTKit, Wireplumber und deren Configs und Gehampel nicht gegen den BORE-Scheduler des CachyOS-Kernels ankomme (ananicy funkt übrigens ANGEBLICH nicht dazwischen), habe ich manuell die RTPRIO gesetzt.
Ich habe auch die systemd Service Dateien für pipewire und pipewire-pulse per --user edit editiert und gesetzt.
Kernel Setting¶
Für den Kernel habe ich die Datei /etc/sysctl.d/99-kernel-sched-rt.conf angelegt:
kernel.sched_rt_runtime_us=950000
Aktivieren über sudo sysctl --system
Der Benutzer sollte in der Gruppe audio und ggf. zusätzlich in der Gruppe realtime sein!
❯ groups
marquisor sys network rfkill users video storage lp audio wheel realtime plugdev
Systemd Services¶
Beispiel für systemctl --user edit pipewire.service und äquivalent für systemctl --user edit pipewire-pulse.service
### Editing /home/marquisor/.config/systemd/user/pipewire.service.d/override.conf
### Anything between here and the comment below will become the contents of the drop-in file
[Service]
CPUSchedulingPolicy=rr
CPUSchedulingPriority=88
LimitRTPRIO=99
RestrictRealtime=no
### Edits below this comment will be discarded
### /usr/lib/systemd/user/pipewire.service
# [Unit]
# Description=PipeWire Multimedia Service
#
...
Dienste neu starten
systemctl --user daemon-reload
systemctl --user restart pipewire pipewire-pulse
RT setzen und prüfen¶
PIDs ermitteln
❯ pidof pipewire
34758 34756
Prioritäten MANUELL setzen mit
chrtsudo chrt -f -p 88 34756
sudo chrt -f -p 88 34758
Finales Ergebnis
❯ ps -eo user,pid,cls,rtprio,ni,pri,pcpu,comm |grep pipewire
marquis+ 34756 FF 88 - 128 0.0 pipewire
marquis+ 34758 FF 88 - 128 0.0 pipewire-pulse
RTPRIO liegt jetzt also bei 88
Weitere Prüfung
❯ chrt -p 34756
Derzeitige Scheduling-Vorschrift für PID 34756: SCHED_FIFO
Derzeitige Scheduling-Priorität für PID 34756: 88
~
❯ chrt -p 34758
Derzeitige Scheduling-Vorschrift für PID 34758: SCHED_FIFO
Derzeitige Scheduling-Priorität für PID 34758: 88
~
Man soll nun mit pw-top prüfen, ob in der Spalte ERR weiterhin oder überhaupt Fehler auftauchen, wenn nicht, läuft alles gut und normal! Muss ich beim Stream also beobachten dann.
Line-In und S/PDIF-In steuern (ALSA)¶
Ja mit alsactl store bringt das irgendwie nix, auch nicht mit sudo alsactl store. Beim Reboot gehen die Einstellungen wieder flöten.
Ich möchte meine RetroPC Sounds über die Creative X-Fi Soundkarte Line-In bzw. Digital-In (TOSlink) wiedergeben.
Dafür müssen im Alsamixer im Aufnahme-Abschnitt die Ports auf "AUFNAHME" gesetzt werden, siehe Bild:

Das wird aber über einen Reboot hinaus nicht gespeichert, wahrscheinlich weil Pipewire oder andere (modernere) Soundklamotten den Krempel überschreiben beim Booten.
Ich mache mir dafür ein Skript, alternativ Kommandozeile mit folgendem Einzeiler:
amixer -c XFi sset Line 80% cap
Das setzt das "Aufnahme-Flag", sowie 80% Lautstärke beim Line-In Anschluss. Lautstärke ist hier nicht nötig, das ist nur für wirkliche Aufnahme. Für die Wiedergabe an sich ist die Lautstärke irrelevant.
Die Lautstärke der Wiedergabe wird - wer hätt's gedacht - im Abschnitt "F3: Wiedergabe" eingestellt.
Den Namen der steuerbaren Ein-/Ausgänge kann man hierüber herausfinden:
amixer -c XFi scontrols
Ausgabe:
❯ amixer -c XFi scontrols
Simple mixer control 'Master',0
Simple mixer control 'PCM',0
Simple mixer control 'Front',0
Simple mixer control 'Surround',0
Simple mixer control 'Center/LFE',0
Simple mixer control 'Side',0
Simple mixer control 'Line',0
Simple mixer control 'Mic',0
Simple mixer control 'IEC958',0
Simple mixer control 'Digital',0
Hier das Skript XFi_set.sh:
#!/bin/bash
# Scriptname für lesbarere Ausgabe
SCRIPTNAME=${0##*/}
# Name der Soundkarte aus /proc/asound/cards
CARD="XFi"
usage() {
echo "Nutzung: $SCRIPTNAME [OPTIONEN]"
echo ""
echo "Optionen:"
echo " --line on|off Line-In Aufnahme aktivieren / deaktivieren"
echo " --spdif on|off S/PDIF Aufnahme aktivieren / deaktivieren"
echo " -h, --help Hilfe anzeigen"
echo ""
echo "Beispiele:"
echo " $SCRIPTNAME --line on"
echo " $SCRIPTNAME --spdif off"
echo " $SCRIPTNAME line on spdif off"
exit 1
}
# Wenn Parameterübergabe leer, dann zeige an wie das Programm zu benutzen ist
if [ "$#" -eq 0 ]; then
usage
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--line|line)
case "$2" in
on)
amixer -q -c "$CARD" sset Line cap
echo "[X-Fi] Line-In Capture: EIN"
shift 2
;;
off)
amixer -q -c "$CARD" sset Line nocap
echo "[X-Fi] Line-In Capture: AUS"
shift 2
;;
*)
echo "Fehler: Parameter '$2' für Line ungültig (nur 'on' oder 'off' erlaubt)."
exit 1
;;
esac
;;
--spdif|spdif)
case "$2" in
on)
amixer -q -c "$CARD" sset IEC958 cap
echo "[X-Fi] S/PDIF Capture: EIN"
shift 2
;;
off)
amixer -q -c "$CARD" sset IEC958 nocap
echo "[X-Fi] S/PDIF Capture: AUS"
shift 2
;;
*)
echo "Fehler: Parameter '$2' für S/PDIF ungültig (nur 'on' oder 'off' erlaubt)."
exit 1
;;
esac
;;
-h|--help)
usage
;;
*)
echo "Fehler: Unbekannter Parameter '$1'"
usage
;;
esac
done
