[PATCH 0/3] Introduce pipewire audio backend

This is a resend of patches from earlier: https://listman.redhat.com/archives/libvir-list/2023-May/239860.html Apparently, users want this now. Michal Prívozník (3): conf: Introduce pipewire audio backend qemu: Generate cmd line for pipewire audio backend NEWS: Document pipewire audio backend NEWS.rst | 10 +++ docs/formatdomain.rst | 35 +++++++++- src/conf/domain_conf.c | 70 +++++++++++++++++++ src/conf/domain_conf.h | 12 ++++ src/conf/schemas/domaincommon.rng | 37 ++++++++++ src/qemu/qemu_command.c | 63 +++++++++++++++++ src/qemu/qemu_validate.c | 1 + .../audio-many-backends.x86_64-latest.args | 2 + .../qemuxml2argvdata/audio-many-backends.xml | 1 + .../audio-pipewire-best.x86_64-latest.args | 36 ++++++++++ .../qemuxml2argvdata/audio-pipewire-best.xml | 43 ++++++++++++ .../audio-pipewire-full.x86_64-latest.args | 36 ++++++++++ .../qemuxml2argvdata/audio-pipewire-full.xml | 43 ++++++++++++ .../audio-pipewire-minimal.x86_64-latest.args | 36 ++++++++++ .../audio-pipewire-minimal.xml | 36 ++++++++++ tests/qemuxml2argvtest.c | 14 ++++ .../audio-many-backends.x86_64-latest.xml | 1 + .../audio-pipewire-best.x86_64-latest.xml | 46 ++++++++++++ .../audio-pipewire-full.x86_64-latest.xml | 46 ++++++++++++ .../audio-pipewire-minimal.x86_64-latest.xml | 39 +++++++++++ tests/qemuxml2xmltest.c | 3 + 21 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 tests/qemuxml2argvdata/audio-pipewire-best.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/audio-pipewire-best.xml create mode 100644 tests/qemuxml2argvdata/audio-pipewire-full.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/audio-pipewire-full.xml create mode 100644 tests/qemuxml2argvdata/audio-pipewire-minimal.x86_64-latest.args create mode 100644 tests/qemuxml2argvdata/audio-pipewire-minimal.xml create mode 100644 tests/qemuxml2xmloutdata/audio-pipewire-best.x86_64-latest.xml create mode 100644 tests/qemuxml2xmloutdata/audio-pipewire-full.x86_64-latest.xml create mode 100644 tests/qemuxml2xmloutdata/audio-pipewire-minimal.x86_64-latest.xml -- 2.41.0

