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