1 /*
2    This file is part of the imp project.
3    Copyright (C) 2009 Universit� degli Studi di Bergamo, Politecnico di Milano
4    Authors:
5        Cristian Gatti, gatti DOT kris AT gmail DOT com
6        Silvio Moioli, silvio AT moioli DOT net, <http://www.moioli.net>
7  */
8 
9 #include "config.h"
10 
11 #if defined(AD_BACKEND_S60)
12 
13 /*
14     S60 Sphinx audio backend.
15     Currently it is limited to recording 8kHz PCM16 mono audio data.
16  */
17 
18 //Symbian includes must go first
19 #include <e32base.h>
20 #include <e32msgqueue.h>
21 #include <e32debug.h>
22 #include <MdaAudioInputStream.h>
23 #include <mda/common/audio.h>
24 
25 #include "ad.h"
26 
27 /*
28  * Implementation notes
29  * Since Symbian uses a callback system based on Active Objects to carry out asynchronous
30  * operations we must make use of a helper thread, which is also useful for priority reasons.
31  *
32  * Sphinxbase functions are implemented through the CAudioDevice class that communicates
33  * with the helper thread, which is encapsulated in CHelperThreadHost. Threads use:
34  *  - a synchronized temporaryBuffer and
35  *  - Symbian thread-safe queues (RMsgQueues)
36  * to communicate.
37  */
38 
39 //constants
40 
41 /*
42  * Messages sent through RMsgQueues.
43  */
44 enum TMessage {
45     ENullMessage = 0,
46     EInited,
47     EStartRecording,
48     ERecordingStarted,
49     EStopRecording,
50     ERecordingStopped,
51     EClose,
52     EClosed
53 };
54 
55 /*
56  * Max RMsgQueue size (will block if full).
57  */
58 const TInt KQueueLength = 10;
59 
60 /*
61  * Only PCM16 is supported at the moment.
62  */
63 const TInt KBytesPerSample = 2;
64 
65 /*
66  * Only 16kHz audio is supported at the moment.
67  */
68 const TInt KSampleRate = 16000;
69 
70 /*
71  * Temporary buffer length in milliseconds. The temporary buffer is filled
72  * by the OS and then copied to the main buffer where it is read by Sphinxbase
73  * functions.
74  */
75 const TInt KTemporaryBufferTime = 150;
76 
77 /*
78  * Temporary buffer length in bytes.
79  */
80 const TInt KTemporaryBufferSize = (KTemporaryBufferTime * KSampleRate * KBytesPerSample) / 1000;
81 
82 /*
83  * Helper thread name.
84  */
85 _LIT(KHelperThreadName, "HelperThread");
86 
87 /*
88  * Possible helper thread states.
89  */
90 enum THelperThreadState {EPaused = 0, ERecording, EClosing};
91 
92 //classes
93 
94 /*
95  * Helper thread wrapper class.
96  */
97 class CHelperThreadHost : public MMdaAudioInputStreamCallback {
98     public:
99         CHelperThreadHost(CBufSeg*, RFastLock*, RMsgQueue<TInt>*, RMsgQueue<TInt>*);
100         virtual ~CHelperThreadHost();
101         static TInt ThreadFunction(TAny*);
102         void InitializeL();
103         void DestroyL();
104 
105         virtual void MaiscOpenComplete(TInt);
106         virtual void MaiscBufferCopied(TInt, const TDesC8&);
107         virtual void MaiscRecordComplete(TInt);
108 
109     private:
110         CMdaAudioInputStream* iStream;
111         TMdaAudioDataSettings iStreamSettings;
112         THelperThreadState iState;
113 
114         RBuf8 iTemporaryBuffer;
115 
116         CBufSeg* iBuffer;
117         RFastLock* iBufferLock;
118 
119         RMsgQueue<TInt>* iCommandQueue;
120         RMsgQueue<TInt>* iNotificationQueue;
121 };
122 
123 /*
124  * Class used to invoke Symbian functions from Sphinx functions.
125  */
126 class CAudioDevice {
127     public:
128         CAudioDevice();
129         void ConstructL();
130         static CAudioDevice* NewL();
131         virtual ~CAudioDevice();
132 
133         void ResumeRecording();
134         void PauseRecording();
135         TInt ReadSamples(TAny*, TInt);
136 
137     private:
138         RThread iThread;
139         CHelperThreadHost* iThreadHost;
140 
141         CBufSeg* iBuffer;
142         RFastLock iBufferLock;
143 
144         RMsgQueue<TInt> iCommandQueue;
145         RMsgQueue<TInt> iNotificationQueue;
146 };
147 
CAudioDevice()148 CAudioDevice::CAudioDevice(){
149     iCommandQueue.CreateLocal(KQueueLength);
150     iNotificationQueue.CreateLocal(KQueueLength);
151 }
152 
ConstructL()153 void CAudioDevice::ConstructL(){
154     iBuffer = CBufSeg::NewL(KTemporaryBufferSize);
155     iBufferLock.CreateLocal();
156 
157     iThreadHost = new (ELeave) CHelperThreadHost(iBuffer, &(iBufferLock), &(iCommandQueue), &(iNotificationQueue));
158     iThread.Create(KHelperThreadName, CHelperThreadHost::ThreadFunction, KDefaultStackSize, NULL, iThreadHost);
159     iThread.Resume(); //new thread starts at ThreadFunction
160 
161     //wait until init is done
162     TInt message = ENullMessage;
163     iNotificationQueue.ReceiveBlocking(message);
164     if(message != EInited){
165         RDebug::Print(_L("expecting %d, got %d"), EInited, message);
166     }
167 }
168 
NewL()169 CAudioDevice* CAudioDevice::NewL(){
170     CAudioDevice* self = new (ELeave) CAudioDevice();
171     CleanupStack::PushL(self);
172     self->ConstructL();
173     CleanupStack::Pop(self);
174     return self;
175 }
176 
177 /*
178  * Request to record samples.
179  */
ResumeRecording()180 void CAudioDevice::ResumeRecording(){
181     iCommandQueue.SendBlocking(EStartRecording);
182 
183     TInt message = ENullMessage;
184     iNotificationQueue.ReceiveBlocking(message);
185     if(message != ERecordingStarted){
186         RDebug::Print(_L("expecting %d, got %d"), ERecordingStarted, message);
187     }
188 }
189 
190 /*
191  * Request to stop recording samples. Note that actually we don't stop the recording,
192  * but just discard incoming data until ResumeRecording is called again.
193  */
PauseRecording()194 void CAudioDevice::PauseRecording(){
195     iCommandQueue.SendBlocking(EStopRecording);
196 
197     TInt message = ENullMessage;
198     iNotificationQueue.ReceiveBlocking(message);
199     if(message != ERecordingStopped){
200         RDebug::Print(_L("expecting %d, got %d"), ERecordingStopped, message);
201     }
202 }
203 
204 /*
205  * Reads at most maxSamples samples into destinationBuffer, returning
206  * the actual number of samples read.
207  */
ReadSamples(TAny * aDestinationBuffer,TInt aMaxSamples)208 TInt CAudioDevice::ReadSamples(TAny* aDestinationBuffer, TInt aMaxSamples){
209     iBufferLock.Wait();
210         TInt availableSamples = iBuffer->Size() / KBytesPerSample;
211         TInt samplesToCopy = aMaxSamples;
212         if (availableSamples < aMaxSamples){
213             samplesToCopy = availableSamples;
214         }
215         TInt bytesToCopy = samplesToCopy * KBytesPerSample;
216         iBuffer->Read(0, aDestinationBuffer, bytesToCopy);
217         iBuffer->Delete(0, bytesToCopy);
218     iBufferLock.Signal();
219 
220     return samplesToCopy;
221 }
222 
~CAudioDevice()223 CAudioDevice::~CAudioDevice(){
224     //tell the thread to stop operations
225     iCommandQueue.SendBlocking(EClose);
226 
227     TInt message = ENullMessage;
228     iNotificationQueue.ReceiveBlocking(message);
229     if(message != EClosed){
230         RDebug::Print(_L("expecting %d, got %d"), EClosed, message);
231     }
232 
233     //join thread
234     TRequestStatus status;
235     iThread.Logon(status);
236     User::WaitForRequest(status);
237 
238     //destroy fields
239     delete iThreadHost;
240     iThread.Close();
241     iBufferLock.Close();
242     delete iBuffer;
243     iNotificationQueue.Close();
244     iCommandQueue.Close();
245 }
246 
CHelperThreadHost(CBufSeg * aBuffer,RFastLock * aBufferLock,RMsgQueue<TInt> * aCommandQueue,RMsgQueue<TInt> * aNotificationQueue)247 CHelperThreadHost::CHelperThreadHost(CBufSeg* aBuffer, RFastLock* aBufferLock, RMsgQueue<TInt>* aCommandQueue, RMsgQueue<TInt>* aNotificationQueue){
248     iBuffer = aBuffer;
249     iBufferLock = aBufferLock;
250     iCommandQueue = aCommandQueue;
251     iNotificationQueue = aNotificationQueue;
252     iState = EPaused;
253 }
254 
ThreadFunction(TAny * aParam)255 TInt CHelperThreadHost::ThreadFunction(TAny* aParam){
256     CHelperThreadHost* host = (CHelperThreadHost*) aParam;
257 
258     //add cleanup stack support
259     CTrapCleanup* cleanupStack = CTrapCleanup::New();
260 
261     //add active objects suppport
262     TRAPD(error,
263         CActiveScheduler* activeScheduler = new (ELeave) CActiveScheduler;
264         CleanupStack::PushL(activeScheduler);
265         CActiveScheduler::Install(activeScheduler);
266 
267         //init multimedia system
268         host->InitializeL();
269 
270         //run active scheduler
271         CActiveScheduler::Start();
272 
273         //thread execution ended
274         CleanupStack::PopAndDestroy(activeScheduler);
275     );
276     if(error != KErrNone){
277         RDebug::Print(_L("thread error: %d"), error);
278     }
279 
280     delete cleanupStack;
281     return KErrNone;
282 }
283 
284 /*
285  * Inits iStream and iTemporaryBuffer.
286  */
InitializeL()287 void CHelperThreadHost::InitializeL(){
288     iStream = CMdaAudioInputStream::NewL(*this, EMdaPriorityMax, EMdaPriorityPreferenceTime);
289     iStream->Open(&(iStreamSettings)); //calls MaiscOpenComplete asynchronously
290     iTemporaryBuffer.CreateL(KTemporaryBufferSize);
291 }
292 
293 /*
294  * Destroys iStream and iTemporaryBuffer.
295  */
DestroyL()296 void CHelperThreadHost::DestroyL(){
297     iTemporaryBuffer.Close();
298 #if defined(__WINSCW__)
299     iStream->Stop();
300     CMdaAudioInputStream::Delete(iStream);
301 #else
302     delete iStream;
303 #endif
304 }
305 
306 /*
307  * Called by the OS when iStream has been opened.
308  */
MaiscOpenComplete(TInt aError)309 void CHelperThreadHost::MaiscOpenComplete(TInt aError){
310     if (aError == KErrNone){
311         iNotificationQueue->SendBlocking(EInited);
312 
313         iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate16000Hz, TMdaAudioDataSettings::EChannelsMono);
314         iStream->SetGain(iStream->MaxGain());
315 
316         iStream->ReadL(iTemporaryBuffer); //calls MaiscBufferCopied asynchronously
317     }
318     else{
319         RDebug::Print(_L("error %d in MaiscOpenComplete"), aError);
320     }
321 }
322 
323 /*
324  * Called by the OS when iTemporaryBuffer has been filled.
325  */
MaiscBufferCopied(TInt aError,const TDesC8 & aBuffer)326 void CHelperThreadHost::MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer){
327     if (aError == KErrNone){
328         //if needed, record data
329         if(iState == ERecording){
330             TInt availableBytes = aBuffer.Size();
331             iBufferLock->Wait();
332                 TInt bufferSize = iBuffer->Size();
333                 iBuffer->ExpandL(bufferSize, availableBytes);
334                 iBuffer->Write(bufferSize, aBuffer, availableBytes);
335             iBufferLock->Signal();
336         }
337 
338         //empty buffer
339         iTemporaryBuffer.Zero();
340 
341         //process pending messages
342         TInt message = ENullMessage;
343         TInt result = iCommandQueue->Receive(message);
344         if (result == KErrNone){
345             if(message == EStartRecording){
346                 iState = ERecording;
347                 iNotificationQueue->SendBlocking(ERecordingStarted);
348             }
349             else if(message == EStopRecording){
350                 iState = EPaused;
351                 iNotificationQueue->SendBlocking(ERecordingStopped);
352             }
353             else if(message == EClose){
354                 iState = EClosing;
355                 iStream->Stop(); //calls MaiscRecordComplete asynchronously
356                 this->DestroyL();
357                 iNotificationQueue->SendBlocking(EClosed);
358                 User::Exit(0);
359             }
360             else{
361                 RDebug::Print(_L("received unexpected %d"), message);
362             }
363         }
364 
365         //unless stopping, request filling the next buffer
366         if (iState != EClosing){
367             iStream->ReadL(iTemporaryBuffer); //calls MaiscBufferCopied asynchronously
368         }
369     }
370     else if (aError == KErrAbort){
371         //sent when discarding data during close, nothing to do here
372     }
373     else{
374         RDebug::Print(_L("error %d in MaiscBufferCopied"), aError);
375     }
376 }
377 
378 /*
379  * Should be called by the OS when the recording is finished.
380  * Due to a bug, this method never gets called.
381  * http://carbidehelp.nokia.com/help/index.jsp?topic=/S60_5th_Edition_Cpp_Developers_Library/GUID-441D327D-D737-42A2-BCEA-FE89FBCA2F35/AudioStreamExample/doc/index.html
382  */
MaiscRecordComplete(TInt aError)383 void CHelperThreadHost::MaiscRecordComplete(TInt aError){
384     //nothing to do here
385 }
386 
~CHelperThreadHost()387 CHelperThreadHost::~CHelperThreadHost(){
388     //nothing to do here
389 }
390 
391 //Sphinxbase methods
392 
ad_open(void)393 ad_rec_t* ad_open(void){
394     ad_rec_t* result = new ad_rec_t;
395     result->recorder = CAudioDevice::NewL();
396     result->recording = FALSE;
397     result->sps = KSampleRate;
398     result->bps = KBytesPerSample;
399     return result;
400 }
401 
ad_open_dev(const char * dev,int32 sps)402 ad_rec_t* ad_open_dev(const char* dev, int32 sps){
403     //dummy
404     return ad_open();
405 }
406 
ad_open_sps(int32 sps)407 ad_rec_t* ad_open_sps(int32 sps){
408     //dummy
409     return ad_open();
410 }
411 
ad_open_sps_bufsize(int32 sps,int32 bufsize_msec)412 ad_rec_t* ad_open_sps_bufsize(int32 sps, int32 bufsize_msec){
413     //dummy
414     return ad_open();
415 }
416 
ad_start_rec(ad_rec_t * r)417 int32 ad_start_rec(ad_rec_t* r){
418     ((CAudioDevice*)r->recorder)->ResumeRecording();
419     r->recording = TRUE;
420     return AD_OK;
421 }
422 
ad_read(ad_rec_t * r,int16 * buf,int32 max)423 int32 ad_read(ad_rec_t* r, int16* buf, int32 max){
424     int32 result = (int32) ((CAudioDevice*)r->recorder)->ReadSamples((TAny*) buf, (TInt)max);
425     if(result == 0 && r->recording == FALSE){
426         result = AD_EOF;
427     }
428     return result;
429 }
430 
ad_stop_rec(ad_rec_t * r)431 int32 ad_stop_rec(ad_rec_t* r){
432     ((CAudioDevice*)r->recorder)->PauseRecording();
433     r->recording = FALSE;
434     return AD_OK;
435 }
436 
ad_close(ad_rec_t * r)437 int32 ad_close(ad_rec_t* r){
438     delete ((CAudioDevice*)r->recorder);
439     delete r;
440     return AD_OK;
441 }
442 
443 #endif //defined(AD_BACKEND_S60)
444