From 17f3b440cc39fa1f61a310e01aa12dbc59a86eaa Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Tue, 6 Apr 2021 09:09:49 +0200 Subject: [PATCH 1/7] Show new functions in recordtest.py --- .gitignore | 5 ++++- recordtest.py | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2adcf2b..bdd931d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ doc/_build/ gh-pages/ build/ dist/ -.vscode/ \ No newline at end of file +.vscode/ +/__pycache__/ +/pyalsaaudio.egg-info/ +*.raw diff --git a/recordtest.py b/recordtest.py index c77646b..6d77518 100755 --- a/recordtest.py +++ b/recordtest.py @@ -53,12 +53,15 @@ if __name__ == '__main__': channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=160, device=device) - loops = 1000000 + print(inp.info()) + help(inp.htimestamp) + + loops = 100000 while loops > 0: loops -= 1 # Read data from device l, data = inp.read() - if l: + print(l, inp.htimestamp(), time.time()) f.write(data) time.sleep(.001) From 3c3f0af74a880c1fd182dd22027c4688412eeef1 Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Tue, 6 Apr 2021 14:31:45 +0200 Subject: [PATCH 2/7] First version enable timestamps --- alsaaudio.c | 24 ++++++++++++++++++++++++ recordtest.py | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/alsaaudio.c b/alsaaudio.c index 61f68ac..ba02604 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -852,6 +852,29 @@ Returns a tuple containing the seconds since epoch in the first element \n\ , nanoseconds in the second element, and number of frames available in \n\ the buffer at the time of the time stamp. \n"); + +static PyObject * +alsapcm_enable_timestamp(alsapcm_t *self, PyObject *args) +{ + + snd_pcm_sw_params_t* swParams; + snd_pcm_sw_params_alloca( &swParams); + + snd_pcm_sw_params_current(self->handle, swParams); + + snd_pcm_sw_params_set_tstamp_mode(self->handle, swParams, SND_PCM_TSTAMP_ENABLE); + snd_pcm_sw_params_set_tstamp_type(self->handle, swParams, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY); + snd_pcm_sw_params(self->handle, swParams); + + return Py_None; +} + + +PyDoc_STRVAR(alsapcm_enable_timestamp_doc, +"enable_timestamp() -> tuple\n\ +\n\ +Hic sunt dragonis \n"); + // auxiliary function @@ -1590,6 +1613,7 @@ static PyMethodDef alsapcm_methods[] = { setperiodsize_doc}, {"htimestamp", (PyCFunction) alsapcm_htimestamp, METH_VARARGS, pcm_htimestamp_doc}, + {"enable_timestamp", (PyCFunction) alsapcm_enable_timestamp, METH_VARARGS, alsapcm_enable_timestamp_doc}, {"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS}, {"info", (PyCFunction)alsapcm_info, METH_VARARGS, pcm_info_doc}, {"getformats", (PyCFunction)alsapcm_getformats, METH_VARARGS, getformats_doc}, diff --git a/recordtest.py b/recordtest.py index 6d77518..c6830f8 100755 --- a/recordtest.py +++ b/recordtest.py @@ -54,8 +54,8 @@ if __name__ == '__main__': periodsize=160, device=device) print(inp.info()) - help(inp.htimestamp) - + # help(inp.htimestamp) + inp.enable_timestamp() loops = 100000 while loops > 0: loops -= 1 From c5f22fd7e07b2c9a3275ccc24cc1e91fe04af162 Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Tue, 6 Apr 2021 22:48:17 +0200 Subject: [PATCH 3/7] Second version enable timestamps --- alsaaudio.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/alsaaudio.c b/alsaaudio.c index ba02604..c4584eb 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -856,6 +856,7 @@ Returns a tuple containing the seconds since epoch in the first element \n\ static PyObject * alsapcm_enable_timestamp(alsapcm_t *self, PyObject *args) { + int err; snd_pcm_sw_params_t* swParams; snd_pcm_sw_params_alloca( &swParams); @@ -864,7 +865,12 @@ alsapcm_enable_timestamp(alsapcm_t *self, PyObject *args) snd_pcm_sw_params_set_tstamp_mode(self->handle, swParams, SND_PCM_TSTAMP_ENABLE); snd_pcm_sw_params_set_tstamp_type(self->handle, swParams, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY); - snd_pcm_sw_params(self->handle, swParams); + err = snd_pcm_sw_params(self->handle, swParams); + + if (err < 0) { + PyErr_SetString(PyExc_RuntimeError, "Unable to set sw params for input capture!"); + return NULL; + } return Py_None; } From ebd2b5359d20e33cadc1bfe24fcdcb6a9c836d15 Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Wed, 7 Apr 2021 11:59:16 +0200 Subject: [PATCH 4/7] Add function to set timestamp mode and type. Add a function to get the alsa version. --- alsaaudio.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 182 insertions(+), 13 deletions(-) diff --git a/alsaaudio.c b/alsaaudio.c index c4584eb..550a562 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -651,7 +651,7 @@ alsapcm_info(alsapcm_t *self, PyObject *args) snd_pcm_hw_params_alloca(&hwparams); snd_pcm_hw_params_current(self->handle,hwparams); - if (!PyArg_ParseTuple(args,":dumpinfo")) + if (!PyArg_ParseTuple(args,":info")) return NULL; if (!self->handle) { @@ -827,6 +827,25 @@ they represent values stored by pyalsaaudio and they are prefixed with ' (call v "); +static PyObject * +alsa_asoundlib_version(PyObject * module, PyObject *args) +{ + PyObject *value; + + if (!PyArg_ParseTuple(args,":asoundlib_version")) + return NULL; + + value=PyUnicode_FromString(snd_asoundlib_version()); + + return value; +} + +PyDoc_STRVAR(asoundlib_version_doc, +"asoundlib_version() -> string\n\ +\n\ +Returns ALSA version string. \n\ +"); + static PyObject * alsapcm_htimestamp(alsapcm_t *self, PyObject *args) { @@ -845,7 +864,7 @@ alsapcm_htimestamp(alsapcm_t *self, PyObject *args) } -PyDoc_STRVAR(pcm_htimestamp_doc, +PyDoc_STRVAR(htimestamp_doc, "htimestamp() -> tuple\n\ \n\ Returns a tuple containing the seconds since epoch in the first element \n\ @@ -854,21 +873,31 @@ Returns a tuple containing the seconds since epoch in the first element \n\ static PyObject * -alsapcm_enable_timestamp(alsapcm_t *self, PyObject *args) +alsapcm_set_tstamp_mode(alsapcm_t *self, PyObject *args) { - int err; + snd_pcm_tstamp_t mode = SND_PCM_TSTAMP_ENABLE; + int err; + + if (!PyArg_ParseTuple(args,"|i:set_tstamp_mode", &mode)) + return NULL; + + if (!self->handle) + { + PyErr_SetString(ALSAAudioError, "PCM device is closed"); + return NULL; + } snd_pcm_sw_params_t* swParams; snd_pcm_sw_params_alloca( &swParams); snd_pcm_sw_params_current(self->handle, swParams); - snd_pcm_sw_params_set_tstamp_mode(self->handle, swParams, SND_PCM_TSTAMP_ENABLE); - snd_pcm_sw_params_set_tstamp_type(self->handle, swParams, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY); + snd_pcm_sw_params_set_tstamp_mode(self->handle, swParams, mode); + err = snd_pcm_sw_params(self->handle, swParams); if (err < 0) { - PyErr_SetString(PyExc_RuntimeError, "Unable to set sw params for input capture!"); + PyErr_SetString(PyExc_RuntimeError, "Unable to set pcm tstamp mode!"); return NULL; } @@ -876,10 +905,131 @@ alsapcm_enable_timestamp(alsapcm_t *self, PyObject *args) } -PyDoc_STRVAR(alsapcm_enable_timestamp_doc, -"enable_timestamp() -> tuple\n\ +PyDoc_STRVAR(set_tstamp_mode_doc, +"set_tstamp_mode() -> None\n\ \n\ -Hic sunt dragonis \n"); +Set the timestamp mode of the device. \n"); + + +static PyObject * +alsapcm_get_tstamp_mode(alsapcm_t *self, PyObject *args) +{ + snd_pcm_tstamp_t mode; + int err; + + + PyObject *value; + + if (!PyArg_ParseTuple(args,":get_tstamp_mode")) + return NULL; + + if (!self->handle) + { + PyErr_SetString(ALSAAudioError, "PCM device is closed"); + return NULL; + } + + snd_pcm_sw_params_t* swParams; + snd_pcm_sw_params_alloca( &swParams); + + snd_pcm_sw_params_current(self->handle, swParams); + + err = snd_pcm_sw_params_get_tstamp_mode(swParams, &mode); + + if (err < 0) { + PyErr_SetString(PyExc_RuntimeError, "Unable to get pcm tstamp mode!"); + return NULL; + } + + value = PyLong_FromUnsignedLong((unsigned long) mode); + return value; +} + + +PyDoc_STRVAR(get_tstamp_mode_doc, +"get_tstamp_mode() -> integer \n\ +\n\ +Get the timestamp mode of the device. \n"); + + +static PyObject * +alsapcm_set_tstamp_type(alsapcm_t *self, PyObject *args) +{ + snd_pcm_tstamp_type_t type = SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY; + int err; + + if (!PyArg_ParseTuple(args,"|i:set_tstamp_type", &type)) + return NULL; + + if (!self->handle) + { + PyErr_SetString(ALSAAudioError, "PCM device is closed"); + return NULL; + } + + snd_pcm_sw_params_t* swParams; + snd_pcm_sw_params_alloca( &swParams); + + snd_pcm_sw_params_current(self->handle, swParams); + + snd_pcm_sw_params_set_tstamp_type(self->handle, swParams, type); + + err = snd_pcm_sw_params(self->handle, swParams); + + if (err < 0) { + PyErr_SetString(PyExc_RuntimeError, "Unable to set pcm tstamp type!"); + return NULL; + } + + return Py_None; +} + + +PyDoc_STRVAR(set_tstamp_type_doc, +"set_tstamp_type() -> None\n\ +\n\ +Set the timestamp type of the device. \n"); + +static PyObject * +alsapcm_get_tstamp_type(alsapcm_t *self, PyObject *args) +{ + snd_pcm_tstamp_type_t type; + int err; + + + PyObject *value; + + if (!PyArg_ParseTuple(args,":get_tstamp_type")) + return NULL; + + if (!self->handle) + { + PyErr_SetString(ALSAAudioError, "PCM device is closed"); + return NULL; + } + + snd_pcm_sw_params_t* swParams; + snd_pcm_sw_params_alloca( &swParams); + + snd_pcm_sw_params_current(self->handle, swParams); + + err = snd_pcm_sw_params_get_tstamp_type(swParams, &type); + + if (err < 0) { + PyErr_SetString(PyExc_RuntimeError, "Unable to get pcm tstamp type!"); + return NULL; + } + + value = PyLong_FromUnsignedLong((unsigned long) type); + return value; +} + + +PyDoc_STRVAR(get_tstamp_type_doc, +"get_tstamp_type() -> int \n\ +\n\ +Get the timestamp type of the device. \n"); + // auxiliary function @@ -1617,9 +1767,11 @@ static PyMethodDef alsapcm_methods[] = { {"setformat", (PyCFunction)alsapcm_setformat, METH_VARARGS, setformat_doc}, {"setperiodsize", (PyCFunction)alsapcm_setperiodsize, METH_VARARGS, setperiodsize_doc}, - {"htimestamp", (PyCFunction) alsapcm_htimestamp, METH_VARARGS, - pcm_htimestamp_doc}, - {"enable_timestamp", (PyCFunction) alsapcm_enable_timestamp, METH_VARARGS, alsapcm_enable_timestamp_doc}, + {"htimestamp", (PyCFunction) alsapcm_htimestamp, METH_VARARGS, htimestamp_doc}, + {"set_tstamp_type", (PyCFunction) alsapcm_set_tstamp_type, METH_VARARGS, set_tstamp_type_doc}, + {"set_tstamp_mode", (PyCFunction) alsapcm_set_tstamp_mode, METH_VARARGS, set_tstamp_mode_doc}, + {"get_tstamp_type", (PyCFunction) alsapcm_get_tstamp_type, METH_VARARGS, get_tstamp_type_doc}, + {"get_tstamp_mode", (PyCFunction) alsapcm_get_tstamp_mode, METH_VARARGS, get_tstamp_mode_doc}, {"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS}, {"info", (PyCFunction)alsapcm_info, METH_VARARGS, pcm_info_doc}, {"getformats", (PyCFunction)alsapcm_getformats, METH_VARARGS, getformats_doc}, @@ -1636,6 +1788,12 @@ static PyMethodDef alsapcm_methods[] = { {NULL, NULL} }; +static PyMethodDef alsa_methods[] = { + {"asoundlib_version", (PyCFunction) alsa_asoundlib_version, METH_VARARGS, asoundlib_version_doc}, + {NULL, NULL} +}; + + #if PY_VERSION_HEX < 0x02020000 static PyObject * alsapcm_getattr(alsapcm_t *self, char *name) { @@ -3063,6 +3221,8 @@ PyObject *PyInit_alsaaudio(void) Py_INCREF(ALSAAudioError); PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError); + PyModule_AddFunctions(m, alsa_methods); + _EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK); _EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE); @@ -3099,6 +3259,15 @@ PyObject *PyInit_alsaaudio(void) _EXPORT_INT(m, "PCM_FORMAT_U24_3LE",SND_PCM_FORMAT_U24_3LE); _EXPORT_INT(m, "PCM_FORMAT_U24_3BE",SND_PCM_FORMAT_U24_3BE); + /* PCM tstamp modes */ + _EXPORT_INT(m, "PCM_TSTAMP_NONE",SND_PCM_TSTAMP_NONE); + _EXPORT_INT(m, "PCM_TSTAMP_ENABLE",SND_PCM_TSTAMP_ENABLE); + + /* PCM tstamp types */ + _EXPORT_INT(m, "PCM_TSTAMP_TYPE_GETTIMEOFDAY",SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY); + _EXPORT_INT(m, "PCM_TSTAMP_TYPE_MONOTONIC",SND_PCM_TSTAMP_TYPE_MONOTONIC); + _EXPORT_INT(m, "PCM_TSTAMP_TYPE_MONOTONIC_RAW",SND_PCM_TSTAMP_TYPE_MONOTONIC_RAW); + /* DSD sample formats are included in ALSA 1.0.29 and higher * define OVERRIDE_DSD_COMPILE to include DSD sample support * if you use a patched ALSA lib version From b8980d992b3b5fdb45f7135a6542d41a4a177acf Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Wed, 7 Apr 2021 12:10:21 +0200 Subject: [PATCH 5/7] Remove recordtestchanges. --- recordtest.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/recordtest.py b/recordtest.py index c6830f8..b9680a5 100755 --- a/recordtest.py +++ b/recordtest.py @@ -53,15 +53,11 @@ if __name__ == '__main__': channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=160, device=device) - print(inp.info()) - # help(inp.htimestamp) - inp.enable_timestamp() - loops = 100000 + loops = 1000000 while loops > 0: loops -= 1 # Read data from device l, data = inp.read() if l: - print(l, inp.htimestamp(), time.time()) f.write(data) time.sleep(.001) From f19af8eba03fb640317c8458759080de4b58abed Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Wed, 7 Apr 2021 12:11:14 +0200 Subject: [PATCH 6/7] Remove recordtestchanges. --- recordtest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/recordtest.py b/recordtest.py index b9680a5..62d13ff 100755 --- a/recordtest.py +++ b/recordtest.py @@ -58,6 +58,7 @@ if __name__ == '__main__': loops -= 1 # Read data from device l, data = inp.read() + if l: f.write(data) time.sleep(.001) From c8f3916337c3067f7ef2d909e6ef04b392007ac1 Mon Sep 17 00:00:00 2001 From: Ronald van Elburg Date: Wed, 7 Apr 2021 23:30:24 +0200 Subject: [PATCH 7/7] On phys_from_sound: Small memory management fixes and code simplification. And add documentation on new functionality. --- alsaaudio.c | 20 +++++------------ doc/libalsaaudio.rst | 53 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/alsaaudio.c b/alsaaudio.c index 550a562..ce9b610 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -830,14 +830,10 @@ they represent values stored by pyalsaaudio and they are prefixed with ' (call v static PyObject * alsa_asoundlib_version(PyObject * module, PyObject *args) { - PyObject *value; - if (!PyArg_ParseTuple(args,":asoundlib_version")) return NULL; - value=PyUnicode_FromString(snd_asoundlib_version()); - - return value; + return PyUnicode_FromString(snd_asoundlib_version()); } PyDoc_STRVAR(asoundlib_version_doc, @@ -901,6 +897,7 @@ alsapcm_set_tstamp_mode(alsapcm_t *self, PyObject *args) return NULL; } + Py_INCREF(Py_None); return Py_None; } @@ -917,9 +914,6 @@ alsapcm_get_tstamp_mode(alsapcm_t *self, PyObject *args) snd_pcm_tstamp_t mode; int err; - - PyObject *value; - if (!PyArg_ParseTuple(args,":get_tstamp_mode")) return NULL; @@ -941,8 +935,7 @@ alsapcm_get_tstamp_mode(alsapcm_t *self, PyObject *args) return NULL; } - value = PyLong_FromUnsignedLong((unsigned long) mode); - return value; + return PyLong_FromUnsignedLong((unsigned long) mode); } @@ -981,6 +974,7 @@ alsapcm_set_tstamp_type(alsapcm_t *self, PyObject *args) return NULL; } + Py_INCREF(Py_None); return Py_None; } @@ -996,9 +990,6 @@ alsapcm_get_tstamp_type(alsapcm_t *self, PyObject *args) snd_pcm_tstamp_type_t type; int err; - - PyObject *value; - if (!PyArg_ParseTuple(args,":get_tstamp_type")) return NULL; @@ -1020,8 +1011,7 @@ alsapcm_get_tstamp_type(alsapcm_t *self, PyObject *args) return NULL; } - value = PyLong_FromUnsignedLong((unsigned long) type); - return value; + return PyLong_FromUnsignedLong((unsigned long) type); } diff --git a/doc/libalsaaudio.rst b/doc/libalsaaudio.rst index c9b9c5e..4e9a4e9 100644 --- a/doc/libalsaaudio.rst +++ b/doc/libalsaaudio.rst @@ -97,6 +97,9 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA. changed. Since 0.8, this functions returns the mixers for the default device, not the mixers for the first card. +.. function:: asoundlib_version() + + Return a Python string containing the ALSA version found. .. _pcm-objects: @@ -260,6 +263,56 @@ PCM objects have the following methods: The *eventmask* value is compatible with `poll.register`__ in the Python :const:`select` module. +.. method:: PCM.set_tstamp_mode([mode=PCM_TSTAMP_ENABLE]) + + Set the ALSA timestamp mode on the device. The mode argument can be set to + either :const:`PCM_TSTAMP_NONE` or :const:`PCM_TSTAMP_ENABLE`. + +.. method:: PCM.get_tstamp_mode() + + Return the integer value corresponding to the ALSA timestamp mode. The + return value can be either :const:`PCM_TSTAMP_NONE` or :const:`PCM_TSTAMP_ENABLE`. + +.. method:: PCM.set_tstamp_type([type=PCM_TSTAMP_TYPE_GETTIMEOFDAY]) + + Set the ALSA timestamp mode on the device. The type argument + can be set to either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`, + :const:`PCM_TSTAMP_TYPE_MONOTONIC` or :const:`PCM_TSTAMP_TYPE_MONOTONIC_RAW`. + +.. method:: PCM.get_tstamp_type() + + Return the integer value corresponding to the ALSA timestamp type. The + return value can be either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`, + :const:`PCM_TSTAMP_TYPE_MONOTONIC` or :const:`PCM_TSTAMP_TYPE_MONOTONIC_RAW`. + +.. method:: PCM.htimestamp() + + Return a Python tuple *(seconds, nanoseconds, frames_available_in_buffer)*. + + The type of output is controlled by the tstamp_type, as described in the table below. + + ================================= =========================================== + Timestamp Type Description + ================================= =========================================== + ``PCM_TSTAMP_TYPE_GETTIMEOFDAY`` System-wide realtime clock with seconds + since epoch. + ``PCM_TSTAMP_TYPE_MONOTONIC`` Monotonic time from an unspecified starting + time. Progress is NTP synchronized. + ``PCM_TSTAMP_TYPE_MONOTONIC_RAW`` Monotonic time from an unspecified starting + time using only the system clock. + ================================= =========================================== + + The timestamp mode is controlled by the tstamp_mode, as described in the table below. + + ================================= =========================================== + Timestamp Mode Description + ================================= =========================================== + ``PCM_TSTAMP_NONE`` No timestamp. + ``PCM_TSTAMP_ENABLE`` Update timestamp at every hardware position + update. + ================================= =========================================== + + __ poll_objects_ **A few hints on using PCM devices for playback**