test_dummy_soundplay_node.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 #***********************************************************
4 #* Software License Agreement (BSD License)
5 #*
6 #* Copyright (c) 2009, Willow Garage, Inc.
7 #* All rights reserved.
8 #*
9 #* Redistribution and use in source and binary forms, with or without
10 #* modification, are permitted provided that the following conditions
11 #* are met:
12 #*
13 #* * Redistributions of source code must retain the above copyright
14 #* notice, this list of conditions and the following disclaimer.
15 #* * Redistributions in binary form must reproduce the above
16 #* copyright notice, this list of conditions and the following
17 #* disclaimer in the documentation and/or other materials provided
18 #* with the distribution.
19 #* * Neither the name of the Willow Garage nor the names of its
20 #* contributors may be used to endorse or promote products derived
21 #* from this software without specific prior written permission.
22 #*
23 #* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 #* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 #* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 #* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 #* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 #* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 #* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 #* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 #* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 #* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 #* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 #* POSSIBILITY OF SUCH DAMAGE.
35 #***********************************************************
36 
37 # Author: Blaise Gassend
38 
39 # 08-Feb-2019: Kei Okada remove gst stuff for Travis tests
40 import roslib
41 import rospy
42 import threading
43 import os
44 import logging
45 import sys
46 import traceback
47 import tempfile
48 from diagnostic_msgs.msg import DiagnosticStatus, KeyValue, DiagnosticArray
49 from sound_play.msg import SoundRequest, SoundRequestAction, SoundRequestResult, SoundRequestFeedback
50 import actionlib
51 
52 # try:
53 # import pygst
54 # pygst.require('0.10')
55 # import gst
56 # import gobject
57 # except:
58 # str="""
59 # **************************************************************
60 # Error opening pygst. Is gstreamer installed? (sudo apt-get install python-gst0.10
61 # **************************************************************
62 # """
63 # rospy.logfatal(str)
64 # print str
65 # exit(1)
66 
67 def sleep(t):
68  try:
69  rospy.sleep(t)
70  except:
71  pass
72 
73 
74 class soundtype:
75  STOPPED = 0
76  LOOPING = 1
77  COUNTING = 2
78 
79  def __init__(self, file, device, volume = 1.0):
80  self.lock = threading.RLock()
81  self.state = self.STOPPED
82  # self.sound = gst.element_factory_make("playbin2","player")
83  if device:
84  pass
85  # self.sink = gst.element_factory_make("alsasink", "sink")
86  # self.sink.set_property("device", device)
87  # self.sound.set_property("audio-sink", self.sink)
88  if (":" in file):
89  uri = file
90  elif os.path.isfile(file):
91  uri = "file://" + os.path.abspath(file)
92  else:
93  rospy.logerr('Error: URI is invalid: %s'%file)
94 
95  self.uri = uri
96  self.volume = volume
97  #self.sound.set_property('uri', uri)
98  #self.sound.set_property("volume",volume)
99  self.staleness = 1
100  self.file = file
101 
102  # self.bus = self.sound.get_bus()
103  # self.bus.add_signal_watch()
104  # self.bus.connect("message", self.on_stream_end)
105 
106  # def on_stream_end(self, bus, message):
107  # if message.type == gst.MESSAGE_EOS:
108  # self.state = self.STOPPED
109 
110  def __del__(self):
111  # stop our GST object so that it gets garbage-collected
112  self.stop()
113 
114  def update(self):
115  #self.bus.poll(gst.MESSAGE_ERROR, 10)
116  self.state = self.STOPPED
117 
118  def loop(self):
119  self.lock.acquire()
120  try:
121  self.staleness = 0
122 
123  if self.state == self.COUNTING:
124  self.stop()
125 
126  if self.state == self.STOPPED:
127  pass
128  # self.sound.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0)
129  # self.sound.set_state(gst.STATE_PLAYING)
130  self.state = self.LOOPING
131  finally:
132  self.lock.release()
133 
134  def stop(self):
135  if self.state != self.STOPPED:
136  self.lock.acquire()
137  try:
138  # self.sound.set_state(gst.STATE_NULL)
139  self.state = self.STOPPED
140  finally:
141  self.lock.release()
142 
143  def single(self):
144  self.lock.acquire()
145  try:
146  rospy.logdebug("Playing %s"%self.uri)
147  self.staleness = 0
148  if self.state == self.LOOPING:
149  self.stop()
150 
151  # self.sound.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0)
152  # self.sound.set_state(gst.STATE_PLAYING)
153  self.state = self.COUNTING
154  finally:
155  self.lock.release()
156 
157  def command(self, cmd):
158  if cmd == SoundRequest.PLAY_STOP:
159  self.stop()
160  elif cmd == SoundRequest.PLAY_ONCE:
161  self.single()
162  elif cmd == SoundRequest.PLAY_START:
163  self.loop()
164 
165  def get_staleness(self):
166  self.lock.acquire()
167  position = 0
168  duration = 0
169  try:
170  pass
171  # position = self.sound.query_position(gst.FORMAT_TIME)[0]
172  # duration = self.sound.query_duration(gst.FORMAT_TIME)[0]
173  except Exception, e:
174  position = 0
175  duration = 0
176  finally:
177  self.lock.release()
178 
179  if position != duration:
180  self.staleness = 0
181  else:
182  self.staleness = self.staleness + 1
183  return self.staleness
184 
185  def get_playing(self):
186  return self.state == self.COUNTING
187 
188 class soundplay:
189  _feedback = SoundRequestFeedback()
190  _result = SoundRequestResult()
191 
192  def stopdict(self,dict):
193  for sound in dict.values():
194  sound.stop()
195 
196  def stopall(self):
197  self.stopdict(self.builtinsounds)
198  self.stopdict(self.filesounds)
199  self.stopdict(self.voicesounds)
200 
201  def select_sound(self, data):
202  if data.sound == SoundRequest.PLAY_FILE:
203  if not data.arg2:
204  if not data.arg in self.filesounds.keys():
205  rospy.logdebug('command for uncached wave: "%s"'%data.arg)
206  try:
207  self.filesounds[data.arg] = soundtype(data.arg, self.device)
208  except:
209  rospy.logerr('Error setting up to play "%s". Does this file exist on the machine on which sound_play is running?'%data.arg)
210  return
211  else:
212  rospy.logdebug('command for cached wave: "%s"'%data.arg)
213  sound = self.filesounds[data.arg]
214  else:
215  absfilename = os.path.join(roslib.packages.get_pkg_dir(data.arg2), data.arg)
216  if not absfilename in self.filesounds.keys():
217  rospy.logdebug('command for uncached wave: "%s"'%absfilename)
218  try:
219  self.filesounds[absfilename] = soundtype(absfilename, self.device)
220  except:
221  rospy.logerr('Error setting up to play "%s" from package "%s". Does this file exist on the machine on which sound_play is running?'%(data.arg, data.arg2))
222  return
223  else:
224  rospy.logdebug('command for cached wave: "%s"'%absfilename)
225  sound = self.filesounds[absfilename]
226  elif data.sound == SoundRequest.SAY:
227  if not data.arg in self.voicesounds.keys():
228  rospy.logdebug('command for uncached text: "%s"' % data.arg)
229  txtfile = tempfile.NamedTemporaryFile(prefix='sound_play', suffix='.txt')
230  (wavfile,wavfilename) = tempfile.mkstemp(prefix='sound_play', suffix='.wav')
231  txtfilename=txtfile.name
232  os.close(wavfile)
233  voice = data.arg2
234  try:
235  txtfile.write(data.arg)
236  txtfile.flush()
237  rospy.loginfo("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
238  '''
239  os.system("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
240  try:
241  if os.stat(wavfilename).st_size == 0:
242  raise OSError # So we hit the same catch block
243  except OSError:
244  rospy.logerr("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
245  rospy.logerr('Sound synthesis failed. Is festival installed? Is a festival voice installed? Try running "rosdep satisfy sound_play|sh". Refer to http://wiki.ros.org/sound_play/Troubleshooting')
246  return
247  '''
248  self.voicesounds[data.arg] = soundtype(wavfilename, self.device)
249  finally:
250  txtfile.close()
251  else:
252  rospy.logdebug('command for cached text: "%s"'%data.arg)
253  sound = self.voicesounds[data.arg]
254  else:
255  rospy.logdebug('command for builtin wave: %i'%data.sound)
256  if not data.sound in self.builtinsounds:
257  params = self.builtinsoundparams[data.sound]
258  self.builtinsounds[data.sound] = soundtype(params[0], self.device, params[1])
259  sound = self.builtinsounds[data.sound]
260  if sound.staleness != 0 and data.command != SoundRequest.PLAY_STOP:
261  # This sound isn't counted in active_sounds
262  rospy.logdebug("activating %i %s"%(data.sound,data.arg))
263  self.active_sounds = self.active_sounds + 1
264  sound.staleness = 0
265  # if self.active_sounds > self.num_channels:
266  # mixer.set_num_channels(self.active_sounds)
267  # self.num_channels = self.active_sounds
268  return sound
269 
270  def callback(self,data):
271  if not self.initialized:
272  return
273  self.mutex.acquire()
274  # Force only one sound at a time
275  self.stopall()
276  try:
277  if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP:
278  self.stopall()
279  else:
280  sound = self.select_sound(data)
281  sound.command(data.command)
282  except Exception, e:
283  rospy.logerr('Exception in callback: %s'%str(e))
284  rospy.loginfo(traceback.format_exc())
285  finally:
286  self.mutex.release()
287  rospy.logdebug("done callback")
288 
289  # Purge sounds that haven't been played in a while.
290  def cleanupdict(self, dict):
291  purgelist = []
292  for (key,sound) in dict.iteritems():
293  try:
294  staleness = sound.get_staleness()
295  except Exception, e:
296  rospy.logerr('Exception in cleanupdict for sound (%s): %s'%(str(key),str(e)))
297  staleness = 100 # Something is wrong. Let's purge and try again.
298  #print "%s %i"%(key, staleness)
299  if staleness >= 10:
300  purgelist.append(key)
301  if staleness == 0: # Sound is playing
302  self.active_sounds = self.active_sounds + 1
303  for key in purgelist:
304  rospy.logdebug('Purging %s from cache'%key)
305  dict[key].stop() # clean up resources
306  del dict[key]
307 
308  def cleanup(self):
309  self.mutex.acquire()
310  try:
311  self.active_sounds = 0
312  self.cleanupdict(self.filesounds)
313  self.cleanupdict(self.voicesounds)
314  self.cleanupdict(self.builtinsounds)
315  except:
316  rospy.loginfo('Exception in cleanup: %s'%sys.exc_info()[0])
317  finally:
318  self.mutex.release()
319 
320  def diagnostics(self, state):
321  try:
322  da = DiagnosticArray()
323  ds = DiagnosticStatus()
324  ds.name = rospy.get_caller_id().lstrip('/') + ": Node State"
325  if state == 0:
326  ds.level = DiagnosticStatus.OK
327  ds.message = "%i sounds playing"%self.active_sounds
328  ds.values.append(KeyValue("Active sounds", str(self.active_sounds)))
329  ds.values.append(KeyValue("Allocated sound channels", str(self.num_channels)))
330  ds.values.append(KeyValue("Buffered builtin sounds", str(len(self.builtinsounds))))
331  ds.values.append(KeyValue("Buffered wave sounds", str(len(self.filesounds))))
332  ds.values.append(KeyValue("Buffered voice sounds", str(len(self.voicesounds))))
333  elif state == 1:
334  ds.level = DiagnosticStatus.WARN
335  ds.message = "Sound device not open yet."
336  else:
337  ds.level = DiagnosticStatus.ERROR
338  ds.message = "Can't open sound device. See http://wiki.ros.org/sound_play/Troubleshooting"
339  da.status.append(ds)
340  da.header.stamp = rospy.get_rostime()
341  self.diagnostic_pub.publish(da)
342  except Exception, e:
343  rospy.loginfo('Exception in diagnostics: %s'%str(e))
344 
345  def execute_cb(self, data):
346  data = data.sound_request
347  if not self.initialized:
348  return
349  self.mutex.acquire()
350  # Force only one sound at a time
351  self.stopall()
352  try:
353  if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP:
354  self.stopall()
355  else:
356  sound = self.select_sound(data)
357  sound.command(data.command)
358 
359  r = rospy.Rate(1)
360  start_time = rospy.get_rostime()
361  success = True
362  while sound.get_playing():
363  sound.update()
364  if self._as.is_preempt_requested():
365  rospy.loginfo('sound_play action: Preempted')
366  sound.stop()
367  self._as.set_preempted()
368  success = False
369  break
370 
371  self._feedback.playing = sound.get_playing()
372  self._feedback.stamp = rospy.get_rostime() - start_time
373  self._as.publish_feedback(self._feedback)
374  r.sleep()
375 
376  if success:
377  self._result.playing = self._feedback.playing
378  self._result.stamp = self._feedback.stamp
379  rospy.loginfo('sound_play action: Succeeded')
380  self._as.set_succeeded(self._result)
381 
382  except Exception, e:
383  rospy.logerr('Exception in actionlib callback: %s'%str(e))
384  rospy.loginfo(traceback.format_exc())
385  finally:
386  self.mutex.release()
387  rospy.logdebug("done actionlib callback")
388 
389  def __init__(self):
390  rospy.init_node('sound_play')
391  self.device = rospy.get_param("~device", str())
392  self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray, queue_size=1)
393  rootdir = os.path.join(roslib.packages.get_pkg_dir('sound_play'),'sounds')
394 
396  SoundRequest.BACKINGUP : (os.path.join(rootdir, 'BACKINGUP.ogg'), 0.1),
397  SoundRequest.NEEDS_UNPLUGGING : (os.path.join(rootdir, 'NEEDS_UNPLUGGING.ogg'), 1),
398  SoundRequest.NEEDS_PLUGGING : (os.path.join(rootdir, 'NEEDS_PLUGGING.ogg'), 1),
399  SoundRequest.NEEDS_UNPLUGGING_BADLY : (os.path.join(rootdir, 'NEEDS_UNPLUGGING_BADLY.ogg'), 1),
400  SoundRequest.NEEDS_PLUGGING_BADLY : (os.path.join(rootdir, 'NEEDS_PLUGGING_BADLY.ogg'), 1),
401  }
402 
403  self.no_error = True
404  self.initialized = False
405  self.active_sounds = 0
406 
407  self.mutex = threading.Lock()
408  sub = rospy.Subscriber("robotsound", SoundRequest, self.callback)
409  self._as = actionlib.SimpleActionServer('sound_play', SoundRequestAction, execute_cb=self.execute_cb, auto_start = False)
410  self._as.start()
411 
412  self.mutex.acquire()
413  self.sleep(0.5) # For ros startup race condition
414  self.diagnostics(1)
415 
416  while not rospy.is_shutdown():
417  while not rospy.is_shutdown():
418  self.init_vars()
419  self.no_error = True
420  self.initialized = True
421  self.mutex.release()
422  try:
423  self.idle_loop()
424  # Returns after inactive period to test device availability
425  #print "Exiting idle"
426  except:
427  rospy.loginfo('Exception in idle_loop: %s'%sys.exc_info()[0])
428  finally:
429  self.mutex.acquire()
430 
431  self.diagnostics(2)
432  self.mutex.release()
433 
434  def init_vars(self):
435  self.num_channels = 10
436  self.builtinsounds = {}
437  self.filesounds = {}
438  self.voicesounds = {}
439  self.hotlist = []
440  if not self.initialized:
441  rospy.loginfo('sound_play node is ready to play sound')
442 
443  def sleep(self, duration):
444  try:
445  rospy.sleep(duration)
446  except rospy.exceptions.ROSInterruptException:
447  pass
448 
449  def idle_loop(self):
450  self.last_activity_time = rospy.get_time()
451  while (rospy.get_time() - self.last_activity_time < 10 or
452  len(self.builtinsounds) + len(self.voicesounds) + len(self.filesounds) > 0) \
453  and not rospy.is_shutdown():
454  #print "idle_loop"
455  self.diagnostics(0)
456  self.sleep(1)
457  self.cleanup()
458  #print "idle_exiting"
459 
460 if __name__ == '__main__':
461  soundplay()
def __init__(self, file, device, volume=1.0)


pr2eus
Author(s): Kei Okada
autogenerated on Fri Mar 5 2021 04:00:40