forked from auracaster/pyalsaaudio
Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b3f1f41c7 | ||
|
|
3e360b1bb7 | ||
|
|
44ccbf839d | ||
|
|
2c2e43d3d1 | ||
|
|
a142b70033 | ||
|
|
26ba938e04 | ||
|
|
f5e9d52c74 | ||
|
|
436c31f9fd | ||
|
|
eda913b203 | ||
|
|
0aba948277 | ||
|
|
9b7b767594 | ||
|
|
db87f2ced5 | ||
|
|
f179db2d9b | ||
|
|
420b538321 | ||
|
|
ae5c4aad9b | ||
|
|
d23b26b2e5 | ||
|
|
1d63226e56 | ||
|
|
664f81a777 | ||
|
|
eb51d11619 | ||
|
|
2f74e8e8a4 | ||
|
|
6f52de9da0 | ||
|
|
8fb33ddd49 | ||
|
|
691c1d9b23 | ||
|
|
061c297f4b | ||
|
|
8ff3e169cd | ||
|
|
7d9c16618b | ||
|
|
16345a139a | ||
|
|
1c730123eb | ||
|
|
0df2e0ee6f | ||
|
|
fe3fbe5376 | ||
|
|
42ca8acbad | ||
|
|
522131123c | ||
|
|
43a94b3c62 | ||
|
|
9637703ab5 | ||
|
|
438e52e3fc | ||
|
|
07ac637b1c | ||
|
|
bdca4dc061 | ||
|
|
24eef474da | ||
|
|
24d26a5161 | ||
|
|
f62e61f844 | ||
|
|
53f4f093e1 | ||
|
|
82308f32ed | ||
|
|
39d6acd3ac | ||
|
|
c5153db0ac | ||
|
|
f25c8243dc | ||
|
|
073d708bd1 | ||
|
|
946694d263 | ||
|
|
574f78939d | ||
|
|
17d171c1a5 | ||
|
|
de2fc3c992 | ||
|
|
c2a6b6e583 | ||
|
|
da7d04e2fd | ||
|
|
1c1af45a7f | ||
|
|
9b773b48d6 | ||
|
|
b05efa0ad6 | ||
|
|
4e098da908 | ||
|
|
c266d302e0 | ||
|
|
b094ac096b | ||
|
|
46b91980e0 | ||
|
|
9ab4f721d6 | ||
|
|
a967b7db78 | ||
|
|
01a444ac21 | ||
|
|
8bcb7ba626 | ||
|
|
9dc0fc2fd3 | ||
|
|
4318b63912 | ||
|
|
a7b9d617b2 | ||
|
|
379fc05b5e | ||
|
|
dff8ef031f | ||
|
|
8ea9470454 | ||
|
|
19c9ba3ed9 | ||
|
|
b2f0466dd2 | ||
|
|
6317d9addc | ||
|
|
2432089759 | ||
|
|
279760add5 | ||
|
|
59a712c486 | ||
|
|
dfda54642d | ||
|
|
3f6fb9844d | ||
|
|
4d9f6e5b50 | ||
|
|
40a4a36b1d | ||
|
|
38ea69bbaa | ||
|
|
c8f3916337 | ||
|
|
f19af8eba0 | ||
|
|
b8980d992b | ||
|
|
ebd2b5359d | ||
|
|
c5f22fd7e0 | ||
|
|
3c3f0af74a | ||
|
|
17f3b440cc | ||
|
|
b2a303121a | ||
|
|
3168833b4e | ||
|
|
c74669850b | ||
|
|
1a4c0541d7 | ||
|
|
e6a6445375 | ||
|
|
97f2abcb30 | ||
|
|
a53ffd0d4f | ||
|
|
da71e01f9c | ||
|
|
f6736ec43a | ||
|
|
e48b294b84 | ||
|
|
d037297632 | ||
|
|
c8e7261e94 | ||
|
|
5c481b4094 | ||
|
|
1e3c7f3fd0 | ||
|
|
0ae60f80f3 | ||
|
|
4018ab4f6c | ||
|
|
07f84a8e95 | ||
|
|
d83e829de1 | ||
|
|
62e5515341 | ||
|
|
ed027a6141 | ||
|
|
5302dc524d | ||
|
|
b17b36be50 | ||
|
|
08bdce9ed9 | ||
|
|
0224c8a308 | ||
|
|
f07627543c | ||
|
|
df889b94ef | ||
|
|
2a21bf6c42 | ||
|
|
8084297926 | ||
|
|
8fbc04e18d | ||
|
|
8ed9f924cd | ||
|
|
046e7c4e87 | ||
|
|
a4c4c7cb62 | ||
|
|
f478797f6f | ||
|
|
12f807698a | ||
|
|
fc011b5ea6 | ||
|
|
f244a70111 | ||
|
|
a056a90c61 | ||
|
|
be1b3e131d | ||
|
|
8abf06bedf | ||
|
|
dcc831e607 | ||
|
|
e587df9143 | ||
|
|
82febd3f7e | ||
|
|
1695066c11 | ||
|
|
25717020ef | ||
|
|
1aae655d24 | ||
|
|
c1c8362eb2 | ||
|
|
723eff3887 | ||
|
|
aa9867de18 | ||
|
|
58f4522769 | ||
|
|
f2fb61d324 | ||
|
|
9e79494a95 | ||
|
|
bfe4899721 | ||
|
|
40a1219dac | ||
|
|
54e2712b7a | ||
|
|
f9685e0b30 | ||
|
|
b4a670c50d | ||
|
|
370a4b6249 | ||
|
|
eca217dff9 | ||
|
|
65d3c4a283 | ||
|
|
adc0d800e1 | ||
|
|
02cf16d10d | ||
|
|
94ced0517e |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -4,6 +4,11 @@ MANIFEST
|
|||||||
doc/gh-pages/
|
doc/gh-pages/
|
||||||
doc/html/
|
doc/html/
|
||||||
doc/doctrees/
|
doc/doctrees/
|
||||||
|
doc/_build/
|
||||||
gh-pages/
|
gh-pages/
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
.vscode/
|
||||||
|
/__pycache__/
|
||||||
|
/pyalsaaudio.egg-info/
|
||||||
|
*.raw
|
||||||
|
|||||||
70
CHANGES
70
CHANGES
@@ -1,70 +0,0 @@
|
|||||||
Version 0.8.2:
|
|
||||||
- fix #3 (we cannot get the revision from git for pip installs)
|
|
||||||
|
|
||||||
Version 0.8.1:
|
|
||||||
- document changes (this file)
|
|
||||||
|
|
||||||
Version 0.8:
|
|
||||||
- 'PCM()' has new 'device' and 'cardindex' keyword arguments.
|
|
||||||
|
|
||||||
The keyword 'device' allows to select virtual devices, 'cardindex' can be
|
|
||||||
used to select hardware cards by index (as with 'mixers()' and 'Mixer()').
|
|
||||||
|
|
||||||
The 'card' keyword argument is still supported, but deprecated.
|
|
||||||
|
|
||||||
The reason for this change is that the 'card' keyword argument guessed
|
|
||||||
a device name from the card name, but this only works sometimes, and breaks
|
|
||||||
opening virtual devices.
|
|
||||||
|
|
||||||
- new function 'pcms()' to list available PCM devices.
|
|
||||||
|
|
||||||
- mixers() and Mixer() take an additional 'device' keyword argument.
|
|
||||||
This allows to list or open virtual devices.
|
|
||||||
|
|
||||||
- The default behaviour of Mixer() without any arguments has changed.
|
|
||||||
Now Mixer() will try to open the 'default' Mixer instead of the Mixer
|
|
||||||
that is associated with card 0.
|
|
||||||
|
|
||||||
- fix a memory leak under Python 3.x
|
|
||||||
|
|
||||||
- some more memory leaks in error conditions fixed.
|
|
||||||
|
|
||||||
Version 0.7:
|
|
||||||
- fixed several memory leaks (patch 3372909), contributed by Erik Kulyk)
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.6:
|
|
||||||
- mostly reverted patch 2594366: alsapcm_setup did not do complete error
|
|
||||||
checking for good reasons; some ALSA functions in alsapcm_setup may fail without
|
|
||||||
rendering the device unusable
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.5:
|
|
||||||
- applied patch 2777035: Fixed setrec method in alsaaudio.c
|
|
||||||
This included a mixertest with more features
|
|
||||||
- fixed/applied patch 2594366: alsapcm_setup does not do any error checking
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.4:
|
|
||||||
- API changes: mixers() and Mixer() now take a card index instead of a
|
|
||||||
card name as optional parameter.
|
|
||||||
- Support for Python 3.0
|
|
||||||
- Documentation converted to reStructuredText; use Sphinx instead of LaTeX.
|
|
||||||
- added cards()
|
|
||||||
- added PCM.close()
|
|
||||||
- added Mixer.close()
|
|
||||||
- added mixer.getenum()
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.3:
|
|
||||||
- wrapped blocking calls with Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS
|
|
||||||
- added pause
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.2:
|
|
||||||
- Many bugfixes related to playback in particular
|
|
||||||
- Module documentation in the doc subdirectory
|
|
||||||
|
|
||||||
|
|
||||||
Version 0.1:
|
|
||||||
- Initial version
|
|
||||||
127
CHANGES.md
Normal file
127
CHANGES.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# 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.0
|
||||||
|
- assorted improvements (#123 from @ossilator)
|
||||||
|
- support for `periods` in the `PCM` constructor.
|
||||||
|
- new functions `PCM.state()`, `PCM.drop()` and `PCM.drain()`
|
||||||
|
- improved underrun/overrun handling
|
||||||
|
- documentation improvements/consolidation (docstrings were removed in favour of online documentation)
|
||||||
|
- more sampling rates
|
||||||
|
- bug fixes
|
||||||
|
|
||||||
|
# Version 0.9.2
|
||||||
|
- Fix alsamixer_getvolume (#112 from @stephensp)
|
||||||
|
|
||||||
|
# Version 0.9.1:
|
||||||
|
- Support decibel, percentage, and raw volumes in getvolume, setvolume, and getrange (#109 from @chrisdiamand)
|
||||||
|
|
||||||
|
# Version 0.9.0:
|
||||||
|
- Added keyword arguments for channels, format, rate and periodsize
|
||||||
|
- Deprecated `setchannel`, `setformat`, `setrate` and `setperiodsize`
|
||||||
|
|
||||||
|
# Version 0.8.6:
|
||||||
|
- Added four methods to the `PCM` class to allow users to get detailed information about the device:
|
||||||
|
|
||||||
|
- `getformats()` returns a dictionary of name / value pairs, one for each of the card's
|
||||||
|
supported formats - e.g. `{"U8": 1, "S16_LE": 2}`,
|
||||||
|
- `getchannels()` returns a list of the supported channel numbers, e.g. `[1, 2]`,
|
||||||
|
- `getrates()` returns supported sample rates for the device, e.g. `[48000]`,
|
||||||
|
- `getratebounds()` returns the device's official minimum and maximum supported
|
||||||
|
sample rates as a tuple, e.g. `(4000, 48000)`.
|
||||||
|
|
||||||
|
(#82 contributed by @jdstmporter)
|
||||||
|
|
||||||
|
- Prevent hang on close after capturing audio (#80 contributed by @daym)
|
||||||
|
|
||||||
|
# Version 0.8.5:
|
||||||
|
- Return an empty string/bytestring when `read()` detects an
|
||||||
|
overrun. Previously the returned data was undefined (contributed by @jcea)
|
||||||
|
|
||||||
|
- Unlimited setperiod buffer size when reading frames (contributed by @jcea)
|
||||||
|
|
||||||
|
# Version 0.8.4:
|
||||||
|
- Fix Python3 API usage broken in 0.8.3
|
||||||
|
|
||||||
|
# Version 0.8.3:
|
||||||
|
- Add DSD sample formats (contributed by @lintweaker)
|
||||||
|
- Add Mixer.handleevents() to acknowledge events identified by select.poll (contributed by @PaulSD)
|
||||||
|
- Add functions for listing cards and their names (contributed by @chrisdiamand)
|
||||||
|
- Add a method for setting enums (contributed by @chrisdiamand)
|
||||||
|
|
||||||
|
# Version 0.8.2:
|
||||||
|
- fix #3 (we cannot get the revision from git for pip installs)
|
||||||
|
|
||||||
|
# Version 0.8.1:
|
||||||
|
- document changes (this file)
|
||||||
|
|
||||||
|
# Version 0.8:
|
||||||
|
- `PCM()` has new `device` and `cardindex` keyword arguments.
|
||||||
|
|
||||||
|
The keyword `device` allows to select virtual devices, `cardindex` can be
|
||||||
|
used to select hardware cards by index (as with `mixers()` and `Mixer()`).
|
||||||
|
|
||||||
|
The `card` keyword argument is still supported, but deprecated.
|
||||||
|
|
||||||
|
The reason for this change is that the `card` keyword argument guessed
|
||||||
|
a device name from the card name, but this only works sometimes, and breaks
|
||||||
|
opening virtual devices.
|
||||||
|
|
||||||
|
- new function `pcms()` to list available PCM devices.
|
||||||
|
|
||||||
|
- `mixers()` and `Mixer()` take an additional `device` keyword argument.
|
||||||
|
This allows to list or open virtual devices.
|
||||||
|
|
||||||
|
- The default behaviour of `Mixer()` without any arguments has changed.
|
||||||
|
Now Mixer() will try to open the `default` Mixer instead of the Mixer
|
||||||
|
that is associated with card 0.
|
||||||
|
|
||||||
|
- fix a memory leak under Python 3.x
|
||||||
|
|
||||||
|
- some more memory leaks in error conditions fixed.
|
||||||
|
|
||||||
|
# Version 0.7:
|
||||||
|
- fixed several memory leaks (patch 3372909), contributed by Erik Kulyk)
|
||||||
|
|
||||||
|
# Version 0.6:
|
||||||
|
- mostly reverted patch 2594366: alsapcm_setup did not do complete error
|
||||||
|
checking for good reasons; some ALSA functions in alsapcm_setup may fail without
|
||||||
|
rendering the device unusable
|
||||||
|
|
||||||
|
# Version 0.5:
|
||||||
|
- applied patch 2777035: Fixed setrec method in alsaaudio.c
|
||||||
|
This included a mixertest with more features
|
||||||
|
- fixed/applied patch 2594366: alsapcm_setup does not do any error checking
|
||||||
|
|
||||||
|
# Version 0.4:
|
||||||
|
- API changes: mixers() and Mixer() now take a card index instead of a
|
||||||
|
card name as optional parameter.
|
||||||
|
- Support for Python 3.0
|
||||||
|
- Documentation converted to reStructuredText; use Sphinx instead of LaTeX.
|
||||||
|
- added `cards()`
|
||||||
|
- added `PCM.close()`
|
||||||
|
- added `Mixer.close()`
|
||||||
|
- added `mixer.getenum()`
|
||||||
|
|
||||||
|
# Version 0.3:
|
||||||
|
- wrapped blocking calls with `Py_BEGIN_ALLOW_THREADS`/`Py_END_ALLOW_THREADS`
|
||||||
|
- added pause
|
||||||
|
|
||||||
|
# Version 0.2:
|
||||||
|
- Many bugfixes related to playback in particular
|
||||||
|
- Module documentation in the doc subdirectory
|
||||||
|
|
||||||
|
# Version 0.1:
|
||||||
|
- Initial version
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
include *.py
|
include *.py
|
||||||
|
include *.pyi
|
||||||
include CHANGES
|
include CHANGES
|
||||||
include TODO
|
include TODO
|
||||||
include LICENSE
|
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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
For documentation, see http://larsimmisch.github.io/pyalsaaudio/
|
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)
|
> Maintainer: Lars Immisch (lars@ibp.de)
|
||||||
|
|
||||||
This package contains wrappers for accessing the
|
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
|
$ git clone https://github.com/larsimmisch/pyalsaaudio.git
|
||||||
$ cd pyalsaaudio
|
$ cd pyalsaaudio
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, build:
|
Then, build:
|
||||||
```
|
```
|
||||||
$ python setup.py build
|
$ python setup.py build
|
||||||
```
|
```
|
||||||
|
|
||||||
And install:
|
And install:
|
||||||
```
|
```
|
||||||
$ sudo python setup.py install
|
$ sudo python setup.py install
|
||||||
|
|||||||
4532
alsaaudio.c
4532
alsaaudio.c
File diff suppressed because it is too large
Load Diff
135
alsaaudio.pyi
Normal file
135
alsaaudio.pyi
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
from typing import list
|
||||||
|
|
||||||
|
PCM_PLAYBACK: int
|
||||||
|
PCM_CAPTURE: int
|
||||||
|
|
||||||
|
PCM_NORMAL: int
|
||||||
|
PCM_NONBLOCK: int
|
||||||
|
PCM_ASYNC: int
|
||||||
|
|
||||||
|
PCM_FORMAT_S8: int
|
||||||
|
PCM_FORMAT_U8: int
|
||||||
|
PCM_FORMAT_S16_LE: int
|
||||||
|
PCM_FORMAT_S16_BE: int
|
||||||
|
PCM_FORMAT_U16_LE: int
|
||||||
|
PCM_FORMAT_U16_BE: int
|
||||||
|
PCM_FORMAT_S24_LE: int
|
||||||
|
PCM_FORMAT_S24_BE: int
|
||||||
|
PCM_FORMAT_U24_LE: int
|
||||||
|
PCM_FORMAT_U24_BE: int
|
||||||
|
PCM_FORMAT_S32_LE: int
|
||||||
|
PCM_FORMAT_S32_BE: int
|
||||||
|
PCM_FORMAT_U32_LE: int
|
||||||
|
PCM_FORMAT_U32_BE: int
|
||||||
|
PCM_FORMAT_FLOAT_LE: int
|
||||||
|
PCM_FORMAT_FLOAT_BE: int
|
||||||
|
PCM_FORMAT_FLOAT64_LE: int
|
||||||
|
PCM_FORMAT_FLOAT64_BE: int
|
||||||
|
PCM_FORMAT_MU_LAW: int
|
||||||
|
PCM_FORMAT_A_LAW: int
|
||||||
|
PCM_FORMAT_IMA_ADPCM: int
|
||||||
|
PCM_FORMAT_MPEG: int
|
||||||
|
PCM_FORMAT_GSM: int
|
||||||
|
PCM_FORMAT_S24_3LE: int
|
||||||
|
PCM_FORMAT_S24_3BE: int
|
||||||
|
PCM_FORMAT_U24_3LE: int
|
||||||
|
PCM_FORMAT_U24_3BE: int
|
||||||
|
|
||||||
|
PCM_TSTAMP_NONE: int
|
||||||
|
PCM_TSTAMP_ENABLE: int
|
||||||
|
|
||||||
|
PCM_TSTAMP_TYPE_GETTIMEOFDAY: int
|
||||||
|
PCM_TSTAMP_TYPE_MONOTONIC: int
|
||||||
|
PCM_TSTAMP_TYPE_MONOTONIC_RAW: int
|
||||||
|
|
||||||
|
PCM_FORMAT_DSD_U8: int
|
||||||
|
PCM_FORMAT_DSD_U16_LE: int
|
||||||
|
PCM_FORMAT_DSD_U32_LE: int
|
||||||
|
PCM_FORMAT_DSD_U32_BE: int
|
||||||
|
|
||||||
|
PCM_STATE_OPEN: int
|
||||||
|
PCM_STATE_SETUP: int
|
||||||
|
PCM_STATE_PREPARED: int
|
||||||
|
PCM_STATE_RUNNING: int
|
||||||
|
PCM_STATE_XRUN: int
|
||||||
|
PCM_STATE_DRAINING: int
|
||||||
|
PCM_STATE_PAUSED: int
|
||||||
|
PCM_STATE_SUSPENDED: int
|
||||||
|
PCM_STATE_DISCONNECTED: int
|
||||||
|
|
||||||
|
MIXER_CHANNEL_ALL: int
|
||||||
|
|
||||||
|
MIXER_SCHN_UNKNOWN: int
|
||||||
|
MIXER_SCHN_FRONT_LEFT: int
|
||||||
|
MIXER_SCHN_FRONT_RIGHT: int
|
||||||
|
MIXER_SCHN_REAR_LEFT: int
|
||||||
|
MIXER_SCHN_REAR_RIGHT: int
|
||||||
|
MIXER_SCHN_FRONT_CENTER: int
|
||||||
|
MIXER_SCHN_WOOFER: int
|
||||||
|
MIXER_SCHN_SIDE_LEFT: int
|
||||||
|
MIXER_SCHN_SIDE_RIGHT: int
|
||||||
|
MIXER_SCHN_REAR_CENTER: int
|
||||||
|
MIXER_SCHN_MONO: int
|
||||||
|
|
||||||
|
VOLUME_UNITS_PERCENTAGE: int
|
||||||
|
VOLUME_UNITS_RAW: int
|
||||||
|
VOLUME_UNITS_DB: int
|
||||||
|
|
||||||
|
def pcms(pcmtype: int) -> list[str]: ...
|
||||||
|
def cards() -> 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 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 setrate(rate: int) -> None: ...
|
||||||
|
def setformat(format: int) -> int: ...
|
||||||
|
def setperiodsize(period: int) -> int: ...
|
||||||
|
def read() -> tuple[int, bytes]: ...
|
||||||
|
def write(data: bytes) -> int: ...
|
||||||
|
def avail() -> 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: ...
|
||||||
|
|
||||||
|
class 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 getmute() -> list[int]: ...
|
||||||
|
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 polldescriptors() -> list[tuple[int, int]]: ...
|
||||||
|
def handleevents() -> int: ...
|
||||||
|
|
||||||
@@ -1,9 +1,32 @@
|
|||||||
|
# Make a new release
|
||||||
|
|
||||||
|
Update the version in setup.py
|
||||||
|
|
||||||
|
pyalsa_version = '0.9.0'
|
||||||
|
|
||||||
|
Commit and push the update.
|
||||||
|
|
||||||
|
Create and push a tag naming the version (i.e. 0.9.0):
|
||||||
|
|
||||||
|
git tag 0.9.0
|
||||||
|
git push origin 0.9.0
|
||||||
|
|
||||||
|
Create the package:
|
||||||
|
|
||||||
|
python3 setup.py sdist
|
||||||
|
|
||||||
|
Upload the package
|
||||||
|
|
||||||
|
twine upload dist/*
|
||||||
|
|
||||||
|
Don't forget to update the documentation.
|
||||||
|
|
||||||
# Publish the documentation
|
# Publish the documentation
|
||||||
|
|
||||||
The documentation is published through the `gh-pages` branch.
|
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
|
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
|
git clone -b gh-pages git@github.com:larsimmisch/pyalsaaudio.git gh-pages
|
||||||
|
|
||||||
|
|||||||
228
doc/conf.py
228
doc/conf.py
@@ -1,182 +1,160 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
# alsaaudio documentation build configuration file, created by
|
# alsaaudio documentation documentation build configuration file, created by
|
||||||
# sphinx-quickstart on Sat Nov 22 00:17:09 2008.
|
# sphinx-quickstart on Thu Mar 30 23:52:21 2017.
|
||||||
#
|
#
|
||||||
# This file is execfile()d with the current directory set to its containing dir.
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
#
|
#
|
||||||
# The contents of this file are pickled, so don't put values in the namespace
|
# Note that not all possible configuration values are present in this
|
||||||
# that aren't pickleable (module imports are okay, they're removed automatically).
|
# autogenerated file.
|
||||||
#
|
#
|
||||||
# All configuration values have a default value; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default value.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
# import os
|
||||||
|
# import sys
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
import sys
|
||||||
sys.path.insert(0, '..')
|
sys.path.insert(0, '..')
|
||||||
from setup import pyalsa_version
|
from setup import pyalsa_version
|
||||||
|
|
||||||
# If your extensions are in another directory, add it here. If the directory
|
|
||||||
# is relative to the documentation root, use os.path.abspath to make it
|
|
||||||
# absolute, like shown here.
|
|
||||||
#sys.path.append(os.path.abspath('some/directory'))
|
|
||||||
|
|
||||||
# General configuration
|
# -- General configuration ------------------------------------------------
|
||||||
# ---------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
extensions = []
|
extensions = []
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['.templates']
|
templates_path = ['_templates']
|
||||||
|
|
||||||
# The suffix of source filenames.
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
||||||
# General substitutions.
|
# General information about the project.
|
||||||
project = u'alsaaudio'
|
project = u'alsaaudio documentation'
|
||||||
copyright = u'2008-20017, Casper Wilstrup, Lars Immisch'
|
copyright = u'2017, Lars Immisch & Casper Wilstrup'
|
||||||
|
author = u'Lars Immisch & Casper Wilstrup'
|
||||||
|
|
||||||
# The default replacements for |version| and |release|, also used in various
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# other places throughout the built documents.
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = pyalsa_version
|
version = pyalsa_version
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = pyalsa_version
|
release = version
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# non-false value, then it is used:
|
# for a list of supported languages.
|
||||||
#today = ''
|
#
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
today_fmt = '%B %d, %Y'
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
# List of documents that shouldn't be included in the build.
|
# List of patterns, relative to source directory, that match files and
|
||||||
#unused_docs = []
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
# List of directories, relative to source directories, that shouldn't be searched
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
# for source files.
|
|
||||||
exclude_trees = ['.build']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = False
|
||||||
|
|
||||||
# Options for HTML output
|
|
||||||
# -----------------------
|
|
||||||
|
|
||||||
# The style sheet to use for HTML and HTML Help pages. A file of that name
|
# -- Options for HTML output ----------------------------------------------
|
||||||
# must exist either in Sphinx' static/ path, or in one of the custom paths
|
|
||||||
# given in html_static_path.
|
|
||||||
html_style = 'default.css'
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# "<project> v<release> documentation".
|
# a list of builtin themes.
|
||||||
#html_title = None
|
#
|
||||||
|
html_theme = 'alabaster'
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
#html_short_title = None
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
#
|
||||||
# of the sidebar.
|
# html_theme_options = {}
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
# -- Options for HTMLHelp output ------------------------------------------
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_use_modindex = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, the reST sources are included in the HTML build as _sources/<name>.
|
|
||||||
#html_copy_source = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = ''
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'alsaaudiodoc'
|
htmlhelp_basename = 'alsaaudiodocumentationdoc'
|
||||||
|
|
||||||
|
|
||||||
# Options for LaTeX output
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
# ------------------------
|
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
latex_elements = {
|
||||||
#latex_paper_size = 'letter'
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#latex_font_size = '10pt'
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, document class [howto/manual]).
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'alsaaudio.tex', u'alsaaudio Documentation',
|
(master_doc, 'alsaaudiodocumentation.tex', u'alsaaudio documentation Documentation',
|
||||||
u'Casper Wilstrup, Lars Immisch', 'manual'),
|
u'Lars Immisch', 'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
# -- Options for manual page output ---------------------------------------
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'alsaaudiodocumentation', u'alsaaudio documentation Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'alsaaudiodocumentation', u'alsaaudio documentation Documentation',
|
||||||
|
author, 'alsaaudiodocumentation', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#latex_preamble = ''
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_use_modindex = True
|
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
alsaaudio documentation
|
alsaaudio documentation
|
||||||
=======================
|
===================================================
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
pyalsaaudio
|
pyalsaaudio
|
||||||
terminology
|
terminology
|
||||||
libalsaaudio
|
libalsaaudio
|
||||||
|
|
||||||
|
Download
|
||||||
|
========
|
||||||
|
|
||||||
Github pages
|
|
||||||
=================
|
|
||||||
|
|
||||||
* `Project page <https://github.com/larsimmisch/pyalsaaudio/>`_
|
|
||||||
* `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_
|
* `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_
|
||||||
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
|
||||||
|
|
||||||
|
Github
|
||||||
|
======
|
||||||
|
|
||||||
|
* `Repository <https://github.com/larsimmisch/pyalsaaudio/>`_
|
||||||
|
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|||||||
@@ -5,42 +5,19 @@
|
|||||||
.. module:: alsaaudio
|
.. module:: alsaaudio
|
||||||
:platform: Linux
|
:platform: Linux
|
||||||
|
|
||||||
|
|
||||||
.. % \declaremodule{builtin}{alsaaudio} % standard library, in C
|
|
||||||
.. % not standard, in C
|
|
||||||
|
|
||||||
.. moduleauthor:: Casper Wilstrup <cwi@aves.dk>
|
.. moduleauthor:: Casper Wilstrup <cwi@aves.dk>
|
||||||
.. moduleauthor:: Lars Immisch <lars@ibp.de>
|
.. moduleauthor:: Lars Immisch <lars@ibp.de>
|
||||||
|
|
||||||
.. % Author of the module code;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The :mod:`alsaaudio` module defines functions and classes for using ALSA.
|
The :mod:`alsaaudio` module defines functions and classes for using ALSA.
|
||||||
|
|
||||||
.. % ---- 3.1. ----
|
.. function:: pcms(pcmtype: int = PCM_PLAYBACK) ->list[str]
|
||||||
.. % For each function, use a ``funcdesc'' block. This has exactly two
|
|
||||||
.. % parameters (each parameters is contained in a set of curly braces):
|
|
||||||
.. % the first parameter is the function name (this automatically
|
|
||||||
.. % generates an index entry); the second parameter is the function's
|
|
||||||
.. % argument list. If there are no arguments, use an empty pair of
|
|
||||||
.. % curly braces. If there is more than one argument, separate the
|
|
||||||
.. % arguments with backslash-comma. Optional parts of the parameter
|
|
||||||
.. % list are contained in \optional{...} (this generates a set of square
|
|
||||||
.. % brackets around its parameter). Arguments are automatically set in
|
|
||||||
.. % italics in the parameter list. Each argument should be mentioned at
|
|
||||||
.. % least once in the description; each usage (even inside \code{...})
|
|
||||||
.. % should be enclosed in \var{...}.
|
|
||||||
|
|
||||||
|
|
||||||
.. function:: pcms([type=PCM_PLAYBACK])
|
|
||||||
|
|
||||||
List available PCM devices by name.
|
List available PCM devices by name.
|
||||||
|
|
||||||
Arguments are:
|
Arguments are:
|
||||||
|
|
||||||
* *type* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
|
* *pcmtype* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
|
||||||
(default).
|
(default).
|
||||||
|
|
||||||
**Note:**
|
**Note:**
|
||||||
|
|
||||||
@@ -57,14 +34,19 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
|
|||||||
|
|
||||||
*New in 0.8*
|
*New in 0.8*
|
||||||
|
|
||||||
.. function:: cards()
|
.. function:: cards() -> list[str]
|
||||||
|
|
||||||
List the available ALSA cards by name. This function is only moderately
|
List the available ALSA cards by name. This function is only moderately
|
||||||
useful. If you want to see a list of available PCM devices, use :func:`pcms`
|
useful. If you want to see a list of available PCM devices, use :func:`pcms`
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
|
|
||||||
.. function:: mixers(cardindex=-1, device='default')
|
..
|
||||||
|
Omitted by intention due to being superseded by cards():
|
||||||
|
|
||||||
|
.. function:: card_indexes()
|
||||||
|
.. function:: card_name()
|
||||||
|
|
||||||
|
.. function:: mixers(cardindex: int = -1, device: str = 'default') -> list[str]
|
||||||
|
|
||||||
List the available mixers. The arguments are:
|
List the available mixers. The arguments are:
|
||||||
|
|
||||||
@@ -73,12 +55,14 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
|
|||||||
the `device` keyword argument is ignored. ``0`` is the first hardware sound
|
the `device` keyword argument is ignored. ``0`` is the first hardware sound
|
||||||
card.
|
card.
|
||||||
|
|
||||||
|
**Note:** This should not be used, as it bypasses most of ALSA's configuration.
|
||||||
|
|
||||||
* *device* - the name of the device on which the mixer resides. The default
|
* *device* - the name of the device on which the mixer resides. The default
|
||||||
is ``'default'``.
|
is ``'default'``.
|
||||||
|
|
||||||
**Note:** For a list of available controls, you can also use ``amixer`` on
|
**Note:** For a list of available controls, you can also use ``amixer`` on
|
||||||
the commandline::
|
the commandline::
|
||||||
|
|
||||||
$ amixer
|
$ amixer
|
||||||
|
|
||||||
To elaborate the example, calling :func:`mixers` with the argument
|
To elaborate the example, calling :func:`mixers` with the argument
|
||||||
@@ -92,12 +76,16 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
|
|||||||
$ amixer -D foo
|
$ amixer -D foo
|
||||||
|
|
||||||
*Changed in 0.8*:
|
*Changed in 0.8*:
|
||||||
|
|
||||||
- The keyword argument `device` is new and can be used to
|
- The keyword argument `device` is new and can be used to
|
||||||
select virtual devices. As a result, the default behaviour has subtly
|
select virtual devices. As a result, the default behaviour has subtly
|
||||||
changed. Since 0.8, this functions returns the mixers for the default
|
changed. Since 0.8, this functions returns the mixers for the default
|
||||||
device, not the mixers for the first card.
|
device, not the mixers for the first card.
|
||||||
|
|
||||||
|
.. function:: asoundlib_version() -> str
|
||||||
|
|
||||||
|
Return a Python string containing the ALSA version found.
|
||||||
|
|
||||||
|
|
||||||
.. _pcm-objects:
|
.. _pcm-objects:
|
||||||
|
|
||||||
@@ -108,95 +96,35 @@ PCM objects in :mod:`alsaaudio` can play or capture (record) PCM
|
|||||||
sound through speakers or a microphone. The PCM constructor takes the
|
sound through speakers or a microphone. The PCM constructor takes the
|
||||||
following arguments:
|
following arguments:
|
||||||
|
|
||||||
.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1)
|
.. 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
|
||||||
|
|
||||||
This class is used to represent a PCM device (either for playback and
|
This class is used to represent a PCM device (either for playback or
|
||||||
recording). The arguments are:
|
recording). The constructor's arguments are:
|
||||||
|
|
||||||
* *type* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
|
* *type* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
|
||||||
(default).
|
(default).
|
||||||
* *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL`
|
* *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL`
|
||||||
(default).
|
(default).
|
||||||
* *device* - the name of the PCM device that should be used (for example
|
* *rate* - the sampling rate in Hz. Typical values are ``8000`` (mainly used for telephony), ``16000``, ``44100`` (default), ``48000`` and ``96000``.
|
||||||
a value from the output of :func:`pcms`). The default value is
|
* *channels* - the number of channels. The default value is 2 (stereo).
|
||||||
``'default'``.
|
* *format* - the data format. This controls how the PCM device interprets data for playback, and how data is encoded in captures.
|
||||||
* *cardindex* - the card index. If this argument is given, the device name
|
The default value is :const:`PCM_FORMAT_S16_LE`.
|
||||||
is constructed as 'hw:*cardindex*' and
|
|
||||||
the `device` keyword argument is ignored.
|
|
||||||
``0`` is the first hardware sound card.
|
|
||||||
|
|
||||||
This will construct a PCM object with these default settings:
|
|
||||||
|
|
||||||
* Sample format: :const:`PCM_FORMAT_S16_LE`
|
|
||||||
* Rate: 44100 Hz
|
|
||||||
* Channels: 2
|
|
||||||
* Period size: 32 frames
|
|
||||||
|
|
||||||
*Changed in 0.8:*
|
|
||||||
|
|
||||||
- The `card` keyword argument is still supported,
|
|
||||||
but deprecated. Please use `device` instead.
|
|
||||||
|
|
||||||
- The keyword argument `cardindex` was added.
|
|
||||||
|
|
||||||
The `card` keyword is deprecated because it guesses the real ALSA
|
|
||||||
name of the card. This was always fragile and broke some legitimate usecases.
|
|
||||||
|
|
||||||
|
|
||||||
PCM objects have the following methods:
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: PCM.pcmtype()
|
|
||||||
|
|
||||||
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
|
|
||||||
:const:`PCM_PLAYBACK`.
|
|
||||||
|
|
||||||
|
|
||||||
.. 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()
|
|
||||||
|
|
||||||
Return the name of the sound card used by this PCM object.
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: PCM.setchannels(nchannels)
|
|
||||||
|
|
||||||
Used to set the number of capture or playback channels. Common
|
|
||||||
values are: ``1`` = mono, ``2`` = stereo, and ``6`` = full 6 channel audio.
|
|
||||||
Few sound cards support more than 2 channels
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: PCM.setrate(rate)
|
|
||||||
|
|
||||||
Set the sample rate in Hz for the device. Typical values are ``8000``
|
|
||||||
(mainly used for telephony), ``16000``, ``44100`` (CD quality),
|
|
||||||
``48000`` and ``96000``.
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: PCM.setformat(format)
|
|
||||||
|
|
||||||
The sound *format* of the device. Sound format controls how the PCM device
|
|
||||||
interpret data for playback, and how data is encoded in captures.
|
|
||||||
|
|
||||||
The following formats are provided by ALSA:
|
|
||||||
|
|
||||||
========================= ===============
|
========================= ===============
|
||||||
Format Description
|
Format Description
|
||||||
========================= ===============
|
========================= ===============
|
||||||
``PCM_FORMAT_S8`` Signed 8 bit samples for each channel
|
``PCM_FORMAT_S8`` Signed 8 bit samples for each channel
|
||||||
``PCM_FORMAT_U8`` Signed 8 bit samples for each channel
|
``PCM_FORMAT_U8`` Unsigned 8 bit samples for each channel
|
||||||
``PCM_FORMAT_S16_LE`` Signed 16 bit samples for each channel Little Endian byte order)
|
``PCM_FORMAT_S16_LE`` Signed 16 bit samples for each channel Little Endian byte order)
|
||||||
``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order)
|
||||||
``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order)
|
||||||
``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order)
|
||||||
``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order)}
|
``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order)
|
||||||
``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order)
|
||||||
``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order)
|
||||||
@@ -210,59 +138,365 @@ PCM objects have the following methods:
|
|||||||
``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association.
|
``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association.
|
||||||
``PCM_FORMAT_MPEG`` MPEG encoded audio?
|
``PCM_FORMAT_MPEG`` MPEG encoded audio?
|
||||||
``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech
|
``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech
|
||||||
|
``PCM_FORMAT_S24_3LE`` Signed 24 bit samples for each channel (Little Endian byte order in 3 bytes)
|
||||||
|
``PCM_FORMAT_S24_3BE`` Signed 24 bit samples for each channel (Big Endian byte order in 3 bytes)
|
||||||
|
``PCM_FORMAT_U24_3LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 3 bytes)
|
||||||
|
``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes)
|
||||||
========================= ===============
|
========================= ===============
|
||||||
|
|
||||||
|
|
||||||
.. method:: PCM.setperiodsize(period)
|
* *periodsize* - the period size in frames.
|
||||||
|
Make sure you understand :ref:`the meaning of periods <term-period>`.
|
||||||
|
The default value is 32, which is below the actual minimum of most devices,
|
||||||
|
and will therefore likely be larger in practice.
|
||||||
|
* *periods* - the number of periods in the buffer. The default value is 4.
|
||||||
|
* *device* - the name of the PCM device that should be used (for example
|
||||||
|
a value from the output of :func:`pcms`). The default value is
|
||||||
|
``'default'``.
|
||||||
|
* *cardindex* - the card index. If this argument is given, the device name
|
||||||
|
is constructed as 'hw:*cardindex*' and
|
||||||
|
the `device` keyword argument is ignored.
|
||||||
|
``0`` is the first hardware sound card.
|
||||||
|
|
||||||
Sets the actual period size in frames. Each write should consist of
|
**Note:** This should not be used, as it bypasses most of ALSA's configuration.
|
||||||
exactly this number of frames, and each read will return this
|
|
||||||
number of frames (unless the device is in :const:`PCM_NONBLOCK` mode, in
|
|
||||||
which case it may return nothing at all)
|
|
||||||
|
|
||||||
|
The defaults mentioned above are values passed by :mod:alsaaudio
|
||||||
|
to ALSA, not anything internal to ALSA.
|
||||||
|
|
||||||
.. method:: PCM.read()
|
**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.
|
||||||
|
|
||||||
|
*Changed in 0.10:*
|
||||||
|
|
||||||
|
- Added the optional named parameter `periods`.
|
||||||
|
|
||||||
|
*Changed in 0.9:*
|
||||||
|
|
||||||
|
- Added the optional named parameters `rate`, `channels`, `format` and `periodsize`.
|
||||||
|
|
||||||
|
*Changed in 0.8:*
|
||||||
|
|
||||||
|
- The `card` keyword argument is still supported,
|
||||||
|
but deprecated. Please use `device` instead.
|
||||||
|
|
||||||
|
- The keyword argument `cardindex` was added.
|
||||||
|
|
||||||
|
The `card` keyword is deprecated because it guesses the real ALSA
|
||||||
|
name of the card. This was always fragile and broke some legitimate usecases.
|
||||||
|
|
||||||
|
PCM objects have the following methods:
|
||||||
|
|
||||||
|
.. method:: PCM.info() -> dict
|
||||||
|
|
||||||
|
Returns a dictionary containing the configuration of a PCM device.
|
||||||
|
|
||||||
|
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) "**.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
|
||||||
|
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
|
||||||
|
|
||||||
|
.. method:: PCM.cardname() -> string
|
||||||
|
|
||||||
|
Return the name of the sound card used by this PCM object.
|
||||||
|
|
||||||
|
..
|
||||||
|
Omitted by intention due to not really fitting the c'tor-based setup concept:
|
||||||
|
|
||||||
|
.. method:: PCM.getchannels()
|
||||||
|
|
||||||
|
Returns list of the device's supported channel counts.
|
||||||
|
|
||||||
|
.. method:: PCM.getratebounds()
|
||||||
|
|
||||||
|
Returns the card's minimum and maximum supported sample rates as
|
||||||
|
a tuple of integers.
|
||||||
|
|
||||||
|
.. method:: PCM.getrates()
|
||||||
|
|
||||||
|
Returns the sample rates supported by the device.
|
||||||
|
The returned value can be of one of the following, depending on
|
||||||
|
the card's properties:
|
||||||
|
* Card supports only a single rate: returns the rate
|
||||||
|
* Card supports a continuous range of rates: returns a tuple of
|
||||||
|
the range's lower and upper bounds (inclusive)
|
||||||
|
* Card supports a collection of well-known rates: returns a list of
|
||||||
|
the supported rates
|
||||||
|
|
||||||
|
.. method:: PCM.getformats()
|
||||||
|
|
||||||
|
Returns a dictionary of supported format codes (integers) keyed by
|
||||||
|
their standard ALSA names (strings).
|
||||||
|
|
||||||
|
.. method:: PCM.setchannels(nchannels: int) -> int
|
||||||
|
|
||||||
|
.. deprecated:: 0.9 Use the `channels` named argument to :func:`PCM`.
|
||||||
|
|
||||||
|
.. method:: PCM.setrate(rate: int) -> int
|
||||||
|
|
||||||
|
.. deprecated:: 0.9 Use the `rate` named argument to :func:`PCM`.
|
||||||
|
|
||||||
|
.. method:: PCM.setformat(format: int) -> int
|
||||||
|
|
||||||
|
.. deprecated:: 0.9 Use the `format` named argument to :func:`PCM`.
|
||||||
|
|
||||||
|
.. method:: PCM.setperiodsize(period: int) -> int
|
||||||
|
|
||||||
|
.. deprecated:: 0.9 Use the `periodsize` named argument to :func:`PCM`.
|
||||||
|
|
||||||
|
.. method:: PCM.state() -> int
|
||||||
|
|
||||||
|
Returs the current state of the stream, which can be one of
|
||||||
|
:const:`PCM_STATE_OPEN` (this should not actually happen),
|
||||||
|
:const:`PCM_STATE_SETUP` (after :func:`drop` or :func:`drain`),
|
||||||
|
:const:`PCM_STATE_PREPARED` (after construction),
|
||||||
|
:const:`PCM_STATE_RUNNING`,
|
||||||
|
:const:`PCM_STATE_XRUN`,
|
||||||
|
:const:`PCM_STATE_DRAINING`,
|
||||||
|
:const:`PCM_STATE_PAUSED`,
|
||||||
|
:const:`PCM_STATE_SUSPENDED`, and
|
||||||
|
:const:`PCM_STATE_DISCONNECTED`.
|
||||||
|
|
||||||
|
*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
|
In :const:`PCM_NORMAL` mode, this function blocks until a full period is
|
||||||
available, and then returns a tuple (length,data) where *length* is
|
available, and then returns a tuple (length,data) where *length* is
|
||||||
the number of frames of captured data, and *data* is the captured
|
the number of frames of captured data, and *data* is the captured
|
||||||
sound frames as a string. The length of the returned data will be
|
sound frames as a string. The length of the returned data will be
|
||||||
periodsize\*framesize bytes.
|
periodsize\*framesize bytes.
|
||||||
|
|
||||||
In :const:`PCM_NONBLOCK` mode, the call will not block, but will return
|
In :const:`PCM_NONBLOCK` mode, the call will not block, but will return
|
||||||
``(0,'')`` if no new period has become available since the last
|
``(0,'')`` if no new period has become available since the last
|
||||||
call to read.
|
call to read.
|
||||||
|
|
||||||
|
In case of a buffer overrun, this function will return the negative
|
||||||
|
size :const:`-EPIPE`, and no data is read.
|
||||||
|
This indicates that data was lost. To resume capturing, just call read
|
||||||
|
again, but note that the stream was already corrupted.
|
||||||
|
To avoid the problem in the future, try using a larger period size
|
||||||
|
and/or more periods, at the cost of higher latency.
|
||||||
|
|
||||||
.. method:: PCM.write(data)
|
.. method:: PCM.write(data: bytes) -> int
|
||||||
|
|
||||||
Writes (plays) the sound in data. The length of data *must* be a
|
Writes (plays) the sound in data. The length of data *must* be a
|
||||||
multiple of the frame size, and *should* be exactly the size of a
|
multiple of the frame size, and *should* be exactly the size of a
|
||||||
period. If less than 'period size' frames are provided, the actual
|
period. If less than 'period size' frames are provided, the actual
|
||||||
playout will not happen until more data is written.
|
playout will not happen until more data is written.
|
||||||
|
|
||||||
If the device is not in :const:`PCM_NONBLOCK` mode, this call will block if
|
If the data was successfully written, the call returns the size of the
|
||||||
the kernel buffer is full, and until enough sound has been played
|
data *in frames*.
|
||||||
to allow the sound data to be buffered. The call always returns the
|
|
||||||
size of the data provided.
|
If the device is not in :const:`PCM_NONBLOCK` mode, this call will block
|
||||||
|
if the kernel buffer is full, and until enough sound has been played
|
||||||
|
to allow the sound data to be buffered.
|
||||||
|
|
||||||
In :const:`PCM_NONBLOCK` mode, the call will return immediately, with a
|
In :const:`PCM_NONBLOCK` mode, the call will return immediately, with a
|
||||||
return value of zero, if the buffer is full. In this case, the data
|
return value of zero, if the buffer is full. In this case, the data
|
||||||
should be written at a later time.
|
should be written again at a later time.
|
||||||
|
|
||||||
|
In case of a buffer underrun, this function will return the negative
|
||||||
|
size :const:`-EPIPE`, and no data is written.
|
||||||
|
At this point, the playback was already corrupted. If you want to play
|
||||||
|
the data nonetheless, call write again with the same data.
|
||||||
|
To avoid the problem in the future, try using a larger period size
|
||||||
|
and/or more periods, at the cost of higher latency.
|
||||||
|
|
||||||
.. method:: PCM.pause([enable=True])
|
Note that this call completing means only that the samples were buffered
|
||||||
|
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
|
||||||
|
|
||||||
If *enable* is :const:`True`, playback or capture is paused.
|
If *enable* is :const:`True`, playback or capture is paused.
|
||||||
Otherwise, playback/capture is resumed.
|
Otherwise, playback/capture is resumed.
|
||||||
|
|
||||||
|
.. method:: PCM.drop() -> int
|
||||||
|
|
||||||
|
Stop the stream and drop residual buffered frames.
|
||||||
|
|
||||||
|
*New in 0.9*
|
||||||
|
|
||||||
|
.. method:: PCM.drain() -> int
|
||||||
|
|
||||||
|
For :const:`PCM_PLAYBACK` PCM objects, play residual buffered frames
|
||||||
|
and then stop the stream. In :const:`PCM_NORMAL` mode,
|
||||||
|
this function blocks until all pending playback is drained.
|
||||||
|
|
||||||
|
For :const:`PCM_CAPTURE` PCM objects, this function is not very useful.
|
||||||
|
|
||||||
|
*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
|
||||||
|
used to wait for changes on the PCM with *select.poll*.
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
.. method:: PCM.get_tstamp_mode() -> int
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Set the ALSA timestamp mode on the device. The type argument
|
||||||
|
can be set to either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`,
|
||||||
|
:const:`PCM_TSTAMP_TYPE_MONOTONIC` or :const:`PCM_TSTAMP_TYPE_MONOTONIC_RAW`.
|
||||||
|
|
||||||
|
.. method:: PCM.get_tstamp_type() -> int
|
||||||
|
|
||||||
|
Return the integer value corresponding to the ALSA timestamp type. The
|
||||||
|
return value can be either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`,
|
||||||
|
:const:`PCM_TSTAMP_TYPE_MONOTONIC` or :const:`PCM_TSTAMP_TYPE_MONOTONIC_RAW`.
|
||||||
|
|
||||||
|
.. method:: PCM.htimestamp() -> tuple[int, int, int]
|
||||||
|
|
||||||
|
Return a Python tuple *(seconds, nanoseconds, frames_available_in_buffer)*.
|
||||||
|
|
||||||
|
The type of output is controlled by the tstamp_type, as described in the table below.
|
||||||
|
|
||||||
|
================================= ===========================================
|
||||||
|
Timestamp Type Description
|
||||||
|
================================= ===========================================
|
||||||
|
``PCM_TSTAMP_TYPE_GETTIMEOFDAY`` System-wide realtime clock with seconds
|
||||||
|
since epoch.
|
||||||
|
``PCM_TSTAMP_TYPE_MONOTONIC`` Monotonic time from an unspecified starting
|
||||||
|
time. Progress is NTP synchronized.
|
||||||
|
``PCM_TSTAMP_TYPE_MONOTONIC_RAW`` Monotonic time from an unspecified starting
|
||||||
|
time using only the system clock.
|
||||||
|
================================= ===========================================
|
||||||
|
|
||||||
|
The timestamp mode is controlled by the tstamp_mode, as described in the table below.
|
||||||
|
|
||||||
|
================================= ===========================================
|
||||||
|
Timestamp Mode Description
|
||||||
|
================================= ===========================================
|
||||||
|
``PCM_TSTAMP_NONE`` No timestamp.
|
||||||
|
``PCM_TSTAMP_ENABLE`` Update timestamp at every hardware position
|
||||||
|
update.
|
||||||
|
================================= ===========================================
|
||||||
|
|
||||||
**A few hints on using PCM devices for playback**
|
**A few hints on using PCM devices for playback**
|
||||||
|
|
||||||
The most common reason for problems with playback of PCM audio is that writes
|
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.
|
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
|
If too little data is written to the device, it will underrun, and
|
||||||
ugly clicking sounds will occur. Conversely, of too much data is
|
ugly clicking sounds will occur. Conversely, if too much data is
|
||||||
written to the device, the write function will either block
|
written to the device, the write function will either block
|
||||||
(:const:`PCM_NORMAL` mode) or return zero (:const:`PCM_NONBLOCK` mode).
|
(:const:`PCM_NORMAL` mode) or return zero (:const:`PCM_NONBLOCK` mode).
|
||||||
|
|
||||||
@@ -291,13 +525,12 @@ Mixer Objects
|
|||||||
|
|
||||||
Mixer objects provides access to the ALSA mixer API.
|
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')
|
|
||||||
|
|
||||||
Arguments are:
|
Arguments are:
|
||||||
|
|
||||||
* *control* - specifies which control to manipulate using this mixer
|
* *control* - specifies which control to manipulate using this mixer
|
||||||
object. The list of available controls can be found with the
|
object. The list of available controls can be found with the
|
||||||
:mod:`alsaaudio`.\ :func:`mixers` function. The default value is
|
:mod:`alsaaudio`.\ :func:`mixers` function. The default value is
|
||||||
``'Master'`` - other common controls may be ``'Master Mono'``, ``'PCM'``,
|
``'Master'`` - other common controls may be ``'Master Mono'``, ``'PCM'``,
|
||||||
``'Line'``, etc.
|
``'Line'``, etc.
|
||||||
@@ -307,35 +540,32 @@ Mixer objects provides access to the ALSA mixer API.
|
|||||||
* *cardindex* - specifies which card should be used. If this argument
|
* *cardindex* - specifies which card should be used. If this argument
|
||||||
is given, the device name is constructed like this: 'hw:*cardindex*' and
|
is given, the device name is constructed like this: 'hw:*cardindex*' and
|
||||||
the `device` keyword argument is ignored. ``0`` is the
|
the `device` keyword argument is ignored. ``0`` is the
|
||||||
first sound card.
|
first sound card.
|
||||||
|
|
||||||
* *device* - the name of the device on which the mixer resides. The default
|
* *device* - the name of the device on which the mixer resides. The default
|
||||||
value is ``'default'``.
|
value is ``'default'``.
|
||||||
|
|
||||||
*Changed in 0.8*:
|
*Changed in 0.8*:
|
||||||
|
|
||||||
- The keyword argument `device` is new and can be used to select virtual
|
- The keyword argument `device` is new and can be used to select virtual
|
||||||
devices.
|
devices.
|
||||||
|
|
||||||
Mixer objects have the following methods:
|
Mixer objects have the following methods:
|
||||||
|
|
||||||
.. method:: Mixer.cardname()
|
.. method:: Mixer.cardname() -> str
|
||||||
|
|
||||||
Return the name of the sound card used by this Mixer object
|
Return the name of the sound card used by this Mixer object
|
||||||
|
|
||||||
|
.. method:: Mixer.mixer() -> str
|
||||||
.. method:: Mixer.mixer()
|
|
||||||
|
|
||||||
Return the name of the specific mixer controlled by this object, For example
|
Return the name of the specific mixer controlled by this object, For example
|
||||||
``'Master'`` or ``'PCM'``
|
``'Master'`` or ``'PCM'``
|
||||||
|
|
||||||
|
.. method:: Mixer.mixerid() -> int
|
||||||
.. method:: Mixer.mixerid()
|
|
||||||
|
|
||||||
Return the ID of the ALSA mixer controlled by this object.
|
Return the ID of the ALSA mixer controlled by this object.
|
||||||
|
|
||||||
|
.. method:: Mixer.switchcap() -> int
|
||||||
.. method:: Mixer.switchcap()
|
|
||||||
|
|
||||||
Returns a list of the switches which are defined by this specific mixer.
|
Returns a list of the switches which are defined by this specific mixer.
|
||||||
Possible values in this list are:
|
Possible values in this list are:
|
||||||
@@ -347,7 +577,7 @@ Mixer objects have the following methods:
|
|||||||
'Joined Mute' This mixer can mute all channels at the same time
|
'Joined Mute' This mixer can mute all channels at the same time
|
||||||
'Playback Mute' This mixer can mute the playback output
|
'Playback Mute' This mixer can mute the playback output
|
||||||
'Joined Playback Mute' Mute playback for all channels at the same time}
|
'Joined Playback Mute' Mute playback for all channels at the same time}
|
||||||
'Capture Mute' Mute sound capture
|
'Capture Mute' Mute sound capture
|
||||||
'Joined Capture Mute' Mute sound capture for all channels at a time}
|
'Joined Capture Mute' Mute sound capture for all channels at a time}
|
||||||
'Capture Exclusive' Not quite sure what this is
|
'Capture Exclusive' Not quite sure what this is
|
||||||
====================== ================
|
====================== ================
|
||||||
@@ -355,8 +585,7 @@ Mixer objects have the following methods:
|
|||||||
To manipulate these switches use the :meth:`setrec` or
|
To manipulate these switches use the :meth:`setrec` or
|
||||||
:meth:`setmute` methods
|
:meth:`setmute` methods
|
||||||
|
|
||||||
|
.. method:: Mixer.volumecap() -> int
|
||||||
.. method:: Mixer.volumecap()
|
|
||||||
|
|
||||||
Returns a list of the volume control capabilities of this
|
Returns a list of the volume control capabilities of this
|
||||||
mixer. Possible values in the list are:
|
mixer. Possible values in the list are:
|
||||||
@@ -371,8 +600,8 @@ Mixer objects have the following methods:
|
|||||||
'Capture Volume' Manipulate sound capture volume
|
'Capture Volume' Manipulate sound capture volume
|
||||||
'Joined Capture Volume' Manipulate sound capture volume for all channels at a time
|
'Joined Capture Volume' Manipulate sound capture volume for all channels at a time
|
||||||
======================== ================
|
======================== ================
|
||||||
|
|
||||||
.. method:: Mixer.getenum()
|
.. method:: Mixer.getenum() -> tuple[str, list[str]]
|
||||||
|
|
||||||
For enumerated controls, return the currently selected item and the list of
|
For enumerated controls, return the currently selected item and the list of
|
||||||
items available.
|
items available.
|
||||||
@@ -398,59 +627,64 @@ Mixer objects have the following methods:
|
|||||||
This method will return an empty tuple if the mixer is not an enumerated
|
This method will return an empty tuple if the mixer is not an enumerated
|
||||||
control.
|
control.
|
||||||
|
|
||||||
|
.. method:: Mixer.setenum(index: int) -> None
|
||||||
|
|
||||||
.. method:: Mixer.getmute()
|
For enumerated controls, sets the currently selected item.
|
||||||
|
*index* is an index into the list of available enumerated items returned
|
||||||
|
by :func:`getenum`.
|
||||||
|
|
||||||
Return a list indicating the current mute setting for each
|
.. method:: Mixer.getrange(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_RAW) -> tuple[int, int]
|
||||||
channel. 0 means not muted, 1 means muted.
|
|
||||||
|
|
||||||
This method will fail if the mixer has no playback switch capabilities.
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: Mixer.getrange([direction])
|
|
||||||
|
|
||||||
Return the volume range of the ALSA mixer controlled by this object.
|
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
|
||||||
|
*units* argument.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The optional *pcmtype* argument can be either :const:`PCM_PLAYBACK` or
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||||
|
|
||||||
|
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
|
||||||
|
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
|
||||||
|
|
||||||
.. method:: Mixer.getrec()
|
.. method:: Mixer.getvolume(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE) -> int
|
||||||
|
|
||||||
Return a list indicating the current record mute setting for each channel. 0
|
|
||||||
means not recording, 1 means recording.
|
|
||||||
|
|
||||||
This method will fail if the mixer has no capture switch capabilities.
|
|
||||||
|
|
||||||
|
|
||||||
.. method:: Mixer.getvolume([direction])
|
|
||||||
|
|
||||||
Returns a list with the current volume settings for each channel. The list
|
Returns a list with the current volume settings for each channel. The list
|
||||||
elements are integer percentages.
|
elements are integers whose meaning is determined by the *units* argument.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The optional *pcmtype* argument can be either :const:`PCM_PLAYBACK` or
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||||
|
|
||||||
|
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, [channel], [direction])
|
.. method:: Mixer.setvolume(volume: int, pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE, channel: (int | None) = None) -> None
|
||||||
|
|
||||||
Change the current volume settings for this mixer. The *volume* argument
|
Change the current volume settings for this mixer. The *volume* argument
|
||||||
controls the new volume setting as an integer percentage.
|
is an integer whose meaning is determined by the *units* argument.
|
||||||
|
|
||||||
If the optional argument *channel* is present, the volume is set
|
If the optional argument *channel* is present, the volume is set
|
||||||
only for this channel. This assumes that the mixer can control the
|
only for this channel. This assumes that the mixer can control the
|
||||||
volume for the channels independently.
|
volume for the channels independently.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The optional *pcmtype* argument can be either :const:`PCM_PLAYBACK` or
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||||
|
|
||||||
.. method:: Mixer.setmute(mute, [channel])
|
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
|
||||||
|
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
|
||||||
|
|
||||||
|
.. method:: Mixer.getmute() -> list[int]
|
||||||
|
|
||||||
|
Return a list indicating the current mute setting for each channel.
|
||||||
|
0 means not muted, 1 means muted.
|
||||||
|
|
||||||
|
This method will fail if the mixer has no playback switch capabilities.
|
||||||
|
|
||||||
|
.. 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
|
Sets the mute flag to a new value. The *mute* argument is either 0 for not
|
||||||
muted, or 1 for muted.
|
muted, or 1 for muted.
|
||||||
@@ -460,8 +694,14 @@ Mixer objects have the following methods:
|
|||||||
|
|
||||||
This method will fail if the mixer has no playback mute capabilities
|
This method will fail if the mixer has no playback mute capabilities
|
||||||
|
|
||||||
|
.. method:: Mixer.getrec() -> list[int]
|
||||||
|
|
||||||
.. method:: Mixer.setrec(capture, [channel])
|
Return a list indicating the current record mute setting for each channel.
|
||||||
|
0 means not recording, 1 means recording.
|
||||||
|
|
||||||
|
This method will fail if the mixer has no capture switch capabilities.
|
||||||
|
|
||||||
|
.. method:: Mixer.setrec(capture: int, channel: (int | None) = None) -> None
|
||||||
|
|
||||||
Sets the capture mute flag to a new value. The *capture* argument
|
Sets the capture mute flag to a new value. The *capture* argument
|
||||||
is either 0 for no capture, or 1 for capture.
|
is either 0 for no capture, or 1 for capture.
|
||||||
@@ -471,17 +711,24 @@ Mixer objects have the following methods:
|
|||||||
|
|
||||||
This method will fail if the mixer has no capture switch capabilities.
|
This method will fail if the mixer has no capture switch capabilities.
|
||||||
|
|
||||||
.. method:: Mixer.polldescriptors()
|
.. method:: Mixer.polldescriptors() -> list[tuple[int, int]]
|
||||||
|
|
||||||
Returns a tuple of (file descriptor, eventmask) that can be used to
|
Returns a list of tuples of *(file descriptor, eventmask)* that can be
|
||||||
wait for changes on the mixer with *select.poll*.
|
used to wait for changes on the mixer with *select.poll*.
|
||||||
|
|
||||||
.. method:: Mixer.handleevents()
|
The *eventmask* value is compatible with `poll.register`__ in the Python
|
||||||
|
:const:`select` module.
|
||||||
|
|
||||||
Acknowledge events on the *polldescriptors* file descriptors
|
.. method:: Mixer.handleevents() -> int
|
||||||
|
|
||||||
|
Acknowledge events on the :func:`polldescriptors` file descriptors
|
||||||
to prevent subsequent polls from returning the same events again.
|
to prevent subsequent polls from returning the same events again.
|
||||||
Returns the number of events that were acknowledged.
|
Returns the number of events that were acknowledged.
|
||||||
|
|
||||||
|
.. method:: Mixer.close() -> None
|
||||||
|
|
||||||
|
Closes the Mixer device.
|
||||||
|
|
||||||
**A rant on the ALSA Mixer API**
|
**A rant on the ALSA Mixer API**
|
||||||
|
|
||||||
The ALSA mixer API is extremely complicated - and hardly documented at all.
|
The ALSA mixer API is extremely complicated - and hardly documented at all.
|
||||||
@@ -504,8 +751,6 @@ Unfortunately, I'm not able to create such a HOWTO myself, since I only
|
|||||||
understand half of the API, and that which I do understand has come from a
|
understand half of the API, and that which I do understand has come from a
|
||||||
painful trial and error process.
|
painful trial and error process.
|
||||||
|
|
||||||
.. % ==== 4. ====
|
|
||||||
|
|
||||||
|
|
||||||
.. _pcm-example:
|
.. _pcm-example:
|
||||||
|
|
||||||
@@ -519,7 +764,7 @@ The following example are provided:
|
|||||||
* `playbacktest.py`
|
* `playbacktest.py`
|
||||||
* `mixertest.py`
|
* `mixertest.py`
|
||||||
|
|
||||||
All examples (except `mixertest.py`) accept the commandline option
|
All examples (except `mixertest.py`) accept the commandline option
|
||||||
*-c <cardname>*.
|
*-c <cardname>*.
|
||||||
|
|
||||||
To determine a valid card name, use the commandline ALSA player::
|
To determine a valid card name, use the commandline ALSA player::
|
||||||
@@ -534,12 +779,12 @@ or::
|
|||||||
>>> alsaaudio.pcms()
|
>>> alsaaudio.pcms()
|
||||||
|
|
||||||
mixertest.py accepts the commandline options *-d <device>* and
|
mixertest.py accepts the commandline options *-d <device>* and
|
||||||
*-c <cardindex>*.
|
*-c <cardindex>*.
|
||||||
|
|
||||||
playwav.py
|
playwav.py
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
**playwav.py** plays a wav file.
|
**playwav.py** plays a wav file.
|
||||||
|
|
||||||
To test PCM playback (on your default soundcard), run::
|
To test PCM playback (on your default soundcard), run::
|
||||||
|
|
||||||
@@ -547,6 +792,7 @@ To test PCM playback (on your default soundcard), run::
|
|||||||
|
|
||||||
recordtest.py and playbacktest.py
|
recordtest.py and playbacktest.py
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
**recordtest.py** and **playbacktest.py** will record and play a raw
|
**recordtest.py** and **playbacktest.py** will record and play a raw
|
||||||
sound file in CD quality.
|
sound file in CD quality.
|
||||||
|
|
||||||
@@ -568,7 +814,7 @@ Without arguments, **mixertest.py** will list all available *controls* on the
|
|||||||
default soundcard.
|
default soundcard.
|
||||||
|
|
||||||
The output might look like this::
|
The output might look like this::
|
||||||
|
|
||||||
$ ./mixertest.py
|
$ ./mixertest.py
|
||||||
Available mixer controls:
|
Available mixer controls:
|
||||||
'Master'
|
'Master'
|
||||||
@@ -586,7 +832,7 @@ The output might look like this::
|
|||||||
'Mix'
|
'Mix'
|
||||||
'Mix Mono'
|
'Mix Mono'
|
||||||
|
|
||||||
With a single argument - the *control*, it will display the settings of
|
With a single argument - the *control*, it will display the settings of
|
||||||
that control; for example::
|
that control; for example::
|
||||||
|
|
||||||
$ ./mixertest.py Master
|
$ ./mixertest.py Master
|
||||||
@@ -595,7 +841,7 @@ that control; for example::
|
|||||||
Channel 0 volume: 61%
|
Channel 0 volume: 61%
|
||||||
Channel 1 volume: 61%
|
Channel 1 volume: 61%
|
||||||
|
|
||||||
With two arguments, the *control* and a *parameter*, it will set the
|
With two arguments, the *control* and a *parameter*, it will set the
|
||||||
parameter on the mixer::
|
parameter on the mixer::
|
||||||
|
|
||||||
$ ./mixertest.py Master mute
|
$ ./mixertest.py Master mute
|
||||||
@@ -616,7 +862,3 @@ argument::
|
|||||||
Capabilities: Playback Volume Playback Mute
|
Capabilities: Playback Volume Playback Mute
|
||||||
Channel 0 volume: 61%
|
Channel 0 volume: 61%
|
||||||
Channel 1 volume: 61%
|
Channel 1 volume: 61%
|
||||||
|
|
||||||
.. rubric:: Footnotes
|
|
||||||
|
|
||||||
.. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet.
|
|
||||||
|
|||||||
@@ -7,33 +7,19 @@ Introduction
|
|||||||
|
|
||||||
.. |release| replace:: version
|
.. |release| replace:: version
|
||||||
|
|
||||||
.. % At minimum, give your name and an email address. You can include a
|
|
||||||
.. % snail-mail address if you like.
|
|
||||||
|
|
||||||
.. % This makes the Abstract go on a separate page in the HTML version;
|
|
||||||
.. % if a copyright notice is used, it should go immediately after this.
|
|
||||||
.. %
|
|
||||||
|
|
||||||
|
|
||||||
.. _front:
|
.. _front:
|
||||||
|
|
||||||
This software is licensed under the PSF license - the same one used by the
|
This software is licensed under the PSF license - the same one used by the
|
||||||
majority of the python distribution. Basically you can use it for anything you
|
majority of the python distribution. Basically you can use it for anything you
|
||||||
wish (even commercial purposes). There is no warranty whatsoever.
|
wish (even commercial purposes). There is no warranty whatsoever.
|
||||||
|
|
||||||
.. % Copyright statement should go here, if needed.
|
|
||||||
|
|
||||||
.. % The abstract should be a paragraph or two long, and describe the
|
|
||||||
.. % scope of the document.
|
|
||||||
|
|
||||||
|
|
||||||
.. topic:: Abstract
|
.. topic:: Abstract
|
||||||
|
|
||||||
This package contains wrappers for accessing the ALSA API from Python. It is
|
This package contains wrappers for accessing the ALSA API from Python. It is
|
||||||
currently fairly complete for PCM devices and Mixer access. MIDI sequencer
|
currently fairly complete for PCM devices and Mixer access. MIDI sequencer
|
||||||
support is low on our priority list, but volunteers are welcome.
|
support is low on our priority list, but volunteers are welcome.
|
||||||
|
|
||||||
If you find bugs in the wrappers please use thegithub issue tracker.
|
If you find bugs in the wrappers please use the github issue tracker.
|
||||||
Please don't send bug reports regarding ALSA specifically. There are several
|
Please don't send bug reports regarding ALSA specifically. There are several
|
||||||
bugs in this API, and those should be reported to the ALSA team - not me.
|
bugs in this API, and those should be reported to the ALSA team - not me.
|
||||||
|
|
||||||
@@ -64,8 +50,8 @@ More information about ALSA may be found on the project homepage
|
|||||||
ALSA and Python
|
ALSA and Python
|
||||||
===============
|
===============
|
||||||
|
|
||||||
The older Linux sound API (OSS) which is now deprecated is well supported from
|
The older Linux sound API (OSS) -- which is now deprecated -- is well supported
|
||||||
the standard Python library, through the ossaudiodev module. No native ALSA
|
by the standard Python library, through the ossaudiodev module. No native ALSA
|
||||||
support exists in the standard library.
|
support exists in the standard library.
|
||||||
|
|
||||||
There are a few other "ALSA for Python" projects available, including at least
|
There are a few other "ALSA for Python" projects available, including at least
|
||||||
@@ -75,7 +61,7 @@ development at the time - and neither are very feature complete.
|
|||||||
I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module
|
I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module
|
||||||
included in the standard Python library, but that looks currently unlikely.
|
included in the standard Python library, but that looks currently unlikely.
|
||||||
|
|
||||||
PyAlsaAudio hass full support for sound capture, playback of sound, as well as
|
PyAlsaAudio has full support for sound capture, playback of sound, as well as
|
||||||
the ALSA Mixer API.
|
the ALSA Mixer API.
|
||||||
|
|
||||||
MIDI support is not available, and since I don't own any MIDI hardware, it's
|
MIDI support is not available, and since I don't own any MIDI hardware, it's
|
||||||
@@ -95,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
|
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
|
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.
|
ship with ALSA kernels.
|
||||||
|
|
||||||
To install, execute the following: --- ::
|
To install, execute the following: --- ::
|
||||||
@@ -106,29 +92,37 @@ And then as root: --- ::
|
|||||||
|
|
||||||
# python setup.py install
|
# python setup.py install
|
||||||
|
|
||||||
|
|
||||||
*******
|
*******
|
||||||
Testing
|
Testing
|
||||||
*******
|
*******
|
||||||
|
|
||||||
First of all, run::
|
Make sure that :code:`aplay` plays a file through the soundcard you want, then
|
||||||
|
try::
|
||||||
$ python test.py
|
|
||||||
|
|
||||||
This is a small test suite that mostly performs consistency tests. If
|
$ python playwav.py <filename.wav>
|
||||||
it fails, please file a `bug report
|
|
||||||
<https://github.com/larsimmisch/pyalsaaudio/issues>`_.
|
If :code:`aplay` needs a device argument, like
|
||||||
|
:code:`aplay -D hw:CARD=sndrpihifiberry,DEV=0`, use::
|
||||||
|
|
||||||
|
$ python playwav.py -d hw:CARD=sndrpihifiberry,DEV=0 <filename.wav>
|
||||||
|
|
||||||
To test PCM recordings (on your default soundcard), verify your
|
To test PCM recordings (on your default soundcard), verify your
|
||||||
microphone works, then do::
|
microphone works, then do::
|
||||||
|
|
||||||
$ python recordtest.py <filename>
|
$ python recordtest.py -d <device> <filename>
|
||||||
|
|
||||||
Speak into the microphone, and interrupt the recording at any time
|
Speak into the microphone, and interrupt the recording at any time
|
||||||
with ``Ctl-C``.
|
with ``Ctl-C``.
|
||||||
|
|
||||||
Play back the recording with::
|
Play back the recording with::
|
||||||
|
|
||||||
$ python playbacktest.py <filename>
|
$ python playbacktest.py -d <device> <filename>
|
||||||
|
|
||||||
|
There is a minimal test suite in :code:`test.py`, but it is
|
||||||
|
a bit dependent on the ALSA configuration and may fail without indicating
|
||||||
|
a real problem.
|
||||||
|
|
||||||
|
If you find bugs/problems, please file a `bug report
|
||||||
|
<https://github.com/larsimmisch/pyalsaaudio/issues>`_.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ In order to use PCM devices it is useful to be familiar with some concepts and
|
|||||||
terminology.
|
terminology.
|
||||||
|
|
||||||
Sample
|
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
|
A single sample represents the amplitude of one channel of sound
|
||||||
at a certain point in time. A lot of individual samples are
|
at a certain point in time. A lot of individual samples are
|
||||||
necessary to represent actual sound; for CD audio, 44100 samples
|
necessary to represent actual sound; for CD audio, 44100 samples
|
||||||
@@ -19,26 +19,26 @@ Sample
|
|||||||
|
|
||||||
Musically, the sample size determines the dynamic range. The
|
Musically, the sample size determines the dynamic range. The
|
||||||
dynamic range is the difference between the quietest and the
|
dynamic range is the difference between the quietest and the
|
||||||
loudest signal that can be resproduced.
|
loudest signal that can be reproduced.
|
||||||
|
|
||||||
Frame
|
Frame
|
||||||
A frame consists of exactly one sample per channel. If there is only one
|
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
|
channel (Mono sound) a frame is simply a single sample. If the sound is
|
||||||
stereo, each frame consists of two samples, etc.
|
stereo, each frame consists of two samples, etc.
|
||||||
|
|
||||||
Frame size
|
Frame size
|
||||||
This is the size in bytes of each frame. This can vary a lot: if each sample
|
This is the size in bytes of each frame. This can vary a lot: if each sample
|
||||||
is 8 bits, and we're handling mono sound, the frame size is one byte.
|
is 8 bits, and we're handling mono sound, the frame size is one byte.
|
||||||
Similarly in 6 channel audio with 64 bit floating point samples, the frame
|
For six channel audio with 64 bit floating point samples, the frame size
|
||||||
size is 48 bytes
|
is 48 bytes.
|
||||||
|
|
||||||
Rate
|
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
|
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.
|
means that a new frame is played or captured 8000 times per second.
|
||||||
|
|
||||||
Data rate
|
Data rate
|
||||||
This is the number of bytes, which must be recorded or provided per
|
This is the number of bytes which must be consumed or provided per
|
||||||
second at a certain frame size and rate.
|
second at a certain frame size and rate.
|
||||||
|
|
||||||
8000 Hz mono sound with 8 bit (1 byte) samples has a data rate of
|
8000 Hz mono sound with 8 bit (1 byte) samples has a data rate of
|
||||||
@@ -46,29 +46,60 @@ Data rate
|
|||||||
|
|
||||||
At the other end of the scale, 96000 Hz, 6 channel sound with 64
|
At the other end of the scale, 96000 Hz, 6 channel sound with 64
|
||||||
bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608
|
bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608
|
||||||
kb/s (almost 5 Mb sound data per second)
|
kb/s (almost 5 MB sound data per second).
|
||||||
|
|
||||||
|
If the data rate requirement is not met, an overrun (on capture) or
|
||||||
|
underrun (on playback) occurs; the term "xrun" is used to refer to
|
||||||
|
either event.
|
||||||
|
|
||||||
|
.. _term-period:
|
||||||
|
|
||||||
Period
|
Period
|
||||||
When the hardware processes data this is done in chunks of frames. The time
|
The CPU processes sample data in chunks of frames, so-called periods
|
||||||
interval between each processing (A/D or D/A conversion) is known
|
(also called fragments by some systems). The operating system kernel's
|
||||||
as the period.
|
sample buffer must hold at least two periods (at any given time, one
|
||||||
The size of the period has direct implication on the latency of the
|
is processed by the sound hardware, and one by the CPU).
|
||||||
sound input or output. For low-latency the period size should be
|
|
||||||
very small, while low CPU resource usage would usually demand
|
The completion of a *period* triggers a CPU interrupt, which causes
|
||||||
larger period sizes. With ALSA, the CPU utilization is not impacted
|
processing and context switching overhead. Therefore, a smaller period
|
||||||
much by the period size, since the kernel layer buffers multiple
|
size causes higher CPU resource usage at a given data rate.
|
||||||
periods internally, so each period generates an interrupt and a
|
|
||||||
memory copy, but userspace can be slower and read or write multiple
|
A bigger size of the *buffer* improves the system's resilience to xruns.
|
||||||
periods at the same time.
|
The buffer being split into a bigger number of smaller periods also does
|
||||||
|
that, as it allows it to be drained / topped up sooner.
|
||||||
|
|
||||||
|
On the other hand, a bigger size of the *buffer* also increases the
|
||||||
|
playback latency, that is, the time it takes for a frame from being
|
||||||
|
sent out by the application to being actually audible.
|
||||||
|
|
||||||
|
Similarly, a bigger *period* size increases the capture latency.
|
||||||
|
|
||||||
|
The trade-off between latency, xrun resilience, and resource usage
|
||||||
|
must be made depending on the application.
|
||||||
|
|
||||||
Period size
|
Period size
|
||||||
This is the size of each period in Hz. *Not bytes, but Hz!.* In
|
This is the size of each period in frames. *Not bytes, but frames!*
|
||||||
:mod:`alsaaudio` the period size is set directly, and it is
|
In :mod:`alsaaudio` the period size is set directly, and it is
|
||||||
therefore important to understand the significance of this
|
therefore important to understand the significance of this
|
||||||
number. If the period size is configured to for example 32,
|
number. If the period size is configured to for example 32,
|
||||||
each write should contain exactly 32 frames of sound data, and each
|
each write should contain exactly 32 frames of sound data, and each
|
||||||
read will return either 32 frames of data or nothing at all.
|
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
|
Once you understand these concepts, you will be ready to use the PCM API. Read
|
||||||
on.
|
on.
|
||||||
|
|
||||||
|
|||||||
100
isine.py
100
isine.py
@@ -6,40 +6,59 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from queue import Queue, Empty
|
from multiprocessing import Queue
|
||||||
from math import pi, sin
|
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
from Queue import Empty
|
||||||
|
else:
|
||||||
|
from queue import Empty
|
||||||
|
|
||||||
|
from math import pi, sin, ceil
|
||||||
import struct
|
import struct
|
||||||
|
import itertools
|
||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
|
||||||
sampling_rate = 44100
|
sampling_rate = 48000
|
||||||
|
|
||||||
format = alsaaudio.PCM_FORMAT_S16_LE
|
format = alsaaudio.PCM_FORMAT_S16_LE
|
||||||
framesize = 2 # bytes per frame for the values above
|
pack_format = 'h' # short int, matching S16
|
||||||
|
channels = 2
|
||||||
|
|
||||||
def digitize(s):
|
def nearest_frequency(frequency):
|
||||||
if s > 1.0 or s < -1.0:
|
# calculate the nearest frequency where the wave form fits into the buffer
|
||||||
raise ValueError
|
# in other words, select f so that sampling_rate/f is an integer
|
||||||
|
return float(sampling_rate)/int(sampling_rate/frequency)
|
||||||
return struct.pack('h', int(s * 32767))
|
|
||||||
|
|
||||||
def generate(frequency):
|
def generate(frequency, duration = 0.125):
|
||||||
# generate a buffer with a sine wave of frequency
|
# generate a buffer with a sine wave of `frequency`
|
||||||
size = int(sampling_rate / frequency)
|
# that is approximately `duration` seconds long
|
||||||
buffer = bytes()
|
|
||||||
for i in range(size):
|
# the length of a full sine wave at the frequency
|
||||||
buffer = buffer + digitize(sin(i/(2 * pi)))
|
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
|
||||||
|
|
||||||
|
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 buffer
|
|
||||||
|
|
||||||
class SinePlayer(Thread):
|
class SinePlayer(Thread):
|
||||||
|
|
||||||
def __init__(self, frequency = 440.0):
|
def __init__(self, frequency = 440.0):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self, daemon=True)
|
||||||
self.setDaemon(True)
|
self.device = alsaaudio.PCM(channels=channels, format=format, rate=sampling_rate)
|
||||||
self.device = alsaaudio.PCM()
|
|
||||||
self.device.setchannels(1)
|
|
||||||
self.device.setformat(format)
|
|
||||||
self.device.setrate(sampling_rate)
|
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
self.change(frequency)
|
self.change(frequency)
|
||||||
|
|
||||||
@@ -47,35 +66,32 @@ class SinePlayer(Thread):
|
|||||||
'''This is called outside of the player thread'''
|
'''This is called outside of the player thread'''
|
||||||
# we generate the buffer in the calling thread for less
|
# we generate the buffer in the calling thread for less
|
||||||
# latency when switching frequencies
|
# latency when switching frequencies
|
||||||
|
|
||||||
|
|
||||||
# More than 100 writes/s are pushing it - play multiple buffers
|
if frequency > sampling_rate / 2:
|
||||||
# for higher frequencies
|
raise ValueError('maximum frequency is %d' % (sampling_rate / 2))
|
||||||
|
|
||||||
factor = int(frequency/100.0)
|
f = nearest_frequency(frequency)
|
||||||
if factor == 0:
|
print('nearest frequency: %f' % f)
|
||||||
factor = 1
|
|
||||||
|
buf = generate(f)
|
||||||
buf = generate(frequency) * factor
|
self.queue.put(buf)
|
||||||
print('factor: %d, frames: %d' % (factor, len(buf) / framesize))
|
|
||||||
|
|
||||||
self.queue.put( buf)
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
buffer = None
|
buffer = None
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
buffer = self.queue.get(False)
|
buffer = self.queue.get(False)
|
||||||
self.device.setperiodsize(int(len(buffer) / framesize))
|
|
||||||
self.device.write(buffer)
|
|
||||||
except Empty:
|
except Empty:
|
||||||
if buffer:
|
pass
|
||||||
self.device.write(buffer)
|
if buffer:
|
||||||
|
if self.device.write(buffer) < 0:
|
||||||
|
print("Playback buffer underrun! Continuing nonetheless ...")
|
||||||
|
|
||||||
|
|
||||||
isine = SinePlayer()
|
isine = SinePlayer()
|
||||||
isine.start()
|
isine.start()
|
||||||
|
|
||||||
def change(f):
|
time.sleep(1)
|
||||||
isine.change(f)
|
isine.change(1000)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|||||||
403
loopback.py
Normal file
403
loopback.py
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import select
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from alsaaudio import (PCM, pcms, PCM_PLAYBACK, PCM_CAPTURE, PCM_NONBLOCK, Mixer,
|
||||||
|
PCM_STATE_OPEN, PCM_STATE_SETUP, PCM_STATE_PREPARED, PCM_STATE_RUNNING, PCM_STATE_XRUN, PCM_STATE_DRAINING,
|
||||||
|
PCM_STATE_PAUSED, PCM_STATE_SUSPENDED, ALSAAudioError)
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
poll_names = {
|
||||||
|
select.POLLIN: 'POLLIN',
|
||||||
|
select.POLLPRI: 'POLLPRI',
|
||||||
|
select.POLLOUT: 'POLLOUT',
|
||||||
|
select.POLLERR: 'POLLERR',
|
||||||
|
select.POLLHUP: 'POLLHUP',
|
||||||
|
select.POLLRDHUP: 'POLLRDHUP',
|
||||||
|
select.POLLNVAL: 'POLLNVAL'
|
||||||
|
}
|
||||||
|
|
||||||
|
state_names = {
|
||||||
|
PCM_STATE_OPEN: 'PCM_STATE_OPEN',
|
||||||
|
PCM_STATE_SETUP: 'PCM_STATE_SETUP',
|
||||||
|
PCM_STATE_PREPARED: 'PCM_STATE_PREPARED',
|
||||||
|
PCM_STATE_RUNNING: 'PCM_STATE_RUNNING',
|
||||||
|
PCM_STATE_XRUN: 'PCM_STATE_XRUN',
|
||||||
|
PCM_STATE_DRAINING: 'PCM_STATE_DRAINING',
|
||||||
|
PCM_STATE_PAUSED: 'PCM_STATE_PAUSED',
|
||||||
|
PCM_STATE_SUSPENDED: 'PCM_STATE_SUSPENDED'
|
||||||
|
}
|
||||||
|
|
||||||
|
def poll_desc(mask):
|
||||||
|
return '|'.join([poll_names[bit] for bit, name in poll_names.items() if mask & bit])
|
||||||
|
|
||||||
|
class PollDescriptor(object):
|
||||||
|
'''File Descriptor, event mask and a name for logging'''
|
||||||
|
def __init__(self, name, fd, mask):
|
||||||
|
self.name = name
|
||||||
|
self.fd = fd
|
||||||
|
self.mask = mask
|
||||||
|
|
||||||
|
def as_tuple(self):
|
||||||
|
return (self.fd, self.mask)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_alsa_object(cls, name, alsaobject, mask=None):
|
||||||
|
# TODO maybe refactor: we ignore objects that have more then one polldescriptor
|
||||||
|
fd, alsamask = alsaobject.polldescriptors()[0]
|
||||||
|
|
||||||
|
if mask is None:
|
||||||
|
mask = alsamask
|
||||||
|
|
||||||
|
return cls(name, fd, mask)
|
||||||
|
|
||||||
|
class Loopback(object):
|
||||||
|
'''Loopback state and event handling'''
|
||||||
|
|
||||||
|
def __init__(self, capture, playback_args, volume_handler, run_after_stop=None, run_before_start=None):
|
||||||
|
self.playback_args = playback_args
|
||||||
|
self.playback = None
|
||||||
|
self.volume_handler = volume_handler
|
||||||
|
self.capture_started = None
|
||||||
|
self.last_capture_event = None
|
||||||
|
|
||||||
|
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_did_run = False
|
||||||
|
|
||||||
|
self.waitBeforeOpen = False
|
||||||
|
self.queue = []
|
||||||
|
|
||||||
|
self.period_size = 0
|
||||||
|
self.silent_periods = 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compute_energy(data):
|
||||||
|
values = struct.unpack(f'{len(data)//2}h', data)
|
||||||
|
e = 0
|
||||||
|
for v in values:
|
||||||
|
e = e + v * v
|
||||||
|
|
||||||
|
return e
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_command(cmd):
|
||||||
|
if cmd:
|
||||||
|
rc = subprocess.run(cmd)
|
||||||
|
if rc.returncode:
|
||||||
|
logging.warning(f'run {cmd}, return code {rc.returncode}')
|
||||||
|
else:
|
||||||
|
logging.info(f'run {cmd}, return code {rc.returncode}')
|
||||||
|
|
||||||
|
def register(self, reactor):
|
||||||
|
reactor.register_timeout_handler(self.timeout_handler)
|
||||||
|
reactor.register(self.capture_pd, self)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
# start reading data
|
||||||
|
size, data = self.capture.read()
|
||||||
|
if size:
|
||||||
|
self.queue.append(data)
|
||||||
|
|
||||||
|
def timeout_handler(self):
|
||||||
|
if self.playback and self.capture_started:
|
||||||
|
if self.last_capture_event:
|
||||||
|
if datetime.now() - self.last_capture_event > timedelta(seconds=2):
|
||||||
|
logging.info('timeout - closing playback device')
|
||||||
|
self.playback.close()
|
||||||
|
self.playback = None
|
||||||
|
self.capture_started = None
|
||||||
|
if self.volume_handler:
|
||||||
|
self.volume_handler.stop()
|
||||||
|
self.run_command(self.run_after_stop)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.waitBeforeOpen = False
|
||||||
|
|
||||||
|
if not self.run_after_stop_did_run and not self.playback:
|
||||||
|
if self.volume_handler:
|
||||||
|
self.volume_handler.stop()
|
||||||
|
self.run_command(self.run_after_stop)
|
||||||
|
self.run_after_stop_did_run = True
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
if len(self.queue):
|
||||||
|
return self.queue.pop()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def handle_capture_event(self, eventmask, name):
|
||||||
|
'''called when data is available for reading'''
|
||||||
|
self.last_capture_event = datetime.now()
|
||||||
|
size, data = self.capture.read()
|
||||||
|
if not size:
|
||||||
|
logging.warning(f'capture event but no data')
|
||||||
|
return False
|
||||||
|
|
||||||
|
energy = self.compute_energy(data)
|
||||||
|
logging.debug(f'energy: {energy}')
|
||||||
|
|
||||||
|
# the usecase is a USB capture device where we get perfect silence when it's idle
|
||||||
|
if energy == 0:
|
||||||
|
self.silent_periods = self.silent_periods + 1
|
||||||
|
|
||||||
|
# turn off playback after two seconds of silence
|
||||||
|
# 2 channels * 2 seconds * 2 bytes per sample
|
||||||
|
fps = self.playback_args['rate'] * 8 // (self.playback_args['periodsize'] * self.playback_args['periods'])
|
||||||
|
|
||||||
|
logging.debug(f'{self.silent_periods} of {fps} silent periods: {self.playback}')
|
||||||
|
|
||||||
|
if self.silent_periods > fps and self.playback:
|
||||||
|
logging.info(f'closing playback due to silence')
|
||||||
|
self.playback.close()
|
||||||
|
self.playback = None
|
||||||
|
if self.volume_handler:
|
||||||
|
self.volume_handler.stop()
|
||||||
|
self.run_command(self.run_after_stop)
|
||||||
|
self.run_after_stop_did_run = True
|
||||||
|
|
||||||
|
if not self.playback:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.silent_periods = 0
|
||||||
|
|
||||||
|
if not self.playback:
|
||||||
|
if self.waitBeforeOpen:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
if self.volume_handler:
|
||||||
|
self.volume_handler.start()
|
||||||
|
self.run_command(self.run_before_start)
|
||||||
|
self.playback = PCM(**self.playback_args)
|
||||||
|
self.period_size = self.playback.info()['period_size']
|
||||||
|
logging.info(f'opened playback device with period_size {self.period_size}')
|
||||||
|
except ALSAAudioError as e:
|
||||||
|
logging.info('opening PCM playback device failed: %s', e)
|
||||||
|
self.waitBeforeOpen = True
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.capture_started = datetime.now()
|
||||||
|
logging.info(f'{self.playback} capture started: {self.capture_started}')
|
||||||
|
|
||||||
|
self.queue.append(data)
|
||||||
|
|
||||||
|
if len(self.queue) <= 2:
|
||||||
|
logging.info(f'buffering: {len(self.queue)}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = self.pop()
|
||||||
|
if data:
|
||||||
|
space = self.playback.avail()
|
||||||
|
written = self.playback.write(data)
|
||||||
|
logging.debug(f'wrote {written} bytes while space was {space}')
|
||||||
|
except ALSAAudioError:
|
||||||
|
logging.error('underrun', exc_info=1)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __call__(self, fd, eventmask, name):
|
||||||
|
|
||||||
|
if fd == self.capture_pd.fd:
|
||||||
|
real_mask = self.capture.polldescriptors_revents([self.capture_pd.as_tuple()])
|
||||||
|
if real_mask:
|
||||||
|
return self.handle_capture_event(real_mask, name)
|
||||||
|
else:
|
||||||
|
logging.debug('null capture event')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
real_mask = self.playback.polldescriptors_revents([self.playback_pd.as_tuple()])
|
||||||
|
if real_mask:
|
||||||
|
return self.handle_playback_event(real_mask, name)
|
||||||
|
else:
|
||||||
|
logging.debug('null playback event')
|
||||||
|
return False
|
||||||
|
|
||||||
|
class VolumeForwarder(object):
|
||||||
|
'''Volume control event handling'''
|
||||||
|
|
||||||
|
def __init__(self, capture_control, playback_control):
|
||||||
|
self.playback_control = playback_control
|
||||||
|
self.capture_control = capture_control
|
||||||
|
self.active = True
|
||||||
|
self.volume = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.active = True
|
||||||
|
if self.volume:
|
||||||
|
self.volume = playback_control.setvolume(self.volume)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.active = False
|
||||||
|
self.volume = self.playback_control.getvolume(pcmtype=PCM_CAPTURE)[0]
|
||||||
|
|
||||||
|
def __call__(self, fd, eventmask, name):
|
||||||
|
if not self.active:
|
||||||
|
return
|
||||||
|
|
||||||
|
volume = self.capture_control.getvolume(pcmtype=PCM_CAPTURE)
|
||||||
|
# indicate that we've handled the event
|
||||||
|
self.capture_control.handleevents()
|
||||||
|
logging.info(f'{name} adjusting volume to {volume}')
|
||||||
|
if volume:
|
||||||
|
self.playback_control.setvolume(volume[0])
|
||||||
|
|
||||||
|
|
||||||
|
class Reactor(object):
|
||||||
|
'''A wrapper around select.poll'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.poll = select.poll()
|
||||||
|
self.descriptors = {}
|
||||||
|
self.timeout_handlers = set()
|
||||||
|
|
||||||
|
def register(self, polldescriptor, callable):
|
||||||
|
logging.debug(f'registered {polldescriptor.name}: {poll_desc(polldescriptor.mask)}')
|
||||||
|
self.descriptors[polldescriptor.fd] = (polldescriptor, callable)
|
||||||
|
self.poll.register(polldescriptor.fd, polldescriptor.mask)
|
||||||
|
|
||||||
|
def unregister(self, polldescriptor):
|
||||||
|
self.poll.unregister(polldescriptor.fd)
|
||||||
|
del self.descriptors[polldescriptor.fd]
|
||||||
|
|
||||||
|
def register_timeout_handler(self, callable):
|
||||||
|
self.timeout_handlers.add(callable)
|
||||||
|
|
||||||
|
def unregister_timeout_handler(self, callable):
|
||||||
|
self.timeout_handlers.remove(callable)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
last_timeout_ev = datetime.now()
|
||||||
|
while True:
|
||||||
|
# poll for a bit, then send a timeout to registered handlers
|
||||||
|
events = self.poll.poll(0.25)
|
||||||
|
for fd, ev in events:
|
||||||
|
polldescriptor, handler = self.descriptors[fd]
|
||||||
|
|
||||||
|
# very chatty - log all events
|
||||||
|
# logging.debug(f'{polldescriptor.name}: {poll_desc(ev)} ({ev})')
|
||||||
|
|
||||||
|
handler(fd, ev, polldescriptor.name)
|
||||||
|
|
||||||
|
if datetime.now() - last_timeout_ev > timedelta(seconds=0.25):
|
||||||
|
for t in self.timeout_handlers:
|
||||||
|
t()
|
||||||
|
last_timeout_ev = datetime.now()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
|
||||||
|
|
||||||
|
parser = ArgumentParser(description='ALSA loopback (with volume forwarding)')
|
||||||
|
|
||||||
|
playback_pcms = pcms(pcmtype=PCM_PLAYBACK)
|
||||||
|
capture_pcms = pcms(pcmtype=PCM_CAPTURE)
|
||||||
|
|
||||||
|
if not playback_pcms:
|
||||||
|
logging.error('no playback PCM found')
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if not capture_pcms:
|
||||||
|
logging.error('no capture PCM found')
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
parser.add_argument('-d', '--debug', action='store_true')
|
||||||
|
parser.add_argument('-i', '--input', default=capture_pcms[0])
|
||||||
|
parser.add_argument('-o', '--output', default=playback_pcms[0])
|
||||||
|
parser.add_argument('-r', '--rate', type=int, default=44100)
|
||||||
|
parser.add_argument('-c', '--channels', type=int, default=2)
|
||||||
|
parser.add_argument('-p', '--periodsize', type=int, default=444) # must be divisible by 6 for 44k1
|
||||||
|
parser.add_argument('-P', '--periods', type=int, default=2)
|
||||||
|
parser.add_argument('-I', '--input-mixer', help='Control of the input mixer, can contain the card index, e.g. Digital:2')
|
||||||
|
parser.add_argument('-O', '--output-mixer', help='Control of the output mixer, can contain the card index, e.g. PCM:1')
|
||||||
|
parser.add_argument('-A', '--run-after-stop', help='command to run when the capture device is idle/silent')
|
||||||
|
parser.add_argument('-B', '--run-before-start', help='command to run when the capture device becomes active')
|
||||||
|
parser.add_argument('-V', '--volume', help='Initial volume (default is leave unchanged)')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
playback_args = {
|
||||||
|
'type': PCM_PLAYBACK,
|
||||||
|
'mode': PCM_NONBLOCK,
|
||||||
|
'device': args.output,
|
||||||
|
'rate': args.rate,
|
||||||
|
'channels': args.channels,
|
||||||
|
'periodsize': args.periodsize,
|
||||||
|
'periods': args.periods
|
||||||
|
}
|
||||||
|
|
||||||
|
reactor = Reactor()
|
||||||
|
|
||||||
|
# If args.input_mixer and args.output_mixer are set, forward the capture volume to the playback volume.
|
||||||
|
# The usecase is a capture device that is implemented using g_audio, i.e. the Linux USB gadget driver.
|
||||||
|
# When a USB device (eg. an iPad) is connected to this machine, its volume events will go to the volume control
|
||||||
|
# of the output device
|
||||||
|
|
||||||
|
capture = None
|
||||||
|
playback = None
|
||||||
|
volume_handler = None
|
||||||
|
if args.input_mixer and args.output_mixer:
|
||||||
|
re_mixer = re.compile(r'([a-zA-Z0-9]+):?([0-9+])?')
|
||||||
|
|
||||||
|
input_mixer_card = None
|
||||||
|
m = re_mixer.match(args.input_mixer)
|
||||||
|
if m:
|
||||||
|
input_mixer = m.group(1)
|
||||||
|
if m.group(2):
|
||||||
|
input_mixer_card = int(m.group(2))
|
||||||
|
else:
|
||||||
|
parser.print_usage()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
output_mixer_card = None
|
||||||
|
m = re_mixer.match(args.output_mixer)
|
||||||
|
if m:
|
||||||
|
output_mixer = m.group(1)
|
||||||
|
if m.group(2):
|
||||||
|
output_mixer_card = int(m.group(2))
|
||||||
|
else:
|
||||||
|
parser.print_usage()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if input_mixer_card is None:
|
||||||
|
capture = PCM(type=PCM_CAPTURE, mode=PCM_NONBLOCK, device=args.input, rate=args.rate,
|
||||||
|
channels=args.channels, periodsize=args.periodsize, periods=args.periods)
|
||||||
|
input_mixer_card = capture.info()['card_no']
|
||||||
|
|
||||||
|
if output_mixer_card is None:
|
||||||
|
playback = PCM(**playback_args)
|
||||||
|
output_mixer_card = playback.info()['card_no']
|
||||||
|
playback.close()
|
||||||
|
|
||||||
|
playback_control = Mixer(control=output_mixer, cardindex=int(output_mixer_card))
|
||||||
|
capture_control = Mixer(control=input_mixer, cardindex=int(input_mixer_card))
|
||||||
|
|
||||||
|
volume_handler = VolumeForwarder(capture_control, playback_control)
|
||||||
|
reactor.register(PollDescriptor.from_alsa_object('capture_control', capture_control, select.POLLIN), volume_handler)
|
||||||
|
|
||||||
|
if args.volume and playback_control:
|
||||||
|
playback_control.setvolume(int(args.volume))
|
||||||
|
|
||||||
|
loopback = Loopback(capture, playback_args, volume_handler, args.run_after_stop, args.run_before_start)
|
||||||
|
loopback.register(reactor)
|
||||||
|
loopback.start()
|
||||||
|
|
||||||
|
reactor.run()
|
||||||
38
mixertest.py
38
mixertest.py
@@ -43,12 +43,42 @@ def show_mixer(name, kwargs):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("Mixer name: '%s'" % mixer.mixer())
|
print("Mixer name: '%s'" % mixer.mixer())
|
||||||
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
|
volcap = mixer.volumecap()
|
||||||
|
print("Capabilities: %s %s" % (' '.join(volcap),
|
||||||
' '.join(mixer.switchcap())))
|
' '.join(mixer.switchcap())))
|
||||||
|
|
||||||
|
if "Volume" in volcap or "Joined Volume" in volcap or "Playback Volume" in volcap:
|
||||||
|
pmin, pmax = mixer.getrange(alsaaudio.PCM_PLAYBACK)
|
||||||
|
pmin_keyword, pmax_keyword = mixer.getrange(pcmtype=alsaaudio.PCM_PLAYBACK, units=alsaaudio.VOLUME_UNITS_RAW)
|
||||||
|
pmin_default, pmax_default = mixer.getrange()
|
||||||
|
assert pmin == pmin_keyword
|
||||||
|
assert pmax == pmax_keyword
|
||||||
|
assert pmin == pmin_default
|
||||||
|
assert pmax == pmax_default
|
||||||
|
print("Raw playback volume range {}-{}".format(pmin, pmax))
|
||||||
|
pmin_dB, pmax_dB = mixer.getrange(units=alsaaudio.VOLUME_UNITS_DB)
|
||||||
|
print("dB playback volume range {}-{}".format(pmin_dB / 100.0, pmax_dB / 100.0))
|
||||||
|
|
||||||
|
if "Capture Volume" in volcap or "Joined Capture Volume" in volcap:
|
||||||
|
# Check that `getrange` works with keyword and positional arguments
|
||||||
|
cmin, cmax = mixer.getrange(alsaaudio.PCM_CAPTURE)
|
||||||
|
cmin_keyword, cmax_keyword = mixer.getrange(pcmtype=alsaaudio.PCM_CAPTURE, units=alsaaudio.VOLUME_UNITS_RAW)
|
||||||
|
assert cmin == cmin_keyword
|
||||||
|
assert cmax == cmax_keyword
|
||||||
|
print("Raw capture volume range {}-{}".format(cmin, cmax))
|
||||||
|
cmin_dB, cmax_dB = mixer.getrange(pcmtype=alsaaudio.PCM_CAPTURE, units=alsaaudio.VOLUME_UNITS_DB)
|
||||||
|
print("dB capture volume range {}-{}".format(cmin_dB / 100.0, cmax_dB / 100.0))
|
||||||
|
|
||||||
volumes = mixer.getvolume()
|
volumes = mixer.getvolume()
|
||||||
|
volumes_dB = mixer.getvolume(units=alsaaudio.VOLUME_UNITS_DB)
|
||||||
for i in range(len(volumes)):
|
for i in range(len(volumes)):
|
||||||
print("Channel %i volume: %i%%" % (i,volumes[i]))
|
print("Channel %i playback volume: %i%% (%.1f dB)" % (i, volumes[i], volumes_dB[i] / 100.0))
|
||||||
|
|
||||||
|
volumes = mixer.getvolume(pcmtype=alsaaudio.PCM_CAPTURE)
|
||||||
|
volumes_dB = mixer.getvolume(pcmtype=alsaaudio.PCM_CAPTURE, units=alsaaudio.VOLUME_UNITS_DB)
|
||||||
|
for i in range(len(volumes)):
|
||||||
|
print("Channel %i capture volume: %i%% (%.1f dB)" % (i, volumes[i], volumes_dB[i] / 100.0))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mutes = mixer.getmute()
|
mutes = mixer.getmute()
|
||||||
for i in range(len(mutes)):
|
for i in range(len(mutes)):
|
||||||
@@ -88,7 +118,7 @@ def set_mixer(name, args, kwargs):
|
|||||||
mixer.setmute(1, channel)
|
mixer.setmute(1, channel)
|
||||||
else:
|
else:
|
||||||
mixer.setmute(0, channel)
|
mixer.setmute(0, channel)
|
||||||
|
|
||||||
elif args in ['rec','unrec']:
|
elif args in ['rec','unrec']:
|
||||||
# Enable/disable recording
|
# Enable/disable recording
|
||||||
if args == 'rec':
|
if args == 'rec':
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
|
||||||
|
|
||||||
## playbacktest.py
|
## playbacktest.py
|
||||||
##
|
##
|
||||||
@@ -38,22 +39,16 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
f = open(args[0], 'rb')
|
f = open(args[0], 'rb')
|
||||||
|
|
||||||
# Open the device in playback mode.
|
# Open the device in playback mode in Mono, 44100 Hz, 16 bit little endian frames
|
||||||
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device=device)
|
|
||||||
|
|
||||||
# Set attributes: Mono, 44100 Hz, 16 bit little endian frames
|
|
||||||
out.setchannels(1)
|
|
||||||
out.setrate(44100)
|
|
||||||
out.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
|
||||||
|
|
||||||
# The period size controls the internal number of frames per period.
|
# The period size controls the internal number of frames per period.
|
||||||
# The significance of this parameter is documented in the ALSA api.
|
# The significance of this parameter is documented in the ALSA api.
|
||||||
out.setperiodsize(160)
|
|
||||||
|
|
||||||
|
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=160, device=device)
|
||||||
# Read data from stdin
|
# Read data from stdin
|
||||||
data = f.read(320)
|
data = f.read(320)
|
||||||
while data:
|
while data:
|
||||||
out.write(data)
|
if out.write(data) < 0:
|
||||||
|
print("Playback buffer underrun! Continuing nonetheless ...")
|
||||||
data = f.read(320)
|
data = f.read(320)
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
|||||||
85
playwav.py
85
playwav.py
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
|
||||||
|
|
||||||
# Simple test script that plays (some) wav files
|
# Simple test script that plays (some) wav files
|
||||||
|
|
||||||
@@ -9,57 +10,55 @@ import wave
|
|||||||
import getopt
|
import getopt
|
||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
|
||||||
def play(device, f):
|
def play(device, f):
|
||||||
|
|
||||||
print('%d channels, %d sampling rate\n' % (f.getnchannels(),
|
format = None
|
||||||
f.getframerate()))
|
|
||||||
# Set attributes
|
|
||||||
device.setchannels(f.getnchannels())
|
|
||||||
device.setrate(f.getframerate())
|
|
||||||
|
|
||||||
# 8bit is unsigned in wav files
|
# 8bit is unsigned in wav files
|
||||||
if f.getsampwidth() == 1:
|
if f.getsampwidth() == 1:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_U8)
|
format = alsaaudio.PCM_FORMAT_U8
|
||||||
# Otherwise we assume signed data, little endian
|
# Otherwise we assume signed data, little endian
|
||||||
elif f.getsampwidth() == 2:
|
elif f.getsampwidth() == 2:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
format = alsaaudio.PCM_FORMAT_S16_LE
|
||||||
elif f.getsampwidth() == 3:
|
elif f.getsampwidth() == 3:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_S24_LE)
|
format = alsaaudio.PCM_FORMAT_S24_3LE
|
||||||
elif f.getsampwidth() == 4:
|
elif f.getsampwidth() == 4:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
|
format = alsaaudio.PCM_FORMAT_S32_LE
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unsupported format')
|
raise ValueError('Unsupported format')
|
||||||
|
|
||||||
periodsize = f.getframerate() / 8
|
periodsize = f.getframerate() // 8
|
||||||
|
|
||||||
device.setperiodsize(periodsize)
|
print('%d channels, %d sampling rate, format %d, periodsize %d\n' % (f.getnchannels(),
|
||||||
|
f.getframerate(),
|
||||||
data = f.readframes(periodsize)
|
format,
|
||||||
while data:
|
periodsize))
|
||||||
# Read data from stdin
|
|
||||||
device.write(data)
|
device = alsaaudio.PCM(channels=f.getnchannels(), rate=f.getframerate(), format=format, periodsize=periodsize, device=device)
|
||||||
data = f.readframes(periodsize)
|
|
||||||
|
data = f.readframes(periodsize)
|
||||||
|
while data:
|
||||||
|
# Read data from stdin
|
||||||
|
if device.write(data) < 0:
|
||||||
|
print("Playback buffer underrun! Continuing nonetheless ...")
|
||||||
|
data = f.readframes(periodsize)
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print('usage: playwav.py [-d <device>] <file>', file=sys.stderr)
|
print('usage: playwav.py [-d <device>] <file>', file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
device = 'default'
|
device = 'default'
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == '-d':
|
if o == '-d':
|
||||||
device = a
|
device = a
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
usage()
|
usage()
|
||||||
|
|
||||||
f = wave.open(args[0], 'rb')
|
with wave.open(args[0], 'rb') as f:
|
||||||
device = alsaaudio.PCM(device=device)
|
play(device, f)
|
||||||
|
|
||||||
play(device, f)
|
|
||||||
|
|
||||||
f.close()
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
|
||||||
|
|
||||||
## recordtest.py
|
## recordtest.py
|
||||||
##
|
##
|
||||||
@@ -22,48 +23,44 @@ import getopt
|
|||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print('usage: recordtest.py [-d <device>] <file>', file=sys.stderr)
|
print('usage: recordtest.py [-d <device>] <file>', file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
device = 'default'
|
device = 'default'
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == '-d':
|
if o == '-d':
|
||||||
device = a
|
device = a
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
usage()
|
usage()
|
||||||
|
|
||||||
f = open(args[0], 'wb')
|
f = open(args[0], 'wb')
|
||||||
|
|
||||||
# Open the device in nonblocking capture mode. The last argument could
|
# Open the device in nonblocking capture mode in mono, with a sampling rate of 44100 Hz
|
||||||
# just as well have been zero for blocking mode. Then we could have
|
# and 16 bit little endian samples
|
||||||
# left out the sleep call in the bottom of the loop
|
# The period size controls the internal number of frames per period.
|
||||||
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, device=device)
|
# The significance of this parameter is documented in the ALSA api.
|
||||||
|
# For our purposes, it is suficcient to know that reads from the device
|
||||||
|
# will return this many frames. Each frame being 2 bytes long.
|
||||||
|
# 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,
|
||||||
|
periodsize=160, device=device)
|
||||||
|
|
||||||
# Set attributes: Mono, 44100 Hz, 16 bit little endian samples
|
loops = 1000000
|
||||||
inp.setchannels(1)
|
while loops > 0:
|
||||||
inp.setrate(44100)
|
loops -= 1
|
||||||
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
# Read data from device
|
||||||
|
l, data = inp.read()
|
||||||
|
|
||||||
# The period size controls the internal number of frames per period.
|
if l < 0:
|
||||||
# The significance of this parameter is documented in the ALSA api.
|
print("Capture buffer overrun! Continuing nonetheless ...")
|
||||||
# For our purposes, it is suficcient to know that reads from the device
|
elif l:
|
||||||
# will return this many frames. Each frame being 2 bytes long.
|
f.write(data)
|
||||||
# This means that the reads below will return either 320 bytes of data
|
time.sleep(.001)
|
||||||
# or 0 bytes of data. The latter is possible because we are in nonblocking
|
|
||||||
# mode.
|
|
||||||
inp.setperiodsize(160)
|
|
||||||
|
|
||||||
loops = 1000000
|
|
||||||
while loops > 0:
|
|
||||||
loops -= 1
|
|
||||||
# Read data from device
|
|
||||||
l, data = inp.read()
|
|
||||||
|
|
||||||
if l:
|
|
||||||
f.write(data)
|
|
||||||
time.sleep(.001)
|
|
||||||
|
|||||||
6
setup.py
6
setup.py
@@ -8,7 +8,7 @@ from setuptools import setup
|
|||||||
from setuptools.extension import Extension
|
from setuptools.extension import Extension
|
||||||
from sys import version
|
from sys import version
|
||||||
|
|
||||||
pyalsa_version = '0.8.4'
|
pyalsa_version = '0.11.0'
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
setup(
|
setup(
|
||||||
@@ -29,12 +29,12 @@ if __name__ == '__main__':
|
|||||||
'License :: OSI Approved :: Python Software Foundation License',
|
'License :: OSI Approved :: Python Software Foundation License',
|
||||||
'Operating System :: POSIX :: Linux',
|
'Operating System :: POSIX :: Linux',
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 2',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Topic :: Multimedia :: Sound/Audio',
|
'Topic :: Multimedia :: Sound/Audio',
|
||||||
'Topic :: Multimedia :: Sound/Audio :: Mixers',
|
'Topic :: Multimedia :: Sound/Audio :: Mixers',
|
||||||
'Topic :: Multimedia :: Sound/Audio :: Players',
|
'Topic :: Multimedia :: Sound/Audio :: Players',
|
||||||
'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
|
'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
|
||||||
],
|
],
|
||||||
ext_modules=[Extension('alsaaudio',['alsaaudio.c'],
|
ext_modules=[Extension('alsaaudio',['alsaaudio.c'],
|
||||||
libraries=['asound'])]
|
libraries=['asound'])]
|
||||||
)
|
)
|
||||||
|
|||||||
239
test.py
239
test.py
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python3
|
||||||
|
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
|
||||||
|
|
||||||
# These are internal tests. They shouldn't fail, but they don't cover all
|
# These are internal tests. They shouldn't fail, but they don't cover all
|
||||||
# of the ALSA API. Most importantly PCM.read and PCM.write are missing.
|
# of the ALSA API. Most importantly PCM.read and PCM.write are missing.
|
||||||
@@ -10,127 +11,177 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import alsaaudio
|
import alsaaudio
|
||||||
import warnings
|
import warnings
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
# we can't test read and write well - these are tested otherwise
|
# we can't test read and write well - these are tested otherwise
|
||||||
PCMMethods = [('pcmtype', None),
|
PCMMethods = [
|
||||||
('pcmmode', None),
|
('pcmtype', None),
|
||||||
('cardname', None),
|
('pcmmode', None),
|
||||||
('setchannels', (2,)),
|
('cardname', None)
|
||||||
('setrate', (44100,)),
|
]
|
||||||
('setformat', (alsaaudio.PCM_FORMAT_S8,)),
|
|
||||||
('setperiodsize', (320,))]
|
PCMDeprecatedMethods = [
|
||||||
|
('setchannels', (2,)),
|
||||||
|
('setrate', (44100,)),
|
||||||
|
('setformat', (alsaaudio.PCM_FORMAT_S8,)),
|
||||||
|
('setperiodsize', (320,))
|
||||||
|
]
|
||||||
|
|
||||||
# A clever test would look at the Mixer capabilities and selectively run the
|
# A clever test would look at the Mixer capabilities and selectively run the
|
||||||
# omitted tests, but I am too tired for that.
|
# omitted tests, but I am too tired for that.
|
||||||
|
|
||||||
MixerMethods = [('cardname', None),
|
MixerMethods = [('cardname', None),
|
||||||
('mixer', None),
|
('mixer', None),
|
||||||
('mixerid', None),
|
('mixerid', None),
|
||||||
('switchcap', None),
|
('switchcap', None),
|
||||||
('volumecap', None),
|
('volumecap', None),
|
||||||
('getvolume', None),
|
('getvolume', None),
|
||||||
('getrange', None),
|
('getrange', None),
|
||||||
('getenum', None),
|
('getenum', None),
|
||||||
# ('getmute', None),
|
# ('getmute', None),
|
||||||
# ('getrec', None),
|
# ('getrec', None),
|
||||||
# ('setvolume', (60,)),
|
# ('setvolume', (60,)),
|
||||||
# ('setmute', (0,))
|
# ('setmute', (0,))
|
||||||
# ('setrec', (0')),
|
# ('setrec', (0')),
|
||||||
]
|
]
|
||||||
|
|
||||||
class MixerTest(unittest.TestCase):
|
class MixerTest(unittest.TestCase):
|
||||||
"""Test Mixer objects"""
|
"""Test Mixer objects"""
|
||||||
|
|
||||||
def testMixer(self):
|
def testMixer(self):
|
||||||
"""Open the default Mixers and the Mixers on every card"""
|
"""Open the default Mixers and the Mixers on every card"""
|
||||||
|
|
||||||
for c in alsaaudio.card_indexes():
|
|
||||||
mixers = alsaaudio.mixers(cardindex=c)
|
|
||||||
|
|
||||||
for m in mixers:
|
|
||||||
mixer = alsaaudio.Mixer(m, cardindex=c)
|
|
||||||
mixer.close()
|
|
||||||
|
|
||||||
def testMixerAll(self):
|
for c in alsaaudio.card_indexes():
|
||||||
"Run common Mixer methods on an open object"
|
mixers = alsaaudio.mixers(cardindex=c)
|
||||||
|
|
||||||
mixers = alsaaudio.mixers()
|
for m in mixers:
|
||||||
mixer = alsaaudio.Mixer(mixers[0])
|
mixer = alsaaudio.Mixer(m, cardindex=c)
|
||||||
|
mixer.close()
|
||||||
|
|
||||||
for m, a in MixerMethods:
|
def testMixerAll(self):
|
||||||
f = alsaaudio.Mixer.__dict__[m]
|
"Run common Mixer methods on an open object"
|
||||||
if a is None:
|
|
||||||
f(mixer)
|
|
||||||
else:
|
|
||||||
f(mixer, *a)
|
|
||||||
|
|
||||||
mixer.close()
|
mixers = alsaaudio.mixers()
|
||||||
|
mixer = alsaaudio.Mixer(mixers[0])
|
||||||
|
|
||||||
def testMixerClose(self):
|
for m, a in MixerMethods:
|
||||||
"""Run common Mixer methods on a closed object and verify it raises an
|
f = alsaaudio.Mixer.__dict__[m]
|
||||||
error"""
|
if a is None:
|
||||||
|
f(mixer)
|
||||||
|
else:
|
||||||
|
f(mixer, *a)
|
||||||
|
|
||||||
mixers = alsaaudio.mixers()
|
mixer.close()
|
||||||
mixer = alsaaudio.Mixer(mixers[0])
|
|
||||||
mixer.close()
|
|
||||||
|
|
||||||
for m, a in MixerMethods:
|
def testMixerClose(self):
|
||||||
f = alsaaudio.Mixer.__dict__[m]
|
"""Run common Mixer methods on a closed object and verify it raises an
|
||||||
if a is None:
|
error"""
|
||||||
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer)
|
|
||||||
else:
|
mixers = alsaaudio.mixers()
|
||||||
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer, *a)
|
mixer = alsaaudio.Mixer(mixers[0])
|
||||||
|
mixer.close()
|
||||||
|
|
||||||
|
for m, a in MixerMethods:
|
||||||
|
f = alsaaudio.Mixer.__dict__[m]
|
||||||
|
if a is None:
|
||||||
|
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer)
|
||||||
|
else:
|
||||||
|
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer, *a)
|
||||||
|
|
||||||
class PCMTest(unittest.TestCase):
|
class PCMTest(unittest.TestCase):
|
||||||
"""Test PCM objects"""
|
"""Test PCM objects"""
|
||||||
|
|
||||||
def testPCM(self):
|
def testPCM(self):
|
||||||
"Open a PCM object on every card"
|
"Open a PCM object on every card"
|
||||||
|
|
||||||
for c in alsaaudio.card_indexes():
|
for c in alsaaudio.card_indexes():
|
||||||
pcm = alsaaudio.PCM(cardindex=c)
|
pcm = alsaaudio.PCM(cardindex=c)
|
||||||
pcm.close()
|
pcm.close()
|
||||||
|
|
||||||
def testPCMAll(self):
|
def testPCMAll(self):
|
||||||
"Run all PCM methods on an open object"
|
"Run all PCM methods on an open object"
|
||||||
|
|
||||||
pcm = alsaaudio.PCM()
|
pcm = alsaaudio.PCM()
|
||||||
|
|
||||||
for m, a in PCMMethods:
|
for m, a in PCMMethods:
|
||||||
f = alsaaudio.PCM.__dict__[m]
|
f = alsaaudio.PCM.__dict__[m]
|
||||||
if a is None:
|
if a is None:
|
||||||
f(pcm)
|
f(pcm)
|
||||||
else:
|
else:
|
||||||
f(pcm, *a)
|
f(pcm, *a)
|
||||||
|
|
||||||
pcm.close()
|
pcm.close()
|
||||||
|
|
||||||
def testPCMClose(self):
|
def testPCMClose(self):
|
||||||
"Run all PCM methods on a closed object and verify it raises an error"
|
"Run all PCM methods on a closed object and verify it raises an error"
|
||||||
|
|
||||||
pcm = alsaaudio.PCM()
|
pcm = alsaaudio.PCM()
|
||||||
pcm.close()
|
pcm.close()
|
||||||
|
|
||||||
for m, a in PCMMethods:
|
for m, a in PCMMethods:
|
||||||
f = alsaaudio.PCM.__dict__[m]
|
f = alsaaudio.PCM.__dict__[m]
|
||||||
if a is None:
|
if a is None:
|
||||||
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm)
|
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm)
|
||||||
else:
|
else:
|
||||||
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm, *a)
|
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm, *a)
|
||||||
|
|
||||||
|
def testPCMDeprecated(self):
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pcm = alsaaudio.PCM(card='default')
|
||||||
|
except alsaaudio.ALSAAudioError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Verify we got a DepreciationWarning
|
||||||
|
self.assertEqual(len(w), 1, "PCM(card='default') expected a warning" )
|
||||||
|
self.assertTrue(issubclass(w[-1].category, DeprecationWarning), "PCM(card='default') expected a DeprecationWarning")
|
||||||
|
|
||||||
|
for m, a in PCMDeprecatedMethods:
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
|
||||||
|
pcm = alsaaudio.PCM()
|
||||||
|
|
||||||
|
f = alsaaudio.PCM.__dict__[m]
|
||||||
|
if a is None:
|
||||||
|
f(pcm)
|
||||||
|
else:
|
||||||
|
f(pcm, *a)
|
||||||
|
|
||||||
|
# Verify we got a DepreciationWarning
|
||||||
|
method = "%s%s" % (m, str(a))
|
||||||
|
self.assertEqual(len(w), 1, method + " expected a warning")
|
||||||
|
self.assertTrue(issubclass(w[-1].category, DeprecationWarning), method + " expected a DeprecationWarning")
|
||||||
|
|
||||||
|
class PollDescriptorArgsTest(unittest.TestCase):
|
||||||
|
'''Test invalid args for polldescriptors_revents (takes a list of tuples of 2 integers)'''
|
||||||
|
def testArgsNoList(self):
|
||||||
|
with closing(alsaaudio.PCM()) as pcm:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
pcm.polldescriptors_revents('foo')
|
||||||
|
|
||||||
|
def testArgsListButNoTuples(self):
|
||||||
|
with closing(alsaaudio.PCM()) as pcm:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
pcm.polldescriptors_revents(['foo', 1])
|
||||||
|
|
||||||
|
def testArgsListButInvalidTuples(self):
|
||||||
|
with closing(alsaaudio.PCM()) as pcm:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
pcm.polldescriptors_revents([('foo', 'bar')])
|
||||||
|
|
||||||
|
def testArgsListTupleWrongLength(self):
|
||||||
|
with closing(alsaaudio.PCM()) as pcm:
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
pcm.polldescriptors_revents([(1, )])
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
pcm.polldescriptors_revents([(1, 2, 3)])
|
||||||
|
|
||||||
def testPCMDeprecated(self):
|
|
||||||
with warnings.catch_warnings(record=True) as w:
|
|
||||||
# Cause all warnings to always be triggered.
|
|
||||||
warnings.simplefilter("always")
|
|
||||||
|
|
||||||
try:
|
|
||||||
pcm = alsaaudio.PCM(card='default')
|
|
||||||
except alsaaudio.ALSAAudioError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Verify we got a DepreciationWarning
|
|
||||||
assert len(w) == 1
|
|
||||||
assert issubclass(w[-1].category, DeprecationWarning)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user