-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ed1ea30
Showing
8 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
cmake_minimum_required(VERSION 2.8.3) | ||
project(audio_file_player) | ||
|
||
find_package(catkin REQUIRED COMPONENTS | ||
rospy | ||
actionlib_msgs | ||
) | ||
|
||
################################################ | ||
## Declare ROS messages, services and actions ## | ||
################################################ | ||
|
||
|
||
# Generate actions in the 'action' folder | ||
add_action_files( | ||
FILES | ||
AudioFilePlay.action | ||
) | ||
|
||
# Generate added messages and services with any dependencies listed here | ||
generate_messages( | ||
DEPENDENCIES | ||
actionlib_msgs | ||
) | ||
|
||
|
||
################################### | ||
## catkin specific configuration ## | ||
################################### | ||
catkin_package( | ||
CATKIN_DEPENDS actionlib_msgs | ||
) | ||
|
||
########### | ||
## Build ## | ||
########### | ||
|
||
## Specify additional locations of header files | ||
## Your package locations should be listed before other locations | ||
# include_directories(include) | ||
include_directories( | ||
${catkin_INCLUDE_DIRS} | ||
) | ||
|
||
############# | ||
## Install ## | ||
############# | ||
|
||
# Mark executable scripts (Python etc.) for installation | ||
# in contrast to setup.py, you can choose the destination | ||
install(PROGRAMS | ||
scripts/play_file_server.py | ||
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} | ||
) | ||
|
||
# Mark other files for installation (e.g. launch and bag files, etc.) | ||
foreach (dir launch assets) | ||
install(DIRECTORY ${dir}/ | ||
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/${dir}) | ||
endforeach(dir) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# audio_file_player | ||
audio_file_player offers a simple way to play audio files through | ||
an action server interface and a topic interface. | ||
|
||
By default it uses sox's `play` to play the audio files (as it plays more file types than for example `aplay`). | ||
You may need to install: | ||
```` | ||
sudo apt-get install sox libsox-fmt-all | ||
```` | ||
|
||
# Example usage | ||
|
||
You can find an example mp3 file in this package in the folder assets, called `tada.mp3`. | ||
|
||
Launch the node: | ||
|
||
roslaunch audio_file_player audio_file_player.launch | ||
|
||
Try the topic interface: | ||
|
||
rostopic pub /audio_file_player/play std_msgs/String `rospack find audio_file_player`/assets/tada.mp3 | ||
|
||
Try the actionlib interface: | ||
|
||
rosrun actionlib axclient.py /audio_file_player | ||
|
||
# Use your own playing script | ||
If you want to use your own command for playing the files (or even use this node for some other | ||
purpose) just modify the launch file to use the command and flags that you want: | ||
|
||
```` | ||
<launch> | ||
<node pkg="audio_file_player" name="audio_file_player" type="play_file_server.py" output="screen"> | ||
<!-- Parameters to change the player to use other commands, flags and the rate (Hz) at which | ||
the feedback is published, this is equivalent to the default configuration --> | ||
<param name="command" value="play"/> | ||
<param name="flags" value=""/> | ||
<param name="feedback_rate" value="10"/> | ||
</node> | ||
</launch> | ||
```` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Path to the audio file | ||
string filepath | ||
--- | ||
# Result information | ||
# If it was successful | ||
bool success | ||
# If it wasn't reason why it wasn't | ||
string reason | ||
# Total time the file was playing | ||
time total_time | ||
--- | ||
# Feedback about the amount of time the audio has been played | ||
time elapsed_played_time |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<launch> | ||
<node pkg="audio_file_player" name="audio_file_player" type="play_file_server.py" output="screen"> | ||
<!-- Parameters to change the player to use other commands, flags and the rate (Hz) at which | ||
the feedback is published, this is equivalent to the default configuration --> | ||
<param name="command" value="play"/> | ||
<param name="flags" value=""/> | ||
<param name="feedback_rate" value="10"/> | ||
</node> | ||
</launch> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<launch> | ||
<node pkg="audio_file_player" name="audio_file_player" type="play_file_server.py" output="screen"> | ||
<!-- Parameters to change the player to use other commands, flags and the rate (Hz) at which | ||
the feedback is published, this is equivalent to the default configuration --> | ||
<param name="command" value="aplay"/> | ||
<param name="flags" value=""/> | ||
<param name="feedback_rate" value="10"/> | ||
</node> | ||
</launch> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<?xml version="1.0"?> | ||
<package> | ||
<name>audio_file_player</name> | ||
<version>0.0.1</version> | ||
<description>The audio_file_player package contains a node to play audio files | ||
through an action server or a topic interface.</description> | ||
|
||
<author email="Sammy.Pfeiffer@student.uts.edu.au">Sammy Pfeiffer</author> | ||
<maintainer email="Sammy.Pfeiffer@student.uts.edu.au">Sammy Pfeiffer</maintainer> | ||
|
||
<license>BSD</license> | ||
|
||
<buildtool_depend>catkin</buildtool_depend> | ||
<build_depend>rospy</build_depend> | ||
<build_depend>actionlib_msgs</build_depend> | ||
<run_depend>rospy</run_depend> | ||
<run_depend>actionlib_msgs</run_depend> | ||
|
||
</package> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
#!/usr/bin/env python | ||
|
||
import subprocess | ||
import tempfile | ||
import os | ||
import signal | ||
import time | ||
import rospy | ||
from actionlib import SimpleActionServer | ||
from audio_file_player.msg import AudioFilePlayAction, AudioFilePlayGoal, AudioFilePlayResult, AudioFilePlayFeedback | ||
from std_msgs.msg import String | ||
|
||
|
||
class ShellCmd: | ||
"""Helpful class to spawn commands and keep track of them""" | ||
|
||
def __init__(self, cmd): | ||
self.retcode = None | ||
self.outf = tempfile.NamedTemporaryFile(mode="w") | ||
self.errf = tempfile.NamedTemporaryFile(mode="w") | ||
self.inf = tempfile.NamedTemporaryFile(mode="r") | ||
self.process = subprocess.Popen(cmd, shell=True, stdin=self.inf, | ||
stdout=self.outf, stderr=self.errf, | ||
preexec_fn=os.setsid, close_fds=True) | ||
|
||
def __del__(self): | ||
if not self.is_done(): | ||
self.kill() | ||
self.outf.close() | ||
self.errf.close() | ||
self.inf.close() | ||
|
||
def get_stdout(self): | ||
with open(self.outf.name, "r") as f: | ||
return f.read() | ||
|
||
def get_stderr(self): | ||
with open(self.errf.name, "r") as f: | ||
return f.read() | ||
|
||
def get_retcode(self): | ||
"""Get retcode or None if still running""" | ||
if self.retcode is None: | ||
self.retcode = self.process.poll() | ||
return self.retcode | ||
|
||
def is_done(self): | ||
return self.get_retcode() is not None | ||
|
||
def is_succeeded(self): | ||
"""Check if the process ended with success state (retcode 0) | ||
If the process hasn't finished yet this will be False.""" | ||
return self.get_retcode() == 0 | ||
|
||
def kill(self): | ||
self.retcode = -1 | ||
os.killpg(self.process.pid, signal.SIGTERM) | ||
self.process.wait() | ||
|
||
|
||
class AudioFilePlayer(object): | ||
def __init__(self): | ||
rospy.loginfo("Initializing AudioFilePlayer...") | ||
self.current_playing_process = None | ||
self.afp_as = SimpleActionServer(rospy.get_name(), AudioFilePlayAction, | ||
self.as_cb, auto_start=False) | ||
self.afp_sub = rospy.Subscriber('~play', String, self.topic_cb, | ||
queue_size=1) | ||
# By default this node plays files using the aplay command | ||
# Feel free to use any other command or flags | ||
# by using the params provided | ||
self.command = rospy.get_param('~/command', 'play') | ||
self.flags = rospy.get_param('~/flags', '') | ||
self.feedback_rate = rospy.get_param('~/feedback_rate', 10) | ||
self.afp_as.start() | ||
# Needs to be done after start | ||
self.afp_as.register_preempt_callback(self.as_preempt_cb) | ||
|
||
rospy.loginfo( | ||
"Done, playing files from action server or topic interface.") | ||
|
||
def as_preempt_cb(self): | ||
if self.current_playing_process: | ||
self.current_playing_process.kill() | ||
# Put the AS as cancelled/preempted | ||
res = AudioFilePlayResult() | ||
res.success = False | ||
res.reason = "Got a cancel request." | ||
self.afp_as.set_preempted(res, text="Cancel requested.") | ||
|
||
def as_cb(self, goal): | ||
initial_time = time.time() | ||
self.play_audio_file(goal.filepath) | ||
r = rospy.Rate(self.feedback_rate) | ||
while not rospy.is_shutdown() and not self.current_playing_process.is_done(): | ||
feedback = AudioFilePlayFeedback() | ||
curr_time = time.time() | ||
feedback.elapsed_played_time = rospy.Duration( | ||
curr_time - initial_time) | ||
self.afp_as.publish_feedback(feedback) | ||
r.sleep() | ||
|
||
final_time = time.time() | ||
res = AudioFilePlayResult() | ||
if self.current_playing_process.is_succeeded(): | ||
res.success = True | ||
res.total_time = rospy.Duration(final_time) | ||
self.afp_as.set_succeeded(res) | ||
else: | ||
if self.afp_as.is_preempt_requested(): | ||
return | ||
res.success = False | ||
reason = "stderr: " + self.current_playing_process.get_stderr() | ||
reason += "\nstdout: " + self.current_playing_process.get_stdout() | ||
res.reason = reason | ||
self.afp_as.set_aborted(res) | ||
|
||
def topic_cb(self, data): | ||
if self.current_playing_process: | ||
if not self.current_playing_process.is_done(): | ||
self.current_playing_process.kill() | ||
self.play_audio_file(data.data) | ||
|
||
def play_audio_file(self, audio_file_path): | ||
# Replace any ' or " characters with emptyness to avoid bad usage | ||
audio_file_path = audio_file_path.replace("'", "") | ||
audio_file_path = audio_file_path.replace('"', '') | ||
full_command = self.command + " " + self.flags + " '" + audio_file_path + "'" | ||
rospy.loginfo("Playing audio file: " + str(audio_file_path) + | ||
" with command: " + str(full_command)) | ||
self.current_playing_process = ShellCmd(full_command) | ||
|
||
|
||
if __name__ == '__main__': | ||
rospy.init_node('audio_file_player') | ||
afp = AudioFilePlayer() | ||
rospy.spin() |