summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2006-02-20 01:28:25 +0000
committerBenny Prijono <bennylp@teluu.com>2006-02-20 01:28:25 +0000
commit295cee81042c609eea993b4f5c292614f979cbaa (patch)
treedd2dff73e000544e4b4c16772daa65821eec1adb
parentca9c14f791178caba306b47766b26bbbed8a34a8 (diff)
Added conference bridge prototype
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@203 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r--pjmedia/build/pjmedia.dsp8
-rw-r--r--pjmedia/include/pjmedia/audio_conf.h78
-rw-r--r--pjmedia/include/pjmedia/stream.h17
-rw-r--r--pjmedia/include/pjmedia/types.h5
-rw-r--r--pjmedia/include/pjmedia/vad.h57
-rw-r--r--pjmedia/src/pjmedia/audio_conf.c405
-rw-r--r--pjmedia/src/pjmedia/g711.c10
-rw-r--r--pjmedia/src/pjmedia/vad.c244
8 files changed, 800 insertions, 24 deletions
diff --git a/pjmedia/build/pjmedia.dsp b/pjmedia/build/pjmedia.dsp
index 0f87dc57..c9bea930 100644
--- a/pjmedia/build/pjmedia.dsp
+++ b/pjmedia/build/pjmedia.dsp
@@ -87,6 +87,10 @@ LIB32=link.exe -lib
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
+SOURCE=..\src\pjmedia\audio_conf.c
+# End Source File
+# Begin Source File
+
SOURCE=..\src\pjmedia\codec.c
# End Source File
# Begin Source File
@@ -157,6 +161,10 @@ SOURCE=..\src\pjmedia\vad.c
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
+SOURCE=..\include\pjmedia\audio_conf.h
+# End Source File
+# Begin Source File
+
SOURCE=..\include\pjmedia\codec.h
# End Source File
# Begin Source File
diff --git a/pjmedia/include/pjmedia/audio_conf.h b/pjmedia/include/pjmedia/audio_conf.h
new file mode 100644
index 00000000..0131ac4e
--- /dev/null
+++ b/pjmedia/include/pjmedia/audio_conf.h
@@ -0,0 +1,78 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 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
+ */
+#ifndef __PJMEDIA_CONF_H__
+#define __PJMEDIA_CONF_H__
+
+
+/**
+ * @file conf.h
+ * @brief Conference bridge.
+ */
+#include <pjmedia/types.h>
+
+/**
+ * Opaque type for conference bridge.
+ */
+typedef struct pjmedia_conf pjmedia_conf;
+
+
+/**
+ * Create conference bridge.
+ */
+PJ_DECL(pj_status_t) pjmedia_conf_create( pj_pool_t *pool,
+ unsigned max_ports,
+ pjmedia_conf **p_conf );
+
+
+/**
+ * Add stream port to the conference bridge.
+ */
+PJ_DECL(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ pjmedia_stream_port *strm_port,
+ const pj_str_t *port_name,
+ unsigned *p_port );
+
+
+/**
+ * Mute or unmute port.
+ */
+PJ_DECL(pj_status_t) pjmedia_conf_set_mute( pjmedia_conf *conf,
+ unsigned port,
+ pj_bool_t mute );
+
+
+/**
+ * Set the specified port to be member of conference bridge.
+ */
+PJ_DECL(pj_status_t) pjmedia_conf_set_membership( pjmedia_conf *conf,
+ unsigned port,
+ pj_bool_t enabled );
+
+
+/**
+ * Remove the specified port.
+ */
+PJ_DECL(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
+ unsigned port );
+
+
+
+#endif /* __PJMEDIA_CONF_H__ */
+
diff --git a/pjmedia/include/pjmedia/stream.h b/pjmedia/include/pjmedia/stream.h
index 5f61bd61..57bbe636 100644
--- a/pjmedia/include/pjmedia/stream.h
+++ b/pjmedia/include/pjmedia/stream.h
@@ -98,6 +98,23 @@ struct pjmedia_stream_stat
/**
+ * Stream ports.
+ */
+struct pjmedia_stream_port
+{
+ /**
+ * Sink port.
+ */
+ pj_status_t (*put_frame)(const pj_int16_t *frame, pj_size_t frame_cnt);
+
+ /**
+ * Source port.
+ */
+ pj_status_t (*get_frame)(pj_int16_t *frame, pj_size_t frame_cnt);
+};
+
+
+/**
* Create a media stream based on the specified stream parameter.
* All channels in the stream initially will be inactive.
*
diff --git a/pjmedia/include/pjmedia/types.h b/pjmedia/include/pjmedia/types.h
index b0bda7e4..7f1a1060 100644
--- a/pjmedia/include/pjmedia/types.h
+++ b/pjmedia/include/pjmedia/types.h
@@ -151,6 +151,11 @@ typedef struct pjmedia_stream_info pjmedia_stream_info;
typedef struct pjmedia_stream_stat pjmedia_stream_stat;
/**
+ * @see pjmedia_stream_port
+ */
+typedef struct pjmedia_stream_port pjmedia_stream_port;
+
+/**
* Typedef for media stream.
*/
typedef struct pjmedia_stream pjmedia_stream;
diff --git a/pjmedia/include/pjmedia/vad.h b/pjmedia/include/pjmedia/vad.h
index 4788799b..a7da42d2 100644
--- a/pjmedia/include/pjmedia/vad.h
+++ b/pjmedia/include/pjmedia/vad.h
@@ -26,9 +26,11 @@
*/
#include <pjmedia/types.h>
+PJ_BEGIN_DECL
+
/**
- * Opaque data type for pjmedia vad.
+ * @see pjmedia_vad
*/
typedef struct pjmedia_vad pjmedia_vad;
@@ -48,6 +50,41 @@ PJ_DECL(pj_status_t) pjmedia_vad_create( pj_pool_t *pool,
/**
+ * Set the vad to operate in adaptive mode.
+ *
+ * @param vad The vad
+ * @param frame_size Number of samplse per frame.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vad_set_adaptive( pjmedia_vad *vad,
+ unsigned frame_size);
+
+
+/**
+ * Set the vad to operate in fixed threshold mode.
+ *
+ * @param vad The vad
+ * @param frame_size Number of samplse per frame.
+ * @param threshold The silence threshold.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vad_set_fixed( pjmedia_vad *vad,
+ unsigned frame_size,
+ unsigned threshold );
+
+/**
+ * Disable the vad.
+ *
+ * @param vad The vad
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_vad_disable( pjmedia_vad *vad );
+
+
+/**
* Calculate average signal level for the given samples.
*
* @param samples Pointer to 16-bit PCM samples.
@@ -56,8 +93,8 @@ PJ_DECL(pj_status_t) pjmedia_vad_create( pj_pool_t *pool,
* @return The average signal level, which simply is total level
* divided by number of samples.
*/
-PJ_DECL(pj_uint32_t) pjmedia_vad_calc_avg_signal_level( pj_int16_t samples[],
- pj_size_t count );
+PJ_DECL(pj_int32_t) pjmedia_vad_calc_avg_signal( const pj_int16_t samples[],
+ pj_size_t count );
/**
@@ -66,18 +103,18 @@ PJ_DECL(pj_uint32_t) pjmedia_vad_calc_avg_signal_level( pj_int16_t samples[],
* @param vad The VAD instance.
* @param samples Pointer to 16-bit PCM input samples.
* @param count Number of samples in the input.
- * @param p_silence Pointer to receive the silence detection result.
- * Non-zero value indicates that that input is considered
- * as silence.
+ * @param p_level Optional pointer to receive average signal level
+ * of the input samples.
*
* @return PJ_SUCCESS on success.
*/
-PJ_DECL(pj_status_t) pjmedia_vad_detect_silence( pjmedia_vad *vad,
- pj_int16_t samples[],
- pj_size_t count,
- pj_bool_t *p_silence);
+PJ_DECL(pj_bool_t) pjmedia_vad_detect_silence( pjmedia_vad *vad,
+ const pj_int16_t samples[],
+ pj_size_t count,
+ pj_int32_t *p_level);
+PJ_END_DECL
#endif /* __PJMEDIA_VAD_H__ */
diff --git a/pjmedia/src/pjmedia/audio_conf.c b/pjmedia/src/pjmedia/audio_conf.c
new file mode 100644
index 00000000..45c3fafc
--- /dev/null
+++ b/pjmedia/src/pjmedia/audio_conf.c
@@ -0,0 +1,405 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2006 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/audio_conf.h>
+#include <pjmedia/vad.h>
+#include <pjmedia/stream.h>
+#include <pjmedia/sound.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+
+#define THIS_FILE "audio_conf.c"
+
+
+struct conf_port
+{
+ pj_str_t name;
+ pjmedia_stream_port *port;
+ pj_bool_t online;
+ pj_bool_t is_member;
+ pjmedia_vad *vad;
+ pj_int32_t level;
+};
+
+/*
+ * Conference bridge.
+ */
+struct pjmedia_conf
+{
+ unsigned max_ports; /**< Maximum ports. */
+ unsigned port_cnt; /**< Current number of ports. */
+ pj_snd_stream *snd_rec; /**< Sound recorder stream. */
+ pj_snd_stream *snd_player; /**< Sound player stream. */
+ struct conf_port **port; /**< Array of ports. */
+ pj_int16_t *rec_buf; /**< Sample buffer for rec. */
+ pj_int16_t *play_buf; /**< Sample buffer for player */
+ unsigned samples_cnt; /**< Samples per frame. */
+ pj_size_t buf_size; /**< Buffer size, in bytes. */
+};
+
+
+/* Prototypes */
+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 rec_cb( /* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* in */ const void *input,
+ /* in*/ unsigned size);
+
+
+/*
+ * Create conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_create( pj_pool_t *pool,
+ unsigned max_ports,
+ pjmedia_conf **p_conf )
+{
+ pjmedia_conf *conf;
+ pj_snd_stream_info snd_info;
+ pj_status_t status;
+
+ conf = pj_pool_zalloc(pool, sizeof(pjmedia_conf));
+ conf->max_ports = max_ports;
+ conf->port = pj_pool_zalloc(pool, max_ports*sizeof(void*));
+
+ /* Create default parameters. */
+ pj_memset(&snd_info, 0, sizeof(snd_info));
+ snd_info.samples_per_sec = 8000;
+ snd_info.bits_per_sample = 16;
+ snd_info.samples_per_frame = 160;
+ snd_info.bytes_per_frame = 16000;
+ snd_info.frames_per_packet = 1;
+
+ /* Create buffers. */
+ conf->samples_cnt = snd_info.samples_per_frame;
+ conf->buf_size = snd_info.samples_per_frame * snd_info.bits_per_sample / 8;
+ conf->rec_buf = pj_pool_alloc(pool, conf->buf_size);
+ conf->play_buf = pj_pool_alloc(pool, conf->buf_size );
+
+
+ /* Open recorder. */
+ conf->snd_rec = pj_snd_open_recorder(-1 ,&snd_info, &rec_cb, conf);
+ if (conf->snd_rec == NULL) {
+ status = -1;
+ goto on_error;
+ }
+
+ /* Open player */
+ conf->snd_player = pj_snd_open_player(-1, &snd_info, &play_cb, conf);
+ if (conf->snd_player == NULL) {
+ status = -1;
+ goto on_error;
+ }
+
+ /* Done */
+
+ *p_conf = conf;
+
+ return PJ_SUCCESS;
+
+
+on_error:
+ if (conf->snd_rec) {
+ pj_snd_stream_stop(conf->snd_rec);
+ pj_snd_stream_close(conf->snd_rec);
+ conf->snd_rec = NULL;
+ }
+ if (conf->snd_player) {
+ pj_snd_stream_stop(conf->snd_player);
+ pj_snd_stream_close(conf->snd_player);
+ conf->snd_player = NULL;
+ }
+ return status;
+}
+
+/*
+ * Activate sound device.
+ */
+static pj_status_t activate_conf( pjmedia_conf *conf )
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_status_t status;
+
+ /* Start recorder. */
+ status = pj_snd_stream_start(conf->snd_rec);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Start player. */
+ status = pj_snd_stream_start(conf->snd_rec);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "Error starting sound player/recorder: %s",
+ errmsg));
+ return status;
+}
+
+
+/*
+ * Suspend sound device
+ */
+static void suspend_conf( pjmedia_conf *conf )
+{
+ pj_snd_stream_stop(conf->snd_rec);
+ pj_snd_stream_stop(conf->snd_player);
+}
+
+
+/*
+ * Add stream port to the conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf,
+ pj_pool_t *pool,
+ pjmedia_stream_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 && port_name && p_port,
+ PJ_EINVAL);
+
+ if (conf->port_cnt >= conf->max_ports) {
+ pj_assert(!"Too many ports");
+ return PJ_ETOOMANY;
+ }
+
+ /* Create port structure. */
+ conf_port = pj_pool_zalloc(pool, sizeof(struct conf_port));
+ pj_strdup_with_null(pool, &conf_port->name, port_name);
+ conf_port->port = strm_port;
+ conf_port->online = PJ_TRUE;
+ conf_port->level = 0;
+
+ /* Create VAD for this port. */
+ status = pjmedia_vad_create(pool, &conf_port->vad);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set vad settings. */
+ pjmedia_vad_set_adaptive(conf_port->vad, conf->samples_cnt);
+
+ /* Find empty port in the conference bridge. */
+ for (index=0; index < conf->max_ports; ++index) {
+ if (conf->port[index] == NULL)
+ break;
+ }
+
+ pj_assert(index != conf->max_ports);
+
+ /* Put the port. */
+ conf->port[index] = conf_port;
+ conf->port_cnt++;
+
+ /* If this is the first port, activate sound device. */
+ if (conf->port_cnt == 1) {
+ status = activate_conf(conf);;
+ if (status != PJ_SUCCESS) {
+ conf->port[index] = NULL;
+ --conf->port_cnt;
+ return status;
+ }
+ }
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Mute or unmute port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_set_mute( pjmedia_conf *conf,
+ unsigned port,
+ pj_bool_t mute )
+{
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->port[port] != NULL, PJ_EINVAL);
+
+ conf->port[port]->online = !mute;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the specified port to be member of conference bridge.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_set_membership( pjmedia_conf *conf,
+ unsigned port,
+ pj_bool_t enabled )
+{
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->port[port] != NULL, PJ_EINVAL);
+
+ conf->port[port]->is_member = enabled;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Remove the specified port.
+ */
+PJ_DEF(pj_status_t) pjmedia_conf_remove_port( pjmedia_conf *conf,
+ unsigned port )
+{
+ /* Check arguments */
+ PJ_ASSERT_RETURN(conf && port < conf->max_ports, PJ_EINVAL);
+
+ /* Port must be valid. */
+ PJ_ASSERT_RETURN(conf->port[port] != NULL, PJ_EINVAL);
+
+ /* Suspend the sound devices.
+ * Don't want to remove port while port is being accessed by sound
+ * device's threads.
+ */
+ suspend_conf(conf);
+
+ /* Remove the port. */
+ conf->port[port] = NULL;
+ --conf->port_cnt;
+
+ /* Reactivate sound device if ports are not zero */
+ if (conf->port_cnt != 0)
+ activate_conf(conf);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Player callback.
+ */
+static pj_status_t play_cb( /* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* out */ void *output,
+ /* out */ unsigned size)
+{
+ pjmedia_conf *conf = user_data;
+ pj_int16_t *output_buf = output;
+ pj_int32_t highest_level = 0;
+ int highest_index = -1;
+ unsigned sources = 0;
+ unsigned i, j;
+
+ PJ_UNUSED_ARG(timestamp);
+
+ /* Clear temporary buffer. */
+ pj_memset(output_buf, 0, size);
+
+ /* Get frames from ports. */
+ for (i=0; i<conf->max_ports; ++i) {
+ struct conf_port *conf_port = conf->port[i];
+ pj_int32_t level;
+ pj_bool_t silence;
+
+ if (!conf_port)
+ continue;
+
+ conf_port->port->get_frame(conf->play_buf, conf->samples_cnt);
+ silence = pjmedia_vad_detect_silence(conf_port->vad,
+ conf->play_buf,
+ conf->samples_cnt,
+ &level);
+ if (!silence) {
+ if (level > highest_level) {
+ highest_index = i;
+ highest_level = level;
+ }
+
+ ++sources;
+
+ for (j=0; j<conf->samples_cnt; ++j) {
+ output_buf[j] = (pj_int16_t)(output_buf[j] + conf->play_buf[j]);
+ }
+ }
+ }
+
+ /* Calculate average signal. */
+ if (sources) {
+ for (j=0; j<conf->samples_cnt; ++j) {
+ output_buf[j] = (pj_int16_t)(output_buf[j] / sources);
+ }
+ }
+
+ /* Broadcast to conference member. */
+ for (i=0; i<conf->max_ports; ++i) {
+ struct conf_port *conf_port = conf->port[i];
+
+ if (!conf_port)
+ continue;
+
+ if (!conf_port->is_member)
+ continue;
+
+ conf_port->port->put_frame(output_buf, conf->samples_cnt);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recorder callback.
+ */
+static pj_status_t rec_cb( /* in */ void *user_data,
+ /* in */ pj_uint32_t timestamp,
+ /* in */ const void *input,
+ /* in*/ unsigned size)
+{
+ pjmedia_conf *conf = user_data;
+ unsigned i;
+
+ PJ_UNUSED_ARG(timestamp);
+ PJ_UNUSED_ARG(size);
+
+ for (i=0; i<conf->max_ports; ++i) {
+ struct conf_port *conf_port = conf->port[i];
+
+ if (!conf_port)
+ continue;
+
+ if (!conf_port->online)
+ continue;
+
+ conf_port->port->put_frame(input, conf->samples_cnt);
+ }
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia/g711.c b/pjmedia/src/pjmedia/g711.c
index 2701879c..befd1107 100644
--- a/pjmedia/src/pjmedia/g711.c
+++ b/pjmedia/src/pjmedia/g711.c
@@ -34,10 +34,10 @@ PJ_DECL(pj_status_t) g711_init_factory (pjmedia_codec_factory *factory, pj_pool_
PJ_DECL(pj_status_t) g711_deinit_factory (pjmedia_codec_factory *factory);
/* Algorithm prototypes. */
-static unsigned char linear2alaw(int pcm_val); /* 2's complement (16-bit range) */
-static int alaw2linear(unsigned char a_val);
-static unsigned char linear2ulaw(int pcm_val);
-static int ulaw2linear(unsigned char u_val);
+unsigned char linear2alaw(int pcm_val); /* 2's complement (16-bit range) */
+int alaw2linear(unsigned char a_val);
+unsigned char linear2ulaw(int pcm_val);
+int ulaw2linear(unsigned char u_val);
/* Prototypes for G711 factory */
static pj_status_t g711_test_alloc( pjmedia_codec_factory *factory,
@@ -593,7 +593,7 @@ alaw2linear(
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
-static unsigned char
+unsigned char
linear2ulaw(
int pcm_val) /* 2's complement (16-bit range) */
{
diff --git a/pjmedia/src/pjmedia/vad.c b/pjmedia/src/pjmedia/vad.c
index 399879a7..4544afa5 100644
--- a/pjmedia/src/pjmedia/vad.c
+++ b/pjmedia/src/pjmedia/vad.c
@@ -18,25 +18,251 @@
*/
#include <pjmedia/vad.h>
#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#define THIS_FILE "vad.c"
+
+typedef enum pjmedia_vad_mode {
+ VAD_MODE_NONE,
+ VAD_MODE_FIXED,
+ VAD_MODE_ADAPTIVE
+} pjmedia_vad_mode;
+
+
+/**
+ * This structure holds the vad state.
+ */
+struct pjmedia_vad
+{
+ int mode; /**< VAD mode. */
+ unsigned frame_size; /**< Samples per frame. */
+
+
+ unsigned min_signal_cnt; /**< # of signal frames.before talk burst */
+ unsigned min_silence_cnt; /**< # of silence frames before silence. */
+ unsigned recalc_cnt; /**< # of frames before adaptive recalc. */
+
+ pj_bool_t in_talk; /**< In talk burst? */
+ unsigned cur_cnt; /**< # of frames in current mode. */
+ unsigned signal_cnt; /**< # of signal frames received. */
+ unsigned silence_cnt; /**< # of silence frames received */
+ unsigned cur_threshold; /**< Current silence threshold. */
+ unsigned weakest_signal; /**< Weakest signal detected. */
+ unsigned loudest_silence; /**< Loudest silence detected. */
+};
+
+
+
+unsigned char linear2ulaw(int pcm_val);
+
PJ_DEF(pj_status_t) pjmedia_vad_create( pj_pool_t *pool,
pjmedia_vad **p_vad)
{
- return PJ_EINVALIDOP;
+ pjmedia_vad *vad;
+
+ PJ_ASSERT_RETURN(pool && p_vad, PJ_EINVAL);
+
+ vad = pj_pool_zalloc(pool, sizeof(struct pjmedia_vad));
+
+ vad->weakest_signal = 0xFFFFFFFFUL;
+ vad->loudest_silence = 0;
+ vad->signal_cnt = 0;
+ vad->silence_cnt = 0;
+
+ /* Restart in adaptive, silent mode */
+ vad->in_talk = PJ_FALSE;
+ pjmedia_vad_set_adaptive( vad, 160 );
+
+ *p_vad = vad;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vad_set_adaptive( pjmedia_vad *vad,
+ unsigned frame_size)
+{
+ PJ_ASSERT_RETURN(vad && frame_size, PJ_EINVAL);
+
+ vad->frame_size = frame_size;
+ vad->mode = VAD_MODE_ADAPTIVE;
+ vad->min_signal_cnt = 3;
+ vad->min_silence_cnt = 20;
+ vad->recalc_cnt = 30;
+ vad->cur_threshold = 20;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vad_set_fixed( pjmedia_vad *vad,
+ unsigned frame_size,
+ unsigned threshold )
+{
+ PJ_ASSERT_RETURN(vad && frame_size, PJ_EINVAL);
+
+ vad->mode = VAD_MODE_FIXED;
+ vad->frame_size = frame_size;
+ vad->cur_threshold = threshold;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjmedia_vad_disable( pjmedia_vad *vad )
+{
+ PJ_ASSERT_RETURN(vad, PJ_EINVAL);
+
+ vad->mode = VAD_MODE_NONE;
+
+ return PJ_SUCCESS;
}
-PJ_DEF(pj_uint32_t) pjmedia_vad_calc_avg_signal_level(pj_int16_t samples[],
- pj_size_t count)
+
+PJ_DEF(pj_int32_t) pjmedia_vad_calc_avg_signal(const pj_int16_t samples[],
+ pj_size_t count)
{
- return PJ_EINVALIDOP;
+ pj_uint32_t sum = 0;
+
+ const pj_int16_t * pcm = samples;
+ const pj_int16_t * end = samples + count;
+
+ if (count==0)
+ return 0;
+
+ while (pcm != end) {
+ if (*pcm < 0)
+ sum -= *pcm++;
+ else
+ sum += *pcm++;
+ }
+
+ return (pj_int32_t)(sum / count);
}
-PJ_DEF(pj_status_t) pjmedia_vad_detect_silence( pjmedia_vad *vad,
- pj_int16_t samples[],
- pj_size_t count,
- pj_bool_t *p_silence)
+PJ_DEF(pj_bool_t) pjmedia_vad_detect_silence( pjmedia_vad *vad,
+ const pj_int16_t samples[],
+ pj_size_t count,
+ pj_int32_t *p_level)
{
- return PJ_EINVALIDOP;
+ pj_uint32_t level;
+ pj_bool_t have_signal;
+
+ /* Always return false if VAD is disabled */
+ if (vad->mode == VAD_MODE_NONE) {
+ if (p_level)
+ *p_level = -1;
+ return PJ_FALSE;
+ }
+
+ /* Calculate average signal level. */
+ level = pjmedia_vad_calc_avg_signal(samples, count);
+
+ /* Report to caller, if required. */
+ if (p_level)
+ *p_level = level;
+
+ /* Convert PCM level to ulaw */
+ level = linear2ulaw(level) ^ 0xff;
+
+ /* Do we have signal? */
+ have_signal = level > vad->cur_threshold;
+
+ /* We we're in transition between silence and signel, increment the
+ * current frame counter. We will only switch mode when we have enough
+ * frames.
+ */
+ if (vad->in_talk != have_signal) {
+ unsigned limit;
+
+ vad->cur_cnt++;
+
+ limit = (vad->in_talk ? vad->min_silence_cnt :
+ vad->min_signal_cnt);
+
+ if (vad->cur_cnt > limit) {
+
+ /* Swap mode */
+ vad->in_talk = !vad->in_talk;
+
+ /* Restart adaptive cur_threshold measurements */
+ vad->weakest_signal = 0xFFFFFFFFUL;
+ vad->loudest_silence = 0;
+ vad->signal_cnt = 0;
+ vad->silence_cnt = 0;
+ }
+
+ } else {
+ /* Reset frame count */
+ vad->cur_cnt = 0;
+ }
+
+ /* For fixed threshold vad, everything is done. */
+ if (vad->mode == VAD_MODE_FIXED) {
+ return !vad->in_talk;
+ }
+
+
+ /* Count the number of silent and signal frames and calculate min/max */
+ if (have_signal) {
+ if (level < vad->weakest_signal)
+ vad->weakest_signal = level;
+ vad->signal_cnt++;
+ }
+ else {
+ if (level > vad->loudest_silence)
+ vad->loudest_silence = level;
+ vad->silence_cnt++;
+ }
+
+ /* See if we have had enough frames to look at proportions of
+ * silence/signal frames.
+ */
+ if ((vad->signal_cnt + vad->silence_cnt) > vad->recalc_cnt) {
+
+ /* Adjust silence threshold by looking at the proportions of
+ * signal and silence frames.
+ */
+ if (vad->signal_cnt >= vad->recalc_cnt) {
+ /* All frames where signal frames.
+ * Increase silence threshold.
+ */
+ vad->cur_threshold += (vad->weakest_signal - vad->cur_threshold)/4;
+ PJ_LOG(5,(THIS_FILE, "Vad cur_threshold increased to %d",
+ vad->cur_threshold));
+ }
+ else if (vad->silence_cnt >= vad->recalc_cnt) {
+ /* All frames where silence frames.
+ * Decrease silence threshold.
+ */
+ vad->cur_threshold = (vad->cur_threshold+vad->loudest_silence)/2+1;
+ PJ_LOG(5,(THIS_FILE, "Vad cur_threshold decreased to %d",
+ vad->cur_threshold));
+ }
+ else {
+ pj_bool_t updated = PJ_TRUE;
+
+ /* Adjust according to signal/silence proportions. */
+ if (vad->signal_cnt > vad->silence_cnt * 2)
+ vad->cur_threshold++;
+ else if (vad->silence_cnt > vad->signal_cnt* 2)
+ vad->cur_threshold--;
+ else
+ updated = PJ_FALSE;
+
+ if (updated) {
+ PJ_LOG(5,(THIS_FILE,
+ "Vad cur_threshold updated to %d",
+ vad->cur_threshold));
+ }
+ }
+
+ /* Reset. */
+ vad->weakest_signal = 0xFFFFFFFFUL;
+ vad->loudest_silence = 0;
+ vad->signal_cnt = 0;
+ vad->silence_cnt = 0;
+ }
+
+ return !vad->in_talk;
}