blob: 9b60ae1b11056f0d1999b85ac78439b6bc4bb88b [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id: symb_aps_dev.cpp 4243 2012-08-31 11:42:17Z nanang $ */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002/*
3 * Copyright (C) 2008-2011 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-audiodev/errno.h>
22#include <pjmedia/alaw_ulaw.h>
23#include <pjmedia/resample.h>
24#include <pjmedia/stereo.h>
25#include <pj/assert.h>
26#include <pj/log.h>
27#include <pj/math.h>
28#include <pj/os.h>
29#include <pj/string.h>
30
31#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
32
33#include <e32msgqueue.h>
34#include <sounddevice.h>
35#include <APSClientSession.h>
36#include <pjmedia-codec/amr_helper.h>
37
38/* Pack/unpack G.729 frame of S60 DSP codec, taken from:
39 * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format
40 */
41#include "s60_g729_bitstream.h"
42
43
44#define THIS_FILE "symb_aps_dev.c"
45#define BITS_PER_SAMPLE 16
46
47
48#if 1
49# define TRACE_(st) PJ_LOG(3, st)
50#else
51# define TRACE_(st)
52#endif
53
54
55/* App UID to open global APS queues to communicate with the APS server. */
56extern TPtrC APP_UID;
57
58/* APS G.711 frame length */
59static pj_uint8_t aps_g711_frame_len;
60
61
62/* APS factory */
63struct aps_factory
64{
65 pjmedia_aud_dev_factory base;
66 pj_pool_t *pool;
67 pj_pool_factory *pf;
68 pjmedia_aud_dev_info dev_info;
69};
70
71
72/* Forward declaration of CPjAudioEngine */
73class CPjAudioEngine;
74
75
76/* APS stream. */
77struct aps_stream
78{
79 // Base
80 pjmedia_aud_stream base; /**< Base class. */
81
82 // Pool
83 pj_pool_t *pool; /**< Memory pool. */
84
85 // Common settings.
86 pjmedia_aud_param param; /**< Stream param. */
87 pjmedia_aud_rec_cb rec_cb; /**< Record callback. */
88 pjmedia_aud_play_cb play_cb; /**< Playback callback. */
89 void *user_data; /**< Application data. */
90
91 // Audio engine
92 CPjAudioEngine *engine; /**< Internal engine. */
93
94 pj_timestamp ts_play; /**< Playback timestamp.*/
95 pj_timestamp ts_rec; /**< Record timestamp. */
96
97 pj_int16_t *play_buf; /**< Playback buffer. */
98 pj_uint16_t play_buf_len; /**< Playback buffer length. */
99 pj_uint16_t play_buf_start; /**< Playback buffer start index. */
100 pj_int16_t *rec_buf; /**< Record buffer. */
101 pj_uint16_t rec_buf_len; /**< Record buffer length. */
102 void *strm_data; /**< Stream data. */
103
104 /* Resampling is needed, in case audio device is opened with clock rate
105 * other than 8kHz (only for PCM format).
106 */
107 pjmedia_resample *play_resample; /**< Resampler for playback. */
108 pjmedia_resample *rec_resample; /**< Resampler for recording */
109 pj_uint16_t resample_factor; /**< Resample factor, requested
110 clock rate / 8000 */
111
112 /* When stream is working in PCM format, where the samples may need to be
113 * resampled from/to different clock rate and/or channel count, PCM buffer
114 * is needed to perform such resampling operations.
115 */
116 pj_int16_t *pcm_buf; /**< PCM buffer. */
117};
118
119
120/* Prototypes */
121static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
122static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
123static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
124static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
125static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
126 unsigned index,
127 pjmedia_aud_dev_info *info);
128static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
129 unsigned index,
130 pjmedia_aud_param *param);
131static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
132 const pjmedia_aud_param *param,
133 pjmedia_aud_rec_cb rec_cb,
134 pjmedia_aud_play_cb play_cb,
135 void *user_data,
136 pjmedia_aud_stream **p_aud_strm);
137
138static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
139 pjmedia_aud_param *param);
140static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
141 pjmedia_aud_dev_cap cap,
142 void *value);
143static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
144 pjmedia_aud_dev_cap cap,
145 const void *value);
146static pj_status_t stream_start(pjmedia_aud_stream *strm);
147static pj_status_t stream_stop(pjmedia_aud_stream *strm);
148static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
149
150
151/* Operations */
152static pjmedia_aud_dev_factory_op factory_op =
153{
154 &factory_init,
155 &factory_destroy,
156 &factory_get_dev_count,
157 &factory_get_dev_info,
158 &factory_default_param,
159 &factory_create_stream,
160 &factory_refresh
161};
162
163static pjmedia_aud_stream_op stream_op =
164{
165 &stream_get_param,
166 &stream_get_cap,
167 &stream_set_cap,
168 &stream_start,
169 &stream_stop,
170 &stream_destroy
171};
172
173
174/****************************************************************************
175 * Internal APS Engine
176 */
177
178/*
179 * Utility: print sound device error
180 */
181static void snd_perror(const char *title, TInt rc)
182{
183 PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc));
184}
185
186/*
187 * Utility: wait for specified time.
188 */
189static void snd_wait(unsigned ms)
190{
191 TTime start, now;
192
193 start.UniversalTime();
194 do {
195 pj_symbianos_poll(-1, ms);
196 now.UniversalTime();
197 } while (now.MicroSecondsFrom(start) < ms * 1000);
198}
199
200typedef void(*PjAudioCallback)(TAPSCommBuffer &buf, void *user_data);
201
202/**
203 * Abstract class for handler of callbacks from APS client.
204 */
205class MQueueHandlerObserver
206{
207public:
208 MQueueHandlerObserver(PjAudioCallback RecCb_, PjAudioCallback PlayCb_,
209 void *UserData_)
210 : RecCb(RecCb_), PlayCb(PlayCb_), UserData(UserData_)
211 {}
212
213 virtual void InputStreamInitialized(const TInt aStatus) = 0;
214 virtual void OutputStreamInitialized(const TInt aStatus) = 0;
215 virtual void NotifyError(const TInt aError) = 0;
216
217public:
218 PjAudioCallback RecCb;
219 PjAudioCallback PlayCb;
220 void *UserData;
221};
222
223/**
224 * Handler for communication and data queue.
225 */
226class CQueueHandler : public CActive
227{
228public:
229 // Types of queue handler
230 enum TQueueHandlerType {
231 ERecordCommQueue,
232 EPlayCommQueue,
233 ERecordQueue,
234 EPlayQueue
235 };
236
237 // The order corresponds to the APS Server state, do not change!
238 enum TState {
239 EAPSPlayerInitialize = 1,
240 EAPSRecorderInitialize = 2,
241 EAPSPlayData = 3,
242 EAPSRecordData = 4,
243 EAPSPlayerInitComplete = 5,
244 EAPSRecorderInitComplete = 6
245 };
246
247 static CQueueHandler* NewL(MQueueHandlerObserver* aObserver,
248 RMsgQueue<TAPSCommBuffer>* aQ,
249 RMsgQueue<TAPSCommBuffer>* aWriteQ,
250 TQueueHandlerType aType)
251 {
252 CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ,
253 aType);
254 CleanupStack::PushL(self);
255 self->ConstructL();
256 CleanupStack::Pop(self);
257 return self;
258 }
259
260 // Destructor
261 ~CQueueHandler() { Cancel(); }
262
263 // Start listening queue event
264 void Start() {
265 iQ->NotifyDataAvailable(iStatus);
266 SetActive();
267 }
268
269private:
270 // Constructor
271 CQueueHandler(MQueueHandlerObserver* aObserver,
272 RMsgQueue<TAPSCommBuffer>* aQ,
273 RMsgQueue<TAPSCommBuffer>* aWriteQ,
274 TQueueHandlerType aType)
275 : CActive(CActive::EPriorityHigh),
276 iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType)
277 {
278 CActiveScheduler::Add(this);
279
280 // use lower priority for comm queues
281 if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue))
282 SetPriority(CActive::EPriorityStandard);
283 }
284
285 // Second phase constructor
286 void ConstructL() {}
287
288 // Inherited from CActive
289 void DoCancel() { iQ->CancelDataAvailable(); }
290
291 void RunL() {
292 if (iStatus != KErrNone) {
293 iObserver->NotifyError(iStatus.Int());
294 return;
295 }
296
297 TAPSCommBuffer buffer;
298 TInt ret = iQ->Receive(buffer);
299
300 if (ret != KErrNone) {
301 iObserver->NotifyError(ret);
302 return;
303 }
304
305 switch (iType) {
306 case ERecordQueue:
307 if (buffer.iCommand == EAPSRecordData) {
308 iObserver->RecCb(buffer, iObserver->UserData);
309 } else {
310 iObserver->NotifyError(buffer.iStatus);
311 }
312 break;
313
314 // Callbacks from the APS main thread
315 case EPlayCommQueue:
316 switch (buffer.iCommand) {
317 case EAPSPlayData:
318 if (buffer.iStatus == KErrUnderflow) {
319 iObserver->PlayCb(buffer, iObserver->UserData);
320 iWriteQ->Send(buffer);
321 }
322 break;
323 case EAPSPlayerInitialize:
324 iObserver->NotifyError(buffer.iStatus);
325 break;
326 case EAPSPlayerInitComplete:
327 iObserver->OutputStreamInitialized(buffer.iStatus);
328 break;
329 case EAPSRecorderInitComplete:
330 iObserver->InputStreamInitialized(buffer.iStatus);
331 break;
332 default:
333 iObserver->NotifyError(buffer.iStatus);
334 break;
335 }
336 break;
337
338 // Callbacks from the APS recorder thread
339 case ERecordCommQueue:
340 switch (buffer.iCommand) {
341 // The APS recorder thread will only report errors
342 // through this handler. All other callbacks will be
343 // sent from the APS main thread through EPlayCommQueue
344 case EAPSRecorderInitialize:
345 case EAPSRecordData:
346 default:
347 iObserver->NotifyError(buffer.iStatus);
348 break;
349 }
350 break;
351
352 default:
353 break;
354 }
355
356 // issue next request
357 iQ->NotifyDataAvailable(iStatus);
358 SetActive();
359 }
360
361 TInt RunError(TInt) {
362 return 0;
363 }
364
365 // Data
366 RMsgQueue<TAPSCommBuffer> *iQ; // (not owned)
367 RMsgQueue<TAPSCommBuffer> *iWriteQ; // (not owned)
368 MQueueHandlerObserver *iObserver; // (not owned)
369 TQueueHandlerType iType;
370};
371
372/*
373 * Audio setting for CPjAudioEngine.
374 */
375class CPjAudioSetting
376{
377public:
378 TFourCC fourcc;
379 TAPSCodecMode mode;
380 TBool plc;
381 TBool vad;
382 TBool cng;
383 TBool loudspk;
384};
385
386/*
387 * Implementation: Symbian Input & Output Stream.
388 */
389class CPjAudioEngine : public CBase, MQueueHandlerObserver
390{
391public:
392 enum State
393 {
394 STATE_NULL,
395 STATE_INITIALIZING,
396 STATE_READY,
397 STATE_STREAMING,
398 STATE_PENDING_STOP
399 };
400
401 ~CPjAudioEngine();
402
403 static CPjAudioEngine *NewL(struct aps_stream *parent_strm,
404 PjAudioCallback rec_cb,
405 PjAudioCallback play_cb,
406 void *user_data,
407 const CPjAudioSetting &setting);
408
409 TInt StartL();
410 void Stop();
411
412 TInt ActivateSpeaker(TBool active);
413
414 TInt SetVolume(TInt vol) { return iSession.SetVolume(vol); }
415 TInt GetVolume() { return iSession.Volume(); }
416 TInt GetMaxVolume() { return iSession.MaxVolume(); }
417
418 TInt SetGain(TInt gain) { return iSession.SetGain(gain); }
419 TInt GetGain() { return iSession.Gain(); }
420 TInt GetMaxGain() { return iSession.MaxGain(); }
421
422private:
423 CPjAudioEngine(struct aps_stream *parent_strm,
424 PjAudioCallback rec_cb,
425 PjAudioCallback play_cb,
426 void *user_data,
427 const CPjAudioSetting &setting);
428 void ConstructL();
429
430 TInt InitPlayL();
431 TInt InitRecL();
432 TInt StartStreamL();
433 void Deinit();
434
435 // Inherited from MQueueHandlerObserver
436 virtual void InputStreamInitialized(const TInt aStatus);
437 virtual void OutputStreamInitialized(const TInt aStatus);
438 virtual void NotifyError(const TInt aError);
439
440 TBool session_opened;
441 State state_;
442 struct aps_stream *parentStrm_;
443 CPjAudioSetting setting_;
444
445 RAPSSession iSession;
446 TAPSInitSettings iPlaySettings;
447 TAPSInitSettings iRecSettings;
448
449 RMsgQueue<TAPSCommBuffer> iReadQ;
450 RMsgQueue<TAPSCommBuffer> iReadCommQ;
451 TBool readq_opened;
452 RMsgQueue<TAPSCommBuffer> iWriteQ;
453 RMsgQueue<TAPSCommBuffer> iWriteCommQ;
454 TBool writeq_opened;
455
456 CQueueHandler *iPlayCommHandler;
457 CQueueHandler *iRecCommHandler;
458 CQueueHandler *iRecHandler;
459};
460
461
462CPjAudioEngine* CPjAudioEngine::NewL(struct aps_stream *parent_strm,
463 PjAudioCallback rec_cb,
464 PjAudioCallback play_cb,
465 void *user_data,
466 const CPjAudioSetting &setting)
467{
468 CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm,
469 rec_cb, play_cb,
470 user_data,
471 setting);
472 CleanupStack::PushL(self);
473 self->ConstructL();
474 CleanupStack::Pop(self);
475 return self;
476}
477
478CPjAudioEngine::CPjAudioEngine(struct aps_stream *parent_strm,
479 PjAudioCallback rec_cb,
480 PjAudioCallback play_cb,
481 void *user_data,
482 const CPjAudioSetting &setting)
483 : MQueueHandlerObserver(rec_cb, play_cb, user_data),
484 session_opened(EFalse),
485 state_(STATE_NULL),
486 parentStrm_(parent_strm),
487 setting_(setting),
488 readq_opened(EFalse),
489 writeq_opened(EFalse),
490 iPlayCommHandler(0),
491 iRecCommHandler(0),
492 iRecHandler(0)
493{
494}
495
496CPjAudioEngine::~CPjAudioEngine()
497{
498 Deinit();
499
500 TRACE_((THIS_FILE, "Sound device destroyed"));
501}
502
503TInt CPjAudioEngine::InitPlayL()
504{
505 TInt err = iSession.InitializePlayer(iPlaySettings);
506 if (err != KErrNone) {
507 Deinit();
508 snd_perror("Failed to initialize player", err);
509 return err;
510 }
511
512 // Open message queues for the output stream
513 TBuf<128> buf2 = iPlaySettings.iGlobal;
514 buf2.Append(_L("PlayQueue"));
515 TBuf<128> buf3 = iPlaySettings.iGlobal;
516 buf3.Append(_L("PlayCommQueue"));
517
518 while (iWriteQ.OpenGlobal(buf2))
519 User::After(10);
520 while (iWriteCommQ.OpenGlobal(buf3))
521 User::After(10);
522
523 writeq_opened = ETrue;
524
525 // Construct message queue handler
526 iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ,
527 CQueueHandler::EPlayCommQueue);
528
529 // Start observing APS callbacks on output stream message queue
530 iPlayCommHandler->Start();
531
532 return 0;
533}
534
535TInt CPjAudioEngine::InitRecL()
536{
537 // Initialize input stream device
538 TInt err = iSession.InitializeRecorder(iRecSettings);
539 if (err != KErrNone && err != KErrAlreadyExists) {
540 Deinit();
541 snd_perror("Failed to initialize recorder", err);
542 return err;
543 }
544
545 TBuf<128> buf1 = iRecSettings.iGlobal;
546 buf1.Append(_L("RecordQueue"));
547 TBuf<128> buf4 = iRecSettings.iGlobal;
548 buf4.Append(_L("RecordCommQueue"));
549
550 // Must wait for APS thread to finish creating message queues
551 // before we can open and use them.
552 while (iReadQ.OpenGlobal(buf1))
553 User::After(10);
554 while (iReadCommQ.OpenGlobal(buf4))
555 User::After(10);
556
557 readq_opened = ETrue;
558
559 // Construct message queue handlers
560 iRecHandler = CQueueHandler::NewL(this, &iReadQ, NULL,
561 CQueueHandler::ERecordQueue);
562 iRecCommHandler = CQueueHandler::NewL(this, &iReadCommQ, NULL,
563 CQueueHandler::ERecordCommQueue);
564
565 // Start observing APS callbacks from on input stream message queue
566 iRecHandler->Start();
567 iRecCommHandler->Start();
568
569 return 0;
570}
571
572TInt CPjAudioEngine::StartL()
573{
574 if (state_ == STATE_READY)
575 return StartStreamL();
576
577 PJ_ASSERT_RETURN(state_ == STATE_NULL, PJMEDIA_EAUD_INVOP);
578
579 if (!session_opened) {
580 TInt err = iSession.Connect();
581 if (err != KErrNone)
582 return err;
583 session_opened = ETrue;
584 }
585
586 // Even if only capturer are opened, playback thread of APS Server need
587 // to be run(?). Since some messages will be delivered via play comm queue.
588 state_ = STATE_INITIALIZING;
589
590 return InitPlayL();
591}
592
593void CPjAudioEngine::Stop()
594{
595 if (state_ == STATE_STREAMING) {
596 iSession.Stop();
597 state_ = STATE_READY;
598 TRACE_((THIS_FILE, "Sound device stopped"));
599 } else if (state_ == STATE_INITIALIZING) {
600 // Initialization is on progress, so let's set the state to
601 // STATE_PENDING_STOP to prevent it starting the stream.
602 state_ = STATE_PENDING_STOP;
603
604 // Then wait until initialization done.
605 while (state_ != STATE_READY && state_ != STATE_NULL)
606 pj_symbianos_poll(-1, 100);
607 }
608}
609
610void CPjAudioEngine::ConstructL()
611{
612 // Recorder settings
613 iRecSettings.iFourCC = setting_.fourcc;
614 iRecSettings.iGlobal = APP_UID;
615 iRecSettings.iPriority = TMdaPriority(100);
616 iRecSettings.iPreference = TMdaPriorityPreference(0x05210001);
617 iRecSettings.iSettings.iChannels = EMMFMono;
618 iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
619
620 // Player settings
621 iPlaySettings.iFourCC = setting_.fourcc;
622 iPlaySettings.iGlobal = APP_UID;
623 iPlaySettings.iPriority = TMdaPriority(100);
624 iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001);
625 iPlaySettings.iSettings.iChannels = EMMFMono;
626 iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
627 iPlaySettings.iSettings.iVolume = 0;
628
629 User::LeaveIfError(iSession.Connect());
630 session_opened = ETrue;
631}
632
633TInt CPjAudioEngine::StartStreamL()
634{
635 pj_assert(state_==STATE_READY || state_==STATE_INITIALIZING);
636
637 iSession.SetCng(setting_.cng);
638 iSession.SetVadMode(setting_.vad);
639 iSession.SetPlc(setting_.plc);
640 iSession.SetEncoderMode(setting_.mode);
641 iSession.SetDecoderMode(setting_.mode);
642 iSession.ActivateLoudspeaker(setting_.loudspk);
643
644 // Not only capture
645 if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) {
646 iSession.Write();
647 TRACE_((THIS_FILE, "Player started"));
648 }
649
650 // Not only playback
651 if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
652 iSession.Read();
653 TRACE_((THIS_FILE, "Recorder started"));
654 }
655
656 state_ = STATE_STREAMING;
657
658 return 0;
659}
660
661void CPjAudioEngine::Deinit()
662{
663 Stop();
664
665 delete iRecHandler;
666 delete iPlayCommHandler;
667 delete iRecCommHandler;
668
669 if (session_opened) {
670 enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
671
672 // On some devices, immediate closing after stopping may cause
673 // APS server panic KERN-EXEC 0, so let's wait for sometime before
674 // closing the client session.
675 snd_wait(APS_CLOSE_WAIT_TIME);
676
677 iSession.Close();
678 session_opened = EFalse;
679 }
680
681 if (readq_opened) {
682 iReadQ.Close();
683 iReadCommQ.Close();
684 readq_opened = EFalse;
685 }
686
687 if (writeq_opened) {
688 iWriteQ.Close();
689 iWriteCommQ.Close();
690 writeq_opened = EFalse;
691 }
692
693 state_ = STATE_NULL;
694}
695
696void CPjAudioEngine::InputStreamInitialized(const TInt aStatus)
697{
698 TRACE_((THIS_FILE, "Recorder initialized, err=%d", aStatus));
699
700 if (aStatus == KErrNone) {
701 // Don't start the stream since Stop() has been requested.
702 if (state_ != STATE_PENDING_STOP) {
703 StartStreamL();
704 } else {
705 state_ = STATE_READY;
706 }
707 } else {
708 Deinit();
709 }
710}
711
712void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus)
713{
714 TRACE_((THIS_FILE, "Player initialized, err=%d", aStatus));
715
716 if (aStatus == KErrNone) {
717 if (parentStrm_->param.dir == PJMEDIA_DIR_PLAYBACK) {
718 // Don't start the stream since Stop() has been requested.
719 if (state_ != STATE_PENDING_STOP) {
720 StartStreamL();
721 } else {
722 state_ = STATE_READY;
723 }
724 } else
725 InitRecL();
726 } else {
727 Deinit();
728 }
729}
730
731void CPjAudioEngine::NotifyError(const TInt aError)
732{
733 Deinit();
734 snd_perror("Error from CQueueHandler", aError);
735}
736
737TInt CPjAudioEngine::ActivateSpeaker(TBool active)
738{
739 if (state_ == STATE_READY || state_ == STATE_STREAMING) {
740 iSession.ActivateLoudspeaker(active);
741 TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off")));
742 return KErrNone;
743 }
744 return KErrNotReady;
745}
746
747/****************************************************************************
748 * Internal APS callbacks for PCM format
749 */
750
751static void RecCbPcm(TAPSCommBuffer &buf, void *user_data)
752{
753 struct aps_stream *strm = (struct aps_stream*) user_data;
754
755 /* Buffer has to contain normal speech. */
756 pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
757
758 /* Detect the recorder G.711 frame size, player frame size will follow
759 * this recorder frame size.
760 */
761 if (aps_g711_frame_len == 0) {
762 aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
763 TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
764 aps_g711_frame_len));
765 }
766
767 /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf.
768 * Whenever rec_buf is full, call parent stream callback.
769 */
770 unsigned samples_processed = 0;
771
772 while (samples_processed < aps_g711_frame_len) {
773 unsigned samples_to_process;
774 unsigned samples_req;
775
776 samples_to_process = aps_g711_frame_len - samples_processed;
777 samples_req = (strm->param.samples_per_frame /
778 strm->param.channel_count /
779 strm->resample_factor) -
780 strm->rec_buf_len;
781 if (samples_to_process > samples_req)
782 samples_to_process = samples_req;
783
784 pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
785 buf.iBuffer.Ptr() + 2 + samples_processed,
786 samples_to_process);
787
788 strm->rec_buf_len += samples_to_process;
789 samples_processed += samples_to_process;
790
791 /* Buffer is full, time to call parent callback */
792 if (strm->rec_buf_len == strm->param.samples_per_frame /
793 strm->param.channel_count /
794 strm->resample_factor)
795 {
796 pjmedia_frame f;
797
798 /* Need to resample clock rate? */
799 if (strm->rec_resample) {
800 unsigned resampled = 0;
801
802 while (resampled < strm->rec_buf_len) {
803 pjmedia_resample_run(strm->rec_resample,
804 &strm->rec_buf[resampled],
805 strm->pcm_buf +
806 resampled * strm->resample_factor);
807 resampled += 80;
808 }
809 f.buf = strm->pcm_buf;
810 } else {
811 f.buf = strm->rec_buf;
812 }
813
814 /* Need to convert channel count? */
815 if (strm->param.channel_count != 1) {
816 pjmedia_convert_channel_1ton((pj_int16_t*)f.buf,
817 (pj_int16_t*)f.buf,
818 strm->param.channel_count,
819 strm->param.samples_per_frame /
820 strm->param.channel_count,
821 0);
822 }
823
824 /* Call parent callback */
825 f.type = PJMEDIA_FRAME_TYPE_AUDIO;
826 f.size = strm->param.samples_per_frame << 1;
827 strm->rec_cb(strm->user_data, &f);
828 strm->rec_buf_len = 0;
829 }
830 }
831}
832
833static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data)
834{
835 struct aps_stream *strm = (struct aps_stream*) user_data;
836 unsigned g711_frame_len = aps_g711_frame_len;
837
838 /* Init buffer attributes and header. */
839 buf.iCommand = CQueueHandler::EAPSPlayData;
840 buf.iStatus = 0;
841 buf.iBuffer.Zero();
842 buf.iBuffer.Append(1);
843 buf.iBuffer.Append(0);
844
845 /* Assume frame size is 10ms if frame size hasn't been known. */
846 if (g711_frame_len == 0)
847 g711_frame_len = 80;
848
849 /* Call parent stream callback to get PCM samples to play,
850 * encode the PCM samples into G.711 and put it into APS buffer.
851 */
852 unsigned samples_processed = 0;
853
854 while (samples_processed < g711_frame_len) {
855 /* Need more samples to play, time to call parent callback */
856 if (strm->play_buf_len == 0) {
857 pjmedia_frame f;
858 unsigned samples_got;
859
860 f.size = strm->param.samples_per_frame << 1;
861 if (strm->play_resample || strm->param.channel_count != 1)
862 f.buf = strm->pcm_buf;
863 else
864 f.buf = strm->play_buf;
865
866 /* Call parent callback */
867 strm->play_cb(strm->user_data, &f);
868 if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) {
869 pjmedia_zero_samples((pj_int16_t*)f.buf,
870 strm->param.samples_per_frame);
871 }
872
873 samples_got = strm->param.samples_per_frame /
874 strm->param.channel_count /
875 strm->resample_factor;
876
877 /* Need to convert channel count? */
878 if (strm->param.channel_count != 1) {
879 pjmedia_convert_channel_nto1((pj_int16_t*)f.buf,
880 (pj_int16_t*)f.buf,
881 strm->param.channel_count,
882 strm->param.samples_per_frame,
883 PJ_FALSE,
884 0);
885 }
886
887 /* Need to resample clock rate? */
888 if (strm->play_resample) {
889 unsigned resampled = 0;
890
891 while (resampled < samples_got)
892 {
893 pjmedia_resample_run(strm->play_resample,
894 strm->pcm_buf +
895 resampled * strm->resample_factor,
896 &strm->play_buf[resampled]);
897 resampled += 80;
898 }
899 }
900
901 strm->play_buf_len = samples_got;
902 strm->play_buf_start = 0;
903 }
904
905 unsigned tmp;
906
907 tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed);
908 pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start],
909 &strm->play_buf[strm->play_buf_start],
910 tmp);
911 buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp);
912 samples_processed += tmp;
913 strm->play_buf_len -= tmp;
914 strm->play_buf_start += tmp;
915 }
916}
917
918/****************************************************************************
919 * Internal APS callbacks for non-PCM format
920 */
921
922static void RecCb(TAPSCommBuffer &buf, void *user_data)
923{
924 struct aps_stream *strm = (struct aps_stream*) user_data;
925 pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf;
926
927 switch(strm->param.ext_fmt.id) {
928 case PJMEDIA_FORMAT_AMR:
929 {
930 const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1;
931 unsigned len = buf.iBuffer.Length() - 1;
932
933 pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160);
934 if (frame->samples_cnt == strm->param.samples_per_frame) {
935 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
936 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
937 frame->samples_cnt = 0;
938 frame->subframe_cnt = 0;
939 }
940 }
941 break;
942
943 case PJMEDIA_FORMAT_G729:
944 {
945 /* Check if we got a normal or SID frame. */
946 if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) {
947 enum { NORMAL_LEN = 22, SID_LEN = 8 };
948 TBitStream *bitstream = (TBitStream*)strm->strm_data;
949 unsigned src_len = buf.iBuffer.Length()- 2;
950
951 pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN);
952
953 const TDesC8& p = bitstream->CompressG729Frame(
954 buf.iBuffer.Right(src_len),
955 src_len == SID_LEN);
956
957 pjmedia_frame_ext_append_subframe(frame, p.Ptr(),
958 p.Length() << 3, 80);
959 } else { /* We got null frame. */
960 pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80);
961 }
962
963 if (frame->samples_cnt == strm->param.samples_per_frame) {
964 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
965 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
966 frame->samples_cnt = 0;
967 frame->subframe_cnt = 0;
968 }
969 }
970 break;
971
972 case PJMEDIA_FORMAT_ILBC:
973 {
974 unsigned samples_got;
975
976 samples_got =
977 strm->param.ext_fmt.det.aud.avg_bps == 15200? 160 : 240;
978
979 /* Check if we got a normal frame. */
980 if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) {
981 const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2;
982 unsigned len = buf.iBuffer.Length() - 2;
983
984 pjmedia_frame_ext_append_subframe(frame, p, len << 3,
985 samples_got);
986 } else { /* We got null frame. */
987 pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got);
988 }
989
990 if (frame->samples_cnt == strm->param.samples_per_frame) {
991 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
992 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
993 frame->samples_cnt = 0;
994 frame->subframe_cnt = 0;
995 }
996 }
997 break;
998
999 case PJMEDIA_FORMAT_PCMU:
1000 case PJMEDIA_FORMAT_PCMA:
1001 {
1002 unsigned samples_processed = 0;
1003
1004 /* Make sure it is normal frame. */
1005 pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0);
1006
1007 /* Detect the recorder G.711 frame size, player frame size will
1008 * follow this recorder frame size.
1009 */
1010 if (aps_g711_frame_len == 0) {
1011 aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160;
1012 TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples",
1013 aps_g711_frame_len));
1014 }
1015
1016 /* Convert APS buffer format into pjmedia_frame_ext. Whenever
1017 * samples count in the frame is equal to stream's samples per
1018 * frame, call parent stream callback.
1019 */
1020 while (samples_processed < aps_g711_frame_len) {
1021 unsigned tmp;
1022 const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() +
1023 2 + samples_processed;
1024
1025 tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt,
1026 aps_g711_frame_len - samples_processed);
1027
1028 pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp);
1029 samples_processed += tmp;
1030
1031 if (frame->samples_cnt == strm->param.samples_per_frame) {
1032 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1033 strm->rec_cb(strm->user_data, (pjmedia_frame*)frame);
1034 frame->samples_cnt = 0;
1035 frame->subframe_cnt = 0;
1036 }
1037 }
1038 }
1039 break;
1040
1041 default:
1042 break;
1043 }
1044}
1045
1046static void PlayCb(TAPSCommBuffer &buf, void *user_data)
1047{
1048 struct aps_stream *strm = (struct aps_stream*) user_data;
1049 pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf;
1050
1051 /* Init buffer attributes and header. */
1052 buf.iCommand = CQueueHandler::EAPSPlayData;
1053 buf.iStatus = 0;
1054 buf.iBuffer.Zero();
1055
1056 switch(strm->param.ext_fmt.id) {
1057 case PJMEDIA_FORMAT_AMR:
1058 {
1059 if (frame->samples_cnt == 0) {
1060 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1061 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1062 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1063 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1064 }
1065
1066 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1067 pjmedia_frame_ext_subframe *sf;
1068 unsigned samples_cnt;
1069
1070 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1071 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1072
1073 if (sf->data && sf->bitlen) {
1074 /* AMR header for APS is one byte, the format (may be!):
1075 * 0xxxxy00, where xxxx:frame type, y:not sure.
1076 */
1077 unsigned len = (sf->bitlen+7)>>3;
1078 enum {SID_FT = 8 };
1079 pj_uint8_t amr_header = 4, ft = SID_FT;
1080
1081 if (len >= pjmedia_codec_amrnb_framelen[0])
1082 ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len);
1083
1084 amr_header |= ft << 3;
1085 buf.iBuffer.Append(amr_header);
1086
1087 buf.iBuffer.Append((TUint8*)sf->data, len);
1088 } else {
1089 enum {NO_DATA_FT = 15 };
1090 pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
1091
1092 buf.iBuffer.Append(amr_header);
1093 }
1094
1095 pjmedia_frame_ext_pop_subframes(frame, 1);
1096
1097 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1098 enum {NO_DATA_FT = 15 };
1099 pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3);
1100
1101 buf.iBuffer.Append(amr_header);
1102
1103 frame->samples_cnt = 0;
1104 frame->subframe_cnt = 0;
1105 }
1106 }
1107 break;
1108
1109 case PJMEDIA_FORMAT_G729:
1110 {
1111 if (frame->samples_cnt == 0) {
1112 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1113 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1114 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1115 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1116 }
1117
1118 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1119 pjmedia_frame_ext_subframe *sf;
1120 unsigned samples_cnt;
1121
1122 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1123 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1124
1125 if (sf->data && sf->bitlen) {
1126 enum { NORMAL_LEN = 10, SID_LEN = 2 };
1127 pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN);
1128 TBitStream *bitstream = (TBitStream*)strm->strm_data;
1129 const TPtrC8 src(sf->data, sf->bitlen>>3);
1130 const TDesC8 &dst = bitstream->ExpandG729Frame(src,
1131 sid_frame);
1132 if (sid_frame) {
1133 buf.iBuffer.Append(2);
1134 buf.iBuffer.Append(0);
1135 } else {
1136 buf.iBuffer.Append(1);
1137 buf.iBuffer.Append(0);
1138 }
1139 buf.iBuffer.Append(dst);
1140 } else {
1141 buf.iBuffer.Append(2);
1142 buf.iBuffer.Append(0);
1143 buf.iBuffer.AppendFill(0, 22);
1144 }
1145
1146 pjmedia_frame_ext_pop_subframes(frame, 1);
1147
1148 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1149 buf.iBuffer.Append(2);
1150 buf.iBuffer.Append(0);
1151 buf.iBuffer.AppendFill(0, 22);
1152
1153 frame->samples_cnt = 0;
1154 frame->subframe_cnt = 0;
1155 }
1156 }
1157 break;
1158
1159 case PJMEDIA_FORMAT_ILBC:
1160 {
1161 if (frame->samples_cnt == 0) {
1162 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1163 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1164 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1165 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1166 }
1167
1168 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1169 pjmedia_frame_ext_subframe *sf;
1170 unsigned samples_cnt;
1171
1172 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1173 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1174
1175 pj_assert((strm->param.ext_fmt.det.aud.avg_bps == 15200 &&
1176 samples_cnt == 160) ||
1177 (strm->param.ext_fmt.det.aud.avg_bps != 15200 &&
1178 samples_cnt == 240));
1179
1180 if (sf->data && sf->bitlen) {
1181 buf.iBuffer.Append(1);
1182 buf.iBuffer.Append(0);
1183 buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
1184 } else {
1185 buf.iBuffer.Append(0);
1186 buf.iBuffer.Append(0);
1187 }
1188
1189 pjmedia_frame_ext_pop_subframes(frame, 1);
1190
1191 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1192 buf.iBuffer.Append(0);
1193 buf.iBuffer.Append(0);
1194
1195 frame->samples_cnt = 0;
1196 frame->subframe_cnt = 0;
1197 }
1198 }
1199 break;
1200
1201 case PJMEDIA_FORMAT_PCMU:
1202 case PJMEDIA_FORMAT_PCMA:
1203 {
1204 unsigned samples_ready = 0;
1205 unsigned samples_req = aps_g711_frame_len;
1206
1207 /* Assume frame size is 10ms if frame size hasn't been known. */
1208 if (samples_req == 0)
1209 samples_req = 80;
1210
1211 buf.iBuffer.Append(1);
1212 buf.iBuffer.Append(0);
1213
1214 /* Call parent stream callback to get samples to play. */
1215 while (samples_ready < samples_req) {
1216 if (frame->samples_cnt == 0) {
1217 frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED;
1218 strm->play_cb(strm->user_data, (pjmedia_frame*)frame);
1219 pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED ||
1220 frame->base.type==PJMEDIA_FRAME_TYPE_NONE);
1221 }
1222
1223 if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) {
1224 pjmedia_frame_ext_subframe *sf;
1225 unsigned samples_cnt;
1226
1227 sf = pjmedia_frame_ext_get_subframe(frame, 0);
1228 samples_cnt = frame->samples_cnt / frame->subframe_cnt;
1229 if (sf->data && sf->bitlen) {
1230 buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3);
1231 } else {
1232 pj_uint8_t silc;
1233 silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
1234 pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
1235 buf.iBuffer.AppendFill(silc, samples_cnt);
1236 }
1237 samples_ready += samples_cnt;
1238
1239 pjmedia_frame_ext_pop_subframes(frame, 1);
1240
1241 } else { /* PJMEDIA_FRAME_TYPE_NONE */
1242 pj_uint8_t silc;
1243
1244 silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)?
1245 pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0);
1246 buf.iBuffer.AppendFill(silc, samples_req - samples_ready);
1247
1248 samples_ready = samples_req;
1249 frame->samples_cnt = 0;
1250 frame->subframe_cnt = 0;
1251 }
1252 }
1253 }
1254 break;
1255
1256 default:
1257 break;
1258 }
1259}
1260
1261
1262/****************************************************************************
1263 * Factory operations
1264 */
1265
1266/*
1267 * C compatible declaration of APS factory.
1268 */
1269PJ_BEGIN_DECL
1270PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf);
1271PJ_END_DECL
1272
1273/*
1274 * Init APS audio driver.
1275 */
1276PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf)
1277{
1278 struct aps_factory *f;
1279 pj_pool_t *pool;
1280
1281 pool = pj_pool_create(pf, "APS", 1000, 1000, NULL);
1282 f = PJ_POOL_ZALLOC_T(pool, struct aps_factory);
1283 f->pf = pf;
1284 f->pool = pool;
1285 f->base.op = &factory_op;
1286
1287 return &f->base;
1288}
1289
1290/* API: init factory */
1291static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
1292{
1293 struct aps_factory *af = (struct aps_factory*)f;
1294
1295 pj_ansi_strcpy(af->dev_info.name, "S60 APS");
1296 af->dev_info.default_samples_per_sec = 8000;
1297 af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT |
1298 //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
1299 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING |
1300 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE |
1301 PJMEDIA_AUD_DEV_CAP_VAD |
1302 PJMEDIA_AUD_DEV_CAP_CNG;
1303 af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE |
1304 PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
1305 af->dev_info.input_count = 1;
1306 af->dev_info.output_count = 1;
1307
1308 /* Enumerate codecs by trying to initialize each codec and examining
1309 * the error code. Consider the following:
1310 * - not possible to reinitialize the same APS session with
1311 * different settings,
1312 * - closing APS session and trying to immediately reconnect may fail,
1313 * clients should wait ~5s before attempting to reconnect.
1314 */
1315
1316 unsigned i, fmt_cnt = 0;
1317 pj_bool_t g711_supported = PJ_FALSE;
1318
1319 /* Do not change the order! */
1320 TFourCC fourcc[] = {
1321 TFourCC(KMCPFourCCIdAMRNB),
1322 TFourCC(KMCPFourCCIdG711),
1323 TFourCC(KMCPFourCCIdG729),
1324 TFourCC(KMCPFourCCIdILBC)
1325 };
1326
1327 for (i = 0; i < PJ_ARRAY_SIZE(fourcc); ++i) {
1328 pj_bool_t supported = PJ_FALSE;
1329 unsigned retry_cnt = 0;
1330 enum { MAX_RETRY = 3 };
1331
1332#if (PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC == 0)
1333 /* Codec detection is disabled */
1334 supported = PJ_TRUE;
1335#elif (PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC == 1)
1336 /* Minimal codec detection, AMR-NB and G.711 only */
1337 if (i > 1) {
1338 /* If G.711 has been checked, skip G.729 and iLBC checks */
1339 retry_cnt = MAX_RETRY;
1340 supported = g711_supported;
1341 }
1342#endif
1343
1344 while (!supported && ++retry_cnt <= MAX_RETRY) {
1345 RAPSSession iSession;
1346 TAPSInitSettings iPlaySettings;
1347 TAPSInitSettings iRecSettings;
1348 TInt err;
1349
1350 // Recorder settings
1351 iRecSettings.iGlobal = APP_UID;
1352 iRecSettings.iPriority = TMdaPriority(100);
1353 iRecSettings.iPreference = TMdaPriorityPreference(0x05210001);
1354 iRecSettings.iSettings.iChannels = EMMFMono;
1355 iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
1356
1357 // Player settings
1358 iPlaySettings.iGlobal = APP_UID;
1359 iPlaySettings.iPriority = TMdaPriority(100);
1360 iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001);
1361 iPlaySettings.iSettings.iChannels = EMMFMono;
1362 iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz;
1363
1364 iRecSettings.iFourCC = iPlaySettings.iFourCC = fourcc[i];
1365
1366 err = iSession.Connect();
1367 if (err == KErrNone)
1368 err = iSession.InitializePlayer(iPlaySettings);
1369 if (err == KErrNone)
1370 err = iSession.InitializeRecorder(iRecSettings);
1371
1372 // On some devices, immediate closing causes APS Server panic,
1373 // e.g: N95, so let's just wait for some time before closing.
1374 enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
1375 snd_wait(APS_CLOSE_WAIT_TIME);
1376
1377 iSession.Close();
1378
1379 if (err == KErrNone) {
1380 /* All fine, stop retyring */
1381 supported = PJ_TRUE;
1382 } else if (err == KErrAlreadyExists && retry_cnt < MAX_RETRY) {
1383 /* Seems that the previous session is still arround,
1384 * let's wait before retrying.
1385 */
1386 enum { RETRY_WAIT_TIME = 3000 }; /* in msecs */
1387 snd_wait(RETRY_WAIT_TIME);
1388 } else {
1389 /* Seems that this format is not supported */
1390 retry_cnt = MAX_RETRY;
1391 }
1392 }
1393
1394 if (supported) {
1395 pjmedia_format ext_fmt;
1396
1397 switch(i) {
1398 case 0: /* AMRNB */
1399 pjmedia_format_init_audio(&ext_fmt, PJMEDIA_FORMAT_AMR,
1400 8000, 1, 16, 20, 7400, 12200);
1401 af->dev_info.ext_fmt[fmt_cnt] = ext_fmt;
1402 //af->dev_info.ext_fmt[fmt_cnt].vad = PJ_TRUE;
1403 ++fmt_cnt;
1404 break;
1405 case 1: /* G.711 */
1406 pjmedia_format_init_audio(&ext_fmt, PJMEDIA_FORMAT_PCMU,
1407 8000, 1, 16, 20, 64000, 64000);
1408 af->dev_info.ext_fmt[fmt_cnt] = ext_fmt;
1409 //af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE;
1410 ++fmt_cnt;
1411 pjmedia_format_init_audio(&ext_fmt, PJMEDIA_FORMAT_PCMA,
1412 8000, 1, 16, 20, 64000, 64000);
1413 af->dev_info.ext_fmt[fmt_cnt] = ext_fmt;
1414 //af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE;
1415 ++fmt_cnt;
1416 g711_supported = PJ_TRUE;
1417 break;
1418 case 2: /* G.729 */
1419 pjmedia_format_init_audio(&ext_fmt, PJMEDIA_FORMAT_G729,
1420 8000, 1, 16, 20, 8000, 8000);
1421 af->dev_info.ext_fmt[fmt_cnt] = ext_fmt;
1422 //af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE;
1423 ++fmt_cnt;
1424 break;
1425 case 3: /* iLBC */
1426 pjmedia_format_init_audio(&ext_fmt, PJMEDIA_FORMAT_ILBC,
1427 8000, 1, 16, 30, 13333, 15200);
1428 af->dev_info.ext_fmt[fmt_cnt] = ext_fmt;
1429 //af->dev_info.ext_fmt[fmt_cnt].vad = PJ_TRUE;
1430 ++fmt_cnt;
1431 break;
1432 }
1433 }
1434 }
1435
1436 af->dev_info.ext_fmt_cnt = fmt_cnt;
1437
1438 PJ_LOG(4, (THIS_FILE, "APS initialized"));
1439
1440 return PJ_SUCCESS;
1441}
1442
1443/* API: destroy factory */
1444static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
1445{
1446 struct aps_factory *af = (struct aps_factory*)f;
1447 pj_pool_t *pool = af->pool;
1448
1449 af->pool = NULL;
1450 pj_pool_release(pool);
1451
1452 PJ_LOG(4, (THIS_FILE, "APS destroyed"));
1453
1454 return PJ_SUCCESS;
1455}
1456
1457/* API: refresh the device list */
1458static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
1459{
1460 PJ_UNUSED_ARG(f);
1461 return PJ_ENOTSUP;
1462}
1463
1464/* API: get number of devices */
1465static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
1466{
1467 PJ_UNUSED_ARG(f);
1468 return 1;
1469}
1470
1471/* API: get device info */
1472static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
1473 unsigned index,
1474 pjmedia_aud_dev_info *info)
1475{
1476 struct aps_factory *af = (struct aps_factory*)f;
1477
1478 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
1479
1480 pj_memcpy(info, &af->dev_info, sizeof(*info));
1481
1482 return PJ_SUCCESS;
1483}
1484
1485/* API: create default device parameter */
1486static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
1487 unsigned index,
1488 pjmedia_aud_param *param)
1489{
1490 struct aps_factory *af = (struct aps_factory*)f;
1491
1492 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
1493
1494 pj_bzero(param, sizeof(*param));
1495 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
1496 param->rec_id = index;
1497 param->play_id = index;
1498 param->clock_rate = af->dev_info.default_samples_per_sec;
1499 param->channel_count = 1;
1500 param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
1501 param->bits_per_sample = BITS_PER_SAMPLE;
1502 param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE;
1503 param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE;
1504
1505 return PJ_SUCCESS;
1506}
1507
1508
1509/* API: create stream */
1510static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
1511 const pjmedia_aud_param *param,
1512 pjmedia_aud_rec_cb rec_cb,
1513 pjmedia_aud_play_cb play_cb,
1514 void *user_data,
1515 pjmedia_aud_stream **p_aud_strm)
1516{
1517 struct aps_factory *af = (struct aps_factory*)f;
1518 pj_pool_t *pool;
1519 struct aps_stream *strm;
1520
1521 CPjAudioSetting aps_setting;
1522 PjAudioCallback aps_rec_cb;
1523 PjAudioCallback aps_play_cb;
1524
1525 /* Can only support 16bits per sample */
1526 PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
1527
1528 /* Supported clock rates:
1529 * - for non-PCM format: 8kHz
1530 * - for PCM format: 8kHz and 16kHz
1531 */
1532 PJ_ASSERT_RETURN(param->clock_rate == 8000 ||
1533 (param->clock_rate == 16000 &&
1534 param->ext_fmt.id == PJMEDIA_FORMAT_L16),
1535 PJ_EINVAL);
1536
1537 /* Supported channels number:
1538 * - for non-PCM format: mono
1539 * - for PCM format: mono and stereo
1540 */
1541 PJ_ASSERT_RETURN(param->channel_count == 1 ||
1542 (param->channel_count == 2 &&
1543 param->ext_fmt.id == PJMEDIA_FORMAT_L16),
1544 PJ_EINVAL);
1545
1546 /* Create and Initialize stream descriptor */
1547 pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL);
1548 PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
1549
1550 strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream);
1551 strm->pool = pool;
1552 strm->param = *param;
1553
1554 if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0)
1555 strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16;
1556
1557 /* Set audio engine fourcc. */
1558 switch(strm->param.ext_fmt.id) {
1559 case PJMEDIA_FORMAT_L16:
1560 case PJMEDIA_FORMAT_PCMU:
1561 case PJMEDIA_FORMAT_PCMA:
1562 aps_setting.fourcc = TFourCC(KMCPFourCCIdG711);
1563 break;
1564 case PJMEDIA_FORMAT_AMR:
1565 aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB);
1566 break;
1567 case PJMEDIA_FORMAT_G729:
1568 aps_setting.fourcc = TFourCC(KMCPFourCCIdG729);
1569 break;
1570 case PJMEDIA_FORMAT_ILBC:
1571 aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC);
1572 break;
1573 default:
1574 aps_setting.fourcc = 0;
1575 break;
1576 }
1577
1578 /* Set audio engine mode. */
1579 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR)
1580 {
1581 aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.det.aud.avg_bps;
1582 }
1583 else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
1584 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
1585 (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
1586 strm->param.ext_fmt.det.aud.avg_bps != 15200))
1587 {
1588 aps_setting.mode = EULawOr30ms;
1589 }
1590 else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
1591 (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC &&
1592 strm->param.ext_fmt.det.aud.avg_bps == 15200))
1593 {
1594 aps_setting.mode = EALawOr20ms;
1595 }
1596
1597 /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially
1598 * causes noise?).
1599 */
1600 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU ||
1601 strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA ||
1602 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 ||
1603 strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729)
1604 {
1605 aps_setting.vad = EFalse;
1606 } else {
1607 aps_setting.vad = (strm->param.flags & PJMEDIA_AUD_DEV_CAP_VAD) &&
1608 strm->param.vad_enabled;
1609 }
1610
1611 /* Set other audio engine attributes. */
1612 aps_setting.plc = (strm->param.flags & PJMEDIA_AUD_DEV_CAP_PLC) &&
1613 strm->param.plc_enabled;
1614 aps_setting.cng = aps_setting.vad;
1615 aps_setting.loudspk =
1616 strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER;
1617
1618 /* Set audio engine callbacks. */
1619 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) {
1620 aps_play_cb = &PlayCbPcm;
1621 aps_rec_cb = &RecCbPcm;
1622 } else {
1623 aps_play_cb = &PlayCb;
1624 aps_rec_cb = &RecCb;
1625 }
1626
1627 strm->rec_cb = rec_cb;
1628 strm->play_cb = play_cb;
1629 strm->user_data = user_data;
1630 strm->resample_factor = strm->param.clock_rate / 8000;
1631
1632 /* play_buf size is samples per frame scaled in to 8kHz mono. */
1633 strm->play_buf = (pj_int16_t*)pj_pool_zalloc(
1634 pool,
1635 (strm->param.samples_per_frame /
1636 strm->resample_factor /
1637 strm->param.channel_count) << 1);
1638 strm->play_buf_len = 0;
1639 strm->play_buf_start = 0;
1640
1641 /* rec_buf size is samples per frame scaled in to 8kHz mono. */
1642 strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(
1643 pool,
1644 (strm->param.samples_per_frame /
1645 strm->resample_factor /
1646 strm->param.channel_count) << 1);
1647 strm->rec_buf_len = 0;
1648
1649 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
1650 TBitStream *g729_bitstream = new TBitStream;
1651
1652 PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM);
1653 strm->strm_data = (void*)g729_bitstream;
1654 }
1655
1656 /* Init resampler when format is PCM and clock rate is not 8kHz */
1657 if (strm->param.clock_rate != 8000 &&
1658 strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16)
1659 {
1660 pj_status_t status;
1661
1662 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1663 /* Create resample for recorder */
1664 status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
1665 8000,
1666 strm->param.clock_rate,
1667 80,
1668 &strm->rec_resample);
1669 if (status != PJ_SUCCESS)
1670 return status;
1671 }
1672
1673 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1674 /* Create resample for player */
1675 status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1,
1676 strm->param.clock_rate,
1677 8000,
1678 80 * strm->resample_factor,
1679 &strm->play_resample);
1680 if (status != PJ_SUCCESS)
1681 return status;
1682 }
1683 }
1684
1685 /* Create PCM buffer, when the clock rate is not 8kHz or not mono */
1686 if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 &&
1687 (strm->resample_factor > 1 || strm->param.channel_count != 1))
1688 {
1689 strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool,
1690 strm->param.samples_per_frame << 1);
1691 }
1692
1693
1694 /* Create the audio engine. */
1695 TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm,
1696 aps_rec_cb, aps_play_cb,
1697 strm, aps_setting));
1698 if (err != KErrNone) {
1699 pj_pool_release(pool);
1700 return PJ_RETURN_OS_ERROR(err);
1701 }
1702
1703 /* Apply output volume setting if specified */
1704 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) {
1705 stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1706 &param->output_vol);
1707 }
1708
1709 /* Done */
1710 strm->base.op = &stream_op;
1711 *p_aud_strm = &strm->base;
1712
1713 return PJ_SUCCESS;
1714}
1715
1716/* API: Get stream info. */
1717static pj_status_t stream_get_param(pjmedia_aud_stream *s,
1718 pjmedia_aud_param *pi)
1719{
1720 struct aps_stream *strm = (struct aps_stream*)s;
1721
1722 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1723
1724 pj_memcpy(pi, &strm->param, sizeof(*pi));
1725
1726 /* Update the output volume setting */
1727 if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1728 &pi->output_vol) == PJ_SUCCESS)
1729 {
1730 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1731 }
1732
1733 return PJ_SUCCESS;
1734}
1735
1736/* API: get capability */
1737static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1738 pjmedia_aud_dev_cap cap,
1739 void *pval)
1740{
1741 struct aps_stream *strm = (struct aps_stream*)s;
1742 pj_status_t status = PJ_ENOTSUP;
1743
1744 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1745
1746 switch (cap) {
1747 case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
1748 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1749 *(pjmedia_aud_dev_route*)pval = strm->param.output_route;
1750 status = PJ_SUCCESS;
1751 }
1752 break;
1753
1754 /* There is a case that GetMaxGain() stucks, e.g: in N95. */
1755 /*
1756 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
1757 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1758 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1759
1760 TInt max_gain = strm->engine->GetMaxGain();
1761 TInt gain = strm->engine->GetGain();
1762
1763 if (max_gain > 0 && gain >= 0) {
1764 *(unsigned*)pval = gain * 100 / max_gain;
1765 status = PJ_SUCCESS;
1766 } else {
1767 status = PJMEDIA_EAUD_NOTREADY;
1768 }
1769 }
1770 break;
1771 */
1772
1773 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
1774 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1775 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1776
1777 TInt max_vol = strm->engine->GetMaxVolume();
1778 TInt vol = strm->engine->GetVolume();
1779
1780 if (max_vol > 0 && vol >= 0) {
1781 *(unsigned*)pval = vol * 100 / max_vol;
1782 status = PJ_SUCCESS;
1783 } else {
1784 status = PJMEDIA_EAUD_NOTREADY;
1785 }
1786 }
1787 break;
1788 default:
1789 break;
1790 }
1791
1792 return status;
1793}
1794
1795/* API: set capability */
1796static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1797 pjmedia_aud_dev_cap cap,
1798 const void *pval)
1799{
1800 struct aps_stream *strm = (struct aps_stream*)s;
1801 pj_status_t status = PJ_ENOTSUP;
1802
1803 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1804
1805 switch (cap) {
1806 case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE:
1807 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1808 pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval;
1809 TInt err;
1810
1811 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1812
1813 switch (r) {
1814 case PJMEDIA_AUD_DEV_ROUTE_DEFAULT:
1815 case PJMEDIA_AUD_DEV_ROUTE_EARPIECE:
1816 err = strm->engine->ActivateSpeaker(EFalse);
1817 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1818 break;
1819 case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER:
1820 err = strm->engine->ActivateSpeaker(ETrue);
1821 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1822 break;
1823 default:
1824 status = PJ_EINVAL;
1825 break;
1826 }
1827 if (status == PJ_SUCCESS)
1828 strm->param.output_route = r;
1829 }
1830 break;
1831
1832 /* There is a case that GetMaxGain() stucks, e.g: in N95. */
1833 /*
1834 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
1835 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1836 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1837
1838 TInt max_gain = strm->engine->GetMaxGain();
1839 if (max_gain > 0) {
1840 TInt gain, err;
1841
1842 gain = *(unsigned*)pval * max_gain / 100;
1843 err = strm->engine->SetGain(gain);
1844 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1845 } else {
1846 status = PJMEDIA_EAUD_NOTREADY;
1847 }
1848 if (status == PJ_SUCCESS)
1849 strm->param.input_vol = *(unsigned*)pval;
1850 }
1851 break;
1852 */
1853
1854 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
1855 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1856 PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL);
1857
1858 TInt max_vol = strm->engine->GetMaxVolume();
1859 if (max_vol > 0) {
1860 TInt vol, err;
1861
1862 vol = *(unsigned*)pval * max_vol / 100;
1863 err = strm->engine->SetVolume(vol);
1864 status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err);
1865 } else {
1866 status = PJMEDIA_EAUD_NOTREADY;
1867 }
1868 if (status == PJ_SUCCESS)
1869 strm->param.output_vol = *(unsigned*)pval;
1870 }
1871 break;
1872 default:
1873 break;
1874 }
1875
1876 return status;
1877}
1878
1879/* API: Start stream. */
1880static pj_status_t stream_start(pjmedia_aud_stream *strm)
1881{
1882 struct aps_stream *stream = (struct aps_stream*)strm;
1883
1884 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1885
1886 if (stream->engine) {
1887 TInt err = stream->engine->StartL();
1888 if (err != KErrNone)
1889 return PJ_RETURN_OS_ERROR(err);
1890 }
1891
1892 return PJ_SUCCESS;
1893}
1894
1895/* API: Stop stream. */
1896static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1897{
1898 struct aps_stream *stream = (struct aps_stream*)strm;
1899
1900 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1901
1902 if (stream->engine) {
1903 stream->engine->Stop();
1904 }
1905
1906 return PJ_SUCCESS;
1907}
1908
1909
1910/* API: Destroy stream. */
1911static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1912{
1913 struct aps_stream *stream = (struct aps_stream*)strm;
1914
1915 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1916
1917 stream_stop(strm);
1918
1919 delete stream->engine;
1920 stream->engine = NULL;
1921
1922 if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) {
1923 TBitStream *g729_bitstream = (TBitStream*)stream->strm_data;
1924 stream->strm_data = NULL;
1925 delete g729_bitstream;
1926 }
1927
1928 pj_pool_t *pool;
1929 pool = stream->pool;
1930 if (pool) {
1931 stream->pool = NULL;
1932 pj_pool_release(pool);
1933 }
1934
1935 return PJ_SUCCESS;
1936}
1937
1938#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
1939