SphinxBase
0.6
|
00001 /* 00002 This file is part of the imp project. 00003 Copyright (C) 2009 Università degli Studi di Bergamo, Politecnico di Milano 00004 Authors: 00005 Cristian Gatti, gatti DOT kris AT gmail DOT com 00006 Silvio Moioli, silvio AT moioli DOT net, <http://www.moioli.net> 00007 */ 00008 00009 #include "config.h" 00010 00011 #if defined(AD_BACKEND_S60) 00012 00013 /* 00014 S60 Sphinx audio backend. 00015 Currently it is limited to recording 8kHz PCM16 mono audio data. 00016 */ 00017 00018 //Symbian includes must go first 00019 #include <e32base.h> 00020 #include <e32msgqueue.h> 00021 #include <e32debug.h> 00022 #include <MdaAudioInputStream.h> 00023 #include <mda/common/audio.h> 00024 00025 #include "ad.h" 00026 00027 /* 00028 * Implementation notes 00029 * Since Symbian uses a callback system based on Active Objects to carry out asynchronous 00030 * operations we must make use of a helper thread, which is also useful for priority reasons. 00031 * 00032 * Sphinxbase functions are implemented through the CAudioDevice class that communicates 00033 * with the helper thread, which is encapsulated in CHelperThreadHost. Threads use: 00034 * - a synchronized temporaryBuffer and 00035 * - Symbian thread-safe queues (RMsgQueues) 00036 * to communicate. 00037 */ 00038 00039 //constants 00040 00041 /* 00042 * Messages sent through RMsgQueues. 00043 */ 00044 enum TMessage { 00045 ENullMessage = 0, 00046 EInited, 00047 EStartRecording, 00048 ERecordingStarted, 00049 EStopRecording, 00050 ERecordingStopped, 00051 EClose, 00052 EClosed 00053 }; 00054 00055 /* 00056 * Max RMsgQueue size (will block if full). 00057 */ 00058 const TInt KQueueLength = 10; 00059 00060 /* 00061 * Only PCM16 is supported at the moment. 00062 */ 00063 const TInt KBytesPerSample = 2; 00064 00065 /* 00066 * Only 16kHz audio is supported at the moment. 00067 */ 00068 const TInt KSampleRate = 16000; 00069 00070 /* 00071 * Temporary buffer length in milliseconds. The temporary buffer is filled 00072 * by the OS and then copied to the main buffer where it is read by Sphinxbase 00073 * functions. 00074 */ 00075 const TInt KTemporaryBufferTime = 150; 00076 00077 /* 00078 * Temporary buffer length in bytes. 00079 */ 00080 const TInt KTemporaryBufferSize = (KTemporaryBufferTime * KSampleRate * KBytesPerSample) / 1000; 00081 00082 /* 00083 * Helper thread name. 00084 */ 00085 _LIT(KHelperThreadName, "HelperThread"); 00086 00087 /* 00088 * Possible helper thread states. 00089 */ 00090 enum THelperThreadState {EPaused = 0, ERecording, EClosing}; 00091 00092 //classes 00093 00094 /* 00095 * Helper thread wrapper class. 00096 */ 00097 class CHelperThreadHost : public MMdaAudioInputStreamCallback { 00098 public: 00099 CHelperThreadHost(CBufSeg*, RFastLock*, RMsgQueue<TInt>*, RMsgQueue<TInt>*); 00100 virtual ~CHelperThreadHost(); 00101 static TInt ThreadFunction(TAny*); 00102 void InitializeL(); 00103 void DestroyL(); 00104 00105 virtual void MaiscOpenComplete(TInt); 00106 virtual void MaiscBufferCopied(TInt, const TDesC8&); 00107 virtual void MaiscRecordComplete(TInt); 00108 00109 private: 00110 CMdaAudioInputStream* iStream; 00111 TMdaAudioDataSettings iStreamSettings; 00112 THelperThreadState iState; 00113 00114 RBuf8 iTemporaryBuffer; 00115 00116 CBufSeg* iBuffer; 00117 RFastLock* iBufferLock; 00118 00119 RMsgQueue<TInt>* iCommandQueue; 00120 RMsgQueue<TInt>* iNotificationQueue; 00121 }; 00122 00123 /* 00124 * Class used to invoke Symbian functions from Sphinx functions. 00125 */ 00126 class CAudioDevice { 00127 public: 00128 CAudioDevice(); 00129 void ConstructL(); 00130 static CAudioDevice* NewL(); 00131 virtual ~CAudioDevice(); 00132 00133 void ResumeRecording(); 00134 void PauseRecording(); 00135 TInt ReadSamples(TAny*, TInt); 00136 00137 private: 00138 RThread iThread; 00139 CHelperThreadHost* iThreadHost; 00140 00141 CBufSeg* iBuffer; 00142 RFastLock iBufferLock; 00143 00144 RMsgQueue<TInt> iCommandQueue; 00145 RMsgQueue<TInt> iNotificationQueue; 00146 }; 00147 00148 CAudioDevice::CAudioDevice(){ 00149 iCommandQueue.CreateLocal(KQueueLength); 00150 iNotificationQueue.CreateLocal(KQueueLength); 00151 } 00152 00153 void CAudioDevice::ConstructL(){ 00154 iBuffer = CBufSeg::NewL(KTemporaryBufferSize); 00155 iBufferLock.CreateLocal(); 00156 00157 iThreadHost = new (ELeave) CHelperThreadHost(iBuffer, &(iBufferLock), &(iCommandQueue), &(iNotificationQueue)); 00158 iThread.Create(KHelperThreadName, CHelperThreadHost::ThreadFunction, KDefaultStackSize, NULL, iThreadHost); 00159 iThread.Resume(); //new thread starts at ThreadFunction 00160 00161 //wait until init is done 00162 TInt message = ENullMessage; 00163 iNotificationQueue.ReceiveBlocking(message); 00164 if(message != EInited){ 00165 RDebug::Print(_L("expecting %d, got %d"), EInited, message); 00166 } 00167 } 00168 00169 CAudioDevice* CAudioDevice::NewL(){ 00170 CAudioDevice* self = new (ELeave) CAudioDevice(); 00171 CleanupStack::PushL(self); 00172 self->ConstructL(); 00173 CleanupStack::Pop(self); 00174 return self; 00175 } 00176 00177 /* 00178 * Request to record samples. 00179 */ 00180 void CAudioDevice::ResumeRecording(){ 00181 iCommandQueue.SendBlocking(EStartRecording); 00182 00183 TInt message = ENullMessage; 00184 iNotificationQueue.ReceiveBlocking(message); 00185 if(message != ERecordingStarted){ 00186 RDebug::Print(_L("expecting %d, got %d"), ERecordingStarted, message); 00187 } 00188 } 00189 00190 /* 00191 * Request to stop recording samples. Note that actually we don't stop the recording, 00192 * but just discard incoming data until ResumeRecording is called again. 00193 */ 00194 void CAudioDevice::PauseRecording(){ 00195 iCommandQueue.SendBlocking(EStopRecording); 00196 00197 TInt message = ENullMessage; 00198 iNotificationQueue.ReceiveBlocking(message); 00199 if(message != ERecordingStopped){ 00200 RDebug::Print(_L("expecting %d, got %d"), ERecordingStopped, message); 00201 } 00202 } 00203 00204 /* 00205 * Reads at most maxSamples samples into destinationBuffer, returning 00206 * the actual number of samples read. 00207 */ 00208 TInt CAudioDevice::ReadSamples(TAny* aDestinationBuffer, TInt aMaxSamples){ 00209 iBufferLock.Wait(); 00210 TInt availableSamples = iBuffer->Size() / KBytesPerSample; 00211 TInt samplesToCopy = aMaxSamples; 00212 if (availableSamples < aMaxSamples){ 00213 samplesToCopy = availableSamples; 00214 } 00215 TInt bytesToCopy = samplesToCopy * KBytesPerSample; 00216 iBuffer->Read(0, aDestinationBuffer, bytesToCopy); 00217 iBuffer->Delete(0, bytesToCopy); 00218 iBufferLock.Signal(); 00219 00220 return samplesToCopy; 00221 } 00222 00223 CAudioDevice::~CAudioDevice(){ 00224 //tell the thread to stop operations 00225 iCommandQueue.SendBlocking(EClose); 00226 00227 TInt message = ENullMessage; 00228 iNotificationQueue.ReceiveBlocking(message); 00229 if(message != EClosed){ 00230 RDebug::Print(_L("expecting %d, got %d"), EClosed, message); 00231 } 00232 00233 //join thread 00234 TRequestStatus status; 00235 iThread.Logon(status); 00236 User::WaitForRequest(status); 00237 00238 //destroy fields 00239 delete iThreadHost; 00240 iThread.Close(); 00241 iBufferLock.Close(); 00242 delete iBuffer; 00243 iNotificationQueue.Close(); 00244 iCommandQueue.Close(); 00245 } 00246 00247 CHelperThreadHost::CHelperThreadHost(CBufSeg* aBuffer, RFastLock* aBufferLock, RMsgQueue<TInt>* aCommandQueue, RMsgQueue<TInt>* aNotificationQueue){ 00248 iBuffer = aBuffer; 00249 iBufferLock = aBufferLock; 00250 iCommandQueue = aCommandQueue; 00251 iNotificationQueue = aNotificationQueue; 00252 iState = EPaused; 00253 } 00254 00255 TInt CHelperThreadHost::ThreadFunction(TAny* aParam){ 00256 CHelperThreadHost* host = (CHelperThreadHost*) aParam; 00257 00258 //add cleanup stack support 00259 CTrapCleanup* cleanupStack = CTrapCleanup::New(); 00260 00261 //add active objects suppport 00262 TRAPD(error, 00263 CActiveScheduler* activeScheduler = new (ELeave) CActiveScheduler; 00264 CleanupStack::PushL(activeScheduler); 00265 CActiveScheduler::Install(activeScheduler); 00266 00267 //init multimedia system 00268 host->InitializeL(); 00269 00270 //run active scheduler 00271 CActiveScheduler::Start(); 00272 00273 //thread execution ended 00274 CleanupStack::PopAndDestroy(activeScheduler); 00275 ); 00276 if(error != KErrNone){ 00277 RDebug::Print(_L("thread error: %d"), error); 00278 } 00279 00280 delete cleanupStack; 00281 return KErrNone; 00282 } 00283 00284 /* 00285 * Inits iStream and iTemporaryBuffer. 00286 */ 00287 void CHelperThreadHost::InitializeL(){ 00288 iStream = CMdaAudioInputStream::NewL(*this, EMdaPriorityMax, EMdaPriorityPreferenceTime); 00289 iStream->Open(&(iStreamSettings)); //calls MaiscOpenComplete asynchronously 00290 iTemporaryBuffer.CreateL(KTemporaryBufferSize); 00291 } 00292 00293 /* 00294 * Destroys iStream and iTemporaryBuffer. 00295 */ 00296 void CHelperThreadHost::DestroyL(){ 00297 iTemporaryBuffer.Close(); 00298 #if defined(__WINSCW__) 00299 iStream->Stop(); 00300 CMdaAudioInputStream::Delete(iStream); 00301 #else 00302 delete iStream; 00303 #endif 00304 } 00305 00306 /* 00307 * Called by the OS when iStream has been opened. 00308 */ 00309 void CHelperThreadHost::MaiscOpenComplete(TInt aError){ 00310 if (aError == KErrNone){ 00311 iNotificationQueue->SendBlocking(EInited); 00312 00313 iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate16000Hz, TMdaAudioDataSettings::EChannelsMono); 00314 iStream->SetGain(iStream->MaxGain()); 00315 00316 iStream->ReadL(iTemporaryBuffer); //calls MaiscBufferCopied asynchronously 00317 } 00318 else{ 00319 RDebug::Print(_L("error %d in MaiscOpenComplete"), aError); 00320 } 00321 } 00322 00323 /* 00324 * Called by the OS when iTemporaryBuffer has been filled. 00325 */ 00326 void CHelperThreadHost::MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer){ 00327 if (aError == KErrNone){ 00328 //if needed, record data 00329 if(iState == ERecording){ 00330 TInt availableBytes = aBuffer.Size(); 00331 iBufferLock->Wait(); 00332 TInt bufferSize = iBuffer->Size(); 00333 iBuffer->ExpandL(bufferSize, availableBytes); 00334 iBuffer->Write(bufferSize, aBuffer, availableBytes); 00335 iBufferLock->Signal(); 00336 } 00337 00338 //empty buffer 00339 iTemporaryBuffer.Zero(); 00340 00341 //process pending messages 00342 TInt message = ENullMessage; 00343 TInt result = iCommandQueue->Receive(message); 00344 if (result == KErrNone){ 00345 if(message == EStartRecording){ 00346 iState = ERecording; 00347 iNotificationQueue->SendBlocking(ERecordingStarted); 00348 } 00349 else if(message == EStopRecording){ 00350 iState = EPaused; 00351 iNotificationQueue->SendBlocking(ERecordingStopped); 00352 } 00353 else if(message == EClose){ 00354 iState = EClosing; 00355 iStream->Stop(); //calls MaiscRecordComplete asynchronously 00356 this->DestroyL(); 00357 iNotificationQueue->SendBlocking(EClosed); 00358 User::Exit(0); 00359 } 00360 else{ 00361 RDebug::Print(_L("received unexpected %d"), message); 00362 } 00363 } 00364 00365 //unless stopping, request filling the next buffer 00366 if (iState != EClosing){ 00367 iStream->ReadL(iTemporaryBuffer); //calls MaiscBufferCopied asynchronously 00368 } 00369 } 00370 else if (aError == KErrAbort){ 00371 //sent when discarding data during close, nothing to do here 00372 } 00373 else{ 00374 RDebug::Print(_L("error %d in MaiscBufferCopied"), aError); 00375 } 00376 } 00377 00378 /* 00379 * Should be called by the OS when the recording is finished. 00380 * Due to a bug, this method never gets called. 00381 * http://carbidehelp.nokia.com/help/index.jsp?topic=/S60_5th_Edition_Cpp_Developers_Library/GUID-441D327D-D737-42A2-BCEA-FE89FBCA2F35/AudioStreamExample/doc/index.html 00382 */ 00383 void CHelperThreadHost::MaiscRecordComplete(TInt aError){ 00384 //nothing to do here 00385 } 00386 00387 CHelperThreadHost::~CHelperThreadHost(){ 00388 //nothing to do here 00389 } 00390 00391 //Sphinxbase methods 00392 00393 ad_rec_t* ad_open(void){ 00394 ad_rec_t* result = new ad_rec_t; 00395 result->recorder = CAudioDevice::NewL(); 00396 result->recording = FALSE; 00397 result->sps = KSampleRate; 00398 result->bps = KBytesPerSample; 00399 return result; 00400 } 00401 00402 ad_rec_t* ad_open_dev(const char* dev, int32 sps){ 00403 //dummy 00404 return ad_open(); 00405 } 00406 00407 ad_rec_t* ad_open_sps(int32 sps){ 00408 //dummy 00409 return ad_open(); 00410 } 00411 00412 ad_rec_t* ad_open_sps_bufsize(int32 sps, int32 bufsize_msec){ 00413 //dummy 00414 return ad_open(); 00415 } 00416 00417 int32 ad_start_rec(ad_rec_t* r){ 00418 ((CAudioDevice*)r->recorder)->ResumeRecording(); 00419 r->recording = TRUE; 00420 return AD_OK; 00421 } 00422 00423 int32 ad_read(ad_rec_t* r, int16* buf, int32 max){ 00424 int32 result = (int32) ((CAudioDevice*)r->recorder)->ReadSamples((TAny*) buf, (TInt)max); 00425 if(result == 0 && r->recording == FALSE){ 00426 result = AD_EOF; 00427 } 00428 return result; 00429 } 00430 00431 int32 ad_stop_rec(ad_rec_t* r){ 00432 ((CAudioDevice*)r->recorder)->PauseRecording(); 00433 r->recording = FALSE; 00434 return AD_OK; 00435 } 00436 00437 int32 ad_close(ad_rec_t* r){ 00438 delete ((CAudioDevice*)r->recorder); 00439 delete r; 00440 return AD_OK; 00441 } 00442 00443 #endif //defined(AD_BACKEND_S60)