Usage
Connecting to the sound server
Instantiation of the LibPulse class by the async context manager with the async with statement does the following:
The LibPulse instance is created and assigned as the target of the context manager.
The instance connects to the sound server.
The instance monitors the state of the connection by subscribing a callback to the sound server.
The c_context instance attribute is available as parameter to non-async functions.
The state instance attribute is updated by the state monitor callback whenever the callback is invoked by the sound server.
The context manager may raise LibPulseStateError while attempting the connection.
Session management
When the connection fails for any reason, the state monitor callback cancels the asyncio task of the async context manager. This causes the async context manager to terminate gracefully.
When the async context manager terminates because it got a CancelledError exception or because it terminates normally or because it got any other exception, all the asyncio tasks listed by libpulse_tasks are also cancelled allowing those tasks to do some clean up on exit.
libpulse_tasks is an attribute of the LibPulse instance and an instance of AsyncioTasks. The tasks listed by libpulse_tasks are the still active asyncio tasks that have been created by its create_task() method.
So libpulse_tasks can also be used by any application built upon libpulse to have tasks do some clean up upon termination of the async context manager. As an example, in the following code the task of the user_task coroutine is cancelled upon termination of the async context manager and “user_task got CancelledError” is printed:
import asyncio
import libpulse.libpulse as libpulse
async def user_task(ready, cancelled):
try:
ready.set_result(True)
# Run an infinite loop.
while True:
await asyncio.sleep(0.1)
except asyncio.CancelledError:
print('user_task got CancelledError')
cancelled.set_result(True)
pass
async def main():
async with libpulse.LibPulse('my libpulse') as lp_instance:
ready = lp_instance.loop.create_future()
cancelled = lp_instance.loop.create_future()
lp_instance.libpulse_tasks.create_task(user_task(ready, cancelled))
# Wait for 'user_task' to be ready.
await ready
await cancelled
asyncio.run(main())
Error handling
The return value of a libpulse function corresponding to a non-async pulse
function should be checked against None or PA_INVALID_INDEX when the
function returns an index.
All the LibPulse methods that are asyncio coroutines corresponding to pulse
async functions may raise LibPulseOperationError.
See also the Error Handling section of the PulseAudio documentation.
ctypes pulse structures
The parameters of some pulse functions are pointers to pulse structures. Here is an example showing how to build a ctypes pointer to the pa_sample_spec structure:
import ctypes as ct
import libpulse.libpulse as libpulse
# The 'pa_sample_spec' ctypes subclass of ct.Structure.
ct_struct_sample_spec = libpulse.struct_ctypes['pa_sample_spec']
# Instantiate ct_struct_sample_spec with (3, 44100, 2)
sample_spec = {'format': 3, 'rate': 44100, 'channels': 2}
ct_sample_spec = ct_struct_sample_spec(*sample_spec.values())
# 'ptr' may be used as a parameter of type 'pa_sample_spec *' of a ctypes
# foreign function.
# Using ctypes pointer() here to be able to print the pointer contents
# below, but lightweight byref() is sufficient if only passing the pointer
# as a function parameter.
ptr = ct.pointer(ct_sample_spec)
# Dereference the pointer.
contents = ptr.contents
# This will print 'format: 3, rate: 44100, channels: 2'.
print(f'format: {contents.format}, rate: {contents.rate},'
f' channels: {contents.channels}')
# This will print '176400'.
bps = libpulse.pa_bytes_per_second(ptr)
print(bps)
# Using ct.byref() instead of ct.pointer().
# This will print '176400'.
bps = libpulse.pa_bytes_per_second(ct.byref(ct_sample_spec))
print(bps)
A simpler way is to instantiate one of the convenience classes Pa_buffer_attr, Pa_cvolume, Pa_channel_map, Pa_format_info or Pa_sample_spec and call its byref() method. See the CtypesPulseStructure section.
In that case the above example becomes:
ptr = libpulse.Pa_sample_spec(*sample_spec.values()).byref()
examples/pa_stream_new.py shows how to create instances of two structures and pass their pointers to pa_stream_new(). The example shows also how to build a PulseStructure from a pointer returned by pa_stream_get_sample_spec(). See the PulseStructure section.
The implementation of the pactl module uses the Pa_cvolume and
Pa_channel_map classes to build ctypes Structure instances and pass their
pointer to some of the pactl.py non-async functions.