Initial GIT import

This commit is contained in:
Andreas Eversberg 2020-11-14 15:45:09 +01:00
commit c354814c97
15 changed files with 2899 additions and 0 deletions

44
.gitignore vendored Normal file
View File

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

5
Makefile.am Normal file
View File

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

92
configure.ac Normal file
View File

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

151
git-version-gen Executable file
View File

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

13
src/Makefile.am Normal file
View File

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

23
src/ss5/Makefile.am Normal file
View File

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

9
src/ss5/display.h Normal file
View File

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

169
src/ss5/display_status.c Normal file
View File

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

552
src/ss5/dsp.c Normal file
View File

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

46
src/ss5/dsp.h Normal file
View File

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

346
src/ss5/main.c Normal file
View File

@ -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);
while (!quit) {
int w;
/* send clock calls to play/record audio files */
now = get_time();
if (now - last_time_call >= 0.1)
last_time_call = now;
if (now - last_time_call >= 0.020) {
last_time_call += 0.020;
/* call clock every 20ms */
audio_clock(ss5_ep_sunset, ss5_ep_sunrise, 160);
}
process_timer();
do {
w = 0;
w |= osmo_cc_handle();
} while (w);
usleep(1000);
/* process keyboard input */
next_char:
c = get_char();
switch (c) {
case 3:
printf("CTRL+c received, quitting!\n");
quit = 1;
goto next_char;
case 'c':
display_status_on(-1);
goto next_char;
}
}
/* reset signals */
signal(SIGINT, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
/* reset terminal */
tcsetattr(0, TCSANOW, &term_orig);
error:
/* destroy endpoints */
if (ss5_ep_sunset) {
osmo_cc_delete(&ss5_ep_sunset->cc_ep);
ss5_ep_destroy(ss5_ep_sunset);
}
if (ss5_ep_sunrise) {
osmo_cc_delete(&ss5_ep_sunrise->cc_ep);
ss5_ep_destroy(ss5_ep_sunrise);
}
/* exit MF */
mf_exit();
return 0;
}

202
src/ss5/mf.c Normal file
View File

