From b3d11582e03df6929b2e7acbaa1306afc7b8a6bc Mon Sep 17 00:00:00 2001
From: Pbopbo
Date: Thu, 2 Apr 2026 17:27:53 +0200
Subject: [PATCH] Adds read_sw function to be able to read any size of frames.
---
src/alsaaudio.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 109 insertions(+)
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},