Compare commits

..

24 Commits

Author SHA1 Message Date
Hubert Pham a9a4ec1ae5 Update Standards-Version. 2015-10-24 16:10:34 -07:00
Hubert Pham 91c9045b2d Update changelog. 2015-10-18 22:51:19 -07:00
Hubert Pham 5c8039c992 Merge branch 'master' into debian 2015-10-18 22:48:12 -07:00
Hubert Pham 44f3718dba Changelog: change target from experimental to unstable. 2014-04-15 20:35:43 -07:00
Hubert Pham 7d82845733 Update changelog for upload. 2014-04-14 21:37:08 -07:00
Hubert Pham ebf5b24939 Constrain files copied to .deb to prevent conflicts with other packages. 2014-04-14 21:21:46 -07:00
Felipe Sateler 5eb8166341 Debian packaging updates for upload.
* Upload to unstable
* Bump standards version
* Link jquery and underscore to the libjs- packages
2014-02-17 16:22:07 -08:00
Hubert Pham 359cf0310a Merge branch 'master' into debian 2014-02-16 19:09:24 -08:00
Hubert Pham 182b374699 Update control and changelog for PyAudio 0.2.7. 2012-10-20 21:41:52 -04:00
Hubert Pham 791122f068 Merge branch 'master' into debian 2012-10-20 21:41:20 -04:00
Hubert Pham 9b4fc102f4 Update changelog for upload. 2012-10-20 21:04:11 -04:00
Hubert Pham ba6c7f648f Differentiate the long descriptions. 2012-10-16 14:19:18 -04:00
Hubert Pham b0a71ac85b Add Suggests field for binary packages. 2012-10-16 14:19:18 -04:00
Hubert Pham f6cdfc6a41 Put documentation into separate package named python-pyaudio-doc. 2012-10-16 14:19:11 -04:00
Hubert Pham 4d2dbe20fb Simplify debian/rules. 2012-10-16 14:15:48 -04:00
Hubert Pham 0212839006 Bump debian/compat version. 2012-10-16 14:13:42 -04:00
Hubert Pham bf6555e7f0 Add support for building python3-pyaudio. 2012-10-16 14:13:42 -04:00
Hubert Pham 9da301168c Update debian files. 2012-10-16 14:13:39 -04:00
Hubert Pham b905c6efde Merge branch 'master' into debian 2012-09-02 00:53:23 -04:00
Hubert Pham 658d6a0617 Update Debian changelog. 2010-11-03 00:28:23 -04:00
Hubert Pham 42c6ec28d7 Generate documentation against built portaudio module. 2010-11-02 01:00:43 -04:00
Hubert Pham 9337013c34 Add python-docutils to Build-Depends. 2010-10-15 04:17:51 -04:00
Hubert Pham 1f859c6291 Add license to Debian packaging. 2010-09-03 01:47:00 -04:00
Hubert Pham 2067c4a45d Add debian packaging. 2010-09-02 00:51:41 -04:00
34 changed files with 303 additions and 888 deletions

View File

@ -1,25 +1,3 @@
2017-03-18 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.11
- Fix use-after-free memory issue in callback handler.
Thanks to both Blaise Potard and Matthias Schaff for their patches!
- Fix docstring for get_output_latency().
Thanks to Timothy Port for finding the issue!
2017-01-10 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.10
- Release the GIL during PortAudio I/O calls to avoid potential deadlock.
Thanks to Michael Graczyk for submitting a patch!
- Add a few automated unit tests.
2015-10-18 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.9

View File

