Skip to content

Commit 8ccbe85

Browse files
committed
Include platform-specific API in query_hostapis results
1 parent 85729e8 commit 8ccbe85

File tree

1 file changed

+314
-0
lines changed

1 file changed

+314
-0
lines changed

sounddevice.py

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import atexit as _atexit
2929
from cffi import FFI as _FFI
30+
from collections import namedtuple as _namedtuple
3031
import os as _os
3132
import platform as _platform
3233
import sys as _sys
@@ -247,6 +248,7 @@
247248
void PaMacCore_SetupStreamInfo( PaMacCoreStreamInfo *data, unsigned long flags );
248249
void PaMacCore_SetupChannelMap( PaMacCoreStreamInfo *data, const SInt32 * const channelMap, unsigned long channelMapSize );
249250
const char *PaMacCore_GetChannelName( int device, int channelIndex, bool input );
251+
PaError PaMacCore_GetBufferSizeRange( PaDeviceIndex device, long *minBufferSizeFrames, long *maxBufferSizeFrames );
250252
#define paMacCoreChangeDeviceParameters 0x01
251253
#define paMacCoreFailIfConversionRequired 0x02
252254
#define paMacCoreConversionQualityMin 0x0100
@@ -265,6 +267,11 @@
265267
266268
/* pa_asio.h */
267269
270+
PaError PaAsio_GetAvailableBufferSizes( PaDeviceIndex device, long *minBufferSizeFrames, long *maxBufferSizeFrames, long *preferredBufferSizeFrames, long *granularity );
271+
PaError PaAsio_GetInputChannelName( PaDeviceIndex device, int channelIndex, const char** channelName );
272+
PaError PaAsio_GetOutputChannelName( PaDeviceIndex device, int channelIndex, const char** channelName );
273+
PaError PaAsio_SetStreamSampleRate( PaStream* stream, double sampleRate );
274+
268275
#define paAsioUseChannelSelectors 0x01
269276
270277
typedef struct PaAsioStreamInfo
@@ -276,6 +283,14 @@
276283
int *channelSelectors;
277284
} PaAsioStreamInfo;
278285
286+
/* pa_linux_alsa.h */
287+
288+
void PaAlsa_EnableRealtimeScheduling( PaStream *s, int enable );
289+
PaError PaAlsa_GetStreamInputCard( PaStream *s, int *card );
290+
PaError PaAlsa_GetStreamOutputCard( PaStream *s, int *card );
291+
PaError PaAlsa_SetNumPeriods( int numPeriods );
292+
PaError PaAlsa_SetRetriesBusy( int retries );
293+
279294
/* pa_win_wasapi.h */
280295
281296
typedef enum PaWasapiFlags
@@ -337,6 +352,8 @@
337352
PaWasapiStreamCategory streamCategory;
338353
PaWasapiStreamOption streamOption;
339354
} PaWasapiStreamInfo;
355+
356+
PaError PaWasapi_GetFramesPerHostBuffer( PaStream *pStream, unsigned int *nInput, unsigned int *nOutput );
340357
""")
341358

342359
try:
@@ -814,6 +831,10 @@ def query_hostapis(index=None):
814831
overwritten by assigning to `default.device` -- take(s)
815832
precedence over `default.hostapi` and the information in
816833
the abovementioned dictionaries.
834+
``'api'``
835+
A namedtuple containing the platform-specific API from
836+
PortAudio. If a platform-specific API is unavailable, this
837+
is None.
817838
818839
See Also
819840
--------
@@ -827,12 +848,17 @@ def query_hostapis(index=None):
827848
if not info:
828849
raise PortAudioError('Error querying host API {0}'.format(index))
829850
assert info.structVersion == 1
851+
try:
852+
api = _get_host_api(info.type)
853+
except KeyError:
854+
api = None
830855
return {
831856
'name': _ffi.string(info.name).decode(),
832857
'devices': [_lib.Pa_HostApiDeviceIndexToDeviceIndex(index, i)
833858
for i in range(info.deviceCount)],
834859
'default_input_device': info.defaultInputDevice,
835860
'default_output_device': info.defaultOutputDevice,
861+
'api': api,
836862
}
837863

838864

@@ -2324,6 +2350,40 @@ class CallbackAbort(Exception):
23242350
"""
23252351

