WirePlumber is completely overkill as a session manager here, and ideally a trivial session manager would be used instead. PipeWire is configured to listen on the PulseAudio socket, so PulseAudio compatibility works. pw-record and pw-play both work, and if PulseAudio is installed paplay and parecord also work. This does install a lot of unnecessary files into the VMs, which will hopefully be removed later as part of a debloating effort. Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com> --- img/app/Makefile | 18 +- img/app/default.nix | 3 + img/app/etc/mdev.conf | 1 + img/app/etc/pipewire/pipewire.conf | 199 ++++++++++++++++++ .../etc/s6-rc/app/dependencies.d/wireplumber | 0 .../etc/s6-rc/pipewire/dependencies.d/dbus | 0 img/app/etc/s6-rc/pipewire/notification-fd | 1 + .../s6-rc/pipewire/notification-fd.license | 2 + img/app/etc/s6-rc/pipewire/run | 23 ++ img/app/etc/s6-rc/pipewire/type | 1 + img/app/etc/s6-rc/pipewire/type.license | 2 + .../etc/s6-rc/wireplumber/dependencies.d/dbus | 0 .../s6-rc/wireplumber/dependencies.d/pipewire | 0 img/app/etc/s6-rc/wireplumber/run | 4 + img/app/etc/s6-rc/wireplumber/type | 1 + img/app/etc/s6-rc/wireplumber/type.license | 2 + .../wireplumber.conf.d/99_spectrum.conf | 39 ++++ 17 files changed, 293 insertions(+), 3 deletions(-) create mode 100644 img/app/etc/pipewire/pipewire.conf create mode 100644 img/app/etc/s6-rc/app/dependencies.d/wireplumber create mode 100644 img/app/etc/s6-rc/pipewire/dependencies.d/dbus create mode 100644 img/app/etc/s6-rc/pipewire/notification-fd create mode 100644 img/app/etc/s6-rc/pipewire/notification-fd.license create mode 100644 img/app/etc/s6-rc/pipewire/run create mode 100644 img/app/etc/s6-rc/pipewire/type create mode 100644 img/app/etc/s6-rc/pipewire/type.license create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/dbus create mode 100644 img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire create mode 100644 img/app/etc/s6-rc/wireplumber/run create mode 100644 img/app/etc/s6-rc/wireplumber/type create mode 100644 img/app/etc/s6-rc/wireplumber/type.license create mode 100644 img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf diff --git a/img/app/Makefile b/img/app/Makefile index 4b4d64f81d99a01eebe777f3737fef813ebb6d3f..69bc9afc0756b0628ddc1fe53913a75b45b3213b 100644 --- a/img/app/Makefile +++ b/img/app/Makefile @@ -53,7 +53,10 @@ VM_FILES = \ etc/s6-linux-init/scripts/rc.init \ etc/s6-linux-init/scripts/rc.shutdown \ etc/s6-linux-init/scripts/rc.shutdown.final \ - etc/xdg/xdg-desktop-portal/portals.conf + etc/xdg/xdg-desktop-portal/portals.conf \ + etc/pipewire/pipewire.conf \ + etc/wireplumber/wireplumber.conf.d/99_spectrum.conf + VM_DIRS = dev run proc sys tmp \ etc/s6-linux-init/run-image/service \ etc/s6-linux-init/run-image/user \ @@ -85,6 +88,7 @@ build/rootfs.erofs: ../../scripts/make-erofs.sh $(PACKAGES_FILE) $(VM_FILES) $(V VM_S6_RC_FILES = \ etc/s6-rc/app/dependencies.d/dbus \ etc/s6-rc/app/dependencies.d/wayland-proxy-virtwl \ + etc/s6-rc/app/dependencies.d/wireplumber \ etc/s6-rc/app/run \ etc/s6-rc/app/type \ etc/s6-rc/dbus/notification-fd \ @@ -98,9 +102,16 @@ VM_S6_RC_FILES = \ etc/s6-rc/mdevd/type \ etc/s6-rc/ok-all/contents \ etc/s6-rc/ok-all/type \ + etc/s6-rc/pipewire/notification-fd \ + etc/s6-rc/pipewire/run \ + etc/s6-rc/pipewire/type \ etc/s6-rc/wayland-proxy-virtwl/notification-fd \ etc/s6-rc/wayland-proxy-virtwl/run \ - etc/s6-rc/wayland-proxy-virtwl/type + etc/s6-rc/wayland-proxy-virtwl/type \ + etc/s6-rc/wireplumber/dependencies.d/dbus \ + etc/s6-rc/wireplumber/dependencies.d/pipewire \ + etc/s6-rc/wireplumber/run \ + etc/s6-rc/wireplumber/type build/etc/s6-rc: $(VM_S6_RC_FILES) mkdir -p $$(dirname $@) @@ -137,7 +148,7 @@ start-virtiofsd: scripts/start-virtiofsd.elb .PHONY: start-virtiofsd run-qemu: $(imgdir)/appvm/blk/root.img start-vhost-user-net start-virtiofsd - @../../scripts/run-qemu.sh -m 256 -cpu host -kernel $(KERNEL) -vga none \ + @../../scripts/run-qemu.sh -m 256 -kernel $(KERNEL) -vga none \ -drive file=$(imgdir)/appvm/blk/root.img,if=virtio,format=raw,readonly=on \ -append "root=PARTLABEL=root nokaslr" \ -gdb unix:build/gdb.sock,server,nowait \ @@ -147,6 +158,7 @@ run-qemu: $(imgdir)/appvm/blk/root.img start-vhost-user-net start-virtiofsd -chardev socket,id=virtiofsd,path=build/virtiofsd.sock \ -device vhost-user-fs-pci,chardev=virtiofsd,tag=virtiofs0 \ -device virtio-gpu-rutabaga-pci,cross-domain=on,hostmem=8G \ + -audio driver=pipewire,model=virtio \ -object memory-backend-memfd,id=mem,size=256M,share=on \ -numa node,memdev=mem \ -device vhost-vsock-pci,guest-cid=3 \ diff --git a/img/app/default.nix b/img/app/default.nix index 740643ac41f6473cdb6f6b0fd1f5f47f4493240d..d3eed1f0accdc8968d1ba5bdec74ab597789082f 100644 --- a/img/app/default.nix +++ b/img/app/default.nix @@ -48,6 +48,9 @@ let pkgs.xwayland pkgs.xdg-desktop-portal pkgs.xdg-desktop-portal-gtk + # Depends on pulseaudio libs + pkgs.pipewire + pkgs.wireplumber ]; })).fhsenv; in diff --git a/img/app/etc/mdev.conf b/img/app/etc/mdev.conf index f2101e1f683c49808358b25520080c59ed2afa8e..0e4a1a088522c05da9e0ce15fe135c40d6cf3064 100644 --- a/img/app/etc/mdev.conf +++ b/img/app/etc/mdev.conf @@ -5,3 +5,4 @@ $INTERFACE=.* 0:0 660 ! +/etc/mdev/iface $MODALIAS=virtio:d0000001Av.* 0:0 660 ! +/etc/mdev/virtiofs dri/card0 0:0 660 +background { /etc/mdev/listen card0 } +snd/controlC0 0:0 660 +background { /etc/mdev/listen controlC0 } diff --git a/img/app/etc/pipewire/pipewire.conf b/img/app/etc/pipewire/pipewire.conf new file mode 100644 index 0000000000000000000000000000000000000000..e5a413a409f46e7fe176102bbd6780db14f85dba --- /dev/null +++ b/img/app/etc/pipewire/pipewire.conf @@ -0,0 +1,199 @@ +# SPDX-License-Identifier: MIT + +# Copyright © 2018 Wim Taymans +# Copyright © 2025 Demi Marie Obenour +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# This file is based on the upstream default configuration. This can be +# found in upstream GitLab or in any distro with a recent version of PipeWire. +# The following changes have been made: +# +# - Conditions that have known values in Spectrum VMs are omitted. +# - Modules for hardware devices Spectrum VMs don't have are not loaded. +# - The PulseAudio emulation server is loaded. +# - Settings for VMs are applied unconditionally. +# - Most comments in the upstream files have been removed. +# - Device nodes for virtio-sound devices have been added. +# - Integration with udev and logind is removed. +context.properties = { + # Upstream defaults. + link.max-buffers = 16 + core.daemon = true + core.name = pipewire-0 + # Account for running in a VM + default.clock.min-quantum = 1024 +} + +# Upstream defaults, with support for AVB, V4L2, libcamera +# bluez, Vulkan, JACK, and video conversion omitted. +context.spa-libs = { + audio.convert.* = audioconvert/libspa-audioconvert + api.alsa.* = alsa/libspa-alsa + support.* = support/libspa-support +} + +context.modules = [ + # Upstream defaults + { name = libpipewire-module-rt + args = { nice.level = -11, rt.prio = 88 } + } + { name = libpipewire-module-protocol-native } + { name = libpipewire-module-metadata } + { name = libpipewire-module-spa-device-factory } + { name = libpipewire-module-spa-node-factory } + { name = libpipewire-module-client-node } + { name = libpipewire-module-access } + { name = libpipewire-module-client-device } + { name = libpipewire-module-portal } + { name = libpipewire-module-adapter } + { name = libpipewire-module-link-factory } + { name = libpipewire-module-session-manager } + + # Load the PulseAudio server into PipeWire. + # This avoids needing a separate pipewire-pulse + # process. The args are those used when running + # in a VM. + { name = libpipewire-module-protocol-pulse + args = { + server.address = [ "unix:native" ] + pulse.min.quantum = 1024/48000 + } + } +] + +context.objects = [ + # Upstream defaults + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Dummy-Driver + node.group = pipewire.dummy + node.sync-group = sync.dummy + priority.driver = 200000 + } + } + { factory = spa-node-factory + args = { + factory.name = support.node.driver + node.name = Freewheel-Driver + priority.driver = 190000 + node.group = pipewire.freewheel + node.sync-group = sync.dummy + node.freewheel = true + } + } + + # Spectrum doesn't use udev, so device nodes must be created statically. + # Creating them with pw-cli works as long as pw-cli is running, but + # the nodes are destroyed when pw-cli exits. + { factory = adapter + args = { + alsa.card = 0, + alsa.card_name = "VirtIO SoundCard" + alsa.device = 0 + alsa.driver_name = "virtio_snd" + alsa.id = "SoundCard" + alsa.long_card_name = "VirtIO SoundCard at pci/0000:00:01.0/virtio0" + alsa.name = "VirtIO SoundCard" + alsa.subdevice = 0 + alsa.subdevice_name = "subdevice #0" + api.alsa.card.longname = "VirtIO SoundCard at pci/0000:00:01.0/virtio0" + api.alsa.card.name = "VirtIO SoundCard" + api.alsa.headroom = 0 + api.alsa.path = "hw:0,0,0" + api.alsa.pcm.card = 0, + api.alsa.pcm.stream = "playback" + audio.allowed-rates = [ ] + audio.channels = 2 + audio.format = "S32" + audio.position = "FL,FR" + audio.rate = 48000 + factory.name = "api.alsa.pcm.sink" + media.class = "Audio/Sink" + node.name = "alsa_output.pci-0000_00_01.0.analog-stereo" + node.suspend-on-idle = true + } + } + { factory = adapter + args = { + alsa.card = 0, + alsa.card_name = "VirtIO SoundCard" + alsa.device = 0 + alsa.driver_name = "virtio_snd" + alsa.id = "SoundCard" + alsa.long_card_name = "VirtIO SoundCard at pci/0000:00:01.0/virtio0" + alsa.name = "VirtIO SoundCard" + alsa.subdevice = 0 + alsa.subdevice_name = "subdevice #0" + api.alsa.card.longname = "VirtIO SoundCard at pci/0000:00:01.0/virtio0" + api.alsa.card.name = "VirtIO SoundCard" + api.alsa.headroom = 0 + api.alsa.path = "hw:0,0,0" + api.alsa.pcm.card = 0, + api.alsa.pcm.stream = "capture" + audio.allowed-rates = [ ] + audio.channels = 2 + audio.format = "S32" + audio.position = "FL,FR" + audio.rate = 48000 + factory.name = "api.alsa.pcm.source" + media.class = "Audio/Source" + node.name = "alsa_input.pci-0000_00_01.0.analog-stereo" + node.suspend-on-idle = true + } + } +] + +# Load the modules that are in the default config *except* +# for ones whose job is to maintain state. +pulse.cmd = [ + { cmd = "load-module" args = "module-always-sink" flags = [ ] } + { cmd = "load-module" args = "module-device-manager" flags = [ ] } +] + +# More default stuff. +pulse.rules = [ + { + matches = [ + { application.process.binary = "teams" } + { application.process.binary = "teams-insiders" } + { application.process.binary = "teams-for-linux" } + { application.process.binary = "skypeforlinux" } + ] + actions = { quirks = [ force-s16-info ] } + } + { + matches = [ { application.process.binary = "firefox" } ] + actions = { quirks = [ remove-capture-dont-move ] } + } + { + matches = [ { application.name = "~speech-dispatcher.*" } ] + actions = { + update-props = { + pulse.min.req = 512/48000 + pulse.min.quantum = 512/48000 + pulse.idle.timeout = 5 + } + } + } +] + +context.exec = [] diff --git a/img/app/etc/s6-rc/app/dependencies.d/wireplumber b/img/app/etc/s6-rc/app/dependencies.d/wireplumber new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/img/app/etc/s6-rc/pipewire/dependencies.d/dbus b/img/app/etc/s6-rc/pipewire/dependencies.d/dbus new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/img/app/etc/s6-rc/pipewire/notification-fd b/img/app/etc/s6-rc/pipewire/notification-fd new file mode 100644 index 0000000000000000000000000000000000000000..7ed6ff82de6bcc2a78243fc9c54d3ef5ac14da69 --- /dev/null +++ b/img/app/etc/s6-rc/pipewire/notification-fd @@ -0,0 +1 @@ +5 diff --git a/img/app/etc/s6-rc/pipewire/notification-fd.license b/img/app/etc/s6-rc/pipewire/notification-fd.license new file mode 100644 index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4 --- /dev/null +++ b/img/app/etc/s6-rc/pipewire/notification-fd.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: CC0-1.0 +SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> diff --git a/img/app/etc/s6-rc/pipewire/run b/img/app/etc/s6-rc/pipewire/run new file mode 100644 index 0000000000000000000000000000000000000000..c5cf090fb4779e0f3ede1782ada5c95ce5b25702 --- /dev/null +++ b/img/app/etc/s6-rc/pipewire/run @@ -0,0 +1,23 @@ +#!/bin/execlineb -P +# SPDX-License-Identifier: EUPL-1.2+ +# SPDX-FileCopyrightText: 2023-2024 Alyssa Ross <hi@alyssa.is> +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> + +s6-ipcserver-socketbinder -B /run/user/0/pipewire-0 +fdmove -c 3 0 + +s6-ipcserver-socketbinder -B /run/user/0/pipewire-0-manager +fdmove -c 4 0 + +redirfd -r 0 /dev/null + +# Wait for sound devices to be available +if { /etc/mdev/wait controlC0 } + +# Notify readiness. +if { fdmove 1 5 echo } +fdclose 5 + +export LISTEN_FDS 2 +getpid LISTEN_PID +pipewire diff --git a/img/app/etc/s6-rc/pipewire/type b/img/app/etc/s6-rc/pipewire/type new file mode 100644 index 0000000000000000000000000000000000000000..5883cff0cd1514b2836f4ffa39fdac769a5213cb --- /dev/null +++ b/img/app/etc/s6-rc/pipewire/type @@ -0,0 +1 @@ +longrun diff --git a/img/app/etc/s6-rc/pipewire/type.license b/img/app/etc/s6-rc/pipewire/type.license new file mode 100644 index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4 --- /dev/null +++ b/img/app/etc/s6-rc/pipewire/type.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: CC0-1.0 +SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> diff --git a/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus b/img/app/etc/s6-rc/wireplumber/dependencies.d/dbus new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire b/img/app/etc/s6-rc/wireplumber/dependencies.d/pipewire new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/img/app/etc/s6-rc/wireplumber/run b/img/app/etc/s6-rc/wireplumber/run new file mode 100644 index 0000000000000000000000000000000000000000..d58f1971c7387c896256a91ad0c92386a02fd9e2 --- /dev/null +++ b/img/app/etc/s6-rc/wireplumber/run @@ -0,0 +1,4 @@ +#!/bin/execlineb -P +# SPDX-License-Identifier: EUPL-1.2+ +# SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> +wireplumber --profile spectrum diff --git a/img/app/etc/s6-rc/wireplumber/type b/img/app/etc/s6-rc/wireplumber/type new file mode 100644 index 0000000000000000000000000000000000000000..5883cff0cd1514b2836f4ffa39fdac769a5213cb --- /dev/null +++ b/img/app/etc/s6-rc/wireplumber/type @@ -0,0 +1 @@ +longrun diff --git a/img/app/etc/s6-rc/wireplumber/type.license b/img/app/etc/s6-rc/wireplumber/type.license new file mode 100644 index 0000000000000000000000000000000000000000..c4a0586a407fe14c3e0855749a7524ac3871dda4 --- /dev/null +++ b/img/app/etc/s6-rc/wireplumber/type.license @@ -0,0 +1,2 @@ +SPDX-License-Identifier: CC0-1.0 +SPDX-FileCopyrightText: 2025 Demi Marie Obenour <demiobenour@gmail.com> diff --git a/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf new file mode 100644 index 0000000000000000000000000000000000000000..ff2f464395aaf7c8f0a739b0f01552c4ee987740 --- /dev/null +++ b/img/app/etc/wireplumber/wireplumber.conf.d/99_spectrum.conf @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: MIT + +# Copyright © 2019-2021 Collabora Ltd. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# Disable various stuff Spectrum doesn't need and which causes errors. +wireplumber.profiles = { + spectrum = { + inherits = [ main-embedded ] + hardware.video-capture = disabled + hardware.bluetooth = disabled + support.settings = disabled + check.no-media-session = disabled + policy.standard = required + } +} + +# Default to 100% sink volume. The host will adjust this as needed. +wireplumber.settings = { + device.routes.default-sink-volume = 1.0 +} -- Sincerely, Demi Marie Obenour (she/her/hers)