diff --git a/alsaaudio.c b/alsaaudio.c index 49d903f..ad86dc8 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -69,28 +69,6 @@ typedef struct { snd_mixer_t *handle; } alsamixer_t; -/* Translate a card id to a ALSA cardname - - Returns a newly allocated string. -*/ -char *translate_cardname(char *name) -{ - static char dflt[] = "default"; - char *full = NULL; - - if (!name || !strcmp(name, dflt)) - return strdup(dflt); - - // If we find a colon, we assume it is a real ALSA cardname - if (strchr(name, ':')) - return strdup(name); - - full = malloc(strlen("default:CARD=") + strlen(name) + 1); - sprintf(full, "default:CARD=%s", name); - - return full; -} - /******************************************/ /* PCM object wrapper */ /******************************************/ @@ -152,6 +130,61 @@ PyDoc_STRVAR(cards_doc, \n\ List the available card ids."); +static PyObject * +alsapcm_list(PyObject *self, PyObject *args) +{ + int pcmtype = SND_PCM_STREAM_PLAYBACK; + PyObject *result = NULL; + PyObject *item; + void **hints, **n; + char *name, *io; + const char *filter; + + if (!PyArg_ParseTuple(args,"|i:pcms", &pcmtype)) + return NULL; + + if (pcmtype != SND_PCM_STREAM_PLAYBACK && + pcmtype != SND_PCM_STREAM_CAPTURE) + { + PyErr_SetString(ALSAAudioError, "PCM type must be PCM_PLAYBACK (0) " + "or PCM_CAPTURE (1)"); + return NULL; + } + + result = PyList_New(0); + + if (snd_device_name_hint(-1, "pcm", &hints) < 0) + return result; + + n = hints; + filter = pcmtype == SND_PCM_STREAM_CAPTURE ? "Input" : "Output"; + while (*n != NULL) { + name = snd_device_name_get_hint(*n, "NAME"); + io = snd_device_name_get_hint(*n, "IOID"); + if (io != NULL && strcmp(io, filter) != 0) + goto __end; + + item = PyUnicode_FromString(name); + PyList_Append(result, item); + Py_DECREF(item); + + __end: + if (name != NULL) + free(name); + if (io != NULL) + free(io); + n++; + } + snd_device_name_free_hint(hints); + + return result; +} + +PyDoc_STRVAR(pcms_doc, +"pcms([type])\n\ +\n\ +List the available PCM devices"); + static int alsapcm_setup(alsapcm_t *self) { int res,dir; @@ -210,12 +243,25 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds) alsapcm_t *self; int pcmtype = SND_PCM_STREAM_PLAYBACK; int pcmmode = 0; - char *kw[] = { "type", "mode", "card", NULL }; - char *cardname = NULL; + char *device = "default"; + int cardidx = -1; + char hw_device[32]; + char *kw[] = { "type", "mode", "device", "cardindex", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiz", kw, - &pcmtype, &pcmmode, &cardname)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iisi", kw, + &pcmtype, &pcmmode, &device, &cardidx)) return NULL; + + if (cardidx >= 0) { + if (cardidx < 32) { + snprintf(hw_device, sizeof(hw_device), "hw:%d", cardidx); + device = hw_device; + } + else { + PyErr_SetString(ALSAAudioError, "Invalid card number"); + return NULL; + } + } if (!(self = (alsapcm_t *)PyObject_New(alsapcm_t, &ALSAPCMType))) return NULL; @@ -234,21 +280,22 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self->handle = 0; self->pcmtype = pcmtype; self->pcmmode = pcmmode; - self->cardname = translate_cardname(cardname); self->channels = 2; self->rate = 44100; self->format = SND_PCM_FORMAT_S16_LE; self->periodsize = 32; - - res = snd_pcm_open(&(self->handle), self->cardname, self->pcmtype, + + res = snd_pcm_open(&(self->handle), device, self->pcmtype, self->pcmmode); - - if (res >= 0) + + if (res >= 0) { res = alsapcm_setup(self); - - if (res < 0) - { - if (self->handle) + } + if (res >= 0) { + self->cardname = strdup(device); + } + else { + if (self->handle) { snd_pcm_close(self->handle); self->handle = 0; @@ -933,21 +980,21 @@ alsamixer_list(PyObject *self, PyObject *args, PyObject *kwds) char *device = "default"; PyObject *result; - char *kw[] = { "cardindex", "device", NULL }; + char *kw[] = { "device", "cardindex", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|is", kw, - &cardidx, &device)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|si", kw, + &device, &cardidx)) return NULL; if (cardidx >= 0) { - if (cardidx >= 0 && cardidx < 32) { + if (cardidx < 32) { snprintf(hw_device, sizeof(hw_device), "hw:%d", cardidx); + device = hw_device; } else { PyErr_SetString(ALSAAudioError, "Invalid card number"); return NULL; } - device = hw_device; } snd_mixer_selem_id_alloca(&sid); @@ -1006,37 +1053,36 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) int id = 0; snd_mixer_elem_t *elem; int channel; - char *kw[] = { "control", "id", "cardindex", "device", NULL }; + char *kw[] = { "control", "id", "device", "cardindex", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|siis", kw, - &control, &id, &cardidx, &device)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sisi", kw, + &control, &id, &device, &cardidx)) return NULL; if (cardidx >= 0) { - if (cardidx >= 0 && cardidx < 32) { + if (cardidx < 32) { snprintf(hw_device, sizeof(hw_device), "hw:%d", cardidx); + device = hw_device; } 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 = strdup(device); - err = alsamixer_gethandle(self->cardname, &self->handle); - if (err<0) + err = alsamixer_gethandle(device, &self->handle); + if (err < 0) { PyErr_SetString(ALSAAudioError,snd_strerror(err)); - free(self->cardname); return NULL; } - + + self->cardname = strdup(device); self->controlname = strdup(control); self->controlid = id; @@ -1044,13 +1090,15 @@ alsamixer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!elem) { char errtext[128]; - sprintf(errtext,"Unable to find mixer control '%s',%i on card '%s'", - self->controlname, - self->controlid, - self->cardname); + snprintf(errtext, sizeof(errtext), + "Unable to find mixer control '%s',%i on card '%s'", + self->controlname, + self->controlid, + self->cardname); snd_mixer_close(self->handle); PyErr_SetString(ALSAAudioError,errtext); free(self->cardname); + free(self->controlname); return NULL; } /* Determine mixer capabilities */ @@ -2074,6 +2122,7 @@ static PyTypeObject ALSAMixerType = { static PyMethodDef alsaaudio_methods[] = { { "cards", (PyCFunction)alsacard_list, METH_VARARGS, cards_doc}, + { "pcms", (PyCFunction)alsapcm_list, METH_VARARGS, pcms_doc}, { "mixers", (PyCFunction)alsamixer_list, METH_VARARGS|METH_KEYWORDS, mixers_doc}, { 0, 0 }, }; diff --git a/doc/libalsaaudio.rst b/doc/libalsaaudio.rst index 9fa9b75..37ecd40 100644 --- a/doc/libalsaaudio.rst +++ b/doc/libalsaaudio.rst @@ -34,34 +34,43 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA. .. % should be enclosed in \var{...}. +.. function:: pcms([type=PCM_PLAYBACK]) + + List available PCM objects by name (the suitable for PCM objects). + .. function:: cards() - List the available cards by name (suitable for PCM objects). + List the available cards by name. -.. function:: mixers(cardindex=-1, device='default') +.. function:: mixers(device='default', cardindex=-1) List the available mixers. The arguments are: - *cardindex* - specifies which card should be used [#f3]_. If this argument + * *device* - the name of the device on which the mixer resides. The default + is 'default'. + + * *cardindex* - the card index [#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. - - *device* - the name of the device on which the mixer resides. The default is - 'default'. + first hardware sound card. **Note:** The arguments for this function were extended in version 0.8. The keyword argument `device` is new and can be used to select virtual devices. -.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, card='default') +.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', + cardindex=-1) This class is used to represent a PCM device (both for playback and recording - capture). The arguments are: * *type* - can be either ``PCM_CAPTURE`` or ``PCM_PLAYBACK`` (default). * *mode* - can be either ``PCM_NONBLOCK``, or ``PCM_NORMAL`` (default). - * *card* - specifies the name of the card that should be used. + * *device* - the name of the PCM device that should be used. + * *cardindex* - the card index [#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 hardware sound card. .. class:: Mixer(control='Master', id=0, cardindex=-1, device='default') diff --git a/test.py b/test.py index 48cfcf4..69fedeb 100755 --- a/test.py +++ b/test.py @@ -44,7 +44,7 @@ class MixerTest(unittest.TestCase): def testMixer(self): """Open the default Mixers and the Mixers on every card""" - for d in ['default'] + range(len(alsaaudio.cards())): + for d in ['default'] + list(range(len(alsaaudio.cards()))): if type(d) == type(0): kwargs = { 'cardindex': d } else: @@ -90,10 +90,14 @@ class PCMTest(unittest.TestCase): """Test PCM objects""" def testPCM(self): - "Open a PCM object on every card" + "Open a PCM object on every device" - for i in range(len(alsaaudio.cards())): - pcm = alsaaudio.PCM(i) + for device in alsaaudio.pcms(): + pcm = alsaaudio.PCM(device=device) + pcm.close() + + for device in alsaaudio.pcms(alsaaudio.PCM_CAPTURE): + pcm = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, device=device) pcm.close() def testPCMAll(self):