diff --git a/livesupport/modules/playlistExecutor/etc/Makefile.in b/livesupport/modules/playlistExecutor/etc/Makefile.in index e65823625..a74dd1e73 100644 --- a/livesupport/modules/playlistExecutor/etc/Makefile.in +++ b/livesupport/modules/playlistExecutor/etc/Makefile.in @@ -21,7 +21,7 @@ # # # Author : $Author: maroy $ -# Version : $Revision: 1.8 $ +# Version : $Revision: 1.9 $ # Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/etc/Makefile.in,v $ # # @configure_input@ @@ -77,6 +77,9 @@ VPATH = ${SRC_DIR} LIBXMLPP_CFLAGS=@LIBXMLPP_CFLAGS@ LIBXMLPP_LIBS=@LIBXMLPP_LIBS@ +GSTREAMER_CFLAGS=@GSTREAMER_CFLAGS@ +GSTREAMER_LIBS=@GSTREAMER_LIBS@ + TAGLIB_LIBS =`${USR_DIR}/bin/taglib-config --libs` TEST_RESULTS = ${DOC_DIR}/testResults.xml @@ -100,6 +103,7 @@ CPPFLAGS = @CPPFLAGS@ CXXFLAGS = @CXXFLAGS@ @DEFS@ @COVERAGE_CXXFLAGS@ -pthread \ -Wall -Wno-long-long \ ${LIBXMLPP_CFLAGS} \ + ${GSTREAMER_CFLAGS} \ ${HELIX_CFLAGS} \ -I${USR_INCLUDE_DIR} \ -I${BOOST_INCLUDE_DIR} \ @@ -108,6 +112,7 @@ CXXFLAGS = @CXXFLAGS@ @DEFS@ @COVERAGE_CXXFLAGS@ -pthread \ -I${INCLUDE_DIR} -I${TMP_DIR} LDFLAGS = @LDFLAGS@ -pthread \ ${LIBXMLPP_LIBS} \ + ${GSTREAMER_LIBS} \ ${TAGLIB_LIBS} \ -L${USR_LIB_DIR} \ -L${CORE_LIB_DIR} \ @@ -125,8 +130,11 @@ PLAYLIST_EXECUTOR_LIB_OBJS = ${TMP_DIR}/HelixPlayer.o \ ${TMP_DIR}/ClientContext.o \ ${TMP_DIR}/ErrorSink.o \ ${TMP_DIR}/HelixIIDs.o \ + ${TMP_DIR}/GstreamerPlayer.o \ ${TMP_DIR}/AudioPlayerFactory.o TEST_RUNNER_OBJS = ${TMP_DIR}/TestRunner.o \ + ${TMP_DIR}/GstreamerPlayerTest.o +DONT_TEST= ${TMP_DIR}/GstreamerPlayerTest.o \ ${TMP_DIR}/HelixPlayerTest.o \ ${TMP_DIR}/AudioPlayerFactoryTest.o TEST_RUNNER_LIBS = -l${PLAYLIST_EXECUTOR_LIB} -l${CORE_LIB} \ @@ -134,6 +142,7 @@ TEST_RUNNER_LIBS = -l${PLAYLIST_EXECUTOR_LIB} -l${CORE_LIB} \ -lcppunit -ldl -lm -lxmlrpc++ TWOTEST_RUNNER_OBJS = ${TMP_DIR}/TestRunner.o \ + ${TMP_DIR}/TwoGstreamerPlayersTest.o \ ${TMP_DIR}/TwoHelixPlayersTest.o diff --git a/livesupport/modules/playlistExecutor/etc/configure.ac b/livesupport/modules/playlistExecutor/etc/configure.ac index a5cff38ce..4a108b90e 100644 --- a/livesupport/modules/playlistExecutor/etc/configure.ac +++ b/livesupport/modules/playlistExecutor/etc/configure.ac @@ -21,7 +21,7 @@ dnl Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA dnl dnl dnl Author : $Author: maroy $ -dnl Version : $Revision: 1.3 $ +dnl Version : $Revision: 1.4 $ dnl Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/etc/configure.ac,v $ dnl----------------------------------------------------------------------------- @@ -35,7 +35,7 @@ dnl----------------------------------------------------------------------------- AC_INIT(PlaylistExecutor, 1.0, bugs@campware.org) AC_PREREQ(2.59) AC_COPYRIGHT([Copyright (c) 2004 Media Development Loan Fund under the GNU GPL]) -AC_REVISION($Revision: 1.3 $) +AC_REVISION($Revision: 1.4 $) AC_CONFIG_SRCDIR(../src/HelixPlayer.cxx) @@ -48,6 +48,10 @@ PKG_CHECK_MODULES(LIBXMLPP,[libxml++-2.6 >= 2.6.0]) AC_SUBST(LIBXMLPP_CFLAGS) AC_SUBST(LIBXMLPP_LIBS) +PKG_CHECK_MODULES(GSTREAMER,[gstreamer-0.8 >= 0.8]) +AC_SUBST(GSTREAMER_CFLAGS) +AC_SUBST(GSTREAMER_LIBS) + dnl----------------------------------------------------------------------------- dnl enable compilaton for code coverage data dnl----------------------------------------------------------------------------- diff --git a/livesupport/modules/playlistExecutor/etc/gstreamerPlayer.xml b/livesupport/modules/playlistExecutor/etc/gstreamerPlayer.xml new file mode 100644 index 000000000..a30020193 --- /dev/null +++ b/livesupport/modules/playlistExecutor/etc/gstreamerPlayer.xml @@ -0,0 +1,8 @@ + + + +]> + diff --git a/livesupport/modules/playlistExecutor/etc/twoGstreamerPlayers.xml b/livesupport/modules/playlistExecutor/etc/twoGstreamerPlayers.xml new file mode 100644 index 000000000..66545f473 --- /dev/null +++ b/livesupport/modules/playlistExecutor/etc/twoGstreamerPlayers.xml @@ -0,0 +1,12 @@ + + + + + +]> + + + + diff --git a/livesupport/modules/playlistExecutor/src/GstreamerPlayer.cxx b/livesupport/modules/playlistExecutor/src/GstreamerPlayer.cxx new file mode 100644 index 000000000..3a3fc77d1 --- /dev/null +++ b/livesupport/modules/playlistExecutor/src/GstreamerPlayer.cxx @@ -0,0 +1,668 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2004 Media Development Loan Fund + + This file is part of the LiveSupport project. + http://livesupport.campware.org/ + To report bugs, send an e-mail to bugs@campware.org + + LiveSupport is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + LiveSupport is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LiveSupport; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + Author : $Author: maroy $ + Version : $Revision: 1.1 $ + Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/src/GstreamerPlayer.cxx,v $ + +------------------------------------------------------------------------------*/ + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "configure.h" +#endif + +#include "LiveSupport/Core/TimeConversion.h" +#include "GstreamerPlayer.h" + + +using namespace boost::posix_time; +using namespace LiveSupport::Core; +using namespace LiveSupport::PlaylistExecutor; + +/* =================================================== local data structures */ + + +/* ================================================ local constants & macros */ + +/** + * The name of the config element for this class + */ +const std::string GstreamerPlayer::configElementNameStr = "gstreamerPlayer"; + +/** + * The name of the audio device attribute. + */ +static const std::string audioDeviceName = "audioDevice"; + +/** + * The factories considered when creating the pipeline + */ +GList * GstreamerPlayer::factories = 0; + + +/* =============================================== local function prototypes */ + + +/* ============================================================= module code */ + +/*------------------------------------------------------------------------------ + * Configure the Helix Player. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: configure(const xmlpp::Element & element) + throw (std::invalid_argument, + std::logic_error) +{ + if (element.get_name() != configElementNameStr) { + std::string eMsg = "Bad configuration element "; + eMsg += element.get_name(); + throw std::invalid_argument(eMsg); + } + + const xmlpp::Attribute * attribute; + + if ((attribute = element.get_attribute(audioDeviceName))) { + audioDevice = attribute->get_value(); + } +} + + +/*------------------------------------------------------------------------------ + * Initialize the Helix Player + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: initialize(void) throw (std::exception) +{ + if (initialized) { + return; + } + + // initialize the gstreamer library + if (!gst_init_check(0, 0)) { + throw std::runtime_error("couldn't initialize the gstreamer library"); + } + initFactories(); + + // initialize the pipeline + pipeline = gst_thread_new("audio-player"); + // take ownership of the pipeline object + gst_object_ref(GST_OBJECT(pipeline)); + gst_object_sink(GST_OBJECT(pipeline)); + + filesrc = gst_element_factory_make("filesrc", "file-source"); + typefinder = gst_element_factory_make("typefind", "typefind"); + gst_element_link(filesrc, typefinder); + gst_bin_add_many(GST_BIN(pipeline), filesrc, typefinder, NULL); + + g_signal_connect(pipeline, "error", G_CALLBACK(errorHandler), this); + g_signal_connect(pipeline, "state-change", G_CALLBACK(stateChange), this); + g_signal_connect(typefinder, "have-type", G_CALLBACK(typeFound), this); + + audiosink = gst_element_factory_make("alsasink", "audiosink"); + setAudioDevice(audioDevice); + + // set up other variables + initialized = true; +} + + +/*------------------------------------------------------------------------------ + * Initialize the list of factories + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: initFactories(void) throw () +{ + if (factories) { + return; + } + + factories = gst_registry_pool_feature_filter( + (GstPluginFeatureFilter) featureFilter, FALSE, NULL); + // sort the factories according to their ranks + factories = g_list_sort(factories, (GCompareFunc) compareRanks); +} + + +/*------------------------------------------------------------------------------ + * Filter plugins so that only demixers, decoders and parsers are considered + *----------------------------------------------------------------------------*/ +gboolean +GstreamerPlayer :: featureFilter(GstPluginFeature * feature, + gpointer data) + throw () +{ + const gchar * klass; + guint rank; + + // we only care about element factories + if (!GST_IS_ELEMENT_FACTORY(feature)) { + return FALSE; + } + + // only parsers, demuxers and decoders + klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature)); + if (g_strrstr(klass, "Demux") == NULL && + g_strrstr(klass, "Decoder") == NULL && + g_strrstr(klass, "Parse") == NULL) { + + return FALSE; + } + + // only select elements with autoplugging rank + rank = gst_plugin_feature_get_rank(feature); + if (rank < GST_RANK_MARGINAL) { + return FALSE; + } + + return TRUE; +} + + +/*------------------------------------------------------------------------------ + * Compare two plugins according to their ranks + *----------------------------------------------------------------------------*/ +gint +GstreamerPlayer :: compareRanks(GstPluginFeature * feature1, + GstPluginFeature * feature2) + throw () +{ + return gst_plugin_feature_get_rank(feature1) + - gst_plugin_feature_get_rank(feature2); +} + + +/*------------------------------------------------------------------------------ + * Handler for gstreamer errors. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: errorHandler(GstElement * pipeline, + GstElement * source, + GError * error, + gchar * debug, + gpointer self) + throw () +{ + // TODO: handle error + std::cerr << "gstreamer error: " << error->message << std::endl; +} + + +/*------------------------------------------------------------------------------ + * Handler for the event when a matching type has been found + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: typeFound(GstElement * typefinder, + guint probability, + GstCaps * caps, + gpointer self) + throw () +{ + // actually plug now + GstreamerPlayer * player = (GstreamerPlayer*) self; + try { + player->tryToPlug(gst_element_get_pad(typefinder, "src"), caps); + } catch (std::logic_error &e) { + // TODO: handle error + std::cerr << e.what() << std::endl; + } +} + + +/*------------------------------------------------------------------------------ + * Try to plug a matching element to the specified pad + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: tryToPlug(GstPad * pad, + const GstCaps * caps) + throw (std::logic_error) +{ + GstObject * parent = GST_OBJECT(gst_pad_get_parent(pad)); + const gchar * mime; + const GList * item; + GstCaps * res; + GstCaps * audiocaps; + + // don't plug if we're already plugged + if (GST_PAD_IS_LINKED(gst_element_get_pad(audiosink, "sink"))) { + throw std::logic_error(std::string("Omitting link for pad ") + + gst_object_get_name(parent) + ":" + + gst_pad_get_name(pad) + + " because we're alreadey linked"); + } + + // only plug audio + mime = gst_structure_get_name(gst_caps_get_structure(caps, 0)); + if (!g_strrstr (mime, "audio")) { + throw std::logic_error(std::string("Omitting link for pad ") + + gst_object_get_name(parent) + ":" + + gst_pad_get_name(pad) + + " because mimetype " + + mime + + " is non-audio"); + } + + // can it link to the audiopad? + audiocaps = gst_pad_get_caps(gst_element_get_pad(audiosink, "sink")); + res = gst_caps_intersect(caps, audiocaps); + if (res && !gst_caps_is_empty(res)) { + closeLink(pad, audiosink, "sink", NULL); + gst_caps_free(audiocaps); + gst_caps_free(res); + return; + } + gst_caps_free(audiocaps); + gst_caps_free(res); + + // try to plug from our list + for (item = factories; item != NULL; item = item->next) { + GstElementFactory * factory = GST_ELEMENT_FACTORY(item->data); + const GList * pads; + + for (pads = gst_element_factory_get_pad_templates (factory); + pads != NULL; + pads = pads->next) { + + GstPadTemplate * templ = GST_PAD_TEMPLATE(pads->data); + + // find the sink template - need an always pad + if (templ->direction != GST_PAD_SINK || + templ->presence != GST_PAD_ALWAYS) { + continue; + } + + // can it link? + res = gst_caps_intersect(caps, templ->caps); + if (res && !gst_caps_is_empty(res)) { + GstElement *element; + + // close link and return + gst_caps_free(res); + element = gst_element_factory_create(factory, NULL); + closeLink(pad, + element, + templ->name_template, + gst_element_factory_get_pad_templates(factory)); + + const gchar *klass = + gst_element_factory_get_klass(GST_ELEMENT_FACTORY(factory)); + if (g_strrstr(klass, "Decoder")) { + // if a decoder element, store it + decoder = element; + decoderSrc = gst_element_get_pad(decoder, "src"); + } + return; + } + gst_caps_free(res); + + // we only check one sink template per factory, so move on to the + // next factory now + break; + } + } + + throw std::logic_error(std::string("No compatible pad found to decode ") + + mime + + " on " + + gst_object_get_name (parent) + ":" + + gst_pad_get_name (pad)); +} + + +/*------------------------------------------------------------------------------ + * Close the element links + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: closeLink(GstPad * srcpad, + GstElement * sinkelement, + const gchar * padname, + const GList * templlist) + throw () +{ + gboolean has_dynamic_pads = FALSE; + + // add the element to the pipeline and set correct state + gst_element_set_state(sinkelement, GST_STATE_PAUSED); + gst_bin_add(GST_BIN(pipeline), sinkelement); + gst_pad_link(srcpad, gst_element_get_pad(sinkelement, padname)); + gst_bin_sync_children_state(GST_BIN(pipeline)); + + // if we have static source pads, link those. If we have dynamic + // source pads, listen for new-pad signals on the element + for ( ; templlist != NULL; templlist = templlist->next) { + GstPadTemplate *templ = GST_PAD_TEMPLATE(templlist->data); + + // only sourcepads, no request pads + if (templ->direction != GST_PAD_SRC || + templ->presence == GST_PAD_REQUEST) { + + continue; + } + + switch (templ->presence) { + case GST_PAD_ALWAYS: { + GstPad *pad = gst_element_get_pad(sinkelement, + templ->name_template); + GstCaps *caps = gst_pad_get_caps(pad); + + // link + tryToPlug(pad, caps); + gst_caps_free(caps); + break; + } + + case GST_PAD_SOMETIMES: + has_dynamic_pads = TRUE; + break; + + default: + break; + } + } + + // listen for newly created pads if this element supports that + if (has_dynamic_pads) { + g_signal_connect(sinkelement, "new-pad", G_CALLBACK(newPad), this); + } +} + + +/*------------------------------------------------------------------------------ + * Event handler for when a new dynamic pad is created + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: newPad(GstElement * element, + GstPad * pad, + gpointer self) + throw () +{ + GstreamerPlayer * player = (GstreamerPlayer*) self; + GstCaps * caps = gst_pad_get_caps(pad); + + player->tryToPlug(pad, caps); + gst_caps_free(caps); +} + + +/*------------------------------------------------------------------------------ + * Event handler for when the state of the pipeline changes + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: stateChange(GstElement * element, + gint oldState, + gint newState, + gpointer self) + throw () +{ + GstreamerPlayer * player = (GstreamerPlayer*) self; + + if (oldState == GST_STATE_PLAYING && newState != GST_STATE_PLAYING) { + player->fireOnStopEvent(); + } +} + + +/*------------------------------------------------------------------------------ + * De-initialize the Gstreamer Player + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: deInitialize(void) throw () +{ + if (initialized) { + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_bin_sync_children_state(GST_BIN(pipeline)); + + if (!gst_element_get_parent(audiosink)) { + // delete manually, if audiosink wasn't added to the pipeline + // for some reason + gst_object_unref(GST_OBJECT(audiosink)); + } + gst_object_unref(GST_OBJECT(pipeline)); + + initialized = false; + } +} + + +/*------------------------------------------------------------------------------ + * Attach an event listener. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: attachListener(AudioPlayerEventListener* eventListener) + throw () +{ + listeners.push_back(eventListener); +} + + +/*------------------------------------------------------------------------------ + * Detach an event listener. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: detachListener(AudioPlayerEventListener* eventListener) + throw (std::invalid_argument) +{ + ListenerVector::iterator it = listeners.begin(); + ListenerVector::iterator end = listeners.end(); + + while (it != end) { + if (*it == eventListener) { + listeners.erase(it); + return; + } + ++it; + } + + throw std::invalid_argument("supplied event listener not found"); +} + + +/*------------------------------------------------------------------------------ + * Send the onStop event to all attached listeners. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: fireOnStopEvent(void) throw () +{ + ListenerVector::iterator it = listeners.begin(); + ListenerVector::iterator end = listeners.end(); + + while (it != end) { + (*it)->onStop(); + ++it; + } +} + + +/*------------------------------------------------------------------------------ + * Specify which file to play + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: open(const std::string fileUrl) + throw (std::invalid_argument) +{ + std::string filePath; + + if (fileUrl.find("file:") == 0) { + filePath = fileUrl.substr(5, fileUrl.size()); + } else if (fileUrl.find("file://") == 0) { + filePath = fileUrl.substr(7, fileUrl.size()); + } else { + throw std::invalid_argument("badly formed URL or unsupported protocol"); + } + + g_object_set(G_OBJECT(filesrc), "location", filePath.c_str(), NULL); +} + + +/*------------------------------------------------------------------------------ + * Tell if we've been opened. + *----------------------------------------------------------------------------*/ +bool +GstreamerPlayer :: isOpened(void) throw () +{ + gchar * str; + + g_object_get(G_OBJECT(filesrc), "location", &str, NULL); + + return str != 0; +} + + +/*------------------------------------------------------------------------------ + * Get the length of the current audio clip. + *----------------------------------------------------------------------------*/ +Ptr::Ref +GstreamerPlayer :: getPlaylength(void) throw () +{ + Ptr::Ref length; + gint64 ns; + GstFormat format = GST_FORMAT_TIME; + + if (decoderSrc + && gst_pad_query(decoderSrc, GST_QUERY_TOTAL, &format, &ns) + && format == GST_FORMAT_TIME) { + + // use microsec, as nanosec() is not found by the compiler (?) + length.reset(new time_duration(microsec(ns / 1000LL))); + } + + return length; +} + + +/*------------------------------------------------------------------------------ + * Start playing + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: start(void) throw (std::logic_error) +{ + if (!isOpened()) { + throw std::logic_error("GstreamerPlayer not opened yet"); + } + + if (!isPlaying()) { + gst_element_set_state(audiosink, GST_STATE_PAUSED); + gst_element_set_state(pipeline, GST_STATE_PLAYING); + } +} + + +/*------------------------------------------------------------------------------ + * Pause the player + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: pause(void) throw (std::logic_error) +{ + if (isPlaying()) { + gst_element_set_state(pipeline, GST_STATE_PAUSED); + } +} + + +/*------------------------------------------------------------------------------ + * Tell if we're playing + *----------------------------------------------------------------------------*/ +bool +GstreamerPlayer :: isPlaying(void) throw () +{ + return gst_element_get_state(pipeline) == GST_STATE_PLAYING; +} + + +/*------------------------------------------------------------------------------ + * Stop playing + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: stop(void) throw (std::logic_error) +{ + if (!isOpened()) { + throw std::logic_error("GstreamerPlayer not opened yet"); + } + + if (isPlaying()) { + gst_element_set_state(pipeline, GST_STATE_READY); + } +} + + +/*------------------------------------------------------------------------------ + * Close the currently opened audio file. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: close(void) throw () +{ + if (isPlaying()) { + stop(); + } + + gst_element_set_state(pipeline, GST_STATE_NULL); +} + + +/*------------------------------------------------------------------------------ + * Get the volume of the player. + *----------------------------------------------------------------------------*/ +unsigned int +GstreamerPlayer :: getVolume(void) throw () +{ + return 0; +} + + +/*------------------------------------------------------------------------------ + * Set the volume of the player. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: setVolume(unsigned int volume) throw () +{ +} + + +/*------------------------------------------------------------------------------ + * Open a playlist, with simulated fading. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayer :: openAndStart(Ptr::Ref playlist) + throw (std::invalid_argument, + std::logic_error, + std::runtime_error) +{ +} + + +/*------------------------------------------------------------------------------ + * Set the audio device. + *----------------------------------------------------------------------------*/ +bool +GstreamerPlayer :: setAudioDevice(const std::string &deviceName) + throw () +{ + // TODO: support OSS as well + if (deviceName.size() > 0) { + g_object_set(G_OBJECT(audiosink), "device", deviceName.c_str(), NULL); + } + + return true; +} + diff --git a/livesupport/modules/playlistExecutor/src/GstreamerPlayer.h b/livesupport/modules/playlistExecutor/src/GstreamerPlayer.h new file mode 100644 index 000000000..b0659f3e5 --- /dev/null +++ b/livesupport/modules/playlistExecutor/src/GstreamerPlayer.h @@ -0,0 +1,553 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2004 Media Development Loan Fund + + This file is part of the LiveSupport project. + http://livesupport.campware.org/ + To report bugs, send an e-mail to bugs@campware.org + + LiveSupport is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + LiveSupport is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LiveSupport; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + Author : $Author: maroy $ + Version : $Revision: 1.1 $ + Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/src/GstreamerPlayer.h,v $ + +------------------------------------------------------------------------------*/ +#ifndef GstreamerPlayer_h +#define GstreamerPlayer_h + +#ifndef __cplusplus +#error This is a C++ include file +#endif + + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "configure.h" +#endif + +#include + +#include + +#include "LiveSupport/Core/Configurable.h" +#include "LiveSupport/PlaylistExecutor/AudioPlayerInterface.h" + +#include "LiveSupport/Core/Playlist.h" + + +namespace LiveSupport { +namespace PlaylistExecutor { + +using namespace boost::posix_time; + +using namespace LiveSupport; +using namespace LiveSupport::Core; + +/* ================================================================ constants */ + + +/* =================================================================== macros */ + + +/* =============================================================== data types */ + +/** + * A class to play audio files and some SMIL files through the Gstreamer + * library. + * This class can be configured with the following XML element. + * + *

+ *  
+ *  

+ *
+ *  where the dllPath is the path to the directory containing the Helix
+ *  library shared objects.  The optional audioDevice argument sets the
+ *  AUDIO environment variable which is read by the Helix client.
+ *
+ *  There are two parameters which are only there because the current version
+ *  of the Helix client does not handle animation tags in SMIL files properly.
+ *  They will be removed from later versions.
+ *  
    + *
  • audioStreamTimeOut (milliseconds) - the time to wait for each + * GetAudioStream() operation before a timeout occurs; + * the default is 5;
  • + *
  • fadeLookAheadTime (milliseconds) - each fade-in or fade-out is + * scheduled (using IHXAudioCrossFade::CrossFade()) this + * much time before it is to happen; the default is 2500.
  • + *
+ * + * The DTD for the above configuration is the following: + * + *

+ *  
+ *  
+ *  
+ *  
+ *  
+ *  
+ * + * @author $Author: maroy $ + * @version $Revision: 1.1 $ + */ +class GstreamerPlayer : virtual public Configurable, + virtual public AudioPlayerInterface +{ + private: + /** + * The name of the configuration XML elmenent used by GstreamerPlayer + */ + static const std::string configElementNameStr; + + /** + * The pipeline inside the player + */ + GstElement * pipeline; + + /** + * The file source element. + */ + GstElement * filesrc; + + /** + * The typefinder element. + */ + GstElement * typefinder; + + /** + * The decoder element. + */ + GstElement * decoder; + + /** + * The source pad of the decoder element. + * This pad can be used to navigate in a time-based manner. + */ + GstPad * decoderSrc; + + /** + * The audio sink + */ + GstElement * audiosink; + + /** + * The list of factories considered when creating the pipeline. + */ + static GList * factories; + + /** + * The URL to play. + */ + std::string url; + + /** + * Flag to indicate if this object has been initialized. + */ + bool initialized; + + /** + * The audio device to play on. + */ + std::string audioDevice; + + /** + * The type for the vector of listeners. + * Just a shorthand notation, to make reference to the type + * easier. + */ + typedef std::vector + ListenerVector; + + /** + * A vector of event listeners, which are interested in events + * related to this player. + */ + ListenerVector listeners; + + /** + * Initialize the list of factories that we're interested in + * when creating the pipeline. + */ + static void + initFactories(void) throw (); + + /** + * Filter plugins so that only factories for demuxers, decoders + * and parsers are considered. + * + * @param feature the features of the plugin to check + * @param data not used + * @return true of the supplied feature is a factory for a demuxer, + * decoder or parser, false otherwise + */ + static gboolean + featureFilter(GstPluginFeature * feature, + gpointer data) + throw (); + + /** + * Compare the plugin features according to their rank. + * + * @param feature1 one of the features to compare + * @param feature2 the second feature to compare + * @return 0 of the ranks are equal, <0 if feature1 is ranked lower, + * >0 if feature1 is ranked higher + */ + static gint + compareRanks(GstPluginFeature * feature1, + GstPluginFeature * feature2) + throw (); + + /** + * Handler to recieve errors from gstreamer. + * + * @param pipeline the pipeline generating the error + * @param source the source of the error + * @param error the error itself + * @param debug debug info + * @param self pointer to the associated GsreamerPlayer object. + */ + static void + errorHandler(GstElement * pipeline, + GstElement * source, + GError * error, + gchar * debug, + gpointer self) throw (); + + /** + * Even handler for when a matching type was found by typefineder. + * + * @param typefinder the typefineder that found the match + * @param probability the probability of the match + * @param caps the capabilities of the found match + * @param self pointer to the associated GstreamPlayer object. + */ + static void + typeFound(GstElement * typefinder, + guint probability, + GstCaps * caps, + gpointer self) throw (); + + /** + * Event handler for when a new dynamic pad is found. Plug the found + * pad by calling tryToPlug(). + * + * @param element the element where the new pad came up. + * @param pad the new pad + * @param self reference to the associated GstreamerPlayer object + */ + static void + newPad(GstElement * element, + GstPad * pad, + gpointer self) + throw (); + + /** + * Event handler for the state change event on the pipeline. + * Use this to catch events like playing has ended. + * + * @param element the pipeline the event change has occured at + * @param oldState the old state + * @param newState the new state + * @param self a pointer to the associated GstreamerPlayer object. + */ + static void + stateChange(GstElement * element, + gint oldState, + gint newState, + gpointer self) + throw (); + + /** + * Try to plug a matching element to the specified pad + * + * @param pad the pad to plug to + * @param caps find a matching element to these capabilities + * @exception std::logic_error if couldn't plug + */ + void + tryToPlug(GstPad * pad, + const GstCaps * caps) + throw (std::logic_error); + + /** + * Close the link between a source pad and a sink element + * + * @param srcpad the source pad to link up + * @param sinkelement link srcpad to this element + * @param padname use this pad from sinkelement to link to + * @param templlist use pads from these templates + */ + void + closeLink(GstPad * srcpad, + GstElement * sinkelement, + const gchar * padname, + const GList * templlist) + throw (); + + /** + * Send the onStop event to all attached listeners. + */ + virtual void + fireOnStopEvent(void) throw (); + + /** + * Tell if the object is currently opened (has a file source to + * read.) + * + * @return true if the object is currently opened, false otherwise. + */ + bool + isOpened(void) throw (); + + + public: + /** + * Constructor. + */ + GstreamerPlayer(void) throw () + { + pipeline = 0; + filesrc = 0; + typefinder = 0; + decoder = 0; + decoderSrc = 0; + audiosink = 0; + initialized = false; + } + + /** + * A virtual destructor, as this class has virtual functions. + */ + virtual + ~GstreamerPlayer(void) throw () + { + deInitialize(); + } + + /** + * Return the name of the XML element this object expects + * to be sent to a call to configure(). + * + * @return the name of the expected XML configuration element. + */ + static const std::string + getConfigElementName(void) throw () + { + return configElementNameStr; + } + + /** + * Configure the object based on the XML element supplied. + * + * @param element the XML element to configure the object from. + * @exception std::invalid_argument if the supplied XML element + * contains bad configuraiton information + * @exception std::logic_error if the scheduler daemon has already + * been configured, and can not be reconfigured. + */ + virtual void + configure(const xmlpp::Element & element) + throw (std::invalid_argument, + std::logic_error); + + /** + * Initialize the Helix Player object, so that it is ready to + * play audio files. + * + * @exception std::exception on initialization problems. + */ + virtual void + initialize(void) throw (std::exception); + + /** + * De-initialize the Helix Player object. + */ + virtual void + deInitialize(void) throw (); + + /** + * Attach an event listener for this audio player. + * After this call, the supplied event will recieve all events + * related to this audio player. + * + * @param eventListener the event listener to register. + * @see #detach + */ + virtual void + attachListener(AudioPlayerEventListener* eventListener) + throw (); + + /** + * Detach an event listener for this audio player. + * + * @param eventListener the event listener to unregister. + * @exception std::invalid_argument if the supplied event listener + * has not been previously registered. + * @see #attach + */ + virtual void + detachListener(AudioPlayerEventListener* eventListener) + throw (std::invalid_argument); + + /** + * Set the audio device used for playback. + * + * @param deviceName the new device name, e.g., /dev/dsp or + * plughw:0,0 + * @return true if successful, false if not + */ + virtual bool + setAudioDevice(const std::string &deviceName) + throw (); + + /** + * Specify which audio resource to play. + * The file may be a playlist, referencing other files, which + * will be accessed automatically. + * Note: this call will not start playing! You will + * have to call the start() function to begin playing. + * Always close any opened resource with a call to close(). + * + * @param fileUrl a URL to a file + * @exception std::invalid_argument if the supplied fileUrl + * seems to be invalid. + * @see #close + * @see #start + */ + virtual void + open(const std::string fileUrl) throw (std::invalid_argument); + + /** + * Close an audio source that was opened. + * + * @see #open + */ + virtual void + close(void) throw (); + + /** + * Start playing. + * This call will start playing the active playlist, which was + * set by a previous call to open(). + * Playing can be stopped by calling stop(). + * + * @exception std::logic_error if there was no previous call to + * open(). + * @see #open + * @see #stop + */ + virtual void + start(void) throw (std::logic_error); + + /** + * Pause the player. + * Playing can be resumed by calling start(). + * + * @exception std::logic_error if there was no previous call to + * open(). + * @see #open + * @see #start + */ + virtual void + pause(void) throw (std::logic_error); + + /** + * Tell if we're currently playing. + * + * @return true of the player is currently playing, false + * otherwise. + */ + virtual bool + isPlaying(void) throw (); + + /** + * Stop playing. + * + * @exception std::logic_error if there was no previous call to + * start() + */ + virtual void + stop(void) throw (std::logic_error); + + /** + * Get the length of the currently opened audio clip. + * This function waits as long as necessary to get the length. + * + * @return the length of the currently playing audio clip, or 0, + * if nothing is openned. + */ + virtual Ptr::Ref + getPlaylength(void) throw (); + + /** + * Get the volume of the player. + * + * @return the volume, from 1 to 100. + */ + virtual unsigned int + getVolume(void) throw (); + + /** + * Set the volume of the player. + * + * @param volume the new volume, from 1 to 100. + */ + virtual void + setVolume(unsigned int volume) throw (); + + /** + * Play a playlist, with simulated fading. + * + * This is a stopgap method, and should be replaced as soon as + * the SMIL animation issues are fixed in the Helix client. + * + * The playlist is assumed to contain a URI field, which points + * to a SMIL file containing the same audio clips, with the same + * offsets, as the playlist. This can be ensured, for example, by + * calling Storage::WebStorageClient::acquirePlaylist(). + * + * @param playlist the Playlist object to be played. + * @exception std::invalid_argument playlist is invalid (e.g., + * does not have a URI field, or there is no valid + * SMIL file at the given URI). + * @exception std::logic_error thrown by start() if open() was + * unsuccessful, but returned normally (never happens) + * @exception std::runtime_error on errors thrown by the helix player + */ + virtual void + openAndStart(Ptr::Ref playlist) + throw (std::invalid_argument, + std::logic_error, + std::runtime_error); +}; + + +/* ================================================= external data structures */ + + +/* ====================================================== function prototypes */ + + +} // namespace PlaylistExecutor +} // namespace LiveSupport + + +#endif // GstreamerPlayer_h + diff --git a/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.cxx b/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.cxx new file mode 100644 index 000000000..5addb2593 --- /dev/null +++ b/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.cxx @@ -0,0 +1,339 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2004 Media Development Loan Fund + + This file is part of the LiveSupport project. + http://livesupport.campware.org/ + To report bugs, send an e-mail to bugs@campware.org + + LiveSupport is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + LiveSupport is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LiveSupport; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + Author : $Author: maroy $ + Version : $Revision: 1.1 $ + Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.cxx,v $ + +------------------------------------------------------------------------------*/ + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "configure.h" +#endif + +#if HAVE_UNISTD_H +#include +#else +#error "Need unistd.h" +#endif + + +#include +#include + +#include "LiveSupport/Core/TimeConversion.h" + +#include "GstreamerPlayer.h" +#include "TestEventListener.h" +#include "GstreamerPlayerTest.h" + + +using namespace LiveSupport::PlaylistExecutor; + +/* =================================================== local data structures */ + + +/* ================================================ local constants & macros */ + +CPPUNIT_TEST_SUITE_REGISTRATION(GstreamerPlayerTest); + +/** + * The name of the configuration file for the Helix player. + */ +static const std::string configFileName = "etc/gstreamerPlayer.xml"; + + +/* =============================================== local function prototypes */ + + +/* ============================================================= module code */ + +/*------------------------------------------------------------------------------ + * Set up the test environment + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: setUp(void) throw () +{ + try { + Ptr::Ref parser( + new xmlpp::DomParser(configFileName, true)); + const xmlpp::Document * document = parser->get_document(); + const xmlpp::Element * root = document->get_root_node(); + + player.reset(new GstreamerPlayer()); + player->configure(*root); + + } catch (std::invalid_argument &e) { + std::cerr << "semantic error in configuration file" << std::endl; + } catch (xmlpp::exception &e) { + std::cerr << e.what() << std::endl; + } +} + + +/*------------------------------------------------------------------------------ + * Clean up the test environment + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: tearDown(void) throw () +{ + player.reset(); +} + + +/*------------------------------------------------------------------------------ + * Test to see if the GstreamerPlayer engine can be started and stopped + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: firstTest(void) + throw (CPPUNIT_NS::Exception) +{ + try { + player->initialize(); + CPPUNIT_ASSERT(!player->isPlaying()); + player->deInitialize(); + } catch (std::exception &e) { + CPPUNIT_FAIL("failed to initialize or de-initialize GstreamerPlayer"); + } +} + + +/*------------------------------------------------------------------------------ + * Play something simple + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: simplePlayTest(void) + throw (CPPUNIT_NS::Exception) +{ + Ptr::Ref sleepT(new time_duration(microseconds(10))); + + player->initialize(); + try { + player->open("file:var/test.mp3"); + } catch (std::invalid_argument &e) { + CPPUNIT_FAIL(e.what()); + } + CPPUNIT_ASSERT(!player->isPlaying()); + player->start(); + CPPUNIT_ASSERT(player->isPlaying()); + while (player->isPlaying()) { + TimeConversion::sleep(sleepT); + } + + Ptr::Ref playlength = player->getPlaylength(); + CPPUNIT_ASSERT(playlength.get()); + CPPUNIT_ASSERT(playlength->seconds() == 14); + CPPUNIT_ASSERT(playlength->fractional_seconds() == 785187); + + CPPUNIT_ASSERT(!player->isPlaying()); + player->close(); + player->deInitialize(); +} + + +/*------------------------------------------------------------------------------ + * Check for error conditions + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: checkErrorConditions(void) + throw (CPPUNIT_NS::Exception) +{ + player->initialize(); + + bool gotException; + + CPPUNIT_ASSERT(!player->isPlaying()); + + gotException = false; + try { + player->start(); + } catch (std::logic_error &e) { + gotException = true; + } + CPPUNIT_ASSERT(gotException); + + gotException = false; + try { + player->stop(); + } catch (std::logic_error &e) { + gotException = true; + } + CPPUNIT_ASSERT(gotException); + + gotException = false; + try { + player->open("totally/bad/URL"); + } catch (std::invalid_argument &e) { + gotException = true; + } + CPPUNIT_ASSERT(gotException); + + gotException = false; + try { + player->start(); + } catch (std::logic_error &e) { + gotException = true; + } + CPPUNIT_ASSERT(gotException); + + // check for opening a wrong URL after opening a proper one + try { + player->open("file:var/test.mp3"); + } catch (std::invalid_argument &e) { + CPPUNIT_FAIL(e.what()); + } + player->close(); + gotException = false; + try { + player->open("totally/bad/URL"); + } catch (std::invalid_argument &e) { + gotException = true; + } + CPPUNIT_ASSERT(gotException); + + player->deInitialize(); +} + + +/*------------------------------------------------------------------------------ + * Test to see if attaching and detaching event listeners works. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: eventListenerAttachTest(void) + throw (CPPUNIT_NS::Exception) +{ + CPPUNIT_ASSERT_NO_THROW(player->initialize()); + + Ptr::Ref listener1(new TestEventListener()); + Ptr::Ref listener2(new TestEventListener()); + + // try with one listener + player->attachListener(listener1.get()); + CPPUNIT_ASSERT_NO_THROW( + player->detachListener(listener1.get()) + ); + CPPUNIT_ASSERT_THROW( + player->detachListener(listener1.get()), + std::invalid_argument + ); + + // try with two listeners + player->attachListener(listener1.get()); + CPPUNIT_ASSERT_THROW( + player->detachListener(listener2.get()), + std::invalid_argument + ); + player->attachListener(listener2.get()); + CPPUNIT_ASSERT_NO_THROW( + player->detachListener(listener1.get()); + ); + + player->deInitialize(); +} + + +/*------------------------------------------------------------------------------ + * Test to see if the player event listener mechanism works. + *----------------------------------------------------------------------------*/ +void +GstreamerPlayerTest :: eventListenerTest(void) + throw (CPPUNIT_NS::Exception) +{ + CPPUNIT_ASSERT_NO_THROW(player->initialize()); + + Ptr::Ref sleepT(new time_duration(microseconds(10))); + Ptr::Ref listener1(new TestEventListener()); + player->attachListener(listener1.get()); + + // try with one listener + CPPUNIT_ASSERT(!listener1->stopFlag); + CPPUNIT_ASSERT_NO_THROW( + player->open("file:var/test.mp3"); + ); + CPPUNIT_ASSERT(!player->isPlaying()); + CPPUNIT_ASSERT(!listener1->stopFlag); + player->start(); + CPPUNIT_ASSERT(player->isPlaying()); + CPPUNIT_ASSERT(!listener1->stopFlag); + while (player->isPlaying()) { + CPPUNIT_ASSERT(!listener1->stopFlag); + TimeConversion::sleep(sleepT); + } + CPPUNIT_ASSERT(!player->isPlaying()); + CPPUNIT_ASSERT(listener1->stopFlag); + listener1->stopFlag = false; + + // try with two listeners + Ptr::Ref listener2(new TestEventListener()); + player->attachListener(listener2.get()); + + CPPUNIT_ASSERT(!listener1->stopFlag); + CPPUNIT_ASSERT(!listener2->stopFlag); + CPPUNIT_ASSERT_NO_THROW( + player->open("file:var/test.mp3"); + ); + CPPUNIT_ASSERT(!player->isPlaying()); + CPPUNIT_ASSERT(!listener1->stopFlag); + CPPUNIT_ASSERT(!listener2->stopFlag); + player->start(); + CPPUNIT_ASSERT(player->isPlaying()); + CPPUNIT_ASSERT(!listener1->stopFlag); + CPPUNIT_ASSERT(!listener2->stopFlag); + while (player->isPlaying()) { + CPPUNIT_ASSERT(!listener1->stopFlag); + CPPUNIT_ASSERT(!listener2->stopFlag); + TimeConversion::sleep(sleepT); + } + CPPUNIT_ASSERT(!player->isPlaying()); + CPPUNIT_ASSERT(listener1->stopFlag); + CPPUNIT_ASSERT(listener2->stopFlag); + listener1->stopFlag = false; + listener2->stopFlag = false; + + // try with only the second listener + CPPUNIT_ASSERT_NO_THROW( + player->detachListener(listener1.get()); + ); + CPPUNIT_ASSERT(!listener2->stopFlag); + CPPUNIT_ASSERT_NO_THROW( + player->open("file:var/test.mp3"); + ); + CPPUNIT_ASSERT(!player->isPlaying()); + CPPUNIT_ASSERT(!listener2->stopFlag); + player->start(); + CPPUNIT_ASSERT(player->isPlaying()); + CPPUNIT_ASSERT(!listener2->stopFlag); + while (player->isPlaying()) { + CPPUNIT_ASSERT(!listener2->stopFlag); + TimeConversion::sleep(sleepT); + } + CPPUNIT_ASSERT(!player->isPlaying()); + CPPUNIT_ASSERT(listener2->stopFlag); + listener2->stopFlag = false; + + player->close(); + player->deInitialize(); +} + diff --git a/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.h b/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.h new file mode 100644 index 000000000..ca202df50 --- /dev/null +++ b/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.h @@ -0,0 +1,151 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2004 Media Development Loan Fund + + This file is part of the LiveSupport project. + http://livesupport.campware.org/ + To report bugs, send an e-mail to bugs@campware.org + + LiveSupport is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + LiveSupport is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LiveSupport; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + Author : $Author: maroy $ + Version : $Revision: 1.1 $ + Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/src/GstreamerPlayerTest.h,v $ + +------------------------------------------------------------------------------*/ +#ifndef GstreamerPlayerTest_h +#define GstreamerPlayerTest_h + +#ifndef __cplusplus +#error This is a C++ include file +#endif + + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "configure.h" +#endif + +#include + + +namespace LiveSupport { +namespace PlaylistExecutor { + +/* ================================================================ constants */ + + +/* =================================================================== macros */ + + +/* =============================================================== data types */ + +/** + * Unit test for the GstreamerPlayer class. + * + * @author $Author: maroy $ + * @version $Revision: 1.1 $ + * @see GstreamerPlayer + */ +class GstreamerPlayerTest : public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE(GstreamerPlayerTest); + CPPUNIT_TEST(firstTest); + CPPUNIT_TEST(simplePlayTest); + CPPUNIT_TEST(checkErrorConditions); + CPPUNIT_TEST(eventListenerAttachTest); + CPPUNIT_TEST(eventListenerTest); + CPPUNIT_TEST_SUITE_END(); + + private: + + /** + * The player to use for the tests. + */ + Ptr::Ref player; + + + protected: + + /** + * A simple test. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + firstTest(void) throw (CPPUNIT_NS::Exception); + + /** + * A simple play test. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + simplePlayTest(void) throw (CPPUNIT_NS::Exception); + + /** + * Check for error conditions. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + checkErrorConditions(void) throw (CPPUNIT_NS::Exception); + + /** + * Test to see if attaching and detaching event listeners works. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + eventListenerAttachTest(void) throw (CPPUNIT_NS::Exception); + + /** + * Test to see if the player event listener mechanism works. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + eventListenerTest(void) throw (CPPUNIT_NS::Exception); + + + public: + + /** + * Set up the environment for the test case. + */ + void + setUp(void) throw (); + + /** + * Clean up the environment after the test case. + */ + void + tearDown(void) throw (); +}; + + +/* ================================================= external data structures */ + + +/* ====================================================== function prototypes */ + + +} // namespace PlaylistExecutor +} // namespace LiveSupport + +#endif // GstreamerPlayerTest_h + diff --git a/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.cxx b/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.cxx new file mode 100644 index 000000000..c9f57424c --- /dev/null +++ b/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.cxx @@ -0,0 +1,247 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2004 Media Development Loan Fund + + This file is part of the LiveSupport project. + http://livesupport.campware.org/ + To report bugs, send an e-mail to bugs@campware.org + + LiveSupport is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + LiveSupport is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LiveSupport; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + Author : $Author: maroy $ + Version : $Revision: 1.1 $ + Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.cxx,v $ + +------------------------------------------------------------------------------*/ + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "configure.h" +#endif + +#if HAVE_UNISTD_H +#include +#else +#error "Need unistd.h" +#endif + + +#include +#include + +#include "LiveSupport/Core/TimeConversion.h" + +#include "GstreamerPlayer.h" +#include "TestEventListener.h" +#include "TwoGstreamerPlayersTest.h" + + +using namespace LiveSupport::PlaylistExecutor; + +/* =================================================== local data structures */ + + +/* ================================================ local constants & macros */ + +CPPUNIT_TEST_SUITE_REGISTRATION(TwoGstreamerPlayersTest); + +/** + * The name of the configuration file for the Helix player. + */ +static const std::string configFileName = "etc/twoGstreamerPlayers.xml"; + +/** + * The name of the root XML element in the configuration file. + */ +static const std::string rootElementName = "twoGstreamerPlayers"; + + +/* =============================================== local function prototypes */ + + +/* ============================================================= module code */ + +/*------------------------------------------------------------------------------ + * Set up the test environment + *----------------------------------------------------------------------------*/ +void +TwoGstreamerPlayersTest :: setUp(void) throw () +{ + try { + Ptr::Ref parser( + new xmlpp::DomParser(configFileName, true)); + const xmlpp::Document * document = parser->get_document(); + const xmlpp::Element * root = document->get_root_node(); + + xmlpp::Node::NodeList children; + const xmlpp::Element * element; + + children = root->get_children(GstreamerPlayer::getConfigElementName()); + + element = dynamic_cast (*(children.begin())); + player1.reset(new GstreamerPlayer()); + player1->configure(*element); + + children.pop_front(); + element = dynamic_cast (*(children.begin())); + player2.reset(new GstreamerPlayer()); + player2->configure(*element); + + } catch (std::invalid_argument &e) { + std::cerr << "semantic error in configuration file" << std::endl; + } catch (xmlpp::exception &e) { + std::cerr << e.what() << std::endl; + } +} + + +/*------------------------------------------------------------------------------ + * Clean up the test environment + *----------------------------------------------------------------------------*/ +void +TwoGstreamerPlayersTest :: tearDown(void) throw () +{ + player2.reset(); + player1.reset(); +} + + +/*------------------------------------------------------------------------------ + * Test to see if the GstreamerPlayer engine can be started and stopped + *----------------------------------------------------------------------------*/ +void +TwoGstreamerPlayersTest :: firstTest(void) + throw (CPPUNIT_NS::Exception) +{ + try { + player1->initialize(); + player2->initialize(); + CPPUNIT_ASSERT(!player1->isPlaying()); + CPPUNIT_ASSERT(!player2->isPlaying()); + player2->deInitialize(); + player1->deInitialize(); + } catch (std::exception &e) { + CPPUNIT_FAIL("failed to initialize or de-initialize GstreamerPlayer"); + } +} + + +/*------------------------------------------------------------------------------ + * Play something simple on player #1 + *----------------------------------------------------------------------------*/ +void +TwoGstreamerPlayersTest :: simplePlay1Test(void) + throw (CPPUNIT_NS::Exception) +{ + Ptr::Ref sleepT(new time_duration(microseconds(10))); + + player1->initialize(); + try { + player1->open("file:var/test10001.mp3"); + } catch (std::invalid_argument &e) { + CPPUNIT_FAIL(e.what()); + } + CPPUNIT_ASSERT(!player1->isPlaying()); + player1->start(); + CPPUNIT_ASSERT(player1->isPlaying()); + while (player1->isPlaying()) { + TimeConversion::sleep(sleepT); + } + CPPUNIT_ASSERT(!player1->isPlaying()); + player1->close(); + player1->deInitialize(); +} + + +/*------------------------------------------------------------------------------ + * Play something simple on player #2 + *----------------------------------------------------------------------------*/ +void +TwoGstreamerPlayersTest :: simplePlay2Test(void) + throw (CPPUNIT_NS::Exception) +{ + Ptr::Ref sleepT(new time_duration(microseconds(10))); + + player2->initialize(); + try { + player2->open("file:var/test.mp3"); + } catch (std::invalid_argument &e) { + CPPUNIT_FAIL(e.what()); + } + CPPUNIT_ASSERT(!player2->isPlaying()); + player2->start(); + CPPUNIT_ASSERT(player2->isPlaying()); + while (player2->isPlaying()) { + TimeConversion::sleep(sleepT); + } + CPPUNIT_ASSERT(!player2->isPlaying()); + player2->close(); + player2->deInitialize(); +} + + +/*------------------------------------------------------------------------------ + * Play something simple on both players + *----------------------------------------------------------------------------*/ +void +TwoGstreamerPlayersTest :: playBothTest(void) + throw (CPPUNIT_NS::Exception) +{ + Ptr::Ref sleepT(new time_duration(microseconds(10))); + + player1->initialize(); + player2->initialize(); + + // start playing on player1 + try { + player1->open("file:var/test10001.mp3"); + } catch (std::invalid_argument &e) { + CPPUNIT_FAIL(e.what()); + } + CPPUNIT_ASSERT(!player1->isPlaying()); + player1->start(); + CPPUNIT_ASSERT(player1->isPlaying()); + + // sleep some time + for (unsigned i = 0; i < 100; ++i) { + TimeConversion::sleep(sleepT); + } + + // start playing on player2 + try { + player2->open("file:var/test.mp3"); + } catch (std::invalid_argument &e) { + CPPUNIT_FAIL(e.what()); + } + CPPUNIT_ASSERT(!player2->isPlaying()); + player2->start(); + CPPUNIT_ASSERT(player2->isPlaying()); + + // wait for both players to finish + while (player1->isPlaying() || player2->isPlaying()) { + TimeConversion::sleep(sleepT); + } + CPPUNIT_ASSERT(!player1->isPlaying()); + CPPUNIT_ASSERT(!player2->isPlaying()); + + player2->close(); + player1->close(); + player2->deInitialize(); + player1->deInitialize(); +} + + diff --git a/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.h b/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.h new file mode 100644 index 000000000..b137d8831 --- /dev/null +++ b/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.h @@ -0,0 +1,161 @@ +/*------------------------------------------------------------------------------ + + Copyright (c) 2004 Media Development Loan Fund + + This file is part of the LiveSupport project. + http://livesupport.campware.org/ + To report bugs, send an e-mail to bugs@campware.org + + LiveSupport is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + LiveSupport is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with LiveSupport; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + Author : $Author: maroy $ + Version : $Revision: 1.1 $ + Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/playlistExecutor/src/TwoGstreamerPlayersTest.h,v $ + +------------------------------------------------------------------------------*/ +#ifndef TwoGstreamerPlayersTest_h +#define TwoGstreamerPlayersTest_h + +#ifndef __cplusplus +#error This is a C++ include file +#endif + + +/* ============================================================ include files */ + +#ifdef HAVE_CONFIG_H +#include "configure.h" +#endif + +#include + + +namespace LiveSupport { +namespace PlaylistExecutor { + +/* ================================================================ constants */ + + +/* =================================================================== macros */ + + +/* =============================================================== data types */ + +/** + * Unit test for the GstreamerPlayer class, two see if two helix players, + * playing on two different sound cards work correctly. + * + * @author $Author: maroy $ + * @version $Revision: 1.1 $ + * @see GstreamerPlayer + */ +class TwoGstreamerPlayersTest : public CPPUNIT_NS::TestFixture +{ + CPPUNIT_TEST_SUITE(TwoGstreamerPlayersTest); + CPPUNIT_TEST(firstTest); + CPPUNIT_TEST(simplePlay1Test); + CPPUNIT_TEST(simplePlay2Test); + CPPUNIT_TEST(playBothTest); + CPPUNIT_TEST_SUITE_END(); + + private: + + /** + * Helix player #1 to use for the tests. + */ + Ptr::Ref player1; + + /** + * Helix player #2 to use for the tests. + */ + Ptr::Ref player2; + + /** + * Play a specific file. + * + * @param fileName the name of the file to play. + * @param player the player to use for playing the file. + * @exception CPPUNIT_NS::Exception on playing failures + */ + void + playFile(const std::string & fileName, + Ptr::Ref player) + throw (CPPUNIT_NS::Exception); + + + protected: + + /** + * A simple test. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + firstTest(void) throw (CPPUNIT_NS::Exception); + + + /** + * Play something on player #1. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + simplePlay1Test(void) throw (CPPUNIT_NS::Exception); + + /** + * Play something on player #2. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + simplePlay2Test(void) throw (CPPUNIT_NS::Exception); + + /** + * Play something on both players, at the same time. + * + * @exception CPPUNIT_NS::Exception on test failures. + */ + void + playBothTest(void) throw (CPPUNIT_NS::Exception); + + + public: + + /** + * Set up the environment for the test case. + */ + void + setUp(void) throw (); + + /** + * Clean up the environment after the test case. + */ + void + tearDown(void) throw (); +}; + + +/* ================================================= external data structures */ + + +/* ====================================================== function prototypes */ + + +} // namespace PlaylistExecutor +} // namespace LiveSupport + +#endif // TwoGstreamerPlayersTest_h +