pipewire.conf(5)

πŸ’– pipewire.conf(5) β€” File Formats Manual πŸ’–

okay bestie, grab a strawberry πŸ“ because we're about to read a whole entire manual page together. This one lives in section 5 (File Formats Manual) and it's called pipewire.conf. Yes girl, the config file itself gets its own manual page. Iconic behavior honestly.


✨ NAME ✨

pipewire.conf β€” the PipeWire server configuration file. That's it, that's the name section, short and slay.


🌸 DESCRIPTION 🌸

PipeWire is a service that facilitates sharing of multimedia content between devices and applications. Like, it's the girl who introduces your audio streams to your hardware and makes sure everyone gets along at the party. πŸŽ‰

On startup, the daemon reads a main configuration file to configure itself. It executes a series of commands listed in the config file. So the daemon wakes up, opens its little config, and does what it's told. Main character energy fr. πŸ‘‘

The config file is looked up in the order listed in the SYNOPSIS. The environment variables PIPEWIRE_CONFIG_DIR, PIPEWIRE_CONFIG_PREFIX and PIPEWIRE_CONFIG_NAME can be used to specify an alternative config directory, subdirectory and file respectively. (Yes, all three, in that exact order, don't mix them up πŸ’…)

Other PipeWire configuration files generally follow the same lookup logic, replacing pipewire.conf with the name of the particular config file.


πŸ¦‹ SYNOPSIS πŸ¦‹

These are the places it goes looking, in this exact order:

$XDG_CONFIG_HOME/pipewire/pipewire.conf
/etc/pipewire/pipewire.conf
/usr/share/pipewire/pipewire.conf
/usr/share/pipewire/pipewire.conf.d/
/etc/pipewire/pipewire.conf.d/
$XDG_CONFIG_HOME/pipewire/pipewire.conf.d/

lowkey a whole ✨journey✨. It checks every one of these, bestie.


πŸŽ€ DROP-IN CONFIGURATION FILES πŸŽ€

All *.conf files in the pipewire.conf.d/ directories are loaded and merged into the configuration. Dictionary sections are merged, overriding properties if they already existed, and array sections are appended to. The drop-in files have the same format as the main configuration file, but only contain the settings to be modified.

As the pipewire.conf configuration file contains various parts that must be present for correct functioning, using drop-in files for configuration is recommended. Like, please. Do the drop-in thing. It's the move. πŸ™

🌈 Example 🌈

A configuration file ~/.config/pipewire/pipewire.conf.d/custom.conf to change the value of the default.clock.min-quantum setting in pipewire.conf:

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.properties = {
    default.clock.min-quantum = 128
}

see how small and cute that is?? no cap, that's the whole file. πŸ’…


⭐ CONFIGURATION FILE FORMAT ⭐

The configuration file is in "SPA" JSON format. Yes it's JSON but it's got a relaxed little attitude and we love her for it.

The configuration file contains top-level keys, which are the sections. The value of a section is either a dictionary, { }, or an array, [ ]. Section and dictionary item declarations have KEY = VALUE form, and are separated by whitespace. For example:

context.properties = {  # top-level dictionary section

    key1 = value  # a simple value

    key2 = { key1 = value1 key2 = value2 }  # a dictionary with two entries

    key3 = [ value1 value2 ]  # an array with two entries

    key4 = [ { k = v1 } { k = v2 } ]  # an array of dictionaries

}

context.modules = [  # top-level array section

    value1

    value2

]

The configuration files can also be written in standard JSON syntax, but for easier manual editing, the relaxed "SPA" variant is allowed. In "SPA" JSON:

  • : to delimit keys and values can be substituted by = or a space.
  • " around keys and string can be omitted as long as no special characters are used in the strings.
  • , to separate objects can be replaced with a whitespace character.
  • # can be used to start a comment until the line end

it's giving "JSON but she got comfortable at home" 😌✨ and honestly same.


πŸ‘‘ CONFIGURATION FILE SECTIONS πŸ‘‘

Here's the whole lineup, bestie. Every section, every vibe.

  • context.properties β€” Dictionary. These properties configure the PipeWire instance.
  • context.spa-libs β€” Dictionary. Maps plugin features with globs to a spa library.
  • context.modules β€” Array of dictionaries. Each entry in the array is a dictionary with the name of the module to load, including optional args and flags. Most modules support being loaded multiple times.
  • context.objects β€” Array of dictionaries. Each entry in the array is a dictionary containing the factory to create an object from and optional extra arguments specific to that factory.
  • context.exec β€” Array of dictionaries. Each entry in the array is dictionary containing the path of a program to execute on startup and optional args.
bestie, listen. This array used to contain an entry to start the session manager but this mode of operation has since been demoted to development aid. Avoid starting a session manager in this way in production environment. Like genuinely, don't do it in prod. That's a real warning, not a vibe. 🚨
  • node.rules β€” Array of dictionaries. Match rules for modifying node properties on the server.
  • device.rules β€” Array of dictionaries. Match rules for modifying device properties on the server.

✨ CONTEXT PROPERTIES ✨

Okay bestie, this is the good stuff πŸ’– β€” all the properties you can drop in context.properties, with their possible default values. Buckle up, there's like thirty of them and every single one is getting its moment.

  • clock.power-of-two-quantum = true β€” rounds the quantum requests that come in from clients, and the final graph quantum, down to a power of two. Why would you want that? Because a power-of-two quantum can be way more efficient for a whole bunch of processing tasks. Math girlies stay winning 🧚
  • context.data-loop.library.name.system β€” the name of the shared library used for the system functions of the data processing thread. You'd typically only change this if your data thread is running on a realtime kernel like EVL.
  • loop.rt-prio = -1 β€” the priority of the data loops. Data loops are what schedule the nodes in the graph. Set it to -1 and you get the default realtime priority from module-rt. Set it to 0 and realtime scheduling for the data loops is off.
  • loop.class = [ data.rt .. ] β€” an array of classes for the data loops. Normally nodes get assigned to a loop by name or by class, and nodes default to the data.rt class, so it's genuinely a good idea to have a data loop of that class too. iykyk πŸ’…
  • context.num-data-loops = 1 β€” how many data loops to create. Default is 1, so every node gets scheduled in that one thread. 0 kills the real-time data loops and schedules all nodes in the main thread instead. And -1? That spawns as many data threads as you have CPU cores. Go off, CPU πŸ”₯
  • context.data-loops = [ ... ] β€” this controls the data loops created for the context. It's an array of data loop specifications, one entry per data loop you want to start:
# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.data-loops = [
    {
         #library.name.system = support/libspa-support
         loop.rt-prio = -1
         loop.class = [ data.rt .. ]
         thread.name = data-loop.0
         thread.affinity = [ 0 1 ]
    }
    ...
]

You can give a specific priority, classes and name using loop.rt-prio, loop.class and thread.name respectively. And if you feel like it, you can pin that data loop to specific CPU cores with the thread.affinity property. Very main character of you.

  • core.daemon = false β€” makes the PipeWire process started with this config a daemon process, meaning it manages and schedules a graph for clients. You'll also wanna set a core.name so it has a well-known name to go by πŸŽ€
  • core.name = pipewire-0 β€” the name of the PipeWire context. This also becomes the name of the PipeWire socket that clients connect to.
  • cpu.zero.denormals = false β€” configures the CPU to zero denormals automatically. When it's enabled, it's only enabled for the data processing thread.
  • cpu.vm.name = null β€” set automatically when the context is created, and it holds the name of the VM. Usually used to write match rules that set extra properties.
  • default.clock.rate = 48000 β€” the default clock rate decides the real-time duration of the min/max/default quantums. Pro tip (straight from the page πŸ’œ): you might wanna change the quantums when you change the default clock rate, so the quantums keep the same duration.
  • default.clock.allowed-rates = [ ] β€” you can specify up to 32 alternative sample rates. The graph sample rate gets switched whenever the devices are idle. Heads up though: this is not enabled by default right now, because of various kernel and Bluetooth issues 😭 And also note the min/max/default quantum values get scaled when the samplerate changes.
  • default.clock.min-quantum = 32 β€” default minimum quantum.
  • default.clock.max-quantum = 8192 β€” default maximum quantum.
  • default.clock.quantum = 1024 β€” the default quantum used when no client specifies one.
  • default.clock.quantum-limit = 8192 β€” maximum quantum to reserve space for. This is the biggest buffer size used in the graph, no matter the samplerate.
  • default.clock.quantum-floor = 4 β€” minimum quantum to reserve space for. This is the smallest buffer size used in the graph, no matter the samplerate.
  • default.video.width β€” default video width.
  • default.video.height β€” default video height.
  • default.video.rate.num β€” default video rate numerator.
  • default.video.rate.denom β€” default video rate denominator.
  • library.name.system = support/libspa-support β€” the name of the shared library used for the system functions of the main thread.
  • link.max-buffers = 64 β€” the maximum number of buffers negotiated between nodes. Real talk: clients with version < 3 can only support 16 buffers. And more buffers is almost always worse than less β€” latency-wise and memory-wise. Less is more, bestie 🌸
  • log.level = 2 β€” the default log level used by the process.
  • mem.allow-mlock = true β€” tries to mlock the memory for the realtime processes. Locked memory won't get swapped out by the kernel, which avoids hiccups in the processing threads. Iconic behaviour 🫢
  • mem.warn-mlock = false β€” warns you about failures to lock memory.
  • mem.mlock-all = false β€” tries to mlock all current and future memory used by the process.
  • rlimit.nofile = 4096 β€” tries to set the max file descriptor number resource limit of the process. A value of -1 raises the limit to the system-defined hard maximum. The file resource limit is usually 1024, and it should only be raised if the program does not use the select() system call. Good news: PipeWire normally does not use select(). Slay.
  • rlimit.*resource* = value β€” sets resource limits. resource can be one of: as, core, cpu, data, fsize, locks, memlock, msgqueue, nice, nofile, nproc, rss, rtprio, rttime, sigpending or stack. Go read the documentation of setrlimit if you want the meaning of these resources. A value of -1 sets the maximum allowed limit.
  • settings.check-quantum = false β€” checks whether the quantum in the settings metadata update is compatible with the configured limits.
  • settings.check-rate = false β€” checks whether the rate in the settings metadata update is compatible with the configured limits.
  • support.dbus = true β€” enables DBus support. This turns on DBus support in the various modules that need it. Set it to false if you want to globally disable DBus support in the process.
  • vm.overrides = { default.clock.min-quantum = 1024 } β€” any property inside the vm.overrides property object overrides the property in context.properties when PipeWire detects it's running inside a VM.
⚠️ This one is deprecated β€” use the context.properties.rules instead. No cap, it's on the way out.
  • context.modules.allow-empty = false β€” by default a warning gets logged when there are no context.modules loaded, because that usually means something's broken πŸ’€ Some applications load the modules themselves though, and when they set this property to true, no warning gets logged.

Alsoooo, the context properties can totally contain custom values of your own. For example, the context.modules and context.objects sections can declare additional conditions that control whether a module or object gets loaded, depending on which properties are present. Cute, right? πŸ¦‹


🌈 SPA LIBRARIES 🌈

SPA plugins get loaded based on their factory-name. That's a well-known name that uniquely describes the features the plugin is supposed to have. The context.spa-libs section gives you the mapping between the factory-name and the plugin where that factory lives. Matchmaking, but for libraries πŸ’–

Factory names can contain a wildcard, so you can group several related factories into one plugin. The plugin gets loaded from the first matching factory-name. First come, first served, periodt.

Example

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.spa-libs = {
    audio.convert.* = audioconvert/libspa-audioconvert
    avb.*           = avb/libspa-avb
    api.alsa.*      = alsa/libspa-alsa
    api.v4l2.*      = v4l2/libspa-v4l2
    api.libcamera.* = libcamera/libspa-libcamera
    api.bluez5.*    = bluez5/libspa-bluez5
    api.vulkan.*    = vulkan/libspa-vulkan
    api.jack.*      = jack/libspa-jack
    support.*       = support/libspa-support
    video.convert.* = videoconvert/libspa-videoconvert
}

🧚 MODULES 🧚

These are the PipeWire modules to be loaded. Go look at libpipewire-modules(7) for the whole menu, bestie ✨

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.modules = [
    #{ name = MODULENAME
    #    ( args  = { KEY = VALUE ... } )
    #    ( flags = [ ( ifexists ) ( nofail ) ] )
    #    ( condition = [ { KEY = VALUE ... } ... ] )
    #}
    #
]
  • name β€” name of the module to be loaded.
  • args = { } β€” arguments passed to the module.
  • flags = [ ] β€” loading flags. ifexists means only load the module if it exists, and nofail means don't fail PipeWire startup if the module fails to load. Very forgiving of you πŸ’…
  • condition = [ ] β€” a match rule matches condition. The module gets loaded only if one of the expressions in the array matches a context property.

