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