1from test import support 2support.requires('audio') 3 4from test.support import findfile 5 6ossaudiodev = support.import_module('ossaudiodev') 7 8import errno 9import sys 10import sunau 11import time 12import audioop 13import unittest 14 15# Arggh, AFMT_S16_NE not defined on all platforms -- seems to be a 16# fairly recent addition to OSS. 17try: 18 from ossaudiodev import AFMT_S16_NE 19except ImportError: 20 if sys.byteorder == "little": 21 AFMT_S16_NE = ossaudiodev.AFMT_S16_LE 22 else: 23 AFMT_S16_NE = ossaudiodev.AFMT_S16_BE 24 25 26def read_sound_file(path): 27 with open(path, 'rb') as fp: 28 au = sunau.open(fp) 29 rate = au.getframerate() 30 nchannels = au.getnchannels() 31 encoding = au._encoding 32 fp.seek(0) 33 data = fp.read() 34 35 if encoding != sunau.AUDIO_FILE_ENCODING_MULAW_8: 36 raise RuntimeError("Expect .au file with 8-bit mu-law samples") 37 38 # Convert the data to 16-bit signed. 39 data = audioop.ulaw2lin(data, 2) 40 return (data, rate, 16, nchannels) 41 42class OSSAudioDevTests(unittest.TestCase): 43 44 def play_sound_file(self, data, rate, ssize, nchannels): 45 try: 46 dsp = ossaudiodev.open('w') 47 except OSError as msg: 48 if msg.args[0] in (errno.EACCES, errno.ENOENT, 49 errno.ENODEV, errno.EBUSY): 50 raise unittest.SkipTest(msg) 51 raise 52 53 # at least check that these methods can be invoked 54 dsp.bufsize() 55 dsp.obufcount() 56 dsp.obuffree() 57 dsp.getptr() 58 dsp.fileno() 59 60 # Make sure the read-only attributes work. 61 self.assertFalse(dsp.closed) 62 self.assertEqual(dsp.name, "/dev/dsp") 63 self.assertEqual(dsp.mode, "w", "bad dsp.mode: %r" % dsp.mode) 64 65 # And make sure they're really read-only. 66 for attr in ('closed', 'name', 'mode'): 67 try: 68 setattr(dsp, attr, 42) 69 except (TypeError, AttributeError): 70 pass 71 else: 72 self.fail("dsp.%s not read-only" % attr) 73 74 # Compute expected running time of sound sample (in seconds). 75 expected_time = float(len(data)) / (ssize/8) / nchannels / rate 76 77 # set parameters based on .au file headers 78 dsp.setparameters(AFMT_S16_NE, nchannels, rate) 79 self.assertTrue(abs(expected_time - 3.51) < 1e-2, expected_time) 80 t1 = time.time() 81 dsp.write(data) 82 dsp.close() 83 t2 = time.time() 84 elapsed_time = t2 - t1 85 86 percent_diff = (abs(elapsed_time - expected_time) / expected_time) * 100 87 self.assertTrue(percent_diff <= 10.0, 88 "elapsed time (%s) > 10%% off of expected time (%s)" % 89 (elapsed_time, expected_time)) 90 91 def set_parameters(self, dsp): 92 # Two configurations for testing: 93 # config1 (8-bit, mono, 8 kHz) should work on even the most 94 # ancient and crufty sound card, but maybe not on special- 95 # purpose high-end hardware 96 # config2 (16-bit, stereo, 44.1kHz) should work on all but the 97 # most ancient and crufty hardware 98 config1 = (ossaudiodev.AFMT_U8, 1, 8000) 99 config2 = (AFMT_S16_NE, 2, 44100) 100 101 for config in [config1, config2]: 102 (fmt, channels, rate) = config 103 if (dsp.setfmt(fmt) == fmt and 104 dsp.channels(channels) == channels and 105 dsp.speed(rate) == rate): 106 break 107 else: 108 raise RuntimeError("unable to set audio sampling parameters: " 109 "you must have really weird audio hardware") 110 111 # setparameters() should be able to set this configuration in 112 # either strict or non-strict mode. 113 result = dsp.setparameters(fmt, channels, rate, False) 114 self.assertEqual(result, (fmt, channels, rate), 115 "setparameters%r: returned %r" % (config, result)) 116 117 result = dsp.setparameters(fmt, channels, rate, True) 118 self.assertEqual(result, (fmt, channels, rate), 119 "setparameters%r: returned %r" % (config, result)) 120 121 def set_bad_parameters(self, dsp): 122 # Now try some configurations that are presumably bogus: eg. 300 123 # channels currently exceeds even Hollywood's ambitions, and 124 # negative sampling rate is utter nonsense. setparameters() should 125 # accept these in non-strict mode, returning something other than 126 # was requested, but should barf in strict mode. 127 fmt = AFMT_S16_NE 128 rate = 44100 129 channels = 2 130 for config in [(fmt, 300, rate), # ridiculous nchannels 131 (fmt, -5, rate), # impossible nchannels 132 (fmt, channels, -50), # impossible rate 133 ]: 134 (fmt, channels, rate) = config 135 result = dsp.setparameters(fmt, channels, rate, False) 136 self.assertNotEqual(result, config, 137 "unexpectedly got requested configuration") 138 139 try: 140 result = dsp.setparameters(fmt, channels, rate, True) 141 except ossaudiodev.OSSAudioError as err: 142 pass 143 else: 144 self.fail("expected OSSAudioError") 145 146 def test_playback(self): 147 sound_info = read_sound_file(findfile('audiotest.au')) 148 self.play_sound_file(*sound_info) 149 150 def test_set_parameters(self): 151 dsp = ossaudiodev.open("w") 152 try: 153 self.set_parameters(dsp) 154 155 # Disabled because it fails under Linux 2.6 with ALSA's OSS 156 # emulation layer. 157 #self.set_bad_parameters(dsp) 158 finally: 159 dsp.close() 160 self.assertTrue(dsp.closed) 161 162 def test_mixer_methods(self): 163 # Issue #8139: ossaudiodev didn't initialize its types properly, 164 # therefore some methods were unavailable. 165 with ossaudiodev.openmixer() as mixer: 166 self.assertGreaterEqual(mixer.fileno(), 0) 167 168 def test_with(self): 169 with ossaudiodev.open('w') as dsp: 170 pass 171 self.assertTrue(dsp.closed) 172 173 def test_on_closed(self): 174 dsp = ossaudiodev.open('w') 175 dsp.close() 176 self.assertRaises(ValueError, dsp.fileno) 177 self.assertRaises(ValueError, dsp.read, 1) 178 self.assertRaises(ValueError, dsp.write, b'x') 179 self.assertRaises(ValueError, dsp.writeall, b'x') 180 self.assertRaises(ValueError, dsp.bufsize) 181 self.assertRaises(ValueError, dsp.obufcount) 182 self.assertRaises(ValueError, dsp.obufcount) 183 self.assertRaises(ValueError, dsp.obuffree) 184 self.assertRaises(ValueError, dsp.getptr) 185 186 mixer = ossaudiodev.openmixer() 187 mixer.close() 188 self.assertRaises(ValueError, mixer.fileno) 189 190def test_main(): 191 try: 192 dsp = ossaudiodev.open('w') 193 except (ossaudiodev.error, OSError) as msg: 194 if msg.args[0] in (errno.EACCES, errno.ENOENT, 195 errno.ENODEV, errno.EBUSY): 196 raise unittest.SkipTest(msg) 197 raise 198 dsp.close() 199 support.run_unittest(__name__) 200 201if __name__ == "__main__": 202 test_main() 203