Skip to content

Commit

Permalink
Include platform-specific API in query_hostapis results
Browse files Browse the repository at this point in the history
  • Loading branch information
dholl committed Apr 26, 2017
1 parent 85729e8 commit 8ccbe85
Showing 1 changed file with 314 additions and 0 deletions.
314 changes: 314 additions & 0 deletions sounddevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import atexit as _atexit
from cffi import FFI as _FFI
from collections import namedtuple as _namedtuple
import os as _os
import platform as _platform
import sys as _sys
Expand Down Expand Up @@ -247,6 +248,7 @@
void PaMacCore_SetupStreamInfo( PaMacCoreStreamInfo *data, unsigned long flags );
void PaMacCore_SetupChannelMap( PaMacCoreStreamInfo *data, const SInt32 * const channelMap, unsigned long channelMapSize );
const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input );
PaError PaMacCore_GetBufferSizeRange( PaDeviceIndex device, long *minBufferSizeFrames, long *maxBufferSizeFrames );
#define paMacCoreChangeDeviceParameters 0x01
#define paMacCoreFailIfConversionRequired 0x02
#define paMacCoreConversionQualityMin 0x0100
Expand All @@ -265,6 +267,11 @@
/* pa_asio.h */
PaError PaAsio_GetAvailableBufferSizes( PaDeviceIndex device, long *minBufferSizeFrames, long *maxBufferSizeFrames, long *preferredBufferSizeFrames, long *granularity );
PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, const char** channelName );
PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, const char** channelName );
PaError PaAsio_SetStreamSampleRate( PaStream* stream, double sampleRate );
#define paAsioUseChannelSelectors 0x01
typedef struct PaAsioStreamInfo
Expand All @@ -276,6 +283,14 @@
int *channelSelectors;
} PaAsioStreamInfo;
/* pa_linux_alsa.h */
void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable );
PaError PaAlsa_GetStreamInputCard( PaStream *s, int *card );
PaError PaAlsa_GetStreamOutputCard( PaStream *s, int *card );
PaError PaAlsa_SetNumPeriods( int numPeriods );
PaError PaAlsa_SetRetriesBusy( int retries );
/* pa_win_wasapi.h */
typedef enum PaWasapiFlags
Expand Down Expand Up @@ -337,6 +352,8 @@
PaWasapiStreamCategory streamCategory;
PaWasapiStreamOption streamOption;
} PaWasapiStreamInfo;
PaError PaWasapi_GetFramesPerHostBuffer( PaStream *pStream, unsigned int *nInput, unsigned int *nOutput );
""")

try:
Expand Down Expand Up @@ -814,6 +831,10 @@ def query_hostapis(index=None):
overwritten by assigning to `default.device` -- take(s)
precedence over `default.hostapi` and the information in
the abovementioned dictionaries.
``'api'``
A namedtuple containing the platform-specific API from
PortAudio. If a platform-specific API is unavailable, this
is None.
See Also
--------
Expand All @@ -827,12 +848,17 @@ def query_hostapis(index=None):
if not info:
raise PortAudioError('Error querying host API {0}'.format(index))
assert info.structVersion == 1
try:
api = _get_host_api(info.type)
except KeyError:
api = None
return {
'name': _ffi.string(info.name).decode(),
'devices': [_lib.Pa_HostApiDeviceIndexToDeviceIndex(index, i)
for i in range(info.deviceCount)],
'default_input_device': info.defaultInputDevice,
'default_output_device': info.defaultOutputDevice,
'api': api,
}


Expand Down Expand Up @@ -2324,6 +2350,40 @@ class CallbackAbort(Exception):
"""


# Host-API:


_api_dicts = {}
def _get_host_api(hostapi_typeid):
"""Lookup hostapi_typeid and return the results as a namedtuple.
Parameters
----------
hostapi_typeid : int
*hostapi_typeid* is a value from enum PaHostApiTypeId, such as
_lib.paASIO
Example
-------
api = _get_host_api(_lib.paASIO)
extra_settings = api.Settings(channel_selectors=[12, 13])
available_buffer_sizes = api.get_available_buffer_sizes(device)
Implementation Notes
--------------------
The fields in the returned namedtuple are formed from a dict, and thus,
index and iteration order is not guaranteed.
"""
api_dict = _api_dicts[hostapi_typeid]
API = _namedtuple('_API_'+str(hostapi_typeid), api_dict.keys())
api = API(**api_dict)
return api


