osmo-cc-ss5-endpoint/src/ss5/main.c

382 lines
11 KiB
C

/* 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 "../common/common.h"
#include "../common/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];
int no_l16 = 0;
static void print_usage(const char *app)
{
printf("Usage: %s [<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(" --config [~/]<path to config file>\n");
printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n");
printf(" Each line in config file is one option, '-' or '--' must not be given!\n");
debug_print_help();
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(" Increase sensitivity of tone detector. A bluebox can have lower level\n");
printf(" than what the standard requires. (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");
printf(" --no-l16\n");
printf(" Disable L16 (linear 16 bit) codec.\n");
print_help_common();
}
#define OPT_SENSE 256
#define OPT_CC2 257
#define OPT_NO_L16 258
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);
option_add(OPT_NO_L16, "no-l16", 0);
}
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++] = options_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++] = options_strdup(argv[argi]);
break;
case OPT_NO_L16:
no_l16 = 1;
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;
}
struct timer clock_timer;
double last_time_clock = 0;
static void clock_timeout(void __attribute__((unused)) *data)
{
double now;
int c;
/* add timer to wait for next 20ms */
now = get_time();
if (now - last_time_clock >= 0.1)
last_time_clock = now;
last_time_clock += 0.020;
if (last_time_clock < now)
last_time_clock = now;
timer_start(&clock_timer, last_time_clock - now);
/* call audio clock every 20ms */
audio_clock((ss5_ep_sunset) ? ss5_ep_sunset->dsp_list : NULL, (ss5_ep_sunrise) ? ss5_ep_sunrise->dsp_list : NULL, 160);
/* process keyboard input */
c = get_char();
switch (c) {
case 3:
printf("CTRL+c received, quitting!\n");
quit = 1;
break;
case 'c':
display_status_on(-1);
}
}
int main(int argc, char *argv[])
{
int argi, rc;
struct termios term, term_orig;
/* init MF */
mf_init(0);
/* init codecs */
g711_init();
/* init dsp */
dsp_set_sf(-9.0, -16.0);
cc_argv_sunset[cc_argc_sunset++] = options_strdup("remote auto");
cc_argv_sunrise[cc_argc_sunrise++] = options_strdup("remote auto");
/* handle options / config file */
add_options();
rc = options_config_file(argc, argv, "~/.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, suppress_disconnect, crosstalk, comfort_noise, delay_ms, sense_db, no_l16);
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, suppress_disconnect, crosstalk, comfort_noise, delay_ms, sense_db, no_l16);
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();
/* init clock timer for clocking call */
timer_init(&clock_timer, clock_timeout, NULL);
timer_start(&clock_timer, 0.020);
/* 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 work;
double timeout;
do {
work = 0;
work |= osmo_cc_handle();
} while (work);
/* handle all timers
* timeout is 0, if there was an event
* -> handle FDs without waiting, continue this loop
* timeout is not 0, if there was no event
* -> wait until FD or timeout
*/
timeout = process_timer();
/* wait for FD event until given timeout */
osmo_fd_select(timeout);
}
/* 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:
timer_exit(&clock_timer);
/* 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();
options_free();
return 0;
}