diff options
Diffstat (limited to 'src')
26 files changed, 1698 insertions, 11 deletions
diff --git a/src/bindings/swig/java/JavaInvoker.cpp b/src/bindings/swig/java/JavaInvoker.cpp new file mode 100644 index 0000000..c10ae6d --- /dev/null +++ b/src/bindings/swig/java/JavaInvoker.cpp @@ -0,0 +1,8 @@ +#include "JavaInvoker.h" + +namespace uscxml { + +JavaInvoker::JavaInvoker() {} +JavaInvoker::~JavaInvoker() {} + +}
\ No newline at end of file diff --git a/src/bindings/swig/java/JavaInvoker.h b/src/bindings/swig/java/JavaInvoker.h new file mode 100644 index 0000000..0a13176 --- /dev/null +++ b/src/bindings/swig/java/JavaInvoker.h @@ -0,0 +1,39 @@ +#ifndef JAVAINVOKER_H_WDV9B5F6 +#define JAVAINVOKER_H_WDV9B5F6 + +#include "../../../uscxml/Message.h" +#include "../../../uscxml/Factory.h" +#include "../../../uscxml/Interpreter.h" + +namespace uscxml { + +class JavaInvoker : public InvokerImpl { +public: + JavaInvoker(); + virtual ~JavaInvoker(); + + virtual std::set<std::string> getNames() { + return std::set<std::string>(); + }; + + virtual Data getDataModelVariables() { + Data data; + return data; + } + + virtual void send(const SendRequest& req) {} + virtual void invoke(const InvokeRequest& req) {} + + virtual JavaInvoker* create(Interpreter interpreter) { + return new JavaInvoker(); + } + + virtual boost::shared_ptr<InvokerImpl> create(InterpreterImpl* interpreter) { + return boost::shared_ptr<InvokerImpl>(create(interpreter->shared_from_this())); + } + +}; + +} + +#endif /* end of include guard: JAVAINVOKER_H_WDV9B5F6 */ diff --git a/src/bindings/swig/java/stl_list.i b/src/bindings/swig/java/stl_list.i new file mode 100644 index 0000000..aabd448 --- /dev/null +++ b/src/bindings/swig/java/stl_list.i @@ -0,0 +1,49 @@ +/* ----------------------------------------------------------------------------- + * See the LICENSE file for information on copyright, usage and redistribution + * of SWIG, and the README file for authors - http://www.swig.org/release.html. + * + * std_list.i + * ----------------------------------------------------------------------------- */ + +%include <std_common.i> + +%{ +#include <list> +#include <stdexcept> +%} + +namespace std { + + template<class T> class list { + public: + typedef size_t size_type; + typedef T value_type; + typedef const value_type& const_reference; + list(); + size_type size() const; + %rename(isEmpty) empty; + bool empty() const; + void clear(); + %rename(add) push_back; + void push_back(const value_type& x); + %extend { + const_reference get(int i) throw (std::out_of_range) { + int size = int(self->size()); + int j; + if (i>=0 && i<size) { + std::list<T>::const_iterator p; + p=self->begin(); + for (j=0; j<i; j++) {p++;} + return (*p); + } + else + throw std::out_of_range("list index out of range"); + } + } + }; +} + +%define specialize_std_list(T) +#warning "specialize_std_list - specialization for type T no longer needed" +%enddef + diff --git a/src/bindings/swig/java/stl_set.i b/src/bindings/swig/java/stl_set.i new file mode 100644 index 0000000..d009a7b --- /dev/null +++ b/src/bindings/swig/java/stl_set.i @@ -0,0 +1,73 @@ +/*========================================================================= + + Program: GDCM (Grassroots DICOM). A DICOM library + + Copyright (c) 2006-2011 Mathieu Malaterre + All rights reserved. + See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/* ----------------------------------------------------------------------------- + * std_set.i + * + * SWIG typemaps for std::set + * ----------------------------------------------------------------------------- */ + +%include <std_common.i> + +// ------------------------------------------------------------------------ +// std::set +// ------------------------------------------------------------------------ + +%{ +#include <set> +#include <algorithm> +#include <stdexcept> +%} + +// exported class + +namespace std { + + template<class V> class set { + // add typemaps here + public: + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef V value_type; + set(); + set(const set<V> &); + + unsigned int size() const; + bool empty() const; + void clear(); + %extend { + const V& get(const V& key) throw (std::out_of_range) { + std::set<V>::iterator i = self->find(key); + if (i != self->end()) + return *i; + else + throw std::out_of_range("key not found"); + } + void insert(const V& key) { // Do NOT call this function 'set' ! + self->insert(key); + } + void del(const V& key) throw (std::out_of_range) { + std::set<V>::iterator i = self->find(key); + if (i != self->end()) + self->erase(i); + else + throw std::out_of_range("key not found"); + } + bool has_key(const V& key) { + std::set<V>::iterator i = self->find(key); + return i != self->end(); + } + } + }; + +}
\ No newline at end of file diff --git a/src/bindings/swig/java/uscxml.i b/src/bindings/swig/java/uscxml.i index ee7133c..af979d0 100644 --- a/src/bindings/swig/java/uscxml.i +++ b/src/bindings/swig/java/uscxml.i @@ -3,8 +3,21 @@ // import swig typemaps //%include <arrays_java.i> //%include <inttypes.i> + +%include <stl.i> +%include <std_map.i> +%include <inttypes.i> +%include "stl_set.i" +%include "stl_list.i" + %include <boost_shared_ptr.i> + +typedef uscxml::Data Data; +typedef uscxml::Event Event; +typedef uscxml::InvokeRequest InvokeRequest; +typedef uscxml::SendRequest SendRequest; + // disable warning related to unknown base class #pragma SWIG nowarn=401 //%ignore boost::enable_shared_from_this; @@ -32,10 +45,14 @@ %{ #include "../../../uscxml/Message.h" +#include "../../../uscxml/Factory.h" #include "../../../uscxml/Interpreter.h" +#include "JavaInvoker.h" using namespace uscxml; +#include "JavaInvoker.cpp" + %} %rename(toString) operator<<; @@ -46,10 +63,22 @@ using namespace uscxml; %ignore uscxml::Interpreter::getDelayQueue(); +%ignore uscxml::JavaInvoker::create(InterpreterImpl*); + +%template(DataMap) std::map<std::string, uscxml::Data>; +%template(DataList) std::list<uscxml::Data>; +%template(StringSet) std::set<std::string>; + + +%feature("director") uscxml::JavaInvoker; + //*********************************************** // Parse the header file to generate wrappers //*********************************************** +#define SWIGIMPORTED 1 +%include "../../../uscxml/Factory.h" %include "../../../uscxml/Message.h" %include "../../../uscxml/Interpreter.h" +%include "JavaInvoker.h" diff --git a/src/bindings/swig/php/uscxmlNativePHP.php b/src/bindings/swig/php/uscxmlNativePHP.php index ef37b94..08fc515 100644 --- a/src/bindings/swig/php/uscxmlNativePHP.php +++ b/src/bindings/swig/php/uscxmlNativePHP.php @@ -306,8 +306,8 @@ class Data { return Data_toXMLString($this->_cPtr); } - function getCompund() { - return Data_getCompund($this->_cPtr); + function getCompound() { + return Data_getCompound($this->_cPtr); } function setCompound($compound) { @@ -357,8 +357,8 @@ class Event { } function __get($var) { - if ($var === 'data') return new Data(Event_data_get($this->_cPtr)); if ($var === 'namelist') return new StringMap(Event_namelist_get($this->_cPtr)); + if ($var === 'data') return new Data(Event_data_get($this->_cPtr)); $func = 'Event_'.$var.'_get'; if (function_exists($func)) return call_user_func($func,$this->_cPtr); if ($var === 'thisown') return swig_uscxmlNativePHP_get_newobject($this->_cPtr); @@ -447,6 +447,14 @@ class Event { Event_setContent($this->_cPtr,$content); } + function getXML() { + return Event_getXML($this->_cPtr); + } + + function setXML($xml) { + Event_setXML($this->_cPtr,$xml); + } + function getSendId() { return Event_getSendId($this->_cPtr); } diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp index b8bf60a..9b6a84a 100644 --- a/src/uscxml/Factory.cpp +++ b/src/uscxml/Factory.cpp @@ -49,6 +49,10 @@ # include "uscxml/plugins/invoker/calendar/CalendarInvoker.h" # endif +#ifdef OPENAL_FOUND +# include "uscxml/plugins/invoker/audio/OpenALInvoker.h" +#endif + # ifdef CORELOCATION_FOUND # include "uscxml/plugins/invoker/location/CoreLocation/LocationInvoker.h" # endif @@ -162,6 +166,13 @@ Factory::Factory() { } #endif +#if (defined OPENAL_FOUND && (defined LIBSNDFILE_FOUND || defined AUDIOTOOLBOX_FOUND)) + { + OpenALInvoker* invoker = new OpenALInvoker(); + registerInvoker(invoker); + } +#endif + #ifdef OPENSCENEGRAPH_FOUND { OSGInvoker* invoker = new OSGInvoker(); @@ -472,6 +483,22 @@ Factory* Factory::getInstance() { return _instance; } +void EventHandlerImpl::returnErrorExecution(const std::string& cause) { + Event exceptionEvent; + exceptionEvent.data.compound["exception"] = Data(cause, Data::VERBATIM); + exceptionEvent.name = "error.execution"; + exceptionEvent.type = Event::PLATFORM; + returnEvent(exceptionEvent); +} + +void EventHandlerImpl::returnErrorPlatform(const std::string& cause) { + Event exceptionEvent; + exceptionEvent.data.compound["exception"] = Data(cause, Data::VERBATIM); + exceptionEvent.name = "error.platform"; + exceptionEvent.type = Event::PLATFORM; + returnEvent(exceptionEvent); +} + void EventHandlerImpl::returnEvent(Event& event) { if (event.invokeid.length() == 0) event.invokeid = _invokeId; diff --git a/src/uscxml/Factory.h b/src/uscxml/Factory.h index 3ddd9ad..74cb2a4 100644 --- a/src/uscxml/Factory.h +++ b/src/uscxml/Factory.h @@ -111,6 +111,8 @@ protected: class EventHandlerImpl { public: + virtual ~EventHandlerImpl() {} + virtual std::set<std::string> getNames() = 0; virtual void setInterpreter(InterpreterImpl* interpreter) { @@ -128,6 +130,8 @@ public: virtual void runOnMainThread() {}; void returnEvent(Event& event); + void returnErrorExecution(const std::string&); + void returnErrorPlatform(const std::string&); protected: InterpreterImpl* _interpreter; @@ -211,6 +215,7 @@ protected: class InvokerImpl : public EventHandlerImpl { public: + virtual ~InvokerImpl() {} virtual void invoke(const InvokeRequest& req) = 0; virtual boost::shared_ptr<InvokerImpl> create(InterpreterImpl* interpreter) = 0; }; diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 77c6805..b6266e2 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -709,6 +709,13 @@ void InterpreterImpl::send(const Arabica::DOM::Node<std::string>& element) { return; } + if (sendReq.dom) { + std::stringstream ss; + ss << sendReq.dom; + sendReq.xml = ss.str(); + _dataModel.replaceExpressions(sendReq.xml); + } + assert(_sendIds.find(sendReq.sendid) == _sendIds.end()); _sendIds[sendReq.sendid] = std::make_pair(this, sendReq); if (sendReq.delayMs > 0) { @@ -839,6 +846,13 @@ void InterpreterImpl::invoke(const Arabica::DOM::Node<std::string>& element) { return; } + if (invokeReq.dom) { + std::stringstream ss; + ss << invokeReq.dom; + invokeReq.xml = ss.str(); + _dataModel.replaceExpressions(invokeReq.xml); + } + // test 422 if (invokeReq.type.size() == 0) invokeReq.type = "http://www.w3.org/TR/scxml/"; @@ -1330,11 +1344,11 @@ Arabica::XPath::NodeSet<std::string> InterpreterImpl::getStates(const std::vecto Arabica::DOM::Node<std::string> InterpreterImpl::getState(const std::string& stateId) { - if (_cachedStates.find(stateId) != _cachedStates.end() && false) { + if (_cachedStates.find(stateId) != _cachedStates.end()) { return _cachedStates[stateId]; } - // first try atomic and compund states + // first try atomic and compound states NodeSet<std::string> target = _xpath.evaluate("//" + _xpathPrefix + "state[@id='" + stateId + "']", _scxml).asNodeSet(); if (target.size() > 0) goto FOUND; diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h index dda2279..ed481e2 100644 --- a/src/uscxml/Message.h +++ b/src/uscxml/Message.h @@ -72,7 +72,7 @@ public: return ss.str(); } - std::map<std::string, Data> getCompund() { + std::map<std::string, Data> getCompound() { return compound; } void setCompound(const std::map<std::string, Data>& compound) { @@ -187,6 +187,13 @@ public: this->content = content; } + std::string getXML() { + return xml; + } + void setXML(const std::string& xml) { + this->xml = xml; + } + std::string getSendId() { return sendid; } @@ -270,6 +277,7 @@ protected: #endif std::string raw; + std::string xml; std::string name; Type type; std::string origin; diff --git a/src/uscxml/URL.cpp b/src/uscxml/URL.cpp index ac947d7..65cd5ee 100644 --- a/src/uscxml/URL.cpp +++ b/src/uscxml/URL.cpp @@ -64,6 +64,24 @@ URLImpl::~URLImpl() { curl_easy_cleanup(_handle); } +URLImpl::operator Data() { + Data data; + data.compound["url"] = Data(asString(), Data::VERBATIM); + data.compound["host"] = Data(_uri.host(), Data::VERBATIM); + data.compound["scheme"] = Data(_uri.scheme(), Data::VERBATIM); + data.compound["path"] = Data(_uri.path(), Data::VERBATIM); + data.compound["port"] = Data(_uri.port()); + data.compound["isAbsolute"] = Data(_uri.is_absolute() ? "true" : "false"); + + std::vector<std::string>::iterator pathIter = _pathComponents.begin(); + while(pathIter != _pathComponents.end()) { + data.compound["pathComponent"].array.push_back(Data(*pathIter, Data::VERBATIM)); + pathIter++; + } + + return data; +} + CURL* URLImpl::getCurlHandle() { if (_handle == NULL) { _handle = curl_easy_init(); diff --git a/src/uscxml/URL.h b/src/uscxml/URL.h index bb33a7c..646ea3f 100644 --- a/src/uscxml/URL.h +++ b/src/uscxml/URL.h @@ -8,6 +8,7 @@ #include <map> #include <vector> #include <set> +#include "Message.h" #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <sys/stat.h> @@ -89,6 +90,7 @@ public: bool downloadFailed() { return _hasFailed; } + operator Data(); friend class URLFetcher; @@ -225,6 +227,10 @@ public: friend class URLFetcher; friend std::ostream & operator<<(std::ostream &stream, const URL& p); + operator Data() { + return _impl->operator Data(); + } + protected: void downloadStarted() { return _impl->downloadStarted(); diff --git a/src/uscxml/interpreter/InterpreterDraft6.cpp b/src/uscxml/interpreter/InterpreterDraft6.cpp index 9140d17..0291ab1 100644 --- a/src/uscxml/interpreter/InterpreterDraft6.cpp +++ b/src/uscxml/interpreter/InterpreterDraft6.cpp @@ -882,7 +882,7 @@ void InterpreterDraft6::enterStates(const Arabica::XPath::NodeSet<std::string>& executeContent(onEntryElems); if (isMember(stateElem, statesForDefaultEntry)) { - // execute initial transition content for compund states + // execute initial transition content for compound states Arabica::XPath::NodeSet<std::string> transitions = _xpath.evaluate("" + _xpathPrefix + "initial/" + _xpathPrefix + "transition", stateElem).asNodeSet(); for (int j = 0; j < transitions.size(); j++) { executeContent(transitions[j]); diff --git a/src/uscxml/plugins/datamodel/CMakeLists.txt b/src/uscxml/plugins/datamodel/CMakeLists.txt index 01f8279..731e316 100644 --- a/src/uscxml/plugins/datamodel/CMakeLists.txt +++ b/src/uscxml/plugins/datamodel/CMakeLists.txt @@ -1,4 +1,4 @@ -if (APPLE OR IOS) +if (JSC_FOUND AND BUILD_DM_ECMA) # JavaScriptCore ecmascript datamodel file(GLOB JSC_DATAMODEL ecmascript/JavaScriptCore/*.cpp diff --git a/src/uscxml/plugins/invoker/CMakeLists.txt b/src/uscxml/plugins/invoker/CMakeLists.txt index 4f13aaf..e23d65e 100644 --- a/src/uscxml/plugins/invoker/CMakeLists.txt +++ b/src/uscxml/plugins/invoker/CMakeLists.txt @@ -3,6 +3,7 @@ file(GLOB_RECURSE SAMPLE_INVOKER sample/*.cpp sample/*.h ) + source_group("Invoker\\sample" FILES ${SAMPLE_INVOKER}) if (BUILD_AS_PLUGINS) add_library( @@ -14,7 +15,6 @@ else() list (APPEND USCXML_FILES ${SAMPLE_INVOKER}) endif() - # DirMon invoker to watch for filesystem changes file(GLOB_RECURSE DIRMON_INVOKER @@ -281,7 +281,40 @@ if (OPENSCENEGRAPH_FOUND AND OPENGL_FOUND) else() list (APPEND USCXML_FILES ${OPENSCENEGRAPH_CONVERTER_INVOKER}) endif() +endif() + + +# OpenAL modality components + +if (OPENAL_FOUND AND (LIBSNDFILE_FOUND OR AUDIOTOOLBOX_FOUND)) + file(GLOB_RECURSE OPENAL_INVOKER + audio/OpenAL*.cpp + audio/OpenAL*.h) + if (LIBSNDFILE_FOUND) + file(GLOB_RECURSE LIBSNDFILE_WRAPPER + audio/LibSoundFile*.cpp + audio/LibSoundFile*.h) + LIST(APPEND OPENAL_INVOKER ${LIBSNDFILE_WRAPPER}) + elseif(AUDIOTOOLBOX_FOUND) + file(GLOB_RECURSE AUDIOTOOLBOX_WRAPPER + audio/AudioToolbox*.mm + audio/AudioToolbox*.h) + LIST(APPEND OPENAL_INVOKER ${AUDIOTOOLBOX_WRAPPER}) + endif() + source_group("Invoker\\audio" FILES ${OPENAL_INVOKER}) +# message("MILES_INVOKER ${MILES_INVOKER}") + if (BUILD_AS_PLUGINS) + add_library( + invoker_openal SHARED + ${OPENAL_INVOKER}) + target_link_libraries(invoker_openal + ${OPENAL_LIBRARY} + uscxml) + set_target_properties(invoker_openal PROPERTIES FOLDER "Plugin Invoker") + else() + list (APPEND USCXML_FILES ${OPENAL_INVOKER}) + endif() endif() diff --git a/src/uscxml/plugins/invoker/audio/AudioToolbox.h b/src/uscxml/plugins/invoker/audio/AudioToolbox.h new file mode 100644 index 0000000..3e04d8f --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/AudioToolbox.h @@ -0,0 +1,32 @@ +#ifndef AUDIOTOOLBOX_H_GX4SW17C +#define AUDIOTOOLBOX_H_GX4SW17C + +#include "PCMConverter.h" +#include <AudioToolbox/AudioToolbox.h> + +namespace uscxml { + +class AudioToolbox : public PCMConverter { +public: + AudioToolbox(const std::string filename); + virtual ~AudioToolbox(); + virtual void seek(unsigned int pos); + virtual int read(char* buffer, unsigned int size); + + virtual void setOutFormat(const PCMFormat& format); + virtual PCMFormat getInFormat(); + +protected: + ExtAudioFileRef _afId; + AudioStreamBasicDescription _outputFormat; + AudioStreamBasicDescription _inputFormat; + + ALenum formatToALEnum(AudioStreamBasicDescription); + bool alEnumToFormat(AudioStreamBasicDescription&, ALenum); +}; + +} + +#endif /* end of include guard: AUDIOTOOLBOX_H_GX4SW17C */ + + diff --git a/src/uscxml/plugins/invoker/audio/AudioToolbox.mm b/src/uscxml/plugins/invoker/audio/AudioToolbox.mm new file mode 100644 index 0000000..44720b1 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/AudioToolbox.mm @@ -0,0 +1,152 @@ +#include "AudioToolbox.h" +#include <glog/logging.h> + +#import <Foundation/Foundation.h> +#import <Foundation/NSURL.h> + +namespace uscxml { + +AudioToolbox::AudioToolbox(const std::string filename) { + @autoreleasepool { + _afId = 0; + NSString* filePath = [NSString stringWithCString:filename.c_str() encoding:NSASCIIStringEncoding]; + NSURL* afUrl = [NSURL fileURLWithPath:filePath]; + + OSStatus result = noErr; + + result = ExtAudioFileOpenURL((CFURLRef)afUrl, &_afId); + + if (result != noErr) { + LOG(WARNING) << "Cannot open audio file " << filename; + return; + } + UInt32 thePropertySize = sizeof(_inputFormat); + result = ExtAudioFileGetProperty(_afId, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &_inputFormat); + if (result != noErr) { + LOG(WARNING) << "Cannot determine input format of " << filename; + return; + } + + // output format is input format + memcpy(&_outputFormat, &_inputFormat, sizeof(_inputFormat)); + + // except for things that make no sense for open al + _outputFormat.mFormatID = kAudioFormatLinearPCM; + _outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; + + ALenum bestFormat = formatToALEnum(_outputFormat); + alEnumToFormat(_outputFormat, bestFormat); + + result = ExtAudioFileSetProperty(_afId, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat), &_outputFormat); + + if (result != noErr) { + LOG(WARNING) << "Cannot set audio format file " << filename; + return; + } + + } +} + +AudioToolbox::~AudioToolbox() { + if (_afId) + ExtAudioFileDispose(_afId); //close the file +} + +void AudioToolbox::seek(unsigned int pos) { + ExtAudioFileSeek(_afId, pos); +} + +int AudioToolbox::read(char* buffer, unsigned int size) { + UInt32 read = size / _outputFormat.mBytesPerFrame; + OSStatus result = noErr; + + SInt64 theFileLengthInFrames = 0; + UInt32 thePropertySize = sizeof(theFileLengthInFrames); + result = ExtAudioFileGetProperty(_afId, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames); + + read = (theFileLengthInFrames < read ? theFileLengthInFrames : read); + + AudioBufferList dataBuffer; + dataBuffer.mNumberBuffers = 1; + dataBuffer.mBuffers[0].mDataByteSize = size; + dataBuffer.mBuffers[0].mNumberChannels = _outputFormat.mChannelsPerFrame; + dataBuffer.mBuffers[0].mData = buffer; + + result = ExtAudioFileRead(_afId, &read, &dataBuffer); + if (result != noErr) { + LOG(WARNING) << "Cannot read data"; + return 0; + } + + return read * _outputFormat.mBytesPerFrame; +} + +ALenum AudioToolbox::formatToALEnum(AudioStreamBasicDescription asbd) { + if (asbd.mBitsPerChannel < 16) { + if (asbd.mChannelsPerFrame == 1) { + return AL_FORMAT_MONO8; + } else { + return AL_FORMAT_STEREO8; + } + } else { + if (asbd.mChannelsPerFrame == 1) { + return AL_FORMAT_MONO16; + } else { + return AL_FORMAT_STEREO16; + } + } +} + +bool AudioToolbox::alEnumToFormat(AudioStreamBasicDescription& asbd, ALenum format) { + switch (format) { + case AL_FORMAT_MONO8: + asbd.mBitsPerChannel = 8; + asbd.mBytesPerFrame = 1; + asbd.mBytesPerPacket = 1; + asbd.mChannelsPerFrame = 1; + break; + case AL_FORMAT_MONO16: + asbd.mBitsPerChannel = 16; + asbd.mBytesPerFrame = 2; + asbd.mBytesPerPacket = 2; + asbd.mChannelsPerFrame = 1; + break; + case AL_FORMAT_STEREO8: + asbd.mBitsPerChannel = 8; + asbd.mBytesPerFrame = 2; + asbd.mBytesPerPacket = 2; + asbd.mChannelsPerFrame = 2; + break; + case AL_FORMAT_STEREO16: + asbd.mBitsPerChannel = 16; + asbd.mBytesPerFrame = 4; + asbd.mBytesPerPacket = 4; + asbd.mChannelsPerFrame = 2; + break; + default: + break; + } + return true; +} + +void AudioToolbox::setOutFormat(const PCMFormat& format) { + + alEnumToFormat(_outputFormat, format.alFormat); + _outputFormat.mSampleRate = format.sampleRate; + + OSStatus result = ExtAudioFileSetProperty(_afId, kExtAudioFileProperty_ClientDataFormat, sizeof(_outputFormat), &_outputFormat); + if (result != noErr) { + LOG(WARNING) << "Cannot set audio format"; + return; + } + +} + +PCMFormat AudioToolbox::getInFormat() { + PCMFormat format; + format.sampleRate = _inputFormat.mSampleRate; + format.alFormat = formatToALEnum(_inputFormat); + return format; +} + +}
\ No newline at end of file diff --git a/src/uscxml/plugins/invoker/audio/LibSoundFile.cpp b/src/uscxml/plugins/invoker/audio/LibSoundFile.cpp new file mode 100644 index 0000000..1340140 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/LibSoundFile.cpp @@ -0,0 +1,50 @@ +#include "LibSoundFile.h" + +namespace uscxml { + +LibSoundFile::LibSoundFile(const std::string filename) : PCMConverter() { + _filename = filename; + _handle = SndfileHandle(_filename, SFM_READ, SF_FORMAT_PCM_16, 1, 44100); + _format.sampleRate = _handle.samplerate(); + _format.alFormat = AL_FORMAT_MONO16; + +} + +LibSoundFile::~LibSoundFile() { + +} + +void LibSoundFile::seek(unsigned int pos) { + _handle.seek(pos, 0); +} + +int LibSoundFile::read(char* buffer, unsigned int size) { + return _handle.readRaw(buffer, size); +} + +void LibSoundFile::setOutFormat(const PCMFormat& format) { + _format = format; + switch (_format.alFormat) { + case AL_FORMAT_MONO8: + _handle = SndfileHandle(_filename, SFM_READ, SF_FORMAT_PCM_S8, 1, _format.sampleRate); + break; + case AL_FORMAT_MONO16: + _handle = SndfileHandle(_filename, SFM_READ, SF_FORMAT_PCM_16, 1, _format.sampleRate); + break; + case AL_FORMAT_STEREO8: + _handle = SndfileHandle(_filename, SFM_READ, SF_FORMAT_PCM_S8, 2, _format.sampleRate); + break; + case AL_FORMAT_STEREO16: + _handle = SndfileHandle(_filename, SFM_READ, SF_FORMAT_PCM_16, 2, _format.sampleRate); + break; + + default: + break; + } +} + +PCMFormat LibSoundFile::getInFormat() { + return _format; +} + +}
\ No newline at end of file diff --git a/src/uscxml/plugins/invoker/audio/LibSoundFile.h b/src/uscxml/plugins/invoker/audio/LibSoundFile.h new file mode 100644 index 0000000..5491609 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/LibSoundFile.h @@ -0,0 +1,27 @@ +#ifndef LIBSOUNDFILE_H_Q97OEKGG +#define LIBSOUNDFILE_H_Q97OEKGG + +#include "PCMConverter.h" +#include <sndfile.hh> + +namespace uscxml { + +class LibSoundFile : public PCMConverter { +public: + LibSoundFile(const std::string filename); + virtual ~LibSoundFile(); + void seek(unsigned int pos); + int read(char* buffer, unsigned int size); + + virtual void setOutFormat(const PCMFormat& format); + virtual PCMFormat getInFormat(); + +protected: + std::string _filename; + SndfileHandle _handle; + PCMFormat _format; +}; + +} + +#endif /* end of include guard: LIBSOUNDFILE_H_Q97OEKGG */ diff --git a/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp b/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp new file mode 100644 index 0000000..cf9d15a --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/OpenALInvoker.cpp @@ -0,0 +1,362 @@ +// see http://stackoverflow.com/questions/6563810/m-pi-works-with-math-h-but-not-with-cmath-in-visual-studio
+#define _USE_MATH_DEFINES
+#include <cmath>
+ +#include "OpenALInvoker.h" +#include <uscxml/config.h> +#include <glog/logging.h> +#include <limits.h> + +#ifdef BUILD_AS_PLUGINS +#include <Pluma/Connector.hpp> +#endif + +namespace uscxml { + +#ifdef BUILD_AS_PLUGINS +PLUMA_CONNECTOR +bool connect(pluma::Host& host) { + host.add( new OpenALInvokerProvider() ); + return true; +} +#endif +
+// see http://stackoverflow.com/questions/1904635/warning-c4003-and-errors-c2589-and-c2059-on-x-stdnumeric-limitsintmax
+#undef max + +OpenALInvoker::OpenALInvoker() { + _isStarted = false; + _isRunning = false; + _alContext = NULL; + _alDevice = NULL; + _thread = NULL; +} + +OpenALInvoker::~OpenALInvoker() { + if (_thread) { + _isRunning = false; + _thread->join(); + delete(_thread); + } + if (_alContext) { + alcCloseDevice(alcGetContextsDevice(_alContext)); + alcDestroyContext(_alContext); + } +}; + +boost::shared_ptr<InvokerImpl> OpenALInvoker::create(InterpreterImpl* interpreter) { + boost::shared_ptr<OpenALInvoker> invoker = boost::shared_ptr<OpenALInvoker>(new OpenALInvoker()); + invoker->_interpreter = interpreter; + return invoker; +} + +Data OpenALInvoker::getDataModelVariables() { + Data data; + return data; +} + +void OpenALInvoker::send(const SendRequest& req) { + tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); + + if (!_isStarted) + start(); + + if (boost::iequals(req.name, "play")) { + if (req.params.find("src") == req.params.end()) { + LOG(ERROR) << "Sent event play with no src URL"; + } + + URL srcURL = req.params.find("src")->second; + if (!srcURL.toAbsolute(_interpreter->getBaseURI())) { + LOG(ERROR) << "src URL " << req.params.find("src")->second << " is relative with no base URI set for interpreter"; + return; + } + + _sources[req.sendid] = new OpenALSource(); + _sources[req.sendid]->loop = req.params.find("loop") != req.params.end() && boost::iequals(req.params.find("loop")->second, "true"); + _sources[req.sendid]->file = srcURL; +#ifdef LIBSNDFILE_FOUND + _sources[req.sendid]->transform = new LibSoundFile(srcURL.asLocalFile(".audio")); +#else +# ifdef AUDIOTOOLBOX_FOUND + _sources[req.sendid]->transform = new AudioToolbox(srcURL.asLocalFile(".audio")); +# endif +#endif + if (_sources[req.sendid]->transform == NULL) { + LOG(ERROR) << "No transcoder for input file known - install libsndfile or AudioToolbox"; + _sources.erase(req.sendid); + return; + } + + // force mono format to ensure actual spatial audio + PCMFormat format = _sources[req.sendid]->transform->getInFormat(); + format.alFormat = AL_FORMAT_MONO16; + _sources[req.sendid]->transform->setOutFormat(format); + + try { + _sources[req.sendid]->player = new OpenALPlayer(_alContext, NULL, format.alFormat, format.sampleRate); + } catch (std::exception ex) { + returnErrorExecution(ex.what()); + return; + } + + getPosFromParams(req.params, _sources[req.sendid]->pos); + + try { + _sources[req.sendid]->player->setPosition(_sources[req.sendid]->pos); + } catch (std::exception ex) { + returnErrorExecution(ex.what()); + } + + + _sourcesAvailable.notify_all(); + } + + if (boost::iequals(req.name, "move.source")) { + std::string sourceId; + if (req.params.find("source") == req.params.end()) { + LOG(WARNING) << "Cannot move source with no source given in parameters"; + return; + } + sourceId = req.params.find("source")->second; + + if (_sources.find(sourceId) == _sources.end()) { + LOG(WARNING) << "Given source '" << sourceId << "' not active or not existing"; + return; + } + + getPosFromParams(req.params, _sources[sourceId]->pos); + try { + _sources[sourceId]->player->setPosition(_sources[sourceId]->pos); + } catch (std::exception ex) { + returnErrorExecution(ex.what()); + } + } +} + +void OpenALInvoker::start() { + _isStarted = true; + _thread = new tthread::thread(&OpenALInvoker::fillBuffers, this); +} + +void OpenALInvoker::fillBuffers(void* userdata) { + OpenALInvoker* INST = (OpenALInvoker*)userdata; + while(INST->_isStarted) { + // do nothing until we have at least one source + int waitMs = std::numeric_limits<int>::max(); + INST->_mutex.lock(); + while (INST->_sources.size() == 0) { + INST->_sourcesAvailable.wait(INST->_mutex); + } + // here we are with at least one source and a locked mutex + assert(INST->_sources.size() > 0); + + std::map<std::string, OpenALSource*>::iterator srcIter = INST->_sources.begin(); + while(srcIter != INST->_sources.end()) { + OpenALSource* src = srcIter->second; + int wait = std::numeric_limits<int>::max(); + + if (src->finished) { + // source has already finished playing, feed no more samples to it + try { + wait = src->player->isPlaying(); + if (wait == 0) { + // source stopped playing, delete it + INST->notifyOfEnd(src); + delete src; + INST->_sources.erase(srcIter++); + continue; + } else { + // source returned time when to repoll + assert(wait > 0); + } + } catch (std::exception ex) { + INST->returnErrorExecution(ex.what()); + delete src; + INST->_sources.erase(srcIter++); + continue; + } + } else { + // source still needs more samples or play existing buffer + if (src->written == src->read) { + // all read samples have been written, read some more + src->written = 0; + src->read = src->transform->read(src->buffer, ALPLAY_AUDIO_BUFFER_SIZE); + if (src->read < ALPLAY_AUDIO_BUFFER_SIZE) { + if (src->loop) { + INST->notifyOfLoop(src); + while (src->read < ALPLAY_AUDIO_BUFFER_SIZE) { + src->transform->seek(0); + src->read += src->transform->read(src->buffer + src->read, ALPLAY_AUDIO_BUFFER_SIZE - src->read); + } + } else { + src->finished = true; + memset(src->buffer + src->read, 0, ALPLAY_AUDIO_BUFFER_SIZE - src->read); + } + } + } + + // there are unwritten samples in the buffer + if (src->read != src->written) { + try { + int written = src->player->write(src->buffer, ALPLAY_AUDIO_BUFFER_SIZE, &wait); + if (written >=0 ) { + src->written += written; + } + } catch (std::exception ex) { + INST->returnErrorExecution(ex.what()); + src->finished = true; + } + } else { + assert(src->finished); + } + } + + waitMs = (wait < waitMs ? wait : waitMs); + srcIter++; + } + +// std::cout << "W" << waitMs << "."; + + INST->_mutex.unlock(); + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(waitMs)); + } +} + +void OpenALInvoker::cancel(const std::string sendId) { + tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); + +} + +void OpenALInvoker::invoke(const InvokeRequest& req) { + _alDevice = alcOpenDevice(NULL); + if (_alDevice == NULL) { + throw std::string("__FILE__ __LINE__ openal error opening device"); + } + + // create new context with device + _alContext = alcCreateContext (_alDevice, NULL); + if (_alContext == NULL) { + alcCloseDevice (_alDevice); + throw std::string("openal error create context"); + } + +// alcMakeContextCurrent(_alContext); +// float listener[3] = {0,0,0}; +// alListenerfv(AL_POSITION, listener); +// +// float orientation[6] = { +// 0.0, 0.0, -1.0, // direction +// 0.0, 1.0, 0.0 }; //up +// alListenerfv(AL_ORIENTATION, orientation); +// +// float velocity[3] = { 0.0, 0.0, 0.0}; //up +// alListenerfv(AL_VELOCITY, velocity); +// +// alListenerf(AL_GAIN, 0.5); + + start(); +} + +void OpenALInvoker::notifyOfEnd(OpenALSource* src) { + Event ev; + ev.name = "audio.end"; + ev.data.compound["file"] = src->file; + returnEvent(ev); +} + +void OpenALInvoker::notifyOfLoop(OpenALSource* src) { + Event ev; + ev.name = "audio.loop"; + ev.data.compound["file"] = src->file; + returnEvent(ev); +} + +void OpenALInvoker::getPosFromParams(const std::multimap<std::string, std::string>& params, float* position) { + // vector explicitly given + try { + if (params.find("x") != params.end()) + position[0] = boost::lexical_cast<float>(params.find("x")->second); + if (params.find("y") != params.end()) + position[1] = boost::lexical_cast<float>(params.find("y")->second); + if (params.find("z") != params.end()) + position[2] = boost::lexical_cast<float>(params.find("z")->second); + } catch (boost::bad_lexical_cast& e) { + LOG(ERROR) << "Cannot interpret x, y or z as float value in params: " << e.what(); + } + + try { + // right is an alias for x + if (params.find("right") != params.end()) + position[0] = boost::lexical_cast<float>(params.find("right")->second); + // height is an alias for y + if (params.find("height") != params.end()) + position[1] = boost::lexical_cast<float>(params.find("height")->second); + // front is an alias for z + if (params.find("front") != params.end()) + position[2] = boost::lexical_cast<float>(params.find("front")->second); + } catch (boost::bad_lexical_cast& e) { + LOG(ERROR) << "Cannot interpret right, height or front as float value in params: " << e.what(); + } + + // do we have a position on a circle? + try { + if (params.find("circle") != params.end()) { + float rad = posToRadian(params.find("circle")->second); + position[0] = cosf(rad); + position[2] = -1 * sinf(rad); // z axis increases to front + position[0] *= 150; + position[2] *= 150; + + } + } catch (boost::bad_lexical_cast& e) { + LOG(ERROR) << "Cannot interpret circle as float value in params: " << e.what(); + } + +// position[0] = position[0] / _maxPos[0]; +// position[1] = position[1] / _maxPos[1]; +// position[2] = position[2] / _maxPos[2]; + // std::cout << _pos[0] << ":" << _pos[1] << ":" << _pos[2] << std::endl; + +} + +float OpenALInvoker::posToRadian(const std::string& pos) { + + std::string trimmedPos = boost::trim_copy(pos); + float rad = 0; + + if (trimmedPos.size() > 3 && boost::iequals("deg", trimmedPos.substr(trimmedPos.length() - 3, 3))) { + rad = boost::lexical_cast<float>(trimmedPos.substr(0, trimmedPos.size() - 3)); + rad = fmodf(rad, 360); // into range [0-360] + rad /= 180; // into range [0-2] + rad *= M_PI; // into range [0-2PI] + rad -= M_PI_2; // 0 to top; + rad *= -1; // make clockwise + rad += 2 * M_PI; // make positive + } else if (trimmedPos.size() > 3 && boost::iequals("rad", trimmedPos.substr(trimmedPos.length() - 3, 3))) { + rad = boost::lexical_cast<float>(trimmedPos.substr(0, trimmedPos.size() - 3)); + rad = fmodf(rad, M_PI * 2); // into range [0-2*PI] + } else { + LOG(ERROR) << "Cannot make sense of position value " << trimmedPos << ": does not end in 'deg', 'rad'"; + } + return rad; +} + +OpenALSource::OpenALSource() { + pos[0] = pos[1] = pos[2] = 0; + player = NULL; + loop = false; + finished = false; + transform = NULL; + read = written = 0; + memset(buffer, 0, ALPLAY_AUDIO_BUFFER_SIZE); +} + +OpenALSource::~OpenALSource() { + if (player) + delete player; + if (transform) + delete transform; +} + +}
\ No newline at end of file diff --git a/src/uscxml/plugins/invoker/audio/OpenALInvoker.h b/src/uscxml/plugins/invoker/audio/OpenALInvoker.h new file mode 100644 index 0000000..c0ed842 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/OpenALInvoker.h @@ -0,0 +1,88 @@ +#ifndef OPENALINVOKER_H_W09J90F0 +#define OPENALINVOKER_H_W09J90F0 + +#include "uscxml/config.h" +#include <uscxml/Interpreter.h> +#include "OpenALPlayer.h" + +#include "PCMConverter.h" +#ifdef LIBSNDFILE_FOUND +# include "LibSoundFile.h" +#else +# ifdef AUDIOTOOLBOX_FOUND +# include "AudioToolbox.h" +# endif +#endif + +#ifdef BUILD_AS_PLUGINS +#include "uscxml/plugins/Plugins.h" +#endif + +namespace uscxml { + +class OpenALSource { +public: + OpenALSource(); + ~OpenALSource(); + + OpenALPlayer* player; + char buffer[ALPLAY_AUDIO_BUFFER_SIZE]; + bool loop; + bool finished; + int read; + int written; + float pos[3]; + URL file; + PCMConverter* transform; +}; + +class OpenALInvoker : public InvokerImpl { +public: + OpenALInvoker(); + virtual ~OpenALInvoker(); + virtual boost::shared_ptr<InvokerImpl> create(InterpreterImpl* interpreter); + + virtual std::set<std::string> getNames() { + std::set<std::string> names; + names.insert("openal"); + names.insert("spatial-audio"); + names.insert("http://uscxml.tk.informatik.tu-darmstadt.de/#openal"); + return names; + } + + virtual Data getDataModelVariables(); + virtual void send(const SendRequest& req); + virtual void cancel(const std::string sendId); + virtual void invoke(const InvokeRequest& req); + +protected: + std::map<std::string, OpenALSource*> _sources; + ALCcontext* _alContext; + ALCdevice* _alDevice; + + tthread::recursive_mutex _mutex; + tthread::thread* _thread; + tthread::condition_variable _sourcesAvailable; + + bool _isStarted; + bool _isRunning; + + static void fillBuffers(void* userdata); + void start(); + + void notifyOfEnd(OpenALSource*); + void notifyOfLoop(OpenALSource*); + + float posToRadian(const std::string& pos); + void getPosFromParams(const std::multimap<std::string, std::string>& params, float* position); + +}; + +#ifdef BUILD_AS_PLUGINS +PLUMA_INHERIT_PROVIDER(OpenALInvoker, InvokerImpl); +#endif + +} + + +#endif /* end of include guard: OPENALINVOKER_H_W09J90F0 */ diff --git a/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp b/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp new file mode 100644 index 0000000..4266f79 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp @@ -0,0 +1,523 @@ +#include "OpenALPlayer.h" +#include <assert.h> +#include <stdexcept> + +tthread::recursive_mutex OpenALPlayer::_alMutex; + +/** +* Create a new OpenAL stream source +*/ +OpenALPlayer::OpenALPlayer(ALCcontext* context, OpenALPlayerCallback* audioCallback, ALenum format, ALsizei freq) { + + _isInitialized = false; + _audioCallback = audioCallback; + _freq = freq; + _format = format; + _bufferSize = 0; + _nrBuffers = 0; + _thread = NULL; + _alId = 0; + + _position[0] = _position[1] = _position[2] = 0; + _velocity[0] = _velocity[1] = _velocity[2] = 0; + _direction[0] = _direction[1] = _direction[2] = 0; + + tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex); + if (context == NULL) { + // this is in essence alutInit() from freealut. + + // use the current context if there is one + _context = alcGetCurrentContext(); + + if (_context == NULL) { +// std::cout << "\tnew context" << std::endl; + // create a new context if none was given and no is current + + // get default device + ALCdevice* device = alcOpenDevice(NULL); + if (device == NULL) { + throw std::runtime_error("__FILE__ __LINE__ openal error opening device"); + } + + // create new context with device + _context = alcCreateContext (device, NULL); + if (_context == NULL) { + alcCloseDevice (device); + throw std::runtime_error("openal error create context"); + } + + // make context current + if (!alcMakeContextCurrent (_context)) { + alcDestroyContext (_context); + alcCloseDevice (device); + throw std::runtime_error("openal error make context current"); + } + } else { +// std::cout << "\texisting context" << std::endl; + } + } else { +// std::cout << "\tgiven context" << std::endl; + _context = context; + } +} + +OpenALPlayer::~OpenALPlayer() { + tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex); + if (isPlaying()) { + alSourceStop(_alId); + } + if (_thread) { + stop(); + _thread->join(); + delete(_thread); + } + + if (_isInitialized) { + alDeleteSources(1, &_alId); + if (alIsSource(_alId)) { + throw std::runtime_error("openal source id still valid"); + } + for (int i = 0; i < _nrBuffers; i++) { + assert(alIsBuffer(_bufferIds[i])); + free(_buffers[i]); + } + alDeleteBuffers(_nrBuffers, _bufferIds); + for (int i = 0; i < _nrBuffers; i++) { + assert(!alIsBuffer(_bufferIds[i])); + } + free(_buffers); + free(_bufferIds); + } + // clear errors and begone + alGetError(); + +} + +/** +* Allocate; data and set defaults +*/ +void OpenALPlayer::init() { + _userData = NULL; + _pitch = 0; + _gain = 0; + _referenceDistance = 1.0; + _isLooping = false; + + // no one set a buffer size yet + if (_bufferSize <= 0) + _bufferSize = ALPLAY_AUDIO_BUFFER_SIZE; + + // no one set the number of buffers yet + if (_nrBuffers <= 0) + _nrBuffers = ALPLAY_NR_AUDIO_BUFFERS; + + _isInitialized = true; + + _buffers = (char**)malloc(_nrBuffers * sizeof(char*)); + _bufferIds = (ALuint*)malloc(_nrBuffers * sizeof(ALuint)); + for (int i = 0; i < _nrBuffers; i++) { + _buffers[i] = 0; //(char*)malloc(_bufferSize); + } + + // there are other formats as well and this will have to be extended + int bytesPerSample = 2; + switch(_format) { + case AL_FORMAT_MONO8: + case AL_FORMAT_STEREO8: + bytesPerSample = 1; + break; + + case AL_FORMAT_MONO16: + case AL_FORMAT_STEREO16: + bytesPerSample = 2; + break; + } + + // how many ms audio is in one buffer? + _msForBuffer = (int)(((float)_bufferSize / (float)bytesPerSample) / ((float)_freq / 1000.0f)); + _initialSleep = (_msForBuffer - (int)(0.6 * _msForBuffer)) * _nrBuffers; + _bufferSleep = _msForBuffer - (int)(0.4 * _msForBuffer); + _repollSleep = _msForBuffer - (int)(0.7 * _msForBuffer); + +// std::cout << _msForBuffer << "ms in one buffer" << std::endl; + + // get available buffer ids + tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex); + + if (!alcMakeContextCurrent (_context)) { + throw std::runtime_error("openal error make context current"); + } + + alGenBuffers(_nrBuffers, _bufferIds); + checkOpenALError(__LINE__); + + // get new source id from openAL + alGenSources(1, &_alId); + + checkOpenALError(__LINE__); + if (!alIsSource(_alId)) { + throw std::runtime_error("openal source id not valid"); + } + + // set our position and various flags to meaningful defaults + alSourcei(_alId, AL_LOOPING, AL_FALSE); + checkOpenALError(__LINE__); + alSourcefv(_alId, AL_POSITION, _position); + checkOpenALError(__LINE__); + alSourcef(_alId,AL_REFERENCE_DISTANCE, 5.0f); + checkOpenALError(__LINE__); +// alDistanceModel(AL_LINEAR_DISTANCE); +// checkOpenALError(__LINE__); +// alSourcefv(_alId, AL_VELOCITY, _velocity); +// checkOpenALError(__LINE__); +// alSourcefv(_alId, AL_DIRECTION, _direction); +// checkOpenALError(__LINE__); +// alSourcef (_alId, AL_ROLLOFF_FACTOR, 1.0); +// checkOpenALError(__LINE__); + alSourcef(_alId,AL_REFERENCE_DISTANCE, 5.0f); + checkOpenALError(__LINE__); +// float listener[] = { 0.0, 0.0, 0.0 }; +// alListenerfv(AL_POSITION, listener); +// checkOpenALError(__LINE__); + + alSourcei (_alId, AL_SOURCE_RELATIVE, AL_TRUE); + checkOpenALError(__LINE__); +} + +/** +* Start the sound source. +* +* This will trigger continuous calls top the audio callback. +*/ +void OpenALPlayer::start() { + if (!_isInitialized) + init(); + + if (_audioCallback == NULL) + throw std::runtime_error("cannot start without an audio callback"); + + _isStarted = true; + + // prime the buffers with some initial data and register for buffer ids + tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex); + for (ALuint i = 0; i < (unsigned int)_nrBuffers; i++) { + _buffers[i] = (char*)malloc(_bufferSize); + _audioCallback->getSamples(_buffers[i], _bufferSize, this); + alBufferData(_bufferIds[i], _format, _buffers[i], _bufferSize, _freq); + checkOpenALError(__LINE__); + } + // enqueue all buffers + alSourceQueueBuffers(_alId, _nrBuffers, _bufferIds); + checkOpenALError(__LINE__); + + // start thread + if (_audioCallback != NULL) { + _thread = new tthread::thread(&OpenALPlayer::updateBuffersWrapper, this); + } + + // tell openAL to start rendering the buffers + alSourcePlay(_alId); + checkOpenALError(__LINE__); +} + +// find bufferId in _bufferIds to get bufferIndex into _buffers - messy +int OpenALPlayer::bufferIndex(int bufferId) { + int bufferIndex = 0; + for (; bufferIndex < _nrBuffers; bufferIndex++) { + if (_bufferIds[bufferIndex] == (unsigned int)bufferId) + break; + } + if (bufferIndex >= _nrBuffers) + throw std::runtime_error("could not find dequeued bufferId in ids"); + return bufferIndex; +} + +/** +* Write a buffer (blocking). +* +* This allows for a pushing model, whereas the callback allows for a polling model. +*/ +int OpenALPlayer::write(char* buffer, int size, int* repollAt, bool blocking) { + tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex); + + if (!_isInitialized) + init(); + + if (_audioCallback != NULL) { + throw std::runtime_error("you cannot use the write interface with an audio callback"); + } + + if (size != _bufferSize) { + throw std::runtime_error("buffersize does not match"); + } + + if (!alcMakeContextCurrent (_context)) { + throw std::runtime_error("openal error make context current"); + } + + // try to enqueue the given buffer data + for (;;) { + // do we have an empty buffer in the OpenAL queue? + int processed; + alGetSourcei(_alId, AL_BUFFERS_PROCESSED, &processed); + checkOpenALError(__LINE__); + +// if (!isPlaying()) +// std::cout << "-"; + + if (processed > 0) { +// std::cout << "P" << processed; + ALuint bufferId = 0; + alSourceUnqueueBuffers(_alId, 1, &bufferId); + checkOpenALError(__LINE__); + + int bufferIdx = bufferIndex(bufferId); + + // fill the buffer with the given data + memcpy(_buffers[bufferIdx], buffer, _bufferSize); + alBufferData(bufferId, _format, _buffers[bufferIdx], _bufferSize, _freq); + checkOpenALError(__LINE__); + + // enqueue + alSourceQueueBuffers(_alId, 1, &bufferId); + checkOpenALError(__LINE__); + + // some buffers were processed + if (repollAt) + *repollAt = _repollSleep; + break; + + } else { + // no buffer processed - is there an uninitialized buffer left? + int nextBuffer = 0; + for(; nextBuffer < _nrBuffers; nextBuffer++) { + if (_buffers[nextBuffer] == 0) { + break; + } + } + if (nextBuffer < _nrBuffers) { +// std::cout << "N"; + _buffers[nextBuffer] = (char*)malloc(_bufferSize); + memcpy(_buffers[nextBuffer], buffer, _bufferSize); + + alBufferData(_bufferIds[nextBuffer], _format, _buffers[nextBuffer], _bufferSize, _freq); + checkOpenALError(__LINE__); + + alSourceQueueBuffers(_alId, 1, &_bufferIds[nextBuffer]); + checkOpenALError(__LINE__); + // there was a free buffer, repoll immediately to try to write more + if (repollAt) + *repollAt = 0; + + break; + } else { +// std::cout << "X"; + // no processed, no new buffer, wait until we processed one + if (blocking) { + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_repollSleep)); + } else { + if (repollAt) + *repollAt = _repollSleep; + return -1; + } + } + } + } + + // we have at least one buffer queued, start playing + if (!_isStarted || !isPlaying()) { + alSourcePlay(_alId); + checkOpenALError(__LINE__); + _isStarted = true; + } + + return size; +} + + +/** +* Dequeue, refill and re-enqueue buffers. +*/ +void OpenALPlayer::updateBuffers() { + int processed; +// int queued; + +// std::cout << "Initial sleep: " << initialSleep << "ms" << std::endl; +// std::cout << "Buffer sleep: " << bufferSleep << "ms" << std::endl; +// std::cout << "Repoll sleep: " << repollSleep << "ms" << std::endl; + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_bufferSleep * _initialSleep)); + + + while(_isStarted) { + + // how many buffers have been rendered already? + tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex); + alGetSourcei(_alId, AL_BUFFERS_PROCESSED, &processed); + checkOpenALError(__LINE__); + //std::cout << processed << std::flush; + + if (processed == 0) { + // avoid busy wait by sleeping + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_bufferSleep * _initialSleep)); + } else { + // dequeue buffers and get ids + // see http://stackoverflow.com/questions/1900665/c-compiler-differences-vs2008-and-g + ALuint bufferIds[ALPLAY_NR_AUDIO_BUFFERS]; + alSourceUnqueueBuffers(_alId, processed, bufferIds); + checkOpenALError(__LINE__); + + for (int id = 0; id < processed; id++) { + int bufferIdx = bufferIndex(bufferIds[id]); + + // refill the buffer with data from the callback + _audioCallback->getSamples(_buffers[bufferIdx], _bufferSize, this); + alBufferData(bufferIds[id], _format, _buffers[bufferIdx], _bufferSize, _freq); + checkOpenALError(__LINE__); + + } + // re-enqueue + alSourceQueueBuffers(_alId, processed, bufferIds); + checkOpenALError(__LINE__); + + // restart if we are not running anymore + if (!isPlaying()) { + alSourcePlay(_alId); + checkOpenALError(__LINE__); + } + + // sleep a bit less than the duration of one buffer + tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_bufferSleep * processed)); + } + } +} + +/** +* TODO +*/ +void OpenALPlayer::stop() { + _isStarted = false; + _thread->join(); +} + +void OpenALPlayer::checkOpenALError(int line) { + int error = alGetError(); + if(error != AL_NO_ERROR) { + std::stringstream out; + out << "OpenALError:" << line << ":"; + + switch (error) { + case AL_INVALID_NAME: + out << "OpenAL invalid name."; + break; + case AL_INVALID_ENUM: + out << "OpenAL invalid enum."; + break; + case AL_INVALID_VALUE: + out << "OpenAL invalid value."; + break; + case AL_INVALID_OPERATION: + out << "OpenAL invalid operation."; + break; + case AL_OUT_OF_MEMORY: + out << "OpenAL out of memory."; + break; + + default: + out << "OpenAL unknown error."; + break; + } + throw std::runtime_error(out.str()); + } +} + +unsigned int OpenALPlayer::isPlaying() { + ALint val; + alGetSourcei(_alId, AL_SOURCE_STATE, &val); + if(val != AL_PLAYING) + return 0; + return _repollSleep; +} + +void OpenALPlayer::updateBuffersWrapper(void *obj) { + try { + reinterpret_cast<OpenALPlayer *>(obj)->updateBuffers(); + } catch(std::runtime_error& error) { +// std::cout << "Terminating Thread: " << error << std::endl; + } catch(...) { +// std::cout << "Terminating Thread! " << std::endl; + } +} + +void OpenALPlayer::setNrBuffers(int nrBuffers) { + if (_nrBuffers > 0) + throw std::runtime_error("cannot modify number of buffers"); + _nrBuffers = nrBuffers; +} + +int OpenALPlayer::getNrBuffers() { + return _nrBuffers; +} + +/** +* Set position of sound source in coordinate system +*/ +void OpenALPlayer::setPosition(ALfloat position[]) { + memcpy(&_position, position, 3 * sizeof(ALfloat)); +// std::cout << _position[0] << ", " << _position[1] << ", " << _position[2] << std::endl; + if (_isInitialized) + alSourcefv(_alId, AL_POSITION, _position); +} + +ALfloat* OpenALPlayer::getPosition() { + return _position; +} + +/** +* Set velocity of sound source in coordinate system +*/ +void OpenALPlayer::setVelocity(ALfloat velocity[]) { + memcpy(&_velocity, velocity, 3 * sizeof(ALfloat)); + if (_isInitialized) + alSourcefv(_alId, AL_VELOCITY, _velocity); +} + +ALfloat* OpenALPlayer::getVelocity() { + return _velocity; +} + +/** +* Set direction of sound source in coordinate system +*/ +void OpenALPlayer::setDirection(ALfloat direction[]) { + memcpy(&_direction, direction, 3 * sizeof(ALfloat)); + if (_isInitialized) + alSourcefv(_alId, AL_DIRECTION, _direction); +} + +ALfloat* OpenALPlayer::getDirection() { + return _direction; +} + +void OpenALPlayer::setBufferSize(int bufferSize) { + if (_bufferSize > 0) + throw std::runtime_error("cannot modify buffersize"); + _bufferSize = bufferSize; +} + +int OpenALPlayer::getBufferSize() { + return _bufferSize; +} + +OpenALPlayerCallback* OpenALPlayer::getCallback() { + return _audioCallback; +} +void OpenALPlayer::setCallback(OpenALPlayerCallback* callback) { + _audioCallback = callback; +} + +void* OpenALPlayer::getUserData() { + return _userData; +} +void OpenALPlayer::setUserData(void* userData) { + _userData = userData; +} diff --git a/src/uscxml/plugins/invoker/audio/OpenALPlayer.h b/src/uscxml/plugins/invoker/audio/OpenALPlayer.h new file mode 100644 index 0000000..4d7d189 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/OpenALPlayer.h @@ -0,0 +1,106 @@ +#ifndef OPENALPLAYER_H_3PORVJDU +#define OPENALPLAYER_H_3PORVJDU + +#include <iostream> +#include <string.h> +#include <stdlib.h> +#include <sstream> +#include <uscxml/concurrency/tinythread.h> + +#include <al.h> +#include <alc.h> + +// sometimes the stream drops if this is less than 5000 bytes +#define ALPLAY_NR_AUDIO_BUFFERS 3 +#define ALPLAY_AUDIO_BUFFER_SIZE 2048 +//#define ALPLAYER_FORMAT_MONO16 0x1101 + +class OpenALPlayer; + +class OpenALPlayerCallback { +public: + virtual ~OpenALPlayerCallback() {} + /* + * Returning an OpenALPlayerCallback is a hack to be able to provide a typemap for SWIG. + * We cannot use SWIG directors with byte arrays otherwise. Posted to swig-ML already. + */ + virtual OpenALPlayerCallback* getSamples(char* buffer, int size, OpenALPlayer* player) = 0; +}; + +class OpenALPlayer { +private: + ALCcontext* _context; + + ALuint _alId; + ALfloat _position[3]; + ALfloat _velocity[3]; + ALfloat _direction[3]; + ALfloat _pitch; + ALfloat _gain; + ALfloat _referenceDistance; + ALboolean _isLooping; + + // OpenAL is not really thread safe + static tthread::recursive_mutex _alMutex; + + ALenum _format; + ALsizei _freq; + int _msForBuffer; + int _initialSleep; + int _bufferSleep; + int _repollSleep; + + int _bufferSize; + int _nrBuffers; + ALuint* _bufferIds; + char** _buffers; + OpenALPlayerCallback* _audioCallback; + void* _userData; + + tthread::thread* _thread; + bool _isStarted; + bool _isInitialized; + + void updateBuffers(); + void init(); + void checkOpenALError(int line); + + // static wrapper as an entry point for pthreads + static void updateBuffersWrapper(void *obj); + + // get the index in _buffers for a buffer ID + int inline bufferIndex(int bufferId); + +public: + OpenALPlayer(ALCcontext*, OpenALPlayerCallback*, ALenum, ALsizei); + virtual ~OpenALPlayer(); + + unsigned int isPlaying(); + + ALfloat* getPosition(); + void setPosition(ALfloat[3]); + ALfloat* getVelocity(); + void setVelocity(ALfloat[3]); + ALfloat* getDirection(); + void setDirection(ALfloat[3]); + + void setBufferSize(int bufferSize); + int getBufferSize(); + void setNrBuffers(int nrBuffers); + int getNrBuffers(); + + // callback interface for pull + OpenALPlayerCallback* getCallback(); + void setCallback(OpenALPlayerCallback* callback); + + // stream interface for push + int write(char* buffer, int size, int* repollAt, bool blocking = false); + + void* getUserData(); + void setUserData(void* userData); + + void start(); + void stop(); +}; + +#endif /* end of include guard: OPENALPLAYER_H_3PORVJDU */ diff --git a/src/uscxml/plugins/invoker/audio/PCMConverter.h b/src/uscxml/plugins/invoker/audio/PCMConverter.h new file mode 100644 index 0000000..6036af2 --- /dev/null +++ b/src/uscxml/plugins/invoker/audio/PCMConverter.h @@ -0,0 +1,30 @@ +#ifndef PCMCONVERTER_H_97Z8U7PA +#define PCMCONVERTER_H_97Z8U7PA + +#include <string> +#include <al.h> +#include <alc.h> + +namespace uscxml { + +struct PCMFormat { + ALenum alFormat; + unsigned int sampleRate; +}; + +class PCMConverter { +public: + PCMConverter(const std::string filename) {} + virtual ~PCMConverter() {} + virtual void seek(unsigned int pos) = 0; + virtual int read(char* buffer, unsigned int size) = 0; + + virtual void setOutFormat(const PCMFormat& format) = 0; + virtual PCMFormat getInFormat() = 0; +protected: + PCMConverter() {} +}; + +} + +#endif /* end of include guard: PCMCONVERTER_H_97Z8U7PA */ diff --git a/src/uscxml/plugins/invoker/xhtml/XHTMLInvoker.cpp b/src/uscxml/plugins/invoker/xhtml/XHTMLInvoker.cpp index 82e0a48..f53eb67 100644 --- a/src/uscxml/plugins/invoker/xhtml/XHTMLInvoker.cpp +++ b/src/uscxml/plugins/invoker/xhtml/XHTMLInvoker.cpp @@ -186,7 +186,7 @@ void XHTMLInvoker::cancel(const std::string sendId) { void XHTMLInvoker::invoke(const InvokeRequest& req) { _invokeReq = req; - HTTPServer::registerServlet(_interpreter->getName() + "/xhtml/" + req.invokeid + ".html", this); + HTTPServer::registerServlet(_interpreter->getName() + "/" + req.invokeid + ".html", this); #if __APPLE__ # if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR # else diff --git a/src/uscxml/server/HTTPServer.cpp b/src/uscxml/server/HTTPServer.cpp index 7e1a858..f4b1a57 100644 --- a/src/uscxml/server/HTTPServer.cpp +++ b/src/uscxml/server/HTTPServer.cpp @@ -346,7 +346,7 @@ bool HTTPServer::registerServlet(const std::string& path, HTTPServlet* servlet) INSTANCE->_servlets[suffixedPath] = servlet; -// LOG(INFO) << "HTTP Servlet listening at: " << servletURL.str() << std::endl; + LOG(INFO) << "HTTP Servlet listening at: " << servletURL.str() << std::endl; // register callback evhttp_set_cb(INSTANCE->_http, ("/" + suffixedPath).c_str(), HTTPServer::httpRecvReqCallback, servlet); |