33 Commits

Author SHA1 Message Date
Lars Immisch 62e5515341 Document the release process. 2020-07-13 22:00:44 +02:00
Lars Immisch ed027a6141 More output for playwav 2020-07-13 20:42:25 +01:00
Lars Immisch 5302dc524d Cleanup warnings 2020-07-13 20:59:49 +02:00
Lars Immisch b17b36be50 Better error messages in tests 2020-07-13 20:51:59 +02:00
Lars Immisch 08bdce9ed9 Tests for Depreciations 2020-07-13 20:20:28 +02:00
Lars Immisch 0224c8a308 Inline documentation (and .gitignore) 2020-07-10 00:54:24 +02:00
Lars Immisch f07627543c Update documentation 2020-07-10 00:45:57 +02:00
Lars Immisch df889b94ef Don't use setrate etc. in samples. 2020-07-09 21:22:06 +02:00
Lars Immisch 2a21bf6c42 Support all essential parameters in alsapcm_new. 2020-07-08 22:39:46 +02:00
Lars Immisch 8084297926 Merge pull request #83 from stalkerg/master
fix generate switch capabilities
2020-05-25 12:58:03 +02:00
stalkerg 8fbc04e18d fix generate switch capabilities 2020-05-21 17:21:40 +09:00
Lars Immisch 8ed9f924cd Attempt to fix #45 2020-04-23 21:36:29 +01: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
13 changed files with 2301 additions and 1983 deletions
+2
View File
@@ -4,6 +4,8 @@ MANIFEST
doc/gh-pages/ doc/gh-pages/
doc/html/ doc/html/
doc/doctrees/ doc/doctrees/
doc/_build/
gh-pages/ gh-pages/
build/ build/
dist/ dist/
.vscode/
+29
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)
+442 -163
View File
@@ -1,4 +1,4 @@
/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: t -*- */
/* /*
* alsaaudio -- Python interface to ALSA (Advanced Linux Sound Architecture). * alsaaudio -- Python interface to ALSA (Advanced Linux Sound Architecture).
@@ -29,6 +29,65 @@
#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"
@@ -48,7 +107,7 @@ typedef struct {
// Configurable parameters // Configurable parameters
int channels; int channels;
int rate; unsigned int rate;
int format; int format;
snd_pcm_uframes_t periodsize; snd_pcm_uframes_t periodsize;
int framesize; int framesize;
@@ -77,12 +136,6 @@ typedef struct {
snd_mixer_t *handle; snd_mixer_t *handle;
} alsamixer_t; } alsamixer_t;
typedef enum {
unit_percent,
unit_dB,
unit_last
} volume_unit_t;
/******************************************/ /******************************************/
/* PCM object wrapper */ /* PCM object wrapper */
/******************************************/ /******************************************/
@@ -317,8 +370,9 @@ static int alsapcm_setup(alsapcm_t *self)
and fill it with configuration space */ and fill it with configuration space */
snd_pcm_hw_params_alloca(&hwparams); snd_pcm_hw_params_alloca(&hwparams);
res = snd_pcm_hw_params_any(self->handle, hwparams); res = snd_pcm_hw_params_any(self->handle, hwparams);
if (res < 0) if (res < 0) {
return res; return res;
}
/* Fill it with default values. /* Fill it with default values.
@@ -333,10 +387,11 @@ static int alsapcm_setup(alsapcm_t *self)
self->channels); self->channels);
dir = 0; dir = 0;
snd_pcm_hw_params_set_rate(self->handle, hwparams, self->rate, dir); unsigned int periods = 4;
snd_pcm_hw_params_set_period_size(self->handle, hwparams, snd_pcm_hw_params_set_rate_near(self->handle, hwparams, &self->rate, &dir);
self->periodsize, dir); snd_pcm_hw_params_set_period_size_near(self->handle, hwparams,
snd_pcm_hw_params_set_periods(self->handle, hwparams, 4, 0); &self->periodsize, &dir);
snd_pcm_hw_params_set_periods_near(self->handle, hwparams, &periods, &dir);
/* Write it to the device */ /* Write it to the device */
res = snd_pcm_hw_params(self->handle, hwparams); res = snd_pcm_hw_params(self->handle, hwparams);
@@ -368,11 +423,16 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
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 rate = 44100;
int channels = 2;
int format = SND_PCM_FORMAT_S16_LE;
int periodsize = 32;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiz", kw, char *kw[] = { "type", "mode", "device", "cardindex", "card", "rate", "channels", "format", "periodsize", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiziiii", kw,
&pcmtypeobj, &pcmmode, &device, &pcmtypeobj, &pcmmode, &device,
&cardidx, &card)) &cardidx, &card, &rate, &channels, &format, &periodsize))
return NULL; return NULL;
if (cardidx >= 0) { if (cardidx >= 0) {
@@ -416,10 +476,10 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->handle = 0; self->handle = 0;
self->pcmtype = pcmtype; self->pcmtype = pcmtype;
self->pcmmode = pcmmode; self->pcmmode = pcmmode;
self->channels = 2; self->channels = channels;
self->rate = 44100; self->rate = rate;
self->format = SND_PCM_FORMAT_S16_LE; self->format = format;
self->periodsize = 32; self->periodsize = periodsize;
res = snd_pcm_open(&(self->handle), device, self->pcmtype, res = snd_pcm_open(&(self->handle), device, self->pcmtype,
self->pcmmode); self->pcmmode);
@@ -446,6 +506,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);
} }
@@ -578,6 +639,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)
{ {
@@ -644,8 +883,9 @@ Returns the name of the sound card used by this PCM object.");
static PyObject * static PyObject *
alsapcm_setchannels(alsapcm_t *self, PyObject *args) alsapcm_setchannels(alsapcm_t *self, PyObject *args)
{ {
int channels; int channels, saved;
int res; int res;
if (!PyArg_ParseTuple(args,"i:setchannels", &channels)) if (!PyArg_ParseTuple(args,"i:setchannels", &channels))
return NULL; return NULL;
@@ -654,10 +894,16 @@ alsapcm_setchannels(alsapcm_t *self, PyObject *args)
return NULL; return NULL;
} }
PyErr_WarnEx(PyExc_DeprecationWarning,
"This function is deprecated. "
"Please use the named parameter `channels` to `PCM()` instead", 1);
saved = self->channels;
self->channels = channels; self->channels = channels;
res = alsapcm_setup(self); res = alsapcm_setup(self);
if (res < 0) if (res < 0)
{ {
self->channels = saved;
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname); self->cardname);
return NULL; return NULL;
@@ -668,6 +914,8 @@ alsapcm_setchannels(alsapcm_t *self, PyObject *args)
PyDoc_STRVAR(setchannels_doc, PyDoc_STRVAR(setchannels_doc,
"setchannels(numchannels)\n\ "setchannels(numchannels)\n\
\n\ \n\
Deprecated since 0.9\n\
\n\
Used to set the number of capture or playback channels. Common values\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\ are: 1 = mono, 2 = stereo, and 6 = full 6 channel audio.\n\
\n\ \n\
@@ -677,8 +925,9 @@ Few sound cards support more than 2 channels.");
static PyObject * static PyObject *
alsapcm_setrate(alsapcm_t *self, PyObject *args) alsapcm_setrate(alsapcm_t *self, PyObject *args)
{ {
int rate; int rate, saved;
int res; int res;
if (!PyArg_ParseTuple(args,"i:setrate", &rate)) if (!PyArg_ParseTuple(args,"i:setrate", &rate))
return NULL; return NULL;
@@ -688,10 +937,16 @@ alsapcm_setrate(alsapcm_t *self, PyObject *args)
return NULL; return NULL;
} }
PyErr_WarnEx(PyExc_DeprecationWarning,
"This function is deprecated. "
"Please use the named parameter `rate` to `PCM()` instead", 1);
saved = self->rate;
self->rate = rate; self->rate = rate;
res = alsapcm_setup(self); res = alsapcm_setup(self);
if (res < 0) if (res < 0)
{ {
self->rate = saved;
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname); self->cardname);
return NULL; return NULL;
@@ -702,6 +957,8 @@ alsapcm_setrate(alsapcm_t *self, PyObject *args)
PyDoc_STRVAR(setrate_doc, PyDoc_STRVAR(setrate_doc,
"setrate(rate)\n\ "setrate(rate)\n\
\n\ \n\
Deprecated since 0.9\n\
\n\
Set the sample rate in Hz for the device. Typical values are\n\ Set the sample rate in Hz for the device. Typical values are\n\
8000 (telephony), 11025, 44100 (CD), 48000 (DVD audio) and 96000"); 8000 (telephony), 11025, 44100 (CD), 48000 (DVD audio) and 96000");
@@ -709,8 +966,9 @@ Set the sample rate in Hz for the device. Typical values are\n\
static PyObject * static PyObject *
alsapcm_setformat(alsapcm_t *self, PyObject *args) alsapcm_setformat(alsapcm_t *self, PyObject *args)
{ {
int format; int format, saved;
int res; int res;
if (!PyArg_ParseTuple(args,"i:setformat", &format)) if (!PyArg_ParseTuple(args,"i:setformat", &format))
return NULL; return NULL;
@@ -720,10 +978,16 @@ alsapcm_setformat(alsapcm_t *self, PyObject *args)
return NULL; return NULL;
} }
PyErr_WarnEx(PyExc_DeprecationWarning,
"This function is deprecated. "
"Please use the named parameter `format` to `PCM()` instead", 1);
saved = self->format;
self->format = format; self->format = format;
res = alsapcm_setup(self); res = alsapcm_setup(self);
if (res < 0) if (res < 0)
{ {
self->format = saved;
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname); self->cardname);
return NULL; return NULL;
@@ -732,12 +996,14 @@ alsapcm_setformat(alsapcm_t *self, PyObject *args)
} }
PyDoc_STRVAR(setformat_doc, PyDoc_STRVAR(setformat_doc,
"setformat(rate)\n"); "setformat(rate)\n\
\n\
Deprecated since 0.9");
static PyObject * static PyObject *
alsapcm_setperiodsize(alsapcm_t *self, PyObject *args) alsapcm_setperiodsize(alsapcm_t *self, PyObject *args)
{ {
int periodsize; int periodsize, saved;
int res; int res;
if (!PyArg_ParseTuple(args,"i:setperiodsize", &periodsize)) if (!PyArg_ParseTuple(args,"i:setperiodsize", &periodsize))
@@ -749,21 +1015,32 @@ alsapcm_setperiodsize(alsapcm_t *self, PyObject *args)
return NULL; return NULL;
} }
PyErr_WarnEx(PyExc_DeprecationWarning,
"This function is deprecated. "
"Please use the named parameter `periodsize` to `PCM()` instead", 1);
saved = self->periodsize;
self->periodsize = periodsize; self->periodsize = periodsize;
res = alsapcm_setup(self); res = alsapcm_setup(self);
if (res < 0) if (res < 0)
{ {
self->periodsize = saved;
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res), PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname); self->cardname);
return NULL; return NULL;
} }
return PyLong_FromLong(self->periodsize); return PyLong_FromLong(self->periodsize);
} }
PyDoc_STRVAR(setperiodsize_doc, PyDoc_STRVAR(setperiodsize_doc,
"setperiodsize(period) -> int\n\ "setperiodsize(period) -> int\n\
\n\ \n\
Deprecated since 0.9\n\
\n\
Sets the actual period size in frames. Each write should consist of\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\ 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\ frames (unless the device is in PCM_NONBLOCK mode, in which case it\n\
@@ -773,14 +1050,9 @@ 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;
@@ -797,6 +1069,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)
@@ -816,17 +1100,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\
@@ -956,6 +1264,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)
{ {
@@ -1026,9 +1371,14 @@ static PyMethodDef alsapcm_methods[] = {
{"setperiodsize", (PyCFunction)alsapcm_setperiodsize, METH_VARARGS, {"setperiodsize", (PyCFunction)alsapcm_setperiodsize, METH_VARARGS,
setperiodsize_doc}, 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},
@@ -1512,43 +1862,43 @@ alsamixer_switchcap(alsamixer_t *self, PyObject *args)
} }
result = PyList_New(0); result = PyList_New(0);
if (self->volume_cap & MIXER_CAP_SWITCH) if (self->switch_cap & MIXER_CAP_SWITCH)
{ {
item = PyUnicode_FromString("Mute"); item = PyUnicode_FromString("Mute");
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
if (self->volume_cap & MIXER_CAP_SWITCH_JOINED) if (self->switch_cap & MIXER_CAP_SWITCH_JOINED)
{ {
item = PyUnicode_FromString("Joined Mute"); item = PyUnicode_FromString("Joined Mute");
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
if (self->volume_cap & MIXER_CAP_PSWITCH) if (self->switch_cap & MIXER_CAP_PSWITCH)
{ {
item = PyUnicode_FromString("Playback Mute"); item = PyUnicode_FromString("Playback Mute");
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
if (self->volume_cap & MIXER_CAP_PSWITCH_JOINED) if (self->switch_cap & MIXER_CAP_PSWITCH_JOINED)
{ {
item = PyUnicode_FromString("Joined Playback Mute"); item = PyUnicode_FromString("Joined Playback Mute");
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
if (self->volume_cap & MIXER_CAP_CSWITCH) if (self->switch_cap & MIXER_CAP_CSWITCH)
{ {
item = PyUnicode_FromString("Capture Mute"); item = PyUnicode_FromString("Capture Mute");
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
if (self->volume_cap & MIXER_CAP_CSWITCH_JOINED) if (self->switch_cap & MIXER_CAP_CSWITCH_JOINED)
{ {
item = PyUnicode_FromString("Joined Capture Mute"); item = PyUnicode_FromString("Joined Capture Mute");
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
if (self->volume_cap & MIXER_CAP_CSWITCH_EXCLUSIVE) if (self->switch_cap & MIXER_CAP_CSWITCH_EXCLUSIVE)
{ {
item = PyUnicode_FromString("Capture Exclusive"); item = PyUnicode_FromString("Capture Exclusive");
PyList_Append(result, item); PyList_Append(result, item);
@@ -1573,31 +1923,21 @@ Possible values in this list are:\n\
- 'Capture Exclusive'\n"); - 'Capture Exclusive'\n");
static double alsamixer_getpercentage(long min, long max, long value) static int alsamixer_getpercentage(long min, long max, long value)
{ {
/* Convert from number in range to percentage */ /* Convert from number in range to percentage */
int range = max - min; int range = max - min;
int tmp;
if (range == 0) if (range == 0)
return 0; return 0;
value -= min; value -= min;
return (double)value/(double)range * 100.0; tmp = rint((double)value/(double)range * 100);
return tmp;
} }
static double alsamixer_getdB(long min, long max, long value) static long alsamixer_getphysvolume(long min, long max, int percentage)
{
/* Convert from number in range to dB */
int range = max - min;
if (range == 0)
return 0;
value -= min;
return log10((double)value/range) * 60.0;
}
static long alsamixer_getphysvolume(long min, long max, double percentage)
{ {
/* Convert from percentage to number in range */ /* Convert from percentage to number in range */
int range = max - min; int range = max - min;
@@ -1606,76 +1946,57 @@ static long alsamixer_getphysvolume(long min, long max, double percentage)
if (range == 0) if (range == 0)
return 0; return 0;
tmp = rint((double)range * (percentage * .01)) + min; tmp = rint((double)range * ((double)percentage*.01)) + min;
return tmp; return tmp;
} }
static PyObject * static PyObject *
alsamixer_getvolume(alsamixer_t *self, PyObject *args, PyObject *kw) alsamixer_getvolume(alsamixer_t *self, PyObject *args)
{ {
snd_mixer_elem_t *elem; snd_mixer_elem_t *elem;
int channel; int channel;
long ival; long ival;
PyObject *dirobj = NULL; PyObject *pcmtypeobj = NULL;
long dir; long pcmtype;
int unit = unit_percent;
PyObject *result; PyObject *result;
PyObject *item; PyObject *item;
static char *kwlist[] = { "direction", "unit", NULL }; if (!PyArg_ParseTuple(args,"|O:getvolume", &pcmtypeobj))
if (!PyArg_ParseTupleAndKeywords(args, kw, "|Oi:getvolume", kwlist, &dirobj, &unit))
return NULL; return NULL;
if (unit >= unit_last) {
PyErr_SetString(PyExc_ValueError, "unit must be 'percent' or 'dB'");
return NULL;
}
dir = get_pcmtype(dirobj);
if (dir < 0) {
return NULL;
}
if (!self->handle) if (!self->handle)
{ {
PyErr_SetString(ALSAAudioError, "Mixer is closed"); PyErr_SetString(ALSAAudioError, "Mixer is closed");
return NULL; return NULL;
} }
pcmtype = get_pcmtype(pcmtypeobj);
if (pcmtype < 0) {
return NULL;
}
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
result = PyList_New(0); result = PyList_New(0);
for (channel = 0; channel <= SND_MIXER_SCHN_LAST; channel++) { for (channel = 0; channel <= SND_MIXER_SCHN_LAST; channel++) {
if (dir == SND_PCM_STREAM_PLAYBACK && if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
snd_mixer_selem_has_playback_channel(elem, channel)) snd_mixer_selem_has_playback_channel(elem, channel))
{ {
snd_mixer_selem_get_playback_volume(elem, channel, &ival); snd_mixer_selem_get_playback_volume(elem, channel, &ival);
if (unit == unit_percent) { item = PyLong_FromLong(alsamixer_getpercentage(self->pmin,
item = PyFloat_FromDouble( self->pmax,
alsamixer_getpercentage(self->pmin, self->pmax, ival)); ival));
}
else {
item = PyFloat_FromDouble(
alsamixer_getdB(self->pmin, self->pmax, ival));
}
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
else if (dir == SND_PCM_STREAM_CAPTURE else if (pcmtype == SND_PCM_STREAM_CAPTURE
&& snd_mixer_selem_has_capture_channel(elem, channel) && snd_mixer_selem_has_capture_channel(elem, channel)
&& snd_mixer_selem_has_capture_volume(elem)) && snd_mixer_selem_has_capture_volume(elem)) {
{
snd_mixer_selem_get_capture_volume(elem, channel, &ival); snd_mixer_selem_get_capture_volume(elem, channel, &ival);
if (unit == unit_percent) { item = PyLong_FromLong(alsamixer_getpercentage(self->cmin,
item = PyFloat_FromDouble( self->cmax,
alsamixer_getpercentage(self->cmin, self->cmax, ival)); ival));
}
else {
item = PyFloat_FromDouble(
alsamixer_getdB(self->cmin, self->cmax, ival));
}
PyList_Append(result, item); PyList_Append(result, item);
Py_DECREF(item); Py_DECREF(item);
} }
@@ -1685,17 +2006,15 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
} }
PyDoc_STRVAR(getvolume_doc, PyDoc_STRVAR(getvolume_doc,
"getvolume(direction=PCM_PLAYBACK, unit=Percent) -> List of volume settings (float)\n\ "getvolume([pcmtype]) -> List of volume settings (int)\n\
\n\ \n\
Returns a list with the current volume settings for each channel.\n\ Returns a list with the current volume settings for each channel.\n\
The list elements are float percentages.\n\ The list elements are integer percentages.\n\
\n\ \n\
The 'direction' argument can be either PCM_PLAYBACK or\n\ The optional 'pcmtype' argument can be either PCM_PLAYBACK or\n\
PCM_CAPTURE, which is relevant if the mixer can control both\n\ PCM_CAPTURE, which is relevant if the mixer can control both\n\
playback and capture volume. The default value is PCM_PLAYBACK\n\ playback and capture volume. The default value is PCM_PLAYBACK\n\
if the mixer has this capability, otherwise PCM_CAPTURE\ if the mixer has this capability, otherwise PCM_CAPTURE");
\n\
The optional 'unit' argument can be either 'percent' or 'dB'.");
static PyObject * static PyObject *
@@ -2021,53 +2340,29 @@ This method will fail if the mixer has no capture switch capabilities.");
static PyObject * static PyObject *
alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw) alsamixer_setvolume(alsamixer_t *self, PyObject *args)
{ {
snd_mixer_elem_t *elem; snd_mixer_elem_t *elem;
int i; int i;
double volume = 0.0; long volume;
PyObject *volumeobj = NULL;
int physvolume; int physvolume;
PyObject *dirobj = NULL; PyObject *pcmtypeobj = NULL;
long dir; long pcmtype;
int unit = unit_percent;
int channel = MIXER_CHANNEL_ALL; int channel = MIXER_CHANNEL_ALL;
int done = 0; int done = 0;
static char *kwlist[] = { "channel", "direction", "unit", NULL }; if (!PyArg_ParseTuple(args,"l|iO:setvolume", &volume, &channel,
&pcmtypeobj))
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|iOi:setvolume", kwlist, &volumeobj, &channel,
&dirobj, &unit)) {
return NULL; return NULL;
}
// unit if (volume < 0 || volume > 100)
if (unit >= unit_last) {
PyErr_SetString(PyExc_ValueError, "unit must be 'percent' or 'dB'");
return NULL;
}
if (PyLong_Check(volumeobj)) {
volume = (double)PyLong_AsLong(volumeobj);
}
else if (PyFloat_Check(volumeobj)) {
volume = PyFloat_AsDouble(volumeobj);
}
else {
PyErr_SetString(PyExc_ValueError, "Volume must be integer or float");
return NULL;
}
if (unit == unit_percent && (volume < 0.0 || volume > 100.0))
{ {
PyErr_SetString(PyExc_ValueError, "Volume in percent must be between 0 and 100"); PyErr_SetString(ALSAAudioError, "Volume must be between 0 and 100");
return NULL; return NULL;
} }
// pcmtype pcmtype = get_pcmtype(pcmtypeobj);
dir = get_pcmtype(dirobj); if (pcmtype < 0) {
if (dir < 0) {
return NULL; return NULL;
} }
@@ -2079,42 +2374,32 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid); elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
if (!dirobj || (dirobj == Py_None)) if (!pcmtypeobj || (pcmtypeobj == Py_None))
{ {
if (self->pchannels) if (self->pchannels)
dir = SND_PCM_STREAM_PLAYBACK; pcmtype = SND_PCM_STREAM_PLAYBACK;
else else
dir = SND_PCM_STREAM_CAPTURE; pcmtype = SND_PCM_STREAM_CAPTURE;
} }
for (i = 0; i <= SND_MIXER_SCHN_LAST; i++) for (i = 0; i <= SND_MIXER_SCHN_LAST; i++)
{ {
if (channel == -1 || channel == i) if (channel == -1 || channel == i)
{ {
if (dir == SND_PCM_STREAM_PLAYBACK && if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
snd_mixer_selem_has_playback_channel(elem, i)) { snd_mixer_selem_has_playback_channel(elem, i)) {
if (unit == unit_percent) {
physvolume = alsamixer_getphysvolume(self->pmin, physvolume = alsamixer_getphysvolume(self->pmin,
self->pmax, volume); self->pmax, volume);
snd_mixer_selem_set_playback_volume(elem, i, physvolume); snd_mixer_selem_set_playback_volume(elem, i, physvolume);
}
else {
snd_mixer_selem_set_playback_dB(elem, i, (long)(volume * 100.0), 0);
}
done++; done++;
} }
else if (dir == SND_PCM_STREAM_CAPTURE else if (pcmtype == SND_PCM_STREAM_CAPTURE
&& snd_mixer_selem_has_capture_channel(elem, i) && snd_mixer_selem_has_capture_channel(elem, i)
&& snd_mixer_selem_has_capture_volume(elem)) && snd_mixer_selem_has_capture_volume(elem))
{ {
if (unit == unit_percent) {
physvolume = alsamixer_getphysvolume(self->cmin, self->cmax, physvolume = alsamixer_getphysvolume(self->cmin, self->cmax,
volume); volume);
snd_mixer_selem_set_capture_volume(elem, i, physvolume); snd_mixer_selem_set_capture_volume(elem, i, physvolume);
}
else {
snd_mixer_selem_set_capture_dB(elem, i, (long)(volume * 100), 0);
}
done++; done++;
} }
} }
@@ -2132,21 +2417,19 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
} }
PyDoc_STRVAR(setvolume_doc, PyDoc_STRVAR(setvolume_doc,
"setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit='percent')\n\ "setvolume(volume[[, channel] [, pcmtype]])\n\
\n\ \n\
Change the current volume settings for this mixer. The volume argument\n\ Change the current volume settings for this mixer. The volume argument\n\
controls the new volume setting as a percentage.\n\ controls the new volume setting as an integer percentage.\n\
If the optional argument channel is present, the volume is set only for\n\ If the optional argument channel is present, the volume is set only for\n\
this channel. This assumes that the mixer can control the volume for the\n\ this channel. This assumes that the mixer can control the volume for the\n\
channels independently.\n\ channels independently.\n\
\n\ \n\
The optional 'direction' argument can be either PCM_PLAYBACK or PCM_CAPTURE.\n\ The optional direction argument can be either PCM_PLAYBACK or PCM_CAPTURE.\n\
It is relevant if the mixer has independent playback and capture volume\n\ It is relevant if the mixer has independent playback and capture volume\n\
capabilities, and controls which of the volumes will be changed.\n\ capabilities, and controls which of the volumes will be changed.\n\
The default is PCM_PLAYBACK if the mixer has this capability, otherwise\n\ The default is 'playback' if the mixer has this capability, otherwise\n\
PCM_CAPTURE.\n\ 'capture'.");
\n\
The optional 'unit' argument can be either 'percent' (the default) or 'dB'.");
static PyObject * static PyObject *
@@ -2157,7 +2440,6 @@ alsamixer_setmute(alsamixer_t *self, PyObject *args)
int mute = 0; int mute = 0;
int done = 0; int done = 0;
int channel = MIXER_CHANNEL_ALL; int channel = MIXER_CHANNEL_ALL;
if (!PyArg_ParseTuple(args,"i|i:setmute", &mute, &channel)) if (!PyArg_ParseTuple(args,"i|i:setmute", &mute, &channel))
return NULL; return NULL;
@@ -2365,13 +2647,13 @@ static PyMethodDef alsamixer_methods[] = {
switchcap_doc}, switchcap_doc},
{"volumecap", (PyCFunction)alsamixer_volumecap, METH_VARARGS, {"volumecap", (PyCFunction)alsamixer_volumecap, METH_VARARGS,
volumecap_doc}, volumecap_doc},
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS | METH_KEYWORDS, {"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS,
getvolume_doc}, getvolume_doc},
{"getrange", (PyCFunction)alsamixer_getrange, METH_VARARGS, getrange_doc}, {"getrange", (PyCFunction)alsamixer_getrange, METH_VARARGS, getrange_doc},
{"getenum", (PyCFunction)alsamixer_getenum, METH_VARARGS, getenum_doc}, {"getenum", (PyCFunction)alsamixer_getenum, METH_VARARGS, getenum_doc},
{"getmute", (PyCFunction)alsamixer_getmute, METH_VARARGS, getmute_doc}, {"getmute", (PyCFunction)alsamixer_getmute, METH_VARARGS, getmute_doc},
{"getrec", (PyCFunction)alsamixer_getrec, METH_VARARGS, getrec_doc}, {"getrec", (PyCFunction)alsamixer_getrec, METH_VARARGS, getrec_doc},
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS | METH_KEYWORDS, {"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS,
setvolume_doc}, setvolume_doc},
{"setenum", (PyCFunction)alsamixer_setenum, METH_VARARGS, setenum_doc}, {"setenum", (PyCFunction)alsamixer_setenum, METH_VARARGS, setenum_doc},
{"setmute", (PyCFunction)alsamixer_setmute, METH_VARARGS, setmute_doc}, {"setmute", (PyCFunction)alsamixer_setmute, METH_VARARGS, setmute_doc},
@@ -2521,9 +2803,6 @@ PyObject *PyInit_alsaaudio(void)
Py_INCREF(ALSAAudioError); Py_INCREF(ALSAAudioError);
PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError); PyModule_AddObject(m, "ALSAAudioError", ALSAAudioError);
_EXPORT_INT(m, "Percent", unit_percent);
_EXPORT_INT(m, "dB", unit_dB);
_EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK); _EXPORT_INT(m, "PCM_PLAYBACK",SND_PCM_STREAM_PLAYBACK);
_EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE); _EXPORT_INT(m, "PCM_CAPTURE",SND_PCM_STREAM_CAPTURE);
+19
View File
@@ -1,3 +1,22 @@
# Make a new release
Update the version in setup.py
pyalsa_version = '0.9.0'
Commit and push the update.
Create and push a tag naming the version (i.e. 0.9.0):
git tag 0.9.0
git push origin 0.9.0
Upload the package:
python3 setup.py sdist
Don't forget to update the documentation.
# Publish the documentation # Publish the documentation
The documentation is published through the `gh-pages` branch. The documentation is published through the `gh-pages` branch.
+71 -86
View File
@@ -63,7 +63,6 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
useful. If you want to see a list of available PCM devices, use :func:`pcms` useful. If you want to see a list of available PCM devices, use :func:`pcms`
instead. instead.
.. function:: mixers(cardindex=-1, device='default') .. function:: mixers(cardindex=-1, device='default')
List the available mixers. The arguments are: List the available mixers. The arguments are:
@@ -108,7 +107,7 @@ PCM objects in :mod:`alsaaudio` can play or capture (record) PCM
sound through speakers or a microphone. The PCM constructor takes the sound through speakers or a microphone. The PCM constructor takes the
following arguments: following arguments:
.. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1) .. class:: PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, rate=44100, channels=2, format=PCM_FORMAT_S16_LE, periodsize=32, device='default', cardindex=-1)
This class is used to represent a PCM device (either for playback and This class is used to represent a PCM device (either for playback and
recording). The arguments are: recording). The arguments are:
@@ -117,72 +116,11 @@ following arguments:
(default). (default).
* *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL` * *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL`
(default). (default).
* *device* - the name of the PCM device that should be used (for example * *rate* - the sampling rate in Hz. Typical values are ``8000``
a value from the output of :func:`pcms`). The default value is (mainly used for telephony), ``16000``, ``44100`` (default), ``48000`` and ``96000``.
``'default'``. * *channels* - the number of channels. The default value is 2 (stereo).
* *cardindex* - the card index. If this argument is given, the device name * *format* - the data format. This controls how the PCM device interprets data for playback, and how data is encoded in captures.
is constructed as 'hw:*cardindex*' and The default value is :const:`PCM_FORMAT_S16_LE`.
the `device` keyword argument is ignored.
``0`` is the first hardware sound card.
This will construct a PCM object with these default settings:
* Sample format: :const:`PCM_FORMAT_S16_LE`
* Rate: 44100 Hz
* Channels: 2
* Period size: 32 frames
*Changed in 0.8:*
- The `card` keyword argument is still supported,
but deprecated. Please use `device` instead.
- The keyword argument `cardindex` was added.
The `card` keyword is deprecated because it guesses the real ALSA
name of the card. This was always fragile and broke some legitimate usecases.
PCM objects have the following methods:
.. method:: PCM.pcmtype()
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
:const:`PCM_PLAYBACK`.
.. method:: PCM.pcmmode()
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
.. method:: PCM.cardname()
Return the name of the sound card used by this PCM object.
.. method:: PCM.setchannels(nchannels)
Used to set the number of capture or playback channels. Common
values are: ``1`` = mono, ``2`` = stereo, and ``6`` = full 6 channel audio.
Few sound cards support more than 2 channels
.. method:: PCM.setrate(rate)
Set the sample rate in Hz for the device. Typical values are ``8000``
(mainly used for telephony), ``16000``, ``44100`` (CD quality),
``48000`` and ``96000``.
.. method:: PCM.setformat(format)
The sound *format* of the device. Sound format controls how the PCM device
interpret data for playback, and how data is encoded in captures.
The following formats are provided by ALSA:
========================= =============== ========================= ===============
Format Description Format Description
@@ -216,14 +154,65 @@ PCM objects have the following methods:
``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes) ``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes)
========================= =============== ========================= ===============
* *periodsize* - the period size in frames. Each write should consist of *periodsize* frames. The default value is 32.
* *device* - the name of the PCM device that should be used (for example
a value from the output of :func:`pcms`). The default value is
``'default'``.
* *cardindex* - the card index. If this argument is given, the device name
is constructed as 'hw:*cardindex*' and
the `device` keyword argument is ignored.
``0`` is the first hardware sound card.
This will construct a PCM object with the given settings.
*Changed in 0.9:*
- Added the optional named parameters `rate`, `channels`, `format` and `periodsize`.
*Changed in 0.8:*
- The `card` keyword argument is still supported,
but deprecated. Please use `device` instead.
- The keyword argument `cardindex` was added.
The `card` keyword is deprecated because it guesses the real ALSA
name of the card. This was always fragile and broke some legitimate usecases.
PCM objects have the following methods:
.. method:: PCM.pcmtype()
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
:const:`PCM_PLAYBACK`.
.. method:: PCM.pcmmode()
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
.. method:: PCM.cardname()
Return the name of the sound card used by this PCM object.
.. method:: PCM.setchannels(nchannels)
.. deprecated:: 0.9 Use the `channels` named argument to :func:`PCM`.
.. method:: PCM.setrate(rate)
.. deprecated:: 0.9 Use the `rate` named argument to :func:`PCM`.
.. method:: PCM.setformat(format)
.. deprecated:: 0.9 Use the `format` named argument to :func:`PCM`.
.. method:: PCM.setperiodsize(period) .. method:: PCM.setperiodsize(period)
Sets the actual period size in frames. Each write should consist of .. deprecated:: 0.9 Use the `periodsize` named argument to :func:`PCM`.
exactly this number of frames, and each read will return this
number of frames (unless the device is in :const:`PCM_NONBLOCK` mode, in
which case it may return nothing at all)
.. method:: PCM.read() .. method:: PCM.read()
@@ -443,35 +432,31 @@ Mixer objects have the following methods:
This method will fail if the mixer has no capture switch capabilities. This method will fail if the mixer has no capture switch capabilities.
.. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent) .. method:: Mixer.getvolume([direction])
Returns a list with the current volume settings for each channel. The list Returns a list with the current volume settings for each channel. The list
elements are percentages or dB values, depending on *unit*. elements are integer percentages.
The *direction* argument can be either :const:`PCM_PLAYBACK` or The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both :const:`PCM_CAPTURE`, which is relevant if the mixer can control both
playback and capture volume. The default value is :const:`PCM_PLAYBACK` playback and capture volume. The default value is :const:`PCM_PLAYBACK`
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`. if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
.. method:: Mixer.setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit=Percent) .. method:: Mixer.setvolume(volume, [channel], [direction])
Change the current volume settings for this mixer. The *volume* argument Change the current volume settings for this mixer. The *volume* argument
controls the new volume setting as either a percentage or a dB value. Both controls the new volume setting as an integer percentage.
integer and floating point values can be given.
The *channel* argument can be used to restrict the channels for which the volume is If the optional argument *channel* is present, the volume is set
set. By default, the volume of all channels is adjusted. This assumes that the mixer only for this channel. This assumes that the mixer can control the
can control the volume for the channels independently. volume for the channels independently.
The *direction* argument can be either :const:`PCM_PLAYBACK` or The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both :const:`PCM_CAPTURE`, which is relevant if the mixer can control both
playback and capture volume. The default value is :const:`PCM_PLAYBACK` playback and capture volume. The default value is :const:`PCM_PLAYBACK`
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`. if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
The *unit* argument determines how the volume value is interpreted, as a prcentage
or as a dB value.
.. method:: Mixer.setmute(mute, [channel]) .. method:: Mixer.setmute(mute, [channel])
Sets the mute flag to a new value. The *mute* argument is either 0 for not Sets the mute flag to a new value. The *mute* argument is either 0 for not
+1 -1
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
+1 -4
View File
@@ -56,10 +56,7 @@ class SinePlayer(Thread):
def __init__(self, frequency = 440.0): def __init__(self, frequency = 440.0):
Thread.__init__(self) Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
self.device = alsaaudio.PCM() self.device = alsaaudio.PCM(channels=channels, format=format, rate=sampling_rate)
self.device.setchannels(channels)
self.device.setformat(format)
self.device.setrate(sampling_rate)
self.queue = Queue() self.queue = Queue()
self.change(frequency) self.change(frequency)
+6 -10
View File
@@ -46,17 +46,13 @@ def show_mixer(name, kwargs):
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()), print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
' '.join(mixer.switchcap()))) ' '.join(mixer.switchcap())))
volumes = mixer.getvolume() volumes = mixer.getvolume()
for i, v in enumerate(volumes): for i in range(len(volumes)):
print("Channel %i volume: %.02f%%" % (i, v)) print("Channel %i volume: %i%%" % (i,volumes[i]))
volumes = mixer.getvolume(unit=alsaaudio.dB)
for i, v in enumerate(volumes):
print("Channel %i volume: %.02fdB" % (i, v))
try: try:
mutes = mixer.getmute() mutes = mixer.getmute()
for i, m in enumerate(mutes): for i in range(len(mutes)):
if m: if mutes[i]:
print("Channel %i is muted" % i) print("Channel %i is muted" % i)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
# May not support muting # May not support muting
@@ -64,8 +60,8 @@ def show_mixer(name, kwargs):
try: try:
recs = mixer.getrec() recs = mixer.getrec()
for i, r in enumerate(recs): for i in range(len(recs)):
if r: if recs[i]:
print("Channel %i is recording" % i) print("Channel %i is recording" % i)
except alsaaudio.ALSAAudioError: except alsaaudio.ALSAAudioError:
# May not support recording # May not support recording
+4 -10
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
## playbacktest.py ## playbacktest.py
## ##
@@ -38,18 +39,11 @@ if __name__ == '__main__':
f = open(args[0], 'rb') f = open(args[0], 'rb')
# Open the device in playback mode. # Open the device in playback mode in Mono, 44100 Hz, 16 bit little endian frames
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device=device)
# Set attributes: Mono, 44100 Hz, 16 bit little endian frames
out.setchannels(1)
out.setrate(44100)
out.setformat(alsaaudio.PCM_FORMAT_S16_LE)
# The period size controls the internal number of frames per period. # The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api. # The significance of this parameter is documented in the ALSA api.
out.setperiodsize(160)
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=160, device=device)
# Read data from stdin # Read data from stdin
data = f.read(320) data = f.read(320)
while data: while data:
+15 -17
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
# Simple test script that plays (some) wav files # Simple test script that plays (some) wav files
@@ -11,28 +12,29 @@ import alsaaudio
def play(device, f): def play(device, f):
print('%d channels, %d sampling rate\n' % (f.getnchannels(), format = None
f.getframerate()))
# Set attributes
device.setchannels(f.getnchannels())
device.setrate(f.getframerate())
# 8bit is unsigned in wav files # 8bit is unsigned in wav files
if f.getsampwidth() == 1: if f.getsampwidth() == 1:
device.setformat(alsaaudio.PCM_FORMAT_U8) format = alsaaudio.PCM_FORMAT_U8
# Otherwise we assume signed data, little endian # Otherwise we assume signed data, little endian
elif f.getsampwidth() == 2: elif f.getsampwidth() == 2:
device.setformat(alsaaudio.PCM_FORMAT_S16_LE) format = alsaaudio.PCM_FORMAT_S16_LE
elif f.getsampwidth() == 3: elif f.getsampwidth() == 3:
device.setformat(alsaaudio.PCM_FORMAT_S24_3LE) format = alsaaudio.PCM_FORMAT_S24_3LE
elif f.getsampwidth() == 4: elif f.getsampwidth() == 4:
device.setformat(alsaaudio.PCM_FORMAT_S32_LE) format = alsaaudio.PCM_FORMAT_S32_LE
else: else:
raise ValueError('Unsupported format') raise ValueError('Unsupported format')
periodsize = f.getframerate() / 8 periodsize = f.getframerate() // 8
device.setperiodsize(periodsize) print('%d channels, %d sampling rate, format %d, periodsize %d\n' % (f.getnchannels(),
f.getframerate(),
format,
periodsize))
device = alsaaudio.PCM(channels=f.getnchannels(), rate=f.getframerate(), format=format, periodsize=periodsize, device=device)
data = f.readframes(periodsize) data = f.readframes(periodsize)
while data: while data:
@@ -57,9 +59,5 @@ if __name__ == '__main__':
if not args: if not args:
usage() usage()
f = wave.open(args[0], 'rb') with wave.open(args[0], 'rb') as f:
device = alsaaudio.PCM(device=device)
play(device, f) play(device, f)
f.close()
+7 -12
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
## recordtest.py ## recordtest.py
## ##
@@ -39,16 +40,8 @@ if __name__ == '__main__':
f = open(args[0], 'wb') f = open(args[0], 'wb')
# Open the device in nonblocking capture mode. The last argument could # Open the device in nonblocking capture mode in mono, with a sampling rate of 44100 Hz
# just as well have been zero for blocking mode. Then we could have # and 16 bit little endian samples
# left out the sleep call in the bottom of the loop
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, device=device)
# Set attributes: Mono, 44100 Hz, 16 bit little endian samples
inp.setchannels(1)
inp.setrate(44100)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
# The period size controls the internal number of frames per period. # The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api. # The significance of this parameter is documented in the ALSA api.
# For our purposes, it is suficcient to know that reads from the device # For our purposes, it is suficcient to know that reads from the device
@@ -56,7 +49,9 @@ if __name__ == '__main__':
# This means that the reads below will return either 320 bytes of data # 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 # or 0 bytes of data. The latter is possible because we are in nonblocking
# mode. # mode.
inp.setperiodsize(160) inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK,
channels=1, rate=44100, format=alsaaudio.PCM_FORMAT_S16_LE,
periodsize=160, device=device)
loops = 1000000 loops = 1000000
while loops > 0: while loops > 0:
+1 -1
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.4' pyalsa_version = '0.9.0'
if __name__ == '__main__': if __name__ == '__main__':
setup( setup(
+30 -6
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- mode: python; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-
# These are internal tests. They shouldn't fail, but they don't cover all # These are internal tests. They shouldn't fail, but they don't cover all
# of the ALSA API. Most importantly PCM.read and PCM.write are missing. # of the ALSA API. Most importantly PCM.read and PCM.write are missing.
@@ -12,13 +13,18 @@ import alsaaudio
import warnings import warnings
# we can't test read and write well - these are tested otherwise # we can't test read and write well - these are tested otherwise
PCMMethods = [('pcmtype', None), PCMMethods = [
('pcmtype', None),
('pcmmode', None), ('pcmmode', None),
('cardname', None), ('cardname', None)
]
PCMDeprecatedMethods = [
('setchannels', (2,)), ('setchannels', (2,)),
('setrate', (44100,)), ('setrate', (44100,)),
('setformat', (alsaaudio.PCM_FORMAT_S8,)), ('setformat', (alsaaudio.PCM_FORMAT_S8,)),
('setperiodsize', (320,))] ('setperiodsize', (320,))
]
# A clever test would look at the Mixer capabilities and selectively run the # A clever test would look at the Mixer capabilities and selectively run the
# omitted tests, but I am too tired for that. # omitted tests, but I am too tired for that.
@@ -129,8 +135,26 @@ class PCMTest(unittest.TestCase):
pass pass
# Verify we got a DepreciationWarning # Verify we got a DepreciationWarning
assert len(w) == 1 self.assertEqual(len(w), 1, "PCM(card='default') expected a warning" )
assert issubclass(w[-1].category, DeprecationWarning) self.assertTrue(issubclass(w[-1].category, DeprecationWarning), "PCM(card='default') expected a DeprecationWarning")
for m, a in PCMDeprecatedMethods:
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
pcm = alsaaudio.PCM()
f = alsaaudio.PCM.__dict__[m]
if a is None:
f(pcm)
else:
f(pcm, *a)
# Verify we got a DepreciationWarning
method = "%s%s" % (m, str(a))
self.assertEqual(len(w), 1, method + " expected a warning")
self.assertTrue(issubclass(w[-1].category, DeprecationWarning), method + " expected a DeprecationWarning")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()