• Main Page
  • Related Pages
  • Data Structures
  • Files
  • File List
  • Globals

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     delete iStream;
00299 }
00300 
00301 /*
00302  * Called by the OS when iStream has been opened.
00303  */
00304 void CHelperThreadHost::MaiscOpenComplete(TInt aError){
00305     if (aError == KErrNone){
00306         iNotificationQueue->SendBlocking(EInited);
00307                 
00308         iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate16000Hz, TMdaAudioDataSettings::EChannelsMono);
00309         iStream->SetGain(iStream->MaxGain());
00310 
00311         iStream->ReadL(iTemporaryBuffer); //calls MaiscBufferCopied asynchronously
00312     }
00313     else{
00314         RDebug::Print(_L("error %d in MaiscOpenComplete"), aError);
00315     }
00316 }
00317 
00318 /*
00319  * Called by the OS when iTemporaryBuffer has been filled.
00320  */
00321 void CHelperThreadHost::MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer){
00322     if (aError == KErrNone){
00323         //if needed, record data
00324         if(iState == ERecording){
00325             TInt availableBytes = aBuffer.Size();
00326             iBufferLock->Wait();
00327                 TInt bufferSize = iBuffer->Size();
00328                 iBuffer->ExpandL(bufferSize, availableBytes);
00329                 iBuffer->Write(bufferSize, aBuffer, availableBytes);
00330             iBufferLock->Signal();
00331         }
00332         
00333         //empty buffer
00334         iTemporaryBuffer.Zero();
00335         
00336         //process pending messages
00337         TInt message = ENullMessage;
00338         TInt result = iCommandQueue->Receive(message);
00339         if (result == KErrNone){
00340             if(message == EStartRecording){
00341                 iState = ERecording;
00342                 iNotificationQueue->SendBlocking(ERecordingStarted);
00343             }
00344             else if(message == EStopRecording){
00345                 iState = EPaused;
00346                 iNotificationQueue->SendBlocking(ERecordingStopped);
00347             }
00348             else if(message == EClose){
00349                 iState = EClosing;
00350                 iStream->Stop(); //calls MaiscRecordComplete asynchronously
00351                 this->DestroyL();
00352                 iNotificationQueue->SendBlocking(EClosed);
00353                 User::Exit(0);
00354             }
00355             else{
00356                 RDebug::Print(_L("received unexpected %d"), message);
00357             }
00358         }
00359         
00360         //unless stopping, request filling the next buffer
00361         if (iState != EClosing){
00362             iStream->ReadL(iTemporaryBuffer); //calls MaiscBufferCopied asynchronously
00363         }
00364     }
00365     else if (aError == KErrAbort){
00366         //sent when discarding data during close, nothing to do here
00367     }
00368     else{
00369         RDebug::Print(_L("error %d in MaiscBufferCopied"), aError);
00370     }
00371 }
00372 
00373 /*
00374  * Should be called by the OS when the recording is finished.
00375  * Due to a bug, this method never gets called.
00376  * http://carbidehelp.nokia.com/help/index.jsp?topic=/S60_5th_Edition_Cpp_Developers_Library/GUID-441D327D-D737-42A2-BCEA-FE89FBCA2F35/AudioStreamExample/doc/index.html
00377  */
00378 void CHelperThreadHost::MaiscRecordComplete(TInt aError){
00379     //nothing to do here
00380 }
00381 
00382 CHelperThreadHost::~CHelperThreadHost(){
00383     //nothing to do here
00384 }
00385 
00386 //Sphinxbase methods
00387 
00388 ad_rec_t* ad_open(void){
00389     ad_rec_t* result = new ad_rec_t;
00390     result->recorder = CAudioDevice::NewL();
00391     result->recording = FALSE;
00392     result->sps = KSampleRate;
00393     result->bps = KBytesPerSample;
00394     return result;
00395 }
00396 
00397 ad_rec_t* ad_open_dev(const char* dev, int32 sps){
00398     //dummy
00399     return ad_open();
00400 }
00401 
00402 ad_rec_t* ad_open_sps(int32 sps){
00403     //dummy
00404     return ad_open();
00405 }
00406 
00407 ad_rec_t* ad_open_sps_bufsize(int32 sps, int32 bufsize_msec){
00408     //dummy
00409     return ad_open();
00410 }
00411 
00412 int32 ad_start_rec(ad_rec_t* r){
00413     ((CAudioDevice*)r->recorder)->ResumeRecording();
00414     r->recording = TRUE;
00415     return AD_OK;
00416 }
00417 
00418 int32 ad_read(ad_rec_t* r, int16* buf, int32 max){
00419     int32 result = (int32) ((CAudioDevice*)r->recorder)->ReadSamples((TAny*) buf, (TInt)max);
00420     if(result == 0 && r->recording == FALSE){
00421         result = AD_EOF;
00422     }
00423     return result;
00424 }
00425 
00426 int32 ad_stop_rec(ad_rec_t* r){
00427     ((CAudioDevice*)r->recorder)->PauseRecording();
00428     r->recording = FALSE;
00429     return AD_OK;
00430 }
00431 
00432 int32 ad_close(ad_rec_t* r){
00433     delete ((CAudioDevice*)r->recorder);
00434     delete r;
00435     return AD_OK;
00436 }
00437 
00438 #endif //defined(AD_BACKEND_S60)

Generated on Mon Aug 29 2011 for SphinxBase by  doxygen 1.7.1