1
2
3
4
5
6
7 """Provides a simple Python interface to playing audio using the Windows multimedia waveOut functions, as well as other useful utilities.
8 """
9
10 import threading
11 from ctypes import *
12 from ctypes.wintypes import *
13 import winKernel
14 import wave
15 import config
16
17 __all__ = (
18 "WavePlayer", "getOutputDeviceNames", "outputDeviceIDToName", "outputDeviceNameToID",
19 )
20
21 winmm = windll.winmm
22
23 HWAVEOUT = HANDLE
24 LPHWAVEOUT = POINTER(HWAVEOUT)
25
36 LPWAVEFORMATEX = POINTER(WAVEFORMATEX)
37
40 LPWAVEHDR = POINTER(WAVEHDR)
41 WAVEHDR._fields_ = [
42 ("lpData", LPSTR),
43 ("dwBufferLength", DWORD),
44 ("dwBytesRecorded", DWORD),
45 ("dwUser", DWORD),
46 ("dwFlags", DWORD),
47 ("dwLoops", DWORD),
48 ("lpNext", LPWAVEHDR),
49 ("reserved", DWORD)
50 ]
51 WHDR_DONE = 1
52
53 WAVE_FORMAT_PCM = 1
54 WAVE_MAPPER = -1
55 MMSYSERR_NOERROR = 0
56
57 CALLBACK_NULL = 0
58
59 CALLBACK_EVENT = 0x50000
60
61
62
63 MAXPNAMELEN = 32
65 _fields_ = [
66 ('wMid', WORD),
67 ('wPid', WORD),
68 ('vDriverVersion', c_uint),
69 ('szPname', WCHAR*MAXPNAMELEN),
70 ('dwFormats', DWORD),
71 ('wChannels', WORD),
72 ('wReserved1', WORD),
73 ('dwSupport', DWORD),
74 ]
75
76
77 winmm.waveOutOpen.argtypes = (LPHWAVEOUT, UINT, LPWAVEFORMATEX, DWORD, DWORD, DWORD)
78
79
81 if res != MMSYSERR_NOERROR:
82 buf = create_unicode_buffer(256)
83 winmm.waveOutGetErrorTextW(res, buf, sizeof(buf))
84 raise WindowsError(res, buf.value)
85 for func in (
86 winmm.waveOutOpen, winmm.waveOutPrepareHeader, winmm.waveOutWrite, winmm.waveOutUnprepareHeader,
87 winmm.waveOutPause, winmm.waveOutRestart, winmm.waveOutReset, winmm.waveOutClose,
88 winmm.waveOutGetDevCapsW
89 ):
90 func.errcheck = _winmm_errcheck
91
93 """Synchronously play a stream of audio.
94 To use, construct an instance and feed it waveform audio using L{feed}.
95 """
96
97 _global_waveout_lock = threading.RLock()
98
99 - def __init__(self, channels, samplesPerSec, bitsPerSample, outputDevice=WAVE_MAPPER, closeWhenIdle=True):
100 """Constructor.
101 @param channels: The number of channels of audio; e.g. 2 for stereo, 1 for mono.
102 @type channels: int
103 @param samplesPerSec: Samples per second (hz).
104 @type samplesPerSec: int
105 @param bitsPerSample: The number of bits per sample.
106 @type bitsPerSample: int
107 @param outputDevice: The device ID or name of the audio output device to use.
108 @type outputDevice: int or basestring
109 @param closeWhenIdle: If C{True}, close the output device when no audio is being played.
110 @type closeWhenIdle: bool
111 @note: If C{outputDevice} is a name and no such device exists, the default device will be used.
112 @raise WindowsError: If there was an error opening the audio output device.
113 """
114 self.channels=channels
115 self.samplesPerSec=samplesPerSec
116 self.bitsPerSample=bitsPerSample
117 if isinstance(outputDevice, basestring):
118 outputDevice = outputDeviceNameToID(outputDevice, True)
119 self.outputDeviceID = outputDevice
120
121
122 self.closeWhenIdle = closeWhenIdle
123 self._waveout = None
124 self._waveout_event = winKernel.kernel32.CreateEventW(None, False, False, None)
125 self._waveout_lock = threading.RLock()
126 self._lock = threading.RLock()
127 self.open()
128
130 """Open the output device.
131 This will be called automatically when required.
132 It is not an error if the output device is already open.
133 """
134 with self._waveout_lock:
135 if self._waveout:
136 return
137 wfx = WAVEFORMATEX()
138 wfx.wFormatTag = WAVE_FORMAT_PCM
139 wfx.nChannels = self.channels
140 wfx.nSamplesPerSec = self.samplesPerSec
141 wfx.wBitsPerSample = self.bitsPerSample
142 wfx.nBlockAlign = self.bitsPerSample / 8 * self.channels
143 wfx.nAvgBytesPerSec = self.samplesPerSec * wfx.nBlockAlign
144 waveout = HWAVEOUT(0)
145 with self._global_waveout_lock:
146 winmm.waveOutOpen(byref(waveout), self.outputDeviceID, LPWAVEFORMATEX(wfx), self._waveout_event, 0, CALLBACK_EVENT)
147 self._waveout = waveout.value
148 self._prev_whdr = None
149
150 - def feed(self, data):
151 """Feed a chunk of audio data to be played.
152 This is normally synchronous.
153 However, synchronisation occurs on the previous chunk, rather than the current chunk; i.e. calling this while no audio is playing will begin playing the chunk but return immediately.
154 This allows for uninterrupted playback as long as a new chunk is fed before the previous chunk has finished playing.
155 @param data: Waveform audio in the format specified when this instance was constructed.
156 @type data: str
157 @raise WindowsError: If there was an error playing the audio.
158 """
159 whdr = WAVEHDR()
160 whdr.lpData = data
161 whdr.dwBufferLength = len(data)
162 with self._lock:
163 with self._waveout_lock:
164 self.open()
165 with self._global_waveout_lock:
166 winmm.waveOutPrepareHeader(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR))
167 try:
168 with self._global_waveout_lock:
169 winmm.waveOutWrite(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR))
170 except WindowsError, e:
171 self.close()
172 raise e
173 self.sync()
174 self._prev_whdr = whdr
175
177 """Synchronise with playback.
178 This method blocks until the previously fed chunk of audio has finished playing.
179 It is called automatically by L{feed}, so usually need not be called directly by the user.
180 """
181 with self._lock:
182 if not self._prev_whdr:
183 return
184 assert self._waveout, "waveOut None before wait"
185 while not (self._prev_whdr.dwFlags & WHDR_DONE):
186 winKernel.waitForSingleObject(self._waveout_event, winKernel.INFINITE)
187 with self._waveout_lock:
188 assert self._waveout, "waveOut None after wait"
189 with self._global_waveout_lock:
190 winmm.waveOutUnprepareHeader(self._waveout, LPWAVEHDR(self._prev_whdr), sizeof(WAVEHDR))
191 self._prev_whdr = None
192
193 - def pause(self, switch):
194 """Pause or unpause playback.
195 @param switch: C{True} to pause playback, C{False} to unpause.
196 @type switch: bool
197 """
198 with self._waveout_lock:
199 if not self._waveout:
200 return
201 if switch:
202 with self._global_waveout_lock:
203 winmm.waveOutPause(self._waveout)
204 else:
205 with self._global_waveout_lock:
206 winmm.waveOutRestart(self._waveout)
207
209 """Indicate that this player is now idle; i.e. the current continuous segment of audio is complete.
210 This will first call L{sync} to synchronise with playback.
211 If L{closeWhenIdle} is C{True}, the output device will be closed.
212 A subsequent call to L{feed} will reopen it.
213 """
214 with self._lock:
215 self.sync()
216 with self._waveout_lock:
217 if not self._waveout:
218 return
219 if self.closeWhenIdle:
220 self._close()
221
223 """Stop playback.
224 """
225 with self._waveout_lock:
226 if not self._waveout:
227 return
228 try:
229 with self._global_waveout_lock:
230
231 winmm.waveOutPause(self._waveout)
232 winmm.waveOutReset(self._waveout)
233 except WindowsError:
234
235 pass
236
237 self.idle()
238
240 """Close the output device.
241 """
242 self.stop()
243 with self._lock:
244 with self._waveout_lock:
245 if not self._waveout:
246 return
247 self._close()
248
253
258
260 caps = WAVEOUTCAPS()
261 for devID in xrange(-1, winmm.waveOutGetNumDevs()):
262 try:
263 winmm.waveOutGetDevCapsW(devID, byref(caps), sizeof(caps))
264 yield devID, caps.szPname
265 except WindowsError:
266
267 pass
268
270 """Obtain the names of all audio output devices on the system.
271 @return: The names of all output devices on the system.
272 @rtype: [str, ...]
273 """
274 return [name for ID, name in _getOutputDevices()]
275
277 """Obtain the name of an output device given its device ID.
278 @param ID: The device ID.
279 @type ID: int
280 @return: The device name.
281 @rtype: str
282 """
283 caps = WAVEOUTCAPS()
284 try:
285 winmm.waveOutGetDevCapsW(ID, byref(caps), sizeof(caps))
286 except WindowsError:
287 raise LookupError("No such device ID")
288 return caps.szPname
289
291 """Obtain the device ID of an output device given its name.
292 @param name: The device name.
293 @type name: str
294 @param useDefaultIfInvalid: C{True} to use the default device (wave mapper) if there is no such device,
295 C{False} to raise an exception.
296 @return: The device ID.
297 @rtype: int
298 @raise LookupError: If there is no such device and C{useDefaultIfInvalid} is C{False}.
299 """
300 for curID, curName in _getOutputDevices():
301 if curName == name:
302 return curID
303
304
305 if useDefaultIfInvalid:
306 return WAVE_MAPPER
307 else:
308 raise LookupError("No such device name")
309
310 fileWavePlayer = None
311 fileWavePlayerThread=None
329