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