diff --git a/README b/README new file mode 100644 index 0000000..ca3b76d --- /dev/null +++ b/README @@ -0,0 +1,50 @@ +PyAlsaAudio +=========== + +Author: Casper Wilstrup (cwi@unispeed.dk) + +This package contains wrappers for acessing the ALSA api from Python. It +is currently fairly complete for PCM devices. My next goal is to have +complete mixer supports as well. MIDI sequencer support is low on my +priority list, but volunteers are welcome. + +If you find bugs in the wrappers please notify me on email. Please +don't send bug reports regarding ALSA specifically. There are several +bugs in this api, and those should be reported to the ALSA team - not +me. + +This software is licensed under the PSF license - the same one used +by the majority of the python distribution. Basically you can use it +for anything you wish (even commercial purposes). There is no warranty +whatsoever. + + +Installation +============ + +Note: the wrappers link with the alsasound library alsa (from the alsa-lib +package). Verify that this is installed by looking for /usr/lib/libasound.so +before building. Naturally you also need to use a kernel with proper ALSA +support. This is the default in Linux kernel 2.6 and later. If you are using +kernel version 2.4 you may need to install the ALSA patches yourself - althoug +most distributions ship with ALSA kernels. + +To install, execute the following: + $ python setup.py build + +And then as root: + # python setup.py install + + +Using the API +============= + +There are two example programs included with the source: +'playbacktest.py' which plays back raw sound data read from +stdin + +'recordtest.py' which captures sound from the microphone at writes +it raw to stdout. + +In a future version, I'll include documentation with the package, but +for now, you're stuck with the examples. diff --git a/TODO b/TODO new file mode 100644 index 0000000..e12a0b5 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +- Implement the Mixer API + +- Implement MIDI/sequencer support. diff --git a/alsaaudio.c b/alsaaudio.c new file mode 100644 index 0000000..6086c43 --- /dev/null +++ b/alsaaudio.c @@ -0,0 +1,459 @@ +/* + * alsaaudio -- Python interface to ALSA (Advanced Linux Sound Architecture). + * The standard audio API for Linux since kernel 2.0 + * + * Contributed by Unispeed A/S (http://www.unispeed.com) + * Primary author: Casper Wilstup (cwi@unispeed.dk) + * + * + * (c) 2004 by Unispeed A/S. All rights reserved. + * + * License: CRNI's Python 1.6 license (or any later version of the same) + * + */ + +#include "Python.h" +#include +#include + + +typedef struct { + PyObject_HEAD; + int pcmtype; + int pcmmode; + char *pcmname; + + snd_pcm_t *handle; + + // Configurable parameters + int channels; + int rate; + int format; + snd_pcm_uframes_t periodsize; + int framesize; + +} alsapcm_t; + +static PyTypeObject ALSAAudioType; + +static PyObject *ALSAAudioError; + +static int alsapcm_setup(alsapcm_t *self) { + int res,dir; + unsigned int val; + snd_pcm_hw_params_t *hwparams; + snd_pcm_uframes_t frames; + + if (self->handle) { + snd_pcm_close(self->handle); + self->handle = 0; + } + res = snd_pcm_open(&(self->handle),self->pcmname,self->pcmtype,self->pcmmode); + if (res < 0) return res; + + snd_pcm_hw_params_alloca(&hwparams); + res = snd_pcm_hw_params_any(self->handle, hwparams); + if (res < 0) return res; + + snd_pcm_hw_params_alloca(&hwparams); + + /* Fill it in with default values. */ + snd_pcm_hw_params_any(self->handle, hwparams); + snd_pcm_hw_params_set_access(self->handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(self->handle, hwparams, self->format); + snd_pcm_hw_params_set_channels(self->handle, hwparams, self->channels); + dir = 0; + snd_pcm_hw_params_set_rate(self->handle, hwparams, self->rate, dir); + snd_pcm_hw_params_set_period_size(self->handle, hwparams, self->periodsize, dir); + snd_pcm_hw_params_set_periods(self->handle,hwparams,4,0); + + res = snd_pcm_hw_params(self->handle, hwparams); + + self->framesize = self->channels * snd_pcm_hw_params_get_sbits(hwparams)/8; + return res; +} + +/* ALSA PCM Object members */ +static alsapcm_t * +newalsapcmobject(PyObject *args) { + int res; + alsapcm_t *self; + int pcmtype=0; + int pcmmode=0; + char *pcmname = "default"; + if (!PyArg_ParseTuple(args,"|iis",&pcmtype,&pcmmode,&pcmname)) return NULL; + if (!(self = (alsapcm_t *)PyObject_New(struct alsa_audio, &ALSAAudioType))) 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_CAPTUPE (1)"); + return NULL; + } + if (pcmmode < 0 || pcmmode > SND_PCM_ASYNC) { + PyErr_SetString(ALSAAudioError, "Invalid PCM mode"); + return NULL; + } + self->pcmtype = pcmtype; + self->pcmmode = pcmmode; + self->pcmname = strdup(pcmname); + + self->channels = 2; + self->rate = 44100; + self->format = SND_PCM_FORMAT_S16_LE; + self->periodsize = 32; + + self->handle = 0; + res = alsapcm_setup(self); + if (res < 0) { + if (self->handle) { + snd_pcm_close(self->handle); + self->handle = 0; + } + PyErr_SetString(ALSAAudioError, snd_strerror(res)); + return NULL; + } + return self; +} + +static void alsapcm_dealloc(alsapcm_t *self) { + if (self->handle) { + snd_pcm_drain(self->handle); + snd_pcm_close(self->handle); + } + PyObject_Del(self); +} + +static PyObject * +alsapcm_dumpinfo(alsapcm_t *self, PyObject *args) { + unsigned int val,val2; + int dir; + snd_pcm_uframes_t frames; + snd_pcm_hw_params_t *hwparams; + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_hw_params_current(self->handle,hwparams); + + + if (!PyArg_ParseTuple(args,"")) return NULL; + + printf("PCM handle name = '%s'\n", snd_pcm_name(self->handle)); + printf("PCM state = %s\n", snd_pcm_state_name(snd_pcm_state(self->handle))); + + snd_pcm_hw_params_get_access(hwparams, (snd_pcm_access_t *) &val); + printf("access type = %s\n", snd_pcm_access_name((snd_pcm_access_t)val)); + + snd_pcm_hw_params_get_format(hwparams, &val); + printf("format = '%s' (%s)\n", + snd_pcm_format_name((snd_pcm_format_t)val), + snd_pcm_format_description((snd_pcm_format_t)val)); + + snd_pcm_hw_params_get_subformat(hwparams, (snd_pcm_subformat_t *)&val); + printf("subformat = '%s' (%s)\n", + snd_pcm_subformat_name((snd_pcm_subformat_t)val), + snd_pcm_subformat_description((snd_pcm_subformat_t)val)); + + snd_pcm_hw_params_get_channels(hwparams, &val); + printf("channels = %d\n", val); + + snd_pcm_hw_params_get_rate(hwparams, &val, &dir); + printf("rate = %d bps\n", val); + + snd_pcm_hw_params_get_period_time(hwparams, &val, &dir); + printf("period time = %d us\n", val); + + snd_pcm_hw_params_get_period_size(hwparams, &frames, &dir); + printf("period size = %d frames\n", (int)frames); + + snd_pcm_hw_params_get_buffer_time(hwparams, &val, &dir); + printf("buffer time = %d us\n", val); + + snd_pcm_hw_params_get_buffer_size(hwparams, (snd_pcm_uframes_t *) &val); + printf("buffer size = %d frames\n", val); + + snd_pcm_hw_params_get_periods(hwparams, &val, &dir); + printf("periods per buffer = %d frames\n", val); + + snd_pcm_hw_params_get_rate_numden(hwparams, &val, &val2); + printf("exact rate = %d/%d bps\n", val, val2); + + val = snd_pcm_hw_params_get_sbits(hwparams); + printf("significant bits = %d\n", val); + + snd_pcm_hw_params_get_tick_time(hwparams, &val, &dir); + printf("tick time = %d us\n", val); + + val = snd_pcm_hw_params_is_batch(hwparams); + printf("is batch = %d\n", val); + + val = snd_pcm_hw_params_is_block_transfer(hwparams); + printf("is block transfer = %d\n", val); + + val = snd_pcm_hw_params_is_double(hwparams); + printf("is double = %d\n", val); + + val = snd_pcm_hw_params_is_half_duplex(hwparams); + printf("is half duplex = %d\n", val); + + val = snd_pcm_hw_params_is_joint_duplex(hwparams); + printf("is joint duplex = %d\n", val); + + val = snd_pcm_hw_params_can_overrange(hwparams); + printf("can overrange = %d\n", val); + + val = snd_pcm_hw_params_can_mmap_sample_resolution(hwparams); + printf("can mmap = %d\n", val); + + val = snd_pcm_hw_params_can_pause(hwparams); + printf("can pause = %d\n", val); + + val = snd_pcm_hw_params_can_resume(hwparams); + printf("can resume = %d\n", val); + + val = snd_pcm_hw_params_can_sync_start(hwparams); + printf("can sync start = %d\n", val); + + Py_INCREF(Py_None); + return Py_None; + +} + +static PyObject * +alsapcm_pcmtype(alsapcm_t *self, PyObject *args) { + if (!PyArg_ParseTuple(args,"")) return NULL; + return PyInt_FromLong(self->pcmtype); +} + +static PyObject * +alsapcm_pcmmode(alsapcm_t *self, PyObject *args) { + if (!PyArg_ParseTuple(args,"")) return NULL; + return PyInt_FromLong(self->pcmmode); +} + +static PyObject * +alsapcm_pcmname(alsapcm_t *self, PyObject *args) { + if (!PyArg_ParseTuple(args,"")) return NULL; + return PyString_FromString(self->pcmname); +} + +static PyObject * +alsapcm_setchannels(alsapcm_t *self, PyObject *args) { + int channels; + int res; + if (!PyArg_ParseTuple(args,"i",&channels)) return NULL; + self->channels = channels; + res = alsapcm_setup(self); + if (res < 0) { + PyErr_SetString(ALSAAudioError, snd_strerror(res)); + return NULL; + } + return PyInt_FromLong(channels); +} + +static PyObject * +alsapcm_setrate(alsapcm_t *self, PyObject *args) { + int rate; + int res; + if (!PyArg_ParseTuple(args,"i",&rate)) return NULL; + self->rate = rate; + res = alsapcm_setup(self); + if (res < 0) { + PyErr_SetString(ALSAAudioError, snd_strerror(res)); + return NULL; + } + return PyInt_FromLong(self->rate); +} + +static PyObject * +alsapcm_setformat(alsapcm_t *self, PyObject *args) { + int format; + int res; + if (!PyArg_ParseTuple(args,"i",&format)) return NULL; + self->format = format; + res = alsapcm_setup(self); + if (res < 0) { + PyErr_SetString(ALSAAudioError, snd_strerror(res)); + return NULL; + } + return PyInt_FromLong(self->format); +} + +static PyObject * +alsapcm_setperiodsize(alsapcm_t *self, PyObject *args) { + int periodsize; + int res; + if (!PyArg_ParseTuple(args,"i",&periodsize)) return NULL; + self->periodsize = periodsize; + res = alsapcm_setup(self); + if (res < 0) { + PyErr_SetString(ALSAAudioError, snd_strerror(res)); + return NULL; + } + return PyInt_FromLong(self->periodsize); +} + + +static PyObject * +alsapcm_read(alsapcm_t *self, PyObject *args) { + int res; + int val,dir; + char buffer[8000]; + snd_pcm_hw_params_t *hwparams; + + if (self->framesize * self->periodsize > 8000) { + PyErr_SetString(ALSAAudioError,"Capture data too large. Try decreasing period size"); + return NULL; + } + + if (!PyArg_ParseTuple(args,"")) return NULL; + if (self->pcmtype != SND_PCM_STREAM_CAPTURE) { + PyErr_SetString(ALSAAudioError,"Cannot read from playback PCM"); + return NULL; + } + + res = snd_pcm_readi(self->handle, buffer, self->periodsize); + if (res == -EPIPE) { + /* EPIPE means overrun */ + snd_pcm_prepare(self->handle); + } + else if (res == -EAGAIN) { + res = 0; + } + else if (res < 0) { + PyErr_SetString(ALSAAudioError,snd_strerror(res)); + return NULL; + } + + return Py_BuildValue("is#",res,buffer,res*self->framesize); +} + +static PyObject *alsapcm_write(alsapcm_t *self, PyObject *args) { + char *data; + int datalen; + int res; + if (!PyArg_ParseTuple(args,"s#",&data,&datalen)) return NULL; + if (datalen%self->framesize) { + PyErr_SetString(ALSAAudioError,"Data size must be a multipla of framesize"); + return NULL; + } + res = snd_pcm_writei(self->handle, data, datalen/self->framesize); + if (res == -EPIPE) { + /* EPIPE means underrun */ + snd_pcm_prepare(self->handle); + snd_pcm_writei(self->handle, data, datalen/self->framesize); + snd_pcm_writei(self->handle, data, datalen/self->framesize); + } + else if (res == -EAGAIN) { + return PyInt_FromLong(0); + } + else if (res < 0) { + PyErr_SetString(ALSAAudioError,snd_strerror(res)); + return NULL; + } + return PyInt_FromLong(datalen); +} + +/* Module functions */ + +static PyObject * +alsa_openpcm(PyObject *self, PyObject *args) { + return (PyObject *)newalsapcmobject(args); +} + + +/* ALSA PCM Object Bureaucracy */ + +static PyMethodDef alsapcm_methods[] = { + {"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS}, + {"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS}, + {"pcmname", (PyCFunction)alsapcm_pcmname, METH_VARARGS}, + {"setchannels", (PyCFunction)alsapcm_setchannels, METH_VARARGS}, + {"setrate", (PyCFunction)alsapcm_setrate, METH_VARARGS}, + {"setformat", (PyCFunction)alsapcm_setformat, METH_VARARGS}, + {"setperiodsize", (PyCFunction)alsapcm_setperiodsize, METH_VARARGS}, + + {"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS}, + + {"read", (PyCFunction)alsapcm_read, METH_VARARGS}, + {"write", (PyCFunction)alsapcm_write, METH_VARARGS}, + + {NULL, NULL} +}; + +static PyObject * +alsapcm_getattr(alsapcm_t *self, char *name) { + return Py_FindMethod(alsapcm_methods, (PyObject *)self, name); +} + +static PyTypeObject ALSAAudioType = { + PyObject_HEAD_INIT(&PyType_Type) + 0, + "alsaaudio.pcm", + sizeof(alsapcm_t), + 0, + /* methods */ + (destructor) alsapcm_dealloc, + 0, + (getattrfunc)alsapcm_getattr, + 0, + 0, + 0, +}; + + + +/* Initialization */ + +static PyMethodDef alsaaudio_methods[] = { + { "openpcm", alsa_openpcm, METH_VARARGS }, + { 0, 0 }, +}; + +#define _EXPORT_CONSTANT(mod, name) \ + if (PyModule_AddIntConstant(mod, #name, (long) (name)) == -1) return; +#define _EXPORT_INT(mod, name, value) \ + if (PyModule_AddIntConstant(mod, name, (long) value) == -1) return; + +void initalsaaudio(void) { + PyObject *m; + m = Py_InitModule("alsaaudio",alsaaudio_methods); + + ALSAAudioError = PyErr_NewException("alsaaudio.ALSAAudioError", NULL, NULL); + if (ALSAAudioError) { + /* Each call to PyModule_AddObject decrefs it; compensate: */ + Py_INCREF(ALSAAudioError); + Py_INCREF(ALSAAudioError); + PyModule_AddObject(m, "error", ALSAAudioError); + PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError); + } + + + _EXPORT_INT(m,"PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK); + _EXPORT_INT(m,"PCM_CAPTURE",SND_PCM_STREAM_CAPTURE); + + _EXPORT_INT(m,"PCM_NONBLOCK",SND_PCM_NONBLOCK); + _EXPORT_INT(m,"PCM_ASYNC",SND_PCM_ASYNC); + + /* PCM Formats */ + _EXPORT_INT(m,"PCM_FORMAT_S8",SND_PCM_FORMAT_S8); + _EXPORT_INT(m,"PCM_FORMAT_U8",SND_PCM_FORMAT_U8); + _EXPORT_INT(m,"PCM_FORMAT_S16_LE",SND_PCM_FORMAT_S16_LE); + _EXPORT_INT(m,"PCM_FORMAT_S16_BE",SND_PCM_FORMAT_S16_BE); + _EXPORT_INT(m,"PCM_FORMAT_U16_LE",SND_PCM_FORMAT_U16_LE); + _EXPORT_INT(m,"PCM_FORMAT_U16_BE",SND_PCM_FORMAT_U16_BE); + _EXPORT_INT(m,"PCM_FORMAT_S24_LE",SND_PCM_FORMAT_S24_LE); + _EXPORT_INT(m,"PCM_FORMAT_S24_BE",SND_PCM_FORMAT_S24_BE); + _EXPORT_INT(m,"PCM_FORMAT_U24_LE",SND_PCM_FORMAT_U24_LE); + _EXPORT_INT(m,"PCM_FORMAT_U24_BE",SND_PCM_FORMAT_U24_BE); + _EXPORT_INT(m,"PCM_FORMAT_S32_LE",SND_PCM_FORMAT_S32_LE); + _EXPORT_INT(m,"PCM_FORMAT_S32_BE",SND_PCM_FORMAT_S32_BE); + _EXPORT_INT(m,"PCM_FORMAT_U32_LE",SND_PCM_FORMAT_U32_LE); + _EXPORT_INT(m,"PCM_FORMAT_U32_BE",SND_PCM_FORMAT_U32_BE); + _EXPORT_INT(m,"PCM_FORMAT_FLOAT_LE",SND_PCM_FORMAT_FLOAT_LE); + _EXPORT_INT(m,"PCM_FORMAT_FLOAT_BE",SND_PCM_FORMAT_FLOAT_BE); + _EXPORT_INT(m,"PCM_FORMAT_FLOAT64_LE",SND_PCM_FORMAT_FLOAT64_LE); + _EXPORT_INT(m,"PCM_FORMAT_FLOAT64_BE",SND_PCM_FORMAT_FLOAT64_BE); + _EXPORT_INT(m,"PCM_FORMAT_MU_LAW",SND_PCM_FORMAT_MU_LAW); + _EXPORT_INT(m,"PCM_FORMAT_A_LAW",SND_PCM_FORMAT_A_LAW); + _EXPORT_INT(m,"PCM_FORMAT_IMA_ADPCM",SND_PCM_FORMAT_IMA_ADPCM); + _EXPORT_INT(m,"PCM_FORMAT_MPEG",SND_PCM_FORMAT_MPEG); + _EXPORT_INT(m,"PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM); + +} diff --git a/playbacktest.py b/playbacktest.py new file mode 100644 index 0000000..acc86f0 --- /dev/null +++ b/playbacktest.py @@ -0,0 +1,34 @@ +## recordtest.py +## +## This is an example of a simple sound playback script. +## +## The script opens an ALSA pcm for sound playback. Set +## various attributes of the device. It then reads data +## from stdin and writes it to the device. +## +## To test it out do the following: +## python recordtest.py > out.raw # talk to the microphone +## python playbacktest.py < out.raw + +import alsaaudio +import sys +import time + +# Open the device in playback mode. +out = alsaaudio.openpcm(alsaaudio.PCM_PLAYBACK) + +# Set attributes: Mono, 8000 Hz, 16 bit little endian frames +out.setchannels(1) +out.setrate(8000) +out.setformat(alsaaudio.PCM_FORMAT_S16_LE) + +# The period size controls the internal number of frames per period. +# The significance of this parameter is documented in the ALSA api. +out.setperiodsize(160) + +loops = 10000 +while loops > 0: + loops -= 1 + # Read data from stdin + data = sys.stdin.read(320) + out.write(data) diff --git a/recordtest.py b/recordtest.py new file mode 100644 index 0000000..5a47e57 --- /dev/null +++ b/recordtest.py @@ -0,0 +1,45 @@ +## recordtest.py +## +## This is an example of a simple sound capture script. +## +## The script opens an ALSA pcm forsound capture. Set +## various attributes of the capture, and reads in a loop, +## writing the data to standard out. +## +## To test it out do the following: +## python recordtest.py > out.raw # talk to the microphone +## aplay -r 8000 -f S16_LE -c 1 out.raw + +import alsaaudio +import sys +import time + +# Open the device in nonblocking capture mode. The last argument could +# just as well have been zero for blocking mode. Then we could have +# left out the sleep call in the bottom of the loop +inp = alsaaudio.openpcm(alsaaudio.PCM_CAPTURE,alsaaudio.PCM_NONBLOCK) + +# Set attributes: Mono, 8000 Hz, 16 bit little endian samples +inp.setchannels(1) +inp.setrate(8000) +inp.setformat(alsaaudio.PCM_FORMAT_S16_LE) + +# The period size controls the internal number of frames per period. +# The significance of this parameter is documented in the ALSA api. +# For our purposes, it is suficcient to know that reads from the device +# will return this many frames. Each frame being 2 bytes long. +# This means that the reads below will return either 320 bytes of data +# or 0 bytes of data. The latter is possible because we are in nonblocking +# mode. +inp.setperiodsize(160) + +loops = 10000 +while loops > 0: + loops -= 1 + # Read data from device + l,data = inp.read() + + if l: + # actual data read. Write it to stdout + sys.stdout.write(data) + time.sleep(.001) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d2534a7 --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +from distutils.core import setup +from distutils.extension import Extension + +setup( + name = "alsaaudio", + version = "0.1", + description = "alsa bindings", + author = "Casper Wilstrup", + author_email="cwi@unispeed.com", + ext_modules=[Extension("alsaaudio",["alsaaudio.c"],libraries=['asound']) + ] + ) + +