SphinxBase  0.6
src/libsphinxad/ad_s60.cpp
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)