diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-02-20 01:28:25 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-02-20 01:28:25 +0000 |
commit | 295cee81042c609eea993b4f5c292614f979cbaa (patch) | |
tree | dd2dff73e000544e4b4c16772daa65821eec1adb | |
parent | ca9c14f791178caba306b47766b26bbbed8a34a8 (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.dsp | 8 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/audio_conf.h | 78 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/stream.h | 17 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/types.h | 5 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/vad.h | 57 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/audio_conf.c | 405 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/g711.c | 10 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/vad.c | 244 |
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; } |