Allow card index or device name for mixers.

This change breaks API compatibility. Sorry.
This commit is contained in:
Lars Immisch
2015-05-04 21:13:15 +00:00
parent 00cb3e0a3a
commit 9188071945
5 changed files with 147 additions and 87 deletions
+48 -33
View File
@@ -91,19 +91,6 @@ char *translate_cardname(char *name)
return full; return full;
} }
/* Translate a card index to a ALSA cardname
Returns a newly allocated string.
*/
char *translate_cardidx(int idx)
{
char name[32];
sprintf(name, "hw:%d", idx);
return strdup(name);
}
/******************************************/ /******************************************/
/* PCM object wrapper */ /* PCM object wrapper */
/******************************************/ /******************************************/
@@ -927,26 +914,39 @@ alsamixer_gethandle(char *cardname, snd_mixer_t **handle)
} }
static PyObject * static PyObject *
alsamixer_list(PyObject *self, PyObject *args) alsamixer_list(PyObject *self, PyObject *args, PyObject *kwds)
{ {
snd_mixer_t *handle; snd_mixer_t *handle;
snd_mixer_selem_id_t *sid; snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *elem; snd_mixer_elem_t *elem;
int err; int err;
int cardidx = 0; int cardidx = -1;
char cardname[32]; char hw_device[32];
char *device = "default";
PyObject *result; PyObject *result;
if (!PyArg_ParseTuple(args,"|i:mixers",&cardidx))
return NULL;
sprintf(cardname, "hw:%d", cardidx);
char *kw[] = { "device", "cardindex", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|si", kw,
&device, &cardidx))
return NULL;
if (cardidx >= 0) {
if (cardidx >= 0 && cardidx < 32) {
snprintf(hw_device, sizeof(hw_device), "hw:%d", cardidx);
}
else {
PyErr_SetString(ALSAAudioError, "Invalid card number");
return NULL;
}
device = hw_device;
}
snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_id_alloca(&sid);
err = alsamixer_gethandle(cardname, &handle); err = alsamixer_gethandle(device, &handle);
if (err < 0) if (err < 0)
{ {
PyErr_SetString(ALSAAudioError,snd_strerror(err)); PyErr_SetString(ALSAAudioError, snd_strerror(err));
snd_mixer_close(handle); snd_mixer_close(handle);
return NULL; return NULL;
} }
@@ -991,22 +991,35 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{ {
alsamixer_t *self; alsamixer_t *self;
int err; int err;
int cardindex = 0;
char *control = "Master"; char *control = "Master";
char *device = "default";
char hw_device[32];
int cardidx = -1;
int id = 0; int id = 0;
snd_mixer_elem_t *elem; snd_mixer_elem_t *elem;
int channel; int channel;
char *kw[] = { "control", "id", "cardindex", NULL }; char *kw[] = { "control", "id", "device", "cardindex", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sii", kw, if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sisi", kw,
&control, &id, &cardindex)) &control, &id, &device, &cardidx))
return NULL; return NULL;
if (cardidx >= 0) {
if (cardidx >= 0 && cardidx < 32) {
snprintf(hw_device, sizeof(hw_device), "hw:%d", cardidx);
}
else {
PyErr_SetString(ALSAAudioError, "Invalid card number");
return NULL;
}
device = hw_device;
}
if (!(self = (alsamixer_t *)PyObject_New(alsamixer_t, &ALSAMixerType))) if (!(self = (alsamixer_t *)PyObject_New(alsamixer_t, &ALSAMixerType)))
return NULL; return NULL;
self->handle = 0; self->handle = 0;
self->cardname = translate_cardidx(cardindex); self->cardname = strdup(device);
err = alsamixer_gethandle(self->cardname, &self->handle); err = alsamixer_gethandle(self->cardname, &self->handle);
if (err<0) if (err<0)
@@ -1019,15 +1032,17 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->controlname = strdup(control); self->controlname = strdup(control);
self->controlid = id; self->controlid = id;
elem = alsamixer_find_elem(self->handle,control,id); elem = alsamixer_find_elem(self->handle,control, id);
if (!elem) if (!elem)
{ {
char errtext[128]; char errtext[128];
sprintf(errtext,"Unable to find mixer control '%s',%i", sprintf(errtext,"Unable to find mixer control '%s',%i on card '%s'",
self->controlname, self->controlname,
self->controlid); self->controlid,
self->cardname);
snd_mixer_close(self->handle); snd_mixer_close(self->handle);
PyErr_SetString(ALSAAudioError,errtext); PyErr_SetString(ALSAAudioError,errtext);
free(self->cardname);
return NULL; return NULL;
} }
/* Determine mixer capabilities */ /* Determine mixer capabilities */
@@ -2050,8 +2065,8 @@ static PyTypeObject ALSAMixerType = {
/******************************************/ /******************************************/
static PyMethodDef alsaaudio_methods[] = { static PyMethodDef alsaaudio_methods[] = {
{ "cards", alsacard_list, METH_VARARGS, cards_doc}, { "cards", (PyCFunction)alsacard_list, METH_VARARGS, cards_doc},
{ "mixers", alsamixer_list, METH_VARARGS, mixers_doc}, { "mixers", (PyCFunction)alsamixer_list, METH_VARARGS|METH_KEYWORDS, mixers_doc},
{ 0, 0 }, { 0, 0 },
}; };
+64 -22
View File
@@ -38,11 +38,23 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
List the available cards by name (suitable for PCM objects). List the available cards by name (suitable for PCM objects).
.. function:: mixers([cardindex]) .. function:: mixers(device='default', cardindex=-1)
List the available mixers. The optional *cardindex* specifies which card List the available mixers. The arguments are:
should be queried. The default is 0.
*device* - the name of the device on which the mixer resides. The default is
'default'.
*cardindex* - specifies which card should be used [#f3]_. If this argument
is given, the device name is constructed like this: `hw:<cardindex>` and
the `device` keyword argument is ignored. 0 is the
first sound card.
**Note:** The arguments for this function were changed in
version 0.8 and this change is not completely backward compatible.
Old versions accepted just the optional parameter *cardindex*.
The current version accepts either a device name or a cardindex.
.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, card='default') .. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, card='default')
This class is used to represent a PCM device (both for playback and This class is used to represent a PCM device (both for playback and
@@ -52,14 +64,24 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
* *mode* - can be either ``PCM_NONBLOCK``, or ``PCM_NORMAL`` (default). * *mode* - can be either ``PCM_NONBLOCK``, or ``PCM_NORMAL`` (default).
* *card* - specifies the name of the card that should be used. * *card* - specifies the name of the card that should be used.
.. class:: Mixer(control='Master', id=0, cardindex=0) .. class:: Mixer(control='Master', id=0, device='default', cardindex=-1)
This class is used to access a specific ALSA mixer. The arguments This class is used to access a specific ALSA mixer. The arguments
are: are:
* *control* - Name of the chosen mixed (default is 'Master'). * *control* - Name of the chosen mixed (default is 'Master').
* *id* - id of mixer -- More explanation needed here * *id* - id of mixer -- More explanation needed here
* *cardindex* specifies which card should be used. *device* - the name of the device on which the mixer resides. The default is
'default'.
*cardindex* - specifies which card should be used [#f3]_. If this argument
is given, the device name is constructed like this: `hw:<cardindex>` and
the `device` keyword argument is ignored. 0 is the
first sound card.
**Note:** The arguments for this constructor were changed in
version 0.8 and this change is not completely backward compatible.
Old versions accepted just the optional parameter *cardindex*.
The current version accepts either a device name or a cardindex.
.. exception:: ALSAAudioError .. exception:: ALSAAudioError
@@ -243,7 +265,7 @@ Mixer Objects
Mixer objects provides access to the ALSA mixer API. Mixer objects provides access to the ALSA mixer API.
.. class:: Mixer(control='Master', id=0, cardindex=0) .. class:: Mixer(control='Master', id=0, device='default', cardindex=-1)
*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
@@ -252,10 +274,19 @@ Mixer objects provides access to the ALSA mixer API.
*id* - the id of the mixer control. Default is 0 *id* - the id of the mixer control. Default is 0
*cardindex* - specifies which card should be used [#f3]_. 0 is the *device* - the name of the device on which the mixer resides. The default is
first sound card. 'default'.
*cardindex* - specifies which card should be used [#f3]_. If this argument
is given, the device name is constructed like this: `hw:<cardindex>` and
the `device` keyword argument is ignored. 0 is the
first sound card.
**Note:** The arguments for this function were changed in
version 0.8 and this change is not completely backward compatible.
Old versions accepted just the optional parameter *cardindex*.
The current version accepts either a device name or a cardindex.
**Note:** For a list of available controls, you can also use **amixer**:: **Note:** For a list of available controls, you can also use **amixer**::
amixer amixer
@@ -471,8 +502,8 @@ or::
>>> import alsaaudio >>> import alsaaudio
>>> alsaaudio.cards() >>> alsaaudio.cards()
mixertest.py accepts the commandline option *-c <cardindex>*. Card mixertest.py accepts the commandline options *-d <device> and
indices start at 0. *-c <cardindex>*.
playwav.py playwav.py
~~~~~~~~~~ ~~~~~~~~~~
@@ -502,11 +533,13 @@ Play back the recording with::
mixertest.py mixertest.py
~~~~~~~~~~~~ ~~~~~~~~~~~~
Without arguments, **mixertest.py** will list all available *controls*. Without arguments, **mixertest.py** will list all available *controls* on the
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'
'Master Mono' 'Master Mono'
'Headphone' 'Headphone'
@@ -525,25 +558,34 @@ The output might look like this::
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
Mixer name: 'Master' Mixer name: 'Master'
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%
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
This will mute the Master mixer. This will mute the Master mixer.
Or:: Or::
$ ./mixertest.py Master 40 $ ./mixertest.py Master 40
This sets the volume to 40% on all channels. This sets the volume to 40% on all channels.
To select a different soundcard, use either the *device* or *cardindex*
argument::
$ ./mixertest.py -c 0 Master
Mixer name: 'Master'
Capabilities: Playback Volume Playback Mute
Channel 0 volume: 61%
Channel 1 volume: 61%
.. 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.
+3 -3
View File
@@ -2,10 +2,10 @@
Introduction Introduction
************ ************
:Author: Casper Wilstrup :Author: Casper Wilstrup <cwi@aves.dk>
:Author: Lars Immisch :Author: Lars Immisch <lars@ibp.de>
.. |release| replace:: 0.4 .. |release| replace:: 0.8
.. % At minimum, give your name and an email address. You can include a .. % At minimum, give your name and an email address. You can include a
.. % snail-mail address if you like. .. % snail-mail address if you like.
+22 -24
View File
@@ -18,39 +18,35 @@
## python mixertest.py Capture 0,[un]rec # [dis/en]able capture on channel 0 ## python mixertest.py Capture 0,[un]rec # [dis/en]able capture on channel 0
# Footnote: I'd normally use print instead of sys.std(out|err).write,
# but we're in the middle of the conversion between python 2 and 3
# and this code runs on both versions without conversion
import sys import sys
import getopt import getopt
import alsaaudio import alsaaudio
def list_mixers(idx=0): def list_mixers(kwargs):
sys.stdout.write("Available mixer controls:\n") print("Available mixer controls:")
for m in alsaaudio.mixers(idx): for m in alsaaudio.mixers(**kwargs):
sys.stdout.write(" '%s'\n" % m) print(" '%s'\n" % m)
def show_mixer(name, idx=0): def show_mixer(name, kwargs):
# Demonstrates how mixer settings are queried. # Demonstrates how mixer settings are queried.
try: try:
mixer = alsaaudio.Mixer(name, cardindex=idx) mixer = alsaaudio.Mixer(name, **kwargs)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
sys.stderr.write("No such mixer\n") sys.stderr.write("No such mixer\n")
sys.exit(1) sys.exit(1)
sys.stdout.write("Mixer name: '%s'\n" % mixer.mixer()) print("Mixer name: '%s'" % mixer.mixer())
sys.stdout.write("Capabilities: %s %s\n" % (' '.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 in range(len(volumes)):
sys.stdout.write("Channel %i volume: %i%%\n" % (i,volumes[i])) print("Channel %i volume: %i%%" % (i,volumes[i]))
try: try:
mutes = mixer.getmute() mutes = mixer.getmute()
for i in range(len(mutes)): for i in range(len(mutes)):
if mutes[i]: if mutes[i]:
sys.stdout.write("Channel %i is muted\n" % i) print("Channel %i is muted" % i)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
# May not support muting # May not support muting
pass pass
@@ -59,15 +55,15 @@ def show_mixer(name, idx=0):
recs = mixer.getrec() recs = mixer.getrec()
for i in range(len(recs)): for i in range(len(recs)):
if recs[i]: if recs[i]:
sys.stdout.write("Channel %i is recording\n" % i) print("Channel %i is recording" % i)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
# May not support recording # May not support recording
pass pass
def set_mixer(name, args, idx=0): def set_mixer(name, args, kwargs):
# Demonstrates how to set mixer settings # Demonstrates how to set mixer settings
try: try:
mixer = alsaaudio.Mixer(name, cardindex=idx) mixer = alsaaudio.Mixer(name, **kwargs)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
sys.stderr.write("No such mixer") sys.stderr.write("No such mixer")
sys.exit(1) sys.exit(1)
@@ -106,17 +102,19 @@ def usage():
if __name__ == '__main__': if __name__ == '__main__':
cardindex = 0 kwargs = {}
opts, args = getopt.getopt(sys.argv[1:], 'c:?h') opts, args = getopt.getopt(sys.argv[1:], 'c:d:?h')
for o, a in opts: for o, a in opts:
if o == '-c': if o == '-c':
cardindex = int(a) kwargs = { 'cardindex': int(a) }
elif o == '-d':
kwargs = { 'device': a }
else: else:
usage() usage()
if not len(args): if not len(args):
list_mixers(cardindex) list_mixers(kwargs)
elif len(args) == 1: elif len(args) == 1:
show_mixer(args[0], cardindex) show_mixer(args[0], kwargs)
else: else:
set_mixer(args[0], args[1], cardindex) set_mixer(args[0], args[1], kwargs)
+10 -5
View File
@@ -42,13 +42,18 @@ class MixerTest(unittest.TestCase):
"""Test Mixer objects""" """Test Mixer objects"""
def testMixer(self): def testMixer(self):
"""Open a Mixer on every card""" """Open the default Mixers and the Mixers on every card"""
for d in ['default'] + range(len(alsaaudio.cards())):
if type(d) == type(0):
kwargs = { 'cardindex': d }
else:
kwargs = { 'device': d }
# Mixers are addressed by index, not name mixers = alsaaudio.mixers(**kwargs)
for i in range(len(alsaaudio.cards())):
mixers = alsaaudio.mixers(i)
for m in mixers: for m in mixers:
mixer = alsaaudio.Mixer(m, cardindex=i) mixer = alsaaudio.Mixer(m, **kwargs)
mixer.close() mixer.close()
def testMixerAll(self): def testMixerAll(self):