Repository navigation
Add AlsaSettings for opening an ALSA PCM by address #644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
07f9935
8e0aeba
c783241
ffcda44
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -91,6 +91,9 @@ | |||||||||
| next(iter(_sounddevice_data.__path__)), 'portaudio-binaries', _libname) | ||||||||||
| _lib: ... = _ffi.dlopen(_libname) | ||||||||||
|
|
||||||||||
| DEVICE_FROM_EXTRA_SETTINGS = _lib.paUseHostApiSpecificDeviceSpecification | ||||||||||
| """Select a device through host-API-specific ``extra_settings``.""" | ||||||||||
|
|
||||||||||
| _sampleformats: ... = { | ||||||||||
| 'float32': _lib.paFloat32, | ||||||||||
| 'int32': _lib.paInt32, | ||||||||||
|
|
@@ -690,7 +693,7 @@ def check_input_settings(device=None, channels=None, dtype=None, | |||||||||
| """ | ||||||||||
| parameters, dtype, samplesize, samplerate = _get_stream_parameters( | ||||||||||
| 'input', device=device, channels=channels, dtype=dtype, latency=None, | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole
Suggested change
|
||||||||||
| extra_settings=extra_settings, samplerate=samplerate) | ||||||||||
| extra_settings=extra_settings, samplerate=samplerate, for_check=True) | ||||||||||
| _check(_lib.Pa_IsFormatSupported(parameters, _ffi.NULL, samplerate)) | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
@@ -704,7 +707,7 @@ def check_output_settings(device=None, channels=None, dtype=None, | |||||||||
| """ | ||||||||||
| parameters, dtype, samplesize, samplerate = _get_stream_parameters( | ||||||||||
| 'output', device=device, channels=channels, dtype=dtype, latency=None, | ||||||||||
| extra_settings=extra_settings, samplerate=samplerate) | ||||||||||
| extra_settings=extra_settings, samplerate=samplerate, for_check=True) | ||||||||||
| _check(_lib.Pa_IsFormatSupported(_ffi.NULL, parameters, samplerate)) | ||||||||||
|
|
||||||||||
|
|
||||||||||
|
|
@@ -808,6 +811,12 @@ def __init__(self, kind, samplerate=None, blocksize=None, device=None, | |||||||||
| if prime_output_buffers_using_stream_callback: | ||||||||||
| stream_flags |= _lib.paPrimeOutputBuffersUsingStreamCallback | ||||||||||
|
|
||||||||||
| # Retain effective settings, including their C buffers, through close. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is hard to understand. I guess it's about what in other places is called "keep alive"? |
||||||||||
| if extra_settings is None: | ||||||||||
| extra_settings = tuple(default.extra_settings) | ||||||||||
| self._extra_settings = (tuple(_split(extra_settings)), | ||||||||||
| tuple(default.extra_settings)) | ||||||||||
|
Comment on lines
+815
to
+818
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain how that combination of Note that |
||||||||||
|
|
||||||||||
| if kind == 'duplex': | ||||||||||
| idevice, odevice = _split(device) | ||||||||||
| ichannels, ochannels = _split(channels) | ||||||||||
|
|
@@ -1661,6 +1670,10 @@ def __init__(self, samplerate=None, blocksize=None, | |||||||||
| all space-separated parts in the right order. Each device | ||||||||||
| string contains the name of the corresponding host API in | ||||||||||
| the end. The string comparison is case-insensitive. | ||||||||||
| Use `DEVICE_FROM_EXTRA_SETTINGS` to select a device through | ||||||||||
| *extra_settings*, e.g. with `AlsaSettings`. In this case, | ||||||||||
| channels, sample rate and numeric latency must be supplied | ||||||||||
| explicitly or through defaults. | ||||||||||
|
Comment on lines
+1674
to
+1676
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I don't think this needs to be repeated here:
Suggested change
|
||||||||||
| channels : int or pair of int, optional | ||||||||||
| The number of channels of sound to be delivered to the | ||||||||||
| stream callback or accessed by `read()` or `write()`. It | ||||||||||
|
|
@@ -2167,7 +2180,7 @@ class default: | |||||||||
|
|
||||||||||
| See Also | ||||||||||
| -------- | ||||||||||
| AsioSettings, CoreAudioSettings, WasapiSettings | ||||||||||
| AlsaSettings, AsioSettings, CoreAudioSettings, WasapiSettings | ||||||||||
|
|
||||||||||
| """ | ||||||||||
| samplerate: ... = None | ||||||||||
|
|
@@ -2316,6 +2329,56 @@ class CallbackAbort(Exception): | |||||||||
| """ | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class AlsaSettings: | ||||||||||
|
|
||||||||||
| def __init__(self, device_string): | ||||||||||
| """ALSA-specific settings for opening a PCM by address. | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other classes use something like this:
Suggested change
You can also use the next sentence from the other classes, to keep them consistent. |
||||||||||
|
|
||||||||||
| Use as ``extra_settings`` with | ||||||||||
| ``device=sd.DEVICE_FROM_EXTRA_SETTINGS``. Supplying these settings | ||||||||||
| alone does not override `default.device`. Channels and sample rate | ||||||||||
| must be supplied explicitly or through defaults. Streams also | ||||||||||
| require a numeric latency in seconds. | ||||||||||
|
Comment on lines
+2337
to
+2341
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be moved to the documentation of |
||||||||||
|
|
||||||||||
| Parameters | ||||||||||
| ---------- | ||||||||||
| device_string : str | ||||||||||
| ALSA PCM address, such as ``'hw:2,0'`` or a named PCM from the | ||||||||||
| ALSA configuration. It must be nonempty and contain no NUL. | ||||||||||
| An ALSA address is not a persistent hardware identifier. | ||||||||||
|
|
||||||||||
| Notes | ||||||||||
| ----- | ||||||||||
| The stream's device attribute reports PortAudio's special value | ||||||||||
| ``-2`` when `DEVICE_FROM_EXTRA_SETTINGS` is selected. | ||||||||||
| PortAudio rejects these settings with a regular ALSA device index. | ||||||||||
| For direct output with ``mapping=[1]`` in `play()` or `playrec()`, | ||||||||||
| use `OutputStream` with explicit channels instead; automatic | ||||||||||
| stereo detection requires enumerated device information. | ||||||||||
|
Comment on lines
+2350
to
+2357
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these notes also belong into the |
||||||||||
|
|
||||||||||
| Examples | ||||||||||
| -------- | ||||||||||
| >>> import sounddevice as sd | ||||||||||
| >>> with sd.RawInputStream( | ||||||||||
| ... device=sd.DEVICE_FROM_EXTRA_SETTINGS, | ||||||||||
| ... samplerate=48000, channels=2, dtype='int16', | ||||||||||
| ... blocksize=1024, latency=4096 / 48000, | ||||||||||
| ... extra_settings=sd.AlsaSettings('null')) as stream: | ||||||||||
| ... data, overflowed = stream.read(1024) | ||||||||||
|
|
||||||||||
| """ | ||||||||||
| if not isinstance(device_string, str): | ||||||||||
| raise TypeError('device_string must be a string') | ||||||||||
| if not device_string or '\0' in device_string: | ||||||||||
| raise ValueError('device_string must be nonempty and contain no NUL') | ||||||||||
| _check(_lib.Pa_HostApiTypeIdToHostApiIndex(_lib.paALSA), | ||||||||||
| 'ALSA host API is unavailable') | ||||||||||
| self._device_string = _ffi.new('char[]', device_string.encode('utf-8')) | ||||||||||
| self._streaminfo: ... = _ffi.new('PaAlsaStreamInfo*') | ||||||||||
| _lib.PaAlsa_InitializeStreamInfo(self._streaminfo) | ||||||||||
| self._streaminfo.deviceString = self._device_string | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class AsioSettings: | ||||||||||
|
|
||||||||||
| def __init__(self, channel_selectors): | ||||||||||
|
|
@@ -2730,11 +2793,12 @@ def _check_dtype(dtype): | |||||||||
|
|
||||||||||
|
|
||||||||||
| def _get_stream_parameters(kind, device, channels, dtype, latency, | ||||||||||
| extra_settings: ..., samplerate): | ||||||||||
| extra_settings: ..., samplerate, *, for_check=False): | ||||||||||
| """Get parameters for one direction (input or output) of a stream.""" | ||||||||||
| assert kind in ('input', 'output') | ||||||||||
| if device is None: | ||||||||||
| device = default.device | ||||||||||
| if extra_settings is None: | ||||||||||
| extra_settings = default.extra_settings | ||||||||||
| extra_settings = _select_input_or_output(extra_settings, kind) | ||||||||||
| device = _get_device_id(device, kind, raise_on_error=True) | ||||||||||
| if channels is None: | ||||||||||
| channels = default.channels | ||||||||||
|
|
@@ -2745,15 +2809,36 @@ def _get_stream_parameters(kind, device, channels, dtype, latency, | |||||||||
| if latency is None: | ||||||||||
| latency = default.latency | ||||||||||
| latency = _select_input_or_output(latency, kind) | ||||||||||
| if extra_settings is None: | ||||||||||
| extra_settings = default.extra_settings | ||||||||||
| extra_settings = _select_input_or_output(extra_settings, kind) | ||||||||||
| if samplerate is None: | ||||||||||
| samplerate = default.samplerate | ||||||||||
|
|
||||||||||
| info = query_devices(device) | ||||||||||
| if channels is None: | ||||||||||
| channels = info['max_' + kind + '_channels'] | ||||||||||
| if for_check: | ||||||||||
| latency = 0.0 # Pa_IsFormatSupported ignores suggested latency. | ||||||||||
| if channels is None or samplerate is None or latency in ('low', 'high'): | ||||||||||
| info = query_devices(device) | ||||||||||
| if channels is None: | ||||||||||
| channels = info['max_' + kind + '_channels'] | ||||||||||
| if samplerate is None: | ||||||||||
| samplerate = info['default_samplerate'] | ||||||||||
| if latency in ('low', 'high'): | ||||||||||
| latency = info['default_' + latency + '_' + kind + '_latency'] | ||||||||||
|
|
||||||||||
| if device == DEVICE_FROM_EXTRA_SETTINGS: | ||||||||||
| import math | ||||||||||
| import numbers | ||||||||||
| if extra_settings is None: | ||||||||||
| raise ValueError('DEVICE_FROM_EXTRA_SETTINGS requires extra_settings') | ||||||||||
| if (isinstance(channels, bool) or | ||||||||||
| not isinstance(channels, numbers.Integral) or channels < 1): | ||||||||||
| raise ValueError('A positive channel count is required') | ||||||||||
| if (isinstance(samplerate, bool) or | ||||||||||
| not isinstance(samplerate, numbers.Real) or | ||||||||||
| not math.isfinite(samplerate) or samplerate <= 0): | ||||||||||
| raise ValueError('A positive finite sample rate is required') | ||||||||||
| if (isinstance(latency, bool) or | ||||||||||
| not isinstance(latency, numbers.Real) or | ||||||||||
| not math.isfinite(latency) or latency < 0): | ||||||||||
| raise ValueError('Finite numeric latency >= 0 is required') | ||||||||||
| try: | ||||||||||
| # If NumPy is available, get canonical dtype name | ||||||||||
| dtype = _sys.modules['numpy'].dtype(dtype).name | ||||||||||
|
|
@@ -2764,10 +2849,6 @@ def _get_stream_parameters(kind, device, channels, dtype, latency, | |||||||||
| except KeyError as e: | ||||||||||
| raise ValueError('Invalid ' + kind + ' sample format') from e | ||||||||||
| samplesize = _check(_lib.Pa_GetSampleSize(sampleformat)) | ||||||||||
| if latency in ('low', 'high'): | ||||||||||
| latency = info['default_' + latency + '_' + kind + '_latency'] | ||||||||||
| if samplerate is None: | ||||||||||
| samplerate = info['default_samplerate'] | ||||||||||
| parameters: ... = _ffi.new('PaStreamParameters*', ( | ||||||||||
| device, channels, sampleformat, latency, | ||||||||||
| extra_settings._streaminfo if extra_settings else _ffi.NULL)) | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you adding this text here instead of
sounddevice.py?Also, this text seems a bit long ... what about linking to
AlsaSettings(which is AFAIK the only host API that supports it) and having all the details documented there, avoiding some repetition?