From f3ab456a17af1c89a6e3be4d20c5944853df1cb0 Mon Sep 17 00:00:00 2001 From: "David M. Lee" Date: Mon, 7 Jan 2013 14:24:28 -0600 Subject: Import pjproject-2.0.1 --- pjmedia/src/pjmedia-audiodev/alsa_dev.c | 980 ++++++++++ pjmedia/src/pjmedia-audiodev/audiodev.c | 822 ++++++++ pjmedia/src/pjmedia-audiodev/audiotest.c | 269 +++ pjmedia/src/pjmedia-audiodev/bb10_dev.c | 965 ++++++++++ pjmedia/src/pjmedia-audiodev/coreaudio_dev.c | 2107 +++++++++++++++++++++ pjmedia/src/pjmedia-audiodev/errno.c | 206 ++ pjmedia/src/pjmedia-audiodev/legacy_dev.c | 468 +++++ pjmedia/src/pjmedia-audiodev/null_dev.c | 388 ++++ pjmedia/src/pjmedia-audiodev/pa_dev.c | 1284 +++++++++++++ pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h | 171 ++ pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp | 1929 +++++++++++++++++++ pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp | 1196 ++++++++++++ pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp | 2006 ++++++++++++++++++++ pjmedia/src/pjmedia-audiodev/wmme_dev.c | 1524 +++++++++++++++ 14 files changed, 14315 insertions(+) create mode 100644 pjmedia/src/pjmedia-audiodev/alsa_dev.c create mode 100644 pjmedia/src/pjmedia-audiodev/audiodev.c create mode 100644 pjmedia/src/pjmedia-audiodev/audiotest.c create mode 100644 pjmedia/src/pjmedia-audiodev/bb10_dev.c create mode 100644 pjmedia/src/pjmedia-audiodev/coreaudio_dev.c create mode 100644 pjmedia/src/pjmedia-audiodev/errno.c create mode 100644 pjmedia/src/pjmedia-audiodev/legacy_dev.c create mode 100644 pjmedia/src/pjmedia-audiodev/null_dev.c create mode 100644 pjmedia/src/pjmedia-audiodev/pa_dev.c create mode 100644 pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h create mode 100644 pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp create mode 100644 pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp create mode 100644 pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp create mode 100644 pjmedia/src/pjmedia-audiodev/wmme_dev.c (limited to 'pjmedia/src/pjmedia-audiodev') diff --git a/pjmedia/src/pjmedia-audiodev/alsa_dev.c b/pjmedia/src/pjmedia-audiodev/alsa_dev.c new file mode 100644 index 0000000..72b995e --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/alsa_dev.c @@ -0,0 +1,980 @@ +/* $Id: alsa_dev.c 4130 2012-05-17 08:35:51Z nanang $ */ +/* + * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2007-2009 Keystream AB and Konftel AB, All rights reserved. + * Author: + * + * 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 + */ +#include +#include +#include +#include +#include +#include + +#if defined(PJMEDIA_AUDIO_DEV_HAS_ALSA) && PJMEDIA_AUDIO_DEV_HAS_ALSA + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define THIS_FILE "alsa_dev.c" +#define ALSA_DEVICE_NAME "plughw:%d,%d" +#define ALSASOUND_PLAYBACK 1 +#define ALSASOUND_CAPTURE 2 +#define MAX_SOUND_CARDS 5 +#define MAX_SOUND_DEVICES_PER_CARD 5 +#define MAX_DEVICES 16 + +/* Set to 1 to enable tracing */ +#if 0 +# define TRACE_(expr) PJ_LOG(5,expr) +#else +# define TRACE_(expr) +#endif + +/* + * Factory prototypes + */ +static pj_status_t alsa_factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t alsa_factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned alsa_factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t alsa_factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t alsa_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 alsa_stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t alsa_stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t alsa_stream_start(pjmedia_aud_stream *strm); +static pj_status_t alsa_stream_stop(pjmedia_aud_stream *strm); +static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); + + +struct alsa_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[MAX_DEVICES]; +}; + +struct alsa_stream +{ + pjmedia_aud_stream base; + + /* Common */ + pj_pool_t *pool; + struct alsa_factory *af; + void *user_data; + pjmedia_aud_param param; /* Running parameter */ + int rec_id; /* Capture device id */ + int quit; + + /* Playback */ + snd_pcm_t *pb_pcm; + snd_pcm_uframes_t 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; + snd_pcm_uframes_t 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 alsa_factory_op = +{ + &alsa_factory_init, + &alsa_factory_destroy, + &alsa_factory_get_dev_count, + &alsa_factory_get_dev_info, + &alsa_factory_default_param, + &alsa_factory_create_stream, + &alsa_factory_refresh +}; + +static pjmedia_aud_stream_op alsa_stream_op = +{ + &alsa_stream_get_param, + &alsa_stream_get_cap, + &alsa_stream_set_cap, + &alsa_stream_start, + &alsa_stream_stop, + &alsa_stream_destroy +}; + +static void null_alsa_error_handler (const char *file, + int line, + const char *function, + int err, + const char *fmt, + ...) +{ + PJ_UNUSED_ARG(file); + PJ_UNUSED_ARG(line); + PJ_UNUSED_ARG(function); + PJ_UNUSED_ARG(err); + PJ_UNUSED_ARG(fmt); +} + +static void alsa_error_handler (const char *file, + int line, + const char *function, + int err, + const char *fmt, + ...) +{ + char err_msg[128]; + int index; + va_list arg; + +#ifndef NDEBUG + index = snprintf (err_msg, sizeof(err_msg), "ALSA lib %s:%i:(%s) ", + file, line, function); +#else + index = snprintf (err_msg, sizeof(err_msg), "ALSA lib: "); +#endif + va_start (arg, fmt); + if (index < sizeof(err_msg)-1) + index += vsnprintf (err_msg+index, sizeof(err_msg)-index, fmt, arg); + va_end(arg); + if (err && index < sizeof(err_msg)-1) + index += snprintf (err_msg+index, sizeof(err_msg)-index, ": %s", + snd_strerror(err)); + PJ_LOG (4,(THIS_FILE, "%s", err_msg)); +} + + +static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) +{ + pjmedia_aud_dev_info *adi; + snd_pcm_t* pcm; + int pb_result, ca_result; + + if (af->dev_cnt >= PJ_ARRAY_SIZE(af->devs)) + return PJ_ETOOMANY; + + adi = &af->devs[af->dev_cnt]; + + TRACE_((THIS_FILE, "add_dev (%s): Enter", dev_name)); + + /* Try to open the device in playback mode */ + pb_result = snd_pcm_open (&pcm, dev_name, SND_PCM_STREAM_PLAYBACK, 0); + if (pb_result >= 0) { + TRACE_((THIS_FILE, "Try to open the device for playback - success")); + snd_pcm_close (pcm); + } else { + TRACE_((THIS_FILE, "Try to open the device for playback - failure")); + } + + /* Try to open the device in capture mode */ + ca_result = snd_pcm_open (&pcm, dev_name, SND_PCM_STREAM_CAPTURE, 0); + if (ca_result >= 0) { + TRACE_((THIS_FILE, "Try to open the device for capture - success")); + snd_pcm_close (pcm); + } else { + TRACE_((THIS_FILE, "Try to open the device for capture - failure")); + } + + /* Check if the device could be opened in playback or capture mode */ + if (pb_result<0 && ca_result<0) { + TRACE_((THIS_FILE, "Unable to open sound device %s", dev_name)); + return PJMEDIA_EAUD_NODEV; + } + + /* Reset device info */ + pj_bzero(adi, sizeof(*adi)); + + /* Set device name */ + strcpy(adi->name, dev_name); + + /* 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, "ALSA"); + + ++af->dev_cnt; + + PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name)); + + return PJ_SUCCESS; +} + + +/* Create ALSA audio driver. */ +pjmedia_aud_dev_factory* pjmedia_alsa_factory(pj_pool_factory *pf) +{ + struct alsa_factory *af; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "alsa_aud_base", 256, 256, NULL); + af = PJ_POOL_ZALLOC_T(pool, struct alsa_factory); + af->pf = pf; + af->base_pool = pool; + af->base.op = &alsa_factory_op; + + return &af->base; +} + + +/* API: init factory */ +static pj_status_t alsa_factory_init(pjmedia_aud_dev_factory *f) +{ + pj_status_t status = alsa_factory_refresh(f); + if (PJ_SUCCESS != status) + return status; + + PJ_LOG(4,(THIS_FILE, "ALSA initialized")); + return PJ_SUCCESS; +} + + +/* API: destroy factory */ +static pj_status_t alsa_factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct alsa_factory *af = (struct alsa_factory*)f; + + if (af->pool) + pj_pool_release(af->pool); + + if (af->base_pool) { + pj_pool_t *pool = af->base_pool; + af->base_pool = NULL; + pj_pool_release(pool); + } + + /* Restore handler */ + snd_lib_error_set_handler(NULL); + + return PJ_SUCCESS; +} + + +/* API: refresh the device list */ +static pj_status_t alsa_factory_refresh(pjmedia_aud_dev_factory *f) +{ + struct alsa_factory *af = (struct alsa_factory*)f; + char **hints, **n; + int err; + + TRACE_((THIS_FILE, "pjmedia_snd_init: Enumerate sound devices")); + + if (af->pool != NULL) { + pj_pool_release(af->pool); + af->pool = NULL; + } + + af->pool = pj_pool_create(af->pf, "alsa_aud", 256, 256, NULL); + af->dev_cnt = 0; + + /* Enumerate sound devices */ + err = snd_device_name_hint(-1, "pcm", (void***)&hints); + if (err != 0) + return PJMEDIA_EAUD_SYSERR; + + /* Set a null error handler prior to enumeration to suppress errors */ + snd_lib_error_set_handler(null_alsa_error_handler); + + n = hints; + while (*n != NULL) { + char *name = snd_device_name_get_hint(*n, "NAME"); + if (name != NULL && 0 != strcmp("null", name)) { + add_dev(af, name); + free(name); + } + n++; + } + + /* Install error handler after enumeration, otherwise we'll get many + * error messages about invalid card/device ID. + */ + snd_lib_error_set_handler(alsa_error_handler); + + err = snd_device_name_free_hint((void**)hints); + + PJ_LOG(4,(THIS_FILE, "ALSA driver found %d devices", af->dev_cnt)); + + return PJ_SUCCESS; +} + + +/* API: get device count */ +static unsigned alsa_factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + struct alsa_factory *af = (struct alsa_factory*)f; + return af->dev_cnt; +} + + +/* API: get device info */ +static pj_status_t alsa_factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct alsa_factory *af = (struct alsa_factory*)f; + + PJ_ASSERT_RETURN(index>=0 && indexdev_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; + return PJ_SUCCESS; +} + +/* API: create default parameter */ +static pj_status_t alsa_factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct alsa_factory *af = (struct alsa_factory*)f; + pjmedia_aud_dev_info *adi; + + PJ_ASSERT_RETURN(index>=0 && indexdev_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; + + return PJ_SUCCESS; +} + + +static int pb_thread_func (void *arg) +{ + struct alsa_stream* stream = (struct alsa_stream*) arg; + snd_pcm_t* pcm = stream->pb_pcm; + int size = stream->pb_buf_size; + snd_pcm_uframes_t nframes = stream->pb_frames; + void* user_data = stream->user_data; + char* buf = stream->pb_buf; + pj_timestamp tstamp; + int result; + + pj_bzero (buf, size); + tstamp.u64 = 0; + + TRACE_((THIS_FILE, "pb_thread_func(%u): Started", + (unsigned)syscall(SYS_gettid))); + + snd_pcm_prepare (pcm); + + while (!stream->quit) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = buf; + frame.size = size; + frame.timestamp.u64 = tstamp.u64; + frame.bit_info = 0; + + 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); + + result = snd_pcm_writei (pcm, buf, nframes); + if (result == -EPIPE) { + PJ_LOG (4,(THIS_FILE, "pb_thread_func: underrun!")); + snd_pcm_prepare (pcm); + } else if (result < 0) { + PJ_LOG (4,(THIS_FILE, "pb_thread_func: error writing data!")); + } + + tstamp.u64 += nframes; + } + + snd_pcm_drain (pcm); + TRACE_((THIS_FILE, "pb_thread_func: Stopped")); + return PJ_SUCCESS; +} + + + +static int ca_thread_func (void *arg) +{ + struct alsa_stream* stream = (struct alsa_stream*) arg; + snd_pcm_t* pcm = stream->ca_pcm; + int size = stream->ca_buf_size; + snd_pcm_uframes_t nframes = stream->ca_frames; + void* user_data = stream->user_data; + char* buf = stream->ca_buf; + pj_timestamp tstamp; + int result; + struct sched_param param; + pthread_t* thid; + + thid = (pthread_t*) pj_thread_get_os_handle (pj_thread_this()); + param.sched_priority = sched_get_priority_max (SCHED_RR); + PJ_LOG (5,(THIS_FILE, "ca_thread_func(%u): Set thread priority " + "for audio capture thread.", + (unsigned)syscall(SYS_gettid))); + result = pthread_setschedparam (*thid, SCHED_RR, ¶m); + if (result) { + if (result == EPERM) + PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, " + "root access needed.")); + else + PJ_LOG (5,(THIS_FILE, "Unable to increase thread priority, " + "error: %d", + result)); + } + + pj_bzero (buf, size); + tstamp.u64 = 0; + + TRACE_((THIS_FILE, "ca_thread_func(%u): Started", + (unsigned)syscall(SYS_gettid))); + + snd_pcm_prepare (pcm); + + while (!stream->quit) { + pjmedia_frame frame; + + pj_bzero (buf, size); + result = snd_pcm_readi (pcm, buf, nframes); + if (result == -EPIPE) { + PJ_LOG (4,(THIS_FILE, "ca_thread_func: overrun!")); + snd_pcm_prepare (pcm); + continue; + } else if (result < 0) { + PJ_LOG (4,(THIS_FILE, "ca_thread_func: error reading data!")); + } + if (stream->quit) + break; + + 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; + } + snd_pcm_drain (pcm); + TRACE_((THIS_FILE, "ca_thread_func: Stopped")); + + return PJ_SUCCESS; +} + + +static pj_status_t open_playback (struct alsa_stream* stream, + const pjmedia_aud_param *param) +{ + snd_pcm_hw_params_t* params; + snd_pcm_format_t format; + int result; + unsigned int rate; + snd_pcm_uframes_t tmp_buf_size; + snd_pcm_uframes_t tmp_period_size; + + if (param->play_id < 0 || param->play_id >= stream->af->dev_cnt) + return PJMEDIA_EAUD_INVDEV; + + /* Open PCM for playback */ + PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'", + stream->af->devs[param->play_id].name)); + result = snd_pcm_open (&stream->pb_pcm, + stream->af->devs[param->play_id].name, + SND_PCM_STREAM_PLAYBACK, + 0); + if (result < 0) + return PJMEDIA_EAUD_SYSERR; + + /* Allocate a hardware parameters object. */ + snd_pcm_hw_params_alloca (¶ms); + + /* Fill it in with default values. */ + snd_pcm_hw_params_any (stream->pb_pcm, params); + + /* Set interleaved mode */ + snd_pcm_hw_params_set_access (stream->pb_pcm, params, + SND_PCM_ACCESS_RW_INTERLEAVED); + + /* Set format */ + switch (param->bits_per_sample) { + case 8: + TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S8")); + format = SND_PCM_FORMAT_S8; + break; + case 16: + TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S16_LE")); + format = SND_PCM_FORMAT_S16_LE; + break; + case 24: + TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S24_LE")); + format = SND_PCM_FORMAT_S24_LE; + break; + case 32: + TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S32_LE")); + format = SND_PCM_FORMAT_S32_LE; + break; + default: + TRACE_((THIS_FILE, "open_playback: set format SND_PCM_FORMAT_S16_LE")); + format = SND_PCM_FORMAT_S16_LE; + break; + } + snd_pcm_hw_params_set_format (stream->pb_pcm, params, format); + + /* Set number of channels */ + TRACE_((THIS_FILE, "open_playback: set channels: %d", + param->channel_count)); + snd_pcm_hw_params_set_channels (stream->pb_pcm, params, + param->channel_count); + + /* Set clock rate */ + rate = param->clock_rate; + TRACE_((THIS_FILE, "open_playback: set clock rate: %d", rate)); + snd_pcm_hw_params_set_rate_near (stream->pb_pcm, params, &rate, NULL); + TRACE_((THIS_FILE, "open_playback: clock rate set to: %d", rate)); + + /* Set period size to samples_per_frame frames. */ + stream->pb_frames = (snd_pcm_uframes_t) param->samples_per_frame / + param->channel_count; + TRACE_((THIS_FILE, "open_playback: set period size: %d", + stream->pb_frames)); + tmp_period_size = stream->pb_frames; + snd_pcm_hw_params_set_period_size_near (stream->pb_pcm, params, + &tmp_period_size, NULL); + TRACE_((THIS_FILE, "open_playback: period size set to: %d", + tmp_period_size)); + + /* Set the sound device buffer size and latency */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) + tmp_buf_size = (rate / 1000) * param->output_latency_ms; + else + tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + snd_pcm_hw_params_set_buffer_size_near (stream->pb_pcm, params, + &tmp_buf_size); + stream->param.output_latency_ms = tmp_buf_size / (rate / 1000); + + /* 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, "open_playback: buffer size set to: %d", + (int)tmp_buf_size)); + TRACE_((THIS_FILE, "open_playback: playback_latency set to: %d ms", + (int)stream->param.output_latency_ms)); + + /* Activate the parameters */ + result = snd_pcm_hw_params (stream->pb_pcm, params); + if (result < 0) { + snd_pcm_close (stream->pb_pcm); + return PJMEDIA_EAUD_SYSERR; + } + + PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" + ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", + stream->af->devs[param->play_id].name, + rate, param->channel_count, + param->bits_per_sample, stream->pb_frames, + (int)stream->param.output_latency_ms)); + + return PJ_SUCCESS; +} + + +static pj_status_t open_capture (struct alsa_stream* stream, + const pjmedia_aud_param *param) +{ + snd_pcm_hw_params_t* params; + snd_pcm_format_t format; + int result; + unsigned int rate; + snd_pcm_uframes_t tmp_buf_size; + snd_pcm_uframes_t tmp_period_size; + + if (param->rec_id < 0 || param->rec_id >= stream->af->dev_cnt) + return PJMEDIA_EAUD_INVDEV; + + /* Open PCM for capture */ + PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'", + stream->af->devs[param->rec_id].name)); + result = snd_pcm_open (&stream->ca_pcm, + stream->af->devs[param->rec_id].name, + SND_PCM_STREAM_CAPTURE, + 0); + if (result < 0) + return PJMEDIA_EAUD_SYSERR; + + /* Allocate a hardware parameters object. */ + snd_pcm_hw_params_alloca (¶ms); + + /* Fill it in with default values. */ + snd_pcm_hw_params_any (stream->ca_pcm, params); + + /* Set interleaved mode */ + snd_pcm_hw_params_set_access (stream->ca_pcm, params, + SND_PCM_ACCESS_RW_INTERLEAVED); + + /* Set format */ + switch (param->bits_per_sample) { + case 8: + TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S8")); + format = SND_PCM_FORMAT_S8; + break; + case 16: + TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S16_LE")); + format = SND_PCM_FORMAT_S16_LE; + break; + case 24: + TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S24_LE")); + format = SND_PCM_FORMAT_S24_LE; + break; + case 32: + TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S32_LE")); + format = SND_PCM_FORMAT_S32_LE; + break; + default: + TRACE_((THIS_FILE, "open_capture: set format SND_PCM_FORMAT_S16_LE")); + format = SND_PCM_FORMAT_S16_LE; + break; + } + snd_pcm_hw_params_set_format (stream->ca_pcm, params, format); + + /* Set number of channels */ + TRACE_((THIS_FILE, "open_capture: set channels: %d", + param->channel_count)); + snd_pcm_hw_params_set_channels (stream->ca_pcm, params, + param->channel_count); + + /* Set clock rate */ + rate = param->clock_rate; + TRACE_((THIS_FILE, "open_capture: set clock rate: %d", rate)); + snd_pcm_hw_params_set_rate_near (stream->ca_pcm, params, &rate, NULL); + TRACE_((THIS_FILE, "open_capture: clock rate set to: %d", rate)); + + /* Set period size to samples_per_frame frames. */ + stream->ca_frames = (snd_pcm_uframes_t) param->samples_per_frame / + param->channel_count; + TRACE_((THIS_FILE, "open_capture: set period size: %d", + stream->ca_frames)); + tmp_period_size = stream->ca_frames; + snd_pcm_hw_params_set_period_size_near (stream->ca_pcm, params, + &tmp_period_size, NULL); + TRACE_((THIS_FILE, "open_capture: period size set to: %d", + tmp_period_size)); + + /* Set the sound device buffer size and latency */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) + tmp_buf_size = (rate / 1000) * param->input_latency_ms; + else + tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_REC_LATENCY; + snd_pcm_hw_params_set_buffer_size_near (stream->ca_pcm, params, + &tmp_buf_size); + stream->param.input_latency_ms = tmp_buf_size / (rate / 1000); + + /* 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, "open_capture: buffer size set to: %d", + (int)tmp_buf_size)); + TRACE_((THIS_FILE, "open_capture: capture_latency set to: %d ms", + (int)stream->param.input_latency_ms)); + + /* Activate the parameters */ + result = snd_pcm_hw_params (stream->ca_pcm, params); + if (result < 0) { + snd_pcm_close (stream->ca_pcm); + return PJMEDIA_EAUD_SYSERR; + } + + PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d" + ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", + stream->af->devs[param->rec_id].name, + rate, param->channel_count, + param->bits_per_sample, stream->ca_frames, + (int)stream->param.input_latency_ms)); + + return PJ_SUCCESS; +} + + +/* API: create stream */ +static pj_status_t alsa_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 alsa_factory *af = (struct alsa_factory*)f; + pj_status_t status; + pj_pool_t* pool; + struct alsa_stream* stream; + + pool = pj_pool_create (af->pf, "alsa%p", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Allocate and initialize comon stream data */ + stream = PJ_POOL_ZALLOC_T (pool, struct alsa_stream); + stream->base.op = &alsa_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 = open_playback (stream, param); + if (status != PJ_SUCCESS) { + pj_pool_release (pool); + return status; + } + } + + /* Init capture */ + if (param->dir & PJMEDIA_DIR_CAPTURE) { + status = open_capture (stream, param); + if (status != PJ_SUCCESS) { + if (param->dir & PJMEDIA_DIR_PLAYBACK) + snd_pcm_close (stream->pb_pcm); + pj_pool_release (pool); + return status; + } + } + + *p_strm = &stream->base; + return PJ_SUCCESS; +} + + +/* API: get running parameter */ +static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct alsa_stream *stream = (struct alsa_stream*)s; + + PJ_ASSERT_RETURN(s && pi, PJ_EINVAL); + + pj_memcpy(pi, &stream->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + + +/* API: get capability */ +static pj_status_t alsa_stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct alsa_stream *stream = (struct alsa_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 { + return PJMEDIA_EAUD_INVCAP; + } +} + + +/* API: set capability */ +static pj_status_t alsa_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value) +{ + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(value); + + return PJMEDIA_EAUD_INVCAP; +} + + +/* API: start stream */ +static pj_status_t alsa_stream_start (pjmedia_aud_stream *s) +{ + struct alsa_stream *stream = (struct alsa_stream*)s; + pj_status_t status = PJ_SUCCESS; + + stream->quit = 0; + if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) { + status = pj_thread_create (stream->pool, + "alsasound_playback", + pb_thread_func, + stream, + 0, //ZERO, + 0, + &stream->pb_thread); + if (status != PJ_SUCCESS) + return status; + } + + if (stream->param.dir & PJMEDIA_DIR_CAPTURE) { + status = pj_thread_create (stream->pool, + "alsasound_playback", + ca_thread_func, + stream, + 0, //ZERO, + 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 alsa_stream_stop (pjmedia_aud_stream *s) +{ + struct alsa_stream *stream = (struct alsa_stream*)s; + + stream->quit = 1; + + if (stream->pb_thread) { + TRACE_((THIS_FILE, + "alsa_stream_stop(%u): Waiting for playback to stop.", + (unsigned)syscall(SYS_gettid))); + pj_thread_join (stream->pb_thread); + TRACE_((THIS_FILE, + "alsa_stream_stop(%u): playback stopped.", + (unsigned)syscall(SYS_gettid))); + pj_thread_destroy(stream->pb_thread); + stream->pb_thread = NULL; + } + + if (stream->ca_thread) { + TRACE_((THIS_FILE, + "alsa_stream_stop(%u): Waiting for capture to stop.", + (unsigned)syscall(SYS_gettid))); + pj_thread_join (stream->ca_thread); + TRACE_((THIS_FILE, + "alsa_stream_stop(%u): capture stopped.", + (unsigned)syscall(SYS_gettid))); + pj_thread_destroy(stream->ca_thread); + stream->ca_thread = NULL; + } + + return PJ_SUCCESS; +} + + + +static pj_status_t alsa_stream_destroy (pjmedia_aud_stream *s) +{ + struct alsa_stream *stream = (struct alsa_stream*)s; + + alsa_stream_stop (s); + + if (stream->param.dir & PJMEDIA_DIR_PLAYBACK) { + snd_pcm_close (stream->pb_pcm); + stream->pb_pcm = NULL; + } + if (stream->param.dir & PJMEDIA_DIR_CAPTURE) { + snd_pcm_close (stream->ca_pcm); + stream->ca_pcm = NULL; + } + + pj_pool_release (stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_ALSA */ diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c new file mode 100644 index 0000000..9a70ab0 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/audiodev.c @@ -0,0 +1,822 @@ +/* $Id: audiodev.c 4150 2012-06-01 04:29:56Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + */ +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "audiodev.c" + +#define DEFINE_CAP(name, info) {name, info} + +/* Capability names */ +static struct cap_info +{ + const char *name; + const char *info; +} cap_infos[] = +{ + DEFINE_CAP("ext-fmt", "Extended/non-PCM format"), + DEFINE_CAP("latency-in", "Input latency/buffer size setting"), + DEFINE_CAP("latency-out", "Output latency/buffer size setting"), + DEFINE_CAP("vol-in", "Input volume setting"), + DEFINE_CAP("vol-out", "Output volume setting"), + DEFINE_CAP("meter-in", "Input meter"), + DEFINE_CAP("meter-out", "Output meter"), + DEFINE_CAP("route-in", "Input routing"), + DEFINE_CAP("route-out", "Output routing"), + DEFINE_CAP("aec", "Accoustic echo cancellation"), + DEFINE_CAP("aec-tail", "Tail length setting for AEC"), + DEFINE_CAP("vad", "Voice activity detection"), + DEFINE_CAP("cng", "Comfort noise generation"), + DEFINE_CAP("plg", "Packet loss concealment") +}; + + +/* + * The device index seen by application and driver is different. + * + * At application level, device index is index to global list of device. + * At driver level, device index is index to device list on that particular + * factory only. + */ +#define MAKE_DEV_ID(f_id, index) (((f_id & 0xFFFF) << 16) | (index & 0xFFFF)) +#define GET_INDEX(dev_id) ((dev_id) & 0xFFFF) +#define GET_FID(dev_id) ((dev_id) >> 16) +#define DEFAULT_DEV_ID 0 + + +/* extern functions to create factories */ +#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO +pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO +pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_ALSA +pjmedia_aud_dev_factory* pjmedia_alsa_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_BB10 +pjmedia_aud_dev_factory* pjmedia_bb10_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_WMME +pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS +pjmedia_aud_dev_factory* pjmedia_symb_vas_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS +pjmedia_aud_dev_factory* pjmedia_aps_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA +pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO +pjmedia_aud_dev_factory* pjmedia_null_audio_factory(pj_pool_factory *pf); +#endif + +#define MAX_DRIVERS 16 +#define MAX_DEVS 64 + + +/* driver structure */ +struct driver +{ + /* Creation function */ + pjmedia_aud_dev_factory_create_func_ptr create; + /* Factory instance */ + pjmedia_aud_dev_factory *f; + char name[32]; /* Driver name */ + unsigned dev_cnt; /* Number of devices */ + unsigned start_idx; /* Start index in global list */ + int rec_dev_idx;/* Default capture device. */ + int play_dev_idx;/* Default playback device */ + int dev_idx; /* Default device. */ +}; + +/* The audio subsystem */ +static struct aud_subsys +{ + unsigned init_count; /* How many times init() is called */ + pj_pool_factory *pf; /* The pool factory. */ + + unsigned drv_cnt; /* Number of drivers. */ + struct driver drv[MAX_DRIVERS]; /* Array of drivers. */ + + unsigned dev_cnt; /* Total number of devices. */ + pj_uint32_t dev_list[MAX_DEVS];/* Array of device IDs. */ + +} aud_subsys; + +/* API: get capability name/info */ +PJ_DEF(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap, + const char **p_desc) +{ + const char *desc; + unsigned i; + + if (p_desc==NULL) p_desc = &desc; + + for (i=0; iname; \ + *size = sizeof(param->name) + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_EXT_FORMAT: + FIELD_INFO(ext_fmt); + break; + case PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY: + FIELD_INFO(input_latency_ms); + break; + case PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY: + FIELD_INFO(output_latency_ms); + break; + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + FIELD_INFO(input_vol); + break; + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + FIELD_INFO(output_vol); + break; + case PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE: + FIELD_INFO(input_route); + break; + case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: + FIELD_INFO(output_route); + break; + case PJMEDIA_AUD_DEV_CAP_EC: + FIELD_INFO(ec_enabled); + break; + case PJMEDIA_AUD_DEV_CAP_EC_TAIL: + FIELD_INFO(ec_tail_ms); + break; + /* vad is no longer in "fmt" in 2.0. + case PJMEDIA_AUD_DEV_CAP_VAD: + FIELD_INFO(ext_fmt.vad); + break; + */ + case PJMEDIA_AUD_DEV_CAP_CNG: + FIELD_INFO(cng_enabled); + break; + case PJMEDIA_AUD_DEV_CAP_PLC: + FIELD_INFO(plc_enabled); + break; + default: + return PJMEDIA_EAUD_INVCAP; + } + +#undef FIELD_INFO + + return PJ_SUCCESS; +} + +/* API: set cap value to param */ +PJ_DEF(pj_status_t) pjmedia_aud_param_set_cap( pjmedia_aud_param *param, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + void *cap_ptr; + unsigned cap_size; + pj_status_t status; + + status = get_cap_pointer(param, cap, &cap_ptr, &cap_size); + if (status != PJ_SUCCESS) + return status; + + pj_memcpy(cap_ptr, pval, cap_size); + param->flags |= cap; + + return PJ_SUCCESS; +} + +/* API: get cap value from param */ +PJ_DEF(pj_status_t) pjmedia_aud_param_get_cap( const pjmedia_aud_param *param, + pjmedia_aud_dev_cap cap, + void *pval) +{ + void *cap_ptr; + unsigned cap_size; + pj_status_t status; + + status = get_cap_pointer(param, cap, &cap_ptr, &cap_size); + if (status != PJ_SUCCESS) + return status; + + if ((param->flags & cap) == 0) { + pj_bzero(cap_ptr, cap_size); + return PJMEDIA_EAUD_INVCAP; + } + + pj_memcpy(pval, cap_ptr, cap_size); + return PJ_SUCCESS; +} + +/* Internal: init driver */ +static pj_status_t init_driver(unsigned drv_idx, pj_bool_t refresh) +{ + struct driver *drv = &aud_subsys.drv[drv_idx]; + pjmedia_aud_dev_factory *f; + unsigned i, dev_cnt; + pj_status_t status; + + if (!refresh) { + /* Create the factory */ + f = (*drv->create)(aud_subsys.pf); + if (!f) + return PJ_EUNKNOWN; + + /* Call factory->init() */ + status = f->op->init(f); + if (status != PJ_SUCCESS) { + f->op->destroy(f); + return status; + } + } else { + f = drv->f; + } + + /* Get number of devices */ + dev_cnt = f->op->get_dev_count(f); + if (dev_cnt + aud_subsys.dev_cnt > MAX_DEVS) { + PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because" + " there are too many devices", + aud_subsys.dev_cnt + dev_cnt - MAX_DEVS)); + dev_cnt = MAX_DEVS - aud_subsys.dev_cnt; + } + + /* enabling this will cause pjsua-lib initialization to fail when there + * is no sound device installed in the system, even when pjsua has been + * run with --null-audio + * + if (dev_cnt == 0) { + f->op->destroy(f); + return PJMEDIA_EAUD_NODEV; + } + */ + + /* Fill in default devices */ + drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1; + for (i=0; iop->get_dev_info(f, i, &info); + if (status != PJ_SUCCESS) { + f->op->destroy(f); + return status; + } + + if (drv->name[0]=='\0') { + /* Set driver name */ + pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name)); + drv->name[sizeof(drv->name)-1] = '\0'; + } + + if (drv->play_dev_idx < 0 && info.output_count) { + /* Set default playback device */ + drv->play_dev_idx = i; + } + if (drv->rec_dev_idx < 0 && info.input_count) { + /* Set default capture device */ + drv->rec_dev_idx = i; + } + if (drv->dev_idx < 0 && info.input_count && + info.output_count) + { + /* Set default capture and playback device */ + drv->dev_idx = i; + } + + if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 && + drv->dev_idx >= 0) + { + /* Done. */ + break; + } + } + + /* Register the factory */ + drv->f = f; + drv->f->sys.drv_idx = drv_idx; + drv->start_idx = aud_subsys.dev_cnt; + drv->dev_cnt = dev_cnt; + + /* Register devices to global list */ + for (i=0; if) { + drv->f->op->destroy(drv->f); + drv->f = NULL; + } + + drv->dev_cnt = 0; + drv->play_dev_idx = drv->rec_dev_idx = drv->dev_idx = -1; +} + +/* API: Initialize the audio subsystem. */ +PJ_DEF(pj_status_t) pjmedia_aud_subsys_init(pj_pool_factory *pf) +{ + unsigned i; + pj_status_t status; + + /* Allow init() to be called multiple times as long as there is matching + * number of shutdown(). + */ + if (aud_subsys.init_count++ != 0) { + return PJ_SUCCESS; + } + + /* Register error subsystem */ + status = pj_register_strerror(PJMEDIA_AUDIODEV_ERRNO_START, + PJ_ERRNO_SPACE_SIZE, + &pjmedia_audiodev_strerror); + pj_assert(status == PJ_SUCCESS); + + /* Init */ + aud_subsys.pf = pf; + aud_subsys.drv_cnt = 0; + aud_subsys.dev_cnt = 0; + + /* Register creation functions */ +#if PJMEDIA_AUDIO_DEV_HAS_BB10 + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_bb10_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_ALSA + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_alsa_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_coreaudio_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_pa_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_WMME + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_vas_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_aps_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_symb_mda_factory; +#endif +#if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO + aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_null_audio_factory; +#endif + + /* Initialize each factory and build the device ID list */ + for (i=0; icreate == adf) { + for (j = drv->start_idx; j < drv->start_idx + drv->dev_cnt; j++) + { + aud_subsys.dev_list[j] = (pj_uint32_t)PJMEDIA_AUD_INVALID_DEV; + } + + deinit_driver(i); + pj_bzero(drv, sizeof(*drv)); + return PJ_SUCCESS; + } + } + + return PJMEDIA_EAUD_ERR; +} + +/* API: get the pool factory registered to the audio subsystem. */ +PJ_DEF(pj_pool_factory*) pjmedia_aud_subsys_get_pool_factory(void) +{ + return aud_subsys.pf; +} + +/* API: Shutdown the audio subsystem. */ +PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void) +{ + unsigned i; + + /* Allow shutdown() to be called multiple times as long as there is matching + * number of init(). + */ + if (aud_subsys.init_count == 0) { + return PJ_SUCCESS; + } + --aud_subsys.init_count; + + if (aud_subsys.init_count == 0) { + for (i=0; if && drv->f->op->refresh) { + pj_status_t status = drv->f->op->refresh(drv->f); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (THIS_FILE, status, "Unable to refresh device " + "list for %s", drv->name)); + } + } + init_driver(i, PJ_TRUE); + } + return PJ_SUCCESS; +} + +/* API: Get the number of sound devices installed in the system. */ +PJ_DEF(unsigned) pjmedia_aud_dev_count(void) +{ + return aud_subsys.dev_cnt; +} + +/* Internal: convert local index to global device index */ +static pj_status_t make_global_index(unsigned drv_idx, + pjmedia_aud_dev_index *id) +{ + if (*id < 0) { + return PJ_SUCCESS; + } + + /* Check that factory still exists */ + PJ_ASSERT_RETURN(aud_subsys.drv[drv_idx].f, PJ_EBUG); + + /* Check that device index is valid */ + PJ_ASSERT_RETURN(*id>=0 && *id<(int)aud_subsys.drv[drv_idx].dev_cnt, + PJ_EBUG); + + *id += aud_subsys.drv[drv_idx].start_idx; + return PJ_SUCCESS; +} + +/* Internal: lookup device id */ +static pj_status_t lookup_dev(pjmedia_aud_dev_index id, + pjmedia_aud_dev_factory **p_f, + unsigned *p_local_index) +{ + int f_id, index; + + if (id < 0) { + unsigned i; + + if (id == PJMEDIA_AUD_INVALID_DEV) + return PJMEDIA_EAUD_INVDEV; + + for (i=0; idev_idx >= 0) { + id = drv->dev_idx; + make_global_index(i, &id); + break; + } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && + drv->rec_dev_idx >= 0) + { + id = drv->rec_dev_idx; + make_global_index(i, &id); + break; + } else if (id==PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV && + drv->play_dev_idx >= 0) + { + id = drv->play_dev_idx; + make_global_index(i, &id); + break; + } + } + + if (id < 0) { + return PJMEDIA_EAUD_NODEFDEV; + } + } + + f_id = GET_FID(aud_subsys.dev_list[id]); + index = GET_INDEX(aud_subsys.dev_list[id]); + + if (f_id < 0 || f_id >= (int)aud_subsys.drv_cnt) + return PJMEDIA_EAUD_INVDEV; + + if (index < 0 || index >= (int)aud_subsys.drv[f_id].dev_cnt) + return PJMEDIA_EAUD_INVDEV; + + *p_f = aud_subsys.drv[f_id].f; + *p_local_index = (unsigned)index; + + return PJ_SUCCESS; + +} + +/* API: Get device information. */ +PJ_DEF(pj_status_t) pjmedia_aud_dev_get_info(pjmedia_aud_dev_index id, + pjmedia_aud_dev_info *info) +{ + pjmedia_aud_dev_factory *f; + unsigned index; + pj_status_t status; + + PJ_ASSERT_RETURN(info && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + + status = lookup_dev(id, &f, &index); + if (status != PJ_SUCCESS) + return status; + + return f->op->get_dev_info(f, index, info); +} + +/* API: find device */ +PJ_DEF(pj_status_t) pjmedia_aud_dev_lookup( const char *drv_name, + const char *dev_name, + pjmedia_aud_dev_index *id) +{ + pjmedia_aud_dev_factory *f = NULL; + unsigned drv_idx, dev_idx; + + PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + + for (drv_idx=0; drv_idxop->get_dev_info(f, dev_idx, &info); + if (status != PJ_SUCCESS) + return status; + + if (!pj_ansi_stricmp(dev_name, info.name)) + break; + } + + if (dev_idx==aud_subsys.drv[drv_idx].dev_cnt) + return PJ_ENOTFOUND; + + *id = dev_idx; + make_global_index(drv_idx, id); + + return PJ_SUCCESS; +} + +/* API: Initialize the audio device parameters with default values for the + * specified device. + */ +PJ_DEF(pj_status_t) pjmedia_aud_dev_default_param(pjmedia_aud_dev_index id, + pjmedia_aud_param *param) +{ + pjmedia_aud_dev_factory *f; + unsigned index; + pj_status_t status; + + PJ_ASSERT_RETURN(param && id!=PJMEDIA_AUD_INVALID_DEV, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + + status = lookup_dev(id, &f, &index); + if (status != PJ_SUCCESS) + return status; + + status = f->op->default_param(f, index, param); + if (status != PJ_SUCCESS) + return status; + + /* Normalize device IDs */ + make_global_index(f->sys.drv_idx, ¶m->rec_id); + make_global_index(f->sys.drv_idx, ¶m->play_id); + + return PJ_SUCCESS; +} + +/* API: Open audio stream object using the specified parameters. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm, + pjmedia_aud_rec_cb rec_cb, + pjmedia_aud_play_cb play_cb, + void *user_data, + pjmedia_aud_stream **p_aud_strm) +{ + pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL; + pjmedia_aud_param param; + pj_status_t status; + + PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE || + prm->dir==PJMEDIA_DIR_PLAYBACK || + prm->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK, + PJ_EINVAL); + + /* Must make copy of param because we're changing device ID */ + pj_memcpy(¶m, prm, sizeof(param)); + + /* Normalize rec_id */ + if (param.dir & PJMEDIA_DIR_CAPTURE) { + unsigned index; + + if (param.rec_id < 0) + param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; + + status = lookup_dev(param.rec_id, &rec_f, &index); + if (status != PJ_SUCCESS) + return status; + + param.rec_id = index; + f = rec_f; + } + + /* Normalize play_id */ + if (param.dir & PJMEDIA_DIR_PLAYBACK) { + unsigned index; + + if (param.play_id < 0) + param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; + + status = lookup_dev(param.play_id, &play_f, &index); + if (status != PJ_SUCCESS) + return status; + + param.play_id = index; + f = play_f; + } + + PJ_ASSERT_RETURN(f != NULL, PJ_EBUG); + + /* For now, rec_id and play_id must belong to the same factory */ + PJ_ASSERT_RETURN((param.dir != PJMEDIA_DIR_CAPTURE_PLAYBACK) || + (rec_f == play_f), + PJMEDIA_EAUD_INVDEV); + + /* Create the stream */ + status = f->op->create_stream(f, ¶m, rec_cb, play_cb, + user_data, p_aud_strm); + if (status != PJ_SUCCESS) + return status; + + /* Assign factory id to the stream */ + (*p_aud_strm)->sys.drv_idx = f->sys.drv_idx; + return PJ_SUCCESS; +} + +/* API: Get the running parameters for the specified audio stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(strm && param, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + + status = strm->op->get_param(strm, param); + if (status != PJ_SUCCESS) + return status; + + /* Normalize device id's */ + make_global_index(strm->sys.drv_idx, ¶m->rec_id); + make_global_index(strm->sys.drv_idx, ¶m->play_id); + + return PJ_SUCCESS; +} + +/* API: Get the value of a specific capability of the audio stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value) +{ + return strm->op->get_cap(strm, cap, value); +} + +/* API: Set the value of a specific capability of the audio stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value) +{ + return strm->op->set_cap(strm, cap, value); +} + +/* API: Start the stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm) +{ + return strm->op->start(strm); +} + +/* API: Stop the stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm) +{ + return strm->op->stop(strm); +} + +/* API: Destroy the stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm) +{ + return strm->op->destroy(strm); +} + + diff --git a/pjmedia/src/pjmedia-audiodev/audiotest.c b/pjmedia/src/pjmedia-audiodev/audiotest.c new file mode 100644 index 0000000..82893f5 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/audiotest.c @@ -0,0 +1,269 @@ +/* $Id: audiotest.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + */ +#include +#include +#include +#include + +#define THIS_FILE "audiotest.c" + +/* Test duration in msec */ +#define DURATION 10000 + +/* Skip the first msec from the calculation */ +#define SKIP_DURATION 1000 + +/* Division helper */ +#define DIV_ROUND_UP(a,b) (((a) + ((b) - 1)) / (b)) +#define DIV_ROUND(a,b) (((a) + ((b)/2 - 1)) / (b)) + +struct stream_data +{ + pj_uint32_t first_timestamp; + pj_uint32_t last_timestamp; + pj_timestamp last_called; + pj_math_stat delay; +}; + +struct test_data +{ + pj_pool_t *pool; + const pjmedia_aud_param *param; + pjmedia_aud_test_results *result; + pj_bool_t running; + pj_bool_t has_error; + pj_mutex_t *mutex; + + struct stream_data capture_data; + struct stream_data playback_data; +}; + +static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) +{ + struct test_data *test_data = (struct test_data *)user_data; + struct stream_data *strm_data = &test_data->playback_data; + + pj_mutex_lock(test_data->mutex); + + /* Skip frames when test is not started or test has finished */ + if (!test_data->running) { + pj_bzero(frame->buf, frame->size); + pj_mutex_unlock(test_data->mutex); + return PJ_SUCCESS; + } + + /* Save last timestamp seen (to calculate drift) */ + strm_data->last_timestamp = frame->timestamp.u32.lo; + + if (strm_data->last_called.u64 == 0) { + /* Init vars. */ + pj_get_timestamp(&strm_data->last_called); + pj_math_stat_init(&strm_data->delay); + strm_data->first_timestamp = frame->timestamp.u32.lo; + } else { + pj_timestamp now; + unsigned delay; + + /* Calculate frame interval */ + pj_get_timestamp(&now); + delay = pj_elapsed_usec(&strm_data->last_called, &now); + strm_data->last_called = now; + + /* Update frame interval statistic */ + pj_math_stat_update(&strm_data->delay, delay); + } + + pj_bzero(frame->buf, frame->size); + + pj_mutex_unlock(test_data->mutex); + + return PJ_SUCCESS; +} + +static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) +{ + struct test_data *test_data = (struct test_data*)user_data; + struct stream_data *strm_data = &test_data->capture_data; + + pj_mutex_lock(test_data->mutex); + + /* Skip frames when test is not started or test has finished */ + if (!test_data->running) { + pj_mutex_unlock(test_data->mutex); + return PJ_SUCCESS; + } + + /* Save last timestamp seen (to calculate drift) */ + strm_data->last_timestamp = frame->timestamp.u32.lo; + + if (strm_data->last_called.u64 == 0) { + /* Init vars. */ + pj_get_timestamp(&strm_data->last_called); + pj_math_stat_init(&strm_data->delay); + strm_data->first_timestamp = frame->timestamp.u32.lo; + } else { + pj_timestamp now; + unsigned delay; + + /* Calculate frame interval */ + pj_get_timestamp(&now); + delay = pj_elapsed_usec(&strm_data->last_called, &now); + strm_data->last_called = now; + + /* Update frame interval statistic */ + pj_math_stat_update(&strm_data->delay, delay); + } + + pj_mutex_unlock(test_data->mutex); + return PJ_SUCCESS; +} + +static void app_perror(const char *title, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + printf( "%s: %s (err=%d)\n", + title, errmsg, status); +} + + +PJ_DEF(pj_status_t) pjmedia_aud_test( const pjmedia_aud_param *param, + pjmedia_aud_test_results *result) +{ + pj_status_t status = PJ_SUCCESS; + pjmedia_aud_stream *strm; + struct test_data test_data; + unsigned ptime, tmp; + + /* + * Init test parameters + */ + pj_bzero(&test_data, sizeof(test_data)); + test_data.param = param; + test_data.result = result; + + test_data.pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), + "audtest", 1000, 1000, NULL); + pj_mutex_create_simple(test_data.pool, "sndtest", &test_data.mutex); + + /* + * Open device. + */ + status = pjmedia_aud_stream_create(test_data.param, &rec_cb, &play_cb, + &test_data, &strm); + if (status != PJ_SUCCESS) { + app_perror("Unable to open device", status); + pj_pool_release(test_data.pool); + return status; + } + + + /* Sleep for a while to let sound device "settles" */ + pj_thread_sleep(200); + + /* + * Start the stream. + */ + status = pjmedia_aud_stream_start(strm); + if (status != PJ_SUCCESS) { + app_perror("Unable to start capture stream", status); + pjmedia_aud_stream_destroy(strm); + pj_pool_release(test_data.pool); + return status; + } + + PJ_LOG(3,(THIS_FILE, + " Please wait while test is in progress (~%d secs)..", + (DURATION+SKIP_DURATION)/1000)); + + /* Let the stream runs for few msec/sec to get stable result. + * (capture normally begins with frames available simultaneously). + */ + pj_thread_sleep(SKIP_DURATION); + + + /* Begin gather data */ + test_data.running = 1; + + /* + * Let the test runs for a while. + */ + pj_thread_sleep(DURATION); + + + /* + * Close stream. + */ + test_data.running = 0; + pjmedia_aud_stream_destroy(strm); + pj_pool_release(test_data.pool); + + + /* + * Gather results + */ + ptime = param->samples_per_frame * 1000 / param->clock_rate; + + tmp = pj_math_stat_get_stddev(&test_data.capture_data.delay); + result->rec.frame_cnt = test_data.capture_data.delay.n; + result->rec.min_interval = DIV_ROUND(test_data.capture_data.delay.min, 1000); + result->rec.max_interval = DIV_ROUND(test_data.capture_data.delay.max, 1000); + result->rec.avg_interval = DIV_ROUND(test_data.capture_data.delay.mean, 1000); + result->rec.dev_interval = DIV_ROUND(tmp, 1000); + result->rec.max_burst = DIV_ROUND_UP(result->rec.max_interval, ptime); + + tmp = pj_math_stat_get_stddev(&test_data.playback_data.delay); + result->play.frame_cnt = test_data.playback_data.delay.n; + result->play.min_interval = DIV_ROUND(test_data.playback_data.delay.min, 1000); + result->play.max_interval = DIV_ROUND(test_data.playback_data.delay.max, 1000); + result->play.avg_interval = DIV_ROUND(test_data.playback_data.delay.mean, 1000); + result->play.dev_interval = DIV_ROUND(tmp, 1000); + result->play.max_burst = DIV_ROUND_UP(result->play.max_interval, ptime); + + /* Check drifting */ + if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { + int play_diff, cap_diff, drift; + + play_diff = test_data.playback_data.last_timestamp - + test_data.playback_data.first_timestamp; + cap_diff = test_data.capture_data.last_timestamp - + test_data.capture_data.first_timestamp; + drift = play_diff > cap_diff? play_diff - cap_diff : + cap_diff - play_diff; + + /* Allow one frame tolerance for clock drift detection */ + if (drift < (int)param->samples_per_frame) { + result->rec_drift_per_sec = 0; + } else { + unsigned msec_dur; + + msec_dur = (test_data.capture_data.last_timestamp - + test_data.capture_data.first_timestamp) * 1000 / + test_data.param->clock_rate; + + result->rec_drift_per_sec = drift * 1000 / msec_dur; + + } + } + + return test_data.has_error? PJ_EUNKNOWN : PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia-audiodev/bb10_dev.c b/pjmedia/src/pjmedia-audiodev/bb10_dev.c new file mode 100644 index 0000000..418256d --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/bb10_dev.c @@ -0,0 +1,965 @@ +/* $Id: bb10_dev.c 4151 2012-06-01 04:49:57Z ming $ */ +/* + * 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 +#include +#include +#include +#include +#include + +#if defined(PJMEDIA_AUDIO_DEV_HAS_BB10) && PJMEDIA_AUDIO_DEV_HAS_BB10 != 0 + +#include +#include +#include +#include +#include +#include +#include + + +#define THIS_FILE "bb10_dev.c" +#define BB10_DEVICE_NAME "plughw:%d,%d" +/* Double these for 16khz sampling */ +#define PREFERRED_FRAME_SIZE 320 +#define VOIP_SAMPLE_RATE 8000 + +/* 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 */ + snd_pcm_t *pb_pcm; + snd_mixer_t *pb_mixer; + 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; + snd_mixer_t *ca_mixer; + 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; + int card = -1; + int dev = 0; + 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 = snd_pcm_open_preferred (&pcm_handle, &card, &dev, + SND_PCM_OPEN_PLAYBACK)) >= 0) + { + TRACE_((THIS_FILE, "Try to open the device for playback - success")); + snd_pcm_close (pcm_handle); + } else { + TRACE_((THIS_FILE, "Try to open the device for playback - failure")); + } + + if ((ca_result = snd_pcm_open_preferred (&pcm_handle, &card, &dev, + SND_PCM_OPEN_CAPTURE)) >=0) + { + TRACE_((THIS_FILE, "Try to open the device for capture - success")); + snd_pcm_close (pcm_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 && indexdev_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; + + 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 && indexdev_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; + } +} + +static void close_play_mixer(struct bb10_stream *stream) +{ + if (stream != NULL && stream->pb_mixer != NULL) { + snd_mixer_close(stream->pb_mixer); + stream->pb_mixer = NULL; + } +} + +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; + } +} + +static void close_capture_mixer(struct bb10_stream *stream) +{ + if (stream != NULL && stream->ca_mixer != NULL) { + snd_mixer_close(stream->ca_mixer); + stream->ca_mixer = NULL; + } +} + +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; + /* Handle from bb10_open_playback */ + /* Will be 640 */ + int size = stream->pb_buf_size; + /* 160 frames for 20ms */ + unsigned long nframes = stream->pb_frames; + void *user_data = stream->user_data; + char *buf = stream->pb_buf; + pj_timestamp tstamp; + int result = 0; + + pj_bzero (buf, size); + tstamp.u64 = 0; + + TRACE_((THIS_FILE, "pb_thread_func: size = %d ", size)); + + /* 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_mixer(stream); + 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; + + 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) { + TRACE_((THIS_FILE, "pb_thread_func failed write = %d", result)); + } + + tstamp.u64 += nframes; + } + + flush_play(stream); + close_play_mixer(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; + struct sched_param param; + pthread_t *thid; + + TRACE_((THIS_FILE, "ca_thread_func: size = %d ", size)); + + thid = (pthread_t*) pj_thread_get_os_handle (pj_thread_this()); + param.sched_priority = sched_get_priority_max (SCHED_RR); + + result = pthread_setschedparam (*thid, SCHED_RR, ¶m); + if (result) { + if (result == EPERM) { + PJ_LOG (4,(THIS_FILE, "Unable to increase thread priority, " + "root access needed.")); + } else { + PJ_LOG (4,(THIS_FILE, "Unable to increase thread priority, " + "error: %d", result)); + } + } + + 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_mixer(stream); + 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); + + result = snd_pcm_plugin_read(stream->ca_pcm, buf,size); + if (result == -EPIPE) { + PJ_LOG (4,(THIS_FILE, "ca_thread_func: overrun!")); + snd_pcm_plugin_prepare (stream->ca_pcm, SND_PCM_CHANNEL_CAPTURE); + continue; + } else if (result < 0) { + PJ_LOG (4,(THIS_FILE, "ca_thread_func: error reading data!")); + } + + if (stream->quit) + break; + + 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_mixer(stream); + close_capture_pcm(stream); + TRACE_((THIS_FILE, "ca_thread_func: Stopped")); + + return PJ_SUCCESS; +} + + +static pj_status_t bb10_open_playback (struct bb10_stream *stream, + const pjmedia_aud_param *param) +{ + int card = -1; + int dev = 0; + 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; + } + + if ((ret = snd_pcm_open_preferred (&stream->pb_pcm, &card, &dev, + SND_PCM_OPEN_PLAYBACK)) < 0) + { + TRACE_((THIS_FILE, "snd_pcm_open_preferred ret = %d", ret)); + return PJMEDIA_EAUD_SYSERR; + } + + /* TODO PJ_ZERO */ + 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 + * On simulator frag_size is always negotiated to 170 + */ + pp.mode = SND_PCM_MODE_BLOCK; + pp.channel = SND_PCM_CHANNEL_PLAYBACK; + pp.start_mode = SND_PCM_START_DATA; + pp.stop_mode = SND_PCM_STOP_ROLLOVER; + /* HARD CODE for the time being PJMEDIA expects 640 for 16khz */ + pp.buf.block.frag_size = PREFERRED_FRAME_SIZE*2; + /* Increasing this internal buffer count delays write failure in the loop */ + pp.buf.block.frags_max = 4; + pp.buf.block.frags_min = 1; + pp.format.interleave = 1; + /* HARD CODE for the time being PJMEDIA expects 16khz */ + PJ_TODO(REMOVE_SAMPLE_RATE_HARD_CODE); + pj_assert(param->clock_rate == VOIP_SAMPLE_RATE * 2); + pp.format.rate = VOIP_SAMPLE_RATE*2; + pp.format.voices = 1; + pp.format.format = SND_PCM_SFMT_S16_LE; + + /* 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; + } + + if ((ret = snd_mixer_open (&stream->pb_mixer, card, + setup.mixer_device)) < 0) + { + TRACE_((THIS_FILE, "snd_mixer_open ret = %d", ret)); + 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 / 1000) * param->output_latency_ms; + } else { + tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + } + /* Set period size to samples_per_frame frames. */ + stream->pb_frames = param->samples_per_frame; + stream->param.output_latency_ms = tmp_buf_size / (rate / 1000); + + /* 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 card = -1; + int dev = 0; + 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; + + /* BB10 Audio init here (not prepare) */ + if ((ret = snd_pcm_open_preferred (&stream->ca_pcm, &card, &dev, + SND_PCM_OPEN_CAPTURE)) < 0) + { + TRACE_((THIS_FILE, "snd_pcm_open_preferred ret = %d", ret)); + return PJMEDIA_EAUD_SYSERR; + } + + /* 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_DATA; + /* Auto-recover from errors */ + pp.stop_mode = SND_PCM_STOP_ROLLOVER; + /* HARD CODE for the time being PJMEDIA expects 640 for 16khz */ + pp.buf.block.frag_size = PREFERRED_FRAME_SIZE*2; + /* Not applicable for capture hence -1 */ + pp.buf.block.frags_max = -1; + pp.buf.block.frags_min = 1; + pp.format.interleave = 1; + /* HARD CODE for the time being PJMEDIA expects 16khz */ + PJ_TODO(REMOVE_SAMPLE_RATE_HARD_CODE); + pj_assert(param->clock_rate == VOIP_SAMPLE_RATE * 2); + pp.format.rate = VOIP_SAMPLE_RATE*2; + pp.format.voices = 1; + pp.format.format = SND_PCM_SFMT_S16_LE; + + /* 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 { + } + + if ((ret = snd_mixer_open (&stream->ca_mixer, card, + setup.mixer_device)) < 0) + { + TRACE_((THIS_FILE,"snd_mixer_open ret = %d",ret)); + return PJMEDIA_EAUD_SYSERR; + } + + /* frag_size should be 160 */ + frame_size = setup.buf.block.frag_size; + + /* END BB10 init */ + + /* 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 / 1000) * param->input_latency_ms; + } else { + tmp_buf_size = (rate / 1000) * PJMEDIA_SND_DEFAULT_REC_LATENCY; + } + + stream->param.input_latency_ms = tmp_buf_size / (rate / 1000); + + /* 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_mixer(stream); + close_play_pcm(stream); + } + pj_pool_release (pool); + return status; + } + } + + *p_strm = &stream->base; + return PJ_SUCCESS; +} + + +/* API: get running parameter */ +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 */ +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 { + return PJMEDIA_EAUD_INVCAP; + } +} + + +/* API: set capability */ +static pj_status_t bb10_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value) +{ + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(value); + + 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 */ diff --git a/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c new file mode 100644 index 0000000..b0bad03 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/coreaudio_dev.c @@ -0,0 +1,2107 @@ +/* $Id: coreaudio_dev.c 4082 2012-04-24 13:09:14Z bennylp $ */ +/* + * Copyright (C) 2008-2011 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 + */ +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO + +#include "TargetConditionals.h" +#if TARGET_OS_IPHONE + #define COREAUDIO_MAC 0 +#else + #define COREAUDIO_MAC 1 +#endif + +#include +#include +#if !COREAUDIO_MAC + #include + + #define AudioDeviceID unsigned + + /** + * As in iOS SDK 4 or later, audio route change property listener is + * no longer necessary. Just make surethat your application can receive + * remote control events by adding the code: + * [[UIApplication sharedApplication] + * beginReceivingRemoteControlEvents]; + * Otherwise audio route change (such as headset plug/unplug) will not be + * processed while your application is in the background mode. + */ + #define USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER 0 + +#endif + +/* For Mac OS 10.5.x and earlier */ +#if AUDIO_UNIT_VERSION < 1060 + #define AudioComponent Component + #define AudioComponentDescription ComponentDescription + #define AudioComponentInstance ComponentInstance + #define AudioComponentFindNext FindNextComponent + #define AudioComponentInstanceNew OpenAComponent + #define AudioComponentInstanceDispose CloseComponent +#endif + + +#define THIS_FILE "coreaudio_dev.c" + +/* coreaudio device info */ +struct coreaudio_dev_info +{ + pjmedia_aud_dev_info info; + AudioDeviceID dev_id; +}; + +/* linked list of streams */ +struct stream_list +{ + PJ_DECL_LIST_MEMBER(struct stream_list); + struct coreaudio_stream *stream; +}; + +/* coreaudio factory */ +struct coreaudio_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *base_pool; + pj_pool_t *pool; + pj_pool_factory *pf; + pj_mutex_t *mutex; + + unsigned dev_count; + struct coreaudio_dev_info *dev_info; + + AudioComponent io_comp; + struct stream_list streams; +}; + +/* Sound stream. */ +struct coreaudio_stream +{ + pjmedia_aud_stream base; /**< Base stream */ + pjmedia_aud_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + struct coreaudio_factory *cf; + struct stream_list list_entry; + + pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */ + pjmedia_aud_play_cb play_cb; /**< Playback callback. */ + void *user_data; /**< Application data. */ + + pj_timestamp play_timestamp; + pj_timestamp rec_timestamp; + + pj_int16_t *rec_buf; + unsigned rec_buf_count; + pj_int16_t *play_buf; + unsigned play_buf_count; + + pj_bool_t interrupted; + pj_bool_t quit_flag; + pj_bool_t running; + + pj_bool_t rec_thread_initialized; + pj_thread_desc rec_thread_desc; + pj_thread_t *rec_thread; + + pj_bool_t play_thread_initialized; + pj_thread_desc play_thread_desc; + pj_thread_t *play_thread; + + AudioUnit io_units[2]; + AudioStreamBasicDescription streamFormat; + AudioBufferList *audio_buf; + + AudioConverterRef resample; + pj_int16_t *resample_buf; + void *resample_buf_ptr; + unsigned resample_buf_count; + unsigned resample_buf_size; +}; + +/* Static variable */ +static struct coreaudio_factory *cf_instance = NULL; + +/* Prototypes */ +static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t ca_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_aud_strm); + +static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t ca_stream_start(pjmedia_aud_stream *strm); +static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm); +static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm); +static pj_status_t create_audio_unit(AudioComponent io_comp, + AudioDeviceID dev_id, + pjmedia_dir dir, + struct coreaudio_stream *strm, + AudioUnit *io_unit); +#if !COREAUDIO_MAC +static void interruptionListener(void *inClientData, UInt32 inInterruption); +static void propListener(void * inClientData, + AudioSessionPropertyID inID, + UInt32 inDataSize, + const void * inData); +#endif + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &ca_factory_init, + &ca_factory_destroy, + &ca_factory_get_dev_count, + &ca_factory_get_dev_info, + &ca_factory_default_param, + &ca_factory_create_stream, + &ca_factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &ca_stream_get_param, + &ca_stream_get_cap, + &ca_stream_set_cap, + &ca_stream_start, + &ca_stream_stop, + &ca_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init coreaudio audio driver. + */ +pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf) +{ + struct coreaudio_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "core audio base", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct coreaudio_factory); + f->pf = pf; + f->base_pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t ca_factory_init(pjmedia_aud_dev_factory *f) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + AudioComponentDescription desc; + pj_status_t status; +#if !COREAUDIO_MAC + unsigned i; + OSStatus ostatus; +#endif + + pj_list_init(&cf->streams); + status = pj_mutex_create_recursive(cf->base_pool, + "coreaudio", + &cf->mutex); + if (status != PJ_SUCCESS) + return status; + + desc.componentType = kAudioUnitType_Output; +#if COREAUDIO_MAC + desc.componentSubType = kAudioUnitSubType_HALOutput; +#else + desc.componentSubType = kAudioUnitSubType_RemoteIO; +#endif + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + cf->io_comp = AudioComponentFindNext(NULL, &desc); + if (cf->io_comp == NULL) + return PJMEDIA_EAUD_INIT; // cannot find IO unit; + + status = ca_factory_refresh(f); + if (status != PJ_SUCCESS) + return status; + +#if !COREAUDIO_MAC + cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL); + cf->dev_count = 1; + cf->dev_info = (struct coreaudio_dev_info*) + pj_pool_calloc(cf->pool, cf->dev_count, + sizeof(struct coreaudio_dev_info)); + for (i = 0; i < cf->dev_count; i++) { + struct coreaudio_dev_info *cdi; + + cdi = &cf->dev_info[i]; + pj_bzero(cdi, sizeof(*cdi)); + cdi->dev_id = 0; + strcpy(cdi->info.name, "iPhone IO device"); + strcpy(cdi->info.driver, "apple"); + cdi->info.input_count = 1; + cdi->info.output_count = 1; + cdi->info.default_samples_per_sec = 8000; + + /* Set the device capabilities here */ + cdi->info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE | + PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE | + PJMEDIA_AUD_DEV_CAP_EC; + cdi->info.routes = PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER | + PJMEDIA_AUD_DEV_ROUTE_EARPIECE | + PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH; + + PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz", + i, + cdi->info.name, + cdi->info.input_count, + cdi->info.output_count, + cdi->info.default_samples_per_sec)); + } + + /* Initialize the Audio Session */ + ostatus = AudioSessionInitialize(NULL, NULL, interruptionListener, NULL); + if (ostatus != kAudioSessionNoError) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot initialize audio session services (%i)", + ostatus)); + } + + /* Listen for audio routing change notifications. */ +#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0 + ostatus = AudioSessionAddPropertyListener( + kAudioSessionProperty_AudioRouteChange, + propListener, cf); + if (ostatus != kAudioSessionNoError) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot listen for audio route change " + "notifications (%i)", ostatus)); + } +#endif + + cf_instance = cf; +#endif + + PJ_LOG(4, (THIS_FILE, "core audio initialized")); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t ca_factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + pj_pool_t *pool; + + pj_assert(cf); + pj_assert(cf->base_pool); + pj_assert(pj_list_empty(&cf->streams)); + +#if !COREAUDIO_MAC +#if USE_AUDIO_ROUTE_CHANGE_PROP_LISTENER != 0 + AudioSessionRemovePropertyListenerWithUserData( + kAudioSessionProperty_AudioRouteChange, propListener, cf); +#endif +#endif + + if (cf->pool) { + pj_pool_release(cf->pool); + cf->pool = NULL; + } + + if (cf->mutex) { + pj_mutex_lock(cf->mutex); + cf_instance = NULL; + pj_mutex_unlock(cf->mutex); + pj_mutex_destroy(cf->mutex); + cf->mutex = NULL; + } + + pool = cf->base_pool; + cf->base_pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the device list */ +static pj_status_t ca_factory_refresh(pjmedia_aud_dev_factory *f) +{ +#if !COREAUDIO_MAC + /* iPhone doesn't support refreshing the device list */ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +#else + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + unsigned i; + unsigned dev_count; + AudioObjectPropertyAddress addr; + AudioDeviceID *dev_ids; + UInt32 buf_size, dev_size, size = sizeof(AudioDeviceID); + AudioBufferList *buf = NULL; + OSStatus ostatus; + + if (cf->pool != NULL) { + pj_pool_release(cf->pool); + cf->pool = NULL; + } + + cf->dev_count = 0; + cf->pool = pj_pool_create(cf->pf, "core audio", 1000, 1000, NULL); + + /* Find out how many audio devices there are */ + addr.mSelector = kAudioHardwarePropertyDevices; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + ostatus = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr, + 0, NULL, &dev_size); + if (ostatus != noErr) { + dev_size = 0; + } + + /* Calculate the number of audio devices available */ + dev_count = dev_size / size; + if (dev_count==0) { + PJ_LOG(4,(THIS_FILE, "core audio found no sound devices")); + /* Enabling this will cause pjsua-lib initialization to fail when + * there is no sound device installed in the system, even when pjsua + * has been run with --null-audio. Moreover, it might be better to + * think that the core audio backend initialization is successful, + * regardless there is no audio device installed, as later application + * can check it using get_dev_count(). + return PJMEDIA_EAUD_NODEV; + */ + return PJ_SUCCESS; + } + PJ_LOG(4, (THIS_FILE, "core audio detected %d devices", + dev_count)); + + /* Get all the audio device IDs */ + dev_ids = (AudioDeviceID *)pj_pool_calloc(cf->pool, dev_size, size); + if (!dev_ids) + return PJ_ENOMEM; + pj_bzero(dev_ids, dev_count); + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr, + 0, NULL, + &dev_size, (void *)dev_ids); + if (ostatus != noErr ) { + /* This should not happen since we have successfully retrieved + * the property data size before + */ + return PJMEDIA_EAUD_INIT; + } + + if (dev_size > 1) { + AudioDeviceID dev_id = kAudioObjectUnknown; + unsigned idx = 0; + + /* Find default audio input device */ + addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(dev_id); + + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &addr, 0, NULL, + &size, (void *)&dev_id); + if (ostatus != noErr && dev_id != dev_ids[idx]) { + AudioDeviceID temp_id = dev_ids[idx]; + + for (i = idx + 1; i < dev_size; i++) { + if (dev_ids[i] == dev_id) { + dev_ids[idx++] = dev_id; + dev_ids[i] = temp_id; + break; + } + } + } + + /* Find default audio output device */ + addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &addr, 0, NULL, + &size, (void *)&dev_id); + if (ostatus != noErr && dev_id != dev_ids[idx]) { + AudioDeviceID temp_id = dev_ids[idx]; + + for (i = idx + 1; i < dev_size; i++) { + if (dev_ids[i] == dev_id) { + dev_ids[idx] = dev_id; + dev_ids[i] = temp_id; + break; + } + } + } + } + + /* Build the devices' info */ + cf->dev_info = (struct coreaudio_dev_info*) + pj_pool_calloc(cf->pool, dev_count, + sizeof(struct coreaudio_dev_info)); + buf_size = 0; + for (i = 0; i < dev_count; i++) { + struct coreaudio_dev_info *cdi; + Float64 sampleRate; + + cdi = &cf->dev_info[i]; + pj_bzero(cdi, sizeof(*cdi)); + cdi->dev_id = dev_ids[i]; + + /* Get device name */ + addr.mSelector = kAudioDevicePropertyDeviceName; + addr.mScope = kAudioObjectPropertyScopeGlobal; + addr.mElement = kAudioObjectPropertyElementMaster; + size = sizeof(cdi->info.name); + AudioObjectGetPropertyData(cdi->dev_id, &addr, + 0, NULL, + &size, (void *)cdi->info.name); + + strcpy(cdi->info.driver, "core audio"); + + /* Get the number of input channels */ + addr.mSelector = kAudioDevicePropertyStreamConfiguration; + addr.mScope = kAudioDevicePropertyScopeInput; + size = 0; + ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr, + 0, NULL, &size); + if (ostatus == noErr && size > 0) { + + if (size > buf_size) { + buf = pj_pool_alloc(cf->pool, size); + buf_size = size; + } + if (buf) { + UInt32 idx; + + /* Get the input stream configuration */ + ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr, + 0, NULL, + &size, buf); + if (ostatus == noErr) { + /* Count the total number of input channels in + * the stream + */ + for (idx = 0; idx < buf->mNumberBuffers; idx++) { + cdi->info.input_count += + buf->mBuffers[idx].mNumberChannels; + } + } + } + } + + /* Get the number of output channels */ + addr.mScope = kAudioDevicePropertyScopeOutput; + size = 0; + ostatus = AudioObjectGetPropertyDataSize(cdi->dev_id, &addr, + 0, NULL, &size); + if (ostatus == noErr && size > 0) { + + if (size > buf_size) { + buf = pj_pool_alloc(cf->pool, size); + buf_size = size; + } + if (buf) { + UInt32 idx; + + /* Get the output stream configuration */ + ostatus = AudioObjectGetPropertyData(cdi->dev_id, &addr, + 0, NULL, + &size, buf); + if (ostatus == noErr) { + /* Count the total number of output channels in + * the stream + */ + for (idx = 0; idx < buf->mNumberBuffers; idx++) { + cdi->info.output_count += + buf->mBuffers[idx].mNumberChannels; + } + } + } + } + + /* Get default sample rate */ + addr.mSelector = kAudioDevicePropertyNominalSampleRate; + addr.mScope = kAudioObjectPropertyScopeGlobal; + size = sizeof(Float64); + ostatus = AudioObjectGetPropertyData (cdi->dev_id, &addr, + 0, NULL, + &size, &sampleRate); + cdi->info.default_samples_per_sec = (ostatus == noErr ? + sampleRate: + 16000); + + /* Set device capabilities here */ + if (cdi->info.input_count > 0) { + cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + } + if (cdi->info.output_count > 0) { + cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + addr.mSelector = kAudioDevicePropertyVolumeScalar; + addr.mScope = kAudioDevicePropertyScopeOutput; + if (AudioObjectHasProperty(cdi->dev_id, &addr)) { + cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + } + + cf->dev_count++; + + PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d) %dHz", + i, + cdi->info.name, + cdi->info.input_count, + cdi->info.output_count, + cdi->info.default_samples_per_sec)); + } + + return PJ_SUCCESS; +#endif +} + +/* API: get number of devices */ +static unsigned ca_factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + return cf->dev_count; +} + +/* API: get device info */ +static pj_status_t ca_factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t ca_factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + struct coreaudio_dev_info *di = &cf->dev_info[index]; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_bzero(param, sizeof(*param)); + if (di->info.input_count && di->info.output_count) { + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + } else if (di->info.input_count) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->rec_id = index; + param->play_id = PJMEDIA_AUD_INVALID_DEV; + } else if (di->info.output_count) { + param->dir = PJMEDIA_DIR_PLAYBACK; + param->play_id = index; + param->rec_id = PJMEDIA_AUD_INVALID_DEV; + } else { + return PJMEDIA_EAUD_INVDEV; + } + + /* Set the mandatory settings here */ + param->clock_rate = di->info.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = 16; + + /* Set the param for device capabilities here */ + param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; + param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + + return PJ_SUCCESS; +} + +OSStatus resampleProc(AudioConverterRef inAudioConverter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **outDataPacketDescription, + void *inUserData) +{ + struct coreaudio_stream *strm = (struct coreaudio_stream*)inUserData; + + if (*ioNumberDataPackets > strm->resample_buf_size) + *ioNumberDataPackets = strm->resample_buf_size; + + ioData->mNumberBuffers = 1; + ioData->mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame; + ioData->mBuffers[0].mData = strm->resample_buf_ptr; + ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets * + strm->streamFormat.mChannelsPerFrame * + strm->param.bits_per_sample >> 3; + + return noErr; +} + +static OSStatus resample_callback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon; + OSStatus ostatus; + pj_status_t status = 0; + unsigned nsamples; + AudioBufferList *buf = strm->audio_buf; + pj_int16_t *input; + UInt32 resampleSize; + + pj_assert(!strm->quit_flag); + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("ca_rec", strm->rec_thread_desc, + &strm->rec_thread); + strm->rec_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)", + inNumberFrames)); + } + + buf->mBuffers[0].mData = NULL; + buf->mBuffers[0].mDataByteSize = inNumberFrames * + strm->streamFormat.mChannelsPerFrame; + /* Render the unit to get input data */ + ostatus = AudioUnitRender(strm->io_units[0], + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + buf); + + if (ostatus != noErr) { + PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus)); + goto on_break; + } + input = (pj_int16_t *)buf->mBuffers[0].mData; + + resampleSize = strm->resample_buf_size; + nsamples = inNumberFrames * strm->param.channel_count + + strm->resample_buf_count; + + if (nsamples >= resampleSize) { + pjmedia_frame frame; + UInt32 resampleOutput = strm->param.samples_per_frame / + strm->streamFormat.mChannelsPerFrame; + AudioBufferList ab; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = (void*) strm->rec_buf; + frame.size = strm->param.samples_per_frame * + strm->param.bits_per_sample >> 3; + frame.bit_info = 0; + + ab.mNumberBuffers = 1; + ab.mBuffers[0].mNumberChannels = strm->streamFormat.mChannelsPerFrame; + ab.mBuffers[0].mData = strm->rec_buf; + ab.mBuffers[0].mDataByteSize = frame.size; + + /* If buffer is not empty, combine the buffer with the just incoming + * samples, then call put_frame. + */ + if (strm->resample_buf_count) { + unsigned chunk_count = resampleSize - strm->resample_buf_count; + pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count, + input, chunk_count); + + /* Do the resample */ + + strm->resample_buf_ptr = strm->resample_buf; + ostatus = AudioConverterFillComplexBuffer(strm->resample, + resampleProc, + strm, + &resampleOutput, + &ab, + NULL); + if (ostatus != noErr) { + goto on_break; + } + frame.timestamp.u64 = strm->rec_timestamp.u64; + + status = (*strm->rec_cb)(strm->user_data, &frame); + + input = input + chunk_count; + nsamples -= resampleSize; + strm->resample_buf_count = 0; + strm->rec_timestamp.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + } + + + /* Give all frames we have */ + while (nsamples >= resampleSize && status == 0) { + frame.timestamp.u64 = strm->rec_timestamp.u64; + + /* Do the resample */ + strm->resample_buf_ptr = input; + ab.mBuffers[0].mDataByteSize = frame.size; + resampleOutput = strm->param.samples_per_frame / + strm->streamFormat.mChannelsPerFrame; + ostatus = AudioConverterFillComplexBuffer(strm->resample, + resampleProc, + strm, + &resampleOutput, + &ab, + NULL); + if (ostatus != noErr) { + goto on_break; + } + + status = (*strm->rec_cb)(strm->user_data, &frame); + + input = (pj_int16_t*) input + resampleSize; + nsamples -= resampleSize; + strm->rec_timestamp.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + } + + /* Store the remaining samples into the buffer */ + if (nsamples && status == 0) { + strm->resample_buf_count = nsamples; + pjmedia_copy_samples(strm->resample_buf, input, + nsamples); + } + + } else { + /* Not enough samples, let's just store them in the buffer */ + pjmedia_copy_samples(strm->resample_buf + strm->resample_buf_count, + input, + inNumberFrames * strm->param.channel_count); + strm->resample_buf_count += inNumberFrames * + strm->param.channel_count; + } + + return noErr; + +on_break: + return -1; +} + +static OSStatus input_callback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + struct coreaudio_stream *strm = (struct coreaudio_stream*)inRefCon; + OSStatus ostatus; + pj_status_t status = 0; + unsigned nsamples; + AudioBufferList *buf = strm->audio_buf; + pj_int16_t *input; + + pj_assert(!strm->quit_flag); + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (strm->rec_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(strm->rec_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("ca_rec", strm->rec_thread_desc, + &strm->rec_thread); + strm->rec_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Recorder thread started, (%i frames)", + inNumberFrames)); + } + + buf->mBuffers[0].mData = NULL; + buf->mBuffers[0].mDataByteSize = inNumberFrames * + strm->streamFormat.mChannelsPerFrame; + /* Render the unit to get input data */ + ostatus = AudioUnitRender(strm->io_units[0], + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + buf); + + if (ostatus != noErr) { + PJ_LOG(5, (THIS_FILE, "Core audio unit render error %i", ostatus)); + goto on_break; + } + input = (pj_int16_t *)buf->mBuffers[0].mData; + + /* Calculate number of samples we've got */ + nsamples = inNumberFrames * strm->param.channel_count + + strm->rec_buf_count; + if (nsamples >= strm->param.samples_per_frame) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.size = strm->param.samples_per_frame * + strm->param.bits_per_sample >> 3; + frame.bit_info = 0; + + /* If buffer is not empty, combine the buffer with the just incoming + * samples, then call put_frame. + */ + if (strm->rec_buf_count) { + unsigned chunk_count = 0; + + chunk_count = strm->param.samples_per_frame - strm->rec_buf_count; + pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count, + input, chunk_count); + + frame.buf = (void*) strm->rec_buf; + frame.timestamp.u64 = strm->rec_timestamp.u64; + + status = (*strm->rec_cb)(strm->user_data, &frame); + + input = input + chunk_count; + nsamples -= strm->param.samples_per_frame; + strm->rec_buf_count = 0; + strm->rec_timestamp.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + } + + /* Give all frames we have */ + while (nsamples >= strm->param.samples_per_frame && status == 0) { + frame.buf = (void*) input; + frame.timestamp.u64 = strm->rec_timestamp.u64; + + status = (*strm->rec_cb)(strm->user_data, &frame); + + input = (pj_int16_t*) input + strm->param.samples_per_frame; + nsamples -= strm->param.samples_per_frame; + strm->rec_timestamp.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + } + + /* Store the remaining samples into the buffer */ + if (nsamples && status == 0) { + strm->rec_buf_count = nsamples; + pjmedia_copy_samples(strm->rec_buf, input, + nsamples); + } + + } else { + /* Not enough samples, let's just store them in the buffer */ + pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_count, + input, + inNumberFrames * strm->param.channel_count); + strm->rec_buf_count += inNumberFrames * strm->param.channel_count; + } + + return noErr; + +on_break: + return -1; +} + +static OSStatus output_renderer(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + struct coreaudio_stream *stream = (struct coreaudio_stream*)inRefCon; + pj_status_t status = 0; + unsigned nsamples_req = inNumberFrames * stream->param.channel_count; + pj_int16_t *output = ioData->mBuffers[0].mData; + + pj_assert(!stream->quit_flag); + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("coreaudio", stream->play_thread_desc, + &stream->play_thread); + stream->play_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Player thread started, (%i frames)", + inNumberFrames)); + } + + + /* Check if any buffered samples */ + if (stream->play_buf_count) { + /* samples buffered >= requested by sound device */ + if (stream->play_buf_count >= nsamples_req) { + pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, + nsamples_req); + stream->play_buf_count -= nsamples_req; + pjmedia_move_samples(stream->play_buf, + stream->play_buf + nsamples_req, + stream->play_buf_count); + nsamples_req = 0; + + return noErr; + } + + /* samples buffered < requested by sound device */ + pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, + stream->play_buf_count); + nsamples_req -= stream->play_buf_count; + output = (pj_int16_t*)output + stream->play_buf_count; + stream->play_buf_count = 0; + } + + /* Fill output buffer as requested */ + while (nsamples_req && status == 0) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.size = stream->param.samples_per_frame * + stream->param.bits_per_sample >> 3; + frame.timestamp.u64 = stream->play_timestamp.u64; + frame.bit_info = 0; + + if (nsamples_req >= stream->param.samples_per_frame) { + frame.buf = output; + status = (*stream->play_cb)(stream->user_data, &frame); + if (status != PJ_SUCCESS) + goto on_break; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frame.buf, frame.size); + + nsamples_req -= stream->param.samples_per_frame; + output = (pj_int16_t*)output + stream->param.samples_per_frame; + } else { + frame.buf = stream->play_buf; + status = (*stream->play_cb)(stream->user_data, &frame); + if (status != PJ_SUCCESS) + goto on_break; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frame.buf, frame.size); + + pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, + nsamples_req); + stream->play_buf_count = stream->param.samples_per_frame - + nsamples_req; + pjmedia_move_samples(stream->play_buf, + stream->play_buf+nsamples_req, + stream->play_buf_count); + nsamples_req = 0; + } + + stream->play_timestamp.u64 += stream->param.samples_per_frame / + stream->param.channel_count; + } + + return noErr; + +on_break: + return -1; +} + +#if !COREAUDIO_MAC +static void propListener(void *inClientData, + AudioSessionPropertyID inID, + UInt32 inDataSize, + const void * inData) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)inClientData; + struct stream_list *it, *itBegin; + CFDictionaryRef routeDictionary; + CFNumberRef reason; + SInt32 reasonVal; + pj_assert(cf); + + if (inID != kAudioSessionProperty_AudioRouteChange) + return; + + routeDictionary = (CFDictionaryRef)inData; + reason = (CFNumberRef) + CFDictionaryGetValue( + routeDictionary, + CFSTR(kAudioSession_AudioRouteChangeKey_Reason)); + CFNumberGetValue(reason, kCFNumberSInt32Type, &reasonVal); + + if (reasonVal != kAudioSessionRouteChangeReason_OldDeviceUnavailable) { + PJ_LOG(3, (THIS_FILE, "ignoring audio route change...")); + return; + } + + PJ_LOG(3, (THIS_FILE, "audio route changed")); + + pj_mutex_lock(cf->mutex); + itBegin = &cf->streams; + for (it = itBegin->next; it != itBegin; it = it->next) { + if (it->stream->interrupted) + continue; + + /* + status = ca_stream_stop((pjmedia_aud_stream *)it->stream); + status = ca_stream_start((pjmedia_aud_stream *)it->stream); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, + "Error: failed to restart the audio unit (%i)", + status)); + continue; + } + PJ_LOG(3, (THIS_FILE, "core audio unit successfully restarted")); + */ + } + pj_mutex_unlock(cf->mutex); +} + +static void interruptionListener(void *inClientData, UInt32 inInterruption) +{ + struct stream_list *it, *itBegin; + pj_status_t status; + pj_thread_desc thread_desc; + pj_thread_t *thread; + + /* Register the thread with PJLIB, this is must for any external threads + * which need to use the PJLIB framework. + */ + if (!pj_thread_is_registered()) { + pj_bzero(thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("intListener", thread_desc, &thread); + } + + PJ_LOG(3, (THIS_FILE, "Session interrupted! --- %s ---", + inInterruption == kAudioSessionBeginInterruption ? + "Begin Interruption" : "End Interruption")); + + if (!cf_instance) + return; + + pj_mutex_lock(cf_instance->mutex); + itBegin = &cf_instance->streams; + for (it = itBegin->next; it != itBegin; it = it->next) { + if (inInterruption == kAudioSessionEndInterruption && + it->stream->interrupted == PJ_TRUE) + { + UInt32 audioCategory; + OSStatus ostatus; + + /* Make sure that your application can receive remote control + * events by adding the code: + * [[UIApplication sharedApplication] + * beginReceivingRemoteControlEvents]; + * Otherwise audio unit will fail to restart while your + * application is in the background mode. + */ + /* Make sure we set the correct audio category before restarting */ + audioCategory = kAudioSessionCategory_PlayAndRecord; + ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, + sizeof(audioCategory), + &audioCategory); + if (ostatus != kAudioSessionNoError) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot set the audio session category (%i)", + ostatus)); + } + + /* Restart the stream */ + status = ca_stream_start((pjmedia_aud_stream*)it->stream); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, + "Error: failed to restart the audio unit (%i)", + status)); + continue; + } + PJ_LOG(3, (THIS_FILE, "core audio unit successfully resumed" + " after interruption")); + } else if (inInterruption == kAudioSessionBeginInterruption && + it->stream->running == PJ_TRUE) + { + status = ca_stream_stop((pjmedia_aud_stream*)it->stream); + it->stream->interrupted = PJ_TRUE; + } + } + pj_mutex_unlock(cf_instance->mutex); +} + +#endif + +#if COREAUDIO_MAC +/* Internal: create audio converter for resampling the recorder device */ +static pj_status_t create_audio_resample(struct coreaudio_stream *strm, + AudioStreamBasicDescription *desc) +{ + OSStatus ostatus; + + pj_assert(strm->streamFormat.mSampleRate != desc->mSampleRate); + pj_assert(NULL == strm->resample); + pj_assert(NULL == strm->resample_buf); + + /* Create the audio converter */ + ostatus = AudioConverterNew(desc, &strm->streamFormat, &strm->resample); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + /* + * Allocate the buffer required to hold enough input data + */ + strm->resample_buf_size = (unsigned)(desc->mSampleRate * + strm->param.samples_per_frame / + strm->param.clock_rate); + strm->resample_buf = (pj_int16_t*) + pj_pool_alloc(strm->pool, + strm->resample_buf_size * + strm->param.bits_per_sample >> 3); + if (!strm->resample_buf) + return PJ_ENOMEM; + strm->resample_buf_count = 0; + + return PJ_SUCCESS; +} +#endif + +/* Internal: create audio unit for recorder/playback device */ +static pj_status_t create_audio_unit(AudioComponent io_comp, + AudioDeviceID dev_id, + pjmedia_dir dir, + struct coreaudio_stream *strm, + AudioUnit *io_unit) +{ + OSStatus ostatus; +#if !COREAUDIO_MAC + UInt32 audioCategory = kAudioSessionCategory_PlayAndRecord; + /* We want to be able to open playback and recording streams */ + ostatus = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, + sizeof(audioCategory), + &audioCategory); + if (ostatus != kAudioSessionNoError) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot set the audio session category (%i)", + ostatus)); + } +#endif + + /* Create an audio unit to interface with the device */ + ostatus = AudioComponentInstanceNew(io_comp, io_unit); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + /* Set audio unit's properties for capture device */ + if (dir & PJMEDIA_DIR_CAPTURE) { + UInt32 enable = 1; + + /* Enable input */ + ostatus = AudioUnitSetProperty(*io_unit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + 1, + &enable, + sizeof(enable)); + if (ostatus != noErr) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot enable IO of capture device %d", + dev_id)); + } + + /* Disable output */ + if (!(dir & PJMEDIA_DIR_PLAYBACK)) { + enable = 0; + ostatus = AudioUnitSetProperty(*io_unit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, + &enable, + sizeof(enable)); + if (ostatus != noErr) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot disable IO of capture device %d", + dev_id)); + } + } + } + + /* Set audio unit's properties for playback device */ + if (dir & PJMEDIA_DIR_PLAYBACK) { + UInt32 enable = 1; + + /* Enable output */ + ostatus = AudioUnitSetProperty(*io_unit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + 0, + &enable, + sizeof(enable)); + if (ostatus != noErr) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot enable IO of playback device %d", + dev_id)); + } + + } + +#if COREAUDIO_MAC + PJ_LOG(5, (THIS_FILE, "Opening device %d", dev_id)); + ostatus = AudioUnitSetProperty(*io_unit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &dev_id, + sizeof(dev_id)); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } +#endif + + if (dir & PJMEDIA_DIR_CAPTURE) { +#if COREAUDIO_MAC + AudioStreamBasicDescription deviceFormat; + UInt32 size; + + /* + * Keep the sample rate from the device, otherwise we will confuse + * AUHAL + */ + size = sizeof(AudioStreamBasicDescription); + ostatus = AudioUnitGetProperty(*io_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 1, + &deviceFormat, + &size); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + strm->streamFormat.mSampleRate = deviceFormat.mSampleRate; +#endif + + /* When setting the stream format, we have to make sure the sample + * rate is supported. Setting an unsupported sample rate will cause + * AudioUnitRender() to fail later. + */ + ostatus = AudioUnitSetProperty(*io_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &strm->streamFormat, + sizeof(strm->streamFormat)); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + +#if COREAUDIO_MAC + strm->streamFormat.mSampleRate = strm->param.clock_rate; + size = sizeof(AudioStreamBasicDescription); + ostatus = AudioUnitGetProperty (*io_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 1, + &deviceFormat, + &size); + if (ostatus == noErr) { + if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) { + pj_status_t rc = create_audio_resample(strm, &deviceFormat); + if (PJ_SUCCESS != rc) + return rc; + } + } else { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } +#endif + } + + if (dir & PJMEDIA_DIR_PLAYBACK) { + AURenderCallbackStruct output_cb; + + /* Set the stream format */ + ostatus = AudioUnitSetProperty(*io_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &strm->streamFormat, + sizeof(strm->streamFormat)); + if (ostatus != noErr) { + PJ_LOG(4, (THIS_FILE, + "Warning: cannot set playback stream format of dev %d", + dev_id)); + } + + /* Set render callback */ + output_cb.inputProc = output_renderer; + output_cb.inputProcRefCon = strm; + ostatus = AudioUnitSetProperty(*io_unit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &output_cb, + sizeof(output_cb)); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + /* Allocate playback buffer */ + strm->play_buf = (pj_int16_t*)pj_pool_alloc(strm->pool, + strm->param.samples_per_frame * + strm->param.bits_per_sample >> 3); + if (!strm->play_buf) + return PJ_ENOMEM; + strm->play_buf_count = 0; + } + + if (dir & PJMEDIA_DIR_CAPTURE) { + AURenderCallbackStruct input_cb; +#if COREAUDIO_MAC + AudioBuffer *ab; + UInt32 size, buf_size; +#endif + + /* Set input callback */ + input_cb.inputProc = strm->resample ? resample_callback : + input_callback; + input_cb.inputProcRefCon = strm; + ostatus = AudioUnitSetProperty( + *io_unit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + 0, + &input_cb, + sizeof(input_cb)); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + +#if COREAUDIO_MAC + /* Get device's buffer frame size */ + size = sizeof(UInt32); + ostatus = AudioUnitGetProperty(*io_unit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Global, + 0, + &buf_size, + &size); + if (ostatus != noErr) + { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + /* Allocate audio buffer */ + strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool, + sizeof(AudioBufferList) + sizeof(AudioBuffer)); + if (!strm->audio_buf) + return PJ_ENOMEM; + + strm->audio_buf->mNumberBuffers = 1; + ab = &strm->audio_buf->mBuffers[0]; + ab->mNumberChannels = strm->streamFormat.mChannelsPerFrame; + ab->mDataByteSize = buf_size * ab->mNumberChannels * + strm->param.bits_per_sample >> 3; + ab->mData = pj_pool_alloc(strm->pool, + ab->mDataByteSize); + if (!ab->mData) + return PJ_ENOMEM; + +#else + /* We will let AudioUnitRender() to allocate the buffer + * for us later + */ + strm->audio_buf = (AudioBufferList*)pj_pool_alloc(strm->pool, + sizeof(AudioBufferList) + sizeof(AudioBuffer)); + if (!strm->audio_buf) + return PJ_ENOMEM; + + strm->audio_buf->mNumberBuffers = 1; + strm->audio_buf->mBuffers[0].mNumberChannels = + strm->streamFormat.mChannelsPerFrame; + +#endif + + /* Allocate recording buffer */ + strm->rec_buf = (pj_int16_t*)pj_pool_alloc(strm->pool, + strm->param.samples_per_frame * + strm->param.bits_per_sample >> 3); + if (!strm->rec_buf) + return PJ_ENOMEM; + strm->rec_buf_count = 0; + } + + /* Initialize the audio unit */ + ostatus = AudioUnitInitialize(*io_unit); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + return PJ_SUCCESS; +} + +/* API: create stream */ +static pj_status_t ca_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_aud_strm) +{ + struct coreaudio_factory *cf = (struct coreaudio_factory*)f; + pj_pool_t *pool; + struct coreaudio_stream *strm; + pj_status_t status; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(cf->pf, "coreaudio-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct coreaudio_stream); + pj_list_init(&strm->list_entry); + strm->list_entry.stream = strm; + strm->cf = cf; + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + strm->rec_cb = rec_cb; + strm->play_cb = play_cb; + strm->user_data = user_data; + + /* Set the stream format */ + strm->streamFormat.mSampleRate = param->clock_rate; + strm->streamFormat.mFormatID = kAudioFormatLinearPCM; + strm->streamFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + | kLinearPCMFormatFlagIsPacked; + strm->streamFormat.mBitsPerChannel = strm->param.bits_per_sample; + strm->streamFormat.mChannelsPerFrame = param->channel_count; + strm->streamFormat.mBytesPerFrame = strm->streamFormat.mChannelsPerFrame + * strm->param.bits_per_sample >> 3; + strm->streamFormat.mFramesPerPacket = 1; + strm->streamFormat.mBytesPerPacket = strm->streamFormat.mBytesPerFrame * + strm->streamFormat.mFramesPerPacket; + + /* Apply input/output routes settings before we create the audio units */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE) { + ca_stream_set_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, + ¶m->input_route); + } + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) { + ca_stream_set_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, + ¶m->output_route); + } + if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) { + ca_stream_set_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_EC, + ¶m->ec_enabled); + } else { + pj_bool_t ec = PJ_FALSE; + ca_stream_set_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_EC, &ec); + } + + strm->io_units[0] = strm->io_units[1] = NULL; + if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && + param->rec_id == param->play_id) + { + /* If both input and output are on the same device, only create + * one audio unit to interface with the device. + */ + status = create_audio_unit(cf->io_comp, + cf->dev_info[param->rec_id].dev_id, + param->dir, strm, &strm->io_units[0]); + if (status != PJ_SUCCESS) + goto on_error; + } else { + unsigned nunits = 0; + + if (param->dir & PJMEDIA_DIR_CAPTURE) { + status = create_audio_unit(cf->io_comp, + cf->dev_info[param->rec_id].dev_id, + PJMEDIA_DIR_CAPTURE, + strm, &strm->io_units[nunits++]); + if (status != PJ_SUCCESS) + goto on_error; + } + if (param->dir & PJMEDIA_DIR_PLAYBACK) { + + status = create_audio_unit(cf->io_comp, + cf->dev_info[param->play_id].dev_id, + PJMEDIA_DIR_PLAYBACK, + strm, &strm->io_units[nunits++]); + if (status != PJ_SUCCESS) + goto on_error; + } + } + + /* Apply the remaining settings */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) { + ca_stream_get_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, + &strm->param.input_latency_ms); + } + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) { + ca_stream_get_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, + &strm->param.output_latency_ms); + } + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + ca_stream_set_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + ¶m->output_vol); + } + + pj_mutex_lock(strm->cf->mutex); + pj_assert(pj_list_empty(&strm->list_entry)); + pj_list_insert_after(&strm->cf->streams, &strm->list_entry); + pj_mutex_unlock(strm->cf->mutex); + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; + + on_error: + ca_stream_destroy((pjmedia_aud_stream *)strm); + return status; +} + +/* API: Get stream info. */ +static pj_status_t ca_stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct coreaudio_stream *strm = (struct coreaudio_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Update the device capabilities' values */ + if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, + &pi->input_latency_ms) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + } + if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, + &pi->output_latency_ms) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + } + if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, + &pi->input_route) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE; + } + if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, + &pi->output_route) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE; + } + if (ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_EC, + &pi->ec_enabled) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_EC; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct coreaudio_stream *strm = (struct coreaudio_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) + { +#if COREAUDIO_MAC + UInt32 latency, size = sizeof(UInt32); + + /* Recording latency */ + if (AudioUnitGetProperty (strm->io_units[0], + kAudioDevicePropertyLatency, + kAudioUnitScope_Input, + 1, + &latency, + &size) == noErr) + { + UInt32 latency2; + if (AudioUnitGetProperty (strm->io_units[0], + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Input, + 1, + &latency2, + &size) == noErr) + { + strm->param.input_latency_ms = (latency + latency2) * 1000 / + strm->param.clock_rate; + strm->param.input_latency_ms++; + } + } +#else + Float32 latency, latency2; + UInt32 size = sizeof(Float32); + + if ((AudioSessionGetProperty( + kAudioSessionProperty_CurrentHardwareInputLatency, + &size, &latency) == kAudioSessionNoError) && + (AudioSessionGetProperty( + kAudioSessionProperty_CurrentHardwareIOBufferDuration, + &size, &latency2) == kAudioSessionNoError)) + { + strm->param.input_latency_ms = (unsigned) + ((latency + latency2) * 1000); + strm->param.input_latency_ms++; + } +#endif + + *(unsigned*)pval = strm->param.input_latency_ms; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { +#if COREAUDIO_MAC + UInt32 latency, size = sizeof(UInt32); + AudioUnit *io_unit = strm->io_units[1] ? &strm->io_units[1] : + &strm->io_units[0]; + + /* Playback latency */ + if (AudioUnitGetProperty (*io_unit, + kAudioDevicePropertyLatency, + kAudioUnitScope_Output, + 0, + &latency, + &size) == noErr) + { + UInt32 latency2; + if (AudioUnitGetProperty (*io_unit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Output, + 0, + &latency2, + &size) == noErr) + { + strm->param.output_latency_ms = (latency + latency2) * 1000 / + strm->param.clock_rate; + strm->param.output_latency_ms++; + } + } +#else + Float32 latency, latency2; + UInt32 size = sizeof(Float32); + + if ((AudioSessionGetProperty( + kAudioSessionProperty_CurrentHardwareOutputLatency, + &size, &latency) == kAudioSessionNoError) && + (AudioSessionGetProperty( + kAudioSessionProperty_CurrentHardwareIOBufferDuration, + &size, &latency2) == kAudioSessionNoError)) + { + strm->param.output_latency_ms = (unsigned) + ((latency + latency2) * 1000); + strm->param.output_latency_ms++; + } +#endif + *(unsigned*)pval = (++strm->param.output_latency_ms * 2); + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + OSStatus ostatus; + Float32 volume; + UInt32 size = sizeof(Float32); + + /* Output volume setting */ +#if COREAUDIO_MAC + ostatus = AudioUnitGetProperty (strm->io_units[1] ? strm->io_units[1] : + strm->io_units[0], + kAudioDevicePropertyVolumeScalar, + kAudioUnitScope_Output, + 0, + &volume, + &size); + if (ostatus != noErr) + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); +#else + ostatus = AudioSessionGetProperty( + kAudioSessionProperty_CurrentHardwareOutputVolume, + &size, &volume); + if (ostatus != kAudioSessionNoError) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } +#endif + + *(unsigned*)pval = (unsigned)(volume * 100); + return PJ_SUCCESS; +#if !COREAUDIO_MAC + } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) + { + UInt32 btooth, size = sizeof(UInt32); + OSStatus ostatus; + + ostatus = AudioSessionGetProperty ( + kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, + &size, &btooth); + if (ostatus != kAudioSessionNoError) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + *(pjmedia_aud_dev_route*)pval = btooth? + PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH: + PJMEDIA_AUD_DEV_ROUTE_DEFAULT; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + CFStringRef route; + UInt32 size = sizeof(CFStringRef); + OSStatus ostatus; + + ostatus = AudioSessionGetProperty (kAudioSessionProperty_AudioRoute, + &size, &route); + if (ostatus != kAudioSessionNoError) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + if (!route) { + *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT; + } else if (CFStringHasPrefix(route, CFSTR("Headset"))) { + *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_EARPIECE; + } else { + *(pjmedia_aud_dev_route*)pval = PJMEDIA_AUD_DEV_ROUTE_DEFAULT; + } + + CFRelease(route); + + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) { + AudioComponentDescription desc; + OSStatus ostatus; + + ostatus = AudioComponentGetDescription(strm->cf->io_comp, &desc); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + *(pj_bool_t*)pval = (desc.componentSubType == + kAudioUnitSubType_VoiceProcessingIO); + return PJ_SUCCESS; +#endif + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t ca_stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct coreaudio_stream *strm = (struct coreaudio_stream*)s; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + +#if COREAUDIO_MAC + if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + OSStatus ostatus; + Float32 volume = *(unsigned*)pval; + + /* Output volume setting */ + volume /= 100.0; + ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] : + strm->io_units[0], + kAudioDevicePropertyVolumeScalar, + kAudioUnitScope_Output, + 0, + &volume, + sizeof(Float32)); + if (ostatus != noErr) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + strm->param.output_vol = *(unsigned*)pval; + return PJ_SUCCESS; + } + +#else + + if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) || + (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK))) + { + Float32 bufferDuration = *(unsigned *)pval; + OSStatus ostatus; + unsigned latency; + + /* For low-latency audio streaming, you can set this value to + * as low as 5 ms (the default is 23ms). However, lowering the + * latency may cause a decrease in audio quality. + */ + bufferDuration /= 1000; + ostatus = AudioSessionSetProperty( + kAudioSessionProperty_PreferredHardwareIOBufferDuration, + sizeof(bufferDuration), &bufferDuration); + if (ostatus != kAudioSessionNoError) { + PJ_LOG(4, (THIS_FILE, + "Error: cannot set the preferred buffer duration (%i)", + ostatus)); + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + + ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY, &latency); + ca_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY, &latency); + + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) + { + UInt32 btooth = *(pjmedia_aud_dev_route*)pval == + PJMEDIA_AUD_DEV_ROUTE_BLUETOOTH ? 1 : 0; + OSStatus ostatus; + + ostatus = AudioSessionSetProperty ( + kAudioSessionProperty_OverrideCategoryEnableBluetoothInput, + sizeof(btooth), &btooth); + if (ostatus != kAudioSessionNoError) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + strm->param.input_route = *(pjmedia_aud_dev_route*)pval; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + OSStatus ostatus; + UInt32 route = *(pjmedia_aud_dev_route*)pval == + PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER ? + kAudioSessionOverrideAudioRoute_Speaker : + kAudioSessionOverrideAudioRoute_None; + + ostatus = AudioSessionSetProperty ( + kAudioSessionProperty_OverrideAudioRoute, + sizeof(route), &route); + if (ostatus != kAudioSessionNoError) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + strm->param.output_route = *(pjmedia_aud_dev_route*)pval; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_EC) { + AudioComponentDescription desc; + AudioComponent io_comp; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = (*(pj_bool_t*)pval)? + kAudioUnitSubType_VoiceProcessingIO : + kAudioUnitSubType_RemoteIO; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + io_comp = AudioComponentFindNext(NULL, &desc); + if (io_comp == NULL) + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(-1); + strm->cf->io_comp = io_comp; + strm->param.ec_enabled = *(pj_bool_t*)pval; + + PJ_LOG(4, (THIS_FILE, "Using %s audio unit", + (desc.componentSubType == + kAudioUnitSubType_RemoteIO? "RemoteIO": + "VoiceProcessingIO"))); + + return PJ_SUCCESS; + } +#endif + + return PJMEDIA_EAUD_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t ca_stream_start(pjmedia_aud_stream *strm) +{ + struct coreaudio_stream *stream = (struct coreaudio_stream*)strm; + OSStatus ostatus; + UInt32 i; + + if (stream->running) + return PJ_SUCCESS; + + stream->quit_flag = 0; + stream->interrupted = PJ_FALSE; + stream->rec_buf_count = 0; + stream->play_buf_count = 0; + stream->resample_buf_count = 0; + + if (stream->resample) { + ostatus = AudioConverterReset(stream->resample); + if (ostatus != noErr) + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + +#if !COREAUDIO_MAC + AudioSessionSetActive(true); +#endif + + for (i = 0; i < 2; i++) { + if (stream->io_units[i] == NULL) break; + ostatus = AudioOutputUnitStart(stream->io_units[i]); + if (ostatus != noErr) { + if (i == 1) + AudioOutputUnitStop(stream->io_units[0]); + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + } + + stream->running = PJ_TRUE; + + PJ_LOG(4, (THIS_FILE, "core audio stream started")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t ca_stream_stop(pjmedia_aud_stream *strm) +{ + struct coreaudio_stream *stream = (struct coreaudio_stream*)strm; + OSStatus ostatus; + unsigned i; + int should_deactivate; + struct stream_list *it, *itBegin; + + if (!stream->running) + return PJ_SUCCESS; + + for (i = 0; i < 2; i++) { + if (stream->io_units[i] == NULL) break; + ostatus = AudioOutputUnitStop(stream->io_units[i]); + if (ostatus != noErr) { + if (i == 0 && stream->io_units[1]) + AudioOutputUnitStop(stream->io_units[1]); + return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); + } + } + + /* Check whether we need to deactivate the audio session. */ + pj_mutex_lock(stream->cf->mutex); + pj_assert(!pj_list_empty(&stream->cf->streams)); + pj_assert(!pj_list_empty(&stream->list_entry)); + stream->running = PJ_FALSE; + should_deactivate = PJ_TRUE; + itBegin = &stream->cf->streams; + for (it = itBegin->next; it != itBegin; it = it->next) { + if (it->stream->running) { + should_deactivate = PJ_FALSE; + break; + } + } + pj_mutex_unlock(stream->cf->mutex); + +#if !COREAUDIO_MAC + if (should_deactivate) + AudioSessionSetActive(false); +#endif + + stream->quit_flag = 1; + stream->play_thread_initialized = 0; + stream->rec_thread_initialized = 0; + pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc)); + pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc)); + + PJ_LOG(4, (THIS_FILE, "core audio stream stopped")); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t ca_stream_destroy(pjmedia_aud_stream *strm) +{ + struct coreaudio_stream *stream = (struct coreaudio_stream*)strm; + unsigned i; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + ca_stream_stop(strm); + + for (i = 0; i < 2; i++) { + if (stream->io_units[i]) { + AudioUnitUninitialize(stream->io_units[i]); + AudioComponentInstanceDispose(stream->io_units[i]); + stream->io_units[i] = NULL; + } + } + + if (stream->resample) + AudioConverterDispose(stream->resample); + + pj_mutex_lock(stream->cf->mutex); + if (!pj_list_empty(&stream->list_entry)) + pj_list_erase(&stream->list_entry); + pj_mutex_unlock(stream->cf->mutex); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_COREAUDIO */ diff --git a/pjmedia/src/pjmedia-audiodev/errno.c b/pjmedia/src/pjmedia-audiodev/errno.c new file mode 100644 index 0000000..1066386 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/errno.c @@ -0,0 +1,206 @@ +/* $Id: errno.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + */ +#include +#include +#include +#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO +# include +#endif +#if PJMEDIA_AUDIO_DEV_HAS_WMME +# ifdef _MSC_VER +# pragma warning(push, 3) +# endif +# include +# include +# ifdef _MSC_VER +# pragma warning(pop) +# endif +#endif + +/* PJMEDIA-Audiodev's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + PJ_BUILD_ERR( PJMEDIA_EAUD_ERR, "Unspecified audio device error" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_SYSERR, "Unknown error from audio driver" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_INIT, "Audio subsystem not initialized" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_INVDEV, "Invalid audio device" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_NODEV, "Found no audio devices" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_NODEFDEV, "Unable to find default audio device" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_NOTREADY, "Audio device not ready" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_INVCAP, "Invalid or unsupported audio capability" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_INVOP, "Invalid or unsupported audio device operation" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_BADFORMAT, "Bad or invalid audio device format" ), + PJ_BUILD_ERR( PJMEDIA_EAUD_SAMPFORMAT, "Invalid audio device sample format"), + PJ_BUILD_ERR( PJMEDIA_EAUD_BADLATENCY, "Bad audio latency setting") + +}; + +#endif /* PJ_HAS_ERROR_STRING */ + + + +/* + * pjmedia_audiodev_strerror() + */ +PJ_DEF(pj_str_t) pjmedia_audiodev_strerror(pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + + /* See if the error comes from Core Audio. */ +#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO + if (statcode >= PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_START && + statcode <= PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_END) + { + int ca_err = PJMEDIA_AUDIODEV_COREAUDIO_ERRNO_START - statcode; + + PJ_UNUSED_ARG(ca_err); + // TODO: create more helpful error messages + errstr.ptr = buf; + pj_strcpy2(&errstr, "Core audio error"); + return errstr; + } else +#endif + + /* See if the error comes from PortAudio. */ +#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO + if (statcode >= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START && + statcode <= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END) + { + + //int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0); + int pa_err = PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START - statcode; + pj_str_t msg; + + msg.ptr = (char*)Pa_GetErrorText(pa_err); + msg.slen = pj_ansi_strlen(msg.ptr); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } else +#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ + + /* See if the error comes from WMME */ +#if PJMEDIA_AUDIO_DEV_HAS_WMME + if ((statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START && + statcode < PJMEDIA_AUDIODEV_WMME_IN_ERROR_END) || + (statcode >= PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START && + statcode < PJMEDIA_AUDIODEV_WMME_OUT_ERROR_END)) + { + MMRESULT native_err, mr; + MMRESULT (WINAPI *waveGetErrText)(UINT mmrError, LPTSTR pszText, UINT cchText); + PJ_DECL_UNICODE_TEMP_BUF(wbuf, 80) + + if (statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START && + statcode <= PJMEDIA_AUDIODEV_WMME_IN_ERROR_END) + { + native_err = statcode - PJMEDIA_AUDIODEV_WMME_IN_ERROR_START; + waveGetErrText = &waveInGetErrorText; + } else { + native_err = statcode - PJMEDIA_AUDIODEV_WMME_OUT_ERROR_START; + waveGetErrText = &waveOutGetErrorText; + } + +#if PJ_NATIVE_STRING_IS_UNICODE + mr = (*waveGetErrText)(native_err, wbuf, PJ_ARRAY_SIZE(wbuf)); + if (mr == MMSYSERR_NOERROR) { + int len = wcslen(wbuf); + pj_unicode_to_ansi(wbuf, len, buf, bufsize); + } +#else + mr = (*waveGetErrText)(native_err, buf, bufsize); +#endif + + if (mr==MMSYSERR_NOERROR) { + errstr.ptr = buf; + errstr.slen = pj_ansi_strlen(buf); + return errstr; + } else { + pj_ansi_snprintf(buf, bufsize, "MMSYSTEM native error %d", + native_err); + return pj_str(buf); + } + + } else +#endif + + /* Audiodev error */ + if (statcode >= PJMEDIA_AUDIODEV_ERRNO_START && + statcode < PJMEDIA_AUDIODEV_ERRNO_END) + { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n/2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid+1; + n -= (half+1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char*)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } + } +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, + "Unknown pjmedia-audiodev error %d", + statcode); + + return errstr; +} + diff --git a/pjmedia/src/pjmedia-audiodev/legacy_dev.c b/pjmedia/src/pjmedia-audiodev/legacy_dev.c new file mode 100644 index 0000000..45c6c5d --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/legacy_dev.c @@ -0,0 +1,468 @@ +/* $Id: legacy_dev.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + */ +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE + +#define THIS_FILE "legacy_dev.c" + +/* Legacy devices factory */ +struct legacy_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; +}; + + +struct legacy_stream +{ + pjmedia_aud_stream base; + + pj_pool_t *pool; + pjmedia_aud_param param; + pjmedia_snd_stream *snd_strm; + pjmedia_aud_play_cb user_play_cb; + pjmedia_aud_rec_cb user_rec_cb; + void *user_user_data; + unsigned input_latency; + unsigned output_latency; +}; + + +/* Prototypes */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t 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_aud_strm); + +static pj_status_t stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t stream_start(pjmedia_aud_stream *strm); +static pj_status_t stream_stop(pjmedia_aud_stream *strm); +static pj_status_t stream_destroy(pjmedia_aud_stream *strm); + + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &factory_init, + &factory_destroy, + &factory_get_dev_count, + &factory_get_dev_info, + &factory_default_param, + &factory_create_stream, + &factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &stream_get_param, + &stream_get_cap, + &stream_set_cap, + &stream_start, + &stream_stop, + &stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ + +/* + * Init legacy audio driver. + */ +pjmedia_aud_dev_factory* pjmedia_legacy_factory(pj_pool_factory *pf) +{ + struct legacy_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "legacy-snd", 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct legacy_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f) +{ + struct legacy_factory *wf = (struct legacy_factory*)f; + + return pjmedia_snd_init(wf->pf); +} + +/* API: destroy factory */ +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct legacy_factory *wf = (struct legacy_factory*)f; + pj_status_t status; + + status = pjmedia_snd_deinit(); + + if (status == PJ_SUCCESS) { + pj_pool_t *pool = wf->pool; + wf->pool = NULL; + pj_pool_release(pool); + } + + return status; +} + +/* API: refresh the list of devices */ +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_ENOTSUP; +} + +/* API: get number of devices */ +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return pjmedia_snd_get_dev_count(); +} + +/* API: get device info */ +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + const pjmedia_snd_dev_info *si = + pjmedia_snd_get_dev_info(index);; + + PJ_UNUSED_ARG(f); + + if (si == NULL) + return PJMEDIA_EAUD_INVDEV; + + pj_bzero(info, sizeof(*info)); + pj_ansi_strncpy(info->name, si->name, sizeof(info->name)); + info->name[sizeof(info->name)-1] = '\0'; + info->input_count = si->input_count; + info->output_count = si->output_count; + info->default_samples_per_sec = si->default_samples_per_sec; + pj_ansi_strcpy(info->driver, "legacy"); + info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + pjmedia_aud_dev_info di; + pj_status_t status; + + status = factory_get_dev_info(f, index, &di); + if (status != PJ_SUCCESS) + return status; + + pj_bzero(param, sizeof(*param)); + if (di.input_count && di.output_count) { + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + } else if (di.input_count) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->rec_id = index; + param->play_id = PJMEDIA_AUD_INVALID_DEV; + } else if (di.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 = di.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = di.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = 16; + param->flags = di.caps; + param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; + param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + + return PJ_SUCCESS; +} + +/* Callback from legacy sound device */ +static pj_status_t snd_play_cb(/* in */ void *user_data, + /* in */ pj_uint32_t timestamp, + /* out */ void *output, + /* out */ unsigned size) +{ + struct legacy_stream *strm = (struct legacy_stream*)user_data; + pjmedia_frame frame; + pj_status_t status; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = output; + frame.size = size; + frame.timestamp.u64 = timestamp; + + status = strm->user_play_cb(strm->user_user_data, &frame); + if (status != PJ_SUCCESS) + return status; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { + pj_bzero(output, size); + } + + return PJ_SUCCESS; +} + +/* Callback from legacy sound device */ +static pj_status_t snd_rec_cb( /* in */ void *user_data, + /* in */ pj_uint32_t timestamp, + /* in */ void *input, + /* in*/ unsigned size) +{ + struct legacy_stream *strm = (struct legacy_stream*)user_data; + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = input; + frame.size = size; + frame.timestamp.u64 = timestamp; + + return strm->user_rec_cb(strm->user_user_data, &frame); +} + +/* API: create stream */ +static pj_status_t 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_aud_strm) +{ + struct legacy_factory *wf = (struct legacy_factory*)f; + pj_pool_t *pool; + struct legacy_stream *strm; + pj_status_t status; + + /* Initialize our stream data */ + pool = pj_pool_create(wf->pf, "legacy-snd", 512, 512, NULL); + strm = PJ_POOL_ZALLOC_T(pool, struct legacy_stream); + strm->pool = pool; + strm->user_rec_cb = rec_cb; + strm->user_play_cb = play_cb; + strm->user_user_data = user_data; + pj_memcpy(&strm->param, param, sizeof(*param)); + + /* Set the latency if wanted */ + if (param->dir==PJMEDIA_DIR_CAPTURE_PLAYBACK && + param->flags & (PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)) + { + PJ_ASSERT_RETURN(param->input_latency_ms && + param->output_latency_ms, + PJMEDIA_EAUD_BADLATENCY); + + strm->input_latency = param->input_latency_ms; + strm->output_latency = param->output_latency_ms; + + status = pjmedia_snd_set_latency(param->input_latency_ms, + param->output_latency_ms); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + } + + /* Open the stream */ + if (param->dir == PJMEDIA_DIR_CAPTURE) { + status = pjmedia_snd_open_rec(param->rec_id, + param->clock_rate, + param->channel_count, + param->samples_per_frame, + param->bits_per_sample, + &snd_rec_cb, + strm, + &strm->snd_strm); + } else if (param->dir == PJMEDIA_DIR_PLAYBACK) { + status = pjmedia_snd_open_player(param->play_id, + param->clock_rate, + param->channel_count, + param->samples_per_frame, + param->bits_per_sample, + &snd_play_cb, + strm, + &strm->snd_strm); + + } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { + status = pjmedia_snd_open(param->rec_id, + param->play_id, + param->clock_rate, + param->channel_count, + param->samples_per_frame, + param->bits_per_sample, + &snd_rec_cb, + &snd_play_cb, + strm, + &strm->snd_strm); + } else { + pj_assert(!"Invalid direction!"); + return PJ_EINVAL; + } + + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + + *p_aud_strm = &strm->base; + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct legacy_stream *strm = (struct legacy_stream*)s; + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + if (strm->input_latency) { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + pi->input_latency_ms = strm->input_latency; + } else { + pi->flags &= ~PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + } + + if (strm->output_latency) { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + pi->output_latency_ms = strm->output_latency; + } else { + pi->flags &= ~PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct legacy_stream *strm = (struct legacy_stream*)s; + + PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) + { + /* Recording latency */ + if (strm->input_latency) { + *(unsigned*)pval = strm->input_latency; + return PJ_SUCCESS; + } else { + return PJMEDIA_EAUD_INVCAP; + } + + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + /* Playback latency */ + if (strm->output_latency) { + *(unsigned*)pval = strm->output_latency; + return PJ_SUCCESS; + } else { + return PJMEDIA_EAUD_INVCAP; + } + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + PJ_UNUSED_ARG(s); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + return PJMEDIA_EAUD_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *s) +{ + struct legacy_stream *strm = (struct legacy_stream*)s; + return pjmedia_snd_stream_start(strm->snd_strm); +} + +/* API: Stop stream. */ +static pj_status_t stream_stop(pjmedia_aud_stream *s) +{ + struct legacy_stream *strm = (struct legacy_stream*)s; + return pjmedia_snd_stream_stop(strm->snd_strm); +} + + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *s) +{ + struct legacy_stream *strm = (struct legacy_stream*)s; + pj_status_t status; + + status = pjmedia_snd_stream_close(strm->snd_strm); + + if (status == PJ_SUCCESS) { + pj_pool_t *pool = strm->pool; + + strm->pool = NULL; + pj_pool_release(pool); + } + + return status; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE */ + diff --git a/pjmedia/src/pjmedia-audiodev/null_dev.c b/pjmedia/src/pjmedia-audiodev/null_dev.c new file mode 100644 index 0000000..6426641 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/null_dev.c @@ -0,0 +1,388 @@ +/* $Id: null_dev.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 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 + */ +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO + +#define THIS_FILE "null_dev.c" + +/* null_audio device info */ +struct null_audio_dev_info +{ + pjmedia_aud_dev_info info; + unsigned dev_id; +}; + +/* null_audio factory */ +struct null_audio_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct null_audio_dev_info *dev_info; +}; + +/* Sound stream. */ +struct null_audio_stream +{ + pjmedia_aud_stream base; /**< Base stream */ + pjmedia_aud_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */ + pjmedia_aud_play_cb play_cb; /**< Playback callback. */ + void *user_data; /**< Application data. */ +}; + + +/* Prototypes */ +static pj_status_t null_factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t null_factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t null_factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned null_factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t null_factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t null_factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t null_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_aud_strm); + +static pj_status_t null_stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t null_stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t null_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t null_stream_start(pjmedia_aud_stream *strm); +static pj_status_t null_stream_stop(pjmedia_aud_stream *strm); +static pj_status_t null_stream_destroy(pjmedia_aud_stream *strm); + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &null_factory_init, + &null_factory_destroy, + &null_factory_get_dev_count, + &null_factory_get_dev_info, + &null_factory_default_param, + &null_factory_create_stream, + &null_factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &null_stream_get_param, + &null_stream_get_cap, + &null_stream_set_cap, + &null_stream_start, + &null_stream_stop, + &null_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init null_audio audio driver. + */ +pjmedia_aud_dev_factory* pjmedia_null_audio_factory(pj_pool_factory *pf) +{ + struct null_audio_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "null audio", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct null_audio_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t null_factory_init(pjmedia_aud_dev_factory *f) +{ + struct null_audio_factory *nf = (struct null_audio_factory*)f; + struct null_audio_dev_info *ndi; + + /* Initialize input and output devices here */ + nf->dev_count = 1; + nf->dev_info = (struct null_audio_dev_info*) + pj_pool_calloc(nf->pool, nf->dev_count, + sizeof(struct null_audio_dev_info)); + ndi = &nf->dev_info[0]; + pj_bzero(ndi, sizeof(*ndi)); + strcpy(ndi->info.name, "null device"); + strcpy(ndi->info.driver, "null"); + ndi->info.input_count = 1; + ndi->info.output_count = 1; + ndi->info.default_samples_per_sec = 16000; + /* Set the device capabilities here */ + ndi->info.caps = 0; + + PJ_LOG(4, (THIS_FILE, "null audio initialized")); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t null_factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct null_audio_factory *nf = (struct null_audio_factory*)f; + pj_pool_t *pool = nf->pool; + + nf->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t null_factory_refresh(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned null_factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + struct null_audio_factory *nf = (struct null_audio_factory*)f; + return nf->dev_count; +} + +/* API: get device info */ +static pj_status_t null_factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct null_audio_factory *nf = (struct null_audio_factory*)f; + + PJ_ASSERT_RETURN(index < nf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &nf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t null_factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct null_audio_factory *nf = (struct null_audio_factory*)f; + struct null_audio_dev_info *di = &nf->dev_info[index]; + + PJ_ASSERT_RETURN(index < nf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_bzero(param, sizeof(*param)); + if (di->info.input_count && di->info.output_count) { + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + } else if (di->info.input_count) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->rec_id = index; + param->play_id = PJMEDIA_AUD_INVALID_DEV; + } else if (di->info.output_count) { + param->dir = PJMEDIA_DIR_PLAYBACK; + param->play_id = index; + param->rec_id = PJMEDIA_AUD_INVALID_DEV; + } else { + return PJMEDIA_EAUD_INVDEV; + } + + /* Set the mandatory settings here */ + /* The values here are just some examples */ + param->clock_rate = di->info.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = 16; + + /* Set the device capabilities here */ + param->flags = 0; + + return PJ_SUCCESS; +} + +/* API: create stream */ +static pj_status_t null_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_aud_strm) +{ + struct null_audio_factory *nf = (struct null_audio_factory*)f; + pj_pool_t *pool; + struct null_audio_stream *strm; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(nf->pf, "null_audio-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct null_audio_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + strm->rec_cb = rec_cb; + strm->play_cb = play_cb; + strm->user_data = user_data; + + /* Create player stream here */ + if (param->dir & PJMEDIA_DIR_PLAYBACK) { + } + + /* Create capture stream here */ + if (param->dir & PJMEDIA_DIR_CAPTURE) { + } + + /* Apply the remaining settings */ + /* Below is an example if you want to set the output volume */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + null_stream_set_cap(&strm->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + ¶m->output_vol); + } + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t null_stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct null_audio_stream *strm = (struct null_audio_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Example: Update the volume setting */ + if (null_stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t null_stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct null_audio_stream *strm = (struct null_audio_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + /* Example: Get the output's volume setting */ + if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) + { + /* Output volume setting */ + *(unsigned*)pval = 0; // retrieve output device's volume here + return PJ_SUCCESS; + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t null_stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct null_audio_stream *strm = (struct null_audio_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + /* Example */ + if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) + { + /* Output volume setting */ + // set output's volume level here + return PJ_SUCCESS; + } + + return PJMEDIA_EAUD_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t null_stream_start(pjmedia_aud_stream *strm) +{ + struct null_audio_stream *stream = (struct null_audio_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting null audio stream")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t null_stream_stop(pjmedia_aud_stream *strm) +{ + struct null_audio_stream *stream = (struct null_audio_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping null audio stream")); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t null_stream_destroy(pjmedia_aud_stream *strm) +{ + struct null_audio_stream *stream = (struct null_audio_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + null_stream_stop(strm); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_NULL_AUDIO */ diff --git a/pjmedia/src/pjmedia-audiodev/pa_dev.c b/pjmedia/src/pjmedia-audiodev/pa_dev.c new file mode 100644 index 0000000..fb745a9 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/pa_dev.c @@ -0,0 +1,1284 @@ +/* $Id: pa_dev.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + */ +#include +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO + +#include + +#define THIS_FILE "pa_dev.c" +#define DRIVER_NAME "PA" + +/* Enable call to PaUtil_SetDebugPrintFunction, but this is not always + * available across all PortAudio versions (?) + */ +/*#define USE_PA_DEBUG_PRINT */ + +struct pa_aud_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_factory *pf; + pj_pool_t *pool; +}; + + +/* + * Sound stream descriptor. + * This struct may be used for both unidirectional or bidirectional sound + * streams. + */ +struct pa_aud_stream +{ + pjmedia_aud_stream base; + + pj_pool_t *pool; + pj_str_t name; + pjmedia_dir dir; + int play_id; + int rec_id; + int bytes_per_sample; + pj_uint32_t samples_per_sec; + unsigned samples_per_frame; + int channel_count; + + PaStream *rec_strm; + PaStream *play_strm; + + void *user_data; + pjmedia_aud_rec_cb rec_cb; + pjmedia_aud_play_cb play_cb; + + pj_timestamp play_timestamp; + pj_timestamp rec_timestamp; + pj_uint32_t underflow; + pj_uint32_t overflow; + + pj_bool_t quit_flag; + + pj_bool_t rec_thread_exited; + pj_bool_t rec_thread_initialized; + pj_thread_desc rec_thread_desc; + pj_thread_t *rec_thread; + + pj_bool_t play_thread_exited; + pj_bool_t play_thread_initialized; + pj_thread_desc play_thread_desc; + pj_thread_t *play_thread; + + /* Sometime the record callback does not return framesize as configured + * (e.g: in OSS), while this module must guarantee returning framesize + * as configured in the creation settings. In this case, we need a buffer + * for the recorded samples. + */ + pj_int16_t *rec_buf; + unsigned rec_buf_count; + + /* Sometime the player callback does not request framesize as configured + * (e.g: in Linux OSS) while sound device will always get samples from + * the other component as many as configured samples_per_frame. + */ + pj_int16_t *play_buf; + unsigned play_buf_count; +}; + + +/* Factory prototypes */ +static pj_status_t pa_init(pjmedia_aud_dev_factory *f); +static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t pa_refresh(pjmedia_aud_dev_factory *f); +static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t pa_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_aud_strm); + +/* Stream prototypes */ +static pj_status_t strm_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t strm_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t strm_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t strm_start(pjmedia_aud_stream *strm); +static pj_status_t strm_stop(pjmedia_aud_stream *strm); +static pj_status_t strm_destroy(pjmedia_aud_stream *strm); + + +static pjmedia_aud_dev_factory_op pa_op = +{ + &pa_init, + &pa_destroy, + &pa_get_dev_count, + &pa_get_dev_info, + &pa_default_param, + &pa_create_stream, + &pa_refresh +}; + +static pjmedia_aud_stream_op pa_strm_op = +{ + &strm_get_param, + &strm_get_cap, + &strm_set_cap, + &strm_start, + &strm_stop, + &strm_destroy +}; + + + +static int PaRecorderCallback(const void *input, + void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + struct pa_aud_stream *stream = (struct pa_aud_stream*) userData; + pj_status_t status = 0; + unsigned nsamples; + + PJ_UNUSED_ARG(output); + PJ_UNUSED_ARG(timeInfo); + + if (stream->quit_flag) + goto on_break; + + if (input == NULL) + return paContinue; + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session, e.g: in MacOS + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (stream->rec_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(stream->rec_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("pa_rec", stream->rec_thread_desc, + &stream->rec_thread); + stream->rec_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Recorder thread started")); + } + + if (statusFlags & paInputUnderflow) + ++stream->underflow; + if (statusFlags & paInputOverflow) + ++stream->overflow; + + /* Calculate number of samples we've got */ + nsamples = frameCount * stream->channel_count + stream->rec_buf_count; + + if (nsamples >= stream->samples_per_frame) + { + /* If buffer is not empty, combine the buffer with the just incoming + * samples, then call put_frame. + */ + if (stream->rec_buf_count) { + unsigned chunk_count = 0; + pjmedia_frame frame; + + chunk_count = stream->samples_per_frame - stream->rec_buf_count; + pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count, + (pj_int16_t*)input, chunk_count); + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = (void*) stream->rec_buf; + frame.size = stream->samples_per_frame * stream->bytes_per_sample; + frame.timestamp.u64 = stream->rec_timestamp.u64; + frame.bit_info = 0; + + status = (*stream->rec_cb)(stream->user_data, &frame); + + input = (pj_int16_t*) input + chunk_count; + nsamples -= stream->samples_per_frame; + stream->rec_buf_count = 0; + stream->rec_timestamp.u64 += stream->samples_per_frame / + stream->channel_count; + } + + /* Give all frames we have */ + while (nsamples >= stream->samples_per_frame && status == 0) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = (void*) input; + frame.size = stream->samples_per_frame * stream->bytes_per_sample; + frame.timestamp.u64 = stream->rec_timestamp.u64; + frame.bit_info = 0; + + status = (*stream->rec_cb)(stream->user_data, &frame); + + input = (pj_int16_t*) input + stream->samples_per_frame; + nsamples -= stream->samples_per_frame; + stream->rec_timestamp.u64 += stream->samples_per_frame / + stream->channel_count; + } + + /* Store the remaining samples into the buffer */ + if (nsamples && status == 0) { + stream->rec_buf_count = nsamples; + pjmedia_copy_samples(stream->rec_buf, (pj_int16_t*)input, + nsamples); + } + + } else { + /* Not enough samples, let's just store them in the buffer */ + pjmedia_copy_samples(stream->rec_buf + stream->rec_buf_count, + (pj_int16_t*)input, + frameCount * stream->channel_count); + stream->rec_buf_count += frameCount * stream->channel_count; + } + + if (status==0) + return paContinue; + +on_break: + stream->rec_thread_exited = 1; + return paAbort; +} + +static int PaPlayerCallback( const void *input, + void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + struct pa_aud_stream *stream = (struct pa_aud_stream*) userData; + pj_status_t status = 0; + unsigned nsamples_req = frameCount * stream->channel_count; + + PJ_UNUSED_ARG(input); + PJ_UNUSED_ARG(timeInfo); + + if (stream->quit_flag) + goto on_break; + + if (output == NULL) + return paContinue; + + /* Known cases of callback's thread: + * - The thread may be changed in the middle of a session, e.g: in MacOS + * it happens when plugging/unplugging headphone. + * - The same thread may be reused in consecutive sessions. The first + * session will leave TLS set, but release the TLS data address, + * so the second session must re-register the callback's thread. + */ + if (stream->play_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_bzero(stream->play_thread_desc, sizeof(pj_thread_desc)); + status = pj_thread_register("portaudio", stream->play_thread_desc, + &stream->play_thread); + stream->play_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Player thread started")); + } + + if (statusFlags & paOutputUnderflow) + ++stream->underflow; + if (statusFlags & paOutputOverflow) + ++stream->overflow; + + + /* Check if any buffered samples */ + if (stream->play_buf_count) { + /* samples buffered >= requested by sound device */ + if (stream->play_buf_count >= nsamples_req) { + pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, + nsamples_req); + stream->play_buf_count -= nsamples_req; + pjmedia_move_samples(stream->play_buf, + stream->play_buf + nsamples_req, + stream->play_buf_count); + nsamples_req = 0; + + return paContinue; + } + + /* samples buffered < requested by sound device */ + pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, + stream->play_buf_count); + nsamples_req -= stream->play_buf_count; + output = (pj_int16_t*)output + stream->play_buf_count; + stream->play_buf_count = 0; + } + + /* Fill output buffer as requested */ + while (nsamples_req && status == 0) { + if (nsamples_req >= stream->samples_per_frame) { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = output; + frame.size = stream->samples_per_frame * stream->bytes_per_sample; + frame.timestamp.u64 = stream->play_timestamp.u64; + frame.bit_info = 0; + + status = (*stream->play_cb)(stream->user_data, &frame); + if (status != PJ_SUCCESS) + goto on_break; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frame.buf, frame.size); + + nsamples_req -= stream->samples_per_frame; + output = (pj_int16_t*)output + stream->samples_per_frame; + } else { + pjmedia_frame frame; + + frame.type = PJMEDIA_FRAME_TYPE_AUDIO; + frame.buf = stream->play_buf; + frame.size = stream->samples_per_frame * stream->bytes_per_sample; + frame.timestamp.u64 = stream->play_timestamp.u64; + frame.bit_info = 0; + + status = (*stream->play_cb)(stream->user_data, &frame); + if (status != PJ_SUCCESS) + goto on_break; + + if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frame.buf, frame.size); + + pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, + nsamples_req); + stream->play_buf_count = stream->samples_per_frame - nsamples_req; + pjmedia_move_samples(stream->play_buf, + stream->play_buf+nsamples_req, + stream->play_buf_count); + nsamples_req = 0; + } + + stream->play_timestamp.u64 += stream->samples_per_frame / + stream->channel_count; + } + + if (status==0) + return paContinue; + +on_break: + stream->play_thread_exited = 1; + return paAbort; +} + + +static int PaRecorderPlayerCallback( const void *input, + void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + int rc; + + rc = PaRecorderCallback(input, output, frameCount, timeInfo, + statusFlags, userData); + if (rc != paContinue) + return rc; + + rc = PaPlayerCallback(input, output, frameCount, timeInfo, + statusFlags, userData); + return rc; +} + +#ifdef USE_PA_DEBUG_PRINT +/* Logging callback from PA */ +static void pa_log_cb(const char *log) +{ + PJ_LOG(5,(THIS_FILE, "PA message: %s", log)); +} + +/* We should include pa_debugprint.h for this, but the header + * is not available publicly. :( + */ +typedef void (*PaUtilLogCallback ) (const char *log); +void PaUtil_SetDebugPrintFunction(PaUtilLogCallback cb); +#endif + + +/* + * Init PortAudio audio driver. + */ +pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf) +{ + struct pa_aud_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "portaudio", 64, 64, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct pa_aud_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &pa_op; + + return &f->base; +} + + +/* API: Init factory */ +static pj_status_t pa_init(pjmedia_aud_dev_factory *f) +{ + int err; + + PJ_UNUSED_ARG(f); + +#ifdef USE_PA_DEBUG_PRINT + PaUtil_SetDebugPrintFunction(&pa_log_cb); +#endif + + err = Pa_Initialize(); + + PJ_LOG(4,(THIS_FILE, + "PortAudio sound library initialized, status=%d", err)); + PJ_LOG(4,(THIS_FILE, "PortAudio host api count=%d", + Pa_GetHostApiCount())); + PJ_LOG(4,(THIS_FILE, "Sound device count=%d", + pa_get_dev_count(f))); + + return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; +} + + +/* API: Destroy factory */ +static pj_status_t pa_destroy(pjmedia_aud_dev_factory *f) +{ + struct pa_aud_factory *pa = (struct pa_aud_factory*)f; + pj_pool_t *pool; + int err; + + PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down..")); + + err = Pa_Terminate(); + + pool = pa->pool; + pa->pool = NULL; + pj_pool_release(pool); + + return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; +} + + +/* API: Refresh the device list. */ +static pj_status_t pa_refresh(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_ENOTSUP; +} + + +/* API: Get device count. */ +static unsigned pa_get_dev_count(pjmedia_aud_dev_factory *f) +{ + int count = Pa_GetDeviceCount(); + PJ_UNUSED_ARG(f); + return count < 0 ? 0 : count; +} + + +/* API: Get device info. */ +static pj_status_t pa_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + const PaDeviceInfo *pa_info; + + PJ_UNUSED_ARG(f); + + pa_info = Pa_GetDeviceInfo(index); + if (!pa_info) + return PJMEDIA_EAUD_INVDEV; + + pj_bzero(info, sizeof(*info)); + strncpy(info->name, pa_info->name, sizeof(info->name)); + info->name[sizeof(info->name)-1] = '\0'; + info->input_count = pa_info->maxInputChannels; + info->output_count = pa_info->maxOutputChannels; + info->default_samples_per_sec = (unsigned)pa_info->defaultSampleRate; + strncpy(info->driver, DRIVER_NAME, sizeof(info->driver)); + info->driver[sizeof(info->driver)-1] = '\0'; + info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + + return PJ_SUCCESS; +} + + +/* API: fill in with default parameter. */ +static pj_status_t pa_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + pjmedia_aud_dev_info adi; + pj_status_t status; + + PJ_UNUSED_ARG(f); + + status = pa_get_dev_info(f, index, &adi); + if (status != PJ_SUCCESS) + return status; + + 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; + + return PJ_SUCCESS; +} + + +/* Internal: Get PortAudio default input device ID */ +static int pa_get_default_input_dev(int channel_count) +{ + int i, count; + + /* Special for Windows - try to use the DirectSound implementation + * first since it provides better latency. + */ +#if PJMEDIA_PREFER_DIRECT_SOUND + if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) { + const PaHostApiInfo *pHI; + int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound); + pHI = Pa_GetHostApiInfo(index); + if (pHI) { + const PaDeviceInfo *paDevInfo = NULL; + paDevInfo = Pa_GetDeviceInfo(pHI->defaultInputDevice); + if (paDevInfo && paDevInfo->maxInputChannels >= channel_count) + return pHI->defaultInputDevice; + } + } +#endif + + /* Enumerate the host api's for the default devices, and return + * the device with suitable channels. + */ + count = Pa_GetHostApiCount(); + for (i=0; i < count; ++i) { + const PaHostApiInfo *pHAInfo; + + pHAInfo = Pa_GetHostApiInfo(i); + if (!pHAInfo) + continue; + + if (pHAInfo->defaultInputDevice >= 0) { + const PaDeviceInfo *paDevInfo; + + paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultInputDevice); + + if (paDevInfo->maxInputChannels >= channel_count) + return pHAInfo->defaultInputDevice; + } + } + + /* If still no device is found, enumerate all devices */ + count = Pa_GetDeviceCount(); + for (i=0; imaxInputChannels >= channel_count) + return i; + } + + return -1; +} + +/* Internal: Get PortAudio default output device ID */ +static int pa_get_default_output_dev(int channel_count) +{ + int i, count; + + /* Special for Windows - try to use the DirectSound implementation + * first since it provides better latency. + */ +#if PJMEDIA_PREFER_DIRECT_SOUND + if (Pa_HostApiTypeIdToHostApiIndex(paDirectSound) >= 0) { + const PaHostApiInfo *pHI; + int index = Pa_HostApiTypeIdToHostApiIndex(paDirectSound); + pHI = Pa_GetHostApiInfo(index); + if (pHI) { + const PaDeviceInfo *paDevInfo = NULL; + paDevInfo = Pa_GetDeviceInfo(pHI->defaultOutputDevice); + if (paDevInfo && paDevInfo->maxOutputChannels >= channel_count) + return pHI->defaultOutputDevice; + } + } +#endif + + /* Enumerate the host api's for the default devices, and return + * the device with suitable channels. + */ + count = Pa_GetHostApiCount(); + for (i=0; i < count; ++i) { + const PaHostApiInfo *pHAInfo; + + pHAInfo = Pa_GetHostApiInfo(i); + if (!pHAInfo) + continue; + + if (pHAInfo->defaultOutputDevice >= 0) { + const PaDeviceInfo *paDevInfo; + + paDevInfo = Pa_GetDeviceInfo(pHAInfo->defaultOutputDevice); + + if (paDevInfo->maxOutputChannels >= channel_count) + return pHAInfo->defaultOutputDevice; + } + } + + /* If still no device is found, enumerate all devices */ + count = Pa_GetDeviceCount(); + for (i=0; imaxOutputChannels >= channel_count) + return i; + } + + return -1; +} + + +/* Internal: create capture/recorder stream */ +static pj_status_t create_rec_stream( struct pa_aud_factory *pa, + const pjmedia_aud_param *param, + pjmedia_aud_rec_cb rec_cb, + void *user_data, + pjmedia_aud_stream **p_snd_strm) +{ + pj_pool_t *pool; + pjmedia_aud_dev_index rec_id; + struct pa_aud_stream *stream; + PaStreamParameters inputParam; + int sampleFormat; + const PaDeviceInfo *paDevInfo = NULL; + const PaHostApiInfo *paHostApiInfo = NULL; + unsigned paFrames, paRate, paLatency; + const PaStreamInfo *paSI; + PaError err; + + PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL); + + rec_id = param->rec_id; + if (rec_id < 0) { + rec_id = pa_get_default_input_dev(param->channel_count); + if (rec_id < 0) { + /* No such device. */ + return PJMEDIA_EAUD_NODEFDEV; + } + } + + paDevInfo = Pa_GetDeviceInfo(rec_id); + if (!paDevInfo) { + /* Assumed it is "No such device" error. */ + return PJMEDIA_EAUD_INVDEV; + } + + if (param->bits_per_sample == 8) + sampleFormat = paUInt8; + else if (param->bits_per_sample == 16) + sampleFormat = paInt16; + else if (param->bits_per_sample == 32) + sampleFormat = paInt32; + else + return PJMEDIA_EAUD_SAMPFORMAT; + + pool = pj_pool_create(pa->pf, "recstrm", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream); + stream->pool = pool; + pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); + stream->dir = PJMEDIA_DIR_CAPTURE; + stream->rec_id = rec_id; + stream->play_id = -1; + stream->user_data = user_data; + stream->samples_per_sec = param->clock_rate; + stream->samples_per_frame = param->samples_per_frame; + stream->bytes_per_sample = param->bits_per_sample / 8; + stream->channel_count = param->channel_count; + stream->rec_cb = rec_cb; + + stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, + stream->samples_per_frame * stream->bytes_per_sample); + stream->rec_buf_count = 0; + + pj_bzero(&inputParam, sizeof(inputParam)); + inputParam.device = rec_id; + inputParam.channelCount = param->channel_count; + inputParam.hostApiSpecificStreamInfo = NULL; + inputParam.sampleFormat = sampleFormat; + if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) + inputParam.suggestedLatency = param->input_latency_ms / 1000.0; + else + inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0; + + paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); + + /* Frames in PortAudio is number of samples in a single channel */ + paFrames = param->samples_per_frame / param->channel_count; + + err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL, + param->clock_rate, paFrames, + paClipOff, &PaRecorderCallback, stream ); + if (err != paNoError) { + pj_pool_release(pool); + return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err); + } + + paSI = Pa_GetStreamInfo(stream->rec_strm); + paRate = (unsigned)paSI->sampleRate; + paLatency = (unsigned)(paSI->inputLatency * 1000); + + PJ_LOG(5,(THIS_FILE, "Opened device %s (%s) for recording, sample " + "rate=%d, ch=%d, " + "bits=%d, %d samples per frame, latency=%d ms", + paDevInfo->name, paHostApiInfo->name, + paRate, param->channel_count, + param->bits_per_sample, param->samples_per_frame, + paLatency)); + + *p_snd_strm = &stream->base; + return PJ_SUCCESS; +} + + +/* Internal: create playback stream */ +static pj_status_t create_play_stream(struct pa_aud_factory *pa, + const pjmedia_aud_param *param, + pjmedia_aud_play_cb play_cb, + void *user_data, + pjmedia_aud_stream **p_snd_strm) +{ + pj_pool_t *pool; + pjmedia_aud_dev_index play_id; + struct pa_aud_stream *stream; + PaStreamParameters outputParam; + int sampleFormat; + const PaDeviceInfo *paDevInfo = NULL; + const PaHostApiInfo *paHostApiInfo = NULL; + const PaStreamInfo *paSI; + unsigned paFrames, paRate, paLatency; + PaError err; + + PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL); + + play_id = param->play_id; + if (play_id < 0) { + play_id = pa_get_default_output_dev(param->channel_count); + if (play_id < 0) { + /* No such device. */ + return PJMEDIA_EAUD_NODEFDEV; + } + } + + paDevInfo = Pa_GetDeviceInfo(play_id); + if (!paDevInfo) { + /* Assumed it is "No such device" error. */ + return PJMEDIA_EAUD_INVDEV; + } + + if (param->bits_per_sample == 8) + sampleFormat = paUInt8; + else if (param->bits_per_sample == 16) + sampleFormat = paInt16; + else if (param->bits_per_sample == 32) + sampleFormat = paInt32; + else + return PJMEDIA_EAUD_SAMPFORMAT; + + pool = pj_pool_create(pa->pf, "playstrm", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream); + stream->pool = pool; + pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); + stream->dir = PJMEDIA_DIR_PLAYBACK; + stream->play_id = play_id; + stream->rec_id = -1; + stream->user_data = user_data; + stream->samples_per_sec = param->clock_rate; + stream->samples_per_frame = param->samples_per_frame; + stream->bytes_per_sample = param->bits_per_sample / 8; + stream->channel_count = param->channel_count; + stream->play_cb = play_cb; + + stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, + stream->samples_per_frame * + stream->bytes_per_sample); + stream->play_buf_count = 0; + + pj_bzero(&outputParam, sizeof(outputParam)); + outputParam.device = play_id; + outputParam.channelCount = param->channel_count; + outputParam.hostApiSpecificStreamInfo = NULL; + outputParam.sampleFormat = sampleFormat; + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) + outputParam.suggestedLatency=param->output_latency_ms / 1000.0; + else + outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0; + + paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); + + /* Frames in PortAudio is number of samples in a single channel */ + paFrames = param->samples_per_frame / param->channel_count; + + err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, + param->clock_rate, paFrames, + paClipOff, &PaPlayerCallback, stream ); + if (err != paNoError) { + pj_pool_release(pool); + return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err); + } + + paSI = Pa_GetStreamInfo(stream->play_strm); + paRate = (unsigned)(paSI->sampleRate); + paLatency = (unsigned)(paSI->outputLatency * 1000); + + PJ_LOG(5,(THIS_FILE, "Opened device %d: %s(%s) for playing, sample rate=%d" + ", ch=%d, " + "bits=%d, %d samples per frame, latency=%d ms", + play_id, paDevInfo->name, paHostApiInfo->name, + paRate, param->channel_count, + param->bits_per_sample, param->samples_per_frame, + paLatency)); + + *p_snd_strm = &stream->base; + + return PJ_SUCCESS; +} + + +/* Internal: Create both player and recorder stream */ +static pj_status_t create_bidir_stream(struct pa_aud_factory *pa, + const pjmedia_aud_param *param, + pjmedia_aud_rec_cb rec_cb, + pjmedia_aud_play_cb play_cb, + void *user_data, + pjmedia_aud_stream **p_snd_strm) +{ + pj_pool_t *pool; + pjmedia_aud_dev_index rec_id, play_id; + struct pa_aud_stream *stream; + PaStream *paStream = NULL; + PaStreamParameters inputParam; + PaStreamParameters outputParam; + int sampleFormat; + const PaDeviceInfo *paRecDevInfo = NULL; + const PaDeviceInfo *paPlayDevInfo = NULL; + const PaHostApiInfo *paRecHostApiInfo = NULL; + const PaHostApiInfo *paPlayHostApiInfo = NULL; + const PaStreamInfo *paSI; + unsigned paFrames, paRate, paInputLatency, paOutputLatency; + PaError err; + + PJ_ASSERT_RETURN(play_cb && rec_cb && p_snd_strm, PJ_EINVAL); + + rec_id = param->rec_id; + if (rec_id < 0) { + rec_id = pa_get_default_input_dev(param->channel_count); + if (rec_id < 0) { + /* No such device. */ + return PJMEDIA_EAUD_NODEFDEV; + } + } + + paRecDevInfo = Pa_GetDeviceInfo(rec_id); + if (!paRecDevInfo) { + /* Assumed it is "No such device" error. */ + return PJMEDIA_EAUD_INVDEV; + } + + play_id = param->play_id; + if (play_id < 0) { + play_id = pa_get_default_output_dev(param->channel_count); + if (play_id < 0) { + /* No such device. */ + return PJMEDIA_EAUD_NODEFDEV; + } + } + + paPlayDevInfo = Pa_GetDeviceInfo(play_id); + if (!paPlayDevInfo) { + /* Assumed it is "No such device" error. */ + return PJMEDIA_EAUD_INVDEV; + } + + + if (param->bits_per_sample == 8) + sampleFormat = paUInt8; + else if (param->bits_per_sample == 16) + sampleFormat = paInt16; + else if (param->bits_per_sample == 32) + sampleFormat = paInt32; + else + return PJMEDIA_EAUD_SAMPFORMAT; + + pool = pj_pool_create(pa->pf, "sndstream", 1024, 1024, NULL); + if (!pool) + return PJ_ENOMEM; + + stream = PJ_POOL_ZALLOC_T(pool, struct pa_aud_stream); + stream->pool = pool; + pj_strdup2_with_null(pool, &stream->name, paRecDevInfo->name); + stream->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + stream->play_id = play_id; + stream->rec_id = rec_id; + stream->user_data = user_data; + stream->samples_per_sec = param->clock_rate; + stream->samples_per_frame = param->samples_per_frame; + stream->bytes_per_sample = param->bits_per_sample / 8; + stream->channel_count = param->channel_count; + stream->rec_cb = rec_cb; + stream->play_cb = play_cb; + + stream->rec_buf = (pj_int16_t*)pj_pool_alloc(pool, + stream->samples_per_frame * stream->bytes_per_sample); + stream->rec_buf_count = 0; + + stream->play_buf = (pj_int16_t*)pj_pool_alloc(pool, + stream->samples_per_frame * stream->bytes_per_sample); + stream->play_buf_count = 0; + + pj_bzero(&inputParam, sizeof(inputParam)); + inputParam.device = rec_id; + inputParam.channelCount = param->channel_count; + inputParam.hostApiSpecificStreamInfo = NULL; + inputParam.sampleFormat = sampleFormat; + if (param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) + inputParam.suggestedLatency = param->input_latency_ms / 1000.0; + else + inputParam.suggestedLatency = PJMEDIA_SND_DEFAULT_REC_LATENCY / 1000.0; + + paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi); + + pj_bzero(&outputParam, sizeof(outputParam)); + outputParam.device = play_id; + outputParam.channelCount = param->channel_count; + outputParam.hostApiSpecificStreamInfo = NULL; + outputParam.sampleFormat = sampleFormat; + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) + outputParam.suggestedLatency=param->output_latency_ms / 1000.0; + else + outputParam.suggestedLatency=PJMEDIA_SND_DEFAULT_PLAY_LATENCY/1000.0; + + paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi); + + /* Frames in PortAudio is number of samples in a single channel */ + paFrames = param->samples_per_frame / param->channel_count; + + /* If both input and output are on the same device, open a single stream + * for both input and output. + */ + if (rec_id == play_id) { + err = Pa_OpenStream( &paStream, &inputParam, &outputParam, + param->clock_rate, paFrames, + paClipOff, &PaRecorderPlayerCallback, stream ); + if (err == paNoError) { + /* Set play stream and record stream to the same stream */ + stream->play_strm = stream->rec_strm = paStream; + } + } else { + err = -1; + } + + /* .. otherwise if input and output are on the same device, OR if we're + * unable to open a bidirectional stream, then open two separate + * input and output stream. + */ + if (paStream == NULL) { + /* Open input stream */ + err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL, + param->clock_rate, paFrames, + paClipOff, &PaRecorderCallback, stream ); + if (err == paNoError) { + /* Open output stream */ + err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, + param->clock_rate, paFrames, + paClipOff, &PaPlayerCallback, stream ); + if (err != paNoError) + Pa_CloseStream(stream->rec_strm); + } + } + + if (err != paNoError) { + pj_pool_release(pool); + return PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err); + } + + paSI = Pa_GetStreamInfo(stream->rec_strm); + paRate = (unsigned)(paSI->sampleRate); + paInputLatency = (unsigned)(paSI->inputLatency * 1000); + paSI = Pa_GetStreamInfo(stream->play_strm); + paOutputLatency = (unsigned)(paSI->outputLatency * 1000); + + PJ_LOG(5,(THIS_FILE, "Opened device %s(%s)/%s(%s) for recording and " + "playback, sample rate=%d, ch=%d, " + "bits=%d, %d samples per frame, input latency=%d ms, " + "output latency=%d ms", + paRecDevInfo->name, paRecHostApiInfo->name, + paPlayDevInfo->name, paPlayHostApiInfo->name, + paRate, param->channel_count, + param->bits_per_sample, param->samples_per_frame, + paInputLatency, paOutputLatency)); + + *p_snd_strm = &stream->base; + + return PJ_SUCCESS; +} + + +/* API: create stream */ +static pj_status_t pa_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_aud_strm) +{ + struct pa_aud_factory *pa = (struct pa_aud_factory*)f; + pj_status_t status; + + if (param->dir == PJMEDIA_DIR_CAPTURE) { + status = create_rec_stream(pa, param, rec_cb, user_data, p_aud_strm); + } else if (param->dir == PJMEDIA_DIR_PLAYBACK) { + status = create_play_stream(pa, param, play_cb, user_data, p_aud_strm); + } else if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { + status = create_bidir_stream(pa, param, rec_cb, play_cb, user_data, + p_aud_strm); + } else { + return PJ_EINVAL; + } + + if (status != PJ_SUCCESS) + return status; + + (*p_aud_strm)->op = &pa_strm_op; + + return PJ_SUCCESS; +} + + +/* API: Get stream parameters */ +static pj_status_t strm_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct pa_aud_stream *strm = (struct pa_aud_stream*)s; + const PaStreamInfo *paPlaySI = NULL, *paRecSI = NULL; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + PJ_ASSERT_RETURN(strm->play_strm || strm->rec_strm, PJ_EINVALIDOP); + + if (strm->play_strm) { + paPlaySI = Pa_GetStreamInfo(strm->play_strm); + } + if (strm->rec_strm) { + paRecSI = Pa_GetStreamInfo(strm->rec_strm); + } + + pj_bzero(pi, sizeof(*pi)); + pi->dir = strm->dir; + pi->play_id = strm->play_id; + pi->rec_id = strm->rec_id; + pi->clock_rate = (unsigned)(paPlaySI ? paPlaySI->sampleRate : + paRecSI->sampleRate); + pi->channel_count = strm->channel_count; + pi->samples_per_frame = strm->samples_per_frame; + pi->bits_per_sample = strm->bytes_per_sample * 8; + if (paRecSI) { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + pi->input_latency_ms = (unsigned)(paRecSI ? paRecSI->inputLatency * + 1000 : 0); + } + if (paPlaySI) { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + pi->output_latency_ms = (unsigned)(paPlaySI? paPlaySI->outputLatency * + 1000 : 0); + } + + return PJ_SUCCESS; +} + + +/* API: get capability */ +static pj_status_t strm_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct pa_aud_stream *strm = (struct pa_aud_stream*)s; + + PJ_ASSERT_RETURN(strm && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && strm->rec_strm) { + const PaStreamInfo *si = Pa_GetStreamInfo(strm->rec_strm); + if (!si) + return PJMEDIA_EAUD_SYSERR; + + *(unsigned*)pval = (unsigned)(si->inputLatency * 1000); + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && strm->play_strm) { + const PaStreamInfo *si = Pa_GetStreamInfo(strm->play_strm); + if (!si) + return PJMEDIA_EAUD_SYSERR; + + *(unsigned*)pval = (unsigned)(si->outputLatency * 1000); + return PJ_SUCCESS; + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + + +/* API: set capability */ +static pj_status_t strm_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value) +{ + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(value); + + /* Nothing is supported */ + return PJMEDIA_EAUD_INVCAP; +} + + +/* API: start stream. */ +static pj_status_t strm_start(pjmedia_aud_stream *s) +{ + struct pa_aud_stream *stream = (struct pa_aud_stream*)s; + int err = 0; + + PJ_LOG(5,(THIS_FILE, "Starting %s stream..", stream->name.ptr)); + + if (stream->play_strm) + err = Pa_StartStream(stream->play_strm); + + if (err==0 && stream->rec_strm && stream->rec_strm != stream->play_strm) { + err = Pa_StartStream(stream->rec_strm); + if (err != 0) + Pa_StopStream(stream->play_strm); + } + + PJ_LOG(5,(THIS_FILE, "Done, status=%d", err)); + + return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; +} + + +/* API: stop stream. */ +static pj_status_t strm_stop(pjmedia_aud_stream *s) +{ + struct pa_aud_stream *stream = (struct pa_aud_stream*)s; + int i, err = 0; + + stream->quit_flag = 1; + for (i=0; !stream->rec_thread_exited && i<100; ++i) + pj_thread_sleep(10); + for (i=0; !stream->play_thread_exited && i<100; ++i) + pj_thread_sleep(10); + + pj_thread_sleep(1); + + PJ_LOG(5,(THIS_FILE, "Stopping stream..")); + + if (stream->play_strm) + err = Pa_StopStream(stream->play_strm); + + if (stream->rec_strm && stream->rec_strm != stream->play_strm) + err = Pa_StopStream(stream->rec_strm); + + stream->play_thread_initialized = 0; + stream->rec_thread_initialized = 0; + + PJ_LOG(5,(THIS_FILE, "Done, status=%d", err)); + + return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; +} + + +/* API: destroy stream. */ +static pj_status_t strm_destroy(pjmedia_aud_stream *s) +{ + struct pa_aud_stream *stream = (struct pa_aud_stream*)s; + int i, err = 0; + + stream->quit_flag = 1; + for (i=0; !stream->rec_thread_exited && i<100; ++i) { + pj_thread_sleep(1); + } + for (i=0; !stream->play_thread_exited && i<100; ++i) { + pj_thread_sleep(1); + } + + PJ_LOG(5,(THIS_FILE, "Closing %.*s: %lu underflow, %lu overflow", + (int)stream->name.slen, + stream->name.ptr, + stream->underflow, stream->overflow)); + + if (stream->play_strm) + err = Pa_CloseStream(stream->play_strm); + + if (stream->rec_strm && stream->rec_strm != stream->play_strm) + err = Pa_CloseStream(stream->rec_strm); + + pj_pool_release(stream->pool); + + return err ? PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO */ + diff --git a/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h b/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h new file mode 100644 index 0000000..ae13bb1 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h @@ -0,0 +1,171 @@ +#ifndef __BITSTREAM_H_ +#define __BITSTREAM_H_ + +#define KPackedFrameLen 10 +#define KUnpackedFrameLen 22 + +// Below values are taken from the APS design document +const TUint8 KG729FullPayloadBits[] = { 8, 10, 8, 1, 13, 4, 7, 5, 13, 4, 7 }; +const TUint KNumFullFrameParams = 11; +const TUint8 KG729SIDPayloadBits[] = { 1, 5, 4, 5 }; +const TUint KNumSIDFrameParams = 4; + +/*! + @class TBitStream + + @discussion Provides compression from 16-bit-word-aligned G.729 audio frames + (used in S60 G.729 DSP codec) to 8-bit stream, and vice versa. + */ +class TBitStream + { +public: + /*! + @function TBitStream + + @discussion Constructor + */ + TBitStream():iDes(iData,KUnpackedFrameLen){} + /*! + @function CompressG729Frame + + @discussion Compress either a 22-byte G.729 full rate frame to 10 bytes + or a 8-byte G.729 Annex.B SID frame to 2 bytes. + @param aSrc Reference to the uncompressed source frame data + @param aIsSIDFrame True if the source is a SID frame + @result a reference to the compressed frame + */ + const TDesC8& CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse ); + + /*! + @function ExpandG729Frame + + @discussion Expand a 10-byte G.729 full rate frame to 22 bytes + or a 2-byte G.729 Annex.B SID frame to 8(22) bytes. + @param aSrc Reference to the compressed source frame data + @param aIsSIDFrame True if the source is a SID frame + @result a reference to a descriptor representing the uncompressed frame. + Note that SID frames are zero-padded to 22 bytes as well. + */ + const TDesC8& ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame = EFalse ); + +private: + void Compress( TUint8 aValue, TUint8 aNumOfBits ); + void Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits ); + +private: + TUint8 iData[KUnpackedFrameLen]; + TPtr8 iDes; + TInt iIdx; + TInt iBitOffset; + }; + + +const TDesC8& TBitStream::CompressG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame ) + { + // reset data + iDes.FillZ(iDes.MaxLength()); + iIdx = iBitOffset = 0; + + TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams; + const TUint8* p = const_cast(aSrc.Ptr()); + + for(TInt i = 0, pIdx = 0; i < numParams; i++, pIdx += 2) + { + TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i]; + if(paramBits > 8) + { + Compress(p[pIdx+1], paramBits - 8); // msb + paramBits = 8; + } + Compress(p[pIdx], paramBits); // lsb + } + + if( iBitOffset ) + iIdx++; + + iDes.SetLength(iIdx); + return iDes; + } + + +const TDesC8& TBitStream::ExpandG729Frame( const TDesC8& aSrc, TBool aIsSIDFrame ) + { + // reset data + iDes.FillZ(iDes.MaxLength()); + iIdx = iBitOffset = 0; + + TInt numParams = (aIsSIDFrame) ? KNumSIDFrameParams : KNumFullFrameParams; + const TUint8* p = const_cast(aSrc.Ptr()); + + for(TInt i = 0, dIdx = 0; i < numParams; i++, dIdx += 2) + { + TUint8 paramBits = (aIsSIDFrame) ? KG729SIDPayloadBits[i] : KG729FullPayloadBits[i]; + if(paramBits > 8) + { + Expand(p, dIdx+1, paramBits - 8); // msb + paramBits = 8; + } + Expand(p, dIdx, paramBits); // lsb + } + + iDes.SetLength(KUnpackedFrameLen); + return iDes; + } + + +void TBitStream::Compress( TUint8 aValue, TUint8 aNumOfBits ) + { + // clear bits that will be discarded + aValue &= (0xff >> (8 - aNumOfBits)); + + // calculate required bitwise left shift + TInt shl = 8 - (iBitOffset + aNumOfBits); + + if (shl == 0) // no shift required + { + iData[iIdx++] |= aValue; + iBitOffset = 0; + } + else if (shl > 0) // bits fit into current byte + { + iData[iIdx] |= (aValue << shl); + iBitOffset += aNumOfBits; + } + else + { + iBitOffset = -shl; + iData[iIdx] |= (aValue >> iBitOffset); // right shift + iData[++iIdx] |= (aValue << (8-iBitOffset)); // push remaining bits to next byte + } + } + + +void TBitStream::Expand( const TUint8* aSrc, TInt aDstIdx, TUint8 aNumOfBits ) + { + TUint8 aValue = aSrc[iIdx] & (0xff >> iBitOffset); + + // calculate required bitwise right shift + TInt shr = 8 - (iBitOffset + aNumOfBits); + + if (shr == 0) // no shift required + { + iData[aDstIdx] = aValue; + iIdx++; + iBitOffset = 0; + } + else if (shr > 0) // right shift + { + iData[aDstIdx] = (aValue >> shr); + iBitOffset += aNumOfBits; + } + else // shift left and take remaining bits from the next src byte + { + iBitOffset = -shr; + iData[aDstIdx] = aValue << iBitOffset; + iData[aDstIdx] |= aSrc[++iIdx] >> (8 - iBitOffset); + } + } + +#endif // __BITSTREAM_H_ + +// eof diff --git a/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp new file mode 100644 index 0000000..d2c6564 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp @@ -0,0 +1,1929 @@ +/* $Id: symb_aps_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS + +#include +#include +#include +#include + +/* Pack/unpack G.729 frame of S60 DSP codec, taken from: + * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format + */ +#include "s60_g729_bitstream.h" + + +#define THIS_FILE "symb_aps_dev.c" +#define BITS_PER_SAMPLE 16 + + +#if 1 +# define TRACE_(st) PJ_LOG(3, st) +#else +# define TRACE_(st) +#endif + + +/* App UID to open global APS queues to communicate with the APS server. */ +extern TPtrC APP_UID; + +/* APS G.711 frame length */ +static pj_uint8_t aps_g711_frame_len; + + +/* APS factory */ +struct aps_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + pjmedia_aud_dev_info dev_info; +}; + + +/* Forward declaration of CPjAudioEngine */ +class CPjAudioEngine; + + +/* APS stream. */ +struct aps_stream +{ + // Base + pjmedia_aud_stream base; /**< Base class. */ + + // Pool + pj_pool_t *pool; /**< Memory pool. */ + + // Common settings. + pjmedia_aud_param param; /**< Stream param. */ + pjmedia_aud_rec_cb rec_cb; /**< Record callback. */ + pjmedia_aud_play_cb play_cb; /**< Playback callback. */ + void *user_data; /**< Application data. */ + + // Audio engine + CPjAudioEngine *engine; /**< Internal engine. */ + + pj_timestamp ts_play; /**< Playback timestamp.*/ + pj_timestamp ts_rec; /**< Record timestamp. */ + + pj_int16_t *play_buf; /**< Playback buffer. */ + pj_uint16_t play_buf_len; /**< Playback buffer length. */ + pj_uint16_t play_buf_start; /**< Playback buffer start index. */ + pj_int16_t *rec_buf; /**< Record buffer. */ + pj_uint16_t rec_buf_len; /**< Record buffer length. */ + void *strm_data; /**< Stream data. */ + + /* Resampling is needed, in case audio device is opened with clock rate + * other than 8kHz (only for PCM format). + */ + pjmedia_resample *play_resample; /**< Resampler for playback. */ + pjmedia_resample *rec_resample; /**< Resampler for recording */ + pj_uint16_t resample_factor; /**< Resample factor, requested + clock rate / 8000 */ + + /* When stream is working in PCM format, where the samples may need to be + * resampled from/to different clock rate and/or channel count, PCM buffer + * is needed to perform such resampling operations. + */ + pj_int16_t *pcm_buf; /**< PCM buffer. */ +}; + + +/* Prototypes */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t 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_aud_strm); + +static pj_status_t stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t stream_start(pjmedia_aud_stream *strm); +static pj_status_t stream_stop(pjmedia_aud_stream *strm); +static pj_status_t stream_destroy(pjmedia_aud_stream *strm); + + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &factory_init, + &factory_destroy, + &factory_get_dev_count, + &factory_get_dev_info, + &factory_default_param, + &factory_create_stream, + &factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &stream_get_param, + &stream_get_cap, + &stream_set_cap, + &stream_start, + &stream_stop, + &stream_destroy +}; + + +/**************************************************************************** + * Internal APS Engine + */ + +/* + * Utility: print sound device error + */ +static void snd_perror(const char *title, TInt rc) +{ + PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc)); +} + +/* + * Utility: wait for specified time. + */ +static void snd_wait(unsigned ms) +{ + TTime start, now; + + start.UniversalTime(); + do { + pj_symbianos_poll(-1, ms); + now.UniversalTime(); + } while (now.MicroSecondsFrom(start) < ms * 1000); +} + +typedef void(*PjAudioCallback)(TAPSCommBuffer &buf, void *user_data); + +/** + * Abstract class for handler of callbacks from APS client. + */ +class MQueueHandlerObserver +{ +public: + MQueueHandlerObserver(PjAudioCallback RecCb_, PjAudioCallback PlayCb_, + void *UserData_) + : RecCb(RecCb_), PlayCb(PlayCb_), UserData(UserData_) + {} + + virtual void InputStreamInitialized(const TInt aStatus) = 0; + virtual void OutputStreamInitialized(const TInt aStatus) = 0; + virtual void NotifyError(const TInt aError) = 0; + +public: + PjAudioCallback RecCb; + PjAudioCallback PlayCb; + void *UserData; +}; + +/** + * Handler for communication and data queue. + */ +class CQueueHandler : public CActive +{ +public: + // Types of queue handler + enum TQueueHandlerType { + ERecordCommQueue, + EPlayCommQueue, + ERecordQueue, + EPlayQueue + }; + + // The order corresponds to the APS Server state, do not change! + enum TState { + EAPSPlayerInitialize = 1, + EAPSRecorderInitialize = 2, + EAPSPlayData = 3, + EAPSRecordData = 4, + EAPSPlayerInitComplete = 5, + EAPSRecorderInitComplete = 6 + }; + + static CQueueHandler* NewL(MQueueHandlerObserver* aObserver, + RMsgQueue* aQ, + RMsgQueue* aWriteQ, + TQueueHandlerType aType) + { + CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, aWriteQ, + aType); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + // Destructor + ~CQueueHandler() { Cancel(); } + + // Start listening queue event + void Start() { + iQ->NotifyDataAvailable(iStatus); + SetActive(); + } + +private: + // Constructor + CQueueHandler(MQueueHandlerObserver* aObserver, + RMsgQueue* aQ, + RMsgQueue* aWriteQ, + TQueueHandlerType aType) + : CActive(CActive::EPriorityHigh), + iQ(aQ), iWriteQ(aWriteQ), iObserver(aObserver), iType(aType) + { + CActiveScheduler::Add(this); + + // use lower priority for comm queues + if ((iType == ERecordCommQueue) || (iType == EPlayCommQueue)) + SetPriority(CActive::EPriorityStandard); + } + + // Second phase constructor + void ConstructL() {} + + // Inherited from CActive + void DoCancel() { iQ->CancelDataAvailable(); } + + void RunL() { + if (iStatus != KErrNone) { + iObserver->NotifyError(iStatus.Int()); + return; + } + + TAPSCommBuffer buffer; + TInt ret = iQ->Receive(buffer); + + if (ret != KErrNone) { + iObserver->NotifyError(ret); + return; + } + + switch (iType) { + case ERecordQueue: + if (buffer.iCommand == EAPSRecordData) { + iObserver->RecCb(buffer, iObserver->UserData); + } else { + iObserver->NotifyError(buffer.iStatus); + } + break; + + // Callbacks from the APS main thread + case EPlayCommQueue: + switch (buffer.iCommand) { + case EAPSPlayData: + if (buffer.iStatus == KErrUnderflow) { + iObserver->PlayCb(buffer, iObserver->UserData); + iWriteQ->Send(buffer); + } + break; + case EAPSPlayerInitialize: + iObserver->NotifyError(buffer.iStatus); + break; + case EAPSPlayerInitComplete: + iObserver->OutputStreamInitialized(buffer.iStatus); + break; + case EAPSRecorderInitComplete: + iObserver->InputStreamInitialized(buffer.iStatus); + break; + default: + iObserver->NotifyError(buffer.iStatus); + break; + } + break; + + // Callbacks from the APS recorder thread + case ERecordCommQueue: + switch (buffer.iCommand) { + // The APS recorder thread will only report errors + // through this handler. All other callbacks will be + // sent from the APS main thread through EPlayCommQueue + case EAPSRecorderInitialize: + case EAPSRecordData: + default: + iObserver->NotifyError(buffer.iStatus); + break; + } + break; + + default: + break; + } + + // issue next request + iQ->NotifyDataAvailable(iStatus); + SetActive(); + } + + TInt RunError(TInt) { + return 0; + } + + // Data + RMsgQueue *iQ; // (not owned) + RMsgQueue *iWriteQ; // (not owned) + MQueueHandlerObserver *iObserver; // (not owned) + TQueueHandlerType iType; +}; + +/* + * Audio setting for CPjAudioEngine. + */ +class CPjAudioSetting +{ +public: + TFourCC fourcc; + TAPSCodecMode mode; + TBool plc; + TBool vad; + TBool cng; + TBool loudspk; +}; + +/* + * Implementation: Symbian Input & Output Stream. + */ +class CPjAudioEngine : public CBase, MQueueHandlerObserver +{ +public: + enum State + { + STATE_NULL, + STATE_INITIALIZING, + STATE_READY, + STATE_STREAMING, + STATE_PENDING_STOP + }; + + ~CPjAudioEngine(); + + static CPjAudioEngine *NewL(struct aps_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting); + + TInt StartL(); + void Stop(); + + TInt ActivateSpeaker(TBool active); + + TInt SetVolume(TInt vol) { return iSession.SetVolume(vol); } + TInt GetVolume() { return iSession.Volume(); } + TInt GetMaxVolume() { return iSession.MaxVolume(); } + + TInt SetGain(TInt gain) { return iSession.SetGain(gain); } + TInt GetGain() { return iSession.Gain(); } + TInt GetMaxGain() { return iSession.MaxGain(); } + +private: + CPjAudioEngine(struct aps_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting); + void ConstructL(); + + TInt InitPlayL(); + TInt InitRecL(); + TInt StartStreamL(); + void Deinit(); + + // Inherited from MQueueHandlerObserver + virtual void InputStreamInitialized(const TInt aStatus); + virtual void OutputStreamInitialized(const TInt aStatus); + virtual void NotifyError(const TInt aError); + + TBool session_opened; + State state_; + struct aps_stream *parentStrm_; + CPjAudioSetting setting_; + + RAPSSession iSession; + TAPSInitSettings iPlaySettings; + TAPSInitSettings iRecSettings; + + RMsgQueue iReadQ; + RMsgQueue iReadCommQ; + TBool readq_opened; + RMsgQueue iWriteQ; + RMsgQueue iWriteCommQ; + TBool writeq_opened; + + CQueueHandler *iPlayCommHandler; + CQueueHandler *iRecCommHandler; + CQueueHandler *iRecHandler; +}; + + +CPjAudioEngine* CPjAudioEngine::NewL(struct aps_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting) +{ + CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm, + rec_cb, play_cb, + user_data, + setting); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +CPjAudioEngine::CPjAudioEngine(struct aps_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting) + : MQueueHandlerObserver(rec_cb, play_cb, user_data), + session_opened(EFalse), + state_(STATE_NULL), + parentStrm_(parent_strm), + setting_(setting), + readq_opened(EFalse), + writeq_opened(EFalse), + iPlayCommHandler(0), + iRecCommHandler(0), + iRecHandler(0) +{ +} + +CPjAudioEngine::~CPjAudioEngine() +{ + Deinit(); + + TRACE_((THIS_FILE, "Sound device destroyed")); +} + +TInt CPjAudioEngine::InitPlayL() +{ + TInt err = iSession.InitializePlayer(iPlaySettings); + if (err != KErrNone) { + Deinit(); + snd_perror("Failed to initialize player", err); + return err; + } + + // Open message queues for the output stream + TBuf<128> buf2 = iPlaySettings.iGlobal; + buf2.Append(_L("PlayQueue")); + TBuf<128> buf3 = iPlaySettings.iGlobal; + buf3.Append(_L("PlayCommQueue")); + + while (iWriteQ.OpenGlobal(buf2)) + User::After(10); + while (iWriteCommQ.OpenGlobal(buf3)) + User::After(10); + + writeq_opened = ETrue; + + // Construct message queue handler + iPlayCommHandler = CQueueHandler::NewL(this, &iWriteCommQ, &iWriteQ, + CQueueHandler::EPlayCommQueue); + + // Start observing APS callbacks on output stream message queue + iPlayCommHandler->Start(); + + return 0; +} + +TInt CPjAudioEngine::InitRecL() +{ + // Initialize input stream device + TInt err = iSession.InitializeRecorder(iRecSettings); + if (err != KErrNone && err != KErrAlreadyExists) { + Deinit(); + snd_perror("Failed to initialize recorder", err); + return err; + } + + TBuf<128> buf1 = iRecSettings.iGlobal; + buf1.Append(_L("RecordQueue")); + TBuf<128> buf4 = iRecSettings.iGlobal; + buf4.Append(_L("RecordCommQueue")); + + // Must wait for APS thread to finish creating message queues + // before we can open and use them. + while (iReadQ.OpenGlobal(buf1)) + User::After(10); + while (iReadCommQ.OpenGlobal(buf4)) + User::After(10); + + readq_opened = ETrue; + + // Construct message queue handlers + iRecHandler = CQueueHandler::NewL(this, &iReadQ, NULL, + CQueueHandler::ERecordQueue); + iRecCommHandler = CQueueHandler::NewL(this, &iReadCommQ, NULL, + CQueueHandler::ERecordCommQueue); + + // Start observing APS callbacks from on input stream message queue + iRecHandler->Start(); + iRecCommHandler->Start(); + + return 0; +} + +TInt CPjAudioEngine::StartL() +{ + if (state_ == STATE_READY) + return StartStreamL(); + + PJ_ASSERT_RETURN(state_ == STATE_NULL, PJMEDIA_EAUD_INVOP); + + if (!session_opened) { + TInt err = iSession.Connect(); + if (err != KErrNone) + return err; + session_opened = ETrue; + } + + // Even if only capturer are opened, playback thread of APS Server need + // to be run(?). Since some messages will be delivered via play comm queue. + state_ = STATE_INITIALIZING; + + return InitPlayL(); +} + +void CPjAudioEngine::Stop() +{ + if (state_ == STATE_STREAMING) { + iSession.Stop(); + state_ = STATE_READY; + TRACE_((THIS_FILE, "Sound device stopped")); + } else if (state_ == STATE_INITIALIZING) { + // Initialization is on progress, so let's set the state to + // STATE_PENDING_STOP to prevent it starting the stream. + state_ = STATE_PENDING_STOP; + + // Then wait until initialization done. + while (state_ != STATE_READY && state_ != STATE_NULL) + pj_symbianos_poll(-1, 100); + } +} + +void CPjAudioEngine::ConstructL() +{ + // Recorder settings + iRecSettings.iFourCC = setting_.fourcc; + iRecSettings.iGlobal = APP_UID; + iRecSettings.iPriority = TMdaPriority(100); + iRecSettings.iPreference = TMdaPriorityPreference(0x05210001); + iRecSettings.iSettings.iChannels = EMMFMono; + iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz; + + // Player settings + iPlaySettings.iFourCC = setting_.fourcc; + iPlaySettings.iGlobal = APP_UID; + iPlaySettings.iPriority = TMdaPriority(100); + iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001); + iPlaySettings.iSettings.iChannels = EMMFMono; + iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz; + iPlaySettings.iSettings.iVolume = 0; + + User::LeaveIfError(iSession.Connect()); + session_opened = ETrue; +} + +TInt CPjAudioEngine::StartStreamL() +{ + pj_assert(state_==STATE_READY || state_==STATE_INITIALIZING); + + iSession.SetCng(setting_.cng); + iSession.SetVadMode(setting_.vad); + iSession.SetPlc(setting_.plc); + iSession.SetEncoderMode(setting_.mode); + iSession.SetDecoderMode(setting_.mode); + iSession.ActivateLoudspeaker(setting_.loudspk); + + // Not only capture + if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) { + iSession.Write(); + TRACE_((THIS_FILE, "Player started")); + } + + // Not only playback + if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) { + iSession.Read(); + TRACE_((THIS_FILE, "Recorder started")); + } + + state_ = STATE_STREAMING; + + return 0; +} + +void CPjAudioEngine::Deinit() +{ + Stop(); + + delete iRecHandler; + delete iPlayCommHandler; + delete iRecCommHandler; + + if (session_opened) { + enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */ + + // On some devices, immediate closing after stopping may cause + // APS server panic KERN-EXEC 0, so let's wait for sometime before + // closing the client session. + snd_wait(APS_CLOSE_WAIT_TIME); + + iSession.Close(); + session_opened = EFalse; + } + + if (readq_opened) { + iReadQ.Close(); + iReadCommQ.Close(); + readq_opened = EFalse; + } + + if (writeq_opened) { + iWriteQ.Close(); + iWriteCommQ.Close(); + writeq_opened = EFalse; + } + + state_ = STATE_NULL; +} + +void CPjAudioEngine::InputStreamInitialized(const TInt aStatus) +{ + TRACE_((THIS_FILE, "Recorder initialized, err=%d", aStatus)); + + if (aStatus == KErrNone) { + // Don't start the stream since Stop() has been requested. + if (state_ != STATE_PENDING_STOP) { + StartStreamL(); + } else { + state_ = STATE_READY; + } + } else { + Deinit(); + } +} + +void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus) +{ + TRACE_((THIS_FILE, "Player initialized, err=%d", aStatus)); + + if (aStatus == KErrNone) { + if (parentStrm_->param.dir == PJMEDIA_DIR_PLAYBACK) { + // Don't start the stream since Stop() has been requested. + if (state_ != STATE_PENDING_STOP) { + StartStreamL(); + } else { + state_ = STATE_READY; + } + } else + InitRecL(); + } else { + Deinit(); + } +} + +void CPjAudioEngine::NotifyError(const TInt aError) +{ + Deinit(); + snd_perror("Error from CQueueHandler", aError); +} + +TInt CPjAudioEngine::ActivateSpeaker(TBool active) +{ + if (state_ == STATE_READY || state_ == STATE_STREAMING) { + iSession.ActivateLoudspeaker(active); + TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off"))); + return KErrNone; + } + return KErrNotReady; +} + +/**************************************************************************** + * Internal APS callbacks for PCM format + */ + +static void RecCbPcm(TAPSCommBuffer &buf, void *user_data) +{ + struct aps_stream *strm = (struct aps_stream*) user_data; + + /* Buffer has to contain normal speech. */ + pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0); + + /* Detect the recorder G.711 frame size, player frame size will follow + * this recorder frame size. + */ + if (aps_g711_frame_len == 0) { + aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160; + TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples", + aps_g711_frame_len)); + } + + /* Decode APS buffer (coded in G.711) and put the PCM result into rec_buf. + * Whenever rec_buf is full, call parent stream callback. + */ + unsigned samples_processed = 0; + + while (samples_processed < aps_g711_frame_len) { + unsigned samples_to_process; + unsigned samples_req; + + samples_to_process = aps_g711_frame_len - samples_processed; + samples_req = (strm->param.samples_per_frame / + strm->param.channel_count / + strm->resample_factor) - + strm->rec_buf_len; + if (samples_to_process > samples_req) + samples_to_process = samples_req; + + pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len], + buf.iBuffer.Ptr() + 2 + samples_processed, + samples_to_process); + + strm->rec_buf_len += samples_to_process; + samples_processed += samples_to_process; + + /* Buffer is full, time to call parent callback */ + if (strm->rec_buf_len == strm->param.samples_per_frame / + strm->param.channel_count / + strm->resample_factor) + { + pjmedia_frame f; + + /* Need to resample clock rate? */ + if (strm->rec_resample) { + unsigned resampled = 0; + + while (resampled < strm->rec_buf_len) { + pjmedia_resample_run(strm->rec_resample, + &strm->rec_buf[resampled], + strm->pcm_buf + + resampled * strm->resample_factor); + resampled += 80; + } + f.buf = strm->pcm_buf; + } else { + f.buf = strm->rec_buf; + } + + /* Need to convert channel count? */ + if (strm->param.channel_count != 1) { + pjmedia_convert_channel_1ton((pj_int16_t*)f.buf, + (pj_int16_t*)f.buf, + strm->param.channel_count, + strm->param.samples_per_frame / + strm->param.channel_count, + 0); + } + + /* Call parent callback */ + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.size = strm->param.samples_per_frame << 1; + strm->rec_cb(strm->user_data, &f); + strm->rec_buf_len = 0; + } + } +} + +static void PlayCbPcm(TAPSCommBuffer &buf, void *user_data) +{ + struct aps_stream *strm = (struct aps_stream*) user_data; + unsigned g711_frame_len = aps_g711_frame_len; + + /* Init buffer attributes and header. */ + buf.iCommand = CQueueHandler::EAPSPlayData; + buf.iStatus = 0; + buf.iBuffer.Zero(); + buf.iBuffer.Append(1); + buf.iBuffer.Append(0); + + /* Assume frame size is 10ms if frame size hasn't been known. */ + if (g711_frame_len == 0) + g711_frame_len = 80; + + /* Call parent stream callback to get PCM samples to play, + * encode the PCM samples into G.711 and put it into APS buffer. + */ + unsigned samples_processed = 0; + + while (samples_processed < g711_frame_len) { + /* Need more samples to play, time to call parent callback */ + if (strm->play_buf_len == 0) { + pjmedia_frame f; + unsigned samples_got; + + f.size = strm->param.samples_per_frame << 1; + if (strm->play_resample || strm->param.channel_count != 1) + f.buf = strm->pcm_buf; + else + f.buf = strm->play_buf; + + /* Call parent callback */ + strm->play_cb(strm->user_data, &f); + if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples((pj_int16_t*)f.buf, + strm->param.samples_per_frame); + } + + samples_got = strm->param.samples_per_frame / + strm->param.channel_count / + strm->resample_factor; + + /* Need to convert channel count? */ + if (strm->param.channel_count != 1) { + pjmedia_convert_channel_nto1((pj_int16_t*)f.buf, + (pj_int16_t*)f.buf, + strm->param.channel_count, + strm->param.samples_per_frame, + PJ_FALSE, + 0); + } + + /* Need to resample clock rate? */ + if (strm->play_resample) { + unsigned resampled = 0; + + while (resampled < samples_got) + { + pjmedia_resample_run(strm->play_resample, + strm->pcm_buf + + resampled * strm->resample_factor, + &strm->play_buf[resampled]); + resampled += 80; + } + } + + strm->play_buf_len = samples_got; + strm->play_buf_start = 0; + } + + unsigned tmp; + + tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed); + pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start], + &strm->play_buf[strm->play_buf_start], + tmp); + buf.iBuffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp); + samples_processed += tmp; + strm->play_buf_len -= tmp; + strm->play_buf_start += tmp; + } +} + +/**************************************************************************** + * Internal APS callbacks for non-PCM format + */ + +static void RecCb(TAPSCommBuffer &buf, void *user_data) +{ + struct aps_stream *strm = (struct aps_stream*) user_data; + pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf; + + switch(strm->param.ext_fmt.id) { + case PJMEDIA_FORMAT_AMR: + { + const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 1; + unsigned len = buf.iBuffer.Length() - 1; + + pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160); + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_G729: + { + /* Check if we got a normal or SID frame. */ + if (buf.iBuffer[0] != 0 || buf.iBuffer[1] != 0) { + enum { NORMAL_LEN = 22, SID_LEN = 8 }; + TBitStream *bitstream = (TBitStream*)strm->strm_data; + unsigned src_len = buf.iBuffer.Length()- 2; + + pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN); + + const TDesC8& p = bitstream->CompressG729Frame( + buf.iBuffer.Right(src_len), + src_len == SID_LEN); + + pjmedia_frame_ext_append_subframe(frame, p.Ptr(), + p.Length() << 3, 80); + } else { /* We got null frame. */ + pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80); + } + + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_ILBC: + { + unsigned samples_got; + + samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240; + + /* Check if we got a normal frame. */ + if (buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0) { + const pj_uint8_t *p = (const pj_uint8_t*)buf.iBuffer.Ptr() + 2; + unsigned len = buf.iBuffer.Length() - 2; + + pjmedia_frame_ext_append_subframe(frame, p, len << 3, + samples_got); + } else { /* We got null frame. */ + pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got); + } + + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_PCMU: + case PJMEDIA_FORMAT_PCMA: + { + unsigned samples_processed = 0; + + /* Make sure it is normal frame. */ + pj_assert(buf.iBuffer[0] == 1 && buf.iBuffer[1] == 0); + + /* Detect the recorder G.711 frame size, player frame size will + * follow this recorder frame size. + */ + if (aps_g711_frame_len == 0) { + aps_g711_frame_len = buf.iBuffer.Length() < 160? 80 : 160; + TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples", + aps_g711_frame_len)); + } + + /* Convert APS buffer format into pjmedia_frame_ext. Whenever + * samples count in the frame is equal to stream's samples per + * frame, call parent stream callback. + */ + while (samples_processed < aps_g711_frame_len) { + unsigned tmp; + const pj_uint8_t *pb = (const pj_uint8_t*)buf.iBuffer.Ptr() + + 2 + samples_processed; + + tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt, + aps_g711_frame_len - samples_processed); + + pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp); + samples_processed += tmp; + + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + } + break; + + default: + break; + } +} + +static void PlayCb(TAPSCommBuffer &buf, void *user_data) +{ + struct aps_stream *strm = (struct aps_stream*) user_data; + pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf; + + /* Init buffer attributes and header. */ + buf.iCommand = CQueueHandler::EAPSPlayData; + buf.iStatus = 0; + buf.iBuffer.Zero(); + + switch(strm->param.ext_fmt.id) { + case PJMEDIA_FORMAT_AMR: + { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + + if (sf->data && sf->bitlen) { + /* AMR header for APS is one byte, the format (may be!): + * 0xxxxy00, where xxxx:frame type, y:not sure. + */ + unsigned len = (sf->bitlen+7)>>3; + enum {SID_FT = 8 }; + pj_uint8_t amr_header = 4, ft = SID_FT; + + if (len >= pjmedia_codec_amrnb_framelen[0]) + ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len); + + amr_header |= ft << 3; + buf.iBuffer.Append(amr_header); + + buf.iBuffer.Append((TUint8*)sf->data, len); + } else { + enum {NO_DATA_FT = 15 }; + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); + + buf.iBuffer.Append(amr_header); + } + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + enum {NO_DATA_FT = 15 }; + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); + + buf.iBuffer.Append(amr_header); + + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_G729: + { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + + if (sf->data && sf->bitlen) { + enum { NORMAL_LEN = 10, SID_LEN = 2 }; + pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN); + TBitStream *bitstream = (TBitStream*)strm->strm_data; + const TPtrC8 src(sf->data, sf->bitlen>>3); + const TDesC8 &dst = bitstream->ExpandG729Frame(src, + sid_frame); + if (sid_frame) { + buf.iBuffer.Append(2); + buf.iBuffer.Append(0); + } else { + buf.iBuffer.Append(1); + buf.iBuffer.Append(0); + } + buf.iBuffer.Append(dst); + } else { + buf.iBuffer.Append(2); + buf.iBuffer.Append(0); + buf.iBuffer.AppendFill(0, 22); + } + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + buf.iBuffer.Append(2); + buf.iBuffer.Append(0); + buf.iBuffer.AppendFill(0, 22); + + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_ILBC: + { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + + pj_assert((strm->param.ext_fmt.bitrate == 15200 && + samples_cnt == 160) || + (strm->param.ext_fmt.bitrate != 15200 && + samples_cnt == 240)); + + if (sf->data && sf->bitlen) { + buf.iBuffer.Append(1); + buf.iBuffer.Append(0); + buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3); + } else { + buf.iBuffer.Append(0); + buf.iBuffer.Append(0); + } + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + buf.iBuffer.Append(0); + buf.iBuffer.Append(0); + + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_PCMU: + case PJMEDIA_FORMAT_PCMA: + { + unsigned samples_ready = 0; + unsigned samples_req = aps_g711_frame_len; + + /* Assume frame size is 10ms if frame size hasn't been known. */ + if (samples_req == 0) + samples_req = 80; + + buf.iBuffer.Append(1); + buf.iBuffer.Append(0); + + /* Call parent stream callback to get samples to play. */ + while (samples_ready < samples_req) { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + if (sf->data && sf->bitlen) { + buf.iBuffer.Append((TUint8*)sf->data, sf->bitlen>>3); + } else { + pj_uint8_t silc; + silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)? + pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0); + buf.iBuffer.AppendFill(silc, samples_cnt); + } + samples_ready += samples_cnt; + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + pj_uint8_t silc; + + silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)? + pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0); + buf.iBuffer.AppendFill(silc, samples_req - samples_ready); + + samples_ready = samples_req; + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + } + break; + + default: + break; + } +} + + +/**************************************************************************** + * Factory operations + */ + +/* + * C compatible declaration of APS factory. + */ +PJ_BEGIN_DECL +PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf); +PJ_END_DECL + +/* + * Init APS audio driver. + */ +PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_aps_factory(pj_pool_factory *pf) +{ + struct aps_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "APS", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct aps_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f) +{ + struct aps_factory *af = (struct aps_factory*)f; + + pj_ansi_strcpy(af->dev_info.name, "S60 APS"); + af->dev_info.default_samples_per_sec = 8000; + af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT | + //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE | + PJMEDIA_AUD_DEV_CAP_VAD | + PJMEDIA_AUD_DEV_CAP_CNG; + af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE | + PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER; + af->dev_info.input_count = 1; + af->dev_info.output_count = 1; + + /* Enumerate codecs by trying to initialize each codec and examining + * the error code. Consider the following: + * - not possible to reinitialize the same APS session with + * different settings, + * - closing APS session and trying to immediately reconnect may fail, + * clients should wait ~5s before attempting to reconnect. + */ + + unsigned i, fmt_cnt = 0; + pj_bool_t g711_supported = PJ_FALSE; + + /* Do not change the order! */ + TFourCC fourcc[] = { + TFourCC(KMCPFourCCIdAMRNB), + TFourCC(KMCPFourCCIdG711), + TFourCC(KMCPFourCCIdG729), + TFourCC(KMCPFourCCIdILBC) + }; + + for (i = 0; i < PJ_ARRAY_SIZE(fourcc); ++i) { + pj_bool_t supported = PJ_FALSE; + unsigned retry_cnt = 0; + enum { MAX_RETRY = 3 }; + +#if (PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC == 0) + /* Codec detection is disabled */ + supported = PJ_TRUE; +#elif (PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC == 1) + /* Minimal codec detection, AMR-NB and G.711 only */ + if (i > 1) { + /* If G.711 has been checked, skip G.729 and iLBC checks */ + retry_cnt = MAX_RETRY; + supported = g711_supported; + } +#endif + + while (!supported && ++retry_cnt <= MAX_RETRY) { + RAPSSession iSession; + TAPSInitSettings iPlaySettings; + TAPSInitSettings iRecSettings; + TInt err; + + // Recorder settings + iRecSettings.iGlobal = APP_UID; + iRecSettings.iPriority = TMdaPriority(100); + iRecSettings.iPreference = TMdaPriorityPreference(0x05210001); + iRecSettings.iSettings.iChannels = EMMFMono; + iRecSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz; + + // Player settings + iPlaySettings.iGlobal = APP_UID; + iPlaySettings.iPriority = TMdaPriority(100); + iPlaySettings.iPreference = TMdaPriorityPreference(0x05220001); + iPlaySettings.iSettings.iChannels = EMMFMono; + iPlaySettings.iSettings.iSampleRate = EMMFSampleRate8000Hz; + + iRecSettings.iFourCC = iPlaySettings.iFourCC = fourcc[i]; + + err = iSession.Connect(); + if (err == KErrNone) + err = iSession.InitializePlayer(iPlaySettings); + if (err == KErrNone) + err = iSession.InitializeRecorder(iRecSettings); + + // On some devices, immediate closing causes APS Server panic, + // e.g: N95, so let's just wait for some time before closing. + enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */ + snd_wait(APS_CLOSE_WAIT_TIME); + + iSession.Close(); + + if (err == KErrNone) { + /* All fine, stop retyring */ + supported = PJ_TRUE; + } else if (err == KErrAlreadyExists && retry_cnt < MAX_RETRY) { + /* Seems that the previous session is still arround, + * let's wait before retrying. + */ + enum { RETRY_WAIT_TIME = 3000 }; /* in msecs */ + snd_wait(RETRY_WAIT_TIME); + } else { + /* Seems that this format is not supported */ + retry_cnt = MAX_RETRY; + } + } + + if (supported) { + switch(i) { + case 0: /* AMRNB */ + af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_AMR; + af->dev_info.ext_fmt[fmt_cnt].bitrate = 7400; + af->dev_info.ext_fmt[fmt_cnt].vad = PJ_TRUE; + ++fmt_cnt; + break; + case 1: /* G.711 */ + af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_PCMU; + af->dev_info.ext_fmt[fmt_cnt].bitrate = 64000; + af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE; + ++fmt_cnt; + af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_PCMA; + af->dev_info.ext_fmt[fmt_cnt].bitrate = 64000; + af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE; + ++fmt_cnt; + g711_supported = PJ_TRUE; + break; + case 2: /* G.729 */ + af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_G729; + af->dev_info.ext_fmt[fmt_cnt].bitrate = 8000; + af->dev_info.ext_fmt[fmt_cnt].vad = PJ_FALSE; + ++fmt_cnt; + break; + case 3: /* iLBC */ + af->dev_info.ext_fmt[fmt_cnt].id = PJMEDIA_FORMAT_ILBC; + af->dev_info.ext_fmt[fmt_cnt].bitrate = 13333; + af->dev_info.ext_fmt[fmt_cnt].vad = PJ_TRUE; + ++fmt_cnt; + break; + } + } + } + + af->dev_info.ext_fmt_cnt = fmt_cnt; + + PJ_LOG(4, (THIS_FILE, "APS initialized")); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct aps_factory *af = (struct aps_factory*)f; + pj_pool_t *pool = af->pool; + + af->pool = NULL; + pj_pool_release(pool); + + PJ_LOG(4, (THIS_FILE, "APS destroyed")); + + return PJ_SUCCESS; +} + +/* API: refresh the device list */ +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_ENOTSUP; +} + +/* API: get number of devices */ +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return 1; +} + +/* API: get device info */ +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct aps_factory *af = (struct aps_factory*)f; + + PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &af->dev_info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct aps_factory *af = (struct aps_factory*)f; + + PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + param->clock_rate = af->dev_info.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = BITS_PER_SAMPLE; + param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE; + param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE; + + return PJ_SUCCESS; +} + + +/* API: create stream */ +static pj_status_t 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_aud_strm) +{ + struct aps_factory *af = (struct aps_factory*)f; + pj_pool_t *pool; + struct aps_stream *strm; + + CPjAudioSetting aps_setting; + PjAudioCallback aps_rec_cb; + PjAudioCallback aps_play_cb; + + /* Can only support 16bits per sample */ + PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); + + /* Supported clock rates: + * - for non-PCM format: 8kHz + * - for PCM format: 8kHz and 16kHz + */ + PJ_ASSERT_RETURN(param->clock_rate == 8000 || + (param->clock_rate == 16000 && + param->ext_fmt.id == PJMEDIA_FORMAT_L16), + PJ_EINVAL); + + /* Supported channels number: + * - for non-PCM format: mono + * - for PCM format: mono and stereo + */ + PJ_ASSERT_RETURN(param->channel_count == 1 || + (param->channel_count == 2 && + param->ext_fmt.id == PJMEDIA_FORMAT_L16), + PJ_EINVAL); + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(af->pf, "aps-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct aps_stream); + strm->pool = pool; + strm->param = *param; + + if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0) + strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16; + + /* Set audio engine fourcc. */ + switch(strm->param.ext_fmt.id) { + case PJMEDIA_FORMAT_L16: + case PJMEDIA_FORMAT_PCMU: + case PJMEDIA_FORMAT_PCMA: + aps_setting.fourcc = TFourCC(KMCPFourCCIdG711); + break; + case PJMEDIA_FORMAT_AMR: + aps_setting.fourcc = TFourCC(KMCPFourCCIdAMRNB); + break; + case PJMEDIA_FORMAT_G729: + aps_setting.fourcc = TFourCC(KMCPFourCCIdG729); + break; + case PJMEDIA_FORMAT_ILBC: + aps_setting.fourcc = TFourCC(KMCPFourCCIdILBC); + break; + default: + aps_setting.fourcc = 0; + break; + } + + /* Set audio engine mode. */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR) + { + aps_setting.mode = (TAPSCodecMode)strm->param.ext_fmt.bitrate; + } + else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 || + (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC && + strm->param.ext_fmt.bitrate != 15200)) + { + aps_setting.mode = EULawOr30ms; + } + else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA || + (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC && + strm->param.ext_fmt.bitrate == 15200)) + { + aps_setting.mode = EALawOr20ms; + } + + /* Disable VAD on L16, G711, and also G729 (G729's VAD potentially + * causes noise?). + */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) + { + aps_setting.vad = EFalse; + } else { + aps_setting.vad = strm->param.ext_fmt.vad; + } + + /* Set other audio engine attributes. */ + aps_setting.plc = strm->param.plc_enabled; + aps_setting.cng = aps_setting.vad; + aps_setting.loudspk = + strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER; + + /* Set audio engine callbacks. */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) { + aps_play_cb = &PlayCbPcm; + aps_rec_cb = &RecCbPcm; + } else { + aps_play_cb = &PlayCb; + aps_rec_cb = &RecCb; + } + + strm->rec_cb = rec_cb; + strm->play_cb = play_cb; + strm->user_data = user_data; + strm->resample_factor = strm->param.clock_rate / 8000; + + /* play_buf size is samples per frame scaled in to 8kHz mono. */ + strm->play_buf = (pj_int16_t*)pj_pool_zalloc( + pool, + (strm->param.samples_per_frame / + strm->resample_factor / + strm->param.channel_count) << 1); + strm->play_buf_len = 0; + strm->play_buf_start = 0; + + /* rec_buf size is samples per frame scaled in to 8kHz mono. */ + strm->rec_buf = (pj_int16_t*)pj_pool_zalloc( + pool, + (strm->param.samples_per_frame / + strm->resample_factor / + strm->param.channel_count) << 1); + strm->rec_buf_len = 0; + + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) { + TBitStream *g729_bitstream = new TBitStream; + + PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM); + strm->strm_data = (void*)g729_bitstream; + } + + /* Init resampler when format is PCM and clock rate is not 8kHz */ + if (strm->param.clock_rate != 8000 && + strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) + { + pj_status_t status; + + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + /* Create resample for recorder */ + status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1, + 8000, + strm->param.clock_rate, + 80, + &strm->rec_resample); + if (status != PJ_SUCCESS) + return status; + } + + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + /* Create resample for player */ + status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1, + strm->param.clock_rate, + 8000, + 80 * strm->resample_factor, + &strm->play_resample); + if (status != PJ_SUCCESS) + return status; + } + } + + /* Create PCM buffer, when the clock rate is not 8kHz or not mono */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 && + (strm->resample_factor > 1 || strm->param.channel_count != 1)) + { + strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool, + strm->param.samples_per_frame << 1); + } + + + /* Create the audio engine. */ + TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, + aps_rec_cb, aps_play_cb, + strm, aps_setting)); + if (err != KErrNone) { + pj_pool_release(pool); + return PJ_RETURN_OS_ERROR(err); + } + + /* Apply output volume setting if specified */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + ¶m->output_vol); + } + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct aps_stream *strm = (struct aps_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Update the output volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct aps_stream *strm = (struct aps_stream*)s; + pj_status_t status = PJ_ENOTSUP; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + *(pjmedia_aud_dev_route*)pval = strm->param.output_route; + status = PJ_SUCCESS; + } + break; + + /* There is a case that GetMaxGain() stucks, e.g: in N95. */ + /* + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_gain = strm->engine->GetMaxGain(); + TInt gain = strm->engine->GetGain(); + + if (max_gain > 0 && gain >= 0) { + *(unsigned*)pval = gain * 100 / max_gain; + status = PJ_SUCCESS; + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + */ + + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_vol = strm->engine->GetMaxVolume(); + TInt vol = strm->engine->GetVolume(); + + if (max_vol > 0 && vol >= 0) { + *(unsigned*)pval = vol * 100 / max_vol; + status = PJ_SUCCESS; + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + default: + break; + } + + return status; +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct aps_stream *strm = (struct aps_stream*)s; + pj_status_t status = PJ_ENOTSUP; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval; + TInt err; + + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + switch (r) { + case PJMEDIA_AUD_DEV_ROUTE_DEFAULT: + case PJMEDIA_AUD_DEV_ROUTE_EARPIECE: + err = strm->engine->ActivateSpeaker(EFalse); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + break; + case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER: + err = strm->engine->ActivateSpeaker(ETrue); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + break; + default: + status = PJ_EINVAL; + break; + } + if (status == PJ_SUCCESS) + strm->param.output_route = r; + } + break; + + /* There is a case that GetMaxGain() stucks, e.g: in N95. */ + /* + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_gain = strm->engine->GetMaxGain(); + if (max_gain > 0) { + TInt gain, err; + + gain = *(unsigned*)pval * max_gain / 100; + err = strm->engine->SetGain(gain); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + if (status == PJ_SUCCESS) + strm->param.input_vol = *(unsigned*)pval; + } + break; + */ + + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_vol = strm->engine->GetMaxVolume(); + if (max_vol > 0) { + TInt vol, err; + + vol = *(unsigned*)pval * max_vol / 100; + err = strm->engine->SetVolume(vol); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + if (status == PJ_SUCCESS) + strm->param.output_vol = *(unsigned*)pval; + } + break; + default: + break; + } + + return status; +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *strm) +{ + struct aps_stream *stream = (struct aps_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->engine) { + TInt err = stream->engine->StartL(); + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t stream_stop(pjmedia_aud_stream *strm) +{ + struct aps_stream *stream = (struct aps_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->engine) { + stream->engine->Stop(); + } + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *strm) +{ + struct aps_stream *stream = (struct aps_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + stream_stop(strm); + + delete stream->engine; + stream->engine = NULL; + + if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) { + TBitStream *g729_bitstream = (TBitStream*)stream->strm_data; + stream->strm_data = NULL; + delete g729_bitstream; + } + + pj_pool_t *pool; + pool = stream->pool; + if (pool) { + stream->pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + +#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_APS + diff --git a/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp new file mode 100644 index 0000000..b4a6b9c --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp @@ -0,0 +1,1196 @@ +/* $Id: symb_mda_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA + +/* + * This file provides sound implementation for Symbian Audio Streaming + * device. Application using this sound abstraction must link with: + * - mediaclientaudiostream.lib, and + * - mediaclientaudioinputstream.lib + */ +#include +#include +#include + + +#define THIS_FILE "symb_mda_dev.c" +#define BITS_PER_SAMPLE 16 +#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8) + + +#if 1 +# define TRACE_(st) PJ_LOG(3, st) +#else +# define TRACE_(st) +#endif + + +/* MDA factory */ +struct mda_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + pjmedia_aud_dev_info dev_info; +}; + +/* Forward declaration of internal engine. */ +class CPjAudioInputEngine; +class CPjAudioOutputEngine; + +/* MDA stream. */ +struct mda_stream +{ + // Base + pjmedia_aud_stream base; /**< Base class. */ + + // Pool + pj_pool_t *pool; /**< Memory pool. */ + + // Common settings. + pjmedia_aud_param param; /**< Stream param. */ + + // Audio engine + CPjAudioInputEngine *in_engine; /**< Record engine. */ + CPjAudioOutputEngine *out_engine; /**< Playback engine. */ +}; + + +/* Prototypes */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t 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_aud_strm); + +static pj_status_t stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t stream_start(pjmedia_aud_stream *strm); +static pj_status_t stream_stop(pjmedia_aud_stream *strm); +static pj_status_t stream_destroy(pjmedia_aud_stream *strm); + + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &factory_init, + &factory_destroy, + &factory_get_dev_count, + &factory_get_dev_info, + &factory_default_param, + &factory_create_stream, + &factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &stream_get_param, + &stream_get_cap, + &stream_set_cap, + &stream_start, + &stream_stop, + &stream_destroy +}; + + +/* + * Convert clock rate to Symbian's TMdaAudioDataSettings capability. + */ +static TInt get_clock_rate_cap(unsigned clock_rate) +{ + switch (clock_rate) { + case 8000: return TMdaAudioDataSettings::ESampleRate8000Hz; + case 11025: return TMdaAudioDataSettings::ESampleRate11025Hz; + case 12000: return TMdaAudioDataSettings::ESampleRate12000Hz; + case 16000: return TMdaAudioDataSettings::ESampleRate16000Hz; + case 22050: return TMdaAudioDataSettings::ESampleRate22050Hz; + case 24000: return TMdaAudioDataSettings::ESampleRate24000Hz; + case 32000: return TMdaAudioDataSettings::ESampleRate32000Hz; + case 44100: return TMdaAudioDataSettings::ESampleRate44100Hz; + case 48000: return TMdaAudioDataSettings::ESampleRate48000Hz; + case 64000: return TMdaAudioDataSettings::ESampleRate64000Hz; + case 96000: return TMdaAudioDataSettings::ESampleRate96000Hz; + default: + return 0; + } +} + +/* + * Convert number of channels into Symbian's TMdaAudioDataSettings capability. + */ +static TInt get_channel_cap(unsigned channel_count) +{ + switch (channel_count) { + case 1: return TMdaAudioDataSettings::EChannelsMono; + case 2: return TMdaAudioDataSettings::EChannelsStereo; + default: + return 0; + } +} + +/* + * Utility: print sound device error + */ +static void snd_perror(const char *title, TInt rc) +{ + PJ_LOG(1,(THIS_FILE, "%s: error code %d", title, rc)); +} + +////////////////////////////////////////////////////////////////////////////// +// + +/* + * Implementation: Symbian Input Stream. + */ +class CPjAudioInputEngine : public CBase, MMdaAudioInputStreamCallback +{ +public: + enum State + { + STATE_INACTIVE, + STATE_ACTIVE, + }; + + ~CPjAudioInputEngine(); + + static CPjAudioInputEngine *NewL(struct mda_stream *parent_strm, + pjmedia_aud_rec_cb rec_cb, + void *user_data); + + static CPjAudioInputEngine *NewLC(struct mda_stream *parent_strm, + pjmedia_aud_rec_cb rec_cb, + void *user_data); + + pj_status_t StartRecord(); + void Stop(); + + pj_status_t SetGain(TInt gain) { + if (iInputStream_) { + iInputStream_->SetGain(gain); + return PJ_SUCCESS; + } else + return PJ_EINVALIDOP; + } + + TInt GetGain() { + if (iInputStream_) { + return iInputStream_->Gain(); + } else + return PJ_EINVALIDOP; + } + + TInt GetMaxGain() { + if (iInputStream_) { + return iInputStream_->MaxGain(); + } else + return PJ_EINVALIDOP; + } + +private: + State state_; + struct mda_stream *parentStrm_; + pjmedia_aud_rec_cb recCb_; + void *userData_; + CMdaAudioInputStream *iInputStream_; + HBufC8 *iStreamBuffer_; + TPtr8 iFramePtr_; + TInt lastError_; + pj_uint32_t timeStamp_; + CActiveSchedulerWait startAsw_; + + // cache variable + // to avoid calculating frame length repeatedly + TInt frameLen_; + + // sometimes recorded size != requested framesize, so let's + // provide a buffer to make sure the rec callback returning + // framesize as requested. + TUint8 *frameRecBuf_; + TInt frameRecBufLen_; + + CPjAudioInputEngine(struct mda_stream *parent_strm, + pjmedia_aud_rec_cb rec_cb, + void *user_data); + void ConstructL(); + TPtr8 & GetFrame(); + +public: + virtual void MaiscOpenComplete(TInt aError); + virtual void MaiscBufferCopied(TInt aError, const TDesC8 &aBuffer); + virtual void MaiscRecordComplete(TInt aError); + +}; + + +CPjAudioInputEngine::CPjAudioInputEngine(struct mda_stream *parent_strm, + pjmedia_aud_rec_cb rec_cb, + void *user_data) + : state_(STATE_INACTIVE), parentStrm_(parent_strm), + recCb_(rec_cb), userData_(user_data), + iInputStream_(NULL), iStreamBuffer_(NULL), iFramePtr_(0, 0), + lastError_(KErrNone), timeStamp_(0), + frameLen_(parent_strm->param.samples_per_frame * + BYTES_PER_SAMPLE), + frameRecBuf_(NULL), frameRecBufLen_(0) +{ +} + +CPjAudioInputEngine::~CPjAudioInputEngine() +{ + Stop(); + + delete iStreamBuffer_; + iStreamBuffer_ = NULL; + + delete [] frameRecBuf_; + frameRecBuf_ = NULL; + frameRecBufLen_ = 0; +} + +void CPjAudioInputEngine::ConstructL() +{ + iStreamBuffer_ = HBufC8::NewL(frameLen_); + CleanupStack::PushL(iStreamBuffer_); + + frameRecBuf_ = new TUint8[frameLen_*2]; + CleanupStack::PushL(frameRecBuf_); +} + +CPjAudioInputEngine *CPjAudioInputEngine::NewLC(struct mda_stream *parent, + pjmedia_aud_rec_cb rec_cb, + void *user_data) +{ + CPjAudioInputEngine* self = new (ELeave) CPjAudioInputEngine(parent, + rec_cb, + user_data); + CleanupStack::PushL(self); + self->ConstructL(); + return self; +} + +CPjAudioInputEngine *CPjAudioInputEngine::NewL(struct mda_stream *parent, + pjmedia_aud_rec_cb rec_cb, + void *user_data) +{ + CPjAudioInputEngine *self = NewLC(parent, rec_cb, user_data); + CleanupStack::Pop(self->frameRecBuf_); + CleanupStack::Pop(self->iStreamBuffer_); + CleanupStack::Pop(self); + return self; +} + + +pj_status_t CPjAudioInputEngine::StartRecord() +{ + + // Ignore command if recording is in progress. + if (state_ == STATE_ACTIVE) + return PJ_SUCCESS; + + // According to Nokia's AudioStream example, some 2nd Edition, FP2 devices + // (such as Nokia 6630) require the stream to be reconstructed each time + // before calling Open() - otherwise the callback never gets called. + // For uniform behavior, lets just delete/re-create the stream for all + // devices. + + // Destroy existing stream. + if (iInputStream_) delete iInputStream_; + iInputStream_ = NULL; + + // Create the stream. + TRAPD(err, iInputStream_ = CMdaAudioInputStream::NewL(*this)); + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + + // Initialize settings. + TMdaAudioDataSettings iStreamSettings; + iStreamSettings.iChannels = + get_channel_cap(parentStrm_->param.channel_count); + iStreamSettings.iSampleRate = + get_clock_rate_cap(parentStrm_->param.clock_rate); + + pj_assert(iStreamSettings.iChannels != 0 && + iStreamSettings.iSampleRate != 0); + + PJ_LOG(4,(THIS_FILE, "Opening sound device for capture, " + "clock rate=%d, channel count=%d..", + parentStrm_->param.clock_rate, + parentStrm_->param.channel_count)); + + // Open stream. + lastError_ = KRequestPending; + iInputStream_->Open(&iStreamSettings); + +#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \ + PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0 + + startAsw_.Start(); + +#endif + + // Success + PJ_LOG(4,(THIS_FILE, "Sound capture started.")); + return PJ_SUCCESS; +} + + +void CPjAudioInputEngine::Stop() +{ + // If capture is in progress, stop it. + if (iInputStream_ && state_ == STATE_ACTIVE) { + lastError_ = KRequestPending; + iInputStream_->Stop(); + + // Wait until it's actually stopped + while (lastError_ == KRequestPending) + pj_symbianos_poll(-1, 100); + } + + if (iInputStream_) { + delete iInputStream_; + iInputStream_ = NULL; + } + + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + + state_ = STATE_INACTIVE; +} + + +TPtr8 & CPjAudioInputEngine::GetFrame() +{ + //iStreamBuffer_->Des().FillZ(frameLen_); + iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_); + return iFramePtr_; +} + +void CPjAudioInputEngine::MaiscOpenComplete(TInt aError) +{ + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + + lastError_ = aError; + if (aError != KErrNone) { + snd_perror("Error in MaiscOpenComplete()", aError); + return; + } + + /* Apply input volume setting if specified */ + if (parentStrm_->param.flags & + PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) + { + stream_set_cap(&parentStrm_->base, + PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &parentStrm_->param.input_vol); + } + + // set stream priority to normal and time sensitive + iInputStream_->SetPriority(EPriorityNormal, + EMdaPriorityPreferenceTime); + + // Read the first frame. + TPtr8 & frm = GetFrame(); + TRAPD(err2, iInputStream_->ReadL(frm)); + if (err2) { + PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()")); + lastError_ = err2; + return; + } + + // input stream opened succesfully, set status to Active + state_ = STATE_ACTIVE; +} + +void CPjAudioInputEngine::MaiscBufferCopied(TInt aError, + const TDesC8 &aBuffer) +{ + lastError_ = aError; + if (aError != KErrNone) { + snd_perror("Error in MaiscBufferCopied()", aError); + return; + } + + if (frameRecBufLen_ || aBuffer.Length() < frameLen_) { + pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Length()); + frameRecBufLen_ += aBuffer.Length(); + } + + if (frameRecBufLen_) { + while (frameRecBufLen_ >= frameLen_) { + pjmedia_frame f; + + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.buf = frameRecBuf_; + f.size = frameLen_; + f.timestamp.u32.lo = timeStamp_; + f.bit_info = 0; + + // Call the callback. + recCb_(userData_, &f); + // Increment timestamp. + timeStamp_ += parentStrm_->param.samples_per_frame; + + frameRecBufLen_ -= frameLen_; + pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_); + } + } else { + pjmedia_frame f; + + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.buf = (void*)aBuffer.Ptr(); + f.size = aBuffer.Length(); + f.timestamp.u32.lo = timeStamp_; + f.bit_info = 0; + + // Call the callback. + recCb_(userData_, &f); + + // Increment timestamp. + timeStamp_ += parentStrm_->param.samples_per_frame; + } + + // Record next frame + TPtr8 & frm = GetFrame(); + TRAPD(err2, iInputStream_->ReadL(frm)); + if (err2) { + PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()")); + } +} + + +void CPjAudioInputEngine::MaiscRecordComplete(TInt aError) +{ + lastError_ = aError; + state_ = STATE_INACTIVE; + if (aError != KErrNone && aError != KErrCancel) { + snd_perror("Error in MaiscRecordComplete()", aError); + } +} + + + +////////////////////////////////////////////////////////////////////////////// +// + +/* + * Implementation: Symbian Output Stream. + */ + +class CPjAudioOutputEngine : public CBase, MMdaAudioOutputStreamCallback +{ +public: + enum State + { + STATE_INACTIVE, + STATE_ACTIVE, + }; + + ~CPjAudioOutputEngine(); + + static CPjAudioOutputEngine *NewL(struct mda_stream *parent_strm, + pjmedia_aud_play_cb play_cb, + void *user_data); + + static CPjAudioOutputEngine *NewLC(struct mda_stream *parent_strm, + pjmedia_aud_play_cb rec_cb, + void *user_data); + + pj_status_t StartPlay(); + void Stop(); + + pj_status_t SetVolume(TInt vol) { + if (iOutputStream_) { + iOutputStream_->SetVolume(vol); + return PJ_SUCCESS; + } else + return PJ_EINVALIDOP; + } + + TInt GetVolume() { + if (iOutputStream_) { + return iOutputStream_->Volume(); + } else + return PJ_EINVALIDOP; + } + + TInt GetMaxVolume() { + if (iOutputStream_) { + return iOutputStream_->MaxVolume(); + } else + return PJ_EINVALIDOP; + } + +private: + State state_; + struct mda_stream *parentStrm_; + pjmedia_aud_play_cb playCb_; + void *userData_; + CMdaAudioOutputStream *iOutputStream_; + TUint8 *frameBuf_; + unsigned frameBufSize_; + TPtrC8 frame_; + TInt lastError_; + unsigned timestamp_; + CActiveSchedulerWait startAsw_; + + CPjAudioOutputEngine(struct mda_stream *parent_strm, + pjmedia_aud_play_cb play_cb, + void *user_data); + void ConstructL(); + + virtual void MaoscOpenComplete(TInt aError); + virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer); + virtual void MaoscPlayComplete(TInt aError); +}; + + +CPjAudioOutputEngine::CPjAudioOutputEngine(struct mda_stream *parent_strm, + pjmedia_aud_play_cb play_cb, + void *user_data) +: state_(STATE_INACTIVE), parentStrm_(parent_strm), playCb_(play_cb), + userData_(user_data), iOutputStream_(NULL), frameBuf_(NULL), + lastError_(KErrNone), timestamp_(0) +{ +} + + +void CPjAudioOutputEngine::ConstructL() +{ + frameBufSize_ = parentStrm_->param.samples_per_frame * + BYTES_PER_SAMPLE; + frameBuf_ = new TUint8[frameBufSize_]; +} + +CPjAudioOutputEngine::~CPjAudioOutputEngine() +{ + Stop(); + delete [] frameBuf_; +} + +CPjAudioOutputEngine * +CPjAudioOutputEngine::NewLC(struct mda_stream *parent_strm, + pjmedia_aud_play_cb play_cb, + void *user_data) +{ + CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm, + play_cb, + user_data); + CleanupStack::PushL(self); + self->ConstructL(); + return self; +} + +CPjAudioOutputEngine * +CPjAudioOutputEngine::NewL(struct mda_stream *parent_strm, + pjmedia_aud_play_cb play_cb, + void *user_data) +{ + CPjAudioOutputEngine *self = NewLC(parent_strm, play_cb, user_data); + CleanupStack::Pop(self); + return self; +} + +pj_status_t CPjAudioOutputEngine::StartPlay() +{ + // Ignore command if playing is in progress. + if (state_ == STATE_ACTIVE) + return PJ_SUCCESS; + + // Destroy existing stream. + if (iOutputStream_) delete iOutputStream_; + iOutputStream_ = NULL; + + // Create the stream + TRAPD(err, iOutputStream_ = CMdaAudioOutputStream::NewL(*this)); + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + + // Initialize settings. + TMdaAudioDataSettings iStreamSettings; + iStreamSettings.iChannels = + get_channel_cap(parentStrm_->param.channel_count); + iStreamSettings.iSampleRate = + get_clock_rate_cap(parentStrm_->param.clock_rate); + + pj_assert(iStreamSettings.iChannels != 0 && + iStreamSettings.iSampleRate != 0); + + PJ_LOG(4,(THIS_FILE, "Opening sound device for playback, " + "clock rate=%d, channel count=%d..", + parentStrm_->param.clock_rate, + parentStrm_->param.channel_count)); + + // Open stream. + lastError_ = KRequestPending; + iOutputStream_->Open(&iStreamSettings); + +#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \ + PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0 + + startAsw_.Start(); + +#endif + + // Success + PJ_LOG(4,(THIS_FILE, "Sound playback started")); + return PJ_SUCCESS; + +} + +void CPjAudioOutputEngine::Stop() +{ + // Stop stream if it's playing + if (iOutputStream_ && state_ != STATE_INACTIVE) { + lastError_ = KRequestPending; + iOutputStream_->Stop(); + + // Wait until it's actually stopped + while (lastError_ == KRequestPending) + pj_symbianos_poll(-1, 100); + } + + if (iOutputStream_) { + delete iOutputStream_; + iOutputStream_ = NULL; + } + + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + + state_ = STATE_INACTIVE; +} + +void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError) +{ + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + + lastError_ = aError; + + if (aError==KErrNone) { + // set stream properties, 16bit 8KHz mono + TMdaAudioDataSettings iSettings; + iSettings.iChannels = + get_channel_cap(parentStrm_->param.channel_count); + iSettings.iSampleRate = + get_clock_rate_cap(parentStrm_->param.clock_rate); + + iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate, + iSettings.iChannels); + + /* Apply output volume setting if specified */ + if (parentStrm_->param.flags & + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) + { + stream_set_cap(&parentStrm_->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &parentStrm_->param.output_vol); + } else { + // set volume to 1/2th of stream max volume + iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2); + } + + // set stream priority to normal and time sensitive + iOutputStream_->SetPriority(EPriorityNormal, + EMdaPriorityPreferenceTime); + + // Call callback to retrieve frame from upstream. + pjmedia_frame f; + pj_status_t status; + + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.buf = frameBuf_; + f.size = frameBufSize_; + f.timestamp.u32.lo = timestamp_; + f.bit_info = 0; + + status = playCb_(this->userData_, &f); + if (status != PJ_SUCCESS) { + this->Stop(); + return; + } + + if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frameBuf_, frameBufSize_); + + // Increment timestamp. + timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE); + + // issue WriteL() to write the first audio data block, + // subsequent calls to WriteL() will be issued in + // MMdaAudioOutputStreamCallback::MaoscBufferCopied() + // until whole data buffer is written. + frame_.Set(frameBuf_, frameBufSize_); + iOutputStream_->WriteL(frame_); + + // output stream opened succesfully, set status to Active + state_ = STATE_ACTIVE; + } else { + snd_perror("Error in MaoscOpenComplete()", aError); + } +} + +void CPjAudioOutputEngine::MaoscBufferCopied(TInt aError, + const TDesC8& aBuffer) +{ + PJ_UNUSED_ARG(aBuffer); + + if (aError==KErrNone) { + // Buffer successfully written, feed another one. + + // Call callback to retrieve frame from upstream. + pjmedia_frame f; + pj_status_t status; + + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.buf = frameBuf_; + f.size = frameBufSize_; + f.timestamp.u32.lo = timestamp_; + f.bit_info = 0; + + status = playCb_(this->userData_, &f); + if (status != PJ_SUCCESS) { + this->Stop(); + return; + } + + if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) + pj_bzero(frameBuf_, frameBufSize_); + + // Increment timestamp. + timestamp_ += (frameBufSize_ / BYTES_PER_SAMPLE); + + // Write to playback stream. + frame_.Set(frameBuf_, frameBufSize_); + iOutputStream_->WriteL(frame_); + + } else if (aError==KErrAbort) { + // playing was aborted, due to call to CMdaAudioOutputStream::Stop() + state_ = STATE_INACTIVE; + } else { + // error writing data to output + lastError_ = aError; + state_ = STATE_INACTIVE; + snd_perror("Error in MaoscBufferCopied()", aError); + } +} + +void CPjAudioOutputEngine::MaoscPlayComplete(TInt aError) +{ + lastError_ = aError; + state_ = STATE_INACTIVE; + if (aError != KErrNone && aError != KErrCancel) { + snd_perror("Error in MaoscPlayComplete()", aError); + } +} + +/**************************************************************************** + * Factory operations + */ + +/* + * C compatible declaration of MDA factory. + */ +PJ_BEGIN_DECL +PJ_DECL(pjmedia_aud_dev_factory*) pjmedia_symb_mda_factory(pj_pool_factory *pf); +PJ_END_DECL + +/* + * Init Symbian audio driver. + */ +pjmedia_aud_dev_factory* pjmedia_symb_mda_factory(pj_pool_factory *pf) +{ + struct mda_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "symb_aud", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct mda_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f) +{ + struct mda_factory *af = (struct mda_factory*)f; + + pj_ansi_strcpy(af->dev_info.name, "Symbian Audio"); + af->dev_info.default_samples_per_sec = 8000; + af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + af->dev_info.input_count = 1; + af->dev_info.output_count = 1; + + PJ_LOG(4, (THIS_FILE, "Symb Mda initialized")); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct mda_factory *af = (struct mda_factory*)f; + pj_pool_t *pool = af->pool; + + af->pool = NULL; + pj_pool_release(pool); + + PJ_LOG(4, (THIS_FILE, "Symbian Mda destroyed")); + + return PJ_SUCCESS; +} + +/* API: refresh the device list */ +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_ENOTSUP; +} + +/* API: get number of devices */ +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return 1; +} + +/* API: get device info */ +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct mda_factory *af = (struct mda_factory*)f; + + PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &af->dev_info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct mda_factory *af = (struct mda_factory*)f; + + PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + param->clock_rate = af->dev_info.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = BITS_PER_SAMPLE; + // Don't set the flags without specifying the flags value. + //param->flags = af->dev_info.caps; + + return PJ_SUCCESS; +} + + +/* API: create stream */ +static pj_status_t 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_aud_strm) +{ + struct mda_factory *mf = (struct mda_factory*)f; + pj_pool_t *pool; + struct mda_stream *strm; + + /* Can only support 16bits per sample raw PCM format. */ + PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); + PJ_ASSERT_RETURN((param->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT)==0 || + param->ext_fmt.id == PJMEDIA_FORMAT_L16, + PJ_ENOTSUP); + + /* It seems that MDA recorder only supports for mono channel. */ + PJ_ASSERT_RETURN(param->channel_count == 1, PJ_EINVAL); + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(mf->pf, "symb_aud_dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct mda_stream); + strm->pool = pool; + strm->param = *param; + + // Create the output stream. + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + TRAPD(err, strm->out_engine = CPjAudioOutputEngine::NewL(strm, play_cb, + user_data)); + if (err != KErrNone) { + pj_pool_release(pool); + return PJ_RETURN_OS_ERROR(err); + } + } + + // Create the input stream. + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + TRAPD(err, strm->in_engine = CPjAudioInputEngine::NewL(strm, rec_cb, + user_data)); + if (err != KErrNone) { + strm->in_engine = NULL; + delete strm->out_engine; + strm->out_engine = NULL; + pj_pool_release(pool); + return PJ_RETURN_OS_ERROR(err); + } + } + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct mda_stream *strm = (struct mda_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Update the output volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + /* Update the input volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &pi->input_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct mda_stream *strm = (struct mda_stream*)s; + pj_status_t status = PJ_ENOTSUP; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL); + + TInt max_gain = strm->in_engine->GetMaxGain(); + TInt gain = strm->in_engine->GetGain(); + + if (max_gain > 0 && gain >= 0) { + *(unsigned*)pval = gain * 100 / max_gain; + status = PJ_SUCCESS; + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL); + + TInt max_vol = strm->out_engine->GetMaxVolume(); + TInt vol = strm->out_engine->GetVolume(); + + if (max_vol > 0 && vol >= 0) { + *(unsigned*)pval = vol * 100 / max_vol; + status = PJ_SUCCESS; + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + default: + break; + } + + return status; +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct mda_stream *strm = (struct mda_stream*)s; + pj_status_t status = PJ_ENOTSUP; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + PJ_ASSERT_RETURN(strm->in_engine, PJ_EINVAL); + + TInt max_gain = strm->in_engine->GetMaxGain(); + if (max_gain > 0) { + TInt gain; + + gain = *(unsigned*)pval * max_gain / 100; + status = strm->in_engine->SetGain(gain); + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL); + + TInt max_vol = strm->out_engine->GetMaxVolume(); + if (max_vol > 0) { + TInt vol; + + vol = *(unsigned*)pval * max_vol / 100; + status = strm->out_engine->SetVolume(vol); + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + default: + break; + } + + return status; +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *strm) +{ + struct mda_stream *stream = (struct mda_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->out_engine) { + pj_status_t status; + status = stream->out_engine->StartPlay(); + if (status != PJ_SUCCESS) + return status; + } + + if (stream->in_engine) { + pj_status_t status; + status = stream->in_engine->StartRecord(); + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t stream_stop(pjmedia_aud_stream *strm) +{ + struct mda_stream *stream = (struct mda_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->in_engine) { + stream->in_engine->Stop(); + } + + if (stream->out_engine) { + stream->out_engine->Stop(); + } + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *strm) +{ + struct mda_stream *stream = (struct mda_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + stream_stop(strm); + + delete stream->in_engine; + stream->in_engine = NULL; + + delete stream->out_engine; + stream->out_engine = NULL; + + pj_pool_t *pool; + pool = stream->pool; + if (pool) { + stream->pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA */ diff --git a/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp new file mode 100644 index 0000000..5b94903 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp @@ -0,0 +1,2006 @@ +/* $Id: symb_vas_dev.cpp 3841 2011-10-24 09:28:13Z ming $ */ +/* + * Copyright (C) 2009-2011 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS + +/* VAS headers */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AMR helper */ +#include + +/* Pack/unpack G.729 frame of S60 DSP codec, taken from: + * http://wiki.forum.nokia.com/index.php/TSS000776_-_Payload_conversion_for_G.729_audio_format + */ +#include "s60_g729_bitstream.h" + + +#define THIS_FILE "symb_vas_dev.c" +#define BITS_PER_SAMPLE 16 + + +/* When this macro is set, VAS will use EPCM16 format for PCM input/output, + * otherwise VAS will use EG711 then transcode it to PCM. + * Note that using native EPCM16 format may introduce (much) delay. + */ +//#define USE_NATIVE_PCM + +#if 1 +# define TRACE_(st) PJ_LOG(3, st) +#else +# define TRACE_(st) +#endif + +/* VAS G.711 frame length */ +static pj_uint8_t vas_g711_frame_len; + + +/* VAS factory */ +struct vas_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + pjmedia_aud_dev_info dev_info; +}; + + +/* Forward declaration of CPjAudioEngine */ +class CPjAudioEngine; + + +/* VAS stream. */ +struct vas_stream +{ + // Base + pjmedia_aud_stream base; /**< Base class. */ + + // Pool + pj_pool_t *pool; /**< Memory pool. */ + + // Common settings. + pjmedia_aud_param param; /**< Stream param. */ + pjmedia_aud_rec_cb rec_cb; /**< Record callback. */ + pjmedia_aud_play_cb play_cb; /**< Playback callback. */ + void *user_data; /**< Application data. */ + + // Audio engine + CPjAudioEngine *engine; /**< Internal engine. */ + + pj_timestamp ts_play; /**< Playback timestamp.*/ + pj_timestamp ts_rec; /**< Record timestamp. */ + + pj_int16_t *play_buf; /**< Playback buffer. */ + pj_uint16_t play_buf_len; /**< Playback buffer length. */ + pj_uint16_t play_buf_start; /**< Playback buffer start index. */ + pj_int16_t *rec_buf; /**< Record buffer. */ + pj_uint16_t rec_buf_len; /**< Record buffer length. */ + void *strm_data; /**< Stream data. */ + + /* Resampling is needed, in case audio device is opened with clock rate + * other than 8kHz (only for PCM format). + */ + pjmedia_resample *play_resample; /**< Resampler for playback. */ + pjmedia_resample *rec_resample; /**< Resampler for recording */ + pj_uint16_t resample_factor; /**< Resample factor, requested + clock rate / 8000 */ + + /* When stream is working in PCM format, where the samples may need to be + * resampled from/to different clock rate and/or channel count, PCM buffer + * is needed to perform such resampling operations. + */ + pj_int16_t *pcm_buf; /**< PCM buffer. */ +}; + + +/* Prototypes */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t 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_aud_strm); + +static pj_status_t stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t stream_start(pjmedia_aud_stream *strm); +static pj_status_t stream_stop(pjmedia_aud_stream *strm); +static pj_status_t stream_destroy(pjmedia_aud_stream *strm); + + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &factory_init, + &factory_destroy, + &factory_get_dev_count, + &factory_get_dev_info, + &factory_default_param, + &factory_create_stream, + &factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &stream_get_param, + &stream_get_cap, + &stream_set_cap, + &stream_start, + &stream_stop, + &stream_destroy +}; + + +/**************************************************************************** + * Internal VAS Engine + */ + +/* + * Utility: print sound device error + */ +static void snd_perror(const char *title, TInt rc) +{ + PJ_LOG(1,(THIS_FILE, "%s (error code=%d)", title, rc)); +} + +typedef void(*PjAudioCallback)(CVoIPDataBuffer *buf, void *user_data); + +/* + * Audio setting for CPjAudioEngine. + */ +class CPjAudioSetting +{ +public: + TVoIPCodecFormat format; + TInt mode; + TBool plc; + TBool vad; + TBool cng; + TBool loudspk; +}; + +/* + * Implementation: Symbian Input & Output Stream. + */ +class CPjAudioEngine : public CBase, + public MVoIPDownlinkObserver, + public MVoIPUplinkObserver, + public MVoIPFormatObserver +{ +public: + enum State + { + STATE_NULL, + STATE_STARTING, + STATE_READY, + STATE_STREAMING + }; + + ~CPjAudioEngine(); + + static CPjAudioEngine *NewL(struct vas_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting); + + TInt Start(); + void Stop(); + + TInt ActivateSpeaker(TBool active); + + TInt SetVolume(TInt vol) { return iVoIPDnlink->SetVolume(vol); } + TInt GetVolume() { TInt vol;iVoIPDnlink->GetVolume(vol);return vol; } + TInt GetMaxVolume() { TInt vol;iVoIPDnlink->GetMaxVolume(vol);return vol; } + + TInt SetGain(TInt gain) { return iVoIPUplink->SetGain(gain); } + TInt GetGain() { TInt gain;iVoIPUplink->GetGain(gain);return gain; } + TInt GetMaxGain() { TInt gain;iVoIPUplink->GetMaxGain(gain);return gain; } + + TBool IsStarted(); + +private: + CPjAudioEngine(struct vas_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting); + void ConstructL(); + + TInt InitPlay(); + TInt InitRec(); + + TInt StartPlay(); + TInt StartRec(); + + // From MVoIPDownlinkObserver + void FillBuffer(const CVoIPAudioDownlinkStream& aSrc, + CVoIPDataBuffer* aBuffer); + void Event(const CVoIPAudioDownlinkStream& aSrc, + TInt aEventType, + TInt aError); + + // From MVoIPUplinkObserver + void EmptyBuffer(const CVoIPAudioUplinkStream& aSrc, + CVoIPDataBuffer* aBuffer); + void Event(const CVoIPAudioUplinkStream& aSrc, + TInt aEventType, + TInt aError); + + // From MVoIPFormatObserver + void Event(const CVoIPFormatIntfc& aSrc, TInt aEventType); + + State dn_state_; + State up_state_; + struct vas_stream *parentStrm_; + CPjAudioSetting setting_; + PjAudioCallback rec_cb_; + PjAudioCallback play_cb_; + void *user_data_; + + // VAS objects + CVoIPUtilityFactory *iFactory; + CVoIPAudioDownlinkStream *iVoIPDnlink; + CVoIPAudioUplinkStream *iVoIPUplink; + CVoIPFormatIntfc *enc_fmt_if; + CVoIPFormatIntfc *dec_fmt_if; +}; + + +CPjAudioEngine* CPjAudioEngine::NewL(struct vas_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting) +{ + CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm, + rec_cb, play_cb, + user_data, + setting); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +void CPjAudioEngine::ConstructL() +{ + TInt err; + const TVersion ver(1, 0, 0); /* Not really used at this time */ + + err = CVoIPUtilityFactory::CreateFactory(iFactory); + User::LeaveIfError(err); + + if (parentStrm_->param.dir != PJMEDIA_DIR_CAPTURE) { + err = iFactory->CreateDownlinkStream(ver, + CVoIPUtilityFactory::EVoIPCall, + iVoIPDnlink); + User::LeaveIfError(err); + } + + if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) { + err = iFactory->CreateUplinkStream(ver, + CVoIPUtilityFactory::EVoIPCall, + iVoIPUplink); + User::LeaveIfError(err); + } +} + +CPjAudioEngine::CPjAudioEngine(struct vas_stream *parent_strm, + PjAudioCallback rec_cb, + PjAudioCallback play_cb, + void *user_data, + const CPjAudioSetting &setting) + : dn_state_(STATE_NULL), + up_state_(STATE_NULL), + parentStrm_(parent_strm), + setting_(setting), + rec_cb_(rec_cb), + play_cb_(play_cb), + user_data_(user_data), + iFactory(NULL), + iVoIPDnlink(NULL), + iVoIPUplink(NULL), + enc_fmt_if(NULL), + dec_fmt_if(NULL) +{ +} + +CPjAudioEngine::~CPjAudioEngine() +{ + Stop(); + + if (iVoIPUplink) + iVoIPUplink->Close(); + + if (iVoIPDnlink) + iVoIPDnlink->Close(); + + delete enc_fmt_if; + delete dec_fmt_if; + delete iVoIPDnlink; + delete iVoIPUplink; + delete iFactory; + + TRACE_((THIS_FILE, "Sound device destroyed")); +} + +TBool CPjAudioEngine::IsStarted() +{ + return ((((parentStrm_->param.dir & PJMEDIA_DIR_CAPTURE) == 0) || + up_state_ == STATE_STREAMING) && + (((parentStrm_->param.dir & PJMEDIA_DIR_PLAYBACK) == 0) || + dn_state_ == STATE_STREAMING)); +} + +TInt CPjAudioEngine::InitPlay() +{ + TInt err; + + pj_assert(iVoIPDnlink); + + delete dec_fmt_if; + dec_fmt_if = NULL; + err = iVoIPDnlink->SetFormat(setting_.format, dec_fmt_if); + if (err != KErrNone) + return err; + + err = dec_fmt_if->SetObserver(*this); + if (err != KErrNone) + return err; + + return iVoIPDnlink->Open(*this); +} + +TInt CPjAudioEngine::InitRec() +{ + TInt err; + + pj_assert(iVoIPUplink); + + delete enc_fmt_if; + enc_fmt_if = NULL; + err = iVoIPUplink->SetFormat(setting_.format, enc_fmt_if); + if (err != KErrNone) + return err; + + err = enc_fmt_if->SetObserver(*this); + if (err != KErrNone) + return err; + + return iVoIPUplink->Open(*this); +} + +TInt CPjAudioEngine::StartPlay() +{ + TInt err = KErrNone; + + pj_assert(iVoIPDnlink); + pj_assert(dn_state_ == STATE_READY); + + /* Configure specific codec setting */ + switch (setting_.format) { + case EG711: + { + CVoIPG711DecoderIntfc *g711dec_if = (CVoIPG711DecoderIntfc*) + dec_fmt_if; + err = g711dec_if->SetMode((CVoIPFormatIntfc::TG711CodecMode) + setting_.mode); + } + break; + + case EILBC: + { + CVoIPILBCDecoderIntfc *ilbcdec_if = (CVoIPILBCDecoderIntfc*) + dec_fmt_if; + err = ilbcdec_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode) + setting_.mode); + } + break; + + case EAMR_NB: + /* Ticket #1008: AMR playback issue on few devices, e.g: E72, E52 */ + err = dec_fmt_if->SetFrameMode(ETrue); + break; + + default: + break; + } + + if (err != KErrNone) + goto on_return; + + /* Configure audio routing */ + ActivateSpeaker(setting_.loudspk); + + /* Start player */ + err = iVoIPDnlink->Start(); + +on_return: + if (err == KErrNone) { + dn_state_ = STATE_STREAMING; + TRACE_((THIS_FILE, "Downlink started")); + } else { + snd_perror("Failed starting downlink", err); + } + + return err; +} + +TInt CPjAudioEngine::StartRec() +{ + TInt err = KErrNone; + + pj_assert(iVoIPUplink); + pj_assert(up_state_ == STATE_READY); + + /* Configure specific codec setting */ + switch (setting_.format) { + case EG711: + { + CVoIPG711EncoderIntfc *g711enc_if = (CVoIPG711EncoderIntfc*) + enc_fmt_if; + err = g711enc_if->SetMode((CVoIPFormatIntfc::TG711CodecMode) + setting_.mode); + } + break; + + case EILBC: + { + CVoIPILBCEncoderIntfc *ilbcenc_if = (CVoIPILBCEncoderIntfc*) + enc_fmt_if; + err = ilbcenc_if->SetMode((CVoIPFormatIntfc::TILBCCodecMode) + setting_.mode); + } + break; + + case EAMR_NB: + err = enc_fmt_if->SetBitRate(setting_.mode); + break; + + default: + break; + } + + if (err != KErrNone) + goto on_return; + + /* Configure general codec setting */ + enc_fmt_if->SetVAD(setting_.vad); + + /* Start recorder */ + err = iVoIPUplink->Start(); + +on_return: + if (err == KErrNone) { + up_state_ = STATE_STREAMING; + TRACE_((THIS_FILE, "Uplink started")); + } else { + snd_perror("Failed starting uplink", err); + } + + return err; +} + +TInt CPjAudioEngine::Start() +{ + TInt err = KErrNone; + + if (iVoIPDnlink) { + switch(dn_state_) { + case STATE_READY: + err = StartPlay(); + break; + case STATE_NULL: + err = InitPlay(); + if (err != KErrNone) + return err; + dn_state_ = STATE_STARTING; + break; + default: + break; + } + } + + if (iVoIPUplink) { + switch(up_state_) { + case STATE_READY: + err = StartRec(); + break; + case STATE_NULL: + err = InitRec(); + if (err != KErrNone) + return err; + up_state_ = STATE_STARTING; + break; + default: + break; + } + } + + return err; +} + +void CPjAudioEngine::Stop() +{ + if (iVoIPDnlink) { + switch(dn_state_) { + case STATE_STREAMING: + iVoIPDnlink->Stop(); + dn_state_ = STATE_READY; + break; + case STATE_STARTING: + dn_state_ = STATE_NULL; + break; + default: + break; + } + } + + if (iVoIPUplink) { + switch(up_state_) { + case STATE_STREAMING: + iVoIPUplink->Stop(); + up_state_ = STATE_READY; + break; + case STATE_STARTING: + up_state_ = STATE_NULL; + break; + default: + break; + } + } +} + + +TInt CPjAudioEngine::ActivateSpeaker(TBool active) +{ + TInt err = KErrNotSupported; + + if (iVoIPDnlink) { + err = iVoIPDnlink->SetAudioDevice(active? + CVoIPAudioDownlinkStream::ELoudSpeaker : + CVoIPAudioDownlinkStream::EHandset); + TRACE_((THIS_FILE, "Loudspeaker turned %s", (active? "on":"off"))); + } + + return err; +} + +// Callback from MVoIPDownlinkObserver +void CPjAudioEngine::FillBuffer(const CVoIPAudioDownlinkStream& aSrc, + CVoIPDataBuffer* aBuffer) +{ + play_cb_(aBuffer, user_data_); + iVoIPDnlink->BufferFilled(aBuffer); +} + +// Callback from MVoIPUplinkObserver +void CPjAudioEngine::EmptyBuffer(const CVoIPAudioUplinkStream& aSrc, + CVoIPDataBuffer* aBuffer) +{ + rec_cb_(aBuffer, user_data_); + iVoIPUplink->BufferEmptied(aBuffer); +} + +// Callback from MVoIPDownlinkObserver +void CPjAudioEngine::Event(const CVoIPAudioDownlinkStream& /*aSrc*/, + TInt aEventType, + TInt aError) +{ + switch (aEventType) { + case MVoIPDownlinkObserver::KOpenComplete: + if (aError == KErrNone) { + State last_state = dn_state_; + + dn_state_ = STATE_READY; + TRACE_((THIS_FILE, "Downlink opened")); + + if (last_state == STATE_STARTING) + StartPlay(); + } + break; + + case MVoIPDownlinkObserver::KDownlinkClosed: + dn_state_ = STATE_NULL; + TRACE_((THIS_FILE, "Downlink closed")); + break; + + case MVoIPDownlinkObserver::KDownlinkError: + dn_state_ = STATE_READY; + snd_perror("Downlink problem", aError); + break; + default: + break; + } +} + +// Callback from MVoIPUplinkObserver +void CPjAudioEngine::Event(const CVoIPAudioUplinkStream& /*aSrc*/, + TInt aEventType, + TInt aError) +{ + switch (aEventType) { + case MVoIPUplinkObserver::KOpenComplete: + if (aError == KErrNone) { + State last_state = up_state_; + + up_state_ = STATE_READY; + TRACE_((THIS_FILE, "Uplink opened")); + + if (last_state == STATE_STARTING) + StartRec(); + } + break; + + case MVoIPUplinkObserver::KUplinkClosed: + up_state_ = STATE_NULL; + TRACE_((THIS_FILE, "Uplink closed")); + break; + + case MVoIPUplinkObserver::KUplinkError: + up_state_ = STATE_READY; + snd_perror("Uplink problem", aError); + break; + default: + break; + } +} + +// Callback from MVoIPFormatObserver +void CPjAudioEngine::Event(const CVoIPFormatIntfc& /*aSrc*/, + TInt aEventType) +{ + snd_perror("Format event", aEventType); +} + +/**************************************************************************** + * Internal VAS callbacks for PCM format + */ + +#ifdef USE_NATIVE_PCM + +static void RecCbPcm2(CVoIPDataBuffer *buf, void *user_data) +{ + struct vas_stream *strm = (struct vas_stream*) user_data; + TPtr8 buffer(0, 0, 0); + pj_int16_t *p_buf; + unsigned buf_len; + + /* Get the buffer */ + buf->GetPayloadPtr(buffer); + + /* Call parent callback */ + p_buf = (pj_int16_t*) buffer.Ptr(); + buf_len = buffer.Length() >> 1; + while (buf_len) { + unsigned req; + + req = strm->param.samples_per_frame - strm->rec_buf_len; + if (req > buf_len) + req = buf_len; + pjmedia_copy_samples(strm->rec_buf + strm->rec_buf_len, p_buf, req); + p_buf += req; + buf_len -= req; + strm->rec_buf_len += req; + + if (strm->rec_buf_len >= strm->param.samples_per_frame) { + pjmedia_frame f; + + f.buf = strm->rec_buf; + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.size = strm->param.samples_per_frame << 1; + strm->rec_cb(strm->user_data, &f); + strm->rec_buf_len = 0; + } + } +} + +static void PlayCbPcm2(CVoIPDataBuffer *buf, void *user_data) +{ + struct vas_stream *strm = (struct vas_stream*) user_data; + TPtr8 buffer(0, 0, 0); + pjmedia_frame f; + + /* Get the buffer */ + buf->GetPayloadPtr(buffer); + + /* Call parent callback */ + f.buf = strm->play_buf; + f.size = strm->param.samples_per_frame << 1; + strm->play_cb(strm->user_data, &f); + if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples((pj_int16_t*)f.buf, + strm->param.samples_per_frame); + } + f.size = strm->param.samples_per_frame << 1; + + /* Init buffer attributes and header. */ + buffer.Zero(); + buffer.Append((TUint8*)f.buf, f.size); + + /* Set the buffer */ + buf->SetPayloadPtr(buffer); +} + +#else // not USE_NATIVE_PCM + +static void RecCbPcm(CVoIPDataBuffer *buf, void *user_data) +{ + struct vas_stream *strm = (struct vas_stream*) user_data; + TPtr8 buffer(0, 0, 0); + + /* Get the buffer */ + buf->GetPayloadPtr(buffer); + + /* Buffer has to contain normal speech. */ + pj_assert(buffer[0] == 1 && buffer[1] == 0); + + /* Detect the recorder G.711 frame size, player frame size will follow + * this recorder frame size. + */ + if (vas_g711_frame_len == 0) { + vas_g711_frame_len = buffer.Length() < 160? 80 : 160; + TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples", + vas_g711_frame_len)); + } + + /* Decode VAS buffer (coded in G.711) and put the PCM result into rec_buf. + * Whenever rec_buf is full, call parent stream callback. + */ + unsigned samples_processed = 0; + + while (samples_processed < vas_g711_frame_len) { + unsigned samples_to_process; + unsigned samples_req; + + samples_to_process = vas_g711_frame_len - samples_processed; + samples_req = (strm->param.samples_per_frame / + strm->param.channel_count / + strm->resample_factor) - + strm->rec_buf_len; + if (samples_to_process > samples_req) + samples_to_process = samples_req; + + pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len], + buffer.Ptr() + 2 + samples_processed, + samples_to_process); + + strm->rec_buf_len += samples_to_process; + samples_processed += samples_to_process; + + /* Buffer is full, time to call parent callback */ + if (strm->rec_buf_len == strm->param.samples_per_frame / + strm->param.channel_count / + strm->resample_factor) + { + pjmedia_frame f; + + /* Need to resample clock rate? */ + if (strm->rec_resample) { + unsigned resampled = 0; + + while (resampled < strm->rec_buf_len) { + pjmedia_resample_run(strm->rec_resample, + &strm->rec_buf[resampled], + strm->pcm_buf + + resampled * strm->resample_factor); + resampled += 80; + } + f.buf = strm->pcm_buf; + } else { + f.buf = strm->rec_buf; + } + + /* Need to convert channel count? */ + if (strm->param.channel_count != 1) { + pjmedia_convert_channel_1ton((pj_int16_t*)f.buf, + (pj_int16_t*)f.buf, + strm->param.channel_count, + strm->param.samples_per_frame / + strm->param.channel_count, + 0); + } + + /* Call parent callback */ + f.type = PJMEDIA_FRAME_TYPE_AUDIO; + f.size = strm->param.samples_per_frame << 1; + strm->rec_cb(strm->user_data, &f); + strm->rec_buf_len = 0; + } + } +} + +#endif // USE_NATIVE_PCM + +static void PlayCbPcm(CVoIPDataBuffer *buf, void *user_data) +{ + struct vas_stream *strm = (struct vas_stream*) user_data; + unsigned g711_frame_len = vas_g711_frame_len; + TPtr8 buffer(0, 0, 0); + + /* Get the buffer */ + buf->GetPayloadPtr(buffer); + + /* Init buffer attributes and header. */ + buffer.Zero(); + buffer.Append(1); + buffer.Append(0); + + /* Assume frame size is 10ms if frame size hasn't been known. */ + if (g711_frame_len == 0) + g711_frame_len = 80; + + /* Call parent stream callback to get PCM samples to play, + * encode the PCM samples into G.711 and put it into VAS buffer. + */ + unsigned samples_processed = 0; + + while (samples_processed < g711_frame_len) { + /* Need more samples to play, time to call parent callback */ + if (strm->play_buf_len == 0) { + pjmedia_frame f; + unsigned samples_got; + + f.size = strm->param.samples_per_frame << 1; + if (strm->play_resample || strm->param.channel_count != 1) + f.buf = strm->pcm_buf; + else + f.buf = strm->play_buf; + + /* Call parent callback */ + strm->play_cb(strm->user_data, &f); + if (f.type != PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples((pj_int16_t*)f.buf, + strm->param.samples_per_frame); + } + + samples_got = strm->param.samples_per_frame / + strm->param.channel_count / + strm->resample_factor; + + /* Need to convert channel count? */ + if (strm->param.channel_count != 1) { + pjmedia_convert_channel_nto1((pj_int16_t*)f.buf, + (pj_int16_t*)f.buf, + strm->param.channel_count, + strm->param.samples_per_frame, + PJ_FALSE, + 0); + } + + /* Need to resample clock rate? */ + if (strm->play_resample) { + unsigned resampled = 0; + + while (resampled < samples_got) + { + pjmedia_resample_run(strm->play_resample, + strm->pcm_buf + + resampled * strm->resample_factor, + &strm->play_buf[resampled]); + resampled += 80; + } + } + + strm->play_buf_len = samples_got; + strm->play_buf_start = 0; + } + + unsigned tmp; + + tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - samples_processed); + pjmedia_ulaw_encode((pj_uint8_t*)&strm->play_buf[strm->play_buf_start], + &strm->play_buf[strm->play_buf_start], + tmp); + buffer.Append((TUint8*)&strm->play_buf[strm->play_buf_start], tmp); + samples_processed += tmp; + strm->play_buf_len -= tmp; + strm->play_buf_start += tmp; + } + + /* Set the buffer */ + buf->SetPayloadPtr(buffer); +} + +/**************************************************************************** + * Internal VAS callbacks for non-PCM format + */ + +static void RecCb(CVoIPDataBuffer *buf, void *user_data) +{ + struct vas_stream *strm = (struct vas_stream*) user_data; + pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->rec_buf; + TPtr8 buffer(0, 0, 0); + + /* Get the buffer */ + buf->GetPayloadPtr(buffer); + + switch(strm->param.ext_fmt.id) { + case PJMEDIA_FORMAT_AMR: + { + const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 1; + unsigned len = buffer.Length() - 1; + + pjmedia_frame_ext_append_subframe(frame, p, len << 3, 160); + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_G729: + { + /* Check if we got a normal or SID frame. */ + if (buffer[0] != 0) { + enum { NORMAL_LEN = 22, SID_LEN = 8 }; + TBitStream *bitstream = (TBitStream*)strm->strm_data; + unsigned src_len = buffer.Length()- 2; + + pj_assert(src_len == NORMAL_LEN || src_len == SID_LEN); + + const TDesC8& p = bitstream->CompressG729Frame( + buffer.Right(src_len), + src_len == SID_LEN); + + pjmedia_frame_ext_append_subframe(frame, p.Ptr(), + p.Length() << 3, 80); + } else { /* We got null frame. */ + pjmedia_frame_ext_append_subframe(frame, NULL, 0, 80); + } + + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_ILBC: + { + unsigned samples_got; + + samples_got = strm->param.ext_fmt.bitrate == 15200? 160 : 240; + + /* Check if we got a normal or SID frame. */ + if (buffer[0] != 0) { + const pj_uint8_t *p = (const pj_uint8_t*)buffer.Ptr() + 2; + unsigned len = buffer.Length() - 2; + + pjmedia_frame_ext_append_subframe(frame, p, len << 3, + samples_got); + } else { /* We got null frame. */ + pjmedia_frame_ext_append_subframe(frame, NULL, 0, samples_got); + } + + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_PCMU: + case PJMEDIA_FORMAT_PCMA: + { + unsigned samples_processed = 0; + + /* Make sure it is normal frame. */ + pj_assert(buffer[0] == 1 && buffer[1] == 0); + + /* Detect the recorder G.711 frame size, player frame size will + * follow this recorder frame size. + */ + if (vas_g711_frame_len == 0) { + vas_g711_frame_len = buffer.Length() < 160? 80 : 160; + TRACE_((THIS_FILE, "Detected VAS G.711 frame size = %u samples", + vas_g711_frame_len)); + } + + /* Convert VAS buffer format into pjmedia_frame_ext. Whenever + * samples count in the frame is equal to stream's samples per + * frame, call parent stream callback. + */ + while (samples_processed < vas_g711_frame_len) { + unsigned tmp; + const pj_uint8_t *pb = (const pj_uint8_t*)buffer.Ptr() + + 2 + samples_processed; + + tmp = PJ_MIN(strm->param.samples_per_frame - frame->samples_cnt, + vas_g711_frame_len - samples_processed); + + pjmedia_frame_ext_append_subframe(frame, pb, tmp << 3, tmp); + samples_processed += tmp; + + if (frame->samples_cnt == strm->param.samples_per_frame) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->rec_cb(strm->user_data, (pjmedia_frame*)frame); + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + } + break; + + default: + break; + } +} + +static void PlayCb(CVoIPDataBuffer *buf, void *user_data) +{ + struct vas_stream *strm = (struct vas_stream*) user_data; + pjmedia_frame_ext *frame = (pjmedia_frame_ext*) strm->play_buf; + TPtr8 buffer(0, 0, 0); + + /* Get the buffer */ + buf->GetPayloadPtr(buffer); + + /* Init buffer attributes and header. */ + buffer.Zero(); + + switch(strm->param.ext_fmt.id) { + case PJMEDIA_FORMAT_AMR: + { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + + if (sf->data && sf->bitlen) { + /* AMR header for VAS is one byte, the format (may be!): + * 0xxxxy00, where xxxx:frame type, y:not sure. + */ + unsigned len = (sf->bitlen+7)>>3; + enum {SID_FT = 8 }; + pj_uint8_t amr_header = 4, ft = SID_FT; + + if (len >= pjmedia_codec_amrnb_framelen[0]) + ft = pjmedia_codec_amr_get_mode2(PJ_TRUE, len); + + amr_header |= ft << 3; + buffer.Append(amr_header); + + buffer.Append((TUint8*)sf->data, len); + } else { + enum {NO_DATA_FT = 15 }; + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); + + buffer.Append(amr_header); + } + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + enum {NO_DATA_FT = 15 }; + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); + + buffer.Append(amr_header); + + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + break; + + case PJMEDIA_FORMAT_G729: + { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + + if (sf->data && sf->bitlen) { + enum { NORMAL_LEN = 10, SID_LEN = 2 }; + pj_bool_t sid_frame = ((sf->bitlen >> 3) == SID_LEN); + TBitStream *bitstream = (TBitStream*)strm->strm_data; + const TPtrC8 src(sf->data, sf->bitlen>>3); + const TDesC8 &dst = bitstream->ExpandG729Frame(src, + sid_frame); + if (sid_frame) { + buffer.Append(2); + buffer.Append(0); + } else { + buffer.Append(1); + buffer.Append(0); + } + buffer.Append(dst); + } else { + buffer.Append(2); + buffer.Append(0); + + buffer.AppendFill(0, 22); + } + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + buffer.Append(2); + buffer.Append(0); + + buffer.AppendFill(0, 22); + } + } + break; + + case PJMEDIA_FORMAT_ILBC: + { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + + pj_assert((strm->param.ext_fmt.bitrate == 15200 && + samples_cnt == 160) || + (strm->param.ext_fmt.bitrate != 15200 && + samples_cnt == 240)); + + if (sf->data && sf->bitlen) { + buffer.Append(1); + buffer.Append(0); + buffer.Append((TUint8*)sf->data, sf->bitlen>>3); + } else { + unsigned frame_len; + + buffer.Append(1); + buffer.Append(0); + + /* VAS iLBC frame is 20ms or 30ms */ + frame_len = strm->param.ext_fmt.bitrate == 15200? 38 : 50; + buffer.AppendFill(0, frame_len); + } + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + + unsigned frame_len; + + buffer.Append(1); + buffer.Append(0); + + /* VAS iLBC frame is 20ms or 30ms */ + frame_len = strm->param.ext_fmt.bitrate == 15200? 38 : 50; + buffer.AppendFill(0, frame_len); + + } + } + break; + + case PJMEDIA_FORMAT_PCMU: + case PJMEDIA_FORMAT_PCMA: + { + unsigned samples_ready = 0; + unsigned samples_req = vas_g711_frame_len; + + /* Assume frame size is 10ms if frame size hasn't been known. */ + if (samples_req == 0) + samples_req = 80; + + buffer.Append(1); + buffer.Append(0); + + /* Call parent stream callback to get samples to play. */ + while (samples_ready < samples_req) { + if (frame->samples_cnt == 0) { + frame->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->play_cb(strm->user_data, (pjmedia_frame*)frame); + pj_assert(frame->base.type==PJMEDIA_FRAME_TYPE_EXTENDED || + frame->base.type==PJMEDIA_FRAME_TYPE_NONE); + } + + if (frame->base.type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext_subframe *sf; + unsigned samples_cnt; + + sf = pjmedia_frame_ext_get_subframe(frame, 0); + samples_cnt = frame->samples_cnt / frame->subframe_cnt; + if (sf->data && sf->bitlen) { + buffer.Append((TUint8*)sf->data, sf->bitlen>>3); + } else { + pj_uint8_t silc; + silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)? + pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0); + buffer.AppendFill(silc, samples_cnt); + } + samples_ready += samples_cnt; + + pjmedia_frame_ext_pop_subframes(frame, 1); + + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + pj_uint8_t silc; + + silc = (strm->param.ext_fmt.id==PJMEDIA_FORMAT_PCMU)? + pjmedia_linear2ulaw(0) : pjmedia_linear2alaw(0); + buffer.AppendFill(silc, samples_req - samples_ready); + + samples_ready = samples_req; + frame->samples_cnt = 0; + frame->subframe_cnt = 0; + } + } + } + break; + + default: + break; + } + + /* Set the buffer */ + buf->SetPayloadPtr(buffer); +} + + +/**************************************************************************** + * Factory operations + */ + +/* + * C compatible declaration of VAS factory. + */ +PJ_BEGIN_DECL +PJ_DECL(pjmedia_aud_dev_factory*)pjmedia_symb_vas_factory(pj_pool_factory *pf); +PJ_END_DECL + +/* + * Init VAS audio driver. + */ +PJ_DEF(pjmedia_aud_dev_factory*) pjmedia_symb_vas_factory(pj_pool_factory *pf) +{ + struct vas_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "VAS", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct vas_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f) +{ + struct vas_factory *af = (struct vas_factory*)f; + CVoIPUtilityFactory *vas_factory_; + CVoIPAudioUplinkStream *vas_uplink; + CVoIPAudioDownlinkStream *vas_dnlink; + RArray uplink_formats, dnlink_formats; + unsigned ext_fmt_cnt = 0; + TVersion vas_version(1, 0, 0); /* Not really used at this time */ + TInt err; + + pj_ansi_strcpy(af->dev_info.name, "S60 VAS"); + af->dev_info.default_samples_per_sec = 8000; + af->dev_info.caps = PJMEDIA_AUD_DEV_CAP_EXT_FORMAT | + //PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING | + PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE | + PJMEDIA_AUD_DEV_CAP_VAD | + PJMEDIA_AUD_DEV_CAP_CNG; + af->dev_info.routes = PJMEDIA_AUD_DEV_ROUTE_EARPIECE | + PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER; + af->dev_info.input_count = 1; + af->dev_info.output_count = 1; + af->dev_info.ext_fmt_cnt = 0; + + /* Enumerate supported formats */ + err = CVoIPUtilityFactory::CreateFactory(vas_factory_); + if (err != KErrNone) + goto on_error; + + /* On VAS 2.0, uplink & downlink stream should be instantiated before + * querying formats. + */ + err = vas_factory_->CreateUplinkStream(vas_version, + CVoIPUtilityFactory::EVoIPCall, + vas_uplink); + if (err != KErrNone) + goto on_error; + + err = vas_factory_->CreateDownlinkStream(vas_version, + CVoIPUtilityFactory::EVoIPCall, + vas_dnlink); + if (err != KErrNone) + goto on_error; + + uplink_formats.Reset(); + err = vas_factory_->GetSupportedUplinkFormats(uplink_formats); + if (err != KErrNone) + goto on_error; + + dnlink_formats.Reset(); + err = vas_factory_->GetSupportedDownlinkFormats(dnlink_formats); + if (err != KErrNone) + goto on_error; + + /* Free the streams, they are just used for querying formats */ + delete vas_uplink; + vas_uplink = NULL; + delete vas_dnlink; + vas_dnlink = NULL; + delete vas_factory_; + vas_factory_ = NULL; + + for (TInt i = 0; i < dnlink_formats.Count(); i++) { + /* Format must be supported by both downlink & uplink. */ + if (uplink_formats.Find(dnlink_formats[i]) == KErrNotFound) + continue; + + switch (dnlink_formats[i]) { + case EAMR_NB: + af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_AMR; + af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 7400; + af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE; + break; + + case EG729: + af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_G729; + af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 8000; + af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE; + break; + + case EILBC: + af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_ILBC; + af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 13333; + af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_TRUE; + break; + + case EG711: +#if PJMEDIA_AUDIO_DEV_SYMB_VAS_VERSION==2 + case EG711_10MS: +#endif + af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMU; + af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000; + af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE; + ++ext_fmt_cnt; + af->dev_info.ext_fmt[ext_fmt_cnt].id = PJMEDIA_FORMAT_PCMA; + af->dev_info.ext_fmt[ext_fmt_cnt].bitrate = 64000; + af->dev_info.ext_fmt[ext_fmt_cnt].vad = PJ_FALSE; + break; + + default: + continue; + } + + ++ext_fmt_cnt; + } + + af->dev_info.ext_fmt_cnt = ext_fmt_cnt; + + uplink_formats.Close(); + dnlink_formats.Close(); + + PJ_LOG(3, (THIS_FILE, "VAS initialized")); + + return PJ_SUCCESS; + +on_error: + return PJ_RETURN_OS_ERROR(err); +} + +/* API: destroy factory */ +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct vas_factory *af = (struct vas_factory*)f; + pj_pool_t *pool = af->pool; + + af->pool = NULL; + pj_pool_release(pool); + + PJ_LOG(3, (THIS_FILE, "VAS destroyed")); + + return PJ_SUCCESS; +} + +/* API: refresh the device list */ +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_ENOTSUP; +} + +/* API: get number of devices */ +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return 1; +} + +/* API: get device info */ +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct vas_factory *af = (struct vas_factory*)f; + + PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &af->dev_info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct vas_factory *af = (struct vas_factory*)f; + + PJ_ASSERT_RETURN(index == 0, PJMEDIA_EAUD_INVDEV); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + param->clock_rate = af->dev_info.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = BITS_PER_SAMPLE; + param->flags = PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE; + param->output_route = PJMEDIA_AUD_DEV_ROUTE_EARPIECE; + + return PJ_SUCCESS; +} + + +/* API: create stream */ +static pj_status_t 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_aud_strm) +{ + struct vas_factory *af = (struct vas_factory*)f; + pj_pool_t *pool; + struct vas_stream *strm; + + CPjAudioSetting vas_setting; + PjAudioCallback vas_rec_cb; + PjAudioCallback vas_play_cb; + + /* Can only support 16bits per sample */ + PJ_ASSERT_RETURN(param->bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); + + /* Supported clock rates: + * - for non-PCM format: 8kHz + * - for PCM format: 8kHz and 16kHz + */ + PJ_ASSERT_RETURN(param->clock_rate == 8000 || + (param->clock_rate == 16000 && + param->ext_fmt.id == PJMEDIA_FORMAT_L16), + PJ_EINVAL); + + /* Supported channels number: + * - for non-PCM format: mono + * - for PCM format: mono and stereo + */ + PJ_ASSERT_RETURN(param->channel_count == 1 || + (param->channel_count == 2 && + param->ext_fmt.id == PJMEDIA_FORMAT_L16), + PJ_EINVAL); + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(af->pf, "vas-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct vas_stream); + strm->pool = pool; + strm->param = *param; + + if (strm->param.flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT == 0) + strm->param.ext_fmt.id = PJMEDIA_FORMAT_L16; + + /* Set audio engine fourcc. */ + switch(strm->param.ext_fmt.id) { + case PJMEDIA_FORMAT_L16: +#ifdef USE_NATIVE_PCM + vas_setting.format = EPCM16; +#else + vas_setting.format = EG711; +#endif + break; + case PJMEDIA_FORMAT_PCMU: + case PJMEDIA_FORMAT_PCMA: + vas_setting.format = EG711; + break; + case PJMEDIA_FORMAT_AMR: + vas_setting.format = EAMR_NB; + break; + case PJMEDIA_FORMAT_G729: + vas_setting.format = EG729; + break; + case PJMEDIA_FORMAT_ILBC: + vas_setting.format = EILBC; + break; + default: + vas_setting.format = ENULL; + break; + } + + /* Set audio engine mode. */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) + { +#ifdef USE_NATIVE_PCM + vas_setting.mode = 0; +#else + vas_setting.mode = CVoIPFormatIntfc::EG711uLaw; +#endif + } + else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_AMR) + { + vas_setting.mode = strm->param.ext_fmt.bitrate; + } + else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU) + { + vas_setting.mode = CVoIPFormatIntfc::EG711uLaw; + } + else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA) + { + vas_setting.mode = CVoIPFormatIntfc::EG711ALaw; + } + else if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC) + { + if (strm->param.ext_fmt.bitrate == 15200) + vas_setting.mode = CVoIPFormatIntfc::EiLBC20mSecFrame; + else + vas_setting.mode = CVoIPFormatIntfc::EiLBC30mSecFrame; + } else { + vas_setting.mode = 0; + } + + /* Disable VAD on L16, G711, iLBC, and also G729 (G729's SID + * potentially cause noise?). + */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMU || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_PCMA || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_ILBC || + strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) + { + vas_setting.vad = EFalse; + } else { + vas_setting.vad = strm->param.ext_fmt.vad; + } + + /* Set other audio engine attributes. */ + vas_setting.plc = strm->param.plc_enabled; + vas_setting.cng = vas_setting.vad; + vas_setting.loudspk = + strm->param.output_route==PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER; + + /* Set audio engine callbacks. */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) { +#ifdef USE_NATIVE_PCM + vas_play_cb = &PlayCbPcm2; + vas_rec_cb = &RecCbPcm2; +#else + vas_play_cb = &PlayCbPcm; + vas_rec_cb = &RecCbPcm; +#endif + } else { + vas_play_cb = &PlayCb; + vas_rec_cb = &RecCb; + } + + strm->rec_cb = rec_cb; + strm->play_cb = play_cb; + strm->user_data = user_data; + strm->resample_factor = strm->param.clock_rate / 8000; + + /* play_buf size is samples per frame scaled in to 8kHz mono. */ + strm->play_buf = (pj_int16_t*)pj_pool_zalloc( + pool, + (strm->param.samples_per_frame / + strm->resample_factor / + strm->param.channel_count) << 1); + strm->play_buf_len = 0; + strm->play_buf_start = 0; + + /* rec_buf size is samples per frame scaled in to 8kHz mono. */ + strm->rec_buf = (pj_int16_t*)pj_pool_zalloc( + pool, + (strm->param.samples_per_frame / + strm->resample_factor / + strm->param.channel_count) << 1); + strm->rec_buf_len = 0; + + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_G729) { + TBitStream *g729_bitstream = new TBitStream; + + PJ_ASSERT_RETURN(g729_bitstream, PJ_ENOMEM); + strm->strm_data = (void*)g729_bitstream; + } + + /* Init resampler when format is PCM and clock rate is not 8kHz */ + if (strm->param.clock_rate != 8000 && + strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16) + { + pj_status_t status; + + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + /* Create resample for recorder */ + status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1, + 8000, + strm->param.clock_rate, + 80, + &strm->rec_resample); + if (status != PJ_SUCCESS) + return status; + } + + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + /* Create resample for player */ + status = pjmedia_resample_create( pool, PJ_TRUE, PJ_FALSE, 1, + strm->param.clock_rate, + 8000, + 80 * strm->resample_factor, + &strm->play_resample); + if (status != PJ_SUCCESS) + return status; + } + } + + /* Create PCM buffer, when the clock rate is not 8kHz or not mono */ + if (strm->param.ext_fmt.id == PJMEDIA_FORMAT_L16 && + (strm->resample_factor > 1 || strm->param.channel_count != 1)) + { + strm->pcm_buf = (pj_int16_t*)pj_pool_zalloc(pool, + strm->param.samples_per_frame << 1); + } + + + /* Create the audio engine. */ + TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, + vas_rec_cb, vas_play_cb, + strm, vas_setting)); + if (err != KErrNone) { + pj_pool_release(pool); + return PJ_RETURN_OS_ERROR(err); + } + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct vas_stream *strm = (struct vas_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Update the output volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct vas_stream *strm = (struct vas_stream*)s; + pj_status_t status = PJ_ENOTSUP; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + *(pjmedia_aud_dev_route*)pval = strm->param.output_route; + status = PJ_SUCCESS; + } + break; + + /* There is a case that GetMaxGain() stucks, e.g: in N95. */ + /* + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_gain = strm->engine->GetMaxGain(); + TInt gain = strm->engine->GetGain(); + + if (max_gain > 0 && gain >= 0) { + *(unsigned*)pval = gain * 100 / max_gain; + status = PJ_SUCCESS; + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + */ + + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_vol = strm->engine->GetMaxVolume(); + TInt vol = strm->engine->GetVolume(); + + if (max_vol > 0 && vol >= 0) { + *(unsigned*)pval = vol * 100 / max_vol; + status = PJ_SUCCESS; + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + } + break; + default: + break; + } + + return status; +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct vas_stream *strm = (struct vas_stream*)s; + pj_status_t status = PJ_ENOTSUP; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + switch (cap) { + case PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + pjmedia_aud_dev_route r = *(const pjmedia_aud_dev_route*)pval; + TInt err; + + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + switch (r) { + case PJMEDIA_AUD_DEV_ROUTE_DEFAULT: + case PJMEDIA_AUD_DEV_ROUTE_EARPIECE: + err = strm->engine->ActivateSpeaker(EFalse); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + break; + case PJMEDIA_AUD_DEV_ROUTE_LOUDSPEAKER: + err = strm->engine->ActivateSpeaker(ETrue); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + break; + default: + status = PJ_EINVAL; + break; + } + if (status == PJ_SUCCESS) + strm->param.output_route = r; + } + break; + + /* There is a case that GetMaxGain() stucks, e.g: in N95. */ + /* + case PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_gain = strm->engine->GetMaxGain(); + if (max_gain > 0) { + TInt gain, err; + + gain = *(unsigned*)pval * max_gain / 100; + err = strm->engine->SetGain(gain); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + if (status == PJ_SUCCESS) + strm->param.input_vol = *(unsigned*)pval; + } + break; + */ + + case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { + PJ_ASSERT_RETURN(strm->engine, PJ_EINVAL); + + TInt max_vol = strm->engine->GetMaxVolume(); + if (max_vol > 0) { + TInt vol, err; + + vol = *(unsigned*)pval * max_vol / 100; + err = strm->engine->SetVolume(vol); + status = (err==KErrNone)? PJ_SUCCESS:PJ_RETURN_OS_ERROR(err); + } else { + status = PJMEDIA_EAUD_NOTREADY; + } + if (status == PJ_SUCCESS) + strm->param.output_vol = *(unsigned*)pval; + } + break; + default: + break; + } + + return status; +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *strm) +{ + struct vas_stream *stream = (struct vas_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->engine) { + enum { VAS_WAIT_START = 2000 }; /* in msecs */ + TTime start, now; + TInt err = stream->engine->Start(); + + if (err != KErrNone) + return PJ_RETURN_OS_ERROR(err); + + /* Perform synchronous start, timeout after VAS_WAIT_START ms */ + start.UniversalTime(); + do { + pj_symbianos_poll(-1, 100); + now.UniversalTime(); + } while (!stream->engine->IsStarted() && + (now.MicroSecondsFrom(start) < VAS_WAIT_START * 1000)); + + if (stream->engine->IsStarted()) { + + /* Apply output volume setting if specified */ + if (stream->param.flags & + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) + { + stream_set_cap(strm, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &stream->param.output_vol); + } + + return PJ_SUCCESS; + } else { + return PJ_ETIMEDOUT; + } + } + + return PJ_EINVALIDOP; +} + +/* API: Stop stream. */ +static pj_status_t stream_stop(pjmedia_aud_stream *strm) +{ + struct vas_stream *stream = (struct vas_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->engine) { + stream->engine->Stop(); + } + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *strm) +{ + struct vas_stream *stream = (struct vas_stream*)strm; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + stream_stop(strm); + + delete stream->engine; + stream->engine = NULL; + + if (stream->param.ext_fmt.id == PJMEDIA_FORMAT_G729) { + TBitStream *g729_bitstream = (TBitStream*)stream->strm_data; + stream->strm_data = NULL; + delete g729_bitstream; + } + + pj_pool_t *pool; + pool = stream->pool; + if (pool) { + stream->pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + +#endif // PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS + diff --git a/pjmedia/src/pjmedia-audiodev/wmme_dev.c b/pjmedia/src/pjmedia-audiodev/wmme_dev.c new file mode 100644 index 0000000..3f9dff7 --- /dev/null +++ b/pjmedia/src/pjmedia-audiodev/wmme_dev.c @@ -0,0 +1,1524 @@ +/* $Id: wmme_dev.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono + * + * 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 + */ +#include +#include +#include +#include +#include +#include + +#if PJMEDIA_AUDIO_DEV_HAS_WMME + +#ifdef _MSC_VER +# pragma warning(push, 3) +#endif + +#include +#include +#include + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#ifndef PJMEDIA_WMME_DEV_USE_MMDEVICE_API +# define PJMEDIA_WMME_DEV_USE_MMDEVICE_API \ + (defined(_WIN32_WINNT) && (_WIN32_WINNT>=0x0600)) +#endif + +#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0 +# define DRV_QUERYFUNCTIONINSTANCEID (DRV_RESERVED + 17) +# define DRV_QUERYFUNCTIONINSTANCEIDSIZE (DRV_RESERVED + 18) +#endif + +/* mingw lacks WAVE_FORMAT_ALAW/MULAW */ +#ifndef WAVE_FORMAT_ALAW +# define WAVE_FORMAT_ALAW 0x0006 +#endif +#ifndef WAVE_FORMAT_MULAW +# define WAVE_FORMAT_MULAW 0x0007 +#endif + +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE!=0 +# pragma comment(lib, "Coredll.lib") +#elif defined(_MSC_VER) +# pragma comment(lib, "winmm.lib") +#endif + + +#define THIS_FILE "wmme_dev.c" + +/* WMME device info */ +struct wmme_dev_info +{ + pjmedia_aud_dev_info info; + unsigned deviceId; + const wchar_t *endpointId; +}; + +/* WMME factory */ +struct wmme_factory +{ + pjmedia_aud_dev_factory base; + pj_pool_t *base_pool; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct wmme_dev_info *dev_info; +}; + + +/* Individual WMME capture/playback stream descriptor */ +struct wmme_channel +{ + union + { + HWAVEIN In; + HWAVEOUT Out; + } hWave; + + WAVEHDR *WaveHdr; + HANDLE hEvent; + DWORD dwBufIdx; + DWORD dwMaxBufIdx; + pj_timestamp timestamp; +}; + + +/* Sound stream. */ +struct wmme_stream +{ + pjmedia_aud_stream base; /**< Base stream */ + pjmedia_aud_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_aud_rec_cb rec_cb; /**< Capture callback. */ + pjmedia_aud_play_cb play_cb; /**< Playback callback. */ + void *user_data; /**< Application data. */ + + struct wmme_channel play_strm; /**< Playback stream. */ + struct wmme_channel rec_strm; /**< Capture stream. */ + + void *buffer; /**< Temp. frame buffer. */ + pjmedia_format_id fmt_id; /**< Frame format */ + pj_uint8_t silence_char; /**< Silence pattern */ + unsigned bytes_per_frame; /**< Bytes per frame */ + + pjmedia_frame_ext *xfrm; /**< Extended frame buffer */ + unsigned xfrm_size; /**< Total ext frm size */ + + pj_thread_t *thread; /**< Thread handle. */ + HANDLE thread_quit_event; /**< Quit signal to thread */ +}; + + +/* Prototypes */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f); +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f); +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f); +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f); +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info); +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param); +static pj_status_t 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_aud_strm); + +static pj_status_t stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value); +static pj_status_t stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value); +static pj_status_t stream_start(pjmedia_aud_stream *strm); +static pj_status_t stream_stop(pjmedia_aud_stream *strm); +static pj_status_t stream_destroy(pjmedia_aud_stream *strm); + + +/* Operations */ +static pjmedia_aud_dev_factory_op factory_op = +{ + &factory_init, + &factory_destroy, + &factory_get_dev_count, + &factory_get_dev_info, + &factory_default_param, + &factory_create_stream, + &factory_refresh +}; + +static pjmedia_aud_stream_op stream_op = +{ + &stream_get_param, + &stream_get_cap, + &stream_set_cap, + &stream_start, + &stream_stop, + &stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init WMME audio driver. + */ +pjmedia_aud_dev_factory* pjmedia_wmme_factory(pj_pool_factory *pf) +{ + struct wmme_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "WMME base", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory); + f->pf = pf; + f->base_pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* Internal: Windows Vista and Windows 7 have their device + * names truncated when using the waveXXX api. The names + * should be acquired from the MMDevice APIs + */ +#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0 + +#define COBJMACROS +#include +#define INITGUID +#include +#include + +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, + 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, + 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6); + +static void get_dev_names(pjmedia_aud_dev_factory *f) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + HRESULT coinit = S_OK; + HRESULT hr = S_OK; + IMMDeviceEnumerator *pEnumerator = NULL; + IMMDeviceCollection *pDevices = NULL; + UINT cDevices = 0; + UINT nDevice = 0; + + coinit = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (coinit == RPC_E_CHANGED_MODE) + coinit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + if (FAILED(coinit)) + goto on_error; + + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, + CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + goto on_error; + hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll, + DEVICE_STATE_ACTIVE, + &pDevices); + if (FAILED(hr)) + goto on_error; + hr = IMMDeviceCollection_GetCount(pDevices, &cDevices); + if (FAILED(hr)) + goto on_error; + + for (nDevice = 0; nDevice < cDevices; ++nDevice) { + IMMDevice *pDevice = NULL; + IPropertyStore *pProps = NULL; + LPWSTR pwszID = NULL; + PROPVARIANT varName; + unsigned i; + + PropVariantInit(&varName); + + hr = IMMDeviceCollection_Item(pDevices, nDevice, &pDevice); + if (FAILED(hr)) + goto cleanup; + hr = IMMDevice_GetId(pDevice, &pwszID); + if (FAILED(hr)) + goto cleanup; + hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProps); + if (FAILED(hr)) + goto cleanup; + hr = IPropertyStore_GetValue(pProps, &PKEY_Device_FriendlyName, + &varName); + if (FAILED(hr)) + goto cleanup; + + for (i = 0; i < wf->dev_count; ++i) { + if (0 == wcscmp(wf->dev_info[i].endpointId, pwszID)) { + wcstombs(wf->dev_info[i].info.name, varName.pwszVal, + sizeof(wf->dev_info[i].info.name)); + break; + } + } + + PropVariantClear(&varName); + + cleanup: + if (pProps) + IPropertyStore_Release(pProps); + if (pwszID) + CoTaskMemFree(pwszID); + if (pDevice) + hr = IMMDevice_Release(pDevice); + } + +on_error: + if (pDevices) + hr = IMMDeviceCollection_Release(pDevices); + + if (pEnumerator) + hr = IMMDeviceEnumerator_Release(pEnumerator); + + if (SUCCEEDED(coinit)) + CoUninitialize(); +} + +#else + +static void get_dev_names(pjmedia_aud_dev_factory *f) +{ + PJ_UNUSED_ARG(f); +} + +#endif + +/* Internal: build device info from WAVEINCAPS/WAVEOUTCAPS */ +static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi, + const WAVEINCAPS *wic, const WAVEOUTCAPS *woc) +{ +#define WIC_WOC(wic,woc,field) (wic? wic->field : woc->field) + + pj_bzero(wdi, sizeof(*wdi)); + wdi->deviceId = deviceId; + + /* Device Name */ + if (deviceId==WAVE_MAPPER) { + strncpy(wdi->info.name, "Wave mapper", sizeof(wdi->info.name)); + wdi->info.name[sizeof(wdi->info.name)-1] = '\0'; + } else { + const pj_char_t *szPname = WIC_WOC(wic, woc, szPname); + PJ_DECL_ANSI_TEMP_BUF(wTmp, sizeof(wdi->info.name)); + + strncpy(wdi->info.name, + PJ_NATIVE_TO_STRING(szPname, wTmp, PJ_ARRAY_SIZE(wTmp)), + sizeof(wdi->info.name)); + wdi->info.name[sizeof(wdi->info.name)-1] = '\0'; + } + + wdi->info.default_samples_per_sec = 16000; + strcpy(wdi->info.driver, "WMME"); + + if (wic) { + wdi->info.input_count = wic->wChannels; + wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + + /* Sometimes a device can return a rediculously large number of + * channels. This happened with an SBLive card on a Windows ME box. + * It also happens on Win XP! + */ + if (wdi->info.input_count<1 || wdi->info.input_count>256) { + wdi->info.input_count = 2; + } + } + + if (woc) { + wdi->info.output_count = woc->wChannels; + wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + + if (woc->dwSupport & WAVECAPS_VOLUME) { + wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + /* Sometimes a device can return a rediculously large number of + * channels. This happened with an SBLive card on a Windows ME box. + * It also happens on Win XP! + */ + if (wdi->info.output_count<1 || wdi->info.output_count>256) { + wdi->info.output_count = 2; + } + } + + /* Extended formats */ + wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT; + wdi->info.ext_fmt_cnt = 2; + pjmedia_format_init_audio(&wdi->info.ext_fmt[0], + PJMEDIA_FORMAT_PCMU, 8000, 1, 8, + 20000, 64000, 64000); + pjmedia_format_init_audio(&wdi->info.ext_fmt[0], + PJMEDIA_FORMAT_PCMA, 8000, 1, 8, + 20000, 64000, 64000); +} + +/* API: init factory */ +static pj_status_t factory_init(pjmedia_aud_dev_factory *f) +{ + pj_status_t ret = factory_refresh(f); + if (ret != PJ_SUCCESS) + return ret; + + PJ_LOG(4, (THIS_FILE, "WMME initialized")); + return PJ_SUCCESS; +} + +/* API: refresh the device list */ +static pj_status_t factory_refresh(pjmedia_aud_dev_factory *f) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + unsigned c; + int i; + int inputDeviceCount, outputDeviceCount, devCount=0; + pj_bool_t waveMapperAdded = PJ_FALSE; + + if (wf->pool != NULL) { + pj_pool_release(wf->pool); + wf->pool = NULL; + } + + /* Enumerate sound devices */ + wf->dev_count = 0; + wf->pool = pj_pool_create(wf->pf, "WMME", 1000, 1000, NULL); + + inputDeviceCount = waveInGetNumDevs(); + devCount += inputDeviceCount; + + outputDeviceCount = waveOutGetNumDevs(); + devCount += outputDeviceCount; + + if (devCount) { + /* Assume there is WAVE_MAPPER */ + devCount += 2; + } + + if (devCount==0) { + PJ_LOG(4,(THIS_FILE, "WMME found no sound devices")); + /* Enabling this will cause pjsua-lib initialization to fail when there + * is no sound device installed in the system, even when pjsua has been + * run with --null-audio. Moreover, it might be better to think that + * the WMME backend initialization is successfull, regardless there is + * no audio device installed, as later application can check it using + * get_dev_count(). + return PJMEDIA_EAUD_NODEV; + */ + return PJ_SUCCESS; + } + + wf->dev_info = (struct wmme_dev_info*) + pj_pool_calloc(wf->pool, devCount, + sizeof(struct wmme_dev_info)); + + if (inputDeviceCount && outputDeviceCount) { + /* Attempt to add WAVE_MAPPER as input and output device */ + WAVEINCAPS wic; + MMRESULT mr; + + pj_bzero(&wic, sizeof(WAVEINCAPS)); + mr = waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(WAVEINCAPS)); + + if (mr == MMSYSERR_NOERROR) { + WAVEOUTCAPS woc; + + pj_bzero(&woc, sizeof(WAVEOUTCAPS)); + mr = waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(WAVEOUTCAPS)); + if (mr == MMSYSERR_NOERROR) { + build_dev_info(WAVE_MAPPER, &wf->dev_info[wf->dev_count], + &wic, &woc); + wf->dev_info[wf->dev_count].endpointId = L""; + ++wf->dev_count; + waveMapperAdded = PJ_TRUE; + } + } + + } + + if (inputDeviceCount > 0) { + /* -1 is the WAVE_MAPPER */ + for (i = (waveMapperAdded? 0 : -1); i < inputDeviceCount; ++i) { + UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i); + WAVEINCAPS wic; + MMRESULT mr; + DWORD cbEndpointId; + + pj_bzero(&wic, sizeof(WAVEINCAPS)); + + mr = waveInGetDevCaps(uDeviceID, &wic, sizeof(WAVEINCAPS)); + + if (mr == MMSYSERR_NOMEM) + return PJ_ENOMEM; + + if (mr != MMSYSERR_NOERROR) + continue; + + build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count], + &wic, NULL); + +#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0 + /* Try to get the endpoint id of the audio device */ + wf->dev_info[wf->dev_count].endpointId = L""; + + mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID), + DRV_QUERYFUNCTIONINSTANCEIDSIZE, + (DWORD_PTR)&cbEndpointId, (DWORD_PTR)NULL); + if (mr == MMSYSERR_NOERROR) { + const wchar_t **epid = &wf->dev_info[wf->dev_count].endpointId; + *epid = (const wchar_t*) pj_pool_calloc(wf->pool, + cbEndpointId, 1); + mr = waveInMessage((HWAVEIN)IntToPtr(uDeviceID), + DRV_QUERYFUNCTIONINSTANCEID, + (DWORD_PTR)*epid, + cbEndpointId); + } +#else + PJ_UNUSED_ARG(cbEndpointId); +#endif + + ++wf->dev_count; + } + } + + if( outputDeviceCount > 0 ) + { + /* -1 is the WAVE_MAPPER */ + for (i = (waveMapperAdded? 0 : -1); i < outputDeviceCount; ++i) { + UINT uDeviceID = (UINT)((i==-1) ? WAVE_MAPPER : i); + WAVEOUTCAPS woc; + MMRESULT mr; + DWORD cbEndpointId; + + pj_bzero(&woc, sizeof(WAVEOUTCAPS)); + + mr = waveOutGetDevCaps(uDeviceID, &woc, sizeof(WAVEOUTCAPS)); + + if (mr == MMSYSERR_NOMEM) + return PJ_ENOMEM; + + if (mr != MMSYSERR_NOERROR) + continue; + + build_dev_info(uDeviceID, &wf->dev_info[wf->dev_count], + NULL, &woc); + +#if PJMEDIA_WMME_DEV_USE_MMDEVICE_API != 0 + /* Try to get the endpoint id of the audio device */ + wf->dev_info[wf->dev_count].endpointId = L""; + + mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID), + DRV_QUERYFUNCTIONINSTANCEIDSIZE, + (DWORD_PTR)&cbEndpointId, (DWORD_PTR)NULL); + if (mr == MMSYSERR_NOERROR) { + const wchar_t **epid = &wf->dev_info[wf->dev_count].endpointId; + *epid = (const wchar_t*)pj_pool_calloc(wf->pool, + cbEndpointId, 1); + mr = waveOutMessage((HWAVEOUT)IntToPtr(uDeviceID), + DRV_QUERYFUNCTIONINSTANCEID, + (DWORD_PTR)*epid, cbEndpointId); + } +#else + PJ_UNUSED_ARG(cbEndpointId); +#endif + + ++wf->dev_count; + } + } + + /* On Windows Vista and Windows 7 get the full device names */ + get_dev_names(f); + + PJ_LOG(4, (THIS_FILE, "WMME found %d devices:", + wf->dev_count)); + for (c = 0; c < wf->dev_count; ++c) { + PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)", + c, + wf->dev_info[c].info.name, + wf->dev_info[c].info.input_count, + wf->dev_info[c].info.output_count)); + } + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t factory_destroy(pjmedia_aud_dev_factory *f) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + pj_pool_t *pool = wf->base_pool; + + pj_pool_release(wf->pool); + wf->base_pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned factory_get_dev_count(pjmedia_aud_dev_factory *f) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + return wf->dev_count; +} + +/* API: get device info */ +static pj_status_t factory_get_dev_info(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_dev_info *info) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + + PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_memcpy(info, &wf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, + unsigned index, + pjmedia_aud_param *param) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + struct wmme_dev_info *di = &wf->dev_info[index]; + + PJ_ASSERT_RETURN(index < wf->dev_count, PJMEDIA_EAUD_INVDEV); + + pj_bzero(param, sizeof(*param)); + if (di->info.input_count && di->info.output_count) { + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; + } else if (di->info.input_count) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->rec_id = index; + param->play_id = PJMEDIA_AUD_INVALID_DEV; + } else if (di->info.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 = di->info.default_samples_per_sec; + param->channel_count = 1; + param->samples_per_frame = di->info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = 16; + param->flags = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; + param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + + return PJ_SUCCESS; +} + +/* Internal: init WAVEFORMATEX */ +static pj_status_t init_waveformatex(LPWAVEFORMATEX wfx, + const pjmedia_aud_param *prm) +{ + + pj_bzero(wfx, sizeof(WAVEFORMATEX)); + if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) { + enum { BYTES_PER_SAMPLE = 2 }; + wfx->wFormatTag = WAVE_FORMAT_PCM; + wfx->nChannels = (pj_uint16_t)prm->channel_count; + wfx->nSamplesPerSec = prm->clock_rate; + wfx->nBlockAlign = (pj_uint16_t)(prm->channel_count * + BYTES_PER_SAMPLE); + wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count * + BYTES_PER_SAMPLE; + wfx->wBitsPerSample = 16; + + return PJ_SUCCESS; + + } else if ((prm->flags & PJMEDIA_AUD_DEV_CAP_EXT_FORMAT) && + (prm->ext_fmt.id == PJMEDIA_FORMAT_PCMA || + prm->ext_fmt.id == PJMEDIA_FORMAT_PCMU)) + { + unsigned ptime; + + ptime = prm->samples_per_frame * 1000 / + (prm->clock_rate * prm->channel_count); + wfx->wFormatTag = (pj_uint16_t) + ((prm->ext_fmt.id==PJMEDIA_FORMAT_PCMA) ? + WAVE_FORMAT_ALAW : WAVE_FORMAT_MULAW); + wfx->nChannels = (pj_uint16_t)prm->channel_count; + wfx->nSamplesPerSec = prm->clock_rate; + wfx->nAvgBytesPerSec = prm->clock_rate * prm->channel_count; + wfx->nBlockAlign = (pj_uint16_t)(wfx->nAvgBytesPerSec * ptime / + 1000); + wfx->wBitsPerSample = 8; + wfx->cbSize = 0; + + return PJ_SUCCESS; + + } else { + + return PJMEDIA_EAUD_BADFORMAT; + + } +} + +/* Get format name */ +static const char *get_fmt_name(pj_uint32_t id) +{ + static char name[8]; + + if (id == PJMEDIA_FORMAT_L16) + return "PCM"; + pj_memcpy(name, &id, 4); + name[4] = '\0'; + return name; +} + +/* Internal: create WMME player device. */ +static pj_status_t init_player_stream( struct wmme_factory *wf, + pj_pool_t *pool, + struct wmme_stream *parent, + struct wmme_channel *wmme_strm, + const pjmedia_aud_param *prm, + unsigned buffer_count) +{ + MMRESULT mr; + WAVEFORMATEX wfx; + unsigned i, ptime; + DWORD flag; + pj_status_t status; + + PJ_ASSERT_RETURN(prm->play_id < (int)wf->dev_count, PJ_EINVAL); + + /* + * Create a wait event. + */ + wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (NULL == wmme_strm->hEvent) + return pj_get_os_error(); + + /* + * Set up wave format structure for opening the device. + */ + status = init_waveformatex(&wfx, prm); + if (status != PJ_SUCCESS) + return status; + + ptime = prm->samples_per_frame * 1000 / + (prm->clock_rate * prm->channel_count); + parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000; + + flag = CALLBACK_EVENT; + if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) + flag |= WAVE_FORMAT_DIRECT; + + /* + * Open wave device. + */ + mr = waveOutOpen(&wmme_strm->hWave.Out, + wf->dev_info[prm->play_id].deviceId, + &wfx, (DWORD)wmme_strm->hEvent, 0, flag); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + + /* Pause the wave out device */ + mr = waveOutPause(wmme_strm->hWave.Out); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + + /* + * Create the buffers. + */ + wmme_strm->WaveHdr = (WAVEHDR*) + pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count); + for (i = 0; i < buffer_count; ++i) { + wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, + parent->bytes_per_frame); + wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame; + mr = waveOutPrepareHeader(wmme_strm->hWave.Out, + &(wmme_strm->WaveHdr[i]), + sizeof(WAVEHDR)); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]), + sizeof(WAVEHDR)); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + } + + wmme_strm->dwBufIdx = 0; + wmme_strm->dwMaxBufIdx = buffer_count; + wmme_strm->timestamp.u64 = 0; + + /* Done setting up play device. */ + PJ_LOG(4, (THIS_FILE, + " WaveAPI Sound player \"%s\" initialized (" + "format=%s, clock_rate=%d, " + "channel_count=%d, samples_per_frame=%d (%dms))", + wf->dev_info[prm->play_id].info.name, + get_fmt_name(prm->ext_fmt.id), + prm->clock_rate, prm->channel_count, prm->samples_per_frame, + prm->samples_per_frame * 1000 / prm->clock_rate)); + + return PJ_SUCCESS; +} + + +/* Internal: create Windows Multimedia recorder device */ +static pj_status_t init_capture_stream( struct wmme_factory *wf, + pj_pool_t *pool, + struct wmme_stream *parent, + struct wmme_channel *wmme_strm, + const pjmedia_aud_param *prm, + unsigned buffer_count) +{ + MMRESULT mr; + WAVEFORMATEX wfx; + DWORD flag; + unsigned i, ptime; + + PJ_ASSERT_RETURN(prm->rec_id < (int)wf->dev_count, PJ_EINVAL); + + /* + * Create a wait event. + */ + wmme_strm->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (NULL == wmme_strm->hEvent) { + return pj_get_os_error(); + } + + /* + * Set up wave format structure for opening the device. + */ + init_waveformatex(&wfx, prm); + ptime = prm->samples_per_frame * 1000 / + (prm->clock_rate * prm->channel_count); + parent->bytes_per_frame = wfx.nAvgBytesPerSec * ptime / 1000; + + flag = CALLBACK_EVENT; + if (prm->ext_fmt.id == PJMEDIA_FORMAT_L16) + flag |= WAVE_FORMAT_DIRECT; + + /* + * Open wave device. + */ + mr = waveInOpen(&wmme_strm->hWave.In, + wf->dev_info[prm->rec_id].deviceId, + &wfx, (DWORD)wmme_strm->hEvent, 0, flag); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + + /* + * Create the buffers. + */ + wmme_strm->WaveHdr = (WAVEHDR*) + pj_pool_zalloc(pool, sizeof(WAVEHDR) * buffer_count); + for (i = 0; i < buffer_count; ++i) { + wmme_strm->WaveHdr[i].lpData = pj_pool_zalloc(pool, + parent->bytes_per_frame); + wmme_strm->WaveHdr[i].dwBufferLength = parent->bytes_per_frame; + mr = waveInPrepareHeader(wmme_strm->hWave.In, + &(wmme_strm->WaveHdr[i]), + sizeof(WAVEHDR)); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]), + sizeof(WAVEHDR)); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + } + + wmme_strm->dwBufIdx = 0; + wmme_strm->dwMaxBufIdx = buffer_count; + wmme_strm->timestamp.u64 = 0; + + /* Done setting up play device. */ + PJ_LOG(4,(THIS_FILE, + " WaveAPI Sound recorder \"%s\" initialized " + "(format=%s, clock_rate=%d, " + "channel_count=%d, samples_per_frame=%d (%dms))", + wf->dev_info[prm->rec_id].info.name, + get_fmt_name(prm->ext_fmt.id), + prm->clock_rate, prm->channel_count, prm->samples_per_frame, + prm->samples_per_frame * 1000 / prm->clock_rate)); + + return PJ_SUCCESS; +} + + +/* WMME capture and playback thread. */ +static int PJ_THREAD_FUNC wmme_dev_thread(void *arg) +{ + struct wmme_stream *strm = (struct wmme_stream*)arg; + HANDLE events[3]; + unsigned eventCount; + pj_status_t status = PJ_SUCCESS; + static unsigned rec_cnt, play_cnt; + enum { MAX_BURST = 1000 }; + + rec_cnt = play_cnt = 0; + + eventCount = 0; + events[eventCount++] = strm->thread_quit_event; + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) + events[eventCount++] = strm->play_strm.hEvent; + if (strm->param.dir & PJMEDIA_DIR_CAPTURE) + events[eventCount++] = strm->rec_strm.hEvent; + + + /* Raise self priority. We don't want the audio to be distorted by + * system activity. + */ +#if defined(PJ_WIN32_WINCE) && PJ_WIN32_WINCE != 0 + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) + CeSetThreadPriority(GetCurrentThread(), 153); + else + CeSetThreadPriority(GetCurrentThread(), 247); +#else + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); +#endif + + /* + * Loop while not signalled to quit, wait for event objects to be + * signalled by WMME capture and play buffer. + */ + while (status == PJ_SUCCESS) + { + + DWORD rc; + pjmedia_dir signalled_dir; + + /* Swap hWaveIn and hWaveOut to get equal opportunity for both */ + if (eventCount==3) { + HANDLE hTemp = events[2]; + events[2] = events[1]; + events[1] = hTemp; + } + + rc = WaitForMultipleObjects(eventCount, events, FALSE, INFINITE); + if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + eventCount) + continue; + + if (rc == WAIT_OBJECT_0) + break; + + if (rc == (WAIT_OBJECT_0 + 1)) + { + if (events[1] == strm->play_strm.hEvent) + signalled_dir = PJMEDIA_DIR_PLAYBACK; + else + signalled_dir = PJMEDIA_DIR_CAPTURE; + } + else + { + if (events[2] == strm->play_strm.hEvent) + signalled_dir = PJMEDIA_DIR_PLAYBACK; + else + signalled_dir = PJMEDIA_DIR_CAPTURE; + } + + + if (signalled_dir == PJMEDIA_DIR_PLAYBACK) + { + struct wmme_channel *wmme_strm = &strm->play_strm; + unsigned burst; + + status = PJ_SUCCESS; + + /* + * Windows Multimedia has requested us to feed some frames to + * playback buffer. + */ + + for (burst=0; burstWaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE); + ++burst) + { + void *buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData; + pjmedia_frame pcm_frame, *frame; + MMRESULT mr = MMSYSERR_NOERROR; + + //PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d", + // wmme_strm->dwBufIdx)); + + if (strm->fmt_id == PJMEDIA_FORMAT_L16) { + /* PCM mode */ + frame = &pcm_frame; + + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + frame->size = strm->bytes_per_frame; + frame->buf = buffer; + frame->timestamp.u64 = wmme_strm->timestamp.u64; + frame->bit_info = 0; + } else { + /* Codec mode */ + frame = &strm->xfrm->base; + + strm->xfrm->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + strm->xfrm->base.size = strm->bytes_per_frame; + strm->xfrm->base.buf = NULL; + strm->xfrm->base.timestamp.u64 = wmme_strm->timestamp.u64; + strm->xfrm->base.bit_info = 0; + } + + /* Get frame from application. */ + //PJ_LOG(5,(THIS_FILE, "xxx %u play_cb", play_cnt++)); + status = (*strm->play_cb)(strm->user_data, frame); + + if (status != PJ_SUCCESS) + break; + + if (strm->fmt_id == PJMEDIA_FORMAT_L16) { + /* PCM mode */ + if (frame->type == PJMEDIA_FRAME_TYPE_NONE) { + pj_bzero(buffer, strm->bytes_per_frame); + } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pj_assert(!"Frame type not supported"); + } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { + /* Nothing to do */ + } else { + pj_assert(!"Frame type not supported"); + } + } else { + /* Codec mode */ + if (frame->type == PJMEDIA_FRAME_TYPE_NONE) { + pj_memset(buffer, strm->silence_char, + strm->bytes_per_frame); + } else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) { + unsigned sz; + sz = pjmedia_frame_ext_copy_payload(strm->xfrm, + buffer, + strm->bytes_per_frame); + if (sz < strm->bytes_per_frame) { + pj_memset((char*)buffer+sz, + strm->silence_char, + strm->bytes_per_frame - sz); + } + } else { + pj_assert(!"Frame type not supported"); + } + } + + /* Write to the device. */ + mr = waveOutWrite(wmme_strm->hWave.Out, + &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), + sizeof(WAVEHDR)); + if (mr != MMSYSERR_NOERROR) { + status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + break; + } + + /* Increment position. */ + if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx) + wmme_strm->dwBufIdx = 0; + wmme_strm->timestamp.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + } /* for */ + } + else + { + struct wmme_channel *wmme_strm = &strm->rec_strm; + unsigned burst; + MMRESULT mr = MMSYSERR_NOERROR; + status = PJ_SUCCESS; + + /* + * Windows Multimedia has indicated that it has some frames ready + * in the capture buffer. Get as much frames as possible to + * prevent overflows. + */ +#if 0 + { + static DWORD tc = 0; + DWORD now = GetTickCount(); + DWORD i = 0; + DWORD bits = 0; + + if (tc == 0) tc = now; + + for (i = 0; i < wmme_strm->dwMaxBufIdx; ++i) + { + bits = bits << 4; + bits |= wmme_strm->WaveHdr[i].dwFlags & WHDR_DONE; + } + PJ_LOG(5,(THIS_FILE, "Record Signal> Index: %d, Delta: %4.4d, " + "Flags: %6.6x\n", + wmme_strm->dwBufIdx, + now - tc, + bits)); + tc = now; + } +#endif + + for (burst=0; burstWaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE); + ++burst) + { + char* buffer = (char*) + wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData; + unsigned cap_len = + wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded; + pjmedia_frame pcm_frame, *frame; + + /* + PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len, + wmme_strm->dwBufIdx)); + */ + + if (strm->fmt_id == PJMEDIA_FORMAT_L16) { + /* PCM mode */ + if (cap_len < strm->bytes_per_frame) + pj_bzero(buffer + cap_len, + strm->bytes_per_frame - cap_len); + + /* Copy the audio data out of the wave buffer. */ + pj_memcpy(strm->buffer, buffer, strm->bytes_per_frame); + + /* Prepare frame */ + frame = &pcm_frame; + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + frame->buf = strm->buffer; + frame->size = strm->bytes_per_frame; + frame->timestamp.u64 = wmme_strm->timestamp.u64; + frame->bit_info = 0; + + } else { + /* Codec mode */ + frame = &strm->xfrm->base; + + frame->type = PJMEDIA_FRAME_TYPE_EXTENDED; + frame->buf = NULL; + frame->size = strm->bytes_per_frame; + frame->timestamp.u64 = wmme_strm->timestamp.u64; + frame->bit_info = 0; + + strm->xfrm->samples_cnt = 0; + strm->xfrm->subframe_cnt = 0; + pjmedia_frame_ext_append_subframe( + strm->xfrm, buffer, + strm->bytes_per_frame *8, + strm->param.samples_per_frame + ); + } + + /* Re-add the buffer to the device. */ + mr = waveInAddBuffer(wmme_strm->hWave.In, + &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), + sizeof(WAVEHDR)); + if (mr != MMSYSERR_NOERROR) { + status = PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + break; + } + + + /* Call callback */ + //PJ_LOG(5,(THIS_FILE, "xxx %u rec_cb", rec_cnt++)); + status = (*strm->rec_cb)(strm->user_data, frame); + if (status != PJ_SUCCESS) + break; + + /* Increment position. */ + if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx) + wmme_strm->dwBufIdx = 0; + wmme_strm->timestamp.u64 += strm->param.samples_per_frame / + strm->param.channel_count; + } /* for */ + } + } + + PJ_LOG(5,(THIS_FILE, "WMME: thread stopping..")); + return 0; +} + + +/* API: create stream */ +static pj_status_t 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_aud_strm) +{ + struct wmme_factory *wf = (struct wmme_factory*)f; + pj_pool_t *pool; + struct wmme_stream *strm; + pj_uint8_t silence_char; + pj_status_t status; + + switch (param->ext_fmt.id) { + case PJMEDIA_FORMAT_L16: + silence_char = '\0'; + break; + case PJMEDIA_FORMAT_ALAW: + silence_char = (pj_uint8_t)'\xd5'; + break; + case PJMEDIA_FORMAT_ULAW: + silence_char = (pj_uint8_t)'\xff'; + break; + default: + return PJMEDIA_EAUD_BADFORMAT; + } + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(wf->pf, "wmme-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct wmme_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + strm->rec_cb = rec_cb; + strm->play_cb = play_cb; + strm->user_data = user_data; + strm->fmt_id = param->ext_fmt.id; + strm->silence_char = silence_char; + + /* Create player stream */ + if (param->dir & PJMEDIA_DIR_PLAYBACK) { + unsigned buf_count; + + if ((param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY)==0) { + strm->param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + strm->param.output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + } + + buf_count = strm->param.output_latency_ms * param->clock_rate * + param->channel_count / param->samples_per_frame / 1000; + + status = init_player_stream(wf, strm->pool, + strm, + &strm->play_strm, + param, + buf_count); + + if (status != PJ_SUCCESS) { + stream_destroy(&strm->base); + return status; + } + } + + /* Create capture stream */ + if (param->dir & PJMEDIA_DIR_CAPTURE) { + unsigned buf_count; + + if ((param->flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY)==0) { + strm->param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + strm->param.input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; + } + + buf_count = strm->param.input_latency_ms * param->clock_rate * + param->channel_count / param->samples_per_frame / 1000; + + status = init_capture_stream(wf, strm->pool, + strm, + &strm->rec_strm, + param, + buf_count); + + if (status != PJ_SUCCESS) { + stream_destroy(&strm->base); + return status; + } + } + + strm->buffer = pj_pool_alloc(pool, strm->bytes_per_frame); + if (!strm->buffer) { + pj_pool_release(pool); + return PJ_ENOMEM; + } + + /* If format is extended, must create buffer for the extended frame. */ + if (strm->fmt_id != PJMEDIA_FORMAT_L16) { + strm->xfrm_size = sizeof(pjmedia_frame_ext) + + 32 * sizeof(pjmedia_frame_ext_subframe) + + strm->bytes_per_frame + 4; + strm->xfrm = (pjmedia_frame_ext*) + pj_pool_alloc(pool, strm->xfrm_size); + } + + /* Create the stop event */ + strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (strm->thread_quit_event == NULL) { + status = pj_get_os_error(); + stream_destroy(&strm->base); + return status; + } + + /* Create and start the thread */ + status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0, + &strm->thread); + if (status != PJ_SUCCESS) { + stream_destroy(&strm->base); + return status; + } + + /* Apply the remaining settings */ + if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { + stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + ¶m->output_vol); + } + + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct wmme_stream *strm = (struct wmme_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Update the volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct wmme_stream *strm = (struct wmme_stream*)s; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) + { + /* Recording latency */ + *(unsigned*)pval = strm->param.input_latency_ms; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + /* Playback latency */ + *(unsigned*)pval = strm->param.output_latency_ms; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + strm->play_strm.hWave.Out) + { + /* Output volume setting */ + DWORD waveVol; + MMRESULT mr; + + mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + + waveVol &= 0xFFFF; + *(unsigned*)pval = (waveVol * 100) / 0xFFFF; + return PJ_SUCCESS; + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct wmme_stream *strm = (struct wmme_stream*)s; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + strm->play_strm.hWave.Out) + { + /* Output volume setting */ + unsigned vol = *(unsigned*)pval; + DWORD waveVol; + MMRESULT mr; + pj_status_t status; + + if (vol > 100) + vol = 100; + + waveVol = (vol * 0xFFFF) / 100; + waveVol |= (waveVol << 16); + + mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol); + status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS : + PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + if (status == PJ_SUCCESS) { + strm->param.output_vol = *(unsigned*)pval; + } + return status; + } + + return PJMEDIA_EAUD_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *strm) +{ + struct wmme_stream *stream = (struct wmme_stream*)strm; + MMRESULT mr; + + if (stream->play_strm.hWave.Out != NULL) + { + mr = waveOutRestart(stream->play_strm.hWave.Out); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + PJ_LOG(4,(THIS_FILE, "WMME playback stream started")); + } + + if (stream->rec_strm.hWave.In != NULL) + { + mr = waveInStart(stream->rec_strm.hWave.In); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + PJ_LOG(4,(THIS_FILE, "WMME capture stream started")); + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t stream_stop(pjmedia_aud_stream *strm) +{ + struct wmme_stream *stream = (struct wmme_stream*)strm; + MMRESULT mr; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + if (stream->play_strm.hWave.Out != NULL) + { + mr = waveOutPause(stream->play_strm.hWave.Out); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream")); + } + + if (stream->rec_strm.hWave.In != NULL) + { + mr = waveInStop(stream->rec_strm.hWave.In); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream")); + } + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *strm) +{ + struct wmme_stream *stream = (struct wmme_stream*)strm; + unsigned i; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + stream_stop(strm); + + /* Stop the stream thread */ + if (stream->thread) + { + SetEvent(stream->thread_quit_event); + pj_thread_join(stream->thread); + pj_thread_destroy(stream->thread); + stream->thread = NULL; + } + + /* Close the thread quit event */ + if (stream->thread_quit_event) + { + CloseHandle(stream->thread_quit_event); + stream->thread_quit_event = NULL; + } + + /* Unprepare the headers and close the play device */ + if (stream->play_strm.hWave.Out) + { + waveOutReset(stream->play_strm.hWave.Out); + for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i) + waveOutUnprepareHeader(stream->play_strm.hWave.Out, + &(stream->play_strm.WaveHdr[i]), + sizeof(WAVEHDR)); + waveOutClose(stream->play_strm.hWave.Out); + stream->play_strm.hWave.Out = NULL; + } + + /* Close the play event */ + if (stream->play_strm.hEvent) + { + CloseHandle(stream->play_strm.hEvent); + stream->play_strm.hEvent = NULL; + } + + /* Unprepare the headers and close the record device */ + if (stream->rec_strm.hWave.In) + { + waveInReset(stream->rec_strm.hWave.In); + for (i = 0; i < stream->play_strm.dwMaxBufIdx; ++i) + waveInUnprepareHeader(stream->rec_strm.hWave.In, + &(stream->rec_strm.WaveHdr[i]), + sizeof(WAVEHDR)); + waveInClose(stream->rec_strm.hWave.In); + stream->rec_strm.hWave.In = NULL; + } + + /* Close the record event */ + if (stream->rec_strm.hEvent) + { + CloseHandle(stream->rec_strm.hEvent); + stream->rec_strm.hEvent = NULL; + } + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_AUDIO_DEV_HAS_WMME */ + -- cgit v1.2.3