Python高级接口和基于ctypes的PulseAudio(libpulse)绑定
项目描述
Python (3.x 和 2.x) 阻塞高级接口和基于ctypes的PulseAudio(libpulse)绑定,用于在简单同步代码中应用。
包装器主要用于类似混音器的控制和使用内省相关操作,而不是例如提交声音样本以播放和类似客户端。
对于要配合asyncio使用的异步版本,请参阅pulsectl-asyncio项目。
最初从pulsemixer项目分支,该项目包含此代码。
仓库URL
用法
简单示例
import pulsectl with pulsectl.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)
监听服务器状态改变事件
import pulsectl with pulsectl.Pulse('event-printer') as pulse: # print('Event types:', pulsectl.PulseEventTypeEnum) # print('Event facilities:', pulsectl.PulseEventFacilityEnum) # print('Event masks:', pulsectl.PulseEventMaskEnum) def print_events(ev): print('Pulse event:', ev) ### Raise PulseLoopStop for event_listen() to return before timeout (if any) # raise pulsectl.PulseLoopStop pulse.event_mask_set('all') pulse.event_callback_set(print_events) pulse.event_listen(timeout=10)
其他杂项修改
>>> import pulsectl >>> pulse = pulsectl.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) >>> pulse.server_info().default_sink_name 'alsa_output.pci-0000_00_14.2.analog-stereo' >>> pulse.default_set(sink) >>> card = pulse.card_list()[0] >>> card.profile_list [<PulseCardProfileInfo at 7f02e7e88ac8 - description='Analog Stereo Input', n_sinks=0, n_sources=1, name='input:analog-stereo', priority=60>, <PulseCardProfileInfo at 7f02e7e88b70 - description='Analog Stereo Output', n_sinks=1, n_sources=0, name='output:analog-stereo', priority=6000>, ... <PulseCardProfileInfo at 7f02e7e9a4e0 - description='Off', n_sinks=0, n_sources=0, name='off', priority=0>] >>> pulse.card_profile_set(card, 'output:hdmi-stereo') >>> help(pulse) ... >>> pulse.close()
当前代码逻辑是所有方法都通过Pulse实例调用,从这些方法返回的所有内容都是“Pulse-Something-Info”对象 - 这是描述该事物的一个C结构体的薄包装,没有任何附加的方法。
除了少数添加的便捷方法外,大多数方法应具有类似的签名,并执行与C libpulse API对应方法相同的功能,因此请参阅pulseaudio doxygen文档以获取更多关于它们的信息。
可以使用Pulse.set_poll_func()或使用单独线程中的Pulse.event_listen()将Pulse客户端集成到现有的事件循环中(例如asyncio、twisted等)。
有关示例,请参阅pulseaudio-mixer-cli项目代码以及此处测试。
注意
本节描述了一些不太明显的事情。
尚未在Python中包装/公开的事物
libpulse中有许多信息、方法和其他事物尚未被本模块包装/公开,因为它们(目前)不是作者/开发者用例所需要的。
可以通过在PulseSomethingInfo对象中将属性名称添加到“c_struct_fields”值中,使它们从Python代码中可访问。
有关如何查找/添加此类内容的更具体示例,请参阅github #3。
对于通过libpulse内省API不可用的信息和命令,可以使用pulsectl.connect_to_cli()回退函数,该函数将打开到服务器的“module-cli”的Unix套接字(如果需要,则加载它),可以像“pacmd”工具(不要与使用本地协议而不是module-cli的“pactl”混淆)或pulseaudio启动文件(例如“default.pa”)一样使用。
不过,解析那里命令的字符串输出可能不是个好主意,因为这些不仅会发生变化,还可能根据系统区域设置而有所不同。
音量
在PulseAudio中,“音量”对于任何事物都不是一个简单的数字,而是一个数字列表,每个通道一个(例如“左”,“右”,“前”,“后”等),它应与相关/应用的对象的通道映射相对应。
在这个模块中,此类列表由PulseVolumeInfo对象表示。
即sink.volume是一个PulseVolumeInfo实例,所有接受对象索引的薄/简单包装器都期望传递此类实例,例如pulse.sink_input_volume_set(sink.index, sink.volume)。
存在便捷的volume_get_all_chans、volume_set_all_chans和volume_change_all_chans方法,可以按单个数值获取/设置/调整音量,这些值也作为value_flat属性在PulseVolumeInfo对象上可用。
PulseVolumeInfo可以从数值音量值加上通道数或按通道数的Python列表构造。
PulseVolumeInfo中的所有通道音量值(以及上述包装器函数中的平面值),都是0-65536范围内的浮点对象,具有以下含义
0.0音量是“无声音”(对应于PA_VOLUME_MUTED)。
1.0值是“当前输出音量级别”,100%或PA_VOLUME_NORM。
>1.0和65536.0(PA_VOLUME_MAX / PA_VOLUME_NORM) - 软件增强音量(更高的值将负影响音质)。
可能最好只在0-1.0范围内设置音量,并通过硬件提升音量而不损失质量来提升音量,例如,如果可用,则调整输出音量(这对应于ALSA/硬件音量)。
请注意,pulseaudio daemon.conf中的flat-volumes=yes选项(某些发行版中默认为“yes”,例如Arch Linux中为“no”)已经将设备音量与“最响亮”的应用程序的音量成比例缩放,因此已经完成了上述操作。
模块中使用的分数体积值被转换为(线性方式)到/从libpulse的pa_volume_t整数。有关详细信息,请参阅pulseaudio源中的src/pulse/volume.h(例如,它与dB中的音量级的关系)。
代码示例
from pulsectl import Pulse, PulseVolumeInfo with Pulse('volume-example') as pulse: sink_input = pulse.sink_input_list()[0] # first random sink-input stream volume = sink_input.volume print(volume.values) # list of per-channel values (floats) print(volume.value_flat) # average level across channels (float) time.sleep(1) volume.value_flat = 0.3 # sets all volume.values to 0.3 pulse.volume_set(sink_input, volume) # applies the change time.sleep(1) n_channels = len(volume.values) new_volume = PulseVolumeInfo(0.5, n_channels) # 0.5 across all n_channels # new_volume = PulseVolumeInfo([0.15, 0.25]) # from a list of channel levels (stereo) pulse.volume_set(sink_input, new_volume) # pulse.sink_input_volume_set(sink_input.index, new_volume) # same as above
在大多数常见情况下,执行类似pulse.volume_set_all_chans(sink_input, 0.2)的操作应该就可以了,不需要在PulseVolumeInfo中担心特定的通道。
字符串值
libpulse显式返回utf-8编码的字符串值,在python-2(称为“unicode”)和python-3(称为“str”)中,总是解码为“抽象字符串”类型,以保持一致性。
在代码中最好避免将这些与编码字符串(“bytes”)混合,特别是在python-2中,其中“bytes”通常用作默认字符串类型。
枚举/命名值(枚举)
模块代替某些枚举或常量对应的C整数(例如,-1对应于PA_SINK_INVALID_STATE),返回EnumValue对象,类似于字符串(py2/py3中的“str”类型)。
例如
>>> pulsectl.PulseEventTypeEnum.change == 'change' True >>> pulsectl.PulseEventTypeEnum.change <EnumValue event-type=change> >>> pulsectl.PulseEventTypeEnum <Enum event-type [change new remove]>
在代码中使用枚举而不是字符串可能更合适,这样解释器就可以在指定任何拼写错误或未知值时发出错误,而不是总是对无效字符串的检查静默失败。
事件处理代码,线程
尽管这个模块有点隐藏,但libpulse客户端始终作为一个事件循环运行。
所以任何调用(例如pulse.mute(...))发生的情况是
调用libpulse,指定当操作完成时的回调。
运行libpulse事件循环,直到该回调被调用。
如果有的话,返回传递给该回调调用的结果(对于各种“get”方法)。
event_callback_set()和event_listen()调用基本上在这里执行原始的第一和第二步。
这意味着在event_listen()(或通过此模块的任何其他pulse调用)等待返回值并运行libpulse循环时,不能在回调函数中使用任何pulse调用。
可以通过抛出PulseLoopStop异常来使event_listen()返回,运行随后的任何pulse调用,然后重新启动event_listen()。
这不会错过任何事件,因为所有阻塞调用都执行与event_listen()相同的事情(如上所述的第二步),并且可以导致传递给event_callback_set()的可调用对象被调用(当循环运行时)。
此外,不能从不同的线程运行相同的libpulse事件循环,因此如果使用线程,则可以使用threading_lock=True选项(也可以接受锁实例而不是True)初始化客户端,以在上述列表中的第二步(运行事件循环)周围创建互斥锁,这样多个线程就不会同时执行它。
为了正确集成Python事件循环(例如twisted或asyncio),请使用pulsectl-asyncio模块。
有关将此模块塞入异步应用程序的技巧,请参阅github #11,但即使在没有asyncio事件循环的非异步情况下,从pulsectl-asyncio开始可能更容易。
测试
测试代码与模块一起打包/安装,在更改模块代码或检查当前Python、模块和pulseaudio版本是否都能正常工作时非常有用。
从检出目录或安装的模块运行测试的命令
% python2 -m unittest discover % python3 -m unittest discover
请注意,如果“pulsectl”模块在当前目录(例如检出目录)和用户/系统Python模块路径中都可用,则前者在上述命令中应始终优先。
为上述命令添加例如 -k test_stream_move,以匹配并运行特定测试,当隔离特定故障时,也可以使用带有PA_DEBUG=1环境变量的运行方式来获取完整的详细脉冲音频日志,例如
% PA_DEBUG=1 python -m unittest discover -k test_module_funcs
测试套件运行一个独立的脉冲音频实例,该实例使用null-sinks(不接触硬件),自定义(非默认)启动脚本和环境,并且只与该实例交互,之后终止它。但仍然使用系统/用户daemon.conf文件,因此这些可能会影响测试。
任何测试失败都可能表明不兼容性、模块代码中的错误、脉冲音频(或其daemon.conf)和底层依赖的问题。没有“预期”的测试用例失败。
目前(v19.9.6)所有测试都可以运行最长10秒,因为其中一些涉及到播放(使用来自/dev/urandom的paplay),这是时间敏感的。
变更日志和版本方案
本软件包使用每提交一次版本方案(由pre-commit钩子更新)和基本上每提交一次git提交进行发布,除非计划有更及时的后续提交,或者太懒惰而不想为一些微不足道的README拼写错误修正运行py setup.py sdist bdist_wheel upload。
存在一个CHANGES.rst文件,其中列出了任何有意的破坏性变更(应非常罕见,如果有)和新/添加的非平凡功能。
安装
这是一个常规的Python(3.x或2.x)软件包。
如果您的发行版中有可用的软件包,建议使用您的软件包管理器来安装它。
否则,使用pip是最好的方式
% pip install pulsectl
(添加–user选项以仅安装到当前用户的$HOME)
请确保根据您想要为哪个Python版本安装模块,使用python3/python2,pip3/pip2,easy_install-…命令。
如果您没有“pip”命令
% python -m ensurepip % python -m pip install --upgrade pip % python -m pip install pulsectl
(与上面关于“install –user”的建议相同)
在非常旧的系统上,以下命令中可能有其中一个有效
% curl https://bootstrap.pypa.io/get-pip.py | python % pip install pulsectl % easy_install pulsectl % git clone --depth=1 https://github.com/mk-fg/python-pulse-control % cd python-pulse-control % python setup.py install
(这里的所有安装命令都有–user选项)
可以像这样安装当前git版本
% pip install 'git+https://github.com/mk-fg/python-pulse-control#egg=pulsectl'
请注意,要将内容安装到系统范围内的PATH和site-packages(不使用–user),通常需要提升权限(即root和su/sudo)。
使用“…install –user”,~/.pydistutils.cfg或virtualenv以在自定义路径中进行无权限安装。
有关Python打包的更多信息,请参阅packaging.python.org。
链接
pulsemixer - 此项目的初始源(嵌入在工具中)。
pulsectl-asyncio - 与此类似的libpulse包装器,但用于异步Python代码。
libpulseaudio - 不同的libpulse绑定模块,更底层,自动从脉冲音频头文件生成。
那里的分支为不同的(较新)脉冲音频版本提供了绑定。
pypulseaudio - 高级绑定模块,与这个模块相当相似。
pulseaudio-mixer-cli - 建立在上述模块之上的类似于alsamixer的脚本。
项目详情
下载文件
下载适合您平台的应用程序文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。