blob: 2a87c0ef1ccf6f280deb59690eb5c3f3719b8e6b [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);
733 strm->play_buf_len = strm->param.samples_per_frame;
734 strm->play_buf_start = 0;
735 }
736
737 unsigned tmp;
738
739 tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - enc_len);
740 pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
741 &strm->play_buf[strm->play_buf_start],
742 tmp);
743 buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
744 enc_len += tmp;
745 strm->play_buf_len -= tmp;
746 strm->play_buf_start += tmp;
747 }
748}
749
750/****************************************************************************
751 * Internal APS callbacks
752 */
753
754/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
755 * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
756 */
757#include "../pjmedia/s60_g729_bitstream.h"
758#include <pjmedia-codec/amr_helper.h>
759
760static void RecCb(TAPSCommBuffer &buf, void *user_data)
761{
762 struct aps_stream *strm = (struct aps_stream*) user_data;
763 pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
764
765 switch(strm->param.ext_fmt.id) {
766 case PJMEDIA_FORMAT_AMR:
767 {
768 const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1;
769 unsigned len = buf.iBuffer.Length() - 1;
770
771 pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
772 if (frame->samples_cnt == strm->param.samples_per_frame) {
773 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
774 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
775 frame->samples_cnt = 0;
776 frame->subframe_cnt = 0;
777 }
778 }
779 break;
780
781 case PJMEDIA_FORMAT_G729:
782 {
783 /* Check if we got a normal or SID frame. */
784 if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) {
785 enum { NORMAL_LEN = 22, SID_LEN = 8 };
786 TBitStream *bitstream = (TBitStream*)strm->strm_data;
787 unsigned src_len = buf.iBuffer.Length()- 2;
788
789 pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
790
791 const TDesC8& p = bitstream->CompressG729Frame(
792 buf.iBuffer.Right(src_len),
793 src_len == SID_LEN);
794
795 pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
796 p.Length() << 3, 80);
797 } else { /* We got null frame. */
798 pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
799 }
800
801 if (frame->samples_cnt == strm->param.samples_per_frame) {
802 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
803 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
804 frame->samples_cnt = 0;
805 frame->subframe_cnt = 0;
806 }
807 }
808 break;
809
810 case PJMEDIA_FORMAT_ILBC:
811 {
812 unsigned samples_got;
813
814 samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240;
815
816 /* Check if we got a normal frame. */
817 if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) {
818 const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2;
819 unsigned len = buf.iBuffer.Length() - 2;
820
821 pjmedia_frame_ext_append_subframe(frame, p, len << 3,
822 samples_got);
823 } else { /* We got null frame. */
824 pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
825 }
826
827 if (frame->samples_cnt == strm->param.samples_per_frame) {
828 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
829 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
830 frame->samples_cnt = 0;
831 frame->subframe_cnt = 0;
832 }
833 }
834 break;
835
836 case PJMEDIA_FORMAT_PCMU:
837 case PJMEDIA_FORMAT_PCMA:
838 {
839 unsigned samples_processed = 0;
840
841 /* Make sure it is normal frame. */
842 pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
843
844 /* Detect the recorder G.711 frame size, player frame size will
845 * follow this recorder frame size.
846 */
847 if (aps_g711_frame_len == 0) {
848 aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
849 TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
850 aps_g711_frame_len));
851 }
852
853 /* Convert APS buffer format into pjmedia_frame_ext. Whenever
854 * samples count in the frame is equal to stream's samples per
855 * frame, call parent stream callback.
856 */
857 while (samples_processed < aps_g711_frame_len) {
858 unsigned tmp;
859 const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() +
860 2 + samples_processed;
861
862 tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
863 aps_g711_frame_len - samples_processed);
864
865 pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
866 samples_processed += tmp;
867
868 if (frame->samples_cnt == strm->param.samples_per_frame) {
869 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
870 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
871 frame->samples_cnt = 0;
872 frame->subframe_cnt = 0;
873 }
874 }
875 }
876 break;
877
878 default:
879 break;
880 }
881}
882
883static void PlayCb(TAPSCommBuffer &buf, void *user_data)
884{
885 struct aps_stream *strm = (struct aps_stream*) user_data;
886 pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
887
888 /* Init buffer attributes and header. */
889 buf.iCommand = CQueueHandler::EAPSPlayData;
890 buf.iStatus = 0;
891 buf.iBuffer.Zero();
892
893 switch(strm->param.ext_fmt.id) {
894 case PJMEDIA_FORMAT_AMR:
895 {
896 if (frame->samples_cnt == 0) {
897 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
898 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
899 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
900 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
901 }
902
903 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
904 pjmedia_frame_ext_subframe *sf;
905 unsigned samples_cnt;
906
907 sf = pjmedia_frame_ext_get_subframe(frame, 0);
908 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
909
910 if (sf->data && sf->bitlen) {
911 /* AMR header for APS is one byte, the format (may be!):
912 * 0xxxxy00, where xxxx:frame type, y:not sure.
913 */
914 unsigned len = (sf->bitlen+7)>>3;
915 enum {SID_FT = 8 };
916 pj_uint8_t amr_header = 4, ft = SID_FT;
917
918 if (len >= pjmedia_codec_amrnb_framelen[0])
919 ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
920
921 amr_header |= ft << 3;
922 buf.iBuffer.Append(amr_header);
923
924 buf.iBuffer.Append((TUint8*)sf->data, len);
925 } else {
926 buf.iBuffer.Append(0);
927 }
928
929 pjmedia_frame_ext_pop_subframes(frame, 1);
930
931 } else { /* PJMEDIA_FRAME_TYPE_NONE */
932 buf.iBuffer.Append(0);
933
934 frame->samples_cnt = 0;
935 frame->subframe_cnt = 0;
936 }
937 }
938 break;
939
940 case PJMEDIA_FORMAT_G729:
941 {
942 if (frame->samples_cnt == 0) {
943 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
944 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
945 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
946 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
947 }
948
949 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
950 pjmedia_frame_ext_subframe *sf;
951 unsigned samples_cnt;
952
953 sf = pjmedia_frame_ext_get_subframe(frame, 0);
954 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
955
956 if (sf->data && sf->bitlen) {
957 enum { NORMAL_LEN = 10, SID_LEN = 2 };
958 pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
959 TBitStream *bitstream = (TBitStream*)strm->strm_data;
960 const TPtrC8 src(sf->data, sf->bitlen>>3);
961 const TDesC8 &dst = bitstream->ExpandG729Frame(src,
962 sid_frame);
963 if (sid_frame) {
964 buf.iBuffer.Append(0);
965 buf.iBuffer.Append(1);
966 } else {
967 buf.iBuffer.Append(1);
968 buf.iBuffer.Append(0);
969 }
970 buf.iBuffer.Append(dst);
971 } else {
972 buf.iBuffer.Append(0);
973 buf.iBuffer.Append(0);
974 }
975
976 pjmedia_frame_ext_pop_subframes(frame, 1);
977
978 } else { /* PJMEDIA_FRAME_TYPE_NONE */
979 buf.iBuffer.Append(0);
980 buf.iBuffer.Append(0);
981
982 frame->samples_cnt = 0;
983 frame->subframe_cnt = 0;
984 }
985 }
986 break;
987
988 case PJMEDIA_FORMAT_ILBC:
989 {
990 if (frame->samples_cnt == 0) {
991 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
992 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
993 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
994 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
995 }
996
997 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
998 pjmedia_frame_ext_subframe *sf;
999 unsigned samples_cnt;
1000
1001 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1002 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1003
1004 pj_assert((strm->param.ext_fmt.bitrate == 15200 &&
1005 samples_cnt == 160) ||
1006 (strm->param.ext_fmt.bitrate != 15200 &&
1007 samples_cnt == 240));
1008
1009 if (sf->data && sf->bitlen) {
1010 buf.iBuffer.Append(1);
1011 buf.iBuffer.Append(0);
1012 buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
1013 } else {
1014 buf.iBuffer.Append(0);
1015 buf.iBuffer.Append(0);
1016 }
1017
1018 pjmedia_frame_ext_pop_subframes(frame, 1);
1019
1020 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1021 buf.iBuffer.Append(0);
1022 buf.iBuffer.Append(0);
1023
1024 frame->samples_cnt = 0;
1025 frame->subframe_cnt = 0;
1026 }
1027 }
1028 break;
1029
1030 case PJMEDIA_FORMAT_PCMU:
1031 case PJMEDIA_FORMAT_PCMA:
1032 {
1033 unsigned samples_ready = 0;
1034 unsigned samples_req = aps_g711_frame_len;
1035
1036 /* Assume frame size is 10ms if frame size hasn't been known. */
1037 if (samples_req == 0)
1038 samples_req = 80;
1039
1040 buf.iBuffer.Append(1);
1041 buf.iBuffer.Append(0);
1042
1043 /* Call parent stream callback to get samples to play. */
1044 while (samples_ready < samples_req) {
1045 if (frame->samples_cnt == 0) {
1046 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1047 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1048 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1049 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1050 }
1051
1052 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1053 pjmedia_frame_ext_subframe *sf;
1054 unsigned samples_cnt;
1055
1056 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1057 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1058 if (sf->data && sf->bitlen) {
1059 buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
1060 } else {
1061 pj_uint8_t silc;
1062 silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
1063 pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
1064 buf.iBuffer.AppendFill(silc, samples_cnt);
1065 }
1066 samples_ready += samples_cnt;
1067
1068 pjmedia_frame_ext_pop_subframes(frame, 1);
1069
1070 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1071 pj_uint8_t silc;
1072
1073 silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
1074 pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
1075 buf.iBuffer.AppendFill(silc, samples_req - samples_ready);
1076
1077 samples_ready = samples_req;
1078 frame->samples_cnt = 0;
1079 frame->subframe_cnt = 0;
1080 }
1081 }
1082 }
1083 break;
1084
1085 default:
1086 break;
1087 }
1088}
1089
1090
1091/****************************************************************************
1092 * Factory operations
1093 */
Nanang Izzuddina940b362009-02-23 13:53:30 +00001094
1095/*
1096 * C compatible declaration of APS factory.
1097 */
1098PJ_BEGIN_DECL
1099PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf);
1100PJ_END_DECL
1101
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001102/*
1103 * Init APS audio driver.
1104 */
Nanang Izzuddina940b362009-02-23 13:53:30 +00001105PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001106{
1107 struct aps_factory *f;
1108 pj_pool_t *pool;
1109
1110 pool = pj_pool_create(pf, "APS", 1000, 1000, NULL);
1111 f = PJ_POOL_ZALLOC_T(pool, struct aps_factory);
1112 f->pf = pf;
1113 f->pool = pool;
1114 f->base.op = &factory_op;
1115
1116 return &f->base;
1117}
1118
1119/* API: init factory */
1120static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
1121{
1122 struct aps_factory *af = (struct aps_factory*)f;
1123
1124 pj_ansi_strcpy(af->dev_info.name, "S60 APS");
1125 af->dev_info.default_samples_per_sec = 8000;
1126 af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001127 PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
1128 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001129 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
1130 PJMEDIA_AUD_DEV_CAP_VAD |
1131 PJMEDIA_AUD_DEV_CAP_CNG;
1132 af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
1133 PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
1134 af->dev_info.input_count = 1;
1135 af->dev_info.output_count = 1;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001136
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001137 af->dev_info.ext_fmt_cnt = 6;
1138 af->dev_info.ext_fmt[0].id = PJMEDIA_FORMAT_AMR;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001139 af->dev_info.ext_fmt[1].id = PJMEDIA_FORMAT_G729;
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001140 af->dev_info.ext_fmt[2].id = PJMEDIA_FORMAT_ILBC;
1141 af->dev_info.ext_fmt[3].id = PJMEDIA_FORMAT_PCMU;
1142 af->dev_info.ext_fmt[4].id = PJMEDIA_FORMAT_PCMA;
1143 af->dev_info.ext_fmt[5].id = PJMEDIA_FORMAT_L16;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001144
1145 PJ_LOG(4, (THIS_FILE, "APS initialized"));
1146
1147 return PJ_SUCCESS;
1148}
1149
1150/* API: destroy factory */
1151static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
1152{
1153 struct aps_factory *af = (struct aps_factory*)f;
1154 pj_pool_t *pool = af->pool;
1155
1156 af->pool = NULL;
1157 pj_pool_release(pool);
1158
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001159 PJ_LOG(4, (THIS_FILE, "APS destroyed"));
1160
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001161 return PJ_SUCCESS;
1162}
1163
1164/* API: get number of devices */
1165static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
1166{
1167 PJ_UNUSED_ARG(f);
1168 return 1;
1169}
1170
1171/* API: get device info */
1172static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
1173 unsigned index,
1174 pjmedia_aud_dev_info *info)
1175{
1176 struct aps_factory *af = (struct aps_factory*)f;
1177
1178 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
1179
1180 pj_memcpy(info, &af->dev_info, sizeof(*info));
1181
1182 return PJ_SUCCESS;
1183}
1184
1185/* API: create default device parameter */
1186static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
1187 unsigned index,
Benny Prijono10454dc2009-02-21 14:21:59 +00001188 pjmedia_aud_param *param)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001189{
1190 struct aps_factory *af = (struct aps_factory*)f;
1191
1192 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
1193
1194 pj_bzero(param, sizeof(*param));
1195 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
1196 param->rec_id = index;
1197 param->play_id = index;
1198 param->clock_rate = af->dev_info.default_samples_per_sec;
1199 param->channel_count = 1;
1200 param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
1201 param->bits_per_sample = BITS_PER_SAMPLE;
1202 param->flags = af->dev_info.caps;
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001203 param->ext_fmt.id = PJMEDIA_FORMAT_L16;
1204 param->out_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001205
1206 return PJ_SUCCESS;
1207}
1208
1209
1210/* API: create stream */
1211static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
Benny Prijono10454dc2009-02-21 14:21:59 +00001212 const pjmedia_aud_param *param,
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001213 pjmedia_aud_rec_cb rec_cb,
1214 pjmedia_aud_play_cb play_cb,
1215 void *user_data,
1216 pjmedia_aud_stream **p_aud_strm)
1217{
1218 struct aps_factory *af = (struct aps_factory*)f;
1219 pj_pool_t *pool;
1220 struct aps_stream *strm;
1221
1222 CPjAudioSetting aps_setting;
1223 PjAudioCallback aps_rec_cb;
1224 PjAudioCallback aps_play_cb;
1225
1226 /* Can only support 16bits per sample */
1227 PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
1228
1229 /* Create and Initialize stream descriptor */
1230 pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL);
1231 PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
1232
1233 strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream);
1234 strm->pool = pool;
1235 strm->param = *param;
1236
1237 if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
1238 strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
1239
1240 /* Set audio engine fourcc. */
1241 switch(strm->param.ext_fmt.id) {
1242 case PJMEDIA_FORMAT_L16:
1243 case PJMEDIA_FORMAT_PCMU:
1244 case PJMEDIA_FORMAT_PCMA:
1245 aps_setting.fourcc = TFourCC(KMCPFourCCIdG711);
1246 break;
1247 case PJMEDIA_FORMAT_AMR:
1248 aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB);
1249 break;
1250 case PJMEDIA_FORMAT_G729:
1251 aps_setting.fourcc = TFourCC(KMCPFourCCIdG729);
1252 break;
1253 case PJMEDIA_FORMAT_ILBC:
1254 aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC);
1255 break;
1256 default:
1257 aps_setting.fourcc = 0;
1258 break;
1259 }
1260
1261 /* Set audio engine mode. */
1262 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
1263 {
1264 aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.bitrate;
1265 }
1266 else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
1267 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
1268 (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
1269 strm->param.ext_fmt.bitrate != 15200))
1270 {
1271 aps_setting.mode = EULawOr30ms;
1272 }
1273 else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
1274 (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
1275 strm->param.ext_fmt.bitrate == 15200))
1276 {
1277 aps_setting.mode = EALawOr20ms;
1278 }
1279
1280 /* Disable VAD on L16 and G711. */
1281 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
1282 strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
1283 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
1284 {
1285 aps_setting.vad = EFalse;
1286 } else {
1287 aps_setting.vad = strm->param.ext_fmt.vad;
1288 }
1289
1290 /* Set other audio engine attributes. */
1291 aps_setting.plc = strm->param.plc_enabled;
1292 aps_setting.cng = aps_setting.vad;
1293 aps_setting.loudspk =
1294 strm->param.out_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
1295
1296 /* Set audio engine callbacks. */
1297 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
1298 aps_play_cb = &PlayCbPcm;
1299 aps_rec_cb = &RecCbPcm;
1300 } else {
1301 aps_play_cb = &PlayCb;
1302 aps_rec_cb = &RecCb;
1303 }
1304
1305 /* Create the audio engine. */
1306 TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
1307 aps_rec_cb, aps_play_cb,
1308 strm, aps_setting));
1309 if (err != KErrNone) {
1310 pj_pool_release(pool);
1311 return PJ_RETURN_OS_ERROR(err);
1312 }
1313
1314 strm->rec_cb = rec_cb;
1315 strm->play_cb = play_cb;
1316 strm->user_data = user_data;
1317
1318 /* play_buf size is samples per frame. */
1319 strm->play_buf = (pj_int16_t*)pj_pool_zalloc(pool,
1320 strm->param.samples_per_frame << 1);
1321 strm->play_buf_len = 0;
1322 strm->play_buf_start = 0;
1323
1324 /* rec_buf size is samples per frame. */
1325 strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(pool,
1326 strm->param.samples_per_frame << 1);
1327 strm->rec_buf_len = 0;
1328
1329 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
1330 TBitStream *g729_bitstream = new TBitStream;
1331
1332 PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
1333 strm->strm_data = (void*)g729_bitstream;
1334 }
1335
1336 /* Done */
1337 strm->base.op = &stream_op;
1338 *p_aud_strm = &strm->base;
1339
1340 return PJ_SUCCESS;
1341}
1342
1343/* API: Get stream info. */
1344static pj_status_t stream_get_param(pjmedia_aud_stream *s,
Benny Prijono10454dc2009-02-21 14:21:59 +00001345 pjmedia_aud_param *pi)
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001346{
1347 struct aps_stream *strm = (struct aps_stream*)s;
1348
1349 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1350
1351 pj_memcpy(pi, &strm->param, sizeof(*pi));
1352
1353 return PJ_SUCCESS;
1354}
1355
1356/* API: get capability */
1357static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1358 pjmedia_aud_dev_cap cap,
1359 void *pval)
1360{
1361 struct aps_stream *strm = (struct aps_stream*)s;
1362 pj_status_t status = PJ_ENOTSUP;
1363
1364 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1365
1366 switch (cap) {
1367 case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
1368 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1369 *(pjmedia_aud_dev_route*)pval = strm->param.out_route;
1370 status = PJ_SUCCESS;
1371 }
1372 break;
1373 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001374 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1375 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1376
1377 TInt max_gain = strm->engine->GetMaxGain();
1378 TInt gain = strm->engine->GetGain();
1379
1380 if (max_gain > 0 && gain >= 0) {
1381 *(unsigned*)pval = gain * 100 / max_gain;
1382 status = PJ_SUCCESS;
1383 } else {
1384 status = PJMEDIA_EAUD_NOTREADY;
1385 }
1386 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001387 break;
1388 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001389 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1390 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1391
1392 TInt max_vol = strm->engine->GetMaxVolume();
1393 TInt vol = strm->engine->GetVolume();
1394
1395 if (max_vol > 0 && vol >= 0) {
1396 *(unsigned*)pval = vol * 100 / max_vol;
1397 status = PJ_SUCCESS;
1398 } else {
1399 status = PJMEDIA_EAUD_NOTREADY;
1400 }
1401 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001402 break;
1403 default:
1404 break;
1405 }
1406
1407 return status;
1408}
1409
1410/* API: set capability */
1411static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1412 pjmedia_aud_dev_cap cap,
1413 const void *pval)
1414{
1415 struct aps_stream *strm = (struct aps_stream*)s;
1416 pj_status_t status = PJ_ENOTSUP;
1417
1418 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1419
1420 switch (cap) {
1421 case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
1422 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1423 pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
1424 TInt err;
1425
1426 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1427
1428 switch (r) {
1429 case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
1430 case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
1431 err = strm->engine->ActivateSpeaker(EFalse);
1432 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1433 break;
1434 case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
1435 err = strm->engine->ActivateSpeaker(ETrue);
1436 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1437 break;
1438 default:
1439 status = PJ_EINVAL;
1440 break;
1441 }
1442 if (status == PJ_SUCCESS)
1443 strm->param.out_route = r;
1444 }
1445 break;
1446 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001447 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1448 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1449
1450 TInt max_gain = strm->engine->GetMaxGain();
1451 if (max_gain > 0) {
1452 TInt gain, err;
1453
1454 gain = *(unsigned*)pval * max_gain / 100;
1455 err = strm->engine->SetGain(gain);
1456 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1457 } else {
1458 status = PJMEDIA_EAUD_NOTREADY;
1459 }
1460 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001461 break;
1462 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
Nanang Izzuddin1b791142009-02-19 19:13:49 +00001463 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1464 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1465
1466 TInt max_vol = strm->engine->GetMaxVolume();
1467 if (max_vol > 0) {
1468 TInt vol, err;
1469
1470 vol = *(unsigned*)pval * max_vol / 100;
1471 err = strm->engine->SetVolume(vol);
1472 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1473 } else {
1474 status = PJMEDIA_EAUD_NOTREADY;
1475 }
1476 }
Nanang Izzuddin1c1c7d42009-02-19 17:19:46 +00001477 break;
1478 default:
1479 break;
1480 }
1481
1482 return status;
1483}
1484
1485/* API: Start stream. */
1486static pj_status_t stream_start(pjmedia_aud_stream *strm)
1487{
1488 struct aps_stream *stream = (struct aps_stream*)strm;
1489
1490 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1491
1492 if (stream->engine) {
1493 TInt err = stream->engine->StartL();
1494 if (err != KErrNone)
1495 return PJ_RETURN_OS_ERROR(err);
1496 }
1497
1498 return PJ_SUCCESS;
1499}
1500
1501/* API: Stop stream. */
1502static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1503{
1504 struct aps_stream *stream = (struct aps_stream*)strm;
1505
1506 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1507
1508 if (stream->engine) {
1509 stream->engine->Stop();
1510 }
1511
1512 return PJ_SUCCESS;
1513}
1514
1515
1516/* API: Destroy stream. */
1517static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1518{
1519 struct aps_stream *stream = (struct aps_stream*)strm;
1520
1521 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1522
1523 stream_stop(strm);
1524
1525 delete stream->engine;
1526 stream->engine = NULL;
1527
1528 if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
1529 TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
1530 stream->strm_data = NULL;
1531 delete g729_bitstream;
1532 }
1533
1534 pj_pool_t *pool;
1535 pool = stream->pool;
1536 if (pool) {
1537 stream->pool = NULL;
1538 pj_pool_release(pool);
1539 }
1540
1541 return PJ_SUCCESS;
1542}
1543
Nanang Izzuddina940b362009-02-23 13:53:30 +00001544#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
1545