Initial GIT import

laforge/submodule
Andreas Eversberg 2 years ago
commit c354814c97
  1. 44
      .gitignore
  2. 5
      Makefile.am
  3. 92
      configure.ac
  4. 151
      git-version-gen
  5. 13
      src/Makefile.am
  6. 23
      src/ss5/Makefile.am
  7. 9
      src/ss5/display.h
  8. 169
      src/ss5/display_status.c
  9. 552
      src/ss5/dsp.c
  10. 46
      src/ss5/dsp.h
  11. 346
      src/ss5/main.c
  12. 202
      src/ss5/mf.c
  13. 34
      src/ss5/mf.h
  14. 1128
      src/ss5/ss5.c
  15. 85
      src/ss5/ss5.h

44
.gitignore vendored

@ -0,0 +1,44 @@
Makefile
Makefile.in
.deps
.libs
*.o
*.lo
*.la
*.pc
aclocal.m4
acinclude.m4
aminclude.am
m4/*.m4
autom4te.cache
compile
config.h*
config.sub
config.log
config.status
config.guess
configure
depcomp
missing
ltmain.sh
install-sh
stamp-h1
libtool
.tarball-version
.version
.dirstamp
Doxyfile
.*.sw?
src/libdebug/libdebug.a
src/libg711/libg711.a
src/libjitter/libjitter.a
src/liboptions/liboptions.a
src/libosmocc/libosmocc.a
src/libsample/libsample.a
src/libtimer/libtimer.a
src/libfilter/libfilter.a
src/ss5/osmo-cc-ss5-endpoint

@ -0,0 +1,5 @@
AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src

@ -0,0 +1,92 @@
AC_INIT([osmo-cc-ss5-endpoint],
m4_esyscmd([./git-version-gen .tarball-version]),
[jolly@eversberg.eu])
dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([subdir-objects dist-bzip2])
AC_CONFIG_TESTDIR(tests)
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl include release helper
RELMAKE='-include osmo-release.mk'
AC_SUBST([RELMAKE])
dnl checks for programs
AC_PROG_MAKE_SET
AC_PROG_CC
AC_PROG_INSTALL
LT_INIT
AC_PROG_LIBTOOL
AC_CONFIG_MACRO_DIR([m4])
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
[--enable-sanitize],
[Compile with address sanitizer enabled],
)],
[sanitize=$enableval], [sanitize="no"])
if test x"$sanitize" = x"yes"
then
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
AC_ARG_ENABLE(werror,
[AS_HELP_STRING(
[--enable-werror],
[Turn all compiler warnings into errors, with exceptions:
a) deprecation (allow upstream to mark deprecation without breaking builds);
b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
]
)],
[werror=$enableval], [werror="no"])
if test x"$werror" = x"yes"
then
WERROR_FLAGS="-Werror"
WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
CFLAGS="$CFLAGS $WERROR_FLAGS"
CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
fi
CFLAGS="$CFLAGS -Wall"
CPPFLAGS="$CPPFLAGS -Wall"
dnl checks for header files
AC_HEADER_STDC
AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h syslog.h ctype.h)
# The following test is taken from WebKit's webkit.m4
saved_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -fvisibility=hidden "
AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
[ AC_MSG_RESULT([yes])
SYMBOL_VISIBILITY="-fvisibility=hidden"],
AC_MSG_RESULT([no]))
CFLAGS="$saved_CFLAGS"
AC_SUBST(SYMBOL_VISIBILITY)
dnl Generate the output
AM_CONFIG_HEADER(config.h)
AC_CHECK_LIB([m], [main])
AC_CHECK_LIB([pthread], [main])
AC_OUTPUT(
src/liboptions/Makefile
src/libdebug/Makefile
src/libsample/Makefile
src/libtimer/Makefile
src/libjitter/Makefile
src/libosmocc/Makefile
src/libg711/Makefile
src/libfilter/Makefile
src/ss5/Makefile
src/Makefile
Makefile)

@ -0,0 +1,151 @@
#!/bin/sh
# Print a version string.
scriptversion=2010-01-28.01
# Copyright (C) 2007-2010 Free Software Foundation, Inc.
#
# 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 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
# It may be run two ways:
# - from a git repository in which the "git describe" command below
# produces useful output (thus requiring at least one signed tag)
# - from a non-git-repo directory containing a .tarball-version file, which
# presumes this script is invoked like "./git-version-gen .tarball-version".
# In order to use intra-version strings in your project, you will need two
# separate generated version string files:
#
# .tarball-version - present only in a distribution tarball, and not in
# a checked-out repository. Created with contents that were learned at
# the last time autoconf was run, and used by git-version-gen. Must not
# be present in either $(srcdir) or $(builddir) for git-version-gen to
# give accurate answers during normal development with a checked out tree,
# but must be present in a tarball when there is no version control system.
# Therefore, it cannot be used in any dependencies. GNUmakefile has
# hooks to force a reconfigure at distribution time to get the value
# correct, without penalizing normal development with extra reconfigures.
#
# .version - present in a checked-out repository and in a distribution
# tarball. Usable in dependencies, particularly for files that don't
# want to depend on config.h but do want to track version changes.
# Delete this file prior to any autoconf run where you want to rebuild
# files to pick up a version string change; and leave it stale to
# minimize rebuild time after unrelated changes to configure sources.
#
# It is probably wise to add these two files to .gitignore, so that you
# don't accidentally commit either generated file.
#
# Use the following line in your configure.ac, so that $(VERSION) will
# automatically be up-to-date each time configure is run (and note that
# since configure.ac no longer includes a version string, Makefile rules
# should not depend on configure.ac for version updates).
#
# AC_INIT([GNU project],
# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
# [bug-project@example])
#
# Then use the following lines in your Makefile.am, so that .version
# will be present for dependencies, and so that .tarball-version will
# exist in distribution tarballs.
#
# BUILT_SOURCES = $(top_srcdir)/.version
# $(top_srcdir)/.version:
# echo $(VERSION) > $@-t && mv $@-t $@
# dist-hook:
# echo $(VERSION) > $(distdir)/.tarball-version
case $# in
1) ;;
*) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
esac
tarball_version_file=$1
nl='
'
# First see if there is a tarball-only version file.
# then try "git describe", then default.
if test -f $tarball_version_file
then
v=`cat $tarball_version_file` || exit 1
case $v in
*$nl*) v= ;; # reject multi-line output
[0-9]*) ;;
*) v= ;;
esac
test -z "$v" \
&& echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
fi
if test -n "$v"
then
: # use $v
elif
v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
|| git describe --abbrev=4 HEAD 2>/dev/null` \
&& case $v in
[0-9]*) ;;
v[0-9]*) ;;
*) (exit 1) ;;
esac
then
# Is this a new git that lists number of commits since the last
# tag or the previous older version that did not?
# Newer: v6.10-77-g0f8faeb
# Older: v6.10-g0f8faeb
case $v in
*-*-*) : git describe is okay three part flavor ;;
*-*)
: git describe is older two part flavor
# Recreate the number of commits and rewrite such that the
# result is the same as if we were using the newer version
# of git describe.
vtag=`echo "$v" | sed 's/-.*//'`
numcommits=`git rev-list "$vtag"..HEAD | wc -l`
v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
;;
esac
# Change the first '-' to a '.', so version-comparing tools work properly.
# Remove the "g" in git describe's output string, to save a byte.
v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
else
v=UNKNOWN
fi
v=`echo "$v" |sed 's/^v//'`
# Don't declare a version "dirty" merely because a time stamp has changed.
git status > /dev/null 2>&1
dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
case "$dirty" in
'') ;;
*) # Append the suffix only if there isn't one already.
case $v in
*-dirty) ;;
*) v="$v-dirty" ;;
esac ;;
esac
# Omit the trailing newline, so that m4_esyscmd can use the result directly.
echo "$v" | tr -d '\012'
# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-end: "$"
# End:

