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.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
---
src/qemu/qemu_command.c | 61 +++++++++++++++++++
.../audio-many-backends.x86_64-latest.args | 2 +
.../qemuxml2argvdata/audio-many-backends.xml | 1 +
.../audio-pipewire-best.x86_64-latest.args | 36 +++++++++++
.../audio-pipewire-full.x86_64-latest.args | 36 +++++++++++
.../audio-pipewire-minimal.x86_64-latest.args | 36 +++++++++++
tests/qemuxml2argvtest.c | 12 ++++
7 files changed, 184 insertions(+)
create mode 100644 tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args
create mode 100644 tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args
create mode 100644 tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 3a08cac870..b31f4db6b1 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -7783,6 +7783,60 @@ 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 *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), we can add just the first existing one
+ * (basically mimic get_runtime_dir() logic).
+ */
+
+ virCommandAddEnvPass(cmd, "PIPEWIRE_CORE");
+ virCommandAddEnvPass(cmd, "PIPEWIRE_REMOTE");
+
+ 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)
@@ -7889,6 +7943,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);
+ break;
+
case VIR_DOMAIN_AUDIO_TYPE_LAST:
default:
virReportEnumRangeError(virDomainAudioType, def->type);
diff --git a/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args b/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args
index 9caf591daf..13dd55054e 100644
--- a/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args
+++ b/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args
@@ -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 \
+XDG_RUNTIME_DIR=/bad-test-used-env-xdg-runtime-dir \
/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"}' \
diff --git a/tests/qemuxml2argvdata/audio-many-backends.xml b/tests/qemuxml2argvdata/audio-many-backends.xml
index c681784526..1659723f91 100644
--- a/tests/qemuxml2argvdata/audio-many-backends.xml
+++ b/tests/qemuxml2argvdata/audio-many-backends.xml
@@ -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
index 0000000000..2e8635c1b0
--- /dev/null
+++ b/tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args
@@ -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 214 \
+-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
index 0000000000..caed694823
--- /dev/null
+++ b/tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args
@@ -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 214 \
+-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","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
index 0000000000..2e76997d58
--- /dev/null
+++ b/tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args
@@ -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 214 \
+-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
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 3100078b54..f29597a19f 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -926,6 +926,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_NOCAPS("minimal");
DO_TEST_PARSE_ERROR_NOCAPS("minimal-no-memory");
@@ -967,6 +970,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");
@@ -976,6 +982,9 @@ mymain(void)
DO_TEST_CAPS_LATEST("audio-coreaudio-best");
DO_TEST_CAPS_LATEST("audio-oss-best");
DO_TEST_CAPS_LATEST("audio-pulseaudio-best");
+ g_setenv("PIPEWIRE_RUNTIME_DIR", "/run/user/1000", TRUE);
+ DO_TEST_CAPS_LATEST("audio-pipewire-best");
+ g_unsetenv("PIPEWIRE_RUNTIME_DIR");
DO_TEST_CAPS_LATEST("audio-sdl-best");
DO_TEST_CAPS_LATEST("audio-spice-best");
DO_TEST_CAPS_LATEST("audio-file-best");
@@ -986,6 +995,9 @@ mymain(void)
DO_TEST_CAPS_LATEST("audio-jack-full");
DO_TEST_CAPS_LATEST("audio-oss-full");
DO_TEST_CAPS_LATEST("audio-pulseaudio-full");
+ g_setenv("PIPEWIRE_RUNTIME_DIR", "/run/user/1000", TRUE);
+ DO_TEST_CAPS_LATEST("audio-pipewire-full");
+ g_unsetenv("PIPEWIRE_RUNTIME_DIR");
DO_TEST_CAPS_LATEST("audio-sdl-full");
DO_TEST_CAPS_LATEST("audio-spice-full");
DO_TEST_CAPS_LATEST("audio-file-full");
--
2.39.3
On Thu, May 11, 2023 at 02:14:51PM +0200, Michal Privoznik wrote: >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, I spent some time thinking about this even after I said "just give it runtime dir" and now I'm wondering, is this not similar to the dbus daemon? When we launch it with a constructed config file for graphics type='dbus' we could also launch pipewire deamon with our config file and let qemu connect to that daemon. I'm not sure what the audio permissions would look like, but it seems like a less messy approach. >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. > >Signed-off-by: Michal Privoznik <mprivozn@redhat.com> >--- > src/qemu/qemu_command.c | 61 +++++++++++++++++++ > .../audio-many-backends.x86_64-latest.args | 2 + > .../qemuxml2argvdata/audio-many-backends.xml | 1 + > .../audio-pipewire-best.x86_64-latest.args | 36 +++++++++++ > .../audio-pipewire-full.x86_64-latest.args | 36 +++++++++++ > .../audio-pipewire-minimal.x86_64-latest.args | 36 +++++++++++ > tests/qemuxml2argvtest.c | 12 ++++ > 7 files changed, 184 insertions(+) > create mode 100644 tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args > create mode 100644 tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args > create mode 100644 tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args > >diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c >index 3a08cac870..b31f4db6b1 100644 >--- a/src/qemu/qemu_command.c >+++ b/src/qemu/qemu_command.c >@@ -7783,6 +7783,60 @@ 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 *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), we can add just the first existing one >+ * (basically mimic get_runtime_dir() logic). >+ */ >+ >+ virCommandAddEnvPass(cmd, "PIPEWIRE_CORE"); >+ virCommandAddEnvPass(cmd, "PIPEWIRE_REMOTE"); >+ >+ 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) >@@ -7889,6 +7943,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); >+ break; >+ > case VIR_DOMAIN_AUDIO_TYPE_LAST: > default: > virReportEnumRangeError(virDomainAudioType, def->type); >diff --git a/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args b/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >index 9caf591daf..13dd55054e 100644 >--- a/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >+++ b/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >@@ -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 \ >+XDG_RUNTIME_DIR=/bad-test-used-env-xdg-runtime-dir \ This should fail the test suite IIRC.
On 5/16/23 11:52, Martin Kletzander wrote: > On Thu, May 11, 2023 at 02:14:51PM +0200, Michal Privoznik wrote: >> 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, > > I spent some time thinking about this even after I said "just give it > runtime dir" and now I'm wondering, is this not similar to the dbus > daemon? When we launch it with a constructed config file for graphics > type='dbus' we could also launch pipewire deamon with our config file > and let qemu connect to that daemon. I'm not sure what the audio > permissions would look like, but it seems like a less messy approach. And how would then this per-domain pipewire daemon talk to the user session daemon that's actually doing all the mixing? I mean, how would sound from a domain get to speakers/headphones? For dbus it's fairly easy to use, because the socket (and config file) is located under configurable location (cfg->dbusStateDir) and with predictable name (domain short name) AND (more importantly) we have virDomainOpenGraphics() API which then handles virt-viewer connection requests. > >> 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. >> >> Signed-off-by: Michal Privoznik <mprivozn@redhat.com> >> --- >> src/qemu/qemu_command.c | 61 +++++++++++++++++++ >> .../audio-many-backends.x86_64-latest.args | 2 + >> .../qemuxml2argvdata/audio-many-backends.xml | 1 + >> .../audio-pipewire-best.x86_64-latest.args | 36 +++++++++++ >> .../audio-pipewire-full.x86_64-latest.args | 36 +++++++++++ >> .../audio-pipewire-minimal.x86_64-latest.args | 36 +++++++++++ >> tests/qemuxml2argvtest.c | 12 ++++ >> 7 files changed, 184 insertions(+) >> create mode 100644 >> tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args >> create mode 100644 >> tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args >> create mode 100644 >> tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args >> >> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c >> index 3a08cac870..b31f4db6b1 100644 >> --- a/src/qemu/qemu_command.c >> +++ b/src/qemu/qemu_command.c >> @@ -7783,6 +7783,60 @@ 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 *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), we can add just the first existing one >> + * (basically mimic get_runtime_dir() logic). >> + */ >> + >> + virCommandAddEnvPass(cmd, "PIPEWIRE_CORE"); >> + virCommandAddEnvPass(cmd, "PIPEWIRE_REMOTE"); >> + >> + 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) >> @@ -7889,6 +7943,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); >> + break; >> + >> case VIR_DOMAIN_AUDIO_TYPE_LAST: >> default: >> virReportEnumRangeError(virDomainAudioType, def->type); >> diff --git >> a/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >> b/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >> index 9caf591daf..13dd55054e 100644 >> --- a/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >> +++ b/tests/qemuxml2argvdata/audio-many-backends.x86_64-latest.args >> @@ -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 \ >> +XDG_RUNTIME_DIR=/bad-test-used-env-xdg-runtime-dir \ > > This should fail the test suite IIRC. I'm failing to see why. The poisoning was done so that things like: virDirCreate("${XGD_RUNTIME_DIR}/something/something"); fail. It's okay if it's in environment. But for everybody's peace of mind, I can squash this in: diff --git i/tests/qemuxml2argvtest.c w/tests/qemuxml2argvtest.c index f29597a19f..37c2ab0826 100644 --- i/tests/qemuxml2argvtest.c +++ w/tests/qemuxml2argvtest.c @@ -1002,7 +1002,9 @@ mymain(void) 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); Michal
On Tue, May 16, 2023 at 12:49:02PM +0200, Michal Prívozník wrote: >On 5/16/23 11:52, Martin Kletzander wrote: >> On Thu, May 11, 2023 at 02:14:51PM +0200, Michal Privoznik wrote: >>> 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, >> >> I spent some time thinking about this even after I said "just give it >> runtime dir" and now I'm wondering, is this not similar to the dbus >> daemon? When we launch it with a constructed config file for graphics >> type='dbus' we could also launch pipewire deamon with our config file >> and let qemu connect to that daemon. I'm not sure what the audio >> permissions would look like, but it seems like a less messy approach. > >And how would then this per-domain pipewire daemon talk to the user >session daemon that's actually doing all the mixing? I mean, how would >sound from a domain get to speakers/headphones? > >For dbus it's fairly easy to use, because the socket (and config file) >is located under configurable location (cfg->dbusStateDir) and with >predictable name (domain short name) AND (more importantly) we have >virDomainOpenGraphics() API which then handles virt-viewer connection >requests. > I'm not sure how pipewire works in this context, my idea was that it would not go through the user's daemon at all, that would only be done for qemu:///session. But maybe my understanding is incomplete (highly probable).
© 2016 - 2024 Red Hat, Inc.