@ -0,0 +1,202 @@
/* multi frequency coder and decoder
*
* (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 <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <math.h>
#include "../libsample/sample.h"
#include "mf.h"
static int has_init = 0;
static int fast_math = 0;
static float *sin_tab = NULL, *cos_tab = NULL;
/* global init */
int mf_init(int _fast_math)
{
fast_math = _fast_math;
if (fast_math) {
int i;
sin_tab = calloc(65536+16384, sizeof(*sin_tab));
if (!sin_tab) {
fprintf(stderr, "No mem!\n");
return -ENOMEM;
}
cos_tab = sin_tab + 16384;
/* generate sine and cosine */
for (i = 0; i < 65536+16384; i++)
sin_tab[i] = sin(2.0 * M_PI * (double)i / 65536.0);
}
has_init = 1;
return 0;
}
/* global exit */
void mf_exit(void)
{
if (sin_tab) {
free(sin_tab);
sin_tab = cos_tab = NULL;
}
has_init = 0;
}
mf_mod_t *mf_mod_init(double samplerate, int tones, double *freq, double *amplitude)
{
mf_mod_t *mod;
int t;
if (!has_init) {
fprintf(stderr, "libmf was not initialized, plese fix!\n");
abort();
}
mod = calloc(1, sizeof(*mod) + sizeof(*mod->tone) * tones);
if (!mod) {
fprintf(stderr, "No mem!\n");
return NULL;
}
mod->tones = tones;
for (t = 0; t < tones; t++) {
/* set rotation of IQ vector */
if (fast_math)
mod->tone[t].rot = 65536.0 * freq[t] / samplerate;
else
mod->tone[t].rot = 2 * M_PI * freq[t] / samplerate;
mod->tone[t].amplitude = amplitude[t];
}
return mod;
}
void mf_mod_exit(mf_mod_t *mod)
{
free(mod);
}
void mf_mod(mf_mod_t *mod, uint32_t *mask, sample_t *samples, int length)
{
int t, s;
double phase;
for (s = 0; s < length; s++) {
samples[s] = 0;
for (t = 0; t < mod->tones; t++) {
/* continue phase even on muted tones */
phase = (mod->tone[t].phase += mod->tone[t].rot);
if (!((1 << t) & mask[s]))
continue;
if (fast_math) {
if (phase >= 65536.0)
phase -= 65536.0;
samples[s] += sin_tab[(uint16_t)phase] * mod->tone[t].amplitude;
} else {
if (phase >= 2.0 * M_PI)
phase -= 2.0 * M_PI;
samples[s] += cos(phase) * mod->tone[t].amplitude;
}
}
}
}
mf_demod_t *mf_demod_init(double samplerate, int tones, double *freq, double *width)
{
mf_demod_t *demod;
int t;
if (!has_init) {
fprintf(stderr, "libmf was not initialized, plese fix!\n");
abort();
}
demod = calloc(1, sizeof(*demod) + sizeof(*demod->tone) * tones);
if (!demod) {
fprintf(stderr, "No mem!\n");
return NULL;
}
demod->tones = tones;
for (t = 0; t < tones; t++) {
/* set rotation of IQ vector */
if (fast_math)
demod->tone[t].rot = 65536.0 * -freq[t] / samplerate;
else
demod->tone[t].rot = 2 * M_PI * -freq[t] / samplerate;
/* use fourth order (2 iter) filter, since it is as fast as second order (1 iter) filter */
iir_lowpass_init(&demod->tone[t].lp[0], width[t], samplerate, 2);
iir_lowpass_init(&demod->tone[t].lp[1], width[t], samplerate, 2);
}
return demod;
}
void mf_demod_exit(mf_demod_t *demod)
{
free(demod);
}
void mf_demod(mf_demod_t *demod, sample_t *samples, int length, sample_t **levels_squared)
{
sample_t I[length], Q[length];
double phase, rot;
int t, s;
double i, _sin, _cos;
sample_t *level_squared;
/* we apply filters and get the squared levels as a result */
for (t = 0; t < demod->tones; t++) {
phase = demod->tone[t].phase;
rot = demod->tone[t].rot;
for (s = 0; s < length; s++) {
phase += rot;
i = samples[s];
if (fast_math) {
if (phase >= 65536.0)
phase -= 65536.0;
_sin = sin_tab[(uint16_t)phase];
_cos = cos_tab[(uint16_t)phase];
} else {
if (phase >= 2.0 * M_PI)
phase -= 2.0 * M_PI;
_sin = sin(phase);
_cos = cos(phase);
}
I[s] = i * _cos;
Q[s] = i * _sin;
}
demod->tone[t].phase = phase;
iir_process(&demod->tone[t].lp[0], I, length); /* filter single side band */
iir_process(&demod->tone[t].lp[1], Q, length);
level_squared = levels_squared[t];
for (s = 0; s < length; s++)
level_squared[s] = 4.0 * (I[s] * I[s] + Q[s] * Q[s]); /* x2 because of single side band */
}
}

34
src/ss5/mf.h Normal file
View File

@ -0,0 +1,34 @@
#include "../libfilter/iir_filter.h"
struct mf_mod_tone {
double rot;
double phase;
double amplitude;
};
typedef struct mf_mod {
int tones;
struct mf_mod_tone tone[0];
} mf_mod_t;
struct mf_demod_tone {
double rot;
double phase;
iir_filter_t lp[2];
};
typedef struct mf_demod {
int tones;
struct mf_demod_tone tone[0];
} mf_demod_t;
int mf_init(int _fast_math);
void mf_exit(void);
mf_mod_t *mf_mod_init(double samplerate, int tones, double *freq, double *amplitude);
void mf_mod_exit(mf_mod_t *mod);
void mf_mod(mf_mod_t *mod, uint32_t *mask, sample_t *samples, int length);
mf_demod_t *mf_demod_init(double samplerate, int tones, double *freq, double *width);
void mf_demod_exit(mf_demod_t *demod);
void mf_demod(mf_demod_t *demod, sample_t *samples, int length, sample_t **levels_squared);

1128
src/ss5/ss5.c Normal file

File diff suppressed because it is too large Load Diff

85
src/ss5/ss5.h Normal file
View File

