from util import utf2unicode import os import traceback #------------------------------------------------------------------------------ ## @package mlx.sound # # Sound playback handling. # # This is the low level sound playback handling. The \ref initializeSound # function should be called to initialize the sound handling with the directory # containing the sound files. Then the \ref startSound function should be # called to start the playback of a certain sound file. A callback may be # called when the playback of a certain file has finished. # # See also the \ref mlx.soundsched module. #------------------------------------------------------------------------------ if os.name=="nt": import time import threading from ctypes import windll, c_buffer class MCIException(Exception): """MCI exception.""" def __init__(self, mci, command, errorCode): """Construct an MCI exception for the given error code.""" message = "MCI error: %s: %s" % (command, mci.getErrorString(errorCode)) super(MCIException, self).__init__(message) class MCI: """Interface for the Media Control Interface.""" def __init__(self): """Construct the interface.""" self.w32mci = windll.winmm.mciSendStringA self.w32mcierror = windll.winmm.mciGetErrorStringA def send(self, command): """Send the given command to the MCI.""" buffer = c_buffer(255) errorCode = self.w32mci(str(command), buffer, 254, 0) if errorCode: raise MCIException(self, command, errorCode) else: return buffer.value def getErrorString(self, errorCode): """Get the string representation of the given error code.""" buffer = c_buffer(255) self.w32mcierror(int(errorCode), buffer, 254) return buffer.value class SoundThread(threading.Thread): """The thread controlling the playback of sounds.""" def __init__(self, soundsDirectory): threading.Thread.__init__(self) self._soundsDirectory = soundsDirectory self._mci = MCI() self._requestCondition = threading.Condition() self._requests = [] self._pending = [] self._count = 0 self.daemon = True def requestSound(self, name, finishCallback = None, extra = None): """Request the playback of the sound with the given name.""" path = name if os.path.isabs(name) \ else os.path.join(self._soundsDirectory, name) with self._requestCondition: self._requests.append((path, (finishCallback, extra))) self._requestCondition.notify() def run(self): """Perform the operation of the thread. It waits for a request or a timeout. If a request is received, that is started to be played back. If a timeout occurs, the file is closed.""" while True: with self._requestCondition: if not self._requests: if self._pending: timeout = max(self._pending[0][0] - time.time(), 0.0) else: timeout = 10.0 #print "Waiting", timeout self._requestCondition.wait(timeout) requests = [] for (path, finishData) in self._requests: requests.append((path, finishData, self._count)) self._count += 1 self._requests = [] now = time.time() toClose = [] while self._pending and \ self._pending[0][0]<=now: toClose.append(self._pending[0][1]) del self._pending[0] for (alias, (finishCallback, extra)) in toClose: success = True try: print "Closing", alias self._mci.send("close " + alias) print "Closed", alias except Exception, e: print "Failed closing " + alias + ":", print utf2unicode(str(e)) success = False if finishCallback is not None: try: finishCallback(success, extra) except: traceback.print_exc() for (path, finishData, counter) in requests: try: alias = "mlxsound%d" % (counter,) print "Starting to play", path, "as", alias self._mci.send("open \"%s\" alias %s" % \ (path, alias)) self._mci.send("set %s time format milliseconds" % \ (alias,)) lengthBuffer = self._mci.send("status %s length" % \ (alias,)) self._mci.send("play %s from 0 to %s" % \ (alias, lengthBuffer)) length = int(lengthBuffer) timeout = time.time() + length / 1000.0 with self._requestCondition: self._pending.append((timeout, (alias, finishData))) self._pending.sort() print "Started to play", path except Exception, e: print "Failed to start playing " + path + ":", print utf2unicode(str(e)) (finishCallback, extra) = finishData if finishCallback is not None: try: finishCallback(None, extra) except: traceback.print_exc() _thread = None def initializeSound(soundsDirectory): """Initialize the sound handling with the given directory containing the sound files.""" global _thread _thread = SoundThread(soundsDirectory) _thread.start() def startSound(name, finishCallback = None, extra = None): """Start playing back the given sound. name should be the name of a sound file relative to the sound directory given in initializeSound.""" _thread.requestSound(name, finishCallback = finishCallback, extra = extra) def finalizeSound(): """Finalize the sound handling.""" pass #------------------------------------------------------------------------------ else: # os.name!="nt" import threading import time try: from common import gst_init, gobject, gst_element_factory_make from common import GST_STATE_PLAYING, GST_MESSAGE_EOS _soundsDirectory = None _bins = set() def initializeSound(soundsDirectory): """Initialize the sound handling with the given directory containing the sound files.""" gst_init() global _soundsDirectory _soundsDirectory = soundsDirectory def startSound(name, finishCallback = None, extra = None): """Start playing back the given sound.""" try: playBin = gst_element_factory_make("playbin") bus = playBin.get_bus() bus.enable_sync_message_emission() bus.add_signal_watch() bus.connect("message", _handlePlayBinMessage, playBin, finishCallback, extra) path = os.path.join(_soundsDirectory, name) playBin.set_property( "uri", "file://%s" % (path,)) playBin.set_state(GST_STATE_PLAYING) _bins.add(playBin) except Exception, e: print "mlx.sound.startSound: " + str(e) if finishCallback is not None: finishCallback(False, extra) def finalizeSound(): """Finalize the sound handling.""" pass def _handlePlayBinMessage(bus, message, bin, finishCallback, extra): """Handle the end of the playback of a sound file.""" # if message.type==GST_MESSAGE_EOS: # _bins.remove(bin) # if finishCallback is not None: # finishCallback(True, extra) except: print "The Gst library is missing from your system. It is needed for sound playback on Linux" traceback.print_exc() def initializeSound(soundsDirectory): """Initialize the sound handling with the given directory containing the sound files.""" pass def startSound(name, finishCallback = None, extra = None): """Start playing back the given sound. FIXME: it does not do anything currently, but it should.""" print "sound.startSound:", name def finalizeSound(): """Finalize the sound handling.""" pass #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ if __name__ == "__main__": def callback(result, extra): print "callback", result, extra initializeSound("e:\\home\\vi\\tmp") startSound("malev.mp3", finishCallback = callback, extra="malev.mp3") time.sleep(5) startSound("ding.wav", finishCallback = callback, extra="ding1.wav") time.sleep(5) startSound("ding.wav", finishCallback = callback, extra="ding2.wav") time.sleep(5) startSound("ding.wav", finishCallback = callback, extra="ding3.wav") time.sleep(50) #------------------------------------------------------------------------------