aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHolger Hans Peter Freyther <zecke@selfish.org>2012-11-29 21:41:01 +0100
committerHolger Hans Peter Freyther <zecke@selfish.org>2012-12-16 11:33:27 +0100
commitd74ac335748ad2fe4971fd7dcca653284b9bbc17 (patch)
tree68c9ae77ca34bc413bed5e8f6e0bba6e6a6241e3
parent9b2474490a738665247ea3a04648f96411a78d6d (diff)
dtmf: Schedule DTMF tones for the MTN hardware
Create a simple queue for pending DTMF tones, play them using the MTN API, and then send the next tones once the playback is complete. The callback and scheduling is done from the same context so no locking needs to be done.
-rw-r--r--.gitignore4
-rw-r--r--configure.ac1
-rw-r--r--include/Makefile.am2
-rw-r--r--include/dtmf_scheduler.h25
-rw-r--r--include/mgcp/mgcp_internal.h2
-rw-r--r--include/mgcp_ss7.h1
-rw-r--r--src/Makefile.am3
-rw-r--r--src/dtmf_scheduler.c59
-rw-r--r--src/mgcp_ss7.c72
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/dtmf/Makefile.am8
-rw-r--r--tests/dtmf/dtmf_test.c103
-rw-r--r--tests/dtmf/dtmf_test.ok1
-rw-r--r--tests/testsuite.at6
14 files changed, 286 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 867b766..5056481 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,9 @@ stamp-h1
*.o
*.sw?
*.orig
+*.gcda
+*.gcno
+*.info
# binaries
cellmgr_ng
@@ -34,6 +37,7 @@ aclocal.m4
autom4te.cache/
configure.lineno
tests/atconfig
+tests/dtmf/dtmf_test
tests/isup/isup_parse_test
tests/mgcp/mgcp_patch_test
tests/package.m4
diff --git a/configure.ac b/configure.ac
index b37d8d8..f0a50e6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -52,4 +52,5 @@ AC_OUTPUT(
tests/patching/Makefile
tests/isup/Makefile
tests/mgcp/Makefile
+ tests/dtmf/Makefile
Makefile)
diff --git a/include/Makefile.am b/include/Makefile.am
index 01c7b9c..c923515 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -2,6 +2,6 @@ noinst_HEADERS = mtp_level3.h mtp_data.h ipaccess.h thread.h mtp_pcap.h \
mgcp_ss7.h bss_patch.h bssap_sccp.h bsc_data.h udp_input.h \
snmp_mtp.h cellmgr_debug.h bsc_sccp.h bsc_ussd.h sctp_m2ua.h \
isup_types.h counter.h msc_connection.h ss7_application.h \
- mgcp_patch.h ss7_vty.h
+ mgcp_patch.h ss7_vty.h dtmf_scheduler.h
SUBDIRS = mgcp
diff --git a/include/dtmf_scheduler.h b/include/dtmf_scheduler.h
new file mode 100644
index 0000000..22a4e55
--- /dev/null
+++ b/include/dtmf_scheduler.h
@@ -0,0 +1,25 @@
+#ifndef DTMF_SCHEDULER_H
+#define DTMF_SCHEDULER_H
+
+/**
+ * The state/queue for DTMF signalling.
+ */
+struct dtmf_state {
+ int size; /* <! The last tone to play */
+ char tones[24]; /* <! Pending tones */
+ int playing; /* <! Playing a tone right now? */
+};
+
+/* initialize */
+void dtmf_state_init(struct dtmf_state *state);
+
+/* add a tone to the list */
+int dtmf_state_add(struct dtmf_state *state, char tone);
+
+/* tones that should be played, playing will be set to 1 */
+void dtmf_state_get_pending(struct dtmf_state *state, char *tones);
+
+/* call when the playout is done */
+void dtmf_state_played(struct dtmf_state *state);
+
+#endif
diff --git a/include/mgcp/mgcp_internal.h b/include/mgcp/mgcp_internal.h
index 1918cb0..6b2989d 100644
--- a/include/mgcp/mgcp_internal.h
+++ b/include/mgcp/mgcp_internal.h
@@ -24,6 +24,7 @@
#define OPENBSC_MGCP_DATA_H
#include <osmocom/core/select.h>
+#include <dtmf_scheduler.h>
#define CI_UNUSED 0
@@ -124,6 +125,7 @@ struct mgcp_endpoint {
struct mgcp_rtp_tap taps[MGCP_TAP_COUNT];
/* Special MGW handling */
+ struct dtmf_state dtmf_state;
int blocked;
unsigned int hw_dsp_port; /** This is index 1 based */
unsigned int audio_port;
diff --git a/include/mgcp_ss7.h b/include/mgcp_ss7.h
index 4847989..babfde6 100644
--- a/include/mgcp_ss7.h
+++ b/include/mgcp_ss7.h
@@ -46,6 +46,7 @@ enum {
MGCP_SS7_MUTE_STATUS,
MGCP_SS7_ALLOCATE,
MGCP_SS7_DELETE,
+ MGCP_SS7_DTMF,
};
struct mgcp_ss7_cmd {
diff --git a/src/Makefile.am b/src/Makefile.am
index 4ef3edf..e681537 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -6,7 +6,8 @@ AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOSCCP_CFLAGS) $(LIBOSMOVTY_CFLAGS)
sbin_PROGRAMS = cellmgr_ng osmo_stp mgcp_mgw
mgcp_mgw_SOURCES = mgcp_ss7.c mgcp_ss7_vty.c mgcp_hw.c thread.c debug.c \
- mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c
+ mgcp/mgcp_protocol.c mgcp/mgcp_network.c mgcp/mgcp_vty.c \
+ dtmf_scheduler.c
mgcp_mgw_LDADD = $(LAFORGE_LIBS) $(NEXUSWARE_C7_LIBS) $(NEXUSWARE_UNIPORTE_LIBS) \
$(LIBOSMOVTY_LIBS) $(LIBOSMOCORE_LIBS) -lpthread -lcrypto
diff --git a/src/dtmf_scheduler.c b/src/dtmf_scheduler.c
new file mode 100644
index 0000000..26dc090
--- /dev/null
+++ b/src/dtmf_scheduler.c
@@ -0,0 +1,59 @@
+/*
+ * (C) 2012 by Holger Hans Peter Freyther
+ * (C) 2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "dtmf_scheduler.h"
+#include <string.h>
+#include <stdio.h>
+
+void dtmf_state_init(struct dtmf_state *state)
+{
+ memset(state, 0, sizeof(*state));
+}
+
+int dtmf_state_add(struct dtmf_state *state, char tone)
+{
+ /* we would override the head */
+ if (state->size == sizeof(state->tones))
+ return -1;
+
+ state->tones[state->size++] = tone;
+ return 0;
+}
+
+void dtmf_state_get_pending(struct dtmf_state *state, char *tones)
+{
+ int pos;
+
+ for (pos = 0; pos < state->size; ++pos)
+ tones[pos] = state->tones[pos];
+
+ /* consume everything up to the tail */
+ state->size = 0;
+
+ /* remember that we play things */
+ if (pos > 0)
+ state->playing = 1;
+ tones[pos] = '\0';
+}
+
+void dtmf_state_played(struct dtmf_state *state)
+{
+ state->playing = 0;
+}
diff --git a/src/mgcp_ss7.c b/src/mgcp_ss7.c
index 47c162b..c9e9b4f 100644
--- a/src/mgcp_ss7.c
+++ b/src/mgcp_ss7.c
@@ -70,6 +70,44 @@ static void mgcp_ss7_do_exec(struct mgcp_ss7 *mgcp, uint8_t type, struct mgcp_en
/* Contains a mapping from UniPorte to the MGCP side of things */
static struct mgcp_endpoint *s_endpoints[240];
+static void play_pending_tones(struct mgcp_endpoint *endp)
+{
+ ToneGenerationT toneGeneration;
+ char tones[25];
+
+ /* Check if we need to play anything? */
+ dtmf_state_get_pending(&endp->dtmf_state, tones);
+
+ /* nothing to play? */
+ if (strlen(tones) == 0)
+ return;
+
+ /* fill out the data now */
+ osmo_static_assert(sizeof(tones) <= sizeof(toneGeneration.list), Enough_space_for_tones);
+ memset(&toneGeneration, 0, sizeof(toneGeneration));
+ toneGeneration.count = strlen(tones);
+ strcpy(toneGeneration.list, tones);
+ MtnSaSetMOB(endp->audio_port, ChannelType_PORT,
+ PredefMob_C_TONE_GENERATION, (char *) &toneGeneration,
+ sizeof(toneGeneration), 1);
+}
+
+static void send_dtmf(struct mgcp_endpoint *mgw_endp, int ascii_tone)
+{
+ int rc;
+ rc = dtmf_state_add(&mgw_endp->dtmf_state, ascii_tone);
+ if (rc != 0) {
+ fprintf(stderr, "DTMF queue too long on 0x%x\n",
+ ENDPOINT_NUMBER(mgw_endp));
+ syslog(LOG_ERR, "DTMF queue too long on 0x%x\n",
+ ENDPOINT_NUMBER(mgw_endp));
+ return;
+ }
+
+ if (!mgw_endp->dtmf_state.playing)
+ play_pending_tones(mgw_endp);
+}
+
static int select_voice_port(struct mgcp_endpoint *endp)
{
int mgw_port;
@@ -200,6 +238,24 @@ static int uniporte_events(unsigned long port, EventTypeT event,
fprintf(stderr, "State change on a non blocked port. ERROR.\n");
}
endp->block_processing = 0;
+ } else if (info->trapId == Trap_TONE_GENERATION_COMPLETE) {
+ sprintf(text, "DTMF complete on #%ld", port);
+ puts(text);
+
+ /* update the mgcp state */
+ if (port >= ARRAY_SIZE(s_endpoints)) {
+ syslog(LOG_ERR, "The port is bigger than we can manage.\n");
+ fprintf(stderr, "The port is bigger than we can manage.\n");
+ return 0;
+ }
+
+ endp = s_endpoints[port];
+ if (!endp) {
+ syslog(LOG_ERR, "Unexpected event on port %d\n", port);
+ fprintf(stderr, "Unexpected event on port %d\n", port);
+ return 0;
+ }
+ play_pending_tones(endp);
}
}
else if ( event == Event_MANAGED_OBJECT_SET_COMPLETE ) {
@@ -356,6 +412,9 @@ static void allocate_endp(struct mgcp_ss7 *ss7, struct mgcp_endpoint *endp)
int mgw_port;
unsigned long mgw_address, loc_address;
+ /* reset the DTMF state */
+ dtmf_state_init(&endp->dtmf_state);
+
/* now find the voice processor we want to use */
mgw_port = select_voice_port(endp);
if (mgw_port < 0)
@@ -471,6 +530,10 @@ static void mgcp_ss7_do_exec(struct mgcp_ss7 *mgcp, uint8_t type,
if (mgw_endp->audio_port != UINT_MAX)
update_mute_status(mgw_endp->audio_port, param);
break;
+ case MGCP_SS7_DTMF:
+ if (mgw_endp->audio_port != UINT_MAX)
+ send_dtmf(mgw_endp, param);
+ break;
case MGCP_SS7_DELETE:
if (mgw_endp->audio_port != UINT_MAX) {
rc = MtnSaDisconnect(mgw_endp->audio_port);
@@ -487,6 +550,7 @@ static void mgcp_ss7_do_exec(struct mgcp_ss7 *mgcp, uint8_t type,
mgw_endp->audio_port = UINT_MAX;
mgw_endp->block_processing = 1;
}
+ dtmf_state_init(&mgw_endp->dtmf_state);
hw_maybe_loop_endp(mgw_endp);
break;
case MGCP_SS7_ALLOCATE:
@@ -579,6 +643,12 @@ static int mgcp_ss7_policy(struct mgcp_trunk_config *tcfg, int endp_no, int stat
return rc;
}
+static int mgcp_dtmf_cb(struct mgcp_endpoint *endp, char tone, const char *data)
+{
+ mgcp_ss7_exec(endp, MGCP_SS7_DTMF, tone);
+ return 0;
+}
+
static void enqueue_msg(struct osmo_wqueue *queue, struct sockaddr_in *addr, struct msgb *msg)
{
struct sockaddr_in *data;
@@ -905,6 +975,8 @@ int main(int argc, char **argv)
return -1;
}
+ g_cfg->rqnt_cb = mgcp_dtmf_cb;
+
if (mgcp_parse_config(config_file, g_cfg) != 0) {
LOGP(DMGCP, LOGL_ERROR,
"Failed to parse the config file: '%s'\n", config_file);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 6efc649..5c9cdd5 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = mtp patching isup mgcp
+SUBDIRS = mtp patching isup mgcp dtmf
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
diff --git a/tests/dtmf/Makefile.am b/tests/dtmf/Makefile.am
new file mode 100644
index 0000000..4241120
--- /dev/null
+++ b/tests/dtmf/Makefile.am
@@ -0,0 +1,8 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS)
+noinst_PROGRAMS = dtmf_test
+
+EXTRA_DIST = dtmf_test.ok
+
+dtmf_test_SOURCES = dtmf_test.c $(top_srcdir)/src/dtmf_scheduler.c
+dtmf_test_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/tests/dtmf/dtmf_test.c b/tests/dtmf/dtmf_test.c
new file mode 100644
index 0000000..5ffb121
--- /dev/null
+++ b/tests/dtmf/dtmf_test.c
@@ -0,0 +1,103 @@
+/*
+ * (C) 2012 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2012 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <dtmf_scheduler.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define ASSERT(got,want) \
+ if (got != want) { \
+ fprintf(stderr, "Values should be the same 0x%x 0x%x at %s:%d\n", \
+ got, want, __FILE__, __LINE__); \
+ abort(); \
+ }
+
+static void test_queue_while_play(void)
+{
+ struct dtmf_state state;
+ char tone[sizeof(state.tones) + 1];
+
+ dtmf_state_init(&state);
+
+ ASSERT(dtmf_state_add(&state, 'a'), 0);
+ ASSERT(dtmf_state_add(&state, 'b'), 0);
+ ASSERT(dtmf_state_add(&state, 'c'), 0);
+
+ dtmf_state_get_pending(&state, tone);
+ ASSERT(strlen(tone), 3);
+ ASSERT(state.playing, 1);
+ ASSERT(strcmp(tone, "abc"), 0);
+
+ ASSERT(dtmf_state_add(&state, 'd'), 0);
+ dtmf_state_played(&state);
+ ASSERT(state.playing, 0);
+
+ dtmf_state_get_pending(&state, tone);
+ ASSERT(strlen(tone), 1);
+ ASSERT(state.playing, 1);
+ ASSERT(strcmp(tone, "d"), 0);
+
+ ASSERT(state.playing, 1);
+ dtmf_state_played(&state);
+ ASSERT(state.playing, 0);
+
+ /* and check that nothing is played */
+ dtmf_state_get_pending(&state, tone);
+ ASSERT(strlen(tone), 0);
+ ASSERT(state.playing, 0);
+}
+
+static void test_queue_over_flow(void)
+{
+ struct dtmf_state state;
+ const size_t max_items = sizeof(state.tones);
+ char tone[sizeof(state.tones) + 1];
+ int i;
+
+ dtmf_state_init(&state);
+
+ /* add everything that should fit.. */
+ for (i = 0; i < max_items; ++i) {
+ ASSERT(dtmf_state_add(&state, 'a' + i), 0);
+ }
+
+ /* this should fail */
+ ASSERT(dtmf_state_add(&state, 'Z'), -1);
+
+ /* read all of it */
+ dtmf_state_get_pending(&state, tone);
+ ASSERT(strlen(tone), max_items);
+ for (i = 0; i < strlen(tone); ++i)
+ ASSERT(tone[i], 'a' + i);
+ ASSERT(state.playing, 1);
+ dtmf_state_played(&state);
+ ASSERT(state.playing, 0);
+}
+
+
+int main(int argc, char **argv)
+{
+ test_queue_while_play();
+ test_queue_over_flow();
+ printf("All tests passed.\n");
+ return 0;
+}
diff --git a/tests/dtmf/dtmf_test.ok b/tests/dtmf/dtmf_test.ok
new file mode 100644
index 0000000..828a010
--- /dev/null
+++ b/tests/dtmf/dtmf_test.ok
@@ -0,0 +1 @@
+All tests passed.
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 069a502..de06c92 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -24,3 +24,9 @@ AT_KEYWORDS([patching])
cat $abs_srcdir/patching/patching_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/patching/patching_test], [], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([dtmf])
+AT_KEYWORDS([dtmf])
+cat $abs_srcdir/dtmf/dtmf_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/dtmf/dtmf_test], [], [expout], [ignore])
+AT_CLEANUP