Changeset 2348

Show
Ignore:
Timestamp:
08/12/08 08:32:18 (5 months ago)
Author:
bzr
Message:

Merge nvwaveImprovements branch.

nvwave.WavePlayer?:

  • Close the output device when idle, rather than always keeping it open.
    • This means that if using the default output device (wave mapper) and the default output device changes (e.g. due to plugging in an external sound card), the new device will be used. Users will notice this for eSpeak and tones.
    • This also fixes the issue where the last chunk of audio would be cut off when using NVDA with eSpeak on a Remote Desktop server.
    • Note that tones may exhibit some strange effects when issued rapidly.
  • When stopping audio, pause first before resetting the output device. This is bizarre, but it seems to improve performance with some Vista audio drivers.
  • Other efficiency and reliability fixes.
Location:
trunk
Files:
3 modified

Legend:

Unmodified
Added
Removed
  • trunk

    • Property bzr:revision-id:v3-list-QlpoOTFBWSZTWbrL2vUAAB1VgAAQABCAQDrrnqAgAFCgaaGRkxBoTIJ6mmaNRwhndFAoNhZjh_YY4a01fOg1ulgNNC2UrzPdXXEnDpX8XckU4UJC6y9r1A..
      •  

        old new  
        1761762145 jamie@jantrid.net-20080807065623-wh0xcx1l34f41h39 
        1771772146 jamie@jantrid.net-20080812020217-6ofbi1u1q3sbq7ni 
         1782147 jamie@jantrid.net-20080812071342-5fqd45cc29z5ugh6 
    • Property bzr:revision-info
      •  

        old new  
        1 timestamp: 2008-08-12 12:02:17.473999977 +1000 
         1timestamp: 2008-08-12 17:13:42.000000000 +1000 
        22committer: James Teh <jamie@jantrid.net> 
        33properties:  
    • Property bzr:ancestry:v3-list-QlpoOTFBWSZTWbrL2vUAAB1VgAAQABCAQDrrnqAgAFCgaaGRkxBoTIJ6mmaNRwhndFAoNhZjh_YY4a01fOg1ulgNNC2UrzPdXXEnDpX8XckU4UJC6y9r1A..
      •  

        old new  
        88mick@kulgan.net-20080701041209-fd196zokjqabm0u6 
        99jamie@jantrid.net-20080702061732-274iwmap8bkwbp8b 
         10jamie@jantrid.net-20080810101432-te9chzpil51jwjtd 
  • trunk/source/nvwave.py

    r2343 r2348  
    99 
    1010from __future__ import with_statement 
    11 import time 
    1211import threading 
    1312from ctypes import * 
     
    1918 
    2019winmm = windll.winmm 
     20kernel32 = windll.kernel32 
     21 
     22INFINITE = 0xffffffff 
    2123 
    2224HWAVEOUT = HANDLE 
     
    5658CALLBACK_NULL = 0 
    5759#CALLBACK_FUNCTION = 0x30000 
     60CALLBACK_EVENT = 0x50000 
    5861#waveOutProc = CFUNCTYPE(HANDLE, UINT, DWORD, DWORD, DWORD) 
    5962#WOM_DONE = 0x3bd 
     
    9396        """ 
    9497 
    95         def __init__(self, channels, samplesPerSec, bitsPerSample, outputDevice=WAVE_MAPPER): 
     98        def __init__(self, channels, samplesPerSec, bitsPerSample, outputDevice=WAVE_MAPPER, closeWhenIdle=True): 
    9699                """Constructor. 
    97100                @param channels: The number of channels of audio; e.g. 2 for stereo, 1 for mono. 
     
    103106                @param outputDevice: The device ID or name of the audio output device to use. 
    104107                @type outputDevice: int or basestring 
     108                @param closeWhenIdle: If C{True}, close the output device when no audio is being played. 
     109                @type closeWhenIdle: bool 
    105110                @note: If C{outputDevice} is a name and no such device exists, the default device will be used. 
    106111                @raise WindowsError: If there was an error opening the audio output device. 
     
    112117                        outputDevice = outputDeviceNameToID(outputDevice, True) 
    113118                self.outputDeviceID = outputDevice 
    114                 self._open() 
    115                 self._whdr_lock = threading.RLock() 
    116  
    117         def _open(self): 
    118                 wfx = WAVEFORMATEX() 
    119                 wfx.wFormatTag = WAVE_FORMAT_PCM 
    120                 wfx.nChannels = self.channels 
    121                 wfx.nSamplesPerSec = self.samplesPerSec 
    122                 wfx.wBitsPerSample = self.bitsPerSample 
    123                 wfx.nBlockAlign = self.bitsPerSample / 8 * self.channels 
    124                 wfx.nAvgBytesPerSec = self.samplesPerSec * wfx.nBlockAlign 
    125                 waveout = HWAVEOUT(0) 
    126                 winmm.waveOutOpen(byref(waveout), self.outputDeviceID, LPWAVEFORMATEX(wfx), 0, 0, CALLBACK_NULL) 
    127                 self._waveout = waveout.value 
    128                 self._prev_whdr = None 
     119                #: If C{True}, close the output device when no audio is being played. 
     120                #: @type: bool 
     121                self.closeWhenIdle = closeWhenIdle 
     122                self._waveout = None 
     123                self._waveout_event = kernel32.CreateEventW(None, False, False, None) 
     124                self._waveout_lock = threading.RLock() 
     125                self._lock = threading.RLock() 
     126                self.open() 
     127 
     128        def open(self): 
     129                """Open the output device. 
     130                This will be called automatically when required. 
     131                It is not an error if the output device is already open. 
     132                """ 
     133                with self._waveout_lock: 
     134                        if self._waveout: 
     135                                return 
     136                        wfx = WAVEFORMATEX() 
     137                        wfx.wFormatTag = WAVE_FORMAT_PCM 
     138                        wfx.nChannels = self.channels 
     139                        wfx.nSamplesPerSec = self.samplesPerSec 
     140                        wfx.wBitsPerSample = self.bitsPerSample 
     141                        wfx.nBlockAlign = self.bitsPerSample / 8 * self.channels 
     142                        wfx.nAvgBytesPerSec = self.samplesPerSec * wfx.nBlockAlign 
     143                        waveout = HWAVEOUT(0) 
     144                        winmm.waveOutOpen(byref(waveout), self.outputDeviceID, LPWAVEFORMATEX(wfx), self._waveout_event, 0, CALLBACK_EVENT) 
     145                        self._waveout = waveout.value 
     146                        self._prev_whdr = None 
    129147 
    130148        def feed(self, data): 
     
    140158                whdr.lpData = data 
    141159                whdr.dwBufferLength = len(data) 
    142                 winmm.waveOutPrepareHeader(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR)) 
    143                 try: 
    144                         winmm.waveOutWrite(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR)) 
     160                with self._lock: 
     161                        with self._waveout_lock: 
     162                                self.open() 
     163                                winmm.waveOutPrepareHeader(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR)) 
     164                                try: 
     165                                        winmm.waveOutWrite(self._waveout, LPWAVEHDR(whdr), sizeof(WAVEHDR)) 
     166                                except WindowsError, e: 
     167                                        self.close() 
     168                                        raise e 
    145169                        self.sync() 
    146                 except WindowsError, e: 
    147                         self.close() 
    148                         self._open() 
    149                         raise e 
    150                 with self._whdr_lock: 
    151170                        self._prev_whdr = whdr 
    152171 
     
    154173                """Synchronise with playback. 
    155174                This method blocks until the previously fed chunk of audio has finished playing. 
    156                 It need only be called directly if there is no more audio to feed, but synchronisation is nevertheless desired. 
    157                 """ 
    158                 with self._whdr_lock: 
     175                It is called automatically by L{feed}, so usually need not be called directly by the user. 
     176                """ 
     177                with self._lock: 
    159178                        if not self._prev_whdr: 
    160179                                return 
    161                         # todo: Wait for an event instead of spinning. 
     180                        assert self._waveout, "waveOut None before wait" 
    162181                        while not (self._prev_whdr.dwFlags & WHDR_DONE): 
    163                                 time.sleep(0.005) 
    164                         winmm.waveOutUnprepareHeader(self._waveout, LPWAVEHDR(self._prev_whdr), sizeof(WAVEHDR)) 
     182                                kernel32.WaitForSingleObject(self._waveout_event, INFINITE) 
     183                        with self._waveout_lock: 
     184                                assert self._waveout, "waveOut None after wait" 
     185                                winmm.waveOutUnprepareHeader(self._waveout, LPWAVEHDR(self._prev_whdr), sizeof(WAVEHDR)) 
    165186                        self._prev_whdr = None 
    166187 
     
    170191                @type switch: bool 
    171192                """ 
    172                 if switch: 
    173                         winmm.waveOutPause(self._waveout) 
    174                 else: 
    175                         winmm.waveOutRestart(self._waveout) 
     193                with self._waveout_lock: 
     194                        if not self._waveout: 
     195                                return 
     196                        if switch: 
     197                                winmm.waveOutPause(self._waveout) 
     198                        else: 
     199                                winmm.waveOutRestart(self._waveout) 
     200 
     201        def idle(self): 
     202                """Indicate that this player is now idle; i.e. the current continuous segment  of audio is complete. 
     203                This will first call L{sync} to synchronise with playback. 
     204                If L{closeWhenIdle} is C{True}, the output device will be closed. 
     205                A subsequent call to L{feed} will reopen it. 
     206                """ 
     207                with self._lock: 
     208                        self.sync() 
     209                        with self._waveout_lock: 
     210                                if not self._waveout: 
     211                                        return 
     212                                if self.closeWhenIdle: 
     213                                        self._close() 
    176214 
    177215        def stop(self): 
    178216                """Stop playback. 
    179217                """ 
    180                 try: 
    181                         winmm.waveOutReset(self._waveout) 
    182                 except WindowsError: 
    183                         # waveOutReset seems to fail randomly on some systems. 
    184                         pass 
    185                 # Unprepare the previous buffer. 
    186                 self.sync() 
     218                with self._waveout_lock: 
     219                        if not self._waveout: 
     220                                return 
     221                        try: 
     222                                # Pausing first seems to make waveOutReset respond faster on some systems. 
     223                                winmm.waveOutPause(self._waveout) 
     224                                winmm.waveOutReset(self._waveout) 
     225                        except WindowsError: 
     226                                # waveOutReset seems to fail randomly on some systems. 
     227                                pass 
     228                # Unprepare the previous buffer and close the output device if appropriate. 
     229                self.idle() 
    187230 
    188231        def close(self): 
    189232                """Close the output device. 
    190                 @postcondition: The audio device is closed; this instance is no longer useable. 
    191233                """ 
    192234                self.stop() 
     235                with self._lock: 
     236                        with self._waveout_lock: 
     237                                if not self._waveout: 
     238                                        return 
     239                                self._close() 
     240 
     241        def _close(self): 
    193242                winmm.waveOutClose(self._waveout) 
    194243                self._waveout = None 
     244 
     245        def __del__(self): 
     246                self.close() 
     247                kernel32.CloseHandle(self._waveout_event) 
     248                self._waveout_event = None 
    195249 
    196250def _getOutputDevices(): 
     
    241295        # No such ID. 
    242296        if useDefaultIfInvalid: 
    243                 return -1 
     297                return WAVE_MAPPER 
    244298        else: 
    245299                raise LookupError("No such device name") 
  • trunk/source/synthDrivers/_espeak.py

    r2204 r2348  
    122122                lastIndex = event.contents.user_data 
    123123                if not wav: 
    124                         player.sync() 
     124                        player.idle() 
    125125                        isSpeaking = False 
    126126                        return 0