23262352

2353+
# Host-API:
2354+
2355+
2356+
_api_dicts = {}
2357+
def _get_host_api(hostapi_typeid):
2358+
"""Lookup hostapi_typeid and return the results as a namedtuple.
2359+
2360+
Parameters
2361+
----------
2362+
hostapi_typeid : int
2363+
*hostapi_typeid* is a value from enum PaHostApiTypeId, such as
2364+
_lib.paASIO
2365+
2366+
Example
2367+
-------
2368+
api = _get_host_api(_lib.paASIO)
2369+
extra_settings = api.Settings(channel_selectors=[12, 13])
2370+
available_buffer_sizes = api.get_available_buffer_sizes(device)
2371+
2372+
Implementation Notes
2373+
--------------------
2374+
The fields in the returned namedtuple are formed from a dict, and thus,
2375+
index and iteration order is not guaranteed.
2376+
2377+
"""
2378+
api_dict = _api_dicts[hostapi_typeid]
2379+
API = _namedtuple('_API_'+str(hostapi_typeid), api_dict.keys())
2380+
api = API(**api_dict)
2381+
return api
2382+
2383+
2384+
# Host-API: ASIO
2385+
2386+
23272387
class AsioSettings(object):
23282388

23292389
def __init__(self, channel_selectors):
@@ -2376,6 +2436,109 @@ def __init__(self, channel_selectors):
23762436
channelSelectors=self._selectors))
23772437

23782438

2439+
_api_asio_buf_sz = _namedtuple('_api_asio_buf_sz', ('min', 'max', 'preferred',
2440+
'granularity'))
2441+
def _api_asio_get_available_buffer_sizes(device):
2442+
"""Retrieve legal native buffer sizes for the specificed device, in
2443+
sample frames.
2444+
2445+
Parameters
2446+
----------
2447+
device : int
2448+
Device ID. (aka The global index of the PortAudio device.)
2449+
2450+
Returns
2451+
-------
2452+
namedtuple containing:
2453+
min : int
2454+
the minimum buffer size value.
2455+
max : int
2456+
the maximum buffer size value.
2457+
preferred : int
2458+
the preferred buffer size value.
2459+
granularity : int
2460+
the step size used to compute the legal values between
2461+
minBufferSizeFrames and maxBufferSizeFrames. If granularity is
2462+
-1 then available buffer size values are powers of two.
2463+
2464+
@see ASIOGetBufferSize in the ASIO SDK.
2465+
2466+
"""
2467+
min = _ffi.new('long[1]')
2468+
max = _ffi.new('long[1]')
2469+
pref = _ffi.new('long[1]')
2470+
gran = _ffi.new('long[1]')
2471+
_check(_lib.PaAsio_GetAvailableBufferSizes(device, min, max, pref, gran))
2472+
# Let's be friendly and return a namedtuple...
2473+
return _api_asio_buf_sz(min=min[0], max=max[0], preferred=pref[0],
2474+
granularity=gran[0])
2475+
2476+
2477+
def _api_asio_get_input_channel_name(device, channel):
2478+
"""Retrieve the name of the specified output channel.
2479+
2480+
Parameters
2481+
----------
2482+
device : int
2483+
Device ID. (aka The global index of the PortAudio device.)
2484+
channel : int
2485+
Channel number from 0 to max_*_channels-1.
2486+
2487+
Returns
2488+
-------
2489+
The channel's name : str
2490+
2491+
"""
2492+
channel_name = _ffi.new('char*[1]')
2493+
_check(_lib.PaAsio_GetInputChannelName(device, channel, channel_name))
2494+
return _ffi.string(channel_name[0]).decode()
2495+
2496+
2497+
def _api_asio_get_output_channel_name(device, channel):
2498+
"""Retrieve the name of the specified output channel.
2499+
2500+
Parameters
2501+
----------
2502+
device : int
2503+
Device ID. (aka The global index of the PortAudio device.)
2504+
channel : int
2505+
Channel number from 0 to max_*_channels-1.
2506+
2507+
Returns
2508+
-------
2509+
The channel's name : str
2510+
2511+
"""
2512+
channel_name = _ffi.new('char*[1]')
2513+
_check(_lib.PaAsio_GetOutputChannelName(device, channel, channel_name))
2514+
return _ffi.string(channel_name[0]).decode()
2515+
2516+
2517+
def _api_asio_set_stream_sample_rate(stream, sample_rate):
2518+
"""Set stream sample rate.
2519+
2520+
Parameters
2521+
----------
2522+
stream : an open stream
2523+
Device ID. (aka The global index of the PortAudio device.)
2524+
sample_rate : float
2525+
2526+
"""
2527+
_check(_lib.PaAsio_SetStreamSampleRate(stream._ptr, sample_rate))
2528+
2529+
2530+
_api_dicts[_lib.paASIO] = dict(
2531+
Settings = AsioSettings,
2532+
get_available_buffer_sizes = _api_asio_get_available_buffer_sizes,
2533+
get_input_channel_name = _api_asio_get_input_channel_name,
2534+
get_output_channel_name = _api_asio_get_output_channel_name,
2535+
set_stream_sample_rate = _api_asio_set_stream_sample_rate,
2536+
)
2537+
2538+
2539+
# Host-API: Core Audio
2540+
2541+
23792542
class CoreAudioSettings(object):
23802543

