| /* $Id: bb10_dev.c 4512 2013-04-30 09:00:36Z bennylp $ */ |
| /* |
| * Copyright (C) 2008-2012 Teluu Inc. (http://www.teluu.com) |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| /* |
| * This is the implementation of BlackBerry 10 (BB10) audio device. |
| * Original code was kindly donated by Truphone Ltd. (http://www.truphone.com) |
| * The key methods here are bb10_capture_open, bb10_play_open together |
| * with the capture and play threads ca_thread_func and pb_thread_func |
| */ |
| |
| #include <pjmedia_audiodev.h> |
| #include <pj/assert.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/pool.h> |
| #include <pjmedia/errno.h> |
| |
| #if defined(PJMEDIA_AUDIO_DEV_HAS_BB10) && PJMEDIA_AUDIO_DEV_HAS_BB10 != 0 |
| |
| #ifndef PJ_BBSDK_VER |
| /* Format: 0xMMNNRR: MM: major, NN: minor, RR: revision */ |
| # define PJ_BBSDK_VER 0x100006 |
| #endif |
| |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <sys/select.h> |
| #include <pthread.h> |
| #include <errno.h> |
| #include <sys/asoundlib.h> |
| #if PJ_BBSDK_VER >= 0x100006 |
| #include <audio/audio_manager_routing.h> |
| #endif |
| |
| |
| #define THIS_FILE "bb10_dev.c" |
| #define BB10_DEVICE_NAME "plughw:%d,%d" |
| |
| |
| /* Set to 1 to enable tracing */ |
| #if 1 |
| # define TRACE_(expr) PJ_LOG(4,expr) |
| #else |
| # define TRACE_(expr) |
| #endif |
| |
| /* |
| * Factory prototypes |
| */ |
| static pj_status_t bb10_factory_init(pjmedia_aud_dev_factory *f); |
| static pj_status_t bb10_factory_destroy(pjmedia_aud_dev_factory *f); |
| static pj_status_t bb10_factory_refresh(pjmedia_aud_dev_factory *f); |
| static unsigned bb10_factory_get_dev_count(pjmedia_aud_dev_factory *f); |
| static pj_status_t bb10_factory_get_dev_info(pjmedia_aud_dev_factory *f, |
| unsigned index, |
| pjmedia_aud_dev_info *info); |
| static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f, |
| unsigned index, |
| pjmedia_aud_param *param); |
| static pj_status_t bb10_factory_create_stream(pjmedia_aud_dev_factory *f, |
| const pjmedia_aud_param *param, |
| pjmedia_aud_rec_cb rec_cb, |
| pjmedia_aud_play_cb play_cb, |
| void *user_data, |
| pjmedia_aud_stream **p_strm); |
| |
| /* |
| * Stream prototypes |
| */ |
| static pj_status_t bb10_stream_get_param(pjmedia_aud_stream *strm, |
| pjmedia_aud_param *param); |
| static pj_status_t bb10_stream_get_cap(pjmedia_aud_stream *strm, |
| pjmedia_aud_dev_cap cap, |
| void *value); |
| static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm, |
| pjmedia_aud_dev_cap cap, |
| const void *value); |
| static pj_status_t bb10_stream_start(pjmedia_aud_stream *strm); |
| static pj_status_t bb10_stream_stop(pjmedia_aud_stream *strm); |
| static pj_status_t bb10_stream_destroy(pjmedia_aud_stream *strm); |
| |
| |
| struct bb10_factory |
| { |
| pjmedia_aud_dev_factory base; |
| pj_pool_factory *pf; |
| pj_pool_t *pool; |
| pj_pool_t *base_pool; |
| unsigned dev_cnt; |
| pjmedia_aud_dev_info devs[1]; |
| }; |
| |
| struct bb10_stream |
| { |
| pjmedia_aud_stream base; |
| |
| /* Common */ |
| pj_pool_t *pool; |
| struct bb10_factory *af; |
| void *user_data; |
| pjmedia_aud_param param; /* Running parameter */ |
| int rec_id; /* Capture device id */ |
| int quit; |
| |
| /* Playback */ |
| unsigned int pb_ctrl_audio_manager_handle; |
| snd_pcm_t *pb_pcm; |
| unsigned int pb_audio_manager_handle; |
| unsigned long pb_frames; /* samples_per_frame */ |
| pjmedia_aud_play_cb pb_cb; |
| unsigned pb_buf_size; |
| char *pb_buf; |
| pj_thread_t *pb_thread; |
| |
| /* Capture */ |
| snd_pcm_t *ca_pcm; |
| unsigned int ca_audio_manager_handle; |
| unsigned long ca_frames; /* samples_per_frame */ |
| pjmedia_aud_rec_cb ca_cb; |
| unsigned ca_buf_size; |
| char *ca_buf; |
| pj_thread_t *ca_thread; |
| }; |
| |
| static pjmedia_aud_dev_factory_op bb10_factory_op = |
| { |
| &bb10_factory_init, |
| &bb10_factory_destroy, |
| &bb10_factory_get_dev_count, |
| &bb10_factory_get_dev_info, |
| &bb10_factory_default_param, |
| &bb10_factory_create_stream, |
| &bb10_factory_refresh |
| }; |
| |
| static pjmedia_aud_stream_op bb10_stream_op = |
| { |
| &bb10_stream_get_param, |
| &bb10_stream_get_cap, |
| &bb10_stream_set_cap, |
| &bb10_stream_start, |
| &bb10_stream_stop, |
| &bb10_stream_destroy |
| }; |
| |
| /* |
| * BB10 - tests loads the audio units and sets up the driver structure |
| */ |
| static pj_status_t bb10_add_dev (struct bb10_factory *af) |
| { |
| pjmedia_aud_dev_info *adi; |
| int pb_result, ca_result; |
| unsigned int handle; |
| snd_pcm_t *pcm_handle; |
| |
| if (af->dev_cnt >= PJ_ARRAY_SIZE(af->devs)) |
| return PJ_ETOOMANY; |
| |
| adi = &af->devs[af->dev_cnt]; |
| |
| TRACE_((THIS_FILE, "bb10_add_dev Enter")); |
| |
| if ((pb_result = audio_manager_snd_pcm_open_name(AUDIO_TYPE_VIDEO_CHAT, |
| &pcm_handle, |
| &handle, |
| (char*)"voice", |
| SND_PCM_OPEN_PLAYBACK)) |
| >= 0) |
| { |
| snd_pcm_close (pcm_handle); |
| audio_manager_free_handle(handle); |
| } else { |
| TRACE_((THIS_FILE, "Try to open the device for playback - failure")); |
| } |
| |
| if ((ca_result = audio_manager_snd_pcm_open_name(AUDIO_TYPE_VIDEO_CHAT, |
| &pcm_handle, |
| &handle, |
| (char*)"voice", |
| SND_PCM_OPEN_CAPTURE)) |
| >= 0) |
| { |
| snd_pcm_close (pcm_handle); |
| audio_manager_free_handle(handle); |
| |
| } else { |
| TRACE_((THIS_FILE, "Try to open the device for capture - failure")); |
| } |
| |
| if (pb_result < 0 && ca_result < 0) { |
| TRACE_((THIS_FILE, "Unable to open sound device", "preferred")); |
| return PJMEDIA_EAUD_NODEV; |
| } |
| |
| /* Reset device info */ |
| pj_bzero(adi, sizeof(*adi)); |
| |
| /* Set device name */ |
| strcpy(adi->name, "preferred"); |
| |
| /* Check the number of playback channels */ |
| adi->output_count = (pb_result >= 0) ? 1 : 0; |
| |
| /* Check the number of capture channels */ |
| adi->input_count = (ca_result >= 0) ? 1 : 0; |
| |
| /* Set the default sample rate */ |
| adi->default_samples_per_sec = 8000; |
| |
| /* Driver name */ |
| strcpy(adi->driver, "BB10"); |
| |
| ++af->dev_cnt; |
| |
| PJ_LOG (4,(THIS_FILE, "Added sound device %s", adi->name)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* Create BB10 audio driver. */ |
| pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf) |
| { |
| struct bb10_factory *af; |
| pj_pool_t *pool; |
| |
| pool = pj_pool_create(pf, "bb10_aud_base", 256, 256, NULL); |
| af = PJ_POOL_ZALLOC_T(pool, struct bb10_factory); |
| af->pf = pf; |
| af->base_pool = pool; |
| af->base.op = &bb10_factory_op; |
| |
| return &af->base; |
| } |
| |
| |
| /* API: init factory */ |
| static pj_status_t bb10_factory_init(pjmedia_aud_dev_factory *f) |
| { |
| pj_status_t status; |
| |
| status = bb10_factory_refresh(f); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| PJ_LOG(4,(THIS_FILE, "BB10 initialized")); |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* API: destroy factory */ |
| static pj_status_t bb10_factory_destroy(pjmedia_aud_dev_factory *f) |
| { |
| struct bb10_factory *af = (struct bb10_factory*)f; |
| |
| if (af->pool) { |
| TRACE_((THIS_FILE, "bb10_factory_destroy() - 1")); |
| pj_pool_release(af->pool); |
| } |
| |
| if (af->base_pool) { |
| pj_pool_t *pool = af->base_pool; |
| af->base_pool = NULL; |
| TRACE_((THIS_FILE, "bb10_factory_destroy() - 2")); |
| pj_pool_release(pool); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* API: refresh the device list */ |
| static pj_status_t bb10_factory_refresh(pjmedia_aud_dev_factory *f) |
| { |
| struct bb10_factory *af = (struct bb10_factory*)f; |
| int err; |
| |
| TRACE_((THIS_FILE, "bb10_factory_refresh()")); |
| |
| if (af->pool != NULL) { |
| pj_pool_release(af->pool); |
| af->pool = NULL; |
| } |
| |
| af->pool = pj_pool_create(af->pf, "bb10_aud", 256, 256, NULL); |
| af->dev_cnt = 0; |
| |
| err = bb10_add_dev(af); |
| |
| PJ_LOG(4,(THIS_FILE, "BB10 driver found %d devices", af->dev_cnt)); |
| |
| return err; |
| } |
| |
| |
| /* API: get device count */ |
| static unsigned bb10_factory_get_dev_count(pjmedia_aud_dev_factory *f) |
| { |
| struct bb10_factory *af = (struct bb10_factory*)f; |
| return af->dev_cnt; |
| } |
| |
| |
| /* API: get device info */ |
| static pj_status_t bb10_factory_get_dev_info(pjmedia_aud_dev_factory *f, |
| unsigned index, |
| pjmedia_aud_dev_info *info) |
| { |
| struct bb10_factory *af = (struct bb10_factory*)f; |
| |
| PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL); |
| |
| pj_memcpy(info, &af->devs[index], sizeof(*info)); |
| info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | |
| PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY | |
| PJMEDIA_AUD_DEV_CAP_EC; |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* API: create default parameter */ |
| static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f, |
| unsigned index, |
| pjmedia_aud_param *param) |
| { |
| struct bb10_factory *af = (struct bb10_factory*)f; |
| pjmedia_aud_dev_info *adi; |
| |
| PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL); |
| |
| adi = &af->devs[index]; |
| |
| pj_bzero(param, sizeof(*param)); |
| if (adi->input_count && adi->output_count) { |
| param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; |
| param->rec_id = index; |
| param->play_id = index; |
| } else if (adi->input_count) { |
| param->dir = PJMEDIA_DIR_CAPTURE; |
| param->rec_id = index; |
| param->play_id = PJMEDIA_AUD_INVALID_DEV; |
| } else if (adi->output_count) { |
| param->dir = PJMEDIA_DIR_PLAYBACK; |
| param->play_id = index; |
| param->rec_id = PJMEDIA_AUD_INVALID_DEV; |
| } else { |
| return PJMEDIA_EAUD_INVDEV; |
| } |
| |
| param->clock_rate = adi->default_samples_per_sec; |
| param->channel_count = 1; |
| param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000; |
| param->bits_per_sample = 16; |
| param->flags = adi->caps; |
| param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; |
| param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; |
| |
| TRACE_((THIS_FILE, "bb10_factory_default_param clock = %d flags = %d" |
| " spf = %d", param->clock_rate, param->flags, |
| param->samples_per_frame)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| static void close_play_pcm(struct bb10_stream *stream) |
| { |
| if (stream != NULL && stream->pb_pcm != NULL) { |
| snd_pcm_close(stream->pb_pcm); |
| stream->pb_pcm = NULL; |
| |
| if (stream->pb_audio_manager_handle != 0) { |
| audio_manager_free_handle(stream->pb_audio_manager_handle); |
| stream->pb_audio_manager_handle = 0; |
| } |
| |
| if (stream->pb_ctrl_audio_manager_handle != 0) { |
| audio_manager_free_handle(stream->pb_ctrl_audio_manager_handle); |
| stream->pb_ctrl_audio_manager_handle = 0; |
| } |
| } |
| } |
| |
| static void flush_play(struct bb10_stream *stream) |
| { |
| if (stream != NULL && stream->pb_pcm != NULL) { |
| snd_pcm_plugin_flush (stream->pb_pcm, SND_PCM_CHANNEL_PLAYBACK); |
| } |
| } |
| |
| static void close_capture_pcm(struct bb10_stream *stream) |
| { |
| if (stream != NULL && stream->ca_pcm != NULL) { |
| snd_pcm_close(stream->ca_pcm); |
| stream->ca_pcm = NULL; |
| |
| if (stream->ca_audio_manager_handle != 0) { |
| audio_manager_free_handle(stream->ca_audio_manager_handle); |
| stream->ca_audio_manager_handle = 0; |
| } |
| } |
| } |
| |
| static void flush_capture(struct bb10_stream *stream) |
| { |
| if (stream != NULL && stream->ca_pcm != NULL) { |
| snd_pcm_plugin_flush (stream->ca_pcm, SND_PCM_CHANNEL_CAPTURE); |
| } |
| } |
| |
| |
| /** |
| * Play audio received from PJMEDIA |
| */ |
| static int pb_thread_func (void *arg) |
| { |
| struct bb10_stream* stream = (struct bb10_stream *) arg; |
| int size = stream->pb_buf_size; |
| unsigned long nframes = stream->pb_frames; |
| void *user_data = stream->user_data; |
| char *buf = stream->pb_buf; |
| pj_timestamp tstamp; |
| int result = 0; |
| int policy; |
| struct sched_param param; |
| |
| TRACE_((THIS_FILE, "pb_thread_func: size = %d ", size)); |
| |
| if (pthread_getschedparam(pthread_self(), &policy, ¶m) == 0) { |
| param.sched_priority = 18; |
| pthread_setschedparam (pthread_self(), policy, ¶m); |
| } |
| |
| pj_bzero (buf, size); |
| tstamp.u64 = 0; |
| |
| /* Do the final initialization now the thread has started. */ |
| if ((result = snd_pcm_plugin_prepare(stream->pb_pcm, |
| SND_PCM_CHANNEL_PLAYBACK)) < 0) |
| { |
| close_play_pcm(stream); |
| TRACE_((THIS_FILE, "pb_thread_func failed prepare = %d", result)); |
| return PJ_SUCCESS; |
| } |
| |
| while (!stream->quit) { |
| pjmedia_frame frame; |
| |
| frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
| /* pointer to buffer filled by PJMEDIA */ |
| frame.buf = buf; |
| frame.size = size; |
| frame.timestamp.u64 = tstamp.u64; |
| frame.bit_info = 0; |
| |
| /* Read the audio from pjmedia */ |
| result = stream->pb_cb (user_data, &frame); |
| if (result != PJ_SUCCESS || stream->quit) |
| break; |
| |
| if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) |
| pj_bzero (buf, size); |
| |
| /* Write 640 to play unit */ |
| result = snd_pcm_plugin_write(stream->pb_pcm,buf,size); |
| if (result != size || result < 0) { |
| /* either the write to output device has failed or not the |
| * full amount of bytes have been written. This usually happens |
| * when audio routing is being changed by another thread |
| * Use a status variable for reading the error |
| */ |
| snd_pcm_channel_status_t status; |
| status.channel = SND_PCM_CHANNEL_PLAYBACK; |
| if (snd_pcm_plugin_status (stream->pb_pcm, &status) < 0) { |
| /* Call has failed nothing we can do except log and |
| * continue */ |
| PJ_LOG(4,(THIS_FILE, |
| "underrun: playback channel status error")); |
| } else { |
| /* The status of the error has been read |
| * RIM say these are expected so we can "re-prepare" the stream |
| */ |
| PJ_LOG(4,(THIS_FILE,"PLAY thread ERROR status = %d", |
| status.status)); |
| if (status.status == SND_PCM_STATUS_READY || |
| status.status == SND_PCM_STATUS_UNDERRUN || |
| status.status == SND_PCM_STATUS_ERROR ) |
| { |
| if (snd_pcm_plugin_prepare (stream->pb_pcm, |
| SND_PCM_CHANNEL_PLAYBACK) < 0) |
| { |
| PJ_LOG(4,(THIS_FILE, |
| "underrun: playback channel prepare error")); |
| } |
| } |
| } |
| } |
| tstamp.u64 += nframes; |
| } |
| |
| flush_play(stream); |
| close_play_pcm(stream); |
| TRACE_((THIS_FILE, "pb_thread_func: Stopped")); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| |
| static int ca_thread_func (void *arg) |
| { |
| struct bb10_stream* stream = (struct bb10_stream *) arg; |
| int size = stream->ca_buf_size; |
| unsigned long nframes = stream->ca_frames; |
| void *user_data = stream->user_data; |
| /* Buffer to fill for PJMEDIA */ |
| char *buf = stream->ca_buf; |
| pj_timestamp tstamp; |
| int result; |
| int policy; |
| struct sched_param param; |
| |
| TRACE_((THIS_FILE, "ca_thread_func: size = %d ", size)); |
| |
| if (pthread_getschedparam(pthread_self(), &policy, ¶m) == 0) { |
| param.sched_priority = 18; |
| pthread_setschedparam (pthread_self(), policy, ¶m); |
| } |
| |
| pj_bzero (buf, size); |
| tstamp.u64 = 0; |
| |
| /* Final init now the thread has started */ |
| if ((result = snd_pcm_plugin_prepare (stream->ca_pcm, |
| SND_PCM_CHANNEL_CAPTURE)) < 0) |
| { |
| close_capture_pcm(stream); |
| TRACE_((THIS_FILE, "ca_thread_func failed prepare = %d", result)); |
| return PJ_SUCCESS; |
| } |
| |
| while (!stream->quit) { |
| pjmedia_frame frame; |
| |
| //pj_bzero (buf, size); |
| |
| /* read the input device */ |
| result = snd_pcm_plugin_read(stream->ca_pcm, buf,size); |
| if(result <0 || result != size) { |
| /* We expect result to be size (640) |
| * It's not so we have to read the status error and "prepare" |
| * the channel. This usually happens when output audio routing |
| * has been changed by another thread. |
| * We won't "continue", instead just do what we can and leave |
| * the end of the loop to write what's in the buffer. Not entirely |
| * correct but saves a potential underrun in PJMEDIA |
| */ |
| PJ_LOG (4,(THIS_FILE, |
| "snd_pcm_plugin_read ERROR read = %d required = %d", |
| result,size)); |
| snd_pcm_channel_status_t status; |
| status.channel = SND_PCM_CHANNEL_CAPTURE; |
| if ((result = snd_pcm_plugin_status (stream->ca_pcm, &status)) < 0) |
| { |
| /* Should not fail but all we can do is continue */ |
| PJ_LOG(4,(THIS_FILE, "capture: snd_pcm_plugin_status ret = %d", |
| result)); |
| } else { |
| /* RIM say these are the errors that we should "prepare" |
| * after */ |
| if (status.status == SND_PCM_STATUS_READY || |
| status.status == SND_PCM_STATUS_OVERRUN || |
| status.status == SND_PCM_STATUS_ERROR) |
| { |
| if (snd_pcm_plugin_prepare (stream->ca_pcm, |
| SND_PCM_CHANNEL_CAPTURE) < 0) |
| { |
| PJ_LOG (4,(THIS_FILE, |
| "overrun: capture channel prepare error")); |
| } |
| } |
| } |
| } |
| |
| if (stream->quit) |
| break; |
| |
| /* Write the capture audio data to PJMEDIA */ |
| frame.type = PJMEDIA_FRAME_TYPE_AUDIO; |
| frame.buf = (void *) buf; |
| frame.size = size; |
| frame.timestamp.u64 = tstamp.u64; |
| frame.bit_info = 0; |
| |
| result = stream->ca_cb (user_data, &frame); |
| if (result != PJ_SUCCESS || stream->quit) |
| break; |
| |
| tstamp.u64 += nframes; |
| } |
| |
| flush_capture(stream); |
| close_capture_pcm(stream); |
| TRACE_((THIS_FILE, "ca_thread_func: Stopped")); |
| |
| return PJ_SUCCESS; |
| } |
| |
| /* Audio routing, speaker/headset */ |
| static pj_status_t bb10_initialize_playback_ctrl(struct bb10_stream *stream, |
| bool speaker) |
| { |
| /* Although the play and capture have audio manager handles, audio routing |
| * requires a separate handle |
| */ |
| int ret = PJ_SUCCESS; |
| |
| if (stream->pb_ctrl_audio_manager_handle == 0) { |
| /* lazy init an audio manager handle */ |
| ret = audio_manager_get_handle(AUDIO_TYPE_VOICE, 0, false, |
| &stream->pb_ctrl_audio_manager_handle); |
| if (ret != 0) { |
| TRACE_((THIS_FILE, "audio_manager_get_handle ret = %d",ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| } |
| |
| /* Set for either speaker or earpiece */ |
| if (speaker) { |
| ret = audio_manager_set_handle_type( |
| stream->pb_ctrl_audio_manager_handle, |
| AUDIO_TYPE_VIDEO_CHAT, |
| AUDIO_DEVICE_DEFAULT, |
| AUDIO_DEVICE_DEFAULT); |
| } else { |
| ret = audio_manager_set_handle_type( |
| stream->pb_ctrl_audio_manager_handle, |
| AUDIO_TYPE_VOICE, |
| AUDIO_DEVICE_DEFAULT, |
| AUDIO_DEVICE_DEFAULT); |
| } |
| |
| if (ret != 0) { |
| return PJMEDIA_EAUD_SYSERR; |
| }else{ |
| return PJ_SUCCESS; |
| } |
| } |
| |
| static int32_t get_alsa_pcm_fmt(const pjmedia_aud_param *param) |
| { |
| switch (param->bits_per_sample) { |
| case 8: |
| return SND_PCM_SFMT_S8; |
| case 16: |
| return SND_PCM_SFMT_S16_LE; |
| case 24: |
| return SND_PCM_SFMT_S24_LE; |
| case 32: |
| return SND_PCM_SFMT_S32_LE; |
| default: |
| PJ_ASSERT_RETURN(!"Unsupported bits_per_frame", SND_PCM_SFMT_S16_LE); |
| } |
| } |
| |
| static pj_status_t bb10_open_playback (struct bb10_stream *stream, |
| const pjmedia_aud_param *param) |
| { |
| int ret = 0; |
| snd_pcm_channel_info_t pi; |
| snd_pcm_channel_setup_t setup; |
| snd_mixer_group_t group; |
| snd_pcm_channel_params_t pp; |
| unsigned int rate; |
| unsigned long tmp_buf_size; |
| |
| if (param->play_id < 0 || param->play_id >= stream->af->dev_cnt) { |
| return PJMEDIA_EAUD_INVDEV; |
| } |
| |
| PJ_ASSERT_RETURN(param->bits_per_sample == 16, PJMEDIA_EAUD_SAMPFORMAT); |
| |
| /* Use the bb10 audio manager API to open as opposed to QNX core audio |
| * Echo cancellation built in |
| */ |
| if ((ret = audio_manager_snd_pcm_open_name( |
| AUDIO_TYPE_VIDEO_CHAT, |
| &stream->pb_pcm, &stream->pb_audio_manager_handle, |
| (char*)"voice", |
| SND_PCM_OPEN_PLAYBACK)) < 0) |
| { |
| TRACE_((THIS_FILE, "audio_manager_snd_pcm_open_name ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| /* Required call from January 2013 gold OS release */ |
| snd_pcm_plugin_set_disable(stream->pb_pcm, PLUGIN_DISABLE_MMAP); |
| |
| /* Required call from January 2013 gold OS release */ |
| snd_pcm_plugin_set_enable(stream->pb_pcm, PLUGIN_ROUTING); |
| |
| memset (&pi, 0, sizeof (pi)); |
| pi.channel = SND_PCM_CHANNEL_PLAYBACK; |
| if ((ret = snd_pcm_plugin_info (stream->pb_pcm, &pi)) < 0) { |
| TRACE_((THIS_FILE, "snd_pcm_plugin_info ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| memset (&pp, 0, sizeof (pp)); |
| |
| /* Request VoIP compatible capabilities */ |
| pp.mode = SND_PCM_MODE_BLOCK; |
| pp.channel = SND_PCM_CHANNEL_PLAYBACK; |
| pp.start_mode = SND_PCM_START_FULL; |
| pp.stop_mode = SND_PCM_STOP_ROLLOVER; |
| pp.buf.block.frag_size = param->samples_per_frame * param->bits_per_sample / 8; |
| /* RIM recommends maximum of 5 */ |
| pp.buf.block.frags_max = 5; |
| pp.buf.block.frags_min = 1; |
| pp.format.interleave = 1; |
| pp.format.rate = param->clock_rate; |
| pp.format.voices = param->channel_count; |
| pp.format.format = get_alsa_pcm_fmt(param); |
| |
| /* Make the calls as per the wave sample */ |
| if ((ret = snd_pcm_plugin_params (stream->pb_pcm, &pp)) < 0) { |
| TRACE_((THIS_FILE, "snd_pcm_plugin_params ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| memset (&setup, 0, sizeof (setup)); |
| memset (&group, 0, sizeof (group)); |
| setup.channel = SND_PCM_CHANNEL_PLAYBACK; |
| setup.mixer_gid = &group.gid; |
| |
| if ((ret = snd_pcm_plugin_setup (stream->pb_pcm, &setup)) < 0) { |
| TRACE_((THIS_FILE, "snd_pcm_plugin_setup ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| if (group.gid.name[0] == 0) { |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| rate = param->clock_rate; |
| /* Set the sound device buffer size and latency */ |
| if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) { |
| tmp_buf_size = rate * param->output_latency_ms / 1000; |
| } else { |
| tmp_buf_size = rate * PJMEDIA_SND_DEFAULT_PLAY_LATENCY / 1000; |
| } |
| /* Set period size to samples_per_frame frames. */ |
| stream->pb_frames = param->samples_per_frame / param->channel_count; |
| stream->param.output_latency_ms = tmp_buf_size * 1000 / rate; |
| |
| /* Set our buffer */ |
| stream->pb_buf_size = stream->pb_frames * param->channel_count * |
| param->bits_per_sample / 8; |
| stream->pb_buf = (char *) pj_pool_alloc(stream->pool, stream->pb_buf_size); |
| |
| TRACE_((THIS_FILE, "bb10_open_playback: pb_frames = %d clock = %d", |
| stream->pb_frames, param->clock_rate)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t bb10_open_capture (struct bb10_stream *stream, |
| const pjmedia_aud_param *param) |
| { |
| int ret = 0; |
| unsigned int rate; |
| unsigned long tmp_buf_size; |
| int frame_size; |
| snd_pcm_channel_info_t pi; |
| snd_mixer_group_t group; |
| snd_pcm_channel_params_t pp; |
| snd_pcm_channel_setup_t setup; |
| |
| if (param->rec_id < 0 || param->rec_id >= stream->af->dev_cnt) |
| return PJMEDIA_EAUD_INVDEV; |
| |
| PJ_ASSERT_RETURN(param->bits_per_sample == 16, PJMEDIA_EAUD_SAMPFORMAT); |
| |
| if ((ret=audio_manager_snd_pcm_open_name(AUDIO_TYPE_VIDEO_CHAT, |
| &stream->ca_pcm, |
| &stream->ca_audio_manager_handle, |
| (char*)"voice", |
| SND_PCM_OPEN_CAPTURE)) < 0) |
| { |
| TRACE_((THIS_FILE, "audio_manager_snd_pcm_open_name ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| /* Required call from January 2013 gold OS release */ |
| snd_pcm_plugin_set_disable (stream->ca_pcm, PLUGIN_DISABLE_MMAP); |
| |
| /* Required call from January 2013 gold OS release */ |
| snd_pcm_plugin_set_enable(stream->ca_pcm, PLUGIN_ROUTING); |
| |
| /* sample reads the capabilities of the capture */ |
| memset (&pi, 0, sizeof (pi)); |
| pi.channel = SND_PCM_CHANNEL_CAPTURE; |
| if ((ret = snd_pcm_plugin_info (stream->ca_pcm, &pi)) < 0) { |
| TRACE_((THIS_FILE, "snd_pcm_plugin_info ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| /* Request the VoIP parameters |
| * These parameters are different to waverec sample |
| */ |
| memset (&pp, 0, sizeof (pp)); |
| /* Blocking read */ |
| pp.mode = SND_PCM_MODE_BLOCK; |
| pp.channel = SND_PCM_CHANNEL_CAPTURE; |
| pp.start_mode = SND_PCM_START_FULL; |
| /* Auto-recover from errors */ |
| pp.stop_mode = SND_PCM_STOP_ROLLOVER; |
| pp.buf.block.frag_size = param->samples_per_frame * param->bits_per_sample / 8; |
| /* From January 2013 gold OS release. RIM recommend these for capture */ |
| pp.buf.block.frags_max = 3; |
| pp.buf.block.frags_min = 1; |
| pp.format.interleave = 1; |
| pp.format.rate = param->clock_rate; |
| pp.format.voices = param->channel_count; |
| pp.format.format = get_alsa_pcm_fmt(param); |
| |
| /* make the request */ |
| if ((ret = snd_pcm_plugin_params (stream->ca_pcm, &pp)) < 0) { |
| TRACE_((THIS_FILE, "snd_pcm_plugin_params ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| /* Again based on the sample */ |
| memset (&setup, 0, sizeof (setup)); |
| memset (&group, 0, sizeof (group)); |
| setup.channel = SND_PCM_CHANNEL_CAPTURE; |
| setup.mixer_gid = &group.gid; |
| if ((ret = snd_pcm_plugin_setup (stream->ca_pcm, &setup)) < 0) { |
| TRACE_((THIS_FILE, "snd_pcm_plugin_setup ret = %d", ret)); |
| return PJMEDIA_EAUD_SYSERR; |
| } |
| |
| frame_size = setup.buf.block.frag_size; |
| |
| if (group.gid.name[0] == 0) { |
| } else { |
| } |
| |
| frame_size = setup.buf.block.frag_size; |
| |
| /* Set clock rate */ |
| rate = param->clock_rate; |
| stream->ca_frames = (unsigned long) param->samples_per_frame / |
| param->channel_count; |
| |
| /* Set the sound device buffer size and latency */ |
| if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) { |
| tmp_buf_size = rate * param->input_latency_ms / 1000; |
| } else { |
| tmp_buf_size = rate * PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000; |
| } |
| |
| stream->param.input_latency_ms = tmp_buf_size * 1000 / rate; |
| |
| /* Set our buffer */ |
| stream->ca_buf_size = stream->ca_frames * param->channel_count * |
| param->bits_per_sample / 8; |
| stream->ca_buf = (char *)pj_pool_alloc (stream->pool, stream->ca_buf_size); |
| |
| TRACE_((THIS_FILE, "bb10_open_capture: ca_frames = %d clock = %d", |
| stream->ca_frames, param->clock_rate)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* API: create stream */ |
| static pj_status_t bb10_factory_create_stream(pjmedia_aud_dev_factory *f, |
| const pjmedia_aud_param *param, |
| pjmedia_aud_rec_cb rec_cb, |
| pjmedia_aud_play_cb play_cb, |
| void *user_data, |
| pjmedia_aud_stream **p_strm) |
| { |
| struct bb10_factory *af = (struct bb10_factory*)f; |
| pj_status_t status; |
| pj_pool_t* pool; |
| struct bb10_stream* stream; |
| |
| pool = pj_pool_create (af->pf, "bb10%p", 1024, 1024, NULL); |
| if (!pool) |
| return PJ_ENOMEM; |
| |
| /* Allocate and initialize comon stream data */ |
| stream = PJ_POOL_ZALLOC_T (pool, struct bb10_stream); |
| stream->base.op = &bb10_stream_op; |
| stream->pool = pool; |
| stream->af = af; |
| stream->user_data = user_data; |
| stream->pb_cb = play_cb; |
| stream->ca_cb = rec_cb; |
| stream->quit = 0; |
| pj_memcpy(&stream->param, param, sizeof(*param)); |
| |
| /* Init playback */ |
| if (param->dir & PJMEDIA_DIR_PLAYBACK) { |
| status = bb10_open_playback (stream, param); |
| if (status != PJ_SUCCESS) { |
| pj_pool_release (pool); |
| return status; |
| } |
| } |
| |
| /* Init capture */ |
| if (param->dir & PJMEDIA_DIR_CAPTURE) { |
| status = bb10_open_capture (stream, param); |
| if (status != PJ_SUCCESS) { |
| if (param->dir & PJMEDIA_DIR_PLAYBACK) { |
| close_play_pcm(stream); |
| } |
| pj_pool_release (pool); |
| return status; |
| } |
| } |
| |
| /* Set the audio routing ONLY if app explicitly asks one */ |
| if ((param->dir & PJMEDIA_DIR_PLAYBACK) && |
| (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE)) |
| { |
| status = bb10_stream_set_cap(&stream->base, |
| PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, |
| ¶m->output_route); |
| if (status != PJ_SUCCESS) { |
| TRACE_((THIS_FILE, "Error setting output route")); |
| bb10_stream_destroy(&stream->base); |
| return status; |
| } |
| } else { |
| /* Legacy behavior: if none specified, set to speaker */ |
| status = bb10_initialize_playback_ctrl(stream, false); |
| } |
| |
| *p_strm = &stream->base; |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * API: get running parameter |
| * based on ALSA template |
| */ |
| static pj_status_t bb10_stream_get_param(pjmedia_aud_stream *s, |
| pjmedia_aud_param *pi) |
| { |
| struct bb10_stream *stream = (struct bb10_stream*)s; |
| |
| PJ_ASSERT_RETURN(s && pi, PJ_EINVAL); |
| |
| pj_memcpy(pi, &stream->param, sizeof(*pi)); |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * API: get capability |
| * based on ALSA template |
| */ |
| static pj_status_t bb10_stream_get_cap(pjmedia_aud_stream *s, |
| pjmedia_aud_dev_cap cap, |
| void *pval) |
| { |
| struct bb10_stream *stream = (struct bb10_stream*)s; |
| |
| PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); |
| |
| if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && |
| (stream->param.dir & PJMEDIA_DIR_CAPTURE)) |
| { |
| /* Recording latency */ |
| *(unsigned*)pval = stream->param.input_latency_ms; |
| return PJ_SUCCESS; |
| |
| } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && |
| (stream->param.dir & PJMEDIA_DIR_PLAYBACK)) |
| { |
| /* Playback latency */ |
| *(unsigned*)pval = stream->param.output_latency_ms; |
| return PJ_SUCCESS; |
| |
| } else if (cap==PJMEDIA_AUD_DEV_CAP_EC && |
| (stream->param.dir & PJMEDIA_DIR_CAPTURE)) |
| { |
| /* EC is enablied implicitly by opening "voice" device */ |
| *(pj_bool_t*)pval = PJ_TRUE; |
| return PJ_SUCCESS; |
| } else { |
| return PJMEDIA_EAUD_INVCAP; |
| } |
| } |
| |
| |
| /* |
| * API: set capability |
| * Currently just supporting toggle between speaker and earpiece |
| */ |
| static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm, |
| pjmedia_aud_dev_cap cap, |
| const void *value) |
| { |
| |
| struct bb10_stream *stream = (struct bb10_stream*)strm; |
| |
| if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE && |
| (stream->param.dir & PJMEDIA_DIR_PLAYBACK)) |
| { |
| pjmedia_aud_dev_route route; |
| pj_status_t ret; |
| |
| PJ_ASSERT_RETURN(value, PJ_EINVAL); |
| |
| route = *((pjmedia_aud_dev_route*)value); |
| /* Use the initialization function which lazy-inits the |
| * handle for routing |
| */ |
| if (route == PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER) { |
| ret = bb10_initialize_playback_ctrl(stream,true); |
| } else { |
| ret = bb10_initialize_playback_ctrl(stream,false); |
| } |
| return ret; |
| |
| } else if (cap==PJMEDIA_AUD_DEV_CAP_EC && |
| (stream->param.dir & PJMEDIA_DIR_CAPTURE)) |
| { |
| /* EC is always enabled. Silently ignore the request */ |
| return PJ_SUCCESS; |
| } |
| |
| TRACE_((THIS_FILE,"bb10_stream_set_cap() = PJMEDIA_EAUD_INVCAP")); |
| return PJMEDIA_EAUD_INVCAP; |
| } |
| |
| |
| /* API: start stream */ |
| static pj_status_t bb10_stream_start (pjmedia_aud_stream *s) |
| { |
| struct bb10_stream *stream = (struct bb10_stream*)s; |
| pj_status_t status = PJ_SUCCESS; |
| |
| stream->quit = 0; |
| if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) { |
| status = pj_thread_create (stream->pool, |
| "bb10sound_playback", |
| pb_thread_func, |
| stream, |
| 0, |
| 0, |
| &stream->pb_thread); |
| if (status != PJ_SUCCESS) |
| return status; |
| } |
| |
| if (stream->param.dir & PJMEDIA_DIR_CAPTURE) { |
| status = pj_thread_create (stream->pool, |
| "bb10sound_playback", |
| ca_thread_func, |
| stream, |
| 0, |
| 0, |
| &stream->ca_thread); |
| if (status != PJ_SUCCESS) { |
| stream->quit = PJ_TRUE; |
| pj_thread_join(stream->pb_thread); |
| pj_thread_destroy(stream->pb_thread); |
| stream->pb_thread = NULL; |
| } |
| } |
| |
| return status; |
| } |
| |
| |
| /* API: stop stream */ |
| static pj_status_t bb10_stream_stop (pjmedia_aud_stream *s) |
| { |
| struct bb10_stream *stream = (struct bb10_stream*)s; |
| |
| stream->quit = 1; |
| TRACE_((THIS_FILE,"bb10_stream_stop()")); |
| |
| if (stream->pb_thread) { |
| pj_thread_join (stream->pb_thread); |
| pj_thread_destroy(stream->pb_thread); |
| stream->pb_thread = NULL; |
| } |
| |
| if (stream->ca_thread) { |
| pj_thread_join (stream->ca_thread); |
| pj_thread_destroy(stream->ca_thread); |
| stream->ca_thread = NULL; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| static pj_status_t bb10_stream_destroy (pjmedia_aud_stream *s) |
| { |
| struct bb10_stream *stream = (struct bb10_stream*)s; |
| |
| TRACE_((THIS_FILE,"bb10_stream_destroy()")); |
| |
| bb10_stream_stop (s); |
| |
| pj_pool_release (stream->pool); |
| |
| return PJ_SUCCESS; |
| } |
| |
| #endif /* PJMEDIA_AUDIO_DEV_HAS_BB10 */ |