From 918807194555d20a51ddeac00e2e6b5bb7d243e2 Mon Sep 17 00:00:00 2001 From: Lars Immisch Date: Mon, 4 May 2015 21:13:15 +0000 Subject: [PATCH] Allow card index or device name for mixers. This change breaks API compatibility. Sorry. --- alsaaudio.c | 81 ++++++++++++++++++++++++----------------- doc/libalsaaudio.rst | 86 ++++++++++++++++++++++++++++++++------------ doc/pyalsaaudio.rst | 6 ++-- mixertest.py | 46 ++++++++++++------------ test.py | 15 +++++--- 5 files changed, 147 insertions(+), 87 deletions(-) diff --git a/alsaaudio.c b/alsaaudio.c index 53bf579..f08721d 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -91,19 +91,6 @@ char *translate_cardname(char *name) 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 */ /******************************************/ @@ -927,26 +914,39 @@ alsamixer_gethandle(char *cardname, snd_mixer_t **handle) } static PyObject * -alsamixer_list(PyObject *self, PyObject *args) +alsamixer_list(PyObject *self, PyObject *args, PyObject *kwds) { snd_mixer_t *handle; snd_mixer_selem_id_t *sid; snd_mixer_elem_t *elem; int err; - int cardidx = 0; - char cardname[32]; + int cardidx = -1; + char hw_device[32]; + char *device = "default"; 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); - err = alsamixer_gethandle(cardname, &handle); + err = alsamixer_gethandle(device, &handle); if (err < 0) { - PyErr_SetString(ALSAAudioError,snd_strerror(err)); + PyErr_SetString(ALSAAudioError, snd_strerror(err)); snd_mixer_close(handle); return NULL; } @@ -991,22 +991,35 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { alsamixer_t *self; int err; - int cardindex = 0; char *control = "Master"; + char *device = "default"; + char hw_device[32]; + int cardidx = -1; int id = 0; snd_mixer_elem_t *elem; int channel; - char *kw[] = { "control", "id", "cardindex", NULL }; + char *kw[] = { "control", "id", "device", "cardindex", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sii", kw, - &control, &id, &cardindex)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sisi", kw, + &control, &id, &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; + } + if (!(self = (alsamixer_t *)PyObject_New(alsamixer_t, &ALSAMixerType))) return NULL; self->handle = 0; - self->cardname = translate_cardidx(cardindex); + self->cardname = strdup(device); err = alsamixer_gethandle(self->cardname, &self->handle); if (err<0) @@ -1019,15 +1032,17 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->controlname = strdup(control); self->controlid = id; - elem = alsamixer_find_elem(self->handle,control,id); + elem = alsamixer_find_elem(self->handle,control, id); if (!elem) { 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->controlid); + self->controlid, + self->cardname); snd_mixer_close(self->handle); PyErr_SetString(ALSAAudioError,errtext); + free(self->cardname); return NULL; } /* Determine mixer capabilities */ @@ -2050,8 +2065,8 @@ static PyTypeObject ALSAMixerType = { /******************************************/ static PyMethodDef alsaaudio_methods[] = { - { "cards", alsacard_list, METH_VARARGS, cards_doc}, - { "mixers", alsamixer_list, METH_VARARGS, mixers_doc}, + { "cards", (PyCFunction)alsacard_list, METH_VARARGS, cards_doc}, + { "mixers", (PyCFunction)alsamixer_list, METH_VARARGS|METH_KEYWORDS, mixers_doc}, { 0, 0 }, }; diff --git a/doc/libalsaaudio.rst b/doc/libalsaaudio.rst index 5336c9b..4e869b5 100644 --- a/doc/libalsaaudio.rst +++ b/doc/libalsaaudio.rst @@ -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). -.. function:: mixers([cardindex]) +.. function:: mixers(device='default', cardindex=-1) - List the available mixers. The optional *cardindex* specifies which card - should be queried. The default is 0. + List the available mixers. The arguments are: + *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:` 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') 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). * *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 are: * *control* - Name of the chosen mixed (default is 'Master'). - * *id* - id of mixer -- More explanation needed here - * *cardindex* specifies which card should be used. + * *id* - id of mixer -- More explanation needed here + *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:` 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 @@ -243,7 +265,7 @@ Mixer Objects 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 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 - *cardindex* - specifies which card should be used [#f3]_. 0 is the - first sound card. - + *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:` 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**:: amixer @@ -471,8 +502,8 @@ or:: >>> import alsaaudio >>> alsaaudio.cards() -mixertest.py accepts the commandline option *-c *. Card -indices start at 0. +mixertest.py accepts the commandline options *-d and +*-c *. playwav.py ~~~~~~~~~~ @@ -502,11 +533,13 @@ Play back the recording with:: 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:: - $ ./mixertest.py - Available mixer controls: + $ ./mixertest.py + Available mixer controls: 'Master' 'Master Mono' 'Headphone' @@ -525,25 +558,34 @@ The output might look like this:: With a single argument - the *control*, it will display the settings of that control; for example:: - $ ./mixertest.py Master - Mixer name: 'Master' - Capabilities: Playback Volume Playback Mute - Channel 0 volume: 61% - Channel 1 volume: 61% + $ ./mixertest.py Master + Mixer name: 'Master' + Capabilities: Playback Volume Playback Mute + Channel 0 volume: 61% + Channel 1 volume: 61% With two arguments, the *control* and a *parameter*, it will set the parameter on the mixer:: - $ ./mixertest.py Master mute + $ ./mixertest.py Master mute This will mute the Master mixer. Or:: - $ ./mixertest.py Master 40 + $ ./mixertest.py Master 40 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 .. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet. diff --git a/doc/pyalsaaudio.rst b/doc/pyalsaaudio.rst index ea584c8..1dc21cd 100644 --- a/doc/pyalsaaudio.rst +++ b/doc/pyalsaaudio.rst @@ -2,10 +2,10 @@ Introduction ************ -:Author: Casper Wilstrup -:Author: Lars Immisch +:Author: Casper Wilstrup +:Author: Lars Immisch -.. |release| replace:: 0.4 +.. |release| replace:: 0.8 .. % At minimum, give your name and an email address. You can include a .. % snail-mail address if you like. diff --git a/mixertest.py b/mixertest.py index 488b562..28fe958 100755 --- a/mixertest.py +++ b/mixertest.py @@ -18,39 +18,35 @@ ## 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 getopt import alsaaudio -def list_mixers(idx=0): - sys.stdout.write("Available mixer controls:\n") - for m in alsaaudio.mixers(idx): - sys.stdout.write(" '%s'\n" % m) +def list_mixers(kwargs): + print("Available mixer controls:") + for m in alsaaudio.mixers(**kwargs): + print(" '%s'\n" % m) -def show_mixer(name, idx=0): +def show_mixer(name, kwargs): # Demonstrates how mixer settings are queried. try: - mixer = alsaaudio.Mixer(name, cardindex=idx) + mixer = alsaaudio.Mixer(name, **kwargs) except alsaaudio.ALSAAudioError: sys.stderr.write("No such mixer\n") sys.exit(1) - sys.stdout.write("Mixer name: '%s'\n" % mixer.mixer()) - sys.stdout.write("Capabilities: %s %s\n" % (' '.join(mixer.volumecap()), - ' '.join(mixer.switchcap()))) + print("Mixer name: '%s'" % mixer.mixer()) + print("Capabilities: %s %s" % (' '.join(mixer.volumecap()), + ' '.join(mixer.switchcap()))) volumes = mixer.getvolume() 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: mutes = mixer.getmute() for i in range(len(mutes)): if mutes[i]: - sys.stdout.write("Channel %i is muted\n" % i) + print("Channel %i is muted" % i) except alsaaudio.ALSAAudioError: # May not support muting pass @@ -59,15 +55,15 @@ def show_mixer(name, idx=0): recs = mixer.getrec() for i in range(len(recs)): if recs[i]: - sys.stdout.write("Channel %i is recording\n" % i) + print("Channel %i is recording" % i) except alsaaudio.ALSAAudioError: # May not support recording pass -def set_mixer(name, args, idx=0): +def set_mixer(name, args, kwargs): # Demonstrates how to set mixer settings try: - mixer = alsaaudio.Mixer(name, cardindex=idx) + mixer = alsaaudio.Mixer(name, **kwargs) except alsaaudio.ALSAAudioError: sys.stderr.write("No such mixer") sys.exit(1) @@ -106,17 +102,19 @@ def usage(): if __name__ == '__main__': - cardindex = 0 - opts, args = getopt.getopt(sys.argv[1:], 'c:?h') + kwargs = {} + opts, args = getopt.getopt(sys.argv[1:], 'c:d:?h') for o, a in opts: if o == '-c': - cardindex = int(a) + kwargs = { 'cardindex': int(a) } + elif o == '-d': + kwargs = { 'device': a } else: usage() if not len(args): - list_mixers(cardindex) + list_mixers(kwargs) elif len(args) == 1: - show_mixer(args[0], cardindex) + show_mixer(args[0], kwargs) else: - set_mixer(args[0], args[1], cardindex) + set_mixer(args[0], args[1], kwargs) diff --git a/test.py b/test.py index b77df79..bc8f19a 100755 --- a/test.py +++ b/test.py @@ -42,13 +42,18 @@ class MixerTest(unittest.TestCase): """Test Mixer objects""" 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 - for i in range(len(alsaaudio.cards())): - mixers = alsaaudio.mixers(i) + mixers = alsaaudio.mixers(**kwargs) + for m in mixers: - mixer = alsaaudio.Mixer(m, cardindex=i) + mixer = alsaaudio.Mixer(m, **kwargs) mixer.close() def testMixerAll(self):