mirror of
https://github.com/larsimmisch/pyalsaaudio.git
synced 2026-04-16 16:15:31 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917a11b398 | ||
|
|
ce84e69cc1 | ||
|
|
c2cfe0211b | ||
|
|
bfe4899721 | ||
|
|
40a1219dac | ||
|
|
54e2712b7a | ||
|
|
f9685e0b30 | ||
|
|
b4a670c50d | ||
|
|
370a4b6249 | ||
|
|
eca217dff9 | ||
|
|
65d3c4a283 | ||
|
|
adc0d800e1 | ||
|
|
02cf16d10d | ||
|
|
94ced0517e | ||
|
|
698e6044d3 | ||
|
|
2c95f4ff6b | ||
|
|
f19d139f64 | ||
|
|
dc51fa75b5 | ||
|
|
85ff47ad43 | ||
|
|
88f38284bb | ||
|
|
fe7561beea | ||
|
|
2314aaeb7e | ||
|
|
bf24ec65ca | ||
|
|
478d0559e6 | ||
|
|
891a30eb08 | ||
|
|
74d9e7d6e1 | ||
|
|
fa10bf6999 | ||
|
|
7de446c3c7 | ||
|
|
5cbc88607d | ||
|
|
0fb8b1d9f3 | ||
|
|
3cb51bdf90 | ||
|
|
6e96f8556c | ||
|
|
fddd239be1 | ||
|
|
8950de75bf |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,9 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.so
|
*.so
|
||||||
|
MANIFEST
|
||||||
doc/gh-pages/
|
doc/gh-pages/
|
||||||
doc/html/
|
doc/html/
|
||||||
doc/doctrees/
|
doc/doctrees/
|
||||||
gh-pages/
|
gh-pages/
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
35
CHANGES
35
CHANGES
@@ -1,9 +1,40 @@
|
|||||||
|
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:
|
Version 0.7:
|
||||||
- fixed several memory leaks (patch 3372909), contributed by Erik Kulyk)
|
- fixed several memory leaks (patch 3372909), contributed by Erik Kulyk)
|
||||||
|
|
||||||
|
|
||||||
Version 0.6:
|
Version 0.6:
|
||||||
- mostly reverted patch 2594366: alsapcm_setup did not do complete error
|
- mostly reverted patch 2594366: alsapcm_setup did not do complete error
|
||||||
checking for good reasons; some ALSA functions in alsapcm_setup may fail without
|
checking for good reasons; some ALSA functions in alsapcm_setup may fail without
|
||||||
rendering the device unusable
|
rendering the device unusable
|
||||||
|
|
||||||
@@ -15,7 +46,7 @@ Version 0.5:
|
|||||||
|
|
||||||
|
|
||||||
Version 0.4:
|
Version 0.4:
|
||||||
- API changes: mixers() and Mixer() now take a card index instead of a
|
- API changes: mixers() and Mixer() now take a card index instead of a
|
||||||
card name as optional parameter.
|
card name as optional parameter.
|
||||||
- Support for Python 3.0
|
- Support for Python 3.0
|
||||||
- Documentation converted to reStructuredText; use Sphinx instead of LaTeX.
|
- Documentation converted to reStructuredText; use Sphinx instead of LaTeX.
|
||||||
|
|||||||
11
NOTES.md
Normal file
11
NOTES.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Publishing the documentation
|
||||||
|
|
||||||
|
- Install Sphinx; `sudo pip install sphinx`
|
||||||
|
- Clone gh-pages branch: `cd doc; git clone -b gh-pages git@github.com:larsimmisch/pyalsaaudio.git gh-pages`
|
||||||
|
- `cd doc; make publish`
|
||||||
|
|
||||||
|
# Release procedure
|
||||||
|
|
||||||
|
- Update version number in setup.py
|
||||||
|
- Create tag and push it, i.e. `git tag x.y.z; git push origin x.y.z`
|
||||||
|
- `python setup.py sdist upload -r pypi`
|
||||||
@@ -5,12 +5,13 @@ 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 ALSA api from Python. It
|
This package contains wrappers for accessing the
|
||||||
|
[ALSA](http://www.alsa-project.org/) API from Python. It
|
||||||
is currently fairly complete for PCM devices, and has some support for mixers.
|
is currently fairly complete for PCM devices, and has some support for mixers.
|
||||||
|
|
||||||
If you find bugs in the wrappers please open an issue in the issue tracker.
|
If you find bugs in the wrappers please open an issue in the 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
|
bugs in the ALSA API, and those should be reported to the ALSA team - not
|
||||||
me.
|
me.
|
||||||
|
|
||||||
This software is licensed under the PSF license - the same one used
|
This software is licensed under the PSF license - the same one used
|
||||||
|
|||||||
388
alsaaudio.c
388
alsaaudio.c
@@ -20,7 +20,13 @@
|
|||||||
#define PyLong_Check PyInt_Check
|
#define PyLong_Check PyInt_Check
|
||||||
#define PyLong_AS_LONG PyInt_AS_LONG
|
#define PyLong_AS_LONG PyInt_AS_LONG
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if PY_MAJOR_VERSION < 3
|
||||||
|
#define PyLong_FromLong PyInt_FromLong
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
#include <alsa/version.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
PyDoc_STRVAR(alsaaudio_module_doc,
|
PyDoc_STRVAR(alsaaudio_module_doc,
|
||||||
@@ -71,6 +77,12 @@ typedef struct {
|
|||||||
snd_mixer_t *handle;
|
snd_mixer_t *handle;
|
||||||
} alsamixer_t;
|
} alsamixer_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
unit_percent,
|
||||||
|
unit_dB,
|
||||||
|
unit_last
|
||||||
|
} volume_unit_t;
|
||||||
|
|
||||||
/******************************************/
|
/******************************************/
|
||||||
/* PCM object wrapper */
|
/* PCM object wrapper */
|
||||||
/******************************************/
|
/******************************************/
|
||||||
@@ -170,6 +182,76 @@ PyDoc_STRVAR(cards_doc,
|
|||||||
\n\
|
\n\
|
||||||
List the available card ids.");
|
List the available card ids.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsacard_list_indexes(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
int card = -1;
|
||||||
|
PyObject *result = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args,":card_indexes"))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
result = PyList_New(0);
|
||||||
|
|
||||||
|
for (rc = snd_card_next(&card); !rc && (card >= 0);
|
||||||
|
rc = snd_card_next(&card))
|
||||||
|
{
|
||||||
|
PyObject *item = PyLong_FromLong(card);
|
||||||
|
|
||||||
|
PyList_Append(result, item);
|
||||||
|
Py_DECREF(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(card_indexes_doc,
|
||||||
|
"card_indexes()\n\
|
||||||
|
\n\
|
||||||
|
List the available card indexes.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsacard_name(PyObject *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int err, card;
|
||||||
|
PyObject *result = NULL;
|
||||||
|
char *name = NULL, *longname = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args,"i:card_name", &card))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
err = snd_card_get_name(card, &name);
|
||||||
|
if (err < 0) {
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%d]", snd_strerror(err), card);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = snd_card_get_longname(card, &longname);
|
||||||
|
if (err < 0) {
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%d]", snd_strerror(err), card);
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = PyTuple_New(2);
|
||||||
|
PyTuple_SetItem(result, 0, PyUnicode_FromString(name));
|
||||||
|
PyTuple_SetItem(result, 1, PyUnicode_FromString(longname));
|
||||||
|
|
||||||
|
exit:
|
||||||
|
free(name);
|
||||||
|
free(longname);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(card_name_doc,
|
||||||
|
"card_name(card_index) -> Tuple of (name, longname)\n\
|
||||||
|
\n\
|
||||||
|
Return the card name and long name for card 'card_index'.");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsapcm_list(PyObject *self, PyObject *args)
|
alsapcm_list(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
@@ -755,7 +837,11 @@ frames as bytes (or a string in Python 2.x). The length of the returned data\n\
|
|||||||
will be periodsize*framesize bytes.\n\
|
will be periodsize*framesize bytes.\n\
|
||||||
\n\
|
\n\
|
||||||
In PCM_NONBLOCK mode, the call will not block, but will return (0,'')\n\
|
In PCM_NONBLOCK mode, the call will not block, but will return (0,'')\n\
|
||||||
if no new period has become available since the last call to read.");
|
if no new period has become available since the last call to read.\n\
|
||||||
|
\n\
|
||||||
|
In case of an overrun, this function will return a negative size: -EPIPE.\n\
|
||||||
|
This indicates that data was lost, even if the operation itself succeeded.\n\
|
||||||
|
Try using a larger periodsize");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *alsapcm_write(alsapcm_t *self, PyObject *args)
|
static PyObject *alsapcm_write(alsapcm_t *self, PyObject *args)
|
||||||
@@ -1487,21 +1573,31 @@ Possible values in this list are:\n\
|
|||||||
- 'Capture Exclusive'\n");
|
- 'Capture Exclusive'\n");
|
||||||
|
|
||||||
|
|
||||||
static int alsamixer_getpercentage(long min, long max, long value)
|
static double alsamixer_getpercentage(long min, long max, long value)
|
||||||
{
|
{
|
||||||
/* Convert from number in range to percentage */
|
/* Convert from number in range to percentage */
|
||||||
int range = max - min;
|
int range = max - min;
|
||||||
int tmp;
|
|
||||||
|
|
||||||
if (range == 0)
|
if (range == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
value -= min;
|
value -= min;
|
||||||
tmp = rint((double)value/(double)range * 100);
|
return (double)value/(double)range * 100.0;
|
||||||
return tmp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static long alsamixer_getphysvolume(long min, long max, int percentage)
|
static double alsamixer_getdB(long min, long max, long value)
|
||||||
|
{
|
||||||
|
/* Convert from number in range to dB */
|
||||||
|
int range = max - min;
|
||||||
|
|
||||||
|
if (range == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
value -= min;
|
||||||
|
return log10((double)value/range) * 60.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long alsamixer_getphysvolume(long min, long max, double percentage)
|
||||||
{
|
{
|
||||||
/* Convert from percentage to number in range */
|
/* Convert from percentage to number in range */
|
||||||
int range = max - min;
|
int range = max - min;
|
||||||
@@ -1510,57 +1606,76 @@ static long alsamixer_getphysvolume(long min, long max, int percentage)
|
|||||||
if (range == 0)
|
if (range == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
tmp = rint((double)range * ((double)percentage*.01)) + min;
|
tmp = rint((double)range * (percentage * .01)) + min;
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsamixer_getvolume(alsamixer_t *self, PyObject *args)
|
alsamixer_getvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
|
||||||
{
|
{
|
||||||
snd_mixer_elem_t *elem;
|
snd_mixer_elem_t *elem;
|
||||||
int channel;
|
int channel;
|
||||||
long ival;
|
long ival;
|
||||||
PyObject *pcmtypeobj = NULL;
|
PyObject *dirobj = NULL;
|
||||||
long pcmtype;
|
long dir;
|
||||||
|
int unit = unit_percent;
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
PyObject *item;
|
PyObject *item;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args,"|O:getvolume", &pcmtypeobj))
|
static char *kwlist[] = { "direction", "unit", NULL };
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "|Oi:getvolume", kwlist, &dirobj, &unit))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
if (unit >= unit_last) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "unit must be 'percent' or 'dB'");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir = get_pcmtype(dirobj);
|
||||||
|
if (dir < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (!self->handle)
|
if (!self->handle)
|
||||||
{
|
{
|
||||||
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcmtype = get_pcmtype(pcmtypeobj);
|
|
||||||
if (pcmtype < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
||||||
|
|
||||||
result = PyList_New(0);
|
result = PyList_New(0);
|
||||||
|
|
||||||
for (channel = 0; channel <= SND_MIXER_SCHN_LAST; channel++) {
|
for (channel = 0; channel <= SND_MIXER_SCHN_LAST; channel++) {
|
||||||
if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
|
if (dir == SND_PCM_STREAM_PLAYBACK &&
|
||||||
snd_mixer_selem_has_playback_channel(elem, channel))
|
snd_mixer_selem_has_playback_channel(elem, channel))
|
||||||
{
|
{
|
||||||
snd_mixer_selem_get_playback_volume(elem, channel, &ival);
|
snd_mixer_selem_get_playback_volume(elem, channel, &ival);
|
||||||
item = PyLong_FromLong(alsamixer_getpercentage(self->pmin,
|
if (unit == unit_percent) {
|
||||||
self->pmax,
|
item = PyFloat_FromDouble(
|
||||||
ival));
|
alsamixer_getpercentage(self->pmin, self->pmax, ival));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item = PyFloat_FromDouble(
|
||||||
|
alsamixer_getdB(self->pmin, self->pmax, ival));
|
||||||
|
}
|
||||||
PyList_Append(result, item);
|
PyList_Append(result, item);
|
||||||
Py_DECREF(item);
|
Py_DECREF(item);
|
||||||
}
|
}
|
||||||
else if (pcmtype == SND_PCM_STREAM_CAPTURE
|
else if (dir == SND_PCM_STREAM_CAPTURE
|
||||||
&& snd_mixer_selem_has_capture_channel(elem, channel)
|
&& snd_mixer_selem_has_capture_channel(elem, channel)
|
||||||
&& snd_mixer_selem_has_capture_volume(elem)) {
|
&& snd_mixer_selem_has_capture_volume(elem))
|
||||||
|
{
|
||||||
snd_mixer_selem_get_capture_volume(elem, channel, &ival);
|
snd_mixer_selem_get_capture_volume(elem, channel, &ival);
|
||||||
item = PyLong_FromLong(alsamixer_getpercentage(self->cmin,
|
if (unit == unit_percent) {
|
||||||
self->cmax,
|
item = PyFloat_FromDouble(
|
||||||
ival));
|
alsamixer_getpercentage(self->cmin, self->cmax, ival));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item = PyFloat_FromDouble(
|
||||||
|
alsamixer_getdB(self->cmin, self->cmax, ival));
|
||||||
|
}
|
||||||
PyList_Append(result, item);
|
PyList_Append(result, item);
|
||||||
Py_DECREF(item);
|
Py_DECREF(item);
|
||||||
}
|
}
|
||||||
@@ -1570,15 +1685,17 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(getvolume_doc,
|
PyDoc_STRVAR(getvolume_doc,
|
||||||
"getvolume([pcmtype]) -> List of volume settings (int)\n\
|
"getvolume(direction=PCM_PLAYBACK, unit=Percent) -> List of volume settings (float)\n\
|
||||||
\n\
|
\n\
|
||||||
Returns a list with the current volume settings for each channel.\n\
|
Returns a list with the current volume settings for each channel.\n\
|
||||||
The list elements are integer percentages.\n\
|
The list elements are float percentages.\n\
|
||||||
\n\
|
\n\
|
||||||
The optional 'pcmtype' argument can be either PCM_PLAYBACK or\n\
|
The 'direction' argument can be either PCM_PLAYBACK or\n\
|
||||||
PCM_CAPTURE, which is relevant if the mixer can control both\n\
|
PCM_CAPTURE, which is relevant if the mixer can control both\n\
|
||||||
playback and capture volume. The default value is PCM_PLAYBACK\n\
|
playback and capture volume. The default value is PCM_PLAYBACK\n\
|
||||||
if the mixer has this capability, otherwise PCM_CAPTURE");
|
if the mixer has this capability, otherwise PCM_CAPTURE\
|
||||||
|
\n\
|
||||||
|
The optional 'unit' argument can be either 'percent' or 'dB'.");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
@@ -1744,6 +1861,60 @@ PyDoc_STRVAR(getenum_doc,
|
|||||||
Returns a a tuple. The first element is name of the active enumerated item, \n\
|
Returns a a tuple. The first element is name of the active enumerated item, \n\
|
||||||
the second a list available enumerated items.");
|
the second a list available enumerated items.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsamixer_setenum(alsamixer_t *self, PyObject *args)
|
||||||
|
{
|
||||||
|
snd_mixer_elem_t *elem;
|
||||||
|
int index, count, rc;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "i:setenum", &index))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!self->handle)
|
||||||
|
{
|
||||||
|
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
||||||
|
if (!snd_mixer_selem_is_enumerated(elem)) {
|
||||||
|
PyErr_SetString(ALSAAudioError, "Not an enumerated control");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = snd_mixer_selem_get_enum_items(elem);
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(count),
|
||||||
|
self->cardname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0 || index >= count) {
|
||||||
|
PyErr_Format(ALSAAudioError, "Enum index out of range 0 <= %d < %d",
|
||||||
|
index, count);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = snd_mixer_selem_set_enum_item(elem, 0, index);
|
||||||
|
if (rc)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(rc),
|
||||||
|
self->cardname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(setenum_doc,
|
||||||
|
"setenum(index) -> None\n\
|
||||||
|
\n\
|
||||||
|
Sets the value of the enum, where 'index' is an index into the list of\n\
|
||||||
|
available enumerated items returned by getenum().");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsamixer_getmute(alsamixer_t *self, PyObject *args)
|
alsamixer_getmute(alsamixer_t *self, PyObject *args)
|
||||||
{
|
{
|
||||||
@@ -1850,29 +2021,53 @@ This method will fail if the mixer has no capture switch capabilities.");
|
|||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
|
||||||
{
|
{
|
||||||
snd_mixer_elem_t *elem;
|
snd_mixer_elem_t *elem;
|
||||||
int i;
|
int i;
|
||||||
long volume;
|
double volume = 0.0;
|
||||||
|
PyObject *volumeobj = NULL;
|
||||||
int physvolume;
|
int physvolume;
|
||||||
PyObject *pcmtypeobj = NULL;
|
PyObject *dirobj = NULL;
|
||||||
long pcmtype;
|
long dir;
|
||||||
|
int unit = unit_percent;
|
||||||
int channel = MIXER_CHANNEL_ALL;
|
int channel = MIXER_CHANNEL_ALL;
|
||||||
int done = 0;
|
int done = 0;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args,"l|iO:setvolume", &volume, &channel,
|
static char *kwlist[] = { "channel", "direction", "unit", NULL };
|
||||||
&pcmtypeobj))
|
|
||||||
return NULL;
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|iOi:setvolume", kwlist, &volumeobj, &channel,
|
||||||
if (volume < 0 || volume > 100)
|
&dirobj, &unit)) {
|
||||||
{
|
|
||||||
PyErr_SetString(ALSAAudioError, "Volume must be between 0 and 100");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcmtype = get_pcmtype(pcmtypeobj);
|
// unit
|
||||||
if (pcmtype < 0) {
|
if (unit >= unit_last) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "unit must be 'percent' or 'dB'");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyLong_Check(volumeobj)) {
|
||||||
|
volume = (double)PyLong_AsLong(volumeobj);
|
||||||
|
}
|
||||||
|
else if (PyFloat_Check(volumeobj)) {
|
||||||
|
volume = PyFloat_AsDouble(volumeobj);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Volume must be integer or float");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit == unit_percent && (volume < 0.0 || volume > 100.0))
|
||||||
|
{
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Volume in percent must be between 0 and 100");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pcmtype
|
||||||
|
dir = get_pcmtype(dirobj);
|
||||||
|
if (dir < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1881,41 +2076,51 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
|||||||
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
||||||
|
|
||||||
if (!pcmtypeobj || (pcmtypeobj == Py_None))
|
if (!dirobj || (dirobj == Py_None))
|
||||||
{
|
{
|
||||||
if (self->pchannels)
|
if (self->pchannels)
|
||||||
pcmtype = SND_PCM_STREAM_PLAYBACK;
|
dir = SND_PCM_STREAM_PLAYBACK;
|
||||||
else
|
else
|
||||||
pcmtype = SND_PCM_STREAM_CAPTURE;
|
dir = SND_PCM_STREAM_CAPTURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i <= SND_MIXER_SCHN_LAST; i++)
|
for (i = 0; i <= SND_MIXER_SCHN_LAST; i++)
|
||||||
{
|
{
|
||||||
if (channel == -1 || channel == i)
|
if (channel == -1 || channel == i)
|
||||||
{
|
{
|
||||||
if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
|
if (dir == SND_PCM_STREAM_PLAYBACK &&
|
||||||
snd_mixer_selem_has_playback_channel(elem, i)) {
|
snd_mixer_selem_has_playback_channel(elem, i)) {
|
||||||
physvolume = alsamixer_getphysvolume(self->pmin,
|
if (unit == unit_percent) {
|
||||||
self->pmax, volume);
|
physvolume = alsamixer_getphysvolume(self->pmin,
|
||||||
snd_mixer_selem_set_playback_volume(elem, i, physvolume);
|
self->pmax, volume);
|
||||||
|
snd_mixer_selem_set_playback_volume(elem, i, physvolume);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snd_mixer_selem_set_playback_dB(elem, i, (long)(volume * 100.0), 0);
|
||||||
|
}
|
||||||
done++;
|
done++;
|
||||||
}
|
}
|
||||||
else if (pcmtype == SND_PCM_STREAM_CAPTURE
|
else if (dir == SND_PCM_STREAM_CAPTURE
|
||||||
&& snd_mixer_selem_has_capture_channel(elem, i)
|
&& snd_mixer_selem_has_capture_channel(elem, i)
|
||||||
&& snd_mixer_selem_has_capture_volume(elem))
|
&& snd_mixer_selem_has_capture_volume(elem))
|
||||||
{
|
{
|
||||||
physvolume = alsamixer_getphysvolume(self->cmin, self->cmax,
|
if (unit == unit_percent) {
|
||||||
volume);
|
physvolume = alsamixer_getphysvolume(self->cmin, self->cmax,
|
||||||
snd_mixer_selem_set_capture_volume(elem, i, physvolume);
|
volume);
|
||||||
|
snd_mixer_selem_set_capture_volume(elem, i, physvolume);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snd_mixer_selem_set_capture_dB(elem, i, (long)(volume * 100), 0);
|
||||||
|
}
|
||||||
done++;
|
done++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!done)
|
if (!done)
|
||||||
{
|
{
|
||||||
PyErr_Format(ALSAAudioError, "No such channel [%s]",
|
PyErr_Format(ALSAAudioError, "No such channel [%s]",
|
||||||
self->cardname);
|
self->cardname);
|
||||||
@@ -1927,19 +2132,21 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(setvolume_doc,
|
PyDoc_STRVAR(setvolume_doc,
|
||||||
"setvolume(volume[[, channel] [, pcmtype]])\n\
|
"setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit='percent')\n\
|
||||||
\n\
|
\n\
|
||||||
Change the current volume settings for this mixer. The volume argument\n\
|
Change the current volume settings for this mixer. The volume argument\n\
|
||||||
controls the new volume setting as an integer percentage.\n\
|
controls the new volume setting as a percentage.\n\
|
||||||
If the optional argument channel is present, the volume is set only for\n\
|
If the optional argument channel is present, the volume is set only for\n\
|
||||||
this channel. This assumes that the mixer can control the volume for the\n\
|
this channel. This assumes that the mixer can control the volume for the\n\
|
||||||
channels independently.\n\
|
channels independently.\n\
|
||||||
\n\
|
\n\
|
||||||
The optional direction argument can be either PCM_PLAYBACK or PCM_CAPTURE.\n\
|
The optional 'direction' argument can be either PCM_PLAYBACK or PCM_CAPTURE.\n\
|
||||||
It is relevant if the mixer has independent playback and capture volume\n\
|
It is relevant if the mixer has independent playback and capture volume\n\
|
||||||
capabilities, and controls which of the volumes will be changed.\n\
|
capabilities, and controls which of the volumes will be changed.\n\
|
||||||
The default is 'playback' if the mixer has this capability, otherwise\n\
|
The default is PCM_PLAYBACK if the mixer has this capability, otherwise\n\
|
||||||
'capture'.");
|
PCM_CAPTURE.\n\
|
||||||
|
\n\
|
||||||
|
The optional 'unit' argument can be either 'percent' (the default) or 'dB'.");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
@@ -1950,6 +2157,7 @@ alsamixer_setmute(alsamixer_t *self, PyObject *args)
|
|||||||
int mute = 0;
|
int mute = 0;
|
||||||
int done = 0;
|
int done = 0;
|
||||||
int channel = MIXER_CHANNEL_ALL;
|
int channel = MIXER_CHANNEL_ALL;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args,"i|i:setmute", &mute, &channel))
|
if (!PyArg_ParseTuple(args,"i|i:setmute", &mute, &channel))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@@ -2115,6 +2323,38 @@ PyDoc_STRVAR(polldescriptors_doc,
|
|||||||
Return a list of file descriptors and event masks\n\
|
Return a list of file descriptors and event masks\n\
|
||||||
suitable for use with poll to monitor changes on this mixer.");
|
suitable for use with poll to monitor changes on this mixer.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsamixer_handleevents(alsamixer_t *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int handled;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args,":handleevents"))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!self->handle)
|
||||||
|
{
|
||||||
|
PyErr_SetString(ALSAAudioError, "Mixer is closed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
handled = snd_mixer_handle_events(self->handle);
|
||||||
|
if (handled < 0)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(handled),
|
||||||
|
self->cardname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromLong(handled);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(handleevents_doc,
|
||||||
|
"handleevents() -> int\n\
|
||||||
|
\n\
|
||||||
|
Acknowledge events on the polldescriptors() file descriptors\n\
|
||||||
|
to prevent subsequent polls from returning the same events again.\n\
|
||||||
|
Returns the number of events that were acknowledged.");
|
||||||
|
|
||||||
static PyMethodDef alsamixer_methods[] = {
|
static PyMethodDef alsamixer_methods[] = {
|
||||||
{"cardname", (PyCFunction)alsamixer_cardname, METH_VARARGS,
|
{"cardname", (PyCFunction)alsamixer_cardname, METH_VARARGS,
|
||||||
mixer_cardname_doc},
|
mixer_cardname_doc},
|
||||||
@@ -2125,18 +2365,21 @@ static PyMethodDef alsamixer_methods[] = {
|
|||||||
switchcap_doc},
|
switchcap_doc},
|
||||||
{"volumecap", (PyCFunction)alsamixer_volumecap, METH_VARARGS,
|
{"volumecap", (PyCFunction)alsamixer_volumecap, METH_VARARGS,
|
||||||
volumecap_doc},
|
volumecap_doc},
|
||||||
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS,
|
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS | METH_KEYWORDS,
|
||||||
getvolume_doc},
|
getvolume_doc},
|
||||||
{"getrange", (PyCFunction)alsamixer_getrange, METH_VARARGS, getrange_doc},
|
{"getrange", (PyCFunction)alsamixer_getrange, METH_VARARGS, getrange_doc},
|
||||||
{"getenum", (PyCFunction)alsamixer_getenum, METH_VARARGS, getenum_doc},
|
{"getenum", (PyCFunction)alsamixer_getenum, METH_VARARGS, getenum_doc},
|
||||||
{"getmute", (PyCFunction)alsamixer_getmute, METH_VARARGS, getmute_doc},
|
{"getmute", (PyCFunction)alsamixer_getmute, METH_VARARGS, getmute_doc},
|
||||||
{"getrec", (PyCFunction)alsamixer_getrec, METH_VARARGS, getrec_doc},
|
{"getrec", (PyCFunction)alsamixer_getrec, METH_VARARGS, getrec_doc},
|
||||||
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS,
|
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS | METH_KEYWORDS,
|
||||||
setvolume_doc},
|
setvolume_doc},
|
||||||
|
{"setenum", (PyCFunction)alsamixer_setenum, METH_VARARGS, setenum_doc},
|
||||||
{"setmute", (PyCFunction)alsamixer_setmute, METH_VARARGS, setmute_doc},
|
{"setmute", (PyCFunction)alsamixer_setmute, METH_VARARGS, setmute_doc},
|
||||||
{"setrec", (PyCFunction)alsamixer_setrec, METH_VARARGS, setrec_doc},
|
{"setrec", (PyCFunction)alsamixer_setrec, METH_VARARGS, setrec_doc},
|
||||||
{"polldescriptors", (PyCFunction)alsamixer_polldescriptors, METH_VARARGS,
|
{"polldescriptors", (PyCFunction)alsamixer_polldescriptors, METH_VARARGS,
|
||||||
polldescriptors_doc},
|
polldescriptors_doc},
|
||||||
|
{"handleevents", (PyCFunction)alsamixer_handleevents, METH_VARARGS,
|
||||||
|
handleevents_doc},
|
||||||
|
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
@@ -2201,6 +2444,8 @@ static PyTypeObject ALSAMixerType = {
|
|||||||
/******************************************/
|
/******************************************/
|
||||||
|
|
||||||
static PyMethodDef alsaaudio_methods[] = {
|
static PyMethodDef alsaaudio_methods[] = {
|
||||||
|
{ "card_indexes", (PyCFunction)alsacard_list_indexes, METH_VARARGS, card_indexes_doc},
|
||||||
|
{ "card_name", (PyCFunction)alsacard_name, METH_VARARGS, card_name_doc},
|
||||||
{ "cards", (PyCFunction)alsacard_list, METH_VARARGS, cards_doc},
|
{ "cards", (PyCFunction)alsacard_list, METH_VARARGS, cards_doc},
|
||||||
{ "pcms", (PyCFunction)alsapcm_list, METH_VARARGS, pcms_doc},
|
{ "pcms", (PyCFunction)alsapcm_list, METH_VARARGS, pcms_doc},
|
||||||
{ "mixers", (PyCFunction)alsamixer_list, METH_VARARGS|METH_KEYWORDS, mixers_doc},
|
{ "mixers", (PyCFunction)alsamixer_list, METH_VARARGS|METH_KEYWORDS, mixers_doc},
|
||||||
@@ -2276,6 +2521,9 @@ PyObject *PyInit_alsaaudio(void)
|
|||||||
Py_INCREF(ALSAAudioError);
|
Py_INCREF(ALSAAudioError);
|
||||||
PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError);
|
PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError);
|
||||||
|
|
||||||
|
_EXPORT_INT(m, "Percent", unit_percent);
|
||||||
|
_EXPORT_INT(m, "dB", unit_dB);
|
||||||
|
|
||||||
_EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK);
|
_EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK);
|
||||||
_EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE);
|
_EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE);
|
||||||
|
|
||||||
@@ -2307,6 +2555,22 @@ PyObject *PyInit_alsaaudio(void)
|
|||||||
_EXPORT_INT(m, "PCM_FORMAT_IMA_ADPCM",SND_PCM_FORMAT_IMA_ADPCM);
|
_EXPORT_INT(m, "PCM_FORMAT_IMA_ADPCM",SND_PCM_FORMAT_IMA_ADPCM);
|
||||||
_EXPORT_INT(m, "PCM_FORMAT_MPEG",SND_PCM_FORMAT_MPEG);
|
_EXPORT_INT(m, "PCM_FORMAT_MPEG",SND_PCM_FORMAT_MPEG);
|
||||||
_EXPORT_INT(m, "PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM);
|
_EXPORT_INT(m, "PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_S24_3LE",SND_PCM_FORMAT_S24_3LE);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_S24_3BE",SND_PCM_FORMAT_S24_3BE);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_U24_3LE",SND_PCM_FORMAT_U24_3LE);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_U24_3BE",SND_PCM_FORMAT_U24_3BE);
|
||||||
|
|
||||||
|
/* DSD sample formats are included in ALSA 1.0.29 and higher
|
||||||
|
* define OVERRIDE_DSD_COMPILE to include DSD sample support
|
||||||
|
* if you use a patched ALSA lib version
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if SND_LIB_VERSION >= 0x1001d || defined OVERRIDE_DSD_COMPILE
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_DSD_U8", SND_PCM_FORMAT_DSD_U8);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_DSD_U16_LE", SND_PCM_FORMAT_DSD_U16_LE);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_DSD_U32_LE", SND_PCM_FORMAT_DSD_U32_LE);
|
||||||
|
_EXPORT_INT(m, "PCM_FORMAT_DSD_U32_BE", SND_PCM_FORMAT_DSD_U32_BE);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Mixer stuff */
|
/* Mixer stuff */
|
||||||
_EXPORT_INT(m, "MIXER_CHANNEL_ALL", MIXER_CHANNEL_ALL);
|
_EXPORT_INT(m, "MIXER_CHANNEL_ALL", MIXER_CHANNEL_ALL);
|
||||||
|
|||||||
230
doc/conf.py
230
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('.'))
|
||||||
|
|
||||||
sys.path.append('..')
|
import sys
|
||||||
|
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-2009, 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 = None
|
||||||
|
|
||||||
# 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,22 +1,32 @@
|
|||||||
|
.. alsaaudio documentation documentation master file, created by
|
||||||
|
sphinx-quickstart on Thu Mar 30 23:52:21 2017.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
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>`_
|
||||||
|
|
||||||
|
|
||||||
|
Github
|
||||||
|
======
|
||||||
|
|
||||||
|
* `Repository <https://github.com/larsimmisch/pyalsaaudio/>`_
|
||||||
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
||||||
@@ -24,3 +34,5 @@ Indices and tables
|
|||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
* :ref:`search`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -193,10 +193,10 @@ PCM objects have the following methods:
|
|||||||
``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,6 +210,10 @@ 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)
|
||||||
========================= ===============
|
========================= ===============
|
||||||
|
|
||||||
|
|
||||||
@@ -233,6 +237,9 @@ PCM objects have the following methods:
|
|||||||
``(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 an overrun, this function will return a negative size: :const:`-EPIPE`.
|
||||||
|
This indicates that data was lost, even if the operation itself succeeded.
|
||||||
|
Try using a larger periodsize.
|
||||||
|
|
||||||
.. method:: PCM.write(data)
|
.. method:: PCM.write(data)
|
||||||
|
|
||||||
@@ -256,6 +263,17 @@ PCM objects have the following methods:
|
|||||||
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.polldescriptors()
|
||||||
|
|
||||||
|
Returns a tuple of *(file descriptor, eventmask)* that can be used to
|
||||||
|
wait for changes on the mixer with *select.poll*.
|
||||||
|
|
||||||
|
The *eventmask* value is compatible with `poll.register`__ in the Python
|
||||||
|
:const:`select` module.
|
||||||
|
|
||||||
|
__ poll_objects_
|
||||||
|
|
||||||
**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
|
||||||
@@ -425,31 +443,35 @@ 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.getvolume([direction])
|
.. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent)
|
||||||
|
|
||||||
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 percentages or dB values, depending on *unit*.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The *direction* 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.setvolume(volume, [channel], [direction])
|
.. method:: Mixer.setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit=Percent)
|
||||||
|
|
||||||
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.
|
controls the new volume setting as either a percentage or a dB value. Both
|
||||||
|
integer and floating point values can be given.
|
||||||
|
|
||||||
If the optional argument *channel* is present, the volume is set
|
The *channel* argument can be used to restrict the channels for which the volume is
|
||||||
only for this channel. This assumes that the mixer can control the
|
set. By default, the volume of all channels is adjusted. This assumes that the mixer
|
||||||
volume for the channels independently.
|
can control the volume for the channels independently.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The *direction* 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 *unit* argument determines how the volume value is interpreted, as a prcentage
|
||||||
|
or as a dB value.
|
||||||
|
|
||||||
.. method:: Mixer.setmute(mute, [channel])
|
.. method:: Mixer.setmute(mute, [channel])
|
||||||
|
|
||||||
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
|
||||||
@@ -473,9 +495,20 @@ Mixer objects have the following methods:
|
|||||||
|
|
||||||
.. method:: Mixer.polldescriptors()
|
.. method:: Mixer.polldescriptors()
|
||||||
|
|
||||||
Returns a tuple of (file descriptor, eventmask) that can be used to
|
Returns a tuple of *(file descriptor, eventmask)* that can be used to
|
||||||
wait for changes on the mixer with *select.poll*.
|
wait for changes on the mixer with *select.poll*.
|
||||||
|
|
||||||
|
The *eventmask* value is compatible with `poll.register`__ in the Python
|
||||||
|
:const:`select` module.
|
||||||
|
|
||||||
|
__ poll_objects_
|
||||||
|
|
||||||
|
.. method:: Mixer.handleevents()
|
||||||
|
|
||||||
|
Acknowledge events on the *polldescriptors* file descriptors
|
||||||
|
to prevent subsequent polls from returning the same events again.
|
||||||
|
Returns the number of events that were acknowledged.
|
||||||
|
|
||||||
**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.
|
||||||
@@ -614,3 +647,5 @@ argument::
|
|||||||
.. rubric:: Footnotes
|
.. rubric:: Footnotes
|
||||||
|
|
||||||
.. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet.
|
.. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet.
|
||||||
|
|
||||||
|
.. _poll_objects: http://docs.python.org/library/select.html#poll-objects
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ wish (even commercial purposes). There is no warranty whatsoever.
|
|||||||
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 the SourceForge bug tracker.
|
If you find bugs in the wrappers please use thegithub 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.
|
||||||
|
|
||||||
@@ -110,25 +110,32 @@ And then as root: --- ::
|
|||||||
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>`_.
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ 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)
|
||||||
|
|
||||||
Period
|
Period
|
||||||
When the hardware processes data this is done in chunks of frames. The time
|
When the hardware processes data this is done in chunks of frames. The time
|
||||||
|
|||||||
66
isine.py
66
isine.py
@@ -6,30 +6,50 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from queue import Queue, Empty
|
from multiprocessing import Queue
|
||||||
|
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
from Queue import Empty
|
||||||
|
else:
|
||||||
|
from queue import Empty
|
||||||
|
|
||||||
from math import pi, sin
|
from math import pi, sin
|
||||||
import struct
|
import struct
|
||||||
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
|
framesize = 2 # bytes per frame for the values above
|
||||||
|
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):
|
|
||||||
buffer = buffer + digitize(sin(i/(2 * pi)))
|
|
||||||
|
|
||||||
return buffer
|
# the buffersize we approximately want
|
||||||
|
target_size = int(sampling_rate * channels * duration)
|
||||||
|
|
||||||
|
# the length of a full sine wave at the frequency
|
||||||
|
cycle_size = int(sampling_rate / frequency)
|
||||||
|
|
||||||
|
# number of full cycles we can fit into target_size
|
||||||
|
factor = int(target_size / cycle_size)
|
||||||
|
|
||||||
|
size = max(int(cycle_size * factor), 1)
|
||||||
|
|
||||||
|
sine = [ int(32767 * sin(2 * pi * frequency * i / sampling_rate)) \
|
||||||
|
for i in range(size)]
|
||||||
|
|
||||||
|
return struct.pack('%dh' % size, *sine)
|
||||||
|
|
||||||
|
|
||||||
class SinePlayer(Thread):
|
class SinePlayer(Thread):
|
||||||
|
|
||||||
@@ -37,7 +57,7 @@ class SinePlayer(Thread):
|
|||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.setDaemon(True)
|
self.setDaemon(True)
|
||||||
self.device = alsaaudio.PCM()
|
self.device = alsaaudio.PCM()
|
||||||
self.device.setchannels(1)
|
self.device.setchannels(channels)
|
||||||
self.device.setformat(format)
|
self.device.setformat(format)
|
||||||
self.device.setrate(sampling_rate)
|
self.device.setrate(sampling_rate)
|
||||||
self.queue = Queue()
|
self.queue = Queue()
|
||||||
@@ -47,19 +67,15 @@ 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(frequency) * factor
|
|
||||||
print('factor: %d, frames: %d' % (factor, len(buf) / framesize))
|
|
||||||
|
|
||||||
self.queue.put( buf)
|
buf = generate(f)
|
||||||
|
self.queue.put(buf)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
buffer = None
|
buffer = None
|
||||||
|
|||||||
26
mixertest.py
26
mixertest.py
@@ -23,6 +23,12 @@ import sys
|
|||||||
import getopt
|
import getopt
|
||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
|
||||||
|
def list_cards():
|
||||||
|
print("Available sound cards:")
|
||||||
|
for i in alsaaudio.card_indexes():
|
||||||
|
(name, longname) = alsaaudio.card_name(i)
|
||||||
|
print(" %d: %s (%s)" % (i, name, longname))
|
||||||
|
|
||||||
def list_mixers(kwargs):
|
def list_mixers(kwargs):
|
||||||
print("Available mixer controls:")
|
print("Available mixer controls:")
|
||||||
for m in alsaaudio.mixers(**kwargs):
|
for m in alsaaudio.mixers(**kwargs):
|
||||||
@@ -40,13 +46,17 @@ def show_mixer(name, kwargs):
|
|||||||
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
|
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
|
||||||
' '.join(mixer.switchcap())))
|
' '.join(mixer.switchcap())))
|
||||||
volumes = mixer.getvolume()
|
volumes = mixer.getvolume()
|
||||||
for i in range(len(volumes)):
|
for i, v in enumerate(volumes):
|
||||||
print("Channel %i volume: %i%%" % (i,volumes[i]))
|
print("Channel %i volume: %.02f%%" % (i, v))
|
||||||
|
|
||||||
|
volumes = mixer.getvolume(unit=alsaaudio.dB)
|
||||||
|
for i, v in enumerate(volumes):
|
||||||
|
print("Channel %i volume: %.02fdB" % (i, v))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mutes = mixer.getmute()
|
mutes = mixer.getmute()
|
||||||
for i in range(len(mutes)):
|
for i, m in enumerate(mutes):
|
||||||
if mutes[i]:
|
if m:
|
||||||
print("Channel %i is muted" % i)
|
print("Channel %i is muted" % i)
|
||||||
except alsaaudio.ALSAAudioError:
|
except alsaaudio.ALSAAudioError:
|
||||||
# May not support muting
|
# May not support muting
|
||||||
@@ -54,8 +64,8 @@ def show_mixer(name, kwargs):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
recs = mixer.getrec()
|
recs = mixer.getrec()
|
||||||
for i in range(len(recs)):
|
for i, r in enumerate(recs):
|
||||||
if recs[i]:
|
if r:
|
||||||
print("Channel %i is recording" % i)
|
print("Channel %i is recording" % i)
|
||||||
except alsaaudio.ALSAAudioError:
|
except alsaaudio.ALSAAudioError:
|
||||||
# May not support recording
|
# May not support recording
|
||||||
@@ -113,6 +123,8 @@ if __name__ == '__main__':
|
|||||||
else:
|
else:
|
||||||
usage()
|
usage()
|
||||||
|
|
||||||
|
list_cards()
|
||||||
|
|
||||||
if not len(args):
|
if not len(args):
|
||||||
list_mixers(kwargs)
|
list_mixers(kwargs)
|
||||||
elif len(args) == 1:
|
elif len(args) == 1:
|
||||||
|
|||||||
@@ -21,17 +21,17 @@ import getopt
|
|||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print('usage: playbacktest.py [-c <card>] <file>', file=sys.stderr)
|
print('usage: playbacktest.py [-d <device>] <file>', file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
card = 'default'
|
device = 'default'
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'c:')
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == '-c':
|
if o == '-d':
|
||||||
card = a
|
device = a
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
usage()
|
usage()
|
||||||
@@ -39,7 +39,7 @@ 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.
|
||||||
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, card=card)
|
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device=device)
|
||||||
|
|
||||||
# Set attributes: Mono, 44100 Hz, 16 bit little endian frames
|
# Set attributes: Mono, 44100 Hz, 16 bit little endian frames
|
||||||
out.setchannels(1)
|
out.setchannels(1)
|
||||||
|
|||||||
22
playwav.py
22
playwav.py
@@ -24,39 +24,41 @@ def play(device, f):
|
|||||||
elif f.getsampwidth() == 2:
|
elif f.getsampwidth() == 2:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
||||||
elif f.getsampwidth() == 3:
|
elif f.getsampwidth() == 3:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_S24_LE)
|
device.setformat(alsaaudio.PCM_FORMAT_S24_3LE)
|
||||||
elif f.getsampwidth() == 4:
|
elif f.getsampwidth() == 4:
|
||||||
device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
|
device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unsupported format')
|
raise ValueError('Unsupported format')
|
||||||
|
|
||||||
device.setperiodsize(320)
|
periodsize = f.getframerate() / 8
|
||||||
|
|
||||||
|
device.setperiodsize(periodsize)
|
||||||
|
|
||||||
data = f.readframes(320)
|
data = f.readframes(periodsize)
|
||||||
while data:
|
while data:
|
||||||
# Read data from stdin
|
# Read data from stdin
|
||||||
device.write(data)
|
device.write(data)
|
||||||
data = f.readframes(320)
|
data = f.readframes(periodsize)
|
||||||
|
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print('usage: playwav.py [-c <card>] <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__':
|
||||||
|
|
||||||
card = 'default'
|
device = 'default'
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'c:')
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == '-c':
|
if o == '-d':
|
||||||
card = a
|
device = a
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
usage()
|
usage()
|
||||||
|
|
||||||
f = wave.open(args[0], 'rb')
|
f = wave.open(args[0], 'rb')
|
||||||
device = alsaaudio.PCM(card=card)
|
device = alsaaudio.PCM(device=device)
|
||||||
|
|
||||||
play(device, f)
|
play(device, f)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
##
|
##
|
||||||
## This is an example of a simple sound capture script.
|
## This is an example of a simple sound capture script.
|
||||||
##
|
##
|
||||||
## The script opens an ALSA pcm forsound capture. Set
|
## The script opens an ALSA pcm device for sound capture, sets
|
||||||
## various attributes of the capture, and reads in a loop,
|
## various attributes of the capture, and reads in a loop,
|
||||||
## writing the data to standard out.
|
## writing the data to standard out.
|
||||||
##
|
##
|
||||||
@@ -22,17 +22,17 @@ import getopt
|
|||||||
import alsaaudio
|
import alsaaudio
|
||||||
|
|
||||||
def usage():
|
def usage():
|
||||||
print('usage: recordtest.py [-c <card>] <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__':
|
||||||
|
|
||||||
card = 'default'
|
device = 'default'
|
||||||
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], 'c:')
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
for o, a in opts:
|
for o, a in opts:
|
||||||
if o == '-c':
|
if o == '-d':
|
||||||
card = a
|
device = a
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
usage()
|
usage()
|
||||||
@@ -42,7 +42,7 @@ if __name__ == '__main__':
|
|||||||
# Open the device in nonblocking capture mode. The last argument could
|
# Open the device in nonblocking capture mode. The last argument could
|
||||||
# just as well have been zero for blocking mode. Then we could have
|
# just as well have been zero for blocking mode. Then we could have
|
||||||
# left out the sleep call in the bottom of the loop
|
# left out the sleep call in the bottom of the loop
|
||||||
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, card)
|
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, device=device)
|
||||||
|
|
||||||
# Set attributes: Mono, 44100 Hz, 16 bit little endian samples
|
# Set attributes: Mono, 44100 Hz, 16 bit little endian samples
|
||||||
inp.setchannels(1)
|
inp.setchannels(1)
|
||||||
|
|||||||
20
setup.py
20
setup.py
@@ -4,25 +4,11 @@
|
|||||||
It is fairly complete for PCM devices and Mixer access.
|
It is fairly complete for PCM devices and Mixer access.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import subprocess
|
from setuptools import setup
|
||||||
from distutils.core import setup
|
from setuptools.extension import Extension
|
||||||
from distutils.extension import Extension
|
|
||||||
from sys import version
|
from sys import version
|
||||||
|
|
||||||
def gitrev():
|
pyalsa_version = '0.8.4'
|
||||||
rev = subprocess.check_output(['git', 'describe', '--tags', '--dirty=-dev',
|
|
||||||
'--always'])
|
|
||||||
return rev.decode('utf-8').strip()
|
|
||||||
|
|
||||||
pyalsa_version = gitrev()
|
|
||||||
|
|
||||||
# patch distutils if it's too old to cope with the "classifiers" or
|
|
||||||
# "download_url" keywords
|
|
||||||
from sys import version
|
|
||||||
if version < '2.2.3':
|
|
||||||
from distutils.dist import DistributionMetadata
|
|
||||||
DistributionMetadata.classifiers = None
|
|
||||||
DistributionMetadata.download_url = None
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
setup(
|
setup(
|
||||||
|
|||||||
21
test.py
21
test.py
@@ -44,16 +44,11 @@ class MixerTest(unittest.TestCase):
|
|||||||
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 d in ['default'] + list(range(len(alsaaudio.cards()))):
|
for c in alsaaudio.card_indexes():
|
||||||
if type(d) == type(0):
|
mixers = alsaaudio.mixers(cardindex=c)
|
||||||
kwargs = { 'cardindex': d }
|
|
||||||
else:
|
|
||||||
kwargs = { 'device': d }
|
|
||||||
|
|
||||||
mixers = alsaaudio.mixers(**kwargs)
|
|
||||||
|
|
||||||
for m in mixers:
|
for m in mixers:
|
||||||
mixer = alsaaudio.Mixer(m, **kwargs)
|
mixer = alsaaudio.Mixer(m, cardindex=c)
|
||||||
mixer.close()
|
mixer.close()
|
||||||
|
|
||||||
def testMixerAll(self):
|
def testMixerAll(self):
|
||||||
@@ -90,14 +85,10 @@ class PCMTest(unittest.TestCase):
|
|||||||
"""Test PCM objects"""
|
"""Test PCM objects"""
|
||||||
|
|
||||||
def testPCM(self):
|
def testPCM(self):
|
||||||
"Open a PCM object on every device"
|
"Open a PCM object on every card"
|
||||||
|
|
||||||
for pd in alsaaudio.pcms():
|
for c in alsaaudio.card_indexes():
|
||||||
pcm = alsaaudio.PCM(device=pd)
|
pcm = alsaaudio.PCM(cardindex=c)
|
||||||
pcm.close()
|
|
||||||
|
|
||||||
for pd in alsaaudio.pcms(alsaaudio.PCM_CAPTURE):
|
|
||||||
pcm = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, device=pd)
|
|
||||||
pcm.close()
|
pcm.close()
|
||||||
|
|
||||||
def testPCMAll(self):
|
def testPCMAll(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user