LIBPIPEWIRE-MODULE-PROTOCOL-PULSE(7)
πΈ libpipewire-module-protocol-pulse πΈ
okay bestie, so this is a page from the Miscellaneous Information Manual β say hi to libpipewire-module-protocol-pulse(7). she's long. she's iconic. let's get into it π
β¨ NAME β¨
libpipewire-module-protocol-pulse β Protocol Pulse π«Ά
π DESCRIPTION π
okay so boom: this module implements a whole entire PulseAudio server on top of PipeWire. like, the entire thing. no cap.
and listen β this is only the server implementation. the clients are expected to use the original PulseAudio client library. which is literally the whole point, because that gives you a high level of compatibility with existing applications. fr fr, all the usual PulseAudio tools β like pavucontrol, pactl, pamon, paplay β should just keep working exactly like they did before. they don't even know. main character energy β¨
this module is usually loaded as part of a standalone pipewire process, called pipewire-pulse, with the pipewire-pulse.conf config file. iykyk.
and one more thing bestie β the pulse server implements a sample cache that is otherwise not available in PipeWire. yeah. that one's special. π
π MODULE NAME π
libpipewire-module-protocol-pulse
π MODULE OPTIONS π
the module arguments can be the contents of the pulse.properties, but ngl it's recommended to make a separate pulse.properties section in the config file so that overrides can be done. do it the polite way, bestie. π
π¦ PULSE.PROPERTIES π¦
a config section with server properties can be given. here's the vibe:
# ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf
pulse.properties = {
# the addresses this server listens on
server.address = [
"unix:native"
#"unix:/tmp/something" # absolute paths may be used
#"tcp:4713" # IPv4 and IPv6 on all addresses
#"tcp:[::]:9999" # IPv6 on all addresses
#"tcp:127.0.0.1:8888" # IPv4 on a single address
#
#{ address = "tcp:4713" # address
# max-clients = 64 # maximum number of clients
# listen-backlog = 32 # backlog in the server listen queue
# client.access = "restricted" # permissions for clients
#}
]
#server.dbus-name = "org.pulseaudio.Server"
#pulse.allow-module-loading = true
#pulse.min.req = 256/48000 # 5.3ms
#pulse.default.req = 960/48000 # 20 milliseconds
#pulse.min.frag = 256/48000 # 5.3ms
#pulse.default.frag = 96000/48000 # 2 seconds
#pulse.default.tlength = 96000/48000 # 2 seconds
#pulse.min.quantum = 256/48000 # 5.3ms
#pulse.default.format = F32
#pulse.default.position = [ FL FR ]
#pulse.idle.timeout = 0
}
pulse.properties.rules = [
{ matches = [ { cpu.vm.name = !null } ]
actions = {
update-props = {
# These overrides are only applied when running in a vm.
pulse.min.quantum = 1024/48000 # 22ms
}
}
}
]
π Connection options π
...
server.address = [
"unix:native"
# "tcp:4713"
]
...
these are the addresses the server listens on when starting. uncomment the tcp:4713 entry to also make the server listen on a tcp socket. that's equivalent to loading libpipewire-module-native-protocol-tcp, btw. β¨
there's also a slightly more verbose syntax with more options, if you're feeling extra:
....
server.address = [
{ address = "tcp:4713" # address
max-clients = 64 # maximum number of clients
listen-backlog = 32 # backlog in the server listen queue
client.access = "restricted" # permissions for clients
}
....
use client.access to use one of the access methods to restrict the permissions given to clients connected via this address. π§
by default network access is given the "restricted" permissions. the session manager is the one responsible for assigning permission to clients with restricted permissions (usually read-only permissions). so like, don't be surprised, that's just how it is. π
server.dbus-name="org.pulseaudio.Server"β the DBus name to reserve for the server. if you have multiple servers, you might want to change the name. two servers, one name? not the move, bestie. πpulse.allow-module-loading=trueβ by default, clients are allowed to load and unload modules. you can disable this feature with this option. (yes, that's the default. yes, that's a choice. it's fine, it's configurable.)
π§ Playback buffering options π§
pulse.min.req=256/48000# 5.3ms β the minimum amount of data to request for clients. the client requested values will be clamped to this value. lowering this value together withtlengthcan decrease latency if the client wants this, but it will increase CPU overhead. trade-offs, bestie, always trade-offs.pulse.default.req=960/48000# 20 milliseconds β the default amount of data to request for clients. if the client does not specify any particular value, this default will be used. lowering this value together withtlengthcan decrease latency but increase CPU overhead. same deal as above, just the fallback one.pulse.default.tlength=96000/48000# 2 seconds β the target amount of data to buffer on the server side. if the client did not specify a value, this default will be used. lower values can decrease the latency. β‘
πΈ Record buffering options πΈ
pulse.min.frag=256/48000# 5.3ms β the minimum allowed size of the capture buffer before it is sent to a client. the requested value of the client will be clamped to this. lowering this value can reduce latency at the expense of more CPU usage. it's giving "pick your poison" but make it configurable. πpulse.default.frag=96000/48000# 2 seconds β the default size of the capture buffer before it is sent to a client. if the client did not specify any value, this default will be used. lowering this value can reduce latency at the expense of more CPU usage. yeah. same energy.
β±οΈ Scheduling options β±οΈ
pulse.min.quantum=256/48000# 5.3ms β the minimum quantum (buffer size in samples) to use for pulseaudio clients. this value is calculated based on the frag and req/tlength for record and playback streams respectively, and then clamped to this value to ensure no pulseaudio client asks for too small quantums. lowering this value might decrease latency at the expense of more CPU usage. she said "no tiny quantums in this house". π«Ά
π¨ Format options π¨
pulse.default.format=F32β some modules will default to this format when no other format was given. this is equivalent to the PulseAudiodefault-sample-formatoption in/etc/pulse/daemon.conf. β¨pulse.default.position=[ FL FR ]β some modules will default to this channelmap (with its number of channels). this is equivalent to the PulseAudiodefault-sample-channelsanddefault-channel-mapoptions in/etc/pulse/daemon.conf. ποΈ
π Quirk options π
pulse.fix.format="S16LE"β when a stream uses theFIX_FORMATflag, fixate the format to this value. normally the format would be fixed to the sink/source that the stream connects to. when an invalid format (nullor"") is set, theFIX_FORMATflag is ignored.pulse.fix.rate=48000β when a stream uses theFIX_RATEflag, fixate the sample rate to this value. normally the rate would be fixed to the sink/source that the stream connects to. when a0rate is set, theFIX_RATEflag is ignored.pulse.fix.position="[ FL FR ]"β when a stream uses theFIX_CHANNELSflag, fixate the channels to this value. normally the channels would be fixed to the sink/source that the stream connects to. when an invalid position (nullor"") is set, theFIX_CHANNELSflag is ignored.pulse.idle.timeout=0β okay so some clients are not sending data when they should and cause underruns. π when setting this option such clients will be set to paused if they underrun for the given amount of seconds. this makes sure that sinks can suspend and save battery power. when the client resumes, it will unpause again. a value of0disables this feature. so0= feature off, periodt.
π COMMAND EXECUTION π
as part of the server startup sequence, a set of commands can be executed. currently, this can be used to load additional modules into the server. slay. π
# Extra commands can be executed here.
# load-module : loads a module with args and flags
# args = "<module-name> <module-args>"
# ( flags = [ "no-fail" ] )
# ( condition = [ { <key1> = <value1>, ... } ... ] )
# conditions will check the pulse.properties key/values.
pulse.cmd = [
{ cmd = "load-module" args = "module-always-sink" flags = [ ] }
#{ cmd = "load-module" args = "module-switch-on-connect" condition = [ { pulse.cmd.switch-on-connect = true } ]
#{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] }
]
π₯ DYNAMIC PROPERTIES π₯
the pulse.properties can be dynamically updated with rules. it supports an update-props action. the matches will be performed on the values in context.properties. ooooh fancy, right? π
pulse.properties.rules = [
{ matches = [ { cpu.vm.name = !null } ]
actions = {
update-props = {
# These overrides are only applied when running in a vm.
pulse.min.quantum = 1024/48000 # 22ms
}
}
}
]
in the above example, when running in a VM, the rule will override the properties in pulse.properties with the given values. this might be interesting because VMs usually can't support the low latency settings that are possible on real hardware. real ones know π
π§ STREAM SETTINGS AND RULES π§
streams created by module-protocol-pulse will use the stream.properties section and stream.rules sections as usual. nothing weird, nothing extra. just the standard treatment. β¨
okay bestie, that's only part one β and there is SO much more coming, so don't go anywhere. πβ¨
π APPLICATION SETTINGS (RULES) π
The pulse protocol module supports generic config rules, bestie. It supports a pulse.rules section with a quirks and an update-props action. These rules operate on the client properties (not the stream properties, peep the section above π).
# ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf
pulse.rules = [
{
# skype does not want to use devices that don't have an S16 sample format.
matches = [
{ application.process.binary = "teams" }
{ application.process.binary = "teams-insiders" }
{ application.process.binary = "skypeforlinux" }
]
actions = { quirks = [ force-s16-info ] }
}
{
# speech dispatcher asks for too small latency and then underruns.
matches = [ { application.name = "~speech-dispatcher*" } ]
actions = {
update-props = {
pulse.min.req = 1024/48000 # 21ms
pulse.min.quantum = 1024/48000 # 21ms
pulse.idle.timeout = 5 # pause after 5 seconds of underrun
}
}
}
]
π Quirks
The quirks action takes an array of quirks to apply for the client. It's giving β¨vibesβ¨-based patching, fr fr.
force-s16-infomakes the sink and source introspect code pretend that the sample format isS16(16 bits) samples. Some application refuse the sink/source if this is not the case. Like, they're just dramatic about it. πremove-capture-dont-moveRemoves theDONT_MOVEflag on capture streams. Some applications set this flag so that the stream can't be moved anymore with tools such aspavucontrol. So rude of them tbh.block-source-volumeblocks the client from updating any source volumes. This can be used to disable things like automatic gain control. No more sneaky volume gremlins. π€block-sink-volumeblocks the client from updating any sink volumes.block-record-streamblocks the client from creating any record stream. Not today, bestie, no recording. π€π«block-playback-streamblocks the client from creating any playback stream.
π¦ update-props
Takes an object with the properties to update on the client. Common actions are to tweak the quantum values. You can use the stream specific keys in pulse.properties. Slay, that's literally the whole thing. π
πΈ startup notification
A newline will be written into the notification file descriptor when the server has started if the following environment variable is set:
PIPEWIRE_PULSE_NOTIFICATION_FD
Like a little "we're up, bestie π«Ά" text message to your fd. Obsessed.
π EXAMPLE CONFIGURATION π
Okay here's the full look, the whole fit, no notes:
# ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf
context.modules = [
{ name = libpipewire-module-protocol-pulse
args = { }
}
]
pulse.properties = {
server.address = [ "unix:native" ]
}
pulse.rules = [
{
# skype does not want to use devices that don't have an S16 sample format.
matches = [
{ application.process.binary = "teams" }
{ application.process.binary = "teams-insiders" }
{ application.process.binary = "skypeforlinux" }
]
actions = { quirks = [ force-s16-info ] }
}
{
# speech dispatcher asks for too small latency and then underruns.
matches = [ { application.name = "~speech-dispatcher*" } ]
actions = {
update-props = {
pulse.min.req = 1024/48000 # 21ms
pulse.min.quantum = 1024/48000 # 21ms
pulse.idle.timeout = 5 # pause after 5 seconds of underrun
}
}
}
]
and that's a wrap on part 2, bestie π this whole thing is from PipeWire, version 1.6.11, and the page is libpipew...col-pulse(7). thanks for reading my yapping about audio config, you ate π
β¨π¦
π¦ where this page comes from
- fedora 44: pipewire-libs 1.6.11-1.fc44 Β· MIT AND GPL-2.0-or-later AND BSD-2-Clause AND LGPL-2.0-or-later
- fedora 44: pipewire-libs 1.6.11-1.fc44 Β· MIT AND GPL-2.0-or-later AND BSD-2-Clause AND LGPL-2.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms