[asterisk-scf-commits] asterisk-scf/release/test_channel.git branch "fileplayback_endpoint" created.

Commits to the Asterisk SCF project code repositories asterisk-scf-commits at lists.digium.com
Tue Dec 21 15:54:53 UTC 2010


branch "fileplayback_endpoint" has been created
        at  c47719f27b298b80c547c972716708d3654f312c (commit)

- Log -----------------------------------------------------------------
commit c47719f27b298b80c547c972716708d3654f312c
Author: Brent Eagles <beagles at digium.com>
Date:   Tue Dec 21 12:14:56 2010 -0330

    Added RemoteControl.ice (to be moved to a more public location, even if
    it is still only in this component). This slice contains interfaces
    intended to allow some control over media streams as well as copying
    frames from a sink to source to be used for echo. Some of these
    interfaces aren't yet implemented.
    
    Added a TestTools.py example script that illustrates how you might
    use the interfaces in RemoteControl.ice to initiate a file playback.
    
    Added an initial implementation of file-based endpoint. At the moment,
    it only supports playback of ulaw encoded wave files. It uses some data
    types included in the pjmedia library.
    
    Modified the MediaEchoThread to use the details of the media frame being
    sent to control the pacing.
    
    There are lots of conceivable TODO's for this component. I will write a
    little design document that describes the direction I think this should
    go in. It does seem a fairly valuable tool as I was able to do some end
    to end testing with and uncovered some issues.

diff --git a/TestTools.py b/TestTools.py
new file mode 100644
index 0000000..d2da4f5
--- /dev/null
+++ b/TestTools.py
@@ -0,0 +1,112 @@
+#
+# Snippet module for running some simple tests with the test_channel library.
+#
+import Ice
+import AsteriskSCF
+import sys
+
+#
+# The file playback session endpoint implements a specialized SessionEndpoint
+# derived interface, AsteriskSCF::RemoteControl::V1::SessionFactory. This
+# interface allows you to register listeners to automatically be added to
+# session's it creates.  This makes it possible for a session listener to react
+# immediately to events on that session.
+#
+class SessionListener(AsteriskSCF.SessionCommunications.V1.SessionListener):
+    def __init__(self, trackName):
+        self.mTrackname = trackName
+
+    def connected(self, sessionPrx, current):
+        try:
+            if sessionPrx == None:
+                print "Received connect with nil proxy"
+                return
+
+            mediaSession = sessionPrx.getMediaSession()
+            if mediaSession == None:
+                print "No media session"
+                return
+
+            sources = mediaSession.getSources()
+            if len(sources) < 1:
+                print "No sources"
+                return
+
+            remoteControl = AsteriskSCF.RemoteControl.V1.SourcePrx.checkedCast(sources[0].ice_facet("Control"))
+            if remoteControl == None:
+                print "No remote control interface"
+                return
+
+            # 
+            # Media sink should already have been set by the bridge.
+            #
+            print "Starting the playback"
+            remoteControl.playTrack(self.mTrackname)
+            remoteControl.start()
+        except Ice.Exception,e:
+            print e
+
+
+def makeAutoPlaybackListener(filename):
+    return SessionListener(filename)
+
+global communicator
+global adapter
+global testEndpointLocatorPrx
+global endpointLocatorRegistryPrx
+global serviceLocatorPrx
+global sessionListener
+global sessionListenerPrx
+
+# 
+# Set up our Ice communicator and an object adapter.  This code can be safely
+# modified to use configuration for the object adapter configuraiton.
+#
+communicator = Ice.initialize(sys.argv)
+adapter = communicator.createObjectAdapterWithEndpoints("interactive", "default -p 54321")
+adapter.activate()
+
+#
+# Obtain some proxies to the services of interest. We'll use checked casts
+# for now, but these can easily be changed to unchecked casts if you want to 
+# the script to run without really "doing anything" and the services aren't
+# running yet.
+#
+testEndpointLocatorPrx  = AsteriskSCF.Core.Routing.V1.EndpointLocatorPrx.checkedCast(communicator.propertyToProxy("FilePlaybackLocator.Proxy"))
+if testEndpointLocatorPrx == None:
+    print "Unable to obtain file playback endpoint locator."
+    sys.exit(1)
+
+serviceLocatorPrx = AsteriskSCF.Core.Discovery.V1.ServiceLocatorPrx.checkedCast(communicator.propertyToProxy("LocatorService.Proxy"))
+if serviceLocatorPrx == None:
+    print "Unable to obtain service locator proxy."
+    sys.exit(1)
+
+# 
+# The Locator registry is found by way of the service locator.
+#
+searchParams = AsteriskSCF.Core.Discovery.V1.ServiceLocatorParams()
+searchParams.category = AsteriskSCF.Core.Routing.V1.RoutingServiceLocatorRegistryDiscoveryCategory
+endpointRegistryPrx = AsteriskSCF.Core.Routing.V1.LocatorRegistryPrx.checkedCast(serviceLocatorPrx.locate(searchParams))
+if endpointRegistryPrx == None:
+    print "Unable to obtain locator registry proxy."
+    sys.exit(1)
+
+endpointRegistryPrx.addEndpointLocator("fileplayback", ["9.*"], testEndpointLocatorPrx)
+
+sessionListenerPrx = AsteriskSCF.SessionCommunications.V1.SessionListenerPrx.uncheckedCast(adapter.addWithUUID(makeAutoPlaybackListener("blip.au")))
+
+def registerDefaultExampleListener():
+    playbackEndpointPrx = AsteriskSCF.RemoteControl.V1.SessionFactory(testEndpointLocatorPrx.lookup("999")) # can really be anything.
+    playbackEndpointPrx.addDefaultSessionListener(sessionListenerPrx)
+
+#
+# Now we have an environment with everything setup to add a sample session
+# listener.  It is recommended that you instantiate your own session listener
+# instance with an audio file of your liking and add it to the playback
+# endpoint. registerDefaultExampleListener() contains example code on how to do
+# this.
+# 
+# If you run this code as an argument to "python -i", you can interactively 
+# use these variables and methods to perform ad-hoc testing.
+#
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 33d3155..281b69e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,18 +1,38 @@
+asterisk_scf_slice_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../slice)
+asterisk_scf_compile_slice(RemoteControl.ice lib "Media remote control API" test_channel)
 asterisk_scf_component_init(test_channel CXX)
 asterisk_scf_component_add_slice(test_channel EndpointIf)
 asterisk_scf_component_add_slice(test_channel ComponentServiceIf)
 asterisk_scf_component_add_slice(test_channel SessionCommunicationsIf)
 asterisk_scf_component_add_slice(test_channel RoutingIf)
 asterisk_scf_component_add_slice(test_channel CommandsIf)
+asterisk_scf_component_add_slice(test_channel MediaIf)
+asterisk_scf_component_add_slice(test_channel RemoteControl)
 asterisk_scf_component_add_file(test_channel Service.cpp)
 asterisk_scf_component_add_file(test_channel MediaEchoThread.cpp)
+asterisk_scf_component_add_file(test_channel MediaEchoThread.h)
 asterisk_scf_component_add_file(test_channel MediaSession.cpp)
+asterisk_scf_component_add_file(test_channel MediaSession.h)
 asterisk_scf_component_add_file(test_channel TestEndpoint.cpp)
 asterisk_scf_component_add_file(test_channel TestEndpoint.h)
+asterisk_scf_component_add_file(test_channel FilePlaybackSessionEndpoint.h)
+asterisk_scf_component_add_file(test_channel FilePlaybackSessionEndpoint.cpp)
 asterisk_scf_component_add_ice_libraries(test_channel IceStorm)
 asterisk_scf_component_add_boost_libraries(test_channel thread)
 asterisk_scf_component_add_boost_libraries(test_channel date_time)
+asterisk_scf_component_add_boost_libraries(test_channel filesystem)
+asterisk_scf_component_add_boost_libraries(test_channel core thread)
+if(NOT logger_dir)
+   message(FATAL_ERROR "The logger directory could not be found ${logger_dir}")
+endif()
+include_directories(${logger_dir}/common)
+include_directories(${logger_dir}/client/src)
 asterisk_scf_component_build_icebox(test_channel)
+target_link_libraries(test_channel logging-client)
+pjproject_link(test_channel pjlib)
+pjproject_link(test_channel pjlib-util)
+pjproject_link(test_channel pjmedia)
+pjproject_link(test_channel pjnath)
 
 asterisk_scf_component_init(console_driver CXX)
 asterisk_scf_component_add_slice(console_driver CommandsIf)
@@ -21,3 +41,4 @@ asterisk_scf_component_add_file(console_driver ConsoleDriver.h)
 asterisk_scf_component_add_file(console_driver main.cpp)
 asterisk_scf_component_build_standalone(console_driver)
 
+
diff --git a/src/FilePlaybackService.h b/src/FilePlaybackService.h
new file mode 100644
index 0000000..dccfa29
--- /dev/null
+++ b/src/FilePlaybackService.h
@@ -0,0 +1,72 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+#pragma once
+
+#include <string>
+#include <Ice/Ice.h>
+
+//
+// Some service-wide definitions.
+//
+namespace AsteriskSCF
+{
+namespace System
+{
+namespace Logging
+{
+    class Logger;
+    //
+    // Temporary until the logger is a reference counted entity.
+    //
+    typedef Logger& LoggerPtr; 
+
+} // End of namespace Logging
+} // End of namespace System
+
+namespace FilePlayback
+{
+inline std::string getId(const Ice::Current& current)
+{
+    return current.adapter->getCommunicator()->identityToString(current.id);
+}
+}
+} // End of namespace AsteriskSCF
+
+template <class T>
+class IdentityComparePred : public std::unary_function<T, bool>
+{
+public:
+    IdentityComparePred(const T& example) :
+        mExample(example)
+    {
+    }
+
+    bool operator()(const T& toTest)
+    {
+        if(toTest == 0)
+        {
+            return (mExample == 0);
+        }
+        if(mExample == 0)
+        {
+            return false;
+        }
+        return (mExample->ice_getIdentity() == toTest->ice_getIdentity());
+    }
+private:
+    T mExample;
+};
+
diff --git a/src/FilePlaybackSessionEndpoint.cpp b/src/FilePlaybackSessionEndpoint.cpp
new file mode 100644
index 0000000..56f5c82
--- /dev/null
+++ b/src/FilePlaybackSessionEndpoint.cpp
@@ -0,0 +1,1213 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+#include "FilePlaybackSessionEndpoint.h"
+#include <System/Time/TimeIf.h>
+#include <IceUtil/UUID.h>
+#include <logger.h>
+
+#include <boost/filesystem.hpp>
+#include "ListenerManager.h"
+#include "MediaEchoThread.h"
+#include <RemoteControl.h>
+#include <IceUtil/Mutex.h>
+#include <IceUtil/Handle.h>
+#include <pjmedia.h>
+
+#include <fstream>
+#include <memory>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO:
+// * separate class implementations in this file into their own header/source files.
+// * rename the controller interface, its not really a controller but more like a filter/extension point
+//   kind of thing.
+// * finish implementing the "record" functionality provided by the sink.
+// * incorporate matroska catalogue file
+//   * it would be better to incorporate the matroska tools instead of the library directly as they've
+//     implemented loads of higher level stuff. There is another C++ library to look at, but the name
+//     escapes me at the moment.
+// * stereo to mono conversion
+// * real format handling
+//
+
+/**
+ * I usually eschew these using clauses, but the logger 
+ * log level descriptors would get awfully cumbersome if I 
+ * didn't do this.
+ */
+using namespace AsteriskSCF::System::Logging;
+
+namespace FBSPNS /* arbitrary ns name to deal with anonymous NS bugs */
+{
+
+static std::string getFilename(const Ice::CommunicatorPtr& communicator, const std::string& destination,
+  const AsteriskSCF::System::Logging::LoggerPtr& logger)
+{
+    boost::filesystem::path basePath = communicator->getProperties()->getProperty("MediaFiles");
+    basePath /= destination;
+    if (!boost::filesystem::exists(basePath))
+    {
+        logger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " non-existent file " << destination;
+        throw AsteriskSCF::RemoteControl::V1::FileNotFoundException(basePath.string());
+    }
+    return basePath.string();
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Default controller objects.
+//
+class DummySourceController : public AsteriskSCF::RemoteControl::V1::SourceController
+{
+public:
+    AsteriskSCF::Media::V1::FormatSeq filterFormats(const AsteriskSCF::Media::V1::FormatSeq& possibleFormats, 
+      const Ice::Current& current)
+    {
+        return possibleFormats;
+    }
+
+    bool allowFormatChange(const AsteriskSCF::Media::V1::FormatPtr& newFormat, const Ice::Current& current)
+    {
+        return true;
+    }
+};
+
+class DummySinkController : public AsteriskSCF::RemoteControl::V1::SinkController
+{
+public:
+    AsteriskSCF::Media::V1::FormatSeq filterFormats(const AsteriskSCF::Media::V1::FormatSeq& possibleFormats, 
+      const Ice::Current& current)
+    {
+        return possibleFormats;
+    }
+
+    bool allowWrite(const Ice::Current& current)
+    {
+        return true;
+    }
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// Media implementations for file playback and recording.
+//
+
+/**
+ * MatroskaMediaSource - a Media StreamSource object implementation.
+ * This source object is designed to be used along with a remote control
+ * object implemented elsewhere in this project to allow some extra
+ * control over the media information it produces. The remote control 
+ * interface is useful for test suites or interactive testing through
+ * a command line interface or GUI app. 
+ */
+class MatroskaMediaSource : public AsteriskSCF::Media::V1::StreamSource
+{
+public:
+    MatroskaMediaSource(const AsteriskSCF::System::Logging::LoggerPtr& logger,
+      const std::string& id, 
+      const AsteriskSCF::RemoteControl::V1::SourceControllerPrx& controller,
+      const std::string& defaultFile):
+        mId(id),
+        mController(controller),
+        mDefaultFilename(defaultFile),
+        mThread(new AsteriskSCF::TestUtil::MediaEchoThread),
+        mLogger(logger),
+        mStarted(false)
+    {
+    }
+      
+    //
+    // Media StreamSource interface implementations.
+    //
+    void setSink(const AsteriskSCF::Media::V1::StreamSinkPrx& destination, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        mSink = destination;
+        mThread->setSink(mSink);
+    }
+
+    AsteriskSCF::Media::V1::StreamSinkPrx getSink(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        return mSink;
+    }
+
+    AsteriskSCF::Media::V1::FormatSeq getFormats(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        //
+        // check our list of available formats and filter them through the controller.
+        //
+        if(mController)
+        {
+            return mController->filterFormats(mFormats);
+        }
+        return mFormats;
+    }
+
+    std::string getId(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        //
+        // The id is set at construction and is immutable, there is no need for
+        // a lock here.
+        //
+        return mId;
+    }
+
+    void requestFormat(const AsteriskSCF::Media::V1::FormatPtr& newFormat, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        //
+        // check to see if we can produce the format
+        // then ask the controller if we can switch
+        //
+        if(mController && !mController->allowFormatChange(newFormat))
+        {
+            throw AsteriskSCF::Media::V1::MediaFormatSwitchException();
+        }
+        mCurrentFormat = newFormat;
+    }
+
+    //
+    // Internal methods made available for remote control.
+    //
+
+    //
+    // Start the echo thread.
+    //
+    void startThread()
+    {
+        bool startThread = false;
+        {
+            IceUtil::Mutex::Lock lock(mMutex);
+            if(!mStarted)
+            {
+                mStarted = true;
+                startThread = true;
+            }
+        }
+        mThread->resume();
+        if(startThread)
+        {
+            mThread->start();
+        }
+    }
+
+    void pauseThread()
+    {
+        mThread->pause();
+    }
+
+    //
+    // Set the controller object.
+    //
+    void setController(const AsteriskSCF::RemoteControl::V1::SourceControllerPrx& controller)
+    {
+        IceUtil::Mutex::Lock lock(mMutex);
+        mController = controller;
+    }
+
+    //
+    // Get the list of the tracks that are available in this source.
+    //
+    Ice::StringSeq getTracks()
+    {
+        IceUtil::Mutex::Lock lock(mMutex);
+        return mTracks;
+    }
+
+    //
+    // Play the track by name.
+    //
+    void playTrack(const std::string& name, const Ice::Current& current)
+    {
+        if(!mSink)
+        {
+            return;
+        }
+        std::string filename = name;
+        if(name.size() == 0)
+        {
+            filename = mDefaultFilename;
+        }
+        filename = getFilename(current.adapter->getCommunicator(), filename, mLogger);
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            "loading file " << filename;
+
+        //
+        // Find the track, read it into our encoder, and push the frames to the echo thread.
+        //
+
+        // 
+        // The null arguments to this call basically encourage the sox library to 
+        // figure out info about the file on its own. We'll be able to provide
+        // this info by the matroska track info.
+        //
+        bool done = false;
+
+        std::ifstream input(filename, std::ios::binary | std::ios::in);
+        if(input.good())
+        {
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+                " file opened successfully " << filename;
+        }
+        else
+        {
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+                " file open failed " << filename;
+            return;
+        }
+        input.seekg(0);
+
+        //
+        // TODO: this and the media echo thread should be put into a separate file.
+        // 
+
+        pjmedia_wave_hdr waveHeader;
+        int rdSize = sizeof(waveHeader) -8;
+        input.get((char*)(&waveHeader), rdSize + 1);
+        if(!input.good() || input.gcount() != rdSize)
+        {
+            std::cout << "header read reported: " << input.gcount() << " was supposed to have read " << rdSize << std::endl;
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+                " unable to read the wav header.";
+            return;
+        }
+        pjmedia_wave_hdr_file_to_host(&waveHeader);
+        
+        if(waveHeader.riff_hdr.riff != PJMEDIA_RIFF_TAG ||
+          waveHeader.riff_hdr.wave != PJMEDIA_WAVE_TAG ||
+          waveHeader.fmt_hdr.fmt != PJMEDIA_FMT_TAG)
+        {
+            mLogger(Debug) << "Riff header " << waveHeader.riff_hdr.riff << " expected " << PJMEDIA_RIFF_TAG;
+            mLogger(Debug) << "Wave header " << waveHeader.riff_hdr.wave << " expected " << PJMEDIA_WAVE_TAG;
+            mLogger(Debug) << "Fmt header " << waveHeader.fmt_hdr.fmt << " expected " << PJMEDIA_FMT_TAG;
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' <<  __FUNCTION__ <<
+                " invalid file format for playback.";
+            return;
+        }
+
+        //
+        // TODO: expand to support other PCM file formats.
+        //
+        if(waveHeader.fmt_hdr.fmt_tag != PJMEDIA_WAVE_FMT_TAG_ULAW)
+        {
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+                " sorry, we are only support ulaw right now."; 
+            return;
+        }
+
+        pjmedia_wave_fmt_tag format = static_cast<pjmedia_wave_fmt_tag>(waveHeader.fmt_hdr.fmt_tag);
+        size_t bytesPerSample = waveHeader.fmt_hdr.bits_per_sample /8;
+
+        if(waveHeader.fmt_hdr.len > 16)
+        {
+            rdSize = waveHeader.fmt_hdr.len -16;
+            input.seekg(rdSize, std::ios_base::cur);
+            if(!input.good())
+            {
+                mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+                    " sorry, processing header.";
+                return;
+            }
+        }
+
+        //
+        // get to the actual audio data.
+        //
+        int readSize = 8;
+        while(true)
+        {
+            pjmedia_wave_subchunk subchunk;
+            input.get(reinterpret_cast<char*>(&subchunk), readSize +1);
+            if(input.gcount() != readSize || !input.good())
+            {
+                mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+                    " sorry, wave file was too short."; 
+                return;
+            }
+            PJMEDIA_WAVE_NORMALIZE_SUBCHUNK(&subchunk);
+            if(subchunk.id == PJMEDIA_DATA_TAG)
+            {
+                waveHeader.data_hdr.data = PJMEDIA_DATA_TAG;
+                waveHeader.data_hdr.len = subchunk.len;
+                break;
+            }
+            readSize = subchunk.len;
+            input.seekg(readSize, std::ios_base::cur);
+        }
+
+        //
+        // XXX keeping track of this for now, but we probably don't need it.
+        // 
+        size_t dataStart = input.tellg();
+
+        //
+        // TODO- more file validation.
+        //
+
+        if(waveHeader.fmt_hdr.nchan != 1)
+        {
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+                " sorry, we are only support mono right now.";
+            return;
+        }
+        size_t sampleRate = waveHeader.fmt_hdr.sample_rate;
+        size_t bitsPerSample = waveHeader.fmt_hdr.bits_per_sample;
+        //
+        // TODO: variable clock rates? Media echo thread is set at 8000Hz by default.
+        //
+        size_t samplesPerFrame =  20 * sampleRate / 1000;
+
+        size_t bufSize = samplesPerFrame * bitsPerSample / 8;
+        std::unique_ptr<char> buf(new char[bufSize]);
+        mLogger(Debug) <<  __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+            "Using a buffer size of " << bufSize << " bytes.";
+
+        size_t sequence = 0;
+        AsteriskSCF::Media::V1::FrameSeq frames;
+        AsteriskSCF::Media::V1::AudioFormatPtr mediaFormat(new AsteriskSCF::Media::V1::AudioFormat);
+        mediaFormat->name = "ulaw";
+        mediaFormat->sampleRate = sampleRate;
+        mediaFormat->frameSize = 20;
+        mediaFormat->maximumFrameSize = 20;
+        mediaFormat->minimumFrameSize = 20;
+
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ <<
+            "Format: " << format <<
+            ", Bytes per sample: " << bytesPerSample <<
+            ", Bits per sample: " << bitsPerSample <<
+            ", Data start: " << dataStart <<
+            ", Sample rate: " << sampleRate;
+
+        while(true)
+        {
+            input.get(buf.get(), bufSize +1);
+            size_t bytesRead = input.gcount();
+
+            if(bytesRead == 0)
+            {
+                done = true;
+                break;
+            }
+            //
+            // for the moment we are going to assume that the output is ulaw.
+            //
+            AsteriskSCF::Media::V1::AudioFramePtr frame = new AsteriskSCF::Media::V1::AudioFrame;
+            frame->mediaformat = mediaFormat;
+            frame->seqno = sequence++;
+            frame->timestamp = samplesPerFrame;
+            frame->payload.assign(buf.get(), buf.get()+bytesRead);
+            frames.push_back(frame);
+        }
+        mThread->pushFrames(frames);
+    }
+
+    void destroyImpl()
+    {
+        mThread->destroy();
+    }
+
+private:
+
+    // Lock for the internal state.
+    IceUtil::Mutex mMutex;
+
+    // Id string, required for the StreamSource interface implementation.
+    std::string mId;
+
+    // Remote control object to modify behavior.
+    AsteriskSCF::RemoteControl::V1::SourceControllerPrx mController;
+
+    // Default file name or track.
+    const std::string mDefaultFilename;
+
+    // The media sink that will receive the data from the source.
+    AsteriskSCF::Media::V1::StreamSinkPrx mSink;
+
+    // The formats supported by the source.
+    AsteriskSCF::Media::V1::FormatSeq mFormats;
+
+    // Background thread for taking data from the source and
+    // writing the sink at the prescribed rate.
+    AsteriskSCF::TestUtil::MediaEchoThreadPtr mThread;
+
+    // Asterisk SCF logger object.
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+
+    // The names of the tracks in this source, in string form.
+    Ice::StringSeq mTracks;
+
+    // The current format we are supposed to be using.
+    AsteriskSCF::Media::V1::FormatPtr mCurrentFormat;
+
+    // Flag for whether the media echo thread has been started or not.
+    bool mStarted;
+};
+typedef IceUtil::Handle<MatroskaMediaSource> MatroskaMediaSourcePtr;
+
+/**
+ * This servant is meant to act in tandem with a Media source implementation. 
+ * it forward calls from the source remote control interfaces to a media source
+ * implemented by this service. 
+ **/
+class MatroskaSourceRemoteControl : public AsteriskSCF::RemoteControl::V1::Source
+{
+public:
+    MatroskaSourceRemoteControl(const MatroskaMediaSourcePtr& source,
+      const AsteriskSCF::System::Logging::LoggerPtr& logger) :
+        mLogger(logger),
+        mSource(source)
+    {
+    }
+    
+    void start(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        mSource->startThread();
+    }
+
+    void pause(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        mSource->pauseThread();
+    }
+    
+    void setController(const AsteriskSCF::RemoteControl::V1::SourceControllerPrx& controller, 
+      const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        mSource->setController(controller);
+    }
+
+    Ice::StringSeq listTracks(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        return mSource->getTracks();
+    }
+
+    void playTrack(const std::string& trackName, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        mSource->playTrack(trackName, current);
+    }
+
+    AsteriskSCF::Media::V1::StreamSinkPrx getTrackInput(const std::string& trackName, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        return 0; // XXX 
+    }
+
+    //
+    // there isn't too much to do here except remove the reference to the relevant source object.
+    //
+    void destroy(const Ice::Current& current)
+    {
+        mSource->destroyImpl();
+        mSource = 0;
+    }
+private:
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+    MatroskaMediaSourcePtr mSource;
+};
+typedef IceUtil::Handle<MatroskaSourceRemoteControl> MatroskaSourceRemoteControlPtr;
+
+/**
+ * MatroskaMediaSink - a Media StreamSink object implementation.
+ * This sink implementation's primary purpose is to act as a cache for
+ * frames written to a media session hosted by this service. The 
+ * cached frames can be "copied" out to a Remote control source object.
+ */
+class MatroskaMediaSink : public AsteriskSCF::Media::V1::StreamSink
+{
+public:
+    MatroskaMediaSink(const AsteriskSCF::System::Logging::LoggerPtr& logger, const std::string& id, 
+      const AsteriskSCF::RemoteControl::V1::SinkControllerPrx& controller) :
+        mLogger(logger),
+        mId(id),
+        mController(controller)
+    {
+    }
+
+    void write(const AsteriskSCF::Media::V1::FrameSeq& frames, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        mReceivedFrames.insert(mReceivedFrames.end(), frames.begin(), frames.end());
+    }
+
+    void setSource(const AsteriskSCF::Media::V1::StreamSourcePrx& source, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        mSource = source;
+    }
+
+    AsteriskSCF::Media::V1::StreamSourcePrx getSource(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        return mSource;
+    }
+
+    AsteriskSCF::Media::V1::FormatSeq getFormats(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        return mFormats;
+    }
+
+    std::string getId(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << 
+            " for " << AsteriskSCF::FilePlayback::getId(current);
+        return mId;
+    }
+
+
+private:
+
+    // Lock for the internal state.
+    IceUtil::Mutex mMutex;
+
+    // Logger object.
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+
+    // Id string, required for the StreamSink interface implementation.
+    std::string mId;
+
+    // Remote control object to modify behavior. 
+    AsteriskSCF::RemoteControl::V1::SinkControllerPrx mController;
+
+    // The media source the sink accepts media from.
+    AsteriskSCF::Media::V1::StreamSourcePrx mSource;
+
+    // The formats supported by the sinks.
+    AsteriskSCF::Media::V1::FormatSeq mFormats;
+
+    // The frame cache received by the sink
+    AsteriskSCF::Media::V1::FrameSeq mReceivedFrames;
+};
+typedef IceUtil::Handle<MatroskaMediaSink> MatroskaMediaSinkPtr;
+
+class MatroskaSinkRemoteControl : public AsteriskSCF::RemoteControl::V1::Sink
+{
+public:
+    MatroskaSinkRemoteControl(const MatroskaMediaSinkPtr& sink, const AsteriskSCF::System::Logging::LoggerPtr& logger) :
+        mSink(sink),
+        mLogger(logger)
+    {
+    }
+
+    void setController(const AsteriskSCF::RemoteControl::V1::SinkControllerPrx& controller, const Ice::Current& current)
+    {
+    }
+
+    void startRecording(const Ice::Current& current)
+    {
+    }
+
+    void stopRecording(const Ice::Current& current)
+    {
+    }
+
+    void copyToSink(const AsteriskSCF::Media::V1::StreamSinkPrx& destinationSink, const Ice::Current& current)
+    {
+    }
+    
+    void reset(const Ice::Current& current)
+    {
+    }
+
+    void destroy(const Ice::Current& current)
+    {
+        mSink = 0;
+    }
+
+private:
+    MatroskaMediaSinkPtr mSink;
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+};
+typedef IceUtil::Handle<MatroskaSinkRemoteControl> MatroskaSinkRemoteControlPtr;
+
+class MatroskaMediaSession : public AsteriskSCF::Media::V1::Session
+{
+public:
+    MatroskaMediaSession(const Ice::ObjectAdapterPtr& adapter, 
+      const AsteriskSCF::System::Logging::LoggerPtr& logger,
+      const std::string& file, 
+      const std::string& id) :
+        mAdapter(adapter),
+        mLogger(logger),
+        mFilename(file),
+        mId(id),
+        mDestroyed(false)
+    {
+    }
+
+    ~MatroskaMediaSession()
+    {
+        destroyImpl();
+    }
+
+    void initialize()
+    {
+        mSink = new MatroskaMediaSink(mLogger, mId + ".Sink", 0);
+        mSinkRemoteControl = new MatroskaSinkRemoteControl(mSink, mLogger);
+        mSource = new MatroskaMediaSource(mLogger, mId + ".Source", 0, mFilename);
+        mSourceRemoteControl = new MatroskaSourceRemoteControl(mSource, mLogger);
+
+        Ice::CommunicatorPtr comm = mAdapter->getCommunicator();
+        Ice::Identity id = comm->stringToIdentity(mId + ".Sink");
+        mSinkPrx = AsteriskSCF::Media::V1::StreamSinkPrx::uncheckedCast(mAdapter->add(mSink, id));
+        mSinkControlPrx = AsteriskSCF::RemoteControl::V1::SinkPrx::uncheckedCast(
+          mAdapter->addFacet(mSinkRemoteControl, id, "Control"));
+        id = comm->stringToIdentity(mId + ".Source");
+        mSourcePrx = AsteriskSCF::Media::V1::StreamSourcePrx::uncheckedCast(mAdapter->add(mSource, id));
+        mSourceControlPrx = AsteriskSCF::RemoteControl::V1::SourcePrx::uncheckedCast(
+          mAdapter->addFacet(mSourceRemoteControl, id, "Control"));
+    }
+
+    void destroyImpl()
+    {
+        {
+            IceUtil::Mutex::Lock lock(mMutex);
+            if(mDestroyed)
+            {
+                return;
+            }
+            mDestroyed = true;
+        }
+        try
+        {
+            mSinkControlPrx->destroy();
+            mSourceControlPrx->destroy();
+            mAdapter->remove(mSinkPrx->ice_getIdentity());
+            mAdapter->remove(mSourcePrx->ice_getIdentity());
+        }
+        catch(const Ice::Exception& ex)
+        {
+            mLogger(Debug) << __FILE__ << ':' << __LINE__ << '(' << __FUNCTION__ << ')' <<
+                ex.what();
+        }
+    }
+
+    AsteriskSCF::Media::V1::StreamSourceSeq getSources(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        if(mDestroyed)
+        {
+            throw Ice::ObjectNotExistException(__FILE__, __LINE__);
+        }
+        AsteriskSCF::Media::V1::StreamSourceSeq result;
+        result.push_back(mSourcePrx);
+        return result;
+    }
+
+    AsteriskSCF::Media::V1::StreamSinkSeq getSinks(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        IceUtil::Mutex::Lock lock(mMutex);
+        if(mDestroyed)
+        {
+            throw Ice::ObjectNotExistException(__FILE__, __LINE__);
+        }
+        AsteriskSCF::Media::V1::StreamSinkSeq result;
+        result.push_back(mSinkPrx);
+        return result;
+    }
+
+    std::string getId(const Ice::Current& current)
+    {
+        IceUtil::Mutex::Lock lock(mMutex);
+        if(mDestroyed)
+        {
+            throw Ice::ObjectNotExistException(__FILE__, __LINE__);
+        }
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " 
+            << AsteriskSCF::FilePlayback::getId(current);
+        return mId;
+    }
+
+private:
+    IceUtil::Mutex mMutex;
+    Ice::ObjectAdapterPtr mAdapter;
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+    const std::string mFilename;
+    const std::string mId;
+    MatroskaMediaSinkPtr mSink;
+    MatroskaSinkRemoteControlPtr mSinkRemoteControl;
+    AsteriskSCF::Media::V1::StreamSinkPrx mSinkPrx;
+    AsteriskSCF::RemoteControl::V1::SinkPrx mSinkControlPrx;
+
+    MatroskaMediaSourcePtr mSource;
+    MatroskaSourceRemoteControlPtr mSourceRemoteControl;
+    AsteriskSCF::Media::V1::StreamSourcePrx mSourcePrx;
+    AsteriskSCF::RemoteControl::V1::SourcePrx mSourceControlPrx;
+    bool mDestroyed;
+};
+typedef IceUtil::Handle<MatroskaMediaSession> MatroskaMediaSessionPtr;
+
+typedef AsteriskSCF::TestUtil::ListenerManagerT<AsteriskSCF::SessionCommunications::V1::SessionListenerPrx> 
+    SessionListenerMgr;
+
+class FileSession : virtual public AsteriskSCF::SessionCommunications::V1::Session, 
+    virtual public SessionListenerMgr
+{
+public:
+    FileSession(const Ice::ObjectAdapterPtr& adapter, const std::string& id, const std::string& name, 
+      const AsteriskSCF::System::Logging::LoggerPtr& logger) : 
+        SessionListenerMgr(adapter->getCommunicator(), id, true),
+        mAdapter(adapter),
+        mId(id),
+        mName(name),
+        mLogger(logger),
+        mDestroyed(false),
+        mMediaSession(new MatroskaMediaSession(adapter, logger, mName, id + ".Media")),
+        mInfo(new AsteriskSCF::SessionCommunications::V1::SessionInfo),
+        mStartTime(static_cast<time_t>(0))
+    {
+        mInfo->role = "callee";
+    }
+
+    void publishProxy(const AsteriskSCF::SessionCommunications::V1::SessionPrx& prx)
+    {
+        mProxy = prx;
+    }
+    
+    void endpointProxy(const AsteriskSCF::SessionCommunications::V1::SessionEndpointPrx& prx)
+    {
+        mInfo->caller = prx;
+        mInfo->destination = prx;
+    }
+
+    AsteriskSCF::SessionCommunications::V1::SessionInfoPtr addListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, 
+      const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        addListenerImpl(listener);
+        return updateInfo();
+    }
+
+    void addListenerImpl(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener)
+    {
+        SessionListenerMgr::addListener(listener);
+    }
+
+    void removeListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, 
+      const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        SessionListenerMgr::removeListener(listener);
+    }
+
+    void connect(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        mStartTime = time(0);
+        mPublisher->connected(mProxy);
+    }
+
+    void flash(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+    }
+
+    AsteriskSCF::SessionCommunications::V1::SessionEndpointPrx getEndpoint(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        return mInfo->caller;
+    }
+
+    AsteriskSCF::SessionCommunications::V1::SessionInfoPtr getInfo(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        return updateInfo();
+    }
+
+    AsteriskSCF::Media::V1::SessionPrx getMediaSession(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        return mMediaPrx;
+    }
+
+    void hold(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        mPublisher->held(mProxy);
+    }
+
+    void progress(const AsteriskSCF::SessionCommunications::V1::ResponseCodePtr& responseCode, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+    }
+
+    void ring(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        mPublisher->ringing(mProxy);
+    }
+
+    void start(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        //
+        // Initialize the media sessions now or later? I think I will create the media sessions when I create
+        // the file session but do something a little different once the session is started.
+        // 
+        mPublisher->connected(mProxy);
+    }
+
+    void stop(const AsteriskSCF::SessionCommunications::V1::ResponseCodePtr& responseCode, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        mPublisher->stopped(mProxy, responseCode);
+        mMediaSession->destroyImpl();
+    }
+
+    void unhold(const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+    }
+
+    AsteriskSCF::SessionCommunications::V1::BridgePrx getBridge(const Ice::Current& current) 
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        return mBridge;
+    }
+
+    AsteriskSCF::SessionCommunications::V1::SessionInfoPtr setBridge(const AsteriskSCF::SessionCommunications::V1::BridgePrx& bridge, 
+      const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        SessionListenerMgr::addListener(listener);
+        mBridge = bridge;
+        return updateInfo();
+    }
+
+    void removeBridge(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current)
+    {
+        mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+        SessionListenerMgr::removeListener(listener);
+        mBridge = 0;
+    }
+
+    void initialize()
+    {
+        mMediaSession->initialize();
+        mMediaPrx = AsteriskSCF::Media::V1::SessionPrx::uncheckedCast(mAdapter->add(mMediaSession,
+              mAdapter->getCommunicator()->stringToIdentity(mId + ".Media")));
+    }
+
+    void destroy()
+    {
+        mMediaSession->destroyImpl();
+    }
+
+    bool destroyed() const
+    {
+        IceUtil::Mutex::Lock lock(mMutex);
+        return mDestroyed;
+    }
+
+    AsteriskSCF::SessionCommunications::V1::SessionInfoPtr updateInfo()
+    {
+        IceUtil::Mutex::Lock lock(mMutex);
+        AsteriskSCF::SessionCommunications::V1::SessionInfoPtr result(
+            AsteriskSCF::SessionCommunications::V1::SessionInfoPtr::dynamicCast(mInfo->ice_clone()));
+        mInfo->connectedTime = (unsigned long)time(0) - (unsigned long)mStartTime;
+        mInfo->startTime = new AsteriskSCF::System::Time::V1::TimeMarker((long)mStartTime, 1.0);
+        return result;
+    }
+
+private:
+    IceUtil::Mutex mMutex;
+    Ice::ObjectAdapterPtr mAdapter;
+    std::string mId;
+    std::string mName;
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+    bool mDestroyed;
+    AsteriskSCF::SessionCommunications::V1::SessionPrx mProxy;
+    AsteriskSCF::SessionCommunications::V1::BridgePrx mBridge;
+    MatroskaMediaSessionPtr mMediaSession;
+    AsteriskSCF::Media::V1::SessionPrx mMediaPrx;
+    AsteriskSCF::SessionCommunications::V1::SessionInfoPtr mInfo;
+    time_t mStartTime;
+};
+
+typedef IceUtil::Handle<FileSession> FileSessionPtr;
+
+struct FileSessionInfo
+{
+    FileSessionPtr servant;
+    AsteriskSCF::SessionCommunications::V1::SessionPrx proxy;
+};
+
+AsteriskSCF::SessionCommunications::V1::SessionPrx getProxies(const FileSessionInfo& info)
+{
+    return info.proxy;
+}
+
+}
+using namespace FBSPNS;
+
+namespace AsteriskSCF
+{
+namespace FilePlayback
+{
+class FilePlaybackEndpointImpl
+{
+public:
+    FilePlaybackEndpointImpl(const Ice::ObjectAdapterPtr& adapter, const AsteriskSCF::System::Logging::LoggerPtr& logger);
+    AsteriskSCF::SessionCommunications::V1::SessionPrx createSession(const std::string& destination, 
+      const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current&);
+    AsteriskSCF::SessionCommunications::V1::SessionSeq getSessions(const Ice::Current&);
+    void addDefaultSessionListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current);
+    void removeDefaultSessionListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current);
+private:
+
+    AsteriskSCF::System::Logging::LoggerPtr mLogger;
+    Ice::ObjectAdapterPtr mAdapter;
+    std::vector<FileSessionInfo> mSessions;
+    std::vector<AsteriskSCF::SessionCommunications::V1::SessionListenerPrx> mDefaultListeners;
+    IceUtil::Mutex mMutex;
+
+    void reap();
+};
+}
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// FilePlaybackEndpointImpl
+//
+AsteriskSCF::FilePlayback::FilePlaybackEndpointImpl::FilePlaybackEndpointImpl(const Ice::ObjectAdapterPtr& adapter,
+  const AsteriskSCF::System::Logging::LoggerPtr& logger):
+    mLogger(logger),
+    mAdapter(adapter)
+{
+}
+
+static bool
+nonIdChar(const char c)
+{
+    return !isalnum(c);
+}
+
+static std::string
+pathToId(const std::string& path)
+{
+    std::string result = path;
+    std::replace_if(result.begin(), result.end(), nonIdChar, '.');
+    return result;
+}
+
+AsteriskSCF::SessionCommunications::V1::SessionPrx AsteriskSCF::FilePlayback::FilePlaybackEndpointImpl::createSession(
+  const std::string& destination, const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, 
+  const Ice::Current& current)
+{
+    std::string myId = AsteriskSCF::FilePlayback::getId(current);
+    mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << myId;
+    std::string file;
+    try
+    {
+        file = getFilename(current.adapter->getCommunicator(), destination, mLogger);
+    }
+    catch(const AsteriskSCF::RemoteControl::V1::FileNotFoundException&)
+    {
+        throw AsteriskSCF::SessionCommunications::V1::SessionCreationException();
+    }
+    myId += '.';
+    myId += destination;
+    myId = pathToId(myId);
+    myId += '.';
+    myId += IceUtil::generateUUID();
+
+    FileSessionPtr newSession(new FileSession(mAdapter, myId, file, mLogger));
+    //
+    // The only place that is liable to throw after newSession is created is
+    // the addition to the object adapter. If that occurs, the adapter will not
+    // have a reference and the servant will be deleted when newSession goes
+    // out of scope.
+    //
+    try
+    {
+        //
+        // Order of calls is a little important here. "initialize()" is called
+        // on the new session before anything else as a kind of "check" to make
+        // sure nothing in the initialization of the session depends on the
+        // session being activated. That being said, calling initialize before
+        // adding it means that the objects are all ready to start receiving
+        // requests.
+        //
+        newSession->initialize();
+        for(std::vector<AsteriskSCF::SessionCommunications::V1::SessionListenerPrx>::const_iterator i = mDefaultListeners.begin();
+          i != mDefaultListeners.end(); ++i)
+        {
+            newSession->addListenerImpl(*i);
+        }
+        AsteriskSCF::SessionCommunications::V1::SessionPrx prx(
+          AsteriskSCF::SessionCommunications::V1::SessionPrx::uncheckedCast(
+              mAdapter->add(newSession, mAdapter->getCommunicator()->stringToIdentity(myId))));
+        newSession->publishProxy(prx);
+        if(listener)
+        {
+            newSession->addListenerImpl(listener);
+        }
+        FileSessionInfo info;
+        info.servant = newSession;
+        info.proxy = prx;
+
+        IceUtil::Mutex::Lock lock(mMutex);
+        mSessions.push_back(info);
+        return prx;
+    }
+    catch (const Ice::Exception& ex)
+    {
+        mLogger(Error) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " exception caught " << ex.what();
+        throw AsteriskSCF::SessionCommunications::V1::SessionCreationException(destination);
+    }
+}
+
+AsteriskSCF::SessionCommunications::V1::SessionSeq AsteriskSCF::FilePlayback::FilePlaybackEndpointImpl::getSessions(
+  const Ice::Current& current)
+{
+    mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+    IceUtil::Mutex::Lock lock(mMutex);
+    reap();
+    AsteriskSCF::SessionCommunications::V1::SessionSeq result(mSessions.size());
+    std::transform(mSessions.begin(), mSessions.end(), result.begin(), getProxies);
+    return result;
+}
+
+void AsteriskSCF::FilePlayback::FilePlaybackEndpointImpl::addDefaultSessionListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current)
+{
+    mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+    if(!listener)
+    {
+        mLogger(Debug) << __FUNCTION__ << ":" << 
+            current.adapter->getCommunicator()->identityToString(current.id) <<  " attempting to add a null proxy";
+        throw AsteriskSCF::SessionCommunications::V1::NullProxyException();
+    }
+    IceUtil::Mutex::Lock lock(mMutex);
+    if(mDefaultListeners.end() == std::find_if(mDefaultListeners.begin(), mDefaultListeners.end(), 
+          IdentityComparePred<AsteriskSCF::SessionCommunications::V1::SessionListenerPrx>(listener)))
+    {
+        mDefaultListeners.push_back(listener);
+    }
+}
+
+void AsteriskSCF::FilePlayback::FilePlaybackEndpointImpl::removeDefaultSessionListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current)
+{
+    mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " for " << AsteriskSCF::FilePlayback::getId(current);
+    std::cerr << __FUNCTION__ << ":" << current.adapter->getCommunicator()->identityToString(current.id) << std::endl;
+    if(!listener)
+    {
+        mLogger(Debug) << __FUNCTION__ << ":" << current.adapter->getCommunicator()->identityToString(current.id) 
+            <<  " attempting to remove a null proxy";
+        throw AsteriskSCF::SessionCommunications::V1::NullProxyException();
+    }
+    IceUtil::Mutex::Lock lock(mMutex);
+    std::remove_if(mDefaultListeners.begin(), mDefaultListeners.end(), 
+      IdentityComparePred<AsteriskSCF::SessionCommunications::V1::SessionListenerPrx>(listener)); 
+}
+
+void AsteriskSCF::FilePlayback::FilePlaybackEndpointImpl::reap()
+{
+    mLogger(Debug) << __FILE__ << ':' << __LINE__ << ' ' << __FUNCTION__ << " reaping.";
+    std::vector<FileSessionInfo> remainingSessions;
+    for (std::vector<FileSessionInfo>::iterator i = mSessions.begin(); i != mSessions.end(); ++i)
+    {
+        if (!i->servant->destroyed())
+        {
+            remainingSessions.push_back(*i);
+        }
+        else
+        {
+            i->servant = 0;
+            mAdapter->remove(i->proxy->ice_getIdentity());
+        }
+    }
+    mSessions = remainingSessions;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// FilePlaybackEndpoint
+//
+AsteriskSCF::FilePlayback::FilePlaybackEndpoint::FilePlaybackEndpoint(const Ice::ObjectAdapterPtr& adapter, 
+  const AsteriskSCF::System::Logging::LoggerPtr& logger) :
+    mImpl(new FilePlaybackEndpointImpl(adapter, logger)),
+    mId("fileplayback")
+{
+}
+
+AsteriskSCF::FilePlayback::FilePlaybackEndpoint::~FilePlaybackEndpoint()
+{
+    delete mImpl;
+}
+
+AsteriskSCF::SessionCommunications::V1::SessionPrx AsteriskSCF::FilePlayback::FilePlaybackEndpoint::createSession(const std::string& destination, 
+  const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current)
+{
+    return mImpl->createSession(destination, listener, current);
+}
+
+AsteriskSCF::SessionCommunications::V1::SessionSeq AsteriskSCF::FilePlayback::FilePlaybackEndpoint::getSessions(const Ice::Current& current)
+{
+    return mImpl->getSessions(current);
+}
+
+std::string AsteriskSCF::FilePlayback::FilePlaybackEndpoint::getId(const Ice::Current&)
+{
+    return mId;
+}
+
+void AsteriskSCF::FilePlayback::FilePlaybackEndpoint::addDefaultSessionListener(
+  const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener,
+  const Ice::Current& current)
+{
+    mImpl->addDefaultSessionListener(listener, current);
+}
+
+void AsteriskSCF::FilePlayback::FilePlaybackEndpoint::removeDefaultSessionListener(
+  const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener,
+  const Ice::Current& current)
+{
+    mImpl->removeDefaultSessionListener(listener, current);
+}
diff --git a/src/FilePlaybackSessionEndpoint.h b/src/FilePlaybackSessionEndpoint.h
new file mode 100644
index 0000000..b41c404
--- /dev/null
+++ b/src/FilePlaybackSessionEndpoint.h
@@ -0,0 +1,49 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#pragma once
+
+#include "FilePlaybackService.h"
+#include <SessionCommunications/SessionCommunicationsIf.h>
+#include <RemoteControl.h>
+#include <string>
+#include <Ice/Ice.h>
+
+namespace AsteriskSCF
+{
+namespace FilePlayback
+{
+    class FilePlaybackEndpointImpl;
+
+    class FilePlaybackEndpoint : public AsteriskSCF::RemoteControl::V1::SessionFactory
+    {
+    public:
+        FilePlaybackEndpoint(const Ice::ObjectAdapterPtr& adapter, const AsteriskSCF::System::Logging::LoggerPtr&);
+        ~FilePlaybackEndpoint();
+
+        AsteriskSCF::SessionCommunications::V1::SessionPrx createSession(const std::string& destination, 
+          const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& callback, const Ice::Current&);
+        AsteriskSCF::SessionCommunications::V1::SessionSeq getSessions(const Ice::Current&);
+        std::string getId(const Ice::Current&);
+
+        void addDefaultSessionListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current);
+        void removeDefaultSessionListener(const AsteriskSCF::SessionCommunications::V1::SessionListenerPrx& listener, const Ice::Current& current);
+    private:
+        FilePlaybackEndpointImpl* mImpl;
+        std::string mId;
+    };
+} // End of namespace FileplayBack
+} // End of namespace AsteriskSCF
diff --git a/src/ListenerManager.h b/src/ListenerManager.h
index 17f3932..8182b9a 100644
--- a/src/ListenerManager.h
+++ b/src/ListenerManager.h
@@ -10,10 +10,12 @@
 #include <Ice/Ice.h>
 #include <IceStorm/IceStorm.h>
 #include <boost/thread/shared_mutex.hpp>
+#include <boost/thread/locks.hpp>
 #include <string>
 #include <algorithm>
 #include <vector>
 #include "InternalExceptions.h"
+#include <IceUtil/Thread.h>
 
 namespace AsteriskSCF
 {
@@ -23,66 +25,64 @@ namespace TestUtil
 // Helper template for classes that need to implement listener style interfaces.
 //
 template <class T>
-class ListenerManagerT : public IceUtil::Shared
+class ListenerManagerT 
 {
     typedef std::vector<T> ListenerSeq;
     typename std::vector<T>::iterator ListenerIter;
-public:
-    ListenerManagerT(const Ice::CommunicatorPtr& communicator, const std::string& topicName) :
-        mCommunicator(communicator),
-        mTopicName(topicName)
+
+    class InitializationThread : public IceUtil::Thread
     {
-        //
-        // TODO: While this is being concocted for a single component, it would make more sense
-        // to have the topic manager passed in during construction for more general usage.
-        //
-        const std::string propertyName = "TopicManager.Proxy";
-        std::string topicManagerProperty = mCommunicator->getProperties()->getProperty(propertyName);
-        if(topicManagerProperty.size() == 0)
+    public:
+        InitializationThread(ListenerManagerT* mgr) :
+            mMgr(mgr),
+            mStopped(false)
         {
-            throw ConfigException(propertyName, "Topic manager proxy property missing. "
-                "Unable to initialize listener support.");
         }
 
-        try
-        {
-            mTopicManager = IceStorm::TopicManagerPrx::checkedCast(mCommunicator->stringToProxy(topicManagerProperty));
-        }
-        catch(const Ice::Exception&)
+        void run()
         {
+            bool initialized = false;
+            IceUtil::Monitor<IceUtil::Mutex>::Lock lock(mMonitor);
+            while(!mStopped && !initialized)
+            {
+                //
+                // TODO: Make configurable.
+                //
+                mMonitor.timedWait(IceUtil::Time::seconds(15));
+                initialized = mMgr->init();
+            }
         }
-        if(!mTopicManager)
+
+        void stop()
         {
-            throw ConfigException(propertyName, "Topic manager proxy is not a valid proxy or is unreachable");
+            IceUtil::Monitor<IceUtil::Mutex>::Lock lock(mMonitor);
+            mStopped = true;
+            mMonitor.notify();
         }
 
+    private:
+        IceUtil::Monitor<IceUtil::Mutex> mMonitor;
+        ListenerManagerT* mMgr;
+        bool mStopped;
+    };
+
+public:
+    ListenerManagerT(const Ice::CommunicatorPtr& communicator, const std::string& topicName, bool enableBackgroundInit) :
+        mCommunicator(communicator),
+        mTopicName(topicName),
+        mInitialized(false)
+    {
         try
         {
-            mTopic = mTopicManager->retrieve(mTopicName);
+            init();
         }
-        catch(const IceStorm::NoSuchTopic&)
+        catch(const Ice::Exception&)
         {
         }
-
-        if(!mTopic)
+        if(!mInitialized && enableBackgroundInit)
         {
-            try
-            {
-                mTopic = mTopicManager->create(mTopicName);
-            }
-            catch(const IceStorm::TopicExists&)
-            {
-                //
-                // In case there is a race condition when creating the topic.
-                //
-                mTopic = mTopicManager->retrieve(mTopicName);
-            }
-        }
-
-        if(!mTopic)
-        {
-            throw ConfigException(propertyName,
-                std::string("unable to create topic with the provided configuration :") + mTopicName);
+            mInitThread = new InitializationThread(this);
+            mInitThread->start();
         }
     }
 
@@ -101,6 +101,9 @@ public:
                 //
             }
         }
+        ///
+        // TODO: join on initialization thread for destruction.
+        //
     }
 
     //
@@ -114,17 +117,22 @@ public:
         {
             mListeners.push_back(listener);
         }
-        IceStorm::QoS qos;
-        qos["reliability"] = "ordered";
-        try
-        {
-            mTopic->subscribeAndGetPublisher(qos, listener);
-        }
-        catch(const IceStorm::AlreadySubscribed&)
+
+        if(mInitialized)
         {
-            //
-            // This indicates some kind of inconsistent state.
-            //
+            IceStorm::QoS qos;
+            qos["reliability"] = "ordered";
+
+            try
+            {
+                mTopic->subscribeAndGetPublisher(qos, listener);
+            }
+            catch(const IceStorm::AlreadySubscribed&)
+            {
+                //
+                // This indicates some kind of inconsistent state.
+                //
+            }
         }
     }
 
@@ -135,7 +143,10 @@ public:
         if(i != mListeners.end())
         {
             mListeners.erase(i);
-            mTopic->unsubscribe(listener);
+            if(mInitialized)
+            {
+                mTopic->unsubscribe(listener);
+            }
         }
     }
 
@@ -146,13 +157,112 @@ public:
         return result;
     }
 
+    bool isSuspended()
+    {
+        boost::shared_lock<boost::shared_mutex> lock(mLock);
+        return !mInitialized;
+    }
+
+    void stop()
+    {
+        if(mInitThread)
+        {
+            mInitThread->stop();
+        }
+    }
+
 protected:
     boost::shared_mutex mLock;
     Ice::CommunicatorPtr mCommunicator;
     std::string mTopicName;
     IceStorm::TopicPrx mTopic;
     IceStorm::TopicManagerPrx mTopicManager;
+    T mPublisher;
     ListenerSeq mListeners;
+    IceUtil::Handle<InitializationThread> mInitThread;
+
+    bool mInitialized;
+
+    bool init()
+    {
+        boost::shared_lock<boost::shared_mutex> lock(mLock);
+        if(mInitialized)
+        {
+            return mInitialized;
+        }
+
+        //
+        // TODO: While this is being concocted for a single component, it would make more sense
+        // to have the topic manager passed in during construction for more general usage.
+        //
+        const std::string propertyName = "TopicManager.Proxy";
+        std::string topicManagerProperty = mCommunicator->getProperties()->getProperty(propertyName);
+        if(topicManagerProperty.size() == 0)
+        {
+            throw ConfigException(propertyName, "Topic manager proxy property missing. "
+                "Unable to initialize listener support.");
+        }
+
+        try
+        {
+            mTopicManager = IceStorm::TopicManagerPrx::checkedCast(mCommunicator->stringToProxy(topicManagerProperty));
+        }
+        catch(const Ice::Exception&)
+        {
+            return false;
+        }
+
+        try
+        {
+            mTopic = mTopicManager->retrieve(mTopicName);
+        }
+        catch(const IceStorm::NoSuchTopic&)
+        {
+        }
+
+        if(!mTopic)
+        {
+            try
+            {
+                mTopic = mTopicManager->create(mTopicName);
+            }
+            catch(const IceStorm::TopicExists&)
+            {
+                //
+                // In case there is a race condition when creating the topic.
+                //
+                mTopic = mTopicManager->retrieve(mTopicName);
+            }
+        }
+
+        if(!mTopic)
+        {
+            return mInitialized;
+        }
+        mPublisher = T::uncheckedCast(mTopic->getPublisher());
+
+        if(mListeners.size() > 0)
+        {
+            for(typename std::vector<T>::iterator i = mListeners.begin(); i != mListeners.end(); ++i)
+            {
+                IceStorm::QoS qos;
+                qos["reliability"] = "ordered";
+
+                try
+                {
+                    mTopic->subscribeAndGetPublisher(qos, *i);
+                }
+                catch(const IceStorm::AlreadySubscribed&)
+                {
+                    //
+                    // This indicates some kind of inconsistent state.
+                    //
+                }
+            }
+        }
+        mInitialized = true;
+        return mInitialized;
+    }
 };
 }
 }