QEMU gained support for PipeWire audio backend (see QEMU commit of v8.0.0-403-gc2d3d1c294). Its configuration knobs are basically the same as pulseaudio's, except for PA's server name. Therefore, a lot of code is copied over from pulseadio and fixed by s/Pulse/Pipewire/ or s/pulseaudio/pipewire/. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- docs/formatdomain.rst | 35 +++++++++- src/conf/domain_conf.c | 70 +++++++++++++++++++ src/conf/domain_conf.h | 12 ++++ src/conf/schemas/domaincommon.rng | 37 ++++++++++ src/qemu/qemu_command.c | 2 + src/qemu/qemu_validate.c | 1 + .../qemuxml2argvdata/audio-pipewire-best.xml | 43 ++++++++++++ .../qemuxml2argvdata/audio-pipewire-full.xml | 43 ++++++++++++ .../audio-pipewire-minimal.xml | 36 ++++++++++ .../audio-pipewire-best.x86_64-latest.xml | 46 ++++++++++++ .../audio-pipewire-full.x86_64-latest.xml | 46 ++++++++++++ .../audio-pipewire-minimal.x86_64-latest.xml | 39 +++++++++++ tests/qemuxml2xmltest.c | 3 + 13 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 tests/qemuxml2argvdata/audio-pipewire-best.xml create mode 100644 tests/qemuxml2argvdata/audio-pipewire-full.xml create mode 100644 tests/qemuxml2argvdata/audio-pipewire-minimal.xml create mode 100644 tests/qemuxml2xmloutdata/audio-pipewire-best.x86_64-latest.xml create mode 100644 tests/qemuxml2xmloutdata/audio-pipewire-full.x86_64-latest.xml create mode 100644 tests/qemuxml2xmloutdata/audio-pipewire-minimal.x86_64-latest.xml diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst index 459815d2b5..8c318d2539 100644 --- a/docs/formatdomain.rst +++ b/docs/formatdomain.rst @@ -7368,7 +7368,8 @@ to the guest sound device. ``type`` The required ``type`` attribute specifies audio backend type. Currently, the supported values are ``none``, ``alsa``, ``coreaudio``, - ``dbus``, ``jack``, ``oss``, ``pulseaudio``, ``sdl``, ``spice``, ``file``. + ``dbus``, ``jack``, ``oss``, ``pipewire``, ``pulseaudio``, ``sdl``, + ``spice``, ``file``. ``id`` Integer id of the audio device. Must be greater than 0. @@ -7452,7 +7453,7 @@ following environment variables: * ``QEMU_AUDIO_DRV`` Valid values are ``pa``, ``none``, ``alsa``, ``coreaudio``, ``jack``, ``oss``, - ``sdl``, ``spice`` or ``wav``. + ``pipewire``, ``sdl``, ``spice`` or ``wav``. None audio backend ^^^^^^^^^^^^^^^^^^ @@ -7601,6 +7602,36 @@ and ``<output>`` elements :since:`Since 6.7.0, bhyve; Since 7.2.0, qemu` +PipeWire audio backend +^^^^^^^^^^^^^^^^^^^^^^ + +The ``pipewire`` audio backend delegates to a PipeWire daemon audio input and +output. + +The following additional attributes are permitted on the ``<input/>`` and +``<output/>`` elements: + +* ``name`` + + The sink/source name to use + +* ``streamName`` + + The name to identify the stream associated with the VM + +* ``latency`` + + Desired latency for the server to target in microseconds + +:: + + <audio id="1" type="pipewire"> + <input name="fish" streamName="food" latency="100"/> + <output name="fish" streamName="food" latency="200"/> + </audio> + +:since:`Since 9.4.0, qemu` + PulseAudio audio backend ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index fa97def9f7..0af854e294 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -791,6 +791,7 @@ VIR_ENUM_IMPL(virDomainAudioType, "spice", "file", "dbus", + "pipewire", ); VIR_ENUM_IMPL(virDomainAudioSDLDriver, @@ -3230,6 +3231,13 @@ virDomainAudioIOPulseAudioFree(virDomainAudioIOPulseAudio *def) g_free(def->streamName); } +static void +virDomainAudioIOPipewireAudioFree(virDomainAudioIOPipewireAudio *def) +{ + g_free(def->name); + g_free(def->streamName); +} + void virDomainAudioDefFree(virDomainAudioDef *def) { @@ -3274,6 +3282,11 @@ virDomainAudioDefFree(virDomainAudioDef *def) g_free(def->backend.file.path); break; + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: + virDomainAudioIOPipewireAudioFree(&def->backend.pipewire.input); + virDomainAudioIOPipewireAudioFree(&def->backend.pipewire.output); + break; + case VIR_DOMAIN_AUDIO_TYPE_DBUS: case VIR_DOMAIN_AUDIO_TYPE_LAST: break; @@ -11920,6 +11933,21 @@ virDomainAudioSDLParse(virDomainAudioIOSDL *def, } +static int +virDomainAudioPipewireAudioParse(virDomainAudioIOPipewireAudio *def, + xmlNodePtr node) +{ + def->name = virXMLPropString(node, "name"); + def->streamName = virXMLPropString(node, "streamName"); + + if (virXMLPropUInt(node, "latency", 10, VIR_XML_PROP_NONE, + &def->latency) < 0) + return -1; + + return 0; +} + + static virDomainAudioDef * virDomainAudioDefParseXML(virDomainXMLOption *xmlopt G_GNUC_UNUSED, xmlNodePtr node, @@ -12050,6 +12078,15 @@ virDomainAudioDefParseXML(virDomainXMLOption *xmlopt G_GNUC_UNUSED, case VIR_DOMAIN_AUDIO_TYPE_DBUS: break; + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: + if (inputNode && + virDomainAudioPipewireAudioParse(&def->backend.pipewire.input, inputNode) < 0) + goto error; + if (outputNode && + virDomainAudioPipewireAudioParse(&def->backend.pipewire.output, outputNode) < 0) + goto error; + break; + case VIR_DOMAIN_AUDIO_TYPE_LAST: default: virReportEnumRangeError(virDomainAudioType, def->type); @@ -24833,6 +24870,18 @@ virDomainAudioSDLFormat(virDomainAudioIOSDL *def, } +static void +virDomainAudioPipewireAudioFormat(virDomainAudioIOPipewireAudio *def, + virBuffer *buf) +{ + virBufferEscapeString(buf, " name='%s'", def->name); + virBufferEscapeString(buf, " streamName='%s'", def->streamName); + if (def->latency) + virBufferAsprintf(buf, " latency='%u'", def->latency); + +} + + static int virDomainAudioDefFormat(virBuffer *buf, virDomainAudioDef *def) @@ -24915,6 +24964,11 @@ virDomainAudioDefFormat(virBuffer *buf, case VIR_DOMAIN_AUDIO_TYPE_DBUS: break; + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: + virDomainAudioPipewireAudioFormat(&def->backend.pipewire.input, &inputBuf); + virDomainAudioPipewireAudioFormat(&def->backend.pipewire.output, &outputBuf); + break; + case VIR_DOMAIN_AUDIO_TYPE_LAST: default: virReportEnumRangeError(virDomainAudioType, def->type); @@ -29267,6 +29321,16 @@ virDomainAudioIOSDLIsEqual(virDomainAudioIOSDL *this, } +static bool +virDomainAudioIOPipewireAudioIsEqual(virDomainAudioIOPipewireAudio *this, + virDomainAudioIOPipewireAudio *that) +{ + return STREQ_NULLABLE(this->name, that->name) && + STREQ_NULLABLE(this->streamName, that->streamName) && + this->latency == that->latency; +} + + static bool virDomainAudioBackendIsEqual(virDomainAudioDef *this, virDomainAudioDef *that) @@ -29327,6 +29391,12 @@ virDomainAudioBackendIsEqual(virDomainAudioDef *this, case VIR_DOMAIN_AUDIO_TYPE_FILE: return STREQ_NULLABLE(this->backend.file.path, that->backend.file.path); + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: + return virDomainAudioIOPipewireAudioIsEqual(&this->backend.pipewire.input, + &that->backend.pipewire.input) && + virDomainAudioIOPipewireAudioIsEqual(&this->backend.pipewire.output, + &that->backend.pipewire.output); + case VIR_DOMAIN_AUDIO_TYPE_DBUS: case VIR_DOMAIN_AUDIO_TYPE_LAST: default: diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 7c0e017038..c50ce6e5e2 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1579,6 +1579,7 @@ typedef enum { VIR_DOMAIN_AUDIO_TYPE_SPICE, VIR_DOMAIN_AUDIO_TYPE_FILE, VIR_DOMAIN_AUDIO_TYPE_DBUS, + VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE, VIR_DOMAIN_AUDIO_TYPE_LAST } virDomainAudioType; @@ -1654,6 +1655,13 @@ struct _virDomainAudioIOSDL { unsigned int bufferCount; }; +typedef struct _virDomainAudioIOPipewireAudio virDomainAudioIOPipewireAudio; +struct _virDomainAudioIOPipewireAudio { + char *name; + char *streamName; + unsigned int latency; +}; + struct _virDomainAudioDef { virDomainAudioType type; @@ -1697,6 +1705,10 @@ struct _virDomainAudioDef { struct { char *path; } file; + struct { + virDomainAudioIOPipewireAudio input; + virDomainAudioIOPipewireAudio output; + } pipewire; } backend; }; diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng index a26986b5ce..032ff042da 100644 --- a/src/conf/schemas/domaincommon.rng +++ b/src/conf/schemas/domaincommon.rng @@ -5186,6 +5186,26 @@ <ref name="audiocommonchild"/> </define> + <define name="audiopipewire"> + <ref name="audiocommonattr"/> + <optional> + <attribute name="name"> + <data type="string"/> + </attribute> + </optional> + <optional> + <attribute name="streamName"> + <data type="string"/> + </attribute> + </optional> + <optional> + <attribute name="latency"> + <ref name="uint32"/> + </attribute> + </optional> + <ref name="audiocommonchild"/> + </define> + <define name="audiopulseaudio"> <ref name="audiocommonattr"/> <optional> @@ -5361,6 +5381,23 @@ </optional> </interleave> </group> + <group> + <attribute name="type"> + <value>pipewire</value> + </attribute> + <interleave> + <optional> + <element name="input"> + <ref name="audiopipewire"/> + </element> + </optional> + <optional> + <element name="output"> + <ref name="audiopipewire"/> + </element> + </optional> + </interleave> + </group> <group> <attribute name="type"> <value>pulseaudio</value> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 2674dd6959..173639277c 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -142,6 +142,7 @@ qemuAudioDriverTypeToString(virDomainAudioType type) case VIR_DOMAIN_AUDIO_TYPE_SDL: case VIR_DOMAIN_AUDIO_TYPE_SPICE: case VIR_DOMAIN_AUDIO_TYPE_DBUS: + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: case VIR_DOMAIN_AUDIO_TYPE_LAST: break; } @@ -7937,6 +7938,7 @@ qemuBuildAudioCommandLineArg(virCommand *cmd, case VIR_DOMAIN_AUDIO_TYPE_DBUS: break; + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: case VIR_DOMAIN_AUDIO_TYPE_LAST: default: virReportEnumRangeError(virDomainAudioType, def->type); diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c index 93df9e4c8e..2ced1ef9a7 100644 --- a/src/qemu/qemu_validate.c +++ b/src/qemu/qemu_validate.c @@ -4403,6 +4403,7 @@ qemuValidateDomainDeviceDefAudio(virDomainAudioDef *audio, case VIR_DOMAIN_AUDIO_TYPE_PULSEAUDIO: case VIR_DOMAIN_AUDIO_TYPE_SDL: case VIR_DOMAIN_AUDIO_TYPE_FILE: + case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: break; case VIR_DOMAIN_AUDIO_TYPE_SPICE: diff --git a/tests/qemuxml2argvdata/audio-pipewire-best.xml b/tests/qemuxml2argvdata/audio-pipewire-best.xml new file mode 100644 index 0000000000..e71688d25b --- /dev/null +++ b/tests/qemuxml2argvdata/audio-pipewire-best.xml @@ -0,0 +1,43 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='cdrom'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/cdrom'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='pipewire' timerPeriod='50'> + <input mixingEngine='yes' fixedSettings='yes' voices='1' bufferLength='200' name='fish'> + <settings frequency='44100' channels='2' format='s16'/> + </input> + <output mixingEngine='yes' fixedSettings='yes' voices='2' bufferLength='200' name='fish'> + <settings frequency='22050' channels='4' format='f32'/> + </output> + </audio> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvdata/audio-pipewire-full.xml b/tests/qemuxml2argvdata/audio-pipewire-full.xml new file mode 100644 index 0000000000..5811eef6d4 --- /dev/null +++ b/tests/qemuxml2argvdata/audio-pipewire-full.xml @@ -0,0 +1,43 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='cdrom'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/cdrom'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='pipewire'> + <input mixingEngine='yes' fixedSettings='yes' voices='1' bufferLength='100' name='fish' streamName='food' latency='100'> + <settings frequency='44100' channels='2' format='s16'/> + </input> + <output mixingEngine='yes' fixedSettings='yes' voices='2' bufferLength='200' name='fish' streamName='food' latency='200'> + <settings frequency='22050' channels='4' format='f32'/> + </output> + </audio> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvdata/audio-pipewire-minimal.xml b/tests/qemuxml2argvdata/audio-pipewire-minimal.xml new file mode 100644 index 0000000000..2085225722 --- /dev/null +++ b/tests/qemuxml2argvdata/audio-pipewire-minimal.xml @@ -0,0 +1,36 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='cdrom'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/cdrom'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='pipewire'/> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2xmloutdata/audio-pipewire-best.x86_64-latest.xml b/tests/qemuxml2xmloutdata/audio-pipewire-best.x86_64-latest.xml new file mode 100644 index 0000000000..169e052366 --- /dev/null +++ b/tests/qemuxml2xmloutdata/audio-pipewire-best.x86_64-latest.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='cdrom'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/cdrom'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='pipewire' timerPeriod='50'> + <input mixingEngine='yes' fixedSettings='yes' voices='1' bufferLength='200' name='fish'> + <settings frequency='44100' channels='2' format='s16'/> + </input> + <output mixingEngine='yes' fixedSettings='yes' voices='2' bufferLength='200' name='fish'> + <settings frequency='22050' channels='4' format='f32'/> + </output> + </audio> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2xmloutdata/audio-pipewire-full.x86_64-latest.xml b/tests/qemuxml2xmloutdata/audio-pipewire-full.x86_64-latest.xml new file mode 100644 index 0000000000..728f25584f --- /dev/null +++ b/tests/qemuxml2xmloutdata/audio-pipewire-full.x86_64-latest.xml @@ -0,0 +1,46 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='cdrom'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/cdrom'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='pipewire'> + <input mixingEngine='yes' fixedSettings='yes' voices='1' bufferLength='100' name='fish' streamName='food' latency='100'> + <settings frequency='44100' channels='2' format='s16'/> + </input> + <output mixingEngine='yes' fixedSettings='yes' voices='2' bufferLength='200' name='fish' streamName='food' latency='200'> + <settings frequency='22050' channels='4' format='f32'/> + </output> + </audio> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2xmloutdata/audio-pipewire-minimal.x86_64-latest.xml b/tests/qemuxml2xmloutdata/audio-pipewire-minimal.x86_64-latest.xml new file mode 100644 index 0000000000..7fc0a65a68 --- /dev/null +++ b/tests/qemuxml2xmloutdata/audio-pipewire-minimal.x86_64-latest.xml @@ -0,0 +1,39 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='cdrom'/> + </os> + <cpu mode='custom' match='exact' check='none'> + <model fallback='forbid'>qemu64</model> + </cpu> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <source dev='/dev/cdrom'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' target='0' unit='0'/> + </disk> + <controller type='usb' index='0' model='piix3-uhci'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <audio id='1' type='pipewire'/> + <memballoon model='none'/> + </devices> +</domain> diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 72f976358f..1010b68ebc 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -957,6 +957,7 @@ mymain(void) DO_TEST_CAPS_LATEST("audio-coreaudio-minimal"); DO_TEST_CAPS_LATEST("audio-oss-minimal"); DO_TEST_CAPS_LATEST("audio-pulseaudio-minimal"); + DO_TEST_CAPS_LATEST("audio-pipewire-minimal"); DO_TEST_CAPS_LATEST("audio-sdl-minimal"); DO_TEST_CAPS_LATEST("audio-spice-minimal"); DO_TEST_CAPS_LATEST("audio-file-minimal"); @@ -967,6 +968,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"); @@ -978,6 +980,7 @@ 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"); -- 2.41.0

On Tue, Nov 07, 2023 at 13:55:22 +0100, Michal Privoznik wrote:
QEMU gained support for PipeWire audio backend (see QEMU commit of v8.0.0-403-gc2d3d1c294). Its configuration knobs are basically the same as pulseaudio's, except for PA's server name. Therefore, a lot of code is copied over from pulseadio and fixed by s/Pulse/Pipewire/ or s/pulseaudio/pipewire/.
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> ---
[...]
+PipeWire audio backend +^^^^^^^^^^^^^^^^^^^^^^ + +The ``pipewire`` audio backend delegates to a PipeWire daemon audio input and +output. + +The following additional attributes are permitted on the ``<input/>`` and +``<output/>`` elements: + +* ``name`` + + The sink/source name to use + +* ``streamName`` + + The name to identify the stream associated with the VM + +* ``latency`` + + Desired latency for the server to target in microseconds + +:: + + <audio id="1" type="pipewire"> + <input name="fish" streamName="food" latency="100"/> + <output name="fish" streamName="food" latency="200"/> + </audio> + +:since:`Since 9.4.0, qemu`
This is a bit stale ;)
+static int +virDomainAudioPipewireAudioParse(virDomainAudioIOPipewireAudio *def, + xmlNodePtr node) +{ + def->name = virXMLPropString(node, "name"); + def->streamName = virXMLPropString(node, "streamName"); + + if (virXMLPropUInt(node, "latency", 10, VIR_XML_PROP_NONE, + &def->latency) < 0)
You probably want VIR_XML_PROP_NONZERO, as ...
+ return -1; + + return 0; +}
[...]
+static void +virDomainAudioPipewireAudioFormat(virDomainAudioIOPipewireAudio *def, + virBuffer *buf) +{ + virBufferEscapeString(buf, " name='%s'", def->name); + virBufferEscapeString(buf, " streamName='%s'", def->streamName); + if (def->latency)
... 0 is considered as default.
+ virBufferAsprintf(buf, " latency='%u'", def->latency); + +}
[...]
@@ -29267,6 +29321,16 @@ virDomainAudioIOSDLIsEqual(virDomainAudioIOSDL *this, }
+static bool +virDomainAudioIOPipewireAudioIsEqual(virDomainAudioIOPipewireAudio *this, + virDomainAudioIOPipewireAudio *that) +{ + return STREQ_NULLABLE(this->name, that->name) && + STREQ_NULLABLE(this->streamName, that->streamName) && + this->latency == that->latency;
'this' and 'that' share too much of a prefix to be easily visually distinguishible .... but ...
+} + + static bool virDomainAudioBackendIsEqual(virDomainAudioDef *this, virDomainAudioDef *that)
.... it seems to be prior art, unfortunately.
@@ -29327,6 +29391,12 @@ virDomainAudioBackendIsEqual(virDomainAudioDef *this, case VIR_DOMAIN_AUDIO_TYPE_FILE: return STREQ_NULLABLE(this->backend.file.path, that->backend.file.path);
+ case VIR_DOMAIN_AUDIO_TYPE_PIPEWIRE: + return virDomainAudioIOPipewireAudioIsEqual(&this->backend.pipewire.input, + &that->backend.pipewire.input) && + virDomainAudioIOPipewireAudioIsEqual(&this->backend.pipewire.output, + &that->backend.pipewire.output);
The alignment here is rather funky. Did the mailing list mess this up?
+ case VIR_DOMAIN_AUDIO_TYPE_DBUS: case VIR_DOMAIN_AUDIO_TYPE_LAST: default:
With the default of the latency properly handled: Reviewed-by: Peter Krempa <pkrempa@redhat.com>

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> --- 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 | 14 +++++ .../audio-many-backends.x86_64-latest.xml | 1 + 8 files changed, 187 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 173639277c..a7bb999528 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -7833,6 +7833,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) @@ -7939,6 +7993,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 4c6c925ccd..0bdc75689f 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 \ +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"}' \ 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..d94bd6a09e --- /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 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 index 0000000000..a779e5ad79 --- /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 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","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..90951a3219 --- /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 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 diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 4fda68a4ce..2707f56024 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -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,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"); @@ -928,11 +937,16 @@ 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"); + 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); diff --git a/tests/qemuxml2xmloutdata/audio-many-backends.x86_64-latest.xml b/tests/qemuxml2xmloutdata/audio-many-backends.x86_64-latest.xml index c681784526..1659723f91 100644 --- a/tests/qemuxml2xmloutdata/audio-many-backends.x86_64-latest.xml +++ b/tests/qemuxml2xmloutdata/audio-many-backends.x86_64-latest.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'/> -- 2.41.0

On Tue, Nov 07, 2023 at 13:55:23 +0100, 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, 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: Peter Krempa <pkrempa@redhat.com> but note I didn't too deeply investigate any potential side effects of the env variable stuff.

Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- NEWS.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index 08e5a3d04a..f73dd867d5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,16 @@ v9.10.0 (unreleased) * **New features** + * Introduce pipewire audio backend + + The QEMU hypervisor driver now allows setting ``pipewire`` backend for + ``<audio/>`` device. Please note, that for domains under system URI it + might be necessary to set various environment variables (e.g. + ``PIPEWIRE_REMOTE`` and/or ``PIPEWIRE_RUNTIME_DIR``) to point QEMU process + to a PipeWire daemon running under a non-privileged user. See `knowledge base + <https://libvirt.org/kbase/qemu-passthrough-security.html#xml-document-additions>`_ + for more information on passing additional environment variables. + * **Improvements** * **Bug fixes** -- 2.41.0

On Tue, Nov 07, 2023 at 13:55:24 +0100, Michal Privoznik wrote:
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- NEWS.rst | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/NEWS.rst b/NEWS.rst index 08e5a3d04a..f73dd867d5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,16 @@ v9.10.0 (unreleased)
* **New features**
+ * Introduce pipewire audio backend + + The QEMU hypervisor driver now allows setting ``pipewire`` backend for + ``<audio/>`` device. Please note, that for domains under system URI it + might be necessary to set various environment variables (e.g. + ``PIPEWIRE_REMOTE`` and/or ``PIPEWIRE_RUNTIME_DIR``) to point QEMU process + to a PipeWire daemon running under a non-privileged user. See `knowledge base + <https://libvirt.org/kbase/qemu-passthrough-security.html#xml-document-additions>`_ + for more information on passing additional environment variables.
This seems to imply that you must set the XML env variable overrides, but the code seems to do the passthrough from users environment. I think it should be made obvious which is the case and also most likely a better idea is to clarify it in formatdomain.rst to point users to what additional config they need.

On 11/16/23 14:30, Peter Krempa wrote:
On Tue, Nov 07, 2023 at 13:55:24 +0100, Michal Privoznik wrote:
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- NEWS.rst | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/NEWS.rst b/NEWS.rst index 08e5a3d04a..f73dd867d5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,16 @@ v9.10.0 (unreleased)
* **New features**
+ * Introduce pipewire audio backend + + The QEMU hypervisor driver now allows setting ``pipewire`` backend for + ``<audio/>`` device. Please note, that for domains under system URI it + might be necessary to set various environment variables (e.g. + ``PIPEWIRE_REMOTE`` and/or ``PIPEWIRE_RUNTIME_DIR``) to point QEMU process + to a PipeWire daemon running under a non-privileged user. See `knowledge base + <https://libvirt.org/kbase/qemu-passthrough-security.html#xml-document-additions>`_ + for more information on passing additional environment variables.
This seems to imply that you must set the XML env variable overrides, but the code seems to do the passthrough from users environment. I think it should be made obvious which is the case and also most likely a better idea is to clarify it in formatdomain.rst to point users to what additional config they need.
Maybe the wording is bad, but basically: 1) for qemu:///session, the virtqemud is started automagically, i.e. after pipewire daemon was started and thus one of the env vars is already present in the environment and passed through. Mind you, pipewire daemon is also per user. 2) for qemu:///system, the virtqemud is going to be started by init system and thus no env var is going to be set for it. This is different from pulseaudio. In this case, you need to set the env var explicitly to tell pipewire client (running inside of qemu) where to connect. I'm not sure how to express this in the NEWS. Though, there is some discussion in linked gitlab issue which suggest that env var passing doesn't work. But I need to investigate. Michal

On Wed, Nov 22, 2023 at 12:37:58 +0100, Michal Prívozník wrote:
On 11/16/23 14:30, Peter Krempa wrote:
On Tue, Nov 07, 2023 at 13:55:24 +0100, Michal Privoznik wrote:
Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- NEWS.rst | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/NEWS.rst b/NEWS.rst index 08e5a3d04a..f73dd867d5 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -17,6 +17,16 @@ v9.10.0 (unreleased)
* **New features**
+ * Introduce pipewire audio backend + + The QEMU hypervisor driver now allows setting ``pipewire`` backend for + ``<audio/>`` device. Please note, that for domains under system URI it + might be necessary to set various environment variables (e.g. + ``PIPEWIRE_REMOTE`` and/or ``PIPEWIRE_RUNTIME_DIR``) to point QEMU process + to a PipeWire daemon running under a non-privileged user. See `knowledge base + <https://libvirt.org/kbase/qemu-passthrough-security.html#xml-document-additions>`_ + for more information on passing additional environment variables.
This seems to imply that you must set the XML env variable overrides, but the code seems to do the passthrough from users environment. I think it should be made obvious which is the case and also most likely a better idea is to clarify it in formatdomain.rst to point users to what additional config they need.
Maybe the wording is bad, but basically:
1) for qemu:///session, the virtqemud is started automagically, i.e. after pipewire daemon was started and thus one of the env vars is already present in the environment and passed through. Mind you, pipewire daemon is also per user.
2) for qemu:///system, the virtqemud is going to be started by init system and thus no env var is going to be set for it. This is different from pulseaudio. In this case, you need to set the env var explicitly to tell pipewire client (running inside of qemu) where to connect.
If this is the expected usage then use of the qemu:env XML namespace is not what we should suggest. Instead the definition should have explicit XML elements in the main schema to configure these variables.
I'm not sure how to express this in the NEWS.
Ideally the NEWS should not contain information about how to configure a feature, that's the job of the XML documentation. Drop anything about how to set it up and link to formatdomain.html instead.
participants (3)
-
Michal Privoznik
-
Michal Prívozník
-
Peter Krempa