| Added dtmf2num tone decoding - warvox - VoIP based wardialing tool, forked from… | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit 6ff719b811c876e58c6477c8f70411c5ce5a0291 | |
| parent 953ea21a9aeace23a66b41311f6cb5e21c01137d | |
| Author: HD Moore <[email protected]> | |
| Date: Tue, 26 May 2009 03:20:48 +0000 | |
| Added dtmf2num tone decoding | |
| Diffstat: | |
| M Makefile | 7 ++++++- | |
| M bin/verify_install.rb | 6 ++++++ | |
| M docs/ChangeLog | 1 + | |
| M etc/warvox.conf | 1 + | |
| M lib/warvox/jobs/analysis.rb | 27 ++++++++++++++++++++++++--- | |
| A src/dtmf2num/Makefile | 16 ++++++++++++++++ | |
| A src/dtmf2num/README | 5 +++++ | |
| A src/dtmf2num/dsp.c | 502 +++++++++++++++++++++++++++++… | |
| A src/dtmf2num/dtmf2num.c | 372 +++++++++++++++++++++++++++++… | |
| A src/dtmf2num/mywav.h | 244 +++++++++++++++++++++++++++++… | |
| A src/dtmf2num/resample2.c | 342 +++++++++++++++++++++++++++++… | |
| M web/app/controllers/analyze_contro… | 4 ++-- | |
| M web/app/views/analyze/view.html.erb | 9 +++++++-- | |
| M web/db/schema.rb | 4 +++- | |
| 14 files changed, 1531 insertions(+), 9 deletions(-) | |
| --- | |
| diff --git a/Makefile b/Makefile | |
| @@ -3,8 +3,9 @@ all: test | |
| test: install | |
| bin/verify_install.rb | |
| -install: iaxrecord ruby-kissfft db | |
| +install: iaxrecord dtmf2num ruby-kissfft db | |
| cp -a src/iaxrecord/iaxrecord bin/ | |
| + cp -a src/dtmf2num/dtmf2num bin/ | |
| ruby-kissfft-install: ruby-kissfft | |
| cp -a src/ruby-kissfft/kissfft.so lib/ | |
| @@ -12,6 +13,9 @@ ruby-kissfft-install: ruby-kissfft | |
| iaxrecord: | |
| make -C src/iaxrecord/ | |
| +dtmf2num: | |
| + make -C src/dtmf2num/ | |
| + | |
| ruby-kissfft: | |
| ( cd src/ruby-kissfft/; ruby extconf.rb ) | |
| make -C src/ruby-kissfft/ | |
| @@ -29,4 +33,5 @@ clean: | |
| ( cd src/ruby-kissfft/; ruby extconf.rb ) | |
| make -C src/ruby-kissfft/ clean | |
| make -C src/iaxrecord/ clean | |
| + make -C src/dtmf2num/ clean | |
| rm -f bin/iaxrecord lib/kissfft.so | |
| diff --git a/bin/verify_install.rb b/bin/verify_install.rb | |
| @@ -63,6 +63,12 @@ if(not WarVOX::Config.tool_path('iaxrecord')) | |
| end | |
| puts "[*] The IAXRECORD binary appears to be available" | |
| +if(not WarVOX::Config.tool_path('dtmf2num')) | |
| + puts "[*] ERROR: The 'dtmf2num' binary could not be installed" | |
| + exit | |
| +end | |
| +puts "[*] The DTMF2NUM binary appears to be available" | |
| + | |
| puts " " | |
| puts "[*] Congratulations! You are now ready to run WarVOX" | |
| diff --git a/docs/ChangeLog b/docs/ChangeLog | |
| @@ -4,6 +4,7 @@ | |
| * overhaul of the signature system (etc/sigs/*.rb) | |
| * filter analysis results by line type | |
| * overhaul of results view + show signatures | |
| + * added dtmf2num support to decode dtmf tones | |
| 2009-05-10 H D Moore <hdm[at]metasploit.com> | |
| diff --git a/etc/warvox.conf b/etc/warvox.conf | |
| @@ -25,6 +25,7 @@ tools: | |
| sox: sox | |
| lame: lame | |
| iaxrecord: %BASE%/bin/iaxrecord | |
| + dtmf2num: %BASE%/bin/dtmf2num | |
| # | |
| # Concurrent processing jobs, change this to | |
| diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb | |
| @@ -284,7 +284,7 @@ class Analysis < Base | |
| plotter.puts("plot \"#{frefile.path}\" using 1:2 notitle with … | |
| plotter.flush | |
| - system("gnuplot #{plotter.path}") | |
| + system("#{WarVOX::Config.tool_path('gnuplot')} #{plotter.path}… | |
| File.unlink(plotter.path) | |
| File.unlink(datfile.path) | |
| File.unlink(frefile.path) | |
| @@ -292,11 +292,32 @@ class Analysis < Base | |
| datfile.close | |
| frefile.path | |
| + | |
| + # Detect DTMF and MF tones | |
| + dtmf = '' | |
| + mf = '' | |
| + pfd = IO.popen("#{WarVOX::Config.tool_path('dtmf2num')} -r 800… | |
| + pfd.each_line do |line| | |
| + line = line.strip | |
| + if(line.strip =~ /^- MF numbers:\s+(.*)/) | |
| + next if $1 == 'none' | |
| + mf = $1 | |
| + end | |
| + if(line.strip =~ /^- DTMF numbers:\s+(.*)/) | |
| + next if $1 == 'none' | |
| + dtmf = $1 | |
| + end | |
| + end | |
| + pfd.close | |
| + res[:dtmf] = dtmf | |
| + res[:mf] = mf | |
| + | |
| + | |
| # Generate a MP3 audio file | |
| - system("sox -s -2 -r 8000 -t raw -c 1 #{rawfile.path} #{bname}… | |
| + system("#{WarVOX::Config.tool_path('sox')} -s -2 -r 8000 -t ra… | |
| # Default samples at 8k, bump it to 32k to get better quality | |
| - system("lame -b 32 #{bname}.wav #{bname}.mp3 >/dev/null 2>&1") | |
| + system("#{WarVOX::Config.tool_path('lame')} -b 32 #{bname}.wav… | |
| File.unlink("#{bname}.wav") | |
| File.unlink(rawfile.path) | |
| diff --git a/src/dtmf2num/Makefile b/src/dtmf2num/Makefile | |
| @@ -0,0 +1,16 @@ | |
| +EXE = dtmf2num | |
| +CFLAGS += -O2 -s | |
| +BINDIR = $(PREFIX)/bin | |
| +LIBS = -lm | |
| + | |
| +all: | |
| + $(CC) $(CFLAGS) -c dtmf2num.c | |
| + $(CC) $(CFLAGS) -c dsp.c | |
| + $(CC) $(CFLAGS) -c resample2.c | |
| + $(CC) $(CFLAGS) -o $(EXE) $(LIBS) dtmf2num.o dsp.o resample2.o | |
| + | |
| +clean: | |
| + @rm -f *.o $(EXE) | |
| + | |
| +.PHONY: | |
| + install | |
| diff --git a/src/dtmf2num/README b/src/dtmf2num/README | |
| @@ -0,0 +1,5 @@ | |
| +This is a barely tweaked copy of DTMF2NUM, developed by Luigi Auriemma | |
| + - http://aluigi.org/mytoolz.htm#dtmf2num | |
| + | |
| +This tool is provided under the GPL: | |
| + - http://www.gnu.org/licenses/gpl.txt | |
| diff --git a/src/dtmf2num/dsp.c b/src/dtmf2num/dsp.c | |
| @@ -0,0 +1,502 @@ | |
| +/* | |
| + * Asterisk -- An open source telephony toolkit. | |
| + * | |
| + * Copyright (C) 1999 - 2005, Digium, Inc. | |
| + * | |
| + * Mark Spencer <[email protected]> | |
| + * | |
| + * Goertzel routines are borrowed from Steve Underwood's tremendous work on the | |
| + * DTMF detector. | |
| + * | |
| + * See http://www.asterisk.org for more information about | |
| + * the Asterisk project. Please do not directly contact | |
| + * any of the maintainers of this project for assistance; | |
| + * the project provides a web site, mailing lists and IRC | |
| + * channels for your use. | |
| + * | |
| + * This program is free software, distributed under the terms of | |
| + * the GNU General Public License Version 2. See the LICENSE file | |
| + * at the top of the source tree. | |
| + */ | |
| + | |
| +/*! \file | |
| + * | |
| + * \brief Convenience Signal Processing routines | |
| + * | |
| + * \author Mark Spencer <[email protected]> | |
| + * \author Steve Underwood <[email protected]> | |
| + */ | |
| + | |
| +/* Some routines from tone_detect.c by Steven Underwood as published under the… | |
| +/* | |
| + tone_detect.c - General telephony tone detection, and specific | |
| + detection of DTMF. | |
| + | |
| + Copyright (C) 2001 Steve Underwood <[email protected]> | |
| + | |
| + Despite my general liking of the GPL, I place this code in the | |
| + public domain for the benefit of all mankind - even the slimy | |
| + ones who might try to proprietize my work and use it to my | |
| + detriment. | |
| +*/ | |
| + | |
| +/* | |
| + modifications for decoding also damaged audio by Luigi auriemma "// aluigi w… | |
| +*/ | |
| + | |
| +#include <stdio.h> | |
| +#include <stdlib.h> | |
| +#include <string.h> | |
| +#include <stdint.h> | |
| +#include <math.h> | |
| +#include <malloc.h> | |
| + | |
| +#define DSP_DIGITMODE_DTMF 0 … | |
| +#define DSP_DIGITMODE_MF 1 … | |
| + | |
| +#define DSP_DIGITMODE_NOQUELCH (1 << 8) /*!< Do … | |
| +#define DSP_DIGITMODE_RELAXDTMF (1 << 11) /*!< "… | |
| + | |
| +#define MAX_DTMF_DIGITS 1024 | |
| + | |
| +/* Basic DTMF specs: | |
| + * | |
| + * Minimum tone on = 40ms | |
| + * Minimum tone off = 50ms | |
| + * Maximum digit rate = 10 per second | |
| + * Normal twist <= 8dB accepted | |
| + * Reverse twist <= 4dB accepted | |
| + * S/N >= 15dB will detect OK | |
| + * Attenuation <= 26dB will detect OK | |
| + * Frequency tolerance +- 1.5% will detect, +-3.5% will reject | |
| + */ | |
| + | |
| +//#define DTMF_THRESHOLD 8.0e7 | |
| +#define DTMF_THRESHOLD 800000000.0 // aluigi work-around | |
| +#define DTMF_NORMAL_TWIST 6.3 /* 8dB */ | |
| +#ifdef RADIO_RELAX | |
| +#define DTMF_REVERSE_TWIST ((digitmode & DSP_DIGITMODE_RELAXDTMF) ? 6… | |
| +#else | |
| +#define DTMF_REVERSE_TWIST ((digitmode & DSP_DIGITMODE_RELAXDTMF) ? 4… | |
| +#endif | |
| +#define DTMF_RELATIVE_PEAK_ROW 6.3 /* 8dB */ | |
| +#define DTMF_RELATIVE_PEAK_COL 6.3 /* 8dB */ | |
| +#define DTMF_2ND_HARMONIC_ROW ((digitmode & DSP_DIGITMODE_RELAXDTMF) ? 1… | |
| +#define DTMF_2ND_HARMONIC_COL 63.1 /* 18dB */ | |
| +#define DTMF_TO_TOTAL_ENERGY 42.0 | |
| + | |
| +//#define BELL_MF_THRESHOLD 1.6e9 | |
| +#define BELL_MF_THRESHOLD DTMF_THRESHOLD // aluigi work-around | |
| +#define BELL_MF_TWIST 4.0 /* 6dB */ | |
| +#define BELL_MF_RELATIVE_PEAK 12.6 /* 11dB */ | |
| + | |
| +static int SAMPLE_RATE = 8000; | |
| + | |
| +typedef struct { | |
| + int v2; | |
| + int v3; | |
| + int chunky; | |
| + int fac; | |
| + int samples; | |
| +} goertzel_state_t; | |
| + | |
| +typedef struct { | |
| + int value; | |
| + int power; | |
| +} goertzel_result_t; | |
| + | |
| +typedef struct | |
| +{ | |
| + goertzel_state_t row_out[4]; | |
| + goertzel_state_t col_out[4]; | |
| + int lasthit; | |
| + int current_hit; | |
| + float energy; | |
| + int current_sample; | |
| +} dtmf_detect_state_t; | |
| + | |
| +typedef struct | |
| +{ | |
| + goertzel_state_t tone_out[6]; | |
| + int current_hit; | |
| + int hits[5]; | |
| + int current_sample; | |
| +} mf_detect_state_t; | |
| + | |
| +typedef struct | |
| +{ | |
| + char digits[MAX_DTMF_DIGITS + 1]; | |
| + int current_digits; | |
| + int detected_digits; | |
| + int lost_digits; | |
| + | |
| + union { | |
| + dtmf_detect_state_t dtmf; | |
| + mf_detect_state_t mf; | |
| + } td; | |
| +} digit_detect_state_t; | |
| + | |
| +static float dtmf_row[] = | |
| +{ | |
| + 697.0, 770.0, 852.0, 941.0 | |
| +}; | |
| +static float dtmf_col[] = | |
| +{ | |
| + 1209.0, 1336.0, 1477.0, 1633.0 | |
| +}; | |
| + | |
| +static float mf_tones[] = | |
| +{ | |
| + 700.0, 900.0, 1100.0, 1300.0, 1500.0, 1700.0 | |
| +}; | |
| + | |
| +static char dtmf_positions[] = "123A" "456B" "789C" "*0#D"; | |
| + | |
| +static char bell_mf_positions[] = "1247C-358A--69*---0B----#"; | |
| + | |
| +static inline void goertzel_sample(goertzel_state_t *s, short sample) | |
| +{ | |
| + int v1; | |
| + | |
| + v1 = s->v2; | |
| + s->v2 = s->v3; | |
| + | |
| + s->v3 = (s->fac * s->v2) >> 15; | |
| + s->v3 = s->v3 - v1 + (sample >> s->chunky); | |
| + if (abs(s->v3) > 32768) { | |
| + s->chunky++; | |
| + s->v3 = s->v3 >> 1; | |
| + s->v2 = s->v2 >> 1; | |
| + v1 = v1 >> 1; | |
| + } | |
| +} | |
| + | |
| +static inline void goertzel_update(goertzel_state_t *s, short *samps, int coun… | |
| +{ | |
| + int i; | |
| + | |
| + for (i=0;i<count;i++) | |
| + goertzel_sample(s, samps[i]); | |
| +} | |
| + | |
| +static inline float goertzel_result(goertzel_state_t *s) | |
| +{ | |
| + goertzel_result_t r; | |
| + r.value = (s->v3 * s->v3) + (s->v2 * s->v2); | |
| + r.value -= ((s->v2 * s->v3) >> 15) * s->fac; | |
| + r.power = s->chunky * 2; | |
| + return (float)r.value * (float)(1 << r.power); | |
| +} | |
| + | |
| +static inline void goertzel_init(goertzel_state_t *s, float freq, int samples) | |
| +{ | |
| + s->v2 = s->v3 = s->chunky = 0.0; | |
| + s->fac = (int)(32768.0 * 2.0 * cos(2.0 * M_PI * freq / SAMPLE_RATE)); | |
| + s->samples = samples; | |
| +} | |
| + | |
| +static inline void goertzel_reset(goertzel_state_t *s) | |
| +{ | |
| + s->v2 = s->v3 = s->chunky = 0.0; | |
| +} | |
| + | |
| +static void ast_dtmf_detect_init (dtmf_detect_state_t *s) | |
| +{ | |
| + int i; | |
| + | |
| + s->lasthit = 0; | |
| + s->current_hit = 0; | |
| + for (i = 0; i < 4; i++) { | |
| + goertzel_init (&s->row_out[i], dtmf_row[i], 102); | |
| + goertzel_init (&s->col_out[i], dtmf_col[i], 102); | |
| + s->energy = 0.0; | |
| + } | |
| + s->current_sample = 0; | |
| +} | |
| + | |
| +static void ast_mf_detect_init (mf_detect_state_t *s) | |
| +{ | |
| + int i; | |
| + s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0; | |
| + for (i = 0; i < 6; i++) { | |
| + goertzel_init (&s->tone_out[i], mf_tones[i], 160); | |
| + } | |
| + s->current_sample = 0; | |
| + s->current_hit = 0; | |
| +} | |
| + | |
| +static void ast_digit_detect_init(digit_detect_state_t *s, int mf) | |
| +{ | |
| + s->current_digits = 0; | |
| + s->detected_digits = 0; | |
| + s->lost_digits = 0; | |
| + s->digits[0] = '\0'; | |
| + | |
| + if (mf) | |
| + ast_mf_detect_init(&s->td.mf); | |
| + else | |
| + ast_dtmf_detect_init(&s->td.dtmf); | |
| +} | |
| + | |
| +static void store_digit(digit_detect_state_t *s, char digit) | |
| +{ | |
| + s->detected_digits++; | |
| + if (s->current_digits < MAX_DTMF_DIGITS) { | |
| + s->digits[s->current_digits++] = digit; | |
| + s->digits[s->current_digits] = '\0'; | |
| + } else { | |
| + //ast_log(LOG_WARNING, "Digit lost due to full buffer\n"); | |
| + s->lost_digits++; | |
| + } | |
| +} | |
| + | |
| +static int dtmf_detect(digit_detect_state_t *s, int16_t amp[], int samples, | |
| + int digitmode, int *writeback) | |
| +{ | |
| + float row_energy[4]; | |
| + float col_energy[4]; | |
| + float famp; | |
| + int i; | |
| + int j; | |
| + int sample; | |
| + int best_row; | |
| + int best_col; | |
| + int hit; | |
| + int limit; | |
| + | |
| + hit = 0; | |
| + for (sample = 0; sample < samples; sample = limit) { | |
| + /* 102 is optimised to meet the DTMF specs. */ | |
| + if ((samples - sample) >= (102 - s->td.dtmf.current_sample)) | |
| + limit = sample + (102 - s->td.dtmf.current_sample); | |
| + else | |
| + limit = samples; | |
| + /* The following unrolled loop takes only 35% (rough estimate)… | |
| + time of a rolled loop on the machine on which it was develo… | |
| + for (j = sample; j < limit; j++) { | |
| + famp = amp[j]; | |
| + s->td.dtmf.energy += famp*famp; | |
| + /* With GCC 2.95, the following unrolled code seems to… | |
| + (rough estimate) as long as a neat little 0-3 loop … | |
| + goertzel_sample(s->td.dtmf.row_out, amp[j]); | |
| + goertzel_sample(s->td.dtmf.col_out, amp[j]); | |
| + goertzel_sample(s->td.dtmf.row_out + 1, amp[j]); | |
| + goertzel_sample(s->td.dtmf.col_out + 1, amp[j]); | |
| + goertzel_sample(s->td.dtmf.row_out + 2, amp[j]); | |
| + goertzel_sample(s->td.dtmf.col_out + 2, amp[j]); | |
| + goertzel_sample(s->td.dtmf.row_out + 3, amp[j]); | |
| + goertzel_sample(s->td.dtmf.col_out + 3, amp[j]); | |
| + } | |
| + s->td.dtmf.current_sample += (limit - sample); | |
| + if (s->td.dtmf.current_sample < 102) { | |
| + if (hit && !((digitmode & DSP_DIGITMODE_NOQUELCH))) { | |
| + /* If we had a hit last time, go ahead and cle… | |
| + will be another hit */ | |
| + for (i=sample;i<limit;i++) | |
| + amp[i] = 0; | |
| + *writeback = 1; | |
| + } | |
| + continue; | |
| + } | |
| + /* We are at the end of a DTMF detection block */ | |
| + /* Find the peak row and the peak column */ | |
| + row_energy[0] = goertzel_result (&s->td.dtmf.row_out[0]); | |
| + col_energy[0] = goertzel_result (&s->td.dtmf.col_out[0]); | |
| + | |
| + for (best_row = best_col = 0, i = 1; i < 4; i++) { | |
| + row_energy[i] = goertzel_result (&s->td.dtmf.row_out[i… | |
| + if (row_energy[i] > row_energy[best_row]) | |
| + best_row = i; | |
| + col_energy[i] = goertzel_result (&s->td.dtmf.col_out[i… | |
| + if (col_energy[i] > col_energy[best_col]) | |
| + best_col = i; | |
| + } | |
| + hit = 0; | |
| + | |
| + /* Basic signal level test and the twist test */ | |
| + if (row_energy[best_row] >= DTMF_THRESHOLD && | |
| + col_energy[best_col] >= DTMF_THRESHOLD && | |
| +// col_energy[best_col] < row_energy[best_row] *DTMF_REVERS… | |
| + col_energy[best_col]*DTMF_NORMAL_TWIST > row_energy[best_r… | |
| + /* Relative peak test */ | |
| + for (i = 0; i < 4; i++) { | |
| + if ((i != best_col && | |
| + col_energy[i]*DTMF_RELATIVE_PEAK_COL > col… | |
| + (i != best_row | |
| + && row_energy[i]*DTMF_RELATIVE_PEAK_ROW >… | |
| + break; | |
| + } | |
| + } | |
| + /* ... and fraction of total energy test */ | |
| + if (i >= 4 /*&& | |
| + (row_energy[best_row] + col_energy[best_col]) > DT… | |
| + /* Got a hit */ | |
| + hit = dtmf_positions[(best_row << 2) + best_co… | |
| + if (!(digitmode & DSP_DIGITMODE_NOQUELCH)) { | |
| + /* Zero out frame data if this is part… | |
| + for (i=sample;i<limit;i++) | |
| + amp[i] = 0; | |
| + *writeback = 1; | |
| + } | |
| + } | |
| + } | |
| + | |
| + /* The logic in the next test is: | |
| + For digits we need two successive identical clean detects, … | |
| + something different preceeding it. This can work with | |
| + back to back differing digits. More importantly, it | |
| + can work with nasty phones that give a very wobbly start | |
| + to a digit */ | |
| + if (hit != s->td.dtmf.current_hit) { | |
| + if (hit && s->td.dtmf.lasthit == hit) { | |
| + s->td.dtmf.current_hit = hit; | |
| + store_digit(s, hit); | |
| + } else if (s->td.dtmf.lasthit != s->td.dtmf.current_hi… | |
| + s->td.dtmf.current_hit = 0; | |
| + } | |
| + } | |
| + s->td.dtmf.lasthit = hit; | |
| + | |
| + /* Reinitialise the detector for the next block */ | |
| + for (i = 0; i < 4; i++) { | |
| + goertzel_reset(&s->td.dtmf.row_out[i]); | |
| + goertzel_reset(&s->td.dtmf.col_out[i]); | |
| + } | |
| + s->td.dtmf.energy = 0.0; | |
| + s->td.dtmf.current_sample = 0; | |
| + } | |
| + return (s->td.dtmf.current_hit); /* return the debounced hit */ | |
| +} | |
| + | |
| +/* MF goertzel size */ | |
| +#define MF_GSIZE 120 | |
| + | |
| +static int mf_detect(digit_detect_state_t *s, int16_t amp[], | |
| + int samples, int digitmode, int *writeback) | |
| +{ | |
| + float energy[6]; | |
| + int best; | |
| + int second_best; | |
| + float famp; | |
| + int i; | |
| + int j; | |
| + int sample; | |
| + int hit; | |
| + int limit; | |
| + | |
| + hit = 0; | |
| + for (sample = 0; sample < samples; sample = limit) { | |
| + /* 80 is optimised to meet the MF specs. */ | |
| + if ((samples - sample) >= (MF_GSIZE - s->td.mf.current_sample)) | |
| + limit = sample + (MF_GSIZE - s->td.mf.current_sample); | |
| + else | |
| + limit = samples; | |
| + /* The following unrolled loop takes only 35% (rough estimate)… | |
| + time of a rolled loop on the machine on which it was develo… | |
| + for (j = sample; j < limit; j++) { | |
| + famp = amp[j]; | |
| + /* With GCC 2.95, the following unrolled code seems to… | |
| + (rough estimate) as long as a neat little 0-3 loop … | |
| + goertzel_sample(s->td.mf.tone_out, amp[j]); | |
| + goertzel_sample(s->td.mf.tone_out + 1, amp[j]); | |
| + goertzel_sample(s->td.mf.tone_out + 2, amp[j]); | |
| + goertzel_sample(s->td.mf.tone_out + 3, amp[j]); | |
| + goertzel_sample(s->td.mf.tone_out + 4, amp[j]); | |
| + goertzel_sample(s->td.mf.tone_out + 5, amp[j]); | |
| + } | |
| + s->td.mf.current_sample += (limit - sample); | |
| + if (s->td.mf.current_sample < MF_GSIZE) { | |
| + if (hit && !((digitmode & DSP_DIGITMODE_NOQUELCH))) { | |
| + /* If we had a hit last time, go ahead and cle… | |
| + will be another hit */ | |
| + for (i=sample;i<limit;i++) | |
| + amp[i] = 0; | |
| + *writeback = 1; | |
| + } | |
| + continue; | |
| + } | |
| + /* We're at the end of an MF detection block. */ | |
| + /* Find the two highest energies. The spec says to look for | |
| + two tones and two tones only. Taking this literally -ie | |
| + only two tones pass the minimum threshold - doesn't work | |
| + well. The sinc function mess, due to rectangular windowing | |
| + ensure that! Find the two highest energies and ensure they | |
| + are considerably stronger than any of the others. */ | |
| + energy[0] = goertzel_result(&s->td.mf.tone_out[0]); | |
| + energy[1] = goertzel_result(&s->td.mf.tone_out[1]); | |
| + if (energy[0] > energy[1]) { | |
| + best = 0; | |
| + second_best = 1; | |
| + } else { | |
| + best = 1; | |
| + second_best = 0; | |
| + } | |
| + /*endif*/ | |
| + for (i=2;i<6;i++) { | |
| + energy[i] = goertzel_result(&s->td.mf.tone_out[i]); | |
| + if (energy[i] >= energy[best]) { | |
| + second_best = best; | |
| + best = i; | |
| + } else if (energy[i] >= energy[second_best]) { | |
| + second_best = i; | |
| + } | |
| + } | |
| + /* Basic signal level and twist tests */ | |
| + hit = 0; | |
| + if (energy[best] >= BELL_MF_THRESHOLD && energy[second_best] >… | |
| +// && energy[best] < energy[second_best]*BELL_MF_TWIST … | |
| + && energy[best]*BELL_MF_TWIST > energy[second_best]) { | |
| + /* Relative peak test */ | |
| + hit = -1; | |
| + for (i=0;i<6;i++) { | |
| + if (i != best && i != second_best) { | |
| + if (energy[i]*BELL_MF_RELATIVE_PEAK >=… | |
| + /* The best two are not clearl… | |
| + hit = 0; | |
| + break; | |
| + } | |
| + } | |
| + } | |
| + } | |
| + if (hit) { | |
| + /* Get the values into ascending order */ | |
| + if (second_best < best) { | |
| + i = best; | |
| + best = second_best; | |
| + second_best = i; | |
| + } | |
| + best = best*5 + second_best - 1; | |
| + hit = bell_mf_positions[best]; | |
| + /* Look for two successive similar results */ | |
| + /* The logic in the next test is: | |
| + For KP we need 4 successive identical clean detects… | |
| + two blocks of something different preceeding it. Fo… | |
| + else we need two successive identical clean detects… | |
| + two blocks of something different preceeding it. */ | |
| + if (hit == s->td.mf.hits[4] && hit == s->td.mf.hits[3]… | |
| + ((hit != '*' && hit != s->td.mf.hits[2] && hit != s… | |
| + (hit == '*' && hit == s->td.mf.hits[2] && hit != s… | |
| + hit != s->td.mf.hits[0]))) { | |
| + store_digit(s, hit); | |
| + } | |
| + } | |
| + | |
| + | |
| + if (hit != s->td.mf.hits[4] && hit != s->td.mf.hits[3]) { | |
| + /* Two successive block without a hit terminate curren… | |
| + s->td.mf.current_hit = 0; | |
| + } | |
| + | |
| + s->td.mf.hits[0] = s->td.mf.hits[1]; | |
| + s->td.mf.hits[1] = s->td.mf.hits[2]; | |
| + s->td.mf.hits[2] = s->td.mf.hits[3]; | |
| + s->td.mf.hits[3] = s->td.mf.hits[4]; | |
| + s->td.mf.hits[4] = hit; | |
| + /* Reinitialise the detector for the next block */ | |
| + for (i = 0; i < 6; i++) | |
| + goertzel_reset(&s->td.mf.tone_out[i]); | |
| + s->td.mf.current_sample = 0; | |
| + } | |
| + | |
| + return (s->td.mf.current_hit); /* return the debounced hit */ | |
| +} | |
| diff --git a/src/dtmf2num/dtmf2num.c b/src/dtmf2num/dtmf2num.c | |
| @@ -0,0 +1,372 @@ | |
| +/* | |
| + Copyright 2008 Luigi Auriemma | |
| + | |
| + This program is free software; you can redistribute it and/or modify | |
| + it under the terms of the GNU General Public License as published by | |
| + the Free Software Foundation; either version 2 of the License, or | |
| + (at your option) any later version. | |
| + | |
| + This program is distributed in the hope that it will be useful, | |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| + GNU General Public License for more details. | |
| + | |
| + You should have received a copy of the GNU General Public License | |
| + along with this program; if not, write to the Free Software | |
| + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| + | |
| + http://www.gnu.org/licenses/gpl.txt | |
| +*/ | |
| + | |
| +#include <stdio.h> | |
| +#include <stdlib.h> | |
| +#include <string.h> | |
| +#include <stdint.h> | |
| +#include <sys/stat.h> | |
| +#include "mywav.h" | |
| +#include "dsp.c" | |
| +//#include "resample2.c" | |
| + | |
| + | |
| + | |
| +typedef int8_t i8; | |
| +typedef uint8_t u8; | |
| +typedef int16_t i16; | |
| +typedef uint16_t u16; | |
| +typedef int32_t i32; | |
| +typedef uint32_t u32; | |
| + | |
| + | |
| + | |
| +#define VER "0.1c" | |
| + | |
| + | |
| + | |
| +int mywav_fri24(FILE *fd, uint32_t *num); | |
| +i16 *do_samples(FILE *fd, int wavsize, int *ret_samples, int bits); | |
| +int do_mono(i16 *smp, int samples, int ch); | |
| +void do_dcbias(i16 *smp, int samples); | |
| +void do_normalize(i16 *smp, int samples); | |
| +int do_8000(i16 *smp, int samples, int *freq); | |
| +void my_err(u8 *err); | |
| +void std_err(void); | |
| + | |
| + | |
| + | |
| +int main(int argc, char *argv[]) { | |
| + digit_detect_state_t dtmf; | |
| + mywav_fmtchunk fmt; | |
| + struct stat xstat; | |
| + FILE *fd; | |
| + int i, | |
| + wavsize, | |
| + samples, | |
| + writeback, | |
| + raw = 0, | |
| + optimize = 1; | |
| + i16 *smp; | |
| + u8 *fname, | |
| + *outfile = NULL; | |
| + | |
| + setbuf(stdin, NULL); | |
| + setbuf(stdout, NULL); | |
| + | |
| + fputs("\n" | |
| + "DTMF2NUM "VER"\n" | |
| + "by Luigi Auriemma\n" | |
| + "e-mail: [email protected]\n" | |
| + "web: aluigi.org\n" | |
| + "\n", stderr); | |
| + | |
| + if(argc < 2) { | |
| + printf("\n" | |
| + "Usage: %s [options] <file.WAV>\n" | |
| + "\n" | |
| + "Options:\n" | |
| + "-r F C B consider the file as raw headerless PCM data, you must … | |
| + " Frequency, Channels and Bits like -r 44100 2 16\n" | |
| + "-o disable the automatic optimizations: DC bias adjust and… | |
| + " use this option only if your file is already clean and … | |
| + "-w FILE debug option for dumping the handled samples from the m… | |
| + "\n", argv[0]); | |
| + exit(1); | |
| + } | |
| + | |
| + argc--; | |
| + for(i = 1; i < argc; i++) { | |
| + if(((argv[i][0] != '-') && (argv[i][0] != '/')) || (strlen(argv[i]) !=… | |
| + printf("\nError: wrong argument (%s)\n", argv[i]); | |
| + exit(1); | |
| + } | |
| + switch(argv[i][1]) { | |
| + case 'r': { | |
| + memset(&fmt, 0, sizeof(fmt)); | |
| + if(!argv[++i]) exit(1); | |
| + fmt.dwSamplesPerSec = atoi(argv[i]); | |
| + if(!argv[++i]) exit(1); | |
| + fmt.wChannels = atoi(argv[i]); | |
| + if(!argv[++i]) exit(1); | |
| + fmt.wBitsPerSample = atoi(argv[i]); | |
| + fmt.wFormatTag = 1; | |
| + raw = 1; | |
| + } break; | |
| + case 'o': { | |
| + optimize = 0; | |
| + } break; | |
| + case 'w': { | |
| + if(!argv[++i]) exit(1); | |
| + outfile = argv[i]; | |
| + } break; | |
| + default: { | |
| + printf("\nError: wrong option (%s)\n", argv[i]); | |
| + exit(1); | |
| + } | |
| + } | |
| + } | |
| + | |
| + fname = argv[argc]; | |
| + | |
| + if(!strcmp(fname, "-")) { | |
| + printf("- open stdin\n"); | |
| + fd = stdin; | |
| + } else { | |
| + printf("- open %s\n", fname); | |
| + fd = fopen(fname, "rb"); | |
| + if(!fd) std_err(); | |
| + } | |
| + | |
| + if(raw) { | |
| + fstat(fileno(fd), &xstat); | |
| + wavsize = xstat.st_size; | |
| + } else { | |
| + wavsize = mywav_data(fd, &fmt); | |
| + } | |
| + fprintf(stderr, | |
| + " wave size %u\n" | |
| + " format tag %hu\n" | |
| + " channels: %hu\n" | |
| + " samples/sec: %u\n" | |
| + " avg/bytes/sec: %u\n" | |
| + " block align: %hu\n" | |
| + " bits: %hu\n", | |
| + wavsize, | |
| + fmt.wFormatTag, | |
| + fmt.wChannels, | |
| + fmt.dwSamplesPerSec, | |
| + fmt.dwAvgBytesPerSec, | |
| + fmt.wBlockAlign, | |
| + fmt.wBitsPerSample); | |
| + | |
| + if(wavsize <= 0) my_err("corrupted WAVE file"); | |
| + if(fmt.wFormatTag != 1) my_err("only the classical PCM WAVE files are supp… | |
| + | |
| + smp = do_samples(fd, wavsize, &samples, fmt.wBitsPerSample); | |
| + fprintf(stderr, " samples: %d\n", samples); | |
| + if(fd != stdin) fclose(fd); | |
| + | |
| + samples = do_mono(smp, samples, fmt.wChannels); | |
| + if(optimize) { | |
| + do_dcbias(smp, samples); | |
| + do_normalize(smp, samples); | |
| + } | |
| + samples = do_8000(smp, samples, &fmt.dwSamplesPerSec); | |
| + | |
| + fmt.wFormatTag = 0x0001; | |
| + fmt.wChannels = 1; | |
| + fmt.wBitsPerSample = 16; | |
| + fmt.wBlockAlign = (fmt.wBitsPerSample >> 3) * fmt.wChannels; | |
| + fmt.dwAvgBytesPerSec = fmt.dwSamplesPerSec * fmt.wBlockAlign; | |
| + wavsize = samples * sizeof(* smp); | |
| + | |
| + if(outfile) { | |
| + fprintf(stderr, "- dump %s\n", outfile); | |
| + fd = fopen(outfile, "wb"); | |
| + if(!fd) std_err(); | |
| + mywav_writehead(fd, &fmt, wavsize, NULL, 0); | |
| + fwrite(smp, 1, wavsize, fd); | |
| + fclose(fd); | |
| + } | |
| + | |
| + SAMPLE_RATE = fmt.dwSamplesPerSec; | |
| + | |
| + ast_digit_detect_init(&dtmf, DSP_DIGITMODE_MF); | |
| + mf_detect(&dtmf, smp, samples, DSP_DIGITMODE_NOQUELCH, &writeback); | |
| + printf("\n- MF numbers: %s\n", dtmf.digits[0] ? dtmf.digits : "none"); | |
| + | |
| + ast_digit_detect_init(&dtmf, DSP_DIGITMODE_DTMF); | |
| + dtmf_detect(&dtmf, smp, samples, DSP_DIGITMODE_NOQUELCH, &writeback); | |
| + printf("\n- DTMF numbers: %s\n", dtmf.digits[0] ? dtmf.digits : "none"); | |
| + | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| +int mywav_fri24(FILE *fd, uint32_t *num) { | |
| + uint32_t ret; | |
| + uint8_t tmp; | |
| + | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret = tmp; | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret |= (tmp << 8); | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret |= (tmp << 16); | |
| + *num = ret; | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| +i16 *do_samples(FILE *fd, int wavsize, int *ret_samples, int bits) { | |
| + i32 tmp32; | |
| + int i = 0, | |
| + samples; | |
| + i16 *smp; | |
| + i8 tmp8; | |
| + | |
| + samples = wavsize / (bits >> 3); | |
| + smp = malloc(sizeof(* smp) * samples); | |
| + if(!smp) std_err(); | |
| + | |
| + if(bits == 8) { | |
| + for(i = 0; i < samples; i++) { | |
| + if(mywav_fri08(fd, &tmp8) < 0) break; | |
| + smp[i] = (tmp8 << 8) - 32768; | |
| + } | |
| + | |
| + } else if(bits == 16) { | |
| + for(i = 0; i < samples; i++) { | |
| + if(mywav_fri16(fd, &smp[i]) < 0) break; | |
| + } | |
| + | |
| + } else if(bits == 24) { | |
| + for(i = 0; i < samples; i++) { | |
| + if(mywav_fri24(fd, &tmp32) < 0) break; | |
| + smp[i] = tmp32 >> 8; | |
| + } | |
| + | |
| + } else if(bits == 32) { | |
| + for(i = 0; i < samples; i++) { | |
| + if(mywav_fri32(fd, &tmp32) < 0) break; | |
| + smp[i] = tmp32 >> 16; | |
| + } | |
| + | |
| + } else { | |
| + my_err("number of bits used in the WAVE file not supported"); | |
| + } | |
| + *ret_samples = i; | |
| + return(smp); | |
| +} | |
| + | |
| + | |
| + | |
| +int do_mono(i16 *smp, int samples, int ch) { | |
| + i32 tmp; // max 65535 channels | |
| + int i, | |
| + j; | |
| + | |
| + if(!ch) my_err("the WAVE file doesn't have channels"); | |
| + if(ch == 1) return(samples); | |
| + | |
| + for(i = 0; samples > 0; i++) { | |
| + tmp = 0; | |
| + for(j = 0; j < ch; j++) { | |
| + tmp += smp[(i * ch) + j]; | |
| + } | |
| + smp[i] = tmp / ch; | |
| + samples -= ch; | |
| + } | |
| + return(i); | |
| +} | |
| + | |
| + | |
| + | |
| +void do_dcbias(i16 *smp, int samples) { | |
| + int i; | |
| + i16 bias, | |
| + maxneg, | |
| + maxpos; | |
| + | |
| + maxneg = 32767; | |
| + maxpos = -32768; | |
| + for(i = 0; i < samples; i++) { | |
| + if(smp[i] < maxneg) { | |
| + maxneg = smp[i]; | |
| + } else if(smp[i] > maxpos) { | |
| + maxpos = smp[i]; | |
| + } | |
| + } | |
| + | |
| + bias = (maxneg + maxpos) / 2; | |
| + fprintf(stderr, " bias adjust: %d\n", bias); | |
| + | |
| + for(i = 0; i < samples; i++) { | |
| + smp[i] -= bias; | |
| + } | |
| +} | |
| + | |
| + | |
| + | |
| +void do_normalize(i16 *smp, int samples) { | |
| + int i; | |
| + i16 bias, | |
| + maxneg, | |
| + maxpos; | |
| + | |
| + maxneg = 0; | |
| + maxpos = 0; | |
| + for(i = 0; i < samples; i++) { | |
| + if(smp[i] < maxneg) { | |
| + maxneg = smp[i]; | |
| + } else if(smp[i] > maxpos) { | |
| + maxpos = smp[i]; | |
| + } | |
| + } | |
| + | |
| + fprintf(stderr, " volume peaks: %d %d\n", maxneg, maxpos); | |
| + | |
| + if(maxneg < 0) maxneg = (-maxneg) - 1; | |
| + if(maxneg > maxpos) { | |
| + bias = maxneg; | |
| + } else { | |
| + bias = maxpos; | |
| + } | |
| + if(bias == 32767) return; | |
| + | |
| + fprintf(stderr, " normalize: %d\n", 32767 - bias); | |
| + | |
| + for(i = 0; i < samples; i++) { | |
| + smp[i] = (smp[i] * 32767) / bias; | |
| + } | |
| +} | |
| + | |
| + | |
| + | |
| +int do_8000(i16 *smp, int samples, int *freq) { | |
| + void *res; | |
| + int consumed; | |
| + | |
| + if(*freq <= 8000) return(samples); | |
| + | |
| + fprintf(stderr, " resampling to: 8000hz\n"); | |
| + res = av_resample_init(8000, *freq, 16, 10, 0, 0.8); | |
| + samples = av_resample(res, smp, smp, &consumed, samples, samples, 1); | |
| + av_resample_close(res); | |
| + | |
| + *freq = 8000; | |
| + return(samples); | |
| +} | |
| + | |
| + | |
| + | |
| +void my_err(u8 *err) { | |
| + fprintf(stderr, "\nError: %s\n", err); | |
| + exit(1); | |
| +} | |
| + | |
| + | |
| + | |
| +void std_err(void) { | |
| + perror("\nError"); | |
| + exit(1); | |
| +} | |
| + | |
| + | |
| diff --git a/src/dtmf2num/mywav.h b/src/dtmf2num/mywav.h | |
| @@ -0,0 +1,244 @@ | |
| +/* | |
| + MyWAV 0.1.1 | |
| + by Luigi Auriemma | |
| + e-mail: [email protected] | |
| + web: aluigi.org | |
| + | |
| + Copyright 2005,2006 Luigi Auriemma | |
| + | |
| + This program is free software; you can redistribute it and/or modify | |
| + it under the terms of the GNU General Public License as published by | |
| + the Free Software Foundation; either version 2 of the License, or | |
| + (at your option) any later version. | |
| + | |
| + This program is distributed in the hope that it will be useful, | |
| + but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| + GNU General Public License for more details. | |
| + | |
| + You should have received a copy of the GNU General Public License | |
| + along with this program; if not, write to the Free Software | |
| + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| + | |
| + http://www.gnu.org/licenses/gpl.txt | |
| +*/ | |
| + | |
| +#include <string.h> | |
| +#include <stdint.h> | |
| + | |
| + | |
| + | |
| +/* | |
| +the functions return ever 0 if success, other values (-1) if error | |
| +note that these functions have been written with compatibility in mind | |
| +so don't worry if you see useless instructions | |
| +*/ | |
| + | |
| + | |
| + | |
| +typedef struct { | |
| + uint8_t id[4]; | |
| + uint32_t size; | |
| +} mywav_chunk; | |
| + | |
| +typedef struct { | |
| + int16_t wFormatTag; | |
| + uint16_t wChannels; | |
| + uint32_t dwSamplesPerSec; | |
| + uint32_t dwAvgBytesPerSec; | |
| + uint16_t wBlockAlign; | |
| + uint16_t wBitsPerSample; | |
| +} mywav_fmtchunk; | |
| + | |
| + | |
| + | |
| + /* FILE WRITING */ | |
| + | |
| + // 8 bit | |
| +int mywav_fwi08(FILE *fd, int num) { | |
| + if(fputc((num ) & 0xff, fd) < 0) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // 16 bit | |
| +int mywav_fwi16(FILE *fd, int num) { | |
| + if(fputc((num ) & 0xff, fd) < 0) return(-1); | |
| + if(fputc((num >> 8) & 0xff, fd) < 0) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // 32 bit | |
| +int mywav_fwi32(FILE *fd, int num) { | |
| + if(fputc((num ) & 0xff, fd) < 0) return(-1); | |
| + if(fputc((num >> 8) & 0xff, fd) < 0) return(-1); | |
| + if(fputc((num >> 16) & 0xff, fd) < 0) return(-1); | |
| + if(fputc((num >> 24) & 0xff, fd) < 0) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // data | |
| +int mywav_fwmem(FILE *fd, uint8_t *mem, int size) { | |
| + if(size) { | |
| + if(fwrite(mem, size, 1, fd) != 1) return(-1); | |
| + } | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // chunk | |
| +int mywav_fwchunk(FILE *fd, mywav_chunk *chunk) { | |
| + if(mywav_fwmem(fd, chunk->id, 4)) return(-1); | |
| + if(mywav_fwi32(fd, chunk->size)) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // fmtchunk | |
| +int mywav_fwfmtchunk(FILE *fd, mywav_fmtchunk *fmtchunk) { | |
| + if(mywav_fwi16(fd, fmtchunk->wFormatTag)) return(-1); | |
| + if(mywav_fwi16(fd, fmtchunk->wChannels)) return(-1); | |
| + if(mywav_fwi32(fd, fmtchunk->dwSamplesPerSec)) return(-1); | |
| + if(mywav_fwi32(fd, fmtchunk->dwAvgBytesPerSec)) return(-1); | |
| + if(mywav_fwi16(fd, fmtchunk->wBlockAlign)) return(-1); | |
| + if(mywav_fwi16(fd, fmtchunk->wBitsPerSample)) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + /* FILE READING */ | |
| + | |
| + // 8 bit | |
| +int mywav_fri08(FILE *fd, uint8_t *num) { | |
| + if(fread(num, 1, 1, fd) != 1) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // 16 bit | |
| +int mywav_fri16(FILE *fd, uint16_t *num) { | |
| + uint16_t ret; | |
| + uint8_t tmp; | |
| + | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret = tmp; | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret |= (tmp << 8); | |
| + *num = ret; | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // 32 bit | |
| +int mywav_fri32(FILE *fd, uint32_t *num) { | |
| + uint32_t ret; | |
| + uint8_t tmp; | |
| + | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret = tmp; | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret |= (tmp << 8); | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret |= (tmp << 16); | |
| + if(fread(&tmp, 1, 1, fd) != 1) return(-1); ret |= (tmp << 24); | |
| + *num = ret; | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // data | |
| +int mywav_frmem(FILE *fd, uint8_t *mem, int size) { | |
| + if(size) { | |
| + if(fread(mem, size, 1, fd) != 1) return(-1); | |
| + } | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // chunk | |
| +int mywav_frchunk(FILE *fd, mywav_chunk *chunk) { | |
| + if(mywav_frmem(fd, (void *)&chunk->id, 4)) return(-1); | |
| + if(mywav_fri32(fd, (void *)&chunk->size)) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + | |
| + // fmtchunk | |
| +int mywav_frfmtchunk(FILE *fd, mywav_fmtchunk *fmtchunk) { | |
| + if(mywav_fri16(fd, (void *)&fmtchunk->wFormatTag)) return(-1); | |
| + if(mywav_fri16(fd, (void *)&fmtchunk->wChannels)) return(-1); | |
| + if(mywav_fri32(fd, (void *)&fmtchunk->dwSamplesPerSec)) return(-1); | |
| + if(mywav_fri32(fd, (void *)&fmtchunk->dwAvgBytesPerSec)) return(-1); | |
| + if(mywav_fri16(fd, (void *)&fmtchunk->wBlockAlign)) return(-1); | |
| + if(mywav_fri16(fd, (void *)&fmtchunk->wBitsPerSample)) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| + | |
| + /* MYWAV MAIN FUNCTIONS */ | |
| + | |
| +int mywav_seekchunk(FILE *fd, uint8_t *find) { | |
| + mywav_chunk chunk; | |
| + | |
| + if(fseek(fd, sizeof(mywav_chunk) + 4, SEEK_SET) < 0) return(-1); | |
| + | |
| + while(!mywav_frchunk(fd, &chunk)) { | |
| + if(!memcmp(chunk.id, find, 4)) return(chunk.size); | |
| + if(fseek(fd, chunk.size, SEEK_CUR) < 0) break; | |
| + } | |
| + return(-1); | |
| +} | |
| + | |
| + | |
| + | |
| +int mywav_data(FILE *fd, mywav_fmtchunk *fmt) { | |
| + mywav_chunk chunk; | |
| + uint8_t type[4]; | |
| + | |
| + if(mywav_frchunk(fd, &chunk) < 0) return(-1); | |
| + if(mywav_frmem(fd, type, 4) < 0) return(-1); | |
| + if(memcmp(type, "WAVE", 4)) return(-1); | |
| + | |
| + if(mywav_seekchunk(fd, "fmt ") < 0) return(-1); | |
| + if(mywav_frfmtchunk(fd, fmt) < 0) return(-1); | |
| + | |
| + return(mywav_seekchunk(fd, "data")); | |
| +} | |
| + | |
| + | |
| + | |
| +int mywav_writehead(FILE *fd, mywav_fmtchunk *fmt, uint32_t data_size, uint8_t… | |
| + mywav_chunk chunk; | |
| + | |
| + memcpy(chunk.id, "RIFF", 4); | |
| + chunk.size = | |
| + 4 + | |
| + sizeof(mywav_chunk) + | |
| + sizeof(mywav_fmtchunk) + | |
| + morelen + | |
| + sizeof(mywav_chunk) + | |
| + data_size; | |
| + | |
| + if(mywav_fwchunk(fd, &chunk) < 0) return(-1); | |
| + if(mywav_fwmem(fd, "WAVE", 4) < 0) return(-1); | |
| + | |
| + memcpy(chunk.id, "fmt ", 4); | |
| + chunk.size = sizeof(mywav_fmtchunk) + morelen; | |
| + if(mywav_fwchunk(fd, &chunk) < 0) return(-1); | |
| + if(mywav_fwfmtchunk(fd, fmt) < 0) return(-1); | |
| + if(mywav_fwmem(fd, more, morelen) < 0) return(-1); | |
| + | |
| + memcpy(chunk.id, "data", 4); | |
| + chunk.size = data_size; | |
| + if(mywav_fwchunk(fd, &chunk) < 0) return(-1); | |
| + return(0); | |
| +} | |
| + | |
| diff --git a/src/dtmf2num/resample2.c b/src/dtmf2num/resample2.c | |
| @@ -0,0 +1,342 @@ | |
| +/* | |
| + * audio resampling | |
| + * Copyright (c) 2004 Michael Niedermayer <[email protected]> | |
| + * | |
| + * This file is part of FFmpeg. | |
| + * | |
| + * FFmpeg is free software; you can redistribute it and/or | |
| + * modify it under the terms of the GNU Lesser General Public | |
| + * License as published by the Free Software Foundation; either | |
| + * version 2.1 of the License, or (at your option) any later version. | |
| + * | |
| + * FFmpeg is distributed in the hope that it will be useful, | |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| + * Lesser General Public License for more details. | |
| + * | |
| + * You should have received a copy of the GNU Lesser General Public | |
| + * License along with FFmpeg; if not, write to the Free Software | |
| + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| + */ | |
| + | |
| +/** | |
| + * @file resample2.c | |
| + * audio resampling | |
| + * @author Michael Niedermayer <[email protected]> | |
| + */ | |
| + | |
| +#include <stdio.h> | |
| +#include <stdlib.h> | |
| +#include <string.h> | |
| +#include <stdint.h> | |
| +#include <math.h> | |
| +#include <malloc.h> | |
| + | |
| +#define FFABS(a) ((a) >= 0 ? (a) : (-(a))) | |
| +#define FFSIGN(a) ((a) > 0 ? 1 : -1) | |
| +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) | |
| +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) | |
| +#define av_mallocz malloc | |
| +#define av_freep free | |
| + | |
| +#ifndef CONFIG_RESAMPLE_HP | |
| +#define FILTER_SHIFT 15 | |
| + | |
| +#define FELEM int16_t | |
| +#define FELEM2 int32_t | |
| +#define FELEML int64_t | |
| +#define FELEM_MAX INT16_MAX | |
| +#define FELEM_MIN INT16_MIN | |
| +#define WINDOW_TYPE 9 | |
| +#elif !defined(CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE) | |
| +#define FILTER_SHIFT 30 | |
| + | |
| +#define FELEM int32_t | |
| +#define FELEM2 int64_t | |
| +#define FELEML int64_t | |
| +#define FELEM_MAX INT32_MAX | |
| +#define FELEM_MIN INT32_MIN | |
| +#define WINDOW_TYPE 12 | |
| +#else | |
| +#define FILTER_SHIFT 0 | |
| + | |
| +#define FELEM double | |
| +#define FELEM2 double | |
| +#define FELEML double | |
| +#define WINDOW_TYPE 24 | |
| +#endif | |
| + | |
| + | |
| +typedef struct AVResampleContext{ | |
| + FELEM *filter_bank; | |
| + int filter_length; | |
| + int ideal_dst_incr; | |
| + int dst_incr; | |
| + int index; | |
| + int frac; | |
| + int src_incr; | |
| + int compensation_distance; | |
| + int phase_shift; | |
| + int phase_mask; | |
| + int linear; | |
| +}AVResampleContext; | |
| + | |
| +/** | |
| + * 0th order modified bessel function of the first kind. | |
| + */ | |
| +static double bessel(double x){ | |
| + double v=1; | |
| + double t=1; | |
| + int i; | |
| + | |
| + x= x*x/4; | |
| + for(i=1; i<50; i++){ | |
| + t *= x/(i*i); | |
| + v += t; | |
| + } | |
| + return v; | |
| +} | |
| + | |
| +static inline int av_clip(int a, int amin, int amax) | |
| +{ | |
| + if (a < amin) return amin; | |
| + else if (a > amax) return amax; | |
| + else return a; | |
| +} | |
| + | |
| +/** | |
| + * builds a polyphase filterbank. | |
| + * @param factor resampling factor | |
| + * @param scale wanted sum of coefficients for each filter | |
| + * @param type 0->cubic, 1->blackman nuttall windowed sinc, 2..16->kaiser wind… | |
| + */ | |
| +void av_build_filter(FELEM *filter, double factor, int tap_count, int phase_co… | |
| + int ph, i; | |
| + double x, y, w, tab[tap_count]; | |
| + const int center= (tap_count-1)/2; | |
| + | |
| + /* if upsampling, only need to interpolate, no filter */ | |
| + if (factor > 1.0) | |
| + factor = 1.0; | |
| + | |
| + for(ph=0;ph<phase_count;ph++) { | |
| + double norm = 0; | |
| + for(i=0;i<tap_count;i++) { | |
| + x = M_PI * ((double)(i - center) - (double)ph / phase_count) * fac… | |
| + if (x == 0) y = 1.0; | |
| + else y = sin(x) / x; | |
| + switch(type){ | |
| + case 0:{ | |
| + const float d= -0.5; //first order derivative = -0.5 | |
| + x = fabs(((double)(i - center) - (double)ph / phase_count) * f… | |
| + if(x<1.0) y= 1 - 3*x*x + 2*x*x*x + d*( -x*x + x*x*x… | |
| + else y= d*(-4 + 8*x - 5*x*x + x*x*x… | |
| + break;} | |
| + case 1: | |
| + w = 2.0*x / (factor*tap_count) + M_PI; | |
| + y *= 0.3635819 - 0.4891775 * cos(w) + 0.1365995 * cos(2*w) - 0… | |
| + break; | |
| + default: | |
| + w = 2.0*x / (factor*tap_count*M_PI); | |
| + y *= bessel(type*sqrt(FFMAX(1-w*w, 0))); | |
| + break; | |
| + } | |
| + | |
| + tab[i] = y; | |
| + norm += y; | |
| + } | |
| + | |
| + /* normalize so that an uniform color remains the same */ | |
| + for(i=0;i<tap_count;i++) { | |
| +#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE | |
| + filter[ph * tap_count + i] = tab[i] / norm; | |
| +#else | |
| + filter[ph * tap_count + i] = av_clip(lrintf(tab[i] * scale / norm)… | |
| +#endif | |
| + } | |
| + } | |
| +#if 0 | |
| + { | |
| +#define LEN 1024 | |
| + int j,k; | |
| + double sine[LEN + tap_count]; | |
| + double filtered[LEN]; | |
| + double maxff=-2, minff=2, maxsf=-2, minsf=2; | |
| + for(i=0; i<LEN; i++){ | |
| + double ss=0, sf=0, ff=0; | |
| + for(j=0; j<LEN+tap_count; j++) | |
| + sine[j]= cos(i*j*M_PI/LEN); | |
| + for(j=0; j<LEN; j++){ | |
| + double sum=0; | |
| + ph=0; | |
| + for(k=0; k<tap_count; k++) | |
| + sum += filter[ph * tap_count + k] * sine[k+j]; | |
| + filtered[j]= sum / (1<<FILTER_SHIFT); | |
| + ss+= sine[j + center] * sine[j + center]; | |
| + ff+= filtered[j] * filtered[j]; | |
| + sf+= sine[j + center] * filtered[j]; | |
| + } | |
| + ss= sqrt(2*ss/LEN); | |
| + ff= sqrt(2*ff/LEN); | |
| + sf= 2*sf/LEN; | |
| + maxff= FFMAX(maxff, ff); | |
| + minff= FFMIN(minff, ff); | |
| + maxsf= FFMAX(maxsf, sf); | |
| + minsf= FFMIN(minsf, sf); | |
| + if(i%11==0){ | |
| + //av_log(NULL, AV_LOG_ERROR, "i:%4d ss:%f ff:%13.6e-%13.6e sf:… | |
| + minff=minsf= 2; | |
| + maxff=maxsf= -2; | |
| + } | |
| + } | |
| + } | |
| +#endif | |
| +} | |
| + | |
| +/** | |
| + * Initializes an audio resampler. | |
| + * Note, if either rate is not an integer then simply scale both rates up so t… | |
| + */ | |
| +AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_size… | |
| + AVResampleContext *c= av_mallocz(sizeof(AVResampleContext)); | |
| + double factor= FFMIN(out_rate * cutoff / in_rate, 1.0); | |
| + int phase_count= 1<<phase_shift; | |
| + | |
| + c->phase_shift= phase_shift; | |
| + c->phase_mask= phase_count-1; | |
| + c->linear= linear; | |
| + | |
| + c->filter_length= FFMAX((int)ceil(filter_size/factor), 1); | |
| + c->filter_bank= av_mallocz(c->filter_length*(phase_count+1)*sizeof(FELEM)); | |
| + av_build_filter(c->filter_bank, factor, c->filter_length, phase_count, 1<<… | |
| + memcpy(&c->filter_bank[c->filter_length*phase_count+1], c->filter_bank, (c… | |
| + c->filter_bank[c->filter_length*phase_count]= c->filter_bank[c->filter_len… | |
| + | |
| + c->src_incr= out_rate; | |
| + c->ideal_dst_incr= c->dst_incr= in_rate * phase_count; | |
| + c->index= -phase_count*((c->filter_length-1)/2); | |
| + | |
| + return c; | |
| +} | |
| + | |
| +void av_resample_close(AVResampleContext *c){ | |
| + av_freep(&c->filter_bank); | |
| + av_freep(&c); | |
| +} | |
| + | |
| +/** | |
| + * Compensates samplerate/timestamp drift. The compensation is done by changing | |
| + * the resampler parameters, so no audible clicks or similar distortions ocur | |
| + * @param compensation_distance distance in output samples over which the comp… | |
| + * @param sample_delta number of output samples which should be output less | |
| + * | |
| + * example: av_resample_compensate(c, 10, 500) | |
| + * here instead of 510 samples only 500 samples would be output | |
| + * | |
| + * note, due to rounding the actual compensation might be slightly different, | |
| + * especially if the compensation_distance is large and the in_rate used durin… | |
| + */ | |
| +void av_resample_compensate(AVResampleContext *c, int sample_delta, int compen… | |
| +// sample_delta += (c->ideal_dst_incr - c->dst_incr)*(int64_t)c->compensati… | |
| + c->compensation_distance= compensation_distance; | |
| + c->dst_incr = c->ideal_dst_incr - c->ideal_dst_incr * (int64_t)sample_delt… | |
| +} | |
| + | |
| +/** | |
| + * resamples. | |
| + * @param src an array of unconsumed samples | |
| + * @param consumed the number of samples of src which have been consumed are r… | |
| + * @param src_size the number of unconsumed samples available | |
| + * @param dst_size the amount of space in samples available in dst | |
| + * @param update_ctx if this is 0 then the context wont be modified, that way … | |
| + * @return the number of samples written in dst or -1 if an error occured | |
| + */ | |
| +int av_resample(AVResampleContext *c, short *dst, short *src, int *consumed, i… | |
| + int dst_index, i; | |
| + int index= c->index; | |
| + int frac= c->frac; | |
| + int dst_incr_frac= c->dst_incr % c->src_incr; | |
| + int dst_incr= c->dst_incr / c->src_incr; | |
| + int compensation_distance= c->compensation_distance; | |
| + | |
| + if(compensation_distance == 0 && c->filter_length == 1 && c->phase_shift==0){ | |
| + int64_t index2= ((int64_t)index)<<32; | |
| + int64_t incr= (1LL<<32) * c->dst_incr / c->src_incr; | |
| + dst_size= FFMIN(dst_size, (src_size-1-index) * (int64_t)c->src_incr / … | |
| + | |
| + for(dst_index=0; dst_index < dst_size; dst_index++){ | |
| + dst[dst_index] = src[index2>>32]; | |
| + index2 += incr; | |
| + } | |
| + frac += dst_index * dst_incr_frac; | |
| + index += dst_index * dst_incr; | |
| + index += frac / c->src_incr; | |
| + frac %= c->src_incr; | |
| + }else{ | |
| + for(dst_index=0; dst_index < dst_size; dst_index++){ | |
| + FELEM *filter= c->filter_bank + c->filter_length*(index & c->phase_mas… | |
| + int sample_index= index >> c->phase_shift; | |
| + FELEM2 val=0; | |
| + | |
| + if(sample_index < 0){ | |
| + for(i=0; i<c->filter_length; i++) | |
| + val += src[FFABS(sample_index + i) % src_size] * filter[i]; | |
| + }else if(sample_index + c->filter_length > src_size){ | |
| + break; | |
| + }else if(c->linear){ | |
| + FELEM2 v2=0; | |
| + for(i=0; i<c->filter_length; i++){ | |
| + val += src[sample_index + i] * (FELEM2)filter[i]; | |
| + v2 += src[sample_index + i] * (FELEM2)filter[i + c->filter_le… | |
| + } | |
| + val+=(v2-val)*(FELEML)frac / c->src_incr; | |
| + }else{ | |
| + for(i=0; i<c->filter_length; i++){ | |
| + val += src[sample_index + i] * (FELEM2)filter[i]; | |
| + } | |
| + } | |
| + | |
| +#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE | |
| + dst[dst_index] = av_clip_int16(lrintf(val)); | |
| +#else | |
| + val = (val + (1<<(FILTER_SHIFT-1)))>>FILTER_SHIFT; | |
| + dst[dst_index] = (unsigned)(val + 32768) > 65535 ? (val>>31) ^ 32767 :… | |
| +#endif | |
| + | |
| + frac += dst_incr_frac; | |
| + index += dst_incr; | |
| + if(frac >= c->src_incr){ | |
| + frac -= c->src_incr; | |
| + index++; | |
| + } | |
| + | |
| + if(dst_index + 1 == compensation_distance){ | |
| + compensation_distance= 0; | |
| + dst_incr_frac= c->ideal_dst_incr % c->src_incr; | |
| + dst_incr= c->ideal_dst_incr / c->src_incr; | |
| + } | |
| + } | |
| + } | |
| + *consumed= FFMAX(index, 0) >> c->phase_shift; | |
| + if(index>=0) index &= c->phase_mask; | |
| + | |
| + if(compensation_distance){ | |
| + compensation_distance -= dst_index; | |
| + if(!(compensation_distance > 0)) return(-1); | |
| + } | |
| + if(update_ctx){ | |
| + c->frac= frac; | |
| + c->index= index; | |
| + c->dst_incr= dst_incr_frac + c->src_incr*dst_incr; | |
| + c->compensation_distance= compensation_distance; | |
| + } | |
| +#if 0 | |
| + if(update_ctx && !c->compensation_distance){ | |
| +#undef rand | |
| + av_resample_compensate(c, rand() % (8000*2) - 8000, 8000*2); | |
| +//av_log(NULL, AV_LOG_DEBUG, "%d %d %d\n", c->dst_incr, c->ideal_dst_incr, c->… | |
| + } | |
| +#endif | |
| + | |
| + return dst_index; | |
| +} | |
| diff --git a/web/app/controllers/analyze_controller.rb b/web/app/controllers/an… | |
| @@ -81,7 +81,7 @@ class AnalyzeController < ApplicationController | |
| @job_id, | |
| :page => params[:page], | |
| :order => 'number ASC', | |
| - :per_page => 10, | |
| + :per_page => 20, | |
| :conditions => [ 'completed = ? and processed = ? and … | |
| ) | |
| else | |
| @@ -89,7 +89,7 @@ class AnalyzeController < ApplicationController | |
| @job_id, | |
| :page => params[:page], | |
| :order => 'number ASC', | |
| - :per_page => 10, | |
| + :per_page => 20, | |
| :conditions => [ 'completed = ? and processed = ? and … | |
| ) | |
| end | |
| diff --git a/web/app/views/analyze/view.html.erb b/web/app/views/analyze/view.h… | |
| @@ -35,8 +35,13 @@ | |
| CallerID: <%= dial_result.cid%><br/> | |
| Provider: <%=h dial_result.provider.name %><br/> | |
| Audio: <%=h dial_result.seconds %> Seconds<br/> | |
| - Ringer: <%=h dial_result.ringtime %> Seconds | |
| - | |
| + Ringer: <%=h dial_result.ringtime %> Seconds<br/> | |
| + <% if(dial_result.dtmf and dial_result.dtmf.length > 0) %> | |
| + DTMF: <%=h dial_result.dtmf %><br/> | |
| + <% end %> | |
| + <% if(dial_result.mf and dial_result.mf.length > 0) %> | |
| + MF: <%=h dial_result.mf %><br/> | |
| + <% end %> | |
| </td> | |
| <td align='center'> | |
| <b><%=h dial_result.line_type.upcase %></b><br/> | |
| diff --git a/web/db/schema.rb b/web/db/schema.rb | |
| @@ -9,7 +9,7 @@ | |
| # | |
| # It's strongly recommended to check this file into your version control syste… | |
| -ActiveRecord::Schema.define(:version => 20090522202032) do | |
| +ActiveRecord::Schema.define(:version => 20090526031826) do | |
| create_table "dial_jobs", :force => true do |t| | |
| t.string "range" | |
| @@ -45,6 +45,8 @@ ActiveRecord::Schema.define(:version => 20090522202032) do | |
| t.string "line_type" | |
| t.string "notes" | |
| t.string "signatures" | |
| + t.string "dtmf" | |
| + t.string "mf" | |
| end | |
| create_table "providers", :force => true do |t| |