diff --git a/src/MediaEchoThread.cpp b/src/MediaEchoThread.cpp
index c9eccf1..dcd2ee7 100644
--- a/src/MediaEchoThread.cpp
+++ b/src/MediaEchoThread.cpp
@@ -19,37 +19,94 @@ using namespace AsteriskSCF::TestUtil;
 
 MediaEchoThread::MediaEchoThread():
     mDone(false),
-    mPaused(true)
+    mPaused(true),
+    mInterval(IceUtil::Time::microSecondsDouble(1000000.0 / (8 * 1000)))
+{
+}
+
+MediaEchoThread::MediaEchoThread(const unsigned long rateInHertz) :
+    mDone(false),
+    mPaused(true),
+    mInterval(IceUtil::Time::microSecondsDouble(1000000.0 / rateInHertz))
 {
 }
 
 void MediaEchoThread::run()
 {
-    IceUtil::Monitor<IceUtil::Mutex>::Lock lock(mMonitor);
-    while(!mDone)
+    //
+    // This loop is structured so that new frames can continuously be added as
+    // they are being sent.  The lock on the "main" frame sequence is only held
+    // long enough to copy the current frames and some other key state. Writing
+    // is done without holding the lock. A useful improvement might be to add
+    // an upper limit to the number of frames that can be "queued".
+    //
+    std::cerr << "entering thread" << std::endl;
+    bool done = false;
+    AsteriskSCF::Media::V1::FrameSeq currentFrames;
+    size_t numberOfFramesWritten = 0;
+    AsteriskSCF::Media::V1::StreamSinkPrx sink;
+    while(!done)
     {
-        mMonitor.wait();
+        std::cerr << "looping" << std::endl;
+        {
+            IceUtil::Monitor<IceUtil::Mutex>::Lock lock(mMonitor);
+            if(mFrames.size() == 0 || mSink == 0)
+            {
+                std::cerr << "no frames or no sink... continuing to wait" << std::endl;
+                mMonitor.wait();
+            }
+            if(mDone)
+            {
+                std::cerr << "this thread is done" << std::endl;
+                done = true;
+                break;
+            }
 
-        if(!mDone && mFrames.size() > 0 && mSink != 0)
+            sink = mSink;
+            std::cerr << "not done yet " << std::endl;
+            if(numberOfFramesWritten != 0)
+            {
+                mFrames.erase(mFrames.begin(), mFrames.begin() + numberOfFramesWritten);
+                numberOfFramesWritten = 0;
+            }
+            currentFrames.assign(mFrames.begin(), mFrames.end());
+        }
+
+        AsteriskSCF::Media::V1::FrameSeq::iterator current = currentFrames.begin();
+        std::cerr << "writing " << currentFrames.size() << " frames" << std::endl;
+        while(current != currentFrames.end() && sink != 0)
         {
+            AsteriskSCF::Media::V1::FrameSeq buf(current, current+1);
             try
             {
-                mSink->write(mFrames);
-                mFrames.clear();
+                AsteriskSCF::Media::V1::AudioFormatPtr format =
+                    AsteriskSCF::Media::V1::AudioFormatPtr::dynamicCast((*current)->mediaformat);
+                assert(format);
+                sink->write(buf);
+                //
+                // Waiting for the configured interval creates the proper frame "rate".
+                //
+                
+                IceUtil::Time interval(IceUtil::Time::milliSeconds(format->frameSize));
+                IceUtil::ThreadControl::sleep(interval);
             }
             catch(const Ice::Exception&)
             {
-                // TODO: log and continue
+                // TODO: log and continue.. the current frame will not be re-attempted.
             }
+            ++numberOfFramesWritten;
+            ++current;
         }
+        std::cerr << "done .. going back to wait loop" << std::endl;
     }
 }
 
 void MediaEchoThread::pushFrames(const AsteriskSCF::Media::V1::FrameSeq& newFrames)
 {
     IceUtil::Monitor<IceUtil::Mutex>::Lock lock(mMonitor);
+    std::cerr << "pushing " << newFrames.size() << " frames" << std::endl;
     mFrames.insert(mFrames.end(), newFrames.begin(), newFrames.end());
-    if(!mPaused)
+    if(!mPaused && mSink != 0)
     {
         mMonitor.notify();
     }
diff --git a/src/MediaEchoThread.h b/src/MediaEchoThread.h
index 6fc87fb..cbbd14c 100644
--- a/src/MediaEchoThread.h
+++ b/src/MediaEchoThread.h
@@ -22,13 +22,15 @@ class MediaEchoThread : public virtual IceUtil::Thread
 {
 public:
     MediaEchoThread();
+    MediaEchoThread(const unsigned long rateInHz);
     void run();
     void pushFrames(const AsteriskSCF::Media::V1::FrameSeq& newFrames);
     void destroy();
     void setSink(const AsteriskSCF::Media::V1::StreamSinkPrx& prx);
 
     //
-    // Use when paused... causes the loop to run once.
+    // Use when paused... causes the loop to run until the currently buffered
+    // frames have been sent.
     //
     void echo();
     void pause();
@@ -40,6 +42,13 @@ private:
     AsteriskSCF::Media::V1::FrameSeq mFrames;
     bool mDone;
     bool mPaused;
+    IceUtil::Time mInterval;
+
+    //
+    // Not implemented.
+    //
+    MediaEchoThread(const MediaEchoThread&);
+    void operator=(const MediaEchoThread&);
 };
 typedef IceUtil::Handle<MediaEchoThread> MediaEchoThreadPtr;
 
diff --git a/src/RemoteControl.ice b/src/RemoteControl.ice
new file mode 100644
index 0000000..d2be87b
--- /dev/null
+++ b/src/RemoteControl.ice
@@ -0,0 +1,84 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
... 319 lines suppressed ...


-- 
asterisk-scf/release/test_channel.git



More information about the asterisk-scf-commits mailing list