diff --git a/src/mod/applications/mod_avmd/Makefile b/src/mod/applications/mod_avmd/Makefile new file mode 100644 index 0000000000..5480f8d520 --- /dev/null +++ b/src/mod/applications/mod_avmd/Makefile @@ -0,0 +1,14 @@ +BASE=../../../.. +MOD_CFLAGS= -march=core2 -g -O2 -malign-double -mtune=core2 -mmmx -msse -msse2 -msse3 -mssse3 -mfpmath=sse -ffast-math -funroll-loops -fprefetch-loop-arrays -funit-at-a-time -ftracer --save-temps +MOD_LDFLAGS= --save-temps +LOCAL_SOURCES=amplitude.c buffer.c desa2.c goertzel.c fast_acosf.c +LOCAL_OBJS=amplitude.o buffer.o desa2.o goertzel.o fast_acosf.o +include $(BASE)/build/modmake.rules + +#compute_table: compute_table.o +# gcc -o compute_table compute_table.o -lm +# +#compute_table.o: compute_table.c +# gcc -c compute_table.c +# + diff --git a/src/mod/applications/mod_avmd/amplitude.c b/src/mod/applications/mod_avmd/amplitude.c new file mode 100644 index 0000000000..1f9d75208e --- /dev/null +++ b/src/mod/applications/mod_avmd/amplitude.c @@ -0,0 +1,23 @@ +#ifndef __AMPLITUDE_H__ +#include +#include "amplitude.h" +#include "psi.h" + +/*! \brief + * @author Eric des Courtis + * @param b A circular audio sample buffer + * @param i Position in the buffer + * @param f Frequency estimate + * @return The amplitude at position i + */ +extern double amplitude(circ_buffer_t *b, size_t i, double f) +{ + double result; + + result = sqrt(PSI(b, i) / sin(f * f)); + + return result; +} + +#endif + diff --git a/src/mod/applications/mod_avmd/amplitude.h b/src/mod/applications/mod_avmd/amplitude.h new file mode 100644 index 0000000000..4d67bd9dc9 --- /dev/null +++ b/src/mod/applications/mod_avmd/amplitude.h @@ -0,0 +1,10 @@ +#ifndef __AMPLITUDE_H__ +#define __AMPLITUDE_H__ +#include "buffer.h" + +extern double amplitude(circ_buffer_t *, size_t i, double f); + + +#endif + + diff --git a/src/mod/applications/mod_avmd/buffer.c b/src/mod/applications/mod_avmd/buffer.c new file mode 100644 index 0000000000..02d306fe14 --- /dev/null +++ b/src/mod/applications/mod_avmd/buffer.c @@ -0,0 +1,23 @@ +#ifndef __BUFFER_H__ +#include "buffer.h" +#endif + +extern size_t next_power_of_2(size_t v) +{ + size_t prev; + size_t tmp = 1; + + v++; + + do{ + prev = v; + v &= ~tmp; + tmp <<= 1; + }while(v != 0); + + prev <<= 1; + + return prev; +} + + diff --git a/src/mod/applications/mod_avmd/buffer.h b/src/mod/applications/mod_avmd/buffer.h new file mode 100644 index 0000000000..cadf76d733 --- /dev/null +++ b/src/mod/applications/mod_avmd/buffer.h @@ -0,0 +1,97 @@ +#ifndef __BUFFER_H__ +#define __BUFFER_H__ +#include +#include + +#define BUFF_TYPE double + +typedef struct { + size_t pos; + size_t lpos; + BUFF_TYPE *buf; + size_t buf_len; + size_t mask; + size_t i; + size_t backlog; +} circ_buffer_t; + +extern size_t next_power_of_2(size_t v); + +#define INC_POS(b) \ + do{ \ + (b)->pos++; \ + (b)->pos &= (b)->mask; \ + (b)->lpos++; \ + if((b)->backlog < (b)->buf_len) (b)->backlog++; \ + }while(0) + +#define DEC_POS(b) \ + do{ \ + (b)->pos--; \ + (b)->pos &= (b)->mask; \ + (b)->lpos--; \ + if(((b)->backlog - 1) < (b)->backlog) (b)->backlog--; \ + }while(0) + +#define GET_SAMPLE(b, i) ((b)->buf[(i) & (b)->mask]) +#define SET_SAMPLE(b, i, v) ((b)->buf[(i) & (b)->mask] = (v)) + +#define INSERT_FRAME(b, f, l) \ + do{ \ + for((b)->i = 0; (b)->i < (l); (b)->i++){ \ + SET_SAMPLE((b), ((b)->i + (b)->pos), (f)[(b)->i]); \ + } \ + (b)->pos += (l); \ + (b)->lpos += (l); \ + (b)->pos %= (b)->buf_len; \ + (b)->backlog += (l); \ + if((b)->backlog > (b)->buf_len) (b)->backlog = (b)->buf_len; \ + }while(0) + +#define INSERT_INT16_FRAME(b, f, l) \ + do{ \ + for((b)->i = 0; (b)->i < (l); (b)->i++){ \ + SET_SAMPLE( \ + (b), \ + ((b)->i + (b)->pos), \ + ( \ + ((f)[(b)->i] >= 0) ? \ + ((BUFF_TYPE)(f)[(b)->i] / (BUFF_TYPE)INT16_MAX): \ + (0.0 - ((BUFF_TYPE)(f)[(b)->i] / (BUFF_TYPE)INT16_MIN)) \ + ) \ + ); \ + } \ + (b)->pos += (l); \ + (b)->lpos += (l); \ + (b)->pos &= (b)->mask; \ + (b)->backlog += (l); \ + if((b)->backlog > (b)->buf_len) (b)->backlog = (b)->buf_len; \ + }while(0) + + +#define CALC_BUFF_LEN(fl, bl) (((fl) >= (bl))? next_power_of_2((fl) << 1): next_power_of_2((bl) << 1)) + +#define INIT_CIRC_BUFFER(bf, bl, fl) \ + do{ \ + (bf)->buf_len = CALC_BUFF_LEN((fl), (bl)); \ + (bf)->mask = (bf)->buf_len - 1; \ + (bf)->buf = (BUFF_TYPE *)calloc((bf)->buf_len, sizeof(BUFF_TYPE)); \ + assert((bf)->buf != NULL); \ + (bf)->pos = 0; \ + (bf)->lpos = 0; \ + (bf)->backlog = 0; \ + }while(0) + +#define DESTROY_CIRC_BUFFER(b) free((b)->buf) +#define GET_BACKLOG_POS(b) ((b)->lpos - (b)->backlog) +#define GET_CURRENT_POS(b) ((b)->lpos) +#define GET_CURRENT_SAMPLE(b) GET_SAMPLE((b), GET_CURRENT_POS((b))) + +#define ADD_SAMPLE(b, s) \ + do{ \ + INC_POS((b)); \ + SET_SAMPLE((b), GET_CURRENT_POS((b)), (s)); \ + }while(0) + +#endif + diff --git a/src/mod/applications/mod_avmd/desa2.c b/src/mod/applications/mod_avmd/desa2.c new file mode 100644 index 0000000000..3998dc00c4 --- /dev/null +++ b/src/mod/applications/mod_avmd/desa2.c @@ -0,0 +1,59 @@ +#ifndef __DESA2_H__ +#include +#ifdef WIN32 +#include +#define ISNAN(x) (!!(_isnan(x))) +#else +#define ISNAN(x) (isnan(x)) +#endif +#include "buffer.h" +#include "desa2.h" +#include "options.h" + +#ifdef FASTMATH +#include "fast_acosf.h" +#endif + +extern double desa2(circ_buffer_t *b, size_t i) +{ + double d; + double n; + double x0; + double x1; + double x2; + double x3; + double x4; + double x2sq; + double result; + + x0 = GET_SAMPLE((b), (i)); + x1 = GET_SAMPLE((b), ((i) + 1)); + x2 = GET_SAMPLE((b), ((i) + 2)); + x3 = GET_SAMPLE((b), ((i) + 3)); + x4 = GET_SAMPLE((b), ((i) + 4)); + + x2sq = x2 * x2; + + d = 2.0 * ((x2sq) - (x1 * x3)); + if(d == 0.0) return 0.0; + + n = ((x2sq) - (x0 * x4)) - ((x1 * x1) - (x0 * x2)) - ((x3 * x3) - (x2 * x4)); + + +#ifdef FASTMATH + result = 0.5 * (double)fast_acosf((float)n/d); +#else + result = 0.5 * acos(n/d); +#endif + + if(ISNAN(result)){ + result = 0.0; + } + + + return result; + +} + +#endif + diff --git a/src/mod/applications/mod_avmd/desa2.h b/src/mod/applications/mod_avmd/desa2.h new file mode 100644 index 0000000000..8cd70c4063 --- /dev/null +++ b/src/mod/applications/mod_avmd/desa2.h @@ -0,0 +1,8 @@ +#ifndef __DESA2_H__ +#define __DESA2_H__ +#include +#include "buffer.h" + +extern double desa2(circ_buffer_t *b, size_t i); +#endif + diff --git a/src/mod/applications/mod_avmd/fast_acosf.c b/src/mod/applications/mod_avmd/fast_acosf.c new file mode 100644 index 0000000000..28a2a54ec1 --- /dev/null +++ b/src/mod/applications/mod_avmd/fast_acosf.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fast_acosf.h" + +#define SIGN_MASK (0x80000000) +#define DATA_MASK (0x07FFFFF8) + +#define SIGN_UNPACK_MASK (0x01000000) +#define DATA_UNPACK_MASK (0x00FFFFFF) + +#define VARIA_DATA_MASK (0x87FFFFF8) +#define CONST_DATA_MASK (0x38000000) + +#define ACOS_TABLE_LENGTH (1<<25) +#define ACOS_TABLE_FILENAME "/tmp/acos_table.dat" + +typedef union { + uint32_t i; + float f; +} float_conv_t; + +#ifdef FAST_ACOSF_TESTING +static float strip_float(float f); +#endif +static uint32_t index_from_float(float f); +static float float_from_index(uint32_t d); +static float *acos_table = NULL; +static int acos_fd = -1; + +#ifdef FAST_ACOSF_TESTING +static float strip_float(float f) +{ + float_conv_t d; + + d.i = d.i & (VARIA_DATA_MASK | CONST_DATA_MASK); + + return d.i; +} +#endif + +extern void compute_table(void) +{ + uint32_t i; + float f; + FILE *acos_table_file; + size_t ret; + + acos_table_file = fopen(ACOS_TABLE_FILENAME, "w"); + + + for(i = 0; i < (1 << 25); i++){ + f = acosf(float_from_index(i)); + ret = fwrite(&f, sizeof(f), 1, acos_table_file); + assert(ret != 0); + } + + + ret = fclose(acos_table_file); + assert(ret != EOF); +} + + +extern void init_fast_acosf(void) +{ + int ret; + + if(acos_table == NULL){ + ret = access(ACOS_TABLE_FILENAME, F_OK); + if(ret == 0) compute_table(); + + acos_fd = open(ACOS_TABLE_FILENAME, O_RDONLY); + if(acos_fd == -1) perror("Could not open file " ACOS_TABLE_FILENAME); + assert(acos_fd != -1); + acos_table = (float *)mmap( + NULL, + ACOS_TABLE_LENGTH * sizeof(float), + PROT_READ, + MAP_SHARED | MAP_POPULATE, + acos_fd, + 0 + ); + } +} + +extern void destroy_fast_acosf(void) +{ + int ret; + + ret = munmap(acos_table, ACOS_TABLE_LENGTH); + assert(ret != -1); + ret = close(acos_fd); + assert(ret != -1); + acos_table = NULL; +} + +extern float fast_acosf(float x) +{ + return acos_table[index_from_float(x)]; +} + + +static uint32_t index_from_float(float f) +{ + float_conv_t d; + + d.f = f; + return ((d.i & SIGN_MASK) >> 7) | ((d.i & DATA_MASK) >> 3); +} + + +static float float_from_index(uint32_t d) +{ + float_conv_t f; + + f.i = ((d & SIGN_UNPACK_MASK) << 7) | ((d & DATA_UNPACK_MASK) << 3) | CONST_DATA_MASK; + return f.f; +} + + + + diff --git a/src/mod/applications/mod_avmd/fast_acosf.h b/src/mod/applications/mod_avmd/fast_acosf.h new file mode 100644 index 0000000000..d608050da0 --- /dev/null +++ b/src/mod/applications/mod_avmd/fast_acosf.h @@ -0,0 +1,10 @@ +#ifndef __FAST_ACOSF_H__ +#define __FAST_ACOSF_H__ + +extern void init_fast_acosf(void); +extern float fast_acosf(float x); +extern void destroy_fast_acosf(void); +extern void compute_table(void); + +#endif + diff --git a/src/mod/applications/mod_avmd/goertzel.c b/src/mod/applications/mod_avmd/goertzel.c new file mode 100644 index 0000000000..357af98360 --- /dev/null +++ b/src/mod/applications/mod_avmd/goertzel.c @@ -0,0 +1,36 @@ +#ifndef __GOERTZEL_H__ +#include +#include "goertzel.h" +#include "buffer.h" + +/*! \brief Identify frequency components of a signal + * @author Eric des Courtis + * @param b A circular buffer + * @param pos Position in the buffer + * @param f Frequency to look at + * @param num Number of samples to look at + * @return A power estimate for frequency f at position pos in the stream + */ +extern double goertzel(circ_buffer_t *b, size_t pos, double f, size_t num) +{ + double s = 0.0; + double p = 0.0; + double p2 = 0.0; + double coeff; + size_t i; + + coeff = 2.0 * cos(2.0 * M_PI * f); + + for(i = 0; i < num; i++){ + /* TODO: optimize to avoid GET_SAMPLE when possible */ + s = GET_SAMPLE(b, i + pos) + (coeff * p) - p2; + p2 = p; + p = s; + } + + return (p2 * p2) + (p * p) - (coeff * p2 * p); +} + + +#endif + diff --git a/src/mod/applications/mod_avmd/goertzel.h b/src/mod/applications/mod_avmd/goertzel.h new file mode 100644 index 0000000000..6e976662c0 --- /dev/null +++ b/src/mod/applications/mod_avmd/goertzel.h @@ -0,0 +1,11 @@ +#ifndef __GOERTZEL_H__ +#define __GOERTZEL_H__ + +#include +#include "buffer.h" + +extern double goertzel(circ_buffer_t *b, size_t pos, double f, size_t num); + +#endif + + diff --git a/src/mod/applications/mod_avmd/mod_avmd.c b/src/mod/applications/mod_avmd/mod_avmd.c new file mode 100644 index 0000000000..77a40de337 --- /dev/null +++ b/src/mod/applications/mod_avmd/mod_avmd.c @@ -0,0 +1,585 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2010, Eric des Courtis + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * Eric des Courtis + * Copyright (C) Benbria. All Rights Reserved. + * + * Contributor(s): + * + * Eric des Courtis + * + * mod_avmd.c -- Advanced Voicemail Detection Module + * + * This module detects voicemail beeps using a generalized approach. + * + */ + +#include +#include +#include +#include +#include +#ifdef WIN32 +#include +#define ISNAN(x) (!!(_isnan(x))) +#else +#define ISNAN(x) (isnan(x)) +#endif + +/*! Calculate how many audio samples per ms based on the rate */ +#define SAMPLES_PER_MS(r, m) ((r) / (1000/(m))) +/*! Minimum beep length */ +#define BEEP_TIME (100) +/*! How often to evaluate the output of desa2 in ms */ +#define SINE_TIME (10) +/*! How long in samples does desa2 results get evaluated */ +#define SINE_LEN(r) SAMPLES_PER_MS((r), SINE_TIME) +/*! How long in samples is the minimum beep length */ +#define BEEP_LEN(r) SAMPLES_PER_MS((r), BEEP_TIME) +/*! Number of points in desa2 sample */ +#define P (5) +/*! Guesstimate frame length in ms */ +#define FRAME_TIME (20) +/*! Length in samples of the frame (guesstimate) */ +#define FRAME_LEN(r) SAMPLES_PER_MS((r), FRAME_TIME) +/*! Conversion to Hertz */ +#define TO_HZ(r, f) (((r) * (f)) / (2.0 * M_PI)) +/*! Minimum beep frequency in Hertz */ +#define MIN_FREQUENCY (300.0) +/*! Maximum beep frequency in Hertz */ +#define MAX_FREQUENCY (1500.0) + +#include "amplitude.h" +#include "buffer.h" +#include "desa2.h" +#include "goertzel.h" +#include "psi.h" +#include "sma_buf.h" +#include "options.h" + +#ifdef FASTMATH +#include "fast_acosf.h" +#endif + +/*! Syntax of the API call. */ +#define AVMD_SYNTAX " " + +/*! Number of expected parameters in api call. */ +#define AVMD_PARAMS 2 + +/*! FreeSWITCH CUSTOM event type. */ +#define AVMD_EVENT_BEEP "avmd::beep" + + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_avmd_shutdown); +SWITCH_STANDARD_API(avmd_api_main); + +SWITCH_MODULE_LOAD_FUNCTION(mod_avmd_load); +SWITCH_MODULE_DEFINITION(mod_avmd, mod_avmd_load, NULL, NULL); +SWITCH_STANDARD_APP(avmd_start_function); + +/*! Status of the beep detection */ +typedef enum { + BEEP_DETECTED, + BEEP_NOTDETECTED +} avmd_beep_state_t; + +/*! Data related to the current status of the beep */ +typedef struct { + avmd_beep_state_t beep_state; + size_t last_beep; +} avmd_state_t; + +/*! Type that holds session information pertinent to the avmd module. */ +typedef struct { + /*! Internal FreeSWITCH session. */ + switch_core_session_t *session; + uint32_t rate; + circ_buffer_t b; + sma_buffer_t sma_b; + size_t pos; + /* freq_table_t ft; */ + avmd_state_t state; +} avmd_session_t; + +static void avmd_process(avmd_session_t *session, switch_frame_t *frame); +static switch_bool_t avmd_callback(switch_media_bug_t * bug, void *user_data, switch_abc_type_t type); +static void init_avmd_session_data(avmd_session_t *avmd_session, switch_core_session_t *fs_session); + + +/*! \brief The avmd session data initialization function + * @author Eric des Courtis + * @param avmd_session A reference to a avmd session + * @param fs_session A reference to a FreeSWITCH session + */ +static void init_avmd_session_data(avmd_session_t *avmd_session, switch_core_session_t *fs_session) +{ + /*! This is a worst case sample rate estimate */ + avmd_session->rate = 48000; + INIT_CIRC_BUFFER(&avmd_session->b, BEEP_LEN(avmd_session->rate), FRAME_LEN(avmd_session->rate)); + + avmd_session->session = fs_session; + avmd_session->pos = 0; + avmd_session->state.last_beep = 0; + avmd_session->state.beep_state = BEEP_NOTDETECTED; + + INIT_SMA_BUFFER( + &avmd_session->sma_b, + BEEP_LEN(avmd_session->rate) / SINE_LEN(avmd_session->rate), + fs_session + ); +} + + +/*! \brief The callback function that is called when new audio data becomes available + * + * @author Eric des Courtis + * @param bug A reference to the media bug. + * @param user_data The session information for this call. + * @param type The switch callback type. + * @return The success or failure of the function. + */ +static switch_bool_t avmd_callback(switch_media_bug_t * bug, void *user_data, switch_abc_type_t type) +{ + avmd_session_t *avmd_session; + switch_codec_t *read_codec; + switch_frame_t *frame; + + + avmd_session = (avmd_session_t *) user_data; + if (avmd_session == NULL) { + return SWITCH_FALSE; + } + + switch (type) { + + case SWITCH_ABC_TYPE_INIT: + read_codec = switch_core_session_get_read_codec(avmd_session->session); + avmd_session->rate = read_codec->implementation->samples_per_second; + /* avmd_session->vmd_codec.channels = read_codec->implementation->number_of_channels; */ + break; + + case SWITCH_ABC_TYPE_READ_PING: + break; + case SWITCH_ABC_TYPE_CLOSE: + break; + case SWITCH_ABC_TYPE_READ: + break; + case SWITCH_ABC_TYPE_WRITE: + break; + + case SWITCH_ABC_TYPE_READ_REPLACE: + frame = switch_core_media_bug_get_read_replace_frame(bug); + avmd_process(avmd_session, frame); + return SWITCH_TRUE; + + case SWITCH_ABC_TYPE_WRITE_REPLACE: + break; + } + + return SWITCH_TRUE; +} + +/*! \brief FreeSWITCH module loading function + * + * @author Eric des Courtis + * @return Load success or failure. + */ +SWITCH_MODULE_LOAD_FUNCTION(mod_avmd_load) +{ + + switch_application_interface_t *app_interface; + switch_api_interface_t *api_interface; + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + switch_log_printf( + SWITCH_CHANNEL_LOG, + SWITCH_LOG_NOTICE, + "Advanced Voicemail detection enabled\n" + ); + +#ifdef FASTMATH + init_fast_acosf(); + switch_log_printf( + SWITCH_CHANNEL_LOG, + SWITCH_LOG_NOTICE, + "Advanced Voicemail detection: fast math enabled\n" + ); +#endif + + SWITCH_ADD_APP( + app_interface, + "avmd", + "Beep detection", + "Advanced detection of voicemail beeps", + avmd_start_function, + "[start] [stop]", + SAF_NONE + ); + + SWITCH_ADD_API(api_interface, "avmd", "Voicemail beep detection", avmd_api_main, AVMD_SYNTAX); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/*! \brief FreeSWITCH application handler function. + * This handles calls made from applications such as LUA and the dialplan + * + * @author Eric des Courtis + * @return Success or failure of the function. + */ +SWITCH_STANDARD_APP(avmd_start_function) +{ + switch_media_bug_t *bug; + switch_status_t status; + switch_channel_t *channel; + avmd_session_t *avmd_session; + + if (session == NULL) + return; + + channel = switch_core_session_get_channel(session); + + /* Is this channel already using avmd ? */ + bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_avmd_"); + /* If it is using avmd */ + if (bug != NULL) { + /* If we have a stop remove audio bug */ + if (strcasecmp(data, "stop") == 0) { + switch_channel_set_private(channel, "_avmd_", NULL); + switch_core_media_bug_remove(session, &bug); + return; + } + + /* We have already started */ + switch_log_printf( + SWITCH_CHANNEL_SESSION_LOG(session), + SWITCH_LOG_WARNING, + "Cannot run 2 at once on the same channel!\n" + ); + + return; + } + + avmd_session = (avmd_session_t *)switch_core_session_alloc(session, sizeof(avmd_session_t)); + + init_avmd_session_data(avmd_session, session); + + status = switch_core_media_bug_add( + session, + "avmd", + NULL, + avmd_callback, + avmd_session, + 0, + SMBF_READ_REPLACE, + &bug + ); + + if (status != SWITCH_STATUS_SUCCESS) { + switch_log_printf( + SWITCH_CHANNEL_SESSION_LOG(session), + SWITCH_LOG_ERROR, + "Failure hooking to stream\n" + ); + + return; + } + + switch_channel_set_private(channel, "_avmd_", bug); +} + +/*! \brief Called when the module shuts down + * + * @author Eric des Courtis + * @return The success or failure of the function. + */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_avmd_shutdown) +{ + +#ifdef FASTMATH + destroy_fast_acosf(); +#endif + + switch_log_printf( + SWITCH_CHANNEL_LOG, + SWITCH_LOG_NOTICE, + "Advanced Voicemail detection disabled\n" + ); + + return SWITCH_STATUS_SUCCESS; +} + +/*! \brief FreeSWITCH API handler function. + * This function handles API calls such as the ones from mod_event_socket and in some cases + * scripts such as LUA scripts. + * + * @author Eric des Courtis + * @return The success or failure of the function. + */ +SWITCH_STANDARD_API(avmd_api_main) +{ + switch_core_session_t *fs_session = NULL; + switch_media_bug_t *bug; + avmd_session_t *avmd_session; + switch_channel_t *channel; + switch_status_t status; + int argc; + char *argv[AVMD_PARAMS]; + char *ccmd = NULL; + char *uuid; + char *command; + + /* No command? Display usage */ + if (zstr(cmd)) { + stream->write_function(stream, "-USAGE: %s\n", AVMD_SYNTAX); + return SWITCH_STATUS_SUCCESS; + } + + /* Duplicated contents of original string */ + ccmd = strdup(cmd); + /* Separate the arguments */ + argc = switch_separate_string(ccmd, ' ', argv, AVMD_PARAMS); + + /* If we don't have the expected number of parameters + * display usage */ + if (argc != AVMD_PARAMS) { + stream->write_function(stream, "-USAGE: %s\n", AVMD_SYNTAX); + goto end; + } + + uuid = argv[0]; + command = argv[1]; + + /* using uuid locate a reference to the FreeSWITCH session */ + fs_session = switch_core_session_locate(uuid); + + /* If the session was not found exit */ + if (fs_session == NULL) { + stream->write_function(stream, "-USAGE: %s\n", AVMD_SYNTAX); + goto end; + } + + /* Get current channel of the session to tag the session + * This indicates that our module is present */ + channel = switch_core_session_get_channel(fs_session); + + /* Is this channel already set? */ + bug = (switch_media_bug_t *) switch_channel_get_private(channel, "_avmd_"); + /* If yes */ + if (bug != NULL) { + /* If we have a stop remove audio bug */ + if (strcasecmp(command, "stop") == 0) { + switch_channel_set_private(channel, "_avmd_", NULL); + switch_core_media_bug_remove(fs_session, &bug); + switch_safe_free(ccmd); + stream->write_function(stream, "+OK\n"); + goto end; + } + + /* We have already started */ + switch_log_printf( + SWITCH_CHANNEL_SESSION_LOG(session), + SWITCH_LOG_WARNING, + "Cannot run 2 at once on the same channel!\n" + ); + + goto end; + } + + /* If we don't see the expected start exit */ + if (strcasecmp(command, "start") != 0) { + stream->write_function(stream, "-USAGE: %s\n", AVMD_SYNTAX); + goto end; + } + + /* Allocate memory attached to this FreeSWITCH session for + * use in the callback routine and to store state information */ + avmd_session = (avmd_session_t *) switch_core_session_alloc(fs_session, sizeof(avmd_session_t)); + + init_avmd_session_data(avmd_session, fs_session); + + /* Add a media bug that allows me to intercept the + * reading leg of the audio stream */ + status = switch_core_media_bug_add( + fs_session, + "avmd", + NULL, + avmd_callback, + avmd_session, + 0, + SMBF_READ_REPLACE, + &bug + ); + + /* If adding a media bug fails exit */ + if (status != SWITCH_STATUS_SUCCESS) { + + switch_log_printf( + SWITCH_CHANNEL_SESSION_LOG(session), + SWITCH_LOG_ERROR, + "Failure hooking to stream\n" + ); + + goto end; + } + + /* Set the vmd tag to detect an existing vmd media bug */ + switch_channel_set_private(channel, "_avmd_", bug); + + /* Everything went according to plan! Notify the user */ + stream->write_function(stream, "+OK\n"); + + +end: + + if (fs_session) { + switch_core_session_rwunlock(fs_session); + } + + switch_safe_free(ccmd); + + return SWITCH_STATUS_SUCCESS; +} + +/*! \brief Process one frame of data with avmd algorithm + * @author Eric des Courtis + * @param session An avmd session + * @param frame A audio frame + */ +static void avmd_process(avmd_session_t *session, switch_frame_t *frame) +{ + switch_event_t *event; + switch_status_t status; + switch_event_t *event_copy; + switch_channel_t *channel; + + circ_buffer_t *b; + size_t pos; + double f; + double a; + double error = 0.0; + double success = 0.0; + double amp = 0.0; + double s_rate; + double e_rate; + double avg_a; + double sine_len; + uint32_t sine_len_i; + int valid; + + b = &session->b; + + /*! If beep has already been detected skip the CPU heavy stuff */ + if(session->state.beep_state == BEEP_DETECTED){ + return; + } + + /*! Precompute values used heavily in the inner loop */ + sine_len_i = SINE_LEN(session->rate); + sine_len = (double)sine_len_i; + + + channel = switch_core_session_get_channel(session->session); + + /*! Insert frame of 16 bit samples into buffer */ + INSERT_INT16_FRAME(b, (int16_t *)(frame->data), frame->samples); + + /*! INNER LOOP -- OPTIMIZATION TARGET */ + for(pos = GET_BACKLOG_POS(b); pos != (GET_CURRENT_POS(b) - P); pos++){ + + /*! Get a desa2 frequency estimate in Hertz */ + f = TO_HZ(session->rate, desa2(b, pos)); + + /*! Don't caculate amplitude if frequency is not within range */ + if(f < MIN_FREQUENCY || f > MAX_FREQUENCY) { + a = 0.0; + error += 1.0; + } else { + a = amplitude(b, pos, f); + success += 1.0; + if(!ISNAN(a)){ + amp += a; + } + } + + /*! Every once in a while we evaluate the desa2 and amplitude results */ + if(((pos + 1) % sine_len_i) == 0){ + s_rate = success / (error + success); + e_rate = error / (error + success); + avg_a = amp / sine_len; + + /*! Results out of these ranges are considered invalid */ + valid = 0; + if( s_rate > 0.60 && avg_a > 0.50) valid = 1; + else if(s_rate > 0.65 && avg_a > 0.45) valid = 1; + else if(s_rate > 0.70 && avg_a > 0.40) valid = 1; + else if(s_rate > 0.80 && avg_a > 0.30) valid = 1; + else if(s_rate > 0.95 && avg_a > 0.05) valid = 1; + else if(s_rate >= 0.99 && avg_a > 0.04) valid = 1; + else if(s_rate == 1.00 && avg_a > 0.02) valid = 1; + + if(valid) APPEND_SMA_VAL(&session->sma_b, s_rate * avg_a); + else APPEND_SMA_VAL(&session->sma_b, 0.0 ); + + /*! If sma is higher then 0 we have some kind of detection (increase this value to eliminate false positives ex: 0.01) */ + if(session->sma_b.sma > 0.00){ + /*! Throw an event to FreeSWITCH */ + status = switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, AVMD_EVENT_BEEP); + if(status != SWITCH_STATUS_SUCCESS) { + return; + } + + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Beep-Status", "stop"); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Unique-ID", switch_core_session_get_uuid(session->session)); + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "call-command", "avmd"); + + if ((switch_event_dup(&event_copy, event)) != SWITCH_STATUS_SUCCESS) { + return; + } + + switch_core_session_queue_event(session->session, &event); + switch_event_fire(&event_copy); + + switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session->session), SWITCH_LOG_INFO, "<<< AVMD - Beep Detected >>>\n"); + switch_channel_set_variable(channel, "avmd_detect", "TRUE"); + RESET_SMA_BUFFER(&session->sma_b); + session->state.beep_state = BEEP_DETECTED; + + return; + } + + amp = 0.0; + success = 0.0; + error = 0.0; + } + } +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4: + */ + diff --git a/src/mod/applications/mod_avmd/options.h b/src/mod/applications/mod_avmd/options.h new file mode 100644 index 0000000000..9be3263938 --- /dev/null +++ b/src/mod/applications/mod_avmd/options.h @@ -0,0 +1,7 @@ +#ifndef __OPTIONS_H__ +#define __OPTIONS_H__ + +/* #define FASTMATH */ + +#endif + diff --git a/src/mod/applications/mod_avmd/psi.h b/src/mod/applications/mod_avmd/psi.h new file mode 100644 index 0000000000..fc98498ef9 --- /dev/null +++ b/src/mod/applications/mod_avmd/psi.h @@ -0,0 +1,9 @@ +#ifndef __PSI_H__ +#define __PSI_H__ +#include "buffer.h" + +#define PSI(b, i) (GET_SAMPLE((b), ((i) + 1))*GET_SAMPLE((b), ((i) + 1))-GET_SAMPLE((b), ((i) + 2))*GET_SAMPLE((b), ((i) + 0))) + +#endif + + diff --git a/src/mod/applications/mod_avmd/sma_buf.h b/src/mod/applications/mod_avmd/sma_buf.h new file mode 100644 index 0000000000..e2baabc71e --- /dev/null +++ b/src/mod/applications/mod_avmd/sma_buf.h @@ -0,0 +1,82 @@ +#ifndef __SMA_BUFFER_H__ +#define __SMA_BUFFER_H__ +#include +#include +#include +#include +#include +#include "buffer.h" + +typedef struct { + size_t len; + BUFF_TYPE *data; + BUFF_TYPE sma; + size_t pos; + size_t lpos; +} sma_buffer_t; + +#define INIT_SMA_BUFFER(b, l, s) \ + do{ \ + (void)memset((b), 0, sizeof(sma_buffer_t)); \ + (b)->len = (l); \ + (b)->data = (BUFF_TYPE *)switch_core_session_alloc((s), sizeof(BUFF_TYPE) * (l)); \ + assert((b)->data != NULL); \ + (void)memset((b)->data, 0, sizeof(BUFF_TYPE) * (l)); \ + (b)->sma = 0.0; \ + (b)->pos = 0; \ + (b)->lpos = 0; \ + }while(0) + +#define GET_SMA_SAMPLE(b, p) ((b)->data[(p) % (b)->len]) +#define SET_SMA_SAMPLE(b, p, v) ((b)->data[(p) % (b)->len] = (v)) +#define GET_CURRENT_SMA_POS(b) ((b)->lpos) + +#define INC_SMA_POS(b) \ + do { \ + (b)->lpos++; \ + (b)->pos = (b)->lpos % (b)->len; \ + }while(0) + +#define APPEND_SMA_VAL(b, v) \ + do{ \ + INC_SMA_POS(b); \ + (b)->sma -= ((b)->data[(b)->pos] / (BUFF_TYPE)(b)->len); \ + (b)->data[(b)->pos] = (v); \ + (b)->sma += ((b)->data[(b)->pos] / (BUFF_TYPE)(b)->len); \ + }while(0) + +#define RESET_SMA_BUFFER(b) \ + do{ \ + (b)->sma = 0.0; \ + (void)memset((b)->data, 0, sizeof(BUFF_TYPE) * (b)->len); \ + }while(0) + +/* +#define DESTROY_SMA_BUFFER(b) \ + do{ \ + free((b)->data); \ + }while(0); + +*/ +#endif +/* + +int main(void) +{ + int i; + sma_buffer_t b; + + INIT_SMA_BUFFER(&b, 100); + + for(i = 0; i < 20; i++){ + APPEND_SMA_VAL(&b, 100.0); + printf("SMA = %lf\n", b.sma); + } + + DESTROY_SMA_BUFFER(&b); + + return EXIT_SUCCESS; +} + +*/ +