docs: Add filters documentation

This commit is contained in:
Julian Bouzas
2023-08-21 11:47:03 -04:00
committed by George Kiagiadakis
parent 56017fdbe6
commit 3eaaae3a45

View File

@@ -49,3 +49,245 @@ This file contains generic default policy properties that can be configured.
How much to lower the volume of lower priority streams when ducking. Note that
this is a linear volume modifier (not cubic as in PulseAudio).
Filters
^^^^^^^
* *Introduction*
A pair of nodes will be considered filter nodes by wireplumber if they have the
"node.link-group" property set to a common value. This propery is always set by
PipeWire when creating filter nodes if they are defined in the PipeWire's
configuration file. The pair of nodes always consist of a stream node, and a
main node. When using the filter nodes, the main node acts as a virtual device,
where the audio is sent or captured to/from; and the stream node acts as a
virtual stream, where the audio is sent or received to/from the next node in the
graph.
For example, the media class of the nodes for a input filter would be:
- main node: Audio/Sink
- stream node: Stream/Output/Audio
And, if this filter is used between an application stream, and the default audio
device, the graph would look like this:
application stream node -> filter main node
(Stream/Output/Audio) (Audio/Sink)
filter stream node -> default device node
(Stream/Output/Audio) (Audio/Sink)
On the other hand, the media class of the nodes for an output filter would be:
- main node: Audio/Source
- stream node: Stream/Input/Audio
And the same logic is applied if they are used, but in the opposite direction.
This is how the graph would look like if an application wants to capture audio
from a device that uses an input filter.
application stream node <- filter main node
(Stream/Input/Audio) (Audio/Source)
filter stream node <- default device node
(Stream/Input/Audio) (Audio/Source)
Finally, if multiple filters have the same direction, they can also be chained
together so that the audio of a filter is sent to the input of the next filter.
Example of existing filters in PipeWire are echo-cancel, filter-chain and
loopback nodes.
The next section will describe how we can define filter properties so that they
are automatically linked by the wirepluber policy in any way we want.
* *Filter properties*
Currently, if a filter node is created, wireplumber will check the following
optional node properties on the main node:
- filter.name: the unique name of the filter. WirePlumber will use the
"node.link-group" property as filter name if this property is not set.
- filter.enabled: Boolean indicating whether the filter should be used at all
or not. If it is not set, wireplumber will consider the filter enabled by default.
- filter.target: a JSON object that defines the matching properties of the filter's
target node. A filter target can never be another filter node (wireplumber will
ignore it), and must always be a device node. If this property is not set,
WirePlumber will use the default node as target.
- filter.before: a JSON array with the filters names that are supposed to be
used before this filter. If not set, wireplumber will link the filters by order
of creation.
- filter.after: a JSON array with the filters names that are supposed to be
used after this filter. If not set, wireplumber will link the filters by order
of creation.
Note that these properties must be set in the filter's main node, not the
filter's stream node.
As an example, we will describe here how to create 2 loopback filters in the
PipeWire's configuration, with names loopback-1 and loopback-2, that will be
linked with the default audio device, and use loopback-2 filter as the last
filter in the chain.
The PipeWire configuration files for the 2 filters should be like this:
- /usr/share/pipewire/pipewire.conf.d/loopback-1.conf:
context.modules = [
{ name = libpipewire-module-loopback
args = {
node.name = loopback-1-sink
node.description = "Loopback 1 Sink"
capture.props = {
audio.position = [ FL FR ]
media.class = Audio/Sink
filter.name = loopback-1
filter.enabled = true
filter.before = [ loopback-2 ]
}
playback.props = {
audio.position = [ FL FR ]
node.passive = true
node.dont-remix = true
}
}
}
]
- /usr/share/pipewire/pipewire.conf.d/loopback-2.conf:
context.modules = [
{ name = libpipewire-module-loopback
args = {
node.name = loopback-2-sink
node.description = "Loopback 2 Sink"
capture.props = {
audio.position = [ FL FR ]
media.class = Audio/Sink
filter.name = loopback-2
filter.enabled = true
}
playback.props = {
audio.position = [ FL FR ]
node.passive = true
node.dont-remix = true
}
}
}
]
Finally, if we restart PipeWire and WirePlumber to apply the configuration
changes, and play a test.wave audio file with paplay to see if wireplumber links
the filter nodes properly, the graph should look like this:
paplay node -> loopback-1 main node
(Stream/Output/Audio) (Audio/Sink)
loopback-1 stream node -> loopback-1 main node
(Stream/Output/Audio) (Audio/Sink)
loopback-2 stream node -> default device node
(Stream/Output/Audio) (Audio/Sink)
If we remove `filter.before = [ loopback-2 ]` property from the loopback-1 filter,
and add a `filter.before = [ loopback-1 ]` property in the loopback-2 filter
configuration file. WirePlumber should link the loopback-1 filter as the last filter
in the chain, like this:
paplay node -> loopback-2 main node
(Stream/Output/Audio) (Audio/Sink)
loopback-2 stream node -> loopback-1 main node
(Stream/Output/Audio) (Audio/Sink)
loopback-1 stream node -> default device node
(Stream/Output/Audio) (Audio/Sink)
On the other hand, the filters can have different targets. For example, we can
define the filters like this:
- `/usr/share/pipewire/pipewire.conf.d/loopback-1.conf`:
context.modules = [
{ name = libpipewire-module-loopback
args = {
node.name = loopback-1-sink
node.description = "Loopback 1 Sink"
capture.props = {
audio.position = [ FL FR ]
media.class = Audio/Sink
filter.name = loopback-1
filter.enabled = true
filter.before = [ loopback-2 ]
filter.target = { node.name = "not-default-audio-device-name" }
}
playback.props = {
audio.position = [ FL FR ]
node.passive = true
node.dont-remix = true
}
}
}
]
- `/usr/share/pipewire/pipewire.conf.d/loopback-2.conf`:
context.modules = [
{ name = libpipewire-module-loopback
args = {
node.name = loopback-2-sink
node.description = "Loopback 2 Sink"
capture.props = {
audio.position = [ FL FR ]
media.class = Audio/Sink
filter.name = loopback-2
filter.enabled = true
}
playback.props = {
audio.position = [ FL FR ]
node.passive = true
node.dont-remix = true
}
}
}
]
If this is the case, WirePlumber will link the filters like this when using
paplay:
paplay node -> loopback-2 main node
(Stream/Output/Audio) (Audio/Sink)
loopback-2 stream node -> default device node
(Stream/Output/Audio) (Audio/Sink)
loopback-1 stream node -> not-default-audio-device-name device node
(Stream/Output/Audio) (Audio/Sink)
The loopback-1 main node will only be used if an application wants to play audio
on the device node with node name "not-default-audio-device-name".
* *Filters metadata*
Similar to the default metadata, it is also possible to override the filter
properties by using the "filters" metadata. This allow users to change the filters
policy at runtime.
For example, if loopback-1 main node Id is `40`, we can disable the filter by
setting its "filter.enabled" metadata key to false using the `pw-metadata` tool:
$ pw-metadata -n filters 40 "filter.enabled" false Spa:String:JSON
We can also change the target of a filter at runtime:
$ pw-metadata -n filters 40 "filter.target" { node.name = "new-target-node-name" } Spa:String:JSON
Every time a key in the filters metadata changes, all filters are unlinked and
re-linked properly by the policy.