Compare commits
22 Commits
_debian_te
...
master
Author | SHA1 | Date |
---|---|---|
Derek | f749f2187e | |
Derek | 2ee560056e | |
Hubert Pham | 7090e25bcb | |
Hubert Pham | 045ebc7767 | |
Hubert Pham | 653fdfeb87 | |
Hubert Pham | 281a8f32e7 | |
Hubert Pham | 39de78a74a | |
Hubert Pham | 2e696d98c0 | |
Hubert Pham | 833ebc0b14 | |
Hubert Pham | fe528b2050 | |
Hubert Pham | 8285b9bc5c | |
Hubert Pham | a0f6d13628 | |
Hubert Pham | 47a80684e1 | |
Hubert Pham | 439327bcd7 | |
Hubert Pham | c9bc0193b0 | |
Hubert Pham | 884cedae7f | |
Hubert Pham | 50e08d41d4 | |
Hubert Pham | 7a61080270 | |
Hubert Pham | e59fa4a24e | |
Hubert Pham | 870016f6d1 | |
Hubert Pham | bff409df19 | |
Hubert Pham | da8c238eee |
22
CHANGELOG
22
CHANGELOG
|
@ -1,3 +1,25 @@
|
||||||
|
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>
|
2015-10-18 Hubert Pham <hubert@mit.edu>
|
||||||
|
|
||||||
PyAudio 0.2.9
|
PyAudio 0.2.9
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
include src/*.c src/*.h src/*.py
|
include src/*.c src/*.h src/*.py
|
||||||
include Makefile CHANGELOG INSTALL MANIFEST.in
|
include Makefile CHANGELOG INSTALL MANIFEST.in
|
||||||
recursive-include test *.py *.c
|
recursive-include examples *.py
|
||||||
graft docs
|
recursive-include tests *.py
|
||||||
graft sphinx
|
graft sphinx
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
.PHONY: docs clean build
|
.PHONY: docs clean build
|
||||||
|
|
||||||
VERSION := 0.2.9
|
VERSION := 0.2.11
|
||||||
PYTHON ?= python
|
PYTHON ?= python
|
||||||
BUILD_ARGS ?=
|
BUILD_ARGS ?=
|
||||||
SPHINX ?= sphinx-build
|
SPHINX ?= sphinx-build
|
||||||
|
@ -11,7 +11,8 @@ PYTHON_BUILD_DIR:=$(shell $(PYTHON) -c "import distutils.util; import sys; print
|
||||||
BUILD_DIR:=lib.$(PYTHON_BUILD_DIR)
|
BUILD_DIR:=lib.$(PYTHON_BUILD_DIR)
|
||||||
BUILD_STAMP:=$(BUILD_DIR)/build
|
BUILD_STAMP:=$(BUILD_DIR)/build
|
||||||
SRCFILES := src/*.c src/*.h src/*.py
|
SRCFILES := src/*.c src/*.h src/*.py
|
||||||
EXAMPLES := test/*.py
|
EXAMPLES := examples/*.py
|
||||||
|
TESTS := tests/*.py
|
||||||
|
|
||||||
what:
|
what:
|
||||||
@echo "make targets:"
|
@echo "make targets:"
|
||||||
|
@ -43,5 +44,5 @@ docs: build
|
||||||
######################################################################
|
######################################################################
|
||||||
# Source Tarball
|
# Source Tarball
|
||||||
######################################################################
|
######################################################################
|
||||||
tarball: docs $(SRCFILES) $(EXAMPLES) MANIFEST.in
|
tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in
|
||||||
@$(PYTHON) setup.py sdist
|
@$(PYTHON) setup.py sdist
|
||||||
|
|
2
README
2
README
|
@ -1,5 +1,5 @@
|
||||||
======================================================================
|
======================================================================
|
||||||
PyAudio v0.2.9: Python Bindings for PortAudio.
|
PyAudio v0.2.12: Python Bindings for PortAudio.
|
||||||
======================================================================
|
======================================================================
|
||||||
|
|
||||||
See: http://people.csail.mit.edu/hubert/pyaudio/
|
See: http://people.csail.mit.edu/hubert/pyaudio/
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
9
|
|
|
@ -1,42 +0,0 @@
|
||||||
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.
|
|
|
@ -1,37 +0,0 @@
|
||||||
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>
|
|
|
@ -1,6 +0,0 @@
|
||||||
[DEFAULT]
|
|
||||||
upstream-branch = master
|
|
||||||
debian-branch = debian
|
|
||||||
|
|
||||||
upstream-tag = v%(version)s
|
|
||||||
debian-tag = debian/%(version)s
|
|
|
@ -1,9 +0,0 @@
|
||||||
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/*
|
|
|
@ -1,2 +0,0 @@
|
||||||
README
|
|
||||||
docs/
|
|
|
@ -1,3 +0,0 @@
|
||||||
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
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
|
@ -1,3 +0,0 @@
|
||||||
usr/lib/python2*/*-packages/pyaudio.py
|
|
||||||
usr/lib/python2*/*-packages/*.so
|
|
||||||
usr/lib/python2*/*-packages/*.egg-info
|
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
|
@ -1,3 +0,0 @@
|
||||||
usr/lib/python3*/*-packages/pyaudio.py
|
|
||||||
usr/lib/python3*/*-packages/*.so
|
|
||||||
usr/lib/python3*/*-packages/*.egg-info
|
|
|
@ -1,28 +0,0 @@
|
||||||
#!/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 +0,0 @@
|
||||||
3.0 (quilt)
|
|
|
@ -12,6 +12,7 @@ import sys
|
||||||
WIDTH = 2
|
WIDTH = 2
|
||||||
CHANNELS = 2
|
CHANNELS = 2
|
||||||
RATE = 44100
|
RATE = 44100
|
||||||
|
DURATION = 5
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
CHANNELS = 1
|
CHANNELS = 1
|
||||||
|
@ -30,7 +31,9 @@ stream = p.open(format=p.get_format_from_width(WIDTH),
|
||||||
|
|
||||||
stream.start_stream()
|
stream.start_stream()
|
||||||
|
|
||||||
while stream.is_active():
|
start = time.time()
|
||||||
|
|
||||||
|
while stream.is_active() and (time.time() - start) < DURATION:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
stream.stop_stream()
|
stream.stop_stream()
|
4
setup.py
4
setup.py
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
PyAudio v0.2.9: Python Bindings for PortAudio.
|
PyAudio v0.2.11: Python Bindings for PortAudio.
|
||||||
|
|
||||||
Copyright (c) 2006 Hubert Pham
|
Copyright (c) 2006 Hubert Pham
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from distutils.core import setup, Extension
|
from distutils.core import setup, Extension
|
||||||
|
|
||||||
__version__ = "0.2.9"
|
__version__ = "0.2.12"
|
||||||
|
|
||||||
# distutils will try to locate and link dynamically against portaudio.
|
# distutils will try to locate and link dynamically against portaudio.
|
||||||
#
|
#
|
||||||
|
|
|
@ -49,9 +49,9 @@ copyright = '2006, Hubert Pham'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.2.9'
|
version = '0.2.11'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.2.9'
|
release = '0.2.11'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
Example: Blocking Mode Audio I/O
|
Example: Blocking Mode Audio I/O
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
.. literalinclude:: ../test/play_wave.py
|
.. literalinclude:: ../examples/play_wave.py
|
||||||
|
|
||||||
To use PyAudio, first instantiate PyAudio using
|
To use PyAudio, first instantiate PyAudio using
|
||||||
:py:func:`pyaudio.PyAudio` (1), which sets up the portaudio system.
|
: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
|
Example: Callback Mode Audio I/O
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
.. literalinclude:: ../test/play_wave_callback.py
|
.. literalinclude:: ../examples/play_wave_callback.py
|
||||||
|
|
||||||
In callback mode, PyAudio will call a specified callback function (2)
|
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
|
whenever it needs new audio data (to play) and/or when there is new
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "portaudio.h"
|
#include "portaudio.h"
|
||||||
#include "_portaudiomodule.h"
|
#include "_portaudiomodule.h"
|
||||||
|
@ -975,14 +976,26 @@ static PyObject *pa_get_version_text(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *pa_initialize(PyObject *self, PyObject *args) {
|
static PyObject *pa_initialize(PyObject *self, PyObject *args) {
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
err = Pa_Initialize();
|
err = Pa_Initialize();
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
Pa_Terminate();
|
Pa_Terminate();
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
fprintf(stderr, "An error occured while using the portaudio stream\n");
|
fprintf(stderr, "An error occured while using the portaudio stream\n");
|
||||||
fprintf(stderr, "Error number: %d\n", err);
|
fprintf(stderr, "Error number: %d\n", err);
|
||||||
fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err));
|
fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PyErr_SetObject(PyExc_IOError,
|
PyErr_SetObject(PyExc_IOError,
|
||||||
Py_BuildValue("(i,s)", err, Pa_GetErrorText(err)));
|
Py_BuildValue("(i,s)", err, Pa_GetErrorText(err)));
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -993,7 +1006,12 @@ static PyObject *pa_initialize(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *pa_terminate(PyObject *self, PyObject *args) {
|
static PyObject *pa_terminate(PyObject *self, PyObject *args) {
|
||||||
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
Pa_Terminate();
|
Pa_Terminate();
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
@ -1274,7 +1292,7 @@ int _stream_callback_cfunction(const void *input, void *output,
|
||||||
PyObject *py_status_flags = PyLong_FromUnsignedLong(statusFlags);
|
PyObject *py_status_flags = PyLong_FromUnsignedLong(statusFlags);
|
||||||
PyObject *py_input_data = Py_None;
|
PyObject *py_input_data = Py_None;
|
||||||
const char *pData;
|
const char *pData;
|
||||||
int output_len;
|
Py_ssize_t output_len;
|
||||||
PyObject *py_result;
|
PyObject *py_result;
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
|
@ -1331,8 +1349,6 @@ int _stream_callback_cfunction(const void *input, void *output,
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_DECREF(py_result);
|
|
||||||
|
|
||||||
if ((return_val != paComplete) && (return_val != paAbort) &&
|
if ((return_val != paComplete) && (return_val != paAbort) &&
|
||||||
(return_val != paContinue)) {
|
(return_val != paContinue)) {
|
||||||
PyErr_SetString(PyExc_ValueError,
|
PyErr_SetString(PyExc_ValueError,
|
||||||
|
@ -1341,6 +1357,7 @@ int _stream_callback_cfunction(const void *input, void *output,
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
|
|
||||||
// Quit the callback loop
|
// Quit the callback loop
|
||||||
|
Py_DECREF(py_result);
|
||||||
return_val = paAbort;
|
return_val = paAbort;
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
@ -1357,6 +1374,7 @@ int _stream_callback_cfunction(const void *input, void *output,
|
||||||
return_val = paComplete;
|
return_val = paComplete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Py_DECREF(py_result);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if (input) {
|
if (input) {
|
||||||
|
@ -1583,6 +1601,8 @@ static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) {
|
||||||
context->frame_size = Pa_GetSampleSize(format) * channels;
|
context->frame_size = Pa_GetSampleSize(format) * channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
err = Pa_OpenStream(&stream,
|
err = Pa_OpenStream(&stream,
|
||||||
/* input/output parameters */
|
/* input/output parameters */
|
||||||
/* NULL values are ignored */
|
/* NULL values are ignored */
|
||||||
|
@ -1598,6 +1618,8 @@ static PyObject *pa_open(PyObject *self, PyObject *args, PyObject *kwargs) {
|
||||||
(stream_callback) ? (_stream_callback_cfunction) : (NULL),
|
(stream_callback) ? (_stream_callback_cfunction) : (NULL),
|
||||||
/* callback userData, if applicable */
|
/* callback userData, if applicable */
|
||||||
context);
|
context);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1744,7 +1766,6 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) {
|
||||||
int err;
|
int err;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1758,9 +1779,13 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
err = Pa_StartStream(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if (((err = Pa_StartStream(stream)) != paNoError) &&
|
if ((err != paNoError) &&
|
||||||
(err != paStreamIsNotStopped)) {
|
(err != paStreamIsNotStopped)) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
|
@ -1783,7 +1808,6 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) {
|
||||||
int err;
|
int err;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1796,11 +1820,9 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
err = Pa_StopStream(stream);
|
err = Pa_StopStream(streamObject->stream);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -1826,7 +1848,6 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) {
|
||||||
int err;
|
int err;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1839,11 +1860,9 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
err = Pa_AbortStream(stream);
|
err = Pa_AbortStream(streamObject->stream);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -1869,7 +1888,6 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) {
|
||||||
int err;
|
int err;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1883,9 +1901,13 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
err = Pa_IsStreamStopped(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if ((err = Pa_IsStreamStopped(stream)) < 0) {
|
if (err < 0) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1912,7 +1934,6 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) {
|
||||||
int err;
|
int err;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1925,9 +1946,13 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
err = Pa_IsStreamActive(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if ((err = Pa_IsStreamActive(stream)) < 0) {
|
if (err < 0) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1954,7 +1979,6 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
|
||||||
double time;
|
double time;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1968,9 +1992,13 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
time = Pa_GetStreamTime(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if ((time = Pa_GetStreamTime(stream)) == 0) {
|
if (time == 0) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
PyErr_SetObject(PyExc_IOError,
|
PyErr_SetObject(PyExc_IOError,
|
||||||
Py_BuildValue("(i,s)", paInternalError, "Internal Error"));
|
Py_BuildValue("(i,s)", paInternalError, "Internal Error"));
|
||||||
|
@ -1981,9 +2009,9 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
|
static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
|
||||||
|
double cpuload;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1997,8 +2025,13 @@ static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
return PyFloat_FromDouble(Pa_GetStreamCpuLoad(stream));
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
cpuload = Pa_GetStreamCpuLoad(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
return PyFloat_FromDouble(cpuload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************
|
/*************************************************************
|
||||||
|
@ -2014,7 +2047,6 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
if (!PyArg_ParseTuple(args, "O!s#i|i",
|
if (!PyArg_ParseTuple(args, "O!s#i|i",
|
||||||
|
@ -2041,11 +2073,9 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
err = Pa_WriteStream(stream, data, total_frames);
|
err = Pa_WriteStream(streamObject->stream, data, total_frames);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -2085,7 +2115,6 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
PaStreamParameters *inputParameters;
|
PaStreamParameters *inputParameters;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
@ -2111,7 +2140,6 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
|
||||||
inputParameters = streamObject->inputParameters;
|
inputParameters = streamObject->inputParameters;
|
||||||
num_bytes = (total_frames) * (inputParameters->channelCount) *
|
num_bytes = (total_frames) * (inputParameters->channelCount) *
|
||||||
(Pa_GetSampleSize(inputParameters->sampleFormat));
|
(Pa_GetSampleSize(inputParameters->sampleFormat));
|
||||||
|
@ -2131,7 +2159,7 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
err = Pa_ReadStream(stream, sampleBlock, total_frames);
|
err = Pa_ReadStream(streamObject->stream, sampleBlock, total_frames);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
@ -2166,7 +2194,6 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) {
|
||||||
signed long frames;
|
signed long frames;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2180,8 +2207,12 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
frames = Pa_GetStreamWriteAvailable(stream);
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
frames = Pa_GetStreamWriteAvailable(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
return PyLong_FromLong(frames);
|
return PyLong_FromLong(frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2189,7 +2220,6 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) {
|
||||||
signed long frames;
|
signed long frames;
|
||||||
PyObject *stream_arg;
|
PyObject *stream_arg;
|
||||||
_pyAudio_Stream *streamObject;
|
_pyAudio_Stream *streamObject;
|
||||||
PaStream *stream;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -2203,8 +2233,12 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = streamObject->stream;
|
// clang-format off
|
||||||
frames = Pa_GetStreamReadAvailable(stream);
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
frames = Pa_GetStreamReadAvailable(streamObject->stream);
|
||||||
|
Py_END_ALLOW_THREADS
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
return PyLong_FromLong(frames);
|
return PyLong_FromLong(frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ Overview
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = "Hubert Pham"
|
__author__ = "Hubert Pham"
|
||||||
__version__ = "0.2.9"
|
__version__ = "0.2.11"
|
||||||
__docformat__ = "restructuredtext en"
|
__docformat__ = "restructuredtext en"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
@ -471,7 +471,7 @@ class Stream:
|
||||||
|
|
||||||
def get_output_latency(self):
|
def get_output_latency(self):
|
||||||
"""
|
"""
|
||||||
Return the input latency.
|
Return the output latency.
|
||||||
|
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
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')
|
|
@ -0,0 +1,642 @@
|
||||||
|
# -*- 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)
|
Loading…
Reference in New Issue