Python high-level interface and ctypes-based bindings for PulseAudio (libpulse)
Project description
Python (3.x and 2.x) high-level interface and ctypes-based bindings for PulseAudio (libpulse), mostly focused on mixer-like controls and introspection-related operations (as opposed to e.g. submitting sound samples to play, player-like client).
Originally forked from pulsemixer project, which had this code bundled.
Usage
Simple example:
from pulsectl import Pulse with Pulse('volume-increaser') as pulse: for sink in pulse.sink_list(): # Volume is usually in 0-1.0 range, with >1.0 being soft-boosted pulse.volume_change_all_chans(sink, 0.1)
Listening for server state change events:
from pulsectl import Pulse, PulseLoopStop with Pulse('event-printer') as pulse: # print('Event types:', ', '.join(pulse.event_types)) # print('Event facilities:', ', '.join(pulse.event_facilities)) # print('Event masks:', ', '.join(pulse.event_masks)) def print_events(ev): print('Pulse event:', ev) ### Raise PulseLoopStop for event_listen() to return before timeout (if any) # raise PulseLoopStop pulse.event_mask_set('all') pulse.event_callback_set(print_events) pulse.event_listen(timeout=10)
Misc other tinkering:
>>> from pulsectl import Pulse >>> pulse = Pulse('my-client-name') >>> pulse.sink_list() [<PulseSinkInfo at 7f85cfd053d0 - desc='Built-in Audio', index=0L, mute=0, name='alsa-speakers', channels=2, volumes='44.0%, 44.0%'>] >>> pulse.sink_input_list() [<PulseSinkInputInfo at 7fa06562d3d0 - index=181L, mute=0, name='mpv Media Player', channels=2, volumes='25.0%, 25.0%'>] >>> pulse.sink_input_list()[0].proplist {'application.icon_name': 'mpv', 'application.language': 'C', 'application.name': 'mpv Media Player', ... 'native-protocol.version': '30', 'window.x11.display': ':1.0'} >>> pulse.source_list() [<PulseSourceInfo at 7fcb0615d8d0 - desc='Monitor of Built-in Audio', index=0L, mute=0, name='alsa-speakers.monitor', channels=2, volumes='100.0%, 100.0%'>, <PulseSourceInfo at 7fcb0615da10 - desc='Built-in Audio', index=1L, mute=0, name='alsa-mic', channels=2, volumes='100.0%, 100.0%'>] >>> sink = pulse.sink_list()[0] >>> pulse.volume_change_all_chans(sink, -0.1) >>> pulse.volume_set_all_chans(sink, 0.5) >>> help(pulse) ...
Current code logic is that all methods are invoked through the Pulse instance, and everything returned from these are “Pulse-Something-Info” objects - thin wrappers around C structs that describe the thing, without any methods attached.
Pulse client can be integrated into existing eventloop (e.g. asyncio, twisted, etc) using Pulse.set_poll_func() or Pulse.event_listen() in a separate thread.
Somewhat extended usage example can be found in pulseaudio-mixer-cli project code.
Concepts
Some less obvious things are described in this section.
Volume
All volume values in this module are float objects in 0-65536 range, with following meaning:
0.0 volume is “no sound”.
1.0 value is “current sink volume level”, 100% or PA_VOLUME_NORM.
>1.0 and up to 65536.0 (PA_VOLUME_MAX / PA_VOLUME_NORM) - software-boosted sound volume (higher values will negatively affect sound quality).
Probably a good idea to set volume only in 0-1.0 range and boost volume in hardware without any quality loss, e.g. by tweaking sink volume (which corresponds to ALSA/hardware volume), if that option is available.
Note that flat-volumes=yes option (“yes” by default on some distros, “no” in e.g. Arch Linux) in pulseaudio daemon.conf already scales device-volume with the volume of the “loudest” application, so already does what’s suggested above.
See src/pulse/volume.h in pulseaudio sources for all the gory details.
Event-handling code, threads
libpulse clients always work as an event loop, though this module kinda hides it, presenting a more conventional blocking interface.
So what happens on any call (e.g. pulse.mute(...)) is:
Make a call to libpulse, specifying callback for when operation will be completed.
Run libpulse event loop until that callback gets called.
Return result passed to that callback call.
event_callback_set() and event_listen() calls essentally do raw first and second step here.
Which means that any pulse calls from callback function can’t be used when event_listen() (or any other pulse call through this module, for that matter) waits for return value and runs libpulse loop already.
One can raise PulseLoopStop exception there to make event_listen() return, run whatever pulse calls after that, then re-start the event_listen() thing.
This will not miss any events, as all blocking calls do same thing as event_listen() does (second step above), and can cause callable passed to event_callback_set() to be called (when loop is running).
Also, same instance of libpulse eventloop can’t be run from different threads, naturally, so if threads are used, client can be initialized with threading_lock=True option (can also accept lock instance instead of True) to create a mutex around step-2 (run event loop) from the list above, so multiple threads won’t do it at the same time.
For proper eventloop integration (think twisted or asyncio), _pulse_get_list / _pulse_method_call wrappers should be overidden to not run pulse loop, but rather return “future” object and register a set of fd’s (as passed to set_poll_func callback) with eventloop. Never needed that, so not implemented in the module, but should be rather easy to implement on top of it, as described.
Installation
It’s a regular package for Python (3.x or 2.x).
Be sure to use python3/python2, pip3/pip2, easy_install-… binaries below, based on which python version you want to install the module for, if you have several on the system (as is norm these days for py2-py3 transition).
Using pip is the best way:
% pip install pulsectl
If you don’t have pip:
% easy_install pip % pip install pulsectl
Alternatively (see also pip2014.com and pip install guide):
% curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python % pip install pulsectl
Or, if you absolutely must:
% easy_install pulsectl
But, you really shouldn’t do that.
Current-git version can be installed like this:
% pip install 'git+https://github.com/mk-fg/python-pulse-control.git#egg=pulsectl'
Note that to install stuff in system-wide PATH and site-packages, elevated privileges are often required. Use “…install –user”, ~/.pydistutils.cfg or virtualenv to do unprivileged installs into custom paths.
Links
pulsemixer - initial source for this project (embedded in the tool).
libpulseaudio - different libpulse bindings module, more low-level, auto-generated from pulseaudio header files.
Branches there have bindings for different (newer) pulseaudio versions.
pulseaudio-mixer-cli - alsamixer-like script built on top of this module.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.