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