blob: 645ed39453f04244e6c928a7a4bbbff27f60c9bb [file] [log] [blame]
Alexandre Lision67916dd2014-01-24 13:33:04 -05001/* $Id$ */
2/*
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 <pj/assert.h>
24#include <pj/log.h>
25#include <pj/math.h>
26#include <pj/os.h>
27#include <pj/string.h>
28
29#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA
30
31/*
32 * This file provides sound implementation for Symbian Audio Streaming
33 * device. Application using this sound abstraction must link with:
34 * - mediaclientaudiostream.lib, and
35 * - mediaclientaudioinputstream.lib
36 */
37#include <mda/common/audio.h>
38#include <mdaaudiooutputstream.h>
39#include <mdaaudioinputstream.h>
40
41
42#define THIS_FILE "symb_mda_dev.c"
43#define BITS_PER_SAMPLE 16
44#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8)
45
46
47#if 1
48# define TRACE_(st) PJ_LOG(3, st)
49#else
50# define TRACE_(st)
51#endif
52
53
54/* MDA factory */
55struct mda_factory
56{
57 pjmedia_aud_dev_factory base;
58 pj_pool_t *pool;
59 pj_pool_factory *pf;
60 pjmedia_aud_dev_info dev_info;
61};
62
63/* Forward declaration of internal engine. */
64class CPjAudioInputEngine;
65class CPjAudioOutputEngine;
66
67/* MDA stream. */
68struct mda_stream
69{
70 // Base
71 pjmedia_aud_stream base; /**< Base class. */
72
73 // Pool
74 pj_pool_t *pool; /**< Memory pool. */
75
76 // Common settings.
77 pjmedia_aud_param param; /**< Stream param. */
78
79 // Audio engine
80 CPjAudioInputEngine *in_engine; /**< Record engine. */
81 CPjAudioOutputEngine *out_engine; /**< Playback engine. */
82};
83
84
85/* Prototypes */
86static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
87static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f);
88static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f);
89static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f);
90static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
91 unsigned index,
92 pjmedia_aud_dev_info *info);
93static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
94 unsigned index,
95 pjmedia_aud_param *param);
96static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
97 const pjmedia_aud_param *param,
98 pjmedia_aud_rec_cb rec_cb,
99 pjmedia_aud_play_cb play_cb,
100 void *user_data,
101 pjmedia_aud_stream **p_aud_strm);
102
103static pj_status_t stream_get_param(pjmedia_aud_stream *strm,
104 pjmedia_aud_param *param);
105static pj_status_t stream_get_cap(pjmedia_aud_stream *strm,
106 pjmedia_aud_dev_cap cap,
107 void *value);
108static pj_status_t stream_set_cap(pjmedia_aud_stream *strm,
109 pjmedia_aud_dev_cap cap,
110 const void *value);
111static pj_status_t stream_start(pjmedia_aud_stream *strm);
112static pj_status_t stream_stop(pjmedia_aud_stream *strm);
113static pj_status_t stream_destroy(pjmedia_aud_stream *strm);
114
115
116/* Operations */
117static pjmedia_aud_dev_factory_op factory_op =
118{
119 &factory_init,
120 &factory_destroy,
121 &factory_get_dev_count,
122 &factory_get_dev_info,
123 &factory_default_param,
124 &factory_create_stream,
125 &factory_refresh
126};
127
128static pjmedia_aud_stream_op stream_op =
129{
130 &stream_get_param,
131 &stream_get_cap,
132 &stream_set_cap,
133 &stream_start,
134 &stream_stop,
135 &stream_destroy
136};
137
138
139/*
140 * Convert clock rate to Symbian's TMdaAudioDataSettings capability.
141 */
142static TInt get_clock_rate_cap(unsigned clock_rate)
143{
144 switch (clock_rate) {
145 case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz;
146 case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz;
147 case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz;
148 case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz;
149 case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz;
150 case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz;
151 case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz;
152 case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz;
153 case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz;
154 case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz;
155 case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz;
156 default:
157 return 0;
158 }
159}
160
161/*
162 * Convert number of channels into Symbian's TMdaAudioDataSettings capability.
163 */
164static TInt get_channel_cap(unsigned channel_count)
165{
166 switch (channel_count) {
167 case 1: return TMdaAudioDataSettings::EChannelsMono;
168 case 2: return TMdaAudioDataSettings::EChannelsStereo;
169 default:
170 return 0;
171 }
172}
173
174/*
175 * Utility: print sound device error
176 */
177static void snd_perror(const char *title, TInt rc)
178{
179 PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc));
180}
181
182//////////////////////////////////////////////////////////////////////////////
183//
184
185/*
186 * Implementation: Symbian Input Stream.
187 */
188class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback
189{
190public:
191 enum State
192 {
193 STATE_INACTIVE,
194 STATE_ACTIVE,
195 };
196
197 ~CPjAudioInputEngine();
198
199 static CPjAudioInputEngine *NewL(struct mda_stream *parent_strm,
200 pjmedia_aud_rec_cb rec_cb,
201 void *user_data);
202
203 static CPjAudioInputEngine *NewLC(struct mda_stream *parent_strm,
204 pjmedia_aud_rec_cb rec_cb,
205 void *user_data);
206
207 pj_status_t StartRecord();
208 void Stop();
209
210 pj_status_t SetGain(TInt gain) {
211 if (iInputStream_) {
212 iInputStream_->SetGain(gain);
213 return PJ_SUCCESS;
214 } else
215 return PJ_EINVALIDOP;
216 }
217
218 TInt GetGain() {
219 if (iInputStream_) {
220 return iInputStream_->Gain();
221 } else
222 return PJ_EINVALIDOP;
223 }
224
225 TInt GetMaxGain() {
226 if (iInputStream_) {
227 return iInputStream_->MaxGain();
228 } else
229 return PJ_EINVALIDOP;
230 }
231
232private:
233 State state_;
234 struct mda_stream *parentStrm_;
235 pjmedia_aud_rec_cb recCb_;
236 void *userData_;
237 CMdaAudioInputStream *iInputStream_;
238 HBufC8 *iStreamBuffer_;
239 TPtr8 iFramePtr_;
240 TInt lastError_;
241 pj_uint32_t timeStamp_;
242 CActiveSchedulerWait startAsw_;
243
244 // cache variable
245 // to avoid calculating frame length repeatedly
246 TInt frameLen_;
247
248 // sometimes recorded size != requested framesize, so let's
249 // provide a buffer to make sure the rec callback returning
250 // framesize as requested.
251 TUint8 *frameRecBuf_;
252 TInt frameRecBufLen_;
253
254 CPjAudioInputEngine(struct mda_stream *parent_strm,
255 pjmedia_aud_rec_cb rec_cb,
256 void *user_data);
257 void ConstructL();
258 TPtr8 & GetFrame();
259
260public:
261 virtual void MaiscOpenComplete(TInt aError);
262 virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer);
263 virtual void MaiscRecordComplete(TInt aError);
264
265};
266
267
268CPjAudioInputEngine::CPjAudioInputEngine(struct mda_stream *parent_strm,
269 pjmedia_aud_rec_cb rec_cb,
270 void *user_data)
271 : state_(STATE_INACTIVE), parentStrm_(parent_strm),
272 recCb_(rec_cb), userData_(user_data),
273 iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0),
274 lastError_(KErrNone), timeStamp_(0),
275 frameLen_(parent_strm->param.samples_per_frame *
276 BYTES_PER_SAMPLE),
277 frameRecBuf_(NULL), frameRecBufLen_(0)
278{
279}
280
281CPjAudioInputEngine::~CPjAudioInputEngine()
282{
283 Stop();
284
285 delete iStreamBuffer_;
286 iStreamBuffer_ = NULL;
287
288 delete [] frameRecBuf_;
289 frameRecBuf_ = NULL;
290 frameRecBufLen_ = 0;
291}
292
293void CPjAudioInputEngine::ConstructL()
294{
295 iStreamBuffer_ = HBufC8::NewL(frameLen_);
296 CleanupStack::PushL(iStreamBuffer_);
297
298 frameRecBuf_ = new TUint8[frameLen_*2];
299 CleanupStack::PushL(frameRecBuf_);
300}
301
302CPjAudioInputEngine *CPjAudioInputEngine::NewLC(struct mda_stream *parent,
303 pjmedia_aud_rec_cb rec_cb,
304 void *user_data)
305{
306 CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent,
307 rec_cb,
308 user_data);
309 CleanupStack::PushL(self);
310 self->ConstructL();
311 return self;
312}
313
314CPjAudioInputEngine *CPjAudioInputEngine::NewL(struct mda_stream *parent,
315 pjmedia_aud_rec_cb rec_cb,
316 void *user_data)
317{
318 CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data);
319 CleanupStack::Pop(self->frameRecBuf_);
320 CleanupStack::Pop(self->iStreamBuffer_);
321 CleanupStack::Pop(self);
322 return self;
323}
324
325
326pj_status_t CPjAudioInputEngine::StartRecord()
327{
328
329 // Ignore command if recording is in progress.
330 if (state_ == STATE_ACTIVE)
331 return PJ_SUCCESS;
332
333 // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices
334 // (such as Nokia 6630) require the stream to be reconstructed each time
335 // before calling Open() - otherwise the callback never gets called.
336 // For uniform behavior, lets just delete/re-create the stream for all
337 // devices.
338
339 // Destroy existing stream.
340 if (iInputStream_) delete iInputStream_;
341 iInputStream_ = NULL;
342
343 // Create the stream.
344 TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this));
345 if (err != KErrNone)
346 return PJ_RETURN_OS_ERROR(err);
347
348 // Initialize settings.
349 TMdaAudioDataSettings iStreamSettings;
350 iStreamSettings.iChannels =
351 get_channel_cap(parentStrm_->param.channel_count);
352 iStreamSettings.iSampleRate =
353 get_clock_rate_cap(parentStrm_->param.clock_rate);
354
355 pj_assert(iStreamSettings.iChannels != 0 &&
356 iStreamSettings.iSampleRate != 0);
357
358 PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, "
359 "clock rate=%d, channel count=%d..",
360 parentStrm_->param.clock_rate,
361 parentStrm_->param.channel_count));
362
363 // Open stream.
364 lastError_ = KRequestPending;
365 iInputStream_->Open(&iStreamSettings);
366
367#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \
368 PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0
369
370 startAsw_.Start();
371
372#endif
373
374 // Success
375 PJ_LOG(4,(THIS_FILE, "Sound capture started."));
376 return PJ_SUCCESS;
377}
378
379
380void CPjAudioInputEngine::Stop()
381{
382 // If capture is in progress, stop it.
383 if (iInputStream_ && state_ == STATE_ACTIVE) {
384 lastError_ = KRequestPending;
385 iInputStream_->Stop();
386
387 // Wait until it's actually stopped
388 while (lastError_ == KRequestPending)
389 pj_symbianos_poll(-1, 100);
390 }
391
392 if (iInputStream_) {
393 delete iInputStream_;
394 iInputStream_ = NULL;
395 }
396
397 if (startAsw_.IsStarted()) {
398 startAsw_.AsyncStop();
399 }
400
401 state_ = STATE_INACTIVE;
402}
403
404
405TPtr8 & CPjAudioInputEngine::GetFrame()
406{
407 //iStreamBuffer_->Des().FillZ(frameLen_);
408 iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
409 return iFramePtr_;
410}
411
412void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
413{
414 if (startAsw_.IsStarted()) {
415 startAsw_.AsyncStop();
416 }
417
418 lastError_ = aError;
419 if (aError != KErrNone) {
420 snd_perror("Error in MaiscOpenComplete()", aError);
421 return;
422 }
423
424 /* Apply input volume setting if specified */
425 if (parentStrm_->param.flags &
426 PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING)
427 {
428 stream_set_cap(&parentStrm_->base,
429 PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
430 &parentStrm_->param.input_vol);
431 }
432
433 // set stream priority to normal and time sensitive
434 iInputStream_->SetPriority(EPriorityNormal,
435 EMdaPriorityPreferenceTime);
436
437 // Read the first frame.
438 TPtr8 & frm = GetFrame();
439 TRAPD(err2, iInputStream_->ReadL(frm));
440 if (err2) {
441 PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
442 lastError_ = err2;
443 return;
444 }
445
446 // input stream opened succesfully, set status to Active
447 state_ = STATE_ACTIVE;
448}
449
450void CPjAudioInputEngine::MaiscBufferCopied(TInt aError,
451 const TDesC8 &aBuffer)
452{
453 lastError_ = aError;
454 if (aError != KErrNone) {
455 snd_perror("Error in MaiscBufferCopied()", aError);
456 return;
457 }
458
459 if (frameRecBufLen_ || aBuffer.Length() < frameLen_) {
460 pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Length());
461 frameRecBufLen_ += aBuffer.Length();
462 }
463
464 if (frameRecBufLen_) {
465 while (frameRecBufLen_ >= frameLen_) {
466 pjmedia_frame f;
467
468 f.type = PJMEDIA_FRAME_TYPE_AUDIO;
469 f.buf = frameRecBuf_;
470 f.size = frameLen_;
471 f.timestamp.u32.lo = timeStamp_;
472 f.bit_info = 0;
473
474 // Call the callback.
475 recCb_(userData_, &f);
476 // Increment timestamp.
477 timeStamp_ += parentStrm_->param.samples_per_frame;
478
479 frameRecBufLen_ -= frameLen_;
480 pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_);
481 }
482 } else {
483 pjmedia_frame f;
484
485 f.type = PJMEDIA_FRAME_TYPE_AUDIO;
486 f.buf = (void*)aBuffer.Ptr();
487 f.size = aBuffer.Length();
488 f.timestamp.u32.lo = timeStamp_;
489 f.bit_info = 0;
490
491 // Call the callback.
492 recCb_(userData_, &f);
493
494 // Increment timestamp.
495 timeStamp_ += parentStrm_->param.samples_per_frame;
496 }
497
498 // Record next frame
499 TPtr8 & frm = GetFrame();
500 TRAPD(err2, iInputStream_->ReadL(frm));
501 if (err2) {
502 PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()"));
503 }
504}
505
506
507void CPjAudioInputEngine::MaiscRecordComplete(TInt aError)
508{
509 lastError_ = aError;
510 state_ = STATE_INACTIVE;
511 if (aError != KErrNone && aError != KErrCancel) {
512 snd_perror("Error in MaiscRecordComplete()", aError);
513 }
514}
515
516
517
518//////////////////////////////////////////////////////////////////////////////
519//
520
521/*
522 * Implementation: Symbian Output Stream.
523 */
524
525class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback
526{
527public:
528 enum State
529 {
530 STATE_INACTIVE,
531 STATE_ACTIVE,
532 };
533
534 ~CPjAudioOutputEngine();
535
536 static CPjAudioOutputEngine *NewL(struct mda_stream *parent_strm,
537 pjmedia_aud_play_cb play_cb,
538 void *user_data);
539
540 static CPjAudioOutputEngine *NewLC(struct mda_stream *parent_strm,
541 pjmedia_aud_play_cb rec_cb,
542 void *user_data);
543
544 pj_status_t StartPlay();
545 void Stop();
546
547 pj_status_t SetVolume(TInt vol) {
548 if (iOutputStream_) {
549 iOutputStream_->SetVolume(vol);
550 return PJ_SUCCESS;
551 } else
552 return PJ_EINVALIDOP;
553 }
554
555 TInt GetVolume() {
556 if (iOutputStream_) {
557 return iOutputStream_->Volume();
558 } else
559 return PJ_EINVALIDOP;
560 }
561
562 TInt GetMaxVolume() {
563 if (iOutputStream_) {
564 return iOutputStream_->MaxVolume();
565 } else
566 return PJ_EINVALIDOP;
567 }
568
569private:
570 State state_;
571 struct mda_stream *parentStrm_;
572 pjmedia_aud_play_cb playCb_;
573 void *userData_;
574 CMdaAudioOutputStream *iOutputStream_;
575 TUint8 *frameBuf_;
576 unsigned frameBufSize_;
577 TPtrC8 frame_;
578 TInt lastError_;
579 unsigned timestamp_;
580 CActiveSchedulerWait startAsw_;
581
582 CPjAudioOutputEngine(struct mda_stream *parent_strm,
583 pjmedia_aud_play_cb play_cb,
584 void *user_data);
585 void ConstructL();
586
587 virtual void MaoscOpenComplete(TInt aError);
588 virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer);
589 virtual void MaoscPlayComplete(TInt aError);
590};
591
592
593CPjAudioOutputEngine::CPjAudioOutputEngine(struct mda_stream *parent_strm,
594 pjmedia_aud_play_cb play_cb,
595 void *user_data)
596: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb),
597 userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL),
598 lastError_(KErrNone), timestamp_(0)
599{
600}
601
602
603void CPjAudioOutputEngine::ConstructL()
604{
605 frameBufSize_ = parentStrm_->param.samples_per_frame *
606 BYTES_PER_SAMPLE;
607 frameBuf_ = new TUint8[frameBufSize_];
608}
609
610CPjAudioOutputEngine::~CPjAudioOutputEngine()
611{
612 Stop();
613 delete [] frameBuf_;
614}
615
616CPjAudioOutputEngine *
617CPjAudioOutputEngine::NewLC(struct mda_stream *parent_strm,
618 pjmedia_aud_play_cb play_cb,
619 void *user_data)
620{
621 CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm,
622 play_cb,
623 user_data);
624 CleanupStack::PushL(self);
625 self->ConstructL();
626 return self;
627}
628
629CPjAudioOutputEngine *
630CPjAudioOutputEngine::NewL(struct mda_stream *parent_strm,
631 pjmedia_aud_play_cb play_cb,
632 void *user_data)
633{
634 CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data);
635 CleanupStack::Pop(self);
636 return self;
637}
638
639pj_status_t CPjAudioOutputEngine::StartPlay()
640{
641 // Ignore command if playing is in progress.
642 if (state_ == STATE_ACTIVE)
643 return PJ_SUCCESS;
644
645 // Destroy existing stream.
646 if (iOutputStream_) delete iOutputStream_;
647 iOutputStream_ = NULL;
648
649 // Create the stream
650 TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this));
651 if (err != KErrNone)
652 return PJ_RETURN_OS_ERROR(err);
653
654 // Initialize settings.
655 TMdaAudioDataSettings iStreamSettings;
656 iStreamSettings.iChannels =
657 get_channel_cap(parentStrm_->param.channel_count);
658 iStreamSettings.iSampleRate =
659 get_clock_rate_cap(parentStrm_->param.clock_rate);
660
661 pj_assert(iStreamSettings.iChannels != 0 &&
662 iStreamSettings.iSampleRate != 0);
663
664 PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, "
665 "clock rate=%d, channel count=%d..",
666 parentStrm_->param.clock_rate,
667 parentStrm_->param.channel_count));
668
669 // Open stream.
670 lastError_ = KRequestPending;
671 iOutputStream_->Open(&iStreamSettings);
672
673#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \
674 PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0
675
676 startAsw_.Start();
677
678#endif
679
680 // Success
681 PJ_LOG(4,(THIS_FILE, "Sound playback started"));
682 return PJ_SUCCESS;
683
684}
685
686void CPjAudioOutputEngine::Stop()
687{
688 // Stop stream if it's playing
689 if (iOutputStream_ && state_ != STATE_INACTIVE) {
690 lastError_ = KRequestPending;
691 iOutputStream_->Stop();
692
693 // Wait until it's actually stopped
694 while (lastError_ == KRequestPending)
695 pj_symbianos_poll(-1, 100);
696 }
697
698 if (iOutputStream_) {
699 delete iOutputStream_;
700 iOutputStream_ = NULL;
701 }
702
703 if (startAsw_.IsStarted()) {
704 startAsw_.AsyncStop();
705 }
706
707 state_ = STATE_INACTIVE;
708}
709
710void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
711{
712 if (startAsw_.IsStarted()) {
713 startAsw_.AsyncStop();
714 }
715
716 lastError_ = aError;
717
718 if (aError==KErrNone) {
719 // set stream properties, 16bit 8KHz mono
720 TMdaAudioDataSettings iSettings;
721 iSettings.iChannels =
722 get_channel_cap(parentStrm_->param.channel_count);
723 iSettings.iSampleRate =
724 get_clock_rate_cap(parentStrm_->param.clock_rate);
725
726 iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate,
727 iSettings.iChannels);
728
729 /* Apply output volume setting if specified */
730 if (parentStrm_->param.flags &
731 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING)
732 {
733 stream_set_cap(&parentStrm_->base,
734 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
735 &parentStrm_->param.output_vol);
736 } else {
737 // set volume to 1/2th of stream max volume
738 iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2);
739 }
740
741 // set stream priority to normal and time sensitive
742 iOutputStream_->SetPriority(EPriorityNormal,
743 EMdaPriorityPreferenceTime);
744
745 // Call callback to retrieve frame from upstream.
746 pjmedia_frame f;
747 pj_status_t status;
748
749 f.type = PJMEDIA_FRAME_TYPE_AUDIO;
750 f.buf = frameBuf_;
751 f.size = frameBufSize_;
752 f.timestamp.u32.lo = timestamp_;
753 f.bit_info = 0;
754
755 status = playCb_(this->userData_, &f);
756 if (status != PJ_SUCCESS) {
757 this->Stop();
758 return;
759 }
760
761 if (f.type != PJMEDIA_FRAME_TYPE_AUDIO)
762 pj_bzero(frameBuf_, frameBufSize_);
763
764 // Increment timestamp.
765 timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
766
767 // issue WriteL() to write the first audio data block,
768 // subsequent calls to WriteL() will be issued in
769 // MMdaAudioOutputStreamCallback::MaoscBufferCopied()
770 // until whole data buffer is written.
771 frame_.Set(frameBuf_, frameBufSize_);
772 iOutputStream_->WriteL(frame_);
773
774 // output stream opened succesfully, set status to Active
775 state_ = STATE_ACTIVE;
776 } else {
777 snd_perror("Error in MaoscOpenComplete()", aError);
778 }
779}
780
781void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError,
782 const TDesC8& aBuffer)
783{
784 PJ_UNUSED_ARG(aBuffer);
785
786 if (aError==KErrNone) {
787 // Buffer successfully written, feed another one.
788
789 // Call callback to retrieve frame from upstream.
790 pjmedia_frame f;
791 pj_status_t status;
792
793 f.type = PJMEDIA_FRAME_TYPE_AUDIO;
794 f.buf = frameBuf_;
795 f.size = frameBufSize_;
796 f.timestamp.u32.lo = timestamp_;
797 f.bit_info = 0;
798
799 status = playCb_(this->userData_, &f);
800 if (status != PJ_SUCCESS) {
801 this->Stop();
802 return;
803 }
804
805 if (f.type != PJMEDIA_FRAME_TYPE_AUDIO)
806 pj_bzero(frameBuf_, frameBufSize_);
807
808 // Increment timestamp.
809 timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE);
810
811 // Write to playback stream.
812 frame_.Set(frameBuf_, frameBufSize_);
813 iOutputStream_->WriteL(frame_);
814
815 } else if (aError==KErrAbort) {
816 // playing was aborted, due to call to CMdaAudioOutputStream::Stop()
817 state_ = STATE_INACTIVE;
818 } else {
819 // error writing data to output
820 lastError_ = aError;
821 state_ = STATE_INACTIVE;
822 snd_perror("Error in MaoscBufferCopied()", aError);
823 }
824}
825
826void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError)
827{
828 lastError_ = aError;
829 state_ = STATE_INACTIVE;
830 if (aError != KErrNone && aError != KErrCancel) {
831 snd_perror("Error in MaoscPlayComplete()", aError);
832 }
833}
834
835/****************************************************************************
836 * Factory operations
837 */
838
839/*
840 * C compatible declaration of MDA factory.
841 */
842PJ_BEGIN_DECL
843PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_symb_mda_factory(pj_pool_factory *pf);
844PJ_END_DECL
845
846/*
847 * Init Symbian audio driver.
848 */
849pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf)
850{
851 struct mda_factory *f;
852 pj_pool_t *pool;
853
854 pool = pj_pool_create(pf, "symb_aud", 1000, 1000, NULL);
855 f = PJ_POOL_ZALLOC_T(pool, struct mda_factory);
856 f->pf = pf;
857 f->pool = pool;
858 f->base.op = &factory_op;
859
860 return &f->base;
861}
862
863/* API: init factory */
864static pj_status_t factory_init(pjmedia_aud_dev_factory *f)
865{
866 struct mda_factory *af = (struct mda_factory*)f;
867
868 pj_ansi_strcpy(af->dev_info.name, "Symbian Audio");
869 af->dev_info.default_samples_per_sec = 8000;
870 af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING |
871 PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
872 af->dev_info.input_count = 1;
873 af->dev_info.output_count = 1;
874
875 PJ_LOG(4, (THIS_FILE, "Symb Mda initialized"));
876
877 return PJ_SUCCESS;
878}
879
880/* API: destroy factory */
881static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f)
882{
883 struct mda_factory *af = (struct mda_factory*)f;
884 pj_pool_t *pool = af->pool;
885
886 af->pool = NULL;
887 pj_pool_release(pool);
888
889 PJ_LOG(4, (THIS_FILE, "Symbian Mda destroyed"));
890
891 return PJ_SUCCESS;
892}
893
894/* API: refresh the device list */
895static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f)
896{
897 PJ_UNUSED_ARG(f);
898 return PJ_ENOTSUP;
899}
900
901/* API: get number of devices */
902static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f)
903{
904 PJ_UNUSED_ARG(f);
905 return 1;
906}
907
908/* API: get device info */
909static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f,
910 unsigned index,
911 pjmedia_aud_dev_info *info)
912{
913 struct mda_factory *af = (struct mda_factory*)f;
914
915 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
916
917 pj_memcpy(info, &af->dev_info, sizeof(*info));
918
919 return PJ_SUCCESS;
920}
921
922/* API: create default device parameter */
923static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f,
924 unsigned index,
925 pjmedia_aud_param *param)
926{
927 struct mda_factory *af = (struct mda_factory*)f;
928
929 PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV);
930
931 pj_bzero(param, sizeof(*param));
932 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
933 param->rec_id = index;
934 param->play_id = index;
935 param->clock_rate = af->dev_info.default_samples_per_sec;
936 param->channel_count = 1;
937 param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000;
938 param->bits_per_sample = BITS_PER_SAMPLE;
939 // Don't set the flags without specifying the flags value.
940 //param->flags = af->dev_info.caps;
941
942 return PJ_SUCCESS;
943}
944
945
946/* API: create stream */
947static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f,
948 const pjmedia_aud_param *param,
949 pjmedia_aud_rec_cb rec_cb,
950 pjmedia_aud_play_cb play_cb,
951 void *user_data,
952 pjmedia_aud_stream **p_aud_strm)
953{
954 struct mda_factory *mf = (struct mda_factory*)f;
955 pj_pool_t *pool;
956 struct mda_stream *strm;
957
958 /* Can only support 16bits per sample raw PCM format. */
959 PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL);
960 PJ_ASSERT_RETURN((param->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT)==0 ||
961 param->ext_fmt.id == PJMEDIA_FORMAT_L16,
962 PJ_ENOTSUP);
963
964 /* It seems that MDA recorder only supports for mono channel. */
965 PJ_ASSERT_RETURN(param->channel_count == 1, PJ_EINVAL);
966
967 /* Create and Initialize stream descriptor */
968 pool = pj_pool_create(mf->pf, "symb_aud_dev", 1000, 1000, NULL);
969 PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
970
971 strm = PJ_POOL_ZALLOC_T(pool, struct mda_stream);
972 strm->pool = pool;
973 strm->param = *param;
974
975 // Create the output stream.
976 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
977 TRAPD(err, strm->out_engine = CPjAudioOutputEngine::NewL(strm, play_cb,
978 user_data));
979 if (err != KErrNone) {
980 pj_pool_release(pool);
981 return PJ_RETURN_OS_ERROR(err);
982 }
983 }
984
985 // Create the input stream.
986 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
987 TRAPD(err, strm->in_engine = CPjAudioInputEngine::NewL(strm, rec_cb,
988 user_data));
989 if (err != KErrNone) {
990 strm->in_engine = NULL;
991 delete strm->out_engine;
992 strm->out_engine = NULL;
993 pj_pool_release(pool);
994 return PJ_RETURN_OS_ERROR(err);
995 }
996 }
997
998 /* Done */
999 strm->base.op = &stream_op;
1000 *p_aud_strm = &strm->base;
1001
1002 return PJ_SUCCESS;
1003}
1004
1005/* API: Get stream info. */
1006static pj_status_t stream_get_param(pjmedia_aud_stream *s,
1007 pjmedia_aud_param *pi)
1008{
1009 struct mda_stream *strm = (struct mda_stream*)s;
1010
1011 PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
1012
1013 pj_memcpy(pi, &strm->param, sizeof(*pi));
1014
1015 /* Update the output volume setting */
1016 if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING,
1017 &pi->output_vol) == PJ_SUCCESS)
1018 {
1019 pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING;
1020 }
1021
1022 /* Update the input volume setting */
1023 if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING,
1024 &pi->input_vol) == PJ_SUCCESS)
1025 {
1026 pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING;
1027 }
1028
1029 return PJ_SUCCESS;
1030}
1031
1032/* API: get capability */
1033static pj_status_t stream_get_cap(pjmedia_aud_stream *s,
1034 pjmedia_aud_dev_cap cap,
1035 void *pval)
1036{
1037 struct mda_stream *strm = (struct mda_stream*)s;
1038 pj_status_t status = PJ_ENOTSUP;
1039
1040 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1041
1042 switch (cap) {
1043 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
1044 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1045 PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
1046
1047 TInt max_gain = strm->in_engine->GetMaxGain();
1048 TInt gain = strm->in_engine->GetGain();
1049
1050 if (max_gain > 0 && gain >= 0) {
1051 *(unsigned*)pval = gain * 100 / max_gain;
1052 status = PJ_SUCCESS;
1053 } else {
1054 status = PJMEDIA_EAUD_NOTREADY;
1055 }
1056 }
1057 break;
1058 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
1059 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1060 PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
1061
1062 TInt max_vol = strm->out_engine->GetMaxVolume();
1063 TInt vol = strm->out_engine->GetVolume();
1064
1065 if (max_vol > 0 && vol >= 0) {
1066 *(unsigned*)pval = vol * 100 / max_vol;
1067 status = PJ_SUCCESS;
1068 } else {
1069 status = PJMEDIA_EAUD_NOTREADY;
1070 }
1071 }
1072 break;
1073 default:
1074 break;
1075 }
1076
1077 return status;
1078}
1079
1080/* API: set capability */
1081static pj_status_t stream_set_cap(pjmedia_aud_stream *s,
1082 pjmedia_aud_dev_cap cap,
1083 const void *pval)
1084{
1085 struct mda_stream *strm = (struct mda_stream*)s;
1086 pj_status_t status = PJ_ENOTSUP;
1087
1088 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1089
1090 switch (cap) {
1091 case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING:
1092 if (strm->param.dir & PJMEDIA_DIR_CAPTURE) {
1093 PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL);
1094
1095 TInt max_gain = strm->in_engine->GetMaxGain();
1096 if (max_gain > 0) {
1097 TInt gain;
1098
1099 gain = *(unsigned*)pval * max_gain / 100;
1100 status = strm->in_engine->SetGain(gain);
1101 } else {
1102 status = PJMEDIA_EAUD_NOTREADY;
1103 }
1104 }
1105 break;
1106 case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING:
1107 if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) {
1108 PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL);
1109
1110 TInt max_vol = strm->out_engine->GetMaxVolume();
1111 if (max_vol > 0) {
1112 TInt vol;
1113
1114 vol = *(unsigned*)pval * max_vol / 100;
1115 status = strm->out_engine->SetVolume(vol);
1116 } else {
1117 status = PJMEDIA_EAUD_NOTREADY;
1118 }
1119 }
1120 break;
1121 default:
1122 break;
1123 }
1124
1125 return status;
1126}
1127
1128/* API: Start stream. */
1129static pj_status_t stream_start(pjmedia_aud_stream *strm)
1130{
1131 struct mda_stream *stream = (struct mda_stream*)strm;
1132
1133 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1134
1135 if (stream->out_engine) {
1136 pj_status_t status;
1137 status = stream->out_engine->StartPlay();
1138 if (status != PJ_SUCCESS)
1139 return status;
1140 }
1141
1142 if (stream->in_engine) {
1143 pj_status_t status;
1144 status = stream->in_engine->StartRecord();
1145 if (status != PJ_SUCCESS)
1146 return status;
1147 }
1148
1149 return PJ_SUCCESS;
1150}
1151
1152/* API: Stop stream. */
1153static pj_status_t stream_stop(pjmedia_aud_stream *strm)
1154{
1155 struct mda_stream *stream = (struct mda_stream*)strm;
1156
1157 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1158
1159 if (stream->in_engine) {
1160 stream->in_engine->Stop();
1161 }
1162
1163 if (stream->out_engine) {
1164 stream->out_engine->Stop();
1165 }
1166
1167 return PJ_SUCCESS;
1168}
1169
1170
1171/* API: Destroy stream. */
1172static pj_status_t stream_destroy(pjmedia_aud_stream *strm)
1173{
1174 struct mda_stream *stream = (struct mda_stream*)strm;
1175
1176 PJ_ASSERT_RETURN(stream, PJ_EINVAL);
1177
1178 stream_stop(strm);
1179
1180 delete stream->in_engine;
1181 stream->in_engine = NULL;
1182
1183 delete stream->out_engine;
1184 stream->out_engine = NULL;
1185
1186 pj_pool_t *pool;
1187 pool = stream->pool;
1188 if (pool) {
1189 stream->pool = NULL;
1190 pj_pool_release(pool);
1191 }
1192
1193 return PJ_SUCCESS;
1194}
1195
1196#endif /* PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA */