mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-04-08 08:21:04 -04:00
doc: add docs about the runnable calculations
This commit is contained in:
parent
50aacea749
commit
247918339e
3 changed files with 364 additions and 0 deletions
|
|
@ -10,6 +10,7 @@
|
|||
- \subpage page_objects_design
|
||||
- \subpage page_library
|
||||
- \subpage page_dma_buf
|
||||
- \subpage page_running
|
||||
- \subpage page_scheduling
|
||||
- \subpage page_driver
|
||||
- \subpage page_latency
|
||||
|
|
|
|||
360
doc/dox/internals/running.dox
Normal file
360
doc/dox/internals/running.dox
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
/** \page page_running Node running
|
||||
|
||||
This document tries to explain how the Nodes in a PipeWire graph become
|
||||
runnable so that they can be scheduled later.
|
||||
|
||||
It also describes how nodes are grouped together and scheduled together.
|
||||
|
||||
# Runnable Nodes
|
||||
|
||||
A runnable node is a node that will participate in the dataflow in the
|
||||
PipeWire graph.
|
||||
|
||||
Not all nodes should participate by default. For example, filters or device
|
||||
nodes that are not linked to any runnable nodes should not do useless
|
||||
processing.
|
||||
|
||||
Nodes form one or more groups depending on properties and how they are
|
||||
linked. For each group, one driver is selected to run the group. Inside the
|
||||
group, all runnable nodes are scheduled by the group driver. If there are no
|
||||
runnable nodes in a group, the driver is not started.
|
||||
|
||||
PipeWire provides mechanisms to precisely describe how and when nodes should
|
||||
be scheduled and grouped using:
|
||||
|
||||
- port properties to control how port links control runnable state
|
||||
- node properties to control processing
|
||||
- grouping of nodes and internal links between nodes
|
||||
|
||||
# Port passive modes
|
||||
|
||||
A Port has 4 passive modes, this depends on the value of the `port.passive` property:
|
||||
|
||||
* `false`, the port will make the peer active and an active peer will make this port
|
||||
active.
|
||||
* `true`, the port will not make the peer active and an active peer will not make this
|
||||
port active.
|
||||
* `follow`, the port will not make the peer active but an active peer will make this
|
||||
port active.
|
||||
* `follow-suspend`, the port will only make another follow-suspend peer active but any
|
||||
active peer will make this port active.
|
||||
|
||||
The combination of these 4 modes on the output and input ports of a link results in a
|
||||
wide range of use cases.
|
||||
|
||||
# Node passive modes
|
||||
|
||||
A Node can have 8 passive modes, `node.passive` can be set to a comma separated list
|
||||
of the following values:
|
||||
|
||||
* `false`, both input and output ports have `port.passive = false`
|
||||
* `in`, input ports have `port.passive = true`
|
||||
* `out`, output ports have `port.passive = true`
|
||||
* `true`, both input and output ports have `port.passive = true`. This is the same
|
||||
as `in,out`.
|
||||
* `in-follow`, input ports have `port.passive = follow`
|
||||
* `out-follow`, output ports have `port.passive = follow`
|
||||
* `follow`, input and output ports have `port.passive = follow`. This is the same
|
||||
as `in-follow,out-follow`.
|
||||
* `follow-suspend`, input and output ports have `port.passive = follow-suspend`
|
||||
|
||||
Node by default have the `false` mode but nodes with the `media.class` property
|
||||
containing `Sink` or `Source` receive the `follow-suspend` mode by default.
|
||||
|
||||
Unless explicitly configured, ports inherit the mode from their parent node.
|
||||
|
||||
# Updating the node runnable state
|
||||
|
||||
We iterate all nodes A in the graph and look at its peers B.
|
||||
|
||||
Based on the port passive modes of the port links we can decide if the nodes are
|
||||
runnable or not. A link will always make both nodes runnable or none.
|
||||
|
||||
The following table decides the runnability of the 2 nodes based on the port.passive
|
||||
mode of the link between the 2 ports:
|
||||
|
||||
B-false B-true B-follow B-follow-suspend
|
||||
|
||||
A-false X X X X
|
||||
A-true
|
||||
A-follow
|
||||
A-follow-suspend X
|
||||
Table 1
|
||||
|
||||
|
||||
When a node is made runnable, the port passive mode will then decide if the peer ports
|
||||
should become active as well with the following table.
|
||||
|
||||
B-false B-true B-follow B-follow-suspend
|
||||
|
||||
A-false X X X
|
||||
A-true X X X
|
||||
A-follow X X X
|
||||
A-follow-suspend X X X
|
||||
Table 2
|
||||
|
||||
So when A is runnable, all peers are activated except those with `port.passive=true`.
|
||||
|
||||
When A is runnable, all the nodes that share the same group or link-group will also
|
||||
be made runnable.
|
||||
|
||||
# Use cases
|
||||
|
||||
Let's check some cases that we want to solve with these node and port properties.
|
||||
|
||||
## Device nodes
|
||||
|
||||
+--------+ +--------+
|
||||
| ALSA | | ALSA |
|
||||
| Source | | Sink |
|
||||
| FL FL |
|
||||
| FR FR |
|
||||
+--------+ +--------+
|
||||
|
||||
Unlinked device nodes are supposed to stay suspended when nothing is linked to
|
||||
them.
|
||||
|
||||
+----------+ +--------+
|
||||
| playback | | ALSA |
|
||||
| | | Sink |
|
||||
| FL ------ FL |
|
||||
| FR ------ FR |
|
||||
+----------+ +--------+
|
||||
|
||||
An (active) player node linked to a device node should make both nodes runnable.
|
||||
|
||||
Device nodes have the `port.passive = follow-suspend` property by default. The
|
||||
playback node has the `port.passive = false` by default.
|
||||
|
||||
If we look at the playback node as A and the sink as B, both nodes will be made
|
||||
runnable according to Table 1.
|
||||
|
||||
The two runnable nodes form a group and will be scheduled together. One of the
|
||||
nodes of a group with the `node.driver = true` property is selected as the
|
||||
driver. In the above case, that will be the ALSA Sink.
|
||||
|
||||
Likewise, a capture node linked to an ALSA Source should make both nodes runnable.
|
||||
|
||||
+--------+ +---------+
|
||||
| ALSA | | capture |
|
||||
| Source | | |
|
||||
| FL ------ FL |
|
||||
| FR ------ FR |
|
||||
+--------+ +---------+
|
||||
|
||||
The ALSA Source is now the driver.
|
||||
|
||||
Also, linking 2 device nodes together should make them runnable:
|
||||
|
||||
+--------+ +--------+
|
||||
| ALSA | | ALSA |
|
||||
| Source | | Sink |
|
||||
| FL ----------------------- FL |
|
||||
| FR ----------------------- FR |
|
||||
+--------+ +--------+
|
||||
|
||||
This is the case because in Table 1, the two `port.passive = follow-suspend` ports
|
||||
from the Source and Sink activate each other.
|
||||
|
||||
## Filter nodes
|
||||
|
||||
When there is a filter in front of the ALSA Sink, it should not make the filter and
|
||||
sink runnable.
|
||||
|
||||
+--------+ +--------+
|
||||
| filter | | ALSA |
|
||||
| | | Sink |
|
||||
FL FL ------ FL |
|
||||
FR FR ------ FR |
|
||||
+--------+ +--------+
|
||||
|
||||
The links between the filter and ALSA Sink are `port.passive = true` and don't make
|
||||
the nodes runnable.
|
||||
|
||||
The filter needs to be made runnable via some other means to also make the ALSA
|
||||
Sink runnable, for example by linking a playback node:
|
||||
|
||||
+----------+ +--------+ +--------+
|
||||
| playback | | filter | | ALSA |
|
||||
| | | | | Sink |
|
||||
| FL ------ FL FL ------ FL |
|
||||
| FR ------ FR FR ------ FR |
|
||||
+----------+ +--------+ +--------+
|
||||
|
||||
The input port of the filter is `port.passive = follow-suspend' and so it can be
|
||||
activated by the playback node.
|
||||
|
||||
Likewise, if the ALSA Sink is runnable, it should not automatically make the
|
||||
filter runnable. For example:
|
||||
|
||||
+--------+ +--------+
|
||||
| filter | | ALSA |
|
||||
| | | Sink |
|
||||
FL FL ---+-- FL |
|
||||
FR FR ---|+- FR |
|
||||
+--------+ || +--------+
|
||||
||
|
||||
+----------+ ||
|
||||
| playback | ||
|
||||
| | ||
|
||||
| FL ---+|
|
||||
| FR ----+
|
||||
+----------+
|
||||
|
||||
Here the playback node makes the ALSA Sink runnable but the filter
|
||||
stays not-runnable because the output port is `port.passive = true`.
|
||||
|
||||
## Device node monitor
|
||||
|
||||
Consider the case where we have an ALSA Sink and a monitor stream
|
||||
connected to the sink monitor ports.
|
||||
|
||||
+-------+ +--------++
|
||||
| ALSA | | monitor |
|
||||
| Sink | | |
|
||||
FL FL ------ FL |
|
||||
FR FR ------ FR |
|
||||
+-------+ +---------+
|
||||
|
||||
We would like to keep the monitor stream and the ALSA sink suspended
|
||||
unless something else activated the ALSA Sink:
|
||||
|
||||
|
||||
+----------+ +-------+ +---------+
|
||||
| playback | | ALSA | | monitor |
|
||||
| | | Sink | | |
|
||||
| FL ------ FL FL ------ FL |
|
||||
| FR ------ FR FR ------ FR |
|
||||
+----------+ +-------+ +---------+
|
||||
|
||||
We can do this by making the monitor stream input ports `port.passive = follow`
|
||||
and leave the ALSA Sink monitor output ports as `port.passive = follow-suspend`.
|
||||
|
||||
According to Table 1, both nodes will not activate each other but when ALSA Sink
|
||||
becomes runnable because of playback, according to Table 2, the monitor will
|
||||
become runnable as well.
|
||||
|
||||
Note how we need the distinction between `follow` and `follow-suspend` for this
|
||||
use case.
|
||||
|
||||
## Node groups
|
||||
|
||||
Normally when an application makes a capture and playback node, both nodes will
|
||||
be scheduled in different groups, consider:
|
||||
|
||||
|
||||
+--------+ +---------+
|
||||
| ALSA | | capture |
|
||||
| Source | | |
|
||||
| FL ------ FL |
|
||||
| FR ------ FR |
|
||||
+--------+ +---------+
|
||||
|
||||
+----------+ +--------+
|
||||
| playback | | ALSA |
|
||||
| | | Sink |
|
||||
| FL ------ FL |
|
||||
| FR ------ FR |
|
||||
+----------+ +--------+
|
||||
|
||||
Here we see 2 groups with the ALSA Source and ALSA Sink respectively as the
|
||||
drivers. Depending on the clocks of the nodes, the capture and playback will not
|
||||
be in sync. They will each run in their own time domain depending on the rate of
|
||||
the drivers.
|
||||
|
||||
When we place a node.group property with the same value on the capture and playback
|
||||
nodes, they will be grouped together and this whole graph becomes one single group.
|
||||
|
||||
Because there are 2 potential drivers in the group, the one with the highest
|
||||
`priority.driver` property is selected as the driver in the group. The other nodes
|
||||
in the group (including the other driver) become followers in the group.
|
||||
|
||||
When a node becomes runnable, all other nodes with the same node.group property
|
||||
become runnable as well.
|
||||
|
||||
## Node link groups
|
||||
|
||||
When we have a filter that is constructed from two nodes, an input and an output
|
||||
node, we could use the `node.group` property to make sure they are both scheduled
|
||||
and made runnable together.
|
||||
|
||||
+--------+ +-------+ +--------+ +-------+
|
||||
| ALSA | | input | | output | | ALSA |
|
||||
| Source | | | | | | Sink |
|
||||
| FL ------ FL -- processing-- FL ------ FL |
|
||||
| FR ------ FR | | FR ------ FR |
|
||||
+--------+ +-------+ +--------+ +-------+
|
||||
|
||||
This would work fine but it does not describe that there is an implicit internal
|
||||
link between the input and output node. This information is important for the
|
||||
session manager to avoid linking the output node to the input node and make a
|
||||
loop.
|
||||
|
||||
The `node.link-group` property can be used to both group the nodes together and
|
||||
descibe that they are internally linked together.
|
||||
|
||||
When a node becomes runnable, all other nodes with the same node.link-group property
|
||||
become runnable as well.
|
||||
|
||||
For the 2 node filters, like loopback and filter-chain, the same `port.passive`
|
||||
property rules apply as for the filter nodes. Note that for the virtual devices,
|
||||
the Source/Sink nodes will be `follow-suspend` by default and the other node should
|
||||
be set to `node.passive = true` to make the ports passive.
|
||||
|
||||
## Want driver
|
||||
|
||||
When there is no driver node in the group, nothing should be scheduled. This can
|
||||
happen when a playback node is linked to a capture node:
|
||||
|
||||
+--------+ +---------+
|
||||
| player | | capture |
|
||||
| | | |
|
||||
| FL ----------- FL |
|
||||
| FR ----------- FR |
|
||||
+--------+ +---------+
|
||||
|
||||
None of these nodes is a driver so there is no driver in the group and nothing
|
||||
will be scheduled.
|
||||
|
||||
When one of the nodes has `node.want-driver = true` they are grouped and
|
||||
scheduled with a random driver node. This is often the driver node with the
|
||||
highest priority (usually the Dummy-Driver) or otherwise a driver that is already
|
||||
scheduling some other nodes.
|
||||
|
||||
## Always process nodes
|
||||
|
||||
A simple node, unlinked to anything should normally not run.
|
||||
|
||||
+--------+
|
||||
| player |
|
||||
| |
|
||||
| FL
|
||||
| FR
|
||||
+--------+
|
||||
|
||||
When the `node.always-process = true` property is set, the node will however be
|
||||
made runnable even if unlinked. This is done by adding the node to a random driver.
|
||||
|
||||
`node.always-process = true` implies the `node.want-driver = true` property.
|
||||
|
||||
## Sync groups
|
||||
|
||||
In some cases, you only want to group nodes together depending on some condition.
|
||||
|
||||
For example, when the JACK transport is activated, all nodes in the graph should share
|
||||
the same driver node, regardless of the grouping or linking of the nodes.
|
||||
|
||||
This is done by setting the same node.sync-group property on all nodes (by default all
|
||||
nodes have `node.sync-group = group.sync.0`). When a node sets `node.sync = true` all
|
||||
the other nodes with the same `node.sync-group` property are grouped together.
|
||||
|
||||
This can be used to implement the JACK transport. When the transport is started, the
|
||||
`node.sync=true` property is set and all nodes join one group with a shared driver
|
||||
and timing information.
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
|
@ -23,6 +23,9 @@ node is scheduled to run.
|
|||
This document describes the processing that happens in the data processing
|
||||
thread after the main thread has configured it.
|
||||
|
||||
Before scheduling of the node happens, the scheduler will collect a list of
|
||||
nodes that are runnable, see \ref page_running
|
||||
|
||||
# Nodes
|
||||
|
||||
Nodes are objects with 0 or more input and output ports.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue