initial git import

This commit is contained in:
Andreas Eversberg
2016-03-01 18:40:38 +01:00
commit 946c9ce10a
84 changed files with 12959 additions and 0 deletions

16
src/anetz/Makefile.am Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
void print_image(void);

177
src/anetz/main.c Normal file
View 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;
}