# Host-API: ASIO


class AsioSettings(object):

def __init__(self, channel_selectors):
Expand Down Expand Up @@ -2376,6 +2436,109 @@ def __init__(self, channel_selectors):
channelSelectors=self._selectors))


_api_asio_buf_sz = _namedtuple('_api_asio_buf_sz', ('min', 'max', 'preferred',
'granularity'))
def _api_asio_get_available_buffer_sizes(device):
"""Retrieve legal native buffer sizes for the specificed device, in
sample frames.
Parameters
----------
device : int
Device ID. (aka The global index of the PortAudio device.)
Returns
-------
namedtuple containing:
min : int
the minimum buffer size value.
max : int
the maximum buffer size value.
preferred : int
the preferred buffer size value.
granularity : int
the step size used to compute the legal values between
minBufferSizeFrames and maxBufferSizeFrames. If granularity is
-1 then available buffer size values are powers of two.
@see ASIOGetBufferSize in the ASIO SDK.
"""
min = _ffi.new('long[1]')
max = _ffi.new('long[1]')
pref = _ffi.new('long[1]')
gran = _ffi.new('long[1]')
_check(_lib.PaAsio_GetAvailableBufferSizes(device, min, max, pref, gran))
# Let's be friendly and return a namedtuple...
return _api_asio_buf_sz(min=min[0], max=max[0], preferred=pref[0],
granularity=gran[0])


def _api_asio_get_input_channel_name(device, channel):
"""Retrieve the name of the specified output channel.
Parameters
----------
device : int
Device ID. (aka The global index of the PortAudio device.)
channel : int
Channel number from 0 to max_*_channels-1.
Returns
-------
The channel's name : str
"""
channel_name = _ffi.new('char*[1]')
_check(_lib.PaAsio_GetInputChannelName(device, channel, channel_name))
return _ffi.string(channel_name[0]).decode()


def _api_asio_get_output_channel_name(device, channel):
"""Retrieve the name of the specified output channel.
Parameters
----------
device : int
Device ID. (aka The global index of the PortAudio device.)
channel : int
Channel number from 0 to max_*_channels-1.
Returns
-------
The channel's name : str
"""
channel_name = _ffi.new('char*[1]')
_check(_lib.PaAsio_GetOutputChannelName(device, channel, channel_name))
return _ffi.string(channel_name[0]).decode()


def _api_asio_set_stream_sample_rate(stream, sample_rate):
"""Set stream sample rate.
Parameters
----------
stream : an open stream
Device ID. (aka The global index of the PortAudio device.)
sample_rate : float
"""
_check(_lib.PaAsio_SetStreamSampleRate(stream._ptr, sample_rate))


_api_dicts[_lib.paASIO] = dict(
Settings = AsioSettings,
get_available_buffer_sizes = _api_asio_get_available_buffer_sizes,
get_input_channel_name = _api_asio_get_input_channel_name,
get_output_channel_name = _api_asio_get_output_channel_name,
set_stream_sample_rate = _api_asio_set_stream_sample_rate,
)


# Host-API: Core Audio


class CoreAudioSettings(object):