@ -0,0 +1,13 @@
AUTOMAKE_OPTIONS = foreign
SUBDIRS = \
liboptions \
libdebug \
libsample \
libtimer \
libjitter \
libosmocc \
libg711 \
libfilter \
ss5

@ -0,0 +1,23 @@
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
bin_PROGRAMS = \
osmo-cc-ss5-endpoint
osmo_cc_ss5_endpoint_SOURCES = \
mf.c \
dsp.c \
ss5.c \
display_status.c \
main.c
osmo_cc_ss5_endpoint_LDADD = \
$(COMMON_LA) \
../libdebug/libdebug.a \
../liboptions/liboptions.a \
../libsample/libsample.a \
../libtimer/libtimer.a \
../libjitter/libjitter.a \
../libosmocc/libosmocc.a \
../libg711/libg711.a \
../libfilter/libfilter.a

@ -0,0 +1,9 @@
#define MAX_DISPLAY_WIDTH 1024
#define MAX_HEIGHT_STATUS 64
void display_status_on(int on);
void display_status_start(void);
void display_status_line(const char *if_name, int link_count, const char *from_id, const char *to_id, enum ss5_state state);
void display_status_end(void);

@ -0,0 +1,169 @@
/* display status functions
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include "../libdebug/debug.h"
#include "ss5.h"
#include "display.h"
static int status_on = 0;
static int line_count = 0;
static int lines_total = 0;
static char screen[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH];
static char screen_color[MAX_HEIGHT_STATUS][MAX_DISPLAY_WIDTH];
static void print_status(int on)
{
int i, j;
int w, h;
char color, last_color = -1;
get_win_size(&w, &h);
if (w > MAX_DISPLAY_WIDTH - 1)
w = MAX_DISPLAY_WIDTH - 1;
if (w > MAX_DISPLAY_WIDTH)
w = MAX_DISPLAY_WIDTH;
h--;
if (h > lines_total)
h = lines_total;
printf("\0337\033[H");
for (i = 0; i < h; i++) {
j = 0;
if (on) {
for (j = 0; j < w; j++) {
color = screen_color[i][j];
if (screen[i][j] > ' ' && last_color != color) {
printf("\033[%d;3%dm", color / 10, color % 10);
last_color = color;
}
putchar(screen[i][j]);
}
} else {
for (j = 0; j < w; j++)
putchar(' ');
}
putchar('\n');
}
printf("\033[0;39m\0338"); fflush(stdout);
}
void display_status_on(int on)
{
if (status_on)
print_status(0);
if (on < 0)
status_on = 1 - status_on;
else
status_on = on;
if (status_on)
print_status(1);
if (status_on)
debug_limit_scroll = lines_total;
else
debug_limit_scroll = 0;
}
/* start status display */
void display_status_start(void)
{
memset(screen, ' ', sizeof(screen));
memset(screen_color, 7, sizeof(screen_color));
memset(screen[0], '-', sizeof(screen[0]));
memcpy(screen[0] + 4, "Call Status", 11);
line_count = 1;
}
void display_status_line(const char *if_name, int link_count, const char *from_id, const char *to_id, enum ss5_state state)
{
char line[MAX_DISPLAY_WIDTH + 4096];
char color[MAX_DISPLAY_WIDTH + 4096];
int index, from_len, to_len;
memset(color, 7, sizeof(color)); // make valgrind happy
if (line_count == MAX_HEIGHT_STATUS)
return;
/* at first interface or when it changes */
if (!link_count)
line_count++;
/* check line count again */
if (line_count == MAX_HEIGHT_STATUS)
return;
if (!link_count) {
/* interface */
strcpy(line, if_name);
memset(color, 3, strlen(if_name));
} else {
memset(line, ' ', strlen(if_name));
}
index = strlen(if_name);
if (from_id && to_id) {
/* '<id>' */
sprintf(line + index, " \'%s\' -> \'%s\' ", from_id, to_id);
from_len = strlen(from_id);
to_len = strlen(to_id);
memset(color + index, 1, 4 + from_len);
memset(color + index + 4 + from_len, 7, 2);
memset(color + index + 4 + from_len + 2, 2, 4 + to_len);
index += 4 + from_len + 2 + 4 + to_len;
line[index] = '\0';
strcpy(line + index, ss5_state_names[state]);
memset(color + index, 7, strlen(ss5_state_names[state]));
}
/* store line without CR, but not more than MAX_DISPLAY_WIDTH - 1 */
line[MAX_DISPLAY_WIDTH - 1] = '\0';
memcpy(screen[line_count], line, strlen(line));
memcpy(screen_color[line_count], color, strlen(line));
line_count++;
}
void display_status_end(void)
{
if (line_count < MAX_HEIGHT_STATUS)
line_count++;
if (line_count < MAX_HEIGHT_STATUS) {
memset(screen[line_count], '-', sizeof(screen[line_count]));
line_count++;
}
/* if last total lines exceed current line count, keep it, so removed lines are overwritten with spaces */
if (line_count > lines_total)
lines_total = line_count;
if (status_on)
print_status(1);
/* set new total lines */
lines_total = line_count;
if (status_on)
debug_limit_scroll = lines_total;
}

