Compare commits
24 Commits
master
...
_debian_te
Author | SHA1 | Date |
---|---|---|
Hubert Pham | a9a4ec1ae5 | |
Hubert Pham | 91c9045b2d | |
Hubert Pham | 5c8039c992 | |
Hubert Pham | 44f3718dba | |
Hubert Pham | 7d82845733 | |
Hubert Pham | ebf5b24939 | |
Felipe Sateler | 5eb8166341 | |
Hubert Pham | 359cf0310a | |
Hubert Pham | 182b374699 | |
Hubert Pham | 791122f068 | |
Hubert Pham | 9b4fc102f4 | |
Hubert Pham | ba6c7f648f | |
Hubert Pham | b0a71ac85b | |
Hubert Pham | f6cdfc6a41 | |
Hubert Pham | 4d2dbe20fb | |
Hubert Pham | 0212839006 | |
Hubert Pham | bf6555e7f0 | |
Hubert Pham | 9da301168c | |
Hubert Pham | b905c6efde | |
Hubert Pham | 658d6a0617 | |
Hubert Pham | 42c6ec28d7 | |
Hubert Pham | 9337013c34 | |
Hubert Pham | 1f859c6291 | |
Hubert Pham | 2067c4a45d |
22
CHANGELOG
22
CHANGELOG
|
@ -1,25 +1,3 @@
|
||||||
2017-03-18 Hubert Pham <hubert@mit.edu>
|
|
||||||
|
|
||||||
PyAudio 0.2.11
|
|
||||||
|
|
||||||
- Fix use-after-free memory issue in callback handler.
|
|
||||||
|
|
||||||
Thanks to both Blaise Potard and Matthias Schaff for their patches!
|
|
||||||
|
|
||||||
- Fix docstring for get_output_latency().
|
|
||||||
|
|
||||||
Thanks to Timothy Port for finding the issue!
|
|
||||||
|
|
||||||
2017-01-10 Hubert Pham <hubert@mit.edu>
|
|
||||||
|
|
||||||
PyAudio 0.2.10
|
|
||||||
|
|
||||||
- Release the GIL during PortAudio I/O calls to avoid potential deadlock.
|
|
||||||
|
|
||||||
Thanks to Michael Graczyk for submitting a patch!
|
|
||||||
|
|
||||||
- Add a few automated unit tests.
|
|
||||||
|
|
||||||
2015-10-18 Hubert Pham <hubert@mit.edu>
|
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 examples *.py
|
recursive-include test *.py *.c
|
||||||
recursive-include tests *.py
|
graft docs
|
||||||
graft sphinx
|
graft sphinx
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
.PHONY: docs clean build
|
.PHONY: docs clean build
|
||||||
|
|
||||||
VERSION := 0.2.11
|
VERSION := 0.2.9
|
||||||
PYTHON ?= python
|
PYTHON ?= python
|
||||||
BUILD_ARGS ?=
|
BUILD_ARGS ?=
|
||||||
SPHINX ?= sphinx-build
|
SPHINX ?= sphinx-build
|
||||||
|
@ -11,8 +11,7 @@ PYTHON_BUILD_DIR:=$(shell $(PYTHON) -c "import distutils.util; import sys; print
|
||||||
BUILD_DIR:=lib.$(PYTHON_BUILD_DIR)
|
BUILD_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 := examples/*.py
|
EXAMPLES := test/*.py
|
||||||
TESTS := tests/*.py
|
|
||||||
|
|
||||||
what:
|
what:
|
||||||
@echo "make targets:"
|
@echo "make targets:"
|
||||||
|
@ -44,5 +43,5 @@ docs: build
|
||||||
######################################################################
|
######################################################################
|
||||||
# Source Tarball
|
# Source Tarball
|
||||||
######################################################################
|
######################################################################
|
||||||
tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in
|
tarball: docs $(SRCFILES) $(EXAMPLES) MANIFEST.in
|
||||||
@$(PYTHON) setup.py sdist
|
@$(PYTHON) setup.py sdist
|
||||||
|
|
2
README
2
README
|
@ -1,5 +1,5 @@
|
||||||
======================================================================
|
======================================================================
|
||||||
PyAudio v0.2.12: Python Bindings for PortAudio.
|
PyAudio v0.2.9: Python Bindings for PortAudio.
|
||||||
======================================================================
|
======================================================================
|
||||||
|
|
||||||
See: http://people.csail.mit.edu/hubert/pyaudio/
|
See: http://people.csail.mit.edu/hubert/pyaudio/
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
python-pyaudio (0.2.9-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Fix overflow error handling logic for pa_read_stream.
|
||||||
|
* Fix IOError exception arguments.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Sun, 18 Oct 2015 19:00:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.8-1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Fix support for non-UTF8 encoded device names.
|
||||||
|
* Fix deadlock on some platforms when calling pa.stop_stream.
|
||||||
|
* Fix debian packaging to avoid including unnecessary files.
|
||||||
|
Closes: #743660.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Mon, 12 Apr 2014 19:00:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.7-2) unstable; urgency=low
|
||||||
|
|
||||||
|
* Upload to unstable
|
||||||
|
* Bump standards version
|
||||||
|
* Link jquery and underscore to the libjs- packages
|
||||||
|
|
||||||
|
-- Felipe Sateler <fsateler@debian.org> Sat, 19 Oct 2013 17:35:39 -0300
|
||||||
|
|
||||||
|
python-pyaudio (0.2.7-1) experimental; urgency=low
|
||||||
|
|
||||||
|
* Add support for callables in non-blocking mode.
|
||||||
|
* Change documentation generator to Sphinx.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Sat, 20 Oct 2012 20:00:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.6-1) experimental; urgency=low
|
||||||
|
|
||||||
|
* New upstream release.
|
||||||
|
* Add support for Python 3.
|
||||||
|
* Split documentation into python-pyaudio-doc package.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Sat, 02 Sep 2012 21:00:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.5-1) UNRELEASED; urgency=low
|
||||||
|
|
||||||
|
* Add support for callback (non-blocking) operation.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Sat, 02 Sep 2012 20:00:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.4-2) unstable; urgency=low
|
||||||
|
|
||||||
|
* Add python-docutils to Build-Depends.
|
||||||
|
* Generate documentation against built portaudio module.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Tue, 02 Nov 2010 23:16:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.4-1) UNRELEASED; urgency=low
|
||||||
|
|
||||||
|
[ Felipe Sateler ]
|
||||||
|
* Bump standards version (no changes needed)
|
||||||
|
* Introduce a long description
|
||||||
|
* Use --intall-layout=deb to comply with python policy
|
||||||
|
* Use ${shlib:Depends} and ${misc:Depends}
|
||||||
|
* Move to python section
|
||||||
|
* Add dummy binary-indep target
|
||||||
|
* Add call to dh_compress
|
||||||
|
* Add homepage field
|
||||||
|
|
||||||
|
[ Hubert Pham ]
|
||||||
|
* Updated directory structure and packaging.
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Wed, 18 Aug 2010 15:23:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.3) unstable; urgency=low
|
||||||
|
|
||||||
|
* Release the GIL during blocking PortAudio I/O calls.
|
||||||
|
|
||||||
|
* Fixed Python argument parsing to use a long for PaSampleFormat.
|
||||||
|
|
||||||
|
* pyaudio.PyAudio.is_format_supported() now throws a ValueError
|
||||||
|
exception if the specified format is not supported for any reason
|
||||||
|
(or returns True if the format is supported).
|
||||||
|
|
||||||
|
-- Hubert Pham <hubert@mit.edu> Thu, 30 Oct 2008 17:00:00 -0500
|
||||||
|
|
||||||
|
python-pyaudio (0.2.0) unstable; urgency=low
|
||||||
|
|
||||||
|
* Initial version
|
||||||
|
|
||||||
|
-- Justin Mazzola Paluska <jmp@mit.edu> Fri, 08 Feb 2008 13:47:27 -0500
|
|
@ -0,0 +1 @@
|
||||||
|
9
|
|
@ -0,0 +1,42 @@
|
||||||
|
Source: python-pyaudio
|
||||||
|
Section: python
|
||||||
|
Priority: optional
|
||||||
|
Standards-Version: 3.9.5
|
||||||
|
Build-Depends: python-all-dev, python3-all-dev, debhelper(>= 9), portaudio19-dev, python-sphinx, python-docutils, dh-linktree, libjs-jquery, libjs-underscore
|
||||||
|
Maintainer: Hubert Pham <hubert@mit.edu>
|
||||||
|
Uploaders: Felipe Sateler <fsateler@debian.org>, Justin Mazzola Paluska <jmp@mit.edu>
|
||||||
|
Homepage: http://people.csail.mit.edu/hubert/pyaudio/
|
||||||
|
Vcs-Git: http://people.csail.mit.edu/hubert/git/pyaudio.git
|
||||||
|
Vcs-Browser: http://people.csail.mit.edu/hubert/git/pyaudio.git
|
||||||
|
|
||||||
|
Package: python-pyaudio
|
||||||
|
Depends: ${python:Depends}, ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Provides: ${python:Provides}
|
||||||
|
Suggests: python-pyaudio-doc (>= ${source:Upstream-Version})
|
||||||
|
Architecture: any
|
||||||
|
Description: Python bindings for PortAudio v19
|
||||||
|
PyAudio provides Python bindings for PortAudio v19, the
|
||||||
|
cross-platform audio I/O library. PyAudio makes it easy to use Python
|
||||||
|
to play and record audio via pythonic wrappers around the PortAudio
|
||||||
|
API. This package is for Python2.
|
||||||
|
|
||||||
|
Package: python3-pyaudio
|
||||||
|
Depends: ${python3:Depends}, ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Provides: ${python3:Provides}
|
||||||
|
Suggests: python-pyaudio-doc (>= ${source:Upstream-Version})
|
||||||
|
Architecture: any
|
||||||
|
Description: Python3 bindings for PortAudio v19
|
||||||
|
PyAudio provides Python bindings for PortAudio v19, the
|
||||||
|
cross-platform audio I/O library. PyAudio makes it easy to use Python
|
||||||
|
to play and record audio via pythonic wrappers around the PortAudio
|
||||||
|
API. This package is for Python3.
|
||||||
|
|
||||||
|
Package: python-pyaudio-doc
|
||||||
|
Section: doc
|
||||||
|
Depends: ${misc:Depends}
|
||||||
|
Architecture: all
|
||||||
|
Description: Documentation for Python bindings for PortAudio v19
|
||||||
|
Documentation for PyAudio, which provides Python bindings for
|
||||||
|
PortAudio v19, the cross-platform audio I/O library. PyAudio makes it
|
||||||
|
easy to use Python to play and record audio via pythonic wrappers
|
||||||
|
around the PortAudio API.
|
|
@ -0,0 +1,37 @@
|
||||||
|
PyAudio is distributed under the MIT License. That is to say,
|
||||||
|
|
||||||
|
Copyright (c) 2006-2012 Hubert Pham
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
The upstream PyAudio source is available at:
|
||||||
|
|
||||||
|
git: http://people.csail.mit.edu/hubert/git/pyaudio.git
|
||||||
|
homepage: http://people.csail.mit.edu/hubert/pyaudio/
|
||||||
|
|
||||||
|
The original PyAudio author is: Hubert Pham <hubert@mit.edu>.
|
||||||
|
|
||||||
|
The Debian packaging of PyAudio is also distributed under the
|
||||||
|
MIT license.
|
||||||
|
|
||||||
|
Debian package maintainers include:
|
||||||
|
Hubert Pham <hubert@mit.edu>
|
||||||
|
Felipe Sateler <fsateler@debian.org>
|
||||||
|
Justin Mazzola Paluska <jmp@mit.edu>
|
|
@ -0,0 +1,6 @@
|
||||||
|
[DEFAULT]
|
||||||
|
upstream-branch = master
|
||||||
|
debian-branch = debian
|
||||||
|
|
||||||
|
upstream-tag = v%(version)s
|
||||||
|
debian-tag = debian/%(version)s
|
|
@ -0,0 +1,9 @@
|
||||||
|
Document: python-pyaudio-doc
|
||||||
|
Title: PyAudio API
|
||||||
|
Author: Hubert Pham
|
||||||
|
Abstract: This manual is the API reference for the Python PyAudio library.
|
||||||
|
Section: Programming/Python
|
||||||
|
|
||||||
|
Format: HTML
|
||||||
|
Index: /usr/share/doc/python-pyaudio-doc/docs/index.html
|
||||||
|
Files: /usr/share/doc/python-pyaudio-doc/docs/*
|
|
@ -0,0 +1,2 @@
|
||||||
|
README
|
||||||
|
docs/
|
|
@ -0,0 +1,3 @@
|
||||||
|
replace usr/share/javascript/jquery usr/share/doc/python-pyaudio-doc/docs/_static
|
||||||
|
replace usr/share/javascript/underscore usr/share/doc/python-pyaudio-doc/docs/_static
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
test/error.py
|
||||||
|
test/play_wave.py
|
||||||
|
test/play_wave_callback.py
|
||||||
|
test/record.py
|
||||||
|
test/system_info.py
|
||||||
|
test/wire_callback.py
|
||||||
|
test/wire_full.py
|
||||||
|
test/wire_half.py
|
|
@ -0,0 +1,3 @@
|
||||||
|
usr/lib/python2*/*-packages/pyaudio.py
|
||||||
|
usr/lib/python2*/*-packages/*.so
|
||||||
|
usr/lib/python2*/*-packages/*.egg-info
|
|
@ -0,0 +1,8 @@
|
||||||
|
test/error.py
|
||||||
|
test/play_wave.py
|
||||||
|
test/play_wave_callback.py
|
||||||
|
test/record.py
|
||||||
|
test/system_info.py
|
||||||
|
test/wire_callback.py
|
||||||
|
test/wire_full.py
|
||||||
|
test/wire_half.py
|
|
@ -0,0 +1,3 @@
|
||||||
|
usr/lib/python3*/*-packages/pyaudio.py
|
||||||
|
usr/lib/python3*/*-packages/*.so
|
||||||
|
usr/lib/python3*/*-packages/*.egg-info
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- mode: makefile -*-
|
||||||
|
|
||||||
|
SPACE :=
|
||||||
|
SPACE +=
|
||||||
|
|
||||||
|
PYVERS := $(shell pyversions -vs) $(shell py3versions -vs)
|
||||||
|
|
||||||
|
PYTHONPATH=$(subst $(SPACE),:,$(abspath $(wildcard build/lib*)))
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@ --with python2,python3,linktree
|
||||||
|
|
||||||
|
override_dh_auto_build: $(PYVERS:%=build-python%)
|
||||||
|
PYTHONPATH=$(PYTHONPATH) make docs
|
||||||
|
|
||||||
|
# This has to be in a separate target otherwise
|
||||||
|
# make gets confused and executes the PYTHONPATH too early
|
||||||
|
build-python%:
|
||||||
|
dh_testdir
|
||||||
|
python$* setup.py build
|
||||||
|
|
||||||
|
|
||||||
|
override_dh_auto_install: $(PYVERS:%=install-python%)
|
||||||
|
|
||||||
|
install-python%:
|
||||||
|
python$* setup.py install --install-layout=deb --root=$(CURDIR)/debian/tmp
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (quilt)
|
4
setup.py
4
setup.py
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
PyAudio v0.2.11: Python Bindings for PortAudio.
|
PyAudio v0.2.9: Python Bindings for PortAudio.
|
||||||
|
|
||||||
Copyright (c) 2006 Hubert Pham
|
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.12"
|
__version__ = "0.2.9"
|
||||||
|
|
||||||
# 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.11'
|
version = '0.2.9'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.2.11'
|
release = '0.2.9'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# 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:: ../examples/play_wave.py
|
.. literalinclude:: ../test/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:: ../examples/play_wave_callback.py
|
.. literalinclude:: ../test/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,7 +25,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#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"
|
||||||
|
@ -755,8 +754,8 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
// clang-format on
|
// clang-format on
|
||||||
PaStream *stream;
|
PaStream *stream;
|
||||||
PaStreamParameters *inputParameters;
|
PaStreamParameters *inputParameters;
|
||||||
PaStreamParameters *outputParameters;
|
PaStreamParameters *outputParameters;
|
||||||
PaStreamInfo *streamInfo;
|
PaStreamInfo *streamInfo;
|
||||||
|
@ -772,8 +771,8 @@ static void _cleanup_Stream_object(_pyAudio_Stream *streamObject) {
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
Pa_CloseStream(streamObject->stream);
|
Pa_CloseStream(streamObject->stream);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
streamObject->stream = NULL;
|
streamObject->stream = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (streamObject->streamInfo) streamObject->streamInfo = NULL;
|
if (streamObject->streamInfo) streamObject->streamInfo = NULL;
|
||||||
|
@ -976,26 +975,14 @@ static PyObject *pa_get_version_text(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *pa_initialize(PyObject *self, PyObject *args) {
|
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;
|
||||||
|
@ -1006,12 +993,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -1292,7 +1274,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;
|
||||||
Py_ssize_t output_len;
|
int output_len;
|
||||||
PyObject *py_result;
|
PyObject *py_result;
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
|
@ -1349,6 +1331,8 @@ 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,
|
||||||
|
@ -1357,7 +1341,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
@ -1374,7 +1357,6 @@ 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) {
|
||||||
|
@ -1601,8 +1583,6 @@ 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 */
|
||||||
|
@ -1618,8 +1598,6 @@ 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
|
||||||
|
@ -1766,6 +1744,7 @@ 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;
|
||||||
|
@ -1779,13 +1758,9 @@ static PyObject *pa_start_stream(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
|
||||||
err = Pa_StartStream(streamObject->stream);
|
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
if ((err != paNoError) &&
|
if (((err = Pa_StartStream(stream)) != paNoError) &&
|
||||||
(err != paStreamIsNotStopped)) {
|
(err != paStreamIsNotStopped)) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
|
@ -1808,6 +1783,7 @@ 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;
|
||||||
|
@ -1820,13 +1796,15 @@ 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(streamObject->stream);
|
err = Pa_StopStream(stream);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if ((err != paNoError) && (err != paStreamIsStopped)) {
|
if ((err != paNoError) && (err != paStreamIsStopped)) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1848,6 +1826,7 @@ 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;
|
||||||
|
@ -1860,13 +1839,15 @@ 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(streamObject->stream);
|
err = Pa_AbortStream(stream);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if ((err != paNoError) && (err != paStreamIsStopped)) {
|
if ((err != paNoError) && (err != paStreamIsStopped)) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1888,6 +1869,7 @@ 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;
|
||||||
|
@ -1901,13 +1883,9 @@ static PyObject *pa_is_stream_stopped(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
|
||||||
err = Pa_IsStreamStopped(streamObject->stream);
|
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
if (err < 0) {
|
if ((err = Pa_IsStreamStopped(stream)) < 0) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1934,6 +1912,7 @@ 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;
|
||||||
|
@ -1946,13 +1925,9 @@ static PyObject *pa_is_stream_active(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
|
||||||
err = Pa_IsStreamActive(streamObject->stream);
|
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
if (err < 0) {
|
if ((err = Pa_IsStreamActive(stream)) < 0) {
|
||||||
_cleanup_Stream_object(streamObject);
|
_cleanup_Stream_object(streamObject);
|
||||||
|
|
||||||
#ifdef VERBOSE
|
#ifdef VERBOSE
|
||||||
|
@ -1979,6 +1954,7 @@ 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;
|
||||||
|
@ -1992,13 +1968,9 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
|
||||||
time = Pa_GetStreamTime(streamObject->stream);
|
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
if (time == 0) {
|
if ((time = Pa_GetStreamTime(stream)) == 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"));
|
||||||
|
@ -2009,9 +1981,9 @@ static PyObject *pa_get_stream_time(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
|
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;
|
||||||
|
@ -2025,13 +1997,8 @@ static PyObject *pa_get_stream_cpu_load(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
return PyFloat_FromDouble(Pa_GetStreamCpuLoad(stream));
|
||||||
cpuload = Pa_GetStreamCpuLoad(streamObject->stream);
|
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
return PyFloat_FromDouble(cpuload);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*************************************************************
|
/*************************************************************
|
||||||
|
@ -2047,6 +2014,7 @@ 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",
|
||||||
|
@ -2073,13 +2041,15 @@ 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(streamObject->stream, data, total_frames);
|
err = Pa_WriteStream(stream, data, total_frames);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
if (err == paOutputUnderflowed) {
|
if (err == paOutputUnderflowed) {
|
||||||
if (should_throw_exception) {
|
if (should_throw_exception) {
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -2115,6 +2085,7 @@ 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
|
||||||
|
@ -2140,6 +2111,7 @@ 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));
|
||||||
|
@ -2159,11 +2131,11 @@ 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(streamObject->stream, sampleBlock, total_frames);
|
err = Pa_ReadStream(stream, sampleBlock, total_frames);
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
if (err == paInputOverflowed) {
|
if (err == paInputOverflowed) {
|
||||||
if (should_raise_exception) {
|
if (should_raise_exception) {
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -2194,6 +2166,7 @@ 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;
|
||||||
|
@ -2207,12 +2180,8 @@ static PyObject *pa_get_stream_write_available(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
frames = Pa_GetStreamWriteAvailable(stream);
|
||||||
frames = Pa_GetStreamWriteAvailable(streamObject->stream);
|
|
||||||
Py_END_ALLOW_THREADS
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
return PyLong_FromLong(frames);
|
return PyLong_FromLong(frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2220,6 +2189,7 @@ 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;
|
||||||
|
@ -2233,12 +2203,8 @@ static PyObject *pa_get_stream_read_available(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
stream = streamObject->stream;
|
||||||
Py_BEGIN_ALLOW_THREADS
|
frames = Pa_GetStreamReadAvailable(stream);
|
||||||
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.11"
|
__version__ = "0.2.9"
|
||||||
__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 output latency.
|
Return the input latency.
|
||||||
|
|
||||||
:rtype: float
|
:rtype: float
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,7 +12,6 @@ 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
|
||||||
|
@ -31,9 +30,7 @@ stream = p.open(format=p.get_format_from_width(WIDTH),
|
||||||
|
|
||||||
stream.start_stream()
|
stream.start_stream()
|
||||||
|
|
||||||
start = time.time()
|
while stream.is_active():
|
||||||
|
|
||||||
while stream.is_active() and (time.time() - start) < DURATION:
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
stream.stop_stream()
|
stream.stop_stream()
|
|
@ -1,120 +0,0 @@
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import pyaudio
|
|
||||||
|
|
||||||
class PyAudioErrorTests(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.p = pyaudio.PyAudio()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.p.terminate()
|
|
||||||
|
|
||||||
def test_invalid_sample_size(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.p.get_sample_size(10)
|
|
||||||
|
|
||||||
def test_invalid_width(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.p.get_format_from_width(8)
|
|
||||||
|
|
||||||
def test_invalid_device(self):
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
self.p.get_host_api_info_by_type(-1)
|
|
||||||
|
|
||||||
def test_invalid_hostapi(self):
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
self.p.get_host_api_info_by_index(-1)
|
|
||||||
|
|
||||||
def test_invalid_host_api_devinfo(self):
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
self.p.get_device_info_by_host_api_device_index(0, -1)
|
|
||||||
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
self.p.get_device_info_by_host_api_device_index(-1, 0)
|
|
||||||
|
|
||||||
def test_invalid_device_devinfo(self):
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
self.p.get_device_info_by_index(-1)
|
|
||||||
|
|
||||||
def test_error_without_stream_start(self):
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
stream = self.p.open(channels=1,
|
|
||||||
rate=44100,
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
input=True,
|
|
||||||
start=False) # not starting stream
|
|
||||||
stream.read(2)
|
|
||||||
|
|
||||||
def test_error_writing_to_readonly_stream(self):
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
stream = self.p.open(channels=1,
|
|
||||||
rate=44100,
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
input=True)
|
|
||||||
stream.write('foo')
|
|
||||||
|
|
||||||
def test_error_negative_frames(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
stream = self.p.open(channels=1,
|
|
||||||
rate=44100,
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
input=True)
|
|
||||||
stream.read(-1)
|
|
||||||
|
|
||||||
def test_invalid_attr_on_closed_stream(self):
|
|
||||||
stream = self.p.open(channels=1,
|
|
||||||
rate=44100,
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
input=True)
|
|
||||||
stream.close()
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
stream.get_input_latency()
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
stream.read(1)
|
|
||||||
|
|
||||||
def test_invalid_format_supported(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.p.is_format_supported(8000, -1, 1, pyaudio.paInt16)
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.p.is_format_supported(8000, 0, -1, pyaudio.paInt16)
|
|
||||||
|
|
||||||
def test_write_underflow_exception(self):
|
|
||||||
stream = self.p.open(channels=1,
|
|
||||||
rate=44100,
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
output=True)
|
|
||||||
time.sleep(0.5)
|
|
||||||
stream.write('\x00\x00\x00\x00', exception_on_underflow=False)
|
|
||||||
|
|
||||||
# It's difficult to invoke an underflow on ALSA, so skip.
|
|
||||||
if sys.platform in ('linux', 'linux2'):
|
|
||||||
return
|
|
||||||
|
|
||||||
with self.assertRaises(IOError) as err:
|
|
||||||
time.sleep(0.5)
|
|
||||||
stream.write('\x00\x00\x00\x00', exception_on_underflow=True)
|
|
||||||
|
|
||||||
self.assertEqual(err.exception.errno, pyaudio.paOutputUnderflowed)
|
|
||||||
self.assertEqual(err.exception.strerror, 'Output underflowed')
|
|
||||||
|
|
||||||
def test_read_overflow_exception(self):
|
|
||||||
stream = self.p.open(channels=1,
|
|
||||||
rate=44100,
|
|
||||||
format=pyaudio.paInt16,
|
|
||||||
input=True)
|
|
||||||
time.sleep(0.5)
|
|
||||||
stream.read(2, exception_on_overflow=False)
|
|
||||||
|
|
||||||
# It's difficult to invoke an underflow on ALSA, so skip.
|
|
||||||
if sys.platform in ('linux', 'linux2'):
|
|
||||||
return
|
|
||||||
|
|
||||||
with self.assertRaises(IOError) as err:
|
|
||||||
time.sleep(0.5)
|
|
||||||
stream.read(2, exception_on_overflow=True)
|
|
||||||
|
|
||||||
self.assertEqual(err.exception.errno, pyaudio.paInputOverflowed)
|
|
||||||
self.assertEqual(err.exception.strerror, 'Input overflowed')
|
|
|
@ -1,642 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Automated unit tests for testing audio playback and capture.
|
|
||||||
|
|
||||||
These tests require an OS loopback sound device that forwards audio
|
|
||||||
output, generated by PyAudio for playback, and forwards it to an input
|
|
||||||
device, which PyAudio can record and verify against a test signal.
|
|
||||||
|
|
||||||
On Mac OS X, Soundflower can create such a device.
|
|
||||||
|
|
||||||
On GNU/Linux, the snd-aloop kernel module provides a loopback ALSA
|
|
||||||
device. Use examples/system_info.py to identify the name of the loopback
|
|
||||||
device.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
|
||||||
import struct
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
import wave
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
import pyaudio
|
|
||||||
|
|
||||||
DUMP_CAPTURE=False
|
|
||||||
|
|
||||||
class PyAudioTests(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.p = pyaudio.PyAudio()
|
|
||||||
(self.loopback_input_idx,
|
|
||||||
self.loopback_output_idx) = self.get_audio_loopback()
|
|
||||||
assert (self.loopback_input_idx is None
|
|
||||||
or self.loopback_input_idx >= 0), "No loopback device found"
|
|
||||||
assert (self.loopback_output_idx is None
|
|
||||||
or self.loopback_output_idx >= 0), "No loopback device found"
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.p.terminate()
|
|
||||||
|
|
||||||
def get_audio_loopback(self):
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
return self._find_audio_loopback(
|
|
||||||
'Soundflower (2ch)', 'Soundflower (2ch)')
|
|
||||||
if sys.platform in ('linux', 'linux2'):
|
|
||||||
return self._find_audio_loopback(
|
|
||||||
'Loopback: PCM (hw:1,0)', 'Loopback: PCM (hw:1,1)')
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
# Assumes running in a VM, in which the hypervisor can
|
|
||||||
# set up a loopback device to back the "default" audio devices.
|
|
||||||
# Here, None indicates default device.
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
return -1, -1
|
|
||||||
|
|
||||||
def _find_audio_loopback(self, indev, outdev):
|
|
||||||
"""Utility to find audio loopback device."""
|
|
||||||
input_idx, output_idx = -1, -1
|
|
||||||
for device_idx in range(self.p.get_device_count()):
|
|
||||||
devinfo = self.p.get_device_info_by_index(device_idx)
|
|
||||||
if (outdev == devinfo.get('name') and
|
|
||||||
devinfo.get('maxOutputChannels', 0) > 0):
|
|
||||||
output_idx = device_idx
|
|
||||||
|
|
||||||
if (indev == devinfo.get('name') and
|
|
||||||
devinfo.get('maxInputChannels', 0) > 0):
|
|
||||||
input_idx = device_idx
|
|
||||||
|
|
||||||
if output_idx > -1 and input_idx > -1:
|
|
||||||
break
|
|
||||||
|
|
||||||
return input_idx, output_idx
|
|
||||||
|
|
||||||
def test_system_info(self):
|
|
||||||
"""Basic system info tests"""
|
|
||||||
self.assertTrue(self.p.get_host_api_count() > 0)
|
|
||||||
self.assertTrue(self.p.get_device_count() > 0)
|
|
||||||
api_info = self.p.get_host_api_info_by_index(0)
|
|
||||||
self.assertTrue(len(api_info.items()) > 0)
|
|
||||||
|
|
||||||
def test_input_output_blocking(self):
|
|
||||||
"""Test blocking-based record and playback."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
# Blocking-mode might add some initial choppiness on some
|
|
||||||
# platforms/loopback devices, so set a longer duration.
|
|
||||||
duration = 3 # seconds
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99]
|
|
||||||
test_signal = self.create_reference_signal(freqs, rate, width, duration)
|
|
||||||
audio_chunks = self.signal_to_chunks(
|
|
||||||
test_signal, frames_per_chunk, channels)
|
|
||||||
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx)
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx)
|
|
||||||
|
|
||||||
captured = []
|
|
||||||
for chunk in audio_chunks:
|
|
||||||
out_stream.write(chunk)
|
|
||||||
captured.append(in_stream.read(frames_per_chunk))
|
|
||||||
# Capture a few more frames, since there is some lag.
|
|
||||||
for i in range(8):
|
|
||||||
captured.append(in_stream.read(frames_per_chunk))
|
|
||||||
|
|
||||||
in_stream.stop_stream()
|
|
||||||
out_stream.stop_stream()
|
|
||||||
|
|
||||||
if DUMP_CAPTURE:
|
|
||||||
self.write_wav('test_blocking.wav', b''.join(captured),
|
|
||||||
width, channels, rate)
|
|
||||||
|
|
||||||
captured_signal = self.pcm16_to_numpy(b''.join(captured))
|
|
||||||
captured_left_channel = captured_signal[::2]
|
|
||||||
captured_right_channel = captured_signal[1::2]
|
|
||||||
|
|
||||||
self.assert_pcm16_spectrum_nearly_equal(
|
|
||||||
rate,
|
|
||||||
captured_left_channel,
|
|
||||||
test_signal,
|
|
||||||
len(freqs))
|
|
||||||
self.assert_pcm16_spectrum_nearly_equal(
|
|
||||||
rate,
|
|
||||||
captured_right_channel,
|
|
||||||
test_signal,
|
|
||||||
len(freqs))
|
|
||||||
|
|
||||||
def test_input_output_callback(self):
|
|
||||||
"""Test callback-based record and playback."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
duration = 1 # second
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99]
|
|
||||||
test_signal = self.create_reference_signal(freqs, rate, width, duration)
|
|
||||||
audio_chunks = self.signal_to_chunks(
|
|
||||||
test_signal, frames_per_chunk, channels)
|
|
||||||
|
|
||||||
state = {'count': 0}
|
|
||||||
def out_callback(_, frame_count, time_info, status):
|
|
||||||
if state['count'] >= len(audio_chunks):
|
|
||||||
return ('', pyaudio.paComplete)
|
|
||||||
rval = (audio_chunks[state['count']], pyaudio.paContinue)
|
|
||||||
state['count'] += 1
|
|
||||||
return rval
|
|
||||||
|
|
||||||
captured = []
|
|
||||||
def in_callback(in_data, frame_count, time_info, status):
|
|
||||||
captured.append(in_data)
|
|
||||||
return (None, pyaudio.paContinue)
|
|
||||||
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx,
|
|
||||||
stream_callback=in_callback)
|
|
||||||
|
|
||||||
in_stream.start_stream()
|
|
||||||
out_stream.start_stream()
|
|
||||||
time.sleep(duration + 1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
out_stream.stop_stream()
|
|
||||||
|
|
||||||
if DUMP_CAPTURE:
|
|
||||||
self.write_wav('test_callback.wav', b''.join(captured),
|
|
||||||
width, channels, rate)
|
|
||||||
|
|
||||||
captured_signal = self.pcm16_to_numpy(b''.join(captured))
|
|
||||||
captured_left_channel = captured_signal[::2]
|
|
||||||
captured_right_channel = captured_signal[1::2]
|
|
||||||
|
|
||||||
self.assert_pcm16_spectrum_nearly_equal(
|
|
||||||
rate,
|
|
||||||
captured_left_channel,
|
|
||||||
test_signal,
|
|
||||||
len(freqs))
|
|
||||||
self.assert_pcm16_spectrum_nearly_equal(
|
|
||||||
rate,
|
|
||||||
captured_right_channel,
|
|
||||||
test_signal,
|
|
||||||
len(freqs))
|
|
||||||
|
|
||||||
def test_device_lock_gil_order(self):
|
|
||||||
"""Ensure no deadlock between Pa_{Open,Start,Stop}Stream and GIL."""
|
|
||||||
# This test targets OSX/macOS CoreAudio, which seems to use
|
|
||||||
# audio device locks. On ALSA and Win32 MME, this problem
|
|
||||||
# doesn't seem to appear despite not releasing the GIL when
|
|
||||||
# calling into PortAudio.
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def out_callback(_, frame_count, time_info, status):
|
|
||||||
return ('', pyaudio.paComplete)
|
|
||||||
|
|
||||||
def in_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx,
|
|
||||||
stream_callback=in_callback)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
in_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Opening a stream and starting it MUST
|
|
||||||
# release the GIL before attempting to acquire the device
|
|
||||||
# lock. Otherwise, the following code will wait for the device
|
|
||||||
# lock (while holding the GIL), while the in_callback thread
|
|
||||||
# will be waiting for the GIL once time.sleep completes (while
|
|
||||||
# holding the device lock), leading to deadlock.
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
out_stream.start_stream()
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
out_stream.stop_stream()
|
|
||||||
|
|
||||||
def test_stream_state_gil(self):
|
|
||||||
"""Ensure no deadlock between Pa_IsStream{Active,Stopped} and GIL."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def out_callback(_, frame_count, time_info, status):
|
|
||||||
return ('', pyaudio.paComplete)
|
|
||||||
|
|
||||||
def in_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx,
|
|
||||||
stream_callback=in_callback)
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
in_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Checking the state of the stream MUST
|
|
||||||
# not require the device lock, but if it does, it must release the GIL
|
|
||||||
# before attempting to acquire the device
|
|
||||||
# lock. Otherwise, the following code will wait for the device
|
|
||||||
# lock (while holding the GIL), while the in_callback thread
|
|
||||||
# will be waiting for the GIL once time.sleep completes (while
|
|
||||||
# holding the device lock), leading to deadlock.
|
|
||||||
self.assertTrue(in_stream.is_active())
|
|
||||||
self.assertFalse(in_stream.is_stopped())
|
|
||||||
|
|
||||||
self.assertTrue(out_stream.is_stopped())
|
|
||||||
self.assertFalse(out_stream.is_active())
|
|
||||||
out_stream.start_stream()
|
|
||||||
self.assertFalse(out_stream.is_stopped())
|
|
||||||
self.assertTrue(out_stream.is_active())
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
out_stream.stop_stream()
|
|
||||||
|
|
||||||
def test_get_stream_time_gil(self):
|
|
||||||
"""Ensure no deadlock between PA_GetStreamTime and GIL."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def out_callback(_, frame_count, time_info, status):
|
|
||||||
return ('', pyaudio.paComplete)
|
|
||||||
|
|
||||||
def in_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx,
|
|
||||||
stream_callback=in_callback)
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
in_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Getting the stream time MUST not
|
|
||||||
# require the device lock, but if it does, it must release the
|
|
||||||
# GIL before attempting to acquire the device lock. Otherwise,
|
|
||||||
# the following code will wait for the device lock (while
|
|
||||||
# holding the GIL), while the in_callback thread will be
|
|
||||||
# waiting for the GIL once time.sleep completes (while holding
|
|
||||||
# the device lock), leading to deadlock.
|
|
||||||
self.assertGreater(in_stream.get_time(), -1)
|
|
||||||
self.assertGreater(out_stream.get_time(), 1)
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
out_stream.stop_stream()
|
|
||||||
|
|
||||||
def test_get_stream_cpuload_gil(self):
|
|
||||||
"""Ensure no deadlock between Pa_GetStreamCpuLoad and GIL."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def out_callback(_, frame_count, time_info, status):
|
|
||||||
return ('', pyaudio.paComplete)
|
|
||||||
|
|
||||||
def in_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx,
|
|
||||||
stream_callback=in_callback)
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
in_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Getting the stream cpuload MUST not
|
|
||||||
# require the device lock, but if it does, it must release the
|
|
||||||
# GIL before attempting to acquire the device lock. Otherwise,
|
|
||||||
# the following code will wait for the device lock (while
|
|
||||||
# holding the GIL), while the in_callback thread will be
|
|
||||||
# waiting for the GIL once time.sleep completes (while holding
|
|
||||||
# the device lock), leading to deadlock.
|
|
||||||
self.assertGreater(in_stream.get_cpu_load(), -1)
|
|
||||||
self.assertGreater(out_stream.get_cpu_load(), -1)
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
out_stream.stop_stream()
|
|
||||||
|
|
||||||
def test_get_stream_write_available_gil(self):
|
|
||||||
"""Ensure no deadlock between Pa_GetStreamWriteAvailable and GIL."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def in_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx,
|
|
||||||
stream_callback=in_callback)
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
in_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Getting the stream write available MUST not
|
|
||||||
# require the device lock, but if it does, it must release the
|
|
||||||
# GIL before attempting to acquire the device lock. Otherwise,
|
|
||||||
# the following code will wait for the device lock (while
|
|
||||||
# holding the GIL), while the in_callback thread will be
|
|
||||||
# waiting for the GIL once time.sleep completes (while holding
|
|
||||||
# the device lock), leading to deadlock.
|
|
||||||
self.assertGreater(out_stream.get_write_available(), -1)
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
|
|
||||||
def test_get_stream_read_available_gil(self):
|
|
||||||
"""Ensure no deadlock between Pa_GetStreamReadAvailable and GIL."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def out_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
in_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
input=True,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
input_device_index=self.loopback_input_idx)
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
out_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Getting the stream read available MUST not
|
|
||||||
# require the device lock, but if it does, it must release the
|
|
||||||
# GIL before attempting to acquire the device lock. Otherwise,
|
|
||||||
# the following code will wait for the device lock (while
|
|
||||||
# holding the GIL), while the in_callback thread will be
|
|
||||||
# waiting for the GIL once time.sleep completes (while holding
|
|
||||||
# the device lock), leading to deadlock.
|
|
||||||
self.assertGreater(in_stream.get_read_available(), -1)
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
in_stream.stop_stream()
|
|
||||||
|
|
||||||
def test_terminate_gil(self):
|
|
||||||
"""Ensure no deadlock between Pa_Terminate and GIL."""
|
|
||||||
rate = 44100 # frames per second
|
|
||||||
width = 2 # bytes per sample
|
|
||||||
channels = 2
|
|
||||||
frames_per_chunk = 1024
|
|
||||||
|
|
||||||
def out_callback(in_data, frame_count, time_info, status):
|
|
||||||
# Release the GIL for a bit
|
|
||||||
time.sleep(2)
|
|
||||||
return (None, pyaudio.paComplete)
|
|
||||||
|
|
||||||
out_stream = self.p.open(
|
|
||||||
format=self.p.get_format_from_width(width),
|
|
||||||
channels=channels,
|
|
||||||
rate=rate,
|
|
||||||
output=True,
|
|
||||||
start=False,
|
|
||||||
frames_per_buffer=frames_per_chunk,
|
|
||||||
output_device_index=self.loopback_output_idx,
|
|
||||||
stream_callback=out_callback)
|
|
||||||
# In a separate (C) thread, portaudio/driver will grab the device lock,
|
|
||||||
# then the GIL to call in_callback.
|
|
||||||
out_stream.start_stream()
|
|
||||||
# Wait a bit to let that callback thread start.
|
|
||||||
time.sleep(1)
|
|
||||||
# in_callback will eventually drop the GIL when executing
|
|
||||||
# time.sleep (while retaining the device lock), allowing the
|
|
||||||
# following code to run. Terminating PyAudio MUST not
|
|
||||||
# require the device lock, but if it does, it must release the
|
|
||||||
# GIL before attempting to acquire the device lock. Otherwise,
|
|
||||||
# the following code will wait for the device lock (while
|
|
||||||
# holding the GIL), while the in_callback thread will be
|
|
||||||
# waiting for the GIL once time.sleep completes (while holding
|
|
||||||
# the device lock), leading to deadlock.
|
|
||||||
self.p.terminate()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_reference_signal(freqs, sampling_rate, width, duration):
|
|
||||||
"""Return reference signal with several sinuoids with frequencies
|
|
||||||
specified by freqs."""
|
|
||||||
total_frames = int(sampling_rate * duration)
|
|
||||||
max_amp = float(2**(width * 8 - 1) - 1)
|
|
||||||
avg_amp = max_amp / len(freqs)
|
|
||||||
return [
|
|
||||||
int(sum(avg_amp * math.sin(2*math.pi*freq*(k/float(sampling_rate)))
|
|
||||||
for freq in freqs))
|
|
||||||
for k in range(total_frames)]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def signal_to_chunks(frame_data, frames_per_chunk, channels):
|
|
||||||
"""Given an array of values comprising the signal, return an iterable
|
|
||||||
of binary chunks, with each chunk containing frames_per_chunk
|
|
||||||
frames. Each frame represents a single value from the signal,
|
|
||||||
duplicated for each channel specified by channels.
|
|
||||||
"""
|
|
||||||
frames = [struct.pack('h', x) * channels for x in frame_data]
|
|
||||||
# Chop up frames into chunks
|
|
||||||
return [b''.join(chunk_frames) for chunk_frames in
|
|
||||||
tuple(frames[i:i+frames_per_chunk]
|
|
||||||
for i in range(0, len(frames), frames_per_chunk))]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pcm16_to_numpy(bytestring):
|
|
||||||
"""From PCM 16-bit bytes, return an equivalent numpy array of values."""
|
|
||||||
return struct.unpack('%dh' % (len(bytestring) / 2), bytestring)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def write_wav(filename, data, width, channels, rate):
|
|
||||||
"""Write PCM data to wave file."""
|
|
||||||
wf = wave.open(filename, 'wb')
|
|
||||||
wf.setnchannels(channels)
|
|
||||||
wf.setsampwidth(width)
|
|
||||||
wf.setframerate(rate)
|
|
||||||
wf.writeframes(data)
|
|
||||||
wf.close()
|
|
||||||
|
|
||||||
def assert_pcm16_spectrum_nearly_equal(self, sampling_rate, cap, ref,
|
|
||||||
num_freq_peaks_expected):
|
|
||||||
"""Compares the discrete fourier transform of a captured signal
|
|
||||||
against the reference signal and ensures that the frequency peaks
|
|
||||||
match."""
|
|
||||||
# When passing a reference signal through the loopback device,
|
|
||||||
# the captured signal may include additional noise, as well as
|
|
||||||
# time lag, so testing that the captured signal is "similar
|
|
||||||
# enough" to the reference using bit-wise equality won't work
|
|
||||||
# well. Instead, the approach here a) assumes the reference
|
|
||||||
# signal is a sum of sinusoids and b) computes the discrete
|
|
||||||
# fourier transform of the reference and captured signals, and
|
|
||||||
# ensures that the frequencies of the top
|
|
||||||
# num_freq_peaks_expected frequency peaks are close.
|
|
||||||
cap_fft = numpy.absolute(numpy.fft.rfft(cap))
|
|
||||||
ref_fft = numpy.absolute(numpy.fft.rfft(ref))
|
|
||||||
# Find the indices of the peaks:
|
|
||||||
cap_peak_indices = sorted(numpy.argpartition(
|
|
||||||
cap_fft, -num_freq_peaks_expected)[-num_freq_peaks_expected:])
|
|
||||||
ref_peak_indices = sorted(numpy.argpartition(
|
|
||||||
ref_fft, -num_freq_peaks_expected)[-num_freq_peaks_expected:])
|
|
||||||
# Ensure that the corresponding frequencies of the peaks are close:
|
|
||||||
for cap_freq_index, ref_freq_index in zip(cap_peak_indices,
|
|
||||||
ref_peak_indices):
|
|
||||||
cap_freq = cap_freq_index / float(len(cap)) * (sampling_rate / 2)
|
|
||||||
ref_freq = ref_freq_index / float(len(ref)) * (sampling_rate / 2)
|
|
||||||
diff = abs(cap_freq - ref_freq)
|
|
||||||
self.assertLess(diff, 1.0)
|
|
||||||
|
|
||||||
# As an additional test, verify that the spectrum (not just
|
|
||||||
# the peaks) of the reference and captured signal are similar
|
|
||||||
# by computing the cross-correlation of the spectra. Assuming they
|
|
||||||
# are nearly identical, the cross-correlation should contain a large
|
|
||||||
# peak when the spectra overlap and mostly 0s elsewhere. Verify that
|
|
||||||
# using a histogram of the cross-correlation:
|
|
||||||
freq_corr_hist, _ = numpy.histogram(
|
|
||||||
numpy.correlate(cap_fft, ref_fft, mode='full'),
|
|
||||||
bins=10)
|
|
||||||
self.assertLess(sum(freq_corr_hist[2:])/sum(freq_corr_hist), 1e-2)
|
|
Loading…
Reference in New Issue