blob: bfdec1e5e0f7fa7d95501682ff2b0dda5b158501 [file] [log] [blame]
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001/* $Id$ */
2/*
3 * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
4 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20#include <pjmedia-audiodev/audiodev_imp.h>
Nanang Izzuddinda37ea32009-03-06 16:24:43 +000021#include <pjmedia-audiodev/errno.h>
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +000022#include <pjmedia/alaw_ulaw.h>
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +000023#include <pj/assert.h>
24#include <pj/log.h>
25#include <pj/math.h>
26#include <pj/os.h>
27#include <pj/string.h>
28
Nanang Izzuddina940b362009-02-23 13:53:30 +000029#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
30
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +000031#include <e32msgqueue.h>
32#include <sounddevice.h>
33#include <APSClientSession.h>
Nanang Izzuddinda37ea32009-03-06 16:24:43 +000034#include <pjmedia-codec/amr_helper.h>
35
36/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
37 * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
38 */
39#include "s60_g729_bitstream.h"
40
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +000041
42#define THIS_FILE "symb_aps_dev.c"
43#define BITS_PER_SAMPLE 16
44
45#if 1
46# define TRACE_(st) PJ_LOG(3, st)
47#else
48# define TRACE_(st)
49#endif
50
51
52/* App UID to open global APS queues to communicate with the APS server. */
53extern TPtrC APP_UID;
54
55/* APS G.711 frame length */
56static pj_uint8_t aps_g711_frame_len;
57
58
59/* APS factory */
60struct aps_factory
61{
62 pjmedia_aud_dev_factory base;
63 pj_pool_t *pool;
64 pj_pool_factory *pf;
65 pjmedia_aud_dev_info dev_info;
66};
67
68
69/* Forward declaration of CPjAudioEngine */
70class CPjAudioEngine;
71
72
73/* APS stream. */
74struct aps_stream
75{
76 // Base
77 pjmedia_aud_stream base; /**< Base class. */
78
79 // Pool
80 pj_pool_t *pool; /**< Memory pool. */
81
82 // Common settings.
Benny Prijono10454dc2009-02-21 14:21:59 +000083 pjmedia_aud_param param; /**< Stream param. */
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +000084 pjmedia_aud_rec_cb rec_cb; /**< Record callback. */
85 pjmedia_aud_play_cb play_cb; /**< Playback callback. */
86 void *user_data; /**< Application data. */
87
88 // Audio engine
89 CPjAudioEngine *engine; /**< Internal engine. */
90
91 pj_timestamp ts_play; /**< Playback timestamp.*/
92 pj_timestamp ts_rec; /**< Record timestamp. */
93
94 pj_int16_t *play_buf; /**< Playback buffer. */
95 pj_uint16_t play_buf_len; /**< Playback buffer length. */
96 pj_uint16_t play_buf_start; /**< Playback buffer start index. */
97 pj_int16_t *rec_buf; /**< Record buffer. */
98 pj_uint16_t rec_buf_len; /**< Record buffer length. */
99 void *strm_data; /**< Stream data. */
100};
101
102
103/* Prototypes */
104static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
105static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
106static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
107static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
108 unsigned index,
109 pjmedia_aud_dev_info *info);
110static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
111 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +0000112 pjmedia_aud_param *param);
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000113static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +0000114 const pjmedia_aud_param *param,
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000115 pjmedia_aud_rec_cb rec_cb,
116 pjmedia_aud_play_cb play_cb,
117 void *user_data,
118 pjmedia_aud_stream **p_aud_strm);
119
120static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
Benny Prijono10454dc2009-02-21 14:21:59 +0000121 pjmedia_aud_param *param);
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000122static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
123 pjmedia_aud_dev_cap cap,
124 void *value);
125static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
126 pjmedia_aud_dev_cap cap,
127 const void *value);
128static pj_status_t stream_start(pjmedia_aud_stream *strm);
129static pj_status_t stream_stop(pjmedia_aud_stream *strm);
130static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
131
132
133/* Operations */
134static pjmedia_aud_dev_factory_op factory_op =
135{
136 &factory_init,
137 &factory_destroy,
138 &factory_get_dev_count,
139 &factory_get_dev_info,
140 &factory_default_param,
141 &factory_create_stream
142};
143
144static pjmedia_aud_stream_op stream_op =
145{
146 &stream_get_param,
147 &stream_get_cap,
148 &stream_set_cap,
149 &stream_start,
150 &stream_stop,
151 &stream_destroy
152};
153
154
155/****************************************************************************
156 * Internal APS Engine
157 */
158
159/*
160 * Utility: print sound device error
161 */
162static void snd_perror(const char *title, TInt rc)
163{
164 PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
165}
166
167typedef void(*PjAudioCallback)(TAPSCommBuffer &buf, void *user_data);
168
169/**
170 * Abstract class for handler of callbacks from APS client.
171 */
172class MQueueHandlerObserver
173{
174public:
175 MQueueHandlerObserver(PjAudioCallback RecCb_, PjAudioCallback PlayCb_,
176 void *UserData_)
177 : RecCb(RecCb_), PlayCb(PlayCb_), UserData(UserData_)
178 {}
179
180 virtual void InputStreamInitialized(const TInt aStatus) = 0;
181 virtual void OutputStreamInitialized(const TInt aStatus) = 0;
182 virtual void NotifyError(const TInt aError) = 0;
183
184public:
185 PjAudioCallback RecCb;
186 PjAudioCallback PlayCb;
187 void *UserData;
188};
189
190/**
191 * Handler for communication and data queue.
192 */
193class CQueueHandler : public CActive
194{
195public:
196 // Types of queue handler
197 enum TQueueHandlerType {
198 ERecordCommQueue,
199 EPlayCommQueue,
200 ERecordQueue,
201 EPlayQueue
202 };
203
204 // The order corresponds to the APS Server state, do not change!
205 enum TState {
206 EAPSPlayerInitialize = 1,
207 EAPSRecorderInitialize = 2,
208 EAPSPlayData = 3,
209 EAPSRecordData = 4,
210 EAPSPlayerInitComplete = 5,
211 EAPSRecorderInitComplete = 6
212 };
213
214 static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
215 RMsgQueue<TAPSCommBuffer>* aQ,
216 RMsgQueue<TAPSCommBuffer>* aWriteQ,
217 TQueueHandlerType aType)
218 {
219 CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ,
220 aType);
221 CleanupStack::PushL(self);
222 self->ConstructL();
223 CleanupStack::Pop(self);
224 return self;
225 }
226
227 // Destructor
228 ~CQueueHandler() { Cancel(); }
229
230 // Start listening queue event
231 void Start() {
232 iQ->NotifyDataAvailable(iStatus);
233 SetActive();
234 }
235
236private:
237 // Constructor
238 CQueueHandler(MQueueHandlerObserver* aObserver,
239 RMsgQueue<TAPSCommBuffer>* aQ,
240 RMsgQueue<TAPSCommBuffer>* aWriteQ,
241 TQueueHandlerType aType)
242 : CActive(CActive::EPriorityHigh),
243 iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType)
244 {
245 CActiveScheduler::Add(this);
246
247 // use lower priority for comm queues
248 if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
249 SetPriority(CActive::EPriorityStandard);
250 }
251
252 // Second phase constructor
253 void ConstructL() {}
254
255 // Inherited from CActive
256 void DoCancel() { iQ->CancelDataAvailable(); }
257
258 void RunL() {
259 if (iStatus != KErrNone) {
260 iObserver->NotifyError(iStatus.Int());
261 return;
262 }
263
264 TAPSCommBuffer buffer;
265 TInt ret = iQ->Receive(buffer);
266
267 if (ret != KErrNone) {
268 iObserver->NotifyError(ret);
269 return;
270 }
271
272 switch (iType) {
273 case ERecordQueue:
274 if (buffer.iCommand == EAPSRecordData) {
275 iObserver->RecCb(buffer, iObserver->UserData);
276 } else {
277 iObserver->NotifyError(buffer.iStatus);
278 }
279 break;
280
281 // Callbacks from the APS main thread
282 case EPlayCommQueue:
283 switch (buffer.iCommand) {
284 case EAPSPlayData:
285 if (buffer.iStatus == KErrUnderflow) {
286 iObserver->PlayCb(buffer, iObserver->UserData);
287 iWriteQ->Send(buffer);
288 }
289 break;
290 case EAPSPlayerInitialize:
291 iObserver->NotifyError(buffer.iStatus);
292 break;
293 case EAPSPlayerInitComplete:
294 iObserver->OutputStreamInitialized(buffer.iStatus);
295 break;
296 case EAPSRecorderInitComplete:
297 iObserver->InputStreamInitialized(buffer.iStatus);
298 break;
299 default:
300 iObserver->NotifyError(buffer.iStatus);
301 break;
302 }
303 break;
304
305 // Callbacks from the APS recorder thread
306 case ERecordCommQueue:
307 switch (buffer.iCommand) {
308 // The APS recorder thread will only report errors
309 // through this handler. All other callbacks will be
310 // sent from the APS main thread through EPlayCommQueue
311 case EAPSRecorderInitialize:
312 case EAPSRecordData:
313 default:
314 iObserver->NotifyError(buffer.iStatus);
315 break;
316 }
317 break;
318
319 default:
320 break;
321 }
322
323 // issue next request
324 iQ->NotifyDataAvailable(iStatus);
325 SetActive();
326 }
327
328 TInt RunError(TInt) {
329 return 0;
330 }
331
332 // Data
333 RMsgQueue<TAPSCommBuffer> *iQ; // (not owned)
334 RMsgQueue<TAPSCommBuffer> *iWriteQ; // (not owned)
335 MQueueHandlerObserver *iObserver; // (not owned)
336 TQueueHandlerType iType;
337};
338
339/*
340 * Audio setting for CPjAudioEngine.
341 */
342class CPjAudioSetting
343{
344public:
345 TFourCC fourcc;
346 TAPSCodecMode mode;
347 TBool plc;
348 TBool vad;
349 TBool cng;
350 TBool loudspk;
351};
352
353/*
354 * Implementation: Symbian Input & Output Stream.
355 */
356class CPjAudioEngine : public CBase, MQueueHandlerObserver
357{
358public:
359 enum State
360 {
361 STATE_NULL,
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000362 STATE_INITIALIZING,
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000363 STATE_READY,
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000364 STATE_STREAMING,
365 STATE_PENDING_STOP
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000366 };
367
368 ~CPjAudioEngine();
369
370 static CPjAudioEngine *NewL(struct aps_stream *parent_strm,
371 PjAudioCallback rec_cb,
372 PjAudioCallback play_cb,
373 void *user_data,
374 const CPjAudioSetting &setting);
375
376 TInt StartL();
377 void Stop();
378
379 TInt ActivateSpeaker(TBool active);
Nanang Izzuddin1b791142009-02-19 19:13:49 +0000380
381 TInt SetVolume(TInt vol) { return iSession.SetVolume(vol); }
382 TInt GetVolume() { return iSession.Volume(); }
383 TInt GetMaxVolume() { return iSession.MaxVolume(); }
384
385 TInt SetGain(TInt gain) { return iSession.SetGain(gain); }
386 TInt GetGain() { return iSession.Gain(); }
387 TInt GetMaxGain() { return iSession.MaxGain(); }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000388
389private:
390 CPjAudioEngine(struct aps_stream *parent_strm,
391 PjAudioCallback rec_cb,
392 PjAudioCallback play_cb,
393 void *user_data,
394 const CPjAudioSetting &setting);
395 void ConstructL();
396
397 TInt InitPlayL();
398 TInt InitRecL();
399 TInt StartStreamL();
400
401 // Inherited from MQueueHandlerObserver
402 virtual void InputStreamInitialized(const TInt aStatus);
403 virtual void OutputStreamInitialized(const TInt aStatus);
404 virtual void NotifyError(const TInt aError);
405
406 State state_;
407 struct aps_stream *parentStrm_;
408 CPjAudioSetting setting_;
409
410 RAPSSession iSession;
411 TAPSInitSettings iPlaySettings;
412 TAPSInitSettings iRecSettings;
413
414 RMsgQueue<TAPSCommBuffer> iReadQ;
415 RMsgQueue<TAPSCommBuffer> iReadCommQ;
416 RMsgQueue<TAPSCommBuffer> iWriteQ;
417 RMsgQueue<TAPSCommBuffer> iWriteCommQ;
418
419 CQueueHandler *iPlayCommHandler;
420 CQueueHandler *iRecCommHandler;
421 CQueueHandler *iRecHandler;
422};
423
424
425CPjAudioEngine* CPjAudioEngine::NewL(struct aps_stream *parent_strm,
426 PjAudioCallback rec_cb,
427 PjAudioCallback play_cb,
428 void *user_data,
429 const CPjAudioSetting &setting)
430{
431 CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
432 rec_cb, play_cb,
433 user_data,
434 setting);
435 CleanupStack::PushL(self);
436 self->ConstructL();
437 CleanupStack::Pop(self);
438 return self;
439}
440
441CPjAudioEngine::CPjAudioEngine(struct aps_stream *parent_strm,
442 PjAudioCallback rec_cb,
443 PjAudioCallback play_cb,
444 void *user_data,
445 const CPjAudioSetting &setting)
446 : MQueueHandlerObserver(rec_cb, play_cb, user_data),
447 state_(STATE_NULL),
448 parentStrm_(parent_strm),
449 setting_(setting),
450 iPlayCommHandler(0),
451 iRecCommHandler(0),
452 iRecHandler(0)
453{
454}
455
456CPjAudioEngine::~CPjAudioEngine()
457{
458 Stop();
459
460 delete iRecHandler;
461 delete iPlayCommHandler;
462 delete iRecCommHandler;
463
464 // On some devices, immediate closing after stopping may cause APS server
465 // panic KERN-EXEC 0, so let's wait for sometime before really closing
466 // the client session.
467 TTime start, now;
468 enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
469
470 start.UniversalTime();
471 do {
472 pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
473 now.UniversalTime();
474 } while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000);
475
476 iSession.Close();
477
478 if (state_ == STATE_READY) {
479 if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
480 iReadQ.Close();
481 iReadCommQ.Close();
482 }
483 iWriteQ.Close();
484 iWriteCommQ.Close();
485 }
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000486
487 TRACE_((THIS_FILE, "Sound device destroyed"));
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000488}
489
490TInt CPjAudioEngine::InitPlayL()
491{
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000492 TInt err = iSession.InitializePlayer(iPlaySettings);
493 if (err != KErrNone) {
494 snd_perror("Failed to initialize player", err);
495 return err;
496 }
497
498 // Open message queues for the output stream
499 TBuf<128> buf2 = iPlaySettings.iGlobal;
500 buf2.Append(_L("PlayQueue"));
501 TBuf<128> buf3 = iPlaySettings.iGlobal;
502 buf3.Append(_L("PlayCommQueue"));
503
504 while (iWriteQ.OpenGlobal(buf2))
505 User::After(10);
506 while (iWriteCommQ.OpenGlobal(buf3))
507 User::After(10);
508
509 // Construct message queue handler
510 iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ,
511 CQueueHandler::EPlayCommQueue);
512
513 // Start observing APS callbacks on output stream message queue
514 iPlayCommHandler->Start();
515
516 return 0;
517}
518
519TInt CPjAudioEngine::InitRecL()
520{
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000521 // Initialize input stream device
522 TInt err = iSession.InitializeRecorder(iRecSettings);
523 if (err != KErrNone && err != KErrAlreadyExists) {
524 snd_perror("Failed to initialize recorder", err);
525 return err;
526 }
527
528 TBuf<128> buf1 = iRecSettings.iGlobal;
529 buf1.Append(_L("RecordQueue"));
530 TBuf<128> buf4 = iRecSettings.iGlobal;
531 buf4.Append(_L("RecordCommQueue"));
532
533 // Must wait for APS thread to finish creating message queues
534 // before we can open and use them.
535 while (iReadQ.OpenGlobal(buf1))
536 User::After(10);
537 while (iReadCommQ.OpenGlobal(buf4))
538 User::After(10);
539
540 // Construct message queue handlers
541 iRecHandler = CQueueHandler::NewL(this, &iReadQ, NULL,
542 CQueueHandler::ERecordQueue);
543 iRecCommHandler = CQueueHandler::NewL(this, &iReadCommQ, NULL,
544 CQueueHandler::ERecordCommQueue);
545
546 // Start observing APS callbacks from on input stream message queue
547 iRecHandler->Start();
548 iRecCommHandler->Start();
549
550 return 0;
551}
552
553TInt CPjAudioEngine::StartL()
554{
555 if (state_ == STATE_READY)
556 return StartStreamL();
557
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000558 PJ_ASSERT_RETURN(state_ == STATE_NULL, PJMEDIA_EAUD_INVOP);
559
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000560 // Even if only capturer are opened, playback thread of APS Server need
561 // to be run(?). Since some messages will be delivered via play comm queue.
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000562 state_ = STATE_INITIALIZING;
563
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000564 return InitPlayL();
565}
566
567void CPjAudioEngine::Stop()
568{
569 if (state_ == STATE_STREAMING) {
570 iSession.Stop();
571 state_ = STATE_READY;
572 TRACE_((THIS_FILE, "Sound device stopped"));
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000573 } else if (state_ == STATE_INITIALIZING) {
574 // Initialization is on progress, so let's set the state to
575 // STATE_PENDING_STOP to prevent it starting the stream.
576 state_ = STATE_PENDING_STOP;
577
578 // Then wait until initialization done.
579 while (state_ != STATE_READY)
580 pj_symbianos_poll(-1, 100);
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000581 }
582}
583
584void CPjAudioEngine::ConstructL()
585{
586 // Recorder settings
587 iRecSettings.iFourCC = setting_.fourcc;
588 iRecSettings.iGlobal = APP_UID;
589 iRecSettings.iPriority = TMdaPriority(100);
590 iRecSettings.iPreference = TMdaPriorityPreference(0x05210001);
591 iRecSettings.iSettings.iChannels = EMMFMono;
592 iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
593
594 // Player settings
595 iPlaySettings.iFourCC = setting_.fourcc;
596 iPlaySettings.iGlobal = APP_UID;
597 iPlaySettings.iPriority = TMdaPriority(100);
598 iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001);
599 iPlaySettings.iSettings.iChannels = EMMFMono;
600 iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
601 iPlaySettings.iSettings.iVolume = 0;
602
603 User::LeaveIfError(iSession.Connect());
604}
605
606TInt CPjAudioEngine::StartStreamL()
607{
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000608 pj_assert(state_==STATE_READY || state_==STATE_INITIALIZING);
609
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000610 iSession.SetCng(setting_.cng);
611 iSession.SetVadMode(setting_.vad);
612 iSession.SetPlc(setting_.plc);
613 iSession.SetEncoderMode(setting_.mode);
614 iSession.SetDecoderMode(setting_.mode);
615 iSession.ActivateLoudspeaker(setting_.loudspk);
616
617 // Not only capture
618 if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
619 iSession.Write();
620 TRACE_((THIS_FILE, "Player started"));
621 }
622
623 // Not only playback
624 if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
625 iSession.Read();
626 TRACE_((THIS_FILE, "Recorder started"));
627 }
628
629 state_ = STATE_STREAMING;
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000630
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000631 return 0;
632}
633
634void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
635{
636 TRACE_((THIS_FILE, "Recorder initialized, err=%d", aStatus));
637
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000638 if (aStatus == KErrNone) {
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000639 // Don't start the stream since Stop() has been requested.
640 if (state_ != STATE_PENDING_STOP) {
641 StartStreamL();
642 } else {
643 state_ = STATE_READY;
644 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000645 }
646}
647
648void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
649{
650 TRACE_((THIS_FILE, "Player initialized, err=%d", aStatus));
651
652 if (aStatus == KErrNone) {
653 if (parentStrm_->param.dir == PJMEDIA_DIR_PLAYBACK) {
Nanang Izzuddin05b25282009-03-11 14:52:10 +0000654 // Don't start the stream since Stop() has been requested.
655 if (state_ != STATE_PENDING_STOP) {
656 StartStreamL();
657 } else {
658 state_ = STATE_READY;
659 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000660 } else
661 InitRecL();
662 }
663}
664
665void CPjAudioEngine::NotifyError(const TInt aError)
666{
667 snd_perror("Error from CQueueHandler", aError);
668}
669
670TInt CPjAudioEngine::ActivateSpeaker(TBool active)
671{
672 if (state_ == STATE_READY || state_ == STATE_STREAMING) {
673 iSession.ActivateLoudspeaker(active);
674 TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
675 return KErrNone;
676 }
677 return KErrNotReady;
678}
679
680
681
682static void RecCbPcm(TAPSCommBuffer &buf, void *user_data)
683{
684 struct aps_stream *strm = (struct aps_stream*) user_data;
685
686 /* Buffer has to contain normal speech. */
687 pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
688
689 /* Detect the recorder G.711 frame size, player frame size will follow
690 * this recorder frame size.
691 */
692 if (aps_g711_frame_len == 0) {
693 aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
694 TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
695 aps_g711_frame_len));
696 }
697
698 /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
699 * Whenever rec_buf is full, call parent stream callback.
700 */
701 unsigned dec_len = 0;
702
703 while (dec_len < aps_g711_frame_len) {
704 unsigned tmp;
705
706 tmp = PJ_MIN(strm->param.samples_per_frame - strm->rec_buf_len,
707 aps_g711_frame_len - dec_len);
708 pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
709 buf.iBuffer.Ptr() + 2 + dec_len,
710 tmp);
711 strm->rec_buf_len += tmp;
712 dec_len += tmp;
713
714 pj_assert(strm->rec_buf_len <= strm->param.samples_per_frame);
715
716 if (strm->rec_buf_len == strm->param.samples_per_frame) {
717 pjmedia_frame f;
718
719 f.type = PJMEDIA_FRAME_TYPE_AUDIO;
720 f.buf = strm->rec_buf;
721 f.size = strm->rec_buf_len << 1;
722
723 strm->rec_cb(strm->user_data, &f);
724 strm->rec_buf_len = 0;
725 }
726 }
727}
728
729static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data)
730{
731 struct aps_stream *strm = (struct aps_stream*) user_data;
732 unsigned g711_frame_len = aps_g711_frame_len;
733
734 /* Init buffer attributes and header. */
735 buf.iCommand = CQueueHandler::EAPSPlayData;
736 buf.iStatus = 0;
737 buf.iBuffer.Zero();
738 buf.iBuffer.Append(1);
739 buf.iBuffer.Append(0);
740
741 /* Assume frame size is 10ms if frame size hasn't been known. */
742 if (g711_frame_len == 0)
743 g711_frame_len = 80;
744
745 /* Call parent stream callback to get PCM samples to play,
746 * encode the PCM samples into G.711 and put it into APS buffer.
747 */
748 unsigned enc_len = 0;
749 while (enc_len < g711_frame_len) {
750 if (strm->play_buf_len == 0) {
751 pjmedia_frame f;
752
753 f.buf = strm->play_buf;
754 f.size = strm->param.samples_per_frame << 1;
755
756 strm->play_cb(strm->user_data, &f);
Nanang Izzuddina3775972009-03-03 18:25:55 +0000757 if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
758 pjmedia_zero_samples(strm->play_buf,
759 strm->param.samples_per_frame);
760 }
761
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000762 strm->play_buf_len = strm->param.samples_per_frame;
763 strm->play_buf_start = 0;
764 }
765
766 unsigned tmp;
767
768 tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - enc_len);
769 pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
770 &strm->play_buf[strm->play_buf_start],
771 tmp);
772 buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
773 enc_len += tmp;
774 strm->play_buf_len -= tmp;
775 strm->play_buf_start += tmp;
776 }
777}
778
779/****************************************************************************
780 * Internal APS callbacks
781 */
782
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +0000783static void RecCb(TAPSCommBuffer &buf, void *user_data)
784{
785 struct aps_stream *strm = (struct aps_stream*) user_data;
786 pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
787
788 switch(strm->param.ext_fmt.id) {
789 case PJMEDIA_FORMAT_AMR:
790 {
791 const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1;
792 unsigned len = buf.iBuffer.Length() - 1;
793
794 pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
795 if (frame->samples_cnt == strm->param.samples_per_frame) {
796 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
797 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
798 frame->samples_cnt = 0;
799 frame->subframe_cnt = 0;
800 }
801 }
802 break;
803
804 case PJMEDIA_FORMAT_G729:
805 {
806 /* Check if we got a normal or SID frame. */
807 if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) {
808 enum { NORMAL_LEN = 22, SID_LEN = 8 };
809 TBitStream *bitstream = (TBitStream*)strm->strm_data;
810 unsigned src_len = buf.iBuffer.Length()- 2;
811
812 pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
813
814 const TDesC8& p = bitstream->CompressG729Frame(
815 buf.iBuffer.Right(src_len),
816 src_len == SID_LEN);
817
818 pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
819 p.Length() << 3, 80);
820 } else { /* We got null frame. */
821 pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
822 }
823
824 if (frame->samples_cnt == strm->param.samples_per_frame) {
825 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
826 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
827 frame->samples_cnt = 0;
828 frame->subframe_cnt = 0;
829 }
830 }
831 break;
832
833 case PJMEDIA_FORMAT_ILBC:
834 {
835 unsigned samples_got;
836
837 samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
838
839 /* Check if we got a normal frame. */
840 if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) {
841 const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2;
842 unsigned len = buf.iBuffer.Length() - 2;
843
844 pjmedia_frame_ext_append_subframe(frame, p, len << 3,
845 samples_got);
846 } else { /* We got null frame. */
847 pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
848 }
849
850 if (frame->samples_cnt == strm->param.samples_per_frame) {
851 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
852 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
853 frame->samples_cnt = 0;
854 frame->subframe_cnt = 0;
855 }
856 }
857 break;
858
859 case PJMEDIA_FORMAT_PCMU:
860 case PJMEDIA_FORMAT_PCMA:
861 {
862 unsigned samples_processed = 0;
863
864 /* Make sure it is normal frame. */
865 pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
866
867 /* Detect the recorder G.711 frame size, player frame size will
868 * follow this recorder frame size.
869 */
870 if (aps_g711_frame_len == 0) {
871 aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
872 TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
873 aps_g711_frame_len));
874 }
875
876 /* Convert APS buffer format into pjmedia_frame_ext. Whenever
877 * samples count in the frame is equal to stream's samples per
878 * frame, call parent stream callback.
879 */
880 while (samples_processed < aps_g711_frame_len) {
881 unsigned tmp;
882 const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() +
883 2 + samples_processed;
884
885 tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
886 aps_g711_frame_len - samples_processed);
887
888 pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
889 samples_processed += tmp;
890
891 if (frame->samples_cnt == strm->param.samples_per_frame) {
892 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
893 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
894 frame->samples_cnt = 0;
895 frame->subframe_cnt = 0;
896 }
897 }
898 }
899 break;
900
901 default:
902 break;
903 }
904}
905
906static void PlayCb(TAPSCommBuffer &buf, void *user_data)
907{
908 struct aps_stream *strm = (struct aps_stream*) user_data;
909 pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
910
911 /* Init buffer attributes and header. */
912 buf.iCommand = CQueueHandler::EAPSPlayData;
913 buf.iStatus = 0;
914 buf.iBuffer.Zero();
915
916 switch(strm->param.ext_fmt.id) {
917 case PJMEDIA_FORMAT_AMR:
918 {
919 if (frame->samples_cnt == 0) {
920 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
921 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
922 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
923 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
924 }
925
926 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
927 pjmedia_frame_ext_subframe *sf;
928 unsigned samples_cnt;
929
930 sf = pjmedia_frame_ext_get_subframe(frame, 0);
931 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
932
933 if (sf->data && sf->bitlen) {
934 /* AMR header for APS is one byte, the format (may be!):
935 * 0xxxxy00, where xxxx:frame type, y:not sure.
936 */
937 unsigned len = (sf->bitlen+7)>>3;
938 enum {SID_FT = 8 };
939 pj_uint8_t amr_header = 4, ft = SID_FT;
940
941 if (len >= pjmedia_codec_amrnb_framelen[0])
942 ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
943
944 amr_header |= ft << 3;
945 buf.iBuffer.Append(amr_header);
946
947 buf.iBuffer.Append((TUint8*)sf->data, len);
948 } else {
949 buf.iBuffer.Append(0);
950 }
951
952 pjmedia_frame_ext_pop_subframes(frame, 1);
953
954 } else { /* PJMEDIA_FRAME_TYPE_NONE */
955 buf.iBuffer.Append(0);
956
957 frame->samples_cnt = 0;
958 frame->subframe_cnt = 0;
959 }
960 }
961 break;
962
963 case PJMEDIA_FORMAT_G729:
964 {
965 if (frame->samples_cnt == 0) {
966 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
967 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
968 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
969 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
970 }
971
972 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
973 pjmedia_frame_ext_subframe *sf;
974 unsigned samples_cnt;
975
976 sf = pjmedia_frame_ext_get_subframe(frame, 0);
977 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
978
979 if (sf->data && sf->bitlen) {
980 enum { NORMAL_LEN = 10, SID_LEN = 2 };
981 pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
982 TBitStream *bitstream = (TBitStream*)strm->strm_data;
983 const TPtrC8 src(sf->data, sf->bitlen>>3);
984 const TDesC8 &dst = bitstream->ExpandG729Frame(src,
985 sid_frame);
986 if (sid_frame) {
987 buf.iBuffer.Append(0);
988 buf.iBuffer.Append(1);
989 } else {
990 buf.iBuffer.Append(1);
991 buf.iBuffer.Append(0);
992 }
993 buf.iBuffer.Append(dst);
994 } else {
995 buf.iBuffer.Append(0);
996 buf.iBuffer.Append(0);
997 }
998
999 pjmedia_frame_ext_pop_subframes(frame, 1);
1000
1001 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1002 buf.iBuffer.Append(0);
1003 buf.iBuffer.Append(0);
1004
1005 frame->samples_cnt = 0;
1006 frame->subframe_cnt = 0;
1007 }
1008 }
1009 break;
1010
1011 case PJMEDIA_FORMAT_ILBC:
1012 {
1013 if (frame->samples_cnt == 0) {
1014 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1015 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1016 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1017 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1018 }
1019
1020 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1021 pjmedia_frame_ext_subframe *sf;
1022 unsigned samples_cnt;
1023
1024 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1025 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1026
1027 pj_assert((strm->param.ext_fmt.bitrate == 15200 &&
1028 samples_cnt == 160) ||
1029 (strm->param.ext_fmt.bitrate != 15200 &&
1030 samples_cnt == 240));
1031
1032 if (sf->data && sf->bitlen) {
1033 buf.iBuffer.Append(1);
1034 buf.iBuffer.Append(0);
1035 buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
1036 } else {
1037 buf.iBuffer.Append(0);
1038 buf.iBuffer.Append(0);
1039 }
1040
1041 pjmedia_frame_ext_pop_subframes(frame, 1);
1042
1043 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1044 buf.iBuffer.Append(0);
1045 buf.iBuffer.Append(0);
1046
1047 frame->samples_cnt = 0;
1048 frame->subframe_cnt = 0;
1049 }
1050 }
1051 break;
1052
1053 case PJMEDIA_FORMAT_PCMU:
1054 case PJMEDIA_FORMAT_PCMA:
1055 {
1056 unsigned samples_ready = 0;
1057 unsigned samples_req = aps_g711_frame_len;
1058
1059 /* Assume frame size is 10ms if frame size hasn't been known. */
1060 if (samples_req == 0)
1061 samples_req = 80;
1062
1063 buf.iBuffer.Append(1);
1064 buf.iBuffer.Append(0);
1065
1066 /* Call parent stream callback to get samples to play. */
1067 while (samples_ready < samples_req) {
1068 if (frame->samples_cnt == 0) {
1069 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1070 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1071 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1072 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1073 }
1074
1075 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1076 pjmedia_frame_ext_subframe *sf;
1077 unsigned samples_cnt;
1078
1079 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1080 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1081 if (sf->data && sf->bitlen) {
1082 buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
1083 } else {
1084 pj_uint8_t silc;
1085 silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
1086 pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
1087 buf.iBuffer.AppendFill(silc, samples_cnt);
1088 }
1089 samples_ready += samples_cnt;
1090
1091 pjmedia_frame_ext_pop_subframes(frame, 1);
1092
1093 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1094 pj_uint8_t silc;
1095
1096 silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
1097 pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
1098 buf.iBuffer.AppendFill(silc, samples_req - samples_ready);
1099
1100 samples_ready = samples_req;
1101 frame->samples_cnt = 0;
1102 frame->subframe_cnt = 0;
1103 }
1104 }
1105 }
1106 break;
1107
1108 default:
1109 break;
1110 }
1111}
1112
1113
1114/****************************************************************************
1115 * Factory operations
1116 */
Nanang Izzuddina940b362009-02-23 13:53:30 +00001117
1118/*
1119 * C compatible declaration of APS factory.
1120 */
1121PJ_BEGIN_DECL
1122PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf);
1123PJ_END_DECL
1124
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001125/*
1126 * Init APS audio driver.
1127 */
Nanang Izzuddina940b362009-02-23 13:53:30 +00001128PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001129{
1130 struct aps_factory *f;
1131 pj_pool_t *pool;
1132
1133 pool = pj_pool_create(pf, "APS", 1000, 1000, NULL);
1134 f = PJ_POOL_ZALLOC_T(pool, struct aps_factory);
1135 f->pf = pf;
1136 f->pool = pool;
1137 f->base.op = &factory_op;
1138
1139 return &f->base;
1140}
1141
1142/* API: init factory */
1143static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
1144{
1145 struct aps_factory *af = (struct aps_factory*)f;
1146
1147 pj_ansi_strcpy(af->dev_info.name, "S60 APS");
1148 af->dev_info.default_samples_per_sec = 8000;
1149 af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001150 //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001151 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001152 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
1153 PJMEDIA_AUD_DEV_CAP_VAD |
1154 PJMEDIA_AUD_DEV_CAP_CNG;
1155 af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
1156 PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
1157 af->dev_info.input_count = 1;
1158 af->dev_info.output_count = 1;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001159
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001160 af->dev_info.ext_fmt_cnt = 5;
1161
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001162 af->dev_info.ext_fmt[0].id = PJMEDIA_FORMAT_AMR;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001163 af->dev_info.ext_fmt[0].bitrate = 7400;
1164 af->dev_info.ext_fmt[0].vad = PJ_TRUE;
1165
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001166 af->dev_info.ext_fmt[1].id = PJMEDIA_FORMAT_G729;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001167 af->dev_info.ext_fmt[1].bitrate = 8000;
1168 af->dev_info.ext_fmt[1].vad = PJ_FALSE;
1169
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001170 af->dev_info.ext_fmt[2].id = PJMEDIA_FORMAT_ILBC;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001171 af->dev_info.ext_fmt[2].bitrate = 13333;
1172 af->dev_info.ext_fmt[2].vad = PJ_TRUE;
1173
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001174 af->dev_info.ext_fmt[3].id = PJMEDIA_FORMAT_PCMU;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001175 af->dev_info.ext_fmt[3].bitrate = 64000;
1176 af->dev_info.ext_fmt[3].vad = PJ_FALSE;
1177
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001178 af->dev_info.ext_fmt[4].id = PJMEDIA_FORMAT_PCMA;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001179 af->dev_info.ext_fmt[4].bitrate = 64000;
1180 af->dev_info.ext_fmt[4].vad = PJ_FALSE;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001181
1182 PJ_LOG(4, (THIS_FILE, "APS initialized"));
1183
1184 return PJ_SUCCESS;
1185}
1186
1187/* API: destroy factory */
1188static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
1189{
1190 struct aps_factory *af = (struct aps_factory*)f;
1191 pj_pool_t *pool = af->pool;
1192
1193 af->pool = NULL;
1194 pj_pool_release(pool);
1195
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001196 PJ_LOG(4, (THIS_FILE, "APS destroyed"));
1197
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001198 return PJ_SUCCESS;
1199}
1200
1201/* API: get number of devices */
1202static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
1203{
1204 PJ_UNUSED_ARG(f);
1205 return 1;
1206}
1207
1208/* API: get device info */
1209static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
1210 unsigned index,
1211 pjmedia_aud_dev_info *info)
1212{
1213 struct aps_factory *af = (struct aps_factory*)f;
1214
1215 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
1216
1217 pj_memcpy(info, &af->dev_info, sizeof(*info));
1218
1219 return PJ_SUCCESS;
1220}
1221
1222/* API: create default device parameter */
1223static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
1224 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +00001225 pjmedia_aud_param *param)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001226{
1227 struct aps_factory *af = (struct aps_factory*)f;
1228
1229 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
1230
1231 pj_bzero(param, sizeof(*param));
1232 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
1233 param->rec_id = index;
1234 param->play_id = index;
1235 param->clock_rate = af->dev_info.default_samples_per_sec;
1236 param->channel_count = 1;
1237 param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
1238 param->bits_per_sample = BITS_PER_SAMPLE;
Benny Prijono7a380002009-03-09 12:55:29 +00001239 param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
Benny Prijono7a380002009-03-09 12:55:29 +00001240 param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001241
1242 return PJ_SUCCESS;
1243}
1244
1245
1246/* API: create stream */
1247static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +00001248 const pjmedia_aud_param *param,
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001249 pjmedia_aud_rec_cb rec_cb,
1250 pjmedia_aud_play_cb play_cb,
1251 void *user_data,
1252 pjmedia_aud_stream **p_aud_strm)
1253{
1254 struct aps_factory *af = (struct aps_factory*)f;
1255 pj_pool_t *pool;
1256 struct aps_stream *strm;
1257
1258 CPjAudioSetting aps_setting;
1259 PjAudioCallback aps_rec_cb;
1260 PjAudioCallback aps_play_cb;
1261
1262 /* Can only support 16bits per sample */
1263 PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
1264
1265 /* Create and Initialize stream descriptor */
1266 pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL);
1267 PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
1268
1269 strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream);
1270 strm->pool = pool;
1271 strm->param = *param;
1272
1273 if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
1274 strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
1275
1276 /* Set audio engine fourcc. */
1277 switch(strm->param.ext_fmt.id) {
1278 case PJMEDIA_FORMAT_L16:
1279 case PJMEDIA_FORMAT_PCMU:
1280 case PJMEDIA_FORMAT_PCMA:
1281 aps_setting.fourcc = TFourCC(KMCPFourCCIdG711);
1282 break;
1283 case PJMEDIA_FORMAT_AMR:
1284 aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB);
1285 break;
1286 case PJMEDIA_FORMAT_G729:
1287 aps_setting.fourcc = TFourCC(KMCPFourCCIdG729);
1288 break;
1289 case PJMEDIA_FORMAT_ILBC:
1290 aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC);
1291 break;
1292 default:
1293 aps_setting.fourcc = 0;
1294 break;
1295 }
1296
1297 /* Set audio engine mode. */
1298 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
1299 {
1300 aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.bitrate;
1301 }
1302 else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
1303 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
1304 (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
1305 strm->param.ext_fmt.bitrate != 15200))
1306 {
1307 aps_setting.mode = EULawOr30ms;
1308 }
1309 else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
1310 (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
1311 strm->param.ext_fmt.bitrate == 15200))
1312 {
1313 aps_setting.mode = EALawOr20ms;
1314 }
1315
Nanang Izzuddin48c91fd2009-03-04 18:49:06 +00001316 /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially
1317 * causes noise?).
1318 */
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001319 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
1320 strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
Nanang Izzuddin48c91fd2009-03-04 18:49:06 +00001321 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
1322 strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001323 {
1324 aps_setting.vad = EFalse;
1325 } else {
1326 aps_setting.vad = strm->param.ext_fmt.vad;
1327 }
1328
1329 /* Set other audio engine attributes. */
1330 aps_setting.plc = strm->param.plc_enabled;
1331 aps_setting.cng = aps_setting.vad;
1332 aps_setting.loudspk =
Benny Prijono7a380002009-03-09 12:55:29 +00001333 strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001334
1335 /* Set audio engine callbacks. */
1336 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
1337 aps_play_cb = &PlayCbPcm;
1338 aps_rec_cb = &RecCbPcm;
1339 } else {
1340 aps_play_cb = &PlayCb;
1341 aps_rec_cb = &RecCb;
1342 }
1343
1344 /* Create the audio engine. */
1345 TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
1346 aps_rec_cb, aps_play_cb,
1347 strm, aps_setting));
1348 if (err != KErrNone) {
1349 pj_pool_release(pool);
1350 return PJ_RETURN_OS_ERROR(err);
1351 }
1352
Nanang Izzuddindf7937c2009-03-11 11:24:12 +00001353 /* Apply output volume setting if specified */
1354 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
1355 stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1356 &param->output_vol);
1357 }
1358
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001359 strm->rec_cb = rec_cb;
1360 strm->play_cb = play_cb;
1361 strm->user_data = user_data;
1362
1363 /* play_buf size is samples per frame. */
1364 strm->play_buf = (pj_int16_t*)pj_pool_zalloc(pool,
1365 strm->param.samples_per_frame << 1);
1366 strm->play_buf_len = 0;
1367 strm->play_buf_start = 0;
1368
1369 /* rec_buf size is samples per frame. */
1370 strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(pool,
1371 strm->param.samples_per_frame << 1);
1372 strm->rec_buf_len = 0;
1373
1374 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
1375 TBitStream *g729_bitstream = new TBitStream;
1376
1377 PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
1378 strm->strm_data = (void*)g729_bitstream;
1379 }
1380
1381 /* Done */
1382 strm->base.op = &stream_op;
1383 *p_aud_strm = &strm->base;
1384
1385 return PJ_SUCCESS;
1386}
1387
1388/* API: Get stream info. */
1389static pj_status_t stream_get_param(pjmedia_aud_stream *s,
Benny Prijono10454dc2009-02-21 14:21:59 +00001390 pjmedia_aud_param *pi)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001391{
1392 struct aps_stream *strm = (struct aps_stream*)s;
1393
1394 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1395
1396 pj_memcpy(pi, &strm->param, sizeof(*pi));
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001397
1398 /* Update the output volume setting */
1399 if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1400 &pi->output_vol) == PJ_SUCCESS)
1401 {
1402 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1403 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001404
1405 return PJ_SUCCESS;
1406}
1407
1408/* API: get capability */
1409static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1410 pjmedia_aud_dev_cap cap,
1411 void *pval)
1412{
1413 struct aps_stream *strm = (struct aps_stream*)s;
1414 pj_status_t status = PJ_ENOTSUP;
1415
1416 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1417
1418 switch (cap) {
1419 case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
1420 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
Benny Prijono7a380002009-03-09 12:55:29 +00001421 *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001422 status = PJ_SUCCESS;
1423 }
1424 break;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001425
1426 /* There is a case that GetMaxGain() stucks, e.g: in N95. */
1427 /*
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001428 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001429 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1430 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1431
1432 TInt max_gain = strm->engine->GetMaxGain();
1433 TInt gain = strm->engine->GetGain();
1434
1435 if (max_gain > 0 && gain >= 0) {
1436 *(unsigned*)pval = gain * 100 / max_gain;
1437 status = PJ_SUCCESS;
1438 } else {
1439 status = PJMEDIA_EAUD_NOTREADY;
1440 }
1441 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001442 break;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001443 */
1444
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001445 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001446 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1447 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1448
1449 TInt max_vol = strm->engine->GetMaxVolume();
1450 TInt vol = strm->engine->GetVolume();
1451
1452 if (max_vol > 0 && vol >= 0) {
1453 *(unsigned*)pval = vol * 100 / max_vol;
1454 status = PJ_SUCCESS;
1455 } else {
1456 status = PJMEDIA_EAUD_NOTREADY;
1457 }
1458 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001459 break;
1460 default:
1461 break;
1462 }
1463
1464 return status;
1465}
1466
1467/* API: set capability */
1468static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1469 pjmedia_aud_dev_cap cap,
1470 const void *pval)
1471{
1472 struct aps_stream *strm = (struct aps_stream*)s;
1473 pj_status_t status = PJ_ENOTSUP;
1474
1475 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1476
1477 switch (cap) {
1478 case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
1479 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1480 pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
1481 TInt err;
1482
1483 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1484
1485 switch (r) {
1486 case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
1487 case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
1488 err = strm->engine->ActivateSpeaker(EFalse);
1489 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1490 break;
1491 case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
1492 err = strm->engine->ActivateSpeaker(ETrue);
1493 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1494 break;
1495 default:
1496 status = PJ_EINVAL;
1497 break;
1498 }
1499 if (status == PJ_SUCCESS)
Benny Prijono7a380002009-03-09 12:55:29 +00001500 strm->param.output_route = r;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001501 }
1502 break;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001503
1504 /* There is a case that GetMaxGain() stucks, e.g: in N95. */
1505 /*
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001506 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001507 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1508 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1509
1510 TInt max_gain = strm->engine->GetMaxGain();
1511 if (max_gain > 0) {
1512 TInt gain, err;
1513
1514 gain = *(unsigned*)pval * max_gain / 100;
1515 err = strm->engine->SetGain(gain);
1516 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1517 } else {
1518 status = PJMEDIA_EAUD_NOTREADY;
1519 }
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001520 if (status == PJ_SUCCESS)
1521 strm->param.input_vol = *(unsigned*)pval;
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001522 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001523 break;
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001524 */
1525
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001526 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001527 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001528 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1529
1530 TInt max_vol = strm->engine->GetMaxVolume();
1531 if (max_vol > 0) {
1532 TInt vol, err;
1533
1534 vol = *(unsigned*)pval * max_vol / 100;
1535 err = strm->engine->SetVolume(vol);
1536 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1537 } else {
1538 status = PJMEDIA_EAUD_NOTREADY;
1539 }
Nanang Izzuddinfcb92d02009-03-10 13:32:09 +00001540 if (status == PJ_SUCCESS)
1541 strm->param.output_vol = *(unsigned*)pval;
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001542 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001543 break;
1544 default:
1545 break;
1546 }
1547
1548 return status;
1549}
1550
1551/* API: Start stream. */
1552static pj_status_t stream_start(pjmedia_aud_stream *strm)
1553{
1554 struct aps_stream *stream = (struct aps_stream*)strm;
1555
1556 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1557
1558 if (stream->engine) {
1559 TInt err = stream->engine->StartL();
1560 if (err != KErrNone)
1561 return PJ_RETURN_OS_ERROR(err);
1562 }
1563
1564 return PJ_SUCCESS;
1565}
1566
1567/* API: Stop stream. */
1568static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1569{
1570 struct aps_stream *stream = (struct aps_stream*)strm;
1571
1572 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1573
1574 if (stream->engine) {
1575 stream->engine->Stop();
1576 }
1577
1578 return PJ_SUCCESS;
1579}
1580
1581
1582/* API: Destroy stream. */
1583static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1584{
1585 struct aps_stream *stream = (struct aps_stream*)strm;
1586
1587 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1588
1589 stream_stop(strm);
1590
1591 delete stream->engine;
1592 stream->engine = NULL;
1593
1594 if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
1595 TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
1596 stream->strm_data = NULL;
1597 delete g729_bitstream;
1598 }
1599
1600 pj_pool_t *pool;
1601 pool = stream->pool;
1602 if (pool) {
1603 stream->pool = NULL;
1604 pj_pool_release(pool);
1605 }
1606
1607 return PJ_SUCCESS;
1608}
1609
Nanang Izzuddina940b362009-02-23 13:53:30 +00001610#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
1611