@ -0,0 +1,552 @@
/* DSP functions
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define CHAN ((ss5_t *)(dsp->priv))->name
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <errno.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include "../libdebug/debug.h"
#include "ss5.h"
//#define DEBUG_DEMODULATOR
#define NUM_TONES 8
#define db2level(db) pow(10, (double)(db) / 20.0)
#define level2db(level) (20 * log10(level))
static double tone_dbm[NUM_TONES] = { -7, -7, -7, -7, -7, -7, -9, -9 };
static double tone_freq[NUM_TONES] = { 700, 900, 1100, 1300, 1500, 1700, 2400, 2600 };
static double tone_width[NUM_TONES] = { 25, 25, 25, 25, 25, 25, 25, 25 };
static double tone_min_dbm[NUM_TONES] = { -14, -14, -14, -14, -14, -14, -16, -16 };
static double tone_min_ampl_sq[NUM_TONES];
static double tone_diff_db[NUM_TONES] = { 4, 4, 4, 4, 4, 4, 5, 5 };
static double tone_diff_ampl_sq[NUM_TONES];
#define INTERRUPT_DURATION 0.015
#define SPLIT_DURATION 0.030
#define MF_RECOGNITION 0.025
int dsp_init_inst(dsp_t *dsp, void *priv, double samplerate, double sense_db)
{
double tone_amplitude[NUM_TONES];
int t;
PDEBUG(DDSP, DEBUG_DEBUG, "Init DSP for SS5 instance.\n");
memset(dsp, 0, sizeof(*dsp));
dsp->priv = priv;
dsp->samplerate = samplerate;
dsp->interrupt_duration = (int)(1000.0 * INTERRUPT_DURATION);
dsp->split_duration = (int)(1000.0 * SPLIT_DURATION);
dsp->mf_detect_duration = (int)(1000.0 * MF_RECOGNITION);
dsp->ms_per_sample = 1000.0 / samplerate;
dsp->detect_tone = ' ';
/* convert dbm of tones to speech level */
for (t = 0; t < NUM_TONES; t++) {
tone_amplitude[t] = db2level(tone_dbm[t]) / SPEECH_LEVEL;
tone_min_ampl_sq[t] = pow(db2level(tone_min_dbm[t] - sense_db) / SPEECH_LEVEL, 2);
tone_diff_ampl_sq[t] = pow(db2level(tone_diff_db[t]), 2);
}
/* init MF modulator */
dsp->mf_mod = mf_mod_init(samplerate, NUM_TONES, tone_freq, tone_amplitude);
if (!dsp->mf_mod)
return -EINVAL;
/* init MF demodulator */
dsp->mf_demod = mf_demod_init(samplerate, NUM_TONES, tone_freq, tone_width);
if (!dsp->mf_mod)
return -EINVAL;
return 0;
}
void dsp_cleanup_inst(dsp_t *dsp)
{
PDEBUG(DDSP, DEBUG_DEBUG, "Cleanup DSP of SS5 instance.\n");
if (dsp->mf_mod) {
mf_mod_exit(dsp->mf_mod);
dsp->mf_mod = NULL;
}
if (dsp->mf_demod) {
mf_demod_exit(dsp->mf_demod);
dsp->mf_demod = NULL;
}
}
/*
* tone encoder
*/
static struct dsp_digits {
char tone;
uint32_t mask;
} dsp_digits[] = {
{ '1', 0x01 + 0x02 },
{ '2', 0x01 + 0x04 },
{ '3', 0x02 + 0x04 },
{ '4', 0x01 + 0x08 },
{ '5', 0x02 + 0x08 },
{ '6', 0x04 + 0x08 },
{ '7', 0x01 + 0x10 },
{ '8', 0x02 + 0x10 },
{ '9', 0x04 + 0x10 },
{ '0', 0x08 + 0x10 },
{ '*', 0x01 + 0x20 }, /* code 11 */
{ '#', 0x02 + 0x20 }, /* code 12 */
{ 'a', 0x04 + 0x20 }, /* KP1 */
{ 'b', 0x08 + 0x20 }, /* KP2 */
{ 'c', 0x10 + 0x20 }, /* ST */
{ 'A', 0x40 }, /* 2400 answer, acknowledge */
{ 'B', 0x80 }, /* 2600 busy */
{ 'C', 0x40 + 0x80 }, /* 2600+2400 clear forward */
{ ' ', 0 }, /* silence */
{ 0 , 0 },
};
#define KP_DIGIT_DURATION 0.100
#define OTHER_DIGIT_DURATION 0.055
#define DIGIT_PAUSE 0.055
/* set signaling tone duration threshold */
void set_sig_detect_duration(dsp_t *dsp, double duration_AB, double duration_C)
{
dsp->detect_count = 0;
dsp->sig_detect_duration_AB = (int)(1000.0 * duration_AB);
dsp->sig_detect_duration_C = (int)(1000.0 * duration_C);
}
/* set given tone with duration (ms) or continuously (0) */
void set_tone(dsp_t *dsp, char tone, double duration)
{
int i;
dsp->tone = 0;
if (!tone) {
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Remove tone\n");
return;
}
for (i = 0; dsp_digits[i].tone; i++) {
if (dsp_digits[i].tone == tone) {
dsp->tone_mask = dsp_digits[i].mask;
dsp->tone = tone;
dsp->tone_duration = (int)(dsp->samplerate * duration);
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Set tone=\'%c\' duration=%.0fms (mask = 0x%02x)\n", dsp->tone, 1000.0 * duration, dsp->tone_mask);
return;
}
}
}
/* get next tone from dial string, if any */
static void get_tone_from_dial_string(dsp_t *dsp)
{
char tone;
double duration;
dsp->tone = 0;
if (dsp->dial_index == dsp->dial_length) {
dsp->dial_length = 0;
dialing_complete(dsp->priv);
return;
}
/* get alternating tone/pause from dial string */
if (!dsp->digit_pause) {
/* digit on */
tone = dsp->dial_string[dsp->dial_index++];
dsp->digit_pause = 1;
if (tone == 'a' || tone == 'b')
duration = KP_DIGIT_DURATION;
else
duration = OTHER_DIGIT_DURATION;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Send digit \'%c\' from dial string\n", tone);
} else {
/* digit pause */
tone = ' ';
dsp->digit_pause = 0;
duration = DIGIT_PAUSE;
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Send pause after digit from dial string\n");
}
set_tone(dsp, tone, duration);
}
/* set given dial string */
void set_dial_string(dsp_t *dsp, const char *dial)
{
dsp->digit_pause = 0;
strncpy(dsp->dial_string, dial, sizeof(dsp->dial_string) - 1);
dsp->dial_index = 0;
dsp->dial_length = strlen(dsp->dial_string);
}
/* determine which tones to be modulated, get next tone, if elapsed */
static int assemble_tones(dsp_t *dsp, uint32_t *mask, int length)
{
int i;
for (i = 0; i < length; i++) {
/* if tone was done, try to get next digit */
if (!dsp->tone) {
if (!dsp->dial_length)
return i;
get_tone_from_dial_string(dsp);
if (!dsp->tone)
return i;
}
*mask++ = dsp->tone_mask;
if (dsp->tone_duration) {
/* count down duration, if tones is not continuous */
if (!(--dsp->tone_duration))
dsp->tone = 0;
}
}
return i;
}
/*
* tone deencoder
*/
/* detection array for one frequency */
static char decode_one[8] =
{ ' ', ' ', ' ', ' ', ' ', ' ', 'A', 'B' }; /* A = 2400, B = 2600 */
/* detection matrix for two frequencies */
static char decode_two[8][8] =
{
{ ' ', '1', '2', '4', '7', '*', ' ', ' ' }, /* * = code 11 */
{ '1', ' ', '3', '5', '8', '#', ' ', ' ' }, /* # = code 12 */
{ '2', '3', ' ', '6', '9', 'a', ' ', ' ' }, /* a = KP1 */
{ '4', '5', '6', ' ', '0', 'b', ' ', ' ' }, /* b = KP2 */
{ '7', '8', '9', '0', ' ', 'c', ' ', ' ' }, /* c = ST */
{ '*', '#', 'a', 'b', 'c', ' ', ' ', ' ' },
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'C' }, /* C = 2600+2400 */
{ ' ', ' ', ' ', ' ', ' ', ' ', 'C', ' ' }
};
#define NONE_MIN_LEVEL_SQUARED
/* determine which tone is played */
static void detect_tones(dsp_t *dsp, sample_t *samples, sample_t **levels_squared, int length, int incoming)
{
int f1, f2;
double f1_level_squared, f2_level_squared;
char tone;
int s, t;
for (s = 0; s < length; s++) {
/* mute if split duration reached */
if (dsp->split_duration && dsp->split_count == dsp->split_duration)
samples[s] = 0.0;
/* only perform tone detection every millisecond */
dsp->detect_interval += dsp->ms_per_sample;
if (dsp->detect_interval < 1.0)
continue;
dsp->detect_interval -= 1.0;
if (incoming) {
#ifdef DEBUG_DEMODULATOR
for (t = 0; t < dsp->mf_demod->tones; t++) {
char level[20];
int db;
memset(level, 32, sizeof(level));
db = roundf(level2db(sqrt(levels_squared[t][s]) * SPEECH_LEVEL) + 25);
if (db >= 0 && db < (int)sizeof(level))
level[db] = '*';
level[sizeof(level)-1]=0;
printf("%s|", level);
}
printf("\n");
#endif
}
/* find the tone which is the loudest */
f1 = -1;
f1_level_squared = -1.0;
for (t = 0; t < dsp->mf_demod->tones; t++) {
if (levels_squared[t][s] > f1_level_squared) {
f1_level_squared = levels_squared[t][s];
f1 = t;
}
}
/* find the tone which is the second loudest */
f2 = -1;
f2_level_squared = -1.0;
for (t = 0; t < dsp->mf_demod->tones; t++) {
if (t == f1)
continue;
if (levels_squared[t][s] > f2_level_squared) {
f2_level_squared = levels_squared[t][s];
f2 = t;
}
}
/* now check if the minimum level is reached */
if (f1 >= 0 && f1_level_squared < tone_min_ampl_sq[f1])
f1 = -1;
if (f2 >= 0 && f2_level_squared < tone_min_ampl_sq[f2])
f2 = -1;
// printf("%s f1=%.0f (%.1f dBm) f2=%.0f (%.1f dBm)\n", CHAN, (f1 >= 0) ? tone_freq[f1] : 0, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL), (f2 >= 0) ? tone_freq[f2] : 0, level2db(sqrt(f2_level_squared) * SPEECH_LEVEL));
/* check if no, one or two tones are detected */
if (f1 < 0)
tone = ' ';
else if (f2 < 0)
tone = decode_one[f1];
else {
if (f2_level_squared * tone_diff_ampl_sq[f2] < f1_level_squared)
tone = ' ';
else
tone = decode_two[f1][f2];
}
//printf("tone=%c\n", tone);
/* process interrupt counting, keep tone until interrupt counter expires */
if (dsp->detect_tone != ' ' && tone != dsp->detect_tone) {
if (dsp->interrupt_count < dsp->interrupt_duration) {
dsp->interrupt_count++;
tone = dsp->detect_tone;
}
} else
dsp->interrupt_count = 0;
/* split audio, after minimum duration of detecting a tone */
if (tone >= 'A' && tone <= 'C') {
if (dsp->split_count < dsp->split_duration)
dsp->split_count++;
} else
dsp->split_count = 0;
/* some change in tone */
if (dsp->detect_tone != tone) {
if (dsp->detect_count == 0)
PDEBUG_CHAN(DDSP, DEBUG_DEBUG, "Detected new tone '%c' (%.1f dBm)\n", tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL));
switch (tone) {
case 'A':
case 'B':
/* tone appears, wait some time */
if (dsp->detect_count < dsp->sig_detect_duration_AB)
dsp->detect_count++;
else {
/* sign tone detected */
dsp->detect_count = 0;
dsp->detect_tone = tone;
receive_digit(dsp->priv, tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL));
}
break;
case 'C':
/* tone appears, wait some time */
if (dsp->detect_count < dsp->sig_detect_duration_C)
dsp->detect_count++;
else {
/* sign tone detected */
dsp->detect_count = 0;
dsp->detect_tone = tone;
receive_digit(dsp->priv, tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL));
}
break;
case ' ':
/* tone appears or ceases */
dsp->detect_count = 0;
dsp->detect_tone = tone;
receive_digit(dsp->priv, tone, 0.0);
break;
default:
/* tone appears, wait some time */
if (dsp->detect_count < dsp->mf_detect_duration)
dsp->detect_count++;
else {
/* sign tone detected */
dsp->detect_count = 0;
dsp->detect_tone = tone;
receive_digit(dsp->priv, tone, level2db(sqrt(f1_level_squared) * SPEECH_LEVEL));
}
}
} else
dsp->detect_count = 0;
}
}
/* process audio from one link (source) to another (destination) */
static void process_audio(ss5_t *ss5_a, ss5_t *ss5_b, int length)
{
sample_t samples[2][length], s;
sample_t b1[length], b2[length], b3[length], b4[length], b5[length], b6[length], b7[length], b8[length];
sample_t *levels_squared[NUM_TONES] = { b1, b2, b3, b4, b5, b6, b7, b8 };
uint32_t mask[length];
int16_t data[160];
int count1, count2;
int i;
/* trigger reception of RTP stuff */
if (ss5_a->cc_session)
osmo_cc_session_handle(ss5_a->cc_session);
if (ss5_b->cc_session)
osmo_cc_session_handle(ss5_b->cc_session);
/* get audio from jitter buffer */
jitter_load(&ss5_a->dejitter, samples[0], length);
jitter_load(&ss5_b->dejitter, samples[1], length);
/* optionally add comfort noise */
if (!ss5_a->cc_callref && ss5_a->ss5_ep->comfort_noise) {
for (i = 0; i < length; i++)
samples[0][i] += (double)((int8_t)random()) / 2000.0;
}
if (!ss5_b->cc_callref && ss5_b->ss5_ep->comfort_noise) {
for (i = 0; i < length; i++)
samples[1][i] += (double)((int8_t)random()) / 2000.0;
}
/* modulate tone/digit. if no tone has to be played (or it stopped), count is less than length */
count1 = assemble_tones(&ss5_a->dsp, mask, length);
mf_mod(ss5_a->dsp.mf_mod, mask, samples[0], count1);
count2 = assemble_tones(&ss5_b->dsp, mask, length);
mf_mod(ss5_b->dsp.mf_mod, mask, samples[1], count2);
/* optionally add some crosstalk */
if (ss5_a->ss5_ep->crosstalk) {
/* use count, since it carries number of samples with signalling */
for (i = 0; i < count1; i++)
samples[1][i] += samples[0][i] / 70.0;
}
if (ss5_b->ss5_ep->crosstalk) {
/* use count, since it carries number of samples with signalling */
for (i = 0; i < count2; i++)
samples[0][i] += samples[1][i] / 70.0;
}
/* ! here is the bridge from a to b and from b to a ! */
/* optionally add one way delay */
if (ss5_b->delay_buffer) {
for (i = 0; i < length; i++) {
s = ss5_b->delay_buffer[ss5_b->delay_index];
ss5_b->delay_buffer[ss5_b->delay_index] = samples[0][i];
if (++(ss5_b->delay_index) == ss5_b->delay_length)
ss5_b->delay_index = 0;
samples[0][i] = s;
}
}
if (ss5_a->delay_buffer) {
for (i = 0; i < length; i++) {
s = ss5_a->delay_buffer[ss5_a->delay_index];
ss5_a->delay_buffer[ss5_a->delay_index] = samples[1][i];
if (++(ss5_a->delay_index) == ss5_a->delay_length)
ss5_a->delay_index = 0;
samples[1][i] = s;
}
}
/* demodulate and call tone detector */
mf_demod(ss5_b->dsp.mf_demod, samples[0], length, levels_squared);
detect_tones(&ss5_b->dsp, samples[0], levels_squared, length, 1);
mf_demod(ss5_a->dsp.mf_demod, samples[1], length, levels_squared);
detect_tones(&ss5_a->dsp, samples[1], levels_squared, length, 0);
/* forward audio to CC if call exists */
if (ss5_b->cc_callref && ss5_b->codec) {
samples_to_int16(data, samples[0], length);
osmo_cc_rtp_send(ss5_b->codec, (uint8_t *)data, length * 2, 1, length);
}
if (ss5_a->cc_callref && ss5_a->codec) {
samples_to_int16(data, samples[1], length);
osmo_cc_rtp_send(ss5_a->codec, (uint8_t *)data, length * 2, 1, length);
}
}
/* clock is called every given number of samples (20ms) */
void audio_clock(ss5_endpoint_t *ss5_ep_sunset, ss5_endpoint_t *ss5_ep_sunrise, int len)
{
ss5_t *ss5_a, *ss5_b;
if (!ss5_ep_sunset)
return;
if (!ss5_ep_sunrise) {
/* each pair of links on the same endpoint are bridged */
for (ss5_b = ss5_ep_sunset->link_list; ss5_b; ss5_b = ss5_b->next) {
ss5_a = ss5_b;
ss5_b = ss5_b->next;
if (!ss5_b)
break;
process_audio(ss5_a, ss5_b, len);
}
} else {
/* each link on two endpoints are bridged */
for (ss5_a = ss5_ep_sunset->link_list, ss5_b = ss5_ep_sunrise->link_list; ss5_a && ss5_b; ss5_a = ss5_a->next, ss5_b = ss5_b->next) {
process_audio(ss5_a, ss5_b, len);
}
}
}
/* take audio from CC and store in jitter buffer */
void down_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len)
{
ss5_t *ss5 = codec->media->session->priv;
sample_t samples[len / 2];
int16_to_samples(samples, (int16_t *)data, len / 2);
jitter_save(&ss5->dejitter, samples, len / 2);
}
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
{
uint16_t *src = (uint16_t *)src_data, *dst;
int len = src_len / 2, i;
dst = malloc(len * 2);
if (!dst)
return;
for (i = 0; i < len; i++)
dst[i] = htons(src[i]);
*dst_data = (uint8_t *)dst;
*dst_len = len * 2;
}
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)
{
uint16_t *src = (uint16_t *)src_data, *dst;
int len = src_len / 2, i;
dst = malloc(len * 2);
if (!dst)
return;
for (i = 0; i < len; i++)
dst[i] = ntohs(src[i]);
*dst_data = (uint8_t *)dst;
*dst_len = len * 2;
}

