Hello community,
here is the log from the commit of package python-SoundCard for
openSUSE:Factory checked in at 2019-12-03 15:21:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-SoundCard (Old)
and /work/SRC/openSUSE:Factory/.python-SoundCard.new.4691 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-SoundCard"
Tue Dec 3 15:21:43 2019 rev:5 rq:752995 version:0.3.3
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-SoundCard/python-SoundCard.changes
2019-04-02 09:21:36.944672817 +0200
+++
/work/SRC/openSUSE:Factory/.python-SoundCard.new.4691/python-SoundCard.changes
2019-12-03 15:21:43.858524714 +0100
@@ -1,0 +2,15 @@
+Mon Dec 2 17:11:34 UTC 2019 - Todd R <[email protected]>
+
+- Update to 0.3.3
+ * Fix attribute error when accessing stream state
+ * adds experimental support for exclusive mode on Windows
+ * adds latency hints to the documentation
+ * fix exception when monitor is default pulseaudio device
+ * fixes deprecation warning
+ * fixes missing dtype declaration
+ * fixes sample rate conversion on macOS
+ * fixes silence recording on macOS
+ * makes mainloop a global singleton
+ * remove useless declaration and call to _pa_stream_get_buffer_attr()
+
+-------------------------------------------------------------------
Old:
----
SoundCard-0.3.0.tar.gz
New:
----
SoundCard-0.3.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-SoundCard.spec ++++++
--- /var/tmp/diff_new_pack.XUzeaE/_old 2019-12-03 15:21:44.334524496 +0100
+++ /var/tmp/diff_new_pack.XUzeaE/_new 2019-12-03 15:21:44.342524492 +0100
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-SoundCard
-Version: 0.3.0
+Version: 0.3.3
Release: 0
Summary: Python package to play and record audio
License: BSD-3-Clause
++++++ SoundCard-0.3.0.tar.gz -> SoundCard-0.3.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/PKG-INFO new/SoundCard-0.3.3/PKG-INFO
--- old/SoundCard-0.3.0/PKG-INFO 2019-03-25 14:00:27.000000000 +0100
+++ new/SoundCard-0.3.3/PKG-INFO 2019-10-08 10:29:17.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: SoundCard
-Version: 0.3.0
+Version: 0.3.3
Summary: Play and record audio without resorting to CPython extensions
Home-page: https://github.com/bastibe/SoundCard
Author: Bastian Bechtold
@@ -49,7 +49,6 @@
.. |closed-issues| image::
https://img.shields.io/github/issues-closed/bastibe/soundcard.svg
.. |open-prs| image::
https://img.shields.io/github/issues-pr/bastibe/soundcard.svg
.. |closed-prs| image::
https://img.shields.io/github/issues-pr-closed/bastibe/soundcard.svg
- .. |status| image:: https://img.shields.io/pypi/status/soundcard.svg
Tutorial
--------
@@ -104,6 +103,33 @@
data = mic.record(numframes=1024)
sp.play(data)
+ Latency
+ -------
+
+ By default, SoundCard records and plays at the operating system's
default
+ configuration. Particularly on laptops, this configuration might have
extreme
+ latencies, up to multiple seconds.
+
+ In order to request lower latencies, pass a ``blocksize`` to
``player`` or
+ ``recorder``. This tells the operating system your desired latency,
and it will
+ try to honor your request as best it can. On Windows/WASAPI, setting
+ ``exclusive_mode=True`` might help, too (this is currently
experimental).
+
+ Another source of latency is in the ``record`` function, which buffers
output up
+ to the requested ``numframes``. In general, for optimal latency, you
should use
+ a ``numframes`` significantly lower than the ``blocksize`` above,
maybe by a
+ factor of two or four.
+
+ To get the audio data as quickly as absolutely possible, you can use
+ ``numframes=None``, which will return whatever audio data is available
right
+ now, without any buffering. Note that this might receive different
numbers of
+ frames each time.
+
+ With the above settings, block sizes of 256 samples or ten
milliseconds are
+ usually no problem. The total latency of playback and recording is
dependent on
+ how these buffers are handled by the operating system, though, and
might be
+ significantly higher.
+
Channel Maps
------------
@@ -124,8 +150,8 @@
* Windows/WASAPI currently records garbage if you record only a single
channel.
The reason for this is yet unknown. Multi-channel and channel maps
work,
though.
- * Windows/WASAPI silently ignores the blocksize. Apparently, it only
supports
- variable block sizes in exclusive mode, which is not yet supported.
+ * Windows/WASAPI silently ignores the blocksize in some cases.
Apparently, it
+ only supports variable block sizes in exclusive mode.
* Error messages often report some internal CFFI/backend errors. This
will be
improved in the future.
@@ -145,6 +171,15 @@
- 2018-11-28 adds Sphinx/Readthedocs documentation
- 2019-03-25 adds support for Python 3.5
(Thank you, Daniel R. Kumor!)
+ - 2019-04-29 adds experimental support for exclusive mode on Windows
+ - 2019-05-13 fixes sample rate conversion on macOS
+ - 2019-05-15 fixes silence recording on macOS
+ - 2019-06-11 fixes exception when monitoring default device on Linux
+ (Thank you, Inti Pelupessy!)
+ - 2019-06-18 fixes crash when opening many streams on Linux
+ - 2019-08-23 fixes attribute error when accessing stream state on Linux
+ (Thank you, Davíð Sindri Pétursson)
+ - 2019-10-08 fixes inconsistent dtypes when recording on Linux
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/README.rst
new/SoundCard-0.3.3/README.rst
--- old/SoundCard-0.3.0/README.rst 2019-03-25 13:59:48.000000000 +0100
+++ new/SoundCard-0.3.3/README.rst 2019-10-08 10:16:13.000000000 +0200
@@ -42,7 +42,6 @@
.. |closed-issues| image::
https://img.shields.io/github/issues-closed/bastibe/soundcard.svg
.. |open-prs| image::
https://img.shields.io/github/issues-pr/bastibe/soundcard.svg
.. |closed-prs| image::
https://img.shields.io/github/issues-pr-closed/bastibe/soundcard.svg
-.. |status| image:: https://img.shields.io/pypi/status/soundcard.svg
Tutorial
--------
@@ -97,6 +96,33 @@
data = mic.record(numframes=1024)
sp.play(data)
+Latency
+-------
+
+By default, SoundCard records and plays at the operating system's default
+configuration. Particularly on laptops, this configuration might have extreme
+latencies, up to multiple seconds.
+
+In order to request lower latencies, pass a ``blocksize`` to ``player`` or
+``recorder``. This tells the operating system your desired latency, and it will
+try to honor your request as best it can. On Windows/WASAPI, setting
+``exclusive_mode=True`` might help, too (this is currently experimental).
+
+Another source of latency is in the ``record`` function, which buffers output
up
+to the requested ``numframes``. In general, for optimal latency, you should use
+a ``numframes`` significantly lower than the ``blocksize`` above, maybe by a
+factor of two or four.
+
+To get the audio data as quickly as absolutely possible, you can use
+``numframes=None``, which will return whatever audio data is available right
+now, without any buffering. Note that this might receive different numbers of
+frames each time.
+
+With the above settings, block sizes of 256 samples or ten milliseconds are
+usually no problem. The total latency of playback and recording is dependent on
+how these buffers are handled by the operating system, though, and might be
+significantly higher.
+
Channel Maps
------------
@@ -117,8 +143,8 @@
* Windows/WASAPI currently records garbage if you record only a single channel.
The reason for this is yet unknown. Multi-channel and channel maps work,
though.
-* Windows/WASAPI silently ignores the blocksize. Apparently, it only supports
- variable block sizes in exclusive mode, which is not yet supported.
+* Windows/WASAPI silently ignores the blocksize in some cases. Apparently, it
+ only supports variable block sizes in exclusive mode.
* Error messages often report some internal CFFI/backend errors. This will be
improved in the future.
@@ -138,3 +164,12 @@
- 2018-11-28 adds Sphinx/Readthedocs documentation
- 2019-03-25 adds support for Python 3.5
(Thank you, Daniel R. Kumor!)
+- 2019-04-29 adds experimental support for exclusive mode on Windows
+- 2019-05-13 fixes sample rate conversion on macOS
+- 2019-05-15 fixes silence recording on macOS
+- 2019-06-11 fixes exception when monitoring default device on Linux
+ (Thank you, Inti Pelupessy!)
+- 2019-06-18 fixes crash when opening many streams on Linux
+- 2019-08-23 fixes attribute error when accessing stream state on Linux
+ (Thank you, Davíð Sindri Pétursson)
+- 2019-10-08 fixes inconsistent dtypes when recording on Linux
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/SoundCard.egg-info/PKG-INFO
new/SoundCard-0.3.3/SoundCard.egg-info/PKG-INFO
--- old/SoundCard-0.3.0/SoundCard.egg-info/PKG-INFO 2019-03-25
14:00:27.000000000 +0100
+++ new/SoundCard-0.3.3/SoundCard.egg-info/PKG-INFO 2019-10-08
10:29:17.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: SoundCard
-Version: 0.3.0
+Version: 0.3.3
Summary: Play and record audio without resorting to CPython extensions
Home-page: https://github.com/bastibe/SoundCard
Author: Bastian Bechtold
@@ -49,7 +49,6 @@
.. |closed-issues| image::
https://img.shields.io/github/issues-closed/bastibe/soundcard.svg
.. |open-prs| image::
https://img.shields.io/github/issues-pr/bastibe/soundcard.svg
.. |closed-prs| image::
https://img.shields.io/github/issues-pr-closed/bastibe/soundcard.svg
- .. |status| image:: https://img.shields.io/pypi/status/soundcard.svg
Tutorial
--------
@@ -104,6 +103,33 @@
data = mic.record(numframes=1024)
sp.play(data)
+ Latency
+ -------
+
+ By default, SoundCard records and plays at the operating system's
default
+ configuration. Particularly on laptops, this configuration might have
extreme
+ latencies, up to multiple seconds.
+
+ In order to request lower latencies, pass a ``blocksize`` to
``player`` or
+ ``recorder``. This tells the operating system your desired latency,
and it will
+ try to honor your request as best it can. On Windows/WASAPI, setting
+ ``exclusive_mode=True`` might help, too (this is currently
experimental).
+
+ Another source of latency is in the ``record`` function, which buffers
output up
+ to the requested ``numframes``. In general, for optimal latency, you
should use
+ a ``numframes`` significantly lower than the ``blocksize`` above,
maybe by a
+ factor of two or four.
+
+ To get the audio data as quickly as absolutely possible, you can use
+ ``numframes=None``, which will return whatever audio data is available
right
+ now, without any buffering. Note that this might receive different
numbers of
+ frames each time.
+
+ With the above settings, block sizes of 256 samples or ten
milliseconds are
+ usually no problem. The total latency of playback and recording is
dependent on
+ how these buffers are handled by the operating system, though, and
might be
+ significantly higher.
+
Channel Maps
------------
@@ -124,8 +150,8 @@
* Windows/WASAPI currently records garbage if you record only a single
channel.
The reason for this is yet unknown. Multi-channel and channel maps
work,
though.
- * Windows/WASAPI silently ignores the blocksize. Apparently, it only
supports
- variable block sizes in exclusive mode, which is not yet supported.
+ * Windows/WASAPI silently ignores the blocksize in some cases.
Apparently, it
+ only supports variable block sizes in exclusive mode.
* Error messages often report some internal CFFI/backend errors. This
will be
improved in the future.
@@ -145,6 +171,15 @@
- 2018-11-28 adds Sphinx/Readthedocs documentation
- 2019-03-25 adds support for Python 3.5
(Thank you, Daniel R. Kumor!)
+ - 2019-04-29 adds experimental support for exclusive mode on Windows
+ - 2019-05-13 fixes sample rate conversion on macOS
+ - 2019-05-15 fixes silence recording on macOS
+ - 2019-06-11 fixes exception when monitoring default device on Linux
+ (Thank you, Inti Pelupessy!)
+ - 2019-06-18 fixes crash when opening many streams on Linux
+ - 2019-08-23 fixes attribute error when accessing stream state on Linux
+ (Thank you, Davíð Sindri Pétursson)
+ - 2019-10-08 fixes inconsistent dtypes when recording on Linux
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/setup.py new/SoundCard-0.3.3/setup.py
--- old/SoundCard-0.3.0/setup.py 2019-03-25 13:58:21.000000000 +0100
+++ new/SoundCard-0.3.3/setup.py 2019-10-08 10:10:49.000000000 +0200
@@ -2,7 +2,7 @@
setup(
name='SoundCard',
- version='0.3.0',
+ version='0.3.3',
description='Play and record audio without resorting to CPython
extensions',
author='Bastian Bechtold',
url='https://github.com/bastibe/SoundCard',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/coreaudio.py
new/SoundCard-0.3.3/soundcard/coreaudio.py
--- old/SoundCard-0.3.0/soundcard/coreaudio.py 2018-11-29 09:48:16.000000000
+0100
+++ new/SoundCard-0.3.3/soundcard/coreaudio.py 2019-10-08 10:01:45.000000000
+0200
@@ -755,7 +755,7 @@
converted_data = numpy.concatenate(self.queue)
self.queue.clear()
- return converted_data
+ return converted_data.reshape([-1, self.channels])
def __del__(self):
_au.AudioConverterDispose(self.audioconverter[0])
@@ -781,7 +781,7 @@
def __enter__(self):
self._queue = collections.deque()
- self._pending_chunk = numpy.zeros([0])
+ self._pending_chunk = numpy.zeros([0, self._au.channels])
channels = self._au.channels
au = self._au.ptr[0]
@@ -793,8 +793,7 @@
bufferlist.mNumberBuffers = 1
bufferlist.mBuffers[0].mNumberChannels = channels
bufferlist.mBuffers[0].mDataByteSize = numframes * 4 * channels
- data = _ffi.new("Float32[]", numframes * channels)
- bufferlist.mBuffers[0].mData = data
+ bufferlist.mBuffers[0].mData = _ffi.NULL
status = _au.AudioUnitRender(au,
actionflags,
@@ -803,10 +802,21 @@
numframes,
bufferlist)
+ # special case if output is silence:
+ if (actionflags[0] == _cac.kAudioUnitRenderAction_OutputIsSilence
+ and status == _cac.kAudioUnitErr_CannotDoInCurrentContext):
+ actionflags[0] = 0 # reset actionflags
+ status = 0 # reset error code
+ data = numpy.zeros([numframes, channels], 'float32')
+ else:
+ data =
numpy.frombuffer(_ffi.buffer(bufferlist.mBuffers[0].mData,
+
bufferlist.mBuffers[0].mDataByteSize),
+ dtype='float32')
+ data = data.reshape([-1,
bufferlist.mBuffers[0].mNumberChannels]).copy()
+
if status != 0:
print('error during recording:', status)
- data = numpy.frombuffer(_ffi.buffer(data), dtype='float32')
self._queue.append(data)
self._record_event.set()
return status
@@ -829,7 +839,12 @@
while not self._queue:
self._record_event.wait()
self._record_event.clear()
- return self._queue.popleft()
+ block = self._queue.popleft()
+
+ # perform sample rate conversion:
+ if self._au.resample != 1:
+ block = self._resampler.resample(block)
+ return block
def record(self, numframes=None):
"""Record a block of audio data.
@@ -853,30 +868,19 @@
if numframes is None:
blocks = [self._pending_chunk, self._record_chunk()]
- self._pending_chunk = numpy.zeros([0])
+ self._pending_chunk = numpy.zeros([0, self._au.channels])
else:
blocks = [self._pending_chunk]
- self._pending_chunk = numpy.zeros([0])
+ self._pending_chunk = numpy.zeros([0, self._au.channels])
recorded_frames = len(blocks[0])
- required_frames =
int(numframes/self._au.resample)*self._au.channels
- while recorded_frames < required_frames:
+ while recorded_frames < numframes:
block = self._record_chunk()
blocks.append(block)
recorded_frames += len(block)
- if recorded_frames > required_frames:
- to_split = -(recorded_frames-required_frames)
+ if recorded_frames > numframes:
+ to_split = -(recorded_frames-numframes)
blocks[-1], self._pending_chunk = numpy.split(blocks[-1],
[to_split])
-
- data = numpy.concatenate(blocks)
-
- if self._au.channels != 1:
- data = data.reshape([-1, self._au.channels])
-
- if self._au.resample != 1:
- data = self._resampler.resample(data)
-
- if self._au.channels != 1:
- data = data.reshape([-1, self._au.channels])
+ data = numpy.concatenate(blocks, axis=0)
return data
@@ -887,5 +891,5 @@
"""
last_chunk = numpy.reshape(self._pending_chunk, [-1,
self._au.channels])
- self._pending_chunk = numpy.zeros([0])
+ self._pending_chunk = numpy.zeros([0, self._au.channels])
return last_chunk
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/coreaudioconstants.py
new/SoundCard-0.3.3/soundcard/coreaudioconstants.py
--- old/SoundCard-0.3.0/soundcard/coreaudioconstants.py 2017-10-25
10:58:42.000000000 +0200
+++ new/SoundCard-0.3.3/soundcard/coreaudioconstants.py 2019-10-08
10:01:45.000000000 +0200
@@ -132,6 +132,15 @@
kAudioUnitProperty_ElementName = 30
kAudioUnitProperty_NickName = 54
+kAudioUnitRenderAction_PreRender = 1 << 2
+kAudioUnitRenderAction_PostRender = 1 << 3
+kAudioUnitRenderAction_OutputIsSilence = 1 << 4
+kAudioOfflineUnitRenderAction_Preflight = 1 << 5
+kAudioOfflineUnitRenderAction_Render = 1 << 6
+kAudioOfflineUnitRenderAction_Complete = 1 << 7
+kAudioUnitRenderAction_PostRenderError = 1 << 8
+kAudioUnitRenderAction_DoNotCheckRenderArgs = 1 << 9
+
kAudioUnitScope_Global = 0 # The context for audio unit characteristics that
apply to the audio unit as a whole
kAudioUnitScope_Input = 1 # The context for audio data coming into an audio
unit
kAudioUnitScope_Output = 2 # The context for audio data leaving an audio unit
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/mediafoundation.py
new/SoundCard-0.3.3/soundcard/mediafoundation.py
--- old/SoundCard-0.3.0/soundcard/mediafoundation.py 2019-03-20
09:38:16.000000000 +0100
+++ new/SoundCard-0.3.3/soundcard/mediafoundation.py 2019-10-08
10:01:45.000000000 +0200
@@ -410,10 +410,10 @@
def __repr__(self):
return '<Speaker {} ({} channels)>'.format(self.name,self.channels)
- def player(self, samplerate, channels=None, blocksize=None):
+ def player(self, samplerate, channels=None, blocksize=None,
exclusive_mode=False):
if channels is None:
channels = self.channels
- return _Player(self._audio_client(), samplerate, channels, blocksize,
False)
+ return _Player(self._audio_client(), samplerate, channels, blocksize,
False, exclusive_mode)
def play(self, data, samplerate, channels=None, blocksize=None):
with self.player(samplerate, channels, blocksize) as p:
@@ -444,10 +444,10 @@
else:
return '<Microphone {} ({}
channels)>'.format(self.name,self.channels)
- def recorder(self, samplerate, channels=None, blocksize=None):
+ def recorder(self, samplerate, channels=None, blocksize=None,
exclusive_mode=False):
if channels is None:
channels = self.channels
- return _Recorder(self._audio_client(), samplerate, channels,
blocksize, self.isloopback)
+ return _Recorder(self._audio_client(), samplerate, channels,
blocksize, self.isloopback, exclusive_mode)
def record(self, numframes, samplerate, channels=None, blocksize=None):
with self.recorder(samplerate, channels, blocksize) as r:
@@ -463,7 +463,7 @@
"""
- def __init__(self, ptr, samplerate, channels, blocksize, isloopback):
+ def __init__(self, ptr, samplerate, channels, blocksize, isloopback,
exclusive_mode=False):
self._ptr = ptr
if isinstance(channels, int):
@@ -510,7 +510,10 @@
# does not work:
# ppMixFormat[0][0].dwChannelMask=channelmask
- sharemode = _combase.AUDCLNT_SHAREMODE_SHARED
+ if exclusive_mode:
+ sharemode = _combase.AUDCLNT_SHAREMODE_EXCLUSIVE
+ else:
+ sharemode = _combase.AUDCLNT_SHAREMODE_SHARED
# resample | remix | better-SRC | nopersist
streamflags = 0x00100000 | 0x80000000 | 0x08000000 | 0x00080000
if isloopback:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SoundCard-0.3.0/soundcard/pulseaudio.py
new/SoundCard-0.3.3/soundcard/pulseaudio.py
--- old/SoundCard-0.3.0/soundcard/pulseaudio.py 2019-03-20 09:38:16.000000000
+0100
+++ new/SoundCard-0.3.3/soundcard/pulseaudio.py 2019-10-08 10:07:30.000000000
+0200
@@ -1,4 +1,11 @@
import os
+import atexit
+import collections
+import time
+import re
+import threading
+import warnings
+import numpy
import cffi
_ffi = cffi.FFI()
@@ -8,13 +15,196 @@
_pa = _ffi.dlopen('pulse')
-import collections
-import time
-import re
-import numpy
-import threading
-import warnings
+# First, we need to define a global _PulseAudio proxy for interacting
+# with the C API:
+def _lock(func):
+ """Call a pulseaudio function while holding the mainloop lock."""
+ def func_with_lock(*args, **kwargs):
+ self = args[0]
+ with self._lock_mainloop():
+ return func(*args[1:], **kwargs)
+ return func_with_lock
+
+
+def _lock_and_block(func):
+ """Call a pulseaudio function while holding the mainloop lock, and
+ block until the operation has finished.
+
+ Use this for pulseaudio functions that return a `pa_operation *`.
+
+ """
+ def func_with_lock(*args, **kwargs):
+ self = args[0]
+ with self._lock_mainloop():
+ operation = func(*args[1:], **kwargs)
+ self._block_operation(operation)
+ self._pa_operation_unref(operation)
+ return func_with_lock
+
+class _PulseAudio:
+ """Proxy for communcation with Pulseaudio.
+
+ This holds the pulseaudio main loop, and a pulseaudio context.
+ Together, these provide the building blocks for interacting with
+ pulseaudio.
+
+ This can be used to query the pulseaudio server for sources,
+ sinks, and server information, and provides thread-safe access to
+ the main pulseaudio functions.
+
+ Any function that would return a `pa_operation *` in pulseaudio
+ will block until the operation has finished.
+
+ """
+
+ def __init__(self):
+ # these functions are called before the mainloop starts, so we
+ # don't need to hold the lock:
+ self.mainloop = _pa.pa_threaded_mainloop_new()
+ self.mainloop_api = _pa.pa_threaded_mainloop_get_api(self.mainloop)
+ self.context = _pa.pa_context_new(self.mainloop_api, b"audio")
+ _pa.pa_context_connect(self.context, _ffi.NULL,
_pa.PA_CONTEXT_NOFLAGS, _ffi.NULL)
+ _pa.pa_threaded_mainloop_start(self.mainloop)
+
+ while self._pa_context_get_state(self.context) != _pa.PA_CONTEXT_READY:
+ time.sleep(0.001)
+
+ def _shutdown(self):
+ operation = self._pa_context_drain(self.context, _ffi.NULL, _ffi.NULL)
+ self._block_operation(operation)
+ self._pa_context_disconnect(self.context)
+ self._pa_context_unref(self.context)
+ # no more mainloop locking necessary from here on:
+ _pa.pa_threaded_mainloop_stop(self.mainloop)
+ _pa.pa_threaded_mainloop_free(self.mainloop)
+
+ def _block_operation(self, operation):
+ """Wait until the operation has finished."""
+ if operation == _ffi.NULL:
+ return
+ while self._pa_operation_get_state(operation) ==
_pa.PA_OPERATION_RUNNING:
+ time.sleep(0.001)
+
+ @property
+ def source_list(self):
+ """Return a list of dicts of information about available sources."""
+ info = []
+ @_ffi.callback("pa_source_info_cb_t")
+ def callback(context, source_info, eol, userdata):
+ if not eol:
+
info.append(dict(name=_ffi.string(source_info.description).decode('utf-8'),
+
id=_ffi.string(source_info.name).decode('utf-8')))
+ self._pa_context_get_source_info_list(self.context, callback,
_ffi.NULL)
+ return info
+
+ def source_info(self, id):
+ """Return a dictionary of information about a specific source."""
+ info = []
+ @_ffi.callback("pa_source_info_cb_t")
+ def callback(context, source_info, eol, userdata):
+ if not eol:
+ info_dict = dict(latency=source_info.latency,
+
configured_latency=source_info.configured_latency,
+ channels=source_info.sample_spec.channels,
+
name=_ffi.string(source_info.description).decode('utf-8'))
+ for prop in ['device.class', 'device.api', 'device.bus']:
+ data = _pa.pa_proplist_gets(source_info.proplist,
prop.encode())
+ info_dict[prop] = _ffi.string(data).decode('utf-8') if
data else None
+ info.append(info_dict)
+
+ self._pa_context_get_source_info_by_name(self.context, id.encode(),
callback, _ffi.NULL)
+ return info[0]
+
+ @property
+ def sink_list(self):
+ """Return a list of dicts of information about available sinks."""
+ info = []
+ @_ffi.callback("pa_sink_info_cb_t")
+ def callback(context, sink_info, eol, userdata):
+ if not eol:
+
info.append((dict(name=_ffi.string(sink_info.description).decode('utf-8'),
+
id=_ffi.string(sink_info.name).decode('utf-8'))))
+ self._pa_context_get_sink_info_list(self.context, callback, _ffi.NULL)
+ return info
+
+ def sink_info(self, id):
+ """Return a dictionary of information about a specific sink."""
+ info = []
+ @_ffi.callback("pa_sink_info_cb_t")
+ def callback(context, sink_info, eol, userdata):
+ if not eol:
+ info_dict = dict(latency=sink_info.latency,
+
configured_latency=sink_info.configured_latency,
+ channels=sink_info.sample_spec.channels,
+
name=_ffi.string(sink_info.description).decode('utf-8'))
+ for prop in ['device.class', 'device.api', 'device.bus']:
+ data = _pa.pa_proplist_gets(sink_info.proplist,
prop.encode())
+ info_dict[prop] = _ffi.string(data).decode('utf-8') if
data else None
+ info.append(info_dict)
+ self._pa_context_get_sink_info_by_name(self.context, id.encode(),
callback, _ffi.NULL)
+ return info[0]
+
+ @property
+ def server_info(self):
+ """Return a dictionary of information about the server."""
+ info = {}
+ @_ffi.callback("pa_server_info_cb_t")
+ def callback(context, server_info, userdata):
+ info['server version'] =
_ffi.string(server_info.server_version).decode('utf-8')
+ info['server name'] =
_ffi.string(server_info.server_name).decode('utf-8')
+ info['default sink id'] =
_ffi.string(server_info.default_sink_name).decode('utf-8')
+ info['default source id'] =
_ffi.string(server_info.default_source_name).decode('utf-8')
+ self._pa_context_get_server_info(self.context, callback, _ffi.NULL)
+ return info
+
+ def _lock_mainloop(self):
+ """Context manager for locking the mainloop.
+
+ Hold this lock before calling any pulseaudio function while
+ the mainloop is running.
+
+ """
+
+ class Lock():
+ def __enter__(self_):
+ _pa.pa_threaded_mainloop_lock(self.mainloop)
+ def __exit__(self_, exc_type, exc_value, traceback):
+ _pa.pa_threaded_mainloop_unlock(self.mainloop)
+ return Lock()
+
+ # create thread-safe versions of all used pulseaudio functions:
+ _pa_context_get_source_info_list =
_lock_and_block(_pa.pa_context_get_source_info_list)
+ _pa_context_get_source_info_by_name =
_lock_and_block(_pa.pa_context_get_source_info_by_name)
+ _pa_context_get_sink_info_list =
_lock_and_block(_pa.pa_context_get_sink_info_list)
+ _pa_context_get_sink_info_by_name =
_lock_and_block(_pa.pa_context_get_sink_info_by_name)
+ _pa_context_get_server_info =
_lock_and_block(_pa.pa_context_get_server_info)
+ _pa_context_get_state = _lock(_pa.pa_context_get_state)
+ _pa_context_drain = _lock(_pa.pa_context_drain)
+ _pa_context_disconnect = _lock(_pa.pa_context_disconnect)
+ _pa_context_unref = _lock(_pa.pa_context_unref)
+ _pa_operation_get_state = _lock(_pa.pa_operation_get_state)
+ _pa_operation_unref = _lock(_pa.pa_operation_unref)
+ _pa_stream_get_state = _lock(_pa.pa_stream_get_state)
+ _pa_sample_spec_valid = _lock(_pa.pa_sample_spec_valid)
+ _pa_stream_new = _lock(_pa.pa_stream_new)
+ _pa_stream_get_channel_map = _lock(_pa.pa_stream_get_channel_map)
+ _pa_stream_drain = _lock_and_block(_pa.pa_stream_drain)
+ _pa_stream_disconnect = _lock(_pa.pa_stream_disconnect)
+ _pa_stream_unref = _lock(_pa.pa_stream_unref)
+ _pa_stream_connect_record = _lock(_pa.pa_stream_connect_record)
+ _pa_stream_readable_size = _lock(_pa.pa_stream_readable_size)
+ _pa_stream_peek = _lock(_pa.pa_stream_peek)
+ _pa_stream_drop = _lock(_pa.pa_stream_drop)
+ _pa_stream_connect_playback = _lock(_pa.pa_stream_connect_playback)
+ _pa_stream_update_timing_info =
_lock_and_block(_pa.pa_stream_update_timing_info)
+ _pa_stream_get_latency = _lock(_pa.pa_stream_get_latency)
+ _pa_stream_writable_size = _lock(_pa.pa_stream_writable_size)
+ _pa_stream_write = _lock(_pa.pa_stream_write)
+ _pa_stream_set_read_callback = _pa.pa_stream_set_read_callback
+
+_pulse = _PulseAudio()
+atexit.register(_pulse._shutdown)
def all_speakers():
"""A list of all connected speakers.
@@ -24,8 +214,7 @@
speakers : list(_Speaker)
"""
- with _PulseAudio() as p:
- return [_Speaker(id=s['id']) for s in p.sink_list]
+ return [_Speaker(id=s['id']) for s in _pulse.sink_list]
def default_speaker():
@@ -36,9 +225,8 @@
speaker : _Speaker
"""
- with _PulseAudio() as p:
- name = p.server_info['default sink id']
- return get_speaker(name)
+ name = _pulse.server_info['default sink id']
+ return get_speaker(name)
def get_speaker(id):
@@ -55,8 +243,7 @@
speaker : _Speaker
"""
- with _PulseAudio() as p:
- speakers = p.sink_list
+ speakers = _pulse.sink_list
return _Speaker(id=_match_soundcard(id, speakers)['id'])
@@ -83,12 +270,11 @@
warnings.warn("The exclude_monitors flag is being replaced by the
include_loopback flag", DeprecationWarning)
include_loopback = not exclude_monitors
- with _PulseAudio() as p:
- mics = [_Microphone(id=m['id']) for m in p.source_list]
- if not include_loopback:
- return [m for m in mics if m._get_info()['device.class'] !=
'monitor']
- else:
- return mics
+ mics = [_Microphone(id=m['id']) for m in _pulse.source_list]
+ if not include_loopback:
+ return [m for m in mics if m._get_info()['device.class'] != 'monitor']
+ else:
+ return mics
def default_microphone():
@@ -98,9 +284,8 @@
-------
microphone : _Microphone
"""
- with _PulseAudio() as p:
- name = p.server_info['default source id']
- return get_microphone(name)
+ name = _pulse.server_info['default source id']
+ return get_microphone(name, include_loopback=True)
def get_microphone(id, include_loopback=False, exclude_monitors=True):
@@ -128,8 +313,7 @@
warnings.warn("The exclude_monitors flag is being replaced by the
include_loopback flag", DeprecationWarning)
include_loopback = not exclude_monitors
- with _PulseAudio() as p:
- microphones = p.source_list
+ microphones = _pulse.source_list
return _Microphone(id=_match_soundcard(id, microphones,
include_loopback)['id'])
@@ -186,8 +370,7 @@
return self._get_info()['name']
def _get_info(self):
- with _PulseAudio() as p:
- return p.source_info(self._id)
+ return _pulse.source_info(self._id)
class _Speaker(_SoundCard):
@@ -222,6 +405,10 @@
blocksize : int
Will play this many samples at a time. Choose a lower
block size for lower latency and more CPU usage.
+ exclusive_mode : bool, optional
+ Windows only: open sound card in exclusive mode, which
+ might be necessary for short block lengths or high
+ sample rates or optimal performance. Default is ``False``.
Returns
-------
@@ -256,8 +443,7 @@
s.play(data)
def _get_info(self):
- with _PulseAudio() as p:
- return p.sink_info(self._id)
+ return _pulse.sink_info(self._id)
class _Microphone(_SoundCard):
@@ -300,6 +486,10 @@
blocksize : int
Will record this many samples at a time. Choose a lower
block size for lower latency and more CPU usage.
+ exclusive_mode : bool, optional
+ Windows only: open sound card in exclusive mode, which
+ might be necessary for short block lengths or high
+ sample rates or optimal performance. Default is ``False``.
Returns
-------
@@ -359,9 +549,6 @@
self.channels = channels
def __enter__(self):
- self._pulse = _PulseAudio()
- self._pulse.__enter__()
-
samplespec = _ffi.new("pa_sample_spec*")
samplespec.format = _pa.PA_SAMPLE_FLOAT32LE
samplespec.rate = self._samplerate
@@ -371,7 +558,7 @@
samplespec.channels = self.channels
else:
raise TypeError('channels must be iterable or integer')
- if not self._pulse._pa_sample_spec_valid(samplespec):
+ if not _pulse._pa_sample_spec_valid(samplespec):
raise RuntimeError('invalid sample spec')
channelmap = _ffi.new("pa_channel_map*")
@@ -382,7 +569,7 @@
if not _pa.pa_channel_map_valid(channelmap):
raise RuntimeError('invalid channel map')
- self.stream = self._pulse._pa_stream_new(self._pulse.context,
self._name.encode(), samplespec, channelmap)
+ self.stream = _pulse._pa_stream_new(_pulse.context,
self._name.encode(), samplespec, channelmap)
bufattr = _ffi.new("pa_buffer_attr*")
bufattr.maxlength = 2**32-1 # max buffer length
numchannels = self.channels if isinstance(self.channels, int) else
len(self.channels)
@@ -391,33 +578,29 @@
bufattr.prebuf = 2**32-1 # start playback after this bytes are
available
bufattr.tlength = self._blocksize*numchannels*4 if self._blocksize
else 2**32-1 # buffer length in bytes on server
self._connect_stream(bufattr)
- while self._pulse._pa_stream_get_state(self.stream) not in
[_pa.PA_STREAM_READY, _pa.PA_STREAM_FAILED]:
+ while _pulse._pa_stream_get_state(self.stream) not in
[_pa.PA_STREAM_READY, _pa.PA_STREAM_FAILED]:
time.sleep(0.01)
- if self._pulse._pa_stream_get_state(self.stream) ==
_pa.PA_STREAM_FAILED:
+ if _pulse._pa_stream_get_state(self.stream) == _pa.PA_STREAM_FAILED:
raise RuntimeError('Stream creation failed. Stream is in status {}'
-
.format(self._pulse.pa_stream_get_state(self.stream)))
- channel_map = self._pulse._pa_stream_get_channel_map(self.stream)
+
.format(_pulse._pa_stream_get_state(self.stream)))
+ channel_map = _pulse._pa_stream_get_channel_map(self.stream)
self.channels = int(channel_map.channels)
return self
def __exit__(self, exc_type, exc_value, traceback):
- try:
- if isinstance(self, _Player): # only playback streams need to drain
- self._pulse._pa_stream_drain(self.stream, _ffi.NULL, _ffi.NULL)
- self._pulse._pa_stream_disconnect(self.stream)
- while self._pulse._pa_stream_get_state(self.stream) !=
_pa.PA_STREAM_TERMINATED:
- time.sleep(0.01)
- self._pulse._pa_stream_unref(self.stream)
- finally:
- # make sure that this definitely gets called no matter what:
- self._pulse.__exit__(exc_type, exc_value, traceback)
+ if isinstance(self, _Player): # only playback streams need to drain
+ _pulse._pa_stream_drain(self.stream, _ffi.NULL, _ffi.NULL)
+ _pulse._pa_stream_disconnect(self.stream)
+ while _pulse._pa_stream_get_state(self.stream) !=
_pa.PA_STREAM_TERMINATED:
+ time.sleep(0.01)
+ _pulse._pa_stream_unref(self.stream)
@property
def latency(self):
"""float : Latency of the stream in seconds (only available on
Linux)"""
- self._pulse._pa_stream_update_timing_info(self.stream, _ffi.NULL,
_ffi.NULL)
+ _pulse._pa_stream_update_timing_info(self.stream, _ffi.NULL, _ffi.NULL)
microseconds = _ffi.new("pa_usec_t*")
- self._pulse._pa_stream_get_latency(self.stream, microseconds,
_ffi.NULL)
+ _pulse._pa_stream_get_latency(self.stream, microseconds, _ffi.NULL)
return microseconds[0] / 1000000 # 1_000_000 (3.5 compat)
@@ -435,7 +618,7 @@
"""
def _connect_stream(self, bufattr):
- self._pulse._pa_stream_connect_playback(self.stream,
self._id.encode(), bufattr, _pa.PA_STREAM_ADJUST_LATENCY,
+ _pulse._pa_stream_connect_playback(self.stream, self._id.encode(),
bufattr, _pa.PA_STREAM_ADJUST_LATENCY,
_ffi.NULL, _ffi.NULL)
def play(self, data):
@@ -472,14 +655,13 @@
data = numpy.tile(data, [1, self.channels])
if data.shape[1] != self.channels:
raise TypeError('second dimension of data must be equal to the
number of channels, not {}'.format(data.shape[1]))
- bufattr = self._pulse._pa_stream_get_buffer_attr(self.stream)
while data.nbytes > 0:
- nwrite = self._pulse._pa_stream_writable_size(self.stream) // 4
+ nwrite = _pulse._pa_stream_writable_size(self.stream) // 4
if nwrite == 0:
time.sleep(0.001)
continue
bytes = data[:nwrite].ravel().tostring()
- self._pulse._pa_stream_write(self.stream, bytes, len(bytes),
_ffi.NULL, 0, _pa.PA_SEEK_RELATIVE)
+ _pulse._pa_stream_write(self.stream, bytes, len(bytes), _ffi.NULL,
0, _pa.PA_SEEK_RELATIVE)
data = data[nwrite:]
class _Recorder(_Stream):
@@ -497,16 +679,16 @@
def __init__(self, *args, **kwargs):
super(_Recorder, self).__init__(*args, **kwargs)
- self._pending_chunk = numpy.zeros((0, ))
+ self._pending_chunk = numpy.zeros((0, ), dtype='float32')
self._record_event = threading.Event()
def _connect_stream(self, bufattr):
- self._pulse._pa_stream_connect_record(self.stream, self._id.encode(),
bufattr, _pa.PA_STREAM_ADJUST_LATENCY)
+ _pulse._pa_stream_connect_record(self.stream, self._id.encode(),
bufattr, _pa.PA_STREAM_ADJUST_LATENCY)
@_ffi.callback("pa_stream_request_cb_t")
def read_callback(stream, nbytes, userdata):
self._record_event.set()
self._callback = read_callback
- self._pulse._pa_stream_set_read_callback(self.stream, read_callback,
_ffi.NULL)
+ _pulse._pa_stream_set_read_callback(self.stream, read_callback,
_ffi.NULL)
def _record_chunk(self):
'''Record one chunk of audio data, as returned by pulseaudio
@@ -517,21 +699,21 @@
'''
data_ptr = _ffi.new('void**')
nbytes_ptr = _ffi.new('size_t*')
- readable_bytes = self._pulse._pa_stream_readable_size(self.stream)
+ readable_bytes = _pulse._pa_stream_readable_size(self.stream)
while not readable_bytes:
self._record_event.wait()
self._record_event.clear()
- readable_bytes = self._pulse._pa_stream_readable_size(self.stream)
+ readable_bytes = _pulse._pa_stream_readable_size(self.stream)
data_ptr[0] = _ffi.NULL
nbytes_ptr[0] = 0
- self._pulse._pa_stream_peek(self.stream, data_ptr, nbytes_ptr)
+ _pulse._pa_stream_peek(self.stream, data_ptr, nbytes_ptr)
if data_ptr[0] != _ffi.NULL:
buffer = _ffi.buffer(data_ptr[0], nbytes_ptr[0])
chunk = numpy.frombuffer(buffer, dtype='float32').copy()
if data_ptr[0] == _ffi.NULL and nbytes_ptr[0] != 0:
chunk = numpy.zeros(nbytes_ptr[0]//4, dtype='float32')
if nbytes_ptr[0] > 0:
- self._pulse._pa_stream_drop(self.stream)
+ _pulse._pa_stream_drop(self.stream)
return chunk
def record(self, numframes=None):
@@ -597,203 +779,5 @@
"""
last_chunk = numpy.reshape(self._pending_chunk, [-1, self.channels])
- self._pending_chunk = numpy.zeros((0, ))
+ self._pending_chunk = numpy.zeros((0, ), dtype='float32')
return last_chunk
-
-
-def _lock(func):
- """Call a pulseaudio function while holding the mainloop lock."""
- def func_with_lock(*args, **kwargs):
- self = args[0]
- with self._lock_mainloop():
- return func(*args[1:], **kwargs)
- return func_with_lock
-
-
-def _lock_and_block(func):
- """Call a pulseaudio function while holding the mainloop lock, and
- block until the operation has finished.
-
- Use this for pulseaudio functions that return a `pa_operation *`.
-
- """
- def func_with_lock(*args, **kwargs):
- self = args[0]
- with self._lock_mainloop():
- operation = func(*args[1:], **kwargs)
- self._block_operation(operation)
- self._pa_operation_unref(operation)
- return func_with_lock
-
-
-class _PulseAudio:
- """Context manager for communcation with Pulseaudio.
-
- This instantiates the pulseaudio main loop, and a pulseaudio
- context. Together, these provide the building blocks for
- interacting with pulseaudio.
-
- Pulseaudio can be interacted with as soon as the context manager
- is entered.
-
- This can be used to query the pulseaudio server for sources,
- sinks, and server information, and provides thread-safe access to
- the main pulseaudio functions.
-
- Any function that would return a `pa_operation *` in pulseaudio
- will block until the operation has finished.
-
- This context manager can only be entered once, and can not be used
- after it is closed.
-
- """
-
- def __init__(self):
- # these functions are called before the mainloop starts, so we
- # don't need to hold the lock:
- self.mainloop = _pa.pa_threaded_mainloop_new()
- self.mainloop_api = _pa.pa_threaded_mainloop_get_api(self.mainloop)
- self.context = _pa.pa_context_new(self.mainloop_api, b"audio")
- _pa.pa_context_connect(self.context, _ffi.NULL,
_pa.PA_CONTEXT_NOFLAGS, _ffi.NULL)
-
- def __enter__(self):
- _pa.pa_threaded_mainloop_start(self.mainloop)
- # from now on, all pulseaudio interactions needs to hold the
- # mainloop lock.
- while self._pa_context_get_state(self.context) != _pa.PA_CONTEXT_READY:
- time.sleep(0.001)
- return self
-
- def __exit__(self, exc_type, exc_value, traceback):
- operation = self._pa_context_drain(self.context, _ffi.NULL, _ffi.NULL)
- self._block_operation(operation)
- self._pa_context_disconnect(self.context)
- self._pa_context_unref(self.context)
- # no more mainloop locking necessary from here on:
- _pa.pa_threaded_mainloop_stop(self.mainloop)
- _pa.pa_threaded_mainloop_free(self.mainloop)
-
- def _block_operation(self, operation):
- """Wait until the operation has finished."""
- if operation == _ffi.NULL:
- return
- while self._pa_operation_get_state(operation) ==
_pa.PA_OPERATION_RUNNING:
- time.sleep(0.001)
-
- @property
- def source_list(self):
- """Return a list of dicts of information about available sources."""
- info = []
- @_ffi.callback("pa_source_info_cb_t")
- def callback(context, source_info, eol, userdata):
- if not eol:
-
info.append(dict(name=_ffi.string(source_info.description).decode('utf-8'),
-
id=_ffi.string(source_info.name).decode('utf-8')))
- self._pa_context_get_source_info_list(self.context, callback,
_ffi.NULL)
- return info
-
- def source_info(self, id):
- """Return a dictionary of information about a specific source."""
- info = []
- @_ffi.callback("pa_source_info_cb_t")
- def callback(context, source_info, eol, userdata):
- if not eol:
- info_dict = dict(latency=source_info.latency,
-
configured_latency=source_info.configured_latency,
- channels=source_info.sample_spec.channels,
-
name=_ffi.string(source_info.description).decode('utf-8'))
- for prop in ['device.class', 'device.api', 'device.bus']:
- data = _pa.pa_proplist_gets(source_info.proplist,
prop.encode())
- info_dict[prop] = _ffi.string(data).decode('utf-8') if
data else None
- info.append(info_dict)
-
- self._pa_context_get_source_info_by_name(self.context, id.encode(),
callback, _ffi.NULL)
- return info[0]
-
- @property
- def sink_list(self):
- """Return a list of dicts of information about available sinks."""
- info = []
- @_ffi.callback("pa_sink_info_cb_t")
- def callback(context, sink_info, eol, userdata):
- if not eol:
-
info.append((dict(name=_ffi.string(sink_info.description).decode('utf-8'),
-
id=_ffi.string(sink_info.name).decode('utf-8'))))
- self._pa_context_get_sink_info_list(self.context, callback, _ffi.NULL)
- return info
-
- def sink_info(self, id):
- """Return a dictionary of information about a specific sink."""
- info = []
- @_ffi.callback("pa_sink_info_cb_t")
- def callback(context, sink_info, eol, userdata):
- if not eol:
- info_dict = dict(latency=sink_info.latency,
-
configured_latency=sink_info.configured_latency,
- channels=sink_info.sample_spec.channels,
-
name=_ffi.string(sink_info.description).decode('utf-8'))
- for prop in ['device.class', 'device.api', 'device.bus']:
- data = _pa.pa_proplist_gets(sink_info.proplist,
prop.encode())
- info_dict[prop] = _ffi.string(data).decode('utf-8') if
data else None
- info.append(info_dict)
- self._pa_context_get_sink_info_by_name(self.context, id.encode(),
callback, _ffi.NULL)
- return info[0]
-
- @property
- def server_info(self):
- """Return a dictionary of information about the server."""
- info = {}
- @_ffi.callback("pa_server_info_cb_t")
- def callback(context, server_info, userdata):
- info['server version'] =
_ffi.string(server_info.server_version).decode('utf-8')
- info['server name'] =
_ffi.string(server_info.server_name).decode('utf-8')
- info['default sink id'] =
_ffi.string(server_info.default_sink_name).decode('utf-8')
- info['default source id'] =
_ffi.string(server_info.default_source_name).decode('utf-8')
- self._pa_context_get_server_info(self.context, callback, _ffi.NULL)
- return info
-
- def _lock_mainloop(self):
- """Context manager for locking the mainloop.
-
- Hold this lock before calling any pulseaudio function while
- the mainloop is running.
-
- """
-
- class Lock():
- def __enter__(self_):
- _pa.pa_threaded_mainloop_lock(self.mainloop)
- def __exit__(self_, exc_type, exc_value, traceback):
- _pa.pa_threaded_mainloop_unlock(self.mainloop)
- return Lock()
-
- # create thread-safe versions of all used pulseaudio functions:
- _pa_context_get_source_info_list =
_lock_and_block(_pa.pa_context_get_source_info_list)
- _pa_context_get_source_info_by_name =
_lock_and_block(_pa.pa_context_get_source_info_by_name)
- _pa_context_get_sink_info_list =
_lock_and_block(_pa.pa_context_get_sink_info_list)
- _pa_context_get_sink_info_by_name =
_lock_and_block(_pa.pa_context_get_sink_info_by_name)
- _pa_context_get_server_info =
_lock_and_block(_pa.pa_context_get_server_info)
- _pa_context_get_state = _lock(_pa.pa_context_get_state)
- _pa_context_drain = _lock(_pa.pa_context_drain)
- _pa_context_disconnect = _lock(_pa.pa_context_disconnect)
- _pa_context_unref = _lock(_pa.pa_context_unref)
- _pa_operation_get_state = _lock(_pa.pa_operation_get_state)
- _pa_operation_unref = _lock(_pa.pa_operation_unref)
- _pa_stream_get_state = _lock(_pa.pa_stream_get_state)
- _pa_sample_spec_valid = _lock(_pa.pa_sample_spec_valid)
- _pa_stream_new = _lock(_pa.pa_stream_new)
- _pa_stream_get_channel_map = _lock(_pa.pa_stream_get_channel_map)
- _pa_stream_drain = _lock_and_block(_pa.pa_stream_drain)
- _pa_stream_disconnect = _lock(_pa.pa_stream_disconnect)
- _pa_stream_unref = _lock(_pa.pa_stream_unref)
- _pa_stream_connect_record = _lock(_pa.pa_stream_connect_record)
- _pa_stream_readable_size = _lock(_pa.pa_stream_readable_size)
- _pa_stream_peek = _lock(_pa.pa_stream_peek)
- _pa_stream_drop = _lock(_pa.pa_stream_drop)
- _pa_stream_connect_playback = _lock(_pa.pa_stream_connect_playback)
- _pa_stream_update_timing_info =
_lock_and_block(_pa.pa_stream_update_timing_info)
- _pa_stream_get_latency = _lock(_pa.pa_stream_get_latency)
- _pa_stream_get_buffer_attr = _lock(_pa.pa_stream_get_buffer_attr)
- _pa_stream_writable_size = _lock(_pa.pa_stream_writable_size)
- _pa_stream_write = _lock(_pa.pa_stream_write)
- _pa_stream_set_read_callback = _pa.pa_stream_set_read_callback