diff --git a/include/remote_plugin.h b/include/remote_plugin.h new file mode 100755 index 000000000..e678d4f9e --- /dev/null +++ b/include/remote_plugin.h @@ -0,0 +1,657 @@ +/* + * remote_plugin.h - base class providing RPC like mechanisms + * + * Copyright (c) 2008 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#ifndef _REMOTE_PLUGIN_H +#define _REMOTE_PLUGIN_H + +#include "lmmsconfig.h" +#include "mixer.h" +#include "midi.h" + +#ifdef LMMS_HAVE_UNISTD_H +#include +#endif + +#include +#include + +#ifdef LMMS_HOST_X86_64 +#define USE_NATIVE_SHM_API +#else +#if QT_VERSION >= 0x040400 +#include +#else +#define USE_NATIVE_SHM_API +#endif +#endif + +#ifdef USE_NATIVE_SHM_API +#ifdef LMMS_HAVE_SYS_IPC_H +#include +#endif + +#ifdef LMMS_HAVE_SYS_SHM_H +#include +#endif +#endif + + +#ifdef BUILD_REMOTE_PLUGIN_CLIENT +#define COMPILE_REMOTE_PLUGIN_BASE +#endif + + +inline int32_t readInt( int _fd ) +{ + int32_t i; + if( read( _fd, &i, sizeof( i ) ) == sizeof( i ) ) + { + return( i ); + } + return( 0 ); +} + + + + +inline bool writeInt( const int32_t & _i, int _fd ) +{ + if( write( _fd, &_i, sizeof( _i ) ) != sizeof( _i ) ) + { + return false; + } + return true; +} + + + + +static inline std::string readString( int _fd ) +{ + const int len = readInt( _fd ); + if( len ) + { + char * sc = new char[len + 1]; + if( read( _fd, sc, len ) == len ) + { + sc[len] = 0; + std::string s( sc ); + delete[] sc; + return( s ); + } + delete[] sc; + } + return std::string(); +} + + + + +static inline bool writeString( const std::string & _s, int _fd ) +{ + const int len = _s.size(); + writeInt( len, _fd ); + if( write( _fd, _s.c_str(), len ) != len ) + { + return false; + } + return true; +} + + +enum RemoteMessageIDs +{ + IdUndefined, + IdGeneralFailure, + IdInitDone, + IdClosePlugin, + IdDebugMessage, + IdSampleRateInformation, + IdBufferSizeInformation, + IdMidiEvent, + IdStartProcessing, + IdProcessingDone, + IdChangeSharedMemoryKey, + IdChangeInputCount, + IdChangeOutputCount, + IdShowUI, + IdHideUI, + IdSaveSettingsToString, + IdSaveSettingsToFile, + IdLoadSettingsFromString, + IdLoadSettingsFromFile, + IdLoadPresetFromFile, + IdUserBase = 64 +} ; + + + +class remotePluginBase +{ +public: + struct message + { + message() : + id( IdUndefined ), + data() + { + } + + message( const message & _m ) : + id( _m.id ), + data( _m.data ) + { + } + + message( int _id ) : + id( _id ), + data() + { + } + + void addInt( int _i ) + { + char buf[128]; + buf[0] = 0; + sprintf( buf, "%d", _i ); + data.push_back( std::string( buf ) ); + } + + int getInt( int _p ) const + { + return( atoi( data[_p].c_str() ) ); + } + + bool operator==( const message & _m ) + { + return( id == _m.id ); + } + + int id; + std::vector data; + } ; + + remotePluginBase( void ); + virtual ~remotePluginBase(); + + void sendMessage( const message & _m ); + message receiveMessage( void ); + + bool messagesLeft( void ) const; + message waitForMessage( const message & _m, + bool _busy_waiting = FALSE ); + + inline message fetchAndProcessNextMessage( void ) + { + message m = receiveMessage(); + processMessage( m ); + return m; + } + + inline bool fetchAndProcessAllMessages( void ) + { + while( messagesLeft() ) + { + fetchAndProcessNextMessage(); + } + } + + virtual bool processMessage( const message & _m ) = 0; + + +protected: + void setInFD( int _fd ) + { + m_inFD = _fd; + } + + void setOutFD( int _fd ) + { + m_outFD = _fd; + } + +private: + int m_inFD; + int m_outFD; + +} ; + + + +#ifndef BUILD_REMOTE_PLUGIN_CLIENT + +class remotePlugin : public remotePluginBase +{ +public: + remotePlugin( const QString & _plugin_executable ); + virtual ~remotePlugin(); + + virtual bool processMessage( const message & _m ); + + bool process( const sampleFrame * _in_buf, + sampleFrame * _out_buf, bool _wait ); + bool waitForProcessingFinished( sampleFrame * _out_buf ); + + void processMidiEvent( const midiEvent &, const f_cnt_t _offset ); + + void updateSampleRate( sample_rate_t _sr ) + { + message m( IdSampleRateInformation ); + m.addInt( _sr ); + sendMessage( m ); + } + + void showUI( void ) + { + sendMessage( IdShowUI ); + } + + void hideUI( void ) + { + sendMessage( IdHideUI ); + } + + +protected: + inline void lock( void ) + { + m_commMutex.lock(); + } + + inline void unlock( void ) + { + m_commMutex.unlock(); + } + + +private: + void resizeSharedMemory( void ); + + + bool m_initialized; + bool m_failed; + + int m_pluginPID; + int m_pipes[2][2]; + + QMutex m_commMutex; +#ifdef USE_NATIVE_SHM_API + int m_shmID; +#else + QSharedMemory m_shmObj; +#endif + size_t m_shmSize; + float * m_shm; + + int m_inputCount; + int m_outputCount; + +} ; + +#endif + + +#ifdef BUILD_REMOTE_PLUGIN_CLIENT + +class remotePluginClient : public remotePluginBase +{ +public: + remotePluginClient( void ); + virtual ~remotePluginClient(); + + virtual bool processMessage( const message & _m ); + + virtual bool process( const sampleFrame * _in_buf, + sampleFrame * _out_buf ) = 0; + + virtual void processMidiEvent( const midiEvent &, + const f_cnt_t /* _offset */ ) + { + } + + inline float * sharedMemory( void ) + { + return( m_shm ); + } + + virtual void updateSampleRate( sample_rate_t ) + { + } + + virtual void updateBufferSize( fpp_t ) + { + } + + inline fpp_t bufferSize( void ) const + { + return( m_bufferSize ); + } + + void debugMessage( const char * _fmt, ... ) + { + va_list ap; + char buffer[512]; + + va_start( ap, _fmt ); + buffer[0] = 0; + vsnprintf( buffer, sizeof( buffer ), _fmt, ap ); + message m( IdDebugMessage ); + m.data.push_back( std::string( buffer ) ); + sendMessage( m ); + va_end( ap ); + } + + void setInputCount( int _i ) + { + m_inputCount = _i; + message m( IdChangeInputCount ); + m.addInt( _i ); + sendMessage( m ); + } + + void setOutputCount( int _i ) + { + m_outputCount = _i; + message m( IdChangeOutputCount ); + m.addInt( _i ); + sendMessage( m ); + } + + +private: + void setShmKey( int _key, int _size ); + void doProcessing( void ); + +#ifndef USE_NATIVE_SHM_API + QSharedMemory m_shmObj; +#endif + float * m_shm; + + int m_inputCount; + int m_outputCount; + + fpp_t m_bufferSize; + +} ; + +#endif + + + + + +#ifdef COMPILE_REMOTE_PLUGIN_BASE + +#ifndef BUILD_REMOTE_PLUGIN_CLIENT +#include +#endif + + +remotePluginBase::remotePluginBase( void ) : + m_inFD( -1 ), + m_outFD( -1 ) +{ +} + + + + +remotePluginBase::~remotePluginBase() +{ +} + + + + +void remotePluginBase::sendMessage( const message & _m ) +{ + writeInt( _m.id, m_outFD ); + writeInt( _m.data.size(), m_outFD ); + for( int i = 0; i < _m.data.size(); ++i ) + { + writeString( _m.data[i], m_outFD ); + } +} + + + + +remotePluginBase::message remotePluginBase::receiveMessage( void ) +{ + message m; + m.id = readInt( m_inFD ); + const int s = readInt( m_inFD ); + for( int i = 0; i < s; ++i ) + { + m.data.push_back( readString( m_inFD ) ); + } + return( m ); +} + + + + +inline bool remotePluginBase::messagesLeft( void ) const +{ + fd_set rfds; + FD_ZERO( &rfds ); + FD_SET( m_inFD, &rfds ); + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 1; // can we use 0 here? + return( select( m_inFD + 1, &rfds, NULL, NULL, &tv ) > 0 ); +} + + + + +remotePluginBase::message remotePluginBase::waitForMessage( + const message & _wm, + bool _busy_waiting ) +{ + while( 1 ) + { + if( messagesLeft() ) + { + message m; + m = receiveMessage(); + processMessage( m ); + if( m.id == _wm.id ) + { + return( m ); + } + else if( m.id == IdGeneralFailure ) + { + return( m ); + } + } +#ifndef BUILD_REMOTE_PLUGIN_CLIENT + if( _busy_waiting ) + { + QCoreApplication::processEvents( + QEventLoop::AllEvents, 50 ); + } + else + { + usleep( 10 ); + } +#else + usleep( 10 ); +#endif + } +} + + +#endif + + + + + +#ifdef BUILD_REMOTE_PLUGIN_CLIENT + + +remotePluginClient::remotePluginClient( void ) : + remotePluginBase(), +#ifndef USE_NATIVE_SHM_API + m_shmObj(), +#endif + m_shm( NULL ), + m_inputCount( DEFAULT_CHANNELS ), + m_outputCount( DEFAULT_CHANNELS ), + m_bufferSize( DEFAULT_BUFFER_SIZE ) +{ + setInFD( 0 ); + setOutFD( 1 ); + sendMessage( IdSampleRateInformation ); + sendMessage( IdBufferSizeInformation ); +} + + + + +remotePluginClient::~remotePluginClient() +{ + sendMessage( IdClosePlugin ); + +#ifdef USE_NATIVE_SHM_API + shmdt( m_shm ); +#endif +} + + + + +bool remotePluginClient::processMessage( const message & _m ) +{ + message reply_message( _m.id ); + bool reply = false; + switch( _m.id ) + { + case IdGeneralFailure: + return( false ); + + case IdSampleRateInformation: + updateSampleRate( _m.getInt( 0 ) ); + break; + + case IdBufferSizeInformation: + m_bufferSize = _m.getInt( 0 ); + updateBufferSize( m_bufferSize ); + break; + + case IdClosePlugin: + return( false ); + + case IdMidiEvent: + processMidiEvent( + midiEvent( static_cast( + _m.getInt( 0 ) ), + _m.getInt( 1 ), + _m.getInt( 2 ), + _m.getInt( 3 ) ), + _m.getInt( 4 ) ); + break; + + case IdStartProcessing: + doProcessing(); + reply_message.id = IdProcessingDone; + reply = true; + break; + + case IdChangeSharedMemoryKey: + setShmKey( _m.getInt( 0 ), _m.getInt( 1 ) ); + break; + + case IdUndefined: + default: + break; + } + if( reply ) + { + sendMessage( reply_message ); + } + + return( true ); +} + + + + +void remotePluginClient::setShmKey( int _key, int _size ) +{ +#ifdef USE_NATIVE_SHM_API + if( m_shm != NULL ) + { + shmdt( m_shm ); + m_shm = NULL; + } + + // only called for detaching SHM? + if( _key == 0 ) + { + return; + } + + int shm_id = shmget( _key, _size, 0 ); + if( shm_id == -1 ) + { + debugMessage( "failed getting shared memory" ); + } + else + { + m_shm = (float *) shmat( shm_id, 0, 0 ); + } +#else + m_shmObj.setKey( QString::number( _key ) ); + if( m_shmObj.attach() ) + { + m_shm = (float *) m_shmObj.data(); + } + else + { + debugMessage( "failed getting shared memory" ); + } +#endif +} + + + + +void remotePluginClient::doProcessing( void ) +{ + if( m_shm != NULL ) + { + process( (sampleFrame *)( m_inputCount > 0 ? m_shm : NULL ), + (sampleFrame *)( m_shm + + ( m_inputCount*m_bufferSize ) ) ); + } +} + + + +#endif + +#endif diff --git a/src/core/remote_plugin.cpp b/src/core/remote_plugin.cpp new file mode 100644 index 000000000..c8eb4b612 --- /dev/null +++ b/src/core/remote_plugin.cpp @@ -0,0 +1,370 @@ +/* + * remote_plugin.cpp - base class providing RPC like mechanisms + * + * Copyright (c) 2008 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + + +#define COMPILE_REMOTE_PLUGIN_BASE + +#include "remote_plugin.h" +#include "lmmsconfig.h" +#include "engine.h" +#include "config_mgr.h" + +#include +#include + +#ifdef LMMS_HAVE_UNISTD_H +#include +#endif + +#ifdef LMMS_HAVE_SIGNAL_H +#include +#endif + +#include + +#ifdef LMMS_HAVE_SYS_SELECT_H +#include +#endif + +#ifdef LMMS_HAVE_SYS_TIME_H +#include +#endif + +#ifdef LMMS_HAVE_SYS_WAIT_H +#include +#endif + + + + + + + +remotePlugin::remotePlugin( const QString & _plugin_executable ) : + remotePluginBase(), + m_initialized( false ), + m_failed( true ), + m_pluginPID( -1 ), + m_commMutex( QMutex::Recursive ), +#ifdef USE_NATIVE_SHM_API + m_shmID( 0 ), +#else + m_shmObj(), +#endif + m_shmSize( 0 ), + m_shm( NULL ), + m_inputCount( DEFAULT_CHANNELS ), + m_outputCount( DEFAULT_CHANNELS ) +{ + QString fe = configManager::inst()->pluginDir() + + QDir::separator() + _plugin_executable; + if( pipe( m_pipes[0] ) || pipe( m_pipes[1] ) ) + { + printf( "error while creating pipes!\n" ); + } + + if( ( m_pluginPID = fork() ) < 0 ) + { + printf( "fork() failed!\n" ); + return; + } + else if( m_pluginPID == 0 ) + { + dup2( m_pipes[0][0], 0 ); + dup2( m_pipes[1][1], 1 ); + execlp( fe.toAscii().constData(), fe.toAscii().constData(), + NULL ); + exit( 0 ); + } + setInFD( m_pipes[1][0] ); + setOutFD( m_pipes[0][1] ); + + resizeSharedMemory(); + + lock(); + m_failed = waitForMessage( IdInitDone ).id != IdInitDone; + unlock(); +} + + + + +remotePlugin::~remotePlugin() +{ + if( m_failed == false ) + { + sendMessage( IdClosePlugin ); + + // wait for acknowledge + QTime t; + t.start(); + while( t.elapsed() < 1000 ) + { + if( messagesLeft() && + receiveMessage().id == IdClosePlugin ) + { + //m_pluginPID = 0; + break; + } + usleep( 10 ); + } + // timeout? +/* if( m_pluginPID != 0 ) + {*/ + kill( m_pluginPID, SIGTERM ); + kill( m_pluginPID, SIGKILL ); + //} + // remove process from PCB + waitpid( m_pluginPID, NULL, 0 ); + + // close all sides of our pipes + close( m_pipes[0][0] ); + close( m_pipes[0][1] ); + close( m_pipes[1][0] ); + close( m_pipes[1][1] ); + +#ifdef USE_NATIVE_SHM_API + shmdt( m_shm ); + shmctl( m_shmID, IPC_RMID, NULL ); +#endif + } +} + + + + + +bool remotePlugin::process( const sampleFrame * _in_buf, + sampleFrame * _out_buf, bool _wait ) +{ + const fpp_t frames = engine::getMixer()->framesPerPeriod(); + + if( m_shm == NULL ) + { + // m_shm being zero means we didn't initialize everything so + // far so process one message each time (and hope we get + // information like SHM-key etc.) until we process messages + // in a later stage of this procedure + if( m_shmSize == 0 ) + { + lock(); + fetchAndProcessAllMessages(); + unlock(); + } + if( _out_buf != NULL ) + { + engine::getMixer()->clearAudioBuffer( _out_buf, + frames ); + } + return( false ); + } + + memset( m_shm, 0, m_shmSize ); + + ch_cnt_t inputs = tMax( m_inputCount, DEFAULT_CHANNELS ); + + if( _in_buf != NULL && inputs > 0 ) + { + for( ch_cnt_t ch = 0; ch < inputs; ++ch ) + { + for( fpp_t frame = 0; frame < frames; ++frame ) + { + m_shm[ch * frames + frame] = _in_buf[frame][ch]; + } + } + } + + lock(); + sendMessage( IdStartProcessing ); + + m_initialized = TRUE; + if( _wait ) + { + waitForProcessingFinished( _out_buf ); + } + unlock(); + return( TRUE ); +} + + + + +bool remotePlugin::waitForProcessingFinished( sampleFrame * _out_buf ) +{ + if( !m_initialized || _out_buf == NULL || m_outputCount == 0 ) + { + return( false ); + } + + lock(); + waitForMessage( IdProcessingDone ); + unlock(); + + const fpp_t frames = engine::getMixer()->framesPerPeriod(); + const ch_cnt_t outputs = tMax( m_outputCount, + DEFAULT_CHANNELS ); + if( outputs != DEFAULT_CHANNELS ) + { + // clear buffer, if plugin didn't fill up both channels + engine::getMixer()->clearAudioBuffer( _out_buf, frames ); + } + + sampleFrame * o = (sampleFrame *) ( m_shm + m_inputCount*frames ); + for( ch_cnt_t ch = 0; ch < outputs; ++ch ) + { + for( fpp_t frame = 0; frame < frames; ++frame ) + { + _out_buf[frame][ch] = o[frame][ch]; + } + } + + return( TRUE ); +} + + + + +void remotePlugin::processMidiEvent( const midiEvent & _e, + const f_cnt_t _offset ) +{ + message m( IdMidiEvent ); + m.addInt( _e.m_type ); + m.addInt( _e.m_channel ); + m.addInt( _e.m_data.m_param[0] ); + m.addInt( _e.m_data.m_param[1] ); + m.addInt( _offset ); + lock(); + sendMessage( m ); + unlock(); +} + + + + +void remotePlugin::resizeSharedMemory( void ) +{ + const size_t s = ( m_inputCount+m_outputCount ) * + engine::getMixer()->framesPerPeriod() * + sizeof( float ); + if( m_shm != NULL ) + { +#ifdef USE_NATIVE_SHM_API + shmdt( m_shm ); + shmctl( m_shmID, IPC_RMID, NULL ); +#else + m_shmObj.detach(); +#endif + } + + int shm_key = 0; +#ifdef USE_NATIVE_SHM_API + while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL | + 0600 ) ) == -1 ) + { + } + + m_shm = (float *) shmat( m_shmID, 0, 0 ); +#else + do + { + m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) ); + m_shmObj.create( s ); + } while( m_shmObj.error() != QSharedMemory::NoError ); + + m_shm = (float *) m_shmObj.data(); +#endif + m_shmSize = s; + message m( IdChangeSharedMemoryKey ); + m.addInt( shm_key ); + m.addInt( m_shmSize ); + sendMessage( m ); +} + + + + + + + +bool remotePlugin::processMessage( const message & _m ) +{ + lock(); + message reply_message( _m.id ); + bool reply = false; + switch( _m.id ) + { + case IdGeneralFailure: + return( false ); + + case IdDebugMessage: + printf( "debug message from client: %s\n", + _m.data[0].c_str() ); + break; + + case IdInitDone: + reply = true; + break; + + case IdSampleRateInformation: + reply = true; + reply_message.addInt( + engine::getMixer()->processingSampleRate() ); + break; + + case IdBufferSizeInformation: + reply = true; + reply_message.addInt( + engine::getMixer()->framesPerPeriod() ); + break; + + case IdChangeInputCount: + m_inputCount = _m.getInt( 0 ); + resizeSharedMemory(); + break; + + case IdChangeOutputCount: + m_outputCount = _m.getInt( 0 ); + resizeSharedMemory(); + break; + + case IdProcessingDone: + case IdClosePlugin: + case IdUndefined: + default: + break; + } + if( reply ) + { + sendMessage( reply_message ); + } + unlock(); + + return( true ); +} + + + + + +