]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
qemu: Generate cmd line for pipewire audio backend
authorMichal Privoznik <mprivozn@redhat.com>
Wed, 10 May 2023 10:58:44 +0000 (12:58 +0200)
committerMichal Privoznik <mprivozn@redhat.com>
Fri, 24 Nov 2023 16:49:20 +0000 (17:49 +0100)
This is mostly straightforward, except for a teensy-weensy
detail: usually, there's no system wide daemon running, no system
wide available socket that anybody could connect to. PipeWire
uses a per user daemon approach instead. But this in turn means,
that the socket location floats between various locations and is
derived from various environment variables (just like the actual
socket name) and thus we must pass the variables to QEMU.

Resolves: https://gitlab.com/libvirt/libvirt/-/issues/560
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
src/qemu/qemu_command.c
tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args
tests/qemuxml2argvdata/audio-many-backends.xml
tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args [new file with mode: 0644]
tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args [new file with mode: 0644]
tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args [new file with mode: 0644]
tests/qemuxml2argvtest.c
tests/qemuxml2xmloutdata/audio-many-backends.x86_64-latest.xml

index 173639277ced084ae72d29cf85c3c5b093732e06..d54149ed2d376dc1c2f23f110679bc39d38a7682 100644 (file)
@@ -7833,6 +7833,68 @@ qemuBuildAudioSDLProps(virDomainAudioIOSDL *def,
 }
 
 
+static int
+qemuBuildAudioPipewireAudioProps(virDomainAudioIOPipewireAudio *def,
+                                 virJSONValue **props)
+{
+    return virJSONValueObjectAdd(props,
+                                 "S:name", def->name,
+                                 "S:stream-name", def->streamName,
+                                 "p:latency", def->latency,
+                                 NULL);
+}
+
+
+static void
+qemuBuildAudioPipewireAudioEnv(virCommand *cmd,
+                               const char *runtimeDir)
+{
+    const char *envVars[] = { "PIPEWIRE_RUNTIME_DIR", "XDG_RUNTIME_DIR",
+                              "USERPROFILE" };
+    size_t i;
+
+    /* PipeWire needs access to its daemon socket. The socket name is
+     * configurable (core.name in pipewire.conf, or PIPEWIRE_CORE and
+     * PIPEWIRE_REMOTE env vars). If the socket name is not an absolute
+     * path, then the socket is looked for in the following directories
+     * (in order):
+     *
+     * - PIPEWIRE_RUNTIME_DIR
+     * - XDG_RUNTIME_DIR
+     * - USERPROFILE
+     *
+     * This order is defined in get_runtime_dir() from
+     * src/modules/module-protocol-native/local-socket.c from PipeWire's
+     * codebase.
+     *
+     * Now, PIPEWIRE_CORE and/or PIPEWIRE_REMOTE should be passed
+     * whenever present in the environment. But for the other three
+     * (socket location dirs):
+     *
+     * 1) set it to user defined value (@runtimeDir != NULL), or
+     * 2) we can add just the first existing one (basically mimic
+     *    get_runtime_dir() logic; @runtimeDir == NULL).
+     */
+
+    virCommandAddEnvPass(cmd, "PIPEWIRE_CORE");
+    virCommandAddEnvPass(cmd, "PIPEWIRE_REMOTE");
+
+    if (runtimeDir) {
+        virCommandAddEnvPair(cmd, "PIPEWIRE_RUNTIME_DIR", runtimeDir);
+    } else {
+        for (i = 0; i < G_N_ELEMENTS(envVars); i++) {
+            const char *value = getenv(envVars[i]);
+
+            if (!value)
+                continue;
+
+            virCommandAddEnvPair(cmd, envVars[i], value);
+            break;
+        }
+    }
+}
+
+
 static int
 qemuBuildAudioCommandLineArg(virCommand *cmd,
                              virDomainAudioDef *def)
