Compare commits

...

36 Commits

Author SHA1 Message Date
Lars Immisch
d3aa602a04 Setup PCM device in constructor. 2020-04-02 23:24:48 +02:00
Lars Immisch
046e7c4e87 Get rid of warnings, adjust CHANGES 2020-04-01 22:47:11 +02:00
Lars Immisch
a4c4c7cb62 Consistent indentation and some code style changes (whould be ws only) 2020-03-09 22:28:08 +01:00
Lars Immisch
f478797f6f Merge branch 'dev/card-detail' of https://github.com/jdstmporter/pyalsaaudio into jdstmporter-dev/card-detail 2020-03-09 22:07:23 +01:00
Lars Immisch
12f807698a Merge #80 2020-03-09 22:05:50 +01:00
Julian Porter
fc011b5ea6 restored gitignore! 2020-03-06 20:21:47 +00:00
Julian Porter
f244a70111 tidied up 2020-03-06 20:06:59 +00:00
Julian Porter
a056a90c61 modified version of pyalsaaudio module 2020-03-06 19:59:04 +00:00
Julian Porter
be1b3e131d demo 2020-03-05 00:50:30 +00:00
Danny
8abf06bedf Prevent hang on close after capturing audio
Currently, after recording audio using pyalsaaudio, the client is unable to close the device.

The reason is that PulseAudio client tries to drain the pipe to the PulseAudio server (presumably in order to prevent Broken Pipe error) on closing. That will never finish since new data will always arrive in the pipe.

Worse, the __del__ handler was auto-closing and thus auto-hanging.

Therefore, pause before de-allocating.
2019-12-02 21:39:44 +00:00
Lars Immisch
dcc831e607 Merge pull request #44 from Oranos25/contribution
add support for snd_pcm_drop function
2019-11-14 13:24:36 +01:00
Lars Immisch
e587df9143 Merge pull request #55 from moham96/patch-1
update playwav.py for python 3
2019-11-14 13:20:12 +01:00
Lars Immisch
82febd3f7e Merge pull request #67 from pdericson/master
Update pyalsaaudio.rst
2018-11-16 12:50:52 +01:00
Peter Ericson
1695066c11 Update pyalsaaudio.rst 2018-11-16 16:51:05 +08:00
Lars Immisch
25717020ef Transactional semantics for the alsapcm_set* calls 2018-02-28 09:52:53 +00:00
Lars Immisch
1aae655d24 Update periodsize only after alsapcm_setup succeeded 2018-02-28 00:35:26 +01:00
MOHAMMAD RASIM
c1c8362eb2 update playwav.py for python 3
use int division for periodsize to be compatible with python 3
2018-02-24 19:40:45 +03:00
Lars Immisch
723eff3887 Prepare next release 2018-02-20 12:18:44 +01:00
Lars Immisch
aa9867de18 Document changes, i.e. #53. 2018-02-20 12:10:20 +01:00
Lars Immisch
58f4522769 Merge pull request #53 from jcea/jcea/read_period_size
Unlimited setperiod buffer size when reading frames
2018-02-20 12:05:37 +01:00
Jesus Cea
f2fb61d324 Unlimited setperiod buffer size when reading frames 2018-02-20 11:52:47 +01:00
Anthony Piau
9e79494a95 add support for snd_pcm_drop function 2017-12-28 16:30:32 +00:00
Lars Immisch
bfe4899721 Merge pull request #39 from michals/master
Support 24bit audio
2017-08-30 20:52:49 +02:00
Michał Šrajer
40a1219dac Support 24bit audio
SND_PCM_FORMAT_S24_LE and similar are for 24bit ints packed in 4-bytes each.
There is a similar family of formats for 3-bytes packed data (as stored in 24bit wave files).

This commit:
 - adds S24_3LE, S24_3BE, U24_3LE, U24_3BE PCM formats to the alsaaudio.c
 - updates documentation
 - updates playwav.py to correctly play typical 24Bit PCM wave files

Closes #38
2017-08-29 19:09:54 +02:00
Lars Immisch
54e2712b7a Document release procedure 2017-07-09 15:01:41 +02:00
Lars Immisch
f9685e0b30 Correct capitalization
as suggested by Ben Loveridge
2017-07-09 13:32:08 +02:00
Lars Immisch
b4a670c50d Doc fixes. 2017-03-31 00:29:19 +02:00
Lars Immisch
370a4b6249 Regenerated doc. 2017-03-31 00:25:00 +02:00
Lars Immisch
eca217dff9 Document PCM.polldescriptors.
Closes #32
2017-03-30 23:20:22 +02:00
Lars Immisch
65d3c4a283 Typo. 2017-03-17 20:42:02 +01:00
Lars Immisch
adc0d800e1 Document EPIPE 2017-03-17 20:40:40 +01:00
Lars Immisch
02cf16d10d Improve documentation 2017-02-25 01:32:54 +01:00
Lars Immisch
94ced0517e Correct the sine example (finally!) Closes #10 2017-02-25 01:04:18 +01:00
Lars Immisch
698e6044d3 Bump version number 2017-02-24 20:57:53 +01:00
Lars Immisch
2c95f4ff6b Larger periodsize.
Before, it wasn't playing properly on my Raspberry Pi + Hifiberry DAC
2017-02-24 20:54:49 +01:00
Lars Immisch
f19d139f64 Fix C-API usage for Python 3. Closes #29 2017-02-24 13:25:36 +01:00
11 changed files with 612 additions and 415 deletions

29
CHANGES
View File