@ -0,0 +1,46 @@
struct ss5_endpoint;
typedef struct dsp {
void *priv;
double samplerate;
/* tone generation */
mf_mod_t *mf_mod; /* MF modulator */
char tone; /* current digit playing or 0 */
uint32_t tone_mask; /* bit-mask of which MF tones to play */
int tone_duration; /* counter of samples for length of tone */
char dial_string[33]; /* stores digits when dialing number sequence */
int dial_length; /* length of dial string, or 0 */
int dial_index; /* current position at dial string */
int digit_pause; /* flag that states if pause is played after digit */
/* tone detection */
mf_demod_t *mf_demod; /* MF demodulator */
double ms_per_sample; /* how many milliseconds between two samples */
double detect_interval; /* counts milliseconds */
int interrupt_duration; /* number of milliseconds until interruption is detected */
int interrupt_count; /* counter for interrupt condition */
int split_duration; /* number of milliseconds until split (mute) condition meats */
int split_count; /* counter for split condition */
int sig_detect_duration_AB; /* signaling tone duration in milliseconds (TONE A/B) */
int sig_detect_duration_C; /* signaling tone duration in milliseconds (TONE C) */
int mf_detect_duration; /* MF tone duration in milliseconds */
int detect_count; /* counter for tone detection */
char detect_tone; /* current tone detected or ' ' for no tone */
} dsp_t;
int dsp_init_inst(dsp_t *dsp, void *priv, double samplerate, double sense_db);
void dsp_cleanup_inst(dsp_t *dsp);
void set_sig_detect_duration(dsp_t *dsp, double duration_AB, double duration_C);
void set_tone(dsp_t *dsp, char tone, double duration);
void set_dial_string(dsp_t *dsp, const char *dial);
void audio_clock(struct ss5_endpoint *ss5_ep_sunset, struct ss5_endpoint *ss5_ep_sunrise, int len);
void down_audio(struct osmo_cc_session_codec *codec, uint16_t __attribute__((unused)) sequence_number, uint32_t __attribute__((unused)) timestamp, uint8_t *data, int len);
void encode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
void decode_l16(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len);
void receive_digit(void *priv, char digit, double dbm);
void dialing_complete(void *priv);

