test_dummy_soundplay_node.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 #***********************************************************
00004 #* Software License Agreement (BSD License)
00005 #*
00006 #*  Copyright (c) 2009, Willow Garage, Inc.
00007 #*  All rights reserved.
00008 #*
00009 #*  Redistribution and use in source and binary forms, with or without
00010 #*  modification, are permitted provided that the following conditions
00011 #*  are met:
00012 #*
00013 #*   * Redistributions of source code must retain the above copyright
00014 #*     notice, this list of conditions and the following disclaimer.
00015 #*   * Redistributions in binary form must reproduce the above
00016 #*     copyright notice, this list of conditions and the following
00017 #*     disclaimer in the documentation and/or other materials provided
00018 #*     with the distribution.
00019 #*   * Neither the name of the Willow Garage nor the names of its
00020 #*     contributors may be used to endorse or promote products derived
00021 #*     from this software without specific prior written permission.
00022 #*
00023 #*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00024 #*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00025 #*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00026 #*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00027 #*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00028 #*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00029 #*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00030 #*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00031 #*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00032 #*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00033 #*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00034 #*  POSSIBILITY OF SUCH DAMAGE.
00035 #***********************************************************
00036 
00037 # Author: Blaise Gassend
00038 
00039 # 08-Feb-2019: Kei Okada remove gst stuff for Travis tests
00040 import roslib
00041 import rospy
00042 import threading
00043 import os
00044 import logging
00045 import sys
00046 import traceback
00047 import tempfile
00048 from diagnostic_msgs.msg import DiagnosticStatus, KeyValue, DiagnosticArray
00049 from sound_play.msg import SoundRequest, SoundRequestAction, SoundRequestResult, SoundRequestFeedback
00050 import actionlib
00051 
00052 # try:
00053 #     import pygst
00054 #     pygst.require('0.10')
00055 #     import gst
00056 #     import gobject
00057 # except:
00058 #     str="""
00059 # **************************************************************
00060 # Error opening pygst. Is gstreamer installed? (sudo apt-get install python-gst0.10
00061 # **************************************************************
00062 # """
00063 #     rospy.logfatal(str)
00064 #     print str
00065 #     exit(1)
00066 
00067 def sleep(t):
00068     try:
00069         rospy.sleep(t)
00070     except:
00071         pass
00072 
00073 
00074 class soundtype:
00075     STOPPED = 0
00076     LOOPING = 1
00077     COUNTING = 2
00078 
00079     def __init__(self, file, device, volume = 1.0):
00080         self.lock = threading.RLock()
00081         self.state = self.STOPPED
00082         # self.sound = gst.element_factory_make("playbin2","player")
00083         if device:
00084             pass
00085             # self.sink = gst.element_factory_make("alsasink", "sink")
00086             # self.sink.set_property("device", device)
00087             # self.sound.set_property("audio-sink", self.sink)
00088         if (":" in file):
00089             uri = file
00090         elif os.path.isfile(file):
00091             uri = "file://" + os.path.abspath(file)
00092         else:
00093           rospy.logerr('Error: URI is invalid: %s'%file)
00094 
00095         self.uri = uri
00096         self.volume = volume
00097         #self.sound.set_property('uri', uri)
00098         #self.sound.set_property("volume",volume)
00099         self.staleness = 1
00100         self.file = file
00101 
00102         # self.bus = self.sound.get_bus()
00103         # self.bus.add_signal_watch()
00104         # self.bus.connect("message", self.on_stream_end)
00105 
00106     # def on_stream_end(self, bus, message):
00107     #     if message.type == gst.MESSAGE_EOS:
00108     #         self.state = self.STOPPED
00109 
00110     def __del__(self):
00111         # stop our GST object so that it gets garbage-collected
00112         self.stop()
00113 
00114     def update(self):
00115         #self.bus.poll(gst.MESSAGE_ERROR, 10)
00116         self.state = self.STOPPED
00117 
00118     def loop(self):
00119         self.lock.acquire()
00120         try:
00121             self.staleness = 0
00122 
00123             if self.state == self.COUNTING:
00124                 self.stop()
00125 
00126             if self.state == self.STOPPED:
00127                 pass
00128               # self.sound.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0)
00129               # self.sound.set_state(gst.STATE_PLAYING)
00130             self.state = self.LOOPING
00131         finally:
00132             self.lock.release()
00133 
00134     def stop(self):
00135         if self.state != self.STOPPED:
00136             self.lock.acquire()
00137             try:
00138                 # self.sound.set_state(gst.STATE_NULL)
00139                 self.state = self.STOPPED
00140             finally:
00141                 self.lock.release()
00142 
00143     def single(self):
00144         self.lock.acquire()
00145         try:
00146             rospy.logdebug("Playing %s"%self.uri)
00147             self.staleness = 0
00148             if self.state == self.LOOPING:
00149                 self.stop()
00150 
00151             # self.sound.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, 0)
00152             # self.sound.set_state(gst.STATE_PLAYING)
00153             self.state = self.COUNTING
00154         finally:
00155             self.lock.release()
00156 
00157     def command(self, cmd):
00158          if cmd == SoundRequest.PLAY_STOP:
00159              self.stop()
00160          elif cmd == SoundRequest.PLAY_ONCE:
00161              self.single()
00162          elif cmd == SoundRequest.PLAY_START:
00163              self.loop()
00164 
00165     def get_staleness(self):
00166         self.lock.acquire()
00167         position = 0
00168         duration = 0
00169         try:
00170             pass
00171             # position = self.sound.query_position(gst.FORMAT_TIME)[0]
00172             # duration = self.sound.query_duration(gst.FORMAT_TIME)[0]
00173         except Exception, e:
00174             position = 0
00175             duration = 0
00176         finally:
00177             self.lock.release()
00178 
00179         if position != duration:
00180             self.staleness = 0
00181         else:
00182             self.staleness = self.staleness + 1
00183         return self.staleness
00184 
00185     def get_playing(self):
00186         return self.state == self.COUNTING
00187 
00188 class soundplay:
00189     _feedback = SoundRequestFeedback()
00190     _result   = SoundRequestResult()
00191 
00192     def stopdict(self,dict):
00193         for sound in dict.values():
00194             sound.stop()
00195 
00196     def stopall(self):
00197         self.stopdict(self.builtinsounds)
00198         self.stopdict(self.filesounds)
00199         self.stopdict(self.voicesounds)
00200 
00201     def select_sound(self, data):
00202         if data.sound == SoundRequest.PLAY_FILE:
00203             if not data.arg2:
00204                 if not data.arg in self.filesounds.keys():
00205                     rospy.logdebug('command for uncached wave: "%s"'%data.arg)
00206                     try:
00207                         self.filesounds[data.arg] = soundtype(data.arg, self.device)
00208                     except:
00209                         rospy.logerr('Error setting up to play "%s". Does this file exist on the machine on which sound_play is running?'%data.arg)
00210                         return
00211                 else:
00212                     rospy.logdebug('command for cached wave: "%s"'%data.arg)
00213                 sound = self.filesounds[data.arg]
00214             else:
00215                 absfilename = os.path.join(roslib.packages.get_pkg_dir(data.arg2), data.arg)
00216                 if not absfilename in self.filesounds.keys():
00217                     rospy.logdebug('command for uncached wave: "%s"'%absfilename)
00218                     try:
00219                         self.filesounds[absfilename] = soundtype(absfilename, self.device)
00220                     except:
00221                         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))
00222                         return
00223                 else:
00224                     rospy.logdebug('command for cached wave: "%s"'%absfilename)
00225                 sound = self.filesounds[absfilename]
00226         elif data.sound == SoundRequest.SAY:
00227             if not data.arg in self.voicesounds.keys():
00228                 rospy.logdebug('command for uncached text: "%s"' % data.arg)
00229                 txtfile = tempfile.NamedTemporaryFile(prefix='sound_play', suffix='.txt')
00230                 (wavfile,wavfilename) = tempfile.mkstemp(prefix='sound_play', suffix='.wav')
00231                 txtfilename=txtfile.name
00232                 os.close(wavfile)
00233                 voice = data.arg2
00234                 try:
00235                     txtfile.write(data.arg)
00236                     txtfile.flush()
00237                     rospy.loginfo("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
00238                     '''
00239                     os.system("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
00240                     try:
00241                         if os.stat(wavfilename).st_size == 0:
00242                             raise OSError # So we hit the same catch block
00243                     except OSError:
00244                         rospy.logerr("text2wave -eval '("+voice+")' "+txtfilename+" -o "+wavfilename)
00245                         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')
00246                         return
00247                     '''
00248                     self.voicesounds[data.arg] = soundtype(wavfilename, self.device)
00249                 finally:
00250                     txtfile.close()
00251             else:
00252                 rospy.logdebug('command for cached text: "%s"'%data.arg)
00253             sound = self.voicesounds[data.arg]
00254         else:
00255             rospy.logdebug('command for builtin wave: %i'%data.sound)
00256             if not data.sound in self.builtinsounds:
00257                 params = self.builtinsoundparams[data.sound]
00258                 self.builtinsounds[data.sound] = soundtype(params[0], self.device, params[1])
00259             sound = self.builtinsounds[data.sound]
00260         if sound.staleness != 0 and data.command != SoundRequest.PLAY_STOP:
00261             # This sound isn't counted in active_sounds
00262             rospy.logdebug("activating %i %s"%(data.sound,data.arg))
00263             self.active_sounds = self.active_sounds + 1
00264             sound.staleness = 0
00265             #                    if self.active_sounds > self.num_channels:
00266             #                        mixer.set_num_channels(self.active_sounds)
00267             #                        self.num_channels = self.active_sounds
00268         return sound
00269 
00270     def callback(self,data):
00271         if not self.initialized:
00272             return
00273         self.mutex.acquire()
00274         # Force only one sound at a time
00275         self.stopall()
00276         try:
00277             if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP:
00278                 self.stopall()
00279             else:
00280                 sound = self.select_sound(data)
00281                 sound.command(data.command)
00282         except Exception, e:
00283             rospy.logerr('Exception in callback: %s'%str(e))
00284             rospy.loginfo(traceback.format_exc())
00285         finally:
00286             self.mutex.release()
00287             rospy.logdebug("done callback")
00288 
00289     # Purge sounds that haven't been played in a while.
00290     def cleanupdict(self, dict):
00291         purgelist = []
00292         for (key,sound) in dict.iteritems():
00293             try:
00294                 staleness = sound.get_staleness()
00295             except Exception, e:
00296                 rospy.logerr('Exception in cleanupdict for sound (%s): %s'%(str(key),str(e)))
00297                 staleness = 100 # Something is wrong. Let's purge and try again.
00298             #print "%s %i"%(key, staleness)
00299             if staleness >= 10:
00300                 purgelist.append(key)
00301             if staleness == 0: # Sound is playing
00302                 self.active_sounds = self.active_sounds + 1
00303         for key in purgelist:
00304            rospy.logdebug('Purging %s from cache'%key)
00305            dict[key].stop() # clean up resources
00306            del dict[key]
00307 
00308     def cleanup(self):
00309         self.mutex.acquire()
00310         try:
00311             self.active_sounds = 0
00312             self.cleanupdict(self.filesounds)
00313             self.cleanupdict(self.voicesounds)
00314             self.cleanupdict(self.builtinsounds)
00315         except:
00316             rospy.loginfo('Exception in cleanup: %s'%sys.exc_info()[0])
00317         finally:
00318             self.mutex.release()
00319 
00320     def diagnostics(self, state):
00321         try:
00322             da = DiagnosticArray()
00323             ds = DiagnosticStatus()
00324             ds.name = rospy.get_caller_id().lstrip('/') + ": Node State"
00325             if state == 0:
00326                 ds.level = DiagnosticStatus.OK
00327                 ds.message = "%i sounds playing"%self.active_sounds
00328                 ds.values.append(KeyValue("Active sounds", str(self.active_sounds)))
00329                 ds.values.append(KeyValue("Allocated sound channels", str(self.num_channels)))
00330                 ds.values.append(KeyValue("Buffered builtin sounds", str(len(self.builtinsounds))))
00331                 ds.values.append(KeyValue("Buffered wave sounds", str(len(self.filesounds))))
00332                 ds.values.append(KeyValue("Buffered voice sounds", str(len(self.voicesounds))))
00333             elif state == 1:
00334                 ds.level = DiagnosticStatus.WARN
00335                 ds.message = "Sound device not open yet."
00336             else:
00337                 ds.level = DiagnosticStatus.ERROR
00338                 ds.message = "Can't open sound device. See http://wiki.ros.org/sound_play/Troubleshooting"
00339             da.status.append(ds)
00340             da.header.stamp = rospy.get_rostime()
00341             self.diagnostic_pub.publish(da)
00342         except Exception, e:
00343             rospy.loginfo('Exception in diagnostics: %s'%str(e))
00344 
00345     def execute_cb(self, data):
00346         data = data.sound_request
00347         if not self.initialized:
00348             return
00349         self.mutex.acquire()
00350         # Force only one sound at a time
00351         self.stopall()
00352         try:
00353             if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP:
00354                 self.stopall()
00355             else:
00356                 sound = self.select_sound(data)
00357                 sound.command(data.command)
00358 
00359                 r = rospy.Rate(1)
00360                 start_time = rospy.get_rostime()
00361                 success = True
00362                 while sound.get_playing():
00363                     sound.update()
00364                     if self._as.is_preempt_requested():
00365                         rospy.loginfo('sound_play action: Preempted')
00366                         sound.stop()
00367                         self._as.set_preempted()
00368                         success = False
00369                         break
00370 
00371                     self._feedback.playing = sound.get_playing()
00372                     self._feedback.stamp = rospy.get_rostime() - start_time
00373                     self._as.publish_feedback(self._feedback)
00374                     r.sleep()
00375 
00376                 if success:
00377                     self._result.playing = self._feedback.playing
00378                     self._result.stamp = self._feedback.stamp
00379                     rospy.loginfo('sound_play action: Succeeded')
00380                     self._as.set_succeeded(self._result)
00381 
00382         except Exception, e:
00383             rospy.logerr('Exception in actionlib callback: %s'%str(e))
00384             rospy.loginfo(traceback.format_exc())
00385         finally:
00386             self.mutex.release()
00387             rospy.logdebug("done actionlib callback")
00388 
00389     def __init__(self):
00390         rospy.init_node('sound_play')
00391         self.device = rospy.get_param("~device", str())
00392         self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray, queue_size=1)
00393         rootdir = os.path.join(roslib.packages.get_pkg_dir('sound_play'),'sounds')
00394 
00395         self.builtinsoundparams = {
00396                 SoundRequest.BACKINGUP              : (os.path.join(rootdir, 'BACKINGUP.ogg'), 0.1),
00397                 SoundRequest.NEEDS_UNPLUGGING       : (os.path.join(rootdir, 'NEEDS_UNPLUGGING.ogg'), 1),
00398                 SoundRequest.NEEDS_PLUGGING         : (os.path.join(rootdir, 'NEEDS_PLUGGING.ogg'), 1),
00399                 SoundRequest.NEEDS_UNPLUGGING_BADLY : (os.path.join(rootdir, 'NEEDS_UNPLUGGING_BADLY.ogg'), 1),
00400                 SoundRequest.NEEDS_PLUGGING_BADLY   : (os.path.join(rootdir, 'NEEDS_PLUGGING_BADLY.ogg'), 1),
00401                 }
00402 
00403         self.no_error = True
00404         self.initialized = False
00405         self.active_sounds = 0
00406 
00407         self.mutex = threading.Lock()
00408         sub = rospy.Subscriber("robotsound", SoundRequest, self.callback)
00409         self._as = actionlib.SimpleActionServer('sound_play', SoundRequestAction, execute_cb=self.execute_cb, auto_start = False)
00410         self._as.start()
00411 
00412         self.mutex.acquire()
00413         self.sleep(0.5) # For ros startup race condition
00414         self.diagnostics(1)
00415 
00416         while not rospy.is_shutdown():
00417             while not rospy.is_shutdown():
00418                 self.init_vars()
00419                 self.no_error = True
00420                 self.initialized = True
00421                 self.mutex.release()
00422                 try:
00423                     self.idle_loop()
00424                     # Returns after inactive period to test device availability
00425                     #print "Exiting idle"
00426                 except:
00427                     rospy.loginfo('Exception in idle_loop: %s'%sys.exc_info()[0])
00428                 finally:
00429                     self.mutex.acquire()
00430 
00431             self.diagnostics(2)
00432         self.mutex.release()
00433 
00434     def init_vars(self):
00435         self.num_channels = 10
00436         self.builtinsounds = {}
00437         self.filesounds = {}
00438         self.voicesounds = {}
00439         self.hotlist = []
00440         if not self.initialized:
00441             rospy.loginfo('sound_play node is ready to play sound')
00442 
00443     def sleep(self, duration):
00444         try:
00445             rospy.sleep(duration)
00446         except rospy.exceptions.ROSInterruptException:
00447             pass
00448 
00449     def idle_loop(self):
00450         self.last_activity_time = rospy.get_time()
00451         while (rospy.get_time() - self.last_activity_time < 10 or
00452                  len(self.builtinsounds) + len(self.voicesounds) + len(self.filesounds) > 0) \
00453                 and not rospy.is_shutdown():
00454             #print "idle_loop"
00455             self.diagnostics(0)
00456             self.sleep(1)
00457             self.cleanup()
00458         #print "idle_exiting"
00459 
00460 if __name__ == '__main__':
00461     soundplay()


pr2eus
Author(s): Kei Okada
autogenerated on Thu Jun 6 2019 21:51:47