pax_global_header00006660000000000000000000000064143522132670014517gustar00rootroot0000000000000052 comment=c7b9d6898f2ada695389a30454034f07ecbf3491 python-pyaudio-0.2.13/000077500000000000000000000000001435221326700145735ustar00rootroot00000000000000python-pyaudio-0.2.13/.gitignore000066400000000000000000000000731435221326700165630ustar00rootroot00000000000000.DS_Store src/PyAudio.egg-info build/ dist/ docs/ MANIFEST python-pyaudio-0.2.13/CHANGELOG000066400000000000000000000156171435221326700160170ustar00rootroot000000000000002022-12-26 Hubert Pham PyAudio 0.2.13 * Move pyaudio.Stream to pyaudio.PyAudio.Stream. The pyaudio.Stream class is now pyaudio.PyAudio.Stream, nested under the existing pyaudio.PyAudio class. This should not affect existing code, as directly accessing the module-level pyaudio.Stream class has always been unsupported (use PyAudio.open instead). Accessing pyaudio.Stream directly is deprecated and will raise a DeprecationWarning. * Deprecate PaMacCoreStreamInfo's get_channel_map() and get_flags() methods. Use the channel_map and flags properties, respectively, instead. Also deprecates internal method _get_host_api_stream_object. Calling deprecated methods will raise a DeprecationWarning. * Package PyAudio as an actual package (directory). Previously, the library deployed a single pyaudio.py file and a C extension module to the root of site-packages. Now, the library deploys a Python package. * Add default shared library path for Homebrew installations on Apple Silicon. * Refactor and cleanup. - Refactor C extension for better maintainability. - Add more unit tests. - Update and modernize examples directory. - Improve C and python style conformance, plus many cosmetic updates. 2022-07-18 Hubert Pham PyAudio 0.2.12 - Modernize build process for Microsoft Windows, using the native toolchain. Setuptool setup.py and INSTALL instructions are more streamlined. Building from Cygwin/MinGW is no longer supported nor tested. Thanks to Sean Zimmermann for the patches and general help! - Change default frames per buffer size to paFramesPerBufferUnspecified. Previously, pyaudio.py set a default frames per buffer size of 1024, which can lead to dropped frames on some systems. Now, by default, PortAudio selects the buffer size based on host and latency requirements. Thanks to Jason Hihn for the suggestion! - Minor fixes and refactoring for compatibility with Python 3.7+. Updates include: * Remove call to deprecated PyEval_InitThreads() for Python 3.7+ * Use Py_ssize_t types in appropriate places (for Python 3.10+). * Remove the min macro to ease compilation for Windows. - Use the locale's preferred encoding to decode device names. Thanks to Eiichi Takamori for the patch! - Unit tests: add skipIf decorators to skip tests that require hardware. Set the PYAUDIO_SKIP_HW_TESTS environment variable to disable tests that require sound hardware, useful for (automated) test environments without access to audio devices. Thanks to Matěj Cepl for the suggestion and patch! - Documentation, examples, and unit tests: various fixes. * Add more unit tests and repair a few that test the GIL on macOS. * Remove examples/error.py, which is redundant with tests/error_tests.py. * Fix type documentation of return value types in docstrings. Thanks to Vasily Zakharov for pointing out the return-value type errors! - Modernize packaging: add LICENSE.txt, pyproject.toml, and classifiers. 2017-03-18 Hubert Pham 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 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 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 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 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 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 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 PyAudio 0.2.4 - Maintenance release: updated directory structure and packaging. 2008-10-29 Hubert Pham PyAudio 0.2.3 - Release the GIL during blocking PortAudio I/O calls. - Fixed Python argument parsing to use a long for PaSampleFormat (rather than int). Thanks to many who have pointed out these two issues and sent patches. - pyaudio.PyAudio.is_format_supported() now throws a ValueError exception if the specified format is not supported for any reason (or returns True if the format is supported). Prior, the method would return False if and only if the specified sample rate was unsupported. is_format_supported() now will always return True or throw an exception. 2008-03-06 Hubert Pham PyAudio 0.2.0 - Added PaMacCoreStreamInfo for Mac OS X Host API Specific Stream Info (e.g., for channel maps). - Added packaging files for building binaries. 2008-02-12 Justin Mazzola Paluska - Initial version of debian packaging. python-pyaudio-0.2.13/INSTALL000066400000000000000000000111231435221326700156220ustar00rootroot00000000000000================================================================================ PyAudio Installation and Compilation Hints ================================================================================ Install PyAudio using pip on most platforms. -------------------------------------------------------------------------------- Microsoft Windows -------------------------------------------------------------------------------- Pip will fetch and install PyAudio pre-compiled wheels. These wheels statically link PortAudio v19 built with the native toolchain. They support Windows MME, DirectSound, WASAPI, and WDM-KS, but not ASIO. > python -m pip install pyaudio -------------------------------------------------------------------------------- Apple macOS -------------------------------------------------------------------------------- Use a package manager (e.g. Homebrew) to install the PortAudio library, then install PyAudio using pip: % brew install portaudio % pip install pyaudio -------------------------------------------------------------------------------- Most GNU/Linux distributions -------------------------------------------------------------------------------- Use a package manager to install PyAudio. For example, on Debian/Ubuntu: % sudo apt install python3-pyaudio If the latest version of PyAudio is not available, install it using pip. pip will download the PyAudio source and build it for your system. Be sure to install the python and PortAudio development library first. -------------------------------------------------------------------------------- Compiling from source (GNU/Linux, macOS, and POSIX-like platforms) -------------------------------------------------------------------------------- For most platforms, compiling from source is similar to the instructions above for GNU/Linux: obtain or compile PortAudio, and then build PyAudio. To manually build PyAudio from source (rather than relying on pip): % python setup.py build You can then use pip to install PyAudio (pip install .) or create wheels. -------------------------------------------------------------------------------- Compiling from source (Microsoft Windows & VCPKG) -------------------------------------------------------------------------------- Microsoft provides a C/C++ package manager called vcpkg, which can install PortAudio on Windows along with any necessary headers. You'll need a copy of both Visual Studio with the C compilers installed, as well as vcpkg. To install PortAudio with static linking, run: For 32-bit builds: > vcpkg install portaudio:x86-windows-static For 64-bit builds: > vcpkg install portaudio:x64-windows-static This will place PortAudio headers and libraries into your vcpkg install folder (assuming vcpkg.exe is in C:\vcpkg, this would be C:\vcpkg\installed). PortAudio headers will be in C:\vcpkg\installed\{x86,x64}-windows-static\include, and the library will be in C:\vcpkg\installed\{x86,x64}-windows-static\lib, assuming that vcpkg has the install root at C:\vcpkg\installed). To build this python package with those libraries, run: > set VCPKG_PATH=C:\vcpkg\installed\x86-windows-static (or x64-windows-static) > python setup.py build To distribute it to users as a binary wheel, run: > python setup.py bdist_wheel The generated wheel will be placed in the dist folder, and can be copied to any target machine and installed with 'pip install PYAUDIO_WHEEL.whl'. Without VCPKG -------------------------------------------------------------------------------- If you prefer not to use VCPKG, you can build PortAudio directly using the standard Windows compiler chain. You will need Visual Studio with the C compiler installed, and a copy of CMake. Once you have the PortAudio sources, run the following from the sources directory: > mkdir mybuild > cd mybuild > cmake .. # Generates build with default options. # You can also run cmake-gui .. > cmake --build . --config Release # Build the library in release mode. This will generate ./Release/portaudio_static_x86.lib (or portaudio_static_x64.lib for 64-bit builds). We can now build pyaudio. For this example, we'll assume your PortAudio sources are in the same folder as pyaudio. > python setup.py build_ext ` -I .\portaudio\include ` -L .\portaudio\mybuild\Release ` -l portaudio_static_x86 (or portaudio_static_x64) To distribute it to users as a binary wheel, run: > python setup.py bdist_wheel The generated wheel will be placed in the dist folder, and can be copied to any target machine and installed with 'pip install PYAUDIO_WHEEL.whl'. python-pyaudio-0.2.13/LICENSE.txt000066400000000000000000000020371435221326700164200ustar00rootroot00000000000000Copyright (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 "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-pyaudio-0.2.13/MANIFEST.in000066400000000000000000000003341435221326700163310ustar00rootroot00000000000000include src/pyaudio/*.c src/pyaudio/*.h src/pyaudio/*.py include Makefile CHANGELOG INSTALL MANIFEST.in recursive-include examples *.py recursive-include tests *.py graft sphinx prune **/__pycache__ prune **/.mypy_cache python-pyaudio-0.2.13/Makefile000066400000000000000000000027431435221326700162410ustar00rootroot00000000000000# This is the PyAudio distribution makefile. .PHONY: docs clean build VERSION := 0.2.13 PYTHON ?= python3 SPHINX ?= sphinx-build DOCS_OUTPUT = docs/ # To build the docs, we need to first build the library. Hardcode the lib output # directory, since different versions of Python and setuptools use different # default directory names. BUILD_DIR := build/lib BUILD_STAMP := $(BUILD_DIR)/build BUILD_ARGS := --build-platlib $(BUILD_DIR) SRCFILES := src/pyaudio/*.c src/pyaudio/*.h src/pyaudio/*.py EXAMPLES := examples/*.py TESTS := tests/*.py what: @echo "make targets:" @echo @echo " tarball : build source tarball" @echo " docs : generate documentation (requires sphinx)" @echo " clean : remove build files" @echo @echo "To build pyaudio, run:" @echo @echo " python setup.py install" clean: @rm -rf build dist MANIFEST $(DOCS_OUTPUT) src/pyaudio/*.pyc \ src/pyaudio/*.so src/pyaudio/__pycache__ ###################################################################### # Documentation ###################################################################### build: $(BUILD_STAMP) $(BUILD_STAMP): $(SRCFILES) $(PYTHON) setup.py build $(BUILD_ARGS) touch $@ docs: build PYTHONPATH=$(BUILD_DIR) $(SPHINX) -b html sphinx/ $(DOCS_OUTPUT) ###################################################################### # Source Tarball ###################################################################### tarball: $(SRCFILES) $(EXAMPLES) $(TESTS) MANIFEST.in @$(PYTHON) setup.py sdist python-pyaudio-0.2.13/README.md000066400000000000000000000040711435221326700160540ustar00rootroot00000000000000 # PyAudio PyAudio provides Python bindings for PortAudio v19, the cross-platform audio I/O library. With PyAudio, you can easily use Python to play and record audio on a variety of platforms, such as GNU/Linux, Microsoft Windows, and Apple macOS. PyAudio is distributed under the MIT License. * [Homepage](https://people.csail.mit.edu/hubert/pyaudio/) * [API Documentation](https://people.csail.mit.edu/hubert/pyaudio/docs/) * [PyPi](https://pypi.python.org/pypi/PyAudio) ## Installation See the INSTALLATION file in the source distribution for details. In summary, install PyAudio using `pip` on most platforms. ### Windows ```sh python -m pip install pyaudio ``` This installs the precompiled PyAudio library with PortAudio v19 19.7.0 included. The library is compiled with support for Windows MME API, DirectSound, WASAPI, and WDM-KS. It does not include support for ASIO. If you require support for APIs not included, you will need to compile PortAudio and PyAudio. ### macOS Use [Homebrew](https://brew.sh) to install the prerequisite [portaudio](http://portaudio.com) library, then install PyAudio using `pip`: ```sh brew install portaudio pip install pyaudio ``` ### GNU/Linux Use the package manager to install PyAudio. For example, on Debian-based systems: ```sh sudo apt install python3-pyaudio ``` Alternatively, if the latest version of PyAudio is not available, install it using `pip`. Be sure to first install development libraries for `portaudio19` and `python3`. ### Building from source See the INSTALLATION file. ## Documentation & Usage Examples * Read the [API Documentation](https://people.csail.mit.edu/hubert/pyaudio/docs/), or generate it from the source using [`sphinx`](https://www.sphinx-doc.org/). * Usage examples are in the `examples` directory of the source distribution, or see the [project homepage](https://people.csail.mit.edu/hubert/pyaudio/). ## License PyAudio is distributed under the MIT License. See LICENSE.txt. python-pyaudio-0.2.13/examples/000077500000000000000000000000001435221326700164115ustar00rootroot00000000000000python-pyaudio-0.2.13/examples/play_wave.py000066400000000000000000000014571435221326700207610ustar00rootroot00000000000000"""PyAudio Example: Play a wave file.""" import wave import sys import pyaudio CHUNK = 1024 if len(sys.argv) < 2: print(f'Plays a wave file. Usage: {sys.argv[0]} filename.wav') sys.exit(-1) with wave.open(sys.argv[1], 'rb') as wf: # Instantiate PyAudio and initialize PortAudio system resources (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) # Play samples from the wave file (3) while len(data := wf.readframes(CHUNK)): # Requires Python 3.8+ for := stream.write(data) # Close stream (4) stream.close() # Release PortAudio system resources (5) p.terminate() python-pyaudio-0.2.13/examples/play_wave_callback.py000066400000000000000000000022221435221326700225640ustar00rootroot00000000000000"""PyAudio Example: Play a wave file (callback version).""" import wave import time import sys import pyaudio if len(sys.argv) < 2: print(f'Plays a wave file. Usage: {sys.argv[0]} filename.wav') sys.exit(-1) with wave.open(sys.argv[1], 'rb') as wf: # Define callback for playback (1) def callback(in_data, frame_count, time_info, status): data = wf.readframes(frame_count) # If len(data) is less than requested frame_count, PyAudio automatically # assumes the stream is finished, and the stream stops. return (data, pyaudio.paContinue) # Instantiate PyAudio and initialize PortAudio system resources (2) p = pyaudio.PyAudio() # 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) # Wait for stream to finish (4) while stream.is_active(): time.sleep(0.1) # Close the stream (5) stream.close() # Release PortAudio system resources (6) p.terminate() python-pyaudio-0.2.13/examples/play_wave_macosx_channelmap.py000066400000000000000000000027331435221326700245170ustar00rootroot00000000000000"""PyAudio Example: macOS-only: Play a wave file with channel maps.""" import wave import sys import pyaudio CHUNK = 1024 if len(sys.argv) < 2: print(f'Plays a wave file. Usage: {sys.argv[0]} filename.wav') sys.exit(-1) # standard L-R stereo # channel_map = (0, 1) # reverse: R-L stereo # channel_map = (1, 0) # no audio # channel_map = (-1, -1) # left channel audio --> left speaker; no right channel # channel_map = (0, -1) # right channel audio --> right speaker; no left channel # channel_map = (-1, 1) # left channel audio --> right speaker # channel_map = (-1, 0) # right channel audio --> left speaker channel_map = (1, -1) # etc... try: stream_info = pyaudio.PaMacCoreStreamInfo( flags=pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, channel_map=channel_map) except AttributeError: print( 'Could not find PaMacCoreStreamInfo. Ensure you are running on macOS.') sys.exit(-1) print('Stream Info Flags:', stream_info.flags) print('Stream Info Channel Map:', stream_info.channel_map) with wave.open(sys.argv[1], 'rb') as wf: p = pyaudio.PyAudio() 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) # Play stream while len(data := wf.readframes(CHUNK)): # Requires Python 3.8+ for := stream.write(data) stream.close() p.terminate() python-pyaudio-0.2.13/examples/record.py000066400000000000000000000012351435221326700202420ustar00rootroot00000000000000"""PyAudio Example: Record a few seconds of audio and save to a wave file.""" import wave import sys import pyaudio CHUNK = 1024 FORMAT = pyaudio.paInt16 CHANNELS = 1 if sys.platform == 'darwin' else 2 RATE = 44100 RECORD_SECONDS = 5 with wave.open('output.wav', 'wb') as wf: p = pyaudio.PyAudio() wf.setnchannels(CHANNELS) wf.setsampwidth(p.get_sample_size(FORMAT)) wf.setframerate(RATE) stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True) print('Recording...') for _ in range(0, RATE // CHUNK * RECORD_SECONDS): wf.writeframes(stream.read(CHUNK)) print('Done') stream.close() p.terminate() python-pyaudio-0.2.13/examples/system_info.py000066400000000000000000000100611435221326700213200ustar00rootroot00000000000000"""PyAudio Example: Query PortAudio Host APIs, devices, and supported rates.""" import pyaudio STANDARD_SAMPLE_RATES = [ 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, 44100.0, 48000.0, 88200.0, 96000.0, 192000.0 ] def print_header(header): print() print(header) print("=" * len(header)) def print_device_info(p, device_info): for name, value in device_info.items(): # If host API, value is an index, so also print the friendly name. if name == "hostApi": value = f"{value} ({p.get_host_api_info_by_index(value)['name']})" # Crashing? See http://stackoverflow.com/a/5146914 print(f"{name}: {value}") # Print supported format rates input_supported_rates = [] output_supported_rates = [] full_duplex_rates = [] for rate in STANDARD_SAMPLE_RATES: if device_info["maxInputChannels"] > 0: try: if p.is_format_supported( rate, input_device=device_info["index"], input_channels=device_info["maxInputChannels"], input_format=pyaudio.paInt16): input_supported_rates.append(rate) except ValueError: pass if device_info["maxOutputChannels"] > 0: try: if p.is_format_supported( rate, output_device=device_info["index"], output_channels=device_info["maxOutputChannels"], output_format=pyaudio.paInt16): output_supported_rates.append(rate) except ValueError: pass if (device_info["maxInputChannels"] > 0 and device_info["maxOutputChannels"] > 0): try: if p.is_format_supported( rate, input_device=device_info["index"], input_channels=device_info["maxInputChannels"], input_format=pyaudio.paInt16, output_device=device_info["index"], output_channels=device_info["maxOutputChannels"], output_format=pyaudio.paInt16): full_duplex_rates.append(rate) except ValueError: pass if input_supported_rates: print(f"Input rates: {input_supported_rates}") if output_supported_rates: print(f"Output rates: {output_supported_rates}") if full_duplex_rates: print(f"Full duplex rates: {full_duplex_rates}") print("-" * 32) def print_system_info(p): print_header("PortAudio System Info") print(f"Version: {pyaudio.get_portaudio_version()}") print(f"Version Text: {pyaudio.get_portaudio_version_text()}") print(f"Number of Host APIs: {p.get_host_api_count()}") print(f"Number of Devices: {p.get_device_count()}") def print_all_host_apis(p): print_header("Host APIs") for i in range(p.get_host_api_count()): for key, value in p.get_host_api_info_by_index(i).items(): print(f"{key}: {value}") print("-" * 26) def print_all_device_infos(p): print_header("Devices") for i in range(p.get_device_count()): print_device_info(p, p.get_device_info_by_index(i)) def print_default_devices(p): print_header("Default Devices") try: device_info = p.get_default_input_device_info() except IOError as err: print(f"No Input devices: {err}") else: print(f"Default Input Device: {device_info['index']}") print_device_info(p, device_info) try: device_info = p.get_default_output_device_info() except IOError as err: print(f"No Output devices: {err}") else: print(f"Default Output Device: {device_info['index']}") print_device_info(p, device_info) if __name__ == "__main__": p = pyaudio.PyAudio() print_system_info(p) print_all_host_apis(p) print_all_device_infos(p) print_default_devices(p) p.terminate() python-pyaudio-0.2.13/examples/wire_callback.py000066400000000000000000000011771435221326700215530ustar00rootroot00000000000000"""PyAudio Example: Audio wire between input and output. Callback version.""" import time import sys import pyaudio DURATION = 5 # seconds def callback(in_data, frame_count, time_info, status): return (in_data, pyaudio.paContinue) p = pyaudio.PyAudio() stream = p.open(format=p.get_format_from_width(2), channels=1 if sys.platform == 'darwin' else 2, rate=44100, input=True, output=True, stream_callback=callback) start = time.time() while stream.is_active() and (time.time() - start) < DURATION: time.sleep(0.1) stream.close() p.terminate() python-pyaudio-0.2.13/examples/wire_full.py000066400000000000000000000010631435221326700207530ustar00rootroot00000000000000"""PyAudio Example: full-duplex wire between input and output.""" import sys import pyaudio RECORD_SECONDS = 5 CHUNK = 1024 RATE = 44100 p = pyaudio.PyAudio() stream = p.open(format=p.get_format_from_width(2), channels=1 if sys.platform == 'darwin' else 2, rate=RATE, input=True, output=True, frames_per_buffer=CHUNK) print('* recording') for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)): stream.write(stream.read(CHUNK)) print('* done') stream.close() p.terminate() python-pyaudio-0.2.13/examples/wire_half.py000066400000000000000000000016641435221326700207320ustar00rootroot00000000000000"""PyAudio Example: Half-duplex wire between input and output.""" import sys import pyaudio CHUNK = 1024 WIDTH = 2 CHANNELS = 1 if sys.platform == 'darwin' else 2 RATE = 44100 RECORD_SECONDS = 5 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 output 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)): stream_output.write(stream_input.read(CHUNK)) print('* done') stream_input.close() stream_output.close() p.terminate() python-pyaudio-0.2.13/pyproject.toml000066400000000000000000000001421435221326700175040ustar00rootroot00000000000000[build-system] requires = ["setuptools<=65.1.1", "wheel"] build-backend = "setuptools.build_meta" python-pyaudio-0.2.13/setup.py000066400000000000000000000141741435221326700163140ustar00rootroot00000000000000"""PyAudio: Cross-platform audio I/O with PortAudio. PyAudio provides Python bindings for PortAudio, the cross-platform audio I/O library. With PyAudio, you can easily use Python to play and record audio on a variety of platforms, such as GNU/Linux, Microsoft Windows, and Apple macOS. PyAudio is distributed under the MIT License: 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 "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import os import logging import platform from setuptools import setup, Extension import sys __version__ = "0.2.13" # setup.py/setuptools will try to locate and link dynamically against portaudio, # except on Windows. On Windows, setup.py will attempt to statically link in # portaudio, since most users will install PyAudio from pre-compiled wheels. # # If you wish to compile PyAudio on Windows, use vcpkg to install portaudio with # either: # - vcpkg install portaudio (for dynamic linking) # - vcpkg install portaudio:x86-windows-static (for 32-bit static linking) # - vcpkg install portaudio:x64-windows-static (for 64-bit static linking) MAC_SYSROOT_PATH = os.environ.get("SYSROOT_PATH", None) WIN_VCPKG_PATH = os.environ.get("VCPKG_PATH", None) def setup_extension(): pyaudio_module_sources = [ 'src/pyaudio/main.c', 'src/pyaudio/device_api.c', 'src/pyaudio/host_api.c', 'src/pyaudio/init.c', 'src/pyaudio/mac_core_stream_info.c', 'src/pyaudio/misc.c', 'src/pyaudio/stream.c', 'src/pyaudio/stream_io.c', 'src/pyaudio/stream_lifecycle.c', ] include_dirs = [] external_libraries = ["portaudio"] external_libraries_path = [] extra_compile_args = [] extra_link_args = [] defines = [] if sys.platform == 'darwin': # Support only dynamic linking with portaudio, since the supported path # is to install portaudio using a package manager (e.g., Homebrew). # TODO: let users pass in location of portaudio library on command line. defines += [('MACOS', '1')] include_dirs += [ '/usr/local/include', '/usr/include', '/opt/homebrew/include' ] external_libraries_path += [ path for path in ('/usr/local/lib', '/usr/lib', '/opt/homebrew/lib') if os.path.exists(path) ] if MAC_SYSROOT_PATH: extra_compile_args += ["-isysroot", MAC_SYSROOT_PATH] extra_link_args += ["-isysroot", MAC_SYSROOT_PATH] elif sys.platform == 'win32': # Only supports statically linking with portaudio, since the typical # way users install PyAudio on win32 is through pre-compiled wheels. bits = platform.architecture()[0] if '64' in bits: defines.append(('MS_WIN64', '1')) if WIN_VCPKG_PATH: include_dirs += [os.path.join(WIN_VCPKG_PATH, 'include')] external_libraries_path = [os.path.join(WIN_VCPKG_PATH, 'lib')] else: # If VCPKG_PATH is not set, it is likely a user oversight, as the # extension compiler likely won't be able to find the portaudio # library to link against. logging.warning("Warning: VCPKG_PATH envrionment variable not set.") # So if VCPKG_PATH is not set, be sure to manually add the correct # path to portaudio's include and lib dirs, or use setuptools # build_ext to specify them on the command line. external_libraries.remove("portaudio") # The static portaudio lib does not include user32 and advapi32, so # those need to be linked manually. external_libraries += ["user32", "Advapi32"] # For static linking, use MT flag to match both vcpkg's portaudio and # the standard portaudio cmake settings. For details, see: # https://devblogs.microsoft.com/cppblog/vcpkg-updates-static-linking-is-now-available/ extra_compile_args += ["/MT"] else: # GNU/Linux and other posix-like OSes will dynamically link to # portaudio, installed by the package manager. include_dirs += ['/usr/local/include', '/usr/include'] external_libraries_path += ['/usr/local/lib', '/usr/lib'] return Extension( 'pyaudio._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, library_dirs=external_libraries_path) with open('README.md', 'r') as fh: long_description = fh.read() setup( name='PyAudio', version=__version__, author="Hubert Pham", url="https://people.csail.mit.edu/hubert/pyaudio/", description="Cross-platform audio I/O with PortAudio", long_description=long_description, long_description_content_type='text/markdown', license="MIT", scripts=[], packages=['pyaudio'], package_dir={'': 'src'}, extras_require={ "test": ["numpy"], }, ext_modules=[setup_extension()], classifiers=[ "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Topic :: Multimedia :: Sound/Audio" ]) python-pyaudio-0.2.13/sphinx/000077500000000000000000000000001435221326700161045ustar00rootroot00000000000000python-pyaudio-0.2.13/sphinx/conf.py000066400000000000000000000173331435221326700174120ustar00rootroot00000000000000#!/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.13' # The full version, including alpha/beta/rc tags. release = '0.2.13' # 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 # " v 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 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 pyaudio._portaudio import paMacCoreStreamInfo except ImportError: pass else: tags.add('pamac') python-pyaudio-0.2.13/sphinx/examples.rst000066400000000000000000000051371435221326700204620ustar00rootroot00000000000000Example: Blocking Mode Audio I/O -------------------------------- .. literalinclude:: ../examples/play_wave.py To use PyAudio, first instantiate PyAudio using :py:func:`pyaudio.PyAudio` (1), which acquires system resources for PortAudio. 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.PyAudio.Stream` to play or record audio. Play audio by writing audio data to the stream using :py:func:`pyaudio.PyAudio.Stream.write`, or read audio data from the stream using :py:func:`pyaudio.PyAudio.Stream.read`. (3) Note that in "blocking mode", each :py:func:`pyaudio.PyAudio.Stream.write` or :py:func:`pyaudio.PyAudio.Stream.read` blocks until all frames have been played/recorded. An alternative approach is "callback mode", described below, in which PyAudio invokes a user-defined function to process recorded audio or generate output audio. Use :py:func:`pyaudio.PyAudio.Stream.close` to close the stream. (4) Finally, terminate the PortAudio session and release system resources 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 user-defined callback function (1) whenever it needs new audio data to play and/or when new recorded audio data becomes available. PyAudio calls the callback function in a separate thread. The callback function must have the following signature ``callback(, , , )``. It must return a tuple containing ``frame_count`` frames of audio data to output (for output streams) and a flag signifying whether there are more expected frames to play or record. (For input-only streams, the audio data portion of the return value is ignored.) The audio stream starts processing once the stream is opened (3), which will call the callback function repeatedly until that function returns :py:data:`pyaudio.paComplete` or :py:data:`pyaudio.paAbort`, or until either :py:data:`pyaudio.PyAudio.Stream.stop` or :py:data:`pyaudio.PyAudio.Stream.close` is called. Note that if the callback returns fewer frames than the :py:data:`frame_count` argument (2), the stream automatically closes after those frames are played. To keep the stream active, the main thread must remain alive, e.g., by sleeping (4). In the example above, once the entire wavefile is read, :py:data:`wf.readframes(frame_count)` will eventually return fewer than the requested frames. The stream will stop, and the while loop (4) will end. python-pyaudio-0.2.13/sphinx/index.rst000066400000000000000000000014461435221326700177520ustar00rootroot00000000000000PyAudio Documentation ===================== .. contents:: ------------ Introduction ------------ .. automodule:: pyaudio :members: :special-members: :exclude-members: PyAudio, Stream, PaMacCoreStreamInfo Details ------- ------------- Class PyAudio ------------- .. autoclass:: pyaudio.PyAudio :members: :special-members: :exclude-members: Stream -------------------- Class PyAudio.Stream -------------------- .. autoclass:: pyaudio.PyAudio.Stream :members: :special-members: ----------------- Platform Specific ----------------- .. only:: pamac Class PaMacCoreStreamInfo ------------------------- .. autoclass:: pyaudio.PaMacCoreStreamInfo :members: :special-members: Indices and tables ================== * :ref:`genindex` * :ref:`search` python-pyaudio-0.2.13/src/000077500000000000000000000000001435221326700153625ustar00rootroot00000000000000python-pyaudio-0.2.13/src/pyaudio/000077500000000000000000000000001435221326700170345ustar00rootroot00000000000000python-pyaudio-0.2.13/src/pyaudio/__init__.py000066400000000000000000001100621435221326700211450ustar00rootroot00000000000000# PyAudio : Python Bindings for PortAudio. # # 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 # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ PyAudio provides Python bindings for PortAudio, the cross-platform audio I/O library. With PyAudio, you can easily use Python to play and record audio on a variety of platforms. .. include:: ../sphinx/examples.rst Overview -------- **Classes** :py:class:`PyAudio`, :py:class:`PyAudio.Stream` .. only:: pamac **Host Specific Classes** :py:class:`PaMacCoreStreamInfo` **Stream Conversion Convenience Functions** :py:func:`get_sample_size`, :py:func:`get_format_from_width` **PortAudio version** :py:func:`get_portaudio_version`, :py:func:`get_portaudio_version_text` .. |PaSampleFormat| replace:: :ref:`PortAudio Sample Format ` .. _PaSampleFormat: **Portaudio Sample Formats** :py:data:`paFloat32`, :py:data:`paInt32`, :py:data:`paInt24`, :py:data:`paInt16`, :py:data:`paInt8`, :py:data:`paUInt8`, :py:data:`paCustomFormat` .. |PaHostAPI| replace:: :ref:`PortAudio Host API ` .. _PaHostAPI: **PortAudio Host APIs** :py:data:`paInDevelopment`, :py:data:`paDirectSound`, :py:data:`paMME`, :py:data:`paASIO`, :py:data:`paSoundManager`, :py:data:`paCoreAudio`, :py:data:`paOSS`, :py:data:`paALSA`, :py:data:`paAL`, :py:data:`paBeOS`, :py:data:`paWDMKS`, :py:data:`paJACK`, :py:data:`paWASAPI`, :py:data:`paNoDevice` .. |PaErrorCode| replace:: :ref:`PortAudio Error Code ` .. _PaErrorCode: **PortAudio Error Codes** :py:data:`paNoError`, :py:data:`paNotInitialized`, :py:data:`paUnanticipatedHostError`, :py:data:`paInvalidChannelCount`, :py:data:`paInvalidSampleRate`, :py:data:`paInvalidDevice`, :py:data:`paInvalidFlag`, :py:data:`paSampleFormatNotSupported`, :py:data:`paBadIODeviceCombination`, :py:data:`paInsufficientMemory`, :py:data:`paBufferTooBig`, :py:data:`paBufferTooSmall`, :py:data:`paNullCallback`, :py:data:`paBadStreamPtr`, :py:data:`paTimedOut`, :py:data:`paInternalError`, :py:data:`paDeviceUnavailable`, :py:data:`paIncompatibleHostApiSpecificStreamInfo`, :py:data:`paStreamIsStopped`, :py:data:`paStreamIsNotStopped`, :py:data:`paInputOverflowed`, :py:data:`paOutputUnderflowed`, :py:data:`paHostApiNotFound`, :py:data:`paInvalidHostApi`, :py:data:`paCanNotReadFromACallbackStream`, :py:data:`paCanNotWriteToACallbackStream`, :py:data:`paCanNotReadFromAnOutputOnlyStream`, :py:data:`paCanNotWriteToAnInputOnlyStream`, :py:data:`paIncompatibleStreamHostApi` .. |PaCallbackReturnCodes| replace:: :ref:`PortAudio Callback Return Code ` .. _PaCallbackReturnCodes: **PortAudio Callback Return Codes** :py:data:`paContinue`, :py:data:`paComplete`, :py:data:`paAbort` .. |PaCallbackFlags| replace:: :ref:`PortAutio Callback Flag ` .. _PaCallbackFlags: **PortAudio Callback Flags** :py:data:`paInputUnderflow`, :py:data:`paInputOverflow`, :py:data:`paOutputUnderflow`, :py:data:`paOutputOverflow`, :py:data:`paPrimingOutput` """ __author__ = "Hubert Pham" __version__ = "0.2.13" __docformat__ = "restructuredtext en" import locale import warnings try: import pyaudio._portaudio as pa except ImportError: print("Could not import the PyAudio C module 'pyaudio._portaudio'.") raise # PaSampleFormat Sample Formats paFloat32 = pa.paFloat32 #: 32 bit float paInt32 = pa.paInt32 #: 32 bit int paInt24 = pa.paInt24 #: 24 bit int paInt16 = pa.paInt16 #: 16 bit int paInt8 = pa.paInt8 #: 8 bit int paUInt8 = pa.paUInt8 #: 8 bit unsigned int paCustomFormat = pa.paCustomFormat #: a custom data format # HostAPI TypeId paInDevelopment = pa.paInDevelopment #: Still in development paDirectSound = pa.paDirectSound #: DirectSound (Windows only) paMME = pa.paMME #: Multimedia Extension (Windows only) paASIO = pa.paASIO #: Steinberg Audio Stream Input/Output paSoundManager = pa.paSoundManager #: SoundManager (OSX only) paCoreAudio = pa.paCoreAudio #: CoreAudio (OSX only) paOSS = pa.paOSS #: Open Sound System (Linux only) paALSA = pa.paALSA #: Advanced Linux Sound Architecture (Linux only) paAL = pa.paAL #: Open Audio Library paBeOS = pa.paBeOS #: BeOS Sound System paWDMKS = pa.paWDMKS #: Windows Driver Model (Windows only) paJACK = pa.paJACK #: JACK Audio Connection Kit paWASAPI = pa.paWASAPI #: Windows Vista Audio stack architecture paNoDevice = pa.paNoDevice #: Not actually an audio device # PortAudio Error Codes paNoError = pa.paNoError paNotInitialized = pa.paNotInitialized paUnanticipatedHostError = pa.paUnanticipatedHostError paInvalidChannelCount = pa.paInvalidChannelCount paInvalidSampleRate = pa.paInvalidSampleRate paInvalidDevice = pa.paInvalidDevice paInvalidFlag = pa.paInvalidFlag paSampleFormatNotSupported = pa.paSampleFormatNotSupported paBadIODeviceCombination = pa.paBadIODeviceCombination paInsufficientMemory = pa.paInsufficientMemory paBufferTooBig = pa.paBufferTooBig paBufferTooSmall = pa.paBufferTooSmall paNullCallback = pa.paNullCallback paBadStreamPtr = pa.paBadStreamPtr paTimedOut = pa.paTimedOut paInternalError = pa.paInternalError paDeviceUnavailable = pa.paDeviceUnavailable paIncompatibleHostApiSpecificStreamInfo = ( pa.paIncompatibleHostApiSpecificStreamInfo) paStreamIsStopped = pa.paStreamIsStopped paStreamIsNotStopped = pa.paStreamIsNotStopped paInputOverflowed = pa.paInputOverflowed paOutputUnderflowed = pa.paOutputUnderflowed paHostApiNotFound = pa.paHostApiNotFound paInvalidHostApi = pa.paInvalidHostApi paCanNotReadFromACallbackStream = pa.paCanNotReadFromACallbackStream paCanNotWriteToACallbackStream = pa.paCanNotWriteToACallbackStream paCanNotReadFromAnOutputOnlyStream = pa.paCanNotReadFromAnOutputOnlyStream paCanNotWriteToAnInputOnlyStream = pa.paCanNotWriteToAnInputOnlyStream paIncompatibleStreamHostApi = pa.paIncompatibleStreamHostApi # PortAudio Callback Return Codes paContinue = pa.paContinue #: There is more audio data to come paComplete = pa.paComplete #: This was the last block of audio data paAbort = pa.paAbort #: An error ocurred, stop playback/recording # PortAudio Callback Flags paInputUnderflow = pa.paInputUnderflow #: Buffer underflow in input paInputOverflow = pa.paInputOverflow #: Buffer overflow in input paOutputUnderflow = pa.paOutputUnderflow #: Buffer underflow in output paOutputOverflow = pa.paOutputOverflow #: Buffer overflow in output paPrimingOutput = pa.paPrimingOutput #: Just priming, not playing yet # PortAudio Misc Constants paFramesPerBufferUnspecified = pa.paFramesPerBufferUnspecified # Utilities def get_sample_size(format): """Returns the size (in bytes) for the specified sample *format*. :param format: A |PaSampleFormat| constant. :raises ValueError: on invalid specified `format`. :rtype: integer """ return pa.get_sample_size(format) 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: when invalid *width* :rtype: A |PaSampleFormat| constant """ if width == 1: if unsigned: return paUInt8 return paInt8 if width == 2: return paInt16 if width == 3: return paInt24 if width == 4: return paFloat32 raise ValueError(f"Invalid width: {width}") # Versioning def get_portaudio_version(): """Returns portaudio version. :rtype: int """ return pa.get_version() def get_portaudio_version_text(): """Returns PortAudio version as a text string. :rtype: string """ return pa.get_version_text() class PyAudio: """Python interface to PortAudio. Provides methods to: - initialize and terminate PortAudio - open and close streams - query and inspect the available PortAudio Host APIs - query and inspect the available PortAudio audio devices. **Stream Management** :py:func:`open`, :py:func:`close` **Host API** :py:func:`get_host_api_count`, :py:func:`get_default_host_api_info`, :py:func:`get_host_api_info_by_type`, :py:func:`get_host_api_info_by_index`, :py:func:`get_device_info_by_host_api_device_index` **Device API** :py:func:`get_device_count`, :py:func:`is_format_supported`, :py:func:`get_default_input_device_info`, :py:func:`get_default_output_device_info`, :py:func:`get_device_info_by_index` **Stream Format Conversion** :py:func:`get_sample_size`, :py:func:`get_format_from_width` **Details** """ class Stream: """PortAudio Stream Wrapper. Use :py:func:`PyAudio.open` to instantiate. **Opening and Closing** :py:func:`__init__`, :py:func:`close` **Stream Info** :py:func:`get_input_latency`, :py:func:`get_output_latency`, :py:func:`get_time`, :py:func:`get_cpu_load` **Stream Management** :py:func:`start_stream`, :py:func:`stop_stream`, :py:func:`is_active`, :py:func:`is_stopped` **Input Output** :py:func:`write`, :py:func:`read`, :py:func:`get_read_available`, :py:func:`get_write_available` """ def __init__(self, PA_manager, rate, channels, format, input=False, output=False, input_device_index=None, output_device_index=None, frames_per_buffer=pa.paFramesPerBufferUnspecified, start=True, input_host_api_specific_stream_info=None, output_host_api_specific_stream_info=None, stream_callback=None): """Initialize an audio stream. Do not call directly. Use :py:func:`PyAudio.open`. A stream can either be input, output, or both. :param PA_manager: A reference to the managing :py:class:`PyAudio` instance :param rate: Sampling rate :param channels: Number of channels :param format: Sampling size and format. See |PaSampleFormat|. :param input: Specifies whether this is an input stream. Defaults to ``False``. :param output: Specifies whether this is an output stream. Defaults to ``False``. :param input_device_index: Index of Input Device to use. Unspecified (or ``None``) uses default device. Ignored if `input` is ``False``. :param output_device_index: Index of Output Device to use. Unspecified (or ``None``) uses the default device. Ignored if `output` is ``False``. :param frames_per_buffer: Specifies the number of frames per buffer. :param start: Start the stream running immediately. Defaults to ``True``. In general, there is no reason to set this to ``False``. :param input_host_api_specific_stream_info: Specifies a host API specific stream information data structure for input. .. only:: pamac See :py:class:`PaMacCoreStreamInfo`. :param output_host_api_specific_stream_info: Specifies a host API specific stream information data structure for output. .. only:: pamac See :py:class:`PaMacCoreStreamInfo`. :param stream_callback: Specifies a callback function for *non-blocking* (callback) operation. Default is ``None``, which indicates *blocking* operation (i.e., :py:func:`PyAudio.Stream.read` and :py:func:`PyAudio.Stream.write`). To use non-blocking operation, specify a callback that conforms to the following signature: .. code-block:: python callback(in_data, # input data if input=True; else None frame_count, # number of frames time_info, # dictionary status_flags) # PaCallbackFlags ``time_info`` is a dictionary with the following keys: ``input_buffer_adc_time``, ``current_time``, and ``output_buffer_dac_time``; see the PortAudio documentation for their meanings. ``status_flags`` is one of |PaCallbackFlags|. The callback must return a tuple: .. code-block:: python (out_data, flag) ``out_data`` is a byte array whose length should be the (``frame_count * channels * bytes-per-channel``) if ``output=True`` or ``None`` if ``output=False``. ``flag`` must be either :py:data:`paContinue`, :py:data:`paComplete` or :py:data:`paAbort` (one of |PaCallbackReturnCodes|). When ``output=True`` and ``out_data`` does not contain at least ``frame_count`` frames, :py:data:`paComplete` is assumed for ``flag``. **Note:** ``stream_callback`` is called in a separate thread (from the main thread). Exceptions that occur in the ``stream_callback`` will: 1. print a traceback on standard error to aid debugging, 2. queue the exception to be thrown (at some point) in the main thread, and 3. return `paAbort` to PortAudio to stop the stream. **Note:** Do not call :py:func:`PyAudio.Stream.read` or :py:func:`PyAudio.Stream.write` if using non-blocking operation. **See:** PortAudio's callback signature for additional details: http://portaudio.com/docs/v19-doxydocs/portaudio_8h.html#a8a60fb2a5ec9cbade3f54a9c978e2710 :raise ValueError: Neither input nor output are set True. """ if not (input or output): raise ValueError("Must specify an input or output " + "stream.") self._parent = PA_manager self._is_input = input self._is_output = output self._is_running = start self._rate = rate self._channels = channels self._format = format self._frames_per_buffer = frames_per_buffer arguments = { 'rate': rate, 'channels': channels, 'format': format, 'input': input, 'output': output, 'input_device_index': input_device_index, 'output_device_index': output_device_index, 'frames_per_buffer': frames_per_buffer } if input_host_api_specific_stream_info: arguments[ 'input_host_api_specific_stream_info' ] = input_host_api_specific_stream_info if output_host_api_specific_stream_info: arguments[ 'output_host_api_specific_stream_info' ] = output_host_api_specific_stream_info if stream_callback: arguments['stream_callback'] = stream_callback # calling pa.open returns a stream object self._stream = pa.open(**arguments) self._input_latency = self._stream.inputLatency self._output_latency = self._stream.outputLatency if self._is_running: pa.start_stream(self._stream) def close(self): """Closes the stream.""" pa.close(self._stream) self._is_running = False self._parent._remove_stream(self) # Stream Info def get_input_latency(self): """Returns the input latency. :rtype: float """ return self._stream.inputLatency def get_output_latency(self): """Returns the output latency. :rtype: float """ return self._stream.outputLatency def get_time(self): """Returns stream time. :rtype: float """ return pa.get_stream_time(self._stream) def get_cpu_load(self): """Return the CPU load. Always 0.0 when using the blocking API. :rtype: float """ return pa.get_stream_cpu_load(self._stream) # Stream Lifecycle def start_stream(self): """Starts the stream.""" if self._is_running: return pa.start_stream(self._stream) self._is_running = True def stop_stream(self): """Stops the stream.""" if not self._is_running: return pa.stop_stream(self._stream) self._is_running = False def is_active(self): """Returns whether the stream is active. :rtype: bool """ return pa.is_stream_active(self._stream) def is_stopped(self): """Returns whether the stream is stopped. :rtype: bool """ return pa.is_stream_stopped(self._stream) # Stream blocking I/O def write(self, frames, num_frames=None, exception_on_underflow=False): """Write samples to the stream for playback. Do not call when using non-blocking mode. :param frames: The frames of data. :param num_frames: The number of frames to write. Defaults to None, in which this value will be automatically computed. :param exception_on_underflow: Specifies whether an IOError exception should be thrown (or silently ignored) on buffer underflow. Defaults to False for improved performance, especially on slower platforms. :raises IOError: if the stream is not an output stream or if the write operation was unsuccessful. :rtype: `None` """ if not self._is_output: raise IOError("Not output stream", paCanNotWriteToAnInputOnlyStream) if num_frames is None: # Determine how many frames to read: width = get_sample_size(self._format) num_frames = int(len(frames) / (self._channels * width)) pa.write_stream(self._stream, frames, num_frames, exception_on_underflow) def read(self, num_frames, exception_on_overflow=True): """Read samples from the stream. Do not call when using non-blocking mode. :param num_frames: The number of frames to read. :param exception_on_overflow: Specifies whether an IOError exception should be thrown (or silently ignored) on input buffer overflow. Defaults to True. :raises IOError: if stream is not an input stream or if the read operation was unsuccessful. :rtype: bytes """ if not self._is_input: raise IOError("Not input stream", paCanNotReadFromAnOutputOnlyStream) return pa.read_stream(self._stream, num_frames, exception_on_overflow) def get_read_available(self): """Return the number of frames that can be read without waiting. :rtype: integer """ return pa.get_stream_read_available(self._stream) def get_write_available(self): """Return the number of frames that can be written without waiting. :rtype: integer """ return pa.get_stream_write_available(self._stream) # Initialization and Termination def __init__(self): """Initialize PortAudio.""" pa.initialize() self._streams = set() def terminate(self): """Terminates PortAudio. :attention: Be sure to call this method for every instance of this object to release PortAudio resources. """ for stream in self._streams.copy(): stream.close() self._streams = set() pa.terminate() # Utilities def get_sample_size(self, format): """Returns the size (in bytes) for the specified sample `format` (a |PaSampleFormat| constant). :param format: A |PaSampleFormat| constant. :raises ValueError: Invalid specified `format`. :rtype: integer """ return pa.get_sample_size(format) def get_format_from_width(self, 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: A |PaSampleFormat| constant. """ return get_format_from_width(width, unsigned) # Stream Factory def open(self, *args, **kwargs): """Opens a new stream. See constructor for :py:func:`PyAudio.Stream.__init__` for parameter details. :returns: A new :py:class:`PyAudio.Stream` """ stream = PyAudio.Stream(self, *args, **kwargs) self._streams.add(stream) return stream def close(self, stream): """Closes a stream. Use :py:func:`PyAudio.Stream.close` instead. :param stream: An instance of the :py:class:`PyAudio.Stream` object. :raises ValueError: if stream does not exist. """ if stream not in self._streams: raise ValueError(f"Stream {stream} not found") stream.close() def _remove_stream(self, stream): """Removes a stream. (Internal) :param stream: An instance of the :py:class:`PyAudio.Stream` object. """ if stream in self._streams: self._streams.remove(stream) # Host API Inspection def get_host_api_count(self): """Returns the number of available PortAudio Host APIs. :rtype: integer """ return pa.get_host_api_count() def get_default_host_api_info(self): """Returns a dictionary containing the default Host API parameters. The keys of the dictionary mirror the data fields of PortAudio's ``PaHostApiInfo`` structure. :raises IOError: if no default input device is available :rtype: dict """ default_host_api_index = pa.get_default_host_api() return self.get_host_api_info_by_index(default_host_api_index) def get_host_api_info_by_type(self, host_api_type): """Returns a dictionary containing the Host API parameters for the host API specified by the `host_api_type`. The keys of the dictionary mirror the data fields of PortAudio's ``PaHostApiInfo`` structure. :param host_api_type: The desired |PaHostAPI| :raises IOError: for invalid `host_api_type` :rtype: dict """ index = pa.host_api_type_id_to_host_api_index(host_api_type) return self.get_host_api_info_by_index(index) def get_host_api_info_by_index(self, host_api_index): """Returns a dictionary containing the Host API parameters for the host API specified by the `host_api_index`. The keys of the dictionary mirror the data fields of PortAudio's ``PaHostApiInfo`` structure. :param host_api_index: The host api index :raises IOError: for invalid `host_api_index` :rtype: dict """ return self._make_host_api_dictionary( host_api_index, pa.get_host_api_info(host_api_index)) def get_device_info_by_host_api_device_index(self, host_api_index, host_api_device_index): """Returns a dictionary containing the Device parameters for a given Host API's n'th device. The keys of the dictionary mirror the data fields of PortAudio's ``PaDeviceInfo`` structure. :param host_api_index: The Host API index number :param host_api_device_index: The n'th device of the host API :raises IOError: for invalid indices :rtype: dict """ long_method_name = pa.host_api_device_index_to_device_index device_index = long_method_name(host_api_index, host_api_device_index) return self.get_device_info_by_index(device_index) def _make_host_api_dictionary(self, index, host_api_struct): """Creates dictionary like PortAudio's ``PaHostApiInfo`` structure. :rtype: dict """ return { 'index': index, 'structVersion': host_api_struct.structVersion, 'type': host_api_struct.type, 'name': host_api_struct.name, 'deviceCount': host_api_struct.deviceCount, 'defaultInputDevice': host_api_struct.defaultInputDevice, 'defaultOutputDevice': host_api_struct.defaultOutputDevice } # Device Inspection def get_device_count(self): """Returns the number of PortAudio Host APIs. :rtype: integer """ return pa.get_device_count() def is_format_supported(self, rate, input_device=None, input_channels=None, input_format=None, output_device=None, output_channels=None, output_format=None): """Checks if specified device configuration is supported. Returns True if the configuration is supported; raises ValueError otherwise. :param rate: Specifies the desired rate (in Hz) :param input_device: The input device index. Specify ``None`` (default) for half-duplex output-only streams. :param input_channels: The desired number of input channels. Ignored if `input_device` is not specified (or ``None``). :param input_format: PortAudio sample format constant defined in this module :param output_device: The output device index. Specify ``None`` (default) for half-duplex input-only streams. :param output_channels: The desired number of output channels. Ignored if `input_device` is not specified (or ``None``). :param output_format: |PaSampleFormat| constant. :rtype: bool :raises ValueError: tuple containing (error string, |PaErrorCode|). """ if input_device is None and output_device is None: raise ValueError( "Must specify stream format for input, output, or both", paInvalidDevice) kwargs = {} if input_device is not None: kwargs['input_device'] = input_device kwargs['input_channels'] = input_channels kwargs['input_format'] = input_format if output_device is not None: kwargs['output_device'] = output_device kwargs['output_channels'] = output_channels kwargs['output_format'] = output_format return pa.is_format_supported(rate, **kwargs) def get_default_input_device_info(self): """Returns the default input device parameters as a dictionary. The keys of the dictionary mirror the data fields of PortAudio's ``PaDeviceInfo`` structure. :raises IOError: No default input device available. :rtype: dict """ device_index = pa.get_default_input_device() return self.get_device_info_by_index(device_index) def get_default_output_device_info(self): """Returns the default output device parameters as a dictionary. The keys of the dictionary mirror the data fields of PortAudio's ``PaDeviceInfo`` structure. :raises IOError: No default output device available. :rtype: dict """ device_index = pa.get_default_output_device() return self.get_device_info_by_index(device_index) def get_device_info_by_index(self, device_index): """Returns the device parameters for device specified in `device_index` as a dictionary. The keys of the dictionary mirror the data fields of PortAudio's ``PaDeviceInfo`` structure. :param device_index: The device index :raises IOError: Invalid `device_index`. :rtype: dict """ return self._make_device_info_dictionary( device_index, pa.get_device_info(device_index)) def _make_device_info_dictionary(self, index, device_info): """Creates a dictionary like PortAudio's ``PaDeviceInfo`` structure. :rtype: dict """ device_name = device_info.name # Attempt to decode device_name. If we fail to decode, return the raw # bytes and let the caller deal with the encoding. os_encoding = locale.getpreferredencoding(do_setlocale=False) for codec in [os_encoding, "utf-8"]: try: device_name = device_name.decode(codec) break except: pass return {'index': index, 'structVersion': device_info.structVersion, 'name': device_name, 'hostApi': device_info.hostApi, 'maxInputChannels': device_info.maxInputChannels, 'maxOutputChannels': device_info.maxOutputChannels, 'defaultLowInputLatency': device_info.defaultLowInputLatency, 'defaultLowOutputLatency': device_info.defaultLowOutputLatency, 'defaultHighInputLatency': device_info.defaultHighInputLatency, 'defaultHighOutputLatency': device_info.defaultHighOutputLatency, 'defaultSampleRate': device_info.defaultSampleRate} # Host Specific Stream Info if hasattr(pa, 'paMacCoreStreamInfo'): class PaMacCoreStreamInfo(pa.paMacCoreStreamInfo): """PortAudio Host API Specific Stream Info for macOS-specific settings. To configure macOS-specific settings, instantiate this class and pass it as the argument in :py:func:`PyAudio.open` to parameters ``input_host_api_specific_stream_info`` or ``output_host_api_specific_stream_info``. (See :py:func:`PyAudio.Stream.__init__`.) :note: macOS-only. .. |PaMacCoreFlags| replace:: :ref:`PortAudio Mac Core Flags ` .. _PaMacCoreFlags: **PortAudio Mac Core Flags** :py:data:`paMacCoreChangeDeviceParameters`, :py:data:`paMacCoreFailIfConversionRequired`, :py:data:`paMacCoreConversionQualityMin`, :py:data:`paMacCoreConversionQualityMedium`, :py:data:`paMacCoreConversionQualityLow`, :py:data:`paMacCoreConversionQualityHigh`, :py:data:`paMacCoreConversionQualityMax`, :py:data:`paMacCorePlayNice`, :py:data:`paMacCorePro`, :py:data:`paMacCoreMinimizeCPUButPlayNice`, :py:data:`paMacCoreMinimizeCPU` .. attribute:: flags The flags specified to the constructor. :type: |PaMacCoreFlags| .. attribute:: channel_map The channel_map specified to the constructor :type: tuple or None if unspecified """ paMacCoreChangeDeviceParameters = pa.paMacCoreChangeDeviceParameters paMacCoreFailIfConversionRequired = pa.paMacCoreFailIfConversionRequired paMacCoreConversionQualityMin = pa.paMacCoreConversionQualityMin paMacCoreConversionQualityMedium = pa.paMacCoreConversionQualityMedium paMacCoreConversionQualityLow = pa.paMacCoreConversionQualityLow paMacCoreConversionQualityHigh = pa.paMacCoreConversionQualityHigh paMacCoreConversionQualityMax = pa.paMacCoreConversionQualityMax paMacCorePlayNice = pa.paMacCorePlayNice paMacCorePro = pa.paMacCorePro paMacCoreMinimizeCPUButPlayNice = pa.paMacCoreMinimizeCPUButPlayNice paMacCoreMinimizeCPU = pa.paMacCoreMinimizeCPU def __init__(self, flags=None, channel_map=None): """Initialize with macOS setting flags and channel_map. See PortAudio documentation for more details on these parameters. :param flags: |PaMacCoreFlags| OR'ed together. :param channel_map: An array describing the channel mapping. See PortAudio documentation for usage. """ kwargs = {} if flags is not None: kwargs["flags"] = flags if channel_map is not None: kwargs["channel_map"] = channel_map super().__init__(**kwargs) # Deprecated: def get_flags(self): """Returns the flags set at instantiation. Deprecated. :rtype: integer .. deprecated:: 0.2.13 Use :py:attr:`flags` property. """ warnings.warn( "PaMacCoreStreamInfo.get_flags is deprecated. Use the flags " "property instead.", DeprecationWarning, stacklevel=2) return self.flags def get_channel_map(self): """Returns the channel map set at instantiation. Deprecated. :rtype: tuple or None .. deprecated:: 0.2.13 Use :py:attr:`channel_map` property. """ warnings.warn( "PaMacCoreStreamInfo.get_channel_map is deprecated. Use the " "channel_map property instead.", DeprecationWarning, stacklevel=2) return self.channel_map def _get_host_api_stream_object(self): """Returns the underyling stream info. .. :deprecated:: 0.2.13 Use stream_info property. """ warnings.warn( "PaMacCoreStreamInfo._get_host_api_stream_object is " "deprecated. Use this object instance instead.", DeprecationWarning, stacklevel=2) return self # The top-level Stream class is reserved for future API changes. Users should # never instantiate Stream directly. Instead, users must use PyAudio.open() # instead, as documented. # # But for existing code that happens to instantiate Stream directly, this class # issues a warning and maintains backwards-compatibility, for now. In the # future, Stream may be repurposed. class Stream(PyAudio.Stream): """Reserved. Do not instantiate.""" def __init__(self, *args, **kwargs): # Users should never instantiate this class. warnings.warn( "Do not instantiate pyaudio.Stream directly. Use " "pyaudio.PyAudio.open() instead. pyaudio.Stream may change or be " "removed in the future.", DeprecationWarning, stacklevel=2) super().__init__(*args, **kwargs) python-pyaudio-0.2.13/src/pyaudio/device_api.c000066400000000000000000000174631435221326700213030ustar00rootroot00000000000000#include "device_api.h" #include #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" // Wrapper object for the PaDeviceInfo struct. typedef struct { // clang-format off PyObject_HEAD const PaDeviceInfo *device_info; // clang-format on } PyAudioDeviceInfo; // Property getters for PyAudioDeviceInfo: static PyObject *get_structVersion(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->device_info->structVersion); } static PyObject *get_name(PyAudioDeviceInfo *self, void *closure) { if ((!self->device_info) || (self->device_info->name == NULL)) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyBytes_FromString(self->device_info->name); } static PyObject *get_hostApi(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->device_info->hostApi); } static PyObject *get_maxInputChannels(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->device_info->maxInputChannels); } static PyObject *get_maxOutputChannels(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyLong_FromLong(self->device_info->maxOutputChannels); } static PyObject *get_defaultLowInputLatency(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->device_info->defaultLowInputLatency); } static PyObject *get_defaultLowOutputLatency(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->device_info->defaultLowOutputLatency); } static PyObject *get_defaultHighInputLatency(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->device_info->defaultHighInputLatency); } static PyObject *get_defaultHighOutputLatency(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->device_info->defaultHighOutputLatency); } static PyObject *get_defaultSampleRate(PyAudioDeviceInfo *self, void *closure) { if (!self->device_info) { PyErr_SetString(PyExc_AttributeError, "No Device Info available"); return NULL; } return PyFloat_FromDouble(self->device_info->defaultSampleRate); } static int antiset(PyAudioDeviceInfo *self, PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, "Fields read-only: cannot modify values"); return -1; } static void dealloc(PyAudioDeviceInfo *self) { self->device_info = NULL; Py_TYPE(self)->tp_free((PyObject *)self); } static PyGetSetDef get_setters[] = { {"name", (getter)get_name, (setter)antiset, "device name", NULL}, {"structVersion", (getter)get_structVersion, (setter)antiset, "struct version", NULL}, {"hostApi", (getter)get_hostApi, (setter)antiset, "host api index", NULL}, {"maxInputChannels", (getter)get_maxInputChannels, (setter)antiset, "max input channels", NULL}, {"maxOutputChannels", (getter)get_maxOutputChannels, (setter)antiset, "max output channels", NULL}, {"defaultLowInputLatency", (getter)get_defaultLowInputLatency, (setter)antiset, "default low input latency", NULL}, {"defaultLowOutputLatency", (getter)get_defaultLowOutputLatency, (setter)antiset, "default low output latency", NULL}, {"defaultHighInputLatency", (getter)get_defaultHighInputLatency, (setter)antiset, "default high input latency", NULL}, {"defaultHighOutputLatency", (getter)get_defaultHighOutputLatency, (setter)antiset, "default high output latency", NULL}, {"defaultSampleRate", (getter)get_defaultSampleRate, (setter)antiset, "default sample rate", NULL}, {NULL}}; PyTypeObject PyAudioDeviceInfoType = { // clang-format off PyVarObject_HEAD_INIT(NULL, 0) // clang-format on .tp_name = "_portaudio.paDeviceInfo", .tp_basicsize = sizeof(PyAudioDeviceInfo), .tp_itemsize = 0, .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("PortAudio PaDeviceInfo"), .tp_getset = get_setters, .tp_new = PyType_GenericNew, }; // Public Device API functions // Creates and returns a PyAudioDeviceInfo (PyAudioDeviceInfoType). PyObject *PyAudio_GetDeviceInfo(PyObject *self, PyObject *args) { PaDeviceIndex index; if (!PyArg_ParseTuple(args, "i", &index)) { return NULL; } const PaDeviceInfo *pa_device_info = Pa_GetDeviceInfo(index); if (!pa_device_info) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInvalidDevice, "Invalid device info")); return NULL; } PyAudioDeviceInfo *py_device_info = (PyAudioDeviceInfo *)PyObject_New( PyAudioDeviceInfo, &PyAudioDeviceInfoType); py_device_info->device_info = pa_device_info; return (PyObject *)py_device_info; } PyObject *PyAudio_GetDeviceCount(PyObject *self, PyObject *args) { PaDeviceIndex count; if (!PyArg_ParseTuple(args, "")) { return NULL; } count = Pa_GetDeviceCount(); if (count < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", count); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(count)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", count, Pa_GetErrorText(count))); return NULL; } return PyLong_FromLong(count); } PyObject *PyAudio_GetDefaultInputDevice(PyObject *self, PyObject *args) { PaDeviceIndex index; if (!PyArg_ParseTuple(args, "")) { return NULL; } index = Pa_GetDefaultInputDevice(); if (index == paNoDevice) { PyErr_SetString(PyExc_IOError, "No Default Input Device Available"); return NULL; } else if (index < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(index)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } PyObject *PyAudio_GetDefaultOutputDevice(PyObject *self, PyObject *args) { PaDeviceIndex index; if (!PyArg_ParseTuple(args, "")) { return NULL; } index = Pa_GetDefaultOutputDevice(); if (index == paNoDevice) { PyErr_SetString(PyExc_IOError, "No Default Output Device Available"); return NULL; } else if (index < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(index)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } python-pyaudio-0.2.13/src/pyaudio/device_api.h000066400000000000000000000011271435221326700212760ustar00rootroot00000000000000#ifndef PYAUDIO_DEVICE_API_H_ #define PYAUDIO_DEVICE_API_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" // Python object wrapper for PortAudio's PaDeviceInfo struct. extern PyTypeObject PyAudioDeviceInfoType; // Returns a PyAudioDeviceInfoType object PyObject *PyAudio_GetDeviceInfo(PyObject *self, PyObject *args); PyObject *PyAudio_GetDeviceCount(PyObject *self, PyObject *args); PyObject *PyAudio_GetDefaultInputDevice(PyObject *self, PyObject *args); PyObject *PyAudio_GetDefaultOutputDevice(PyObject *self, PyObject *args); #endif // PYAUDIO_DEVICE_API_H_ python-pyaudio-0.2.13/src/pyaudio/host_api.c000066400000000000000000000153231435221326700210120ustar00rootroot00000000000000#include "host_api.h" #include #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" // Wrapper object for the PaHostApiInfo struct. typedef struct { // clang-format off PyObject_HEAD // clang-format on const PaHostApiInfo *api_info; } PyAudioHostApiInfo; // Property getters for PyAudioHostApiInfo: static PyObject *get_structVersion(PyAudioHostApiInfo *self, void *closure) { if ((!self->api_info)) { PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->api_info->structVersion); } static PyObject *get_type(PyAudioHostApiInfo *self, void *closure) { if ((!self->api_info)) { PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong((long)self->api_info->type); } static PyObject *get_name(PyAudioHostApiInfo *self, void *closure) { if ((!self->api_info) || (self->api_info->name == NULL)) { PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyUnicode_FromString(self->api_info->name); } static PyObject *get_deviceCount(PyAudioHostApiInfo *self, void *closure) { if ((!self->api_info)) { PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->api_info->deviceCount); } static PyObject *get_defaultInputDevice(PyAudioHostApiInfo *self, void *closure) { if ((!self->api_info)) { PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->api_info->defaultInputDevice); } static PyObject *get_defaultOutputDevice(PyAudioHostApiInfo *self, void *closure) { if ((!self->api_info)) { PyErr_SetString(PyExc_AttributeError, "No HostApi Info available"); return NULL; } return PyLong_FromLong(self->api_info->defaultOutputDevice); } static int antiset(PyAudioHostApiInfo *self, PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, "Fields read-only: cannot modify values"); return -1; } static void dealloc(PyAudioHostApiInfo *self) { self->api_info = NULL; Py_TYPE(self)->tp_free((PyObject *)self); } static PyGetSetDef get_setters[] = { {"name", (getter)get_name, (setter)antiset, "host api name", NULL}, {"structVersion", (getter)get_structVersion, (setter)antiset, "struct version", NULL}, {"type", (getter)get_type, (setter)antiset, "host api type", NULL}, {"deviceCount", (getter)get_deviceCount, (setter)antiset, "number of devices", NULL}, {"defaultInputDevice", (getter)get_defaultInputDevice, (setter)antiset, "default input device index", NULL}, {"defaultOutputDevice", (getter)get_defaultOutputDevice, (setter)antiset, "default output device index", NULL}, {NULL}}; PyTypeObject PyAudioHostApiInfoType = { // clang-format off PyVarObject_HEAD_INIT(NULL, 0) // clang-format on .tp_name = "_portaudio.paHostApiInfo", .tp_basicsize = sizeof(PyAudioHostApiInfo), .tp_itemsize = 0, .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("PortAudio PaHostApiInfo"), .tp_getset = get_setters, .tp_new = PyType_GenericNew, }; // Public Functions // Creates and returns a PyAudioHostApiInfo (PyAudioHostApiInfoType). PyObject *PyAudio_GetHostApiInfo(PyObject *self, PyObject *args) { PaHostApiIndex index; if (!PyArg_ParseTuple(args, "i", &index)) { return NULL; } const PaHostApiInfo *pa_hostapi_info = Pa_GetHostApiInfo(index); if (!pa_hostapi_info) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInvalidHostApi, "Invalid host api info")); return NULL; } PyAudioHostApiInfo *py_hostapi_info = (PyAudioHostApiInfo *)PyObject_New( PyAudioHostApiInfo, &PyAudioHostApiInfoType); py_hostapi_info->api_info = pa_hostapi_info; return (PyObject *)py_hostapi_info; } PyObject *PyAudio_GetHostApiCount(PyObject *self, PyObject *args) { PaHostApiIndex count; if (!PyArg_ParseTuple(args, "")) { return NULL; } count = Pa_GetHostApiCount(); if (count < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", count); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(count)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", count, Pa_GetErrorText(count))); return NULL; } return PyLong_FromLong(count); } PyObject *PyAudio_GetDefaultHostApi(PyObject *self, PyObject *args) { PaHostApiIndex index; if (!PyArg_ParseTuple(args, "")) { return NULL; } index = Pa_GetDefaultHostApi(); if (index < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(index)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } PyObject *PyAudio_HostApiTypeIdToHostApiIndex(PyObject *self, PyObject *args) { PaHostApiTypeId typeid; PaHostApiIndex index; if (!PyArg_ParseTuple(args, "i", &typeid)) { return NULL; } index = Pa_HostApiTypeIdToHostApiIndex(typeid); if (index < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", index); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(index)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", index, Pa_GetErrorText(index))); return NULL; } return PyLong_FromLong(index); } PyObject *PyAudio_HostApiDeviceIndexToDeviceIndex(PyObject *self, PyObject *args) { PaHostApiIndex apiIndex; int hostApiDeviceindex; PaDeviceIndex devIndex; if (!PyArg_ParseTuple(args, "ii", &apiIndex, &hostApiDeviceindex)) { return NULL; } devIndex = Pa_HostApiDeviceIndexToDeviceIndex(apiIndex, hostApiDeviceindex); if (devIndex < 0) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", devIndex); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(devIndex)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", devIndex, Pa_GetErrorText(devIndex))); return NULL; } return PyLong_FromLong(devIndex); } python-pyaudio-0.2.13/src/pyaudio/host_api.h000066400000000000000000000013671435221326700210220ustar00rootroot00000000000000// HostAPI related functions. #ifndef PYAUDIO_HOST_API_H_ #define PYAUDIO_HOST_API_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" // Python object wrapper for PortAudio's PaHostApi struct. extern PyTypeObject PyAudioHostApiInfoType; // Returns a PyAudioHostApiInfoType object PyObject *PyAudio_GetHostApiInfo(PyObject *self, PyObject *args); PyObject *PyAudio_GetHostApiCount(PyObject *self, PyObject *args); PyObject *PyAudio_GetDefaultHostApi(PyObject *self, PyObject *args); PyObject *PyAudio_HostApiTypeIdToHostApiIndex(PyObject *self, PyObject *args); PyObject *PyAudio_HostApiDeviceIndexToDeviceIndex(PyObject *self, PyObject *args); #endif // PYAUDIO_HOST_API_H_ python-pyaudio-0.2.13/src/pyaudio/init.c000066400000000000000000000020651435221326700201460ustar00rootroot00000000000000#include "init.h" #include #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" PyObject *PyAudio_Initialize(PyObject *self, PyObject *args) { int err; // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_Initialize(); Py_END_ALLOW_THREADS // clang-format on if (err != paNoError) { // clang-format off Py_BEGIN_ALLOW_THREADS Pa_Terminate(); Py_END_ALLOW_THREADS // clang-format on #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } Py_INCREF(Py_None); return Py_None; } PyObject *PyAudio_Terminate(PyObject *self, PyObject *args) { // clang-format off Py_BEGIN_ALLOW_THREADS Pa_Terminate(); Py_END_ALLOW_THREADS // clang-format on Py_INCREF(Py_None); return Py_None; } python-pyaudio-0.2.13/src/pyaudio/init.h000066400000000000000000000003751435221326700201550ustar00rootroot00000000000000#ifndef INIT_H_ #define INIT_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" PyObject *PyAudio_Initialize(PyObject *self, PyObject *args); PyObject *PyAudio_Terminate(PyObject *self, PyObject *args); #endif // INIT_H python-pyaudio-0.2.13/src/pyaudio/mac_core_stream_info.c000066400000000000000000000103301435221326700233330ustar00rootroot00000000000000#ifdef MACOS #include "mac_core_stream_info.h" #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" #include "pa_mac_core.h" static void cleanup(PyAudioMacCoreStreamInfo *self) { if (self->channel_map != NULL) { free(self->channel_map); self->channel_map = NULL; } self->flags = paMacCorePlayNice; self->channel_map_size = 0; } static void dealloc(PyAudioMacCoreStreamInfo *self) { cleanup(self); Py_TYPE(self)->tp_free((PyObject *)self); } static int init(PyObject *_self, PyObject *args, PyObject *kwargs) { PyAudioMacCoreStreamInfo *self = (PyAudioMacCoreStreamInfo *)_self; // Init struct with default values. self->flags = paMacCorePlayNice; self->channel_map = NULL; self->channel_map_size = 0; PaMacCore_SetupStreamInfo(&self->stream_info, self->flags); PyObject *channel_map = NULL; static char *kwlist[] = {"flags", "channel_map", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iO", kwlist, &self->flags, &channel_map)) { return -1; } if (channel_map != NULL) { if (!PyTuple_Check(channel_map)) { PyErr_SetString(PyExc_ValueError, "Channel map must be a tuple"); return -1; } // generate SInt32 channel_map self->channel_map_size = (int)PyTuple_Size(channel_map); self->channel_map = (SInt32 *)malloc(sizeof(SInt32) * self->channel_map_size); if (self->channel_map == NULL) { PyErr_SetString(PyExc_SystemError, "Out of memory"); cleanup(self); return -1; } for (int i = 0; i < self->channel_map_size; ++i) { PyObject *element = PyTuple_GetItem(channel_map, i); if (element == NULL) { PyErr_SetString(PyExc_ValueError, "Internal error: out of bounds index"); cleanup(self); return -1; } if (!PyNumber_Check(element)) { PyErr_SetString(PyExc_ValueError, "Channel Map must consist of integer elements"); cleanup(self); return -1; } PyObject *long_element = PyNumber_Long(element); self->channel_map[i] = (SInt32)PyLong_AsLong(long_element); Py_DECREF(long_element); } PaMacCore_SetupChannelMap(&self->stream_info, self->channel_map, self->channel_map_size); } return 0; } static PyObject *get_flags(PyAudioMacCoreStreamInfo *self, void *closure) { return PyLong_FromLong(self->flags); } static PyObject *get_channel_map(PyAudioMacCoreStreamInfo *self, void *closure) { if (self->channel_map == NULL || self->channel_map_size == 0) { Py_INCREF(Py_None); return Py_None; } PyObject *channel_map_tuple = PyTuple_New(self->channel_map_size); for (int i = 0; i < self->channel_map_size; ++i) { PyObject *element = PyLong_FromLong(self->channel_map[i]); if (!element) { PyErr_SetString(PyExc_SystemError, "Invalid channel map"); return NULL; } if (PyTuple_SetItem(channel_map_tuple, i, PyLong_FromLong(self->channel_map[i]))) { // non-zero on error PyErr_SetString(PyExc_SystemError, "Can't create channel map."); return NULL; } } return channel_map_tuple; } static int antiset(PyAudioMacCoreStreamInfo *self, PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, "Fields read-only: cannot modify values"); return -1; } static PyGetSetDef get_setters[] = { {"flags", (getter)get_flags, (setter)antiset, "flags", NULL}, {"channel_map", (getter)get_channel_map, (setter)antiset, "channel map", NULL}, {NULL}}; PyTypeObject PyAudioMacCoreStreamInfoType = { // clang-format off PyVarObject_HEAD_INIT(NULL, 0) // clang-format on .tp_name = "_portaudio.PaMacCoreStreamInfo", .tp_basicsize = sizeof(PyAudioMacCoreStreamInfo), .tp_itemsize = 0, .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = PyDoc_STR("macOS Specific HostAPI configuration"), .tp_getset = get_setters, .tp_init = (initproc)init, .tp_new = PyType_GenericNew, }; #endif // MACOS python-pyaudio-0.2.13/src/pyaudio/mac_core_stream_info.h000066400000000000000000000011021435221326700233350ustar00rootroot00000000000000// Python wrapper for PaMacCoreStreamInfo (macOS host-specific API). #ifndef MAC_CORE_STREAM_INFO_H_ #define MAC_CORE_STREAM_INFO_H_ #ifdef MACOS #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" #include "pa_mac_core.h" typedef struct { // clang-format off PyObject_HEAD // clang-format on PaMacCoreStreamInfo stream_info; int flags; SInt32 *channel_map; int channel_map_size; } PyAudioMacCoreStreamInfo; extern PyTypeObject PyAudioMacCoreStreamInfoType; #endif // MACOS #endif // MAC_CORE_STREAM_INFO_H_ python-pyaudio-0.2.13/src/pyaudio/main.c000066400000000000000000000270601435221326700201310ustar00rootroot00000000000000/** * PyAudio: Python Bindings for PortAudio. * * 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 "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" #include "device_api.h" #include "host_api.h" #include "init.h" #include "mac_core_stream_info.h" #include "misc.h" #include "stream.h" #include "stream_io.h" #include "stream_lifecycle.h" static PyMethodDef exported_functions[] = { // init.h {"initialize", PyAudio_Initialize, METH_VARARGS, "Initializes PortAudio"}, {"terminate", PyAudio_Terminate, METH_VARARGS, "Terminates PortAudio"}, // misc.h {"get_sample_size", PyAudio_GetSampleSize, METH_VARARGS, "Returns sample size of a format in bytes"}, {"is_format_supported", (PyCFunction)PyAudio_IsFormatSupported, METH_VARARGS | METH_KEYWORDS, "Returns whether format is supported"}, {"get_version", PyAudio_GetPortAudioVersion, METH_VARARGS, "PortAudio version"}, {"get_version_text", PyAudio_GetPortAudioVersionText, METH_VARARGS, "PortAudio version text"}, // host_api.h {"get_host_api_count", PyAudio_GetHostApiCount, METH_VARARGS, "Returns the number of Host APIs"}, {"get_default_host_api", PyAudio_GetDefaultHostApi, METH_VARARGS, "Returns the default Host API index"}, {"host_api_type_id_to_host_api_index", PyAudio_HostApiTypeIdToHostApiIndex, METH_VARARGS, "Returns the Host API index for given a PortAudio Host API Type ID"}, {"host_api_device_index_to_device_index", PyAudio_HostApiDeviceIndexToDeviceIndex, METH_VARARGS, "Returns a Host API-specific device index to PortAudio device index"}, {"get_host_api_info", PyAudio_GetHostApiInfo, METH_VARARGS, "Returns an object with information about the Host API"}, // device_api.h {"get_device_count", PyAudio_GetDeviceCount, METH_VARARGS, "Returns the number of available devices"}, {"get_default_input_device", PyAudio_GetDefaultInputDevice, METH_VARARGS, "Returns the default input device index"}, {"get_default_output_device", PyAudio_GetDefaultOutputDevice, METH_VARARGS, "Returns the default output device index"}, {"get_device_info", PyAudio_GetDeviceInfo, METH_VARARGS, "Returns an object with device properties"}, // stream.h {"get_stream_time", PyAudio_GetStreamTime, METH_VARARGS, "Returns the number of seconds for the stream. See PortAudio docs for " "details."}, {"get_stream_cpu_load", PyAudio_GetStreamCpuLoad, METH_VARARGS, "Returns the stream's CPU load (always 0 for blocking mode)"}, // stream_lifecycle.h (and stream.h) {"open", (PyCFunction)PyAudio_OpenStream, METH_VARARGS | METH_KEYWORDS, "Opens a PortAudio stream"}, {"close", PyAudio_CloseStream, METH_VARARGS, "Closes a PortAudio stream"}, {"start_stream", PyAudio_StartStream, METH_VARARGS, "Starts the stream"}, {"stop_stream", PyAudio_StopStream, METH_VARARGS, "Stops (pauses) the stream"}, {"abort_stream", PyAudio_AbortStream, METH_VARARGS, "Aborts the stream"}, {"is_stream_stopped", PyAudio_IsStreamStopped, METH_VARARGS, "Returns whether the stream is stopped"}, {"is_stream_active", PyAudio_IsStreamActive, METH_VARARGS, "Returns whether the stream is active"}, // stream_io.h (and stream.h) {"write_stream", PyAudio_WriteStream, METH_VARARGS, "Write samples to stream"}, {"read_stream", PyAudio_ReadStream, METH_VARARGS, "Read samples from stream"}, {"get_stream_write_available", PyAudio_GetStreamWriteAvailable, METH_VARARGS, "Returns the number of frames that can be written without waiting"}, {"get_stream_read_available", PyAudio_GetStreamReadAvailable, METH_VARARGS, "Returns the number of frames that can be read without waiting"}, {NULL, NULL, 0, NULL}}; #if PY_MAJOR_VERSION >= 3 #define ERROR_INIT NULL #else #define ERROR_INIT /**/ #endif #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { // PyModuleDef_HEAD_INIT, "_portaudio", NULL, -1, exported_functions, NULL, NULL, NULL, NULL}; #endif PyMODINIT_FUNC #if PY_MAJOR_VERSION >= 3 PyInit__portaudio(void) #else init_portaudio(void) #endif { PyObject *m; #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION <= 6 // Deprecated since Python 3.7; now called by Py_Initialize(). PyEval_InitThreads(); #endif if (PyType_Ready(&PyAudioStreamType) < 0) { return ERROR_INIT; } if (PyType_Ready(&PyAudioDeviceInfoType) < 0) { return ERROR_INIT; } if (PyType_Ready(&PyAudioHostApiInfoType) < 0) { return ERROR_INIT; } #ifdef MACOS if (PyType_Ready(&PyAudioMacCoreStreamInfoType) < 0) { return ERROR_INIT; } #endif #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&moduledef); #else m = Py_InitModule("_portaudio", exported_functions); #endif Py_INCREF(&PyAudioStreamType); Py_INCREF(&PyAudioDeviceInfoType); Py_INCREF(&PyAudioHostApiInfoType); #ifdef MACOS Py_INCREF(&PyAudioMacCoreStreamInfoType); PyModule_AddObject(m, "paMacCoreStreamInfo", (PyObject *)&PyAudioMacCoreStreamInfoType); #endif // Add PortAudio constants // Host APIs PyModule_AddIntConstant(m, "paInDevelopment", paInDevelopment); PyModule_AddIntConstant(m, "paDirectSound", paDirectSound); PyModule_AddIntConstant(m, "paMME", paMME); PyModule_AddIntConstant(m, "paASIO", paASIO); PyModule_AddIntConstant(m, "paSoundManager", paSoundManager); PyModule_AddIntConstant(m, "paCoreAudio", paCoreAudio); PyModule_AddIntConstant(m, "paOSS", paOSS); PyModule_AddIntConstant(m, "paALSA", paALSA); PyModule_AddIntConstant(m, "paAL", paAL); PyModule_AddIntConstant(m, "paBeOS", paBeOS); PyModule_AddIntConstant(m, "paWDMKS", paWDMKS); PyModule_AddIntConstant(m, "paJACK", paJACK); PyModule_AddIntConstant(m, "paWASAPI", paWASAPI); PyModule_AddIntConstant(m, "paNoDevice", paNoDevice); // Formats PyModule_AddIntConstant(m, "paFloat32", paFloat32); PyModule_AddIntConstant(m, "paInt32", paInt32); PyModule_AddIntConstant(m, "paInt24", paInt24); PyModule_AddIntConstant(m, "paInt16", paInt16); PyModule_AddIntConstant(m, "paInt8", paInt8); PyModule_AddIntConstant(m, "paUInt8", paUInt8); PyModule_AddIntConstant(m, "paCustomFormat", paCustomFormat); // Error codes PyModule_AddIntConstant(m, "paNoError", paNoError); PyModule_AddIntConstant(m, "paNotInitialized", paNotInitialized); PyModule_AddIntConstant(m, "paUnanticipatedHostError", paUnanticipatedHostError); PyModule_AddIntConstant(m, "paInvalidChannelCount", paInvalidChannelCount); PyModule_AddIntConstant(m, "paInvalidSampleRate", paInvalidSampleRate); PyModule_AddIntConstant(m, "paInvalidDevice", paInvalidDevice); PyModule_AddIntConstant(m, "paInvalidFlag", paInvalidFlag); PyModule_AddIntConstant(m, "paSampleFormatNotSupported", paSampleFormatNotSupported); PyModule_AddIntConstant(m, "paBadIODeviceCombination", paBadIODeviceCombination); PyModule_AddIntConstant(m, "paInsufficientMemory", paInsufficientMemory); PyModule_AddIntConstant(m, "paBufferTooBig", paBufferTooBig); PyModule_AddIntConstant(m, "paBufferTooSmall", paBufferTooSmall); PyModule_AddIntConstant(m, "paNullCallback", paNullCallback); PyModule_AddIntConstant(m, "paBadStreamPtr", paBadStreamPtr); PyModule_AddIntConstant(m, "paTimedOut", paTimedOut); PyModule_AddIntConstant(m, "paInternalError", paInternalError); PyModule_AddIntConstant(m, "paDeviceUnavailable", paDeviceUnavailable); PyModule_AddIntConstant(m, "paIncompatibleHostApiSpecificStreamInfo", paIncompatibleHostApiSpecificStreamInfo); PyModule_AddIntConstant(m, "paStreamIsStopped", paStreamIsStopped); PyModule_AddIntConstant(m, "paStreamIsNotStopped", paStreamIsNotStopped); PyModule_AddIntConstant(m, "paInputOverflowed", paInputOverflowed); PyModule_AddIntConstant(m, "paOutputUnderflowed", paOutputUnderflowed); PyModule_AddIntConstant(m, "paHostApiNotFound", paHostApiNotFound); PyModule_AddIntConstant(m, "paInvalidHostApi", paInvalidHostApi); PyModule_AddIntConstant(m, "paCanNotReadFromACallbackStream", paCanNotReadFromACallbackStream); PyModule_AddIntConstant(m, "paCanNotWriteToACallbackStream", paCanNotWriteToACallbackStream); PyModule_AddIntConstant(m, "paCanNotReadFromAnOutputOnlyStream", paCanNotReadFromAnOutputOnlyStream); PyModule_AddIntConstant(m, "paCanNotWriteToAnInputOnlyStream", paCanNotWriteToAnInputOnlyStream); PyModule_AddIntConstant(m, "paIncompatibleStreamHostApi", paIncompatibleStreamHostApi); // Callback constants PyModule_AddIntConstant(m, "paContinue", paContinue); PyModule_AddIntConstant(m, "paComplete", paComplete); PyModule_AddIntConstant(m, "paAbort", paAbort); // Callback status flags PyModule_AddIntConstant(m, "paInputUnderflow", paInputUnderflow); PyModule_AddIntConstant(m, "paInputOverflow", paInputOverflow); PyModule_AddIntConstant(m, "paOutputUnderflow", paOutputUnderflow); PyModule_AddIntConstant(m, "paOutputOverflow", paOutputOverflow); PyModule_AddIntConstant(m, "paPrimingOutput", paPrimingOutput); // Misc PyModule_AddIntConstant(m, "paFramesPerBufferUnspecified", paFramesPerBufferUnspecified); #ifdef MACOS PyModule_AddIntConstant(m, "paMacCoreChangeDeviceParameters", paMacCoreChangeDeviceParameters); PyModule_AddIntConstant(m, "paMacCoreFailIfConversionRequired", paMacCoreFailIfConversionRequired); PyModule_AddIntConstant(m, "paMacCoreConversionQualityMin", paMacCoreConversionQualityMin); PyModule_AddIntConstant(m, "paMacCoreConversionQualityMedium", paMacCoreConversionQualityMedium); PyModule_AddIntConstant(m, "paMacCoreConversionQualityLow", paMacCoreConversionQualityLow); PyModule_AddIntConstant(m, "paMacCoreConversionQualityHigh", paMacCoreConversionQualityHigh); PyModule_AddIntConstant(m, "paMacCoreConversionQualityMax", paMacCoreConversionQualityMax); PyModule_AddIntConstant(m, "paMacCorePlayNice", paMacCorePlayNice); PyModule_AddIntConstant(m, "paMacCorePro", paMacCorePro); PyModule_AddIntConstant(m, "paMacCoreMinimizeCPUButPlayNice", paMacCoreMinimizeCPUButPlayNice); PyModule_AddIntConstant(m, "paMacCoreMinimizeCPU", paMacCoreMinimizeCPU); #endif #if PY_MAJOR_VERSION >= 3 return m; #endif } python-pyaudio-0.2.13/src/pyaudio/misc.c000066400000000000000000000057651435221326700201500ustar00rootroot00000000000000#include "misc.h" #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" PyObject *PyAudio_GetPortAudioVersion(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "")) { return NULL; } return PyLong_FromLong(Pa_GetVersion()); } PyObject *PyAudio_GetPortAudioVersionText(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "")) { return NULL; } return PyUnicode_FromString(Pa_GetVersionText()); } PyObject *PyAudio_GetSampleSize(PyObject *self, PyObject *args) { PaSampleFormat format; int size_in_bytes; if (!PyArg_ParseTuple(args, "k", &format)) { return NULL; } size_in_bytes = Pa_GetSampleSize(format); if (size_in_bytes < 0) { PyErr_SetObject( PyExc_ValueError, Py_BuildValue("(s,i)", Pa_GetErrorText(size_in_bytes), size_in_bytes)); return NULL; } return PyLong_FromLong(size_in_bytes); } PyObject *PyAudio_IsFormatSupported(PyObject *self, PyObject *args, PyObject *kwargs) { // clang-format off static char *kwlist[] = { "sample_rate", "input_device", "input_channels", "input_format", "output_device", "output_channels", "output_format", NULL }; // clang-format on int input_device, input_channels; int output_device, output_channels; float sample_rate; PaStreamParameters inputParams; PaStreamParameters outputParams; PaSampleFormat input_format, output_format; PaError error; input_device = input_channels = output_device = output_channels = -1; input_format = output_format = -1; // clang-format off if (!PyArg_ParseTupleAndKeywords(args, kwargs, "f|iikiik", kwlist, &sample_rate, &input_device, &input_channels, &input_format, &output_device, &output_channels, &output_format)) { return NULL; } // clang-format on if (!(input_device < 0)) { inputParams.device = input_device; inputParams.channelCount = input_channels; inputParams.sampleFormat = input_format; inputParams.suggestedLatency = 0; inputParams.hostApiSpecificStreamInfo = NULL; } if (!(output_device < 0)) { outputParams.device = output_device; outputParams.channelCount = output_channels; outputParams.sampleFormat = output_format; outputParams.suggestedLatency = 0; outputParams.hostApiSpecificStreamInfo = NULL; } error = Pa_IsFormatSupported((input_device < 0) ? NULL : &inputParams, (output_device < 0) ? NULL : &outputParams, sample_rate); if (error == paFormatIsSupported) { Py_INCREF(Py_True); return Py_True; } else { PyErr_SetObject(PyExc_ValueError, Py_BuildValue("(s,i)", Pa_GetErrorText(error), error)); return NULL; } } python-pyaudio-0.2.13/src/pyaudio/misc.h000066400000000000000000000007201435221326700201370ustar00rootroot00000000000000#ifndef MISC_H_ #define MISC_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" PyObject *PyAudio_GetPortAudioVersion(PyObject *self, PyObject *args); PyObject *PyAudio_GetPortAudioVersionText(PyObject *self, PyObject *args); PyObject *PyAudio_GetSampleSize(PyObject *self, PyObject *args); PyObject *PyAudio_IsFormatSupported(PyObject *self, PyObject *args, PyObject *kwargs); #endif // MISC_H_ python-pyaudio-0.2.13/src/pyaudio/stream.c000066400000000000000000000146251435221326700205030ustar00rootroot00000000000000#include "stream.h" #include #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" static void dealloc(PyAudioStream *self) { PyAudioStream_Cleanup(self); Py_TYPE(self)->tp_free((PyObject *)self); } static PyObject *get_structVersion(PyAudioStream *self, void *closure) { if (!PyAudioStream_IsOpen(self)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } const PaStreamInfo *stream_info = Pa_GetStreamInfo(self->context.stream); if (!stream_info) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInternalError, "Could not get stream information")); return NULL; } return PyLong_FromLong(stream_info->structVersion); } static PyObject *get_inputLatency(PyAudioStream *self, void *closure) { if (!PyAudioStream_IsOpen(self)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } const PaStreamInfo *stream_info = Pa_GetStreamInfo(self->context.stream); if (!stream_info) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInternalError, "Could not get stream information")); return NULL; } return PyFloat_FromDouble(stream_info->inputLatency); } static PyObject *get_outputLatency(PyAudioStream *self, void *closure) { if (!PyAudioStream_IsOpen(self)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } const PaStreamInfo *stream_info = Pa_GetStreamInfo(self->context.stream); if (!stream_info) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInternalError, "Could not get stream information")); return NULL; } return PyFloat_FromDouble(stream_info->outputLatency); } static PyObject *get_sampleRate(PyAudioStream *self, void *closure) { if (!PyAudioStream_IsOpen(self)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } const PaStreamInfo *stream_info = Pa_GetStreamInfo(self->context.stream); if (!stream_info) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInternalError, "Could not get stream information")); return NULL; } return PyFloat_FromDouble(stream_info->sampleRate); } static int antiset(PyAudioStream *self, PyObject *value, void *closure) { /* read-only: do not allow users to change values */ PyErr_SetString(PyExc_AttributeError, "Fields read-only: cannot modify values"); return -1; } static PyGetSetDef get_setters[] = {{"structVersion", (getter)get_structVersion, (setter)antiset, "struct version", NULL}, {"inputLatency", (getter)get_inputLatency, (setter)antiset, "input latency", NULL}, {"outputLatency", (getter)get_outputLatency, (setter)antiset, "output latency", NULL}, {"sampleRate", (getter)get_sampleRate, (setter)antiset, "sample rate", NULL}, {NULL}}; PyTypeObject PyAudioStreamType = { // clang-format off PyVarObject_HEAD_INIT(NULL, 0) // clang-format on .tp_name = "_portaudio.Stream", .tp_basicsize = sizeof(PyAudioStream), .tp_itemsize = 0, .tp_dealloc = (destructor)dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("PyAudio Stream"), .tp_getset = get_setters, .tp_new = PyType_GenericNew, }; int PyAudioStream_IsOpen(PyAudioStream *stream) { return (stream) && (stream->context.stream != NULL); } PyAudioStream *PyAudioStream_Create(void) { PyAudioStream *stream = (PyAudioStream *)PyObject_New(PyAudioStream, &PyAudioStreamType); if (!stream) { return NULL; } memset(&(stream->context), 0, sizeof(struct StreamContext)); return stream; } void PyAudioStream_Cleanup(PyAudioStream *stream) { // Note that this function may be called multiple times on the same stream. // For example, stream_lifecycle.c may call this when the user closes the // stream, and Python may call it again during deallocation, i.e., when the // stream Python object's reference count reaches 0. if (stream->context.stream != NULL) { // clang-format off Py_BEGIN_ALLOW_THREADS Pa_CloseStream(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on stream->context.stream = NULL; } if (stream->context.callback != NULL) { Py_XDECREF(stream->context.callback); stream->context.callback = NULL; } // Just in case, zero out the entire struct. memset(&(stream->context), 0, sizeof(struct StreamContext)); } PyObject *PyAudio_GetStreamTime(PyObject *self, PyObject *args) { double time; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS time = Pa_GetStreamTime(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on if (time == 0) { PyAudioStream_Cleanup(stream); PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInternalError, "Internal Error")); return NULL; } return PyFloat_FromDouble(time); } PyObject *PyAudio_GetStreamCpuLoad(PyObject *self, PyObject *args) { double cpuload; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS cpuload = Pa_GetStreamCpuLoad(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on return PyFloat_FromDouble(cpuload); } python-pyaudio-0.2.13/src/pyaudio/stream.h000066400000000000000000000025051435221326700205020ustar00rootroot00000000000000#ifndef STREAM_H_ #define STREAM_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" typedef struct { // clang-format off PyObject_HEAD // clang-format on struct StreamContext { // PortAudio stream object. NULL when the stream is closed. PaStream *stream; // User audio callback routine, for when using callback mode. // NULL otherwise. PyObject *callback; // Frame size, in bytes, for input and output. Equal to // num channels x bytes per sample. unsigned int frame_size; // Main thread ID. long main_thread_id; } context; } PyAudioStream; extern PyTypeObject PyAudioStreamType; // "Internal" utilities for other stream_*.c modules. // Creates a PyAudioStream and zeros out the fields. Returns NULL if memory // allocation fails. PyAudioStream *PyAudioStream_Create(void); // Closes the PortAudio stream (if open) and garbage collects the fields within // a PyAudioStream. May be called multiple times on the same stream. void PyAudioStream_Cleanup(PyAudioStream *stream); // Returns whether the stream is open. int PyAudioStream_IsOpen(PyAudioStream *stream); // Exported functions. PyObject *PyAudio_GetStreamTime(PyObject *self, PyObject *args); PyObject *PyAudio_GetStreamCpuLoad(PyObject *self, PyObject *args); #endif // STREAM_H_ python-pyaudio-0.2.13/src/pyaudio/stream_io.c000066400000000000000000000250321435221326700211640ustar00rootroot00000000000000#include "stream_io.h" #include #include #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" #include "stream.h" int PyAudioStream_CallbackCFunc(const void *input, void *output, unsigned long frame_count, const PaStreamCallbackTimeInfo *time_info, PaStreamCallbackFlags status_flags, void *user_data) { PyGILState_STATE _state = PyGILState_Ensure(); #ifdef VERBOSE if (status_flags != 0) { printf("Status flag set: "); if (status_flags & paInputUnderflow) { printf("input underflow!\n"); } if (status_flags & paInputOverflow) { printf("input overflow!\n"); } if (status_flags & paOutputUnderflow) { printf("output underflow!\n"); } if (status_flags & paOutputUnderflow) { printf("output overflow!\n"); } if (status_flags & paPrimingOutput) { printf("priming output!\n"); } } #endif int return_val = paAbort; PyAudioStream *stream = (PyAudioStream *)user_data; PyObject *py_callback = stream->context.callback; unsigned int bytes_per_frame = stream->context.frame_size; long main_thread_id = stream->context.main_thread_id; // Prepare arguments for calling the python callback: PyObject *py_frame_count = PyLong_FromUnsignedLong(frame_count); // clang-format off PyObject *py_time_info = Py_BuildValue("{s:d,s:d,s:d}", "input_buffer_adc_time", time_info->inputBufferAdcTime, "current_time", time_info->currentTime, "output_buffer_dac_time", time_info->outputBufferDacTime); // clang-format on PyObject *py_status_flags = PyLong_FromUnsignedLong(status_flags); PyObject *py_input_samples; if (input != NULL) { py_input_samples = PyBytes_FromStringAndSize(input, bytes_per_frame * frame_count); } else { // Output stream, so provide None to the callback. Py_INCREF(Py_None); py_input_samples = Py_None; } PyObject *callback_result = PyObject_CallFunctionObjArgs( py_callback, py_input_samples, py_frame_count, py_time_info, py_status_flags, NULL); if (callback_result == NULL) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error message: Could not call callback function\n"); #endif PyObject *err = PyErr_Occurred(); if (err) { PyThreadState_SetAsyncExc(main_thread_id, err); // Print out a stack trace to help debugging. // TODO: make VERBOSE a runtime flag so users can control // the amount of logging. PyErr_Print(); } goto end; } // Parse the callback's response, which should be the samples to playback (if // output stream; ignored otherwise) and the desired next stream state // (paContinue, pAbort, or paComplete): const char *samples_for_output; Py_ssize_t output_len; // clang-format off if (!PyArg_ParseTuple(callback_result, "z#i", &samples_for_output, &output_len, &return_val)) { // clang-format on #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error message: Could not parse callback return value\n"); #endif PyObject *err = PyErr_Occurred(); if (err) { PyThreadState_SetAsyncExc(main_thread_id, err); // Print out a stack trace to help debugging. // TODO: make VERBOSE a runtime flag so users can control // the amount of logging. PyErr_Print(); } Py_XDECREF(callback_result); return_val = paAbort; // Quit the callback loop goto end; } if ((return_val != paComplete) && (return_val != paAbort) && (return_val != paContinue)) { PyErr_SetString(PyExc_ValueError, "Invalid PaStreamCallbackResult from callback"); PyThreadState_SetAsyncExc(main_thread_id, PyErr_Occurred()); PyErr_Print(); Py_XDECREF(callback_result); return_val = paAbort; // Quit the callback loop goto end; } // Copy bytes for playback only if this is an output stream: if (output) { char *output_data = (char *)output; size_t pa_max_num_bytes = bytes_per_frame * frame_count; // Though PyArg_ParseTuple returns the size of samples_for_output in // output_len, a signed Py_ssize_t, that value should never be negative. assert(output_len >= 0); // Only copy min(output_len, pa_max_num_bytes) bytes. size_t bytes_to_copy = (size_t)output_len < pa_max_num_bytes ? (size_t)output_len : pa_max_num_bytes; if (samples_for_output != NULL && bytes_to_copy > 0) { memcpy(output_data, samples_for_output, bytes_to_copy); } // If callback returned too few frames, pad out the rest of the buffer with // 0s and assume the stream is done (paComplete). if (bytes_to_copy < pa_max_num_bytes) { memset(output_data + bytes_to_copy, 0, pa_max_num_bytes - bytes_to_copy); return_val = paComplete; } } Py_DECREF(callback_result); end: // Decrement py_input_samples at the end, after the memcpy above, in case the // user returns py_input_samples (from the callback) for playback. Py_XDECREF(py_input_samples); Py_XDECREF(py_frame_count); Py_XDECREF(py_time_info); Py_XDECREF(py_status_flags); PyGILState_Release(_state); return return_val; } /************************************************************* * Stream Read/Write *************************************************************/ PyObject *PyAudio_WriteStream(PyObject *self, PyObject *args) { const char *data; Py_ssize_t total_size; int total_frames; int err; int should_throw_exception = 0; PyObject *stream_arg; // clang-format off if (!PyArg_ParseTuple(args, "O!s#i|i", &PyAudioStreamType, &stream_arg, &data, &total_size, &total_frames, &should_throw_exception)) { return NULL; } // clang-format on if (total_frames < 0) { PyErr_SetString(PyExc_ValueError, "Invalid number of frames"); return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_WriteStream(stream->context.stream, data, total_frames); Py_END_ALLOW_THREADS // clang-format on if (err != paNoError) { if (err == paOutputUnderflowed) { if (should_throw_exception) { goto error; } } else goto error; } Py_INCREF(Py_None); return Py_None; error: PyAudioStream_Cleanup(stream); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } PyObject *PyAudio_ReadStream(PyObject *self, PyObject *args) { int err; int total_frames; int should_raise_exception = 0; PyObject *stream_arg; // clang-format off if (!PyArg_ParseTuple(args, "O!i|i", &PyAudioStreamType, &stream_arg, &total_frames, &should_raise_exception)) { return NULL; } // clang-format on if (total_frames < 0) { PyErr_SetString(PyExc_ValueError, "Invalid number of frames"); return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } int num_bytes = total_frames * stream->context.frame_size; #ifdef VERBOSE fprintf(stderr, "Allocating %d bytes\n", num_bytes); #endif PyObject *rv = PyBytes_FromStringAndSize(NULL, num_bytes); short *sample_block = (short *)PyBytes_AsString(rv); if (sample_block == NULL) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInsufficientMemory, "Out of memory")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_ReadStream(stream->context.stream, sample_block, total_frames); Py_END_ALLOW_THREADS // clang-format on if (err != paNoError) { if (err == paInputOverflowed) { if (should_raise_exception) { goto error; } } else { goto error; } } return rv; error: PyAudioStream_Cleanup(stream); Py_XDECREF(rv); PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif return NULL; } PyObject *PyAudio_GetStreamWriteAvailable(PyObject *self, PyObject *args) { signed long frames; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS frames = Pa_GetStreamWriteAvailable(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on return PyLong_FromLong(frames); } PyObject *PyAudio_GetStreamReadAvailable(PyObject *self, PyObject *args) { signed long frames; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS frames = Pa_GetStreamReadAvailable(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on return PyLong_FromLong(frames); } python-pyaudio-0.2.13/src/pyaudio/stream_io.h000066400000000000000000000013641435221326700211730ustar00rootroot00000000000000#ifndef STREAM_IO_H_ #define STREAM_IO_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" int PyAudioStream_CallbackCFunc(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); PyObject *PyAudio_WriteStream(PyObject *self, PyObject *args); PyObject *PyAudio_ReadStream(PyObject *self, PyObject *args); PyObject *PyAudio_GetStreamWriteAvailable(PyObject *self, PyObject *args); PyObject *PyAudio_GetStreamReadAvailable(PyObject *self, PyObject *args); #endif // STREAM_IO_H python-pyaudio-0.2.13/src/pyaudio/stream_lifecycle.c000066400000000000000000000333111435221326700225130ustar00rootroot00000000000000#include "stream_lifecycle.h" #include #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" #include "portaudio.h" #include "mac_core_stream_info.h" #include "stream.h" #include "stream_io.h" #define DEFAULT_FRAMES_PER_BUFFER paFramesPerBufferUnspecified PyObject *PyAudio_OpenStream(PyObject *self, PyObject *args, PyObject *kwargs) { int rate, channels; int input_device_index = -1; int output_device_index = -1; PyObject *input_device_index_arg = NULL; PyObject *output_device_index_arg = NULL; PyObject *stream_callback = NULL; PaSampleFormat format; PaError err; PyObject *input_device_index_long; PyObject *output_device_index_long; static char *kwlist[] = {"rate", "channels", "format", "input", "output", "input_device_index", "output_device_index", "frames_per_buffer", "input_host_api_specific_stream_info", "output_host_api_specific_stream_info", "stream_callback", NULL}; #ifdef MACOS PyAudioMacCoreStreamInfo *input_host_specific_stream_info = NULL; PyAudioMacCoreStreamInfo *output_host_specific_stream_info = NULL; #else /* mostly ignored...*/ PyObject *input_host_specific_stream_info = NULL; PyObject *output_host_specific_stream_info = NULL; #endif /* default to neither output nor input */ int input = 0; int output = 0; int frames_per_buffer = DEFAULT_FRAMES_PER_BUFFER; // clang-format off if (!PyArg_ParseTupleAndKeywords(args, kwargs, #ifdef MACOS "iik|iiOOiO!O!O", #else "iik|iiOOiOOO", #endif kwlist, &rate, &channels, &format, &input, &output, &input_device_index_arg, &output_device_index_arg, &frames_per_buffer, #ifdef MACOS &PyAudioMacCoreStreamInfoType, #endif &input_host_specific_stream_info, #ifdef MACOS &PyAudioMacCoreStreamInfoType, #endif &output_host_specific_stream_info, &stream_callback)) { return NULL; } // clang-format on if (stream_callback && (PyCallable_Check(stream_callback) == 0)) { PyErr_SetString(PyExc_TypeError, "stream_callback must be callable"); return NULL; } if ((input_device_index_arg == NULL) || (input_device_index_arg == Py_None)) { #ifdef VERBOSE printf("Using default input device\n"); #endif input_device_index = -1; } else { if (!PyNumber_Check(input_device_index_arg)) { PyErr_SetString(PyExc_ValueError, "input_device_index must be integer (or None)"); return NULL; } input_device_index_long = PyNumber_Long(input_device_index_arg); input_device_index = (int)PyLong_AsLong(input_device_index_long); Py_DECREF(input_device_index_long); #ifdef VERBOSE printf("Using input device index number: %d\n", input_device_index); #endif } if ((output_device_index_arg == NULL) || (output_device_index_arg == Py_None)) { #ifdef VERBOSE printf("Using default output device\n"); #endif output_device_index = -1; } else { if (!PyNumber_Check(output_device_index_arg)) { PyErr_SetString(PyExc_ValueError, "output_device_index must be integer (or None)"); return NULL; } output_device_index_long = PyNumber_Long(output_device_index_arg); output_device_index = (int)PyLong_AsLong(output_device_index_long); Py_DECREF(output_device_index_long); #ifdef VERBOSE printf("Using output device index number: %d\n", output_device_index); #endif } if (input == 0 && output == 0) { PyErr_SetString(PyExc_ValueError, "Must specify either input or output"); return NULL; } if (channels < 1) { PyErr_SetString(PyExc_ValueError, "Invalid audio channels"); return NULL; } PaStreamParameters output_parameters; if (output) { if (output_device_index < 0) { output_parameters.device = Pa_GetDefaultOutputDevice(); } else { output_parameters.device = output_device_index; } /* final check -- ensure that there is a default device */ if (output_parameters.device < 0 || output_parameters.device >= Pa_GetDeviceCount()) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInvalidDevice, "Invalid output device " "(no default output device)")); return NULL; } output_parameters.channelCount = channels; output_parameters.sampleFormat = format; output_parameters.suggestedLatency = Pa_GetDeviceInfo(output_parameters.device)->defaultLowOutputLatency; output_parameters.hostApiSpecificStreamInfo = NULL; #ifdef MACOS if (output_host_specific_stream_info) { output_parameters.hostApiSpecificStreamInfo = &output_host_specific_stream_info->stream_info; } #endif } PaStreamParameters input_parameters; if (input) { if (input_device_index < 0) { input_parameters.device = Pa_GetDefaultInputDevice(); } else { input_parameters.device = input_device_index; } /* final check -- ensure that there is a default device */ if (input_parameters.device < 0) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paInvalidDevice, "Invalid input device " "(no default output device)")); return NULL; } input_parameters.channelCount = channels; input_parameters.sampleFormat = format; input_parameters.suggestedLatency = Pa_GetDeviceInfo(input_parameters.device)->defaultLowInputLatency; input_parameters.hostApiSpecificStreamInfo = NULL; #ifdef MACOS if (input_host_specific_stream_info) { input_parameters.hostApiSpecificStreamInfo = &input_host_specific_stream_info->stream_info; } #endif } PyAudioStream *stream = PyAudioStream_Create(); if (!stream) { PyErr_SetString(PyExc_MemoryError, "Cannot allocate stream object"); return NULL; } PaStream *pa_stream = NULL; // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_OpenStream(&pa_stream, /* input/output parameters */ /* NULL values are ignored */ input ? &input_parameters : NULL, output ? &output_parameters : NULL, /* samples per second */ rate, /* frames in the buffer */ frames_per_buffer, /* we won't output out of range samples so don't bother clipping them */ paClipOff, /* callback, if specified */ stream_callback ? PyAudioStream_CallbackCFunc : NULL, /* callback userData, if applicable */ stream); Py_END_ALLOW_THREADS // clang-format on if (err != paNoError) { #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif // Decrement reference, which automatically cleanups & deallocates stream. Py_DECREF(stream); PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } stream->context.stream = pa_stream; stream->context.frame_size = Pa_GetSampleSize(format) * channels; stream->context.main_thread_id = PyThreadState_Get()->thread_id; stream->context.callback = NULL; if (stream_callback) { Py_INCREF(stream_callback); stream->context.callback = stream_callback; } return (PyObject *)stream; } PyObject *PyAudio_CloseStream(PyObject *self, PyObject *args) { PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; // Closes the PortAudio stream and cleans up. PyAudioStream_Cleanup(stream); Py_INCREF(Py_None); return Py_None; } /************************************************************* * Stream Start / Stop / Info *************************************************************/ PyObject *PyAudio_StartStream(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_StartStream(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on if ((err != paNoError) && (err != paStreamIsNotStopped)) { PyAudioStream_Cleanup(stream); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } Py_INCREF(Py_None); return Py_None; } PyObject *PyAudio_StopStream(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetString(PyExc_IOError, "Stream not open"); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_StopStream(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on if ((err != paNoError) && (err != paStreamIsStopped)) { PyAudioStream_Cleanup(stream); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } Py_INCREF(Py_None); return Py_None; } PyObject *PyAudio_AbortStream(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetString(PyExc_IOError, "Stream not open"); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_AbortStream(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on if ((err != paNoError) && (err != paStreamIsStopped)) { PyAudioStream_Cleanup(stream); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } Py_INCREF(Py_None); return Py_None; } PyObject *PyAudio_IsStreamStopped(PyObject *self, PyObject *args) { int err; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", paBadStreamPtr, "Stream closed")); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS err = Pa_IsStreamStopped(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on if (err < 0) { PyAudioStream_Cleanup(stream); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } if (err) { Py_INCREF(Py_True); return Py_True; } Py_INCREF(Py_False); return Py_False; } PyObject *PyAudio_IsStreamActive(PyObject *self, PyObject *args) { int is_active; PyObject *stream_arg; if (!PyArg_ParseTuple(args, "O!", &PyAudioStreamType, &stream_arg)) { return NULL; } PyAudioStream *stream = (PyAudioStream *)stream_arg; if (!PyAudioStream_IsOpen(stream)) { PyErr_SetString(PyExc_IOError, "Stream not open"); return NULL; } // clang-format off Py_BEGIN_ALLOW_THREADS is_active = Pa_IsStreamActive(stream->context.stream); Py_END_ALLOW_THREADS // clang-format on if (is_active < 0) { PaError err = is_active; PyAudioStream_Cleanup(stream); #ifdef VERBOSE fprintf(stderr, "An error occured while using the portaudio stream\n"); fprintf(stderr, "Error number: %d\n", err); fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err)); #endif PyErr_SetObject(PyExc_IOError, Py_BuildValue("(i,s)", err, Pa_GetErrorText(err))); return NULL; } if (is_active) { Py_INCREF(Py_True); return Py_True; } Py_INCREF(Py_False); return Py_False; } python-pyaudio-0.2.13/src/pyaudio/stream_lifecycle.h000066400000000000000000000011671435221326700225240ustar00rootroot00000000000000#ifndef STREAM_LIFECYCLE_H_ #define STREAM_LIFECYCLE_H_ #ifndef PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN #endif #include "Python.h" PyObject *PyAudio_OpenStream(PyObject *self, PyObject *args, PyObject *kwargs); PyObject *PyAudio_CloseStream(PyObject *self, PyObject *args); PyObject *PyAudio_StartStream(PyObject *self, PyObject *args); PyObject *PyAudio_StopStream(PyObject *self, PyObject *args); PyObject *PyAudio_AbortStream(PyObject *self, PyObject *args); PyObject *PyAudio_IsStreamStopped(PyObject *self, PyObject *args); PyObject *PyAudio_IsStreamActive(PyObject *self, PyObject *args); #endif // STREAM_LIFECYCLE_H_ python-pyaudio-0.2.13/tests/000077500000000000000000000000001435221326700157355ustar00rootroot00000000000000python-pyaudio-0.2.13/tests/alsa_utils.py000066400000000000000000000020111435221326700204410ustar00rootroot00000000000000"""Utilities to silence ALSA console output.""" # ALSA tends to generate noisy console output. The functions here suppress that # output by patching the ALSA's error output routine. Heavily inspired by: # https://stackoverflow.com/a/13453192 import ctypes import sys ERROR_HANDLER_FUNC = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p) def py_error_handler(filename, line, function, err, fmt): pass def disable_error_handler_output(): if sys.platform != 'linux': return try: asound = ctypes.cdll.LoadLibrary('libasound.so') asound.snd_lib_error_set_handler(ERROR_HANDLER_FUNC(py_error_handler)) except OSError: pass def enable_error_handler_output(): if sys.platform != 'linux': return try: asound = ctypes.cdll.LoadLibrary('libasound.so') asound.snd_lib_error_set_handler(None) except OSError: pass python-pyaudio-0.2.13/tests/deprecation_tests.py000066400000000000000000000033431435221326700220310ustar00rootroot00000000000000"""PyAudio: ensure deprecated API usage raises warnings.""" import os import unittest import sys import pyaudio import alsa_utils # To skip tests requiring hardware, set this environment variable: SKIP_HW_TESTS = 'PYAUDIO_SKIP_HW_TESTS' in os.environ setUpModule = alsa_utils.disable_error_handler_output tearDownModule = alsa_utils.disable_error_handler_output class DeprecationTests(unittest.TestCase): @unittest.skipIf('darwin' not in sys.platform, 'macOS-only test.') def test_deprecated_mac_core_stream_info_getters(self): stream_info = pyaudio.PaMacCoreStreamInfo( flags=pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, channel_map=(1, 0)) with self.assertWarns(DeprecationWarning): self.assertEqual(stream_info.get_flags(), stream_info.flags) with self.assertWarns(DeprecationWarning): self.assertEqual(stream_info.get_channel_map(), stream_info.channel_map) with self.assertWarns(DeprecationWarning): self.assertEqual(stream_info._get_host_api_stream_object(), stream_info) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_deprecated_stream(self): p = pyaudio.PyAudio() # Do not directly instantiate or reference pyaudio.Stream. # Use pyaudio.PyAudio.open instead, and if necessary, reference # pyaudio.PyAudio.Stream. with self.assertWarns(DeprecationWarning): stream = pyaudio.Stream( # Don't do this! p, format=p.get_format_from_width(2), channels=2, rate=44100, output=True) stream.close() p.terminate() python-pyaudio-0.2.13/tests/error_tests.py000066400000000000000000000150761435221326700206730ustar00rootroot00000000000000import os import sys import time import unittest import pyaudio import alsa_utils # To skip tests requiring hardware, set this environment variable: SKIP_HW_TESTS = 'PYAUDIO_SKIP_HW_TESTS' in os.environ setUpModule = alsa_utils.disable_error_handler_output tearDownModule = alsa_utils.disable_error_handler_output 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) as cm: self.p.get_sample_size(10) e = cm.exception self.assertEqual(e.args[1], pyaudio.paSampleFormatNotSupported) def test_invalid_width(self): with self.assertRaises(ValueError): self.p.get_format_from_width(8) def test_invalid_hostapi_type(self): with self.assertRaises(IOError) as cm: self.p.get_host_api_info_by_type(-1) e = cm.exception self.assertEqual(e.args[0], pyaudio.paHostApiNotFound) def test_invalid_hostapi_index(self): with self.assertRaises(IOError) as cm: self.p.get_host_api_info_by_index(-1) e = cm.exception self.assertEqual(e.args[0], pyaudio.paInvalidHostApi) def test_valid_host_api_invalid_devinfo(self): with self.assertRaises(IOError) as cm: self.p.get_device_info_by_host_api_device_index(0, -1) e = cm.exception self.assertTrue(e.args[0] in (pyaudio.paInvalidDevice, pyaudio.paInvalidHostApi)) def test_invalid_host_api_valid_devinfo(self): with self.assertRaises(IOError) as cm: self.p.get_device_info_by_host_api_device_index(-1, 0) e = cm.exception self.assertTrue(e.args[0] in (pyaudio.paInvalidDevice, pyaudio.paInvalidHostApi)) def test_invalid_device_devinfo(self): with self.assertRaises(IOError) as cm: self.p.get_device_info_by_index(-1) e = cm.exception self.assertEqual(e.args[0], pyaudio.paInvalidDevice) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_error_without_stream_start(self): with self.assertRaises(IOError) as cm: stream = self.p.open(channels=1, rate=44100, format=pyaudio.paInt16, input=True, start=False) # not starting stream stream.read(2) e = cm.exception self.assertEqual(e.args[0], pyaudio.paStreamIsStopped) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_error_writing_to_readonly_stream(self): with self.assertRaises(IOError) as cm: stream = self.p.open(channels=1, rate=44100, format=pyaudio.paInt16, input=True) stream.write('foo') e = cm.exception self.assertEqual(e.args[1], pyaudio.paCanNotWriteToAnInputOnlyStream) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') 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) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') 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) as cm: stream.get_input_latency() e = cm.exception self.assertEqual(e.args[0], pyaudio.paBadStreamPtr) with self.assertRaises(IOError) as cm: stream.read(1) e = cm.exception self.assertEqual(e.args[0], pyaudio.paBadStreamPtr) def test_invalid_format_supported(self): with self.assertRaises(ValueError) as cm: self.p.is_format_supported(8000, -1, 1, pyaudio.paInt16) e = cm.exception self.assertEqual(e.args[1], pyaudio.paInvalidDevice) with self.assertRaises(ValueError) as cm: self.p.is_format_supported(8000, 0, -1, pyaudio.paInt16) e = cm.exception self.assertEqual(e.args[1], pyaudio.paInvalidChannelCount) with self.assertRaises(ValueError) as cm: self.p.is_format_supported(8000, input_device=None, output_device=None) e = cm.exception self.assertEqual(e.args[1], pyaudio.paInvalidDevice) # It's difficult to invoke an underflow on ALSA, so skip. @unittest.skipIf('linux' in sys.platform, 'skipping underflow test on linux.') @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') 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) with self.assertRaises(IOError) as err: # The sleep time requires some tuning to invoke an underflow error, # depending on the platform. time.sleep(1) stream.write('\x00\x00\x00\x00', exception_on_underflow=True) self.assertEqual(err.exception.errno, pyaudio.paOutputUnderflowed) self.assertEqual(err.exception.strerror, 'Output underflowed') # It's difficult to invoke an underflow on ALSA, so skip. @unittest.skipIf('linux' in sys.platform, 'skipping underflow test on linux.') @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') 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) 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') python-pyaudio-0.2.13/tests/host_device_api_tests.py000066400000000000000000000052151435221326700226610ustar00rootroot00000000000000"""PyAudio Host API and Device API tests.""" import os import unittest import pyaudio import alsa_utils # To skip tests requiring hardware, set this environment variable: SKIP_HW_TESTS = 'PYAUDIO_SKIP_HW_TESTS' in os.environ setUpModule = alsa_utils.disable_error_handler_output tearDownModule = alsa_utils.disable_error_handler_output class HostDeviceApiTests(unittest.TestCase): def setUp(self): self.p = pyaudio.PyAudio() def tearDown(self): self.p.terminate() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_host_api(self): """Host API tests""" self.assertTrue(self.p.get_host_api_count() > 0) default_info = self.p.get_default_host_api_info() default_index = default_info['index'] default_type = default_info['type'] self.assertDictEqual( default_info, self.p.get_host_api_info_by_index(default_index)) self.assertDictEqual( default_info, self.p.get_host_api_info_by_type(default_type)) with self.assertRaises(TypeError): self.p.get_host_api_info_by_type("not an integer") with self.assertRaises(IOError): self.p.get_host_api_info_by_type(-2) with self.assertRaises(TypeError): self.p.get_host_api_info_by_index("not an integer") with self.assertRaises(IOError): self.p.get_host_api_info_by_index(-2) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_device_api(self): """Device API tests""" self.assertTrue(self.p.get_device_count() > 0) default_input = self.p.get_default_input_device_info() default_output = self.p.get_default_output_device_info() self.assertDictEqual( default_input, self.p.get_device_info_by_index(default_input['index'])) self.assertDictEqual( default_output, self.p.get_device_info_by_index(default_output['index'])) with self.assertRaises(TypeError): self.p.get_device_info_by_index("not an integer") with self.assertRaises(IOError): self.p.get_device_info_by_index(-2) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_format_supported(self): with self.assertRaises(ValueError): # Need to specify input_device or output_device self.p.is_format_supported(44100) with self.assertRaises(ValueError): self.p.is_format_supported(44100, input_device=0, input_channels=-2, input_format=pyaudio.paInt16) python-pyaudio-0.2.13/tests/mac_core_stream_info_tests.py000066400000000000000000000066101435221326700236720ustar00rootroot00000000000000"""PyAudio Mac Core Stream Info Tests.""" import os import unittest import sys import pyaudio # To skip tests requiring hardware, set this environment variable: SKIP_HW_TESTS = 'PYAUDIO_SKIP_HW_TESTS' in os.environ # If unset, defaults to default devices. INPUT_DEVICE_INDEX = os.environ.get('PYAUDIO_INPUT_DEVICE_INDEX', None) OUTPUT_DEVICE_INDEX = os.environ.get('PYAUDIO_OUTPUT_DEVICE_INDEX', None) @unittest.skipIf('darwin' not in sys.platform, 'macOS-only test.') class MacCoreStreamInfoTests(unittest.TestCase): def test_getters(self): channel_map = (1, 0) stream_info = pyaudio.PaMacCoreStreamInfo( flags=pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, channel_map=channel_map) self.assertEqual(stream_info.channel_map, channel_map) self.assertEqual(stream_info.flags, pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice) def test_default(self): stream_info = pyaudio.PaMacCoreStreamInfo() self.assertEqual(stream_info.channel_map, None) self.assertEqual(stream_info.flags, pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_macos_channel_map(self): # reverse: R-L stereo channel_map = (1, 0) stream_info = pyaudio.PaMacCoreStreamInfo( flags=pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, # default channel_map=channel_map) self.assertEqual(stream_info.flags, pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice) self.assertEqual(stream_info.channel_map, channel_map) p = pyaudio.PyAudio() stream = p.open( format=p.get_format_from_width(2), channels=2, rate=44100, output=True, output_host_api_specific_stream_info=stream_info, start=False) # Make sure portaudio no longer depends on state inside this object # once the stream is initialized. del stream_info self.assertFalse(stream.is_active()) self.assertTrue(stream.is_stopped()) stream.start_stream() self.assertTrue(stream.is_active()) self.assertFalse(stream.is_stopped()) stream.stop_stream() self.assertFalse(stream.is_active()) self.assertTrue(stream.is_stopped()) stream.close() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_macos_inline_stream_info(self): p = pyaudio.PyAudio() stream = p.open( format=p.get_format_from_width(2), channels=2, rate=44100, output=True, # Instantiate inline, so PaMacCoreStreamInfo instance could get GCed # subsequently. Ensure the stream still works. output_host_api_specific_stream_info=pyaudio.PaMacCoreStreamInfo( flags=pyaudio.PaMacCoreStreamInfo.paMacCorePlayNice, # default channel_map=(1, 0)), # reverse: R-L stereo start=False) self.assertFalse(stream.is_active()) self.assertTrue(stream.is_stopped()) stream.start_stream() self.assertTrue(stream.is_active()) self.assertFalse(stream.is_stopped()) stream.stop_stream() self.assertFalse(stream.is_active()) self.assertTrue(stream.is_stopped()) stream.close() python-pyaudio-0.2.13/tests/misc_tests.py000066400000000000000000000043731435221326700204730ustar00rootroot00000000000000"""PyAudio misc tests.""" import unittest import pyaudio import alsa_utils setUpModule = alsa_utils.disable_error_handler_output tearDownModule = alsa_utils.disable_error_handler_output class MiscTests(unittest.TestCase): def test_get_sample_size(self): self.assertEqual(pyaudio.get_sample_size(pyaudio.paFloat32), 4) self.assertEqual(pyaudio.get_sample_size(pyaudio.paInt32), 4) self.assertEqual(pyaudio.get_sample_size(pyaudio.paInt24), 3) self.assertEqual(pyaudio.get_sample_size(pyaudio.paInt16), 2) self.assertEqual(pyaudio.get_sample_size(pyaudio.paInt8), 1) self.assertEqual(pyaudio.get_sample_size(pyaudio.paUInt8), 1) def test_get_format_from_width(self): self.assertEqual(pyaudio.get_format_from_width(1, unsigned=True), pyaudio.paUInt8) self.assertEqual(pyaudio.get_format_from_width(1, unsigned=False), pyaudio.paInt8) self.assertEqual(pyaudio.get_format_from_width(2), pyaudio.paInt16) self.assertEqual(pyaudio.get_format_from_width(3), pyaudio.paInt24) self.assertEqual(pyaudio.get_format_from_width(4), pyaudio.paFloat32) with self.assertRaises(ValueError): pyaudio.get_format_from_width(-1) with self.assertRaises(ValueError): pyaudio.get_format_from_width(5) def test_get_format_from_width_pyaudio(self): p = pyaudio.PyAudio() self.assertEqual(p.get_format_from_width(1, unsigned=True), pyaudio.paUInt8) self.assertEqual(p.get_format_from_width(1, unsigned=False), pyaudio.paInt8) self.assertEqual(p.get_format_from_width(2), pyaudio.paInt16) self.assertEqual(p.get_format_from_width(3), pyaudio.paInt24) self.assertEqual(p.get_format_from_width(4), pyaudio.paFloat32) with self.assertRaises(ValueError): p.get_format_from_width(-1) with self.assertRaises(ValueError): p.get_format_from_width(5) p.terminate() def test_get_portaudio_version(self): self.assertGreater(pyaudio.get_portaudio_version(), 0) def test_get_portaudio_version_text(self): self.assertGreater(len(pyaudio.get_portaudio_version_text()), 0) python-pyaudio-0.2.13/tests/stream_tests.py000066400000000000000000000613541435221326700210350ustar00rootroot00000000000000"""Stream tests.""" import os import time import threading import unittest import pyaudio import alsa_utils # To skip tests requiring hardware, set this environment variable: SKIP_HW_TESTS = 'PYAUDIO_SKIP_HW_TESTS' in os.environ # If unset, defaults to default devices. INPUT_DEVICE_INDEX = os.environ.get('PYAUDIO_INPUT_DEVICE_INDEX', None) OUTPUT_DEVICE_INDEX = os.environ.get('PYAUDIO_OUTPUT_DEVICE_INDEX', None) setUpModule = alsa_utils.disable_error_handler_output tearDownModule = alsa_utils.disable_error_handler_output class StreamTests(unittest.TestCase): def setUp(self): self.p = pyaudio.PyAudio() self.input_device = INPUT_DEVICE_INDEX and int(INPUT_DEVICE_INDEX) self.output_device = OUTPUT_DEVICE_INDEX and int(OUTPUT_DEVICE_INDEX) # Different platforms/devices support different number of channels for # input streams. Inspect the desired input device and use the maximum # number of channels. try: input_device_info = self.p.get_host_api_info_by_index( self.input_device ) if self.input_device else self.p.get_default_input_device_info() except OSError as err: raise OSError(f"Invalid device index {self.input_device}") from err self.input_channels = input_device_info['maxInputChannels'] if self.input_channels < 1: raise OSError("Invalid number of input channels for device") def tearDown(self): self.p.terminate() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_output_start_stop_stream_properties(self): out_stream = self.p.open( format=self.p.get_format_from_width(2), channels=2, rate=44100, output=True) self.assertTrue(out_stream.is_active()) self.assertFalse(out_stream.is_stopped()) self.assertGreaterEqual(out_stream.get_output_latency(), 0) self.assertGreaterEqual(out_stream.get_input_latency(), 0) self.assertGreaterEqual(out_stream.get_time(), 0) self.assertGreaterEqual(out_stream.get_cpu_load(), 0) self.assertGreaterEqual(out_stream.get_write_available(), 0) out_stream.stop_stream() self.assertTrue(out_stream.is_stopped()) self.assertFalse(out_stream.is_active()) # Make sure we can still read PaStreamInfo, as stream is not closed yet. self.assertGreater(out_stream.get_output_latency(), 0) self.assertGreaterEqual(out_stream.get_input_latency(), 0) self.assertGreaterEqual(out_stream.get_time(), 0) self.assertGreaterEqual(out_stream.get_cpu_load(), 0) out_stream.start_stream() self.assertFalse(out_stream.is_stopped()) self.assertTrue(out_stream.is_active()) # Make sure we can still read PaStreamInfo, as stream is not closed yet. self.assertGreater(out_stream.get_output_latency(), 0) self.assertGreaterEqual(out_stream.get_input_latency(), 0) self.assertGreaterEqual(out_stream.get_time(), 0) self.assertGreaterEqual(out_stream.get_cpu_load(), 0) self.assertGreaterEqual(out_stream.get_write_available(), 0) out_stream.close() with self.assertRaises(IOError): self.assertTrue(out_stream.is_stopped()) with self.assertRaises(IOError): self.assertFalse(out_stream.is_active()) with self.assertRaises(IOError): out_stream.get_output_latency() with self.assertRaises(IOError): out_stream.get_time() with self.assertRaises(IOError): out_stream.get_cpu_load() with self.assertRaises(IOError): out_stream.get_write_available() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_input_start_stop_stream_properties(self): in_stream = self.p.open( format=self.p.get_format_from_width(2), channels=self.input_channels, rate=44100, input=True) self.assertTrue(in_stream.is_active()) self.assertFalse(in_stream.is_stopped()) self.assertGreaterEqual(in_stream.get_output_latency(), 0) self.assertGreaterEqual(in_stream.get_input_latency(), 0) self.assertGreaterEqual(in_stream.get_time(), 0) self.assertGreaterEqual(in_stream.get_cpu_load(), 0) self.assertGreaterEqual(in_stream.get_read_available(), 0) in_stream.stop_stream() self.assertTrue(in_stream.is_stopped()) self.assertFalse(in_stream.is_active()) # Make sure we can still read PaStreamInfo, as stream is not closed yet. self.assertGreaterEqual(in_stream.get_output_latency(), 0) self.assertGreater(in_stream.get_input_latency(), 0) self.assertGreaterEqual(in_stream.get_time(), 0) self.assertGreaterEqual(in_stream.get_cpu_load(), 0) in_stream.start_stream() self.assertFalse(in_stream.is_stopped()) self.assertTrue(in_stream.is_active()) # Make sure we can still read PaStreamInfo, as stream is not closed yet. self.assertGreaterEqual(in_stream.get_output_latency(), 0) self.assertGreater(in_stream.get_input_latency(), 0) self.assertGreaterEqual(in_stream.get_time(), 0) self.assertGreaterEqual(in_stream.get_cpu_load(), 0) self.assertGreaterEqual(in_stream.get_read_available(), 0) in_stream.close() with self.assertRaises(IOError): self.assertTrue(in_stream.is_stopped()) with self.assertRaises(IOError): self.assertFalse(in_stream.is_active()) with self.assertRaises(IOError): in_stream.get_output_latency() with self.assertRaises(IOError): in_stream.get_time() with self.assertRaises(IOError): in_stream.get_cpu_load() with self.assertRaises(IOError): in_stream.get_read_available() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_close_stream_from_pyaudio(self): out_stream = self.p.open( format=self.p.get_format_from_width(2), channels=2, rate=44100, output=True) self.assertTrue(out_stream.is_active()) self.p.close(out_stream) with self.assertRaises(OSError) as err: out_stream.is_stopped() self.assertEqual(err.exception.args[0], pyaudio.paBadStreamPtr) self.assertTrue(out_stream not in self.p._streams) @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_close_unknown_stream(self): out_stream = self.p.open( format=self.p.get_format_from_width(2), channels=2, rate=44100, output=True) # Simulate the condition where out_stream is unknown to self.p: self.p._streams = set() with self.assertRaises(ValueError): self.p.close(out_stream) out_stream.close() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_output_blocking(self): out_stream = self.p.open( format=self.p.get_format_from_width(2), channels=2, rate=44100, output=True) out_stream.write(b'\0' * 512) with self.assertRaises(IOError): out_stream.read(512) out_stream.close() @unittest.skipIf(SKIP_HW_TESTS, 'Hardware device required.') def test_input_blocking(self): width = 2 in_stream = self.p.open( format=self.p.get_format_from_width(width), channels=self.input_channels, rate=44100, input=True) samples = in_stream.read(512) with self.assertRaises(IOError): in_stream.write(b'\0' * 512) in_stream.close() self.assertEqual(len(samples), 512 * width * self.input_channels) @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') def test_return_none_callback(self): """Ensure that return None ends the stream.""" num_times_called = 0 def out_callback(_, frame_count, time_info, status): nonlocal num_times_called num_times_called += 1 return (None, pyaudio.paContinue) out_stream = self.p.open( format=self.p.get_format_from_width(2), channels=2, rate=44100, output=True, output_device_index=self.output_device, stream_callback=out_callback) out_stream.start_stream() time.sleep(0.5) out_stream.stop_stream() self.assertEqual(num_times_called, 1) @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') def test_excess_output_callback(self): """Ensure that returning more bytes than allowed does not fail.""" num_times_called = 0 width = 2 channels = 2 bytes_per_frame = width * channels def out_callback(_, frame_count, time_info, status): nonlocal num_times_called num_times_called += 1 # Make sure this is called twice, so we know that the first time # didn't crash (at least). result = (pyaudio.paComplete if num_times_called == 2 else pyaudio.paContinue) max_allowed_bytes = frame_count * bytes_per_frame return (b'\1' * (max_allowed_bytes * 2), result) out_stream = self.p.open( format=self.p.get_format_from_width(width), channels=channels, rate=44100, output=True, output_device_index=self.output_device, stream_callback=out_callback) out_stream.start_stream() time.sleep(0.5) out_stream.stop_stream() self.assertEqual(num_times_called, 2) @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') def test_device_lock_gil_order(self): """Ensure no deadlock between Pa_{Open,Start,Stop}Stream and GIL.""" # This test targets macOS CoreAudio, which seems to use audio device # locks. The test is less relevant on ALSA and Win32 MME, which don't # seem to suffer even if the GIL is held while calling PortAudio. def in_callback(in_data, frame_count, time_info, status): # Release the GIL for a bit, but on macOS, still hold the device # lock. time.sleep(1) # Note: on macOS, must return paContinue; paComplete will deadlock # in the underlying call to AudioOutputUnitStop. return (None, pyaudio.paContinue) in_stream = self.p.open( format=self.p.get_format_from_width(2), channels=self.input_channels, rate=44100, input=True, start=False, input_device_index=self.input_device, 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(0.5) # in_callback will eventually drop the GIL when executing time.sleep # (while retaining the device lock), allowing the following code to # run. All stream operations MUST release the GIL before attempting to # acquire the device lock. If that discipline is violated, the following # code would wait for the device lock while holding the GIL, while the # in_callback thread would be waiting for the GIL once time.sleep # completes (while holding the device lock), leading to deadlock. in_stream.stop_stream() @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') 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 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(1) return (None, pyaudio.paComplete) in_stream = self.p.open( format=self.p.get_format_from_width(width), channels=self.input_channels, rate=rate, input=True, start=False, frames_per_buffer=frames_per_chunk, input_device_index=self.input_device, stream_callback=in_callback) out_stream = self.p.open( format=self.p.get_format_from_width(width), channels=2, rate=rate, output=True, start=False, frames_per_buffer=frames_per_chunk, output_device_index=self.output_device, 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(0.5) # 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(1) in_stream.stop_stream() out_stream.stop_stream() @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') 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 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(1) return (None, pyaudio.paComplete) in_stream = self.p.open( format=self.p.get_format_from_width(width), channels=self.input_channels, rate=rate, input=True, start=False, frames_per_buffer=frames_per_chunk, input_device_index=self.input_device, stream_callback=in_callback) out_stream = self.p.open( format=self.p.get_format_from_width(width), channels=2, rate=rate, output=True, start=False, frames_per_buffer=frames_per_chunk, output_device_index=self.output_device, 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(0.5) # 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(1) in_stream.stop_stream() out_stream.stop_stream() @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') 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 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(1) return (None, pyaudio.paComplete) in_stream = self.p.open( format=self.p.get_format_from_width(width), channels=self.input_channels, rate=rate, input=True, start=False, frames_per_buffer=frames_per_chunk, input_device_index=self.input_device, stream_callback=in_callback) out_stream = self.p.open( format=self.p.get_format_from_width(width), channels=2, rate=rate, output=True, start=False, frames_per_buffer=frames_per_chunk, output_device_index=self.output_device, 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(0.5) # 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(1) in_stream.stop_stream() out_stream.stop_stream() @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') 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 frames_per_chunk = 1024 def in_callback(in_data, frame_count, time_info, status): # Release the GIL for a bit time.sleep(1) return (None, pyaudio.paComplete) in_stream = self.p.open( format=self.p.get_format_from_width(width), channels=self.input_channels, rate=rate, input=True, start=False, frames_per_buffer=frames_per_chunk, input_device_index=self.input_device, stream_callback=in_callback) out_stream = self.p.open( # Blocking mode format=self.p.get_format_from_width(width), channels=2, rate=rate, output=True, frames_per_buffer=frames_per_chunk, output_device_index=self.output_device) # 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(0.5) # 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.assertGreaterEqual(out_stream.get_write_available(), 0) time.sleep(1) in_stream.stop_stream() @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') 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 frames_per_chunk = 1024 def out_callback(in_data, frame_count, time_info, status): # Release the GIL for a bit time.sleep(1) return (None, pyaudio.paComplete) in_stream = self.p.open( # Blocking mode format=self.p.get_format_from_width(width), channels=self.input_channels, rate=rate, input=True, frames_per_buffer=frames_per_chunk, input_device_index=self.input_device) out_stream = self.p.open( format=self.p.get_format_from_width(width), channels=2, rate=rate, output=True, start=False, frames_per_buffer=frames_per_chunk, output_device_index=self.output_device, 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(0.5) # 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.assertGreaterEqual(in_stream.get_read_available(), 0) time.sleep(1) in_stream.stop_stream() @unittest.skipIf(SKIP_HW_TESTS, 'Sound hardware required.') def test_terminate_gil(self): """Ensure no deadlock between Pa_Terminate and GIL.""" # This test targets macOS CoreAudio, which seems to use audio device # locks. The test is less relevant on ALSA and Win32 MME, which don't # seem to suffer even if the GIL is held while calling PortAudio. width = 2 channels = 2 bytes_per_frame = width * channels event = threading.Event() def out_callback(in_data, frame_count, time_info, status): event.set() time.sleep(0.5) # Release the GIL for a bit event.clear() return (b'\1' * frame_count * bytes_per_frame, pyaudio.paContinue) out_stream = self.p.open( format=self.p.get_format_from_width(width), channels=channels, rate=44100, output=True, start=False, output_device_index=self.output_device, 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. For output streams on # macOS, it's important to have one complete call to out_callback before # attempting to terminate. Otherwise, the underlying call to # AudioOutputUnitStop will deadlock. time.sleep(0.6) # out_callback will eventually drop the GIL when executing time.sleep # (while retaining the device lock), allowing the following code to # run. Terminating PyAudio MUST release the GIL before attempting to # acquire the device lock (if the lock is needed). If that discipline is # violated, the following code would wait for the device lock while # holding the GIL, while the out_callback thread would be waiting for # the GIL once time.sleep completes (while holding the device lock), # leading to deadlock. # # Wait until out_callback is about to sleep, plus a little extra to # help ensure that sleep() is called first before we concurrently call # into self.p.terminate(). event.wait() time.sleep(0.1) self.p.terminate() python-pyaudio-0.2.13/tests/stream_wire_tests.py000066400000000000000000000304321435221326700220540ustar00rootroot00000000000000# -*- 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 os import struct import time import unittest import wave import sys import numpy import pyaudio import alsa_utils # To skip tests requiring hardware, set this environment variable: SKIP_HW_TESTS = 'PYAUDIO_SKIP_HW_TESTS' in os.environ # To run tests that require a loopback device (disabled by default), set this # variable. If SKIP_HW_TESTS is set, this variable has no effect. ENABLE_LOOPBACK_TESTS = 'PYAUDIO_ENABLE_LOOPBACK_TESTS' in os.environ DUMP_CAPTURE = False setUpModule = alsa_utils.disable_error_handler_output tearDownModule = alsa_utils.disable_error_handler_output def _create_reference_signal(freqs, sampling_rate, width, duration): """Return reference signal with several sinuoids with frequencies.""" 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) ] 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)) ] 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) 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() class StreamWireTests(unittest.TestCase): def setUp(self): self.p = pyaudio.PyAudio() self.loopback_input_idx = None self.loopback_output_idx = None if ENABLE_LOOPBACK_TESTS: (self.loopback_input_idx, self.loopback_output_idx) = self._get_audio_loopback() if not (self.loopback_input_idx is None or self.loopback_input_idx >= 0): raise OSError("No loopback device found") if not (self.loopback_output_idx is None or self.loopback_output_idx >= 0): raise OSError("No loopback device found") # Different platforms/devices support different number of channels for # input streams. Inspect the desired input device and use the maximum # number of channels. try: input_device_info = self.p.get_host_api_info_by_index( self.loopback_input_idx) if self.loopback_input_idx else ( self.p.get_default_input_device_info()) except OSError as err: raise OSError( f"Invalid device index {self.loopback_input_idx}") from err self.input_channels = input_device_info['maxInputChannels'] if self.input_channels < 1: raise OSError("Invalid number of input channels for device") def tearDown(self): self.p.terminate() def _get_audio_loopback(self): if sys.platform == 'darwin': return self._find_audio_device( 'Soundflower (2ch)', 'Soundflower (2ch)') if sys.platform in ('linux', 'linux2'): return self._find_audio_device( '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_device(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 @unittest.skipIf(SKIP_HW_TESTS or not ENABLE_LOOPBACK_TESTS, 'Loopback device required.') def test_input_output_blocking(self): """Test blocking-based record and playback.""" rate = 44100 # frames per second width = 2 # bytes per sample channels = self.input_channels # 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 = _create_reference_signal(freqs, rate, width, duration) audio_chunks = _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: _write_wav('test_blocking.wav', b''.join(captured), width, channels, rate) captured_signal = _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)) @unittest.skipIf(SKIP_HW_TESTS or not ENABLE_LOOPBACK_TESTS, 'Loopback device required.') def test_input_output_callback(self): """Test callback-based record and playback.""" rate = 44100 # frames per second width = 2 # bytes per sample channels = self.input_channels duration = 1 # second frames_per_chunk = 1024 freqs = [130.81, 329.63, 440.0, 466.16, 587.33, 739.99] test_signal = _create_reference_signal(freqs, rate, width, duration) audio_chunks = _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: _write_wav('test_callback.wav', b''.join(captured), width, channels, rate) captured_signal = _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 _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)