blob: 2be485ed3292c9310763b66f3921349af01bd4e5 [file] [log] [blame]
Alexandre Lision0e143012014-01-22 11:02:46 -05001/* $Id: bb10_dev.c 4707 2014-01-17 05:09:29Z bennylp $ */
Tristan Matthews0a329cc2013-07-17 13:20:14 -04002/*
3 * Copyright (C) 2008-2012 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
20/*
21 * This is the implementation of BlackBerry 10 (BB10) audio device.
22 * Original code was kindly donated by Truphone Ltd. (http://www.truphone.com)
23 * The key methods here are bb10_capture_open, bb10_play_open together
24 * with the capture and play threads ca_thread_func and pb_thread_func
25 */
26
27#include <pjmedia_audiodev.h>
28#include <pj/assert.h>
29#include <pj/log.h>
30#include <pj/os.h>
31#include <pj/pool.h>
32#include <pjmedia/errno.h>
33
34#if defined(PJMEDIA_AUDIO_DEV_HAS_BB10) && PJMEDIA_AUDIO_DEV_HAS_BB10 != 0
35
36#ifndef PJ_BBSDK_VER
37 /* Format: 0xMMNNRR: MM: major, NN: minor, RR: revision */
38# define PJ_BBSDK_VER 0x100006
39#endif
40
41#include <sys/time.h>
42#include <sys/types.h>
43#include <unistd.h>
44#include <sys/select.h>
45#include <pthread.h>
46#include <errno.h>
47#include <sys/asoundlib.h>
48#if PJ_BBSDK_VER >= 0x100006
49#include <audio/audio_manager_routing.h>
50#endif
51
52
53#define THIS_FILE "bb10_dev.c"
54#define BB10_DEVICE_NAME "plughw:%d,%d"
55
56
57/* Set to 1 to enable tracing */
58#if 1
59# define TRACE_(expr) PJ_LOG(4,expr)
60#else
61# define TRACE_(expr)
62#endif
63
64/*
65 * Factory prototypes
66 */
67static pj_status_t bb10_factory_init(pjmedia_aud_dev_factory *f);
68static pj_status_t bb10_factory_destroy(pjmedia_aud_dev_factory *f);
69static pj_status_t bb10_factory_refresh(pjmedia_aud_dev_factory *f);
70static unsigned bb10_factory_get_dev_count(pjmedia_aud_dev_factory *f);
71static pj_status_t bb10_factory_get_dev_info(pjmedia_aud_dev_factory *f,
72 unsigned index,
73 pjmedia_aud_dev_info *info);
74static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f,
75 unsigned index,
76 pjmedia_aud_param *param);
77static pj_status_t bb10_factory_create_stream(pjmedia_aud_dev_factory *f,
78 const pjmedia_aud_param *param,
79 pjmedia_aud_rec_cb rec_cb,
80 pjmedia_aud_play_cb play_cb,
81 void *user_data,
82 pjmedia_aud_stream **p_strm);
83
84/*
85 * Stream prototypes
86 */
87static pj_status_t bb10_stream_get_param(pjmedia_aud_stream *strm,
88 pjmedia_aud_param *param);
89static pj_status_t bb10_stream_get_cap(pjmedia_aud_stream *strm,
90 pjmedia_aud_dev_cap cap,
91 void *value);
92static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm,
93 pjmedia_aud_dev_cap cap,
94 const void *value);
95static pj_status_t bb10_stream_start(pjmedia_aud_stream *strm);
96static pj_status_t bb10_stream_stop(pjmedia_aud_stream *strm);
97static pj_status_t bb10_stream_destroy(pjmedia_aud_stream *strm);
98
99
100struct bb10_factory
101{
102 pjmedia_aud_dev_factory base;
103 pj_pool_factory *pf;
104 pj_pool_t *pool;
105 pj_pool_t *base_pool;
106 unsigned dev_cnt;
107 pjmedia_aud_dev_info devs[1];
108};
109
110struct bb10_stream
111{
112 pjmedia_aud_stream base;
113
114 /* Common */
115 pj_pool_t *pool;
116 struct bb10_factory *af;
117 void *user_data;
118 pjmedia_aud_param param; /* Running parameter */
119 int rec_id; /* Capture device id */
120 int quit;
121
122 /* Playback */
123 unsigned int pb_ctrl_audio_manager_handle;
124 snd_pcm_t *pb_pcm;
125 unsigned int pb_audio_manager_handle;
126 unsigned long pb_frames; /* samples_per_frame */
127 pjmedia_aud_play_cb pb_cb;
128 unsigned pb_buf_size;
129 char *pb_buf;
130 pj_thread_t *pb_thread;
131
132 /* Capture */
133 snd_pcm_t *ca_pcm;
134 unsigned int ca_audio_manager_handle;
135 unsigned long ca_frames; /* samples_per_frame */
136 pjmedia_aud_rec_cb ca_cb;
137 unsigned ca_buf_size;
138 char *ca_buf;
139 pj_thread_t *ca_thread;
140};
141
142static pjmedia_aud_dev_factory_op bb10_factory_op =
143{
144 &bb10_factory_init,
145 &bb10_factory_destroy,
146 &bb10_factory_get_dev_count,
147 &bb10_factory_get_dev_info,
148 &bb10_factory_default_param,
149 &bb10_factory_create_stream,
150 &bb10_factory_refresh
151};
152
153static pjmedia_aud_stream_op bb10_stream_op =
154{
155 &bb10_stream_get_param,
156 &bb10_stream_get_cap,
157 &bb10_stream_set_cap,
158 &bb10_stream_start,
159 &bb10_stream_stop,
160 &bb10_stream_destroy
161};
162
163/*
164 * BB10 - tests loads the audio units and sets up the driver structure
165 */
166static pj_status_t bb10_add_dev (struct bb10_factory *af)
167{
168 pjmedia_aud_dev_info *adi;
169 int pb_result, ca_result;
170 unsigned int handle;
171 snd_pcm_t *pcm_handle;
172
173 if (af->dev_cnt >= PJ_ARRAY_SIZE(af->devs))
174 return PJ_ETOOMANY;
175
176 adi = &af->devs[af->dev_cnt];
177
178 TRACE_((THIS_FILE, "bb10_add_dev Enter"));
179
180 if ((pb_result = audio_manager_snd_pcm_open_name(AUDIO_TYPE_VIDEO_CHAT,
181 &pcm_handle,
182 &handle,
183 (char*)"voice",
184 SND_PCM_OPEN_PLAYBACK))
185 >= 0)
186 {
187 snd_pcm_close (pcm_handle);
188 audio_manager_free_handle(handle);
189 } else {
190 TRACE_((THIS_FILE, "Try to open the device for playback - failure"));
191 }
192
193 if ((ca_result = audio_manager_snd_pcm_open_name(AUDIO_TYPE_VIDEO_CHAT,
194 &pcm_handle,
195 &handle,
196 (char*)"voice",
197 SND_PCM_OPEN_CAPTURE))
198 >= 0)
199 {
200 snd_pcm_close (pcm_handle);
201 audio_manager_free_handle(handle);
202
203 } else {
204 TRACE_((THIS_FILE, "Try to open the device for capture - failure"));
205 }
206
207 if (pb_result < 0 && ca_result < 0) {
208 TRACE_((THIS_FILE, "Unable to open sound device", "preferred"));
209 return PJMEDIA_EAUD_NODEV;
210 }
211
212 /* Reset device info */
213 pj_bzero(adi, sizeof(*adi));
214
215 /* Set device name */
216 strcpy(adi->name, "preferred");
217
218 /* Check the number of playback channels */
219 adi->output_count = (pb_result >= 0) ? 1 : 0;
220
221 /* Check the number of capture channels */
222 adi->input_count = (ca_result >= 0) ? 1 : 0;
223
224 /* Set the default sample rate */
225 adi->default_samples_per_sec = 8000;
226
227 /* Driver name */
228 strcpy(adi->driver, "BB10");
229
230 ++af->dev_cnt;
231
232 PJ_LOG (4,(THIS_FILE, "Added sound device %s", adi->name));
233
234 return PJ_SUCCESS;
235}
236
237/* Create BB10 audio driver. */
238pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf)
239{
240 struct bb10_factory *af;
241 pj_pool_t *pool;
242
243 pool = pj_pool_create(pf, "bb10_aud_base", 256, 256, NULL);
244 af = PJ_POOL_ZALLOC_T(pool, struct bb10_factory);
245 af->pf = pf;
246 af->base_pool = pool;
247 af->base.op = &bb10_factory_op;
248
249 return &af->base;
250}
251
252
253/* API: init factory */
254static pj_status_t bb10_factory_init(pjmedia_aud_dev_factory *f)
255{
256 pj_status_t status;
257
258 status = bb10_factory_refresh(f);
259 if (status != PJ_SUCCESS)
260 return status;
261
262 PJ_LOG(4,(THIS_FILE, "BB10 initialized"));
263 return PJ_SUCCESS;
264}
265
266
267/* API: destroy factory */
268static pj_status_t bb10_factory_destroy(pjmedia_aud_dev_factory *f)
269{
270 struct bb10_factory *af = (struct bb10_factory*)f;
271
272 if (af->pool) {
273 TRACE_((THIS_FILE, "bb10_factory_destroy() - 1"));
274 pj_pool_release(af->pool);
275 }
276
277 if (af->base_pool) {
278 pj_pool_t *pool = af->base_pool;
279 af->base_pool = NULL;
280 TRACE_((THIS_FILE, "bb10_factory_destroy() - 2"));
281 pj_pool_release(pool);
282 }
283
284 return PJ_SUCCESS;
285}
286
287
288/* API: refresh the device list */
289static pj_status_t bb10_factory_refresh(pjmedia_aud_dev_factory *f)
290{
291 struct bb10_factory *af = (struct bb10_factory*)f;
292 int err;
293
294 TRACE_((THIS_FILE, "bb10_factory_refresh()"));
295
296 if (af->pool != NULL) {
297 pj_pool_release(af->pool);
298 af->pool = NULL;
299 }
300
301 af->pool = pj_pool_create(af->pf, "bb10_aud", 256, 256, NULL);
302 af->dev_cnt = 0;
303
304 err = bb10_add_dev(af);
305
306 PJ_LOG(4,(THIS_FILE, "BB10 driver found %d devices", af->dev_cnt));
307
308 return err;
309}
310
311
312/* API: get device count */
313static unsigned bb10_factory_get_dev_count(pjmedia_aud_dev_factory *f)
314{
315 struct bb10_factory *af = (struct bb10_factory*)f;
316 return af->dev_cnt;
317}
318
319
320/* API: get device info */
321static pj_status_t bb10_factory_get_dev_info(pjmedia_aud_dev_factory *f,
322 unsigned index,
323 pjmedia_aud_dev_info *info)
324{
325 struct bb10_factory *af = (struct bb10_factory*)f;
326
327 PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
328
329 pj_memcpy(info, &af->devs[index], sizeof(*info));
330 info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
331 PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY |
332 PJMEDIA_AUD_DEV_CAP_EC;
333
334 return PJ_SUCCESS;
335}
336
337/* API: create default parameter */
338static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f,
339 unsigned index,
340 pjmedia_aud_param *param)
341{
342 struct bb10_factory *af = (struct bb10_factory*)f;
343 pjmedia_aud_dev_info *adi;
344
345 PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
346
347 adi = &af->devs[index];
348
349 pj_bzero(param, sizeof(*param));
350 if (adi->input_count && adi->output_count) {
351 param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
352 param->rec_id = index;
353 param->play_id = index;
354 } else if (adi->input_count) {
355 param->dir = PJMEDIA_DIR_CAPTURE;
356 param->rec_id = index;
357 param->play_id = PJMEDIA_AUD_INVALID_DEV;
358 } else if (adi->output_count) {
359 param->dir = PJMEDIA_DIR_PLAYBACK;
360 param->play_id = index;
361 param->rec_id = PJMEDIA_AUD_INVALID_DEV;
362 } else {
363 return PJMEDIA_EAUD_INVDEV;
364 }
365
366 param->clock_rate = adi->default_samples_per_sec;
367 param->channel_count = 1;
368 param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000;
369 param->bits_per_sample = 16;
370 param->flags = adi->caps;
371 param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
372 param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
373
374 TRACE_((THIS_FILE, "bb10_factory_default_param clock = %d flags = %d"
375 " spf = %d", param->clock_rate, param->flags,
376 param->samples_per_frame));
377
378 return PJ_SUCCESS;
379}
380
381
382static void close_play_pcm(struct bb10_stream *stream)
383{
384 if (stream != NULL && stream->pb_pcm != NULL) {
385 snd_pcm_close(stream->pb_pcm);
386 stream->pb_pcm = NULL;
387
388 if (stream->pb_audio_manager_handle != 0) {
389 audio_manager_free_handle(stream->pb_audio_manager_handle);
390 stream->pb_audio_manager_handle = 0;
391 }
392
393 if (stream->pb_ctrl_audio_manager_handle != 0) {
394 audio_manager_free_handle(stream->pb_ctrl_audio_manager_handle);
395 stream->pb_ctrl_audio_manager_handle = 0;
396 }
397 }
398}
399
400static void flush_play(struct bb10_stream *stream)
401{
402 if (stream != NULL && stream->pb_pcm != NULL) {
403 snd_pcm_plugin_flush (stream->pb_pcm, SND_PCM_CHANNEL_PLAYBACK);
404 }
405}
406
407static void close_capture_pcm(struct bb10_stream *stream)
408{
409 if (stream != NULL && stream->ca_pcm != NULL) {
410 snd_pcm_close(stream->ca_pcm);
411 stream->ca_pcm = NULL;
412
413 if (stream->ca_audio_manager_handle != 0) {
414 audio_manager_free_handle(stream->ca_audio_manager_handle);
415 stream->ca_audio_manager_handle = 0;
416 }
417 }
418}
419
420static void flush_capture(struct bb10_stream *stream)
421{
422 if (stream != NULL && stream->ca_pcm != NULL) {
423 snd_pcm_plugin_flush (stream->ca_pcm, SND_PCM_CHANNEL_CAPTURE);
424 }
425}
426
427
428/**
429 * Play audio received from PJMEDIA
430 */
431static int pb_thread_func (void *arg)
432{
433 struct bb10_stream* stream = (struct bb10_stream *) arg;
434 int size = stream->pb_buf_size;
435 unsigned long nframes = stream->pb_frames;
436 void *user_data = stream->user_data;
437 char *buf = stream->pb_buf;
438 pj_timestamp tstamp;
439 int result = 0;
440 int policy;
441 struct sched_param param;
442
443 TRACE_((THIS_FILE, "pb_thread_func: size = %d ", size));
444
445 if (pthread_getschedparam(pthread_self(), &policy, &param) == 0) {
446 param.sched_priority = 18;
447 pthread_setschedparam (pthread_self(), policy, &param);
448 }
449
450 pj_bzero (buf, size);
451 tstamp.u64 = 0;
452
453 /* Do the final initialization now the thread has started. */
454 if ((result = snd_pcm_plugin_prepare(stream->pb_pcm,
455 SND_PCM_CHANNEL_PLAYBACK)) < 0)
456 {
457 close_play_pcm(stream);
458 TRACE_((THIS_FILE, "pb_thread_func failed prepare = %d", result));
459 return PJ_SUCCESS;
460 }
461
462 while (!stream->quit) {
463 pjmedia_frame frame;
464
465 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
466 /* pointer to buffer filled by PJMEDIA */
467 frame.buf = buf;
468 frame.size = size;
469 frame.timestamp.u64 = tstamp.u64;
470 frame.bit_info = 0;
471
472 /* Read the audio from pjmedia */
473 result = stream->pb_cb (user_data, &frame);
474 if (result != PJ_SUCCESS || stream->quit)
475 break;
476
477 if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO)
478 pj_bzero (buf, size);
479
480 /* Write 640 to play unit */
481 result = snd_pcm_plugin_write(stream->pb_pcm,buf,size);
482 if (result != size || result < 0) {
483 /* either the write to output device has failed or not the
484 * full amount of bytes have been written. This usually happens
485 * when audio routing is being changed by another thread
486 * Use a status variable for reading the error
487 */
488 snd_pcm_channel_status_t status;
489 status.channel = SND_PCM_CHANNEL_PLAYBACK;
490 if (snd_pcm_plugin_status (stream->pb_pcm, &status) < 0) {
491 /* Call has failed nothing we can do except log and
492 * continue */
493 PJ_LOG(4,(THIS_FILE,
494 "underrun: playback channel status error"));
495 } else {
496 /* The status of the error has been read
497 * RIM say these are expected so we can "re-prepare" the stream
498 */
499 PJ_LOG(4,(THIS_FILE,"PLAY thread ERROR status = %d",
500 status.status));
501 if (status.status == SND_PCM_STATUS_READY ||
502 status.status == SND_PCM_STATUS_UNDERRUN ||
503 status.status == SND_PCM_STATUS_ERROR )
504 {
505 if (snd_pcm_plugin_prepare (stream->pb_pcm,
506 SND_PCM_CHANNEL_PLAYBACK) < 0)
507 {
508 PJ_LOG(4,(THIS_FILE,
509 "underrun: playback channel prepare error"));
510 }
511 }
512 }
513 }
514 tstamp.u64 += nframes;
515 }
516
517 flush_play(stream);
518 close_play_pcm(stream);
519 TRACE_((THIS_FILE, "pb_thread_func: Stopped"));
520
521 return PJ_SUCCESS;
522}
523
524
525
526static int ca_thread_func (void *arg)
527{
528 struct bb10_stream* stream = (struct bb10_stream *) arg;
529 int size = stream->ca_buf_size;
530 unsigned long nframes = stream->ca_frames;
531 void *user_data = stream->user_data;
532 /* Buffer to fill for PJMEDIA */
533 char *buf = stream->ca_buf;
534 pj_timestamp tstamp;
535 int result;
536 int policy;
537 struct sched_param param;
538
539 TRACE_((THIS_FILE, "ca_thread_func: size = %d ", size));
540
541 if (pthread_getschedparam(pthread_self(), &policy, &param) == 0) {
542 param.sched_priority = 18;
543 pthread_setschedparam (pthread_self(), policy, &param);
544 }
545
546 pj_bzero (buf, size);
547 tstamp.u64 = 0;
548
549 /* Final init now the thread has started */
550 if ((result = snd_pcm_plugin_prepare (stream->ca_pcm,
551 SND_PCM_CHANNEL_CAPTURE)) < 0)
552 {
553 close_capture_pcm(stream);
554 TRACE_((THIS_FILE, "ca_thread_func failed prepare = %d", result));
555 return PJ_SUCCESS;
556 }
557
558 while (!stream->quit) {
559 pjmedia_frame frame;
560
561 //pj_bzero (buf, size);
562
563 /* read the input device */
564 result = snd_pcm_plugin_read(stream->ca_pcm, buf,size);
565 if(result <0 || result != size) {
566 /* We expect result to be size (640)
567 * It's not so we have to read the status error and "prepare"
568 * the channel. This usually happens when output audio routing
569 * has been changed by another thread.
570 * We won't "continue", instead just do what we can and leave
571 * the end of the loop to write what's in the buffer. Not entirely
572 * correct but saves a potential underrun in PJMEDIA
573 */
574 PJ_LOG (4,(THIS_FILE,
575 "snd_pcm_plugin_read ERROR read = %d required = %d",
576 result,size));
577 snd_pcm_channel_status_t status;
578 status.channel = SND_PCM_CHANNEL_CAPTURE;
579 if ((result = snd_pcm_plugin_status (stream->ca_pcm, &status)) < 0)
580 {
581 /* Should not fail but all we can do is continue */
582 PJ_LOG(4,(THIS_FILE, "capture: snd_pcm_plugin_status ret = %d",
583 result));
584 } else {
585 /* RIM say these are the errors that we should "prepare"
586 * after */
587 if (status.status == SND_PCM_STATUS_READY ||
588 status.status == SND_PCM_STATUS_OVERRUN ||
589 status.status == SND_PCM_STATUS_ERROR)
590 {
591 if (snd_pcm_plugin_prepare (stream->ca_pcm,
592 SND_PCM_CHANNEL_CAPTURE) < 0)
593 {
594 PJ_LOG (4,(THIS_FILE,
595 "overrun: capture channel prepare error"));
596 }
597 }
598 }
599 }
600
601 if (stream->quit)
602 break;
603
604 /* Write the capture audio data to PJMEDIA */
605 frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
606 frame.buf = (void *) buf;
607 frame.size = size;
608 frame.timestamp.u64 = tstamp.u64;
609 frame.bit_info = 0;
610
611 result = stream->ca_cb (user_data, &frame);
612 if (result != PJ_SUCCESS || stream->quit)
613 break;
614
615 tstamp.u64 += nframes;
616 }
617
618 flush_capture(stream);
619 close_capture_pcm(stream);
620 TRACE_((THIS_FILE, "ca_thread_func: Stopped"));
621
622 return PJ_SUCCESS;
623}
624
625/* Audio routing, speaker/headset */
626static pj_status_t bb10_initialize_playback_ctrl(struct bb10_stream *stream,
627 bool speaker)
628{
629 /* Although the play and capture have audio manager handles, audio routing
630 * requires a separate handle
631 */
632 int ret = PJ_SUCCESS;
633
634 if (stream->pb_ctrl_audio_manager_handle == 0) {
635 /* lazy init an audio manager handle */
636 ret = audio_manager_get_handle(AUDIO_TYPE_VOICE, 0, false,
637 &stream->pb_ctrl_audio_manager_handle);
638 if (ret != 0) {
639 TRACE_((THIS_FILE, "audio_manager_get_handle ret = %d",ret));
640 return PJMEDIA_EAUD_SYSERR;
641 }
642 }
643
644 /* Set for either speaker or earpiece */
645 if (speaker) {
646 ret = audio_manager_set_handle_type(
647 stream->pb_ctrl_audio_manager_handle,
648 AUDIO_TYPE_VIDEO_CHAT,
649 AUDIO_DEVICE_DEFAULT,
650 AUDIO_DEVICE_DEFAULT);
651 } else {
652 ret = audio_manager_set_handle_type(
653 stream->pb_ctrl_audio_manager_handle,
654 AUDIO_TYPE_VOICE,
655 AUDIO_DEVICE_DEFAULT,
656 AUDIO_DEVICE_DEFAULT);
657 }
658
659 if (ret != 0) {
Alexandre Lision0e143012014-01-22 11:02:46 -0500660 TRACE_((THIS_FILE, "audio_manager_set_handle_type error ret = %d",ret));
Tristan Matthews0a329cc2013-07-17 13:20:14 -0400661 return PJMEDIA_EAUD_SYSERR;
662 }else{
663 return PJ_SUCCESS;
664 }
665}
666
667static int32_t get_alsa_pcm_fmt(const pjmedia_aud_param *param)
668{
669 switch (param->bits_per_sample) {
670 case 8:
671 return SND_PCM_SFMT_S8;
672 case 16:
673 return SND_PCM_SFMT_S16_LE;
674 case 24:
675 return SND_PCM_SFMT_S24_LE;
676 case 32:
677 return SND_PCM_SFMT_S32_LE;
678 default:
679 PJ_ASSERT_RETURN(!"Unsupported bits_per_frame", SND_PCM_SFMT_S16_LE);
680 }
681}
682
683static pj_status_t bb10_open_playback (struct bb10_stream *stream,
684 const pjmedia_aud_param *param)
685{
686 int ret = 0;
687 snd_pcm_channel_info_t pi;
688 snd_pcm_channel_setup_t setup;
689 snd_mixer_group_t group;
690 snd_pcm_channel_params_t pp;
691 unsigned int rate;
692 unsigned long tmp_buf_size;
693
694 if (param->play_id < 0 || param->play_id >= stream->af->dev_cnt) {
695 return PJMEDIA_EAUD_INVDEV;
696 }
697
698 PJ_ASSERT_RETURN(param->bits_per_sample == 16, PJMEDIA_EAUD_SAMPFORMAT);
699
700 /* Use the bb10 audio manager API to open as opposed to QNX core audio
701 * Echo cancellation built in
702 */
703 if ((ret = audio_manager_snd_pcm_open_name(
704 AUDIO_TYPE_VIDEO_CHAT,
705 &stream->pb_pcm, &stream->pb_audio_manager_handle,
706 (char*)"voice",
707 SND_PCM_OPEN_PLAYBACK)) < 0)
708 {
709 TRACE_((THIS_FILE, "audio_manager_snd_pcm_open_name ret = %d", ret));
710 return PJMEDIA_EAUD_SYSERR;
711 }
712
713 /* Required call from January 2013 gold OS release */
714 snd_pcm_plugin_set_disable(stream->pb_pcm, PLUGIN_DISABLE_MMAP);
715
716 /* Required call from January 2013 gold OS release */
717 snd_pcm_plugin_set_enable(stream->pb_pcm, PLUGIN_ROUTING);
718
719 memset (&pi, 0, sizeof (pi));
720 pi.channel = SND_PCM_CHANNEL_PLAYBACK;
721 if ((ret = snd_pcm_plugin_info (stream->pb_pcm, &pi)) < 0) {
722 TRACE_((THIS_FILE, "snd_pcm_plugin_info ret = %d", ret));
723 return PJMEDIA_EAUD_SYSERR;
724 }
725
726 memset (&pp, 0, sizeof (pp));
727
728 /* Request VoIP compatible capabilities */
729 pp.mode = SND_PCM_MODE_BLOCK;
730 pp.channel = SND_PCM_CHANNEL_PLAYBACK;
731 pp.start_mode = SND_PCM_START_FULL;
732 pp.stop_mode = SND_PCM_STOP_ROLLOVER;
733 pp.buf.block.frag_size = param->samples_per_frame * param->bits_per_sample / 8;
734 /* RIM recommends maximum of 5 */
735 pp.buf.block.frags_max = 5;
736 pp.buf.block.frags_min = 1;
737 pp.format.interleave = 1;
738 pp.format.rate = param->clock_rate;
739 pp.format.voices = param->channel_count;
740 pp.format.format = get_alsa_pcm_fmt(param);
741
742 /* Make the calls as per the wave sample */
743 if ((ret = snd_pcm_plugin_params (stream->pb_pcm, &pp)) < 0) {
744 TRACE_((THIS_FILE, "snd_pcm_plugin_params ret = %d", ret));
745 return PJMEDIA_EAUD_SYSERR;
746 }
747
748 memset (&setup, 0, sizeof (setup));
749 memset (&group, 0, sizeof (group));
750 setup.channel = SND_PCM_CHANNEL_PLAYBACK;
751 setup.mixer_gid = &group.gid;
752
753 if ((ret = snd_pcm_plugin_setup (stream->pb_pcm, &setup)) < 0) {
754 TRACE_((THIS_FILE, "snd_pcm_plugin_setup ret = %d", ret));
755 return PJMEDIA_EAUD_SYSERR;
756 }
757
758 if (group.gid.name[0] == 0) {
759 return PJMEDIA_EAUD_SYSERR;
760 }
761
762 rate = param->clock_rate;
763 /* Set the sound device buffer size and latency */
764 if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) {
765 tmp_buf_size = rate * param->output_latency_ms / 1000;
766 } else {
767 tmp_buf_size = rate * PJMEDIA_SND_DEFAULT_PLAY_LATENCY / 1000;
768 }
769 /* Set period size to samples_per_frame frames. */
770 stream->pb_frames = param->samples_per_frame / param->channel_count;
771 stream->param.output_latency_ms = tmp_buf_size * 1000 / rate;
772
773 /* Set our buffer */
774 stream->pb_buf_size = stream->pb_frames * param->channel_count *
775 param->bits_per_sample / 8;
776 stream->pb_buf = (char *) pj_pool_alloc(stream->pool, stream->pb_buf_size);
777
778 TRACE_((THIS_FILE, "bb10_open_playback: pb_frames = %d clock = %d",
779 stream->pb_frames, param->clock_rate));
780
781 return PJ_SUCCESS;
782}
783
784static pj_status_t bb10_open_capture (struct bb10_stream *stream,
785 const pjmedia_aud_param *param)
786{
787 int ret = 0;
788 unsigned int rate;
789 unsigned long tmp_buf_size;
790 int frame_size;
791 snd_pcm_channel_info_t pi;
792 snd_mixer_group_t group;
793 snd_pcm_channel_params_t pp;
794 snd_pcm_channel_setup_t setup;
795
796 if (param->rec_id < 0 || param->rec_id >= stream->af->dev_cnt)
797 return PJMEDIA_EAUD_INVDEV;
798
799 PJ_ASSERT_RETURN(param->bits_per_sample == 16, PJMEDIA_EAUD_SAMPFORMAT);
800
801 if ((ret=audio_manager_snd_pcm_open_name(AUDIO_TYPE_VIDEO_CHAT,
802 &stream->ca_pcm,
803 &stream->ca_audio_manager_handle,
804 (char*)"voice",
805 SND_PCM_OPEN_CAPTURE)) < 0)
806 {
807 TRACE_((THIS_FILE, "audio_manager_snd_pcm_open_name ret = %d", ret));
808 return PJMEDIA_EAUD_SYSERR;
809 }
810 /* Required call from January 2013 gold OS release */
811 snd_pcm_plugin_set_disable (stream->ca_pcm, PLUGIN_DISABLE_MMAP);
812
813 /* Required call from January 2013 gold OS release */
814 snd_pcm_plugin_set_enable(stream->ca_pcm, PLUGIN_ROUTING);
815
816 /* sample reads the capabilities of the capture */
817 memset (&pi, 0, sizeof (pi));
818 pi.channel = SND_PCM_CHANNEL_CAPTURE;
819 if ((ret = snd_pcm_plugin_info (stream->ca_pcm, &pi)) < 0) {
820 TRACE_((THIS_FILE, "snd_pcm_plugin_info ret = %d", ret));
821 return PJMEDIA_EAUD_SYSERR;
822 }
823
824 /* Request the VoIP parameters
825 * These parameters are different to waverec sample
826 */
827 memset (&pp, 0, sizeof (pp));
828 /* Blocking read */
829 pp.mode = SND_PCM_MODE_BLOCK;
830 pp.channel = SND_PCM_CHANNEL_CAPTURE;
831 pp.start_mode = SND_PCM_START_FULL;
832 /* Auto-recover from errors */
833 pp.stop_mode = SND_PCM_STOP_ROLLOVER;
834 pp.buf.block.frag_size = param->samples_per_frame * param->bits_per_sample / 8;
835 /* From January 2013 gold OS release. RIM recommend these for capture */
836 pp.buf.block.frags_max = 3;
837 pp.buf.block.frags_min = 1;
838 pp.format.interleave = 1;
839 pp.format.rate = param->clock_rate;
840 pp.format.voices = param->channel_count;
841 pp.format.format = get_alsa_pcm_fmt(param);
842
843 /* make the request */
844 if ((ret = snd_pcm_plugin_params (stream->ca_pcm, &pp)) < 0) {
845 TRACE_((THIS_FILE, "snd_pcm_plugin_params ret = %d", ret));
846 return PJMEDIA_EAUD_SYSERR;
847 }
848
849 /* Again based on the sample */
850 memset (&setup, 0, sizeof (setup));
851 memset (&group, 0, sizeof (group));
852 setup.channel = SND_PCM_CHANNEL_CAPTURE;
853 setup.mixer_gid = &group.gid;
854 if ((ret = snd_pcm_plugin_setup (stream->ca_pcm, &setup)) < 0) {
855 TRACE_((THIS_FILE, "snd_pcm_plugin_setup ret = %d", ret));
856 return PJMEDIA_EAUD_SYSERR;
857 }
858
859 frame_size = setup.buf.block.frag_size;
860
861 if (group.gid.name[0] == 0) {
862 } else {
863 }
864
865 frame_size = setup.buf.block.frag_size;
866
867 /* Set clock rate */
868 rate = param->clock_rate;
869 stream->ca_frames = (unsigned long) param->samples_per_frame /
870 param->channel_count;
871
872 /* Set the sound device buffer size and latency */
873 if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) {
874 tmp_buf_size = rate * param->input_latency_ms / 1000;
875 } else {
876 tmp_buf_size = rate * PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000;
877 }
878
879 stream->param.input_latency_ms = tmp_buf_size * 1000 / rate;
880
881 /* Set our buffer */
882 stream->ca_buf_size = stream->ca_frames * param->channel_count *
883 param->bits_per_sample / 8;
884 stream->ca_buf = (char *)pj_pool_alloc (stream->pool, stream->ca_buf_size);
885
886 TRACE_((THIS_FILE, "bb10_open_capture: ca_frames = %d clock = %d",
887 stream->ca_frames, param->clock_rate));
888
889 return PJ_SUCCESS;
890}
891
892
893/* API: create stream */
894static pj_status_t bb10_factory_create_stream(pjmedia_aud_dev_factory *f,
895 const pjmedia_aud_param *param,
896 pjmedia_aud_rec_cb rec_cb,
897 pjmedia_aud_play_cb play_cb,
898 void *user_data,
899 pjmedia_aud_stream **p_strm)
900{
901 struct bb10_factory *af = (struct bb10_factory*)f;
902 pj_status_t status;
903 pj_pool_t* pool;
904 struct bb10_stream* stream;
905
906 pool = pj_pool_create (af->pf, "bb10%p", 1024, 1024, NULL);
907 if (!pool)
908 return PJ_ENOMEM;
909
910 /* Allocate and initialize comon stream data */
911 stream = PJ_POOL_ZALLOC_T (pool, struct bb10_stream);
912 stream->base.op = &bb10_stream_op;
913 stream->pool = pool;
914 stream->af = af;
915 stream->user_data = user_data;
916 stream->pb_cb = play_cb;
917 stream->ca_cb = rec_cb;
918 stream->quit = 0;
919 pj_memcpy(&stream->param, param, sizeof(*param));
920
921 /* Init playback */
922 if (param->dir & PJMEDIA_DIR_PLAYBACK) {
923 status = bb10_open_playback (stream, param);
924 if (status != PJ_SUCCESS) {
925 pj_pool_release (pool);
926 return status;
927 }
928 }
929
930 /* Init capture */
931 if (param->dir & PJMEDIA_DIR_CAPTURE) {
932 status = bb10_open_capture (stream, param);
933 if (status != PJ_SUCCESS) {
934 if (param->dir & PJMEDIA_DIR_PLAYBACK) {
935 close_play_pcm(stream);
936 }
937 pj_pool_release (pool);
938 return status;
939 }
940 }
941
942 /* Set the audio routing ONLY if app explicitly asks one */
943 if ((param->dir & PJMEDIA_DIR_PLAYBACK) &&
944 (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE))
945 {
946 status = bb10_stream_set_cap(&stream->base,
947 PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
948 &param->output_route);
949 if (status != PJ_SUCCESS) {
950 TRACE_((THIS_FILE, "Error setting output route"));
951 bb10_stream_destroy(&stream->base);
952 return status;
953 }
954 } else {
955 /* Legacy behavior: if none specified, set to speaker */
956 status = bb10_initialize_playback_ctrl(stream, false);
957 }
958
959 *p_strm = &stream->base;
960 return PJ_SUCCESS;
961}
962
963
964/*
965 * API: get running parameter
966 * based on ALSA template
967 */
968static pj_status_t bb10_stream_get_param(pjmedia_aud_stream *s,
969 pjmedia_aud_param *pi)
970{
971 struct bb10_stream *stream = (struct bb10_stream*)s;
972
973 PJ_ASSERT_RETURN(s && pi, PJ_EINVAL);
974
975 pj_memcpy(pi, &stream->param, sizeof(*pi));
976
977 return PJ_SUCCESS;
978}
979
980
981/*
982 * API: get capability
983 * based on ALSA template
984*/
985static pj_status_t bb10_stream_get_cap(pjmedia_aud_stream *s,
986 pjmedia_aud_dev_cap cap,
987 void *pval)
988{
989 struct bb10_stream *stream = (struct bb10_stream*)s;
990
991 PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
992
993 if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY &&
994 (stream->param.dir & PJMEDIA_DIR_CAPTURE))
995 {
996 /* Recording latency */
997 *(unsigned*)pval = stream->param.input_latency_ms;
998 return PJ_SUCCESS;
999
1000 } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY &&
1001 (stream->param.dir & PJMEDIA_DIR_PLAYBACK))
1002 {
1003 /* Playback latency */
1004 *(unsigned*)pval = stream->param.output_latency_ms;
1005 return PJ_SUCCESS;
1006
1007 } else if (cap==PJMEDIA_AUD_DEV_CAP_EC &&
1008 (stream->param.dir & PJMEDIA_DIR_CAPTURE))
1009 {
1010 /* EC is enablied implicitly by opening "voice" device */
1011 *(pj_bool_t*)pval = PJ_TRUE;
1012 return PJ_SUCCESS;
1013 } else {
1014 return PJMEDIA_EAUD_INVCAP;
1015 }
1016}
1017
1018
1019/*
1020 * API: set capability
1021 * Currently just supporting toggle between speaker and earpiece
1022 */
1023static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm,
1024 pjmedia_aud_dev_cap cap,
1025 const void *value)
1026{
1027
1028 struct bb10_stream *stream = (struct bb10_stream*)strm;
1029
1030 if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE &&
1031 (stream->param.dir & PJMEDIA_DIR_PLAYBACK))
1032 {
1033 pjmedia_aud_dev_route route;
1034 pj_status_t ret;
1035
1036 PJ_ASSERT_RETURN(value, PJ_EINVAL);
1037
1038 route = *((pjmedia_aud_dev_route*)value);
1039 /* Use the initialization function which lazy-inits the
1040 * handle for routing
1041 */
1042 if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER) {
1043 ret = bb10_initialize_playback_ctrl(stream,true);
1044 } else {
1045 ret = bb10_initialize_playback_ctrl(stream,false);
1046 }
1047 return ret;
1048
1049 } else if (cap==PJMEDIA_AUD_DEV_CAP_EC &&
1050 (stream->param.dir & PJMEDIA_DIR_CAPTURE))
1051 {
1052 /* EC is always enabled. Silently ignore the request */
1053 return PJ_SUCCESS;
1054 }
1055
1056 TRACE_((THIS_FILE,"bb10_stream_set_cap() = PJMEDIA_EAUD_INVCAP"));
1057 return PJMEDIA_EAUD_INVCAP;
1058}
1059
1060
1061/* API: start stream */
1062static pj_status_t bb10_stream_start (pjmedia_aud_stream *s)
1063{
1064 struct bb10_stream *stream = (struct bb10_stream*)s;
1065 pj_status_t status = PJ_SUCCESS;
1066
1067 stream->quit = 0;
1068 if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) {
1069 status = pj_thread_create (stream->pool,
1070 "bb10sound_playback",
1071 pb_thread_func,
1072 stream,
1073 0,
1074 0,
1075 &stream->pb_thread);
1076 if (status != PJ_SUCCESS)
1077 return status;
1078 }
1079
1080 if (stream->param.dir & PJMEDIA_DIR_CAPTURE) {
1081 status = pj_thread_create (stream->pool,
1082 "bb10sound_playback",
1083 ca_thread_func,
1084 stream,
1085 0,
1086 0,
1087 &stream->ca_thread);
1088 if (status != PJ_SUCCESS) {
1089 stream->quit = PJ_TRUE;
1090 pj_thread_join(stream->pb_thread);
1091 pj_thread_destroy(stream->pb_thread);
1092 stream->pb_thread = NULL;
1093 }
1094 }
1095
1096 return status;
1097}
1098
1099
1100/* API: stop stream */
1101static pj_status_t bb10_stream_stop (pjmedia_aud_stream *s)
1102{
1103 struct bb10_stream *stream = (struct bb10_stream*)s;
1104
1105 stream->quit = 1;
1106 TRACE_((THIS_FILE,"bb10_stream_stop()"));
1107
1108 if (stream->pb_thread) {
1109 pj_thread_join (stream->pb_thread);
1110 pj_thread_destroy(stream->pb_thread);
1111 stream->pb_thread = NULL;
1112 }
1113
1114 if (stream->ca_thread) {
1115 pj_thread_join (stream->ca_thread);
1116 pj_thread_destroy(stream->ca_thread);
1117 stream->ca_thread = NULL;
1118 }
1119
1120 return PJ_SUCCESS;
1121}
1122
1123static pj_status_t bb10_stream_destroy (pjmedia_aud_stream *s)
1124{
1125 struct bb10_stream *stream = (struct bb10_stream*)s;
1126
1127 TRACE_((THIS_FILE,"bb10_stream_destroy()"));
1128
1129 bb10_stream_stop (s);
1130
1131 pj_pool_release (stream->pool);
1132
1133 return PJ_SUCCESS;
1134}
1135
1136#endif /* PJMEDIA_AUDIO_DEV_HAS_BB10 */