mirror of
https://gitlab.freedesktop.org/pipewire/wireplumber.git
synced 2026-05-06 04:48:10 +02:00
docs: initial design documentation
This commit is contained in:
parent
3779a92fcc
commit
fe3d0b55ac
6 changed files with 309 additions and 0 deletions
139
docs/rst/design/events_and_hooks.rst
Normal file
139
docs/rst/design/events_and_hooks.rst
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
.. _events_and_hooks:
|
||||
|
||||
Events and Hooks
|
||||
================
|
||||
|
||||
Session management is all about reacting to events and taking neccessary
|
||||
actions. This is why WirePlumber's logic is all built on events and hooks.
|
||||
|
||||
Events
|
||||
------
|
||||
|
||||
Events are objects that represent a change that has just happened on a PipeWire
|
||||
object, or just a trigger for making a decision and potentially taking some
|
||||
action.
|
||||
|
||||
Every event has a source, a subject and some properties, which include the
|
||||
event type.
|
||||
|
||||
* The ``source`` is a reference to the GObject that created this event.
|
||||
Typically, this is the ``WpStandardEventSource`` plugin.
|
||||
* The ``subject`` is an *optional* reference to the object that this event
|
||||
is about. For example, in a ``node-added`` event, the ``subject`` would be
|
||||
a reference to the ``WpNode`` object that was just added. Some events,
|
||||
especially those which are used only to trigger actions, do not have a
|
||||
subject.
|
||||
* The ``properties`` is a dictionary that contains information about the event,
|
||||
including the event type, and also includes all the PipeWire properties of the
|
||||
``subject``, if there is one.
|
||||
* The ``event.type`` property describes the nature of the event, for example
|
||||
``node-added`` or ``metadata-changed`` are some valid event types.
|
||||
|
||||
Every event also has a priority. Events with a higher priority are processed
|
||||
before events with a lower priority. When two or more events have the same
|
||||
priority, they are processed in a first-in-first-out manner. This logic
|
||||
is defined in the *event dispatcher*.
|
||||
|
||||
Events are short-lived objects. They are created at the time that something is
|
||||
happening and they are destroyed after they get processed. Processing an event
|
||||
means executing all the hooks that are associated with it. The next section
|
||||
explains what hooks are and how they are associated with events.
|
||||
|
||||
Hooks
|
||||
-----
|
||||
|
||||
Hooks are objects that represent a runnable action that needs to be executed
|
||||
when a certain event is processed. Every hook, therefore, consists of a
|
||||
function - synchronous or asynchronous - that can be executed. Additionally,
|
||||
every hook has a means to associate itself with specific events. This is
|
||||
normally done by declaring *interest* to specific event properties or
|
||||
combinations of them.
|
||||
|
||||
There are two main types of hooks: ``SimpleEventHook`` and ``AsyncEventHook``.
|
||||
|
||||
* ``SimpleEventHook`` contains a single, synchronous function. As soon as this
|
||||
function is executed, the hook is completed.
|
||||
* ``AsyncEventHook`` contains multiple functions, combined together in a state
|
||||
machine using ``WpTransition`` underneath. The hook is completed only after
|
||||
the state machine reaches its final state and this can take any amount of time
|
||||
neccessary.
|
||||
|
||||
Every hook also has a name, which can be an arbitrary string of characters.
|
||||
Additionally, it has two arrays of names, which declare dependencies between
|
||||
this hook and others. One array is called ``before`` and the other is called
|
||||
``after``. The hook names in the ``before`` array specify that this hook must
|
||||
be executed *before* those other hooks. Similarly, the hook names in the
|
||||
``after`` array specify that this hook must be executed *after* those other
|
||||
hooks. Using this mechanism, it is possible to define the order in which
|
||||
hooks will be executed, for a specific event.
|
||||
|
||||
Hooks are long-lived objects. They are created once, registered in the
|
||||
*event dispatcher*, they are attached on events and detached after their
|
||||
execution. They don't maintain any internal state, so the actions of the hook
|
||||
depend solely on the event itself.
|
||||
|
||||
The Event Dispatcher
|
||||
--------------------
|
||||
|
||||
The event dispatcher is a (per core) singleton object that processes all events
|
||||
and also maintains a list of all the registered hooks. It has a method to
|
||||
*push* events on it, which causes them to be scheduled for processing.
|
||||
|
||||
Scheduling of events and hooks
|
||||
------------------------------
|
||||
|
||||
The main idea and reasoning behind this architecture is to have everything
|
||||
execute in a predefined order and always wait for an action to finish before
|
||||
executing the next one.
|
||||
|
||||
Every event has a *priority* and every hook also has an order of execution that
|
||||
derives from the inter-dependencies between hooks, which are defined with
|
||||
``before`` and ``after`` (see above). When an event is pushed on the dispatcher,
|
||||
the dispatcher goes through all the registered hooks and checks which hooks are
|
||||
configured to run on this event (their event interest matches the event).
|
||||
It then makes a list of them, sorted by their order of execution, and stores it
|
||||
on the event. The event is then added on the dispatcher's list of events, which
|
||||
is sorted by priority.
|
||||
|
||||
For example::
|
||||
|
||||
List of events
|
||||
| event1 (prio 99) -> hook1, hook2, hook3
|
||||
| event2 (prio 50) -> hook5, hook2, hook4
|
||||
v
|
||||
|
||||
The dispatcher has an internal ``GSource`` that is registered with
|
||||
``G_PRIORITY_HIGH_IDLE`` priority. When there is at least one event in the
|
||||
list of events, the source is dispatched. Every time it gets dispatched,
|
||||
it takes the top-most event (the highest priority one) and executes the highest
|
||||
priority hook in that event. If the hook executes synchronously, it then takes
|
||||
the next hook and continues until there are no more hooks on this event;
|
||||
then it goes to the next event, and so on. If the hook, however, executes
|
||||
asynchronously, processing stops until the hook finishes; after finishing,
|
||||
processing resumes like before.
|
||||
|
||||
It is important to notice here that the list of events may be modified while
|
||||
events are getting processed. For example, a device is added; that's a
|
||||
``device-added`` event. Then a hook is executed to set the profile. That creates
|
||||
nodes, so a couple of ``node-added`` events... But there is also another hook to
|
||||
set the route, which was attached on the ``device-added`` event for the device.
|
||||
Suppose that we give the ``node-added`` events lower priority than the
|
||||
``device-added`` events, then the ``set-route`` hook will execute right after
|
||||
the ``set-profile`` and before any ``node-added`` events are processed.
|
||||
|
||||
Visually, with sample priorities::
|
||||
|
||||
List of events
|
||||
| "device-added" (prio 20) -> set-profile, set-route
|
||||
| "node-added" (prio 10) -> restore-stream, create-session-item
|
||||
v
|
||||
|
||||
Obviously, there can also be a case where a newly added event has higher
|
||||
priority than the event that was being processed before. In that case,
|
||||
processing the hooks of the original event is stopped until all the hooks from
|
||||
the higher priority event have been processed. For example, a capture stream
|
||||
node being added may trigger the "bluetooth autoswitch" hook, which will then
|
||||
change the profile of a device. Changing the profile also has to trigger setting
|
||||
a new route and also handling the new device nodes, creating session items for
|
||||
them... After all this is done, processing the original capture stream
|
||||
``node-added`` event can continue.
|
||||
6
docs/rst/design/meson.build
Normal file
6
docs/rst/design/meson.build
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# you need to add here any files you add to the toc directory as well
|
||||
sphinx_files += files(
|
||||
'understanding_session_management.rst',
|
||||
'understanding_wireplumber.rst',
|
||||
'events_and_hooks.rst',
|
||||
)
|
||||
105
docs/rst/design/understanding_session_management.rst
Normal file
105
docs/rst/design/understanding_session_management.rst
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
.. _understanding_session_management:
|
||||
|
||||
Understanding Session Management
|
||||
================================
|
||||
|
||||
The PipeWire session manager is a tool that is tasked to do a lot of things.
|
||||
Many people understand the term "session manager" as a tool that is responsible
|
||||
for managing links between nodes, but that is only one of many tasks. To
|
||||
understand the entirety of its operation, we need to discuss how PipeWire works,
|
||||
first.
|
||||
|
||||
When PipeWire starts, it loads a set of modules that are defined in its
|
||||
configuration file. These modules provide functionality to PipeWire, otherwise
|
||||
it is just an empty process that does nothing. Under normal circumstances,
|
||||
the modules that are loaded on PipeWire's startup contain object *factories*,
|
||||
plus the native protocol module that allows inter-process communication.
|
||||
Other than that, PipeWire does not really load or do anything else. This is
|
||||
where session management begins.
|
||||
|
||||
Session management is basically about setting up PipeWire to do something
|
||||
useful. This is achieved by utilizing PipeWire's exposed object factories to
|
||||
create some useful objects, then work with their methods to modify and later
|
||||
destroy them. Such objects include devices, nodes, ports, links and others.
|
||||
This is a task that requires continuous monitoring and action taking, reacting
|
||||
on a large number of different events that happen as the system is being used.
|
||||
|
||||
High-level areas of operation
|
||||
-----------------------------
|
||||
|
||||
The session management logic, in WirePlumber, is divided into 6 different areas
|
||||
of operation:
|
||||
|
||||
1. Device Enablement
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Enabling devices is a fundamental area of operation. It is achieved by using
|
||||
the device monitor objects (or just "monitors"), which are typically
|
||||
implemented as SPA plugins in PipeWire, but they are loaded by WirePlumber.
|
||||
Their task is to discover available media devices and create objects in PipeWire
|
||||
that offer a way to interact with them.
|
||||
|
||||
Well-known monitors include:
|
||||
|
||||
- The ALSA monitor, which enables audio devices
|
||||
- The ALSA MIDI monitor, which enables MIDI devices
|
||||
- The libcamera monitor, which enables cameras
|
||||
- The Video4Linux2 (V4L2) monitor, which also enables cameras, but also
|
||||
other video capture devices through the V4L2 Linux API
|
||||
- The BlueZ monitor, which enables bluetooth audio devices
|
||||
|
||||
2. Device Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Most devices expose complex functionality, from the computer's perspective, that
|
||||
needs to be managed in order to provide a simple and smooth user experience.
|
||||
For that reason, for example, audio devices are organized into *profiles* and
|
||||
*routes*, which allow setting them up to serve a specific use case. These
|
||||
need to be configured and managed by the session manager.
|
||||
|
||||
3. Client Access Control
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When client applications connect to PipeWire, they need to obtain permissions
|
||||
in order to be able to access the objects exposed by PipeWire and interact
|
||||
with them. In some circumstances and configurations, the session manager is also
|
||||
tasked with deciding which permissions should be granted to each client.
|
||||
|
||||
4. Node Configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Nodes are the fundamental elements of media processing. They are typically
|
||||
created either by the device monitors or by client applications. When they are
|
||||
created, they are in a state where they cannot be linked. Linking them requires
|
||||
some configuration, such as configuring the media format and subsequently
|
||||
the number and the type of ports that should be exposed. Additionally, some
|
||||
properties and metadata related to the node might need to be set according to
|
||||
user preferences. All of this is taken care of by the session manager.
|
||||
|
||||
5. Link Management
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When nodes are finally ready to use, the session manager is also tasked to
|
||||
decide how they should be linked together in order for media to flow though.
|
||||
For instance, an audio playback stream node most likely needs to be linked to
|
||||
the default audio output device node. The session manager then also needs to
|
||||
create all these links and monitor all conditions that may affect them so that
|
||||
dynamic re-linking is possible in case something changes
|
||||
(ex. if a device disconnects). In some cases, device and node configuration
|
||||
may also need to change as a result of links being created or destroyed.
|
||||
|
||||
6. Metadata Management
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
While in operation, PipeWire and WirePlumber both store some additional
|
||||
properties about objects and their operation in storage that lives outside
|
||||
these objects. These properties are referred to as "metadata" and they are
|
||||
stored in "metadata objects". This metadata can be changed externally by tools
|
||||
such as `pw-metadata`, but also others.
|
||||
|
||||
In some circumstances, this metadata needs to interact with logic inside
|
||||
the session manager. Most notably, selecting the default audio and video inputs
|
||||
and outputs is done by setting metadata. The session manager then needs to
|
||||
validate this information, store it and restore it on the next restart, but also
|
||||
ensure that the default inputs and outputs stay valid and reasonable when
|
||||
devices are plugged and unplugged dynamically.
|
||||
50
docs/rst/design/understanding_wireplumber.rst
Normal file
50
docs/rst/design/understanding_wireplumber.rst
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
.. _understanding_wireplumber:
|
||||
|
||||
Understanding WirePlumber
|
||||
=========================
|
||||
|
||||
Knowing the fundamentals of session management, let's see here how WirePlumber
|
||||
is structured.
|
||||
|
||||
The Library
|
||||
-----------
|
||||
|
||||
WirePlumber is built on top of a library that provides some fundamental building
|
||||
blocks for expressing all the session management logic. This library can also
|
||||
be used outside the scope of the WirePlumber daemon in order to build external
|
||||
tools and GUIs that interact with PipeWire.
|
||||
|
||||
The Object Model
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The most fundamental code contained in the WirePlumber library is the object
|
||||
model, i.e. its representation of PipeWire's objects.
|
||||
|
||||
PipeWire exposes several objects, such nodes and ports, via the IPC protocol
|
||||
in a manner that is hard to interact with using standard object-oriented
|
||||
principles, because it is asynchronous. For example, when an object is created,
|
||||
its existence is announced over the protocol, but its properties are announced
|
||||
later, on a secondary message. If something needs to react on this object
|
||||
creation event, it typically needs to access the object's properties, so it
|
||||
must wait until the properties have been sent. Doing this might sound simple,
|
||||
and it is, but it becomes a tedious repetitive process to be doing this
|
||||
everywhere instead of focusing on writing the actual event handling logic.
|
||||
|
||||
WirePlumber's library solves this by creating proxy objects that cache all the
|
||||
information and updates received from PipeWire throughout each object's
|
||||
lifetime. Then, it makes them available via the `WpObjectManager` API, which has
|
||||
the ability to wait until certain information (ex, the properties) has been
|
||||
cached on each object before announcing it.
|
||||
|
||||
Session management utilities
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Daemon
|
||||
----------
|
||||
|
||||
Modules
|
||||
^^^^^^^
|
||||
|
||||
Scripts
|
||||
^^^^^^^
|
||||
|
||||
|
|
@ -12,6 +12,14 @@ Table of Contents
|
|||
configuration.rst
|
||||
daemon-logging.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: WirePlumber's Design
|
||||
|
||||
design/understanding_session_management.rst
|
||||
design/understanding_wireplumber.rst
|
||||
design/events_and_hooks.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: The WirePlumber Library
|
||||
|
|
|
|||
|
|
@ -16,3 +16,4 @@ sphinx_files += files(
|
|||
subdir('c_api')
|
||||
subdir('lua_api')
|
||||
subdir('configuration')
|
||||
subdir('design')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue