Compare commits

..

3 Commits

Author SHA1 Message Date
Lars Immisch
9693a932a3 Add echo (seed for loopback.py) 2024-02-16 14:14:37 +01:00
Lars Immisch
ee1c3a546b Documentation for type hints 2024-02-05 23:34:41 +00:00
Lars Immisch
e4ec455ffa Add type hints 2024-02-05 23:12:44 +00:00
17 changed files with 222 additions and 296 deletions

View File

@@ -1,17 +1,7 @@
# Version 0.11.0
- Fixed `Mixer.getvolume()` returning outdated value (#126)
- Fixed PCM crashing with some sample formats due to buffer size
miscalculation
- Fixed `PCM.read()` ignoring overruns (regression in 0.10.0)
- Reverted to `PCM.write()` not throwing an exception on playback buffer
underrun; instead, return -EPIPE like `PCM.read()` does on overrun (#130)
- Added `PCM.avail()` and `PCM.polldescriptors_revents()` functions
- Added `nominal_bits` and `physical_bits` entries to `PCM.info()`'s
return value
- Added Python type hint file, and adjusted documentation accordingly (#58)
- Improvements to the examples, in particular isine.py (#42)
Contributions by @ossilator and @viinikv.
# Version 0.10.1
- revert to not throwing an exception on playback buffer underrun;
instead, return -EPIPE like `PCM.read()` does on overrun; #131
- type hints
# Version 0.10.0
- assorted improvements (#123 from @ossilator)

View File

@@ -1,6 +1,6 @@
include *.py
include *.pyi
include alsaaudio.pyi
include CHANGES
include TODO
include LICENSE
recursive-include doc *.html *.gif *.png *.css *.py *.rst *.js *.json Makefile
recursive-include doc *.html *.gif *.png *.css *.py *.rst *.js *.json Makefile

View File

@@ -2,7 +2,7 @@
For documentation, see http://larsimmisch.github.io/pyalsaaudio/
> Author: Casper Wilstrup (cwi@aves.dk)
> Author: Casper Wilstrup (cwi@aves.dk)
> Maintainer: Lars Immisch (lars@ibp.de)
This package contains wrappers for accessing the
@@ -45,12 +45,12 @@ First, get the sources and change to the source directory:
$ git clone https://github.com/larsimmisch/pyalsaaudio.git
$ cd pyalsaaudio
```
Then, build:
```
$ python setup.py build
```
And install:
```
$ sudo python setup.py install

View File

@@ -403,7 +403,7 @@ static int alsapcm_setup(alsapcm_t *self)
snd_pcm_hw_params_get_period_size(hwparams, &self->periodsize, &dir);
snd_pcm_hw_params_get_periods(hwparams, &self->periods, &dir);
self->framesize = self->channels * snd_pcm_format_physical_width(self->format)/8;
self->framesize = self->channels * snd_pcm_hw_params_get_sbits(hwparams)/8;
return res;
}
@@ -598,12 +598,6 @@ alsapcm_dumpinfo(alsapcm_t *self, PyObject *args)
val = snd_pcm_hw_params_get_sbits(hwparams);
printf("significant bits = %d\n", val);
val = snd_pcm_format_width(self->format);
printf("nominal bits = %d\n", val);
val = snd_pcm_format_physical_width(self->format);
printf("physical bits = %d\n", val);
val = snd_pcm_hw_params_is_batch(hwparams);
printf("is batch = %d\n", val);
@@ -784,16 +778,6 @@ alsapcm_info(alsapcm_t *self, PyObject *args)
PyDict_SetItemString(info,"significant_bits", value);
Py_DECREF(value);
val = snd_pcm_format_width(self->format);
value=PyLong_FromUnsignedLong((unsigned long) val);
PyDict_SetItemString(info,"nominal_bits", value);
Py_DECREF(value);
val = snd_pcm_format_physical_width(self->format);
value=PyLong_FromUnsignedLong((unsigned long) val);
PyDict_SetItemString(info,"physical_bits", value);
Py_DECREF(value);
val = snd_pcm_hw_params_is_batch(hwparams);
value=PyBool_FromLong((unsigned long) val);
PyDict_SetItemString(info,"is_batch", value);
@@ -1170,37 +1154,6 @@ alsapcm_getchannels(alsapcm_t *self,PyObject *args)
return out;
}
static PyObject *
alsapcm_setchannels(alsapcm_t *self, PyObject *args)
{
int channels, saved;
int res;
if (!PyArg_ParseTuple(args,"i:setchannels", &channels))
return NULL;
if (!self->handle) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
PyErr_WarnEx(PyExc_DeprecationWarning,
"This function is deprecated. "
"Please use the named parameter `channels` to `PCM()` instead", 1);
saved = self->channels;
self->channels = channels;
res = alsapcm_setup(self);
if (res < 0)
{
self->channels = saved;
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(self->channels);
}
static PyObject *
alsapcm_pcmtype(alsapcm_t *self, PyObject *args)
{
@@ -1243,6 +1196,37 @@ alsapcm_cardname(alsapcm_t *self, PyObject *args)
return PyUnicode_FromString(self->cardname);
}
static PyObject *
alsapcm_setchannels(alsapcm_t *self, PyObject *args)
{
int channels, saved;
int res;
if (!PyArg_ParseTuple(args,"i:setchannels", &channels))
return NULL;
if (!self->handle) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
PyErr_WarnEx(PyExc_DeprecationWarning,
"This function is deprecated. "
"Please use the named parameter `channels` to `PCM()` instead", 1);
saved = self->channels;
self->channels = channels;
res = alsapcm_setup(self);
if (res < 0)
{
self->channels = saved;
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(self->channels);
}
static PyObject *
alsapcm_setrate(alsapcm_t *self, PyObject *args)
{
@@ -1750,7 +1734,6 @@ static PyMethodDef alsapcm_methods[] = {
{"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS},
{"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS},
{"cardname", (PyCFunction)alsapcm_cardname, METH_VARARGS},
{"getchannels", (PyCFunction)alsapcm_getchannels, METH_VARARGS},
{"setchannels", (PyCFunction)alsapcm_setchannels, METH_VARARGS},
{"setrate", (PyCFunction)alsapcm_setrate, METH_VARARGS},
{"setformat", (PyCFunction)alsapcm_setformat, METH_VARARGS},
@@ -1766,6 +1749,7 @@ static PyMethodDef alsapcm_methods[] = {
{"getformats", (PyCFunction)alsapcm_getformats, METH_VARARGS},
{"getratebounds", (PyCFunction)alsapcm_getratemaxmin, METH_VARARGS},
{"getrates", (PyCFunction)alsapcm_getrates, METH_VARARGS},
{"getchannels", (PyCFunction)alsapcm_getchannels, METH_VARARGS},
{"read", (PyCFunction)alsapcm_read, METH_VARARGS},
{"write", (PyCFunction)alsapcm_write, METH_VARARGS},
{"avail", (PyCFunction)alsapcm_avail, METH_VARARGS},

View File

@@ -75,61 +75,54 @@ VOLUME_UNITS_PERCENTAGE: int
VOLUME_UNITS_RAW: int
VOLUME_UNITS_DB: int
def pcms(pcmtype: int) -> list[str]: ...
def pcms(pcmtype:int) -> list[str]: ...
def cards() -> list[str]: ...
def mixers(cardindex: int = -1, device: str = 'default') -> list[str]: ...
def mixers(cardindex:int=-1, device:str='default') -> list[str]: ...
def asoundlib_version() -> str: ...
class PCM:
def __init__(type: int = PCM_PLAYBACK, mode: int = PCM_NORMAL, rate: int = 44100, channels: int = 2,
format: int = PCM_FORMAT_S16_LE, periodsize: int = 32, periods: int = 4,
device: str = 'default', cardindex: int = -1) -> PCM: ...
def close() -> None: ...
def dumpinfo() -> None: ...
def __init__(type:int=PCM_PLAYBACK, mode:int=PCM_NORMAL, rate:int=44100, channels:int=2,
format:int=PCM_FORMAT_S16_LE, periodsize:int=32, periods:int=4,
device:str='default', cardindex:int=-1) -> PCM: ...
def info() -> dict: ...
def state() -> int: ...
def htimestamp() -> tuple[int, int, int]: ...
def set_tstamp_mode(mode: int = PCM_TSTAMP_ENABLE) -> None: ...
def get_tstamp_mode() -> int: ...
def set_tstamp_type(type: int = PCM_TSTAMP_TYPE_GETTIMEOFDAY) -> None: ...
def get_tstamp_type() -> int: ...
def getformats() -> dict: ...
def getratebounds() -> tuple[int, int]: ...
def getrates() -> int | tuple[int, int] | list[int]: ...
def getchannels() -> list[int]: ...
def setchannels(nchannels: int) -> None: ...
def pcmtype() -> int: ...
def pcmmode() -> int: ...
def cardname() -> str: ...
def setchannels(nchannels: int) -> None: ...
def setrate(rate: int) -> None: ...
def setformat(format: int) -> int: ...
def setperiodsize(period: int) -> int: ...
def dumpinfo() -> None: ...
def state() -> int: ...
def read() -> tuple[int, bytes]: ...
def write(data: bytes) -> int: ...
def avail() -> int: ...
def pause(enable: bool = True) -> int: ...
def write(data:bytes) -> int: ...
def pause(enable:bool=True) -> int: ...
def drop() -> int: ...
def drain() -> int: ...
def polldescriptors() -> list[tuple[int, int]]: ...
def polldescriptors_revents(descriptors: list[tuple[int, int]]) -> int: ...
def set_tstamp_mode(mode:int=PCM_TSTAMP_ENABLE) -> None: ...
def get_tstamp_mode() -> int: ...
def set_tstamp_type(type:int=PCM_TSTAMP_TYPE_GETTIMEOFDAY) -> None: ...
def get_tstamp_type() -> int: ...
def htimestamp() -> tuple[int, int, int]: ...
class Mixer:
def __init__(control: str = 'Master', id: int = 0, cardindex: int = -1, device: str = 'default') -> Mixer: ...
def __init__(control:str='Master', id:int=0, cardindex:int=-1, device:str='default') -> Mixer: ...
def cardname() -> str: ...
def close() -> None: ...
def mixer() -> str: ...
def mixerid() -> int: ...
def switchcap() -> int: ...
def volumecap() -> int: ...
def getvolume(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE) -> int: ...
def getrange(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_RAW) -> tuple[int, int]: ...
def getenum() -> tuple[str, list[str]]: ...
def getenum() -> tuple[ str, list[str]]: ...
def setenum(index:int) -> None: ...
def getrange(pcmtype:int=PCM_PLAYBACK, units:int=VOLUME_UNITS_RAW) -> tuple[int, int]: ...
def getvolume(pcmtype:int=PCM_PLAYBACK, units:int=VOLUME_UNITS_PERCENTAGE) -> int: ...
def setvolume(volume:int, pcmtype:int=PCM_PLAYBACK, units:int=VOLUME_UNITS_PERCENTAGE, channel:int|None=None) -> None: ...
def getmute() -> list[int]: ...
def setmute(mute:bool, channel:int|None=None) -> None: ...
def getrec() -> list[int]: ...
def setvolume(volume: int, pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE, channel: (int | None) = None) -> None: ...
def setenum(index: int) -> None: ...
def setmute(mute: bool, channel: (int | None) = None) -> None: ...
def setrec(capture: int, channel: (int | None) = None) -> None: ...
def setrec(capture:int, channel:int|None=None) -> None: ...
def polldescriptors() -> list[tuple[int, int]]: ...
def handleevents() -> int: ...
def close() -> None: ...

View File

@@ -26,7 +26,7 @@ Don't forget to update the documentation.
The documentation is published through the `gh-pages` branch.
To publish the documentation, you need to clone the `gh-pages` branch of this repository into
`doc/gh-pages`. In `doc`, do:
`doc/gh-pages`. In `doc`, do:
git clone -b gh-pages git@github.com:larsimmisch/pyalsaaudio.git gh-pages

View File

@@ -67,7 +67,7 @@ release = version
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.

View File

@@ -10,7 +10,7 @@
The :mod:`alsaaudio` module defines functions and classes for using ALSA.
.. function:: pcms(pcmtype: int = PCM_PLAYBACK) ->list[str]
.. function:: pcms(pcmtype:int=PCM_PLAYBACK) ->list[str]
List available PCM devices by name.
@@ -46,7 +46,7 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
.. function:: card_indexes()
.. function:: card_name()
.. function:: mixers(cardindex: int = -1, device: str = 'default') -> list[str]
.. function:: mixers(cardindex=-1, device='default')
List the available mixers. The arguments are:
@@ -82,7 +82,7 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
changed. Since 0.8, this functions returns the mixers for the default
device, not the mixers for the first card.
.. function:: asoundlib_version() -> str
.. function:: asoundlib_version()
Return a Python string containing the ALSA version found.
@@ -96,12 +96,10 @@ PCM objects in :mod:`alsaaudio` can play or capture (record) PCM
sound through speakers or a microphone. The PCM constructor takes the
following arguments:
.. class:: PCM(type: int = PCM_PLAYBACK, mode: int = PCM_NORMAL, rate: int = 44100, channels: int = 2,
format: int = PCM_FORMAT_S16_LE, periodsize: int = 32, periods: int = 4,
device: str = 'default', cardindex: int = -1) -> PCM
.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, rate=44100, channels=2, format=PCM_FORMAT_S16_LE, periodsize=32, periods=4, device='default', cardindex=-1) -> PCM
This class is used to represent a PCM device (either for playback or
recording). The constructor's arguments are:
This class is used to represent a PCM device (either for playback and
recording). The arguments are:
* *type* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
(default).
@@ -159,15 +157,7 @@ following arguments:
**Note:** This should not be used, as it bypasses most of ALSA's configuration.
The defaults mentioned above are values passed by :mod:alsaaudio
to ALSA, not anything internal to ALSA.
**Note:** For default and non-default values alike, there is no
guarantee that a PCM device supports the requested configuration,
and ALSA may pick realizable values which it believes to be closest
to the request. Therefore, after creating a PCM object, it is
necessary to verify whether its realized configuration is acceptable.
The :func:info method can be used to query it.
This will construct a PCM object with the given settings.
*Changed in 0.10:*
@@ -191,87 +181,61 @@ PCM objects have the following methods:
.. method:: PCM.info() -> dict
Returns a dictionary containing the configuration of a PCM device.
The info function returns a dictionary containing the configuration of a PCM device. As ALSA takes into account limitations of the hardware and software devices the configuration achieved might not correspond to the values used during creation. There is therefore a need to check the realised configuration before processing the sound coming from the device or before sending sound to a device. A small subset of parameters can be set, but cannot be queried. These parameters are stored by alsaaudio and returned as they were given by the user, to distinguish them from parameters retrieved from ALSA these parameters have a name prefixed with **" (call value) "**. Yet another set of properties derives directly from the hardware and can be obtained through ALSA.
A small subset of properties reflects fixed parameters given by the
user, stored within alsaaudio. To distinguish them from properties
retrieved from ALSA when the call is made, they have their name
prefixed with **" (call value) "**.
=========================== ============================= ==================================================================
Key Description (Reference) Type
=========================== ============================= ==================================================================
name PCM():device string
card_no *index of card* integer (negative indicates device not associable with a card)
device_no *index of PCM device* integer
subdevice_no *index of PCM subdevice* integer
state *name of PCM state* string
access_type *name of PCM access type* string
(call value) type PCM():type integer
(call value) type_name PCM():type string
(call value) mode PCM():mode integer
(call value) mode_name PCM():mode string
format PCM():format integer
format_name PCM():format string
format_description PCM():format string
subformat_name *name of PCM subformat* string
subformat_description *description of subformat* string
channels PCM():channels integer
rate PCM():rate integer (Hz)
period_time *period duration* integer (:math:`\mu s`)
period_size PCM():period_size integer (frames)
buffer_time *buffer time* integer (:math:`\mu s`) (negative indicates error)
buffer_size *buffer size* integer (frames) (negative indicates error)
get_periods *approx. periods in buffer* integer (negative indicates error)
rate_numden *numerator, denominator* tuple (integer (Hz), integer (Hz))
significant_bits *significant bits in sample* integer (negative indicates error)
is_batch *hw: double buffering* boolean (True: hardware supported)
is_block_transfer *hw: block transfer* boolean (True: hardware supported)
is_double *hw: double buffering* boolean (True: hardware supported)
is_half_duplex *hw: half-duplex* boolean (True: hardware supported)
is_joint_duplex *hw: joint-duplex* boolean (True: hardware supported)
can_overrange *hw: overrange detection* boolean (True: hardware supported)
can_mmap_sample_resolution *hw: sample-resol. mmap* boolean (True: hardware supported)
can_pause *hw: pause* boolean (True: hardware supported)
can_resume *hw: resume* boolean (True: hardware supported)
can_sync_start *hw: synchronized start* boolean (True: hardware supported)
=========================== ============================= ==================================================================
Descriptions of properties which can be directly set during PCM object
instantiation carry the prefix "PCM():", followed by the respective
constructor parameter. Note that due to device limitations, the values
may deviate from those originally requested.
The italicized descriptions give a summary of the "full" description as it can be found in the `ALSA documentation <https://www.alsa-project.org/alsa-doc>`_. "hw:": indicates that the property indicated relates to the hardware. Parameters passed to the PCM object during instantation are prefixed with "PCM():", they are described there for the keyword argument indicated after "PCM():".
Yet another set of properties cannot be set, and derives directly from
the hardware, possibly depending on other properties. Those properties'
descriptions are prefixed with "hw:" below.
=========================== ==================================== ==================================================================
Key Description (Reference) Type
=========================== ==================================== ==================================================================
name PCM():device string
card_no *index of card* integer (negative indicates device not associable with a card)
device_no *index of PCM device* integer
subdevice_no *index of PCM subdevice* integer
state *name of PCM state* string
access_type *name of PCM access type* string
(call value) type PCM():type integer
(call value) type_name PCM():type string
(call value) mode PCM():mode integer
(call value) mode_name PCM():mode string
format PCM():format integer
format_name PCM():format string
format_description PCM():format string
subformat_name *name of PCM subformat* string
subformat_description *description of subformat* string
channels PCM():channels integer
rate PCM():rate integer (Hz)
period_time *period duration* integer (:math:`\mu s`)
period_size PCM():period_size integer (frames)
buffer_time *buffer time* integer (:math:`\mu s`) (negative indicates error)
buffer_size *buffer size* integer (frames) (negative indicates error)
get_periods *approx. periods in buffer* integer (negative indicates error)
rate_numden *numerator, denominator* tuple (integer (Hz), integer (Hz))
significant_bits *significant bits in sample* [#tss]_ integer (negative indicates error)
nominal_bits *nominal bits in sample* [#tss]_ integer (negative indicates error)
physical_bits *sample width in bits* [#tss]_ integer (negative indicates error)
is_batch *hw: double buffering* boolean (True: hardware supported)
is_block_transfer *hw: block transfer* boolean (True: hardware supported)
is_double *hw: double buffering* boolean (True: hardware supported)
is_half_duplex *hw: half-duplex* boolean (True: hardware supported)
is_joint_duplex *hw: joint-duplex* boolean (True: hardware supported)
can_overrange *hw: overrange detection* boolean (True: hardware supported)
can_mmap_sample_resolution *hw: sample-resol. mmap* boolean (True: hardware supported)
can_pause *hw: pause* boolean (True: hardware supported)
can_resume *hw: resume* boolean (True: hardware supported)
can_sync_start *hw: synchronized start* boolean (True: hardware supported)
=========================== ==================================== ==================================================================
.. [#tss] More information in the :ref:`terminology section for sample size <term-sample-size>`
..
The italicized descriptions give a summary of the "full" description
as can be found in the
`ALSA documentation <https://www.alsa-project.org/alsa-doc>`_.
*New in 0.9.1*
.. method:: PCM.dumpinfo()
Dumps the PCM object's configured parameters to stdout.
.. method:: PCM.pcmtype() -> int
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
:const:`PCM_PLAYBACK`.
.. method:: PCM.pcmmode() -> int
.. method:: PCM.pcmmode()
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
.. method:: PCM.cardname() -> string
.. method:: PCM.cardname()
Return the name of the sound card used by this PCM object.
@@ -319,6 +283,12 @@ PCM objects have the following methods:
.. deprecated:: 0.9 Use the `periodsize` named argument to :func:`PCM`.
*New in 0.9.1*
.. method:: PCM.dumpinfo() -> None
Dumps the PCM object's configured parameters to stdout.
.. method:: PCM.state() -> int
Returs the current state of the stream, which can be one of
@@ -334,20 +304,6 @@ PCM objects have the following methods:
*New in 0.10*
.. method:: PCM.avail() -> int
For :const:`PCM_PLAYBACK` PCM objects, returns the number of writable
(that is, free) frames in the buffer.
For :const:`PCM_CAPTURE` PCM objects, returns the number of readable
(that is, filled) frames in the buffer.
An attempt to read/write more frames than indicated will block (in
:const:`PCM_NORMAL` mode) or fail and return zero (in
:const:`PCM_NONBLOCK` mode).
*New in 0.11*
.. method:: PCM.read() -> tuple[int, bytes]
In :const:`PCM_NORMAL` mode, this function blocks until a full period is
@@ -396,7 +352,7 @@ PCM objects have the following methods:
in the kernel, and playout will continue afterwards. Make sure that the
stream is drained before discarding the PCM handle.
.. method:: PCM.pause([enable: int = True]) -> int
.. method:: PCM.pause([enable=True]) -> int
If *enable* is :const:`True`, playback or capture is paused.
Otherwise, playback/capture is resumed.
@@ -417,13 +373,6 @@ PCM objects have the following methods:
*New in 0.10*
.. method:: PCM.close() -> None
Closes the PCM device.
For :const:`PCM_PLAYBACK` PCM objects in :const:`PCM_NORMAL` mode,
this function blocks until all pending playback is drained.
.. method:: PCM.polldescriptors() -> list[tuple[int, int]]
Returns a list of tuples of *(file descriptor, eventmask)* that can be
@@ -432,16 +381,7 @@ PCM objects have the following methods:
The *eventmask* value is compatible with `poll.register`__ in the Python
:const:`select` module.
.. method:: PCM.polldescriptors_revents(descriptors: list[tuple[int, int]]) -> int
Processes the descriptor list returned by :func:`polldescriptors` after
using it with *select.poll*, and returns a single *eventmask* value that
is meaningful for deciding whether :func:`read` or :func:`write` should
be called.
*New in 0.11*
.. method:: PCM.set_tstamp_mode([mode: int = PCM_TSTAMP_ENABLE])
.. method:: PCM.set_tstamp_mode([mode=PCM_TSTAMP_ENABLE]) -> None
Set the ALSA timestamp mode on the device. The mode argument can be set to
either :const:`PCM_TSTAMP_NONE` or :const:`PCM_TSTAMP_ENABLE`.
@@ -451,7 +391,7 @@ PCM objects have the following methods:
Return the integer value corresponding to the ALSA timestamp mode. The
return value can be either :const:`PCM_TSTAMP_NONE` or :const:`PCM_TSTAMP_ENABLE`.
.. method:: PCM.set_tstamp_type([type: int = PCM_TSTAMP_TYPE_GETTIMEOFDAY]) -> None
.. method:: PCM.set_tstamp_type([type=PCM_TSTAMP_TYPE_GETTIMEOFDAY]) -> None
Set the ALSA timestamp mode on the device. The type argument
can be set to either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`,
@@ -490,13 +430,20 @@ PCM objects have the following methods:
update.
================================= ===========================================
.. method:: PCM.close() -> None
Closes the PCM device.
For :const:`PCM_PLAYBACK` PCM objects in :const:`PCM_NORMAL` mode,
this function blocks until all pending playback is drained.
**A few hints on using PCM devices for playback**
The most common reason for problems with playback of PCM audio is that writes
to PCM devices must *exactly* match the data rate of the device.
If too little data is written to the device, it will underrun, and
ugly clicking sounds will occur. Conversely, if too much data is
ugly clicking sounds will occur. Conversely, of too much data is
written to the device, the write function will either block
(:const:`PCM_NORMAL` mode) or return zero (:const:`PCM_NONBLOCK` mode).
@@ -525,7 +472,7 @@ Mixer Objects
Mixer objects provides access to the ALSA mixer API.
.. class:: Mixer(control: str = 'Master', id: int = 0, cardindex: int = -1, device: str = 'default') -> Mixer
.. class:: Mixer(control='Master', id=0, cardindex=-1, device='default') -> Mixer
Arguments are:
@@ -601,7 +548,7 @@ Mixer objects have the following methods:
'Joined Capture Volume' Manipulate sound capture volume for all channels at a time
======================== ================
.. method:: Mixer.getenum() -> tuple[str, list[str]]
.. method:: Mixer.getenum() -> tuple[ str, list[str]]
For enumerated controls, return the currently selected item and the list of
items available.
@@ -627,13 +574,13 @@ Mixer objects have the following methods:
This method will return an empty tuple if the mixer is not an enumerated
control.
.. method:: Mixer.setenum(index: int) -> None
.. method:: Mixer.setenum(index:int) -> None
For enumerated controls, sets the currently selected item.
*index* is an index into the list of available enumerated items returned
by :func:`getenum`.
.. method:: Mixer.getrange(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_RAW) -> tuple[int, int]
.. method:: Mixer.getrange(pcmtype=PCM_PLAYBACK, units=VOLUME_UNITS_RAW) -> tuple[int, int]
Return the volume range of the ALSA mixer controlled by this object.
The value is a tuple of integers whose meaning is determined by the
@@ -647,7 +594,7 @@ Mixer objects have the following methods:
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
.. method:: Mixer.getvolume(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE) -> int
.. method:: Mixer.getvolume(pcmtype:int=PCM_PLAYBACK, units:int=VOLUME_UNITS_PERCENTAGE) -> int
Returns a list with the current volume settings for each channel. The list
elements are integers whose meaning is determined by the *units* argument.
@@ -660,7 +607,7 @@ Mixer objects have the following methods:
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
.. method:: Mixer.setvolume(volume: int, pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE, channel: (int | None) = None) -> None
.. method:: Mixer.setvolume(volume:int, pcmtype:int=PCM_PLAYBACK, units:int=VOLUME_UNITS_PERCENTAGE, channel:int|None) -> None
Change the current volume settings for this mixer. The *volume* argument
is an integer whose meaning is determined by the *units* argument.
@@ -684,7 +631,7 @@ Mixer objects have the following methods:
This method will fail if the mixer has no playback switch capabilities.
.. method:: Mixer.setmute(mute: bool, channel: (int | None) = None) -> None
.. method:: Mixer.setmute(mute:bool, channel:int|None=None) -> None
Sets the mute flag to a new value. The *mute* argument is either 0 for not
muted, or 1 for muted.
@@ -701,7 +648,7 @@ Mixer objects have the following methods:
This method will fail if the mixer has no capture switch capabilities.
.. method:: Mixer.setrec(capture: int, channel: (int | None) = None) -> None
.. method:: Mixer.setrec(capture:int, channel:int|None=None) -> None
Sets the capture mute flag to a new value. The *capture* argument
is either 0 for no capture, or 1 for capture.

View File

@@ -81,7 +81,7 @@ and need the ALSA headers for compilation. Verify that you have
Naturally you also need to use a kernel with proper ALSA support. This is the
default in Linux kernel 2.6 and later. If you are using kernel version 2.4 you
may need to install the ALSA patches yourself - although most distributions
may need to install the ALSA patches yourself - although most distributions
ship with ALSA kernels.
To install, execute the following: --- ::

View File

@@ -6,7 +6,7 @@ In order to use PCM devices it is useful to be familiar with some concepts and
terminology.
Sample
PCM audio, whether it is input or output, consists of *samples*.
PCM audio, whether it is input or output, consists of *samples*.
A single sample represents the amplitude of one channel of sound
at a certain point in time. A lot of individual samples are
necessary to represent actual sound; for CD audio, 44100 samples
@@ -22,8 +22,8 @@ Sample
loudest signal that can be reproduced.
Frame
A frame consists of exactly one sample per channel. If there is only one
channel (Mono sound) a frame is simply a single sample. If the sound is
A frame consists of exactly one sample per channel. If there is only one
channel (Mono sound) a frame is simply a single sample. If the sound is
stereo, each frame consists of two samples, etc.
Frame size
@@ -33,7 +33,7 @@ Frame size
is 48 bytes.
Rate
PCM sound consists of a flow of sound frames. The sound rate controls how
PCM sound consists of a flow of sound frames. The sound rate controls how
often the current frame is replaced. For example, a rate of 8000 Hz
means that a new frame is played or captured 8000 times per second.
@@ -85,21 +85,6 @@ Period size
each write should contain exactly 32 frames of sound data, and each
read will return either 32 frames of data or nothing at all.
.. _term-sample-size:
Sample size
Each sample takes *physical_bits* of space. *nominal_bits* tells
how many least significant bits are used. This is the bit depth
in the format name and sometimes called just *sample bits* or
*format width*. *significant_bits* tells how many most significant
bits of the *nominal_bits* are used by the sample. This can be thought
of as the *sample resolution*. This is visualized as follows::
MSB |00000000 XXXXXXXX XXXXXXXX 00000000| LSB
|--significant--|
|---------nominal---------|
|-------------physical--------------|
Once you understand these concepts, you will be ready to use the PCM API. Read
on.

40
echo.py Normal file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
import alsaaudio
import select
def echo(inpcm, outpcm):
q = list()
# setup the synchronous event loop
# See https://docs.python.org/3/library/select.html#poll-objects for background
reactor = select.poll()
infd, inmask = inpcm.polldecriptors()
outfd, outmask = outpcm.polldescriptors()
write_started = False
def write():
data = q.pop()
written = outpcm.write(data)
if written < len(data):
q.insert(0, data[written:])
reactor.register(infd, inmask)
reactor.register(outfd, outmask)
while True:
events = reactor.poll()
for fd, event in events:
if event == select.POLLIN and fd == infd:
data = inpcm.read()
q.append(data)
if not write_started:
write()
write_started = True
elif event == select.POLLOUT and fd == outfd:
if not q:
return
write()

View File

@@ -7,7 +7,6 @@
from __future__ import print_function
import sys
import time
from threading import Thread
from multiprocessing import Queue
@@ -16,15 +15,14 @@ if sys.version_info[0] < 3:
else:
from queue import Empty
from math import pi, sin, ceil
from math import pi, sin
import struct
import itertools
import alsaaudio
sampling_rate = 48000
format = alsaaudio.PCM_FORMAT_S16_LE
pack_format = 'h' # short int, matching S16
framesize = 2 # bytes per frame for the values above
channels = 2
def nearest_frequency(frequency):
@@ -36,28 +34,28 @@ def generate(frequency, duration = 0.125):
# generate a buffer with a sine wave of `frequency`
# that is approximately `duration` seconds long
# the buffersize we approximately want
target_size = int(sampling_rate * channels * duration)
# the length of a full sine wave at the frequency
cycle_size = int(sampling_rate / frequency)
# number of full cycles we can fit into the duration
factor = int(ceil(duration * frequency))
# total number of frames
size = cycle_size * factor
# number of full cycles we can fit into target_size
factor = int(target_size / cycle_size)
size = max(int(cycle_size * factor), 1)
sine = [ int(32767 * sin(2 * pi * frequency * i / sampling_rate)) \
for i in range(size)]
if channels > 1:
sine = list(itertools.chain.from_iterable(itertools.repeat(x, channels) for x in sine))
return struct.pack(str(size * channels) + pack_format, *sine)
return struct.pack('%dh' % size, *sine)
class SinePlayer(Thread):
def __init__(self, frequency = 440.0):
Thread.__init__(self, daemon=True)
Thread.__init__(self)
self.setDaemon(True)
self.device = alsaaudio.PCM(channels=channels, format=format, rate=sampling_rate)
self.queue = Queue()
self.change(frequency)
@@ -75,23 +73,22 @@ class SinePlayer(Thread):
buf = generate(f)
self.queue.put(buf)
def run(self):
buffer = None
while True:
try:
buffer = self.queue.get(False)
self.device.setperiodsize(int(len(buffer) / framesize))
self.device.write(buffer)
except Empty:
pass
if buffer:
if self.device.write(buffer) < 0:
print("Playback buffer underrun! Continuing nonetheless ...")
if buffer:
self.device.write(buffer)
isine = SinePlayer()
isine.start()
time.sleep(1)
isine.change(1000)
time.sleep(1)
def change(f):
isine.change(f)

View File

@@ -70,13 +70,8 @@ class Loopback(object):
self.capture = capture
self.capture_pd = PollDescriptor.from_alsa_object('capture', capture)
self.run_after_stop = None
if run_after_stop:
self.run_after_stop = run_after_stop.split(' ')
self.run_before_start = None
if run_before_start:
self.run_before_start = run_before_start.split(' ')
self.run_after_stop = run_after_stop.split(' ')
self.run_before_start = run_before_start.split(' ')
self.run_after_stop_did_run = False
self.waitBeforeOpen = False
@@ -234,7 +229,6 @@ class VolumeForwarder(object):
self.playback_control = playback_control
self.capture_control = capture_control
self.active = True
self.volume = None
def start(self):
self.active = True

View File

@@ -47,8 +47,7 @@ if __name__ == '__main__':
# Read data from stdin
data = f.read(320)
while data:
if out.write(data) < 0:
print("Playback buffer underrun! Continuing nonetheless ...")
out.write(data)
data = f.read(320)
out.close()

View File

@@ -39,8 +39,7 @@ def play(device, f):
data = f.readframes(periodsize)
while data:
# Read data from stdin
if device.write(data) < 0:
print("Playback buffer underrun! Continuing nonetheless ...")
device.write(data)
data = f.readframes(periodsize)

View File

@@ -40,7 +40,7 @@ if __name__ == '__main__':
f = open(args[0], 'wb')
# Open the device in nonblocking capture mode in mono, with a sampling rate of 44100 Hz
# Open the device in nonblocking capture mode in mono, with a sampling rate of 44100 Hz
# and 16 bit little endian samples
# The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api.
@@ -49,8 +49,8 @@ if __name__ == '__main__':
# This means that the reads below will return either 320 bytes of data
# or 0 bytes of data. The latter is possible because we are in nonblocking
# mode.
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK,
channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE,
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK,
channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE,
periodsize=160, device=device)
loops = 1000000
@@ -59,8 +59,6 @@ if __name__ == '__main__':
# Read data from device
l, data = inp.read()
if l < 0:
print("Capture buffer overrun! Continuing nonetheless ...")
elif l:
if l:
f.write(data)
time.sleep(.001)

View File

@@ -8,7 +8,7 @@ from setuptools import setup
from setuptools.extension import Extension
from sys import version
pyalsa_version = '0.11.0'
pyalsa_version = '0.10.1'
if __name__ == '__main__':
setup(