summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia-audiodev
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
committerDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
commitf3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch)
treed00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjmedia/src/pjmedia-audiodev
Import pjproject-2.0.1
Diffstat (limited to 'pjmedia/src/pjmedia-audiodev')
-rw-r--r--pjmedia/src/pjmedia-audiodev/alsa_dev.c980
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiodev.c822
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiotest.c269
-rw-r--r--pjmedia/src/pjmedia-audiodev/bb10_dev.c965
-rw-r--r--pjmedia/src/pjmedia-audiodev/coreaudio_dev.c2107
-rw-r--r--pjmedia/src/pjmedia-audiodev/errno.c206
-rw-r--r--pjmedia/src/pjmedia-audiodev/legacy_dev.c468
-rw-r--r--pjmedia/src/pjmedia-audiodev/null_dev.c388
-rw-r--r--pjmedia/src/pjmedia-audiodev/pa_dev.c1284
-rw-r--r--pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h171
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp1929
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp1196
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp2006
-rw-r--r--pjmedia/src/pjmedia-audiodev/wmme_dev.c1524
14 files changed, 14315 insertions, 0 deletions
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: <dan.aberg@keystream.se>
+ *
+ * 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 <pjmedia_audiodev.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pjmedia/errno.h>
+
+#if defined(PJMEDIA_AUDIO_DEV_HAS_ALSA) && PJMEDIA_AUDIO_DEV_HAS_ALSA
+
+#include <sys/syscall.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <pthread.h>
+#include <errno.h>
+#include <alsa/asoundlib.h>
+
+
+#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 && index<af->dev_cnt, PJ_EINVAL);
+
+ pj_memcpy(info, &af->devs[index], sizeof(*info));
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+ 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 && index<af->dev_cnt, PJ_EINVAL);
+
+ adi = &af->devs[index];
+
+ pj_bzero(param, sizeof(*param));
+ if (adi->input_count && adi->output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (adi->input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (adi->output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = adi->default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = adi->caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ 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, &param);
+ 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 (&params);
+
+ /* 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 (&params);
+
+ /* 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 <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#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; i<PJ_ARRAY_SIZE(cap_infos); ++i) {
+ if ((1 << i)==cap)
+ break;
+ }
+
+ if (i==PJ_ARRAY_SIZE(cap_infos)) {
+ *p_desc = "??";
+ return "??";
+ }
+
+ *p_desc = cap_infos[i].info;
+ return cap_infos[i].name;
+}
+
+static pj_status_t get_cap_pointer(const pjmedia_aud_param *param,
+ pjmedia_aud_dev_cap cap,
+ void **ptr,
+ unsigned *size)
+{
+#define FIELD_INFO(name) *ptr = (void*)&param->name; \
+ *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; i<dev_cnt; ++i) {
+ pjmedia_aud_dev_info info;
+
+ status = f->op->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; i<dev_cnt; ++i) {
+ aud_subsys.dev_list[aud_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/* Internal: deinit driver */
+static void deinit_driver(unsigned drv_idx)
+{
+ struct driver *drv = &aud_subsys.drv[drv_idx];
+
+ if (drv->f) {
+ 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; i<aud_subsys.drv_cnt; ++i) {
+ status = init_driver(i, PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ deinit_driver(i);
+ continue;
+ }
+ }
+
+ return aud_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* API: register an audio device factory to the audio subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_aud_register_factory(pjmedia_aud_dev_factory_create_func_ptr adf)
+{
+ pj_status_t status;
+
+ if (aud_subsys.init_count == 0)
+ return PJMEDIA_EAUD_INIT;
+
+ aud_subsys.drv[aud_subsys.drv_cnt].create = adf;
+ status = init_driver(aud_subsys.drv_cnt, PJ_FALSE);
+ if (status == PJ_SUCCESS) {
+ aud_subsys.drv_cnt++;
+ } else {
+ deinit_driver(aud_subsys.drv_cnt);
+ }
+
+ return status;
+}
+
+/* API: unregister an audio device factory from the audio subsystem. */
+PJ_DEF(pj_status_t)
+pjmedia_aud_unregister_factory(pjmedia_aud_dev_factory_create_func_ptr adf)
+{
+ unsigned i, j;
+
+ if (aud_subsys.init_count == 0)
+ return PJMEDIA_EAUD_INIT;
+
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+
+ if (drv->create == 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; i<aud_subsys.drv_cnt; ++i) {
+ deinit_driver(i);
+ }
+
+ aud_subsys.pf = NULL;
+ }
+ return PJ_SUCCESS;
+}
+
+/* API: Refresh the list of sound devices installed in the system. */
+PJ_DEF(pj_status_t) pjmedia_aud_dev_refresh(void)
+{
+ unsigned i;
+
+ aud_subsys.dev_cnt = 0;
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+
+ if (drv->f && 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; i<aud_subsys.drv_cnt; ++i) {
+ struct driver *drv = &aud_subsys.drv[i];
+ if (drv->dev_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_idx<aud_subsys.drv_cnt; ++drv_idx) {
+ if (!pj_ansi_stricmp(drv_name, aud_subsys.drv[drv_idx].name)) {
+ f = aud_subsys.drv[drv_idx].f;
+ break;
+ }
+ }
+
+ if (!f)
+ return PJ_ENOTFOUND;
+
+ for (dev_idx=0; dev_idx<aud_subsys.drv[drv_idx].dev_cnt; ++dev_idx) {
+ pjmedia_aud_dev_info info;
+ pj_status_t status;
+
+ status = f->op->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, &param->rec_id);
+ make_global_index(f->sys.drv_idx, &param->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(&param, 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, &param, 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, &param->rec_id);
+ make_global_index(strm->sys.drv_idx, &param->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 <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiotest.h>
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjlib.h>
+#include <pjlib-util.h>
+
+#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 <pjmedia_audiodev.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pjmedia/errno.h>
+
+#if defined(PJMEDIA_AUDIO_DEV_HAS_BB10) && PJMEDIA_AUDIO_DEV_HAS_BB10 != 0
+
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <pthread.h>
+#include <errno.h>
+#include <sys/asoundlib.h>
+
+
+#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 && index<af->dev_cnt, PJ_EINVAL);
+
+ pj_memcpy(info, &af->devs[index], sizeof(*info));
+ info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY |
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY;
+
+ return PJ_SUCCESS;
+}
+
+/* API: create default parameter */
+static pj_status_t bb10_factory_default_param(pjmedia_aud_dev_factory *f,
+ unsigned index,
+ pjmedia_aud_param *param)
+{
+ struct bb10_factory *af = (struct bb10_factory*)f;
+ pjmedia_aud_dev_info *adi;
+
+ PJ_ASSERT_RETURN(index>=0 && index<af->dev_cnt, PJ_EINVAL);
+
+ adi = &af->devs[index];
+
+ pj_bzero(param, sizeof(*param));
+ if (adi->input_count && adi->output_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK;
+ param->rec_id = index;
+ param->play_id = index;
+ } else if (adi->input_count) {
+ param->dir = PJMEDIA_DIR_CAPTURE;
+ param->rec_id = index;
+ param->play_id = PJMEDIA_AUD_INVALID_DEV;
+ } else if (adi->output_count) {
+ param->dir = PJMEDIA_DIR_PLAYBACK;
+ param->play_id = index;
+ param->rec_id = PJMEDIA_AUD_INVALID_DEV;
+ } else {
+ return PJMEDIA_EAUD_INVDEV;
+ }
+
+ param->clock_rate = adi->default_samples_per_sec;
+ param->channel_count = 1;
+ param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000;
+ param->bits_per_sample = 16;
+ param->flags = adi->caps;
+ param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY;
+ param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY;
+
+ TRACE_((THIS_FILE, "bb10_factory_default_param clock = %d flags = %d"
+ " spf = %d", param->clock_rate, param->flags,
+ param->samples_per_frame));
+
+ return PJ_SUCCESS;
+}
+
+
+static void close_play_pcm(struct bb10_stream *stream)
+{
+ if (stream != NULL && stream->pb_pcm != NULL) {
+ snd_pcm_close(stream->pb_pcm);
+ stream->pb_pcm = NULL;
+ }
+}
+
+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, &param);
+ 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO
+
+#include "TargetConditionals.h"
+#if TARGET_OS_IPHONE
+ #define COREAUDIO_MAC 0
+#else
+ #define COREAUDIO_MAC 1
+#endif
+
+#include <AudioUnit/AudioUnit.h>
+#include <AudioToolbox/AudioConverter.h>
+#if !COREAUDIO_MAC
+ #include <AudioToolbox/AudioServices.h>
+
+ #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,
+ &param->input_route);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE,
+ &param->output_route);
+ }
+ if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) {
+ ca_stream_set_cap(&strm->base,
+ PJMEDIA_AUD_DEV_CAP_EC,
+ &param->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,
+ &param->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 <pjmedia-audiodev/errno.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+# include <portaudio.h>
+#endif
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+# ifdef _MSC_VER
+# pragma warning(push, 3)
+# endif
+# include <windows.h>
+# include <mmsystem.h>
+# 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia/sound.h>
+#include <pj/assert.h>
+
+#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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+
+#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,
+ &param->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 <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+
+#include <portaudio.h>
+
+#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; i<count; ++i) {
+ const PaDeviceInfo *paDevInfo;
+
+ paDevInfo = Pa_GetDeviceInfo(i);
+ if (paDevInfo->maxInputChannels >= 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; i<count; ++i) {
+ const PaDeviceInfo *paDevInfo;
+
+ paDevInfo = Pa_GetDeviceInfo(i);
+ if (paDevInfo->maxOutputChannels >= 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<TUint8*>(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<TUint8*>(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 <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/resample.h>
+#include <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_APS
+
+#include <e32msgqueue.h>
+#include <sounddevice.h>
+#include <APSClientSession.h>
+#include <pjmedia-codec/amr_helper.h>
+
+/* 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<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* 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<TAPSCommBuffer>* aQ,
+ RMsgQueue<TAPSCommBuffer>* 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<TAPSCommBuffer> *iQ; // (not owned)
+ RMsgQueue<TAPSCommBuffer> *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<TAPSCommBuffer> iReadQ;
+ RMsgQueue<TAPSCommBuffer> iReadCommQ;
+ TBool readq_opened;
+ RMsgQueue<TAPSCommBuffer> iWriteQ;
+ RMsgQueue<TAPSCommBuffer> 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,
+ &param->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 <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#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 <mda/common/audio.h>
+#include <mdaaudiooutputstream.h>
+#include <mdaaudioinputstream.h>
+
+
+#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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pjmedia-audiodev/errno.h>
+#include <pjmedia/alaw_ulaw.h>
+#include <pjmedia/resample.h>
+#include <pjmedia/stereo.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/os.h>
+#include <pj/string.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS
+
+/* VAS headers */
+#include <VoIPUtilityFactory.h>
+#include <VoIPDownlinkStream.h>
+#include <VoIPUplinkStream.h>
+#include <VoIPFormatIntfc.h>
+#include <VoIPG711DecoderIntfc.h>
+#include <VoIPG711EncoderIntfc.h>
+#include <VoIPG729DecoderIntfc.h>
+#include <VoIPILBCDecoderIntfc.h>
+#include <VoIPILBCEncoderIntfc.h>
+
+/* AMR helper */
+#include <pjmedia-codec/amr_helper.h>
+
+/* 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<TVoIPCodecFormat> 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 <benny@prijono.org>
+ *
+ * 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 <pjmedia-audiodev/audiodev_imp.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/unicode.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_WMME
+
+#ifdef _MSC_VER
+# pragma warning(push, 3)
+#endif
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <mmreg.h>
+
+#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 <mmdeviceapi.h>
+#define INITGUID
+#include <Guiddef.h>
+#include <FunctionDiscoveryKeys_devpkey.h>
+
+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; burst<MAX_BURST &&
+ (wmme_strm->WaveHdr[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; burst<MAX_BURST &&
+ (wmme_strm->WaveHdr[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,
+ &param->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 */
+