@ -0,0 +1,346 @@
/* osmo-cc-ss5-endpoint main
*
* (C) 2020 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <termios.h>
#include "../libdebug/debug.h"
#include "../liboptions/options.h"
#include "../libg711/g711.h"
#include "ss5.h"
#include "display.h"
ss5_endpoint_t *ss5_ep_sunset = NULL, *ss5_ep_sunrise = NULL;
int num_kanal = 2;
int endpoints = 1;
int links = 0;
int prevent_blueboxing = 0;
int suppress_disconnect = 1;
int crosstalk = 1;
int delay_ms = 300;
int comfort_noise = 1;
double sense_db = 5;
#define MAX_CC_ARGS 1024
static int cc_argc_sunset, cc_argc_sunrise = 0;
static const char *cc_argv_sunset[MAX_CC_ARGS], *cc_argv_sunrise[MAX_CC_ARGS];
static void print_usage(const char *app)
{
printf("Usage: %s [--port <misdn port>] [--nt] [<options>]\n", app);
printf("This will create pairs of SS5 channels that are bridged together, so that\n");
printf("calls from one link to the other can be made using SS5. The a bluebox can be\n");
printf("used to play with it.\n");
printf("If one endpoint is used (default), its name is 'sunset' and each pair of\n");
printf("channels are bridged together. If two endpoints are used, their names are\n");
printf("'sunset' and 'sunrise' and same channel index of both endpoints are bridged\n");
printf("together.\n");
}
static void print_help()
{
/* - - */
printf(" -h --help\n");
printf(" This help\n");
printf(" -v --verbose <level> | <level>,<category>[,<category>[,...]] | list\n");
printf(" Use 'list' to get a list of all levels and categories\n");
printf(" Verbose level: digit of debug level (default = '%d')\n", debuglevel);
printf(" Verbose level+category: level digit followed by one or more categories\n");
printf(" -> If no category is specified, all categories are selected\n");
printf(" -2 --two\n");
printf(" Create two Osmo-CC endpoints instead of one.\n");
printf(" -c --channels\n");
printf(" Give number of channels per endpoint. If you use a single endpoint,\n");
printf(" you must define an even number. By default this is '2' for one\n");
printf(" endpoint and '1' for two endpoints.\n");
printf(" -s --suppress-disconnect 1 | 0\n");
printf(" When a 'busy-flash' or 'release-guard' is received a disconnect is\n");
printf(" forwarded towards OsmoCC. Set to 1 to suppress this. (Default is %d.)\n", suppress_disconnect);
printf(" -p --prevent-blueboxing 1 | 0\n");
printf(" Prevent blueboxing by making 'release-guard' 200 ms minimum length.\n");
printf(" -x --crosstalk 1 | 0\n");
printf(" Enable or disable some minor crosstalk. This allows you to hear\n");
printf(" transmitted tones at a low volume. (Default is %d.).\n", crosstalk);
printf(" -d --delay <ms> | 0\n");
printf(" Add one-way delay to the connection between two SS5 links. This allows\n");
printf(" to hear 'acknowlege' tones delayed. (Default is %d ms.).\n", delay_ms);
printf(" -n --comfort-noise 1 | 0\n");
printf(" Add comfort noise whenever there is no audio from the remote link\n");
printf(" (before or after call). (Default is %d ms.).\n", comfort_noise);
printf(" --sense 0 | <db>\n");
printf(" Change sensitivity (level) of tone detector. A bluebox must not be\n");
printf(" that loud. (Default is %.0f dB.).\n", sense_db);
printf(" -C --cc \"<osmo-cc arg>\" [--cc ...]\n");
printf(" --cc2 \"<osmo-cc arg>\" [--cc2 ...]\n");
printf(" Pass arguments to Osmo-CC endpoint. Use '-cc help' for description.\n");
printf(" If you select two endpoints, use '--cc2' to pass arguments to the\n");
printf(" second endpoint.\n");
}
#define OPT_SENSE 256
#define OPT_CC2 257
static void add_options(void)
{
option_add('h', "help", 0);
option_add('v', "verbose", 1);
option_add('2', "two", 0);
option_add('c', "channels", 1);
option_add('s', "suppress-disconnect", 1);
option_add('p', "prevent-blueboxing", 1);
option_add('x', "crosstalk", 1);
option_add('d', "delay", 1);
option_add('n', "comfort-noise", 1);
option_add(OPT_SENSE, "sense", 1);
option_add('C', "cc", 1);
option_add(OPT_CC2, "cc2", 1);
}
static int handle_options(int short_option, int argi, char **argv)
{
int rc;
switch (short_option) {
case 'h':
print_usage(argv[0]);
print_help();
return 0;
case 'v':
if (!strcasecmp(argv[argi], "list")) {
debug_list_cat();
return 0;
}
rc = parse_debug_opt(argv[argi]);
if (rc < 0) {
fprintf(stderr, "Failed to parse debug option, please use -h for help.\n");
return rc;
}
break;
case '2':
endpoints = 2;
break;
case 'c':
links = atoi(argv[argi]);
break;
case 's':
suppress_disconnect = atoi(argv[argi]);
break;
case 'p':
prevent_blueboxing = atoi(argv[argi]);
break;
case 'x':
crosstalk = atoi(argv[argi]);
break;
case 'd':
delay_ms = atoi(argv[argi]);
break;
case 'n':
comfort_noise = atoi(argv[argi]);
break;
case OPT_SENSE:
sense_db = (double)atoi(argv[argi]);
break;
case 'C':
if (!strcasecmp(argv[argi], "help")) {
osmo_cc_help();
return 0;
}
if (cc_argc_sunset == MAX_CC_ARGS) {
fprintf(stderr, "Too many osmo-cc args!\n");
break;
}
cc_argv_sunset[cc_argc_sunset++] = strdup(argv[argi]);
break;
case OPT_CC2:
if (!strcasecmp(argv[argi], "help")) {
osmo_cc_help();
return 0;
}
if (cc_argc_sunrise == MAX_CC_ARGS) {
fprintf(stderr, "Too many osmo-cc args!\n");
break;
}
cc_argv_sunrise[cc_argc_sunrise++] = strdup(argv[argi]);
break;
default:
return -EINVAL;
}
return 1;
}
static int quit = 0;
void sighandler(int sigset)
{
if (sigset == SIGHUP || sigset == SIGPIPE)
return;
fprintf(stderr, "\nSignal %d received.\n", sigset);
quit = 1;
}
static int get_char()
{
struct timeval tv = {0, 0};
fd_set fds;
char c = 0;
int __attribute__((__unused__)) rc;
FD_ZERO(&fds);
FD_SET(0, &fds);
select(0+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(0, &fds)) {
rc = read(0, &c, 1);
return c;
} else
return -1;
}
int main(int argc, char *argv[])
{
int argi, rc;
struct termios term, term_orig;
double now, last_time_call = 0.0;
int c;
/* init MF */
mf_init(0);
/* init codecs (for recording) */
g711_init();
cc_argv_sunset[cc_argc_sunset++] = strdup("remote auto");
cc_argv_sunrise[cc_argc_sunrise++] = strdup("remote auto");
/* handle options / config file */
add_options();
rc = options_config_file("~/.osmocom/ss5/ss5.conf", handle_options);
if (rc < 0)
return 0;
argi = options_command_line(argc, argv, handle_options);
if (argi <= 0)
return argi;
/* check links (per endpoint) */
if (!links)
links = (endpoints == 2) ? 1 : 2;
if (links == 1 && (endpoints % 1)) {
PDEBUG(DSS5, DEBUG_ERROR, "You must define an even number of channels on a single endpoint!\n");
goto error;
}
/* create sunset and (optionally) sunrise */
ss5_ep_sunset = ss5_ep_create("sunset", links, prevent_blueboxing, crosstalk, delay_ms, comfort_noise, suppress_disconnect, sense_db);
if (!ss5_ep_sunset)
goto error;
rc = osmo_cc_new(&ss5_ep_sunset->cc_ep, OSMO_CC_VERSION, "sunset", OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_message, NULL, ss5_ep_sunset, cc_argc_sunset, cc_argv_sunset);
if (rc < 0)
goto error;
if (endpoints == 2) {
ss5_ep_sunrise = ss5_ep_create("sunrise", links, prevent_blueboxing, crosstalk, delay_ms, comfort_noise, suppress_disconnect, sense_db);
if (!ss5_ep_sunrise)
goto error;
rc = osmo_cc_new(&ss5_ep_sunrise->cc_ep, OSMO_CC_VERSION, "sunrise", OSMO_CC_LOCATION_BEYOND_INTERWORKING, cc_message, NULL, ss5_ep_sunrise, cc_argc_sunrise, cc_argv_sunrise);
if (rc < 0)
goto error;
PDEBUG(DSS5, DEBUG_NOTICE, "Created endpoints 'sunset' and 'sunrise' with %d links that connect these endpoints.\n", links);
} else
PDEBUG(DSS5, DEBUG_NOTICE, "Created endpoint 'sunset' with %d links, each pair connected.\n", links);
refresh_status();
/* prepare terminal */
tcgetattr(0, &term_orig);
term = term_orig;
term.c_lflag &= ~(ISIG|ICANON|ECHO);
term.c_cc[VMIN]=1;
term.c_cc[VTIME]=2;
tcsetattr(0, TCSANOW, &term);
/* catch signals */
signal(SIGINT, sighandler);
signal(SIGHUP, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);