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>
|
||||
|
||||
PyAudio 0.2.9
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
include src/*.c src/*.h src/*.py
|
||||
include Makefile CHANGELOG INSTALL MANIFEST.in
|
||||
recursive-include test *.py *.c
|
||||
graft docs
|
||||
recursive-include examples *.py
|
||||
recursive-include tests *.py
|
||||
graft sphinx
|
||||
|
|
7
Makefile
7
Makefile
|
@ -2,7 +2,7 @@
|
|||
|
||||
.PHONY: docs clean build
|
||||
|
||||
VERSION := 0.2.9
|
||||
VERSION := 0.2.11
|
||||
PYTHON ?= python
|
||||
BUILD_ARGS ?=
|
||||
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_STAMP:=$(BUILD_DIR)/build
|
||||
SRCFILES := src/*.c src/*.h src/*.py
|
||||
EXAMPLES := test/*.py
|
||||
EXAMPLES := examples/*.py
|
||||
TESTS := tests/*.py
|
||||
|
||||
what:
|
||||
@echo "make targets:"
|
||||
|
@ -43,5 +44,5 @@ docs: build
|
|||
######################################################################
|
||||
# Source Tarball
|
||||
######################################################################
|
||||
tarball: docs $(SRCFILES) $(EXAMPLES) MANIFEST.in
|
||||
tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in
|
||||
@$(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/
|
||||
|
|
|
@ -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
|
||||
CHANNELS = 2
|
||||
RATE = 44100
|
||||
DURATION = 5
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
CHANNELS = 1
|
||||
|
@ -30,7 +31,9 @@ stream = p.open(format=p.get_format_from_width(WIDTH),
|
|||
|
||||
stream.start_stream()
|
||||
|
||||
while stream.is_active():
|
||||
start = time.time()
|
||||
|
||||
while stream.is_active() and (time.time() - start) < DURATION:
|
||||
time.sleep(0.1)
|
||||
|
||||
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
|
||||
|
||||
|
@ -34,7 +34,7 @@ try:
|
|||
except ImportError:
|
||||
from distutils.core import setup, Extension
|
||||
|
||||
__version__ = "0.2.9"
|
||||
__version__ = "0.2.12"
|
||||
|
||||
# distutils will try to locate and link dynamically against portaudio.
|
||||
#
|
||||
|
|
|
@ -49,9 +49,9 @@ copyright = '2006, Hubert Pham'
|
|||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.2.9'
|
||||
version = '0.2.11'
|
||||
# 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
|
||||
# for a list of supported languages.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Example: Blocking Mode Audio I/O
|
||||
--------------------------------
|
||||
|
||||
.. literalinclude:: ../test/play_wave.py
|
||||
.. literalinclude:: ../examples/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:: ../test/play_wave_callback.py
|
||||
.. literalinclude:: ../examples/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
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include "Python.h"
|
||||
#include "portaudio.h"
|
||||
#include "_portaudiomodule.h"
|
||||
|
@ -754,8 +755,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;
|
||||
|
@ -771,8 +772,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;
|
||||
|
@ -975,14 +976,26 @@ 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;
|
||||
|
@ -993,7 +1006,12 @@ 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;
|
||||
}
|
||||
|
@ -1274,7 +1292,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;
|
||||
int output_len;
|
||||
Py_ssize_t output_len;
|
||||
PyObject *py_result;
|
||||
|
||||
if (input) {
|
||||
|
@ -1331,8 +1349,6 @@ 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,
|
||||
|
@ -1341,6 +1357,7 @@ int _stream_callback_cfunction(const void *input, void *output,
|
|||
PyErr_Print();
|
||||
|
||||
// Quit the callback loop
|
||||
Py_DECREF(py_result);
|
||||
return_val = paAbort;
|
||||
goto end;
|
||||
}
|
||||
|
@ -1357,6 +1374,7 @@ int _stream_callback_cfunction(const void *input, void *output,
|
|||
return_val = paComplete;
|
||||
}
|
||||
}
|
||||
Py_DECREF(py_result);
|
||||
|
||||
end:
|
||||
if (input) {
|
||||
|
@ -1583,6 +1601,8 @@ 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 */
|
||||
|
@ -1598,6 +1618,8 @@ 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
|
||||
|
@ -1744,7 +1766,6 @@ 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;
|
||||
|
@ -1758,9 +1779,13 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) {
|
|||
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)) {
|
||||
_cleanup_Stream_object(streamObject);
|
||||
|
||||
|
@ -1783,7 +1808,6 @@ 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;
|
||||
|
@ -1796,15 +1820,13 @@ static PyObject *pa_stop_stream(PyObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream = streamObject->stream;
|
||||
|
||||
// clang-format off
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
err = Pa_StopStream(stream);
|
||||
err = Pa_StopStream(streamObject->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
|
||||
|
@ -1826,7 +1848,6 @@ 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;
|
||||
|
@ -1839,15 +1860,13 @@ static PyObject *pa_abort_stream(PyObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream = streamObject->stream;
|
||||
|
||||
// clang-format off
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
err = Pa_AbortStream(stream);
|
||||
err = Pa_AbortStream(streamObject->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
|
||||
|
@ -1869,7 +1888,6 @@ 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;
|
||||
|
@ -1883,9 +1901,13 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) {
|
|||
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);
|
||||
|
||||
#ifdef VERBOSE
|
||||
|
@ -1912,7 +1934,6 @@ 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;
|
||||
|
@ -1925,9 +1946,13 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) {
|
|||
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);
|
||||
|
||||
#ifdef VERBOSE
|
||||
|
@ -1954,7 +1979,6 @@ 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;
|
||||
|
@ -1968,9 +1992,13 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
|
|||
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);
|
||||
PyErr_SetObject(PyExc_IOError,
|
||||
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) {
|
||||
double cpuload;
|
||||
PyObject *stream_arg;
|
||||
_pyAudio_Stream *streamObject;
|
||||
PaStream *stream;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!", &_pyAudio_StreamType, &stream_arg)) {
|
||||
return NULL;
|
||||
|
@ -1997,8 +2025,13 @@ static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream = streamObject->stream;
|
||||
return PyFloat_FromDouble(Pa_GetStreamCpuLoad(stream));
|
||||
// clang-format off
|
||||
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;
|
||||
_pyAudio_Stream *streamObject;
|
||||
PaStream *stream;
|
||||
|
||||
// clang-format off
|
||||
if (!PyArg_ParseTuple(args, "O!s#i|i",
|
||||
|
@ -2041,15 +2073,13 @@ static PyObject *pa_write_stream(PyObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream = streamObject->stream;
|
||||
|
||||
// clang-format off
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
err = Pa_WriteStream(stream, data, total_frames);
|
||||
err = Pa_WriteStream(streamObject->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;
|
||||
|
@ -2085,7 +2115,6 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
|
|||
|
||||
PyObject *stream_arg;
|
||||
_pyAudio_Stream *streamObject;
|
||||
PaStream *stream;
|
||||
PaStreamParameters *inputParameters;
|
||||
|
||||
// clang-format off
|
||||
|
@ -2111,7 +2140,6 @@ 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));
|
||||
|
@ -2131,11 +2159,11 @@ static PyObject *pa_read_stream(PyObject *self, PyObject *args) {
|
|||
|
||||
// clang-format off
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
err = Pa_ReadStream(stream, sampleBlock, total_frames);
|
||||
err = Pa_ReadStream(streamObject->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;
|
||||
|
@ -2166,7 +2194,6 @@ 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;
|
||||
|
@ -2180,8 +2207,12 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream = streamObject->stream;
|
||||
frames = Pa_GetStreamWriteAvailable(stream);
|
||||
// clang-format off
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
frames = Pa_GetStreamWriteAvailable(streamObject->stream);
|
||||
Py_END_ALLOW_THREADS
|
||||
// clang-format on
|
||||
|
||||
return PyLong_FromLong(frames);
|
||||
}
|
||||
|
||||
|
@ -2189,7 +2220,6 @@ 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;
|
||||
|
@ -2203,8 +2233,12 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
stream = streamObject->stream;
|
||||
frames = Pa_GetStreamReadAvailable(stream);
|
||||
// clang-format off
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
frames = Pa_GetStreamReadAvailable(streamObject->stream);
|
||||
Py_END_ALLOW_THREADS
|
||||
// clang-format on
|
||||
|
||||
return PyLong_FromLong(frames);
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ Overview
|
|||
"""
|
||||
|
||||
__author__ = "Hubert Pham"
|
||||
__version__ = "0.2.9"
|
||||
__version__ = "0.2.11"
|
||||
__docformat__ = "restructuredtext en"
|
||||
|
||||
import sys
|
||||
|
@ -471,7 +471,7 @@ class Stream:
|
|||
|
||||
def get_output_latency(self):
|
||||
"""
|
||||
Return the input latency.
|
||||
Return the output latency.
|
||||
|
||||
: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