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