23812544
def __init__(self, channel_map=None, change_device_parameters=False,
@@ -2468,6 +2631,133 @@ def __init__(self, channel_map=None, change_device_parameters=False,
24682631
len(self._channel_map))
24692632

24702633

2634+
def _api_coreaudio_get_input_channel_name(device, channel):
2635+
"""Retrieve the name of the specified input channel.
2636+
2637+
Parameters
2638+
----------
2639+
device : int
2640+
Device ID. (aka The global index of the PortAudio device.)
2641+
channel : int
2642+
Channel number from 0 to max_*_channels-1.
2643+
2644+
"""
2645+
return _ffi.string(_lib.PaMacCore_GetChannelName(device, channel, True)
2646+
).decode()
2647+
2648+
2649+
def _api_coreaudio_get_output_channel_name(device, channel):
2650+
"""Retrieve the name of the specified output channel.
2651+
2652+
Parameters
2653+
----------
2654+
device : int
2655+
Device ID. (aka The global index of the PortAudio device.)
2656+
channel : int
2657+
Channel number from 0 to max_*_channels-1.
2658+
2659+
"""
2660+
return _ffi.string(_lib.PaMacCore_GetChannelName(device, channel, False)
2661+
).decode()
2662+
2663+
2664+
_api_coreaudio_buf_sz = _namedtuple('_api_coreaudio_buf_sz', ('min', 'max'))
2665+
def _api_coreaudio_get_buffer_size_range(device):
2666+
"""Retrieve the range of legal native buffer sizes for the
2667+
specificed device, in sample frames.
2668+
2669+
Parameters
2670+
----------
2671+
device : int
2672+
Device ID. (aka The global index of the PortAudio device.)
2673+
2674+
Returns
2675+
-------
2676+
namedtuple containing:
2677+
min : int
2678+
the minimum buffer size value.
2679+
max : int
2680+
the maximum buffer size value.
2681+
2682+
See Also
2683+
--------
2684+
kAudioDevicePropertyBufferFrameSizeRange in the CoreAudio SDK.
2685+
2686+
"""
2687+
min = _ffi.new('long[1]')
2688+
max = _ffi.new('long[1]')
2689+
_check(_lib.PaMacCore_GetBufferSizeRange(device, min, max))
2690+
return _api_coreaudio_buf_sz(min=min[0], max=max[0])
2691+
2692+
2693+
_api_dicts[_lib.paCoreAudio] = dict(
2694+
Settings = CoreAudioSettings,
2695+
get_input_channel_name = _api_coreaudio_get_input_channel_name,
2696+
get_output_channel_name = _api_coreaudio_get_output_channel_name,
2697+
get_buffer_size_range = _api_coreaudio_get_buffer_size_range,
2698+
)
2699+
2700+
2701+
# Host-API: ALSA
2702+
2703+
2704+
def _api_alsa_enable_realtime_scheduling(stream, enable):
2705+
""" Instruct whether to enable real-time priority when starting the
2706+
audio thread.
2707+
2708+
If this is turned on by the stream is started, the audio callback
2709+
thread will be created with the FIFO scheduling policy, which is
2710+
suitable for realtime operation.
2711+
2712+
"""
2713+
_lib.PaAlsa_EnableRealtimeScheduling(stream._ptr, enable)
2714+
2715+
2716+
def _api_alsa_get_stream_input_card(stream):
2717+
"""Get the ALSA-lib card index of this stream's input device."""
2718+
card = _ffi.new('int[1]')
2719+
_check(_lib.PaAlsa_GetStreamInputCard(stream._ptr, card))
2720+
return card[0]
2721+
2722+
2723+
def _api_alsa_get_stream_output_card(stream):
2724+
"""Get the ALSA-lib card index of this stream's output device."""
2725+
card = _ffi.new('int[1]')
2726+
_check(_lib.PaAlsa_GetStreamOutputCard(stream._ptr, card))
2727+
return card[0]
2728+
2729+
2730+
def _api_alsa_set_num_periods(num_periods):
2731+
"""Set the number of periods (buffer fragments) to configure devices
2732+
with.
2733+
2734+
By default the number of periods is 4, this is the lowest number of
2735+
periods that works well on the author's soundcard.
2736+
2737+
"""
2738+
_check(_lib.PaAlsa_SetNumPeriods(num_periods))
2739+
2740+
2741+
def _api_alsa_set_retries_busy(retries):
2742+
"""Set the maximum number of times to retry opening busy device
2743+
(sleeping for a short interval inbetween).
2744+
2745+
"""
2746+
_check(_lib.PaAlsa_SetRetriesBusy(retries))
2747+
2748+
2749+
_api_dicts[_lib.paALSA] = dict(
2750+
enable_realtime_scheduling = _api_alsa_enable_realtime_scheduling,
2751+
get_stream_input_card = _api_alsa_get_stream_input_card,
2752+
get_stream_output_card = _api_alsa_get_stream_output_card,
2753+
set_num_periods = _api_alsa_set_num_periods,
2754+
set_retries_busy = _api_alsa_set_retries_busy,
2755+
)
2756+
2757+
2758+
# Host-API: WASAPI
2759+
2760+
24712761
class WasapiSettings(object):
24722762

24732763
def __init__(self, exclusive=False):
@@ -2509,6 +2799,30 @@ def __init__(self, exclusive=False):
25092799
))
25102800

25112801

2802+
_api_wasapi_buf_sz = _namedtuple('_api_wasapi_buf_sz', ('max_in', 'max_out'))
2803+
def _api_wasapi_get_frames_per_host_buffer(stream):
2804+
"""Get number of frames per host buffer.
2805+
2806+
Returns
2807+
-------
2808+
This returns the maximal value of frames of WASAPI buffer which can
2809+
be locked for operations. Use this method as helper to findout
2810+
maximal values of inputFrames / outputFrames of
2811+
PaWasapiHostProcessorCallback.
2812+
2813+
"""
2814+
max_in = _ffi.new('unsigned int[1]')
2815+
max_out = _ffi.new('unsigned int[1]')
2816+
_check(_lib.PaWasapi_GetFramesPerHostBuffer(stream._ptr, max_in, max_out))
2817+
return _api_wasapi_buf_sz(max_in=max_in[0], max_out=max_out[0])
2818+
2819+
2820+
_api_dicts[_lib.paWASAPI] = dict(
2821+
Settings = WasapiSettings,
2822+
get_frames_per_host_buffer = _api_wasapi_get_frames_per_host_buffer,
2823+
)
2824+
2825+
25122826
class _CallbackContext(object):
25132827
"""Helper class for re-use in play()/rec()/playrec() callbacks."""
25142828

0 commit comments

Comments
 (0)