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