summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia-audiodev
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia-audiodev')
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiodev.c697
-rw-r--r--pjmedia/src/pjmedia-audiodev/audiotest.c269
-rw-r--r--pjmedia/src/pjmedia-audiodev/errno.c190
-rw-r--r--pjmedia/src/pjmedia-audiodev/legacy_dev.c459
-rw-r--r--pjmedia/src/pjmedia-audiodev/pa_dev.c1263
-rw-r--r--pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h171
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp1611
-rw-r--r--pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp1110
-rw-r--r--pjmedia/src/pjmedia-audiodev/wmme_dev.c1311
9 files changed, 7081 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c
new file mode 100644
index 00000000..956b5a97
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiodev.c
@@ -0,0 +1,697 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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_WMME
+pjmedia_aud_dev_factory* pjmedia_wmme_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
+
+#define MAX_DRIVERS 16
+#define MAX_DEVS 64
+
+/* typedef for factory creation function */
+typedef pjmedia_aud_dev_factory* (*create_func_ptr)(pj_pool_factory*);
+
+/* driver structure */
+struct driver
+{
+ create_func_ptr create; /* Creation function. */
+ pjmedia_aud_dev_factory *f; /* Factory instance. */
+ 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==32) {
+ *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;
+ 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)
+{
+ struct driver *drv = &aud_subsys.drv[drv_idx];
+ pjmedia_aud_dev_factory *f;
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ /* 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;
+ }
+
+ /* 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;
+ }
+ 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 = PJ_ENOMEM;
+
+ /* 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 */
+ pj_register_strerror(PJMEDIA_AUDIODEV_ERRNO_START,
+ PJ_ERRNO_SPACE_SIZE,
+ &pjmedia_audiodev_strerror);
+
+ /* Init */
+ aud_subsys.pf = pf;
+ aud_subsys.drv_cnt = 0;
+ aud_subsys.dev_cnt = 0;
+
+ /* Register creation functions */
+#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_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
+
+ /* Initialize each factory and build the device ID list */
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ status = init_driver(i);
+ if (status != PJ_SUCCESS) {
+ deinit_driver(i);
+ continue;
+ }
+ }
+
+ return aud_subsys.dev_cnt ? PJ_SUCCESS : status;
+}
+
+/* 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;
+
+ for (i=0; i<aud_subsys.drv_cnt; ++i) {
+ deinit_driver(i);
+ }
+
+ aud_subsys.pf = NULL;
+ 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);
+
+ /* 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;
+
+ /* For now, rec_id and play_id must belong to the same factory */
+ PJ_ASSERT_RETURN(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 00000000..bf0ac1f2
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/audiotest.c
@@ -0,0 +1,269 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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.capture_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 end_diff, start_diff, drift;
+
+ end_diff = test_data.capture_data.last_timestamp -
+ test_data.playback_data.last_timestamp;
+ start_diff = test_data.capture_data.first_timestamp-
+ test_data.playback_data.first_timestamp;
+ drift = end_diff > start_diff? end_diff - start_diff :
+ start_diff - end_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/errno.c b/pjmedia/src/pjmedia-audiodev/errno.c
new file mode 100644
index 00000000..28522490
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/errno.c
@@ -0,0 +1,190 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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 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 00000000..66fad9b0
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/legacy_dev.c
@@ -0,0 +1,459 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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 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
+};
+
+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: 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/pa_dev.c b/pjmedia/src/pjmedia-audiodev/pa_dev.c
new file mode 100644
index 00000000..5644e1f1
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/pa_dev.c
@@ -0,0 +1,1263 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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 <portaudio.h>
+
+#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO
+
+
+#define THIS_FILE "pa_dev.c"
+#define DRIVER_NAME "PA"
+
+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 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
+};
+
+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())
+ {
+ 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())
+ {
+ 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;
+}
+
+/* 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);
+
+
+/*
+ * 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);
+
+ PaUtil_SetDebugPrintFunction(&pa_log_cb);
+
+ 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: 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 00000000..ae13bb11
--- /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 00000000..bfdec1e5
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp
@@ -0,0 +1,1611 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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_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. */
+};
+
+
+/* Prototypes */
+static pj_status_t factory_init(pjmedia_aud_dev_factory *f);
+static pj_status_t factory_destroy(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
+};
+
+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));
+}
+
+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();
+
+ // Inherited from MQueueHandlerObserver
+ virtual void InputStreamInitialized(const TInt aStatus);
+ virtual void OutputStreamInitialized(const TInt aStatus);
+ virtual void NotifyError(const TInt aError);
+
+ State state_;
+ struct aps_stream *parentStrm_;
+ CPjAudioSetting setting_;
+
+ RAPSSession iSession;
+ TAPSInitSettings iPlaySettings;
+ TAPSInitSettings iRecSettings;
+
+ RMsgQueue<TAPSCommBuffer> iReadQ;
+ RMsgQueue<TAPSCommBuffer> iReadCommQ;
+ RMsgQueue<TAPSCommBuffer> iWriteQ;
+ RMsgQueue<TAPSCommBuffer> iWriteCommQ;
+
+ 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),
+ state_(STATE_NULL),
+ parentStrm_(parent_strm),
+ setting_(setting),
+ iPlayCommHandler(0),
+ iRecCommHandler(0),
+ iRecHandler(0)
+{
+}
+
+CPjAudioEngine::~CPjAudioEngine()
+{
+ Stop();
+
+ delete iRecHandler;
+ delete iPlayCommHandler;
+ delete iRecCommHandler;
+
+ // On some devices, immediate closing after stopping may cause APS server
+ // panic KERN-EXEC 0, so let's wait for sometime before really closing
+ // the client session.
+ TTime start, now;
+ enum { APS_CLOSE_WAIT_TIME = 200 }; /* in msecs */
+
+ start.UniversalTime();
+ do {
+ pj_symbianos_poll(-1, APS_CLOSE_WAIT_TIME);
+ now.UniversalTime();
+ } while (now.MicroSecondsFrom(start) < APS_CLOSE_WAIT_TIME * 1000);
+
+ iSession.Close();
+
+ if (state_ == STATE_READY) {
+ if (parentStrm_->param.dir != PJMEDIA_DIR_PLAYBACK) {
+ iReadQ.Close();
+ iReadCommQ.Close();
+ }
+ iWriteQ.Close();
+ iWriteCommQ.Close();
+ }
+
+ TRACE_((THIS_FILE, "Sound device destroyed"));
+}
+
+TInt CPjAudioEngine::InitPlayL()
+{
+ TInt err = iSession.InitializePlayer(iPlaySettings);
+ if (err != KErrNone) {
+ 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);
+
+ // 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) {
+ 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);
+
+ // 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);
+
+ // 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)
+ 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());
+}
+
+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::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;
+ }
+ }
+}
+
+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();
+ }
+}
+
+void CPjAudioEngine::NotifyError(const TInt aError)
+{
+ 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;
+}
+
+
+
+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 dec_len = 0;
+
+ while (dec_len < aps_g711_frame_len) {
+ unsigned tmp;
+
+ tmp = PJ_MIN(strm->param.samples_per_frame - strm->rec_buf_len,
+ aps_g711_frame_len - dec_len);
+ pjmedia_ulaw_decode(&strm->rec_buf[strm->rec_buf_len],
+ buf.iBuffer.Ptr() + 2 + dec_len,
+ tmp);
+ strm->rec_buf_len += tmp;
+ dec_len += tmp;
+
+ pj_assert(strm->rec_buf_len <= strm->param.samples_per_frame);
+
+ if (strm->rec_buf_len == strm->param.samples_per_frame) {
+ pjmedia_frame f;
+
+ f.type = PJMEDIA_FRAME_TYPE_AUDIO;
+ f.buf = strm->rec_buf;
+ f.size = strm->rec_buf_len << 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 enc_len = 0;
+ while (enc_len < g711_frame_len) {
+ if (strm->play_buf_len == 0) {
+ pjmedia_frame f;
+
+ 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(strm->play_buf,
+ strm->param.samples_per_frame);
+ }
+
+ strm->play_buf_len = strm->param.samples_per_frame;
+ strm->play_buf_start = 0;
+ }
+
+ unsigned tmp;
+
+ tmp = PJ_MIN(strm->play_buf_len, g711_frame_len - enc_len);
+ 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);
+ enc_len += tmp;
+ strm->play_buf_len -= tmp;
+ strm->play_buf_start += tmp;
+ }
+}
+
+/****************************************************************************
+ * Internal APS callbacks
+ */
+
+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 {
+ buf.iBuffer.Append(0);
+ }
+
+ pjmedia_frame_ext_pop_subframes(frame, 1);
+
+ } else { /* PJMEDIA_FRAME_TYPE_NONE */
+ buf.iBuffer.Append(0);
+
+ 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(0);
+ buf.iBuffer.Append(1);
+ } else {
+ buf.iBuffer.Append(1);
+ buf.iBuffer.Append(0);
+ }
+ buf.iBuffer.Append(dst);
+ } 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_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;
+
+ af->dev_info.ext_fmt_cnt = 5;
+
+ af->dev_info.ext_fmt[0].id = PJMEDIA_FORMAT_AMR;
+ af->dev_info.ext_fmt[0].bitrate = 7400;
+ af->dev_info.ext_fmt[0].vad = PJ_TRUE;
+
+ af->dev_info.ext_fmt[1].id = PJMEDIA_FORMAT_G729;
+ af->dev_info.ext_fmt[1].bitrate = 8000;
+ af->dev_info.ext_fmt[1].vad = PJ_FALSE;
+
+ af->dev_info.ext_fmt[2].id = PJMEDIA_FORMAT_ILBC;
+ af->dev_info.ext_fmt[2].bitrate = 13333;
+ af->dev_info.ext_fmt[2].vad = PJ_TRUE;
+
+ af->dev_info.ext_fmt[3].id = PJMEDIA_FORMAT_PCMU;
+ af->dev_info.ext_fmt[3].bitrate = 64000;
+ af->dev_info.ext_fmt[3].vad = PJ_FALSE;
+
+ af->dev_info.ext_fmt[4].id = PJMEDIA_FORMAT_PCMA;
+ af->dev_info.ext_fmt[4].bitrate = 64000;
+ af->dev_info.ext_fmt[4].vad = PJ_FALSE;
+
+ 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: 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);
+
+ /* 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;
+ }
+
+ /* 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);
+ }
+
+ strm->rec_cb = rec_cb;
+ strm->play_cb = play_cb;
+ strm->user_data = user_data;
+
+ /* play_buf size is samples per frame. */
+ strm->play_buf = (pj_int16_t*)pj_pool_zalloc(pool,
+ strm->param.samples_per_frame << 1);
+ strm->play_buf_len = 0;
+ strm->play_buf_start = 0;
+
+ /* rec_buf size is samples per frame. */
+ strm->rec_buf = (pj_int16_t*)pj_pool_zalloc(pool,
+ strm->param.samples_per_frame << 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;
+ }
+
+ /* 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 00000000..f9437e55
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp
@@ -0,0 +1,1110 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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 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
+};
+
+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_;
+
+ // 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 *
+ parent_strm->param.channel_count *
+ 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);
+
+ // 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;
+ }
+
+ state_ = STATE_INACTIVE;
+}
+
+
+TPtr8 & CPjAudioInputEngine::GetFrame()
+{
+ //iStreamBuffer_->Des().FillZ(frameLen_);
+ iFramePtr_.Set((TUint8*)(iStreamBuffer_->Ptr()), frameLen_, frameLen_);
+ return iFramePtr_;
+}
+
+void CPjAudioInputEngine::MaiscOpenComplete(TInt aError)
+{
+ lastError_ = aError;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscOpenComplete()", aError);
+ return;
+ }
+
+ // 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()"));
+ }
+}
+
+void CPjAudioInputEngine::MaiscBufferCopied(TInt aError,
+ const TDesC8 &aBuffer)
+{
+ lastError_ = aError;
+ if (aError != KErrNone) {
+ snd_perror("Error in MaiscBufferCopied()", aError);
+ return;
+ }
+
+ if (frameRecBufLen_ || aBuffer.Size() < frameLen_) {
+ pj_memcpy(frameRecBuf_ + frameRecBufLen_, (void*) aBuffer.Ptr(), aBuffer.Size());
+ frameRecBufLen_ += aBuffer.Size();
+ }
+
+ 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.Size();
+ 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) {
+ 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_;
+
+ 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 *
+ parentStrm_->param.channel_count *
+ 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);
+
+ // 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;
+ }
+
+ state_ = STATE_INACTIVE;
+}
+
+void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError)
+{
+ lastError_ = aError;
+
+ if (aError==KErrNone) {
+ // output stream opened succesfully, set status to Active
+ state_ = STATE_ACTIVE;
+
+ // 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);
+
+ // 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;
+ }
+
+ // 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_);
+ } 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;
+ }
+
+ // 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) {
+ 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: 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;
+ 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);
+
+ /* 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));
+
+ 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_CAPTURE) {
+ 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/wmme_dev.c b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
new file mode 100644
index 00000000..4c690eb7
--- /dev/null
+++ b/pjmedia/src/pjmedia-audiodev/wmme_dev.c
@@ -0,0 +1,1311 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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
+
+/* 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;
+};
+
+/* WMME factory */
+struct wmme_factory
+{
+ pjmedia_aud_dev_factory base;
+ 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 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
+};
+
+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", 1000, 1000, NULL);
+ f = PJ_POOL_ZALLOC_T(pool, struct wmme_factory);
+ f->pf = pf;
+ f->pool = pool;
+ f->base.op = &factory_op;
+
+ return &f->base;
+}
+
+
+/* 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;
+ wdi->info.ext_fmt[0].id = PJMEDIA_FORMAT_PCMU;
+ wdi->info.ext_fmt[0].bitrate = 64000;
+ wdi->info.ext_fmt[0].vad = 0;
+ wdi->info.ext_fmt[1].id = PJMEDIA_FORMAT_PCMA;
+ wdi->info.ext_fmt[1].bitrate = 64000;
+ wdi->info.ext_fmt[1].vad = 0;
+}
+
+/* API: init factory */
+static pj_status_t factory_init(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;
+
+ /* Enumerate sound devices */
+ wf->dev_count = 0;
+
+ 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"));
+ return PJMEDIA_EAUD_NODEV;
+ }
+
+ 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_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;
+
+ 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);
+ ++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;
+
+ 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);
+ ++wf->dev_count;
+ }
+ }
+
+ PJ_LOG(4, (THIS_FILE, "WMME initialized, 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->pool;
+
+ wf->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(PCMWAVEFORMAT));
+ 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;
+ 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;
+
+ /*
+ * Open wave device.
+ */
+ mr = waveOutOpen(&wmme_strm->hWave.Out,
+ wf->dev_info[prm->play_id].deviceId,
+ &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
+ 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;
+ 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;
+
+ /*
+ * Open wave device.
+ */
+ mr = waveInOpen(&wmme_strm->hWave.In,
+ wf->dev_info[prm->rec_id].deviceId,
+ &wfx, (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT);
+ 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 = 1 };
+
+ 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);
+
+ if (stream->thread)
+ {
+ SetEvent(stream->thread_quit_event);
+ pj_thread_join(stream->thread);
+ pj_thread_destroy(stream->thread);
+ stream->thread = 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 */
+