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