Added new FreeSWITCH module mod_avmd or the Advanced Voicemail Detection Module.
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?
This commit is contained in:
parent
596c001231
commit
10c6a30a59
|
@ -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
|
||||
#
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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:
|
||||
*/
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef __OPTIONS_H__
|
||||
#define __OPTIONS_H__
|
||||
|
||||
/* #define FASTMATH */
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
*/
|
||||
|
Loading…
Reference in New Issue