Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
awesomebytes committed Oct 27, 2016
0 parents commit ed1ea30
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 0 deletions.
60 changes: 60 additions & 0 deletions CMakeLists.txt
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)
41 changes: 41 additions & 0 deletions README.md
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>
````
13 changes: 13 additions & 0 deletions action/AudioFilePlay.action
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 added assets/tada.mp3
Binary file not shown.
9 changes: 9 additions & 0 deletions launch/audio_file_player.launch
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>
9 changes: 9 additions & 0 deletions launch/audio_file_player_aplay.launch
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>
19 changes: 19 additions & 0 deletions package.xml
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>
137 changes: 137 additions & 0 deletions scripts/play_file_server.py
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()

0 comments on commit ed1ea30

Please sign in to comment.