SphinxBase  0.6
ad_s60.cpp
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 
148 CAudioDevice::CAudioDevice(){
149  iCommandQueue.CreateLocal(KQueueLength);
150  iNotificationQueue.CreateLocal(KQueueLength);
151 }
152 
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 
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  */
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  */
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  */
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 
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 
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 
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  */
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  */
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  */
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  */
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  */
383 void CHelperThreadHost::MaiscRecordComplete(TInt aError){
384  //nothing to do here
385 }
386 
387 CHelperThreadHost::~CHelperThreadHost(){
388  //nothing to do here
389 }
390 
391 //Sphinxbase methods
392 
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 
402 ad_rec_t* ad_open_dev(const char* dev, int32 sps){
403  //dummy
404  return ad_open();
405 }
406 
407 ad_rec_t* ad_open_sps(int32 sps){
408  //dummy
409  return ad_open();
410 }
411 
412 ad_rec_t* ad_open_sps_bufsize(int32 sps, int32 bufsize_msec){
413  //dummy
414  return ad_open();
415 }
416 
417 int32 ad_start_rec(ad_rec_t* r){
418  ((CAudioDevice*)r->recorder)->ResumeRecording();
419  r->recording = TRUE;
420  return AD_OK;
421 }
422 
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 
431 int32 ad_stop_rec(ad_rec_t* r){
432  ((CAudioDevice*)r->recorder)->PauseRecording();
433  r->recording = FALSE;
434  return AD_OK;
435 }
436 
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)
Definition: ad.h:255
int32 sps
Samples/sec.
Definition: ad.h:256
int32 bps
Bytes/sample.
Definition: ad.h:257
SPHINXBASE_EXPORT ad_rec_t * ad_open(void)
Open the default audio device.
Definition: ad_alsa.c:296
generic live audio interface for recording and playback
SPHINXBASE_EXPORT ad_rec_t * ad_open_dev(const char *dev, int32 samples_per_sec)
Open a specific audio device for recording.
Definition: ad_alsa.c:252
SPHINXBASE_EXPORT ad_rec_t * ad_open_sps(int32 samples_per_sec)
Open the default audio device with a given sampling rate.
Definition: ad_alsa.c:290