source: src/mlx/sound.py@ 585:1a277e09c6b9

Last change on this file since 585:1a277e09c6b9 was 585:1a277e09c6b9, checked in by István Váradi <ivaradi@…>, 9 years ago

Fixed the handling of the Gst library with PyGTK

File size: 10.0 KB
Line 
1
2from util import utf2unicode
3
4import os
5import traceback
6
7#------------------------------------------------------------------------------
8
9## @package mlx.sound
10#
11# Sound playback handling.
12#
13# This is the low level sound playback handling. The \ref initializeSound
14# function should be called to initialize the sound handling with the directory
15# containing the sound files. Then the \ref startSound function should be
16# called to start the playback of a certain sound file. A callback may be
17# called when the playback of a certain file has finished.
18#
19# See also the \ref mlx.soundsched module.
20
21#------------------------------------------------------------------------------
22
23if os.name=="nt":
24 import time
25 import threading
26 from ctypes import windll, c_buffer
27
28 class MCIException(Exception):
29 """MCI exception."""
30 def __init__(self, mci, command, errorCode):
31 """Construct an MCI exception for the given error code."""
32 message = "MCI error: %s: %s" % (command, mci.getErrorString(errorCode))
33 super(MCIException, self).__init__(message)
34
35 class MCI:
36 """Interface for the Media Control Interface."""
37 def __init__(self):
38 """Construct the interface."""
39 self.w32mci = windll.winmm.mciSendStringA
40 self.w32mcierror = windll.winmm.mciGetErrorStringA
41
42 def send(self, command):
43 """Send the given command to the MCI."""
44 buffer = c_buffer(255)
45 errorCode = self.w32mci(str(command), buffer, 254, 0)
46 if errorCode:
47 raise MCIException(self, command, errorCode)
48 else:
49 return buffer.value
50
51 def getErrorString(self, errorCode):
52 """Get the string representation of the given error code."""
53 buffer = c_buffer(255)
54 self.w32mcierror(int(errorCode), buffer, 254)
55 return buffer.value
56
57 class SoundThread(threading.Thread):
58 """The thread controlling the playback of sounds."""
59 def __init__(self, soundsDirectory):
60 threading.Thread.__init__(self)
61
62 self._soundsDirectory = soundsDirectory
63 self._mci = MCI()
64
65 self._requestCondition = threading.Condition()
66 self._requests = []
67 self._pending = []
68 self._count = 0
69
70 self.daemon = True
71
72 def requestSound(self, name, finishCallback = None, extra = None):
73 """Request the playback of the sound with the given name."""
74 path = name if os.path.isabs(name) \
75 else os.path.join(self._soundsDirectory, name)
76 with self._requestCondition:
77 self._requests.append((path, (finishCallback, extra)))
78 self._requestCondition.notify()
79
80 def run(self):
81 """Perform the operation of the thread.
82
83 It waits for a request or a timeout. If a request is received, that
84 is started to be played back. If a timeout occurs, the file is
85 closed."""
86
87 while True:
88 with self._requestCondition:
89 if not self._requests:
90 if self._pending:
91 timeout = max(self._pending[0][0] - time.time(),
92 0.0)
93 else:
94 timeout = 10.0
95
96 #print "Waiting", timeout
97 self._requestCondition.wait(timeout)
98
99 requests = []
100 for (path, finishData) in self._requests:
101 requests.append((path, finishData, self._count))
102 self._count += 1
103 self._requests = []
104
105 now = time.time()
106 toClose = []
107 while self._pending and \
108 self._pending[0][0]<=now:
109 toClose.append(self._pending[0][1])
110 del self._pending[0]
111
112 for (alias, (finishCallback, extra)) in toClose:
113 success = True
114 try:
115 print "Closing", alias
116 self._mci.send("close " + alias)
117 print "Closed", alias
118 except Exception, e:
119 print "Failed closing " + alias + ":",
120 print utf2unicode(str(e))
121 success = False
122
123 if finishCallback is not None:
124 try:
125 finishCallback(success, extra)
126 except:
127 traceback.print_exc()
128
129 for (path, finishData, counter) in requests:
130 try:
131 alias = "mlxsound%d" % (counter,)
132 print "Starting to play", path, "as", alias
133 self._mci.send("open \"%s\" alias %s" % \
134 (path, alias))
135 self._mci.send("set %s time format milliseconds" % \
136 (alias,))
137 lengthBuffer = self._mci.send("status %s length" % \
138 (alias,))
139 self._mci.send("play %s from 0 to %s" % \
140 (alias, lengthBuffer))
141 length = int(lengthBuffer)
142 timeout = time.time() + length / 1000.0
143 with self._requestCondition:
144 self._pending.append((timeout, (alias, finishData)))
145 self._pending.sort()
146 print "Started to play", path
147 except Exception, e:
148 print "Failed to start playing " + path + ":",
149 print utf2unicode(str(e))
150 (finishCallback, extra) = finishData
151 if finishCallback is not None:
152 try:
153 finishCallback(None, extra)
154 except:
155 traceback.print_exc()
156
157 _thread = None
158
159 def initializeSound(soundsDirectory):
160 """Initialize the sound handling with the given directory containing
161 the sound files."""
162 global _thread
163 _thread = SoundThread(soundsDirectory)
164 _thread.start()
165
166 def startSound(name, finishCallback = None, extra = None):
167 """Start playing back the given sound.
168
169 name should be the name of a sound file relative to the sound directory
170 given in initializeSound."""
171 _thread.requestSound(name, finishCallback = finishCallback,
172 extra = extra)
173
174 def finalizeSound():
175 """Finalize the sound handling."""
176 pass
177
178#------------------------------------------------------------------------------
179
180else: # os.name!="nt"
181 import threading
182 import time
183 try:
184 from common import gst_init, gobject, gst_element_factory_make
185 from common import GST_STATE_PLAYING, GST_MESSAGE_EOS
186
187 _soundsDirectory = None
188 _bins = set()
189
190 def initializeSound(soundsDirectory):
191 """Initialize the sound handling with the given directory containing
192 the sound files."""
193 gst_init()
194 global _soundsDirectory
195 _soundsDirectory = soundsDirectory
196
197 def startSound(name, finishCallback = None, extra = None):
198 """Start playing back the given sound."""
199 try:
200 playBin = gst_element_factory_make("playbin")
201
202 bus = playBin.get_bus()
203 bus.enable_sync_message_emission()
204 bus.add_signal_watch()
205 bus.connect("message", _handlePlayBinMessage,
206 playBin, finishCallback, extra)
207
208 path = os.path.join(_soundsDirectory, name)
209 playBin.set_property( "uri", "file://%s" % (path,))
210
211 playBin.set_state(GST_STATE_PLAYING)
212 _bins.add(playBin)
213 except Exception, e:
214 print "mlx.sound.startSound: " + str(e)
215 if finishCallback is not None:
216 finishCallback(False, extra)
217
218 def finalizeSound():
219 """Finalize the sound handling."""
220 pass
221
222 def _handlePlayBinMessage(bus, message, bin, finishCallback, extra):
223 """Handle the end of the playback of a sound file."""
224
225 # if message.type==GST_MESSAGE_EOS:
226 # _bins.remove(bin)
227 # if finishCallback is not None:
228 # finishCallback(True, extra)
229
230 except:
231 print "The Gst library is missing from your system. It is needed for sound playback on Linux"
232 traceback.print_exc()
233 def initializeSound(soundsDirectory):
234 """Initialize the sound handling with the given directory containing
235 the sound files."""
236 pass
237
238 def startSound(name, finishCallback = None, extra = None):
239 """Start playing back the given sound.
240
241 FIXME: it does not do anything currently, but it should."""
242 print "sound.startSound:", name
243
244 def finalizeSound():
245 """Finalize the sound handling."""
246 pass
247
248#------------------------------------------------------------------------------
249#------------------------------------------------------------------------------
250
251if __name__ == "__main__":
252 def callback(result, extra):
253 print "callback", result, extra
254
255 initializeSound("e:\\home\\vi\\tmp")
256 startSound("malev.mp3", finishCallback = callback, extra="malev.mp3")
257 time.sleep(5)
258 startSound("ding.wav", finishCallback = callback, extra="ding1.wav")
259 time.sleep(5)
260 startSound("ding.wav", finishCallback = callback, extra="ding2.wav")
261 time.sleep(5)
262 startSound("ding.wav", finishCallback = callback, extra="ding3.wav")
263 time.sleep(50)
264
265#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.