summaryrefslogtreecommitdiffstats
path: root/doc/spa-plugins.dox
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:28:17 +0000
commit7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch)
treed483300dab478b994fe199a5d19d18d74153718a /doc/spa-plugins.dox
parentInitial commit. (diff)
downloadpipewire-upstream.tar.xz
pipewire-upstream.zip
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'doc/spa-plugins.dox')
-rw-r--r--doc/spa-plugins.dox360
1 files changed, 360 insertions, 0 deletions
diff --git a/doc/spa-plugins.dox b/doc/spa-plugins.dox
new file mode 100644
index 0000000..af14d5e
--- /dev/null
+++ b/doc/spa-plugins.dox
@@ -0,0 +1,360 @@
+/** \page page_spa_plugins SPA Plugins
+
+\ref spa_handle "SPA plugins" are dynamically loadable objects that contain objects and interfaces that
+can be introspected and used at runtime in any application. This document
+introduces the basic concepts of SPA plugins. It first covers using the API
+and then talks about implementing new plugins.
+
+
+# Outline
+
+To use a plugin, the following steps are required:
+
+- **Load** the shared library.
+- **Enumerate** the available factories.
+- **Enumerate** the interfaces in each factory.
+- **Instantiate** the desired interface.
+- **Use** the interface-specific functions.
+
+In pseudo-code, loading a logger interface looks like this:
+
+\code{.py}
+handle = dlopen("$SPA_PLUGIN_DIR/support/libspa-support.so")
+factory_enumeration_func = dlsym(handle, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)
+spa_log *logger = NULL
+
+while True:
+ factory = get_next_factory(factory_enumeration_func):
+ if factory != SPA_NAME_SUPPORT_LOG: # <spa/utils/name.h>
+ continue
+
+ interface_info = get_next_interface_info(factory)
+ if info->type != SPA_TYPE_INTERFACE_Log: # </spa/support/log.h>
+ continue
+
+ interface = spa_load_interface(handle, interface_info->type)
+ logger = (struct spa_log *)interface
+ break
+
+spa_log_error(log, "This is an error message\n")
+\endcode
+
+SPA does not specify where plugins need to live, although plugins are
+normally installed in `/usr/lib64/spa-0.2/` or equivalent. Plugins and API
+are versioned and many versions can live on the same system.
+
+\note The directory the SPA plugins reside in is available through
+ `pkg-config --variable plugindir libspa-0.2`
+
+The `spa-inspect` tool provides a CLI interface to inspect SPA plugins:
+
+\verbatim
+$ export SPA_PLUGIN_DIR=$(pkg-config --variable plugindir libspa-0.2)
+$ spa-inspect ${SPA_PLUGIN_DIR}/support/libspa-support.so
+...
+factory version: 1
+factory name: 'support.cpu'
+factory info:
+ none
+factory interfaces:
+ interface: 'Spa:Pointer:Interface:CPU'
+factory instance:
+ interface: 'Spa:Pointer:Interface:CPU'
+skipping unknown interface
+factory version: 1
+factory name: 'support.loop'
+factory info:
+ none
+factory interfaces:
+ interface: 'Spa:Pointer:Interface:Loop'
+ interface: 'Spa:Pointer:Interface:LoopControl'
+ interface: 'Spa:Pointer:Interface:LoopUtils'
+...
+\endverbatim
+
+
+# Open A Plugin
+
+A plugin is opened with a platform specific API. In this example we use
+`dlopen()` as the method used on Linux.
+
+A plugin always consists of two parts, the vendor path and then the .so file.
+
+As an example we will load the "support/libspa-support.so" plugin. You will
+usually use some mapping between functionality and plugin path as we'll see
+later, instead of hardcoding the plugin name.
+
+To `dlopen` a plugin we then need to prefix the plugin path like this:
+
+\code{.c}
+#define SPA_PLUGIN_DIR /usr/lib64/spa-0.2/"
+void *hnd = dlopen(SPA_PLUGIN_DIR"/support/libspa-support.so", RTLD_NOW);
+\endcode
+
+The environment variable `SPA_PLUGIN_DIR` and `pkg-config` variable
+`plugindir` are usually used to find the location of the plugins. You will
+have to do some more work to construct the shared object path.
+
+The plugin must have exactly one public symbol, called
+`spa_handle_factory_enum`, which is defined with the macro
+`SPA_HANDLE_FACTORY_ENUM_FUNC_NAME` to get some compile time checks and avoid
+typos in the symbol name. We can get the symbol like so:
+
+\code{.c}
+spa_handle_factory_enum_func_t enum_func;
+enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME));
+\endcode
+
+If this symbol is not available, the library is not a valid SPA plugin.
+
+
+# Enumerating Factories
+
+With the `enum_func` we can now enumerate all the factories in the plugin:
+
+\code{.c}
+uint32_t i;
+const struct spa_handle_factory *factory = NULL;
+for (i = 0;;) {
+ if (enum_func(&factory, &i) <= 0)
+ break;
+ // check name and version, introspect interfaces,
+ // do something with the factory.
+}
+\endcode
+
+A factory has a version, a name, some properties and a couple of functions
+that we can check and use. The main use of a factory is to create an
+actual new object from it.
+
+We can enumerate the interfaces that we will find on this new object with
+the `spa_handle_factory_enum_interface_info()` method. Interface types
+are simple strings that uniquely define the interface (see also the type
+system).
+
+The name of the factory is a well-known name that describes the functionality
+of the objects created from the factory. `<spa/utils/names.h>` contains
+definitions for common functionality, for example:
+
+\code{.c}
+#define SPA_NAME_SUPPORT_CPU "support.cpu" // A CPU interface
+#define SPA_NAME_SUPPORT_LOG "support.log" // A Log interface
+#define SPA_NAME_SUPPORT_DBUS "support.dbus" // A DBUS interface
+\endcode
+
+Usually the name will be mapped to a specific plugin. This way an
+alternative compatible implementation can be made in a different library.
+
+
+# Making A Handle
+
+Once we have a suitable factory, we need to allocate memory for the object
+it can create. SPA usually does not allocate memory itself but relies on
+the application and the stack for storage.
+
+First get the size of the required memory:
+
+\code{.c}
+struct spa_dict *extra_params = NULL;
+size_t size = spa_handle_factory_get_size(factory, extra_params);
+\endcode
+
+Sometimes the memory can depend on the extra parameters given in
+`_get_size()`. Next we need to allocate the memory and initialize the object
+in it:
+
+\code{.c}
+handle = calloc(1, size);
+spa_handle_factory_init(factory, handle,
+ NULL, // info
+ NULL, // support
+ 0 // n_support
+ );
+\endcode
+
+The info parameter should contain the same extra properties given in
+`spa_handle_factory_get_size()`.
+
+The support parameter is an array of `struct spa_support` items. They
+contain a string type and a pointer to extra support objects. This can
+be a logging API or a main loop API for example. Some plugins require
+certain support libraries to function.
+
+
+# Retrieving An Interface
+
+When a SPA handle is made, you can retrieve any of the interfaces that
+it provides:
+
+\code{.c}
+void *iface;
+spa_handle_get_interface(handle, SPA_NAME_SUPPORT_LOG, &iface);
+\endcode
+
+If this method succeeds, you can cast the `iface` variable to
+`struct spa_log *` and start using the log interface methods.
+
+\code{.c}
+struct spa_log *log = iface;
+spa_log_warn(log, "Hello World!\n");
+\endcode
+
+
+# Clearing An Object
+
+After you are done with a handle you can clear it with
+`spa_handle_clear()` and you can unload the library with `dlclose()`.
+
+
+# SPA Interfaces
+
+We briefly talked about retrieving an interface from a plugin in the
+previous section. Now we will explore what an interface actually is
+and how to use it.
+
+When you retrieve an interface from a handle, you get a reference to
+a small structure that contains the type (string) of the interface,
+a version and a structure with a set of methods (and data) that are
+the implementation of the interface. Calling a method on the interface
+will just call the appropriate method in the implementation.
+
+Interfaces are defined in a header file (for example see
+`<spa/support/log.h>` for the logger API). It is a self contained
+definition that you can just use in your application after you `dlopen()`
+the plugin.
+
+Some interfaces also provide extra fields in the interface, like the
+log interface above that has the log level as a read/write parameter.
+
+See \ref spa_interface for some implementation details on interfaces.
+
+
+# SPA Events
+
+Some interfaces will also allow you to register a callback (a hook or
+listener) to be notified of events. This is usually when something
+changed internally in the interface and it wants to notify the registered
+listeners about this.
+
+For example, the `struct spa_node` interface has a method to register such
+an event handler like this:
+
+\code{.c}
+static void node_info(void *data, const struct spa_node_info *info)
+{
+ printf("got node info!\n");
+}
+
+static struct spa_node_events node_events = {
+ SPA_VERSION_NODE_EVENTS,
+ .info = node_info,
+};
+
+struct spa_hook listener;
+spa_zero(listener);
+spa_node_add_listener(node, &listener, &node_event, my_data);
+\endcode
+
+You make a structure with pointers to the events you are interested in
+and then use `spa_node_add_listener()` to register a listener. The
+`struct spa_hook` is used by the interface to keep track of registered
+event listeners.
+
+Whenever the node information is changed, your `node_info` method will
+be called with `my_data` as the first data field. The events are usually
+also triggered when the listener is added, to enumerate the current
+state of the object.
+
+Events have a `version` field, set to `SPA_VERSION_NODE_EVENTS` in the
+above example. It should contain the version of the event structure
+you compiled with. When new events are added later, the version field
+will be checked and the new signal will be ignored for older versions.
+
+You can remove your listener with:
+
+\code{.c}
+spa_hook_remove(&listener);
+\endcode
+
+
+# API Results
+
+Some interfaces provide API that gives you a list or enumeration of
+objects/values. To avoid allocation overhead and ownership problems,
+SPA uses events to push results to the application. This makes it
+possible for the plugin to temporarily create complex objects on the
+stack and push this to the application without allocation or ownership
+problems. The application can look at the pushed result and keep/copy
+only what it wants to keep.
+
+## Synchronous Results
+
+Here is an example of enumerating parameters on a node interface.
+
+First install a listener for the result:
+
+\code{.c}
+static void node_result(void *data, int seq, int res,
+ uint32_t type, const void *result)
+{
+ const struct spa_result_node_params *r =
+ (const struct spa_result_node_params *) result;
+ printf("got param:\n");
+ spa_debug_pod(0, NULL, r->param);
+}
+
+struct spa_hook listener = { 0 };
+static const struct spa_node_events node_events = {
+ SPA_VERSION_NODE_EVENTS,
+ .result = node_result,
+};
+
+spa_node_add_listener(node, &listener, &node_events, node);
+\endcode
+
+Then perform the `enum_param` method:
+
+\code{.c}
+int res = spa_node_enum_params(node, 0, SPA_PARAM_EnumFormat, 0, MAXINT, NULL);
+\endcode
+
+This triggers the result event handler with a 0 sequence number for each
+supported format. After this completes, remove the listener again:
+
+\code{.c}
+spa_hook_remove(&listener);
+\endcode
+
+## Asynchronous Results
+
+Asynchronous results are pushed to the application in the same way as
+synchronous results, they are just pushed later. You can check that
+a result is asynchronous by the return value of the enum function:
+
+\code{.c}
+int res = spa_node_enum_params(node, 0, SPA_PARAM_EnumFormat, 0, MAXINT, NULL);
+
+if (SPA_RESULT_IS_ASYNC(res)) {
+ // result will be received later
+ ...
+}
+\endcode
+
+In the case of async results, the result callback will be called with the
+sequence number of the async result code, which can be obtained with:
+
+\code{.c}
+expected_seq = SPA_RESULT_ASYNC_SEQ(res);
+\endcode
+
+# Implementing A New Plugin
+
+***FIXME***
+
+
+
+\addtogroup spa_handle
+
+See: \ref page_spa_plugins
+
+*/