9
0
Fork 0

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.
This commit is contained in:
Holger Hans Peter Freyther 2012-11-29 21:41:01 +01:00
parent 9b2474490a
commit d74ac33574
14 changed files with 286 additions and 3 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -52,4 +52,5 @@ AC_OUTPUT(
tests/patching/Makefile
tests/isup/Makefile
tests/mgcp/Makefile
tests/dtmf/Makefile
Makefile)

View File

@ -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

25
include/dtmf_scheduler.h Normal file
View File

@ -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

View File

@ -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;

View File

@ -46,6 +46,7 @@ enum {
MGCP_SS7_MUTE_STATUS,
MGCP_SS7_ALLOCATE,
MGCP_SS7_DELETE,
MGCP_SS7_DTMF,
};
struct mgcp_ss7_cmd {

View File

@ -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

59
src/dtmf_scheduler.c Normal file
View File

@ -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;
}

View File

@ -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);

View File

@ -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

8
tests/dtmf/Makefile.am Normal file
View File

@ -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)

103
tests/dtmf/dtmf_test.c Normal file
View File

@ -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;
}

1
tests/dtmf/dtmf_test.ok Normal file
View File

@ -0,0 +1 @@
All tests passed.

View File

@ -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