[asterisk-commits] dsp.c: Add fax and DTMF detection unit tests. (asterisk[master])

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Jul 28 17:35:14 CDT 2016


Joshua Colp has submitted this change and it was merged.

Change subject: dsp.c: Add fax and DTMF detection unit tests.
......................................................................


dsp.c: Add fax and DTMF detection unit tests.

* Add fax amplitude and frequency sweep tests.
* Add DTMF amplitude and twist unit tests.

Change-Id: I8d77c9a1eec89e440d715f998c928687e870c3f7
---
M main/dsp.c
1 file changed, 460 insertions(+), 3 deletions(-)

Approvals:
  George Joseph: Looks good to me, but someone else must approve
  Joshua Colp: Looks good to me, approved; Verified



diff --git a/main/dsp.c b/main/dsp.c
index 5c46730..1a3ebea 100644
--- a/main/dsp.c
+++ b/main/dsp.c
@@ -68,6 +68,7 @@
 #include "asterisk/utils.h"
 #include "asterisk/options.h"
 #include "asterisk/config.h"
+#include "asterisk/test.h"
 
 /*! Number of goertzels for progress detect */
 enum gsamp_size {
@@ -597,11 +598,11 @@
 		tone_energy *= 2.0;
 		s->energy *= s->block_size;
 
-		ast_debug(10, "tone %d, Ew=%.2E, Et=%.2E, s/n=%10.2f\n", s->freq, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
+		ast_debug(10, "%d Hz tone %2d Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
 		hit = 0;
 		if (TONE_THRESHOLD <= tone_energy
 			&& tone_energy > s->energy * s->threshold) {
-			ast_debug(10, "Hit! count=%d\n", s->hit_count);
+			ast_debug(10, "%d Hz tone Hit! %2d Ew=%.4E, Et=%.4E, s/n=%10.2f\n", s->freq, s->hit_count, tone_energy, s->energy, tone_energy / (s->energy - tone_energy));
 			hit = 1;
 		}
 
@@ -733,6 +734,10 @@
 				best_col = i;
 			}
 		}
+		ast_debug(10, "DTMF best '%c' Erow=%.4E Ecol=%.4E Erc=%.4E Et=%.4E\n",
+			dtmf_positions[(best_row << 2) + best_col],
+			row_energy[best_row], col_energy[best_col],
+			row_energy[best_row] + col_energy[best_col], s->td.dtmf.energy);
 		hit = 0;
 		/* Basic signal level test and the twist test */
 		if (row_energy[best_row] >= DTMF_THRESHOLD &&
@@ -753,6 +758,7 @@
 			    (row_energy[best_row] + col_energy[best_col]) > DTMF_TO_TOTAL_ENERGY * s->td.dtmf.energy) {
 				/* Got a hit */
 				hit = dtmf_positions[(best_row << 2) + best_col];
+				ast_debug(10, "DTMF hit '%c'\n", hit);
 			}
 		}
 
@@ -1946,9 +1952,460 @@
 	return thresholds[which];
 }
 
+#ifdef TEST_FRAMEWORK
+static void test_tone_sample_gen(short *slin_buf, int samples, int rate, int freq, short amplitude)
+{
+	int idx;
+	double sample_step = 2.0 * M_PI * freq / rate;/* radians per step */
+
+	for (idx = 0; idx < samples; ++idx) {
+		slin_buf[idx] = amplitude * sin(sample_step * idx);
+	}
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static void test_tone_sample_gen_add(short *slin_buf, int samples, int rate, int freq, short amplitude)
+{
+	int idx;
+	double sample_step = 2.0 * M_PI * freq / rate;/* radians per step */
+
+	for (idx = 0; idx < samples; ++idx) {
+		slin_buf[idx] += amplitude * sin(sample_step * idx);
+	}
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static void test_dual_sample_gen(short *slin_buf, int samples, int rate, int f1, short a1, int f2, short a2)
+{
+	test_tone_sample_gen(slin_buf, samples, rate, f1, a1);
+	test_tone_sample_gen_add(slin_buf, samples, rate, f2, a2);
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+#define TONE_AMPLITUDE_MAX	0x7fff	/* Max signed linear amplitude */
+#define TONE_AMPLITUDE_MIN	80		/* Min signed linear amplitude detectable */
+
+static int test_tone_amplitude_sweep(struct ast_test *test, struct ast_dsp *dsp, tone_detect_state_t *tone_state)
+{
+	short slin_buf[tone_state->block_size];
+	int result;
+	int idx;
+	struct {
+		short amp_val;
+		int detect;
+	} amp_tests[] = {
+		{ .amp_val = TONE_AMPLITUDE_MAX,	.detect = 1, },
+		{ .amp_val = 10000,					.detect = 1, },
+		{ .amp_val = 1000,					.detect = 1, },
+		{ .amp_val = 100,					.detect = 1, },
+		{ .amp_val = TONE_AMPLITUDE_MIN,	.detect = 1, },
+		{ .amp_val = 75,					.detect = 0, },
+		{ .amp_val = 10,					.detect = 0, },
+		{ .amp_val = 1,						.detect = 0, },
+	};
+
+	result = 0;
+
+	for (idx = 0; idx < ARRAY_LEN(amp_tests); ++idx) {
+		int detected;
+		int duration;
+
+		ast_debug(1, "Test %d Hz at amplitude %d\n",
+			tone_state->freq, amp_tests[idx].amp_val);
+		test_tone_sample_gen(slin_buf, tone_state->block_size, DEFAULT_SAMPLE_RATE,
+			tone_state->freq, amp_tests[idx].amp_val);
+
+		detected = 0;
+		for (duration = 0; !detected && duration < tone_state->hits_required + 3; ++duration) {
+			detected = tone_detect(dsp, tone_state, slin_buf, tone_state->block_size) ? 1 : 0;
+		}
+		if (amp_tests[idx].detect != detected) {
+			/*
+			 * Both messages are needed.  ast_debug for when figuring out
+			 * what went wrong and the test update for normal output before
+			 * you start debugging.  The different logging methods are not
+			 * synchronized.
+			 */
+			ast_debug(1,
+				"Test %d Hz at amplitude %d failed.  Detected: %s\n",
+				tone_state->freq, amp_tests[idx].amp_val,
+				detected ? "yes" : "no");
+			ast_test_status_update(test,
+				"Test %d Hz at amplitude %d failed.  Detected: %s\n",
+				tone_state->freq, amp_tests[idx].amp_val,
+				detected ? "yes" : "no");
+			result = -1;
+		}
+		tone_state->hit_count = 0;
+	}
+
+	return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static int test_dtmf_amplitude_sweep(struct ast_test *test, struct ast_dsp *dsp, int digit_index)
+{
+	short slin_buf[DTMF_GSIZE];
+	int result;
+	int row;
+	int column;
+	int idx;
+	struct {
+		short amp_val;
+		int digit;
+	} amp_tests[] = {
+		/*
+		 * XXX Since there is no current DTMF level detection issue.  This test
+		 * just checks the current detection levels.
+		 */
+		{ .amp_val = TONE_AMPLITUDE_MAX/2,	.digit = dtmf_positions[digit_index], },
+		{ .amp_val = 10000,					.digit = dtmf_positions[digit_index], },
+		{ .amp_val = 1000,					.digit = dtmf_positions[digit_index], },
+		{ .amp_val = 500,					.digit = dtmf_positions[digit_index], },
+		{ .amp_val = 250,					.digit = dtmf_positions[digit_index], },
+		{ .amp_val = 200,					.digit = dtmf_positions[digit_index], },
+		{ .amp_val = 180,					.digit = dtmf_positions[digit_index], },
+		/* Various digits detect and not detect in this range */
+		{ .amp_val = 170,					.digit = 0, },
+		{ .amp_val = 100,					.digit = 0, },
+		/*
+		 * Amplitudes below TONE_AMPLITUDE_MIN start having questionable detection
+		 * over quantization and background noise.
+		 */
+		{ .amp_val = TONE_AMPLITUDE_MIN,	.digit = 0, },
+		{ .amp_val = 75,					.digit = 0, },
+		{ .amp_val = 10,					.digit = 0, },
+		{ .amp_val = 1,						.digit = 0, },
+	};
+
+	row = (digit_index >> 2) & 0x03;
+	column = digit_index & 0x03;
+
+	result = 0;
+
+	for (idx = 0; idx < ARRAY_LEN(amp_tests); ++idx) {
+		int digit;
+		int duration;
+
+		ast_debug(1, "Test '%c' at amplitude %d\n",
+			dtmf_positions[digit_index], amp_tests[idx].amp_val);
+		test_dual_sample_gen(slin_buf, ARRAY_LEN(slin_buf), DEFAULT_SAMPLE_RATE,
+			(int) dtmf_row[row], amp_tests[idx].amp_val,
+			(int) dtmf_col[column], amp_tests[idx].amp_val);
+
+		digit = 0;
+		for (duration = 0; !digit && duration < 3; ++duration) {
+			digit = dtmf_detect(dsp, &dsp->digit_state, slin_buf, ARRAY_LEN(slin_buf),
+				0, 0);
+		}
+		if (amp_tests[idx].digit != digit) {
+			/*
+			 * Both messages are needed.  ast_debug for when figuring out
+			 * what went wrong and the test update for normal output before
+			 * you start debugging.  The different logging methods are not
+			 * synchronized.
+			 */
+			ast_debug(1,
+				"Test '%c' at amplitude %d failed.  Detected Digit: '%c'\n",
+				dtmf_positions[digit_index], amp_tests[idx].amp_val,
+				digit ?: ' ');
+			ast_test_status_update(test,
+				"Test '%c' at amplitude %d failed.  Detected Digit: '%c'\n",
+				dtmf_positions[digit_index], amp_tests[idx].amp_val,
+				digit ?: ' ');
+			result = -1;
+		}
+		ast_dsp_digitreset(dsp);
+	}
+
+	return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static int test_dtmf_twist_sweep(struct ast_test *test, struct ast_dsp *dsp, int digit_index)
+{
+	short slin_buf[DTMF_GSIZE];
+	int result;
+	int row;
+	int column;
+	int idx;
+	struct {
+		short amp_row;
+		short amp_col;
+		int digit;
+	} twist_tests[] = {
+		/*
+		 * XXX Since there is no current DTMF twist detection issue.  This test
+		 * just checks the current detection levels.
+		 *
+		 * Normal twist has the column higher than the row amplitude.
+		 * Reverse twist is the other way.
+		 */
+		{ .amp_row = 1000 + 1800, .amp_col = 1000 +    0, .digit = 0, },
+		{ .amp_row = 1000 + 1700, .amp_col = 1000 +    0, .digit = 0, },
+		/* Various digits detect and not detect in this range */
+		{ .amp_row = 1000 + 1400, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 + 1300, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 + 1200, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 + 1100, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 + 1000, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +  100, .amp_col = 1000 +    0, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  100, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  200, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  300, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  400, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  500, .digit = dtmf_positions[digit_index], },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  550, .digit = dtmf_positions[digit_index], },
+		/* Various digits detect and not detect in this range */
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  650, .digit = 0, },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  700, .digit = 0, },
+		{ .amp_row = 1000 +    0, .amp_col = 1000 +  800, .digit = 0, },
+	};
+	float save_normal_twist;
+	float save_reverse_twist;
+
+	save_normal_twist = dtmf_normal_twist;
+	save_reverse_twist = dtmf_reverse_twist;
+	dtmf_normal_twist = DEF_DTMF_NORMAL_TWIST;
+	dtmf_reverse_twist = DEF_DTMF_REVERSE_TWIST;
+
+	row = (digit_index >> 2) & 0x03;
+	column = digit_index & 0x03;
+
+	result = 0;
+
+	for (idx = 0; idx < ARRAY_LEN(twist_tests); ++idx) {
+		int digit;
+		int duration;
+
+		ast_debug(1, "Test '%c' twist row %d col %d amplitudes\n",
+			dtmf_positions[digit_index],
+			twist_tests[idx].amp_row, twist_tests[idx].amp_col);
+		test_dual_sample_gen(slin_buf, ARRAY_LEN(slin_buf), DEFAULT_SAMPLE_RATE,
+			(int) dtmf_row[row], twist_tests[idx].amp_row,
+			(int) dtmf_col[column], twist_tests[idx].amp_col);
+
+		digit = 0;
+		for (duration = 0; !digit && duration < 3; ++duration) {
+			digit = dtmf_detect(dsp, &dsp->digit_state, slin_buf, ARRAY_LEN(slin_buf),
+				0, 0);
+		}
+		if (twist_tests[idx].digit != digit) {
+			/*
+			 * Both messages are needed.  ast_debug for when figuring out
+			 * what went wrong and the test update for normal output before
+			 * you start debugging.  The different logging methods are not
+			 * synchronized.
+			 */
+			ast_debug(1,
+				"Test '%c' twist row %d col %d amplitudes failed.  Detected Digit: '%c'\n",
+				dtmf_positions[digit_index],
+				twist_tests[idx].amp_row, twist_tests[idx].amp_col,
+				digit ?: ' ');
+			ast_test_status_update(test,
+				"Test '%c' twist row %d col %d amplitudes failed.  Detected Digit: '%c'\n",
+				dtmf_positions[digit_index],
+				twist_tests[idx].amp_row, twist_tests[idx].amp_col,
+				digit ?: ' ');
+			result = -1;
+		}
+		ast_dsp_digitreset(dsp);
+	}
+
+	dtmf_normal_twist = save_normal_twist;
+	dtmf_reverse_twist = save_reverse_twist;
+
+	return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static int test_tone_freq_sweep(struct ast_test *test, struct ast_dsp *dsp, tone_detect_state_t *tone_state, short amplitude)
+{
+	short slin_buf[tone_state->block_size];
+	int result;
+	int freq;
+	int lower_freq;
+	int upper_freq;
+
+	/* Calculate detection frequency range */
+	lower_freq = tone_state->freq - 4;
+	upper_freq = tone_state->freq + 4;
+
+	result = 0;
+
+	/* Sweep frequencies loop. */
+	for (freq = 100; freq <= 3500; freq += 1) {
+		int detected;
+		int duration;
+		int expect_detection;
+
+		if (freq == tone_state->freq) {
+			/* This case is done by the amplitude sweep. */
+			continue;
+		}
+
+		expect_detection = (lower_freq <= freq && freq <= upper_freq) ? 1 : 0;
+
+		ast_debug(1, "Test %d Hz detection given %d Hz tone at amplitude %d.  Range:%d-%d Expect detect: %s\n",
+			tone_state->freq, freq, amplitude, lower_freq, upper_freq,
+			expect_detection ? "yes" : "no");
+		test_tone_sample_gen(slin_buf, tone_state->block_size, DEFAULT_SAMPLE_RATE, freq,
+			amplitude);
+
+		detected = 0;
+		for (duration = 0; !detected && duration < tone_state->hits_required + 3; ++duration) {
+			detected = tone_detect(dsp, tone_state, slin_buf, tone_state->block_size) ? 1 : 0;
+		}
+		if (expect_detection != detected) {
+			/*
+			 * Both messages are needed.  ast_debug for when figuring out
+			 * what went wrong and the test update for normal output before
+			 * you start debugging.  The different logging methods are not
+			 * synchronized.
+			 */
+			ast_debug(1,
+				"Test %d Hz detection given %d Hz tone at amplitude %d failed.  Range:%d-%d Detected: %s\n",
+				tone_state->freq, freq, amplitude, lower_freq, upper_freq,
+				detected ? "yes" : "no");
+			ast_test_status_update(test,
+				"Test %d Hz detection given %d Hz tone at amplitude %d failed.  Range:%d-%d Detected: %s\n",
+				tone_state->freq, freq, amplitude, lower_freq, upper_freq,
+				detected ? "yes" : "no");
+			result = -1;
+		}
+		tone_state->hit_count = 0;
+	}
+
+	return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_dsp_fax_detect)
+{
+	struct ast_dsp *dsp;
+	enum ast_test_result_state result;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "fax";
+		info->category = "/main/dsp/";
+		info->summary = "DSP fax tone detect unit test";
+		info->description =
+			"Tests fax tone detection code.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	dsp = ast_dsp_new();
+	if (!dsp) {
+		return AST_TEST_FAIL;
+	}
+
+	result = AST_TEST_PASS;
+
+	/* Test CNG tone amplitude detection */
+	if (test_tone_amplitude_sweep(test, dsp, &dsp->cng_tone_state)) {
+		result = AST_TEST_FAIL;
+	}
+
+	/* Test CED tone amplitude detection */
+	if (test_tone_amplitude_sweep(test, dsp, &dsp->ced_tone_state)) {
+		result = AST_TEST_FAIL;
+	}
+
+	/* Test CNG tone frequency detection */
+	if (test_tone_freq_sweep(test, dsp, &dsp->cng_tone_state, TONE_AMPLITUDE_MAX)) {
+		result = AST_TEST_FAIL;
+	}
+	if (test_tone_freq_sweep(test, dsp, &dsp->cng_tone_state, TONE_AMPLITUDE_MIN)) {
+		result = AST_TEST_FAIL;
+	}
+
+	/* Test CED tone frequency detection */
+	if (test_tone_freq_sweep(test, dsp, &dsp->ced_tone_state, TONE_AMPLITUDE_MAX)) {
+		result = AST_TEST_FAIL;
+	}
+	if (test_tone_freq_sweep(test, dsp, &dsp->ced_tone_state, TONE_AMPLITUDE_MIN)) {
+		result = AST_TEST_FAIL;
+	}
+
+	ast_dsp_free(dsp);
+	return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+AST_TEST_DEFINE(test_dsp_dtmf_detect)
+{
+	int idx;
+	struct ast_dsp *dsp;
+	enum ast_test_result_state result;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "dtmf";
+		info->category = "/main/dsp/";
+		info->summary = "DSP DTMF detect unit test";
+		info->description =
+			"Tests DTMF detection code.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	dsp = ast_dsp_new();
+	if (!dsp) {
+		return AST_TEST_FAIL;
+	}
+
+	result = AST_TEST_PASS;
+
+	for (idx = 0; dtmf_positions[idx]; ++idx) {
+		if (test_dtmf_amplitude_sweep(test, dsp, idx)) {
+			result = AST_TEST_FAIL;
+		}
+	}
+
+	for (idx = 0; dtmf_positions[idx]; ++idx) {
+		if (test_dtmf_twist_sweep(test, dsp, idx)) {
+			result = AST_TEST_FAIL;
+		}
+	}
+
+	ast_dsp_free(dsp);
+	return result;
+}
+#endif
+
+#ifdef TEST_FRAMEWORK
+static void test_dsp_shutdown(void)
+{
+	AST_TEST_UNREGISTER(test_dsp_fax_detect);
+	AST_TEST_UNREGISTER(test_dsp_dtmf_detect);
+}
+#endif
+
 int ast_dsp_init(void)
 {
-	return _dsp_init(0);
+	int res = _dsp_init(0);
+
+#ifdef TEST_FRAMEWORK
+	if (!res) {
+		AST_TEST_REGISTER(test_dsp_fax_detect);
+		AST_TEST_REGISTER(test_dsp_dtmf_detect);
+
+		ast_register_cleanup(test_dsp_shutdown);
+	}
+#endif
+	return res;
 }
 
 int ast_dsp_reload(void)

-- 
To view, visit https://gerrit.asterisk.org/3350
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I8d77c9a1eec89e440d715f998c928687e870c3f7
Gerrit-PatchSet: 2
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Richard Mudgett <rmudgett at digium.com>
Gerrit-Reviewer: Anonymous Coward #1000019
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Richard Mudgett <rmudgett at digium.com>



More information about the asterisk-commits mailing list