]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
Added new FreeSWITCH module mod_avmd or the Advanced Voicemail Detection Module.
authorEric des Courtis <eric.des.courtis@benbria.ca>
Tue, 25 May 2010 19:03:14 +0000 (15:03 -0400)
committerEric des Courtis <eric.des.courtis@benbria.ca>
Tue, 25 May 2010 19:03:14 +0000 (15:03 -0400)
It provides better detection than mod_vmd but is a little more CPU intensive than mod_vmd.

Perhaps someone would like to CUDAfy this code?

15 files changed:
src/mod/applications/mod_avmd/Makefile [new file with mode: 0644]
src/mod/applications/mod_avmd/amplitude.c [new file with mode: 0644]
src/mod/applications/mod_avmd/amplitude.h [new file with mode: 0644]
src/mod/applications/mod_avmd/buffer.c [new file with mode: 0644]
src/mod/applications/mod_avmd/buffer.h [new file with mode: 0644]
src/mod/applications/mod_avmd/desa2.c [new file with mode: 0644]
src/mod/applications/mod_avmd/desa2.h [new file with mode: 0644]
src/mod/applications/mod_avmd/fast_acosf.c [new file with mode: 0644]
src/mod/applications/mod_avmd/fast_acosf.h [new file with mode: 0644]
src/mod/applications/mod_avmd/goertzel.c [new file with mode: 0644]
src/mod/applications/mod_avmd/goertzel.h [new file with mode: 0644]
src/mod/applications/mod_avmd/mod_avmd.c [new file with mode: 0644]
src/mod/applications/mod_avmd/options.h [new file with mode: 0644]
src/mod/applications/mod_avmd/psi.h [new file with mode: 0644]
src/mod/applications/mod_avmd/sma_buf.h [new file with mode: 0644]

diff --git a/src/mod/applications/mod_avmd/Makefile b/src/mod/applications/mod_avmd/Makefile
new file mode 100644 (file)
index 0000000..5480f8d
--- /dev/null
@@ -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 (file)
index 0000000..1f9d752
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef __AMPLITUDE_H__
+#include <math.h>
+#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 (file)
index 0000000..4d67bd9
--- /dev/null
@@ -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 (file)
index 0000000..02d306f
--- /dev/null
@@ -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 (file)
index 0000000..cadf76d
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef __BUFFER_H__
+#define __BUFFER_H__
+#include <stdlib.h>
+#include <assert.h>
+
+#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 (file)
index 0000000..3998dc0
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef __DESA2_H__
+#include <stdio.h>
+#ifdef WIN32
+#include <float.h>
+#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 (file)
index 0000000..8cd70c4
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef __DESA2_H__
+#define __DESA2_H__
+#include <math.h>
+#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 (file)
index 0000000..28a2a54
--- /dev/null
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <assert.h>
+#include <errno.h>
+#include <math.h>
+#include <unistd.h>
+#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 (file)
index 0000000..d608050
--- /dev/null
@@ -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 (file)
index 0000000..357af98
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef __GOERTZEL_H__
+#include <math.h>
+#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 (file)
index 0000000..6e97666
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef __GOERTZEL_H__
+#define __GOERTZEL_H__
+
+#include <stdint.h>
+#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 (file)
index 0000000..77a40de
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2010, Eric des Courtis <eric.des.courtis@benbria.com>
+ *
+ * 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 <eric.des.courtis@benbria.com>
+ * Copyright (C) Benbria. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Eric des Courtis <eric.des.courtis@benbria.com>
+ *
+ * mod_avmd.c -- Advanced Voicemail Detection Module
+ *
+ * This module detects voicemail beeps using a generalized approach.
+ *
+ */
+
+#include <switch.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#ifdef WIN32
+#include <float.h>
+#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 "<uuid> <command>"
+
+/*! 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 (file)
index 0000000..9be3263
--- /dev/null
@@ -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 (file)
index 0000000..fc98498
--- /dev/null
@@ -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 (file)
index 0000000..e2baabc
--- /dev/null
@@ -0,0 +1,82 @@
+#ifndef __SMA_BUFFER_H__
+#define __SMA_BUFFER_H__
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#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;
+}
+
+*/
+