This is release 0.4; it was tested with python2.5 and python3.0 (rc2 &

3)


git-svn-id: svn://svn.code.sf.net/p/pyalsaaudio/code/trunk@29 ec2f30ec-7544-0410-870e-f70ca00c83f0
This commit is contained in:
larsimmisch
2008-11-29 03:29:13 +00:00
parent aa77693b79
commit fc3517b00e
10 changed files with 425 additions and 218 deletions

View File

@@ -1,6 +1,11 @@
Version 0.4:
- API changes: mixers() and Mixer() now take a card index instead of a
card name as optional parameter.
- Support for Python 3.0
- Documentation in reStructuredText; use Sphinx instead of LaTeX.
- Documentation converted to reStructuredText; use Sphinx instead of LaTeX.
- added cards()
- added PCM.close()
- added Mixer.close()
- added mixer.getenum()

View File

@@ -2,4 +2,4 @@ include *.py
include CHANGES
include TODO
include LICENSE
recursive-include doc *.html *.gif *.png *.css *.py *.tex Makefile
recursive-include doc *.html *.gif *.png *.css *.py *.rst *.js *.json Makefile

View File

@@ -16,8 +16,7 @@
#include "Python.h"
#if PY_MAJOR_VERSION < 3
#include "stringobject.h"
#define PyUnicode_AS_DATA PyString_AS_STRING
#define PyUnicode_Check PyString_Check
#define PyUnicode_FromString PyString_FromString
#endif
#include <alsa/asoundlib.h>
#include <stdio.h>

View File

@@ -36,7 +36,7 @@ The :mod:`alsaaudio` module defines functions and classes for using ALSA.
.. function:: cards()
List the available cards.
List the available cards by name (suitable for PCM objects).
.. function:: mixers([cardindex])
@@ -101,13 +101,13 @@ PCM objects have the following methods:
.. method:: PCM.pcmtype()
Returns the type of PCM object. Either PCM_CAPTURE or PCM_PLAYBACK.
Returns the type of PCM object. Either ``PCM_CAPTURE`` or ``PCM_PLAYBACK``.
.. method:: PCM.pcmmode()
Return the mode of the PCM object. One of PCM_NONBLOCK, PCM_ASYNC,
or PCM_NORMAL
Return the mode of the PCM object. One of ``PCM_NONBLOCK``, ``PCM_ASYNC``,
or ``PCM_NORMAL``
.. method:: PCM.cardname()
@@ -168,19 +168,19 @@ PCM objects have the following methods:
Sets the actual period size in frames. Each write should consist of
exactly this number of frames, and each read will return this
number of frames (unless the device is in PCM_NONBLOCK mode, in
number of frames (unless the device is in ``PCM_NONBLOCK`` mode, in
which case it may return nothing at all)
.. method:: PCM.read()
In PCM_NORMAL mode, this function blocks until a full period is
In ``PCM_NORMAL`` mode, this function blocks until a full period is
available, and then returns a tuple (length,data) where *length* is
the number of frames of captured data, and *data* is the captured
sound frames as a string. The length of the returned data will be
periodsize\*framesize bytes.
In PCM_NONBLOCK mode, the call will not block, but will return
In ``PCM_NONBLOCK`` mode, the call will not block, but will return
``(0,'')`` if no new period has become available since the last
call to read.
@@ -192,12 +192,12 @@ PCM objects have the following methods:
period. If less than 'period size' frames are provided, the actual
playout will not happen until more data is written.
If the device is not in PCM_NONBLOCK mode, this call will block if
If the device is not in ``PCM_NONBLOCK`` mode, this call will block if
the kernel buffer is full, and until enough sound has been played
to allow the sound data to be buffered. The call always returns the
size of the data provided.
In PCM_NONBLOCK mode, the call will return immediately, with a
In ``PCM_NONBLOCK`` mode, the call will return immediately, with a
return value of zero, if the buffer is full. In this case, the data
should be written at a later time.
@@ -209,17 +209,16 @@ PCM objects have the following methods:
**A few hints on using PCM devices for playback**
The most common reason for problems with playback of PCM audio, is that the
people don't properly understand that writes to PCM devices must match
*exactly* the data rate of the device.
The most common reason for problems with playback of PCM audio is that writes
to PCM devices must *exactly* match the data rate of the device.
If too little data is written to the device, it will underrun, and
ugly clicking sounds will occur. Conversely, of too much data is
written to the device, the write function will either block
(PCM_NORMAL mode) or return zero (PCM_NONBLOCK mode).
(``PCM_NORMAL`` mode) or return zero (``PCM_NONBLOCK`` mode).
If your program does nothing but play sound, the best strategy is to put the
device in PCM_NORMAL mode, and just write as much data to the device as
device in ``PCM_NORMAL`` mode, and just write as much data to the device as
possible. This strategy can also be achieved by using a separate
thread with the sole task of playing out sound.
@@ -230,7 +229,7 @@ period. The purpose of the preloading is to avoid underrun clicks if the used
timer doesn't expire exactly on time.
Also note, that most timer APIs that you can find for Python will
acummulate time delays: If you set the timer to expire after 1/10'th
accummulate time delays: If you set the timer to expire after 1/10'th
of a second, the actual timeout will happen slightly later, which will
accumulate to quite a lot after a few seconds. Hint: use time.time()
to check how much time has really passed, and add extra writes as nessecary.
@@ -244,7 +243,7 @@ Mixer Objects
Mixer objects provides access to the ALSA mixer API.
.. class:: Mixer([control], [id], [cardindex])
.. class:: Mixer(control='Master', id=0, cardindex=0)
*control* - specifies which control to manipulate using this mixer
object. The list of available controls can be found with the
@@ -253,12 +252,13 @@ Mixer objects provides access to the ALSA mixer API.
*id* - the id of the mixer control. Default is 0
*cardindex* - specifies which card should be used[#f3]_. 0 is the
first sound card. Default is 0.
*cardindex* - specifies which card should be used [#f3]_. 0 is the
first sound card.
For a list of available controls, you can also use ``amixer``::
**Note:** For a list of available controls, you can also use **amixer**::
amixer -c 1
amixer
Mixer objects have the following methods:
@@ -283,19 +283,19 @@ Mixer objects have the following methods:
Returns a list of the switches which are defined by this specific mixer.
Possible values in this list are:
==================== ================
Switch Description
==================== ================
Mute This mixer can mute
Joined Mute This mixer can mute all channels at the same time
Playback Mute This mixer can mute the playback output
Joined Playback Mute Mute playback for all channels at the same time}
Capture Mute Mute sound capture
Joined Capture Mute Mute sound capture for all channels at a time}
Capture Exclusive Not quite sure what this is
==================== ================
To manipulate these swithes use the :meth:`setrec` or
====================== ================
Switch Description
====================== ================
'Mute' This mixer can mute
'Joined Mute' This mixer can mute all channels at the same time
'Playback Mute' This mixer can mute the playback output
'Joined Playback Mute' Mute playback for all channels at the same time}
'Capture Mute' Mute sound capture
'Joined Capture Mute' Mute sound capture for all channels at a time}
'Capture Exclusive' Not quite sure what this is
====================== ================
To manipulate these switches use the :meth:`setrec` or
:meth:`setmute` methods
@@ -304,16 +304,16 @@ Mixer objects have the following methods:
Returns a list of the volume control capabilities of this
mixer. Possible values in the list are:
====================== ================
Capability Description
====================== ================
Volume This mixer can control volume
Joined Volume This mixer can control volume for all channels at the same time
Playback Volume This mixer can manipulate the playback output
Joined Playback Volume Manipulate playback volumne for all channels at the same time
Capture Volume Manipulate sound capture volume
Joined Capture Volume Manipulate sound capture volume for all channels at a time
====================== ================
======================== ================
Capability Description
======================== ================
'Volume' This mixer can control volume
'Joined Volume' This mixer can control volume for all channels at the same time
'Playback Volume' This mixer can manipulate the playback output
'Joined Playback Volume' Manipulate playback volumne for all channels at the same time
'Capture Volume' Manipulate sound capture volume
'Joined Capture Volume' Manipulate sound capture volume for all channels at a time
======================== ================
.. method:: Mixer.getenum()
@@ -445,13 +445,15 @@ painful trial and error process.
Examples
--------
The following examples are provided:
The following example are provided:
* playwav.py
* recordtest.py
* playbacktest.py
* mixertest.py
All examples take the commandline option '-c <cardname>'.
All examples (except mixertest.py) accept the commandline option
*-c <cardname>*.
To determine a valid card name, use the commandline ALSA player::
@@ -464,22 +466,24 @@ or::
>>> import alsaaudio
>>> alsaaudio.cards()
mixertest.py accepts the commandline option *-c <cardindex>*. Card
indices start at 0.
playwav.py
~~~~~~~~~~
``playwav.py`` plays a wav file. A sample wav file is
provided in the source distribution.
**playwav.py** plays a wav file.
To test PCM playback (on your default soundcard), do::
To test PCM playback (on your default soundcard), run::
$ python playwav.py foo.wav
$ python playwav.py <wav file>
recordtest.py and playbacktest.py
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``recordtest.py`` and ``playbacktest.py`` will record and play a raw
**recordtest.py** and **playbacktest.py** will record and play a raw
sound file in CD quality.
To test PCM recordings (on your default soundcard), do::
To test PCM recordings (on your default soundcard), run::
$ python recordtest.py <filename>
@@ -490,8 +494,53 @@ Play back the recording with::
$ python playbacktest.py <filename>
mixertest.py
~~~~~~~~~~~~
Without arguments, **mixertest.py** will list all available *controls*.
The output might look like this::
$ ./mixertest.py
Available mixer controls:
'Master'
'Master Mono'
'Headphone'
'PCM'
'Line'
'Line In->Rear Out'
'CD'
'Mic'
'PC Speaker'
'Aux'
'Mono Output Select'
'Capture'
'Mix'
'Mix Mono'
With a single argument - the *control*, it will display the settings of
that control; for example::
$ ./mixertest.py Master
Mixer name: 'Master'
Capabilities: Playback Volume Playback Mute
Channel 0 volume: 61%
Channel 1 volume: 61%
With two arguments, the *control* and a *parameter*, it will set the
parameter on the mixer::
$ ./mixertest.py Master mute
This will mute the Master mixer.
Or::
$ ./mixertest.py Master 40
This sets the volume to 40% on all channels.
.. rubric:: Footnotes
.. [#f1] ALSA also allows ``PCM_ASYNC``, but this is not supported yet.
.. [#f2] But :mod:`alsaaudio` will leave any name alone that has a ':' (colon) in it.
.. [#f2] :mod:`alsaaudio` will leave any name alone that has a ':' (colon) in it.
.. [#f3] This is inconsistent with the PCM objects, which use names, but it is consistent with aplay and amixer.

92
mixertest.py Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env python
## mixertest.py
##
## This is an example of using the ALSA mixer API
@@ -13,34 +15,40 @@
## python mixertest.py Master mute # mute the master mixer
## python mixertest.py Master unmute # unmute the master mixer
import alsaaudio
# Footnote: I'd normally use print instead of sys.std(out|err).write,
# but we're in the middle of the conversion between python 2 and 3
# and this code runs on both versions without conversion
import sys
import getopt
import alsaaudio
if len(sys.argv) == 1:
# Demonstrates how to read the available mixers
print "Available mixer controls:"
for m in alsaaudio.mixers():
print " '%s'" % m
if len(sys.argv) == 2:
def list_mixers(idx=0):
sys.stdout.write("Available mixer controls:\n")
for m in alsaaudio.mixers(idx):
sys.stdout.write(" '%s'\n" % m)
def show_mixer(name, idx=0):
# Demonstrates how mixer settings are queried.
name = sys.argv[1]
try:
mixer = alsaaudio.Mixer(name)
mixer = alsaaudio.Mixer(name, cardindex=idx)
except alsaaudio.ALSAAudioError:
print "No such mixer"
sys.stderr.write("No such mixer\n")
sys.exit(1)
print "Mixer name: '%s'"%mixer.mixer()
print "Capabilities",mixer.volumecap()+mixer.switchcap()
sys.stdout.write("Mixer name: '%s'\n" % mixer.mixer())
sys.stdout.write("Capabilities: %s %s\n" % (' '.join(mixer.volumecap()),
' '.join(mixer.switchcap())))
volumes = mixer.getvolume()
for i in range(len(volumes)):
print "Channel %i volume: %i%%"%(i,volumes[i])
sys.stdout.write("Channel %i volume: %i%%\n" % (i,volumes[i]))
try:
mutes = mixer.getmute()
for i in range(len(mutes)):
if mutes[i]: print "Channel %i is muted"%i
if mutes[i]:
sys.stdout.write("Channel %i is muted\n" % i)
except alsaaudio.ALSAAudioError:
# May not support muting
pass
@@ -48,41 +56,65 @@ if len(sys.argv) == 2:
try:
recs = mixer.getrec()
for i in range(len(recs)):
if recs[i]: print "Channel %i is recording"%i
if recs[i]:
sys.stdout.write("Channel %i is recording\n" % i)
except alsaaudio.ALSAAudioError:
# May not support recording
pass
if (len(sys.argv)) == 3:
def set_mixer(name, args, idx=0):
# Demonstrates how to set mixer settings
name = sys.argv[1]
try:
mixer = alsaaudio.Mixer(name)
mixer = alsaaudio.Mixer(name, cardindex=idx)
except alsaaudio.ALSAAudioError:
print "No such mixer"
sys.stderr.write("No such mixer")
sys.exit(1)
args = sys.argv[2]
if args in ['mute','unmute']:
# Mute/unmute the mixer
if args == 'mute': mixer.setmute(1)
else: mixer.setmute(0)
sys.exit(0)
if args in ['rec','unrec']:
# Enable/disable recording
if args == 'rec': mixer.setrec(1)
else: mixer.setrec(0)
if args == 'mute':
mixer.setmute(1)
else:
mixer.setmute(0)
sys.exit(0)
if args in ['rec','unrec']:
# Enable/disable recording
if args == 'rec':
mixer.setrec(1)
else:
mixer.setrec(0)
sys.exit(0)
if args.find(',')!=-1:
if args.find(',') != -1:
channel,volume = map(int,args.split(','))
else:
channel = alsaaudio.MIXER_CHANNEL_ALL
volume = int(args)
# Set volume for specified channel. MIXER_CHANNEL_ALL means set
# volume for all channels
mixer.setvolume(volume,channel)
mixer.setvolume(volume, channel)
def usage():
sys.stderr.write('usage: mixertest.py [-c <card>] ' \
'[ <control>[,<value>]]\n')
sys.exit(2)
if __name__ == '__main__':
cardindex = 0
opts, args = getopt.getopt(sys.argv[1:], 'c:')
for o, a in opts:
if o == '-c':
cardindex = int(a)
if not len(args):
list_mixers(cardindex)
elif len(args) == 1:
show_mixer(args[0], cardindex)
else:
set_mixer(args[0], args[1], cardindex)

67
playbacktest.py Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env python
## playbacktest.py
##
## This is an example of a simple sound playback script.
@@ -7,30 +9,53 @@
## from stdin and writes it to the device.
##
## To test it out do the following:
## python recordtest.py > out.raw # talk to the microphone
## python playbacktest.py < out.raw
##
## If you have Gnome, you could also just test by doing something like:
## python playbacktest.py < /usr/share/sounds/gnibbles/laughter.wav
import alsaaudio
## python recordtest.py out.raw # talk to the microphone
## python playbacktest.py out.raw
# Footnote: I'd normally use print instead of sys.std(out|err).write,
# but we're in the middle of the conversion between python 2 and 3
# and this code runs on both versions without conversion
import sys
import time
import getopt
import alsaaudio
# Open the device in playback mode.
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK)
def usage():
sys.stderr.write('usage: playbacktest.py [-c <card>] <file>\n')
sys.exit(2)
# Set attributes: Mono, 8000 Hz, 16 bit little endian frames
out.setchannels(1)
out.setrate(8000)
out.setformat(alsaaudio.PCM_FORMAT_S16_LE)
if __name__ == '__main__':
# The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api.
out.setperiodsize(160)
card = 'default'
loops = 10000
while loops > 0:
loops -= 1
# Read data from stdin
data = sys.stdin.read(320)
out.write(data)
opts, args = getopt.getopt(sys.argv[1:], 'c:')
for o, a in opts:
if o == '-c':
card = a
if not args:
usage()
f = open(args[0], 'rb')
# Open the device in playback mode.
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, card=card)
# 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 significance of this parameter is documented in the ALSA api.
out.setperiodsize(160)
# Read data from stdin
data = f.read(320)
while data:
out.write(data)
data = f.read(320)

View File

@@ -2,87 +2,59 @@
# Simple test script that plays (some) wav files
# Footnote: I'd normally use print instead of sys.std(out|err).write,
# but we're in the middle of the conversion between python 2 and 3
# and this code runs on both versions without conversion
import sys
import struct
import wave
import getopt
import alsaaudio
# this is all a bit simplified, and won't cope with any wav extensions
# or multiple data chunks, but it's good enough here
WAV_FORMAT_PCM = 1
WAV_FORMAT_ALAW = 6
WAV_FORMAT_MULAW = 7
WAV_HEADER = '<4sl4s4slhhllhh4sl'
WAV_HEADER_SIZE = struct.calcsize(WAV_HEADER)
def _b(s):
'Helper for 3.0 compatibility'
if sys.version_info[0] >= 3:
return bytes(s, 'UTF-8')
return s
def wav_header_unpack(data):
(riff, riffsize, wave, fmt, fmtsize, format, nchannels, framerate,
datarate, blockalign, bitspersample, data, datalength) \
= struct.unpack(WAV_HEADER, data)
print(data)
if riff != _b('RIFF') or fmtsize != 16 or fmt != _b('fmt ') \
or data != _b('data'):
raise ValueError('wav header too complicated')
return (format, nchannels, framerate, bitspersample, datalength)
def play(device, f):
header = f.read(WAV_HEADER_SIZE)
format, nchannels, framerate, bitspersample, datalength \
= wav_header_unpack(header)
# Set attributes
device.setchannels(nchannels)
device.setrate(framerate)
device.setchannels(f.getnchannels())
device.setrate(f.getframerate())
# We assume signed data, little endian
if format == WAV_FORMAT_PCM:
if bitspersample == 8:
device.setformat(alsaaudio.PCM_FORMAT_S8)
elif bitspersample == 16:
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
elif bitspersample == 24:
device.setformat(alsaaudio.PCM_FORMAT_S24_LE)
elif bitspersample == 32:
device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
elif format == WAV_FORMAT_ALAW:
device.setformat(alsaaudio.PCM_FORMAT_A_LAW)
elif format == WAV_FORMAT_MULAW:
device.setformat(alsaaudio.PCM_FORMAT_MU_LAW)
if f.getsampwidth() == 1:
device.setformat(alsaaudio.PCM_FORMAT_S8)
elif f.getsampwidth() == 2:
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
elif f.getsampwidth() == 3:
device.setformat(alsaaudio.PCM_FORMAT_S24_LE)
elif f.getsampwidth() == 4:
device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
else:
raise ValueError('Unsupported format %d' % format)
raise ValueError('Unsupported format')
# The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api.
# rs = framerate / 25
# out.setperiodsize(rs)
data = f.read()
data = f.readframes(320)
while data:
# Read data from stdin
device.write(data)
data = f.read()
data = f.readframes(320)
def usage():
sys.stderr.write('usage: playwav.py [-c <card>] <file>\n')
sys.exit(2)
if __name__ == '__main__':
if len(sys.argv) < 2:
print('usage: playwav.py <file>')
sys.exit(2)
card = 'default'
f = open(sys.argv[1], 'rb')
device = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK)
opts, args = getopt.getopt(sys.argv[1:], 'c:')
for o, a in opts:
if o == '-c':
card = a
if not args:
usage()
f = wave.open(args[0], 'rb')
device = alsaaudio.PCM(card=card)
play(device, f)

81
recordtest.py Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env python
## recordtest.py
##
## This is an example of a simple sound capture script.
@@ -7,39 +9,62 @@
## writing the data to standard out.
##
## To test it out do the following:
## python recordtest.py > out.raw # talk to the microphone
## python recordtest.py out.raw # talk to the microphone
## aplay -r 8000 -f S16_LE -c 1 out.raw
import alsaaudio
# Footnote: I'd normally use print instead of sys.std(out|err).write,
# but we're in the middle of the conversion between python 2 and 3
# and this code runs on both versions without conversion
import sys
import time
import getopt
import alsaaudio
# Open the device in nonblocking capture mode. The last argument could
# just as well have been zero for blocking mode. Then we could have
# left out the sleep call in the bottom of the loop
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE,alsaaudio.PCM_NONBLOCK)
def usage():
sys.stderr.write('usage: recordtest.py [-c <card>] <file>\n')
sys.exit(2)
# Set attributes: Mono, 8000 Hz, 16 bit little endian samples
inp.setchannels(1)
inp.setrate(8000)
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
if __name__ == '__main__':
# The period size controls the internal number of frames per period.
# The significance of this parameter is documented in the ALSA api.
# For our purposes, it is suficcient to know that reads from the device
# will return this many frames. Each frame being 2 bytes long.
# This means that the reads below will return either 320 bytes of data
# or 0 bytes of data. The latter is possible because we are in nonblocking
# mode.
inp.setperiodsize(160)
card = 'default'
loops = 1000000
while loops > 0:
loops -= 1
# Read data from device
l,data = inp.read()
if l:
# actual data read. Write it to stdout
sys.stdout.write(data)
time.sleep(.001)
opts, args = getopt.getopt(sys.argv[1:], 'c:')
for o, a in opts:
if o == '-c':
card = a
if not args:
usage()
f = open(args[0], 'wb')
# Open the device in nonblocking capture mode. The last argument could
# just as well have been zero for blocking mode. Then we could have
# left out the sleep call in the bottom of the loop
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, card)
# 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 significance of this parameter is documented in the ALSA api.
# For our purposes, it is suficcient to know that reads from the device
# will return this many frames. Each frame being 2 bytes long.
# This means that the reads below will return either 320 bytes of data
# or 0 bytes of data. The latter is possible because we are in nonblocking
# mode.
inp.setperiodsize(160)
loops = 1000000
while loops > 0:
loops -= 1
# Read data from device
l, data = inp.read()
if l:
f.write(data)
time.sleep(.001)

