Initial GIT import
This commit is contained in:
commit
c354814c97
|
@ -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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue