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