Module nvwave
[hide private]
[frames] | no frames]

Source Code for Module nvwave

  1  #nvwave.py 
  2  #A part of NonVisual Desktop Access (NVDA) 
  3  #Copyright (C) 2006-2008 NVDA Contributors <http://www.nvda-project.org/> 
  4  #This file is covered by the GNU General Public License. 
  5  #See the file COPYING for more details. 
  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   
26 -class WAVEFORMATEX(Structure):
27 _fields_ = [ 28 ("wFormatTag", WORD), 29 ("nChannels", WORD), 30 ("nSamplesPerSec", DWORD), 31 ("nAvgBytesPerSec", DWORD), 32 ("nBlockAlign", WORD), 33 ("wBitsPerSample", WORD), 34 ("cbSize", WORD) 35 ]
36 LPWAVEFORMATEX = POINTER(WAVEFORMATEX) 37
38 -class WAVEHDR(Structure):
39 pass
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 #CALLBACK_FUNCTION = 0x30000 59 CALLBACK_EVENT = 0x50000 60 #waveOutProc = CFUNCTYPE(HANDLE, UINT, DWORD, DWORD, DWORD) 61 #WOM_DONE = 0x3bd 62 63 MAXPNAMELEN = 32
64 -class WAVEOUTCAPS(Structure):
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 # Set argument types. 77 winmm.waveOutOpen.argtypes = (LPHWAVEOUT, UINT, LPWAVEFORMATEX, DWORD, DWORD, DWORD) 78 79 # Initialize error checking.
80 -def _winmm_errcheck(res, func, args):
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
92 -class WavePlayer(object):
93 """Synchronously play a stream of audio. 94 To use, construct an instance and feed it waveform audio using L{feed}. 95 """ 96 #: A lock to prevent WaveOut* functions from being called simultaneously, as this can cause problems even if they are for different HWAVEOUTs. 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 #: If C{True}, close the output device when no audio is being played. 121 #: @type: bool 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
129 - def open(self):
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
176 - def sync(self):
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
208 - def idle(self):
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
222 - def stop(self):
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 # Pausing first seems to make waveOutReset respond faster on some systems. 231 winmm.waveOutPause(self._waveout) 232 winmm.waveOutReset(self._waveout) 233 except WindowsError: 234 # waveOutReset seems to fail randomly on some systems. 235 pass 236 # Unprepare the previous buffer and close the output device if appropriate. 237 self.idle()
238
239 - def close(self):
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
249 - def _close(self):
250 with self._global_waveout_lock: 251 winmm.waveOutClose(self._waveout) 252 self._waveout = None
253
254 - def __del__(self):
255 self.close() 256 winKernel.kernel32.CloseHandle(self._waveout_event) 257 self._waveout_event = None
258
259 -def _getOutputDevices():
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 # It seems that in certain cases, Windows includes devices which cannot be accessed. 267 pass
268
269 -def getOutputDeviceNames():
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
276 -def outputDeviceIDToName(ID):
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
290 -def outputDeviceNameToID(name, useDefaultIfInvalid=False):
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 # No such ID. 305 if useDefaultIfInvalid: 306 return WAVE_MAPPER 307 else: 308 raise LookupError("No such device name")
309 310 fileWavePlayer = None 311 fileWavePlayerThread=None
312 -def playWaveFile(fileName, async=True):
313 """plays a specified wave file. 314 """ 315 global fileWavePlayer, fileWavePlayerThread 316 f = wave.open(fileName,"r") 317 if f is None: raise RuntimeError("can not open file %s"%fileName) 318 if fileWavePlayer is not None: 319 fileWavePlayer.stop() 320 fileWavePlayer = WavePlayer(channels=f.getnchannels(), samplesPerSec=f.getframerate(),bitsPerSample=f.getsampwidth()*8, outputDevice=config.conf["speech"]["outputDevice"]) 321 fileWavePlayer.feed(f.readframes(f.getnframes())) 322 if async: 323 if fileWavePlayerThread is not None: 324 fileWavePlayerThread.join() 325 fileWavePlayerThread=threading.Thread(target=fileWavePlayer.idle) 326 fileWavePlayerThread.start() 327 else: 328 fileWavePlayer.idle()
329