• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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