@ -1,5 +1,5 @@
include src/*.c src/*.h src/*.py
include Makefile CHANGELOG INSTALL MANIFEST.in
recursive-include examples *.py
recursive-include tests *.py
recursive-include test *.py *.c
graft docs
graft sphinx

View File

@ -2,7 +2,7 @@
.PHONY: docs clean build
VERSION := 0.2.11
VERSION := 0.2.9
PYTHON ?= python
BUILD_ARGS ?=
SPHINX ?= sphinx-build
@ -11,8 +11,7 @@ PYTHON_BUILD_DIR:=$(shell $(PYTHON) -c "import distutils.util; import sys; print
BUILD_DIR:=lib.$(PYTHON_BUILD_DIR)
BUILD_STAMP:=$(BUILD_DIR)/build
SRCFILES := src/*.c src/*.h src/*.py
EXAMPLES := examples/*.py
TESTS := tests/*.py
EXAMPLES := test/*.py
what:
@echo "make targets:"
@ -44,5 +43,5 @@ docs: build
######################################################################
# Source Tarball
######################################################################
tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in
tarball: docs $(SRCFILES) $(EXAMPLES) MANIFEST.in
@$(PYTHON) setup.py sdist

2
README
View File

@ -1,5 +1,5 @@
======================================================================
PyAudio v0.2.12: Python Bindings for PortAudio.
PyAudio v0.2.9: Python Bindings for PortAudio.
======================================================================
See: http://people.csail.mit.edu/hubert/pyaudio/

86
debian/changelog vendored Normal file
View File

@ -0,0 +1,86 @@
python-pyaudio (0.2.9-1) unstable; urgency=low
* Fix overflow error handling logic for pa_read_stream.
* Fix IOError exception arguments.
-- Hubert Pham <hubert@mit.edu> Sun, 18 Oct 2015 19:00:00 -0500
python-pyaudio (0.2.8-1) unstable; urgency=low
* Fix support for non-UTF8 encoded device names.
* Fix deadlock on some platforms when calling pa.stop_stream.
* Fix debian packaging to avoid including unnecessary files.
Closes: #743660.
-- Hubert Pham <hubert@mit.edu> Mon, 12 Apr 2014 19:00:00 -0500
python-pyaudio (0.2.7-2) unstable; urgency=low
* Upload to unstable
* Bump standards version
* Link jquery and underscore to the libjs- packages
-- Felipe Sateler <fsateler@debian.org> Sat, 19 Oct 2013 17:35:39 -0300
python-pyaudio (0.2.7-1) experimental; urgency=low
* Add support for callables in non-blocking mode.
* Change documentation generator to Sphinx.
-- Hubert Pham <hubert@mit.edu> Sat, 20 Oct 2012 20:00:00 -0500
python-pyaudio (0.2.6-1) experimental; urgency=low
* New upstream release.
* Add support for Python 3.
* Split documentation into python-pyaudio-doc package.
-- Hubert Pham <hubert@mit.edu> Sat, 02 Sep 2012 21:00:00 -0500
python-pyaudio (0.2.5-1) UNRELEASED; urgency=low
* Add support for callback (non-blocking) operation.
-- Hubert Pham <hubert@mit.edu> Sat, 02 Sep 2012 20:00:00 -0500
python-pyaudio (0.2.4-2) unstable; urgency=low
* Add python-docutils to Build-Depends.
* Generate documentation against built portaudio module.
-- Hubert Pham <hubert@mit.edu> Tue, 02 Nov 2010 23:16:00 -0500
python-pyaudio (0.2.4-1) UNRELEASED; urgency=low
[ Felipe Sateler ]
* Bump standards version (no changes needed)
* Introduce a long description
* Use --intall-layout=deb to comply with python policy
* Use ${shlib:Depends} and ${misc:Depends}
* Move to python section
* Add dummy binary-indep target
* Add call to dh_compress
* Add homepage field
[ Hubert Pham ]
* Updated directory structure and packaging.
-- Hubert Pham <hubert@mit.edu> Wed, 18 Aug 2010 15:23:00 -0500
python-pyaudio (0.2.3) unstable; urgency=low
* Release the GIL during blocking PortAudio I/O calls.
* Fixed Python argument parsing to use a long for PaSampleFormat.
* pyaudio.PyAudio.is_format_supported() now throws a ValueError
exception if the specified format is not supported for any reason
(or returns True if the format is supported).
-- Hubert Pham <hubert@mit.edu> Thu, 30 Oct 2008 17:00:00 -0500
python-pyaudio (0.2.0) unstable; urgency=low
* Initial version
-- Justin Mazzola Paluska <jmp@mit.edu> Fri, 08 Feb 2008 13:47:27 -0500

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
9

42
debian/control vendored Normal file
View File

@ -0,0 +1,42 @@
Source: python-pyaudio
Section: python
Priority: optional
Standards-Version: 3.9.5
Build-Depends: python-all-dev, python3-all-dev, debhelper(>= 9), portaudio19-dev, python-sphinx, python-docutils, dh-linktree, libjs-jquery, libjs-underscore
Maintainer: Hubert Pham <hubert@mit.edu>
Uploaders: Felipe Sateler <fsateler@debian.org>, Justin Mazzola Paluska <jmp@mit.edu>
Homepage: http://people.csail.mit.edu/hubert/pyaudio/
Vcs-Git: http://people.csail.mit.edu/hubert/git/pyaudio.git
Vcs-Browser: http://people.csail.mit.edu/hubert/git/pyaudio.git
Package: python-pyaudio
Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}
Provides: ${python:Provides}
Suggests: python-pyaudio-doc (>= ${source:Upstream-Version})
Architecture: any
Description: Python bindings for PortAudio v19
PyAudio provides Python bindings for PortAudio v19, the
cross-platform audio I/O library. PyAudio makes it easy to use Python
to play and record audio via pythonic wrappers around the PortAudio
API. This package is for Python2.
Package: python3-pyaudio
Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}
Provides: ${python3:Provides}
Suggests: python-pyaudio-doc (>= ${source:Upstream-Version})
Architecture: any
Description: Python3 bindings for PortAudio v19
PyAudio provides Python bindings for PortAudio v19, the
cross-platform audio I/O library. PyAudio makes it easy to use Python
to play and record audio via pythonic wrappers around the PortAudio
API. This package is for Python3.
Package: python-pyaudio-doc
Section: doc
Depends: ${misc:Depends}
Architecture: all
Description: Documentation for Python bindings for PortAudio v19
Documentation for PyAudio, which provides Python bindings for
PortAudio v19, the cross-platform audio I/O library. PyAudio makes it
easy to use Python to play and record audio via pythonic wrappers
around the PortAudio API.

37
debian/copyright vendored Normal file
View File

@ -0,0 +1,37 @@
PyAudio is distributed under the MIT License. That is to say,
Copyright (c) 2006-2012 Hubert Pham
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The upstream PyAudio source is available at:
git: http://people.csail.mit.edu/hubert/git/pyaudio.git
homepage: http://people.csail.mit.edu/hubert/pyaudio/
The original PyAudio author is: Hubert Pham <hubert@mit.edu>.
The Debian packaging of PyAudio is also distributed under the
MIT license.
Debian package maintainers include:
Hubert Pham <hubert@mit.edu>
Felipe Sateler <fsateler@debian.org>
Justin Mazzola Paluska <jmp@mit.edu>

6
debian/gbp.conf vendored Normal file
View File

@ -0,0 +1,6 @@
[DEFAULT]
upstream-branch = master
debian-branch = debian
upstream-tag = v%(version)s
debian-tag = debian/%(version)s

9
debian/python-pyaudio-doc.doc-base vendored Normal file
View File

@ -0,0 +1,9 @@
Document: python-pyaudio-doc
Title: PyAudio API
Author: Hubert Pham
Abstract: This manual is the API reference for the Python PyAudio library.
Section: Programming/Python
Format: HTML
Index: /usr/share/doc/python-pyaudio-doc/docs/index.html
Files: /usr/share/doc/python-pyaudio-doc/docs/*

2
debian/python-pyaudio-doc.docs vendored Normal file
View File

@ -0,0 +1,2 @@
README
docs/

3
debian/python-pyaudio-doc.linktrees vendored Normal file
View File

@ -0,0 +1,3 @@
replace usr/share/javascript/jquery usr/share/doc/python-pyaudio-doc/docs/_static
replace usr/share/javascript/underscore usr/share/doc/python-pyaudio-doc/docs/_static

8
debian/python-pyaudio.examples vendored Normal file
View File

@ -0,0 +1,8 @@
test/error.py
test/play_wave.py
test/play_wave_callback.py
test/record.py
test/system_info.py
test/wire_callback.py
test/wire_full.py
test/wire_half.py

3
debian/python-pyaudio.install vendored Normal file
View File

@ -0,0 +1,3 @@
usr/lib/python2*/*-packages/pyaudio.py
usr/lib/python2*/*-packages/*.so
usr/lib/python2*/*-packages/*.egg-info

8
debian/python3-pyaudio.examples vendored Normal file
View File

@ -0,0 +1,8 @@
test/error.py
test/play_wave.py
test/play_wave_callback.py
test/record.py
test/system_info.py
test/wire_callback.py
test/wire_full.py
test/wire_half.py

3
debian/python3-pyaudio.install vendored Normal file
View File

@ -0,0 +1,3 @@
usr/lib/python3*/*-packages/pyaudio.py
usr/lib/python3*/*-packages/*.so
usr/lib/python3*/*-packages/*.egg-info

28
debian/rules vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/make -f
# -*- mode: makefile -*-
SPACE :=
SPACE +=
PYVERS := $(shell pyversions -vs) $(shell py3versions -vs)
PYTHONPATH=$(subst $(SPACE),:,$(abspath $(wildcard build/lib*)))
%:
dh $@ --with python2,python3,linktree
override_dh_auto_build: $(PYVERS:%=build-python%)
PYTHONPATH=$(PYTHONPATH) make docs
# This has to be in a separate target otherwise
# make gets confused and executes the PYTHONPATH too early
build-python%:
dh_testdir
python$* setup.py build
override_dh_auto_install: $(PYVERS:%=install-python%)
install-python%:
python$* setup.py install --install-layout=deb --root=$(CURDIR)/debian/tmp

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

View File

@ -1,5 +1,5 @@
"""
PyAudio v0.2.11: Python Bindings for PortAudio.
PyAudio v0.2.9: Python Bindings for PortAudio.
Copyright (c) 2006 Hubert Pham
@ -34,7 +34,7 @@ try:
except ImportError:
from distutils.core import setup, Extension
__version__ = "0.2.12"
__version__ = "0.2.9"
# distutils will try to locate and link dynamically against portaudio.
#

View File

@ -49,9 +49,9 @@ copyright = '2006, Hubert Pham'
# built documents.
#
# The short X.Y version.
version = '0.2.11'
version = '0.2.9'
# The full version, including alpha/beta/rc tags.
release = '0.2.11'
release = '0.2.9'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -1,7 +1,7 @@
Example: Blocking Mode Audio I/O
--------------------------------
.. literalinclude:: ../examples/play_wave.py
.. literalinclude:: ../test/play_wave.py
To use PyAudio, first instantiate PyAudio using
:py:func:`pyaudio.PyAudio` (1), which sets up the portaudio system.
@ -30,7 +30,7 @@ Finally, terminate the portaudio session using
Example: Callback Mode Audio I/O
--------------------------------
.. literalinclude:: ../examples/play_wave_callback.py
.. literalinclude:: ../test/play_wave_callback.py
In callback mode, PyAudio will call a specified callback function (2)
whenever it needs new audio data (to play) and/or when there is new

View File

@ -25,7 +25,6 @@
*/
#include <stdio.h>
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "portaudio.h"
#include "_portaudiomodule.h"
@ -755,8 +754,8 @@ typedef struct {
typedef struct {
// clang-format off
PyObject_HEAD
// clang-format on
PaStream *stream;
// clang-format on
PaStream *stream;
PaStreamParameters *inputParameters;
PaStreamParameters *outputParameters;
PaStreamInfo *streamInfo;
@ -772,8 +771,8 @@ static void _cleanup_Stream_object(_pyAudio_Stream *streamObject) {
Py_BEGIN_ALLOW_THREADS
Pa_CloseStream(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
streamObject->stream = NULL;
// clang-format on
streamObject->stream = NULL;
}
if (streamObject->streamInfo) streamObject->streamInfo = NULL;
@ -976,26 +975,14 @@ static PyObject *pa_get_version_text(PyObject *self, PyObject *args) {
static PyObject *pa_initialize(PyObject *self, PyObject *args) {
int err;
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_Initialize();
Py_END_ALLOW_THREADS
// clang-format on
if (err != paNoError) {
// clang-format off
Py_BEGIN_ALLOW_THREADS
Pa_Terminate();
Py_END_ALLOW_THREADS
// clang-format on
#ifdef VERBOSE
fprintf(stderr, "An error occured while using the portaudio stream\n");
fprintf(stderr, "Error number: %d\n", err);
fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err));
#endif
PyErr_SetObject(PyExc_IOError,
Py_BuildValue("(i,s)", err, Pa_GetErrorText(err)));
return NULL;
@ -1006,12 +993,7 @@ static PyObject *pa_initialize(PyObject *self, PyObject *args) {
}
static PyObject *pa_terminate(PyObject *self, PyObject *args) {
// clang-format off
Py_BEGIN_ALLOW_THREADS
Pa_Terminate();
Py_END_ALLOW_THREADS
// clang-format on
Py_INCREF(Py_None);
return Py_None;
}
@ -1292,7 +1274,7 @@ int _stream_callback_cfunction(const void *input, void *output,
PyObject *py_status_flags = PyLong_FromUnsignedLong(statusFlags);
PyObject *py_input_data = Py_None;
const char *pData;
Py_ssize_t output_len;
int output_len;
PyObject *py_result;
if (input) {
@ -1349,6 +1331,8 @@ int _stream_callback_cfunction(const void *input, void *output,
goto end;
}
Py_DECREF(py_result);
if ((return_val != paComplete) && (return_val != paAbort) &&
(return_val != paContinue)) {
PyErr_SetString(PyExc_ValueError,
@ -1357,7 +1341,6 @@ int _stream_callback_cfunction(const void *input, void *output,
PyErr_Print();
// Quit the callback loop
Py_DECREF(py_result);
return_val = paAbort;
goto end;
}
@ -1374,7 +1357,6 @@ int _stream_callback_cfunction(const void *input, void *output,
return_val = paComplete;
}
}
Py_DECREF(py_result);
end:
if (input) {
@ -1601,8 +1583,6 @@ static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) {
context->frame_size = Pa_GetSampleSize(format) * channels;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_OpenStream(&stream,
/* input/output parameters */
/* NULL values are ignored */
@ -1618,8 +1598,6 @@ static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) {
(stream_callback) ? (_stream_callback_cfunction) : (NULL),
/* callback userData, if applicable */
context);
Py_END_ALLOW_THREADS
// clang-format on
if (err != paNoError) {
#ifdef VERBOSE
@ -1766,6 +1744,7 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) {
int err;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -1779,13 +1758,9 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_StartStream(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
stream = streamObject->stream;
if ((err != paNoError) &&
if (((err = Pa_StartStream(stream)) != paNoError) &&
(err != paStreamIsNotStopped)) {
_cleanup_Stream_object(streamObject);
@ -1808,6 +1783,7 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) {
int err;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -1820,13 +1796,15 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) {
return NULL;
}
stream = streamObject->stream;
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_StopStream(streamObject->stream);
err = Pa_StopStream(stream);
Py_END_ALLOW_THREADS
// clang-format on
// clang-format on
if ((err != paNoError) && (err != paStreamIsStopped)) {
if ((err != paNoError) && (err != paStreamIsStopped)) {
_cleanup_Stream_object(streamObject);
#ifdef VERBOSE
@ -1848,6 +1826,7 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) {
int err;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -1860,13 +1839,15 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) {
return NULL;
}
stream = streamObject->stream;
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_AbortStream(streamObject->stream);
err = Pa_AbortStream(stream);
Py_END_ALLOW_THREADS
// clang-format on
// clang-format on
if ((err != paNoError) && (err != paStreamIsStopped)) {
if ((err != paNoError) && (err != paStreamIsStopped)) {
_cleanup_Stream_object(streamObject);
#ifdef VERBOSE
@ -1888,6 +1869,7 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) {
int err;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -1901,13 +1883,9 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_IsStreamStopped(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
stream = streamObject->stream;
if (err < 0) {
if ((err = Pa_IsStreamStopped(stream)) < 0) {
_cleanup_Stream_object(streamObject);
#ifdef VERBOSE
@ -1934,6 +1912,7 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) {
int err;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -1946,13 +1925,9 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_IsStreamActive(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
stream = streamObject->stream;
if (err < 0) {
if ((err = Pa_IsStreamActive(stream)) < 0) {
_cleanup_Stream_object(streamObject);
#ifdef VERBOSE
@ -1979,6 +1954,7 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
double time;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -1992,13 +1968,9 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
time = Pa_GetStreamTime(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
stream = streamObject->stream;
if (time == 0) {
if ((time = Pa_GetStreamTime(stream)) == 0) {
_cleanup_Stream_object(streamObject);
PyErr_SetObject(PyExc_IOError,
Py_BuildValue("(i,s)", paInternalError, "Internal Error"));
@ -2009,9 +1981,9 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
}
static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
double cpuload;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -2025,13 +1997,8 @@ static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
cpuload = Pa_GetStreamCpuLoad(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
return PyFloat_FromDouble(cpuload);
stream = streamObject->stream;
return PyFloat_FromDouble(Pa_GetStreamCpuLoad(stream));
}
/*************************************************************
@ -2047,6 +2014,7 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) {
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
// clang-format off
if (!PyArg_ParseTuple(args, "O!s#i|i",
@ -2073,13 +2041,15 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) {
return NULL;
}
stream = streamObject->stream;
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_WriteStream(streamObject->stream, data, total_frames);
err = Pa_WriteStream(stream, data, total_frames);
Py_END_ALLOW_THREADS
// clang-format on
// clang-format on
if (err != paNoError) {
if (err != paNoError) {
if (err == paOutputUnderflowed) {
if (should_throw_exception) {
goto error;
@ -2115,6 +2085,7 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
PaStreamParameters *inputParameters;
// clang-format off
@ -2140,6 +2111,7 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
return NULL;
}
stream = streamObject->stream;
inputParameters = streamObject->inputParameters;
num_bytes = (total_frames) * (inputParameters->channelCount) *
(Pa_GetSampleSize(inputParameters->sampleFormat));
@ -2159,11 +2131,11 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
// clang-format off
Py_BEGIN_ALLOW_THREADS
err = Pa_ReadStream(streamObject->stream, sampleBlock, total_frames);
err = Pa_ReadStream(stream, sampleBlock, total_frames);
Py_END_ALLOW_THREADS
// clang-format on
// clang-format on
if (err != paNoError) {
if (err != paNoError) {
if (err == paInputOverflowed) {
if (should_raise_exception) {
goto error;
@ -2194,6 +2166,7 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) {
signed long frames;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -2207,12 +2180,8 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
frames = Pa_GetStreamWriteAvailable(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
stream = streamObject->stream;
frames = Pa_GetStreamWriteAvailable(stream);
return PyLong_FromLong(frames);
}
@ -2220,6 +2189,7 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) {
signed long frames;
PyObject *stream_arg;
_pyAudio_Stream *streamObject;
PaStream *stream;
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
return NULL;
@ -2233,12 +2203,8 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) {
return NULL;
}
// clang-format off
Py_BEGIN_ALLOW_THREADS
frames = Pa_GetStreamReadAvailable(streamObject->stream);
Py_END_ALLOW_THREADS
// clang-format on
stream = streamObject->stream;
frames = Pa_GetStreamReadAvailable(stream);
return PyLong_FromLong(frames);
}

View File

@ -106,7 +106,7 @@ Overview
"""
__author__ = "Hubert Pham"
__version__ = "0.2.11"
__version__ = "0.2.9"
__docformat__ = "restructuredtext en"
import sys
@ -471,7 +471,7 @@ class Stream:
def get_output_latency(self):
"""
Return the output latency.
Return the input latency.
:rtype: float
"""

View File

@ -12,7 +12,6 @@ import sys
WIDTH = 2
CHANNELS = 2
RATE = 44100
DURATION = 5
if sys.platform == 'darwin':
CHANNELS = 1
@ -31,9 +30,7 @@ stream = p.open(format=p.get_format_from_width(WIDTH),
stream.start_stream()
start = time.time()
while stream.is_active() and (time.time() - start) < DURATION:
while stream.is_active():
time.sleep(0.1)
stream.stop_stream()

View File

@ -1,120 +0,0 @@
import sys
import time
import unittest
import pyaudio
class PyAudioErrorTests(unittest.TestCase):
def setUp(self):
self.p = pyaudio.PyAudio()
def tearDown(self):
self.p.terminate()
def test_invalid_sample_size(self):
with self.assertRaises(ValueError):
self.p.get_sample_size(10)
def test_invalid_width(self):
with self.assertRaises(ValueError):
self.p.get_format_from_width(8)
def test_invalid_device(self):
with self.assertRaises(IOError):
self.p.get_host_api_info_by_type(-1)
def test_invalid_hostapi(self):
with self.assertRaises(IOError):
self.p.get_host_api_info_by_index(-1)
def test_invalid_host_api_devinfo(self):
with self.assertRaises(IOError):
self.p.get_device_info_by_host_api_device_index(0, -1)
with self.assertRaises(IOError):
self.p.get_device_info_by_host_api_device_index(-1, 0)
def test_invalid_device_devinfo(self):
with self.assertRaises(IOError):
self.p.get_device_info_by_index(-1)
def test_error_without_stream_start(self):
with self.assertRaises(IOError):
stream = self.p.open(channels=1,
rate=44100,
format=pyaudio.paInt16,
input=True,
start=False) # not starting stream
stream.read(2)
def test_error_writing_to_readonly_stream(self):
with self.assertRaises(IOError):
stream = self.p.open(channels=1,
rate=44100,
format=pyaudio.paInt16,
input=True)
stream.write('foo')
def test_error_negative_frames(self):
with self.assertRaises(ValueError):
stream = self.p.open(channels=1,
rate=44100,
format=pyaudio.paInt16,
input=True)
stream.read(-1)
def test_invalid_attr_on_closed_stream(self):
stream = self.p.open(channels=1,
rate=44100,
format=pyaudio.paInt16,
input=True)
stream.close()
with self.assertRaises(IOError):
stream.get_input_latency()
with self.assertRaises(IOError):
stream.read(1)
def test_invalid_format_supported(self):
with self.assertRaises(ValueError):
self.p.is_format_supported(8000, -1, 1, pyaudio.paInt16)
with self.assertRaises(ValueError):
self.p.is_format_supported(8000, 0, -1, pyaudio.paInt16)
def test_write_underflow_exception(self):
stream = self.p.open(channels=1,
rate=44100,
format=pyaudio.paInt16,
output=True)
time.sleep(0.5)
stream.write('\x00\x00\x00\x00', exception_on_underflow=False)
# It's difficult to invoke an underflow on ALSA, so skip.
if sys.platform in ('linux', 'linux2'):
return
with self.assertRaises(IOError) as err:
time.sleep(0.5)
stream.write('\x00\x00\x00\x00', exception_on_underflow=True)
self.assertEqual(err.exception.errno, pyaudio.paOutputUnderflowed)
self.assertEqual(err.exception.strerror, 'Output underflowed')
def test_read_overflow_exception(self):
stream = self.p.open(channels=1,
rate=44100,
format=pyaudio.paInt16,
input=True)
time.sleep(0.5)
stream.read(2, exception_on_overflow=False)
# It's difficult to invoke an underflow on ALSA, so skip.
if sys.platform in ('linux', 'linux2'):
return
with self.assertRaises(IOError) as err:
time.sleep(0.5)
stream.read(2, exception_on_overflow=True)
self.assertEqual(err.exception.errno, pyaudio.paInputOverflowed)
self.assertEqual(err.exception.strerror, 'Input overflowed')

View File

@ -1,642 +0,0 @@
# -*- coding: utf-8 -*-
"""Automated unit tests for testing audio playback and capture.
These tests require an OS loopback sound device that forwards audio
output, generated by PyAudio for playback, and forwards it to an input
device, which PyAudio can record and verify against a test signal.
On Mac OS X, Soundflower can create such a device.
On GNU/Linux, the snd-aloop kernel module provides a loopback ALSA
device. Use examples/system_info.py to identify the name of the loopback
device.
"""
import math
import struct
import time
import unittest
import wave
import sys
import numpy
import pyaudio
DUMP_CAPTURE=False
class PyAudioTests(unittest.TestCase):
def setUp(self):
self.p = pyaudio.PyAudio()
(self.loopback_input_idx,
self.loopback_output_idx) = self.get_audio_loopback()
assert (self.loopback_input_idx is None
or self.loopback_input_idx >= 0), "No loopback device found"
assert (self.loopback_output_idx is None
or self.loopback_output_idx >= 0), "No loopback device found"
def tearDown(self):
self.p.terminate()
def get_audio_loopback(self):
if sys.platform == 'darwin':
return self._find_audio_loopback(
'Soundflower (2ch)', 'Soundflower (2ch)')
if sys.platform in ('linux', 'linux2'):
return self._find_audio_loopback(
'Loopback: PCM (hw:1,0)', 'Loopback: PCM (hw:1,1)')
if sys.platform == 'win32':
# Assumes running in a VM, in which the hypervisor can
# set up a loopback device to back the "default" audio devices.
# Here, None indicates default device.
return None, None
return -1, -1
def _find_audio_loopback(self, indev, outdev):
"""Utility to find audio loopback device."""
input_idx, output_idx = -1, -1
for device_idx in range(self.p.get_device_count()):
devinfo = self.p.get_device_info_by_index(device_idx)
if (outdev == devinfo.get('name') and
devinfo.get('maxOutputChannels', 0) > 0):
output_idx = device_idx
if (indev == devinfo.get('name') and
devinfo.get('maxInputChannels', 0) > 0):
input_idx = device_idx
if output_idx > -1 and input_idx > -1:
break
return input_idx, output_idx
def test_system_info(self):
"""Basic system info tests"""
self.assertTrue(self.p.get_host_api_count() > 0)
self.assertTrue(self.p.get_device_count() > 0)
api_info = self.p.get_host_api_info_by_index(0)
self.assertTrue(len(api_info.items()) > 0)
def test_input_output_blocking(self):
"""Test blocking-based record and playback."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
# Blocking-mode might add some initial choppiness on some
# platforms/loopback devices, so set a longer duration.
duration = 3 # seconds
frames_per_chunk = 1024
freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99]
test_signal = self.create_reference_signal(freqs, rate, width, duration)
audio_chunks = self.signal_to_chunks(
test_signal, frames_per_chunk, channels)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx)
captured = []
for chunk in audio_chunks:
out_stream.write(chunk)
captured.append(in_stream.read(frames_per_chunk))
# Capture a few more frames, since there is some lag.
for i in range(8):
captured.append(in_stream.read(frames_per_chunk))
in_stream.stop_stream()
out_stream.stop_stream()
if DUMP_CAPTURE:
self.write_wav('test_blocking.wav', b''.join(captured),
width, channels, rate)
captured_signal = self.pcm16_to_numpy(b''.join(captured))
captured_left_channel = captured_signal[::2]
captured_right_channel = captured_signal[1::2]
self.assert_pcm16_spectrum_nearly_equal(
rate,
captured_left_channel,
test_signal,
len(freqs))
self.assert_pcm16_spectrum_nearly_equal(
rate,
captured_right_channel,
test_signal,
len(freqs))
def test_input_output_callback(self):
"""Test callback-based record and playback."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
duration = 1 # second
frames_per_chunk = 1024
freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99]
test_signal = self.create_reference_signal(freqs, rate, width, duration)
audio_chunks = self.signal_to_chunks(
test_signal, frames_per_chunk, channels)
state = {'count': 0}
def out_callback(_, frame_count, time_info, status):
if state['count'] >= len(audio_chunks):
return ('', pyaudio.paComplete)
rval = (audio_chunks[state['count']], pyaudio.paContinue)
state['count'] += 1
return rval
captured = []
def in_callback(in_data, frame_count, time_info, status):
captured.append(in_data)
return (None, pyaudio.paContinue)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx,
stream_callback=in_callback)
in_stream.start_stream()
out_stream.start_stream()
time.sleep(duration + 1)
in_stream.stop_stream()
out_stream.stop_stream()
if DUMP_CAPTURE:
self.write_wav('test_callback.wav', b''.join(captured),
width, channels, rate)
captured_signal = self.pcm16_to_numpy(b''.join(captured))
captured_left_channel = captured_signal[::2]
captured_right_channel = captured_signal[1::2]
self.assert_pcm16_spectrum_nearly_equal(
rate,
captured_left_channel,
test_signal,
len(freqs))
self.assert_pcm16_spectrum_nearly_equal(
rate,
captured_right_channel,
test_signal,
len(freqs))
def test_device_lock_gil_order(self):
"""Ensure no deadlock between Pa_{Open,Start,Stop}Stream and GIL."""
# This test targets OSX/macOS CoreAudio, which seems to use
# audio device locks. On ALSA and Win32 MME, this problem
# doesn't seem to appear despite not releasing the GIL when
# calling into PortAudio.
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def out_callback(_, frame_count, time_info, status):
return ('', pyaudio.paComplete)
def in_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
start=False,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx,
stream_callback=in_callback)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
in_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Opening a stream and starting it MUST
# release the GIL before attempting to acquire the device
# lock. Otherwise, the following code will wait for the device
# lock (while holding the GIL), while the in_callback thread
# will be waiting for the GIL once time.sleep completes (while
# holding the device lock), leading to deadlock.
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
out_stream.start_stream()
time.sleep(0.1)
in_stream.stop_stream()
out_stream.stop_stream()
def test_stream_state_gil(self):
"""Ensure no deadlock between Pa_IsStream{Active,Stopped} and GIL."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def out_callback(_, frame_count, time_info, status):
return ('', pyaudio.paComplete)
def in_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
start=False,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx,
stream_callback=in_callback)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
start=False,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
in_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Checking the state of the stream MUST
# not require the device lock, but if it does, it must release the GIL
# before attempting to acquire the device
# lock. Otherwise, the following code will wait for the device
# lock (while holding the GIL), while the in_callback thread
# will be waiting for the GIL once time.sleep completes (while
# holding the device lock), leading to deadlock.
self.assertTrue(in_stream.is_active())
self.assertFalse(in_stream.is_stopped())
self.assertTrue(out_stream.is_stopped())
self.assertFalse(out_stream.is_active())
out_stream.start_stream()
self.assertFalse(out_stream.is_stopped())
self.assertTrue(out_stream.is_active())
time.sleep(0.1)
in_stream.stop_stream()
out_stream.stop_stream()
def test_get_stream_time_gil(self):
"""Ensure no deadlock between PA_GetStreamTime and GIL."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def out_callback(_, frame_count, time_info, status):
return ('', pyaudio.paComplete)
def in_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
start=False,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx,
stream_callback=in_callback)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
start=False,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
in_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Getting the stream time MUST not
# require the device lock, but if it does, it must release the
# GIL before attempting to acquire the device lock. Otherwise,
# the following code will wait for the device lock (while
# holding the GIL), while the in_callback thread will be
# waiting for the GIL once time.sleep completes (while holding
# the device lock), leading to deadlock.
self.assertGreater(in_stream.get_time(), -1)
self.assertGreater(out_stream.get_time(), 1)
time.sleep(0.1)
in_stream.stop_stream()
out_stream.stop_stream()
def test_get_stream_cpuload_gil(self):
"""Ensure no deadlock between Pa_GetStreamCpuLoad and GIL."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def out_callback(_, frame_count, time_info, status):
return ('', pyaudio.paComplete)
def in_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
start=False,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx,
stream_callback=in_callback)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
start=False,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
in_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Getting the stream cpuload MUST not
# require the device lock, but if it does, it must release the
# GIL before attempting to acquire the device lock. Otherwise,
# the following code will wait for the device lock (while
# holding the GIL), while the in_callback thread will be
# waiting for the GIL once time.sleep completes (while holding
# the device lock), leading to deadlock.
self.assertGreater(in_stream.get_cpu_load(), -1)
self.assertGreater(out_stream.get_cpu_load(), -1)
time.sleep(0.1)
in_stream.stop_stream()
out_stream.stop_stream()
def test_get_stream_write_available_gil(self):
"""Ensure no deadlock between Pa_GetStreamWriteAvailable and GIL."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def in_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
start=False,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx,
stream_callback=in_callback)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
in_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Getting the stream write available MUST not
# require the device lock, but if it does, it must release the
# GIL before attempting to acquire the device lock. Otherwise,
# the following code will wait for the device lock (while
# holding the GIL), while the in_callback thread will be
# waiting for the GIL once time.sleep completes (while holding
# the device lock), leading to deadlock.
self.assertGreater(out_stream.get_write_available(), -1)
time.sleep(0.1)
in_stream.stop_stream()
def test_get_stream_read_available_gil(self):
"""Ensure no deadlock between Pa_GetStreamReadAvailable and GIL."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def out_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
in_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
input=True,
frames_per_buffer=frames_per_chunk,
input_device_index=self.loopback_input_idx)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
start=False,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
out_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Getting the stream read available MUST not
# require the device lock, but if it does, it must release the
# GIL before attempting to acquire the device lock. Otherwise,
# the following code will wait for the device lock (while
# holding the GIL), while the in_callback thread will be
# waiting for the GIL once time.sleep completes (while holding
# the device lock), leading to deadlock.
self.assertGreater(in_stream.get_read_available(), -1)
time.sleep(0.1)
in_stream.stop_stream()
def test_terminate_gil(self):
"""Ensure no deadlock between Pa_Terminate and GIL."""
rate = 44100 # frames per second
width = 2 # bytes per sample
channels = 2
frames_per_chunk = 1024
def out_callback(in_data, frame_count, time_info, status):
# Release the GIL for a bit
time.sleep(2)
return (None, pyaudio.paComplete)
out_stream = self.p.open(
format=self.p.get_format_from_width(width),
channels=channels,
rate=rate,
output=True,
start=False,
frames_per_buffer=frames_per_chunk,
output_device_index=self.loopback_output_idx,
stream_callback=out_callback)
# In a separate (C) thread, portaudio/driver will grab the device lock,
# then the GIL to call in_callback.
out_stream.start_stream()
# Wait a bit to let that callback thread start.
time.sleep(1)
# in_callback will eventually drop the GIL when executing
# time.sleep (while retaining the device lock), allowing the
# following code to run. Terminating PyAudio MUST not
# require the device lock, but if it does, it must release the
# GIL before attempting to acquire the device lock. Otherwise,
# the following code will wait for the device lock (while
# holding the GIL), while the in_callback thread will be
# waiting for the GIL once time.sleep completes (while holding
# the device lock), leading to deadlock.
self.p.terminate()
@staticmethod
def create_reference_signal(freqs, sampling_rate, width, duration):
"""Return reference signal with several sinuoids with frequencies
specified by freqs."""
total_frames = int(sampling_rate * duration)
max_amp = float(2**(width * 8 - 1) - 1)
avg_amp = max_amp / len(freqs)
return [
int(sum(avg_amp * math.sin(2*math.pi*freq*(k/float(sampling_rate)))
for freq in freqs))
for k in range(total_frames)]
@staticmethod
def signal_to_chunks(frame_data, frames_per_chunk, channels):
"""Given an array of values comprising the signal, return an iterable
of binary chunks, with each chunk containing frames_per_chunk
frames. Each frame represents a single value from the signal,
duplicated for each channel specified by channels.
"""
frames = [struct.pack('h', x) * channels for x in frame_data]
# Chop up frames into chunks
return [b''.join(chunk_frames) for chunk_frames in
tuple(frames[i:i+frames_per_chunk]
for i in range(0, len(frames), frames_per_chunk))]
@staticmethod
def pcm16_to_numpy(bytestring):
"""From PCM 16-bit bytes, return an equivalent numpy array of values."""
return struct.unpack('%dh' % (len(bytestring) / 2), bytestring)
@staticmethod
def write_wav(filename, data, width, channels, rate):
"""Write PCM data to wave file."""
wf = wave.open(filename, 'wb')
wf.setnchannels(channels)
wf.setsampwidth(width)
wf.setframerate(rate)
wf.writeframes(data)
wf.close()
def assert_pcm16_spectrum_nearly_equal(self, sampling_rate, cap, ref,
num_freq_peaks_expected):
"""Compares the discrete fourier transform of a captured signal
against the reference signal and ensures that the frequency peaks
match."""
# When passing a reference signal through the loopback device,
# the captured signal may include additional noise, as well as
# time lag, so testing that the captured signal is "similar
# enough" to the reference using bit-wise equality won't work
# well. Instead, the approach here a) assumes the reference
# signal is a sum of sinusoids and b) computes the discrete
# fourier transform of the reference and captured signals, and
# ensures that the frequencies of the top
# num_freq_peaks_expected frequency peaks are close.
cap_fft = numpy.absolute(numpy.fft.rfft(cap))
ref_fft = numpy.absolute(numpy.fft.rfft(ref))
# Find the indices of the peaks:
cap_peak_indices = sorted(numpy.argpartition(
cap_fft, -num_freq_peaks_expected)[-num_freq_peaks_expected:])
ref_peak_indices = sorted(numpy.argpartition(
ref_fft, -num_freq_peaks_expected)[-num_freq_peaks_expected:])
# Ensure that the corresponding frequencies of the peaks are close:
for cap_freq_index, ref_freq_index in zip(cap_peak_indices,
ref_peak_indices):
cap_freq = cap_freq_index / float(len(cap)) * (sampling_rate / 2)
ref_freq = ref_freq_index / float(len(ref)) * (sampling_rate / 2)
diff = abs(cap_freq - ref_freq)
self.assertLess(diff, 1.0)
# As an additional test, verify that the spectrum (not just
# the peaks) of the reference and captured signal are similar
# by computing the cross-correlation of the spectra. Assuming they
# are nearly identical, the cross-correlation should contain a large
# peak when the spectra overlap and mostly 0s elsewhere. Verify that
# using a histogram of the cross-correlation:
freq_corr_hist, _ = numpy.histogram(
numpy.correlate(cap_fft, ref_fft, mode='full'),
bins=10)
self.assertLess(sum(freq_corr_hist[2:])/sum(freq_corr_hist), 1e-2)