@@ -7939,6 +8001,13 @@ qemuBuildAudioCommandLineArg(virCommand *cmd,
         break;
 
     case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE:
+        if (qemuBuildAudioPipewireAudioProps(&def->backend.pipewire.input, &in) < 0 ||
+            qemuBuildAudioPipewireAudioProps(&def->backend.pipewire.output, &out) < 0)
+            return -1;
+
+        qemuBuildAudioPipewireAudioEnv(cmd, def->backend.pipewire.runtimeDir);
+        break;
+
     case VIR_DOMAIN_AUDIO_TYPE_LAST:
     default:
         virReportEnumRangeError(virDomainAudioType, def->type);
index 4c6c925ccd600e9e074faaf00fef17741c369d2d..0bdc75689f4a50dbface0b768064a9b1c16a3470 100644 (file)
@@ -6,6 +6,7 @@ LOGNAME=test \
 XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \
 XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \
 XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \
+PIPEWIRE_RUNTIME_DIR=/run/user/1000 \
 /usr/bin/qemu-system-x86_64 \
 -name guest=QEMUGuest1,debug-threads=on \
 -S \
@@ -32,6 +33,7 @@ XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \
 -audiodev '{"id":"audio1","driver":"none"}' \
 -audiodev '{"id":"audio2","driver":"alsa"}' \
 -audiodev '{"id":"audio3","driver":"pa"}' \
+-audiodev '{"id":"audio4","driver":"pipewire"}' \
 -vnc 127.0.0.1:0,audiodev=audio2 \
 -device '{"driver":"cirrus-vga","id":"video0","bus":"pci.0","addr":"0x2"}' \
 -device '{"driver":"AC97","id":"sound0","audiodev":"audio1","bus":"pci.0","addr":"0x3"}' \
index c681784526dd241e540b4c7e1831b435901df99d..1659723f9164d27adb769f27f34d711e5dbd0371 100644 (file)
@@ -51,6 +51,7 @@
     <audio id='1' type='none'/>
     <audio id='2' type='alsa'/>
     <audio id='3' type='pulseaudio'/>
+    <audio id='4' type='pipewire'/>
     <video>
       <model type='cirrus' vram='16384' heads='1' primary='yes'/>
       <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
diff --git a/tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args b/tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args
new file mode 100644 (file)
index 0000000..d94bd6a
--- /dev/null
@@ -0,0 +1,36 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \
+PIPEWIRE_RUNTIME_DIR=/run/user/1000 \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \
+-accel tcg \
+-cpu qemu64 \
+-m size=219136k \
+-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-boot strict=on \
+-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \
+-blockdev '{"driver":"host_cdrom","filename":"/dev/cdrom","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \
+-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}' \
+-device '{"driver":"ide-cd","bus":"ide.1","unit":0,"drive":"libvirt-1-format","id":"ide0-1-0","bootindex":1}' \
+-audiodev '{"id":"audio1","driver":"pipewire","timer-period":50,"in":{"mixing-engine":true,"fixed-settings":true,"voices":1,"buffer-length":200,"frequency":44100,"channels":2,"format":"s16","name":"fish"},"out":{"mixing-engine":true,"fixed-settings":true,"voices":2,"buffer-length":200,"frequency":22050,"channels":4,"format":"f32","name":"fish"}}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args b/tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args
new file mode 100644 (file)
index 0000000..dd97b46
--- /dev/null
@@ -0,0 +1,36 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \
+PIPEWIRE_RUNTIME_DIR=/run/user/1000 \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \
+-accel tcg \
+-cpu qemu64 \
+-m size=219136k \
+-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-boot strict=on \
+-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \
+-blockdev '{"driver":"host_cdrom","filename":"/dev/cdrom","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \
+-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}' \
+-device '{"driver":"ide-cd","bus":"ide.1","unit":0,"drive":"libvirt-1-format","id":"ide0-1-0","bootindex":1}' \
+-audiodev '{"id":"audio1","driver":"pipewire","timer-period":50,"in":{"mixing-engine":true,"fixed-settings":true,"voices":1,"buffer-length":100,"frequency":44100,"channels":2,"format":"s16","name":"fish","stream-name":"food","latency":100},"out":{"mixing-engine":true,"fixed-settings":true,"voices":2,"buffer-length":200,"frequency":22050,"channels":4,"format":"f32","name":"fish","stream-name":"food","latency":200}}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args b/tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args
new file mode 100644 (file)
index 0000000..90951a3
--- /dev/null
@@ -0,0 +1,36 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \
+XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \
+XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \
+PIPEWIRE_RUNTIME_DIR=/run/user/1000 \
+/usr/bin/qemu-system-x86_64 \
+-name guest=QEMUGuest1,debug-threads=on \
+-S \
+-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \
+-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \
+-accel tcg \
+-cpu qemu64 \
+-m size=219136k \
+-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-boot strict=on \
+-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \
+-blockdev '{"driver":"host_cdrom","filename":"/dev/cdrom","node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}' \
+-blockdev '{"node-name":"libvirt-1-format","read-only":true,"driver":"raw","file":"libvirt-1-storage"}' \
+-device '{"driver":"ide-cd","bus":"ide.1","unit":0,"drive":"libvirt-1-format","id":"ide0-1-0","bootindex":1}' \
+-audiodev '{"id":"audio1","driver":"pipewire"}' \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
index 24997c0aaa3369bdd07041fd5312fd072d452059..b2ea2191dca69d689aaf4f4440dd4811b01702d1 100644 (file)
@@ -869,6 +869,9 @@ mymain(void)
     g_unsetenv("DYLD_FORCE_FLAT_NAMESPACE");
     g_unsetenv("QEMU_AUDIO_DRV");
     g_unsetenv("SDL_AUDIODRIVER");
+    g_unsetenv("PIPEWIRE_CORE");
+    g_unsetenv("PIPEWIRE_REMOTE");
+    g_unsetenv("PIPEWIRE_RUNTIME_DIR");
 
     DO_TEST_CAPS_LATEST("minimal");
     DO_TEST_CAPS_LATEST_PARSE_ERROR("minimal-no-memory");
@@ -909,6 +912,9 @@ mymain(void)
     DO_TEST_CAPS_LATEST("audio-jack-minimal");
     DO_TEST_CAPS_LATEST("audio-oss-minimal");
     DO_TEST_CAPS_LATEST("audio-pulseaudio-minimal");
+    g_setenv("PIPEWIRE_RUNTIME_DIR", "/run/user/1000", TRUE);
+    DO_TEST_CAPS_LATEST("audio-pipewire-minimal");
+    g_unsetenv("PIPEWIRE_RUNTIME_DIR");
     DO_TEST_CAPS_LATEST("audio-sdl-minimal");
     DO_TEST_CAPS_LATEST("audio-spice-minimal");
     DO_TEST_CAPS_LATEST("audio-file-minimal");
@@ -918,6 +924,7 @@ mymain(void)
     DO_TEST_CAPS_LATEST("audio-coreaudio-best");
     DO_TEST_CAPS_LATEST("audio-oss-best");
     DO_TEST_CAPS_LATEST("audio-pulseaudio-best");
+    DO_TEST_CAPS_LATEST("audio-pipewire-best");
     DO_TEST_CAPS_LATEST("audio-sdl-best");
     DO_TEST_CAPS_LATEST("audio-spice-best");
     DO_TEST_CAPS_LATEST("audio-file-best");
@@ -928,11 +935,14 @@ mymain(void)
     DO_TEST_CAPS_LATEST("audio-jack-full");
     DO_TEST_CAPS_LATEST("audio-oss-full");
     DO_TEST_CAPS_LATEST("audio-pulseaudio-full");
+    DO_TEST_CAPS_LATEST("audio-pipewire-full");
     DO_TEST_CAPS_LATEST("audio-sdl-full");
     DO_TEST_CAPS_LATEST("audio-spice-full");
     DO_TEST_CAPS_LATEST("audio-file-full");
 
+    g_setenv("PIPEWIRE_RUNTIME_DIR", "/run/user/1000", TRUE);
     DO_TEST_CAPS_LATEST("audio-many-backends");
+    g_unsetenv("PIPEWIRE_RUNTIME_DIR");
 
     /* Validate auto-creation of <audio> for legacy compat */
     g_setenv("QEMU_AUDIO_DRV", "sdl", TRUE);
index c681784526dd241e540b4c7e1831b435901df99d..1659723f9164d27adb769f27f34d711e5dbd0371 100644 (file)
@@ -51,6 +51,7 @@
     <audio id='1' type='none'/>
     <audio id='2' type='alsa'/>
     <audio id='3' type='pulseaudio'/>
+    <audio id='4' type='pipewire'/>
     <video>
       <model type='cirrus' vram='16384' heads='1' primary='yes'/>
       <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>