diff options
Diffstat (limited to 'pjmedia/src/pjmedia')
-rw-r--r-- | pjmedia/src/pjmedia/conf_switch.c | 1466 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/conference.c | 19 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/dsound.c | 1113 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/endpoint.c | 7 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/errno.c | 6 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/nullsound.c | 197 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/pasound.c | 1037 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_legacy.c | 284 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/sound_port.c | 624 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/stream.c | 127 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/symbian_sound.cpp | 944 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/symbian_sound_aps.cpp | 920 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/transport_ice.c | 1 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/transport_loop.c | 1 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/transport_udp.c | 1 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/wmme_sound.c | 1008 |
16 files changed, 2232 insertions, 5523 deletions
diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c new file mode 100644 index 00000000..bf8ca504 --- /dev/null +++ b/pjmedia/src/pjmedia/conf_switch.c @@ -0,0 +1,1466 @@ +/* $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/conference.h> +#include <pjmedia/alaw_ulaw.h> +#include <pjmedia/errno.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pjmedia/sound_port.h> +#include <pjmedia/stream.h> +#include <pj/array.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#if defined(PJMEDIA_CONF_USE_SWITCH_BOARD) && PJMEDIA_CONF_USE_SWITCH_BOARD!=0 + +/* CONF_DEBUG enables detailed operation of the conference bridge. + * Beware that it prints large amounts of logs (several lines per frame). + */ +//#define CONF_DEBUG +#ifdef CONF_DEBUG +# include <stdio.h> +# define TRACE_(x) PJ_LOG(5,x) +#else +# define TRACE_(x) +#endif + +#define THIS_FILE "conf_switch.c" + +#define SIGNATURE PJMEDIA_CONF_SWITCH_SIGNATURE +#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('S', 'W', 'T', 'P') +#define NORMAL_LEVEL 128 +#define SLOT_TYPE unsigned +#define INVALID_SLOT ((SLOT_TYPE)-1) +#define BUFFER_SIZE PJMEDIA_MAX_MTU +#define MAX_LEVEL (32767) +#define MIN_LEVEL (-32768) + +/* + * DON'T GET CONFUSED WITH TX/RX!! + * + * TX and RX directions are always viewed from the conference bridge's point + * of view, and NOT from the port's point of view. So TX means the bridge + * is transmitting to the port, RX means the bridge is receiving from the + * port. + */ + + +/** + * This is a port connected to conference bridge. + */ +struct conf_port +{ + SLOT_TYPE slot; /**< Array of listeners. */ + pj_str_t name; /**< Port name. */ + pjmedia_port *port; /**< get_frame() and put_frame() */ + pjmedia_port_op rx_setting; /**< Can we receive from this port */ + pjmedia_port_op tx_setting; /**< Can we transmit to this port */ + unsigned listener_cnt; /**< Number of listeners. */ + SLOT_TYPE *listener_slots;/**< Array of listeners. */ + unsigned transmitter_cnt;/**<Number of transmitters. */ + + /* Shortcut for port info. */ + pjmedia_port_info *info; + + /* Calculated signal levels: */ + unsigned tx_level; /**< Last tx level to this port. */ + unsigned rx_level; /**< Last rx level from this port. */ + + /* The normalized signal level adjustment. + * A value of 128 (NORMAL_LEVEL) means there's no adjustment. + */ + unsigned tx_adj_level; /**< Adjustment for TX. */ + unsigned rx_adj_level; /**< Adjustment for RX. */ + + pj_timestamp ts_clock; + pj_timestamp ts_rx; + pj_timestamp ts_tx; + + /* Tx buffer is a temporary buffer to be used when there's mismatch + * between port's ptime with conference's ptime. This buffer is used as + * the source to buffer the samples until there are enough samples to + * fulfill a complete frame to be transmitted to the port. + */ + pj_uint8_t tx_buf[BUFFER_SIZE]; /**< Tx buffer. */ +}; + + +/* + * Conference bridge. + */ +struct pjmedia_conf +{ + unsigned options; /**< Bitmask options. */ + unsigned max_ports; /**< Maximum ports. */ + unsigned port_cnt; /**< Current number of ports. */ + unsigned connect_cnt; /**< Total number of connections */ + pjmedia_port *master_port; /**< Port zero's port. */ + char master_name_buf[80]; /**< Port0 name buffer. */ + pj_mutex_t *mutex; /**< Conference mutex. */ + struct conf_port **ports; /**< Array of ports. */ + pj_uint8_t buf[BUFFER_SIZE]; /**< Common buffer. */ +}; + + +/* Prototypes */ +static pj_status_t put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame); +static pj_status_t get_frame(pjmedia_port *this_port, + pjmedia_frame *frame); +static pj_status_t destroy_port(pjmedia_port *this_port); + + +/* + * Create port. + */ +static pj_status_t create_conf_port( pj_pool_t *pool, + pjmedia_conf *conf, + pjmedia_port *port, + const pj_str_t *name, + struct conf_port **p_conf_port) +{ + struct conf_port *conf_port; + pjmedia_frame *f; + + PJ_ASSERT_RETURN(pool && conf && port && name && p_conf_port, PJ_EINVAL); + + /* Create port. */ + conf_port = PJ_POOL_ZALLOC_T(pool, struct conf_port); + + /* Set name */ + pj_strdup_with_null(pool, &conf_port->name, name); + + /* Default has tx and rx enabled. */ + conf_port->rx_setting = PJMEDIA_PORT_ENABLE; + conf_port->tx_setting = PJMEDIA_PORT_ENABLE; + + /* Default level adjustment is 128 (which means no adjustment) */ + conf_port->tx_adj_level = NORMAL_LEVEL; + conf_port->rx_adj_level = NORMAL_LEVEL; + + /* Create transmit flag array */ + conf_port->listener_slots = (SLOT_TYPE*) + pj_pool_zalloc(pool, + conf->max_ports * sizeof(SLOT_TYPE)); + PJ_ASSERT_RETURN(conf_port->listener_slots, PJ_ENOMEM); + + /* Save some port's infos, for convenience. */ + conf_port->port = port; + conf_port->info = &port->info; + + /* Init pjmedia_frame structure in the TX buffer. */ + f = (pjmedia_frame*)conf_port->tx_buf; + f->buf = conf_port->tx_buf + sizeof(pjmedia_frame); + + /* Done */ + *p_conf_port = conf_port; + return PJ_SUCCESS; +} + +/* + * Create port zero for the sound device. + */ +static pj_status_t create_sound_port( pj_pool_t *pool, + pjmedia_conf *conf ) +{ + struct conf_port *conf_port; + pj_str_t name = { "Master/sound", 12 }; + pj_status_t status; + + status = create_conf_port(pool, conf, conf->master_port, &name, &conf_port); + if (status != PJ_SUCCESS) + return status; + + /* Add the port to the bridge */ + conf_port->slot = 0; + conf->ports[0] = conf_port; + conf->port_cnt++; + + PJ_LOG(5,(THIS_FILE, "Sound device successfully created for port 0")); + return PJ_SUCCESS; +} + +/* + * Create conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool, + unsigned max_ports, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + pjmedia_conf **p_conf ) +{ + pjmedia_conf *conf; + const pj_str_t name = { "Conf", 4 }; + pj_status_t status; + + /* Can only accept 16bits per sample, for now.. */ + PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); + + PJ_LOG(5,(THIS_FILE, "Creating conference bridge with %d ports", + max_ports)); + + /* Create and init conf structure. */ + conf = PJ_POOL_ZALLOC_T(pool, pjmedia_conf); + PJ_ASSERT_RETURN(conf, PJ_ENOMEM); + + conf->ports = (struct conf_port**) + pj_pool_zalloc(pool, max_ports*sizeof(void*)); + PJ_ASSERT_RETURN(conf->ports, PJ_ENOMEM); + + conf->options = options; + conf->max_ports = max_ports; + + /* Create and initialize the master port interface. */ + conf->master_port = PJ_POOL_ZALLOC_T(pool, pjmedia_port); + PJ_ASSERT_RETURN(conf->master_port, PJ_ENOMEM); + + pjmedia_port_info_init(&conf->master_port->info, &name, SIGNATURE, + clock_rate, channel_count, bits_per_sample, + samples_per_frame); + + conf->master_port->port_data.pdata = conf; + conf->master_port->port_data.ldata = 0; + + conf->master_port->get_frame = &get_frame; + conf->master_port->put_frame = &put_frame; + conf->master_port->on_destroy = &destroy_port; + + + /* Create port zero for sound device. */ + status = create_sound_port(pool, conf); + if (status != PJ_SUCCESS) + return status; + + /* Create mutex. */ + status = pj_mutex_create_recursive(pool, "conf", &conf->mutex); + if (status != PJ_SUCCESS) + return status; + + /* Done */ + + *p_conf = conf; + + return PJ_SUCCESS; +} + + +/* + * Pause sound device. + */ +static pj_status_t pause_sound( pjmedia_conf *conf ) +{ + /* Do nothing. */ + PJ_UNUSED_ARG(conf); + return PJ_SUCCESS; +} + +/* + * Resume sound device. + */ +static pj_status_t resume_sound( pjmedia_conf *conf ) +{ + /* Do nothing. */ + PJ_UNUSED_ARG(conf); + return PJ_SUCCESS; +} + + +/** + * Destroy conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_destroy( pjmedia_conf *conf ) +{ + PJ_ASSERT_RETURN(conf != NULL, PJ_EINVAL); + + /* Destroy mutex */ + pj_mutex_destroy(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Destroy the master port (will destroy the conference) + */ +static pj_status_t destroy_port(pjmedia_port *this_port) +{ + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + return pjmedia_conf_destroy(conf); +} + +/* + * Get port zero interface. + */ +PJ_DEF(pjmedia_port*) pjmedia_conf_get_master_port(pjmedia_conf *conf) +{ + /* Sanity check. */ + PJ_ASSERT_RETURN(conf != NULL, NULL); + + /* Can only return port interface when PJMEDIA_CONF_NO_DEVICE was + * present in the option. + */ + PJ_ASSERT_RETURN((conf->options & PJMEDIA_CONF_NO_DEVICE) != 0, NULL); + + return conf->master_port; +} + + +/* + * Set master port name. + */ +PJ_DEF(pj_status_t) pjmedia_conf_set_port0_name(pjmedia_conf *conf, + const pj_str_t *name) +{ + unsigned len; + + /* Sanity check. */ + PJ_ASSERT_RETURN(conf != NULL && name != NULL, PJ_EINVAL); + + len = name->slen; + if (len > sizeof(conf->master_name_buf)) + len = sizeof(conf->master_name_buf); + + if (len > 0) pj_memcpy(conf->master_name_buf, name->ptr, len); + + conf->ports[0]->name.ptr = conf->master_name_buf; + conf->ports[0]->name.slen = len; + + conf->master_port->info.name = conf->ports[0]->name; + + return PJ_SUCCESS; +} + +/* + * Add stream port to the conference bridge. + */ +PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, + pj_pool_t *pool, + pjmedia_port *strm_port, + const pj_str_t *port_name, + unsigned *p_port ) +{ + struct conf_port *conf_port; + unsigned index; + pj_status_t status; + + PJ_ASSERT_RETURN(conf && pool && strm_port, PJ_EINVAL); + /* + PJ_ASSERT_RETURN(conf->clock_rate == strm_port->info.clock_rate, + PJMEDIA_ENCCLOCKRATE); + PJ_ASSERT_RETURN(conf->channel_count == strm_port->info.channel_count, + PJMEDIA_ENCCHANNEL); + PJ_ASSERT_RETURN(conf->bits_per_sample == strm_port->info.bits_per_sample, + PJMEDIA_ENCBITS); + */ + + /* Port's samples per frame should be equal to or multiplication of + * conference's samples per frame. + */ + /* + Not sure if this is needed! + PJ_ASSERT_RETURN((conf->samples_per_frame % + strm_port->info.samples_per_frame==0) || + (strm_port->info.samples_per_frame % + conf->samples_per_frame==0), + PJMEDIA_ENCSAMPLESPFRAME); + */ + + /* If port_name is not specified, use the port's name */ + if (!port_name) + port_name = &strm_port->info.name; + + pj_mutex_lock(conf->mutex); + + if (conf->port_cnt >= conf->max_ports) { + pj_assert(!"Too many ports"); + pj_mutex_unlock(conf->mutex); + return PJ_ETOOMANY; + } + + /* Find empty port in the conference bridge. */ + for (index=0; index < conf->max_ports; ++index) { + if (conf->ports[index] == NULL) + break; + } + + pj_assert(index != conf->max_ports); + + /* Create conf port structure. */ + status = create_conf_port(pool, conf, strm_port, port_name, &conf_port); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(conf->mutex); + return status; + } + + /* Put the port. */ + conf_port->slot = index; + conf->ports[index] = conf_port; + conf->port_cnt++; + + /* Done. */ + if (p_port) { + *p_port = index; + } + + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + + +/* + * Add passive port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_add_passive_port( pjmedia_conf *conf, + pj_pool_t *pool, + const pj_str_t *name, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + unsigned options, + unsigned *p_slot, + pjmedia_port **p_port ) +{ + PJ_UNUSED_ARG(conf); + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(name); + PJ_UNUSED_ARG(clock_rate); + PJ_UNUSED_ARG(channel_count); + PJ_UNUSED_ARG(samples_per_frame); + PJ_UNUSED_ARG(bits_per_sample); + PJ_UNUSED_ARG(options); + PJ_UNUSED_ARG(p_slot); + PJ_UNUSED_ARG(p_port); + + return PJ_ENOTSUP; +} + + + +/* + * Change TX and RX settings for the port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_configure_port( pjmedia_conf *conf, + unsigned slot, + pjmedia_port_op tx, + pjmedia_port_op rx) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + if (tx != PJMEDIA_PORT_NO_CHANGE) + conf_port->tx_setting = tx; + + if (rx != PJMEDIA_PORT_NO_CHANGE) + conf_port->rx_setting = rx; + + return PJ_SUCCESS; +} + + +/* + * Connect port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, + unsigned src_slot, + unsigned sink_slot, + int level ) +{ + struct conf_port *src_port, *dst_port; + pj_bool_t start_sound = PJ_FALSE; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports && + sink_slot<conf->max_ports, PJ_EINVAL); + + /* Ports must be valid. */ + PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL); + + /* For now, level MUST be zero. */ + PJ_ASSERT_RETURN(level == 0, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + src_port = conf->ports[src_slot]; + dst_port = conf->ports[sink_slot]; + + /* Format must match. */ + if (src_port->info->format.id != dst_port->info->format.id || + src_port->info->format.bitrate != dst_port->info->format.bitrate) + { + pj_mutex_unlock(conf->mutex); + return PJMEDIA_ENOTCOMPATIBLE; + } + + /* Clock rate must match. */ + if (src_port->info->clock_rate != dst_port->info->clock_rate) { + pj_mutex_unlock(conf->mutex); + return PJMEDIA_ENCCLOCKRATE; + } + + /* Channel count must match. */ + if (src_port->info->channel_count != dst_port->info->channel_count) { + pj_mutex_unlock(conf->mutex); + return PJMEDIA_ENCCLOCKRATE; + } + + /* Source and sink ptime must be equal or a multiplication factor. */ + if ((src_port->info->samples_per_frame % + dst_port->info->samples_per_frame != 0) && + (dst_port->info->samples_per_frame % + src_port->info->samples_per_frame != 0)) + { + pj_mutex_unlock(conf->mutex); + return PJMEDIA_ENCSAMPLESPFRAME; + } + + /* Check if sink is listening to other ports */ + if (dst_port->transmitter_cnt > 0) { + pj_mutex_unlock(conf->mutex); + return PJ_ETOOMANYCONN; + } + + /* Check if connection has been made */ + for (i=0; i<src_port->listener_cnt; ++i) { + if (src_port->listener_slots[i] == sink_slot) + break; + } + + if (i == src_port->listener_cnt) { + src_port->listener_slots[src_port->listener_cnt] = sink_slot; + ++conf->connect_cnt; + ++src_port->listener_cnt; + ++dst_port->transmitter_cnt; + + if (conf->connect_cnt == 1) + start_sound = 1; + + PJ_LOG(4,(THIS_FILE,"Port %d (%.*s) transmitting to port %d (%.*s)", + src_slot, + (int)src_port->name.slen, + src_port->name.ptr, + sink_slot, + (int)dst_port->name.slen, + dst_port->name.ptr)); + } + + pj_mutex_unlock(conf->mutex); + + /* Sound device must be started without mutex, otherwise the + * sound thread will deadlock (?) + */ + if (start_sound) + resume_sound(conf); + + return PJ_SUCCESS; +} + + +/* + * Disconnect port + */ +PJ_DEF(pj_status_t) pjmedia_conf_disconnect_port( pjmedia_conf *conf, + unsigned src_slot, + unsigned sink_slot ) +{ + struct conf_port *src_port, *dst_port; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && src_slot<conf->max_ports && + sink_slot<conf->max_ports, PJ_EINVAL); + + /* Ports must be valid. */ + PJ_ASSERT_RETURN(conf->ports[src_slot] != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(conf->ports[sink_slot] != NULL, PJ_EINVAL); + + pj_mutex_lock(conf->mutex); + + src_port = conf->ports[src_slot]; + dst_port = conf->ports[sink_slot]; + + /* Check if connection has been made */ + for (i=0; i<src_port->listener_cnt; ++i) { + if (src_port->listener_slots[i] == sink_slot) + break; + } + + if (i != src_port->listener_cnt) { + pjmedia_frame_ext *f; + + pj_assert(src_port->listener_cnt > 0 && + src_port->listener_cnt < conf->max_ports); + pj_assert(dst_port->transmitter_cnt > 0 && + dst_port->transmitter_cnt < conf->max_ports); + pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE), + src_port->listener_cnt, i); + --conf->connect_cnt; + --src_port->listener_cnt; + --dst_port->transmitter_cnt; + + /* Cleanup listener TX buffer. */ + f = (pjmedia_frame_ext*)dst_port->tx_buf; + f->base.type = PJMEDIA_FRAME_TYPE_NONE; + f->base.size = 0; + f->samples_cnt = 0; + f->subframe_cnt = 0; + + PJ_LOG(4,(THIS_FILE, + "Port %d (%.*s) stop transmitting to port %d (%.*s)", + src_slot, + (int)src_port->name.slen, + src_port->name.ptr, + sink_slot, + (int)dst_port->name.slen, + dst_port->name.ptr)); + } + + pj_mutex_unlock(conf->mutex); + + if (conf->connect_cnt == 0) { + pause_sound(conf); + } + + return PJ_SUCCESS; +} + +/* + * Get number of ports currently registered to the conference bridge. + */ +PJ_DEF(unsigned) pjmedia_conf_get_port_count(pjmedia_conf *conf) +{ + return conf->port_cnt; +} + +/* + * Get total number of ports connections currently set up in the bridge. + */ +PJ_DEF(unsigned) pjmedia_conf_get_connect_count(pjmedia_conf *conf) +{ + return conf->connect_cnt; +} + + +/* + * Remove the specified port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf, + unsigned port ) +{ + struct conf_port *conf_port; + unsigned i; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[port] != NULL, PJ_EINVAL); + + /* Suspend the sound devices. + * Don't want to remove port while port is being accessed by sound + * device's threads! + */ + + pj_mutex_lock(conf->mutex); + + conf_port = conf->ports[port]; + conf_port->tx_setting = PJMEDIA_PORT_DISABLE; + conf_port->rx_setting = PJMEDIA_PORT_DISABLE; + + /* Remove this port from transmit array of other ports. */ + for (i=0; i<conf->max_ports; ++i) { + unsigned j; + struct conf_port *src_port; + + src_port = conf->ports[i]; + + if (!src_port) + continue; + + if (src_port->listener_cnt == 0) + continue; + + for (j=0; j<src_port->listener_cnt; ++j) { + if (src_port->listener_slots[j] == port) { + pj_array_erase(src_port->listener_slots, sizeof(SLOT_TYPE), + src_port->listener_cnt, j); + pj_assert(conf->connect_cnt > 0); + --conf->connect_cnt; + --src_port->listener_cnt; + break; + } + } + } + + /* Update transmitter_cnt of ports we're transmitting to */ + while (conf_port->listener_cnt) { + unsigned dst_slot; + struct conf_port *dst_port; + pjmedia_frame_ext *f; + + dst_slot = conf_port->listener_slots[conf_port->listener_cnt-1]; + dst_port = conf->ports[dst_slot]; + --dst_port->transmitter_cnt; + --conf_port->listener_cnt; + pj_assert(conf->connect_cnt > 0); + --conf->connect_cnt; + + /* Cleanup & reinit listener TX buffer. */ + f = (pjmedia_frame_ext*)dst_port->tx_buf; + f->base.type = PJMEDIA_FRAME_TYPE_NONE; + f->base.size = 0; + f->samples_cnt = 0; + f->subframe_cnt = 0; + } + + /* Remove the port. */ + conf->ports[port] = NULL; + --conf->port_cnt; + + pj_mutex_unlock(conf->mutex); + + + /* Stop sound if there's no connection. */ + if (conf->connect_cnt == 0) { + pause_sound(conf); + } + + return PJ_SUCCESS; +} + + +/* + * Enum ports. + */ +PJ_DEF(pj_status_t) pjmedia_conf_enum_ports( pjmedia_conf *conf, + unsigned ports[], + unsigned *p_count ) +{ + unsigned i, count=0; + + PJ_ASSERT_RETURN(conf && p_count && ports, PJ_EINVAL); + + for (i=0; i<conf->max_ports && count<*p_count; ++i) { + if (!conf->ports[i]) + continue; + + ports[count++] = i; + } + + *p_count = count; + return PJ_SUCCESS; +} + +/* + * Get port info + */ +PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, + unsigned slot, + pjmedia_conf_port_info *info) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + pj_bzero(info, sizeof(pjmedia_conf_port_info)); + + info->slot = slot; + info->name = conf_port->name; + info->tx_setting = conf_port->tx_setting; + info->rx_setting = conf_port->rx_setting; + info->listener_cnt = conf_port->listener_cnt; + info->listener_slots = conf_port->listener_slots; + info->transmitter_cnt = conf_port->transmitter_cnt; + info->clock_rate = conf_port->info->clock_rate; + info->channel_count = conf_port->info->channel_count; + info->samples_per_frame = conf_port->info->samples_per_frame; + info->bits_per_sample = conf_port->info->bits_per_sample; + info->format = conf_port->port->info.format; + info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL; + info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL; + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_conf_get_ports_info(pjmedia_conf *conf, + unsigned *size, + pjmedia_conf_port_info info[]) +{ + unsigned i, count=0; + + PJ_ASSERT_RETURN(conf && size && info, PJ_EINVAL); + + for (i=0; i<conf->max_ports && count<*size; ++i) { + if (!conf->ports[i]) + continue; + + pjmedia_conf_get_port_info(conf, i, &info[count]); + ++count; + } + + *size = count; + return PJ_SUCCESS; +} + + +/* + * Get signal level. + */ +PJ_DEF(pj_status_t) pjmedia_conf_get_signal_level( pjmedia_conf *conf, + unsigned slot, + unsigned *tx_level, + unsigned *rx_level) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + if (tx_level != NULL) { + *tx_level = conf_port->tx_level; + } + + if (rx_level != NULL) + *rx_level = conf_port->rx_level; + + return PJ_SUCCESS; +} + + +/* + * Adjust RX level of individual port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf, + unsigned slot, + int adj_level ) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + /* Value must be from -128 to +127 */ + /* Disabled, you can put more than +127, at your own risk: + PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL); + */ + PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + /* Level adjustment is applicable only for ports that work with raw PCM. */ + PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16, + PJ_EIGNORED); + + /* Set normalized adjustment level. */ + conf_port->rx_adj_level = adj_level + NORMAL_LEVEL; + + return PJ_SUCCESS; +} + + +/* + * Adjust TX level of individual port. + */ +PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf, + unsigned slot, + int adj_level ) +{ + struct conf_port *conf_port; + + /* Check arguments */ + PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); + + /* Port must be valid. */ + PJ_ASSERT_RETURN(conf->ports[slot] != NULL, PJ_EINVAL); + + /* Value must be from -128 to +127 */ + /* Disabled, you can put more than +127,, at your own risk: + PJ_ASSERT_RETURN(adj_level >= -128 && adj_level <= 127, PJ_EINVAL); + */ + PJ_ASSERT_RETURN(adj_level >= -128, PJ_EINVAL); + + conf_port = conf->ports[slot]; + + /* Level adjustment is applicable only for ports that work with raw PCM. */ + PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16, + PJ_EIGNORED); + + /* Set normalized adjustment level. */ + conf_port->tx_adj_level = adj_level + NORMAL_LEVEL; + + return PJ_SUCCESS; +} + +/* Deliver frm_src to a listener port, eventually call port's put_frame() + * when samples count in the frm_dst are equal to port's samples_per_frame. + */ +static pj_status_t write_frame(struct conf_port *cport_dst, + const pjmedia_frame *frm_src) +{ + pjmedia_frame *frm_dst = (pjmedia_frame*)cport_dst->tx_buf; + + PJ_TODO(MAKE_SURE_DEST_FRAME_HAS_ENOUGH_SPACE); + + frm_dst->type = frm_src->type; + frm_dst->timestamp = cport_dst->ts_tx; + + if (frm_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) { + + pjmedia_frame_ext *f_src = (pjmedia_frame_ext*)frm_src; + pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst; + unsigned i; + + for (i = 0; i < f_src->subframe_cnt; ++i) { + pjmedia_frame_ext_subframe *sf; + + /* Copy frame to listener's TX buffer. */ + sf = pjmedia_frame_ext_get_subframe(f_src, i); + pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen, + f_src->samples_cnt / + f_src->subframe_cnt); + + /* Check if it's time to deliver the TX buffer to listener, + * i.e: samples count in TX buffer equal to listener's + * samples per frame. + */ + if (f_dst->samples_cnt >= cport_dst->info->samples_per_frame) + { + if (cport_dst->slot) { + pjmedia_port_put_frame(cport_dst->port, + (pjmedia_frame*)f_dst); + + /* Reset TX buffer. */ + f_dst->subframe_cnt = 0; + f_dst->samples_cnt = 0; + } + + /* Update TX timestamp. */ + pj_add_timestamp32(&cport_dst->ts_tx, + cport_dst->info->samples_per_frame); + } + } + + } else if (frm_src->type == PJMEDIA_FRAME_TYPE_AUDIO) { + + pj_int16_t *f_start, *f_end; + + f_start = (pj_int16_t*)frm_src->buf; + f_end = f_start + (frm_src->size >> 1); + + while (f_start < f_end) { + unsigned nsamples_to_copy, nsamples_req; + + /* Copy frame to listener's TX buffer. */ + nsamples_to_copy = f_end - f_start; + nsamples_req = cport_dst->info->samples_per_frame - + (frm_dst->size>>1); + if (nsamples_to_copy > nsamples_req) + nsamples_to_copy = nsamples_req; + + /* Adjust TX level. */ + if (cport_dst->tx_adj_level != NORMAL_LEVEL) { + pj_int16_t *p, *p_end; + + p = f_start; + p_end = p + nsamples_to_copy; + while (p < p_end) { + pj_int32_t itemp = *p; + + /* Adjust the level */ + itemp = (itemp * cport_dst->tx_adj_level) >> 7; + + /* Clip the signal if it's too loud */ + if (itemp > MAX_LEVEL) itemp = MAX_LEVEL; + else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL; + + /* Put back in the buffer. */ + *p = (pj_int16_t)itemp; + ++p; + } + } + + pjmedia_copy_samples((pj_int16_t*)frm_dst->buf + (frm_dst->size>>1), + f_start, + nsamples_to_copy); + frm_dst->size += nsamples_to_copy << 1; + f_start += nsamples_to_copy; + + /* Check if it's time to deliver the TX buffer to listener, + * i.e: samples count in TX buffer equal to listener's + * samples per frame. + */ + if ((frm_dst->size >> 1) == cport_dst->info->samples_per_frame) + { + if (cport_dst->slot) { + pjmedia_port_put_frame(cport_dst->port, frm_dst); + + /* Reset TX buffer. */ + frm_dst->size = 0; + } + + /* Update TX timestamp. */ + pj_add_timestamp32(&cport_dst->ts_tx, + cport_dst->info->samples_per_frame); + } + } + + } else if (frm_src->type == PJMEDIA_FRAME_TYPE_NONE) { + + /* Check port format. */ + if (cport_dst->port && + cport_dst->port->info.format.id == PJMEDIA_FORMAT_L16) + { + /* When there is already some samples in listener's TX buffer, + * pad the buffer with "zero samples". + */ + if (frm_dst->size != 0) { + pjmedia_zero_samples((pj_int16_t*)frm_dst->buf, + cport_dst->info->samples_per_frame - + (frm_dst->size>>1)); + + frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO; + frm_dst->size = cport_dst->info->samples_per_frame << 1; + if (cport_dst->slot) { + pjmedia_port_put_frame(cport_dst->port, frm_dst); + + /* Reset TX buffer. */ + frm_dst->size = 0; + } + + /* Update TX timestamp. */ + pj_add_timestamp32(&cport_dst->ts_tx, + cport_dst->info->samples_per_frame); + } + } else { + pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst; + + if (f_dst->samples_cnt != 0) { + frm_dst->type = PJMEDIA_FRAME_TYPE_EXTENDED; + pjmedia_frame_ext_append_subframe(f_dst, NULL, 0, (pj_uint16_t) + (cport_dst->info->samples_per_frame - f_dst->samples_cnt)); + if (cport_dst->slot) { + pjmedia_port_put_frame(cport_dst->port, frm_dst); + + /* Reset TX buffer. */ + f_dst->subframe_cnt = 0; + f_dst->samples_cnt = 0; + } + + /* Update TX timestamp. */ + pj_add_timestamp32(&cport_dst->ts_tx, + cport_dst->info->samples_per_frame); + } + } + + /* Synchronize clock. */ + while (pj_cmp_timestamp(&cport_dst->ts_clock, + &cport_dst->ts_tx) > 0) + { + frm_dst->type = PJMEDIA_FRAME_TYPE_NONE; + frm_dst->timestamp = cport_dst->ts_tx; + if (cport_dst->slot) + pjmedia_port_put_frame(cport_dst->port, frm_dst); + + /* Update TX timestamp. */ + pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->info->samples_per_frame); + } + } + + return PJ_SUCCESS; +} + +/* + * Player callback. + */ +static pj_status_t get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + unsigned ci, i; + + /* Must lock mutex */ + pj_mutex_lock(conf->mutex); + + /* Call get_frame() from all ports (except port 0) that has + * receiver and distribute the frame (put the frame to the destination + * port's buffer to accommodate different ptime, and ultimately call + * put_frame() of that port) to ports that are receiving from this port. + */ + for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) { + struct conf_port *cport = conf->ports[i]; + + /* Skip empty port. */ + if (!cport) + continue; + + /* Var "ci" is to count how many ports have been visited so far. */ + ++ci; + + /* Update clock of the port. */ + pj_add_timestamp32(&cport->ts_clock, + conf->master_port->info.samples_per_frame); + + /* Skip if we're not allowed to receive from this port or + * the port doesn't have listeners. + */ + if (cport->rx_setting == PJMEDIA_PORT_DISABLE || + cport->listener_cnt == 0) + { + cport->rx_level = 0; + pj_add_timestamp32(&cport->ts_rx, + conf->master_port->info.samples_per_frame); + continue; + } + + /* Get frame from each port, put it to the listener TX buffer, + * and eventually call put_frame() of the listener. This loop + * will also make sure the ptime between conf & port synchronized. + */ + while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_rx) > 0) { + pjmedia_frame *f = (pjmedia_frame*)conf->buf; + pj_status_t status; + unsigned j; + pj_int32_t level = 0; + + pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame); + + f->buf = &conf->buf[sizeof(pjmedia_frame)]; + f->size = cport->info->samples_per_frame<<1; + + /* Get frame from port. */ + status = pjmedia_port_get_frame(cport->port, f); + if (status != PJ_SUCCESS) + continue; + + /* Calculate & adjust RX level. */ + if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) { + if (cport->rx_adj_level != NORMAL_LEVEL) { + pj_int16_t *p = (pj_int16_t*)f->buf; + pj_int16_t *end; + + end = p + (f->size >> 1); + while (p < end) { + pj_int32_t itemp = *p; + + /* Adjust the level */ + itemp = (itemp * cport->rx_adj_level) >> 7; + + /* Clip the signal if it's too loud */ + if (itemp > MAX_LEVEL) itemp = MAX_LEVEL; + else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL; + + level += PJ_ABS(itemp); + + /* Put back in the buffer. */ + *p = (pj_int16_t)itemp; + ++p; + } + level /= (f->size >> 1); + } else { + level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf, + f->size >> 1); + } + } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) { + /* For extended frame, level is unknown, so we just set + * it to NORMAL_LEVEL. + */ + level = NORMAL_LEVEL; + } + + cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff; + + /* Put the frame to all listeners. */ + for (j=0; j < cport->listener_cnt; ++j) + { + struct conf_port *listener; + + listener = conf->ports[cport->listener_slots[j]]; + + /* Skip if this listener doesn't want to receive audio */ + if (listener->tx_setting == PJMEDIA_PORT_DISABLE) { + pj_add_timestamp32(&listener->ts_tx, + listener->info->samples_per_frame); + listener->tx_level = 0; + continue; + } + + status = write_frame(listener, f); + if (status != PJ_SUCCESS) { + listener->tx_level = 0; + continue; + } + + /* Set listener TX level based on transmitter RX level & + * listener TX level. + */ + listener->tx_level = (cport->rx_level * listener->tx_adj_level) + >> 8; + } + } + } + + /* Keep alive. Update TX timestamp and send frame type NONE to all + * underflow ports at their own clock. + */ + for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) { + struct conf_port *cport = conf->ports[i]; + + /* Skip empty port. */ + if (!cport) + continue; + + /* Var "ci" is to count how many ports have been visited so far. */ + ++ci; + + if (cport->tx_setting==PJMEDIA_PORT_MUTE || cport->transmitter_cnt==0) + { + pjmedia_frame_ext *f; + + /* Clear left-over samples in tx_buffer, if any, so that it won't + * be transmitted next time we have audio signal. + */ + f = (pjmedia_frame_ext*)cport->tx_buf; + f->base.type = PJMEDIA_FRAME_TYPE_NONE; + f->base.size = 0; + f->samples_cnt = 0; + f->subframe_cnt = 0; + + cport->tx_level = 0; + + while (pj_cmp_timestamp(&cport->ts_clock, &cport->ts_tx) > 0) + { + if (cport->tx_setting == PJMEDIA_PORT_ENABLE) { + pjmedia_frame tmp_f; + + tmp_f.timestamp = cport->ts_tx; + tmp_f.type = PJMEDIA_FRAME_TYPE_NONE; + tmp_f.buf = NULL; + tmp_f.size = 0; + + pjmedia_port_put_frame(cport->port, &tmp_f); + pj_add_timestamp32(&cport->ts_tx, cport->info->samples_per_frame); + } + } + } + } + + /* Return sound playback frame. */ + do { + struct conf_port *this_cport = conf->ports[this_port->port_data.ldata]; + pjmedia_frame *f_src = (pjmedia_frame*) this_cport->tx_buf; + + frame->type = f_src->type; + + if (f_src->type == PJMEDIA_FRAME_TYPE_EXTENDED) { + pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src; + pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frame; + pjmedia_frame_ext_subframe *sf; + unsigned samples_per_subframe; + + if (f_src_->samples_cnt < this_cport->info->samples_per_frame) { + f_dst->base.type = PJMEDIA_FRAME_TYPE_NONE; + f_dst->samples_cnt = 0; + f_dst->subframe_cnt = 0; + break; + } + + f_dst->samples_cnt = 0; + f_dst->subframe_cnt = 0; + i = 0; + samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt; + + + while (f_dst->samples_cnt < this_cport->info->samples_per_frame) { + sf = pjmedia_frame_ext_get_subframe(f_src_, i++); + pj_assert(sf); + pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen, + samples_per_subframe); + } + + /* Shift left TX buffer. */ + pjmedia_frame_ext_pop_subframes(f_src_, i); + + } else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) { + if ((f_src->size>>1) < this_cport->info->samples_per_frame) { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + break; + } + + pjmedia_copy_samples((pj_int16_t*)frame->buf, + (pj_int16_t*)f_src->buf, + this_cport->info->samples_per_frame); + frame->size = this_cport->info->samples_per_frame << 1; + + /* Shift left TX buffer. */ + f_src->size -= frame->size; + if (f_src->size) + pjmedia_move_samples((pj_int16_t*)f_src->buf, + (pj_int16_t*)f_src->buf + + this_cport->info->samples_per_frame, + f_src->size >> 1); + } else { /* PJMEDIA_FRAME_TYPE_NONE */ + pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src; + + /* Reset source/TX buffer */ + f_src_->base.size = 0; + f_src_->samples_cnt = 0; + f_src_->subframe_cnt = 0; + } + } while (0); + + /* Unlock mutex */ + pj_mutex_unlock(conf->mutex); + + return PJ_SUCCESS; +} + +/* + * Recorder callback. + */ +static pj_status_t put_frame(pjmedia_port *this_port, + const pjmedia_frame *f) +{ + pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; + struct conf_port *cport = conf->ports[this_port->port_data.ldata]; + unsigned j; + pj_int32_t level = 0; + + pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame); + + /* Skip if this port is muted/disabled. */ + if (cport->rx_setting == PJMEDIA_PORT_DISABLE) { + cport->rx_level = 0; + return PJ_SUCCESS; + } + + /* Skip if no port is listening to the microphone */ + if (cport->listener_cnt == 0) { + cport->rx_level = 0; + return PJ_SUCCESS; + } + + /* Calculate & adjust RX level. */ + if (f->type == PJMEDIA_FRAME_TYPE_AUDIO) { + if (cport->rx_adj_level != NORMAL_LEVEL) { + pj_int16_t *p = (pj_int16_t*)f->buf; + pj_int16_t *end; + + end = p + (f->size >> 1); + while (p < end) { + pj_int32_t itemp = *p; + + /* Adjust the level */ + itemp = (itemp * cport->rx_adj_level) >> 7; + + /* Clip the signal if it's too loud */ + if (itemp > MAX_LEVEL) itemp = MAX_LEVEL; + else if (itemp < MIN_LEVEL) itemp = MIN_LEVEL; + + level += PJ_ABS(itemp); + + /* Put back in the buffer. */ + *p = (pj_int16_t)itemp; + ++p; + } + level /= (f->size >> 1); + } else { + level = pjmedia_calc_avg_signal((const pj_int16_t*)f->buf, + f->size >> 1); + } + } else if (f->type == PJMEDIA_FRAME_TYPE_EXTENDED) { + /* For extended frame, level is unknown, so we just set + * it to NORMAL_LEVEL. + */ + level = NORMAL_LEVEL; + } + + cport->rx_level = pjmedia_linear2ulaw(level) ^ 0xff; + + /* Put the frame to all listeners. */ + for (j=0; j < cport->listener_cnt; ++j) + { + struct conf_port *listener; + pj_status_t status; + + listener = conf->ports[cport->listener_slots[j]]; + + /* Skip if this listener doesn't want to receive audio */ + if (listener->tx_setting == PJMEDIA_PORT_DISABLE) { + pj_add_timestamp32(&listener->ts_tx, + listener->info->samples_per_frame); + listener->tx_level = 0; + continue; + } + + /* Skip loopback for now. */ + if (listener == cport) { + pj_add_timestamp32(&listener->ts_tx, + listener->info->samples_per_frame); + listener->tx_level = 0; + continue; + } + + status = write_frame(listener, f); + if (status != PJ_SUCCESS) { + listener->tx_level = 0; + continue; + } + + /* Set listener TX level based on transmitter RX level & listener + * TX level. + */ + listener->tx_level = (cport->rx_level * listener->tx_adj_level) >> 8; + } + + return PJ_SUCCESS; +} + +#endif diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 9cae6a74..c39ec036 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -33,6 +33,7 @@ #include <pj/pool.h> #include <pj/string.h> +#if !defined(PJMEDIA_CONF_USE_SWITCH_BOARD) || PJMEDIA_CONF_USE_SWITCH_BOARD==0 /* CONF_DEBUG enables detailed operation of the conference bridge. * Beware that it prints large amounts of logs (several lines per frame). @@ -63,7 +64,7 @@ static FILE *fhnd_rec; #define BYTES_PER_SAMPLE 2 -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'F') +#define SIGNATURE PJMEDIA_CONF_BRIDGE_SIGNATURE #define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'P') /* Normal level is hardcodec to 128 in all over places */ #define NORMAL_LEVEL 128 @@ -464,8 +465,8 @@ static pj_status_t create_sound_port( pj_pool_t *pool, /* Create sound device port: */ if ((conf->options & PJMEDIA_CONF_NO_DEVICE) == 0) { - pjmedia_snd_stream *strm; - pjmedia_snd_stream_info si; + pjmedia_aud_stream *strm; + pjmedia_aud_param param; /* * If capture is disabled then create player only port. @@ -493,14 +494,14 @@ static pj_status_t create_sound_port( pj_pool_t *pool, return status; strm = pjmedia_snd_port_get_snd_stream(conf->snd_dev_port); - status = pjmedia_snd_stream_get_info(strm, &si); + status = pjmedia_aud_stream_get_param(strm, ¶m); if (status == PJ_SUCCESS) { - const pjmedia_snd_dev_info *snd_dev_info; + pjmedia_aud_dev_info snd_dev_info; if (conf->options & PJMEDIA_CONF_NO_MIC) - snd_dev_info = pjmedia_snd_get_dev_info(si.play_id); + pjmedia_aud_dev_get_info(param.play_id, &snd_dev_info); else - snd_dev_info = pjmedia_snd_get_dev_info(si.rec_id); - pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info->name); + pjmedia_aud_dev_get_info(param.rec_id, &snd_dev_info); + pj_strdup2_with_null(pool, &conf_port->name, snd_dev_info.name); } } @@ -1170,6 +1171,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, info->rx_setting = conf_port->rx_setting; info->listener_cnt = conf_port->listener_cnt; info->listener_slots = conf_port->listener_slots; + info->transmitter_cnt = conf_port->transmitter_cnt; info->clock_rate = conf_port->clock_rate; info->channel_count = conf_port->channel_count; info->samples_per_frame = conf_port->samples_per_frame; @@ -1987,3 +1989,4 @@ static pj_status_t put_frame(pjmedia_port *this_port, return status; } +#endif diff --git a/pjmedia/src/pjmedia/dsound.c b/pjmedia/src/pjmedia/dsound.c deleted file mode 100644 index 367a6974..00000000 --- a/pjmedia/src/pjmedia/dsound.c +++ /dev/null @@ -1,1113 +0,0 @@ -/* $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/sound.h> -#include <pjmedia/errno.h> -#include <pj/assert.h> -#include <pj/log.h> -#include <pj/os.h> -#include <pj/string.h> - -#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_WIN32_DIRECT_SOUND - -#define PJ_MIN(x, y) ((x < y) ? (x) : (y)) - -#ifdef _MSC_VER -# pragma warning(push, 3) -#endif - -#include <windows.h> -#include <mmsystem.h> -#include <dsound.h> - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - - -#define THIS_FILE "dsound.c" -#define BITS_PER_SAMPLE 16 -#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8) - -#define MAX_PACKET_BUFFER_COUNT 50 -#define MIN_PACKET_BUFFER_COUNT 2 - -#define MAX_HARDWARE 16 - -struct dsound_dev_info -{ - pjmedia_snd_dev_info info; - LPGUID lpGuid; - GUID guid; -}; - -static unsigned dev_count; -static struct dsound_dev_info dev_info[MAX_HARDWARE]; -static int snd_init_count; - -/* Latency settings */ -static unsigned snd_input_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY; -static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; - - -/* Individual DirectSound capture/playback stream descriptor */ -struct dsound_stream -{ - union - { - struct - { - LPDIRECTSOUND lpDs; - LPDIRECTSOUNDBUFFER lpDsBuffer; - } play; - - struct - { - LPDIRECTSOUNDCAPTURE lpDs; - LPDIRECTSOUNDCAPTUREBUFFER lpDsBuffer; - } capture; - } ds; - - HANDLE hEvent; - LPDIRECTSOUNDNOTIFY lpDsNotify; - DWORD dwBytePos; - DWORD dwDsBufferSize; - pj_timestamp timestamp; - unsigned latency; -}; - - -/* Sound stream. */ -struct pjmedia_snd_stream -{ - pjmedia_dir dir; /**< Sound direction. */ - int play_id; /**< Playback dev id. */ - int rec_id; /**< Recording dev id. */ - pj_pool_t *pool; /**< Memory pool. */ - - pjmedia_snd_rec_cb rec_cb; /**< Capture callback. */ - pjmedia_snd_play_cb play_cb; /**< Playback callback. */ - void *user_data; /**< Application data. */ - - struct dsound_stream play_strm; /**< Playback stream. */ - struct dsound_stream rec_strm; /**< Capture stream. */ - - void *buffer; /**< Temp. frame buffer. */ - unsigned clock_rate; /**< Clock rate. */ - unsigned samples_per_frame; /**< Samples per frame. */ - unsigned bits_per_sample; /**< Bits per sample. */ - unsigned channel_count; /**< Channel count. */ - - pj_thread_t *thread; /**< Thread handle. */ - HANDLE thread_quit_event; /**< Quit signal to thread */ -}; - - -static pj_pool_factory *pool_factory; - - -static void init_waveformatex (PCMWAVEFORMAT *pcmwf, - unsigned clock_rate, - unsigned channel_count) -{ - pj_bzero(pcmwf, sizeof(PCMWAVEFORMAT)); - pcmwf->wf.wFormatTag = WAVE_FORMAT_PCM; - pcmwf->wf.nChannels = (pj_uint16_t)channel_count; - pcmwf->wf.nSamplesPerSec = clock_rate; - pcmwf->wf.nBlockAlign = (pj_uint16_t)(channel_count * BYTES_PER_SAMPLE); - pcmwf->wf.nAvgBytesPerSec = clock_rate * channel_count * BYTES_PER_SAMPLE; - pcmwf->wBitsPerSample = BITS_PER_SAMPLE; -} - - -/* - * Initialize DirectSound player device. - */ -static pj_status_t init_player_stream( struct dsound_stream *ds_strm, - int dev_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned buffer_count) -{ - HRESULT hr; - HWND hwnd; - PCMWAVEFORMAT pcmwf; - DSBUFFERDESC dsbdesc; - DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT]; - unsigned bytes_per_frame; - unsigned max_latency; - unsigned i; - - - PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL); - - /* Check device ID */ - if (dev_id == -1) - dev_id = 0; - - PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL); - - /* - * Create DirectSound device. - */ - hr = DirectSoundCreate(dev_info[dev_id].lpGuid, &ds_strm->ds.play.lpDs, - NULL); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - hwnd = GetForegroundWindow(); - if (hwnd == NULL) { - hwnd = GetDesktopWindow(); - } - hr = IDirectSound_SetCooperativeLevel( ds_strm->ds.play.lpDs, hwnd, - DSSCL_PRIORITY); - if FAILED(hr) - return PJ_RETURN_OS_ERROR(hr); - - /* - * Set up wave format structure for initialize DirectSound play - * buffer. - */ - init_waveformatex(&pcmwf, clock_rate, channel_count); - bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE; - - /* Set up DSBUFFERDESC structure. */ - pj_bzero(&dsbdesc, sizeof(DSBUFFERDESC)); - dsbdesc.dwSize = sizeof(DSBUFFERDESC); - dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY | - DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS; - - dsbdesc.dwBufferBytes = buffer_count * bytes_per_frame; - dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; - - /* - * Create DirectSound playback buffer. - */ - hr = IDirectSound_CreateSoundBuffer(ds_strm->ds.play.lpDs, &dsbdesc, - &ds_strm->ds.play.lpDsBuffer, NULL); - if (FAILED(hr) ) - return PJ_RETURN_OS_ERROR(hr); - - /* - * Create event for play notification. - */ - ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL); - if (ds_strm->hEvent == NULL) - return pj_get_os_error(); - - /* - * Setup notification for play. - */ - hr = IDirectSoundBuffer_QueryInterface( ds_strm->ds.play.lpDsBuffer, - &IID_IDirectSoundNotify, - (LPVOID *)&ds_strm->lpDsNotify); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - - for (i=0; i<buffer_count; ++i) { - dsPosNotify[i].dwOffset = i * bytes_per_frame; - dsPosNotify[i].hEventNotify = ds_strm->hEvent; - } - - hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify, - buffer_count, - dsPosNotify); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - - hr = IDirectSoundBuffer_SetCurrentPosition(ds_strm->ds.play.lpDsBuffer, 0); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - - ds_strm->dwBytePos = 0; - ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame; - ds_strm->timestamp.u64 = 0; - /* - * Play latency does not need to be on a frame boundry, it is just how far - * ahead of the read pointer we set the write pointer. So we should just - * use the user configured latency. However, if the latency measured in - * bytes causes more buffers than we are allowed, we must cap the latency - * at the time contained in 1-buffer_count. - */ - max_latency = (1 - buffer_count) * samples_per_frame * 1000 / clock_rate / - channel_count; - ds_strm->latency = PJ_MIN(max_latency, snd_output_latency); - - /* Done setting up play device. */ - PJ_LOG(5,(THIS_FILE, - " DirectSound player \"%s\" initialized (clock_rate=%d, " - "channel_count=%d, samples_per_frame=%d (%dms))", - dev_info[dev_id].info.name, - clock_rate, channel_count, samples_per_frame, - samples_per_frame * 1000 / clock_rate)); - - return PJ_SUCCESS; -} - - -/* - * Initialize DirectSound recorder device - */ -static pj_status_t init_capture_stream( struct dsound_stream *ds_strm, - int dev_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned buffer_count) -{ - HRESULT hr; - PCMWAVEFORMAT pcmwf; - DSCBUFFERDESC dscbdesc; - DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT]; - unsigned bytes_per_frame; - unsigned i; - - - PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL); - - - /* Check device id */ - if (dev_id == -1) - dev_id = 0; - - PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL); - - /* - * Creating recorder device. - */ - hr = DirectSoundCaptureCreate(dev_info[dev_id].lpGuid, - &ds_strm->ds.capture.lpDs, NULL); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - - /* Init wave format to initialize buffer */ - init_waveformatex( &pcmwf, clock_rate, channel_count); - bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE; - - /* - * Setup capture buffer using sound buffer structure that was passed - * to play buffer creation earlier. - */ - pj_bzero(&dscbdesc, sizeof(DSCBUFFERDESC)); - dscbdesc.dwSize = sizeof(DSCBUFFERDESC); - dscbdesc.dwFlags = DSCBCAPS_WAVEMAPPED ; - dscbdesc.dwBufferBytes = buffer_count * bytes_per_frame; - dscbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; - - hr = IDirectSoundCapture_CreateCaptureBuffer( ds_strm->ds.capture.lpDs, - &dscbdesc, - &ds_strm->ds.capture.lpDsBuffer, - NULL); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - /* - * Create event for play notification. - */ - ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL); - if (ds_strm->hEvent == NULL) - return pj_get_os_error(); - - /* - * Setup notifications for recording. - */ - hr = IDirectSoundCaptureBuffer_QueryInterface( ds_strm->ds.capture.lpDsBuffer, - &IID_IDirectSoundNotify, - (LPVOID *)&ds_strm->lpDsNotify); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - - for (i=0; i<buffer_count; ++i) { - dsPosNotify[i].dwOffset = i * bytes_per_frame; - dsPosNotify[i].hEventNotify = ds_strm->hEvent; - } - - hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify, - buffer_count, - dsPosNotify); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - hr = IDirectSoundCaptureBuffer_GetCurrentPosition( ds_strm->ds.capture.lpDsBuffer, - NULL, &ds_strm->dwBytePos ); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - ds_strm->timestamp.u64 = 0; - ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame; - /* - * Capture latency must always be on a frame boundry, - * so compute it based off the calculated buffer_count. - */ - ds_strm->latency = buffer_count * samples_per_frame * 1000 / clock_rate / - channel_count; - - /* Done setting up recorder device. */ - PJ_LOG(5,(THIS_FILE, - " DirectSound capture \"%s\" initialized (clock_rate=%d, " - "channel_count=%d, samples_per_frame=%d (%dms))", - dev_info[dev_id].info.name, - clock_rate, channel_count, samples_per_frame, - samples_per_frame * 1000 / clock_rate)); - - return PJ_SUCCESS; -} - - - -static BOOL AppReadDataFromBuffer(LPDIRECTSOUNDCAPTUREBUFFER lpDsb, // The buffer. - DWORD dwOffset, // Our own write cursor. - LPBYTE lpbSoundData, // Start of our data. - DWORD dwSoundBytes) // Size of block to copy. -{ - LPVOID lpvPtr1; - DWORD dwBytes1; - LPVOID lpvPtr2; - DWORD dwBytes2; - HRESULT hr; - - // Obtain memory address of write block. This will be in two parts - // if the block wraps around. - - hr = IDirectSoundCaptureBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, - &dwBytes1, &lpvPtr2, &dwBytes2, 0); - - if SUCCEEDED(hr) { - // Read from pointers. - pj_memcpy(lpbSoundData, lpvPtr1, dwBytes1); - if (lpvPtr2 != NULL) - pj_memcpy(lpbSoundData+dwBytes1, lpvPtr2, dwBytes2); - - // Release the data back to DirectSound. - hr = IDirectSoundCaptureBuffer_Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); - if SUCCEEDED(hr) - return TRUE; - } - - // Lock, Unlock, or Restore failed. - return FALSE; -} - - -static BOOL AppWriteDataToBuffer(LPDIRECTSOUNDBUFFER lpDsb, // The buffer. - DWORD dwOffset, // Our own write cursor. - LPBYTE lpbSoundData, // Start of our data. - DWORD dwSoundBytes) // Size of block to copy. -{ - LPVOID lpvPtr1; - DWORD dwBytes1; - LPVOID lpvPtr2; - DWORD dwBytes2; - HRESULT hr; - - // Obtain memory address of write block. This will be in two parts - // if the block wraps around. - - hr = IDirectSoundBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, - &dwBytes1, &lpvPtr2, &dwBytes2, 0); - - // If the buffer was lost, restore and retry lock. - if (DSERR_BUFFERLOST == hr) { - IDirectSoundBuffer_Restore(lpDsb); - hr = IDirectSoundBuffer_Lock( lpDsb, dwOffset, dwSoundBytes, - &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); - } - if SUCCEEDED(hr) { - pj_memcpy(lpvPtr1, lpbSoundData, dwBytes1); - if (NULL != lpvPtr2) - pj_memcpy(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2); - - hr = IDirectSoundBuffer_Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, dwBytes2); - if SUCCEEDED(hr) - return TRUE; - } - - return FALSE; -} - -/* - * Check if there is space in playing buffer. - */ -static unsigned dsound_play_empty_size(struct dsound_stream *dsound_strm) -{ - HRESULT hr; - long size_available; - DWORD writePos, readPos; - - hr = IDirectSoundBuffer_GetCurrentPosition(dsound_strm->ds.play.lpDsBuffer, - &readPos, &writePos); - if FAILED(hr) - return PJ_FALSE; - - if (readPos < dsound_strm->dwBytePos) - size_available = readPos + dsound_strm->dwDsBufferSize - - dsound_strm->dwBytePos; - else - size_available = readPos - dsound_strm->dwBytePos; - - return size_available; -} - - -/* - * Check if there are captured frames in DirectSound capture buffer. - */ -static unsigned dsound_captured_size(struct dsound_stream *dsound_strm) -{ - HRESULT hr; - long size_available; - DWORD writePos, readPos; - - hr = IDirectSoundCaptureBuffer_GetCurrentPosition( - dsound_strm->ds.capture.lpDsBuffer, - &writePos, &readPos); - if FAILED(hr) - return PJ_FALSE; - - if (readPos < dsound_strm->dwBytePos) - size_available = readPos + - (dsound_strm->dwDsBufferSize) - dsound_strm->dwBytePos; - else - size_available = readPos - dsound_strm->dwBytePos; - - return size_available; -} - -/* - * DirectSound capture and playback thread. - */ -static int dsound_dev_thread(void *arg) -{ - pjmedia_snd_stream *strm = arg; - HANDLE events[3]; - unsigned eventCount; - unsigned bytes_per_frame; - pj_status_t status; - - - eventCount = 0; - events[eventCount++] = strm->thread_quit_event; - if (strm->dir & PJMEDIA_DIR_PLAYBACK) - events[eventCount++] = strm->play_strm.hEvent; - if (strm->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. - */ - SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST); - - /* Calculate bytes per frame */ - bytes_per_frame = strm->samples_per_frame * BYTES_PER_SAMPLE; - - /* - * Loop while not signalled to quit, wait for event objects to be - * signalled by DirectSound capture and play buffer. - */ - while (PJ_TRUE) { - - DWORD rc; - pjmedia_dir signalled_dir; - - 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 dsound_stream *dsound_strm; - - /* - * DirectSound has requested us to feed some frames to - * playback buffer. - */ - - dsound_strm = &strm->play_strm; - status = PJ_SUCCESS; - - while (dsound_play_empty_size(dsound_strm) > bytes_per_frame) { - /* Get frame from application. */ - status = (*strm->play_cb)(strm->user_data, - dsound_strm->timestamp.u32.lo, - strm->buffer, - bytes_per_frame); - if (status != PJ_SUCCESS) - break; - - /* Write to DirectSound buffer. */ - AppWriteDataToBuffer( dsound_strm->ds.play.lpDsBuffer, - dsound_strm->dwBytePos, - (LPBYTE)strm->buffer, - bytes_per_frame); - - /* Increment position. */ - dsound_strm->dwBytePos += bytes_per_frame; - if (dsound_strm->dwBytePos >= dsound_strm->dwDsBufferSize) - dsound_strm->dwBytePos -= dsound_strm->dwDsBufferSize; - dsound_strm->timestamp.u64 += strm->samples_per_frame; - } - - } else { - /* - * DirectSound has indicated that it has some frames ready - * in the capture buffer. Get as much frames as possible to - * prevent overflows. - */ - struct dsound_stream *dsound_strm; - BOOL rc; - - dsound_strm = &strm->rec_strm; - - /* Fetch while we have more than 1 frame */ - while (dsound_captured_size(dsound_strm) > bytes_per_frame) { - - /* Capture from DirectSound buffer. */ - rc = AppReadDataFromBuffer(dsound_strm->ds.capture.lpDsBuffer, - dsound_strm->dwBytePos, - (LPBYTE)strm->buffer, - bytes_per_frame); - - if (!rc) { - pj_bzero(strm->buffer, bytes_per_frame); - } - - /* Call callback */ - status = (*strm->rec_cb)(strm->user_data, - dsound_strm->timestamp.u32.lo, - strm->buffer, - bytes_per_frame); - - /* Quit thread on error. */ - if (status != PJ_SUCCESS) - goto on_error; - - - /* Increment position. */ - dsound_strm->dwBytePos += bytes_per_frame; - if (dsound_strm->dwBytePos >= dsound_strm->dwDsBufferSize) - dsound_strm->dwBytePos -= dsound_strm->dwDsBufferSize; - dsound_strm->timestamp.u64 += strm->samples_per_frame; - } - } - } - - -on_error: - PJ_LOG(5,(THIS_FILE, "DirectSound: thread stopping..")); - return 0; -} - - -/* DirectSound enum device callback */ -static BOOL CALLBACK DSEnumCallback( LPGUID lpGuid, LPCTSTR lpcstrDescription, - LPCTSTR lpcstrModule, LPVOID lpContext) -{ - unsigned index, max = sizeof(dev_info[index].info.name); - pj_bool_t is_capture_device = (lpContext != NULL); - - - PJ_UNUSED_ARG(lpcstrModule); - - - /* Put the capture and playback of the same devices to the same - * dev_info item, by looking at the GUID. - */ - for (index=0; index<dev_count; ++index) { - if ((dev_info[index].lpGuid==NULL && lpGuid==NULL) || - pj_memcmp(&dev_info[index].guid, lpGuid, sizeof(GUID))==0) - { - break; - } - } - - if (index == dev_count) - ++dev_count; - else if (dev_count >= MAX_HARDWARE) { - pj_assert(!"Too many DirectSound hardware found"); - PJ_LOG(4,(THIS_FILE, "Too many hardware found, some devices will " - "not be listed")); - return FALSE; - } - -#ifdef UNICODE - WideCharToMultiByte(CP_ACP, 0, lpcstrDescription, wcslen(lpcstrDescription), - dev_info[index].info.name, max, NULL, NULL); -#else - strncpy(dev_info[index].info.name, lpcstrDescription, max); -#endif - - dev_info[index].info.name[max-1] = '\0'; - if (lpGuid == NULL) { - dev_info[index].lpGuid = NULL; - } else { - pj_memcpy(&dev_info[index].guid, lpGuid, sizeof(GUID)); - dev_info[index].lpGuid = &dev_info[index].guid; - } - dev_info[index].info.default_samples_per_sec = 44100; - - /* Just assumed that device supports stereo capture/playback */ - if (is_capture_device) - dev_info[index].info.input_count+=2; - else - dev_info[index].info.output_count+=2; - - return TRUE; -} - - -/* - * Init sound library. - */ -PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) -{ - HRESULT hr; - unsigned i; - - if (++snd_init_count != 1) - return PJ_SUCCESS; - - pool_factory = factory; - - /* Enumerate sound playback devices */ - hr = DirectSoundEnumerate(&DSEnumCallback, NULL); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - /* Enumerate sound capture devices */ - hr = DirectSoundCaptureEnumerate(&DSEnumCallback, (void*)1); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - PJ_LOG(4,(THIS_FILE, "DirectSound initialized, found %d devices:", - dev_count)); - for (i=0; i<dev_count; ++i) { - PJ_LOG(4,(THIS_FILE, " dev_id %d: %s (in=%d, out=%d)", - i, dev_info[i].info.name, - dev_info[i].info.input_count, - dev_info[i].info.output_count)); - } - - return PJ_SUCCESS; -} - -/* - * Deinitialize sound library. - */ -PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) -{ - --snd_init_count; - return PJ_SUCCESS; -} - -/* - * Get device count. - */ -PJ_DEF(int) pjmedia_snd_get_dev_count(void) -{ - return dev_count; -} - -/* - * Get device info. - */ -PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) -{ - if (index == (unsigned)-1) - index = 0; - - PJ_ASSERT_RETURN(index < dev_count, NULL); - - return &dev_info[index].info; -} - - -/* - * Open stream. - */ -static pj_status_t open_stream( pjmedia_dir dir, - int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *strm; - pj_status_t status; - - /* Make sure sound subsystem has been initialized with - * pjmedia_snd_init() - */ - PJ_ASSERT_RETURN( pool_factory != NULL, PJ_EINVALIDOP ); - - - /* Can only support 16bits per sample */ - PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); - - /* Create and Initialize stream descriptor */ - pool = pj_pool_create(pool_factory, "dsound-dev", 1000, 1000, NULL); - PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); - - strm = pj_pool_zalloc(pool, sizeof(pjmedia_snd_stream)); - strm->dir = dir; - strm->play_id = play_id; - strm->rec_id = rec_id; - strm->pool = pool; - strm->rec_cb = rec_cb; - strm->play_cb = play_cb; - strm->user_data = user_data; - strm->clock_rate = clock_rate; - strm->samples_per_frame = samples_per_frame; - strm->bits_per_sample = bits_per_sample; - strm->channel_count = channel_count; - strm->buffer = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE); - if (!strm->buffer) { - pj_pool_release(pool); - return PJ_ENOMEM; - } - - /* - * Create event for stopping the worker thread. - */ - strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL); - if (strm->thread_quit_event == NULL) { - status = pj_get_os_error(); - pj_pool_release(pool); - return status; - } - - /* Create player stream */ - if (dir & PJMEDIA_DIR_PLAYBACK) { - unsigned buffer_count; - - /* Calculate buffer count, in frame unit */ - buffer_count = clock_rate * snd_output_latency / samples_per_frame / - 1000; - /* There must always be one more buffer than required for the latency */ - buffer_count += 1; - if (buffer_count < MIN_PACKET_BUFFER_COUNT) - buffer_count = MIN_PACKET_BUFFER_COUNT; - if (buffer_count > MAX_PACKET_BUFFER_COUNT) - buffer_count = MAX_PACKET_BUFFER_COUNT; - - status = init_player_stream( &strm->play_strm, play_id, clock_rate, - channel_count, samples_per_frame, - buffer_count ); - if (status != PJ_SUCCESS) { - pjmedia_snd_stream_close(strm); - return status; - } - } - - /* Create capture stream */ - if (dir & PJMEDIA_DIR_CAPTURE) { - unsigned buffer_count; - - /* Calculate buffer count, in frame unit */ - buffer_count = clock_rate * snd_input_latency / samples_per_frame / - 1000; - if (buffer_count < MIN_PACKET_BUFFER_COUNT) - buffer_count = MIN_PACKET_BUFFER_COUNT; - if (buffer_count > MAX_PACKET_BUFFER_COUNT) - buffer_count = MAX_PACKET_BUFFER_COUNT; - - status = init_capture_stream( &strm->rec_strm, rec_id, clock_rate, - channel_count, samples_per_frame, - buffer_count); - if (status != PJ_SUCCESS) { - pjmedia_snd_stream_close(strm); - return status; - } - } - - - /* Create and start the thread */ - status = pj_thread_create(pool, "dsound", &dsound_dev_thread, strm, - 0, 0, &strm->thread); - if (status != PJ_SUCCESS) { - pjmedia_snd_stream_close(strm); - return status; - } - - *p_snd_strm = strm; - - return PJ_SUCCESS; -} - -/* - * Open stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL); - - return open_stream( PJMEDIA_DIR_CAPTURE, index, -1, - clock_rate, channel_count, samples_per_frame, - bits_per_sample, rec_cb, NULL, user_data, - p_snd_strm); -} - -PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL); - - return open_stream( PJMEDIA_DIR_PLAYBACK, -1, index, - clock_rate, channel_count, samples_per_frame, - bits_per_sample, NULL, play_cb, user_data, - p_snd_strm); -} - -/* - * Open both player and recorder. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - PJ_ASSERT_RETURN(rec_cb && play_cb && p_snd_strm, PJ_EINVAL); - - return open_stream( PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id, - clock_rate, channel_count, samples_per_frame, - bits_per_sample, rec_cb, play_cb, user_data, - p_snd_strm ); -} - -/* - * Get stream info. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, - pjmedia_snd_stream_info *pi) -{ - - PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); - - pj_bzero(pi, sizeof(*pi)); - pi->dir = strm->dir; - pi->play_id = strm->play_id; - pi->rec_id = strm->rec_id; - pi->clock_rate = strm->clock_rate; - pi->channel_count = strm->channel_count; - pi->samples_per_frame = strm->samples_per_frame; - pi->bits_per_sample = strm->bits_per_sample; - pi->rec_latency = strm->rec_strm.latency * strm->clock_rate * - strm->channel_count/ 1000; - pi->play_latency = strm->play_strm.latency * strm->clock_rate * - strm->channel_count/ 1000; - - return PJ_SUCCESS; -} - - -/* - * Start stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) -{ - HRESULT hr; - - if (stream->play_strm.ds.play.lpDsBuffer) { - hr = IDirectSoundBuffer_SetCurrentPosition( - stream->play_strm.ds.play.lpDsBuffer, 0); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - /* Set the write pointer ahead of the read pointer by latency bytes */ - stream->play_strm.dwBytePos = BYTES_PER_SAMPLE * stream->clock_rate * - stream->channel_count * stream->play_strm.latency / 1000; - - hr = IDirectSoundBuffer_Play(stream->play_strm.ds.play.lpDsBuffer, - 0, 0, DSBPLAY_LOOPING); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - PJ_LOG(5,(THIS_FILE, "DirectSound playback stream started")); - } - - if (stream->rec_strm.ds.capture.lpDsBuffer) { - hr = IDirectSoundCaptureBuffer_GetCurrentPosition( - stream->rec_strm.ds.capture.lpDsBuffer, - NULL, &stream->rec_strm.dwBytePos ); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - - hr = IDirectSoundCaptureBuffer_Start( - stream->rec_strm.ds.capture.lpDsBuffer, - DSCBSTART_LOOPING ); - if (FAILED(hr)) - return PJ_RETURN_OS_ERROR(hr); - PJ_LOG(5,(THIS_FILE, "DirectSound capture stream started")); - } - - return PJ_SUCCESS; -} - -/* - * Stop stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) -{ - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->play_strm.ds.play.lpDsBuffer) { - PJ_LOG(5,(THIS_FILE, "Stopping DirectSound playback stream")); - IDirectSoundBuffer_Stop( stream->play_strm.ds.play.lpDsBuffer ); - } - - if (stream->rec_strm.ds.capture.lpDsBuffer) { - PJ_LOG(5,(THIS_FILE, "Stopping DirectSound capture stream")); - IDirectSoundCaptureBuffer_Stop(stream->rec_strm.ds.capture.lpDsBuffer); - } - - return PJ_SUCCESS; -} - - -/* - * Destroy stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) -{ - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - pjmedia_snd_stream_stop(stream); - - if (stream->thread) { - pj_assert(stream->thread_quit_event); - SetEvent(stream->thread_quit_event); - pj_thread_join(stream->thread); - pj_thread_destroy(stream->thread); - stream->thread = NULL; - } - - if (stream->thread_quit_event) { - CloseHandle(stream->thread_quit_event); - stream->thread_quit_event = NULL; - } - - if (stream->play_strm.lpDsNotify) { - IDirectSoundNotify_Release( stream->play_strm.lpDsNotify ); - stream->play_strm.lpDsNotify = NULL; - } - - if (stream->play_strm.hEvent) { - CloseHandle(stream->play_strm.hEvent); - stream->play_strm.hEvent = NULL; - } - - if (stream->play_strm.ds.play.lpDsBuffer) { - IDirectSoundBuffer_Release( stream->play_strm.ds.play.lpDsBuffer ); - stream->play_strm.ds.play.lpDsBuffer = NULL; - } - - if (stream->play_strm.ds.play.lpDs) { - IDirectSound_Release( stream->play_strm.ds.play.lpDs ); - stream->play_strm.ds.play.lpDs = NULL; - } - - if (stream->rec_strm.lpDsNotify) { - IDirectSoundNotify_Release( stream->rec_strm.lpDsNotify ); - stream->rec_strm.lpDsNotify = NULL; - } - - if (stream->rec_strm.hEvent) { - CloseHandle(stream->rec_strm.hEvent); - stream->rec_strm.hEvent = NULL; - } - - if (stream->rec_strm.ds.capture.lpDsBuffer) { - IDirectSoundCaptureBuffer_Release( stream->rec_strm.ds.capture.lpDsBuffer ); - stream->rec_strm.ds.capture.lpDsBuffer = NULL; - } - - if (stream->rec_strm.ds.capture.lpDs) { - IDirectSoundCapture_Release( stream->rec_strm.ds.capture.lpDs ); - stream->rec_strm.ds.capture.lpDs = NULL; - } - - - pj_pool_release(stream->pool); - - return PJ_SUCCESS; -} - - -/* - * Set sound latency. - */ -PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, - unsigned output_latency) -{ - snd_input_latency = (input_latency == 0)? - PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency; - snd_output_latency = (output_latency == 0)? - PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency; - - return PJ_SUCCESS; -} - -#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ - diff --git a/pjmedia/src/pjmedia/endpoint.c b/pjmedia/src/pjmedia/endpoint.c index fb66a813..a3e988d5 100644 --- a/pjmedia/src/pjmedia/endpoint.c +++ b/pjmedia/src/pjmedia/endpoint.c @@ -20,6 +20,7 @@ #include <pjmedia/endpoint.h> #include <pjmedia/errno.h> #include <pjmedia/sdp.h> +#include <pjmedia-audiodev/audiodev.h> #include <pj/assert.h> #include <pj/ioqueue.h> #include <pj/log.h> @@ -121,7 +122,7 @@ PJ_DEF(pj_status_t) pjmedia_endpt_create(pj_pool_factory *pf, endpt->thread_cnt = worker_cnt; /* Sound */ - status = pjmedia_snd_init(pf); + status = pjmedia_aud_subsys_init(pf); if (status != PJ_SUCCESS) goto on_error; @@ -171,7 +172,7 @@ on_error: if (endpt->ioqueue && endpt->own_ioqueue) pj_ioqueue_destroy(endpt->ioqueue); - pjmedia_snd_deinit(); + pjmedia_aud_subsys_shutdown(); pj_pool_release(pool); return status; } @@ -212,7 +213,7 @@ PJ_DEF(pj_status_t) pjmedia_endpt_destroy (pjmedia_endpt *endpt) endpt->pf = NULL; - pjmedia_snd_deinit(); + pjmedia_aud_subsys_shutdown(); pj_pool_release (endpt->pool); return PJ_SUCCESS; diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c index f92e888c..d86eb585 100644 --- a/pjmedia/src/pjmedia/errno.c +++ b/pjmedia/src/pjmedia/errno.c @@ -20,7 +20,8 @@ #include <pjmedia/errno.h> #include <pjmedia/types.h> #include <pj/string.h> -#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND +#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \ + PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND # include <portaudio.h> #endif @@ -179,7 +180,8 @@ PJ_DEF(pj_str_t) pjmedia_strerror( pj_status_t statcode, #if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) /* See if the error comes from PortAudio. */ -#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND +#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \ + PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND if (statcode >= PJMEDIA_PORTAUDIO_ERRNO_START && statcode <= PJMEDIA_PORTAUDIO_ERRNO_END) { diff --git a/pjmedia/src/pjmedia/nullsound.c b/pjmedia/src/pjmedia/nullsound.c deleted file mode 100644 index a3ee54a1..00000000 --- a/pjmedia/src/pjmedia/nullsound.c +++ /dev/null @@ -1,197 +0,0 @@ -/* $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/sound.h> -#include <pjmedia/errno.h> -#include <pj/assert.h> -#include <pj/pool.h> - -#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_NULL_SOUND - -static pjmedia_snd_dev_info null_info = -{ - "Null Device", - 1, - 1, - 8000 -}; - -static pj_pool_factory *pool_factory; - -struct pjmedia_snd_stream -{ - pj_pool_t *pool; - pjmedia_dir dir; - int rec_id; - int play_id; - unsigned clock_rate; - unsigned channel_count; - unsigned samples_per_frame; - unsigned bits_per_sample; - pjmedia_snd_rec_cb rec_cb; - pjmedia_snd_play_cb play_cb; - void *user_data; -}; - - -PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) -{ - pool_factory = factory; - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) -{ - return PJ_SUCCESS; -} - -PJ_DEF(int) pjmedia_snd_get_dev_count(void) -{ - return 1; -} - -PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) -{ - PJ_ASSERT_RETURN(index==0 || index==(unsigned)-1, NULL); - return &null_info; -} - -PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - return pjmedia_snd_open(index, -2, clock_rate, channel_count, - samples_per_frame, bits_per_sample, - rec_cb, NULL, user_data, p_snd_strm); -} - -PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm ) -{ - return pjmedia_snd_open(-2, index, clock_rate, channel_count, - samples_per_frame, bits_per_sample, - NULL, play_cb, user_data, p_snd_strm); -} - -PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *snd_strm; - - pool = pj_pool_create(pool_factory, NULL, 128, 128, NULL); - snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream); - - snd_strm->pool = pool; - - if (rec_id == -1) rec_id = 0; - if (play_id == -1) play_id = 0; - - if (rec_id != -2 && play_id != -2) - snd_strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; - else if (rec_id != -2) - snd_strm->dir = PJMEDIA_DIR_CAPTURE; - else if (play_id != -2) - snd_strm->dir = PJMEDIA_DIR_PLAYBACK; - - snd_strm->rec_id = rec_id; - snd_strm->play_id = play_id; - snd_strm->clock_rate = clock_rate; - snd_strm->channel_count = channel_count; - snd_strm->samples_per_frame = samples_per_frame; - snd_strm->bits_per_sample = bits_per_sample; - snd_strm->rec_cb = rec_cb; - snd_strm->play_cb = play_cb; - snd_strm->user_data = user_data; - - *p_snd_strm = snd_strm; - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) -{ - PJ_UNUSED_ARG(stream); - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) -{ - PJ_UNUSED_ARG(stream); - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, - pjmedia_snd_stream_info *pi) -{ - - pj_bzero(pi, sizeof(pjmedia_snd_stream_info)); - pi->dir = strm->dir; - pi->play_id = strm->play_id; - pi->rec_id = strm->rec_id; - pi->clock_rate = strm->clock_rate; - pi->channel_count = strm->channel_count; - pi->samples_per_frame = strm->samples_per_frame; - pi->bits_per_sample = strm->bits_per_sample; - pi->rec_latency = 0; - pi->play_latency = 0; - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) -{ - pj_pool_release(stream->pool); - return PJ_SUCCESS; -} - -/* - * Set sound latency. - */ -PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, - unsigned output_latency) -{ - /* Nothing to do */ - PJ_UNUSED_ARG(input_latency); - PJ_UNUSED_ARG(output_latency); - return PJ_SUCCESS; -} - -#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ diff --git a/pjmedia/src/pjmedia/pasound.c b/pjmedia/src/pjmedia/pasound.c deleted file mode 100644 index a004008d..00000000 --- a/pjmedia/src/pjmedia/pasound.c +++ /dev/null @@ -1,1037 +0,0 @@ -/* $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/sound.h> -#include <pjmedia/errno.h> -#include <pj/assert.h> -#include <pj/log.h> -#include <pj/os.h> -#include <pj/string.h> -#include <portaudio.h> - -#if PJMEDIA_SOUND_IMPLEMENTATION==PJMEDIA_SOUND_PORTAUDIO_SOUND - -#define THIS_FILE "pasound.c" - -static int snd_init_count; - -/* Latency settings */ -static unsigned snd_input_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY; -static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; - -static struct snd_mgr -{ - pj_pool_factory *factory; -} snd_mgr; - -/* - * Sound stream descriptor. - * This struct may be used for both unidirectional or bidirectional sound - * streams. - */ -struct pjmedia_snd_stream -{ - 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_snd_rec_cb rec_cb; - pjmedia_snd_play_cb play_cb; - - pj_uint32_t play_timestamp; - pj_uint32_t 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; -}; - - -static int PaRecorderCallback(const void *input, - void *output, - unsigned long frameCount, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData ) -{ - pjmedia_snd_stream *stream = (pjmedia_snd_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; - - stream->rec_timestamp += frameCount; - - /* 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; - - 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); - status = (*stream->rec_cb)(stream->user_data, - stream->rec_timestamp, - (void*) stream->rec_buf, - stream->samples_per_frame * - stream->bytes_per_sample); - - input = (pj_int16_t*) input + chunk_count; - nsamples -= stream->samples_per_frame; - stream->rec_buf_count = 0; - } - - /* Give all frames we have */ - while (nsamples >= stream->samples_per_frame && status == 0) { - status = (*stream->rec_cb)(stream->user_data, - stream->rec_timestamp, - (void*) input, - stream->samples_per_frame * - stream->bytes_per_sample); - input = (pj_int16_t*) input + stream->samples_per_frame; - nsamples -= stream->samples_per_frame; - } - - /* 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 ) -{ - pjmedia_snd_stream *stream = (pjmedia_snd_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; - - stream->play_timestamp += frameCount; - - /* 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) { - status = (*stream->play_cb)(stream->user_data, - stream->play_timestamp, - output, - stream->samples_per_frame * - stream->bytes_per_sample); - nsamples_req -= stream->samples_per_frame; - output = (pj_int16_t*)output + stream->samples_per_frame; - } else { - status = (*stream->play_cb)(stream->user_data, - stream->play_timestamp, - stream->play_buf, - stream->samples_per_frame * - stream->bytes_per_sample); - 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; - } - } - - 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 sound library. - */ -PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) -{ - if (++snd_init_count == 1) { - int err; - - PaUtil_SetDebugPrintFunction(&pa_log_cb); - - snd_mgr.factory = factory; - 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", - pjmedia_snd_get_dev_count())); - - return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; - } else { - return PJ_SUCCESS; - } -} - - -/* - * Get device count. - */ -PJ_DEF(int) pjmedia_snd_get_dev_count(void) -{ - int count = Pa_GetDeviceCount(); - return count < 0 ? 0 : count; -} - - -/* - * Get device info. - */ -PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) -{ - static pjmedia_snd_dev_info info; - const PaDeviceInfo *pa_info; - - pa_info = Pa_GetDeviceInfo(index); - if (!pa_info) - return NULL; - - 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; - - return &info; -} - - -/* 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 = pjmedia_snd_get_dev_count(); - for (i=0; i<count; ++i) { - const PaDeviceInfo *paDevInfo; - - paDevInfo = Pa_GetDeviceInfo(i); - if (paDevInfo->maxInputChannels >= channel_count) - return i; - } - - return -1; -} - -/* 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 = pjmedia_snd_get_dev_count(); - for (i=0; i<count; ++i) { - const PaDeviceInfo *paDevInfo; - - paDevInfo = Pa_GetDeviceInfo(i); - if (paDevInfo->maxOutputChannels >= channel_count) - return i; - } - - return -1; -} - - -/* - * Open stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *stream; - PaStreamParameters inputParam; - int sampleFormat; - const PaDeviceInfo *paDevInfo = NULL; - const PaHostApiInfo *paHostApiInfo = NULL; - unsigned paFrames, paRate, paLatency; - const PaStreamInfo *paSI; - PaError err; - - if (index < 0) { - index = pa_get_default_input_dev(channel_count); - if (index < 0) { - /* No such device. */ - return PJMEDIA_ENOSNDREC; - } - } - - paDevInfo = Pa_GetDeviceInfo(index); - if (!paDevInfo) { - /* Assumed it is "No such device" error. */ - return PJMEDIA_ESNDINDEVID; - } - - if (bits_per_sample == 8) - sampleFormat = paUInt8; - else if (bits_per_sample == 16) - sampleFormat = paInt16; - else if (bits_per_sample == 32) - sampleFormat = paInt32; - else - return PJMEDIA_ESNDINSAMPLEFMT; - - pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL); - if (!pool) - return PJ_ENOMEM; - - stream = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream); - stream->pool = pool; - pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); - stream->dir = PJMEDIA_DIR_CAPTURE; - stream->rec_id = index; - stream->play_id = -1; - stream->user_data = user_data; - stream->samples_per_sec = clock_rate; - stream->samples_per_frame = samples_per_frame; - stream->bytes_per_sample = bits_per_sample / 8; - stream->channel_count = 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 = index; - inputParam.channelCount = channel_count; - inputParam.hostApiSpecificStreamInfo = NULL; - inputParam.sampleFormat = sampleFormat; - inputParam.suggestedLatency = snd_input_latency / 1000.0; - - paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); - - /* Frames in PortAudio is number of samples in a single channel */ - paFrames = samples_per_frame / channel_count; - - err = Pa_OpenStream( &stream->rec_strm, &inputParam, NULL, - clock_rate, paFrames, - paClipOff, &PaRecorderCallback, stream ); - if (err != paNoError) { - pj_pool_release(pool); - return PJMEDIA_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, channel_count, - bits_per_sample, samples_per_frame, - paLatency)); - - *p_snd_strm = stream; - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *stream; - PaStreamParameters outputParam; - int sampleFormat; - const PaDeviceInfo *paDevInfo = NULL; - const PaHostApiInfo *paHostApiInfo = NULL; - const PaStreamInfo *paSI; - unsigned paFrames, paRate, paLatency; - PaError err; - - if (index < 0) { - index = pa_get_default_output_dev(channel_count); - if (index < 0) { - /* No such device. */ - return PJMEDIA_ENOSNDPLAY; - } - } - - paDevInfo = Pa_GetDeviceInfo(index); - if (!paDevInfo) { - /* Assumed it is "No such device" error. */ - return PJMEDIA_ESNDINDEVID; - } - - if (bits_per_sample == 8) - sampleFormat = paUInt8; - else if (bits_per_sample == 16) - sampleFormat = paInt16; - else if (bits_per_sample == 32) - sampleFormat = paInt32; - else - return PJMEDIA_ESNDINSAMPLEFMT; - - pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL); - if (!pool) - return PJ_ENOMEM; - - stream = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream); - stream->pool = pool; - pj_strdup2_with_null(pool, &stream->name, paDevInfo->name); - stream->dir = PJMEDIA_DIR_PLAYBACK; - stream->play_id = index; - stream->rec_id = -1; - stream->user_data = user_data; - stream->samples_per_sec = clock_rate; - stream->samples_per_frame = samples_per_frame; - stream->bytes_per_sample = bits_per_sample / 8; - stream->channel_count = 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 = index; - outputParam.channelCount = channel_count; - outputParam.hostApiSpecificStreamInfo = NULL; - outputParam.sampleFormat = sampleFormat; - outputParam.suggestedLatency = snd_output_latency / 1000.0; - - paHostApiInfo = Pa_GetHostApiInfo(paDevInfo->hostApi); - - /* Frames in PortAudio is number of samples in a single channel */ - paFrames = samples_per_frame / channel_count; - - err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, - clock_rate, paFrames, - paClipOff, &PaPlayerCallback, stream ); - if (err != paNoError) { - pj_pool_release(pool); - return PJMEDIA_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", - index, paDevInfo->name, paHostApiInfo->name, - paRate, channel_count, - bits_per_sample, samples_per_frame, paLatency)); - - *p_snd_strm = stream; - - return PJ_SUCCESS; -} - - -/* - * Open both player and recorder. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_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; - - if (rec_id < 0) { - rec_id = pa_get_default_input_dev(channel_count); - if (rec_id < 0) { - /* No such device. */ - return PJMEDIA_ENOSNDREC; - } - } - - paRecDevInfo = Pa_GetDeviceInfo(rec_id); - if (!paRecDevInfo) { - /* Assumed it is "No such device" error. */ - return PJMEDIA_ESNDINDEVID; - } - - if (play_id < 0) { - play_id = pa_get_default_output_dev(channel_count); - if (play_id < 0) { - /* No such device. */ - return PJMEDIA_ENOSNDPLAY; - } - } - - paPlayDevInfo = Pa_GetDeviceInfo(play_id); - if (!paPlayDevInfo) { - /* Assumed it is "No such device" error. */ - return PJMEDIA_ESNDINDEVID; - } - - - if (bits_per_sample == 8) - sampleFormat = paUInt8; - else if (bits_per_sample == 16) - sampleFormat = paInt16; - else if (bits_per_sample == 32) - sampleFormat = paInt32; - else - return PJMEDIA_ESNDINSAMPLEFMT; - - pool = pj_pool_create( snd_mgr.factory, "sndstream", 1024, 1024, NULL); - if (!pool) - return PJ_ENOMEM; - - stream = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_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 = clock_rate; - stream->samples_per_frame = samples_per_frame; - stream->bytes_per_sample = bits_per_sample / 8; - stream->channel_count = 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 = channel_count; - inputParam.hostApiSpecificStreamInfo = NULL; - inputParam.sampleFormat = sampleFormat; - inputParam.suggestedLatency = snd_input_latency / 1000.0; - - paRecHostApiInfo = Pa_GetHostApiInfo(paRecDevInfo->hostApi); - - pj_bzero(&outputParam, sizeof(outputParam)); - outputParam.device = play_id; - outputParam.channelCount = channel_count; - outputParam.hostApiSpecificStreamInfo = NULL; - outputParam.sampleFormat = sampleFormat; - outputParam.suggestedLatency = snd_output_latency / 1000.0; - - paPlayHostApiInfo = Pa_GetHostApiInfo(paPlayDevInfo->hostApi); - - /* Frames in PortAudio is number of samples in a single channel */ - paFrames = samples_per_frame / 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, - 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, - clock_rate, paFrames, - paClipOff, &PaRecorderCallback, stream ); - if (err == paNoError) { - /* Open output stream */ - err = Pa_OpenStream( &stream->play_strm, NULL, &outputParam, - clock_rate, paFrames, - paClipOff, &PaPlayerCallback, stream ); - if (err != paNoError) - Pa_CloseStream(stream->rec_strm); - } - } - - if (err != paNoError) { - pj_pool_release(pool); - return PJMEDIA_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, channel_count, - bits_per_sample, samples_per_frame, - paInputLatency, paOutputLatency)); - - *p_snd_strm = stream; - - - return PJ_SUCCESS; -} - - -/* - * Get stream info. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, - pjmedia_snd_stream_info *pi) -{ - 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; - pi->rec_latency = (unsigned)(paRecSI ? paRecSI->inputLatency * - paRecSI->sampleRate : 0); - pi->play_latency = (unsigned)(paPlaySI ? paPlaySI->outputLatency * - paPlaySI->sampleRate : 0); - - return PJ_SUCCESS; -} - - -/* - * Start stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) -{ - 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_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; -} - -/* - * Stop stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) -{ - 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_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; -} - -/* - * Destroy stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) -{ - 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_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; -} - -/* - * Deinitialize sound library. - */ -PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) -{ - if (--snd_init_count == 0) { - int err; - - PJ_LOG(4,(THIS_FILE, "PortAudio sound library shutting down..")); - - err = Pa_Terminate(); - - return err ? PJMEDIA_ERRNO_FROM_PORTAUDIO(err) : PJ_SUCCESS; - } else { - return PJ_SUCCESS; - } -} - -/* - * Set sound latency. - */ -PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, - unsigned output_latency) -{ - snd_input_latency = (input_latency == 0)? - PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency; - snd_output_latency = (output_latency == 0)? - PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency; - - return PJ_SUCCESS; -} - -#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ diff --git a/pjmedia/src/pjmedia/sound_legacy.c b/pjmedia/src/pjmedia/sound_legacy.c new file mode 100644 index 00000000..3500588c --- /dev/null +++ b/pjmedia/src/pjmedia/sound_legacy.c @@ -0,0 +1,284 @@ +/* $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 + */ + +/* + * This is implementation of legacy sound device API, for applications + * that still use the old/deprecated sound device API. This implementation + * uses the new Audio Device API. + * + * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more + * information. + */ + +#include <pjmedia/sound.h> +#include <pjmedia-audiodev/errno.h> +#include <pj/assert.h> + +#if PJMEDIA_HAS_LEGACY_SOUND_API + +static struct legacy_subsys +{ + pjmedia_snd_dev_info info[4]; + unsigned info_counter; + unsigned user_rec_latency; + unsigned user_play_latency; +} g_sys; + +struct pjmedia_snd_stream +{ + pj_pool_t *pool; + pjmedia_aud_stream *aud_strm; + pjmedia_snd_rec_cb user_rec_cb; + pjmedia_snd_play_cb user_play_cb; + void *user_user_data; +}; + +PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) +{ + return pjmedia_aud_subsys_init(factory); +} + +PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) +{ + return pjmedia_aud_subsys_shutdown(); +} + +PJ_DEF(int) pjmedia_snd_get_dev_count(void) +{ + return pjmedia_aud_dev_count(); +} + +PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) +{ + pjmedia_snd_dev_info *oi = &g_sys.info[g_sys.info_counter]; + pjmedia_aud_dev_info di; + + g_sys.info_counter = (g_sys.info_counter+1) % PJ_ARRAY_SIZE(g_sys.info); + + if (pjmedia_aud_dev_get_info(index, &di) != PJ_SUCCESS) + return NULL; + + pj_bzero(oi, sizeof(*oi)); + pj_ansi_strncpy(oi->name, di.name, sizeof(oi->name)); + oi->name[sizeof(oi->name)-1] = '\0'; + oi->input_count = di.input_count; + oi->output_count = di.output_count; + oi->default_samples_per_sec = di.default_samples_per_sec; + + return oi; +} + + +static pj_status_t snd_play_cb(void *user_data, + pjmedia_frame *frame) +{ + pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data; + + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + return strm->user_play_cb(strm->user_user_data, + frame->timestamp.u32.lo, + frame->buf, + frame->size); +} + +static pj_status_t snd_rec_cb(void *user_data, + pjmedia_frame *frame) +{ + pjmedia_snd_stream *strm = (pjmedia_snd_stream*)user_data; + return strm->user_rec_cb(strm->user_user_data, + frame->timestamp.u32.lo, + frame->buf, + frame->size); +} + +static pj_status_t open_stream( pjmedia_dir dir, + int rec_id, + int play_id, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + pj_pool_t *pool; + pjmedia_snd_stream *snd_strm; + pjmedia_aud_param param; + pj_status_t status; + + /* Initialize parameters */ + if (dir & PJMEDIA_DIR_CAPTURE) { + status = pjmedia_aud_dev_default_param(rec_id, ¶m); + } else { + status = pjmedia_aud_dev_default_param(play_id, ¶m); + } + if (status != PJ_SUCCESS) + return status; + + param.dir = dir; + param.rec_id = rec_id; + param.play_id = play_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; + + /* Latencies setting */ + if ((dir & PJMEDIA_DIR_CAPTURE) && g_sys.user_rec_latency) { + param.flags |= PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY; + param.input_latency_ms = g_sys.user_rec_latency; + } + if ((dir & PJMEDIA_DIR_PLAYBACK) && g_sys.user_play_latency) { + param.flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + param.output_latency_ms = g_sys.user_play_latency; + } + + /* Create sound wrapper */ + pool = pj_pool_create(pjmedia_aud_subsys_get_pool_factory(), + "legacy-snd", 512, 512, NULL); + snd_strm = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_stream); + snd_strm->pool = pool; + snd_strm->user_rec_cb = rec_cb; + snd_strm->user_play_cb = play_cb; + snd_strm->user_user_data = user_data; + + /* Create the stream */ + status = pjmedia_aud_stream_create(¶m, &snd_rec_cb, + &snd_play_cb, snd_strm, + &snd_strm->aud_strm); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + return status; + } + + *p_snd_strm = snd_strm; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + return open_stream(PJMEDIA_DIR_CAPTURE, index, PJMEDIA_AUD_INVALID_DEV, + clock_rate, channel_count, samples_per_frame, + bits_per_sample, rec_cb, NULL, + user_data, p_snd_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm ) +{ + return open_stream(PJMEDIA_DIR_PLAYBACK, PJMEDIA_AUD_INVALID_DEV, index, + clock_rate, channel_count, samples_per_frame, + bits_per_sample, NULL, play_cb, + user_data, p_snd_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, + int play_id, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned bits_per_sample, + pjmedia_snd_rec_cb rec_cb, + pjmedia_snd_play_cb play_cb, + void *user_data, + pjmedia_snd_stream **p_snd_strm) +{ + return open_stream(PJMEDIA_DIR_CAPTURE_PLAYBACK, rec_id, play_id, + clock_rate, channel_count, samples_per_frame, + bits_per_sample, rec_cb, play_cb, + user_data, p_snd_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) +{ + return pjmedia_aud_stream_start(stream->aud_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) +{ + return pjmedia_aud_stream_stop(stream->aud_strm); +} + +PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, + pjmedia_snd_stream_info *pi) +{ + pjmedia_aud_param param; + pj_status_t status; + + status = pjmedia_aud_stream_get_param(strm->aud_strm, ¶m); + if (status != PJ_SUCCESS) + return status; + + pj_bzero(pi, sizeof(*pi)); + pi->dir = param.dir; + pi->play_id = param.play_id; + pi->rec_id = param.rec_id; + pi->clock_rate = param.clock_rate; + pi->channel_count = param.channel_count; + pi->samples_per_frame = param.samples_per_frame; + pi->bits_per_sample = param.bits_per_sample; + + if (param.flags & PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY) { + pi->rec_latency = param.input_latency_ms; + } + if (param.flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY) { + pi->play_latency = param.output_latency_ms; + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) +{ + pj_status_t status; + + status = pjmedia_aud_stream_destroy(stream->aud_strm); + if (status != PJ_SUCCESS) + return status; + + pj_pool_release(stream->pool); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, + unsigned output_latency) +{ + g_sys.user_rec_latency = input_latency; + g_sys.user_play_latency = output_latency; + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_HAS_LEGACY_SOUND_API */ + diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 3a21ee5d..7117c82b 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -18,16 +18,15 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <pjmedia/sound_port.h> +#include <pjmedia/alaw_ulaw.h> #include <pjmedia/delaybuf.h> #include <pjmedia/echo.h> #include <pjmedia/errno.h> -#include <pjmedia/plc.h> #include <pj/assert.h> #include <pj/log.h> #include <pj/rand.h> #include <pj/string.h> /* pj_memset() */ -//#define SIMULATE_LOST_PCT 20 #define AEC_TAIL 128 /* default AEC length in ms */ #define AEC_SUSPEND_LIMIT 5 /* seconds of no activity */ @@ -35,119 +34,55 @@ //#define TEST_OVERFLOW_UNDERFLOW -enum -{ - PJMEDIA_PLC_ENABLED = 1, -}; - -//#define DEFAULT_OPTIONS PJMEDIA_PLC_ENABLED -#define DEFAULT_OPTIONS 0 - - struct pjmedia_snd_port { int rec_id; int play_id; - pjmedia_snd_stream *snd_stream; + pj_uint32_t aud_caps; + pjmedia_aud_param aud_param; + pjmedia_aud_stream *aud_stream; pjmedia_dir dir; pjmedia_port *port; - unsigned options; - - pjmedia_echo_state *ec_state; - unsigned aec_tail_len; - - pj_bool_t ec_suspended; - unsigned ec_suspend_count; - unsigned ec_suspend_limit; - - pjmedia_plc *plc; unsigned clock_rate; unsigned channel_count; unsigned samples_per_frame; unsigned bits_per_sample; -#if PJMEDIA_SOUND_USE_DELAYBUF - pjmedia_delay_buf *delay_buf; -#endif + /* software ec */ + pjmedia_echo_state *ec_state; + unsigned ec_options; + unsigned ec_tail_len; + pj_bool_t ec_suspended; + unsigned ec_suspend_count; + unsigned ec_suspend_limit; }; /* * The callback called by sound player when it needs more samples to be * played. */ -static pj_status_t play_cb(/* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* out */ void *output, - /* out */ unsigned size) +static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; - pjmedia_frame frame; + unsigned required_size = frame->size; pj_status_t status; - /* We're risking accessing the port without holding any mutex. - * It's possible that port is disconnected then destroyed while - * we're trying to access it. - * But in the name of performance, we'll try this approach until - * someone complains when it crashes. - */ port = snd_port->port; if (port == NULL) goto no_frame; - frame.buf = output; - frame.size = size; - frame.timestamp.u32.hi = 0; - frame.timestamp.u32.lo = timestamp; - -#if PJMEDIA_SOUND_USE_DELAYBUF - if (snd_port->delay_buf) { - status = pjmedia_delay_buf_get(snd_port->delay_buf, (pj_int16_t*)output); - if (status != PJ_SUCCESS) - pj_bzero(output, size); - - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - pjmedia_port_put_frame(port, &frame); - -#ifdef TEST_OVERFLOW_UNDERFLOW - { - static int count = 1; - if (++count % 10 == 0) { - status = pjmedia_delay_buf_get(snd_port->delay_buf, - (pj_int16_t*)output); - if (status != PJ_SUCCESS) - pj_bzero(output, size); - - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - pjmedia_port_put_frame(port, &frame); - } - } -#endif - - } -#endif - - status = pjmedia_port_get_frame(port, &frame); + status = pjmedia_port_get_frame(port, frame); if (status != PJ_SUCCESS) goto no_frame; - if (frame.type != PJMEDIA_FRAME_TYPE_AUDIO) + if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) goto no_frame; /* Must supply the required samples */ - pj_assert(frame.size == size); - -#ifdef SIMULATE_LOST_PCT - /* Simulate packet lost */ - if (pj_rand() % 100 < SIMULATE_LOST_PCT) { - PJ_LOG(4,(THIS_FILE, "Frame dropped")); - goto no_frame; - } -#endif - - if (snd_port->plc) - pjmedia_plc_save(snd_port->plc, (pj_int16_t*) output); + PJ_UNUSED_ARG(required_size); + pj_assert(frame->size == required_size); if (snd_port->ec_state) { if (snd_port->ec_suspended) { @@ -156,7 +91,7 @@ static pj_status_t play_cb(/* in */ void *user_data, PJ_LOG(4,(THIS_FILE, "EC activated")); } snd_port->ec_suspend_count = 0; - pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output); + pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); } @@ -172,22 +107,10 @@ no_frame: } if (snd_port->ec_state) { /* To maintain correct delay in EC */ - pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)output); + pjmedia_echo_playback(snd_port->ec_state, (pj_int16_t*)frame->buf); } } - /* Apply PLC */ - if (snd_port->plc) { - - pjmedia_plc_generate(snd_port->plc, (pj_int16_t*) output); -#ifdef SIMULATE_LOST_PCT - PJ_LOG(4,(THIS_FILE, "Lost frame generated")); -#endif - } else { - pj_bzero(output, size); - } - - return PJ_SUCCESS; } @@ -196,49 +119,59 @@ no_frame: * The callback called by sound recorder when it has finished capturing a * frame. */ -static pj_status_t rec_cb(/* in */ void *user_data, - /* in */ pj_uint32_t timestamp, - /* in */ void *input, - /* in*/ unsigned size) +static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) { pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; - pjmedia_frame frame; - /* We're risking accessing the port without holding any mutex. - * It's possible that port is disconnected then destroyed while - * we're trying to access it. - * But in the name of performance, we'll try this approach until - * someone complains when it crashes. - */ port = snd_port->port; if (port == NULL) return PJ_SUCCESS; /* Cancel echo */ if (snd_port->ec_state && !snd_port->ec_suspended) { - pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) input, 0); + pjmedia_echo_capture(snd_port->ec_state, (pj_int16_t*) frame->buf, 0); } -#if PJMEDIA_SOUND_USE_DELAYBUF - if (snd_port->delay_buf) { - pjmedia_delay_buf_put(snd_port->delay_buf, (pj_int16_t*)input); - } else { - frame.buf = (void*)input; - frame.size = size; - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - frame.timestamp.u32.lo = timestamp; + pjmedia_port_put_frame(port, frame); + + return PJ_SUCCESS; +} - pjmedia_port_put_frame(port, &frame); +/* + * The callback called by sound player when it needs more samples to be + * played. This version is for non-PCM data. + */ +static pj_status_t play_cb_ext(void *user_data, pjmedia_frame *frame) +{ + pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; + pjmedia_port *port = snd_port->port; + + if (port == NULL) { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; } -#else - frame.buf = (void*)input; - frame.size = size; - frame.type = PJMEDIA_FRAME_TYPE_AUDIO; - frame.timestamp.u32.lo = timestamp; - pjmedia_port_put_frame(port, &frame); -#endif + pjmedia_port_get_frame(port, frame); + + return PJ_SUCCESS; +} + + +/* + * The callback called by sound recorder when it has finished capturing a + * frame. This version is for non-PCM data. + */ +static pj_status_t rec_cb_ext(void *user_data, pjmedia_frame *frame) +{ + pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; + pjmedia_port *port; + + port = snd_port->port; + if (port == NULL) + return PJ_SUCCESS; + + pjmedia_port_put_frame(port, frame); return PJ_SUCCESS; } @@ -250,84 +183,102 @@ static pj_status_t rec_cb(/* in */ void *user_data, static pj_status_t start_sound_device( pj_pool_t *pool, pjmedia_snd_port *snd_port ) { + pjmedia_aud_rec_cb snd_rec_cb; + pjmedia_aud_play_cb snd_play_cb; + pjmedia_aud_param param_copy; pj_status_t status; /* Check if sound has been started. */ - if (snd_port->snd_stream != NULL) + if (snd_port->aud_stream != NULL) return PJ_SUCCESS; - /* Open sound stream. */ - if (snd_port->dir == PJMEDIA_DIR_CAPTURE) { - status = pjmedia_snd_open_rec( snd_port->rec_id, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &rec_cb, - snd_port, - &snd_port->snd_stream); + PJ_ASSERT_RETURN(snd_port->dir == PJMEDIA_DIR_CAPTURE || + snd_port->dir == PJMEDIA_DIR_PLAYBACK || + snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, + PJ_EBUG); - } else if (snd_port->dir == PJMEDIA_DIR_PLAYBACK) { - status = pjmedia_snd_open_player( snd_port->play_id, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &play_cb, - snd_port, - &snd_port->snd_stream); - - } else if (snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK) { - status = pjmedia_snd_open( snd_port->rec_id, - snd_port->play_id, - snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - snd_port->bits_per_sample, - &rec_cb, - &play_cb, - snd_port, - &snd_port->snd_stream); + /* Get device caps */ + if (snd_port->aud_param.dir & PJMEDIA_DIR_CAPTURE) { + pjmedia_aud_dev_info dev_info; + + status = pjmedia_aud_dev_get_info(snd_port->aud_param.rec_id, + &dev_info); + if (status != PJ_SUCCESS) + return status; + + snd_port->aud_caps = dev_info.caps; + } else { + snd_port->aud_caps = 0; + } + + /* Process EC settings */ + pj_memcpy(¶m_copy, &snd_port->aud_param, sizeof(param_copy)); + if (param_copy.flags & PJMEDIA_AUD_DEV_CAP_EC) { + /* EC is wanted */ + if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { + /* Device supports EC */ + /* Nothing to do */ + } else { + /* Device doesn't support EC, remove EC settings from + * device parameters + */ + param_copy.flags &= ~(PJMEDIA_AUD_DEV_CAP_EC | + PJMEDIA_AUD_DEV_CAP_EC_TAIL); + } + } + + /* Use different callback if format is not PCM */ + if (snd_port->aud_param.ext_fmt.id == PJMEDIA_FORMAT_L16) { + snd_rec_cb = &rec_cb; + snd_play_cb = &play_cb; } else { - pj_assert(!"Invalid dir"); - status = PJ_EBUG; + snd_rec_cb = &rec_cb_ext; + snd_play_cb = &play_cb_ext; } + /* Open the device */ + status = pjmedia_aud_stream_create(¶m_copy, + snd_rec_cb, + snd_play_cb, + snd_port, + &snd_port->aud_stream); + if (status != PJ_SUCCESS) return status; + /* Inactivity limit before EC is suspended. */ + snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT * + (snd_port->clock_rate / + snd_port->samples_per_frame); -#ifdef SIMULATE_LOST_PCT - snd_port->options |= PJMEDIA_PLC_ENABLED; -#endif - - /* If we have player components, allocate buffer to save the last - * frame played to the speaker. The last frame is used for packet - * lost concealment (PLC) algorithm. + /* Create software EC if parameter specifies EC but device + * doesn't support EC. Only do this if the format is PCM! */ - if ((snd_port->dir & PJMEDIA_DIR_PLAYBACK) && - (snd_port->options & PJMEDIA_PLC_ENABLED)) + if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC) && + (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC)==0 && + param_copy.ext_fmt.id == PJMEDIA_FORMAT_PCM) { - status = pjmedia_plc_create(pool, snd_port->clock_rate, - snd_port->samples_per_frame * - snd_port->channel_count, - 0, &snd_port->plc); + if ((snd_port->aud_param.flags & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { + snd_port->aud_param.flags |= PJMEDIA_AUD_DEV_CAP_EC_TAIL; + snd_port->aud_param.ec_tail_ms = AEC_TAIL; + PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms", + snd_port->aud_param.ec_tail_ms)); + } + + status = pjmedia_snd_port_set_ec(snd_port, pool, + snd_port->aud_param.ec_tail_ms, 0); if (status != PJ_SUCCESS) { - PJ_LOG(4,(THIS_FILE, "Unable to create PLC")); - snd_port->plc = NULL; + pjmedia_aud_stream_destroy(snd_port->aud_stream); + snd_port->aud_stream = NULL; + return status; } } - /* Inactivity limit before EC is suspended. */ - snd_port->ec_suspend_limit = AEC_SUSPEND_LIMIT * - (snd_port->clock_rate / - snd_port->samples_per_frame); - /* Start sound stream. */ - status = pjmedia_snd_stream_start(snd_port->snd_stream); + status = pjmedia_aud_stream_start(snd_port->aud_stream); if (status != PJ_SUCCESS) { - pjmedia_snd_stream_close(snd_port->snd_stream); - snd_port->snd_stream = NULL; + pjmedia_aud_stream_destroy(snd_port->aud_stream); + snd_port->aud_stream = NULL; return status; } @@ -342,10 +293,10 @@ static pj_status_t start_sound_device( pj_pool_t *pool, static pj_status_t stop_sound_device( pjmedia_snd_port *snd_port ) { /* Check if we have sound stream device. */ - if (snd_port->snd_stream) { - pjmedia_snd_stream_stop(snd_port->snd_stream); - pjmedia_snd_stream_close(snd_port->snd_stream); - snd_port->snd_stream = NULL; + if (snd_port->aud_stream) { + pjmedia_aud_stream_stop(snd_port->aud_stream); + pjmedia_aud_stream_destroy(snd_port->aud_stream); + snd_port->aud_stream = NULL; } /* Destroy AEC */ @@ -371,47 +322,24 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create( pj_pool_t *pool, unsigned options, pjmedia_snd_port **p_port) { - pjmedia_snd_port *snd_port; - - PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); - - snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); - PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); + pjmedia_aud_param param; + pj_status_t status; - snd_port->rec_id = rec_id; - snd_port->play_id = play_id; - snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; - snd_port->options = options | DEFAULT_OPTIONS; - snd_port->clock_rate = clock_rate; - snd_port->channel_count = channel_count; - snd_port->samples_per_frame = samples_per_frame; - snd_port->bits_per_sample = bits_per_sample; - -#if PJMEDIA_SOUND_USE_DELAYBUF - do { - pj_status_t status; - unsigned ptime; - - ptime = samples_per_frame * 1000 / (clock_rate * channel_count); - - status = pjmedia_delay_buf_create(pool, "snd_buff", - clock_rate, samples_per_frame, - channel_count, - PJMEDIA_SOUND_BUFFER_COUNT * ptime, - 0, &snd_port->delay_buf); - PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); - } while (0); -#endif + PJ_UNUSED_ARG(options); - *p_port = snd_port; + status = pjmedia_aud_dev_default_param(rec_id, ¶m); + if (status != PJ_SUCCESS) + return status; + param.dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param.rec_id = rec_id; + param.play_id = play_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; - /* Start sound device immediately. - * If there's no port connected, the sound callback will return - * empty signal. - */ - return start_sound_device( pool, snd_port ); - + return pjmedia_snd_port_create2(pool, ¶m, p_port); } /* @@ -426,28 +354,23 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_rec( pj_pool_t *pool, unsigned options, pjmedia_snd_port **p_port) { - pjmedia_snd_port *snd_port; - - PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + pjmedia_aud_param param; + pj_status_t status; - snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); - PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); + PJ_UNUSED_ARG(options); - snd_port->rec_id = dev_id; - snd_port->dir = PJMEDIA_DIR_CAPTURE; - snd_port->options = options | DEFAULT_OPTIONS; - snd_port->clock_rate = clock_rate; - snd_port->channel_count = channel_count; - snd_port->samples_per_frame = samples_per_frame; - snd_port->bits_per_sample = bits_per_sample; + status = pjmedia_aud_dev_default_param(dev_id, ¶m); + if (status != PJ_SUCCESS) + return status; - *p_port = snd_port; + param.dir = PJMEDIA_DIR_CAPTURE; + param.rec_id = dev_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; - /* Start sound device immediately. - * If there's no port connected, the sound callback will return - * empty signal. - */ - return start_sound_device( pool, snd_port ); + return pjmedia_snd_port_create2(pool, ¶m, p_port); } @@ -463,28 +386,63 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create_player( pj_pool_t *pool, unsigned options, pjmedia_snd_port **p_port) { + pjmedia_aud_param param; + pj_status_t status; + + PJ_UNUSED_ARG(options); + + status = pjmedia_aud_dev_default_param(dev_id, ¶m); + if (status != PJ_SUCCESS) + return status; + + param.dir = PJMEDIA_DIR_PLAYBACK; + param.play_id = dev_id; + param.clock_rate = clock_rate; + param.channel_count = channel_count; + param.samples_per_frame = samples_per_frame; + param.bits_per_sample = bits_per_sample; + + return pjmedia_snd_port_create2(pool, ¶m, p_port); +} + + +/* + * Create sound port. + */ +PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, + const pjmedia_aud_param *prm, + pjmedia_snd_port **p_port) +{ pjmedia_snd_port *snd_port; + pj_status_t status; - PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); + PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL); snd_port = PJ_POOL_ZALLOC_T(pool, pjmedia_snd_port); PJ_ASSERT_RETURN(snd_port, PJ_ENOMEM); - snd_port->play_id = dev_id; - snd_port->dir = PJMEDIA_DIR_PLAYBACK; - snd_port->options = options | DEFAULT_OPTIONS; - snd_port->clock_rate = clock_rate; - snd_port->channel_count = channel_count; - snd_port->samples_per_frame = samples_per_frame; - snd_port->bits_per_sample = bits_per_sample; - - *p_port = snd_port; - + snd_port->dir = prm->dir; + snd_port->rec_id = prm->rec_id; + snd_port->play_id = prm->play_id; + snd_port->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + snd_port->clock_rate = prm->clock_rate; + snd_port->channel_count = prm->channel_count; + snd_port->samples_per_frame = prm->samples_per_frame; + snd_port->bits_per_sample = prm->bits_per_sample; + pj_memcpy(&snd_port->aud_param, prm, sizeof(*prm)); + /* Start sound device immediately. * If there's no port connected, the sound callback will return * empty signal. */ - return start_sound_device( pool, snd_port ); + status = start_sound_device( pool, snd_port ); + if (status != PJ_SUCCESS) { + pjmedia_snd_port_destroy(snd_port); + return status; + } + + *p_port = snd_port; + return PJ_SUCCESS; } @@ -502,23 +460,23 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_destroy(pjmedia_snd_port *snd_port) /* * Retrieve the sound stream associated by this sound device port. */ -PJ_DEF(pjmedia_snd_stream*) pjmedia_snd_port_get_snd_stream( +PJ_DEF(pjmedia_aud_stream*) pjmedia_snd_port_get_snd_stream( pjmedia_snd_port *snd_port) { PJ_ASSERT_RETURN(snd_port, NULL); - return snd_port->snd_stream; + return snd_port->aud_stream; } /* - * Enable AEC + * Change EC settings. */ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, pj_pool_t *pool, unsigned tail_ms, unsigned options) { - pjmedia_snd_stream_info si; + pjmedia_aud_param prm; pj_status_t status; /* Sound must be opened in full-duplex mode */ @@ -526,43 +484,101 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_set_ec( pjmedia_snd_port *snd_port, snd_port->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK, PJ_EINVALIDOP); - /* Sound port must have 16bits per sample */ - PJ_ASSERT_RETURN(snd_port->bits_per_sample == 16, - PJ_EINVALIDOP); + /* Determine whether we use device or software EC */ + if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { + /* We use device EC */ + pj_bool_t ec_enabled; - /* Destroy AEC */ - if (snd_port->ec_state) { - pjmedia_echo_destroy(snd_port->ec_state); - snd_port->ec_state = NULL; - } + /* Query EC status */ + status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &ec_enabled); + if (status != PJ_SUCCESS) + return status; - snd_port->aec_tail_len = tail_ms; + if (tail_ms != 0) { + /* Change EC setting */ - if (tail_ms != 0) { - unsigned delay_ms; + if (!ec_enabled) { + /* Enable EC first */ + pj_bool_t value = PJ_TRUE; + status = pjmedia_aud_stream_set_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &value); + if (status != PJ_SUCCESS) + return status; + } - status = pjmedia_snd_stream_get_info(snd_port->snd_stream, &si); - if (status != PJ_SUCCESS) - si.rec_latency = si.play_latency = 0; - - //No need to add input latency in the latency calculation, - //since actual input latency should be zero. - //delay_ms = (si.rec_latency + si.play_latency) * 1000 / - // snd_port->clock_rate; - delay_ms = si.play_latency * 1000 / snd_port->clock_rate; - status = pjmedia_echo_create2(pool, snd_port->clock_rate, - snd_port->channel_count, - snd_port->samples_per_frame, - tail_ms, delay_ms, - options, &snd_port->ec_state); + if ((snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL)==0) { + /* Device does not support setting EC tail */ + return PJMEDIA_EAUD_INVCAP; + } + + return pjmedia_aud_stream_set_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC_TAIL, + &tail_ms); + + } else if (ec_enabled) { + /* Disable EC */ + pj_bool_t value = PJ_FALSE; + return pjmedia_aud_stream_set_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &value); + } else { + /* Request to disable EC but EC has been disabled */ + /* Do nothing */ + return PJ_SUCCESS; + } + + } else { + /* We use software EC */ + + /* Check if there is change in parameters */ + if (tail_ms==snd_port->ec_tail_len && options==snd_port->ec_options) { + PJ_LOG(5,(THIS_FILE, "pjmedia_snd_port_set_ec() ignored, no " + "change in settings")); + return PJ_SUCCESS; + } + + status = pjmedia_aud_stream_get_param(snd_port->aud_stream, &prm); if (status != PJ_SUCCESS) + return status; + + /* Audio stream must be in PCM format */ + PJ_ASSERT_RETURN(prm.ext_fmt.id == PJMEDIA_FORMAT_PCM, + PJ_EINVALIDOP); + + /* Destroy AEC */ + if (snd_port->ec_state) { + pjmedia_echo_destroy(snd_port->ec_state); snd_port->ec_state = NULL; - else - snd_port->ec_suspended = PJ_FALSE; - } else { - PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " - "sound port")); - status = PJ_SUCCESS; + } + + if (tail_ms != 0) { + unsigned delay_ms; + + //No need to add input latency in the latency calculation, + //since actual input latency should be zero. + //delay_ms = (si.rec_latency + si.play_latency) * 1000 / + // snd_port->clock_rate; + delay_ms = prm.output_latency_ms; + status = pjmedia_echo_create2(pool, snd_port->clock_rate, + snd_port->channel_count, + snd_port->samples_per_frame, + tail_ms, delay_ms, + options, &snd_port->ec_state); + if (status != PJ_SUCCESS) + snd_port->ec_state = NULL; + else + snd_port->ec_suspended = PJ_FALSE; + } else { + PJ_LOG(4,(THIS_FILE, "Echo canceller is now disabled in the " + "sound port")); + status = PJ_SUCCESS; + } + + snd_port->ec_options = options; + snd_port->ec_tail_len = tail_ms; } return status; @@ -574,12 +590,42 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port, unsigned *p_length) { PJ_ASSERT_RETURN(snd_port && p_length, PJ_EINVAL); - *p_length = snd_port->ec_state ? snd_port->aec_tail_len : 0; + + /* Determine whether we use device or software EC */ + if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC) { + /* We use device EC */ + pj_bool_t ec_enabled; + pj_status_t status; + + /* Query EC status */ + status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC, + &ec_enabled); + if (status != PJ_SUCCESS) + return status; + + if (!ec_enabled) { + *p_length = 0; + } else if (snd_port->aud_caps & PJMEDIA_AUD_DEV_CAP_EC_TAIL) { + /* Get device EC tail */ + status = pjmedia_aud_stream_get_cap(snd_port->aud_stream, + PJMEDIA_AUD_DEV_CAP_EC_TAIL, + p_length); + if (status != PJ_SUCCESS) + return status; + } else { + /* Just use default */ + *p_length = AEC_TAIL; + } + + } else { + /* We use software EC */ + *p_length = snd_port->ec_state ? snd_port->ec_tail_len : 0; + } return PJ_SUCCESS; } - /* * Connect a port. */ diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c index f1d01c27..1e289e98 100644 --- a/pjmedia/src/pjmedia/stream.c +++ b/pjmedia/src/pjmedia/stream.c @@ -408,6 +408,116 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) } +/* The other version of get_frame callback used when stream port format + * is non linear PCM. + */ +static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame) +{ + pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata; + pjmedia_channel *channel = stream->dec; + pjmedia_frame_ext *f = (pjmedia_frame_ext*)frame; + unsigned samples_per_frame, samples_required; + pj_status_t status; + + /* Return no frame if channel is paused */ + if (channel->paused) { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + + /* Repeat get frame from the jitter buffer and decode the frame + * until we have enough frames according to codec's ptime. + */ + + samples_required = stream->port.info.samples_per_frame; + samples_per_frame = stream->codec_param.info.frm_ptime * + stream->codec_param.info.clock_rate * + stream->codec_param.info.channel_cnt / + 1000; + + pj_bzero(f, sizeof(pjmedia_frame_ext)); + f->base.type = PJMEDIA_FRAME_TYPE_EXTENDED; + + while (f->samples_cnt < samples_required) { + char frame_type; + pj_size_t frame_size; + pj_uint32_t bit_info; + + /* Lock jitter buffer mutex first */ + pj_mutex_lock( stream->jb_mutex ); + + /* Get frame from jitter buffer. */ + pjmedia_jbuf_get_frame2(stream->jb, channel->out_pkt, &frame_size, + &frame_type, &bit_info); + + /* Unlock jitter buffer mutex. */ + pj_mutex_unlock( stream->jb_mutex ); + + if (frame_type == PJMEDIA_JB_NORMAL_FRAME) { + /* Got "NORMAL" frame from jitter buffer */ + pjmedia_frame frame_in; + + /* Decode */ + frame_in.buf = channel->out_pkt; + frame_in.size = frame_size; + frame_in.bit_info = bit_info; + frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO; + + status = stream->codec->op->decode( stream->codec, &frame_in, + 0, frame); + if (status != PJ_SUCCESS) { + LOGERR_((port->info.name.ptr, "codec decode() error", + status)); + pjmedia_frame_ext_append_subframe(f, NULL, 0, + (pj_uint16_t)samples_per_frame); + } + } else { + status = (*stream->codec->op->recover)(stream->codec, + 0, frame); + if (status != PJ_SUCCESS) { + pjmedia_frame_ext_append_subframe(f, NULL, 0, + (pj_uint16_t)samples_per_frame); + } + + if (frame_type == PJMEDIA_JB_MISSING_FRAME) { + PJ_LOG(5,(stream->port.info.name.ptr, "Frame lost!")); + } else if (frame_type == PJMEDIA_JB_ZERO_EMPTY_FRAME) { + /* Jitter buffer is empty. Check if this is the first "empty" + * state. + */ + if (frame_type != stream->jb_last_frm) { + pjmedia_jb_state jb_state; + + /* Report the state of jitter buffer */ + pjmedia_jbuf_get_state(stream->jb, &jb_state); + PJ_LOG(5,(stream->port.info.name.ptr, + "Jitter buffer empty (prefetch=%d)", + jb_state.prefetch)); + } + } else { + pjmedia_jb_state jb_state; + + /* It can only be PJMEDIA_JB_ZERO_PREFETCH frame */ + pj_assert(frame_type == PJMEDIA_JB_ZERO_PREFETCH_FRAME); + + /* Get the state of jitter buffer */ + pjmedia_jbuf_get_state(stream->jb, &jb_state); + + if (stream->jb_last_frm != frame_type) { + PJ_LOG(5,(stream->port.info.name.ptr, + "Jitter buffer is bufferring (prefetch=%d)", + jb_state.prefetch)); + } + } + } + + stream->jb_last_frm = frame_type; + } + + return PJ_SUCCESS; +} + + /* * Transmit DTMF */ @@ -686,6 +796,9 @@ static pj_status_t put_frame_imp( pjmedia_port *port, /* Number of samples in the frame */ if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) ts_len = (frame->size >> 1) / stream->codec_param.info.channel_cnt; + else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) + ts_len = stream->port.info.samples_per_frame / + stream->port.info.channel_count; else ts_len = 0; @@ -752,6 +865,7 @@ static pj_status_t put_frame_imp( pjmedia_port *port, */ } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO && frame->buf == NULL && + stream->port.info.format.id == PJMEDIA_FORMAT_L16 && (stream->dir & PJMEDIA_DIR_ENCODING) && stream->codec_param.info.frm_ptime * stream->codec_param.info.channel_cnt * @@ -1483,9 +1597,18 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name); stream->port.info.clock_rate = info->fmt.clock_rate; stream->port.info.channel_count = info->fmt.channel_cnt; + stream->port.info.format.id = info->param->info.fmt_id; stream->port.port_data.pdata = stream; - stream->port.put_frame = &put_frame; - stream->port.get_frame = &get_frame; + if (stream->port.info.format.id == PJMEDIA_FORMAT_L16) { + stream->port.put_frame = &put_frame; + stream->port.get_frame = &get_frame; + } else { + stream->port.info.format.bitrate = info->param->info.avg_bps; + stream->port.info.format.vad = (info->param->setting.vad != 0); + + stream->port.put_frame = &put_frame; + stream->port.get_frame = &get_frame_ext; + } /* Init stream: */ diff --git a/pjmedia/src/pjmedia/symbian_sound.cpp b/pjmedia/src/pjmedia/symbian_sound.cpp deleted file mode 100644 index b7c92f9f..00000000 --- a/pjmedia/src/pjmedia/symbian_sound.cpp +++ /dev/null @@ -1,944 +0,0 @@ -/* $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/sound.h> -#include <pjmedia/errno.h> -#include <pj/assert.h> -#include <pj/log.h> -#include <pj/os.h> - - -/* - * 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 "symbian_sound.cpp" -#define BYTES_PER_SAMPLE 2 -#define POOL_NAME "SymbianSound" -#define POOL_SIZE 512 -#define POOL_INC 512 - -static pjmedia_snd_dev_info symbian_snd_dev_info = -{ - "Symbian Sound Device", - 1, - 1, - 8000 -}; - -class CPjAudioInputEngine; -class CPjAudioOutputEngine; - -/* - * PJMEDIA Sound Stream instance - */ -struct pjmedia_snd_stream -{ - // Pool - pj_pool_t *pool; - - // Common settings. - pjmedia_dir dir; - unsigned clock_rate; - unsigned channel_count; - unsigned samples_per_frame; - - // Input stream - CPjAudioInputEngine *inEngine; - - // Output stream - CPjAudioOutputEngine *outEngine; -}; - -static pj_pool_factory *snd_pool_factory; - - -/* - * 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(pjmedia_snd_stream *parent_strm, - pjmedia_snd_rec_cb rec_cb, - void *user_data); - - static CPjAudioInputEngine *NewLC(pjmedia_snd_stream *parent_strm, - pjmedia_snd_rec_cb rec_cb, - void *user_data); - - pj_status_t StartRecord(); - void Stop(); - -private: - State state_; - pjmedia_snd_stream *parentStrm_; - pjmedia_snd_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_; - - // in some SymbianOS (e.g: OSv9.1), sometimes recorded size != requested framesize - // so let's provide a buffer to make sure the rec callback returns framesize as requested. - TUint8 *frameRecBuf_; - TInt frameRecBufLen_; - - CPjAudioInputEngine(pjmedia_snd_stream *parent_strm, - pjmedia_snd_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(pjmedia_snd_stream *parent_strm, - pjmedia_snd_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->samples_per_frame * parent_strm->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(pjmedia_snd_stream *parent, - pjmedia_snd_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(pjmedia_snd_stream *parent, - pjmedia_snd_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_->channel_count); - iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->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_->clock_rate, - parentStrm_->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_) { - // Call the callback. - recCb_(userData_, timeStamp_, frameRecBuf_, frameLen_); - // Increment timestamp. - timeStamp_ += parentStrm_->samples_per_frame; - - frameRecBufLen_ -= frameLen_; - pj_memmove(frameRecBuf_, frameRecBuf_+frameLen_, frameRecBufLen_); - } - } else { - // Call the callback. - recCb_(userData_, timeStamp_, (void*) aBuffer.Ptr(), aBuffer.Size()); - // Increment timestamp. - timeStamp_ += parentStrm_->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(pjmedia_snd_stream *parent_strm, - pjmedia_snd_play_cb play_cb, - void *user_data); - - static CPjAudioOutputEngine *NewLC(pjmedia_snd_stream *parent_strm, - pjmedia_snd_play_cb rec_cb, - void *user_data); - - pj_status_t StartPlay(); - void Stop(); - -private: - State state_; - pjmedia_snd_stream *parentStrm_; - pjmedia_snd_play_cb playCb_; - void *userData_; - CMdaAudioOutputStream *iOutputStream_; - TUint8 *frameBuf_; - unsigned frameBufSize_; - TPtrC8 frame_; - TInt lastError_; - unsigned timestamp_; - - CPjAudioOutputEngine(pjmedia_snd_stream *parent_strm, - pjmedia_snd_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(pjmedia_snd_stream *parent_strm, - pjmedia_snd_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_->samples_per_frame * - parentStrm_->channel_count * - BYTES_PER_SAMPLE; - frameBuf_ = new TUint8[frameBufSize_]; -} - -CPjAudioOutputEngine::~CPjAudioOutputEngine() -{ - Stop(); - delete [] frameBuf_; -} - -CPjAudioOutputEngine * -CPjAudioOutputEngine::NewLC(pjmedia_snd_stream *parent_strm, - pjmedia_snd_play_cb rec_cb, - void *user_data) -{ - CPjAudioOutputEngine* self = new (ELeave) CPjAudioOutputEngine(parent_strm, - rec_cb, - user_data); - CleanupStack::PushL(self); - self->ConstructL(); - return self; -} - -CPjAudioOutputEngine * -CPjAudioOutputEngine::NewL(pjmedia_snd_stream *parent_strm, - pjmedia_snd_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_->channel_count); - iStreamSettings.iSampleRate = get_clock_rate_cap(parentStrm_->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_->clock_rate, - parentStrm_->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_->channel_count); - iSettings.iSampleRate = get_clock_rate_cap(parentStrm_->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. - pj_status_t status; - status = playCb_(this->userData_, timestamp_, frameBuf_, - frameBufSize_); - 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. - pj_status_t status; - status = playCb_(this->userData_, timestamp_, frameBuf_, - frameBufSize_); - 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); - } -} - - -////////////////////////////////////////////////////////////////////////////// -// - - -/* - * Initialize sound subsystem. - */ -PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) -{ - snd_pool_factory = factory; - return PJ_SUCCESS; -} - -/* - * Get device count. - */ -PJ_DEF(int) pjmedia_snd_get_dev_count(void) -{ - /* Always return 1 */ - return 1; -} - -/* - * Get device info. - */ -PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) -{ - /* Always return the default sound device */ - if (index == (unsigned)-1) - index = 0; - - PJ_ASSERT_RETURN(index==0, NULL); - return &symbian_snd_dev_info; -} - - - -/* - * Open sound recorder stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *strm; - - if (index==-1) index = 0; - - PJ_ASSERT_RETURN(index == 0, PJ_EINVAL); - PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame && - bits_per_sample && rec_cb && p_snd_strm, PJ_EINVAL); - - pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, - NULL); - if (!pool) - return PJ_ENOMEM; - - strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, - sizeof(pjmedia_snd_stream)); - strm->dir = PJMEDIA_DIR_CAPTURE; - strm->pool = pool; - strm->clock_rate = clock_rate; - strm->channel_count = channel_count; - strm->samples_per_frame = samples_per_frame; - - PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); - PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL); - PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL); - - // Create the input stream. - TRAPD(err, strm->inEngine = CPjAudioInputEngine::NewL(strm, rec_cb, - user_data)); - if (err != KErrNone) { - pj_pool_release(pool); - return PJ_RETURN_OS_ERROR(err); - } - - // Done. - *p_snd_strm = strm; - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm ) -{ - pj_pool_t *pool; - pjmedia_snd_stream *strm; - - if (index == -1) index = 0; - - PJ_ASSERT_RETURN(index == 0, PJ_EINVAL); - PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame && - bits_per_sample && play_cb && p_snd_strm, PJ_EINVAL); - - pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, - NULL); - if (!pool) - return PJ_ENOMEM; - - strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, - sizeof(pjmedia_snd_stream)); - strm->dir = PJMEDIA_DIR_PLAYBACK; - strm->pool = pool; - strm->clock_rate = clock_rate; - strm->channel_count = channel_count; - strm->samples_per_frame = samples_per_frame; - - PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); - PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL); - PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL); - - // Create the output stream. - TRAPD(err, strm->outEngine = CPjAudioOutputEngine::NewL(strm, play_cb, - user_data)); - if (err != KErrNone) { - pj_pool_release(pool); - return PJ_RETURN_OS_ERROR(err); - } - - // Done. - *p_snd_strm = strm; - return PJ_SUCCESS; -} - -PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *strm; - - if (rec_id == -1) rec_id = 0; - if (play_id == -1) play_id = 0; - - PJ_ASSERT_RETURN(rec_id == 0 && play_id == 0, PJ_EINVAL); - PJ_ASSERT_RETURN(clock_rate && channel_count && samples_per_frame && - bits_per_sample && rec_cb && play_cb && p_snd_strm, - PJ_EINVAL); - - pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, - NULL); - if (!pool) - return PJ_ENOMEM; - - strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, - sizeof(pjmedia_snd_stream)); - strm->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; - strm->pool = pool; - strm->clock_rate = clock_rate; - strm->channel_count = channel_count; - strm->samples_per_frame = samples_per_frame; - - PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); - PJ_ASSERT_RETURN(get_clock_rate_cap(clock_rate) != 0, PJ_EINVAL); - PJ_ASSERT_RETURN(get_channel_cap(channel_count) != 0, PJ_EINVAL); - - // Create the output stream. - TRAPD(err, strm->outEngine = CPjAudioOutputEngine::NewL(strm, play_cb, - user_data)); - if (err != KErrNone) { - pj_pool_release(pool); - return PJ_RETURN_OS_ERROR(err); - } - - // Create the input stream. - TRAPD(err1, strm->inEngine = CPjAudioInputEngine::NewL(strm, rec_cb, - user_data)); - if (err1 != KErrNone) { - strm->inEngine = NULL; - delete strm->outEngine; - strm->outEngine = NULL; - pj_pool_release(pool); - return PJ_RETURN_OS_ERROR(err1); - } - - // Done. - *p_snd_strm = strm; - return PJ_SUCCESS; -} - -/* - * Get stream info. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, - pjmedia_snd_stream_info *pi) -{ - PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); - - pj_bzero(pi, sizeof(*pi)); - pi->dir = strm->dir; - pi->play_id = 0; - pi->rec_id = 0; - pi->clock_rate = strm->clock_rate; - pi->channel_count = strm->channel_count; - pi->samples_per_frame = strm->samples_per_frame; - pi->bits_per_sample = BYTES_PER_SAMPLE * 8; - // Symbian uses 4096 bytes buffer (~2048 samples/256 ms) for PCM rec & play. - // The latencies below are rounded up to be a multiplication of 80. - pi->rec_latency = 2080; - pi->play_latency = 2080; - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) -{ - pj_status_t status; - - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->outEngine) { - status = stream->outEngine->StartPlay(); - if (status != PJ_SUCCESS) - return status; - } - - if (stream->inEngine) { - status = stream->inEngine->StartRecord(); - if (status != PJ_SUCCESS) - return status; - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) -{ - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->inEngine) { - stream->inEngine->Stop(); - } - - if (stream->outEngine) { - stream->outEngine->Stop(); - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) -{ - pj_pool_t *pool; - - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->inEngine) { - delete stream->inEngine; - stream->inEngine = NULL; - } - - if (stream->outEngine) { - delete stream->outEngine; - stream->outEngine = NULL; - } - - pool = stream->pool; - if (pool) { - stream->pool = NULL; - pj_pool_release(pool); - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) -{ - /* Nothing to do */ - return PJ_SUCCESS; -} - - -/* - * Set sound latency. - */ -PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, - unsigned output_latency) -{ - /* Nothing to do */ - PJ_UNUSED_ARG(input_latency); - PJ_UNUSED_ARG(output_latency); - return PJ_SUCCESS; -} diff --git a/pjmedia/src/pjmedia/symbian_sound_aps.cpp b/pjmedia/src/pjmedia/symbian_sound_aps.cpp deleted file mode 100644 index ea419c51..00000000 --- a/pjmedia/src/pjmedia/symbian_sound_aps.cpp +++ /dev/null @@ -1,920 +0,0 @@ -/* $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/sound.h> -#include <pjmedia/alaw_ulaw.h> -#include <pjmedia/errno.h> -#include <pjmedia/symbian_sound_aps.h> -#include <pj/assert.h> -#include <pj/log.h> -#include <pj/math.h> -#include <pj/os.h> - -#include <e32msgqueue.h> -#include <sounddevice.h> -#include <APSClientSession.h> - -////////////////////////////////////////////////////////////////////////////// -// - -#define THIS_FILE "symbian_sound_aps.cpp" - -#define BYTES_PER_SAMPLE 2 -#define POOL_NAME "SymbianSoundAps" -#define POOL_SIZE 512 -#define POOL_INC 512 - -#if 1 -# define TRACE_(st) PJ_LOG(3, st) -#else -# define TRACE_(st) -#endif - -static pjmedia_snd_dev_info symbian_snd_dev_info = -{ - "Symbian Sound Device (APS)", - 1, - 1, - 8000 -}; - -/* App UID to open global APS queues to communicate with the APS server. */ -extern TPtrC APP_UID; - -/* Default setting for loudspeaker */ -static pj_bool_t act_loudspeaker = PJ_FALSE; - -/* Forward declaration of CPjAudioEngine */ -class CPjAudioEngine; - -/* - * PJMEDIA Sound Stream instance - */ -struct pjmedia_snd_stream -{ - // Pool - pj_pool_t *pool; - - // Common settings. - pjmedia_dir dir; - unsigned clock_rate; - unsigned channel_count; - unsigned samples_per_frame; - - // Audio engine - CPjAudioEngine *engine; -}; - -static pj_pool_factory *snd_pool_factory; - - -/* - * 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)); -} - -////////////////////////////////////////////////////////////////////////////// -// - -/** - * Abstract class for handler of callbacks from APS client. - */ -class MQueueHandlerObserver -{ -public: - virtual void InputStreamInitialized(const TInt aStatus) = 0; - virtual void OutputStreamInitialized(const TInt aStatus) = 0; - virtual void NotifyError(const TInt aError) = 0; - - virtual void RecCb(TAPSCommBuffer &buffer) = 0; - virtual void PlayCb(TAPSCommBuffer &buffer) = 0; -}; - -/** - * 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, - TQueueHandlerType aType) - { - CQueueHandler* self = new (ELeave) CQueueHandler(aObserver, aQ, 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, - TQueueHandlerType aType) - : CActive(CActive::EPriorityHigh), - iQ(aQ), 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); - } - break; - - // Callbacks from the APS main thread - case EPlayCommQueue: - switch (buffer.iCommand) { - case EAPSPlayData: - if (buffer.iStatus == KErrUnderflow) { - iObserver->PlayCb(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: - if (buffer.iStatus == KErrNone) { - iObserver->InputStreamInitialized(buffer.iStatus); - break; - } - case EAPSRecordData: - iObserver->NotifyError(buffer.iStatus); - break; - default: - break; - } - break; - - default: - break; - } - - // issue next request - iQ->NotifyDataAvailable(iStatus); - SetActive(); - } - - // Data - RMsgQueue<TAPSCommBuffer> *iQ; // (not owned) - MQueueHandlerObserver *iObserver; // (not owned) - TQueueHandlerType iType; -}; - - -/* - * Implementation: Symbian Input & Output Stream. - */ -class CPjAudioEngine : public CBase, MQueueHandlerObserver -{ -public: - enum State - { - STATE_NULL, - STATE_READY, - STATE_STREAMING - }; - - ~CPjAudioEngine(); - - static CPjAudioEngine *NewL(pjmedia_snd_stream *parent_strm, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data); - - TInt StartL(); - void Stop(); - - TInt ActivateSpeaker(TBool active); - -private: - CPjAudioEngine(pjmedia_snd_stream *parent_strm, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data); - 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); - - virtual void RecCb(TAPSCommBuffer &buffer); - virtual void PlayCb(TAPSCommBuffer &buffer); - - State state_; - pjmedia_snd_stream *parentStrm_; - pjmedia_snd_rec_cb recCb_; - pjmedia_snd_play_cb playCb_; - void *userData_; - pj_uint32_t TsPlay_; - pj_uint32_t TsRec_; - - RAPSSession iSession; - TAPSInitSettings iSettings; - RMsgQueue<TAPSCommBuffer> iReadQ; - RMsgQueue<TAPSCommBuffer> iReadCommQ; - RMsgQueue<TAPSCommBuffer> iWriteQ; - RMsgQueue<TAPSCommBuffer> iWriteCommQ; - - CQueueHandler *iPlayCommHandler; - CQueueHandler *iRecCommHandler; - CQueueHandler *iRecHandler; - - static pj_uint8_t aps_samples_per_frame; - - pj_int16_t *play_buf; - pj_uint16_t play_buf_len; - pj_uint16_t play_buf_start; - pj_int16_t *rec_buf; - pj_uint16_t rec_buf_len; -}; - - -pj_uint8_t CPjAudioEngine::aps_samples_per_frame = 0; - - -CPjAudioEngine* CPjAudioEngine::NewL(pjmedia_snd_stream *parent_strm, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data) -{ - CPjAudioEngine* self = new (ELeave) CPjAudioEngine(parent_strm, - rec_cb, play_cb, - user_data); - CleanupStack::PushL(self); - self->ConstructL(); - CleanupStack::Pop(self); - return self; -} - -CPjAudioEngine::CPjAudioEngine(pjmedia_snd_stream *parent_strm, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data) - : state_(STATE_NULL), - parentStrm_(parent_strm), - recCb_(rec_cb), - playCb_(play_cb), - userData_(user_data), - iPlayCommHandler(0), - iRecCommHandler(0), - iRecHandler(0) -{ -} - -CPjAudioEngine::~CPjAudioEngine() -{ - Stop(); - - delete iPlayCommHandler; - iPlayCommHandler = NULL; - delete iRecCommHandler; - iRecCommHandler = NULL; - - // 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_->dir != PJMEDIA_DIR_PLAYBACK) { - iReadQ.Close(); - iReadCommQ.Close(); - } - iWriteQ.Close(); - iWriteCommQ.Close(); - } -} - -TInt CPjAudioEngine::InitPlayL() -{ - if (state_ == STATE_STREAMING || state_ == STATE_READY) - return 0; - - TInt err = iSession.InitializePlayer(iSettings); - if (err != KErrNone) { - snd_perror("Failed to initialize player", err); - return err; - } - - // Open message queues for the output stream - TBuf<128> buf2 = iSettings.iGlobal; - buf2.Append(_L("PlayQueue")); - TBuf<128> buf3 = iSettings.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, - CQueueHandler::EPlayCommQueue); - - // Start observing APS callbacks on output stream message queue - iPlayCommHandler->Start(); - - return 0; -} - -TInt CPjAudioEngine::InitRecL() -{ - if (state_ == STATE_STREAMING || state_ == STATE_READY) - return 0; - - // Initialize input stream device - TInt err = iSession.InitializeRecorder(iSettings); - if (err != KErrNone) { - snd_perror("Failed to initialize recorder", err); - return err; - } - - TBuf<128> buf1 = iSettings.iGlobal; - buf1.Append(_L("RecordQueue")); - TBuf<128> buf4 = iSettings.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 - iRecCommHandler = CQueueHandler::NewL(this, - &iReadCommQ, - CQueueHandler::ERecordCommQueue); - - // Start observing APS callbacks from on input stream message queue - iRecCommHandler->Start(); - - return 0; -} - -TInt CPjAudioEngine::StartL() -{ - TInt err = iSession.Connect(); - if (err != KErrNone && err != KErrAlreadyExists) - return err; - - if (state_ == STATE_READY) - return StartStreamL(); - - // 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. - return InitPlayL(); -} - -void CPjAudioEngine::Stop() -{ - iSession.Stop(); - - delete iRecHandler; - iRecHandler = NULL; - - state_ = STATE_READY; -} - -void CPjAudioEngine::ConstructL() -{ - iSettings.iFourCC = TFourCC(KMCPFourCCIdG711); - iSettings.iGlobal = APP_UID; - iSettings.iPriority = TMdaPriority(100); - iSettings.iPreference = TMdaPriorityPreference(0x05210001); - iSettings.iSettings.iChannels = EMMFMono; - iSettings.iSettings.iSampleRate = EMMFSampleRate8000Hz; - iSettings.iSettings.iVolume = 0; - - /* play_buf size is samples per frame of parent stream. */ - play_buf = (pj_int16_t*)pj_pool_alloc(parentStrm_->pool, - parentStrm_->samples_per_frame << 1); - play_buf_len = 0; - play_buf_start = 0; - - /* rec_buf size is samples per frame of parent stream. */ - rec_buf = (pj_int16_t*)pj_pool_alloc(parentStrm_->pool, - parentStrm_->samples_per_frame << 1); - rec_buf_len = 0; -} - -TInt CPjAudioEngine::StartStreamL() -{ - if (state_ == STATE_STREAMING) - return 0; - - iSession.SetCng(EFalse); - iSession.SetVadMode(EFalse); - iSession.SetPlc(EFalse); - iSession.SetEncoderMode(EULawOr30ms); - iSession.SetDecoderMode(EULawOr30ms); - iSession.ActivateLoudspeaker(act_loudspeaker); - - // Not only playback - if (parentStrm_->dir != PJMEDIA_DIR_PLAYBACK) { - iRecHandler = CQueueHandler::NewL(this, &iReadQ, - CQueueHandler::ERecordQueue); - iRecHandler->Start(); - iSession.Read(); - TRACE_((THIS_FILE, "APS recorder started")); - } - - // Not only capture - if (parentStrm_->dir != PJMEDIA_DIR_CAPTURE) { - iSession.Write(); - TRACE_((THIS_FILE, "APS player started")); - } - - state_ = STATE_STREAMING; - return 0; -} - -/////////////////////////////////////////////////////////// -// Inherited from MQueueHandlerObserver -// - -void CPjAudioEngine::InputStreamInitialized(const TInt aStatus) -{ - TRACE_((THIS_FILE, "InputStreamInitialized %d", aStatus)); - - state_ = STATE_READY; - if (aStatus == KErrNone) { - StartStreamL(); - } -} - -void CPjAudioEngine::OutputStreamInitialized(const TInt aStatus) -{ - TRACE_((THIS_FILE, "OutputStreamInitialized %d", aStatus)); - - if (aStatus == KErrNone) { - if (parentStrm_->dir == PJMEDIA_DIR_PLAYBACK) { - state_ = STATE_READY; - // Only playback, start directly - StartStreamL(); - } else - InitRecL(); - } -} - -void CPjAudioEngine::NotifyError(const TInt aError) -{ - snd_perror("Error from CQueueHandler", aError); -} - -void CPjAudioEngine::RecCb(TAPSCommBuffer &buffer) -{ - pj_assert(buffer.iBuffer[0] == 1 && buffer.iBuffer[1] == 0); - - /* Detect the recorder G.711 frame size, player frame size will follow - * this recorder frame size. - */ - if (CPjAudioEngine::aps_samples_per_frame == 0) { - CPjAudioEngine::aps_samples_per_frame = buffer.iBuffer.Length() < 160? - 80 : 160; - TRACE_((THIS_FILE, "Detected APS G.711 frame size = %u samples", - CPjAudioEngine::aps_samples_per_frame)); - } - - /* 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 < CPjAudioEngine::aps_samples_per_frame) { - unsigned tmp; - - tmp = PJ_MIN(parentStrm_->samples_per_frame - rec_buf_len, - CPjAudioEngine::aps_samples_per_frame - dec_len); - pjmedia_ulaw_decode(&rec_buf[rec_buf_len], - buffer.iBuffer.Ptr() + 2 + dec_len, - tmp); - rec_buf_len += tmp; - dec_len += tmp; - - pj_assert(rec_buf_len <= parentStrm_->samples_per_frame); - - if (rec_buf_len == parentStrm_->samples_per_frame) { - recCb_(userData_, 0, rec_buf, rec_buf_len << 1); - rec_buf_len = 0; - } - } -} - -void CPjAudioEngine::PlayCb(TAPSCommBuffer &buffer) -{ - buffer.iCommand = CQueueHandler::EAPSPlayData; - buffer.iStatus = 0; - buffer.iBuffer.Zero(); - buffer.iBuffer.Append(1); - buffer.iBuffer.Append(0); - - /* Send 10ms silence frame if frame size hasn't been known. */ - if (CPjAudioEngine::aps_samples_per_frame == 0) { - pjmedia_zero_samples(play_buf, 80); - pjmedia_ulaw_encode((pj_uint8_t*)play_buf, play_buf, 80); - buffer.iBuffer.Append((TUint8*)play_buf, 80); - iWriteQ.Send(buffer); - return; - } - - unsigned enc_len = 0; - - /* Call parent stream callback to get PCM samples to play, - * encode the PCM samples into G.711 and put it into APS buffer. - */ - while (enc_len < CPjAudioEngine::aps_samples_per_frame) { - if (play_buf_len == 0) { - playCb_(userData_, 0, play_buf, parentStrm_->samples_per_frame<<1); - play_buf_len = parentStrm_->samples_per_frame; - play_buf_start = 0; - } - - unsigned tmp; - - tmp = PJ_MIN(play_buf_len, - CPjAudioEngine::aps_samples_per_frame - enc_len); - pjmedia_ulaw_encode((pj_uint8_t*)&play_buf[play_buf_start], - &play_buf[play_buf_start], - tmp); - buffer.iBuffer.Append((TUint8*)&play_buf[play_buf_start], tmp); - enc_len += tmp; - play_buf_len -= tmp; - play_buf_start += tmp; - } - - iWriteQ.Send(buffer); -} - -// -// End of inherited from MQueueHandlerObserver -///////////////////////////////////////////////////////////// - - -TInt CPjAudioEngine::ActivateSpeaker(TBool active) -{ - if (state_ == STATE_READY || state_ == STATE_STREAMING) { - iSession.ActivateLoudspeaker(active); - return KErrNone; - } - return KErrNotReady; -} -////////////////////////////////////////////////////////////////////////////// -// - - -/* - * Initialize sound subsystem. - */ -PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) -{ - snd_pool_factory = factory; - return PJ_SUCCESS; -} - -/* - * Get device count. - */ -PJ_DEF(int) pjmedia_snd_get_dev_count(void) -{ - /* Always return 1 */ - return 1; -} - -/* - * Get device info. - */ -PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) -{ - /* Always return the default sound device */ - if (index == (unsigned)-1) - index = 0; - - PJ_ASSERT_RETURN(index==0, NULL); - return &symbian_snd_dev_info; -} - -static pj_status_t sound_open(pjmedia_dir dir, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *strm; - - PJ_ASSERT_RETURN(p_snd_strm, PJ_EINVAL); - PJ_ASSERT_RETURN(clock_rate == 8000 && channel_count == 1 && - bits_per_sample == 16, PJ_ENOTSUP); - PJ_ASSERT_RETURN((dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && rec_cb && play_cb) - || (dir == PJMEDIA_DIR_CAPTURE && rec_cb && !play_cb) - || (dir == PJMEDIA_DIR_PLAYBACK && !rec_cb && play_cb), - PJ_EINVAL); - - pool = pj_pool_create(snd_pool_factory, POOL_NAME, POOL_SIZE, POOL_INC, - NULL); - if (!pool) - return PJ_ENOMEM; - - strm = (pjmedia_snd_stream*) pj_pool_zalloc(pool, - sizeof(pjmedia_snd_stream)); - strm->dir = dir; - strm->pool = pool; - strm->clock_rate = clock_rate; - strm->channel_count = channel_count; - strm->samples_per_frame = samples_per_frame; - - // Create the audio engine. - TRAPD(err, strm->engine = CPjAudioEngine::NewL(strm, rec_cb, play_cb, - user_data)); - if (err != KErrNone) { - pj_pool_release(pool); - return PJ_RETURN_OS_ERROR(err); - } - - // Done. - *p_snd_strm = strm; - return PJ_SUCCESS; -} - - - -/* - * Open sound recorder stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open_rec( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - if (index < 0) index = 0; - PJ_ASSERT_RETURN(index == 0, PJ_EINVAL); - - return sound_open(PJMEDIA_DIR_CAPTURE, clock_rate, channel_count, - samples_per_frame, bits_per_sample, rec_cb, NULL, - user_data, p_snd_strm); -} - -PJ_DEF(pj_status_t) pjmedia_snd_open_player( int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm ) -{ - if (index < 0) index = 0; - PJ_ASSERT_RETURN(index == 0, PJ_EINVAL); - - return sound_open(PJMEDIA_DIR_PLAYBACK, clock_rate, channel_count, - samples_per_frame, bits_per_sample, NULL, play_cb, - user_data, p_snd_strm); -} - -PJ_DEF(pj_status_t) pjmedia_snd_open( int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - if (rec_id < 0) rec_id = 0; - if (play_id < 0) play_id = 0; - PJ_ASSERT_RETURN(play_id == 0 && rec_id == 0, PJ_EINVAL); - - return sound_open(PJMEDIA_DIR_CAPTURE_PLAYBACK, clock_rate, channel_count, - samples_per_frame, bits_per_sample, rec_cb, play_cb, - user_data, p_snd_strm); -} - -/* - * Get stream info. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, - pjmedia_snd_stream_info *pi) -{ - PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); - - pj_bzero(pi, sizeof(*pi)); - pi->dir = strm->dir; - pi->play_id = 0; - pi->rec_id = 0; - pi->clock_rate = strm->clock_rate; - pi->channel_count = strm->channel_count; - pi->samples_per_frame = strm->samples_per_frame; - pi->bits_per_sample = BYTES_PER_SAMPLE * 8; - // latencies approximation (in samples) - pi->rec_latency = strm->samples_per_frame * 2; - pi->play_latency = strm->samples_per_frame * 2; - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) -{ - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->engine) { - TInt err = stream->engine->StartL(); - if (err != KErrNone) - return PJ_RETURN_OS_ERROR(err); - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) -{ - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->engine) { - stream->engine->Stop(); - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) -{ - pj_pool_t *pool; - - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - if (stream->engine) { - delete stream->engine; - stream->engine = NULL; - } - - pool = stream->pool; - if (pool) { - stream->pool = NULL; - pj_pool_release(pool); - } - - return PJ_SUCCESS; -} - - -PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) -{ - /* Nothing to do */ - return PJ_SUCCESS; -} - - -/* - * Set sound latency. - */ -PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, - unsigned output_latency) -{ - /* Nothing to do */ - PJ_UNUSED_ARG(input_latency); - PJ_UNUSED_ARG(output_latency); - return PJ_SUCCESS; -} - - -/* - * Activate/deactivate loudspeaker. - */ -PJ_DEF(pj_status_t) pjmedia_snd_aps_activate_loudspeaker( - pjmedia_snd_stream *stream, - pj_bool_t active) -{ - if (stream == NULL) { - act_loudspeaker = active; - } else { - if (stream->engine == NULL) - return PJ_EINVAL; - - TInt err = stream->engine->ActivateSpeaker(active); - if (err != KErrNone) - return PJ_RETURN_OS_ERROR(err); - } - - return PJ_SUCCESS; -} diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c index 7e0b6b75..77a2e346 100644 --- a/pjmedia/src/pjmedia/transport_ice.c +++ b/pjmedia/src/pjmedia/transport_ice.c @@ -21,6 +21,7 @@ #include <pjnath/errno.h> #include <pj/assert.h> #include <pj/log.h> +#include <pj/pool.h> #include <pj/rand.h> #define THIS_FILE "transport_ice.c" diff --git a/pjmedia/src/pjmedia/transport_loop.c b/pjmedia/src/pjmedia/transport_loop.c index d9742d26..418e04a6 100644 --- a/pjmedia/src/pjmedia/transport_loop.c +++ b/pjmedia/src/pjmedia/transport_loop.c @@ -22,6 +22,7 @@ #include <pj/errno.h> #include <pj/ioqueue.h> #include <pj/log.h> +#include <pj/pool.h> #include <pj/rand.h> #include <pj/string.h> diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c index 11702713..2236392c 100644 --- a/pjmedia/src/pjmedia/transport_udp.c +++ b/pjmedia/src/pjmedia/transport_udp.c @@ -23,6 +23,7 @@ #include <pj/errno.h> #include <pj/ioqueue.h> #include <pj/log.h> +#include <pj/pool.h> #include <pj/rand.h> #include <pj/string.h> diff --git a/pjmedia/src/pjmedia/wmme_sound.c b/pjmedia/src/pjmedia/wmme_sound.c deleted file mode 100644 index 8f94660c..00000000 --- a/pjmedia/src/pjmedia/wmme_sound.c +++ /dev/null @@ -1,1008 +0,0 @@ -#include <pjmedia/sound.h> -#include <pjmedia/errno.h> -#include <pj/assert.h> -#include <pj/log.h> -#include <pj/os.h> -#include <pj/string.h> - -#if PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_WIN32_MME_SOUND - -#ifdef _MSC_VER -# pragma warning(push, 3) -#endif - -#include <windows.h> -#include <mmsystem.h> - -#ifdef _MSC_VER -# pragma warning(pop) -#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_sound.c" -#define BITS_PER_SAMPLE 16 -#define BYTES_PER_SAMPLE (BITS_PER_SAMPLE/8) - -#define MAX_PACKET_BUFFER_COUNT 32 -#define MAX_HARDWARE 16 - -struct wmme_dev_info -{ - pjmedia_snd_dev_info info; - unsigned deviceId; -}; - -static unsigned dev_count; -static struct wmme_dev_info dev_info[MAX_HARDWARE]; -static pj_bool_t snd_initialized = PJ_FALSE; - -/* Latency settings */ -static unsigned snd_input_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY; -static unsigned snd_output_latency = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; - - -/* Individual WMME capture/playback stream descriptor */ -struct wmme_stream -{ - union - { - HWAVEIN In; - HWAVEOUT Out; - } hWave; - - WAVEHDR *WaveHdr; - HANDLE hEvent; - DWORD dwBufIdx; - DWORD dwMaxBufIdx; - pj_timestamp timestamp; -}; - - -/* Sound stream. */ -struct pjmedia_snd_stream -{ - pjmedia_dir dir; /**< Sound direction. */ - int play_id; /**< Playback dev id. */ - int rec_id; /**< Recording dev id. */ - pj_pool_t *pool; /**< Memory pool. */ - - pjmedia_snd_rec_cb rec_cb; /**< Capture callback. */ - pjmedia_snd_play_cb play_cb; /**< Playback callback. */ - void *user_data; /**< Application data. */ - - struct wmme_stream play_strm; /**< Playback stream. */ - struct wmme_stream rec_strm; /**< Capture stream. */ - - void *buffer; /**< Temp. frame buffer. */ - unsigned clock_rate; /**< Clock rate. */ - unsigned samples_per_frame; /**< Samples per frame. */ - unsigned bits_per_sample; /**< Bits per sample. */ - unsigned channel_count; /**< Channel count. */ - - pj_thread_t *thread; /**< Thread handle. */ - HANDLE thread_quit_event; /**< Quit signal to thread */ -}; - - -static pj_pool_factory *pool_factory; - -static void init_waveformatex (LPWAVEFORMATEX pcmwf, - unsigned clock_rate, - unsigned channel_count) -{ - pj_bzero(pcmwf, sizeof(PCMWAVEFORMAT)); - pcmwf->wFormatTag = WAVE_FORMAT_PCM; - pcmwf->nChannels = (pj_uint16_t)channel_count; - pcmwf->nSamplesPerSec = clock_rate; - pcmwf->nBlockAlign = (pj_uint16_t)(channel_count * BYTES_PER_SAMPLE); - pcmwf->nAvgBytesPerSec = clock_rate * channel_count * BYTES_PER_SAMPLE; - pcmwf->wBitsPerSample = BITS_PER_SAMPLE; -} - - -/* - * Initialize WMME player device. - */ -static pj_status_t init_player_stream( pj_pool_t *pool, - struct wmme_stream *wmme_strm, - int dev_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned buffer_count) -{ - MMRESULT mr; - WAVEFORMATEX pcmwf; - unsigned bytes_per_frame; - unsigned i; - - PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL); - - /* Check device ID */ - if (dev_id == -1) - dev_id = 0; - - PJ_ASSERT_RETURN(dev_id >= 0 && dev_id < (int)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(&pcmwf, clock_rate, channel_count); - bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE; - - /* - * Open wave device. - */ - mr = waveOutOpen(&wmme_strm->hWave.Out, dev_info[dev_id].deviceId, &pcmwf, - (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - - /* Pause the wave out device */ - mr = waveOutPause(wmme_strm->hWave.Out); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - - /* - * Create the buffers. - */ - wmme_strm->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, bytes_per_frame); - wmme_strm->WaveHdr[i].dwBufferLength = bytes_per_frame; - mr = waveOutPrepareHeader(wmme_strm->hWave.Out, - &(wmme_strm->WaveHdr[i]), - sizeof(WAVEHDR)); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - mr = waveOutWrite(wmme_strm->hWave.Out, &(wmme_strm->WaveHdr[i]), - sizeof(WAVEHDR)); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - } - - wmme_strm->dwBufIdx = 0; - wmme_strm->dwMaxBufIdx = buffer_count; - wmme_strm->timestamp.u64 = 0; - - /* Done setting up play device. */ - PJ_LOG(5, (THIS_FILE, - " WaveAPI Sound player \"%s\" initialized (clock_rate=%d, " - "channel_count=%d, samples_per_frame=%d (%dms))", - dev_info[dev_id].info.name, - clock_rate, channel_count, samples_per_frame, - samples_per_frame * 1000 / clock_rate)); - - return PJ_SUCCESS; -} - - -/* - * Initialize Windows Multimedia recorder device - */ -static pj_status_t init_capture_stream( pj_pool_t *pool, - struct wmme_stream *wmme_strm, - int dev_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned buffer_count) -{ - MMRESULT mr; - WAVEFORMATEX pcmwf; - unsigned bytes_per_frame; - unsigned i; - - PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL); - - /* Check device ID */ - if (dev_id == -1) - dev_id = 0; - - PJ_ASSERT_RETURN(dev_id >= 0 && dev_id < (int)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(&pcmwf, clock_rate, channel_count); - bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE; - - /* - * Open wave device. - */ - mr = waveInOpen(&wmme_strm->hWave.In, dev_info[dev_id].deviceId, &pcmwf, - (DWORD)wmme_strm->hEvent, 0, CALLBACK_EVENT); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - - /* - * Create the buffers. - */ - wmme_strm->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, bytes_per_frame); - wmme_strm->WaveHdr[i].dwBufferLength = bytes_per_frame; - mr = waveInPrepareHeader(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]), - sizeof(WAVEHDR)); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - mr = waveInAddBuffer(wmme_strm->hWave.In, &(wmme_strm->WaveHdr[i]), - sizeof(WAVEHDR)); - if (mr != MMSYSERR_NOERROR) - /* TODO: This is for HRESULT/GetLastError() */ - PJ_RETURN_OS_ERROR(mr); - } - - wmme_strm->dwBufIdx = 0; - wmme_strm->dwMaxBufIdx = buffer_count; - wmme_strm->timestamp.u64 = 0; - - /* Done setting up play device. */ - PJ_LOG(5,(THIS_FILE, - " WaveAPI Sound recorder \"%s\" initialized (clock_rate=%d, " - "channel_count=%d, samples_per_frame=%d (%dms))", - dev_info[dev_id].info.name, - clock_rate, channel_count, samples_per_frame, - samples_per_frame * 1000 / clock_rate)); - - return PJ_SUCCESS; -} - - - -/* -* WMME capture and playback thread. -*/ -static int PJ_THREAD_FUNC wmme_dev_thread(void *arg) -{ - pjmedia_snd_stream *strm = arg; - HANDLE events[3]; - unsigned eventCount; - unsigned bytes_per_frame; - pj_status_t status = PJ_SUCCESS; - - - eventCount = 0; - events[eventCount++] = strm->thread_quit_event; - if (strm->dir & PJMEDIA_DIR_PLAYBACK) - events[eventCount++] = strm->play_strm.hEvent; - if (strm->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->dir & PJMEDIA_DIR_PLAYBACK) - CeSetThreadPriority(GetCurrentThread(), 153); - else - CeSetThreadPriority(GetCurrentThread(), 247); -#else - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); -#endif - - /* Calculate bytes per frame */ - bytes_per_frame = strm->samples_per_frame * BYTES_PER_SAMPLE; - - /* - * 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; - - 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_stream *wmme_strm = &strm->play_strm; - MMRESULT mr = MMSYSERR_NOERROR; - status = PJ_SUCCESS; - - /* - * Windows Multimedia has requested us to feed some frames to - * playback buffer. - */ - - while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE) - { - void* buffer = wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData; - - PJ_LOG(5,(THIS_FILE, "Finished writing buffer %d", - wmme_strm->dwBufIdx)); - - /* Get frame from application. */ - status = (*strm->play_cb)(strm->user_data, - wmme_strm->timestamp.u32.lo, - buffer, - bytes_per_frame); - - if (status != PJ_SUCCESS) - break; - - /* Write to the device. */ - mr = waveOutWrite(wmme_strm->hWave.Out, - &(wmme_strm->WaveHdr[wmme_strm->dwBufIdx]), - sizeof(WAVEHDR)); - if (mr != MMSYSERR_NOERROR) - { - status = PJ_STATUS_FROM_OS(mr); - break; - } - - /* Increment position. */ - if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx) - wmme_strm->dwBufIdx = 0; - wmme_strm->timestamp.u64 += strm->samples_per_frame / - strm->channel_count; - } - } - else - { - struct wmme_stream *wmme_strm = &strm->rec_strm; - 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 - - while (wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwFlags & WHDR_DONE) - { - char* buffer = (char*) - wmme_strm->WaveHdr[wmme_strm->dwBufIdx].lpData; - unsigned cap_len = - wmme_strm->WaveHdr[wmme_strm->dwBufIdx].dwBytesRecorded; - - /* - PJ_LOG(5,(THIS_FILE, "Read %d bytes from buffer %d", cap_len, - wmme_strm->dwBufIdx)); - */ - - if (cap_len < bytes_per_frame) - pj_bzero(buffer + cap_len, bytes_per_frame - cap_len); - - /* Copy the audio data out of the wave buffer. */ - pj_memcpy(strm->buffer, buffer, bytes_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 = PJ_STATUS_FROM_OS(mr); - break; - } - - /* Call callback */ - status = (*strm->rec_cb)(strm->user_data, - wmme_strm->timestamp.u32.lo, - strm->buffer, - bytes_per_frame); - - if (status != PJ_SUCCESS) - break; - - /* Increment position. */ - if (++wmme_strm->dwBufIdx >= wmme_strm->dwMaxBufIdx) - wmme_strm->dwBufIdx = 0; - wmme_strm->timestamp.u64 += strm->samples_per_frame / - strm->channel_count; - } - } - } - - PJ_LOG(5,(THIS_FILE, "WMME: thread stopping..")); - return 0; -} - - -/* -* Init sound library. -*/ -PJ_DEF(pj_status_t) pjmedia_snd_init(pj_pool_factory *factory) -{ - unsigned c; - int i; - int inputDeviceCount, outputDeviceCount, maximumPossibleDeviceCount; - - if (snd_initialized) - return PJ_SUCCESS; - - pj_bzero(&dev_info, sizeof(dev_info)); - - dev_count = 0; - pool_factory = factory; - - /* Enumerate sound playback devices */ - maximumPossibleDeviceCount = 0; - - inputDeviceCount = waveInGetNumDevs(); - if (inputDeviceCount > 0) - /* assume there is a WAVE_MAPPER */ - maximumPossibleDeviceCount += inputDeviceCount + 1; - - outputDeviceCount = waveOutGetNumDevs(); - if (outputDeviceCount > 0) - /* assume there is a WAVE_MAPPER */ - maximumPossibleDeviceCount += outputDeviceCount + 1; - - if (maximumPossibleDeviceCount >= MAX_HARDWARE) - { - pj_assert(!"Too many hardware found"); - PJ_LOG(3,(THIS_FILE, "Too many hardware found, " - "some devices will not be listed")); - } - - if (inputDeviceCount > 0) - { - /* -1 is the WAVE_MAPPER */ - for (i = -1; i < inputDeviceCount && dev_count < MAX_HARDWARE; ++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; - -#ifdef UNICODE - WideCharToMultiByte(CP_ACP, 0, wic.szPname, wcslen(wic.szPname), - dev_info[dev_count].info.name, 64, NULL, NULL); -#else - strncpy(dev_info[dev_count].info.name, wic.szPname, MAXPNAMELEN); -#endif - if (uDeviceID == WAVE_MAPPER) - strcat(dev_info[dev_count].info.name, " - Input"); - - dev_info[dev_count].info.input_count = wic.wChannels; - dev_info[dev_count].info.output_count = 0; - dev_info[dev_count].info.default_samples_per_sec = 44100; - dev_info[dev_count].deviceId = uDeviceID; - - /* 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 ((dev_info[dev_count].info.input_count < 1) || - (dev_info[dev_count].info.input_count > 256)) - dev_info[dev_count].info.input_count = 2; - - ++dev_count; - } - } - - if( outputDeviceCount > 0 ) - { - /* -1 is the WAVE_MAPPER */ - for (i = -1; i < outputDeviceCount && dev_count < MAX_HARDWARE; ++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; - -#ifdef UNICODE - WideCharToMultiByte(CP_ACP, 0, woc.szPname, wcslen(woc.szPname), - dev_info[dev_count].info.name, 64, NULL, NULL); -#else - strncpy(dev_info[dev_count].info.name, woc.szPname, MAXPNAMELEN); -#endif - if (uDeviceID == WAVE_MAPPER) - strcat(dev_info[dev_count].info.name, " - Output"); - - dev_info[dev_count].info.output_count = woc.wChannels; - dev_info[dev_count].info.input_count = 0; - dev_info[dev_count].deviceId = uDeviceID; - /* TODO: Perform a search! */ - dev_info[dev_count].info.default_samples_per_sec = 44100; - - /* 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 ((dev_info[dev_count].info.output_count < 1) || - (dev_info[dev_count].info.output_count > 256)) - dev_info[dev_count].info.output_count = 2; - - ++dev_count; - } - } - - PJ_LOG(4, (THIS_FILE, "WMME initialized, found %d devices:", dev_count)); - for (c = 0; c < dev_count; ++c) - { - PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (in=%d, out=%d)", - c, - dev_info[c].info.name, - dev_info[c].info.input_count, - dev_info[c].info.output_count)); - } - return PJ_SUCCESS; -} - -/* - * Deinitialize sound library. - */ -PJ_DEF(pj_status_t) pjmedia_snd_deinit(void) -{ - snd_initialized = PJ_FALSE; - return PJ_SUCCESS; -} - -/* - * Get device count. - */ -PJ_DEF(int) pjmedia_snd_get_dev_count(void) -{ - return dev_count; -} - -/* - * Get device info. - */ -PJ_DEF(const pjmedia_snd_dev_info*) pjmedia_snd_get_dev_info(unsigned index) -{ - if (index == (unsigned)-1) - index = 0; - - PJ_ASSERT_RETURN(index < dev_count, NULL); - - return &dev_info[index].info; -} - - -/* - * Open stream. - */ -static pj_status_t open_stream(pjmedia_dir dir, - int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - pj_pool_t *pool; - pjmedia_snd_stream *strm; - pj_status_t status; - - - /* Make sure sound subsystem has been initialized with - * pjmedia_snd_init() - */ - PJ_ASSERT_RETURN(pool_factory != NULL, PJ_EINVALIDOP); - - - /* Can only support 16bits per sample */ - PJ_ASSERT_RETURN(bits_per_sample == BITS_PER_SAMPLE, PJ_EINVAL); - - /* Create and Initialize stream descriptor */ - pool = pj_pool_create(pool_factory, "wmme-dev", 1000, 1000, NULL); - PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); - - strm = pj_pool_zalloc(pool, sizeof(pjmedia_snd_stream)); - strm->dir = dir; - strm->play_id = play_id; - strm->rec_id = rec_id; - strm->pool = pool; - strm->rec_cb = rec_cb; - strm->play_cb = play_cb; - strm->user_data = user_data; - strm->clock_rate = clock_rate; - strm->samples_per_frame = samples_per_frame; - strm->bits_per_sample = bits_per_sample; - strm->channel_count = channel_count; - strm->buffer = pj_pool_alloc(pool, samples_per_frame * BYTES_PER_SAMPLE); - if (!strm->buffer) - { - pj_pool_release(pool); - return PJ_ENOMEM; - } - - /* Create player stream */ - if (dir & PJMEDIA_DIR_PLAYBACK) - { - unsigned buf_count; - - buf_count = snd_output_latency * clock_rate * channel_count / - samples_per_frame / 1000; - - status = init_player_stream(strm->pool, - &strm->play_strm, - play_id, - clock_rate, - channel_count, - samples_per_frame, - buf_count); - - if (status != PJ_SUCCESS) - { - pjmedia_snd_stream_close(strm); - return status; - } - } - - /* Create capture stream */ - if (dir & PJMEDIA_DIR_CAPTURE) - { - unsigned buf_count; - - buf_count = snd_input_latency * clock_rate * channel_count / - samples_per_frame / 1000; - - status = init_capture_stream(strm->pool, - &strm->rec_strm, - rec_id, - clock_rate, - channel_count, - samples_per_frame, - buf_count); - - if (status != PJ_SUCCESS) - { - pjmedia_snd_stream_close(strm); - return status; - } - } - - /* Create the stop event */ - strm->thread_quit_event = CreateEvent(NULL, FALSE, FALSE, NULL); - if (strm->thread_quit_event == NULL) - return pj_get_os_error(); - - /* Create and start the thread */ - status = pj_thread_create(pool, "wmme", &wmme_dev_thread, strm, 0, 0, - &strm->thread); - if (status != PJ_SUCCESS) - { - pjmedia_snd_stream_close(strm); - return status; - } - - *p_snd_strm = strm; - - return PJ_SUCCESS; -} - -/* - * Open stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open_rec(int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - PJ_ASSERT_RETURN(rec_cb && p_snd_strm, PJ_EINVAL); - - return open_stream( PJMEDIA_DIR_CAPTURE, - index, - -1, - clock_rate, - channel_count, - samples_per_frame, - bits_per_sample, - rec_cb, - NULL, - user_data, - p_snd_strm); -} - -PJ_DEF(pj_status_t) pjmedia_snd_open_player(int index, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - PJ_ASSERT_RETURN(play_cb && p_snd_strm, PJ_EINVAL); - - return open_stream( PJMEDIA_DIR_PLAYBACK, - -1, - index, - clock_rate, - channel_count, - samples_per_frame, - bits_per_sample, - NULL, - play_cb, - user_data, - p_snd_strm); -} - -/* - * Open both player and recorder. - */ -PJ_DEF(pj_status_t) pjmedia_snd_open(int rec_id, - int play_id, - unsigned clock_rate, - unsigned channel_count, - unsigned samples_per_frame, - unsigned bits_per_sample, - pjmedia_snd_rec_cb rec_cb, - pjmedia_snd_play_cb play_cb, - void *user_data, - pjmedia_snd_stream **p_snd_strm) -{ - PJ_ASSERT_RETURN(rec_cb && play_cb && p_snd_strm, PJ_EINVAL); - - return open_stream( PJMEDIA_DIR_CAPTURE_PLAYBACK, - rec_id, - play_id, - clock_rate, - channel_count, - samples_per_frame, - bits_per_sample, - rec_cb, - play_cb, - user_data, - p_snd_strm); -} - -/* - * Get stream info. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_get_info(pjmedia_snd_stream *strm, - pjmedia_snd_stream_info *pi) -{ - PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); - - pj_bzero(pi, sizeof(*pi)); - pi->dir = strm->dir; - pi->play_id = strm->play_id; - pi->rec_id = strm->rec_id; - pi->clock_rate = strm->clock_rate; - pi->channel_count = strm->channel_count; - pi->samples_per_frame = strm->samples_per_frame; - pi->bits_per_sample = strm->bits_per_sample; - pi->rec_latency = snd_input_latency * strm->clock_rate * - strm->channel_count / 1000; - pi->play_latency = snd_output_latency * strm->clock_rate * - strm->channel_count / 1000; - - return PJ_SUCCESS; -} - - -/* -* Start stream. -*/ -PJ_DEF(pj_status_t) pjmedia_snd_stream_start(pjmedia_snd_stream *stream) -{ - MMRESULT mr; - - PJ_UNUSED_ARG(stream); - - if (stream->play_strm.hWave.Out != NULL) - { - mr = waveOutRestart(stream->play_strm.hWave.Out); - if (mr != MMSYSERR_NOERROR) - /* TODO: This macro is supposed to be used for HRESULT, fix. */ - PJ_RETURN_OS_ERROR(mr); - PJ_LOG(5,(THIS_FILE, "WMME playback stream started")); - } - - if (stream->rec_strm.hWave.In != NULL) - { - mr = waveInStart(stream->rec_strm.hWave.In); - if (mr != MMSYSERR_NOERROR) - /* TODO: This macro is supposed to be used for HRESULT, fix. */ - PJ_RETURN_OS_ERROR(mr); - PJ_LOG(5,(THIS_FILE, "WMME capture stream started")); - } - - return PJ_SUCCESS; -} - -/* - * Stop stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_stop(pjmedia_snd_stream *stream) -{ - 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) - /* TODO: This macro is supposed to be used for HRESULT, fix. */ - PJ_RETURN_OS_ERROR(mr); - PJ_LOG(5,(THIS_FILE, "Stopped WMME playback stream")); - } - - if (stream->rec_strm.hWave.In != NULL) - { - mr = waveInStop(stream->rec_strm.hWave.In); - if (mr != MMSYSERR_NOERROR) - /* TODO: This macro is supposed to be used for HRESULT, fix. */ - PJ_RETURN_OS_ERROR(mr); - PJ_LOG(5,(THIS_FILE, "Stopped WMME capture stream")); - } - - return PJ_SUCCESS; -} - - -/* - * Destroy stream. - */ -PJ_DEF(pj_status_t) pjmedia_snd_stream_close(pjmedia_snd_stream *stream) -{ - unsigned i; - - PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); - - pjmedia_snd_stream_stop(stream); - - 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; -} - -/* - * Set sound latency. - */ -PJ_DEF(pj_status_t) pjmedia_snd_set_latency(unsigned input_latency, - unsigned output_latency) -{ - snd_input_latency = (input_latency == 0)? - PJMEDIA_SND_DEFAULT_REC_LATENCY : input_latency; - snd_output_latency = (output_latency == 0)? - PJMEDIA_SND_DEFAULT_PLAY_LATENCY : output_latency; - - return PJ_SUCCESS; -} - -#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ - |