mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	Separate various autogen files from the documentation .dox files. Rename .dox files to match the intended tree structure.
		
			
				
	
	
		
			360 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			360 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
/** \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
 | 
						|
 | 
						|
*/
 |