initial git import
This commit is contained in:
3
src/Makefile.am
Normal file
3
src/Makefile.am
Normal file
@@ -0,0 +1,3 @@
|
||||
AUTOMAKE_OPTIONS = foreign
|
||||
SUBDIRS = common anetz bnetz
|
||||
|
16
src/anetz/Makefile.am
Normal file
16
src/anetz/Makefile.am
Normal file
@@ -0,0 +1,16 @@
|
||||
AM_CPPFLAGS = -Wall -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
anetz
|
||||
|
||||
anetz_SOURCES = \
|
||||
anetz.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
main.c
|
||||
anetz_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
$(ALSA_LIBS) \
|
||||
$(top_builddir)/src/common/libcommon.a \
|
||||
-lm
|
||||
|
458
src/anetz/anetz.c
Normal file
458
src/anetz/anetz.c
Normal file
@@ -0,0 +1,458 @@
|
||||
/* A-Netz protocol handling
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/cause.h"
|
||||
#include "anetz.h"
|
||||
#include "dsp.h"
|
||||
|
||||
/* Call reference for calls from mobile station to network
|
||||
This offset of 0x400000000 is required for MNCC interface. */
|
||||
static int new_callref = 0x40000000;
|
||||
|
||||
/* Timers */
|
||||
#define PAGING_TO 30 /* Nach dieser Zeit ist der Operator genervt... */
|
||||
|
||||
/* Convert channel number to frequency number of base station.
|
||||
Set 'unterband' to 1 to get frequency of mobile station. */
|
||||
double anetz_kanal2freq(int kanal, int unterband)
|
||||
{
|
||||
double freq = 162.050;
|
||||
|
||||
freq += (kanal - 30) * 0.050;
|
||||
if (kanal >= 45)
|
||||
freq += 6.800;
|
||||
if (unterband)
|
||||
freq -= 4.500;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
/* Convert paging frequency number to to frequency. */
|
||||
static double anetz_dauerruf_frq(int n)
|
||||
{
|
||||
if (n < 1 || n > 30)
|
||||
abort();
|
||||
|
||||
return 337.5 + (double)n * 15.0;
|
||||
}
|
||||
|
||||
/* Table with frequency sets to use for paging. */
|
||||
static struct anetz_dekaden {
|
||||
int dekade[4];
|
||||
} anetz_gruppenkennziffer[10] = {
|
||||
{ { 2, 2, 3, 3 } }, /* 0 */
|
||||
{ { 1, 1, 2, 2 } }, /* 1 */
|
||||
{ { 1, 1, 3, 3 } }, /* 2 */
|
||||
{ { 1, 1, 2, 3 } }, /* 3 */
|
||||
{ { 1, 2, 2, 3 } }, /* 4 */
|
||||
{ { 1, 2, 3, 3 } }, /* 5 */
|
||||
{ { 1, 1, 1, 2 } }, /* 6 */
|
||||
{ { 1, 1, 1, 3 } }, /* 7 */
|
||||
{ { 2, 2, 2, 3 } }, /* 8 */
|
||||
{ { 1, 2, 2, 2 } }, /* 9 */
|
||||
};
|
||||
|
||||
/* Takes the last 5 digits of a number and returns 4 paging tones.
|
||||
If number is invalid, NULL is returned. */
|
||||
static double *anetz_nummer2freq(const char *nummer)
|
||||
{
|
||||
int f[4];
|
||||
static double freq[4];
|
||||
int *dekade;
|
||||
int i, j, digit;
|
||||
|
||||
/* get last 5 digits */
|
||||
if (strlen(nummer) < 5) {
|
||||
PDEBUG(DANETZ, DEBUG_ERROR, "Number must have at least 5 digits!\n");
|
||||
return NULL;
|
||||
}
|
||||
nummer = nummer + strlen(nummer) - 5;
|
||||
|
||||
/* check for digits */
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (nummer[i] < '0' || nummer[i] > '9') {
|
||||
PDEBUG(DANETZ, DEBUG_ERROR, "Number must have digits 0..9!\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* get decade */
|
||||
dekade = anetz_gruppenkennziffer[*nummer - '0'].dekade;
|
||||
PDEBUG(DANETZ, DEBUG_DEBUG, "Dekaden: %d %d %d %d\n", dekade[0], dekade[1], dekade[2], dekade[3]);
|
||||
nummer++;
|
||||
|
||||
/* get 4 frequencies out of decades */
|
||||
for (i = 0; i < 4; i++) {
|
||||
digit = nummer[i] - '0';
|
||||
if (digit == 0)
|
||||
digit = 10;
|
||||
f[i] = (dekade[i] - 1) * 10 + digit;
|
||||
freq[i] = anetz_dauerruf_frq(f[i]);
|
||||
for (j = 0; j < i; j++) {
|
||||
if (dekade[i] == dekade[j] && nummer[i] == nummer[j]) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Number invalid, digit #%d and #%d of '%s' use same frequency F%d=%.1f of same decade %d!\n", i+1, j+1, nummer, f[i], freq[i], dekade[i]);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
PDEBUG(DANETZ, DEBUG_DEBUG, "Frequencies: F%d=%.1f F%d=%.1f F%d=%.1f F%d=%.1f\n", f[0], freq[0], f[1], freq[1], f[2], freq[2], f[3], freq[3]);
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
/* global init */
|
||||
int anetz_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void anetz_timeout(struct timer *timer);
|
||||
|
||||
/* Create transceiver instance and link to a list. */
|
||||
int anetz_create(const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume)
|
||||
{
|
||||
anetz_t *anetz;
|
||||
int rc;
|
||||
|
||||
if (kanal < 30 || kanal > 63) {
|
||||
PDEBUG(DANETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
anetz = calloc(1, sizeof(anetz_t));
|
||||
if (!anetz) {
|
||||
PDEBUG(DANETZ, DEBUG_ERROR, "No memory!\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_DEBUG, "Creating 'A-Netz' instance for 'Kanal' = %d (sample rate %d).\n", kanal, samplerate);
|
||||
|
||||
/* init general part of transceiver */
|
||||
rc = sender_create(&anetz->sender, sounddev, samplerate, kanal, loopback, loss_volume, -1);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init 'Sender' processing!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* init audio processing */
|
||||
rc = dsp_init_sender(anetz);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DANETZ, DEBUG_ERROR, "Failed to init signal processing!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* go into idle state */
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Entering IDLE state, sending 2280 Hz tone.\n");
|
||||
anetz->state = ANETZ_FREI;
|
||||
anetz->dsp_mode = DSP_MODE_TONE;
|
||||
timer_init(&anetz->timer, anetz_timeout, anetz);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
anetz_destroy(&anetz->sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void anetz_destroy(sender_t *sender)
|
||||
{
|
||||
anetz_t *anetz = (anetz_t *) sender;
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_DEBUG, "Destroying 'A-Netz' instance for 'Kanal' = %d.\n", sender->kanal);
|
||||
|
||||
timer_exit(&anetz->timer);
|
||||
dsp_cleanup_sender(anetz);
|
||||
sender_destroy(&anetz->sender);
|
||||
free(sender);
|
||||
}
|
||||
|
||||
/* Abort connection towards mobile station by sending idle tone. */
|
||||
static void anetz_go_idle(anetz_t *anetz)
|
||||
{
|
||||
timer_stop(&anetz->timer);
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Entering IDLE state, sending 2280 Hz tone.\n");
|
||||
anetz->state = ANETZ_FREI;
|
||||
anetz->dsp_mode = DSP_MODE_TONE;
|
||||
anetz->station_id[0] = '\0';
|
||||
}
|
||||
|
||||
/* Enter paging state and transmit 4 paging tones. */
|
||||
static void anetz_page(anetz_t *anetz, const char *dial_string, double *freq)
|
||||
{
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Entering paging state, sending 'Selektivruf' to '%s'.\n", dial_string);
|
||||
anetz->state = ANETZ_ANRUF;
|
||||
anetz->dsp_mode = DSP_MODE_PAGING;
|
||||
dsp_set_paging(anetz, freq);
|
||||
strcpy(anetz->station_id, dial_string);
|
||||
timer_start(&anetz->timer, PAGING_TO);
|
||||
}
|
||||
|
||||
/* Loss of signal was detected, release active call. */
|
||||
void anetz_loss_indication(anetz_t *anetz)
|
||||
{
|
||||
if (anetz->state == ANETZ_GESPRAECH) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Detected loss of signal, releasing.\n");
|
||||
anetz_go_idle(anetz);
|
||||
call_in_release(anetz->sender.callref, CAUSE_TEMPFAIL);
|
||||
anetz->sender.callref = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* A continuous tone was detected or is gone. */
|
||||
void anetz_receive_tone(anetz_t *anetz, int tone)
|
||||
{
|
||||
if (tone >= 0)
|
||||
PDEBUG(DANETZ, DEBUG_DEBUG, "Received contiuous %d Hz tone.\n", (tone) ? 1750 : 2280);
|
||||
else
|
||||
PDEBUG(DANETZ, DEBUG_DEBUG, "Continuous tone is gone.\n");
|
||||
|
||||
if (anetz->sender.loopback) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (anetz->state) {
|
||||
case ANETZ_FREI:
|
||||
/* initiate call on calling tone */
|
||||
if (tone == 1) {
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz calling signal from mobile station, removing idle signal.\n");
|
||||
anetz->state = ANETZ_GESPRAECH;
|
||||
anetz->dsp_mode = DSP_MODE_SILENCE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ANETZ_GESPRAECH:
|
||||
/* throughconnect speech when calling/answer tone is gone */
|
||||
if (tone != 1) {
|
||||
if (!anetz->sender.callref) {
|
||||
int callref = ++new_callref;
|
||||
int rc;
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "1750 Hz signal from mobile station is gone, setup call.\n");
|
||||
rc = call_in_setup(callref, anetz->station_id, "0");
|
||||
if (rc < 0) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", -rc);
|
||||
anetz_go_idle(anetz);
|
||||
break;
|
||||
}
|
||||
anetz->sender.callref = callref;
|
||||
} else {
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "1750 Hz signal from mobile station is gone, answer call.\n");
|
||||
call_in_answer(anetz->sender.callref, anetz->station_id);
|
||||
}
|
||||
anetz->dsp_mode = DSP_MODE_AUDIO;
|
||||
}
|
||||
/* release call */
|
||||
if (tone == 1) {
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz release signal from mobile station, sending idle signal.\n");
|
||||
anetz_go_idle(anetz);
|
||||
call_in_release(anetz->sender.callref, CAUSE_NORMAL);
|
||||
anetz->sender.callref = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ANETZ_ANRUF:
|
||||
/* answer call on answer tone */
|
||||
if (tone == 1) {
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Received 1750 Hz answer signal from mobile station, removing paging tones.\n");
|
||||
timer_stop(&anetz->timer);
|
||||
anetz->state = ANETZ_GESPRAECH;
|
||||
anetz->dsp_mode = DSP_MODE_SILENCE;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Timeout handling */
|
||||
static void anetz_timeout(struct timer *timer)
|
||||
{
|
||||
anetz_t *anetz = (anetz_t *)timer->priv;
|
||||
|
||||
switch (anetz->state) {
|
||||
case ANETZ_ANRUF:
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Timeout while waiting for answer, releasing.\n");
|
||||
anetz_go_idle(anetz);
|
||||
call_in_release(anetz->sender.callref, CAUSE_NOANSWER);
|
||||
anetz->sender.callref = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call control starts call towards mobile station. */
|
||||
int call_out_setup(int callref, char *dialing)
|
||||
{
|
||||
sender_t *sender;
|
||||
anetz_t *anetz;
|
||||
double *freq;
|
||||
|
||||
/* 1. check if number is invalid, return INVALNUMBER */
|
||||
if (strlen(dialing) > 7) {
|
||||
inval:
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
|
||||
return -CAUSE_INVALNUMBER;
|
||||
}
|
||||
freq = anetz_nummer2freq(dialing);
|
||||
if (!freq)
|
||||
goto inval;
|
||||
|
||||
/* 2. check if given number is already in a call, return BUSY */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
anetz = (anetz_t *) sender;
|
||||
if (strlen(anetz->station_id) < 5)
|
||||
continue;
|
||||
if (!strcmp(anetz->station_id + strlen(anetz->station_id) - 5, dialing + strlen(dialing) - 5))
|
||||
break;
|
||||
}
|
||||
if (sender) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
|
||||
return -CAUSE_BUSY;
|
||||
}
|
||||
|
||||
/* 3. check if all senders are busy, return NOCHANNEL */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
anetz = (anetz_t *) sender;
|
||||
if (anetz->state == ANETZ_FREI)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n");
|
||||
return -CAUSE_NOCHANNEL;
|
||||
}
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Call to mobile station, paging with tones: %.1f %.1f %.1f %.1f\n", freq[0], freq[1], freq[2], freq[3]);
|
||||
|
||||
/* 4. trying to page mobile station */
|
||||
sender->callref = callref;
|
||||
anetz_page(anetz, dialing, freq);
|
||||
|
||||
call_in_alerting(callref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Call control sends disconnect (with tones).
|
||||
* An active call stays active, so tones and annoucements can be received
|
||||
* by mobile station.
|
||||
*/
|
||||
void call_out_disconnect(int callref, int cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
anetz_t *anetz;
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Call has been disconnected by network.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
anetz = (anetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
|
||||
call_in_release(callref, CAUSE_INVALCALLREF);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Release when not active */
|
||||
if (anetz->state == ANETZ_GESPRAECH)
|
||||
return;
|
||||
switch (anetz->state) {
|
||||
case ANETZ_ANRUF:
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing disconnect, during alerting, going idle!\n");
|
||||
anetz_go_idle(anetz);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
call_in_release(callref, cause);
|
||||
|
||||
sender->callref = 0;
|
||||
|
||||
}
|
||||
|
||||
/* Call control releases call toward mobile station. */
|
||||
void call_out_release(int callref, int cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
anetz_t *anetz;
|
||||
|
||||
PDEBUG(DANETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
anetz = (anetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
|
||||
/* don't send release, because caller already released */
|
||||
return;
|
||||
}
|
||||
|
||||
sender->callref = 0;
|
||||
|
||||
switch (anetz->state) {
|
||||
case ANETZ_GESPRAECH:
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, during call, going idle!\n");
|
||||
anetz_go_idle(anetz);
|
||||
break;
|
||||
case ANETZ_ANRUF:
|
||||
PDEBUG(DANETZ, DEBUG_NOTICE, "Outgoing release, during alerting, going idle!\n");
|
||||
anetz_go_idle(anetz);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_rx_audio(int callref, int16_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
anetz_t *anetz;
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
anetz = (anetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender)
|
||||
return;
|
||||
|
||||
if (anetz->dsp_mode == DSP_MODE_AUDIO) {
|
||||
int16_t up[count * anetz->sender.srstate.factor];
|
||||
count = samplerate_upsample(&anetz->sender.srstate, samples, count, up);
|
||||
jitter_save(&anetz->sender.audio, up, count);
|
||||
}
|
||||
}
|
||||
|
47
src/anetz/anetz.h
Normal file
47
src/anetz/anetz.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "../common/sender.h"
|
||||
|
||||
enum dsp_mode {
|
||||
DSP_MODE_SILENCE, /* send silence to transmitter, block audio from receiver */
|
||||
DSP_MODE_AUDIO, /* stream audio */
|
||||
DSP_MODE_TONE, /* send 2280 Hz tone 0 */
|
||||
DSP_MODE_PAGING, /* send four paging tones */
|
||||
};
|
||||
|
||||
enum anetz_state {
|
||||
ANETZ_FREI, /* sending 2280 Hz tone */
|
||||
ANETZ_GESPRAECH, /* during conversation */
|
||||
ANETZ_ANRUF, /* phone is paged */
|
||||
};
|
||||
|
||||
typedef struct anetz {
|
||||
sender_t sender;
|
||||
|
||||
/* sender's states */
|
||||
enum anetz_state state; /* current sender's state */
|
||||
char station_id[8]; /* current station ID */
|
||||
struct timer timer;
|
||||
|
||||
/* dsp states */
|
||||
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, paging */
|
||||
int fsk_tone_coeff; /* coefficient k = 2*cos(2*PI*f/samplerate), k << 15 */
|
||||
int samples_per_chunk; /* how many samples lasts one chunk */
|
||||
int16_t *fsk_filter_spl; /* array with samples_per_chunk */
|
||||
int fsk_filter_pos; /* current sample position in filter_spl */
|
||||
int tone_detected; /* what tone has been detected */
|
||||
int tone_count; /* how long has that tone been detected */
|
||||
double tone_phaseshift256; /* how much the phase of sine wave changes per sample */
|
||||
double tone_phase256; /* current phase */
|
||||
double paging_phaseshift256[4];/* how much the phase of sine wave changes per sample */
|
||||
double paging_phase256[4]; /* current phase */
|
||||
int paging_tone; /* current tone (0..3) in sequenced mode */
|
||||
int paging_count; /* current sample count of tone in seq. mode */
|
||||
} anetz_t;
|
||||
|
||||
|
||||
double anetz_kanal2freq(int kanal, int unterband);
|
||||
int anetz_init(void);
|
||||
int anetz_create(const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume);
|
||||
void anetz_destroy(sender_t *sender);
|
||||
void anetz_loss_indication(anetz_t *anetz);
|
||||
void anetz_receive_tone(anetz_t *anetz, int bit);
|
||||
|
329
src/anetz/dsp.c
Normal file
329
src/anetz/dsp.c
Normal file
@@ -0,0 +1,329 @@
|
||||
/* A-Netz signal processing
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/goertzel.h"
|
||||
#include "anetz.h"
|
||||
#include "dsp.h"
|
||||
|
||||
#define PI 3.1415927
|
||||
|
||||
/* signalling */
|
||||
#define TX_PEAK 8190.0 /* peak amplitude of sine wave (must be less than 32768/4) */
|
||||
// FIXME: what is the allowed deviation of tone?
|
||||
#define CHUNK_DURATION 0.010 /* 10 ms */
|
||||
|
||||
// FIXME: how long until we detect a tone?
|
||||
#define TONE_DETECT_TH 8 /* chunk intervals to detect continous tone */
|
||||
|
||||
/* carrier loss detection */
|
||||
#define LOSS_INTERVAL 100 /* filter steps (chunk durations) for one second interval */
|
||||
#define LOSS_TIME 12 /* duration of signal loss before release */
|
||||
|
||||
extern int page_sequence;
|
||||
|
||||
/* two signalling tones */
|
||||
static double fsk_tones[2] = {
|
||||
2280.0,
|
||||
1750.0,
|
||||
};
|
||||
|
||||
/* table for fast sine generation */
|
||||
int dsp_sine[256];
|
||||
|
||||
/* global init for audio processing */
|
||||
void dsp_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Generating sine table.\n");
|
||||
for (i = 0; i < 256; i++) {
|
||||
dsp_sine[i] = (int)(sin((double)i / 256.0 * 2.0 * PI) * TX_PEAK);
|
||||
}
|
||||
|
||||
if (TX_PEAK > 8191.0) {
|
||||
fprintf(stderr, "TX_PEAK definition too high, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
/* Init transceiver instance. */
|
||||
int dsp_init_sender(anetz_t *anetz)
|
||||
{
|
||||
int16_t *spl;
|
||||
double coeff;
|
||||
int detect_tone = (anetz->sender.loopback) ? 0 : 1;
|
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Init DSP for 'Sender'.\n");
|
||||
|
||||
audio_init_loss(&anetz->sender.loss, LOSS_INTERVAL, anetz->sender.loss_volume, LOSS_TIME);
|
||||
|
||||
anetz->samples_per_chunk = anetz->sender.samplerate * CHUNK_DURATION;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per chunk duration.\n", anetz->samples_per_chunk);
|
||||
spl = calloc(1, anetz->samples_per_chunk << 1);
|
||||
if (!spl) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
anetz->fsk_filter_spl = spl;
|
||||
|
||||
anetz->tone_detected = -1;
|
||||
|
||||
coeff = 2.0 * cos(2.0 * PI * fsk_tones[detect_tone] / (double)anetz->sender.samplerate);
|
||||
anetz->fsk_tone_coeff = coeff * 32768.0;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "RX %.0f Hz coeff = %d\n", fsk_tones[detect_tone], (int)anetz->fsk_tone_coeff);
|
||||
anetz->tone_phaseshift256 = 256.0 / ((double)anetz->sender.samplerate / fsk_tones[0]);
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "TX %.0f Hz phaseshift = %.4f\n", fsk_tones[0], anetz->tone_phaseshift256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void dsp_cleanup_sender(anetz_t *anetz)
|
||||
{
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n");
|
||||
|
||||
if (anetz->fsk_filter_spl) {
|
||||
free(anetz->fsk_filter_spl);
|
||||
anetz->fsk_filter_spl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Count duration of tone and indicate detection/loss to protocol handler. */
|
||||
static void fsk_receive_tone(anetz_t *anetz, int tone, int goodtone, double level)
|
||||
{
|
||||
/* lost tone because it is not good anymore or has changed */
|
||||
if (!goodtone || tone != anetz->tone_detected) {
|
||||
if (anetz->tone_count >= TONE_DETECT_TH) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Lost %.0f Hz tone after %d ms.\n", fsk_tones[anetz->tone_detected], 1000.0 * CHUNK_DURATION * anetz->tone_count);
|
||||
anetz_receive_tone(anetz, -1);
|
||||
}
|
||||
if (goodtone)
|
||||
anetz->tone_detected = tone;
|
||||
else
|
||||
anetz->tone_detected = -1;
|
||||
anetz->tone_count = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
anetz->tone_count++;
|
||||
|
||||
if (anetz->tone_count >= TONE_DETECT_TH)
|
||||
audio_reset_loss(&anetz->sender.loss);
|
||||
if (anetz->tone_count == TONE_DETECT_TH) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Detecting continous %.0f Hz tone. (level = %d%%)\n", fsk_tones[anetz->tone_detected], (int)(level * 100));
|
||||
anetz_receive_tone(anetz, anetz->tone_detected);
|
||||
}
|
||||
}
|
||||
|
||||
/* Filter one chunk of audio an detect tone, quality and loss of signal. */
|
||||
static void fsk_decode_chunk(anetz_t *anetz, int16_t *spl, int max)
|
||||
{
|
||||
double level, result;
|
||||
|
||||
level = audio_level(spl, max);
|
||||
|
||||
if (audio_detect_loss(&anetz->sender.loss, level))
|
||||
anetz_loss_indication(anetz);
|
||||
|
||||
audio_goertzel(spl, max, 0, &anetz->fsk_tone_coeff, &result, 1);
|
||||
|
||||
/* show quality of tone */
|
||||
if (anetz->sender.loopback) {
|
||||
/* adjust level, so we get peak of sine curve */
|
||||
PDEBUG(DFSK, DEBUG_NOTICE, "Quality Tone:%3.0f%% Level:%3.0f%%\n", result / level * 100.0, level / 0.63662 * 100.0);
|
||||
}
|
||||
|
||||
/* adjust level, so we get peak of sine curve */
|
||||
/* indicate detected tone 1 (1750 Hz) or tone 0 (2280 Hz) at loopback */
|
||||
if (level / 0.63 > 0.05 && result / level > 0.5)
|
||||
fsk_receive_tone(anetz, (anetz->sender.loopback) ? 0 : 1, 1, level / 0.63662);
|
||||
else
|
||||
fsk_receive_tone(anetz, (anetz->sender.loopback) ? 0 : 1, 0, level / 0.63662);
|
||||
}
|
||||
|
||||
/* Process received audio stream from radio unit. */
|
||||
void sender_receive(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
anetz_t *anetz = (anetz_t *) sender;
|
||||
int16_t *spl;
|
||||
int max, pos;
|
||||
int i;
|
||||
|
||||
/* write received samples to decode buffer */
|
||||
max = anetz->samples_per_chunk;
|
||||
pos = anetz->fsk_filter_pos;
|
||||
spl = anetz->fsk_filter_spl;
|
||||
for (i = 0; i < length; i++) {
|
||||
spl[pos++] = samples[i];
|
||||
if (pos == max) {
|
||||
pos = 0;
|
||||
fsk_decode_chunk(anetz, spl, max);
|
||||
}
|
||||
}
|
||||
anetz->fsk_filter_pos = pos;
|
||||
|
||||
/* Forward audio to network (call process). */
|
||||
if (anetz->dsp_mode == DSP_MODE_AUDIO && anetz->sender.callref) {
|
||||
int16_t down[length]; /* more than enough */
|
||||
int count;
|
||||
|
||||
count = samplerate_downsample(&anetz->sender.srstate, samples, length, down);
|
||||
spl = anetz->sender.rxbuf;
|
||||
pos = anetz->sender.rxbuf_pos;
|
||||
for (i = 0; i < count; i++) {
|
||||
spl[pos++] = down[i];
|
||||
if (pos == 160) {
|
||||
call_tx_audio(anetz->sender.callref, spl, 160);
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
anetz->sender.rxbuf_pos = pos;
|
||||
} else
|
||||
anetz->sender.rxbuf_pos = 0;
|
||||
}
|
||||
|
||||
/* Set 4 paging frequencies */
|
||||
void dsp_set_paging(anetz_t *anetz, double *freq)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
anetz->paging_phaseshift256[i] = 256.0 / ((double)anetz->sender.samplerate / freq[i]);
|
||||
anetz->paging_phase256[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate audio stream of 4 simultanious paging tones. Keep phase for next call of function.
|
||||
* Use TX_PEAK for one tone, which gives peak of TX_PEAK * 4 for all tones. */
|
||||
static void fsk_paging_tone(anetz_t *anetz, int16_t *samples, int length)
|
||||
{
|
||||
double phaseshift[5], phase[5];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
phaseshift[i] = anetz->paging_phaseshift256[i];
|
||||
phase[i] = anetz->paging_phase256[i];
|
||||
}
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = dsp_sine[((uint8_t)phase[0]) & 0xff]
|
||||
+ dsp_sine[((uint8_t)phase[1]) & 0xff]
|
||||
+ dsp_sine[((uint8_t)phase[2]) & 0xff]
|
||||
+ dsp_sine[((uint8_t)phase[3]) & 0xff];
|
||||
phase[0] += phaseshift[0];
|
||||
phase[1] += phaseshift[1];
|
||||
phase[2] += phaseshift[2];
|
||||
phase[3] += phaseshift[3];
|
||||
if (phase[0] >= 256) phase[0] -= 256;
|
||||
if (phase[1] >= 256) phase[1] -= 256;
|
||||
if (phase[2] >= 256) phase[2] -= 256;
|
||||
if (phase[3] >= 256) phase[3] -= 256;
|
||||
}
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
anetz->paging_phase256[i] = phase[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate audio stream of 4 sequenced paging tones. Keep phase for next call of function.
|
||||
* Use TX_PEAK * 2 for each tone. We will use a lower peak, because the radio might not TX it. */
|
||||
static void fsk_paging_tone_sequence(anetz_t *anetz, int16_t *samples, int length, int numspl)
|
||||
{
|
||||
double phaseshift, phase;
|
||||
int tone, count;
|
||||
|
||||
phase = anetz->tone_phase256;
|
||||
tone = anetz->paging_tone;
|
||||
count = anetz->paging_count;
|
||||
|
||||
next_tone:
|
||||
phaseshift = anetz->paging_phaseshift256[tone];
|
||||
|
||||
while (length) {
|
||||
*samples++ = dsp_sine[((uint8_t)phase) & 0xff] << 2;
|
||||
phase += phaseshift;
|
||||
if (phase >= 256)
|
||||
phase -= 256;
|
||||
if (++count == numspl) {
|
||||
count = 0;
|
||||
if (++tone == 4)
|
||||
tone = 0;
|
||||
goto next_tone;
|
||||
}
|
||||
length--;
|
||||
}
|
||||
|
||||
anetz->tone_phase256 = phase;
|
||||
anetz->paging_tone = tone;
|
||||
anetz->paging_count = count;
|
||||
}
|
||||
|
||||
/* Generate audio stream from tone. Keep phase for next call of function. */
|
||||
static void fsk_tone(anetz_t *anetz, int16_t *samples, int length)
|
||||
{
|
||||
double phaseshift, phase;
|
||||
int i;
|
||||
|
||||
phaseshift = anetz->tone_phaseshift256;
|
||||
phase = anetz->tone_phase256;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = dsp_sine[((uint8_t)phase) & 0xff];
|
||||
phase += phaseshift;
|
||||
if (phase >= 256)
|
||||
phase -= 256;
|
||||
}
|
||||
|
||||
anetz->tone_phase256 = phase;
|
||||
}
|
||||
|
||||
/* Provide stream of audio toward radio unit */
|
||||
void sender_send(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
anetz_t *anetz = (anetz_t *) sender;
|
||||
|
||||
switch (anetz->dsp_mode) {
|
||||
case DSP_MODE_SILENCE:
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
break;
|
||||
case DSP_MODE_AUDIO:
|
||||
jitter_load(&anetz->sender.audio, samples, length);
|
||||
break;
|
||||
case DSP_MODE_TONE:
|
||||
fsk_tone(anetz, samples, length);
|
||||
break;
|
||||
case DSP_MODE_PAGING:
|
||||
if (page_sequence)
|
||||
fsk_paging_tone_sequence(anetz, samples, length, page_sequence * anetz->sender.samplerate / 1000);
|
||||
else
|
||||
fsk_paging_tone(anetz, samples, length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
6
src/anetz/dsp.h
Normal file
6
src/anetz/dsp.h
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
void dsp_init(void);
|
||||
int dsp_init_sender(anetz_t *anetz);
|
||||
void dsp_cleanup_sender(anetz_t *anetz);
|
||||
void dsp_set_paging(anetz_t *anetz, double *freq);
|
||||
|
59
src/anetz/image.c
Normal file
59
src/anetz/image.c
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "image.h"
|
||||
|
||||
const char *image[] = {
|
||||
"@w",
|
||||
"",
|
||||
" A-NETZ",
|
||||
"@g /",
|
||||
" @w~@g /",
|
||||
" @w~@g / @G/|\\@g",
|
||||
" @w~@g ___________/_______ @G//|\\\\@g",
|
||||
" @G/|\\@g /| | |\\\\ @w~@g @G//|\\\\@g",
|
||||
"@B___@G/|\\@B___________________@g/ | | | \\\\@B_____________@G//|\\\\@B__",
|
||||
" @G//|\\\\@g _/_____________/_(|_______|________|__\\\\________ @G///|\\\\\\@g",
|
||||
" @G//|\\\\@g ( - - \\ @G///|\\\\\\@g",
|
||||
" @G_|_@g | _____ _____ ) @G/ | \\@g",
|
||||
" =____/@b/ \\@g\\_________________________/@b/ \\@g\\______= @G_|_",
|
||||
"@w_____________@b( (@w*@b) )@w_________________________@b( (@w*@b) )@w________________",
|
||||
" @b\\___/@w @b\\___/@w",
|
||||
" ===== ====== ====== ====== ====== ======",
|
||||
"",
|
||||
"____________________________________________________________________",
|
||||
NULL
|
||||
};
|
||||
|
||||
void print_image(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; image[i]; i++) {
|
||||
for (j = 0; j < strlen(image[i]); j++) {
|
||||
if (image[i][j] == '@') {
|
||||
j++;
|
||||
switch(image[i][j]) {
|
||||
case 'g': /* gray */
|
||||
printf("\033[0;37m");
|
||||
break;
|
||||
case 'G': /* green */
|
||||
printf("\033[0;32m");
|
||||
break;
|
||||
case 'w': /* white */
|
||||
printf("\033[1;37m");
|
||||
break;
|
||||
case 'b': /* brown (yellow) */
|
||||
printf("\033[0;33m");
|
||||
break;
|
||||
case 'B': /* blue */
|
||||
printf("\033[0;34m");
|
||||
break;
|
||||
}
|
||||
} else
|
||||
printf("%c", image[i][j]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("\033[0;39m");
|
||||
}
|
||||
|
3
src/anetz/image.h
Normal file
3
src/anetz/image.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void print_image(void);
|
||||
|
177
src/anetz/main.c
Normal file
177
src/anetz/main.c
Normal file
@@ -0,0 +1,177 @@
|
||||
/* A-Netz main
|
||||
*
|
||||
* (C) 2016 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 <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include "../common/main.h"
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/mncc_sock.h"
|
||||
#include "anetz.h"
|
||||
#include "dsp.h"
|
||||
#include "image.h"
|
||||
|
||||
/* settings */
|
||||
int page_sequence = 0;
|
||||
|
||||
void print_help(const char *arg0)
|
||||
{
|
||||
print_help_common(arg0);
|
||||
/* - - */
|
||||
printf(" -P --page-sequence 0 | <ms>\n");
|
||||
printf(" Cycle paging tones, rather than sending simultaniously.\n");
|
||||
printf(" (default = '%d')\n", page_sequence);
|
||||
printf("\nstation-id: Give (last) 5 digits of station-id, you don't need to enter it\n");
|
||||
printf(" for every start of this program.\n");
|
||||
}
|
||||
|
||||
static int handle_options(int argc, char **argv)
|
||||
{
|
||||
int skip_args = 0;
|
||||
|
||||
static struct option long_options_special[] = {
|
||||
{"page-sequence", 1, 0, 'P'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
set_options_common("P:", long_options_special);
|
||||
|
||||
while (1) {
|
||||
int option_index = 0, c;
|
||||
|
||||
c = getopt_long(argc, argv, optstring, long_options, &option_index);
|
||||
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'P':
|
||||
page_sequence = atoi(optarg);
|
||||
skip_args += 2;
|
||||
break;
|
||||
default:
|
||||
opt_switch_common(c, argv[0], &skip_args);
|
||||
}
|
||||
}
|
||||
|
||||
free(long_options);
|
||||
|
||||
return skip_args;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int rc;
|
||||
int skip_args;
|
||||
const char *station_id = "";
|
||||
|
||||
skip_args = handle_options(argc, argv);
|
||||
argc -= skip_args;
|
||||
argv += skip_args;
|
||||
|
||||
if (argc > 1) {
|
||||
station_id = argv[1];
|
||||
if (strlen(station_id) != 5 && strlen(station_id) != 7) {
|
||||
printf("Given station ID '%s' does not have 7 or (the last) 5 digits\n", station_id);
|
||||
return 0;
|
||||
}
|
||||
if (strlen(station_id) > 5)
|
||||
station_id += strlen(station_id) - 5;
|
||||
}
|
||||
|
||||
if (!kanal) {
|
||||
printf("No channel (\"Kanal\") is specified, I suggest channel 30.\n\n");
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!loopback)
|
||||
print_image();
|
||||
|
||||
/* init functions */
|
||||
if (use_mncc_sock) {
|
||||
rc = mncc_init("/tmp/bsc_mncc");
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
dsp_init();
|
||||
anetz_init();
|
||||
rc = call_init(station_id, call_sounddev, samplerate, latency, loopback);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create call control instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* create transceiver instance */
|
||||
rc = anetz_create(sounddev, samplerate, kanal, loopback, lossdetect / 100.0);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
printf("Base station ready, please tune transmitter to %.3f MHz and receiver "
|
||||
"to %.3f MHz.\n", anetz_kanal2freq(kanal, 0),
|
||||
anetz_kanal2freq(kanal, 1));
|
||||
|
||||
signal(SIGINT,sighandler);
|
||||
signal(SIGHUP,sighandler);
|
||||
signal(SIGTERM,sighandler);
|
||||
signal(SIGPIPE,sighandler);
|
||||
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
int rc;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = rt_prio;
|
||||
rc = sched_setscheduler(0, SCHED_RR, &schedp);
|
||||
if (rc)
|
||||
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
|
||||
}
|
||||
|
||||
main_loop(&quit, latency);
|
||||
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = 0;
|
||||
sched_setscheduler(0, SCHED_OTHER, &schedp);
|
||||
}
|
||||
|
||||
fail:
|
||||
/* cleanup functions */
|
||||
call_cleanup();
|
||||
if (use_mncc_sock)
|
||||
mncc_exit();
|
||||
|
||||
/* destroy transceiver instance */
|
||||
while (sender_head)
|
||||
anetz_destroy(sender_head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
17
src/bnetz/Makefile.am
Normal file
17
src/bnetz/Makefile.am
Normal file
@@ -0,0 +1,17 @@
|
||||
AM_CPPFLAGS = -Wall -g $(all_includes)
|
||||
|
||||
bin_PROGRAMS = \
|
||||
bnetz
|
||||
|
||||
bnetz_SOURCES = \
|
||||
bnetz.c \
|
||||
dsp.c \
|
||||
image.c \
|
||||
ansage-27.c \
|
||||
main.c
|
||||
bnetz_LDADD = \
|
||||
$(COMMON_LA) \
|
||||
$(ALSA_LIBS) \
|
||||
$(top_builddir)/src/common/libcommon.a \
|
||||
-lm
|
||||
|
5003
src/bnetz/ansage-27.c
Normal file
5003
src/bnetz/ansage-27.c
Normal file
File diff suppressed because it is too large
Load Diff
3
src/bnetz/ansage-27.h
Normal file
3
src/bnetz/ansage-27.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void init_ansage_27(void);
|
||||
|
870
src/bnetz/bnetz.c
Normal file
870
src/bnetz/bnetz.c
Normal file
@@ -0,0 +1,870 @@
|
||||
/* B-Netz protocol handling
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/cause.h"
|
||||
#include "bnetz.h"
|
||||
#include "dsp.h"
|
||||
|
||||
/* Call reference for calls from mobile station to network
|
||||
This offset of 0x400000000 is required for MNCC interface. */
|
||||
static int new_callref = 0x40000000;
|
||||
|
||||
/* mobile originating call */
|
||||
#define CARRIER_TO 0.08 /* 80 ms search for carrier */
|
||||
#define GRUPPE_TO 0.4 /* 400 ms search for "Gruppensignal" */
|
||||
#define DIALING_TO 1.00 /* FIXME: get real value */
|
||||
|
||||
/* radio loss condition */
|
||||
#define LOSS_OF_SIGNAL 9.6 /* duration of carrier loss until release: 9.6 s */
|
||||
|
||||
/* mobile terminating call */
|
||||
#define ALERTING_TO 60 /* timeout after 60 seconds alerting the MS */
|
||||
#define PAGING_TO 4 /* timeout 4 seconds after "Selektivruf" */
|
||||
#define PAGE_TRIES 2 /* two tries */
|
||||
#define SWITCH19_TIME 1.0 /* time to switch channel (radio should be tansmitting after that) */
|
||||
|
||||
/* Convert channel number to frequency number of base station.
|
||||
Set 'unterband' to 1 to get frequency of mobile station. */
|
||||
double bnetz_kanal2freq(int kanal, int unterband)
|
||||
{
|
||||
double freq = 153.010;
|
||||
|
||||
if (kanal >= 50)
|
||||
freq += 9.200 - 0.020 * 49;
|
||||
freq += (kanal - 1) * 0.020;
|
||||
if (unterband)
|
||||
freq -= 4.600;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
/* List of message digits */
|
||||
static struct impulstelegramme {
|
||||
int digit;
|
||||
const char *sequence;
|
||||
uint16_t telegramm;
|
||||
const char *description;
|
||||
} impulstelegramme[] = {
|
||||
/* Ziffern */
|
||||
{ '0', "0111011000000011", 0x0000, "Ziffer 0" },
|
||||
{ '1', "0111010100000101", 0x0000, "Ziffer 1" },
|
||||
{ '2', "0111010010001001", 0x0000, "Ziffer 2" },
|
||||
{ '3', "0111010001010001", 0x0000, "Ziffer 3" },
|
||||
{ '4', "0111001100000110", 0x0000, "Ziffer 4" },
|
||||
{ '5', "0111001010001010", 0x0000, "Ziffer 5" },
|
||||
{ '6', "0111001001010010", 0x0000, "Ziffer 6" },
|
||||
{ '7', "0111000110001100", 0x0000, "Ziffer 7" },
|
||||
{ '8', "0111000101010100", 0x0000, "Ziffer 8" },
|
||||
{ '9', "0111000011011000", 0x0000, "Ziffer 9" },
|
||||
/* Signale */
|
||||
{ 's', "0111001000100010", 0x0000, "Funkwahl ohne Gebuehrenuebermittlung" },
|
||||
{ 'S', "0111000100100100", 0x0000, "Funkwahl mit Gebuehrenuebermittlung" },
|
||||
{ 'e', "0111010000100001", 0x0000, "Funkwahlende" },
|
||||
{ 't', "0111010101010101", 0x0000, "Trennsignal/Schlusssignal" },
|
||||
/* Kanalbefehl B1 */
|
||||
{ 1001, "0111011000000101", 0x0000, "Kanalbefehl 1" },
|
||||
{ 1002, "0111011000001001", 0x0000, "Kanalbefehl 2" },
|
||||
{ 1003, "0111011000010001", 0x0000, "Kanalbefehl 3" },
|
||||
{ 1004, "0111011000000110", 0x0000, "Kanalbefehl 4" },
|
||||
{ 1005, "0111011000001010", 0x0000, "Kanalbefehl 5" },
|
||||
{ 1006, "0111011000010010", 0x0000, "Kanalbefehl 6" },
|
||||
{ 1007, "0111011000001100", 0x0000, "Kanalbefehl 7" },
|
||||
{ 1008, "0111011000010100", 0x0000, "Kanalbefehl 8" },
|
||||
{ 1009, "0111011000011000", 0x0000, "Kanalbefehl 9" },
|
||||
{ 1010, "0111010100000011", 0x0000, "Kanalbefehl 10" },
|
||||
{ 1011, "0111001100000101", 0x0000, "Kanalbefehl 11" }, /* 41 */
|
||||
{ 1012, "0111010100001001", 0x0000, "Kanalbefehl 12" },
|
||||
{ 1013, "0111010100010001", 0x0000, "Kanalbefehl 13" },
|
||||
{ 1014, "0111010100000110", 0x0000, "Kanalbefehl 14" },
|
||||
{ 1015, "0111010100001010", 0x0000, "Kanalbefehl 15" },
|
||||
{ 1016, "0111010100010010", 0x0000, "Kanalbefehl 16" },
|
||||
{ 1017, "0111010100001100", 0x0000, "Kanalbefehl 17" },
|
||||
{ 1018, "0111010100010100", 0x0000, "Kanalbefehl 18" },
|
||||
{ 1019, "0111010100011000", 0x0000, "Kanalbefehl 19" },
|
||||
{ 1020, "0111010010000011", 0x0000, "Kanalbefehl 20" },
|
||||
{ 1021, "0111010010000101", 0x0000, "Kanalbefehl 21" },
|
||||
{ 1022, "0111001100001001", 0x0000, "Kanalbefehl 22" }, /* 42 */
|
||||
{ 1023, "0111010010010001", 0x0000, "Kanalbefehl 23" },
|
||||
{ 1024, "0111010010000110", 0x0000, "Kanalbefehl 24" },
|
||||
{ 1025, "0111010010001010", 0x0000, "Kanalbefehl 25" },
|
||||
{ 1026, "0111010010010010", 0x0000, "Kanalbefehl 26" },
|
||||
{ 1027, "0111010010001100", 0x0000, "Kanalbefehl 27" },
|
||||
{ 1028, "0111010010010100", 0x0000, "Kanalbefehl 28" },
|
||||
{ 1029, "0111010010011000", 0x0000, "Kanalbefehl 29" },
|
||||
{ 1030, "0111010001000011", 0x0000, "Kanalbefehl 30" },
|
||||
{ 1031, "0111010001000101", 0x0000, "Kanalbefehl 31" },
|
||||
{ 1032, "0111010001001001", 0x0000, "Kanalbefehl 32" },
|
||||
{ 1033, "0111001100010001", 0x0000, "Kanalbefehl 33" }, /* 43 */
|
||||
{ 1034, "0111010001000110", 0x0000, "Kanalbefehl 34" },
|
||||
{ 1035, "0111010001001010", 0x0000, "Kanalbefehl 35" },
|
||||
{ 1036, "0111010001010010", 0x0000, "Kanalbefehl 36" },
|
||||
{ 1037, "0111010001001100", 0x0000, "Kanalbefehl 37" },
|
||||
{ 1038, "0111010001010100", 0x0000, "Kanalbefehl 38" },
|
||||
{ 1039, "0111010001011000", 0x0000, "Kanalbefehl 39" },
|
||||
/* Kanalbefehl B2 */
|
||||
{ 1050, "0111001010000011", 0x0000, "Kanalbefehl 50" },
|
||||
{ 1051, "0111001010000101", 0x0000, "Kanalbefehl 51" },
|
||||
{ 1052, "0111001010001001", 0x0000, "Kanalbefehl 52" },
|
||||
{ 1053, "0111001010010001", 0x0000, "Kanalbefehl 53" },
|
||||
{ 1054, "0111001010000110", 0x0000, "Kanalbefehl 54" },
|
||||
{ 1055, "0111001100001010", 0x0000, "Kanalbefehl 55" }, /* 45 */
|
||||
{ 1056, "0111001010010010", 0x0000, "Kanalbefehl 56" },
|
||||
{ 1057, "0111001010001100", 0x0000, "Kanalbefehl 57" },
|
||||
{ 1058, "0111001010010100", 0x0000, "Kanalbefehl 58" },
|
||||
{ 1059, "0111001010011000", 0x0000, "Kanalbefehl 59" },
|
||||
{ 1060, "0111001001000011", 0x0000, "Kanalbefehl 60" },
|
||||
{ 1061, "0111001001000101", 0x0000, "Kanalbefehl 61" },
|
||||
{ 1062, "0111001001001001", 0x0000, "Kanalbefehl 62" },
|
||||
{ 1063, "0111001001010001", 0x0000, "Kanalbefehl 63" },
|
||||
{ 1064, "0111001001000110", 0x0000, "Kanalbefehl 64" },
|
||||
{ 1065, "0111001001001010", 0x0000, "Kanalbefehl 65" },
|
||||
{ 1066, "0111001100010010", 0x0000, "Kanalbefehl 66" }, /* 46 */
|
||||
{ 1067, "0111001001001100", 0x0000, "Kanalbefehl 67" },
|
||||
{ 1068, "0111001001010100", 0x0000, "Kanalbefehl 68" },
|
||||
{ 1069, "0111001001011000", 0x0000, "Kanalbefehl 69" },
|
||||
{ 1070, "0111000110000011", 0x0000, "Kanalbefehl 70" },
|
||||
{ 1071, "0111000110000101", 0x0000, "Kanalbefehl 71" },
|
||||
{ 1072, "0111000110001001", 0x0000, "Kanalbefehl 72" },
|
||||
{ 1073, "0111000110010001", 0x0000, "Kanalbefehl 73" },
|
||||
{ 1074, "0111000110000110", 0x0000, "Kanalbefehl 74" },
|
||||
{ 1075, "0111000110001010", 0x0000, "Kanalbefehl 75" },
|
||||
{ 1076, "0111000110010010", 0x0000, "Kanalbefehl 76" },
|
||||
{ 1077, "0111001100001100", 0x0000, "Kanalbefehl 77" }, /* 47 */
|
||||
{ 1078, "0111000110010100", 0x0000, "Kanalbefehl 78" },
|
||||
{ 1079, "0111000110011000", 0x0000, "Kanalbefehl 79" },
|
||||
{ 1080, "0111000101000011", 0x0000, "Kanalbefehl 80" },
|
||||
{ 1081, "0111000101000101", 0x0000, "Kanalbefehl 81" },
|
||||
{ 1082, "0111000101001001", 0x0000, "Kanalbefehl 82" },
|
||||
{ 1083, "0111000101010001", 0x0000, "Kanalbefehl 83" },
|
||||
{ 1084, "0111000101000110", 0x0000, "Kanalbefehl 84" },
|
||||
{ 1085, "0111000101001010", 0x0000, "Kanalbefehl 85" },
|
||||
{ 1086, "0111000101010010", 0x0000, "Kanalbefehl 86" },
|
||||
/* Gruppenfreisignale */
|
||||
{ 2001, "0111000011000101", 0x0000, "Gruppenfreisignal 1" }, /* 91 */
|
||||
{ 2002, "0111000011001001", 0x0000, "Gruppenfreisignal 2" }, /* 92 */
|
||||
{ 2003, "0111000011010001", 0x0000, "Gruppenfreisignal 3" }, /* 93 */
|
||||
{ 2004, "0111000011000110", 0x0000, "Gruppenfreisignal 4" }, /* 94 */
|
||||
{ 2005, "0111000011001010", 0x0000, "Gruppenfreisignal 5" }, /* 95 */
|
||||
{ 2006, "0111000011010010", 0x0000, "Gruppenfreisignal 6" }, /* 96 */
|
||||
{ 2007, "0111000011001100", 0x0000, "Gruppenfreisignal 7" }, /* 97 */
|
||||
{ 2008, "0111000011010100", 0x0000, "Gruppenfreisignal 8" }, /* 98 */
|
||||
{ 2009, "0111000011000011", 0x0000, "Gruppenfreisignal 9" }, /* 90 */
|
||||
{ 2010, "0111000101000011", 0x0000, "Gruppenfreisignal 10" }, /* 80 */
|
||||
{ 2011, "0111000101000101", 0x0000, "Gruppenfreisignal 11" }, /* 81 */
|
||||
{ 2012, "0111000101001001", 0x0000, "Gruppenfreisignal 12" }, /* 82 */
|
||||
{ 2013, "0111000101010001", 0x0000, "Gruppenfreisignal 13" }, /* 83 */
|
||||
{ 2014, "0111000101000110", 0x0000, "Gruppenfreisignal 14" }, /* 84 */
|
||||
{ 2015, "0111000101001010", 0x0000, "Gruppenfreisignal 15" }, /* 85 */
|
||||
{ 2016, "0111000101010010", 0x0000, "Gruppenfreisignal 16" }, /* 86 */
|
||||
{ 2017, "0111000101001100", 0x0000, "Gruppenfreisignal 17" }, /* 87 */
|
||||
{ 2018, "0111000101010100", 0x0000, "Gruppenfreisignal 18" }, /* 88 */
|
||||
{ 2019, "0111000101011000", 0x0000, "Gruppenfreisignal 19 (Kanaele kleiner Leistung)" }, /* 89 */
|
||||
{ 0, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
/* Return bit sequence string by given digit. */
|
||||
static struct impulstelegramme *bnetz_telegramm(int digit)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; impulstelegramme[i].digit; i++) {
|
||||
if (impulstelegramme[i].digit == digit)
|
||||
return &impulstelegramme[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* switch pilot signal (tone or file) */
|
||||
static void switch_channel_19(bnetz_t *bnetz, int on)
|
||||
{
|
||||
if (bnetz->sender.use_pilot_signal >= 0) {
|
||||
bnetz->sender.pilot_on = on;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bnetz->pilot_file && bnetz->pilot_is_on != on) {
|
||||
FILE *fp;
|
||||
|
||||
fp = fopen(bnetz->pilot_file, "w");
|
||||
if (!fp) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to open file '%s' to switch channel 19!\n");
|
||||
return;
|
||||
}
|
||||
fprintf(fp, "%s\n", (on) ? bnetz->pilot_on : bnetz->pilot_off);
|
||||
fclose(fp);
|
||||
bnetz->pilot_is_on = on;
|
||||
}
|
||||
}
|
||||
|
||||
/* global init */
|
||||
int bnetz_init(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; impulstelegramme[i].digit; i++) {
|
||||
uint16_t telegramm = 0;
|
||||
for (j = 0; j < strlen(impulstelegramme[i].sequence); j++) {
|
||||
telegramm <<= 1;
|
||||
telegramm |= (impulstelegramme[i].sequence[j] == '1');
|
||||
}
|
||||
impulstelegramme[i].telegramm = telegramm;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bnetz_timeout(struct timer *timer);
|
||||
|
||||
/* Create transceiver instance and link to a list. */
|
||||
int bnetz_create(const char *sounddev, int samplerate, int kanal, int gfs, int loopback, double loss_factor, const char *pilot)
|
||||
{
|
||||
bnetz_t *bnetz;
|
||||
int use_pilot_tone = -1;
|
||||
char pilot_file[256] = "", pilot_on[256] = "", pilot_off[256] = "";
|
||||
int rc;
|
||||
|
||||
if (!(kanal >= 1 && kanal <= 39) && !(kanal >= 50 && kanal <= 86)) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Channel ('Kanal') number %d invalid.\n", kanal);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (kanal == 19) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Selected calling channel ('Rufkanal') number %d can't be used as traffic channel.\n", kanal);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((gfs < 1 || gfs > 19)) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Given 'Gruppenfreisignal' %d invalid.\n", gfs);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!strcmp(pilot, "tone"))
|
||||
use_pilot_tone = 2;
|
||||
else
|
||||
if (!strcmp(pilot, "positive"))
|
||||
use_pilot_tone = 1;
|
||||
else
|
||||
if (!strcmp(pilot, "negative"))
|
||||
use_pilot_tone = 0;
|
||||
else {
|
||||
char *p;
|
||||
|
||||
strncpy(pilot_file, pilot, sizeof(pilot_file) - 1);
|
||||
p = strchr(pilot_file, '=');
|
||||
if (!p) {
|
||||
error_pilot:
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Given pilot file (to switch to channel 19) is missing parameters. Use <file>=<on>:<off> format!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
*p++ = '\0';
|
||||
strncpy(pilot_on, p, sizeof(pilot_on) - 1);
|
||||
p = strchr(pilot_file, ':');
|
||||
if (!p)
|
||||
goto error_pilot;
|
||||
*p++ = '\0';
|
||||
strncpy(pilot_off, p, sizeof(pilot_off) - 1);
|
||||
}
|
||||
|
||||
bnetz = calloc(1, sizeof(bnetz_t));
|
||||
if (!bnetz) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Creating 'B-Netz' instance for 'Kanal' = %d 'Gruppenfreisignal' = %d (sample rate %d).\n", kanal, gfs, samplerate);
|
||||
|
||||
/* init general part of transceiver */
|
||||
rc = sender_create(&bnetz->sender, sounddev, samplerate, kanal, loopback, loss_factor, use_pilot_tone);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to init transceiver process!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* init audio processing */
|
||||
rc = dsp_init_sender(bnetz);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_ERROR, "Failed to init audio processing!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* go into idle state */
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering IDLE state, sending 'Gruppenfreisignal' %d on channel %d.\n", gfs, kanal);
|
||||
bnetz->state = BNETZ_FREI;
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
bnetz->gfs = gfs;
|
||||
strncpy(bnetz->pilot_file, pilot_file, sizeof(bnetz->pilot_file) - 1);
|
||||
strncpy(bnetz->pilot_on, pilot_on, sizeof(bnetz->pilot_on) - 1);
|
||||
strncpy(bnetz->pilot_off, pilot_off, sizeof(bnetz->pilot_off) - 1);
|
||||
timer_init(&bnetz->timer, bnetz_timeout, bnetz);
|
||||
switch_channel_19(bnetz, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
bnetz_destroy(&bnetz->sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void bnetz_destroy(sender_t *sender)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Destroying 'B-Netz' instance for 'Kanal' = %d.\n", sender->kanal);
|
||||
switch_channel_19(bnetz, 0);
|
||||
dsp_cleanup_sender(bnetz);
|
||||
timer_exit(&bnetz->timer);
|
||||
sender_destroy(&bnetz->sender);
|
||||
free(bnetz);
|
||||
}
|
||||
|
||||
/* Abort connection towards mobile station by sending idle digits. */
|
||||
static void bnetz_go_idle(bnetz_t *bnetz)
|
||||
{
|
||||
timer_stop(&bnetz->timer);
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering IDLE state, sending 'Gruppenfreisignal' %d.\n", bnetz->gfs);
|
||||
bnetz->state = BNETZ_FREI;
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
switch_channel_19(bnetz, 0);
|
||||
bnetz->station_id[0] = '\0';
|
||||
}
|
||||
|
||||
/* Release connection towards mobile station by sending release digits. */
|
||||
static void bnetz_release(bnetz_t *bnetz)
|
||||
{
|
||||
timer_stop(&bnetz->timer);
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering release state, sending 'Trennsignal'.\n");
|
||||
bnetz->state = BNETZ_TRENNEN;
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
switch_channel_19(bnetz, 0);
|
||||
bnetz->trenn_count = 0;
|
||||
bnetz->station_id[0] = '\0';
|
||||
}
|
||||
|
||||
/* Enter paging state and transmit station ID. */
|
||||
static void bnetz_page(bnetz_t *bnetz, const char *dial_string, int try)
|
||||
{
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Entering paging state (try %d), sending 'Selektivruf' to '%s'.\n", try, dial_string);
|
||||
bnetz->state = BNETZ_SELEKTIVRUF;
|
||||
bnetz->dsp_mode = DSP_MODE_0;
|
||||
bnetz->page_mode = PAGE_MODE_NUMBER;
|
||||
bnetz->page_try = try;
|
||||
strcpy(bnetz->station_id, dial_string);
|
||||
bnetz->station_id_pos = 0;
|
||||
timer_start(&bnetz->timer, SWITCH19_TIME);
|
||||
switch_channel_19(bnetz, 1);
|
||||
}
|
||||
|
||||
/* FSK processing requests next digit after transmission of previous
|
||||
digit has been finished. */
|
||||
const char *bnetz_get_telegramm(bnetz_t *bnetz)
|
||||
{
|
||||
struct impulstelegramme *it = NULL;
|
||||
|
||||
if (bnetz->sender.loopback) {
|
||||
bnetz->loopback_time[bnetz->loopback_count] = get_time();
|
||||
it = bnetz_telegramm(bnetz->loopback_count + '0');
|
||||
if (++bnetz->loopback_count > 9)
|
||||
bnetz->loopback_count = 0;
|
||||
} else
|
||||
switch(bnetz->state) {
|
||||
case BNETZ_FREI:
|
||||
it = bnetz_telegramm(2000 + bnetz->gfs);
|
||||
break;
|
||||
case BNETZ_WAHLABRUF:
|
||||
if (bnetz->station_id_pos == 5) {
|
||||
bnetz->dsp_mode = DSP_MODE_1;
|
||||
return NULL;
|
||||
}
|
||||
it = bnetz_telegramm(bnetz->station_id[bnetz->station_id_pos++]);
|
||||
break;
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
if (bnetz->page_mode == PAGE_MODE_KANALBEFEHL) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Paging mobile station %s complete, waiting for answer.\n", bnetz->station_id);
|
||||
bnetz->state = BNETZ_RUFBESTAETIGUNG;
|
||||
bnetz->dsp_mode = DSP_MODE_SILENCE;
|
||||
switch_channel_19(bnetz, 0);
|
||||
timer_start(&bnetz->timer, PAGING_TO);
|
||||
return NULL;
|
||||
}
|
||||
if (bnetz->station_id_pos == 5) {
|
||||
it = bnetz_telegramm(bnetz->sender.kanal + 1000);
|
||||
bnetz->page_mode = PAGE_MODE_KANALBEFEHL;
|
||||
break;
|
||||
}
|
||||
it = bnetz_telegramm(bnetz->station_id[bnetz->station_id_pos++]);
|
||||
break;
|
||||
case BNETZ_TRENNEN:
|
||||
if (bnetz->trenn_count++ == 75) {
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Maximum number of release digits sent, going idle.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return NULL;
|
||||
}
|
||||
it = bnetz_telegramm('t');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!it)
|
||||
abort();
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Sending telegramm '%s'.\n", it->description);
|
||||
return it->sequence;
|
||||
}
|
||||
|
||||
/* Loss of signal was detected, release active call. */
|
||||
void bnetz_loss_indication(bnetz_t *bnetz)
|
||||
{
|
||||
if (bnetz->state == BNETZ_GESPRAECH
|
||||
|| bnetz->state == BNETZ_RUFHALTUNG) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Detected loss of signal, releasing.\n");
|
||||
bnetz_release(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_TEMPFAIL);
|
||||
bnetz->sender.callref = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* A continuous tone was detected or is gone. */
|
||||
void bnetz_receive_tone(bnetz_t *bnetz, int bit)
|
||||
{
|
||||
if (bit >= 0)
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Received contiuous %d Hz tone.\n", (bit)?1950:2070);
|
||||
else
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Continuous tone is gone.\n");
|
||||
|
||||
if (bnetz->sender.loopback) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_FREI:
|
||||
if (bit == 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Kanalbelegung' from mobile station, sending signal 'Wahlabruf'.\n");
|
||||
bnetz->state = BNETZ_WAHLABRUF;
|
||||
bnetz->dial_mode = DIAL_MODE_START;
|
||||
bnetz->telegramm = NULL;
|
||||
bnetz->dsp_mode = DSP_MODE_1;
|
||||
timer_start(&bnetz->timer, DIALING_TO);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
if (bit == 1) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Rufbestaetigung' from mobile station, call is ringing.\n");
|
||||
timer_stop(&bnetz->timer);
|
||||
bnetz->state = BNETZ_RUFHALTUNG;
|
||||
bnetz->dsp_mode = DSP_MODE_1;
|
||||
call_in_alerting(bnetz->sender.callref);
|
||||
timer_start(&bnetz->timer, ALERTING_TO);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
if (bit == 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received signal 'Beginnsignal' from mobile station, call establised.\n");
|
||||
timer_stop(&bnetz->timer);
|
||||
bnetz->state = BNETZ_GESPRAECH;
|
||||
bnetz->dsp_mode = DSP_MODE_AUDIO;
|
||||
call_in_answer(bnetz->sender.callref, bnetz->station_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* A digit was received. */
|
||||
void bnetz_receive_telegramm(bnetz_t *bnetz, uint16_t telegramm, double quality, double level)
|
||||
{
|
||||
int digit = 0;
|
||||
int i;
|
||||
int quality_percent = quality * 100;
|
||||
int level_percent = level * 100;
|
||||
|
||||
/* drop any telegramm that is too bad */
|
||||
if (quality_percent < 20)
|
||||
return;
|
||||
|
||||
for (i = 0; impulstelegramme[i].digit; i++) {
|
||||
if (impulstelegramme[i].telegramm == telegramm) {
|
||||
digit = impulstelegramme[i].digit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (digit == 0)
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Received unknown telegramm '0x%04x'. (quality=%d%% level=%d%%)\n", telegramm, quality_percent, level_percent);
|
||||
else
|
||||
PDEBUG(DBNETZ, (bnetz->sender.loopback) ? DEBUG_NOTICE : DEBUG_DEBUG, "Received telegramm '%s'. (quality=%d%% level=%d%%)\n", impulstelegramme[i].description, quality_percent, level_percent);
|
||||
|
||||
if (bnetz->sender.loopback) {
|
||||
if (digit >= '0' && digit <= '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Round trip delay is %.3f seconds\n", get_time() - bnetz->loopback_time[digit - '0'] - 0.160);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_WAHLABRUF:
|
||||
timer_start(&bnetz->timer, DIALING_TO);
|
||||
switch (bnetz->dial_mode) {
|
||||
case DIAL_MODE_START:
|
||||
if (digit != 's' && digit != 'S') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received digit that is not a start digit ('Funkwahl'), aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (digit == 'S')
|
||||
bnetz->dial_metering = 1;
|
||||
else
|
||||
bnetz->dial_metering = 0;
|
||||
bnetz->dial_mode = DIAL_MODE_STATIONID;
|
||||
memset(bnetz->station_id, 0, sizeof(bnetz->station_id));
|
||||
bnetz->dial_pos = 0;
|
||||
break;
|
||||
case DIAL_MODE_STATIONID:
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid station id digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->station_id[bnetz->dial_pos++] = digit;
|
||||
if (bnetz->dial_pos == 5) {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received station id from mobile phone: %s\n", bnetz->station_id);
|
||||
bnetz->dial_mode = DIAL_MODE_NUMBER;
|
||||
memset(bnetz->dial_number, 0, sizeof(bnetz->dial_number));
|
||||
bnetz->dial_pos = 0;
|
||||
}
|
||||
break;
|
||||
case DIAL_MODE_NUMBER:
|
||||
if (digit == 'e') {
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Received number from mobile phone: %s\n", bnetz->dial_number);
|
||||
bnetz->dial_mode = DIAL_MODE_START2;
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Sending station id back to phone: %s.\n", bnetz->station_id);
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
bnetz->station_id_pos = 0;
|
||||
break;
|
||||
}
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid number digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_pos == sizeof(bnetz->dial_number) - 1) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too many number digits, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->dial_number[bnetz->dial_pos++] = digit;
|
||||
break;
|
||||
case DIAL_MODE_START2:
|
||||
if (digit != 's' && digit != 'S') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a start message('Funkwahl'), aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if ((digit == 'S' && bnetz->dial_metering != 1) || (digit == 's' && bnetz->dial_metering != 0)) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received start message('Funkwahl') does not match first one, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->dial_mode = DIAL_MODE_STATIONID2;
|
||||
bnetz->dial_pos = 0;
|
||||
break;
|
||||
case DIAL_MODE_STATIONID2:
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid station id digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->station_id[bnetz->dial_pos++] != digit) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received station id does not match first one, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_pos == 5) {
|
||||
bnetz->dial_mode = DIAL_MODE_NUMBER2;
|
||||
bnetz->dial_pos = 0;
|
||||
}
|
||||
break;
|
||||
case DIAL_MODE_NUMBER2:
|
||||
if (digit == 'e') {
|
||||
int callref = ++new_callref;
|
||||
int rc;
|
||||
/* add 0 in front of number */
|
||||
char dialing[sizeof(bnetz->dial_number) + 1] = "0";
|
||||
strcpy(dialing + 1, bnetz->dial_number);
|
||||
|
||||
if (bnetz->dial_pos != strlen(bnetz->dial_number)) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too few number digits the second time, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Dialing complete %s->%s, call established.\n", bnetz->station_id, dialing);
|
||||
timer_stop(&bnetz->timer);
|
||||
bnetz->dsp_mode = DSP_MODE_AUDIO;
|
||||
bnetz->state = BNETZ_GESPRAECH;
|
||||
|
||||
/* setup call */
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Setup call to network.\n");
|
||||
rc = call_in_setup(callref, bnetz->station_id, dialing);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Call rejected (cause %d), releasing.\n", rc);
|
||||
bnetz_release(bnetz);
|
||||
return;
|
||||
}
|
||||
bnetz->sender.callref = callref;
|
||||
break;
|
||||
}
|
||||
if (digit < '0' || digit > '9') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received message that is not a valid number digit, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_pos == strlen(bnetz->dial_number)) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received too many number digits, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
if (bnetz->dial_number[bnetz->dial_pos++] != digit) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Second received number does not match first one, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BNETZ_GESPRAECH:
|
||||
/* only good telegramms shall pass */
|
||||
if (quality_percent < 70)
|
||||
return;
|
||||
if (digit == 't') {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Received 'Schlusssignal' from mobile station\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_NORMAL);
|
||||
bnetz->sender.callref = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Timeout handling */
|
||||
static void bnetz_timeout(struct timer *timer)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *)timer->priv;
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_WAHLABRUF:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while receiving call setup from mobile station, aborting.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
break;
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
PDEBUG(DBNETZ, DEBUG_DEBUG, "Transmitter switched to channel 19, starting paging telegramms.\n");
|
||||
bnetz->dsp_mode = DSP_MODE_TELEGRAMM;
|
||||
break;
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
if (bnetz->page_try == PAGE_TRIES) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for call acknowledge from mobile station, going idle.\n");
|
||||
bnetz_go_idle(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_OUTOFORDER);
|
||||
bnetz->sender.callref = 0;
|
||||
break;
|
||||
}
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for call acknowledge from mobile station, trying again.\n");
|
||||
bnetz_page(bnetz, bnetz->station_id, bnetz->page_try + 1);
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Timeout while waiting for answer of mobile station, releasing.\n");
|
||||
bnetz_release(bnetz);
|
||||
call_in_release(bnetz->sender.callref, CAUSE_NOANSWER);
|
||||
bnetz->sender.callref = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Call control starts call towards mobile station. */
|
||||
int call_out_setup(int callref, char *dialing)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
int i;
|
||||
|
||||
/* 1. check if number is invalid, return INVALNUMBER */
|
||||
if (strlen(dialing) != 5) {
|
||||
inval:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call to invalid number '%s', rejecting!\n", dialing);
|
||||
return -CAUSE_INVALNUMBER;
|
||||
}
|
||||
for (i = 0; i < 5; i++) {
|
||||
if (dialing[i] < '0' || dialing[i] > '9')
|
||||
goto inval;
|
||||
}
|
||||
|
||||
/* 2. check if given number is already in a call, return BUSY */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (!strcmp(bnetz->station_id, dialing))
|
||||
break;
|
||||
}
|
||||
if (sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call to busy number, rejecting!\n");
|
||||
return -CAUSE_BUSY;
|
||||
}
|
||||
|
||||
/* 3. check if all senders are busy, return NOCHANNEL */
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (bnetz->state == BNETZ_FREI)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing call, but no free channel, rejecting!\n");
|
||||
return -CAUSE_NOCHANNEL;
|
||||
}
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Call to mobile station, paging station id '%s'\n", dialing);
|
||||
|
||||
/* 4. trying to page mobile station */
|
||||
sender->callref = callref;
|
||||
bnetz_page(bnetz, dialing, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Call control sends disconnect (with tones).
|
||||
* An active call stays active, so tones and annoucements can be received
|
||||
* by mobile station.
|
||||
*/
|
||||
void call_out_disconnect(int callref, int cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Call has been disconnected by network.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, but no callref!\n");
|
||||
call_in_release(callref, CAUSE_INVALCALLREF);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Release when not active */
|
||||
if (bnetz->state == BNETZ_GESPRAECH)
|
||||
return;
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, during paging, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing disconnect, during alerting, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
call_in_release(callref, cause);
|
||||
|
||||
sender->callref = 0;
|
||||
}
|
||||
|
||||
/* Call control releases call toward mobile station. */
|
||||
void call_out_release(int callref, int cause)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
||||
PDEBUG(DBNETZ, DEBUG_INFO, "Call has been released by network, releasing call.\n");
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender) {
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, but no callref!\n");
|
||||
/* don't send release, because caller already released */
|
||||
return;
|
||||
}
|
||||
|
||||
sender->callref = 0;
|
||||
|
||||
switch (bnetz->state) {
|
||||
case BNETZ_GESPRAECH:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during call, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
case BNETZ_SELEKTIVRUF:
|
||||
case BNETZ_RUFBESTAETIGUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during paging, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
case BNETZ_RUFHALTUNG:
|
||||
PDEBUG(DBNETZ, DEBUG_NOTICE, "Outgoing release, during alerting, releasing!\n");
|
||||
bnetz_release(bnetz);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Receive audio from call instance. */
|
||||
void call_rx_audio(int callref, int16_t *samples, int count)
|
||||
{
|
||||
sender_t *sender;
|
||||
bnetz_t *bnetz;
|
||||
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
bnetz = (bnetz_t *) sender;
|
||||
if (sender->callref == callref)
|
||||
break;
|
||||
}
|
||||
if (!sender)
|
||||
return;
|
||||
|
||||
if (bnetz->dsp_mode == DSP_MODE_AUDIO) {
|
||||
int16_t up[count * bnetz->sender.srstate.factor];
|
||||
count = samplerate_upsample(&bnetz->sender.srstate, samples, count, up);
|
||||
jitter_save(&bnetz->sender.audio, up, count);
|
||||
}
|
||||
}
|
||||
|
99
src/bnetz/bnetz.h
Normal file
99
src/bnetz/bnetz.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "../common/sender.h"
|
||||
|
||||
/* fsk modes of transmission */
|
||||
enum dsp_mode {
|
||||
DSP_MODE_AUDIO, /* stream audio */
|
||||
DSP_MODE_SILENCE, /* sending silence */
|
||||
DSP_MODE_0, /* send tone 0 */
|
||||
DSP_MODE_1, /* send tone 1 */
|
||||
DSP_MODE_TELEGRAMM, /* send "Telegramm" */
|
||||
};
|
||||
|
||||
/* current state of b-netz sender */
|
||||
enum bnetz_state {
|
||||
BNETZ_FREI, /* sending 'Gruppenfreisignal' */
|
||||
BNETZ_WAHLABRUF, /* sending 'Wahlabruf', receiving call setup */
|
||||
BNETZ_SELEKTIVRUF, /* paging phone */
|
||||
BNETZ_RUFBESTAETIGUNG, /* waitig for acknowledge from phone */
|
||||
BNETZ_RUFHALTUNG, /* phone is ringing */
|
||||
BNETZ_GESPRAECH, /* during conversation */
|
||||
BNETZ_TRENNEN, /* release of call */
|
||||
};
|
||||
|
||||
/* current state of received dialing */
|
||||
enum dial_mode {
|
||||
DIAL_MODE_START,
|
||||
DIAL_MODE_STATIONID,
|
||||
DIAL_MODE_NUMBER,
|
||||
DIAL_MODE_START2,
|
||||
DIAL_MODE_STATIONID2,
|
||||
DIAL_MODE_NUMBER2,
|
||||
};
|
||||
|
||||
/* current state of paging mobile station */
|
||||
enum page_mode {
|
||||
PAGE_MODE_NUMBER,
|
||||
PAGE_MODE_KANALBEFEHL,
|
||||
};
|
||||
|
||||
/* instance of bnetz sender */
|
||||
typedef struct bnetz {
|
||||
sender_t sender;
|
||||
|
||||
/* system info */
|
||||
int gfs; /* 'Gruppenfreisignal' */
|
||||
|
||||
/* switch sender to channel 19 */
|
||||
char pilot_file[256]; /* if set, write to given file to switch to channel 19 or back */
|
||||
char pilot_on[256]; /* what to write to switch to channel 19 */
|
||||
char pilot_off[256]; /* what to write to switch back */
|
||||
int pilot_is_on; /* set, if we are on channel 19. also used to switch back on exit */
|
||||
|
||||
/* all bnetz states */
|
||||
enum bnetz_state state; /* main state of sender */
|
||||
enum dial_mode dial_mode; /* sub state while dialing is received */
|
||||
int dial_metering; /* set, if phone supports metering pulses */
|
||||
char dial_number[14]; /* dial string received */
|
||||
int dial_pos; /* current position while receiving digits */
|
||||
char station_id[6]; /* current station ID */
|
||||
int station_id_pos; /* position while transmitting */
|
||||
enum page_mode page_mode; /* sub state while paging */
|
||||
int page_try; /* try number (1 or 2) */
|
||||
struct timer timer;
|
||||
int trenn_count; /* count number of release messages */
|
||||
|
||||
/* dsp states */
|
||||
enum dsp_mode dsp_mode; /* current mode: audio, durable tone 0 or 1, "Telegramm" */
|
||||
int fsk_coeff[2]; /* coefficient k = 2*cos(2*PI*f/samplerate), k << 15 */
|
||||
int samples_per_bit; /* how many samples lasts one bit */
|
||||
int16_t *fsk_filter_spl; /* array with samples_per_bit */
|
||||
int fsk_filter_pos; /* current sample position in filter_spl */
|
||||
int fsk_filter_step; /* number of samples for each analyzation */
|
||||
int fsk_filter_bit; /* last bit, so we detect a bit change */
|
||||
int fsk_filter_sample; /* sample counter for shifting receive bits */
|
||||
uint16_t fsk_filter_telegramm; /* rx shift register for receiveing telegramm */
|
||||
double fsk_filter_quality[16]; /* quality of each bit in telegramm */
|
||||
double fsk_filter_level[16]; /* level of each bit in telegramm */
|
||||
int fsk_filter_qualidx; /* index of quality array above */
|
||||
int tone_detected; /* what tone has been detected */
|
||||
int tone_count; /* how long has that tone been detected */
|
||||
double phaseshift256[2]; /* how much the phase of sine wave changes per sample */
|
||||
double phase256; /* current phase */
|
||||
const char *telegramm; /* current telegramm sequence */
|
||||
int16_t *telegramm_spl; /* 16 * samples_per_bit */
|
||||
int telegramm_pos; /* current sample position in telegramm_spl */
|
||||
|
||||
/* loopback test for latency */
|
||||
int loopback_count; /* count digits from 0 to 9 */
|
||||
double loopback_time[10]; /* time stamp when sending digits 0..9 */
|
||||
} bnetz_t;
|
||||
|
||||
double bnetz_kanal2freq(int kanal, int unterband);
|
||||
int bnetz_init(void);
|
||||
int bnetz_create(const char *sounddev, int samplerate, int kanal, int gfs, int loopback, double loss_volume, const char *pilot);
|
||||
void bnetz_destroy(sender_t *sender);
|
||||
void bnetz_loss_indication(bnetz_t *bnetz);
|
||||
void bnetz_receive_tone(bnetz_t *bnetz, int bit);
|
||||
void bnetz_receive_telegramm(bnetz_t *bnetz, uint16_t telegramm, double quality, double level);
|
||||
const char *bnetz_get_telegramm(bnetz_t *bnetz);
|
||||
|
416
src/bnetz/dsp.c
Normal file
416
src/bnetz/dsp.c
Normal file
@@ -0,0 +1,416 @@
|
||||
/* B-Netz signal processing
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/goertzel.h"
|
||||
#include "bnetz.h"
|
||||
#include "dsp.h"
|
||||
|
||||
#define PI 3.1415927
|
||||
|
||||
/* signalling */
|
||||
#define TX_PEAK 10000.0 /* peak amplitude of sine wave */
|
||||
#define BIT_DURATION 0.010 /* bit length: 10 ms */
|
||||
#define FILTER_STEP 0.001 /* step every 1 ms */
|
||||
#define METERING_HZ 2900 /* metering pulse frequency */
|
||||
|
||||
#define TONE_DETECT_TH 70 /* 70 milliseconds to detect continous tone */
|
||||
|
||||
/* carrier loss detection */
|
||||
#define LOSS_INTERVAL 1000 /* filter steps (milliseconds) for one second interval */
|
||||
#define LOSS_TIME 12 /* duration of signal loss before release */
|
||||
|
||||
/* two signalling tones */
|
||||
static double fsk_bits[2] = {
|
||||
2070.0,
|
||||
1950.0,
|
||||
};
|
||||
|
||||
/* table for fast sine generation */
|
||||
int dsp_sine[256];
|
||||
|
||||
/* global init for FSK */
|
||||
void dsp_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Generating sine table.\n");
|
||||
for (i = 0; i < 256; i++) {
|
||||
dsp_sine[i] = (int)(sin((double)i / 256.0 * 2.0 * PI) * TX_PEAK);
|
||||
}
|
||||
}
|
||||
|
||||
/* Init transceiver instance. */
|
||||
int dsp_init_sender(bnetz_t *bnetz)
|
||||
{
|
||||
double coeff;
|
||||
int16_t *spl;
|
||||
int i;
|
||||
|
||||
if ((bnetz->sender.samplerate % 1000)) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "Samples rate must be a multiple of 1000 bits per second.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Init DSP for 'Sender'.\n");
|
||||
|
||||
audio_init_loss(&bnetz->sender.loss, LOSS_INTERVAL, bnetz->sender.loss_volume, LOSS_TIME);
|
||||
|
||||
bnetz->samples_per_bit = bnetz->sender.samplerate * BIT_DURATION;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per bit duration.\n", bnetz->samples_per_bit);
|
||||
bnetz->fsk_filter_step = bnetz->sender.samplerate * FILTER_STEP;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Using %d samples per filter step.\n", bnetz->fsk_filter_step);
|
||||
spl = calloc(16, bnetz->samples_per_bit << 1);
|
||||
if (!spl) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
bnetz->telegramm_spl = spl;
|
||||
spl = calloc(1, bnetz->samples_per_bit << 1);
|
||||
if (!spl) {
|
||||
PDEBUG(DFSK, DEBUG_ERROR, "No memory!\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
bnetz->fsk_filter_spl = spl;
|
||||
bnetz->fsk_filter_bit = -1;
|
||||
|
||||
bnetz->tone_detected = -1;
|
||||
|
||||
/* count symbols */
|
||||
for (i = 0; i < 2; i++) {
|
||||
coeff = 2.0 * cos(2.0 * PI * fsk_bits[i] / (double)bnetz->sender.samplerate);
|
||||
bnetz->fsk_coeff[i] = coeff * 32768.0;
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "coeff[%d] = %d (must be -3601 and 2573 at 8000hz)\n", i, (int)bnetz->fsk_coeff[i]);
|
||||
|
||||
bnetz->phaseshift256[i] = 256.0 / ((double)bnetz->sender.samplerate / fsk_bits[i]);
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "phaseshift[%d] = %.4f (must be arround 64 at 8000hz)\n", i, bnetz->phaseshift256[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Cleanup transceiver instance. */
|
||||
void dsp_cleanup_sender(bnetz_t *bnetz)
|
||||
{
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Cleanup DSP for 'Sender'.\n");
|
||||
|
||||
if (bnetz->telegramm_spl) {
|
||||
free(bnetz->telegramm_spl);
|
||||
bnetz->telegramm_spl = NULL;
|
||||
}
|
||||
if (bnetz->fsk_filter_spl) {
|
||||
free(bnetz->fsk_filter_spl);
|
||||
bnetz->fsk_filter_spl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Count duration of tone and indicate detection/loss to protocol handler. */
|
||||
static void fsk_receive_tone(bnetz_t *bnetz, int bit, int goodtone, double level)
|
||||
{
|
||||
/* lost tone because it is not good anymore or has changed */
|
||||
if (!goodtone || bit != bnetz->tone_detected) {
|
||||
if (bnetz->tone_count >= TONE_DETECT_TH) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Lost %.0f Hz tone after %d ms.\n", fsk_bits[bnetz->tone_detected], bnetz->tone_count);
|
||||
bnetz_receive_tone(bnetz, -1);
|
||||
}
|
||||
if (goodtone)
|
||||
bnetz->tone_detected = bit;
|
||||
else
|
||||
bnetz->tone_detected = -1;
|
||||
bnetz->tone_count = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bnetz->tone_count++;
|
||||
|
||||
if (bnetz->tone_count >= TONE_DETECT_TH)
|
||||
audio_reset_loss(&bnetz->sender.loss);
|
||||
if (bnetz->tone_count == TONE_DETECT_TH) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Detecting continous %.0f Hz tone. (level = %d%%)\n", fsk_bits[bnetz->tone_detected], (int)(level * 100));
|
||||
bnetz_receive_tone(bnetz, bnetz->tone_detected);
|
||||
}
|
||||
}
|
||||
|
||||
/* Collect 16 data bits (digit) and check for sync marc '01110'. */
|
||||
static void fsk_receive_bit(bnetz_t *bnetz, int bit, double quality, double level)
|
||||
{
|
||||
int i;
|
||||
|
||||
bnetz->fsk_filter_telegramm = (bnetz->fsk_filter_telegramm << 1) | bit;
|
||||
bnetz->fsk_filter_quality[bnetz->fsk_filter_qualidx] = quality;
|
||||
bnetz->fsk_filter_level[bnetz->fsk_filter_qualidx] = level;
|
||||
if (++bnetz->fsk_filter_qualidx == 16)
|
||||
bnetz->fsk_filter_qualidx = 0;
|
||||
|
||||
/* check if pattern 01110xxxxxxxxxxx matches */
|
||||
if ((bnetz->fsk_filter_telegramm & 0xf800) != 0x7000)
|
||||
return;
|
||||
|
||||
/* get worst bit and average level */
|
||||
level = 0;
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (bnetz->fsk_filter_quality[i] < quality)
|
||||
quality = bnetz->fsk_filter_quality[i];
|
||||
level = bnetz->fsk_filter_level[i];
|
||||
}
|
||||
|
||||
/* send telegramm */
|
||||
bnetz_receive_telegramm(bnetz, bnetz->fsk_filter_telegramm, quality, level);
|
||||
}
|
||||
|
||||
char *show_level(int value)
|
||||
{
|
||||
static char text[22];
|
||||
|
||||
value /= 5;
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 20)
|
||||
value = 20;
|
||||
strcpy(text, " ");
|
||||
text[value] = '*';
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
//#define DEBUG_FILTER
|
||||
//#define DEBUG_QUALITY
|
||||
|
||||
/* Filter one chunk of audio an detect tone, quality and loss of signal.
|
||||
* The chunk is a window of 10ms. This window slides over audio stream
|
||||
* and is processed every 1ms. (one step) */
|
||||
void fsk_decode_step(bnetz_t *bnetz, int pos)
|
||||
{
|
||||
double level, result[2], softbit, quality;
|
||||
int max;
|
||||
int16_t *spl;
|
||||
int bit;
|
||||
|
||||
max = bnetz->samples_per_bit;
|
||||
spl = bnetz->fsk_filter_spl;
|
||||
|
||||
level = audio_level(spl, max);
|
||||
|
||||
if (audio_detect_loss(&bnetz->sender.loss, level))
|
||||
bnetz_loss_indication(bnetz);
|
||||
|
||||
audio_goertzel(spl, max, pos, bnetz->fsk_coeff, result, 2);
|
||||
|
||||
/* calculate soft bit from both frequencies */
|
||||
softbit = (result[1] / level - result[0] / level + 1.0) / 2.0;
|
||||
/* scale it, since both filters overlap by some percent */
|
||||
#define MIN_QUALITY 0.08
|
||||
softbit = (softbit - MIN_QUALITY) / (0.850 - MIN_QUALITY - MIN_QUALITY);
|
||||
if (softbit > 1)
|
||||
softbit = 1;
|
||||
if (softbit < 0)
|
||||
softbit = 0;
|
||||
#ifdef DEBUG_FILTER
|
||||
printf("|%s", show_level(result[0]/level*100));
|
||||
printf("|%s| low=%.3f high=%.3f level=%d\n", show_level(result[1]/level*100), result[0]/level, result[1]/level, (int)level);
|
||||
#endif
|
||||
if (softbit > 0.5)
|
||||
bit = 1;
|
||||
else
|
||||
bit = 0;
|
||||
|
||||
// FIXME: better threshold
|
||||
/* adjust level, so we get peak of sine curve */
|
||||
if (level / 0.63 > 0.05 && (softbit > 0.75 || softbit < 0.25)) {
|
||||
fsk_receive_tone(bnetz, bit, 1, level / 0.63662);
|
||||
} else
|
||||
fsk_receive_tone(bnetz, bit, 0, level / 0.63662);
|
||||
|
||||
if (bnetz->fsk_filter_bit != bit) {
|
||||
/* if we have a bit change, reset sample counter to one half bit duration */
|
||||
bnetz->fsk_filter_bit = bit;
|
||||
bnetz->fsk_filter_sample = 5;
|
||||
} else if (--bnetz->fsk_filter_sample == 0) {
|
||||
/* if sample counter bit reaches 0, we reset sample counter to one bit duration */
|
||||
// quality = result[bit] / level;
|
||||
if (softbit > 0.5)
|
||||
quality = softbit * 2.0 - 1.0;
|
||||
else
|
||||
quality = 1.0 - softbit * 2.0;
|
||||
#ifdef DEBUG_QUALITY
|
||||
printf("|%s| quality=%.2f ", show_level(softbit * 100), quality);
|
||||
printf("|%s|\n", show_level(quality * 100));
|
||||
#endif
|
||||
/* adjust level, so we get peak of sine curve */
|
||||
fsk_receive_bit(bnetz, bit, quality, level / 0.63662);
|
||||
bnetz->fsk_filter_sample = 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process received audio stream from radio unit. */
|
||||
void sender_receive(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
int16_t *spl;
|
||||
int max, pos, step;
|
||||
int i;
|
||||
|
||||
/* write received samples to decode buffer */
|
||||
max = bnetz->samples_per_bit;
|
||||
pos = bnetz->fsk_filter_pos;
|
||||
step = bnetz->fsk_filter_step;
|
||||
spl = bnetz->fsk_filter_spl;
|
||||
for (i = 0; i < length; i++) {
|
||||
spl[pos++] = samples[i];
|
||||
if (pos == max)
|
||||
pos = 0;
|
||||
/* if filter step has been reched */
|
||||
if (!(pos % step)) {
|
||||
fsk_decode_step(bnetz, pos);
|
||||
}
|
||||
}
|
||||
bnetz->fsk_filter_pos = pos;
|
||||
|
||||
if (bnetz->dsp_mode == DSP_MODE_AUDIO && bnetz->sender.callref) {
|
||||
int16_t down[length]; /* more than enough */
|
||||
int count;
|
||||
|
||||
count = samplerate_downsample(&bnetz->sender.srstate, samples, length, down);
|
||||
spl = bnetz->sender.rxbuf;
|
||||
pos = bnetz->sender.rxbuf_pos;
|
||||
for (i = 0; i < count; i++) {
|
||||
spl[pos++] = down[i];
|
||||
if (pos == 160) {
|
||||
call_tx_audio(bnetz->sender.callref, spl, 160);
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
bnetz->sender.rxbuf_pos = pos;
|
||||
} else
|
||||
bnetz->sender.rxbuf_pos = 0;
|
||||
}
|
||||
|
||||
static void fsk_tone(bnetz_t *bnetz, int16_t *samples, int length, int tone)
|
||||
{
|
||||
double phaseshift, phase;
|
||||
int i;
|
||||
|
||||
phase = bnetz->phase256;
|
||||
phaseshift = bnetz->phaseshift256[tone];
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
*samples++ = dsp_sine[((uint8_t)phase) & 0xff];
|
||||
phase += phaseshift;
|
||||
if (phase >= 256)
|
||||
phase -= 256;
|
||||
}
|
||||
|
||||
bnetz->phase256 = phase;
|
||||
}
|
||||
|
||||
static int fsk_telegramm(bnetz_t *bnetz, int16_t *samples, int length)
|
||||
{
|
||||
int16_t *spl;
|
||||
int i, j;
|
||||
double phaseshift, phase;
|
||||
int count, max;
|
||||
|
||||
next_telegramm:
|
||||
if (!bnetz->telegramm) {
|
||||
/* request telegramm */
|
||||
// PDEBUG(DFSK, DEBUG_DEBUG, "Request new 'Telegramm'.\n");
|
||||
bnetz->telegramm = bnetz_get_telegramm(bnetz);
|
||||
if (!bnetz->telegramm) {
|
||||
PDEBUG(DFSK, DEBUG_DEBUG, "Stop sending 'Telegramm'.\n");
|
||||
return length;
|
||||
}
|
||||
bnetz->telegramm_pos = 0;
|
||||
spl = bnetz->telegramm_spl;
|
||||
/* render telegramm */
|
||||
phase = bnetz->phase256;
|
||||
for (i = 0; i < 16; i++) {
|
||||
phaseshift = bnetz->phaseshift256[bnetz->telegramm[i] == '1'];
|
||||
for (j = 0; j < bnetz->samples_per_bit; j++) {
|
||||
*spl++ = dsp_sine[((uint8_t)phase) & 0xff];
|
||||
phase += phaseshift;
|
||||
if (phase >= 256)
|
||||
phase -= 256;
|
||||
}
|
||||
}
|
||||
bnetz->phase256 = phase;
|
||||
}
|
||||
|
||||
/* send audio from telegramm */
|
||||
max = bnetz->samples_per_bit * 16;
|
||||
count = max - bnetz->telegramm_pos;
|
||||
if (count > length)
|
||||
count = length;
|
||||
spl = bnetz->telegramm_spl + bnetz->telegramm_pos;
|
||||
for (i = 0; i < count; i++)
|
||||
*samples++ = *spl++;
|
||||
length -= count;
|
||||
bnetz->telegramm_pos += count;
|
||||
/* check for end of telegramm */
|
||||
if (bnetz->telegramm_pos == max) {
|
||||
bnetz->telegramm = NULL;
|
||||
/* we need more ? */
|
||||
if (length)
|
||||
goto next_telegramm;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/* Provide stream of audio toward radio unit */
|
||||
void sender_send(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
bnetz_t *bnetz = (bnetz_t *) sender;
|
||||
int len;
|
||||
|
||||
again:
|
||||
switch (bnetz->dsp_mode) {
|
||||
case DSP_MODE_AUDIO:
|
||||
jitter_load(&bnetz->sender.audio, samples, length);
|
||||
break;
|
||||
case DSP_MODE_SILENCE:
|
||||
memset(samples, 0, length * sizeof(*samples));
|
||||
break;
|
||||
case DSP_MODE_0:
|
||||
fsk_tone(bnetz, samples, length, 0);
|
||||
break;
|
||||
case DSP_MODE_1:
|
||||
fsk_tone(bnetz, samples, length, 1);
|
||||
break;
|
||||
case DSP_MODE_TELEGRAMM:
|
||||
/* Encode telegramm into audio stream. If telegramms have
|
||||
* stopped, process again for rest of stream. */
|
||||
len = fsk_telegramm(bnetz, samples, length);
|
||||
if (len) {
|
||||
samples += length - len;
|
||||
length = len;
|
||||
goto again;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
5
src/bnetz/dsp.h
Normal file
5
src/bnetz/dsp.h
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
void dsp_init(void);
|
||||
int dsp_init_sender(bnetz_t *bnetz);
|
||||
void dsp_cleanup_sender(bnetz_t *bnetz);
|
||||
|
65
src/bnetz/image.c
Normal file
65
src/bnetz/image.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "image.h"
|
||||
|
||||
const char *image[] = {
|
||||
"@g",
|
||||
"",
|
||||
"",
|
||||
" \\",
|
||||
" \\",
|
||||
" \\",
|
||||
" \\_ @wB-NETZ@g",
|
||||
" \\ \\",
|
||||
" \\_\\___",
|
||||
" / __ )",
|
||||
" (__\\ _\\________",
|
||||
" / _______ )",
|
||||
" / / \\/",
|
||||
" / / ______\\___",
|
||||
" / / / )",
|
||||
" (__\\ / / @w~@g",
|
||||
" \\/ ___ /",
|
||||
" / / \\/ @w~@g",
|
||||
" (______\\ \\",
|
||||
" \\ \\",
|
||||
" \\ \\",
|
||||
" \\ \\",
|
||||
" @w~@g \\ \\",
|
||||
" \\ \\",
|
||||
" \\ \\@G (###)@g",
|
||||
" \\ @G(##))########)",
|
||||
" (#)))################(#))",
|
||||
" (#)#(#######)))#################)",
|
||||
" ((#########)#######################)",
|
||||
"@w=========================================================",
|
||||
NULL
|
||||
};
|
||||
|
||||
void print_image(void)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; image[i]; i++) {
|
||||
for (j = 0; j < strlen(image[i]); j++) {
|
||||
if (image[i][j] == '@') {
|
||||
j++;
|
||||
switch(image[i][j]) {
|
||||
case 'g':
|
||||
printf("\033[0;37m");
|
||||
break;
|
||||
case 'w':
|
||||
printf("\033[1;37m");
|
||||
break;
|
||||
case 'G':
|
||||
printf("\033[0;32m");
|
||||
break;
|
||||
}
|
||||
} else
|
||||
printf("%c", image[i][j]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
printf("\033[0;39m");
|
||||
}
|
||||
|
3
src/bnetz/image.h
Normal file
3
src/bnetz/image.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void print_image(void);
|
||||
|
190
src/bnetz/main.c
Normal file
190
src/bnetz/main.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/* B-Netz main
|
||||
*
|
||||
* (C) 2016 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 <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/call.h"
|
||||
#include "../common/mncc_sock.h"
|
||||
#include "../common/main.h"
|
||||
#include "bnetz.h"
|
||||
#include "dsp.h"
|
||||
#include "image.h"
|
||||
#include "ansage-27.h"
|
||||
|
||||
int gfs = 2;
|
||||
const char *pilot = "tone";
|
||||
|
||||
void print_help(const char *arg0)
|
||||
{
|
||||
print_help_common(arg0);
|
||||
/* - - */
|
||||
printf(" -g --gfs <gruppenfreisignal>\n");
|
||||
printf(" Gruppenfreisignal\" 1..9 | 19 | 10..18 (default = '%d')\n", gfs);
|
||||
printf(" -P --pilot tone | positive | negative | <file>=<on>:<off>\n");
|
||||
printf(" Send a tone, give a signal or write to a file when switching to\n");
|
||||
printf(" channel 19. (paging the phone).\n");
|
||||
printf(" 'tone', 'positive', 'negative' is sent on right audio channel.\n");
|
||||
printf(" 'tone' sends a tone whenever channel 19 is switchted.\n");
|
||||
printf(" 'positive' sends a positive signal for channel 19, else negative.\n");
|
||||
printf(" 'negative' sends a negative signal for channel 19, else positive.\n");
|
||||
printf(" Example: /sys/class/gpio/gpio17/value=1:0 writes a '1' to\n");
|
||||
printf(" /sys/class/gpio/gpio17/value to switching to channel 19 and a '0' to\n");
|
||||
printf(" switch back. (default = %s)\n", pilot);
|
||||
printf("\nstation-id: Give 5 digit station-id, you don't need to enter it for every\n");
|
||||
printf(" start of this program.\n");
|
||||
}
|
||||
|
||||
static int handle_options(int argc, char **argv)
|
||||
{
|
||||
int skip_args = 0;
|
||||
|
||||
static struct option long_options_special[] = {
|
||||
{"gfs", 1, 0, 'g'},
|
||||
{"pilot", 1, 0, 'P'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
set_options_common("g:P:", long_options_special);
|
||||
|
||||
while (1) {
|
||||
int option_index = 0, c;
|
||||
|
||||
c = getopt_long(argc, argv, optstring, long_options, &option_index);
|
||||
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'g':
|
||||
gfs = atoi(optarg);
|
||||
skip_args += 2;
|
||||
break;
|
||||
case 'P':
|
||||
pilot = strdup(optarg);
|
||||
skip_args += 2;
|
||||
break;
|
||||
default:
|
||||
opt_switch_common(c, argv[0], &skip_args);
|
||||
}
|
||||
}
|
||||
|
||||
return skip_args;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int rc;
|
||||
int skip_args;
|
||||
const char *station_id = "";
|
||||
|
||||
skip_args = handle_options(argc, argv);
|
||||
argc -= skip_args;
|
||||
argv += skip_args;
|
||||
|
||||
if (argc > 1) {
|
||||
station_id = argv[1];
|
||||
if (strlen(station_id) != 5) {
|
||||
printf("Given station ID '%s' does not have 5 digits\n", station_id);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!kanal) {
|
||||
printf("No channel (\"Kanal\") is specified, I suggest channel 1.\n\n");
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!loopback)
|
||||
print_image();
|
||||
|
||||
/* init functions */
|
||||
if (use_mncc_sock) {
|
||||
rc = mncc_init("/tmp/bsc_mncc");
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to setup MNCC socket. Quitting!\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
init_ansage_27();
|
||||
dsp_init();
|
||||
bnetz_init();
|
||||
rc = call_init(station_id, call_sounddev, samplerate, latency, loopback);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create call control instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* create transceiver instance */
|
||||
rc = bnetz_create(sounddev, samplerate, kanal, gfs, loopback, (double)lossdetect / 100.0, pilot);
|
||||
if (rc < 0) {
|
||||
fprintf(stderr, "Failed to create \"Sender\" instance. Quitting!\n");
|
||||
goto fail;
|
||||
}
|
||||
printf("Base station ready, please tune transmitter to %.3f MHz and receiver "
|
||||
"to %.3f MHz.\n", bnetz_kanal2freq(kanal, 0),
|
||||
bnetz_kanal2freq(kanal, 1));
|
||||
printf("To call phone, switch transmitter (using pilot signal) to %.3f MHz.\n", bnetz_kanal2freq(19, 0));
|
||||
|
||||
signal(SIGINT,sighandler);
|
||||
signal(SIGHUP,sighandler);
|
||||
signal(SIGTERM,sighandler);
|
||||
signal(SIGPIPE,sighandler);
|
||||
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
int rc;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = rt_prio;
|
||||
rc = sched_setscheduler(0, SCHED_RR, &schedp);
|
||||
if (rc)
|
||||
fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio);
|
||||
}
|
||||
|
||||
main_loop(&quit, latency);
|
||||
|
||||
if (rt_prio > 0) {
|
||||
struct sched_param schedp;
|
||||
|
||||
memset(&schedp, 0, sizeof(schedp));
|
||||
schedp.sched_priority = 0;
|
||||
sched_setscheduler(0, SCHED_OTHER, &schedp);
|
||||
}
|
||||
|
||||
fail:
|
||||
/* cleanup functions */
|
||||
call_cleanup();
|
||||
if (use_mncc_sock)
|
||||
mncc_exit();
|
||||
|
||||
/* destroy transceiver instance */
|
||||
while(sender_head)
|
||||
bnetz_destroy(sender_head);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
21
src/common/Makefile.am
Normal file
21
src/common/Makefile.am
Normal file
@@ -0,0 +1,21 @@
|
||||
AM_CPPFLAGS = -Wall -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libcommon.a
|
||||
|
||||
libcommon_a_SOURCES = \
|
||||
../common/debug.c \
|
||||
../common/timer.c \
|
||||
../common/sound_alsa.c \
|
||||
../common/goertzel.c \
|
||||
../common/jitter.c \
|
||||
../common/loss.c \
|
||||
../common/filter.c \
|
||||
../common/samplerate.c \
|
||||
../common/call.c \
|
||||
../common/freiton.c \
|
||||
../common/besetztton.c \
|
||||
../common/mncc_sock.c \
|
||||
../common/cause.c \
|
||||
../common/sender.c \
|
||||
../common/main_common.c
|
||||
|
185
src/common/besetztton.c
Normal file
185
src/common/besetztton.c
Normal file
@@ -0,0 +1,185 @@
|
||||
#include <stdint.h>
|
||||
|
||||
static int16_t pattern[] = {
|
||||
0x0004, 0xffe9, 0xffc9, 0xffac, 0xff92, 0xff83, 0xff75, 0xff56,
|
||||
0xff40, 0xff2b, 0xff25, 0xff2b, 0xff1f, 0xff1b, 0xff32, 0xff6a,
|
||||
0xffcb, 0x00a2, 0x01cb, 0x02e2, 0x0373, 0x0369, 0x030a, 0x0268,
|
||||
0x01a9, 0x00d9, 0xffc2, 0xfeb4, 0xfddc, 0xfd4f, 0xfce3, 0xfc8d,
|
||||
0xfc6c, 0xfcb2, 0xfd1c, 0xfd99, 0xfe2b, 0xff27, 0x000a, 0x013e,
|
||||
0x030f, 0x036f, 0x0341, 0x0374, 0x03c1, 0x03c7, 0x02f2, 0x01b9,
|
||||
0x00e3, 0x004f, 0xff8f, 0xfe87, 0xfda0, 0xfd40, 0xfd32, 0xfd49,
|
||||
0xfd2a, 0xfd3d, 0xfda1, 0xfe3a, 0xfee4, 0xff6d, 0x0002, 0x00b9,
|
||||
0x0145, 0x01db, 0x0241, 0x025b, 0x0262, 0x025f, 0x01bf, 0x0169,
|
||||
0x0134, 0x0085, 0x0028, 0xffd7, 0xfef3, 0xfe9c, 0xfec7, 0xfe45,
|
||||
0xfe45, 0xfe59, 0xfe55, 0xfec3, 0xff2a, 0xffc8, 0xffe0, 0x006a,
|
||||
0x0162, 0x00c2, 0x010b, 0x0179, 0x015e, 0x00f2, 0x007f, 0x009e,
|
||||
0x0072, 0x00ab, 0xffb6, 0xff70, 0x003a, 0xff02, 0xfedf, 0xff4c,
|
||||
0xff22, 0xff0e, 0xfeb9, 0xff3d, 0xff62, 0xff8c, 0x00ce, 0xffd2,
|
||||
0xff9f, 0x017b, 0x013d, 0x00f3, 0x012e, 0x004e, 0x0081, 0x0077,
|
||||
0xffba, 0xffe5, 0x002b, 0x008c, 0xffc9, 0xff4d, 0xffc5, 0x0022,
|
||||
0x00d0, 0xfff4, 0xff82, 0xff9e, 0xff50, 0xff50, 0xfeb7, 0xff82,
|
||||
0x0051, 0xffcb, 0xffa0, 0xfff2, 0x0050, 0xffc1, 0x004a, 0x023c,
|
||||
0x012d, 0x0018, 0x0161, 0x0095, 0x0135, 0x0014, 0xff45, 0x0011,
|
||||
0x0117, 0x0072, 0xfc89, 0x0370, 0x03a0, 0x05a0, 0x01f7, 0x0967,
|
||||
0x1822, 0xf2cb, 0xe4e2, 0xe57f, 0xd776, 0xe9f6, 0xf5bb, 0xfc27,
|
||||
0x1595, 0x23e7, 0x21f5, 0x1753, 0x1a08, 0x26eb, 0x1a72, 0x0c7c,
|
||||
0x001e, 0xe797, 0xd9df, 0xda14, 0xdd93, 0xd899, 0xd4c1, 0xe25e,
|
||||
0xe7ba, 0xf67b, 0x1d50, 0x2cbd, 0x2940, 0x24e7, 0x277f, 0x2ba5,
|
||||
0x2361, 0x2053, 0x0e46, 0xe7d1, 0xd654, 0xd9fa, 0xdda9, 0xd6b0,
|
||||
0xd1d3, 0xdaa2, 0xde9e, 0xeed9, 0x1663, 0x2855, 0x2644, 0x2489,
|
||||
0x2303, 0x2583, 0x2102, 0x1de4, 0x0c20, 0xea89, 0xdd31, 0xe287,
|
||||
0xe743, 0xdaf7, 0xd314, 0xde3c, 0xe481, 0xf661, 0x1c52, 0x288b,
|
||||
0x1b7a, 0x1844, 0x23b5, 0x2a05, 0x2239, 0x169f, 0x04a7, 0xea9d,
|
||||
0xdb4c, 0xdcca, 0xe12d, 0xdb72, 0xd4d4, 0xdea4, 0xe6d7, 0xf747,
|
||||
0x1ae7, 0x2b19, 0x2582, 0x2091, 0x273c, 0x2a1e, 0x1d7e, 0x17df,
|
||||
0x0837, 0xe881, 0xdb4e, 0xdf81, 0xdf33, 0xd6c6, 0xd54e, 0xdcb5,
|
||||
0xe1f3, 0xf890, 0x1bbe, 0x2868, 0x227e, 0x1c92, 0x260d, 0x2b62,
|
||||
0x2082, 0x1a91, 0x0864, 0xe75f, 0xd979, 0xdf72, 0xe1f7, 0xd799,
|
||||
0xd499, 0xdd37, 0xe310, 0xfc1c, 0x2085, 0x2966, 0x1f41, 0x1b8d,
|
||||
0x259a, 0x2a43, 0x22d9, 0x192a, 0xffe0, 0xe26e, 0xdaad, 0xe16d,
|
||||
0xe1bb, 0xd62c, 0xd3d4, 0xddcf, 0xe53c, 0x008a, 0x2315, 0x28ae,
|
||||
0x219d, 0x1f99, 0x2835, 0x28bf, 0x2124, 0x1baa, 0xffe2, 0xde80,
|
||||
0xd886, 0xe110, 0xdf02, 0xd453, 0xd50b, 0xdbea, 0xe5a8, 0x064d,
|
||||
0x242b, 0x2902, 0x2376, 0x200d, 0x2819, 0x282d, 0x21ad, 0x1879,
|
||||
0xf9d0, 0xdcc7, 0xd869, 0xdfc9, 0xde60, 0xd496, 0xd62e, 0xdc40,
|
||||
0xe92b, 0x0bb1, 0x2631, 0x2704, 0x20b7, 0x21fe, 0x286d, 0x25fc,
|
||||
0x20a7, 0x1461, 0xf346, 0xda0b, 0xdbb7, 0xe190, 0xdaf5, 0xd396,
|
||||
0xdbd2, 0xdf54, 0xeba7, 0x1426, 0x29ee, 0x26ed, 0x2160, 0x2335,
|
||||
0x297c, 0x25a5, 0x1eaf, 0x0ce9, 0xec69, 0xd750, 0xd9ca, 0xe073,
|
||||
0xd9ac, 0xd345, 0xd994, 0xe07d, 0xf5ce, 0x1a91, 0x2a2b, 0x25bf,
|
||||
0x20a4, 0x2596, 0x29c6, 0x2377, 0x1c64, 0x064d, 0xe5e6, 0xd8bf,
|
||||
0xde68, 0xe1b8, 0xd81f, 0xd360, 0xda7f, 0xe288, 0xfc65, 0x1f51,
|
||||
0x294f, 0x2256, 0x1eea, 0x2641, 0x28fe, 0x24dd, 0x1b81, 0xfe12,
|
||||
0xe0ca, 0xda89, 0xe0a1, 0xe057, 0xd67e, 0xd5c4, 0xdba1, 0xe31a,
|
||||
0x0556, 0x266b, 0x28f2, 0x218b, 0x1f44, 0x2717, 0x27c7, 0x21d9,
|
||||
0x15c9, 0xf747, 0xdd97, 0xda9b, 0xe23c, 0xdf46, 0xd419, 0xd634,
|
||||
0xdd0d, 0xe9eb, 0x0c96, 0x25e2, 0x2644, 0x1fea, 0x2041, 0x28d1,
|
||||
0x26c1, 0x20e0, 0x164b, 0xf5db, 0xdba5, 0xdafe, 0xe1c5, 0xddad,
|
||||
0xd3c4, 0xd6ee, 0xdb8c, 0xeb0b, 0x1395, 0x294b, 0x24c4, 0x1ee4,
|
||||
0x228c, 0x29a9, 0x2581, 0x2093, 0x0fad, 0xed42, 0xdad5, 0xddeb,
|
||||
0xe34b, 0xdc1a, 0xd323, 0xd912, 0xdde6, 0xf057, 0x1964, 0x2aea,
|
||||
0x234d, 0x1bfe, 0x2106, 0x29f9, 0x25e5, 0x20eb, 0x0e12, 0xe980,
|
||||
0xd698, 0xdcad, 0xe4c9, 0xdb65, 0xd2a4, 0xd913, 0xde59, 0xf3b4,
|
||||
0x1a85, 0x29cb, 0x24bc, 0x1fab, 0x2359, 0x29a8, 0x244a, 0x1e3e,
|
||||
0x0bac, 0xe9bf, 0xd8b8, 0xdc3e, 0xe1a6, 0xda33, 0xd318, 0xd8ac,
|
||||
0xdd33, 0xf4ec, 0x1d03, 0x2a42, 0x2527, 0x1fac, 0x2303, 0x2a52,
|
||||
0x25db, 0x2117, 0x0bf0, 0xe6e8, 0xd6d7, 0xdd56, 0xe3e0, 0xda45,
|
||||
0xd268, 0xd8aa, 0xdcb1, 0xf2f1, 0x1bac, 0x2a58, 0x24ed, 0x1f57,
|
||||
0x22fb, 0x2a21, 0x2582, 0x2095, 0x0bbc, 0xe7b8, 0xd84e, 0xdd09,
|
||||
0xe20c, 0xdaea, 0xd3a3, 0xd9fe, 0xdef1, 0xf1fb, 0x1996, 0x29f6,
|
||||
0x250b, 0x1f6c, 0x228c, 0x2a58, 0x263e, 0x20f1, 0x0d9f, 0xe85d,
|
||||
0xd74b, 0xdd4e, 0xe27f, 0xdb5f, 0xd335, 0xd7e2, 0xdd4f, 0xf294,
|
||||
0x1a85, 0x2978, 0x24e8, 0x2044, 0x22bd, 0x2a07, 0x2698, 0x20da,
|
||||
0x0b78, 0xe654, 0xd795, 0xde86, 0xe30d, 0xdbd1, 0xd447, 0xd9c3,
|
||||
0xe030, 0xf3d3, 0x18e2, 0x28c7, 0x2497, 0x1f3a, 0x2259, 0x28fa,
|
||||
0x2565, 0x1f47, 0x09e5, 0xe7ef, 0xd93b, 0xdd93, 0xe2cd, 0xdb76,
|
||||
0xd2a9, 0xd8eb, 0xdfe5, 0xf38a, 0x1a10, 0x29f4, 0x2439, 0x1e81,
|
||||
0x22ff, 0x2a74, 0x25eb, 0x20c6, 0x0bd4, 0xe7ba, 0xd781, 0xdcf1,
|
||||
0xe451, 0xdc04, 0xd2a4, 0xda99, 0xe0ff, 0xf41a, 0x1999, 0x28b7,
|
||||
0x2477, 0x1d6e, 0x20f7, 0x2a37, 0x2552, 0x1f54, 0x0ab8, 0xe772,
|
||||
0xd8a3, 0xdd16, 0xe1a4, 0xd9bd, 0xd2e9, 0xda7c, 0xe02a, 0xf636,
|
||||
0x1d2e, 0x2a1a, 0x24c8, 0x1f54, 0x237b, 0x2a4c, 0x24f2, 0x1e74,
|
||||
0x07b6, 0xe5a7, 0xd903, 0xddb4, 0xe0fa, 0xd993, 0xd42a, 0xdb0b,
|
||||
0xe0db, 0xf862, 0x1e38, 0x292b, 0x22f0, 0x1eb2, 0x244f, 0x299d,
|
||||
0x24b2, 0x1e8b, 0x061b, 0xe3a7, 0xd7d0, 0xddc3, 0xe133, 0xd8b8,
|
||||
0xd486, 0xda97, 0xe027, 0xfd4c, 0x21e4, 0x28be, 0x224f, 0x1e61,
|
||||
0x25ab, 0x2a10, 0x24a1, 0x1c33, 0xffb1, 0xe073, 0xda34, 0xe103,
|
||||
0xe143, 0xd6b3, 0xd4d6, 0xdabf, 0xe278, 0x050a, 0x2559, 0x2724,
|
||||
0x206c, 0x1d8a, 0x2623, 0x28db, 0x222f, 0x1869, 0xfb0c, 0xdebe,
|
||||
0xda88, 0xe128, 0xdfc6, 0xd4e6, 0xd695, 0xde27, 0xe839, 0x0b4a,
|
||||
0x26b5, 0x25f1, 0x1f74, 0x2018, 0x28ea, 0x270e, 0x1fb1, 0x14a9,
|
||||
0xf54d, 0xda7d, 0xda17, 0xe1c9, 0xde39, 0xd508, 0xd7f5, 0xddaa,
|
||||
0xec1c, 0x1297, 0x2932, 0x2584, 0x1fa5, 0x215e, 0x298f, 0x2724,
|
||||
0x20c7, 0x109e, 0xed5e, 0xd7c0, 0xdb2f, 0xe1bb, 0xdb69, 0xd2df,
|
||||
0xd833, 0xdec4, 0xf22c, 0x18dc, 0x2a35, 0x251b, 0x1f3f, 0x22a6,
|
||||
0x2a07, 0x2615, 0x1feb, 0x0d08, 0xe995, 0xd820, 0xdd3a, 0xe150,
|
||||
0xd98f, 0xd300, 0xda0b, 0xdfb7, 0xf414, 0x1b1b, 0x295b, 0x2333,
|
||||
0x1e69, 0x23db, 0x2a7e, 0x2537, 0x1fd9, 0x09db, 0xe659, 0xd871,
|
||||
0xdede, 0xe3c8, 0xd989, 0xd253, 0xd979, 0xdf1c, 0xf7d6, 0x1ec3,
|
||||
0x2a2e, 0x21e5, 0x1c2f, 0x2436, 0x2a4f, 0x241a, 0x1d05, 0x0548,
|
||||
0xe44d, 0xd8ae, 0xe0d5, 0xe4c8, 0xd84f, 0xd298, 0xdb09, 0xe0bc,
|
||||
0xfc25, 0x22aa, 0x2a75, 0x2218, 0x1e12, 0x2681, 0x2aa6, 0x2457,
|
||||
0x1d91, 0x0159, 0xdf33, 0xd67b, 0xdf48, 0xe226, 0xd631, 0xd376,
|
||||
0xdb34, 0xe091, 0xfe8f, 0x241d, 0x2a87, 0x2332, 0x1f86, 0x2611,
|
||||
0x28fe, 0x23d2, 0x1ef8, 0x02cd, 0xdefa, 0xd5ef, 0xddf6, 0xe12f,
|
||||
0xd735, 0xd3f5, 0xdb0c, 0xdfb9, 0xfd66, 0x248b, 0x2bca, 0x241a,
|
||||
0x1ef6, 0x25b8, 0x2a2a, 0x246a, 0x1eaa, 0x0248, 0xdf8e, 0xd747,
|
||||
0xde8f, 0xe1e0, 0xd827, 0xd3cf, 0xda8e, 0xe007, 0xfb3b, 0x219f,
|
||||
0x2ac7, 0x231d, 0x1ea4, 0x255e, 0x2a2e, 0x2489, 0x1eeb, 0x05a9,
|
||||
0xe2e8, 0xd805, 0xdeb4, 0xe283, 0xd8b3, 0xd2f3, 0xd9dd, 0xdf63,
|
||||
0xf8d6, 0x1fce, 0x2a7b, 0x237f, 0x1f85, 0x2509, 0x29a5, 0x24ec,
|
||||
0x1f8d, 0x06e5, 0xe2c8, 0xd692, 0xddac, 0xe1d4, 0xd909, 0xd37a,
|
||||
0xd98a, 0xdf3f, 0xf7fd, 0x1eac, 0x2ac7, 0x2486, 0x1ff2, 0x24a9,
|
||||
0x2a5f, 0x256b, 0x1ebc, 0x075f, 0xe45a, 0xd75e, 0xde5a, 0xe284,
|
||||
0xd97f, 0xd3be, 0xd9c8, 0xdf75, 0xf6cd, 0x1c9d, 0x2996, 0x23e3,
|
||||
0x1f19, 0x24a0, 0x2a81, 0x25aa, 0x1fd7, 0x0766, 0xe44d, 0xd870,
|
||||
0xdf09, 0xe30d, 0xd922, 0xd249, 0xd985, 0xdf20, 0xf54d, 0x1c2b,
|
||||
0x29cb, 0x2445, 0x1f54, 0x241e, 0x2aa5, 0x25c2, 0x1ff3, 0x093b,
|
||||
0xe5e2, 0xd837, 0xde46, 0xe316, 0xda28, 0xd2d7, 0xd90b, 0xdecc,
|
||||
0xf4d6, 0x1bff, 0x2ae4, 0x24c1, 0x1ed5, 0x237a, 0x2a79, 0x25e4,
|
||||
0x1f1b, 0x08a0, 0xe65e, 0xd8c0, 0xde21, 0xe22f, 0xd9ce, 0xd2fa,
|
||||
0xd8e6, 0xdf57, 0xf53f, 0x1c1f, 0x2a52, 0x2463, 0x204f, 0x2433,
|
||||
0x2a69, 0x2613, 0x1f9c, 0x0920, 0xe4b9, 0xd761, 0xde2f, 0xe19a,
|
||||
0xd93c, 0xd275, 0xd912, 0xdfbc, 0xf698, 0x1dbe, 0x2a7f, 0x248e,
|
||||
0x20dd, 0x253c, 0x2a69, 0x2548, 0x1ef1, 0x07b5, 0xe3c2, 0xd798,
|
||||
0xdda0, 0xdf93, 0xd788, 0xd3d0, 0xdbfd, 0xe14e, 0xf8b2, 0x1ec1,
|
||||
0x2a28, 0x22bf, 0x1f01, 0x25ff, 0x29be, 0x23e3, 0x1c79, 0x035e,
|
||||
0xe2e4, 0xd967, 0xdfab, 0xe0b6, 0xd72f, 0xd40b, 0xdb3c, 0xe221,
|
||||
0xfebc, 0x2357, 0x29b3, 0x215e, 0x1f1a, 0x2713, 0x2984, 0x2351,
|
||||
0x1b42, 0xfecf, 0xde84, 0xd924, 0xe111, 0xe055, 0xd556, 0xd45e,
|
||||
0xdc5f, 0xe47f, 0x04ac, 0x2566, 0x2835, 0x2184, 0x2118, 0x2895,
|
||||
0x28ad, 0x22d1, 0x17ec, 0xf7d3, 0xdc2b, 0xdaa9, 0xe14f, 0xde61,
|
||||
0xd468, 0xd5c2, 0xdca4, 0xe7da, 0x0b3d, 0x27bf, 0x2744, 0x211e,
|
||||
0x21d8, 0x2890, 0x26a2, 0x216a, 0x136c, 0xf187, 0xd894, 0xdca4,
|
||||
0xe06d, 0xde67, 0xdf37, 0xd7a6, 0xda60, 0xeb63, 0x0fcd, 0x2907,
|
||||
0x25e0, 0x1fb7, 0x20d5, 0x29a4, 0x26be, 0x207d, 0x1143, 0xeecc,
|
||||
0xd932, 0xdb55, 0xe1fc, 0xdcc3, 0xd396, 0xd79d, 0xde85, 0xeec3,
|
||||
0x1396, 0x2913, 0x25da, 0x1fe6, 0x2297, 0x2a00, 0x262c, 0x1f8f,
|
||||
0x0ed2, 0xed2b, 0xd99d, 0xdcca, 0xe269, 0xdbbc, 0xd288, 0xd7ab,
|
||||
0xdeb8, 0xf0ac, 0x178c, 0x29fa, 0x249a, 0x1e38, 0x2246, 0x2abe,
|
||||
0x258f, 0x1dee, 0x0a53, 0xe9a5, 0xda49, 0xde75, 0xe440, 0xdc8d,
|
||||
0xd2fd, 0xd93e, 0xe1aa, 0xf49a, 0x19c7, 0x2989, 0x23c5, 0x1f8b,
|
||||
0x22b2, 0x2901, 0x2421, 0x1cf0, 0x099c, 0xe7f4, 0xd968, 0xde88,
|
||||
0xe307, 0xdb95, 0xd2cf, 0xd945, 0xe0e2, 0xf583, 0x1c2e, 0x2a3a,
|
||||
0x23c5, 0x1dac, 0x216a, 0x2a2d, 0x2614, 0x1ea8, 0x08a7, 0xe64c,
|
||||
0xd801, 0xdeec, 0xe5b9, 0xdd89, 0xd1a0, 0xd4b1, 0xdbc0, 0xe7dc,
|
||||
0x0d71, 0x28a8, 0x2779, 0x238a, 0x232b, 0x290c, 0x2975, 0x23b3,
|
||||
0x160e, 0xf5a1, 0xd923, 0xd7ba, 0xe01a, 0xddc9, 0xd493, 0xd64a,
|
||||
0xe11e, 0xe5fe, 0x0b8d, 0x296e, 0x23b4, 0x2cbc, 0x250a, 0x0cd4,
|
||||
0x0257, 0x0762, 0x0e5f, 0x0a2a, 0xfb3d, 0xf1ea, 0xf764, 0xf951,
|
||||
0xf4be, 0xee32, 0xed9c, 0xf596, 0xf92e, 0xf969, 0xf9ee, 0xfc48,
|
||||
0x01a3, 0x057f, 0x05cb, 0x04a3, 0x06c8, 0x0a5d, 0x0ae3, 0x0932,
|
||||
0x06a8, 0x03f7, 0x039e, 0x028a, 0xffd3, 0xfeeb, 0xfdcb, 0xfb6c,
|
||||
0xfa68, 0xfa7b, 0xfaf0, 0xfbfb, 0xfc13, 0xfb6b, 0xfb93, 0xfc5a,
|
||||
0xfd8a, 0x008d, 0x0256, 0x0152, 0x013b, 0x025e, 0x0359, 0x0474,
|
||||
0x0505, 0x0495, 0x0441, 0x0288, 0x0092, 0x00f9, 0x011c, 0xff6b,
|
||||
0xfe78, 0xfe18, 0xfc7f, 0xfb42, 0xfca7, 0xfd32, 0xfb8c, 0xfbeb,
|
||||
0xfd9c, 0xff71, 0x0130, 0x020d, 0x02b3, 0x028c, 0x01d8, 0x033a,
|
||||
0x0476, 0x03e1, 0x029b, 0x01cb, 0x0119, 0x00d2, 0x0165, 0x0025,
|
||||
0xfe8a, 0xfc11, 0xf9f2, 0xfab0, 0xfaa0, 0xfa9a, 0xfce0, 0xfe08,
|
||||
0xfe75, 0x0105, 0x0337, 0x03ea, 0x0434, 0x03ef, 0x03b0, 0x0404,
|
||||
0x042f, 0x03c3, 0x0305, 0x00ef, 0xff59, 0xfeee, 0xfe4a, 0xfdb8,
|
||||
0xfc9e, 0xfc3c, 0xfc73, 0xfcc2, 0xfd07, 0xfcb8, 0xfd99, 0xff0c,
|
||||
0xffbe, 0x0064, 0x00f3, 0x012d, 0x0277, 0x034f, 0x028f, 0x028b,
|
||||
0x02e7, 0x0231, 0x01b5, 0x0148, 0x0138, 0x00fc, 0x0033, 0x0007,
|
||||
0xfe94, 0xfd82, 0xfda5, 0xfd78, 0xfd0f, 0xfd61, 0xfdfa, 0xfe98,
|
||||
0xff23, 0xff85, 0x0009, 0x00b8, 0x0156, 0x0102, 0x0258, 0x0278,
|
||||
0x01ba, 0x023a, 0x00fd, 0x00d5, 0x009d, 0xff5b, 0xff67, 0xff49,
|
||||
0xff35, 0xffdb, 0xff7a, 0xff15, 0xff30, 0xfee0, 0xfe9a, 0xfebe,
|
||||
0xff20, 0xff76, 0x0071, 0x00a5, 0xffef, 0x0034, 0x00c3, 0x01ce,
|
||||
0x0174, 0x0035, 0x0078, 0x007c, 0x00aa, 0x0081, 0xfffd, 0xfff1,
|
||||
0xff44, 0xfec8, 0xff2b, 0xff81, 0xffa6, 0x002a, 0x0010, 0xff90,
|
||||
0xff80, 0x0044, 0x0012, 0xffa7, 0x0060, 0x0007, 0xffd9, 0x0079,
|
||||
0x008f, 0x0099, 0x001c, 0xffa8, 0xff93, 0xff52, 0xff9a, 0x0060,
|
||||
};
|
||||
|
||||
extern int16_t *besetztton_spl;
|
||||
extern int besetztton_size;
|
||||
|
||||
void init_besetzton(void)
|
||||
{
|
||||
besetztton_spl = pattern;
|
||||
besetztton_size = sizeof(pattern) / sizeof(pattern[0]);
|
||||
}
|
||||
|
||||
|
3
src/common/besetztton.h
Normal file
3
src/common/besetztton.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void init_besetzton(void);
|
||||
|
920
src/common/call.c
Normal file
920
src/common/call.c
Normal file
@@ -0,0 +1,920 @@
|
||||
/* built-in call control
|
||||
*
|
||||
* (C) 2016 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 <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
#include <termios.h>
|
||||
#include "../common/debug.h"
|
||||
#include "../common/sender.h"
|
||||
#include "cause.h"
|
||||
#include "call.h"
|
||||
#include "mncc_sock.h"
|
||||
#include "freiton.h"
|
||||
#include "besetztton.h"
|
||||
|
||||
extern int use_mncc_sock;
|
||||
extern int send_patterns;
|
||||
|
||||
/* stream patterns/announcements */
|
||||
int16_t *ansage_27_spl = NULL;
|
||||
int16_t *freiton_spl = NULL;
|
||||
int16_t *besetztton_spl = NULL;
|
||||
int ansage_27_size = 0;
|
||||
int freiton_size = 0;
|
||||
int besetztton_size = 0;
|
||||
|
||||
enum call_state {
|
||||
CALL_IDLE = 0,
|
||||
CALL_SETUP_MO,
|
||||
CALL_SETUP_MT,
|
||||
CALL_ALERTING,
|
||||
CALL_CONNECT,
|
||||
CALL_DISCONNECTED,
|
||||
};
|
||||
|
||||
enum audio_pattern {
|
||||
PATTERN_NONE = 0,
|
||||
PATTERN_RINGBACK,
|
||||
PATTERN_BUSY,
|
||||
PATTERN_OUTOFORDER,
|
||||
};
|
||||
|
||||
static int new_callref = 0; /* toward mobile */
|
||||
|
||||
/* built in call instance */
|
||||
typedef struct call {
|
||||
int callref;
|
||||
enum call_state state;
|
||||
int disc_cause; /* cause that has been sent by transceiver instance for release */
|
||||
char station_id[6];
|
||||
char dialing[16];
|
||||
void *sound; /* headphone interface */
|
||||
int latspl; /* sample latency at sound interface */
|
||||
samplerate_t srstate; /* patterns/announcement upsampling */
|
||||
jitter_t audio; /* headphone audio dejittering */
|
||||
int audio_pos; /* position when playing patterns */
|
||||
int loopback; /* loopback test for echo */
|
||||
} call_t;
|
||||
|
||||
static call_t call;
|
||||
|
||||
static void call_new_state(enum call_state state)
|
||||
{
|
||||
call.state = state;
|
||||
call.audio_pos = 0;
|
||||
}
|
||||
|
||||
static void get_call_patterns(int16_t *samples, int length, enum audio_pattern pattern)
|
||||
{
|
||||
const int16_t *spl = NULL;
|
||||
int size = 0, max = 0, pos;
|
||||
|
||||
switch (pattern) {
|
||||
case PATTERN_RINGBACK:
|
||||
spl = freiton_spl;
|
||||
size = freiton_size;
|
||||
max = 8 * 5000;
|
||||
break;
|
||||
case PATTERN_BUSY:
|
||||
busy:
|
||||
spl = besetztton_spl;
|
||||
size = besetztton_size;
|
||||
max = 8 * 750;
|
||||
break;
|
||||
case PATTERN_OUTOFORDER:
|
||||
spl = ansage_27_spl;
|
||||
size = ansage_27_size;
|
||||
if (!spl || !size)
|
||||
goto busy;
|
||||
max = size;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
/* stream sample */
|
||||
pos = call.audio_pos;
|
||||
while(length--) {
|
||||
if (pos >= size)
|
||||
*samples++ = 0;
|
||||
else
|
||||
*samples++ = spl[pos] >> 1;
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
}
|
||||
call.audio_pos = pos;
|
||||
}
|
||||
|
||||
static enum audio_pattern cause2pattern(int cause)
|
||||
{
|
||||
int pattern;
|
||||
|
||||
switch (cause) {
|
||||
case CAUSE_OUTOFORDER:
|
||||
pattern = PATTERN_OUTOFORDER;
|
||||
break;
|
||||
default:
|
||||
pattern = PATTERN_BUSY;
|
||||
}
|
||||
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/* MNCC call instance */
|
||||
typedef struct process {
|
||||
struct process *next;
|
||||
int callref;
|
||||
enum call_state state;
|
||||
int audio_disconnected; /* if not associated with transceiver anymore */
|
||||
enum audio_pattern pattern;
|
||||
int audio_pos;
|
||||
} process_t;
|
||||
|
||||
static process_t *process_head = NULL;
|
||||
|
||||
static void create_process(int callref, int state)
|
||||
{
|
||||
process_t *process;
|
||||
|
||||
process = calloc(sizeof(*process), 1);
|
||||
if (!process) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "No memory!\n");
|
||||
abort();
|
||||
}
|
||||
process->next = process_head;
|
||||
process_head = process;
|
||||
|
||||
process->callref = callref;
|
||||
process->state = state;
|
||||
}
|
||||
|
||||
static void destroy_process(int callref)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
process_t **process_p = &process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref) {
|
||||
*process_p = process->next;
|
||||
free(process);
|
||||
return;
|
||||
}
|
||||
process_p = &process->next;
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
|
||||
}
|
||||
|
||||
static int is_process(int callref)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref)
|
||||
return 1;
|
||||
process = process->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum call_state is_process_state(int callref)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref)
|
||||
return process->state;
|
||||
process = process->next;
|
||||
}
|
||||
return CALL_IDLE;
|
||||
}
|
||||
|
||||
static void set_state_process(int callref, enum call_state state)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref) {
|
||||
process->state = state;
|
||||
return;
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
|
||||
}
|
||||
|
||||
static void set_pattern_process(int callref, enum audio_pattern pattern)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref) {
|
||||
process->pattern = pattern;
|
||||
process->audio_pos = 0;
|
||||
return;
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
|
||||
}
|
||||
|
||||
/* disconnect audio, now send audio directly from pattern/announcement, not from transceiver */
|
||||
static void disconnect_process(int callref, int cause)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref) {
|
||||
process->pattern = cause2pattern(cause);
|
||||
process->audio_disconnected = 1;
|
||||
process->audio_pos = 0;
|
||||
return;
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Process with callref 0x%x not found!\n", callref);
|
||||
}
|
||||
|
||||
/* check if audio is disconnected */
|
||||
static int is_process_disconnected(int callref)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref) {
|
||||
return process->audio_disconnected;
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Process with callref 0x%x not found, this is ok!\n", callref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check if pattern is set, so we send patterns and announcements */
|
||||
static int is_process_pattern(int callref)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
|
||||
while (process) {
|
||||
if (process->callref == callref) {
|
||||
return (process->pattern != PATTERN_NONE);
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Process with callref 0x%x not found, this is ok!\n", callref);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void get_process_patterns(process_t *process, int16_t *samples, int length)
|
||||
{
|
||||
const int16_t *spl = NULL;
|
||||
int size = 0, max = 0, pos;
|
||||
|
||||
switch (process->pattern) {
|
||||
case PATTERN_RINGBACK:
|
||||
spl = freiton_spl;
|
||||
size = freiton_size;
|
||||
max = 8 * 5000;
|
||||
break;
|
||||
case PATTERN_BUSY:
|
||||
spl = besetztton_spl;
|
||||
size = besetztton_size;
|
||||
max = 8 * 750;
|
||||
break;
|
||||
case PATTERN_OUTOFORDER:
|
||||
spl = ansage_27_spl;
|
||||
size = ansage_27_size;
|
||||
max = size;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
/* stream sample */
|
||||
pos = process->audio_pos;
|
||||
while(length--) {
|
||||
if (pos >= size)
|
||||
*samples++ = 0;
|
||||
else
|
||||
*samples++ = spl[pos] >> 1;
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
}
|
||||
process->audio_pos = pos;
|
||||
}
|
||||
|
||||
static struct termios term_orig;
|
||||
|
||||
int call_init(const char *station_id, const char *sounddev, int samplerate, int latency, int loopback)
|
||||
{
|
||||
struct termios term;
|
||||
int rc = 0;
|
||||
|
||||
/* init common tones */
|
||||
init_freiton();
|
||||
init_besetzton();
|
||||
|
||||
if (use_mncc_sock)
|
||||
return 0;
|
||||
|
||||
if (!loopback) {
|
||||
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);
|
||||
}
|
||||
|
||||
memset(&call, 0, sizeof(call));
|
||||
strncpy(call.station_id, station_id, sizeof(call.station_id) - 1);
|
||||
call.latspl = latency * samplerate / 1000;
|
||||
call.loopback = loopback;
|
||||
|
||||
if (!sounddev[0])
|
||||
return 0;
|
||||
|
||||
/* open sound device for call control */
|
||||
call.sound = sound_open(sounddev, samplerate);
|
||||
if (!call.sound) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n");
|
||||
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = init_samplerate(&call.srstate, samplerate);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&call.audio, samplerate / 5);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
call_cleanup();
|
||||
return rc;
|
||||
}
|
||||
|
||||
void call_cleanup(void)
|
||||
{
|
||||
if (use_mncc_sock)
|
||||
return;
|
||||
if (!call.loopback)
|
||||
tcsetattr(0, TCSANOW, &term_orig);
|
||||
|
||||
/* close sound devoice */
|
||||
if (call.sound)
|
||||
sound_close(call.sound);
|
||||
|
||||
jitter_destroy(&call.audio);
|
||||
|
||||
if (process_head) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Not all MNCC instances have been released!\n");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int process_ui(void)
|
||||
{
|
||||
int c;
|
||||
|
||||
c = get_char();
|
||||
|
||||
/* break */
|
||||
if (c == 3)
|
||||
return 1;
|
||||
|
||||
switch (call.state) {
|
||||
case CALL_IDLE:
|
||||
if (c > 0) {
|
||||
if (c >= '0' && c <= '9' && strlen(call.station_id) < 5) {
|
||||
call.station_id[strlen(call.station_id) + 1] = '\0';
|
||||
call.station_id[strlen(call.station_id)] = c;
|
||||
}
|
||||
if ((c == 8 || c == 127) && strlen(call.station_id))
|
||||
call.station_id[strlen(call.station_id) - 1] = '\0';
|
||||
if (c == 'd' && strlen(call.station_id) == 5) {
|
||||
int rc;
|
||||
int callref = ++new_callref;
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Outgoing call to %s\n", call.station_id);
|
||||
call.dialing[0] = '\0';
|
||||
call_new_state(CALL_SETUP_MT);
|
||||
call.callref = callref;
|
||||
rc = call_out_setup(callref, call.station_id);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc);
|
||||
call_new_state(CALL_DISCONNECTED);
|
||||
call.callref = 0;
|
||||
call.disc_cause = -rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("on-hook: %s%s (enter 0..9 or d=dial)\r", call.station_id, "....." + strlen(call.station_id));
|
||||
break;
|
||||
case CALL_SETUP_MO:
|
||||
case CALL_SETUP_MT:
|
||||
case CALL_ALERTING:
|
||||
case CALL_CONNECT:
|
||||
case CALL_DISCONNECTED:
|
||||
if (c > 0) {
|
||||
if (c == 'h') {
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call hangup\n");
|
||||
call_new_state(CALL_IDLE);
|
||||
if (call.callref) {
|
||||
call_out_release(call.callref, CAUSE_NORMAL);
|
||||
call.callref = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.state == CALL_SETUP_MT)
|
||||
printf("call setup: %s (enter h=hangup)\r", call.station_id);
|
||||
if (call.state == CALL_ALERTING)
|
||||
printf("call ringing: %s (enter h=hangup)\r", call.station_id);
|
||||
if (call.state == CALL_CONNECT) {
|
||||
if (call.dialing[0])
|
||||
printf("call active: %s->%s (enter h=hangup)\r", call.station_id, call.dialing);
|
||||
else
|
||||
printf("call active: %s (enter h=hangup)\r", call.station_id);
|
||||
}
|
||||
if (call.state == CALL_DISCONNECTED)
|
||||
printf("call disconnected: %s (enter h=hangup)\r", cause_name(call.disc_cause));
|
||||
break;
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* get keys from keyboad to control call via console
|
||||
* returns 1 on exit (ctrl+c) */
|
||||
int process_call(void)
|
||||
{
|
||||
if (use_mncc_sock) {
|
||||
mncc_handle();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!call.loopback) {
|
||||
if (process_ui())
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!call.sound)
|
||||
return 0;
|
||||
/* handle audio, if sound device is used */
|
||||
|
||||
int16_t samples[call.latspl];
|
||||
int count;
|
||||
int rc;
|
||||
|
||||
count = sound_get_inbuffer(call.sound);
|
||||
if (count < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count);
|
||||
if (count == -EPIPE)
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n");
|
||||
return 0;
|
||||
}
|
||||
if (count < call.latspl) {
|
||||
int16_t up[count];
|
||||
count = call.latspl - count;
|
||||
switch(call.state) {
|
||||
case CALL_ALERTING:
|
||||
count = count / call.srstate.factor;
|
||||
get_call_patterns(samples, count, PATTERN_RINGBACK);
|
||||
count = samplerate_upsample(&call.srstate, samples, count, up);
|
||||
/* prevent click after hangup */
|
||||
jitter_clear(&call.audio);
|
||||
break;
|
||||
case CALL_DISCONNECTED:
|
||||
count = count / call.srstate.factor;
|
||||
get_call_patterns(samples, count, cause2pattern(call.disc_cause));
|
||||
count = samplerate_upsample(&call.srstate, samples, count, up);
|
||||
/* prevent click after hangup */
|
||||
jitter_clear(&call.audio);
|
||||
break;
|
||||
default:
|
||||
jitter_load(&call.audio, up, count);
|
||||
}
|
||||
rc = sound_write(call.sound, up, up, count);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to sound device (rc = %d)\n", rc);
|
||||
if (rc == -EPIPE)
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
count = sound_read(call.sound, samples, call.latspl);
|
||||
if (count < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", count);
|
||||
if (count == -EPIPE)
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover.\n");
|
||||
return 0;
|
||||
}
|
||||
if (count) {
|
||||
int16_t down[count]; /* more than enough */
|
||||
|
||||
if (call.loopback == 3)
|
||||
jitter_save(&call.audio, samples, count);
|
||||
count = samplerate_downsample(&call.srstate, samples, count, down);
|
||||
call_rx_audio(call.callref, down, count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Setup is received from transceiver. */
|
||||
int call_in_setup(int callref, const char *callerid, const char *dialing)
|
||||
{
|
||||
if (callref < 0x4000000) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "Invalid callref from mobile station, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (!strcmp(dialing, "0"))
|
||||
dialing = "operator";
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Incomming call from '%s' to '%s'\n", callerid, dialing);
|
||||
|
||||
if (use_mncc_sock) {
|
||||
uint8_t buf[sizeof(struct gsm_mncc)];
|
||||
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
|
||||
int rc;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
mncc->msg_type = MNCC_SETUP_IND;
|
||||
mncc->callref = callref;
|
||||
mncc->fields |= MNCC_F_CALLING;
|
||||
strncpy(mncc->calling.number, callerid, sizeof(mncc->calling.number) - 1);
|
||||
mncc->calling.type = 4; /* caller ID is of type 'subscriber' */
|
||||
mncc->fields |= MNCC_F_CALLED;
|
||||
strncpy(mncc->called.number, dialing, sizeof(mncc->called.number) - 1);
|
||||
mncc->called.type = 0; /* dialing is of type 'unknown' */
|
||||
mncc->lchan_type = GSM_LCHAN_TCH_F;
|
||||
mncc->fields |= MNCC_F_BEARER_CAP;
|
||||
mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ;
|
||||
mncc->bearer_cap.speech_ver[1] = -1;
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Sending MNCC call towards Network\n");
|
||||
|
||||
create_process(callref, CALL_SETUP_MO);
|
||||
|
||||
rc = mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "We have no MNCC connection, rejecting.\n");
|
||||
destroy_process(callref);
|
||||
return -CAUSE_TEMPFAIL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* setup is also allowed on disconnected call */
|
||||
if (call.state == CALL_DISCONNECTED)
|
||||
call_new_state(CALL_IDLE);
|
||||
if (call.state != CALL_IDLE) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "We are busy, rejecting.\n");
|
||||
return -CAUSE_BUSY;
|
||||
}
|
||||
call.callref = callref;
|
||||
call_new_state(CALL_CONNECT);
|
||||
if (callerid[0]) {
|
||||
strncpy(call.station_id, callerid, 5);
|
||||
call.station_id[5] = '\0';
|
||||
}
|
||||
strncpy(call.dialing, dialing, sizeof(call.dialing) - 1);
|
||||
call.dialing[sizeof(call.dialing) - 1] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Transceiver indicates alerting. */
|
||||
void call_in_alerting(int callref)
|
||||
{
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call is alerting\n");
|
||||
|
||||
if (use_mncc_sock) {
|
||||
uint8_t buf[sizeof(struct gsm_mncc)];
|
||||
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
|
||||
|
||||
if (!send_patterns) {
|
||||
memset(buf, 0, sizeof(buf));
|
||||
mncc->msg_type = MNCC_ALERT_IND;
|
||||
mncc->callref = callref;
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Indicate MNCC alerting towards Network\n");
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
} else
|
||||
set_pattern_process(callref, PATTERN_RINGBACK);
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.callref != callref) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n");
|
||||
call_out_release(callref, CAUSE_INVALCALLREF);
|
||||
return;
|
||||
}
|
||||
call_new_state(CALL_ALERTING);
|
||||
}
|
||||
|
||||
/* Transceiver indicates answer. */
|
||||
static void _indicate_answer(int callref, const char *connectid)
|
||||
{
|
||||
uint8_t buf[sizeof(struct gsm_mncc)];
|
||||
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
mncc->msg_type = MNCC_SETUP_CNF;
|
||||
mncc->callref = callref;
|
||||
mncc->fields |= MNCC_F_CONNECTED;
|
||||
strncpy(mncc->connected.number, connectid, sizeof(mncc->connected.number) - 1);
|
||||
mncc->connected.type = 0;
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Indicate MNCC answer towards Network\n");
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
}
|
||||
void call_in_answer(int callref, const char *connectid)
|
||||
{
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call has been answered by '%s'\n", connectid);
|
||||
|
||||
if (use_mncc_sock) {
|
||||
_indicate_answer(callref, connectid);
|
||||
set_pattern_process(callref, PATTERN_NONE);
|
||||
set_state_process(callref, CALL_CONNECT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.callref != callref) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n");
|
||||
call_out_release(callref, CAUSE_INVALCALLREF);
|
||||
return;
|
||||
}
|
||||
call_new_state(CALL_CONNECT);
|
||||
strncpy(call.station_id, connectid, 5);
|
||||
call.station_id[5] = '\0';
|
||||
}
|
||||
|
||||
/* Transceiver indicates release. */
|
||||
void call_in_release(int callref, int cause)
|
||||
{
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call has been released with cause=%d\n", cause);
|
||||
|
||||
if (use_mncc_sock) {
|
||||
uint8_t buf[sizeof(struct gsm_mncc)];
|
||||
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
mncc->msg_type = MNCC_REL_IND;
|
||||
mncc->callref = callref;
|
||||
mncc->fields |= MNCC_F_CAUSE;
|
||||
mncc->cause.location = 1; /* private local */
|
||||
mncc->cause.value = cause;
|
||||
|
||||
if (is_process(callref)) {
|
||||
if (!send_patterns
|
||||
|| is_process_state(callref) == CALL_DISCONNECTED
|
||||
|| is_process_state(callref) == CALL_SETUP_MO) {
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n");
|
||||
destroy_process(callref);
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
} else {
|
||||
disconnect_process(callref, cause);
|
||||
}
|
||||
} else {
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n");
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.callref != callref) {
|
||||
PDEBUG(DCALL, DEBUG_ERROR, "invalid call ref.\n");
|
||||
/* don't send release, because caller already released */
|
||||
return;
|
||||
}
|
||||
call_new_state(CALL_DISCONNECTED);
|
||||
call.callref = 0;
|
||||
call.disc_cause = cause;
|
||||
}
|
||||
|
||||
/* forward audio to MNCC or call instance */
|
||||
void call_tx_audio(int callref, int16_t *samples, int count)
|
||||
{
|
||||
|
||||
if (use_mncc_sock) {
|
||||
uint8_t buf[sizeof(struct gsm_data_frame) + count * sizeof(int16_t)];
|
||||
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
|
||||
|
||||
/* if we are disconnected, ignore audio */
|
||||
if (is_process_pattern(callref))
|
||||
return;
|
||||
|
||||
/* forward audio */
|
||||
data->msg_type = ANALOG_8000HZ;
|
||||
data->callref = callref;
|
||||
memcpy(data->data, samples, count * sizeof(int16_t));
|
||||
|
||||
mncc_write(buf, sizeof(buf));
|
||||
return;
|
||||
}
|
||||
|
||||
/* save audio from transceiver to jitter buffer */
|
||||
if (call.sound) {
|
||||
int16_t up[count * call.srstate.factor];
|
||||
count = samplerate_upsample(&call.srstate, samples, count, up);
|
||||
jitter_save(&call.audio, up, count);
|
||||
}
|
||||
}
|
||||
|
||||
/* clock that is used to transmit patterns */
|
||||
void call_mncc_clock(void)
|
||||
{
|
||||
process_t *process = process_head;
|
||||
uint8_t buf[sizeof(struct gsm_data_frame) + 160 * sizeof(int16_t)];
|
||||
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
|
||||
|
||||
while(process) {
|
||||
if (process->pattern != PATTERN_NONE) {
|
||||
data->msg_type = ANALOG_8000HZ;
|
||||
data->callref = process->callref;
|
||||
/* try to get patterns, else copy the samples we got */
|
||||
get_process_patterns(process, (int16_t *)data->data, 160);
|
||||
mncc_write(buf, sizeof(buf));
|
||||
}
|
||||
process = process->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* mncc messages received from network */
|
||||
void call_mncc_recv(uint8_t *buf, int length)
|
||||
{
|
||||
struct gsm_mncc *mncc = (struct gsm_mncc *)buf;
|
||||
char number[sizeof(mncc->called.number)];
|
||||
int callref;
|
||||
int rc;
|
||||
|
||||
if (mncc->msg_type == ANALOG_8000HZ) {
|
||||
struct gsm_data_frame *data = (struct gsm_data_frame *)buf;
|
||||
int count = (length - sizeof(struct gsm_data_frame)) / 2;
|
||||
/* if we are disconnected, ignore audio */
|
||||
if (is_process_pattern(data->callref))
|
||||
return;
|
||||
call_rx_audio(data->callref, (int16_t *)data->data, count);
|
||||
return;
|
||||
}
|
||||
|
||||
callref = mncc->callref;
|
||||
strcpy(number, mncc->called.number);
|
||||
|
||||
if (is_process_disconnected(callref)) {
|
||||
switch(mncc->msg_type) {
|
||||
case MNCC_DISC_REQ:
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC disconnect from Network with cause %d\n", mncc->cause.value);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected, releasing!\n");
|
||||
|
||||
destroy_process(callref);
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Releasing MNCC call towards Network\n");
|
||||
mncc->msg_type = MNCC_REL_IND;
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
break;
|
||||
case MNCC_REL_REQ:
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC release from Network with cause %d\n", mncc->cause.value);
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released\n");
|
||||
|
||||
destroy_process(callref);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch(mncc->msg_type) {
|
||||
case MNCC_SETUP_REQ:
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC call from Network to '%s'\n", mncc->called.number);
|
||||
|
||||
if (mncc->callref >= 0x4000000) {
|
||||
fprintf(stderr, "Invalid callref from network, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Confirming MNCC call to Network\n");
|
||||
memset(buf, 0, length);
|
||||
mncc->msg_type = MNCC_CALL_CONF_IND;
|
||||
mncc->callref = callref;
|
||||
mncc->lchan_type = GSM_LCHAN_TCH_F;
|
||||
mncc->fields |= MNCC_F_BEARER_CAP;
|
||||
mncc->bearer_cap.speech_ver[0] = BCAP_ANALOG_8000HZ;
|
||||
mncc->bearer_cap.speech_ver[1] = -1;
|
||||
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Outgoing call from to '%s'\n", number);
|
||||
|
||||
create_process(callref, CALL_SETUP_MT);
|
||||
|
||||
rc = call_out_setup(callref, number);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DCALL, DEBUG_NOTICE, "Call rejected, cause %d\n", -rc);
|
||||
if (send_patterns) {
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
|
||||
_indicate_answer(callref, number);
|
||||
disconnect_process(callref, -rc);
|
||||
break;
|
||||
}
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Rejecting MNCC call towards Network (cause=%d)\n", -rc);
|
||||
memset(buf, 0, length);
|
||||
mncc->msg_type = MNCC_REL_IND;
|
||||
mncc->callref = callref;
|
||||
mncc->fields |= MNCC_F_CAUSE;
|
||||
mncc->cause.location = 1; /* private local */
|
||||
mncc->cause.value = -rc;
|
||||
mncc_write(buf, sizeof(struct gsm_mncc));
|
||||
destroy_process(callref);
|
||||
break;
|
||||
}
|
||||
|
||||
if (send_patterns) {
|
||||
PDEBUG(DCALL, DEBUG_DEBUG, "Early connecting after setup\n");
|
||||
set_state_process(callref, CALL_CONNECT);
|
||||
_indicate_answer(callref, number);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case MNCC_SETUP_RSP:
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC answer from Network\n");
|
||||
set_state_process(callref, CALL_CONNECT);
|
||||
break;
|
||||
case MNCC_DISC_REQ:
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC disconnect from Network with cause %d\n", mncc->cause.value);
|
||||
|
||||
set_state_process(callref, CALL_DISCONNECTED);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call disconnected\n");
|
||||
call_out_disconnect(callref, mncc->cause.value);
|
||||
break;
|
||||
case MNCC_REL_REQ:
|
||||
PDEBUG(DMNCC, DEBUG_INFO, "Received MNCC release from Network with cause %d\n", mncc->cause.value);
|
||||
|
||||
destroy_process(callref);
|
||||
PDEBUG(DCALL, DEBUG_INFO, "Call released\n");
|
||||
call_out_release(callref, mncc->cause.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* break down of MNCC socket */
|
||||
void call_mncc_flush(void)
|
||||
{
|
||||
while(process_head) {
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket closed, releasing call\n");
|
||||
call_out_release(process_head->callref, CAUSE_TEMPFAIL);
|
||||
destroy_process(process_head->callref);
|
||||
/* note: callref is released by sender's instance */
|
||||
}
|
||||
}
|
||||
|
26
src/common/call.h
Normal file
26
src/common/call.h
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
int call_init(const char *station_id, const char *sounddev, int samplerate, int latency, int loopback);
|
||||
void call_cleanup(void);
|
||||
int process_call(void);
|
||||
|
||||
/* received messages */
|
||||
int call_in_setup(int callref, const char *callerid, const char *dialing);
|
||||
void call_in_alerting(int callref);
|
||||
void call_in_answer(int callref, const char *connecid);
|
||||
void call_in_release(int callref, int cause);
|
||||
|
||||
/* send messages */
|
||||
int call_out_setup(int callref, char *dialing);
|
||||
void call_out_disconnect(int callref, int cause);
|
||||
void call_out_release(int callref, int cause);
|
||||
|
||||
/* send and receive audio */
|
||||
void call_rx_audio(int callref, int16_t *samples, int count);
|
||||
void call_tx_audio(int callref, int16_t *samples, int count);
|
||||
|
||||
/* receive from mncc */
|
||||
void call_mncc_recv(uint8_t *buf, int length);
|
||||
void call_mncc_flush(void);
|
||||
/* clock to transmit to */
|
||||
void call_mncc_clock(void);
|
||||
|
48
src/common/cause.c
Normal file
48
src/common/cause.c
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Clear cause names
|
||||
*
|
||||
* (C) 2016 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 "cause.h"
|
||||
|
||||
const char *cause_name(int cause)
|
||||
{
|
||||
static char cause_str[16];
|
||||
|
||||
switch (cause) {
|
||||
case CAUSE_NORMAL:
|
||||
return "hangup";
|
||||
case CAUSE_BUSY:
|
||||
return "busy";
|
||||
case CAUSE_NOANSWER:
|
||||
return "no-answer";
|
||||
case CAUSE_OUTOFORDER:
|
||||
return "out-of-order";
|
||||
case CAUSE_INVALNUMBER:
|
||||
return "invalid-number";
|
||||
case CAUSE_NOCHANNEL:
|
||||
return "no-channel";
|
||||
case CAUSE_TEMPFAIL:
|
||||
return "link-failure";
|
||||
default:
|
||||
sprintf(cause_str, "cause=%d\n", cause);
|
||||
return cause_str;
|
||||
}
|
||||
|
||||
}
|
||||
|
12
src/common/cause.h
Normal file
12
src/common/cause.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#define CAUSE_NORMAL 16
|
||||
#define CAUSE_BUSY 17
|
||||
#define CAUSE_NOANSWER 19
|
||||
#define CAUSE_OUTOFORDER 27
|
||||
#define CAUSE_INVALNUMBER 28
|
||||
#define CAUSE_NOCHANNEL 34
|
||||
#define CAUSE_TEMPFAIL 41
|
||||
#define CAUSE_INVALCALLREF 81
|
||||
|
||||
const char *cause_name(int cause);
|
||||
|
||||
|
69
src/common/debug.c
Normal file
69
src/common/debug.c
Normal file
@@ -0,0 +1,69 @@
|
||||
/* Simple debug functions for level and category filtering
|
||||
*
|
||||
* (C) 2016 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 <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "debug.h"
|
||||
|
||||
const char *debug_level[] = {
|
||||
"debug ",
|
||||
"info ",
|
||||
"notice ",
|
||||
"error ",
|
||||
};
|
||||
|
||||
struct debug_cat {
|
||||
const char *name;
|
||||
const char *color;
|
||||
} debug_cat[] = {
|
||||
{ "sender", "\033[1;33m" },
|
||||
{ "sound", "\033[0;35m" },
|
||||
{ "fsk", "\033[0;31m" },
|
||||
{ "audio", "\033[0;31m" },
|
||||
{ "anetz", "\033[1;34m" },
|
||||
{ "bnetz", "\033[1;34m" },
|
||||
{ "call", "\033[1;37m" },
|
||||
{ "mncc", "\033[1;32m" },
|
||||
};
|
||||
|
||||
int debuglevel = DEBUG_INFO;
|
||||
|
||||
void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *fmt, ...)
|
||||
{
|
||||
char buffer[4096];
|
||||
const char *p;
|
||||
va_list args;
|
||||
|
||||
if (debuglevel > level)
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer) - 1, fmt, args);
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
va_end(args);
|
||||
|
||||
while ((p = strchr(file, '/')))
|
||||
file = p + 1;
|
||||
|
||||
// printf("%s%s:%d %s() %s: %s\033[0;39m", debug_cat[cat].color, file, line, function, debug_level[level], buffer);
|
||||
printf("%s%s:%d %s: %s\033[0;39m", debug_cat[cat].color, file, line, debug_level[level], buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
20
src/common/debug.h
Normal file
20
src/common/debug.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#define DEBUG_DEBUG 0 /* debug info, not for normal use */
|
||||
#define DEBUG_INFO 1 /* all info about process */
|
||||
#define DEBUG_NOTICE 2 /* something unexpected happens */
|
||||
#define DEBUG_ERROR 3 /* there is an error with this software */
|
||||
|
||||
#define DSENDER 0
|
||||
#define DSOUND 1
|
||||
#define DFSK 2
|
||||
#define DAUDIO 3
|
||||
#define DANETZ 4
|
||||
#define DBNETZ 5
|
||||
#define DCALL 6
|
||||
#define DMNCC 7
|
||||
|
||||
#define PDEBUG(cat, level, fmt, arg...) _printdebug(__FILE__, __FUNCTION__, __LINE__, cat, level, fmt, ## arg)
|
||||
void _printdebug(const char *file, const char *function, int line, int cat, int level, const char *fmt, ...);
|
||||
|
||||
extern int debuglevel;
|
||||
|
81
src/common/filter.c
Normal file
81
src/common/filter.c
Normal file
@@ -0,0 +1,81 @@
|
||||
/* cut-off filter (biquad) based on Nigel Redmon (www.earlevel.com)
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "filter.h"
|
||||
|
||||
#define PI M_PI
|
||||
|
||||
//#define CASCADE
|
||||
|
||||
void biquad_init(biquad_low_pass_t *bq, double frequency, int samplerate)
|
||||
{
|
||||
double Fc, Q, K, norm;
|
||||
|
||||
memset(bq, 0, sizeof(*bq));
|
||||
Q = sqrt(0.5); /* 0.7071... */
|
||||
Fc = frequency / (double)samplerate;
|
||||
K = tan(PI * Fc);
|
||||
norm = 1 / (1 + K / Q + K * K);
|
||||
bq->a0 = K * K * norm;
|
||||
bq->a1 = 2 * bq->a0;
|
||||
bq->a2 = bq->a0;
|
||||
bq->b1 = 2 * (K * K - 1) * norm;
|
||||
bq->b2 = (1 - K / Q + K * K) * norm;
|
||||
}
|
||||
|
||||
void biquad_process(biquad_low_pass_t *bq, double *samples, int length, int iterations)
|
||||
{
|
||||
double a0, a1, a2, b1, b2;
|
||||
double *z1, *z2;
|
||||
double in, out;
|
||||
int i, j;
|
||||
|
||||
if (iterations > 10) {
|
||||
fprintf(stderr, "%s failed: too many iterations, please fix!\n", __func__);
|
||||
abort();
|
||||
}
|
||||
|
||||
/* get states */
|
||||
a0 = bq->a0;
|
||||
a1 = bq->a1;
|
||||
a2 = bq->a2;
|
||||
b1 = bq->b1;
|
||||
b2 = bq->b2;
|
||||
|
||||
z1 = bq->z1;
|
||||
z2 = bq->z2;
|
||||
|
||||
/* process filter */
|
||||
for (i = 0; i < length; i++) {
|
||||
in = *samples;
|
||||
for (j = 0; j < iterations; j++) {
|
||||
out = in * a0 + z1[j];
|
||||
z1[j] = in * a1 + z2[j] - b1 * out;
|
||||
z2[j] = in * a2 - b2 * out;
|
||||
in = out;
|
||||
}
|
||||
*samples++ = in;
|
||||
}
|
||||
}
|
||||
|
9
src/common/filter.h
Normal file
9
src/common/filter.h
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
typedef struct biquad_low_pass {
|
||||
double a0, a1, a2, b1, b2;
|
||||
double z1[10], z2[10];
|
||||
} biquad_low_pass_t;
|
||||
|
||||
void biquad_init(biquad_low_pass_t *bq, double frequency, int samplerate);
|
||||
void biquad_process(biquad_low_pass_t *bq, double *samples, int length, int iterations);
|
||||
|
1073
src/common/freiton.c
Normal file
1073
src/common/freiton.c
Normal file
File diff suppressed because it is too large
Load Diff
3
src/common/freiton.h
Normal file
3
src/common/freiton.h
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
void init_freiton(void);
|
||||
|
100
src/common/goertzel.c
Normal file
100
src/common/goertzel.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/* Goertzel functions
|
||||
*
|
||||
* (C) 2016 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../common/debug.h"
|
||||
#include "goertzel.h"
|
||||
|
||||
/*
|
||||
* audio level calculation
|
||||
*/
|
||||
|
||||
/* return average value (rectified value), that can be 0..1 */
|
||||
double audio_level(int16_t *samples, int length)
|
||||
{
|
||||
int bias;
|
||||
double level;
|
||||
int sk;
|
||||
int n;
|
||||
|
||||
/* level calculation */
|
||||
bias = 0;
|
||||
for (n = 0; n < length; n++)
|
||||
bias += samples[n];
|
||||
bias = bias / length;
|
||||
|
||||
level = 0;
|
||||
for (n = 0; n < length; n++) {
|
||||
sk = samples[n] - bias;
|
||||
if (sk < 0)
|
||||
level -= (double)sk;
|
||||
if (sk > 0)
|
||||
level += (double)sk;
|
||||
}
|
||||
level = level / (double)length / 32767.0;
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/*
|
||||
* goertzel filter
|
||||
*/
|
||||
|
||||
/* filter frequencies and return their levels
|
||||
*
|
||||
* samples: pointer to sample buffer
|
||||
* length: length of buffer
|
||||
* offset: for ring buffer, start here and wrap arround to 0 when length has been hit
|
||||
* coeff: array of coefficients (coeff << 15)
|
||||
* result: array of result levels (average value of the sine, that is 1 / (PI/2) of the sine's peak)
|
||||
* k: number of frequencies to check
|
||||
*/
|
||||
void audio_goertzel(int16_t *samples, int length, int offset, int *coeff, double *result, int k)
|
||||
{
|
||||
int32_t sk, sk1, sk2;
|
||||
int64_t cos2pik;
|
||||
int i, n;
|
||||
|
||||
/* we do goertzel */
|
||||
for (i = 0; i < k; i++) {
|
||||
sk = 0;
|
||||
sk1 = 0;
|
||||
sk2 = 0;
|
||||
cos2pik = coeff[i];
|
||||
/* note: after 'length' cycles, offset is restored to its initial value */
|
||||
for (n = 0; n < length; n++) {
|
||||
sk = ((cos2pik * sk1) >> 15) - sk2 + samples[offset++];
|
||||
sk2 = sk1;
|
||||
sk1 = sk;
|
||||
if (offset == length)
|
||||
offset = 0;
|
||||
}
|
||||
/* compute level of signal */
|
||||
result[i] = sqrt(
|
||||
((double)sk * (double)sk) -
|
||||
((double)((cos2pik * sk) >> 15) * (double)sk2) +
|
||||
((double)sk2 * (double)sk2)
|
||||
) / (double)length / 32767.0 * 2.0 * 0.63662; /* 1 / (PI/2) */
|
||||
}
|
||||
}
|
||||
|
5
src/common/goertzel.h
Normal file
5
src/common/goertzel.h
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
double audio_level(int16_t *samples, int length);
|
||||
|
||||
void audio_goertzel(int16_t *samples, int length, int offset, int *coeff, double *result, int k);
|
||||
|
114
src/common/jitter.c
Normal file
114
src/common/jitter.c
Normal file
@@ -0,0 +1,114 @@
|
||||
/* Jitter buffering functions
|
||||
*
|
||||
* (C) 2016 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../common/debug.h"
|
||||
#include "jitter.h"
|
||||
|
||||
/* create jitter buffer */
|
||||
int jitter_create(jitter_t *jitter, int length)
|
||||
{
|
||||
memset(jitter, 0, sizeof(jitter));
|
||||
jitter->spl = calloc(length * sizeof(int16_t), 1);
|
||||
if (!jitter->spl) {
|
||||
PDEBUG(DAUDIO, DEBUG_ERROR, "No memory for jitter buffer.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
jitter->len = length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void jitter_destroy(jitter_t *jitter)
|
||||
{
|
||||
if (jitter->spl) {
|
||||
free(jitter->spl);
|
||||
jitter->spl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* store audio in jitterbuffer
|
||||
*
|
||||
* stop if buffer is completely filled
|
||||
*/
|
||||
void jitter_save(jitter_t *jb, int16_t *samples, int length)
|
||||
{
|
||||
int16_t *spl;
|
||||
int inptr, outptr, len, space;
|
||||
int i;
|
||||
|
||||
spl = jb->spl;
|
||||
inptr = jb->inptr;
|
||||
outptr = jb->outptr;
|
||||
len = jb->len;
|
||||
space = (outptr - inptr + len - 1) % len;
|
||||
|
||||
if (space < length)
|
||||
length = space;
|
||||
for (i = 0; i < length; i++) {
|
||||
spl[inptr++] = *samples++;
|
||||
if (inptr == len)
|
||||
inptr = 0;
|
||||
}
|
||||
|
||||
jb->inptr = inptr;
|
||||
}
|
||||
|
||||
/* get audio from jitterbuffer
|
||||
*/
|
||||
void jitter_load(jitter_t *jb, int16_t *samples, int length)
|
||||
{
|
||||
int16_t *spl;
|
||||
int inptr, outptr, len, fill;
|
||||
int i, ii;
|
||||
|
||||
spl = jb->spl;
|
||||
inptr = jb->inptr;
|
||||
outptr = jb->outptr;
|
||||
len = jb->len;
|
||||
fill = (inptr - outptr + len) % len;
|
||||
|
||||
if (fill < length)
|
||||
ii = fill;
|
||||
else
|
||||
ii = length;
|
||||
|
||||
/* fill what we got */
|
||||
for (i = 0; i < ii; i++) {
|
||||
*samples++ = spl[outptr++];
|
||||
if (outptr == len)
|
||||
outptr = 0;
|
||||
}
|
||||
/* on underrun, fill with silence */
|
||||
for (; i < length; i++) {
|
||||
*samples++ = 0;
|
||||
}
|
||||
|
||||
jb->outptr = outptr;
|
||||
}
|
||||
|
||||
void jitter_clear(jitter_t *jb)
|
||||
{
|
||||
jb->inptr = jb->outptr = 0;
|
||||
}
|
||||
|
13
src/common/jitter.h
Normal file
13
src/common/jitter.h
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
typedef struct jitter {
|
||||
int16_t *spl; /* pointer to sample buffer */
|
||||
int len; /* buffer size: number of samples */
|
||||
int inptr, outptr; /* write pointer and read pointer */
|
||||
} jitter_t;
|
||||
|
||||
int jitter_create(jitter_t *jitter, int length);
|
||||
void jitter_destroy(jitter_t *jitter);
|
||||
void jitter_save(jitter_t *jb, int16_t *samples, int length);
|
||||
void jitter_load(jitter_t *jb, int16_t *samples, int length);
|
||||
void jitter_clear(jitter_t *jb);
|
||||
|
93
src/common/loss.c
Normal file
93
src/common/loss.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/* Loss detection
|
||||
*
|
||||
* (C) 2016 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include "../common/debug.h"
|
||||
#include "loss.h"
|
||||
|
||||
/* initialize detector
|
||||
*
|
||||
* interval: number of detector calls for one interval of one second
|
||||
* threshold: intervals may differ by this factor, to be declared as similar
|
||||
* 0 to disable, e.g. 1.3 for 30 percent change
|
||||
*/
|
||||
void audio_init_loss(loss_t *loss, int interval, double threshold, int seconds)
|
||||
{
|
||||
memset(loss, 0, sizeof(*loss));
|
||||
|
||||
loss->interval = interval;
|
||||
loss->threshold = threshold;
|
||||
loss->interval_num = seconds;
|
||||
}
|
||||
|
||||
/* call this when tones/telegrams are detected */
|
||||
void audio_reset_loss(loss_t *loss)
|
||||
{
|
||||
if (loss->interval_count > 0) {
|
||||
PDEBUG(DAUDIO, DEBUG_DEBUG, "Signal is recovered (loss is gone).\n");
|
||||
loss->interval_count = 0;
|
||||
}
|
||||
loss->level = 0;
|
||||
loss->level_count = 0;
|
||||
}
|
||||
|
||||
#define LOSS_MAX_DIFF 1.1 /* 10 % difference */
|
||||
|
||||
/* call this for every interval */
|
||||
int audio_detect_loss(loss_t *loss, double level)
|
||||
{
|
||||
double diff;
|
||||
|
||||
/* disabled */
|
||||
if (loss->threshold == 0.0)
|
||||
return 0;
|
||||
|
||||
/* calculate a total level to detect loss */
|
||||
loss->level += level;
|
||||
|
||||
if (++loss->level_count < loss->interval)
|
||||
return 0;
|
||||
|
||||
/* normalize level */
|
||||
loss->level = loss->level / loss->level_count;
|
||||
|
||||
PDEBUG(DAUDIO, DEBUG_DEBUG, "Noise level = %.0f%%\n", loss->level * 100);
|
||||
|
||||
diff = loss->level / loss->level_last;
|
||||
if (diff < 1.0)
|
||||
diff = 1.0 / diff;
|
||||
loss->level_last = loss->level;
|
||||
loss->level = 0;
|
||||
loss->level_count = 0;
|
||||
if (diff < LOSS_MAX_DIFF && loss->level_last > loss->threshold) {
|
||||
loss->interval_count++;
|
||||
PDEBUG(DAUDIO, DEBUG_DEBUG, "Detected signal loss %d for intervals level change %.0f%% (below %.0f%%).\n", loss->interval_count, diff * 100 - 100, LOSS_MAX_DIFF * 100 - 100);
|
||||
} else if (loss->interval_count > 0) {
|
||||
audio_reset_loss(loss);
|
||||
}
|
||||
|
||||
if (loss->interval_count == loss->interval_num)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
15
src/common/loss.h
Normal file
15
src/common/loss.h
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
typedef struct loss {
|
||||
int interval; /* levels in one interval */
|
||||
int interval_num; /* number of similar intervals until loss */
|
||||
double threshold; /* how much volume change is accedped during loss */
|
||||
double level_last; /* received level of last block */
|
||||
double level; /* received level of current block */
|
||||
int level_count; /* counter of levels inside interval */
|
||||
int interval_count; /* counter of cosecutive intervals with loss */
|
||||
} loss_t;
|
||||
|
||||
void audio_init_loss(loss_t *loss, int interval, double threshold, int seconds);
|
||||
void audio_reset_loss(loss_t *loss);
|
||||
int audio_detect_loss(loss_t *loss, double level);
|
||||
|
22
src/common/main.h
Normal file
22
src/common/main.h
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
extern int kanal;
|
||||
extern const char *sounddev;
|
||||
extern const char *call_sounddev;
|
||||
extern int samplerate;
|
||||
extern int latency;
|
||||
extern int use_mncc_sock;
|
||||
extern int send_patterns;
|
||||
extern int loopback;
|
||||
extern double lossdetect;
|
||||
extern int rt_prio;
|
||||
|
||||
void print_help(const char *arg0);
|
||||
void print_help_common(const char *arg0);
|
||||
extern struct option *long_options;
|
||||
extern char *optstring;
|
||||
void set_options_common(const char *optstring_special, struct option *long_options_special);
|
||||
void opt_switch_common(int c, char *arg0, int *skip_args);
|
||||
|
||||
|
||||
extern int quit;
|
||||
void sighandler(int sigset);
|
183
src/common/main_common.c
Normal file
183
src/common/main_common.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/* Common part for main.c of each base station type
|
||||
*
|
||||
* (C) 2016 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 <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "main.h"
|
||||
#include "debug.h"
|
||||
|
||||
/* common settings */
|
||||
int kanal = 0;
|
||||
const char *sounddev = "hw:0,0";
|
||||
const char *call_sounddev = "";
|
||||
int samplerate = 48000;
|
||||
int latency = 50;
|
||||
int use_mncc_sock = 0;
|
||||
int send_patterns = 1;
|
||||
int loopback = 0;
|
||||
double lossdetect = 0;
|
||||
int rt_prio = 0;
|
||||
|
||||
void print_help_common(const char *arg0)
|
||||
{
|
||||
printf("Usage: %s -k kanal [options] [station-id]\n", arg0);
|
||||
printf("\noptions:\n");
|
||||
/* - - */
|
||||
printf(" -h --help\n");
|
||||
printf(" This help\n");
|
||||
printf(" -D --debug <level>\n");
|
||||
printf(" Debug level: 0 = debug | 1 = info | 2 = notice (default = '%d')\n", debuglevel);
|
||||
printf(" -k --kanal <channel>\n");
|
||||
printf(" Channel number of \"Sender\" (default = '%d')\n", kanal);
|
||||
printf(" -d --device hw:<card>,<device>\n");
|
||||
printf(" Sound card and device number (default = '%s')\n", sounddev);
|
||||
printf(" -s --samplerate <rate>\n");
|
||||
printf(" Sample rate of sound device (default = '%d')\n", samplerate);
|
||||
printf(" -l --latency <delay>\n");
|
||||
printf(" How many milliseconds processed in advance (default = '%d')\n", latency);
|
||||
printf(" -0 --loss <volume>\n");
|
||||
printf(" Detect loss of carrier by detecting steady noise above given volume in\n");
|
||||
printf(" percent. (disabled by default)\n");
|
||||
printf(" -m --mncc-sock\n");
|
||||
printf(" Disable built-in call contol and offer socket (to LCR)\n");
|
||||
printf(" -c --call-device hw:<card>,<device>\n");
|
||||
printf(" Sound card and device number for headset (default = '%s')\n", call_sounddev);
|
||||
printf(" -p --send-patterns 0 | 1\n");
|
||||
printf(" Connect call on setup/release to provide classic tones (default = '%d')\n", send_patterns);
|
||||
printf(" -L --loopback <type>\n");
|
||||
printf(" Loopback test: 1 = internal | 2 = external | 3 = echo\n");
|
||||
printf(" -r --realtime <prio>\n");
|
||||
printf(" Set prio: 0 to diable, 99 for maximum (default = %d)\n", rt_prio);
|
||||
}
|
||||
|
||||
static struct option long_options_common[] = {
|
||||
{"help", 0, 0, 'h'},
|
||||
{"debug", 1, 0, 'D'},
|
||||
{"kanal", 1, 0, 'k'},
|
||||
{"device", 1, 0, 'd'},
|
||||
{"call-device", 1, 0, 'c'},
|
||||
{"samplerate", 1, 0, 's'},
|
||||
{"latency", 1, 0, 'l'},
|
||||
{"loss", 1, 0, '0'},
|
||||
{"mncc-sock", 0, 0, 'm'},
|
||||
{"send-patterns", 0, 0, 'p'},
|
||||
{"loopback", 1, 0, 'L'},
|
||||
{"realtime", 1, 0, 'r'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
const char *optstring_common = "hD:k:d:s:c:l:0:mp:L:r:";
|
||||
|
||||
struct option *long_options;
|
||||
char *optstring;
|
||||
|
||||
void set_options_common(const char *optstring_special, struct option *long_options_special)
|
||||
{
|
||||
int i;
|
||||
|
||||
long_options = calloc(sizeof(*long_options), 100);
|
||||
for (i = 0; long_options_common[i].name; i++)
|
||||
memcpy(&long_options[i], &long_options_common[i], sizeof(*long_options));
|
||||
for (; long_options_special->name; i++)
|
||||
memcpy(&long_options[i], long_options_special++, sizeof(*long_options));
|
||||
|
||||
optstring = calloc(strlen(optstring_common) + strlen(optstring_special) + 1, 1);
|
||||
strcpy(optstring, optstring_common);
|
||||
strcat(optstring, optstring_special);
|
||||
}
|
||||
|
||||
void opt_switch_common(int c, char *arg0, int *skip_args)
|
||||
{
|
||||
switch (c) {
|
||||
case 'h':
|
||||
print_help(arg0);
|
||||
exit(0);
|
||||
case 'D':
|
||||
debuglevel = atoi(optarg);
|
||||
if (debuglevel > 2)
|
||||
debuglevel = 2;
|
||||
if (debuglevel < 0)
|
||||
debuglevel = 0;
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'k':
|
||||
kanal = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'd':
|
||||
sounddev = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 's':
|
||||
samplerate = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'c':
|
||||
call_sounddev = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'l':
|
||||
latency = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case '0':
|
||||
lossdetect = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'm':
|
||||
use_mncc_sock = 1;
|
||||
*skip_args += 1;
|
||||
break;
|
||||
case 'p':
|
||||
send_patterns = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'L':
|
||||
loopback = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case 'r':
|
||||
rt_prio = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
default:
|
||||
exit (0);
|
||||
}
|
||||
}
|
||||
|
||||
/* global variable to quit main loop */
|
||||
int quit = 0;
|
||||
|
||||
void sighandler(int sigset)
|
||||
{
|
||||
if (sigset == SIGHUP)
|
||||
return;
|
||||
if (sigset == SIGPIPE)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "Signal received: %d\n", sigset);
|
||||
|
||||
quit = 1;
|
||||
}
|
||||
|
||||
|
347
src/common/mncc.h
Normal file
347
src/common/mncc.h
Normal file
@@ -0,0 +1,347 @@
|
||||
|
||||
#define MNCC_SETUP_REQ 0x0101
|
||||
#define MNCC_SETUP_IND 0x0102
|
||||
#define MNCC_SETUP_RSP 0x0103
|
||||
#define MNCC_SETUP_CNF 0x0104
|
||||
#define MNCC_SETUP_COMPL_REQ 0x0105
|
||||
#define MNCC_SETUP_COMPL_IND 0x0106
|
||||
/* MNCC_REJ_* is perfomed via MNCC_REL_* */
|
||||
#define MNCC_CALL_CONF_IND 0x0107
|
||||
#define MNCC_CALL_PROC_REQ 0x0108
|
||||
#define MNCC_PROGRESS_REQ 0x0109
|
||||
#define MNCC_ALERT_REQ 0x010a
|
||||
#define MNCC_ALERT_IND 0x010b
|
||||
#define MNCC_NOTIFY_REQ 0x010c
|
||||
#define MNCC_NOTIFY_IND 0x010d
|
||||
#define MNCC_DISC_REQ 0x010e
|
||||
#define MNCC_DISC_IND 0x010f
|
||||
#define MNCC_REL_REQ 0x0110
|
||||
#define MNCC_REL_IND 0x0111
|
||||
#define MNCC_REL_CNF 0x0112
|
||||
#define MNCC_FACILITY_REQ 0x0113
|
||||
#define MNCC_FACILITY_IND 0x0114
|
||||
#define MNCC_START_DTMF_IND 0x0115
|
||||
#define MNCC_START_DTMF_RSP 0x0116
|
||||
#define MNCC_START_DTMF_REJ 0x0117
|
||||
#define MNCC_STOP_DTMF_IND 0x0118
|
||||
#define MNCC_STOP_DTMF_RSP 0x0119
|
||||
#define MNCC_MODIFY_REQ 0x011a
|
||||
#define MNCC_MODIFY_IND 0x011b
|
||||
#define MNCC_MODIFY_RSP 0x011c
|
||||
#define MNCC_MODIFY_CNF 0x011d
|
||||
#define MNCC_MODIFY_REJ 0x011e
|
||||
#define MNCC_HOLD_IND 0x011f
|
||||
#define MNCC_HOLD_CNF 0x0120
|
||||
#define MNCC_HOLD_REJ 0x0121
|
||||
#define MNCC_RETRIEVE_IND 0x0122
|
||||
#define MNCC_RETRIEVE_CNF 0x0123
|
||||
#define MNCC_RETRIEVE_REJ 0x0124
|
||||
#define MNCC_USERINFO_REQ 0x0125
|
||||
#define MNCC_USERINFO_IND 0x0126
|
||||
#define MNCC_REJ_REQ 0x0127
|
||||
#define MNCC_REJ_IND 0x0128
|
||||
#define MNCC_PROGRESS_IND 0x0129
|
||||
#define MNCC_CALL_PROC_IND 0x012a
|
||||
#define MNCC_CALL_CONF_REQ 0x012b
|
||||
#define MNCC_START_DTMF_REQ 0x012c
|
||||
#define MNCC_STOP_DTMF_REQ 0x012d
|
||||
#define MNCC_HOLD_REQ 0x012e
|
||||
#define MNCC_RETRIEVE_REQ 0x012f
|
||||
|
||||
#define MNCC_BRIDGE 0x0200
|
||||
#define MNCC_FRAME_RECV 0x0201
|
||||
#define MNCC_FRAME_DROP 0x0202
|
||||
#define MNCC_LCHAN_MODIFY 0x0203
|
||||
#define MNCC_RTP_CREATE 0x0204
|
||||
#define MNCC_RTP_CONNECT 0x0205
|
||||
#define MNCC_RTP_FREE 0x0206
|
||||
|
||||
#define GSM_TCHF_FRAME 0x0300
|
||||
#define GSM_TCHF_FRAME_EFR 0x0301
|
||||
#define GSM_TCHH_FRAME 0x0302
|
||||
#define GSM_TCH_FRAME_AMR 0x0303
|
||||
#define ANALOG_8000HZ 0x0380
|
||||
#define GSM_BAD_FRAME 0x03ff
|
||||
|
||||
#define MNCC_SOCKET_HELLO 0x0400
|
||||
|
||||
#define GSM_MAX_FACILITY 128
|
||||
#define GSM_MAX_SSVERSION 128
|
||||
#define GSM_MAX_USERUSER 128
|
||||
|
||||
#define MNCC_F_BEARER_CAP 0x0001
|
||||
#define MNCC_F_CALLED 0x0002
|
||||
#define MNCC_F_CALLING 0x0004
|
||||
#define MNCC_F_REDIRECTING 0x0008
|
||||
#define MNCC_F_CONNECTED 0x0010
|
||||
#define MNCC_F_CAUSE 0x0020
|
||||
#define MNCC_F_USERUSER 0x0040
|
||||
#define MNCC_F_PROGRESS 0x0080
|
||||
#define MNCC_F_EMERGENCY 0x0100
|
||||
#define MNCC_F_FACILITY 0x0200
|
||||
#define MNCC_F_SSVERSION 0x0400
|
||||
#define MNCC_F_CCCAP 0x0800
|
||||
#define MNCC_F_KEYPAD 0x1000
|
||||
#define MNCC_F_SIGNAL 0x2000
|
||||
|
||||
#define GSM_MAX_FACILITY 128
|
||||
#define GSM_MAX_SSVERSION 128
|
||||
#define GSM_MAX_USERUSER 128
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Information Transfer Capability */
|
||||
enum gsm48_bcap_itcap {
|
||||
GSM48_BCAP_ITCAP_SPEECH = 0,
|
||||
GSM48_BCAP_ITCAP_UNR_DIG_INF = 1,
|
||||
GSM48_BCAP_ITCAP_3k1_AUDIO = 2,
|
||||
GSM48_BCAP_ITCAP_FAX_G3 = 3,
|
||||
GSM48_BCAP_ITCAP_OTHER = 5,
|
||||
GSM48_BCAP_ITCAP_RESERVED = 7,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Transfer Mode */
|
||||
enum gsm48_bcap_tmod {
|
||||
GSM48_BCAP_TMOD_CIRCUIT = 0,
|
||||
GSM48_BCAP_TMOD_PACKET = 1,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Coding Standard */
|
||||
enum gsm48_bcap_coding {
|
||||
GSM48_BCAP_CODING_GSM_STD = 0,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Radio Channel Requirements */
|
||||
enum gsm48_bcap_rrq {
|
||||
GSM48_BCAP_RRQ_FR_ONLY = 1,
|
||||
GSM48_BCAP_RRQ_DUAL_HR = 2,
|
||||
GSM48_BCAP_RRQ_DUAL_FR = 3,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Rate Adaption */
|
||||
enum gsm48_bcap_ra {
|
||||
GSM48_BCAP_RA_NONE = 0,
|
||||
GSM48_BCAP_RA_V110_X30 = 1,
|
||||
GSM48_BCAP_RA_X31 = 2,
|
||||
GSM48_BCAP_RA_OTHER = 3,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Signalling access protocol */
|
||||
enum gsm48_bcap_sig_access {
|
||||
GSM48_BCAP_SA_I440_I450 = 1,
|
||||
GSM48_BCAP_SA_X21 = 2,
|
||||
GSM48_BCAP_SA_X28_DP_IN = 3,
|
||||
GSM48_BCAP_SA_X28_DP_UN = 4,
|
||||
GSM48_BCAP_SA_X28_NDP = 5,
|
||||
GSM48_BCAP_SA_X32 = 6,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: User Rate */
|
||||
enum gsm48_bcap_user_rate {
|
||||
GSM48_BCAP_UR_300 = 1,
|
||||
GSM48_BCAP_UR_1200 = 2,
|
||||
GSM48_BCAP_UR_2400 = 3,
|
||||
GSM48_BCAP_UR_4800 = 4,
|
||||
GSM48_BCAP_UR_9600 = 5,
|
||||
GSM48_BCAP_UR_12000 = 6,
|
||||
GSM48_BCAP_UR_1200_75 = 7,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Parity */
|
||||
enum gsm48_bcap_parity {
|
||||
GSM48_BCAP_PAR_ODD = 0,
|
||||
GSM48_BCAP_PAR_EVEN = 2,
|
||||
GSM48_BCAP_PAR_NONE = 3,
|
||||
GSM48_BCAP_PAR_ZERO = 4,
|
||||
GSM48_BCAP_PAR_ONE = 5,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Intermediate Rate */
|
||||
enum gsm48_bcap_interm_rate {
|
||||
GSM48_BCAP_IR_8k = 2,
|
||||
GSM48_BCAP_IR_16k = 3,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Transparency */
|
||||
enum gsm48_bcap_transp {
|
||||
GSM48_BCAP_TR_TRANSP = 0,
|
||||
GSM48_BCAP_TR_RLP = 1,
|
||||
GSM48_BCAP_TR_TR_PREF = 2,
|
||||
GSM48_BCAP_TR_RLP_PREF = 3,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Modem Type */
|
||||
enum gsm48_bcap_modem_type {
|
||||
GSM48_BCAP_MT_NONE = 0,
|
||||
GSM48_BCAP_MT_V21 = 1,
|
||||
GSM48_BCAP_MT_V22 = 2,
|
||||
GSM48_BCAP_MT_V22bis = 3,
|
||||
GSM48_BCAP_MT_V23 = 4,
|
||||
GSM48_BCAP_MT_V26ter = 5,
|
||||
GSM48_BCAP_MT_V32 = 6,
|
||||
GSM48_BCAP_MT_UNDEF = 7,
|
||||
GSM48_BCAP_MT_AUTO_1 = 8,
|
||||
};
|
||||
|
||||
/* GSM 04.08 Bearer Capability: Speech Version Indication */
|
||||
enum gsm48_bcap_speech_ver {
|
||||
GSM48_BCAP_SV_FR = 0,
|
||||
GSM48_BCAP_SV_HR = 1,
|
||||
GSM48_BCAP_SV_EFR = 2,
|
||||
GSM48_BCAP_SV_AMR_F = 4,
|
||||
GSM48_BCAP_SV_AMR_H = 5,
|
||||
BCAP_ANALOG_8000HZ = 0x80,
|
||||
};
|
||||
|
||||
/* Expanded fields from GSM TS 04.08, Table 10.5.102 */
|
||||
struct gsm_mncc_bearer_cap {
|
||||
int transfer; /* Information Transfer Capability */
|
||||
int mode; /* Transfer Mode */
|
||||
int coding; /* Coding Standard */
|
||||
int radio; /* Radio Channel Requirement */
|
||||
int speech_ctm; /* CTM text telephony indication */
|
||||
int speech_ver[8]; /* Speech version indication */
|
||||
struct {
|
||||
enum gsm48_bcap_ra rate_adaption;
|
||||
enum gsm48_bcap_sig_access sig_access;
|
||||
int async;
|
||||
int nr_stop_bits;
|
||||
int nr_data_bits;
|
||||
enum gsm48_bcap_user_rate user_rate;
|
||||
enum gsm48_bcap_parity parity;
|
||||
enum gsm48_bcap_interm_rate interm_rate;
|
||||
enum gsm48_bcap_transp transp;
|
||||
enum gsm48_bcap_modem_type modem_type;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct gsm_mncc_number {
|
||||
int type;
|
||||
int plan;
|
||||
int present;
|
||||
int screen;
|
||||
char number[33];
|
||||
};
|
||||
|
||||
struct gsm_mncc_cause {
|
||||
int location;
|
||||
int coding;
|
||||
int rec;
|
||||
int rec_val;
|
||||
int value;
|
||||
int diag_len;
|
||||
char diag[32];
|
||||
};
|
||||
|
||||
struct gsm_mncc_useruser {
|
||||
int proto;
|
||||
char info[GSM_MAX_USERUSER + 1]; /* + termination char */
|
||||
};
|
||||
|
||||
struct gsm_mncc_progress {
|
||||
int coding;
|
||||
int location;
|
||||
int descr;
|
||||
};
|
||||
|
||||
struct gsm_mncc_facility {
|
||||
int len;
|
||||
char info[GSM_MAX_FACILITY];
|
||||
};
|
||||
|
||||
struct gsm_mncc_ssversion {
|
||||
int len;
|
||||
char info[GSM_MAX_SSVERSION];
|
||||
};
|
||||
|
||||
struct gsm_mncc_cccap {
|
||||
int dtmf;
|
||||
int pcp;
|
||||
};
|
||||
|
||||
enum {
|
||||
GSM_MNCC_BCAP_SPEECH = 0,
|
||||
GSM_MNCC_BCAP_UNR_DIG = 1,
|
||||
GSM_MNCC_BCAP_AUDIO = 2,
|
||||
GSM_MNCC_BCAP_FAX_G3 = 3,
|
||||
GSM_MNCC_BCAP_OTHER_ITC = 5,
|
||||
GSM_MNCC_BCAP_RESERVED = 7,
|
||||
};
|
||||
|
||||
enum {
|
||||
GSM_LCHAN_NONE,
|
||||
GSM_LCHAN_SDCCH,
|
||||
GSM_LCHAN_TCH_F,
|
||||
GSM_LCHAN_TCH_H,
|
||||
GSM_LCHAN_UNKNOWN,
|
||||
GSM_LCHAN_CCCH,
|
||||
GSM_LCHAN_PDTCH,
|
||||
_GSM_LCHAN_MAX
|
||||
};
|
||||
|
||||
struct gsm_mncc {
|
||||
/* context based information */
|
||||
uint32_t msg_type;
|
||||
uint32_t callref;
|
||||
|
||||
/* which fields are present */
|
||||
uint32_t fields;
|
||||
|
||||
/* data derived informations (MNCC_F_ based) */
|
||||
struct gsm_mncc_bearer_cap bearer_cap;
|
||||
struct gsm_mncc_number called;
|
||||
struct gsm_mncc_number calling;
|
||||
struct gsm_mncc_number redirecting;
|
||||
struct gsm_mncc_number connected;
|
||||
struct gsm_mncc_cause cause;
|
||||
struct gsm_mncc_progress progress;
|
||||
struct gsm_mncc_useruser useruser;
|
||||
struct gsm_mncc_facility facility;
|
||||
struct gsm_mncc_cccap cccap;
|
||||
struct gsm_mncc_ssversion ssversion;
|
||||
struct {
|
||||
int sup;
|
||||
int inv;
|
||||
} clir;
|
||||
int signal;
|
||||
|
||||
/* data derived information, not MNCC_F based */
|
||||
int keypad;
|
||||
int more;
|
||||
int notify; /* 0..127 */
|
||||
int emergency;
|
||||
char imsi[16];
|
||||
|
||||
unsigned char lchan_type;
|
||||
unsigned char lchan_mode;
|
||||
};
|
||||
|
||||
struct gsm_data_frame {
|
||||
uint32_t msg_type;
|
||||
uint32_t callref;
|
||||
unsigned char data[0];
|
||||
};
|
||||
|
||||
struct gsm_mncc_rtp {
|
||||
uint32_t msg_type;
|
||||
uint32_t callref;
|
||||
uint32_t ip;
|
||||
uint16_t port;
|
||||
uint32_t payload_type;
|
||||
uint32_t payload_msg_type;
|
||||
};
|
||||
|
||||
|
||||
#define MNCC_SOCK_VERSION 5
|
||||
struct gsm_mncc_hello {
|
||||
uint32_t msg_type;
|
||||
uint32_t version;
|
||||
|
||||
/* send the sizes of the structs */
|
||||
uint32_t mncc_size;
|
||||
uint32_t data_frame_size;
|
||||
|
||||
/* send some offsets */
|
||||
uint32_t called_offset;
|
||||
uint32_t signal_offset;
|
||||
uint32_t emergency_offset;
|
||||
uint32_t lchan_type_offset;
|
||||
};
|
236
src/common/mncc_sock.c
Normal file
236
src/common/mncc_sock.c
Normal file
@@ -0,0 +1,236 @@
|
||||
/* Mobie Network Call Control (MNCC) socket handling
|
||||
*
|
||||
* (C) 2016 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 <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <stddef.h>
|
||||
#include <unistd.h>
|
||||
#include "../common/debug.h"
|
||||
#include "call.h"
|
||||
#include "mncc_sock.h"
|
||||
|
||||
static int listen_sock = -1;
|
||||
static int mncc_sock = -1;
|
||||
|
||||
/* write to mncc socket, return error or -EIO if no socket connection */
|
||||
int mncc_write(uint8_t *buf, int length)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (mncc_sock <= 0) {
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC not connected.\n");
|
||||
return -EIO;
|
||||
}
|
||||
rc = send(mncc_sock, buf, length, 0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "MNCC connection failed (errno = %d).\n", errno);
|
||||
mncc_sock_close();
|
||||
return 0;
|
||||
}
|
||||
if (rc != length) {
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC write failed.\n");
|
||||
mncc_sock_close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/* read from mncc socket */
|
||||
static int mncc_read(void)
|
||||
{
|
||||
uint8_t buf[sizeof(struct gsm_mncc)+1024];
|
||||
int rc;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
rc = recv(mncc_sock, buf, sizeof(buf), 0);
|
||||
if (rc == 0) {
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC connection closed.\n");
|
||||
mncc_sock_close();
|
||||
return 0;
|
||||
}
|
||||
if (rc < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
return -errno;
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "MNCC connection failed (errno = %d).\n", errno);
|
||||
mncc_sock_close();
|
||||
return -errno;
|
||||
}
|
||||
|
||||
call_mncc_recv(buf, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void mncc_hello(void)
|
||||
{
|
||||
struct gsm_mncc_hello hello;
|
||||
|
||||
memset(&hello, 0, sizeof(hello));
|
||||
hello.msg_type = MNCC_SOCKET_HELLO;
|
||||
hello.version = MNCC_SOCK_VERSION;
|
||||
hello.mncc_size = sizeof(struct gsm_mncc);
|
||||
hello.data_frame_size = sizeof(struct gsm_data_frame);
|
||||
hello.called_offset = offsetof(struct gsm_mncc, called);
|
||||
hello.signal_offset = offsetof(struct gsm_mncc, signal);
|
||||
hello.emergency_offset = offsetof(struct gsm_mncc, emergency);
|
||||
hello.lchan_type_offset = offsetof(struct gsm_mncc, lchan_type);
|
||||
|
||||
mncc_write((uint8_t *) &hello, sizeof(hello));
|
||||
}
|
||||
|
||||
|
||||
static int mncc_accept(void)
|
||||
{
|
||||
struct sockaddr_un __attribute__((__unused__)) un_addr;
|
||||
socklen_t __attribute__((__unused__)) len;
|
||||
int flags;
|
||||
int rc;
|
||||
|
||||
len = sizeof(un_addr);
|
||||
rc = accept(listen_sock, (struct sockaddr *) &un_addr, &len);
|
||||
if (rc < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
return 0;
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to accept incomming connection (errno=%d).\n", errno);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (mncc_sock > 0) {
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "Rejecting multiple incomming connections.\n");
|
||||
close(rc);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mncc_sock = rc;
|
||||
flags = fcntl(mncc_sock, F_GETFL, 0);
|
||||
flags = 0;
|
||||
rc = fcntl(mncc_sock, F_SETFL, flags | O_NONBLOCK);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to set socket into non-blocking IO mode.\n");
|
||||
mncc_sock_close();
|
||||
return rc;
|
||||
}
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket connected.\n");
|
||||
|
||||
mncc_hello();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void mncc_handle(void)
|
||||
{
|
||||
mncc_accept();
|
||||
|
||||
if (mncc_sock > 0) {
|
||||
while ((mncc_read()) > 0)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mncc_sock_close(void)
|
||||
{
|
||||
if (mncc_sock > 0) {
|
||||
PDEBUG(DMNCC, DEBUG_NOTICE, "MNCC socket disconnected.\n");
|
||||
close(mncc_sock);
|
||||
mncc_sock = -1;
|
||||
/* clear all call instances */
|
||||
call_mncc_flush();
|
||||
}
|
||||
}
|
||||
|
||||
int mncc_init(const char *sock_name)
|
||||
{
|
||||
struct sockaddr_un local;
|
||||
unsigned int namelen;
|
||||
int flags;
|
||||
int rc;
|
||||
|
||||
listen_sock = socket(PF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (listen_sock < 0) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to create socket.\n");
|
||||
return listen_sock;
|
||||
}
|
||||
|
||||
local.sun_family = AF_UNIX;
|
||||
strncpy(local.sun_path, sock_name, sizeof(local.sun_path));
|
||||
local.sun_path[sizeof(local.sun_path) - 1] = '\0';
|
||||
unlink(local.sun_path);
|
||||
|
||||
/* we use the same magic that X11 uses in Xtranssock.c for
|
||||
* calculating the proper length of the sockaddr */
|
||||
#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
|
||||
local.sun_len = strlen(local.sun_path);
|
||||
#endif
|
||||
#if defined(BSD44SOCKETS) || defined(SUN_LEN)
|
||||
namelen = SUN_LEN(&local);
|
||||
#else
|
||||
namelen = strlen(local.sun_path) +
|
||||
offsetof(struct sockaddr_un, sun_path);
|
||||
#endif
|
||||
|
||||
rc = bind(listen_sock, (struct sockaddr *) &local, namelen);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to bind the unix domain "
|
||||
"socket. '%s'\n", local.sun_path);
|
||||
mncc_exit();
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = listen(listen_sock, 0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to listen.\n");
|
||||
mncc_exit();
|
||||
return rc;
|
||||
}
|
||||
|
||||
flags = fcntl(listen_sock, F_GETFL, 0);
|
||||
flags = 0;
|
||||
rc = fcntl(listen_sock, F_SETFL, flags | O_NONBLOCK);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DMNCC, DEBUG_ERROR, "Failed to set socket into non-blocking IO mode.\n");
|
||||
mncc_exit();
|
||||
return rc;
|
||||
}
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_DEBUG, "MNCC socket at '%s' initialized, waiting for connection.\n", sock_name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mncc_exit(void)
|
||||
{
|
||||
mncc_sock_close();
|
||||
|
||||
if (listen_sock > 0) {
|
||||
close(listen_sock);
|
||||
listen_sock = -1;
|
||||
}
|
||||
|
||||
PDEBUG(DMNCC, DEBUG_DEBUG, "MNCC socket removed.\n");
|
||||
}
|
||||
|
8
src/common/mncc_sock.h
Normal file
8
src/common/mncc_sock.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#include "mncc.h"
|
||||
|
||||
int mncc_write(uint8_t *buf, int length);
|
||||
void mncc_handle(void);
|
||||
void mncc_sock_close(void);
|
||||
int mncc_init(const char *sock_name);
|
||||
void mncc_exit(void);
|
||||
|
159
src/common/samplerate.c
Normal file
159
src/common/samplerate.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/* Sample rate conversion
|
||||
*
|
||||
* (C) 2016 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 <errno.h>
|
||||
#include <string.h>
|
||||
#include "samplerate.h"
|
||||
|
||||
/* generally use filter, but disable for test using quick and dirty replacement */
|
||||
#define USE_FILTER
|
||||
|
||||
/* NOTE: This is quick and dirtry. */
|
||||
|
||||
int init_samplerate(samplerate_t *state, int samplerate)
|
||||
{
|
||||
if ((samplerate % 8000)) {
|
||||
fprintf(stderr, "Sample rate must be a muliple of 8000 to support MNCC socket interface, aborting!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
memset(state, 0, sizeof(*state));
|
||||
state->factor = samplerate / 8000;
|
||||
|
||||
biquad_init(&state->up.bq, 4000.0, samplerate);
|
||||
biquad_init(&state->down.bq, 4000.0, samplerate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* convert input sample rate to 8000 Hz */
|
||||
int samplerate_downsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output)
|
||||
{
|
||||
#ifdef USE_FILTER
|
||||
int output_num, i, j;
|
||||
int factor = state->factor;
|
||||
double spl[input_num];
|
||||
int32_t value;
|
||||
|
||||
/* convert samples to double */
|
||||
for (i = 0; i < input_num; i++)
|
||||
spl[i] = *input++ / 32768.0;
|
||||
|
||||
/* filter down */
|
||||
biquad_process(&state->down.bq, spl, input_num, 1);
|
||||
output_num = input_num / factor;
|
||||
|
||||
/* resample filtered result */
|
||||
for (i = 0, j = 0; i < output_num; i++, j += factor) {
|
||||
value = spl[j] * 32768.0;
|
||||
if (value < -32768)
|
||||
value = -32768;
|
||||
else if (value > 32767)
|
||||
value = 32767;
|
||||
*output++ = value;
|
||||
}
|
||||
|
||||
return output_num;
|
||||
#else
|
||||
int output_num = 0, i;
|
||||
double sum;
|
||||
int factor, sum_count;
|
||||
|
||||
//memcpy(output, input, input_num*2);
|
||||
//return input_num;
|
||||
sum = state->down.sum;
|
||||
sum_count = state->down.sum_count;
|
||||
factor = state->factor;
|
||||
|
||||
for (i = 0; i < input_num; i++) {
|
||||
sum += *input++;
|
||||
sum_count++;
|
||||
if (sum_count == factor) {
|
||||
*output++ = sum / (double)sum_count;
|
||||
output_num++;
|
||||
sum = 0;
|
||||
sum_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
state->down.sum = sum;
|
||||
state->down.sum_count = sum_count;
|
||||
|
||||
return output_num;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* convert 8000 Hz sample rate to output sample rate */
|
||||
int samplerate_upsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output)
|
||||
{
|
||||
#ifdef USE_FILTER
|
||||
int output_num, i;
|
||||
int factor = state->factor;
|
||||
double spl[input_num * factor];
|
||||
int32_t value;
|
||||
|
||||
output_num = input_num * factor;
|
||||
|
||||
/* resample input */
|
||||
for (i = 0; i < output_num; i++)
|
||||
spl[i] = input[i / factor] / 32768.0;
|
||||
|
||||
/* filter up */
|
||||
biquad_process(&state->up.bq, spl, output_num, 1);
|
||||
|
||||
/* convert double to samples */
|
||||
for (i = 0; i < output_num; i++) {
|
||||
value = spl[i] * 32768.0;
|
||||
if (value < -32768)
|
||||
value = -32768;
|
||||
else if (value > 32767)
|
||||
value = 32767;
|
||||
*output++ = value;
|
||||
}
|
||||
|
||||
return output_num;
|
||||
#else
|
||||
int output_num = 0, i, j;
|
||||
double last_sample, sample, slope;
|
||||
int factor;
|
||||
|
||||
last_sample = state->up.last_sample;
|
||||
factor = state->factor;
|
||||
|
||||
for (i = 0; i < input_num; i++) {
|
||||
sample = *input++;
|
||||
slope = (double)(sample - last_sample) / (double)factor;
|
||||
//int jolly = (int)last_sample;
|
||||
for (j = 0; j < factor; j++) {
|
||||
// if (last_sample > 32767 || last_sample < -32767)
|
||||
// printf("%.5f sample=%.0f, last_sample=%d, slope=%.5f\n", last_sample, sample, jolly, slope);
|
||||
*output++ = last_sample;
|
||||
output_num++;
|
||||
last_sample += slope;
|
||||
}
|
||||
last_sample = sample;
|
||||
}
|
||||
|
||||
state->up.last_sample = last_sample;
|
||||
|
||||
return output_num;
|
||||
#endif
|
||||
}
|
||||
|
18
src/common/samplerate.h
Normal file
18
src/common/samplerate.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "filter.h"
|
||||
|
||||
typedef struct samplerate {
|
||||
int factor;
|
||||
struct {
|
||||
double sum;
|
||||
int sum_count;
|
||||
biquad_low_pass_t bq;
|
||||
} down;
|
||||
struct {
|
||||
double last_sample;
|
||||
biquad_low_pass_t bq;
|
||||
} up;
|
||||
} samplerate_t;
|
||||
|
||||
int init_samplerate(samplerate_t *state, int samplerate);
|
||||
int samplerate_downsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output);
|
||||
int samplerate_upsample(samplerate_t *state, int16_t *input, int input_num, int16_t *output);
|
228
src/common/sender.c
Normal file
228
src/common/sender.c
Normal file
@@ -0,0 +1,228 @@
|
||||
/* Common transceiver functions
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "debug.h"
|
||||
#include "sender.h"
|
||||
#include "timer.h"
|
||||
#include "call.h"
|
||||
|
||||
sender_t *sender_head = NULL;
|
||||
static sender_t **sender_tailp = &sender_head;
|
||||
|
||||
/* Init transceiver instance and link to list of transceivers. */
|
||||
int sender_create(sender_t *sender, const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume, int use_pilot_signal)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
PDEBUG(DSENDER, DEBUG_DEBUG, "Creating 'Sender' instance\n");
|
||||
|
||||
sender->samplerate = samplerate;
|
||||
sender->kanal = kanal;
|
||||
sender->loopback = loopback;
|
||||
sender->loss_volume = loss_volume;
|
||||
sender->use_pilot_signal = use_pilot_signal;
|
||||
sender->pilotton_phaseshift = 1.0 / ((double)samplerate / 1000.0);
|
||||
|
||||
sender->sound = sound_open(sounddev, samplerate);
|
||||
if (!sender->sound) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "No sound device!\n");
|
||||
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = init_samplerate(&sender->srstate, samplerate);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to init sample rate conversion!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = jitter_create(&sender->audio, samplerate / 5);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to create and init audio buffer!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
*sender_tailp = sender;
|
||||
sender_tailp = &sender->next;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
sender_destroy(sender);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Destroy transceiver instance and unlink from list. */
|
||||
void sender_destroy(sender_t *sender)
|
||||
{
|
||||
PDEBUG(DSENDER, DEBUG_DEBUG, "Destroying 'Sender' instance\n");
|
||||
|
||||
sender_tailp = &sender_head;
|
||||
while (*sender_tailp) {
|
||||
if (sender == *sender_tailp)
|
||||
*sender_tailp = sender->next;
|
||||
sender_tailp = &sender->next;
|
||||
}
|
||||
|
||||
if (sender->sound)
|
||||
sound_close(sender->sound);
|
||||
|
||||
jitter_destroy(&sender->audio);
|
||||
}
|
||||
|
||||
static void gen_pilotton(sender_t *sender, int16_t *samples, int length)
|
||||
{
|
||||
double phaseshift, phase;
|
||||
int i;
|
||||
|
||||
phaseshift = sender->pilotton_phaseshift;
|
||||
phase = sender->pilotton_phase;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (phase < 0.5)
|
||||
*samples++ = 30000;
|
||||
else
|
||||
*samples++ = -30000;
|
||||
phase += phaseshift;
|
||||
if (phase >= 1.0)
|
||||
phase -= 1.0;
|
||||
}
|
||||
|
||||
sender->pilotton_phase = phase;
|
||||
}
|
||||
|
||||
/* Handle audio streaming of one transceiver. */
|
||||
void process_sender(sender_t *sender, int latspl)
|
||||
{
|
||||
int16_t samples[latspl], pilot[latspl];
|
||||
int rc, count;
|
||||
|
||||
count = sound_get_inbuffer(sender->sound);
|
||||
if (count < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to get samples in buffer (rc = %d)!\n", count);
|
||||
if (count == -EPIPE)
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
|
||||
return;
|
||||
}
|
||||
if (count < latspl) {
|
||||
count = latspl - count;
|
||||
if (sender->loopback == 3)
|
||||
jitter_load(&sender->audio, samples, count);
|
||||
else
|
||||
sender_send(sender, samples, count);
|
||||
switch (sender->use_pilot_signal) {
|
||||
case 2:
|
||||
/* tone if pilot signal is on */
|
||||
if (sender->pilot_on)
|
||||
gen_pilotton(sender, pilot, count);
|
||||
else
|
||||
memset(pilot, 0, count << 1);
|
||||
rc = sound_write(sender->sound, samples, pilot, count);
|
||||
break;
|
||||
case 1:
|
||||
/* positive signal if pilot signal is on */
|
||||
if (sender->pilot_on)
|
||||
memset(pilot, 127, count << 1);
|
||||
else
|
||||
memset(pilot, 128, count << 1);
|
||||
rc = sound_write(sender->sound, samples, pilot, count);
|
||||
break;
|
||||
case 0:
|
||||
/* negative signal if pilot signal is on */
|
||||
if (sender->pilot_on)
|
||||
memset(pilot, 128, count << 1);
|
||||
else
|
||||
memset(pilot, 127, count << 1);
|
||||
rc = sound_write(sender->sound, samples, pilot, count);
|
||||
break;
|
||||
default:
|
||||
rc = sound_write(sender->sound, samples, samples, count);
|
||||
}
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to write TX data to sound device (rc = %d)\n", rc);
|
||||
if (rc == -EPIPE)
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
|
||||
return;
|
||||
}
|
||||
if (sender->loopback == 1)
|
||||
sender_receive(sender, samples, count);
|
||||
}
|
||||
|
||||
count = sound_read(sender->sound, samples, latspl);
|
||||
//printf("count=%d time= %.4f\n", count, (double)count * 1000 / sender->samplerate);
|
||||
if (count < 0) {
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Failed to read from sound device (rc = %d)!\n", count);
|
||||
if (count == -EPIPE)
|
||||
PDEBUG(DSENDER, DEBUG_ERROR, "Trying to recover!\n");
|
||||
return;
|
||||
}
|
||||
if (count) {
|
||||
if (sender->loopback != 1)
|
||||
sender_receive(sender, samples, count);
|
||||
if (sender->loopback == 3) {
|
||||
jitter_save(&sender->audio, samples, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through all transceiver instances of one network. */
|
||||
void main_loop(int *quit, int latency)
|
||||
{
|
||||
int latspl;
|
||||
sender_t *sender;
|
||||
double last_time = 0, now;
|
||||
|
||||
while(!(*quit)) {
|
||||
/* process sound of all transceivers */
|
||||
sender = sender_head;
|
||||
while (sender) {
|
||||
latspl = sender->samplerate * latency / 1000;
|
||||
process_sender(sender, latspl);
|
||||
sender = sender->next;
|
||||
}
|
||||
|
||||
/* process timers */
|
||||
process_timer();
|
||||
|
||||
/* process audio for mncc call instances */
|
||||
now = get_time();
|
||||
if (now - last_time >= 0.1)
|
||||
last_time = now;
|
||||
if (now - last_time >= 0.020) {
|
||||
last_time += 0.020;
|
||||
/* call clock every 20ms */
|
||||
call_mncc_clock();
|
||||
}
|
||||
|
||||
/* process audio of built-in call control */
|
||||
if (process_call())
|
||||
break;
|
||||
|
||||
/* sleep a while */
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
|
50
src/common/sender.h
Normal file
50
src/common/sender.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "sound.h"
|
||||
#include "samplerate.h"
|
||||
#include "jitter.h"
|
||||
#include "loss.h"
|
||||
|
||||
/* common structure of each transmitter */
|
||||
typedef struct sender {
|
||||
struct sender *next;
|
||||
|
||||
/* call reference */
|
||||
int callref;
|
||||
|
||||
/* system info */
|
||||
int kanal; /* channel number */
|
||||
|
||||
/* sound */
|
||||
void *sound;
|
||||
int samplerate;
|
||||
samplerate_t srstate; /* sample rate conversion state */
|
||||
|
||||
/* loopback test */
|
||||
int loopback; /* 0 = off, 1 = internal, 2 = external */
|
||||
|
||||
/* audio buffer for audio to send to transmitter (also used as loopback buffer) */
|
||||
jitter_t audio;
|
||||
|
||||
/* audio buffer for audio to send to caller (20ms = 160 samples @ 8000Hz) */
|
||||
int16_t rxbuf[160];
|
||||
int rxbuf_pos; /* current fill of buffer */
|
||||
|
||||
/* loss of carrier detection */
|
||||
double loss_volume;
|
||||
loss_t loss;
|
||||
|
||||
/* pilot tone */
|
||||
int use_pilot_signal; /* -1 if not used, 1 for positive, 0 for negative, 2 for tone */
|
||||
int pilot_on; /* 1 or 0 for on or off */
|
||||
double pilotton_phaseshift; /* phase to shift every sample */
|
||||
double pilotton_phase; /* current phase */
|
||||
} sender_t;
|
||||
|
||||
/* list of all senders */
|
||||
extern sender_t *sender_head;
|
||||
|
||||
int sender_create(sender_t *sender, const char *sounddev, int samplerate, int kanal, int loopback, double loss_volume, int use_pilot_signal);
|
||||
void sender_destroy(sender_t *sender);
|
||||
void sender_send(sender_t *sender, int16_t *samples, int count);
|
||||
void sender_receive(sender_t *sender, int16_t *samples, int count);
|
||||
void main_loop(int *quit, int latency);
|
||||
|
6
src/common/sound.h
Normal file
6
src/common/sound.h
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
void *sound_open(const char *device, int samplerate);
|
||||
void sound_close(void *inst);
|
||||
int sound_write(void *inst, int16_t *samples_left, int16_t *samples_right, int num);
|
||||
int sound_read(void *inst, int16_t *samples, int num);
|
||||
int sound_get_inbuffer(void *inst);
|
260
src/common/sound_alsa.c
Normal file
260
src/common/sound_alsa.c
Normal file
@@ -0,0 +1,260 @@
|
||||
/* Sound device access
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "debug.h"
|
||||
#include "sound.h"
|
||||
|
||||
typedef struct sound {
|
||||
snd_pcm_t *phandle, *chandle;
|
||||
int pchannels, cchannels;
|
||||
} sound_t;
|
||||
|
||||
static int set_hw_params(snd_pcm_t *handle, int samplerate, int *channels)
|
||||
{
|
||||
snd_pcm_hw_params_t *hw_params = NULL;
|
||||
int rc;
|
||||
unsigned int rrate;
|
||||
|
||||
rc = snd_pcm_hw_params_malloc(&hw_params);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to allocate hw_params! (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_any(handle, hw_params);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot initialize hardware parameter structure (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set real hardware rate (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_access (handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set access to interleaved (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample format (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rrate = samplerate;
|
||||
rc = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rrate, 0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set sample rate (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
if (rrate != samplerate) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Rate doesn't match (requested %dHz, get %dHz)\n", samplerate, rrate);
|
||||
rc = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
*channels = 1;
|
||||
rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels);
|
||||
if (rc < 0) {
|
||||
*channels = 2;
|
||||
rc = snd_pcm_hw_params_set_channels(handle, hw_params, *channels);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set channel count to 1 nor 2 (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params(handle, hw_params);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot set parameters (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (hw_params) {
|
||||
snd_pcm_hw_params_free(hw_params);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
void *sound_open(const char *device, int samplerate)
|
||||
{
|
||||
sound_t *sound;
|
||||
int rc;
|
||||
|
||||
sound = calloc(1, sizeof(sound_t));
|
||||
if (!sound) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to alloc memory!\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rc = snd_pcm_open(&sound->phandle, device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for playback! (%s)\n", device, snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = snd_pcm_open(&sound->chandle, device, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to open '%s' for capture! (%s)\n", device, snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = set_hw_params(sound->phandle, samplerate, &sound->pchannels);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set playback hw params\n");
|
||||
goto error;
|
||||
}
|
||||
PDEBUG(DSOUND, DEBUG_DEBUG, "Playback with %d channels.\n", sound->pchannels);
|
||||
|
||||
rc = set_hw_params(sound->chandle, samplerate, &sound->cchannels);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "Failed to set capture hw params\n");
|
||||
goto error;
|
||||
}
|
||||
PDEBUG(DSOUND, DEBUG_DEBUG, "Capture with %d channels.\n", sound->cchannels);
|
||||
|
||||
rc = snd_pcm_prepare(sound->phandle);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
rc = snd_pcm_prepare(sound->chandle);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "cannot prepare audio interface for use (%s)\n", snd_strerror(rc));
|
||||
goto error;
|
||||
}
|
||||
|
||||
return sound;
|
||||
|
||||
error:
|
||||
sound_close(sound);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sound_close(void *inst)
|
||||
{
|
||||
sound_t *sound = (sound_t *)inst;
|
||||
|
||||
if (sound->phandle > 0)
|
||||
snd_pcm_close(sound->phandle);
|
||||
if (sound->chandle > 0)
|
||||
snd_pcm_close(sound->chandle);
|
||||
free(sound);
|
||||
}
|
||||
|
||||
int sound_write(void *inst, int16_t *samples_left, int16_t *samples_right, int num)
|
||||
{
|
||||
sound_t *sound = (sound_t *)inst;
|
||||
int16_t buff[num << 1], *samples;
|
||||
int rc;
|
||||
int i, ii;
|
||||
|
||||
if (sound->pchannels == 2) {
|
||||
|
||||
for (i = 0, ii = 0; i < num; i++) {
|
||||
buff[ii++] = *samples_right++;
|
||||
buff[ii++] = *samples_left++;
|
||||
}
|
||||
samples = buff;
|
||||
} else
|
||||
samples = samples_left;
|
||||
|
||||
rc = snd_pcm_writei(sound->phandle, samples, num);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "failed to write audio to interface (%s)\n", snd_strerror(rc));
|
||||
if (rc == -EPIPE)
|
||||
snd_pcm_prepare(sound->phandle);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (rc != num)
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "short write to audio interface, written %d bytes, got %d bytes\n", num, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sound_read(void *inst, int16_t *samples, int num)
|
||||
{
|
||||
sound_t *sound = (sound_t *)inst;
|
||||
int16_t buff[num << 1];
|
||||
int32_t s32;
|
||||
int rc;
|
||||
int i, ii;
|
||||
|
||||
if (sound->cchannels == 2)
|
||||
rc = snd_pcm_readi(sound->chandle, buff, num);
|
||||
else
|
||||
rc = snd_pcm_readi(sound->chandle, samples, num);
|
||||
|
||||
if (rc < 0) {
|
||||
if (errno == EAGAIN)
|
||||
return 0;
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "failed to read audio from interface (%s)\n", snd_strerror(rc));
|
||||
/* recover read */
|
||||
if (rc == -EPIPE)
|
||||
snd_pcm_prepare(sound->chandle);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (sound->cchannels == 2) {
|
||||
for (i = 0, ii = 0; i < rc; i++) {
|
||||
s32 = buff[ii++];
|
||||
s32 += buff[ii++];
|
||||
*samples++ = s32 >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* get playback buffer fill
|
||||
*
|
||||
* return number of frames */
|
||||
int sound_get_inbuffer(void *inst)
|
||||
{
|
||||
sound_t *sound = (sound_t *)inst;
|
||||
int rc;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
rc = snd_pcm_delay(sound->phandle, &delay);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSOUND, DEBUG_ERROR, "failed to get delay from interface (%s)\n", snd_strerror(rc));
|
||||
if (rc == -EPIPE)
|
||||
snd_pcm_prepare(sound->phandle);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
107
src/common/timer.c
Normal file
107
src/common/timer.c
Normal file
@@ -0,0 +1,107 @@
|
||||
/* Timer handling
|
||||
*
|
||||
* (C) 2016 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include "timer.h"
|
||||
|
||||
static struct timer *timer_head = NULL;
|
||||
static struct timer **timer_tail_p = &timer_head;
|
||||
|
||||
double get_time(void)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
|
||||
}
|
||||
|
||||
void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv)
|
||||
{
|
||||
if (timer->linked) {
|
||||
fprintf(stderr, "Timer is already initialized, aborting!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
timer->timeout = 0;
|
||||
timer->fn = fn;
|
||||
timer->priv = priv;
|
||||
timer->next = NULL;
|
||||
*timer_tail_p = timer;
|
||||
timer_tail_p = &timer->next;
|
||||
timer->linked = 1;
|
||||
}
|
||||
|
||||
void timer_exit(struct timer *timer)
|
||||
{
|
||||
timer_tail_p = &timer_head;
|
||||
while (*timer_tail_p) {
|
||||
if (timer == *timer_tail_p)
|
||||
*timer_tail_p = timer->next;
|
||||
timer_tail_p = &timer->next;
|
||||
}
|
||||
timer->linked = 0;
|
||||
}
|
||||
|
||||
void timer_start(struct timer *timer, double duration)
|
||||
{
|
||||
struct timeval tv;
|
||||
|
||||
if (!timer->linked) {
|
||||
fprintf(stderr, "Timer is not initialized, aborting!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
timer->timeout = get_time() + duration;
|
||||
}
|
||||
|
||||
void timer_stop(struct timer *timer)
|
||||
{
|
||||
if (!timer->linked) {
|
||||
fprintf(stderr, "Timer is not initialized, aborting!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
timer->timeout = 0;
|
||||
}
|
||||
|
||||
void process_timer(void)
|
||||
{
|
||||
struct timer *timer = timer_head;
|
||||
double now;
|
||||
|
||||
now = get_time();
|
||||
|
||||
again:
|
||||
while (timer) {
|
||||
if (timer->linked && timer->timeout > 0 && now >= timer->timeout) {
|
||||
timer->timeout = 0;
|
||||
timer->fn(timer);
|
||||
goto again;
|
||||
}
|
||||
timer = timer->next;
|
||||
}
|
||||
}
|
||||
|
16
src/common/timer.h
Normal file
16
src/common/timer.h
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
struct timer {
|
||||
struct timer *next;
|
||||
int linked; /* set is timer is initialized and linked */
|
||||
double timeout;
|
||||
void (*fn)(struct timer *timer);
|
||||
void *priv;
|
||||
};
|
||||
|
||||
double get_time(void);
|
||||
void timer_init(struct timer *timer, void (*fn)(struct timer *timer), void *priv);
|
||||
void timer_exit(struct timer *timer);
|
||||
void timer_start(struct timer *timer, double duration);
|
||||
void timer_stop(struct timer *timer);
|
||||
void process_timer(void);
|
||||
|
Reference in New Issue
Block a user