@@ -1,3 +1,32 @@
Version 0.8.6:
- Added four methods to the 'PCM' class to allow users to get detailed information about the device:
- 'getformats()' returns a dictionary of name / value pairs, one for each of the card's
supported formats - e.g. '{"U8": 1, "S16_LE": 2}',
- 'getchannels()' returns a list of the supported channel numbers, e.g. '[1, 2]',
- 'getrates()' returns supported sample rates for the device, e.g. '[48000]',
- 'getratebounds()' returns the device's official minimum and maximum supported
sample rates as a tuple, e.g. '(4000, 48000)'.
(#82 contributed by @jdstmporter)
- Prevent hang on close after capturing audio (#80 contributed by @daym)
Version 0.8.5:
- Return an empty string/bytestring when 'read()' detects an
overrun. Previously the returned data was undefined (contributed by @jcea)
- Unlimited setperiod buffer size when reading frames (contributed by @jcea)
Version 0.8.4:
- Fix Python3 API usage broken in 0.8.3
Version 0.8.3:
- Add DSD sample formats (contributed by @lintweaker)
- Add Mixer.handleevents() to acknowledge events identified by select.poll (contributed by @PaulSD)
- Add functions for listing cards and their names (contributed by @chrisdiamand)
- Add a method for setting enums (contributed by @chrisdiamand)
Version 0.8.2: Version 0.8.2:
- fix #3 (we cannot get the revision from git for pip installs) - fix #3 (we cannot get the revision from git for pip installs)

11
NOTES.md Normal file
View File

@@ -0,0 +1,11 @@
# Publishing the documentation
- Install Sphinx; `sudo pip install sphinx`
- Clone gh-pages branch: `cd doc; git clone -b gh-pages git@github.com:larsimmisch/pyalsaaudio.git gh-pages`
- `cd doc; make publish`
# Release procedure
- Update version number in setup.py
- Create tag and push it, i.e. `git tag x.y.z; git push origin x.y.z`
- `python setup.py sdist upload -r pypi`

View File

@@ -20,10 +20,74 @@
#define PyLong_Check PyInt_Check #define PyLong_Check PyInt_Check
#define PyLong_AS_LONG PyInt_AS_LONG #define PyLong_AS_LONG PyInt_AS_LONG
#endif #endif
#if PY_MAJOR_VERSION < 3
#define PyLong_FromLong PyInt_FromLong
#endif
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <alsa/version.h> #include <alsa/version.h>
#include <stdio.h> #include <stdio.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a))
static const snd_pcm_format_t ALSAFormats[] = {
SND_PCM_FORMAT_S8,
SND_PCM_FORMAT_U8,
SND_PCM_FORMAT_S16_LE,
SND_PCM_FORMAT_S16_BE,
SND_PCM_FORMAT_U16_LE,
SND_PCM_FORMAT_U16_BE,
SND_PCM_FORMAT_S24_LE,
SND_PCM_FORMAT_S24_BE,
SND_PCM_FORMAT_U24_LE,
SND_PCM_FORMAT_U24_BE,
SND_PCM_FORMAT_S32_LE,
SND_PCM_FORMAT_S32_BE,
SND_PCM_FORMAT_U32_LE,
SND_PCM_FORMAT_U32_BE,
SND_PCM_FORMAT_FLOAT_LE,
SND_PCM_FORMAT_FLOAT_BE,
SND_PCM_FORMAT_FLOAT64_LE,
SND_PCM_FORMAT_FLOAT64_BE,
SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
SND_PCM_FORMAT_MU_LAW,
SND_PCM_FORMAT_A_LAW,
SND_PCM_FORMAT_IMA_ADPCM,
SND_PCM_FORMAT_MPEG,
SND_PCM_FORMAT_GSM,
SND_PCM_FORMAT_SPECIAL,
SND_PCM_FORMAT_S24_3LE,
SND_PCM_FORMAT_S24_3BE,
SND_PCM_FORMAT_U24_3LE,
SND_PCM_FORMAT_U24_3BE,
SND_PCM_FORMAT_S20_3LE,
SND_PCM_FORMAT_S20_3BE,
SND_PCM_FORMAT_U20_3LE,
SND_PCM_FORMAT_U20_3BE,
SND_PCM_FORMAT_S18_3LE,
SND_PCM_FORMAT_S18_3BE,
SND_PCM_FORMAT_U18_3LE,
SND_PCM_FORMAT_U18_3BE
};
static const unsigned ALSARates[] = {
4000,
5512,
8000,
11025,
16000,
22050,
32000,
44100,
48000,
64000,
88200,
96000,
176400,
192000
};
PyDoc_STRVAR(alsaaudio_module_doc, PyDoc_STRVAR(alsaaudio_module_doc,
"This modules provides support for the ALSA audio API.\n" "This modules provides support for the ALSA audio API.\n"
"\n" "\n"
@@ -187,7 +251,7 @@ alsacard_list_indexes(PyObject *self, PyObject *args)
for (rc = snd_card_next(&card); !rc && (card >= 0); for (rc = snd_card_next(&card); !rc && (card >= 0);
rc = snd_card_next(&card)) rc = snd_card_next(&card))
{ {
PyObject *item = PyInt_FromLong(card); PyObject *item = PyLong_FromLong(card);
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
@@ -294,74 +358,26 @@ PyDoc_STRVAR(pcms_doc,
\n\ \n\
List the available PCM devices"); List the available PCM devices");
static int alsapcm_setup(alsapcm_t *self)
{
int res,dir;
unsigned int val;
snd_pcm_format_t fmt;
snd_pcm_uframes_t frames;
snd_pcm_hw_params_t *hwparams;
/* Allocate a hwparam structure on the stack,
and fill it with configuration space */
snd_pcm_hw_params_alloca(&hwparams);
res = snd_pcm_hw_params_any(self->handle, hwparams);
if (res < 0)
return res;
/* Fill it with default values.
We don't care if any of this fails - we'll read the actual values
back out.
*/
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);
/* Write it to the device */
res = snd_pcm_hw_params(self->handle, hwparams);
/* Query current settings. These may differ from the requested values,
which should therefore be sync'ed with actual values */
snd_pcm_hw_params_current(self->handle, hwparams);
snd_pcm_hw_params_get_format(hwparams, &fmt); self->format = fmt;
snd_pcm_hw_params_get_channels(hwparams, &val); self->channels = val;
snd_pcm_hw_params_get_rate(hwparams, &val, &dir); self->rate = val;
snd_pcm_hw_params_get_period_size(hwparams, &frames, &dir);
self->periodsize = (int) frames;
self->framesize = self->channels * snd_pcm_hw_params_get_sbits(hwparams)/8;
return res;
}
static PyObject * static PyObject *
alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds) alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{ {
int res;
alsapcm_t *self; alsapcm_t *self;
PyObject *pcmtypeobj = NULL; PyObject *pcmtypeobj = NULL;
long pcmtype; long pcmtype;
int pcmmode = 0; int pcmmode = 0;
unsigned int rate = 48000;
unsigned int channels = 2;
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
char *device = "default"; char *device = "default";
char *card = NULL; char *card = NULL;
int cardidx = -1; int cardidx = -1;
char hw_device[128]; char hw_device[128];
char *kw[] = { "type", "mode", "device", "cardindex", "card", NULL }; int latency = 200;
char *kw[] = { "type", "mode", "device", "cardindex", "card", "format", "rate", "channels", "latency", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiz", kw, if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiziiii", kw,
&pcmtypeobj, &pcmmode, &device, &pcmtypeobj, &pcmmode, &device,
&cardidx, &card)) &cardidx, &card, &rate, &format, &channels, &latency))
return NULL; return NULL;
if (cardidx >= 0) { if (cardidx >= 0) {
@@ -394,29 +410,9 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL; return NULL;
} }
if (pcmmode < 0 || pcmmode > SND_PCM_ASYNC) {
PyErr_SetString(ALSAAudioError, "Invalid PCM mode");
return NULL;
}
if (!(self = (alsapcm_t *)PyObject_New(alsapcm_t, &ALSAPCMType)))
return NULL;
self->handle = 0;
self->pcmtype = pcmtype;
self->pcmmode = pcmmode;
self->channels = 2;
self->rate = 44100;
self->format = SND_PCM_FORMAT_S16_LE;
self->periodsize = 32;
res = snd_pcm_open(&(self->handle), device, self->pcmtype, res = snd_pcm_open(&(self->handle), device, self->pcmtype,
self->pcmmode); self->pcmmode);
if (res >= 0) {
res = alsapcm_setup(self);
}
if (res >= 0) { if (res >= 0) {
self->cardname = strdup(device); self->cardname = strdup(device);
} }
@@ -435,6 +431,7 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static void alsapcm_dealloc(alsapcm_t *self) static void alsapcm_dealloc(alsapcm_t *self)
{ {
if (self->handle) { if (self->handle) {
snd_pcm_pause(self->handle, 1);
snd_pcm_drain(self->handle); snd_pcm_drain(self->handle);
snd_pcm_close(self->handle); snd_pcm_close(self->handle);
} }
@@ -567,6 +564,184 @@ alsapcm_dumpinfo(alsapcm_t *self, PyObject *args)
return Py_None; return Py_None;
} }
// auxiliary function
static PyObject *
alsapcm_getformats(alsapcm_t *self, PyObject *args)
{
snd_pcm_t *pcm = self->handle;
if (!pcm) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
int err = snd_pcm_hw_params_any(pcm, params);
if (err < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get hardware parameters");
return NULL;
}
PyObject *fmts = PyDict_New();
for (size_t i = 0; i < ARRAY_SIZE(ALSAFormats); ++i) {
snd_pcm_format_t format = ALSAFormats[i];
if (!snd_pcm_hw_params_test_format(pcm, params, format)) {
const char *name = snd_pcm_format_name(format);
PyObject *pname=PyUnicode_FromString(name);
PyObject *value=PyLong_FromLong((long)format);
PyDict_SetItem(fmts,pname,value);
}
}
return fmts;
}
PyDoc_STRVAR(getformats_doc,
"getformats() -> [str:int]\n\
\n\
Returns dictionary of supported format codes keyed by their standard ALSA names.");
static PyObject *
alsapcm_getratemaxmin(alsapcm_t *self, PyObject *args)
{
snd_pcm_t *pcm = self->handle;
if (!pcm) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
int err = snd_pcm_hw_params_any(pcm, params);
if (err < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get hardware parameters");
return NULL;
}
unsigned min,max;
if (snd_pcm_hw_params_get_rate_min(params, &min,NULL)<0) {
PyErr_SetString(ALSAAudioError, "Cannot get minimum supported bitrate");
return NULL;
}
if (snd_pcm_hw_params_get_rate_max(params, &max,NULL)<0) {
PyErr_SetString(ALSAAudioError, "Cannot get maximum supported bitrate");
return NULL;
}
PyObject *minp=PyLong_FromLong(min);
PyObject *maxp=PyLong_FromLong(max);
return PyTuple_Pack(2, minp, maxp);
}
PyDoc_STRVAR(getratebounds_doc,
"getratebounds() -> (int,int)\n\
\n\
Returns the card's minimum and maximum supported sample rates as a tuple.");
static PyObject *
alsapcm_getrates(alsapcm_t *self, PyObject *args)
{
snd_pcm_t *pcm = self->handle;
if (!pcm) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
int err = snd_pcm_hw_params_any(pcm, params);
if (err < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get hardware parameters");
return NULL;
}
unsigned min, max;
if (snd_pcm_hw_params_get_rate_min(params, &min, NULL) <0 ) {
PyErr_SetString(ALSAAudioError, "Cannot get minimum supported bitrate");
return NULL;
}
if (snd_pcm_hw_params_get_rate_max(params, &max, NULL) < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get maximum supported bitrate");
return NULL;
}
if (min == max) {
return PyLong_FromLong(min);
}
else if (!snd_pcm_hw_params_test_rate(pcm, params, min + 1, 0)) {
PyObject *minp=PyLong_FromLong(min);
PyObject *maxp=PyLong_FromLong(max);
return PyTuple_Pack(2,minp,maxp);
}
else {
PyObject *rates=PyList_New(0);
for (size_t i=0; i<ARRAY_SIZE(ALSARates); i++) {
unsigned rate = ALSARates[i];
if (!snd_pcm_hw_params_test_rate(pcm, params, rate, 0)) {
PyObject *prate=PyLong_FromLong(rate);
PyList_Append(rates,prate);
}
}
return rates;
}
}
PyDoc_STRVAR(getrates_doc,
"getrates() -> obj\n\
\n\
Returns the sample rates supported by the device.\
Returned value can be one of three types, depending on the card's properties.\
There are three cases:\n\
\n\
- Card supports only a single rate: returns the rate\n\
- Card supports a continuous range of rates: returns a tuple of the range's lower and upper bounds (inclusive)\n\
- Card supports a collection of well-known rates: returns a list of the supported rates");
static PyObject *
alsapcm_getchannels(alsapcm_t *self,PyObject *args)
{
snd_pcm_t *pcm = self->handle;
if (!pcm) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
snd_pcm_hw_params_t *params;
snd_pcm_hw_params_alloca(&params);
int err = snd_pcm_hw_params_any(pcm, params);
if (err < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get hardware parameters");
return NULL;
}
unsigned min, max;
if (snd_pcm_hw_params_get_channels_min(params, &min) < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get minimum supported number of channels");
return NULL;
}
if (snd_pcm_hw_params_get_channels_max(params, &max) < 0) {
PyErr_SetString(ALSAAudioError, "Cannot get maximum supported number of channels");
return NULL;
}
PyObject *out = PyList_New(0);
for (unsigned ch=min;ch<=max;++ch) {
if (!snd_pcm_hw_params_test_channels(pcm, params, ch)) {
PyObject *pch=PyLong_FromLong(ch);
PyList_Append(out,pch);
}
}
return out;
}
PyDoc_STRVAR(getchannels_doc,
"getchannels() -> [int]\n\
\n\
Returns list of supported channel numbers.");
static PyObject * static PyObject *
alsapcm_pcmtype(alsapcm_t *self, PyObject *args) alsapcm_pcmtype(alsapcm_t *self, PyObject *args)
{ {
@@ -629,147 +804,13 @@ PyDoc_STRVAR(cardname_doc,
\n\ \n\
Returns the name of the sound card used by this PCM object."); Returns the name of the sound card used by this PCM object.");
static PyObject *
alsapcm_setchannels(alsapcm_t *self, PyObject *args)
{
int channels;
int res;
if (!PyArg_ParseTuple(args,"i:setchannels", &channels))
return NULL;
if (!self->handle) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
self->channels = channels;
res = alsapcm_setup(self);
if (res < 0)
{
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(self->channels);
}
PyDoc_STRVAR(setchannels_doc,
"setchannels(numchannels)\n\
\n\
Used to set the number of capture or playback channels. Common values\n\
are: 1 = mono, 2 = stereo, and 6 = full 6 channel audio.\n\
\n\
Few sound cards support more than 2 channels.");
static PyObject *
alsapcm_setrate(alsapcm_t *self, PyObject *args)
{
int rate;
int res;
if (!PyArg_ParseTuple(args,"i:setrate", &rate))
return NULL;
if (!self->handle)
{
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
self->rate = rate;
res = alsapcm_setup(self);
if (res < 0)
{
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(self->rate);
}
PyDoc_STRVAR(setrate_doc,
"setrate(rate)\n\
\n\
Set the sample rate in Hz for the device. Typical values are\n\
8000 (telephony), 11025, 44100 (CD), 48000 (DVD audio) and 96000");
static PyObject *
alsapcm_setformat(alsapcm_t *self, PyObject *args)
{
int format;
int res;
if (!PyArg_ParseTuple(args,"i:setformat", &format))
return NULL;
if (!self->handle)
{
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
self->format = format;
res = alsapcm_setup(self);
if (res < 0)
{
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(self->format);
}
PyDoc_STRVAR(setformat_doc,
"setformat(rate)\n");
static PyObject *
alsapcm_setperiodsize(alsapcm_t *self, PyObject *args)
{
int periodsize;
int res;
if (!PyArg_ParseTuple(args,"i:setperiodsize", &periodsize))
return NULL;
if (!self->handle)
{
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
self->periodsize = periodsize;
res = alsapcm_setup(self);
if (res < 0)
{
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(self->periodsize);
}
PyDoc_STRVAR(setperiodsize_doc,
"setperiodsize(period) -> int\n\
\n\
Sets the actual period size in frames. Each write should consist of\n\
exactly this number of frames, and each read will return this number of\n\
frames (unless the device is in PCM_NONBLOCK mode, in which case it\n\
may return nothing at all).");
static PyObject * static PyObject *
alsapcm_read(alsapcm_t *self, PyObject *args) alsapcm_read(alsapcm_t *self, PyObject *args)
{ {
int res; int res;
char buffer[8000]; int size = self->framesize * self->periodsize;
PyObject *buffer_obj, *tuple_obj, *res_obj;
if (self->framesize * self->periodsize > 8000) { char *buffer;
PyErr_SetString(ALSAAudioError,"Capture data too large. "
"Try decreasing period size");
return NULL;
}
if (!PyArg_ParseTuple(args,":read")) if (!PyArg_ParseTuple(args,":read"))
return NULL; return NULL;
@@ -786,6 +827,18 @@ alsapcm_read(alsapcm_t *self, PyObject *args)
return NULL; return NULL;
} }
#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
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
res = snd_pcm_readi(self->handle, buffer, self->periodsize); res = snd_pcm_readi(self->handle, buffer, self->periodsize);
if (res == -EPIPE) if (res == -EPIPE)
@@ -805,17 +858,41 @@ alsapcm_read(alsapcm_t *self, PyObject *args)
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname); self->cardname);
Py_DECREF(buffer_obj);
return NULL; return NULL;
} }
} }
if (res <= 0) {
#if PY_MAJOR_VERSION < 3 #if PY_MAJOR_VERSION < 3
return Py_BuildValue("is#", res, buffer, res*self->framesize); /* If the following fails, it will free the object */
if (_PyString_Resize(&buffer_obj, 0))
return NULL;
#else #else
return Py_BuildValue("iy#", res, buffer, res*self->framesize); /* If the following fails, it will free the object */
if (_PyBytes_Resize(&buffer_obj, 0))
return NULL;
#endif #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;
}
PyDoc_STRVAR(read_doc, PyDoc_STRVAR(read_doc,
"read() -> (size, data)\n\ "read() -> (size, data)\n\
\n\ \n\
@@ -945,6 +1022,43 @@ PyDoc_STRVAR(pause_doc,
If enable is 1, playback or capture is paused. If enable is 0,\n\ If enable is 1, playback or capture is paused. If enable is 0,\n\
playback/capture is resumed."); playback/capture is resumed.");
static PyObject *alsapcm_drop(alsapcm_t *self)
{
int res;
if (!self->handle) {
PyErr_SetString(ALSAAudioError, "PCM device is closed");
return NULL;
}
res = snd_pcm_drop(self->handle);
if (res < 0)
{
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
res = snd_pcm_prepare(self->handle);
if (res < 0)
{
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
return NULL;
}
return PyLong_FromLong(res);
}
PyDoc_STRVAR(drop_doc,
"drop(enable=1)\n\
\n\
stop current read and drop residual packet");
static PyObject * static PyObject *
alsapcm_polldescriptors(alsapcm_t *self, PyObject *args) alsapcm_polldescriptors(alsapcm_t *self, PyObject *args)
{ {
@@ -1008,16 +1122,15 @@ static PyMethodDef alsapcm_methods[] = {
{"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS, pcmtype_doc}, {"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS, pcmtype_doc},
{"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS, pcmmode_doc}, {"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS, pcmmode_doc},
{"cardname", (PyCFunction)alsapcm_cardname, METH_VARARGS, cardname_doc}, {"cardname", (PyCFunction)alsapcm_cardname, METH_VARARGS, cardname_doc},
{"setchannels", (PyCFunction)alsapcm_setchannels, METH_VARARGS,
setchannels_doc },
{"setrate", (PyCFunction)alsapcm_setrate, METH_VARARGS, setrate_doc},
{"setformat", (PyCFunction)alsapcm_setformat, METH_VARARGS, setformat_doc},
{"setperiodsize", (PyCFunction)alsapcm_setperiodsize, METH_VARARGS,
setperiodsize_doc},
{"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS}, {"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS},
{"getformats", (PyCFunction)alsapcm_getformats, METH_VARARGS, getformats_doc},
{"getratebounds", (PyCFunction)alsapcm_getratemaxmin, METH_VARARGS, getratebounds_doc},
{"getrates", (PyCFunction)alsapcm_getrates, METH_VARARGS, getrates_doc},
{"getchannels", (PyCFunction)alsapcm_getchannels, METH_VARARGS, getchannels_doc},
{"read", (PyCFunction)alsapcm_read, METH_VARARGS, read_doc}, {"read", (PyCFunction)alsapcm_read, METH_VARARGS, read_doc},
{"write", (PyCFunction)alsapcm_write, METH_VARARGS, write_doc}, {"write", (PyCFunction)alsapcm_write, METH_VARARGS, write_doc},
{"pause", (PyCFunction)alsapcm_pause, METH_VARARGS, pause_doc}, {"pause", (PyCFunction)alsapcm_pause, METH_VARARGS, pause_doc},
{"drop", (PyCFunction)alsapcm_drop, METH_VARARGS, drop_doc},
{"close", (PyCFunction)alsapcm_close, METH_VARARGS, pcm_close_doc}, {"close", (PyCFunction)alsapcm_close, METH_VARARGS, pcm_close_doc},
{"polldescriptors", (PyCFunction)alsapcm_polldescriptors, METH_VARARGS, {"polldescriptors", (PyCFunction)alsapcm_polldescriptors, METH_VARARGS,
pcm_polldescriptors_doc}, pcm_polldescriptors_doc},
@@ -2473,6 +2586,10 @@ PyObject *PyInit_alsaaudio(void)
_EXPORT_INT(m, "PCM_FORMAT_IMA_ADPCM",SND_PCM_FORMAT_IMA_ADPCM); _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_MPEG",SND_PCM_FORMAT_MPEG);
_EXPORT_INT(m, "PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM); _EXPORT_INT(m, "PCM_FORMAT_GSM",SND_PCM_FORMAT_GSM);
_EXPORT_INT(m, "PCM_FORMAT_S24_3LE",SND_PCM_FORMAT_S24_3LE);
_EXPORT_INT(m, "PCM_FORMAT_S24_3BE",SND_PCM_FORMAT_S24_3BE);
_EXPORT_INT(m, "PCM_FORMAT_U24_3LE",SND_PCM_FORMAT_U24_3LE);
_EXPORT_INT(m, "PCM_FORMAT_U24_3BE",SND_PCM_FORMAT_U24_3BE);
/* DSD sample formats are included in ALSA 1.0.29 and higher /* DSD sample formats are included in ALSA 1.0.29 and higher
* define OVERRIDE_DSD_COMPILE to include DSD sample support * define OVERRIDE_DSD_COMPILE to include DSD sample support

View File

@@ -1,182 +1,160 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# alsaaudio documentation build configuration file, created by # alsaaudio documentation documentation build configuration file, created by
# sphinx-quickstart on Sat Nov 22 00:17:09 2008. # sphinx-quickstart on Thu Mar 30 23:52:21 2017.
# #
# This file is execfile()d with the current directory set to its containing dir. # This file is execfile()d with the current directory set to its
# containing dir.
# #
# The contents of this file are pickled, so don't put values in the namespace # Note that not all possible configuration values are present in this
# that aren't pickleable (module imports are okay, they're removed automatically). # autogenerated file.
# #
# All configuration values have a default value; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default value. # serve to show the default.
import sys, os # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import sys
sys.path.insert(0, '..') sys.path.insert(0, '..')
from setup import pyalsa_version from setup import pyalsa_version
# If your extensions are in another directory, add it here. If the directory
# is relative to the documentation root, use os.path.abspath to make it
# absolute, like shown here.
#sys.path.append(os.path.abspath('some/directory'))
# General configuration # -- General configuration ------------------------------------------------
# ---------------------
# Add any Sphinx extension module names here, as strings. They can be extensions # If your documentation needs a minimal Sphinx version, state it here.
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. #
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [] extensions = []
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates'] templates_path = ['_templates']
# The suffix of source filenames. # The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst' source_suffix = '.rst'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
# General substitutions. # General information about the project.
project = u'alsaaudio' project = u'alsaaudio documentation'
copyright = u'2008-20017, Casper Wilstrup, Lars Immisch' copyright = u'2017, Lars Immisch & Casper Wilstrup'
author = u'Lars Immisch & Casper Wilstrup'
# The default replacements for |version| and |release|, also used in various # The version info for the project you're documenting, acts as replacement for
# other places throughout the built documents. # |version| and |release|, also used in various other places throughout the
# built documents.
# #
# The short X.Y version. # The short X.Y version.
version = pyalsa_version version = pyalsa_version
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = pyalsa_version release = version
# There are two options for replacing |today|: either, you set today to some # The language for content autogenerated by Sphinx. Refer to documentation
# non-false value, then it is used: # for a list of supported languages.
#today = '' #
# Else, today_fmt is used as the format for a strftime call. # This is also used if you do content translation via gettext catalogs.
today_fmt = '%B %d, %Y' # Usually you set "language" from the command line for these cases.
language = None
# List of documents that shouldn't be included in the build. # List of patterns, relative to source directory, that match files and
#unused_docs = [] # directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
# List of directories, relative to source directories, that shouldn't be searched exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# for source files.
exclude_trees = ['.build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# Options for HTML output
# -----------------------
# The style sheet to use for HTML and HTML Help pages. A file of that name # -- Options for HTML output ----------------------------------------------
# must exist either in Sphinx' static/ path, or in one of the custom paths
# given in html_static_path.
html_style = 'default.css'
# The name for this set of Sphinx documents. If None, it defaults to # The theme to use for HTML and HTML Help pages. See the documentation for
# "<project> v<release> documentation". # a list of builtin themes.
#html_title = None #
html_theme = 'alabaster'
# A shorter title for the navigation bar. Default is the same as html_title. # Theme options are theme-specific and customize the look and feel of a theme
#html_short_title = None # further. For a list of options available for each theme, see the
# documentation.
# The name of an image file (relative to this directory) to place at the top #
# of the sidebar. # html_theme_options = {}
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['static'] html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # -- Options for HTMLHelp output ------------------------------------------
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_use_modindex = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, the reST sources are included in the HTML build as _sources/<name>.
#html_copy_source = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'alsaaudiodoc' htmlhelp_basename = 'alsaaudiodocumentationdoc'
# Options for LaTeX output # -- Options for LaTeX output ---------------------------------------------
# ------------------------
# The paper size ('letter' or 'a4'). latex_elements = {
#latex_paper_size = 'letter' # The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt'). # The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt' #
# 'pointsize': '10pt',
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, document class [howto/manual]).
latex_documents = [
('index', 'alsaaudio.tex', u'alsaaudio Documentation',
u'Casper Wilstrup, Lars Immisch', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
#latex_preamble = '' #
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'alsaaudiodocumentation.tex', u'alsaaudio documentation Documentation',
u'Lars Immisch', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'alsaaudiodocumentation', u'alsaaudio documentation Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'alsaaudiodocumentation', u'alsaaudio documentation Documentation',
author, 'alsaaudiodocumentation', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_use_modindex = True

View File

@@ -1,19 +1,29 @@
.. alsaaudio documentation documentation master file, created by
sphinx-quickstart on Thu Mar 30 23:52:21 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
alsaaudio documentation alsaaudio documentation
======================= ===================================================
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Contents:
pyalsaaudio pyalsaaudio
terminology terminology
libalsaaudio libalsaaudio
Download
========
Github pages
=================
* `Project page <https://github.com/larsimmisch/pyalsaaudio/>`_
* `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_ * `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_
Github
======
* `Repository <https://github.com/larsimmisch/pyalsaaudio/>`_
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_ * `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
@@ -24,3 +34,5 @@ Indices and tables
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`

View File

@@ -193,10 +193,10 @@ PCM objects have the following methods:
``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order) ``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order)
``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order) ``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order)
``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order) ``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order)
``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order) ``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order in 4 bytes)
``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order)} ``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order in 4 bytes)
``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order) ``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 4 bytes)
``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order) ``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 4 bytes)
``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order) ``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order)
``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order) ``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order)
``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order) ``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order)
@@ -210,6 +210,10 @@ PCM objects have the following methods:
``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association. ``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association.
``PCM_FORMAT_MPEG`` MPEG encoded audio? ``PCM_FORMAT_MPEG`` MPEG encoded audio?
``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech ``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech
``PCM_FORMAT_S24_3LE`` Signed 24 bit samples for each channel (Little Endian byte order in 3 bytes)
``PCM_FORMAT_S24_3BE`` Signed 24 bit samples for each channel (Big Endian byte order in 3 bytes)
``PCM_FORMAT_U24_3LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 3 bytes)
``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes)
========================= =============== ========================= ===============
@@ -233,6 +237,9 @@ PCM objects have the following methods:
``(0,'')`` if no new period has become available since the last ``(0,'')`` if no new period has become available since the last
call to read. call to read.
In case of an overrun, this function will return a negative size: :const:`-EPIPE`.
This indicates that data was lost, even if the operation itself succeeded.
Try using a larger periodsize.
.. method:: PCM.write(data) .. method:: PCM.write(data)
@@ -256,6 +263,17 @@ PCM objects have the following methods:
If *enable* is :const:`True`, playback or capture is paused. If *enable* is :const:`True`, playback or capture is paused.
Otherwise, playback/capture is resumed. Otherwise, playback/capture is resumed.
.. method:: PCM.polldescriptors()
Returns a tuple of *(file descriptor, eventmask)* that can be used to
wait for changes on the mixer with *select.poll*.
The *eventmask* value is compatible with `poll.register`__ in the Python
:const:`select` module.
__ poll_objects_
**A few hints on using PCM devices for playback** **A few hints on using PCM devices for playback**
The most common reason for problems with playback of PCM audio is that writes The most common reason for problems with playback of PCM audio is that writes
@@ -473,9 +491,14 @@ Mixer objects have the following methods:
.. method:: Mixer.polldescriptors() .. method:: Mixer.polldescriptors()
Returns a tuple of (file descriptor, eventmask) that can be used to Returns a tuple of *(file descriptor, eventmask)* that can be used to
wait for changes on the mixer with *select.poll*. wait for changes on the mixer with *select.poll*.
The *eventmask* value is compatible with `poll.register`__ in the Python
:const:`select` module.
__ poll_objects_
.. method:: Mixer.handleevents() .. method:: Mixer.handleevents()
Acknowledge events on the *polldescriptors* file descriptors Acknowledge events on the *polldescriptors* file descriptors
@@ -620,3 +643,5 @@ argument::
.. rubric:: Footnotes .. rubric:: Footnotes
.. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet. .. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet.
.. _poll_objects: http://docs.python.org/library/select.html#poll-objects

View File

@@ -75,7 +75,7 @@ development at the time - and neither are very feature complete.
I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module
included in the standard Python library, but that looks currently unlikely. included in the standard Python library, but that looks currently unlikely.
PyAlsaAudio hass full support for sound capture, playback of sound, as well as PyAlsaAudio has full support for sound capture, playback of sound, as well as
the ALSA Mixer API. the ALSA Mixer API.
MIDI support is not available, and since I don't own any MIDI hardware, it's MIDI support is not available, and since I don't own any MIDI hardware, it's
@@ -110,25 +110,32 @@ And then as root: --- ::
Testing Testing
******* *******
First of all, run:: Make sure that :code:`aplay` plays a file through the soundcard you want, then
try::
$ python test.py $ python playwav.py <filename.wav>
This is a small test suite that mostly performs consistency tests. If If :code:`aplay` needs a device argument, like
it fails, please file a `bug report :code:`aplay -D hw:CARD=sndrpihifiberry,DEV=0`, use::
<https://github.com/larsimmisch/pyalsaaudio/issues>`_.
$ python playwav.py -d hw:CARD=sndrpihifiberry,DEV=0 <filename.wav>
To test PCM recordings (on your default soundcard), verify your To test PCM recordings (on your default soundcard), verify your
microphone works, then do:: microphone works, then do::
$ python recordtest.py <filename> $ python recordtest.py -d <device> <filename>
Speak into the microphone, and interrupt the recording at any time Speak into the microphone, and interrupt the recording at any time
with ``Ctl-C``. with ``Ctl-C``.
Play back the recording with:: Play back the recording with::
$ python playbacktest.py <filename> $ python playbacktest.py-d <device> <filename>
There is a minimal test suite in :code:`test.py`, but it is
a bit dependent on the ALSA configuration and may fail without indicating
a real problem.
If you find bugs/problems, please file a `bug report
<https://github.com/larsimmisch/pyalsaaudio/issues>`_.

View File

@@ -46,7 +46,7 @@ Data rate
At the other end of the scale, 96000 Hz, 6 channel sound with 64 At the other end of the scale, 96000 Hz, 6 channel sound with 64
bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608 bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608
kb/s (almost 5 Mb sound data per second) kb/s (almost 5 MB sound data per second)
Period Period
When the hardware processes data this is done in chunks of frames. The time When the hardware processes data this is done in chunks of frames. The time

View File

@@ -6,30 +6,50 @@
from __future__ import print_function from __future__ import print_function
import sys
from threading import Thread from threading import Thread
from queue import Queue, Empty from multiprocessing import Queue
if sys.version_info[0] < 3:
from Queue import Empty
else:
from queue import Empty
from math import pi, sin from math import pi, sin
import struct import struct
import alsaaudio import alsaaudio
sampling_rate = 44100 sampling_rate = 48000
format = alsaaudio.PCM_FORMAT_S16_LE format = alsaaudio.PCM_FORMAT_S16_LE
framesize = 2 # bytes per frame for the values above framesize = 2 # bytes per frame for the values above
channels = 2
def digitize(s): def nearest_frequency(frequency):
if s > 1.0 or s < -1.0: # calculate the nearest frequency where the wave form fits into the buffer
raise ValueError # in other words, select f so that sampling_rate/f is an integer
return float(sampling_rate)/int(sampling_rate/frequency)
return struct.pack('h', int(s * 32767)) def generate(frequency, duration = 0.125):
# generate a buffer with a sine wave of `frequency`
# that is approximately `duration` seconds long
def generate(frequency): # the buffersize we approximately want
# generate a buffer with a sine wave of frequency target_size = int(sampling_rate * channels * duration)
size = int(sampling_rate / frequency)
buffer = bytes() # the length of a full sine wave at the frequency
for i in range(size): cycle_size = int(sampling_rate / frequency)
buffer = buffer + digitize(sin(i/(2 * pi)))
# number of full cycles we can fit into target_size
factor = int(target_size / cycle_size)
size = max(int(cycle_size * factor), 1)
sine = [ int(32767 * sin(2 * pi * frequency * i / sampling_rate)) \
for i in range(size)]
return struct.pack('%dh' % size, *sine)
return buffer
class SinePlayer(Thread): class SinePlayer(Thread):
@@ -37,7 +57,7 @@ class SinePlayer(Thread):
Thread.__init__(self) Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
self.device = alsaaudio.PCM() self.device = alsaaudio.PCM()
self.device.setchannels(1) self.device.setchannels(channels)
self.device.setformat(format) self.device.setformat(format)
self.device.setrate(sampling_rate) self.device.setrate(sampling_rate)
self.queue = Queue() self.queue = Queue()
@@ -48,17 +68,13 @@ class SinePlayer(Thread):
# we generate the buffer in the calling thread for less # we generate the buffer in the calling thread for less
# latency when switching frequencies # latency when switching frequencies
if frequency > sampling_rate / 2:
raise ValueError('maximum frequency is %d' % (sampling_rate / 2))
# More than 100 writes/s are pushing it - play multiple buffers f = nearest_frequency(frequency)
# for higher frequencies print('nearest frequency: %f' % f)
factor = int(frequency/100.0)
if factor == 0:
factor = 1
buf = generate(frequency) * factor
print('factor: %d, frames: %d' % (factor, len(buf) / framesize))
buf = generate(f)
self.queue.put(buf) self.queue.put(buf)
def run(self): def run(self):

View File

@@ -24,19 +24,21 @@ def play(device, f):
elif f.getsampwidth() == 2: elif f.getsampwidth() == 2:
device.setformat(alsaaudio.PCM_FORMAT_S16_LE) device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
elif f.getsampwidth() == 3: elif f.getsampwidth() == 3:
device.setformat(alsaaudio.PCM_FORMAT_S24_LE) device.setformat(alsaaudio.PCM_FORMAT_S24_3LE)
elif f.getsampwidth() == 4: elif f.getsampwidth() == 4:
device.setformat(alsaaudio.PCM_FORMAT_S32_LE) device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
else: else:
raise ValueError('Unsupported format') raise ValueError('Unsupported format')
device.setperiodsize(320) periodsize = f.getframerate() // 8
data = f.readframes(320) device.setperiodsize(periodsize)
data = f.readframes(periodsize)
while data: while data:
# Read data from stdin # Read data from stdin
device.write(data) device.write(data)
data = f.readframes(320) data = f.readframes(periodsize)
def usage(): def usage():

View File

@@ -8,7 +8,7 @@ from setuptools import setup
from setuptools.extension import Extension from setuptools.extension import Extension
from sys import version from sys import version
pyalsa_version = '0.8.3' pyalsa_version = '0.8.6'
if __name__ == '__main__': if __name__ == '__main__':
setup( setup(