πŸ‘‘ CONTEXT OBJECTS πŸ‘‘

The context.objects section lets you make some objects from factories (which are usually created by loading modules over in context.modules). Manifesting objects into existence, honestly.

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.objects = [
    #{ factory = <factory-name>
    #    ( args  = { <key> = <value> ... } )
    #    ( flags = [ ( nofail ) ] )
    #    ( condition = [ { <key> = <value> ... } ... ] )
    #}
]

This section can be used to make nodes or links between nodes πŸ”₯

  • factory β€” name of the factory used to create the object.
  • args = { } β€” arguments passed to the factory.
  • flags = [ ] β€” the nofail flag means don't fail PipeWire startup if the object fails to load.
  • condition = [ ] β€” a match rule matches condition. The object gets created only if one of the expressions in the array matches a context property.

Example

This little fragment creates a brand new dummy driver node β€” but only if the core.daemon property is true. Conditional queen πŸŽ€

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.objects = [
    { factory = spa-node-factory
      args = {
          factory.name    = support.node.driver
          node.name       = Dummy-Driver
          node.group      = pipewire.dummy
          priority.driver = 20000
      },
      condition = [ { core.daemon = true } ]
    }
]

πŸš€ COMMAND EXECUTION

okay bestie so the context.exec section is literally your backstage pass to start any command you want as part of the initialization of the PipeWire program. it's giving "set the vibe before the party starts" ✨

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.exec = [
    #{   path = <program-name>
    #    ( args = "<arguments>" | [ <arg1> <arg2> ... ] )
    #    ( condition = [ { <key> = <value> ... } ... ] )
    #}
]
  • path β€” the program you wanna execute. that's it, that's the whole vibe 🫢
  • args β€” the arguments handed to that program.
  • condition β€” a match rule that matches condition. the object is created only if one of the expressions in the array matches a context property. picky queen, we respect it πŸ’…

🌸 Example

this lil fragment runs a pactl command with the given arguments:

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.exec = [
    { path = "pactl" args = "load-module module-always-sink" }
]

πŸŽ€ MATCH RULES

some configuration file sections contain match rules, and honestly? iconic feature. it makes it possible to perform some action when an object (usually a node or a stream) is created/updated that matches certain properties. like little if-statements but for your audio graph. no cap πŸ”₯

the general rules object follows the following pattern:

<rules> = [
    {
        matches = [
            # any of the following sets of properties are matched, if
            # any matches, the actions are executed
            {
                # <key> = <value>
                # all keys must match the value. ! negates. ~ starts regex.
                #application.process.binary = "teams"
                #application.name = "~speech-dispatcher.*"

                # Absence of property can be tested by comparing to null
                #pipewire.sec.flatpak = null
            }
            {
                # more matches here...
            }
            ...
        ]
        actions = {
            <action-name> = <action value>
            ...
        }
    }
]

Match rules are an array of rules, bestie. plural. a whole collection πŸ’–

a rule is always a JSON object with two keys: matches and actions. the matches key defines the conditions that need to be met for the rule to be evaluated as true, and the actions key defines the actions that get performed when the rule is evaluated as true. true = action time, periodt.

the matches key is always a JSON array of objects, where each object defines a condition that needs to be met. each condition is a list of key-value pairs, where the key is the name of the property that's being matched, and the value is the value that the property needs to have. within a condition, all the key-value pairs are combined with a logical AND, and all the conditions in the matches array are combined with a logical OR. so: friends inside a condition, merely acquaintances across conditions. iykyk 😌

the actions key is always a JSON object, where each key-value pair defines an action that's performed when the rule is evaluated as true. the action name is specific to the rule and is defined by the rule's documentation, but most frequently you'll see the update-props action, which is used to update the properties of the matched object. it's the main character action, fr fr πŸ‘‘

in the matches array it's also possible to use regular expressions to match property values. for example, to match all nodes with a name that starts with my_, you can use:

matches = [
  {
    node.name = "~my_.*"
  }
]

