diff --git a/src/alsaaudio.c b/src/alsaaudio.c index 017e0fb..25472a4 100644 --- a/src/alsaaudio.c +++ b/src/alsaaudio.c @@ -1448,6 +1448,114 @@ alsapcm_read(alsapcm_t *self, PyObject *args) return tuple_obj; } +static PyObject * +alsapcm_read_sw(alsapcm_t *self, PyObject *args) +{ + snd_pcm_state_t state; + int res; + int max_frames_to_read; + int size; + int sizeout = 0; + PyObject *buffer_obj, *tuple_obj, *res_obj; + char *buffer; + + if (!PyArg_ParseTuple(args,"i:read_sw", &max_frames_to_read)) + return NULL; + + if (!self->handle) { + PyErr_SetString(ALSAAudioError, "PCM device is closed"); + return NULL; + } + + if (self->pcmtype != SND_PCM_STREAM_CAPTURE) + { + PyErr_Format(ALSAAudioError, "Cannot read from playback PCM [%s]", + self->cardname); + return NULL; + } + + size = self->framesize * max_frames_to_read; + +#if PY_MAJOR_VERSION < 3 + buffer_obj = PyString_FromStringAndSize(NULL, size); + if (!buffer_obj) + return NULL; + buffer = PyString_AS_STRING(buffer_obj); +#else + buffer_obj = PyBytes_FromStringAndSize(NULL, size); + if (!buffer_obj) + return NULL; + buffer = PyBytes_AS_STRING(buffer_obj); +#endif + + // After drop() and drain(), we need to prepare the stream again. + // Note that fresh streams are already prepared by snd_pcm_hw_params(). + state = snd_pcm_state(self->handle); + if ((state != SND_PCM_STATE_SETUP) || + !(res = snd_pcm_prepare(self->handle))) { + + Py_BEGIN_ALLOW_THREADS + res = snd_pcm_readi(self->handle, buffer, max_frames_to_read); + Py_END_ALLOW_THREADS + + if (res == -EPIPE) { + // This means buffer overrun, which we need to report. + // However, we recover the stream, so the next PCM.read() will work + // again. If recovery fails (very unlikely), report that instead. + if (!(res = snd_pcm_prepare(self->handle))) + res = -EPIPE; + } + } + + if (res != -EPIPE) + { + if (res == -EAGAIN) + { + res = 0; + } + else if (res < 0) { + PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), + self->cardname); + + Py_DECREF(buffer_obj); + return NULL; + } + else + { + sizeout = res * self->framesize; + } + } + + if (size != sizeout) { +#if PY_MAJOR_VERSION < 3 + /* If the following fails, it will free the object */ + if (_PyString_Resize(&buffer_obj, sizeout)) + return NULL; +#else + /* If the following fails, it will free the object */ + if (_PyBytes_Resize(&buffer_obj, sizeout)) + return NULL; +#endif + } + + res_obj = PyLong_FromLong(res); + if (!res_obj) { + Py_DECREF(buffer_obj); + return NULL; + } + tuple_obj = PyTuple_New(2); + if (!tuple_obj) { + Py_DECREF(buffer_obj); + Py_DECREF(res_obj); + return NULL; + } + /* Steal reference counts */ + PyTuple_SET_ITEM(tuple_obj, 0, res_obj); + PyTuple_SET_ITEM(tuple_obj, 1, buffer_obj); + + return tuple_obj; +} + static PyObject *alsapcm_write(alsapcm_t *self, PyObject *args) { int datalen; @@ -1767,6 +1875,7 @@ static PyMethodDef alsapcm_methods[] = { {"getratebounds", (PyCFunction)alsapcm_getratemaxmin, METH_VARARGS}, {"getrates", (PyCFunction)alsapcm_getrates, METH_VARARGS}, {"read", (PyCFunction)alsapcm_read, METH_VARARGS}, + {"read_sw", (PyCFunction)alsapcm_read_sw, METH_VARARGS}, {"write", (PyCFunction)alsapcm_write, METH_VARARGS}, {"avail", (PyCFunction)alsapcm_avail, METH_VARARGS}, {"pause", (PyCFunction)alsapcm_pause, METH_VARARGS},