Compare commits

...

84 Commits

Author SHA1 Message Date
Derek f749f2187e Bump version 2021-12-14 21:48:36 -07:00
Derek 2ee560056e Use Py_ssize_t for tuple return on stream methods 2021-12-14 21:46:11 -07:00
Hubert Pham 7090e25bcb Version bump. 2017-03-18 16:32:59 -07:00
Hubert Pham 045ebc7767 Merge branch 'docs-fix' 2017-03-18 11:23:57 -07:00
Hubert Pham 653fdfeb87 Remove generated files from source distribution. 2017-03-18 10:59:11 -07:00
Hubert Pham 281a8f32e7 Fix get_output_latency docstring. 2017-02-28 21:20:36 -08:00
Hubert Pham 39de78a74a Merge branch 'use-after-free-fix' 2017-02-26 20:00:35 -08:00
Hubert Pham 2e696d98c0 Fix use-after-free error.
Many thanks to Blaise Potard for spotting the issue and submitting a
patch!
2017-02-26 19:58:48 -08:00
Hubert Pham 833ebc0b14 Version bump. 2017-01-11 20:03:43 -08:00
Hubert Pham fe528b2050 Update references to examples and tests. 2017-01-10 20:05:59 -08:00
Hubert Pham 8285b9bc5c Merge branch 'gil-fix' 2017-01-10 18:55:03 -08:00
Hubert Pham a0f6d13628 Tests: remove dependence on scipy. 2017-01-09 22:14:21 -08:00
Hubert Pham 47a80684e1 Release the GIL before PortAudio stream calls.
This commit adds logic to release the GIL for these PortAudio routines:

Pa_IsStreamStopped
Pa_IsStreamActive
Pa_GetStreamTime
Pa_GetStreamCpuLoad
Pa_GetStreamWriteAvailable
Pa_GetStreamReadAvailable
Pa_Initialize
Pa_Terminate
2017-01-09 20:55:43 -08:00
Hubert Pham 439327bcd7 Minor fix on int type to fix compiler warnings.
Also trivial formatting fixes.
2017-01-09 20:44:09 -08:00
Hubert Pham c9bc0193b0 Add tests to detect potential sources of deadlock. 2017-01-09 20:06:04 -08:00
Hubert Pham 884cedae7f Release GIL when calling Pa_{Open,Start}Stream.
On macOS/OSX, CoreAudio may require a device-level lock for certain
operations, e.g., opening a stream or delivering audio data to a
callback. If the lock acquisition order between the device lock and the
Python GIL is inconsistent, deadlock might occur. Michael Graczyk
identified one such instance, where:

- an input stream, running in thread A, acquires the device lock,
  followed by the GIL, in order to call the user's Python input callback
  handler;
- that callback makes a call that releases the GIL (e.g., I/O)
- thread B acquires the GIL and opens a PyAudio stream, eventually
  invoking Pa_OpenStream. Note that prior to this patch, calling
  Pa_OpenStream did _not_ release the GIL.
- While holding the GIL, Pa_OpenStream attempts to acquire the device
  lock.
- Meanwhile, thread A (still holding the device lock) attempts to
  reacquire the GIL (e.g., once the I/O call is complete).

This commit:

(a) updates the implementation to release the GIL before calling
    Pa_OpenStream and Pa_StartStream. Hopefully this change will bring
    the code closer to the discipline of always acquiring the device
    lock before the GIL. Thanks Michael for the patch!

(b) adds a unit test to reproduce the problem (on CoreAudio systems).

Many thanks again to Michael Graczyk for identifying the issue and
contributing the fix!
2017-01-08 19:12:51 -08:00
Hubert Pham 50e08d41d4 Merge branch 'unittests' 2017-01-08 12:19:56 -08:00
Hubert Pham 7a61080270 Add duration for wire callback example. 2017-01-08 12:18:07 -08:00
Hubert Pham e59fa4a24e Add loopback-based tests. 2017-01-08 12:16:21 -08:00
Hubert Pham 870016f6d1 Skip overflow tests on GNU/Linux ALSA. 2017-01-08 12:16:09 -08:00
Hubert Pham bff409df19 Rename test -> examples. 2016-12-25 09:49:26 -08:00
Hubert Pham da8c238eee Add unit tests. 2016-12-25 09:49:26 -08:00
Hubert Pham dce064b428 Version bump. 2015-10-18 15:38:54 -07:00
Hubert Pham ab5e5b775e Update setup.py and cleanup. 2015-10-18 15:38:54 -07:00
Hubert Pham 5a4da7d870 Update INSTALL instructions. 2015-10-18 15:38:22 -07:00
Hubert Pham 0e5eb7cb9d Use setuptools when possible. 2015-10-18 04:00:13 -07:00
Hubert Pham 3e6adb4588 Update source code style for consistency. 2015-10-18 03:59:36 -07:00
Hubert Pham d786cc59a2 Update examples for Python 3 compatibility. 2015-10-18 03:49:48 -07:00
Hubert Pham ebea06b12c On C module import error, raise the exception rather than sys.exit. 2015-10-18 03:49:48 -07:00
Hubert Pham 1783aaf9bc Fix IOError arguments.
Previously IOErrors raised had strerror and errno arguments swapped,
which this change fixes.

Thanks to Sami Liedes for the report!
2015-10-18 03:49:04 -07:00
Hubert Pham 53ab5eb107 Fix overflow error handling logic for pa_read_stream.
This adds an optional parameter specifying whether an input overflow
exception should be raised (or ignored).  Previously, a code comment
conflicted with actual implementation, by suggesting that overflows
are ignored when in fact they are always surfaced.  Furthermore,
detecting an overflow condition was flawed.  This change allows
the user to decide whether exceptions are thrown, for API parity with
pa_write_stream.

Thanks to Tony Jacobson for the report and patch!
2015-09-21 23:26:28 -07:00
Hubert Pham fc7bd1d2b0 Update version. 2014-02-16 16:04:29 -08:00
Hubert Pham 7cefbc5718 setup.py: fix GNU/Linux static linking configuration. 2014-02-16 15:50:05 -08:00
Hubert Pham 7ec435d961 Fix deadlock when calling Pa_StopStream in callback mode.
Deadlock due to callback thread waiting for the GIL while the
GIL is still held by thread calling Pa_StopStream.  The fix is
to release the GIL before calling Pa_StopStream.

