def _start_playing(self, gh, result): # Now open a file pointer to the wav files so that we can be sure the # file exists while play has access to it try: fd = open(gh.get_goal().sound_request.arg, 'rb') except Exception as e: rospy.logerr("Exception while opening wav file: {}".format(e)) result.stamp = rospy.Time.now() gh.set_aborted(result) # We now have access to the file pointer. Now send it on to play (or # whatever command we have chosen to play sounds) play_command = ([SoundServer.DEFAULT_SOUNDPLAY_COMMAND] + SoundServer.DEFAULT_SOUNDPLAY_ARGS + [gh.get_goal().sound_request.arg]) play_process = psutil.Popen(play_command) self._current_sounds.put(( gh, play_process, rospy.Time.now(), )) gh.publish_feedback( SoundRequestFeedback(playing=True, stamp=rospy.Time.now())) # Now wait until we receive a signal that the process has terminated while play_process.poll() is None: rospy.sleep(0.1) # Then check the status we were in. Close the file descriptor first fd.close() if gh.get_goal_status().status == GoalStatus.PREEMPTING: rospy.logwarn("Goal to play {} preempted.".format( gh.get_goal().sound_request.arg)) result.stamp = rospy.Time.now() gh.set_canceled(result) elif play_process.poll() != 0: rospy.logerr("Goal to play {} aborted.".format( gh.get_goal().sound_request.arg)) result.stamp = rospy.Time.now() gh.set_aborted(result) else: # status == SUCCEEDED and poll() == 0 rospy.logdebug("Goal to play {} succeeded.".format( gh.get_goal().sound_request.arg)) result.stamp = rospy.Time.now() gh.set_succeeded(result)
class soundplay: _feedback = SoundRequestFeedback() _result = SoundRequestResult() def stopdict(self, dict): for sound in dict.values(): sound.stop() def stopall(self): self.stopdict(self.builtinsounds) self.stopdict(self.filesounds) self.stopdict(self.voicesounds) def select_sound(self, data): if data.sound == SoundRequest.PLAY_FILE: if not data.arg2: if not data.arg in self.filesounds.keys() or self.filesounds[ data.arg].volume != data.volume: rospy.logdebug('command for uncached wave: "%s"' % data.arg) try: self.filesounds[data.arg] = soundtype( data.arg, data.volume) except: rospy.logerr( 'Error setting up to play "%s". Does this file exist on the machine on which sound_play is running?' % data.arg) return else: rospy.logdebug('command for cached wave: "%s"' % data.arg) sound = self.filesounds[data.arg] else: absfilename = os.path.join( roslib.packages.get_pkg_dir(data.arg2), data.arg) if not absfilename in self.filesounds.keys( ) or self.filesounds[absfilename].volume != data.volume: rospy.logdebug('command for uncached wave: "%s"' % absfilename) try: self.filesounds[absfilename] = soundtype( absfilename, data.volume) except: 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)) return else: rospy.logdebug('command for cached wave: "%s"' % absfilename) sound = self.filesounds[absfilename] elif data.sound == SoundRequest.SAY: print data if not data.arg in self.voicesounds.keys() or self.voicesounds[ data.arg].volume != data.volume: rospy.logdebug('command for uncached text: "%s"' % data.arg) txtfile = tempfile.NamedTemporaryFile(prefix='sound_play', suffix='.txt') (wavfile, wavfilename) = tempfile.mkstemp(prefix='sound_play', suffix='.wav') txtfilename = txtfile.name os.close(wavfile) voice = data.arg2 try: txtfile.write(data.arg) txtfile.flush() os.system("text2wave -eval '(" + voice + ")' " + txtfilename + " -o " + wavfilename) try: if os.stat(wavfilename).st_size == 0: raise OSError # So we hit the same catch block except OSError: 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' ) return self.voicesounds[data.arg] = soundtype( wavfilename, data.volume) finally: txtfile.close() else: rospy.logdebug('command for cached text: "%s"' % data.arg) sound = self.voicesounds[data.arg] else: rospy.logdebug('command for builtin wave: %i' % data.sound) if data.sound not in self.builtinsounds or ( data.sound in self.builtinsounds and data.volume != self.builtinsounds[data.sound].volume): params = self.builtinsoundparams[data.sound] volume = data.volume if params[ 1] != 1: # use the second param as a scaling for the input volume volume = (volume + params[1]) / 2 self.builtinsounds[data.sound] = soundtype(params[0], volume) sound = self.builtinsounds[data.sound] if sound.staleness != 0 and data.command != SoundRequest.PLAY_STOP: # This sound isn't counted in active_sounds rospy.logdebug("activating %i %s" % (data.sound, data.arg)) self.active_sounds = self.active_sounds + 1 sound.staleness = 0 # if self.active_sounds > self.num_channels: # mixer.set_num_channels(self.active_sounds) # self.num_channels = self.active_sounds return sound def callback(self, data): if not self.initialized: return self.mutex.acquire() # Force only one sound at a time self.stopall() try: if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP: self.stopall() else: sound = self.select_sound(data) sound.command(data.command) #pub_sound = rospy.Publisher("sout", String, queue_size=2) pub_sound.publish("o") rospy.loginfo("sound Check") except Exception, e: rospy.logerr('Exception in callback: %s' % str(e)) rospy.loginfo(traceback.format_exc()) finally:
class soundplay: _feedback = SoundRequestFeedback() _result = SoundRequestResult() def stopdict(self, dict): for sound in dict.values(): sound.stop() def stopall(self): self.stopdict(self.builtinsounds) self.stopdict(self.filesounds) self.stopdict(self.voicesounds) def select_sound(self, data): if data.sound == SoundRequest.PLAY_FILE: if not data.arg2: if not data.arg in self.filesounds.keys(): rospy.logdebug('command for uncached wave: "%s"' % data.arg) try: self.filesounds[data.arg] = soundtype( data.arg, self.device, data.volume) except: rospy.logerr( 'Error setting up to play "%s". Does this file exist on the machine on which sound_play is running?' % data.arg) return else: rospy.logdebug('command for cached wave: "%s"' % data.arg) if self.filesounds[data.arg].sound.get_property( 'volume') != data.volume: rospy.logdebug( 'volume for cached wave has changed, resetting volume' ) self.filesounds[data.arg].sound.set_property( 'volume', data.volume) sound = self.filesounds[data.arg] else: absfilename = os.path.join( roslib.packages.get_pkg_dir(data.arg2), data.arg) if not absfilename in self.filesounds.keys(): rospy.logdebug('command for uncached wave: "%s"' % absfilename) try: self.filesounds[absfilename] = soundtype( absfilename, self.device, data.volume) except: 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)) return else: rospy.logdebug('command for cached wave: "%s"' % absfilename) if self.filesounds[absfilename].sound.get_property( 'volume') != data.volume: rospy.logdebug( 'volume for cached wave has changed, resetting volume' ) self.filesounds[absfilename].sound.set_property( 'volume', data.volume) sound = self.filesounds[absfilename] elif data.sound == SoundRequest.SAY: # print data if not data.arg in self.voicesounds.keys(): rospy.logdebug('command for uncached text: "%s"' % data.arg) txtfile = tempfile.NamedTemporaryFile(prefix='sound_play', suffix='.txt') (wavfile, wavfilename) = tempfile.mkstemp(prefix='sound_play', suffix='.wav') txtfilename = txtfile.name os.close(wavfile) voice = data.arg2 try: try: if hasattr(data.arg, 'decode'): txtfile.write( data.arg.decode('UTF-8').encode('ISO-8859-15')) else: txtfile.write(data.arg.encode('ISO-8859-15')) except UnicodeEncodeError: if hasattr(data.arg, 'decode'): txtfile.write(data.arg) else: txtfile.write(data.arg.encode('UTF-8')) txtfile.flush() os.system("text2wave -eval '(" + voice + ")' " + txtfilename + " -o " + wavfilename) try: if os.stat(wavfilename).st_size == 0: raise OSError # So we hit the same catch block except OSError: 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' ) return self.voicesounds[data.arg] = soundtype( wavfilename, self.device, data.volume) finally: txtfile.close() else: rospy.logdebug('command for cached text: "%s"' % data.arg) if self.voicesounds[data.arg].sound.get_property( 'volume') != data.volume: rospy.logdebug( 'volume for cached text has changed, resetting volume') self.voicesounds[data.arg].sound.set_property( 'volume', data.volume) sound = self.voicesounds[data.arg] else: rospy.logdebug('command for builtin wave: %i' % data.sound) if data.sound not in self.builtinsounds or ( data.sound in self.builtinsounds and data.volume != self.builtinsounds[data.sound].volume): params = self.builtinsoundparams[data.sound] volume = data.volume if params[ 1] != 1: # use the second param as a scaling for the input volume volume = (volume + params[1]) / 2 self.builtinsounds[data.sound] = soundtype( params[0], self.device, volume) sound = self.builtinsounds[data.sound] if sound.staleness != 0 and data.command != SoundRequest.PLAY_STOP: # This sound isn't counted in active_sounds rospy.logdebug("activating %i %s" % (data.sound, data.arg)) self.active_sounds = self.active_sounds + 1 sound.staleness = 0 # if self.active_sounds > self.num_channels: # mixer.set_num_channels(self.active_sounds) # self.num_channels = self.active_sounds return sound def callback(self, data): if not self.initialized: return self.mutex.acquire() try: if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP: self.stopall() else: sound = self.select_sound(data) sound.command(data.command) except Exception as e: rospy.logerr('Exception in callback: %s' % str(e)) rospy.loginfo(traceback.format_exc()) finally: self.mutex.release() rospy.logdebug("done callback") # Purge sounds that haven't been played in a while. def cleanupdict(self, dict): purgelist = [] for (key, sound) in iter(dict.items()): try: staleness = sound.get_staleness() except Exception as e: rospy.logerr('Exception in cleanupdict for sound (%s): %s' % (str(key), str(e))) staleness = 100 # Something is wrong. Let's purge and try again. #print "%s %i"%(key, staleness) if staleness >= 10: purgelist.append(key) if staleness == 0: # Sound is playing self.active_sounds = self.active_sounds + 1 for key in purgelist: rospy.logdebug('Purging %s from cache' % key) dict[key].dispose() # clean up resources del dict[key] def cleanup(self): self.mutex.acquire() try: self.active_sounds = 0 self.cleanupdict(self.filesounds) self.cleanupdict(self.voicesounds) self.cleanupdict(self.builtinsounds) except: rospy.loginfo('Exception in cleanup: %s' % sys.exc_info()[0]) finally: self.mutex.release() def diagnostics(self, state): try: da = DiagnosticArray() ds = DiagnosticStatus() ds.name = rospy.get_caller_id().lstrip('/') + ": Node State" if state == 0: ds.level = DiagnosticStatus.OK ds.message = "%i sounds playing" % self.active_sounds ds.values.append( KeyValue("Active sounds", str(self.active_sounds))) ds.values.append( KeyValue("Allocated sound channels", str(self.num_channels))) ds.values.append( KeyValue("Buffered builtin sounds", str(len(self.builtinsounds)))) ds.values.append( KeyValue("Buffered wave sounds", str(len(self.filesounds)))) ds.values.append( KeyValue("Buffered voice sounds", str(len(self.voicesounds)))) elif state == 1: ds.level = DiagnosticStatus.WARN ds.message = "Sound device not open yet." else: ds.level = DiagnosticStatus.ERROR ds.message = "Can't open sound device. See http://wiki.ros.org/sound_play/Troubleshooting" da.status.append(ds) da.header.stamp = rospy.get_rostime() self.diagnostic_pub.publish(da) except Exception as e: rospy.loginfo('Exception in diagnostics: %s' % str(e)) def execute_cb(self, data): data = data.sound_request if not self.initialized: return self.mutex.acquire() # Force only one sound at a time self.stopall() try: if data.sound == SoundRequest.ALL and data.command == SoundRequest.PLAY_STOP: self.stopall() else: sound = self.select_sound(data) sound.command(data.command) r = rospy.Rate(1) start_time = rospy.get_rostime() success = True while sound.get_playing(): sound.update() if self._as.is_preempt_requested(): rospy.loginfo('sound_play action: Preempted') sound.stop() self._as.set_preempted() success = False break self._feedback.playing = sound.get_playing() self._feedback.stamp = rospy.get_rostime() - start_time self._as.publish_feedback(self._feedback) r.sleep() if success: self._result.playing = self._feedback.playing self._result.stamp = self._feedback.stamp rospy.loginfo('sound_play action: Succeeded') self._as.set_succeeded(self._result) except Exception as e: rospy.logerr('Exception in actionlib callback: %s' % str(e)) rospy.loginfo(traceback.format_exc()) finally: self.mutex.release() rospy.logdebug("done actionlib callback") def __init__(self): Gst.init(None) # Start gobject thread to receive gstreamer messages GObject.threads_init() self.g_loop = threading.Thread(target=GObject.MainLoop().run) self.g_loop.daemon = True self.g_loop.start() rospy.init_node('sound_play') self.device = rospy.get_param("~device", "default") self.diagnostic_pub = rospy.Publisher("/diagnostics", DiagnosticArray, queue_size=1) rootdir = os.path.join(roslib.packages.get_pkg_dir('sound_play'), 'sounds') self.builtinsoundparams = { SoundRequest.BACKINGUP: (os.path.join(rootdir, 'BACKINGUP.ogg'), 0.1), SoundRequest.NEEDS_UNPLUGGING: (os.path.join(rootdir, 'NEEDS_UNPLUGGING.ogg'), 1), SoundRequest.NEEDS_PLUGGING: (os.path.join(rootdir, 'NEEDS_PLUGGING.ogg'), 1), SoundRequest.NEEDS_UNPLUGGING_BADLY: (os.path.join(rootdir, 'NEEDS_UNPLUGGING_BADLY.ogg'), 1), SoundRequest.NEEDS_PLUGGING_BADLY: (os.path.join(rootdir, 'NEEDS_PLUGGING_BADLY.ogg'), 1), } self.no_error = True self.initialized = False self.active_sounds = 0 self.mutex = threading.Lock() sub = rospy.Subscriber("robotsound", SoundRequest, self.callback) self._as = actionlib.SimpleActionServer('sound_play', SoundRequestAction, execute_cb=self.execute_cb, auto_start=False) self._as.start() self.mutex.acquire() self.sleep(0.5) # For ros startup race condition self.diagnostics(1) while not rospy.is_shutdown(): while not rospy.is_shutdown(): self.init_vars() self.no_error = True self.initialized = True self.mutex.release() try: self.idle_loop() # Returns after inactive period to test device availability #print "Exiting idle" except: rospy.loginfo('Exception in idle_loop: %s' % sys.exc_info()[0]) finally: self.mutex.acquire() self.diagnostics(2) self.mutex.release() def init_vars(self): self.num_channels = 10 self.builtinsounds = {} self.filesounds = {} self.voicesounds = {} self.hotlist = [] if not self.initialized: rospy.loginfo('sound_play node is ready to play sound') def sleep(self, duration): try: rospy.sleep(duration) except rospy.exceptions.ROSInterruptException: pass def idle_loop(self): self.last_activity_time = rospy.get_time() while (rospy.get_time() - self.last_activity_time < 10 or len(self.builtinsounds) + len(self.voicesounds) + len(self.filesounds) > 0) \ and not rospy.is_shutdown(): #print "idle_loop" self.diagnostics(0) self.sleep(1) self.cleanup()