mirror of
https://github.com/larsimmisch/pyalsaaudio.git
synced 2026-06-01 10:57:01 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 917a11b398 | |||
| ce84e69cc1 | |||
| c2cfe0211b |
@@ -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:
|
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)
|
||||||
|
|
||||||
|
|||||||
+349
-380
@@ -29,65 +29,6 @@
|
|||||||
#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"
|
||||||
@@ -136,6 +77,12 @@ 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 */
|
||||||
/******************************************/
|
/******************************************/
|
||||||
@@ -358,26 +305,74 @@ PyDoc_STRVAR(pcms_doc,
|
|||||||
\n\
|
\n\
|
||||||
List the available PCM devices");
|
List the available PCM devices");
|
||||||
|
|
||||||
|
static int alsapcm_setup(alsapcm_t *self)
|
||||||
|
{
|
||||||
|
int res,dir;
|
||||||
|
unsigned int val;
|
||||||
|
snd_pcm_format_t fmt;
|
||||||
|
snd_pcm_uframes_t frames;
|
||||||
|
snd_pcm_hw_params_t *hwparams;
|
||||||
|
|
||||||
|
/* Allocate a hwparam structure on the stack,
|
||||||
|
and fill it with configuration space */
|
||||||
|
snd_pcm_hw_params_alloca(&hwparams);
|
||||||
|
res = snd_pcm_hw_params_any(self->handle, hwparams);
|
||||||
|
if (res < 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
/* Fill it with default values.
|
||||||
|
|
||||||
|
We don't care if any of this fails - we'll read the actual values
|
||||||
|
back out.
|
||||||
|
*/
|
||||||
|
snd_pcm_hw_params_any(self->handle, hwparams);
|
||||||
|
snd_pcm_hw_params_set_access(self->handle, hwparams,
|
||||||
|
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||||
|
snd_pcm_hw_params_set_format(self->handle, hwparams, self->format);
|
||||||
|
snd_pcm_hw_params_set_channels(self->handle, hwparams,
|
||||||
|
self->channels);
|
||||||
|
|
||||||
|
dir = 0;
|
||||||
|
snd_pcm_hw_params_set_rate(self->handle, hwparams, self->rate, dir);
|
||||||
|
snd_pcm_hw_params_set_period_size(self->handle, hwparams,
|
||||||
|
self->periodsize, dir);
|
||||||
|
snd_pcm_hw_params_set_periods(self->handle, hwparams, 4, 0);
|
||||||
|
|
||||||
|
/* Write it to the device */
|
||||||
|
res = snd_pcm_hw_params(self->handle, hwparams);
|
||||||
|
|
||||||
|
/* Query current settings. These may differ from the requested values,
|
||||||
|
which should therefore be sync'ed with actual values */
|
||||||
|
snd_pcm_hw_params_current(self->handle, hwparams);
|
||||||
|
|
||||||
|
snd_pcm_hw_params_get_format(hwparams, &fmt); self->format = fmt;
|
||||||
|
snd_pcm_hw_params_get_channels(hwparams, &val); self->channels = val;
|
||||||
|
snd_pcm_hw_params_get_rate(hwparams, &val, &dir); self->rate = val;
|
||||||
|
snd_pcm_hw_params_get_period_size(hwparams, &frames, &dir);
|
||||||
|
self->periodsize = (int) frames;
|
||||||
|
|
||||||
|
self->framesize = self->channels * snd_pcm_hw_params_get_sbits(hwparams)/8;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
int res;
|
||||||
alsapcm_t *self;
|
alsapcm_t *self;
|
||||||
PyObject *pcmtypeobj = NULL;
|
PyObject *pcmtypeobj = NULL;
|
||||||
long pcmtype;
|
long pcmtype;
|
||||||
int pcmmode = 0;
|
int pcmmode = 0;
|
||||||
unsigned int rate = 48000;
|
|
||||||
unsigned int channels = 2;
|
|
||||||
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
|
|
||||||
char *device = "default";
|
char *device = "default";
|
||||||
char *card = NULL;
|
char *card = NULL;
|
||||||
int cardidx = -1;
|
int cardidx = -1;
|
||||||
char hw_device[128];
|
char hw_device[128];
|
||||||
int latency = 200;
|
char *kw[] = { "type", "mode", "device", "cardindex", "card", NULL };
|
||||||
char *kw[] = { "type", "mode", "device", "cardindex", "card", "format", "rate", "channels", "latency", NULL };
|
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiziiii", kw,
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oisiz", kw,
|
||||||
&pcmtypeobj, &pcmmode, &device,
|
&pcmtypeobj, &pcmmode, &device,
|
||||||
&cardidx, &card, &rate, &format, &channels, &latency))
|
&cardidx, &card))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (cardidx >= 0) {
|
if (cardidx >= 0) {
|
||||||
@@ -410,9 +405,29 @@ alsapcm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pcmmode < 0 || pcmmode > SND_PCM_ASYNC) {
|
||||||
|
PyErr_SetString(ALSAAudioError, "Invalid PCM mode");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(self = (alsapcm_t *)PyObject_New(alsapcm_t, &ALSAPCMType)))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
self->handle = 0;
|
||||||
|
self->pcmtype = pcmtype;
|
||||||
|
self->pcmmode = pcmmode;
|
||||||
|
self->channels = 2;
|
||||||
|
self->rate = 44100;
|
||||||
|
self->format = SND_PCM_FORMAT_S16_LE;
|
||||||
|
self->periodsize = 32;
|
||||||
|
|
||||||
res = snd_pcm_open(&(self->handle), device, self->pcmtype,
|
res = snd_pcm_open(&(self->handle), device, self->pcmtype,
|
||||||
self->pcmmode);
|
self->pcmmode);
|
||||||
|
|
||||||
|
if (res >= 0) {
|
||||||
|
res = alsapcm_setup(self);
|
||||||
|
}
|
||||||
|
|
||||||
if (res >= 0) {
|
if (res >= 0) {
|
||||||
self->cardname = strdup(device);
|
self->cardname = strdup(device);
|
||||||
}
|
}
|
||||||
@@ -431,7 +446,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -564,184 +578,6 @@ 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(¶ms);
|
|
||||||
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(¶ms);
|
|
||||||
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(¶ms);
|
|
||||||
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(¶ms);
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -804,13 +640,147 @@ PyDoc_STRVAR(cardname_doc,
|
|||||||
\n\
|
\n\
|
||||||
Returns the name of the sound card used by this PCM object.");
|
Returns the name of the sound card used by this PCM object.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsapcm_setchannels(alsapcm_t *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int channels;
|
||||||
|
int res;
|
||||||
|
if (!PyArg_ParseTuple(args,"i:setchannels", &channels))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!self->handle) {
|
||||||
|
PyErr_SetString(ALSAAudioError, "PCM device is closed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->channels = channels;
|
||||||
|
res = alsapcm_setup(self);
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
|
||||||
|
self->cardname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return PyLong_FromLong(self->channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(setchannels_doc,
|
||||||
|
"setchannels(numchannels)\n\
|
||||||
|
\n\
|
||||||
|
Used to set the number of capture or playback channels. Common values\n\
|
||||||
|
are: 1 = mono, 2 = stereo, and 6 = full 6 channel audio.\n\
|
||||||
|
\n\
|
||||||
|
Few sound cards support more than 2 channels.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsapcm_setrate(alsapcm_t *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int rate;
|
||||||
|
int res;
|
||||||
|
if (!PyArg_ParseTuple(args,"i:setrate", &rate))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!self->handle)
|
||||||
|
{
|
||||||
|
PyErr_SetString(ALSAAudioError, "PCM device is closed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->rate = rate;
|
||||||
|
res = alsapcm_setup(self);
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
|
||||||
|
self->cardname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return PyLong_FromLong(self->rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(setrate_doc,
|
||||||
|
"setrate(rate)\n\
|
||||||
|
\n\
|
||||||
|
Set the sample rate in Hz for the device. Typical values are\n\
|
||||||
|
8000 (telephony), 11025, 44100 (CD), 48000 (DVD audio) and 96000");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsapcm_setformat(alsapcm_t *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int format;
|
||||||
|
int res;
|
||||||
|
if (!PyArg_ParseTuple(args,"i:setformat", &format))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!self->handle)
|
||||||
|
{
|
||||||
|
PyErr_SetString(ALSAAudioError, "PCM device is closed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->format = format;
|
||||||
|
res = alsapcm_setup(self);
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
|
||||||
|
self->cardname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return PyLong_FromLong(self->format);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(setformat_doc,
|
||||||
|
"setformat(rate)\n");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
alsapcm_setperiodsize(alsapcm_t *self, PyObject *args)
|
||||||
|
{
|
||||||
|
int periodsize;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args,"i:setperiodsize", &periodsize))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!self->handle)
|
||||||
|
{
|
||||||
|
PyErr_SetString(ALSAAudioError, "PCM device is closed");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->periodsize = periodsize;
|
||||||
|
res = alsapcm_setup(self);
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
PyErr_Format(ALSAAudioError, "%s [%s]", snd_strerror(res),
|
||||||
|
self->cardname);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return PyLong_FromLong(self->periodsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(setperiodsize_doc,
|
||||||
|
"setperiodsize(period) -> int\n\
|
||||||
|
\n\
|
||||||
|
Sets the actual period size in frames. Each write should consist of\n\
|
||||||
|
exactly this number of frames, and each read will return this number of\n\
|
||||||
|
frames (unless the device is in PCM_NONBLOCK mode, in which case it\n\
|
||||||
|
may return nothing at all).");
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsapcm_read(alsapcm_t *self, PyObject *args)
|
alsapcm_read(alsapcm_t *self, PyObject *args)
|
||||||
{
|
{
|
||||||
int res;
|
int res;
|
||||||
int size = self->framesize * self->periodsize;
|
char buffer[8000];
|
||||||
PyObject *buffer_obj, *tuple_obj, *res_obj;
|
|
||||||
char *buffer;
|
if (self->framesize * self->periodsize > 8000) {
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -827,18 +797,6 @@ 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)
|
||||||
@@ -858,41 +816,17 @@ 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
|
||||||
/* If the following fails, it will free the object */
|
return Py_BuildValue("is#", res, buffer, res*self->framesize);
|
||||||
if (_PyString_Resize(&buffer_obj, 0))
|
|
||||||
return NULL;
|
|
||||||
#else
|
#else
|
||||||
/* If the following fails, it will free the object */
|
return Py_BuildValue("iy#", res, buffer, res*self->framesize);
|
||||||
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\
|
||||||
@@ -1022,43 +956,6 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -1122,15 +1019,16 @@ static PyMethodDef alsapcm_methods[] = {
|
|||||||
{"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS, pcmtype_doc},
|
{"pcmtype", (PyCFunction)alsapcm_pcmtype, METH_VARARGS, pcmtype_doc},
|
||||||
{"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS, pcmmode_doc},
|
{"pcmmode", (PyCFunction)alsapcm_pcmmode, METH_VARARGS, pcmmode_doc},
|
||||||
{"cardname", (PyCFunction)alsapcm_cardname, METH_VARARGS, cardname_doc},
|
{"cardname", (PyCFunction)alsapcm_cardname, METH_VARARGS, cardname_doc},
|
||||||
|
{"setchannels", (PyCFunction)alsapcm_setchannels, METH_VARARGS,
|
||||||
|
setchannels_doc },
|
||||||
|
{"setrate", (PyCFunction)alsapcm_setrate, METH_VARARGS, setrate_doc},
|
||||||
|
{"setformat", (PyCFunction)alsapcm_setformat, METH_VARARGS, setformat_doc},
|
||||||
|
{"setperiodsize", (PyCFunction)alsapcm_setperiodsize, METH_VARARGS,
|
||||||
|
setperiodsize_doc},
|
||||||
{"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS},
|
{"dumpinfo", (PyCFunction)alsapcm_dumpinfo, METH_VARARGS},
|
||||||
{"getformats", (PyCFunction)alsapcm_getformats, METH_VARARGS, getformats_doc},
|
|
||||||
{"getratebounds", (PyCFunction)alsapcm_getratemaxmin, METH_VARARGS, getratebounds_doc},
|
|
||||||
{"getrates", (PyCFunction)alsapcm_getrates, METH_VARARGS, getrates_doc},
|
|
||||||
{"getchannels", (PyCFunction)alsapcm_getchannels, METH_VARARGS, getchannels_doc},
|
|
||||||
{"read", (PyCFunction)alsapcm_read, METH_VARARGS, read_doc},
|
{"read", (PyCFunction)alsapcm_read, METH_VARARGS, read_doc},
|
||||||
{"write", (PyCFunction)alsapcm_write, METH_VARARGS, write_doc},
|
{"write", (PyCFunction)alsapcm_write, METH_VARARGS, write_doc},
|
||||||
{"pause", (PyCFunction)alsapcm_pause, METH_VARARGS, pause_doc},
|
{"pause", (PyCFunction)alsapcm_pause, METH_VARARGS, pause_doc},
|
||||||
{"drop", (PyCFunction)alsapcm_drop, METH_VARARGS, drop_doc},
|
|
||||||
{"close", (PyCFunction)alsapcm_close, METH_VARARGS, pcm_close_doc},
|
{"close", (PyCFunction)alsapcm_close, METH_VARARGS, pcm_close_doc},
|
||||||
{"polldescriptors", (PyCFunction)alsapcm_polldescriptors, METH_VARARGS,
|
{"polldescriptors", (PyCFunction)alsapcm_polldescriptors, METH_VARARGS,
|
||||||
pcm_polldescriptors_doc},
|
pcm_polldescriptors_doc},
|
||||||
@@ -1675,21 +1573,31 @@ Possible values in this list are:\n\
|
|||||||
- 'Capture Exclusive'\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 */
|
/* 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;
|
||||||
tmp = rint((double)value/(double)range * 100);
|
return (double)value/(double)range * 100.0;
|
||||||
return tmp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 */
|
/* Convert from percentage to number in range */
|
||||||
int range = max - min;
|
int range = max - min;
|
||||||
@@ -1698,57 +1606,76 @@ static long alsamixer_getphysvolume(long min, long max, int percentage)
|
|||||||
if (range == 0)
|
if (range == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
tmp = rint((double)range * ((double)percentage*.01)) + min;
|
tmp = rint((double)range * (percentage * .01)) + min;
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsamixer_getvolume(alsamixer_t *self, PyObject *args)
|
alsamixer_getvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
|
||||||
{
|
{
|
||||||
snd_mixer_elem_t *elem;
|
snd_mixer_elem_t *elem;
|
||||||
int channel;
|
int channel;
|
||||||
long ival;
|
long ival;
|
||||||
PyObject *pcmtypeobj = NULL;
|
PyObject *dirobj = NULL;
|
||||||
long pcmtype;
|
long dir;
|
||||||
|
int unit = unit_percent;
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
PyObject *item;
|
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;
|
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 (pcmtype == SND_PCM_STREAM_PLAYBACK &&
|
if (dir == 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);
|
||||||
item = PyLong_FromLong(alsamixer_getpercentage(self->pmin,
|
if (unit == unit_percent) {
|
||||||
self->pmax,
|
item = PyFloat_FromDouble(
|
||||||
ival));
|
alsamixer_getpercentage(self->pmin, self->pmax, 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 (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_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);
|
||||||
item = PyLong_FromLong(alsamixer_getpercentage(self->cmin,
|
if (unit == unit_percent) {
|
||||||
self->cmax,
|
item = PyFloat_FromDouble(
|
||||||
ival));
|
alsamixer_getpercentage(self->cmin, self->cmax, 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);
|
||||||
}
|
}
|
||||||
@@ -1758,15 +1685,17 @@ alsamixer_getvolume(alsamixer_t *self, PyObject *args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(getvolume_doc,
|
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\
|
\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 integer percentages.\n\
|
The list elements are float percentages.\n\
|
||||||
\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\
|
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 *
|
||||||
@@ -2092,29 +2021,53 @@ This method will fail if the mixer has no capture switch capabilities.");
|
|||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
alsamixer_setvolume(alsamixer_t *self, PyObject *args, PyObject *kw)
|
||||||
{
|
{
|
||||||
snd_mixer_elem_t *elem;
|
snd_mixer_elem_t *elem;
|
||||||
int i;
|
int i;
|
||||||
long volume;
|
double volume = 0.0;
|
||||||
|
PyObject *volumeobj = NULL;
|
||||||
int physvolume;
|
int physvolume;
|
||||||
PyObject *pcmtypeobj = NULL;
|
PyObject *dirobj = NULL;
|
||||||
long pcmtype;
|
long dir;
|
||||||
|
int unit = unit_percent;
|
||||||
int channel = MIXER_CHANNEL_ALL;
|
int channel = MIXER_CHANNEL_ALL;
|
||||||
int done = 0;
|
int done = 0;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args,"l|iO:setvolume", &volume, &channel,
|
static char *kwlist[] = { "channel", "direction", "unit", NULL };
|
||||||
&pcmtypeobj))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (volume < 0 || volume > 100)
|
|
||||||
{
|
if (!PyArg_ParseTupleAndKeywords(args, kw, "O|iOi:setvolume", kwlist, &volumeobj, &channel,
|
||||||
PyErr_SetString(ALSAAudioError, "Volume must be between 0 and 100");
|
&dirobj, &unit)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pcmtype = get_pcmtype(pcmtypeobj);
|
// unit
|
||||||
if (pcmtype < 0) {
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2126,32 +2079,42 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
|||||||
|
|
||||||
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
elem = alsamixer_find_elem(self->handle,self->controlname,self->controlid);
|
||||||
|
|
||||||
if (!pcmtypeobj || (pcmtypeobj == Py_None))
|
if (!dirobj || (dirobj == Py_None))
|
||||||
{
|
{
|
||||||
if (self->pchannels)
|
if (self->pchannels)
|
||||||
pcmtype = SND_PCM_STREAM_PLAYBACK;
|
dir = SND_PCM_STREAM_PLAYBACK;
|
||||||
else
|
else
|
||||||
pcmtype = SND_PCM_STREAM_CAPTURE;
|
dir = 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 (pcmtype == SND_PCM_STREAM_PLAYBACK &&
|
if (dir == 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 (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_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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2169,19 +2132,21 @@ alsamixer_setvolume(alsamixer_t *self, PyObject *args)
|
|||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(setvolume_doc,
|
PyDoc_STRVAR(setvolume_doc,
|
||||||
"setvolume(volume[[, channel] [, pcmtype]])\n\
|
"setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit='percent')\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 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\
|
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 'playback' if the mixer has this capability, otherwise\n\
|
The default is PCM_PLAYBACK if the mixer has this capability, otherwise\n\
|
||||||
'capture'.");
|
PCM_CAPTURE.\n\
|
||||||
|
\n\
|
||||||
|
The optional 'unit' argument can be either 'percent' (the default) or 'dB'.");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
@@ -2192,6 +2157,7 @@ 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;
|
||||||
|
|
||||||
@@ -2399,13 +2365,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,
|
{"getvolume", (PyCFunction)alsamixer_getvolume, METH_VARARGS | METH_KEYWORDS,
|
||||||
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,
|
{"setvolume", (PyCFunction)alsamixer_setvolume, METH_VARARGS | METH_KEYWORDS,
|
||||||
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},
|
||||||
@@ -2555,6 +2521,9 @@ 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);
|
||||||
|
|
||||||
|
|||||||
+13
-9
@@ -443,31 +443,35 @@ 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])
|
.. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent)
|
||||||
|
|
||||||
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 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
|
: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], [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
|
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
|
The *channel* argument can be used to restrict the channels for which the volume is
|
||||||
only for this channel. This assumes that the mixer can control the
|
set. By default, the volume of all channels is adjusted. This assumes that the mixer
|
||||||
volume for the channels independently.
|
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
|
: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
@@ -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 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.
|
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
|
||||||
|
|||||||
+10
-6
@@ -46,13 +46,17 @@ 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 in range(len(volumes)):
|
for i, v in enumerate(volumes):
|
||||||
print("Channel %i volume: %i%%" % (i,volumes[i]))
|
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:
|
try:
|
||||||
mutes = mixer.getmute()
|
mutes = mixer.getmute()
|
||||||
for i in range(len(mutes)):
|
for i, m in enumerate(mutes):
|
||||||
if mutes[i]:
|
if m:
|
||||||
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
|
||||||
@@ -60,8 +64,8 @@ def show_mixer(name, kwargs):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
recs = mixer.getrec()
|
recs = mixer.getrec()
|
||||||
for i in range(len(recs)):
|
for i, r in enumerate(recs):
|
||||||
if recs[i]:
|
if r:
|
||||||
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
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ def play(device, f):
|
|||||||
else:
|
else:
|
||||||
raise ValueError('Unsupported format')
|
raise ValueError('Unsupported format')
|
||||||
|
|
||||||
periodsize = f.getframerate() // 8
|
periodsize = f.getframerate() / 8
|
||||||
|
|
||||||
device.setperiodsize(periodsize)
|
device.setperiodsize(periodsize)
|
||||||
|
|
||||||
|
|||||||
@@ -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.6'
|
pyalsa_version = '0.8.4'
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
setup(
|
setup(
|
||||||
|
|||||||
Reference in New Issue
Block a user