Much gratitude to Jason Roehm for the patch!
2014-02-16 15:49:38 -08:00
Hubert Pham f1abe26c96 Return raw bytes of audio device name to allow user to manage encoding. 2014-02-15 22:09:50 -08:00
Christoph Gohlke b9b1cb5a71 Conform to C89/ANSI standard to compile on MSVC.
Thanks to Christoph Gohlke for the patch!
2012-11-14 21:54:36 -05:00
Hubert Pham a0bdcab7b3 Update version. 2012-10-20 21:39:47 -04:00
Hubert Pham 9459d994b1 Play Wave test: stop stream. 2012-10-20 21:38:37 -04:00
Hubert Pham ed830f7b5b Merge branch 'sphinx-doc' 2012-10-20 21:36:43 -04:00
Hubert Pham 949d4b7271 Update MANIFEST.in to include sphinx directory. 2012-10-20 21:35:19 -04:00
Hubert Pham 930a5cd04b Conditionalize doc generation for Mac OS X specific classes. 2012-10-20 21:35:19 -04:00
Hubert Pham 2953234b0b Style: better conformance to PEP8. 2012-10-20 21:35:19 -04:00
Hubert Pham 806d8623e9 Docstring cleanup. 2012-10-20 21:35:19 -04:00
Hubert Pham 72a9bd1dd2 Refactor examples into a separate file. 2012-10-20 21:35:19 -04:00
Hubert Pham 9136083440 Add changes to pyaudio for sphinx. 2012-10-20 21:35:19 -04:00
Hubert Pham 1cb6b8c1f6 Makefile: update docs target for sphinx and necessary dependencies. 2012-10-20 21:35:15 -04:00
Hubert Pham d7bad725ed Sphinx: switch to nature theme. 2012-10-20 20:16:53 -04:00
Hubert Pham 39c41c8cae Add sphinx configuration. 2012-10-19 17:03:43 -04:00
Hubert Pham 679c1c2ead Updated example files for sphinx. 2012-10-19 17:03:42 -04:00
Hubert Pham 34e54db968 Merge branch 'callable' 2012-10-19 17:03:24 -04:00
Hubert Pham 04f2db1222 Support callables in stream_callback.
Thanks to John Luebs and Bastian Bechtold for patch.
2012-10-19 16:41:08 -04:00
Hubert Pham d35ff7a829 Update version number and copyright. 2012-09-01 22:59:33 -04:00
Hubert Pham f3f8c5e2cb Update release notes. 2012-09-01 17:04:09 -04:00
Hubert Pham b6736b5375 Merge branch 'misc-fixes' 2012-09-01 17:03:59 -04:00
Hubert Pham 27424b6045 Cosmetic cleanup. 2012-09-01 14:49:49 -04:00
Hubert Pham bcbfcd8ab5 PyAudio: in terminate, copy list of streams before iterating.
PyAudio.terminate iterates over a list of streams and closes them.
This change makes a copy of the list to prevent a RuntimeError, as
closing a stream has a side-effect of modifying the list of streams
(mid-iteration).
2012-09-01 14:08:47 -04:00
Hubert Pham 73cc135bbc MacOSX channel maps: clear out pointer to avoid free() crash. 2012-09-01 14:08:47 -04:00
Hubert Pham b087335fdf Merge branch 'py2to3' 2012-09-01 14:08:15 -04:00
Hubert Pham 5a547e0cd1 Cleanup: remove low-level module tests, as they are not useful. 2012-09-01 14:07:06 -04:00
Hubert Pham 801b8a21f8 Setup.py: allow manual override of Mac sysroot to build for Python 3. 2012-09-01 14:07:06 -04:00
Hubert Pham a271c17c54 2to3: support ints and longs in Python 2. 2012-09-01 14:07:06 -04:00
Hubert Pham ab339f8352 2to3: fix print statements. 2012-09-01 14:07:06 -04:00
Hubert Pham 1615e4ce80 2to3: remove module state to simplify initialization. 2012-09-01 14:07:06 -04:00
Hubert Pham 1125da6f99 Py2to3: initial patch. 2012-09-01 14:07:06 -04:00
Hubert Pham ba10e04d1e Merge branch 'nonblocking' 2012-09-01 14:05:45 -04:00
Hubert Pham 9de8018304 Non-blocking: combine timing info for callback as suggested by Bastian. 2012-09-01 13:55:39 -04:00
Hubert Pham 1bc6abb9b6 Cosmetic cleanup. 2012-09-01 13:55:39 -04:00
Hubert Pham 4036322c68 Non-blocking: update epydoc documentation. 2012-09-01 13:55:39 -04:00
Hubert Pham 4287326f36 Non-blocking: support read-only streams. 2012-09-01 13:55:39 -04:00
Hubert Pham 51eafbef69 Non-blocking: fix referencing counting memory leak. 2012-09-01 13:55:39 -04:00
Hubert Pham 3d3c57ffbe Non-blocking: increment reference to callback function object. 2012-09-01 13:55:39 -04:00
Hubert Pham 9a99f93bb0 Error handling: show stacktrace and set asyc exception on callback error. 2012-09-01 13:55:39 -04:00
Hubert Pham eedfb3dd8f Non-blocking: use a C struct for callback context. 2012-09-01 13:55:39 -04:00
Hubert Pham ca85fd0d5f Non-blocking: add wire and playback test. 2012-09-01 13:55:39 -04:00
Hubert Pham 2a8d011698 Non-blocking: add more logging. 2012-09-01 13:55:39 -04:00
Hubert Pham 6f9efe0ba3 Non-blocking: add status to callback. 2012-09-01 13:55:39 -04:00
Hubert Pham 742b3b3781 Non-blocking: refactor C callback error handling. 2012-09-01 13:55:39 -04:00
Hubert Pham ab8c3259fa Non-blocking: return bytes (for captured audio) instead of bytearray. 2012-09-01 13:55:39 -04:00
Hubert Pham b6d5593a2b Cosmetic changes. 2012-09-01 13:55:39 -04:00
Hubert Pham 1fb9e8db6c Fix warnings. 2012-09-01 13:55:38 -04:00
Hubert Pham 3f5bc25c72 Non-blocking: call PyEval_InitThreads to prevent segfault. 2012-09-01 13:55:38 -04:00
Hubert Pham 3f55f344a0 Non-blocking: enforce upper bound of bytes copied to prevent buffer overflow. 2012-09-01 13:55:38 -04:00
Hubert Pham 3b8fdb72e7 Non-blocking: decrement reference counts for temporary values. 2012-09-01 13:55:38 -04:00
Hubert Pham ed2bbdfc9a Non-blocking: initial contribution from Bastian Bechtold.
Thanks Bastian!
2012-09-01 13:54:56 -04:00
34 changed files with 3248 additions and 2624 deletions

105
CHANGELOG
View File

@ -1,3 +1,107 @@
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
- Fix overflow error handling logic for pa_read_stream.
Stream.read takes an additional parameter that specifies whether
an exception is raised on audio buffer overflow, for parity with
Stream.write. Includes relevant bug fixes in the C module logic.
Thanks to Tony Jacobson for submitting a patch!
- Fix IOError arguments.
IOError exceptions previously had values in the strerror and errno fields
swapped, which is now corrected.
Thanks to Sami Liedes for the report!
- Miscellaneous updates.
Python library surfaces issues with importing low-level C module.
Code formatting update.
Updates to examples for Python 3 compatibility.
2014-02-16 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.8
- Device names: support non-UTF8 encoded device names.
get_device_info_by_index() now attempts to decode the device name
using a few known encodings (defaults to UTF-8 and CP1252). If
those fail, PyAudio passes the raw bytes for the device name.
Previously, PyAudio assumed a UTF-8 encoding, which is not always
true.
- Callback-mode: fix deadlock on some platforms when calling
pa.stop_stream.
Thanks to Jason Roehm for this patch!
2012-10-20 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.7
- Callback-mode: support callables.
Thanks to John Luebs and Bastian Bechtold for this patch.
- Update documentation to use Sphinx.
Thanks again to Bastian Bechtold for his incredible contribution!
2012-09-01 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.6
- Added support for Python 3. As of this update, PyAudio is
compatible with Python 2.6, Python 2.7, and Python 3.2.
Many thanks to Bastian Bechtold and Bob Jamison for their patches!
- Fixed a bug in which a list could be modified during iteration.
Many thanks to Danilo J. S. Bellini for reporting this error!
- Fixed a memory bug involving Mac OS X channel maps.
2012-09-01 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.5
- Added support for callback (non-blocking) operation.
Many thanks to Bastian Bechtold for his initial contribution and
his generous help towards releasing this feature. Callback mode
would not have happened without Bastian's help!
2010-08-12 Hubert Pham <hubert@mit.edu>
PyAudio 0.2.4
@ -39,4 +143,3 @@
2008-02-12 Justin Mazzola Paluska <jmp@mit.edu>
- Initial version of debian packaging.

96
INSTALL
View File

@ -8,81 +8,83 @@ platforms:
* General UNIX Guide: (GNU/Linux, Mac OS X, Cygwin)
* Microsoft Windows (native)
Generally speaking, you must first install the PortAudio v19 library
before building PyAudio.
Generally speaking, installation involves building the PortAudio v19
library and then building PyAudio.
----------------------------------------------------------------------
General UNIX Guide (GNU/Linux, Mac OS X, Cygwin)
----------------------------------------------------------------------
1. Build and install PortAudio, i.e.:
1. Use a package manager to install PortAudio v19.
To build PortAudio from source instead, extract the source and run:
% ./configure
% make
% make install # you may need to be root
(Or better yet, use your package manager to install PortAudio v19)
2. Extract PyAudio; to build and install, run:
2. Extract PyAudio. To build and install, run:
% python setup.py install
----------------------------------------------------------------------
Microsoft Windows
----------------------------------------------------------------------
If you are targeting native Win32 Python, you will need either
Microsoft Visual Studio or MinGW (via Cygwin). Here are compilation
hints for using MinGW under the Cygwin build environment.
Targeting native Win32 Python will require either Microsoft Visual
Studio or MinGW (via Cygwin). Here are compilation hints for using
MinGW under the Cygwin build environment.
Note: I've only tested this under Cygwin's build environment. Your
mileage may vary in other environments (i.e., compiling PortAudio with
MinGW's compiler and environment).
MinGW's compiler).
(If you have instructions for building PyAudio using Visual Studio,
I'd love to hear about it.)
1. Install cygwin's gcc and mingw packages.
1. Download PortAudio to ./portaudio-v19 in this directory
and build. When running configure, be sure to use ``-mno-cygwin``
(under cygwin) to generate native Win32 binaries:
2. Download PortAudio and build. When running configure, be sure to
specify the MinGW compiler (via a CC environment variable) to
generate native Win32 binaries:
% cd ./portaudio-v19
% CFLAGS="-mno-cygwin" LDFLAGS="-mno-cygwin" ./configure
% make
% cd ..
% CC=i686-w64-mingw32-gcc ./configure --enable-static --with-pic
% make
2. To build PyAudio, run (from this directory):
3. Before building PyAudio, apply a few necessary modifications:
% python setup.py build --static-link -cmingw32
a. Python distutils calls ``gcc'' to build the C extension, so
temporarily move your MinGW compiler to /usr/bin/gcc.
b. Modify Python's Lib/distutils/cygwincompiler.py so that
mscvr900.dll is not included in the build. See:
http://bugs.python.org/issue16472.
Both Python 2.7 and Python 3+ require similar modification.
c. For some versions of Python (e.g., Python 2.7 32-bit), it is
necessary to further modify Python's
Lib/distutils/cygwincompiler.py and remove references to
-cmingw32, a flag which is no longer supported.
See http://hg.python.org/cpython/rev/6b89176f1be5/.
d. For some versions of 64-bit Python 3 (e.g., Python 3.2, 3.3, 3.4),
it is necessary to generate .a archive of the Python DLL.
See https://bugs.python.org/issue20785. Example for Python 3.4:
% cd /path/to/Python34-x64/libs/
% gendef /path/to/Windows/System32/python34.dll
% dlltool --as-flags=--64 -m i386:x64-64 -k --output-lib libpython34.a \
--input-def python34.def
4. To build PyAudio, run:
% PORTAUDIO_PATH=/path/to/portaudio_tree /path/to/win/python \
setup.py build --static-link -cmingw32
Be sure to invoke the native Win32 python rather than cygwin's
python. The --static-link option statically links in the PortAudio
library to the PyAudio module, which is probably the best way to go
on Windows.
library to the PyAudio module.
From: http://boodebr.org/main/python/build-windows-extensions
5. To install PyAudio:
Update: 2008-09-10
% python setup.py install --skip-build
Recent versions of Cygwin binutils have version numbers that are
breaking the version number parsing, resulting in errors like:
ValueError: invalid version number '2.18.50.20080625'
To fix this, edit distutils/version.py. At line 100, replace:
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
re.VERBOSE)
with
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? (\. (\d+))?$',
re.VERBOSE)
3. To install PyAudio:
% python setup.py install --skip-build
The --skip-build option prevents Python from searching your system
for Visual Studio and the .NET framework.
Or create a Python wheel and install using pip.

View File

@ -1,4 +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

View File

@ -1,19 +1,24 @@
# This is the PyAudio distribution makefile.
.PHONY: docs clean
.PHONY: docs clean build
EPYDOC ?= epydoc
VERSION := 0.2.4
VERSION := 0.2.11
PYTHON ?= python
BUILD_ARGS ?=
SPHINX ?= sphinx-build
DOCS_OUTPUT=docs/
DOC_NAME := PyAudio-$(VERSION)
DOC_URL=http://people.csail.mit.edu/hubert/pyaudio/
PYTHON_BUILD_DIR:=$(shell $(PYTHON) -c "import distutils.util; import sys; print(distutils.util.get_platform() + '-' + sys.version[0:3])")
BUILD_DIR:=lib.$(PYTHON_BUILD_DIR)
BUILD_STAMP:=$(BUILD_DIR)/build
SRCFILES := src/*.c src/*.h src/*.py
EXAMPLES := examples/*.py
TESTS := tests/*.py
what:
@echo "make targets:"
@echo
@echo " tarball : build source tarball"
@echo " docs : generate documentation (requires epydoc)"
@echo " docs : generate documentation (requires sphinx)"
@echo " clean : remove build files"
@echo
@echo "To build pyaudio, run:"
@ -27,13 +32,17 @@ clean:
# Documentation
######################################################################
docs:
@cd src; \
$(EPYDOC) -v -o ../$(DOCS_OUTPUT) --name $(DOC_NAME) --url $(DOC_URL) \
--no-private pyaudio.py
build: build/$(BUILD_STAMP)
build/$(BUILD_STAMP): $(SRCFILES)
$(PYTHON) setup.py build $(BUILD_ARGS)
touch $@
docs: build
PYTHONPATH=build/$(BUILD_DIR) $(SPHINX) -b html sphinx/ $(DOCS_OUTPUT)
######################################################################
# Source Tarball
######################################################################
tarball: docs $(SRCFILES) MANIFEST.in
@python setup.py sdist
tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in
@$(PYTHON) setup.py sdist

10
README
View File

@ -1,23 +1,20 @@
======================================================================
PyAudio v0.2.4: Python Bindings for PortAudio.
PyAudio v0.2.12: Python Bindings for PortAudio.
======================================================================
See: http://people.csail.mit.edu/hubert/pyaudio/
PyAudio provides Python bindings for PortAudio, the cross-platform
PyAudio provides Python bindings for PortAudio v19, the cross-platform
audio I/O library. Using PyAudio, you can easily use Python to play
and record audio on a variety of platforms.
PyAudio is designed to work with the PortAudio v19 API 2.0. Note that
PyAudio currently only supports blocking-mode audio I/O.
See INSTALL for compilation hints.
======================================================================
PyAudio : Python Bindings for PortAudio.
Copyright (c) 2006-2010 Hubert Pham
Copyright (c) 2006 Hubert Pham
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@ -39,4 +36,3 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
======================================================================

View File

@ -1,9 +1,7 @@
"""
PyAudio Example:
Test for a variety of error conditions. This
example demonstrates exception handling with
PyAudio. """
PyAudio Example: Test for a variety of error conditions. This example
demonstrates exception handling with PyAudio.
"""
import pyaudio
@ -12,56 +10,56 @@ p = pyaudio.PyAudio()
# get invalid sample size
try:
p.get_sample_size(10)
except ValueError, e:
assert e[1] == pyaudio.paSampleFormatNotSupported
print "OK:", e[0]
except ValueError as e:
assert e.args[1] == pyaudio.paSampleFormatNotSupported
print("OK: %s" % e.args[0])
else:
assert False, "sample size"
# get format from invalid width
try:
p.get_format_from_width(8)
except ValueError, e:
print "OK: invalid format from width"
except ValueError as e:
print("OK: invalid format from width")
else:
assert False, "invalid format"
# try to get an invalid device
try:
p.get_host_api_info_by_type(-1)
except IOError, e:
assert e[1] == pyaudio.paHostApiNotFound
print "OK:", e[0]
except IOError as e:
assert e.args[1] == pyaudio.paHostApiNotFound
print("OK: %s" % e.args[0])
else:
assert False, "invalid host type"
# try to get host api info by index
try:
p.get_host_api_info_by_index(-1)
except IOError, e:
assert e[1] == pyaudio.paInvalidHostApi
print "OK:", e[0]
except IOError as e:
assert e.args[1] == pyaudio.paInvalidHostApi
print("OK: %s" % e.args[0])
else:
assert False, "invalid host api index"
# try good host api device index
try:
p.get_device_info_by_host_api_device_index(0, -1)
except IOError, e:
assert ((e[1] == pyaudio.paInvalidDevice) or \
(e[1] == pyaudio.paInvalidHostApi))
print "OK:", e[0]
except IOError as e:
assert ((e.args[1] == pyaudio.paInvalidDevice) or \
(e.args[1] == pyaudio.paInvalidHostApi))
print("OK: %s" % e.args[0])
else:
assert False, "device info by host api device idnex"
# try bad host api and good device index
try:
p.get_device_info_by_host_api_device_index(-1, 0)
except IOError, e:
assert ((e[1] == pyaudio.paInvalidDevice) or \
(e[1] == pyaudio.paInvalidHostApi))
print "OK:", e[0]
except IOError as e:
assert ((e.args[1] == pyaudio.paInvalidDevice) or \
(e.args[1] == pyaudio.paInvalidHostApi))
print("OK: %s" % e.args[0])
else:
assert False, "device info by host api device idnex"
@ -69,9 +67,9 @@ else:
# bad device index
try:
p.get_device_info_by_index(-1)
except IOError, e:
assert e[1] == pyaudio.paInvalidDevice
print "OK:", e[0]
except IOError as e:
assert e.args[1] == pyaudio.paInvalidDevice
print("OK: %s" % e.args[0])
else:
assert False, "bad device index"
@ -87,9 +85,9 @@ stream = p.open(channels = 1,
try:
data = stream.read(2)
except IOError, e:
print "OK:", e[0]
assert e[1] == pyaudio.paStreamIsStopped, e[1]
except IOError as e:
print("OK: %s" % e.args[0])
assert e.args[1] == pyaudio.paStreamIsStopped, e.args[1]
else:
assert False, "Should have caused exception"
@ -98,29 +96,29 @@ stream.start_stream()
# try to write to the input stream
try:
stream.write('foobar')
except IOError, e:
assert e[1] == pyaudio.paCanNotWriteToAnInputOnlyStream
print "OK:", e[0]
except IOError as e:
assert e.args[1] == pyaudio.paCanNotWriteToAnInputOnlyStream
print("OK: %s" % e.args[0])
else:
assert False, "write to input stream"
# read some negative data
try:
data = stream.read(-1)
except ValueError, e:
print "OK: Invalid frames"
except ValueError as e:
print("OK: Invalid frames")
else:
assert False, "invalid frames"
# read some real data
try:
data = stream.read(2)
except IOError, e:
except IOError as e:
# some slower machines might overflow
assert e[1] == pyaudio.paInputOverflowed, e
print "OK:", e[0]
assert e.args[1] == pyaudio.paInputOverflowed, e
print("OK: %s" % e.args[0])
else:
print "OK: got %d bytes of data" % len(data)
print("OK: got %d bytes of data" % len(data))
# close the stream; nothing should work with
# this stream afterwards
@ -130,36 +128,36 @@ stream.close()
# query for properties
try:
stream.get_input_latency()
except IOError, e:
assert e[1] == pyaudio.paBadStreamPtr
print "OK:", e[0]
except IOError as e:
assert e.args[1] == pyaudio.paBadStreamPtr
print("OK: %s" % e.args[0])
else:
assert False, "closed stream"
# read some data again
try:
stream.read(10)
except IOError, e:
assert e[1] == pyaudio.paBadStreamPtr
print "OK:", e[0]
except IOError as e:
assert e.args[1] == pyaudio.paBadStreamPtr
print("OK: %s" % e.args[0])
else:
assert False, "closed stream"
# get invalid stream capabilities
try:
p.is_format_supported(8000, -1, 1, pyaudio.paInt16)
except ValueError, e:
assert e[1] == pyaudio.paInvalidDevice
print "OK:", e[0]
except ValueError as e:
assert e.args[1] == pyaudio.paInvalidDevice
print("OK: %s" % e.args[0])
else:
assert False, "invalid device"
# get invalid stream capabilities
try:
p.is_format_supported(8000, 0, -1, pyaudio.paInt16)
except ValueError, e:
assert e[1] == pyaudio.paInvalidChannelCount
print "OK:", e[0]
except ValueError as e:
assert e.args[1] == pyaudio.paInvalidChannelCount
print("OK: %s" % e.args[0])
else:
assert False, "invalid number of channels"

37
examples/play_wave.py Normal file
View File

@ -0,0 +1,37 @@
"""PyAudio Example: Play a wave file."""
import pyaudio
import wave
import sys
CHUNK = 1024
if len(sys.argv) < 2:
print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
# instantiate PyAudio (1)
p = pyaudio.PyAudio()
# open stream (2)
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
# read data
data = wf.readframes(CHUNK)
# play stream (3)
while len(data) > 0:
stream.write(data)
data = wf.readframes(CHUNK)
# stop stream (4)
stream.stop_stream()
stream.close()
# close PyAudio (5)
p.terminate()

View File

@ -0,0 +1,42 @@
"""PyAudio Example: Play a wave file (callback version)."""
import pyaudio
import wave
import time
import sys
if len(sys.argv) < 2:
print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
# instantiate PyAudio (1)
p = pyaudio.PyAudio()
# define callback (2)
def callback(in_data, frame_count, time_info, status):
data = wf.readframes(frame_count)
return (data, pyaudio.paContinue)
# open stream using callback (3)
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True,
stream_callback=callback)
# start the stream (4)
stream.start_stream()
# wait for stream to finish (5)
while stream.is_active():
time.sleep(0.1)
# stop stream (6)
stream.stop_stream()
stream.close()
wf.close()
# close PyAudio (7)
p.terminate()

View File

@ -1,4 +1,6 @@
""" PyAudio Example: Mac OS X-only: Play a wave file with channel maps. """
"""
PyAudio Example: Mac OS X-only: Play a wave file with channel maps.
"""
import pyaudio
import wave
@ -9,7 +11,7 @@ chunk = 1024
PyAudio = pyaudio.PyAudio
if len(sys.argv) < 2:
print "Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0]
print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
@ -40,30 +42,29 @@ channel_map = (1, -1)
try:
stream_info = pyaudio.PaMacCoreStreamInfo(
flags = pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, # default
channel_map = channel_map)
flags=pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, # default
channel_map=channel_map)
except AttributeError:
print "Sorry, couldn't find PaMacCoreStreamInfo. Make sure that " \
"you're running on Mac OS X."
import sys
print("Sorry, couldn't find PaMacCoreStreamInfo. Make sure that "
"you're running on Mac OS X.")
sys.exit(-1)
print "Stream Info Flags:", stream_info.get_flags()
print "Stream Info Channel Map:", stream_info.get_channel_map()
print("Stream Info Flags:", stream_info.get_flags())
print("Stream Info Channel Map:", stream_info.get_channel_map())
# open stream
stream = p.open(format =
p.get_format_from_width(wf.getsampwidth()),
channels = wf.getnchannels(),
rate = wf.getframerate(),
output = True,
output_host_api_specific_stream_info = stream_info)
stream = p.open(
format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True,
output_host_api_specific_stream_info=stream_info)
# read data
data = wf.readframes(chunk)
# play stream
while data != '':
while len(data) > 0:
stream.write(data)
data = wf.readframes(chunk)
@ -71,6 +72,3 @@ stream.stop_stream()
stream.close()
p.terminate()

47
examples/record.py Normal file
View File

@ -0,0 +1,47 @@
"""
PyAudio example: Record a few seconds of audio and save to a WAVE
file.
"""
import pyaudio
import wave
import sys
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
print("* recording")
frames = []
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
frames.append(data)
print("* done recording")
stream.stop_stream()
stream.close()
p.terminate()
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

View File

@ -16,42 +16,44 @@ p = pyaudio.PyAudio()
max_apis = p.get_host_api_count()
max_devs = p.get_device_count()
print "\nPortAudio System Info:\n======================"
print "Version: %d" % pyaudio.get_portaudio_version()
print "Version Text: %s" % pyaudio.get_portaudio_version_text()
print "Number of Host APIs: %d" % max_apis
print "Number of Devices : %d" % max_devs
print("\nPortAudio System Info:\n======================")
print("Version: %d" % pyaudio.get_portaudio_version())
print("Version Text: %s" % pyaudio.get_portaudio_version_text())
print("Number of Host APIs: %d" % max_apis)
print("Number of Devices : %d" % max_devs)
print "\nHost APIs:\n=========="
print("\nHost APIs:\n==========")
for i in range(max_apis):
apiinfo = p.get_host_api_info_by_index(i)
for k in apiinfo.items():
print "%s: %s" % k
print "--------------------------"
for k in list(apiinfo.items()):
print("%s: %s" % k)
print("--------------------------")
print "\nDevices:\n========"
print("\nDevices:\n========")
for i in range(max_devs):
devinfo = p.get_device_info_by_index(i)
# print out device parameters
for k in devinfo.items():
for k in list(devinfo.items()):
name, value = k
# if host API, then get friendly name
if name == 'hostApi':
value = str(value) + \
" (%s)" % p.get_host_api_info_by_index(k[1])['name']
print "\t%s: %s" % (name, value)
# Crashing? See http://stackoverflow.com/a/5146914
print("\t%s: %s" % (name, value))
# print out supported format rates
input_supported_rates = []
output_supported_rates = []
full_duplex_rates = []
for f in standard_sample_rates:
if devinfo['maxInputChannels'] > 0:
@ -92,42 +94,41 @@ for i in range(max_devs):
pass
if len(input_supported_rates):
print "\tInput rates:", input_supported_rates
print("\tInput rates: %s" % input_supported_rates)
if len(output_supported_rates):
print "\tOutput rates:", output_supported_rates
print("\tOutput rates: %s" % output_supported_rates)
if len(full_duplex_rates):
print "\tFull duplex: ", full_duplex_rates
print "\t--------------------------------"
print("\tFull duplex: %s" % full_duplex_rates)
print "\nDefault Devices:\n================"
print("\t--------------------------------")
print("\nDefault Devices:\n================")
try:
def_index = p.get_default_input_device_info()['index']
print "Default Input Device :", def_index
print("Default Input Device : %s" % def_index)
devinfo = p.get_device_info_by_index(def_index)
for k in devinfo.items():
for k in list(devinfo.items()):
name, value = k
if name == 'hostApi':
value = str(value) + \
" (%s)" % p.get_host_api_info_by_index(k[1])['name']
print "\t%s: %s" % (name, value)
print "\t--------------------------------"
except IOError, e:
print "No Input devices: %s" % e[0]
print("\t%s: %s" % (name, value))
print("\t--------------------------------")
except IOError as e:
print("No Input devices: %s" % e[0])
try:
def_index = p.get_default_output_device_info()['index']
print "Default Output Device:", def_index
print("Default Output Device: %s" % def_index)
devinfo = p.get_device_info_by_index(def_index)
for k in devinfo.items():
for k in list(devinfo.items()):
name, value = k
if name == 'hostApi':
value = str(value) + \
" (%s)" % p.get_host_api_info_by_index(k[1])['name']
print "\t%s: %s" % (name, value)
print "\t--------------------------------"
except IOError, e:
print "No Output devices: %s" % e[0]
print("\t%s: %s" % (name, value))
print("\t--------------------------------")
except IOError as e:
print("No Output devices: %s" % e[0])
p.terminate()

42
examples/wire_callback.py Normal file
View File

@ -0,0 +1,42 @@
"""
PyAudio Example: Make a wire between input and output (i.e., record a
few samples and play them back immediately).
This is the callback (non-blocking) version.
"""
import pyaudio
import time
import sys
WIDTH = 2
CHANNELS = 2
RATE = 44100
DURATION = 5
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
def callback(in_data, frame_count, time_info, status):
return (in_data, pyaudio.paContinue)
stream = p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
stream_callback=callback)
stream.start_stream()
start = time.time()
while stream.is_active() and (time.time() - start) < DURATION:
time.sleep(0.1)
stream.stop_stream()
stream.close()
p.terminate()

40
examples/wire_full.py Normal file
View File

@ -0,0 +1,40 @@
"""
PyAudio Example: Make a wire between input and output (i.e., record a
few samples and play them back immediately).
This is the full duplex version.
"""
import pyaudio
import sys
CHUNK = 1024
WIDTH = 2
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
frames_per_buffer=CHUNK)
print("* recording")
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream.read(CHUNK)
stream.write(data, CHUNK)
print("* done")
stream.stop_stream()
stream.close()
p.terminate()

48
examples/wire_half.py Normal file
View File

@ -0,0 +1,48 @@
"""
PyAudio Example: Make a wire between input and output (i.e., record a
few samples and play them back immediately).
This is the half duplex version.
"""
import pyaudio
import sys
CHUNK = 1024
WIDTH = 2
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
# Open input stream using default device:
stream_input = p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK)
# Open out stream using default device:
stream_output = p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
output=True,
frames_per_buffer=CHUNK)
print("* recording")
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
data = stream_input.read(CHUNK)
stream_output.write(data, CHUNK)
print("* done")
stream_input.stop_stream()
stream_output.stop_stream()
stream_input.close()
stream_output.close()
p.terminate()

115
setup.py Normal file → Executable file
View File

@ -1,7 +1,7 @@
"""
PyAudio v0.2.4: Python Bindings for PortAudio.
PyAudio v0.2.11: Python Bindings for PortAudio.
Copyright (c) 2006-2010 Hubert Pham
Copyright (c) 2006 Hubert Pham
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@ -26,25 +26,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
from distutils.core import setup, Extension
import sys
import os
import platform
import sys
try:
from setuptools import setup, Extension
except ImportError:
from distutils.core import setup, Extension
__version__ = "0.2.4"
__version__ = "0.2.12"
# Note: distutils will try to locate and link dynamically
# against portaudio.
# distutils will try to locate and link dynamically against portaudio.
#
# You probably don't want to statically link in the PortAudio
# library unless you're building on Microsoft Windows.
# If you would rather statically link in the portaudio library (e.g.,
# typically on Microsoft Windows), run:
#
# In any case, if you would rather statically link in libportaudio,
# run:
# % python setup.py build --static-link
#
# % python setup.py build --static-link
#
# Be sure to specify the location of the libportaudio.a in
# the `extra_link_args' variable below.
# Specify the environment variable PORTAUDIO_PATH with the build tree
# of PortAudio.
STATIC_LINKING = False
@ -53,30 +53,34 @@ if "--static-link" in sys.argv:
sys.argv.remove("--static-link")
portaudio_path = os.environ.get("PORTAUDIO_PATH", "./portaudio-v19")
mac_sysroot_path = os.environ.get("SYSROOT_PATH", None)
pyaudio_module_sources = ['src/_portaudiomodule.c']
include_dirs = []
external_libraries = []
extra_compile_args = ['-fno-strict-aliasing']
extra_compile_args = []
extra_link_args = []
scripts = []
defines = []
if STATIC_LINKING:
if sys.platform == 'darwin':
defines += [('MACOSX', '1')]
if mac_sysroot_path:
extra_compile_args += ["-isysroot", mac_sysroot_path]
extra_link_args += ["-isysroot", mac_sysroot_path]
elif sys.platform == 'win32':
bits = platform.architecture()[0]
if '64' in bits:
defines.append(('MS_WIN64', '1'))
if not STATIC_LINKING:
external_libraries = ['portaudio']
extra_link_args = []
else:
include_dirs = [os.path.join(portaudio_path, 'include/')]
extra_link_args = [
os.path.join(portaudio_path, 'lib/.libs/libportaudio.a')
]
include_dirs = [os.path.join(portaudio_path, 'include/')]
else:
# dynamic linking
external_libraries = ['portaudio']
extra_link_args = []
if sys.platform == 'darwin':
defines += [('MACOSX', '1')]
if STATIC_LINKING:
# platform specific configuration
if sys.platform == 'darwin':
@ -84,45 +88,36 @@ if STATIC_LINKING:
'-framework', 'AudioToolbox',
'-framework', 'AudioUnit',
'-framework', 'Carbon']
elif sys.platform == 'cygwin':
external_libraries += ['winmm']
extra_link_args += ['-lwinmm']
elif sys.platform == 'win32':
# i.e., Win32 Python with mingw32
# run: python setup.py build -cmingw32
external_libraries += ['winmm']
extra_link_args += ['-lwinmm']
elif sys.platform == 'linux2':
external_libraries += ['rt', 'm', 'pthread']
extra_link_args += ['-lrt', '-lm', '-lpthread']
# GNU/Linux has several audio systems (backends) available; be
# sure to specify the desired ones here. Start with ALSA and
# JACK, since that's common today.
extra_link_args += ['-lasound', '-ljack']
# Since you're insisting on linking statically against
# PortAudio on GNU/Linux, be sure to link in whatever sound
# backend you used in portaudio (e.g., ALSA, JACK, etc...)
# I'll start you off with ALSA, since that's the most common
# today. If you need JACK support, add it here.
external_libraries += ['asound']
pyaudio = Extension('_portaudio',
sources = pyaudio_module_sources,
include_dirs = include_dirs,
define_macros = defines,
libraries = external_libraries,
extra_compile_args = extra_compile_args,
extra_link_args = extra_link_args)
setup (name = 'PyAudio',
version = __version__,
author = "Hubert Pham",
url = "http://people.csail.mit.edu/hubert/pyaudio/",
description = 'PortAudio Python Bindings',
long_description = __doc__.lstrip(),
scripts = scripts,
py_modules = ['pyaudio'],
package_dir = {'': 'src'},
ext_modules = [pyaudio])
setup(name='PyAudio',
version=__version__,
author="Hubert Pham",
url="http://people.csail.mit.edu/hubert/pyaudio/",
description='PortAudio Python Bindings',
long_description=__doc__.lstrip(),
scripts=scripts,
py_modules=['pyaudio'],
package_dir={'': 'src'},
ext_modules=[
Extension('_portaudio',
sources=pyaudio_module_sources,
include_dirs=include_dirs,
define_macros=defines,
libraries=external_libraries,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args)
])

252
sphinx/conf.py Normal file
View File

@ -0,0 +1,252 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# PyAudio documentation build configuration file, created by
# sphinx-quickstart on Wed Aug 29 08:37:41 2012.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'PyAudio'
copyright = '2006, Hubert Pham'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0.2.11'
# The full version, including alpha/beta/rc tags.
release = '0.2.11'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
"nosidebar": True
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'PyAudiodoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'PyAudio.tex', 'PyAudio Documentation',
'Hubert Pham', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pyaudio', 'PyAudio Documentation',
['Hubert Pham'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'PyAudio', 'PyAudio Documentation',
'Hubert Pham', 'PyAudio', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
try:
from _portaudio import paMacCoreStreamInfo
except ImportError:
pass
else:
tags.add('pamac')

50
sphinx/examples.rst Normal file
View File

@ -0,0 +1,50 @@
Example: Blocking Mode Audio I/O
--------------------------------
.. literalinclude:: ../examples/play_wave.py
To use PyAudio, first instantiate PyAudio using
:py:func:`pyaudio.PyAudio` (1), which sets up the portaudio system.
To record or play audio, open a stream on the desired device with the
desired audio parameters using :py:func:`pyaudio.PyAudio.open`
(2). This sets up a :py:class:`pyaudio.Stream` to play or record
audio.
Play audio by writing audio data to the stream using
:py:func:`pyaudio.Stream.write`, or read audio data from the stream
using :py:func:`pyaudio.Stream.read`. (3)
Note that in "blocking mode", each :py:func:`pyaudio.Stream.write` or
:py:func:`pyaudio.Stream.read` blocks until all the given/requested
frames have been played/recorded. Alternatively, to generate audio
data on the fly or immediately process recorded audio data, use the
"callback mode" outlined below.
Use :py:func:`pyaudio.Stream.stop_stream` to pause playing/recording,
and :py:func:`pyaudio.Stream.close` to terminate the stream. (4)
Finally, terminate the portaudio session using
:py:func:`pyaudio.PyAudio.terminate` (5)
Example: Callback Mode Audio I/O
--------------------------------
.. 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
(recorded) audio data available. Note that PyAudio calls the callback
function in a separate thread. The function has the following
signature ``callback(<input_data>, <frame_count>, <time_info>,
<status_flag>)`` and must return a tuple containing ``frame_count``
frames of audio data and a flag signifying whether there are more
frames to play/record.
Start processing the audio stream using
:py:func:`pyaudio.Stream.start_stream` (4), which will call the
callback function repeatedly until that function returns
:py:data:`pyaudio.paComplete`.
To keep the stream active, the main thread must not terminate, e.g.,
by sleeping (5).

52
sphinx/index.rst Normal file
View File

@ -0,0 +1,52 @@
PyAudio Documentation
=====================
.. contents::
------------
Introduction
------------
.. automodule:: pyaudio
:members:
:special-members:
:exclude-members: PyAudio, Stream, PaMacCoreStreamInfo
Details
-------
-------------
Class PyAudio
-------------
.. autoclass:: pyaudio.PyAudio
:members:
:special-members:
------------
Class Stream
------------
.. autoclass:: pyaudio.Stream
:members:
:special-members:
-----------------
Platform Specific
-----------------
.. only:: pamac
Class PaMacCoreStreamInfo
-------------------------
.. autoclass:: pyaudio.PaMacCoreStreamInfo
:members:
:special-members:
Indices and tables
==================
* :ref:`genindex`
* :ref:`search`

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
*
* PyAudio : API Header File
*
* Copyright (c) 2006-2008 Hubert Pham
* Copyright (c) 2006 Hubert Pham
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation

File diff suppressed because it is too large Load Diff

View File

@ -1,77 +0,0 @@
# An example of what NOT to do:
# Don't reuse a stream object after closing it!
import _portaudio
import wave
import sys
chunk = 1024
def get_format_from_width(width, unsigned = True):
"""
Returns a PortAudio format constant for
the specified `width`.
:param `width`:
The desired sample width in bytes (1, 2, 3, or 4)
:param `unsigned`:
For 1 byte width, specifies signed or unsigned
format.
:raises ValueError: for invalid `width`
:rtype: `PaSampleFormat`
"""
p = _portaudio
if width == 1:
if unsigned:
return p.paUInt8
else:
return p.paInt8
elif width == 2:
return p.paInt16
elif width == 3:
return p.paInt24
elif width == 4:
return p.paFloat32
else:
raise ValueError, "Invalid width: %d" % width
if len(sys.argv) < 2:
print "Usage: %s filename.wav" % sys.argv[0]
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
print "* initializing"
_portaudio.initialize()
print "* opening"
stream = _portaudio.open(format = get_format_from_width(wf.getsampwidth()),
channels = wf.getnchannels(),
rate = wf.getframerate(),
input = True,
output = True)
data = wf.readframes(chunk)
print "* starting stream"
_portaudio.start_stream(stream)
while data != '':
_portaudio.write_stream(stream, data, chunk)
data = wf.readframes(chunk)
# OK...
_portaudio.close(stream)
# Fixed -- no longer relevant. An exception will be thrown.
# -----DEPRECATED COMMENT:
# BUT! don't re-use the stream object after closing it!
# Depending on the platform, this might crash Python.
print "* CRASH ----------*"
print _portaudio.get_stream_read_available(stream)

View File

@ -1,266 +0,0 @@
/** @file patest_read_record.c
@brief Record input into an array; Save array to a file; Playback recorded
data. Implemented using the blocking API (Pa_ReadStream(), Pa_WriteStream() )
@author Phil Burk http://www.softsynth.com
@author Ross Bencina rossb@audiomulch.com
*/
/*
* $Id: patest_read_record.c 757 2004-02-13 07:48:10Z rossbencina $
*
* This program uses the PortAudio Portable Audio Library.
* For more information see: http://www.portaudio.com
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
* 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.
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version.
*
* 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.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "portaudio.h"
/* #define SAMPLE_RATE (17932) /* Test failure to open with this value. */
#define SAMPLE_RATE (44100)
#define FRAMES_PER_BUFFER (1024)
#define NUM_SECONDS (5)
#define NUM_CHANNELS (2)
/* #define DITHER_FLAG (paDitherOff) */
#define DITHER_FLAG (0) /**/
/* Select sample format. */
#if 1
#define PA_SAMPLE_TYPE paFloat32
typedef float SAMPLE;
#define SAMPLE_SILENCE (0.0f)
#define PRINTF_S_FORMAT "%.8f"
#elif 1
#define PA_SAMPLE_TYPE paInt16
typedef short SAMPLE;
#define SAMPLE_SILENCE (0)
#define PRINTF_S_FORMAT "%d"
#elif 0
#define PA_SAMPLE_TYPE paInt8
typedef char SAMPLE;
#define SAMPLE_SILENCE (0)
#define PRINTF_S_FORMAT "%d"
#else
#define PA_SAMPLE_TYPE paUInt8
typedef unsigned char SAMPLE;
#define SAMPLE_SILENCE (128)
#define PRINTF_S_FORMAT "%d"
#endif
#define FORMATID "fmt "
#define DATAID "data"
typedef struct {
char chunkID[4];
long chunkSize;
short wFormatTag;
unsigned short wChannels;
unsigned long dwSamplesPerSec;
unsigned long dwAvgBytesPerSec;
unsigned short wBlockAlign;
unsigned short wBitsPerSample;
} FormatChunk;
typedef struct {
char chunkID[4];
long chunkSize;
} DataChunkHeader;
int checkHeader(FILE *fid) {
char riff[4];
char wave[4];
fread(riff, 4, sizeof(char), fid);
/* throwaway 4 bytes */
fread(wave, 4, sizeof(char), fid);
fread(wave, 4, sizeof(char), fid);
if (!((strncmp(riff, "RIFF", 4) == 0) &&
(strncmp(wave, "WAVE", 4) == 0))) {
return -1;
}
return 0;
}
int getData(FILE *fid, char **data) {
DataChunkHeader dch;
while (strncmp(dch.chunkID, DATAID, 4) != 0) {
fread(&dch, sizeof(DataChunkHeader), 1, fid);
if (feof(fid) || ferror(fid))
return -1;
}
printf("Size of data: %d\n", dch.chunkSize);
*data = (char *) malloc ( dch.chunkSize * sizeof(char) );
fread(*data, sizeof(char), dch.chunkSize, fid);
if (feof(fid) || ferror(fid)) {
free(data);
return -1;
}
return dch.chunkSize;
}
int getFormatChunk(FILE *fid, FormatChunk *formatChunk) {
while (strncmp(formatChunk->chunkID, FORMATID, 4) != 0) {
fread(formatChunk, sizeof(FormatChunk), 1, fid);
if (feof(fid) || ferror(fid))
return -1;
}
return 0;
}
/*******************************************************************/
int main(int argc, char *argv[])
{
PaStreamParameters outputParameters;
PaStream *stream;
PaError err;
/*int i;
int totalFrames;
int numSamples;
int numBytes;
*/
/* read wave file */
char *filename;
FILE *fid;
if (argc < 2) {
printf("Usage: %s filename.wav\n", argv[0]);
return -1;
}
/* filename */
filename = argv[1];
printf("Filename: %s\n", filename);
/* open file */
fid = fopen(filename, "rb");
if (fid == NULL) {
printf("Could not open file %s\n", filename);
return -1;
}
/* check header */
if (checkHeader(fid) < 0) {
printf("Not a wave file!\n");
return -1;
}
FormatChunk formatChunk;
int data_size;
char *data;
if (getFormatChunk(fid, &formatChunk) < 0) {
printf("Couldn't read header\n");
return -1;
}
printf("Chunk Size : %d\n", formatChunk.chunkSize);
printf("Compressed : %d\n", formatChunk.wFormatTag != 1);
printf("Channels : %d\n", formatChunk.wChannels);
printf("SamplesPerSecond : %d\n", formatChunk.dwSamplesPerSec);
printf("dwAvgBytesPerSec : %d\n", formatChunk.dwAvgBytesPerSec);
printf("wBlockAlign : %d\n", formatChunk.wBlockAlign);
printf("wBitsPerSample : %d\n", formatChunk.wBitsPerSample);
if ((data_size = getData(fid, &data)) < 0) {
printf("Couldn't read data\n");
return -1;
}
int total_frames = data_size / formatChunk.wBlockAlign;
printf("Total Frames : %d\n", total_frames);
/* fclose(fid); */
err = Pa_Initialize();
if( err != paNoError ) goto error;
/* Playback recorded data. -------------------------------------------- */
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
outputParameters.channelCount = formatChunk.wChannels;
outputParameters.sampleFormat = paInt16;
outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
printf("YO YO\n"); fflush(stdout);
outputParameters.hostApiSpecificStreamInfo = NULL;
printf("Begin playback.\n"); fflush(stdout);
err = Pa_OpenStream(
&stream,
NULL, /* no input */
&outputParameters,
formatChunk.dwSamplesPerSec,
0, /*FRAMES_PER_BUFFER, */
paClipOff, /* we won't output out of range samples so don't bother clipping them */
NULL, /* no callback, use blocking API */
NULL ); /* no callback, so no callback userData */
if( err != paNoError ) goto error;
if( stream )
{
err = Pa_StartStream( stream );
if( err != paNoError ) goto error;
printf("Waiting for playback to finish.\n"); fflush(stdout);
err = Pa_WriteStream( stream, data, total_frames );
if( err != paNoError ) goto error;
err = Pa_CloseStream( stream );
if( err != paNoError ) goto error;
printf("Done.\n"); fflush(stdout);
}
free( data );
Pa_Terminate();
return 0;
error:
Pa_Terminate();
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 ) );
return -1;
}

View File

@ -1,82 +0,0 @@
"""
PyAudio Example: Low Level C Module test.
Play a wave file.
"""
import _portaudio
import wave
import sys
def get_format_from_width(width, unsigned = True):
"""
Returns a PortAudio format constant for
the specified `width`.
:param `width`:
The desired sample width in bytes (1, 2, 3, or 4)
:param `unsigned`:
For 1 byte width, specifies signed or unsigned
format.
:raises ValueError: for invalid `width`
:rtype: `PaSampleFormat`
"""
p = _portaudio
if width == 1:
if unsigned:
return p.paUInt8
else:
return p.paInt8
elif width == 2:
return p.paInt16
elif width == 3:
return p.paInt24
elif width == 4:
return p.paFloat32
else:
raise ValueError, "Invalid width: %d" % width
chunk = 1024
if len(sys.argv) < 2:
print "Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0]
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
print "* initializing"
_portaudio.initialize()
print "* opening"
stream = _portaudio.open(rate = wf.getframerate(),
channels = wf.getnchannels(),
format = get_format_from_width(wf.getsampwidth()),
output = True)
data = wf.readframes(chunk)
print "* starting stream"
_portaudio.start_stream(stream)
print "available: %d" % _portaudio.get_stream_write_available(stream)
while data != '':
_portaudio.write_stream(stream, data, chunk)
data = wf.readframes(chunk)
print "* stopping stream"
_portaudio.stop_stream(stream)
print "* closing stream"
_portaudio.close(stream)
# always match an initialize() call with a terminate()
_portaudio.terminate()