View File

@@ -8,18 +8,6 @@ from distutils.core import setup
from distutils.extension import Extension
from sys import version
try:
from distutils.command.build_py import build_py_2to3 as \
build_py
except ImportError:
from distutils.command.build_py import build_py
try:
from distutils.command.build_scripts import build_scripts_2to3 as \
build_scripts
except ImportError:
from distutils.command.build_scripts import build_scripts
# patch distutils if it's too old to cope with the "classifiers" or
# "download_url" keywords
from sys import version
@@ -39,10 +27,7 @@ setup(
maintainer_email = 'lars@ibp.de',
license='PSF',
platforms=['posix'],
scripts=['playbacktest.py', 'recordtest.py', 'playwav.py', 'mixertest.py'],
url='http://pyalsaaudio.sourceforge.net/',
cmdclass = {'build_py':build_py,
'build_scripts':build_scripts},
classifiers = [
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
@@ -55,5 +40,5 @@ setup(
'Topic :: Multimedia :: Sound/Audio :: Players',
'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
],
ext_modules=[Extension('alsaaudio',['alsaaudio.c'],libraries=['asound'])]
ext_modules=[Extension('alsaaudio',['alsaaudio.c'], libraries=['asound'])]
)

123
test.py Normal file → Executable file
View File

@@ -1,7 +1,122 @@
#!/usr/bin/env python
# These are internal test. They shouldn't fail, but they don't cover all
# of the ALSA API. Most of all PCM.read and PCM.write are missing.
# These need to be tested by playbacktest.py and recordtest.py
# In case of a problem, run these tests. If they fail, file a bug report on
# http://sourceforge.net/projects/pyalsaaudio
import unittest
import alsaaudio
import sys
if len(sys.argv) > 1: name = sys.argv[1]
else: name = "Master"
m = alsaaudio.Mixer(name)
# we can't test read and write well - these are tested otherwise
PCMMethods = [('pcmtype', None),
('pcmmode', None),
('cardname', None),
('setchannels', (2,)),
('setrate', (44100,)),
('setformat', (alsaaudio.PCM_FORMAT_S8,)),
('setperiodsize', (320,))]
# A clever test would look at the Mixer capabilities and selectively run the
# omitted tests, but I am too tired for that.
MixerMethods = [('cardname', None),
('mixer', None),
('mixerid', None),
('switchcap', None),
('volumecap', None),
('getvolume', None),
('getrange', None),
('getenum', None),
# ('getmute', None),
# ('getrec', None),
# ('setvolume', (60,)),
# ('setmute', (0,))
# ('setrec', (0')),
]
class MixerTest(unittest.TestCase):
"""Test Mixer objects"""
def testMixer(self):
"""Open a Mixer on every card"""
# Mixers are addressed by index, not name
for i in range(len(alsaaudio.cards())):
mixers = alsaaudio.mixers(i)
for m in mixers:
mixer = alsaaudio.Mixer(m, cardindex=i)
mixer.close()
def testMixerAll(self):
"Run common Mixer methods on an open object"
mixers = alsaaudio.mixers()
mixer = alsaaudio.Mixer(mixers[0])
for m, a in MixerMethods:
f = alsaaudio.Mixer.__dict__[m]
if a is None:
f(mixer)
else:
f(mixer, *a)
mixer.close()
def testMixerClose(self):
"Run common Mixer methods on a closed object and verify it raises an error"
mixers = alsaaudio.mixers()
mixer = alsaaudio.Mixer(mixers[0])
mixer.close()
for m, a in MixerMethods:
f = alsaaudio.Mixer.__dict__[m]
if a is None:
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer)
else:
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer, *a)
class PCMTest(unittest.TestCase):
"""Test PCM objects"""
def testPCM(self):
"Open a PCM object on every card"
for c in alsaaudio.cards():
pcm = alsaaudio.PCM(card=c)
pcm.close()
def testPCMAll(self):
"Run all PCM methods on an open object"
pcm = alsaaudio.PCM()
for m, a in PCMMethods:
f = alsaaudio.PCM.__dict__[m]
if a is None:
f(pcm)
else:
f(pcm, *a)
pcm.close()
def testPCMClose(self):
"Run all PCM methods on a closed object and verify it raises an error"
pcm = alsaaudio.PCM()
pcm.close()
for m, a in PCMMethods:
f = alsaaudio.PCM.__dict__[m]
if a is None:
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm)
else:
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm, *a)
if __name__ == '__main__':
unittest.main()