PageRenderTime 54ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/source/nvwave.py

https://bitbucket.org/nvdaaddonteam/nvda
Python | 328 lines | 212 code | 29 blank | 87 comment | 51 complexity | a49ef558531830634c794e6289d9b8ee MD5 | raw file
  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. """Provides a simple Python interface to playing audio using the Windows multimedia waveOut functions, as well as other useful utilities.
  7. """
  8. import threading
  9. from ctypes import *
  10. from ctypes.wintypes import *
  11. import winKernel
  12. import wave
  13. import config
  14. __all__ = (
  15. "WavePlayer", "getOutputDeviceNames", "outputDeviceIDToName", "outputDeviceNameToID",
  16. )
  17. winmm = windll.winmm
  18. HWAVEOUT = HANDLE
  19. LPHWAVEOUT = POINTER(HWAVEOUT)
  20. class WAVEFORMATEX(Structure):
  21. _fields_ = [
  22. ("wFormatTag", WORD),
  23. ("nChannels", WORD),
  24. ("nSamplesPerSec", DWORD),
  25. ("nAvgBytesPerSec", DWORD),
  26. ("nBlockAlign", WORD),
  27. ("wBitsPerSample", WORD),
  28. ("cbSize", WORD)
  29. ]
  30. LPWAVEFORMATEX = POINTER(WAVEFORMATEX)
  31. class WAVEHDR(Structure):
  32. pass
  33. LPWAVEHDR = POINTER(WAVEHDR)
  34. WAVEHDR._fields_ = [
  35. ("lpData", LPSTR),
  36. ("dwBufferLength", DWORD),
  37. ("dwBytesRecorded", DWORD),
  38. ("dwUser", DWORD),
  39. ("dwFlags", DWORD),
  40. ("dwLoops", DWORD),
  41. ("lpNext", LPWAVEHDR),
  42. ("reserved", DWORD)
  43. ]
  44. WHDR_DONE = 1
  45. WAVE_FORMAT_PCM = 1
  46. WAVE_MAPPER = -1
  47. MMSYSERR_NOERROR = 0
  48. CALLBACK_NULL = 0
  49. #CALLBACK_FUNCTION = 0x30000
  50. CALLBACK_EVENT = 0x50000
  51. #waveOutProc = CFUNCTYPE(HANDLE, UINT, DWORD, DWORD, DWORD)
  52. #WOM_DONE = 0x3bd
  53. MAXPNAMELEN = 32
  54. class WAVEOUTCAPS(Structure):
  55. _fields_ = [
  56. ('wMid', WORD),
  57. ('wPid', WORD),
  58. ('vDriverVersion', c_uint),
  59. ('szPname', WCHAR*MAXPNAMELEN),
  60. ('dwFormats', DWORD),
  61. ('wChannels', WORD),
  62. ('wReserved1', WORD),
  63. ('dwSupport', DWORD),
  64. ]
  65. # Set argument types.
  66. winmm.waveOutOpen.argtypes = (LPHWAVEOUT, UINT, LPWAVEFORMATEX, DWORD, DWORD, DWORD)
  67. # Initialize error checking.
  68. def _winmm_errcheck(res, func, args):
  69. if res != MMSYSERR_NOERROR:
  70. buf = create_unicode_buffer(256)
  71. winmm.waveOutGetErrorTextW(res, buf, sizeof(buf))
  72. raise WindowsError(res, buf.value)
  73. for func in (
  74. winmm.waveOutOpen, winmm.waveOutPrepareHeader, winmm.waveOutWrite, winmm.waveOutUnprepareHeader,
  75. winmm.waveOutPause, winmm.waveOutRestart, winmm.waveOutReset, winmm.waveOutClose,
  76. winmm.waveOutGetDevCapsW
  77. ):
  78. func.errcheck = _winmm_errcheck
  79. class WavePlayer(object):
  80. """Synchronously play a stream of audio.
  81. To use, construct an instance and feed it waveform audio using L{feed}.
  82. """
  83. #: A lock to prevent WaveOut* functions from being called simultaneously, as this can cause problems even if they are for different HWAVEOUTs.
  84. _global_waveout_lock = threading.RLock()
  85. def __init__(self, channels, samplesPerSec, bitsPerSample, outputDevice=WAVE_MAPPER, closeWhenIdle=True):
  86. """Constructor.
  87. @param channels: The number of channels of audio; e.g. 2 for stereo, 1 for mono.
  88. @type channels: int
  89. @param samplesPerSec: Samples per second (hz).
  90. @type samplesPerSec: int
  91. @param bitsPerSample: The number of bits per sample.
  92. @type bitsPerSample: int
  93. @param outputDevice: The device ID or name of the audio output device to use.
  94. @type outputDevice: int or basestring
  95. @param closeWhenIdle: If C{True}, close the output device when no audio is being played.
  96. @type closeWhenIdle: bool
  97. @note: If C{outputDevice} is a name and no such device exists, the default device will be used.
  98. @raise WindowsError: If there was an error opening the audio output device.
  99. """
  100. self.channels=channels
  101. self.samplesPerSec=samplesPerSec
  102. self.bitsPerSample=bitsPerSample
  103. if isinstance(outputDevice, basestring):
  104. outputDevice = outputDeviceNameToID(outputDevice, True)
  105. self.outputDeviceID = outputDevice
  106. #: If C{True}, close the output device when no audio is being played.
  107. #: @type: bool
  108. self.closeWhenIdle = closeWhenIdle
  109. self._waveout = None
  110. self._waveout_event = winKernel.kernel32.CreateEventW(None, False, False, None)
  111. self._waveout_lock = threading.RLock()
  112. self._lock = threading.RLock()
  113. self.open()
  114. def open(self):
  115. """Open the output device.
  116. This will be called automatically when required.
  117. It is not an error if the output device is already open.
  118. """
  119. with self._waveout_lock:
  120. if self._waveout:
  121. return
  122. wfx = WAVEFORMATEX()
  123. wfx.wFormatTag = WAVE_FORMAT_PCM
  124. wfx.nChannels = self.channels
  125. wfx.nSamplesPerSec = self.samplesPerSec
  126. wfx.wBitsPerSample = self.bitsPerSample
  127. wfx.nBlockAlign = self.bitsPerSample / 8 * self.channels
  128. wfx.nAvgBytesPerSec = self.samplesPerSec * wfx.nBlockAlign
  129. waveout = HWAVEOUT(0)
  130. with self._global_waveout_lock:
  131. winmm.waveOutOpen(byref(waveout), self.outputDeviceID, LPWAVEFORMATEX(wfx), self._waveout_event, 0, CALLBACK_EVENT)
  132. self._waveout = waveout.value
  133. self._prev_whdr = None
  134. def feed(self, data):
  135. """Feed a chunk of audio data to be played.
  136. This is normally synchronous.
  137. 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.
  138. This allows for uninterrupted playback as long as a new chunk is fed before the previous chunk has finished playing.
  139. @param data: Waveform audio in the format specified when this instance was constructed.
  140. @type data: str
  141. @raise WindowsError: If there was an error playing the audio.
  142. """
  143. whdr = WAVEHDR()
  144. whdr.lpData = data
  145. whdr.dwBufferLength = len(data)
  146. with self._lock:
  147. with self._waveout_lock:
  148. self.open()
  149. with self._global_waveout_lock:
  150. winmm.waveOutPrepareHeader(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR))
  151. try:
  152. with self._global_waveout_lock:
  153. winmm.waveOutWrite(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR))
  154. except WindowsError, e:
  155. self.close()
  156. raise e
  157. self.sync()
  158. self._prev_whdr = whdr
  159. def sync(self):
  160. """Synchronise with playback.
  161. This method blocks until the previously fed chunk of audio has finished playing.
  162. It is called automatically by L{feed}, so usually need not be called directly by the user.
  163. """
  164. with self._lock:
  165. if not self._prev_whdr:
  166. return
  167. assert self._waveout, "waveOut None before wait"
  168. while not (self._prev_whdr.dwFlags & WHDR_DONE):
  169. winKernel.waitForSingleObject(self._waveout_event, winKernel.INFINITE)
  170. with self._waveout_lock:
  171. assert self._waveout, "waveOut None after wait"
  172. with self._global_waveout_lock:
  173. winmm.waveOutUnprepareHeader(self._waveout, LPWAVEHDR(self._prev_whdr), sizeof(WAVEHDR))
  174. self._prev_whdr = None
  175. def pause(self, switch):
  176. """Pause or unpause playback.
  177. @param switch: C{True} to pause playback, C{False} to unpause.
  178. @type switch: bool
  179. """
  180. with self._waveout_lock:
  181. if not self._waveout:
  182. return
  183. if switch:
  184. with self._global_waveout_lock:
  185. winmm.waveOutPause(self._waveout)
  186. else:
  187. with self._global_waveout_lock:
  188. winmm.waveOutRestart(self._waveout)
  189. def idle(self):
  190. """Indicate that this player is now idle; i.e. the current continuous segment of audio is complete.
  191. This will first call L{sync} to synchronise with playback.
  192. If L{closeWhenIdle} is C{True}, the output device will be closed.
  193. A subsequent call to L{feed} will reopen it.
  194. """
  195. with self._lock:
  196. self.sync()
  197. with self._waveout_lock:
  198. if not self._waveout:
  199. return
  200. if self.closeWhenIdle:
  201. self._close()
  202. def stop(self):
  203. """Stop playback.
  204. """
  205. with self._waveout_lock:
  206. if not self._waveout:
  207. return
  208. try:
  209. with self._global_waveout_lock:
  210. # Pausing first seems to make waveOutReset respond faster on some systems.
  211. winmm.waveOutPause(self._waveout)
  212. winmm.waveOutReset(self._waveout)
  213. except WindowsError:
  214. # waveOutReset seems to fail randomly on some systems.
  215. pass
  216. # Unprepare the previous buffer and close the output device if appropriate.
  217. self.idle()
  218. def close(self):
  219. """Close the output device.
  220. """
  221. self.stop()
  222. with self._lock:
  223. with self._waveout_lock:
  224. if not self._waveout:
  225. return
  226. self._close()
  227. def _close(self):
  228. with self._global_waveout_lock:
  229. winmm.waveOutClose(self._waveout)
  230. self._waveout = None
  231. def __del__(self):
  232. self.close()
  233. winKernel.kernel32.CloseHandle(self._waveout_event)
  234. self._waveout_event = None
  235. def _getOutputDevices():
  236. caps = WAVEOUTCAPS()
  237. for devID in xrange(-1, winmm.waveOutGetNumDevs()):
  238. try:
  239. winmm.waveOutGetDevCapsW(devID, byref(caps), sizeof(caps))
  240. yield devID, caps.szPname
  241. except WindowsError:
  242. # It seems that in certain cases, Windows includes devices which cannot be accessed.
  243. pass
  244. def getOutputDeviceNames():
  245. """Obtain the names of all audio output devices on the system.
  246. @return: The names of all output devices on the system.
  247. @rtype: [str, ...]
  248. """
  249. return [name for ID, name in _getOutputDevices()]
  250. def outputDeviceIDToName(ID):
  251. """Obtain the name of an output device given its device ID.
  252. @param ID: The device ID.
  253. @type ID: int
  254. @return: The device name.
  255. @rtype: str
  256. """
  257. caps = WAVEOUTCAPS()
  258. try:
  259. winmm.waveOutGetDevCapsW(ID, byref(caps), sizeof(caps))
  260. except WindowsError:
  261. raise LookupError("No such device ID")
  262. return caps.szPname
  263. def outputDeviceNameToID(name, useDefaultIfInvalid=False):
  264. """Obtain the device ID of an output device given its name.
  265. @param name: The device name.
  266. @type name: str
  267. @param useDefaultIfInvalid: C{True} to use the default device (wave mapper) if there is no such device,
  268. C{False} to raise an exception.
  269. @return: The device ID.
  270. @rtype: int
  271. @raise LookupError: If there is no such device and C{useDefaultIfInvalid} is C{False}.
  272. """
  273. for curID, curName in _getOutputDevices():
  274. if curName == name:
  275. return curID
  276. # No such ID.
  277. if useDefaultIfInvalid:
  278. return WAVE_MAPPER
  279. else:
  280. raise LookupError("No such device name")
  281. fileWavePlayer = None
  282. fileWavePlayerThread=None
  283. def playWaveFile(fileName, async=True):
  284. """plays a specified wave file.
  285. """
  286. global fileWavePlayer, fileWavePlayerThread
  287. f = wave.open(fileName,"r")
  288. if f is None: raise RuntimeError("can not open file %s"%fileName)
  289. if fileWavePlayer is not None:
  290. fileWavePlayer.stop()
  291. fileWavePlayer = WavePlayer(channels=f.getnchannels(), samplesPerSec=f.getframerate(),bitsPerSample=f.getsampwidth()*8, outputDevice=config.conf["speech"]["outputDevice"])
  292. fileWavePlayer.feed(f.readframes(f.getnframes()))
  293. if async:
  294. if fileWavePlayerThread is not None:
  295. fileWavePlayerThread.join()
  296. fileWavePlayerThread=threading.Thread(target=fileWavePlayer.idle)
  297. fileWavePlayerThread.start()
  298. else:
  299. fileWavePlayer.idle()