View File

@ -1,61 +0,0 @@
"""
PyAudio Example: Low Level C Module test.
Record a few seconds of audio and save to a WAVE file.
"""
import _portaudio
import wave
import sys
chunk = 1024
FORMAT = _portaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
if sys.platform == 'darwin':
CHANNELS = 1
print "* initializing"
_portaudio.initialize()
print "* opening"
stream = _portaudio.open(format = FORMAT,
channels = CHANNELS,
rate = RATE,
input = True,
frames_per_buffer = chunk)
print "* starting stream"
_portaudio.start_stream(stream)
print "* recording"
all = []
for i in range(0, 44100 / chunk * RECORD_SECONDS):
data = _portaudio.read_stream(stream, chunk)
all.append(data)
print "* stopping stream"
_portaudio.stop_stream(stream)
print "* closing stream"
_portaudio.close(stream)
# match all initialize() with terminate() calls
_portaudio.terminate()
# write data to WAVE file
data = ''.join(all)
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(_portaudio.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(data)
wf.close()

View File

@ -1,60 +0,0 @@
"""
PyAudio Example: Low Level C Module test.
Display detected Host APIs and Devices.
"""
import _portaudio as p
p.initialize()
max_apis = p.get_host_api_count()
max_devs = p.get_device_count()
print "\nPortAudio System Info:\n======================"
print "Version: %d" % p.get_version()
print "Version Text: %s" % p.get_version_text()
print "Number of Host APIs: %d" % max_apis
print "Number of Devices : %d" % max_devs
print "\nHost APIs:\n=========="
for i in range(max_apis):
apiinfo = p.get_host_api_info(i)
print "Number : ", i
print "Name : ", apiinfo.name
print "Type : ", apiinfo.type
print "Devices : ", apiinfo.deviceCount
print "defaultInputDevice : ", apiinfo.defaultInputDevice
print "defaultOutputDevice : ", apiinfo.defaultOutputDevice
print "--------------------------"
print "\nDevices:\n========"
for i in range(max_devs):
devinfo = p.get_device_info(i)
print "Number : ", i
print "Name : ", devinfo.name
print "hostApi Index : ", devinfo.hostApi
print "maxInputChannels : ", devinfo.maxInputChannels
print "maxOutputChannels : ", devinfo.maxOutputChannels
print "defaultLowInputLatency : ", devinfo.defaultLowInputLatency
print "defaultLowOutputLatency : ", devinfo.defaultLowOutputLatency
print "defaultHighInputLatency : ", devinfo.defaultHighInputLatency
print "defaultHighOutputLatency : ", devinfo.defaultHighOutputLatency
print "defaultSampleRate : ", devinfo.defaultSampleRate
print "--------------------------------"
print "\nDefault Devices:\n================"
try:
print "Input :", p.get_default_input_device()
except IOError, e:
print "No Input devices."
try:
print "Output :", p.get_default_output_device()
except IOError, e:
print "No Output devices."
p.terminate()

View File

@ -1,51 +0,0 @@
"""
PyAudio Example: Low Level C Module test.
Make a wire between input and output
(i.e., record a few samples and play them back immediately).
Full Duplex version. See wire2.py for Half Duplex.
"""
import _portaudio
import sys
chunk = 1024
FORMAT = _portaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
# poor PPC macs...
if sys.platform == 'darwin':
CHANNELS = 1
print "* initializing"
_portaudio.initialize()
print "* opening"
stream = _portaudio.open(format = FORMAT,
channels = CHANNELS,
rate = RATE,
input = True,
output = True,
frames_per_buffer = chunk)
print "* starting stream"
_portaudio.start_stream(stream)
print "* recording"
for i in range(0, 44100 / chunk * RECORD_SECONDS):
data = _portaudio.read_stream(stream, chunk)
_portaudio.write_stream(stream, data, chunk)
print "* stopping stream"
_portaudio.stop_stream(stream)
print "* closing stream"
_portaudio.close(stream)
# match all initialize() with terminate() calls
_portaudio.terminate()

View File

@ -1,61 +0,0 @@
"""
PyAudio Example: Low Level C Module test.
Make a wire between input and output
(i.e., record a few samples and play them back immediately).
Full Duplex version. See wire2.py for Half Duplex.
"""
import _portaudio
import sys
chunk = 1024
FORMAT = _portaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
# poor PPC macs...
if sys.platform == 'darwin':
CHANNELS = 1
print "* initializing"
_portaudio.initialize()
print "* opening"
stream_input = _portaudio.open(format = FORMAT,
channels = CHANNELS,
rate = RATE,
input = True,
frames_per_buffer = chunk)
stream_output = _portaudio.open(format = FORMAT,
channels = CHANNELS,
rate = RATE,
output = True,
frames_per_buffer = chunk)
print "* starting stream"
_portaudio.start_stream(stream_input)
_portaudio.start_stream(stream_output)
print "* recording"
for i in range(0, 44100 / chunk * RECORD_SECONDS):
data = _portaudio.read_stream(stream_input, chunk)
_portaudio.write_stream(stream_output, data, chunk)
print "* stopping stream"
_portaudio.stop_stream(stream_input)
_portaudio.stop_stream(stream_output)
print "* closing stream"
_portaudio.close(stream_input)
_portaudio.close(stream_output)
# match initialize() with terminate() calls
_portaudio.terminate()

View File

@ -1,40 +0,0 @@
""" PyAudio Example: Play a wave file """
import pyaudio
import wave
import sys
chunk = 1024
PyAudio = pyaudio.PyAudio
if len(sys.argv) < 2:
print "Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0]
sys.exit(-1)
wf = wave.open(sys.argv[1], 'rb')
p = PyAudio()
# open stream
stream = p.open(format =
p.get_format_from_width(wf.getsampwidth()),
channels = wf.getnchannels(),
rate = wf.getframerate(),
output = True)
# read data
data = wf.readframes(chunk)
# play stream
while data != '':
stream.write(data)
data = wf.readframes(chunk)
stream.stop_stream()
stream.close()
p.terminate()

View File

@ -1,48 +0,0 @@
"""
PyAudio example:
Record a few seconds of audio and save to a WAVE file.
"""
import pyaudio
import wave
import sys
chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
stream = p.open(format = FORMAT,
channels = CHANNELS,
rate = RATE,
input = True,
frames_per_buffer = chunk)
print "* recording"
all = []
for i in range(0, RATE / chunk * RECORD_SECONDS):
data = stream.read(chunk)
all.append(data)
print "* done recording"
stream.stop_stream()
stream.close()
p.terminate()
# write data to WAVE file
data = ''.join(all)
wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(data)
wf.close()

View File

@ -1,40 +0,0 @@
"""
PyAudio Example:
Make a wire between input and output (i.e., record a few samples
and play them back immediately).
Full Duplex version; see wire2.py for Half Duplex. """
import pyaudio
import sys
chunk = 1024
WIDTH = 2
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
stream = p.open(format =
p.get_format_from_width(WIDTH),
channels = CHANNELS,
rate = RATE,
input = True,
output = True,
frames_per_buffer = chunk)
print "* recording"
for i in range(0, 44100 / chunk * RECORD_SECONDS):
data = stream.read(chunk)
stream.write(data, chunk)
print "* done"
stream.stop_stream()
stream.close()
p.terminate()

View File

@ -1,50 +0,0 @@
"""
PyAudio Example:
Make a wire between input and output (i.e., record a few samples
and play them back immediately).
Half Duplex version; see wire.py for Full Duplex. """
import pyaudio
import sys
chunk = 1024
WIDTH = 2
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
if sys.platform == 'darwin':
CHANNELS = 1
p = pyaudio.PyAudio()
# use default input device
stream_input = p.open(format =
p.get_format_from_width(WIDTH),
channels = CHANNELS,
rate = RATE,
input = True,
frames_per_buffer = chunk)
# use default output device
stream_output = p.open(format =
p.get_format_from_width(WIDTH),
channels = CHANNELS,
rate = RATE,
output = True,
frames_per_buffer = chunk)
print "* recording"
for i in range(0, 44100 / chunk * RECORD_SECONDS):
data = stream_input.read(chunk)
stream_output.write(data, chunk)
print "* done"
stream_input.stop_stream()
stream_output.stop_stream()
stream_input.close()
stream_output.close()
p.terminate()

120
tests/error_tests.py Normal file
View File

@ -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')

642
tests/pyaudio_tests.py Normal file
View File

@ -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)