diff options
author | Benny Prijono <bennylp@teluu.com> | 2009-03-12 18:11:37 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2009-03-12 18:11:37 +0000 |
commit | 1dacdee696b7591a6dcc0b3c1d0f41573e473168 (patch) | |
tree | 302b09dcd989c0c05cf09f6aebaa63d870b421b9 /pjmedia/src/pjmedia-audiodev | |
parent | ba9d8ca28eb209571c0bd6a080a8bb03d0fa2d33 (diff) |
(Major) Task #737 and #738: integration of APS-Direct and Audiodev from aps-direct branch to trunk.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2506 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src/pjmedia-audiodev')
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/audiodev.c | 697 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/audiotest.c | 269 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/errno.c | 190 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/legacy_dev.c | 459 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/pa_dev.c | 1263 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/s60_g729_bitstream.h | 171 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp | 1611 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp | 1110 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-audiodev/wmme_dev.c | 1311 |
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*)¶m->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, ¶m->rec_id); + make_global_index(f->sys.drv_idx, ¶m->play_id); + + return PJ_SUCCESS; +} + +/* API: Open audio stream object using the specified parameters. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_create(const pjmedia_aud_param *prm, + pjmedia_aud_rec_cb rec_cb, + pjmedia_aud_play_cb play_cb, + void *user_data, + pjmedia_aud_stream **p_aud_strm) +{ + pjmedia_aud_dev_factory *rec_f=NULL, *play_f=NULL, *f=NULL; + pjmedia_aud_param param; + pj_status_t status; + + PJ_ASSERT_RETURN(prm && prm->dir && p_aud_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + + /* Must make copy of param because we're changing device ID */ + pj_memcpy(¶m, prm, sizeof(param)); + + /* Normalize rec_id */ + if (param.dir & PJMEDIA_DIR_CAPTURE) { + unsigned index; + + if (param.rec_id < 0) + param.rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; + + status = lookup_dev(param.rec_id, &rec_f, &index); + if (status != PJ_SUCCESS) + return status; + + param.rec_id = index; + f = rec_f; + } + + /* Normalize play_id */ + if (param.dir & PJMEDIA_DIR_PLAYBACK) { + unsigned index; + + if (param.play_id < 0) + param.play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; + + status = lookup_dev(param.play_id, &play_f, &index); + if (status != PJ_SUCCESS) + return status; + + param.play_id = index; + f = play_f; + + /* 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, ¶m, rec_cb, play_cb, + user_data, p_aud_strm); + if (status != PJ_SUCCESS) + return status; + + /* Assign factory id to the stream */ + (*p_aud_strm)->sys.drv_idx = f->sys.drv_idx; + return PJ_SUCCESS; +} + +/* API: Get the running parameters for the specified audio stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(strm && param, PJ_EINVAL); + PJ_ASSERT_RETURN(aud_subsys.pf, PJMEDIA_EAUD_INIT); + + status = strm->op->get_param(strm, param); + if (status != PJ_SUCCESS) + return status; + + /* Normalize device id's */ + make_global_index(strm->sys.drv_idx, ¶m->rec_id); + make_global_index(strm->sys.drv_idx, ¶m->play_id); + + return PJ_SUCCESS; +} + +/* API: Get the value of a specific capability of the audio stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_get_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + void *value) +{ + return strm->op->get_cap(strm, cap, value); +} + +/* API: Set the value of a specific capability of the audio stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_set_cap(pjmedia_aud_stream *strm, + pjmedia_aud_dev_cap cap, + const void *value) +{ + return strm->op->set_cap(strm, cap, value); +} + +/* API: Start the stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_start(pjmedia_aud_stream *strm) +{ + return strm->op->start(strm); +} + +/* API: Stop the stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_stop(pjmedia_aud_stream *strm) +{ + return strm->op->stop(strm); +} + +/* API: Destroy the stream. */ +PJ_DEF(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm) +{ + return strm->op->destroy(strm); +} + + diff --git a/pjmedia/src/pjmedia-audiodev/audiotest.c b/pjmedia/src/pjmedia-audiodev/audiotest.c new file mode 100644 index 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, + ¶m->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, + ¶m->output_vol); + } + + + /* Done */ + strm->base.op = &stream_op; + *p_aud_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +{ + struct wmme_stream *strm = (struct wmme_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + /* Update the volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t stream_get_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + void *pval) +{ + struct wmme_stream *strm = (struct wmme_stream*)s; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_CAPTURE)) + { + /* Recording latency */ + *(unsigned*)pval = strm->param.input_latency_ms; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) + { + /* Playback latency */ + *(unsigned*)pval = strm->param.output_latency_ms; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + strm->play_strm.hWave.Out) + { + /* Output volume setting */ + DWORD waveVol; + MMRESULT mr; + + mr = waveOutGetVolume(strm->play_strm.hWave.Out, &waveVol); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + + waveVol &= 0xFFFF; + *(unsigned*)pval = (waveVol * 100) / 0xFFFF; + return PJ_SUCCESS; + } else { + return PJMEDIA_EAUD_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t stream_set_cap(pjmedia_aud_stream *s, + pjmedia_aud_dev_cap cap, + const void *pval) +{ + struct wmme_stream *strm = (struct wmme_stream*)s; + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + strm->play_strm.hWave.Out) + { + /* Output volume setting */ + unsigned vol = *(unsigned*)pval; + DWORD waveVol; + MMRESULT mr; + pj_status_t status; + + if (vol > 100) + vol = 100; + + waveVol = (vol * 0xFFFF) / 100; + waveVol |= (waveVol << 16); + + mr = waveOutSetVolume(strm->play_strm.hWave.Out, waveVol); + status = (mr==MMSYSERR_NOERROR)? PJ_SUCCESS : + PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + if (status == PJ_SUCCESS) { + strm->param.output_vol = *(unsigned*)pval; + } + return status; + } + + return PJMEDIA_EAUD_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t stream_start(pjmedia_aud_stream *strm) +{ + struct wmme_stream *stream = (struct wmme_stream*)strm; + MMRESULT mr; + + if (stream->play_strm.hWave.Out != NULL) + { + mr = waveOutRestart(stream->play_strm.hWave.Out); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + PJ_LOG(4,(THIS_FILE, "WMME playback stream started")); + } + + if (stream->rec_strm.hWave.In != NULL) + { + mr = waveInStart(stream->rec_strm.hWave.In); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + PJ_LOG(4,(THIS_FILE, "WMME capture stream started")); + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t stream_stop(pjmedia_aud_stream *strm) +{ + struct wmme_stream *stream = (struct wmme_stream*)strm; + MMRESULT mr; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + if (stream->play_strm.hWave.Out != NULL) + { + mr = waveOutPause(stream->play_strm.hWave.Out); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_OUT(mr); + } + PJ_LOG(4,(THIS_FILE, "Stopped WMME playback stream")); + } + + if (stream->rec_strm.hWave.In != NULL) + { + mr = waveInStop(stream->rec_strm.hWave.In); + if (mr != MMSYSERR_NOERROR) { + return PJMEDIA_AUDIODEV_ERRNO_FROM_WMME_IN(mr); + } + PJ_LOG(4,(THIS_FILE, "Stopped WMME capture stream")); + } + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t stream_destroy(pjmedia_aud_stream *strm) +{ + struct wmme_stream *stream = (struct wmme_stream*)strm; + unsigned i; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + stream_stop(strm); + + 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 */ + |