Skip to content

Instantly share code, notes, and snippets.

@dcoles
Last active October 21, 2024 17:07
Show Gist options
  • Save dcoles/ae4e601d23a6d81e1b215b9fed2d5a98 to your computer and use it in GitHub Desktop.
Save dcoles/ae4e601d23a6d81e1b215b9fed2d5a98 to your computer and use it in GitHub Desktop.
SPA Plugin Loading in Python
# SPA bindings
import os
import functools
from spa._cffi import ffi
from spa.pod import PodObject
from spa.constants import SPA_TYPE_COMMAND__NODE
CURRENT_DIR = os.path.dirname(__file__)
SPA_HANDLE_FACTORY_ENUM_FUNC_NAME = 'spa_handle_factory_enum'
SPA_PLUGIN_DIR = os.getenv('SPA_PLUGIN_DIR', 'plugins')
class Plugin:
@classmethod
def load_plugin(cls, path: str) -> 'Plugin':
path = os.path.join(SPA_PLUGIN_DIR, path)
lib = ffi.dlopen(path)
return cls(lib, path)
def __init__(self, lib, path: str) -> None:
self.lib = lib
self.path = path
def __del__(self) -> None:
ffi.dlclose(self.lib)
self.lib = None
def __repr__(self) -> str:
return f'Plugin<{self.path}>'
def get_factory(self, name) -> 'Factory':
name_ = name.encode('utf-8') if isinstance(name, str) else name
factory_p = ffi.new('struct spa_handle_factory **', ffi.NULL)
index_p = ffi.new('uint32_t *', 0)
while True:
res = self.lib.spa_handle_factory_enum(factory_p, index_p)
if res < 0:
raise RuntimeError('failed to enumerate plugin factory')
if res == 0:
raise ModuleNotFoundError(f'plugin factory {name!r} not found')
factory_name = ffi.string(factory_p[0].name)
if factory_name == name_:
break
return Factory(self, factory_p[0])
class Factory:
def __init__(self, plugin: Plugin, ptr) -> None:
# Hold on to Plugin so shared library isn't closed
self._plugin = plugin
self._ptr = ptr
@property
def name(self) -> str:
return ffi.string(self._ptr.name).decode('utf-8')
def __repr__(self) -> str:
return f'Factory<{self.name}>'
def build(self, info, support, n_support) -> 'Handle':
size = self._ptr.get_size(self._ptr, info)
buffer = ffi.new('uint8_t[]', size)
handle = ffi.cast('struct spa_handle *', buffer)
res = self._ptr.init(self._ptr, handle, info, support, n_support)
if res < 0:
raise RuntimeError('failed to initialize handle')
return Handle(self, buffer)
class Handle:
def __init__(self, factory: Factory, buffer) -> None:
# Hold onto Factory so it can't be garbage collected
self._factory = factory
self._buffer = buffer # Take ownership of handle memory
self._ptr = ffi.cast('struct spa_handle *', buffer)
def __repr__(self) -> str:
return f'Handle<{self._factory.name}>'
def get_interface(self, interface_type) -> 'Interface':
interface_p = ffi.new('struct spa_interface **');
ret = self._ptr.get_interface(self._ptr, interface_type.C_TYPE, ffi.cast('void **', interface_p))
if ret < 0:
raise RuntimeError(f'Failed to get {interface_type.TYPE} interface on {self}')
return interface_type(self, interface_p[0])
class Methods:
def __init__(self, methods_type: str, handle_ptr, interface_ptr) -> None:
self.__methods = ffi.cast(methods_type, interface_ptr.cb.funcs)
self.__ptr = handle_ptr
def __getattr__(self, name: str) -> callable:
method = getattr(self.__methods, name)
return functools.partial(method, self.__ptr)
class Interface:
TYPE = None
C_TYPE = None
METHOD_TYPE = None
def __init__(self, handle: Handle, ptr) -> None:
# Hold onto Handle so it can't be garbage collected
self._handle = handle
self._ptr = ptr
@property
def type(self) -> str:
return ffi.string(self._ptr.type).decode('utf-8')
@property
def _methods(self):
return Methods(self.METHOD_TYPE, self._handle._ptr, self._ptr)
def __repr__(self) -> str:
return f'{self.__class__.__name__}<{self.type}>'
class LogInterface(Interface):
TYPE = 'Spa:Pointer:Interface:Log'
C_TYPE = ffi.new('char[]', TYPE.encode('utf-8'))
METHOD_TYPE = 'struct spa_log_methods *'
LOG_LEVEL_NONE = 0
LOG_LEVEL_ERROR = 1
LOG_LEVEL_WARN = 2
LOG_LEVEL_INFO = 3
LOG_LEVEL_DEBUG = 4
LOG_LEVEL_TRACE = 5
def error(self, msg):
file = ffi.new('char[]', b'file.c')
func = ffi.new('char[]', b'func')
fmt = ffi.new('char[]', b'%s')
msg = ffi.new('char[]', msg.encode('utf-8'))
self._methods.log(self.LOG_LEVEL_ERROR, file, 0, func, fmt, msg)
class LoopInterface(Interface):
TYPE = 'Spa:Pointer:Interface:Loop'
C_TYPE = ffi.new('char[]', TYPE.encode('utf-8'))
METHOD_TYPE = 'struct spa_loop_methods *'
class LoopControlInterface(Interface):
TYPE = 'Spa:Pointer:Interface:LoopControl'
C_TYPE = ffi.new('char[]', TYPE.encode('utf-8'))
METHOD_TYPE = 'struct spa_loop_control_methods *'
def __enter__(self):
self.enter()
def __exit__(self, *_exc_info):
self.leave()
def enter(self):
print('LoopControl.enter')
self._methods.enter()
def leave(self):
print('LoopControl.leave')
self._methods.leave()
def iterate(self, timeout_ms: int = -1):
ret = self._methods.iterate(timeout_ms)
if ret < 0:
raise RuntimeError(f'LoopControl.iterate failed: {ret}')
def check(self) -> bool:
ret = self._methods.check()
if ret < 0:
raise RuntimeError(f'LoopControl.check failed: {ret}')
return bool(ret)
class LoopUtilsInterface(Interface):
TYPE = 'Spa:Pointer:Interface:LoopUtils'
C_TYPE = ffi.new('char[]', TYPE.encode('utf-8'))
METHOD_TYPE = 'struct spa_loop_utils_methods *'
class NodeInterface(Interface):
TYPE = 'Spa:Pointer:Interface:Node'
C_TYPE = ffi.new('char[]', TYPE.encode('utf-8'))
METHOD_TYPE = 'struct spa_node_methods *'
def set_callbacks(self, callbacks, data) -> None:
ret = self._methods.set_callbacks(callbacks, data)
if ret < 0:
raise RuntimeError(f'Failed to set Node callbacks: {ret}')
def add_listener(self, listener, node_events, data) -> None:
ret = self._methods.add_listener(listener, node_events, data)
if ret < 0:
raise RuntimeError(f'Failed to add Node listener: {ret}')
def port_set_io(self, direction: int, port: int, io_type: int, io):
ret = self._methods.port_set_io(direction, port, io_type, io, ffi.sizeof(io[0]))
if ret < 0:
raise RuntimeError(f'Failed to set Node port {port} IO: {ret}')
def port_use_buffers(self, direction: int, port: int, flags: int, spa_buffers, n_buffers: int):
ret = self._methods.port_use_buffers(direction, port, flags, spa_buffers, n_buffers)
if ret < 0:
raise RuntimeError(f'Failed to set Node port {port} buffers: {ret}')
def port_set_param(self, direction: int, port: int, param_type: int, flags: int, format):
ret = self._methods.port_set_param(direction, port, param_type, flags, format)
if ret < 0:
raise RuntimeError(f'Failed to set Node port={port!r} param_type={param_type!r}: {ret}')
def port_reuse_buffer(self, port: int, buffer_id: id) -> int:
ret = self._methods.port_reuse_buffer(port, buffer_id)
if ret < 0:
raise RuntimeError(f'Node.port_reuse_buffer failed port={port!r}: {ret}')
def send_command(self, command: int):
command_buffer = ffi.new('uint8_t[]', bytes(PodObject(SPA_TYPE_COMMAND__NODE, command)))
ret = self._methods.send_command(ffi.cast('struct spa_command *', command_buffer))
if ret < 0:
raise RuntimeError(f'Failed to send command: {ret}')
def process(self) -> int:
ret = self._methods.process()
if ret < 0:
raise RuntimeError(f'Node process failed: {ret}')
return ret
# auto-generated file
import _cffi_backend
ffi = _cffi_backend.FFI('spa._cffi',
_version = 0x2601,
_types = b'\x00\x00\x40\x0D\x00\x00\xD0\x03\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x01\x11\x00\x00\xA7\x03\x00\x00\x19\x03\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x0D\x03\x00\x00\x11\x03\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\xD1\x03\x00\x00\x01\x11\x00\x00\xCD\x03\x00\x00\xEB\x03\x00\x00\x16\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x0D\x11\x00\x00\xD5\x03\x00\x00\x0A\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\xF4\x03\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x00\x0B\x00\x00\x16\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x1D\x11\x00\x00\x16\x01\x00\x00\x0F\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x1D\x11\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\xC1\x03\x00\x00\x16\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x1D\x11\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\xE9\x03\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x1D\x11\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x19\x11\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x07\x01\x00\x00\x1D\x11\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x34\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x07\x01\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x34\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\xC8\x03\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\xD2\x03\x00\x00\xE4\x03\x00\x00\x19\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\xE3\x03\x00\x00\x19\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x16\x01\x00\x00\x16\x01\x00\x00\x34\x11\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x16\x01\x00\x00\x19\x11\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x40\x0D\x00\x00\x19\x11\x00\x00\x18\x01\x00\x00\x18\x01\x00\x00\xE9\x03\x00\x00\x00\x0F\x00\x00\x3C\x0D\x00\x00\x0D\x11\x00\x00\x0F\x11\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x5A\x11\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\x1D\x11\x00\x00\x16\x01\x00\x00\xEA\x03\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\x02\x0B\x00\x00\x05\x11\x00\x00\x07\x01\x00\x00\x05\x11\x00\x00\x05\x11\x00\x00\x01\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x16\x01\x00\x00\xF4\x03\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\xCE\x03\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\x5A\x11\x00\x00\xDC\x03\x00\x00\x19\x11\x00\x00\x00\x0F\x00\x00\xF4\x0D\x00\x00\x19\x11\x00\x00\xE5\x03\x00\x00\x00\x0F\x00\x00\x02\x01\x00\x00\x01\x0B\x00\x00\x00\x03\x00\x00\x03\x03\x00\x00\x0C\x03\x00\x00\x13\x03\x00\x00\x18\x03\x00\x00\x1B\x03\x00\x00\x20\x03\x00\x00\x26\x03\x00\x00\x2E\x03\x00\x00\x36\x03\x00\x00\x3E\x03\x00\x00\x42\x03\x00\x00\x4C\x03\x00\x00\x54\x03\x00\x00\x58\x03\x00\x00\x5E\x03\x00\x00\x63\x03\x00\x00\x68\x03\x00\x00\x6E\x03\x00\x00\x74\x03\x00\x00\x15\x01\x00\x00\x17\x01\x00\x00\x7A\x03\x00\x00\x00\x09\x00\x00\xC2\x03\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\xC5\x03\x00\x00\x03\x09\x00\x00\xC5\x05\x00\x00\x00\x01\x00\x00\x04\x09\x00\x00\xCA\x03\x00\x00\x05\x09\x00\x00\xCA\x05\x00\x00\x00\x01\x00\x00\x06\x09\x00\x00\x07\x09\x00\x00\x08\x09\x00\x00\x09\x09\x00\x00\x0A\x09\x00\x00\x0B\x09\x00\x00\x0C\x09\x00\x00\x0D\x09\x00\x00\xD6\x03\x00\x00\x0E\x09\x00\x00\x0F\x09\x00\x00\xD9\x03\x00\x00\x10\x09\x00\x00\x11\x09\x00\x00\x12\x09\x00\x00\x13\x09\x00\x00\x14\x09\x00\x00\xDF\x03\x00\x00\x15\x09\x00\x00\xDF\x05\x00\x00\x00\x01\x00\x00\x16\x09\x00\x00\x17\x09\x00\x00\x18\x09\x00\x00\x19\x09\x00\x00\x1A\x09\x00\x00\xE8\x03\x00\x00\x1B\x09\x00\x00\x1C\x09\x00\x00\x1D\x09\x00\x00\x1E\x09\x00\x00\x7E\x03\x00\x00\x81\x03\x00\x00\x84\x03\x00\x00\x8A\x03\x00\x00\x92\x03\x00\x00\x99\x03\x00\x00\x9D\x03\x00\x00\xA3\x03\x00\x00\x00\x01',
_globals = (b'\xFF\xFF\xFF\x1FSPA_CHUNK_FLAG_CORRUPTED',1,b'\xFF\xFF\xFF\x1FSPA_CHUNK_FLAG_EMPTY',2,b'\xFF\xFF\xFF\x1FSPA_CHUNK_FLAG_NONE',0,b'\xFF\xFF\xFF\x1FSPA_DATA_FLAG_DYNAMIC',4,b'\xFF\xFF\xFF\x1FSPA_DATA_FLAG_NONE',0,b'\xFF\xFF\xFF\x1FSPA_DATA_FLAG_READABLE',1,b'\xFF\xFF\xFF\x1FSPA_DATA_FLAG_READWRITE',3,b'\xFF\xFF\xFF\x1FSPA_DATA_FLAG_WRITABLE',2,b'\xFF\xFF\xFF\x0BSPA_DIRECTION_INPUT',0,b'\xFF\xFF\xFF\x0BSPA_DIRECTION_OUTPUT',1,b'\xFF\xFF\xFF\x0BSPA_IO_Buffers',1,b'\xFF\xFF\xFF\x0BSPA_IO_Clock',3,b'\xFF\xFF\xFF\x0BSPA_IO_Control',5,b'\xFF\xFF\xFF\x0BSPA_IO_Invalid',0,b'\xFF\xFF\xFF\x0BSPA_IO_Latency',4,b'\xFF\xFF\xFF\x0BSPA_IO_Memory',9,b'\xFF\xFF\xFF\x0BSPA_IO_Notify',6,b'\xFF\xFF\xFF\x0BSPA_IO_Position',7,b'\xFF\xFF\xFF\x0BSPA_IO_Range',2,b'\xFF\xFF\xFF\x0BSPA_IO_RateMatch',8,b'\xFF\xFF\xFF\x0BSPA_LOG_LEVEL_DEBUG',4,b'\xFF\xFF\xFF\x0BSPA_LOG_LEVEL_ERROR',1,b'\xFF\xFF\xFF\x0BSPA_LOG_LEVEL_INFO',3,b'\xFF\xFF\xFF\x0BSPA_LOG_LEVEL_NONE',0,b'\xFF\xFF\xFF\x0BSPA_LOG_LEVEL_TRACE',5,b'\xFF\xFF\xFF\x0BSPA_LOG_LEVEL_WARN',2,b'\xFF\xFF\xFF\x1FSPA_META_HEADER_FLAG_CORRUPTED',2,b'\xFF\xFF\xFF\x1FSPA_META_HEADER_FLAG_DELTA_UNIT',32,b'\xFF\xFF\xFF\x1FSPA_META_HEADER_FLAG_DISCONT',1,b'\xFF\xFF\xFF\x1FSPA_META_HEADER_FLAG_GAP',16,b'\xFF\xFF\xFF\x1FSPA_META_HEADER_FLAG_HEADER',8,b'\xFF\xFF\xFF\x1FSPA_META_HEADER_FLAG_MARKER',4,b'\xFF\xFF\xFF\x1FSPA_PORT_CHANGE_MASK_FLAGS',1,b'\xFF\xFF\xFF\x1FSPA_PORT_CHANGE_MASK_PARAMS',8,b'\xFF\xFF\xFF\x1FSPA_PORT_CHANGE_MASK_PROPS',4,b'\xFF\xFF\xFF\x1FSPA_PORT_CHANGE_MASK_RATE',2,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_CAN_ALLOC_BUFFERS',4,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_DYNAMIC_DATA',256,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_IN_PLACE',8,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_LIVE',32,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_NO_REF',16,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_OPTIONAL',2,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_PHYSICAL',64,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_REMOVABLE',1,b'\xFF\xFF\xFF\x1FSPA_PORT_FLAG_TERMINAL',128,b'\xFF\xFF\xFF\x1FSPA_STATUS_DRAINED',8,b'\xFF\xFF\xFF\x1FSPA_STATUS_HAVE_DATA',2,b'\xFF\xFF\xFF\x1FSPA_STATUS_NEED_DATA',1,b'\xFF\xFF\xFF\x1FSPA_STATUS_OK',0,b'\xFF\xFF\xFF\x1FSPA_STATUS_STOPPED',4,b'\xFF\xFF\xFF\x1FSPA_VERSION_HANDLE',0,b'\xFF\xFF\xFF\x1FSPA_VERSION_LOOP_CONTROL_METHODS',1,b'\xFF\xFF\xFF\x1FSPA_VERSION_NODE_CALLBACKS',0,b'\xFF\xFF\xFF\x1FSPA_VERSION_NODE_EVENTS',0,b'\xFF\xFF\xFF\x1FSPA_VERSION_NODE_METHODS',0,b'\x00\x00\x08\x23spa_handle_factory_enum',0),
_struct_unions = ((b'\x00\x00\x00\xC0\x00\x00\x00\x02buffer',b'\x00\x00\x11\x11id',b'\x00\x00\xC2\x11buffer',b'\x00\x00\xE0\x11metas',b'\x00\x00\xE2\x11header',b'\x00\x00\xCB\x11datas',b'\x00\x00\xC6\x11chunks',b'\x00\x00\x19\x11data',b'\x00\x00\x3C\x11data_size'),(b'\x00\x00\x00\xC2\x00\x00\x00\x02spa_buffer',b'\x00\x00\x11\x11n_metas',b'\x00\x00\x11\x11n_datas',b'\x00\x00\xDE\x11metas',b'\x00\x00\xC9\x11datas'),(b'\x00\x00\x00\xC3\x00\x00\x00\x02spa_callbacks',b'\x00\x00\x97\x11funcs',b'\x00\x00\x19\x11data'),(b'\x00\x00\x00\xC5\x00\x00\x00\x02spa_chunk',b'\x00\x00\x11\x11offset',b'\x00\x00\x11\x11size',b'\x00\x00\xBD\x11stride',b'\x00\x00\xBD\x11flags'),(b'\x00\x00\x00\xC8\x00\x00\x00\x10spa_command',),(b'\x00\x00\x00\xCA\x00\x00\x00\x02spa_data',b'\x00\x00\x11\x11type',b'\x00\x00\x11\x11flags',b'\x00\x00\xBE\x11fd',b'\x00\x00\x11\x11mapoffset',b'\x00\x00\x11\x11maxsize',b'\x00\x00\x19\x11data',b'\x00\x00\xC4\x11chunk'),(b'\x00\x00\x00\xCD\x00\x00\x00\x10spa_dict',),(b'\x00\x00\x00\xCE\x00\x00\x00\x10spa_event',),(b'\x00\x00\x00\xCF\x00\x00\x00\x02spa_fraction',b'\x00\x00\x11\x11num',b'\x00\x00\x11\x11denom'),(b'\x00\x00\x00\xD0\x00\x00\x00\x02spa_handle',b'\x00\x00\x11\x11version',b'\x00\x00\xAA\x11get_interface',b'\x00\x00\xA9\x11clear'),(b'\x00\x00\x00\xD1\x00\x00\x00\x02spa_handle_factory',b'\x00\x00\x11\x11version',b'\x00\x00\x05\x11name',b'\x00\x00\x0F\x11info',b'\x00\x00\xBF\x11get_size',b'\x00\x00\xAB\x11init',b'\x00\x00\xAC\x11enum_interface_info'),(b'\x00\x00\x00\xD2\x00\x00\x00\x02spa_hook',b'\x00\x00\xD9\x11link',b'\x00\x00\xC3\x11cb',b'\x00\x00\xEC\x11removed',b'\x00\x00\x19\x11priv'),(b'\x00\x00\x00\xD3\x00\x00\x00\x02spa_hook_list',b'\x00\x00\xD9\x11list'),(b'\x00\x00\x00\xD4\x00\x00\x00\x02spa_interface',b'\x00\x00\x05\x11type',b'\x00\x00\x11\x11version',b'\x00\x00\xC3\x11cb'),(b'\x00\x00\x00\xD6\x00\x00\x00\x10spa_interface_info',),(b'\x00\x00\x00\xD7\x00\x00\x00\x02spa_io_buffers',b'\x00\x00\xBD\x11status',b'\x00\x00\x11\x11buffer_id'),(b'\x00\x00\x00\xD9\x00\x00\x00\x02spa_list',b'\x00\x00\xD8\x11next',b'\x00\x00\xD8\x11prev'),(b'\x00\x00\x00\xDA\x00\x00\x00\x02spa_log',b'\x00\x00\xD4\x11iface',b'\x00\x00\x8C\x11level'),(b'\x00\x00\x00\xDB\x00\x00\x00\x02spa_log_methods',b'\x00\x00\x11\x11version',b'\x00\x00\xEF\x11log'),(b'\x00\x00\x00\xDC\x00\x00\x00\x10spa_loop_control_hooks',),(b'\x00\x00\x00\xDD\x00\x00\x00\x02spa_loop_control_methods',b'\x00\x00\x11\x11version',b'\x00\x00\xAD\x11get_fd',b'\x00\x00\xF2\x11add_hook',b'\x00\x00\xED\x11enter',b'\x00\x00\xED\x11leave',b'\x00\x00\xB3\x11iterate',b'\x00\x00\xAD\x11check'),(b'\x00\x00\x00\xDF\x00\x00\x00\x02spa_meta',b'\x00\x00\x11\x11type',b'\x00\x00\x11\x11size',b'\x00\x00\x19\x11data'),(b'\x00\x00\x00\xE2\x00\x00\x00\x02spa_meta_header',b'\x00\x00\x11\x11flags',b'\x00\x00\x11\x11offset',b'\x00\x00\xBE\x11pts',b'\x00\x00\xBE\x11dts_offset',b'\x00\x00\x76\x11seq'),(b'\x00\x00\x00\xE3\x00\x00\x00\x02spa_node_callbacks',b'\x00\x00\x11\x11version',b'\x00\x00\xB3\x11ready',b'\x00\x00\xB9\x11reuse_buffer',b'\x00\x00\xBC\x11xrun'),(b'\x00\x00\x00\xE4\x00\x00\x00\x02spa_node_events',b'\x00\x00\x11\x11version',b'\x00\x00\xF3\x11info',b'\x00\x00\xEE\x11port_info',b'\x00\x00\xF0\x11result',b'\x00\x00\xF1\x11event'),(b'\x00\x00\x00\xE5\x00\x00\x00\x10spa_node_info',),(b'\x00\x00\x00\xE6\x00\x00\x00\x02spa_node_methods',b'\x00\x00\x11\x11version',b'\x00\x00\xB7\x11add_listener',b'\x00\x00\xB8\x11set_callbacks',b'\x00\x00\xB3\x11sync',b'\x00\x00\xB5\x11enum_params',b'\x00\x00\xBA\x11set_param',b'\x00\x00\xBB\x11set_io',b'\x00\x00\xB6\x11send_command',b'\x00\x00\xAF\x11add_port',b'\x00\x00\xAE\x11remove_port',b'\x00\x00\xB4\x11port_enum_params',b'\x00\x00\xB1\x11port_set_param',b'\x00\x00\xB0\x11port_use_buffers',b'\x00\x00\xB2\x11port_set_io',b'\x00\x00\xB9\x11port_reuse_buffer',b'\x00\x00\xAD\x11process'),(b'\x00\x00\x00\xE8\x00\x00\x00\x10spa_param_info',),(b'\x00\x00\x00\xE9\x00\x00\x00\x10spa_pod',),(b'\x00\x00\x00\xEA\x00\x00\x00\x02spa_port_info',b'\x00\x00\x76\x11change_mask',b'\x00\x00\x76\x11flags',b'\x00\x00\xCF\x11rate',b'\x00\x00\x0F\x11props',b'\x00\x00\xE7\x11params',b'\x00\x00\x11\x11n_params'),(b'\x00\x00\x00\xEB\x00\x00\x00\x02spa_support',b'\x00\x00\x05\x11type',b'\x00\x00\x19\x11data')),
_enums = (b'\x00\x00\x00\x1D\x00\x00\x00\x16spa_direction\x00SPA_DIRECTION_INPUT,SPA_DIRECTION_OUTPUT',b'\x00\x00\x00\xA8\x00\x00\x00\x16spa_io_type\x00SPA_IO_Invalid,SPA_IO_Buffers,SPA_IO_Range,SPA_IO_Clock,SPA_IO_Latency,SPA_IO_Control,SPA_IO_Notify,SPA_IO_Position,SPA_IO_RateMatch,SPA_IO_Memory',b'\x00\x00\x00\x8C\x00\x00\x00\x16spa_log_level\x00SPA_LOG_LEVEL_NONE,SPA_LOG_LEVEL_ERROR,SPA_LOG_LEVEL_WARN,SPA_LOG_LEVEL_INFO,SPA_LOG_LEVEL_DEBUG,SPA_LOG_LEVEL_TRACE'),
)
#!/usr/bin/env python3
# Build out-of-line CFFI "ABI mode" module for SPA
# Because this uses ABI mode, it does not depend on an actual compiler,
# but not all C definitions may be recognized.
from cffi import FFI
ffi = FFI()
ffi.set_source('spa._cffi', None)
ffi.cdef(r'''
struct spa_dict;
struct spa_interface_info;
struct spa_fraction {
uint32_t num;
uint32_t denom;
};
/**
* Extra supporting infrastructure passed to the init() function of
* a factory. It can be extra information or interfaces such as logging.
*/
struct spa_support {
const char *type; /*< the type of the support item */
void *data; /*< specific data for the item */
};
struct spa_handle {
#define SPA_VERSION_HANDLE 0
uint32_t version;
/**
* Get the interface provided by \a handle with \a type.
*
* \a interface is always a struct spa_interface but depending on
* \a type, the struct might contain other information.
*
* \param handle a spa_handle
* \param type the interface type
* \param interface result to hold the interface.
* \return 0 on success
* -ENOTSUP when there are no interfaces
* -EINVAL when handle or info is NULL
*/
int (*get_interface) (struct spa_handle *handle, const char *type, void **interface);
/**
* Clean up the memory of \a handle. After this, \a handle should not be used
* anymore.
*
* \param handle a pointer to memory
* \return 0 on success
*/
int (*clear) (struct spa_handle *handle);
};
struct spa_handle_factory {
uint32_t version;
const char *name;
const struct spa_dict *info;
size_t (*get_size) (const struct spa_handle_factory *factory,
const struct spa_dict *params);
int (*init) (const struct spa_handle_factory *factory,
struct spa_handle *handle,
const struct spa_dict *info,
const struct spa_support *support,
uint32_t n_support);
int (*enum_interface_info) (const struct spa_handle_factory *factory,
const struct spa_interface_info **info,
uint32_t *index);
};
int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index);
struct spa_callbacks {
const void *funcs;
void *data;
};
struct spa_list {
struct spa_list *next;
struct spa_list *prev;
};
/** \struct spa_hook_list
* A list of hooks. This struct is primarily used by
* implementation that use multiple caller-provided \ref spa_hook. */
struct spa_hook_list {
struct spa_list list;
};
/** \struct spa_hook
* A hook, contains the structure with functions and the data passed
* to the functions.
*
* A hook should be treated as opaque by the caller.
*/
struct spa_hook {
struct spa_list link;
struct spa_callbacks cb;
/** callback and data for the hook list, private to the
* hook_list implementor */
void (*removed) (struct spa_hook *hook);
void *priv;
};
struct spa_interface {
const char *type;
uint32_t version;
struct spa_callbacks cb;
};
''')
# Log interface
ffi.cdef(r'''
enum spa_log_level {
SPA_LOG_LEVEL_NONE = 0,
SPA_LOG_LEVEL_ERROR,
SPA_LOG_LEVEL_WARN,
SPA_LOG_LEVEL_INFO,
SPA_LOG_LEVEL_DEBUG,
SPA_LOG_LEVEL_TRACE,
};
struct spa_log {
struct spa_interface iface;
enum spa_log_level level;
};
struct spa_log_methods {
uint32_t version;
void (*log) (void *object,
enum spa_log_level level,
const char *file,
int line,
const char *func,
const char *fmt, ...);
// ...
};
''')
# Loop interfaces
ffi.cdef(r'''
/**
* Control an event loop
*/
struct spa_loop_control_methods {
/* the version of this structure. This can be used to expand this
* structure in the future */
#define SPA_VERSION_LOOP_CONTROL_METHODS 1
uint32_t version;
int (*get_fd) (void *object);
/** Add a hook
* \param ctrl the control to change
* \param hooks the hooks to add
*
* Adds hooks to the loop controlled by \a ctrl.
*/
void (*add_hook) (void *object,
struct spa_hook *hook,
const struct spa_loop_control_hooks *hooks,
void *data);
/** Enter a loop
* \param ctrl the control
*
* Start an iteration of the loop. This function should be called
* before calling iterate and is typically used to capture the thread
* that this loop will run in.
*/
void (*enter) (void *object);
/** Leave a loop
* \param ctrl the control
*
* Ends the iteration of a loop. This should be called after calling
* iterate.
*/
void (*leave) (void *object);
/** Perform one iteration of the loop.
* \param ctrl the control
* \param timeout an optional timeout in milliseconds.
* 0 for no timeout, -1 for infinite timeout.
*
* This function will block
* up to \a timeout milliseconds and then dispatch the fds with activity.
* The number of dispatched fds is returned.
*/
int (*iterate) (void *object, int timeout);
/** Check context of the loop
* \param ctrl the control
*
* This function will check if the current thread is currently the
* one that did the enter call. Since version 1:1.
*
* returns 1 on success, 0 or negative errno value on error.
*/
int (*check) (void *object);
};
''')
# Node interface
ffi.cdef(r'''
struct spa_io_buffers {
#define SPA_STATUS_OK 0
#define SPA_STATUS_NEED_DATA 1
#define SPA_STATUS_HAVE_DATA 2
#define SPA_STATUS_STOPPED 4
#define SPA_STATUS_DRAINED 8
int32_t status; /**< the status code */
uint32_t buffer_id; /**< a buffer id */
};
enum spa_direction {
SPA_DIRECTION_INPUT = 0,
SPA_DIRECTION_OUTPUT = 1,
};
/** Different IO area types */
enum spa_io_type {
SPA_IO_Invalid,
SPA_IO_Buffers, /**< area to exchange buffers, struct spa_io_buffers */
SPA_IO_Range, /**< expected byte range, struct spa_io_range */
SPA_IO_Clock, /**< area to update clock information, struct spa_io_clock */
SPA_IO_Latency, /**< latency reporting, struct spa_io_latency */
SPA_IO_Control, /**< area for control messages, struct spa_io_sequence */
SPA_IO_Notify, /**< area for notify messages, struct spa_io_sequence */
SPA_IO_Position, /**< position information in the graph, struct spa_io_position */
SPA_IO_RateMatch, /**< rate matching between nodes, struct spa_io_rate_match */
SPA_IO_Memory, /**< memory pointer, struct spa_io_memory */
};
/**
* Node methods
*/
struct spa_node_methods {
/* the version of the node methods. This can be used to expand this
* structure in the future */
#define SPA_VERSION_NODE_METHODS 0
uint32_t version;
/**
* Adds an event listener on \a node.
*
* Setting the events will trigger the info event and a
* port_info event for each managed port on the new
* listener.
*
* \param node a #spa_node
* \param listener a listener
* \param events a struct \ref spa_node_events
* \param data data passed as first argument in functions of \a events
* \return 0 on success
* < 0 errno on error
*/
int (*add_listener) (void *object,
struct spa_hook *listener,
const struct spa_node_events *events,
void *data);
/**
* Set callbacks to on \a node.
* if \a callbacks is NULL, the current callbacks are removed.
*
* This function must be called from the main thread.
*
* All callbacks are called from the data thread.
*
* \param node a spa_node
* \param callbacks callbacks to set
* \return 0 on success
* -EINVAL when node is NULL
*/
int (*set_callbacks) (void *object,
const struct spa_node_callbacks *callbacks,
void *data);
/**
* Perform a sync operation.
*
* This method will emit the result event with the given sequence
* number synchronously or with the returned async return value
* asynchronously.
*
* Because all methods are serialized in the node, this can be used
* to wait for completion of all previous method calls.
*
* \param seq a sequence number
* \return 0 on success
* -EINVAL when node is NULL
* an async result
*/
int (*sync) (void *object, int seq);
/**
* Enumerate the parameters of a node.
*
* Parameters are identified with an \a id. Some parameters can have
* multiple values, see the documentation of the parameter id.
*
* Parameters can be filtered by passing a non-NULL \a filter.
*
* The function will emit the result event up to \a max times with
* the result value. The seq in the result will either be the \a seq
* number when executed synchronously or the async return value of
* this function when executed asynchronously.
*
* This function must be called from the main thread.
*
* \param node a \ref spa_node
* \param seq a sequence number to pass to the result event when
* this method is executed synchronously.
* \param id the param id to enumerate
* \param start the index of enumeration, pass 0 for the first item
* \param max the maximum number of parameters to enumerate
* \param filter and optional filter to use
*
* \return 0 when no more items can be iterated.
* -EINVAL when invalid arguments are given
* -ENOENT the parameter \a id is unknown
* -ENOTSUP when there are no parameters
* implemented on \a node
* an async return value when the result event will be
* emitted later.
*/
int (*enum_params) (void *object, int seq,
uint32_t id, uint32_t start, uint32_t max,
const struct spa_pod *filter);
/**
* Set the configurable parameter in \a node.
*
* Usually, \a param will be obtained from enum_params and then
* modified but it is also possible to set another spa_pod
* as long as its keys and types match a supported object.
*
* Objects with property keys that are not known are ignored.
*
* This function must be called from the main thread.
*
* \param node a \ref spa_node
* \param id the parameter id to configure
* \param flags additional flags
* \param param the parameter to configure
*
* \return 0 on success
* -EINVAL when node is NULL
* -ENOTSUP when there are no parameters implemented on \a node
* -ENOENT the parameter is unknown
*/
int (*set_param) (void *object,
uint32_t id, uint32_t flags,
const struct spa_pod *param);
/**
* Configure the given memory area with \a id on \a node. This
* structure is allocated by the host and is used to exchange
* data and parameters with the node.
*
* Setting an \a io of NULL will disable the node io.
*
* This function must be called from the main thread.
*
* \param id the id of the io area, the available ids can be
* enumerated with the node parameters.
* \param data a io area memory
* \param size the size of \a data
* \return 0 on success
* -EINVAL when invalid input is given
* -ENOENT when \a id is unknown
* -ENOSPC when \a size is too small
*/
int (*set_io) (void *object,
uint32_t id, void *data, size_t size);
/**
* Send a command to a node.
*
* Upon completion, a command might change the state of a node.
*
* This function must be called from the main thread.
*
* \param node a spa_node
* \param command a spa_command
* \return 0 on success
* -EINVAL when node or command is NULL
* -ENOTSUP when this node can't process commands
* -EINVAL \a command is an invalid command
*/
int (*send_command) (void *object, const struct spa_command *command);
/**
* Make a new port with \a port_id. The caller should use the lowest unused
* port id for the given \a direction.
*
* Port ids should be between 0 and max_ports as obtained from the info
* event.
*
* This function must be called from the main thread.
*
* \param node a spa_node
* \param direction a enum \ref spa_direction
* \param port_id an unused port id
* \param props extra properties
* \return 0 on success
* -EINVAL when node is NULL
*/
int (*add_port) (void *object,
enum spa_direction direction, uint32_t port_id,
const struct spa_dict *props);
/**
* Remove a port with \a port_id.
*
* \param node a spa_node
* \param direction a enum \ref spa_direction
* \param port_id a port id
* \return 0 on success
* -EINVAL when node is NULL or when port_id is unknown or
* when the port can't be removed.
*/
int (*remove_port) (void *object,
enum spa_direction direction, uint32_t port_id);
/**
* Enumerate all possible parameters of \a id on \a port_id of \a node
* that are compatible with \a filter.
*
* The result parameters can be queried and modified and ultimately be used
* to call port_set_param.
*
* The function will emit the result event up to \a max times with
* the result value. The seq in the result event will either be the
* \a seq number when executed synchronously or the async return
* value of this function when executed asynchronously.
*
* This function must be called from the main thread.
*
* \param node a spa_node
* \param seq a sequence number to pass to the result event when
* this method is executed synchronously.
* \param direction an spa_direction
* \param port_id the port to query
* \param id the parameter id to query
* \param start the first index to query, 0 to get the first item
* \param max the maximum number of params to query
* \param filter a parameter filter or NULL for no filter
*
* \return 0 when no more items can be iterated.
* -EINVAL when invalid parameters are given
* -ENOENT when \a id is unknown
* an async return value when the result event will be
* emitted later.
*/
int (*port_enum_params) (void *object, int seq,
enum spa_direction direction, uint32_t port_id,
uint32_t id, uint32_t start, uint32_t max,
const struct spa_pod *filter);
/**
* Set a parameter on \a port_id of \a node.
*
* When \a param is NULL, the parameter will be unset.
*
* This function must be called from the main thread. The node muse be paused
* or the port SPA_IO_Buffers area is NULL when this function is called with
* a param that changes the processing state (like a format change).
*
* \param node a struct \ref spa_node
* \param direction a enum \ref spa_direction
* \param port_id the port to configure
* \param id the parameter id to set
* \param flags optional flags
* \param param a struct \ref spa_pod with the parameter to set
* \return 0 on success
* 1 on success, the value of \a param might have been
* changed depending on \a flags and the final value can be found by
* doing port_enum_params.
* -EINVAL when node is NULL or invalid arguments are given
* -ESRCH when one of the mandatory param
* properties is not specified and SPA_NODE_PARAM_FLAG_FIXATE was
* not set in \a flags.
* -ESRCH when the type or size of a property is not correct.
* -ENOENT when the param id is not found
*/
int (*port_set_param) (void *object,
enum spa_direction direction,
uint32_t port_id,
uint32_t id, uint32_t flags,
const struct spa_pod *param);
/**
* Tell the port to use the given buffers
*
* When \a flags contains SPA_NODE_BUFFERS_FLAG_ALLOC, the data
* in the buffers should point to an array of at least 1 data entry
* with the desired supported type that will be filled by this function.
*
* The port should also have a spa_io_buffers io area configured to exchange
* the buffers with the port.
*
* For an input port, all the buffers will remain dequeued.
* Once a buffer has been queued on a port in the spa_io_buffers,
* it should not be reused until the reuse_buffer callback is notified
* or when the buffer has been returned in the spa_io_buffers of
* the port.
*
* For output ports, all buffers will be queued in the port. When process
* returns SPA_STATUS_HAVE_DATA, buffers are available in one or more
* of the spa_io_buffers areas.
*
* When a buffer can be reused, port_reuse_buffer() should be called or the
* buffer_id should be placed in the spa_io_buffers area before calling
* process.
*
* Passing NULL as \a buffers will remove the reference that the port has
* on the buffers.
*
* When this function returns async, use the spa_node_sync operation to
* wait for completion.
*
* This function must be called from the main thread. The node muse be paused
* or the port SPA_IO_Buffers area is NULL when this function is called.
*
* \param object an object implementing the interface
* \param direction a port direction
* \param port_id a port id
* \param flags extra flags
* \param buffers an array of buffer pointers
* \param n_buffers number of elements in \a buffers
* \return 0 on success
*/
int (*port_use_buffers) (void *object,
enum spa_direction direction,
uint32_t port_id,
uint32_t flags,
struct spa_buffer **buffers,
uint32_t n_buffers);
/**
* Configure the given memory area with \a id on \a port_id. This
* structure is allocated by the host and is used to exchange
* data and parameters with the port.
*
* Setting an \a io of NULL will disable the port io.
*
* This function must be called from the main thread.
*
* This function can be called when the node is running and the node
* must be prepared to handle changes in io areas while running. This
* is normally done by synchronizing the port io updates with the
* data processing loop.
*
* \param direction a spa_direction
* \param port_id a port id
* \param id the id of the io area, the available ids can be
* enumerated with the port parameters.
* \param data a io area memory
* \param size the size of \a data
* \return 0 on success
* -EINVAL when invalid input is given
* -ENOENT when \a id is unknown
* -ENOSPC when \a size is too small
*/
int (*port_set_io) (void *object,
enum spa_direction direction,
uint32_t port_id,
uint32_t id,
void *data, size_t size);
/**
* Tell an output port to reuse a buffer.
*
* This function must be called from the data thread.
*
* \param node a spa_node
* \param port_id a port id
* \param buffer_id a buffer id to reuse
* \return 0 on success
* -EINVAL when node is NULL
*/
int (*port_reuse_buffer) (void *object, uint32_t port_id, uint32_t buffer_id);
/**
* Process the node
*
* This function must be called from the data thread.
*
* Output io areas with SPA_STATUS_NEED_DATA will recycle the
* buffers if any.
*
* Input areas with SPA_STATUS_HAVE_DATA are consumed if possible
* and the status is set to SPA_STATUS_NEED_DATA or SPA_STATUS_OK.
*
* When the node has new output buffers, the SPA_STATUS_HAVE_DATA
* bit will be set.
*
* When the node can accept new input in the next cycle, the
* SPA_STATUS_NEED_DATA bit will be set.
*
* Note that the node might return SPA_STATUS_NEED_DATA even when
* no input ports have this status. This means that the amount of
* data still available on the input ports is likely not going to
* be enough for the next cycle and the host might need to prefetch
* data for the next cycle.
*/
int (*process) (void *object);
};
/** Node callbacks
*
* Callbacks are called from the real-time data thread. Only
* one callback structure can be set on an spa_node.
*/
struct spa_node_callbacks {
#define SPA_VERSION_NODE_CALLBACKS 0
uint32_t version;
/**
* \param node a spa_node
*
* The node is ready for processing.
*
* When this function is NULL, synchronous operation is requested
* on the ports.
*/
int (*ready) (void *data, int state);
/**
* \param node a spa_node
* \param port_id an input port_id
* \param buffer_id the buffer id to be reused
*
* The node has a buffer that can be reused.
*
* When this function is NULL, the buffers to reuse will be set in
* the io area of the input ports.
*/
int (*reuse_buffer) (void *data,
uint32_t port_id,
uint32_t buffer_id);
/**
* \param data user data
* \param trigger the timestamp in microseconds when the xrun happened
* \param delay the amount of microseconds of xrun.
* \param info an object with extra info (NULL for now)
*
* The node has encountered an over or underrun
*
* The info contains an object with more information
*/
int (*xrun) (void *data, uint64_t trigger, uint64_t delay,
struct spa_pod *info);
};
/** events from the spa_node.
*
* All event are called from the main thread and multiple
* listeners can be registered for the events with
* spa_node_add_listener().
*/
struct spa_node_events {
#define SPA_VERSION_NODE_EVENTS 0
uint32_t version; /**< version of this structure */
/** Emitted when info changes */
void (*info) (void *data, const struct spa_node_info *info);
/** Emitted when port info changes, NULL when port is removed */
void (*port_info) (void *data,
enum spa_direction direction, uint32_t port,
const struct spa_port_info *info);
/** notify a result.
*
* Some methods will trigger a result event with an optional
* result of the given type. Look at the documentation of the
* method to know when to expect a result event.
*
* The result event can be called synchronously, as an event
* called from inside the method itself, in which case the seq
* number passed to the method will be passed unchanged.
*
* The result event will be called asynchronously when the
* method returned an async return value. In this case, the seq
* number in the result will match the async return value of
* the method call. Users should match the seq number from
* request to the reply.
*/
void (*result) (void *data, int seq, int res,
uint32_t type, const void *result);
/**
* \param node a spa_node
* \param event the event that was emitted
*
* This will be called when an out-of-bound event is notified
* on \a node.
*/
void (*event) (void *data, const struct spa_event *event);
};
/**
* Port information structure
*
* Contains the basic port information.
*/
struct spa_port_info {
#define SPA_PORT_CHANGE_MASK_FLAGS 1
#define SPA_PORT_CHANGE_MASK_RATE 2
#define SPA_PORT_CHANGE_MASK_PROPS 4
#define SPA_PORT_CHANGE_MASK_PARAMS 8
uint64_t change_mask;
#define SPA_PORT_FLAG_REMOVABLE 1 /**< port can be removed */
#define SPA_PORT_FLAG_OPTIONAL 2 /**< processing on port is optional */
#define SPA_PORT_FLAG_CAN_ALLOC_BUFFERS 4 /**< the port can allocate buffer data */
#define SPA_PORT_FLAG_IN_PLACE 8 /**< the port can process data in-place and
* will need a writable input buffer */
#define SPA_PORT_FLAG_NO_REF 16 /**< the port does not keep a ref on the buffer.
* This means the node will always completely
* consume the input buffer and it will be
* recycled after process. */
#define SPA_PORT_FLAG_LIVE 32 /**< output buffers from this port are
* timestamped against a live clock. */
#define SPA_PORT_FLAG_PHYSICAL 64 /**< connects to some device */
#define SPA_PORT_FLAG_TERMINAL 128 /**< data was not created from this port
* or will not be made available on another
* port */
#define SPA_PORT_FLAG_DYNAMIC_DATA 256 /**< data pointer on buffers can be changed.
* Only the buffer data marked as DYNAMIC
* can be changed. */
uint64_t flags; /**< port flags */
struct spa_fraction rate; /**< rate of sequence numbers on port */
const struct spa_dict *props; /**< extra port properties */
struct spa_param_info *params; /**< parameter information */
uint32_t n_params; /**< number of items in \a params */
};
''')
# Custom defines
ffi.cdef(r'''
/**
* A metadata element.
*
* This structure is available on the buffer structure and contains
* the type of the metadata and a pointer/size to the actual metadata
* itself.
*/
struct spa_meta {
uint32_t type; /**< metadata type, one of enum spa_meta_type */
uint32_t size; /**< size of metadata */
void *data; /**< pointer to metadata */
};
/**
* Describes essential buffer header metadata such as flags and
* timestamps.
*/
struct spa_meta_header {
#define SPA_META_HEADER_FLAG_DISCONT 1u /**< data is not continuous with previous buffer */
#define SPA_META_HEADER_FLAG_CORRUPTED 2u /**< data might be corrupted */
#define SPA_META_HEADER_FLAG_MARKER 4u /**< media specific marker */
#define SPA_META_HEADER_FLAG_HEADER 8u /**< data contains a codec specific header */
#define SPA_META_HEADER_FLAG_GAP 16u /**< data contains media neutral data */
#define SPA_META_HEADER_FLAG_DELTA_UNIT 32u /**< cannot be decoded independently */
uint32_t flags; /**< flags */
uint32_t offset; /**< offset in current cycle */
int64_t pts; /**< presentation timestamp in nanoseconds */
int64_t dts_offset; /**< decoding timestamp as a difference with pts */
uint64_t seq; /**< sequence number, increments with a
* media specific frequency */
};
/** Chunk of memory, can change for each buffer */
struct spa_chunk {
uint32_t offset; /**< offset of valid data. Should be taken
* modulo the data maxsize to get the offset
* in the data memory. */
uint32_t size; /**< size of valid data. Should be clamped to
* maxsize. */
int32_t stride; /**< stride of valid data */
#define SPA_CHUNK_FLAG_NONE 0u
#define SPA_CHUNK_FLAG_CORRUPTED 1u /**< chunk data is corrupted in some way */
#define SPA_CHUNK_FLAG_EMPTY 2u /**< chunk data is empty with media specific
* neutral data such as silence or black. This
* could be used to optimize processing. */
int32_t flags; /**< chunk flags */
};
/** Data for a buffer this stays constant for a buffer */
struct spa_data {
uint32_t type; /**< memory type, one of enum spa_data_type, when
* allocating memory, the type contains a bitmask
* of allowed types. SPA_ID_INVALID is a special
* value for the allocator to indicate that the
* other side did not explicitly specify any
* supported data types. It should probably use
* a memory type that does not require special
* handling in addition to simple mmap/munmap. */
#define SPA_DATA_FLAG_NONE 0u
#define SPA_DATA_FLAG_READABLE 1u /**< data is readable */
#define SPA_DATA_FLAG_WRITABLE 2u /**< data is writable */
#define SPA_DATA_FLAG_DYNAMIC 4u /**< data pointer can be changed */
#define SPA_DATA_FLAG_READWRITE 3u
uint32_t flags; /**< data flags */
int64_t fd; /**< optional fd for data */
uint32_t mapoffset; /**< offset to map fd at */
uint32_t maxsize; /**< max size of data */
void *data; /**< optional data pointer */
struct spa_chunk *chunk; /**< valid chunk of memory */
};
/** A Buffer */
struct spa_buffer {
uint32_t n_metas; /**< number of metadata */
uint32_t n_datas; /**< number of data members */
struct spa_meta *metas; /**< array of metadata */
struct spa_data *datas; /**< array of data members */
};
struct buffer {
uint32_t id;
// Owned buffer
struct spa_buffer buffer;
struct spa_meta metas[1];
struct spa_meta_header header;
struct spa_data datas[1];
struct spa_chunk chunks[1];
// Data
void *data;
size_t data_size;
};
''')
if __name__ == "__main__":
ffi.compile(verbose=True)
# SPA constants
# These could also be pulled from CFFI.
SPA_STATUS_OK = 0
SPA_STATUS_NEED_DATA = 1
SPA_STATUS_HAVE_DATA = 2
SPA_TYPE_OBJECT__FORMAT = 0x00040003
SPA_PARAM_ENUM_FORMAT = 3
SPA_PARAM__FORMAT = 4
SPA_FORMAT__MEDIA_TYPE = 1
SPA_FORMAT__MEDIA_SUBTYPE = 2
SPA_MEDIA_TYPE__VIDEO = 2
SPA_MEDIA_SUBTYPE__RAW = 1
SPA_FORMAT_VIDEO__FORMAT = 0x00020001
SPA_FORMAT_VIDEO__SIZE = 0x00020003
SPA_FORMAT_VIDEO__FRAMERATE = 0x00020004
SPA_VIDEO_FORMAT__RGB = 15
SPA_TYPE_COMMAND__NODE = 0x00030002
SPA_NODE_COMMAND__PAUSE = 1
SPA_NODE_COMMAND__START = 2
SPA_DIRECTION_INPUT = 0
SPA_DIRECTION_OUTPUT = 1
# SPA POD types
# See https://docs.pipewire.org/page_spa_pod.html for specification.
import struct
from typing import Any
class Pod:
TYPE = NotImplemented
def __repr__(self) -> str:
return f'Pod<{self.TYPE}>'
def __bytes__(self) -> bytes:
data = self._serialize_body()
padding = -len(data) % 8
return b''.join([
struct.pack('=II', len(data), self.TYPE),
data,
b'\0' * padding,
])
def _serialize_body(self) -> bytes:
"""Serialize the POD body for this type."""
raise NotImplementedError()
class PodNone(Pod):
TYPE = 1
def _serialize_body(self) -> bytes:
return b''
class PodBool(Pod):
TYPE = 2
def __init__(self, value: bool) -> None:
self.value = bool(value)
def _serialize_body(self) -> bytes:
return struct.pack('=I', int(self.value))
class PodId(Pod):
TYPE = 3
def __init__(self, value: int) -> None:
self.value = int(value)
def _serialize_body(self) -> bytes:
return struct.pack('=I', self.value)
class PodInt(Pod):
TYPE = 4
def __init__(self, value: int) -> None:
self.value = int(value)
def _serialize_body(self) -> bytes:
return struct.pack('=i', self.value)
class PodLong(Pod):
TYPE = 5
def __init__(self, value: int) -> None:
self.value = int(value)
def _serialize_body(self) -> bytes:
return struct.pack('=q', self.value)
class PodFloat(Pod):
TYPE = 6
def __init__(self, value: float) -> None:
self.value = float(value)
def _serialize_body(self) -> bytes:
return struct.pack('=f', self.value)
class PodDouble(Pod):
TYPE = 7
def __init__(self, value: float) -> None:
self.value = float(value)
def _serialize_body(self) -> bytes:
return struct.pack('=d', self.value)
class PodString(Pod):
TYPE = 8
def __init__(self, value: str) -> None:
self.value = str(value)
def _serialize_body(self) -> bytes:
return self.value.encode('utf-8') + b'\0'
class PodBytes(Pod):
TYPE = 9
def __init__(self, value: bytes) -> None:
self.value = value
def _serialize_body(self) -> bytes:
return self.value
class PodRectangle(Pod):
TYPE = 10
def __init__(self, width: int, height: int) -> None:
self.width = width
self.height = height
def _serialize_body(self) -> bytes:
return struct.pack('=II', self.width, self.height)
class PodFraction(Pod):
TYPE = 11
def __init__(self, numerator: int, denominator: int) -> None:
self.numerator = numerator
self.denominator = denominator
def _serialize_body(self) -> bytes:
return struct.pack('=II', self.numerator, self.denominator)
class PodBitmap(Pod):
TYPE = 12
def __init__(self, value: bytes) -> None:
self.value = bytes(value)
def _serialize_body(self) -> bytes:
return self.value
class PodArray(Pod):
TYPE = 13
# Not implemented
class PodStruct(Pod):
TYPE = 14
# Not implemented
class PodObject(Pod):
TYPE = 15
def __init__(self, object_type: int, object_id: int, properties: dict = None) -> None:
self.object_type = object_type
self.object_id = object_id
self.properties = properties or {}
def _serialize_body(self) -> bytes:
data = [struct.pack('=II', self.object_type, self.object_id)]
for key, value in self.properties.items():
if not isinstance(value, Pod):
raise TypeError(f'{value!r} is not a Pod type')
flags = 0 # Not supported
data.extend([
struct.pack('=II', key, flags),
bytes(value),
])
return b''.join(data)
class PodSequence(Pod):
TYPE = 16
# Not implemented
class PodPointer(Pod):
TYPE = 17
def __init__(self, pointer_type: int, pointer_address: int) -> None:
self.type = pointer_type
self.address = pointer_address
def _serialize_body(self) -> bytes:
return struct.pack('=IIp', self.pointer_type, 0, self.pointer_address)
class PodFd(Pod):
TYPE = 18
def __init__(self, value: int) -> None:
self.value = value
def _serialize_body(self) -> bytes:
return struct.pack('=Q', self.value)
class PodChoice(Pod):
TYPE = 19
# Not implemented
class PodPod(Pod):
TYPE = 20
# Not implemented
#!/usr/bin/env python3
# Example of using SPA from Python
import errno
import signal
import threading
from spa import Plugin, LogInterface, LoopInterface, LoopControlInterface, LoopUtilsInterface, NodeInterface, ffi
from spa.constants import *
from spa.pod import PodObject, PodId, PodFraction, PodRectangle
N_BUFFERS = 8
WIDTH = 1280
HEIGHT = 720
import ctypes
import sdl2
import sdl2.ext
def main():
sdl2.ext.common.init()
window = sdl2.ext.Window("videotestsrc", (WIDTH, HEIGHT), flags=sdl2.SDL_WINDOW_SHOWN)
renderer = sdl2.ext.renderer.Renderer(window)
# Support plugins
n_support = 0
support = ffi.new('struct spa_support[]', 8)
support_plugin = Plugin.load_plugin('support/libspa-support.so')
log_factory = support_plugin.get_factory('support.log')
log_handle = log_factory.build(ffi.NULL, ffi.NULL, 0)
log = log_handle.get_interface(LogInterface)
print(f'Got {log!r}')
support[n_support].type = LogInterface.C_TYPE
support[n_support].data = log._ptr
n_support += 1
loop_factory = support_plugin.get_factory('support.loop')
loop_handle = loop_factory.build(ffi.NULL, support, n_support)
loop = loop_handle.get_interface(LoopInterface)
print(f'Got {loop!r}')
# This loop is the data loop
data_loop_type = ffi.new('char[]', b'Spa:Pointer:Interface:DataLoop')
support[n_support].type = data_loop_type
support[n_support].data = loop._ptr
n_support += 1
loop_control = loop_handle.get_interface(LoopControlInterface)
print(f'Got {loop_control!r}')
support[n_support].type = LoopControlInterface.C_TYPE
support[n_support].data = loop_control._ptr
n_support += 1
loop_utils = loop_handle.get_interface(LoopUtilsInterface)
print(f'Got {loop_utils!r}')
support[n_support].type = LoopUtilsInterface.C_TYPE
support[n_support].data = loop_utils._ptr
n_support += 1
log.error('Hello, world!')
# IO area
io = ffi.new('struct spa_io_buffers *', (0, 0xFFFFFFFF))
source_plugin = Plugin.load_plugin('videotestsrc/libspa-videotestsrc.so')
source_factory = source_plugin.get_factory('videotestsrc')
source_handle = source_factory.build(ffi.NULL, support, n_support)
source_node = source_handle.get_interface(NodeInterface)
print(f'Got {source_node!r}')
# Allocate buffer structures
bp = ffi.new('struct spa_buffer *[]', N_BUFFERS)
buffers = ffi.new('struct buffer[]', N_BUFFERS)
# extern "Python" int on_source_ready(void *, int);
@ffi.callback('int(void *, int)')
def on_source_ready(_data, _status) -> int:
res = source_node.process()
if res == SPA_STATUS_HAVE_DATA:
buffer_id = io[0].buffer_id
# FIXME: There has got to be a better way to get a ctypes pointer
addr = int(ffi.cast('uintptr_t', bp[buffer_id].datas[0].data))
data = ctypes.POINTER(ctypes.c_ubyte)(ctypes.c_ubyte.from_address(addr))
stride = bp[buffer_id].datas[0].chunk.stride
surface = sdl2.SDL_CreateRGBSurfaceWithFormatFrom(data, WIDTH, HEIGHT, 8, stride, sdl2.SDL_PIXELFORMAT_RGB24)
if not surface:
print(f'SDL_CreateRGBSurfaceWithFormatFrom failed: {sdl2.SDL_GetError()}')
io[0].status = SPA_STATUS_NEED_DATA
source_node.port_reuse_buffer(0, buffer_id)
return -errno.EIO
try:
texture = sdl2.ext.renderer.Texture(renderer, surface)
finally:
sdl2.SDL_FreeSurface(surface)
renderer.clear()
renderer.copy(texture)
renderer.present()
texture.destroy()
# Hand the buffer immediately back
io[0].status = SPA_STATUS_NEED_DATA
source_node.port_reuse_buffer(0, buffer_id)
return 0
# extern "Python" void on_source_port_info(void *, enum spa_direction, uint32_t, const struct spa_port_info *);
@ffi.callback('void(void *, enum spa_direction, uint32_t, const struct spa_port_info *)')
def on_source_port_info(_data, _direction, _port, info) -> None:
print(f'on_source_port_info: flags={info.flags}')
source_callbacks = ffi.new('struct spa_node_callbacks *', {'version': 0, 'ready': on_source_ready})
source_node.set_callbacks(source_callbacks, ffi.NULL)
print('set_callbacks')
source_listener = ffi.new('struct spa_hook *')
source_events = ffi.new('struct spa_node_events *', {'version': 0, 'port_info': on_source_port_info})
source_node.add_listener(source_listener, source_events, ffi.NULL)
print('add_listener')
source_node.port_set_io(source_plugin.lib.SPA_DIRECTION_OUTPUT, 0, source_plugin.lib.SPA_IO_Buffers, io)
print(f'port_set_io')
format_buffer = ffi.new('uint8_t[]', bytes(
PodObject(SPA_TYPE_OBJECT__FORMAT, SPA_PARAM_ENUM_FORMAT, {
SPA_FORMAT__MEDIA_TYPE: PodId(SPA_MEDIA_TYPE__VIDEO),
SPA_FORMAT__MEDIA_SUBTYPE: PodId(SPA_MEDIA_SUBTYPE__RAW),
SPA_FORMAT_VIDEO__FORMAT: PodId(SPA_VIDEO_FORMAT__RGB),
SPA_FORMAT_VIDEO__SIZE: PodRectangle(1280, 720),
SPA_FORMAT_VIDEO__FRAMERATE: PodFraction(25, 1),
})
))
source_node.port_set_param(SPA_DIRECTION_OUTPUT, 0, SPA_PARAM__FORMAT, 0, ffi.cast('struct spa_pod *', format_buffer))
buffers_data = []
for i in range(N_BUFFERS):
b = buffers[i]
b.data_size = 1280 * 720 * 4 # 32-bits per pixel
buffers_data.append(ffi.new('uint8_t[]', b.data_size))
b.data = buffers_data[-1]
# Essential buffer metadata (1 total)
b.metas[0].type = 1 # SPA_META_Header
b.metas[0].data = ffi.addressof(b.header)
b.metas[0].size = ffi.sizeof(b.header)
# Data chunks (1 total)
b.datas[0].type = 1 # SPA_DATA_MemPtr
b.datas[0].flags = 0
b.datas[0].fd = -1
b.datas[0].mapoffset = 0
b.datas[0].maxsize = 0
b.datas[0].data = b.data
b.datas[0].chunk = ffi.addressof(b.chunks[0])
b.datas[0].chunk.offset = 0
b.datas[0].chunk.size = b.data_size
b.datas[0].chunk.stride = 0
# Update `spa_buf``
b.buffer.metas = b.metas
b.buffer.n_metas = 1
b.buffer.datas = b.datas
b.buffer.n_datas = 1
bp[i] = ffi.addressof(b.buffer)
source_node.port_use_buffers(SPA_DIRECTION_OUTPUT, 0, 0, bp, N_BUFFERS)
running = True
def on_signal(signum, _frame):
nonlocal running
if signum == signal.SIGINT:
running = False
signal.signal(signal.SIGINT, on_signal)
def spa_event_loop():
nonlocal running
with loop_control:
source_node.send_command(SPA_NODE_COMMAND__START)
while running:
loop_control.iterate(-1)
source_node.send_command(SPA_NODE_COMMAND__PAUSE)
def sdl_event_loop():
nonlocal running
while running:
for event in sdl2.ext.common.get_events():
if event.type == sdl2.SDL_QUIT:
print("Quit")
running = False
break
window.refresh()
# FIXME: SPA event loop does not exit cleanly, requring forceful termination
t = threading.Thread(target=spa_event_loop)
t.start()
sdl_event_loop()
t.join()
window.close()
sdl2.common.quit()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment