Compare commits

..

3 Commits

Author SHA1 Message Date
Lars Immisch 917a11b398 Report volume like alsamixer. Maybe.
I have no idea how they calculate dB.
2017-11-07 00:57:02 +01:00
Lars Immisch ce84e69cc1 Fix kwargs, and modernize mixertest.py a bit
Preliminary error handling for dB volume settings
2017-11-06 23:32:24 +01:00
Lars Immisch c2cfe0211b Add setting/getting volume in dB.
Potentially breaking change: getvolume now always returns a list of float values,
not integers as before.
2017-11-03 00:01:56 +01:00
7 changed files with 403 additions and 455 deletions
-29
View File
@@ -1,32 +1,3 @@
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:
- fix #3 (we cannot get the revision from git for pip installs)
+350 -381
View File
@@ -29,65 +29,6 @@
#include <alsa/version.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,
"This modules provides support for the ALSA audio API.\n"
"\n"
@@ -136,6 +77,12 @@ typedef struct {
snd_mixer_t *handle;
} alsamixer_t;
typedef enum {
unit_percent,
unit_dB,
unit_last
} volume_unit_t;
/******************************************/
/* PCM object wrapper */
/******************************************/
@@ -358,26 +305,74 @@ PyDoc_STRVAR(pcms_doc,
\n\
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 *
alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
int res;
alsapcm_t *self;
PyObject *pcmtypeobj = NULL;
long pcmtype;
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 *card = NULL;
int cardidx = -1;
char hw_device[128];
int latency = 200;
char *kw[] = { "type", "mode", "device", "cardindex", "card", "format", "rate", "channels", "latency", NULL };
char *kw[] = { "type", "mode", "device", "cardindex", "card", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiziiii", kw,
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiz", kw,
&pcmtypeobj, &pcmmode, &device,
&cardidx, &card, &rate, &format, &channels, &latency))
&cardidx, &card))
return NULL;
if (cardidx >= 0) {
@@ -410,9 +405,29 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
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,
self->pcmmode);
if (res >= 0) {
res = alsapcm_setup(self);
}
if (res >= 0) {
self->cardname = strdup(device);
}
@@ -431,7 +446,6 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static void alsapcm_dealloc(alsapcm_t *self)
{
if (self->handle) {
snd_pcm_pause(self->handle, 1);
snd_pcm_drain(self->handle);
snd_pcm_close(self->handle);
}
@@ -564,184 +578,6 @@ alsapcm_dumpinfo(alsapcm_t *self, PyObject *args)
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 *
alsapcm_pcmtype(alsapcm_t *self, PyObject *args)
{
@@ -804,13 +640,147 @@ PyDoc_STRVAR(cardname_doc,
\n\
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 *
alsapcm_read(alsapcm_t *self, PyObject *args)
{
int res;
int size = self->framesize * self->periodsize;
PyObject *buffer_obj, *tuple_obj, *res_obj;
char *buffer;
char buffer[8000];
if (self->framesize * self->periodsize > 8000) {
PyErr_SetString(ALSAAudioError,"Capture data too large. "
"Try decreasing period size");
return NULL;
}
if (!PyArg_ParseTuple(args,":read"))
return NULL;
@@ -827,18 +797,6 @@ alsapcm_read(alsapcm_t *self, PyObject *args)
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
res = snd_pcm_readi(self->handle, buffer, self->periodsize);
if (res == -EPIPE)
@@ -858,39 +816,15 @@ alsapcm_read(alsapcm_t *self, PyObject *args)
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
self->cardname);
Py_DECREF(buffer_obj);
return NULL;
}
}
if (res <= 0) {
#if PY_MAJOR_VERSION < 3
/* If the following fails, it will free the object */
if (_PyString_Resize(&buffer_obj, 0))
return NULL;
return Py_BuildValue("is#", res, buffer, res*self->framesize);
#else
/* If the following fails, it will free the object */
if (_PyBytes_Resize(&buffer_obj, 0))
return NULL;
return Py_BuildValue("iy#", res, buffer, res*self->framesize);
#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,
@@ -1022,43 +956,6 @@ PyDoc_STRVAR(pause_doc,
If enable is 1, playback or capture is paused. If enable is 0,\n\
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 *
alsapcm_polldescriptors(alsapcm_t *self, PyObject *args)
{
@@ -1122,15 +1019,16 @@ static PyMethodDef alsapcm_methods[] = {
{"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS, pcmtype_doc},
{"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS, pcmmode_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},
{"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},
{"write", (PyCFunction)alsapcm_write, METH_VARARGS, write_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},
{"polldescriptors", (PyCFunction)alsapcm_polldescriptors, METH_VARARGS,
pcm_polldescriptors_doc},
@@ -1675,21 +1573,31 @@ Possible values in this list are:\n\
- 'Capture Exclusive'\n");
static int alsamixer_getpercentage(long min, long max, long value)
static double alsamixer_getpercentage(long min, long max, long value)
{
/* Convert from number in range to percentage */
int range = max - min;
int tmp;
if (range == 0)
return 0;
value -= min;
tmp = rint((double)value/(double)range * 100);
return tmp;
return (double)value/(double)range * 100.0;
}
static long alsamixer_getphysvolume(long min, long max, int percentage)
static double alsamixer_getdB(long min, long max, long value)
{
/* 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 */
int range = max - min;
@@ -1698,57 +1606,76 @@ static long alsamixer_getphysvolume(long min, long max, int percentage)
if (range == 0)
return 0;
tmp = rint((double)range * ((double)percentage*.01)) + min;
tmp = rint((double)range * (percentage * .01)) + min;
return tmp;
}
static PyObject *
alsamixer_getvolume(alsamixer_t *self, PyObject *args)
alsamixer_getvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
{
snd_mixer_elem_t *elem;
int channel;
long ival;
PyObject *pcmtypeobj = NULL;
long pcmtype;
PyObject *dirobj = NULL;
long dir;
int unit = unit_percent;
PyObject *result;
PyObject *item;
if (!PyArg_ParseTuple(args,"|O:getvolume", &pcmtypeobj))
static char *kwlist[] = { "direction", "unit", NULL };
if (!PyArg_ParseTupleAndKeywords(args, kw, "|Oi:getvolume", kwlist, &dirobj, &unit))
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)
{
PyErr_SetString(ALSAAudioError, "Mixer is closed");
return NULL;
}
pcmtype = get_pcmtype(pcmtypeobj);
if (pcmtype < 0) {
return NULL;
}
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
result = PyList_New(0);
for (channel = 0; channel <= SND_MIXER_SCHN_LAST; channel++) {
if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
if (dir == SND_PCM_STREAM_PLAYBACK &&
snd_mixer_selem_has_playback_channel(elem, channel))
{
snd_mixer_selem_get_playback_volume(elem, channel, &ival);
item = PyLong_FromLong(alsamixer_getpercentage(self->pmin,
self->pmax,
ival));
if (unit == unit_percent) {
item = PyFloat_FromDouble(
alsamixer_getpercentage(self->pmin, self->pmax, ival));
}
else {
item = PyFloat_FromDouble(
alsamixer_getdB(self->pmin, self->pmax, ival));
}
PyList_Append(result, item);
Py_DECREF(item);
}
else if (pcmtype == SND_PCM_STREAM_CAPTURE
else if (dir == SND_PCM_STREAM_CAPTURE
&& 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);
item = PyLong_FromLong(alsamixer_getpercentage(self->cmin,
self->cmax,
ival));
if (unit == unit_percent) {
item = PyFloat_FromDouble(
alsamixer_getpercentage(self->cmin, self->cmax, ival));
}
else {
item = PyFloat_FromDouble(
alsamixer_getdB(self->cmin, self->cmax, ival));
}
PyList_Append(result, item);
Py_DECREF(item);
}
@@ -1758,15 +1685,17 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args)
}
PyDoc_STRVAR(getvolume_doc,
"getvolume([pcmtype]) -> List of volume settings (int)\n\
"getvolume(direction=PCM_PLAYBACK, unit=Percent) -> List of volume settings (float)\n\
\n\
Returns a list with the current volume settings for each channel.\n\
The list elements are integer percentages.\n\
The list elements are float percentages.\n\
\n\
The optional 'pcmtype' argument can be either PCM_PLAYBACK or\n\
The 'direction' argument can be either PCM_PLAYBACK or\n\
PCM_CAPTURE, which is relevant if the mixer can control both\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 *
@@ -2092,29 +2021,53 @@ This method will fail if the mixer has no capture switch capabilities.");
static PyObject *
alsamixer_setvolume(alsamixer_t *self, PyObject *args)
alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
{
snd_mixer_elem_t *elem;
int i;
long volume;
double volume = 0.0;
PyObject *volumeobj = NULL;
int physvolume;
PyObject *pcmtypeobj = NULL;
long pcmtype;
PyObject *dirobj = NULL;
long dir;
int unit = unit_percent;
int channel = MIXER_CHANNEL_ALL;
int done = 0;
if (!PyArg_ParseTuple(args,"l|iO:setvolume", &volume, &channel,
&pcmtypeobj))
return NULL;
static char *kwlist[] = { "channel", "direction", "unit", NULL };
if (volume < 0 || volume > 100)
{
PyErr_SetString(ALSAAudioError, "Volume must be between 0 and 100");
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|iOi:setvolume", kwlist, &volumeobj, &channel,
&dirobj, &unit)) {
return NULL;
}
pcmtype = get_pcmtype(pcmtypeobj);
if (pcmtype < 0) {
// unit
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");
return NULL;
}
// pcmtype
dir = get_pcmtype(dirobj);
if (dir < 0) {
return NULL;
}
@@ -2126,38 +2079,48 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
if (!pcmtypeobj || (pcmtypeobj == Py_None))
if (!dirobj || (dirobj == Py_None))
{
if (self->pchannels)
pcmtype = SND_PCM_STREAM_PLAYBACK;
dir = SND_PCM_STREAM_PLAYBACK;
else
pcmtype = SND_PCM_STREAM_CAPTURE;
dir = SND_PCM_STREAM_CAPTURE;
}
for (i = 0; i <= SND_MIXER_SCHN_LAST; i++)
{
if (channel == -1 || channel == i)
{
if (pcmtype == SND_PCM_STREAM_PLAYBACK &&
if (dir == SND_PCM_STREAM_PLAYBACK &&
snd_mixer_selem_has_playback_channel(elem, i)) {
if (unit == unit_percent) {
physvolume = alsamixer_getphysvolume(self->pmin,
self->pmax, volume);
snd_mixer_selem_set_playback_volume(elem, i, physvolume);
}
else {
snd_mixer_selem_set_playback_dB(elem, i, (long)(volume * 100.0), 0);
}
done++;
}
else if (pcmtype == SND_PCM_STREAM_CAPTURE
else if (dir == SND_PCM_STREAM_CAPTURE
&& snd_mixer_selem_has_capture_channel(elem, i)
&& snd_mixer_selem_has_capture_volume(elem))
{
if (unit == unit_percent) {
physvolume = alsamixer_getphysvolume(self->cmin, self->cmax,
volume);
snd_mixer_selem_set_capture_volume(elem, i, physvolume);
}
else {
snd_mixer_selem_set_capture_dB(elem, i, (long)(volume * 100), 0);
}
done++;
}
}
}
if(!done)
if (!done)
{
PyErr_Format(ALSAAudioError, "No such channel [%s]",
self->cardname);
@@ -2169,19 +2132,21 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
}
PyDoc_STRVAR(setvolume_doc,
"setvolume(volume[[, channel] [, pcmtype]])\n\
"setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit='percent')\n\
\n\
Change the current volume settings for this mixer. The volume argument\n\
controls the new volume setting as an integer percentage.\n\
controls the new volume setting as a percentage.\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\
channels independently.\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\
capabilities, and controls which of the volumes will be changed.\n\
The default is 'playback' if the mixer has this capability, otherwise\n\
'capture'.");
The default is PCM_PLAYBACK if the mixer has this capability, otherwise\n\
PCM_CAPTURE.\n\
\n\
The optional 'unit' argument can be either 'percent' (the default) or 'dB'.");
static PyObject *
@@ -2192,6 +2157,7 @@ alsamixer_setmute(alsamixer_t *self, PyObject *args)
int mute = 0;
int done = 0;
int channel = MIXER_CHANNEL_ALL;
if (!PyArg_ParseTuple(args,"i|i:setmute", &mute, &channel))
return NULL;
@@ -2399,13 +2365,13 @@ static PyMethodDef alsamixer_methods[] = {
switchcap_doc},
{"volumecap", (PyCFunction)alsamixer_volumecap, METH_VARARGS,
volumecap_doc},
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS,
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS | METH_KEYWORDS,
getvolume_doc},
{"getrange", (PyCFunction)alsamixer_getrange, METH_VARARGS, getrange_doc},
{"getenum", (PyCFunction)alsamixer_getenum, METH_VARARGS, getenum_doc},
{"getmute", (PyCFunction)alsamixer_getmute, METH_VARARGS, getmute_doc},
{"getrec", (PyCFunction)alsamixer_getrec, METH_VARARGS, getrec_doc},
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS,
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS | METH_KEYWORDS,
setvolume_doc},
{"setenum", (PyCFunction)alsamixer_setenum, METH_VARARGS, setenum_doc},
{"setmute", (PyCFunction)alsamixer_setmute, METH_VARARGS, setmute_doc},
@@ -2555,6 +2521,9 @@ PyObject *PyInit_alsaaudio(void)
Py_INCREF(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_CAPTURE",SND_PCM_STREAM_CAPTURE);
+13 -9
View File
@@ -443,31 +443,35 @@ Mixer objects have the following methods:
This method will fail if the mixer has no capture switch capabilities.
.. method:: Mixer.getvolume([direction])
.. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent)
Returns a list with the current volume settings for each channel. The list
elements are integer percentages.
elements are percentages or dB values, depending on *unit*.
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
The *direction* argument can be either :const:`PCM_PLAYBACK` or
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
.. method:: Mixer.setvolume(volume, [channel], [direction])
.. method:: Mixer.setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit=Percent)
Change the current volume settings for this mixer. The *volume* argument
controls the new volume setting as an integer percentage.
controls the new volume setting as either a percentage or a dB value. Both
integer and floating point values can be given.
If the optional argument *channel* is present, the volume is set
only for this channel. This assumes that the mixer can control the
volume for the channels independently.
The *channel* argument can be used to restrict the channels for which the volume is
set. By default, the volume of all channels is adjusted. This assumes that the mixer
can control the volume for the channels independently.
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
The *direction* argument can be either :const:`PCM_PLAYBACK` or
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
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])
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
included in the standard Python library, but that looks currently unlikely.
PyAlsaAudio has full support for sound capture, playback of sound, as well as
PyAlsaAudio hass full support for sound capture, playback of sound, as well as
the ALSA Mixer API.
MIDI support is not available, and since I don't own any MIDI hardware, it's
+10 -6
View File
@@ -46,13 +46,17 @@ def show_mixer(name, kwargs):
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
' '.join(mixer.switchcap())))
volumes = mixer.getvolume()
for i in range(len(volumes)):
print("Channel %i volume: %i%%" % (i,volumes[i]))
for i, v in enumerate(volumes):
print("Channel %i volume: %.02f%%" % (i, v))
volumes = mixer.getvolume(unit=alsaaudio.dB)
for i, v in enumerate(volumes):
print("Channel %i volume: %.02fdB" % (i, v))
try:
mutes = mixer.getmute()
for i in range(len(mutes)):
if mutes[i]:
for i, m in enumerate(mutes):
if m:
print("Channel %i is muted" % i)
except alsaaudio.ALSAAudioError:
# May not support muting
@@ -60,8 +64,8 @@ def show_mixer(name, kwargs):
try:
recs = mixer.getrec()
for i in range(len(recs)):
if recs[i]:
for i, r in enumerate(recs):
if r:
print("Channel %i is recording" % i)
except alsaaudio.ALSAAudioError:
# May not support recording
+1 -1
View File
@@ -30,7 +30,7 @@ def play(device, f):
else:
raise ValueError('Unsupported format')
periodsize = f.getframerate() // 8
periodsize = f.getframerate() / 8
device.setperiodsize(periodsize)
+1 -1
View File
@@ -8,7 +8,7 @@ from setuptools import setup
from setuptools.extension import Extension
from sys import version
pyalsa_version = '0.8.6'
pyalsa_version = '0.8.4'
if __name__ == '__main__':
setup(