@ -0,0 +1,85 @@
#include "../libtimer/timer.h"
#include "../libosmocc/endpoint.h"
#include "../libosmocc/helper.h"
#include "../libsample/sample.h"
#include "../libjitter/jitter.h"
#include "mf.h"
#include "dsp.h"
enum ss5_state {
SS5_STATE_NULL = 0,
/* idle line */
SS5_STATE_IDLE,
/* outgoing call */
SS5_STATE_SEND_SEIZE, /* sending seize, waiting for proceed-to-send */
SS5_STATE_RECV_PROCEED, /* detected proceed-to-send, waiting to cease */
SS5_STATE_SEND_DIGITS, /* proceed-to-send is ceased, sending digits */
SS5_STATE_OUT_INACTIVE, /* waiting for called party to answer or being busy */
SS5_STATE_SEND_ACK_ANS, /* detected answer, sending acknowledge, waiting to cease */
SS5_STATE_SEND_ACK_BUS, /* detected busy-flash, sending acknowledge, waiting to cease */
SS5_STATE_SEND_ACK_CLR, /* detected clear-back, sending acknowledge, waiting to cease */
SS5_STATE_OUT_ACTIVE, /* detected answer is ceased */
SS5_STATE_SEND_CLR_FWD, /* sending clear-forward, waiting for release-guard */
SS5_STATE_RECV_RELEASE, /* receiving release-guard, waiting to cease */
SS5_STATE_SEND_FORWARD, /* sending forward-transfer for a while */
/* incoming call */
SS5_STATE_SEND_PROCEED, /* detected seize, sending procced-to-send, waiting to cease */
SS5_STATE_RECV_DIGIT, /* seize is ceased, waiting for digits */
SS5_STATE_RECV_SPACE, /* digit received, waiting to cease */
SS5_STATE_IN_INACTIVE, /* waiting for called party to answer or disconnect */
SS5_STATE_SEND_ANSWER, /* sending answer, waiting for for acknowledge */
SS5_STATE_IN_ACTIVE, /* detected acknowledge of answer */
SS5_STATE_SEND_BUSY, /* sending busy-flash, waiting for acknowledge */
SS5_STATE_SEND_CLR_BAK, /* sending clear back (hangup) */
SS5_STATE_SEND_RELEASE, /* detected clear-forward, sending release-guard, waiting for cease */
SS5_STATE_SEND_REL_WAIT,/* clear forward ceased, but waiting to prevent blueboxing */
/* seize collision */
SS5_STATE_DOUBLE_SEIZE, /* seize is detected, sending for while, waiting for cease */
};
extern const char *ss5_state_names[];
struct ss5_endpoint;
/* SS5 link definition */
typedef struct ss5 {
struct ss5 *next;
struct ss5_endpoint *ss5_ep;
char name[32]; /* name of link for debugging */
/* call states */
enum ss5_state state; /* state of link */
struct timer timer; /* for several timeouts */
uint32_t cc_callref; /* ref to CC call */
osmo_cc_session_t *cc_session; /* audio session description */
osmo_cc_session_codec_t *codec; /* selected codec */
char callerid[65]; /* current caller id (outgoing only) */
char dialing[33]; /* current dial string (send or receive) */
/* audio processing */
jitter_t dejitter; /* jitter buffer for audio from CC */
dsp_t dsp; /* all dsp processing */
sample_t *delay_buffer; /* buffer for delaying audio */
int delay_length;
int delay_index;
} ss5_t;
/* SS5 endpoint definition */
typedef struct ss5_endpoint {
osmo_cc_endpoint_t cc_ep;
ss5_t *link_list;
double samplerate;
int suppress_disconnect; /* do not forward disconnect towards CC */
int prevent_blueboxing; /* extend release-guard, so outgoing exchange releases */
int crosstalk; /* mix crosstalk from TX to RX */
int comfort_noise; /* add comfort noise before answer and after disconnect */
int delay_ms; /* add delay to simulate long distance lines */
double sense_db; /* increase sensitivity of decoder by this value */
} ss5_endpoint_t;
void refresh_status(void);
ss5_endpoint_t *ss5_ep_create(const char *ep_name, int links, int prevent_blueboxing, int crosstalk, int delay_ms, int comfort_noise, int suppress_disconnect, double sense_db);
void ss5_ep_destroy(ss5_endpoint_t *ss5_ep);
void cc_message(osmo_cc_endpoint_t *ep, uint32_t callref, osmo_cc_msg_t *msg);