# pipewire-pulse, the PulseAudio protocol on top of PipeWire, for clients that # speak nothing else. Wine's audio driver is one of them. # # The socket moves out of the service user's runtime directory. That directory # is 0700 and the workload runs as a different user, so a client there finds # nothing and plays silently, which is the failure PipeWire's own socket was # moved for. The workload is pointed here with PULSE_SERVER, and nesinit starts # this under a umask that lets a user who does not own the socket open it. This # path is written in nesinit too, and a test there compares the two. # # A drop-in replaces a key of pulse.properties rather than appending to it, so # the default "unix:native" socket is not created as well. pulse.properties = { server.address = [ "unix:/run/pipewire/pulse-native" ] # module-always-sink creates a "Dummy Output" sink whenever no other sink # exists, and while neswire's sink is not up yet that is always. A second # sink is a second default-node candidate, and a game that picks it plays # into nothing. neswire has to be the only sink; see the WirePlumber # drop-in. pulse.cmd.always-sink = false # The root is read-only and every box starts from the same image, so there # is nothing to restore and nowhere to save, which is the reasoning behind # turning off WirePlumber's state hooks. Left on, these are one more place # that could pin a default. pulse.cmd.device-manager = false pulse.cmd.device-restore = false pulse.cmd.stream-restore = false # Wine sizes its whole audio path from this number. At startup its driver # opens a probe stream asking for a 1-frame request size, reads back what # the server grants, which is this floor, and makes the device period ten # times that. Streams then queue three periods. The default floor of 256 # frames therefore becomes a 53 ms period and about 160 ms of audio in # flight, which is desktop playback and not a game being streamed. # # 48 frames (1 ms) makes that period 10 ms, the default period Windows # itself uses, so a game gets the buffering it was written for. A stream # then asks for 480-frame requests, which is what is granted, so the floor # affects the probe and nothing else Wine does. pulse.min.req = 48/48000 # What a client gets when it asks for no particular buffering. The shipped # default is two seconds, which is only harmless where nobody is listening # live. 10 ms requests and 40 ms of target buffer are enough for a client # that does not say. pulse.default.req = 480/48000 pulse.default.tlength = 1920/48000 } # pipewire-pulse.conf raises the minimum node latency to 1024 frames (21.3 ms) # inside a virtual machine, which a box is. That puts every Pulse client, and # therefore Wine, at least 21 ms behind, whatever it asked for. Floor it where # the graph itself is floored instead; see the pipewire.conf drop-in for why # 256 frames. A drop-in appends to this array, and every matching rule applies # in order, so this runs after the shipped rule and wins. pulse.properties.rules = [ { matches = [ { cpu.vm.name = !null } ] actions = { update-props = { pulse.min.quantum = 256/48000 } } } ]