the ~ character signifies that the value is a regular expression. the exact syntax of the regular expressions is the POSIX extended regex syntax, as described in the regex (7) man page. screenshot that bestie, we're quoting sources πŸ’…

in addition to regular expressions, you may also use the ! character to negate a condition. for example, to match all nodes with a name that does not start with my_:

matches = [
  {
    node.name = "!~my_.*"
  }
]

the ! character can be used with or without a regular expression. for example, to match all nodes with a name that is not equal to my_node:

matches = [
  {
    node.name = "!my_node"
  }
]

the null value has a special meaning; it checks if the property is not available (or unset). to check if a property is not set:

matches = [
  {
    node.name = null
  }
]

to check the existence of a property, you can use the !null condition, for example:

matches = [
  {
    node.name = "!null"
  }
  {
    node.name = !null            # simplified syntax
  }
]

to handle the "null" string, you need to escape the string. for example, to check if a property has the string value "null", use:

matches = [
  {
    node.name = "\"null\""
  }
]

to handle anything but the "null" string, use:

matches = [
  {
    node.name = "!\"null\""
  }
  {
    node.name = !"null"      # simplified syntax
  }
]
⚠️ ok careful babe, the quotes matter here. escaping is not optional and the parser will not forgive you 😭

πŸ¦‹ CONTEXT PROPERTIES RULES

context.properties.rules can be used to dynamically update the properties based on other properties. very meta, very "i contain multitudes" energy πŸ’œ

a typical case is to update custom settings when running inside a VM. the cpu.vm.name is automatically set when running in a VM with the name of the VM. a match rule can be written to set custom properties like this:

# ~/.config/pipewire/pipewire.conf.d/custom.conf

context.properties.rules = [
    {   matches = [ { cpu.vm.name = !null } ]
        actions = {
            update-props = {
                # These overrides are only applied when running in a vm.
                default.clock.min-quantum = 1024
            }
        }
    }
]

so if you're in a VM, boom, you get the custom default.clock.min-quantum = 1024. contextual slay ✨


🌈 NODE RULES

the node.rules are evaluated every time the properties on a node are set or updated. this can be used on the server side to override client-set properties on arbitrary nodes. server said "i'll be taking that" πŸ’…

node.rules provides an update-props action that takes an object with properties that are updated on the node object.

add a node.rules section in the config file like this:

# ~/.config/pipewire/pipewire.conf.d/custom.conf

node.rules = [
    {
        matches = [
            {
                # all keys must match the value. ! negates. ~ starts regex.
                client.name = "jack_simple_client"
            }
        ]
        actions = {
            update-props = {
                node.force-quantum = 512
            }
        }
    }
]

will set the node.force-quantum property of jack_simple_client to 512. easy math, gorgeous result πŸŽ€


πŸ’– DEVICE RULES

the device.rules are evaluated every time the properties on a device are set or updated. this can be used on the server side to override client-set properties on arbitrary devices. same energy as node rules but make it hardware πŸ¦‹

device.rules provides an update-props action that takes an object with properties that are updated on the device object.

add a device.rules section in the config file like this:

# ~/.config/pipewire/pipewire.conf.d/custom.conf

device.rules = [
    {
        matches = [
            {
                # all keys must match the value. ! negates. ~ starts regex.
                device.name = ""v4l2_device.pci-0000_00_14.0-usb-0_1.2_1.0
            }
        ]
        actions = {
            update-props = {
                device.description = "My Webcam"
            }
        }
    }
]

will set the device.description property of the device with the given device.name to "My Webcam". your webcam is now self-actualized and named, we love a glow up πŸ‘‘


πŸ‘‘ AUTHORS

the PipeWire Developers β€” <https://gitlab.freedesktop.org/pipewire/pipewire/issues>; PipeWire is available from <https://pipewire.org>


⭐ SEE ALSO

pipewire(1), pw-mon(1), libpipewire-modules(7) pipewire-pulse.conf(5) pipewire-client.conf(5)


okay bestie that's the whole page, you made it to the end and i'm so proud 🫢 go forth and configure, you absolute legend. this lovely thing is PipeWire version 1.6.11, dated with page marker pipewire.conf(5). stan forever πŸ’–βœ¨πŸŒΈ

πŸ“¦ where this page comes from