diff --git a/CHANGES b/CHANGES index 63798b3..44865a5 100644 --- a/CHANGES +++ b/CHANGES @@ -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() diff --git a/MANIFEST.in b/MANIFEST.in index 5a2574b..1d64e63 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,4 @@ include *.py include CHANGES include TODO include LICENSE -recursive-include doc *.html *.gif *.png *.css *.py *.tex Makefile \ No newline at end of file +recursive-include doc *.html *.gif *.png *.css *.py *.rst *.js *.json Makefile \ No newline at end of file diff --git a/alsaaudio.c b/alsaaudio.c index f2d77b4..3a903e0 100644 --- a/alsaaudio.c +++ b/alsaaudio.c @@ -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 #include diff --git a/doc/libalsaaudio.rst b/doc/libalsaaudio.rst index 74b7c1d..0790c24 100644 --- a/doc/libalsaaudio.rst +++ b/doc/libalsaaudio.rst @@ -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 '. +All examples (except mixertest.py) accept the commandline option +*-c *. 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 *. 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 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 @@ -490,8 +494,53 @@ Play back the recording with:: $ python playbacktest.py +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. diff --git a/mixertest.py b/mixertest.py old mode 100644 new mode 100755 index 642ae10..0b53a79 --- a/mixertest.py +++ b/mixertest.py @@ -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 ] ' \ + '[ [,]]\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) + diff --git a/playbacktest.py b/playbacktest.py old mode 100644 new mode 100755 index af7e2cf..22cec26 --- a/playbacktest.py +++ b/playbacktest.py @@ -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 ] \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) + + diff --git a/playwav.py b/playwav.py index d8a3a5f..97c8f99 100755 --- a/playwav.py +++ b/playwav.py @@ -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 ] \n') + sys.exit(2) + if __name__ == '__main__': - if len(sys.argv) < 2: - print('usage: playwav.py ') - 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) diff --git a/recordtest.py b/recordtest.py old mode 100644 new mode 100755 index 216d627..3967860 --- a/recordtest.py +++ b/recordtest.py @@ -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 ] \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) diff --git a/setup.py b/setup.py index 65512c1..b017ea2 100755 --- a/setup.py +++ b/setup.py @@ -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'])] ) diff --git a/test.py b/test.py old mode 100644 new mode 100755 index 51707ec..2f89df9 --- a/test.py +++ b/test.py @@ -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()