// Mikropuhe so-library cpp-wrapper and test program #include #include #include #include #include #include #include #include #include #include "mpwrfile.h" ///////////////////////////////////////////////////////////////////////////// // CMPChannel - The wrapper class for one channel // All functions must be called from only on thread or calling must be synchronized other way // Speech parameter are maintained by instance - instances are completely isolated. // Internally libmplinux.so creates voice file cache when first InitEx is called. // The cache is cleaned when its reference count drops to zero class CMPChannel { // Creating & destroying public: CMPChannel() { m_pLib = 0; m_pChannel = 0; m_pfnChannelInitEx = 0; m_pfnChannelUseSettings = 0; m_pfnChannelExit = 0; m_pfnChannelSpeakFile = 0; } ~CMPChannel() { FreeAll(); } // Frees the library, synthesizer MUST NOT be active when this is called. // (All running SpeakFile-calls must have been completed). void FreeAll(); /* * Loads given library and obtains all needed symbols from it * * pSoFileName -> Full path name for the libmplinux.so * pErr -> English error message is saved here in case of failure * nErrLen -> Length of previous * * Return value: 0=Ok and pErr="", others=failed and pErr is set */ int Load( const char *pSoFileName, char *pErr, int nErrLen ); // Functions, all return 0 if successful and negative if error is from Mikropuhe // Please see mpwrfile.h for more information public: // You MUST call this before anything else int InitEx( const char *pKey, const char *pSettingsStr, void *pReserved=0 ) { if ( !m_pfnChannelInitEx ) return( -1 ); return( m_pfnChannelInitEx(&m_pChannel, pKey, pSettingsStr, pReserved) ); } int UseSettings( const char *pSettingsStr ) { if ( !m_pfnChannelUseSettings ) return( -1 ); return( m_pfnChannelUseSettings(m_pChannel, pSettingsStr) ); } int Exit(char *pSettingsStr, int nSettingsLen) { int nRes; if ( !m_pfnChannelExit ) return( -1 ); nRes = m_pfnChannelExit(m_pChannel, pSettingsStr, nSettingsLen); m_pChannel = 0; return( nRes ); } int SpeakFile( const char *pText, const char *pFileName, MPINT_SpeakFileParams *prParams ) { if ( !m_pfnChannelSpeakFile ) return( -1 ); return( m_pfnChannelSpeakFile(m_pChannel, pText, pFileName, prParams) ); } protected: void *m_pLib; // dlopen-handle void *m_pChannel; // Channel "handle" // Function pointers MPINT_ChannelInitExType m_pfnChannelInitEx; MPINT_ChannelUseSettingsType m_pfnChannelUseSettings; MPINT_ChannelExitType m_pfnChannelExit; MPINT_ChannelSpeakFileType m_pfnChannelSpeakFile; }; void CMPChannel::FreeAll() { // Uninitialize Mikropuhe Exit( NULL, 0 ); if ( m_pLib ) { dlclose( m_pLib ); m_pLib = 0; } m_pfnChannelInitEx = 0; m_pfnChannelUseSettings = 0; m_pfnChannelExit = 0; m_pfnChannelSpeakFile = 0; } int CMPChannel::Load( const char *pSoFileName, char *pErr, int nErrLen ) { char sNameTemp[4096]; // Because dlopen does not take const char * int nIndex; *pErr = 0; FreeAll(); memset( sNameTemp, 0, sizeof(sNameTemp) ); // Load the so strncpy( sNameTemp, pSoFileName, sizeof(sNameTemp)-1 ); m_pLib = dlopen( sNameTemp, RTLD_NOW ); if ( !m_pLib ) { snprintf( pErr, nErrLen, "Loading library %s failed, error message=%s", sNameTemp, dlerror() ); return( -1 ); } // Get func addresses const char *pFunc; if ( (m_pfnChannelInitEx = (MPINT_ChannelInitExType) dlsym(m_pLib, pFunc = "MPINT_ChannelInitEx")) == 0 || (m_pfnChannelUseSettings = (MPINT_ChannelUseSettingsType) dlsym(m_pLib, pFunc = "MPINT_ChannelUseSettings")) == 0 || (m_pfnChannelExit = (MPINT_ChannelExitType) dlsym(m_pLib, pFunc = "MPINT_ChannelExit")) == 0 || (m_pfnChannelSpeakFile = (MPINT_ChannelSpeakFileType) dlsym(m_pLib, pFunc = "MPINT_ChannelSpeakFile")) == 0 ) { snprintf( pErr, nErrLen, "Could not get function %s from library %s, error message=%s", pFunc, pSoFileName, dlerror() ); return( -2 ); } // All ok return( 0 ); } // CMPChannel - The wrapper class ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Testing multithreaded speaking // Globals to simplify the program static const char *_pSoFileName; #define THREAD_COUNT 4 // How many threads are still running // We can increment and decrement this as an atomic operation on single processor system. // Actually we should have a mutex here, but not now for simplicity... static int _nWaitThreadCount = 0; struct SThreadParam { int nThreadNumber; // Number to speak }; static void *ThreadProc( void *pParam ) { SThreadParam *prParams = (SThreadParam *) pParam; CMPChannel rMP; char sBuf[1024], sSettings[1024]; const char *pSpeaker; int nErr; /* * Initialize the synthesizer * Note that threads are totally independent - system will perform initialization quickly if * this is not first running thread */ if ( (nErr = rMP.Load(_pSoFileName, sBuf, sizeof(sBuf))) != 0 ) { printf( "ERROR LOAD:\n%s\n", sBuf ); return( 0 ); } // Different speech settings for each thread pSpeaker = "petteri"; if ( (prParams->nThreadNumber & 1) == 1 ) pSpeaker = "saga"; sprintf( sSettings,"", pSpeaker, -5 + 3*prParams->nThreadNumber ); printf( "Hello from thread %d, default voice settings:\n%s\n\n", prParams->nThreadNumber, sSettings ); if ( (nErr = rMP.InitEx(NULL, sSettings)) != 0 ) { printf( "ERROR INIT:\n%d\n", nErr ); return( 0 ); } /* * Write the file for few times */ MPINT_SpeakFileParams rParams; char sFileName[1024]; int nIndex, nSleep; memset( &rParams, 0, sizeof(rParams) ); rParams.nTags = MPINT_TAGS_SAPI5; // SAPI5 tagging rParams.nWriteWavHeader = 1; // All threads save to different file and speak different text sprintf( sFileName,"test%d.wav", prParams->nThreadNumber ); sprintf( sBuf,"Hei säikeestä %d.", -5 + 3*prParams->nThreadNumber, prParams->nThreadNumber ); for ( nIndex = 0; nIndex < 10; nIndex++ ) { nSleep = 100000 + (random() % 1000000); printf( "Thread %d calls SpeakFile\n", prParams->nThreadNumber ); nErr = rMP.SpeakFile( sBuf, sFileName, &rParams ); printf( "Thread %d returns from SpeakFile, sleeping for %d us\n", prParams->nThreadNumber, nSleep ); if ( nErr ) { printf( "SpeakFile-call failed [%d]\n", nErr ); break; } if ( (nSleep & 3) == 0 ) { printf( "Thread %d calls UseSettings - should not affect anyone else\n", prParams->nThreadNumber ); if ( (nErr = rMP.UseSettings(sSettings)) != 0 ) { printf( "ERROR INIT:\n%d\n", nErr ); return( 0 ); } } usleep( nSleep ); } printf( "Bye from thread %d - created file %s containing text:\n%s\n\n", prParams->nThreadNumber, sFileName, sBuf ); _nWaitThreadCount--; delete prParams; return( 0 ); } static void ThreadTest( const char *pSoFileName ) { int nIndex; pthread_t hThread; SThreadParam *prParam; _pSoFileName = pSoFileName; // Create threads for ( nIndex = 0; nIndex < THREAD_COUNT; nIndex++ ) { prParam = new SThreadParam; if ( prParam ) { prParam->nThreadNumber = nIndex; // Pass paramter from heap - thread will delete it if ( pthread_create(&hThread, NULL, ThreadProc, prParam) == 0 ) _nWaitThreadCount++; else delete prParam; } } // Wait all to finish while ( _nWaitThreadCount ) usleep( 10000 ); printf( "All threads stopped\n" ); } // Testing multithreaded speaking ///////////////////////////////////////////////////////////////////////////// static char _sSyntData[] = "own data"; static int _nSyntResult = 0; static int SyntWrite(const void * /*pData*/, unsigned uBytes, void *pWriteData, void * /*pReserved*/) { // printf( "SyntWrite %u bytes\n", uBytes ); if ( strcmp(_sSyntData, (char *) pWriteData) ) { printf( "SyntWrite - bad pWriteData\n" ); return( 123 ); // Will stop synthesizer immediately } return( _nSyntResult ); } int main(int argc, char* argv[]) { char sFileName[1024]; char sErr[1024], *pPos; int nStatus; CMPChannel rMP; // One instance for main program // libmplinux.so from program's folder strcpy( sFileName, argv[0] ); pPos = strrchr(sFileName, '/'); if ( pPos ) pPos++; else pPos = sFileName; strcpy( pPos, "libmplinux.so" ); if ( rMP.Load(sFileName, sErr, sizeof(sErr)) ) { printf( "ERROR:\n%s\n", sErr ); return( 1 ); } if ( (nStatus = rMP.InitEx(NULL, NULL)) != 0 ) { printf( "ERROR:\n%d\n", nStatus ); return( 1 ); } /* * Speak tagged text to a file using telephony parameters without wav-header */ MPINT_SpeakFileParams rParams; memset( &rParams, 0, sizeof(rParams) ); rParams.nTags = MPINT_TAGS_SAPI5; rParams.nSampleFreq = 8000; rParams.nBits = 16; rParams.nChannels = 1; rParams.nWriteWavHeader = 0; rParams.pfnWrite = SyntWrite; rParams.pWriteData = _sSyntData; nStatus = rMP.SpeakFile( "Hei, mitä kuuluu?", NULL, &rParams ); if ( nStatus ) { printf( "SpeakFile-call failed [%d]\n", nStatus ); return( 1 ); } // Test - should return same error code _nSyntResult = 55; nStatus = rMP.SpeakFile( "Hei, mitä kuuluu?", NULL, &rParams ); if ( nStatus != _nSyntResult ) { printf( "SpeakFile-call did not return excpected value [%d]\n", nStatus ); return( 1 ); } /* * Speak tagged text to a wav-file using default parameters */ memset( &rParams, 0, sizeof(rParams) ); rParams.nTags = 0; // Not any tagging rParams.nWriteWavHeader = 1; // Let synthesizer use default values for everyting, note that this text will not be spoken tagged nStatus = rMP.SpeakFile( "Hei, mitä kuuluu?", "test.wav", &rParams ); if ( nStatus ) { printf( "SpeakFile-call failed [%d]\n", nStatus ); return( 1 ); } // Multithreading test ThreadTest( sFileName, NULL ); printf( "Everything ok, goodbye.\n" ); return 0; }