You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1122 lines
38 KiB
1122 lines
38 KiB
2 years ago
|
# 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. PyAudio is inspired by:
|
||
|
|
||
|
* pyPortAudio/fastaudio: Python bindings for PortAudio v18 API.
|
||
|
* tkSnack: cross-platform sound toolkit for Tcl/Tk and Python.
|
||
|
|
||
|
.. include:: ../sphinx/examples.rst
|
||
|
|
||
|
Overview
|
||
|
--------
|
||
|
|
||
|
**Classes**
|
||
|
:py:class:`PyAudio`, :py:class:`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>`
|
||
|
.. _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>`
|
||
|
.. _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>`
|
||
|
.. _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>`
|
||
|
.. _PaCallbackReturnCodes:
|
||
|
|
||
|
**PortAudio Callback Return Codes**
|
||
|
:py:data:`paContinue`, :py:data:`paComplete`, :py:data:`paAbort`
|
||
|
|
||
|
.. |PaCallbackFlags| replace:: :ref:`PortAutio Callback Flag <PaCallbackFlags>`
|
||
|
.. _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.11"
|
||
|
__docformat__ = "restructuredtext en"
|
||
|
|
||
|
import sys
|
||
|
|
||
|
# attempt to import PortAudio
|
||
|
try:
|
||
|
import _portaudio as pa
|
||
|
except ImportError:
|
||
|
print("Could not import the PyAudio C module '_portaudio'.")
|
||
|
raise
|
||
|
|
||
|
############################################################
|
||
|
# GLOBALS
|
||
|
############################################################
|
||
|
|
||
|
##### 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
|
||
|
|
||
|
############################################################
|
||
|
# Convenience Functions
|
||
|
############################################################
|
||
|
|
||
|
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
|
||
|
else:
|
||
|
return paInt8
|
||
|
elif width == 2:
|
||
|
return paInt16
|
||
|
elif width == 3:
|
||
|
return paInt24
|
||
|
elif width == 4:
|
||
|
return paFloat32
|
||
|
else:
|
||
|
raise ValueError("Invalid width: %d" % width)
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Versioning
|
||
|
############################################################
|
||
|
|
||
|
def get_portaudio_version():
|
||
|
"""
|
||
|
Returns portaudio version.
|
||
|
|
||
|
:rtype: string
|
||
|
"""
|
||
|
|
||
|
return pa.get_version()
|
||
|
|
||
|
def get_portaudio_version_text():
|
||
|
"""
|
||
|
Returns PortAudio version as a text string.
|
||
|
|
||
|
:rtype: string
|
||
|
"""
|
||
|
|
||
|
return pa.get_version_text()
|
||
|
|
||
|
############################################################
|
||
|
# Wrapper around _portaudio Stream (Internal)
|
||
|
############################################################
|
||
|
|
||
|
# Note: See PyAudio class below for main export.
|
||
|
|
||
|
class Stream:
|
||
|
"""
|
||
|
PortAudio Stream Wrapper. Use :py:func:`PyAudio.open` to make a new
|
||
|
:py:class:`Stream`.
|
||
|
|
||
|
**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=1024,
|
||
|
start=True,
|
||
|
input_host_api_specific_stream_info=None,
|
||
|
output_host_api_specific_stream_info=None,
|
||
|
stream_callback=None,
|
||
|
as_loopback=False):
|
||
|
"""
|
||
|
Initialize a stream; this should be called by
|
||
|
: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:`Stream.read` and :py:func:`Stream.write`). To use
|
||
|
non-blocking operation, specify a callback that conforms
|
||
|
to the following signature:
|
||
|
|
||
|
.. code-block:: python
|
||
|
|
||
|
callback(in_data, # recorded 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:`Stream.read` or
|
||
|
:py:func:`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.
|
||
|
"""
|
||
|
|
||
|
# no stupidity allowed
|
||
|
if not (input or output):
|
||
|
raise ValueError("Must specify an input or output " + "stream.")
|
||
|
|
||
|
# remember parent
|
||
|
self._parent = PA_manager
|
||
|
|
||
|
# remember if we are an: input, output (or both)
|
||
|
self._is_input = input
|
||
|
self._is_output = output
|
||
|
|
||
|
# are we running?
|
||
|
self._is_running = start
|
||
|
|
||
|
# remember some parameters
|
||
|
self._rate = rate
|
||
|
self._channels = channels
|
||
|
self._format = format
|
||
|
self._frames_per_buffer = frames_per_buffer
|
||
|
self._as_loopback = as_loopback
|
||
|
|
||
|
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,
|
||
|
'as_loopback' : as_loopback}
|
||
|
|
||
|
if input_host_api_specific_stream_info:
|
||
|
_l = input_host_api_specific_stream_info
|
||
|
arguments[
|
||
|
'input_host_api_specific_stream_info'
|
||
|
] = _l._get_host_api_stream_object()
|
||
|
|
||
|
if output_host_api_specific_stream_info:
|
||
|
_l = output_host_api_specific_stream_info
|
||
|
arguments[
|
||
|
'output_host_api_specific_stream_info'
|
||
|
] = _l._get_host_api_stream_object()
|
||
|
|
||
|
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):
|
||
|
""" Close the stream """
|
||
|
|
||
|
pa.close(self._stream)
|
||
|
|
||
|
self._is_running = False
|
||
|
|
||
|
self._parent._remove_stream(self)
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Stream Info
|
||
|
############################################################
|
||
|
|
||
|
def get_input_latency(self):
|
||
|
"""
|
||
|
Return the input latency.
|
||
|
|
||
|
:rtype: float
|
||
|
"""
|
||
|
|
||
|
return self._stream.inputLatency
|
||
|
|
||
|
def get_output_latency(self):
|
||
|
"""
|
||
|
Return the output latency.
|
||
|
|
||
|
:rtype: float
|
||
|
"""
|
||
|
|
||
|
return self._stream.outputLatency
|
||
|
|
||
|
def get_time(self):
|
||
|
"""
|
||
|
Return stream time.
|
||
|
|
||
|
:rtype: float
|
||
|
"""
|
||
|
|
||
|
return pa.get_stream_time(self._stream)
|
||
|
|
||
|
def get_cpu_load(self):
|
||
|
"""
|
||
|
Return the CPU load. This is always 0.0 for the
|
||
|
blocking API.
|
||
|
|
||
|
:rtype: float
|
||
|
"""
|
||
|
|
||
|
return pa.get_stream_cpu_load(self._stream)
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Stream Management
|
||
|
############################################################
|
||
|
|
||
|
def start_stream(self):
|
||
|
""" Start the stream. """
|
||
|
|
||
|
if self._is_running:
|
||
|
return
|
||
|
|
||
|
pa.start_stream(self._stream)
|
||
|
self._is_running = True
|
||
|
|
||
|
def stop_stream(self):
|
||
|
"""
|
||
|
Stop the stream. Once the stream is stopped, one may not call
|
||
|
write or read. Call :py:func:`start_stream` to resume 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)
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Reading/Writing
|
||
|
############################################################
|
||
|
|
||
|
def write(self, frames, num_frames=None,
|
||
|
exception_on_underflow=False):
|
||
|
|
||
|
"""
|
||
|
Write samples to the stream. 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 == None:
|
||
|
# determine how many frames to read
|
||
|
width = get_sample_size(self._format)
|
||
|
num_frames = int(len(frames) / (self._channels * width))
|
||
|
#print len(frames), self._channels, self._width, num_frames
|
||
|
|
||
|
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: string
|
||
|
"""
|
||
|
|
||
|
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)
|
||
|
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Main Export
|
||
|
############################################################
|
||
|
|
||
|
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
|
||
|
|
||
|
Use this class to open and close streams.
|
||
|
|
||
|
**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**
|
||
|
"""
|
||
|
|
||
|
############################################################
|
||
|
# Initialization and Termination
|
||
|
############################################################
|
||
|
|
||
|
def __init__(self):
|
||
|
"""Initialize PortAudio."""
|
||
|
|
||
|
pa.initialize()
|
||
|
self._streams = set()
|
||
|
|
||
|
def terminate(self):
|
||
|
"""
|
||
|
Terminate 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()
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Stream Format
|
||
|
############################################################
|
||
|
|
||
|
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.
|
||
|
"""
|
||
|
|
||
|
if width == 1:
|
||
|
if unsigned:
|
||
|
return paUInt8
|
||
|
else:
|
||
|
return paInt8
|
||
|
elif width == 2:
|
||
|
return paInt16
|
||
|
elif width == 3:
|
||
|
return paInt24
|
||
|
elif width == 4:
|
||
|
return paFloat32
|
||
|
else:
|
||
|
raise ValueError("Invalid width: %d" % width)
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Stream Factory
|
||
|
############################################################
|
||
|
|
||
|
def open(self, *args, **kwargs):
|
||
|
"""
|
||
|
Open a new stream. See constructor for
|
||
|
:py:func:`Stream.__init__` for parameter details.
|
||
|
|
||
|
:returns: A new :py:class:`Stream`
|
||
|
"""
|
||
|
|
||
|
stream = Stream(self, *args, **kwargs)
|
||
|
self._streams.add(stream)
|
||
|
return stream
|
||
|
|
||
|
def close(self, stream):
|
||
|
"""
|
||
|
Close a stream. Typically use :py:func:`Stream.close` instead.
|
||
|
|
||
|
:param stream: An instance of the :py:class:`Stream` object.
|
||
|
:raises ValueError: if stream does not exist.
|
||
|
"""
|
||
|
|
||
|
if stream not in self._streams:
|
||
|
raise ValueError("Stream `%s' not found" % str(stream))
|
||
|
|
||
|
stream.close()
|
||
|
|
||
|
def _remove_stream(self, stream):
|
||
|
"""
|
||
|
Internal method. Removes a stream.
|
||
|
|
||
|
:param stream: An instance of the :py:class:`Stream` object.
|
||
|
"""
|
||
|
|
||
|
if stream in self._streams:
|
||
|
self._streams.remove(stream)
|
||
|
|
||
|
|
||
|
############################################################
|
||
|
# Host API Inspection
|
||
|
############################################################
|
||
|
|
||
|
def get_host_api_count(self):
|
||
|
"""
|
||
|
Return the number of available PortAudio Host APIs.
|
||
|
|
||
|
:rtype: integer
|
||
|
"""
|
||
|
|
||
|
return pa.get_host_api_count()
|
||
|
|
||
|
def get_default_host_api_info(self):
|
||
|
"""
|
||
|
Return 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
|
||
|
"""
|
||
|
|
||
|
defaultHostApiIndex = pa.get_default_host_api()
|
||
|
return self.get_host_api_info_by_index(defaultHostApiIndex)
|
||
|
|
||
|
def get_host_api_info_by_type(self, host_api_type):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Internal method to create Host API dictionary that mirrors
|
||
|
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):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Check to see if specified device configuration
|
||
|
is supported. Returns True if the configuration
|
||
|
is supported; throws a ValueError exception 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 == None and output_device == None:
|
||
|
raise ValueError("must specify stream format for input, " +\
|
||
|
"output, or both", paInvalidDevice);
|
||
|
|
||
|
kwargs = {}
|
||
|
|
||
|
if input_device != None:
|
||
|
kwargs['input_device'] = input_device
|
||
|
kwargs['input_channels'] = input_channels
|
||
|
kwargs['input_format'] = input_format
|
||
|
|
||
|
if output_device != 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):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Return 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):
|
||
|
"""
|
||
|
Internal method to create Device Info dictionary that mirrors
|
||
|
PortAudio's ``PaDeviceInfo`` structure.
|
||
|
|
||
|
:rtype: dict
|
||
|
"""
|
||
|
|
||
|
device_name = device_info.name
|
||
|
|
||
|
# Attempt to decode device_name
|
||
|
for codec in ["utf-8", "cp1252"]:
|
||
|
try:
|
||
|
device_name = device_name.decode(codec)
|
||
|
break
|
||
|
except:
|
||
|
pass
|
||
|
|
||
|
# If we fail to decode, we return the raw bytes and let the caller
|
||
|
# deal with the encoding.
|
||
|
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
|
||
|
######################################################################
|
||
|
|
||
|
try:
|
||
|
paMacCoreStreamInfo = pa.paMacCoreStreamInfo
|
||
|
except AttributeError:
|
||
|
pass
|
||
|
else:
|
||
|
class PaMacCoreStreamInfo:
|
||
|
"""
|
||
|
Mac OS X-only: PaMacCoreStreamInfo is a PortAudio Host API
|
||
|
Specific Stream Info data structure for specifying Mac OS
|
||
|
X-only settings. Instantiate this class (if desired) and pass
|
||
|
the instance 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:`Stream.__init__`.)
|
||
|
|
||
|
:note: Mac OS X only.
|
||
|
|
||
|
.. |PaMacCoreFlags| replace:: :ref:`PortAudio Mac Core Flags <PaMacCoreFlags>`
|
||
|
.. _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`
|
||
|
|
||
|
**Settings**
|
||
|
:py:func:`get_flags`, :py:func:`get_channel_map`
|
||
|
"""
|
||
|
|
||
|
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 flags and channel_map. See PortAudio
|
||
|
documentation for more details on these parameters; they are
|
||
|
passed almost verbatim to the PortAudio library.
|
||
|
|
||
|
:param flags: |PaMacCoreFlags| OR'ed together.
|
||
|
See :py:class:`PaMacCoreStreamInfo`.
|
||
|
:param channel_map: An array describing the channel mapping.
|
||
|
See PortAudio documentation for usage.
|
||
|
"""
|
||
|
|
||
|
kwargs = {"flags" : flags,
|
||
|
"channel_map" : channel_map}
|
||
|
|
||
|
if flags == None:
|
||
|
del kwargs["flags"]
|
||
|
if channel_map == None:
|
||
|
del kwargs["channel_map"]
|
||
|
|
||
|
self._paMacCoreStreamInfo = paMacCoreStreamInfo(**kwargs)
|
||
|
|
||
|
def get_flags(self):
|
||
|
"""
|
||
|
Return the flags set at instantiation.
|
||
|
|
||
|
:rtype: integer
|
||
|
"""
|
||
|
|
||
|
return self._paMacCoreStreamInfo.flags
|
||
|
|
||
|
def get_channel_map(self):
|
||
|
"""
|
||
|
Return the channel map set at instantiation.
|
||
|
|
||
|
:rtype: tuple or None
|
||
|
"""
|
||
|
|
||
|
return self._paMacCoreStreamInfo.channel_map
|
||
|
|
||
|
def _get_host_api_stream_object(self):
|
||
|
"""Private method."""
|
||
|
|
||
|
return self._paMacCoreStreamInfo
|