def __init__(self, channel_map=None, change_device_parameters=False,
Expand Down Expand Up @@ -2468,6 +2631,133 @@ def __init__(self, channel_map=None, change_device_parameters=False,
len(self._channel_map))


def _api_coreaudio_get_input_channel_name(device, channel):
"""Retrieve the name of the specified input channel.
Parameters
----------
device : int
Device ID. (aka The global index of the PortAudio device.)
channel : int
Channel number from 0 to max_*_channels-1.
"""
return _ffi.string(_lib.PaMacCore_GetChannelName(device, channel, True)
).decode()


def _api_coreaudio_get_output_channel_name(device, channel):
"""Retrieve the name of the specified output channel.
Parameters
----------
device : int
Device ID. (aka The global index of the PortAudio device.)
channel : int
Channel number from 0 to max_*_channels-1.
"""
return _ffi.string(_lib.PaMacCore_GetChannelName(device, channel, False)
).decode()


_api_coreaudio_buf_sz = _namedtuple('_api_coreaudio_buf_sz', ('min', 'max'))
def _api_coreaudio_get_buffer_size_range(device):
"""Retrieve the range of legal native buffer sizes for the
specificed device, in sample frames.
Parameters
----------
device : int
Device ID. (aka The global index of the PortAudio device.)
Returns
-------
namedtuple containing:
min : int
the minimum buffer size value.
max : int
the maximum buffer size value.
See Also
--------
kAudioDevicePropertyBufferFrameSizeRange in the CoreAudio SDK.
"""
min = _ffi.new('long[1]')
max = _ffi.new('long[1]')
_check(_lib.PaMacCore_GetBufferSizeRange(device, min, max))
return _api_coreaudio_buf_sz(min=min[0], max=max[0])


_api_dicts[_lib.paCoreAudio] = dict(
Settings = CoreAudioSettings,
get_input_channel_name = _api_coreaudio_get_input_channel_name,
get_output_channel_name = _api_coreaudio_get_output_channel_name,
get_buffer_size_range = _api_coreaudio_get_buffer_size_range,
)


# Host-API: ALSA


def _api_alsa_enable_realtime_scheduling(stream, enable):
""" Instruct whether to enable real-time priority when starting the
audio thread.
If this is turned on by the stream is started, the audio callback
thread will be created with the FIFO scheduling policy, which is
suitable for realtime operation.
"""
_lib.PaAlsa_EnableRealtimeScheduling(stream._ptr, enable)


def _api_alsa_get_stream_input_card(stream):
"""Get the ALSA-lib card index of this stream's input device."""
card = _ffi.new('int[1]')
_check(_lib.PaAlsa_GetStreamInputCard(stream._ptr, card))
return card[0]


def _api_alsa_get_stream_output_card(stream):
"""Get the ALSA-lib card index of this stream's output device."""
card = _ffi.new('int[1]')
_check(_lib.PaAlsa_GetStreamOutputCard(stream._ptr, card))
return card[0]


def _api_alsa_set_num_periods(num_periods):
"""Set the number of periods (buffer fragments) to configure devices
with.
By default the number of periods is 4, this is the lowest number of
periods that works well on the author's soundcard.
"""
_check(_lib.PaAlsa_SetNumPeriods(num_periods))


def _api_alsa_set_retries_busy(retries):
"""Set the maximum number of times to retry opening busy device
(sleeping for a short interval inbetween).
"""
_check(_lib.PaAlsa_SetRetriesBusy(retries))


_api_dicts[_lib.paALSA] = dict(
enable_realtime_scheduling = _api_alsa_enable_realtime_scheduling,
get_stream_input_card = _api_alsa_get_stream_input_card,
get_stream_output_card = _api_alsa_get_stream_output_card,
set_num_periods = _api_alsa_set_num_periods,
set_retries_busy = _api_alsa_set_retries_busy,
)


# Host-API: WASAPI


class WasapiSettings(object):

def __init__(self, exclusive=False):
Expand Down Expand Up @@ -2509,6 +2799,30 @@ def __init__(self, exclusive=False):
))


_api_wasapi_buf_sz = _namedtuple('_api_wasapi_buf_sz', ('max_in', 'max_out'))
def _api_wasapi_get_frames_per_host_buffer(stream):
"""Get number of frames per host buffer.
Returns
-------
This returns the maximal value of frames of WASAPI buffer which can
be locked for operations. Use this method as helper to findout
maximal values of inputFrames / outputFrames of
PaWasapiHostProcessorCallback.
"""
max_in = _ffi.new('unsigned int[1]')
max_out = _ffi.new('unsigned int[1]')
_check(_lib.PaWasapi_GetFramesPerHostBuffer(stream._ptr, max_in, max_out))
return _api_wasapi_buf_sz(max_in=max_in[0], max_out=max_out[0])


_api_dicts[_lib.paWASAPI] = dict(
Settings = WasapiSettings,
get_frames_per_host_buffer = _api_wasapi_get_frames_per_host_buffer,
)


class _CallbackContext(object):
"""Helper class for re-use in play()/rec()/playrec() callbacks."""

Expand Down

0 comments on commit 8ccbe85

Please sign in to comment.